こんにちは、フロントエンドエンジニアの小張(@kobari41257)です。
今回、肥大化していたE2Eテストのシナリオを整理し、Playwrightを使った実行基盤に移行したことで、Flaky率や実行時間を改善した取り組みについてご紹介します。
背景
これまでPR TIMESではAutify NoCode(以下Autify)を用いたE2Eテストを行ってきました。


しかし、機能追加に伴い新しいテストケースが追加されシナリオ数が肥大化したことで、Flakyなシナリオが放置され、大量の失敗通知の中に正常に検知されたバグが埋もれてしまう問題が発生していました。
実際に1週間に実行されたシナリオの実行結果を集計すると、約16%が失敗していたことがわかりました。
| 1週間の総数 | 全体に対する割合 | |
|---|---|---|
| 成功したシナリオ | 1,055 | 84% |
| 失敗したシナリオ | 205 | 16% |
またそれぞれのシナリオの実行に平均で30分近くかかっていたため、失敗したテストの調査に時間がかかってしまうといった課題も抱えていました。
そこでFlakyなテストを減らし、エンジニアのメンテナンスコストを削減することを目的に、E2Eテストの整理と再構築の取り組みを始めました。
テストシナリオの精査
まず始めに、増えすぎてしまったテストシナリオの見直しを行いました。
Autify上には約150個のシナリオがありましたが、細かな要素の表示など重要度の低いテストが混ざっているものが多くありました。
そこで、E2Eテストを実施してきた最大の目的である「リリース時に既存の重要機能が正常に動作することを担保する」に改めて立ち返り、重要な機能の確認に絞った53個のシナリオを新しく作成しました。
重要な機能の線引きでは、以下の基準を一つの判断基準としました。
- ユーザー情報の公開・非公開や、ユーザー権限による表示の切り替えに関わる機能
- 例:企業ページの公開設定やメディア関係者向けプレスリリースなど
- ユーザーの情報発信や情報受信に関わる機能
- 例:プレスリリース配信やストーリー配信など
また今後の機能追加でE2Eテストを追加する際のガイドラインを作成しました。
(※ patrolというGitHubリポジトリでE2Eテストを管理しています。)

現状のエンジニアのメンテナンス工数を踏まえると、PR TIMESの開発組織では「網羅的なE2Eテストがある状態」よりも「重要な機能にのみE2Eテストがある状態」が望ましいと考えました。
E2Eテストの網羅性を高めようとしすぎると、Flakyなテストが増えた結果信頼できないE2Eテストになりやすく、本物のバグによる失敗を見逃しやすくなるためです。
様々な場合分けやパターンのテストはユニットテストで行い、重要な機能についてはE2Eで重ねてテストする方針をガイドライン化しました。
E2Eテスト環境の移行
次に、E2Eテスト環境の見直しを行いました。
AutifyのようなNoCodeツールに慣れているエンジニアが少なかったこともあり、Playwrightのようなコードでテストを管理できるツールが望ましいと考えました。
以前から実行時間の長いシナリオをPlaywrightに置き換える取り組みを進めていたこともあり、今回のタイミングでAutifyから全面的にPlaywright(+ GitHub Actions)に移行することにしました。

Playwright環境への移行で工夫したことをいくつかご紹介します。
AIによるテストコード生成
当初DevinなどのAIツールを用いてPlaywrightのテストコードを生成させようとしましたが、テスト対象であるステージング環境にアクセスさせることが難しかったこともあり、精度の高いテストコードを得ることができませんでした。
しかしClaude CodeなどのCLI上で実行できるAIの登場により、ステージング環境にアクセス可能な開発者のPC上でAIエージェントを起動し、テスト対象のHTMLをもとにテストコードを生成させることが可能になりました。
実際に以下のようなプロンプトを使いながら、AIにPlaywrightコードを出力させることで移行の工数を大幅に削減することができました。
# 前提
- テストの実行は以下のコマンドを実行してください
```
pnpm test <path>
```
- フォーマットの実行するのは以下のコマンドを実行してください
```
pnpm lint:fix <path>
```
- エラーと修正を5回繰り返したら、一旦作業を中断して、どこでなぜ詰まっているのか報告してください
- 大きいタスクは分割して、それぞれの分割した範囲で以下に書いている「シナリオの実装手順」を実行してください
# Playwrightのコーディングガイドライン
- セレクターの選択では、以下の優先順位で実装する:
**ロケーター優先**: `getByRole()`, `getByText()`, `getByLabel()`などのロケーターを最優先で使用
- **待機処理**: `waitForTimeout()`は使用せず、Web ファーストアサーションを使ってDOM要素の表示を待つ
- **最小限の実装**: 最小限で実装できたらまずテストを実行して確認することを徹底してください(動くようになったら次にいくようにしてください)
- **実証的アプローチ**: セレクターが動作するかわからない場合は、一時的にデバッグ用のコンソールログを追加して確認する
- デバック用のコードは最終的に削除すること
- **UIモードの活用**: セレクターで困った場合はPlaywright UIモードを活用する
- `pnpm exec playwright test --ui`でUIモードを起動
- Pick Locatorで正確なセレクターを生成
- 実際のDOM構造と要素のroleを確認
- 推測ではなく実際の構造に基づいてセレクターを作成
- testIdが存在する場合は複雑なロジックよりもtestIdを優先
- 複数のセレクターの組み合わせ(`.or()`)は、実際に必要性が証明されてから使用する
- 過度に冗長なフォールバック実装は避け、シンプルで読みやすいコードを心がける
# シナリオの実装手順
1. シナリオを確認する(シナリオが提示されていなかったらユーザーに尋ねる)
2. シナリオに対応するテストファイルがなければ作成する(どのテストファイルを使って良いかわからなければユーザーに尋ねる)
3. テストに対応するPOMファイルがもし存在しなければ新規にPOMファイルを作成する(どのPOMを使って良いかわからなければユーザーに尋ねる)
4. シナリオを実行するために必要なブラウザ操作をPOMに追加していく
5. テストファイルでPOMで定義した操作を呼び出し、アサーションについてはPOMに書かずにテストファイルに直で書く
6. テストファイルにPOMを呼び出したりアサーションするときは、対応するシナリオをコメントで書く
7. playwrightを実装できたらテストを必ず実行すること
8. シナリオ通りのテストがpassすることを確認する
9. 最後に、フォーマットエラーを解消し、作業完了とする特に有用だった知見として、POM(Page Object Model)が整備されているページではAIが出力するテストコードの精度が大きく向上することがありました。
POMについて詳しくは以下の記事をご覧ください。

