mcpField
M
multi-user-workflow-to-playwright

multi-user-workflow-to-playwright

github.com/neonwattyテスト・品質3451 回インストール1 週間前に更新
リポジトリ

multi-user-workflow-to-playwright について

multi-user-workflow-to-playwright は、複数ユーザーのワークフロー文書を Playwright テストプロジェクトに変換する QA 自動化エンジニア向けスキルです。ペルソナごとの認証設定を生成し、個別のブラウザーコンテキストでテストします。

主な機能

  1. 01ペルソナ付きワークフローの解析
  2. 02ペルソナ別認証設定の生成
  3. 03複数プロジェクト構成の作成
  4. 04個別ブラウザーコンテキストでのテスト
  5. 05CI 設定とデプロイ保護ヘッダーの生成

活用シーン

  1. 01共同作業フロー文書の E2E テスト化
  2. 02同一フローでの複数ユーザー操作のテスト
  3. 03ペルソナごとのテスト認証設定
  4. 04CI 実行用 Playwright プロジェクトの生成

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/neonwatty/qa-skills --skill multi-user-workflow-to-playwright

インストール後、Claude Code で/multi-user-workflow-to-playwrightを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

examples/
api-helpers.md
translation-example.md
references/
action-mapping.md
agent-prompts.md
multi-context-patterns.md
selector-discovery.md

詳細

複数ユーザーワークフローから Playwright への変換

シニア QA 自動化エンジニアとして、人間が読める複数ユーザーワークフローのドキュメントを、ペルソナごとの認証と複数ブラウザーコンテキストのテストパターンを備えた自己完結型の Playwright テストプロジェクトに変換します。あなたの役割は、/workflows/multi-user-workflows.md からワークフローを読み取り、ペルソナのメタデータを解析し、ペルソナタグ付きのすべてのステップを別々のブラウザーコンテキストを使用する慣用的な Playwright コードに変換し、ペルソナごとの認証セットアップ、複数プロジェクト構成、CI 構成、Vercel デプロイ保護ヘッダーを含む、完全に機能するテストプロジェクトを e2e/multi-user/ に作成することです。

生成されるすべてのテストは、cd e2e/multi-user && npm ci && npx playwright test を実行するだけで、すぐに実行できる必要があります。


タスクリストとの連携

タスクリストはエージェントの進捗を記録し、ユーザーに状況を知らせ、中断後のセッション復旧を可能にし、レビューの反復を記録し、解析・生成・承認された内容の監査証跡として機能します。

タスク階層

このスキルを実行するたびに、次のタスクツリーが作成されます。タスクは順番に完了します。

[Main Task] "Convert: Multi-User Workflows to Playwright"
  +-- [Parse Task]    "Parse: multi-user-workflows.md"
  +-- [Check Task]    "Check: Existing e2e/multi-user/ project"
  +-- [Selector Task] "Selectors: Find for all workflows"   (agent)
  +-- [Generate Task] "Generate: Playwright project"
  +-- [Approval Task] "Approval: Review generated tests"
  +-- [Write Task]    "Write: e2e/multi-user/"
セッション復旧チェック

すべての実行の開始時に、ほかの作業に着手する前に、既存のタスクリストがあるか確認してください。

1. Read the current TaskList.
2. If no task list exists -> start from Phase 1.
3. If a task list exists:
   a. Find the last task with status "completed".
   b. Determine the corresponding phase.
   c. Inform the user: "Resuming from Phase N -- [phase name]."
   d. Skip to that phase's successor.

完全な判断ツリーについては、このドキュメント末尾近くにある「セッション復旧」セクションを参照してください。


変換パイプライン

このスキルは単一の入力ファイルを読み取り、ペルソナごとの認証を備えた完全なテストプロジェクトを生成します。

/workflows/multi-user-workflows.md  ->  e2e/multi-user/
                                          +-- playwright.config.ts
                                          +-- package.json
                                          +-- tests/
                                          |   +-- admin.setup.ts
                                          |   +-- user.setup.ts
                                          |   +-- host.setup.ts
                                          |   +-- guest1.setup.ts
                                          |   +-- ...  (one per persona)
                                          |   +-- workflows.spec.ts
                                          +-- .github/workflows/e2e.yml
                                          +-- .gitignore

出力されるすべてのファイルは自己完結しています。実行時にソースのワークフロー Markdown に依存することはありません。ワークフローは Playwright のテストコードに完全にコンパイルされます。


フェーズ 1:ワークフローの解析

ワークフローの Markdown ファイルを読み込み、ペルソナのメタデータを含む各ワークフローを抽出して、以降のすべてのフェーズで使用する内部表現を作成します。

フォーマットの参照先: 入力ワークフローファイルは、docs/workflow-format.md で定義されている形式に従います。見出しの形式、メタデータコメント、ステップ形式、認識される動詞、アサーションの種類については、この仕様を参照してください。

ステップ 1:ワークフローファイルを特定する

Glob を使用してワークフローファイルを検索します。

Glob patterns:
  - workflows/multi-user-workflows.md
  - workflows/concurrent-workflows.md
  - workflows/collaboration-workflows.md

ファイルが見つからない場合は、処理を中止してユーザーに伝えます。

No multi-user workflow file found at /workflows/multi-user-workflows.md.
Please run "generate multi-user workflows" first, or provide the path
to your workflow file.
ステップ 2:読み取りと解析

ワークフローファイル全体を読み込みます。各ワークフローについて、次の項目を抽出します。

  1. ワークフロー番号 -- ## Workflow [N]: 見出しから取得
  2. ワークフロー名 -- 番号の後に続く説明的な名前
  3. 認証要件 -- または から取得
  4. 優先度 -- , 、または `` から取得
  5. ペルソナ -- `` から取得(カンマ区切りのリスト)
  6. 推定ステップ数 -- `` から取得
  7. 同期ポイント -- `` から取得
  8. 非推奨フラグ -- `` から取得(非推奨のワークフローはスキップ)
  9. 事前条件 -- **Preconditions:** の下の箇条書き
  10. ステップ -- 各番号付きステップと、その [PersonaName] タグおよび検証のサブステップ
  11. 事後条件 -- **Postconditions:** の下の箇条書き
ステップ 3:ペルソナレジストリを解析する

ワークフローファイルの上部付近にあるペルソナレジストリの表を抽出し、ペルソナマップを作成します。各ペルソナについて、次の項目を導出します。

  • contextVar:ペルソナ名を小文字にし、Ctx を付加したもの(例:adminCtx、guest1Ctx)
  • pageVar:ペルソナ名を小文字にし、Page を付加したもの(例:adminPage、guest1Page)
  • authFile:playwright/.auth/<lowercase-persona>.json
  • setupFile:<lowercase-persona>.setup.ts
  • emailVar / passwordVar:Credential Env Vars 列の値(例:ADMIN_EMAIL / ADMIN_PASSWORD)
ステップ 4:内部表現を構築する

