mcpField
P
playwright-automation

playwright-automation

github.com/petrkindlmannテスト・品質2222.0k 回インストール6 日前に更新
リポジトリ

playwright-automation について

playwright-automationは、保守しやすいPlaywrightテストの作成をコーディングエージェント向けに導くスキルです。TypeScriptを中心に、画面上の要素、オートウェイト、分離、フィクスチャを重視します。

主な機能

  1. 01ユーザー向けのロケーターを優先
  2. 02固定待機を避けてオートウェイトを活用
  3. 03BrowserContextでテストを分離
  4. 04フィクスチャでセットアップを管理
  5. 05ページオブジェクトと再利用可能なコンポーネントを構成

活用シーン

  1. 01新規プロジェクトのPlaywright E2Eテストを構築
  2. 02不安定なCypressやSeleniumテストを段階的に移行
  3. 03並列実行とCIのシャーディングを計画

インストール

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

$npx skills add https://github.com/petrkindlmann/qa-skills --skill playwright-automation

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

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
anti-patterns.md
auth-patterns.md
ci-recipes.md
debugging-and-triage.md
fixtures-and-projects.md
multi-site-architecture.md
network-and-mocking.md
selector-strategies.md

詳細

翻訳後に原文が更新されています。最新の内容は原文をご確認ください。

エキスパートエージェントが、安定性と保守性を備えた本番品質の Playwright テストを TypeScript で作成する方法です。これによって防げる失敗は、AI エージェントが、初回は通ってもその後ずっと不安定になるテストスイートを生み出す 3 つのパターンに反射的に頼ってしまうことです。`waitForTimeout` は決して使わず、CSS セレクターをデフォルトにせず、従来の `page.click()` 系のメソッドは避けてください。このスキルでは、自動待機、ユーザー視点のロケーター、フィクスチャベースの規律を定め、リファクタリング後もテストスイートが機能するようにします。

確認事項

まず .agents/qa-project-context.md を確認してください。存在する場合はその内容を使い、そこですでに回答されている質問は省略します。その後、不足していることだけを尋ねます。

  1. TypeScript と JavaScript のどちらですか? TypeScript を強く推奨します。ロケーターやアサーションのミスをコンパイル時に検出でき、ここにあるすべての例も TypeScript を前提としています。
  2. 対象ブラウザーはどれですか? ローカル開発では Chromium を使い、CI では Firefox と WebKit を追加します。モバイルのビューポートは別のテストファイルではなく、Playwright の別プロジェクトとして設定します。変更するのはデバイス記述子です。
  3. 既存のテストスイートがありますか、それとも新規に始めますか? Cypress/Selenium から移行する場合は、まず最も不安定なテストを書き直し、一括で置き換えるのは避けてください。手順が大きく変わります。
  4. 単一サイトですか、それとも複数サイトですか? 複数サイトでは共有フィクスチャとサイトごとの設定オブジェクトが必要です。references/multi-site-architecture.md を参照してください。

基本原則

  1. まずユーザー視点のロケーターを使います。 getByRole > getByLabel > getByTestId > CSS(最終手段)。ロケーターは DOM の構造ではなく、ユーザーに見えるものを反映する必要があります。references/selector-strategies.md を参照してください。
  2. 自動待機を利用します — waitForTimeout は絶対に使わないでください。 Playwright のすべての操作と Web ファーストアサーションは自動で待機します。タイムアウトが必要だと思う場合は、より適切なロケーターまたはアサーションが必要です。
  3. テストを分離します。 各テストには新しい BrowserContext を用意します。テストが、ほかのテストの状態や実行順序に依存してはなりません。
  4. デフォルトでは並列実行し、必要な場合に限り直列実行します。 fullyParallel: true を使います。test.describe.serial は、本当に分離できないフロー(まれです)にのみ使ってください。
  5. セットアップにはフックではなくフィクスチャを使います。 フィクスチャは組み合わせ可能で、型安全性を提供し、自動的にクリーンアップします。重要なセットアップには beforeEach/afterEach よりフィクスチャを優先してください。references/fixtures-and-projects.md を参照してください。

