mcpField
P
playwright-skill

playwright-skill

github.com/dedalus-erp-pasテスト・品質220 回インストール
リポジトリ

playwright-skill について

Playwright を使ったウェブテストと自動化のためのスキルです。カスタム JavaScript を作成し、サーバー管理からページ確認、操作、検証までを進めます。

主な機能

  1. 01JavaScript によるブラウザー自動化
  2. 02テストサーバーの確認と起動
  3. 03操作前の DOM 調査
  4. 04フォームとナビゲーションのテスト
  5. 05スクリーンショットとネットワーク確認

活用シーン

  1. 01ログインやフォームのフローをテスト
  2. 02複数のビューポートでレイアウトを確認
  3. 03リンク切れや画像の読み込み漏れを検出
  4. 04ウェブページからのデータ抽出を自動化

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dedalus-erp-pas/foundation-skills --skill playwright-skill

インストール後、Claude Code で/playwright-skillを入力して読み込まれたか確認します。

詳細

Playwright による Web テストと自動化

Playwright を使用した包括的な Web テストスキルです。あらゆるテストや自動化タスクに対応するカスタム JavaScript コードを作成できます。

前提条件

  • Node.js:必須です — https://nodejs.org からインストールしてください
  • playwright npm パッケージ:npm install playwright(ヘッドレス Chromium が自動的にインストールされます)

判断ツリー:アプローチの選択

User task → Is server already running?
    ├─ Yes → Direct Testing
    │   ├─ Static HTML? → Navigate directly (file:// or http://)
    │   └─ Dynamic webapp? → Use Reconnaissance-Then-Action pattern
    │
    └─ No → Server Management Required
        ├─ Single server → Start server, then test
        └─ Multiple servers → Start all servers, coordinate testing

重要なワークフロー

  1. サーバーが実行中か確認する - 実行中の開発サーバーを検出するか、必要に応じてサーバーを起動します
  2. スクリプトを /tmp に書き込む - テストファイルをスキルディレクトリに書き込まないでください。必ず /tmp/playwright-test-*.js を使用します
  3. デフォルトで表示ブラウザーを使用する - ユーザーからヘッドレスモードを明示的に指定された場合を除き、常に headless: false を使用します
  4. 動的コンテンツを待つ - 動的な Web アプリを調査する前に、waitForLoadState('networkidle') を使用します
  5. URL をパラメーター化する - スクリプトの先頭にある定数で、常に URL を設定できるようにします

調査してから操作するパターン

DOM の構造が事前に分からない動的な Web アプリの場合:

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

const TARGET_URL = 'http://localhost:3000';

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

  // STEP 1: Navigate and wait for dynamic content
  await page.goto(TARGET_URL);
  await page.waitForLoadState('networkidle'); // CRITICAL for dynamic apps

  // STEP 2: Reconnaissance - discover what's on the page
  await page.screenshot({ path: '/tmp/inspect.png', fullPage: true });
  
  const buttons = await page.locator('button').all();
  console.log(`Found ${buttons.length} buttons`);
  
  for (let i = 0; i < buttons.length; i++) {
    const text = await buttons[i].textContent();
    console.log(`  Button ${i}: "${text}"`);
  }

  // STEP 3: Action - interact with discovered elements
  const loginButton = page.locator('button:has-text("Login")');
  if (await loginButton.isVisible()) {
    await loginButton.click();
    console.log('✅ Clicked login button');
  }

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

サーバー管理

実行中のサーバーを確認する
# Check if port is in use
lsof -i :3000  # Mac/Linux
netstat -ano | findstr :3000  # Windows
テスト前にサーバーを起動する
// /tmp/playwright-test-with-server.js
const { chromium } = require('playwright');
const { spawn } = require('child_process');

const TARGET_URL = 'http://localhost:3000';

(async () => {
  // Start server
  console.log('Starting server...');
  const server = spawn('npm', ['run', 'dev'], { shell: true });
  
  server.stdout.on('data', (data) => console.log(`Server: ${data}`));
  server.stderr.on('data', (data) => console.error(`Server Error: ${data}`));

  // Wait for server to be ready
  await new Promise(resolve => setTimeout(resolve, 3000));

  // Run tests
  const browser = await chromium.launch({ headless: false });
  const page = await browser.newPage();
  
  await page.goto(TARGET_URL);
  await page.waitForLoadState('networkidle');
  
  // Your test logic here
  console.log('Title:', await page.title());
  
  await browser.close();
  
  // Clean up server
  server.kill();
  console.log('✅ Tests complete, server stopped');
})();

よくあるパターン

ページをテストする(複数のビューポート)
// /tmp/playwright-test-responsive.js
const { chromium } = require('playwright');

const TARGET_URL = 'http://localhost:3001'; // Auto-detected

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

  // Desktop test
  await page.setViewportSize({ width: 1920, height: 1080 });
  await page.goto(TARGET_URL);
  console.log('Desktop - Title:', await page.title());
  await page.screenshot({ path: '/tmp/desktop.png', fullPage: true });

  // Mobile test
  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'; // Auto-detected

(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"]', '[email protected]');
  await page.fill('input[name="password"]', 'password123');
  await page.click('button[type="submit"]');

  // Wait for redirect
  await page.waitForURL('**/dashboard');
  console.log('✅ Login successful, redirected to dashboard');

  await browser.close();
})();
フォームに入力して送信する
// /tmp/playwright-test-form.js
const { chromium } = require('playwright');

