create_e2e_tests について
create_e2e_testsは、実装前にWebアプリのテストを計画したい開発者向けの、ローカルYAML E2Eテスト作成スキルです。方針、シナリオ仕様、実装計画をつなぎ、ブラウザーでのテスト作成とカバレッジ確認を行い、Shiplightでローカル実行します。
主な機能
- 01各段階で計画ドキュメントを作成
- 02Given/When/Then形式でシナリオを定義
- 03ブラウザーで実際のロケーターを取得
- 04YAMLテストファイルを検証
- 05仕様に対するテスト網羅性を確認
活用シーン
- 01ローカルテストプロジェクトの新規作成
- 02Webアプリ向けYAMLテストの追加
- 03認証や重要なユーザーフローの計画
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ shiplightai/ agent-skills --skill create_e2e_testsインストール後、Claude Code で/create_e2e_testsを入力して読み込まれたか確認します。
詳細
ローカル YAML テストを作成する
テストを作成する前に、構造化された計画を通じてテストの専門知識を取り入れる、仕様主導のワークフローです。テストは npx shiplight test --headed で実行します。クラウドインフラストラクチャは不要です。
使用するタイミング
ユーザーが次のことを希望する場合は、/create_e2e_tests を使用します。
- ローカルテストプロジェクトをゼロから新規作成する
- Web アプリケーション用の YAML テストを追加する
- テストプロジェクトの認証を設定する
- テストを作成する前に、何をテストするか計画する
原則
-
必ず成果物を作成します。 各フェーズで Markdown ファイルを書き出します。成果物は自分の考えを明確にし、ユーザーが確認できるものを提供し、後続のフェーズを導きます。ユーザーが詳細な要件を提示した場合は、それを情報源として使用します。すでに回答済みの質問は省略しますが、成果物は作成してください。
-
実装前に確認します。 ブラウザーでの操作確認やテスト作成に時間をかける前に、仕様(フェーズ 2 のチェックポイント)を提示し、ユーザーの確認を得ます。理解した内容を構造化されたシナリオとして伝え、早い段階で認識の不一致を見つけます。
-
各フェーズでは、前のフェーズの成果物を読み込みます。 Discover から Specify、Specify から Plan、Plan から Implement、Implement から Verify へと引き継ぎます。以前の実行で作成された成果物がある場合は、再利用を提案します。
-
ループせず、エスカレーションします。 失敗や曖昧な点があれば、黙って再試行するのではなく、状況を報告してユーザーに確認します。
フェーズの概要
Phase 1: Discover → test-strategy.md (understand the app & user goals)
Phase 2: Specify → test-spec.md (define what to test in Given/When/Then)
Phase 3: Plan → test-plan.md (prioritize, structure, per-test guidance)
Phase 4: Implement → *.test.yaml files (setup project, write tests, run them)
Phase 5: Verify → updated spec files (coverage check, reconcile spec ↔ tests)
ファストトラック
開始前に既存の成果物を確認します。成果物の作成を省略できるのは、ユーザーが明示的に指示した場合のみです。
| 状況 | 動作 |
|---|---|
| ユーザーが「実装に進んで」または「テストだけ書いて」と明示的に指示した場合 | フェーズ 4 のみ |
test-specs/test-strategy.md が存在する場合 | 再利用を提案し、フェーズ 1 を省略 |
test-specs/test-spec.md が存在する場合 | 再利用を提案し、フェーズ 1〜2 を省略 |
test-specs/test-plan.md が存在する場合 | 再利用を提案し、フェーズ 4 に進む |
フェーズ 1:Discover
目標: アプリケーション、ユーザーの役割、テストで最も重視すべき点を理解します。
出力: <project>/test-specs/test-strategy.md
手順
-
プロジェクトのパスを確認する — テストプロジェクトを作成する場所(例:
./my-tests)を尋ねます。すべての成果物とテストはここに保存します。test-specs/ディレクトリを作成します。クラウド MCP ツールが利用可能な場合(
SHIPLIGHT_API_TOKENが設定されている場合)は、/cloudスキルを使って環境とテストアカウントを取得します。これにより、対象 URL と認証情報を事前に入力できます。 -
サイレントスキャン — 質問をする前に、利用可能な情報からコンテキストを収集します。
- コードベース:ルート、コンポーネント、
package.json、フレームワーク - Git ブランチの差分(最近の変更)
- 既存のテスト(すでにカバーされている内容)
- PRD、ドキュメント、README ファイル
- クラウド環境(クラウド MCP ツールが利用可能な場合)
- コードベース:ルート、コンポーネント、
-
テスト対象を理解する — 何をテストしたいかをユーザーに尋ねた後、スキャン結果に基づく推奨事項を添えて、足りない情報を補うための的を絞った質問を一度に 1 つずつ尋ねます。確認する内容は、リスク領域、ユーザーの役割、認証、データ戦略、重要なユーザージャーニーです。ユーザーがすでに回答した質問は省略します。
-
test-strategy.mdを作成する。次の内容を含めます。- アプリの概要:名前、URL、フレームワーク、主要なページ/機能
- リスクの概要:最も重要な点、壊れやすい箇所
- テスト範囲:対象/対象外、カバーするユーザーの役割
- データ戦略:テストデータの作成方法とクリーンアップ方法
- 環境:対象 URL、認証方式、特別なセットアップ
フェーズ 2:Specify
目標: リスクの優先度に基づき、具体的なテストシナリオを構造化された Given/When/Then 形式で定義します。不安定または不完全なテストの原因となる曖昧さを明らかにします。
入力: test-specs/test-strategy.md を読み込みます。
出力: <project>/test-specs/test-spec.md
手順
-
読む:
test-strategy.mdを読んで、対象範囲と優先度を理解します。 -
ユーザージャーニーの仕様を作成する — 重要な各ジャーニーについて、次の項目を記述します。
- タイトル:説明的な名前(例:「メール認証を伴う新規ユーザー登録」)
- 優先度:P0(必須)、P1(推奨)、P2(あると望ましい)
- 前提条件:テスト開始前に満たされている必要がある条件(Given)
- 正常系:操作と期待される結果をステップごとに記述(When/Then)
- エッジケース:ジャーニーごとに少なくとも2つ(例:無効な入力、タイムアウト、空の状態)
- データ要件:必要なテストデータ
-
テストリスクを確認する — 不安定または不完全なテストにつながる問題がないか、各ジャーニーを確認します。データ依存関係、タイミング/非同期の動作、動的コンテンツ、認証境界、サードパーティサービス、状態の分離、環境の違いを確認してください。各ジャーニーに、特定したリスクと緩和策を記載するテスト時の注意事項セクションを追加します。不明瞭な点があれば、推奨回答と影響の説明を添えて、ユーザーに一度に1つずつ質問します。
-
すべてのジャーニー仕様を含む
test-spec.mdを作成します。 -
チェックポイント — ユーザーが確認できるよう、次の概要表を提示します。
# ジャーニー 優先度 ステップ数 エッジケース リスク 1 ユーザー登録 P0 5 3 タイミング 2 ... ... ... ... ... 「これは正しいですか?追加、削除、または優先度を変更するジャーニーはありますか?」と尋ねます。
続行する前に、ユーザーの確認を待ちます。
フェーズ 3:計画
目標: テストごとの指針を含む、実行可能な実装計画を作成します。
入力: test-specs/test-spec.mdを読み込みます
出力: <project>/test-specs/test-plan.md
手順
-
読む:
test-spec.mdを読みます。 -
テストファイルの構成を定義する — ジャーニーをテストファイルに割り当てます。
tests/ ├── auth.setup.ts (if auth needed) ├── signup.test.yaml (Journey 1) ├── checkout.test.yaml (Journey 2) └── ... -
実装順序を決める — 次の順序に基づいて並べます。
- まず依存関係(認証が必要なテストより先に認証のセットアップ)
- 次に優先度(P0をP1より先に)
- 次にリスク(最もリスクの高いものを先に)
-
テストごとの指針 — 各テストファイルについて、次を指定します。
- データ戦略:作成または使用するデータ、クリーンアップ方法
- 待機戦略:WAIT_UNTILとWAITの使い分け、想定される読み込み箇所
- 不安定化のリスク:このテストで特に注意すべき点
-
test-plan.mdを作成します。 -
チェックポイント — 次の概要を提示します。
N 個のテストファイルを実装する準備ができました。進めてもよろしいですか?
フェーズ 4:実装
目標: プロジェクトをセットアップし、計画に沿ってすべてのYAMLテストを作成します。
入力: test-specs/test-plan.mdを読み込みます
セットアップ
すでに完了している手順(プロジェクトの作成、依存関係のインストール、認証の設定)はスキップしてください。
-
AI プロバイダーを設定する — テストプロジェクトに AI API キーを含む
.envファイルがすでにあるか確認します。ない場合は、ユーザーにプロバイダーを選択してもらいます。YAML テストを実行するには、テスト手順を解決するための AI プロバイダーが必要です。どのプロバイダーを使いますか?
A) Google AI —
GOOGLE_API_KEY(キーを取得) — デフォルトモデル:gemini-3.1-flash-lite-previewB) Anthropic —ANTHROPIC_API_KEY(キーを取得) — デフォルトモデル:claude-haiku-4-5C) OpenAI —OPENAI_API_KEY(キーを取得) — デフォルトモデル:gpt-5.4-miniD) Azure OpenAI —AZURE_OPENAI_API_KEY+AZURE_OPENAI_ENDPOINTが必要です —WEB_AGENT_MODEL=azure:<deployment>を設定します E) AWS Bedrock — AWS の認証情報チェーンを使用します —WEB_AGENT_MODEL=bedrock:<model_id>を設定します F) Google Vertex AI — GCP Application Default Credentials を使用します —WEB_AGENT_MODEL=vertex:<model>を設定します G) すでに設定済みユーザーが選択したら、API キーを尋ねてテストプロジェクトの
.envファイルに保存します。A/B/C の場合、モデルはキーから自動検出されます。D/E/F の場合は、適切なprovider:modelプレフィックスを付けたWEB_AGENT_MODELも保存します。任意で、ユーザーはWEB_AGENT_MODELを設定してデフォルトモデルを上書きできます(例:WEB_AGENT_MODEL=claude-sonnet-4-6)。 -
プロジェクトをスキャフォールドする — 絶対パスを指定して
scaffold_projectを呼び出します。これにより、package.json、playwright.config.ts、.env.example、.gitignore、tests/が作成されます。API キーを.envに保存します。 -
依存関係をインストールする:
npm install npx playwright install chromium -
認証を設定する(必要な場合) — 標準の Playwright 認証パターンに従います。
playwright.config.tsに変数として認証情報を追加します。{ name: 'my-app', testDir: './tests/my-app', dependencies: ['my-app-setup'], use: { baseURL: 'https://app.example.com', storageState: 'tests/my-app/.auth/storage-state.json', variables: { username: process.env.MY_APP_EMAIL, password: { value: process.env.MY_APP_PASSWORD, sensitive: true }, // otp_secret_key: { value: process.env.MY_APP_TOTP_SECRET, sensitive: true }, }, }, },標準の変数名:
username、password、otp_secret_key。シークレットには{ value, sensitive: true }を使用します。値を.envに追加します。標準的な Playwright ログインコードを含む
auth.setup.tsを作成します。TOTP には、node:cryptoを使用して RFC 6238 を実装します(HMAC-SHA1 + base32 デコード)。サードパーティー依存関係は不要です。先に進む前に認証を検証してください。
npx shiplight test --headedを実行して認証セットアップを実行し、storage-state.jsonが保存されることを確認します。失敗した場合は、認証は他のすべての前提条件であるため、ユーザーに対応を求めてください。テスト計画に特別な認証要件(例:テストごとに別のアカウントを使う、複数のロールを使う)が含まれている場合は、先に進む前に認証戦略をユーザーに確認してください。
テストを書く
計画に含まれる各テスト(またはユーザーが希望する各テスト)について、次の手順に従います。
- ブラウザーセッションを開始する — アプリの
starting_urlを指定してnew_sessionを呼び出します。 - フローをたどる —
inspect_pageを使ってページを確認し、次にactを使って各操作を実行します。これにより、レスポンスからロケーターが取得されます。 - ロケーターを取得する — 必要に応じて
get_locatorsを使い、追加の要素情報を取得します。 - YAML を作成する — 以下のベストプラクティスに従って
.test.yamlの内容を作成します。 - 保存して検証する —
.test.yamlファイルを書き込んだ後、ファイルパスを指定してvalidate_yaml_testを呼び出し、ロケーターの網羅率を確認します(最低 50% が必要です)。 - セッションを終了する — 完了したら
close_sessionを呼び出します。
重要: 想像で YAML テストを書かないでください。必ず最初にブラウザーセッションでアプリを操作し、実際のロケーターを取得してください。ロケーターのないテストは validate_yaml_test によって拒否されます。
test-plan.md に沿って進める場合:
- 読み込み箇所では指定された待機戦略を適用します
- 仕様で定義されたエッジケースとアサーションを網羅します
テストの実行
すべてのテストを書き終えたら、実行します。
npx shiplight test --headed
テストが失敗した場合:
- 報告 — 失敗したテストとその理由を、1文でユーザーに伝えます。
- 分類 — 失敗の種類を特定します。
- 実装の修正(ロケーターの誤り、待機の不足、タイミング)→ 修正して再試行します。
- 仕様との不一致(アプリの動作が仕様と異なる)→ 仕様を更新するか、シナリオをスキップするかをユーザーに確認します。
- エスカレーション — 修正がうまくいかない場合は、同じ方法で再試行し続けず、エスカレーションします。
フェーズ 5:検証
目的: 仕様に対するテストカバレッジを検証し、差異を調整します。
入力: test-specs/test-spec.md、test-specs/test-plan.md、すべての .test.yaml ファイルを読み取ります。
このフェーズは、仕様の成果物が存在する場合にのみ実行されます。
カバレッジの確認
仕様に記載された各ジャーニーについて、テストが正常系の経路と、リストに記載されたすべてのエッジケースを網羅していることを確認します。
カバレッジの概要を提示します。
| 仕様上のジャーニー | 優先度 | 仕様で定義されたシナリオ | 作成したテスト | カバレッジ |
|---|---|---|---|---|
| ユーザー登録 | P0 | 4 | 4 | ✓ |
| チェックアウト | P0 | 3 | 2 | ✗ — エッジケース「カートが空」が未カバー |
不足している項目と追加項目(仕様にないテスト手順)を指摘します。
調整
実際に実装された内容に合わせて、仕様の成果物を更新します。
test-spec.mdを更新 — スキップしたシナリオとその理由を記録し、実装中に明らかになったシナリオを追加し、テストした内容を反映するようエッジケースを更新します。test-plan.mdを更新 — ファイル構成を修正し、当初の計画からの変更点を記録します。- 差分の概要を提示 — 何を、なぜ変更したかをユーザーに伝えます。
これにより、今後のテストの保守と拡張に向けて、成果物を正確な状態に保てます。
YAML 形式リファレンス
完全な言語仕様(ステートメントの種類、テンプレート、変数、スイート、フック、パラメーター化テスト)については、MCP リソース shiplight://yaml-test-spec-v1.3.0 を参照してください。
利用可能なアクションとそのパラメーターの完全な一覧については、MCP リソース shiplight://schemas/action-entity を参照してください。
YAML 作成のベストプラクティス
これらのベストプラクティスは、YAML 言語仕様とアクションカタログを結び付け、迅速で信頼性の高いテスト作成に役立ちます。
ステートメントの種類の選択
- ACTION がデフォルトです。 ブラウザーセッション中に MCP ツール(
act、get_locators)でロケーターを取得し、ACTION ステートメントを記述します。ACTION は決定論的に再生されます(約1秒)。 - DRAFT は最終手段です。 作成時にロケーターを本当に特定できない場合にのみ DRAFT を使用します。DRAFT は実行に時間がかかります(1件あたり約5〜10秒、実行時に AI が解決します)。DRAFT が多すぎるテストは
validate_yaml_testに拒否されます。 - アサーションには VERIFY を使用します。 すべてのアサーションに
VERIFY:を使用します。「ボタンが表示されていることを確認する」のようなアサーションの DRAFT は記述しないでください。 - ナビゲーションには URL を使用します。 ナビゲーションには
action: go_to_urlではなく、URL: /pathを使用します。 - スクリプトには CODE を使用します。 ネットワークのモック、localStorage の操作、ページレベルのスクリプトには
CODE:を使用します。クリック、アサーション、ナビゲーションには使用しません。
intent フィールド
intent はステップの意図です。ステップで何を達成するかを定義します。action/locator または js フィールドは、その方法を示すキャッシュです。キャッシュが無効になった場合(ロケーターが古い、DOM が変更されたなど)、AI エージェントは intent を使ってページを再確認し、アクションを一から再生成します。
自己修復を担う intent は、ほかにコンテキストがなくてもエージェントが実行できるだけの具体性が必要です。DOM 要素ではなく、ユーザーの目標を記述してください。UI の変更で壊れる要素インデックス、CSS セレクター、位置参照は避けてください。
# BAD: vague, agent can't re-derive the action
- intent: Click button
# BAD: tied to DOM structure that can change
- intent: Click the 3rd button in the form
- intent: Click element at index 42
# GOOD: describes the user goal, stable across UI changes
- intent: Click the Submit button to save the new project
action: click
locator: "getByRole('button', { name: 'Submit' })"
ACTION:構造化形式と js: の簡略記法
サポートされているすべてのアクションでは、デフォルトで構造化形式を使用します。利用可能なアクションとそのパラメーターの完全な一覧については、MCP リソース shiplight://schemas/action-entity を参照してください。
サポートされているアクションに対応しない場合に限り、js: を使用します。 たとえば、複雑な複数ステップの操作、カスタム Playwright API 呼び出し、連続した操作などです。
- intent: Drag slider to 50% position
js: "await page.getByRole('slider').first().fill('50')"
- intent: Wait for network idle after form submit
js: "await page.waitForLoadState('networkidle')"
js: のコーディングルール
- Playwright の strict-mode エラーを避けるため、ロケーターは必ず単一の要素に解決します(例:
.first()、.nth(1))。 - タイミングを予測可能にするため、アクションには必ず
{ timeout: 5000 }を指定します。 intentは重要です。jsが失敗したときの自己修復に使われる入力です。page、agent、expectはスコープ内で使用できます。
VERIFY のベストプラクティス
- AI フォールバックがある
js:アサーションには、必ず短いタイムアウト(例:{ timeout: 2000 })を指定します。これにより、古くなったロケーターはデフォルトの 5 秒待つのではなく、すぐに AI にフォールバックします。 - 必ず
VERIFY:の省略形を使います。action: verifyを直接使わないでください。 js:アサーションの偽陰性に注意してください。 AI フォールバックが実行されるのは、jsが例外をスローした場合(要素が見つからない、タイムアウトなど)のみです。jsが誤った要素に対して成功した場合(古いセレクターが別の要素に一致した場合)、アサーションは気付かれないまま成功し、フォールバックは実行されません。このリスクを最小限に抑えるため、js:アサーションはシンプルかつ具体的にしてください。
IF/WHILE の js: 条件のベストプラクティス
- DOM に基づく確認(要素の表示、テキストの存在、ページの状態)には、自然言語(AI)の条件を使います。 AI 条件は DOM の変更に対して自己修復しますが、
js:条件は壊れやすく、自動修復できません。 js:条件はカウンターや状態のロジックにのみ使います(例:js: counter++ < 10、js: retryCount < 3)。js: document.querySelector('.modal') !== nullのような DOM の検査には絶対に使わないでください。- JavaScript ベースで DOM を確認する必要がある場合は、
CODE:を使って評価して結果を保存するか、VERIFY:とjs:を組み合わせて使います(少なくとも失敗時に AI フォールバックが実行されます)。
待機構文
WAIT_UNTIL:— 条件が満たされるかタイムアウトするまで、AI が条件を繰り返し確認します。デフォルトのタイムアウトは 60 秒です。AI による各確認には 5~10 秒かかるため、timeout_secondsは 15 以上に設定してください。WAIT:— 指定した時間だけ一時停止します。seconds:で時間を設定します。
それぞれを使うタイミングについては、「E2E テスト設計」の「スマートな待機」を参照してください。
一般的な規約
- 読みやすさのため、ACTION 文では
intentを最初に記述します。 - ACTION に
locatorもjsもない場合に限り、xpathが必要です。 - 単一テストとスイート、パラメーターの使い分け:
- 単一テストファイル — 共有状態のない、独立したテストを 1 つ実行します。
- スイート — 順次依存するテスト(例:テスト A でファイルを作成し、テスト B でそのファイルを使用する)に使います。スイート内の各テストはそれぞれ 1 つのジャーニーを対象とし、スイートは実行順序を保証してブラウザーの状態を共有します。無関係なテストをまとめるためにスイートを使わないでください。
- パラメーター — 同じテスト構造で、異なるデータ入力を使います。
E2E テスト設計のベストプラクティス
これらの原則は、YAML 形式に関係なく、何をテストし、どのようにテストを構成するかを定めるものです。フェーズ 2(仕様策定)とフェーズ 4(実装)で適用してください。
テストの独立性
各テストは単独で実行できる必要があります。別のテストによる副作用、実行順序、残存状態に決して依存させないでください。テストにデータが必要な場合は、そのテスト自身でデータを作成します。
# BAD: depends on a previous test having created "My Project"
test: Delete a project
steps:
- URL: /projects
- intent: Click on "My Project"
action: click
locator: "getByText('My Project')"
- intent: Click the Delete button
action: click
locator: "getByRole('button', { name: 'Delete' })"
# GOOD: creates its own data, then tests the behavior
test: Delete a project
steps:
- CODE:
js: |
const res = await page.request.post('/api/projects', {
data: { name: 'Delete-Test-' + Date.now() }
});
const project = await res.json();
save_variable('projectName', project.name);
- URL: /projects
- WAIT_UNTIL: The project list has loaded
- intent: Click on the project we just created
action: click
js: "await page.getByText('{{projectName}}').click()"
- intent: Click the Delete button
action: click
locator: "getByRole('button', { name: 'Delete' })"
- VERIFY: The project is no longer visible in the list
テストごとに 1 つのジャーニー
各テストでは、論理的に 1 つのユーザージャーニーを検証します。8 ステップ中のステップ 3 が失敗しても、ステップ 4~8 からは何も分かりません。長いフローは、対象を絞ったテストに分割してください。
例外: スイートでは、テスト間の順次依存(例:テスト A でファイルをアップロードし、テスト B でそのファイルをダウンロードする)が可能です。スイート内の各テストはそれぞれ 1 つのジャーニーを対象とし、スイートは実行順序を保証してブラウザーの状態を共有します。
# BAD: tests login, settings change, AND deletion in one test
test: Full user lifecycle
steps:
- intent: Log in
- intent: Navigate to settings
- intent: Change display name
- VERIFY: Name updated
- intent: Navigate to account
- intent: Delete account
- VERIFY: Account deleted
# GOOD: separate tests, each verifiable in isolation
# File: update-display-name.test.yaml
test: Update display name from settings
steps:
- URL: /settings
- intent: Clear the display name field and type "New Name"
action: fill
locator: "getByLabel('Display name')"
value: "New Name"
- intent: Click Save
action: click
locator: "getByRole('button', { name: 'Save' })"
- VERIFY: Success message "Settings saved" is visible
# File: delete-account.test.yaml (separate test)
test: Delete account from account page
steps:
- URL: /account
# ... focused on deletion only
実装の詳細ではなく、ユーザーに見えるものをアサートする
テキスト、ナビゲーション、有効/無効の状態など、目に見える結果をテストします。CSS クラス、データ属性、内部状態、DOM 構造はアサートしないでください。
# BAD: asserts implementation details
- VERIFY:
js: |
const el = await page.locator('.btn-primary');
await expect(el).toHaveClass(/disabled/);
await expect(el).toHaveAttribute('data-state', 'submitted');
# GOOD: asserts what a user would observe
- VERIFY: The Submit button is disabled
js: |
await expect(page.getByRole('button', { name: 'Submit' }))
.toBeDisabled({ timeout: 2000 });
アサーションを絞り込む
機能が動作することを証明する、1 つのことを検証します。アサーションを増やしすぎるとテストが壊れやすくなり、テスト対象の動作とは関係のない見た目の変更で失敗するようになります。
# BAD: asserts every field on the page — breaks when any label changes
- VERIFY: Page title is "Dashboard"
- VERIFY: Welcome message shows username
- VERIFY: Sidebar has 5 menu items
- VERIFY: Footer shows current year
- VERIFY: Avatar image is loaded
- VERIFY: Notification bell is visible
# GOOD: asserts the one thing that proves the user landed on the dashboard
- VERIFY: Dashboard page shows the welcome message with the user's name
サードパーティーサービスはテストしない
Stripe のチェックアウト、Google OAuth の同意画面、Twilio の SMS 配信が動作することをアサートしないでください。ネットワーク境界で外部サービスをモックします。テストするのは自分の連携機能であり、相手の UI ではありません。
# BAD: tests Stripe's UI (will break when Stripe updates their page)
- intent: Enter card number in Stripe iframe
- intent: Click Stripe's pay button
- VERIFY: Stripe shows success checkmark
# GOOD: mock the payment API, test your success handling
- CODE:
js: |
await page.route('**/api/payments', route =>
route.fulfill({ status: 200, json: { status: 'succeeded', id: 'pi_mock' } })
);
- intent: Click the Pay button
action: click
locator: "getByRole('button', { name: 'Pay' })"
- VERIFY: Order confirmation page shows "Payment successful"
決定的なテストデータ
テスト実行ごとに一意の識別子を使い、競合を避けてください。他のテストやユーザーが変更する可能性のあるハードコードされたデータには、決して依存しないでください。
# BAD: hardcoded name — collides if tests run in parallel or data persists
- intent: Type "Test User" into the name field
action: fill
locator: "getByLabel('Name')"
value: "Test User"
# GOOD: unique per run — no collisions
- CODE:
js: "save_variable('testName', 'Test-User-' + Date.now());"
- intent: Type the generated name into the name field
action: fill
locator: "getByLabel('Name')"
text: "{{testName}}"
UI でのセットアップより API によるデータの準備を優先する
テストに前提条件(ユーザーが存在する、プロジェクトが作成されているなど)が必要な場合は、UI をクリックして操作するのではなく、API 呼び出しで準備してください。UI によるセットアップは遅く、不安定で、テスト対象でもありません。
# BAD: 10 UI steps just to set up data before the real test
- URL: /projects/new
- intent: Type project name
- intent: Select team
- intent: Click Create
- WAIT_UNTIL: Project page loads
# ... now the actual test starts
# GOOD: API seed in one step, then test the real behavior
- CODE:
js: |
const res = await page.request.post('/api/projects', {
data: { name: 'Seed-' + Date.now(), team: 'engineering' }
});
const { slug } = await res.json();
save_variable('projectSlug', slug);
- URL: /projects/{{projectSlug}}/settings
- WAIT_UNTIL: Settings page has loaded
# ... test starts immediately at the point that matters
適切な待機
状況に合った待機方法を使ってください。WAIT_UNTIL: はチェックごとに 5~10 秒かかるため(AI による解決)、短く予測可能な遅延に使うのは過剰です。遅延が短く、既知の場合は WAIT: で問題ありません。アンチパターンは、遅延が予測できない状況で条件に基づく待機の代わりに WAIT: を使うことです。
# BAD: guessing how long a data fetch takes — too short in CI, too long locally
- WAIT: Wait for data to load
seconds: 5
- VERIFY: The table shows results
# GOOD: condition-based wait for unpredictable operations
- WAIT_UNTIL: The data table has at least one row visible
timeout_seconds: 30
# ALSO GOOD: short WAIT for known, fast delays (animations, transitions, debounce)
- intent: Type search query
action: fill
locator: "getByRole('searchbox')"
value: "test"
- WAIT: Wait for debounce to fire
seconds: 1
- VERIFY: Search suggestions are visible
目安:遅延が予測可能で 5 秒未満(アニメーション、デバウンス、遷移)なら、WAIT: を使います。遅延が予測できない(API 呼び出し、データ読み込み、ファイル処理)なら、WAIT_UNTIL: を使います。
正常系だけでなくエラー状態もテストする
実際のユーザーはエラーに遭遇します。正常系だけをカバーするテストスイートは、誤った安心感を与えます。重要なユーザージャーニーごとに、少なくとも 1 つはエラーケースまたは境界ケースのテストを含めてください。
# Covers: empty state, invalid input, network failure
test: Search handles no results gracefully
steps:
- URL: /search
- intent: Type a query that returns no results
action: fill
locator: "getByRole('searchbox')"
value: "zzz_no_match_zzz"
- intent: Submit the search
action: click
locator: "getByRole('button', { name: 'Search' })"
- VERIFY: Empty state message "No results found" is displayed
- VERIFY: The search box still contains the query (user can refine)
並列実行を想定して設計する
共有のグローバル状態(サイト全体の設定、唯一の管理者アカウントなど)を変更するテストは、安全に並列実行できません。次の点を考慮して設計してください。
- 共有フィクスチャではなく、テストごとに一意のデータを使う
- グローバル設定を変更するテストを避ける
- 共有状態を変更する必要がある場合は、その旨を記録し、逐次実行の対象として指定する
不安定なテストに関する方針
再試行で成功するテストも、依然として壊れています。不安定さを覆い隠すために再試行を追加してはいけません。根本原因を特定して修正してください。
- タイミングの不安定さ? → 適切な条件で
WAIT_UNTIL:を追加する - データの不安定さ? → 一意のテストデータを使い、適切なクリーンアップを追加する
- 順序による不安定さ? → テストが別のテストに隠れた依存関係を持っています。自己完結するようにする
- 環境による不安定さ? → 不安定な外部サービスをモックする
プロジェクト構成
my-tests/
├── test-specs/ # Spec artifacts (version-controlled)
│ ├── test-strategy.md # Phase 1: app & risk profile
│ ├── test-spec.md # Phase 2: Given/When/Then scenarios
│ └── test-plan.md # Phase 3: implementation plan
│
├── playwright.config.ts
├── package.json
├── .env # API keys + credentials (gitignored)
├── .gitignore
│
├── tests/
│ ├── public-app/ # No login needed
│ │ ├── search.test.yaml
│ │ └── filter.test.yaml
│ │
│ └── my-saas-app/ # Requires login
│ ├── auth.setup.ts # Playwright login setup — you write this
│ ├── dashboard.test.yaml
│ └── settings.test.yaml
test-specs/ ディレクトリには、バージョン管理が可能な、人が読める Markdown 形式の成果物が含まれています。test-specs/ を .gitignore に追加しないでください。
ヒント
- ロケーターを使った ACTION ステートメントは DRAFT より約 10 倍速く再生されます。常に ACTION を優先してください。
inspect_pageを使ってページの状態を把握してください。まず DOM ファイルを必ず確認してください。actに必要な要素インデックスが含まれており、トークン消費も大幅に少なくなります。レイアウト、色、画像など、視覚的な情報が特に必要な場合にのみスクリーンショットを表示してください。スクリーンショットは DOM より大幅に多くのトークンを消費します。- 特定のプロジェクトのテストを実行するには、次のコマンドを使います:
npx shiplight test --headed my-saas-app/ .envファイルはshiplightConfig()によって自動検出されるため、dotenv を手動で設定する必要はありません。