playwright-test-agent 0.1.4 → 0.1.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.
@@ -1,29 +1,29 @@
1
- #!/usr/bin/env node
2
-
3
- import path from 'node:path';
4
- import { fileURLToPath } from 'node:url';
5
- import { initializeProject } from '../playwright-test-agent/scripts/init-playwright.mjs';
6
-
7
- const usage = `Usage: playwright-test-agent init [target-directory]
8
-
1
+ #!/usr/bin/env node
2
+
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { initializeProject } from '../playwright-test-agent/scripts/init-playwright.mjs';
6
+
7
+ const usage = `Usage: playwright-test-agent init [target-directory]
8
+
9
9
  Initialize Playwright Test agents, install the playwright-test-agent skill,
10
- and update AGENTS.md and CLAUDE.md in the target project.`;
11
-
12
- const [command, targetDirectory, ...extraArguments] = process.argv.slice(2);
13
-
14
- if (command !== 'init' || extraArguments.length > 0) {
15
- console.error(usage);
16
- process.exitCode = 1;
17
- } else {
18
- const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
19
-
20
- try {
21
- await initializeProject({
22
- projectDir: path.resolve(targetDirectory ?? process.cwd()),
23
- skillSourceDir: path.join(packageRoot, 'playwright-test-agent'),
24
- });
25
- } catch (error) {
26
- console.error(`playwright-test-agent init failed: ${error.message}`);
27
- process.exitCode = 1;
28
- }
29
- }
10
+ install Playwright CLI globally, and update AGENTS.md and CLAUDE.md.`;
11
+
12
+ const [command, targetDirectory, ...extraArguments] = process.argv.slice(2);
13
+
14
+ if (command !== 'init' || extraArguments.length > 0) {
15
+ console.error(usage);
16
+ process.exitCode = 1;
17
+ } else {
18
+ const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
19
+
20
+ try {
21
+ await initializeProject({
22
+ projectDir: path.resolve(targetDirectory ?? process.cwd()),
23
+ skillSourceDir: path.join(packageRoot, 'playwright-test-agent'),
24
+ });
25
+ } catch (error) {
26
+ console.error(`playwright-test-agent init failed: ${error.message}`);
27
+ process.exitCode = 1;
28
+ }
29
+ }
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
- {
2
- "name": "playwright-test-agent",
3
- "version": "0.1.4",
4
- "description": "Initialize Playwright Test agents and install the playwright-test-agent skill.",
5
- "type": "module",
6
- "bin": {
7
- "playwright-test-agent": "bin/playwright-test-agent.mjs"
8
- },
9
- "files": [
10
- "bin/",
11
- "playwright-test-agent/"
12
- ],
13
- "engines": {
14
- "node": ">=18"
15
- },
16
- "license": "UNLICENSED"
17
- }
1
+ {
2
+ "name": "playwright-test-agent",
3
+ "version": "0.1.7",
4
+ "description": "Initialize Playwright Test agents with a Playwright CLI-first browser workflow.",
5
+ "type": "module",
6
+ "bin": {
7
+ "playwright-test-agent": "bin/playwright-test-agent.mjs"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "playwright-test-agent/"
12
+ ],
13
+ "engines": {
14
+ "node": ">=18"
15
+ },
16
+ "license": "UNLICENSED"
17
+ }
@@ -1,113 +1,54 @@
1
1
  ---
2
2
  name: playwright-test-agent
3
- description: Use first when a user asks to test a website, browser workflow, HTTP API, or application feature, including planning, generating, running, debugging, healing, screenshots, and reusable test evidence.
3
+ description: Use first when a user asks to investigate or test a website, browser workflow, HTTP API, or application feature with Playwright, including planning, generation, execution, debugging, healing, and evidence collection.
4
4
  ---
5
5
 
6
6
  # Playwright Test Agent
7
7
 
8
- Use Playwright Test for durable automation. The main agent orchestrates Playwright's official planner, generator, and healer subagents instead of replacing their work with ad hoc browser exploration. Pure HTTP API tests may be written directly with Playwright `APIRequestContext` without those subagents.
8
+ Use Planner -> Generator -> Healer for durable tests. `AGENTS.md` or `CLAUDE.md` defines the platform-specific role routing. Generated role definitions use `playwright-cli` as their primary browser interface, so MCP tool injection is not required.
9
9
 
10
- This file is the single source of truth for the testing workflow. Project-level `AGENTS.md` and `CLAUDE.md` only force this skill to be loaded and name the platform-specific subagents; do not duplicate or infer the detailed workflow from those routing files.
10
+ ## Prepare the objective
11
11
 
12
- ## Platform agent names
12
+ The main agent may inspect the local project for relevant requirements, routes, fixtures, and constraints. Do not require a seed/config audit before planning.
13
13
 
14
- | Role | Codex | Claude Code |
15
- |---|---|---|
16
- | Planner | `playwright_test_planner` | `playwright-test-planner` |
17
- | Generator | `playwright_test_generator` | `playwright-test-generator` |
18
- | Healer | `playwright_test_healer` | `playwright-test-healer` |
14
+ Before Planner, ask for information that could materially change purpose, scope, safety, or expected behavior: deployed URL, intended role, success criteria, allowed data mutations, and environment constraints. Do not ask for UI details Planner can safely discover.
19
15
 
20
- Use the names for the active platform. The role names below refer to these mapped agents.
16
+ If the request is sufficient, begin Planner immediately. If investigation finds a missing, incorrect, or contradictory URL, credential, role, prerequisite, expected result, or authorization boundary, pause and ask for the specific correction. Resume Planner with the correction and blocking observation. Do not guess credentials, probe nearby environments, bypass access controls, or wander through unrelated pages.
21
17
 
22
- ## Agent responsibilities
18
+ Keep credentials and tokens in environment variables or ignored secret files. Never copy them into plans, tests, screenshots, evidence, or chat.
23
19
 
