مرکزی مواد پر جائیں
友田 陽大
Frontend
Next.js
フロントエンド
アクセシビリティ
TypeScript
アーキテクチャ設計
パフォーマンス

Playwright E2E テスト設計ガイド【2026年版】— 壊れない・速い・信頼できるテストを本番品質で

Playwright で本番品質の E2E テストを設計する完全ガイド。ロールベースのロケーターと Web-first アサーション(自動待機)でフレークを排除し、外部 API をモックし、a11y を CI で検査し、本番ビルドに対して実行する構成、トレース/レポートによる可観測性、CI シャーディングまで、実コードで解説します。

Published
Reading time
10 min read
Author
友田 陽大
شیئر کریں

E2E は「リリースして大丈夫か」を最後に保証する砦です。だからこそ、遅くて壊れやすいテストは無いほうがマシ。設計が品質を決めます。


1. 何を E2E で・何を単体で検証するか

テストには階層があります。すべてを重い E2E で書くと、遅く・脆く・高コストになります。

対象適したテスト理由
純粋ロジック(バリデーション・計算)単体(Vitest 等)最速・最安・網羅しやすい
ユーザー動線(入力→送信→確認)E2E(Playwright)実ブラウザでの統合を保証
SEO 配管(JSON-LD・sitemap・RSS)E2Eレンダリング後の出力を検証できる
アクセシビリティの自動検査E2E(axe)実 DOM に対して走らせる

原則は「価値の高い数本の E2E に集中」。例えばこのサイトでは、フォーム送信・ナビゲーション・予約導線・a11y を E2E で押さえ、データ整形やレート制限の窓計算は単体テストに寄せています。


2. ロケーター:ロール基準で書く(壊れにくさ=a11y)

最も重要な設計判断がロケーター戦略です。page.locator(".btn-primary") のような CSS セレクタは、デザイン変更で即壊れます。**ユーザーが認識する手がかり(役割・ラベル・テキスト)**で要素を掴みます。

// ❌ 実装詳細に依存。クラス名が変わると壊れる
await page.locator("div.form > button.submit").click();

// ✅ ユーザー視点。役割とアクセシブルネームで掴む
await page.getByRole("button", { name: "送信する" }).click();
await page.getByLabel("メールアドレス").fill("taro@example.com");
await page.getByRole("radio", { name: /プロジェクト単位/ }).click();

副次効果が強力です。getByRole / getByLabel で要素を掴めるということは、その要素にアクセシブルな名前が付いている証拠です。ロケーター戦略がそのまま a11y のスモークテストになります(WCAG 2.2 実装ガイド)。


3. Web-first アサーション:固定スリープを捨てる

Playwright のアサーションは自動で待機・再試行します。「表示されるまで」「有効になるまで」を Playwright が面倒見るので、waitForTimeout は基本不要です。固定スリープはフレークの最大要因です。

// ❌ フレークの温床:環境が遅いと落ち、速いと無駄に待つ
await page.click("button");
await page.waitForTimeout(3000);
expect(await page.isVisible(".success")).toBe(true);

// ✅ 条件が満たされるまで自動で待つ(タイムアウトまで再試行)
await page.getByRole("button", { name: "送信する" }).click();
await expect(page.getByText("お問い合わせを送信しました")).toBeVisible();

例外:意図的な時間ゲート(例「送信は表示から2.5秒後まで無効」というスパム対策)を検証する場合に限り、その時間を待つのは正当です。「不確実さを誤魔化すためのスリープ」と「仕様としての待機」は区別します。


4. 外部 API のモック:本物に到達させない

E2E でメール送信・決済・課金などの外部副作用を本物に飛ばしてはいけません(コスト・信頼性・セキュリティの三重の問題)。page.route でリクエストを傍受し、ペイロードの形を検証しつつ、任意のレスポンスを返します。

import { test, expect, type Page, type Route } from "@playwright/test";

// /api/contact を傍受し、Resend 等の本物に到達させない。
// 送信ボディの形を確認しつつ、指定のステータス/JSON を返す。
async function mockContactApi(
  page: Page,
  { status = 200, body = { success: true }, onRequest }: {
    status?: number;
    body?: Record<string, unknown>;
    onRequest?: (payload: unknown) => void;
  } = {},
) {
  await page.route("**/api/contact", async (route: Route) => {
    onRequest?.(route.request().postDataJSON());
    await route.fulfill({ status, contentType: "application/json", body: JSON.stringify(body) });
  });
}

これで「正常系」「429(レート制限)」「500(サーバーエラー)」を確定的に再現できます。回復性(失敗時の UI)も、外部を待たずに検証できます。