ワークフローを構造化されたリストに整理します。各ワークフロー項目には、番号、名前、認証フラグ、優先度、ペルソナのリスト、ステップ(それぞれに persona フィールド、action、verify、任意の syncVerify ブール値、ミリ秒単位の syncTimeout を含む)、前提条件、事後条件を含めます。

各ステップの persona フィールドは、[PersonaName] タグのプレフィックスから抽出します(例:[Admin] → persona: "Admin")。

`` とマークされたワークフローはスキップします。スキップしたワークフローをユーザーに通知します。

Parsed 20 workflows from multi-user-workflows.md.
Skipped 1 deprecated workflow: #9 (Legacy Shared Calendar).
Converting 19 active workflows.
Personas found: Admin, Host, Guest1, Guest2, Guest3, Viewer (6 total).
ステップ 5:タスクを作成する

メタデータにソースファイル、ワークフロー数、ペルソナのリスト、出力パスを含むメインタスク "Convert: Multi-User Workflows to Playwright"(in_progress)を作成します。メタデータに優先度別のワークフロー数、ペルソナ数、同期ポイントの合計数を含む解析タスク "Parse: multi-user-workflows.md"(completed)を作成します。


フェーズ 2:既存プロジェクトを確認する

生成する前に、e2e/multi-user/ ディレクトリがすでに存在するか確認します。

ステップ 1:既存ファイルを確認する

Glob を使って、既存のプロジェクトファイルを確認します。

Glob patterns:
  - e2e/multi-user/playwright.config.ts
  - e2e/multi-user/package.json
  - e2e/multi-user/tests/*.spec.ts
  - e2e/multi-user/tests/*.setup.ts
ステップ 2:方針を決定する

既存のプロジェクトが見つからない場合:

  • 新規生成を進めます。
  • これ以上の判断は必要ありません。

既存のプロジェクトが見つかった場合:

  • 既存の tests/workflows.spec.ts を読み、すでにカバーされている内容を確認します。
  • 既存の tests/*.setup.ts ファイルを読み、現在のペルソナ設定ファイルを特定します。
  • AskUserQuestion を使って、ユーザーの意図を確認します。
I found an existing Playwright project at e2e/multi-user/ with [N] existing
test blocks and [M] persona setup files.

How would you like to proceed?

1. **Overwrite** -- Replace all generated files with fresh output
2. **Update** -- Add new tests for new workflows, update changed workflows, preserve custom modifications
3. **Cancel** -- Stop and keep existing files unchanged
ステップ 3:確認タスクを作成する

既存プロジェクトの状態、テスト数、ペルソナ設定ファイル数、選択した方針のメタデータを含む "Check: Existing e2e/multi-user/ project"(completed)を作成します。


フェーズ 3:セレクターの検出[エージェントに委任]

Explore エージェントを起動し、コードベースを分析して、ワークフローで参照されている要素に最適な Playwright セレクターを見つけます。

ステップ 1:タスクを作成してエージェントを起動する

"Selectors: Find for all workflows"(in_progress)を作成します。

ステップ 2:Explore エージェントを起動する

以下のパラメーターを指定して、Task ツール経由で起動します。

Task tool:
  subagent_type: "Explore"
  model: "sonnet"
  prompt: |
    You are a QA exploration agent focused on finding Playwright selectors.

    Your job is to find the best Playwright-compatible selectors for every
    interactive element referenced in the workflow documentation.
    Use Read, Grep, and Glob to explore the codebase. Do NOT use any browser tools.

    Here are the workflows I need selectors for:
    [Paste the parsed workflow list with all step actions]

    The personas involved are: [List persona names]
    Note: Elements may render differently per persona (role-based UI).
    When searching, look for conditional rendering based on roles.

    For each element, search for: data-testid, aria-label, role attributes,
    <label> associations, placeholder text, and visible text content.
    Also check for role-conditional rendering (elements shown/hidden per role).

    Prefer selectors in this order (Playwright recommended):
    1. getByRole  2. getByLabel  3. getByPlaceholder
    4. getByText  5. getByTestId  6. CSS selector (last resort)

    Return findings as:

    ## Selector Map
    | Workflow | Step | Persona | Element Description | Recommended Selector | Fallback Selector |
    |----------|------|---------|--------------------|--------------------|-------------------|
    | 1 | 2 | Admin | "Invite Member" button | getByRole('button', { name: 'Invite Member' }) | getByTestId('invite-btn') |

    ## Role-Conditional Elements
    - Elements that render differently per persona (e.g., edit button visible to Admin but not Viewer)

    ## Missing Selectors
    - Elements not found in codebase (suggest data-testid additions)

    ## Selector Quality Report
    - Counts by selector type and elements not found
ステップ 3:エージェントの結果を処理する

Explore エージェントから結果が返されたら、その Selector Map を内部ワークフロー表現に統合します。これで、各ステップにコード生成で使用する具体的な Playwright セレクターが設定されます。

セレクターの種類別の数、見つからなかった要素の数、ロール条件付き要素の数をメタデータに含めて、タスクを completed に更新します。

エージェントが特定できなかった要素については、テストコードにコメントを生成します。

// TODO: Add data-testid for this element -- selector not found in codebase
await adminPage.locator('[data-testid="unknown-element"]').click();

フェーズ 4:Playwright プロジェクトを生成する

これは生成の中心となるフェーズです。解析したワークフロー、検出したセレクター、Persona Map、設定テンプレートを使用して、すべてのプロジェクトファイルを生成します。

ステップ 1:生成タスクを作成する

"Generate: Playwright project"(in_progress)を作成します。

ステップ 2:playwright.config.ts を生成する

複数プロジェクト構成の Playwright 設定ファイルを生成します。各ペルソナには独自のセットアッププロジェクトを用意し、メインのテストプロジェクトはすべてのペルソナのセットアッププロジェクトに依存します。各テストではペルソナごとに独自のブラウザーコンテキストを作成するため、プロジェクト設定では storageState を使用しません。

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  fullyParallel: false,
  retries: process.env.CI ? 2 : 0,
  reporter: process.env.CI ? 'html' : [['list'], ['html']],
  use: {
    baseURL: process.env.BASE_URL || 'http://localhost:3000',
    trace: 'on-first-retry',
    extraHTTPHeaders: {
      ...(process.env.VERCEL_AUTOMATION_BYPASS_SECRET && {
        'x-vercel-protection-bypass': process.env.VERCEL_AUTOMATION_BYPASS_SECRET,
        'x-vercel-set-bypass-cookie': 'samesitenone',
      }),
    },
  },
  projects: [
    { name: 'admin-setup', testMatch: /admin\.setup\.ts/ },
    { name: 'host-setup', testMatch: /host\.setup\.ts/ },
    { name: 'guest1-setup', testMatch: /guest1\.setup\.ts/ },
    { name: 'guest2-setup', testMatch: /guest2\.setup\.ts/ },
    { name: 'guest3-setup', testMatch: /guest3\.setup\.ts/ },
    { name: 'viewer-setup', testMatch: /viewer\.setup\.ts/ },
    {
      name: 'multi-user-tests',
      testDir: './tests',
      testMatch: /workflows\.spec\.ts/,
      use: { ...devices['Desktop Chrome'] },
      dependencies: [
        'admin-setup',
        'host-setup',
        'guest1-setup',
        'guest2-setup',
        'guest3-setup',
        'viewer-setup',
      ],
    },
  ],
});

主な設定上の決定事項:複数ユーザーのテストでは状態を共有し、ワークフロー内で順次実行する必要があるため、fullyParallel: false にします。各ペルソナには専用のセットアッププロジェクトを用意し、認証フローを実行してストレージ状態を保存します。メインの multi-user-tests プロジェクトは、認証がテスト開始前に確実に完了するよう、すべてのペルソナのセットアッププロジェクトに依存します。各テストではペルソナごとの認証ファイルを使って複数のブラウザーコンテキストを作成するため、プロジェクトレベルで storageState を宣言しません。Vercel バイパスヘッダーは、VERCEL_AUTOMATION_BYPASS_SECRET が設定されている場合にのみ条件付きで適用されます。

特定のプロジェクト向けに生成する場合は、Persona Registry に含まれるペルソナのみを指定します。上記の例では 6 つのペルソナを示していますが、実際の数は異なります。

ステップ 3:ペルソナごとのセットアップファイルを生成する

Persona Map の各ペルソナについて、専用のセットアップファイルを tests/<persona>.setup.ts に生成します。すべてのセットアップファイルで同じパターンを使いますが、ペルソナ固有の認証情報環境変数と認証ファイルのパスを使用します。

各ペルソナのテンプレート:

import { test as setup } from '@playwright/test';
import * as fs from 'fs';
import * as path from 'path';

const authFile = 'playwright/.auth/<persona-lowercase>.json';

setup('authenticate as <persona-name>', async ({ page }) => {
  // Check for saved profile from /setup-profiles
  const profilePath = path.join(process.cwd(), '.playwright', 'profiles', '<persona-lowercase>.json');
  if (fs.existsSync(profilePath)) {
    const state = JSON.parse(fs.readFileSync(profilePath, 'utf-8'));
    fs.mkdirSync(path.dirname(authFile), { recursive: true });
    fs.writeFileSync(authFile, JSON.stringify(state));
    return;
  }

  // Fall back to env-var credentials
  if (!process.env.<PERSONA_EMAIL_VAR> || !process.env.<PERSONA_PASSWORD_VAR>) {
    await page.context().storageState({ path: authFile });
    return;
  }
  await page.goto('/login');
  await page.getByLabel('Email').fill(process.env.<PERSONA_EMAIL_VAR>);
  await page.getByLabel('Password').fill(process.env.<PERSONA_PASSWORD_VAR>);
  await page.getByRole('button', { name: /sign in|log in/i }).click();
  await page.waitForURL('**/dashboard');
  await page.context().storageState({ path: authFile });
});

