playwright 1.56.1 → 1.57.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.
Files changed (76) hide show
  1. package/README.md +3 -3
  2. package/ThirdPartyNotices.txt +202 -282
  3. package/lib/agents/copilot-setup-steps.yml +34 -0
  4. package/lib/agents/generateAgents.js +292 -160
  5. package/lib/agents/playwright-test-coverage.prompt.md +31 -0
  6. package/lib/agents/playwright-test-generate.prompt.md +8 -0
  7. package/lib/agents/{generator.md → playwright-test-generator.agent.md} +8 -22
  8. package/lib/agents/playwright-test-heal.prompt.md +6 -0
  9. package/lib/agents/{healer.md → playwright-test-healer.agent.md} +5 -28
  10. package/lib/agents/playwright-test-plan.prompt.md +9 -0
  11. package/lib/agents/{planner.md → playwright-test-planner.agent.md} +5 -68
  12. package/lib/common/config.js +6 -0
  13. package/lib/common/expectBundle.js +0 -9
  14. package/lib/common/expectBundleImpl.js +267 -249
  15. package/lib/common/testLoader.js +3 -2
  16. package/lib/common/testType.js +3 -12
  17. package/lib/common/validators.js +68 -0
  18. package/lib/index.js +9 -9
  19. package/lib/isomorphic/teleReceiver.js +1 -0
  20. package/lib/isomorphic/testServerConnection.js +14 -0
  21. package/lib/loader/loaderMain.js +1 -1
  22. package/lib/matchers/expect.js +12 -13
  23. package/lib/matchers/matchers.js +16 -0
  24. package/lib/mcp/browser/browserServerBackend.js +1 -1
  25. package/lib/mcp/browser/config.js +9 -24
  26. package/lib/mcp/browser/context.js +4 -21
  27. package/lib/mcp/browser/response.js +20 -11
  28. package/lib/mcp/browser/tab.js +25 -10
  29. package/lib/mcp/browser/tools/evaluate.js +2 -3
  30. package/lib/mcp/browser/tools/form.js +2 -3
  31. package/lib/mcp/browser/tools/keyboard.js +4 -5
  32. package/lib/mcp/browser/tools/pdf.js +1 -1
  33. package/lib/mcp/browser/tools/runCode.js +75 -0
  34. package/lib/mcp/browser/tools/screenshot.js +33 -15
  35. package/lib/mcp/browser/tools/snapshot.js +13 -14
  36. package/lib/mcp/browser/tools/tabs.js +2 -2
  37. package/lib/mcp/browser/tools/utils.js +0 -11
  38. package/lib/mcp/browser/tools/verify.js +3 -4
  39. package/lib/mcp/browser/tools.js +2 -0
  40. package/lib/mcp/program.js +21 -1
  41. package/lib/mcp/sdk/exports.js +1 -3
  42. package/lib/mcp/sdk/http.js +9 -2
  43. package/lib/mcp/sdk/proxyBackend.js +1 -1
  44. package/lib/mcp/sdk/server.js +13 -5
  45. package/lib/mcp/sdk/tool.js +2 -6
  46. package/lib/mcp/test/browserBackend.js +43 -33
  47. package/lib/mcp/test/generatorTools.js +3 -3
  48. package/lib/mcp/test/plannerTools.js +103 -5
  49. package/lib/mcp/test/seed.js +25 -15
  50. package/lib/mcp/test/streams.js +9 -4
  51. package/lib/mcp/test/testBackend.js +31 -29
  52. package/lib/mcp/test/testContext.js +143 -40
  53. package/lib/mcp/test/testTools.js +12 -21
  54. package/lib/plugins/webServerPlugin.js +37 -9
  55. package/lib/program.js +11 -20
  56. package/lib/reporters/html.js +2 -23
  57. package/lib/reporters/internalReporter.js +4 -2
  58. package/lib/reporters/junit.js +4 -2
  59. package/lib/reporters/list.js +1 -5
  60. package/lib/reporters/merge.js +12 -6
  61. package/lib/reporters/teleEmitter.js +3 -1
  62. package/lib/runner/dispatcher.js +26 -2
  63. package/lib/runner/failureTracker.js +5 -5
  64. package/lib/runner/loadUtils.js +2 -1
  65. package/lib/runner/loaderHost.js +1 -1
  66. package/lib/runner/reporters.js +5 -4
  67. package/lib/runner/testRunner.js +8 -9
  68. package/lib/runner/testServer.js +8 -3
  69. package/lib/runner/workerHost.js +3 -0
  70. package/lib/worker/testInfo.js +28 -17
  71. package/lib/worker/testTracing.js +1 -0
  72. package/lib/worker/workerMain.js +15 -6
  73. package/package.json +2 -2
  74. package/types/test.d.ts +96 -3
  75. package/types/testReporter.d.ts +5 -0
  76. package/lib/mcp/sdk/mdb.js +0 -208
