playwright 1.56.0-alpha-2025-09-29 → 1.56.0-alpha-1759271123000

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.
@@ -183,19 +183,19 @@ async function initClaudeCodeRepo() {
183
183
  }, null, 2));
184
184
  }
185
185
  const vscodeToolMap = /* @__PURE__ */ new Map([
186
- ["ls", ["listDirectory", "fileSearch"]],
187
- ["grep", ["textSearch"]],
188
- ["read", ["readFile"]],
189
- ["edit", ["editFiles"]],
190
- ["write", ["createFile", "createDirectory"]]
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
191
  ]);
192
- const vscodeToolsOrder = ["createFile", "createDirectory", "editFiles", "fileSearch", "textSearch", "listDirectory", "readFile"];
193
- const vscodeToolPrefix = "test_";
192
+ const vscodeToolsOrder = ["edit/createFile", "edit/createDirectory", "edit/editFiles", "search/fileSearch", "search/textSearch", "search/listDirectory", "search/readFile"];
193
+ const vscodeMcpName = "playwright-test";
194
194
  function saveAsVSCodeChatmode(agent) {
195
195
  function asVscodeTool(tool) {
196
196
  const [first, second] = tool.split("/");
197
197
  if (second)
198
- return second.startsWith("browser_") ? vscodeToolPrefix + second : second;
198
+ return `${vscodeMcpName}/${second}`;
199
199
  return vscodeToolMap.get(first) || first;
200
200
  }
201
201
  const tools = agent.header.tools.map(asVscodeTool).flat().sort((a, b) => {
@@ -240,9 +240,7 @@ async function initVSCodeRepo() {
240
240
  mcpJson.servers["playwright-test"] = {
241
241
  type: "stdio",
242
242
  command: commonMcpServers.playwrightTest.command,
243
- args: commonMcpServers.playwrightTest.args,
244
- cwd: "${workspaceFolder}",
245
- env: { "PLAYWRIGHT_MCP_TOOL_PREFIX": vscodeToolPrefix }
243
+ args: commonMcpServers.playwrightTest.args
246
244
  };
247
245
  await writeFile(mcpJsonPath, JSON.stringify(mcpJson, null, 2));
248
246
  }
@@ -7,7 +7,6 @@ tools:
7
7
  - ls
8
8
  - grep
9
9
  - read
10
- - write
11
10
  - playwright-test/browser_click
12
11
  - playwright-test/browser_drag
13
12
  - playwright-test/browser_evaluate
@@ -24,38 +23,29 @@ tools:
24
23
  - playwright-test/browser_verify_text_visible
25
24
  - playwright-test/browser_verify_value
26
25
  - playwright-test/browser_wait_for
27
- - playwright-test/test_setup_page
26
+ - playwright-test/generator_log_step
27
+ - playwright-test/generator_read_log
28
+ - playwright-test/generator_setup_page
29
+ - playwright-test/generator_write_test
28
30
  ---
29
31
 
30
32
  You are a Playwright Test Generator, an expert in browser automation and end-to-end testing.
31
33
  Your specialty is creating robust, reliable Playwright tests that accurately simulate user interactions and validate
32
34
  application behavior.
33
35
 
34
- Your process is methodical and thorough:
35
-
36
- 1. **Scenario Analysis**
37
- - Carefully analyze the test scenario provided, identifying all user actions,
38
- expected outcomes and validation points
39
-
40
- 2. **Interactive Execution**
41
- - For each scenario, start with the `test_setup_page` tool to set up page for the scenario
42
- - Use Playwright tools to manually execute each step of the scenario in real-time
43
- - Verify that each action works as expected
44
- - Identify the correct locators and interaction patterns
45
- - Observe actual application behavior and responses
46
- - Validate that assertions will work correctly
47
-
48
- 3. **Test Code Generation**
49
-
50
- After successfully completing the manual execution, generate clean, maintainable
51
- @playwright/test source code that follows following convention:
52
-
53
- - One file per scenario, one test in a file
54
- - Use seed test content (copyright, structure) to emit consistent tests.
55
- - File name must be fs-friendly scenario name
56
- - Test must be placed in a describe matching the top-level test plan item
57
- - Test title must match the scenario name
58
- - Includes a comment with the step text before each step execution
36
+ # For each test you generate
37
+ - Obtain the test plan with all the steps and verification specification
38
+ - Run the `generator_setup_page` tool to set up page for the scenario
39
+ - For each step and verification in the scenario, do the following:
40
+ - Use Playwright tool to manually execute it in real-time.
41
+ - Immediately after each tool execution, log the step via running `generator_log_step` tool.
42
+ - Retrieve generator log via `generator_read_log`
43
+ - Immediately after reading the test log, invoke `generator_write_test` with the generated source code
44
+ - File should contain single test
45
+ - File name must be fs-friendly scenario name
46
+ - Test must be placed in a describe matching the top-level test plan item
47
+ - Test title must match the scenario name
48
+ - Includes a comment with the step text before each step execution
59
49
 
60
50
  <example-generation>
61
51
  For following plan:
@@ -89,17 +79,17 @@ Your process is methodical and thorough:
89
79
  ```
90
80
  </example-generation>
91
81
 
92
- 4. **Best practices**:
93
- - Each test has clear, descriptive assertions that validate the expected behavior
94
- - Includes proper error handling and meaningful failure messages
95
- - Uses Playwright best practices (page.waitForLoadState, expect.toBeVisible, etc.)
96
- - Do not improvise, do not add directives that were not asked for
97
- - Uses reliable locators (preferring data-testid, role-based, or text-based selectors over fragile CSS selectors)
98
- - Uses local variables for locators that are used multiple times
99
- - Uses explicit waits rather than arbitrary timeouts
100
- - Never waits for networkidle or use other discouraged or deprecated apis
101
- - Is self-contained and can run independently
102
- - Is deterministic and not prone to flaky behavior
82
+ # Best practices
83
+ - Each test has clear, descriptive assertions that validate the expected behavior
84
+ - Includes proper error handling and meaningful failure messages
85
+ - Uses Playwright best practices (page.waitForLoadState, expect.toBeVisible, etc.)
86
+ - Do not improvise, do not add directives that were not asked for
87
+ - Uses reliable locators (preferring data-testid, role-based, or text-based selectors over fragile CSS selectors)
88
+ - Uses local variables for locators that are used multiple times
89
+ - Uses explicit waits rather than arbitrary timeouts
90
+ - Never waits for networkidle or use other discouraged or deprecated apis
91
+ - Is self-contained and can run independently
92
+ - Is deterministic and not prone to flaky behavior
103
93
 
104
94
  <example>
105
95
  Context: User wants to test a login flow on their web application.
@@ -25,7 +25,7 @@ tools:
25
25
  - playwright-test/browser_take_screenshot
26
26
  - playwright-test/browser_type
27
27
  - playwright-test/browser_wait_for
28
- - playwright-test/test_setup_page
28
+ - playwright-test/planner_setup_page
29
29
  ---
30
30
 
31
31
  You are an expert web test planner with extensive experience in quality assurance, user experience testing, and test
@@ -35,7 +35,7 @@ planning.
35
35
  You will:
36
36
 
37
37
  1. **Navigate and Explore**
38
- - Invoke the `test_setup_page` tool once to set up page before using any other tools
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
41
  - Use browser_* tools to navigate and discover interface
@@ -164,9 +164,9 @@ class Tab extends import_events.EventEmitter {
164
164
  }
165
165
  await this.waitForLoadState("load", { timeout: 5e3 });
166
166
  }
167
- async consoleMessages() {
167
+ async consoleMessages(type) {
168
168
  await this._initializedPromise;
169
- return this._consoleMessages;
169
+ return this._consoleMessages.filter((message) => type ? message.type === type : true);
170
170
  }
171
171
  async requests() {
172
172
  await this._initializedPromise;
@@ -249,13 +249,13 @@ function messageToConsoleMessage(message) {
249
249
  function pageErrorToConsoleMessage(errorOrValue) {
250
250
  if (errorOrValue instanceof Error) {
251
251
  return {
252
- type: void 0,
252
+ type: "error",
253
253
  text: errorOrValue.message,
254
254
  toString: () => errorOrValue.stack || errorOrValue.message
255
255
  };
256
256
  }
257
257
  return {
258
- type: void 0,
258
+ type: "error",
259
259
  text: String(errorOrValue),
260
260
  toString: () => String(errorOrValue)
261
261
  };
@@ -29,11 +29,13 @@ const console = (0, import_tool.defineTabTool)({
29
29
  name: "browser_console_messages",
30
30
  title: "Get console messages",
31
31
  description: "Returns all console messages",
32
- inputSchema: import_bundle.z.object({}),
32
+ inputSchema: import_bundle.z.object({
33
+ onlyErrors: import_bundle.z.boolean().optional().describe("Only return error messages")
34
+ }),
33
35
  type: "readOnly"
34
36
  },
35
37
  handle: async (tab, params, response) => {
36
- const messages = await tab.consoleMessages();
38
+ const messages = await tab.consoleMessages(params.onlyErrors ? "error" : void 0);
37
39
  messages.map((message) => response.addResult(message.toString()));
38
40
  }
39
41
  });
@@ -33,12 +33,16 @@ async function waitForCompletion(tab, callback) {
33
33
  const waitBarrier = new Promise((f) => {
34
34
  waitCallback = f;
35
35
  });
36
- const requestListener = (request) => requests.add(request);
37
- const requestFinishedListener = (request) => {
36
+ const responseListener = (request) => {
38
37
  requests.delete(request);
39
38
  if (!requests.size)
40
39
  waitCallback();
41
40
  };
41
+ const requestListener = (request) => {
42
+ requests.add(request);
43
+ void request.response().then(() => responseListener(request)).catch(() => {
44
+ });
45
+ };
42
46
  const frameNavigateListener = (frame) => {
43
47
  if (frame.parentFrame())
44
48
  return;
@@ -52,12 +56,12 @@ async function waitForCompletion(tab, callback) {
52
56
  waitCallback();
53
57
  };
54
58
  tab.page.on("request", requestListener);
55
- tab.page.on("requestfinished", requestFinishedListener);
59
+ tab.page.on("requestfailed", responseListener);
56
60
  tab.page.on("framenavigated", frameNavigateListener);
57
61
  const timeout = setTimeout(onTimeout, 1e4);
58
62
  const dispose = () => {
59
63
  tab.page.off("request", requestListener);
60
- tab.page.off("requestfinished", requestFinishedListener);
64
+ tab.page.off("requestfailed", responseListener);
61
65
  tab.page.off("framenavigated", frameNavigateListener);
62
66
  clearTimeout(timeout);
63
67
  };
@@ -70,11 +70,6 @@ const browserTools = [
70
70
  ...import_wait.default,
71
71
  ...import_verify.default
72
72
  ];
73
- const customPrefix = process.env.PLAYWRIGHT_MCP_TOOL_PREFIX;
74
- if (customPrefix) {
75
- for (const tool of browserTools)
76
- tool.schema.name = customPrefix + tool.schema.name;
77
- }
78
73
  function filteredTools(config) {
79
74
  return browserTools.filter((tool) => tool.capability.startsWith("core") || config.capabilities?.includes(tool.capability));
80
75
  }
@@ -44,10 +44,11 @@ const mdbDebug = (0, import_utilsBundle.debug)("pw:mcp:mdb");
44
44
  const errorsDebug = (0, import_utilsBundle.debug)("pw:mcp:errors");
45
45
  const z = mcpBundle.z;
46
46
  class MDBBackend {
47
- constructor(topLevelBackend) {
47
+ constructor(topLevelBackend, allowedOnPause) {
48
48
  this._stack = [];
49
49
  this._progress = [];
50
50
  this._topLevelBackend = topLevelBackend;
51
+ this._allowedOnPause = allowedOnPause;
51
52
  }
52
53
  async initialize(server, clientInfo) {
53
54
  if (!this._clientInfo)
@@ -64,16 +65,25 @@ class MDBBackend {
64
65
  return await this._pushTools(pushToolsSchema.inputSchema.parse(args || {}));
65
66
  const interruptPromise = new import_utils.ManualPromise();
66
67
  this._interruptPromise = interruptPromise;
67
- let [entry] = this._stack;
68
- while (entry && !entry.toolNames.includes(name)) {
68
+ if (!this._allowedOnPause.includes(name)) {
69
+ for (let i = 0; i < this._stack.length - 1; i++) {
70
+ if (this._stack[i].toolNames.includes(name))
71
+ break;
72
+ await this._stack[i].client.close().catch(errorsDebug);
73
+ this._stack.shift();
74
+ }
75
+ }
76
+ let entry;
77
+ for (let i = 0; i < this._stack.length; i++) {
78
+ if (this._stack[i].toolNames.includes(name)) {
79
+ entry = this._stack[i];
80
+ break;
81
+ }
69
82
  mdbDebug("popping client from stack for ", name);
70
- this._stack.shift();
71
- await entry.client.close().catch(errorsDebug);
72
- entry = this._stack[0];
73
83
  }
74
84
  if (!entry)
75
85
  throw new Error(`Tool ${name} not found in the tool stack`);
76
- const client = await this._client();
86
+ const client = entry.client;
77
87
  const resultPromise = new import_utils.ManualPromise();
78
88
  entry.resultPromise = resultPromise;
79
89
  client.callTool({
@@ -111,7 +121,7 @@ class MDBBackend {
111
121
  }
112
122
  async _pushClient(transport, introMessage) {
113
123
  mdbDebug("pushing client to the stack");
114
- const client = new mcpBundle.Client({ name: "Pushing client", version: "0.0.0" }, { capabilities: { roots: {} } });
124
+ const client = new mcpBundle.Client({ name: "Interrupting client", version: "0.0.0" }, { capabilities: { roots: {} } });
115
125
  client.setRequestHandler(mcpBundle.ListRootsRequestSchema, () => ({ roots: this._clientInfo?.roots || [] }));
116
126
  client.setRequestHandler(mcpBundle.PingRequestSchema, () => ({}));
117
127
  client.setNotificationHandler(mcpBundle.ProgressNotificationSchema, (notification) => {
@@ -147,8 +157,8 @@ const pushToolsSchema = (0, import_tool.defineToolSchema)({
147
157
  }),
148
158
  type: "readOnly"
149
159
  });
150
- async function runMainBackend(backendFactory, options) {
151
- const mdbBackend = new MDBBackend(backendFactory.create());
160
+ async function runMainBackend(backendFactory, allowedOnPause, options) {
161
+ const mdbBackend = new MDBBackend(backendFactory.create(), allowedOnPause);
152
162
  const factory = {
153
163
  ...backendFactory,
154
164
  create: () => mdbBackend
@@ -170,7 +180,7 @@ async function runOnPauseBackendLoop(backend, introMessage) {
170
180
  const httpServer = await mcpHttp.startHttpServer({ port: 0 });
171
181
  await mcpHttp.installHttpTransport(httpServer, factory);
172
182
  const url = mcpHttp.httpAddressToString(httpServer.address());
173
- const client = new mcpBundle.Client({ name: "On-pause client", version: "0.0.0" });
183
+ const client = new mcpBundle.Client({ name: "Pushing client", version: "0.0.0" });
174
184
  client.setRequestHandler(mcpBundle.PingRequestSchema, () => ({}));
175
185
  const transport = new mcpBundle.StreamableHTTPClientTransport(new URL(process.env.PLAYWRIGHT_DEBUGGER_MCP));
176
186
  await client.connect(transport);
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var generatorTools_exports = {};
30
+ __export(generatorTools_exports, {
31
+ generatorLogStep: () => generatorLogStep,
32
+ generatorReadLog: () => generatorReadLog,
33
+ generatorWriteTest: () => generatorWriteTest,
34
+ setupPage: () => setupPage
35
+ });
36
+ module.exports = __toCommonJS(generatorTools_exports);
37
+ var import_fs = __toESM(require("fs"));
38
+ var import_path = __toESM(require("path"));
39
+ var import_bundle = require("../sdk/bundle");
40
+ var import_testTool = require("./testTool");
41
+ var import_testContext = require("./testContext");
42
+ const setupPage = (0, import_testTool.defineTestTool)({
43
+ schema: {
44
+ name: "generator_setup_page",
45
+ title: "Setup generator page",
46
+ description: "Setup the page for test.",
47
+ inputSchema: import_bundle.z.object({
48
+ plan: import_bundle.z.string().describe("The plan for the test. This should be the actual test plan with all the steps."),
49
+ project: import_bundle.z.string().optional().describe('Project to use for setup. For example: "chromium", if no project is provided uses the first project in the config.'),
50
+ seedFile: import_bundle.z.string().optional().describe('A seed file contains a single test that is used to setup the page for testing, for example: "tests/seed.spec.ts". If no seed file is provided, a default seed file is created.')
51
+ }),
52
+ type: "readOnly"
53
+ },
54
+ handle: async (context, params, progress) => {
55
+ const seed = await context.getOrCreateSeedFile(params);
56
+ context.generatorJournal = new import_testContext.GeneratorJournal(context.rootPath, params.plan, seed);
57
+ await context.runSeedTest(seed.file, params.project, progress);
58
+ return { content: [] };
59
+ }
60
+ });
61
+ const generatorLogStep = (0, import_testTool.defineTestTool)({
62
+ schema: {
63
+ name: "generator_log_step",
64
+ title: "Log a test step",
65
+ description: "Log a successful test step.",
66
+ inputSchema: import_bundle.z.object({
67
+ title: import_bundle.z.string().describe("Title of the test step"),
68
+ code: import_bundle.z.string().describe("The code for the last step only, as a markdown code block")
69
+ }),
70
+ type: "readOnly"
71
+ },
72
+ handle: async (context, params) => {
73
+ if (!context.generatorJournal)
74
+ throw new Error(`Please setup page using "${setupPage.schema.name}" first.`);
75
+ context.generatorJournal.logStep(params.title, params.code);
76
+ return { content: [] };
77
+ }
78
+ });
79
+ const generatorReadLog = (0, import_testTool.defineTestTool)({
80
+ schema: {
81
+ name: "generator_read_log",
82
+ title: "Retrieve test log",
83
+ description: "Retrieve the performed test log",
84
+ inputSchema: import_bundle.z.object({}),
85
+ type: "readOnly"
86
+ },
87
+ handle: async (context) => {
88
+ if (!context.generatorJournal)
89
+ throw new Error(`Please setup page using "${setupPage.schema.name}" first.`);
90
+ const result = context.generatorJournal.journal();
91
+ return { content: [{
92
+ type: "text",
93
+ text: result
94
+ }] };
95
+ }
96
+ });
97
+ const generatorWriteTest = (0, import_testTool.defineTestTool)({
98
+ schema: {
99
+ name: "generator_write_test",
100
+ title: "Write test",
101
+ description: "Write the generated test to the test file",
102
+ inputSchema: import_bundle.z.object({
103
+ fileName: import_bundle.z.string().describe("The file to write the test to"),
104
+ code: import_bundle.z.string().describe("The generated test code")
105
+ }),
106
+ type: "destructive"
107
+ },
108
+ handle: async (context, params) => {
109
+ if (!context.generatorJournal)
110
+ throw new Error(`Please setup page using "${setupPage.schema.name}" first.`);
111
+ const testRunner = context.existingTestRunner();
112
+ if (!testRunner)
113
+ throw new Error("No test runner found, please setup page and perform actions first.");
114
+ const config = await testRunner.loadConfig();
115
+ const dirs = [];
116
+ for (const project of config.projects) {
117
+ const testDir = import_path.default.relative(context.rootPath, project.project.testDir).replace(/\\/g, "/");
118
+ const fileName = params.fileName.replace(/\\/g, "/");
119
+ if (fileName.startsWith(testDir)) {
120
+ await import_fs.default.promises.writeFile(import_path.default.resolve(context.rootPath, fileName), params.code);
121
+ return {
122
+ content: [{
123
+ type: "text",
124
+ text: `### Result
125
+ Test written to ${params.fileName}`
126
+ }]
127
+ };
128
+ }
129
+ dirs.push(testDir);
130
+ }
131
+ throw new Error(`Test file did not match any of the test dirs: ${dirs.join(", ")}`);
132
+ }
133
+ });
134
+ // Annotate the CommonJS export names for ESM import in node:
135
+ 0 && (module.exports = {
136
+ generatorLogStep,
137
+ generatorReadLog,
138
+ generatorWriteTest,
139
+ setupPage
140
+ });
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var plannerTools_exports = {};
20
+ __export(plannerTools_exports, {
21
+ setupPage: () => setupPage
22
+ });
23
+ module.exports = __toCommonJS(plannerTools_exports);
24
+ var import_bundle = require("../sdk/bundle");
25
+ var import_testTool = require("./testTool");
26
+ const setupPage = (0, import_testTool.defineTestTool)({
27
+ schema: {
28
+ name: "planner_setup_page",
29
+ title: "Setup planner page",
30
+ description: "Setup the page for test planning",
31
+ inputSchema: import_bundle.z.object({
32
+ project: import_bundle.z.string().optional().describe('Project to use for setup. For example: "chromium", if no project is provided uses the first project in the config.'),
33
+ seedFile: import_bundle.z.string().optional().describe('A seed file contains a single test that is used to setup the page for testing, for example: "tests/seed.spec.ts". If no seed file is provided, a default seed file is created.')
34
+ }),
35
+ type: "readOnly"
36
+ },
37
+ handle: async (context, params, progress) => {
38
+ const seed = await context.getOrCreateSeedFile(params);
39
+ await context.runSeedTest(seed.file, params.project, progress);
40
+ return { content: [] };
41
+ }
42
+ });
43
+ // Annotate the CommonJS export names for ESM import in node:
44
+ 0 && (module.exports = {
45
+ setupPage
46
+ });
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var seed_exports = {};
30
+ __export(seed_exports, {
31
+ ensureSeedTest: () => ensureSeedTest,
32
+ seedProject: () => seedProject
33
+ });
34
+ module.exports = __toCommonJS(seed_exports);
35
+ var import_fs = __toESM(require("fs"));
36
+ var import_path = __toESM(require("path"));
37
+ var import_utils = require("playwright-core/lib/utils");
38
+ var import_projectUtils = require("../../runner/projectUtils");
39
+ function seedProject(config, projectName) {
40
+ if (!projectName)
41
+ return (0, import_projectUtils.findTopLevelProjects)(config)[0];
42
+ const project = config.projects.find((p) => p.project.name === projectName);
43
+ if (!project)
44
+ throw new Error(`Project ${projectName} not found`);
45
+ return project;
46
+ }
47
+ async function ensureSeedTest(config, projectName, logNew) {
48
+ const project = seedProject(config, projectName);
49
+ const files = await (0, import_projectUtils.collectFilesForProject)(project);
50
+ const seed = files.find((file) => import_path.default.basename(file).includes("seed"));
51
+ if (seed)
52
+ return seed;
53
+ const testDir = project.project.testDir;
54
+ const seedFile = import_path.default.resolve(testDir, "seed.spec.ts");
55
+ if (logNew) {
56
+ console.log(`Writing file: ${import_path.default.relative(process.cwd(), seedFile)}`);
57
+ }
58
+ await (0, import_utils.mkdirIfNeeded)(seedFile);
59
+ await import_fs.default.promises.writeFile(seedFile, `import { test, expect } from '@playwright/test';
60
+
61
+ test.describe('Test group', () => {
62
+ test('seed', async ({ page }) => {
63
+ // generate code here.
64
+ });
65
+ });
66
+ `);
67
+ return seedFile;
68
+ }
69
+ // Annotate the CommonJS export names for ESM import in node:
70
+ 0 && (module.exports = {
71
+ ensureSeedTest,
72
+ seedProject
73
+ });
@@ -33,17 +33,33 @@ __export(testBackend_exports, {
33
33
  module.exports = __toCommonJS(testBackend_exports);
34
34
  var mcp = __toESM(require("../sdk/exports"));
35
35
  var import_testContext = require("./testContext");
36
- var import_testTools = require("./testTools.js");
36
+ var testTools = __toESM(require("./testTools.js"));
37
+ var generatorTools = __toESM(require("./generatorTools.js"));
38
+ var plannerTools = __toESM(require("./plannerTools.js"));
37
39
  var import_tools = require("../browser/tools");
38
40
  var import_configLoader = require("../../common/configLoader");
39
41
  class TestServerBackend {
40
42
  constructor(configOption, options) {
41
43
  this.name = "Playwright";
42
44
  this.version = "0.0.1";
43
- this._tools = [import_testTools.listTests, import_testTools.runTests, import_testTools.debugTest, import_testTools.setupPage];
45
+ this._tools = [
46
+ plannerTools.setupPage,
47
+ generatorTools.setupPage,
48
+ generatorTools.generatorLogStep,
49
+ generatorTools.generatorReadLog,
50
+ generatorTools.generatorWriteTest,
51
+ testTools.listTests,
52
+ testTools.runTests,
53
+ testTools.debugTest
54
+ ];
44
55
  this._context = new import_testContext.TestContext(options);
45
56
  this._configOption = configOption;
46
57
  }
58
+ static {
59
+ this.allowedOnPause = [
60
+ generatorTools.generatorLogStep.schema.name
61
+ ];
62
+ }
47
63
  async initialize(server, clientInfo) {
48
64
  const rootPath = mcp.firstRootPath(clientInfo);
49
65
  if (this._configOption) {
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,13 +17,54 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
  var testContext_exports = {};
20
30
  __export(testContext_exports, {
31
+ GeneratorJournal: () => GeneratorJournal,
21
32
  TestContext: () => TestContext
22
33
  });
23
34
  module.exports = __toCommonJS(testContext_exports);
35
+ var import_fs = __toESM(require("fs"));
36
+ var import_path = __toESM(require("path"));
37
+ var import_utils = require("playwright-core/lib/utils");
38
+ var import_base = require("../../reporters/base");
39
+ var import_list = __toESM(require("../../reporters/list"));
40
+ var import_streams = require("./streams");
41
+ var import_util = require("../../util");
24
42
  var import_testRunner = require("../../runner/testRunner");
43
+ var import_seed = require("./seed");
44
+ class GeneratorJournal {
45
+ constructor(rootPath, plan, seed) {
46
+ this._rootPath = rootPath;
47
+ this._plan = plan;
48
+ this._seed = seed;
49
+ this._steps = [];
50
+ }
51
+ logStep(title, code) {
52
+ this._steps.push({ title, code });
53
+ }
54
+ journal() {
55
+ const result = [];
56
+ result.push(`# Plan`);
57
+ result.push(this._plan);
58
+ result.push(`# Seed file: ${import_path.default.relative(this._rootPath, this._seed.file)}`);
59
+ result.push("```ts");
60
+ result.push(this._seed.content);
61
+ result.push("```");
62
+ result.push(`# Steps`);
63
+ result.push(this._steps.map((step) => `### ${step.title}
64
+ ${step.code}`).join("\n\n"));
65
+ return result.join("\n\n");
66
+ }
67
+ }
25
68
  class TestContext {
26
69
  constructor(options) {
27
70
  this.options = options;
@@ -30,6 +73,9 @@ class TestContext {
30
73
  this.configLocation = configLocation;
31
74
  this.rootPath = rootPath || configLocation.configDir;
32
75
  }
76
+ existingTestRunner() {
77
+ return this._testRunner;
78
+ }
33
79
  async createTestRunner() {
34
80
  if (this._testRunner)
35
81
  await this._testRunner.stopTests();
@@ -42,10 +88,67 @@ class TestContext {
42
88
  this._testRunner = testRunner;
43
89
  return testRunner;
44
90
  }
91
+ async getOrCreateSeedFile(params) {
92
+ const configDir = this.configLocation.configDir;
93
+ const testRunner = await this.createTestRunner();
94
+ const config = await testRunner.loadConfig();
95
+ let seedFile;
96
+ if (!params.seedFile) {
97
+ seedFile = await (0, import_seed.ensureSeedTest)(config, params.project, false);
98
+ } else {
99
+ const candidateFiles = [];
100
+ const testDir = (0, import_seed.seedProject)(config, params.project).project.testDir;
101
+ candidateFiles.push(import_path.default.resolve(testDir, params.seedFile));
102
+ candidateFiles.push(import_path.default.resolve(configDir, params.seedFile));
103
+ candidateFiles.push(import_path.default.resolve(this.rootPath, params.seedFile));
104
+ for (const candidateFile of candidateFiles) {
105
+ if (await (0, import_util.fileExistsAsync)(candidateFile)) {
106
+ seedFile = candidateFile;
107
+ break;
108
+ }
109
+ }
110
+ if (!seedFile)
111
+ throw new Error("seed test not found.");
112
+ }
113
+ const seedFileContent = await import_fs.default.promises.readFile(seedFile, "utf8");
114
+ return { file: seedFile, content: seedFileContent };
115
+ }
116
+ async runSeedTest(seedFile, projectName, progress) {
117
+ const { screen } = this.createScreen(progress);
118
+ const configDir = this.configLocation.configDir;
119
+ const reporter = new import_list.default({ configDir, screen });
120
+ const testRunner = await this.createTestRunner();
121
+ const result = await testRunner.runTests(reporter, {
122
+ headed: !this.options?.headless,
123
+ locations: ["/" + (0, import_utils.escapeRegExp)(seedFile) + "/"],
124
+ projects: projectName ? [projectName] : void 0,
125
+ timeout: 0,
126
+ workers: 1,
127
+ pauseAtEnd: true,
128
+ disableConfigReporters: true,
129
+ failOnLoadErrors: true
130
+ });
131
+ if (result.status === "passed" && !reporter.suite?.allTests().length)
132
+ throw new Error("seed test not found.");
133
+ if (result.status !== "passed")
134
+ throw new Error("Errors while running the seed test.");
135
+ }
136
+ createScreen(progress) {
137
+ const stream = new import_streams.StringWriteStream(progress);
138
+ const screen = {
139
+ ...import_base.terminalScreen,
140
+ isTTY: false,
141
+ colors: import_utils.noColors,
142
+ stdout: stream,
143
+ stderr: stream
144
+ };
145
+ return { screen, stream };
146
+ }
45
147
  async close() {
46
148
  }
47
149
  }
48
150
  // Annotate the CommonJS export names for ESM import in node:
49
151
  0 && (module.exports = {
152
+ GeneratorJournal,
50
153
  TestContext
51
154
  });
@@ -30,21 +30,13 @@ var testTools_exports = {};
30
30
  __export(testTools_exports, {
31
31
  debugTest: () => debugTest,
32
32
  listTests: () => listTests,
33
- runTests: () => runTests,
34
- setupPage: () => setupPage
33
+ runTests: () => runTests
35
34
  });
36
35
  module.exports = __toCommonJS(testTools_exports);
37
- var import_fs = __toESM(require("fs"));
38
- var import_path = __toESM(require("path"));
39
- var import_utils = require("playwright-core/lib/utils");
40
36
  var import_bundle = require("../sdk/bundle");
41
- var import_base = require("../../reporters/base");
42
37
  var import_list = __toESM(require("../../reporters/list"));
43
38
  var import_listModeReporter = __toESM(require("../../reporters/listModeReporter"));
44
- var import_projectUtils = require("../../runner/projectUtils");
45
39
  var import_testTool = require("./testTool");
46
- var import_streams = require("./streams");
47
- var import_util = require("../../util");
48
40
  const listTests = (0, import_testTool.defineTestTool)({
49
41
  schema: {
50
42
  name: "test_list",
@@ -54,7 +46,7 @@ const listTests = (0, import_testTool.defineTestTool)({
54
46
  type: "readOnly"
55
47
  },
56
48
  handle: async (context, _, progress) => {
57
- const { screen } = createScreen(progress);
49
+ const { screen } = context.createScreen(progress);
58
50
  const reporter = new import_listModeReporter.default({ screen, includeTestId: true });
59
51
  const testRunner = await context.createTestRunner();
60
52
  await testRunner.listTests(reporter, {});
@@ -73,7 +65,7 @@ const runTests = (0, import_testTool.defineTestTool)({
73
65
  type: "readOnly"
74
66
  },
75
67
  handle: async (context, params, progress) => {
76
- const { screen } = createScreen(progress);
68
+ const { screen } = context.createScreen(progress);
77
69
  const configDir = context.configLocation.configDir;
78
70
  const reporter = new import_list.default({ configDir, screen, includeTestId: true, prefixStdio: "out" });
79
71
  const testRunner = await context.createTestRunner();
@@ -99,7 +91,7 @@ const debugTest = (0, import_testTool.defineTestTool)({
99
91
  type: "readOnly"
100
92
  },
101
93
  handle: async (context, params, progress) => {
102
- const { screen } = createScreen(progress);
94
+ const { screen } = context.createScreen(progress);
103
95
  const configDir = context.configLocation.configDir;
104
96
  const reporter = new import_list.default({ configDir, screen, includeTestId: true, prefixStdio: "out" });
105
97
  const testRunner = await context.createTestRunner();
@@ -115,90 +107,9 @@ const debugTest = (0, import_testTool.defineTestTool)({
115
107
  return { content: [] };
116
108
  }
117
109
  });
118
- const setupPage = (0, import_testTool.defineTestTool)({
119
- schema: {
120
- name: "test_setup_page",
121
- title: "Setup page",
122
- description: "Setup the page for test.",
123
- inputSchema: import_bundle.z.object({
124
- project: import_bundle.z.string().optional().describe('Project to use for setup. For example: "chromium", if no project is provided uses the first project in the config.'),
125
- seedFile: import_bundle.z.string().optional().describe('A seed file contains a single test that is used to setup the page for testing, for example: "tests/seed.spec.ts". If no seed file is provided, a default seed file is created.')
126
- }),
127
- type: "readOnly"
128
- },
129
- handle: async (context, params, progress) => {
130
- const { screen } = createScreen(progress);
131
- const configDir = context.configLocation.configDir;
132
- const reporter = new import_list.default({ configDir, screen });
133
- const testRunner = await context.createTestRunner();
134
- const config = await testRunner.loadConfig();
135
- const project = params.project ? config.projects.find((p) => p.project.name === params.project) : (0, import_projectUtils.findTopLevelProjects)(config)[0];
136
- const testDir = project?.project.testDir || configDir;
137
- let seedFile;
138
- if (!params.seedFile) {
139
- seedFile = import_path.default.resolve(testDir, "seed.spec.ts");
140
- await import_fs.default.promises.mkdir(import_path.default.dirname(seedFile), { recursive: true });
141
- await import_fs.default.promises.writeFile(seedFile, `import { test, expect } from '@playwright/test';
142
-
143
- test.describe('Test group', () => {
144
- test('seed', async ({ page }) => {
145
- // generate code here.
146
- });
147
- });
148
- `);
149
- } else {
150
- const candidateFiles = [];
151
- candidateFiles.push(import_path.default.resolve(testDir, params.seedFile));
152
- candidateFiles.push(import_path.default.resolve(configDir, params.seedFile));
153
- candidateFiles.push(import_path.default.resolve(context.rootPath, params.seedFile));
154
- for (const candidateFile of candidateFiles) {
155
- if (await (0, import_util.fileExistsAsync)(candidateFile)) {
156
- seedFile = candidateFile;
157
- break;
158
- }
159
- }
160
- if (!seedFile)
161
- throw new Error("seed test not found.");
162
- }
163
- const seedFileContent = await import_fs.default.promises.readFile(seedFile, "utf8");
164
- progress({ message: `### Seed test
165
- File: ${import_path.default.relative(context.rootPath, seedFile)}
166
- \`\`\`ts
167
- ${seedFileContent}
168
- \`\`\`
169
- ` });
170
- const result = await testRunner.runTests(reporter, {
171
- headed: !context.options?.headless,
172
- locations: ["/" + (0, import_utils.escapeRegExp)(seedFile) + "/"],
173
- projects: params.project ? [params.project] : void 0,
174
- timeout: 0,
175
- workers: 1,
176
- pauseAtEnd: true,
177
- disableConfigReporters: true,
178
- failOnLoadErrors: true
179
- });
180
- if (result.status === "passed" && !reporter.suite?.allTests().length)
181
- throw new Error("seed test not found.");
182
- if (result.status !== "passed")
183
- throw new Error("Errors while running the seed test.");
184
- return { content: [] };
185
- }
186
- });
187
- function createScreen(progress) {
188
- const stream = new import_streams.StringWriteStream(progress);
189
- const screen = {
190
- ...import_base.terminalScreen,
191
- isTTY: false,
192
- colors: import_utils.noColors,
193
- stdout: stream,
194
- stderr: stream
195
- };
196
- return { screen, stream };
197
- }
198
110
  // Annotate the CommonJS export names for ESM import in node:
199
111
  0 && (module.exports = {
200
112
  debugTest,
201
113
  listTests,
202
- runTests,
203
- setupPage
114
+ runTests
204
115
  });
package/lib/program.js CHANGED
@@ -48,6 +48,7 @@ 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");
51
52
  var import_program3 = require("./mcp/program");
52
53
  var import_watchdog = require("./mcp/browser/watchdog");
53
54
  var import_generateAgents = require("./agents/generateAgents");
@@ -165,7 +166,13 @@ function addTestMCPServerCommand(program3) {
165
166
  version: packageJSON.version,
166
167
  create: () => new import_testBackend.TestServerBackend(options.config, { muteConsole: options.port === void 0, headless: options.headless })
167
168
  };
168
- const mdbUrl = await (0, import_exports.runMainBackend)(backendFactory, { port: options.port === void 0 ? void 0 : +options.port });
169
+ const mdbUrl = await (0, import_exports.runMainBackend)(
170
+ backendFactory,
171
+ import_testBackend.TestServerBackend.allowedOnPause,
172
+ {
173
+ port: options.port === void 0 ? void 0 : +options.port
174
+ }
175
+ );
169
176
  if (mdbUrl)
170
177
  console.error("MCP Listening on: ", mdbUrl);
171
178
  });
@@ -176,15 +183,21 @@ function addInitAgentsCommand(program3) {
176
183
  const option = command.createOption("--loop <loop>", "Agentic loop provider");
177
184
  option.choices(["vscode", "claude", "opencode"]);
178
185
  command.addOption(option);
186
+ command.option("-c, --config <file>", `Configuration file to find a project to use for seed test`);
187
+ command.option("--project <project>", "Project to use for seed test");
179
188
  command.action(async (opts) => {
180
- if (opts.loop === "opencode")
189
+ if (opts.loop === "opencode") {
181
190
  await (0, import_generateAgents.initOpencodeRepo)();
182
- else if (opts.loop === "vscode")
191
+ } else if (opts.loop === "vscode") {
183
192
  await (0, import_generateAgents.initVSCodeRepo)();
184
- else if (opts.loop === "claude")
193
+ } else if (opts.loop === "claude") {
185
194
  await (0, import_generateAgents.initClaudeCodeRepo)();
186
- else
195
+ } else {
187
196
  command.help();
197
+ return;
198
+ }
199
+ const config = await (0, import_configLoader.loadConfigFromFile)(opts.config);
200
+ await (0, import_seed.ensureSeedTest)(config, opts.project, true);
188
201
  });
189
202
  }
190
203
  async function runTests(args, opts) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playwright",
3
- "version": "1.56.0-alpha-2025-09-29",
3
+ "version": "1.56.0-alpha-1759271123000",
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.56.0-alpha-2025-09-29"
67
+ "playwright-core": "1.56.0-alpha-1759271123000"
68
68
  },
69
69
  "optionalDependencies": {
70
70
  "fsevents": "2.3.2"
@@ -773,7 +773,7 @@ export interface TestStep {
773
773
  * - `hook` for hooks initialization and teardown
774
774
  * - `pw:api` for Playwright API calls.
775
775
  * - `test.step` for test.step API calls.
776
- * - `test.attach` for test attachmen calls.
776
+ * - `test.attach` for testInfo.attach API calls.
777
777
  */
778
778
  category: string;
779
779