patchright-bun 1.58.2 → 1.59.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 (108) hide show
  1. package/ThirdPartyNotices.txt +10 -1133
  2. package/lib/agents/generateAgents.js +2 -4
  3. package/lib/common/config.js +4 -5
  4. package/lib/common/expectBundleImpl.js +221 -221
  5. package/lib/common/process.js +1 -0
  6. package/lib/common/test.js +10 -1
  7. package/lib/common/testType.js +3 -0
  8. package/lib/common/validators.js +38 -38
  9. package/lib/errorContext.js +121 -0
  10. package/lib/index.js +98 -60
  11. package/lib/isomorphic/teleReceiver.js +26 -9
  12. package/lib/isomorphic/testServerConnection.js +3 -5
  13. package/lib/matchers/matchers.js +2 -0
  14. package/lib/matchers/toMatchAriaSnapshot.js +5 -1
  15. package/lib/matchers/toMatchSnapshot.js +42 -35
  16. package/lib/mcp/test/browserBackend.js +45 -22
  17. package/lib/mcp/test/generatorTools.js +9 -9
  18. package/lib/mcp/test/plannerTools.js +17 -17
  19. package/lib/mcp/test/testBackend.js +27 -27
  20. package/lib/mcp/test/testContext.js +6 -8
  21. package/lib/mcp/test/testTools.js +9 -9
  22. package/lib/plugins/webServerPlugin.js +2 -1
  23. package/lib/program.js +34 -212
  24. package/lib/reportActions.js +80 -0
  25. package/lib/reporters/base.js +4 -5
  26. package/lib/reporters/blob.js +2 -2
  27. package/lib/reporters/github.js +1 -2
  28. package/lib/reporters/html.js +64 -31
  29. package/lib/reporters/junit.js +104 -15
  30. package/lib/reporters/line.js +1 -1
  31. package/lib/reporters/list.js +2 -3
  32. package/lib/reporters/merge.js +47 -26
  33. package/lib/reporters/multiplexer.js +6 -2
  34. package/lib/reporters/teleEmitter.js +2 -0
  35. package/lib/runner/dispatcher.js +6 -14
  36. package/lib/runner/loadUtils.js +11 -5
  37. package/lib/runner/projectUtils.js +1 -1
  38. package/lib/runner/reporters.js +5 -0
  39. package/lib/runner/tasks.js +11 -8
  40. package/lib/runner/testRunner.js +2 -2
  41. package/lib/runner/workerHost.js +0 -3
  42. package/lib/testActions.js +220 -0
  43. package/lib/transform/babelBundle.js +0 -3
  44. package/lib/transform/babelBundleImpl.js +134 -134
  45. package/lib/transform/compilationCache.js +0 -2
  46. package/lib/transform/esmLoader.js +8 -6
  47. package/lib/transform/transform.js +3 -10
  48. package/lib/utilsBundle.js +0 -7
  49. package/lib/utilsBundleImpl.js +48 -51
  50. package/lib/worker/fixtureRunner.js +2 -2
  51. package/lib/worker/testInfo.js +10 -14
  52. package/lib/worker/testTracing.js +12 -6
  53. package/lib/worker/timeoutManager.js +14 -3
  54. package/lib/worker/workerMain.js +24 -21
  55. package/package.json +2 -6
  56. package/types/test.d.ts +83 -12
  57. package/lib/mcp/browser/browserContextFactory.js +0 -329
  58. package/lib/mcp/browser/browserServerBackend.js +0 -84
  59. package/lib/mcp/browser/config.js +0 -421
  60. package/lib/mcp/browser/context.js +0 -244
  61. package/lib/mcp/browser/response.js +0 -278
  62. package/lib/mcp/browser/sessionLog.js +0 -75
  63. package/lib/mcp/browser/tab.js +0 -343
  64. package/lib/mcp/browser/tools/common.js +0 -65
  65. package/lib/mcp/browser/tools/console.js +0 -46
  66. package/lib/mcp/browser/tools/dialogs.js +0 -60
  67. package/lib/mcp/browser/tools/evaluate.js +0 -61
  68. package/lib/mcp/browser/tools/files.js +0 -58
  69. package/lib/mcp/browser/tools/form.js +0 -63
  70. package/lib/mcp/browser/tools/install.js +0 -72
  71. package/lib/mcp/browser/tools/keyboard.js +0 -107
  72. package/lib/mcp/browser/tools/mouse.js +0 -107
  73. package/lib/mcp/browser/tools/navigate.js +0 -71
  74. package/lib/mcp/browser/tools/network.js +0 -63
  75. package/lib/mcp/browser/tools/open.js +0 -57
  76. package/lib/mcp/browser/tools/pdf.js +0 -49
  77. package/lib/mcp/browser/tools/runCode.js +0 -78
  78. package/lib/mcp/browser/tools/screenshot.js +0 -93
  79. package/lib/mcp/browser/tools/snapshot.js +0 -173
  80. package/lib/mcp/browser/tools/tabs.js +0 -67
  81. package/lib/mcp/browser/tools/tool.js +0 -47
  82. package/lib/mcp/browser/tools/tracing.js +0 -74
  83. package/lib/mcp/browser/tools/utils.js +0 -94
  84. package/lib/mcp/browser/tools/verify.js +0 -143
  85. package/lib/mcp/browser/tools/wait.js +0 -63
  86. package/lib/mcp/browser/tools.js +0 -84
  87. package/lib/mcp/browser/watchdog.js +0 -44
  88. package/lib/mcp/config.d.js +0 -16
  89. package/lib/mcp/extension/cdpRelay.js +0 -351
  90. package/lib/mcp/extension/extensionContextFactory.js +0 -76
  91. package/lib/mcp/extension/protocol.js +0 -28
  92. package/lib/mcp/index.js +0 -61
  93. package/lib/mcp/log.js +0 -35
  94. package/lib/mcp/program.js +0 -111
  95. package/lib/mcp/sdk/exports.js +0 -28
  96. package/lib/mcp/sdk/http.js +0 -152
  97. package/lib/mcp/sdk/inProcessTransport.js +0 -71
  98. package/lib/mcp/sdk/server.js +0 -223
  99. package/lib/mcp/sdk/tool.js +0 -47
  100. package/lib/mcp/terminal/cli.js +0 -296
  101. package/lib/mcp/terminal/command.js +0 -56
  102. package/lib/mcp/terminal/commands.js +0 -333
  103. package/lib/mcp/terminal/daemon.js +0 -129
  104. package/lib/mcp/terminal/help.json +0 -32
  105. package/lib/mcp/terminal/helpGenerator.js +0 -88
  106. package/lib/mcp/terminal/socketConnection.js +0 -80
  107. package/lib/runner/storage.js +0 -91
  108. package/lib/transform/md.js +0 -221
