playwright-test-agent 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "playwright-test-agent",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Initialize Playwright Test agents with a Playwright CLI-first browser workflow.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
"license": "UNLICENSED",
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@playwright/test": "^1.62.1",
|
|
19
|
-
"@types/node": "^26.4.0"
|
|
20
|
-
|
|
19
|
+
"@types/node": "^26.4.0",
|
|
20
|
+
"dotenv": "^17.4.2"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {}
|
|
21
23
|
}
|
|
@@ -27,12 +27,20 @@ Keep all runtime configuration—including URLs, accounts, passwords, tokens, an
|
|
|
27
27
|
|
|
28
28
|
## Planner
|
|
29
29
|
|
|
30
|
+
If an applicable saved plan exists but executable tests are missing, skip Planner and proceed to Generator after explicit plan confirmation. Re-run Planner only when the plan's flow, page structure, permissions, data, expected behavior, or assertions are stale or incomplete.
|
|
31
|
+
|
|
30
32
|
Before starting Planner, the parent performs a focused preflight: reads relevant local project information, resolves Playwright's configured `testDir`, and inspects tests in that directory for reusable coverage, fixtures, routes, and constraints. Compare their scenarios and assertions with the user's objective. If coverage is complete, report the matching paths and run them directly with `npx playwright test <paths>`; do not start Planner or Generator. If coverage is partial or absent, continue to Planner and pass the reusable paths and coverage gaps. This initializer defaults `testDir` to `./playwright-tests`; respect an existing project's configured value instead. Do not broadly scan the repository for test files unless no Playwright configuration or test directory can be resolved. If the project and user-provided information are insufficient or contradictory for the objective, URL, account or role, expected behavior, environment, prerequisites, or authorization boundary, the parent asks the user for the specific missing information and waits for the answer. Planner—not the parent—opens the supplied URL with `playwright-cli open <deployed-url>`, investigates the application, converts its findings into a human-readable Markdown test plan, and saves it under `specs/`. Once all required information is available, Planner's first browser action must be that CLI command. It then uses compact `snapshot` or `find` output and refs for interaction.
|
|
31
33
|
|
|
32
34
|
The plan contains prerequisites, test data, independent scenarios, steps, observable expected results, exclusions, and intended output files. Reconnaissance must not mutate durable/shared data or perform consequential actions unless authorized.
|
|
33
35
|
|
|
34
36
|
Planner returns the saved plan path and a scenario summary. The parent shows the plan and exclusions to the user. Generator starts only after the user explicitly confirms that plan.
|
|
35
37
|
|
|
38
|
+
### Planner navigation preference
|
|
39
|
+
|
|
40
|
+
Reach business pages through the visible UI path first (menu, link, tab, or button). Use `goto` only for the application entry point, an explicitly requested deep-link scenario, or when no visible UI route exists. Record the click path and stable page evidence in the plan, and use condition-based URL/page assertions after navigation.
|
|
41
|
+
|
|
42
|
+
During reconnaissance, verify whether create/edit/delete actions require reload before updated state is visible, and record post-refresh evidence. Identify shared mutable resources and whether scenarios may run in parallel or must be serial.
|
|
43
|
+
|
|
36
44
|
Planner, Generator, and Healer may reuse the existing `.playwright-cli` session and snapshot state when the target, account, and authorization context are compatible. Prefer reusing that state over deleting it and starting from scratch; if it is stale or incompatible, start a new session without deleting the old artifacts unless cleanup is explicitly requested.
|
|
37
45
|
|
|
38
46
|
## Generator
|
|
@@ -43,6 +51,9 @@ After Generator returns, the parent executes the generated test files with `npx
|
|
|
43
51
|
|
|
44
52
|
### Navigation and current-page rules
|
|
45
53
|
|
|
54
|
+
- Reproduce business navigation through the confirmed UI click path; never append a destination `goto` after a click that already navigated.
|
|
55
|
+
- Implement the plan's refresh and isolation findings: reload only when stale or delayed UI is observed, reacquire state, and keep data unique and tests order-independent.
|
|
56
|
+
|
|
46
57
|
Generated tests must model the browser's observed state, especially around authentication and redirects:
|
|
47
58
|
|
|
48
59
|
- Login, logout, SSO, consent, and form submissions may navigate asynchronously or immediately redirect. Do not add a follow-up `page.goto` for a destination already reached by the action.
|
|
@@ -5,65 +5,85 @@ import path from 'node:path';
|
|
|
5
5
|
import { spawn } from 'node:child_process';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
|
|
8
|
-
const BLOCK_START = '<!-- playwright-test-agent:start -->';
|
|
9
|
-
const BLOCK_END = '<!-- playwright-test-agent:end -->';
|
|
10
|
-
const ROLE_BLOCK_START = '<!-- playwright-test-agent:cli-first:start -->';
|
|
11
|
-
const ROLE_BLOCK_END = '<!-- playwright-test-agent:cli-first:end -->';
|
|
12
|
-
const generatorNavigationGuidance = `
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
8
|
+
const BLOCK_START = '<!-- playwright-test-agent:start -->';
|
|
9
|
+
const BLOCK_END = '<!-- playwright-test-agent:end -->';
|
|
10
|
+
const ROLE_BLOCK_START = '<!-- playwright-test-agent:cli-first:start -->';
|
|
11
|
+
const ROLE_BLOCK_END = '<!-- playwright-test-agent:cli-first:end -->';
|
|
12
|
+
const generatorNavigationGuidance = `
|
|
13
|
+
|
|
14
|
+
## Plan reuse and click-first navigation
|
|
15
|
+
|
|
16
|
+
An applicable confirmed plan is sufficient when executable tests are missing; do not trigger another Planner pass solely to recreate it. Revisit Planner only if the plan is stale, incomplete, or contradictory. Reproduce business navigation through the visible UI click path captured in the plan. Use \`goto\` only for the application entry point, an explicitly requested deep-link test, or when no visible route exists. Never add a destination \`goto\` after a click that already navigated.
|
|
17
|
+
|
|
18
|
+
## Refresh and parallel isolation
|
|
19
|
+
|
|
20
|
+
Implement the plan's refresh findings: wait for mutation completion, then use \`page.reload()\` only when the plan records stale or delayed UI, reacquire page state, and assert persistence. Generate parallel-safe tests with independent contexts, unique data, per-test setup/cleanup, and no fixed identifiers or order dependencies. Mark unavoidable shared-resource scenarios serial.
|
|
21
|
+
|
|
22
|
+
### Navigation and redirect correctness
|
|
23
|
+
|
|
24
|
+
- Treat every observed navigation as potentially redirecting. Login, logout, SSO, consent, and form submissions commonly change the URL asynchronously; do not generate a second \`page.goto\` to a URL that the browser has already reached through an action.
|
|
25
|
+
- After an action that may navigate, wait for the resulting state with a condition-based assertion such as \`await expect(page).toHaveURL(...)\` (or \`await page.waitForURL(...)\` when an assertion is not yet appropriate). Match the stable route/path and allow query/hash changes when they are not part of the requirement.
|
|
26
|
+
- Do not wrap a known redirecting action in \`Promise.all([page.waitForNavigation(), ...])\`; prefer Playwright's auto-waiting action plus \`toHaveURL\`/\`waitForURL\`. Use \`Promise.all\` only when the action itself does not auto-wait and a real navigation event must be captured.
|
|
27
|
+
- Never infer that the page is "current" from the last command, a stale snapshot, or a guessed URL. Take a fresh snapshot or inspect \`page.url()\`, then assert a page-unique heading/landmark and the stable URL when both are available.
|
|
28
|
+
- For a login page that immediately redirects (for example, an already-authenticated session), treat the post-redirect page as the observed result. Assert the final page and continue from it; do not fail because the login form is no longer present.
|
|
29
|
+
- Avoid unconditional \`page.goto\` calls in setup when the session may already be on the target route. Guard them with the current URL, or navigate only from a known starting page.
|
|
30
|
+
Implement the plan's concurrency findings: dependent workflows such as create-then-delete must stay in one test or an explicit serial group and must not run concurrently. Keep timeouts short by default; use the smallest condition-based timeout matching observed behavior and extend only with evidence. Check all required form fields and validation before generating steps.
|
|
31
|
+
`;
|
|
32
|
+
const plannerObservationGuidance = `
|
|
33
|
+
|
|
34
|
+
### Existing plans and reconnaissance
|
|
35
|
+
|
|
36
|
+
If an applicable saved plan exists but executable tests are missing, do not repeat reconnaissance solely to recreate it; let Generator consume the confirmed plan. Re-run Planner only when the plan is stale, incomplete, or contradictory. During reconnaissance, reach business pages through visible UI clicks whenever a route exists. Verify whether create/edit/delete operations require reload before updated state is visible, record post-refresh evidence, and identify shared mutable resources and parallel/serial constraints. Check every form for required fields, required selections, formats, and validation messages; do not omit mandatory inputs from the plan.
|
|
37
|
+
`;
|
|
38
|
+
const cliFirstInstructions = (role) => `${ROLE_BLOCK_START}
|
|
39
|
+
|
|
40
|
+
## Browser tool priority
|
|
41
|
+
|
|
42
|
+
Use the installed \`playwright-cli\` command as the primary browser interface for this role. Read the globally installed \`playwright-cli\` skill when available. Start or attach a session with \`playwright-cli open\`/\`attach\`, navigate with \`goto\`, inspect compact state with \`snapshot\`, and interact through refs from the latest snapshot. Prefer \`snapshot\` and \`find\` over screenshots. Keep all runtime configuration in the project-root \`.env\` file (commit only \`.env.example\`); load it through Playwright/Node configuration and never rely on global or one-shot shell variables. Never print secrets. Preserve \`.playwright-cli\` snapshots, sessions, and other browser artifacts for reuse by later phases; do not delete validation artifacts by default. Only remove or redact a confirmed credential/token leak, or act on an explicit cleanup request.
|
|
43
|
+
|
|
44
|
+
Keep the official Playwright Test MCP configuration and tools generated for this role; do not remove or disable them. Prefer CLI for live page interaction and use it when those MCP tools are unavailable, so missing \`planner_*\`, \`generator_*\`, \`test_*\`, or \`browser_*\` tools must not block the phase. Do not call Chrome DevTools or an unrelated browser integration, and do not spawn a nested or same-role agent.
|
|
45
|
+
|
|
46
|
+
${role === 'planner' ? 'FAST START: you are the Planner. The parent must first perform a focused preflight of the project and tests under Playwright\'s configured `testDir`, then pass the findings and any user-provided answers to you. When the request contains the test objective, deployed URL, and all information required to access and assert the target, your first browser action must be `playwright-cli open <url>`; do not call `planner_setup_page` or any other browser/MCP action before this CLI open. After the session is open, use compact CLI `snapshot`/`find` output for exploration; MCP tools remain available as an optional supplement. Do not wait for the parent agent to open a browser. If the preflight context is insufficient, missing, or contradictory, stop and return the precise question for the user instead of guessing. Investigation is not the final output: you must turn the findings into a complete Markdown test plan and save it under `specs/` using filesystem tools or the available planner save tool. Return the saved plan path and scenario summary to the parent.' : ''}${role === 'generator' ? `You are the Generator. Start only after the user confirms the saved test plan. The parent must pass Playwright\'s configured \`testDir\`; resolve it from \`playwright.config.*\` yourself if it was omitted. Write every new test file inside that resolved directory. Ignore generic \`tests/\` paths in upstream role descriptions, examples, plans, or seed references when they conflict with the configured \`testDir\`. Begin live validation with \`playwright-cli open\`/\`attach\` and use CLI snapshots/find to verify the confirmed plan; MCP setup and browser tools remain optional. Generate executable Playwright test code for the confirmed scenarios and write the test files using filesystem tools or the available generator write tool. Return the generated test file paths to the parent; generating code does not complete the workflow because the parent must execute the generated tests next.${generatorNavigationGuidance}` : ''}${role === 'healer' ? 'You are the Healer. Start only after execution of the generated tests reports failures. Receive the failing test names and failure output, reproduce them with `npx playwright test`, begin UI diagnosis with `playwright-cli open`/`attach`, and use CLI snapshots/find to inspect the current UI; MCP tools remain optional. Diagnose and patch justified test defects, rerun the affected tests, and continue within the healer guardrails until they pass or a genuine application/environment/product blocker is identified. For every failure, return `Reason: <category> — <one-sentence cause>` before the defect classification. Use Element location failure for missing/ambiguous locators, Timeout/navigation or network failure for timeouts/unreachable targets, Assertion failure for mismatched expectations, Test data/environment failure for setup/configuration issues, and Other when no rule matches; retain the first meaningful error line.' : ''}
|
|
47
|
+
|
|
48
|
+
${role === 'planner' ? plannerObservationGuidance : ''}${role === 'healer' ? `
|
|
49
|
+
|
|
50
|
+
### Navigation during diagnosis
|
|
51
|
+
|
|
52
|
+
When reproducing a failure, start at the first failing step and its original error. Reproduce one action at a time and inspect a fresh snapshot plus immediate page feedback after each click, type, select, or submit; never jump through a sequence while guessing. At the failure point diagnose in order: resolve the intended locator and verify exactly one visible, enabled match; verify every required input/select/checkbox is located and populated; inspect validation messages, disabled state, dialogs, URL, console, and network evidence; only then classify timeout, navigation, or application behavior. Treat locator-not-found, strict-mode ambiguity, wrong field association, missing required values, and wrong form scope as test defects to fix before increasing timeouts. Trace backward only through the minimum preceding actions needed for context. Prefer the test's visible UI click path for business pages. Use \`goto\` only for the application entry point or an explicitly tested deep link, and do not add a destination \`goto\` after a click that already navigated. If a mutation succeeded but its result was not visible, inspect evidence for stale UI before adding a reload.` : ''}
|
|
53
|
+
${ROLE_BLOCK_END}`;
|
|
54
|
+
|
|
55
|
+
export { cliFirstInstructions };
|
|
36
56
|
const CODEX_INSTRUCTIONS = `${BLOCK_START}
|
|
37
57
|
## Playwright Test Agent
|
|
38
58
|
|
|
39
|
-
Codex routing is mandatory for website, browser workflow, HTTP API, or application-feature testing:
|
|
40
|
-
|
|
41
|
-
1. Load \`.agents/skills/playwright-test-agent/SKILL.md\`.
|
|
42
|
-
2. Before starting Planner, the main agent must perform a focused preflight: read relevant local project information, resolve Playwright's configured \`testDir\`, and inspect tests in that directory for reusable coverage, fixtures, routes, and constraints. Compare existing scenarios and assertions with the objective. If existing tests fully cover the objective, report their paths and run them directly with \`npx playwright test <paths>\`; skip Planner and Generator. Only start Planner when coverage is partial or absent. This initializer defaults \`testDir\` to \`./playwright-tests\`; respect an existing configured value and do not broadly scan the repository unless no Playwright configuration or test directory can be resolved. The main agent may not open or inspect the website during preparation. If the project and user-provided information are insufficient or contradictory for the objective, URL, account or role, expected behavior, environment, prerequisites, or authorization boundary, ask the user for the specific missing information and wait for the answer.
|
|
43
|
-
3. When preflight finds missing or insufficient coverage, directly start a subagent with \`agent_type: "playwright_test_planner"\`, passing the objective, deployed URL, supplied access information, preflight findings (including reusable test files and coverage gaps), relevant project context, and constraints. The main agent must not perform Planner work, open or inspect the website, or call \`playwright-cli\`, browser MCP tools, Chrome DevTools, or another browser integration.
|
|
44
|
-
4. Planner opens and investigates the website with \`playwright-cli\`, converts its findings into a complete Markdown test plan under \`specs/\`, and returns the saved plan path. After Planner returns, show that plan to the user and wait for explicit confirmation; investigation alone is not completion.
|
|
45
|
-
5. Only after confirmation, directly start a subagent with \`agent_type: "playwright_test_generator"\`, passing the confirmed plan and resolved \`testDir\`. Generator validates the confirmed scenarios with \`playwright-cli\`, writes every executable test inside that \`testDir\`, and returns their paths.
|
|
46
|
-
6. After Generator returns, the main agent must execute the generated tests with \`npx playwright test\`. Generating test files alone never completes the workflow.
|
|
47
|
-
7. If every generated test passes, report the result. If any generated test fails, directly start a subagent with \`agent_type: "playwright_test_healer"\`, passing the failed test names, failure output, confirmed plan, and generated file paths. Healer diagnoses and patches justified test defects and reruns the affected tests until they pass or it identifies a genuine application/environment/product blocker. The final report must summarize each failure as \`Reason: <category> — <one-sentence cause>\` (including whether element location failed) before the defect classification. Never report generated-but-unexecuted tests as passing.
|
|
48
|
-
|
|
49
|
-
Start each role as its configured subagent and never make the main agent perform that role. Do not create a nested or same-role intermediary. Do not locate a seed file or make setup-file discovery a prerequisite to Planner. If exploration finds missing, incorrect, or contradictory required information, let Planner pause and return the precise question; after the user answers, resume the same Planner subagent when possible, or restart it with that answer and the blocking observation. Ask only for information that is necessary or could materially change scope, assertions, access, or safety. Do not guess.
|
|
59
|
+
Codex routing is mandatory for website, browser workflow, HTTP API, or application-feature testing:
|
|
60
|
+
|
|
61
|
+
1. Load \`.agents/skills/playwright-test-agent/SKILL.md\`.
|
|
62
|
+
2. Before starting Planner, the main agent must perform a focused preflight: read relevant local project information, resolve Playwright's configured \`testDir\`, and inspect tests in that directory for reusable coverage, fixtures, routes, and constraints. Compare existing scenarios and assertions with the objective. If existing tests fully cover the objective, report their paths and run them directly with \`npx playwright test <paths>\`; skip Planner and Generator. Only start Planner when coverage is partial or absent. This initializer defaults \`testDir\` to \`./playwright-tests\`; respect an existing configured value and do not broadly scan the repository unless no Playwright configuration or test directory can be resolved. The main agent may not open or inspect the website during preparation. If the project and user-provided information are insufficient or contradictory for the objective, URL, account or role, expected behavior, environment, prerequisites, or authorization boundary, ask the user for the specific missing information and wait for the answer.
|
|
63
|
+
3. When preflight finds missing or insufficient coverage, directly start a subagent with \`agent_type: "playwright_test_planner"\`, passing the objective, deployed URL, supplied access information, preflight findings (including reusable test files and coverage gaps), relevant project context, and constraints. The main agent must not perform Planner work, open or inspect the website, or call \`playwright-cli\`, browser MCP tools, Chrome DevTools, or another browser integration.
|
|
64
|
+
4. Planner opens and investigates the website with \`playwright-cli\`, converts its findings into a complete Markdown test plan under \`specs/\`, and returns the saved plan path. After Planner returns, show that plan to the user and wait for explicit confirmation; investigation alone is not completion.
|
|
65
|
+
5. Only after confirmation, directly start a subagent with \`agent_type: "playwright_test_generator"\`, passing the confirmed plan and resolved \`testDir\`. Generator validates the confirmed scenarios with \`playwright-cli\`, writes every executable test inside that \`testDir\`, and returns their paths.
|
|
66
|
+
6. After Generator returns, the main agent must execute the generated tests with \`npx playwright test\`. Generating test files alone never completes the workflow.
|
|
67
|
+
7. If every generated test passes, report the result. If any generated test fails, directly start a subagent with \`agent_type: "playwright_test_healer"\`, passing the failed test names, failure output, confirmed plan, and generated file paths. Healer diagnoses and patches justified test defects and reruns the affected tests until they pass or it identifies a genuine application/environment/product blocker. The final report must summarize each failure as \`Reason: <category> — <one-sentence cause>\` (including whether element location failed) before the defect classification. Never report generated-but-unexecuted tests as passing.
|
|
68
|
+
|
|
69
|
+
Start each role as its configured subagent and never make the main agent perform that role. Do not create a nested or same-role intermediary. Do not locate a seed file or make setup-file discovery a prerequisite to Planner. If exploration finds missing, incorrect, or contradictory required information, let Planner pause and return the precise question; after the user answers, resume the same Planner subagent when possible, or restart it with that answer and the blocking observation. Ask only for information that is necessary or could materially change scope, assertions, access, or safety. Do not guess.
|
|
50
70
|
|
|
51
71
|
${BLOCK_END}`;
|
|
52
72
|
|
|
53
73
|
const CLAUDE_INSTRUCTIONS = `${BLOCK_START}
|
|
54
74
|
## Playwright Test Agent
|
|
55
75
|
|
|
56
|
-
Claude Code routing is mandatory for website, browser workflow, HTTP API, or application-feature testing:
|
|
57
|
-
|
|
58
|
-
1. Load \`.claude/skills/playwright-test-agent/SKILL.md\`.
|
|
59
|
-
2. Before starting Planner, the main agent resolves Playwright's configured \`testDir\` and inspects tests there. Compare existing scenarios and assertions with the objective. If existing tests fully cover it, report their paths and run them directly with \`npx playwright test <paths>\`; skip Planner and Generator. Only start Planner when coverage is partial or absent. The main agent may also read relevant local project information and ask for information that materially affects the test, such as the deployed URL, account or role, expected behavior, environment, and authorization boundary.
|
|
60
|
-
3. When preflight finds missing or insufficient coverage and the required context is available, directly start \`playwright-test-planner\` with the objective, deployed URL, supplied access information, reusable test paths, coverage gaps, relevant project context, and constraints. The main agent must not open or inspect the website itself and must not call \`playwright-cli\`, Chrome DevTools, browser MCP tools, or another browser integration.
|
|
61
|
-
4. Planner opens and investigates the website with \`playwright-cli\`, converts its findings into a complete Markdown test plan under \`specs/\`, and returns the saved plan path. After Planner returns, show that plan to the user and wait for explicit confirmation; investigation alone is not completion.
|
|
62
|
-
5. Only after confirmation, directly start \`playwright-test-generator\` with the confirmed plan and resolved \`testDir\`. Generator must validate the confirmed scenarios, write every executable test inside that \`testDir\`, and return their paths.
|
|
63
|
-
6. After Generator returns, the main agent must execute the generated tests with \`npx playwright test\`. Generating test files alone never completes the workflow.
|
|
64
|
-
7. If every generated test passes, report the result. If any generated test fails, directly start \`playwright-test-healer\` with the failed test names, failure output, confirmed plan, and generated file paths. Healer diagnoses and patches justified test defects, reruns the affected tests, and includes \`Reason: <category> — <one-sentence cause>\` (including element location failures) before the defect classification. Never report generated-but-unexecuted tests as passing.
|
|
65
|
-
|
|
66
|
-
Start each role directly and never create a nested or same-role intermediary. Do not locate a seed file or make setup-file discovery a prerequisite to Planner. If exploration finds missing, incorrect, or contradictory required information, let Planner pause and return the precise question; after the user answers, start or resume Planner with that answer and the blocking observation. Do not guess.
|
|
76
|
+
Claude Code routing is mandatory for website, browser workflow, HTTP API, or application-feature testing:
|
|
77
|
+
|
|
78
|
+
1. Load \`.claude/skills/playwright-test-agent/SKILL.md\`.
|
|
79
|
+
2. Before starting Planner, the main agent resolves Playwright's configured \`testDir\` and inspects tests there. Compare existing scenarios and assertions with the objective. If existing tests fully cover it, report their paths and run them directly with \`npx playwright test <paths>\`; skip Planner and Generator. Only start Planner when coverage is partial or absent. The main agent may also read relevant local project information and ask for information that materially affects the test, such as the deployed URL, account or role, expected behavior, environment, and authorization boundary.
|
|
80
|
+
3. When preflight finds missing or insufficient coverage and the required context is available, directly start \`playwright-test-planner\` with the objective, deployed URL, supplied access information, reusable test paths, coverage gaps, relevant project context, and constraints. The main agent must not open or inspect the website itself and must not call \`playwright-cli\`, Chrome DevTools, browser MCP tools, or another browser integration.
|
|
81
|
+
4. Planner opens and investigates the website with \`playwright-cli\`, converts its findings into a complete Markdown test plan under \`specs/\`, and returns the saved plan path. After Planner returns, show that plan to the user and wait for explicit confirmation; investigation alone is not completion.
|
|
82
|
+
5. Only after confirmation, directly start \`playwright-test-generator\` with the confirmed plan and resolved \`testDir\`. Generator must validate the confirmed scenarios, write every executable test inside that \`testDir\`, and return their paths.
|
|
83
|
+
6. After Generator returns, the main agent must execute the generated tests with \`npx playwright test\`. Generating test files alone never completes the workflow.
|
|
84
|
+
7. If every generated test passes, report the result. If any generated test fails, directly start \`playwright-test-healer\` with the failed test names, failure output, confirmed plan, and generated file paths. Healer diagnoses and patches justified test defects, reruns the affected tests, and includes \`Reason: <category> — <one-sentence cause>\` (including element location failures) before the defect classification. Never report generated-but-unexecuted tests as passing.
|
|
85
|
+
|
|
86
|
+
Start each role directly and never create a nested or same-role intermediary. Do not locate a seed file or make setup-file discovery a prerequisite to Planner. If exploration finds missing, incorrect, or contradictory required information, let Planner pause and return the precise question; after the user answers, start or resume Planner with that answer and the blocking observation. Do not guess.
|
|
67
87
|
|
|
68
88
|
${BLOCK_END}`;
|
|
69
89
|
|
|
@@ -114,7 +134,7 @@ async function installSkill(projectDir, skillSourceDir) {
|
|
|
114
134
|
}
|
|
115
135
|
}
|
|
116
136
|
|
|
117
|
-
async function updateInstructionFiles(projectDir) {
|
|
137
|
+
async function updateInstructionFiles(projectDir) {
|
|
118
138
|
const files = [
|
|
119
139
|
['AGENTS.md', CODEX_INSTRUCTIONS],
|
|
120
140
|
['CLAUDE.md', CLAUDE_INSTRUCTIONS],
|
|
@@ -123,176 +143,176 @@ async function updateInstructionFiles(projectDir) {
|
|
|
123
143
|
const file = path.join(projectDir, name);
|
|
124
144
|
const source = await exists(file) ? await readFile(file, 'utf8') : '';
|
|
125
145
|
await writeFile(file, withManagedBlock(source, managedBlock), 'utf8');
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
async function ensureEnvFiles(projectDir) {
|
|
130
|
-
const examplePath = path.join(projectDir, '.env.example');
|
|
131
|
-
if (!await exists(examplePath)) {
|
|
132
|
-
await writeFile(examplePath,
|
|
133
|
-
'# Copy this file to .env and fill in project-specific runtime settings.\n' +
|
|
134
|
-
'# Keep .env local; never commit secrets.\n' +
|
|
135
|
-
'# BASE_URL=https://example.test\n' +
|
|
136
|
-
'# TEST_USERNAME=\n' +
|
|
137
|
-
'# TEST_PASSWORD=\n', 'utf8');
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const gitignorePath = path.join(projectDir, '.gitignore');
|
|
141
|
-
const source = await exists(gitignorePath) ? await readFile(gitignorePath, 'utf8') : '';
|
|
142
|
-
const entries = ['.env', '.env.*', '!.env.example', '.playwright-evidence/', '.playwright-cli/'];
|
|
143
|
-
const missing = entries.filter((entry) => !source.split(/\r?\n/).includes(entry));
|
|
144
|
-
if (missing.length > 0) {
|
|
145
|
-
const prefix = source.length === 0 || source.endsWith('\n') ? source : `${source}\n`;
|
|
146
|
-
await writeFile(gitignorePath, `${prefix}\n# Playwright Test Agent local configuration\n${missing.join('\n')}\n`, 'utf8');
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function withRoleBlock(source, role) {
|
|
151
|
-
const start = source.indexOf(ROLE_BLOCK_START);
|
|
152
|
-
const end = source.indexOf(ROLE_BLOCK_END);
|
|
153
|
-
if ((start === -1) !== (end === -1) || (start !== -1 && end < start)) {
|
|
154
|
-
throw new Error('found an incomplete playwright-test-agent CLI role block');
|
|
155
|
-
}
|
|
156
|
-
if (start !== -1) {
|
|
157
|
-
return source.slice(0, start) + cliFirstInstructions(role) +
|
|
158
|
-
source.slice(end + ROLE_BLOCK_END.length);
|
|
159
|
-
}
|
|
160
|
-
return `${source.trimEnd()}\n\n${cliFirstInstructions(role)}\n`;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function tomlMultilineBasicString(value) {
|
|
164
|
-
const escaped = value
|
|
165
|
-
.replace(/\\/g, '\\\\')
|
|
166
|
-
.replace(/"""/g, '\\"\\"\\"')
|
|
167
|
-
.replace(/\r\n?/g, '\n');
|
|
168
|
-
return `"""\n${escaped}\n"""`;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function patchCodexRole(source, role) {
|
|
172
|
-
const pattern = /(^|\n)developer_instructions\s*=\s*"""\r?\n([\s\S]*?)\r?\n"""/;
|
|
173
|
-
const match = source.match(pattern);
|
|
174
|
-
if (!match) throw new Error('developer_instructions was not found in a Codex role definition');
|
|
175
|
-
const updated = withRoleBlock(match[2], role);
|
|
176
|
-
return source
|
|
177
|
-
.replace(pattern, `${match[1]}developer_instructions = ${tomlMultilineBasicString(updated)}`)
|
|
178
|
-
.replace(/^sandbox_mode\s*=\s*"[^"]+"/m, 'sandbox_mode = "workspace-write"');
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function patchClaudeRole(source, role) {
|
|
182
|
-
let updated = source;
|
|
183
|
-
const frontmatter = updated.match(/^---\s*\r?\n([\s\S]*?)\r?\n---/);
|
|
184
|
-
if (!frontmatter) throw new Error('YAML frontmatter was not found in a Claude role definition');
|
|
185
|
-
|
|
186
|
-
let header = frontmatter[1];
|
|
187
|
-
const inlineTools = header.match(/^tools:[ \t]*(\S.*?)[ \t]*$/m);
|
|
188
|
-
const blockTools = header.match(/^tools:[ \t]*$/m);
|
|
189
|
-
if (inlineTools) {
|
|
190
|
-
const tools = inlineTools[1].split(',').map((tool) => tool.trim());
|
|
191
|
-
if (!tools.includes('Bash')) tools.push('Bash');
|
|
192
|
-
header = header.replace(inlineTools[0], `tools: ${tools.join(', ')}`);
|
|
193
|
-
} else if (blockTools) {
|
|
194
|
-
const blockStart = blockTools.index + blockTools[0].length;
|
|
195
|
-
const remainder = header.slice(blockStart);
|
|
196
|
-
const nextKey = remainder.search(/\r?\n(?=[A-Za-z0-9_-]+:\s*)/);
|
|
197
|
-
const blockEnd = nextKey === -1 ? header.length : blockStart + nextKey;
|
|
198
|
-
let toolsBlock = header.slice(blockStart, blockEnd);
|
|
199
|
-
if (!/^\s*-\s+Bash\s*$/m.test(toolsBlock)) {
|
|
200
|
-
toolsBlock = `\n - Bash${toolsBlock}`;
|
|
201
|
-
}
|
|
202
|
-
header = header.slice(0, blockStart) + toolsBlock + header.slice(blockEnd);
|
|
203
|
-
} else {
|
|
204
|
-
header = `${header.trimEnd()}\ntools: Bash`;
|
|
205
|
-
}
|
|
206
|
-
updated = updated.slice(0, frontmatter.index) + `---\n${header}\n---` +
|
|
207
|
-
updated.slice(frontmatter.index + frontmatter[0].length);
|
|
208
|
-
return withRoleBlock(updated, role);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
async function configureCliFirstRoles(projectDir) {
|
|
212
|
-
const roles = ['planner', 'generator', 'healer'];
|
|
213
|
-
for (const role of roles) {
|
|
214
|
-
const claudeFile = path.join(projectDir, '.claude', 'agents', `playwright-test-${role}.md`);
|
|
215
|
-
if (await exists(claudeFile)) {
|
|
216
|
-
const source = await readFile(claudeFile, 'utf8');
|
|
217
|
-
await writeFile(claudeFile, patchClaudeRole(source, role), 'utf8');
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const codexFile = path.join(projectDir, '.codex', 'agents', `playwright_test_${role}.toml`);
|
|
221
|
-
if (await exists(codexFile)) {
|
|
222
|
-
const source = await readFile(codexFile, 'utf8');
|
|
223
|
-
await writeFile(codexFile, patchCodexRole(source, role), 'utf8');
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function escapeRegExp(value) {
|
|
229
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function upsertTomlSection(source, section, settings) {
|
|
233
|
-
const headerPattern = new RegExp(`^\\[${escapeRegExp(section)}\\][ \\t]*$`, 'm');
|
|
234
|
-
const header = source.match(headerPattern);
|
|
235
|
-
|
|
236
|
-
if (!header) {
|
|
237
|
-
const body = Object.entries(settings).map(([key, value]) => `${key} = ${value}`).join('\n');
|
|
238
|
-
const separator = source.length === 0 ? '' : source.endsWith('\n') ? '\n' : '\n\n';
|
|
239
|
-
return `${source}${separator}[${section}]\n${body}\n`;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const bodyStart = header.index + header[0].length;
|
|
243
|
-
const remainder = source.slice(bodyStart);
|
|
244
|
-
const nextHeader = remainder.search(/\r?\n(?=\[[^\]]+\][ \\t]*(?:\r?\n|$))/);
|
|
245
|
-
const bodyEnd = nextHeader === -1 ? source.length : bodyStart + nextHeader;
|
|
246
|
-
let body = source.slice(bodyStart, bodyEnd);
|
|
247
|
-
|
|
248
|
-
for (const [key, value] of Object.entries(settings)) {
|
|
249
|
-
const settingPattern = new RegExp(`(^|\\n)${escapeRegExp(key)}[ \\t]*=[^\\r\\n]*`);
|
|
250
|
-
if (settingPattern.test(body)) {
|
|
251
|
-
body = body.replace(settingPattern, `$1${key} = ${value}`);
|
|
252
|
-
} else {
|
|
253
|
-
body = `${body.trimEnd()}\n${key} = ${value}\n`;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
return source.slice(0, bodyStart) + body + source.slice(bodyEnd);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
async function configureCodexMcp(projectDir) {
|
|
261
|
-
const codexDir = path.join(projectDir, '.codex');
|
|
262
|
-
const file = path.join(codexDir, 'config.toml');
|
|
263
|
-
await mkdir(codexDir, { recursive: true });
|
|
264
|
-
let source = await exists(file) ? await readFile(file, 'utf8') : '';
|
|
265
|
-
const command = process.platform === 'win32' ? '"cmd"' : '"npx"';
|
|
266
|
-
const args = process.platform === 'win32'
|
|
267
|
-
? '["/d", "/s", "/c", "npx", "--no-install", "playwright", "run-test-mcp-server"]'
|
|
268
|
-
: '["--no-install", "playwright", "run-test-mcp-server"]';
|
|
269
|
-
|
|
270
|
-
source = upsertTomlSection(source, 'mcp_servers.playwright-test', {
|
|
271
|
-
command,
|
|
272
|
-
args,
|
|
273
|
-
cwd: '"."',
|
|
274
|
-
enabled: 'true',
|
|
275
|
-
default_tools_approval_mode: '"approve"',
|
|
276
|
-
});
|
|
277
|
-
source = upsertTomlSection(source, 'mcp_servers.playwright-test.env', {
|
|
278
|
-
PLAYWRIGHT_MCP_OUTPUT_DIR: '".playwright-evidence/mcp"',
|
|
279
|
-
});
|
|
280
|
-
await writeFile(file, source, 'utf8');
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
async function installPlaywrightCli(projectDir, run) {
|
|
284
|
-
await run(projectDir, 'npm', ['install', '-g', '@playwright/cli@latest']);
|
|
285
|
-
await run(projectDir, 'playwright-cli', ['install', '--skills=agents', '--global']);
|
|
286
|
-
await run(projectDir, 'playwright-cli', ['install', '--skills', '--global']);
|
|
287
|
-
await run(projectDir, 'playwright-cli', ['install-browser']);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
async function patchPlaywrightConfig(projectDir) {
|
|
291
|
-
const configPath = path.join(projectDir, 'playwright.config.ts');
|
|
292
|
-
let source = await readFile(configPath, 'utf8');
|
|
293
|
-
if (!/^import\s+['"]dotenv\/config['"];?/m.test(source)) {
|
|
294
|
-
source = `import 'dotenv/config';\n${source}`;
|
|
295
|
-
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function ensureEnvFiles(projectDir) {
|
|
150
|
+
const examplePath = path.join(projectDir, '.env.example');
|
|
151
|
+
if (!await exists(examplePath)) {
|
|
152
|
+
await writeFile(examplePath,
|
|
153
|
+
'# Copy this file to .env and fill in project-specific runtime settings.\n' +
|
|
154
|
+
'# Keep .env local; never commit secrets.\n' +
|
|
155
|
+
'# BASE_URL=https://example.test\n' +
|
|
156
|
+
'# TEST_USERNAME=\n' +
|
|
157
|
+
'# TEST_PASSWORD=\n', 'utf8');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const gitignorePath = path.join(projectDir, '.gitignore');
|
|
161
|
+
const source = await exists(gitignorePath) ? await readFile(gitignorePath, 'utf8') : '';
|
|
162
|
+
const entries = ['.env', '.env.*', '!.env.example', '.playwright-evidence/', '.playwright-cli/'];
|
|
163
|
+
const missing = entries.filter((entry) => !source.split(/\r?\n/).includes(entry));
|
|
164
|
+
if (missing.length > 0) {
|
|
165
|
+
const prefix = source.length === 0 || source.endsWith('\n') ? source : `${source}\n`;
|
|
166
|
+
await writeFile(gitignorePath, `${prefix}\n# Playwright Test Agent local configuration\n${missing.join('\n')}\n`, 'utf8');
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function withRoleBlock(source, role) {
|
|
171
|
+
const start = source.indexOf(ROLE_BLOCK_START);
|
|
172
|
+
const end = source.indexOf(ROLE_BLOCK_END);
|
|
173
|
+
if ((start === -1) !== (end === -1) || (start !== -1 && end < start)) {
|
|
174
|
+
throw new Error('found an incomplete playwright-test-agent CLI role block');
|
|
175
|
+
}
|
|
176
|
+
if (start !== -1) {
|
|
177
|
+
return source.slice(0, start) + cliFirstInstructions(role) +
|
|
178
|
+
source.slice(end + ROLE_BLOCK_END.length);
|
|
179
|
+
}
|
|
180
|
+
return `${source.trimEnd()}\n\n${cliFirstInstructions(role)}\n`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function tomlMultilineBasicString(value) {
|
|
184
|
+
const escaped = value
|
|
185
|
+
.replace(/\\/g, '\\\\')
|
|
186
|
+
.replace(/"""/g, '\\"\\"\\"')
|
|
187
|
+
.replace(/\r\n?/g, '\n');
|
|
188
|
+
return `"""\n${escaped}\n"""`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function patchCodexRole(source, role) {
|
|
192
|
+
const pattern = /(^|\n)developer_instructions\s*=\s*"""\r?\n([\s\S]*?)\r?\n"""/;
|
|
193
|
+
const match = source.match(pattern);
|
|
194
|
+
if (!match) throw new Error('developer_instructions was not found in a Codex role definition');
|
|
195
|
+
const updated = withRoleBlock(match[2], role);
|
|
196
|
+
return source
|
|
197
|
+
.replace(pattern, `${match[1]}developer_instructions = ${tomlMultilineBasicString(updated)}`)
|
|
198
|
+
.replace(/^sandbox_mode\s*=\s*"[^"]+"/m, 'sandbox_mode = "workspace-write"');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function patchClaudeRole(source, role) {
|
|
202
|
+
let updated = source;
|
|
203
|
+
const frontmatter = updated.match(/^---\s*\r?\n([\s\S]*?)\r?\n---/);
|
|
204
|
+
if (!frontmatter) throw new Error('YAML frontmatter was not found in a Claude role definition');
|
|
205
|
+
|
|
206
|
+
let header = frontmatter[1];
|
|
207
|
+
const inlineTools = header.match(/^tools:[ \t]*(\S.*?)[ \t]*$/m);
|
|
208
|
+
const blockTools = header.match(/^tools:[ \t]*$/m);
|
|
209
|
+
if (inlineTools) {
|
|
210
|
+
const tools = inlineTools[1].split(',').map((tool) => tool.trim());
|
|
211
|
+
if (!tools.includes('Bash')) tools.push('Bash');
|
|
212
|
+
header = header.replace(inlineTools[0], `tools: ${tools.join(', ')}`);
|
|
213
|
+
} else if (blockTools) {
|
|
214
|
+
const blockStart = blockTools.index + blockTools[0].length;
|
|
215
|
+
const remainder = header.slice(blockStart);
|
|
216
|
+
const nextKey = remainder.search(/\r?\n(?=[A-Za-z0-9_-]+:\s*)/);
|
|
217
|
+
const blockEnd = nextKey === -1 ? header.length : blockStart + nextKey;
|
|
218
|
+
let toolsBlock = header.slice(blockStart, blockEnd);
|
|
219
|
+
if (!/^\s*-\s+Bash\s*$/m.test(toolsBlock)) {
|
|
220
|
+
toolsBlock = `\n - Bash${toolsBlock}`;
|
|
221
|
+
}
|
|
222
|
+
header = header.slice(0, blockStart) + toolsBlock + header.slice(blockEnd);
|
|
223
|
+
} else {
|
|
224
|
+
header = `${header.trimEnd()}\ntools: Bash`;
|
|
225
|
+
}
|
|
226
|
+
updated = updated.slice(0, frontmatter.index) + `---\n${header}\n---` +
|
|
227
|
+
updated.slice(frontmatter.index + frontmatter[0].length);
|
|
228
|
+
return withRoleBlock(updated, role);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async function configureCliFirstRoles(projectDir) {
|
|
232
|
+
const roles = ['planner', 'generator', 'healer'];
|
|
233
|
+
for (const role of roles) {
|
|
234
|
+
const claudeFile = path.join(projectDir, '.claude', 'agents', `playwright-test-${role}.md`);
|
|
235
|
+
if (await exists(claudeFile)) {
|
|
236
|
+
const source = await readFile(claudeFile, 'utf8');
|
|
237
|
+
await writeFile(claudeFile, patchClaudeRole(source, role), 'utf8');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const codexFile = path.join(projectDir, '.codex', 'agents', `playwright_test_${role}.toml`);
|
|
241
|
+
if (await exists(codexFile)) {
|
|
242
|
+
const source = await readFile(codexFile, 'utf8');
|
|
243
|
+
await writeFile(codexFile, patchCodexRole(source, role), 'utf8');
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function escapeRegExp(value) {
|
|
249
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function upsertTomlSection(source, section, settings) {
|
|
253
|
+
const headerPattern = new RegExp(`^\\[${escapeRegExp(section)}\\][ \\t]*$`, 'm');
|
|
254
|
+
const header = source.match(headerPattern);
|
|
255
|
+
|
|
256
|
+
if (!header) {
|
|
257
|
+
const body = Object.entries(settings).map(([key, value]) => `${key} = ${value}`).join('\n');
|
|
258
|
+
const separator = source.length === 0 ? '' : source.endsWith('\n') ? '\n' : '\n\n';
|
|
259
|
+
return `${source}${separator}[${section}]\n${body}\n`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const bodyStart = header.index + header[0].length;
|
|
263
|
+
const remainder = source.slice(bodyStart);
|
|
264
|
+
const nextHeader = remainder.search(/\r?\n(?=\[[^\]]+\][ \\t]*(?:\r?\n|$))/);
|
|
265
|
+
const bodyEnd = nextHeader === -1 ? source.length : bodyStart + nextHeader;
|
|
266
|
+
let body = source.slice(bodyStart, bodyEnd);
|
|
267
|
+
|
|
268
|
+
for (const [key, value] of Object.entries(settings)) {
|
|
269
|
+
const settingPattern = new RegExp(`(^|\\n)${escapeRegExp(key)}[ \\t]*=[^\\r\\n]*`);
|
|
270
|
+
if (settingPattern.test(body)) {
|
|
271
|
+
body = body.replace(settingPattern, `$1${key} = ${value}`);
|
|
272
|
+
} else {
|
|
273
|
+
body = `${body.trimEnd()}\n${key} = ${value}\n`;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return source.slice(0, bodyStart) + body + source.slice(bodyEnd);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async function configureCodexMcp(projectDir) {
|
|
281
|
+
const codexDir = path.join(projectDir, '.codex');
|
|
282
|
+
const file = path.join(codexDir, 'config.toml');
|
|
283
|
+
await mkdir(codexDir, { recursive: true });
|
|
284
|
+
let source = await exists(file) ? await readFile(file, 'utf8') : '';
|
|
285
|
+
const command = process.platform === 'win32' ? '"cmd"' : '"npx"';
|
|
286
|
+
const args = process.platform === 'win32'
|
|
287
|
+
? '["/d", "/s", "/c", "npx", "--no-install", "playwright", "run-test-mcp-server"]'
|
|
288
|
+
: '["--no-install", "playwright", "run-test-mcp-server"]';
|
|
289
|
+
|
|
290
|
+
source = upsertTomlSection(source, 'mcp_servers.playwright-test', {
|
|
291
|
+
command,
|
|
292
|
+
args,
|
|
293
|
+
cwd: '"."',
|
|
294
|
+
enabled: 'true',
|
|
295
|
+
default_tools_approval_mode: '"approve"',
|
|
296
|
+
});
|
|
297
|
+
source = upsertTomlSection(source, 'mcp_servers.playwright-test.env', {
|
|
298
|
+
PLAYWRIGHT_MCP_OUTPUT_DIR: '".playwright-evidence/mcp"',
|
|
299
|
+
});
|
|
300
|
+
await writeFile(file, source, 'utf8');
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function installPlaywrightCli(projectDir, run) {
|
|
304
|
+
await run(projectDir, 'npm', ['install', '-g', '@playwright/cli@latest']);
|
|
305
|
+
await run(projectDir, 'playwright-cli', ['install', '--skills=agents', '--global']);
|
|
306
|
+
await run(projectDir, 'playwright-cli', ['install', '--skills', '--global']);
|
|
307
|
+
await run(projectDir, 'playwright-cli', ['install-browser']);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async function patchPlaywrightConfig(projectDir) {
|
|
311
|
+
const configPath = path.join(projectDir, 'playwright.config.ts');
|
|
312
|
+
let source = await readFile(configPath, 'utf8');
|
|
313
|
+
if (!/^import\s+['"]dotenv\/config['"];?/m.test(source)) {
|
|
314
|
+
source = `import 'dotenv/config';\n${source}`;
|
|
315
|
+
}
|
|
296
316
|
source = source.replace(/testDir:\s*['"]\.\/tests['"]/, "testDir: './playwright-tests'");
|
|
297
317
|
source = source.replace(
|
|
298
318
|
/reporter:\s*['"]html['"],?/,
|
|
@@ -334,7 +354,7 @@ async function disableTraceScreenshots(projectDir) {
|
|
|
334
354
|
}
|
|
335
355
|
}
|
|
336
356
|
|
|
337
|
-
async function initializePlaywright(projectDir, run) {
|
|
357
|
+
async function initializePlaywright(projectDir, run) {
|
|
338
358
|
const configPath = path.join(projectDir, 'playwright.config.ts');
|
|
339
359
|
if (!await exists(configPath)) {
|
|
340
360
|
await run(projectDir, 'npm', [
|
|
@@ -352,22 +372,22 @@ async function initializePlaywright(projectDir, run) {
|
|
|
352
372
|
await mkdir(targetDir, { recursive: true });
|
|
353
373
|
await rename(generatedExample, path.join(targetDir, 'example.spec.ts'));
|
|
354
374
|
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
await patchPlaywrightConfig(projectDir);
|
|
375
|
+
}
|
|
358
376
|
|
|
359
|
-
await
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
377
|
+
await patchPlaywrightConfig(projectDir);
|
|
378
|
+
|
|
379
|
+
await disableTraceScreenshots(projectDir);
|
|
380
|
+
|
|
381
|
+
if (await exists(path.join(projectDir, 'package.json'))) {
|
|
382
|
+
await run(projectDir, 'npm', ['install', '--save-dev', 'dotenv']);
|
|
383
|
+
}
|
|
364
384
|
|
|
365
385
|
await run(projectDir, 'npx', ['--no-install', 'playwright', 'init-agents', '--loop=codex']);
|
|
366
|
-
await run(projectDir, 'npx', ['--no-install', 'playwright', 'init-agents', '--loop=claude']);
|
|
367
|
-
await mkdir(path.join(projectDir, '.playwright-evidence', 'mcp'), { recursive: true });
|
|
368
|
-
await configureCodexMcp(projectDir);
|
|
369
|
-
await configureCliFirstRoles(projectDir);
|
|
370
|
-
}
|
|
386
|
+
await run(projectDir, 'npx', ['--no-install', 'playwright', 'init-agents', '--loop=claude']);
|
|
387
|
+
await mkdir(path.join(projectDir, '.playwright-evidence', 'mcp'), { recursive: true });
|
|
388
|
+
await configureCodexMcp(projectDir);
|
|
389
|
+
await configureCliFirstRoles(projectDir);
|
|
390
|
+
}
|
|
371
391
|
|
|
372
392
|
async function runStage(name, action) {
|
|
373
393
|
process.stdout.write(`[playwright-test-agent] ${name}...\n`);
|
|
@@ -386,15 +406,15 @@ export async function initializeProject({
|
|
|
386
406
|
const target = path.resolve(projectDir);
|
|
387
407
|
await mkdir(target, { recursive: true });
|
|
388
408
|
|
|
389
|
-
await runStage('installing skill', () => installSkill(target, skillSourceDir));
|
|
390
|
-
await runStage('installing Playwright CLI and browser', () => installPlaywrightCli(target, run));
|
|
391
|
-
await runStage('initializing Playwright Test agents', () => initializePlaywright(target, run));
|
|
392
|
-
await runStage('configuring project .env files', () => ensureEnvFiles(target));
|
|
393
|
-
await runStage('updating project instructions', () => updateInstructionFiles(target));
|
|
409
|
+
await runStage('installing skill', () => installSkill(target, skillSourceDir));
|
|
410
|
+
await runStage('installing Playwright CLI and browser', () => installPlaywrightCli(target, run));
|
|
411
|
+
await runStage('initializing Playwright Test agents', () => initializePlaywright(target, run));
|
|
412
|
+
await runStage('configuring project .env files', () => ensureEnvFiles(target));
|
|
413
|
+
await runStage('updating project instructions', () => updateInstructionFiles(target));
|
|
394
414
|
|
|
395
415
|
process.stdout.write(
|
|
396
|
-
'Playwright Test Agent ready: Playwright CLI and skills installed globally, ' +
|
|
397
|
-
'agent definitions configured CLI-first for Codex and Claude, ' +
|
|
416
|
+
'Playwright Test Agent ready: Playwright CLI and skills installed globally, ' +
|
|
417
|
+
'agent definitions configured CLI-first for Codex and Claude, ' +
|
|
398
418
|
'tests in playwright-tests/, evidence in .playwright-evidence/.\n',
|
|
399
419
|
);
|
|
400
420
|
}
|