予約配信のテスト
Autifyでは「10分後に配信を予約したプレスリリースが時刻通り配信されること」をテストするため、Sleep Stepを使って10分間待機する処理を行っていました。
これをPlaywrightでsetTimeoutなどを使って実装すると、待機している間GitHub Actionsの料金がかかってしまうため望ましくありません。
そこで「プレスリリースを予約配信するテスト」と「時刻通り配信されることを確認するテスト」を別のGitHub Actionsワークフローとして動かし、2つのワークフローの実行開始時間をずらすことで対応しました。
# release-check.yaml
name: E2Eテストを実行する
on:
schedule:
- cron: "0 12 * * 1-5" # 日本時間で21:00
jobs:
e2e-test:
steps:
- name: Run E2E tests
# E2Eテストを実行する
run: pnpm exec playwright test --project=default# release-check-reserved.yaml
name: 予約配信が時刻通り配信されることを確認するE2Eテストを実行する
on:
schedule:
- cron: "0 13 * * 1-5" # 日本時間で22:00(1時間ずらす)
jobs:
e2e-test:
steps:
- name: Run E2E tests
# 予約配信を確認するE2Eテストを実行する
run: pnpm exec playwright test --project=reservedVRTとAPIモックを禁止するESLintルールの作成
Playwrightでは画面のVRT(Visual Regression Test)やAPIリクエストのモックを行うことができます。
VRTの活用事例ついては以下の記事をご覧ください。

今回E2Eテストを構築するにあたり、UIに依存するFlakyなテストを減らしたかったためVRTは行わないことにしました。
またE2EテストとしてバックエンドAPIも含めたテストを行いたかったため、APIリクエストのモックも禁止することにしました。
これらのルールをESLintに追加することで、新規メンバーやAIエージェントが迷わず開発できる環境を整えています。
const xoConfig: FlatXoConfig = {
rules: {
'custom-rules/no-playwright-tohavescreenshot': 'error',
'custom-rules/no-playwright-route-fulfill': 'error',
},
plugins: {
'custom-rules': {
rules: {
'no-playwright-tohavescreenshot': {
meta: {
type: 'problem',
docs: {
description: 'VRTテストを禁止するルール',
category: 'Best Practices',
},
messages: {
prohibited:
'patrolではVRTテストを禁止しています。理由はE2EテストがFlakyになってしまうためです。',
},
},
create(context) {
return {
CallExpression(node) {
if (
node.callee.type === 'MemberExpression' &&
node.callee.property.type === 'Identifier' &&
node.callee.property.name === 'toHaveScreenshot'
) {
context.report({
node,
messageId: 'prohibited',
});
}
},
};
},
},
'no-playwright-route-fulfill': {
meta: {
type: 'problem',
docs: {
description: 'APIのmockを禁止するルール',
category: 'Best Practices',
},
messages: {
prohibited:
'patrolではAPIのmockを禁止しています。理由はAPIをmockしてしまうとE2Eテストではなくなってしまうためです。',
},
},
create(context) {
return {
CallExpression(node) {
if (
node.callee.type === 'MemberExpression' &&
node.callee.property.type === 'Identifier' &&
node.callee.property.name === 'fulfill' &&
node.callee.object.type === 'Identifier' &&
node.callee.object.name === 'route'
) {
context.report({
node,
messageId: 'prohibited',
});
}
},
};
},
},
},
},
},
};
export default xoConfig;
Self-hosted runners上でPlaywrightを実行する
当初GitHub-hosted runners上でPlaywrightを実行していましたが、2つの課題がありました。
- テスト対象となるステージング環境はIPアドレスによるアクセス制限があるが、GitHub Actionsの実行環境のIPは動的であるため、制限を回避するために少々huckyな方法を使う必要がある
- GitHub-hosted runnersは主にUSリージョンにあるため、東京リージョンにあるステージング環境へのアクセス時にネットワーク遅延が発生しテストがFlakyになる
そこでSelf-hosted runnersとしてEC2を立ててGitHub Actionsを実行するようにしました。

