playwright-test-agent 0.1.0

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.
@@ -0,0 +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
+
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
+ }
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "playwright-test-agent",
3
+ "version": "0.1.0",
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
+ }
@@ -0,0 +1,71 @@
1
+ ---
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.
4
+ ---
5
+
6
+ # Playwright Test Agent
7
+
8
+ Use Playwright Test for durable automation. Browser tests use Playwright's official planner, generator, and healer subagents. Pure HTTP API tests may be written directly with Playwright `APIRequestContext` without those subagents.
9
+
10
+ ## Initialize once per project
11
+
12
+ 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:
13
+
14
+ ```bash
15
+ npx playwright-test-agent init
16
+ ```
17
+
18
+ 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:
19
+
20
+ ```text
21
+ npx playwright init-agents --loop=codex
22
+ npx playwright init-agents --loop=claude
23
+ ```
24
+
25
+ It uses the project-local Playwright command. Never install a global/additional Playwright CLI or run `playwright init-skills`.
26
+
27
+ ## Understand the request
28
+
29
+ Before testing, inspect the target project for relevant source, routes/API clients, environment examples, existing tests, and run instructions. The project may be empty or may contain no useful application context; in that case, do not keep searching the filesystem or invent missing details. Use the user's description and the live target as the available evidence.
30
+
31
+ Establish the objective, reachable environment/base URL, allowed side effects, authentication/roles, required test data/variables, and observable success criteria before invoking the planner. Ask the user for any material fact that cannot be verified from the project or target application.
32
+
33
+ If reconnaissance shows that provided 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 the application behavior conflicts with the stated workflow—stop exploring. Report the exact mismatch without exposing secrets and ask the user to correct or complete the information. Do not repeatedly try nearby URLs, guess credentials, wander through unrelated pages, or invoke the planner while blocked. Resume reconnaissance and invoke the planner only after the user supplies enough corrected information.
34
+
35
+ Never put credentials or tokens in plans, source, screenshots, reports, or chat output. Use environment variables or an ignored secret file.
36
+
37
+ ## Plan and confirm
38
+
39
+ Always save a plan to `specs/<feature>.plan.md` before generating or running tests. Include prerequisites, seed, test data, independent scenarios, steps or requests, expected observable results, and output test files.
40
+
41
+ For UI work, invoke `playwright_test_planner` only after the required project/user information has been checked and no known mismatch is blocking exploration. Have it inspect the live application and save the plan. Reconnaissance must not create, delete, submit, purchase, message, or otherwise mutate durable/shared data without authorization.
42
+
43
+ If the planner discovers a new missing or incorrect prerequisite, stop that planning attempt, summarize what was observed, and ask the user for clarification. Continue with the planner only after the prerequisite is resolved; do not let the planner keep exploring around the missing information.
44
+
45
+ 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.
46
+
47
+ 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.
48
+
49
+ ## Generate and run
50
+
51
+ For confirmed UI scenarios, invoke `playwright_test_generator` once for the complete confirmed plan (or the complete selected set of scenarios), passing the exact plan content, seed, and destination under `playwright-tests/`. The generator processes the scenarios in plan order in that single invocation; its internal per-scenario setup does not mean starting a new generator agent for each scenario. Require one independent test per file, semantic locators, an assertion for every expected result, and environment-based secrets.
52
+
53
+ 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.
54
+
55
+ Run the generated tests with the project-local command. On failure, use `playwright_test_healer` one failure at a time for UI tests; API failures may be diagnosed directly. If observed product behavior conflicts with the confirmed plan, ask whether it is a regression or intended change. Do not weaken assertions, add sleeps/`networkidle`, retry blindly, or skip/fixme tests merely to get green.
56
+
57
+ ## Preserve evidence
58
+
59
+ All browser-agent and test artifacts must stay under:
60
+
61
+ ```text
62
+ .playwright-evidence/
63
+ ├── mcp/ # page YAML snapshots, screenshots, and agent logs
64
+ ├── snapshots/ # reusable visual/ARIA baselines
65
+ ├── test-results/ # failure screenshots, traces, videos, attachments
66
+ └── report/ # HTML report
67
+ ```
68
+
69
+ 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.
70
+
71
+ 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.
@@ -0,0 +1,172 @@
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
+ const BLOCK_START = '<!-- playwright-test-agent:start -->';
9
+ const BLOCK_END = '<!-- playwright-test-agent:end -->';
10
+ const PROJECT_INSTRUCTIONS = `${BLOCK_START}
11
+ ## Playwright Test Agent
12
+
13
+ When asked to test a website, browser workflow, HTTP API, or application feature, load and follow the project-installed \`playwright-test-agent\` skill first. For UI tests, use its Playwright Test planner, generator, and healer workflow instead of ad hoc browser automation. Keep its confirmation, credential, side-effect, and evidence rules in force.
14
+ ${BLOCK_END}`;
15
+
16
+ const exists = async (file) => {
17
+ try {
18
+ await access(file);
19
+ return true;
20
+ } catch {
21
+ return false;
22
+ }
23
+ };
24
+
25
+ const defaultRun = (projectDir, command, args) => new Promise((resolve, reject) => {
26
+ const executable = process.platform === 'win32' ? `${command}.cmd` : command;
27
+ const child = spawn(executable, args, { cwd: projectDir, stdio: 'inherit' });
28
+ child.on('error', reject);
29
+ child.on('exit', (code) => code === 0
30
+ ? resolve()
31
+ : reject(new Error(`${command} ${args.join(' ')} exited with ${code}`)));
32
+ });
33
+
34
+ function withManagedBlock(source) {
35
+ const start = source.indexOf(BLOCK_START);
36
+ const end = source.indexOf(BLOCK_END);
37
+
38
+ if ((start === -1) !== (end === -1) || (start !== -1 && end < start)) {
39
+ throw new Error('found an incomplete playwright-test-agent managed block');
40
+ }
41
+
42
+ if (start !== -1) {
43
+ return source.slice(0, start) + PROJECT_INSTRUCTIONS + source.slice(end + BLOCK_END.length);
44
+ }
45
+
46
+ if (source.length === 0) return `${PROJECT_INSTRUCTIONS}\n`;
47
+ const separator = source.endsWith('\n') ? '\n' : '\n\n';
48
+ return `${source}${separator}${PROJECT_INSTRUCTIONS}\n`;
49
+ }
50
+
51
+ async function installSkill(projectDir, skillSourceDir) {
52
+ for (const root of ['.agents', '.claude']) {
53
+ const destination = path.join(projectDir, root, 'skills', 'playwright-test-agent');
54
+ await mkdir(destination, { recursive: true });
55
+ await cp(skillSourceDir, destination, { recursive: true, force: true });
56
+ }
57
+ }
58
+
59
+ async function updateInstructionFiles(projectDir) {
60
+ for (const name of ['AGENTS.md', 'CLAUDE.md']) {
61
+ const file = path.join(projectDir, name);
62
+ const source = await exists(file) ? await readFile(file, 'utf8') : '';
63
+ await writeFile(file, withManagedBlock(source), 'utf8');
64
+ }
65
+ }
66
+
67
+ async function patchPlaywrightConfig(projectDir) {
68
+ const configPath = path.join(projectDir, 'playwright.config.ts');
69
+ let source = await readFile(configPath, 'utf8');
70
+ source = source.replace(/testDir:\s*['"]\.\/tests['"]/, "testDir: './playwright-tests'");
71
+ source = source.replace(
72
+ /reporter:\s*['"]html['"],?/,
73
+ "reporter: [['html', { outputFolder: '.playwright-evidence/report', open: 'never' }]],",
74
+ );
75
+ source = source.replace(
76
+ /use:\s*\{/,
77
+ "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',",
78
+ );
79
+ source = source.replace(/trace:\s*['"][^'"]+['"]/, "trace: 'retain-on-failure'");
80
+ await writeFile(configPath, source, 'utf8');
81
+ }
82
+
83
+ async function configureClaudeMcp(projectDir) {
84
+ const file = path.join(projectDir, '.mcp.json');
85
+ if (!await exists(file)) return;
86
+ const config = JSON.parse(await readFile(file, 'utf8'));
87
+ const server = config.mcpServers?.['playwright-test'];
88
+ if (!server) return;
89
+ server.env = {
90
+ ...(server.env ?? {}),
91
+ PLAYWRIGHT_MCP_OUTPUT_DIR: '.playwright-evidence/mcp',
92
+ };
93
+ await writeFile(file, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
94
+ }
95
+
96
+ async function configureCodexAgents(projectDir) {
97
+ for (const name of ['planner', 'generator', 'healer']) {
98
+ const file = path.join(projectDir, '.codex', 'agents', `playwright_test_${name}.toml`);
99
+ if (!await exists(file)) continue;
100
+ let source = await readFile(file, 'utf8');
101
+ if (!source.includes('[mcp_servers.playwright-test.env]')) {
102
+ source = `${source.trimEnd()}\n\n[mcp_servers.playwright-test.env]\n` +
103
+ 'PLAYWRIGHT_MCP_OUTPUT_DIR = ".playwright-evidence/mcp"\n';
104
+ await writeFile(file, source, 'utf8');
105
+ }
106
+ }
107
+ }
108
+
109
+ async function initializePlaywright(projectDir, run) {
110
+ const configPath = path.join(projectDir, 'playwright.config.ts');
111
+ if (!await exists(configPath)) {
112
+ await run(projectDir, 'npm', [
113
+ 'init',
114
+ 'playwright@latest',
115
+ '--',
116
+ '--quiet',
117
+ '--lang=TypeScript',
118
+ '--browser=chromium',
119
+ ]);
120
+
121
+ const generatedExample = path.join(projectDir, 'tests', 'example.spec.ts');
122
+ const targetDir = path.join(projectDir, 'playwright-tests');
123
+ if (await exists(generatedExample)) {
124
+ await mkdir(targetDir, { recursive: true });
125
+ await rename(generatedExample, path.join(targetDir, 'example.spec.ts'));
126
+ }
127
+ await patchPlaywrightConfig(projectDir);
128
+ }
129
+
130
+ await run(projectDir, 'npx', ['--no-install', 'playwright', 'init-agents', '--loop=codex']);
131
+ await run(projectDir, 'npx', ['--no-install', 'playwright', 'init-agents', '--loop=claude']);
132
+ await mkdir(path.join(projectDir, '.playwright-evidence', 'mcp'), { recursive: true });
133
+ await configureClaudeMcp(projectDir);
134
+ await configureCodexAgents(projectDir);
135
+ }
136
+
137
+ async function runStage(name, action) {
138
+ process.stdout.write(`[playwright-test-agent] ${name}...\n`);
139
+ try {
140
+ await action();
141
+ } catch (error) {
142
+ throw new Error(`${name}: ${error.message}`, { cause: error });
143
+ }
144
+ }
145
+
146
+ export async function initializeProject({
147
+ projectDir = process.cwd(),
148
+ skillSourceDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'),
149
+ run = defaultRun,
150
+ } = {}) {
151
+ const target = path.resolve(projectDir);
152
+ await mkdir(target, { recursive: true });
153
+
154
+ await runStage('installing skill', () => installSkill(target, skillSourceDir));
155
+ await runStage('initializing Playwright Test agents', () => initializePlaywright(target, run));
156
+ await runStage('updating project instructions', () => updateInstructionFiles(target));
157
+
158
+ process.stdout.write(
159
+ 'Playwright Test Agent ready: skills installed for Codex and Claude, ' +
160
+ 'tests in playwright-tests/, evidence in .playwright-evidence/.\n',
161
+ );
162
+ }
163
+
164
+ const isDirectInvocation = process.argv[1] &&
165
+ path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url));
166
+
167
+ if (isDirectInvocation) {
168
+ initializeProject({ projectDir: process.argv[2] ?? process.cwd() }).catch((error) => {
169
+ console.error(`Playwright initialization failed: ${error.message}`);
170
+ process.exitCode = 1;
171
+ });
172
+ }