tests/admin.setup.ts の具体例:

import { test as setup } from '@playwright/test';
import * as fs from 'fs';
import * as path from 'path';

const authFile = 'playwright/.auth/admin.json';

setup('authenticate as admin', async ({ page }) => {
  // Check for saved profile from /setup-profiles
  const profilePath = path.join(process.cwd(), '.playwright', 'profiles', 'admin.json');
  if (fs.existsSync(profilePath)) {
    const state = JSON.parse(fs.readFileSync(profilePath, 'utf-8'));
    fs.mkdirSync(path.dirname(authFile), { recursive: true });
    fs.writeFileSync(authFile, JSON.stringify(state));
    return;
  }

  // Fall back to env-var credentials
  if (!process.env.ADMIN_EMAIL || !process.env.ADMIN_PASSWORD) {
    await page.context().storageState({ path: authFile });
    return;
  }
  await page.goto('/login');
  await page.getByLabel('Email').fill(process.env.ADMIN_EMAIL);
  await page.getByLabel('Password').fill(process.env.ADMIN_PASSWORD);
  await page.getByRole('button', { name: /sign in|log in/i }).click();
  await page.waitForURL('**/dashboard');
  await page.context().storageState({ path: authFile });
});

プロジェクトに /setup-profiles で作成したプロファイルがある場合、認証セットアップでは保存済みの storageState を直接コピーするため、ローカルでテストを実行する際に認証情報は不要です。CI では、代わりに環境変数の認証情報を使用します。

同じパターンをすべてのペルソナに適用します。変更するのは環境変数名、プロファイルのパス、認証ファイルのパスのみです(例:guest1.setup.ts では GUEST1_EMAIL/GUEST1_PASSWORD を使用し、.playwright/profiles/guest1.json を確認して、playwright/.auth/guest1.json に書き込みます)。

認証に関する主な判断:認証情報が設定されていない場合は、フォールバック処理として空の認証状態を保存し、認証情報がすべて揃っていない環境でもテストが実行されるようにします。正規表現のボタンマッチャー(/sign in|log in/i)で、一般的な表記の違いに対応します。特定のアプリケーション向けに生成する場合は、Phase 3 のセレクター探索結果に基づいて、ログインルート、フィールドラベル、ボタンテキスト、ログイン後の URL を調整します。

ステップ 4:package.json を生成する
{
  "name": "multi-user-e2e",
  "private": true,
  "scripts": {
    "test": "playwright test",
    "test:ui": "playwright test --ui",
    "test:headed": "playwright test --headed"
  },
  "devDependencies": {
    "@playwright/test": "^1.50.0"
  }
}
ステップ 5:.github/workflows/e2e.yml を生成する

Vercel のプレビューデプロイに対してテストを実行する GitHub Actions の CI ワークフローを生成します。CI ワークフローには、Persona Registry に登録されているすべてのペルソナの環境変数を含めます。

name: Multi-User E2E Tests
on: [deployment_status]
jobs:
  test:
    if: >
      github.event.deployment_status.state == 'success' &&
      contains(github.event.deployment_status.environment, 'Preview')
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: cd e2e/multi-user && npm ci
      - run: cd e2e/multi-user && npx playwright install chromium --with-deps
      - run: cd e2e/multi-user && npx playwright test
        env:
          BASE_URL: ${{ github.event.deployment_status.target_url }}
          ADMIN_EMAIL: ${{ secrets.ADMIN_EMAIL }}
          ADMIN_PASSWORD: ${{ secrets.ADMIN_PASSWORD }}
          HOST_EMAIL: ${{ secrets.HOST_EMAIL }}
          HOST_PASSWORD: ${{ secrets.HOST_PASSWORD }}
          GUEST1_EMAIL: ${{ secrets.GUEST1_EMAIL }}
          GUEST1_PASSWORD: ${{ secrets.GUEST1_PASSWORD }}
          GUEST2_EMAIL: ${{ secrets.GUEST2_EMAIL }}
          GUEST2_PASSWORD: ${{ secrets.GUEST2_PASSWORD }}
          GUEST3_EMAIL: ${{ secrets.GUEST3_EMAIL }}
          GUEST3_PASSWORD: ${{ secrets.GUEST3_PASSWORD }}
          VIEWER_EMAIL: ${{ secrets.VIEWER_EMAIL }}
          VIEWER_PASSWORD: ${{ secrets.VIEWER_PASSWORD }}
          VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }}
      - uses: actions/upload-artifact@v4
        if: ${{ !cancelled() }}
        with:
          name: multi-user-playwright-report
          path: e2e/multi-user/playwright-report/