test("レート制限時の文言を出す", async ({ page }) => {
  await page.goto("/contact");
  await mockContactApi(page, { status: 429, body: { error: "rate limited" } });
  // ...入力して送信...
  await expect(page.getByText("短時間に複数回送信されています")).toBeVisible();
});

送信ボディの形を onRequest で捕まえ、toMatchObject で検証すると、「正しいペイロードで・1回だけ」到達したことまで保証できます。


5. アクセシビリティを E2E に組み込む

@axe-core/playwright で、実 DOM に対して WCAG 違反を自動検査します。デザイン変更でコントラストやラベルが壊れても CI で気づけます。

import { test, expect } from "@playwright/test";
import AxeBuilder from "@axe-core/playwright";

test("主要ページに重大な a11y 違反がない", async ({ page }) => {
  await page.goto("/");
  const results = await new AxeBuilder({ page })
    .withTags(["wcag2a", "wcag2aa", "wcag22aa"])
    .analyze();
  expect(results.violations).toEqual([]);
});

自動検査は万能ではありません(検出できるのは機械判定可能な3〜5割)。キーボード操作やスクリーンリーダーの体感は手動確認が要ります。それでも「退行を CI で止める」価値は絶大です。


6. SEO 配管のテスト:レンダリング後の出力を検証する

構造化データ(JSON-LD)・sitemap・RSS・robots は、壊れても画面では気づきにくい。E2E ならレンダリング後の出力を直接検証できます。

test("ブログ記事に BlogPosting 構造化データが出る", async ({ page }) => {
  await page.goto("/blog/tanstack-query");
  const raw = await page
    .locator('script[type="application/ld+json"]')
    .first()
    .textContent();
  const data = JSON.parse(raw ?? "{}");
  expect(data["@type"]).toBe("BlogPosting");
  expect(data.headline).toBeTruthy();
});

集客に直結する SEO 要素を「壊れたら落ちる」状態にしておくと、リリースのたびに安心できます。


7. 本番ビルドに対して実行する構成

next dev には開発専用の挙動(未最適化・追加ログ)があります。E2E は**本番ビルド(next start)**に対して走らせ、本番に近い条件で検証します。Playwright の webServer がサーバー起動を管理します。

// playwright.config.ts(要点)
import { defineConfig, devices } from "@playwright/test";
const PORT = Number(process.env.PLAYWRIGHT_PORT ?? 3100);

export default defineConfig({
  testDir: "./tests/e2e",
  expect: { timeout: 7_500 },
  fullyParallel: true,
  forbidOnly: Boolean(process.env.CI),     // CI で .only を禁止
  retries: process.env.CI ? 2 : 0,         // 再試行は CI のみ
  use: {
    baseURL: `http://127.0.0.1:${PORT}`,
    trace: "on-first-retry",               // 失敗を再現する証拠を残す
    screenshot: "only-on-failure",
    video: "retain-on-failure",
  },
  projects: [
    { name: "chromium-desktop", use: { ...devices["Desktop Chrome"] } },
    { name: "mobile-safari", use: { ...devices["iPhone 14"] } }, // モバイルも検証
  ],
  webServer: {
    command: `npx next start --port ${PORT}`, // 専用ポートで本番ビルドを起動
    url: `http://127.0.0.1:${PORT}`,
    reuseExistingServer: !process.env.CI,     // ローカルは再利用、CI は毎回新規
    timeout: 120_000,
  },
});

専用ポート(例 3100)にすると、別端末で next dev を動かしたままでも衝突しません。デスクトップとモバイルの両プロジェクトで、レスポンシブの破綻も拾えます。


8. 可観測性とフレーク対策(信頼性)

E2E が「たまに落ちる」状態は、テスト全体の信頼を失わせます。落ちないテストにする設計が要です。

  • トレース・スクショ・動画を失敗時に残す。 trace: "on-first-retry" で、再現困難な失敗も Trace Viewer で時系列に追えます。
  • 再試行は CI のみ・少回数。 ローカルで retries: 0 にすると、フレークを開発中に発見できます。CI は 1〜2 回。
  • テスト間を独立に。 共有状態に依存しない。各テストは自分でセットアップする(beforeEachgoto)。
  • 並列実行を前提に設計する。 fullyParallel でも壊れないよう、テスト固有のデータを使う。
  • 固定スリープを使わない(第3章)。

