mcpField
P
playwright-debugging

playwright-debugging

github.com/ed3daiテスト・品質25343 回インストール7 週間前に更新
リポジトリ

playwright-debugging について

playwright-debuggingは、Playwrightのブラウザー自動化で起きる不具合を体系的に診断するスキルです。不安定なテスト、ロケーターやタイミングの問題、CIでの失敗を調べるテスト作成者向けです。

主な機能

  1. 01不具合の再現と切り分け
  2. 02UI ModeとInspectorでのテスト確認
  3. 03ロケーターと要素状態の確認
  4. 04スクリーンショット、ログ、トレースの分析
  5. 05固定待機を条件ベースの待機に置換

活用シーン

  1. 01断続的なテスト失敗の調査
  2. 02要素が見つからない・クリックできない問題の診断
  3. 03CIのみで起きる不具合の分析
  4. 04タイミングやネットワーク問題の追跡

インストール

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

$npx skills add https://github.com/ed3dai/ed3d-plugins --skill playwright-debugging

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

詳細

Playwright のデバッグ

概要

ブラウザー自動化の失敗は、予測可能なカテゴリに分類できます。このスキルでは、問題を迅速に診断して修正するための体系的なアプローチを紹介します。

使用するタイミング

  • 以前は動作していたスクリプトが失敗する
  • テストが断続的に失敗する(不安定)
  • 「Element not found」エラーが発生する
  • タイムアウトエラーが発生する
  • 自動化で予期しない動作が起きる
  • 要素を操作できない

使用しない場合:

  • 新しい自動化を作成する場合(playwright-patterns スキルを使用してください)
  • API またはバックエンドのデバッグ

クイックリファレンス

問題最初の対応
ロケーターのタイムアウト--ui モードで実行し、.count()、.isVisible() で要素の状態を確認する
不安定なテスト(成功することもある)waitForTimeout() を条件に基づく待機に置き換える
「Element not visible」計算済みスタイルを確認し、オーバーレイが消えるまで待機する
ローカルでは動作するが、CI では失敗するwaitForLoadState('networkidle') を使用し、タイムアウトを延長する
要素をクリックできないオーバーレイに覆われていないか確認し、アニメーションの完了を待機する
古い要素ロケーターを保存せず、ナビゲーション後に再取得する

診断フレームワーク

1. 再現して切り分ける

最初のステップ:問題を再現できますか?

// Run single test to isolate issue
npx playwright test path/to/test.spec.js

// Run with headed mode to observe
npx playwright test --headed

// Run with slow motion
npx playwright test --headed --slow-mo=1000

確認事項:

  • 常に失敗しますか、それとも断続的に失敗しますか?
  • すべてのブラウザーで失敗しますか、それとも 1 つだけですか?
  • ヘッドありモードとヘッドレスモードの両方で失敗しますか?
  • 最近何か変更しましたか(サイトの更新、コードの変更など)?
2. 可視性を追加する

対話型デバッグには UI Mode を使用します:

# Best for local development - provides time-travel debugging
npx playwright test --ui

UI Mode では、次のことができます:

  • すべてのアクションを視覚的なタイムラインで確認する
  • ファイルの変更時に再実行するウォッチモードを使用する
  • ネットワークとコンソールのタブを確認する
  • テスト実行を時間をさかのぼって確認する

Inspector を使用してテストをステップ実行します:

# Step through test execution with live browser
npx playwright test --debug

Inspector では、次のことができます:

  • アクションを 1 つずつステップ実行する
  • ブラウザーからロケーターを直接選択する
  • セレクターをその場で編集し、結果を確認する
  • 操作可能性のログを確認する

失敗した時点のスクリーンショットを撮影します:

// Before failing action
await page.screenshot({ path: 'before-action.png', fullPage: true });

// Try action
try {
  await page.click('.button');
} catch (error) {
  await page.screenshot({ path: 'after-error.png', fullPage: true });
  throw error;
}

詳細ログを有効にします:

# API-level debugging
DEBUG=pw:api npx playwright test

# Browser DevTools with playwright object
PWDEBUG=console npx playwright test

PWDEBUG=console を指定すると、DevTools から次の操作ができます:

// In browser console
playwright.$('.selector')      // Query with Playwright engine
playwright.$$('selector')      // Get all matches
playwright.inspect('selector') // Highlight in Elements panel
playwright.locator('selector') // Create locator

トレースビューアーを使用します:

// Record trace
await context.tracing.start({ screenshots: true, snapshots: true });
// ... your test code
await context.tracing.stop({ path: 'trace.zip' });

// View trace
npx playwright show-trace trace.zip

テストのステップでトレースを整理します:

// Group actions in trace viewer
await test.step('Login', async () => {
  await page.fill('input[name="username"]', 'user');
  await page.click('button[type="submit"]');
});

await test.step('Navigate to dashboard', async () => {
  await page.click('a[href="/dashboard"]');
});

わかりやすくするためにロケーターに説明を追加します:

