SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

クラウドE2Eテストを実現するMicrosoft Playwright Workspaces入門

Playwright Workspacesでマルチプラットフォーム・マルチブラウザの並列テストを実行する

クラウドE2Eテストを実現するMicrosoft Playwright Workspaces入門 第3回

モバイルエミュレーションを追加する

 デスクトップの3ブラウザが揃ったら、次はモバイル環境です。スマートフォンでの表示崩れやタッチ操作の不具合は、実際のサービスで頻繁に問題になる領域です。

devicesプリセットでモバイル環境を再現する

 モバイル向けのプロジェクトも、同じくdevicesで定義できます。端末名を指定すれば、ビューポートサイズ、ユーザーエージェント、デバイスピクセル比、タッチ操作の有無がまとめて設定されます。

[リスト3]モバイル向けのprojects定義(playwright.config.ts)
// Android Chromeのエミュレーション
{ name: 'mobile-chrome', use: { ...devices['Pixel 7'] } },
// iOS Safariのエミュレーション
{ name: 'mobile-safari', use: { ...devices['iPhone 14'] } },

 注意しておきたいのは、これがあくまでエミュレーションである点です。実機ではなく、クラウド上のブラウザに画面サイズやユーザーエージェントを合わせているにすぎません。レイアウトの検証には有効ですが、端末固有の描画差異や性能に起因する問題までは検出できません。

モバイル固有の検証を書く

 プロジェクトを追加したら、モバイルでしか意味を持たないテストも用意します。

[リスト4]レイアウト崩れの検証(tests/todo.mobile.spec.ts)
const viewport = page.viewportSize();
const box = await page.locator('#todo-form').boundingBox();

// フォームが画面幅からはみ出していないことを確認します
expect(box!.x).toBeGreaterThanOrEqual(0);
expect(box!.x + box!.width).toBeLessThanOrEqual(viewport!.width);

 boundingBoxは要素の位置とサイズを返すメソッドです。viewportSizeと突き合わせれば、レイアウトが画面外へあふれていないことを機械的に判定できます。

 タッチ操作の検証には、clickではなくtapを使います。tapはタッチイベントを発生させるため、タッチ専用のハンドラーが正しく動くかまで確認できます。利用にはタッチが有効になっている必要がありますが、モバイル端末のプリセットには最初から含まれています。

図2:モバイルエミュレーションで表示したTODO画面
図2:モバイルエミュレーションで表示したTODO画面

 図はPixel 7のエミュレーション環境でTODO一覧を表示したものです。デスクトップとは異なる幅で描画されていることが分かります。なお、モバイル向けのテストはデスクトップのプロジェクトで実行しても意味がないため、projectsのtestIgnoreやtestMatchで対象ファイルを絞り込んでおきましょう。

並列化で顕在化するテストの脆さ

 プロジェクトが5つになりました。しかし現時点でこれらのテストを実行すると、並列実行時に問題が発生します。

並列実行したら落ちるテストの例

 前回書いたテストのまま5つのプロジェクトを並列実行すると、次のエラーが発生します。

[リスト5]マトリクス実行で発生したエラーの例
Error: strict mode violation:
  getByText('記事を執筆する') resolved to 2 elements:
    1) <span>記事を執筆する</span>
    2) <span>記事を執筆する</span>
  4 failed
    [firefox] › TODOを追加・削除できる
    [webkit] › TODOを追加・削除できる
    ...

 単独では通っていたテストが、並列化した途端に落ちるようになりました。しかも失敗するプロジェクトは実行のたびに変わります。典型的なFlaky(不安定な)テストの症状です。原因は2つあります。

原因1:テストデータがサーバー全体で共有されている

 サンプルのTODOアプリは、TODOをサーバー側の配列ひとつで保持しており、ユーザーごとにデータが分かれる作りにはなっていません。

 つまり、5つのプロジェクトが同時にテストを走らせると、全員が同じ一覧に書き込みます。前回のテストは「記事を執筆する」という固定の文言でTODOを追加していたため、同じ文言のTODOが次々と積み上がってしまいます。

原因2:ロケーターが複数の要素にマッチしている

 原因1に加えて、前回のテストは次のように書かれていました。