上記の課題をそれぞれ以下のように解決しています。
- Elastic IPを付与したEC2をPublic subnetに配置し、固定したIPアドレスをステージング環境のIPアドレス制限で許可する
- EC2を東京リージョンに構築し、ステージング環境とのインターネット通信を高速化する
ステージングアカウントのパスワード管理
Playwrightからステージング環境にログインできるようにするため、ステージングアカウントのパスワードをPlaywright実行時に渡す必要がありました。
GitHubにパスワードをpushせずに渡すようにするため以下のような工夫を行いました。
まずCI環境ではGitHub secretsを環境変数に渡すようにしました。
- name: Run E2E tests
run: pnpm exec playwright test --project=default # E2Eテストを実行する
env:
# パスワードをGitHub secretsから渡す
PATROL_ACCOUNT_PASSWORD: ${{ secrets.PATROL_ACCOUNT_PASSWORD }}次にローカル環境では1Password CLIで環境変数に渡すようにしました。
具体的には以下のように.env.local.example に1Passwordのsecret referencesを渡しておき、
# .env.local.example
PATROL_ACCOUNT_PASSWORD="op://foo/bar/password"以下のbashスクリプトを実行し、.env.local を生成します。
#!/bin/bash
set -euo pipefail
ENV_LOCAL_FILE='.env.local'
ENV_EXAMPLE_FILE='.env.local.example'
# .env.localが既に存在するかチェック
if [[ -f "$ENV_LOCAL_FILE" ]]; then
echo '.env.localは作成済みです。'
exit 0
fi
# op injectでopを展開して.env.localを作成
echo '1Password CLIを使用して環境変数を展開しています...'
op inject -i "$ENV_EXAMPLE_FILE" -o "$ENV_LOCAL_FILE"
echo 'セットアップが正常に完了しました!'
# .env.local(スクリプトによる自動生成)
PATROL_ACCOUNT_PASSWORD="<<実際のパスワード>>"このbashスクリプトをpackage.jsonのpostinstallに設定することで、pnpm install時に自動で.env.local が生成されるようにしています。
// package.json
{
"scripts": {
"postinstall": "bash ./setup-env.sh",
}
一方CIでは1Password CLIを実行できないため、--ignore-scripts を使ってbashスクリプトを実行しないようにしています。
- name: Install deps
run: pnpm install --ignore-scripts # postinstallを実行しないようにする結果
テストシナリオの精査とPlaywright環境への移行により、以下のような結果が得られました。
Flakyなテストの減少
移行前は16%だったFlaky率が2%に減少しました。
| 1週間の総数 | 全体に対する割合 | |
|---|---|---|
| 成功したシナリオ | 370 | 98% |
| 失敗したシナリオ | 5 | 2% |
またシナリオの全体数が少なくなったことで、失敗率だけでなく失敗の数自体も抑えられ、現実的な工数でエンジニアがメンテナンスできるようになりました。
実行時間の短縮
移行前は3時間以上かかっていた実行時間が25分に減少しました。
その結果容易に再実行できるようになり、失敗したテストの調査や急遽スポットでE2Eテストを回すことなどが可能になりました。

Slack通知のカスタマイズがしやすくなった
GitHub ActionsからPlaywrightの実行結果をSlack通知するようにしています。
この際失敗したテストに関係するメンバーが自動でメンションされるように、独自でスクリプトを書いており、失敗したテストが放置されない仕組みを導入しています。

GitHub Actions上にE2E基盤を移したことで、こうしたカスタマイズの自由度が上がったことはメリットの一つだと思っています。
まとめ
E2Eテストのシナリオを整理し、実行環境をエンジニアが管理しやすいPlaywrightに移行したことで、Flaky率や実行時間を改善するなど、より長期的に保守しやすいE2Eテストを構築することができました。
今回の移行をきっかけにE2Eテストやユニットテストの役割分担など、全体的な視点からテスト戦略について議論する機会が増えました。
テストの充実度とメンテナンスコストのバランスを意識しながら、今後も品質の高いリリースを続けていきたいと思います。
We are hiring!
開発部では様々なポジションでの採用を進めています!興味があればぜひご応募ください。

