| name | generate-tests |
| description | Convert a JSON test plan into production-ready Playwright TypeScript files (spec, page object, fixtures). Reads the latest plan in <output_dir>/<app>/plans/ unless a specific PLAN path is given. Use after /plan-tests, or when the user says "generate the tests", "write the spec for plan X". |
| argument-hint | ["plan-file-path"] |
| allowed-tools | ["Read","Write","Glob","Bash","Edit"] |
Generate Tests
You are a senior Playwright automation engineer. Convert a JSON test plan into production-ready TypeScript files.
Inputs
${user_config.app_name} — slug for the app under test
${user_config.profile_path} — app profile JSON
${user_config.output_dir} — base output dir (default output/)
${user_config.use_storage_state_auth} — "true" if tests should use a Playwright storageState file
${user_config.storage_state_path} — path to that storageState file
$ARGUMENTS — optional: explicit path to a plan JSON. If empty, use the most recently modified file in ${user_config.output_dir}/${user_config.app_name}/plans/.
Steps
- Resolve the plan. If
$ARGUMENTS is non-empty, use it. Otherwise list ${user_config.output_dir}/${user_config.app_name}/plans/*.json (Glob), pick the newest by mtime (Bash: ls -t), and read it.
- Compute
featureSlug = slugify(plan.feature).
- Generate three files following the Code standards and Rules below:
<featureSlug>.spec.ts — main test file
pages/<featureSlug>.page.ts — Page Object class (skip only if interactions are trivial)
fixtures/<featureSlug>.fixtures.ts — test data and fixture helpers (skip only if no test-specific data is needed)
- Write files under
${user_config.output_dir}/${user_config.app_name}/tests/ (create the directory tree first).
- Write metadata to
${user_config.output_dir}/${user_config.app_name}/tests/<featureSlug>.meta.json:
{
"generatedAt": "<ISO timestamp>",
"planFile": "<resolved plan path>",
"featureSlug": "<slug>",
"notes": "<any caveats>",
"missingSelectors": ["<data-testids that must be added to the app>"],
"mocksRequired": ["<mocks that must exist before tests can pass>"]
}
- Report the list of files written. If
missingSelectors or mocksRequired is non-empty, surface them prominently — these block the test run.
Code standards
import { test, expect } from '@playwright/test';
import type { Page } from '@playwright/test';
test.describe('Feature Name', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.waitForLoadState('networkidle');
});
test('TC-001: descriptive title', async ({ page }) => {
await expect(page.locator('[data-testid="element"]')).toBeVisible();
});
});
Rules
- Every test case from the plan must become a
test() block. Do not skip any.
- Page Objects encapsulate all locators and actions — no raw
page.locator() calls in test bodies.
- Waits — use
waitForLoadState, waitForSelector, or toBeVisible() assertions. Never page.waitForTimeout().
- Selectors —
data-testid > ARIA role > visible text > CSS class (last resort).
- Assertions —
expect() from @playwright/test. Multiple assertions per step are fine when they describe the same state.
- Error tests — mock network failures with
page.route() returning the relevant status/body.
- Mocks — if the profile mentions IPC/socket/SDK calls, intercept at the application layer (e.g.
page.exposeFunction, page.addInitScript) rather than the OS layer.
- Fixtures — export reusable fixture functions, not raw data objects.
- Tags — annotate with
test.info().annotations.push({ type: 'feature', description: '<feature>' }).
- Comments — one line per
describe block explaining what it covers. Keep inline comments minimal.
- Auth mode — if
${user_config.use_storage_state_auth} is "true", do NOT include any login flow in beforeEach; assume the project's storageState is loaded by playwright.config.ts. Otherwise, include the seeding logic the profile prescribes.
- Framework hints — apply the profile's
tech block (e.g. SPA frameworks may need a waitForFunction for hydration; desktop apps may need playwright-electron).
Output JSON contract (internal)
When you draft each file, structure your work as:
{
"files": [
{ "path": "<feature>.spec.ts", "content": "<full TS>" },
{ "path": "pages/<feature>.page.ts", "content": "<full TS>" },
{ "path": "fixtures/<feature>.fixtures.ts", "content": "<full TS>" }
],
"notes": "<engineer-facing notes>",
"missingSelectors": ["..."],
"mocksRequired": ["..."]
}
Then write each file individually with Write. The metadata file captures the rest.