@cotestdev/mcp_playwright 0.0.50 → 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.
Files changed (34) hide show
  1. package/lib/mcp/browser/browserContextFactory.js +11 -4
  2. package/lib/mcp/browser/browserServerBackend.js +2 -4
  3. package/lib/mcp/browser/config.js +71 -47
  4. package/lib/mcp/browser/context.js +65 -4
  5. package/lib/mcp/browser/logFile.js +96 -0
  6. package/lib/mcp/browser/response.js +107 -104
  7. package/lib/mcp/browser/sessionLog.js +1 -1
  8. package/lib/mcp/browser/tab.js +73 -18
  9. package/lib/mcp/browser/tools/config.js +41 -0
  10. package/lib/mcp/browser/tools/console.js +6 -2
  11. package/lib/mcp/browser/tools/cookies.js +152 -0
  12. package/lib/mcp/browser/tools/install.js +1 -0
  13. package/lib/mcp/browser/tools/network.js +25 -11
  14. package/lib/mcp/browser/tools/pdf.js +3 -4
  15. package/lib/mcp/browser/tools/route.js +140 -0
  16. package/lib/mcp/browser/tools/runCode.js +0 -2
  17. package/lib/mcp/browser/tools/screenshot.js +6 -7
  18. package/lib/mcp/browser/tools/storage.js +3 -4
  19. package/lib/mcp/browser/tools/tracing.js +10 -9
  20. package/lib/mcp/browser/tools/utils.js +0 -6
  21. package/lib/mcp/browser/tools/video.js +31 -13
  22. package/lib/mcp/browser/tools/webstorage.js +223 -0
  23. package/lib/mcp/browser/tools.js +11 -3
  24. package/lib/mcp/extension/cdpRelay.js +7 -7
  25. package/lib/mcp/extension/extensionContextFactory.js +4 -2
  26. package/lib/mcp/program.js +19 -12
  27. package/lib/mcp/terminal/cli.js +23 -2
  28. package/lib/mcp/terminal/command.js +34 -30
  29. package/lib/mcp/terminal/commands.js +310 -38
  30. package/lib/mcp/terminal/daemon.js +23 -38
  31. package/lib/mcp/terminal/helpGenerator.js +8 -6
  32. package/lib/mcp/terminal/program.js +482 -199
  33. package/lib/mcp/terminal/socketConnection.js +17 -2
  34. package/package.json +2 -2
