playwright-skill

playwright-skill

熱門

使用 Playwright 提供完整的瀏覽器自動化功能。自動偵測開發伺服器,並將乾淨的測試腳本寫入 /tmp。支援測試頁面、填寫表單、擷取螢幕截圖、檢查響應式設計、驗證 UX(使用者體驗)、測試登入流程、檢查連結,以及自動化執行任何瀏覽器任務。當使用者想要測試網站、自動化瀏覽器互動、驗證網頁功能或進行任何基於瀏覽器的測試時使用。

2933星標
215分支
更新於 2025/12/19
SKILL.md
唯讀
名稱
playwright-skill
描述

使用 Playwright 提供完整的瀏覽器自動化功能。自動偵測開發伺服器,並將乾淨的測試腳本寫入 /tmp。支援測試頁面、填寫表單、擷取螢幕截圖、檢查響應式設計、驗證 UX(使用者體驗)、測試登入流程、檢查連結,以及自動化執行任何瀏覽器任務。當使用者想要測試網站、自動化瀏覽器互動、驗證網頁功能或進行任何基於瀏覽器的測試時使用。

重要說明 - 路徑解析:
此 Skill 可能安裝於不同位置(外掛系統、手動安裝、全域或專案特定)。在執行任何命令之前,請根據載入此 SKILL.md 檔案的位置來確定 Skill 目錄,並在下方的所有命令中使用該路徑。請將 $SKILL_DIR 替換為實際找到的路徑。

常見的安裝路徑:

  • 外掛系統:~/.claude/plugins/marketplaces/playwright-skill/skills/playwright-skill
  • 手動全域安裝:~/.claude/skills/playwright-skill
  • 專案特定安裝:<project>/.claude/skills/playwright-skill

Playwright 瀏覽器自動化

通用型瀏覽器自動化 Skill。我會針對你要求的任何自動化任務撰寫自訂 Playwright 程式碼,並透過通用執行器來執行。

關鍵工作流程 - 請依序執行以下步驟:

  1. 自動偵測開發伺服器 - 進行 localhost 測試時,務必先執行伺服器偵測:

    cd $SKILL_DIR && node -e "require('./lib/helpers').detectDevServers().then(servers => console.log(JSON.stringify(servers)))"
    
    • 找到 1 個伺服器:自動使用該伺服器並通知使用者
    • 找到多個伺服器:詢問使用者要測試哪一個
    • 未找到伺服器:詢問 URL 或提供協助以啟動開發伺服器
  2. 將腳本寫入 /tmp - 切勿將測試檔案寫入 Skill 目錄;請一律使用 /tmp/playwright-test-*.js

  3. 預設使用可見的瀏覽器 - 除非使用者特別要求無頭(headless)模式,否則請一律使用 headless: false

  4. 參數化 URL - 務必在腳本頂端透過環境變數或常數讓 URL 可供設定

運作方式

  1. 你說明想要測試/自動化的內容
  2. 我自動偵測正在執行的開發伺服器(若是測試外部網站則詢問 URL)
  3. 我在 /tmp/playwright-test-*.js 中撰寫自訂 Playwright 程式碼(不會弄亂你的專案)
  4. 我透過以下命令執行:cd $SKILL_DIR && node run.js /tmp/playwright-test-*.js
  5. 實時顯示結果,並顯示瀏覽器視窗以便除錯
  6. 測試檔案會由作業系統自動清理,不留垃圾

安裝與設定(初次使用)

cd $SKILL_DIR
npm run setup

這會安裝 Playwright 和 Chromium 瀏覽器。只需執行一次。

執行模式

步驟 1:偵測開發伺服器(用於 localhost 測試)

cd $SKILL_DIR && node -e "require('./lib/helpers').detectDevServers().then(s => console.log(JSON.stringify(s)))"

步驟 2:將帶有 URL 參數的測試腳本寫入 /tmp

// /tmp/playwright-test-page.js
const { chromium } = require('playwright');

// 參數化 URL(自動偵測或由使用者提供)
const TARGET_URL = 'http://localhost:3001'; // <-- 自動偵測或來自使用者

(async () => {
  const browser = await chromium.launch({ headless: false });
  const page = await browser.newPage();

  await page.goto(TARGET_URL);
  console.log('頁面已載入:', await page.title());

  await page.screenshot({ path: '/tmp/screenshot.png', fullPage: true });
  console.log('📸 螢幕截圖已儲存至 /tmp/screenshot.png');

  await browser.close();
})();