チームの成熟度に合わせて調整してください(.agents/qa-project-context.md で team_maturity を設定します)。

  • startup — Chromium のみ、クリティカルパスのテストを 5~10 件、PR で基本的な CI を実行します。テストスイートが安定するまでは、シャーディングとビジュアルベースラインを省略します。
  • growing — Chromium + Firefox、POM 構造、並列実行、CI でのシャーディング、HTML レポートのアーティファクトを使用します。
  • established — 完全なブラウザーマトリックス、認証フィクスチャ、API モックレイヤー、ビジュアルリグレッションのベースライン、失敗時のトレース、不安定さの追跡を導入します。

プロジェクト構成

project-root/
├── playwright.config.ts
├── e2e/
│   ├── fixtures/              # base.fixture.ts, auth.fixture.ts, data.fixture.ts
│   ├── pages/                 # Page objects by feature
│   │   ├── base.page.ts
│   │   ├── dashboard.page.ts
│   │   └── components/        # Reusable component objects (data-table, modal)
│   ├── tests/                 # Test files by feature (auth/, dashboard/, settings/)
│   ├── helpers/               # test-data.ts, api-client.ts
│   └── global-setup.ts
├── .auth/                     # Git-ignored storageState files
└── test-results/              # Git-ignored artifacts
playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

const isCI = !!process.env.CI;
const baseURL = process.env.BASE_URL ?? 'http://localhost:3000';

export default defineConfig({
  testDir: './e2e/tests',
  fullyParallel: true,
  forbidOnly: isCI,
  retries: isCI ? 2 : 0,
  workers: isCI ? '50%' : undefined,
  reporter: isCI
    ? [['blob'], ['github'], ['json', { outputFile: 'test-results/results.json' }]]
    : [['html', { open: 'on-failure' }]],
  use: {
    baseURL,
    trace: isCI ? 'on-first-retry' : 'retain-on-failure',
    screenshot: 'only-on-failure',
    video: isCI ? 'on-first-retry' : 'off',
    navigationTimeout: 30_000,
    // Avoid a global actionTimeout — it can mask a genuinely slow auto-waited
    // action. Set per-action only where a known-slow widget needs it.
  },
  projects: [
    { name: 'setup', testMatch: /global-setup\.ts/, teardown: 'teardown' },
    { name: 'teardown', testMatch: /global-teardown\.ts/ },
    { name: 'chromium', use: { ...devices['Desktop Chrome'], storageState: '.auth/user.json' }, dependencies: ['setup'] },
    { name: 'firefox', use: { ...devices['Desktop Firefox'], storageState: '.auth/user.json' }, dependencies: ['setup'] },
    { name: 'webkit', use: { ...devices['Desktop Safari'], storageState: '.auth/user.json' }, dependencies: ['setup'] },
  ],
  webServer: isCI ? undefined : {
    command: 'npm run dev', url: baseURL, reuseExistingServer: !isCI, timeout: 120_000,
  },
});

CI の blob レポーターにより、シャーディングした実行結果をマージできます。シャーディングのセクションを参照してください。setup プロジェクトが storageState を一度書き込み、その後、ブラウザープロジェクトがそれに依存します。

グローバルセットアップ(storageState)
import { test as setup, expect } from '@playwright/test';

setup('authenticate as default user', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill(process.env.TEST_USER_EMAIL!);
  await page.getByLabel('Password').fill(process.env.TEST_USER_PASSWORD!);
  await page.getByRole('button', { name: 'Sign in' }).click();
  await expect(page).toHaveURL(/.*dashboard/);
  await page.context().storageState({ path: '.auth/user.json' });
});

これは setup プロジェクトのパターンです。セットアッププロジェクト(または globalSetup ファイル)で UI ログインを一度実行し、すべてのブラウザープロジェクトが設定の storageState を使って保存済みの Cookie と localStorage を再利用します。複数ロールの認証(admin/user/guest)やトークンのシードについては、references/auth-patterns.md を参照してください。


ページオブジェクトモデル

import { type Page, type Locator, expect } from '@playwright/test';

export abstract class BasePage {
  constructor(protected readonly page: Page) {}
  abstract readonly path: string;
  async goto(): Promise<void> {
    await this.page.goto(this.path);
    await this.page.waitForLoadState('domcontentloaded');
  }
}

