@cotestdev/mcp_playwright 0.0.51 → 0.0.52
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/mcp/browser/browserContextFactory.js +11 -4
- package/lib/mcp/browser/browserServerBackend.js +2 -4
- package/lib/mcp/browser/config.js +71 -47
- package/lib/mcp/browser/context.js +65 -4
- package/lib/mcp/browser/logFile.js +96 -0
- package/lib/mcp/browser/response.js +107 -104
- package/lib/mcp/browser/sessionLog.js +1 -1
- package/lib/mcp/browser/tab.js +73 -18
- package/lib/mcp/browser/tools/config.js +41 -0
- package/lib/mcp/browser/tools/console.js +6 -2
- package/lib/mcp/browser/tools/cookies.js +152 -0
- package/lib/mcp/browser/tools/install.js +1 -0
- package/lib/mcp/browser/tools/network.js +25 -11
- package/lib/mcp/browser/tools/pdf.js +3 -4
- package/lib/mcp/browser/tools/route.js +140 -0
- package/lib/mcp/browser/tools/runCode.js +0 -2
- package/lib/mcp/browser/tools/screenshot.js +6 -7
- package/lib/mcp/browser/tools/storage.js +3 -4
- package/lib/mcp/browser/tools/tracing.js +10 -9
- package/lib/mcp/browser/tools/utils.js +0 -6
- package/lib/mcp/browser/tools/video.js +31 -13
- package/lib/mcp/browser/tools/webstorage.js +223 -0
- package/lib/mcp/browser/tools.js +11 -3
- package/lib/mcp/extension/cdpRelay.js +7 -7
- package/lib/mcp/extension/extensionContextFactory.js +4 -2
- package/lib/mcp/program.js +19 -12
- package/lib/mcp/terminal/cli.js +23 -2
- package/lib/mcp/terminal/command.js +34 -30
- package/lib/mcp/terminal/commands.js +310 -38
- package/lib/mcp/terminal/daemon.js +23 -38
- package/lib/mcp/terminal/helpGenerator.js +8 -6
- package/lib/mcp/terminal/program.js +482 -199
- package/lib/mcp/terminal/socketConnection.js +17 -2
- package/package.json +2 -2
|
@@ -128,7 +128,7 @@ class IsolatedContextFactory extends BaseContextFactory {
|
|
|
128
128
|
handleSIGTERM: false
|
|
129
129
|
}).catch((error) => {
|
|
130
130
|
if (error.message.includes("Executable doesn't exist"))
|
|
131
|
-
|
|
131
|
+
throwBrowserIsNotInstalledError(this.config);
|
|
132
132
|
throw error;
|
|
133
133
|
});
|
|
134
134
|
}
|
|
@@ -201,7 +201,7 @@ class PersistentContextFactory {
|
|
|
201
201
|
return { browserContext, close };
|
|
202
202
|
} catch (error) {
|
|
203
203
|
if (error.message.includes("Executable doesn't exist"))
|
|
204
|
-
|
|
204
|
+
throwBrowserIsNotInstalledError(this.config);
|
|
205
205
|
if (error.message.includes("cannot open shared object file: No such file or directory")) {
|
|
206
206
|
const browserName = launchOptions.channel ?? this.config.browser.browserName;
|
|
207
207
|
throw new Error(`Missing system dependencies required to run browser ${browserName}. Install them with: sudo npx playwright install-deps ${browserName}`);
|
|
@@ -306,12 +306,12 @@ class SharedContextFactory {
|
|
|
306
306
|
async function computeTracesDir(config, clientInfo) {
|
|
307
307
|
if (!config.saveTrace && !config.capabilities?.includes("devtools"))
|
|
308
308
|
return;
|
|
309
|
-
return await (0, import_config.outputFile)(config, clientInfo, `traces`, { origin: "code"
|
|
309
|
+
return await (0, import_config.outputFile)(config, clientInfo, `traces`, { origin: "code" });
|
|
310
310
|
}
|
|
311
311
|
async function browserContextOptionsFromConfig(config, clientInfo) {
|
|
312
312
|
const result = { ...config.browser.contextOptions };
|
|
313
313
|
if (config.saveVideo) {
|
|
314
|
-
const dir = await (0, import_config.outputFile)(config, clientInfo, `videos`, { origin: "code"
|
|
314
|
+
const dir = await (0, import_config.outputFile)(config, clientInfo, `videos`, { origin: "code" });
|
|
315
315
|
result.recordVideo = {
|
|
316
316
|
dir,
|
|
317
317
|
size: config.saveVideo
|
|
@@ -319,6 +319,13 @@ async function browserContextOptionsFromConfig(config, clientInfo) {
|
|
|
319
319
|
}
|
|
320
320
|
return result;
|
|
321
321
|
}
|
|
322
|
+
function throwBrowserIsNotInstalledError(config) {
|
|
323
|
+
const channel = config.browser.launchOptions?.channel ?? config.browser.browserName;
|
|
324
|
+
if (config.skillMode)
|
|
325
|
+
throw new Error(`Browser "${channel}" is not installed. Run \`playwright-cli install-browser ${channel}\` to install`);
|
|
326
|
+
else
|
|
327
|
+
throw new Error(`Browser "${channel}" is not installed. Either install it (likely) or change the config.`);
|
|
328
|
+
}
|
|
322
329
|
// Annotate the CommonJS export names for ESM import in node:
|
|
323
330
|
0 && (module.exports = {
|
|
324
331
|
SharedContextFactory,
|
|
@@ -41,7 +41,6 @@ class BrowserServerBackend {
|
|
|
41
41
|
sessionLog: this._sessionLog,
|
|
42
42
|
clientInfo
|
|
43
43
|
});
|
|
44
|
-
this._context.onBrowserContextClosed = () => this.onBrowserContextClosed?.();
|
|
45
44
|
}
|
|
46
45
|
async listTools() {
|
|
47
46
|
return this._tools.map((tool) => (0, import_tool.toMcpTool)(tool.schema));
|
|
@@ -58,13 +57,12 @@ Tool "${name}" not found` }],
|
|
|
58
57
|
const parsedArguments = tool.schema.inputSchema.parse(rawArguments || {});
|
|
59
58
|
const cwd = rawArguments?._meta && typeof rawArguments?._meta === "object" && rawArguments._meta?.cwd;
|
|
60
59
|
const context = this._context;
|
|
61
|
-
const response = import_response.Response
|
|
60
|
+
const response = new import_response.Response(context, name, parsedArguments, cwd);
|
|
62
61
|
context.setRunningTool(name);
|
|
63
62
|
let responseObject;
|
|
64
63
|
try {
|
|
65
64
|
await tool.handle(context, parsedArguments, response);
|
|
66
|
-
|
|
67
|
-
responseObject = await (0, import_response.serializeResponse)(context, sections, cwd ?? context.firstRootPath());
|
|
65
|
+
responseObject = await response.serialize();
|
|
68
66
|
this._sessionLog?.logResponse(name, parsedArguments, responseObject);
|
|
69
67
|
} catch (error) {
|
|
70
68
|
return {
|
|
@@ -40,7 +40,9 @@ __export(config_exports, {
|
|
|
40
40
|
resolutionParser: () => resolutionParser,
|
|
41
41
|
resolveCLIConfig: () => resolveCLIConfig,
|
|
42
42
|
resolveConfig: () => resolveConfig,
|
|
43
|
-
semicolonSeparatedList: () => semicolonSeparatedList
|
|
43
|
+
semicolonSeparatedList: () => semicolonSeparatedList,
|
|
44
|
+
workspaceDir: () => workspaceDir,
|
|
45
|
+
workspaceFile: () => workspaceFile
|
|
44
46
|
});
|
|
45
47
|
module.exports = __toCommonJS(config_exports);
|
|
46
48
|
var import_fs = __toESM(require("fs"));
|
|
@@ -59,7 +61,8 @@ const defaultConfig = {
|
|
|
59
61
|
},
|
|
60
62
|
contextOptions: {
|
|
61
63
|
viewport: null
|
|
62
|
-
}
|
|
64
|
+
},
|
|
65
|
+
isolated: false
|
|
63
66
|
},
|
|
64
67
|
console: {
|
|
65
68
|
level: "info"
|
|
@@ -79,38 +82,35 @@ const defaultConfig = {
|
|
|
79
82
|
navigation: 6e4
|
|
80
83
|
}
|
|
81
84
|
};
|
|
82
|
-
const defaultDaemonConfig =
|
|
85
|
+
const defaultDaemonConfig = mergeConfig(defaultConfig, {
|
|
83
86
|
browser: {
|
|
84
|
-
userDataDir: cliOptions.extension ? void 0 : "<daemon-data-dir>",
|
|
85
|
-
// Use default user profile with extension.
|
|
86
87
|
launchOptions: {
|
|
87
|
-
headless:
|
|
88
|
+
headless: true
|
|
88
89
|
},
|
|
89
|
-
|
|
90
|
-
viewport: cliOptions.daemonHeaded ? null : { width: 1280, height: 720 }
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
|
-
outputMode: "file",
|
|
94
|
-
snapshot: {
|
|
95
|
-
mode: "full"
|
|
90
|
+
isolated: true
|
|
96
91
|
}
|
|
97
92
|
});
|
|
98
93
|
async function resolveConfig(config) {
|
|
99
94
|
return mergeConfig(defaultConfig, config);
|
|
100
95
|
}
|
|
101
96
|
async function resolveCLIConfig(cliOptions) {
|
|
102
|
-
const configInFile = await loadConfig(cliOptions.config);
|
|
103
97
|
const envOverrides = configFromEnv();
|
|
98
|
+
const daemonOverrides = await configForDaemonSession(cliOptions);
|
|
104
99
|
const cliOverrides = configFromCLIOptions(cliOptions);
|
|
105
|
-
|
|
100
|
+
const configFile = cliOverrides.configFile ?? envOverrides.configFile ?? daemonOverrides.configFile;
|
|
101
|
+
const configInFile = await loadConfig(configFile);
|
|
102
|
+
let result = cliOptions.daemonSession ? defaultDaemonConfig : defaultConfig;
|
|
106
103
|
result = mergeConfig(result, configInFile);
|
|
104
|
+
result = mergeConfig(result, daemonOverrides);
|
|
107
105
|
result = mergeConfig(result, envOverrides);
|
|
108
106
|
result = mergeConfig(result, cliOverrides);
|
|
109
|
-
if (
|
|
107
|
+
if (daemonOverrides.sessionConfig) {
|
|
110
108
|
result.skillMode = true;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
109
|
+
if (!result.extension && !result.browser.userDataDir && daemonOverrides.sessionConfig.userDataDirPrefix) {
|
|
110
|
+
const browserToken = result.browser.launchOptions?.channel ?? result.browser?.browserName;
|
|
111
|
+
const userDataDir = `${daemonOverrides.sessionConfig.userDataDirPrefix}-${browserToken}`;
|
|
112
|
+
result.browser.userDataDir = userDataDir;
|
|
113
|
+
}
|
|
114
114
|
}
|
|
115
115
|
if (result.browser.browserName === "chromium" && result.browser.launchOptions.chromiumSandbox === void 0) {
|
|
116
116
|
if (process.platform === "linux")
|
|
@@ -118,6 +118,10 @@ async function resolveCLIConfig(cliOptions) {
|
|
|
118
118
|
else
|
|
119
119
|
result.browser.launchOptions.chromiumSandbox = true;
|
|
120
120
|
}
|
|
121
|
+
result.configFile = configFile;
|
|
122
|
+
result.sessionConfig = daemonOverrides.sessionConfig;
|
|
123
|
+
if (result.sessionConfig && result.browser.launchOptions.headless !== false)
|
|
124
|
+
result.browser.contextOptions.viewport ??= { width: 1280, height: 720 };
|
|
121
125
|
await validateConfig(result);
|
|
122
126
|
return result;
|
|
123
127
|
}
|
|
@@ -189,7 +193,7 @@ function configFromCLIOptions(cliOptions) {
|
|
|
189
193
|
contextOptions.serviceWorkers = "block";
|
|
190
194
|
if (cliOptions.grantPermissions)
|
|
191
195
|
contextOptions.permissions = cliOptions.grantPermissions;
|
|
192
|
-
const
|
|
196
|
+
const config = {
|
|
193
197
|
browser: {
|
|
194
198
|
browserName,
|
|
195
199
|
isolated: cliOptions.isolated,
|
|
@@ -198,6 +202,7 @@ function configFromCLIOptions(cliOptions) {
|
|
|
198
202
|
contextOptions,
|
|
199
203
|
cdpEndpoint: cliOptions.cdpEndpoint,
|
|
200
204
|
cdpHeaders: cliOptions.cdpHeader,
|
|
205
|
+
cdpTimeout: cliOptions.cdpTimeout,
|
|
201
206
|
initPage: cliOptions.initPage,
|
|
202
207
|
initScript: cliOptions.initScript
|
|
203
208
|
},
|
|
@@ -232,7 +237,7 @@ function configFromCLIOptions(cliOptions) {
|
|
|
232
237
|
navigation: cliOptions.timeoutNavigation
|
|
233
238
|
}
|
|
234
239
|
};
|
|
235
|
-
return
|
|
240
|
+
return { ...config, configFile: cliOptions.config };
|
|
236
241
|
}
|
|
237
242
|
function configFromEnv() {
|
|
238
243
|
const options = {};
|
|
@@ -245,6 +250,7 @@ function configFromEnv() {
|
|
|
245
250
|
options.caps = commaSeparatedList(process.env.PLAYWRIGHT_MCP_CAPS);
|
|
246
251
|
options.cdpEndpoint = envToString(process.env.PLAYWRIGHT_MCP_CDP_ENDPOINT);
|
|
247
252
|
options.cdpHeader = headerParser(process.env.PLAYWRIGHT_MCP_CDP_HEADERS, {});
|
|
253
|
+
options.cdpTimeout = numberParser(process.env.PLAYWRIGHT_MCP_CDP_TIMEOUT);
|
|
248
254
|
options.config = envToString(process.env.PLAYWRIGHT_MCP_CONFIG);
|
|
249
255
|
if (process.env.PLAYWRIGHT_MCP_CONSOLE_LEVEL)
|
|
250
256
|
options.consoleLevel = enumParser("--console-level", ["error", "warning", "info", "debug"], process.env.PLAYWRIGHT_MCP_CONSOLE_LEVEL);
|
|
@@ -281,6 +287,22 @@ function configFromEnv() {
|
|
|
281
287
|
options.viewportSize = resolutionParser("--viewport-size", process.env.PLAYWRIGHT_MCP_VIEWPORT_SIZE);
|
|
282
288
|
return configFromCLIOptions(options);
|
|
283
289
|
}
|
|
290
|
+
async function configForDaemonSession(cliOptions) {
|
|
291
|
+
if (!cliOptions.daemonSession)
|
|
292
|
+
return {};
|
|
293
|
+
const sessionConfig = await import_fs.default.promises.readFile(cliOptions.daemonSession, "utf-8").then((data) => JSON.parse(data));
|
|
294
|
+
const config = configFromCLIOptions({
|
|
295
|
+
config: sessionConfig.cli.config,
|
|
296
|
+
browser: sessionConfig.cli.browser,
|
|
297
|
+
isolated: sessionConfig.cli.persistent === true ? false : void 0,
|
|
298
|
+
headless: sessionConfig.cli.headed ? false : void 0,
|
|
299
|
+
extension: sessionConfig.cli.extension,
|
|
300
|
+
userDataDir: sessionConfig.cli.profile,
|
|
301
|
+
outputMode: "file",
|
|
302
|
+
snapshotMode: "full"
|
|
303
|
+
});
|
|
304
|
+
return { ...config, sessionConfig };
|
|
305
|
+
}
|
|
284
306
|
async function loadConfig(configFile) {
|
|
285
307
|
if (!configFile)
|
|
286
308
|
return {};
|
|
@@ -290,31 +312,38 @@ async function loadConfig(configFile) {
|
|
|
290
312
|
throw new Error(`Failed to load config file: ${configFile}, ${error}`);
|
|
291
313
|
}
|
|
292
314
|
}
|
|
293
|
-
function
|
|
294
|
-
return import_path.default.
|
|
315
|
+
function workspaceDir(clientInfo) {
|
|
316
|
+
return import_path.default.resolve((0, import_server.firstRootPath)(clientInfo) ?? process.cwd());
|
|
317
|
+
}
|
|
318
|
+
async function workspaceFile(config, clientInfo, fileName, perCallWorkspaceDir) {
|
|
319
|
+
const workspace = perCallWorkspaceDir ?? workspaceDir(clientInfo);
|
|
320
|
+
const resolvedName = import_path.default.resolve(workspace, fileName);
|
|
321
|
+
await checkFile(config, clientInfo, resolvedName, { origin: "code" });
|
|
322
|
+
return resolvedName;
|
|
295
323
|
}
|
|
296
324
|
function outputDir(config, clientInfo) {
|
|
325
|
+
if (config.outputDir)
|
|
326
|
+
return import_path.default.resolve(config.outputDir);
|
|
297
327
|
const rootPath = (0, import_server.firstRootPath)(clientInfo);
|
|
298
|
-
|
|
328
|
+
if (rootPath)
|
|
329
|
+
return import_path.default.resolve(rootPath, config.skillMode ? ".playwright-cli" : ".playwright-mcp");
|
|
330
|
+
const tmpDir = process.env.PW_TMPDIR_FOR_TEST ?? import_os.default.tmpdir();
|
|
331
|
+
return import_path.default.resolve(tmpDir, "playwright-mcp-output", String(clientInfo.timestamp));
|
|
299
332
|
}
|
|
300
333
|
async function outputFile(config, clientInfo, fileName, options) {
|
|
301
|
-
const
|
|
302
|
-
await
|
|
303
|
-
|
|
304
|
-
|
|
334
|
+
const resolvedFile = import_path.default.resolve(outputDir(config, clientInfo), fileName);
|
|
335
|
+
await checkFile(config, clientInfo, resolvedFile, options);
|
|
336
|
+
await import_fs.default.promises.mkdir(import_path.default.dirname(resolvedFile), { recursive: true });
|
|
337
|
+
(0, import_utilsBundle.debug)("pw:mcp:file")(resolvedFile);
|
|
338
|
+
return resolvedFile;
|
|
305
339
|
}
|
|
306
|
-
async function
|
|
307
|
-
const dir = outputDir(config, clientInfo);
|
|
340
|
+
async function checkFile(config, clientInfo, resolvedFilename, options) {
|
|
308
341
|
if (options.origin === "code")
|
|
309
|
-
return
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
throw new Error(`Resolved file path ${resolvedFile} is outside of the output directory ${dir}. Use relative file names to stay within the output directory.`);
|
|
315
|
-
return resolvedFile;
|
|
316
|
-
}
|
|
317
|
-
return import_path.default.join(dir, sanitizeForFilePath(fileName));
|
|
342
|
+
return;
|
|
343
|
+
const output = outputDir(config, clientInfo);
|
|
344
|
+
const workspace = workspaceDir(clientInfo);
|
|
345
|
+
if (!resolvedFilename.startsWith(output) && !resolvedFilename.startsWith(workspace))
|
|
346
|
+
throw new Error(`Resolved file path ${resolvedFilename} is outside of the output directory ${output} and workspace directory ${workspace}. Use relative file names to stay within the output directory.`);
|
|
318
347
|
}
|
|
319
348
|
function pickDefined(obj) {
|
|
320
349
|
return Object.fromEntries(
|
|
@@ -425,13 +454,6 @@ function envToBoolean(value) {
|
|
|
425
454
|
function envToString(value) {
|
|
426
455
|
return value ? value.trim() : void 0;
|
|
427
456
|
}
|
|
428
|
-
function sanitizeForFilePath(s) {
|
|
429
|
-
const sanitize = (s2) => s2.replace(/[\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, "-");
|
|
430
|
-
const separator = s.lastIndexOf(".");
|
|
431
|
-
if (separator === -1)
|
|
432
|
-
return sanitize(s);
|
|
433
|
-
return sanitize(s.substring(0, separator)) + "." + sanitize(s.substring(separator + 1));
|
|
434
|
-
}
|
|
435
457
|
// Annotate the CommonJS export names for ESM import in node:
|
|
436
458
|
0 && (module.exports = {
|
|
437
459
|
commaSeparatedList,
|
|
@@ -446,5 +468,7 @@ function sanitizeForFilePath(s) {
|
|
|
446
468
|
resolutionParser,
|
|
447
469
|
resolveCLIConfig,
|
|
448
470
|
resolveConfig,
|
|
449
|
-
semicolonSeparatedList
|
|
471
|
+
semicolonSeparatedList,
|
|
472
|
+
workspaceDir,
|
|
473
|
+
workspaceFile
|
|
450
474
|
});
|
|
@@ -31,11 +31,11 @@ __export(context_exports, {
|
|
|
31
31
|
Context: () => Context
|
|
32
32
|
});
|
|
33
33
|
module.exports = __toCommonJS(context_exports);
|
|
34
|
+
var import_os = __toESM(require("os"));
|
|
34
35
|
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
|
35
36
|
var import_utils = require("playwright-core/lib/utils");
|
|
36
37
|
var import_playwright_core = require("playwright-core");
|
|
37
38
|
var import_url = require("url");
|
|
38
|
-
var import_os = __toESM(require("os"));
|
|
39
39
|
var import_log = require("../log");
|
|
40
40
|
var import_tab = require("./tab");
|
|
41
41
|
var import_config = require("./config");
|
|
@@ -43,6 +43,7 @@ const testDebug = (0, import_utilsBundle.debug)("pw:mcp:test");
|
|
|
43
43
|
class Context {
|
|
44
44
|
constructor(options) {
|
|
45
45
|
this._tabs = [];
|
|
46
|
+
this._routes = [];
|
|
46
47
|
this._abortController = new AbortController();
|
|
47
48
|
this.config = options.config;
|
|
48
49
|
this.sessionLog = options.sessionLog;
|
|
@@ -97,8 +98,39 @@ class Context {
|
|
|
97
98
|
await tab.page.close();
|
|
98
99
|
return url;
|
|
99
100
|
}
|
|
100
|
-
async
|
|
101
|
-
return (0, import_config.
|
|
101
|
+
async workspaceFile(fileName, perCallWorkspaceDir) {
|
|
102
|
+
return await (0, import_config.workspaceFile)(this.config, this._clientInfo, fileName, perCallWorkspaceDir);
|
|
103
|
+
}
|
|
104
|
+
async outputFile(template, options) {
|
|
105
|
+
const baseName = template.suggestedFilename || `${template.prefix}-${(template.date ?? /* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}${template.ext ? "." + template.ext : ""}`;
|
|
106
|
+
return await (0, import_config.outputFile)(this.config, this._clientInfo, baseName, options);
|
|
107
|
+
}
|
|
108
|
+
async startVideoRecording(params) {
|
|
109
|
+
if (this._video)
|
|
110
|
+
throw new Error("Video recording has already been started.");
|
|
111
|
+
const listener = (page) => {
|
|
112
|
+
this._video?.allVideos.add(page.video());
|
|
113
|
+
page.video().start(params).catch(() => {
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
this._video = { allVideos: /* @__PURE__ */ new Set(), listener };
|
|
117
|
+
const browserContext = await this.ensureBrowserContext();
|
|
118
|
+
browserContext.pages().forEach(listener);
|
|
119
|
+
browserContext.on("page", listener);
|
|
120
|
+
}
|
|
121
|
+
async stopVideoRecording() {
|
|
122
|
+
if (!this._video)
|
|
123
|
+
throw new Error("Video recording has not been started.");
|
|
124
|
+
const video = this._video;
|
|
125
|
+
if (this._browserContextPromise) {
|
|
126
|
+
const { browserContext } = await this._browserContextPromise;
|
|
127
|
+
browserContext.off("page", video.listener);
|
|
128
|
+
for (const page of browserContext.pages())
|
|
129
|
+
await page.video().stop().catch(() => {
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
this._video = void 0;
|
|
133
|
+
return video.allVideos;
|
|
102
134
|
}
|
|
103
135
|
_onPageCreated(page) {
|
|
104
136
|
const tab = new import_tab.Tab(this, page, (tab2) => this._onPageClosed(tab2));
|
|
@@ -122,6 +154,33 @@ class Context {
|
|
|
122
154
|
await this._closeBrowserContextPromise;
|
|
123
155
|
this._closeBrowserContextPromise = void 0;
|
|
124
156
|
}
|
|
157
|
+
routes() {
|
|
158
|
+
return this._routes;
|
|
159
|
+
}
|
|
160
|
+
async addRoute(entry) {
|
|
161
|
+
const { browserContext } = await this._ensureBrowserContext();
|
|
162
|
+
await browserContext.route(entry.pattern, entry.handler);
|
|
163
|
+
this._routes.push(entry);
|
|
164
|
+
}
|
|
165
|
+
async removeRoute(pattern) {
|
|
166
|
+
if (!this._browserContextPromise)
|
|
167
|
+
return 0;
|
|
168
|
+
const { browserContext } = await this._browserContextPromise;
|
|
169
|
+
let removed = 0;
|
|
170
|
+
if (pattern) {
|
|
171
|
+
const toRemove = this._routes.filter((r) => r.pattern === pattern);
|
|
172
|
+
for (const route of toRemove)
|
|
173
|
+
await browserContext.unroute(route.pattern, route.handler);
|
|
174
|
+
this._routes = this._routes.filter((r) => r.pattern !== pattern);
|
|
175
|
+
removed = toRemove.length;
|
|
176
|
+
} else {
|
|
177
|
+
for (const route of this._routes)
|
|
178
|
+
await browserContext.unroute(route.pattern, route.handler);
|
|
179
|
+
removed = this._routes.length;
|
|
180
|
+
this._routes = [];
|
|
181
|
+
}
|
|
182
|
+
return removed;
|
|
183
|
+
}
|
|
125
184
|
isRunningTool() {
|
|
126
185
|
return this._runningToolName !== void 0;
|
|
127
186
|
}
|
|
@@ -186,7 +245,6 @@ class Context {
|
|
|
186
245
|
for (const page of browserContext.pages())
|
|
187
246
|
this._onPageCreated(page);
|
|
188
247
|
browserContext.on("page", (page) => this._onPageCreated(page));
|
|
189
|
-
browserContext.on("close", () => this.onBrowserContextClosed?.());
|
|
190
248
|
if (this.config.saveTrace) {
|
|
191
249
|
await browserContext.tracing.start({
|
|
192
250
|
name: "trace-" + Date.now(),
|
|
@@ -229,6 +287,9 @@ function allRootPaths(clientInfo) {
|
|
|
229
287
|
return paths;
|
|
230
288
|
}
|
|
231
289
|
function originOrHostGlob(originOrHost) {
|
|
290
|
+
const wildcardPortMatch = originOrHost.match(/^(https?:\/\/[^/:]+):\*$/);
|
|
291
|
+
if (wildcardPortMatch)
|
|
292
|
+
return `${wildcardPortMatch[1]}:*/**`;
|
|
232
293
|
try {
|
|
233
294
|
const url = new URL(originOrHost);
|
|
234
295
|
if (url.origin !== "null")
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
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
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var logFile_exports = {};
|
|
30
|
+
__export(logFile_exports, {
|
|
31
|
+
LogFile: () => LogFile
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(logFile_exports);
|
|
34
|
+
var import_fs = __toESM(require("fs"));
|
|
35
|
+
var import_path = __toESM(require("path"));
|
|
36
|
+
var import_log = require("../log");
|
|
37
|
+
class LogFile {
|
|
38
|
+
constructor(context, startTime, filePrefix, title) {
|
|
39
|
+
this._stopped = false;
|
|
40
|
+
this._line = 0;
|
|
41
|
+
this._entries = 0;
|
|
42
|
+
this._lastLine = 0;
|
|
43
|
+
this._lastEntries = 0;
|
|
44
|
+
this._writeChain = Promise.resolve();
|
|
45
|
+
this._context = context;
|
|
46
|
+
this._startTime = startTime;
|
|
47
|
+
this._filePrefix = filePrefix;
|
|
48
|
+
this._title = title;
|
|
49
|
+
}
|
|
50
|
+
appendLine(wallTime, text) {
|
|
51
|
+
this._writeChain = this._writeChain.then(() => this._write(wallTime, text)).catch(import_log.logUnhandledError);
|
|
52
|
+
}
|
|
53
|
+
stop() {
|
|
54
|
+
this._stopped = true;
|
|
55
|
+
}
|
|
56
|
+
async take(relativeTo) {
|
|
57
|
+
const logChunk = await this._take();
|
|
58
|
+
if (!logChunk)
|
|
59
|
+
return void 0;
|
|
60
|
+
const logFilePath = relativeTo ? import_path.default.relative(relativeTo, logChunk.file) : logChunk.file;
|
|
61
|
+
const lineRange = logChunk.fromLine === logChunk.toLine ? `#L${logChunk.fromLine}` : `#L${logChunk.fromLine}-L${logChunk.toLine}`;
|
|
62
|
+
return `${logFilePath}${lineRange}`;
|
|
63
|
+
}
|
|
64
|
+
async _take() {
|
|
65
|
+
await this._writeChain;
|
|
66
|
+
if (!this._file || this._entries === this._lastEntries)
|
|
67
|
+
return void 0;
|
|
68
|
+
const chunk = {
|
|
69
|
+
type: this._title.toLowerCase(),
|
|
70
|
+
file: this._file,
|
|
71
|
+
fromLine: this._lastLine + 1,
|
|
72
|
+
toLine: this._line,
|
|
73
|
+
entryCount: this._entries - this._lastEntries
|
|
74
|
+
};
|
|
75
|
+
this._lastLine = this._line;
|
|
76
|
+
this._lastEntries = this._entries;
|
|
77
|
+
return chunk;
|
|
78
|
+
}
|
|
79
|
+
async _write(wallTime, text) {
|
|
80
|
+
if (this._stopped)
|
|
81
|
+
return;
|
|
82
|
+
this._file ??= await this._context.outputFile({ prefix: this._filePrefix, ext: "log", date: new Date(this._startTime) }, { origin: "code" });
|
|
83
|
+
const relativeTime = Math.round(wallTime - this._startTime);
|
|
84
|
+
const renderedText = await text();
|
|
85
|
+
const logLine = `[${String(relativeTime).padStart(8, " ")}ms] ${renderedText}
|
|
86
|
+
`;
|
|
87
|
+
await import_fs.default.promises.appendFile(this._file, logLine);
|
|
88
|
+
const lineCount = logLine.split("\n").length - 1;
|
|
89
|
+
this._line += lineCount;
|
|
90
|
+
this._entries++;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
94
|
+
0 && (module.exports = {
|
|
95
|
+
LogFile
|
|
96
|
+
});
|