playwright 1.56.1 → 1.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +3 -3
  2. package/ThirdPartyNotices.txt +202 -282
  3. package/lib/agents/copilot-setup-steps.yml +34 -0
  4. package/lib/agents/generateAgents.js +292 -160
  5. package/lib/agents/playwright-test-coverage.prompt.md +31 -0
  6. package/lib/agents/playwright-test-generate.prompt.md +8 -0
  7. package/lib/agents/{generator.md → playwright-test-generator.agent.md} +8 -22
  8. package/lib/agents/playwright-test-heal.prompt.md +6 -0
  9. package/lib/agents/{healer.md → playwright-test-healer.agent.md} +5 -28
  10. package/lib/agents/playwright-test-plan.prompt.md +9 -0
  11. package/lib/agents/{planner.md → playwright-test-planner.agent.md} +5 -68
  12. package/lib/common/config.js +6 -0
  13. package/lib/common/expectBundle.js +0 -9
  14. package/lib/common/expectBundleImpl.js +267 -249
  15. package/lib/common/testLoader.js +3 -2
  16. package/lib/common/testType.js +3 -12
  17. package/lib/common/validators.js +68 -0
  18. package/lib/index.js +9 -9
  19. package/lib/isomorphic/teleReceiver.js +1 -0
  20. package/lib/isomorphic/testServerConnection.js +14 -0
  21. package/lib/loader/loaderMain.js +1 -1
  22. package/lib/matchers/expect.js +12 -13
  23. package/lib/matchers/matchers.js +16 -0
  24. package/lib/mcp/browser/browserServerBackend.js +1 -1
  25. package/lib/mcp/browser/config.js +9 -24
  26. package/lib/mcp/browser/context.js +4 -21
  27. package/lib/mcp/browser/response.js +20 -11
  28. package/lib/mcp/browser/tab.js +25 -10
  29. package/lib/mcp/browser/tools/evaluate.js +2 -3
  30. package/lib/mcp/browser/tools/form.js +2 -3
  31. package/lib/mcp/browser/tools/keyboard.js +4 -5
  32. package/lib/mcp/browser/tools/pdf.js +1 -1
  33. package/lib/mcp/browser/tools/runCode.js +75 -0
  34. package/lib/mcp/browser/tools/screenshot.js +33 -15
  35. package/lib/mcp/browser/tools/snapshot.js +13 -14
  36. package/lib/mcp/browser/tools/tabs.js +2 -2
  37. package/lib/mcp/browser/tools/utils.js +0 -11
  38. package/lib/mcp/browser/tools/verify.js +3 -4
  39. package/lib/mcp/browser/tools.js +2 -0
  40. package/lib/mcp/program.js +21 -1
  41. package/lib/mcp/sdk/exports.js +1 -3
  42. package/lib/mcp/sdk/http.js +9 -2
  43. package/lib/mcp/sdk/proxyBackend.js +1 -1
  44. package/lib/mcp/sdk/server.js +13 -5
  45. package/lib/mcp/sdk/tool.js +2 -6
  46. package/lib/mcp/test/browserBackend.js +43 -33
  47. package/lib/mcp/test/generatorTools.js +3 -3
  48. package/lib/mcp/test/plannerTools.js +103 -5
  49. package/lib/mcp/test/seed.js +25 -15
  50. package/lib/mcp/test/streams.js +9 -4
  51. package/lib/mcp/test/testBackend.js +31 -29
  52. package/lib/mcp/test/testContext.js +143 -40
  53. package/lib/mcp/test/testTools.js +12 -21
  54. package/lib/plugins/webServerPlugin.js +37 -9
  55. package/lib/program.js +11 -20
  56. package/lib/reporters/html.js +2 -23
  57. package/lib/reporters/internalReporter.js +4 -2
  58. package/lib/reporters/junit.js +4 -2
  59. package/lib/reporters/list.js +1 -5
  60. package/lib/reporters/merge.js +12 -6
  61. package/lib/reporters/teleEmitter.js +3 -1
  62. package/lib/runner/dispatcher.js +26 -2
  63. package/lib/runner/failureTracker.js +5 -5
  64. package/lib/runner/loadUtils.js +2 -1
  65. package/lib/runner/loaderHost.js +1 -1
  66. package/lib/runner/reporters.js +5 -4
  67. package/lib/runner/testRunner.js +8 -9
  68. package/lib/runner/testServer.js +8 -3
  69. package/lib/runner/workerHost.js +3 -0
  70. package/lib/worker/testInfo.js +28 -17
  71. package/lib/worker/testTracing.js +1 -0
  72. package/lib/worker/workerMain.js +15 -6
  73. package/package.json +2 -2
  74. package/types/test.d.ts +96 -3
  75. package/types/testReporter.d.ts +5 -0
  76. package/lib/mcp/sdk/mdb.js +0 -208