コンポーネントオブジェクトは、再利用可能な UI の断片(モーダル、テーブル、ナビゲーション)を表します。Page ではなく、ルートの Locator を受け取ります。

export class DataTable {
  readonly rows: Locator;
  constructor(private readonly root: Locator) {
    this.rows = root.getByRole('row');
  }
  getRowByText(text: string | RegExp): Locator {
    return this.rows.filter({ hasText: text });
  }
}

深い継承ではなく、組み合わせを使います。 ページはコンポーネントを保持し、5 階層の継承構造を拡張しません。

export class UsersPage extends BasePage {
  readonly path = '/admin/users';
  readonly table: DataTable;
  constructor(page: Page) {
    super(page);
    this.table = new DataTable(page.getByRole('table', { name: 'Users' }));
  }
}

テストファイル内でコンストラクターを呼び出すのではなく、フィクスチャを通じてページオブジェクトを注入します。

export const test = base.extend<{ usersPage: UsersPage }>({
  usersPage: async ({ page }, use) => { await use(new UsersPage(page)); },
});
export { expect } from '@playwright/test';

POM のメソッドは状態(ロケーター、値)を返し、アサーションは行いません。失敗時にページオブジェクトではなくテストが示されるよう、アサーションはテストに記述します。


テストパターン

test.step を使ったフォーム操作

論理的な操作のまとまりを test.step() で囲むと、トレースビューアーの出力が読みやすくなります。

test('submits a multi-step form', async ({ page }) => {
  await page.goto('/onboarding');
  await test.step('fill personal info', async () => {
    await page.getByLabel('First name').fill('Jane');
    await page.getByRole('button', { name: 'Next' }).click();
  });
  await test.step('submit', async () => {
    await page.getByRole('button', { name: 'Complete setup' }).click();
  });
  await expect(page).toHaveURL('/dashboard');
});
API のモック
// Mock a response
await page.route('**/api/products*', async (route) => {
  await route.fulfill({ json: { items: [{ id: '1', name: 'Widget', price: 29.99 }] } });
});

// Modify a real response
await page.route('**/api/feature-flags', async (route) => {
  const response = await route.fetch();
  const body = await response.json();
  body.flags['new-checkout'] = true;
  await route.fulfill({ response, json: body });
});

// Simulate an error
await page.route('**/api/products*', (route) => route.fulfill({ status: 500 }));

// WebSocket (v1.48+)
await page.routeWebSocket('**/ws/notifications', (ws) => {
  ws.onMessage(() => ws.send(JSON.stringify({ type: 'alert', title: 'Deployed' })));
});

HAR の再生と条件付きルーティングについては、references/network-and-mocking.md を参照してください。

認証済み APIRequestContext フィクスチャ

UI を操作せずにデータをシードしたり、バックエンドの状態をアサートしたりするには、認証済みの APIRequestContext を注入します。トークンはフィクスチャ内で取得し、ハードコードしないでください。

import { test, request, type APIRequestContext } from '@playwright/test';

// test.extend adds an `api` fixture to the base test object.
export const apiTest = test.extend<{ api: APIRequestContext }>({
  api: async ({ baseURL }, use) => {
    const ctx = await request.newContext({
      baseURL,
      extraHTTPHeaders: { Authorization: `Bearer ${process.env.API_TOKEN!}` },
    });
    await use(ctx);
    await ctx.dispose();
  },
});
タグとアノテーション
test('checkout @smoke', async ({ page }) => { /* npx playwright test --grep @smoke */ });
test.slow();                                    // Triples timeout
test.skip(({ browserName }) => browserName === 'webkit', 'WebKit bug');
test.fixme('known issue tracked in JIRA-1234', async ({ page }) => { /* ... */ });

アサーション

常に Web ファーストのアサーションを優先してください。条件が成立するかタイムアウトになるまで自動的に再試行します。

await expect(page.getByRole('alert')).toBeVisible();
await expect(page.getByRole('heading')).toHaveText('Dashboard');
await expect(page).toHaveURL('/dashboard');
await expect(page.getByRole('button', { name: 'Save' })).toBeEnabled();
await expect(page.getByRole('listitem')).toHaveCount(5);
await expect(page.getByRole('listitem')).toHaveText(['Apple', 'Banana', 'Cherry']);

