playwright-debugging について
playwright-debuggingは、Playwrightのブラウザー自動化で起きる不具合を体系的に診断するスキルです。不安定なテスト、ロケーターやタイミングの問題、CIでの失敗を調べるテスト作成者向けです。
主な機能
- 01不具合の再現と切り分け
- 02UI ModeとInspectorでのテスト確認
- 03ロケーターと要素状態の確認
- 04スクリーンショット、ログ、トレースの分析
- 05固定待機を条件ベースの待機に置換
活用シーン
- 01断続的なテスト失敗の調査
- 02要素が見つからない・クリックできない問題の診断
- 03CIのみで起きる不具合の分析
- 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 セレクターを使います |
| トレースビューアーを使わない | 発生したことの詳細なタイムラインを見逃します | 失敗するテストではトレースを有効にします |
デバッグチェックリスト
自動化が失敗した場合は、次の順に確認します。
- ☐ 失敗を安定して再現できますか?
- ☐ スローモーションを有効にしたヘッド付きモードでも失敗しますか?
- ☐ 失敗の前後にスクリーンショットを撮りましたか?
- ☐ セレクターは実際に要素に一致していますか?
- ☐ 要素は表示されていて、有効になっていますか?
- ☐ 要素は iframe 内にありますか?
- ☐ ページの読み込みが完了するまで待ちましたか?
- ☐ 読み込みに時間がかかる動的コンテンツがありますか?
- ☐ まだ処理中のネットワークリクエストがありますか?
- ☐ ブラウザーコンソールで JavaScript エラーを確認しましたか?
デバッグツールのリファレンス
| ツール | コマンド | 使用する場面 |
|---|---|---|
| UI モード | --ui | ビジュアルタイムラインで時間をさかのぼってデバッグする場合(ローカル開発に最適) |
| インスペクター | --debug | テストの実行をステップごとに確認し、ロケーターをその場で選ぶ場合 |
| ヘッド付きモード | --headed | ブラウザーを表示する必要がある場合 |
| スローモーション | --slow-mo=1000 | 操作が速すぎて確認できない場合 |
| デバッグモード | PWDEBUG=1 | インスペクターを開く場合(古い方法なので、--debug を推奨) |
| コンソールデバッグ | PWDEBUG=console | playwright オブジェクトを使ってブラウザーの DevTools にアクセスする場合 |
| トレースビューアー | show-trace trace.zip | タイムライン全体を分析する必要がある場合 |
| スクリーンショット | page.screenshot() | 視覚的な証拠が必要な場合 |
| コンソールログ | DEBUG=pw:api | API 呼び出しの詳細が必要な場合 |
| 一時停止 | 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();
覚えておくこと
デバッグの優先事項:
- 問題を確実に再現する
- 可視化する(スクリーンショット、ログ、トレース)
- 要素の状態とセレクターを確認する
- タイミングと待機を確認する
- 異なるモード(ヘッド付き、ブラウザー)でテストする
自動待機の利点: Playwright は、要素が次の状態になるまで自動的に待機します。
- DOM にアタッチされている
- 表示されている
- 有効で安定している
- オーバーレイに覆われていない
ほとんどの操作(クリック、入力など)には自動待機が含まれます。明示的な待機が必要なのは、複雑な条件の場合だけです。
Playwright の問題のほとんどはタイミングに関係しています。任意のタイムアウトを条件に基づく待機に置き換えます。判断に迷ったら、--ui または --debug を使い、ヘッド付きモードで速度を落として観察します。