9. CI での実行:速さとコストの最適化

  • シャーディングで分割並列。 --shard=1/4 のようにジョブを分け、ブロブレポートを後でマージすると、総時間を短縮できます。
  • 段階的に走らせる。 PR では主要ブラウザ+重要動線、main へのマージ時に全ブラウザ+全シナリオ、と段階化するとコスト効率が上がります。
  • ブラウザバイナリをキャッシュ。 npx playwright install の取得をキャッシュして CI 時間を削減。
  • HTML レポートを成果物に。 失敗時の調査を速くします。

「全部を毎回・全ブラウザで」は YAGNI になりがちです。守りたいリスクに対して、実行範囲を設計します。


10. アンチパターン

  • waitForTimeout で待つ。 フレークの主因。Web-first アサーションで自動待機する。
  • CSS/XPath セレクタに依存する。 デザイン変更で壊れる。ロール/ラベルで掴む。
  • 本物の外部 API(決済・メール)に到達させる。 コスト・信頼性・セキュリティの問題。page.route でモック。
  • next dev に対してテストする。 本番ビルド(next start)で検証する。
  • テスト間で状態を共有する。 順序依存はフレークと調査困難を生む。各テストを独立に。
  • 何でも E2E で書く。 純粋ロジックは単体へ。E2E は価値の高い動線に集中。
  • 実装詳細(内部 state・関数)をテストする。 ユーザーに見える振る舞いを検証する。

11. FAQ(よくある質問)

Q. E2E はどれくらいの数を書くべき? A. 「壊れたら致命的な動線」に絞るのが基本です。テストトロフィーの考え方では、土台を単体・結合で厚くし、E2E は少数精鋭にします。

Q. getByRolegetByTestId、どちらを使う? A. まず getByRole / getByLabel(ユーザー視点=a11y にも効く)。それで一意に掴めない場合の最後の手段として data-testid を使います。

Q. ログインが必要なフローはどうする? A. 認証状態を storageState に保存して使い回すのが定石です。毎テストでログイン UI を踏まず、高速かつ安定します。

Q. フレーク(不安定テスト)が止まらない。 A. 固定スリープ・順序依存・共有状態・実装詳細依存のいずれかが大半の原因です。Web-first アサーションと独立性で潰し、Trace Viewer で根本原因を特定します。

Q. Playwright と Cypress の違いは? A. Playwright はマルチブラウザ(Chromium/WebKit/Firefox)対応、並列実行、自動待機、トレースが強力です。本番ビルド検証や CI 並列との相性も良好です。


まとめ:E2E は「リリースの信頼」を作る設計物

E2E テストは、書けば安心というものではありません。壊れにくく・速く・確定的であって初めて、チームが信頼してリリースできます。

  1. 役割分担。 純粋ロジックは単体、動線・SEO・a11y は E2E。
  2. ロール基準のロケーターで、壊れにくさと a11y を両取りする。
  3. Web-first アサーションで固定スリープを排除し、フレークを断つ。
  4. 外部 API はモックし、コスト・信頼性・セキュリティを守る。
  5. 本番ビルドに対して実行し、可観測性(トレース)と CI 並列で運用に耐えさせる。

テストが信頼できると、開発速度が上がります。「変更が怖くない」状態こそ、保守性と拡張性の本質です。

本番運用に耐える E2E・テスト基盤の構築、あるいは品質保証の仕組みづくりが必要な場合は、お気軽にご相談ください。 下記の事例では、複数サービスを束ねる社内プラットフォームを、品質と信頼性を重視して設計・実装した過程を紹介しています。

友田

友田 陽大

وزیر (METI) ایوارڈ یافتہ پروڈکٹ کے ڈویلپر۔ TypeScript + Python + AWS کے ساتھ میں SaaS، صنعتی ڈیجیٹل تبدیلی اور پروڈکشن کے قابل جنریٹو اے آئی (RAG) تنہا ابتدا سے انتہا تک فراہم کرتا ہوں — تقاضوں کی تعیین سے انفراسٹرکچر اور آپریشن تک۔

Got a challenge?

From design to implementation and operations — solo × generative AI

Implementation like this article's, end to end from requirements to production. Start with a free 30-minute technical consult and tell me about your situation.

پروجیکٹ کی بنیاد پر کام اور تکنیکی مشاورت دونوں کے لیے دستیاب ہوں۔ 30 منٹ کی مفت مشاورت سے آغاز کریں۔

最短ルート:カレンダーから直接予約

相談内容が固まっている方は、フォーム送信よりその場で日程を確定する方がスムーズです。下記から空き時間をお選びください。

  • 30分のオンライン無料相談
  • Google Meet / Zoom / Microsoft Teams
  • NDA 商談前締結可・無理な営業はいたしません
無料相談の空き枠を予約する

یہ بھی پڑھنے کے قابل