ソフトアサーションは、最初の失敗で停止せず、すべての失敗を収集します。

await expect.soft(page.getByLabel('Name')).toHaveValue('Jane Doe');
await expect.soft(page.getByLabel('Email')).toHaveValue('[email protected]');

ARIA スナップショットは、アクセシビリティツリーの構造を検証し、セマンティクスのリグレッションを検出します。

await expect(page.getByRole('navigation', { name: 'Main' })).toMatchAriaSnapshot(`
  - navigation "Main":
    - link "Home"
    - link "Products"
`);
ビジュアルリグレッション(1 行で記述し、ワークフローの詳細は別途扱います)

Playwright に組み込まれている toHaveScreenshot は自動的に再試行し、初回実行時にベースラインを作成します。動的な領域をマスクし、事前に waitForTimeout を使わないでください。

await expect(page.getByTestId('product-card')).toHaveScreenshot('product-card.png', {
  mask: [page.getByTestId('price')],
});

ベースラインの管理、しきい値(maxDiffPixelRatio、maskColor、stylePath)、レビューのワークフローについては、ビジュアルテストに関する内容を扱う visual-testing を使用してください。

アクセシビリティスキャン(axe。詳細な監査は別途扱います)

上記の ARIA スナップショットで確認できるのは構造であり、WCAG のルールではありません。ルールベースのスキャンには、@axe-core/playwright を追加してください。

import AxeBuilder from '@axe-core/playwright';

test('dashboard has no a11y violations', async ({ page }) => {
  await page.goto('/dashboard');
  const results = await new AxeBuilder({ page }).analyze();
  expect(results.violations).toEqual([]);
});

WCAG のレベル、ルールの調整、修正方法については、accessibility-testing を使用してください。


並列実行と CI

CI ノード間のシャーディング

テストスイートをマトリックスジョブに分割して実行し、各シャードのレポートを 1 つの HTML レポートに統合します。シャーディングが有効なのは、成熟度が growing 以上の場合です。テスト数が 5~10 件の startup スイートではシャーディングしないでください。

strategy:
  fail-fast: false
  matrix:
    shard: [1, 2, 3, 4]
steps:
  - run: npx playwright test --shard=${{ matrix.shard }}/4

各シャードは blob-report/ をアップロードします。最後のジョブでは、npx playwright merge-reports --reporter=html ./all-blob-reports を実行します。マージを機能させるには、(上記の設定で指定した)blob レポーターが必要です。--shard だけでは、分割された HTML レポートが生成されます。GitHub Actions のワークフロー全体、blob のアップロードとダウンロード、アーティファクトのパターンについては、references/ci-recipes.md を参照してください。

デバッグ
  • トレースビューアー: npx playwright show-trace test-results/.../trace.zip — アクション、ネットワーク、DOM スナップショット、コンソールのタイムラインを表示します。
  • UI モード: npx playwright test --ui — ライブでステップごとに実行し、時間を遡って確認できます。
  • デバッグフラグ: npx playwright test my-test.spec.ts --debug — ブラウザーを表示して起動し、各アクションで一時停止します。
  • VS Code 拡張機能 ms-playwright.playwright — ガターから実行・デバッグし、ロケーターを選択して、ウォッチモードを利用できます。
  • page.pause() はテストの途中で Inspector を開きます。ローカルでのみ使用し、決してコミットしないでください。

フレーキーテストのトリアージとアーティファクトの分析については、references/debugging-and-triage.md を参照してください。


新機能(2025~2026年)

現在の最新バージョンは Playwright 1.60.0(2026年5月)です。package.json と CI の Docker イメージで同じバージョンを固定してください。最近追加された注目の機能を紹介します。

