@cotestdev/mcp_playwright 0.0.14 → 0.0.16

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 (49) hide show
  1. package/lib/mcp/browser/browserContextFactory.js +49 -13
  2. package/lib/mcp/browser/browserServerBackend.js +5 -2
  3. package/lib/mcp/browser/config.js +95 -23
  4. package/lib/mcp/browser/context.js +28 -3
  5. package/lib/mcp/browser/response.js +240 -57
  6. package/lib/mcp/browser/sessionLog.js +1 -1
  7. package/lib/mcp/browser/tab.js +96 -69
  8. package/lib/mcp/browser/tools/common.js +8 -8
  9. package/lib/mcp/browser/tools/console.js +6 -3
  10. package/lib/mcp/browser/tools/dialogs.js +13 -13
  11. package/lib/mcp/browser/tools/evaluate.js +9 -20
  12. package/lib/mcp/browser/tools/files.js +10 -5
  13. package/lib/mcp/browser/tools/form.js +11 -22
  14. package/lib/mcp/browser/tools/install.js +3 -3
  15. package/lib/mcp/browser/tools/keyboard.js +12 -12
  16. package/lib/mcp/browser/tools/mouse.js +14 -14
  17. package/lib/mcp/browser/tools/navigate.js +5 -5
  18. package/lib/mcp/browser/tools/network.js +16 -5
  19. package/lib/mcp/browser/tools/pdf.js +7 -18
  20. package/lib/mcp/browser/tools/runCode.js +77 -0
  21. package/lib/mcp/browser/tools/screenshot.js +44 -33
  22. package/lib/mcp/browser/tools/snapshot.js +42 -33
  23. package/lib/mcp/browser/tools/tabs.js +7 -10
  24. package/lib/mcp/browser/tools/tool.js +8 -7
  25. package/lib/mcp/browser/tools/tracing.js +4 -4
  26. package/lib/mcp/browser/tools/utils.js +50 -52
  27. package/lib/mcp/browser/tools/verify.js +23 -34
  28. package/lib/mcp/browser/tools/wait.js +6 -6
  29. package/lib/mcp/browser/tools.js +4 -3
  30. package/lib/mcp/extension/cdpRelay.js +1 -1
  31. package/lib/mcp/extension/extensionContextFactory.js +4 -3
  32. package/lib/mcp/log.js +2 -2
  33. package/lib/mcp/program.js +21 -29
  34. package/lib/mcp/sdk/exports.js +1 -5
  35. package/lib/mcp/sdk/http.js +37 -50
  36. package/lib/mcp/sdk/server.js +61 -9
  37. package/lib/mcp/sdk/tool.js +5 -4
  38. package/lib/mcp/test/browserBackend.js +67 -61
  39. package/lib/mcp/test/generatorTools.js +122 -0
  40. package/lib/mcp/test/plannerTools.js +144 -0
  41. package/lib/mcp/test/seed.js +82 -0
  42. package/lib/mcp/test/streams.js +10 -7
  43. package/lib/mcp/test/testBackend.js +44 -24
  44. package/lib/mcp/test/testContext.js +243 -14
  45. package/lib/mcp/test/testTools.js +23 -109
  46. package/lib/mcpBundle.js +84 -0
  47. package/lib/mcpBundleImpl/index.js +130 -0
  48. package/lib/util.js +12 -6
  49. package/package.json +1 -1
@@ -21,10 +21,9 @@ __export(keyboard_exports, {
21
21
  default: () => keyboard_default
22
22
  });
23
23
  module.exports = __toCommonJS(keyboard_exports);
24
- var import_bundle = require("../../sdk/bundle");
24
+ var import_mcpBundle = require("../../../mcpBundleImpl");
25
25
  var import_tool = require("./tool");
26
26
  var import_snapshot = require("./snapshot");
27
- var import_utils = require("./utils");
28
27
  var import_common = require("./common");