24
- - **Main agent:** Own the user conversation, inspect only the local project for context, gather the minimum non-discoverable information from the user, locate the default seed generated by `playwright init-agents`, start Planner, Generator, and Healer directly as leaf subagents, present the plan for confirmation, and report final results. It must not open, navigate, inspect, screenshot, or otherwise investigate the deployed website itself. It must not insert an intermediary or nested agent between itself and any Playwright Test agent.
25
- - **Planner:** Run the generated seed to establish page context, global setup, dependencies, fixtures, and hooks. Explore the deployed application for the requested scenarios and user flows, compare observations with the request and optional PRD, and save a precise human-readable Markdown plan under `specs/`. It does not generate the formal test files and must not spawn another agent. It must call its own injected `planner_setup_page` and `planner_save_plan` tools directly.
26
- - **Generator:** Read the complete user-confirmed Markdown plan and the same generated seed, execute each scenario to validate locators and assertions against the live application, and generate executable Playwright tests under the configured test directory. It must not invent unconfirmed scenarios or expected behavior, must not spawn another agent, and must call its own injected `generator_setup_page`, `generator_read_log`, and `generator_write_test` tools directly.
27
- - **Healer:** Run the failing test, replay its steps, inspect the current UI, propose and apply test-side patches, and rerun until the test passes or a guardrail stops the loop. It must not spawn another agent and must call its own injected `test_list`, `test_run`, `test_debug`, and relevant `browser_*` tools directly. It must report a likely broken product, environment/data problem, or plan conflict instead of weakening assertions merely to obtain a pass.
20
+ ## Planner
28
21
 
29
- For UI testing, keep these responsibilities and sequence:
22
+ Planner investigates the live application with `playwright-cli` and saves a human-readable Markdown plan under `specs/`. Use `open`/`goto`, compact `snapshot` or `find`, and refs for interaction. Supply a seed only when explicitly identified; do not perform a separate seed discovery pass.
30
23
 
31
- ```text
32
- request + deployed URL -> locate generated seed -> planner -> user confirms plan
33
- -> generator -> healer for failures -> final report
34
- ```
35
-
36
- Planner, Generator, and Healer are terminal/leaf agents in this flow. The main agent starts the platform-specific agent for each role directly and passes all required inputs in that invocation. Never delegate their work to `live_planner`, `live_generator`, `live_healer`, a same-role nested agent, or another intermediary. If a directly started agent lacks any required MCP tool, stop with an MCP-injection diagnosis; do not create another subagent level and do not replace that agent's work in the main agent.
37
-
38
- ## Initialize once per project
39
-
40
- Inspect the target project first. If Playwright Test, its config, or either Codex/Claude agent set is missing, tell the user initialization will modify the project, then run from the target project:
41
-
42
- ```bash
43
- npx playwright-test-agent init
44
- ```
45
-
46
- The command installs this skill for Codex and Claude, selects the ready-to-use Playwright defaults (TypeScript, `playwright-tests/`, no GitHub Actions, Chromium with browser installation), and runs both:
47
-
48
- ```text
49
- npx playwright init-agents --loop=codex
50
- npx playwright init-agents --loop=claude
51
- ```
52
-
53
- It uses the project-local Playwright command. Never install a global/additional Playwright CLI or run `playwright init-skills`.
54
-
55
- `playwright init-agents` also creates the project's default `seed.spec.ts`. Its directory follows the selected Playwright project's `testDir`, so it may be under `playwright-tests/`, `e2e/`, `tests/`, or another configured directory. Do not hardcode the path. Locate it from `playwright.config.*`, the generated agent definitions, or the filesystem before invoking planner or generator.
56
-
57
- ## Understand the request
24
+ 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.
58
25
 
59
- Before testing, the main agent may briefly inspect the local target project for relevant source, routes/API clients, environment examples, existing tests, fixtures, the generated seed, and run instructions. The project may otherwise be empty or contain no useful application context; in that case, do not keep searching the filesystem or invent missing details.
26
+ Show the scenario list and exclusions to the user. Generator starts only after confirmation.
60
27
 
61
- This local project inspection is the maximum reconnaissance the main agent may perform. The main agent must not use browser tools, HTTP requests, screenshots, or other means to investigate the deployed application. All live website reconnaissance belongs exclusively to the Planner. The main agent may read and summarize Planner output, but it must not independently verify or extend that investigation.
28
+ ## Generator
62
29
 
63
- Before starting the Planner, the main agent may ask the user to clarify information that materially affects the purpose, scope, safety, or expected result of the test plan. Relevant questions may cover the business goal, in-scope and excluded flows, user roles or accounts, expected success and failure outcomes, allowed data mutations, test-data constraints, environment limitations, and required browsers or devices. Ask only questions whose answers could change the plan or authorization boundary; if a necessary answer is missing, wait for it before starting the Planner.
30
+ Generator converts the confirmed plan into executable tests. Use `playwright-cli` to validate flows, locators, and assertions against the live application, then write the tests directly. Generate only confirmed scenarios; prefer independent tests, semantic locators, explicit assertions, and environment-based secrets.
64
31
 
65
- Do not ask the user for UI facts the Planner can safely discover from the deployed application, such as field labels, button locations, page structure, or ordinary navigation. Once the main agent has the test objective, deployed base URL, and any necessary non-discoverable constraints, invoke the Planner and let it perform all live investigation. For example, "test this project's login feature" plus its deployment URL is enough to investigate a public login page, unless the intended account role, authenticated outcome, or permitted side effects must first be clarified.
32
+ For API-only scenarios, use Playwright `APIRequestContext` directly and assert status, headers, schema, stable business invariants, and safe mutation cleanup.
66
33
 