CI に関する主な判断:実際の Vercel プレビュー URL に対してテストを実行するため、deployment_status で起動します。対象を Preview 環境のみに絞り、target_url を BASE_URL として使用します。すべてのペルソナのメールアドレスとパスワードに加え、VERCEL_AUTOMATION_BYPASS_SECRET にも GitHub Secrets を必須とします。毎回の実行で Playwright の HTML レポートをアーティファクトとしてアップロードし、速度を優先して Chromium のみをインストールします。特定のプロジェクト向けに生成する場合は、Persona Registry に登録されているペルソナのみを含めます。

ステップ 6:.gitignore を生成する
node_modules/
playwright/.auth/
playwright-report/
test-results/
ステップ 7:tests/workflows.spec.ts を生成する

これは最も大きく、複雑なファイルです。各ワークフローを test.describe() ブロックにし、各ワークフローのステップを、対応するペルソナのブラウザーコンテキストとページに割り当てます。

マルチコンテキストテストのパターン

単一の { page } フィクスチャを使うデスクトップテストとは異なり、複数ユーザーのテストでは { browser } フィクスチャを使い、認証済みの storage state を持つペルソナごとのブラウザーコンテキストを作成します。

import { test, expect } from '@playwright/test';

test.describe('Core Workflows', () => {

  test.describe('Workflow 1: Team Invitation Flow', () => {
    test('admin invites guest1 to the team', async ({ browser }) => {
      // Create per-persona browser contexts with auth state
      const adminCtx = await browser.newContext({
        storageState: 'playwright/.auth/admin.json',
      });
      const guest1Ctx = await browser.newContext({
        storageState: 'playwright/.auth/guest1.json',
      });
      const adminPage = await adminCtx.newPage();
      const guest1Page = await guest1Ctx.newPage();

      // Step 1: [Admin] Navigate to the team management page
      await adminPage.goto('/team');
      // Verify: The "Invite Member" button is visible
      await expect(adminPage.getByRole('button', { name: 'Invite Member' })).toBeVisible();

      // Step 2: [Admin] Click the "Invite Member" button and enter Guest1's email
      await adminPage.getByRole('button', { name: 'Invite Member' }).click();
      await adminPage.getByLabel('Email').fill(process.env.GUEST1_EMAIL || '[email protected]');
      await adminPage.getByRole('button', { name: 'Send Invitation' }).click();
      // Verify: Invitation sent successfully
      await expect(adminPage.getByText('Invitation sent')).toBeVisible();

      // Step 3: [Guest1] Check for the invitation notification
      // Sync Verification: Within 10 seconds, verify Guest1 sees the invitation
      await guest1Page.goto('/notifications');
      await expect(guest1Page.getByText(/invitation/i)).toBeVisible({ timeout: 10000 });

      // Step 4: [Guest1] Accept the invitation
      await guest1Page.getByRole('button', { name: /accept/i }).click();
      // Verify: Guest1 is redirected to the team workspace
      await expect(guest1Page).toHaveURL(/.*\/team/);

      // Step 5: [Admin] Verify the team member list is updated
      // Sync Verification: Within 5 seconds, Admin sees Guest1 in the member list
      await expect(adminPage.getByText('Guest1')).toBeVisible({ timeout: 5000 });

      // Clean up contexts
      await adminCtx.close();
      await guest1Ctx.close();
    });
  });

  // ... more core workflow describe blocks

});

test.describe('Feature Workflows', () => {
  // ... feature workflow describe blocks
});

