playwright 1.57.0-alpha-2025-10-15 → 1.57.0-alpha-2025-10-17
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/generateAgents.js +267 -167
- package/lib/agents/generator.md +2 -3
- package/lib/agents/healer.md +5 -6
- package/lib/agents/planner.md +3 -4
- package/lib/mcp/browser/tools/screenshot.js +1 -1
- package/lib/mcp/test/seed.js +25 -15
- package/lib/mcp/test/testContext.js +1 -1
- package/lib/program.js +7 -8
- package/package.json +2 -2
|
@@ -28,20 +28,27 @@ 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
|
+
AgentGenerator: () => AgentGenerator,
|
|
32
|
+
ClaudeGenerator: () => ClaudeGenerator,
|
|
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(".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
|
-
return { header, instructions, examples };
|
|
51
|
+
return { source, header, instructions, examples };
|
|
45
52
|
}
|
|
46
53
|
static extractYamlAndContent(markdown) {
|
|
47
54
|
const lines = markdown.split("\n");
|
|
@@ -84,182 +91,275 @@ class AgentParser {
|
|
|
84
91
|
return { instructions, examples };
|
|
85
92
|
}
|
|
86
93
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
class ClaudeGenerator {
|
|
95
|
+
static async init(config, projectName) {
|
|
96
|
+
await initRepo(config, projectName);
|
|
97
|
+
const agents = await AgentParser.loadAgents();
|
|
98
|
+
await import_fs.default.promises.mkdir(".claude/agents", { recursive: true });
|
|
99
|
+
for (const agent of agents)
|
|
100
|
+
await writeFile(`.claude/agents/${agent.header.name}.md`, ClaudeGenerator.agentSpec(agent), "\u{1F916}", "agent definition");
|
|
101
|
+
await writeFile(".mcp.json", JSON.stringify({
|
|
102
|
+
mcpServers: {
|
|
103
|
+
"playwright-test": {
|
|
104
|
+
command: "npx",
|
|
105
|
+
args: ["playwright", "run-test-mcp-server"]
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}, null, 2), "\u{1F527}", "mcp configuration");
|
|
109
|
+
initRepoDone();
|
|
99
110
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
111
|
+
static agentSpec(agent) {
|
|
112
|
+
const claudeToolMap = /* @__PURE__ */ new Map([
|
|
113
|
+
["search", ["Glob", "Grep"]],
|
|
114
|
+
["read", ["Read"]],
|
|
115
|
+
["edit", ["Edit", "MultiEdit"]],
|
|
116
|
+
["write", ["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 lines = [];
|
|
125
|
+
lines.push(`---`);
|
|
126
|
+
lines.push(`name: ${agent.header.name}`);
|
|
127
|
+
lines.push(`description: ${agent.header.description}. Examples: ${agent.examples.map((example) => `<example>${example}</example>`).join("")}`);
|
|
128
|
+
lines.push(`tools: ${agent.header.tools.map((tool) => asClaudeTool(tool)).join(", ")}`);
|
|
129
|
+
lines.push(`model: ${agent.header.model}`);
|
|
130
|
+
lines.push(`color: ${agent.header.color}`);
|
|
131
|
+
lines.push(`---`);
|
|
132
|
+
lines.push("");
|
|
133
|
+
lines.push(agent.instructions);
|
|
134
|
+
return lines.join("\n");
|
|
107
135
|
}
|
|
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
136
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
]
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (!second) {
|
|
131
|
-
for (const tool2 of opencodeToolMap.get(first) || [first])
|
|
132
|
-
tools[tool2] = true;
|
|
133
|
-
} else {
|
|
134
|
-
tools[`${first}*${second}`] = true;
|
|
137
|
+
class OpencodeGenerator {
|
|
138
|
+
static async init(config, projectName) {
|
|
139
|
+
await initRepo(config, projectName);
|
|
140
|
+
const agents = await AgentParser.loadAgents();
|
|
141
|
+
await import_fs.default.promises.mkdir(".opencode/prompts", { recursive: true });
|
|
142
|
+
for (const agent of agents) {
|
|
143
|
+
const prompt = [agent.instructions];
|
|
144
|
+
prompt.push("");
|
|
145
|
+
prompt.push(...agent.examples.map((example) => `<example>${example}</example>`));
|
|
146
|
+
await writeFile(`.opencode/prompts/${agent.header.name}.md`, prompt.join("\n"), "\u{1F916}", "agent definition");
|
|
135
147
|
}
|
|
148
|
+
await writeFile("opencode.json", OpencodeGenerator.configuration(agents), "\u{1F527}", "opencode configuration");
|
|
149
|
+
initRepoDone();
|
|
136
150
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
+
static configuration(agents) {
|
|
152
|
+
const opencodeToolMap = /* @__PURE__ */ new Map([
|
|
153
|
+
["search", ["ls", "glob", "grep"]],
|
|
154
|
+
["read", ["read"]],
|
|
155
|
+
["edit", ["edit"]],
|
|
156
|
+
["write", ["write"]]
|
|
157
|
+
]);
|
|
158
|
+
const asOpencodeTool = (tools, tool) => {
|
|
159
|
+
const [first, second] = tool.split("/");
|
|
160
|
+
if (!second) {
|
|
161
|
+
for (const tool2 of opencodeToolMap.get(first) || [first])
|
|
162
|
+
tools[tool2] = true;
|
|
163
|
+
} else {
|
|
164
|
+
tools[`${first}*${second}`] = true;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
const result = {};
|
|
168
|
+
result["$schema"] = "https://opencode.ai/config.json";
|
|
169
|
+
result["mcp"] = {};
|
|
170
|
+
result["tools"] = {
|
|
171
|
+
"playwright*": false
|
|
151
172
|
};
|
|
152
|
-
|
|
153
|
-
|
|
173
|
+
result["agent"] = {};
|
|
174
|
+
for (const agent of agents) {
|
|
175
|
+
const tools = {};
|
|
176
|
+
result["agent"][agent.header.name] = {
|
|
177
|
+
description: agent.header.description,
|
|
178
|
+
mode: "subagent",
|
|
179
|
+
prompt: `{file:.opencode/prompts/${agent.header.name}.md}`,
|
|
180
|
+
tools
|
|
181
|
+
};
|
|
182
|
+
for (const tool of agent.header.tools)
|
|
183
|
+
asOpencodeTool(tools, tool);
|
|
184
|
+
}
|
|
185
|
+
result["mcp"]["playwright-test"] = {
|
|
186
|
+
type: "local",
|
|
187
|
+
command: ["npx", "playwright", "run-test-mcp-server"],
|
|
188
|
+
enabled: true
|
|
189
|
+
};
|
|
190
|
+
return JSON.stringify(result, null, 2);
|
|
154
191
|
}
|
|
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
|
-
}
|
|
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
192
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
mcpServers: {
|
|
178
|
-
"playwright-test": {
|
|
179
|
-
command: commonMcpServers.playwrightTest.command,
|
|
180
|
-
args: commonMcpServers.playwrightTest.args
|
|
181
|
-
}
|
|
193
|
+
class AgentGenerator {
|
|
194
|
+
static async init(config, projectName) {
|
|
195
|
+
const agentsFolder = process.env.AGENTS_FOLDER;
|
|
196
|
+
if (!agentsFolder) {
|
|
197
|
+
console.error("AGENTS_FOLDER environment variable is not set");
|
|
198
|
+
return;
|
|
182
199
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
+
await initRepo(config, projectName);
|
|
201
|
+
const agents = await AgentParser.loadAgents();
|
|
202
|
+
await import_fs.default.promises.mkdir(agentsFolder, { recursive: true });
|
|
203
|
+
for (const agent of agents)
|
|
204
|
+
await writeFile(`${agentsFolder}/${agent.header.name}.md`, agent.source, "\u{1F916}", "agent definition");
|
|
205
|
+
console.log("\u{1F527} MCP configuration");
|
|
206
|
+
console.log(JSON.stringify({
|
|
207
|
+
mcpServers: {
|
|
208
|
+
"playwright-test": {
|
|
209
|
+
type: "stdio",
|
|
210
|
+
command: "npx",
|
|
211
|
+
args: [
|
|
212
|
+
`--prefix=${import_path.default.resolve(process.cwd())}`,
|
|
213
|
+
"playwright",
|
|
214
|
+
"run-test-mcp-server",
|
|
215
|
+
`--headless`,
|
|
216
|
+
`--config=${import_path.default.resolve(process.cwd())}`
|
|
217
|
+
],
|
|
218
|
+
tools: ["*"]
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}, null, 2));
|
|
222
|
+
initRepoDone();
|
|
200
223
|
}
|
|
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
224
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
225
|
+
class VSCodeGenerator {
|
|
226
|
+
static async init(config, projectName) {
|
|
227
|
+
await initRepo(config, projectName);
|
|
228
|
+
const agents = await AgentParser.loadAgents();
|
|
229
|
+
const nameMap = /* @__PURE__ */ new Map([
|
|
230
|
+
["playwright-test-planner", " \u{1F3AD} planner"],
|
|
231
|
+
["playwright-test-generator", "\u{1F3AD} generator"],
|
|
232
|
+
["playwright-test-healer", "\u{1F3AD} healer"]
|
|
233
|
+
]);
|
|
234
|
+
await import_fs.default.promises.mkdir(".github/chatmodes", { recursive: true });
|
|
235
|
+
for (const agent of agents)
|
|
236
|
+
await writeFile(`.github/chatmodes/${nameMap.get(agent.header.name)}.chatmode.md`, VSCodeGenerator.agentSpec(agent), "\u{1F916}", "chatmode definition");
|
|
237
|
+
await import_fs.default.promises.mkdir(".vscode", { recursive: true });
|
|
238
|
+
const mcpJsonPath = ".vscode/mcp.json";
|
|
239
|
+
let mcpJson = {
|
|
240
|
+
servers: {},
|
|
241
|
+
inputs: []
|
|
242
|
+
};
|
|
243
|
+
try {
|
|
244
|
+
mcpJson = JSON.parse(import_fs.default.readFileSync(mcpJsonPath, "utf8"));
|
|
245
|
+
} catch {
|
|
246
|
+
}
|
|
247
|
+
if (!mcpJson.servers)
|
|
248
|
+
mcpJson.servers = {};
|
|
249
|
+
mcpJson.servers["playwright-test"] = {
|
|
250
|
+
type: "stdio",
|
|
251
|
+
command: "npx",
|
|
252
|
+
args: ["playwright", "run-test-mcp-server"]
|
|
253
|
+
};
|
|
254
|
+
await writeFile(mcpJsonPath, JSON.stringify(mcpJson, null, 2), "\u{1F527}", "mcp configuration");
|
|
255
|
+
initRepoDone();
|
|
256
|
+
}
|
|
257
|
+
static agentSpec(agent) {
|
|
258
|
+
const vscodeToolMap = /* @__PURE__ */ new Map([
|
|
259
|
+
["search", ["search/listDirectory", "search/fileSearch", "search/textSearch"]],
|
|
260
|
+
["read", ["search/readFile"]],
|
|
261
|
+
["edit", ["edit/editFiles"]],
|
|
262
|
+
["write", ["edit/createFile", "edit/createDirectory"]]
|
|
263
|
+
]);
|
|
264
|
+
const vscodeToolsOrder = ["edit/createFile", "edit/createDirectory", "edit/editFiles", "search/fileSearch", "search/textSearch", "search/listDirectory", "search/readFile"];
|
|
265
|
+
const vscodeMcpName = "playwright-test";
|
|
266
|
+
function asVscodeTool(tool) {
|
|
267
|
+
const [first, second] = tool.split("/");
|
|
268
|
+
if (second)
|
|
269
|
+
return `${vscodeMcpName}/${second}`;
|
|
270
|
+
return vscodeToolMap.get(first) || first;
|
|
271
|
+
}
|
|
272
|
+
const tools = agent.header.tools.map(asVscodeTool).flat().sort((a, b) => {
|
|
273
|
+
const indexA = vscodeToolsOrder.indexOf(a);
|
|
274
|
+
const indexB = vscodeToolsOrder.indexOf(b);
|
|
275
|
+
if (indexA === -1 && indexB === -1)
|
|
276
|
+
return a.localeCompare(b);
|
|
277
|
+
if (indexA === -1)
|
|
278
|
+
return 1;
|
|
279
|
+
if (indexB === -1)
|
|
280
|
+
return -1;
|
|
281
|
+
return indexA - indexB;
|
|
282
|
+
}).map((tool) => `'${tool}'`).join(", ");
|
|
283
|
+
const lines = [];
|
|
284
|
+
lines.push(`---`);
|
|
285
|
+
lines.push(`description: ${agent.header.description}.`);
|
|
286
|
+
lines.push(`tools: [${tools}]`);
|
|
287
|
+
lines.push(`---`);
|
|
288
|
+
lines.push("");
|
|
289
|
+
lines.push(agent.instructions);
|
|
290
|
+
for (const example of agent.examples)
|
|
291
|
+
lines.push(`<example>${example}</example>`);
|
|
292
|
+
return lines.join("\n");
|
|
237
293
|
}
|
|
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
|
-
cwd: "${workspaceFolder}"
|
|
245
|
-
};
|
|
246
|
-
await writeFile(mcpJsonPath, JSON.stringify(mcpJson, null, 2));
|
|
247
|
-
console.log(import_utilsBundle.colors.yellow(`${import_utilsBundle.colors.bold("Note:")} Playwright Test Agents require VSCode version 1.105+ or VSCode Insiders`));
|
|
248
294
|
}
|
|
249
|
-
async function
|
|
250
|
-
|
|
251
|
-
await
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
295
|
+
async function writeFile(filePath, content, icon, description) {
|
|
296
|
+
console.log(`- ${icon} ${import_path.default.relative(process.cwd(), filePath)} ${import_utilsBundle.colors.dim("- " + description)}`);
|
|
297
|
+
await (0, import_utils.mkdirIfNeeded)(filePath);
|
|
298
|
+
await import_fs.default.promises.writeFile(filePath, content, "utf-8");
|
|
299
|
+
}
|
|
300
|
+
async function initRepo(config, projectName) {
|
|
301
|
+
const project = (0, import_seed.seedProject)(config, projectName);
|
|
302
|
+
console.log(`\u{1F3AD} Using project "${project.project.name}" as a primary project`);
|
|
303
|
+
if (!import_fs.default.existsSync("specs")) {
|
|
304
|
+
await import_fs.default.promises.mkdir("specs");
|
|
305
|
+
await writeFile(import_path.default.join("specs", "README.md"), `# Specs
|
|
306
|
+
|
|
307
|
+
This is a directory for test plans.
|
|
308
|
+
`, "\u{1F4DD}", "directory for test plans");
|
|
257
309
|
}
|
|
258
|
-
|
|
310
|
+
if (!import_fs.default.existsSync("prompts")) {
|
|
311
|
+
await import_fs.default.promises.mkdir("prompts");
|
|
312
|
+
await writeFile(import_path.default.join("prompts", "README.md"), `# Prompts
|
|
313
|
+
|
|
314
|
+
This is a directory for useful prompts.
|
|
315
|
+
`, "\u{1F4DD}", "useful prompts");
|
|
316
|
+
}
|
|
317
|
+
let seedFile = await (0, import_seed.findSeedFile)(project);
|
|
318
|
+
if (!seedFile) {
|
|
319
|
+
seedFile = (0, import_seed.defaultSeedFile)(project);
|
|
320
|
+
await writeFile(seedFile, import_seed.seedFileContent, "\u{1F331}", "default environment seed file");
|
|
321
|
+
}
|
|
322
|
+
const coveragePromptFile = import_path.default.join("prompts", "test-coverage.md");
|
|
323
|
+
if (!import_fs.default.existsSync(coveragePromptFile))
|
|
324
|
+
await writeFile(coveragePromptFile, coveragePrompt(seedFile), "\u{1F4DD}", "test coverage prompt");
|
|
325
|
+
}
|
|
326
|
+
function initRepoDone() {
|
|
327
|
+
console.log("\u2705 Done.");
|
|
259
328
|
}
|
|
329
|
+
const coveragePrompt = (seedFile) => `
|
|
330
|
+
# Produce test coverage
|
|
331
|
+
|
|
332
|
+
Parameters:
|
|
333
|
+
- Task: the task to perform
|
|
334
|
+
- Seed file (optional): the seed file to use, defaults to ${import_path.default.relative(process.cwd(), seedFile)}
|
|
335
|
+
- Test plan file (optional): the test plan file to write, under specs/ folder.
|
|
336
|
+
|
|
337
|
+
1. Call #playwright-test-planner subagent with prompt:
|
|
338
|
+
|
|
339
|
+
<plan>
|
|
340
|
+
<task><!-- the task --></task>
|
|
341
|
+
<seed-file><!-- seed file param --></seed-file>
|
|
342
|
+
<plan-file><!-- test plan file --></plan-file>
|
|
343
|
+
</plan>
|
|
344
|
+
|
|
345
|
+
2. For each test case from the test plan file (1.1, 1.2, ...), Call #playwright-test-generator subagent with prompt:
|
|
346
|
+
|
|
347
|
+
<generate>
|
|
348
|
+
<test-file><!-- Name of the file to save the test into, should be unique for test --></test-file>
|
|
349
|
+
<test-suite><!-- Name of the top level test spec w/o ordinal--></test-suite>
|
|
350
|
+
<test-name><!--Name of the test case without the ordinal --></test-name>
|
|
351
|
+
<seed-file><!-- Seed file from test plan --></seed-file>
|
|
352
|
+
<body><!-- Test case content including steps and expectations --></body>
|
|
353
|
+
</generate>
|
|
354
|
+
|
|
355
|
+
3. Call #playwright-test-healer subagent with prompt:
|
|
356
|
+
|
|
357
|
+
<heal>Run all tests and fix the failing ones one after another.</heal>
|
|
358
|
+
`;
|
|
260
359
|
// Annotate the CommonJS export names for ESM import in node:
|
|
261
360
|
0 && (module.exports = {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
361
|
+
AgentGenerator,
|
|
362
|
+
ClaudeGenerator,
|
|
363
|
+
OpencodeGenerator,
|
|
364
|
+
VSCodeGenerator
|
|
265
365
|
});
|
package/lib/agents/generator.md
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
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
7
|
- read
|
|
8
|
+
- search
|
|
10
9
|
- playwright-test/browser_click
|
|
11
10
|
- playwright-test/browser_drag
|
|
12
11
|
- playwright-test/browser_evaluate
|
package/lib/agents/healer.md
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: healer
|
|
2
|
+
name: playwright-test-healer
|
|
3
3
|
description: Use this agent when you need to debug and fix failing Playwright tests
|
|
4
|
-
color: red
|
|
5
4
|
model: sonnet
|
|
5
|
+
color: red
|
|
6
6
|
tools:
|
|
7
|
-
- ls
|
|
8
|
-
- grep
|
|
9
7
|
- read
|
|
8
|
+
- search
|
|
10
9
|
- write
|
|
11
10
|
- edit
|
|
12
11
|
- playwright-test/browser_console_messages
|
|
@@ -24,8 +23,8 @@ resolving Playwright test failures. Your mission is to systematically identify,
|
|
|
24
23
|
broken Playwright tests using a methodical approach.
|
|
25
24
|
|
|
26
25
|
Your workflow:
|
|
27
|
-
1. **Initial Execution**: Run all tests using
|
|
28
|
-
2. **Debug failed tests**: For each failing test run
|
|
26
|
+
1. **Initial Execution**: Run all tests using `test_run` tool to identify failing tests
|
|
27
|
+
2. **Debug failed tests**: For each failing test run `test_debug`.
|
|
29
28
|
3. **Error Investigation**: When the test pauses on errors, use available Playwright MCP tools to:
|
|
30
29
|
- Examine the error details
|
|
31
30
|
- Capture page snapshot to understand the context
|
package/lib/agents/planner.md
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: planner
|
|
2
|
+
name: playwright-test-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
|
-
- ls
|
|
8
|
-
- grep
|
|
9
7
|
- read
|
|
8
|
+
- search
|
|
10
9
|
- write
|
|
11
10
|
- playwright-test/browser_click
|
|
12
11
|
- playwright-test/browser_close
|
|
@@ -38,7 +37,7 @@ You will:
|
|
|
38
37
|
- Invoke the `planner_setup_page` tool once to set up page before using any other tools
|
|
39
38
|
- Explore the browser snapshot
|
|
40
39
|
- Do not take screenshots unless absolutely necessary
|
|
41
|
-
- Use browser_
|
|
40
|
+
- Use `browser_*` tools to navigate and discover interface
|
|
42
41
|
- Thoroughly explore the interface, identifying all interactive elements, forms, navigation paths, and functionality
|
|
43
42
|
|
|
44
43
|
2. **Analyze User Flows**
|
|
@@ -61,7 +61,7 @@ const screenshot = (0, import_tool.defineTabTool)({
|
|
|
61
61
|
if (params.fullPage && params.ref)
|
|
62
62
|
throw new Error("fullPage cannot be used with element screenshots.");
|
|
63
63
|
const fileType = params.type || "png";
|
|
64
|
-
const fileName = await tab.context.outputFile(params.filename
|
|
64
|
+
const fileName = await tab.context.outputFile(params.filename || (0, import_utils2.dateAsFileName)(fileType), { origin: "llm", reason: "Saving screenshot" });
|
|
65
65
|
const options = {
|
|
66
66
|
type: fileType,
|
|
67
67
|
quality: fileType === "png" ? void 0 : 90,
|
package/lib/mcp/test/seed.js
CHANGED
|
@@ -28,7 +28,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
29
|
var seed_exports = {};
|
|
30
30
|
__export(seed_exports, {
|
|
31
|
-
|
|
31
|
+
defaultSeedFile: () => defaultSeedFile,
|
|
32
|
+
ensureSeedFile: () => ensureSeedFile,
|
|
33
|
+
findSeedFile: () => findSeedFile,
|
|
34
|
+
seedFileContent: () => seedFileContent,
|
|
32
35
|
seedProject: () => seedProject
|
|
33
36
|
});
|
|
34
37
|
module.exports = __toCommonJS(seed_exports);
|
|
@@ -44,29 +47,36 @@ function seedProject(config, projectName) {
|
|
|
44
47
|
throw new Error(`Project ${projectName} not found`);
|
|
45
48
|
return project;
|
|
46
49
|
}
|
|
47
|
-
async function
|
|
50
|
+
async function findSeedFile(project) {
|
|
48
51
|
const files = await (0, import_projectUtils.collectFilesForProject)(project);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
return files.find((file) => import_path.default.basename(file).includes("seed"));
|
|
53
|
+
}
|
|
54
|
+
function defaultSeedFile(project) {
|
|
52
55
|
const testDir = project.project.testDir;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
return import_path.default.resolve(testDir, "seed.spec.ts");
|
|
57
|
+
}
|
|
58
|
+
async function ensureSeedFile(project) {
|
|
59
|
+
const seedFile = await findSeedFile(project);
|
|
60
|
+
if (seedFile)
|
|
61
|
+
return seedFile;
|
|
62
|
+
const seedFilePath = defaultSeedFile(project);
|
|
63
|
+
await (0, import_utils.mkdirIfNeeded)(seedFilePath);
|
|
64
|
+
await import_fs.default.promises.writeFile(seedFilePath, seedFileContent);
|
|
65
|
+
return seedFilePath;
|
|
66
|
+
}
|
|
67
|
+
const seedFileContent = `import { test, expect } from '@playwright/test';
|
|
59
68
|
|
|
60
69
|
test.describe('Test group', () => {
|
|
61
70
|
test('seed', async ({ page }) => {
|
|
62
71
|
// generate code here.
|
|
63
72
|
});
|
|
64
73
|
});
|
|
65
|
-
|
|
66
|
-
return seedFile;
|
|
67
|
-
}
|
|
74
|
+
`;
|
|
68
75
|
// Annotate the CommonJS export names for ESM import in node:
|
|
69
76
|
0 && (module.exports = {
|
|
70
|
-
|
|
77
|
+
defaultSeedFile,
|
|
78
|
+
ensureSeedFile,
|
|
79
|
+
findSeedFile,
|
|
80
|
+
seedFileContent,
|
|
71
81
|
seedProject
|
|
72
82
|
});
|
|
@@ -98,7 +98,7 @@ class TestContext {
|
|
|
98
98
|
const config = await testRunner.loadConfig();
|
|
99
99
|
const project = (0, import_seed.seedProject)(config, projectName);
|
|
100
100
|
if (!seedFile) {
|
|
101
|
-
seedFile = await (0, import_seed.
|
|
101
|
+
seedFile = await (0, import_seed.ensureSeedFile)(project);
|
|
102
102
|
} else {
|
|
103
103
|
const candidateFiles = [];
|
|
104
104
|
const testDir = project.project.testDir;
|
package/lib/program.js
CHANGED
|
@@ -48,7 +48,6 @@ var import_testRunner = require("./runner/testRunner");
|
|
|
48
48
|
var import_reporters = require("./runner/reporters");
|
|
49
49
|
var import_exports = require("./mcp/sdk/exports");
|
|
50
50
|
var import_testBackend = require("./mcp/test/testBackend");
|
|
51
|
-
var import_seed = require("./mcp/test/seed");
|
|
52
51
|
var import_program3 = require("./mcp/program");
|
|
53
52
|
var import_watchdog = require("./mcp/browser/watchdog");
|
|
54
53
|
var import_generateAgents = require("./agents/generateAgents");
|
|
@@ -180,24 +179,24 @@ function addInitAgentsCommand(program3) {
|
|
|
180
179
|
const command = program3.command("init-agents");
|
|
181
180
|
command.description("Initialize repository agents");
|
|
182
181
|
const option = command.createOption("--loop <loop>", "Agentic loop provider");
|
|
183
|
-
option.choices(["vscode", "claude", "opencode"]);
|
|
182
|
+
option.choices(["vscode", "claude", "opencode", "generic"]);
|
|
184
183
|
command.addOption(option);
|
|
185
184
|
command.option("-c, --config <file>", `Configuration file to find a project to use for seed test`);
|
|
186
185
|
command.option("--project <project>", "Project to use for seed test");
|
|
187
186
|
command.action(async (opts) => {
|
|
187
|
+
const config = await (0, import_configLoader.loadConfigFromFile)(opts.config);
|
|
188
188
|
if (opts.loop === "opencode") {
|
|
189
|
-
await (
|
|
189
|
+
await import_generateAgents.OpencodeGenerator.init(config, opts.project);
|
|
190
190
|
} else if (opts.loop === "vscode") {
|
|
191
|
-
await (
|
|
191
|
+
await import_generateAgents.VSCodeGenerator.init(config, opts.project);
|
|
192
192
|
} else if (opts.loop === "claude") {
|
|
193
|
-
await (
|
|
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
196
|
} else {
|
|
195
197
|
command.help();
|
|
196
198
|
return;
|
|
197
199
|
}
|
|
198
|
-
const config = await (0, import_configLoader.loadConfigFromFile)(opts.config);
|
|
199
|
-
const project = (0, import_seed.seedProject)(config, opts.project);
|
|
200
|
-
await (0, import_seed.ensureSeedTest)(project, true);
|
|
201
200
|
});
|
|
202
201
|
}
|
|
203
202
|
async function runTests(args, opts) {
|
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-17",
|
|
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-17"
|
|
68
68
|
},
|
|
69
69
|
"optionalDependencies": {
|
|
70
70
|
"fsevents": "2.3.2"
|