browser について
browserは、タスクに応じてagent-browserとPlaywrightスクリプトを使い分ける、ウェブ確認・テスト自動化スキルです。ブラウザーセッションの維持、ページ構造の確認、複合操作に対応します。
主な機能
- 01ページ移動と要素の操作
- 02ARIAページ構造の取得
- 03操作前後のページ状態比較
- 04Playwrightによる複合自動化
- 05ブラウザーセッションの保存と復元
活用シーン
- 01ページ要素やフォームの確認
- 02操作後のページ変化の検証
- 03複数ビューポートのレスポンシブ撮影
- 04複数手順のウェブテスト自動化
MCP 対応
ローカルChromeセッションへの接続にPlaywright MCP Bridge拡張機能を使うと記載されています。MCPサーバーの提供については説明されていません。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ inkeep/ team-skills --skill browserインストール後、Claude Code で/browserを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
.env.exampleAPI_REFERENCE.mdREADME.mdpackage-lock.jsonpackage.jsonrun.jslib/helpers.jslocal-browser.jssession.jsreferences/cdp-tracing.mdlocal-browser.mdscripts/connect-local.jssession-server.js詳細
ブラウザー自動化
エンジンは 2 種類あります。agent-browser(対話型の作業でデフォルト)と Playwright scripts(複合的/プログラムによる操作向け)です。使用するエンジンは、実行する操作に応じて決まります。
エンジンの選択
Agent-browser(対話型の作業でデフォルト)
次の操作が必要な場合は agent-browser を使用します。
- URL に移動してページを読み取る/調べる
- スクリーンショットを撮る(注釈付き、ページ全体を含む)
- クリック、入力、選択、フォーム要素のチェック
- ページ構造を読み取る(要素参照を含む ARIA スナップショット)
- 変更前後の状態を比較する(
diff snapshot、diff screenshot) - 要素からテキストを抽出する
- Cookie/ストレージを簡単に読み取る
- すばやく見た目を確認する(「このページは正しく表示されていますか?」)
- デバイスのエミュレーション、ビューポートの変更
- PDF の生成
認証モード:
| モード | コマンド | 使用する場面 |
|---|---|---|
| 認証なし(新規) | agent-browser open <url> | 公開ページ、開発サーバー、CI/CD |
| プロファイルのコピー | --profile Default | ユーザーのログイン済み状態にすばやくアクセスする場合(起動後は古くなります) |
| 状態ファイル | --state ./auth.json または state save/load | セッション間で認証を維持する場合、共有可能 |
| セッション名 | --session-name myapp | 繰り返し実行するタスクの自動保存/復元 |
| 認証情報保管庫 | auth save/login | 暗号化された認証情報の保存 |
| 自動接続 | --auto-connect | Chrome のライブセッション(--remote-debugging-port=9222 が必要) |
表示モード:
| モード | 方法 | 使用する場面 |
|---|---|---|
| ヘッドレス(デフォルト) | デフォルトの動作 | CI/CD、自動実行、バックグラウンドタスク |
| ヘッドあり | --headed | ユーザーが操作を見たい場合、デバッグ、手動介入(2FA/CAPTCHA) |
| ダッシュボード | agent-browser dashboard | ウィンドウを表示せずに監視する場合、ライブストリーミング |
Playwright scripts(複合操作および機能に制約のある操作向け)
次の 2 つのカテゴリの操作が必要な場合は、Playwright scripts を使用します。
A. 複合オーケストレーション(常に Playwright — アーキテクチャ上必要):
- 複数ステップのフロー中にコンソールのエラー/警告を記録する(ナビゲーション前のイベントリスナー登録)
- 特定の操作中にネットワークリクエストを記録する(リアルタイムのイベントフィルタリング)
- ブラウザーセッションを動画として記録する
- アクセシビリティ監査を実行する(axe-core の注入と構造化された WCAG の結果)
- 複数のブレークポイントでレスポンシブなスクリーンショットを撮る(ビューポートの一括確認)
- スクリーンショットの連続画像から GIF を生成する
- カスタムのステータス、ヘッダー、条件付きルーティングを使ってネットワークリクエストをインターセプト/モックする
- フローの途中でヘッドレスモードとヘッドありモードの間でセッションを引き継ぐ(
handoff()) - キャプチャの前にスクリプトを実行する(スクリーンショット取得パイプライン)
- オーバーレイ/モーダルに対する自動非表示ハンドラーを登録する(
addLocatorHandler)
B. 機能の不足(現在は Playwright。対応後は agent-browser に移行する可能性があります):
- Web コンポーネントの操作でシャドウ DOM を横断する
- 関数に引数を渡して複雑な JS を実行する(
page.evaluate(fn, args)) - iframe の完全なコンテキスト分離(基本的な iframe 操作は agent-browser で可能ですが、eval/スクリーンショットはフレームを対象にしません)
- ネットワークの低速状態をシミュレートする(agent-browser はオフラインのみ対応し、遅延/帯域幅の設定には対応していません)
- Web Vitals/Navigation Timing のパフォーマンス指標を記録する
移行時の確認事項: シャドウ DOM セレクター、引数を渡す eval、iframe のコンテキストスコープ、ネットワークスロットリングについて、agent-browser のリリースを監視してください。対応されたら、このルーティングツリーを更新してください。
Playwright scripts の認証モード:
| モード | 方法 | 使用する場面 |
|---|---|---|
| 認証なし(新規) | デフォルトの node run.js | 公開ページ、開発サーバー |
| 状態ファイル | helpers.loadAuthState(browser, authPath) | 保存済みの storageState から復元する場合 |
| Chrome に接続 | node run.js --connect | MCP Bridge 拡張機能経由のライブセッション(開発マシンのみ) |
| セッションサーバー | node run.js --session start | スクリプトの呼び出しをまたいでブラウザーを維持する場合 |
Agent-browser クイックリファレンス
基本的なワークフロー — agent-browser での操作はすべて、次のパターンに従います。
# 1. Navigate
agent-browser open https://example.com
# 2. Snapshot — get element refs (@e1, @e2, ...)
agent-browser snapshot -i
# 3. Interact using refs
agent-browser fill @e1 "[email protected]"
agent-browser click @e3
# 4. Re-snapshot after DOM changes
agent-browser snapshot -i
バッチ実行 — 1 回の呼び出しで複数のコマンドを連続して実行します。
agent-browser batch "fill @e1 [email protected]" "fill @e2 password123" "click @e3"
差分の確認 — 実行前後の状態を比較します。
agent-browser snapshot -i # take baseline
# ... perform action ...
agent-browser diff snapshot # text diff of ARIA tree (+ additions, - removals)
agent-browser diff screenshot # pixel diff with mismatch %
コマンドの完全なリファレンス(80 以上のコマンド、認証パターン、バッチワークフロー、テンプレート)については、agent-browser スキルを読み込んでください(agent-browser Claude Code プラグインで提供 — セットアップから claude plugin install agent-browser@agent-browser を実行する必要があります)。
Playwright スクリプト — 複合操作
重要 — パスの解決:
このスキルはプラグインシステムを通じてインストールされます。コマンドを実行する前に、この SKILL.md ファイルを読み込んだ場所を基準にスキルディレクトリを特定し、以下のすべてのコマンドでそのパスを使用してください。$SKILL_DIR を実際に特定したパスに置き換えてください。
想定されるプラグインのパス:~/.claude/plugins/marketplaces/inkeep-team-skills/plugins/eng/skills/browser
Playwright のワークフロー
Playwright スクリプトを使った汎用的なブラウザー自動化です。あらゆる複合自動化タスク用にカスタム Playwright コードを作成し、汎用エグゼキューターで実行します。
重要なワークフロー — 次の手順を順番に実行してください:
-
セッションを開始する — デバッグ、反復、複数ステップのフローなど、複数のスクリプトを実行する予定がある場合は、まず永続ブラウザーセッションを開始します。これは、すべての対話型作業におけるデフォルトモードです。
cd $SKILL_DIR && node run.js --session startスクリプトはセッションを自動検出し、新しいブラウザーを起動する(約 2〜3 秒)のではなく、WebSocket 経由で接続します(約 50 ミリ秒)。ログイン状態、Cookie、localStorage は実行間で保持されます。本当に 1 回限りのスクリプトや CI/CD 環境の場合に限り、この手順を省略してください。
-
開発サーバーを自動検出する — localhost のテストでは、サーバー検出を実行します。
cd $SKILL_DIR && node -e "require('./lib/helpers').detectDevServers().then(servers => console.log(JSON.stringify(servers)))"- サーバーが 1 つ見つかった場合:自動的に使用し、ユーザーに通知します
- 複数のサーバーが見つかった場合:どのサーバーをテストするかユーザーに確認します
- サーバーが見つからない場合:URL を尋ねるか、開発サーバーの起動を支援することを提案します
-
スクリプトを /tmp に書き込む — テストファイルをスキルディレクトリに書き込まないでください。必ず
/tmp/playwright-test-*.jsを使用します。 -
URL をパラメーター化する — スクリプトの先頭で、環境変数または定数を使って URL を常に設定可能にします。
-
完了したらセッションを停止する — タスクが完了したら、永続ブラウザーをクリーンアップします。
cd $SKILL_DIR && node run.js --session stopセッションは 10 分間操作がない場合にも自動停止します。
仕組み
- テストまたは自動化したい内容を説明します
- セッションを開始します(
--session start)— 以降のすべてのスクリプトでブラウザーが起動状態を維持します - 実行中の開発サーバーを自動検出します(外部サイトをテストする場合は URL を尋ねます)
- カスタム Playwright コードを
/tmp/playwright-test-*.jsに書き込みます(プロジェクトが散らかりません) cd $SKILL_DIR && node run.js /tmp/playwright-test-*.jsで実行します — セッションに自動接続します- 結果はリアルタイムで表示され、ログイン状態とページは実行間で保持されます
- 完了したらセッションを停止します(
--session stop)。テストファイルは OS によって /tmp から自動的に削除されます
ローカルブラウザーモード(ユーザーの Chrome)
ユーザーの認証情報、Cookie、または拡張機能を使って実際のブラウザーを操作するよう求められた場合は、ヘッドレス Playwright ではなく、ローカルブラウザーコネクターを使用します。
使用する場面: ユーザーに代わってブラウザーで操作するよう指示された場合や、ユーザーの認証済みセッションが必要な場合に使用します。ユーザーのローカルマシンでのみ利用可能です(Docker/サンドボックスでは利用できません)。
前提条件: Chrome が起動しており、Playwright MCP Bridge 拡張機能がインストールされていること。
実行: cd $SKILL_DIR && node scripts/connect-local.js /tmp/my-script.js または cd $SKILL_DIR && node run.js --connect /tmp/my-script.js
読み込み: ルーティングの指針、制限事項、例については references/local-browser.md を読み込みます。
セットアップ(初回のみ)
cd $SKILL_DIR
npm run setup
Playwright と Chromium ブラウザーをインストールします。必要なのは 1 回だけです。
実行パターン
ステップ 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');
// Parameterized URL (detected or user-provided)
const TARGET_URL = 'http://localhost:3001'; // <-- Auto-detected or from user
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto(TARGET_URL);
console.log('Page loaded:', await page.title());
await page.screenshot({ path: '/tmp/screenshot.png', fullPage: true });
console.log('Screenshot saved to /tmp/screenshot.png');
await browser.close();
})();
ステップ 3:スキルディレクトリから実行する
cd $SKILL_DIR && node run.js /tmp/playwright-test-page.js
状態の確認:まず ARIA スナップショット、次にスクリーンショット
ページに何が表示されているかを確認するには、getPageStructure() を使用します。page.screenshot() は使用しないでください。
| 必要な情報 | 使用するもの | 理由 |
|---|---|---|
| ページ上にどのような要素があるか | getPageStructure(page) | テキストベースでコンパクトであり、ロールとセレクターが含まれます |
| ページがどのように見えるか | page.screenshot() | 視覚的なレイアウト、色、間隔を確認できます。ビジョン処理が必要です |
| 正しい要素がレンダリングされたか | getPageStructure(page, { root: '#my-section' }) | スコープを絞った ARIA ツリーで、小さく高速です |
| フォームがアクセシブルか | getPageStructure(page, { interactiveOnly: true }) | インタラクティブな要素だけに絞って確認できます |
| 操作可能な要素(ref ID 付き) | getPageStructureWithRefs(page) | トークン効率のよい操作のために、短い ref ID(e1、e2)を返します |
| 視覚的なレイアウトとインタラクティブな要素をひとつの成果物で確認するには | annotatedScreenshot(page) | 各インタラクティブ要素に番号付きバッジを付けたスクリーンショットです |
| 操作後に何が変わったか | diffPageStructure(page, previousYaml) | ARIA スナップショットの統合差分で、追加・削除・変更された要素を示します |
理由: ARIA スナップショットは、構造化テキストを約 200~400 トークン生成します。スクリーンショットは、ビジョン処理が必要な約 2000~5000 トークン分の Base64 画像データを生成します。10 シナリオの QA 実行では、この差が大きく積み重なります。
それでもスクリーンショットを使用する場合:
- ビジュアルリグレッションの確認(レイアウト、間隔、配置)
- PR 本文に添付する証拠の取得
- CSS/スタイルの問題のデバッグ
- 要素が「存在するか」ではなく、どのように「見えるか」を確認する必要がある場合
観察してから記述する(複数ステップのフロー向け)
3 回以上の操作を含むスクリプト(フォーム送信、複数ページにまたがるフロー、複雑な UI など)では、セレクターを推測しないでください。まず調査してから記述します。
ステップ 1:ページ構造を調査する
// /tmp/playwright-explore.js
const { chromium } = require('playwright');
const helpers = require('./lib/helpers');
const TARGET_URL = 'http://localhost:3001/login';
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto(TARGET_URL);
// Get the actual page structure — roles, names, selectors
const structure = await helpers.getPageStructure(page, { interactiveOnly: true });
console.log('=== Interactive Elements ===');
console.log(JSON.stringify(structure.tree, null, 2));
console.log('\n=== Summary ===');
console.log(JSON.stringify(structure.summary));
await browser.close();
})();
ステップ 2:確認したセレクターを使って実際のスクリプトを記述する
調査結果に含まれるセレクターとロールを使用します。input[name="..."] や button.class-name を推測せず、ARIA ツリーで実際に確認したものを使用してください。
使用する場合: 複数ステップのフォーム、使い慣れていない UI、認証が必要なフロー、セレクターの推測に一度失敗したページ。
省略する場合: 1 回の操作だけを行うスクリプト(スクリーンショットの撮影、タイトルの確認、ページに移動して検証するなど)。
ref を使った操作(トークン効率のよい代替手段)
多くの要素を操作する複数ステップのフローでは、セレクター文字列を手作業でコピーする代わりに、getPageStructureWithRefs() と resolveRef() を使用します。ref ID(e1、e2 など)は短く、間違いも起きにくくなります。
// /tmp/playwright-ref-interact.js
const { chromium } = require('playwright');
const helpers = require('./lib/helpers');
const TARGET_URL = 'http://localhost:3001/login';
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto(TARGET_URL);
// Get structure with ref IDs
const { tree, refMap } = await helpers.getPageStructureWithRefs(page, { interactiveOnly: true });
console.log('Interactive elements:');
tree.forEach(el => console.log(` ${el.ref || '-'} ${el.role}: "${el.name}"`));
// e.g.: e1 textbox: "Email" e2 textbox: "Password" e3 button: "Sign in"
// Interact by ref (resolveRef is async — includes staleness check)
await (await helpers.resolveRef(page, refMap, 'e1')).fill('[email protected]');
await (await helpers.resolveRef(page, refMap, 'e2')).fill('password123');
await (await helpers.resolveRef(page, refMap, 'e3')).click();
await page.waitForURL('**/dashboard');
console.log('Login successful');
await browser.close();
})();
注釈付きスクリーンショット — ページとインタラクティブな要素をひとつの画像で確認できます。
// /tmp/playwright-annotated.js
const { chromium } = require('playwright');
const helpers = require('./lib/helpers');
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto('http://localhost:3001');
const result = await helpers.annotatedScreenshot(page, { path: '/tmp/annotated.png' });
console.log(`Annotated screenshot: ${result.path} (${result.elementCount} elements labeled)`);
console.log('Ref map:', result.refMap);
// Then: Read tool → /tmp/annotated.png to see the visual with numbered badges
await browser.close();
})();
ref とセレクターの使い分け: ref は、同じページ状態から 3 つ以上の要素を操作する複数ステップのフローに最適です。単一の操作や、セレクターの理解が必要な場合(デバッグなど)には、getPageStructure() を直接使用してください。
スナップショットの差分
操作の前後で ARIA ツリーのスナップショットを比較し、何が変わったかを正確に確認します。生の YAML を手作業で比較せずに、操作が期待どおりの効果をもたらしたか検証するのに役立ちます。
// /tmp/playwright-diff.js
const { chromium } = require('playwright');
const helpers = require('./lib/helpers');
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto('http://localhost:3001/settings');
// Take a baseline snapshot
const { yaml: before } = await helpers.getPageStructure(page);
// Perform an action
await page.click('button.save');
await page.waitForLoadState('networkidle');
// Diff — see exactly what changed
const result = await helpers.diffPageStructure(page, before);
if (result.changed) {
console.log('Changes detected:\n', result.diff);
} else {
console.log('No changes — action had no effect');
}
// Chain: use currentYaml as next baseline
// const result2 = await helpers.diffPageStructure(page, result.currentYaml);
await browser.close();
})();
使用する場合: フォームの送信後、ページ遷移後、AJAX 更新後など、ページが変化した(または変化していない)ことを確認する必要があるあらゆる操作の後。行単位の差分形式(追加は +、削除は -、変更なしは )はコンパクトで読みやすいものです。
認証
認証情報
ブラウザスキルは環境変数から認証情報を読み込みます。ブラウザーの自動操作を実行する前に設定してください。
| 環境変数 | 用途 |
|---|---|
BROWSER_AUTH_USER | デフォルトのユーザー名/メールアドレス |
BROWSER_AUTH_PASS | デフォルトのパスワード |
BROWSER_AUTH_TOTP_SECRET | 2FA 用の Base32 エンコードされた TOTP シークレット |
複数サイトの認証情報: 複数のサービスに対して認証するワークフローでは、ドメイン固有の環境変数を使用し、設定がない場合はデフォルトにフォールバックします。
| 環境変数のパターン | 例 |
|---|---|
BROWSER_AUTH_<DOMAIN>_USER | BROWSER_AUTH_GITHUB_USER |
BROWSER_AUTH_<DOMAIN>_PASS | BROWSER_AUTH_GOOGLE_PASS |
BROWSER_AUTH_<DOMAIN>_TOTP_SECRET | BROWSER_AUTH_GITHUB_TOTP_SECRET |
認証対象のドメインに対応する認証情報セットを使用してください。ドメイン固有の環境変数がない場合は、デフォルトのセット(BROWSER_AUTH_USER など)にフォールバックします。
認証ウォールの分類
ブラウザーの自動操作で認証ウォールに遭遇した場合は、分類して適切に対処してください。人間の介入が必要だと判断する前に、自動化可能なカテゴリのすべてを試してください。 この分類は目安であり、厳格なルールではありません。自動化できそうであれば試してください。実際に試して失敗した場合にのみ「突破困難なウォール」と分類してください。
| カテゴリ | 例 | エージェントの対応 | 確度 |
|---|---|---|---|
| 自動化可能 — フォーム入力 | ユーザー名/パスワードでのログイン、登録フォーム、パスワードのリセット | helpers.authenticate(page, { username, password }) でフィールドに入力するか、helpers.safeType() で手動入力します。 | 高 |
| 自動化可能 — TOTP | ログイン後の 2FA コード入力 | helpers.generateTOTP(process.env.BROWSER_AUTH_TOTP_SECRET) を呼び出し、コードフィールドに入力します。 | 高(シークレットが利用可能な場合) |
| 自動化可能 — OAuth/SSO | 「Google/GitHub/Microsoft でサインイン」ボタン、SAML リダイレクト | SSO ボタンをクリックし、IdP へのリダイレクトをたどり、IdP ページで認証情報を入力して、コールバックで戻ります。 | 中(IdP のボット検出によります) |
| 自動化可能 — 同意/バナー | Cookie への同意、OAuth の「許可」ボタン、利用規約への同意 | 同意/許可をクリックします。Cookie には helpers.handleCookieBanner() を使用します。 | 高 |
| 自動化可能 — メール認証 | 「メール内のリンクをクリックしてください」 | エージェントがメールにアクセスできる場合(メールボックスへの API)に限ります。試して、アクセスできない場合はウォールと分類します。 | 低〜中 |
| 試行 — 人間の介入が必要な場合あり | reCAPTCHA v2 のチェックボックス、Cloudflare Turnstile、reCAPTCHA v3(スコアベース) | クリック/操作します。プロファイルが十分に使い込まれていれば通過できる場合があります。画像チャレンジに進んだ場合はウォールと分類します。 | 30〜50% |
| 突破困難なウォール — 人間の介入が必要 | hCaptcha、reCAPTCHA の画像チャレンジ、SMS/プッシュ MFA、WebAuthn/パスキー、企業向けボット対策(Akamai、PerimeterX、DataDome) | 監視モードでは、helpers.handoff(page, { reason, successUrl }) を呼び出します。ヘッドレスモードでは、内容を記録してスキップします。 | 自動化は約 0% |
引き継ぎ(最終手段 — 監視モードのみ)
監視モードで突破困難なウォールに遭遇した場合、helpers.handoff() は人間がブロックを解消できるようヘッド付きブラウザーを開き、その後ヘッドレスに戻って処理を再開します。
const { page: newPage, context: newCtx } = await helpers.handoff(page, {
reason: 'hCaptcha on login page',
successUrl: '/dashboard',
timeout: 120000
});
// newPage is authenticated — continue testing
引き継ぎでは、セッションモード(共有デーモン)かスタンドアロンモードかを自動的に検出し、それぞれに適切に対応します。ヘッドレス/CI モードでは決して handoff を呼び出さないでください。ウォールの内容を記録して先に進みます。
よくあるパターン
ページをテストする(複数のビューポート)
// /tmp/playwright-test-responsive.js
const { chromium } = require('playwright');
const TARGET_URL = 'http://localhost:3001'; // Auto-detected
(async () => {
const browser = await chromium.launch({ headless: true });
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: true });
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();
})();
認証済みページをテストする
一度ログインしてセッションを保存し、複数回のテスト実行で再利用します。毎回ログインし直す必要がなくなります。
ステップ 1:ログインして認証状態を保存する(1 回実行)
// /tmp/playwright-auth-save.js
const { chromium } = require('playwright');
const helpers = require('./lib/helpers');
const TARGET_URL = 'http://localhost:3001/login';
(async () => {
const browser = await chromium.launch({ headless: true });
const context = await helpers.createContext(browser);
const page = await context.newPage();
await page.goto(TARGET_URL);
await helpers.authenticate(page, {
username: '[email protected]',
password: 'password123'
});
// Save session for reuse
const saved = await helpers.saveAuthState(context);
console.log('Auth state saved:', saved.path);
console.log(` ${saved.cookies} cookies, ${saved.origins} origins`);
// For Firebase/Supabase/modern auth that stores tokens in IndexedDB:
// const saved = await helpers.saveAuthState(context, '/tmp/auth.json', { indexedDB: true });
await browser.close();
})();
ステップ 2:後続のテストで保存した認証状態を再利用する
// /tmp/playwright-test-dashboard.js
const { chromium } = require('playwright');
const helpers = require('./lib/helpers');
const TARGET_URL = 'http://localhost:3001/dashboard';
(async () => {
const browser = await chromium.launch({ headless: true });
// Load saved auth — skips login entirely
const context = await helpers.loadAuthState(browser);
const page = await context.newPage();
await page.goto(TARGET_URL);
console.log('Page title:', await page.title());
// You're now on the authenticated dashboard
await page.screenshot({ path: '/tmp/dashboard.png', fullPage: true });
await browser.close();
})();
起動中の Chrome から認証情報を抽出する
認証が必要なページをテストしたいものの、スクリプトに認証情報をハードコードしたくない場合や、アプリで自動化が難しい SSO、OAuth、2FA が使われている場合に使用します。すでにログイン済みの Chrome に接続してセッションを抽出し、任意のヘッドレススクリプトで再利用します。
必要条件: Chrome が起動していること、および Playwright MCP Bridge 拡張機能がインストールされていること。
ステップ 1:起動中の Chrome から認証情報を抽出する(1 回だけ実行)
// /tmp/playwright-extract-auth.js
const { connectToLocalBrowser, extractAuthState } = require('./lib/local-browser');
(async () => {
const conn = await connectToLocalBrowser();
// Extracts cookies + localStorage from Chrome's current session
await extractAuthState(conn.context, { path: '/tmp/auth-state.json' });
console.log('Auth extracted to /tmp/auth-state.json');
// For Firebase/Supabase/modern auth stored in IndexedDB:
// await extractAuthState(conn.context, { path: '/tmp/auth-state.json', indexedDB: true });
await conn.close();
})();
実行:cd $SKILL_DIR && node run.js --connect /tmp/playwright-extract-auth.js
ステップ 2:抽出した認証情報をヘッドレススクリプトで再利用する
// /tmp/playwright-test-with-chrome-auth.js
const { chromium } = require('playwright');
const helpers = require('./lib/helpers');
const TARGET_URL = 'http://localhost:3001/dashboard';
(async () => {
const browser = await chromium.launch({ headless: true });
// Load Chrome's session — no credentials needed
const context = await helpers.loadAuthState(browser, '/tmp/auth-state.json');
const page = await context.newPage();
await page.goto(TARGET_URL);
console.log('Page title:', await page.title());
await browser.close();
})();
「認証済みページをテストする」との使い分け:
| シナリオ | 使用する方法 |
|---|---|
| 認証情報があり、自己完結型のスクリプトを作成したい | 「認証済みページをテストする」(ログイン → saveAuthState) |
| Chrome にすでにログイン済みで、そのセッションを再利用したい | このパターン(extractAuthState → loadAuthState) |
| アプリでスクリプト化が難しい SSO、OAuth、2FA が使われている | このパターン |
認証状態のうち、確実に引き継がれるもの(ほとんどのセッション Cookie と JWT)と引き継がれないもの(Google DBSC、Cloudflare、WebAuthn)については、references/local-browser.md を参照してください。
フォームへの入力と送信
// /tmp/playwright-test-form.js
const { chromium } = require('playwright');
const TARGET_URL = 'http://localhost:3001'; // Auto-detected
(async () => {
const browser = await chromium.launch({ headless: true });
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();
})();
ネットワークリクエストの検査
// /tmp/playwright-test-network.js
const { chromium } = require('playwright');
const TARGET_URL = 'http://localhost:3001';
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
// Capture all API requests
const apiRequests = [];
page.on('request', request => {
if (request.url().includes('/api/')) {
apiRequests.push({
method: request.method(),
url: request.url(),
headers: request.headers()
});
}
});
page.on('response', response => {
if (response.url().includes('/api/')) {
console.log(`${response.status()} ${response.url()}`);
}
});
await page.goto(TARGET_URL);
await page.waitForLoadState('networkidle');
console.log('API requests captured:', JSON.stringify(apiRequests, null, 2));
await browser.close();
})();
JavaScript の挿入
// /tmp/playwright-test-js-inject.js
const { chromium } = require('playwright');
const TARGET_URL = 'http://localhost:3001';
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto(TARGET_URL);
// Inject and execute JavaScript
const result = await page.evaluate(() => {
return {
title: document.title,
links: document.querySelectorAll('a').length,
meta: Array.from(document.querySelectorAll('meta')).map(m => ({
name: m.getAttribute('name'),
content: m.getAttribute('content')
})).filter(m => m.name),
localStorage: Object.keys(window.localStorage),
cookies: document.cookie
};
});
console.log('Page analysis:', JSON.stringify(result, null, 2));
await browser.close();
})();
リンク切れの確認
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
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: true });
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: true });
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})`,
);
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('All viewports tested');
await browser.close();
})();
フロー中のコンソールエラーを監視する
UI フローで表面化していない JS エラーが発生しないことを確認する場合に使用します。
// /tmp/playwright-test-console.js
const { chromium } = require('playwright');
const helpers = require('./lib/helpers');
const TARGET_URL = 'http://localhost:3001';
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
// Start capturing BEFORE navigation
const consoleLogs = helpers.startConsoleCapture(page);
await page.goto(TARGET_URL);
await page.waitForLoadState('networkidle');
// Interact with the page
await page.click('button.submit').catch(() => {});
await page.waitForTimeout(1000);
// Check for errors
const errors = helpers.getConsoleErrors(consoleLogs);
if (errors.length > 0) {
console.log(`FAIL: ${errors.length} console error(s):`);
errors.forEach(e => console.log(` [${e.type}] ${e.text}`));
} else {
console.log('PASS: No console errors');
}
// Optionally filter for specific logs
const apiLogs = helpers.getConsoleLogs(consoleLogs, /api|fetch/i);
console.log(`API-related logs: ${apiLogs.length}`);
await browser.close();
})();
UI フロー中のネットワークリクエストを検証する
適切なステータスコードで正しい API 呼び出しが行われることを確認する場合に使用します。
// /tmp/playwright-test-network-verify.js
const { chromium } = require('playwright');
const helpers = require('./lib/helpers');
const TARGET_URL = 'http://localhost:3001';
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
// Capture only API requests
const network = helpers.startNetworkCapture(page, '/api/');
await page.goto(`${TARGET_URL}/dashboard`);
await page.waitForLoadState('networkidle');
// Check for failed API calls
const failed = helpers.getFailedRequests(network);
if (failed.length > 0) {
console.log(`FAIL: ${failed.length} failed API request(s):`);
failed.forEach(r => console.log(` ${r.method} ${r.url} -> ${r.status || r.failure}`));
} else {
console.log('PASS: All API requests succeeded');
}
// Review all captured requests
const all = helpers.getCapturedRequests(network);
console.log(`Total API requests: ${all.length}`);
all.forEach(r => console.log(` ${r.status} ${r.method} ${r.url}`));
await browser.close();
})();
フローを動画で記録する
複数ステップにわたるブラウザー操作を記録する場合に使用します。
// /tmp/playwright-test-video.js
const { chromium } = require('playwright');
const helpers = require('./lib/helpers');
const TARGET_URL = 'http://localhost:3001';
(async () => {
const browser = await chromium.launch({ headless: true });
const context = await helpers.createVideoContext(browser, {
outputDir: '/tmp/playwright-videos'
});
const page = await context.newPage();
await page.goto(TARGET_URL);
await page.click('nav a:first-child');
await page.waitForTimeout(1000);
await page.click('button.submit').catch(() => {});
await page.waitForTimeout(1000);
// Video is saved when page closes
const videoPath = await page.video().path();
await page.close();
await context.close();
console.log(`Video saved: ${videoPath}`);
await browser.close();
})();
変更後のブラウザー状態を検査する
UI 操作によってデータが正しく保存されたことを確認する場合に使用します。
// /tmp/playwright-test-state.js
const { chromium } = require('playwright');
const helpers = require('./lib/helpers');
const TARGET_URL = 'http://localhost:3001';
(async () => {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
const page = await context.newPage();
await page.goto(TARGET_URL);
// Check state before action
const storageBefore = await helpers.getLocalStorage(page);
console.log('localStorage before:', JSON.stringify(storageBefore));
const cookies = await helpers.getCookies(context);
console.log('Cookies:', cookies.map(c => `${c.name}=${c.value}`));
// Perform some action that should change state
await page.click('button.save-preferences').catch(() => {});
await page.waitForTimeout(500);
// Check state after action
const storageAfter = await helpers.getLocalStorage(page);
console.log('localStorage after:', JSON.stringify(storageAfter));
// Clean up for next test
await helpers.clearAllStorage(page);
await browser.close();
})();
ページ構造を調べる
ページの DOM 構造がわからない場合、たとえばサードパーティーのサイト、認証が必要なダッシュボード、不慣れな UI で使用します。操作を記述する前に ARIA スナップショットを取得し、適切なセレクターを見つけます。
yaml(階層構造を保持した生の ARIA スナップショット文字列)、tree(推奨セレクターを含む解析済みノード)、summary(ロールの種類ごとの件数)を返します。
// /tmp/playwright-test-discover.js
const { chromium } = require('playwright');
const helpers = require('./lib/helpers');
const TARGET_URL = 'http://localhost:3001';
(async () => {
const browser = await chromium.launch({ headless: true });
const context = await helpers.createContext(browser);
const page = await context.newPage();
await page.goto(TARGET_URL, { waitUntil: 'networkidle' });
// Get full page structure
const structure = await helpers.getPageStructure(page);
console.log('Page:', structure.title);
console.log('Elements:', JSON.stringify(structure.summary));
// Raw YAML preserves nesting — useful for understanding page hierarchy
console.log('ARIA snapshot:\n', structure.yaml);
// Parsed tree has suggested selectors for each element
console.log('Interactive elements:');
structure.tree.filter(el =>
['button','link','textbox','checkbox','combobox'].includes(el.role)
).forEach(el => console.log(` ${el.role}: "${el.name}" → ${el.selector}`));
// Scope to a specific section
const formElements = await helpers.getPageStructure(page, {
interactiveOnly: true,
root: 'form'
});
console.log('Form inputs:', JSON.stringify(formElements.tree, null, 2));
await browser.close();
})();
視覚的な確認(ページを見る)
最終的なスクリーンショットの撮影前、調査中、操作後、または UI の状態確認など、ページがどのように見えるかを目で確認する場合に使用します。これは自身で状況を把握するためのもので、出力用ではありません。
手順:一時的なスクリーンショットを撮影してから、それを読み込みます。
// /tmp/playwright-test-inspect.js
const { chromium } = require('playwright');
const helpers = require('./lib/helpers');
const TARGET_URL = 'http://localhost:3001';
(async () => {
const browser = await chromium.launch({ headless: true });
const context = await helpers.createContext(browser);
const page = await context.newPage();
await page.goto(`${TARGET_URL}/dashboard`, { waitUntil: 'networkidle' });
// Take a quick screenshot to see the page
await page.screenshot({ path: '/tmp/inspect.png' });
// Inspect a specific section
const section = page.locator('.settings-panel');
await section.screenshot({ path: '/tmp/inspect-section.png' });
await browser.close();
})();
スクリプトの実行後、画像ファイルを読み込んでページの見た目を確認します。
Read tool → /tmp/inspect.png
Claude は PNG ファイルを視覚的に表示するため、実際のページレイアウト、コンテンツ、ポップアップ、読み込み状態、問題点を確認できます。
getPageStructure() との使い分け:
| 必要なこと | 使用する方法 |
|---|---|
| セレクターを見つけ、DOM の階層構造を把握する | getPageStructure()(テキスト。より高速で正確) |
| ページの実際の見た目を確認する | 視覚的な確認(スクリーンショット。レイアウト、色、オーバーレイ、レンダリング) |
| 両方(不慣れなページ) | 両方実行します。まず構造からセレクターを見つけ、次にスクリーンショットで視覚的な結果を確認します |
ヒント: ページの調査やスクリプト実行前のデバッグなど、反復作業では永続セッションを使うと、毎回ブラウザーを起動し直す必要がありません。スクリーンショットファイルは実行のたびに上書きされます。
ドキュメント用のスクリーンショットを撮影する
ドキュメント、ヘルプ記事、PR 用のスクリーンショットを作成する際に、実行中の UI を一貫した高品質な画像で記録したい場合に使用します。
// /tmp/playwright-test-doc-screenshot.js
const { chromium } = require('playwright');
const TARGET_URL = 'http://localhost:3001';
(async () => {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
viewport: { width: 1280, height: 720 },
deviceScaleFactor: 2, // Retina clarity
});
const page = await context.newPage();
await page.goto(`${TARGET_URL}/settings`);
await page.waitForLoadState('networkidle');
// Crop to the relevant section — avoid full-page captures with empty space
const section = page.locator('.api-keys-section');
await section.screenshot({
path: '/tmp/doc-settings-api-keys.png',
type: 'png',
});
// Full-page fallback when you need the whole view
await page.screenshot({
path: '/tmp/doc-settings-full.png',
type: 'png',
fullPage: false, // Viewport-only — keep it tight
});
console.log('Doc screenshots saved to /tmp/doc-*.png');
await browser.close();
})();
ドキュメント用スクリーンショットの主な設定:
viewport: { width: 1280, height: 720 }— ドキュメントで標準的な幅deviceScaleFactor: 2— 鮮明なテキストを表示する Retina 解像度type: 'png'— UI スクリーンショットに適したロスレス形式- ページ全体ではなく特定のパネルを切り抜くには、
element.screenshot()を使用します - 画像ごとに 200KB 未満を目標にし、大胆に切り抜きます
メディアアセットのパイプライン
対象に適したプリセットと変換方法を選択してください。プリセットによってビューポートと DPR が自動的に設定されるため、手動で設定する必要はありません。
| 対象 | プリセット | 出力 | 最大サイズ | 理由 |
|---|---|---|---|---|
| ドキュメントサイトのスクリーンショット | docs-retina | 2560×1440 PNG | <500 KB | Next.js Image で Retina 品質の鮮明さを実現します |
| GitHub PR のスクリーンショット | pr-standard | 1280×720 PNG | <200 KB | GitHub の表示幅 894px で鮮明に表示されます。CDN URL を使うには /media-upload スキルでアップロードしてください |
| GitHub PR の GIF | gif-compact | 800×450 アニメーション GIF | <10 MB | DPR 1 が適切です。GIF ではピクセル密度ではなく、256 色のパレットがボトルネックになります。CDN URL を使うには /media-upload スキルでアップロードしてください |
| 動画(社内向けまたは顧客向け) | video | 2560×1440 WebM → Bunny または Vimeo | — | /media-upload スキルでアップロードしてください。どちらのプラットフォームも ABR にトランスコードします。動画には DPR 1 が適切です |
プリセットを使ってドキュメント品質のスクリーンショットを撮影する:
// /tmp/playwright-test-preset-screenshot.js
const { chromium } = require('playwright');
const helpers = require('./lib/helpers');
const TARGET_URL = 'http://localhost:3001';
(async () => {
const browser = await chromium.launch({ headless: true });
// Preset sets viewport 1280x720 + DPR 2 → 2560x1440 output
const context = await helpers.createPresetContext(browser, 'docs-retina');
const page = await context.newPage();
await page.goto(`${TARGET_URL}/settings`);
await page.waitForLoadState('networkidle');
// Element-level crop for tight framing
const section = page.locator('.api-keys-section');
await section.screenshot({ path: '/tmp/doc-api-keys.png', type: 'png' });
console.log('Docs screenshot: 2560x1440 Retina PNG');
await browser.close();
})();
PR 用のステップごとの GIF を作成する:
// /tmp/playwright-test-pr-gif.js
const { chromium } = require('playwright');
const helpers = require('./lib/helpers');
const TARGET_URL = 'http://localhost:3001';
(async () => {
const browser = await chromium.launch({ headless: true });
// gif-compact: 800x450 @ DPR 1 — optimized for GitHub's 10MB limit
const context = await helpers.createPresetContext(browser, 'gif-compact');
const page = await context.newPage();
const frames = [];
// Frame 1: Starting state
await page.goto(`${TARGET_URL}/settings`);
await page.waitForLoadState('networkidle');
frames.push(await page.screenshot({ type: 'png' }));
// Frame 2: Click action
await page.click('button.save');
await page.waitForTimeout(500);
frames.push(await page.screenshot({ type: 'png' }));
// Frame 3: Success state
await page.waitForSelector('.success-toast');
frames.push(await page.screenshot({ type: 'png' }));
// Assemble GIF — 3 frames at 2fps = 1.5s loop
const result = await helpers.screenshotsToGif(frames, '/tmp/pr-demo.gif', {
width: 800, height: 450, fps: 2
});
console.log(`GIF: ${result.path} (${result.sizeMB} MB, ${result.frames} frames)`);
await browser.close();
})();
クリックインジケーターとステップラベルを付けた注釈付き GIF:
// /tmp/playwright-test-annotated-gif.js
const { chromium } = require('playwright');
const helpers = require('./lib/helpers');
const TARGET_URL = 'http://localhost:3001';
(async () => {
const browser = await chromium.launch({ headless: true });
const context = await helpers.createPresetContext(browser, 'gif-compact');
const page = await context.newPage();
const frames = [];
const annotations = [];
// Frame 1: Navigate to page
await page.goto(TARGET_URL);
frames.push(await page.screenshot({ type: 'png' }));
annotations.push({ label: 'Step 1: Open login page' });
// Frame 2: Click username field
await page.click('#username');
frames.push(await page.screenshot({ type: 'png' }));
annotations.push({ label: 'Step 2: Click username', click: { x: 640, y: 300 } });
// Frame 3: Type credentials
await page.fill('#username', 'admin');
frames.push(await page.screenshot({ type: 'png' }));
annotations.push({ label: 'Step 3: Enter username' });
// Frame 4: Click submit
await page.click('button[type="submit"]');
frames.push(await page.screenshot({ type: 'png' }));
annotations.push({ label: 'Step 4: Submit', click: { x: 640, y: 400 } });
const result = await helpers.screenshotsToGif(frames, '/tmp/login-demo.gif', {
width: 800, height: 450, fps: 2,
annotations
});
console.log(`Annotated GIF: ${result.path} (${result.sizeMB} MB, ${result.frames} frames)`);
await browser.close();
})();
アクセシビリティ監査を実行する
WCAG 違反がないかページを確認する場合に使用します。
// /tmp/playwright-test-a11y.js
const { chromium } = require('playwright');
const helpers = require('./lib/helpers');
const TARGET_URL = 'http://localhost:3001';
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto(TARGET_URL);
await page.waitForLoadState('networkidle');
const audit = await helpers.runAccessibilityAudit(page);
console.log(`Accessibility audit: ${audit.violationCount} violation(s), ${audit.passes} passes`);
if (audit.violationCount > 0) {
console.log('\nViolations:');
audit.summary.forEach(v => {
console.log(` [${v.impact}] ${v.id}: ${v.description} (${v.nodes} element(s))`);
console.log(` Help: ${v.helpUrl}`);
});
}
// Test keyboard focus order
const focusOrder = await helpers.checkFocusOrder(page, [
'a[href]:first-of-type',
'nav a:nth-child(2)',
'input[type="search"]'
]);
focusOrder.forEach(f => {
console.log(` Tab ${f.step}: expected ${f.expectedSelector} -> ${f.matches ? 'PASS' : 'FAIL'}`);
});
await browser.close();
})();
ダイアログとオーバーレイを処理する
ページに alert()/confirm()/prompt() ダイアログや、操作を妨げるオーバーレイ(Cookie バナー、モーダル)がある場合に使用します。
// /tmp/playwright-test-dialogs.js
const { chromium } = require('playwright');
const helpers = require('./lib/helpers');
const TARGET_URL = 'http://localhost:3001';
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
// Auto-accept all dialogs (call BEFORE navigating)
const dialogLog = helpers.handleDialogs(page);
// Auto-dismiss cookie banners and common overlays
await helpers.dismissOverlays(page);
await page.goto(TARGET_URL);
await page.click('button.delete'); // triggers confirm()
// Check what dialogs appeared
console.log('Dialogs captured:', dialogLog.dialogs.length);
dialogLog.dialogs.forEach(d =>
console.log(` ${d.type}: "${d.message}"`)
);
// Custom overlay patterns (beyond the defaults)
await helpers.dismissOverlays(page, [
{ locator: '.onboarding-modal .close-btn', action: 'click' },
{ locator: '.promo-popup', action: 'remove' } // remove from DOM entirely
]);
await browser.close();
})();
トレースでデバッグする
フローが失敗し、各ステップで何が起きたか(DOM の状態、スクリーンショット、ネットワーク、コンソール)を正確に把握する必要がある場合に使用します。Playwright Trace Viewer で表示できる .zip を生成します。
// /tmp/playwright-test-trace.js
const { chromium } = require('playwright');
const helpers = require('./lib/helpers');
const TARGET_URL = 'http://localhost:3001';
(async () => {
const browser = await chromium.launch({ headless: true });
const context = await helpers.createContext(browser);
// Start tracing BEFORE creating pages
await helpers.startTracing(context);
const page = await context.newPage();
await page.goto(TARGET_URL);
await page.click('button.submit');
await page.waitForSelector('.result');
// Stop and save trace
const trace = await helpers.stopTracing(context, '/tmp/trace.zip');
console.log(`Trace saved: ${trace.path}`);
console.log('View with: npx playwright show-trace /tmp/trace.zip');
await browser.close();
})();
PDF を生成する
ドキュメント、レポート、印刷用出力など、ページを PDF にエクスポートする必要がある場合に使用します。Chromium のヘッドレスモードのみ対応しています。
// /tmp/playwright-test-pdf.js
const { chromium } = require('playwright');
const helpers = require('./lib/helpers');
const TARGET_URL = 'http://localhost:3001/report';
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto(TARGET_URL, { waitUntil: 'networkidle' });
// Basic PDF
const result = await helpers.generatePdf(page, '/tmp/report.pdf');
console.log('PDF saved:', result.path);
// Accessible PDF with bookmarks
await helpers.generatePdf(page, '/tmp/report-accessible.pdf', {
tagged: true, // accessible/tagged PDF
outline: true, // document outline from headings
format: 'Letter',
margin: { top: '1cm', bottom: '1cm', left: '1cm', right: '1cm' }
});
await browser.close();
})();
ファイルをダウンロードする
ボタンまたはリンクからファイルがダウンロードされ、そのファイルを保存または確認する必要がある場合に使用します。
// /tmp/playwright-test-download.js
const { chromium } = require('playwright');
const helpers = require('./lib/helpers');
const TARGET_URL = 'http://localhost:3001/exports';
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto(TARGET_URL);
// Trigger download and save
const file = await helpers.waitForDownload(
page,
() => page.click('#export-csv'), // action that triggers the download
'/tmp/export.csv' // optional save path
);
console.log(`Downloaded: ${file.suggestedFilename} → ${file.path}`);
await browser.close();
})();
トークン効率モード(@playwright/cli)
大量の QA 実行(ブラウザーシナリオを 5 件以上連続で実行する場合)では、トークン消費を抑えるために @playwright/cli を使用してください。スナップショット、スクリーンショット、ログを LLM のコンテキストではなくディスクに書き込みます。タスクあたり約 27K トークンで、インライン出力を使う場合の約 114K トークンと比べて 4 分の 1 に削減できます。
セットアップ(一度だけ):
npm install -g @playwright/cli@latest
使用パターン:
# Open a page (starts persistent session)
playwright-cli open http://localhost:3001
# Get page structure to disk (reads from .playwright-cli/ dir)
playwright-cli snapshot
# Interact
playwright-cli click "Login"
playwright-cli fill "Email" "[email protected]"
playwright-cli press Enter
# Take screenshot to disk
playwright-cli screenshot
# Read console/network to disk
playwright-cli console
playwright-cli network
その後、必要に応じて .playwright-cli/ から保存したファイルを読み込みます。現在のアサーションに関連するものだけを読み込んでください。
使用する場合: トークン予算が重要な、複数シナリオの QA 実行。特に、委任モードで 10 件以上のシナリオを指定して /qa を実行する場合に有効です。
通常モードを使用する場合: 単一のシナリオ、デバッグ(インライン出力が必要な場合)、プログラムから Playwright API にアクセスする必要があるシナリオ(複雑なアサーション、カスタム待機)。
マルチユーザーフローのテストには名前付きセッションを使用します。
playwright-cli -s=admin open http://localhost:3001
playwright-cli -s=user open http://localhost:3001
playwright-cli -s=admin click "Approve"
playwright-cli -s=user snapshot # verify the user sees the approval
注: @playwright/cli は v0.1.1(Apache-2.0、1.0 より前)です。スクリプトベースの通常モードが引き続き主要な実行方法です。大量実行を最適化する場合に使用してください。
インライン実行(簡単なタスク)
手早く一度だけ実行するタスクでは、ファイルを作成せずにコードをインラインで実行できます。
# Take a quick screenshot
cd $SKILL_DIR && node run.js "
const browser = await chromium.launch({ headless: true });
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();
"
インラインとファイルの使い分け:
- インライン:手早く一度だけ実行するタスク(スクリーンショットの撮影、要素の存在確認、ページタイトルの取得)
- ファイル:複雑なテスト、レスポンシブデザインの確認、ユーザーが再実行する可能性のあるもの
セッションモード(永続ブラウザー)— デフォルト
セッションモードは、すべてのインタラクティブなブラウザー自動化で推奨されるデフォルトです。スクリプトを実行する前にセッションを開始してください。以降のスクリプトは新しいブラウザーを起動する(約 2~3 秒)代わりに、約 50 ミリ秒で接続でき、ログイン状態も自動的に維持されます。
セッションモードを使用する場合: デバッグ、テスト、反復的なフロー、認証が必要なページ、動画の録画、複数ステップの自動化など、すべてのインタラクティブな作業に使用します。エージェントのユースケースの大部分がこれに該当します。
ヘッドレスモード(セッションなし)のみを使用する場合: その場限りのスクリプト、CI/CD パイプライン、または永続デーモンが適さない環境に限ります。
クイックスタート
# Start a session (do this first)
cd $SKILL_DIR && node run.js --session start
# Run scripts — they auto-connect to the session
cd $SKILL_DIR && node run.js /tmp/my-script.js
# Cookies, localStorage, and current URL all persist between runs
# Check session status
cd $SKILL_DIR && node run.js --session status
# Stop when done
cd $SKILL_DIR && node run.js --session stop
仕組み
--session startは、Playwright のlaunchServer()を介してヘッドレス Chromium を起動します- ブラウザーはバックグラウンドデーモン(デタッチされたプロセス)として実行されます
- セッション情報は
/tmp/playwright-session.jsonに書き込まれます - スクリプトを実行すると、
run.jsがセッションを自動検出し、WebSocket 経由で接続します - コードでは、
browser、context、pageの各変数があらかじめ設定されています - スクリプトの終了時に、Cookie/localStorage/現在の URL が
/tmp/playwright-session-state.jsonに保存されます - 次のスクリプトは再接続して状態を復元します。同じ認証状態、同じ URL で続行できます
コードで使用できるもの
セッションモードでは、次の変数がコードであらかじめ定義されています。
| 変数 | 説明 |
|---|---|
browser | 接続済みのブラウザーインスタンス(実行間で維持されます) |
context | 前回の実行から Cookie/localStorage が復元されたブラウザーコンテキスト |
page | 前回の実行時の URL に移動したページ(初回実行時は空白) |
saveState | 終了前に呼び出すと Cookie/localStorage/URL を保存します(ラッパーによって自動的に呼び出されます) |
helpers | lib/helpers のすべてのヘルパー関数 |
chromium、devices | Playwright のエクスポート(追加のコンテキストを作成するため) |
セッションモードとヘッドレス(セッションなし)の比較
| セッションモード(デフォルト) | ヘッドレス — セッションなし | |
|---|---|---|
| ブラウザーの起動 | 1 回(--session start の実行時) | スクリプトの実行ごと |
| 起動時間 | 約 50ms(WebSocket 接続) | 約 2~3s(ブラウザーの起動) |
| ログイン状態 | 自動的に維持されます(Cookie/localStorage が実行間で保存されます) | 実行ごとに失われます(saveAuthState/loadAuthState を使用してください) |
| 現在の URL | 前回の実行時の状態に復元されます | about:blank から開始します |
page.route() | 完全にサポート | 完全にサポート |
| トークンコスト | 最小限(起動/終了の定型処理が不要) | 高め(スクリプトごとに起動と終了が必要) |
| 最適な用途 | あらゆる対話的な作業 — デバッグ、テスト、反復作業、認証フロー | CI/CD、独立したテスト、単発のスクリプト |
オプション
# Start with headed browser (visible)
cd $SKILL_DIR && node run.js --session start --headless false
# Start with a resolution preset
cd $SKILL_DIR && node run.js --session start --preset video
自動クリーンアップ
- スクリプトが 10 分間実行されないと、セッションは自動停止します
- セッションプロセスがクラッシュした場合、次のスクリプトが古いセッションを検出し、新しいヘッドレスモードに切り替えます
- セッションファイルと状態ファイルは自動的にクリーンアップされます
セッションモードで新しいコンテキストを作成する
デフォルトでは、状態を維持するために既存のコンテキストを再利用します。クリーンなコンテキストが必要な場合は、次のようにします。
// Create an isolated context within the session
const freshContext = await browser.newContext();
const freshPage = await freshContext.newPage();
await freshPage.goto('https://example.com');
// This context has no cookies/localStorage from previous runs
利用可能なヘルパー
すべてのヘルパーは lib/helpers.js にあります。スクリプト内で const helpers = require('./lib/helpers'); を使用してください。目的別に整理されています。
ページ操作
| ヘルパー | 使用するタイミング |
|---|---|
helpers.detectDevServers() | 重要 — 最初に実行してください。localhost のテストに使用します。検出されたサーバー URL の配列を返します。 |
helpers.createContext(browser, options?) | デフォルト設定(ビューポート 1280x720、ロケール en-US、タイムゾーン America/New_York)でブラウザーコンテキストを作成します。iPhone UA を使用する場合は { mobile: true } を渡します。環境変数のヘッダーは自動的にマージされます。 |
helpers.waitForPageReady(page, options?) | ページの読み込みをスマートに待機します(デフォルトは networkidle)。動的コンテンツの場合は { waitForSelector: '.loaded' } を渡します。 |
helpers.retryWithBackoff(fn, maxRetries?, initialDelay?) | 非同期関数を指数バックオフで再試行します。デフォルトは再試行 3 回、初期待機時間 1 秒です。 |
helpers.safeClick(page, selector, { retries: 3 }) | すぐには表示されない、またはクリックできない可能性がある要素をクリックします。自動的に再試行します。 |
helpers.safeType(page, selector, text) | 入力欄にテキストを入力します。デフォルトでは最初に入力欄をクリアします。 |
helpers.extractTexts(page, selector) | 複数の一致要素からテキストを取得し、配列として返します。 |
helpers.scrollPage(page, 'down', 500) | ページをスクロールします。方向は 'down'、'up'、'top'、'bottom' です。 |
helpers.handleCookieBanner(page) | 一般的な Cookie 同意バナーを閉じます。早めに実行してください。操作を妨げるオーバーレイが取り除かれます。 |
helpers.authenticate(page, { username, password }) | 一般的なフィールドセレクターを使用してログインします。リダイレクトを自動的に待機します。 |
helpers.generateTOTP(secret) | Base32 シークレットから 6 桁の TOTP コードを生成します。RFC 6238、SHA1、30 秒周期です。2FA フローでは BROWSER_AUTH_TOTP_SECRET 環境変数と組み合わせて使用します。 |
helpers.saveAuthState(context, path?, options?) | 認証後にログインセッションを保存します。デフォルトのパスは /tmp/playwright-auth.json です。Firebase/Supabase 認証では { indexedDB: true } を渡します。loadAuthState で再利用できます。 |
await helpers.handoff(page, { reason, successUrl?, successSelector?, timeout? }) | 監視付きモード専用です。 最終手段として、ヘッドレスブラウザーを閉じて人間向けにヘッドフルブラウザーを開き、成功するまでポーリングしてからヘッドレスに戻します。セッションモードとスタンドアロンモードを自動検出します。{ page, context, completed } を返します。ハンドオフが成功したかタイムアウトしたかは、completed のブール値で確認します。 |
helpers.loadAuthState(browser, path?, options?) | 保存済みの認証状態を持つコンテキストを作成します。再ログインは不要です。createContext のデフォルト設定を継承します。 |
helpers.getPageStructure(page, { interactiveOnly, root }) | ARIA スナップショットを使ってページ構造を検出します。yaml(階層構造の生データ)、tree(セレクター付きで解析した構造)、summary(件数)を返します。見慣れないページで使用してください。 |
helpers.getPageStructureWithRefs(page, options?) | getPageStructure と同様ですが、インタラクティブ要素に短い参照 ID(e1、e2 など)を割り当てます。構造化されたエントリー { role, name, level?, nth? } を含む追加の refMap オブジェクトを返します。nth インデックスを使って、重複する role と name の組み合わせを区別します。トークン効率のよい複数ステップの操作では、resolveRef() と組み合わせて使用します。 |
await helpers.resolveRef(page, refMap, 'e3') | 非同期です。 参照 ID を Playwright Locator に解決します。要素がページ上に存在しなくなっている場合、Playwright の 30 秒のタイムアウトを待たずに、すぐに(約 5 ms で)エラーをスローする古さの検出機能を備えています。.click()、.fill() などを実行できる Locator を返します。(await helpers.resolveRef(...)).method() 構文で使用します。 |
await helpers.diffPageStructure(page, previousYaml, options?) | 現在のページの ARIA 構造を以前のスナップショットと比較します。{ diff, changed, currentYaml } を返します。diff は +/-/ の接頭辞が付いた行単位の差分文字列です。次回の差分比較では、currentYaml を基準として使用します。diff npm パッケージが必要です。 |
helpers.annotatedScreenshot(page, options?) | インタラクティブ要素に番号付きバッジを付けたスクリーンショットを撮影します。{ path, refMap, tree, elementCount } を返します。オプションは path(保存先)、root(範囲)、fullPage です。視覚的なレイアウトと要素の検出を 1 つの成果物にまとめます。 |
helpers.handleDialogs(page, options?) | alert/confirm/prompt ダイアログを自動的に処理します。ナビゲーションの前に呼び出してください。後から確認できるように { dialogs } を返します。 |
helpers.dismissOverlays(page, overlays?) | addLocatorHandler を使って、Cookie バナー、モーダル、操作を妨げるオーバーレイを自動的に閉じます。カスタムパターンを渡すか、デフォルトを使用します。 |
helpers.extractTableData(page, 'table.results') | HTML テーブルから構造化データ(ヘッダーと行)を抽出します。 |
helpers.takeScreenshot(page, 'name') | タイムスタンプ付きのスクリーンショットを保存します。 |
コンソールの監視 — 気づきにくい JS エラーを検出する
| ヘルパー | 使用するタイミング |
|---|---|
helpers.startConsoleCapture(page) | ナビゲーションの前に呼び出してください。 すべてのコンソール出力を蓄積するコレクターを返します。 |
helpers.getConsoleErrors(collector) | コレクターから、エラーレベルのメッセージと捕捉されなかった例外のみを取得します。 |
helpers.getConsoleLogs(collector, filter?) | すべてのログを取得するか、文字列/RegExp/関数でフィルターします。 |
軽量な代替手段(Playwright v1.56+): コレクターを使わずに手早く確認する場合は、事後に page.consoleMessages() と page.pageErrors() を使用します。ページ作成後のすべてのメッセージ/エラーが返されます。
ネットワークの調査 — UI フロー中の API 呼び出しを検証する
| ヘルパー | 使用するタイミング |
|---|---|
helpers.startNetworkCapture(page, '/api/') | ナビゲーションの前に呼び出してください。 リクエスト/レスポンスのペアを記録します。URL フィルターは任意です。 |
helpers.getFailedRequests(collector) | コレクターから、4xx、5xx、接続エラーを取得します。 |
helpers.getCapturedRequests(collector) | 記録されたすべてのリクエスト/レスポンス項目を取得します。 |
helpers.waitForApiResponse(page, '/api/users', { status: 200 }) | 特定の API 呼び出しが完了するまで待機します。{ url, status, body, json } を返します。 |
軽量な代替手段(Playwright v1.56+): page.requests() はページ作成後のすべてのリクエストを返します。コレクターを設定せず、事後に手早く調査するのに便利です。
ブラウザーの状態 — ストレージと Cookie を確認する
| ヘルパー | 使用するタイミング |
|---|---|
helpers.getLocalStorage(page) | localStorage のすべてのエントリーを取得します。単一の値を取得するにはキーを渡します。 |
helpers.getSessionStorage(page) | sessionStorage のすべてのエントリーを取得します。単一の値を取得するにはキーを渡します。 |
helpers.getCookies(context) | ブラウザーコンテキストのすべての Cookie を取得します。 |
helpers.clearAllStorage(page) | localStorage、sessionStorage、Cookie を消去します。クリーンな状態でのテストに使用します。 |
動画の録画 — ブラウザー操作を記録する
| ヘルパー | 使用するタイミング |
|---|---|
helpers.createVideoContext(browser, { outputDir: '/tmp/videos' }) | 動画を録画するコンテキストを作成します。動画はページ/コンテキストを閉じると保存されます。 |
メディアのアップロード
動画またはファイルをアップロードする必要がある場合は、/media-upload スキルを読み込んでください。uploadToVimeo()、uploadToBunnyStream()、uploadToBunnyStorage() が提供されます。セットアップ:./secrets/setup.sh --skill media-upload
解像度プリセット — 対象ごとに一貫した寸法を設定する
| ヘルパー | 使用するタイミング |
|---|---|
helpers.RESOLUTION_PRESETS | プリセット設定にアクセスします。キーは docs-retina、pr-standard、gif-compact です。それぞれに viewport と deviceScaleFactor が含まれます。 |
helpers.createPresetContext(browser, 'preset') | プリセットの viewport と DPR を使用するコンテキストを作成します。手動での viewport/DPR 設定の代わりに使用します。 |
メディア変換 — スクリーンショットを GIF に変換する
| ヘルパー | 使用するタイミング |
|---|---|
helpers.screenshotsToGif(frames, path, opts) | PNG バッファーをアニメーション GIF に変換します。オプション:width、height、fps、quality、annotations(各フレームのクリックインジケーターとラベル)。 |
アクセシビリティ — WCAG 監査とキーボードナビゲーション
| ヘルパー | 使用するタイミング |
|---|---|
helpers.runAccessibilityAudit(page) | axe-core を挿入し、WCAG 2.0 AA 監査を実行します。影響度/説明とともに違反を返します。インターネット接続が必要です(CDN)。 |
helpers.checkFocusOrder(page, ['#first', '#second', '#third']) | 要素間を Tab キーで移動し、フォーカスが期待するセレクターに順番どおり移動することを検証します。 |
パフォーマンス指標 — ページ速度を測定する
| ヘルパー | 使用するタイミング |
|---|---|
helpers.capturePerformanceMetrics(page) | Navigation Timing(TTFB、DOM interactive)と Web Vitals(FCP、LCP、CLS)を取得します。ページ読み込み後に呼び出してください。 |
レスポンシブスクリーンショット — 複数のビューポートで撮影する
| ヘルパー | 使用するタイミング |
|---|---|
helpers.captureResponsiveScreenshots(page, url) | モバイル/タブレット/デスクトップ/ワイドの各ブレークポイントでスクリーンショットを撮影します。ブレークポイントと出力ディレクトリは任意でカスタマイズできます。 |
ネットワークシミュレーション — 劣化した状況をテストする
| ヘルパー | 使用するタイミング |
|---|---|
helpers.simulateSlowNetwork(page, 500) | すべてのリクエストに人工的な遅延(ms)を追加します。 |
helpers.simulateOffline(context) | ブラウザーをオフラインモードに設定します。 |
helpers.blockResources(page, ['image', 'font']) | 特定のリソースタイプ(画像、フォント、スタイルシート、スクリプトなど)をブロックします。 |
特定の障害のシミュレーション: 対象を絞ってエラーをシミュレーションするには、route.abort('connectionrefused') を使用します。エラーの種類:'connectionrefused'、'timedout'、'connectionreset'、'internetdisconnected'。
トレースとデバッグ
| ヘルパー | 使用するタイミング |
|---|---|
helpers.startTracing(context, options?) | トレース(DOM スナップショット、スクリーンショット、ネットワーク)の記録を開始します。ページを操作する前に呼び出してください。 |
helpers.stopTracing(context, path?) | トレースを停止し、.zip を保存します。npx playwright show-trace trace.zip で表示できます。 |
PDF の生成
| ヘルパー | 使用するタイミング |
|---|---|
helpers.generatePdf(page, path?, options?) | 現在のページから PDF を生成します。オプション:format、tagged(アクセシビリティ対応)、outline(ブックマーク)、margin。Chromium のヘッドレスモードでのみ使用できます。 |
ファイルのダウンロード
| ヘルパー | 使用するタイミング |
|---|---|
helpers.waitForDownload(page, triggerAction, savePath?) | アクションによって開始されたダウンロードを待機し、その後保存します。{ path, suggestedFilename, url } を返します。 |
レイアウトの検査 — 要素の位置を確認する
| ヘルパー | 使用するタイミング |
|---|---|
helpers.getElementBounds(page, '.selector') | バウンディングボックス、表示状態、ビューポート内にあるかどうか、計算済みスタイルを取得します。存在しないセレクターには null、非表示の要素には { visible: false } を返します。 |
ページ構造の内部情報 — ARIA スナップショットを単独で解析する
| ヘルパー | 使用するタイミング |
|---|---|
helpers.parseAriaSnapshot(yaml) | Playwright ARIA スナップショットの YAML 文字列を、構造化されたノードオブジェクトに解析します。各ノードには role、name と、任意の level、checked、disabled、expanded、selected が含まれます。 |
helpers.suggestSelector(node) | 解析済みの ARIA ノードから getByRole(...) セレクター文字列を生成します。 |
helpers.INTERACTIVE_ROLES | インタラクティブな ARIA ロール(button、link、textbox、checkbox、radio、combobox、slider、switch、tab、menuitem、searchbox、spinbutton、option)の Set です。 |
ローカルブラウザー — ユーザーの Chrome に接続する
これらのヘルパーは lib/local-browser.js にあります。スクリプトでは const { connectToLocalBrowser, getConnectedPage, extractAuthState } = require('./lib/local-browser'); を使用してください。詳細なドキュメントは references/local-browser.md を参照してください。
| ヘルパー | 使用するタイミング |
|---|---|
connectToLocalBrowser(options?) | 拡張機能ブリッジ経由で、実行中のユーザーの Chrome に接続します。{ browser, context, page, close() } を返します。Playwright MCP Bridge 拡張機能が必要です。承認ダイアログを省略するには、PLAYWRIGHT_MCP_EXTENSION_TOKEN 環境変数を設定してください。 |
getConnectedPage(context, url?) | 拡張機能によって公開されたページを取得し、任意で移動します。注:拡張機能ブリッジ経由では context.newPage() は動作しません。この関数か、connectToLocalBrowser() の page を使用してください。 |
extractAuthState(context, options?) | ユーザーのブラウザーから Cookie と localStorage({ indexedDB: true } を指定すると IndexedDB も)を抽出します。後で helpers.loadAuthState() を使って再利用するには、{ path: '/tmp/auth.json' } を指定してファイルに保存してください。 |
Shadow DOM — Web コンポーネントを操作する
| ヘルパー | 使用する場面 |
|---|---|
helpers.pierceShadowDOM(page, hostSelector, fn, args?) | 要素のオープンなシャドウルート内で関数を実行します。Lit、Stencil、Radix、Shoelace、Salesforce Lightning、Angular Material CDK、およびあらゆる Web コンポーネントで動作します。 |
hostSelector には CSS セレクター文字列または探索オブジェクトを指定できます。
// CSS selector
await helpers.pierceShadowDOM(page, '#my-component', (sr) => {
return sr.querySelector('button')?.textContent;
});
// Dynamic discovery — find host whose id contains a substring
await helpers.pierceShadowDOM(page, { matchId: 'my-widget' }, (sr) => {
sr.querySelector('textarea')?.focus();
});
// Match by attribute
await helpers.pierceShadowDOM(page, { matchAttr: { name: 'data-widget', value: 'chat' } }, (sr) => {
return sr.querySelectorAll('button').length;
});
// Pass arguments
await helpers.pierceShadowDOM(page, '#player', (sr, vol) => {
sr.querySelector('input[type=range]').value = vol;
}, [75]);
この機能がある理由: 標準の Playwright セレクター(page.click、page.locator)では、シャドウ DOM の境界を越えられません。よく使われる回避策である、page.evaluate() と手動での要素スキャンを組み合わせる方法は、冗長でエラーが発生しやすく、特にシャドウホストの ID が動的な場合(例:Radix は inkeep-shadowradix-:r0: を生成します)にその傾向が顕著です。このヘルパーは、境界を越えて実行するパターンをまとめたものです。
DOM の安定化 — ストリーミング/リアルタイムコンテンツを待機する
| ヘルパー | 使用する場面 |
|---|---|
helpers.waitForDOMStabilization(page, options?) | DOM 領域の変化が止まるまで待機します。ストリーミング AI 応答、リアルタイムデータフィード、共同編集の更新など、コンテンツが段階的に届く処理を開始した後に使用します。 |
// Wait for chat response to finish streaming
const result = await helpers.waitForDOMStabilization(page, {
selector: '.chat-messages',
stableMs: 2000, // content unchanged for 2s = done
minContentLength: 100, // don't consider "stable" if still empty/loading
});
console.log(result.stable ? 'Streaming complete' : 'Timed out');
// Wait inside a shadow DOM host
await helpers.waitForDOMStabilization(page, {
inShadowDOM: { matchId: 'my-widget' },
selector: '[data-part="messages"]',
stableMs: 3000,
});
オプション:selector(デフォルト:'body')、timeout(30s)、stableMs(3s)、pollInterval(500ms)、minContentLength(0)、inShadowDOM(null)。
{ stable: boolean, elapsed: number, finalLength: number } を返します。
CDP パフォーマンストレース — GPU レンダリングのメトリクス
レンダリングパイプラインを詳しく測定する場合(ラスタライズタスク、描画イベント、コンポジットのコスト)は、リファレンスを読み込んでください。
読み込み: references/cdp-tracing.md
以下を説明しています:正しいカテゴリを指定した CDP の Tracing.start/end、Performance.getMetrics の差分、ヘッドありモードとヘッドレスモードの違い(重要 — ヘッドレスでは GPU イベントがゼロになります)、高 DPI 負荷テスト用の DPR 設定、トレースイベントの分析。
カスタム HTTP ヘッダー
環境変数を使って、すべての HTTP リクエストにカスタムヘッダーを設定します。次の用途に便利です。
- バックエンドへの自動化トラフィックの識別
- LLM に最適化された応答の取得(例:スタイル付き HTML ではなくプレーンテキストのエラー)
- 認証トークンの一括追加
設定
単一ヘッダー(一般的なケース):
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();
// All requests from this page include your custom headers
生の Playwright API を使うスクリプトでは、注入された getContextOptionsWithHeaders() を使用してください。
const context = await browser.newContext(
getContextOptionsWithHeaders({ viewport: { width: 1920, height: 1080 } }),
);
高度な使い方
Playwright API の詳しいドキュメントについては、API_REFERENCE.md を参照してください。
- セレクターとロケーターのベストプラクティス
- ネットワークインターセプトと API モック
- 認証とセッション管理
- ビジュアルリグレッションテスト
- モバイルデバイスのエミュレーション
- パフォーマンステスト
- デバッグ手法
- CI/CD との統合
ヒント
- 重要:最初にサーバーを検出してください - localhost のテスト用コードを書く前に、必ず
detectDevServers()を実行してください - カスタムヘッダー -
PW_HEADER_NAME/PW_HEADER_VALUE環境変数を使って、バックエンドへの自動化されたトラフィックを識別してください - テストファイルには /tmp を使用してください -
/tmp/playwright-test-*.jsに書き込み、スキルディレクトリやユーザーのプロジェクトには書き込まないでください - URL をパラメーター化してください - 検出または指定された URL を、すべてのスクリプトの先頭にある
TARGET_URL定数に設定してください - デフォルト:ヘッドレスブラウザー - Docker/CI との互換性のため、常に
headless: trueを使用してください - ヘッドありモード - ユーザーがブラウザーの表示を明示的に希望している場合、またはローカルでデバッグしている場合は、
headless: falseを使用してください - 待機方法: 固定タイムアウトではなく、
waitForURL、waitForSelector、waitForLoadStateを使用してください - エラー処理: 信頼性の高い自動化のため、必ず try-catch を使用してください
- コンソール出力:
console.log()を使って進捗を記録し、処理内容を表示してください - Docker: コンテナーとの互換性のため、ヘルパーにはデフォルトで
--no-sandboxフラグが含まれています - 時間操作(Playwright v1.45+): テストで時間を制御するには
page.clockを使用します。await page.clock.install()を実行し、その後await page.clock.fastForward('01:00')で時間を進めるか、await page.clock.pauseAt(new Date('2025-01-01'))で特定の時点に固定します。タイマー、カウントダウン、セッションの有効期限、時間に依存する UI のテストに役立ちます。 - WebSocket のインターセプト(Playwright v1.48+):
page.routeWebSocket(url, handler)を使用して、WebSocket 接続をモックまたは監視します。ハンドラーはonMessage()、send()、close()を備えたWebSocketRouteを受け取ります。サーバーを起動せずに、リアルタイム機能(チャット、通知、ライブ更新)をテストする場合に役立ちます。 - パフォーマンステストにはヘッドありモードが必要です: ヘッドレス Chrome では GPU 合成がスキップされるため、トレースイベントの
PaintとCompositeLayersは常にゼロになります。レンダリングのパフォーマンスを測定する場合は、必ずheadless: falseを使用してください。references/cdp-tracing.mdを参照してください。 - 高 DPI のパフォーマンステスト:
--force-device-scale-factor=N(GPU ラスタライズに影響する Chrome フラグ)とdeviceScaleFactor: N(CSS/スクリーンショットに影響する Playwright コンテキスト)の両方を使用してください。これらは異なる仕組みです。DPR 1 では多くの GPU ボトルネックが隠れるため、レンダリングに影響されるパスのストレステストには DPR 2~3 を使用してください。 - Shadow DOM: 標準の Playwright セレクターではシャドウ境界を越えられません。Web コンポーネント(Lit、Stencil、Radix、Salesforce Lightning など)を使用するページでは、
helpers.pierceShadowDOM()を使用してください。
トラブルシューティング
Playwright がインストールされていない場合:
cd $SKILL_DIR && npm run setup
モジュールが見つからない場合:
run.js ラッパーを使い、スキルディレクトリから実行していることを確認してください
Docker でブラウザーが起動しない場合:
--no-sandbox および --disable-setuid-sandbox 引数が設定されていることを確認してください(ヘルパーにはデフォルトで含まれています)
要素が見つからない場合:
待機を追加してください:await page.waitForSelector('.element', { timeout: 10000 })