67
- Pass the planner the exact objective, deployed URL, generated seed path, known authentication/roles, allowed side effects, supplied test data, and relevant project or PRD context. The seed is produced by initialization, not supplied by the user. It may remain the default page-context seed or be customized for project fixtures, global setup, dependencies, or authenticated context. Credentials are required before attempting an authenticated-only flow, but not merely to investigate a publicly reachable login page.
34
+ ## Healer
68
35
 
69
- If no seed can be located after initialization, treat the Playwright agent setup as incomplete. Check the configured test directory and generated agent definitions, then rerun `npx playwright-test-agent init` when regeneration is needed. Do not ask the user to author a seed, invent an arbitrary path, or continue to planner without the initialized project context.
36
+ Use Healer only for failures. Run the failing test with `npx playwright test`, reproduce and inspect the current UI with `playwright-cli`, apply justified test-side changes, and rerun within guardrails.
70
37
 
71
- If planner reconnaissance shows that information is missing, invalid, stale, or contradictory—for example, the URL is unreachable, a page or control does not exist, credentials fail, the observed role differs, a required variable is absent, or application behavior conflicts with the request—stop that planning attempt. Report the exact mismatch without exposing secrets and ask the user to correct or complete it. Do not repeatedly try nearby URLs, guess credentials, wander through unrelated pages, or substitute the main agent's own exploration. Resume with the planner only after the user supplies enough corrected information.
38
+ Do not weaken assertions, add arbitrary sleeps, retry blindly, or skip merely to pass. If behavior conflicts with the confirmed plan, preserve evidence and ask whether it is a regression or intended change. Classify unresolved failures as application defect, test defect, environment/data problem, or product decision.
72
39
 
73
- Never put credentials or tokens in plans, source, screenshots, reports, or chat output. Use environment variables or an ignored secret file.
40
+ ## Evidence
74
41
 
75
- ## Plan and confirm
42
+ Keep evidence under `.playwright-evidence/`. Prefer DOM snapshots and targeted `find` output; take screenshots only when visual evidence is necessary. Keep trace DOM snapshots, sources, network data, and attachments, but set trace `screenshots: false` to avoid hundreds of screencast JPEGs.
76
43
 
77
- Always have the planner save its human-readable Markdown plan under `specs/` before invoking the generator. Include prerequisites, the generated seed path, test data, independent scenarios, steps or requests, expected observable results, and intended output test files.
44
+ Report the plan path, generated tests, counts when tests ran, evidence paths, and failure classification. Never report skipped or unresolved tests as success.
78
45
 
79
- For UI work, invoke the Planner directly (as the first and only Planner-level subagent) once the objective, deployed base URL, and generated seed path are available and no known mismatch already blocks access. Explicitly name the seed in its prompt and provide an optional PRD only when one is relevant. The Planner runs the seed to perform global setup, dependencies, fixtures, and hooks, then uses its ready page context to inspect the live application. Reconnaissance must not create, delete, submit, purchase, message, or otherwise mutate durable/shared data without authorization.
46
+ ## Initialization
80
47
 
81
- If the planner discovers a new missing or incorrect prerequisite, the main agent stops that planning attempt, summarizes what was observed, and asks the user for clarification. Continue or reinvoke the planner only after the prerequisite is resolved; do not let it keep exploring around missing information. If the Planner session itself lacks a required MCP tool, report that direct-session environment failure and wait for the environment to be repaired; do not create a nested fallback agent.
48
+ Run from the target project:
82
49
 