29
28
  const pressKey = (0, import_tool.defineTabTool)({
30
29
  capability: "core",
@@ -33,12 +32,13 @@ const pressKey = (0, import_tool.defineTabTool)({
33
32
  title: "Press a key",
34
33
  description: "Press a key on the keyboard",
35
34
  inputSchema: import_common.baseSchema.extend({
36
- key: import_bundle.z.string().describe("Name of the key to press or a character to generate, such as `ArrowLeft` or `a`")
35
+ key: import_mcpBundle.z.string().describe("Name of the key to press or a character to generate, such as `ArrowLeft` or `a`")
37
36
  }),
38
- type: "destructive"
37
+ type: "input"
39
38
  },
40
39
  handle: async (tab, params, response) => {
41
40
  response.setIncludeSnapshot();
41
+ response.addCode(`// Press ${params.key}`);
42
42
  response.addCode(`await page.keyboard.press('${params.key}');`);
43
43
  await tab.waitForCompletion(async () => {
44
44
  await tab.page.keyboard.press(params.key);
@@ -46,9 +46,9 @@ const pressKey = (0, import_tool.defineTabTool)({
46
46
  }
47
47
  });
48
48
  const typeSchema = import_snapshot.elementSchema.extend({
49
- text: import_bundle.z.string().describe("Text to type into the element"),
50
- submit: import_bundle.z.boolean().optional().describe("Whether to submit entered text (press Enter after)"),
51
- slowly: import_bundle.z.boolean().optional().describe("Whether to type one character at a time. Useful for triggering key handlers in the page. By default entire text is filled in at once.")
49
+ text: import_mcpBundle.z.string().describe("Text to type into the element"),
50
+ submit: import_mcpBundle.z.boolean().optional().describe("Whether to submit entered text (press Enter after)"),
51
+ slowly: import_mcpBundle.z.boolean().optional().describe("Whether to type one character at a time. Useful for triggering key handlers in the page. By default entire text is filled in at once.")
52
52
  });
53
53
  const type = (0, import_tool.defineTabTool)({
54
54
  capability: "core",
@@ -57,23 +57,23 @@ const type = (0, import_tool.defineTabTool)({
57
57
  title: "Type text",
58
58
  description: "Type text into editable element",
59
59
  inputSchema: typeSchema,
60
- type: "destructive"
60
+ type: "input"
61
61
  },
62
62
  handle: async (tab, params, response) => {
63
- const locator = await tab.refLocator(params);
63
+ const { locator, resolved } = await tab.refLocator(params);
64
64
  const secret = tab.context.lookupSecret(params.text);
65
65
  await tab.waitForCompletion(async () => {
66
66
  if (params.slowly) {
67
67
  response.setIncludeSnapshot();
68
- response.addCode(`await page.${await (0, import_utils.generateLocator)(locator)}.pressSequentially(${secret.code});`);
68
+ response.addCode(`await page.${resolved}.pressSequentially(${secret.code});`);
69
69
  await locator.pressSequentially(secret.value);
70
70
  } else {
71
- response.addCode(`await page.${await (0, import_utils.generateLocator)(locator)}.fill(${secret.code});`);
71
+ response.addCode(`await page.${resolved}.fill(${secret.code});`);
72
72
  await locator.fill(secret.value);
73
73
  }
74
74
  if (params.submit) {
75
75
  response.setIncludeSnapshot();
76
- response.addCode(`await page.${await (0, import_utils.generateLocator)(locator)}.press('Enter');`);
76
+ response.addCode(`await page.${resolved}.press('Enter');`);
77
77
  await locator.press("Enter");
78
78
  }
79
79
  });
@@ -21,10 +21,10 @@ __export(mouse_exports, {
21
21
  default: () => mouse_default
22
22
  });
23
23
  module.exports = __toCommonJS(mouse_exports);
24
- var import_bundle = require("../../sdk/bundle");
24
+ var import_mcpBundle = require("../../../mcpBundleImpl");
25
25
  var import_tool = require("./tool");
26
- const elementSchema = import_bundle.z.object({
27
- element: import_bundle.z.string().describe("Human-readable element description used to obtain permission to interact with the element")
26
+ const elementSchema = import_mcpBundle.z.object({
27
+ element: import_mcpBundle.z.string().describe("Human-readable element description used to obtain permission to interact with the element")
28
28
  });
29
29
  const mouseMove = (0, import_tool.defineTabTool)({
30
30
  capability: "vision",
@@ -33,10 +33,10 @@ const mouseMove = (0, import_tool.defineTabTool)({
33
33
  title: "Move mouse",
34
34
  description: "Move mouse to a given position",
35
35
  inputSchema: elementSchema.extend({
36
- x: import_bundle.z.number().describe("X coordinate"),
37
- y: import_bundle.z.number().describe("Y coordinate")
36
+ x: import_mcpBundle.z.number().describe("X coordinate"),
37
+ y: import_mcpBundle.z.number().describe("Y coordinate")
38
38
  }),
39
- type: "readOnly"
39
+ type: "input"
40
40
  },
41
41
  handle: async (tab, params, response) => {
42
42
  response.addCode(`// Move mouse to (${params.x}, ${params.y})`);
@@ -53,10 +53,10 @@ const mouseClick = (0, import_tool.defineTabTool)({
53
53
  title: "Click",
54
54
  description: "Click left mouse button at a given position",
55
55
  inputSchema: elementSchema.extend({
56
- x: import_bundle.z.number().describe("X coordinate"),
57
- y: import_bundle.z.number().describe("Y coordinate")
56
+ x: import_mcpBundle.z.number().describe("X coordinate"),
57
+ y: import_mcpBundle.z.number().describe("Y coordinate")
58
58
  }),
59
- type: "destructive"
59
+ type: "input"
60
60
  },
61
61
  handle: async (tab, params, response) => {
62
62
  response.setIncludeSnapshot();
@@ -78,12 +78,12 @@ const mouseDrag = (0, import_tool.defineTabTool)({
78
78
  title: "Drag mouse",
79
79
  description: "Drag left mouse button to a given position",
80
80
  inputSchema: elementSchema.extend({
81
- startX: import_bundle.z.number().describe("Start X coordinate"),
82
- startY: import_bundle.z.number().describe("Start Y coordinate"),
83
- endX: import_bundle.z.number().describe("End X coordinate"),
84
- endY: import_bundle.z.number().describe("End Y coordinate")
81
+ startX: import_mcpBundle.z.number().describe("Start X coordinate"),
82
+ startY: import_mcpBundle.z.number().describe("Start Y coordinate"),
83
+ endX: import_mcpBundle.z.number().describe("End X coordinate"),
84
+ endY: import_mcpBundle.z.number().describe("End Y coordinate")
85
85
  }),
86
- type: "destructive"
86
+ type: "input"
87
87
  },
88
88
  handle: async (tab, params, response) => {
89
89
  response.setIncludeSnapshot();
@@ -21,9 +21,9 @@ __export(navigate_exports, {
21
21
  default: () => navigate_default
22
22
  });
23
23
  module.exports = __toCommonJS(navigate_exports);
24
- var import_bundle = require("../../sdk/bundle");
25
- var import_common = require("./common");
24
+ var import_mcpBundle = require("../../../mcpBundleImpl");
26
25
  var import_tool = require("./tool");
26
+ var import_common = require("./common");
27
27
  const navigate = (0, import_tool.defineTool)({
28
28
  capability: "core",
29
29
  schema: {
@@ -31,9 +31,9 @@ const navigate = (0, import_tool.defineTool)({
31
31
  title: "Navigate to a URL",
32
32
  description: "Navigate to a URL",
33
33
  inputSchema: import_common.baseSchema.extend({
34
- url: import_bundle.z.string().describe("The URL to navigate to")
34
+ url: import_mcpBundle.z.string().describe("The URL to navigate to")
35
35
  }),
36
- type: "destructive"
36
+ type: "action"
37
37
  },
38
38
  handle: async (context, params, response) => {
39
39
  const tab = await context.ensureTab();
@@ -49,7 +49,7 @@ const goBack = (0, import_tool.defineTabTool)({
49
49
  title: "Go back",
50
50
  description: "Go back to the previous page",
51
51
  inputSchema: import_common.baseSchema.extend({}),
52
- type: "readOnly"
52
+ type: "action"
53
53
  },
54
54
  handle: async (tab, params, response) => {
55
55
  await tab.page.goBack();
@@ -21,7 +21,7 @@ __export(network_exports, {
21
21
  default: () => network_default
22
22
  });
23
23
  module.exports = __toCommonJS(network_exports);
24
- var import_bundle = require("../../sdk/bundle");
24
+ var import_mcpBundle = require("../../../mcpBundleImpl");
25
25
  var import_tool = require("./tool");
26
26
  const requests = (0, import_tool.defineTabTool)({
27
27
  capability: "core",
@@ -29,15 +29,26 @@ const requests = (0, import_tool.defineTabTool)({
29
29
  name: "browser_network_requests",
30
30
  title: "List network requests",
31
31
  description: "Returns all network requests since loading the page",
32
- inputSchema: import_bundle.z.object({}),
32
+ inputSchema: import_mcpBundle.z.object({
33
+ includeStatic: import_mcpBundle.z.boolean().default(false).describe("Whether to include successful static resources like images, fonts, scripts, etc. Defaults to false.")
34
+ }),
33
35
  type: "readOnly"
34
36
  },
35
37
  handle: async (tab, params, response) => {
36
- const requests2 = tab.requests();
37
- [...requests2.entries()].forEach(([req, res]) => response.addResult(renderRequest(req, res)));
38
+ const requests2 = await tab.requests();
39
+ for (const request of requests2) {
40
+ const rendered = await renderRequest(request, params.includeStatic);
41
+ if (rendered)
42
+ response.addResult(rendered);
43
+ }
38
44
  }
39
45
  });
40
- function renderRequest(request, response) {
46
+ async function renderRequest(request, includeStatic) {
47
+ const response = request._hasResponse ? await request.response() : void 0;
48
+ const isStaticRequest = ["document", "stylesheet", "image", "media", "font", "script", "manifest"].includes(request.resourceType());
49
+ const isSuccessfulRequest = !response || response.status() < 400;
50
+ if (isStaticRequest && isSuccessfulRequest && !includeStatic)
51
+ return void 0;
41
52
  const result = [];
42
53
  result.push(`[${request.method().toUpperCase()}] ${request.url()}`);
43
54
  if (response)
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
6
  var __export = (target, all) => {
9
7
  for (var name in all)
@@ -17,26 +15,18 @@ var __copyProps = (to, from, except, desc) => {
17
15
  }
18
16
  return to;
19
17
  };
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
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
19
  var pdf_exports = {};
30
20
  __export(pdf_exports, {
31
21
  default: () => pdf_default
32
22
  });
33
23
  module.exports = __toCommonJS(pdf_exports);
34
- var import_bundle = require("../../sdk/bundle");
24
+ var import_mcpBundle = require("../../../mcpBundleImpl");
25
+ var import_utils = require("playwright-core/lib/utils");
35
26
  var import_tool = require("./tool");
36
- var javascript = __toESM(require("../codegen"));
37
- var import_utils = require("./utils");
38
- const pdfSchema = import_bundle.z.object({
39
- filename: import_bundle.z.string().optional().describe("File name to save the pdf to. Defaults to `page-{timestamp}.pdf` if not specified.")
27
+ var import_utils2 = require("./utils");
28
+ const pdfSchema = import_mcpBundle.z.object({
29
+ 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.")
40
30
  });
41
31
  const pdf = (0, import_tool.defineTabTool)({
42
32
  capability: "pdf",
@@ -48,9 +38,8 @@ const pdf = (0, import_tool.defineTabTool)({
48
38
  type: "readOnly"
49
39
  },
50
40
  handle: async (tab, params, response) => {
51
- const fileName = await tab.context.outputFile(params.filename ?? `page-${(0, import_utils.dateAsFileName)()}.pdf`, { origin: "llm" });
52
- response.addCode(`await page.pdf(${javascript.formatObject({ path: fileName })});`);
53
- response.addResult(`Saved page as ${fileName}`);
41
+ const fileName = await response.addFile(params.filename ?? (0, import_utils2.dateAsFileName)("pdf"), { origin: "llm", reason: "Page saved as PDF" });
42
+ response.addCode(`await page.pdf(${(0, import_utils.formatObject)({ path: fileName })});`);
54
43
  await tab.page.pdf({ path: fileName });
55
44
  }
56
45
  });
@@ -0,0 +1,77 @@
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 runCode_exports = {};
30
+ __export(runCode_exports, {
31
+ default: () => runCode_default
32
+ });
33
+ module.exports = __toCommonJS(runCode_exports);
34
+ var import_vm = __toESM(require("vm"));
35
+ var import_utils = require("playwright-core/lib/utils");
36
+ var import_mcpBundle = require("../../../mcpBundleImpl");
37
+ var import_tool = require("./tool");
38
+ const codeSchema = import_mcpBundle.z.object({
39
+ code: import_mcpBundle.z.string().describe(`A JavaScript function containing Playwright code to execute. It will be invoked with a single argument, page, which you can use for any page interaction. For example: \`async (page) => { await page.getByRole('button', { name: 'Submit' }).click(); return await page.title(); }\``)
40
+ });
41
+ const runCode = (0, import_tool.defineTabTool)({
42
+ capability: "core",
43
+ schema: {
44
+ name: "browser_run_code",
45
+ title: "Run Playwright code",
46
+ description: "Run Playwright code snippet",
47
+ inputSchema: codeSchema,
48
+ type: "action"
49
+ },
50
+ handle: async (tab, params, response) => {
51
+ response.setIncludeSnapshot();
52
+ response.addCode(`await (${params.code})(page);`);
53
+ const __end__ = new import_utils.ManualPromise();
54
+ const context = {
55
+ page: tab.page,
56
+ __end__
57
+ };
58
+ import_vm.default.createContext(context);
59
+ await tab.waitForCompletion(async () => {
60
+ const snippet = `(async () => {
61
+ try {
62
+ const result = await (${params.code})(page);
63
+ __end__.resolve(JSON.stringify(result));
64
+ } catch (e) {
65
+ __end__.reject(e);
66
+ }
67
+ })()`;
68
+ await import_vm.default.runInContext(snippet, context);
69
+ const result = await __end__;
70
+ if (typeof result === "string")
71
+ response.addResult(result);
72
+ });
73
+ }
74
+ });
75
+ var runCode_default = [
76
+ runCode
77
+ ];
@@ -28,30 +28,24 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
  var screenshot_exports = {};
30
30
  __export(screenshot_exports, {
31
- default: () => screenshot_default
31
+ default: () => screenshot_default,
32
+ scaleImageToFitMessage: () => scaleImageToFitMessage
32
33
  });
33
34
  module.exports = __toCommonJS(screenshot_exports);
34
- var import_bundle = require("../../sdk/bundle");
35
+ var import_fs = __toESM(require("fs"));
36
+ var import_utils = require("playwright-core/lib/utils");
37
+ var import_utilsBundle = require("playwright-core/lib/utilsBundle");
38
+ var import_utils2 = require("playwright-core/lib/utils");
39
+ var import_mcpBundle = require("../../../mcpBundleImpl");
35
40
  var import_tool = require("./tool");
36
- var javascript = __toESM(require("../codegen"));
37
- var import_utils = require("./utils");
41
+ var import_utils3 = require("./utils");
38
42
  var import_common = require("./common");
39
43
  const screenshotSchema = import_common.baseSchema.extend({
40
- type: import_bundle.z.enum(["png", "jpeg"]).default("png").describe("Image format for the screenshot. Default is png."),
41
- filename: import_bundle.z.string().optional().describe("File name to save the screenshot to. Defaults to `page-{timestamp}.{png|jpeg}` if not specified."),
42
- element: import_bundle.z.string().optional().describe("Human-readable element description used to obtain permission to screenshot the element. If not provided, the screenshot will be taken of viewport. If element is provided, ref must be provided too."),
43
- ref: import_bundle.z.string().optional().describe("Exact target element reference from the page snapshot. If not provided, the screenshot will be taken of viewport. If ref is provided, element must be provided too."),
44
- fullPage: import_bundle.z.boolean().optional().describe("When true, takes a screenshot of the full scrollable page, instead of the currently visible viewport. Cannot be used with element screenshots.")
45
- }).refine((data) => {
46
- return !!data.element === !!data.ref;
47
- }, {
48
- message: "Both element and ref must be provided or neither.",
49
- path: ["ref", "element"]
50
- }).refine((data) => {
51
- return !(data.fullPage && (data.element || data.ref));
52
- }, {
53
- message: "fullPage cannot be used with element screenshots.",
54
- path: ["fullPage"]
44
+ type: import_mcpBundle.z.enum(["png", "jpeg"]).default("png").describe("Image format for the screenshot. Default is png."),
45
+ filename: import_mcpBundle.z.string().optional().describe("File name to save the screenshot to. Defaults to `page-{timestamp}.{png|jpeg}` if not specified. Prefer relative file names to stay within the output directory."),
46
+ element: import_mcpBundle.z.string().optional().describe("Human-readable element description used to obtain permission to screenshot the element. If not provided, the screenshot will be taken of viewport. If element is provided, ref must be provided too."),
47
+ ref: import_mcpBundle.z.string().optional().describe("Exact target element reference from the page snapshot. If not provided, the screenshot will be taken of viewport. If ref is provided, element must be provided too."),
48
+ fullPage: import_mcpBundle.z.boolean().optional().describe("When true, takes a screenshot of the full scrollable page, instead of the currently visible viewport. Cannot be used with element screenshots.")
55
49
  });
56
50
  const screenshot = (0, import_tool.defineTabTool)({
57
51
  capability: "core",
@@ -63,33 +57,50 @@ const screenshot = (0, import_tool.defineTabTool)({
63
57
  type: "readOnly"
64
58
  },
65
59
  handle: async (tab, params, response) => {
60
+ if (!!params.element !== !!params.ref)
61
+ throw new Error("Both element and ref must be provided or neither.");
62
+ if (params.fullPage && params.ref)
63
+ throw new Error("fullPage cannot be used with element screenshots.");
66
64
  const fileType = params.type || "png";
67
- const fileName = await tab.context.outputFile(params.filename ?? `page-${(0, import_utils.dateAsFileName)()}.${fileType}`, { origin: "llm" });
68
65
  const options = {
69
66
  type: fileType,
70
67
  quality: fileType === "png" ? void 0 : 90,
71
68
  scale: "css",
72
- path: fileName,
73
69
  ...params.fullPage !== void 0 && { fullPage: params.fullPage }
74
70
  };
75
71
  const isElementScreenshot = params.element && params.ref;
76
72
  const screenshotTarget = isElementScreenshot ? params.element : params.fullPage ? "full page" : "viewport";
73
+ const fileName = await response.addFile(params.filename || (0, import_utils3.dateAsFileName)(fileType), { origin: "llm", reason: `Screenshot of ${screenshotTarget}` });
77
74
  response.addCode(`// Screenshot ${screenshotTarget} and save it as ${fileName}`);
78
- const locator = params.ref ? await tab.refLocator({ element: params.element || "", ref: params.ref }) : null;
79
- if (locator)
80
- response.addCode(`await page.${await (0, import_utils.generateLocator)(locator)}.screenshot(${javascript.formatObject(options)});`);
75
+ const ref = params.ref ? await tab.refLocator({ element: params.element || "", ref: params.ref }) : null;
76
+ if (ref)
77
+ response.addCode(`await page.${ref.resolved}.screenshot(${(0, import_utils2.formatObject)(options)});`);
81
78
  else
82
- response.addCode(`await page.screenshot(${javascript.formatObject(options)});`);
83
- const buffer = locator ? await locator.screenshot(options) : await tab.page.screenshot(options);
84
- response.addResult(`Took the ${screenshotTarget} screenshot and saved it as ${fileName}`);
85
- if (!params.fullPage) {
86
- response.addImage({
87
- contentType: fileType === "png" ? "image/png" : "image/jpeg",
88
- data: buffer
89
- });
90
- }
79
+ response.addCode(`await page.screenshot(${(0, import_utils2.formatObject)(options)});`);
80
+ const buffer = ref ? await ref.locator.screenshot(options) : await tab.page.screenshot(options);
81
+ await (0, import_utils.mkdirIfNeeded)(fileName);
82
+ await import_fs.default.promises.writeFile(fileName, buffer);
83
+ response.addImage({
84
+ contentType: fileType === "png" ? "image/png" : "image/jpeg",
85
+ data: scaleImageToFitMessage(buffer, fileType)
86
+ });
91
87
  }
92
88
  });
89
+ function scaleImageToFitMessage(buffer, imageType) {
90
+ const image = imageType === "png" ? import_utilsBundle.PNG.sync.read(buffer) : import_utilsBundle.jpegjs.decode(buffer, { maxMemoryUsageInMB: 512 });
91
+ const pixels = image.width * image.height;
92
+ const shrink = Math.min(1568 / image.width, 1568 / image.height, Math.sqrt(1.15 * 1024 * 1024 / pixels));
93
+ if (shrink > 1)
94
+ return buffer;
95
+ const width = image.width * shrink | 0;
96
+ const height = image.height * shrink | 0;
97
+ const scaledImage = (0, import_utils.scaleImageToSize)(image, { width, height });
98
+ return imageType === "png" ? import_utilsBundle.PNG.sync.write(scaledImage) : import_utilsBundle.jpegjs.encode(scaledImage, 80).data;
99
+ }
93
100
  var screenshot_default = [
94
101
  screenshot
95
102
  ];
103
+ // Annotate the CommonJS export names for ESM import in node:
104
+ 0 && (module.exports = {
105
+ scaleImageToFitMessage
106
+ });
@@ -32,10 +32,10 @@ __export(snapshot_exports, {
32
32
  elementSchema: () => elementSchema
33
33
  });
34
34
  module.exports = __toCommonJS(snapshot_exports);
35
- var import_bundle = require("../../sdk/bundle");
35
+ var import_fs = __toESM(require("fs"));
36
+ var import_mcpBundle = require("../../../mcpBundleImpl");
37
+ var import_utils = require("playwright-core/lib/utils");
36
38
  var import_tool = require("./tool");
37
- var javascript = __toESM(require("../codegen"));
38
- var import_utils = require("./utils");
39
39
  var import_common = require("./common");
40
40
  const snapshot = (0, import_tool.defineTool)({
41
41
  capability: "core",
@@ -43,22 +43,31 @@ const snapshot = (0, import_tool.defineTool)({
43
43
  name: "browser_snapshot",
44
44
  title: "Page snapshot",
45
45
  description: "Capture accessibility snapshot of the current page, this is better than screenshot",
46
- inputSchema: import_bundle.z.object({}),
46
+ inputSchema: import_mcpBundle.z.object({
47
+ filename: import_mcpBundle.z.string().optional().describe("Save snapshot to markdown file instead of returning it in the response.")
48
+ }),
47
49
  type: "readOnly"
48
50
  },
49
51
  handle: async (context, params, response) => {
50
52
  await context.ensureTab();
51
- response.setIncludeSnapshot("full");
53
+ response.setIncludeFullSnapshot();
54
+ if (params.filename) {
55
+ await response.finish();
56
+ const renderedResponse = response.render();
57
+ const fileName = await response.addFile(params.filename, { origin: "llm", reason: "Saved snapshot" });
58
+ await import_fs.default.promises.writeFile(fileName, renderedResponse.asText());
59
+ response.setIncludeMetaOnly();
60
+ }
52
61
  }
53
62
  });
54
63
  const elementSchema = import_common.baseSchema.extend({
55
- element: import_bundle.z.string().describe("Human-readable element description used to obtain permission to interact with the element"),
56
- ref: import_bundle.z.string().describe("Exact target element reference from the page snapshot")
64
+ element: import_mcpBundle.z.string().describe("Human-readable element description used to obtain permission to interact with the element"),
65
+ ref: import_mcpBundle.z.string().describe("Exact target element reference from the page snapshot")
57
66
  });
58
67
  const clickSchema = elementSchema.extend({
59
- doubleClick: import_bundle.z.boolean().optional().describe("Whether to perform a double click instead of a single click"),
60
- button: import_bundle.z.enum(["left", "right", "middle"]).optional().describe("Button to click, defaults to left"),
61
- modifiers: import_bundle.z.array(import_bundle.z.enum(["Alt", "Control", "ControlOrMeta", "Meta", "Shift"])).optional().describe("Modifier keys to press")
68
+ doubleClick: import_mcpBundle.z.boolean().optional().describe("Whether to perform a double click instead of a single click"),
69
+ button: import_mcpBundle.z.enum(["left", "right", "middle"]).optional().describe("Button to click, defaults to left"),
70
+ modifiers: import_mcpBundle.z.array(import_mcpBundle.z.enum(["Alt", "Control", "ControlOrMeta", "Meta", "Shift"])).optional().describe("Modifier keys to press")
62
71
  });
63
72
  const click = (0, import_tool.defineTabTool)({
64
73
  capability: "core",
@@ -67,21 +76,21 @@ const click = (0, import_tool.defineTabTool)({
67
76
  title: "Click",
68
77
  description: "Perform click on a web page",
69
78
  inputSchema: clickSchema,
70
- type: "destructive"
79
+ type: "input"
71
80
  },
72
81
  handle: async (tab, params, response) => {
73
82
  response.setIncludeSnapshot();
74
- const locator = await tab.refLocator(params);
83
+ const { locator, resolved } = await tab.refLocator(params);
75
84
  const options = {
76
85
  button: params.button,
77
86
  modifiers: params.modifiers
78
87
  };
79
- const formatted = javascript.formatObject(options, " ", "oneline");
88
+ const formatted = (0, import_utils.formatObject)(options, " ", "oneline");
80
89
  const optionsAttr = formatted !== "{}" ? formatted : "";
81
90
  if (params.doubleClick)
82
- response.addCode(`await page.${await (0, import_utils.generateLocator)(locator)}.dblclick(${optionsAttr});`);
91
+ response.addCode(`await page.${resolved}.dblclick(${optionsAttr});`);
83
92
  else
84
- response.addCode(`await page.${await (0, import_utils.generateLocator)(locator)}.click(${optionsAttr});`);
93
+ response.addCode(`await page.${resolved}.click(${optionsAttr});`);
85
94
  await tab.waitForCompletion(async () => {
86
95
  if (params.doubleClick)
87
96
  await locator.dblclick(options);
@@ -96,24 +105,24 @@ const drag = (0, import_tool.defineTabTool)({
96
105
  name: "browser_drag",
97
106
  title: "Drag mouse",
98
107
  description: "Perform drag and drop between two elements",
99
- inputSchema: import_bundle.z.object({
100
- startElement: import_bundle.z.string().describe("Human-readable source element description used to obtain the permission to interact with the element"),
101
- startRef: import_bundle.z.string().describe("Exact source element reference from the page snapshot"),
102
- endElement: import_bundle.z.string().describe("Human-readable target element description used to obtain the permission to interact with the element"),
103
- endRef: import_bundle.z.string().describe("Exact target element reference from the page snapshot")
108
+ inputSchema: import_mcpBundle.z.object({
109
+ startElement: import_mcpBundle.z.string().describe("Human-readable source element description used to obtain the permission to interact with the element"),
110
+ startRef: import_mcpBundle.z.string().describe("Exact source element reference from the page snapshot"),
111
+ endElement: import_mcpBundle.z.string().describe("Human-readable target element description used to obtain the permission to interact with the element"),
112
+ endRef: import_mcpBundle.z.string().describe("Exact target element reference from the page snapshot")
104
113
  }),
105
- type: "destructive"
114
+ type: "input"
106
115
  },
107
116
  handle: async (tab, params, response) => {
108
117
  response.setIncludeSnapshot();
109
- const [startLocator, endLocator] = await tab.refLocators([
118
+ const [start, end] = await tab.refLocators([
110
119
  { ref: params.startRef, element: params.startElement },
111
120
  { ref: params.endRef, element: params.endElement }
112
121
  ]);
113
122
  await tab.waitForCompletion(async () => {
114
- await startLocator.dragTo(endLocator);
123
+ await start.locator.dragTo(end.locator);
115
124
  });
116
- response.addCode(`await page.${await (0, import_utils.generateLocator)(startLocator)}.dragTo(page.${await (0, import_utils.generateLocator)(endLocator)});`);
125
+ response.addCode(`await page.${start.resolved}.dragTo(page.${end.resolved});`);
117
126
  }
118
127
  });
119
128
  const hover = (0, import_tool.defineTabTool)({
@@ -123,19 +132,19 @@ const hover = (0, import_tool.defineTabTool)({
123
132
  title: "Hover mouse",
124
133
  description: "Hover over element on page",
125
134
  inputSchema: elementSchema,
126
- type: "readOnly"
135
+ type: "input"
127
136
  },
128
137
  handle: async (tab, params, response) => {
129
138
  response.setIncludeSnapshot();
130
- const locator = await tab.refLocator(params);
131
- response.addCode(`await page.${await (0, import_utils.generateLocator)(locator)}.hover();`);
139
+ const { locator, resolved } = await tab.refLocator(params);
140
+ response.addCode(`await page.${resolved}.hover();`);
132
141
  await tab.waitForCompletion(async () => {
133
142
  await locator.hover();
134
143
  });
135
144
  }
136
145
  });
137
146
  const selectOptionSchema = elementSchema.extend({
138
- values: import_bundle.z.array(import_bundle.z.string()).describe("Array of values to select in the dropdown. This can be a single value or multiple values.")
147
+ values: import_mcpBundle.z.array(import_mcpBundle.z.string()).describe("Array of values to select in the dropdown. This can be a single value or multiple values.")
139
148
  });
140
149
  const selectOption = (0, import_tool.defineTabTool)({
141
150
  capability: "core",
@@ -144,12 +153,12 @@ const selectOption = (0, import_tool.defineTabTool)({
144
153
  title: "Select option",
145
154
  description: "Select an option in a dropdown",
146
155
  inputSchema: selectOptionSchema,
147
- type: "destructive"
156
+ type: "input"
148
157
  },
149
158
  handle: async (tab, params, response) => {
150
159
  response.setIncludeSnapshot();
151
- const locator = await tab.refLocator(params);
152
- response.addCode(`await page.${await (0, import_utils.generateLocator)(locator)}.selectOption(${javascript.formatObject(params.values)});`);
160
+ const { locator, resolved } = await tab.refLocator(params);
161
+ response.addCode(`await page.${resolved}.selectOption(${(0, import_utils.formatObject)(params.values)});`);
153
162
  await tab.waitForCompletion(async () => {
154
163
  await locator.selectOption(params.values);
155
164
  });
@@ -165,8 +174,8 @@ const pickLocator = (0, import_tool.defineTabTool)({
165
174
  type: "readOnly"
166
175
  },
167
176
  handle: async (tab, params, response) => {
168
- const locator = await tab.refLocator(params);
169
- response.addResult(await (0, import_utils.generateLocator)(locator));
177
+ const { resolved } = await tab.refLocator(params);
178
+ response.addResult(resolved);
170
179
  }
171
180
  });
172
181
  var snapshot_default = [