@cotestdev/mcp_playwright 0.0.47 → 0.0.49

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.
@@ -304,7 +304,7 @@ class SharedContextFactory {
304
304
  }
305
305
  }
306
306
  async function computeTracesDir(config, clientInfo) {
307
- if (!config.saveTrace && !config.capabilities?.includes("tracing"))
307
+ if (!config.saveTrace && !config.capabilities?.includes("devtools"))
308
308
  return;
309
309
  return await (0, import_config.outputFile)(config, clientInfo, `traces`, { origin: "code", title: "Collecting trace" });
310
310
  }
@@ -32,7 +32,6 @@ class BrowserServerBackend {
32
32
  this._config = config;
33
33
  this._browserContextFactory = factory;
34
34
  this._tools = options.allTools ? import_tools.browserTools : (0, import_tools.filteredTools)(config);
35
- this._isStructuredOutput = options.structuredOutput ?? false;
36
35
  }
37
36
  async initialize(clientInfo) {
38
37
  this._sessionLog = this._config.saveSession ? await import_sessionLog.SessionLog.create(this._config, clientInfo) : void 0;
@@ -42,6 +41,7 @@ class BrowserServerBackend {
42
41
  sessionLog: this._sessionLog,
43
42
  clientInfo
44
43
  });
44
+ this._context.onBrowserContextClosed = () => this.onBrowserContextClosed?.();
45
45
  }