步驟 3:從 Skill 目錄執行

cd $SKILL_DIR && node run.js /tmp/playwright-test-page.js

常見用法範例

測試頁面(多種視埠尺寸)

// /tmp/playwright-test-responsive.js
const { chromium } = require('playwright');

const TARGET_URL = 'http://localhost:3001'; // 自動偵測

(async () => {
  const browser = await chromium.launch({ headless: false, slowMo: 100 });
  const page = await browser.newPage();

  // 桌機測試
  await page.setViewportSize({ width: 1920, height: 1080 });
  await page.goto(TARGET_URL);
  console.log('桌機版 - 標題:', await page.title());
  await page.screenshot({ path: '/tmp/desktop.png', fullPage: true });

  // 行動裝置測試
  await page.setViewportSize({ width: 375, height: 667 });
  await page.screenshot({ path: '/tmp/mobile.png', fullPage: true });

  await browser.close();
})();

測試登入流程

// /tmp/playwright-test-login.js
const { chromium } = require('playwright');

const TARGET_URL = 'http://localhost:3001'; // 自動偵測

(async () => {
  const browser = await chromium.launch({ headless: false });
  const page = await browser.newPage();

  await page.goto(`${TARGET_URL}/login`);

  await page.fill('input[name="email"]', 'test@example.com');
  await page.fill('input[name="password"]', 'password123');
  await page.click('button[type="submit"]');

  // 等待轉址
  await page.waitForURL('**/dashboard');
  console.log('✅ 登入成功,已轉址至儀表板');

  await browser.close();
})();

填寫並提交表單

// /tmp/playwright-test-form.js
const { chromium } = require('playwright');

const TARGET_URL = 'http://localhost:3001'; // 自動偵測

(async () => {
  const browser = await chromium.launch({ headless: false, slowMo: 50 });
  const page = await browser.newPage();

  await page.goto(`${TARGET_URL}/contact`);

  await page.fill('input[name="name"]', 'John Doe');
  await page.fill('input[name="email"]', 'john@example.com');
  await page.fill('textarea[name="message"]', 'Test message');
  await page.click('button[type="submit"]');

  // 驗證提交結果
  await page.waitForSelector('.success-message');
  console.log('✅ 表單已成功提交');

  await browser.close();
})();

檢查失效連結(死連結)

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch({ headless: false });
  const page = await browser.newPage();

  await page.goto('http://localhost:3000');

  const links = await page.locator('a[href^="http"]').all();
  const results = { working: 0, broken: [] };

  for (const link of links) {
    const href = await link.getAttribute('href');
    try {
      const response = await page.request.head(href);
      if (response.ok()) {
        results.working++;
      } else {
        results.broken.push({ url: href, status: response.status() });
      }
    } catch (e) {
      results.broken.push({ url: href, error: e.message });
    }
  }

  console.log(`✅ 正常連結:${results.working}`);
  console.log(`❌ 失效連結:`, results.broken);

  await browser.close();
})();

擷取截圖(含錯誤處理)

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch({ headless: false });
  const page = await browser.newPage();

  try {
    await page.goto('http://localhost:3000', {
      waitUntil: 'networkidle',
      timeout: 10000,
    });

    await page.screenshot({
      path: '/tmp/screenshot.png',
      fullPage: true,
    });

    console.log('📸 螢幕截圖已儲存至 /tmp/screenshot.png');
  } catch (error) {
    console.error('❌ 錯誤:', error.message);
  } finally {
    await browser.close();
  }
})();

測試響應式設計

// /tmp/playwright-test-responsive-full.js
const { chromium } = require('playwright');

const TARGET_URL = 'http://localhost:3001'; // 自動偵測

(async () => {
  const browser = await chromium.launch({ headless: false });
  const page = await browser.newPage();

  const viewports = [
    { name: 'Desktop', width: 1920, height: 1080 },
    { name: 'Tablet', width: 768, height: 1024 },
    { name: 'Mobile', width: 375, height: 667 },
  ];

  for (const viewport of viewports) {
    console.log(
      `正在測試 ${viewport.name} (${viewport.width}x${viewport.height})`,
    );

    await page.setViewportSize({
      width: viewport.width,
      height: viewport.height,
    });

    await page.goto(TARGET_URL);
    await page.waitForTimeout(1000);

    await page.screenshot({
      path: `/tmp/${viewport.name.toLowerCase()}.png`,
      fullPage: true,
    });
  }

  console.log('✅ 所有視埠尺寸均已測試完成');
  await browser.close();
})();