@@ -40,7 +40,7 @@ class ProxyBackend {
40
40
  this._mcpProviders = mcpProviders;
41
41
  this._contextSwitchTool = this._defineContextSwitchTool();
42
42
  }
43
- async initialize(server, clientInfo) {
43
+ async initialize(clientInfo) {
44
44
  this._clientInfo = clientInfo;
45
45
  }
46
46
  async listTools() {
@@ -41,6 +41,7 @@ var mcpBundle = __toESM(require("./bundle"));
41
41
  var import_http = require("./http");
42
42
  var import_inProcessTransport = require("./inProcessTransport");
43
43
  const serverDebug = (0, import_utilsBundle.debug)("pw:mcp:server");
44
+ const serverDebugResponse = (0, import_utilsBundle.debug)("pw:mcp:server:response");
44
45
  async function connect(factory, transport, runHeartbeat) {
45
46
  const server = createServer(factory.name, factory.version, factory.create(), runHeartbeat);
46
47
  await server.connect(transport);
@@ -81,7 +82,10 @@ function createServer(name, version, backend, runHeartbeat) {
81
82
  if (!initializePromise)
82
83
  initializePromise = initializeServer(server, backend, runHeartbeat);
83
84
  await initializePromise;
84
- return mergeTextParts(await backend.callTool(request.params.name, request.params.arguments || {}, progress));
85
+ const toolResult = await backend.callTool(request.params.name, request.params.arguments || {}, progress);
86
+ const mergedResult = mergeTextParts(toolResult);
87
+ serverDebugResponse("callResult", mergedResult);
88
+ return mergedResult;
85
89
  } catch (error) {
86
90
  return {
87
91
  content: [{ type: "text", text: "### Result\n" + String(error) }],
@@ -108,7 +112,7 @@ const initializeServer = async (server, backend, runHeartbeat) => {
108
112
  roots: clientRoots,
109
113
  timestamp: Date.now()
110
114
  };
111
- await backend.initialize?.(server, clientInfo);
115
+ await backend.initialize?.(clientInfo);
112
116
  if (runHeartbeat)
113
117
  startHeartbeat(server);
114
118
  };
@@ -138,8 +142,7 @@ async function start(serverBackendFactory, options) {
138
142
  return;
139
143
  }
140
144
  const httpServer = await (0, import_http.startHttpServer)(options);
141
- const url = (0, import_http.httpAddressToString)(httpServer.address());
142
- await (0, import_http.installHttpTransport)(httpServer, serverBackendFactory, options.allowedHosts);
145
+ const url = await (0, import_http.installHttpTransport)(httpServer, serverBackendFactory, false, options.allowedHosts);
143
146
  const mcpConfig = { mcpServers: {} };
144
147
  mcpConfig.mcpServers[serverBackendFactory.nameInConfig] = {
145
148
  url: `${url}/mcp`
@@ -157,7 +160,12 @@ function firstRootPath(clientInfo) {
157
160
  return void 0;
158
161
  const firstRootUri = clientInfo.roots[0]?.uri;
159
162
  const url = firstRootUri ? new URL(firstRootUri) : void 0;
160
- return url ? (0, import_url.fileURLToPath)(url) : void 0;
163
+ try {
164
+ return url ? (0, import_url.fileURLToPath)(url) : void 0;
165
+ } catch (error) {
166
+ serverDebug(error);
167
+ return void 0;
168
+ }
161
169
  }
162
170
  function mergeTextParts(result) {
163
171
  const content = [];
@@ -23,16 +23,12 @@ __export(tool_exports, {
23
23
  });
24
24
  module.exports = __toCommonJS(tool_exports);
25
25
  var import_bundle = require("../sdk/bundle");
26
- const typesWithIntent = ["action", "assertion", "input"];
27
- function toMcpTool(tool, options) {
28
- const inputSchema = options?.addIntent && typesWithIntent.includes(tool.type) ? tool.inputSchema.extend({
29
- intent: import_bundle.z.string().describe("The intent of the call, for example the test step description plan idea")
30
- }) : tool.inputSchema;
26
+ function toMcpTool(tool) {
31
27
  const readOnly = tool.type === "readOnly" || tool.type === "assertion";
32
28
  return {
33
29
  name: tool.name,
34
30
  description: tool.description,
35
- inputSchema: (0, import_bundle.zodToJsonSchema)(inputSchema, { strictUnions: true }),
31
+ inputSchema: (0, import_bundle.zodToJsonSchema)(tool.inputSchema, { strictUnions: true }),
36
32
  annotations: {
37
33
  title: tool.title,
38
34
  readOnlyHint: readOnly,
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
6
  var __export = (target, all) => {
9
7
  for (var name in all)
@@ -17,38 +15,54 @@ var __copyProps = (to, from, except, desc) => {
17
15
  }
18
16
  return to;
19
17
  };
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
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
19
  var browserBackend_exports = {};
30
20
  __export(browserBackend_exports, {
31
- runBrowserBackendAtEnd: () => runBrowserBackendAtEnd
21
+ createCustomMessageHandler: () => createCustomMessageHandler
32
22
  });
33
23
  module.exports = __toCommonJS(browserBackend_exports);
34
- var mcp = __toESM(require("../sdk/exports"));
35
- var import_globals = require("../../common/globals");
36
- var import_util = require("../../util");
37
24
  var import_config = require("../browser/config");
38
25
  var import_browserServerBackend = require("../browser/browserServerBackend");
39
26
  var import_tab = require("../browser/tab");
40
- async function runBrowserBackendAtEnd(context, errorMessage) {
41
- const testInfo = (0, import_globals.currentTestInfo)();
42
- if (!testInfo)
43
- return;
44
- const shouldPause = errorMessage ? testInfo?._pauseOnError() : testInfo?._pauseAtEnd();
45
- if (!shouldPause)
46
- return;
27
+ var import_util = require("../../util");
28
+ function createCustomMessageHandler(testInfo, context) {
29
+ let backend;
30
+ return async (data) => {
31
+ if (data.initialize) {
32
+ if (backend)
33
+ throw new Error("MCP backend is already initialized");
34
+ backend = new import_browserServerBackend.BrowserServerBackend({ ...import_config.defaultConfig, capabilities: ["testing"] }, identityFactory(context));
35
+ await backend.initialize(data.initialize.clientInfo);
36
+ const pausedMessage = await generatePausedMessage(testInfo, context);
37
+ return { initialize: { pausedMessage } };
38
+ }
39
+ if (data.listTools) {
40
+ if (!backend)
41
+ throw new Error("MCP backend is not initialized");
42
+ return { listTools: await backend.listTools() };
43
+ }
44
+ if (data.callTool) {
45
+ if (!backend)
46
+ throw new Error("MCP backend is not initialized");
47
+ return { callTool: await backend.callTool(data.callTool.name, data.callTool.arguments) };
48
+ }
49
+ if (data.close) {
50
+ backend?.serverClosed();
51
+ backend = void 0;
52
+ return { close: {} };
53
+ }
54
+ throw new Error("Unknown MCP request");
55
+ };
56
+ }
57
+ async function generatePausedMessage(testInfo, context) {
47
58
  const lines = [];
48
- if (errorMessage)
49
- lines.push(`### Paused on error:`, (0, import_util.stripAnsiEscapes)(errorMessage));
50
- else
59
+ if (testInfo.errors.length) {
60
+ lines.push(`### Paused on error:`);
61
+ for (const error of testInfo.errors)
62
+ lines.push((0, import_util.stripAnsiEscapes)(error.message || ""));
63
+ } else {
51
64
  lines.push(`### Paused at end of test. ready for interaction`);
65
+ }
52
66
  for (let i = 0; i < context.pages().length; i++) {
53
67
  const page = context.pages()[i];
54
68
  const stateSuffix = context.pages().length > 1 ? i + 1 + " of " + context.pages().length : "state";
@@ -58,7 +72,7 @@ async function runBrowserBackendAtEnd(context, errorMessage) {
58
72
  `- Page URL: ${page.url()}`,
59
73
  `- Page Title: ${await page.title()}`.trim()
60
74
  );
61
- let console = errorMessage ? await import_tab.Tab.collectConsoleMessages(page) : [];
75
+ let console = testInfo.errors.length ? await import_tab.Tab.collectConsoleMessages(page) : [];
62
76
  console = console.filter((msg) => !msg.type || msg.type === "error");
63
77
  if (console.length) {
64
78
  lines.push("- Console Messages:");
@@ -68,18 +82,14 @@ async function runBrowserBackendAtEnd(context, errorMessage) {
68
82
  lines.push(
69
83
  `- Page Snapshot:`,
70
84
  "```yaml",
71
- await page._snapshotForAI(),
85
+ (await page._snapshotForAI()).full,
72
86
  "```"
73
87
  );
74
88
  }
75
89
  lines.push("");
76
- if (errorMessage)
90
+ if (testInfo.errors.length)
77
91
  lines.push(`### Task`, `Try recovering from the error prior to continuing`);
78
- const config = {
79
- ...import_config.defaultConfig,
80
- capabilities: ["testing"]
81
- };
82
- await mcp.runOnPauseBackendLoop(new import_browserServerBackend.BrowserServerBackend(config, identityFactory(context)), lines.join("\n"));
92
+ return lines.join("\n");
83
93
  }
84
94
  function identityFactory(browserContext) {
85
95
  return {
@@ -94,5 +104,5 @@ function identityFactory(browserContext) {
94
104
  }
95
105
  // Annotate the CommonJS export names for ESM import in node:
96
106
  0 && (module.exports = {
97
- runBrowserBackendAtEnd
107
+ createCustomMessageHandler
98
108
  });
@@ -50,11 +50,11 @@ const setupPage = (0, import_testTool.defineTestTool)({
50
50
  }),
51
51
  type: "readOnly"
52
52
  },
53
- handle: async (context, params, progress) => {
53
+ handle: async (context, params) => {
54
54
  const seed = await context.getOrCreateSeedFile(params.seedFile, params.project);
55
55
  context.generatorJournal = new import_testContext.GeneratorJournal(context.rootPath, params.plan, seed);
56
- await context.runSeedTest(seed.file, seed.projectName, progress);
57
- return { content: [] };
56
+ const { output, status } = await context.runSeedTest(seed.file, seed.projectName);
57
+ return { content: [{ type: "text", text: output }], isError: status !== "paused" };
58
58
  }
59
59
  });
60
60
  const generatorReadLog = (0, import_testTool.defineTestTool)({
@@ -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,12 +17,24 @@ 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 plannerTools_exports = {};
20
30
  __export(plannerTools_exports, {
21
- setupPage: () => setupPage
31
+ saveTestPlan: () => saveTestPlan,
32
+ setupPage: () => setupPage,
33
+ submitTestPlan: () => submitTestPlan
22
34
  });
23
35
  module.exports = __toCommonJS(plannerTools_exports);
36
+ var import_fs = __toESM(require("fs"));
37
+ var import_path = __toESM(require("path"));
24
38
  var import_bundle = require("../sdk/bundle");
25
39
  var import_testTool = require("./testTool");
26
40
  const setupPage = (0, import_testTool.defineTestTool)({
@@ -34,13 +48,97 @@ const setupPage = (0, import_testTool.defineTestTool)({
34
48
  }),
35
49
  type: "readOnly"
36
50
  },
37
- handle: async (context, params, progress) => {
51
+ handle: async (context, params) => {
38
52
  const seed = await context.getOrCreateSeedFile(params.seedFile, params.project);
39
- await context.runSeedTest(seed.file, seed.projectName, progress);
40
- return { content: [] };
53
+ const { output, status } = await context.runSeedTest(seed.file, seed.projectName);
54
+ return { content: [{ type: "text", text: output }], isError: status !== "paused" };
55
+ }
56
+ });
57
+ const planSchema = import_bundle.z.object({
58
+ overview: import_bundle.z.string().describe("A brief overview of the application to be tested"),
59
+ suites: import_bundle.z.array(import_bundle.z.object({
60
+ name: import_bundle.z.string().describe("The name of the suite"),
61
+ seedFile: import_bundle.z.string().describe("A seed file that was used to setup the page for testing."),
62
+ tests: import_bundle.z.array(import_bundle.z.object({
63
+ name: import_bundle.z.string().describe("The name of the test"),
64
+ file: import_bundle.z.string().describe('The file the test should be saved to, for example: "tests/<suite-name>/<test-name>.spec.ts".'),
65
+ steps: import_bundle.z.array(import_bundle.z.string().describe(`The steps to be executed to perform the test. For example: 'Click on the "Submit" button'`)),
66
+ expectedResults: import_bundle.z.array(import_bundle.z.string().describe("The expected results of the steps for test to verify."))
67
+ }))
68
+ }))
69
+ });
70
+ const submitTestPlan = (0, import_testTool.defineTestTool)({
71
+ schema: {
72
+ name: "planner_submit_plan",
73
+ title: "Submit test plan",
74
+ description: "Submit the test plan to the test planner",
75
+ inputSchema: planSchema,
76
+ type: "readOnly"
77
+ },
78
+ handle: async (context, params) => {
79
+ return {
80
+ content: [{
81
+ type: "text",
82
+ text: JSON.stringify(params, null, 2)
83
+ }]
84
+ };
85
+ }
86
+ });
87
+ const saveTestPlan = (0, import_testTool.defineTestTool)({
88
+ schema: {
89
+ name: "planner_save_plan",
90
+ title: "Save test plan as markdown file",
91
+ description: "Save the test plan as a markdown file",
92
+ inputSchema: planSchema.extend({
93
+ name: import_bundle.z.string().describe('The name of the test plan, for example: "Test Plan".'),
94
+ fileName: import_bundle.z.string().describe('The file to save the test plan to, for example: "spec/test.plan.md". Relative to the workspace root.')
95
+ }),
96
+ type: "readOnly"
97
+ },
98
+ handle: async (context, params) => {
99
+ const lines = [];
100
+ lines.push(`# ${params.name}`);
101
+ lines.push(``);
102
+ lines.push(`## Application Overview`);
103
+ lines.push(``);
104
+ lines.push(params.overview);
105
+ lines.push(``);
106
+ lines.push(`## Test Scenarios`);
107
+ for (let i = 0; i < params.suites.length; i++) {
108
+ lines.push(``);
109
+ const suite = params.suites[i];
110
+ lines.push(`### ${i + 1}. ${suite.name}`);
111
+ lines.push(``);
112
+ lines.push(`**Seed:** \`${suite.seedFile}\``);
113
+ for (let j = 0; j < suite.tests.length; j++) {
114
+ lines.push(``);
115
+ const test = suite.tests[j];
116
+ lines.push(`#### ${i + 1}.${j + 1}. ${test.name}`);
117
+ lines.push(``);
118
+ lines.push(`**File:** \`${test.file}\``);
119
+ lines.push(``);
120
+ lines.push(`**Steps:**`);
121
+ for (let k = 0; k < test.steps.length; k++)
122
+ lines.push(` ${k + 1}. ${test.steps[k]}`);
123
+ lines.push(``);
124
+ lines.push(`**Expected Results:**`);
125
+ for (const result of test.expectedResults)
126
+ lines.push(` - ${result}`);
127
+ }
128
+ }
129
+ lines.push(``);
130
+ await import_fs.default.promises.writeFile(import_path.default.resolve(context.rootPath, params.fileName), lines.join("\n"));
131
+ return {
132
+ content: [{
133
+ type: "text",
134
+ text: `Test plan saved to ${params.fileName}`
135
+ }]
136
+ };
41
137
  }
42
138
  });
43
139
  // Annotate the CommonJS export names for ESM import in node:
44
140
  0 && (module.exports = {
45
- setupPage
141
+ saveTestPlan,
142
+ setupPage,
143
+ submitTestPlan
46
144
  });
@@ -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
- ensureSeedTest: () => ensureSeedTest,
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 ensureSeedTest(project, logNew) {
50
+ async function findSeedFile(project) {
48
51
  const files = await (0, import_projectUtils.collectFilesForProject)(project);
49
- const seed = files.find((file) => import_path.default.basename(file).includes("seed"));
50
- if (seed)
51
- return seed;
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
- const seedFile = import_path.default.resolve(testDir, "seed.spec.ts");
54
- if (logNew) {
55
- console.log(`Writing file: ${import_path.default.relative(process.cwd(), seedFile)}`);
56
- }
57
- await (0, import_utils.mkdirIfNeeded)(seedFile);
58
- await import_fs.default.promises.writeFile(seedFile, `import { test, expect } from '@playwright/test';
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
- ensureSeedTest,
77
+ defaultSeedFile,
78
+ ensureSeedFile,
79
+ findSeedFile,
80
+ seedFileContent,
71
81
  seedProject
72
82
  });
@@ -22,14 +22,19 @@ __export(streams_exports, {
22
22
  });
23
23
  module.exports = __toCommonJS(streams_exports);
24
24
  var import_stream = require("stream");
25
+ var import_util = require("../../util");
25
26
  class StringWriteStream extends import_stream.Writable {
26
- constructor(progress) {
27
+ constructor(output, stdio) {
27
28
  super();
28
- this._progress = progress;
29
+ this._output = output;
30
+ this._prefix = stdio === "stdout" ? "" : "[err] ";
29
31
  }
30
32
  _write(chunk, encoding, callback) {
31
- const text = chunk.toString();
32
- this._progress({ message: text.endsWith("\n") ? text.slice(0, -1) : text });
33
+ let text = (0, import_util.stripAnsiEscapes)(chunk.toString());
34
+ if (text.endsWith("\n"))
35
+ text = text.slice(0, -1);
36
+ if (text)
37
+ this._output.push(this._prefix + text);
33
38
  callback();
34
39
  }
35
40
  }
@@ -37,60 +37,62 @@ var testTools = __toESM(require("./testTools.js"));
37
37
  var generatorTools = __toESM(require("./generatorTools.js"));
38
38
  var plannerTools = __toESM(require("./plannerTools.js"));
39
39
  var import_tools = require("../browser/tools");
40
- var import_configLoader = require("../../common/configLoader");
41
- var import_response = require("../browser/response");
40
+ var import_bundle = require("../sdk/bundle");
42
41
  class TestServerBackend {
43
42
  constructor(configOption, options) {
44
43
  this.name = "Playwright";
45
44
  this.version = "0.0.1";
46
45
  this._tools = [
46
+ plannerTools.saveTestPlan,
47
47
  plannerTools.setupPage,
48
+ plannerTools.submitTestPlan,
48
49
  generatorTools.setupPage,
49
50
  generatorTools.generatorReadLog,
50
51
  generatorTools.generatorWriteTest,
51
52
  testTools.listTests,
52
53
  testTools.runTests,
53
- testTools.debugTest
54
+ testTools.debugTest,
55
+ ...import_tools.browserTools.map((tool) => wrapBrowserTool(tool))
54
56
  ];
55
- this._context = new import_testContext.TestContext(options);
57
+ this._options = options || {};
56
58
  this._configOption = configOption;
57
59
  }
58
- async initialize(server, clientInfo) {
59
- const rootPath = mcp.firstRootPath(clientInfo);
60
- if (this._configOption) {
61
- this._context.initialize(rootPath, (0, import_configLoader.resolveConfigLocation)(this._configOption));
62
- return;
63
- }
64
- if (rootPath) {
65
- this._context.initialize(rootPath, (0, import_configLoader.resolveConfigLocation)(rootPath));
66
- return;
67
- }
68
- this._context.initialize(rootPath, (0, import_configLoader.resolveConfigLocation)(void 0));
60
+ async initialize(clientInfo) {
61
+ this._context = new import_testContext.TestContext(clientInfo, this._configOption, this._options);
69
62
  }
70
63
  async listTools() {
71
- return [
72
- ...this._tools.map((tool) => mcp.toMcpTool(tool.schema)),
73
- ...import_tools.browserTools.map((tool) => mcp.toMcpTool(tool.schema, { addIntent: true }))
74
- ];
75
- }
76
- async afterCallTool(name, args, result) {
77
- if (!import_tools.browserTools.find((tool) => tool.schema.name === name))
78
- return;
79
- const response = (0, import_response.parseResponse)(result);
80
- if (response && !response.isError && response.code && typeof args?.["intent"] === "string")
81
- this._context.generatorJournal?.logStep(args["intent"], response.code);
64
+ return this._tools.map((tool) => mcp.toMcpTool(tool.schema));
82
65
  }
83
- async callTool(name, args, progress) {
66
+ async callTool(name, args) {
84
67
  const tool = this._tools.find((tool2) => tool2.schema.name === name);
85
68
  if (!tool)
86
69
  throw new Error(`Tool not found: ${name}. Available tools: ${this._tools.map((tool2) => tool2.schema.name).join(", ")}`);
87
- const parsedArguments = tool.schema.inputSchema.parse(args || {});
88
- return await tool.handle(this._context, parsedArguments, progress);
70
+ try {
71
+ return await tool.handle(this._context, tool.schema.inputSchema.parse(args || {}));
72
+ } catch (e) {
73
+ return { content: [{ type: "text", text: String(e) }], isError: true };
74
+ }
89
75
  }
90
76
  serverClosed() {
91
77
  void this._context.close();
92
78
  }
93
79
  }
80
+ const typesWithIntent = ["action", "assertion", "input"];
81
+ function wrapBrowserTool(tool) {
82
+ const inputSchema = typesWithIntent.includes(tool.schema.type) ? tool.schema.inputSchema.extend({
83
+ intent: import_bundle.z.string().describe("The intent of the call, for example the test step description plan idea")
84
+ }) : tool.schema.inputSchema;
85
+ return {
86
+ schema: {
87
+ ...tool.schema,
88
+ inputSchema
89
+ },
90
+ handle: async (context, params) => {
91
+ const response = await context.sendMessageToPausedTest({ callTool: { name: tool.schema.name, arguments: params } });
92
+ return response.callTool;
93
+ }
94
+ };
95
+ }
94
96
  // Annotate the CommonJS export names for ESM import in node:
95
97
  0 && (module.exports = {
96
98
  TestServerBackend