46
46
  async listTools() {
47
47
  return this._tools.map((tool) => (0, import_tool.toMcpTool)(tool.schema));
@@ -56,6 +56,7 @@ Tool "${name}" not found` }],
56
56
  };
57
57
  }
58
58
  const parsedArguments = tool.schema.inputSchema.parse(rawArguments || {});
59
+ const cwd = rawArguments?._meta && typeof rawArguments?._meta === "object" && rawArguments._meta?.cwd;
59
60
  const context = this._context;
60
61
  const response = import_response.Response.create(context, name, parsedArguments);
61
62
  context.setRunningTool(name);
@@ -63,10 +64,7 @@ Tool "${name}" not found` }],
63
64
  try {
64
65
  await tool.handle(context, parsedArguments, response);
65
66
  const sections = await response.build();
66
- if (this._isStructuredOutput)
67
- responseObject = await (0, import_response.serializeStructuredResponse)(sections);
68
- else
69
- responseObject = await (0, import_response.serializeResponse)(context, sections, context.firstRootPath());
67
+ responseObject = await (0, import_response.serializeResponse)(context, sections, cwd ?? context.firstRootPath());
70
68
  this._sessionLog?.logResponse(name, parsedArguments, responseObject);
71
69
  } catch (error) {
72
70
  return {
@@ -81,7 +81,8 @@ const defaultConfig = {
81
81
  };
82
82
  const defaultDaemonConfig = (cliOptions) => mergeConfig(defaultConfig, {
83
83
  browser: {
84
- userDataDir: "<daemon-data-dir>",
84
+ userDataDir: cliOptions.extension ? void 0 : "<daemon-data-dir>",
85
+ // Use default user profile with extension.
85
86
  launchOptions: {
86
87
  headless: !cliOptions.daemonHeaded
87
88
  },
@@ -90,7 +91,6 @@ const defaultDaemonConfig = (cliOptions) => mergeConfig(defaultConfig, {
90
91
  }
91
92
  },
92
93
  outputMode: "file",
93
- codegen: "none",
94
94
  snapshot: {
95
95
  mode: "full"
96
96
  }
@@ -201,6 +201,7 @@ function configFromCLIOptions(cliOptions) {
201
201
  initPage: cliOptions.initPage,
202
202
  initScript: cliOptions.initScript
203
203
  },
204
+ extension: cliOptions.extension,
204
205
  server: {
205
206
  port: cliOptions.port,
206
207
  host: cliOptions.host,
@@ -249,6 +250,7 @@ function configFromEnv() {
249
250
  options.consoleLevel = enumParser("--console-level", ["error", "warning", "info", "debug"], process.env.PLAYWRIGHT_MCP_CONSOLE_LEVEL);
250
251
  options.device = envToString(process.env.PLAYWRIGHT_MCP_DEVICE);
251
252
  options.executablePath = envToString(process.env.PLAYWRIGHT_MCP_EXECUTABLE_PATH);
253
+ options.extension = envToBoolean(process.env.PLAYWRIGHT_MCP_EXTENSION);
252
254
  options.grantPermissions = commaSeparatedList(process.env.PLAYWRIGHT_MCP_GRANT_PERMISSIONS);
253
255
  options.headless = envToBoolean(process.env.PLAYWRIGHT_MCP_HEADLESS);
254
256
  options.host = envToString(process.env.PLAYWRIGHT_MCP_HOST);
@@ -186,6 +186,7 @@ class Context {
186
186
  for (const page of browserContext.pages())
187
187
  this._onPageCreated(page);
188
188
  browserContext.on("page", (page) => this._onPageCreated(page));
189
+ browserContext.on("close", () => this.onBrowserContextClosed?.());
189
190
  if (this.config.saveTrace) {
190
191
  await browserContext.tracing.start({
191
192
  name: "trace-" + Date.now(),
@@ -33,8 +33,7 @@ __export(response_exports, {
33
33
  renderTabMarkdown: () => renderTabMarkdown,
34
34
  renderTabsMarkdown: () => renderTabsMarkdown,
35
35
  requestDebug: () => requestDebug,
36
- serializeResponse: () => serializeResponse,
37
- serializeStructuredResponse: () => serializeStructuredResponse
36
+ serializeResponse: () => serializeResponse
38
37
  });
39
38
  module.exports = __toCommonJS(response_exports);
40
39
  var import_fs = __toESM(require("fs"));
@@ -63,7 +62,7 @@ class Response {
63
62
  addTextResult(text) {
64
63
  this._results.push({ title: "", text });
65
64
  }
66
- async addResult(title, data, file) {
65
+ addResult(title, data, file) {
67
66
  this._results.push({
68
67
  text: typeof data === "string" ? data : void 0,
69
68
  data: typeof data === "string" ? void 0 : data,
@@ -87,8 +86,8 @@ class Response {
87
86
  async build() {
88
87
  const rootPath = this._context.firstRootPath();
89
88
  const sections = [];
90
- const addSection = (title) => {
91
- const section = { title, content: [], isError: title === "Error" };
89
+ const addSection = (title, codeframe) => {
90
+ const section = { title, content: [], isError: title === "Error", codeframe };
92
91
  sections.push(section);
93
92
  return section.content;
94
93
  };
@@ -101,7 +100,7 @@ class Response {
101
100
  content.push(...this._results);
102
101
  }
103
102
  if (this._context.config.codegen !== "none" && this._code.length) {
104
- const content = addSection("Ran Playwright code");
103
+ const content = addSection("Ran Playwright code", "js");
105
104
  for (const code of this._code)
106
105
  content.push({ text: code, title: "code" });
107
106
  }
@@ -120,7 +119,7 @@ class Response {
120
119
  content.push({ text: (0, import_tab.renderModalStates)(this._context.config, tabSnapshot.modalStates).join("\n"), title: "Modal state" });
121
120
  }
122
121
  if (tabSnapshot && this._includeSnapshot !== "none") {
123
- const content = addSection("Snapshot");
122
+ const content = addSection("Snapshot", "yaml");
124
123
  const snapshot = this._includeSnapshot === "full" ? tabSnapshot.ariaSnapshot : tabSnapshot.ariaSnapshotDiff ?? tabSnapshot.ariaSnapshot;
125
124
  content.push({ text: snapshot, title: "snapshot", file: { prefix: "page", ext: "yml", suggestedFilename: this._includeSnapshotFileName } });
126
125
  }
@@ -177,7 +176,7 @@ function parseSections(text) {
177
176
  }
178
177
  return sections;
179
178
  }
180
- async function serializeResponse(context, sections, rootPath) {
179
+ async function serializeResponse(context, sections, relativeTo) {
181
180
  const redactText = (text2) => {
182
181
  for (const [secretName, secretValue] of Object.entries(context.config.secrets ?? {}))
183
182
  text2 = text2.replaceAll(secretValue, `<secret>${secretName}</secret>`);
@@ -186,29 +185,29 @@ async function serializeResponse(context, sections, rootPath) {
186
185
  const text = [];
187
186
  for (const section of sections) {
188
187
  text.push(`### ${section.title}`);
188
+ const codeframe = [];
189
189
  for (const result of section.content) {
190
- if (!result.file) {
191
- if (result.text !== void 0)
192
- text.push(result.text);
193
- continue;
194
- }
195
- if (result.file.suggestedFilename || context.config.outputMode === "file" || result.data) {
190
+ if (result.file && (result.file.suggestedFilename || context.config.outputMode === "file" || result.data)) {
196
191
  const generatedFileName = await context.outputFile((0, import_utils.dateAsFileName)(result.file.prefix, result.file.ext), { origin: "code", title: section.title });
197
192
  const fileName = result.file.suggestedFilename ? await context.outputFile(result.file.suggestedFilename, { origin: "llm", title: section.title }) : generatedFileName;
198
- text.push(`- [${result.title}](${rootPath ? import_path.default.relative(rootPath, fileName) : fileName})`);
193
+ text.push(`- [${result.title}](${relativeTo ? import_path.default.relative(relativeTo, fileName) : fileName})`);
199
194
  if (result.data)
200
195
  await import_fs.default.promises.writeFile(fileName, result.data, "utf-8");
201
196
  else
202
197
  await import_fs.default.promises.writeFile(fileName, result.text);
203
198
  } else {
204
- if (result.file.ext === "yml")
205
- text.push(`\`\`\`yaml
206
- ${result.text}
207
- \`\`\``);
208
- else
209
- text.push(result.text);
199
+ if (result.text !== void 0)
200
+ codeframe.push(result.text);
210
201
  }
211
202
  }
203
+ if (codeframe?.length) {
204
+ if (section.codeframe)
205
+ text.push(`\`\`\`${section.codeframe}
206
+ ${codeframe.join("\n")}
207
+ \`\`\``);
208
+ else
209
+ text.push(codeframe.join("\n"));
210
+ }
212
211
  }
213
212
  const content = [
214
213
  {
@@ -227,21 +226,6 @@ ${result.text}
227
226
  ...sections.some((section) => section.isError) ? { isError: true } : {}
228
227
  };
229
228
  }
230
- async function serializeStructuredResponse(sections) {
231
- for (const section of sections) {
232
- for (const result of section.content) {
233
- if (!result.data)
234
- continue;
235
- result.isBase64 = true;
236
- result.text = result.data.toString("base64");
237
- result.data = void 0;
238
- }
239
- }
240
- return {
241
- content: [{ type: "text", text: "", _meta: { sections } }],
242
- isError: sections.some((section) => section.isError)
243
- };
244
- }
245
229
  function parseResponse(response) {
246
230
  if (response.content?.[0].type !== "text")
247
231
  return void 0;
@@ -279,6 +263,5 @@ function parseResponse(response) {
279
263
  renderTabMarkdown,
280
264
  renderTabsMarkdown,
281
265
  requestDebug,
282
- serializeResponse,
283
- serializeStructuredResponse
266
+ serializeResponse
284
267
  });
@@ -38,7 +38,7 @@ const console = (0, import_tool.defineTabTool)({
38
38
  handle: async (tab, params, response) => {
39
39
  const messages = await tab.consoleMessages(params.level);
40
40
  const text = messages.map((message) => message.toString()).join("\n");
41
- await response.addResult("Console", text, { prefix: "console", ext: "log", suggestedFilename: params.filename });
41
+ response.addResult("Console", text, { prefix: "console", ext: "log", suggestedFilename: params.filename });
42
42
  }
43
43
  });
44
44
  const consoleClear = (0, import_tool.defineTabTool)({
@@ -43,7 +43,7 @@ const requests = (0, import_tool.defineTabTool)({
43
43
  if (rendered)
44
44
  text.push(rendered);
45
45
  }
46
- await response.addResult("Network", text.join("\n"), { prefix: "network", ext: "log", suggestedFilename: params.filename });
46
+ response.addResult("Network", text.join("\n"), { prefix: "network", ext: "log", suggestedFilename: params.filename });
47
47
  }
48
48
  });
49
49
  const networkClear = (0, import_tool.defineTabTool)({
@@ -40,7 +40,7 @@ const pdf = (0, import_tool.defineTabTool)({
40
40
  handle: async (tab, params, response) => {
41
41
  const data = await tab.page.pdf();
42
42
  const suggestedFilename = params.filename ?? (0, import_utils2.dateAsFileName)("page", "pdf");
43
- await response.addResult("Page as pdf", data, { prefix: "page", ext: "pdf", suggestedFilename });
43
+ response.addResult("Page as pdf", data, { prefix: "page", ext: "pdf", suggestedFilename });
44
44
  response.addCode(`await page.pdf(${(0, import_utils.formatObject)({ path: suggestedFilename })});`);
45
45
  }
46
46
  });
@@ -93,7 +93,9 @@ const runScript = (0, import_tool.defineTabTool)({
93
93
  const runner = import_ai_runner_fake.Runner.NewInstance(params.projectId, params.testId);
94
94
  await runner.init(tab.page, tab.page.context(), params.params);
95
95
  const result = await runner.runScript(params.testId, params.code);
96
- response.addCode(`runner.reuseTest('${params.testId}');`);
96
+ const code = `// Returns the out parameters of the reusable test
97
+ const result = await runner.reuseTest('${params.testId}');`;
98
+ response.addCode(code);
97
99
  response.addTextResult(`Out Parameters: ${JSON.stringify(result)}`);
98
100
  }
99
101
  });
@@ -65,7 +65,7 @@ const screenshot = (0, import_tool.defineTabTool)({
65
65
  else
66
66
  response.addCode(`await page.screenshot(${(0, import_utils2.formatObject)({ ...options, path: suggestedFilename })});`);
67
67
  const contentType = fileType === "png" ? "image/png" : "image/jpeg";
68
- await response.addResult(`Screenshot of ${screenshotTarget}`, data, { prefix: ref ? "element" : "page", ext: fileType, suggestedFilename, contentType });
68
+ response.addResult(`Screenshot of ${screenshotTarget}`, data, { prefix: ref ? "element" : "page", ext: fileType, suggestedFilename, contentType });
69
69
  }
70
70
  });
71
71
  function scaleImageToFitMessage(buffer, imageType) {
@@ -0,0 +1,68 @@
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 storage_exports = {};
20
+ __export(storage_exports, {
21
+ default: () => storage_default
22
+ });
23
+ module.exports = __toCommonJS(storage_exports);
24
+ var import_mcpBundle = require("../../../mcpBundle");
25
+ var import_tool = require("./tool");
26
+ var import_utils = require("./utils");
27
+ const storageState = (0, import_tool.defineTool)({
28
+ capability: "storage",
29
+ schema: {
30
+ name: "browser_storage_state",
31
+ title: "Save storage state",
32
+ description: "Save storage state (cookies, local storage) to a file for later reuse",
33
+ inputSchema: import_mcpBundle.z.object({
34
+ filename: import_mcpBundle.z.string().optional().describe("File name to save the storage state to. Defaults to `storage-state-{timestamp}.json` if not specified.")
35
+ }),
36
+ type: "readOnly"
37
+ },
38
+ handle: async (context, params, response) => {
39
+ const browserContext = await context.ensureBrowserContext();
40
+ const state = await browserContext.storageState();
41
+ const suggestedFilename = params.filename || (0, import_utils.dateAsFileName)("storage-state", "json");
42
+ const serializedState = JSON.stringify(state, null, 2);
43
+ response.addResult("Storage state", serializedState, { prefix: "storage-state", ext: "json", suggestedFilename });
44
+ response.addCode(`await page.context().storageState({ path: '${suggestedFilename}' });`);
45
+ }
46
+ });
47
+ const setStorageState = (0, import_tool.defineTool)({
48
+ capability: "storage",
49
+ schema: {
50
+ name: "browser_set_storage_state",
51
+ title: "Restore storage state",
52
+ description: "Restore storage state (cookies, local storage) from a file. This clears existing cookies and local storage before restoring.",
53
+ inputSchema: import_mcpBundle.z.object({
54
+ filename: import_mcpBundle.z.string().describe("Path to the storage state file to restore from")
55
+ }),
56
+ type: "action"
57
+ },
58
+ handle: async (context, params, response) => {
59
+ const browserContext = await context.ensureBrowserContext();
60
+ await browserContext.setStorageState(params.filename);
61
+ response.addTextResult(`Storage state restored from ${params.filename}`);
62
+ response.addCode(`await page.context().setStorageState('${params.filename}');`);
63
+ }
64
+ });
65
+ var storage_default = [
66
+ storageState,
67
+ setStorageState
68
+ ];
@@ -24,7 +24,7 @@ module.exports = __toCommonJS(tracing_exports);
24
24
  var import_mcpBundle = require("../../../mcpBundle");
25
25
  var import_tool = require("./tool");
26
26
  const tracingStart = (0, import_tool.defineTool)({
27
- capability: "tracing",
27
+ capability: "devtools",
28
28
  schema: {
29
29
  name: "browser_start_tracing",
30
30
  title: "Start tracing",
@@ -51,7 +51,7 @@ ${traceLegend}`);
51
51
  }
52
52
  });
53
53
  const tracingStop = (0, import_tool.defineTool)({
54
- capability: "tracing",
54
+ capability: "devtools",
55
55
  schema: {
56
56
  name: "browser_stop_tracing",
57
57
  title: "Stop tracing",
@@ -0,0 +1,71 @@
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 video_exports = {};
20
+ __export(video_exports, {
21
+ default: () => video_default
22
+ });
23
+ module.exports = __toCommonJS(video_exports);
24
+ var import_mcpBundle = require("../../../mcpBundle");
25
+ var import_tool = require("./tool");
26
+ var import_utils = require("./utils");
27
+ const startVideo = (0, import_tool.defineTabTool)({
28
+ capability: "devtools",
29
+ schema: {
30
+ name: "browser_start_video",
31
+ title: "Start video",
32
+ description: "Start video recording",
33
+ inputSchema: import_mcpBundle.z.object({
34
+ size: import_mcpBundle.z.object({
35
+ width: import_mcpBundle.z.number().describe("Video width"),
36
+ height: import_mcpBundle.z.number().describe("Video height")
37
+ }).optional().describe("Video size")
38
+ }),
39
+ type: "readOnly"
40
+ },
41
+ handle: async (tab, params, response) => {
42
+ await tab.page.video().start({ size: params.size });
43
+ response.addTextResult("Video recording started.");
44
+ }
45
+ });
46
+ const stopVideo = (0, import_tool.defineTabTool)({
47
+ capability: "devtools",
48
+ schema: {
49
+ name: "browser_stop_video",
50
+ title: "Stop video",
51
+ description: "Stop video recording",
52
+ inputSchema: import_mcpBundle.z.object({
53
+ filename: import_mcpBundle.z.string().optional().describe("Filename to save the video")
54
+ }),
55
+ type: "readOnly"
56
+ },
57
+ handle: async (tab, params, response) => {
58
+ let videoPath;
59
+ if (params.filename) {
60
+ const suggestedFilename = params.filename ?? (0, import_utils.dateAsFileName)("video", "webm");
61
+ videoPath = await tab.context.outputFile(suggestedFilename, { origin: "llm", title: "Saving video" });
62
+ }
63
+ await tab.page.video().stop({ path: videoPath });
64
+ const tmpPath = await tab.page.video().path();
65
+ response.addTextResult(`Video recording stopped: ${videoPath ?? tmpPath}`);
66
+ }
67
+ });
68
+ var video_default = [
69
+ startVideo,
70
+ stopVideo
71
+ ];
@@ -47,10 +47,12 @@ var import_pdf = __toESM(require("./tools/pdf"));
47
47
  var import_runCode = __toESM(require("./tools/runCode"));
48
48
  var import_snapshot = __toESM(require("./tools/snapshot"));
49
49
  var import_screenshot = __toESM(require("./tools/screenshot"));
50
+ var import_storage = __toESM(require("./tools/storage"));
50
51
  var import_tabs = __toESM(require("./tools/tabs"));
51
52
  var import_tracing = __toESM(require("./tools/tracing"));
52
- var import_wait = __toESM(require("./tools/wait"));
53
53
  var import_verify = __toESM(require("./tools/verify"));
54
+ var import_video = __toESM(require("./tools/video"));
55
+ var import_wait = __toESM(require("./tools/wait"));
54
56
  const browserTools = [
55
57
  ...import_common.default,
56
58
  ...import_console.default,
@@ -67,10 +69,12 @@ const browserTools = [
67
69
  ...import_runCode.default,
68
70
  ...import_screenshot.default,
69
71
  ...import_snapshot.default,
72
+ ...import_storage.default,
70
73
  ...import_tabs.default,
71
74
  ...import_tracing.default,
72
- ...import_wait.default,
73
- ...import_verify.default
75
+ ...import_verify.default,
76
+ ...import_video.default,
77
+ ...import_wait.default
74
78
  ];
75
79
  function filteredTools(config) {
76
80
  return browserTools.filter((tool) => tool.capability.startsWith("core") || config.capabilities?.includes(tool.capability)).filter((tool) => !tool.skillOnly);
@@ -32,6 +32,7 @@ __export(cdpRelay_exports, {
32
32
  });
33
33
  module.exports = __toCommonJS(cdpRelay_exports);
34
34
  var import_child_process = require("child_process");
35
+ var import_os = __toESM(require("os"));
35
36
  var import_utilsBundle = require("playwright-core/lib/utilsBundle");
36
37
  var import_registry = require("playwright-core/lib/server/registry/index");
37
38
  var import_utils = require("playwright-core/lib/utils");
@@ -104,6 +105,8 @@ class CDPRelayServer {
104
105
  const args = [];
105
106
  if (this._userDataDir)
106
107
  args.push(`--user-data-dir=${this._userDataDir}`);
108
+ if (import_os.default.platform() === "linux" && this._browserChannel === "chromium")
109
+ args.push("--no-sandbox");
107
110
  args.push(href);
108
111
  (0, import_child_process.spawn)(executablePath, args, {
109
112
  windowsHide: true,
@@ -42,13 +42,15 @@ var import_browserContextFactory = require("./browser/browserContextFactory");
42
42
  var import_browserServerBackend = require("./browser/browserServerBackend");
43
43
  var import_extensionContextFactory = require("./extension/extensionContextFactory");
44
44
  function decorateCommand(command, version) {
45
- command.option("--allowed-hosts <hosts...>", "comma-separated list of hosts this server is allowed to serve from. Defaults to the host the server is bound to. Pass '*' to disable the host check.", import_config.commaSeparatedList).option("--allowed-origins <origins>", "semicolon-separated list of TRUSTED origins to allow the browser to request. Default is to allow all.\nImportant: *does not* serve as a security boundary and *does not* affect redirects. ", import_config.semicolonSeparatedList).option("--allow-unrestricted-file-access", "allow access to files outside of the workspace roots. Also allows unrestricted access to file:// URLs. By default access to file system is restricted to workspace root directories (or cwd if no roots are configured) only, and navigation to file:// URLs is blocked.").option("--blocked-origins <origins>", "semicolon-separated list of origins to block the browser from requesting. Blocklist is evaluated before allowlist. If used without the allowlist, requests not matching the blocklist are still allowed.\nImportant: *does not* serve as a security boundary and *does not* affect redirects.", import_config.semicolonSeparatedList).option("--block-service-workers", "block service workers").option("--browser <browser>", "browser or chrome channel to use, possible values: chrome, firefox, webkit, msedge.").option("--caps <caps>", "comma-separated list of additional capabilities to enable, possible values: vision, pdf.", import_config.commaSeparatedList).option("--cdp-endpoint <endpoint>", "CDP endpoint to connect to.").option("--cdp-header <headers...>", "CDP headers to send with the connect request, multiple can be specified.", import_config.headerParser).option("--codegen <lang>", 'specify the language to use for code generation, possible values: "typescript", "none". Default is "typescript".', import_config.enumParser.bind(null, "--codegen", ["none", "typescript"])).option("--config <path>", "path to the configuration file.").option("--console-level <level>", 'level of console messages to return: "error", "warning", "info", "debug". Each level includes the messages of more severe levels.', import_config.enumParser.bind(null, "--console-level", ["error", "warning", "info", "debug"])).option("--device <device>", 'device to emulate, for example: "iPhone 15"').option("--executable-path <path>", "path to the browser executable.").option("--extension", 'Connect to a running browser instance (Edge/Chrome only). Requires the "Playwright MCP Bridge" browser extension to be installed.').option("--grant-permissions <permissions...>", 'List of permissions to grant to the browser context, for example "geolocation", "clipboard-read", "clipboard-write".', import_config.commaSeparatedList).option("--headless", "run browser in headless mode, headed by default").option("--host <host>", "host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces.").option("--ignore-https-errors", "ignore https errors").option("--init-page <path...>", "path to TypeScript file to evaluate on Playwright page object").option("--init-script <path...>", "path to JavaScript file to add as an initialization script. The script will be evaluated in every page before any of the page's scripts. Can be specified multiple times.").option("--isolated", "keep the browser profile in memory, do not save it to disk.").option("--image-responses <mode>", 'whether to send image responses to the client. Can be "allow" or "omit", Defaults to "allow".', import_config.enumParser.bind(null, "--image-responses", ["allow", "omit"])).option("--no-sandbox", "disable the sandbox for all process types that are normally sandboxed.").option("--output-dir <path>", "path to the directory for output files.").option("--output-mode <mode>", 'whether to save snapshots, console messages, network logs to a file or to the standard output. Can be "file" or "stdout". Default is "stdout".', import_config.enumParser.bind(null, "--output-mode", ["file", "stdout"])).option("--port <port>", "port to listen on for SSE transport.").option("--proxy-bypass <bypass>", 'comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com"').option("--proxy-server <proxy>", 'specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"').option("--sandbox", "enable the sandbox for all process types that are normally not sandboxed.").option("--save-session", "Whether to save the Playwright MCP session into the output directory.").option("--save-trace", "Whether to save the Playwright Trace of the session into the output directory.").option("--save-video <size>", 'Whether to save the video of the session into the output directory. For example "--save-video=800x600"', import_config.resolutionParser.bind(null, "--save-video")).option("--secrets <path>", "path to a file containing secrets in the dotenv format", import_config.dotenvFileLoader).option("--shared-browser-context", "reuse the same browser context between all connected HTTP clients.").option("--snapshot-mode <mode>", 'when taking snapshots for responses, specifies the mode to use. Can be "incremental", "full", or "none". Default is incremental.').option("--storage-state <path>", "path to the storage state file for isolated sessions.").option("--test-id-attribute <attribute>", 'specify the attribute to use for test ids, defaults to "data-testid"').option("--timeout-action <timeout>", "specify action timeout in milliseconds, defaults to 5000ms", import_config.numberParser).option("--timeout-navigation <timeout>", "specify navigation timeout in milliseconds, defaults to 60000ms", import_config.numberParser).option("--user-agent <ua string>", "specify user agent string").option("--user-data-dir <path>", "path to the user data directory. If not specified, a temporary directory will be created.").option("--viewport-size <size>", 'specify browser viewport size in pixels, for example "1280x720"', import_config.resolutionParser.bind(null, "--viewport-size")).addOption(new import_utilsBundle.ProgramOption("--vision", "Legacy option, use --caps=vision instead").hideHelp()).addOption(new import_utilsBundle.ProgramOption("--daemon <socket>", "run as daemon").hideHelp()).addOption(new import_utilsBundle.ProgramOption("--daemon-data-dir <path>", "path to the daemon data directory.").hideHelp()).addOption(new import_utilsBundle.ProgramOption("--daemon-headed", "run daemon in headed mode").hideHelp()).action(async (options) => {
45
+ command.option("--allowed-hosts <hosts...>", "comma-separated list of hosts this server is allowed to serve from. Defaults to the host the server is bound to. Pass '*' to disable the host check.", import_config.commaSeparatedList).option("--allowed-origins <origins>", "semicolon-separated list of TRUSTED origins to allow the browser to request. Default is to allow all.\nImportant: *does not* serve as a security boundary and *does not* affect redirects. ", import_config.semicolonSeparatedList).option("--allow-unrestricted-file-access", "allow access to files outside of the workspace roots. Also allows unrestricted access to file:// URLs. By default access to file system is restricted to workspace root directories (or cwd if no roots are configured) only, and navigation to file:// URLs is blocked.").option("--blocked-origins <origins>", "semicolon-separated list of origins to block the browser from requesting. Blocklist is evaluated before allowlist. If used without the allowlist, requests not matching the blocklist are still allowed.\nImportant: *does not* serve as a security boundary and *does not* affect redirects.", import_config.semicolonSeparatedList).option("--block-service-workers", "block service workers").option("--browser <browser>", "browser or chrome channel to use, possible values: chrome, firefox, webkit, msedge.").option("--caps <caps>", "comma-separated list of additional capabilities to enable, possible values: vision, pdf, devtools.", import_config.commaSeparatedList).option("--cdp-endpoint <endpoint>", "CDP endpoint to connect to.").option("--cdp-header <headers...>", "CDP headers to send with the connect request, multiple can be specified.", import_config.headerParser).option("--codegen <lang>", 'specify the language to use for code generation, possible values: "typescript", "none". Default is "typescript".', import_config.enumParser.bind(null, "--codegen", ["none", "typescript"])).option("--config <path>", "path to the configuration file.").option("--console-level <level>", 'level of console messages to return: "error", "warning", "info", "debug". Each level includes the messages of more severe levels.', import_config.enumParser.bind(null, "--console-level", ["error", "warning", "info", "debug"])).option("--device <device>", 'device to emulate, for example: "iPhone 15"').option("--executable-path <path>", "path to the browser executable.").option("--extension", 'Connect to a running browser instance (Edge/Chrome only). Requires the "Playwright MCP Bridge" browser extension to be installed.').option("--grant-permissions <permissions...>", 'List of permissions to grant to the browser context, for example "geolocation", "clipboard-read", "clipboard-write".', import_config.commaSeparatedList).option("--headless", "run browser in headless mode, headed by default").option("--host <host>", "host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces.").option("--ignore-https-errors", "ignore https errors").option("--init-page <path...>", "path to TypeScript file to evaluate on Playwright page object").option("--init-script <path...>", "path to JavaScript file to add as an initialization script. The script will be evaluated in every page before any of the page's scripts. Can be specified multiple times.").option("--isolated", "keep the browser profile in memory, do not save it to disk.").option("--image-responses <mode>", 'whether to send image responses to the client. Can be "allow" or "omit", Defaults to "allow".', import_config.enumParser.bind(null, "--image-responses", ["allow", "omit"])).option("--no-sandbox", "disable the sandbox for all process types that are normally sandboxed.").option("--output-dir <path>", "path to the directory for output files.").option("--output-mode <mode>", 'whether to save snapshots, console messages, network logs to a file or to the standard output. Can be "file" or "stdout". Default is "stdout".', import_config.enumParser.bind(null, "--output-mode", ["file", "stdout"])).option("--port <port>", "port to listen on for SSE transport.").option("--proxy-bypass <bypass>", 'comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com"').option("--proxy-server <proxy>", 'specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"').option("--sandbox", "enable the sandbox for all process types that are normally not sandboxed.").option("--save-session", "Whether to save the Playwright MCP session into the output directory.").option("--save-trace", "Whether to save the Playwright Trace of the session into the output directory.").option("--save-video <size>", 'Whether to save the video of the session into the output directory. For example "--save-video=800x600"', import_config.resolutionParser.bind(null, "--save-video")).option("--secrets <path>", "path to a file containing secrets in the dotenv format", import_config.dotenvFileLoader).option("--shared-browser-context", "reuse the same browser context between all connected HTTP clients.").option("--snapshot-mode <mode>", 'when taking snapshots for responses, specifies the mode to use. Can be "incremental", "full", or "none". Default is incremental.').option("--storage-state <path>", "path to the storage state file for isolated sessions.").option("--test-id-attribute <attribute>", 'specify the attribute to use for test ids, defaults to "data-testid"').option("--timeout-action <timeout>", "specify action timeout in milliseconds, defaults to 5000ms", import_config.numberParser).option("--timeout-navigation <timeout>", "specify navigation timeout in milliseconds, defaults to 60000ms", import_config.numberParser).option("--user-agent <ua string>", "specify user agent string").option("--user-data-dir <path>", "path to the user data directory. If not specified, a temporary directory will be created.").option("--viewport-size <size>", 'specify browser viewport size in pixels, for example "1280x720"', import_config.resolutionParser.bind(null, "--viewport-size")).addOption(new import_utilsBundle.ProgramOption("--vision", "Legacy option, use --caps=vision instead").hideHelp()).addOption(new import_utilsBundle.ProgramOption("--daemon <socket>", "run as daemon").hideHelp()).addOption(new import_utilsBundle.ProgramOption("--daemon-data-dir <path>", "path to the daemon data directory.").hideHelp()).addOption(new import_utilsBundle.ProgramOption("--daemon-headed", "run daemon in headed mode").hideHelp()).addOption(new import_utilsBundle.ProgramOption("--daemon-version <version>", "version of this daemon").hideHelp()).action(async (options) => {
46
46
  options.sandbox = options.sandbox === true ? void 0 : false;
47
47
  (0, import_watchdog.setupExitWatchdog)();
48
48
  if (options.vision) {
49
49
  console.error("The --vision option is deprecated, use --caps=vision instead");
50
50
  options.caps = "vision";
51
51
  }
52
+ if (options.caps?.includes("tracing"))
53
+ options.caps.push("devtools");
52
54
  const config = await (0, import_config.resolveCLIConfig)(options);
53
55
  if (config.saveVideo && !checkFfmpeg()) {
54
56
  console.error(import_utilsBundle.colors.red(`
@@ -62,25 +64,26 @@ Please run the command below. It will install a local copy of ffmpeg and will no
62
64
  }
63
65
  const browserContextFactory = (0, import_browserContextFactory.contextFactory)(config);
64
66
  const extensionContextFactory = new import_extensionContextFactory.ExtensionContextFactory(config.browser.launchOptions.channel || "chrome", config.browser.userDataDir, config.browser.launchOptions.executablePath);
65
- if (options.extension) {
67
+ if (options.daemon) {
68
+ const contextFactory2 = config.extension ? extensionContextFactory : browserContextFactory;
66
69
  const serverBackendFactory = {
67
- name: "Playwright w/ extension",
68
- nameInConfig: "playwright-extension",
70
+ name: "Playwright",
71
+ nameInConfig: "playwright-daemon",
69
72
  version,
70
- create: () => new import_browserServerBackend.BrowserServerBackend(config, extensionContextFactory)
73
+ create: () => new import_browserServerBackend.BrowserServerBackend(config, contextFactory2, { allTools: true })
71
74
  };
72
- await mcpServer.start(serverBackendFactory, config.server);
75
+ const socketPath = await (0, import_daemon.startMcpDaemonServer)(options.daemon, serverBackendFactory, options.daemonVersion);
76
+ console.error(`Daemon server listening on ${socketPath}`);
73
77
  return;
74
78
  }
75
- if (options.daemon) {
79
+ if (config.extension) {
76
80
  const serverBackendFactory = {
77
- name: "Playwright",
78
- nameInConfig: "playwright-daemon",
81
+ name: "Playwright w/ extension",
82
+ nameInConfig: "playwright-extension",
79
83
  version,
80
- create: () => new import_browserServerBackend.BrowserServerBackend(config, browserContextFactory, { allTools: true, structuredOutput: true })
84
+ create: () => new import_browserServerBackend.BrowserServerBackend(config, extensionContextFactory)
81
85
  };
82
- const socketPath = await (0, import_daemon.startMcpDaemonServer)(options.daemon, serverBackendFactory);
83
- console.error(`Daemon server listening on ${socketPath}`);
86
+ await mcpServer.start(serverBackendFactory, config.server);
84
87
  return;
85
88
  }
86
89
  const factory = {
@@ -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,6 +17,14 @@ 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 command_exports = {};
20
30
  __export(command_exports, {
@@ -22,10 +32,13 @@ __export(command_exports, {
22
32
  parseCommand: () => parseCommand
23
33
  });
24
34
  module.exports = __toCommonJS(command_exports);
35
+ var import_path = __toESM(require("path"));
25
36
  function declareCommand(command) {
26
37
  return command;
27
38
  }
28
39
  function parseCommand(command, args) {
40
+ if (args.filename)
41
+ args.filename = import_path.default.resolve(args.outputDir, args.filename);
29
42
  const shape = command.args ? command.args.shape : {};
30
43
  const argv = args["_"];
31
44
  const options = command.options?.parse({ ...args, _: void 0 }) ?? {};