@@ -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,18 +28,25 @@ 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
- initClaudeCodeRepo: () => initClaudeCodeRepo,
32
- initOpencodeRepo: () => initOpencodeRepo,
33
- initVSCodeRepo: () => initVSCodeRepo
31
+ ClaudeGenerator: () => ClaudeGenerator,
32
+ CopilotGenerator: () => CopilotGenerator,
33
+ OpencodeGenerator: () => OpencodeGenerator,
34
+ VSCodeGenerator: () => VSCodeGenerator
34
35
  });
35
36
  module.exports = __toCommonJS(generateAgents_exports);
36
37
  var import_fs = __toESM(require("fs"));
37
38
  var import_path = __toESM(require("path"));
38
39
  var import_utilsBundle = require("playwright-core/lib/utilsBundle");
40
+ var import_utils = require("playwright-core/lib/utils");
41
+ var import_seed = require("../mcp/test/seed");
39
42
  class AgentParser {
43
+ static async loadAgents() {
44
+ const files = await import_fs.default.promises.readdir(__dirname);
45
+ return Promise.all(files.filter((file) => file.endsWith(".agent.md")).map((file) => AgentParser.parseFile(import_path.default.join(__dirname, file))));
46
+ }
40
47
  static async parseFile(filePath) {
41
- const rawMarkdown = await import_fs.default.promises.readFile(filePath, "utf-8");
42
- const { header, content } = this.extractYamlAndContent(rawMarkdown);
48
+ const source = await import_fs.default.promises.readFile(filePath, "utf-8");
49
+ const { header, content } = this.extractYamlAndContent(source);
43
50
  const { instructions, examples } = this.extractInstructionsAndExamples(content);
44
51
  return { header, instructions, examples };
45
52
  }
@@ -84,180 +91,305 @@ class AgentParser {
84
91
  return { instructions, examples };
85
92
  }
86
93
  }