test.describe('Edge Case Workflows', () => {
  // ... edge case workflow describe blocks
});
構成ルール
  1. 最上位のグループ -- test.describe('Core Workflows', ...)、test.describe('Feature Workflows', ...)、test.describe('Edge Case Workflows', ...) は、ワークフローファイルの優先度区分を反映します。
  2. ワークフローブロック -- 各ワークフローを、該当する優先度グループ内にネストされた test.describe('Workflow N: Name', ...) にします。
  3. ワークフローごとにテストを 1 つ作成 -- 各ワークフローを、すべてのステップを順番に含む 1 つの test() 呼び出しにします。これにより各ワークフローが独立した単位となり、全体として成功するか失敗するかが決まります。
  4. テスト開始時にコンテキストをセットアップ -- 各テストの開始時に、そのワークフローに関与するすべてのペルソナのブラウザーコンテキストを作成します。ワークフローの `` リストに含まれるペルソナのコンテキストのみを作成します。
  5. テスト終了時にコンテキストをクリーンアップ -- すべてのテストで、finally ブロックを安全に使用するパターンか、テストの最後にすべてのブラウザーコンテキストを閉じます。
  6. ステップコメント -- ワークフローの各ステップの前にコメント(// Step N: [Persona] ...)を記述し、その検証にもコメント(// Verify: ... または // Sync Verification: ...)を記述します。
ペルソナタグとコンテキストの対応付け

各ワークフローステップの [PersonaName] プレフィックスによって、使用するページ変数が決まります。

ワークフロータグコンテキスト変数ページ変数
[Admin]adminCtxadminPage
[Host]hostCtxhostPage
[Guest1]guest1Ctxguest1Page
[Guest2]guest2Ctxguest2Page
[Guest3]guest3Ctxguest3Page
[Viewer]viewerCtxviewerPage

一般的なルール:コンテキスト変数は小文字にしたペルソナ名 + Ctx、ページ変数は小文字にしたペルソナ名 + Page です。

同期検証の変換

ワークフローの同期検証ステップは、タイムアウトを明示した Playwright アサーションに変換します。

// Workflow: **Sync Verification:** Within 5 seconds, verify Guest1 sees the document
// Playwright:
await expect(guest1Page.getByText('Document Title')).toBeVisible({ timeout: 5000 });

// Workflow: **Sync Verification:** Within 10 seconds, verify Admin sees Guest1 in the list
// Playwright:
await expect(adminPage.getByText('Guest1')).toBeVisible({ timeout: 10000 });

// Workflow: **Sync Verification:** Guest1 refreshes the page and verifies the change
// Playwright:
await guest1Page.reload();
await expect(guest1Page.getByText('Updated Content')).toBeVisible();
並列アクションの処理

ワークフローステップで 2 人のペルソナが同時に操作すると記載されている場合(例:「[Host] と [Guest1] が同じ共有ドキュメントを同時に開く」)、Promise.all() を使用します。

// Step 1: [Host] and [Guest1] Open the same shared document simultaneously
await Promise.all([
  hostPage.goto('/documents/shared-doc'),
  guest1Page.goto('/documents/shared-doc'),
]);
// Verify: Both see the document content
await expect(hostPage.getByRole('heading', { name: 'Shared Document' })).toBeVisible();
await expect(guest1Page.getByRole('heading', { name: 'Shared Document' })).toBeVisible();
MANUAL ステップの処理

[MANUAL] とマークされたワークフローステップは自動化できません。スキップするテストステップまたは注釈付きのテストステップを生成します。

// Step 4: [Guest1] [MANUAL] Verify the invitation email arrives in the inbox
// This step requires manual verification -- cannot be automated with Playwright.
// Consider using a test email service (e.g., Mailosaur, Mailhog) for automation.

1 つのステップだけが手動の場合、ワークフロー全体に対して test.skip() を生成しないでください。代わりにコメントを追加し、その後の自動化可能なステップを続けます。

前提条件の処理

ワークフローにペルソナごとの認証以外の前提条件がある場合は、テストの冒頭にインラインのセットアップコードとして生成します。

test.describe('Workflow 5: Collaborative Document Editing', () => {
  test('host and guests collaboratively edit a document', async ({ browser }) => {
    const hostCtx = await browser.newContext({
      storageState: 'playwright/.auth/host.json',
    });
    const guest1Ctx = await browser.newContext({
      storageState: 'playwright/.auth/guest1.json',
    });
    const hostPage = await hostCtx.newPage();
    const guest1Page = await guest1Ctx.newPage();

    // Precondition: A shared document named "Test Doc" exists
    await hostPage.goto('/documents');
    const docExists = await hostPage.getByText('Test Doc').isVisible();
    if (!docExists) {
      await hostPage.getByRole('button', { name: 'New Document' }).click();
      await hostPage.getByLabel('Title').fill('Test Doc');
      await hostPage.getByRole('button', { name: 'Create' }).click();
    }

    // Step 1: [Host] Open the shared document
    await hostPage.getByText('Test Doc').click();
    // ...

    await hostCtx.close();
    await guest1Ctx.close();
  });
});
コンテキストの Vercel バイパスヘッダー

ブラウザーコンテキストを作成する際、環境変数が設定されていれば Vercel バイパスヘッダーを適用します。

const extraHTTPHeaders = process.env.VERCEL_AUTOMATION_BYPASS_SECRET
  ? {
      'x-vercel-protection-bypass': process.env.VERCEL_AUTOMATION_BYPASS_SECRET,
      'x-vercel-set-bypass-cookie': 'samesitenone',
    }
  : {};

const adminCtx = await browser.newContext({
  storageState: 'playwright/.auth/admin.json',
  extraHTTPHeaders,
});

繰り返しを避けるため、spec ファイルの先頭に共有の createContext ヘルパーを生成します。

import { test, expect, Browser, BrowserContext } from '@playwright/test';

async function createAuthContext(
  browser: Browser,
  persona: string,
): Promise<BrowserContext> {
  const extraHTTPHeaders = process.env.VERCEL_AUTOMATION_BYPASS_SECRET
    ? {
        'x-vercel-protection-bypass': process.env.VERCEL_AUTOMATION_BYPASS_SECRET,
        'x-vercel-set-bypass-cookie': 'samesitenone',
      }
    : {};
  return browser.newContext({
    storageState: `playwright/.auth/${persona}.json`,
    extraHTTPHeaders,
  });
}

その後、各テストでは次のように使用します。

const adminCtx = await createAuthContext(browser, 'admin');
const guest1Ctx = await createAuthContext(browser, 'guest1');
ステップ 8:生成タスクを更新する

生成されたファイル数、テストの describe 数、変換されたステップ数、同期検証数、スキップされた手動ステップ数、ペルソナセットアップファイル数、並列アクションブロック数のメタデータを付けて、「Generate: Playwright project」を完了としてマークします。


アクションマッピングリファレンス

この表は、マルチユーザーワークフロー言語から Playwright コードへの完全なマッピングを示しています。デスクトップ向けのマッピングとの主な違いは、各アクションがペルソナ固有のページ変数を対象とする点です。

ワークフロー言語Playwright コード
[Admin] /dashboard に移動await adminPage.goto('/dashboard')
[Host] 「Save」ボタンをクリックawait hostPage.getByRole('button', { name: 'Save' }).click()
[Guest1] 「Settings」リンクをクリックawait guest1Page.getByRole('link', { name: 'Settings' }).click()
[Admin] メールフィールドに「hello」と入力await adminPage.getByLabel('Email').fill('hello')
[Guest2] 検索ボックスに「query」と入力await guest2Page.getByPlaceholder('Search...').fill('query')
[Viewer] 見出し「Dashboard」が表示されることを確認await expect(viewerPage.getByRole('heading', { name: 'Dashboard' })).toBeVisible()
[Host] テキスト「Success」が表示されることを確認await expect(hostPage.getByText('Success')).toBeVisible()
[Guest1] URL に /settings が含まれることを確認await expect(guest1Page).toHaveURL(/.*\/settings/)
[Admin] ロールのドロップダウンから「Editor」を選択await adminPage.getByLabel('Role').selectOption('Editor')
[Host] 「Allow editing」チェックボックスをオンにするawait hostPage.getByLabel('Allow editing').check()
[Admin] 「Notifications」チェックボックスをオフにするawait adminPage.getByLabel('Notifications').uncheck()
[Guest1] 読み込み中の表示が消えるまで待機await expect(guest1Page.getByText('Loading')).toBeHidden()
[Host] URL に /document が含まれるまで待機await hostPage.waitForURL('**/document')
[Guest1] 「file.pdf」をアップロードawait guest1Page.getByLabel('Upload').setInputFiles('file.pdf')
[Host] Escape キーを押すawait hostPage.keyboard.press('Escape')
[Admin] 「Settings」メニュー項目にカーソルを合わせるawait adminPage.getByRole('menuitem', { name: 'Settings' }).hover()
[Viewer] コメントセクションまでスクロールawait viewerPage.getByText('Comments').scrollIntoViewIfNeeded()
同期の検証: 5 秒以内await expect(...).toBeVisible({ timeout: 5000 })
[Host] と [Guest1] が同時に /doc を開くawait Promise.all([hostPage.goto('/doc'), guest1Page.goto('/doc')])
[Guest1] ページを再読み込みawait guest1Page.reload()
[Viewer] 要素が表示されていないことを確認await expect(viewerPage.getByRole('button', { name: 'Delete' })).toBeHidden()
[Admin] 「Task A」を「Done」列にドラッグawait adminPage.getByText('Task A').dragTo(adminPage.getByText('Done'))
[Host] 検索フィールドをクリアawait hostPage.getByLabel('Search').clear()

翻訳例

以下は、multi-user-workflows.md のマルチユーザーワークフローを、ペルソナごとのコンテキストを使用して Playwright テストコードに変換する完全な実例です。

入力ワークフロー
## Workflow 3: Collaborative Document Editing






> Tests real-time collaborative editing where multiple users edit a shared
> document simultaneously and verify cross-user sync.

**Preconditions:**
- Host is logged in as Host persona (HOST_EMAIL / HOST_PASSWORD)
- Guest1 is logged in as Guest1 persona (GUEST1_EMAIL / GUEST1_PASSWORD)
- Guest2 is logged in as Guest2 persona (GUEST2_EMAIL / GUEST2_PASSWORD)
- A shared document named "Project Plan" exists (created by Host)

**Steps:**

1. [Host] Navigate to /documents and click "Project Plan"
   - Verify the document editor loads with title "Project Plan"

2. [Guest1] Navigate to /documents and click "Project Plan"
   - Verify the document editor loads with title "Project Plan"
   - **Sync Verification:** Within 3 seconds, verify Guest1 sees Host's presence
     indicator in the editor

3. [Guest2] Navigate to /documents and click "Project Plan"
   - Verify the document editor loads with title "Project Plan"
   - **Sync Verification:** Within 3 seconds, verify Guest2 sees both Host
     and Guest1 presence indicators

4. [Host] Type "Introduction section" in the document body
   - **Sync Verification:** Within 2 seconds, verify Guest1 sees
     "Introduction section" appear in the document
   - **Sync Verification:** Within 2 seconds, verify Guest2 sees
     "Introduction section" appear in the document

5. [Guest1] Type "Added by Guest1" below Host's text
   - **Sync Verification:** Within 2 seconds, verify Host sees
     "Added by Guest1" appear in the document

6. [Host] Click the "Save" button
   - Verify success message "Document saved" appears

7. [Guest1] Verify the save indicator shows "Saved"

8. [Guest2] Refresh the page
   - Verify both "Introduction section" and "Added by Guest1" are visible

**Postconditions:**
- Document contains content from both Host and Guest1
- All three personas see consistent document state
出力テストコード
test.describe('Workflow 3: Collaborative Document Editing', () => {
  test('host and guests collaboratively edit a shared document', async ({ browser }) => {
    const hostCtx = await createAuthContext(browser, 'host');
    const guest1Ctx = await createAuthContext(browser, 'guest1');
    const guest2Ctx = await createAuthContext(browser, 'guest2');
    const hostPage = await hostCtx.newPage();
    const guest1Page = await guest1Ctx.newPage();
    const guest2Page = await guest2Ctx.newPage();

    // Step 1: [Host] Navigate to /documents and click "Project Plan"
    await hostPage.goto('/documents');
    await hostPage.getByText('Project Plan').click();
    await expect(hostPage.getByRole('heading', { name: 'Project Plan' })).toBeVisible();

    // Step 2: [Guest1] Navigate to /documents and click "Project Plan"
    await guest1Page.goto('/documents');
    await guest1Page.getByText('Project Plan').click();
    await expect(guest1Page.getByRole('heading', { name: 'Project Plan' })).toBeVisible();
    // Sync Verification: Within 3 seconds, Guest1 sees Host's presence
    await expect(guest1Page.getByTestId('presence-indicator')).toBeVisible({ timeout: 3000 });

    // Step 3: [Guest2] Navigate to /documents and click "Project Plan"
    await guest2Page.goto('/documents');
    await guest2Page.getByText('Project Plan').click();
    // Sync Verification: Within 3 seconds, Guest2 sees both presence indicators
    await expect(guest2Page.locator('[data-testid="presence-indicator"]')).toHaveCount(2, { timeout: 3000 });

    // Step 4: [Host] Type "Introduction section" in the document body
    await hostPage.getByLabel('Content').fill('Introduction section');
    // Sync: Guest1 and Guest2 see the text within 2 seconds
    await expect(guest1Page.getByText('Introduction section')).toBeVisible({ timeout: 2000 });
    await expect(guest2Page.getByText('Introduction section')).toBeVisible({ timeout: 2000 });

    // Step 5: [Guest1] Type "Added by Guest1" below Host's text
    await guest1Page.getByLabel('Content').pressSequentially('Added by Guest1');
    await expect(hostPage.getByText('Added by Guest1')).toBeVisible({ timeout: 2000 });

    // Step 6: [Host] Click the "Save" button
    await hostPage.getByRole('button', { name: 'Save' }).click();
    await expect(hostPage.getByText('Document saved')).toBeVisible();

    // Step 7-8: [Guest1] verify saved, [Guest2] refresh and verify
    await expect(guest1Page.getByText('Saved')).toBeVisible();
    await guest2Page.reload();
    await expect(guest2Page.getByText('Introduction section')).toBeVisible();
    await expect(guest2Page.getByText('Added by Guest1')).toBeVisible();

    await hostCtx.close();
    await guest1Ctx.close();
    await guest2Ctx.close();
  });
});
翻訳に関する注記

主なパターン:ステップのコメントには相互参照用にペルソナタグを保持し、同期の検証ステップではワークフローのタイミング要件に一致する明示的な { timeout: N } を使用します。各ペルソナは独自のページ変数を操作し、createAuthContext ヘルパーは Vercel のバイパスヘッダーとストレージ状態を処理します。同時に行うアクションには Promise.all() を使用し、各テストの最後にコンテキストをクリーンアップします。


フェーズ 5:ユーザーとレビュー(必須)

このフェーズは必須です。ユーザーの承認なしにファイルを書き込んではなりません。

生成したテストを提示してレビューを依頼する

AskUserQuestion を使用して、生成したプロジェクトを提示します。以下を含めてください:プロジェクト構成の一覧、テストの概要(優先度別の件数、同期の検証、手動ステップ)、ペルソナと認証情報の対応付け、workflows.spec.ts と playwright.config.ts の内容全体。テストの翻訳、セレクター、同期タイムアウト、ペルソナの対応付け、認証フローをレビューするようユーザーに依頼します。続行する場合は「approved」、修正する場合はフィードバックを求めてください。

承認タスクを作成する

反復番号と件数を含めて、"Approval: Review generated tests" を作成します(in_progress)。

フィードバックへの対応

ユーザーが承認せずにフィードバックを提供した場合は、変更を適用し、現在の承認タスクを result: "changes_requested" とフィードバックの要約を付けて完了にし、変更内容の一覧を含むイテレーション N+1 の新しい承認タスクを作成して、修正したテストを再提示します。ユーザーが承認するまで繰り返します。

承認後

承認タスクを result: "approved" と最終的なテスト/同期検証数を付けて完了にします。


フェーズ 6:ファイルを書き込む

生成したすべてのファイルを e2e/multi-user/ に書き込みます。

ステップ 1:ディレクトリ構造を作成する
1. Ensure e2e/multi-user/ exists (create if not).
2. Ensure e2e/multi-user/tests/ exists (create if not).
3. Ensure .github/workflows/ exists (create if not).
ステップ 2:すべてのファイルを書き込む

各ファイルを作成します:playwright.config.ts、package.json、tsconfig.json、ペルソナごとの <persona>.setup.ts、workflows.spec.ts、.gitignore(すべて e2e/multi-user/ 内)、およびリポジトリのルートに .github/workflows/e2e-multi-user.yml。

tsconfig.json の内容:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["tests/**/*.ts"]
}
ステップ 3:ファイルを検証する

