playwright 1.58.0-alpha-2025-12-03 → 1.58.0-alpha-2025-12-05
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 +2 -2
- package/ThirdPartyNotices.txt +288 -2773
- package/lib/agents/agent.js +2 -2
- package/lib/agents/performTask.js +121 -8
- package/lib/index.js +6 -3
- package/lib/mcp/browser/browserContextFactory.js +2 -0
- package/lib/mcp/browser/browserServerBackend.js +2 -1
- package/lib/mcp/browser/config.js +21 -2
- package/lib/mcp/browser/response.js +129 -53
- package/lib/mcp/browser/tab.js +39 -8
- package/lib/mcp/browser/tools/common.js +5 -5
- package/lib/mcp/browser/tools/console.js +4 -4
- package/lib/mcp/browser/tools/dialogs.js +4 -4
- package/lib/mcp/browser/tools/evaluate.js +5 -5
- package/lib/mcp/browser/tools/files.js +3 -3
- package/lib/mcp/browser/tools/form.js +7 -7
- package/lib/mcp/browser/tools/install.js +2 -2
- package/lib/mcp/browser/tools/keyboard.js +6 -6
- package/lib/mcp/browser/tools/mouse.js +11 -11
- package/lib/mcp/browser/tools/navigate.js +4 -4
- package/lib/mcp/browser/tools/network.js +17 -11
- package/lib/mcp/browser/tools/pdf.js +3 -3
- package/lib/mcp/browser/tools/runCode.js +3 -3
- package/lib/mcp/browser/tools/screenshot.js +7 -7
- package/lib/mcp/browser/tools/snapshot.js +14 -14
- package/lib/mcp/browser/tools/tabs.js +4 -4
- package/lib/mcp/browser/tools/tool.js +8 -7
- package/lib/mcp/browser/tools/tracing.js +3 -3
- package/lib/mcp/browser/tools/verify.js +15 -15
- package/lib/mcp/browser/tools/wait.js +5 -5
- package/lib/mcp/program.js +1 -1
- package/lib/mcp/sdk/http.js +1 -1
- package/lib/mcp/sdk/proxyBackend.js +1 -1
- package/lib/mcp/sdk/server.js +1 -1
- package/lib/mcp/sdk/tool.js +2 -2
- package/lib/mcp/test/browserBackend.js +1 -1
- package/lib/mcp/test/generatorTools.js +9 -9
- package/lib/mcp/test/plannerTools.js +16 -16
- package/lib/mcp/test/testBackend.js +2 -2
- package/lib/mcp/test/testTools.js +9 -9
- package/package.json +2 -2
- package/types/test.d.ts +1 -3
- package/lib/mcp/sdk/bundle.js +0 -84
- package/lib/mcpBundleImpl.js +0 -96
package/lib/agents/agent.js
CHANGED
|
@@ -21,10 +21,10 @@ __export(agent_exports, {
|
|
|
21
21
|
Agent: () => Agent
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(agent_exports);
|
|
24
|
-
var
|
|
24
|
+
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
|
|
25
25
|
class Agent {
|
|
26
26
|
constructor(loopName, spec, clients, resultSchema) {
|
|
27
|
-
this.loop = new
|
|
27
|
+
this.loop = new import_mcpBundle.Loop(loopName, { model: spec.model });
|
|
28
28
|
this.spec = spec;
|
|
29
29
|
this.clients = clients;
|
|
30
30
|
this.resultSchema = resultSchema;
|
|
@@ -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,36 +17,147 @@ 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 performTask_exports = {};
|
|
20
30
|
__export(performTask_exports, {
|
|
31
|
+
performCache: () => performCache,
|
|
21
32
|
performTask: () => performTask
|
|
22
33
|
});
|
|
23
34
|
module.exports = __toCommonJS(performTask_exports);
|
|
35
|
+
var import_fs = __toESM(require("fs"));
|
|
36
|
+
var import_path = __toESM(require("path"));
|
|
24
37
|
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
38
|
+
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
|
|
25
39
|
var import_browserContextFactory = require("../mcp/browser/browserContextFactory");
|
|
26
40
|
var import_browserServerBackend = require("../mcp/browser/browserServerBackend");
|
|
27
41
|
var import_config = require("../mcp/browser/config");
|
|
28
|
-
var import_bundle = require("../mcp/sdk/bundle");
|
|
29
42
|
var import_server = require("../mcp/sdk/server");
|
|
30
|
-
|
|
43
|
+
const resultSchema = import_mcpBundle.z.object({
|
|
44
|
+
code: import_mcpBundle.z.string().optional().describe(`
|
|
45
|
+
Generated code to perform the task using Playwright API.
|
|
46
|
+
Check out the <code> blocks and combine them. Should be presented in the following form:
|
|
47
|
+
|
|
48
|
+
perform(async ({ page }) => {
|
|
49
|
+
// generated code here.
|
|
50
|
+
});
|
|
51
|
+
`),
|
|
52
|
+
error: import_mcpBundle.z.string().optional().describe("The error that occurred if execution failed.").optional()
|
|
53
|
+
});
|
|
54
|
+
async function performTask(testInfo, context, userTask, options) {
|
|
55
|
+
const cacheStatus = await performTaskFromCache(testInfo, context, userTask);
|
|
56
|
+
if (cacheStatus === "success")
|
|
57
|
+
return;
|
|
31
58
|
const backend = new import_browserServerBackend.BrowserServerBackend(import_config.defaultConfig, (0, import_browserContextFactory.identityBrowserContextFactory)(context));
|
|
32
59
|
const client = await (0, import_server.wrapInClient)(backend, { name: "Internal", version: "0.0.0" });
|
|
33
|
-
const loop = new import_bundle.Loop("github", { model: "claude-sonnet-4.5" });
|
|
34
60
|
const callTool = async (params) => {
|
|
35
61
|
return await client.callTool(params);
|
|
36
62
|
};
|
|
63
|
+
const loop = new import_mcpBundle.Loop(options.provider ?? "github", {
|
|
64
|
+
model: options.model ?? "claude-sonnet-4.5",
|
|
65
|
+
reasoning: options.reasoning,
|
|
66
|
+
temperature: options.temperature,
|
|
67
|
+
maxTokens: options.maxTokens,
|
|
68
|
+
summarize: true,
|
|
69
|
+
debug: import_utilsBundle.debug,
|
|
70
|
+
callTool,
|
|
71
|
+
tools: await backend.listTools()
|
|
72
|
+
});
|
|
37
73
|
try {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
debug: import_utilsBundle.debug
|
|
42
|
-
});
|
|
74
|
+
const result = await loop.run(userTask, { resultSchema: (0, import_mcpBundle.zodToJsonSchema)(resultSchema) });
|
|
75
|
+
if (result.code)
|
|
76
|
+
await updatePerformFile(testInfo, userTask, result.code, options);
|
|
43
77
|
} finally {
|
|
44
78
|
await client.close();
|
|
45
79
|
}
|
|
46
80
|
}
|
|
81
|
+
async function updatePerformFile(testInfo, userTask, taskCode, options) {
|
|
82
|
+
const relativeFile = import_path.default.relative(testInfo.project.testDir, testInfo.file);
|
|
83
|
+
const promptCacheFile = testInfo.file.replace(".spec.ts", ".cache.ts");
|
|
84
|
+
const testTitle = testInfo.title;
|
|
85
|
+
const loop = new import_mcpBundle.Loop(options.provider ?? "github", {
|
|
86
|
+
model: options.model ?? "claude-sonnet-4.5",
|
|
87
|
+
reasoning: options.reasoning,
|
|
88
|
+
temperature: options.temperature,
|
|
89
|
+
maxTokens: options.maxTokens,
|
|
90
|
+
summarize: true,
|
|
91
|
+
debug: import_utilsBundle.debug,
|
|
92
|
+
callTool: async () => ({ content: [] }),
|
|
93
|
+
tools: []
|
|
94
|
+
});
|
|
95
|
+
const resultSchema2 = import_mcpBundle.z.object({
|
|
96
|
+
code: import_mcpBundle.z.string().optional().describe(`
|
|
97
|
+
Generated code with all the perofrm routines combined or updated into the following format:
|
|
98
|
+
|
|
99
|
+
import { performCache } from '@playwright/test';
|
|
100
|
+
|
|
101
|
+
performCache({
|
|
102
|
+
file: 'tests/page/perform-task.spec.ts',
|
|
103
|
+
test: 'perform task',
|
|
104
|
+
task: 'Click the learn more button',
|
|
105
|
+
code: async ({ page }) => {
|
|
106
|
+
await page.getByRole('link', { name: 'Learn more' }).click();
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
`)
|
|
110
|
+
});
|
|
111
|
+
const existingCode = await import_fs.default.promises.readFile(promptCacheFile, "utf8").catch(() => "");
|
|
112
|
+
const task = `
|
|
113
|
+
- Create or update a perform file to include performCache block for the given task and code.
|
|
114
|
+
- Dedupe items with the same file, test, and task.
|
|
115
|
+
- Should produce code in the following format
|
|
116
|
+
|
|
117
|
+
import { performCache } from '@playwright/test';
|
|
118
|
+
|
|
119
|
+
performCache({
|
|
120
|
+
file: '<file>',
|
|
121
|
+
test: '<test>',
|
|
122
|
+
task: '<task>',
|
|
123
|
+
code: async ({ page }) => {
|
|
124
|
+
<code>
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
performCache({
|
|
129
|
+
...
|
|
130
|
+
|
|
131
|
+
## Params for the new or updated performCache block
|
|
132
|
+
<file-content>${existingCode}</file-content>
|
|
133
|
+
<file>${relativeFile}</file>
|
|
134
|
+
<test>${testTitle}</test>
|
|
135
|
+
<task>${userTask}</task>
|
|
136
|
+
<code>${taskCode}</code>
|
|
137
|
+
`;
|
|
138
|
+
const result = await loop.run(task, { resultSchema: (0, import_mcpBundle.zodToJsonSchema)(resultSchema2) });
|
|
139
|
+
if (result.code)
|
|
140
|
+
await import_fs.default.promises.writeFile(promptCacheFile, result.code);
|
|
141
|
+
}
|
|
142
|
+
const performCacheMap = /* @__PURE__ */ new Map();
|
|
143
|
+
function performCache(entry) {
|
|
144
|
+
performCacheMap.set(JSON.stringify({ ...entry, code: void 0 }), entry);
|
|
145
|
+
}
|
|
146
|
+
async function performTaskFromCache(testInfo, context, userTask) {
|
|
147
|
+
const relativeFile = import_path.default.relative(testInfo.project.testDir, testInfo.file);
|
|
148
|
+
const key = JSON.stringify({ file: relativeFile, test: testInfo.title, task: userTask });
|
|
149
|
+
const entry = performCacheMap.get(key);
|
|
150
|
+
if (!entry)
|
|
151
|
+
return "cache-miss";
|
|
152
|
+
try {
|
|
153
|
+
await entry.code({ page: context.pages()[0] });
|
|
154
|
+
return "success";
|
|
155
|
+
} catch (error) {
|
|
156
|
+
return error;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
47
159
|
// Annotate the CommonJS export names for ESM import in node:
|
|
48
160
|
0 && (module.exports = {
|
|
161
|
+
performCache,
|
|
49
162
|
performTask
|
|
50
163
|
});
|
package/lib/index.js
CHANGED
|
@@ -33,6 +33,7 @@ __export(index_exports, {
|
|
|
33
33
|
expect: () => import_expect.expect,
|
|
34
34
|
mergeExpects: () => import_expect2.mergeExpects,
|
|
35
35
|
mergeTests: () => import_testType2.mergeTests,
|
|
36
|
+
performCache: () => import_performTask2.performCache,
|
|
36
37
|
test: () => test
|
|
37
38
|
});
|
|
38
39
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -48,6 +49,7 @@ var import_expect = require("./matchers/expect");
|
|
|
48
49
|
var import_configLoader = require("./common/configLoader");
|
|
49
50
|
var import_testType2 = require("./common/testType");
|
|
50
51
|
var import_expect2 = require("./matchers/expect");
|
|
52
|
+
var import_performTask2 = require("./agents/performTask");
|
|
51
53
|
const _baseTest = import_testType.rootTestType.test;
|
|
52
54
|
(0, import_utils.setBoxedStackPrefixes)([import_path.default.dirname(require.resolve("../package.json"))]);
|
|
53
55
|
if (process["__pw_initiator__"]) {
|
|
@@ -417,9 +419,9 @@ const playwrightFixtures = {
|
|
|
417
419
|
await request.dispose();
|
|
418
420
|
}
|
|
419
421
|
},
|
|
420
|
-
_perform: async ({ context }, use) => {
|
|
421
|
-
await use(async (task) => {
|
|
422
|
-
await (0, import_performTask.performTask)(context, task);
|
|
422
|
+
_perform: async ({ context }, use, testInfo) => {
|
|
423
|
+
await use(async (task, options) => {
|
|
424
|
+
await (0, import_performTask.performTask)(testInfo, context, task, options ?? {});
|
|
423
425
|
});
|
|
424
426
|
}
|
|
425
427
|
};
|
|
@@ -684,5 +686,6 @@ const test = _baseTest.extend(playwrightFixtures);
|
|
|
684
686
|
expect,
|
|
685
687
|
mergeExpects,
|
|
686
688
|
mergeTests,
|
|
689
|
+
performCache,
|
|
687
690
|
test
|
|
688
691
|
});
|
|
@@ -216,6 +216,8 @@ class PersistentContextFactory {
|
|
|
216
216
|
});
|
|
217
217
|
await afterClose();
|
|
218
218
|
this._userDataDirs.delete(userDataDir);
|
|
219
|
+
if (process.env.PWMCP_PROFILES_DIR_FOR_TEST && userDataDir.startsWith(process.env.PWMCP_PROFILES_DIR_FOR_TEST))
|
|
220
|
+
await import_fs.default.promises.rm(userDataDir, { recursive: true }).catch(import_log.logUnhandledError);
|
|
219
221
|
(0, import_log.testDebug)("close browser context complete (persistent)");
|
|
220
222
|
}
|
|
221
223
|
async _createUserDataDir(clientInfo) {
|
|
@@ -64,7 +64,8 @@ class BrowserServerBackend {
|
|
|
64
64
|
context.setRunningTool(void 0);
|
|
65
65
|
}
|
|
66
66
|
response.logEnd();
|
|
67
|
-
|
|
67
|
+
const _meta = rawArguments?._meta;
|
|
68
|
+
return response.serialize({ _meta });
|
|
68
69
|
}
|
|
69
70
|
serverClosed() {
|
|
70
71
|
void this._context?.dispose().catch(import_log.logUnhandledError);
|
|
@@ -32,6 +32,7 @@ __export(config_exports, {
|
|
|
32
32
|
configFromCLIOptions: () => configFromCLIOptions,
|
|
33
33
|
defaultConfig: () => defaultConfig,
|
|
34
34
|
dotenvFileLoader: () => dotenvFileLoader,
|
|
35
|
+
enumParser: () => enumParser,
|
|
35
36
|
headerParser: () => headerParser,
|
|
36
37
|
numberParser: () => numberParser,
|
|
37
38
|
outputDir: () => outputDir,
|
|
@@ -61,6 +62,9 @@ const defaultConfig = {
|
|
|
61
62
|
viewport: null
|
|
62
63
|
}
|
|
63
64
|
},
|
|
65
|
+
console: {
|
|
66
|
+
level: "info"
|
|
67
|
+
},
|
|
64
68
|
network: {
|
|
65
69
|
allowedOrigins: void 0,
|
|
66
70
|
blockedOrigins: void 0
|
|
@@ -182,6 +186,9 @@ function configFromCLIOptions(cliOptions) {
|
|
|
182
186
|
allowedHosts: cliOptions.allowedHosts
|
|
183
187
|
},
|
|
184
188
|
capabilities: cliOptions.caps,
|
|
189
|
+
console: {
|
|
190
|
+
level: cliOptions.consoleLevel
|
|
191
|
+
},
|
|
185
192
|
network: {
|
|
186
193
|
allowedOrigins: cliOptions.allowedOrigins,
|
|
187
194
|
blockedOrigins: cliOptions.blockedOrigins
|
|
@@ -213,6 +220,8 @@ function configFromEnv() {
|
|
|
213
220
|
options.cdpEndpoint = envToString(process.env.PLAYWRIGHT_MCP_CDP_ENDPOINT);
|
|
214
221
|
options.cdpHeader = headerParser(process.env.PLAYWRIGHT_MCP_CDP_HEADERS, {});
|
|
215
222
|
options.config = envToString(process.env.PLAYWRIGHT_MCP_CONFIG);
|
|
223
|
+
if (process.env.PLAYWRIGHT_MCP_CONSOLE_LEVEL)
|
|
224
|
+
options.consoleLevel = enumParser("--console-level", ["error", "warning", "info", "debug"], process.env.PLAYWRIGHT_MCP_CONSOLE_LEVEL);
|
|
216
225
|
options.device = envToString(process.env.PLAYWRIGHT_MCP_DEVICE);
|
|
217
226
|
options.executablePath = envToString(process.env.PLAYWRIGHT_MCP_EXECUTABLE_PATH);
|
|
218
227
|
options.grantPermissions = commaSeparatedList(process.env.PLAYWRIGHT_MCP_GRANT_PERMISSIONS);
|
|
@@ -226,8 +235,8 @@ function configFromEnv() {
|
|
|
226
235
|
if (initScript)
|
|
227
236
|
options.initScript = [initScript];
|
|
228
237
|
options.isolated = envToBoolean(process.env.PLAYWRIGHT_MCP_ISOLATED);
|
|
229
|
-
if (process.env.PLAYWRIGHT_MCP_IMAGE_RESPONSES
|
|
230
|
-
options.imageResponses = "omit";
|
|
238
|
+
if (process.env.PLAYWRIGHT_MCP_IMAGE_RESPONSES)
|
|
239
|
+
options.imageResponses = enumParser("--image-responses", ["allow", "omit"], process.env.PLAYWRIGHT_MCP_IMAGE_RESPONSES);
|
|
231
240
|
options.sandbox = envToBoolean(process.env.PLAYWRIGHT_MCP_SANDBOX);
|
|
232
241
|
options.outputDir = envToString(process.env.PLAYWRIGHT_MCP_OUTPUT_DIR);
|
|
233
242
|
options.port = numberParser(process.env.PLAYWRIGHT_MCP_PORT);
|
|
@@ -306,6 +315,10 @@ function mergeConfig(base, overrides) {
|
|
|
306
315
|
...pickDefined(base),
|
|
307
316
|
...pickDefined(overrides),
|
|
308
317
|
browser,
|
|
318
|
+
console: {
|
|
319
|
+
...pickDefined(base.console),
|
|
320
|
+
...pickDefined(overrides.console)
|
|
321
|
+
},
|
|
309
322
|
network: {
|
|
310
323
|
...pickDefined(base.network),
|
|
311
324
|
...pickDefined(overrides.network)
|
|
@@ -369,6 +382,11 @@ function headerParser(arg, previous) {
|
|
|
369
382
|
result[name] = value;
|
|
370
383
|
return result;
|
|
371
384
|
}
|
|
385
|
+
function enumParser(name, options, value) {
|
|
386
|
+
if (!options.includes(value))
|
|
387
|
+
throw new Error(`Invalid ${name}: ${value}. Valid values are: ${options.join(", ")}`);
|
|
388
|
+
return value;
|
|
389
|
+
}
|
|
372
390
|
function envToBoolean(value) {
|
|
373
391
|
if (value === "true" || value === "1")
|
|
374
392
|
return true;
|
|
@@ -392,6 +410,7 @@ function sanitizeForFilePath(s) {
|
|
|
392
410
|
configFromCLIOptions,
|
|
393
411
|
defaultConfig,
|
|
394
412
|
dotenvFileLoader,
|
|
413
|
+
enumParser,
|
|
395
414
|
headerParser,
|
|
396
415
|
numberParser,
|
|
397
416
|
outputDir,
|
|
@@ -18,6 +18,7 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var response_exports = {};
|
|
20
20
|
__export(response_exports, {
|
|
21
|
+
RenderedResponse: () => RenderedResponse,
|
|
21
22
|
Response: () => Response,
|
|
22
23
|
parseResponse: () => parseResponse,
|
|
23
24
|
requestDebug: () => requestDebug
|
|
@@ -71,6 +72,9 @@ class Response {
|
|
|
71
72
|
setIncludeTabs() {
|
|
72
73
|
this._includeTabs = true;
|
|
73
74
|
}
|
|
75
|
+
setIncludeModalStates(modalStates) {
|
|
76
|
+
this._includeModalStates = modalStates;
|
|
77
|
+
}
|
|
74
78
|
async finish() {
|
|
75
79
|
if (this._includeSnapshot !== "none" && this._context.currentTab())
|
|
76
80
|
this._tabSnapshot = await this._context.currentTabOrDie().captureSnapshot();
|
|
@@ -89,72 +93,64 @@ class Response {
|
|
|
89
93
|
requestDebug(this.serialize({ omitSnapshot: true, omitBlobs: true }));
|
|
90
94
|
}
|
|
91
95
|
serialize(options = {}) {
|
|
92
|
-
const
|
|
93
|
-
if (this._result.length)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
${this._code.join("\n")}
|
|
102
|
-
\`\`\``);
|
|
103
|
-
response.push("");
|
|
96
|
+
const renderedResponse = new RenderedResponse();
|
|
97
|
+
if (this._result.length)
|
|
98
|
+
renderedResponse.results.push(...this._result);
|
|
99
|
+
if (this._code.length)
|
|
100
|
+
renderedResponse.code.push(...this._code);
|
|
101
|
+
if (this._includeSnapshot !== "none" || this._includeTabs) {
|
|
102
|
+
const tabsMarkdown = renderTabsMarkdown(this._context.tabs(), this._includeTabs);
|
|
103
|
+
if (tabsMarkdown.length)
|
|
104
|
+
renderedResponse.states.tabs = tabsMarkdown.join("\n");
|
|
104
105
|
}
|
|
105
|
-
if (this._includeSnapshot !== "none" || this._includeTabs)
|
|
106
|
-
response.push(...renderTabsMarkdown(this._context.tabs(), this._includeTabs));
|
|
107
106
|
if (this._tabSnapshot?.modalStates.length) {
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
const modalStatesMarkdown = (0, import_tab.renderModalStates)(this._tabSnapshot.modalStates);
|
|
108
|
+
renderedResponse.states.modal = modalStatesMarkdown.join("\n");
|
|
110
109
|
} else if (this._tabSnapshot) {
|
|
111
110
|
const includeSnapshot = options.omitSnapshot ? "none" : this._includeSnapshot;
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
renderTabSnapshot(this._tabSnapshot, includeSnapshot, renderedResponse);
|
|
112
|
+
} else if (this._includeModalStates) {
|
|
113
|
+
const modalStatesMarkdown = (0, import_tab.renderModalStates)(this._includeModalStates);
|
|
114
|
+
renderedResponse.states.modal = modalStatesMarkdown.join("\n");
|
|
114
115
|
}
|
|
116
|
+
const redactedResponse = this._context.config.secrets ? renderedResponse.redact(this._context.config.secrets) : renderedResponse;
|
|
117
|
+
const includeMeta = options._meta && "dev.lowire/history" in options._meta && "dev.lowire/state" in options._meta;
|
|
118
|
+
const _meta = includeMeta ? redactedResponse.asMeta() : void 0;
|
|
115
119
|
const content = [
|
|
116
|
-
{ type: "text", text:
|
|
120
|
+
{ type: "text", text: redactedResponse.asText() }
|
|
117
121
|
];
|
|
118
122
|
if (this._context.config.imageResponses !== "omit") {
|
|
119
123
|
for (const image of this._images)
|
|
120
124
|
content.push({ type: "image", data: options.omitBlobs ? "<blob>" : image.data.toString("base64"), mimeType: image.contentType });
|
|
121
125
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return;
|
|
128
|
-
for (const item of content) {
|
|
129
|
-
if (item.type !== "text")
|
|
130
|
-
continue;
|
|
131
|
-
for (const [secretName, secretValue] of Object.entries(this._context.config.secrets))
|
|
132
|
-
item.text = item.text.replaceAll(secretValue, `<secret>${secretName}</secret>`);
|
|
133
|
-
}
|
|
126
|
+
return {
|
|
127
|
+
_meta,
|
|
128
|
+
content,
|
|
129
|
+
isError: this._isError
|
|
130
|
+
};
|
|
134
131
|
}
|
|
135
132
|
}
|
|
136
|
-
function renderTabSnapshot(tabSnapshot, includeSnapshot) {
|
|
137
|
-
const lines = [];
|
|
133
|
+
function renderTabSnapshot(tabSnapshot, includeSnapshot, response) {
|
|
138
134
|
if (tabSnapshot.consoleMessages.length) {
|
|
139
|
-
|
|
135
|
+
const lines2 = [];
|
|
140
136
|
for (const message of tabSnapshot.consoleMessages)
|
|
141
|
-
|
|
142
|
-
|
|
137
|
+
lines2.push(`- ${trim(message.toString(), 100)}`);
|
|
138
|
+
response.updates.push({ category: "console", content: lines2.join("\n") });
|
|
143
139
|
}
|
|
144
140
|
if (tabSnapshot.downloads.length) {
|
|
145
|
-
|
|
141
|
+
const lines2 = [];
|
|
146
142
|
for (const entry of tabSnapshot.downloads) {
|
|
147
143
|
if (entry.finished)
|
|
148
|
-
|
|
144
|
+
lines2.push(`- Downloaded file ${entry.download.suggestedFilename()} to ${entry.outputFile}`);
|
|
149
145
|
else
|
|
150
|
-
|
|
146
|
+
lines2.push(`- Downloading file ${entry.download.suggestedFilename()} ...`);
|
|
151
147
|
}
|
|
152
|
-
|
|
148
|
+
response.updates.push({ category: "downloads", content: lines2.join("\n") });
|
|
153
149
|
}
|
|
154
150
|
if (includeSnapshot === "incremental" && tabSnapshot.ariaSnapshotDiff === "") {
|
|
155
|
-
return
|
|
151
|
+
return;
|
|
156
152
|
}
|
|
157
|
-
lines
|
|
153
|
+
const lines = [];
|
|
158
154
|
lines.push(`- Page URL: ${tabSnapshot.url}`);
|
|
159
155
|
lines.push(`- Page Title: ${tabSnapshot.title}`);
|
|
160
156
|
if (includeSnapshot !== "none") {
|
|
@@ -166,25 +162,19 @@ function renderTabSnapshot(tabSnapshot, includeSnapshot) {
|
|
|
166
162
|
lines.push(tabSnapshot.ariaSnapshot);
|
|
167
163
|
lines.push("```");
|
|
168
164
|
}
|
|
169
|
-
|
|
165
|
+
response.states.page = lines.join("\n");
|
|
170
166
|
}
|
|
171
167
|
function renderTabsMarkdown(tabs, force = false) {
|
|
172
168
|
if (tabs.length === 1 && !force)
|
|
173
169
|
return [];
|
|
174
|
-
if (!tabs.length)
|
|
175
|
-
return [
|
|
176
|
-
|
|
177
|
-
'No open tabs. Use the "browser_navigate" tool to navigate to a page first.',
|
|
178
|
-
""
|
|
179
|
-
];
|
|
180
|
-
}
|
|
181
|
-
const lines = ["### Open tabs"];
|
|
170
|
+
if (!tabs.length)
|
|
171
|
+
return ['No open tabs. Use the "browser_navigate" tool to navigate to a page first.'];
|
|
172
|
+
const lines = [];
|
|
182
173
|
for (let i = 0; i < tabs.length; i++) {
|
|
183
174
|
const tab = tabs[i];
|
|
184
175
|
const current = tab.isCurrentTab() ? " (current)" : "";
|
|
185
176
|
lines.push(`- ${i}:${current} [${tab.lastTitle()}] (${tab.page.url()})`);
|
|
186
177
|
}
|
|
187
|
-
lines.push("");
|
|
188
178
|
return lines;
|
|
189
179
|
}
|
|
190
180
|
function trim(text, maxLength) {
|
|
@@ -192,6 +182,90 @@ function trim(text, maxLength) {
|
|
|
192
182
|
return text;
|
|
193
183
|
return text.slice(0, maxLength) + "...";
|
|
194
184
|
}
|
|
185
|
+
class RenderedResponse {
|
|
186
|
+
constructor(copy) {
|
|
187
|
+
this.states = {};
|
|
188
|
+
this.updates = [];
|
|
189
|
+
this.results = [];
|
|
190
|
+
this.code = [];
|
|
191
|
+
if (copy) {
|
|
192
|
+
this.states = copy.states;
|
|
193
|
+
this.updates = copy.updates;
|
|
194
|
+
this.results = copy.results;
|
|
195
|
+
this.code = copy.code;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
asText() {
|
|
199
|
+
const text = [];
|
|
200
|
+
if (this.results.length)
|
|
201
|
+
text.push(`### Result
|
|
202
|
+
${this.results.join("\n")}
|
|
203
|
+
`);
|
|
204
|
+
if (this.code.length)
|
|
205
|
+
text.push(`### Ran Playwright code
|
|
206
|
+
${this.code.join("\n")}
|
|
207
|
+
`);
|
|
208
|
+
for (const { category, content } of this.updates) {
|
|
209
|
+
if (!content.trim())
|
|
210
|
+
continue;
|
|
211
|
+
switch (category) {
|
|
212
|
+
case "console":
|
|
213
|
+
text.push(`### New console messages
|
|
214
|
+
${content}
|
|
215
|
+
`);
|
|
216
|
+
break;
|
|
217
|
+
case "downloads":
|
|
218
|
+
text.push(`### Downloads
|
|
219
|
+
${content}
|
|
220
|
+
`);
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
for (const [category, value] of Object.entries(this.states)) {
|
|
225
|
+
if (!value.trim())
|
|
226
|
+
continue;
|
|
227
|
+
switch (category) {
|
|
228
|
+
case "page":
|
|
229
|
+
text.push(`### Page state
|
|
230
|
+
${value}
|
|
231
|
+
`);
|
|
232
|
+
break;
|
|
233
|
+
case "tabs":
|
|
234
|
+
text.push(`### Open tabs
|
|
235
|
+
${value}
|
|
236
|
+
`);
|
|
237
|
+
break;
|
|
238
|
+
case "modal":
|
|
239
|
+
text.push(`### Modal state
|
|
240
|
+
${value}
|
|
241
|
+
`);
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return text.join("\n");
|
|
246
|
+
}
|
|
247
|
+
asMeta() {
|
|
248
|
+
const codeUpdate = this.code.length ? { category: "code", content: this.code.join("\n") } : void 0;
|
|
249
|
+
const resultUpdate = this.results.length ? { category: "result", content: this.results.join("\n") } : void 0;
|
|
250
|
+
const updates = [resultUpdate, codeUpdate, ...this.updates].filter(Boolean);
|
|
251
|
+
return {
|
|
252
|
+
"dev.lowire/history": updates,
|
|
253
|
+
"dev.lowire/state": { ...this.states }
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
redact(secrets) {
|
|
257
|
+
const redactText = (text) => {
|
|
258
|
+
for (const [secretName, secretValue] of Object.entries(secrets))
|
|
259
|
+
text = text.replaceAll(secretValue, `<secret>${secretName}</secret>`);
|
|
260
|
+
return text;
|
|
261
|
+
};
|
|
262
|
+
const updates = this.updates.map((update) => ({ ...update, content: redactText(update.content) }));
|
|
263
|
+
const results = this.results.map((result) => redactText(result));
|
|
264
|
+
const code = this.code.map((code2) => redactText(code2));
|
|
265
|
+
const states = Object.fromEntries(Object.entries(this.states).map(([key, value]) => [key, redactText(value)]));
|
|
266
|
+
return new RenderedResponse({ states, updates, results, code });
|
|
267
|
+
}
|
|
268
|
+
}
|
|
195
269
|
function parseSections(text) {
|
|
196
270
|
const sections = /* @__PURE__ */ new Map();
|
|
197
271
|
const sectionHeaders = text.split(/^### /m).slice(1);
|
|
@@ -229,11 +303,13 @@ function parseResponse(response) {
|
|
|
229
303
|
modalState,
|
|
230
304
|
downloads,
|
|
231
305
|
isError,
|
|
232
|
-
attachments
|
|
306
|
+
attachments,
|
|
307
|
+
_meta: response._meta
|
|
233
308
|
};
|
|
234
309
|
}
|
|
235
310
|
// Annotate the CommonJS export names for ESM import in node:
|
|
236
311
|
0 && (module.exports = {
|
|
312
|
+
RenderedResponse,
|
|
237
313
|
Response,
|
|
238
314
|
parseResponse,
|
|
239
315
|
requestDebug
|
package/lib/mcp/browser/tab.js
CHANGED
|
@@ -105,9 +105,6 @@ class Tab extends import_events.EventEmitter {
|
|
|
105
105
|
clearModalState(modalState) {
|
|
106
106
|
this._modalStates = this._modalStates.filter((state) => state !== modalState);
|
|
107
107
|
}
|
|
108
|
-
modalStatesMarkdown() {
|
|
109
|
-
return renderModalStates(this.context, this.modalStates());
|
|
110
|
-
}
|
|
111
108
|
_dialogShown(dialog) {
|
|
112
109
|
this.setModalState({
|
|
113
110
|
type: "dialog",
|
|
@@ -176,9 +173,9 @@ class Tab extends import_events.EventEmitter {
|
|
|
176
173
|
}
|
|
177
174
|
await this.waitForLoadState("load", { timeout: 5e3 });
|
|
178
175
|
}
|
|
179
|
-
async consoleMessages(
|
|
176
|
+
async consoleMessages(level) {
|
|
180
177
|
await this._initializedPromise;
|
|
181
|
-
return this._consoleMessages.filter((message) =>
|
|
178
|
+
return this._consoleMessages.filter((message) => shouldIncludeMessage(level, message.type));
|
|
182
179
|
}
|
|
183
180
|
async requests() {
|
|
184
181
|
await this._initializedPromise;
|
|
@@ -200,7 +197,7 @@ class Tab extends import_events.EventEmitter {
|
|
|
200
197
|
};
|
|
201
198
|
});
|
|
202
199
|
if (tabSnapshot) {
|
|
203
|
-
tabSnapshot.consoleMessages = this._recentConsoleMessages;
|
|
200
|
+
tabSnapshot.consoleMessages = this._recentConsoleMessages.filter((message) => shouldIncludeMessage(this.context.config.console.level, message.type));
|
|
204
201
|
this._recentConsoleMessages = [];
|
|
205
202
|
}
|
|
206
203
|
this._needsFullSnapshot = !tabSnapshot;
|
|
@@ -282,14 +279,48 @@ function pageErrorToConsoleMessage(errorOrValue) {
|
|
|
282
279
|
toString: () => String(errorOrValue)
|
|
283
280
|
};
|
|
284
281
|
}
|
|
285
|
-
function renderModalStates(
|
|
286
|
-
const result = [
|
|
282
|
+
function renderModalStates(modalStates) {
|
|
283
|
+
const result = [];
|
|
287
284
|
if (modalStates.length === 0)
|
|
288
285
|
result.push("- There is no modal state present");
|
|
289
286
|
for (const state of modalStates)
|
|
290
287
|
result.push(`- [${state.description}]: can be handled by the "${state.clearedBy}" tool`);
|
|
291
288
|
return result;
|
|
292
289
|
}
|
|
290
|
+
const consoleMessageLevels = ["error", "warning", "info", "debug"];
|
|
291
|
+
function shouldIncludeMessage(thresholdLevel, type) {
|
|
292
|
+
const messageLevel = consoleLevelForMessageType(type);
|
|
293
|
+
return consoleMessageLevels.indexOf(messageLevel) <= consoleMessageLevels.indexOf(thresholdLevel);
|
|
294
|
+
}
|
|
295
|
+
function consoleLevelForMessageType(type) {
|
|
296
|
+
switch (type) {
|
|
297
|
+
case "assert":
|
|
298
|
+
case "error":
|
|
299
|
+
return "error";
|
|
300
|
+
case "warning":
|
|
301
|
+
return "warning";
|
|
302
|
+
case "count":
|
|
303
|
+
case "dir":
|
|
304
|
+
case "dirxml":
|
|
305
|
+
case "info":
|
|
306
|
+
case "log":
|
|
307
|
+
case "table":
|
|
308
|
+
case "time":
|
|
309
|
+
case "timeEnd":
|
|
310
|
+
return "info";
|
|
311
|
+
case "clear":
|
|
312
|
+
case "debug":
|
|
313
|
+
case "endGroup":
|
|
314
|
+
case "profile":
|
|
315
|
+
case "profileEnd":
|
|
316
|
+
case "startGroup":
|
|
317
|
+
case "startGroupCollapsed":
|
|
318
|
+
case "trace":
|
|
319
|
+
return "debug";
|
|
320
|
+
default:
|
|
321
|
+
return "info";
|
|
322
|
+
}
|
|
323
|
+
}
|
|
293
324
|
const tabSymbol = Symbol("tabSymbol");
|
|
294
325
|
// Annotate the CommonJS export names for ESM import in node:
|
|
295
326
|
0 && (module.exports = {
|