playwright 1.57.0-alpha-2025-10-27 → 1.57.0-alpha-2025-10-29
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/lib/agents/copilot-setup-steps.yml +34 -0
- package/lib/agents/generateAgents.js +104 -72
- package/lib/agents/{test-coverage.prompt.md → pwt-coverage.prompt.md} +4 -4
- package/lib/agents/{test-generate.prompt.md → pwt-generate.prompt.md} +1 -1
- package/lib/agents/{generator.agent.md → pwt-generator.agent.md} +7 -20
- package/lib/agents/{test-heal.prompt.md → pwt-heal.prompt.md} +1 -1
- package/lib/agents/{healer.agent.md → pwt-healer.agent.md} +1 -23
- package/lib/agents/{test-plan.prompt.md → pwt-plan.prompt.md} +1 -1
- package/lib/agents/{planner.agent.md → pwt-planner.agent.md} +2 -23
- package/lib/mcp/program.js +20 -0
- package/lib/program.js +6 -7
- package/lib/runner/testRunner.js +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: "Copilot Setup Steps"
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
push:
|
|
6
|
+
paths:
|
|
7
|
+
- .github/workflows/copilot-setup-steps.yml
|
|
8
|
+
pull_request:
|
|
9
|
+
paths:
|
|
10
|
+
- .github/workflows/copilot-setup-steps.yml
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
copilot-setup-steps:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- uses: actions/setup-node@v4
|
|
23
|
+
with:
|
|
24
|
+
node-version: lts/*
|
|
25
|
+
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: npm ci
|
|
28
|
+
|
|
29
|
+
- name: Install Playwright Browsers
|
|
30
|
+
run: npx playwright install --with-deps
|
|
31
|
+
|
|
32
|
+
# Customize this step as needed
|
|
33
|
+
- name: Build application
|
|
34
|
+
run: npx run build
|
|
@@ -28,8 +28,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
29
|
var generateAgents_exports = {};
|
|
30
30
|
__export(generateAgents_exports, {
|
|
31
|
-
AgentGenerator: () => AgentGenerator,
|
|
32
31
|
ClaudeGenerator: () => ClaudeGenerator,
|
|
32
|
+
CopilotGenerator: () => CopilotGenerator,
|
|
33
33
|
OpencodeGenerator: () => OpencodeGenerator,
|
|
34
34
|
VSCodeGenerator: () => VSCodeGenerator
|
|
35
35
|
});
|
|
@@ -48,7 +48,7 @@ class AgentParser {
|
|
|
48
48
|
const source = await import_fs.default.promises.readFile(filePath, "utf-8");
|
|
49
49
|
const { header, content } = this.extractYamlAndContent(source);
|
|
50
50
|
const { instructions, examples } = this.extractInstructionsAndExamples(content);
|
|
51
|
-
return {
|
|
51
|
+
return { header, instructions, examples };
|
|
52
52
|
}
|
|
53
53
|
static extractYamlAndContent(markdown) {
|
|
54
54
|
const lines = markdown.split("\n");
|
|
@@ -92,14 +92,17 @@ class AgentParser {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
class ClaudeGenerator {
|
|
95
|
-
static async init(config, projectName) {
|
|
95
|
+
static async init(config, projectName, prompts) {
|
|
96
96
|
await initRepo(config, projectName, {
|
|
97
|
-
promptsFolder: ".claude/prompts"
|
|
97
|
+
promptsFolder: prompts ? ".claude/prompts" : void 0
|
|
98
98
|
});
|
|
99
99
|
const agents = await AgentParser.loadAgents();
|
|
100
100
|
await import_fs.default.promises.mkdir(".claude/agents", { recursive: true });
|
|
101
101
|
for (const agent of agents)
|
|
102
|
-
await writeFile(`.claude/agents/${agent.header.name}.
|
|
102
|
+
await writeFile(`.claude/agents/${agent.header.name}.md`, ClaudeGenerator.agentSpec(agent), "\u{1F916}", "agent definition");
|
|
103
|
+
await deleteFile(`.claude/agents/playwright-test-planner.md`, "legacy planner agent");
|
|
104
|
+
await deleteFile(`.claude/agents/playwright-test-generator.md`, "legacy generator agent");
|
|
105
|
+
await deleteFile(`.claude/agents/playwright-test-healer.md`, "legacy healer agent");
|
|
103
106
|
await writeFile(".mcp.json", JSON.stringify({
|
|
104
107
|
mcpServers: {
|
|
105
108
|
"playwright-test": {
|
|
@@ -112,10 +115,8 @@ class ClaudeGenerator {
|
|
|
112
115
|
}
|
|
113
116
|
static agentSpec(agent) {
|
|
114
117
|
const claudeToolMap = /* @__PURE__ */ new Map([
|
|
115
|
-
["search", ["Glob", "Grep"]],
|
|
116
|
-
["
|
|
117
|
-
["edit", ["Edit", "MultiEdit"]],
|
|
118
|
-
["write", ["Write"]]
|
|
118
|
+
["search", ["Glob", "Grep", "Read", "LS"]],
|
|
119
|
+
["edit", ["Edit", "MultiEdit", "Write"]]
|
|
119
120
|
]);
|
|
120
121
|
function asClaudeTool(tool) {
|
|
121
122
|
const [first, second] = tool.split("/");
|
|
@@ -123,41 +124,45 @@ class ClaudeGenerator {
|
|
|
123
124
|
return (claudeToolMap.get(first) || [first]).join(", ");
|
|
124
125
|
return `mcp__${first}__${second}`;
|
|
125
126
|
}
|
|
127
|
+
const examples = agent.examples.length ? ` Examples: ${agent.examples.map((example) => `<example>${example}</example>`).join("")}` : "";
|
|
126
128
|
const lines = [];
|
|
129
|
+
const header = {
|
|
130
|
+
name: agent.header.name,
|
|
131
|
+
description: agent.header.description + examples,
|
|
132
|
+
tools: agent.header.tools.map((tool) => asClaudeTool(tool)).join(", "),
|
|
133
|
+
model: agent.header.model,
|
|
134
|
+
color: agent.header.color
|
|
135
|
+
};
|
|
127
136
|
lines.push(`---`);
|
|
128
|
-
lines.push(
|
|
129
|
-
lines.push(`description: ${agent.header.description}. Examples: ${agent.examples.map((example) => `<example>${example}</example>`).join("")}`);
|
|
130
|
-
lines.push(`tools: ${agent.header.tools.map((tool) => asClaudeTool(tool)).join(", ")}`);
|
|
131
|
-
lines.push(`model: ${agent.header.model}`);
|
|
132
|
-
lines.push(`color: ${agent.header.color}`);
|
|
133
|
-
lines.push(`---`);
|
|
137
|
+
lines.push(import_utilsBundle.yaml.stringify(header, { lineWidth: 1e5 }) + `---`);
|
|
134
138
|
lines.push("");
|
|
135
139
|
lines.push(agent.instructions);
|
|
136
140
|
return lines.join("\n");
|
|
137
141
|
}
|
|
138
142
|
}
|
|
139
143
|
class OpencodeGenerator {
|
|
140
|
-
static async init(config, projectName) {
|
|
144
|
+
static async init(config, projectName, prompts) {
|
|
141
145
|
await initRepo(config, projectName, {
|
|
142
|
-
|
|
143
|
-
promptsFolder: ".opencode/prompts"
|
|
146
|
+
defaultAgentName: "Build",
|
|
147
|
+
promptsFolder: prompts ? ".opencode/prompts" : void 0
|
|
144
148
|
});
|
|
145
149
|
const agents = await AgentParser.loadAgents();
|
|
146
150
|
for (const agent of agents) {
|
|
147
151
|
const prompt = [agent.instructions];
|
|
148
152
|
prompt.push("");
|
|
149
153
|
prompt.push(...agent.examples.map((example) => `<example>${example}</example>`));
|
|
150
|
-
await writeFile(`.opencode/prompts/${agent.header.name}.
|
|
154
|
+
await writeFile(`.opencode/prompts/${agent.header.name}.md`, prompt.join("\n"), "\u{1F916}", "agent definition");
|
|
151
155
|
}
|
|
156
|
+
await deleteFile(`.opencode/prompts/playwright-test-planner.md`, "legacy planner agent");
|
|
157
|
+
await deleteFile(`.opencode/prompts/playwright-test-generator.md`, "legacy generator agent");
|
|
158
|
+
await deleteFile(`.opencode/prompts/playwright-test-healer.md`, "legacy healer agent");
|
|
152
159
|
await writeFile("opencode.json", OpencodeGenerator.configuration(agents), "\u{1F527}", "opencode configuration");
|
|
153
160
|
initRepoDone();
|
|
154
161
|
}
|
|
155
162
|
static configuration(agents) {
|
|
156
163
|
const opencodeToolMap = /* @__PURE__ */ new Map([
|
|
157
|
-
["search", ["ls", "glob", "grep"]],
|
|
158
|
-
["
|
|
159
|
-
["edit", ["edit"]],
|
|
160
|
-
["write", ["write"]]
|
|
164
|
+
["search", ["ls", "glob", "grep", "read"]],
|
|
165
|
+
["edit", ["edit", "write"]]
|
|
161
166
|
]);
|
|
162
167
|
const asOpencodeTool = (tools, tool) => {
|
|
163
168
|
const [first, second] = tool.split("/");
|
|
@@ -180,7 +185,7 @@ class OpencodeGenerator {
|
|
|
180
185
|
result["agent"][agent.header.name] = {
|
|
181
186
|
description: agent.header.description,
|
|
182
187
|
mode: "subagent",
|
|
183
|
-
prompt: `{file:.opencode/prompts/${agent.header.name}.
|
|
188
|
+
prompt: `{file:.opencode/prompts/${agent.header.name}.md}`,
|
|
184
189
|
tools
|
|
185
190
|
};
|
|
186
191
|
for (const tool of agent.header.tools)
|
|
@@ -194,59 +199,85 @@ class OpencodeGenerator {
|
|
|
194
199
|
return JSON.stringify(result, null, 2);
|
|
195
200
|
}
|
|
196
201
|
}
|
|
197
|
-
class
|
|
198
|
-
static async init(config, projectName) {
|
|
199
|
-
const agentsFolder = process.env.AGENTS_FOLDER;
|
|
200
|
-
if (!agentsFolder) {
|
|
201
|
-
console.error("AGENTS_FOLDER environment variable is not set");
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
202
|
+
class CopilotGenerator {
|
|
203
|
+
static async init(config, projectName, prompts) {
|
|
204
204
|
await initRepo(config, projectName, {
|
|
205
|
-
|
|
205
|
+
defaultAgentName: "agent",
|
|
206
|
+
promptsFolder: prompts ? ".github/prompts" : void 0,
|
|
207
|
+
promptSuffix: "prompt"
|
|
206
208
|
});
|
|
207
209
|
const agents = await AgentParser.loadAgents();
|
|
208
|
-
await import_fs.default.promises.mkdir(
|
|
210
|
+
await import_fs.default.promises.mkdir(".github/agents", { recursive: true });
|
|
209
211
|
for (const agent of agents)
|
|
210
|
-
await writeFile(
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
212
|
+
await writeFile(`.github/agents/${agent.header.name}.agent.md`, CopilotGenerator.agentSpec(agent), "\u{1F916}", "agent definition");
|
|
213
|
+
await deleteFile(`.github/chatmodes/ \u{1F3AD} planner.chatmode.md`, "legacy planner chatmode");
|
|
214
|
+
await deleteFile(`.github/chatmodes/\u{1F3AD} generator.chatmode.md`, "legacy generator chatmode");
|
|
215
|
+
await deleteFile(`.github/chatmodes/\u{1F3AD} healer.chatmode.md`, "legacy healer chatmode");
|
|
216
|
+
await VSCodeGenerator.appendToMCPJson();
|
|
217
|
+
const cwdFolder = import_path.default.basename(process.cwd());
|
|
218
|
+
const mcpConfig = {
|
|
219
|
+
"mcpServers": {
|
|
214
220
|
"playwright-test": {
|
|
215
|
-
type: "stdio",
|
|
216
|
-
command: "npx",
|
|
217
|
-
args: [
|
|
218
|
-
`--prefix
|
|
221
|
+
"type": "stdio",
|
|
222
|
+
"command": "npx",
|
|
223
|
+
"args": [
|
|
224
|
+
`--prefix=/home/runner/work/${cwdFolder}/${cwdFolder}`,
|
|
219
225
|
"playwright",
|
|
220
226
|
"run-test-mcp-server",
|
|
221
|
-
|
|
222
|
-
`--config
|
|
227
|
+
"--headless",
|
|
228
|
+
`--config=/home/runner/work/${cwdFolder}/${cwdFolder}`
|
|
223
229
|
],
|
|
224
|
-
tools: ["*"]
|
|
230
|
+
"tools": ["*"]
|
|
225
231
|
}
|
|
226
232
|
}
|
|
227
|
-
}
|
|
233
|
+
};
|
|
234
|
+
if (!import_fs.default.existsSync(".github/copilot-setup-steps.yml")) {
|
|
235
|
+
const yaml2 = import_fs.default.readFileSync(import_path.default.join(__dirname, "copilot-setup-steps.yml"), "utf-8");
|
|
236
|
+
await writeFile(".github/workflows/copilot-setup-steps.yml", yaml2, "\u{1F527}", "GitHub Copilot setup steps");
|
|
237
|
+
}
|
|
238
|
+
console.log("");
|
|
239
|
+
console.log("");
|
|
240
|
+
console.log(" \u{1F527} TODO: GitHub > Settings > Copilot > Coding agent > MCP configuration");
|
|
241
|
+
console.log("------------------------------------------------------------------");
|
|
242
|
+
console.log(JSON.stringify(mcpConfig, null, 2));
|
|
243
|
+
console.log("------------------------------------------------------------------");
|
|
228
244
|
initRepoDone();
|
|
229
245
|
}
|
|
246
|
+
static agentSpec(agent) {
|
|
247
|
+
const examples = agent.examples.length ? ` Examples: ${agent.examples.map((example) => `<example>${example}</example>`).join("")}` : "";
|
|
248
|
+
const lines = [];
|
|
249
|
+
const header = {
|
|
250
|
+
name: agent.header.name,
|
|
251
|
+
description: agent.header.description + examples,
|
|
252
|
+
tools: agent.header.tools,
|
|
253
|
+
model: "Claude Sonnet 4"
|
|
254
|
+
};
|
|
255
|
+
lines.push(`---`);
|
|
256
|
+
lines.push(import_utilsBundle.yaml.stringify(header) + `---`);
|
|
257
|
+
lines.push("");
|
|
258
|
+
lines.push(agent.instructions);
|
|
259
|
+
lines.push("");
|
|
260
|
+
return lines.join("\n");
|
|
261
|
+
}
|
|
230
262
|
}
|
|
231
263
|
class VSCodeGenerator {
|
|
232
264
|
static async init(config, projectName) {
|
|
233
265
|
await initRepo(config, projectName, {
|
|
234
|
-
|
|
235
|
-
agentHealer: "\u{1F3AD} healer",
|
|
236
|
-
agentGenerator: "\u{1F3AD} generator",
|
|
237
|
-
agentPlanner: "\u{1F3AD} planner",
|
|
238
|
-
promptsFolder: ".github/prompts"
|
|
266
|
+
promptsFolder: void 0
|
|
239
267
|
});
|
|
240
268
|
const agents = await AgentParser.loadAgents();
|
|
241
269
|
const nameMap = /* @__PURE__ */ new Map([
|
|
242
|
-
["playwright-test-planner", "\u{1F3AD} planner"],
|
|
270
|
+
["playwright-test-planner", " \u{1F3AD} planner"],
|
|
243
271
|
["playwright-test-generator", "\u{1F3AD} generator"],
|
|
244
272
|
["playwright-test-healer", "\u{1F3AD} healer"]
|
|
245
273
|
]);
|
|
246
|
-
await deleteFile(`.github/chatmodes/ \u{1F3AD} planner.chatmode.md`, "old planner chatmode");
|
|
247
274
|
await import_fs.default.promises.mkdir(".github/chatmodes", { recursive: true });
|
|
248
275
|
for (const agent of agents)
|
|
249
276
|
await writeFile(`.github/chatmodes/${nameMap.get(agent.header.name)}.chatmode.md`, VSCodeGenerator.agentSpec(agent), "\u{1F916}", "chatmode definition");
|
|
277
|
+
await VSCodeGenerator.appendToMCPJson();
|
|
278
|
+
initRepoDone();
|
|
279
|
+
}
|
|
280
|
+
static async appendToMCPJson() {
|
|
250
281
|
await import_fs.default.promises.mkdir(".vscode", { recursive: true });
|
|
251
282
|
const mcpJsonPath = ".vscode/mcp.json";
|
|
252
283
|
let mcpJson = {
|
|
@@ -265,7 +296,6 @@ class VSCodeGenerator {
|
|
|
265
296
|
args: ["playwright", "run-test-mcp-server"]
|
|
266
297
|
};
|
|
267
298
|
await writeFile(mcpJsonPath, JSON.stringify(mcpJson, null, 2), "\u{1F527}", "mcp configuration");
|
|
268
|
-
initRepoDone();
|
|
269
299
|
}
|
|
270
300
|
static agentSpec(agent) {
|
|
271
301
|
const vscodeToolMap = /* @__PURE__ */ new Map([
|
|
@@ -302,11 +332,12 @@ class VSCodeGenerator {
|
|
|
302
332
|
lines.push(agent.instructions);
|
|
303
333
|
for (const example of agent.examples)
|
|
304
334
|
lines.push(`<example>${example}</example>`);
|
|
335
|
+
lines.push("");
|
|
305
336
|
return lines.join("\n");
|
|
306
337
|
}
|
|
307
338
|
}
|
|
308
339
|
async function writeFile(filePath, content, icon, description) {
|
|
309
|
-
console.log(
|
|
340
|
+
console.log(` ${icon} ${import_path.default.relative(process.cwd(), filePath)} ${import_utilsBundle.colors.dim("- " + description)}`);
|
|
310
341
|
await (0, import_utils.mkdirIfNeeded)(filePath);
|
|
311
342
|
await import_fs.default.promises.writeFile(filePath, content, "utf-8");
|
|
312
343
|
}
|
|
@@ -317,12 +348,12 @@ async function deleteFile(filePath, description) {
|
|
|
317
348
|
} catch {
|
|
318
349
|
return;
|
|
319
350
|
}
|
|
320
|
-
console.log(
|
|
351
|
+
console.log(` \u2702\uFE0F ${import_path.default.relative(process.cwd(), filePath)} ${import_utilsBundle.colors.dim("- " + description)}`);
|
|
321
352
|
await import_fs.default.promises.unlink(filePath);
|
|
322
353
|
}
|
|
323
354
|
async function initRepo(config, projectName, options) {
|
|
324
355
|
const project = (0, import_seed.seedProject)(config, projectName);
|
|
325
|
-
console.log(
|
|
356
|
+
console.log(` \u{1F3AD} Using project "${project.project.name}" as a primary project`);
|
|
326
357
|
if (!import_fs.default.existsSync("specs")) {
|
|
327
358
|
await import_fs.default.promises.mkdir("specs");
|
|
328
359
|
await writeFile(import_path.default.join("specs", "README.md"), `# Specs
|
|
@@ -335,34 +366,35 @@ This is a directory for test plans.
|
|
|
335
366
|
seedFile = (0, import_seed.defaultSeedFile)(project);
|
|
336
367
|
await writeFile(seedFile, import_seed.seedFileContent, "\u{1F331}", "default environment seed file");
|
|
337
368
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
369
|
+
if (options.promptsFolder) {
|
|
370
|
+
await import_fs.default.promises.mkdir(options.promptsFolder, { recursive: true });
|
|
371
|
+
for (const promptFile of await import_fs.default.promises.readdir(__dirname)) {
|
|
372
|
+
if (!promptFile.endsWith(".prompt.md"))
|
|
373
|
+
continue;
|
|
374
|
+
const shortName = promptFile.replace(".prompt.md", "");
|
|
375
|
+
const fileName = options.promptSuffix ? `${shortName}.${options.promptSuffix}.md` : `${shortName}.md`;
|
|
376
|
+
const content = await loadPrompt(promptFile, {
|
|
377
|
+
defaultAgentName: "default",
|
|
378
|
+
...options,
|
|
379
|
+
seedFile: import_path.default.relative(process.cwd(), seedFile)
|
|
380
|
+
});
|
|
381
|
+
await writeFile(import_path.default.join(options.promptsFolder, fileName), content, "\u{1F4DD}", "prompt template");
|
|
382
|
+
}
|
|
344
383
|
}
|
|
345
384
|
}
|
|
346
385
|
function initRepoDone() {
|
|
347
|
-
console.log("\u2705 Done.");
|
|
386
|
+
console.log(" \u2705 Done.");
|
|
348
387
|
}
|
|
349
388
|
async function loadPrompt(file, params) {
|
|
350
|
-
const templateParams = {
|
|
351
|
-
agentDefault: params.agentDefault ?? "default",
|
|
352
|
-
agentHealer: params.agentHealer ?? "playwright-test-healer",
|
|
353
|
-
agentGenerator: params.agentGenerator ?? "playwright-test-generator",
|
|
354
|
-
agentPlanner: params.agentPlanner ?? "playwright-test-planner",
|
|
355
|
-
seedFile: params.seedFile
|
|
356
|
-
};
|
|
357
389
|
const content = await import_fs.default.promises.readFile(import_path.default.join(__dirname, file), "utf-8");
|
|
358
|
-
return Object.entries(
|
|
390
|
+
return Object.entries(params).reduce((acc, [key, value]) => {
|
|
359
391
|
return acc.replace(new RegExp(`\\\${${key}}`, "g"), value);
|
|
360
392
|
}, content);
|
|
361
393
|
}
|
|
362
394
|
// Annotate the CommonJS export names for ESM import in node:
|
|
363
395
|
0 && (module.exports = {
|
|
364
|
-
AgentGenerator,
|
|
365
396
|
ClaudeGenerator,
|
|
397
|
+
CopilotGenerator,
|
|
366
398
|
OpencodeGenerator,
|
|
367
399
|
VSCodeGenerator
|
|
368
400
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
2
|
+
agent: ${defaultAgentName}
|
|
3
3
|
description: Produce test coverage
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -8,7 +8,7 @@ Parameters:
|
|
|
8
8
|
- Seed file (optional): the seed file to use, defaults to `${seedFile}`
|
|
9
9
|
- Test plan file (optional): the test plan file to write, under `specs/` folder.
|
|
10
10
|
|
|
11
|
-
1. Call
|
|
11
|
+
1. Call #pwt-planner subagent with prompt:
|
|
12
12
|
|
|
13
13
|
<plan>
|
|
14
14
|
<task-text><!-- the task --></task-text>
|
|
@@ -16,7 +16,7 @@ Parameters:
|
|
|
16
16
|
<plan-file><!-- path to test plan file to generate --></plan-file>
|
|
17
17
|
</plan>
|
|
18
18
|
|
|
19
|
-
2. For each test case from the test plan file (1.1, 1.2, ...), one after another, not in parallel, call
|
|
19
|
+
2. For each test case from the test plan file (1.1, 1.2, ...), one after another, not in parallel, call #pwt-generator subagent with prompt:
|
|
20
20
|
|
|
21
21
|
<generate>
|
|
22
22
|
<test-suite><!-- Verbatim name of the test spec group w/o ordinal like "Multiplication tests" --></test-suite>
|
|
@@ -26,6 +26,6 @@ Parameters:
|
|
|
26
26
|
<body><!-- Test case content including steps and expectations --></body>
|
|
27
27
|
</generate>
|
|
28
28
|
|
|
29
|
-
3. Call
|
|
29
|
+
3. Call #pwt-healer subagent with prompt:
|
|
30
30
|
|
|
31
31
|
<heal>Run all tests and fix the failing ones one after another.</heal>
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: pwt-generator
|
|
3
3
|
description: Use this agent when you need to create automated browser tests using Playwright
|
|
4
4
|
model: sonnet
|
|
5
5
|
color: blue
|
|
6
6
|
tools:
|
|
7
|
-
- read
|
|
8
7
|
- search
|
|
9
8
|
- playwright-test/browser_click
|
|
10
9
|
- playwright-test/browser_drag
|
|
@@ -80,22 +79,10 @@ application behavior.
|
|
|
80
79
|
</example-generation>
|
|
81
80
|
|
|
82
81
|
<example>
|
|
83
|
-
Context: User wants to
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
is designed for.
|
|
90
|
-
</commentary>
|
|
91
|
-
</example>
|
|
92
|
-
<example>
|
|
93
|
-
Context: User has built a new checkout flow and wants to ensure it works correctly.
|
|
94
|
-
user: 'Can you create a test that adds items to cart, proceeds to checkout, fills in payment details, and confirms the
|
|
95
|
-
order?'
|
|
96
|
-
assistant: 'I'll use the generator agent to build a comprehensive checkout flow test'
|
|
97
|
-
<commentary>
|
|
98
|
-
This is a complex user journey that needs to be automated and tested, perfect for the generator
|
|
99
|
-
agent.
|
|
100
|
-
</commentary>
|
|
82
|
+
Context: User wants to generate a test for the test plan item.
|
|
83
|
+
<test-suite><!-- Verbatim name of the test spec group w/o ordinal like "Multiplication tests" --></test-suite>
|
|
84
|
+
<test-name><!-- Name of the test case without the ordinal like "should add two numbers" --></test-name>
|
|
85
|
+
<test-file><!-- Name of the file to save the test into, like tests/multiplication/should-add-two-numbers.spec.ts --></test-file>
|
|
86
|
+
<seed-file><!-- Seed file path from test plan --></seed-file>
|
|
87
|
+
<body><!-- Test case content including steps and expectations --></body>
|
|
101
88
|
</example>
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: pwt-healer
|
|
3
3
|
description: Use this agent when you need to debug and fix failing Playwright tests
|
|
4
4
|
model: sonnet
|
|
5
5
|
color: red
|
|
6
6
|
tools:
|
|
7
|
-
- read
|
|
8
7
|
- search
|
|
9
|
-
- write
|
|
10
8
|
- edit
|
|
11
9
|
- playwright-test/browser_console_messages
|
|
12
10
|
- playwright-test/browser_evaluate
|
|
@@ -55,23 +53,3 @@ Key principles:
|
|
|
55
53
|
of the expected behavior.
|
|
56
54
|
- Do not ask user questions, you are not interactive tool, do the most reasonable thing possible to pass the test.
|
|
57
55
|
- Never wait for networkidle or use other discouraged or deprecated apis
|
|
58
|
-
|
|
59
|
-
<example>
|
|
60
|
-
Context: A developer has a failing Playwright test that needs to be debugged and fixed.
|
|
61
|
-
user: 'The login test is failing, can you fix it?'
|
|
62
|
-
assistant: 'I'll use the healer agent to debug and fix the failing login test.'
|
|
63
|
-
<commentary>
|
|
64
|
-
The user has identified a specific failing test that needs debugging and fixing, which is exactly what the
|
|
65
|
-
healer agent is designed for.
|
|
66
|
-
</commentary>
|
|
67
|
-
</example>
|
|
68
|
-
|
|
69
|
-
<example>
|
|
70
|
-
Context: After running a test suite, several tests are reported as failing.
|
|
71
|
-
user: 'Test user-registration.spec.ts is broken after the recent changes'
|
|
72
|
-
assistant: 'Let me use the healer agent to investigate and fix the user-registration test.'
|
|
73
|
-
<commentary>
|
|
74
|
-
A specific test file is failing and needs debugging, which requires the systematic approach of the
|
|
75
|
-
playwright-test-healer agent.
|
|
76
|
-
</commentary>
|
|
77
|
-
</example>
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: pwt-planner
|
|
3
3
|
description: Use this agent when you need to create comprehensive test plan for a web application or website
|
|
4
4
|
model: sonnet
|
|
5
5
|
color: green
|
|
6
6
|
tools:
|
|
7
|
-
- read
|
|
8
7
|
- search
|
|
9
|
-
-
|
|
8
|
+
- edit
|
|
10
9
|
- playwright-test/browser_click
|
|
11
10
|
- playwright-test/browser_close
|
|
12
11
|
- playwright-test/browser_console_messages
|
|
@@ -116,23 +115,3 @@ application features:
|
|
|
116
115
|
|
|
117
116
|
**Output Format**: Always save the complete test plan as a markdown file with clear headings, numbered steps, and
|
|
118
117
|
professional formatting suitable for sharing with development and QA teams.
|
|
119
|
-
|
|
120
|
-
<example>
|
|
121
|
-
Context: User wants to test a new e-commerce checkout flow.
|
|
122
|
-
user: 'I need test scenarios for our new checkout process at https://mystore.com/checkout'
|
|
123
|
-
assistant: 'I'll use the planner agent to navigate to your checkout page and create comprehensive test
|
|
124
|
-
scenarios.'
|
|
125
|
-
<commentary>
|
|
126
|
-
The user needs test planning for a specific web page, so use the planner agent to explore and create
|
|
127
|
-
test scenarios.
|
|
128
|
-
</commentary>
|
|
129
|
-
</example>
|
|
130
|
-
<example>
|
|
131
|
-
Context: User has deployed a new feature and wants thorough testing coverage.
|
|
132
|
-
user: 'Can you help me test our new user dashboard at https://app.example.com/dashboard?'
|
|
133
|
-
assistant: 'I'll launch the planner agent to explore your dashboard and develop detailed test
|
|
134
|
-
scenarios.'
|
|
135
|
-
<commentary>
|
|
136
|
-
This requires web exploration and test scenario creation, perfect for the planner agent.
|
|
137
|
-
</commentary>
|
|
138
|
-
</example>
|
package/lib/mcp/program.js
CHANGED
|
@@ -31,7 +31,9 @@ __export(program_exports, {
|
|
|
31
31
|
decorateCommand: () => decorateCommand
|
|
32
32
|
});
|
|
33
33
|
module.exports = __toCommonJS(program_exports);
|
|
34
|
+
var import_fs = __toESM(require("fs"));
|
|
34
35
|
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
36
|
+
var import_server = require("playwright-core/lib/server");
|
|
35
37
|
var mcpServer = __toESM(require("./sdk/server"));
|
|
36
38
|
var import_config = require("./browser/config");
|
|
37
39
|
var import_watchdog = require("./browser/watchdog");
|
|
@@ -47,6 +49,16 @@ function decorateCommand(command, version) {
|
|
|
47
49
|
options.caps = "vision";
|
|
48
50
|
}
|
|
49
51
|
const config = await (0, import_config.resolveCLIConfig)(options);
|
|
52
|
+
if (config.saveVideo && !checkFfmpeg()) {
|
|
53
|
+
console.error(import_utilsBundle.colors.red(`
|
|
54
|
+
Error: ffmpeg required to save the video is not installed.`));
|
|
55
|
+
console.error(`
|
|
56
|
+
Please run the command below. It will install a local copy of ffmpeg and will not change any system-wide settings.`);
|
|
57
|
+
console.error(`
|
|
58
|
+
npx playwright install ffmpeg
|
|
59
|
+
`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
50
62
|
const browserContextFactory = (0, import_browserContextFactory.contextFactory)(config);
|
|
51
63
|
const extensionContextFactory = new import_extensionContextFactory.ExtensionContextFactory(config.browser.launchOptions.channel || "chrome", config.browser.userDataDir, config.browser.launchOptions.executablePath);
|
|
52
64
|
if (options.extension) {
|
|
@@ -90,6 +102,14 @@ function decorateCommand(command, version) {
|
|
|
90
102
|
await mcpServer.start(factory, config.server);
|
|
91
103
|
});
|
|
92
104
|
}
|
|
105
|
+
function checkFfmpeg() {
|
|
106
|
+
try {
|
|
107
|
+
const executable = import_server.registry.findExecutable("ffmpeg");
|
|
108
|
+
return import_fs.default.existsSync(executable.executablePath("javascript"));
|
|
109
|
+
} catch (error) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
93
113
|
// Annotate the CommonJS export names for ESM import in node:
|
|
94
114
|
0 && (module.exports = {
|
|
95
115
|
decorateCommand
|
package/lib/program.js
CHANGED
|
@@ -179,22 +179,21 @@ function addInitAgentsCommand(program3) {
|
|
|
179
179
|
const command = program3.command("init-agents");
|
|
180
180
|
command.description("Initialize repository agents");
|
|
181
181
|
const option = command.createOption("--loop <loop>", "Agentic loop provider");
|
|
182
|
-
option.choices(["
|
|
182
|
+
option.choices(["claude", "copilot", "opencode", "vscode", "vscode-legacy"]);
|
|
183
183
|
command.addOption(option);
|
|
184
184
|
command.option("-c, --config <file>", `Configuration file to find a project to use for seed test`);
|
|
185
185
|
command.option("--project <project>", "Project to use for seed test");
|
|
186
|
+
command.option("--prompts", "Whether to include prompts in the agent initialization");
|
|
186
187
|
command.action(async (opts) => {
|
|
187
188
|
const config = await (0, import_configLoader.loadConfigFromFile)(opts.config);
|
|
188
189
|
if (opts.loop === "opencode") {
|
|
189
|
-
await import_generateAgents.OpencodeGenerator.init(config, opts.project);
|
|
190
|
-
} else if (opts.loop === "vscode") {
|
|
190
|
+
await import_generateAgents.OpencodeGenerator.init(config, opts.project, opts.prompts);
|
|
191
|
+
} else if (opts.loop === "vscode-legacy") {
|
|
191
192
|
await import_generateAgents.VSCodeGenerator.init(config, opts.project);
|
|
192
193
|
} else if (opts.loop === "claude") {
|
|
193
|
-
await import_generateAgents.ClaudeGenerator.init(config, opts.project);
|
|
194
|
-
} else if (opts.loop === "generic") {
|
|
195
|
-
await import_generateAgents.AgentGenerator.init(config, opts.project);
|
|
194
|
+
await import_generateAgents.ClaudeGenerator.init(config, opts.project, opts.prompts);
|
|
196
195
|
} else {
|
|
197
|
-
|
|
196
|
+
await import_generateAgents.CopilotGenerator.init(config, opts.project, opts.prompts);
|
|
198
197
|
return;
|
|
199
198
|
}
|
|
200
199
|
});
|
package/lib/runner/testRunner.js
CHANGED
|
@@ -93,7 +93,7 @@ class TestRunner extends import_events.default {
|
|
|
93
93
|
}
|
|
94
94
|
async installBrowsers() {
|
|
95
95
|
const executables = import_server.registry.defaultExecutables();
|
|
96
|
-
await import_server.registry.install(executables
|
|
96
|
+
await import_server.registry.install(executables);
|
|
97
97
|
}
|
|
98
98
|
async loadConfig() {
|
|
99
99
|
const { config, error } = await this._loadConfig(this._configCLIOverrides);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "playwright",
|
|
3
|
-
"version": "1.57.0-alpha-2025-10-
|
|
3
|
+
"version": "1.57.0-alpha-2025-10-29",
|
|
4
4
|
"description": "A high-level API to automate web browsers",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
},
|
|
65
65
|
"license": "Apache-2.0",
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"playwright-core": "1.57.0-alpha-2025-10-
|
|
67
|
+
"playwright-core": "1.57.0-alpha-2025-10-29"
|
|
68
68
|
},
|
|
69
69
|
"optionalDependencies": {
|
|
70
70
|
"fsevents": "2.3.2"
|