書き込み後、各ファイルを読み戻し、正しく書き込まれたことを確認します。

ステップ 4:生成されたコードを型チェックする

プロジェクトを完成として提示する前に、生成されたプロジェクトに対して TypeScript の型チェックを実行し、コンパイルエラーを検出します。

前提条件: tsc が利用可能である必要があります。npx tsc --version が失敗した場合は、e2e/multi-user ディレクトリで npm install typescript --save-dev を試します。それでも TypeScript が見つからない場合は、停止してユーザーに次のように伝えます:「型チェックには TypeScript コンパイラ(tsc)が必要ですが、見つかりませんでした。npm install -D typescript でインストールするか、グローバルに利用できることを確認してください。」型チェックは必須です。省略してはいけません。

手順:

1. Install dependencies (capture errors for diagnosis):
   cd e2e/multi-user && npm install --ignore-scripts 2>&1 | tee /tmp/npm-install.log

2. Run type-check:
   cd e2e/multi-user && npx tsc --noEmit

3. If type errors are found:
   a. Read the tsc error output to identify the failing file and line.
   b. Fix the type error in the generated code.
   c. IMPORTANT: Write modified files back to disk BEFORE the next tsc run.
      Do not batch fixes — write each fix immediately so the next tsc run
      sees the corrected code on disk.
   d. Re-run: cd e2e/multi-user && npx tsc --noEmit
   e. Repeat up to a 3-attempt cap (counted per full tsc run, not per error).
      After 3 full tsc runs with errors, STOP and use AskUserQuestion to
      present the remaining errors to the user and ask for guidance.