// Descriptions appear in trace viewer and reports
const submitButton = page.locator('#submit').describe('Submit button');
await submitButton.click();

VS Code でのデバッグ:

Playwright VS Code 拡張機能をインストールすると、次のことができます:

  • VS Code でブレークポイントを使ってライブデバッグする
  • 編集中にブラウザーでロケーターをハイライト表示する
  • 「Show Browser」オプションでリアルタイムのフィードバックを得る
  • 任意のテストを右クリックして「Debug Test」を選択する

これにより、デバッグをエディターのワークフローに直接統合できます。

3. 要素の状態を確認する

要素が存在するか確認します:

const element = page.locator('.button');

// Does it exist in DOM?
const count = await element.count();
console.log(`Found ${count} elements`);

// Is it visible?
const isVisible = await element.isVisible();
console.log(`Visible: ${isVisible}`);

// Is it enabled?
const isEnabled = await element.isEnabled();
console.log(`Enabled: ${isEnabled}`);

// Get all attributes
const attrs = await element.evaluate(el => ({
  classes: el.className,
  id: el.id,
  display: window.getComputedStyle(el).display,
  visibility: window.getComputedStyle(el).visibility,
  opacity: window.getComputedStyle(el).opacity
}));
console.log(attrs);
4. セレクターを検証する

ブラウザーのコンソールでセレクターをテストします:

// Use page.evaluate to test selector
const found = await page.evaluate(() => {
  const el = document.querySelector('.button');
  return el ? {
    text: el.textContent,
    visible: el.offsetParent !== null,
    enabled: !el.disabled
  } : null;
});
console.log('Selector test:', found);

一致する要素が複数ないか確認します:

// Are there multiple elements?
const all = await page.locator('.button').all();
console.log(`Found ${all.length} matching elements`);

// Get text of all matches
const texts = await page.locator('.button').allTextContents();
console.log('All matching texts:', texts);

よくある問題と修正方法

問題:要素が見つからない

原因:

  • セレクターが間違っている
  • 要素の読み込みがまだ完了していない
  • 要素が iframe 内にある
  • 要素が動的に作成される

デバッグ手順:

// 1. Check if selector exists at all
const exists = await page.locator('.button').count() > 0;
console.log('Element exists:', exists);

// 2. Wait for element explicitly (modern approach)
await page.locator('.button').waitFor({ timeout: 10000 });
// Or let auto-waiting handle it:
await page.locator('.button').click();

// 3. Check if in iframe
const frame = page.frameLocator('iframe');
await frame.locator('.button').click();

// 4. Dump all matching elements
const all = await page.evaluate(() => {
  return Array.from(document.querySelectorAll('button')).map(el => ({
    text: el.textContent,
    classes: el.className,
    id: el.id
  }));
});
console.log('All buttons on page:', all);
問題:要素が表示されない/クリックできない

原因:

  • 要素が非表示になっている(CSS:display:none、visibility:hidden)
  • 要素が別の要素に覆われている
  • 要素がビューポートの外にある
  • 要素のアニメーションがまだ完了していない

デバッグ手順:

// 1. Check computed styles
const styles = await page.locator('.button').evaluate(el => ({
  display: window.getComputedStyle(el).display,
  visibility: window.getComputedStyle(el).visibility,
  opacity: window.getComputedStyle(el).opacity,
  zIndex: window.getComputedStyle(el).zIndex
}));
console.log('Element styles:', styles);

// 2. Scroll into view
await page.locator('.button').scrollIntoViewIfNeeded();

// 3. Wait for element to be stable (not animating)
await expect(page.locator('.button')).toBeVisible();
await page.waitForTimeout(100); // Brief wait for animation

// 4. Force click if needed (last resort)
await page.locator('.button').click({ force: true });
問題:タイミング/競合状態

原因:

  • ネットワークリクエストが完了していない
  • JavaScript の実行が続いている
  • アニメーションが進行中である
  • 動的コンテンツの読み込み中である

デバッグ手順:

// 1. Wait for network to be idle
await page.goto('https://example.com');
await page.waitForLoadState('networkidle');

// 2. Wait for specific network request
await page.waitForResponse(resp =>
  resp.url().includes('/api/data') && resp.status() === 200
);

// 3. Wait for JavaScript condition
await page.waitForFunction(() =>
  window.dataLoaded === true
);

// 4. Wait for element count to stabilize
await expect(page.locator('.item')).toHaveCount(10);
問題:古い要素の参照

原因:

  • ページが更新または移動された
  • 要素が DOM から削除され、再度追加された
  • 動的コンテンツによって要素が置き換えられた

修正方法:

// DON'T store element handles across navigation
const button = page.locator('.button'); // BAD: might become stale
await page.goto('/other-page');
await button.click(); // ERROR: stale

// DO re-query after navigation
await page.goto('/other-page');
await page.locator('.button').click(); // GOOD: fresh query
問題:フォームが送信されない

原因:

  • JavaScript の検証によって送信が阻止されている
  • イベントリスナーがまだ追加されていない
  • フォームの action が正しく設定されていない

