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.
- package/README.md +3 -3
- package/ThirdPartyNotices.txt +202 -282
- package/lib/agents/copilot-setup-steps.yml +34 -0
- package/lib/agents/generateAgents.js +292 -160
- package/lib/agents/playwright-test-coverage.prompt.md +31 -0
- package/lib/agents/playwright-test-generate.prompt.md +8 -0
- package/lib/agents/{generator.md → playwright-test-generator.agent.md} +8 -22
- package/lib/agents/playwright-test-heal.prompt.md +6 -0
- package/lib/agents/{healer.md → playwright-test-healer.agent.md} +5 -28
- package/lib/agents/playwright-test-plan.prompt.md +9 -0
- package/lib/agents/{planner.md → playwright-test-planner.agent.md} +5 -68
- package/lib/common/config.js +6 -0
- package/lib/common/expectBundle.js +0 -9
- package/lib/common/expectBundleImpl.js +267 -249
- package/lib/common/testLoader.js +3 -2
- package/lib/common/testType.js +3 -12
- package/lib/common/validators.js +68 -0
- package/lib/index.js +9 -9
- package/lib/isomorphic/teleReceiver.js +1 -0
- package/lib/isomorphic/testServerConnection.js +14 -0
- package/lib/loader/loaderMain.js +1 -1
- package/lib/matchers/expect.js +12 -13
- package/lib/matchers/matchers.js +16 -0
- package/lib/mcp/browser/browserServerBackend.js +1 -1
- package/lib/mcp/browser/config.js +9 -24
- package/lib/mcp/browser/context.js +4 -21
- package/lib/mcp/browser/response.js +20 -11
- package/lib/mcp/browser/tab.js +25 -10
- package/lib/mcp/browser/tools/evaluate.js +2 -3
- package/lib/mcp/browser/tools/form.js +2 -3
- package/lib/mcp/browser/tools/keyboard.js +4 -5
- package/lib/mcp/browser/tools/pdf.js +1 -1
- package/lib/mcp/browser/tools/runCode.js +75 -0
- package/lib/mcp/browser/tools/screenshot.js +33 -15
- package/lib/mcp/browser/tools/snapshot.js +13 -14
- package/lib/mcp/browser/tools/tabs.js +2 -2
- package/lib/mcp/browser/tools/utils.js +0 -11
- package/lib/mcp/browser/tools/verify.js +3 -4
- package/lib/mcp/browser/tools.js +2 -0
- package/lib/mcp/program.js +21 -1
- package/lib/mcp/sdk/exports.js +1 -3
- package/lib/mcp/sdk/http.js +9 -2
- package/lib/mcp/sdk/proxyBackend.js +1 -1
- package/lib/mcp/sdk/server.js +13 -5
- package/lib/mcp/sdk/tool.js +2 -6
- package/lib/mcp/test/browserBackend.js +43 -33
- package/lib/mcp/test/generatorTools.js +3 -3
- package/lib/mcp/test/plannerTools.js +103 -5
- package/lib/mcp/test/seed.js +25 -15
- package/lib/mcp/test/streams.js +9 -4
- package/lib/mcp/test/testBackend.js +31 -29
- package/lib/mcp/test/testContext.js +143 -40
- package/lib/mcp/test/testTools.js +12 -21
- package/lib/plugins/webServerPlugin.js +37 -9
- package/lib/program.js +11 -20
- package/lib/reporters/html.js +2 -23
- package/lib/reporters/internalReporter.js +4 -2
- package/lib/reporters/junit.js +4 -2
- package/lib/reporters/list.js +1 -5
- package/lib/reporters/merge.js +12 -6
- package/lib/reporters/teleEmitter.js +3 -1
- package/lib/runner/dispatcher.js +26 -2
- package/lib/runner/failureTracker.js +5 -5
- package/lib/runner/loadUtils.js +2 -1
- package/lib/runner/loaderHost.js +1 -1
- package/lib/runner/reporters.js +5 -4
- package/lib/runner/testRunner.js +8 -9
- package/lib/runner/testServer.js +8 -3
- package/lib/runner/workerHost.js +3 -0
- package/lib/worker/testInfo.js +28 -17
- package/lib/worker/testTracing.js +1 -0
- package/lib/worker/workerMain.js +15 -6
- package/package.json +2 -2
- package/types/test.d.ts +96 -3
- package/types/testReporter.d.ts +5 -0
- 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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
42
|
-
const { header, content } = this.extractYamlAndContent(
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
await
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
|
|
180
|
-
|
|
247
|
+
"type": "stdio",
|
|
248
|
+
"command": "npx",
|
|
249
|
+
"args": [
|
|
250
|
+
"playwright",
|
|
251
|
+
"run-test-mcp-server"
|
|
252
|
+
],
|
|
253
|
+
"tools": ["*"]
|
|
181
254
|
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
255
|
+
};
|
|
256
|
+
}
|
|
184
257
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
|
224
|
-
|
|
225
|
-
await
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
341
|
+
if (!import_fs.default.existsSync(filePath))
|
|
342
|
+
return;
|
|
236
343
|
} catch {
|
|
344
|
+
return;
|
|
237
345
|
}
|
|
238
|
-
|
|
239
|
-
|
|
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
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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>
|
|
@@ -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
|
-
-
|
|
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
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
<
|
|
89
|
-
|
|
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>
|