6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PlaywrightのFlakyと向き合う

Last updated at Posted at 2025-08-13

PlaywrightのFlakyと向き合う

なぜPlaywrightテストがたまに失敗するのか :thinking:

条件はかわらないのにたまに失敗するテストがある。。。

E2Eテストを書いている方なら、一度は経験したことがあるのではないでしょうか。コードは何も変えていないのに、テストが時々失敗する。これがいわゆる「フレイキーテスト」です。

フレイキーテストは開発チームにとって本当に厄介な存在ですね。CI/CDパイプラインが予期せず失敗して、リリースが遅れる。デバッグのために呼び出されて、結局「もう一回実行したら通りました」で終わる。テストへの信頼性が低下し、だんだんと「どうせフレイキーだから...」と無視されるようになってしまいます。

Playwrightでフレイキーテストが発生する主な原因は、タイミング問題、非同期処理の競合状態、外部サービスへの依存、そしてアニメーションやトランジションの影響などです。今回は、これらのフレイキーテストを見つけて、対処する方法を紹介します。

結論

  • --repeat-eachオプションでフレイキーテストを効率的に検出
  • HTMLレポートでフレイキーテストを可視化して成功率を把握
  • トレースビューアーで失敗の原因を詳細に分析
  • 実践的な対処法でフレイキーテストを根本から解決

それでは、実際に試してみましょう!

検証環境

項目 バージョン
@playwright/test 1.53.2
Node.js 22.x
VSCode拡張 Playwright Test for VSCode

--repeat-eachオプションでフレイキーテストをあぶり出す :mag:

まずは、フレイキーテストを見つけることから始めましょう。Playwrightには--repeat-eachというオプションがあります。これを使うと、各テストを指定した回数だけ繰り返し実行します。

基本的な使い方

# 各テストを10回ずつ実行
npx playwright test --repeat-each=10

# 特定のテストファイルに対して20回実行
npx playwright test tests/login.spec.ts --repeat-each=20

# 並列実行と組み合わせて効率化(おすすめ!)
npx playwright test --repeat-each=5 --workers=4

# 特定のプロジェクトに対して実行
npx playwright test --repeat-each=10 --project=chromium

# タグ付きテストのみ繰り返し
npx playwright test --repeat-each=3 --grep="@critical"

開発環境で50回ぐらいやって3~4回ぐらい失敗すればそれはだいたいフレーキーです。しかもだいたい同じ箇所で失敗します。
CI環境で走らせる前にフレーキーなテストは潰し終わっているのが理想です。並列実行(--workers)と組み合わせると、マシンリソースがより枯渇するせいかよりあぶりだしやすくなります。

HTMLレポートでフレイキーテストを可視化する :chart_with_upwards_trend:

フレイキーテストを見つけたら、次はPlaywrightのHTMLレポートで可視化して分析しましょう。

HTMLレポートの設定

playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  reporter: [
    ["html", { open: "never" }]
  ],

  // リトライ設定(フレイキー検出に必須)
  retries: process.env.CI ? 2 : 1,
});

スクリーンショット 2025-08-14 9.52.29.png

HTMLレポートを開くと、フレイキーテストには特別な「Flaky」タグが付いて表示されます。成功率、失敗パターン、実行時間の分布なども確認できるので、問題の傾向が見えてきます。

# レポートを生成して開く
npx playwright show-report

レポート画面では、フレイキーテストだけをフィルタリングして表示することもできます。Timeline viewを使えば、どのタイミングで失敗しているかのパターンも分析できます。

VSCode拡張のトレースビューアーで失敗原因を突き止める

VSCode拡張(Playwright Test for VSCode)を使うと、トレースを直接VSCode内で表示できます。

VSCodeでのトレース

  1. Test Explorerでテストを実行

    • VSCodeのサイドバーでShow trace viewerにチェックをつける
    • テストを実行
  2. トレースビューアーで分析

    • ステップごとのスクリーンショット
    • DOM変化のタイムライン
    • ネットワークリクエストの詳細
    • コンソールログの確認

よくあるフレイキーパターンの特定

トレースビューアーで以下のパターンを確認すると、原因が特定しやすくなります

  • 要素が見つからない: DOMの更新タイミングの問題
  • ネットワークエラー: APIレスポンスの遅延
  • タイムアウト: アニメーションや非同期処理の完了待ち