單行即時執行(簡單任務)

對於快速的單次任務,你可以直接在命令列執行程式碼,無需建立檔案:

# 快速擷取螢幕截圖
cd $SKILL_DIR && node run.js "
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
await page.goto('http://localhost:3001');
await page.screenshot({ path: '/tmp/quick-screenshot.png', fullPage: true });
console.log('Screenshot saved');
await browser.close();
"

何時使用單行即時執行 vs 建立檔案:

  • 單行即時執行:快速單次任務(擷取截圖、檢查元素是否存在、取得頁面標題)
  • 建立檔案:複雜測試、響應式設計檢查,以及任何使用者可能想要重複執行的內容

可用的 Helper 函式

lib/helpers.js 中提供的選用公用函式:

const helpers = require('./lib/helpers');

// 偵測正在執行的開發伺服器(關鍵 - 請優先使用此函式!)
const servers = await helpers.detectDevServers();
console.log('Found servers:', servers);

// 安全點擊(含重試機制)
await helpers.safeClick(page, 'button.submit', { retries: 3 });

// 安全輸入(含清除舊文字)
await helpers.safeType(page, '#username', 'testuser');

// 擷取帶有時間戳記的螢幕截圖
await helpers.takeScreenshot(page, 'test-result');

// 處理 Cookie 提示橫幅
await helpers.handleCookieBanner(page);

// 擷取表格資料
const data = await helpers.extractTableData(page, 'table.results');

請參閱 lib/helpers.js 查看完整列表。

自訂 HTTP 標頭

透過環境變數為所有 HTTP 請求設定自訂標頭。適用於:

  • 識別傳送至你後端的自動化流量
  • 取得適合 LLM 閱讀的回應(例如純文字錯誤頁面,而非帶樣式的 HTML)
  • 全域新增身份驗證 Token

設定方式

單一標頭(常見用法):

PW_HEADER_NAME=X-Automated-By PW_HEADER_VALUE=playwright-skill \
  cd $SKILL_DIR && node run.js /tmp/my-script.js

多個標頭(JSON 格式):

PW_EXTRA_HEADERS='{"X-Automated-By":"playwright-skill","X-Debug":"true"}' \
  cd $SKILL_DIR && node run.js /tmp/my-script.js

運作原理

使用 helpers.createContext() 時會自動套用標頭:

const context = await helpers.createContext(browser);
const page = await context.newPage();
// 來自此頁面的所有請求都會包含你的自訂標頭

若腳本直接使用原生的 Playwright API,請使用注入的 getContextOptionsWithHeaders()

const context = await browser.newContext(
  getContextOptionsWithHeaders({ viewport: { width: 1920, height: 1080 } }),
);

進階用法

如需完整的 Playwright API 文件,請參閱 API_REFERENCE.md

  • 選擇器與定位器(Selectors & Locators)的最佳實踐
  • 網路攔截與 API 模擬(Mocking)
  • 身份驗證與工作階段(Session)管理
  • 視覺回歸測試(Visual regression testing)
  • 行動裝置模擬
  • 效能測試
  • 除錯技巧
  • CI/CD 整合

技巧與實用建議

  • 關鍵:優先偵測伺服器 - 針對 localhost 測試撰寫測試程式碼前,務必先執行 detectDevServers()
  • 自訂標頭 - 使用 PW_HEADER_NAME/PW_HEADER_VALUE 環境變數來標示傳送至後端的自動化流量
  • 使用 /tmp 存放測試檔案 - 請寫入至 /tmp/playwright-test-*.js,切勿寫入 Skill 目錄或使用者的專案目錄
  • 將 URL 參數化 - 在每個腳本頂端將偵測到或由使用者提供的 URL 設為 TARGET_URL 常數
  • 預設:顯示瀏覽器視窗 - 除非使用者明確要求無頭模式,否則一律使用 headless: false
  • 無頭模式 - 僅在使用者特別要求「無頭(headless)」或「背景執行」時才使用 headless: true
  • 放慢速度: 使用 slowMo: 100 讓操作過程可視化,更容易追蹤
  • 等待策略: 使用 waitForURLwaitForSelectorwaitForLoadState 代替固定的逾時等待時間
  • 錯誤處理

<!-- truncated for translation batch; full body continues in source -->