83
- Choose relevant positive, negative, empty, invalid, boundary, permission, persistence, and error scenarios. For login, normally consider valid credentials, empty username, empty password, both empty, wrong password, and unknown user; add MFA, lockout, recovery, or remember-me only when in scope.
84
-
85
- Show the complete scenario list and exclusions to the user. Ask whether it is complete. Revise until confirmed. Do not invoke the generator or formally execute tests before confirmation.
86
-
87
- ## Generate and run
88
-
89
- For confirmed UI scenarios, invoke the Generator directly once for the complete confirmed plan (or complete selected scenario set), explicitly naming the Markdown plan and the same generated seed; use the configured Playwright test directory for generated tests. The Generator executes scenarios against the live application to validate locators and assertions while generating. Its internal per-scenario setup does not mean starting a new Generator agent for every scenario, and it must not delegate execution to a nested Generator. If the direct Generator lacks `generator_setup_page`, `generator_read_log`, or `generator_write_test`, report an MCP-injection failure and stop. Require one independent test per file, semantic locators, an assertion for every expected result, and environment-based secrets.
90
-
91
- For confirmed API-only scenarios, write `APIRequestContext` tests directly under `playwright-tests/`. Assert status, headers, schema, and stable business invariants; define safe setup/cleanup for mutations.
92
-
93
- After generation, invoke the Healer directly with one failing UI test name at a time. The Healer runs the test, replays the failing steps, inspects the current UI for the equivalent element or flow, proposes a patch such as a locator, synchronization, or test-data repair, and reruns until it passes or a guardrail stops the loop. It must not delegate diagnosis or repair to a nested Healer. If the direct Healer lacks `test_list`, `test_run`, `test_debug`, or the browser tools needed for diagnosis, report an MCP-injection failure and stop. API failures may be diagnosed directly.
94
-
95
- If observed product behavior conflicts with the confirmed plan, report the conflict and ask whether it is a regression or intended change. Do not weaken assertions, add arbitrary sleeps/`networkidle`, retry blindly, or skip/fixme tests merely to get green. If the healer concludes the feature is broken, preserve the failure evidence and report it as an application defect rather than treating a skipped test as success.
96
-
97
- ## Preserve evidence
98
-
99
- All browser-agent and test artifacts must stay under:
100
-
101
- ```text
102
- .playwright-evidence/
103
- ├── mcp/ # page YAML snapshots, screenshots, and agent logs
104
- ├── snapshots/ # reusable visual/ARIA baselines
105
- ├── test-results/ # failure screenshots, traces, videos, attachments
106
- └── report/ # HTML report
50
+ ```bash
51
+ npx playwright-test-agent init
107
52
  ```
108
53
 
109
- Never save snapshots or screenshots in the project root. Automatic failure evidence is enabled. For key business states, save an explicit screenshot with `testInfo.outputPath('screenshots', '<meaningful-name>.png')` so it remains in `test-results/`. Avoid secrets and sensitive personal data.
110
-
111
- Keep trace DOM snapshots, sources, network data, and attachments, but disable trace screenshots (`use.trace.screenshots: false`). Trace screenshots are screencast frames and can create hundreds of JPEG files under trace `resources/`; they are separate from explicit test screenshots and are not needed for ordinary DOM-based diagnosis.
112
-
113
- Finish by reporting the plan path, generated test paths, environment without secrets, pass/fail/flaky/skipped counts, and evidence paths. Classify failures as application defect, test defect, environment/data problem, or unresolved product decision.
54
+ The command globally installs `@playwright/cli`, installs its Codex/Claude skills and browser, initializes Playwright Test agents, and reinjects CLI-first instructions into all six generated role definitions. Run it again after Playwright updates regenerate those definitions. Restart Codex or Claude Code after initialization so global skills and changed definitions are reloaded.
@@ -1,228 +1,301 @@
1
- #!/usr/bin/env node
2
-
3
- import { access, cp, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
4
- import path from 'node:path';
5
- import { spawn } from 'node:child_process';
6
- import { fileURLToPath } from 'node:url';
7
-
1
+ #!/usr/bin/env node
2
+
3
+ import { access, cp, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { spawn } from 'node:child_process';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
8
  const BLOCK_START = '<!-- playwright-test-agent:start -->';
9
9
  const BLOCK_END = '<!-- playwright-test-agent:end -->';
10
- const CODEX_INSTRUCTIONS = `${BLOCK_START}
11
- ## Playwright Test Agent
12
-
13
- For any website, browser workflow, HTTP API, or application-feature testing request, load and follow \`.agents/skills/playwright-test-agent/SKILL.md\` before planning or using browser tools. This skill has priority over ad hoc browser automation.
14
-
15
- For UI testing, the Codex subagents are \`playwright_test_planner\`, \`playwright_test_generator\`, and \`playwright_test_healer\`. The main agent may inspect only the local project; it must not open, navigate, inspect, screenshot, or otherwise investigate the deployed website itself. Before starting Planner, the main agent may ask for necessary information that materially affects test purpose, scope, expected outcomes, roles, allowed side effects, data, or environment constraints. It must not ask for UI details Planner can safely discover. Once the objective, deployed URL, and necessary non-discoverable constraints are available, start \`playwright_test_planner\` directly as the live-investigation leaf agent.
16
-
17
- The main agent must start each Playwright Test agent directly. Planner, Generator, and Healer are leaf agents: do not ask them to spawn \`live_planner\`, \`live_generator\`, \`live_healer\`, or any other nested subagent. Each must use its own injected Playwright Test MCP tools directly. If a directly started agent does not expose its required tools, stop and report the Codex MCP-injection problem; do not retry by creating a second- or third-level agent.
18
-
19
- If Planner reports that required test information is missing, invalid, or inconsistent, stop that planning attempt and ask the user for the specific missing information. After the user responds, start \`playwright_test_planner\` again with the corrected information and the previous blocking observation. Do not continue guessing or exploring around the blocker.
20
- ${BLOCK_END}`;
21
-
22
- const CLAUDE_INSTRUCTIONS = `${BLOCK_START}
23
- ## Playwright Test Agent
24
-
25
- For any website, browser workflow, HTTP API, or application-feature testing request, load and follow \`.claude/skills/playwright-test-agent/SKILL.md\` before planning or using browser tools. This skill has priority over ad hoc browser automation.
26
-
27
- For UI testing, the Claude Code subagents are \`playwright-test-planner\`, \`playwright-test-generator\`, and \`playwright-test-healer\`. The main agent may inspect only the local project; it must not open, navigate, inspect, screenshot, or otherwise investigate the deployed website itself. Before starting Planner, the main agent may ask for necessary information that materially affects test purpose, scope, expected outcomes, roles, allowed side effects, data, or environment constraints. It must not ask for UI details Planner can safely discover. Once the objective, deployed URL, and necessary non-discoverable constraints are available, start \`playwright-test-planner\` directly as the live-investigation leaf agent.
28
-
29
- The main agent must start each Playwright Test agent directly. Planner, Generator, and Healer are leaf agents: do not ask them to spawn \`live_planner\`, \`live_generator\`, \`live_healer\`, or any other nested subagent. Each must use its own injected Playwright Test MCP tools directly. If a directly started agent does not expose its required tools, stop and report the MCP-injection problem; do not retry by creating a second- or third-level agent.
30
-
31
- If Planner reports that required test information is missing, invalid, or inconsistent, stop that planning attempt and ask the user for the specific missing information. After the user responds, start \`playwright-test-planner\` again with the corrected information and the previous blocking observation. Do not continue guessing or exploring around the blocker.
32
- ${BLOCK_END}`;
33
-
34
- const exists = async (file) => {
35
- try {
36
- await access(file);
37
- return true;
38
- } catch {
39
- return false;
10
+ const ROLE_BLOCK_START = '<!-- playwright-test-agent:cli-first:start -->';
11
+ const ROLE_BLOCK_END = '<!-- playwright-test-agent:cli-first:end -->';
12
+ const cliFirstInstructions = (role) => `${ROLE_BLOCK_START}
13
+
14
+ ## Browser tool priority
15
+
16
+ Use the installed \`playwright-cli\` command as the primary browser interface. 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 credentials in environment variables or ignored files and never print them.
17
+
18
+ CLI replaces any conflicting requirement above to call Playwright Test MCP setup, browser, save, generation, or debug tools. Do not stop merely because \`planner_*\`, \`generator_*\`, \`test_*\`, or \`browser_*\` MCP tools are absent, and do not spawn a nested or same-role agent.
19
+
20
+ ${role === 'planner' ? 'Explore with CLI and save the completed Markdown test plan directly under `specs/` using filesystem tools.' : ''}${role === 'generator' ? 'Use CLI to validate the confirmed plan against the live UI, then write executable Playwright test files directly with filesystem tools.' : ''}${role === 'healer' ? 'Run failures with `npx playwright test`, use CLI to inspect and reproduce the current UI, patch the tests directly, and rerun within the healer guardrails.' : ''}
21
+
22
+ ${ROLE_BLOCK_END}`;
23
+ const CODEX_INSTRUCTIONS = `${BLOCK_START}
24
+ ## Playwright Test Agent
25
+
26
+ For website, browser workflow, HTTP API, or application-feature testing, load \`.agents/skills/playwright-test-agent/SKILL.md\` first. Codex must not start Playwright subagents; the main agent performs Planner -> Generator -> Healer in this session. Use \`playwright-cli\` as the primary browser interface and read generated TOML role definitions only for responsibilities. Ask for missing or contradictory purpose, access, role, data, or safety information before or during planning; do not guess or create nested agents.
27
+
28
+ ${BLOCK_END}`;
29
+
30
+ const CLAUDE_INSTRUCTIONS = `${BLOCK_START}
31
+ ## Playwright Test Agent
32
+
33
+ For website, browser workflow, HTTP API, or application-feature testing, load \`.claude/skills/playwright-test-agent/SKILL.md\` first. Claude Code starts the official \`playwright-test-planner\`, \`playwright-test-generator\`, and \`playwright-test-healer\` directly. These roles use \`playwright-cli\` as their primary browser interface and must not create nested agents. Ask for missing or contradictory purpose, access, role, data, or safety information before or during planning; do not guess.
34
+
35
+ ${BLOCK_END}`;
36
+
37
+ const exists = async (file) => {
38
+ try {
39
+ await access(file);
40
+ return true;
41
+ } catch {
42
+ return false;
43
+ }
44
+ };
45
+
46
+ const defaultRun = (projectDir, command, args) => new Promise((resolve, reject) => {
47
+ const child = process.platform === 'win32'
48
+ ? spawn(process.env.ComSpec ?? 'cmd.exe', ['/d', '/s', '/c', command, ...args], {
49
+ cwd: projectDir,
50
+ stdio: 'inherit',
51
+ })
52
+ : spawn(command, args, { cwd: projectDir, stdio: 'inherit' });
53
+ child.on('error', reject);
54
+ child.on('exit', (code) => code === 0
55
+ ? resolve()
56
+ : reject(new Error(`${command} ${args.join(' ')} exited with ${code}`)));
57
+ });
58
+
59
+ function withManagedBlock(source, managedBlock) {
60
+ const start = source.indexOf(BLOCK_START);
61
+ const end = source.indexOf(BLOCK_END);
62
+
63
+ if ((start === -1) !== (end === -1) || (start !== -1 && end < start)) {
64
+ throw new Error('found an incomplete playwright-test-agent managed block');
65
+ }
66
+
67
+ if (start !== -1) {
68
+ return source.slice(0, start) + managedBlock + source.slice(end + BLOCK_END.length);
69
+ }
70
+
71
+ if (source.length === 0) return `${managedBlock}\n`;
72
+ const separator = source.endsWith('\n') ? '\n' : '\n\n';
73
+ return `${source}${separator}${managedBlock}\n`;
74
+ }
75
+
76
+ async function installSkill(projectDir, skillSourceDir) {
77
+ for (const root of ['.agents', '.claude']) {
78
+ const destination = path.join(projectDir, root, 'skills', 'playwright-test-agent');
79
+ await mkdir(destination, { recursive: true });
80
+ await cp(skillSourceDir, destination, { recursive: true, force: true });
81
+ }
82
+ }
83
+
84
+ async function updateInstructionFiles(projectDir) {
85
+ const files = [
86
+ ['AGENTS.md', CODEX_INSTRUCTIONS],
87
+ ['CLAUDE.md', CLAUDE_INSTRUCTIONS],
88
+ ];
89
+ for (const [name, managedBlock] of files) {
90
+ const file = path.join(projectDir, name);
91
+ const source = await exists(file) ? await readFile(file, 'utf8') : '';
92
+ await writeFile(file, withManagedBlock(source, managedBlock), 'utf8');
40
93
  }
41
- };
42
-
43
- const defaultRun = (projectDir, command, args) => new Promise((resolve, reject) => {
44
- const child = process.platform === 'win32'
45
- ? spawn(process.env.ComSpec ?? 'cmd.exe', ['/d', '/s', '/c', command, ...args], {
46
- cwd: projectDir,
47
- stdio: 'inherit',
48
- })
49
- : spawn(command, args, { cwd: projectDir, stdio: 'inherit' });
50
- child.on('error', reject);
51
- child.on('exit', (code) => code === 0
52
- ? resolve()
53
- : reject(new Error(`${command} ${args.join(' ')} exited with ${code}`)));
54
- });
55
-
56
- function withManagedBlock(source, managedBlock) {
57
- const start = source.indexOf(BLOCK_START);
58
- const end = source.indexOf(BLOCK_END);
94
+ }
59
95
 
96
+ function withRoleBlock(source, role) {
97
+ const start = source.indexOf(ROLE_BLOCK_START);
98
+ const end = source.indexOf(ROLE_BLOCK_END);
60
99
  if ((start === -1) !== (end === -1) || (start !== -1 && end < start)) {
61
- throw new Error('found an incomplete playwright-test-agent managed block');
100
+ throw new Error('found an incomplete playwright-test-agent CLI role block');
62
101
  }
63
-
64
102
  if (start !== -1) {
65
- return source.slice(0, start) + managedBlock + source.slice(end + BLOCK_END.length);
66
- }
67
-
68
- if (source.length === 0) return `${managedBlock}\n`;
69
- const separator = source.endsWith('\n') ? '\n' : '\n\n';
70
- return `${source}${separator}${managedBlock}\n`;
71
- }
72
-
73
- async function installSkill(projectDir, skillSourceDir) {
74
- for (const root of ['.agents', '.claude']) {
75
- const destination = path.join(projectDir, root, 'skills', 'playwright-test-agent');
76
- await mkdir(destination, { recursive: true });
77
- await cp(skillSourceDir, destination, { recursive: true, force: true });
78
- }
79
- }
80
-
81
- async function updateInstructionFiles(projectDir) {
82
- const files = [
83
- ['AGENTS.md', CODEX_INSTRUCTIONS],
84
- ['CLAUDE.md', CLAUDE_INSTRUCTIONS],
85
- ];
86
- for (const [name, managedBlock] of files) {
87
- const file = path.join(projectDir, name);
88
- const source = await exists(file) ? await readFile(file, 'utf8') : '';
89
- await writeFile(file, withManagedBlock(source, managedBlock), 'utf8');
103
+ return source.slice(0, start) + cliFirstInstructions(role) +
104
+ source.slice(end + ROLE_BLOCK_END.length);
90
105
  }
106
+ return `${source.trimEnd()}\n\n${cliFirstInstructions(role)}\n`;
91
107
  }
92
108
 
93
- async function patchPlaywrightConfig(projectDir) {
94
- const configPath = path.join(projectDir, 'playwright.config.ts');
95
- let source = await readFile(configPath, 'utf8');
96
- source = source.replace(/testDir:\s*['"]\.\/tests['"]/, "testDir: './playwright-tests'");
97
- source = source.replace(
98
- /reporter:\s*['"]html['"],?/,
99
- "reporter: [['html', { outputFolder: '.playwright-evidence/report', open: 'never' }]],",
100
- );
101
- source = source.replace(
102
- /use:\s*\{/,
103
- "outputDir: '.playwright-evidence/test-results',\n snapshotPathTemplate: '.playwright-evidence/snapshots/{testFilePath}/{arg}{ext}',\n use: {\n screenshot: 'only-on-failure',\n video: 'retain-on-failure',",
104
- );
105
- source = source.replace(
106
- /trace:\s*['"][^'"]+['"]/,
107
- "trace: { mode: 'retain-on-failure', screenshots: false, snapshots: true, sources: true, attachments: true }",
108
- );
109
- await writeFile(configPath, source, 'utf8');
109
+ function tomlMultilineBasicString(value) {
110
+ const escaped = value
111
+ .replace(/\\/g, '\\\\')
112
+ .replace(/"""/g, '\\"\\"\\"')
113
+ .replace(/\r\n?/g, '\n');
114
+ return `"""\n${escaped}\n"""`;
110
115
  }
111
116
 
112
- function withoutTraceScreenshots(source) {
113
- const replacement = "trace: { mode: 'retain-on-failure', screenshots: false, snapshots: true, sources: true, attachments: true }";
114
- const fromString = source.replace(/trace:\s*['"][^'"]+['"]/, replacement);
115
- if (fromString !== source) return fromString;
116
-
117
- return source.replace(/trace:\s*\{([\s\S]*?)\}/, (setting, body) => {
118
- if (/screenshots\s*:/.test(body)) {
119
- return setting.replace(/screenshots\s*:\s*(?:true|false)/, 'screenshots: false');
120
- }
121
- return setting.replace('{', '{ screenshots: false,');
122
- });
117
+ function patchCodexRole(source, role) {
118
+ const pattern = /(^|\n)developer_instructions\s*=\s*"""\r?\n([\s\S]*?)\r?\n"""/;
119
+ const match = source.match(pattern);
120
+ if (!match) throw new Error('developer_instructions was not found in a Codex role definition');
121
+ const updated = withRoleBlock(match[2], role);
122
+ return source
123
+ .replace(pattern, `${match[1]}developer_instructions = ${tomlMultilineBasicString(updated)}`)
124
+ .replace(/^sandbox_mode\s*=\s*"[^"]+"/m, 'sandbox_mode = "workspace-write"');
123
125
  }
124
126
 
125
- async function disableTraceScreenshots(projectDir) {
126
- const configPath = path.join(projectDir, 'playwright.config.ts');
127
- if (!await exists(configPath)) return;
128
-
129
- let source = await readFile(configPath, 'utf8');
130
- const updated = withoutTraceScreenshots(source);
131
-
132
- if (updated !== source) {
133
- await writeFile(configPath, updated, 'utf8');
127
+ function patchClaudeRole(source, role) {
128
+ let updated = source;
129
+ if (!/^\s*-\s+Bash\(playwright-cli:\*\)\s*$/m.test(updated)) {
130
+ const tools = updated.match(/^tools:\s*\r?\n/m);
131
+ if (!tools) throw new Error('tools list was not found in a Claude role definition');
132
+ const insertion = ' - Bash(playwright-cli:*)\n - Bash(npx:*)\n';
133
+ updated = updated.slice(0, tools.index + tools[0].length) + insertion +
134
+ updated.slice(tools.index + tools[0].length);
134
135
  }
136
+ return withRoleBlock(updated, role);
135
137
  }
136
138
 
137
- async function configureClaudeMcp(projectDir) {
138
- const file = path.join(projectDir, '.mcp.json');
139
- if (!await exists(file)) return;
140
- const config = JSON.parse(await readFile(file, 'utf8'));
141
- const server = config.mcpServers?.['playwright-test'];
142
- if (!server) return;
143
- server.env = {
144
- ...(server.env ?? {}),
145
- PLAYWRIGHT_MCP_OUTPUT_DIR: '.playwright-evidence/mcp',
146
- };
147
- await writeFile(file, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
148
- }
149
-
150
- async function configureCodexAgents(projectDir) {
151
- for (const name of ['planner', 'generator', 'healer']) {
152
- const file = path.join(projectDir, '.codex', 'agents', `playwright_test_${name}.toml`);
153
- if (!await exists(file)) continue;
154
- let source = await readFile(file, 'utf8');
155
- if (!source.includes('[mcp_servers.playwright-test.env]')) {
156
- source = `${source.trimEnd()}\n\n[mcp_servers.playwright-test.env]\n` +
157
- 'PLAYWRIGHT_MCP_OUTPUT_DIR = ".playwright-evidence/mcp"\n';
158
- await writeFile(file, source, 'utf8');
139
+ async function configureCliFirstRoles(projectDir) {
140
+ const roles = ['planner', 'generator', 'healer'];
141
+ for (const role of roles) {
142
+ const claudeFile = path.join(projectDir, '.claude', 'agents', `playwright-test-${role}.md`);
143
+ if (await exists(claudeFile)) {
144
+ const source = await readFile(claudeFile, 'utf8');
145
+ await writeFile(claudeFile, patchClaudeRole(source, role), 'utf8');
159
146
  }
160
- }
161
- }
162
147
 
163
- async function initializePlaywright(projectDir, run) {
164
- const configPath = path.join(projectDir, 'playwright.config.ts');
165
- if (!await exists(configPath)) {
166
- await run(projectDir, 'npm', [
167
- 'init',
168
- 'playwright@latest',
169
- '--',
170
- '--quiet',
171
- '--lang=TypeScript',
172
- '--browser=chromium',
173
- ]);
174
-
175
- const generatedExample = path.join(projectDir, 'tests', 'example.spec.ts');
176
- const targetDir = path.join(projectDir, 'playwright-tests');
177
- if (await exists(generatedExample)) {
178
- await mkdir(targetDir, { recursive: true });
179
- await rename(generatedExample, path.join(targetDir, 'example.spec.ts'));
148
+ const codexFile = path.join(projectDir, '.codex', 'agents', `playwright_test_${role}.toml`);
149
+ if (await exists(codexFile)) {
150
+ const source = await readFile(codexFile, 'utf8');
151
+ await writeFile(codexFile, patchCodexRole(source, role), 'utf8');
180
152
  }
181
- await patchPlaywrightConfig(projectDir);
182
153
  }
183
-
184
- await disableTraceScreenshots(projectDir);
185
-
186
- await run(projectDir, 'npx', ['--no-install', 'playwright', 'init-agents', '--loop=codex']);
187
- await run(projectDir, 'npx', ['--no-install', 'playwright', 'init-agents', '--loop=claude']);
188
- await mkdir(path.join(projectDir, '.playwright-evidence', 'mcp'), { recursive: true });
189
- await configureClaudeMcp(projectDir);
190
- await configureCodexAgents(projectDir);
191
154
  }
192
155
 
193
- async function runStage(name, action) {
194
- process.stdout.write(`[playwright-test-agent] ${name}...\n`);
195
- try {
196
- await action();
197
- } catch (error) {
198
- throw new Error(`${name}: ${error.message}`, { cause: error });
199
- }
156
+ async function installPlaywrightCli(projectDir, run) {
157
+ await run(projectDir, 'npm', ['install', '-g', '@playwright/cli@latest']);
158
+ await run(projectDir, 'playwright-cli', ['install', '--skills=agents', '--global']);
159
+ await run(projectDir, 'playwright-cli', ['install', '--skills', '--global']);
160
+ await run(projectDir, 'playwright-cli', ['install-browser']);
200
161
  }
201
-
202
- export async function initializeProject({
203
- projectDir = process.cwd(),
204
- skillSourceDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'),
205
- run = defaultRun,
206
- } = {}) {
207
- const target = path.resolve(projectDir);
208
- await mkdir(target, { recursive: true });
209
-
162
+
163
+ async function patchPlaywrightConfig(projectDir) {
164
+ const configPath = path.join(projectDir, 'playwright.config.ts');
165
+ let source = await readFile(configPath, 'utf8');
166
+ source = source.replace(/testDir:\s*['"]\.\/tests['"]/, "testDir: './playwright-tests'");
167
+ source = source.replace(
168
+ /reporter:\s*['"]html['"],?/,
169
+ "reporter: [['html', { outputFolder: '.playwright-evidence/report', open: 'never' }]],",
170
+ );
171
+ source = source.replace(
172
+ /use:\s*\{/,
173
+ "outputDir: '.playwright-evidence/test-results',\n snapshotPathTemplate: '.playwright-evidence/snapshots/{testFilePath}/{arg}{ext}',\n use: {\n screenshot: 'only-on-failure',\n video: 'retain-on-failure',",
174
+ );
175
+ source = source.replace(
176
+ /trace:\s*['"][^'"]+['"]/,
177
+ "trace: { mode: 'retain-on-failure', screenshots: false, snapshots: true, sources: true, attachments: true }",
178
+ );
179
+ await writeFile(configPath, source, 'utf8');
180
+ }
181
+
182
+ function withoutTraceScreenshots(source) {
183
+ const replacement = "trace: { mode: 'retain-on-failure', screenshots: false, snapshots: true, sources: true, attachments: true }";
184
+ const fromString = source.replace(/trace:\s*['"][^'"]+['"]/, replacement);
185
+ if (fromString !== source) return fromString;
186
+
187
+ return source.replace(/trace:\s*\{([\s\S]*?)\}/, (setting, body) => {
188
+ if (/screenshots\s*:/.test(body)) {
189
+ return setting.replace(/screenshots\s*:\s*(?:true|false)/, 'screenshots: false');
190
+ }
191
+ return setting.replace('{', '{ screenshots: false,');
192
+ });
193
+ }
194
+
195
+ async function disableTraceScreenshots(projectDir) {
196
+ const configPath = path.join(projectDir, 'playwright.config.ts');
197
+ if (!await exists(configPath)) return;
198
+
199
+ let source = await readFile(configPath, 'utf8');
200
+ const updated = withoutTraceScreenshots(source);
201
+
202
+ if (updated !== source) {
203
+ await writeFile(configPath, updated, 'utf8');
204
+ }
205
+ }
206
+
207
+ async function configureClaudeMcp(projectDir) {
208
+ const file = path.join(projectDir, '.mcp.json');
209
+ if (!await exists(file)) return;
210
+ const config = JSON.parse(await readFile(file, 'utf8'));
211
+ const server = config.mcpServers?.['playwright-test'];
212
+ if (!server) return;
213
+ server.env = {
214
+ ...(server.env ?? {}),
215
+ PLAYWRIGHT_MCP_OUTPUT_DIR: '.playwright-evidence/mcp',
216
+ };
217
+ await writeFile(file, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
218
+ }
219
+
220
+ async function configureCodexAgents(projectDir) {
221
+ for (const name of ['planner', 'generator', 'healer']) {
222
+ const file = path.join(projectDir, '.codex', 'agents', `playwright_test_${name}.toml`);
223
+ if (!await exists(file)) continue;
224
+ let source = await readFile(file, 'utf8');
225
+ if (!source.includes('[mcp_servers.playwright-test.env]')) {
226
+ source = `${source.trimEnd()}\n\n[mcp_servers.playwright-test.env]\n` +
227
+ 'PLAYWRIGHT_MCP_OUTPUT_DIR = ".playwright-evidence/mcp"\n';
228
+ await writeFile(file, source, 'utf8');
229
+ }
230
+ }
231
+ }
232
+
233
+ async function initializePlaywright(projectDir, run) {
234
+ const configPath = path.join(projectDir, 'playwright.config.ts');
235
+ if (!await exists(configPath)) {
236
+ await run(projectDir, 'npm', [
237
+ 'init',
238
+ 'playwright@latest',
239
+ '--',
240
+ '--quiet',
241
+ '--lang=TypeScript',
242
+ '--browser=chromium',
243
+ ]);
244
+
245
+ const generatedExample = path.join(projectDir, 'tests', 'example.spec.ts');
246
+ const targetDir = path.join(projectDir, 'playwright-tests');
247
+ if (await exists(generatedExample)) {
248
+ await mkdir(targetDir, { recursive: true });
249
+ await rename(generatedExample, path.join(targetDir, 'example.spec.ts'));
250
+ }
251
+ await patchPlaywrightConfig(projectDir);
252
+ }
253
+
254
+ await disableTraceScreenshots(projectDir);
255
+
256
+ await run(projectDir, 'npx', ['--no-install', 'playwright', 'init-agents', '--loop=codex']);
257
+ await run(projectDir, 'npx', ['--no-install', 'playwright', 'init-agents', '--loop=claude']);
258
+ await mkdir(path.join(projectDir, '.playwright-evidence', 'mcp'), { recursive: true });
259
+ await configureClaudeMcp(projectDir);
260
+ await configureCodexAgents(projectDir);
261
+ await configureCliFirstRoles(projectDir);
262
+ }
263
+
264
+ async function runStage(name, action) {
265
+ process.stdout.write(`[playwright-test-agent] ${name}...\n`);
266
+ try {
267
+ await action();
268
+ } catch (error) {
269
+ throw new Error(`${name}: ${error.message}`, { cause: error });
270
+ }
271
+ }
272
+
273
+ export async function initializeProject({
274
+ projectDir = process.cwd(),
275
+ skillSourceDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'),
276
+ run = defaultRun,
277
+ } = {}) {
278
+ const target = path.resolve(projectDir);
279
+ await mkdir(target, { recursive: true });
280
+
210
281
  await runStage('installing skill', () => installSkill(target, skillSourceDir));
282
+ await runStage('installing Playwright CLI and browser', () => installPlaywrightCli(target, run));
211
283
  await runStage('initializing Playwright Test agents', () => initializePlaywright(target, run));
212
- await runStage('updating project instructions', () => updateInstructionFiles(target));
213
-
214
- process.stdout.write(
215
- 'Playwright Test Agent ready: skills installed for Codex and Claude, ' +
216
- 'tests in playwright-tests/, evidence in .playwright-evidence/.\n',
217
- );
218
- }
219
-
220
- const isDirectInvocation = process.argv[1] &&
221
- path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url));
222
-
223
- if (isDirectInvocation) {
224
- initializeProject({ projectDir: process.argv[2] ?? process.cwd() }).catch((error) => {
225
- console.error(`Playwright initialization failed: ${error.message}`);
226
- process.exitCode = 1;
227
- });
228
- }
284
+ await runStage('updating project instructions', () => updateInstructionFiles(target));
285
+
286
+ process.stdout.write(
287
+ 'Playwright Test Agent ready: Playwright CLI and skills installed globally, ' +
288
+ 'agent definitions configured CLI-first for Codex and Claude, ' +
289
+ 'tests in playwright-tests/, evidence in .playwright-evidence/.\n',
290
+ );
291
+ }
292
+
293
+ const isDirectInvocation = process.argv[1] &&
294
+ path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url));
295
+
296
+ if (isDirectInvocation) {
297
+ initializeProject({ projectDir: process.argv[2] ?? process.cwd() }).catch((error) => {
298
+ console.error(`Playwright initialization failed: ${error.message}`);
299
+ process.exitCode = 1;
300
+ });
301
+ }