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.
- package/README.md +3 -3
- package/ThirdPartyNotices.txt +202 -282
- package/lib/agents/copilot-setup-steps.yml +34 -0
- package/lib/agents/generateAgents.js +292 -160
- package/lib/agents/playwright-test-coverage.prompt.md +31 -0
- package/lib/agents/playwright-test-generate.prompt.md +8 -0
- package/lib/agents/{generator.md → playwright-test-generator.agent.md} +8 -22
- package/lib/agents/playwright-test-heal.prompt.md +6 -0
- package/lib/agents/{healer.md → playwright-test-healer.agent.md} +5 -28
- package/lib/agents/playwright-test-plan.prompt.md +9 -0
- package/lib/agents/{planner.md → playwright-test-planner.agent.md} +5 -68
- package/lib/common/config.js +6 -0
- package/lib/common/expectBundle.js +0 -9
- package/lib/common/expectBundleImpl.js +267 -249
- package/lib/common/testLoader.js +3 -2
- package/lib/common/testType.js +3 -12
- package/lib/common/validators.js +68 -0
- package/lib/index.js +9 -9
- package/lib/isomorphic/teleReceiver.js +1 -0
- package/lib/isomorphic/testServerConnection.js +14 -0
- package/lib/loader/loaderMain.js +1 -1
- package/lib/matchers/expect.js +12 -13
- package/lib/matchers/matchers.js +16 -0
- package/lib/mcp/browser/browserServerBackend.js +1 -1
- package/lib/mcp/browser/config.js +9 -24
- package/lib/mcp/browser/context.js +4 -21
- package/lib/mcp/browser/response.js +20 -11
- package/lib/mcp/browser/tab.js +25 -10
- package/lib/mcp/browser/tools/evaluate.js +2 -3
- package/lib/mcp/browser/tools/form.js +2 -3
- package/lib/mcp/browser/tools/keyboard.js +4 -5
- package/lib/mcp/browser/tools/pdf.js +1 -1
- package/lib/mcp/browser/tools/runCode.js +75 -0
- package/lib/mcp/browser/tools/screenshot.js +33 -15
- package/lib/mcp/browser/tools/snapshot.js +13 -14
- package/lib/mcp/browser/tools/tabs.js +2 -2
- package/lib/mcp/browser/tools/utils.js +0 -11
- package/lib/mcp/browser/tools/verify.js +3 -4
- package/lib/mcp/browser/tools.js +2 -0
- package/lib/mcp/program.js +21 -1
- package/lib/mcp/sdk/exports.js +1 -3
- package/lib/mcp/sdk/http.js +9 -2
- package/lib/mcp/sdk/proxyBackend.js +1 -1
- package/lib/mcp/sdk/server.js +13 -5
- package/lib/mcp/sdk/tool.js +2 -6
- package/lib/mcp/test/browserBackend.js +43 -33
- package/lib/mcp/test/generatorTools.js +3 -3
- package/lib/mcp/test/plannerTools.js +103 -5
- package/lib/mcp/test/seed.js +25 -15
- package/lib/mcp/test/streams.js +9 -4
- package/lib/mcp/test/testBackend.js +31 -29
- package/lib/mcp/test/testContext.js +143 -40
- package/lib/mcp/test/testTools.js +12 -21
- package/lib/plugins/webServerPlugin.js +37 -9
- package/lib/program.js +11 -20
- package/lib/reporters/html.js +2 -23
- package/lib/reporters/internalReporter.js +4 -2
- package/lib/reporters/junit.js +4 -2
- package/lib/reporters/list.js +1 -5
- package/lib/reporters/merge.js +12 -6
- package/lib/reporters/teleEmitter.js +3 -1
- package/lib/runner/dispatcher.js +26 -2
- package/lib/runner/failureTracker.js +5 -5
- package/lib/runner/loadUtils.js +2 -1
- package/lib/runner/loaderHost.js +1 -1
- package/lib/runner/reporters.js +5 -4
- package/lib/runner/testRunner.js +8 -9
- package/lib/runner/testServer.js +8 -3
- package/lib/runner/workerHost.js +3 -0
- package/lib/worker/testInfo.js +28 -17
- package/lib/worker/testTracing.js +1 -0
- package/lib/worker/workerMain.js +15 -6
- package/package.json +2 -2
- package/types/test.d.ts +96 -3
- package/types/testReporter.d.ts +5 -0
- 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(
|
|
43
|
+
async initialize(clientInfo) {
|
|
44
44
|
this._clientInfo = clientInfo;
|
|
45
45
|
}
|
|
46
46
|
async listTools() {
|
package/lib/mcp/sdk/server.js
CHANGED
|
@@ -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
|
-
|
|
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?.(
|
|
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.
|
|
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
|
-
|
|
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 = [];
|
package/lib/mcp/sdk/tool.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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 (
|
|
49
|
-
lines.push(`### Paused on error
|
|
50
|
-
|
|
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 =
|
|
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 (
|
|
90
|
+
if (testInfo.errors.length)
|
|
77
91
|
lines.push(`### Task`, `Try recovering from the error prior to continuing`);
|
|
78
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
51
|
+
handle: async (context, params) => {
|
|
38
52
|
const seed = await context.getOrCreateSeedFile(params.seedFile, params.project);
|
|
39
|
-
await context.runSeedTest(seed.file, seed.projectName
|
|
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
|
-
|
|
141
|
+
saveTestPlan,
|
|
142
|
+
setupPage,
|
|
143
|
+
submitTestPlan
|
|
46
144
|
});
|
package/lib/mcp/test/seed.js
CHANGED
|
@@ -28,7 +28,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
29
|
var seed_exports = {};
|
|
30
30
|
__export(seed_exports, {
|
|
31
|
-
|
|
31
|
+
defaultSeedFile: () => defaultSeedFile,
|
|
32
|
+
ensureSeedFile: () => ensureSeedFile,
|
|
33
|
+
findSeedFile: () => findSeedFile,
|
|
34
|
+
seedFileContent: () => seedFileContent,
|
|
32
35
|
seedProject: () => seedProject
|
|
33
36
|
});
|
|
34
37
|
module.exports = __toCommonJS(seed_exports);
|
|
@@ -44,29 +47,36 @@ function seedProject(config, projectName) {
|
|
|
44
47
|
throw new Error(`Project ${projectName} not found`);
|
|
45
48
|
return project;
|
|
46
49
|
}
|
|
47
|
-
async function
|
|
50
|
+
async function findSeedFile(project) {
|
|
48
51
|
const files = await (0, import_projectUtils.collectFilesForProject)(project);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
return files.find((file) => import_path.default.basename(file).includes("seed"));
|
|
53
|
+
}
|
|
54
|
+
function defaultSeedFile(project) {
|
|
52
55
|
const testDir = project.project.testDir;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
return import_path.default.resolve(testDir, "seed.spec.ts");
|
|
57
|
+
}
|
|
58
|
+
async function ensureSeedFile(project) {
|
|
59
|
+
const seedFile = await findSeedFile(project);
|
|
60
|
+
if (seedFile)
|
|
61
|
+
return seedFile;
|
|
62
|
+
const seedFilePath = defaultSeedFile(project);
|
|
63
|
+
await (0, import_utils.mkdirIfNeeded)(seedFilePath);
|
|
64
|
+
await import_fs.default.promises.writeFile(seedFilePath, seedFileContent);
|
|
65
|
+
return seedFilePath;
|
|
66
|
+
}
|
|
67
|
+
const seedFileContent = `import { test, expect } from '@playwright/test';
|
|
59
68
|
|
|
60
69
|
test.describe('Test group', () => {
|
|
61
70
|
test('seed', async ({ page }) => {
|
|
62
71
|
// generate code here.
|
|
63
72
|
});
|
|
64
73
|
});
|
|
65
|
-
|
|
66
|
-
return seedFile;
|
|
67
|
-
}
|
|
74
|
+
`;
|
|
68
75
|
// Annotate the CommonJS export names for ESM import in node:
|
|
69
76
|
0 && (module.exports = {
|
|
70
|
-
|
|
77
|
+
defaultSeedFile,
|
|
78
|
+
ensureSeedFile,
|
|
79
|
+
findSeedFile,
|
|
80
|
+
seedFileContent,
|
|
71
81
|
seedProject
|
|
72
82
|
});
|
package/lib/mcp/test/streams.js
CHANGED
|
@@ -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(
|
|
27
|
+
constructor(output, stdio) {
|
|
27
28
|
super();
|
|
28
|
-
this.
|
|
29
|
+
this._output = output;
|
|
30
|
+
this._prefix = stdio === "stdout" ? "" : "[err] ";
|
|
29
31
|
}
|
|
30
32
|
_write(chunk, encoding, callback) {
|
|
31
|
-
|
|
32
|
-
|
|
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
|
|
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.
|
|
57
|
+
this._options = options || {};
|
|
56
58
|
this._configOption = configOption;
|
|
57
59
|
}
|
|
58
|
-
async initialize(
|
|
59
|
-
|
|
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
|
|
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
|
-
|
|
88
|
-
|
|
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
|