@@ -278,6 +278,8 @@ function toHaveTitle(page, expected, options = {}) {
278
278
  }, expected, options);
279
279
  }
280
280
  function toHaveURL(page, expected, options) {
281
+ if ((0, import_utils.isURLPattern)(expected))
282
+ return import_toHaveURL.toHaveURLWithPredicate.call(this, page, (url) => expected.test(url.href), options);
281
283
  if (typeof expected === "function")
282
284
  return import_toHaveURL.toHaveURLWithPredicate.call(this, page, expected, options);
283
285
  const baseURL = page.context()._options.baseURL;
@@ -41,7 +41,7 @@ async function toMatchAriaSnapshot(locator, expectedParam, options = {}) {
41
41
  const testInfo = (0, import_globals.currentTestInfo)();
42
42
  if (!testInfo)
43
43
  throw new Error(`toMatchAriaSnapshot() must be called during the test`);
44
- if (testInfo._projectInternal.ignoreSnapshots)
44
+ if (testInfo._projectInternal.project.ignoreSnapshots)
45
45
  return { pass: !this.isNot, message: () => "", name: "toMatchAriaSnapshot", expected: "" };
46
46
  const updateSnapshots = testInfo.config.updateSnapshots;
47
47
  let expected;
@@ -68,6 +68,10 @@ async function toMatchAriaSnapshot(locator, expectedParam, options = {}) {
68
68
  }
69
69
  }
70
70
  expected = unshift(expected);
71
+ const globalChildren = testInfo._projectInternal.expect?.toMatchAriaSnapshot?.children;
72
+ if (globalChildren && !expected.match(/^- \/children:/m))
73
+ expected = `- /children: ${globalChildren}
74
+ ` + expected;
71
75
  const { matches: pass, received, log, timedOut, errorMessage } = await locator._expect("to.match.aria", { expectedValue: expected, isNot: this.isNot, timeout });
72
76
  const typedReceived = received;
73
77
  const message = () => {
@@ -177,7 +177,7 @@ function toMatchSnapshot(received, nameOrOptions = {}, optOptions = {}) {
177
177
  throw new Error(`toMatchSnapshot() must be called during the test`);
178
178
  if (received instanceof Promise)
179
179
  throw new Error("An unresolved Promise was passed to toMatchSnapshot(), make sure to resolve it by adding await to it.");
180
- if (testInfo._projectInternal.ignoreSnapshots)
180
+ if (testInfo._projectInternal.project.ignoreSnapshots)
181
181
  return { pass: !this.isNot, message: () => "", name: "toMatchSnapshot", expected: nameOrOptions };
182
182
  const configOptions = testInfo._projectInternal.expect?.toMatchSnapshot || {};
183
183
  const helper = new SnapshotHelper(
@@ -232,7 +232,7 @@ async function toHaveScreenshot(pageOrLocator, nameOrOptions = {}, optOptions =
232
232
  const testInfo = (0, import_globals.currentTestInfo)();
233
233
  if (!testInfo)
234
234
  throw new Error(`toHaveScreenshot() must be called during the test`);
235
- if (testInfo._projectInternal.ignoreSnapshots)
235
+ if (testInfo._projectInternal.project.ignoreSnapshots)
236
236
  return { pass: !this.isNot, message: () => "", name: "toHaveScreenshot", expected: nameOrOptions };
237
237
  (0, import_util.expectTypes)(pageOrLocator, ["Page", "Locator"], "toHaveScreenshot");
238
238
  const [page, locator] = pageOrLocator.constructor.name === "Page" ? [pageOrLocator, void 0] : [pageOrLocator.page(), pageOrLocator];
@@ -265,45 +265,52 @@ async function toHaveScreenshot(pageOrLocator, nameOrOptions = {}, optOptions =
265
265
  if (this.isNot) {
266
266
  if (!hasSnapshot)
267
267
  return helper.handleMissingNegated();
268
- expectScreenshotOptions.expected = await import_fs.default.promises.readFile(helper.expectedPath);
269
- const isDifferent = !(await page._expectScreenshot(expectScreenshotOptions)).errorMessage;
270
- return isDifferent ? helper.handleDifferentNegated() : helper.handleMatchingNegated();
271
268
  }
272
- if (helper.updateSnapshots === "none" && !hasSnapshot)
269
+ if (!this.isNot && helper.updateSnapshots === "none" && !hasSnapshot)
273
270
  return helper.createMatcherResult(`A snapshot doesn't exist at ${helper.expectedPath}.`, false);
274
- if (!hasSnapshot) {
275
- const { actual: actual2, previous: previous2, diff: diff2, errorMessage: errorMessage2, log: log2, timedOut: timedOut2 } = await page._expectScreenshot(expectScreenshotOptions);
276
- if (errorMessage2) {
277
- const header2 = (0, import_utils.formatMatcherMessage)(this.utils, { promise: this.promise, isNot: this.isNot, matcherName: "toHaveScreenshot", locator: locator?.toString(), expectation: "expected", timeout, timedOut: timedOut2 });
278
- return helper.handleDifferent(actual2, void 0, previous2, diff2, header2, errorMessage2, log2, this._stepInfo);
271
+ await page.screencast.hideOverlays();
272
+ try {
273
+ if (this.isNot) {
274
+ expectScreenshotOptions.expected = await import_fs.default.promises.readFile(helper.expectedPath);
275
+ const isDifferent = !(await page._expectScreenshot(expectScreenshotOptions)).errorMessage;
276
+ return isDifferent ? helper.handleDifferentNegated() : helper.handleMatchingNegated();
279
277
  }
280
- return helper.handleMissing(actual2, this._stepInfo);
281
- }
282
- const expected = await import_fs.default.promises.readFile(helper.expectedPath);
283
- expectScreenshotOptions.expected = helper.updateSnapshots === "all" ? void 0 : expected;
284
- const { actual, previous, diff, errorMessage, log, timedOut } = await page._expectScreenshot(expectScreenshotOptions);
285
- const writeFiles = (actualBuffer) => {
286
- writeFileSync(helper.expectedPath, actualBuffer);
287
- writeFileSync(helper.actualPath, actualBuffer);
288
- console.log(helper.expectedPath + " is re-generated, writing actual.");
289
- return helper.createMatcherResult(helper.expectedPath + " running with --update-snapshots, writing actual.", true);
290
- };
291
- if (!errorMessage) {
292
- if (helper.updateSnapshots === "all" && actual && (0, import_utils.compareBuffersOrStrings)(actual, expected)) {
278
+ if (!hasSnapshot) {
279
+ const { actual: actual2, previous: previous2, diff: diff2, errorMessage: errorMessage2, log: log2, timedOut: timedOut2 } = await page._expectScreenshot(expectScreenshotOptions);
280
+ if (errorMessage2) {
281
+ const header2 = (0, import_utils.formatMatcherMessage)(this.utils, { promise: this.promise, isNot: this.isNot, matcherName: "toHaveScreenshot", locator: locator?.toString(), expectation: "expected", timeout, timedOut: timedOut2 });
282
+ return helper.handleDifferent(actual2, void 0, previous2, diff2, header2, errorMessage2, log2, this._stepInfo);
283
+ }
284
+ return helper.handleMissing(actual2, this._stepInfo);
285
+ }
286
+ const expected = await import_fs.default.promises.readFile(helper.expectedPath);
287
+ expectScreenshotOptions.expected = helper.updateSnapshots === "all" ? void 0 : expected;
288
+ const { actual, previous, diff, errorMessage, log, timedOut } = await page._expectScreenshot(expectScreenshotOptions);
289
+ const writeFiles = (actualBuffer) => {
290
+ writeFileSync(helper.expectedPath, actualBuffer);
291
+ writeFileSync(helper.actualPath, actualBuffer);
293
292
  console.log(helper.expectedPath + " is re-generated, writing actual.");
294
- return writeFiles(actual);
293
+ return helper.createMatcherResult(helper.expectedPath + " running with --update-snapshots, writing actual.", true);
294
+ };
295
+ if (!errorMessage) {
296
+ if (helper.updateSnapshots === "all" && actual && (0, import_utils.compareBuffersOrStrings)(actual, expected)) {
297
+ console.log(helper.expectedPath + " is re-generated, writing actual.");
298
+ return writeFiles(actual);
299
+ }
300
+ return helper.handleMatching();
295
301
  }
296
- return helper.handleMatching();
297
- }
298
- if (helper.updateSnapshots === "changed" || helper.updateSnapshots === "all") {
299
- if (actual)
300
- return writeFiles(actual);
301
- let header2 = (0, import_utils.formatMatcherMessage)(this.utils, { promise: this.promise, isNot: this.isNot, matcherName: "toHaveScreenshot", locator: locator?.toString(), expectation: "expected", timeout, timedOut });
302
- header2 += " Failed to re-generate expected.\n";
303
- return helper.handleDifferent(actual, expectScreenshotOptions.expected, previous, diff, header2, errorMessage, log, this._stepInfo);
302
+ if (helper.updateSnapshots === "changed" || helper.updateSnapshots === "all") {
303
+ if (actual)
304
+ return writeFiles(actual);
305
+ let header2 = (0, import_utils.formatMatcherMessage)(this.utils, { promise: this.promise, isNot: this.isNot, matcherName: "toHaveScreenshot", locator: locator?.toString(), expectation: "expected", timeout, timedOut });
306
+ header2 += " Failed to re-generate expected.\n";
307
+ return helper.handleDifferent(actual, expectScreenshotOptions.expected, previous, diff, header2, errorMessage, log, this._stepInfo);
308
+ }
309
+ const header = (0, import_utils.formatMatcherMessage)(this.utils, { promise: this.promise, isNot: this.isNot, matcherName: "toHaveScreenshot", locator: locator?.toString(), expectation: "expected", timeout, timedOut });
310
+ return helper.handleDifferent(actual, expectScreenshotOptions.expected, previous, diff, header, errorMessage, log, this._stepInfo);
311
+ } finally {
312
+ await page.screencast.showOverlays();
304
313
  }
305
- const header = (0, import_utils.formatMatcherMessage)(this.utils, { promise: this.promise, isNot: this.isNot, matcherName: "toHaveScreenshot", locator: locator?.toString(), expectation: "expected", timeout, timedOut });
306
- return helper.handleDifferent(actual, expectScreenshotOptions.expected, previous, diff, header, errorMessage, log, this._stepInfo);
307
314
  }
308
315
  function writeFileSync(aPath, content) {
309
316
  import_fs.default.mkdirSync(import_path.default.dirname(aPath), { recursive: true });
@@ -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,52 +17,58 @@ 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 browserBackend_exports = {};
20
30
  __export(browserBackend_exports, {
21
- createCustomMessageHandler: () => createCustomMessageHandler
31
+ createCustomMessageHandler: () => createCustomMessageHandler,
32
+ runDaemonForContext: () => runDaemonForContext
22
33
  });
23
34
  module.exports = __toCommonJS(browserBackend_exports);
24
- var import_config = require("../browser/config");
25
- var import_browserServerBackend = require("../browser/browserServerBackend");
26
- var import_tab = require("../browser/tab");
27
- var import_util = require("../../util");
28
- var import_browserContextFactory = require("../browser/browserContextFactory");
35
+ var import_crypto = __toESM(require("crypto"));
36
+ var import_utils = require("patchright-bun-core/lib/utils");
29
37
  function createCustomMessageHandler(testInfo, context) {
30
38
  let backend;
39
+ const config = { capabilities: ["testing"] };
40
+ let tools;
31
41
  return async (data) => {
42
+ if (!tools)
43
+ tools = await import("playwright-core/lib/tools/exports");
44
+ const toolList = tools.filteredTools(config);
32
45
  if (data.initialize) {
33
46
  if (backend)
34
47
  throw new Error("MCP backend is already initialized");
35
- backend = new import_browserServerBackend.BrowserServerBackend({ ...import_config.defaultConfig, capabilities: ["testing"] }, (0, import_browserContextFactory.identityBrowserContextFactory)(context));
48
+ backend = new tools.BrowserBackend(config, context, toolList);
36
49
  await backend.initialize(data.initialize.clientInfo);
37
- const pausedMessage = await generatePausedMessage(testInfo, context);
50
+ const pausedMessage = await generatePausedMessage(tools, testInfo, context);
38
51
  return { initialize: { pausedMessage } };
39
52
  }
40
- if (data.listTools) {
41
- if (!backend)
42
- throw new Error("MCP backend is not initialized");
43
- return { listTools: await backend.listTools() };
44
- }
45
53
  if (data.callTool) {
46
54
  if (!backend)
47
55
  throw new Error("MCP backend is not initialized");
48
56
  return { callTool: await backend.callTool(data.callTool.name, data.callTool.arguments) };
49
57
  }
50
58
  if (data.close) {
51
- backend?.serverClosed();
59
+ await backend?.dispose();
52
60
  backend = void 0;
53
61
  return { close: {} };
54
62
  }
55
63
  throw new Error("Unknown MCP request");
56
64
  };
57
65
  }
58
- async function generatePausedMessage(testInfo, context) {
66
+ async function generatePausedMessage(tools, testInfo, context) {
59
67
  const lines = [];
60
68
  if (testInfo.errors.length) {
61
69
  lines.push(`### Paused on error:`);
62
70
  for (const error of testInfo.errors)
63
- lines.push((0, import_util.stripAnsiEscapes)(error.message || ""));
71
+ lines.push((0, import_utils.stripAnsiEscapes)(error.message || ""));
64
72
  } else {
65
73
  lines.push(`### Paused at end of test. ready for interaction`);
66
74
  }
@@ -73,17 +81,17 @@ async function generatePausedMessage(testInfo, context) {
73
81
  `- Page URL: ${page.url()}`,
74
82
  `- Page Title: ${await page.title()}`.trim()
75
83
  );
76
- let console = testInfo.errors.length ? await import_tab.Tab.collectConsoleMessages(page) : [];
77
- console = console.filter((msg) => msg.type === "error");
78
- if (console.length) {
84
+ let console2 = testInfo.errors.length ? await tools.Tab.collectConsoleMessages(page) : [];
85
+ console2 = console2.filter((msg) => msg.type === "error");
86
+ if (console2.length) {
79
87
  lines.push("- Console Messages:");
80
- for (const message of console)
88
+ for (const message of console2)
81
89
  lines.push(` - ${message.toString()}`);
82
90
  }
83
91
  lines.push(
84
92
  `- Page Snapshot:`,
85
93
  "```yaml",
86
- (await page._snapshotForAI()).full,
94
+ await page.ariaSnapshot({ mode: "ai" }),
87
95
  "```"
88
96
  );
89
97
  }
@@ -92,7 +100,22 @@ async function generatePausedMessage(testInfo, context) {
92
100
  lines.push(`### Task`, `Try recovering from the error prior to continuing`);
93
101
  return lines.join("\n");
94
102
  }
103
+ async function runDaemonForContext(testInfo, context) {
104
+ if (testInfo._configInternal.configCLIOverrides.debug !== "cli")
105
+ return false;
106
+ const sessionName = `tw-${import_crypto.default.randomBytes(3).toString("hex")}`;
107
+ await context.browser().bind(sessionName, { workspaceDir: testInfo.project.testDir });
108
+ console.log([
109
+ `### The test is currently paused at the start`,
110
+ ``,
111
+ `### Debugging Instructions`,
112
+ `- Run "playwright-cli attach ${sessionName}" to attach to this test`
113
+ ].join("\n"));
114
+ await context.debugger.requestPause();
115
+ return true;
116
+ }
95
117
  // Annotate the CommonJS export names for ESM import in node:
96
118
  0 && (module.exports = {
97
- createCustomMessageHandler
119
+ createCustomMessageHandler,
120
+ runDaemonForContext
98
121
  });
@@ -35,7 +35,7 @@ __export(generatorTools_exports, {
35
35
  module.exports = __toCommonJS(generatorTools_exports);
36
36
  var import_fs = __toESM(require("fs"));
37
37
  var import_path = __toESM(require("path"));
38
- var import_mcpBundle = require("patchright-bun-core/lib/mcpBundle");
38
+ var import_zodBundle = require("patchright-bun-core/lib/zodBundle");
39
39
  var import_testTool = require("./testTool");
40
40
  var import_testContext = require("./testContext");
41
41
  const setupPage = (0, import_testTool.defineTestTool)({
@@ -43,10 +43,10 @@ const setupPage = (0, import_testTool.defineTestTool)({
43
43
  name: "generator_setup_page",
44
44
  title: "Setup generator page",
45
45
  description: "Setup the page for test.",
46
- inputSchema: import_mcpBundle.z.object({
47
- plan: import_mcpBundle.z.string().describe("The plan for the test. This should be the actual test plan with all the steps."),
48
- project: import_mcpBundle.z.string().optional().describe('Project to use for setup. For example: "chromium", if no project is provided uses the first project in the config.'),
49
- seedFile: import_mcpBundle.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.')
46
+ inputSchema: import_zodBundle.z.object({
47
+ plan: import_zodBundle.z.string().describe("The plan for the test. This should be the actual test plan with all the steps."),
48
+ project: import_zodBundle.z.string().optional().describe('Project to use for setup. For example: "chromium", if no project is provided uses the first project in the config.'),
49
+ seedFile: import_zodBundle.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.')
50
50
  }),
51
51
  type: "readOnly"
52
52
  },
@@ -62,7 +62,7 @@ const generatorReadLog = (0, import_testTool.defineTestTool)({
62
62
  name: "generator_read_log",
63
63
  title: "Retrieve test log",
64
64
  description: "Retrieve the performed test log",
65
- inputSchema: import_mcpBundle.z.object({}),
65
+ inputSchema: import_zodBundle.z.object({}),
66
66
  type: "readOnly"
67
67
  },
68
68
  handle: async (context) => {
@@ -80,9 +80,9 @@ const generatorWriteTest = (0, import_testTool.defineTestTool)({
80
80
  name: "generator_write_test",
81
81
  title: "Write test",
82
82
  description: "Write the generated test to the test file",
83
- inputSchema: import_mcpBundle.z.object({
84
- fileName: import_mcpBundle.z.string().describe("The file to write the test to"),
85
- code: import_mcpBundle.z.string().describe("The generated test code")
83
+ inputSchema: import_zodBundle.z.object({
84
+ fileName: import_zodBundle.z.string().describe("The file to write the test to"),
85
+ code: import_zodBundle.z.string().describe("The generated test code")
86
86
  }),
87
87
  type: "readOnly"
88
88
  },
@@ -35,16 +35,16 @@ __export(plannerTools_exports, {
35
35
  module.exports = __toCommonJS(plannerTools_exports);
36
36
  var import_fs = __toESM(require("fs"));
37
37
  var import_path = __toESM(require("path"));
38
- var import_mcpBundle = require("patchright-bun-core/lib/mcpBundle");
38
+ var import_zodBundle = require("patchright-bun-core/lib/zodBundle");
39
39
  var import_testTool = require("./testTool");
40
40
  const setupPage = (0, import_testTool.defineTestTool)({
41
41
  schema: {
42
42
  name: "planner_setup_page",
43
43
  title: "Setup planner page",
44
44
  description: "Setup the page for test planning",
45
- inputSchema: import_mcpBundle.z.object({
46
- project: import_mcpBundle.z.string().optional().describe('Project to use for setup. For example: "chromium", if no project is provided uses the first project in the config.'),
47
- seedFile: import_mcpBundle.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.')
45
+ inputSchema: import_zodBundle.z.object({
46
+ project: import_zodBundle.z.string().optional().describe('Project to use for setup. For example: "chromium", if no project is provided uses the first project in the config.'),
47
+ seedFile: import_zodBundle.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.')
48
48
  }),
49
49
  type: "readOnly"
50
50
  },
@@ -54,17 +54,17 @@ const setupPage = (0, import_testTool.defineTestTool)({
54
54
  return { content: [{ type: "text", text: output }], isError: status !== "paused" };
55
55
  }
56
56
  });
57
- const planSchema = import_mcpBundle.z.object({
58
- overview: import_mcpBundle.z.string().describe("A brief overview of the application to be tested"),
59
- suites: import_mcpBundle.z.array(import_mcpBundle.z.object({
60
- name: import_mcpBundle.z.string().describe("The name of the suite"),
61
- seedFile: import_mcpBundle.z.string().describe("A seed file that was used to setup the page for testing."),
62
- tests: import_mcpBundle.z.array(import_mcpBundle.z.object({
63
- name: import_mcpBundle.z.string().describe("The name of the test"),
64
- file: import_mcpBundle.z.string().describe('The file the test should be saved to, for example: "tests/<suite-name>/<test-name>.spec.ts".'),
65
- steps: import_mcpBundle.z.array(import_mcpBundle.z.object({
66
- perform: import_mcpBundle.z.string().optional().describe(`Action to perform. For example: 'Click on the "Submit" button'.`),
67
- expect: import_mcpBundle.z.string().array().describe(`Expected result of the action where appropriate. For example: 'The page should show the "Thank you for your submission" message'`)
57
+ const planSchema = import_zodBundle.z.object({
58
+ overview: import_zodBundle.z.string().describe("A brief overview of the application to be tested"),
59
+ suites: import_zodBundle.z.array(import_zodBundle.z.object({
60
+ name: import_zodBundle.z.string().describe("The name of the suite"),
61
+ seedFile: import_zodBundle.z.string().describe("A seed file that was used to setup the page for testing."),
62
+ tests: import_zodBundle.z.array(import_zodBundle.z.object({
63
+ name: import_zodBundle.z.string().describe("The name of the test"),
64
+ file: import_zodBundle.z.string().describe('The file the test should be saved to, for example: "tests/<suite-name>/<test-name>.spec.ts".'),
65
+ steps: import_zodBundle.z.array(import_zodBundle.z.object({
66
+ perform: import_zodBundle.z.string().optional().describe(`Action to perform. For example: 'Click on the "Submit" button'.`),
67
+ expect: import_zodBundle.z.string().array().describe(`Expected result of the action where appropriate. For example: 'The page should show the "Thank you for your submission" message'`)
68
68
  }))
69
69
  }))
70
70
  }))
@@ -92,8 +92,8 @@ const saveTestPlan = (0, import_testTool.defineTestTool)({
92
92
  title: "Save test plan as markdown file",
93
93
  description: "Save the test plan as a markdown file",
94
94
  inputSchema: planSchema.extend({
95
- name: import_mcpBundle.z.string().describe('The name of the test plan, for example: "Test Plan".'),
96
- fileName: import_mcpBundle.z.string().describe('The file to save the test plan to, for example: "spec/test.plan.md". Relative to the workspace root.')
95
+ name: import_zodBundle.z.string().describe('The name of the test plan, for example: "Test Plan".'),
96
+ fileName: import_zodBundle.z.string().describe('The file to save the test plan to, for example: "spec/test.plan.md". Relative to the workspace root.')
97
97
  }),
98
98
  type: "readOnly"
99
99
  },
@@ -28,59 +28,58 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
  var testBackend_exports = {};
30
30
  __export(testBackend_exports, {
31
- TestServerBackend: () => TestServerBackend
31
+ TestServerBackend: () => TestServerBackend,
32
+ testServerBackendTools: () => testServerBackendTools
32
33
  });
33
34
  module.exports = __toCommonJS(testBackend_exports);
34
- var import_mcpBundle = require("patchright-bun-core/lib/mcpBundle");
35
- var mcp = __toESM(require("../sdk/exports"));
35
+ var import_events = __toESM(require("events"));
36
+ var import_zodBundle = require("patchright-bun-core/lib/zodBundle");
37
+ var import_exports = require("patchright-bun-core/lib/tools/exports");
36
38
  var import_testContext = require("./testContext");
37
39
  var testTools = __toESM(require("./testTools.js"));
38
40
  var generatorTools = __toESM(require("./generatorTools.js"));
39
41
  var plannerTools = __toESM(require("./plannerTools.js"));
40
- var import_tools = require("../browser/tools");
41
- class TestServerBackend {
42
+ const typesWithIntent = ["action", "assertion", "input"];
43
+ const testServerBackendTools = [
44
+ plannerTools.saveTestPlan,
45
+ plannerTools.setupPage,
46
+ plannerTools.submitTestPlan,
47
+ generatorTools.setupPage,
48
+ generatorTools.generatorReadLog,
49
+ generatorTools.generatorWriteTest,
50
+ testTools.listTests,
51
+ testTools.runTests,
52
+ testTools.debugTest,
53
+ ...import_exports.browserTools.map((tool) => wrapBrowserTool(tool))
54
+ ];
55
+ class TestServerBackend extends import_events.default {
42
56
  constructor(configPath, options) {
57
+ super();
43
58
  this.name = "Playwright";
44
59
  this.version = "0.0.1";
45
- this._tools = [
46
- plannerTools.saveTestPlan,
47
- plannerTools.setupPage,
48
- plannerTools.submitTestPlan,
49
- generatorTools.setupPage,
50
- generatorTools.generatorReadLog,
51
- generatorTools.generatorWriteTest,
52
- testTools.listTests,
53
- testTools.runTests,
54
- testTools.debugTest,
55
- ...import_tools.browserTools.map((tool) => wrapBrowserTool(tool))
56
- ];
57
60
  this._options = options || {};
58
61
  this._configPath = configPath;
59
62
  }
60
63
  async initialize(clientInfo) {
61
64
  this._context = new import_testContext.TestContext(clientInfo, this._configPath, this._options);
62
65
  }
63
- async listTools() {
64
- return this._tools.map((tool) => mcp.toMcpTool(tool.schema));
65
- }
66
66
  async callTool(name, args) {
67
- const tool = this._tools.find((tool2) => tool2.schema.name === name);
67
+ const tool = testServerBackendTools.find((tool2) => tool2.schema.name === name);
68
68
  if (!tool)
69
- throw new Error(`Tool not found: ${name}. Available tools: ${this._tools.map((tool2) => tool2.schema.name).join(", ")}`);
69
+ throw new Error(`Tool not found: ${name}. Available tools: ${testServerBackendTools.map((tool2) => tool2.schema.name).join(", ")}`);
70
70
  try {
71
71
  return await tool.handle(this._context, tool.schema.inputSchema.parse(args || {}));
72
72
  } catch (e) {
73
73
  return { content: [{ type: "text", text: String(e) }], isError: true };
74
74
  }
75
75
  }
76
- serverClosed() {
77
- void this._context?.close();
76
+ async dispose() {
77
+ await this._context?.close();
78
78
  }
79
79
  }
80
- const typesWithIntent = ["action", "assertion", "input"];
81
80
  function wrapBrowserTool(tool) {
82
81
  const inputSchema = typesWithIntent.includes(tool.schema.type) ? tool.schema.inputSchema.extend({
83
- intent: import_mcpBundle.z.string().describe("The intent of the call, for example the test step description plan idea")
82
+ intent: import_zodBundle.z.string().describe("The intent of the call, for example the test step description plan idea")
84
83
  }) : tool.schema.inputSchema;
85
84
  return {
86
85
  schema: {
@@ -95,5 +94,6 @@ function wrapBrowserTool(tool) {
95
94
  }
96
95
  // Annotate the CommonJS export names for ESM import in node:
97
96
  0 && (module.exports = {
98
- TestServerBackend
97
+ TestServerBackend,
98
+ testServerBackendTools
99
99
  });
@@ -37,16 +37,15 @@ var import_fs = __toESM(require("fs"));
37
37
  var import_os = __toESM(require("os"));
38
38
  var import_path = __toESM(require("path"));
39
39
  var import_utils = require("patchright-bun-core/lib/utils");
40
+ var import_exports = require("patchright-bun-core/lib/tools/exports");
41
+ var import_utilsBundle = require("patchright-bun-core/lib/utilsBundle");
40
42
  var import_base = require("../../reporters/base");
41
43
  var import_list = __toESM(require("../../reporters/list"));
42
44
  var import_streams = require("./streams");
43
45
  var import_util = require("../../util");
44
46
  var import_testRunner = require("../../runner/testRunner");
45
47
  var import_seed = require("./seed");
46
- var import_exports = require("../sdk/exports");
47
48
  var import_configLoader = require("../../common/configLoader");
48
- var import_response = require("../browser/response");
49
- var import_log = require("../log");
50
49
  class GeneratorJournal {
51
50
  constructor(rootPath, plan, seed) {
52
51
  this._rootPath = rootPath;
@@ -78,9 +77,8 @@ ${step.code}
78
77
  class TestContext {
79
78
  constructor(clientInfo, configPath, options) {
80
79
  this._clientInfo = clientInfo;
81
- const rootPath = (0, import_exports.firstRootPath)(clientInfo);
82
- this._configLocation = (0, import_configLoader.resolveConfigLocation)(configPath || rootPath);
83
- this.rootPath = rootPath || this._configLocation.configDir;
80
+ this._configLocation = (0, import_configLoader.resolveConfigLocation)(configPath || clientInfo.cwd);
81
+ this.rootPath = clientInfo.cwd || this._configLocation.configDir;
84
82
  if (options?.headless !== void 0)
85
83
  this.computedHeaded = !options.headless;
86
84
  else
@@ -213,7 +211,7 @@ class TestContext {
213
211
  return { output: testRunnerAndScreen.output.join("\n"), status };
214
212
  }
215
213
  async close() {
216
- await this._cleanupTestRunner().catch(import_log.logUnhandledError);
214
+ await this._cleanupTestRunner().catch((e) => (0, import_utilsBundle.debug)("pw:mcp:error")(e));
217
215
  }
218
216
  async sendMessageToPausedTest(request) {
219
217
  const sendMessage = this._testRunnerAndScreen?.sendMessageToPausedTest;
@@ -223,7 +221,7 @@ class TestContext {
223
221
  if (result.error)
224
222
  throw new Error(result.error.message);
225
223
  if (typeof request?.callTool?.arguments?.["intent"] === "string") {
226
- const response = (0, import_response.parseResponse)(result.response.callTool);
224
+ const response = (0, import_exports.parseResponse)(result.response.callTool);
227
225
  if (response && !response.isError && response.code)
228
226
  this.generatorJournal?.logStep(request.callTool.arguments["intent"], response.code);
229
227
  }
@@ -33,7 +33,7 @@ __export(testTools_exports, {
33
33
  runTests: () => runTests
34
34
  });
35
35
  module.exports = __toCommonJS(testTools_exports);
36
- var import_mcpBundle = require("patchright-bun-core/lib/mcpBundle");
36
+ var import_zodBundle = require("patchright-bun-core/lib/zodBundle");
37
37
  var import_listModeReporter = __toESM(require("../../reporters/listModeReporter"));
38
38
  var import_testTool = require("./testTool");
39
39
  const listTests = (0, import_testTool.defineTestTool)({
@@ -41,7 +41,7 @@ const listTests = (0, import_testTool.defineTestTool)({
41
41
  name: "test_list",
42
42
  title: "List tests",
43
43
  description: "List tests",
44
- inputSchema: import_mcpBundle.z.object({}),
44
+ inputSchema: import_zodBundle.z.object({}),
45
45
  type: "readOnly"
46
46
  },
47
47
  handle: async (context) => {
@@ -56,9 +56,9 @@ const runTests = (0, import_testTool.defineTestTool)({
56
56
  name: "test_run",
57
57
  title: "Run tests",
58
58
  description: "Run tests",
59
- inputSchema: import_mcpBundle.z.object({
60
- locations: import_mcpBundle.z.array(import_mcpBundle.z.string()).optional().describe('Folder, file or location to run: "test/e2e" or "test/e2e/file.spec.ts" or "test/e2e/file.spec.ts:20"'),
61
- projects: import_mcpBundle.z.array(import_mcpBundle.z.string()).optional().describe('Projects to run, projects from playwright.config.ts, by default runs all projects. Running with "chromium" is a good start')
59
+ inputSchema: import_zodBundle.z.object({
60
+ locations: import_zodBundle.z.array(import_zodBundle.z.string()).optional().describe('Folder, file or location to run: "test/e2e" or "test/e2e/file.spec.ts" or "test/e2e/file.spec.ts:20"'),
61
+ projects: import_zodBundle.z.array(import_zodBundle.z.string()).optional().describe('Projects to run, projects from playwright.config.ts, by default runs all projects. Running with "chromium" is a good start')
62
62
  }),
63
63
  type: "readOnly"
64
64
  },
@@ -76,10 +76,10 @@ const debugTest = (0, import_testTool.defineTestTool)({
76
76
  name: "test_debug",
77
77
  title: "Debug single test",
78
78
  description: "Debug single test",
79
- inputSchema: import_mcpBundle.z.object({
80
- test: import_mcpBundle.z.object({
81
- id: import_mcpBundle.z.string().describe("Test ID to debug."),
82
- title: import_mcpBundle.z.string().describe("Human readable test title for granting permission to debug the test.")
79
+ inputSchema: import_zodBundle.z.object({
80
+ test: import_zodBundle.z.object({
81
+ id: import_zodBundle.z.string().describe("Test ID to debug."),
82
+ title: import_zodBundle.z.string().describe("Human readable test title for granting permission to debug the test.")
83
83
  })
84
84
  }),
85
85
  type: "readOnly"
@@ -53,7 +53,8 @@ class WebServerPlugin {
53
53
  }
54
54
  async setup(config, configDir, reporter) {
55
55
  this._reporter = reporter;
56
- this._isAvailableCallback = this._options.url ? getIsAvailableFunction(this._options.url, this._checkPortOnly, !!this._options.ignoreHTTPSErrors, this._reporter.onStdErr?.bind(this._reporter)) : void 0;
56
+ if (this._options.url)
57
+ this._isAvailableCallback = getIsAvailableFunction(this._options.url, this._checkPortOnly, !!this._options.ignoreHTTPSErrors, this._reporter.onStdErr?.bind(this._reporter));
57
58
  this._options.cwd = this._options.cwd ? import_path.default.resolve(configDir, this._options.cwd) : configDir;
58
59
  try {
59
60
  await this._startProcess();