playwright 1.57.0-alpha-2025-10-15 → 1.57.0-alpha-2025-10-16
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 +79 -12
- package/lib/agents/healer.md +2 -2
- package/lib/agents/planner.md +1 -1
- 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 +4 -7
- package/package.json +2 -2
|
@@ -36,6 +36,8 @@ module.exports = __toCommonJS(generateAgents_exports);
|
|
|
36
36
|
var import_fs = __toESM(require("fs"));
|
|
37
37
|
var import_path = __toESM(require("path"));
|
|
38
38
|
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
39
|
+
var import_utils = require("playwright-core/lib/utils");
|
|
40
|
+
var import_seed = require("../mcp/test/seed");
|
|
39
41
|
class AgentParser {
|
|
40
42
|
static async parseFile(filePath) {
|
|
41
43
|
const rawMarkdown = await import_fs.default.promises.readFile(filePath, "utf-8");
|
|
@@ -164,15 +166,46 @@ async function loadAgents() {
|
|
|
164
166
|
const files = await import_fs.default.promises.readdir(__dirname);
|
|
165
167
|
return Promise.all(files.filter((file) => file.endsWith(".md")).map((file) => AgentParser.parseFile(import_path.default.join(__dirname, file))));
|
|
166
168
|
}
|
|
167
|
-
async function writeFile(filePath, content) {
|
|
168
|
-
console.log(
|
|
169
|
+
async function writeFile(filePath, content, icon, description) {
|
|
170
|
+
console.log(`- ${icon} ${import_path.default.relative(process.cwd(), filePath)} ${import_utilsBundle.colors.dim("- " + description)}`);
|
|
171
|
+
await (0, import_utils.mkdirIfNeeded)(filePath);
|
|
169
172
|
await import_fs.default.promises.writeFile(filePath, content, "utf-8");
|
|
170
173
|
}
|
|
171
|
-
async function
|
|
174
|
+
async function initRepo(config, projectName) {
|
|
175
|
+
const project = (0, import_seed.seedProject)(config, projectName);
|
|
176
|
+
console.log(`\u{1F3AD} Using project "${project.project.name}" as a primary project`);
|
|
177
|
+
if (!import_fs.default.existsSync("specs")) {
|
|
178
|
+
await import_fs.default.promises.mkdir("specs");
|
|
179
|
+
await writeFile(import_path.default.join("specs", "README.md"), `# Specs
|
|
180
|
+
|
|
181
|
+
This is a directory for test plans.
|
|
182
|
+
`, "\u{1F4DD}", "directory for test plans");
|
|
183
|
+
}
|
|
184
|
+
if (!import_fs.default.existsSync("prompts")) {
|
|
185
|
+
await import_fs.default.promises.mkdir("prompts");
|
|
186
|
+
await writeFile(import_path.default.join("prompts", "README.md"), `# Prompts
|
|
187
|
+
|
|
188
|
+
This is a directory for useful prompts.
|
|
189
|
+
`, "\u{1F4DD}", "useful prompts");
|
|
190
|
+
}
|
|
191
|
+
let seedFile = await (0, import_seed.findSeedFile)(project);
|
|
192
|
+
if (!seedFile) {
|
|
193
|
+
seedFile = (0, import_seed.defaultSeedFile)(project);
|
|
194
|
+
await writeFile(seedFile, import_seed.seedFileContent, "\u{1F331}", "default environment seed file");
|
|
195
|
+
}
|
|
196
|
+
const coveragePromptFile = import_path.default.join("prompts", "test-coverage.md");
|
|
197
|
+
if (!import_fs.default.existsSync(coveragePromptFile))
|
|
198
|
+
await writeFile(coveragePromptFile, coveragePrompt(seedFile), "\u{1F4DD}", "test coverage prompt");
|
|
199
|
+
}
|
|
200
|
+
function initRepoDone() {
|
|
201
|
+
console.log("\u2705 Done.");
|
|
202
|
+
}
|
|
203
|
+
async function initClaudeCodeRepo(config, projectName) {
|
|
204
|
+
await initRepo(config, projectName);
|
|
172
205
|
const agents = await loadAgents();
|
|
173
206
|
await import_fs.default.promises.mkdir(".claude/agents", { recursive: true });
|
|
174
207
|
for (const agent of agents)
|
|
175
|
-
await writeFile(`.claude/agents/playwright-test-${agent.header.name}.md`, saveAsClaudeCode(agent));
|
|
208
|
+
await writeFile(`.claude/agents/playwright-test-${agent.header.name}.md`, saveAsClaudeCode(agent), "\u{1F916}", "agent definition");
|
|
176
209
|
await writeFile(".mcp.json", JSON.stringify({
|
|
177
210
|
mcpServers: {
|
|
178
211
|
"playwright-test": {
|
|
@@ -180,7 +213,8 @@ async function initClaudeCodeRepo() {
|
|
|
180
213
|
args: commonMcpServers.playwrightTest.args
|
|
181
214
|
}
|
|
182
215
|
}
|
|
183
|
-
}, null, 2));
|
|
216
|
+
}, null, 2), "\u{1F527}", "mcp configuration");
|
|
217
|
+
initRepoDone();
|
|
184
218
|
}
|
|
185
219
|
const vscodeToolMap = /* @__PURE__ */ new Map([
|
|
186
220
|
["ls", ["search/listDirectory", "search/fileSearch"]],
|
|
@@ -220,11 +254,12 @@ function saveAsVSCodeChatmode(agent) {
|
|
|
220
254
|
lines.push(`<example>${example}</example>`);
|
|
221
255
|
return lines.join("\n");
|
|
222
256
|
}
|
|
223
|
-
async function initVSCodeRepo() {
|
|
257
|
+
async function initVSCodeRepo(config, projectName) {
|
|
258
|
+
await initRepo(config, projectName);
|
|
224
259
|
const agents = await loadAgents();
|
|
225
260
|
await import_fs.default.promises.mkdir(".github/chatmodes", { recursive: true });
|
|
226
261
|
for (const agent of agents)
|
|
227
|
-
await writeFile(`.github/chatmodes/${agent.header.name === "planner" ? " " : ""}\u{1F3AD} ${agent.header.name}.chatmode.md`, saveAsVSCodeChatmode(agent));
|
|
262
|
+
await writeFile(`.github/chatmodes/${agent.header.name === "planner" ? " " : ""}\u{1F3AD} ${agent.header.name}.chatmode.md`, saveAsVSCodeChatmode(agent), "\u{1F916}", "chatmode definition");
|
|
228
263
|
await import_fs.default.promises.mkdir(".vscode", { recursive: true });
|
|
229
264
|
const mcpJsonPath = ".vscode/mcp.json";
|
|
230
265
|
let mcpJson = {
|
|
@@ -243,20 +278,52 @@ async function initVSCodeRepo() {
|
|
|
243
278
|
args: commonMcpServers.playwrightTest.args,
|
|
244
279
|
cwd: "${workspaceFolder}"
|
|
245
280
|
};
|
|
246
|
-
await writeFile(mcpJsonPath, JSON.stringify(mcpJson, null, 2));
|
|
247
|
-
|
|
281
|
+
await writeFile(mcpJsonPath, JSON.stringify(mcpJson, null, 2), "\u{1F527}", "mcp configuration");
|
|
282
|
+
initRepoDone();
|
|
248
283
|
}
|
|
249
|
-
async function initOpencodeRepo() {
|
|
284
|
+
async function initOpencodeRepo(config, projectName) {
|
|
285
|
+
await initRepo(config, projectName);
|
|
250
286
|
const agents = await loadAgents();
|
|
251
287
|
await import_fs.default.promises.mkdir(".opencode/prompts", { recursive: true });
|
|
252
288
|
for (const agent of agents) {
|
|
253
289
|
const prompt = [agent.instructions];
|
|
254
290
|
prompt.push("");
|
|
255
291
|
prompt.push(...agent.examples.map((example) => `<example>${example}</example>`));
|
|
256
|
-
await writeFile(`.opencode/prompts/playwright-test-${agent.header.name}.md`, prompt.join("\n"));
|
|
292
|
+
await writeFile(`.opencode/prompts/playwright-test-${agent.header.name}.md`, prompt.join("\n"), "\u{1F916}", "agent definition");
|
|
257
293
|
}
|
|
258
|
-
await writeFile("opencode.json", saveAsOpencodeJson(agents));
|
|
294
|
+
await writeFile("opencode.json", saveAsOpencodeJson(agents), "\u{1F527}", "opencode configuration");
|
|
295
|
+
initRepoDone();
|
|
259
296
|
}
|
|
297
|
+
const coveragePrompt = (seedFile) => `
|
|
298
|
+
# Produce test coverage
|
|
299
|
+
|
|
300
|
+
Parameters:
|
|
301
|
+
- Task: the task to perform
|
|
302
|
+
- Seed file (optional): the seed file to use, defaults to ${import_path.default.relative(process.cwd(), seedFile)}
|
|
303
|
+
- Test plan file (optional): the test plan file to write, under specs/ folder.
|
|
304
|
+
|
|
305
|
+
1. Call #planner subagent with prompt:
|
|
306
|
+
|
|
307
|
+
<plan>
|
|
308
|
+
<task><!-- the task --></task>
|
|
309
|
+
<seed-file><!-- seed file param --></seed-file>
|
|
310
|
+
<plan-file><!-- test plan file --></plan-file>
|
|
311
|
+
</plan>
|
|
312
|
+
|
|
313
|
+
2. For each test case from the test plan file (1.1, 1.2, ...), Call #generator subagent with prompt:
|
|
314
|
+
|
|
315
|
+
<generate>
|
|
316
|
+
<test-file><!-- Name of the file to save the test into, should be unique for test --></test-file>
|
|
317
|
+
<test-suite><!-- Name of the top level test spec w/o ordinal--></test-suite>
|
|
318
|
+
<test-name><!--Name of the test case without the ordinal --></test-name>
|
|
319
|
+
<seed-file><!-- Seed file from test plan --></seed-file>
|
|
320
|
+
<body><!-- Test case content including steps and expectations --></body>
|
|
321
|
+
</generate>
|
|
322
|
+
|
|
323
|
+
3. Call #healer subagent with prompt:
|
|
324
|
+
|
|
325
|
+
<heal>Run all tests and fix the failing ones one after another.</heal>
|
|
326
|
+
`;
|
|
260
327
|
// Annotate the CommonJS export names for ESM import in node:
|
|
261
328
|
0 && (module.exports = {
|
|
262
329
|
initClaudeCodeRepo,
|
package/lib/agents/healer.md
CHANGED
|
@@ -24,8 +24,8 @@ resolving Playwright test failures. Your mission is to systematically identify,
|
|
|
24
24
|
broken Playwright tests using a methodical approach.
|
|
25
25
|
|
|
26
26
|
Your workflow:
|
|
27
|
-
1. **Initial Execution**: Run all tests using
|
|
28
|
-
2. **Debug failed tests**: For each failing test run
|
|
27
|
+
1. **Initial Execution**: Run all tests using `test_run` tool to identify failing tests
|
|
28
|
+
2. **Debug failed tests**: For each failing test run `test_debug`.
|
|
29
29
|
3. **Error Investigation**: When the test pauses on errors, use available Playwright MCP tools to:
|
|
30
30
|
- Examine the error details
|
|
31
31
|
- Capture page snapshot to understand the context
|
package/lib/agents/planner.md
CHANGED
|
@@ -38,7 +38,7 @@ You will:
|
|
|
38
38
|
- Invoke the `planner_setup_page` tool once to set up page before using any other tools
|
|
39
39
|
- Explore the browser snapshot
|
|
40
40
|
- Do not take screenshots unless absolutely necessary
|
|
41
|
-
- Use browser_
|
|
41
|
+
- Use `browser_*` tools to navigate and discover interface
|
|
42
42
|
- Thoroughly explore the interface, identifying all interactive elements, forms, navigation paths, and functionality
|
|
43
43
|
|
|
44
44
|
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");
|
|
@@ -185,19 +184,17 @@ function addInitAgentsCommand(program3) {
|
|
|
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 (0, import_generateAgents.initOpencodeRepo)();
|
|
189
|
+
await (0, import_generateAgents.initOpencodeRepo)(config, opts.project);
|
|
190
190
|
} else if (opts.loop === "vscode") {
|
|
191
|
-
await (0, import_generateAgents.initVSCodeRepo)();
|
|
191
|
+
await (0, import_generateAgents.initVSCodeRepo)(config, opts.project);
|
|
192
192
|
} else if (opts.loop === "claude") {
|
|
193
|
-
await (0, import_generateAgents.initClaudeCodeRepo)();
|
|
193
|
+
await (0, import_generateAgents.initClaudeCodeRepo)(config, opts.project);
|
|
194
194
|
} else {
|
|
195
195
|
command.help();
|
|
196
196
|
return;
|
|
197
197
|
}
|
|
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
198
|
});
|
|
202
199
|
}
|
|
203
200
|
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-16",
|
|
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-16"
|
|
68
68
|
},
|
|
69
69
|
"optionalDependencies": {
|
|
70
70
|
"fsevents": "2.3.2"
|