セマンティクスのガード: 修正ループでは、アサーション、セレクター、テストの意図を変更してはいけません。修正は以下に限定されます。

  • 型注釈(明示的な型の追加、型の不一致の修正)
  • インポート文(不足しているインポート、誤ったインポートパス)
  • API の使用方法の修正(Playwright API メソッドのシグネチャの誤り)

修正によってテストの確認対象が変わる場合(アサーションの変更、セレクターの変更、テストロジックの変更、テスト手順の削除)は、適用しないでください。代わりに、AskUserQuestion を使ってユーザーにエスカレーションします。

Type error in [file]:[line] requires changing test intent to fix:

  Error: [tsc error message]
  Current code: [the line with the error]

  Fixing this would require changing [what would change — e.g., the assertion,
  the selector, the test logic]. This is beyond the scope of type-error fixes.

  How would you like to proceed?
  1. Fix it manually
  2. Suppress with // @ts-expect-error and move on
  3. Remove the affected test block
ステップ 5:タスクを更新する

書き込んだファイル数、出力ディレクトリ、CI ワークフローのパス、ペルソナセットアップファイル数を含む "Write: e2e/multi-user/"(完了)を作成します。メインタスク "Convert: Multi-User Workflows to Playwright" を、完全な要約メタデータを付けて完了にします。

最終要約

ユーザーに以下を提示します:出力ディレクトリ、CI ワークフローのパス、書き込んだファイル(テストブロック数、ステップ数、同期検証数を含む)、優先度ごとの変換済みワークフローの要約、ペルソナとセットアップファイル数、コメントアウトした手動手順、コードベースのセレクター、レビューのイテレーション数、次の手順(インストール、ローカルでの実行、すべてのペルソナ用 GitHub シークレットの設定、CI を起動するためのプッシュ)。


セッションの復旧

スキルが呼び出されたときに既存のタスクリストが見つかった場合は、次の判断ツリーに従って再開する場所を決定します。

判断ツリー
Check TaskList for "Convert: Multi-User Workflows to Playwright"

CASE 1: No task list exists
  -> Start from Phase 1

CASE 2: Parse task is "completed", no Check task
  -> Workflow file has been parsed, Persona Map is available
  -> Resume from Phase 2 (check existing project)

CASE 3: Check task is "completed", no Selector task
  -> Existing project has been checked
  -> Resume from Phase 3 (selector discovery)

CASE 4: Selector task is "in_progress"
  -> Agent may have timed out
  -> Re-spawn the Explore agent
  -> Resume from Phase 3 (partial)

CASE 5: Selector task is "completed", no Generate task
  -> Selectors have been discovered
  -> Resume from Phase 4 (generate project)

CASE 6: Generate task is "completed", no Approval task
  -> Files were generated but not reviewed
  -> Resume from Phase 5 (review with user)

CASE 7: Approval task exists with result "changes_requested"
  -> User gave feedback but revisions were not completed
  -> Read the feedback from task metadata
  -> Apply changes and re-present for review
  -> Resume from Phase 5 (next iteration)

CASE 8: Approval task is "completed" with result "approved", no Write task
  -> Tests were approved but files were not written
  -> Resume from Phase 6 (write files)

CASE 9: Write task is "completed"
  -> Everything is done
  -> Show the final summary and ask if the user wants to make changes
再開時は必ずユーザーに伝える
I found an existing session for multi-user workflow-to-Playwright conversion.

Current state: [describe where things left off]
Last completed phase: [phase name]
Personas from parse: [list of personas]

I will resume from [next phase]. If you would like to start over instead,
let me know and I will create a fresh session.

更新への対応

ユーザーが「更新」モード(変更されたワークフローに合わせて既存のテストを修正する)を選択した場合は、以下のルールに従います。

既存テストの更新ルール
  1. カスタム変更を保持する -- ユーザーが生成されたテストを手動で編集している場合(カスタムヘルパーの追加、セレクターの変更、追加アサーションの追加など)、それらの編集を保持してください。// CUSTOM: のようなコメントや、生成パターンと一致しないコードを探してください。

  2. ワークフローをテストブロックに対応付ける -- test.describe('Workflow N: ...') という命名規則を使用して、既存のテストブロックを元のワークフローに対応付けてください。一貫した命名が重要なのはこのためです。

  3. 変更されたワークフローを更新する -- 前回の生成後にワークフローの手順、ペルソナの割り当て、または同期タイミングが変更されている場合、そのワークフローの test.describe ブロックだけを再生成してください。ファイル内でブロックの位置は保持してください。

  4. 新しいワークフローを追加する -- 新しいワークフローは、適切な優先度グループ(Core Workflows、Feature Workflows、Edge Case Workflows)に追加します。各グループの末尾に追加してください。

  5. 削除されたワークフローを明示する -- 前回の生成後にワークフローが非推奨になった場合、そのテストブロックを削除せず、コメントアウトしてください。