[リスト6]修正前のテスト(tests/todo.spec.ts)
await page.getByPlaceholder('やることを入力').fill('記事を執筆する');
await page.getByRole('button', { name: '追加' }).click();
await expect(page.getByText('記事を執筆する')).toBeVisible();
await page.getByRole('button', { name: '削除' }).click();

 Playwrightのロケーターにはstrict modeという仕組みがあり、ひとつの要素を指すはずのロケーターが複数にマッチするとエラーになります。一覧に「記事を執筆する」が複数並べば、getByTextはその全てにマッチします。削除ボタンも同様で、一覧全体から探せば行の数だけマッチしてしまいます。

 これは意図せず別の要素を操作してしまう事故を防ぐ安全装置であり、エラーが出たらロケーターの絞り込みが不十分であったサインだと受け止め、修正をしていきます。

修正1:テストデータを一意にする

 まず、テストが投入するデータが衝突しないようにします。プロジェクト名を文言に含めるのが簡単で確実です。プロジェクト名はtest.info().project.nameで取得できます。

[リスト7]テストデータを一意にする(tests/fixtures.ts)
// 他プロジェクトの投入分と衝突しないよう、文言を一意にする
const text = `${baseText}(${testInfo.project.name})`;
await page.getByPlaceholder('やることを入力').fill(text);
await page.getByRole('button', { name: '追加' }).click();
await expect(todoItem(page, text)).toHaveCount(1);

 これで、chromiumが追加したTODOとfirefoxが追加したTODOは別物として区別されます。アプリ側にデータ分離の仕組みがなくても、テスト側の工夫だけで独立性を確保することができます。

修正2:操作対象のスコープを絞る

 次に、削除の対象を正しく指定します。一覧全体から削除ボタンを探すのではなく、目的のTODOが含まれる行に絞り込んでから、その中のボタンを押します。

[リスト8]行単位にスコープを絞る(tests/fixtures.ts)
// 一覧から特定のTODO行だけを取り出すロケーター
export function todoItem(page: Page, text: string): Locator {
  return page.getByRole('listitem').filter({ hasText: text });
}

// 該当行の中の「削除」ボタンだけを押す
await todoItem(page, text).getByRole('button', { name: '削除' }).click();

 filterは、ロケーターの候補を条件で絞り込むメソッドです。hasTextで目的の文言を含むリスト項目だけに限定し、そのうえでgetByRoleを続けることで、検索範囲がその行の内部に閉じます。

 「まず領域を特定し、その中の要素を操作する」というのは、ロケーターを安定させるための基本パターンです。ページ全体を対象にしたロケーターは、画面に要素が増えた瞬間に壊れます。

修正3:テストが作ったデータを片付ける

 最後に、投入したTODOを終了時に削除します。放置すると一覧がテストデータで埋まり、後続のテストが重くなっていきます。

 この後片付けは、Playwrightのフィクスチャで自動化できます。テストごとに追加した文言を記録しておき、useの呼び出しより後に削除処理を書けば、テストが失敗した場合でも確実に実行されます。並列実行を前提にするなら自分が作ったものは自分で片付ける、を徹底しておきましょう。

retriesとtraceで原因を追う

 修正を入れても、ネットワークの揺らぎで散発的に失敗することはあります。そうした失敗に備え、設定ファイル(playwright.config.ts)でretriesに2を、useのtraceにon-first-retryを指定しておきます。

 on-first-retryは、最初のリトライのときだけTraceを記録する設定です。常時採取すると実行時間とアーティファクトの容量が膨らむため、失敗時だけ詳細を残すこの設定が実用的なバランスになります。Traceの読み方は次回詳しく扱います。

 なお、リトライで通ったテストは「Flaky」として集計されます。通ったからよしとせず、件数は継続的に監視しておきましょう。増えているなら、テストの独立性が崩れかけているサインです。

次のページ
--workersで実行時間を縮める

この記事は参考になりましたか?

クラウドE2Eテストを実現するMicrosoft Playwright Workspaces入門連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 秋葉 龍一(アキバ リュウイチ)

<WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。 2026年時点での登録メンバは約50名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書、記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるLaravel実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/29770 2026/10/01 09:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー