playwright-core 1.58.0-alpha-2025-12-10 → 1.58.0-alpha-2025-12-12

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 (39) hide show
  1. package/ThirdPartyNotices.txt +3 -3
  2. package/browsers.json +2 -2
  3. package/lib/client/browser.js +3 -5
  4. package/lib/client/browserType.js +2 -2
  5. package/lib/client/fetch.js +2 -4
  6. package/lib/client/page.js +4 -3
  7. package/lib/mcpBundleImpl/index.js +29 -29
  8. package/lib/protocol/serializers.js +5 -0
  9. package/lib/protocol/validator.js +26 -8
  10. package/lib/server/agent/actionRunner.js +33 -2
  11. package/lib/server/agent/agent.js +28 -12
  12. package/lib/server/agent/backend.js +2 -5
  13. package/lib/server/agent/codegen.js +83 -0
  14. package/lib/server/agent/context.js +43 -11
  15. package/lib/server/agent/tools.js +67 -5
  16. package/lib/server/artifact.js +1 -1
  17. package/lib/server/chromium/crPage.js +5 -5
  18. package/lib/server/codegen/javascript.js +6 -29
  19. package/lib/server/dispatchers/dispatcher.js +5 -8
  20. package/lib/server/dispatchers/pageDispatcher.js +3 -2
  21. package/lib/server/firefox/ffBrowser.js +1 -1
  22. package/lib/server/firefox/ffPage.js +1 -1
  23. package/lib/server/instrumentation.js +3 -0
  24. package/lib/server/page.js +2 -2
  25. package/lib/server/progress.js +2 -0
  26. package/lib/server/screencast.js +24 -25
  27. package/lib/server/videoRecorder.js +20 -11
  28. package/lib/server/webkit/wkBrowser.js +1 -1
  29. package/lib/server/webkit/wkPage.js +7 -7
  30. package/lib/utils/isomorphic/stringUtils.js +29 -0
  31. package/lib/vite/htmlReport/index.html +1 -1
  32. package/lib/vite/traceViewer/index.BVu7tZDe.css +1 -0
  33. package/lib/vite/traceViewer/index.html +2 -2
  34. package/lib/vite/traceViewer/index.zFV_GQE-.js +2 -0
  35. package/lib/vite/traceViewer/sw.bundle.js +3 -1
  36. package/package.json +1 -1
  37. package/types/types.d.ts +27 -2
  38. package/lib/vite/traceViewer/index.C4Y3Aw8n.css +0 -1
  39. package/lib/vite/traceViewer/index.YskCIlQ-.js +0 -2
@@ -19,6 +19,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  var serializers_exports = {};
20
20
  __export(serializers_exports, {
21
21
  parseSerializedValue: () => parseSerializedValue,
22
+ serializePlainValue: () => serializePlainValue,
22
23
  serializeValue: () => serializeValue
23
24
  });
24
25
  module.exports = __toCommonJS(serializers_exports);
@@ -90,6 +91,9 @@ function innerParseSerializedValue(value, handles, refs, accessChain) {
90
91
  function serializeValue(value, handleSerializer) {
91
92
  return innerSerializeValue(value, handleSerializer, { lastId: 0, visited: /* @__PURE__ */ new Map() }, []);
92
93
  }
94
+ function serializePlainValue(arg) {
95
+ return serializeValue(arg, (value) => ({ fallThrough: value }));
96
+ }
93
97
  function innerSerializeValue(value, handleSerializer, visitorInfo, accessChain) {
94
98
  const handle = handleSerializer(value);
95
99
  if ("fallThrough" in handle)
@@ -188,5 +192,6 @@ const constructorToTypedArrayKind = new Map(Object.entries(typedArrayKindToConst
188
192
  // Annotate the CommonJS export names for ESM import in node:
189
193
  0 && (module.exports = {
190
194
  parseSerializedValue,
195
+ serializePlainValue,
191
196
  serializeValue
192
197
  });
@@ -613,7 +613,9 @@ import_validatorPrimitives.scheme.BrowserTypeLaunchPersistentContextParams = (0,
613
613
  model: import_validatorPrimitives.tString,
614
614
  cacheFile: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tString),
615
615
  cacheMode: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tEnum)(["ignore", "force", "auto"])),
616
- secrets: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tArray)((0, import_validatorPrimitives.tType)("NameValue")))
616
+ secrets: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tArray)((0, import_validatorPrimitives.tType)("NameValue"))),
617
+ maxTurns: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt),
618
+ maxTokens: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt)
617
619
  })),
618
620
  userDataDir: import_validatorPrimitives.tString,
619
621
  slowMo: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tFloat)
@@ -712,7 +714,9 @@ import_validatorPrimitives.scheme.BrowserNewContextParams = (0, import_validator
712
714
  model: import_validatorPrimitives.tString,
713
715
  cacheFile: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tString),
714
716
  cacheMode: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tEnum)(["ignore", "force", "auto"])),
715
- secrets: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tArray)((0, import_validatorPrimitives.tType)("NameValue")))
717
+ secrets: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tArray)((0, import_validatorPrimitives.tType)("NameValue"))),
718
+ maxTurns: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt),
719
+ maxTokens: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt)
716
720
  })),
717
721
  proxy: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tObject)({
718
722
  server: import_validatorPrimitives.tString,
@@ -790,7 +794,9 @@ import_validatorPrimitives.scheme.BrowserNewContextForReuseParams = (0, import_v
790
794
  model: import_validatorPrimitives.tString,
791
795
  cacheFile: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tString),
792
796
  cacheMode: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tEnum)(["ignore", "force", "auto"])),
793
- secrets: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tArray)((0, import_validatorPrimitives.tType)("NameValue")))
797
+ secrets: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tArray)((0, import_validatorPrimitives.tType)("NameValue"))),
798
+ maxTurns: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt),
799
+ maxTokens: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt)
794
800
  })),
795
801
  proxy: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tObject)({
796
802
  server: import_validatorPrimitives.tString,
@@ -913,7 +919,9 @@ import_validatorPrimitives.scheme.BrowserContextInitializer = (0, import_validat
913
919
  model: import_validatorPrimitives.tString,
914
920
  cacheFile: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tString),
915
921
  cacheMode: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tEnum)(["ignore", "force", "auto"])),
916
- secrets: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tArray)((0, import_validatorPrimitives.tType)("NameValue")))
922
+ secrets: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tArray)((0, import_validatorPrimitives.tType)("NameValue"))),
923
+ maxTurns: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt),
924
+ maxTokens: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt)
917
925
  }))
918
926
  })
919
927
  });
@@ -1519,16 +1527,24 @@ import_validatorPrimitives.scheme.PageUpdateSubscriptionResult = (0, import_vali
1519
1527
  import_validatorPrimitives.scheme.PagePerformParams = (0, import_validatorPrimitives.tObject)({
1520
1528
  task: import_validatorPrimitives.tString,
1521
1529
  key: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tString),
1522
- maxTurns: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt)
1530
+ maxTurns: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt),
1531
+ maxTokens: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt)
1532
+ });
1533
+ import_validatorPrimitives.scheme.PagePerformResult = (0, import_validatorPrimitives.tObject)({
1534
+ turns: import_validatorPrimitives.tInt,
1535
+ inputTokens: import_validatorPrimitives.tInt,
1536
+ outputTokens: import_validatorPrimitives.tInt
1523
1537
  });
1524
- import_validatorPrimitives.scheme.PagePerformResult = (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tObject)({}));
1525
1538
  import_validatorPrimitives.scheme.PageExtractParams = (0, import_validatorPrimitives.tObject)({
1526
1539
  query: import_validatorPrimitives.tString,
1527
1540
  schema: import_validatorPrimitives.tAny,
1528
1541
  maxTurns: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt)
1529
1542
  });
1530
1543
  import_validatorPrimitives.scheme.PageExtractResult = (0, import_validatorPrimitives.tObject)({
1531
- result: import_validatorPrimitives.tAny
1544
+ result: import_validatorPrimitives.tAny,
1545
+ turns: import_validatorPrimitives.tInt,
1546
+ inputTokens: import_validatorPrimitives.tInt,
1547
+ outputTokens: import_validatorPrimitives.tInt
1532
1548
  });
1533
1549
  import_validatorPrimitives.scheme.FrameInitializer = (0, import_validatorPrimitives.tObject)({
1534
1550
  url: import_validatorPrimitives.tString,
@@ -2823,7 +2839,9 @@ import_validatorPrimitives.scheme.AndroidDeviceLaunchBrowserParams = (0, import_
2823
2839
  model: import_validatorPrimitives.tString,
2824
2840
  cacheFile: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tString),
2825
2841
  cacheMode: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tEnum)(["ignore", "force", "auto"])),
2826
- secrets: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tArray)((0, import_validatorPrimitives.tType)("NameValue")))
2842
+ secrets: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tArray)((0, import_validatorPrimitives.tType)("NameValue"))),
2843
+ maxTurns: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt),
2844
+ maxTokens: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt)
2827
2845
  })),
2828
2846
  pkg: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tString),
2829
2847
  args: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tArray)(import_validatorPrimitives.tString)),
@@ -18,9 +18,12 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var actionRunner_exports = {};
20
20
  __export(actionRunner_exports, {
21
- runAction: () => runAction
21
+ runAction: () => runAction,
22
+ serializeArgument: () => serializeArgument
22
23
  });
23
24
  module.exports = __toCommonJS(actionRunner_exports);
25
+ var import_expectUtils = require("../utils/expectUtils");
26
+ var import_serializers = require("../../protocol/serializers");
24
27
  async function runAction(progress, page, action, secrets) {
25
28
  const frame = page.mainFrame();
26
29
  switch (action.method) {
@@ -59,10 +62,38 @@ async function runAction(progress, page, action, secrets) {
59
62
  else
60
63
  await frame.uncheck(progress, action.selector, { ...strictTrue });
61
64
  break;
65
+ case "expectVisible": {
66
+ const result = await frame.expect(progress, action.selector, { expression: "to.be.visible", isNot: false }, 5e3);
67
+ if (result.errorMessage)
68
+ throw new Error(result.errorMessage);
69
+ break;
70
+ }
71
+ case "expectValue": {
72
+ let result;
73
+ if (action.type === "textbox" || action.type === "combobox" || action.type === "slider") {
74
+ const expectedText = (0, import_expectUtils.serializeExpectedTextValues)([action.value]);
75
+ result = await frame.expect(progress, action.selector, { expression: "to.have.value", expectedText, isNot: false }, 5e3);
76
+ } else if (action.type === "checkbox" || action.type === "radio") {
77
+ const expectedValue = serializeArgument({ checked: true });
78
+ result = await frame.expect(progress, action.selector, { expression: "to.be.checked", expectedValue, isNot: false }, 5e3);
79
+ } else {
80
+ throw new Error(`Unsupported element type: ${action.type}`);
81
+ }
82
+ if (result.errorMessage)
83
+ throw new Error(result.errorMessage);
84
+ break;
85
+ }
62
86
  }
63
87
  }
88
+ function serializeArgument(arg) {
89
+ return {
90
+ value: (0, import_serializers.serializePlainValue)(arg),
91
+ handles: []
92
+ };
93
+ }
64
94
  const strictTrue = { strict: true };
65
95
  // Annotate the CommonJS export names for ESM import in node:
66
96
  0 && (module.exports = {
67
- runAction
97
+ runAction,
98
+ serializeArgument
68
99
  });
@@ -41,11 +41,10 @@ var import_context = require("./context");
41
41
  async function pagePerform(progress, page, options) {
42
42
  const context = new import_context.Context(progress, page);
43
43
  if (await cachedPerform(context, options))
44
- return;
45
- await perform(context, options.task, (0, import_mcpBundle.zodToJsonSchema)(import_mcpBundle.z.object({
46
- error: import_mcpBundle.z.string().optional().describe("An error message if the task could not be completed successfully")
47
- })), options);
44
+ return { turns: 0, inputTokens: 0, outputTokens: 0 };
45
+ const { usage } = await perform(context, options.task, void 0, options);
48
46
  await updateCache(context, options);
47
+ return usage;
49
48
  }
50
49
  async function pageExtract(progress, page, options) {
51
50
  const context = new import_context.Context(progress, page);
@@ -55,7 +54,8 @@ Extract the following information from the page. Do not perform any actions, jus
55
54
 
56
55
  ### Query
57
56
  ${options.query}`;
58
- return await perform(context, task, options.schema, options);
57
+ const { result, usage } = await perform(context, task, options.schema, options);
58
+ return { result, usage };
59
59
  }
60
60
  async function perform(context, userTask, resultSchema, options = {}) {
61
61
  const { progress, page } = context;
@@ -64,12 +64,23 @@ async function perform(context, userTask, resultSchema, options = {}) {
64
64
  throw new Error(`page.perform() and page.extract() require the agent to be set on the browser context`);
65
65
  const { full } = await page.snapshotForAI(progress);
66
66
  const { tools, callTool } = (0, import_backend.toolsForLoop)(context);
67
+ const limits = context.limits(options);
68
+ let turns = 0;
67
69
  const loop = new import_mcpBundle.Loop(browserContext._options.agent.provider, {
68
70
  model: browserContext._options.agent.model,
69
71
  summarize: true,
70
72
  debug: import_utilsBundle.debug,
71
73
  callTool,
72
74
  tools,
75
+ ...limits,
76
+ beforeTurn: (params) => {
77
+ ++turns;
78
+ const lastReply = params.conversation.messages.findLast((m) => m.role === "assistant");
79
+ const toolCall = lastReply?.content.find((c) => c.type === "tool_call");
80
+ if (!resultSchema && toolCall && toolCall.arguments.thatShouldBeIt)
81
+ return "break";
82
+ return "continue";
83
+ },
73
84
  ...options
74
85
  });
75
86
  const task = `${userTask}
@@ -77,16 +88,22 @@ async function perform(context, userTask, resultSchema, options = {}) {
77
88
  ### Page snapshot
78
89
  ${full}
79
90
  `;
80
- return await loop.run(task, {
81
- resultSchema
82
- });
91
+ const { result, usage } = await loop.run(task, { resultSchema });
92
+ return {
93
+ result,
94
+ usage: {
95
+ turns,
96
+ inputTokens: usage.input,
97
+ outputTokens: usage.output
98
+ }
99
+ };
83
100
  }
84
101
  const allCaches = /* @__PURE__ */ new Map();
85
102
  async function cachedPerform(context, options) {
86
103
  if (!context.options?.cacheFile || context.options.cacheMode === "ignore")
87
104
  return false;
88
105
  const cache = await cachedActions(context.options.cacheFile);
89
- const cacheKey = options.key ?? options.task;
106
+ const cacheKey = (options.key ?? options.task).trim();
90
107
  const entry = cache[cacheKey];
91
108
  if (!entry) {
92
109
  if (context.options.cacheMode === "force")
@@ -102,7 +119,7 @@ async function updateCache(context, options) {
102
119
  if (!cacheFile)
103
120
  return;
104
121
  const cache = await cachedActions(cacheFile);
105
- const cacheKey = options.key ?? options.task;
122
+ const cacheKey = (options.key ?? options.task).trim();
106
123
  cache[cacheKey] = {
107
124
  timestamp: Date.now(),
108
125
  actions: context.actions
@@ -112,8 +129,7 @@ async function updateCache(context, options) {
112
129
  async function cachedActions(cacheFile) {
113
130
  let cache = allCaches.get(cacheFile);
114
131
  if (!cache) {
115
- const text = await import_fs.default.promises.readFile(cacheFile, "utf-8").catch(() => "{}");
116
- cache = JSON.parse(text);
132
+ cache = await import_fs.default.promises.readFile(cacheFile, "utf-8").then((text) => JSON.parse(text)).catch(() => ({}));
117
133
  allCaches.set(cacheFile, cache);
118
134
  }
119
135
  return cache;
@@ -32,7 +32,6 @@ __export(backend_exports, {
32
32
  });
33
33
  module.exports = __toCommonJS(backend_exports);
34
34
  var import_tools = __toESM(require("./tools"));
35
- var import_progress = require("../progress");
36
35
  var import_mcpBundle = require("../../mcpBundle");
37
36
  function toolsForLoop(context) {
38
37
  const tools = import_tools.default.map((tool) => {
@@ -44,6 +43,7 @@ function toolsForLoop(context) {
44
43
  return result;
45
44
  });
46
45
  const callTool = async (params) => {
46
+ const intent = params.arguments._meta?.["dev.lowire/intent"];
47
47
  const tool = import_tools.default.find((t) => t.schema.name === params.name);
48
48
  if (!tool) {
49
49
  return {
@@ -54,11 +54,8 @@ function toolsForLoop(context) {
54
54
  isError: true
55
55
  };
56
56
  }
57
- const progressController = new import_progress.ProgressController();
58
57
  try {
59
- return await progressController.run(async (progress) => {
60
- return await tool.handle(context, params.arguments);
61
- });
58
+ return await context.callTool(tool, params.arguments, { intent });
62
59
  } catch (error) {
63
60
  return {
64
61
  content: [{ type: "text", text: error.message }],
@@ -0,0 +1,83 @@
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 codegen_exports = {};
20
+ __export(codegen_exports, {
21
+ generateCode: () => generateCode
22
+ });
23
+ module.exports = __toCommonJS(codegen_exports);
24
+ var import_locatorGenerators = require("../../utils/isomorphic/locatorGenerators");
25
+ var import_stringUtils = require("../../utils/isomorphic/stringUtils");
26
+ async function generateCode(sdkLanguage, action) {
27
+ switch (action.method) {
28
+ case "click": {
29
+ const locator = (0, import_locatorGenerators.asLocator)(sdkLanguage, action.selector);
30
+ return `await page.${locator}.click(${(0, import_stringUtils.formatObjectOrVoid)(action.options)});`;
31
+ }
32
+ case "drag": {
33
+ const sourceLocator = (0, import_locatorGenerators.asLocator)(sdkLanguage, action.sourceSelector);
34
+ const targetLocator = (0, import_locatorGenerators.asLocator)(sdkLanguage, action.targetSelector);
35
+ return `await page.${sourceLocator}.dragAndDrop(${targetLocator});`;
36
+ }
37
+ case "hover": {
38
+ const locator = (0, import_locatorGenerators.asLocator)(sdkLanguage, action.selector);
39
+ return `await page.${locator}.hover(${(0, import_stringUtils.formatObjectOrVoid)(action.options)});`;
40
+ }
41
+ case "pressKey": {
42
+ return `await page.keyboard.press(${(0, import_stringUtils.escapeWithQuotes)(action.key, "'")});`;
43
+ }
44
+ case "selectOption": {
45
+ const locator = (0, import_locatorGenerators.asLocator)(sdkLanguage, action.selector);
46
+ return `await page.${locator}.selectOption(${action.labels.length === 1 ? (0, import_stringUtils.escapeWithQuotes)(action.labels[0]) : "[" + action.labels.map((label) => (0, import_stringUtils.escapeWithQuotes)(label)).join(", ") + "]"});`;
47
+ }
48
+ case "pressSequentially": {
49
+ const locator = (0, import_locatorGenerators.asLocator)(sdkLanguage, action.selector);
50
+ const code = [`await page.${locator}.pressSequentially(${(0, import_stringUtils.escapeWithQuotes)(action.text)});`];
51
+ if (action.submit)
52
+ code.push(`await page.keyboard.press('Enter');`);
53
+ return code.join("\n");
54
+ }
55
+ case "fill": {
56
+ const locator = (0, import_locatorGenerators.asLocator)(sdkLanguage, action.selector);
57
+ const code = [`await page.${locator}.fill(${(0, import_stringUtils.escapeWithQuotes)(action.text)});`];
58
+ if (action.submit)
59
+ code.push(`await page.keyboard.press('Enter');`);
60
+ return code.join("\n");
61
+ }
62
+ case "setChecked": {
63
+ const locator = (0, import_locatorGenerators.asLocator)(sdkLanguage, action.selector);
64
+ if (action.checked)
65
+ return `await page.${locator}.check();`;
66
+ else
67
+ return `await page.${locator}.uncheck();`;
68
+ }
69
+ case "expectVisible": {
70
+ const locator = (0, import_locatorGenerators.asLocator)(sdkLanguage, action.selector);
71
+ return `await expect(page.${locator}).toBeVisible();`;
72
+ }
73
+ case "expectValue": {
74
+ const locator = (0, import_locatorGenerators.asLocator)(sdkLanguage, action.selector);
75
+ return `await expect(page.${locator}).toHaveValue(${(0, import_stringUtils.escapeWithQuotes)(action.value)});`;
76
+ }
77
+ }
78
+ throw new Error("Unknown action " + action.method);
79
+ }
80
+ // Annotate the CommonJS export names for ESM import in node:
81
+ 0 && (module.exports = {
82
+ generateCode
83
+ });
@@ -23,24 +23,39 @@ __export(context_exports, {
23
23
  module.exports = __toCommonJS(context_exports);
24
24
  var import_browserContext = require("../browserContext");
25
25
  var import_actionRunner = require("./actionRunner");
26
+ var import_codegen = require("./codegen");
26
27
  class Context {
27
28
  constructor(progress, page) {
28
29
  this.actions = [];
29
30
  this.progress = progress;
30
31
  this.page = page;
31
32
  this.options = page.browserContext._options.agent;
33
+ this.sdkLanguage = page.browserContext._browser.sdkLanguage();
34
+ }
35
+ async callTool(tool, params, options) {
36
+ this._callIntent = options.intent;
37
+ try {
38
+ return await tool.handle(this, params);
39
+ } finally {
40
+ this._callIntent = void 0;
41
+ }
32
42
  }
33
43
  async runActionAndWait(action) {
34
44
  return await this.runActionsAndWait([action]);
35
45
  }
36
46
  async runActionsAndWait(action) {
37
- await this.waitForCompletion(async () => {
38
- for (const a of action) {
39
- await (0, import_actionRunner.runAction)(this.progress, this.page, a, this.options?.secrets ?? []);
40
- this.actions.push(a);
41
- }
42
- });
43
- return await this.snapshotResult();
47
+ try {
48
+ await this.waitForCompletion(async () => {
49
+ for (const a of action) {
50
+ await (0, import_actionRunner.runAction)(this.progress, this.page, a, this.options?.secrets ?? []);
51
+ const code = await (0, import_codegen.generateCode)(this.sdkLanguage, a);
52
+ this.actions.push({ ...a, code, intent: this._callIntent });
53
+ }
54
+ });
55
+ return await this.snapshotResult();
56
+ } catch (e) {
57
+ return await this.snapshotResult(e);
58
+ }
44
59
  }
45
60
  async waitForCompletion(callback) {
46
61
  const requests = [];
@@ -73,17 +88,28 @@ class Context {
73
88
  await this.progress.wait(500);
74
89
  return result;
75
90
  }
76
- async snapshotResult() {
91
+ async snapshotResult(error) {
77
92
  let { full } = await this.page.snapshotForAI(this.progress);
78
93
  full = this._redactText(full);
79
- const text = [`# Page snapshot
80
- ${full}`];
94
+ const text = [];
95
+ if (error)
96
+ text.push(`# Error
97
+ ${error.message}`);
98
+ else
99
+ text.push(`# Success`);
100
+ text.push(`# Page snapshot
101
+ ${full}`);
81
102
  return {
82
103
  _meta: {
83
104
  "dev.lowire/state": {
84
105
  "Page snapshot": full
85
- }
106
+ },
107
+ "dev.lowire/history": error ? [{
108
+ category: "error",
109
+ content: error.message
110
+ }] : []
86
111
  },
112
+ isError: !!error,
87
113
  content: [{ type: "text", text: text.join("\n\n") }]
88
114
  };
89
115
  }
@@ -97,6 +123,12 @@ ${full}`];
97
123
  }
98
124
  }));
99
125
  }
126
+ limits(options = {}) {
127
+ return {
128
+ maxTurns: options.maxTurns ?? this.options?.maxTurns ?? 10,
129
+ maxTokens: options.maxTokens ?? this.options?.maxTokens ?? void 0
130
+ };
131
+ }
100
132
  _redactText(text) {
101
133
  const secrets = this.options?.secrets;
102
134
  if (!secrets)
@@ -22,21 +22,25 @@ __export(tools_exports, {
22
22
  });
23
23
  module.exports = __toCommonJS(tools_exports);
24
24
  var import_mcpBundle = require("../../mcpBundle");
25
+ var import_locatorUtils = require("../../utils/isomorphic/locatorUtils");
25
26
  function defineTool(tool) {
26
27
  return tool;
27
28
  }
29
+ const baseSchema = import_mcpBundle.z.object({
30
+ thatShouldBeIt: import_mcpBundle.z.boolean().describe("Indicates that this tool call is sufficient to complete the task. If false, the task will continue with the next tool call")
31
+ });
28
32
  const snapshot = defineTool({
29
33
  schema: {
30
34
  name: "browser_snapshot",
31
35
  title: "Page snapshot",
32
36
  description: "Capture accessibility snapshot of the current page, this is better than screenshot",
33
- inputSchema: import_mcpBundle.z.object({})
37
+ inputSchema: baseSchema
34
38
  },
35
39
  handle: async (context, params) => {
36
40
  return await context.snapshotResult();
37
41
  }
38
42
  });
39
- const elementSchema = import_mcpBundle.z.object({
43
+ const elementSchema = baseSchema.extend({
40
44
  element: import_mcpBundle.z.string().describe("Human-readable element description used to obtain permission to interact with the element"),
41
45
  ref: import_mcpBundle.z.string().describe("Exact target element reference from the page snapshot")
42
46
  });
@@ -70,7 +74,7 @@ const drag = defineTool({
70
74
  name: "browser_drag",
71
75
  title: "Drag mouse",
72
76
  description: "Perform drag and drop between two elements",
73
- inputSchema: import_mcpBundle.z.object({
77
+ inputSchema: baseSchema.extend({
74
78
  startElement: import_mcpBundle.z.string().describe("Human-readable source element description used to obtain the permission to interact with the element"),
75
79
  startRef: import_mcpBundle.z.string().describe("Exact source element reference from the page snapshot"),
76
80
  endElement: import_mcpBundle.z.string().describe("Human-readable target element description used to obtain the permission to interact with the element"),
@@ -181,7 +185,7 @@ const fillForm = defineTool({
181
185
  name: "browser_fill_form",
182
186
  title: "Fill form",
183
187
  description: "Fill multiple form fields",
184
- inputSchema: import_mcpBundle.z.object({
188
+ inputSchema: baseSchema.extend({
185
189
  fields: import_mcpBundle.z.array(import_mcpBundle.z.object({
186
190
  name: import_mcpBundle.z.string().describe("Human-readable field name"),
187
191
  type: import_mcpBundle.z.enum(["textbox", "checkbox", "radio", "combobox", "slider"]).describe("Type of the field"),
@@ -217,6 +221,61 @@ const fillForm = defineTool({
217
221
  return await context.runActionsAndWait(actions);
218
222
  }
219
223
  });
224
+ const expectVisible = defineTool({
225
+ schema: {
226
+ name: "browser_expect_visible",
227
+ title: "Expect element visible",
228
+ description: "Expect element is visible on the page",
229
+ inputSchema: baseSchema.extend({
230
+ role: import_mcpBundle.z.string().describe('ROLE of the element. Can be found in the snapshot like this: `- {ROLE} "Accessible Name":`'),
231
+ accessibleName: import_mcpBundle.z.string().describe('ACCESSIBLE_NAME of the element. Can be found in the snapshot like this: `- role "{ACCESSIBLE_NAME}"`')
232
+ })
233
+ },
234
+ handle: async (context, params) => {
235
+ return await context.runActionAndWait({
236
+ method: "expectVisible",
237
+ selector: (0, import_locatorUtils.getByRoleSelector)(params.role, { name: params.accessibleName })
238
+ });
239
+ }
240
+ });
241
+ const expectVisibleText = defineTool({
242
+ schema: {
243
+ name: "browser_expect_visible_text",
244
+ title: "Expect text visible",
245
+ description: `Expect text is visible on the page. Prefer ${expectVisible.schema.name} if possible.`,
246
+ inputSchema: baseSchema.extend({
247
+ text: import_mcpBundle.z.string().describe('TEXT to expect. Can be found in the snapshot like this: `- role "Accessible Name": {TEXT}` or like this: `- text: {TEXT}`')
248
+ })
249
+ },
250
+ handle: async (context, params) => {
251
+ return await context.runActionAndWait({
252
+ method: "expectVisible",
253
+ selector: (0, import_locatorUtils.getByTextSelector)(params.text)
254
+ });
255
+ }
256
+ });
257
+ const expectValue = defineTool({
258
+ schema: {
259
+ name: "browser_expect_value",
260
+ title: "Expect value",
261
+ description: "Expect element value",
262
+ inputSchema: baseSchema.extend({
263
+ type: import_mcpBundle.z.enum(["textbox", "checkbox", "radio", "combobox", "slider"]).describe("Type of the element"),
264
+ element: import_mcpBundle.z.string().describe("Human-readable element description"),
265
+ ref: import_mcpBundle.z.string().describe("Exact target element reference from the page snapshot"),
266
+ value: import_mcpBundle.z.string().describe('Value to expect. For checkbox, use "true" or "false".')
267
+ })
268
+ },
269
+ handle: async (context, params) => {
270
+ const [selector] = await context.refSelectors([{ ref: params.ref, element: params.element }]);
271
+ return await context.runActionAndWait({
272
+ method: "expectValue",
273
+ selector,
274
+ type: params.type,
275
+ value: params.value
276
+ });
277
+ }
278
+ });
220
279
  var tools_default = [
221
280
  snapshot,
222
281
  click,
@@ -225,5 +284,8 @@ var tools_default = [
225
284
  selectOption,
226
285
  pressKey,
227
286
  type,
228
- fillForm
287
+ fillForm,
288
+ expectVisible,
289
+ expectVisibleText,
290
+ expectValue
229
291
  ];
@@ -103,7 +103,7 @@ class Artifact extends import_instrumentation.SdkObject {
103
103
  if (!this._unaccessibleErrorMessage)
104
104
  await import_fs.default.promises.unlink(this._localPath).catch((e) => {
105
105
  });
106
- await this.reportFinished(new import_errors.TargetClosedError());
106
+ await this.reportFinished(new import_errors.TargetClosedError(this.closeReason()));
107
107
  }
108
108
  async reportFinished(error) {
109
109
  if (this._finished)
@@ -357,9 +357,9 @@ class FrameSession {
357
357
  const { windowId } = await this._client.send("Browser.getWindowForTarget");
358
358
  this._windowId = windowId;
359
359
  }
360
- let screencastOptions;
360
+ let videoOptions;
361
361
  if (!this._page.isStorageStatePage && this._isMainFrame() && hasUIWindow)
362
- screencastOptions = await this._crPage._page.screencast.initializeVideoRecorder();
362
+ videoOptions = this._crPage._page.screencast.launchVideoRecorder();
363
363
  let lifecycleEventsEnabled;
364
364
  if (!this._isMainFrame())
365
365
  this._addRendererListeners();
@@ -439,15 +439,15 @@ class FrameSession {
439
439
  true
440
440
  /* runImmediately */
441
441
  ));
442
- if (screencastOptions)
443
- promises.push(this._crPage._page.screencast.startVideoRecording(screencastOptions));
442
+ if (videoOptions)
443
+ promises.push(this._crPage._page.screencast.startVideoRecording(videoOptions));
444
444
  }
445
445
  promises.push(this._client.send("Runtime.runIfWaitingForDebugger"));
446
446
  promises.push(this._firstNonInitialNavigationCommittedPromise);
447
447
  await Promise.all(promises);
448
448
  }
449
449
  dispose() {
450
- this._firstNonInitialNavigationCommittedReject(new import_errors.TargetClosedError());
450
+ this._firstNonInitialNavigationCommittedReject(new import_errors.TargetClosedError(this._page.closeReason()));
451
451
  for (const childSession of this._childSessions)
452
452
  childSession.dispose();
453
453
  if (this._parentSession)