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.
- package/ThirdPartyNotices.txt +10 -1133
- package/lib/agents/generateAgents.js +2 -4
- package/lib/common/config.js +4 -5
- package/lib/common/expectBundleImpl.js +221 -221
- package/lib/common/process.js +1 -0
- package/lib/common/test.js +10 -1
- package/lib/common/testType.js +3 -0
- package/lib/common/validators.js +38 -38
- package/lib/errorContext.js +121 -0
- package/lib/index.js +98 -60
- package/lib/isomorphic/teleReceiver.js +26 -9
- package/lib/isomorphic/testServerConnection.js +3 -5
- package/lib/matchers/matchers.js +2 -0
- package/lib/matchers/toMatchAriaSnapshot.js +5 -1
- package/lib/matchers/toMatchSnapshot.js +42 -35
- package/lib/mcp/test/browserBackend.js +45 -22
- package/lib/mcp/test/generatorTools.js +9 -9
- package/lib/mcp/test/plannerTools.js +17 -17
- package/lib/mcp/test/testBackend.js +27 -27
- package/lib/mcp/test/testContext.js +6 -8
- package/lib/mcp/test/testTools.js +9 -9
- package/lib/plugins/webServerPlugin.js +2 -1
- package/lib/program.js +34 -212
- package/lib/reportActions.js +80 -0
- package/lib/reporters/base.js +4 -5
- package/lib/reporters/blob.js +2 -2
- package/lib/reporters/github.js +1 -2
- package/lib/reporters/html.js +64 -31
- package/lib/reporters/junit.js +104 -15
- package/lib/reporters/line.js +1 -1
- package/lib/reporters/list.js +2 -3
- package/lib/reporters/merge.js +47 -26
- package/lib/reporters/multiplexer.js +6 -2
- package/lib/reporters/teleEmitter.js +2 -0
- package/lib/runner/dispatcher.js +6 -14
- package/lib/runner/loadUtils.js +11 -5
- package/lib/runner/projectUtils.js +1 -1
- package/lib/runner/reporters.js +5 -0
- package/lib/runner/tasks.js +11 -8
- package/lib/runner/testRunner.js +2 -2
- package/lib/runner/workerHost.js +0 -3
- package/lib/testActions.js +220 -0
- package/lib/transform/babelBundle.js +0 -3
- package/lib/transform/babelBundleImpl.js +134 -134
- package/lib/transform/compilationCache.js +0 -2
- package/lib/transform/esmLoader.js +8 -6
- package/lib/transform/transform.js +3 -10
- package/lib/utilsBundle.js +0 -7
- package/lib/utilsBundleImpl.js +48 -51
- package/lib/worker/fixtureRunner.js +2 -2
- package/lib/worker/testInfo.js +10 -14
- package/lib/worker/testTracing.js +12 -6
- package/lib/worker/timeoutManager.js +14 -3
- package/lib/worker/workerMain.js +24 -21
- package/package.json +2 -6
- package/types/test.d.ts +83 -12
- package/lib/mcp/browser/browserContextFactory.js +0 -329
- package/lib/mcp/browser/browserServerBackend.js +0 -84
- package/lib/mcp/browser/config.js +0 -421
- package/lib/mcp/browser/context.js +0 -244
- package/lib/mcp/browser/response.js +0 -278
- package/lib/mcp/browser/sessionLog.js +0 -75
- package/lib/mcp/browser/tab.js +0 -343
- package/lib/mcp/browser/tools/common.js +0 -65
- package/lib/mcp/browser/tools/console.js +0 -46
- package/lib/mcp/browser/tools/dialogs.js +0 -60
- package/lib/mcp/browser/tools/evaluate.js +0 -61
- package/lib/mcp/browser/tools/files.js +0 -58
- package/lib/mcp/browser/tools/form.js +0 -63
- package/lib/mcp/browser/tools/install.js +0 -72
- package/lib/mcp/browser/tools/keyboard.js +0 -107
- package/lib/mcp/browser/tools/mouse.js +0 -107
- package/lib/mcp/browser/tools/navigate.js +0 -71
- package/lib/mcp/browser/tools/network.js +0 -63
- package/lib/mcp/browser/tools/open.js +0 -57
- package/lib/mcp/browser/tools/pdf.js +0 -49
- package/lib/mcp/browser/tools/runCode.js +0 -78
- package/lib/mcp/browser/tools/screenshot.js +0 -93
- package/lib/mcp/browser/tools/snapshot.js +0 -173
- package/lib/mcp/browser/tools/tabs.js +0 -67
- package/lib/mcp/browser/tools/tool.js +0 -47
- package/lib/mcp/browser/tools/tracing.js +0 -74
- package/lib/mcp/browser/tools/utils.js +0 -94
- package/lib/mcp/browser/tools/verify.js +0 -143
- package/lib/mcp/browser/tools/wait.js +0 -63
- package/lib/mcp/browser/tools.js +0 -84
- package/lib/mcp/browser/watchdog.js +0 -44
- package/lib/mcp/config.d.js +0 -16
- package/lib/mcp/extension/cdpRelay.js +0 -351
- package/lib/mcp/extension/extensionContextFactory.js +0 -76
- package/lib/mcp/extension/protocol.js +0 -28
- package/lib/mcp/index.js +0 -61
- package/lib/mcp/log.js +0 -35
- package/lib/mcp/program.js +0 -111
- package/lib/mcp/sdk/exports.js +0 -28
- package/lib/mcp/sdk/http.js +0 -152
- package/lib/mcp/sdk/inProcessTransport.js +0 -71
- package/lib/mcp/sdk/server.js +0 -223
- package/lib/mcp/sdk/tool.js +0 -47
- package/lib/mcp/terminal/cli.js +0 -296
- package/lib/mcp/terminal/command.js +0 -56
- package/lib/mcp/terminal/commands.js +0 -333
- package/lib/mcp/terminal/daemon.js +0 -129
- package/lib/mcp/terminal/help.json +0 -32
- package/lib/mcp/terminal/helpGenerator.js +0 -88
- package/lib/mcp/terminal/socketConnection.js +0 -80
- package/lib/runner/storage.js +0 -91
- package/lib/transform/md.js +0 -221
package/lib/matchers/matchers.js
CHANGED
|
@@ -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
|
-
|
|
275
|
-
|
|
276
|
-
if (
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
|
25
|
-
var
|
|
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
|
|
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?.
|
|
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,
|
|
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
|
|
77
|
-
|
|
78
|
-
if (
|
|
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
|
|
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
|
-
|
|
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
|
|
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:
|
|
47
|
-
plan:
|
|
48
|
-
project:
|
|
49
|
-
seedFile:
|
|
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:
|
|
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:
|
|
84
|
-
fileName:
|
|
85
|
-
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
|
|
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:
|
|
46
|
-
project:
|
|
47
|
-
seedFile:
|
|
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 =
|
|
58
|
-
overview:
|
|
59
|
-
suites:
|
|
60
|
-
name:
|
|
61
|
-
seedFile:
|
|
62
|
-
tests:
|
|
63
|
-
name:
|
|
64
|
-
file:
|
|
65
|
-
steps:
|
|
66
|
-
perform:
|
|
67
|
-
expect:
|
|
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:
|
|
96
|
-
fileName:
|
|
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
|
|
35
|
-
var
|
|
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
|
-
|
|
41
|
-
|
|
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 =
|
|
67
|
+
const tool = testServerBackendTools.find((tool2) => tool2.schema.name === name);
|
|
68
68
|
if (!tool)
|
|
69
|
-
throw new Error(`Tool not found: ${name}. Available tools: ${
|
|
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
|
-
|
|
77
|
-
|
|
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:
|
|
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
|
-
|
|
82
|
-
this.
|
|
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(
|
|
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,
|
|
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
|
|
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:
|
|
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:
|
|
60
|
-
locations:
|
|
61
|
-
projects:
|
|
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:
|
|
80
|
-
test:
|
|
81
|
-
id:
|
|
82
|
-
title:
|
|
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
|
-
|
|
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();
|