@@ -0,0 +1,152 @@
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 cookies_exports = {};
20
+ __export(cookies_exports, {
21
+ default: () => cookies_default
22
+ });
23
+ module.exports = __toCommonJS(cookies_exports);
24
+ var import_mcpBundle = require("../../../mcpBundle");
25
+ var import_tool = require("./tool");
26
+ const cookieList = (0, import_tool.defineTool)({
27
+ capability: "storage",
28
+ schema: {
29
+ name: "browser_cookie_list",
30
+ title: "List cookies",
31
+ description: "List all cookies (optionally filtered by domain/path)",
32
+ inputSchema: import_mcpBundle.z.object({
33
+ domain: import_mcpBundle.z.string().optional().describe("Filter cookies by domain"),
34
+ path: import_mcpBundle.z.string().optional().describe("Filter cookies by path")
35
+ }),
36
+ type: "readOnly"
37
+ },
38
+ handle: async (context, params, response) => {
39
+ const browserContext = await context.ensureBrowserContext();
40
+ let cookies = await browserContext.cookies();
41
+ if (params.domain)
42
+ cookies = cookies.filter((c) => c.domain.includes(params.domain));
43
+ if (params.path)
44
+ cookies = cookies.filter((c) => c.path.startsWith(params.path));
45
+ if (cookies.length === 0)
46
+ response.addTextResult("No cookies found");
47
+ else
48
+ response.addTextResult(cookies.map((c) => `${c.name}=${c.value} (domain: ${c.domain}, path: ${c.path})`).join("\n"));
49
+ response.addCode(`await page.context().cookies();`);
50
+ }
51
+ });
52
+ const cookieGet = (0, import_tool.defineTool)({
53
+ capability: "storage",
54
+ schema: {
55
+ name: "browser_cookie_get",
56
+ title: "Get cookie",
57
+ description: "Get a specific cookie by name",
58
+ inputSchema: import_mcpBundle.z.object({
59
+ name: import_mcpBundle.z.string().describe("Cookie name to get")
60
+ }),
61
+ type: "readOnly"
62
+ },
63
+ handle: async (context, params, response) => {
64
+ const browserContext = await context.ensureBrowserContext();
65
+ const cookies = await browserContext.cookies();
66
+ const cookie = cookies.find((c) => c.name === params.name);
67
+ if (!cookie)
68
+ response.addTextResult(`Cookie '${params.name}' not found`);
69
+ else
70
+ response.addTextResult(`${cookie.name}=${cookie.value} (domain: ${cookie.domain}, path: ${cookie.path}, httpOnly: ${cookie.httpOnly}, secure: ${cookie.secure}, sameSite: ${cookie.sameSite})`);
71
+ response.addCode(`await page.context().cookies();`);
72
+ }
73
+ });
74
+ const cookieSet = (0, import_tool.defineTool)({
75
+ capability: "storage",
76
+ schema: {
77
+ name: "browser_cookie_set",
78
+ title: "Set cookie",
79
+ description: "Set a cookie with optional flags (domain, path, expires, httpOnly, secure, sameSite)",
80
+ inputSchema: import_mcpBundle.z.object({
81
+ name: import_mcpBundle.z.string().describe("Cookie name"),
82
+ value: import_mcpBundle.z.string().describe("Cookie value"),
83
+ domain: import_mcpBundle.z.string().optional().describe("Cookie domain"),
84
+ path: import_mcpBundle.z.string().optional().describe("Cookie path"),
85
+ expires: import_mcpBundle.z.number().optional().describe("Cookie expiration as Unix timestamp"),
86
+ httpOnly: import_mcpBundle.z.boolean().optional().describe("Whether the cookie is HTTP only"),
87
+ secure: import_mcpBundle.z.boolean().optional().describe("Whether the cookie is secure"),
88
+ sameSite: import_mcpBundle.z.enum(["Strict", "Lax", "None"]).optional().describe("Cookie SameSite attribute")
89
+ }),
90
+ type: "action"
91
+ },
92
+ handle: async (context, params, response) => {
93
+ const browserContext = await context.ensureBrowserContext();
94
+ const tab = await context.ensureTab();
95
+ const url = new URL(tab.page.url());
96
+ const cookie = {
97
+ name: params.name,
98
+ value: params.value,
99
+ domain: params.domain || url.hostname,
100
+ path: params.path || "/"
101
+ };
102
+ if (params.expires !== void 0)
103
+ cookie.expires = params.expires;
104
+ if (params.httpOnly !== void 0)
105
+ cookie.httpOnly = params.httpOnly;
106
+ if (params.secure !== void 0)
107
+ cookie.secure = params.secure;
108
+ if (params.sameSite !== void 0)
109
+ cookie.sameSite = params.sameSite;
110
+ await browserContext.addCookies([cookie]);
111
+ response.addCode(`await page.context().addCookies([${JSON.stringify(cookie)}]);`);
112
+ }
113
+ });
114
+ const cookieDelete = (0, import_tool.defineTool)({
115
+ capability: "storage",
116
+ schema: {
117
+ name: "browser_cookie_delete",
118
+ title: "Delete cookie",
119
+ description: "Delete a specific cookie",
120
+ inputSchema: import_mcpBundle.z.object({
121
+ name: import_mcpBundle.z.string().describe("Cookie name to delete")
122
+ }),
123
+ type: "action"
124
+ },
125
+ handle: async (context, params, response) => {
126
+ const browserContext = await context.ensureBrowserContext();
127
+ await browserContext.clearCookies({ name: params.name });
128
+ response.addCode(`await page.context().clearCookies({ name: '${params.name}' });`);
129
+ }
130
+ });
131
+ const cookieClear = (0, import_tool.defineTool)({
132
+ capability: "storage",
133
+ schema: {
134
+ name: "browser_cookie_clear",
135
+ title: "Clear cookies",
136
+ description: "Clear all cookies",
137
+ inputSchema: import_mcpBundle.z.object({}),
138
+ type: "action"
139
+ },
140
+ handle: async (context, params, response) => {
141
+ const browserContext = await context.ensureBrowserContext();
142
+ await browserContext.clearCookies();
143
+ response.addCode(`await page.context().clearCookies();`);
144
+ }
145
+ });
146
+ var cookies_default = [
147
+ cookieList,
148
+ cookieGet,
149
+ cookieSet,
150
+ cookieDelete,
151
+ cookieClear
152
+ ];
@@ -62,6 +62,7 @@ const install = (0, import_tool.defineTool)({
62
62
  reject(new Error(`Failed to install browser: ${output.join("")}`));
63
63
  });