// DEPRECATED: Workflow 9 -- Legacy Shared Calendar
// Reason: Calendar feature removed in v3.0
// Date: 2025-01-15
// test.describe('Workflow 9: Legacy Shared Calendar', () => { ... });
  1. 新しいペルソナのセットアップファイルを追加する -- Persona Registry に新しいペルソナが追加された場合、新しいセットアップファイルを生成し、対応するプロジェクトを playwright.config.ts と CI 環境変数に追加してください。

  2. 削除されたペルソナのセットアップファイルを非推奨にする -- 削除されたペルソナのセットアップファイルを削除してはいけません。代わりに、ファイルの先頭に // DEPRECATED コメントを追加してください。

  3. 設定ファイルを再生成する -- playwright.config.ts、package.json、.gitignore、および CI ワークフローは常に再生成します(カスタム変更を含めないでください)。現在のペルソナを反映するように dependencies 配列と CI 環境変数を更新してください。

  4. auth.setup.ts のカスタマイズを保持する -- ユーザーがペルソナのセットアップファイルをカスタマイズしている場合(異なるログインフロー、MFA、OAuth など)、そのバージョンを保持してください。明示的に要求された場合に限り再生成してください。

更新の概要

更新操作の後、変更概要を提示してください。テストブロックの保持・更新・非推奨化・追加、変更された各ワークフローの詳細、ペルソナの追加・削除、新しいセットアップファイル、再生成されたファイルと保持されたファイルを含めてください。


セレクター戦略のリファレンス

ワークフローの手順を Playwright コードに変換する際は、常に最も堅牢なセレクターを優先してください。この表は、Playwright の公式推奨事項に沿った優先順位を示しています。複数ユーザーのテストでも、セレクター戦略は同じです。ペルソナごとにページ変数だけが変わります。

優先度戦略使用場面例(管理者ペルソナ)
1getByRoleボタン、リンク、見出し、チェックボックス、ラジオボタン、および明示的な ARIA ロールを持つすべての要素adminPage.getByRole('button', { name: 'Submit' })
2getByLabel関連付けられた <label> 要素または aria-label 属性を持つフォーム入力hostPage.getByLabel('Email address')
3getByPlaceholderラベルはないが、プレースホルダーテキストがある入力guest1Page.getByPlaceholder('Search...')
4getByText表示テキストの内容で特定される、操作できない要素guest2Page.getByText('Welcome back')
5getByTestIddata-testid 属性を持つ要素。他のセレクターでは曖昧になる場合に便利viewerPage.getByTestId('sidebar-nav')
6CSS selectorセマンティックセレクターがない場合の最後の手段hostPage.locator('.custom-widget > .action-btn')
セレクターのアンチパターン

生成されるテストでは、ID セレクター(#submit-btn)、クラスセレクター(.btn-primary)、構造セレクター(div > span:nth-child(3))、属性セレクター([onclick="save()"])を避けてください。これらはすべて脆弱で、リファクタリング時に壊れます。上の優先度表にあるセマンティックロケーターを常に優先してください。


複数ユーザーテストのパターンに関するリファレンス

パターン 1:ペルソナ間の順次アクション

あるペルソナが操作し、その後、別のペルソナが確認します。最も一般的なパターンです。

await adminPage.getByRole('button', { name: 'Invite' }).click();
await expect(guest1Page.getByText(/invitation/i)).toBeVisible({ timeout: 5000 });
パターン 2:Promise.all による並列アクション

複数のペルソナが同時に操作する必要がある場合:

await Promise.all([
  hostPage.goto('/documents/shared-doc'),
  guest1Page.goto('/documents/shared-doc'),
]);
パターン 3:ポーリングベースの同期確認

プッシュ更新ではなくポーリングを使用する機能では、再試行ベースのアサーションに toPass() を使用します:

await expect(async () => {
  await guest1Page.reload();
  await expect(guest1Page.getByText('Updated Content')).toBeVisible();
}).toPass({ timeout: 15000, intervals: [1000, 2000, 3000] });
パターン 4:ロールベースの表示確認
await expect(adminPage.getByRole('button', { name: 'Delete' })).toBeVisible();
await expect(viewerPage.getByRole('button', { name: 'Delete' })).toBeHidden();
パターン 5:コンテキストのライフサイクル

ワークフローに含まれるペルソナのコンテキストのみを作成します。テストの終了時には必ずコンテキストを閉じてください:

test('workflow with Admin and Guest1 only', async ({ browser }) => {
  const adminCtx = await createAuthContext(browser, 'admin');
  const guest1Ctx = await createAuthContext(browser, 'guest1');
  const adminPage = await adminCtx.newPage();
  const guest1Page = await guest1Ctx.newPage();
  // ... test steps ...
  await adminCtx.close();
  await guest1Ctx.close();
});

制約

  • 使用可能なツール -- このスキルでは Read、Write、Glob、Grep、および Task/Explore ツールのみを使用します。Chrome MCP、iOS Simulator MCP、Playwright CLI、その他のブラウザー自動化ツールは使用しないでください。ブラウザー操作はすべてコードとして生成し、変換中に実行することはありません。
  • 出力先 -- すべてのテストファイルの出力先は e2e/multi-user/ です。CI ワークフローの出力先はリポジトリのルートにある .github/workflows/e2e-multi-user.yml です。
  • ペルソナごとの認証を必ず生成 -- すべてのペルソナが各ワークフローに登場するとは限りませんが、ペルソナレジストリ内のすべてのペルソナについてセットアップファイルを生成します。各セットアップファイルは、認証情報がない場合も適切に処理します。
  • Vercel ヘッダーを必ず含める -- 環境変数が設定されている場合、x-vercel-protection-bypass と x-vercel-set-bypass-cookie ヘッダーは、playwright.config.ts と createAuthContext ヘルパーの両方で条件付きで適用されます。
  • ワークフローへの実行時依存なし -- 生成されるテストプロジェクトは完全に自己完結しています。実行時にワークフローの Markdown ファイルを読み込んだり、インポートしたりすることはありません。
  • Playwright のベストプラクティス -- getByRole、getByLabel、getByText、getByPlaceholder、getByTestId の順に使用してください。絶対に必要な場合を除き、CSS セレクターや XPath セレクターは避けてください。
  • 複数コンテキストのベストプラクティス -- ペルソナごとにストレージ状態を分けて分離されたコンテキストを取得するには、browser.newPage() ではなく browser.newContext() を使用してください。テスト終了時にはすべてのコンテキストを閉じてください。
  • デフォルトでは逐次実行 -- ワークフロー内の複数ユーザーテストはステップ N がステップ N-1 に依存するため、本質的に逐次実行です。fullyParallel: false を設定してください。Promise.all() は、明示的に同時実行するアクションにのみ使用します。
  • ペルソナ変数の命名 -- 生成するすべてのテストで一貫性を保つため、常に <lowercase-persona>Ctx と <lowercase-persona>Page の命名規則を使用してください。

コメント