playwright 1.58.0-alpha-2025-12-10 → 1.58.0-alpha-2025-12-12
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/lib/agents/agent.js +2 -1
- package/lib/agents/playwright-test-planner.agent.md +1 -0
- package/lib/index.js +41 -19
- package/lib/mcp/browser/context.js +4 -4
- package/lib/mcp/browser/tools/evaluate.js +3 -13
- package/lib/mcp/browser/tools/form.js +2 -12
- package/lib/mcp/browser/tools/pdf.js +4 -14
- package/lib/mcp/browser/tools/runCode.js +8 -6
- package/lib/mcp/browser/tools/screenshot.js +5 -5
- package/lib/mcp/browser/tools/snapshot.js +3 -3
- package/lib/mcp/browser/tools/verify.js +5 -15
- package/lib/worker/testInfo.js +9 -9
- package/package.json +2 -2
- package/types/test.d.ts +1 -1
- package/lib/mcp/browser/codegen.js +0 -66
package/lib/agents/agent.js
CHANGED
|
@@ -33,7 +33,7 @@ class Agent {
|
|
|
33
33
|
const { clients, tools, callTool } = await this._initClients();
|
|
34
34
|
const prompt = this.spec.description;
|
|
35
35
|
try {
|
|
36
|
-
|
|
36
|
+
const { result } = await this.loop.run(`${prompt}
|
|
37
37
|
|
|
38
38
|
Task:
|
|
39
39
|
${task}
|
|
@@ -45,6 +45,7 @@ ${JSON.stringify(params, null, 2)}`, {
|
|
|
45
45
|
callTool,
|
|
46
46
|
resultSchema: this.resultSchema
|
|
47
47
|
});
|
|
48
|
+
return result;
|
|
48
49
|
} finally {
|
|
49
50
|
await this._disconnectFromServers(clients);
|
|
50
51
|
}
|
|
@@ -17,6 +17,7 @@ tools:
|
|
|
17
17
|
- playwright-test/browser_navigate_back
|
|
18
18
|
- playwright-test/browser_network_requests
|
|
19
19
|
- playwright-test/browser_press_key
|
|
20
|
+
- playwright-test/browser_run_code
|
|
20
21
|
- playwright-test/browser_select_option
|
|
21
22
|
- playwright-test/browser_snapshot
|
|
22
23
|
- playwright-test/browser_take_screenshot
|
package/lib/index.js
CHANGED
|
@@ -212,37 +212,26 @@ const playwrightFixtures = {
|
|
|
212
212
|
options.baseURL = baseURL;
|
|
213
213
|
if (serviceWorkers !== void 0)
|
|
214
214
|
options.serviceWorkers = serviceWorkers;
|
|
215
|
-
const workerFile = agent?.cacheFile && agent.cacheMode !== "ignore" ? await testInfo._cloneStorage(agent.cacheFile) : void 0;
|
|
216
|
-
if (agent && workerFile) {
|
|
217
|
-
options.agent = {
|
|
218
|
-
...agent,
|
|
219
|
-
cacheFile: workerFile
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
215
|
await use({
|
|
223
216
|
...contextOptions,
|
|
224
217
|
...options
|
|
225
218
|
});
|
|
226
|
-
if (workerFile)
|
|
227
|
-
await testInfo._upstreamStorage(workerFile);
|
|
228
219
|
}, { box: true }],
|
|
229
|
-
_setupContextOptions: [async ({ playwright,
|
|
220
|
+
_setupContextOptions: [async ({ playwright, actionTimeout, navigationTimeout, testIdAttribute }, use, testInfo) => {
|
|
230
221
|
if (testIdAttribute)
|
|
231
222
|
playwrightLibrary.selectors.setTestIdAttribute(testIdAttribute);
|
|
232
223
|
testInfo.snapshotSuffix = process.platform;
|
|
233
224
|
if ((0, import_utils.debugMode)() === "inspector")
|
|
234
225
|
testInfo._setDebugMode();
|
|
235
|
-
playwright._defaultContextOptions = _combinedContextOptions;
|
|
236
226
|
playwright._defaultContextTimeout = actionTimeout || 0;
|
|
237
227
|
playwright._defaultContextNavigationTimeout = navigationTimeout || 0;
|
|
238
228
|
await use();
|
|
239
|
-
playwright._defaultContextOptions = void 0;
|
|
240
229
|
playwright._defaultContextTimeout = void 0;
|
|
241
230
|
playwright._defaultContextNavigationTimeout = void 0;
|
|
242
231
|
}, { auto: "all-hooks-included", title: "context configuration", box: true }],
|
|
243
|
-
_setupArtifacts: [async ({ playwright, screenshot }, use, testInfo) => {
|
|
232
|
+
_setupArtifacts: [async ({ playwright, screenshot, _combinedContextOptions, agent }, use, testInfo) => {
|
|
244
233
|
testInfo.setTimeout(testInfo.project.timeout);
|
|
245
|
-
const artifactsRecorder = new ArtifactsRecorder(playwright, tracing().artifactsDir(), screenshot);
|
|
234
|
+
const artifactsRecorder = new ArtifactsRecorder(playwright, tracing().artifactsDir(), screenshot, agent);
|
|
246
235
|
await artifactsRecorder.willStartTest(testInfo);
|
|
247
236
|
const tracingGroupSteps = [];
|
|
248
237
|
const csiListener = {
|
|
@@ -288,20 +277,33 @@ const playwrightFixtures = {
|
|
|
288
277
|
if (!keepTestTimeout)
|
|
289
278
|
(0, import_globals.currentTestInfo)()?._setDebugMode();
|
|
290
279
|
},
|
|
280
|
+
runBeforeCreateBrowserContext: async (options) => {
|
|
281
|
+
for (const [key, value] of Object.entries(_combinedContextOptions)) {
|
|
282
|
+
if (!(key in options))
|
|
283
|
+
options[key] = value;
|
|
284
|
+
}
|
|
285
|
+
await artifactsRecorder.willCreateBrowserContext(options);
|
|
286
|
+
},
|
|
287
|
+
runBeforeCreateRequestContext: async (options) => {
|
|
288
|
+
for (const [key, value] of Object.entries(_combinedContextOptions)) {
|
|
289
|
+
if (!(key in options))
|
|
290
|
+
options[key] = value;
|
|
291
|
+
}
|
|
292
|
+
},
|
|
291
293
|
runAfterCreateBrowserContext: async (context) => {
|
|
292
|
-
await artifactsRecorder
|
|
294
|
+
await artifactsRecorder.didCreateBrowserContext(context);
|
|
293
295
|
const testInfo2 = (0, import_globals.currentTestInfo)();
|
|
294
296
|
if (testInfo2)
|
|
295
297
|
attachConnectedHeaderIfNeeded(testInfo2, context.browser());
|
|
296
298
|
},
|
|
297
299
|
runAfterCreateRequestContext: async (context) => {
|
|
298
|
-
await artifactsRecorder
|
|
300
|
+
await artifactsRecorder.didCreateRequestContext(context);
|
|
299
301
|
},
|
|
300
302
|
runBeforeCloseBrowserContext: async (context) => {
|
|
301
|
-
await artifactsRecorder
|
|
303
|
+
await artifactsRecorder.willCloseBrowserContext(context);
|
|
302
304
|
},
|
|
303
305
|
runBeforeCloseRequestContext: async (context) => {
|
|
304
|
-
await artifactsRecorder
|
|
306
|
+
await artifactsRecorder.willCloseRequestContext(context);
|
|
305
307
|
}
|
|
306
308
|
};
|
|
307
309
|
const clientInstrumentation = playwright._instrumentation;
|
|
@@ -560,9 +562,10 @@ class SnapshotRecorder {
|
|
|
560
562
|
}
|
|
561
563
|
}
|
|
562
564
|
class ArtifactsRecorder {
|
|
563
|
-
constructor(playwright, artifactsDir, screenshot) {
|
|
565
|
+
constructor(playwright, artifactsDir, screenshot, agent) {
|
|
564
566
|
this._playwright = playwright;
|
|
565
567
|
this._artifactsDir = artifactsDir;
|
|
568
|
+
this._agent = agent;
|
|
566
569
|
const screenshotOptions = typeof screenshot === "string" ? void 0 : screenshot;
|
|
567
570
|
this._startedCollectingArtifacts = Symbol("startedCollectingArtifacts");
|
|
568
571
|
this._screenshotRecorder = new SnapshotRecorder(this, normalizeScreenshotMode(screenshot), "screenshot", "image/png", ".png", async (page, path2) => {
|
|
@@ -582,10 +585,14 @@ class ArtifactsRecorder {
|
|
|
582
585
|
async didCreateBrowserContext(context) {
|
|
583
586
|
await this._startTraceChunkOnContextCreation(context, context.tracing);
|
|
584
587
|
}
|
|
588
|
+
async willCreateBrowserContext(options) {
|
|
589
|
+
await this._cloneAgentCache(options);
|
|
590
|
+
}
|
|
585
591
|
async willCloseBrowserContext(context) {
|
|
586
592
|
await this._stopTracing(context, context.tracing);
|
|
587
593
|
await this._screenshotRecorder.captureTemporary(context);
|
|
588
594
|
await this._takePageSnapshot(context);
|
|
595
|
+
await this._upstreamAgentCache(context);
|
|
589
596
|
}
|
|
590
597
|
async _takePageSnapshot(context) {
|
|
591
598
|
if (process.env.PLAYWRIGHT_NO_COPY_PROMPT)
|
|
@@ -604,6 +611,21 @@ class ArtifactsRecorder {
|
|
|
604
611
|
} catch {
|
|
605
612
|
}
|
|
606
613
|
}
|
|
614
|
+
async _cloneAgentCache(options) {
|
|
615
|
+
if (!this._agent || this._agent.cacheMode === "ignore")
|
|
616
|
+
return;
|
|
617
|
+
if (!this._agent.cacheFile && !this._agent.cachePathTemplate)
|
|
618
|
+
return;
|
|
619
|
+
const cacheFile = this._agent.cacheFile ?? this._testInfo._applyPathTemplate(this._agent.cachePathTemplate, "cache", ".json");
|
|
620
|
+
const workerFile = await this._testInfo._cloneStorage(cacheFile);
|
|
621
|
+
if (this._agent && workerFile)
|
|
622
|
+
options.agent = { ...this._agent, cacheFile: workerFile };
|
|
623
|
+
}
|
|
624
|
+
async _upstreamAgentCache(context) {
|
|
625
|
+
const agent = context._options.agent;
|
|
626
|
+
if (this._testInfo.status === "passed" && agent?.cacheFile)
|
|
627
|
+
await this._testInfo._upstreamStorage(agent.cacheFile);
|
|
628
|
+
}
|
|
607
629
|
async didCreateRequestContext(context) {
|
|
608
630
|
await this._startTraceChunkOnContextCreation(context, context._tracing);
|
|
609
631
|
}
|
|
@@ -34,12 +34,12 @@ __export(context_exports, {
|
|
|
34
34
|
module.exports = __toCommonJS(context_exports);
|
|
35
35
|
var import_fs = __toESM(require("fs"));
|
|
36
36
|
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
37
|
+
var import_utils = require("playwright-core/lib/utils");
|
|
37
38
|
var import_playwright_core = require("playwright-core");
|
|
38
39
|
var import_log = require("../log");
|
|
39
40
|
var import_tab = require("./tab");
|
|
40
41
|
var import_config = require("./config");
|
|
41
|
-
var
|
|
42
|
-
var import_utils = require("./tools/utils");
|
|
42
|
+
var import_utils2 = require("./tools/utils");
|
|
43
43
|
const testDebug = (0, import_utilsBundle.debug)("pw:mcp:test");
|
|
44
44
|
class Context {
|
|
45
45
|
constructor(options) {
|
|
@@ -141,7 +141,7 @@ class Context {
|
|
|
141
141
|
const videos = this.config.saveVideo ? browserContext.pages().map((page) => page.video()).filter((video) => !!video) : [];
|
|
142
142
|
await close(async () => {
|
|
143
143
|
for (const video of videos) {
|
|
144
|
-
const name = await this.outputFile((0,
|
|
144
|
+
const name = await this.outputFile((0, import_utils2.dateAsFileName)("webm"), { origin: "code", reason: "Saving video" });
|
|
145
145
|
const p = await video.path();
|
|
146
146
|
if (import_fs.default.existsSync(p)) {
|
|
147
147
|
try {
|
|
@@ -215,7 +215,7 @@ class Context {
|
|
|
215
215
|
}
|
|
216
216
|
lookupSecret(secretName) {
|
|
217
217
|
if (!this.config.secrets?.[secretName])
|
|
218
|
-
return { value: secretName, code:
|
|
218
|
+
return { value: secretName, code: (0, import_utils.escapeWithQuotes)(secretName, "'") };
|
|
219
219
|
return {
|
|
220
220
|
value: this.config.secrets[secretName],
|
|
221
221
|
code: `process.env['${secretName}']`
|
|
@@ -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,14 +15,6 @@ 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 evaluate_exports = {};
|
|
30
20
|
__export(evaluate_exports, {
|
|
@@ -32,8 +22,8 @@ __export(evaluate_exports, {
|
|
|
32
22
|
});
|
|
33
23
|
module.exports = __toCommonJS(evaluate_exports);
|
|
34
24
|
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
|
|
25
|
+
var import_utils = require("playwright-core/lib/utils");
|
|
35
26
|
var import_tool = require("./tool");
|
|
36
|
-
var javascript = __toESM(require("../codegen"));
|
|
37
27
|
const evaluateSchema = import_mcpBundle.z.object({
|
|
38
28
|
function: import_mcpBundle.z.string().describe("() => { /* code */ } or (element) => { /* code */ } when element is provided"),
|
|
39
29
|
element: import_mcpBundle.z.string().optional().describe("Human-readable element description used to obtain permission to interact with the element"),
|
|
@@ -53,9 +43,9 @@ const evaluate = (0, import_tool.defineTabTool)({
|
|
|
53
43
|
let locator;
|
|
54
44
|
if (params.ref && params.element) {
|
|
55
45
|
locator = await tab.refLocator({ ref: params.ref, element: params.element });
|
|
56
|
-
response.addCode(`await page.${locator.resolved}.evaluate(${
|
|
46
|
+
response.addCode(`await page.${locator.resolved}.evaluate(${(0, import_utils.escapeWithQuotes)(params.function)});`);
|
|
57
47
|
} else {
|
|
58
|
-
response.addCode(`await page.evaluate(${
|
|
48
|
+
response.addCode(`await page.evaluate(${(0, import_utils.escapeWithQuotes)(params.function)});`);
|
|
59
49
|
}
|
|
60
50
|
await tab.waitForCompletion(async () => {
|
|
61
51
|
const receiver = locator?.locator ?? tab.page;
|
|
@@ -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,14 +15,6 @@ 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 form_exports = {};
|
|
30
20
|
__export(form_exports, {
|
|
@@ -32,8 +22,8 @@ __export(form_exports, {
|
|
|
32
22
|
});
|
|
33
23
|
module.exports = __toCommonJS(form_exports);
|
|
34
24
|
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
|
|
25
|
+
var import_utils = require("playwright-core/lib/utils");
|
|
35
26
|
var import_tool = require("./tool");
|
|
36
|
-
var codegen = __toESM(require("../codegen"));
|
|
37
27
|
const fillForm = (0, import_tool.defineTabTool)({
|
|
38
28
|
capability: "core",
|
|
39
29
|
schema: {
|
|
@@ -63,7 +53,7 @@ const fillForm = (0, import_tool.defineTabTool)({
|
|
|
63
53
|
response.addCode(`${locatorSource}.setChecked(${field.value});`);
|
|
64
54
|
} else if (field.type === "combobox") {
|
|
65
55
|
await locator.selectOption({ label: field.value });
|
|
66
|
-
response.addCode(`${locatorSource}.selectOption(${
|
|
56
|
+
response.addCode(`${locatorSource}.selectOption(${(0, import_utils.escapeWithQuotes)(field.value)});`);
|
|
67
57
|
}
|
|
68
58
|
}
|
|
69
59
|
}
|
|
@@ -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,14 +15,6 @@ 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 pdf_exports = {};
|
|
30
20
|
__export(pdf_exports, {
|
|
@@ -32,9 +22,9 @@ __export(pdf_exports, {
|
|
|
32
22
|
});
|
|
33
23
|
module.exports = __toCommonJS(pdf_exports);
|
|
34
24
|
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
|
|
25
|
+
var import_utils = require("playwright-core/lib/utils");
|
|
35
26
|
var import_tool = require("./tool");
|
|
36
|
-
var
|
|
37
|
-
var import_utils = require("./utils");
|
|
27
|
+
var import_utils2 = require("./utils");
|
|
38
28
|
const pdfSchema = import_mcpBundle.z.object({
|
|
39
29
|
filename: import_mcpBundle.z.string().optional().describe("File name to save the pdf to. Defaults to `page-{timestamp}.pdf` if not specified. Prefer relative file names to stay within the output directory.")
|
|
40
30
|
});
|
|
@@ -48,8 +38,8 @@ const pdf = (0, import_tool.defineTabTool)({
|
|
|
48
38
|
type: "readOnly"
|
|
49
39
|
},
|
|
50
40
|
handle: async (tab, params, response) => {
|
|
51
|
-
const fileName = await response.addFile(params.filename ?? (0,
|
|
52
|
-
response.addCode(`await page.pdf(${
|
|
41
|
+
const fileName = await response.addFile(params.filename ?? (0, import_utils2.dateAsFileName)("pdf"), { origin: "llm", reason: "Page saved as PDF" });
|
|
42
|
+
response.addCode(`await page.pdf(${(0, import_utils.formatObject)({ path: fileName })});`);
|
|
53
43
|
await tab.page.pdf({ path: fileName });
|
|
54
44
|
}
|
|
55
45
|
});
|
|
@@ -36,7 +36,7 @@ var import_utils = require("playwright-core/lib/utils");
|
|
|
36
36
|
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
|
|
37
37
|
var import_tool = require("./tool");
|
|
38
38
|
const codeSchema = import_mcpBundle.z.object({
|
|
39
|
-
code: import_mcpBundle.z.string().describe(`Playwright code
|
|
39
|
+
code: import_mcpBundle.z.string().describe(`A JavaScript function containing Playwright code to execute. It will be invoked with a single argument, page, which you can use for any page interaction. For example: \`async (page) => { await page.getByRole('button', { name: 'Submit' }).click(); return await page.title(); }\``)
|
|
40
40
|
});
|
|
41
41
|
const runCode = (0, import_tool.defineTabTool)({
|
|
42
42
|
capability: "core",
|
|
@@ -49,7 +49,7 @@ const runCode = (0, import_tool.defineTabTool)({
|
|
|
49
49
|
},
|
|
50
50
|
handle: async (tab, params, response) => {
|
|
51
51
|
response.setIncludeSnapshot();
|
|
52
|
-
response.addCode(params.code);
|
|
52
|
+
response.addCode(`await (${params.code})(page);`);
|
|
53
53
|
const __end__ = new import_utils.ManualPromise();
|
|
54
54
|
const context = {
|
|
55
55
|
page: tab.page,
|
|
@@ -59,14 +59,16 @@ const runCode = (0, import_tool.defineTabTool)({
|
|
|
59
59
|
await tab.waitForCompletion(async () => {
|
|
60
60
|
const snippet = `(async () => {
|
|
61
61
|
try {
|
|
62
|
-
${params.code};
|
|
63
|
-
__end__.resolve();
|
|
62
|
+
const result = await (${params.code})(page);
|
|
63
|
+
__end__.resolve(JSON.stringify(result));
|
|
64
64
|
} catch (e) {
|
|
65
65
|
__end__.reject(e);
|
|
66
66
|
}
|
|
67
67
|
})()`;
|
|
68
|
-
import_vm.default.runInContext(snippet, context);
|
|
69
|
-
await __end__;
|
|
68
|
+
await import_vm.default.runInContext(snippet, context);
|
|
69
|
+
const result = await __end__;
|
|
70
|
+
if (typeof result === "string")
|
|
71
|
+
response.addResult(result);
|
|
70
72
|
});
|
|
71
73
|
}
|
|
72
74
|
});
|
|
@@ -35,10 +35,10 @@ module.exports = __toCommonJS(screenshot_exports);
|
|
|
35
35
|
var import_fs = __toESM(require("fs"));
|
|
36
36
|
var import_utils = require("playwright-core/lib/utils");
|
|
37
37
|
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
38
|
+
var import_utils2 = require("playwright-core/lib/utils");
|
|
38
39
|
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
|
|
39
40
|
var import_tool = require("./tool");
|
|
40
|
-
var
|
|
41
|
-
var import_utils2 = require("./utils");
|
|
41
|
+
var import_utils3 = require("./utils");
|
|
42
42
|
const screenshotSchema = import_mcpBundle.z.object({
|
|
43
43
|
type: import_mcpBundle.z.enum(["png", "jpeg"]).default("png").describe("Image format for the screenshot. Default is png."),
|
|
44
44
|
filename: import_mcpBundle.z.string().optional().describe("File name to save the screenshot to. Defaults to `page-{timestamp}.{png|jpeg}` if not specified. Prefer relative file names to stay within the output directory."),
|
|
@@ -69,13 +69,13 @@ const screenshot = (0, import_tool.defineTabTool)({
|
|
|
69
69
|
};
|
|
70
70
|
const isElementScreenshot = params.element && params.ref;
|
|
71
71
|
const screenshotTarget = isElementScreenshot ? params.element : params.fullPage ? "full page" : "viewport";
|
|
72
|
-
const fileName = await response.addFile(params.filename || (0,
|
|
72
|
+
const fileName = await response.addFile(params.filename || (0, import_utils3.dateAsFileName)(fileType), { origin: "llm", reason: `Screenshot of ${screenshotTarget}` });
|
|
73
73
|
response.addCode(`// Screenshot ${screenshotTarget} and save it as ${fileName}`);
|
|
74
74
|
const ref = params.ref ? await tab.refLocator({ element: params.element || "", ref: params.ref }) : null;
|
|
75
75
|
if (ref)
|
|
76
|
-
response.addCode(`await page.${ref.resolved}.screenshot(${
|
|
76
|
+
response.addCode(`await page.${ref.resolved}.screenshot(${(0, import_utils2.formatObject)(options)});`);
|
|
77
77
|
else
|
|
78
|
-
response.addCode(`await page.screenshot(${
|
|
78
|
+
response.addCode(`await page.screenshot(${(0, import_utils2.formatObject)(options)});`);
|
|
79
79
|
const buffer = ref ? await ref.locator.screenshot(options) : await tab.page.screenshot(options);
|
|
80
80
|
await (0, import_utils.mkdirIfNeeded)(fileName);
|
|
81
81
|
await import_fs.default.promises.writeFile(fileName, buffer);
|
|
@@ -34,8 +34,8 @@ __export(snapshot_exports, {
|
|
|
34
34
|
module.exports = __toCommonJS(snapshot_exports);
|
|
35
35
|
var import_fs = __toESM(require("fs"));
|
|
36
36
|
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
|
|
37
|
+
var import_utils = require("playwright-core/lib/utils");
|
|
37
38
|
var import_tool = require("./tool");
|
|
38
|
-
var javascript = __toESM(require("../codegen"));
|
|
39
39
|
const snapshot = (0, import_tool.defineTool)({
|
|
40
40
|
capability: "core",
|
|
41
41
|
schema: {
|
|
@@ -83,7 +83,7 @@ const click = (0, import_tool.defineTabTool)({
|
|
|
83
83
|
button: params.button,
|
|
84
84
|
modifiers: params.modifiers
|
|
85
85
|
};
|
|
86
|
-
const formatted =
|
|
86
|
+
const formatted = (0, import_utils.formatObject)(options, " ", "oneline");
|
|
87
87
|
const optionsAttr = formatted !== "{}" ? formatted : "";
|
|
88
88
|
if (params.doubleClick)
|
|
89
89
|
response.addCode(`await page.${resolved}.dblclick(${optionsAttr});`);
|
|
@@ -156,7 +156,7 @@ const selectOption = (0, import_tool.defineTabTool)({
|
|
|
156
156
|
handle: async (tab, params, response) => {
|
|
157
157
|
response.setIncludeSnapshot();
|
|
158
158
|
const { locator, resolved } = await tab.refLocator(params);
|
|
159
|
-
response.addCode(`await page.${resolved}.selectOption(${
|
|
159
|
+
response.addCode(`await page.${resolved}.selectOption(${(0, import_utils.formatObject)(params.values)});`);
|
|
160
160
|
await tab.waitForCompletion(async () => {
|
|
161
161
|
await locator.selectOption(params.values);
|
|
162
162
|
});
|
|
@@ -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,14 +15,6 @@ 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 verify_exports = {};
|
|
30
20
|
__export(verify_exports, {
|
|
@@ -32,8 +22,8 @@ __export(verify_exports, {
|
|
|
32
22
|
});
|
|
33
23
|
module.exports = __toCommonJS(verify_exports);
|
|
34
24
|
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
|
|
25
|
+
var import_utils = require("playwright-core/lib/utils");
|
|
35
26
|
var import_tool = require("./tool");
|
|
36
|
-
var javascript = __toESM(require("../codegen"));
|
|
37
27
|
const verifyElement = (0, import_tool.defineTabTool)({
|
|
38
28
|
capability: "testing",
|
|
39
29
|
schema: {
|
|
@@ -52,7 +42,7 @@ const verifyElement = (0, import_tool.defineTabTool)({
|
|
|
52
42
|
response.addError(`Element with role "${params.role}" and accessible name "${params.accessibleName}" not found`);
|
|
53
43
|
return;
|
|
54
44
|
}
|
|
55
|
-
response.addCode(`await expect(page.getByRole(${
|
|
45
|
+
response.addCode(`await expect(page.getByRole(${(0, import_utils.escapeWithQuotes)(params.role)}, { name: ${(0, import_utils.escapeWithQuotes)(params.accessibleName)} })).toBeVisible();`);
|
|
56
46
|
response.addResult("Done");
|
|
57
47
|
}
|
|
58
48
|
});
|
|
@@ -73,7 +63,7 @@ const verifyText = (0, import_tool.defineTabTool)({
|
|
|
73
63
|
response.addError("Text not found");
|
|
74
64
|
return;
|
|
75
65
|
}
|
|
76
|
-
response.addCode(`await expect(page.getByText(${
|
|
66
|
+
response.addCode(`await expect(page.getByText(${(0, import_utils.escapeWithQuotes)(params.text)})).toBeVisible();`);
|
|
77
67
|
response.addResult("Done");
|
|
78
68
|
}
|
|
79
69
|
});
|
|
@@ -103,7 +93,7 @@ const verifyList = (0, import_tool.defineTabTool)({
|
|
|
103
93
|
}
|
|
104
94
|
const ariaSnapshot = `\`
|
|
105
95
|
- list:
|
|
106
|
-
${itemTexts.map((t) => ` - listitem: ${
|
|
96
|
+
${itemTexts.map((t) => ` - listitem: ${(0, import_utils.escapeWithQuotes)(t, '"')}`).join("\n")}
|
|
107
97
|
\``;
|
|
108
98
|
response.addCode(`await expect(page.locator('body')).toMatchAriaSnapshot(${ariaSnapshot});`);
|
|
109
99
|
response.addResult("Done");
|
|
@@ -132,7 +122,7 @@ const verifyValue = (0, import_tool.defineTabTool)({
|
|
|
132
122
|
response.addError(`Expected value "${params.value}", but got "${value}"`);
|
|
133
123
|
return;
|
|
134
124
|
}
|
|
135
|
-
response.addCode(`await expect(${locatorSource}).toHaveValue(${
|
|
125
|
+
response.addCode(`await expect(${locatorSource}).toHaveValue(${(0, import_utils.escapeWithQuotes)(params.value)});`);
|
|
136
126
|
} else if (params.type === "checkbox" || params.type === "radio") {
|
|
137
127
|
const value = await locator.isChecked();
|
|
138
128
|
if (value !== (params.value === "true")) {
|
package/lib/worker/testInfo.js
CHANGED
|
@@ -342,13 +342,13 @@ ${(0, import_utils.stringifyStackFrames)(step.boxedStack).join("\n")}`;
|
|
|
342
342
|
this._callbacks.onTestPaused({ testId: this.testId, stepId: step.stepId, errors: this._isFailure() ? this.errors : [] }),
|
|
343
343
|
this._interruptedPromise.then(() => "interrupted")
|
|
344
344
|
]);
|
|
345
|
-
step.complete({});
|
|
346
345
|
if (result !== "interrupted") {
|
|
347
346
|
if (result.action === "abort")
|
|
348
347
|
this._interrupt();
|
|
349
348
|
if (result.action === void 0)
|
|
350
349
|
await this._interruptedPromise;
|
|
351
350
|
}
|
|
351
|
+
step.complete({});
|
|
352
352
|
}
|
|
353
353
|
await this._onDidFinishTestFunctionCallback?.();
|
|
354
354
|
}
|
|
@@ -445,10 +445,6 @@ ${(0, import_utils.stringifyStackFrames)(step.boxedStack).join("\n")}`;
|
|
|
445
445
|
if (index > 1)
|
|
446
446
|
relativeOutputPath = (0, import_util.addSuffixToFilePath)(relativeOutputPath, `-${index - 1}`);
|
|
447
447
|
}
|
|
448
|
-
const absoluteSnapshotPath = this._applyPathTemplate(kind, subPath, ext);
|
|
449
|
-
return { absoluteSnapshotPath, relativeOutputPath };
|
|
450
|
-
}
|
|
451
|
-
_applyPathTemplate(kind, relativePath, ext) {
|
|
452
448
|
const legacyTemplate = "{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}{-projectName}{-snapshotSuffix}{ext}";
|
|
453
449
|
let template;
|
|
454
450
|
if (kind === "screenshot") {
|
|
@@ -459,12 +455,15 @@ ${(0, import_utils.stringifyStackFrames)(step.boxedStack).join("\n")}`;
|
|
|
459
455
|
} else {
|
|
460
456
|
template = this._projectInternal.snapshotPathTemplate || legacyTemplate;
|
|
461
457
|
}
|
|
462
|
-
const
|
|
463
|
-
const
|
|
458
|
+
const nameArgument = import_path.default.join(import_path.default.dirname(subPath), import_path.default.basename(subPath, ext));
|
|
459
|
+
const absoluteSnapshotPath = this._applyPathTemplate(template, nameArgument, ext);
|
|
460
|
+
return { absoluteSnapshotPath, relativeOutputPath };
|
|
461
|
+
}
|
|
462
|
+
_applyPathTemplate(template, nameArgument, ext) {
|
|
464
463
|
const relativeTestFilePath = import_path.default.relative(this.project.testDir, this._requireFile);
|
|
465
464
|
const parsedRelativeTestFilePath = import_path.default.parse(relativeTestFilePath);
|
|
466
465
|
const projectNamePathSegment = (0, import_utils.sanitizeForFilePath)(this.project.name);
|
|
467
|
-
const snapshotPath = template.replace(/\{(.)?testDir\}/g, "$1" + this.project.testDir).replace(/\{(.)?snapshotDir\}/g, "$1" + this.project.snapshotDir).replace(/\{(.)?snapshotSuffix\}/g, this.snapshotSuffix ? "$1" + this.snapshotSuffix : "").replace(/\{(.)?testFileDir\}/g, "$1" + parsedRelativeTestFilePath.dir).replace(/\{(.)?platform\}/g, "$1" + process.platform).replace(/\{(.)?projectName\}/g, projectNamePathSegment ? "$1" + projectNamePathSegment : "").replace(/\{(.)?testName\}/g, "$1" + this._fsSanitizedTestName()).replace(/\{(.)?testFileName\}/g, "$1" + parsedRelativeTestFilePath.base).replace(/\{(.)?testFilePath\}/g, "$1" + relativeTestFilePath).replace(/\{(.)?arg\}/g, "$1" +
|
|
466
|
+
const snapshotPath = template.replace(/\{(.)?testDir\}/g, "$1" + this.project.testDir).replace(/\{(.)?snapshotDir\}/g, "$1" + this.project.snapshotDir).replace(/\{(.)?snapshotSuffix\}/g, this.snapshotSuffix ? "$1" + this.snapshotSuffix : "").replace(/\{(.)?testFileDir\}/g, "$1" + parsedRelativeTestFilePath.dir).replace(/\{(.)?platform\}/g, "$1" + process.platform).replace(/\{(.)?projectName\}/g, projectNamePathSegment ? "$1" + projectNamePathSegment : "").replace(/\{(.)?testName\}/g, "$1" + this._fsSanitizedTestName()).replace(/\{(.)?testFileName\}/g, "$1" + parsedRelativeTestFilePath.base).replace(/\{(.)?testFilePath\}/g, "$1" + relativeTestFilePath).replace(/\{(.)?arg\}/g, "$1" + nameArgument).replace(/\{(.)?ext\}/g, ext ? "$1" + ext : "");
|
|
468
467
|
return import_path.default.normalize(import_path.default.resolve(this._configInternal.configDir, snapshotPath));
|
|
469
468
|
}
|
|
470
469
|
snapshotPath(...args) {
|
|
@@ -482,7 +481,8 @@ ${(0, import_utils.stringifyStackFrames)(step.boxedStack).join("\n")}`;
|
|
|
482
481
|
setTimeout(timeout) {
|
|
483
482
|
this._timeoutManager.setTimeout(timeout);
|
|
484
483
|
}
|
|
485
|
-
async _cloneStorage(
|
|
484
|
+
async _cloneStorage(cacheFileTemplate) {
|
|
485
|
+
const storageFile = this._applyPathTemplate(cacheFileTemplate, "cache", ".json");
|
|
486
486
|
return await this._callbacks.onCloneStorage?.({ storageFile });
|
|
487
487
|
}
|
|
488
488
|
async _upstreamStorage(workerFile) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "playwright",
|
|
3
|
-
"version": "1.58.0-alpha-2025-12-
|
|
3
|
+
"version": "1.58.0-alpha-2025-12-12",
|
|
4
4
|
"description": "A high-level API to automate web browsers",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
},
|
|
65
65
|
"license": "Apache-2.0",
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"playwright-core": "1.58.0-alpha-2025-12-
|
|
67
|
+
"playwright-core": "1.58.0-alpha-2025-12-12"
|
|
68
68
|
},
|
|
69
69
|
"optionalDependencies": {
|
|
70
70
|
"fsevents": "2.3.2"
|
package/types/test.d.ts
CHANGED
|
@@ -6644,7 +6644,7 @@ export type Fixtures<T extends {} = {}, W extends {} = {}, PT extends {} = {}, P
|
|
|
6644
6644
|
[K in Exclude<keyof T, keyof PW | keyof PT>]?: TestFixtureValue<T[K], T & W & PT & PW> | [TestFixtureValue<T[K], T & W & PT & PW>, { scope?: 'test', auto?: boolean, option?: boolean, timeout?: number | undefined, title?: string, box?: boolean | 'self' }];
|
|
6645
6645
|
};
|
|
6646
6646
|
|
|
6647
|
-
type Agent = Exclude<BrowserContextOptions['agent'], undefined> | undefined;
|
|
6647
|
+
type Agent = Exclude<BrowserContextOptions['agent'], undefined> & { cachePathTemplate?: string } | undefined;
|
|
6648
6648
|
type BrowserName = 'chromium' | 'firefox' | 'webkit';
|
|
6649
6649
|
type BrowserChannel = Exclude<LaunchOptions['channel'], undefined>;
|
|
6650
6650
|
type ColorScheme = Exclude<BrowserContextOptions['colorScheme'], undefined>;
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
var codegen_exports = {};
|
|
20
|
-
__export(codegen_exports, {
|
|
21
|
-
escapeWithQuotes: () => escapeWithQuotes,
|
|
22
|
-
formatObject: () => formatObject,
|
|
23
|
-
quote: () => quote
|
|
24
|
-
});
|
|
25
|
-
module.exports = __toCommonJS(codegen_exports);
|
|
26
|
-
function escapeWithQuotes(text, char = "'") {
|
|
27
|
-
const stringified = JSON.stringify(text);
|
|
28
|
-
const escapedText = stringified.substring(1, stringified.length - 1).replace(/\\"/g, '"');
|
|
29
|
-
if (char === "'")
|
|
30
|
-
return char + escapedText.replace(/[']/g, "\\'") + char;
|
|
31
|
-
if (char === '"')
|
|
32
|
-
return char + escapedText.replace(/["]/g, '\\"') + char;
|
|
33
|
-
if (char === "`")
|
|
34
|
-
return char + escapedText.replace(/[`]/g, "\\`") + char;
|
|
35
|
-
throw new Error("Invalid escape char");
|
|
36
|
-
}
|
|
37
|
-
function quote(text) {
|
|
38
|
-
return escapeWithQuotes(text, "'");
|
|
39
|
-
}
|
|
40
|
-
function formatObject(value, indent = " ", mode = "multiline") {
|
|
41
|
-
if (typeof value === "string")
|
|
42
|
-
return quote(value);
|
|
43
|
-
if (Array.isArray(value))
|
|
44
|
-
return `[${value.map((o) => formatObject(o)).join(", ")}]`;
|
|
45
|
-
if (typeof value === "object") {
|
|
46
|
-
const keys = Object.keys(value).filter((key) => value[key] !== void 0).sort();
|
|
47
|
-
if (!keys.length)
|
|
48
|
-
return "{}";
|
|
49
|
-
const tokens = [];
|
|
50
|
-
for (const key of keys)
|
|
51
|
-
tokens.push(`${key}: ${formatObject(value[key])}`);
|
|
52
|
-
if (mode === "multiline")
|
|
53
|
-
return `{
|
|
54
|
-
${tokens.join(`,
|
|
55
|
-
${indent}`)}
|
|
56
|
-
}`;
|
|
57
|
-
return `{ ${tokens.join(", ")} }`;
|
|
58
|
-
}
|
|
59
|
-
return String(value);
|
|
60
|
-
}
|
|
61
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
62
|
-
0 && (module.exports = {
|
|
63
|
-
escapeWithQuotes,
|
|
64
|
-
formatObject,
|
|
65
|
-
quote
|
|
66
|
-
});
|