64
64
  });
65
+ response.addTextResult(`Browser ${channel} installed.`);
65
66
  const tabHeaders = await Promise.all(context.tabs().map((tab) => tab.headerSnapshot()));
66
67
  const result = (0, import_response.renderTabsMarkdown)(tabHeaders);
67
68
  response.addTextResult(result.join("\n"));
@@ -18,7 +18,9 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var network_exports = {};
20
20
  __export(network_exports, {
21
- default: () => network_default
21
+ default: () => network_default,
22
+ isFetch: () => isFetch,
23
+ renderRequest: () => renderRequest
22
24
  });
23
25
  module.exports = __toCommonJS(network_exports);
24
26
  var import_mcpBundle = require("../../../mcpBundle");
@@ -39,11 +41,11 @@ const requests = (0, import_tool.defineTabTool)({
39
41
  const requests2 = await tab.requests();
40
42
  const text = [];
41
43
  for (const request of requests2) {
42
- const rendered = await renderRequest(request, params.includeStatic);
43
- if (rendered)
44
- text.push(rendered);
44
+ if (!params.includeStatic && !isFetch(request) && isSuccessfulResponse(request))
45
+ continue;
46
+ text.push(await renderRequest(request));
45
47
  }
46
- response.addResult("Network", text.join("\n"), { prefix: "network", ext: "log", suggestedFilename: params.filename });
48
+ await response.addResult("Network", text.join("\n"), { prefix: "network", ext: "log", suggestedFilename: params.filename });
47
49
  }
48
50
  });