87
- const claudeToolMap = /* @__PURE__ */ new Map([
88
- ["ls", ["Glob"]],
89
- ["grep", ["Grep"]],
90
- ["read", ["Read"]],
91
- ["edit", ["Edit", "MultiEdit"]],
92
- ["write", ["Write"]]
93
- ]);
94
- const commonMcpServers = {
95
- playwrightTest: {
96
- type: "local",
97
- command: "npx",
98
- args: ["playwright", "run-test-mcp-server"]
94
+ class ClaudeGenerator {
95
+ static async init(config, projectName, prompts) {
96
+ await initRepo(config, projectName, {
97
+ promptsFolder: prompts ? ".claude/prompts" : void 0
98
+ });
99
+ const agents = await AgentParser.loadAgents();
100
+ await import_fs.default.promises.mkdir(".claude/agents", { recursive: true });
101
+ for (const agent of agents)
102
+ await writeFile(`.claude/agents/${agent.header.name}.md`, ClaudeGenerator.agentSpec(agent), "\u{1F916}", "agent definition");
103
+ await writeFile(".mcp.json", JSON.stringify({
104
+ mcpServers: {
105
+ "playwright-test": {
106
+ command: "npx",
107
+ args: ["playwright", "run-test-mcp-server"]
108
+ }
109
+ }
110
+ }, null, 2), "\u{1F527}", "mcp configuration");
111
+ initRepoDone();
99
112
  }
100
- };
101
- function saveAsClaudeCode(agent) {
102
- function asClaudeTool(tool) {
103
- const [first, second] = tool.split("/");
104
- if (!second)
105
- return (claudeToolMap.get(first) || [first]).join(", ");
106
- return `mcp__${first}__${second}`;
113
+ static agentSpec(agent) {
114
+ const claudeToolMap = /* @__PURE__ */ new Map([
115
+ ["search", ["Glob", "Grep", "Read", "LS"]],
116
+ ["edit", ["Edit", "MultiEdit", "Write"]]
117
+ ]);
118
+ function asClaudeTool(tool) {
119
+ const [first, second] = tool.split("/");
120
+ if (!second)
121
+ return (claudeToolMap.get(first) || [first]).join(", ");
122
+ return `mcp__${first}__${second}`;
123
+ }
124
+ const examples = agent.examples.length ? ` Examples: ${agent.examples.map((example) => `<example>${example}</example>`).join("")}` : "";
125
+ const lines = [];
126
+ const header = {
127
+ name: agent.header.name,
128
+ description: agent.header.description + examples,
129
+ tools: agent.header.tools.map((tool) => asClaudeTool(tool)).join(", "),
130
+ model: agent.header.model,
131
+ color: agent.header.color
132
+ };
133
+ lines.push(`---`);
134
+ lines.push(import_utilsBundle.yaml.stringify(header, { lineWidth: 1e5 }) + `---`);
135
+ lines.push("");
136
+ lines.push(agent.instructions);
137
+ return lines.join("\n");
107
138
  }
108
- const lines = [];
109
- lines.push(`---`);
110
- lines.push(`name: playwright-test-${agent.header.name}`);
111
- lines.push(`description: ${agent.header.description}. Examples: ${agent.examples.map((example) => `<example>${example}</example>`).join("")}`);
112
- lines.push(`tools: ${agent.header.tools.map((tool) => asClaudeTool(tool)).join(", ")}`);
113
- lines.push(`model: ${agent.header.model}`);
114
- lines.push(`color: ${agent.header.color}`);
115
- lines.push(`---`);
116
- lines.push("");
117
- lines.push(agent.instructions);
118
- return lines.join("\n");
119
139
  }
120
- const opencodeToolMap = /* @__PURE__ */ new Map([
121
- ["ls", ["ls", "glob"]],
122
- ["grep", ["grep"]],
123
- ["read", ["read"]],
124
- ["edit", ["edit"]],
125
- ["write", ["write"]]
126
- ]);
127
- function saveAsOpencodeJson(agents) {
128
- function asOpencodeTool(tools, tool) {
129
- const [first, second] = tool.split("/");
130
- if (!second) {
131
- for (const tool2 of opencodeToolMap.get(first) || [first])
132
- tools[tool2] = true;
133
- } else {
134
- tools[`${first}*${second}`] = true;
140
+ class OpencodeGenerator {
141
+ static async init(config, projectName, prompts) {
142
+ await initRepo(config, projectName, {
143
+ defaultAgentName: "Build",
144
+ promptsFolder: prompts ? ".opencode/prompts" : void 0
145
+ });
146
+ const agents = await AgentParser.loadAgents();
147
+ for (const agent of agents) {
148
+ const prompt = [agent.instructions];
149
+ prompt.push("");
150
+ prompt.push(...agent.examples.map((example) => `<example>${example}</example>`));
151
+ await writeFile(`.opencode/prompts/${agent.header.name}.md`, prompt.join("\n"), "\u{1F916}", "agent definition");
135
152
  }
153
+ await writeFile("opencode.json", OpencodeGenerator.configuration(agents), "\u{1F527}", "opencode configuration");
154
+ initRepoDone();
136
155
  }
137
- const result = {};
138
- result["$schema"] = "https://opencode.ai/config.json";
139
- result["mcp"] = {};
140
- result["tools"] = {
141
- "playwright*": false
142
- };
143
- result["agent"] = {};
144
- for (const agent of agents) {
145
- const tools = {};
146
- result["agent"]["playwright-test-" + agent.header.name] = {
147
- description: agent.header.description,
148
- mode: "subagent",
149
- prompt: `{file:.opencode/prompts/playwright-test-${agent.header.name}.md}`,
150
- tools
156
+ static configuration(agents) {
157
+ const opencodeToolMap = /* @__PURE__ */ new Map([
158
+ ["search", ["ls", "glob", "grep", "read"]],
159
+ ["edit", ["edit", "write"]]
160
+ ]);
161
+ const asOpencodeTool = (tools, tool) => {
162
+ const [first, second] = tool.split("/");
163
+ if (!second) {
164
+ for (const tool2 of opencodeToolMap.get(first) || [first])
165
+ tools[tool2] = true;
166
+ } else {
167
+ tools[`${first}*${second}`] = true;
168
+ }
169
+ };
170
+ const result = {};
171
+ result["$schema"] = "https://opencode.ai/config.json";
172
+ result["mcp"] = {};
173
+ result["tools"] = {
174
+ "playwright*": false
175
+ };
176
+ result["agent"] = {};
177
+ for (const agent of agents) {
178
+ const tools = {};
179
+ result["agent"][agent.header.name] = {
180
+ description: agent.header.description,
181
+ mode: "subagent",
182
+ prompt: `{file:.opencode/prompts/${agent.header.name}.md}`,
183
+ tools
184
+ };
185
+ for (const tool of agent.header.tools)
186
+ asOpencodeTool(tools, tool);
187
+ }
188
+ result["mcp"]["playwright-test"] = {
189
+ type: "local",
190
+ command: ["npx", "playwright", "run-test-mcp-server"],
191
+ enabled: true
151
192
  };
152
- for (const tool of agent.header.tools)
153
- asOpencodeTool(tools, tool);
193
+ return JSON.stringify(result, null, 2);
154
194
  }
155
- const server = commonMcpServers.playwrightTest;
156
- result["mcp"]["playwright-test"] = {
157
- type: server.type,
158
- command: [server.command, ...server.args],
159
- enabled: true
160
- };
161
- return JSON.stringify(result, null, 2);
162
195
  }
163
- async function loadAgents() {
164
- const files = await import_fs.default.promises.readdir(__dirname);
165
- return Promise.all(files.filter((file) => file.endsWith(".md")).map((file) => AgentParser.parseFile(import_path.default.join(__dirname, file))));
166
- }
167
- async function writeFile(filePath, content) {
168
- console.log(`Writing file: ${filePath}`);
169
- await import_fs.default.promises.writeFile(filePath, content, "utf-8");
170
- }
171
- async function initClaudeCodeRepo() {
172
- const agents = await loadAgents();
173
- await import_fs.default.promises.mkdir(".claude/agents", { recursive: true });
174
- for (const agent of agents)
175
- await writeFile(`.claude/agents/playwright-test-${agent.header.name}.md`, saveAsClaudeCode(agent));
176
- await writeFile(".mcp.json", JSON.stringify({
177
- mcpServers: {
196
+ class CopilotGenerator {
197
+ static async init(config, projectName, prompts) {
198
+ await initRepo(config, projectName, {
199
+ defaultAgentName: "agent",
200
+ promptsFolder: prompts ? ".github/prompts" : void 0,
201
+ promptSuffix: "prompt"
202
+ });
203
+ const agents = await AgentParser.loadAgents();
204
+ await import_fs.default.promises.mkdir(".github/agents", { recursive: true });
205
+ for (const agent of agents)
206
+ await writeFile(`.github/agents/${agent.header.name}.agent.md`, CopilotGenerator.agentSpec(agent), "\u{1F916}", "agent definition");
207
+ await deleteFile(`.github/chatmodes/ \u{1F3AD} planner.chatmode.md`, "legacy planner chatmode");
208
+ await deleteFile(`.github/chatmodes/\u{1F3AD} generator.chatmode.md`, "legacy generator chatmode");
209
+ await deleteFile(`.github/chatmodes/\u{1F3AD} healer.chatmode.md`, "legacy healer chatmode");
210
+ await deleteFile(`.github/agents/ \u{1F3AD} planner.agent.md`, "legacy planner agent");
211
+ await deleteFile(`.github/agents/\u{1F3AD} generator.agent.md`, "legacy generator agent");
212
+ await deleteFile(`.github/agents/\u{1F3AD} healer.agent.md`, "legacy healer agent");
213
+ await VSCodeGenerator.appendToMCPJson();
214
+ const mcpConfig = { mcpServers: CopilotGenerator.mcpServers };
215
+ if (!import_fs.default.existsSync(".github/copilot-setup-steps.yml")) {
216
+ const yaml2 = import_fs.default.readFileSync(import_path.default.join(__dirname, "copilot-setup-steps.yml"), "utf-8");
217
+ await writeFile(".github/workflows/copilot-setup-steps.yml", yaml2, "\u{1F527}", "GitHub Copilot setup steps");
218
+ }
219
+ console.log("");
220
+ console.log("");
221
+ console.log(" \u{1F527} TODO: GitHub > Settings > Copilot > Coding agent > MCP configuration");
222
+ console.log("------------------------------------------------------------------");
223
+ console.log(JSON.stringify(mcpConfig, null, 2));
224
+ console.log("------------------------------------------------------------------");
225
+ initRepoDone();
226
+ }
227
+ static agentSpec(agent) {
228
+ const examples = agent.examples.length ? ` Examples: ${agent.examples.map((example) => `<example>${example}</example>`).join("")}` : "";
229
+ const lines = [];
230
+ const header = {
231
+ "name": agent.header.name,
232
+ "description": agent.header.description + examples,
233
+ "tools": agent.header.tools,
234
+ "model": "Claude Sonnet 4",
235
+ "mcp-servers": CopilotGenerator.mcpServers
236
+ };
237
+ lines.push(`---`);
238
+ lines.push(import_utilsBundle.yaml.stringify(header) + `---`);
239
+ lines.push("");
240
+ lines.push(agent.instructions);
241
+ lines.push("");
242
+ return lines.join("\n");
243
+ }
244
+ static {
245
+ this.mcpServers = {
178
246
  "playwright-test": {
179
- command: commonMcpServers.playwrightTest.command,
180
- args: commonMcpServers.playwrightTest.args
247
+ "type": "stdio",
248
+ "command": "npx",
249
+ "args": [
250
+ "playwright",
251
+ "run-test-mcp-server"
252
+ ],
253
+ "tools": ["*"]
181
254
  }
182
- }
183
- }, null, 2));
255
+ };
256
+ }
184
257
  }
185
- const vscodeToolMap = /* @__PURE__ */ new Map([
186
- ["ls", ["search/listDirectory", "search/fileSearch"]],
187
- ["grep", ["search/textSearch"]],
188
- ["read", ["search/readFile"]],
189
- ["edit", ["edit/editFiles"]],
190
- ["write", ["edit/createFile", "edit/createDirectory"]]
191
- ]);
192
- const vscodeToolsOrder = ["edit/createFile", "edit/createDirectory", "edit/editFiles", "search/fileSearch", "search/textSearch", "search/listDirectory", "search/readFile"];
193
- const vscodeMcpName = "playwright-test";
194
- function saveAsVSCodeChatmode(agent) {
195
- function asVscodeTool(tool) {
196
- const [first, second] = tool.split("/");
197
- if (second)
198
- return `${vscodeMcpName}/${second}`;
199
- return vscodeToolMap.get(first) || first;
258
+ class VSCodeGenerator {
259
+ static async init(config, projectName) {
260
+ await initRepo(config, projectName, {
261
+ promptsFolder: void 0
262
+ });
263
+ const agents = await AgentParser.loadAgents();
264
+ const nameMap = /* @__PURE__ */ new Map([
265
+ ["playwright-test-planner", " \u{1F3AD} planner"],
266
+ ["playwright-test-generator", "\u{1F3AD} generator"],
267
+ ["playwright-test-healer", "\u{1F3AD} healer"]
268
+ ]);
269
+ await import_fs.default.promises.mkdir(".github/chatmodes", { recursive: true });
270
+ for (const agent of agents)
271
+ await writeFile(`.github/chatmodes/${nameMap.get(agent.header.name)}.chatmode.md`, VSCodeGenerator.agentSpec(agent), "\u{1F916}", "chatmode definition");
272
+ await VSCodeGenerator.appendToMCPJson();
273
+ initRepoDone();
274
+ }
275
+ static async appendToMCPJson() {
276
+ await import_fs.default.promises.mkdir(".vscode", { recursive: true });
277
+ const mcpJsonPath = ".vscode/mcp.json";
278
+ let mcpJson = {
279
+ servers: {},
280
+ inputs: []
281
+ };
282
+ try {
283
+ mcpJson = JSON.parse(import_fs.default.readFileSync(mcpJsonPath, "utf8"));
284
+ } catch {
285
+ }
286
+ if (!mcpJson.servers)
287
+ mcpJson.servers = {};
288
+ mcpJson.servers["playwright-test"] = {
289
+ type: "stdio",
290
+ command: "npx",
291
+ args: ["playwright", "run-test-mcp-server"]
292
+ };
293
+ await writeFile(mcpJsonPath, JSON.stringify(mcpJson, null, 2), "\u{1F527}", "mcp configuration");
294
+ }
295
+ static agentSpec(agent) {
296
+ const vscodeToolMap = /* @__PURE__ */ new Map([
297
+ ["search", ["search/listDirectory", "search/fileSearch", "search/textSearch"]],
298
+ ["read", ["search/readFile"]],
299
+ ["edit", ["edit/editFiles"]],
300
+ ["write", ["edit/createFile", "edit/createDirectory"]]
301
+ ]);
302
+ const vscodeToolsOrder = ["edit/createFile", "edit/createDirectory", "edit/editFiles", "search/fileSearch", "search/textSearch", "search/listDirectory", "search/readFile"];
303
+ const vscodeMcpName = "playwright-test";
304
+ function asVscodeTool(tool) {
305
+ const [first, second] = tool.split("/");
306
+ if (second)
307
+ return `${vscodeMcpName}/${second}`;
308
+ return vscodeToolMap.get(first) || first;
309
+ }
310
+ const tools = agent.header.tools.map(asVscodeTool).flat().sort((a, b) => {
311
+ const indexA = vscodeToolsOrder.indexOf(a);
312
+ const indexB = vscodeToolsOrder.indexOf(b);
313
+ if (indexA === -1 && indexB === -1)
314
+ return a.localeCompare(b);
315
+ if (indexA === -1)
316
+ return 1;
317
+ if (indexB === -1)
318
+ return -1;
319
+ return indexA - indexB;
320
+ }).map((tool) => `'${tool}'`).join(", ");
321
+ const lines = [];
322
+ lines.push(`---`);
323
+ lines.push(`description: ${agent.header.description}.`);
324
+ lines.push(`tools: [${tools}]`);
325
+ lines.push(`---`);
326
+ lines.push("");
327
+ lines.push(agent.instructions);
328
+ for (const example of agent.examples)
329
+ lines.push(`<example>${example}</example>`);
330
+ lines.push("");
331
+ return lines.join("\n");
200
332
  }
201
- const tools = agent.header.tools.map(asVscodeTool).flat().sort((a, b) => {
202
- const indexA = vscodeToolsOrder.indexOf(a);
203
- const indexB = vscodeToolsOrder.indexOf(b);
204
- if (indexA === -1 && indexB === -1)
205
- return a.localeCompare(b);
206
- if (indexA === -1)
207
- return 1;
208
- if (indexB === -1)
209
- return -1;
210
- return indexA - indexB;
211
- }).map((tool) => `'${tool}'`).join(", ");
212
- const lines = [];
213
- lines.push(`---`);
214
- lines.push(`description: ${agent.header.description}.`);
215
- lines.push(`tools: [${tools}]`);
216
- lines.push(`---`);
217
- lines.push("");
218
- lines.push(agent.instructions);
219
- for (const example of agent.examples)
220
- lines.push(`<example>${example}</example>`);
221
- return lines.join("\n");
222
333
  }
223
- async function initVSCodeRepo() {
224
- const agents = await loadAgents();
225
- await import_fs.default.promises.mkdir(".github/chatmodes", { recursive: true });
226
- for (const agent of agents)
227
- await writeFile(`.github/chatmodes/${agent.header.name === "planner" ? " " : ""}\u{1F3AD} ${agent.header.name}.chatmode.md`, saveAsVSCodeChatmode(agent));
228
- await import_fs.default.promises.mkdir(".vscode", { recursive: true });
229
- const mcpJsonPath = ".vscode/mcp.json";
230
- let mcpJson = {
231
- servers: {},
232
- inputs: []
233
- };
334
+ async function writeFile(filePath, content, icon, description) {
335
+ console.log(` ${icon} ${import_path.default.relative(process.cwd(), filePath)} ${import_utilsBundle.colors.dim("- " + description)}`);
336
+ await (0, import_utils.mkdirIfNeeded)(filePath);
337
+ await import_fs.default.promises.writeFile(filePath, content, "utf-8");
338
+ }
339
+ async function deleteFile(filePath, description) {
234
340
  try {
235
- mcpJson = JSON.parse(import_fs.default.readFileSync(mcpJsonPath, "utf8"));
341
+ if (!import_fs.default.existsSync(filePath))
342
+ return;
236
343
  } catch {
344
+ return;
237
345
  }
238
- if (!mcpJson.servers)
239
- mcpJson.servers = {};
240
- mcpJson.servers["playwright-test"] = {
241
- type: "stdio",
242
- command: commonMcpServers.playwrightTest.command,
243
- args: commonMcpServers.playwrightTest.args
244
- };
245
- await writeFile(mcpJsonPath, JSON.stringify(mcpJson, null, 2));
346
+ console.log(` \u2702\uFE0F ${import_path.default.relative(process.cwd(), filePath)} ${import_utilsBundle.colors.dim("- " + description)}`);
347
+ await import_fs.default.promises.unlink(filePath);
246
348
  }
247
- async function initOpencodeRepo() {
248
- const agents = await loadAgents();
249
- await import_fs.default.promises.mkdir(".opencode/prompts", { recursive: true });
250
- for (const agent of agents) {
251
- const prompt = [agent.instructions];
252
- prompt.push("");
253
- prompt.push(...agent.examples.map((example) => `<example>${example}</example>`));
254
- await writeFile(`.opencode/prompts/playwright-test-${agent.header.name}.md`, prompt.join("\n"));
349
+ async function initRepo(config, projectName, options) {
350
+ const project = (0, import_seed.seedProject)(config, projectName);
351
+ console.log(` \u{1F3AD} Using project "${project.project.name}" as a primary project`);
352
+ if (!import_fs.default.existsSync("specs")) {
353
+ await import_fs.default.promises.mkdir("specs");
354
+ await writeFile(import_path.default.join("specs", "README.md"), `# Specs
355
+
356
+ This is a directory for test plans.
357
+ `, "\u{1F4DD}", "directory for test plans");
358
+ }
359
+ let seedFile = await (0, import_seed.findSeedFile)(project);
360
+ if (!seedFile) {
361
+ seedFile = (0, import_seed.defaultSeedFile)(project);
362
+ await writeFile(seedFile, import_seed.seedFileContent, "\u{1F331}", "default environment seed file");
255
363
  }
256
- await writeFile("opencode.json", saveAsOpencodeJson(agents));
364
+ if (options.promptsFolder) {
365
+ await import_fs.default.promises.mkdir(options.promptsFolder, { recursive: true });
366
+ for (const promptFile of await import_fs.default.promises.readdir(__dirname)) {
367
+ if (!promptFile.endsWith(".prompt.md"))
368
+ continue;
369
+ const shortName = promptFile.replace(".prompt.md", "");
370
+ const fileName = options.promptSuffix ? `${shortName}.${options.promptSuffix}.md` : `${shortName}.md`;
371
+ const content = await loadPrompt(promptFile, {
372
+ defaultAgentName: "default",
373
+ ...options,
374
+ seedFile: import_path.default.relative(process.cwd(), seedFile)
375
+ });
376
+ await writeFile(import_path.default.join(options.promptsFolder, fileName), content, "\u{1F4DD}", "prompt template");
377
+ }
378
+ }
379
+ }
380
+ function initRepoDone() {
381
+ console.log(" \u2705 Done.");
382
+ }
383
+ async function loadPrompt(file, params) {
384
+ const content = await import_fs.default.promises.readFile(import_path.default.join(__dirname, file), "utf-8");
385
+ return Object.entries(params).reduce((acc, [key, value]) => {
386
+ return acc.replace(new RegExp(`\\\${${key}}`, "g"), value);
387
+ }, content);
257
388
  }
258
389
  // Annotate the CommonJS export names for ESM import in node:
259
390
  0 && (module.exports = {
260
- initClaudeCodeRepo,
261
- initOpencodeRepo,
262
- initVSCodeRepo
391
+ ClaudeGenerator,
392
+ CopilotGenerator,
393
+ OpencodeGenerator,
394
+ VSCodeGenerator
263
395
  });
@@ -0,0 +1,31 @@
1
+ ---
2
+ agent: ${defaultAgentName}
3
+ description: Produce test coverage
4
+ ---
5
+
6
+ Parameters:
7
+ - Task: the task to perform
8
+ - Seed file (optional): the seed file to use, defaults to `${seedFile}`
9
+ - Test plan file (optional): the test plan file to write, under `specs/` folder.
10
+
11
+ 1. Call #playwright-test-planner subagent with prompt:
12
+
13
+ <plan>
14
+ <task-text><!-- the task --></task-text>
15
+ <seed-file><!-- path to seed file --></seed-file>
16
+ <plan-file><!-- path to test plan file to generate --></plan-file>
17
+ </plan>
18
+
19
+ 2. For each test case from the test plan file (1.1, 1.2, ...), one after another, not in parallel, call #playwright-test-generator subagent with prompt:
20
+
21
+ <generate>
22
+ <test-suite><!-- Verbatim name of the test spec group w/o ordinal like "Multiplication tests" --></test-suite>
23
+ <test-name><!-- Name of the test case without the ordinal like "should add two numbers" --></test-name>
24
+ <test-file><!-- Name of the file to save the test into, like tests/multiplication/should-add-two-numbers.spec.ts --></test-file>
25
+ <seed-file><!-- Seed file path from test plan --></seed-file>
26
+ <body><!-- Test case content including steps and expectations --></body>
27
+ </generate>
28
+
29
+ 3. Call #playwright-test-healer subagent with prompt:
30
+
31
+ <heal>Run all tests and fix the failing ones one after another.</heal>
@@ -0,0 +1,8 @@
1
+ ---
2
+ agent: playwright-test-generator
3
+ description: Generate test plan
4
+ ---
5
+
6
+ Generate tests for the test plan's bullet 1.1 Add item to card.
7
+
8
+ Test plan: `specs/coverage.plan.md`
@@ -1,12 +1,10 @@
1
1
  ---
2
- name: generator
2
+ name: playwright-test-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
- - ls
8
- - grep
9
- - read
7
+ - search
10
8
  - playwright-test/browser_click
11
9
  - playwright-test/browser_drag
12
10
  - playwright-test/browser_evaluate
@@ -81,22 +79,10 @@ application behavior.
81
79
  </example-generation>
82
80
 
83
81
  <example>
84
- Context: User wants to test a login flow on their web application.
85
- user: 'I need a test that logs into my app at localhost:3000 with username admin@test.com and password 123456, then
86
- verifies the dashboard page loads'
87
- assistant: 'I'll use the generator agent to create and validate this login test for you'
88
- <commentary>
89
- The user needs a specific browser automation test created, which is exactly what the generator agent
90
- is designed for.
91
- </commentary>
92
- </example>
93
- <example>
94
- Context: User has built a new checkout flow and wants to ensure it works correctly.
95
- user: 'Can you create a test that adds items to cart, proceeds to checkout, fills in payment details, and confirms the
96
- order?'
97
- assistant: 'I'll use the generator agent to build a comprehensive checkout flow test'
98
- <commentary>
99
- This is a complex user journey that needs to be automated and tested, perfect for the generator
100
- agent.
101
- </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>
102
88
  </example>
@@ -0,0 +1,6 @@
1
+ ---
2
+ agent: playwright-test-healer
3
+ description: Fix tests
4
+ ---
5
+
6
+ Run all my tests and fix the failing ones.