失敗の瞬間の前後を詳細に確認でき、フレイキーの原因特定に非常に有効です。

フレイキーテストを減らすための実践的アプローチ

タイミング問題の解決

// ❌ 悪い例 - 固定時間待機(あまり良くない。マシンリソースによって失敗しやすい)
await page.waitForTimeout(1000);
await page.click('#submit');

// ✅ 失敗しにくい例 - 条件待機
await page.waitForURL('/home');
await expect(page.locator('#submit')).toBeVisible();
await page.click('#submit');

ネットワーク依存の排除

外部APIに依存するテストは、ネットワークの状況でフレイキーになりがちです。モックを活用してみるか、下記で述べているポーリングを使ってみましょう(せっかくe2eしているのでモックよりはポーリングがいいかもしれない)

// APIモックの設定
await page.route('**/api/data', async route => {
  await route.fulfill({
    status: 200,
    contentType: 'application/json',
    body: JSON.stringify({ data: 'mocked' })
  });
});

// リクエストの完了を待つ
const responsePromise = page.waitForResponse(
  response => response.url().includes('/api/data') && response.status() === 200
);
await page.click('#fetch-data');
await responsePromise;

アニメーション・トランジションの処理

アニメーションもフレイキーテストの原因になります。

ポーリング

既存のアプリケーションでアニメーションを無効化するのは難しい場合もあります。その場合は、expect.pollを使ってアニメーションの影響を受けにくい状態を待つことができます。

実際にプロジェクトで使っている例を以下に示します。

element-visibility.ts
/**
 * ポーリング処理のためのユーティリティクラス
 */
class ActionWhile<T> {
  constructor(
    private fn: () => Promise<T>,
    private options: {
      intervals?: number[];
      timeout?: number;  // タイムアウト設定
    } = {
      intervals: [100],
      timeout: 30000
    },
  ) {}

  /**
   * 期待値と一致するまで待機する
   * @param expectedValue - 期待する値
   */
  async toBe(expectedValue: T): Promise<void> {
    await expect.poll(this.fn, this.options).toBe(expectedValue);
  }
}

/**
 * ポーリング処理を開始する
 * @param fn - ポーリングする関数
 * @param options - ポーリングのオプション
 * @returns ActionWhileインスタンス
 */
export function actionWhile<T>(
  fn: () => Promise<T>,
  options: {
    intervals?: number[];
    timeout?: number;
  } = {
    intervals: [500],
    timeout: 30000
  },
): ActionWhile<T> {
  return new ActionWhile(fn, options);
}

/**
 * 要素が表示されるまで待機する
 * @param locator - 確認対象の要素のLocator
 * @param options - ポーリングのオプション
 * @returns 表示された要素のLocator
 */
export async function waitForVisible(
  locator: Locator,
  options: {
    intervals?: number[];
    timeout?: number;
  } = {
    intervals: [100],
    timeout: 30000
  },
): Promise<Locator> {
  await actionWhile(async () => await locator.isVisible(), options).toBe(true);
  return locator;
}

自作したactionWhileを使うと、だいたいのフレーキーな箇所は解決できます。

例えば、shadcnのSelectコンポーネントを使っていると、選択肢をクリックした後にアニメーションが発生して、次の操作が失敗することがあります。そんなときは、以下のようにwaitForVisibleを使って待機します。

~~省略~~
  const selectForm = await stepProgress.step(`${label}が表示されるまで待機`, async () => {
    const selectForm = this.page.getByLabel(label);
    await waitForVisible(selectForm);
    return selectForm;
  });

  await stepProgress.step(`${label}をクリックする`, async () => {
    await selectForm.click();
    const divisionList = this.page.getByRole("listbox");
    await waitForVisible(divisionList);
  });
~~省略~~

まとめ

  1. 検出: --repeat-each=50で積極的にフレイキーテストを検出
  2. 分析: HTMLレポートで成功率と傾向を確認
  3. 原因特定: VSCodeトレースビューアーで詳細分析
  4. 対処: actionWhileなどのユーティリティで根本解決

マシンと人間の思い込みのギャップがフレイキーの原因です。
他に対策があれば、ぜひ教えてください!

現場からは以上です :santa:

参考リンク

6
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?