49
51
  const networkClear = (0, import_tool.defineTabTool)({
@@ -60,19 +62,31 @@ const networkClear = (0, import_tool.defineTabTool)({
60
62
  await tab.clearRequests();
61
63
  }
62
64
  });
63
- async function renderRequest(request, includeStatic) {
64
- const response = request._hasResponse ? await request.response() : void 0;
65
- const isStaticRequest = ["document", "stylesheet", "image", "media", "font", "script", "manifest"].includes(request.resourceType());
66
- const isSuccessfulRequest = !response || response.status() < 400;
67
- if (isStaticRequest && isSuccessfulRequest && !includeStatic)
68
- return void 0;
65
+ function isSuccessfulResponse(request) {
66
+ if (request.failure())
67
+ return false;
68
+ const response = request.existingResponse();
69
+ return !!response && response.status() < 400;
70
+ }
71
+ function isFetch(request) {
72
+ return ["fetch", "xhr"].includes(request.resourceType());
73
+ }
74
+ async function renderRequest(request) {
75
+ const response = request.existingResponse();
69
76
  const result = [];
70
77
  result.push(`[${request.method().toUpperCase()}] ${request.url()}`);
71
78
  if (response)
72
79
  result.push(`=> [${response.status()}] ${response.statusText()}`);
80
+ else if (request.failure())
81
+ result.push(`=> [FAILED] ${request.failure()?.errorText ?? "Unknown error"}`);
73
82
  return result.join(" ");
74
83
  }
75
84
  var network_default = [
76
85
  requests,
77
86
  networkClear
78
87
  ];
88
+ // Annotate the CommonJS export names for ESM import in node:
89
+ 0 && (module.exports = {
90
+ isFetch,
91
+ renderRequest
92
+ });
@@ -24,7 +24,6 @@ module.exports = __toCommonJS(pdf_exports);
24
24
  var import_mcpBundle = require("../../../mcpBundle");
25
25
  var import_utils = require("playwright-core/lib/utils");
26
26
  var import_tool = require("./tool");
27
- var import_utils2 = require("./utils");
28
27
  const pdfSchema = import_mcpBundle.z.object({
29
28
  filename: import_mcpBundle.z.string().optional().describe("File name to save the pdf to. Defaults to `page-{timestamp}.pdf` if not specified. Prefer relative file names to stay within the output directory.")
30
29
  });
@@ -39,9 +38,9 @@ const pdf = (0, import_tool.defineTabTool)({
39
38
  },
40
39
  handle: async (tab, params, response) => {
41
40
  const data = await tab.page.pdf();
42
- const suggestedFilename = params.filename ?? (0, import_utils2.dateAsFileName)("page", "pdf");
43
- response.addResult("Page as pdf", data, { prefix: "page", ext: "pdf", suggestedFilename });
44
- response.addCode(`await page.pdf(${(0, import_utils.formatObject)({ path: suggestedFilename })});`);
41
+ const result = await response.resolveClientFile({ prefix: "page", ext: "pdf", suggestedFilename: params.filename }, "Page as pdf");
42
+ await response.addFileResult(result, data);
43
+ response.addCode(`await page.pdf(${(0, import_utils.formatObject)({ path: result.relativeName })});`);
45
44
  }
46
45
  });
47
46
  var pdf_default = [
@@ -0,0 +1,140 @@
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 route_exports = {};
20
+ __export(route_exports, {
21
+ default: () => route_default
22
+ });
23
+ module.exports = __toCommonJS(route_exports);
24
+ var import_mcpBundle = require("../../../mcpBundle");
25
+ var import_tool = require("./tool");
26
+ const route = (0, import_tool.defineTool)({
27
+ capability: "network",
28
+ schema: {
29
+ name: "browser_route",
30
+ title: "Mock network requests",
31
+ description: "Set up a route to mock network requests matching a URL pattern",
32
+ inputSchema: import_mcpBundle.z.object({
33
+ pattern: import_mcpBundle.z.string().describe('URL pattern to match (e.g., "**/api/users", "**/*.{png,jpg}")'),
34
+ status: import_mcpBundle.z.number().optional().describe("HTTP status code to return (default: 200)"),
35
+ body: import_mcpBundle.z.string().optional().describe("Response body (text or JSON string)"),
36
+ contentType: import_mcpBundle.z.string().optional().describe('Content-Type header (e.g., "application/json", "text/html")'),
37
+ headers: import_mcpBundle.z.array(import_mcpBundle.z.string()).optional().describe('Headers to add in "Name: Value" format'),
38
+ removeHeaders: import_mcpBundle.z.string().optional().describe("Comma-separated list of header names to remove from request")
39
+ }),
40
+ type: "action"
41
+ },
42
+ handle: async (context, params, response) => {
43
+ const addHeaders = params.headers ? Object.fromEntries(params.headers.map((h) => {
44
+ const colonIndex = h.indexOf(":");
45
+ return [h.substring(0, colonIndex).trim(), h.substring(colonIndex + 1).trim()];
46
+ })) : void 0;
47
+ const removeHeaders = params.removeHeaders ? params.removeHeaders.split(",").map((h) => h.trim()) : void 0;
48
+ const handler = async (route2) => {
49
+ if (params.body !== void 0 || params.status !== void 0) {
50
+ await route2.fulfill({
51
+ status: params.status ?? 200,
52
+ contentType: params.contentType,
53
+ body: params.body
54
+ });
55
+ return;
56
+ }
57
+ const headers = { ...route2.request().headers() };
58
+ if (addHeaders) {
59
+ for (const [key, value] of Object.entries(addHeaders))
60
+ headers[key] = value;
61
+ }
62
+ if (removeHeaders) {
63
+ for (const header of removeHeaders)
64
+ delete headers[header.toLowerCase()];
65
+ }
66
+ await route2.continue({ headers });
67
+ };
68
+ const entry = {
69
+ pattern: params.pattern,
70
+ status: params.status,
71
+ body: params.body,
72
+ contentType: params.contentType,
73
+ addHeaders,
74
+ removeHeaders,
75
+ handler
76
+ };
77
+ await context.addRoute(entry);
78
+ response.addTextResult(`Route added for pattern: ${params.pattern}`);
79
+ response.addCode(`await page.context().route('${params.pattern}', async route => { /* route handler */ });`);
80
+ }
81
+ });
82
+ const routeList = (0, import_tool.defineTool)({
83
+ capability: "network",
84
+ schema: {
85
+ name: "browser_route_list",
86
+ title: "List network routes",
87
+ description: "List all active network routes",
88
+ inputSchema: import_mcpBundle.z.object({}),
89
+ type: "readOnly"
90
+ },
91
+ handle: async (context, params, response) => {
92
+ const routes = context.routes();
93
+ if (routes.length === 0) {
94
+ response.addTextResult("No active routes");
95
+ return;
96
+ }
97
+ const lines = [];
98
+ for (let i = 0; i < routes.length; i++) {
99
+ const route2 = routes[i];
100
+ const details = [];
101
+ if (route2.status !== void 0)
102
+ details.push(`status=${route2.status}`);
103
+ if (route2.body !== void 0)
104
+ details.push(`body=${route2.body.length > 50 ? route2.body.substring(0, 50) + "..." : route2.body}`);
105
+ if (route2.contentType)
106
+ details.push(`contentType=${route2.contentType}`);
107
+ if (route2.addHeaders)
108
+ details.push(`addHeaders=${JSON.stringify(route2.addHeaders)}`);
109
+ if (route2.removeHeaders)
110
+ details.push(`removeHeaders=${route2.removeHeaders.join(",")}`);
111
+ const detailsStr = details.length ? ` (${details.join(", ")})` : "";
112
+ lines.push(`${i + 1}. ${route2.pattern}${detailsStr}`);
113
+ }
114
+ response.addTextResult(lines.join("\n"));
115
+ }
116
+ });
117
+ const unroute = (0, import_tool.defineTool)({
118
+ capability: "network",
119
+ schema: {
120
+ name: "browser_unroute",
121
+ title: "Remove network routes",
122
+ description: "Remove network routes matching a pattern (or all routes if no pattern specified)",
123
+ inputSchema: import_mcpBundle.z.object({
124
+ pattern: import_mcpBundle.z.string().optional().describe("URL pattern to unroute (omit to remove all routes)")
125
+ }),
126
+ type: "action"
127
+ },
128
+ handle: async (context, params, response) => {
129
+ const removed = await context.removeRoute(params.pattern);
130
+ if (params.pattern)
131
+ response.addTextResult(`Removed ${removed} route(s) for pattern: ${params.pattern}`);
132
+ else
133
+ response.addTextResult(`Removed all ${removed} route(s)`);
134
+ }
135
+ });
136
+ var route_default = [
137
+ route,
138
+ routeList,
139
+ unroute
140
+ ];
@@ -35,7 +35,6 @@ var import_vm = __toESM(require("vm"));
35
35
  var import_utils = require("playwright-core/lib/utils");
36
36
  var import_mcpBundle = require("../../../mcpBundle");
37
37
  var import_ai_runner_fake = require("@cotestdev/ai-runner-fake");
38
- var import_test = require("@playwright/test");
39
38
  var import_tool = require("./tool");
40
39
  var import_schema = require("./schema");
41
40
  const codeSchema = import_schema.baseSchema.extend({
@@ -92,7 +91,6 @@ const runScript = (0, import_tool.defineTabTool)({
92
91
  handle: async (tab, params, response) => {
93
92
  response.setIncludeSnapshot();
94
93
  const runner = import_ai_runner_fake.Runner.NewInstance(params.projectId, params.testId);
95
- runner.setExternalContext({ "expect": import_test.expect });
96
94
  await runner.init(tab.page, tab.page.context(), params.params);
97
95
  const result = await runner.runScript(params.testId, params.code);
98
96
  const code = `// Returns the out parameters of the reusable test
@@ -27,7 +27,6 @@ var import_utilsBundle = require("playwright-core/lib/utilsBundle");
27
27
  var import_utils2 = require("playwright-core/lib/utils");
28
28
  var import_mcpBundle = require("../../../mcpBundle");
29
29
  var import_tool = require("./tool");
30
- var import_utils3 = require("./utils");
31
30
  var import_schema = require("./schema");
32
31
  const screenshotSchema = import_schema.baseSchema.extend({
33
32
  type: import_mcpBundle.z.enum(["png", "jpeg"]).default("png").describe("Image format for the screenshot. Default is png."),
@@ -58,14 +57,14 @@ const screenshot = (0, import_tool.defineTabTool)({
58
57
  const screenshotTarget = params.ref ? params.element || "element" : params.fullPage ? "full page" : "viewport";
59
58
  const ref = params.ref ? await tab.refLocator({ element: params.element || "", ref: params.ref }) : null;
60
59
  const data = ref ? await ref.locator.screenshot(options) : await tab.page.screenshot(options);
61
- const suggestedFilename = params.filename || (0, import_utils3.dateAsFileName)(ref ? "element" : "page", fileType);
62
- response.addCode(`// Screenshot ${screenshotTarget} and save it as ${suggestedFilename}`);
60
+ const resolvedFile = await response.resolveClientFile({ prefix: ref ? "element" : "page", ext: fileType, suggestedFilename: params.filename }, `Screenshot of ${screenshotTarget}`);
61
+ response.addCode(`// Screenshot ${screenshotTarget} and save it as ${resolvedFile.relativeName}`);
63
62
  if (ref)
64
- response.addCode(`await page.${ref.resolved}.screenshot(${(0, import_utils2.formatObject)({ ...options, path: suggestedFilename })});`);
63
+ response.addCode(`await page.${ref.resolved}.screenshot(${(0, import_utils2.formatObject)({ ...options, path: resolvedFile.relativeName })});`);
65
64
  else
66
- response.addCode(`await page.screenshot(${(0, import_utils2.formatObject)({ ...options, path: suggestedFilename })});`);
67
- const contentType = fileType === "png" ? "image/png" : "image/jpeg";
68
- response.addResult(`Screenshot of ${screenshotTarget}`, data, { prefix: ref ? "element" : "page", ext: fileType, suggestedFilename, contentType });
65
+ response.addCode(`await page.screenshot(${(0, import_utils2.formatObject)({ ...options, path: resolvedFile.relativeName })});`);
66
+ await response.addFileResult(resolvedFile, data);
67
+ await response.registerImageResult(data, fileType);
69
68
  }
70
69
  });
71
70
  function scaleImageToFitMessage(buffer, imageType) {
@@ -23,7 +23,6 @@ __export(storage_exports, {
23
23
  module.exports = __toCommonJS(storage_exports);
24
24
  var import_mcpBundle = require("../../../mcpBundle");
25
25
  var import_tool = require("./tool");
26
- var import_utils = require("./utils");
27
26
  const storageState = (0, import_tool.defineTool)({
28
27
  capability: "storage",
29
28
  schema: {
@@ -38,10 +37,10 @@ const storageState = (0, import_tool.defineTool)({
38
37
  handle: async (context, params, response) => {
39
38
  const browserContext = await context.ensureBrowserContext();
40
39
  const state = await browserContext.storageState();
41
- const suggestedFilename = params.filename || (0, import_utils.dateAsFileName)("storage-state", "json");
42
40
  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}' });`);
41
+ const resolvedFile = await response.resolveClientFile({ prefix: "storage-state", ext: "json", suggestedFilename: params.filename }, "Storage state");
42
+ response.addCode(`await page.context().storageState({ path: '${resolvedFile.relativeName}' });`);
43
+ await response.addFileResult(resolvedFile, serializedState);
45
44
  }
46
45
  });
47
46
  const setStorageState = (0, import_tool.defineTool)({
@@ -34,7 +34,7 @@ const tracingStart = (0, import_tool.defineTool)({
34
34
  },
35
35
  handle: async (context, params, response) => {
36
36
  const browserContext = await context.ensureBrowserContext();
37
- const tracesDir = await context.outputFile(`traces`, { origin: "code", title: "Collecting trace" });
37
+ const tracesDir = await context.outputFile({ prefix: "", suggestedFilename: `traces`, ext: "" }, { origin: "code" });
38
38
  const name = "trace-" + Date.now();
39
39
  await browserContext.tracing.start({
40
40
  name,
@@ -42,12 +42,11 @@ const tracingStart = (0, import_tool.defineTool)({
42
42
  snapshots: true,
43
43
  _live: true
44
44
  });
45
- const traceLegend = `- Action log: ${tracesDir}/${name}.trace
46
- - Network log: ${tracesDir}/${name}.network
47
- - Resources with content by sha1: ${tracesDir}/resources`;
48
- response.addTextResult(`Tracing started, saving to ${tracesDir}.
49
- ${traceLegend}`);
50
- browserContext.tracing[traceLegendSymbol] = traceLegend;
45
+ response.addTextResult(`Trace recording started`);
46
+ response.addFileLink("Action log", `${tracesDir}/${name}.trace`);
47
+ response.addFileLink("Network log", `${tracesDir}/${name}.network`);
48
+ response.addFileLink("Resources", `${tracesDir}/resources`);
49
+ browserContext.tracing[traceLegendSymbol] = { tracesDir, name };
51
50
  }
52
51
  });
53
52
  const tracingStop = (0, import_tool.defineTool)({
@@ -63,8 +62,10 @@ const tracingStop = (0, import_tool.defineTool)({
63
62
  const browserContext = await context.ensureBrowserContext();
64
63
  await browserContext.tracing.stop();
65
64
  const traceLegend = browserContext.tracing[traceLegendSymbol];
66
- response.addTextResult(`Tracing stopped.
67
- ${traceLegend}`);
65
+ response.addTextResult(`Trace recording stopped.`);
66
+ response.addFileLink("Trace", `${traceLegend.tracesDir}/${traceLegend.name}.trace`);
67
+ response.addFileLink("Network log", `${traceLegend.tracesDir}/${traceLegend.name}.network`);
68
+ response.addFileLink("Resources", `${traceLegend.tracesDir}/resources`);
68
69
  }
69
70
  });
70
71
  var tracing_default = [
@@ -19,7 +19,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  var utils_exports = {};
20
20
  __export(utils_exports, {
21
21
  callOnPageNoTrace: () => callOnPageNoTrace,
22
- dateAsFileName: () => dateAsFileName,
23
22
  eventWaiter: () => eventWaiter,
24
23
  waitForCompletion: () => waitForCompletion
25
24
  });
@@ -62,10 +61,6 @@ async function waitForCompletion(tab, callback) {
62
61
  async function callOnPageNoTrace(page, callback) {
63
62
  return await page._wrapApiCall(() => callback(page), { internal: true });
64
63
  }
65
- function dateAsFileName(prefix, extension) {
66
- const date = /* @__PURE__ */ new Date();
67
- return `${prefix}-${date.toISOString().replace(/[:.]/g, "-")}.${extension}`;
68
- }
69
64
  function eventWaiter(page, event, timeout) {
70
65
  const disposables = [];
71
66
  const eventPromise = new Promise((resolve, reject) => {
@@ -88,7 +83,6 @@ function eventWaiter(page, event, timeout) {
88
83
  // Annotate the CommonJS export names for ESM import in node:
89
84
  0 && (module.exports = {
90
85
  callOnPageNoTrace,
91
- dateAsFileName,
92
86
  eventWaiter,
93
87
  waitForCompletion
94
88
  });
@@ -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,16 +17,24 @@ 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 video_exports = {};
20
30
  __export(video_exports, {
21
31
  default: () => video_default
22
32
  });
23
33
  module.exports = __toCommonJS(video_exports);
34
+ var import_path = __toESM(require("path"));
24
35
  var import_mcpBundle = require("../../../mcpBundle");
25
36
  var import_tool = require("./tool");
26
- var import_utils = require("./utils");
27
- const startVideo = (0, import_tool.defineTabTool)({
37
+ const startVideo = (0, import_tool.defineTool)({
28
38
  capability: "devtools",
29
39
  schema: {
30
40
  name: "browser_start_video",
@@ -38,12 +48,12 @@ const startVideo = (0, import_tool.defineTabTool)({
38
48
  }),
39
49
  type: "readOnly"
40
50
  },
41
- handle: async (tab, params, response) => {
42
- await tab.page.video().start({ size: params.size });
51
+ handle: async (context, params, response) => {
52
+ await context.startVideoRecording({ size: params.size });
43
53
  response.addTextResult("Video recording started.");
44
54
  }
45
55
  });
46
- const stopVideo = (0, import_tool.defineTabTool)({
56
+ const stopVideo = (0, import_tool.defineTool)({
47
57
  capability: "devtools",
48
58
  schema: {
49
59
  name: "browser_stop_video",
@@ -54,15 +64,23 @@ const stopVideo = (0, import_tool.defineTabTool)({
54
64
  }),
55
65
  type: "readOnly"
56
66
  },
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" });
67
+ handle: async (context, params, response) => {
68
+ const videos = await context.stopVideoRecording();
69
+ if (!videos.size) {
70
+ response.addTextResult("No videos were recorded.");
71
+ return;
72
+ }
73
+ for (const [index, video] of [...videos].entries()) {
74
+ const suffix = index ? `-${index}` : "";
75
+ let suggestedFilename = params.filename;
76
+ if (suggestedFilename && suffix) {
77
+ const ext = import_path.default.extname(suggestedFilename);
78
+ suggestedFilename = import_path.default.basename(suggestedFilename, ext) + suffix + ext;
79
+ }
80
+ const resolvedFile = await response.resolveClientFile({ prefix: "video" + suffix, ext: "webm", suggestedFilename }, "Video");
81
+ await video.saveAs(resolvedFile.fileName);
82
+ await response.addFileResult(resolvedFile, null);
62
83
  }
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
84
  }
67
85
  });
68
86
  var video_default = [