ScuttleBot

Blame History Raw 422 lines
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 config_exports = {};
30
__export(config_exports, {
31
commaSeparatedList: () => commaSeparatedList,
32
configFromCLIOptions: () => configFromCLIOptions,
33
defaultConfig: () => defaultConfig,
34
dotenvFileLoader: () => dotenvFileLoader,
35
enumParser: () => enumParser,
36
headerParser: () => headerParser,
37
numberParser: () => numberParser,
38
outputDir: () => outputDir,
39
outputFile: () => outputFile,
40
resolutionParser: () => resolutionParser,
41
resolveCLIConfig: () => resolveCLIConfig,
42
resolveConfig: () => resolveConfig,
43
semicolonSeparatedList: () => semicolonSeparatedList
44
});
45
module.exports = __toCommonJS(config_exports);
46
var import_fs = __toESM(require("fs"));
47
var import_os = __toESM(require("os"));
48
var import_path = __toESM(require("path"));
49
var import_playwright_core = require("playwright-core");
50
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
51
var import_util = require("../../util");
52
var import_server = require("../sdk/server");
53
const defaultConfig = {
54
browser: {
55
browserName: "chromium",
56
launchOptions: {
57
channel: "chrome",
58
headless: import_os.default.platform() === "linux" && !process.env.DISPLAY,
59
chromiumSandbox: true
60
},
61
contextOptions: {
62
viewport: null
63
}
64
},
65
console: {
66
level: "info"
67
},
68
network: {
69
allowedOrigins: void 0,
70
blockedOrigins: void 0
71
},
72
server: {},
73
saveTrace: false,
74
snapshot: {
75
mode: "incremental",
76
output: "stdout"
77
},
78
timeouts: {
79
action: 5e3,
80
navigation: 6e4
81
}
82
};
83
async function resolveConfig(config) {
84
return mergeConfig(defaultConfig, config);
85
}
86
async function resolveCLIConfig(cliOptions) {
87
const configInFile = await loadConfig(cliOptions.config);
88
const envOverrides = configFromEnv();
89
const cliOverrides = configFromCLIOptions(cliOptions);
90
let result = defaultConfig;
91
result = mergeConfig(result, configInFile);
92
result = mergeConfig(result, envOverrides);
93
result = mergeConfig(result, cliOverrides);
94
await validateConfig(result);
95
return result;
96
}
97
async function validateConfig(config) {
98
if (config.browser.initScript) {
99
for (const script of config.browser.initScript) {
100
if (!await (0, import_util.fileExistsAsync)(script))
101
throw new Error(`Init script file does not exist: ${script}`);
102
}
103
}
104
if (config.browser.initPage) {
105
for (const page of config.browser.initPage) {
106
if (!await (0, import_util.fileExistsAsync)(page))
107
throw new Error(`Init page file does not exist: ${page}`);
108
}
109
}
110
if (config.sharedBrowserContext && config.saveVideo)
111
throw new Error("saveVideo is not supported when sharedBrowserContext is true");
112
}
113
function configFromCLIOptions(cliOptions) {
114
let browserName;
115
let channel;
116
switch (cliOptions.browser) {
117
case "chrome":
118
case "chrome-beta":
119
case "chrome-canary":
120
case "chrome-dev":
121
case "chromium":
122
case "msedge":
123
case "msedge-beta":
124
case "msedge-canary":
125
case "msedge-dev":
126
browserName = "chromium";
127
channel = cliOptions.browser;
128
break;
129
case "firefox":
130
browserName = "firefox";
131
break;
132
case "webkit":
133
browserName = "webkit";
134
break;
135
}
136
const launchOptions = {
137
channel,
138
executablePath: cliOptions.executablePath,
139
headless: cliOptions.headless
140
};
141
if (cliOptions.sandbox === false)
142
launchOptions.chromiumSandbox = false;
143
if (cliOptions.proxyServer) {
144
launchOptions.proxy = {
145
server: cliOptions.proxyServer
146
};
147
if (cliOptions.proxyBypass)
148
launchOptions.proxy.bypass = cliOptions.proxyBypass;
149
}
150
if (cliOptions.device && cliOptions.cdpEndpoint)
151
throw new Error("Device emulation is not supported with cdpEndpoint.");
152
const contextOptions = cliOptions.device ? import_playwright_core.devices[cliOptions.device] : {};
153
if (cliOptions.storageState)
154
contextOptions.storageState = cliOptions.storageState;
155
if (cliOptions.userAgent)
156
contextOptions.userAgent = cliOptions.userAgent;
157
if (cliOptions.viewportSize)
158
contextOptions.viewport = cliOptions.viewportSize;
159
if (cliOptions.ignoreHttpsErrors)
160
contextOptions.ignoreHTTPSErrors = true;
161
if (cliOptions.blockServiceWorkers)
162
contextOptions.serviceWorkers = "block";
163
if (cliOptions.grantPermissions)
164
contextOptions.permissions = cliOptions.grantPermissions;
165
const result = {
166
browser: {
167
browserName,
168
isolated: cliOptions.isolated,
169
userDataDir: cliOptions.userDataDir,
170
launchOptions,
171
contextOptions,
172
cdpEndpoint: cliOptions.cdpEndpoint,
173
cdpHeaders: cliOptions.cdpHeader,
174
initPage: cliOptions.initPage,
175
initScript: cliOptions.initScript
176
},
177
server: {
178
port: cliOptions.port,
179
host: cliOptions.host,
180
allowedHosts: cliOptions.allowedHosts
181
},
182
capabilities: cliOptions.caps,
183
console: {
184
level: cliOptions.consoleLevel
185
},
186
network: {
187
allowedOrigins: cliOptions.allowedOrigins,
188
blockedOrigins: cliOptions.blockedOrigins
189
},
190
allowUnrestrictedFileAccess: cliOptions.allowUnrestrictedFileAccess,
191
codegen: cliOptions.codegen,
192
saveSession: cliOptions.saveSession,
193
saveTrace: cliOptions.saveTrace,
194
saveVideo: cliOptions.saveVideo,
195
secrets: cliOptions.secrets,
196
sharedBrowserContext: cliOptions.sharedBrowserContext,
197
snapshot: cliOptions.snapshotMode ? { mode: cliOptions.snapshotMode } : void 0,
198
outputMode: cliOptions.outputMode,
199
outputDir: cliOptions.outputDir,
200
imageResponses: cliOptions.imageResponses,
201
testIdAttribute: cliOptions.testIdAttribute,
202
timeouts: {
203
action: cliOptions.timeoutAction,
204
navigation: cliOptions.timeoutNavigation
205
}
206
};
207
return result;
208
}
209
function configFromEnv() {
210
const options = {};
211
options.allowedHosts = commaSeparatedList(process.env.PLAYWRIGHT_MCP_ALLOWED_HOSTNAMES);
212
options.allowedOrigins = semicolonSeparatedList(process.env.PLAYWRIGHT_MCP_ALLOWED_ORIGINS);
213
options.allowUnrestrictedFileAccess = envToBoolean(process.env.PLAYWRIGHT_MCP_ALLOW_UNRESTRICTED_FILE_ACCESS);
214
options.blockedOrigins = semicolonSeparatedList(process.env.PLAYWRIGHT_MCP_BLOCKED_ORIGINS);
215
options.blockServiceWorkers = envToBoolean(process.env.PLAYWRIGHT_MCP_BLOCK_SERVICE_WORKERS);
216
options.browser = envToString(process.env.PLAYWRIGHT_MCP_BROWSER);
217
options.caps = commaSeparatedList(process.env.PLAYWRIGHT_MCP_CAPS);
218
options.cdpEndpoint = envToString(process.env.PLAYWRIGHT_MCP_CDP_ENDPOINT);
219
options.cdpHeader = headerParser(process.env.PLAYWRIGHT_MCP_CDP_HEADERS, {});
220
options.config = envToString(process.env.PLAYWRIGHT_MCP_CONFIG);
221
if (process.env.PLAYWRIGHT_MCP_CONSOLE_LEVEL)
222
options.consoleLevel = enumParser("--console-level", ["error", "warning", "info", "debug"], process.env.PLAYWRIGHT_MCP_CONSOLE_LEVEL);
223
options.device = envToString(process.env.PLAYWRIGHT_MCP_DEVICE);
224
options.executablePath = envToString(process.env.PLAYWRIGHT_MCP_EXECUTABLE_PATH);
225
options.grantPermissions = commaSeparatedList(process.env.PLAYWRIGHT_MCP_GRANT_PERMISSIONS);
226
options.headless = envToBoolean(process.env.PLAYWRIGHT_MCP_HEADLESS);
227
options.host = envToString(process.env.PLAYWRIGHT_MCP_HOST);
228
options.ignoreHttpsErrors = envToBoolean(process.env.PLAYWRIGHT_MCP_IGNORE_HTTPS_ERRORS);
229
const initPage = envToString(process.env.PLAYWRIGHT_MCP_INIT_PAGE);
230
if (initPage)
231
options.initPage = [initPage];
232
const initScript = envToString(process.env.PLAYWRIGHT_MCP_INIT_SCRIPT);
233
if (initScript)
234
options.initScript = [initScript];
235
options.isolated = envToBoolean(process.env.PLAYWRIGHT_MCP_ISOLATED);
236
if (process.env.PLAYWRIGHT_MCP_IMAGE_RESPONSES)
237
options.imageResponses = enumParser("--image-responses", ["allow", "omit"], process.env.PLAYWRIGHT_MCP_IMAGE_RESPONSES);
238
options.sandbox = envToBoolean(process.env.PLAYWRIGHT_MCP_SANDBOX);
239
options.outputDir = envToString(process.env.PLAYWRIGHT_MCP_OUTPUT_DIR);
240
options.port = numberParser(process.env.PLAYWRIGHT_MCP_PORT);
241
options.proxyBypass = envToString(process.env.PLAYWRIGHT_MCP_PROXY_BYPASS);
242
options.proxyServer = envToString(process.env.PLAYWRIGHT_MCP_PROXY_SERVER);
243
options.saveTrace = envToBoolean(process.env.PLAYWRIGHT_MCP_SAVE_TRACE);
244
options.saveVideo = resolutionParser("--save-video", process.env.PLAYWRIGHT_MCP_SAVE_VIDEO);
245
options.secrets = dotenvFileLoader(process.env.PLAYWRIGHT_MCP_SECRETS_FILE);
246
options.storageState = envToString(process.env.PLAYWRIGHT_MCP_STORAGE_STATE);
247
options.testIdAttribute = envToString(process.env.PLAYWRIGHT_MCP_TEST_ID_ATTRIBUTE);
248
options.timeoutAction = numberParser(process.env.PLAYWRIGHT_MCP_TIMEOUT_ACTION);
249
options.timeoutNavigation = numberParser(process.env.PLAYWRIGHT_MCP_TIMEOUT_NAVIGATION);
250
options.userAgent = envToString(process.env.PLAYWRIGHT_MCP_USER_AGENT);
251
options.userDataDir = envToString(process.env.PLAYWRIGHT_MCP_USER_DATA_DIR);
252
options.viewportSize = resolutionParser("--viewport-size", process.env.PLAYWRIGHT_MCP_VIEWPORT_SIZE);
253
return configFromCLIOptions(options);
254
}
255
async function loadConfig(configFile) {
256
if (!configFile)
257
return {};
258
try {
259
return JSON.parse(await import_fs.default.promises.readFile(configFile, "utf8"));
260
} catch (error) {
261
throw new Error(`Failed to load config file: ${configFile}, ${error}`);
262
}
263
}
264
function tmpDir() {
265
return import_path.default.join(process.env.PW_TMPDIR_FOR_TEST ?? import_os.default.tmpdir(), "playwright-mcp-output");
266
}
267
function outputDir(config, clientInfo) {
268
const rootPath = (0, import_server.firstRootPath)(clientInfo);
269
return config.outputDir ?? (rootPath ? import_path.default.join(rootPath, ".playwright-mcp") : void 0) ?? import_path.default.join(tmpDir(), String(clientInfo.timestamp));
270
}
271
async function outputFile(config, clientInfo, fileName, options) {
272
const file = await resolveFile(config, clientInfo, fileName, options);
273
await import_fs.default.promises.mkdir(import_path.default.dirname(file), { recursive: true });
274
(0, import_utilsBundle.debug)("pw:mcp:file")(options.title, file);
275
return file;
276
}
277
async function resolveFile(config, clientInfo, fileName, options) {
278
const dir = outputDir(config, clientInfo);
279
if (options.origin === "code")
280
return import_path.default.resolve(dir, fileName);
281
if (options.origin === "llm") {
282
fileName = fileName.split("\\").join("/");
283
const resolvedFile = import_path.default.resolve(dir, fileName);
284
if (!resolvedFile.startsWith(import_path.default.resolve(dir) + import_path.default.sep))
285
throw new Error(`Resolved file path ${resolvedFile} is outside of the output directory ${dir}. Use relative file names to stay within the output directory.`);
286
return resolvedFile;
287
}
288
return import_path.default.join(dir, sanitizeForFilePath(fileName));
289
}
290
function pickDefined(obj) {
291
return Object.fromEntries(
292
Object.entries(obj ?? {}).filter(([_, v]) => v !== void 0)
293
);
294
}
295
function mergeConfig(base, overrides) {
296
const browser = {
297
...pickDefined(base.browser),
298
...pickDefined(overrides.browser),
299
browserName: overrides.browser?.browserName ?? base.browser?.browserName ?? "chromium",
300
isolated: overrides.browser?.isolated ?? base.browser?.isolated ?? false,
301
launchOptions: {
302
...pickDefined(base.browser?.launchOptions),
303
...pickDefined(overrides.browser?.launchOptions),
304
...{ assistantMode: true }
305
},
306
contextOptions: {
307
...pickDefined(base.browser?.contextOptions),
308
...pickDefined(overrides.browser?.contextOptions)
309
}
310
};
311
if (browser.browserName !== "chromium" && browser.launchOptions)
312
delete browser.launchOptions.channel;
313
return {
314
...pickDefined(base),
315
...pickDefined(overrides),
316
browser,
317
console: {
318
...pickDefined(base.console),
319
...pickDefined(overrides.console)
320
},
321
network: {
322
...pickDefined(base.network),
323
...pickDefined(overrides.network)
324
},
325
server: {
326
...pickDefined(base.server),
327
...pickDefined(overrides.server)
328
},
329
snapshot: {
330
...pickDefined(base.snapshot),
331
...pickDefined(overrides.snapshot)
332
},
333
timeouts: {
334
...pickDefined(base.timeouts),
335
...pickDefined(overrides.timeouts)
336
}
337
};
338
}
339
function semicolonSeparatedList(value) {
340
if (!value)
341
return void 0;
342
return value.split(";").map((v) => v.trim());
343
}
344
function commaSeparatedList(value) {
345
if (!value)
346
return void 0;
347
return value.split(",").map((v) => v.trim());
348
}
349
function dotenvFileLoader(value) {
350
if (!value)
351
return void 0;
352
return import_utilsBundle.dotenv.parse(import_fs.default.readFileSync(value, "utf8"));
353
}
354
function numberParser(value) {
355
if (!value)
356
return void 0;
357
return +value;
358
}
359
function resolutionParser(name, value) {
360
if (!value)
361
return void 0;
362
if (value.includes("x")) {
363
const [width, height] = value.split("x").map((v) => +v);
364
if (isNaN(width) || isNaN(height) || width <= 0 || height <= 0)
365
throw new Error(`Invalid resolution format: use ${name}="800x600"`);
366
return { width, height };
367
}
368
if (value.includes(",")) {
369
const [width, height] = value.split(",").map((v) => +v);
370
if (isNaN(width) || isNaN(height) || width <= 0 || height <= 0)
371
throw new Error(`Invalid resolution format: use ${name}="800x600"`);
372
return { width, height };
373
}
374
throw new Error(`Invalid resolution format: use ${name}="800x600"`);
375
}
376
function headerParser(arg, previous) {
377
if (!arg)
378
return previous || {};
379
const result = previous || {};
380
const [name, value] = arg.split(":").map((v) => v.trim());
381
result[name] = value;
382
return result;
383
}
384
function enumParser(name, options, value) {
385
if (!options.includes(value))
386
throw new Error(`Invalid ${name}: ${value}. Valid values are: ${options.join(", ")}`);
387
return value;
388
}
389
function envToBoolean(value) {
390
if (value === "true" || value === "1")
391
return true;
392
if (value === "false" || value === "0")
393
return false;
394
return void 0;
395
}
396
function envToString(value) {
397
return value ? value.trim() : void 0;
398
}
399
function sanitizeForFilePath(s) {
400
const sanitize = (s2) => s2.replace(/[\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, "-");
401
const separator = s.lastIndexOf(".");
402
if (separator === -1)
403
return sanitize(s);
404
return sanitize(s.substring(0, separator)) + "." + sanitize(s.substring(separator + 1));
405
}
406
// Annotate the CommonJS export names for ESM import in node:
407
0 && (module.exports = {
408
commaSeparatedList,
409
configFromCLIOptions,
410
defaultConfig,
411
dotenvFileLoader,
412
enumParser,
413
headerParser,
414
numberParser,
415
outputDir,
416
outputFile,
417
resolutionParser,
418
resolveCLIConfig,
419
resolveConfig,
420
semicolonSeparatedList
421
});
422

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button