バージョン機能内容
v1.45Clock APIpage.clock.install() / fastForward() — Date をモンキーパッチせずに時間を制御します
v1.45--fail-on-flaky-testsテストの合格にリトライが必要だった場合、CI の実行を失敗させます
v1.46--only-changed変更されたファイルの影響を受けるテストだけを実行します(git diff を認識します)
v1.46ARIA スナップショットアクセシビリティツリーをアサートする toMatchAriaSnapshot()
v1.48routeWebSocketWebSocket のファーストクラスのインターセプト(CDP のハックを置き換えます)
v1.55Test Migratornpx playwright migrate による Cypress/Selenium から Playwright への自動移行
v1.56Test Agentsnpx playwright init-agents --loop=claude|vscode|opencode — コーディングエージェントのループ内で動作するプランナー/ジェネレーター/ヒーラーエージェント
v1.57Chrome for Testing がデフォルトにヘッドありでは chrome、ヘッドレスでは同梱の Chromium ではなく chrome-headless-shell を使用します。注意点:メモリー使用量が大幅に増える回帰が報告されています(microsoft/playwright #38489)。CI では動作確認済みのイメージタグを固定してください。
v1.57toHaveScreenshot のオプション色のマスキング、カスタムスタイルシート、出力パスの制御に使う maskColor、stylePath、pathTemplate
v1.59Screencast APIテスト途中の動画を開始/停止できる page.screencast.start() / .stop() — recordVideo の代替機能ですが、置き換えるものではありません。アクション注釈、チャプターマーカー、カスタム HTML オーバーレイ、screencast.showOverlays() / hideOverlays() が追加されます。エージェントによる自己検証に便利です。コーディングエージェントは、レビュー可能な動画の証跡を引き渡せます。
v1.59--debug=cli一時停止して接続できるため、エージェントがテストをステップ実行できます
v1.60locator.drop()外部ファイルやクリップボードから要素へのドラッグ&ドロップをシミュレートします
v1.60tracing.startHar()ファーストクラスのトレーシング API として HAR を記録します
AI による作成支援(Test Agents と MCP)

統合方法は 2 つあります。エージェントがエディターのループ内で動作するのか、それとも実際のブラウザーをリモートから操作するのかに応じて選んでください。

方法 A — Test Agents(npx playwright init-agents --loop=claude):コーディングエージェントがループ中に読み込む、プランナー/ジェネレーター/ヒーラーエージェントを作成します。トークン効率に優れており、MCP サーバーやプロセス間通信は必要ありません。「Claude/VS Code/opencode に Playwright テストを書いてもらう」場合に最適です。

方法 B — @playwright/mcp:あらゆる MCP 対応エージェントにブラウザー操作を公開する MCP サーバーです。プロセス境界や JSON のマーシャリングに伴うオーバーヘッドは大きくなりますが、オフラインでテストを作成するのではなく、エージェントが稼働中のブラウザーを対話的に操作する必要がある場合に適しています。設定は .mcp.json に記述します:{ "mcpServers": { "playwright": { "command": "npx", "args": ["@playwright/mcp@latest"] } } }。

テスト失敗の修復については、test-reliability を参照してください。PRD/仕様書から初めてテストを生成する場合は、ai-test-generation を参照してください。


アンチパターン

テストスイートをひそかに劣化させる設計時のミスです。コードレベルの「これをしてはいけない」一覧は references/anti-patterns.md に BAD/GOOD の例とともに記載されています。テスト本体を作成するときに読み込んでください。

1. ゴッド・ページオブジェクト

アプリ全体を 1 つのクラスにまとめると、すべてのテストがインポートする 2000 行のファイルになり、安全なリファクタリングができなくなります。ページや機能ごとに分割し、コンポーネントオブジェクトを組み合わせてください。

2. アサーションを行う POM メソッド

expect を呼び出すページオブジェクトのメソッドは、アサーションをテストから隠してしまいます。失敗時にスタックが指すのは、失敗したシナリオではなくページオブジェクトです。ロケーターや状態を返し、アサーションはテスト内で行ってください。

3. 実装の詳細に対するアサーション

CSS クラス、DOM のネスト、内部 ID を基準にしたテストは、実際の動作に変化がなくてもリファクタリングのたびに壊れます。表示テキスト、ロール、URL など、ユーザーが認識する内容をアサートしてください。

4. テストの実行順に依存するフィクスチャ

共有モジュールの状態を変更するフィクスチャや、別のテストが先に実行されたことを前提とするフィクスチャは、テストの並列実行や単独実行を始めた途端に失敗します。各フィクスチャは単独で動作できる必要があります。

5. getByRole が使える場面での data-testid

すでにアクセシブルな名前が付いているボタンや見出しにテスト ID を追加しても、無料で得られる最も手軽なアクセシビリティシグナルを逃してしまいます。getByTestId は、安定したロールやラベルがない要素に限って使ってください。

最も有害な実行時の間違いは、自動で待機するロケーターではなく waitForTimeout で同期を取ることです。

// BAD — slow on fast machines, flaky on slow ones, hides the real condition
await page.waitForTimeout(2000);
await page.click('#submit');

// GOOD — the action auto-waits for actionability
await page.getByRole('button', { name: 'Submit' }).click();

コードレベルで問題となるその他の 9 項目(ロールではなく CSS を使う、ロケーターではなく page.* を使う、force: true、状態の共有、テストごとのログイン、安定性を確認せずに locator.all() を使う、toHaveText() ではなく allTextContents() を使う、実際のサードパーティーサービスにアクセスする、test.only をコミットする)については、references/anti-patterns.md を参照してください。


検証

生成された成果物に対して、最も手軽なものから順に以下を実行してください。

npx playwright test --list                 # tests are discovered and parse
grep -rn 'waitForTimeout\|page.pause' e2e/  # must print nothing
npx tsc --noEmit                            # locator/assertion types compile

eslint-plugin-playwright のルール no-wait-for-timeout、no-force-option、no-element-handle、no-page-pause を使って、「X を絶対にしない」というルールを CI で適用してください。これらのルールにより、このスキルの禁止事項がリントエラーになります。


完了条件

  • playwright.config.ts が存在し、少なくとも Chromium 用の projects が設定されていること(CI を対象とする場合は Firefox と WebKit も追加します)。また、forbidOnly: !!process.env.CI が設定されていること。
  • Page Object Model のファイルが e2e/pages/(または同等の場所)にあり、コンポーネントオブジェクトはルート Locator を使って合成され、POM メソッド内に expect がないこと。
  • grep -rn 'waitForTimeout' e2e/ が何も返さず、eslint-plugin-playwright の no-wait-for-timeout が有効になっていること。
  • すべてのロケーターで getByRole/getByLabel/getByTestId を使っていること。grep -rn 'page.locator(\|xpath=\|css=' e2e/ が何も返さないこと(または、正当な理由がコメントに記された例外のみを返すこと)。
  • CI で PR ごとにテストスイートが実行されること。成熟度が growing 以上の場合は、blob レポーターを使ったマトリックスジョブでシャーディングし、merge-reports のステップを実行して、失敗時に HTML レポートをアーティファクトとしてアップロードすること。

関連スキル

  • visual-testing — スクリーンショットのベースライン作成、しきい値の調整、レビュー/承認ワークフロー。単一のインライン toHaveScreenshot チェックを超える内容は、こちらを参照してください。
  • accessibility-testing — WCAG のレベル、axe ルールの調整、問題の修正。このスキルでは最小限の axe スキャンのみを扱います。
  • api-testing — バックエンド API の検証、スキーマ/コントラクトテスト、APIRequestContext の包括的なパターン。
  • ci-cd-integration — Playwright 独自の機能を超えるパイプライン設定、並列化、レポート。
  • test-reliability — 不安定な単一テストの実行時の修復(隔離、再試行戦略)。
  • selector-drift-recovery — UI のリファクタリングで多数のテストが壊れた後、セレクターをオフラインで一括再生成します。
参考ファイル(references/ 内)
ファイル用途
anti-patterns.mdコードレベルの間違いそれぞれについて、BAD と GOOD のコード例を掲載
fixtures-and-projects.md認証フィクスチャ、データフィクスチャ、複数環境のプロジェクト、合成
selector-strategies.mdロケーター選択の判断ツリー、getByRole の例、安定性のスコアリング
auth-patterns.mdstorageState、複数ロール、トークンのシード、セッションの有効期限切れ
multi-site-architecture.md共有フィクスチャ、サイトごとの設定、モノレポのパターン
network-and-mocking.mdpage.route、route.fetch、HAR、WebSocket、条件付きルーティング
debugging-and-triage.mdトレースビューアー、不安定なテストのトリアージ、再試行、アーティファクト
ci-recipes.mdレポーター、シャーディングとマージ、--only-changed、ブラウザーのキャッシュ、Docker

コメント