mobile-workflow-to-playwright について
mobile-workflow-to-playwrightは、モバイルのワークフロー文書をPlaywrightのE2Eテストプロジェクトに変換するスキルです。QA自動化向けに、モバイルUX検証や2種類のブラウザー設定、認証の土台、CI設定も生成します。
主な機能
- 01モバイルワークフロー文書の解析
- 02Playwrightテストプロジェクトの生成
- 03モバイルUXアサーションの追加
- 04ChromiumとWebKitの設定
- 05認証とCIの土台を用意
活用シーン
- 01ワークフロー文書から実行可能なテストを作成
- 02複数のブラウザーエンジンでモバイルフローを確認
- 03プロジェクトにモバイルE2Eテストを追加
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ neonwatty/ qa-skills --skill mobile-workflow-to-playwrightインストール後、Claude Code で/mobile-workflow-to-playwrightを入力して読み込まれたか確認します。
詳細
モバイルワークフローから Playwright への変換
あなたは、自然言語で書かれたモバイルワークフローのドキュメントを、モバイルビューポートに最適化された自己完結型の Playwright テストプロジェクトに変換する、シニア QA 自動化エンジニアです。/workflows/mobile-workflows.md からワークフローを読み取り、各ステップをモバイル固有の UX アサーションを備えた慣用的な Playwright コードに変換し、e2e/mobile/ に完全に機能するテストプロジェクトを作成します。このプロジェクトには、2 つのブラウザー(Chromium と WebKit)でのテスト、認証の足場、UX のアンチパターン検出、CI 設定、Vercel デプロイ保護ヘッダーを含めます。
生成されるすべてのテストは、cd e2e/mobile && npm ci && npx playwright test を実行すれば、そのまま動作する必要があります。
タスクリストとの連携
タスクリストは、エージェントの進捗を追跡し、ユーザーに状況を示し、中断後のセッション復旧を可能にし、レビューの繰り返しを記録するとともに、解析・生成・承認された内容の監査証跡として機能します。
タスク階層
このスキルを実行するたびに、次のタスクツリーが作成されます。タスクは順番に完了します。
[Main Task] "Convert: Mobile Workflows to Playwright"
+-- [Parse Task] "Parse: mobile-workflows.md"
+-- [Check Task] "Check: Existing e2e/mobile/ project"
+-- [Selector Task] "Selectors: Find for all workflows" (agent)
+-- [Generate Task] "Generate: Playwright project"
+-- [Approval Task] "Approval: Review generated tests"
+-- [Write Task] "Write: e2e/mobile/"
セッション復旧の確認
実行のたびに、何かを行う前に、まず既存のタスクリストがあるかどうかを確認します。
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.
完全な判断フローについては、このドキュメントの末尾近くにある「セッション復旧」セクションを参照してください。
変換パイプライン
このスキルは入力ファイルを 1 つ読み取り、完全なテストプロジェクトを生成します。
/workflows/mobile-workflows.md -> e2e/mobile/
+-- playwright.config.ts
+-- package.json
+-- tests/
| +-- auth.setup.ts
| +-- workflows.spec.ts
+-- .github/workflows/mobile-e2e.yml
+-- .gitignore
出力内のすべてのファイルは自己完結しています。実行時に元のワークフロー Markdown に依存することはありません。ワークフローは Playwright テストコードに完全にコンパイルされます。
フェーズ 1:ワークフローの解析
ワークフローの Markdown ファイルを読み込み、各ワークフローとそのメタデータを抽出して、後続のすべてのフェーズで使用する内部表現を作成します。
形式の参照先: 入力ワークフローファイルの形式は、
docs/workflow-format.mdで定義されています。見出しの形式、メタデータコメント、ステップの形式、認識される動詞、アサーションの種類については、この仕様を参照してください。
ステップ 1:ワークフローファイルを探す
Glob を使用して、ワークフローファイルを検索します。
Glob patterns:
- workflows/mobile-workflows.md
ファイルが見つからない場合は、処理を停止してユーザーに知らせます。
No mobile workflow file found at /workflows/mobile-workflows.md.
Please run "generate mobile workflows" first, or provide the path
to your workflow file.
ステップ 2:読み込みと解析
ワークフローファイル全体を読み込みます。各ワークフローについて、次の項目を抽出します。
- ワークフロー番号 --
## Workflow [N]:見出しから取得します - ワークフロー名 -- 番号の後に続く説明的な名前
- 認証要件 --
またはから取得します - 優先度 --
,、または `` から取得します - 推定ステップ数 -- `` から取得します
- 非推奨フラグ -- `` から取得します(非推奨のワークフローはスキップします)
- 前提条件 --
**Preconditions:**の下にある箇条書き - ステップ -- 番号付きの各ステップと、その検証サブステップ
- 事後条件 --
**Postconditions:**の下にある箇条書き
ステップ 3:内部表現の作成
ワークフローを構造化されたリストにまとめます。
workflows = [
{
number: 1,
name: "Mobile User Registration",
auth: false,
priority: "core",
estimatedSteps: 7,
preconditions: ["User is on the landing page on a mobile device"],
steps: [
{ action: "Navigate to /signup", verify: "Signup form is visible" },
{ action: "Tap the first name field and type 'John'", verify: "Field shows 'John'" },
...
],
postconditions: ["User account exists", "User is redirected to dashboard"]
},
...
]
`` に該当するワークフローはスキップします。スキップしたワークフローをユーザーに知らせます。
Parsed 25 workflows from mobile-workflows.md.
Skipped 2 deprecated workflows: #7 (Legacy Mobile Export), #15 (Old Settings Page).
Converting 23 active workflows.
ステップ 4:タスクの作成
TaskCreate:
title: "Convert: Mobile Workflows to Playwright"
status: "in_progress"
metadata:
source_file: "/workflows/mobile-workflows.md"
total_workflows: 25
active_workflows: 23
deprecated_skipped: 2
output_path: "e2e/mobile/"
TaskCreate:
title: "Parse: mobile-workflows.md"
status: "completed"
metadata:
workflows_parsed: 25
active: 23
deprecated: 2
core: 5
feature: 12
edge: 6
フェーズ 2:既存プロジェクトの確認
生成を始める前に、e2e/mobile/ ディレクトリがすでに存在するかどうかを確認します。
ステップ 1:既存ファイルの確認
Glob を使用して、既存のプロジェクトファイルを確認します。
Glob patterns:
- e2e/mobile/playwright.config.ts
- e2e/mobile/package.json
- e2e/mobile/tests/*.spec.ts
- e2e/mobile/tests/*.setup.ts
ステップ 2:方針の決定
既存のプロジェクトが見つからない場合:
- 新規生成を進めます。
- 追加の判断は必要ありません。
既存のプロジェクトが見つかった場合:
- すでにカバーされている内容を把握するため、既存の
tests/workflows.spec.tsを読み込みます。 AskUserQuestionを使用して、ユーザーの意図を確認します。
I found an existing Playwright project at e2e/mobile/ with [N] existing test blocks.
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:確認タスクの作成
TaskCreate:
title: "Check: Existing e2e/mobile/ project"
status: "completed"
metadata:
existing_project: true # or false
existing_tests: 18 # count of describe blocks
strategy: "overwrite" # or "update" or "fresh"
フェーズ 3:セレクターの検出[エージェントに委任]
Explore エージェントを起動してコードベースを分析し、ワークフローに記載された要素に最適な Playwright セレクターを特定します。
ステップ 1:タスクの作成
TaskCreate:
title: "Selectors: Find for all workflows"
status: "in_progress"
metadata:
agent_type: "explore"
focus: "selectors"
ステップ 2:Explore エージェントの起動
次のパラメーターを指定して、Task ツールからエージェントを起動します。
Task tool:
subagent_type: "Explore"
model: "sonnet"
prompt: |
You are a QA exploration agent focused on finding Playwright selectors
for mobile-optimized elements.
Your job is to find the best Playwright-compatible selectors for every
interactive element referenced in the workflow documentation. Pay special
attention to mobile-specific elements: hamburger menus, bottom navigation
bars, swipe targets, pull-to-refresh triggers, and touch-optimized controls.
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]
For each element, search for: data-testid, aria-label, role attributes,
<label> associations, placeholder text, and visible text content.
Prefer selectors in this order (Playwright recommended):
1. getByRole 2. getByLabel 3. getByPlaceholder
4. getByText 5. getByTestId 6. CSS selector (last resort)
Additionally, note any elements that appear to be touch-specific
(hamburger icons, bottom tabs, swipeable cards, etc.) and whether
they have appropriate ARIA attributes for accessibility.
Return findings as:
## Selector Map
| Workflow | Step | Element Description | Recommended Selector | Fallback Selector |
|----------|------|--------------------|--------------------|-------------------|
| 1 | 2 | "Save" button | getByRole('button', { name: 'Save' }) | getByTestId('save-btn') |
## Missing Selectors
- Elements not found in codebase (suggest data-testid additions)
## Mobile-Specific Findings
- Elements that change between mobile/desktop layouts
- Hamburger vs sidebar navigation differences
- Bottom nav vs top nav variations
## Selector Quality Report
- Counts by selector type and elements not found
Step 3: エージェントの結果を処理する
Explore エージェントが結果を返したら、そのセレクターマップを内部のワークフロー表現に統合します。コード生成では、各ステップで使用する具体的な Playwright セレクターが決まります。
TaskUpdate:
title: "Selectors: Find for all workflows"
status: "completed"
metadata:
selectors_found: 87
selectors_missing: 4
by_role: 42
by_label: 23
by_testid: 15
by_text: 7
css_fallback: 0
mobile_specific_elements: 12
エージェントが特定できなかった要素については、テストコードにコメントを生成します。
// TODO: Add data-testid for this element -- selector not found in codebase
await page.locator('[data-testid="unknown-element"]').click();
フェーズ 4: Playwright プロジェクトを生成する
これは生成の中核となるフェーズです。解析済みのワークフロー、検出したセレクター、設定テンプレートを使って、すべてのプロジェクトファイルを生成します。
Step 1: 生成タスクを作成する
TaskCreate:
title: "Generate: Playwright project"
status: "in_progress"
metadata:
files_to_generate: 6
Step 2: playwright.config.ts を生成する
2 つのモバイルブラウザープロジェクト(iPhone 15 Pro をエミュレートする Chromium と WebKit)、依存関係としての認証セットアップ、Vercel デプロイ保護バイパスヘッダーを含む Playwright 設定ファイルを生成します。
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
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: 'setup', testMatch: /.*\.setup\.ts/ },
{
name: 'mobile-chromium',
use: {
...devices['iPhone 15 Pro'],
storageState: 'playwright/.auth/user.json',
},
dependencies: ['setup'],
},
{
name: 'mobile-webkit',
use: {
...devices['iPhone 15 Pro'],
browserName: 'webkit',
storageState: 'playwright/.auth/user.json',
},
dependencies: ['setup'],
},
],
});
主な設定上の判断:速度を高めるための fullyParallel、不安定なテストを軽減するための CI でのみの retries: 2、失敗をデバッグするための trace: 'on-first-retry'、ローカルホストとデプロイ済み URL を柔軟に切り替えるための環境変数からの baseURL。Vercel バイパスヘッダーは、VERCEL_AUTOMATION_BYPASS_SECRET が設定されている場合にのみ条件付きで適用されます。setup プロジェクトは、storageState に依存するテストの実行前に auth.setup.ts を実行します。2 つのモバイルプロジェクトにより、Chromium ベースと WebKit ベースのモバイルブラウザーを網羅し、エンジン固有のレンダリングや動作の違いを検出できます。devices['iPhone 15 Pro'] プリセットでは、モバイルのビューポート寸法、デバイスのスケールファクター、タッチ操作のサポート、ユーザーエージェント文字列が設定されます。
Step 3: tests/auth.setup.ts を生成する
認証セットアップファイルを生成します。このファイルは、認証が必要なワークフローがない場合でも必ず生成されます。認証情報が提供されていない場合は、空のストレージ状態を適切に保存し、認証を必要としないテストが引き続き実行できるようにします。
import { test as setup } from '@playwright/test';
import * as fs from 'fs';
import * as path from 'path';
const authFile = 'playwright/.auth/user.json';
setup('authenticate', async ({ page }) => {
// Check for saved profiles from /setup-profiles
const profilesDir = path.join(process.cwd(), '.playwright', 'profiles');
const profilesConfig = path.join(process.cwd(), '.playwright', 'profiles.json');
if (fs.existsSync(profilesConfig)) {
const config = JSON.parse(fs.readFileSync(profilesConfig, 'utf-8'));
const profileName = Object.keys(config.profiles)[0];
const profilePath = path.join(profilesDir, `${profileName}.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.TEST_EMAIL || !process.env.TEST_PASSWORD) {
await page.context().storageState({ path: authFile });
return;
}
await page.goto('/login');
await page.getByLabel('Email').fill(process.env.TEST_EMAIL);
await page.getByLabel('Password').fill(process.env.TEST_PASSWORD);
await page.getByRole('button', { name: /sign in|log in/i }).click();
await page.waitForURL('**/dashboard');
await page.context().storageState({ path: authFile });
});
プロジェクトに /setup-profiles のプロファイルがある場合、認証セットアップは profiles.json を読み込んで最初に利用可能なプロファイルを特定し、その storageState を直接コピーします。ローカルでテストを実行する際に認証情報は必要ありません。CI では、代わりに環境変数の認証情報が使用されます。
主な認証上の判断:認証情報が設定されていない場合は、認証状態が空でも適切に保存されるため、認証を必要としないテストは引き続き成功します。正規表現のボタンマッチャー(/sign in|log in/i)は、一般的な表記の違いに対応します。特定のアプリケーション向けに生成する場合は、フェーズ 3 のセレクター検出結果に基づいて、ログインルート、フィールドラベル、ボタンテキスト、ログイン後の URL を調整します。
Step 4: package.json を生成する
{
"name": "mobile-e2e",
"private": true,
"scripts": {
"test": "playwright test",
"test:ui": "playwright test --ui",
"test:headed": "playwright test --headed",
"test:chromium": "playwright test --project=mobile-chromium",
"test:webkit": "playwright test --project=mobile-webkit"
},
"devDependencies": {
"@playwright/test": "^1.50.0"
}
}
Step 5: .github/workflows/mobile-e2e.yml を生成する
Vercel プレビューのデプロイに対してモバイルテストを実行する GitHub Actions CI ワークフローを生成します。2 つのエンジンを網羅するため、Chromium と WebKit の両方のブラウザーをインストールします。
name: Mobile 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/mobile && npm ci
- run: cd e2e/mobile && npx playwright install chromium webkit --with-deps
- run: cd e2e/mobile && npx playwright test
env:
BASE_URL: ${{ github.event.deployment_status.target_url }}
TEST_EMAIL: ${{ secrets.TEST_EMAIL }}
TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}
VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }}
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: mobile-playwright-report
path: e2e/mobile/playwright-report/
主な CI 上の判断:実際の Vercel プレビュー URL に対してテストを実行するため deployment_status でトリガーし、Preview 環境のみを対象とし、target_url を BASE_URL として使用します。3 つの GitHub シークレット(TEST_EMAIL、TEST_PASSWORD、VERCEL_AUTOMATION_BYPASS_SECRET)が必要です。毎回の実行で Playwright HTML レポートをアーティファクトとしてアップロードします。デュアルエンジンによるモバイルテストをすべて網羅するため、chromium と webkit の両方をインストールします。
Step 6: .gitignore を生成する
node_modules/
playwright/.auth/
playwright-report/
test-results/
Step 7: tests/workflows.spec.ts を生成する
これは最も規模が大きく、最も重要なファイルです。解析した各ワークフローを test.describe() ブロックに、各ワークフローのステップを 1 つ以上の Playwright アクションに対応付けます。生成されるすべてのテストには、test.step() ブロックとしてモバイル UX のアンチパターンに対するアサーションも含まれます。
構成
import { test, expect } from '@playwright/test';
test.describe('Core Workflows', () => {
test.describe('Workflow 1: Mobile User Registration', () => {
test('completes user registration flow on mobile', async ({ page }) => {
// Step 1: Navigate to /signup
await page.goto('/signup');
// Verify: Signup form is visible
await expect(page.getByRole('heading', { name: 'Sign Up' })).toBeVisible();
// Step 2: Tap the first name field and type "John"
await page.getByLabel('First name').fill('John');
// Step 3: Tap the email field and type "[email protected]"
await page.getByLabel('Email').fill('[email protected]');
// Step 4: Tap the "Create Account" button
await page.getByRole('button', { name: 'Create Account' }).click();
// Step 5: Verify success message appears
await expect(page.getByText('Account created successfully')).toBeVisible();
});
test('UX: touch targets and input accessibility', async ({ page }) => {
await page.goto('/signup');
// Runs all four UX checks -- see "UX Anti-Pattern Assertions" below
await test.step('touch targets are at least 44px', async () => { /* ... */ });
await test.step('input font-size >= 16px to prevent iOS zoom', async () => { /* ... */ });
await test.step('no hover-only interactions', async () => { /* ... */ });
await test.step('viewport meta tag is present', async () => { /* ... */ });
});
});
// ... more core workflow describe blocks
});
test.describe('Feature Workflows', () => {
// ... feature workflow describe blocks
});
test.describe('Edge Case Workflows', () => {
// ... edge case workflow describe blocks
});
グループ化のルール
- トップレベルグループ --
test.describe('Core Workflows', ...)、test.describe('Feature Workflows', ...)、test.describe('Edge Case Workflows', ...)は、ワークフローファイルの優先度区分に対応します。 - ワークフローブロック -- 各ワークフローは、適切な優先度グループ内にネストされた
test.describe('Workflow N: Name', ...)になります。 - ワークフローごとに2つのテスト -- 各ワークフローから、次の2つの
test()呼び出しを生成します。- 機能テスト:ワークフローのすべてのステップを順番に含みます(全体を一括で合否判定)。
- UX アンチパターンテスト:そのワークフローで訪問するページのモバイルユーザビリティをチェックします。
- ステップコメント -- ワークフローの各ステップの前に、コメント(
// Step N: ...)と検証内容(// Verify: ...)を記述します。
UX アンチパターンのアサーション
生成する各ワークフローには、ワークフロー中に訪問するページでモバイル向けのベストプラクティスを検証する、対応する UX テストが生成されます。明確にレポートできるよう、これらのアサーションは test.step() ブロックで囲みます。各ワークフローについて、訪問する一意のページを特定し(ナビゲーションのステップと URL の変化を確認)、ワークフローごとに1つの UX テストを生成します。このテストでは、主要なページに移動し、次の4つのチェックをすべて実行します。
必須のチェックは次の4つです。
-
タッチターゲットが44px以上 --
getByRole('button')とgetByRole('link')のすべての要素をループ処理します。それぞれについてboundingBox()を呼び出し、width >= 44およびheight >= 44をアサートします。これにより、Apple HIG および WCAG 2.5.8 のタップターゲット最小サイズを満たします。 -
入力欄のフォントサイズが16px以上 --
input, textarea, selectのすべての要素をループ処理します。それぞれについて、parseFloat(window.getComputedStyle(el).fontSize)を評価し、>= 16をアサートします。これにより、iOS Safari でフォーカス時に自動ズームされる動作を防ぎます。 -
ホバーのみの操作がないこと -- ページのスタイルシートを評価します。
:hoverを含む各 CSS ルールについて、一致する要素を見つけ、それらにもクリック/タップのハンドラーがあること(<button>、<a>、またはrole="button"であること)を確認します。違反を収集し、そのリストが空であることをアサートします。 -
viewport の meta タグ --
meta[name="viewport"]が1つ存在し、そのcontent属性にwidth=device-widthが含まれることをアサートします。
4つのチェックすべての完全な実装は、以下の「変換例」セクションに示されています。
ステップの変換ルール
ワークフローのステップを Playwright コードに変換するときは、次のルールを適用します。各要素に最適なセレクターを選ぶには、フェーズ3のセレクターマップを使用します。モバイルのワークフローではタップ操作の表現を使います。Playwright のモバイルエミュレーションがタッチイベントを自動的に処理するため、これは Playwright の .click() メソッドに対応します。
// Navigation
await page.goto('/dashboard');
// Tap actions (mobile tap = Playwright click with touch emulation)
await page.getByRole('button', { name: 'Save' }).click();
await page.getByRole('link', { name: 'Settings' }).click();
await page.getByText('Learn more').click();
// Tap hamburger menu / mobile navigation
await page.getByRole('button', { name: /menu|hamburger/i }).click();
await page.getByRole('navigation').getByRole('link', { name: 'Settings' }).click();
// Type / fill actions (tap field then type)
await page.getByLabel('Email').fill('[email protected]');
await page.getByPlaceholder('Search...').fill('search term');
// Select, checkbox, toggle
await page.getByLabel('Role').selectOption('Admin');
await page.getByLabel('Remember me').check();
await page.getByLabel('Send notifications').uncheck();
// Swipe actions (dispatch touchstart + touchend with offset clientX)
await page.locator('[data-testid="swipeable-card"]').evaluate((el) => {
el.dispatchEvent(new TouchEvent('touchstart', { touches: [new Touch({ identifier: 0, target: el, clientX: 300, clientY: 200 })] }));
el.dispatchEvent(new TouchEvent('touchend', { changedTouches: [new Touch({ identifier: 0, target: el, clientX: 50, clientY: 200 })] }));
});
// Pull-to-refresh (mouse drag down)
await page.mouse.move(200, 100);
await page.mouse.down();
await page.mouse.move(200, 400, { steps: 10 });
await page.mouse.up();
// Wait and visibility
await expect(page.getByText('Loading')).toBeHidden();
await page.waitForURL('**/dashboard');
// Verification assertions
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
await expect(page.getByText('Changes saved')).toBeVisible();
await expect(page).toHaveURL(/.*\/settings/);
await expect(page).toHaveTitle('Settings');
// Bottom navigation tap
await page.getByRole('navigation').getByRole('link', { name: 'Home' }).click();
await page.getByRole('navigation').getByRole('link', { name: 'Profile' }).click();
// Scroll into view (for content below the fold on mobile)
await page.getByText('Comments').scrollIntoViewIfNeeded();
// File upload
await page.getByLabel('Upload').setInputFiles('photo.jpg');
// Keyboard
await page.keyboard.press('Enter');
手動ステップの処理
[MANUAL] とマークされたワークフローのステップは自動化できません。スキップするテストステップ、または注釈付きのテストステップを生成します。
// Step 4: [MANUAL] Verify the push notification arrives on the device
// This step requires manual verification -- cannot be automated with Playwright.
// Consider using a push notification testing service for automation.
1つのステップだけが手動の場合、ワークフロー全体に対して test.skip() を生成しないでください。代わりにコメントを追加し、その後の自動化可能なステップを続けてください。
前提条件の処理
ワークフローに認証以外の前提条件がある場合は、test.beforeEach またはインラインのセットアップコードとして生成します。
test.describe('Workflow 5: Edit Existing Post', () => {
test('edits an existing post on mobile', async ({ page }) => {
// Precondition: At least one post exists
// Navigate to the posts list to verify
await page.goto('/posts');
await expect(page.getByRole('article')).toHaveCount(1, { timeout: 5000 });
// Step 1: Tap the first post title
await page.getByRole('article').first().click();
// ...
});
});
ステップ8:生成タスクを更新する
TaskUpdate:
title: "Generate: Playwright project"
status: "completed"
metadata:
files_generated: 6
test_describes: 23
functional_tests: 23
ux_tests: 23
total_steps_translated: 142
manual_steps_skipped: 3
selectors_from_agent: 87
アクションマッピングリファレンス
この表は、モバイルワークフローの表現から Playwright コードへの完全な対応を示します。手順を変換する際のクイックリファレンスとしてご利用ください。
| ワークフローの表現 | Playwright コード |
|---|---|
| /dashboard に移動 | await page.goto('/dashboard') |
| 「Save」ボタンをタップ | await page.getByRole('button', { name: 'Save' }).click() |
| 「Settings」リンクをタップ | await page.getByRole('link', { name: 'Settings' }).click() |
| ハンバーガーメニューをタップ | await page.getByRole('button', { name: /menu/i }).click() |
| 下部ナビゲーションの「Home」タブをタップ | await page.getByRole('navigation').getByRole('link', { name: 'Home' }).click() |
| メールフィールドに「hello」と入力 | await page.getByLabel('Email').fill('hello') |
| 検索ボックスに「query」と入力 | await page.getByPlaceholder('Search...').fill('query') |
| 見出し「Dashboard」が表示されることを確認 | await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible() |
| テキスト「Success」が表示されることを確認 | await expect(page.getByText('Success')).toBeVisible() |
| URL に /settings が含まれることを確認 | await expect(page).toHaveURL(/.*\/settings/) |
| ロールのドロップダウンから「Admin」を選択 | await page.getByLabel('Role').selectOption('Admin') |
| 「Remember me」チェックボックスをオンにする | await page.getByLabel('Remember me').check() |
| 「Notifications」チェックボックスをオフにする | await page.getByLabel('Notifications').uncheck() |
| 読み込み表示が消えるまで待機 | await expect(page.getByText('Loading')).toBeHidden() |
| URL に /dashboard が含まれるまで待機 | await page.waitForURL('**/dashboard') |
| 「photo.jpg」をアップロード | await page.getByLabel('Upload').setInputFiles('photo.jpg') |
| Enter キーを押して送信 | await page.keyboard.press('Enter') |
| コメントセクションまでスクロール | await page.getByText('Comments').scrollIntoViewIfNeeded() |
| カード上で左にスワイプ | await card.evaluate((el) => { /* touch events */ }) |
| 下に引っ張って更新 | await page.mouse.move(200, 100); /* drag down */ |
| 項目を長押し | await page.getByText('Item').click({ delay: 800 }) |
| 要素の件数が N であることを確認 | await expect(page.getByRole('listitem')).toHaveCount(N) |
| 入力値が「text」であることを確認 | await expect(page.getByLabel('Name')).toHaveValue('text') |
| 検索フィールドをクリア | await page.getByLabel('Search').clear() |
| キーボードを閉じる | await page.keyboard.press('Escape') |
| モーダルの外側をタップして閉じる | await page.locator('body').click({ position: { x: 10, y: 10 } }) |
変換例
以下は、mobile-workflows.md のワークフローを Playwright テストコードに変換する方法を示す、完成した例です。
入力ワークフロー
## Workflow 3: Create New Post
> Tests the complete flow of creating a new blog post from the mobile dashboard.
**Preconditions:**
- User is logged in as editor
**Steps:**
1. Navigate to /dashboard
- Verify the dashboard heading is visible
2. Tap the "New Post" button
- Verify the post editor page loads
3. Tap the title field and type "My First Post"
- Verify the title field shows "My First Post"
4. Tap the content editor and type "This is the post body content."
- Verify content appears in the editor
5. Tap the category dropdown and select "Technology"
- Verify "Technology" is selected
6. Tap the "Publish" button
- Verify success toast appears with message "Post published"
7. Verify the URL changes to /posts/[slug]
8. Navigate to /posts
- Verify "My First Post" appears in the post list
**Postconditions:**
- New post exists with title "My First Post"
- Post is visible in the public post list
出力テストコード
test.describe('Workflow 3: Create New Post', () => {
test('creates a new blog post from the mobile dashboard', async ({ page }) => {
// Step 1: Navigate to /dashboard
await page.goto('/dashboard');
// Verify: The dashboard heading is visible
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
// Step 2: Tap the "New Post" button
await page.getByRole('button', { name: 'New Post' }).click();
// Verify: The post editor page loads
await expect(page.getByRole('heading', { name: /editor|new post/i })).toBeVisible();
// Step 3: Tap the title field and type "My First Post"
await page.getByLabel('Title').fill('My First Post');
// Verify: The title field shows "My First Post"
await expect(page.getByLabel('Title')).toHaveValue('My First Post');
// Step 4: Tap the content editor and type "This is the post body content."
await page.getByLabel('Content').fill('This is the post body content.');
// Verify: Content appears in the editor
await expect(page.getByLabel('Content')).toHaveValue('This is the post body content.');
// Step 5: Tap the category dropdown and select "Technology"
await page.getByLabel('Category').selectOption('Technology');
// Verify: "Technology" is selected
await expect(page.getByLabel('Category')).toHaveValue('Technology');
// Step 6: Tap the "Publish" button
await page.getByRole('button', { name: 'Publish' }).click();
// Verify: Success toast appears
await expect(page.getByText('Post published')).toBeVisible();
// Step 7: Verify the URL changes to /posts/[slug]
await expect(page).toHaveURL(/\/posts\/.+/);
// Step 8: Navigate to /posts
await page.goto('/posts');
// Verify: "My First Post" appears in the post list
await expect(page.getByText('My First Post')).toBeVisible();
});
test('UX: mobile usability on post editor', async ({ page }) => {
await page.goto('/dashboard');
await page.getByRole('button', { name: 'New Post' }).click();
await test.step('touch targets are at least 44px', async () => {
const buttons = page.getByRole('button');
const count = await buttons.count();
for (let i = 0; i < count; i++) {
const box = await buttons.nth(i).boundingBox();
expect(box, `Button ${i} should have dimensions`).not.toBeNull();
expect(box!.width, `Button ${i} width >= 44px`).toBeGreaterThanOrEqual(44);
expect(box!.height, `Button ${i} height >= 44px`).toBeGreaterThanOrEqual(44);
}
});
await test.step('input font-size >= 16px to prevent iOS zoom', async () => {
const inputs = page.locator('input, textarea, select');
const count = await inputs.count();
for (let i = 0; i < count; i++) {
const fontSize = await inputs.nth(i).evaluate(
(el) => parseFloat(window.getComputedStyle(el).fontSize)
);
expect(fontSize, `Input ${i} font-size >= 16px`).toBeGreaterThanOrEqual(16);
}
});
await test.step('no hover-only interactions', async () => {
const hoverOnlyElements = await page.evaluate(() => {
const issues: string[] = [];
const sheets = Array.from(document.styleSheets);
for (const sheet of sheets) {
try {
const rules = Array.from(sheet.cssRules || []);
for (const rule of rules) {
if (rule instanceof CSSStyleRule && rule.selectorText?.includes(':hover')) {
const baseSelector = rule.selectorText.replace(/:hover/g, '');
const elements = document.querySelectorAll(baseSelector);
elements.forEach((el) => {
const style = window.getComputedStyle(el);
if (style.display !== 'none' && style.visibility !== 'hidden') {
const hasClickHandler = el.getAttribute('onclick') ||
el.tagName === 'BUTTON' || el.tagName === 'A' ||
el.getAttribute('role') === 'button';
if (!hasClickHandler) {
issues.push(`${el.tagName}.${el.className} has :hover but no tap handler`);
}
}
});
}
}
} catch {
// Cross-origin stylesheets cannot be inspected
}
}
return issues;
});
expect(hoverOnlyElements, 'Elements with hover-only interactions').toHaveLength(0);
});
await test.step('viewport meta tag is present', async () => {
const viewport = page.locator('meta[name="viewport"]');
await expect(viewport).toHaveCount(1);
const content = await viewport.getAttribute('content');
expect(content).toContain('width=device-width');
});
});
});
変換に関する注記
主なパターン:相互参照しやすいよう、手順のコメントはワークフローの記述に対応させます。「Verify」の各サブステップはすべて expect() アサーションに変換します。セレクターは優先順位(role > label > text > testid)に従い、Phase 3 の上書きを適用します。テスト名はワークフローの説明から導出します。動的な URL セグメントには正規表現パターンを使用します。また、各ワークフローには、訪問するページでモバイル固有の使いやすさを検証する UX テストを付属させます。UX テストでは、ワークフローの主要ページに移動し、タッチターゲット、入力欄のフォントサイズ、ホバー時のみの操作、ビューポートの meta 設定を確認します。
フェーズ 5:ユーザーとレビュー(必須)
このフェーズは必須です。ユーザーの承認なしにファイルを作成してはいけません。
生成したテストをレビュー用に提示する
AskUserQuestion を使用して、生成したプロジェクトを提示します。プロジェクト構成、テストの概要(優先度ごとの機能テスト数+UX テスト数)、ブラウザーのカバレッジ(mobile-chromium、mobile-webkit)、および生成した tests/workflows.spec.ts の全内容と(カスタマイズした場合は)auth.setup.ts の全内容を含めます。テストの翻訳、セレクター、UX のしきい値、認証フローをレビューするようユーザーに依頼します。「approved」または修正のフィードバックがあるまで待ちます。
承認タスクを作成する
TaskCreate:
title: "Approval: Review generated tests"
status: "in_progress"
metadata:
iteration: 1
test_blocks_presented: 23
functional_tests: 23
ux_tests: 23
total_steps: 142
フィードバックへの対応
ユーザーが承認せずにフィードバックを提供した場合:(1)変更を適用し、(2)現在の承認タスクを result: "changes_requested" および feedback_summary で完了としてマークし、(3)次の反復のために新しい承認タスクを作成し、(4)修正したテストを再提示します。ユーザーが承認するまで繰り返します。
承認されたら、承認タスクを result: "approved" と最終テスト件数で完了としてマークします。
フェーズ 6:ファイルの書き込み
生成したすべてのファイルを e2e/mobile/ に書き込みます。
ステップ 1:ディレクトリ構造の作成
1. Ensure e2e/mobile/ exists (create if not).
2. Ensure e2e/mobile/tests/ exists (create if not).
3. Ensure .github/workflows/ exists (create if not).
ステップ 2:すべてのファイルを書き込む
各ファイルを次の順序で書き込みます。
e2e/mobile/playwright.config.tse2e/mobile/package.jsone2e/mobile/tsconfig.jsone2e/mobile/tests/auth.setup.tse2e/mobile/tests/workflows.spec.tse2e/mobile/.gitignore.github/workflows/mobile-e2e.yml(注:これはe2e/mobile/内ではなく、リポジトリのルートにあります)
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/mobile ディレクトリで npm install typescript --save-dev を試してください。それでも TypeScript が見つからない場合は、作業を中止し、ユーザーに次のように伝えます:「型チェックには TypeScript コンパイラー(tsc)が必要ですが、見つかりませんでした。npm install -D typescript でインストールするか、グローバルに利用できることを確認してください。」型チェックは必須です。省略しないでください。
手順:
1. Install dependencies (capture errors for diagnosis):
cd e2e/mobile && npm install --ignore-scripts 2>&1 | tee /tmp/npm-install.log
2. Run type-check:
cd e2e/mobile && 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/mobile && 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:タスクの更新
"Write: e2e/mobile/" を完了としてマークし、メインタスク "Convert: Mobile Workflows to Playwright" を完了に更新して、最終メタデータ(書き込んだファイル、ワークフロー件数、ブラウザプロジェクト、レビューの反復回数)を記録します。
最終サマリー
完了サマリーには、出力ディレクトリ(e2e/mobile/)、CI ワークフローのパス、書き込んだすべてのファイル、変換件数(ワークフロー、非推奨項目、手動手順、セレクター、レビューの反復回数、ワークフローごとの UX チェック)、ブラウザプロジェクト(mobile-chromium、mobile-webkit)、および次の手順を記載します。
cd e2e/mobile && npm install && npx playwright install chromium webkitnpx playwright test --headed(ローカルのヘッド付きモード)npx playwright test --project=mobile-chromiumまたは--project=mobile-webkit(単一エンジン)- GitHub のシークレットを設定します:
TEST_EMAIL、TEST_PASSWORD、VERCEL_AUTOMATION_BYPASS_SECRET - 次回の Vercel プレビューのデプロイで CI が起動するよう、プッシュします
セッションの復旧
スキルが呼び出され、既存のタスクリストが見つかった場合は、タスク "Convert: Mobile Workflows to Playwright" を確認し、最後に完了したサブタスクを見つけます。次のフェーズから再開します。
| 最後に完了したタスク | 再開するフェーズ |
|---|---|
| なし | フェーズ 1(解析) |
| Parse | フェーズ 2(既存のものを確認) |
| Check | フェーズ 3(セレクターの検出) |
| Selectors (in_progress) | フェーズ 3(エージェントを再起動) |
| Selectors (completed) | フェーズ 4(生成) |
| Generate | フェーズ 5(レビュー) |
| Approval (changes_requested) | フェーズ 5(フィードバックを適用して再提示) |
| Approval (approved) | フェーズ 6(ファイルの書き込み) |
| Write | 完了(サマリーを表示) |
再開する際は、必ずユーザーに知らせてください。完了済みの内容と次のフェーズを伝え、最初からやり直すこともできると案内します。
更新の処理
ユーザーが「更新」モード(変更されたワークフローに合わせて既存のテストを変更するモード)を選択した場合は、次のルールに従ってください。
既存テストを更新する際のルール
-
カスタム変更を保持する -- ユーザーが生成されたテストを手動で編集している場合(カスタムヘルパーの追加、セレクターの変更、追加アサーションの追加など)は、その編集内容を保持してください。
// CUSTOM:のようなコメントや、生成パターンと一致しないコードを探してください。 -
ワークフローとテストブロックを対応付ける --
test.describe('Workflow N: ...')の命名規則を使用して、既存のテストブロックを元のワークフローに対応付けてください。このため、一貫した命名が重要です。 -
変更されたワークフローを更新する -- 前回の生成以降にワークフローの手順が変更されている場合は、そのワークフローの
test.describeブロック(機能テストと UX テストの両方)のみを再生成してください。ファイル内のブロックの位置は保持してください。 -
新しいワークフローを追加する -- 新しいワークフローは、適切な優先度グループ(
Core Workflows、Feature Workflows、Edge Case Workflows)に追加します。それぞれのグループの末尾に追加してください。 -
削除されたワークフローに印を付ける -- 前回の生成以降にワークフローが非推奨になった場合は、テストブロックを削除せず、コメントアウトしてください。
// DEPRECATED: Workflow 7 -- Legacy Mobile Export Feature
// Reason: Export feature removed in v2.3
// Date: 2025-01-15
// test.describe('Workflow 7: Legacy Mobile Export Feature', () => { ... });
-
設定ファイルを再生成する --
playwright.config.ts、package.json、.gitignore、CI ワークフローは、常にテンプレートから再生成してください(カスタム変更を含めないでください)。 -
auth.setup.tsのカスタマイズを保持する -- ユーザーが認証セットアップ(異なるログインフロー、複数ステップの認証など)をカスタマイズしている場合は、そのバージョンを保持してください。ユーザーから明示的に依頼された場合にのみ再生成してください。
更新の概要
更新処理後、保持、更新、非推奨化、追加されたテストブロックを一覧にした変更概要を提示してください。変更された具体的なワークフローとその理由、再生成されたファイルと保持されたファイルも記載してください。
セレクター戦略の参考情報
セレクターは次の順で優先してください:(1)getByRole(2)getByLabel(3)getByPlaceholder(4)getByText(5)getByTestId(6)CSS セレクター(最後の手段)。ID セレクター、クラスセレクター、構造セレクター、属性セレクターは壊れやすく、リファクタリングで機能しなくなるため、使用を避けてください。
制約
- 使用可能なツール -- このスキルでは、Read、Write、Glob、Grep、Task/Explore ツールのみを使用します。Chrome MCP、iOS Simulator MCP、Playwright CLI、その他のブラウザー自動化ツールは使用しないでください。ブラウザーとのやり取りはすべてコードとして生成し、変換中に実行しないでください。
- 出力先 -- すべてのテストファイルは
e2e/mobile/に配置します。CI ワークフローは、リポジトリのルートにある.github/workflows/mobile-e2e.ymlに配置します。 - 認証は常に生成する -- 認証を必要とするワークフローがゼロであっても、
auth.setup.tsは常に含めます。認証情報がない場合も適切に処理します。 - Vercel ヘッダーは常に含める -- 環境変数が設定されている場合、
playwright.config.tsでx-vercel-protection-bypassとx-vercel-set-bypass-cookieヘッダーを条件付きで適用します。 - ワークフローへの実行時依存は禁止 -- 生成されるテストプロジェクトは完全に自己完結しています。実行時にワークフローの Markdown ファイルを読み込んだり、インポートしたりすることはありません。
- Playwright のベストプラクティス --
getByRole、getByLabel、getByText、getByPlaceholder、getByTestIdの順に使用してください。絶対に必要な場合を除き、CSS セレクターと XPath セレクターは避けてください。 - 2 つのブラウザーでテスト -- エンジン固有のレンダリングや動作の違いを検出するため、すべてのテストを
mobile-chromiumとmobile-webkitの両プロジェクトで実行します。 - UX アンチパターンのアサーション -- 各ワークフローでは、モバイルでの使いやすさ(タッチターゲット、入力時のズーム防止、ホバーのみの検出、viewport meta)を確認する UX テストも生成します。これらは任意ではなく、常に生成されます。
- モバイルのビューポート -- すべてのテストは iPhone 15 Pro のデバイスエミュレーションプロファイルで実行します。このプロファイルでは、適切なビューポート寸法、デバイススケールファクター、タッチサポート、ユーザーエージェント文字列が設定されます。