const TARGET_URL = 'http://localhost:3001'; // Auto-detected

(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"]', '[email protected]');
  await page.fill('textarea[name="message"]', 'Test message');
  await page.click('button[type="submit"]');

  // Verify submission
  await page.waitForSelector('.success-message');
  console.log('✅ Form submitted successfully');

  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(`✅ Working links: ${results.working}`);
  console.log(`❌ Broken links:`, 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('📸 Screenshot saved to /tmp/screenshot.png');
  } catch (error) {
    console.error('❌ Error:', error.message);
  } finally {
    await browser.close();
  }
})();
レスポンシブデザインをテストする
// /tmp/playwright-test-responsive-full.js
const { chromium } = require('playwright');

const TARGET_URL = 'http://localhost:3001'; // Auto-detected

(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(
      `Testing ${viewport.name} (${viewport.width}x${viewport.height})`,
    );
 & Discovery

```javascript
// Check visibility
const isVisible = await page.locator('button').isVisible();

// Get text
const text = await page.locator('h1').textContent();

// Get attribute
const href = await page.locator('a').getAttribute('href');

// Find all elements
const allButtons = await page.locator('button').all();
const allLinks = await page.locator('a').all();

// Check if element exists
const count = await page.locator('.modal').count();
console.log(`Found ${count} modals`);
ネットワークとコンソール
// Intercept requests
await page.route('**/api/**', route => {
  route.fulfill({
    status: 200,
    body: JSON.stringify({ mocked: true })
  });
});

// Wait for response
const response = await page.waitForResponse('**/api/data');
console.log(await response.json());

// Capture console logs
page.on('console', msg => {
  console.log(`Browser console [${msg.type()}]:`, msg.text());
});

ベストプラクティス

✅ 推奨事項
  • 動的アプリでは networkidle を待つ - SPA/React/Vue アプリの DOM を調べる前に、必ず page.waitForLoadState('networkidle') を使用します
  • 調査パターンを使用する - スクリーンショットを撮り、DOM を調べてから、見つかった内容に基づいて操作します
  • デフォルトで表示ブラウザーを使用する - デバッグしやすくするため、headless: false を使用します
  • 分かりやすいセレクター - 壊れやすい CSS セレクターよりも、text=、role=、データ属性を使用します
  • エラー処理 - 自動化処理を try-catch ブロックで囲みます
  • リソースをクリーンアップする - 必ずブラウザーを閉じ、サーバーを停止します
❌ 禁止事項
  • networkidle より前に調べない - 動的な Web アプリでは、DOM の準備がまだ整っていない場合があります
  • 固定タイムアウトを使用しない - 任意の待ち時間ではなく、waitForSelector() または waitForLoadState() を使用します
  • スキルディレクトリに書き込まない - テストスクリプトには必ず /tmp を使用します
  • URL をハードコードしない - 変更しやすいよう、スクリプトの先頭にある定数を使用しますeadless: false, // 表示ブラウザー slowMo: 50 // 50ms 遅くする });

const page = await browser.newPage();

// 移動 await page.goto('https://example.com', { waitUntil: 'networkidle' // ネットワークがアイドル状態になるまで待つ });

// 閉じる await browser.close();


### Selectors & Interactions

```javascript
// Click
await page.click('button.submit');
await page.dblclick('.item');

// Fill input
await page.fill('input[name="email"]', '[email protected]');
await page.getByLabel('Email').fill('[email protected]');

// Checkbox
await page.check('input[type="checkbox"]');
await page.uncheck('input[type="checkbox"]');

// Select dropdown
await page.selectOption('select#country', 'usa');

// Type with delay
await page.type('#username', 'testuser', { delay: 100 });
待機方法
// Wait for navigation
await page.waitForURL('**/dashboard');
await page.waitForLoadState('networkidle');

// Wait for element
await page.waitForSelector('.success-message');
await page.waitForSelector('.spinner', { state: 'hidden' });

// Wait for timeout (use sparingly)
await page.waitForTimeout(1000);
スクリーンショット
// Full page screenshot
await page.screenshot({
  path: '/tmp/screenshot.png',
  fullPage: true
});

// Element screenshot
await page.locator('.chart').screenshot({
  path: '/tmp/chart.png'
});
```Quick Tips

- **Visible browser** - Always `headless: false` unless explicitly requested
- **Write to /tmp** - Scripts go to `/tmp/playwright-test-*.js`, never to project directories
- **Parameterize URLs** - Use `TARGET_URL` constant at top of script
- **Slow down** - Use `slowMo: 100` to see actions in real-time
- **Wait smart** - Use `waitForLoadState('networkidle')` for dynamic apps before inspecting
- **Error handling** - Wrap in try-catch with proper cleanup in finally block
- **Progress feedback** - Use `console.log()` to
const text = await page.locator('h1').textContent();

// Get attribute
const href = await page.locator('a').getAttribute('href');
ネットワーク
// Intercept requests
await page.route('**/api/**', route => {
  route.fulfill({
    status: 200,
    body: JSON.stringify({ mocked: true })
  });
});

// Wait for response
const response = await page.waitForResponse('**/api/data');
console.log(await response.json());

ヒント

  • デフォルト:表示ブラウザー - ユーザーからヘッドレスモードを明示的に指定された場合を除き、常に headless: false を使用します
  • テストファイルには /tmp を使用する - /tmp/playwright-test-*.js に書き込みます。スキルディレクトリやユーザーのプロジェクトには書き込まないでください
  • URL をパラメーター化する - 検出または指定された URL を、すべてのスクリプトの先頭にある TARGET_URL 定数に設定します
  • 動作をゆっくりにする: slowMo: 100 を使用すると、操作が見やすく、追いやすくなります
  • 待機方法: 固定タイムアウトではなく、waitForURL、waitForSelector、waitForLoadState を使用します
  • エラー処理: 堅牢な自動化のため、必ず try-catch を使用します
  • コンソール出力: console.log() を使用して進行状況や実行中の処理を表示します

よくあるユースケース

ビジュアルテスト:

  • さまざまなビューポートでスクリーンショットを撮る
  • 視覚的な変更を比較する
  • レスポンシブデザインをテストする

機能テスト:

  • ログインフローをテストする
  • フォームの検証
  • ナビゲーションフロー
  • エラー処理

検証:

  • リンク切れを確認する
  • 画像が読み込まれることを確認する
  • ページの読み込み時間をテストする
  • アクセシビリティを確認する

自動化:

  • フォームに自動入力する
  • ユーザーフローに沿ってクリックする
  • ページからデータを抽出する
  • レポートを生成する

注意事項

  • 各自動化は、依頼内容に合わせて個別に作成されます
  • 事前に用意されたスクリプトに限定されず、あらゆるブラウザータスクに対応できます
  • 実行中の開発サーバーを自動検出し、URL のハードコーディングを不要にします
  • テストスクリプトは /tmp に作成され、自動的に削除されます(不要なファイルが残りません)
  • プログレッシブディスクロージャーにより、必要な場合にのみ高度なドキュメントを読み込みます

参考資料

コメント