デバッグ手順:

// 1. Verify form state before submit
const formState = await page.evaluate(() => {
  const form = document.querySelector('form');
  return {
    action: form?.action,
    method: form?.method,
    valid: form?.checkValidity()
  };
});
console.log('Form state:', formState);

// 2. Trigger form events manually
await page.fill('input[name="email"]', '[email protected]');
await page.dispatchEvent('input[name="email"]', 'blur');

// 3. Use form.submit() instead of clicking button
await page.evaluate(() => document.querySelector('form').submit());

よくある間違い

間違いなぜ間違っているのか正しい方法
waitForTimeout(5000) を追加するタイミングの問題を覆い隠し、テストを遅くし、信頼性を損ないます条件に基づく待機を使います:expect().toBeVisible()
理由を理解せずに強制クリックするPlaywright の操作可能性チェックを回避してしまいます要素をクリックできない理由を診断し、根本原因を解消します
最新のデバッグツールを使わない問題の診断に時間がかかり、推測に頼ることになりますビジュアルデバッグにはまず --ui または --debug を使います
ヘッド付きモードだけでテストするCI で発生するタイミングの問題を見逃します必ずヘッドレスモードでもテストします
壊れやすいセレクターを使うHTML 構造が変わると動作しなくなりますロールベースまたは data-testid セレクターを使います
トレースビューアーを使わない発生したことの詳細なタイムラインを見逃します失敗するテストではトレースを有効にします

デバッグチェックリスト

自動化が失敗した場合は、次の順に確認します。

  1. ☐ 失敗を安定して再現できますか?
  2. ☐ スローモーションを有効にしたヘッド付きモードでも失敗しますか?
  3. ☐ 失敗の前後にスクリーンショットを撮りましたか?
  4. ☐ セレクターは実際に要素に一致していますか?
  5. ☐ 要素は表示されていて、有効になっていますか?
  6. ☐ 要素は iframe 内にありますか?
  7. ☐ ページの読み込みが完了するまで待ちましたか?
  8. ☐ 読み込みに時間がかかる動的コンテンツがありますか?
  9. ☐ まだ処理中のネットワークリクエストがありますか?
  10. ☐ ブラウザーコンソールで JavaScript エラーを確認しましたか?

デバッグツールのリファレンス

ツールコマンド使用する場面
UI モード--uiビジュアルタイムラインで時間をさかのぼってデバッグする場合(ローカル開発に最適)
インスペクター--debugテストの実行をステップごとに確認し、ロケーターをその場で選ぶ場合
ヘッド付きモード--headedブラウザーを表示する必要がある場合
スローモーション--slow-mo=1000操作が速すぎて確認できない場合
デバッグモードPWDEBUG=1インスペクターを開く場合(古い方法なので、--debug を推奨)
コンソールデバッグPWDEBUG=consoleplaywright オブジェクトを使ってブラウザーの DevTools にアクセスする場合
トレースビューアーshow-trace trace.zipタイムライン全体を分析する必要がある場合
スクリーンショットpage.screenshot()視覚的な証拠が必要な場合
コンソールログDEBUG=pw:apiAPI 呼び出しの詳細が必要な場合
一時停止await page.pause()手動で調査する必要がある場合

不安定さのパターン

不安定:80% の確率で動作する

考えられる原因: 競合状態

対処法:

// Replace arbitrary waits
await page.waitForTimeout(2000); // BAD

// With condition-based waits
await expect(page.locator('.result')).toBeVisible(); // GOOD
不安定:CI では失敗するが、ローカルでは動作する

考えられる原因: タイミングの違い

対処法:

// Increase default timeout for CI
test.setTimeout(60000);
page.setDefaultTimeout(30000);

// Wait for network idle
await page.waitForLoadState('networkidle');
不安定:「element not clickable」で失敗する

考えられる原因: 要素の重なり、またはアニメーション

対処法:

// Wait for element to be actionable
await expect(page.locator('.button')).toBeVisible();
await expect(page.locator('.button')).toBeEnabled();

// Or wait for overlay to disappear
await expect(page.locator('.loading-overlay')).not.toBeVisible();

覚えておくこと

デバッグの優先事項:

  1. 問題を確実に再現する
  2. 可視化する(スクリーンショット、ログ、トレース)
  3. 要素の状態とセレクターを確認する
  4. タイミングと待機を確認する
  5. 異なるモード(ヘッド付き、ブラウザー)でテストする

自動待機の利点: Playwright は、要素が次の状態になるまで自動的に待機します。

  • DOM にアタッチされている
  • 表示されている
  • 有効で安定している
  • オーバーレイに覆われていない

ほとんどの操作(クリック、入力など)には自動待機が含まれます。明示的な待機が必要なのは、複雑な条件の場合だけです。

Playwright の問題のほとんどはタイミングに関係しています。任意のタイムアウトを条件に基づく待機に置き換えます。判断に迷ったら、--ui または --debug を使い、ヘッド付きモードで速度を落として観察します。

コメント