playwright-core 1.58.0-alpha-2025-12-15 → 1.58.0-alpha-2025-12-17
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.
- package/ThirdPartyNotices.txt +3 -3
- package/lib/client/events.js +1 -0
- package/lib/client/page.js +1 -0
- package/lib/generated/injectedScriptSource.js +1 -1
- package/lib/mcpBundleImpl/index.js +37 -37
- package/lib/protocol/validator.js +13 -5
- package/lib/server/agent/actionRunner.js +56 -16
- package/lib/server/agent/agent.js +38 -13
- package/lib/server/agent/codegen.js +2 -0
- package/lib/server/agent/context.js +17 -20
- package/lib/server/agent/tools.js +1 -1
- package/lib/server/bidi/bidiExecutionContext.js +72 -15
- package/lib/server/bidi/bidiPage.js +1 -1
- package/lib/server/dispatchers/pageDispatcher.js +1 -0
- package/lib/server/page.js +1 -0
- package/lib/server/progress.js +7 -3
- package/lib/server/trace/viewer/traceViewer.js +17 -13
- package/lib/utils/isomorphic/trace/traceModel.js +4 -4
- package/lib/vite/traceViewer/assets/{codeMirrorModule-CPNe-I5g.js → codeMirrorModule-DqqhreXd.js} +1 -1
- package/lib/vite/traceViewer/assets/{defaultSettingsView-V7hnXDpz.js → defaultSettingsView-B-IIHg_U.js} +55 -55
- package/lib/vite/traceViewer/{index.zFV_GQE-.js → index.CVDmenYz.js} +1 -1
- package/lib/vite/traceViewer/index.html +2 -2
- package/lib/vite/traceViewer/sw.bundle.js +4 -4
- package/lib/vite/traceViewer/uiMode.html +2 -2
- package/lib/vite/traceViewer/{uiMode.DiEbKRwa.js → uiMode.yq3WDGll.js} +3 -3
- package/package.json +1 -1
- package/types/protocol.d.ts +54 -54
- package/types/types.d.ts +126 -1
|
@@ -612,7 +612,7 @@ import_validatorPrimitives.scheme.BrowserTypeLaunchPersistentContextParams = (0,
|
|
|
612
612
|
provider: import_validatorPrimitives.tString,
|
|
613
613
|
model: import_validatorPrimitives.tString,
|
|
614
614
|
cacheFile: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tString),
|
|
615
|
-
cacheMode: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tEnum)(["ignore", "force", "auto"])),
|
|
615
|
+
cacheMode: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tEnum)(["ignore", "force", "update", "auto"])),
|
|
616
616
|
secrets: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tArray)((0, import_validatorPrimitives.tType)("NameValue"))),
|
|
617
617
|
maxTurns: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt),
|
|
618
618
|
maxTokens: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt)
|
|
@@ -713,7 +713,7 @@ import_validatorPrimitives.scheme.BrowserNewContextParams = (0, import_validator
|
|
|
713
713
|
provider: import_validatorPrimitives.tString,
|
|
714
714
|
model: import_validatorPrimitives.tString,
|
|
715
715
|
cacheFile: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tString),
|
|
716
|
-
cacheMode: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tEnum)(["ignore", "force", "auto"])),
|
|
716
|
+
cacheMode: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tEnum)(["ignore", "force", "update", "auto"])),
|
|
717
717
|
secrets: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tArray)((0, import_validatorPrimitives.tType)("NameValue"))),
|
|
718
718
|
maxTurns: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt),
|
|
719
719
|
maxTokens: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt)
|
|
@@ -793,7 +793,7 @@ import_validatorPrimitives.scheme.BrowserNewContextForReuseParams = (0, import_v
|
|
|
793
793
|
provider: import_validatorPrimitives.tString,
|
|
794
794
|
model: import_validatorPrimitives.tString,
|
|
795
795
|
cacheFile: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tString),
|
|
796
|
-
cacheMode: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tEnum)(["ignore", "force", "auto"])),
|
|
796
|
+
cacheMode: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tEnum)(["ignore", "force", "update", "auto"])),
|
|
797
797
|
secrets: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tArray)((0, import_validatorPrimitives.tType)("NameValue"))),
|
|
798
798
|
maxTurns: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt),
|
|
799
799
|
maxTokens: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt)
|
|
@@ -918,7 +918,7 @@ import_validatorPrimitives.scheme.BrowserContextInitializer = (0, import_validat
|
|
|
918
918
|
provider: import_validatorPrimitives.tString,
|
|
919
919
|
model: import_validatorPrimitives.tString,
|
|
920
920
|
cacheFile: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tString),
|
|
921
|
-
cacheMode: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tEnum)(["ignore", "force", "auto"])),
|
|
921
|
+
cacheMode: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tEnum)(["ignore", "force", "update", "auto"])),
|
|
922
922
|
secrets: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tArray)((0, import_validatorPrimitives.tType)("NameValue"))),
|
|
923
923
|
maxTurns: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt),
|
|
924
924
|
maxTokens: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt)
|
|
@@ -1186,6 +1186,14 @@ import_validatorPrimitives.scheme.PageInitializer = (0, import_validatorPrimitiv
|
|
|
1186
1186
|
isClosed: import_validatorPrimitives.tBoolean,
|
|
1187
1187
|
opener: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tChannel)(["Page"]))
|
|
1188
1188
|
});
|
|
1189
|
+
import_validatorPrimitives.scheme.PageAgentTurnEvent = (0, import_validatorPrimitives.tObject)({
|
|
1190
|
+
role: import_validatorPrimitives.tString,
|
|
1191
|
+
message: import_validatorPrimitives.tString,
|
|
1192
|
+
usage: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tObject)({
|
|
1193
|
+
inputTokens: import_validatorPrimitives.tInt,
|
|
1194
|
+
outputTokens: import_validatorPrimitives.tInt
|
|
1195
|
+
}))
|
|
1196
|
+
});
|
|
1189
1197
|
import_validatorPrimitives.scheme.PageBindingCallEvent = (0, import_validatorPrimitives.tObject)({
|
|
1190
1198
|
binding: (0, import_validatorPrimitives.tChannel)(["BindingCall"])
|
|
1191
1199
|
});
|
|
@@ -2838,7 +2846,7 @@ import_validatorPrimitives.scheme.AndroidDeviceLaunchBrowserParams = (0, import_
|
|
|
2838
2846
|
provider: import_validatorPrimitives.tString,
|
|
2839
2847
|
model: import_validatorPrimitives.tString,
|
|
2840
2848
|
cacheFile: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tString),
|
|
2841
|
-
cacheMode: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tEnum)(["ignore", "force", "auto"])),
|
|
2849
|
+
cacheMode: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tEnum)(["ignore", "force", "update", "auto"])),
|
|
2842
2850
|
secrets: (0, import_validatorPrimitives.tOptional)((0, import_validatorPrimitives.tArray)((0, import_validatorPrimitives.tType)("NameValue"))),
|
|
2843
2851
|
maxTurns: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt),
|
|
2844
2852
|
maxTokens: (0, import_validatorPrimitives.tOptional)(import_validatorPrimitives.tInt)
|
|
@@ -18,13 +18,25 @@ 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
|
-
|
|
22
|
-
|
|
21
|
+
generateActionTimeout: () => generateActionTimeout,
|
|
22
|
+
performActionTimeout: () => performActionTimeout,
|
|
23
|
+
runAction: () => runAction
|
|
23
24
|
});
|
|
24
25
|
module.exports = __toCommonJS(actionRunner_exports);
|
|
25
26
|
var import_expectUtils = require("../utils/expectUtils");
|
|
26
|
-
var
|
|
27
|
-
|
|
27
|
+
var import_time = require("../../utils/isomorphic/time");
|
|
28
|
+
var import_progress = require("../progress");
|
|
29
|
+
async function runAction(parentProgress, mode, page, action, secrets) {
|
|
30
|
+
const timeout = mode === "generate" ? generateActionTimeout(action) : performActionTimeout(action);
|
|
31
|
+
const mt = (0, import_time.monotonicTime)();
|
|
32
|
+
const deadline = mt + timeout;
|
|
33
|
+
const minDeadline = parentProgress.deadline ? Math.min(parentProgress.deadline, deadline) : deadline;
|
|
34
|
+
const pc = new import_progress.ProgressController();
|
|
35
|
+
return await pc.run(async (progress) => {
|
|
36
|
+
return await innerRunAction(progress, page, action, secrets);
|
|
37
|
+
}, minDeadline - mt);
|
|
38
|
+
}
|
|
39
|
+
async function innerRunAction(progress, page, action, secrets) {
|
|
28
40
|
const frame = page.mainFrame();
|
|
29
41
|
switch (action.method) {
|
|
30
42
|
case "click":
|
|
@@ -63,7 +75,7 @@ async function runAction(progress, page, action, secrets) {
|
|
|
63
75
|
await frame.uncheck(progress, action.selector, { ...strictTrue });
|
|
64
76
|
break;
|
|
65
77
|
case "expectVisible": {
|
|
66
|
-
const result = await frame.expect(progress, action.selector, { expression: "to.be.visible", isNot: false }
|
|
78
|
+
const result = await frame.expect(progress, action.selector, { expression: "to.be.visible", isNot: false });
|
|
67
79
|
if (result.errorMessage)
|
|
68
80
|
throw new Error(result.errorMessage);
|
|
69
81
|
break;
|
|
@@ -72,28 +84,56 @@ async function runAction(progress, page, action, secrets) {
|
|
|
72
84
|
let result;
|
|
73
85
|
if (action.type === "textbox" || action.type === "combobox" || action.type === "slider") {
|
|
74
86
|
const expectedText = (0, import_expectUtils.serializeExpectedTextValues)([action.value]);
|
|
75
|
-
result = await frame.expect(progress, action.selector, { expression: "to.have.value", expectedText, isNot: false }
|
|
87
|
+
result = await frame.expect(progress, action.selector, { expression: "to.have.value", expectedText, isNot: false });
|
|
76
88
|
} else if (action.type === "checkbox" || action.type === "radio") {
|
|
77
|
-
const expectedValue =
|
|
78
|
-
result = await frame.expect(progress, action.selector, { expression: "to.be.checked", expectedValue, isNot: false }
|
|
89
|
+
const expectedValue = { checked: action.value === "true" };
|
|
90
|
+
result = await frame.expect(progress, action.selector, { selector: action.selector, expression: "to.be.checked", expectedValue, isNot: false });
|
|
79
91
|
} else {
|
|
80
92
|
throw new Error(`Unsupported element type: ${action.type}`);
|
|
81
93
|
}
|
|
82
|
-
if (result.
|
|
94
|
+
if (!result.matches)
|
|
83
95
|
throw new Error(result.errorMessage);
|
|
84
96
|
break;
|
|
85
97
|
}
|
|
86
98
|
}
|
|
87
99
|
}
|
|
88
|
-
function
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
100
|
+
function generateActionTimeout(action) {
|
|
101
|
+
switch (action.method) {
|
|
102
|
+
case "click":
|
|
103
|
+
case "drag":
|
|
104
|
+
case "hover":
|
|
105
|
+
case "selectOption":
|
|
106
|
+
case "pressKey":
|
|
107
|
+
case "pressSequentially":
|
|
108
|
+
case "fill":
|
|
109
|
+
case "setChecked":
|
|
110
|
+
return 5e3;
|
|
111
|
+
case "expectVisible":
|
|
112
|
+
case "expectValue":
|
|
113
|
+
return 1;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function performActionTimeout(action) {
|
|
117
|
+
switch (action.method) {
|
|
118
|
+
case "click":
|
|
119
|
+
case "drag":
|
|
120
|
+
case "hover":
|
|
121
|
+
case "selectOption":
|
|
122
|
+
case "pressKey":
|
|
123
|
+
case "pressSequentially":
|
|
124
|
+
case "fill":
|
|
125
|
+
case "setChecked":
|
|
126
|
+
return 0;
|
|
127
|
+
// no timeout
|
|
128
|
+
case "expectVisible":
|
|
129
|
+
case "expectValue":
|
|
130
|
+
return 5e3;
|
|
131
|
+
}
|
|
93
132
|
}
|
|
94
133
|
const strictTrue = { strict: true };
|
|
95
134
|
// Annotate the CommonJS export names for ESM import in node:
|
|
96
135
|
0 && (module.exports = {
|
|
97
|
-
|
|
98
|
-
|
|
136
|
+
generateActionTimeout,
|
|
137
|
+
performActionTimeout,
|
|
138
|
+
runAction
|
|
99
139
|
});
|
|
@@ -38,11 +38,12 @@ var import_utilsBundle = require("../../utilsBundle");
|
|
|
38
38
|
var import_mcpBundle = require("../../mcpBundle");
|
|
39
39
|
var import_actionRunner = require("./actionRunner");
|
|
40
40
|
var import_context = require("./context");
|
|
41
|
+
var import_page = require("../page");
|
|
41
42
|
async function pagePerform(progress, page, options) {
|
|
42
43
|
const context = new import_context.Context(progress, page);
|
|
43
|
-
if (await cachedPerform(context, options))
|
|
44
|
+
if (await cachedPerform(progress, context, options))
|
|
44
45
|
return { turns: 0, inputTokens: 0, outputTokens: 0 };
|
|
45
|
-
const { usage } = await perform(context, options.task, void 0, options);
|
|
46
|
+
const { usage } = await perform(progress, context, options.task, void 0, options);
|
|
46
47
|
await updateCache(context, options);
|
|
47
48
|
return usage;
|
|
48
49
|
}
|
|
@@ -54,16 +55,17 @@ Extract the following information from the page. Do not perform any actions, jus
|
|
|
54
55
|
|
|
55
56
|
### Query
|
|
56
57
|
${options.query}`;
|
|
57
|
-
const { result, usage } = await perform(context, task, options.schema, options);
|
|
58
|
+
const { result, usage } = await perform(progress, context, task, options.schema, options);
|
|
58
59
|
return { result, usage };
|
|
59
60
|
}
|
|
60
|
-
async function perform(context, userTask, resultSchema, options = {}) {
|
|
61
|
-
const {
|
|
61
|
+
async function perform(progress, context, userTask, resultSchema, options = {}) {
|
|
62
|
+
const { page } = context;
|
|
62
63
|
const browserContext = page.browserContext;
|
|
63
64
|
if (!browserContext._options.agent)
|
|
64
65
|
throw new Error(`page.perform() and page.extract() require the agent to be set on the browser context`);
|
|
65
66
|
const { full } = await page.snapshotForAI(progress);
|
|
66
67
|
const { tools, callTool } = (0, import_backend.toolsForLoop)(context);
|
|
68
|
+
page.emit(import_page.Page.Events.AgentTurn, { role: "user", message: userTask });
|
|
67
69
|
const limits = context.limits(options);
|
|
68
70
|
let turns = 0;
|
|
69
71
|
const loop = new import_mcpBundle.Loop(browserContext._options.agent.provider, {
|
|
@@ -73,14 +75,35 @@ async function perform(context, userTask, resultSchema, options = {}) {
|
|
|
73
75
|
callTool,
|
|
74
76
|
tools,
|
|
75
77
|
...limits,
|
|
76
|
-
|
|
78
|
+
onBeforeTurn: ({ conversation }) => {
|
|
79
|
+
const userMessage = conversation.messages.find((m) => m.role === "user");
|
|
80
|
+
page.emit(import_page.Page.Events.AgentTurn, { role: "user", message: userMessage?.content ?? "" });
|
|
81
|
+
return "continue";
|
|
82
|
+
},
|
|
83
|
+
onAfterTurn: ({ assistantMessage, totalUsage }) => {
|
|
77
84
|
++turns;
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
|
|
85
|
+
const usage2 = { inputTokens: totalUsage.input, outputTokens: totalUsage.output };
|
|
86
|
+
const intent = assistantMessage.content.filter((c) => c.type === "text").map((c) => c.text).join("\n");
|
|
87
|
+
page.emit(import_page.Page.Events.AgentTurn, { role: "assistant", message: intent, usage: usage2 });
|
|
88
|
+
if (!assistantMessage.content.filter((c) => c.type === "tool_call").length)
|
|
89
|
+
page.emit(import_page.Page.Events.AgentTurn, { role: "assistant", message: `no tool calls`, usage: usage2 });
|
|
90
|
+
return "continue";
|
|
91
|
+
},
|
|
92
|
+
onBeforeToolCall: ({ toolCall }) => {
|
|
93
|
+
page.emit(import_page.Page.Events.AgentTurn, { role: "assistant", message: `call tool "${toolCall.name}"` });
|
|
94
|
+
return "continue";
|
|
95
|
+
},
|
|
96
|
+
onAfterToolCall: ({ toolCall }) => {
|
|
97
|
+
const suffix = toolCall.result?.isError ? "failed" : "succeeded";
|
|
98
|
+
page.emit(import_page.Page.Events.AgentTurn, { role: "user", message: `tool "${toolCall.name}" ${suffix}` });
|
|
99
|
+
if (toolCall.arguments.thatShouldBeIt)
|
|
81
100
|
return "break";
|
|
82
101
|
return "continue";
|
|
83
102
|
},
|
|
103
|
+
onToolCallError: ({ toolCall, error }) => {
|
|
104
|
+
page.emit(import_page.Page.Events.AgentTurn, { role: "user", message: `tool "${toolCall.name}" failed: ${error.message}` });
|
|
105
|
+
return "continue";
|
|
106
|
+
},
|
|
84
107
|
...options
|
|
85
108
|
});
|
|
86
109
|
const task = `${userTask}
|
|
@@ -99,8 +122,8 @@ ${full}
|
|
|
99
122
|
};
|
|
100
123
|
}
|
|
101
124
|
const allCaches = /* @__PURE__ */ new Map();
|
|
102
|
-
async function cachedPerform(context, options) {
|
|
103
|
-
if (!context.options?.cacheFile || context.options.cacheMode === "ignore")
|
|
125
|
+
async function cachedPerform(progress, context, options) {
|
|
126
|
+
if (!context.options?.cacheFile || context.options.cacheMode === "ignore" || context.options.cacheMode === "update")
|
|
104
127
|
return false;
|
|
105
128
|
const cache = await cachedActions(context.options.cacheFile);
|
|
106
129
|
const cacheKey = (options.key ?? options.task).trim();
|
|
@@ -111,7 +134,7 @@ async function cachedPerform(context, options) {
|
|
|
111
134
|
return false;
|
|
112
135
|
}
|
|
113
136
|
for (const action of entry.actions)
|
|
114
|
-
await (0, import_actionRunner.runAction)(
|
|
137
|
+
await (0, import_actionRunner.runAction)(progress, "run", context.page, action, context.options.secrets ?? []);
|
|
115
138
|
return true;
|
|
116
139
|
}
|
|
117
140
|
async function updateCache(context, options) {
|
|
@@ -124,7 +147,9 @@ async function updateCache(context, options) {
|
|
|
124
147
|
timestamp: Date.now(),
|
|
125
148
|
actions: context.actions
|
|
126
149
|
};
|
|
127
|
-
|
|
150
|
+
const entries = Object.entries(cache);
|
|
151
|
+
entries.sort((e1, e2) => e1[0].localeCompare(e2[0]));
|
|
152
|
+
await import_fs.default.promises.writeFile(cacheFile, JSON.stringify(Object.fromEntries(entries), void 0, 2));
|
|
128
153
|
}
|
|
129
154
|
async function cachedActions(cacheFile) {
|
|
130
155
|
let cache = allCaches.get(cacheFile);
|
|
@@ -72,6 +72,8 @@ async function generateCode(sdkLanguage, action) {
|
|
|
72
72
|
}
|
|
73
73
|
case "expectValue": {
|
|
74
74
|
const locator = (0, import_locatorGenerators.asLocator)(sdkLanguage, action.selector);
|
|
75
|
+
if (action.type === "checkbox" || action.type === "radio")
|
|
76
|
+
return `await expect(page.${locator}).toBeChecked({ checked: ${action.value === "true"} });`;
|
|
75
77
|
return `await expect(page.${locator}).toHaveValue(${(0, import_stringUtils.escapeWithQuotes)(action.value)});`;
|
|
76
78
|
}
|
|
77
79
|
}
|
|
@@ -25,9 +25,9 @@ var import_browserContext = require("../browserContext");
|
|
|
25
25
|
var import_actionRunner = require("./actionRunner");
|
|
26
26
|
var import_codegen = require("./codegen");
|
|
27
27
|
class Context {
|
|
28
|
-
constructor(
|
|
28
|
+
constructor(apiCallProgress, page) {
|
|
29
29
|
this.actions = [];
|
|
30
|
-
this.
|
|
30
|
+
this._progress = apiCallProgress;
|
|
31
31
|
this.page = page;
|
|
32
32
|
this.options = page.browserContext._options.agent;
|
|
33
33
|
this.sdkLanguage = page.browserContext._browser.sdkLanguage();
|
|
@@ -44,18 +44,15 @@ class Context {
|
|
|
44
44
|
return await this.runActionsAndWait([action]);
|
|
45
45
|
}
|
|
46
46
|
async runActionsAndWait(action) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
} catch (e) {
|
|
57
|
-
return await this.snapshotResult(e);
|
|
58
|
-
}
|
|
47
|
+
const error = await this.waitForCompletion(async () => {
|
|
48
|
+
for (const a of action) {
|
|
49
|
+
await (0, import_actionRunner.runAction)(this._progress, "generate", this.page, a, this.options?.secrets ?? []);
|
|
50
|
+
const code = await (0, import_codegen.generateCode)(this.sdkLanguage, a);
|
|
51
|
+
this.actions.push({ ...a, code, intent: this._callIntent });
|
|
52
|
+
}
|
|
53
|
+
return void 0;
|
|
54
|
+
}).catch((error2) => error2);
|
|
55
|
+
return await this.snapshotResult(error);
|
|
59
56
|
}
|
|
60
57
|
async waitForCompletion(callback) {
|
|
61
58
|
const requests = [];
|
|
@@ -67,13 +64,13 @@ class Context {
|
|
|
67
64
|
let result;
|
|
68
65
|
try {
|
|
69
66
|
result = await callback();
|
|
70
|
-
await this.
|
|
67
|
+
await this._progress.wait(500);
|
|
71
68
|
} finally {
|
|
72
69
|
disposeListeners();
|
|
73
70
|
}
|
|
74
71
|
const requestedNavigation = requests.some((request) => request.isNavigationRequest());
|
|
75
72
|
if (requestedNavigation) {
|
|
76
|
-
await this.page.mainFrame().waitForLoadState(this.
|
|
73
|
+
await this.page.mainFrame().waitForLoadState(this._progress, "load");
|
|
77
74
|
return result;
|
|
78
75
|
}
|
|
79
76
|
const promises = [];
|
|
@@ -83,13 +80,13 @@ class Context {
|
|
|
83
80
|
else
|
|
84
81
|
promises.push(request.response());
|
|
85
82
|
}
|
|
86
|
-
await this.
|
|
83
|
+
await this._progress.race(promises, { timeout: 5e3 });
|
|
87
84
|
if (requests.length)
|
|
88
|
-
await this.
|
|
85
|
+
await this._progress.wait(500);
|
|
89
86
|
return result;
|
|
90
87
|
}
|
|
91
88
|
async snapshotResult(error) {
|
|
92
|
-
let { full } = await this.page.snapshotForAI(this.
|
|
89
|
+
let { full } = await this.page.snapshotForAI(this._progress);
|
|
93
90
|
full = this._redactText(full);
|
|
94
91
|
const text = [];
|
|
95
92
|
if (error)
|
|
@@ -116,7 +113,7 @@ ${full}`);
|
|
|
116
113
|
async refSelectors(params) {
|
|
117
114
|
return Promise.all(params.map(async (param) => {
|
|
118
115
|
try {
|
|
119
|
-
const { resolvedSelector } = await this.page.mainFrame().resolveSelector(this.
|
|
116
|
+
const { resolvedSelector } = await this.page.mainFrame().resolveSelector(this._progress, `aria-ref=${param.ref}`);
|
|
120
117
|
return resolvedSelector;
|
|
121
118
|
} catch (e) {
|
|
122
119
|
throw new Error(`Ref ${param.ref} not found in the current page snapshot. Try capturing new snapshot.`);
|
|
@@ -184,7 +184,7 @@ const fillForm = defineTool({
|
|
|
184
184
|
schema: {
|
|
185
185
|
name: "browser_fill_form",
|
|
186
186
|
title: "Fill form",
|
|
187
|
-
description: "Fill multiple form fields",
|
|
187
|
+
description: "Fill multiple form fields. Always use this tool when you can fill more than one field at a time.",
|
|
188
188
|
inputSchema: baseSchema.extend({
|
|
189
189
|
fields: import_mcpBundle.z.array(import_mcpBundle.z.object({
|
|
190
190
|
name: import_mcpBundle.z.string().describe("Human-readable field name"),
|
|
@@ -123,7 +123,10 @@ class BidiExecutionContext {
|
|
|
123
123
|
}
|
|
124
124
|
return names2;
|
|
125
125
|
});
|
|
126
|
-
const values = await Promise.all(names.map((name) =>
|
|
126
|
+
const values = await Promise.all(names.map(async (name) => {
|
|
127
|
+
const value = await this._rawCallFunction("(object, name) => object[name]", [{ handle: handle._objectId }, { type: "string", value: name }], true, false);
|
|
128
|
+
return createHandle(handle._context, value);
|
|
129
|
+
}));
|
|
127
130
|
const map = /* @__PURE__ */ new Map();
|
|
128
131
|
for (let i = 0; i < names.length; i++)
|
|
129
132
|
map.set(names[i], values[i]);
|
|
@@ -152,7 +155,7 @@ class BidiExecutionContext {
|
|
|
152
155
|
return createHandle(context, result);
|
|
153
156
|
}
|
|
154
157
|
async contentFrameIdForFrame(handle) {
|
|
155
|
-
const contentWindow = await this._rawCallFunction("e => e.contentWindow", { handle: handle._objectId });
|
|
158
|
+
const contentWindow = await this._rawCallFunction("e => e.contentWindow", [{ handle: handle._objectId }]);
|
|
156
159
|
if (contentWindow?.type === "window")
|
|
157
160
|
return contentWindow.value.context;
|
|
158
161
|
return null;
|
|
@@ -166,17 +169,17 @@ class BidiExecutionContext {
|
|
|
166
169
|
return null;
|
|
167
170
|
}
|
|
168
171
|
async _remoteValueForReference(reference, createHandle2) {
|
|
169
|
-
return await this._rawCallFunction("e => e", reference, createHandle2);
|
|
172
|
+
return await this._rawCallFunction("e => e", [reference], createHandle2);
|
|
170
173
|
}
|
|
171
|
-
async _rawCallFunction(functionDeclaration,
|
|
174
|
+
async _rawCallFunction(functionDeclaration, args, createHandle2, awaitPromise = true) {
|
|
172
175
|
const response = await this._session.send("script.callFunction", {
|
|
173
176
|
functionDeclaration,
|
|
174
177
|
target: this._target,
|
|
175
|
-
arguments:
|
|
178
|
+
arguments: args,
|
|
176
179
|
// "Root" is necessary for the handle to be returned.
|
|
177
180
|
resultOwnership: createHandle2 ? bidi.Script.ResultOwnership.Root : bidi.Script.ResultOwnership.None,
|
|
178
181
|
serializationOptions: { maxObjectDepth: 0, maxDomDepth: 0 },
|
|
179
|
-
awaitPromise
|
|
182
|
+
awaitPromise,
|
|
180
183
|
userActivation: true
|
|
181
184
|
});
|
|
182
185
|
if (response.type === "exception")
|
|
@@ -186,14 +189,65 @@ class BidiExecutionContext {
|
|
|
186
189
|
throw new js.JavaScriptErrorInEvaluate("Unexpected response type: " + JSON.stringify(response));
|
|
187
190
|
}
|
|
188
191
|
}
|
|
189
|
-
function renderPreview(remoteObject) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
192
|
+
function renderPreview(remoteObject, nested = false) {
|
|
193
|
+
switch (remoteObject.type) {
|
|
194
|
+
case "undefined":
|
|
195
|
+
case "null":
|
|
196
|
+
return remoteObject.type;
|
|
197
|
+
case "number":
|
|
198
|
+
case "boolean":
|
|
199
|
+
case "string":
|
|
200
|
+
return String(remoteObject.value);
|
|
201
|
+
case "bigint":
|
|
202
|
+
return `${remoteObject.value}n`;
|
|
203
|
+
case "date":
|
|
204
|
+
return String(new Date(remoteObject.value));
|
|
205
|
+
case "regexp":
|
|
206
|
+
return String(new RegExp(remoteObject.value.pattern, remoteObject.value.flags));
|
|
207
|
+
case "node":
|
|
208
|
+
return remoteObject.value?.localName || "Node";
|
|
209
|
+
case "object":
|
|
210
|
+
if (nested)
|
|
211
|
+
return "Object";
|
|
212
|
+
const tokens = [];
|
|
213
|
+
for (const [name, value] of remoteObject.value || []) {
|
|
214
|
+
if (typeof name === "string")
|
|
215
|
+
tokens.push(`${name}: ${renderPreview(value, true)}`);
|
|
216
|
+
}
|
|
217
|
+
return `{${tokens.join(", ")}}`;
|
|
218
|
+
case "array":
|
|
219
|
+
case "htmlcollection":
|
|
220
|
+
case "nodelist":
|
|
221
|
+
if (nested || !remoteObject.value)
|
|
222
|
+
return remoteObject.value ? `Array(${remoteObject.value.length})` : "Array";
|
|
223
|
+
return `[${remoteObject.value.map((v) => renderPreview(v, true)).join(", ")}]`;
|
|
224
|
+
case "map":
|
|
225
|
+
return remoteObject.value ? `Map(${remoteObject.value.length})` : "Map";
|
|
226
|
+
case "set":
|
|
227
|
+
return remoteObject.value ? `Set(${remoteObject.value.length})` : "Set";
|
|
228
|
+
case "arraybuffer":
|
|
229
|
+
return "ArrayBuffer";
|
|
230
|
+
case "error":
|
|
231
|
+
return "Error";
|
|
232
|
+
case "function":
|
|
233
|
+
return "Function";
|
|
234
|
+
case "generator":
|
|
235
|
+
return "Generator";
|
|
236
|
+
case "promise":
|
|
237
|
+
return "Promise";
|
|
238
|
+
case "proxy":
|
|
239
|
+
return "Proxy";
|
|
240
|
+
case "symbol":
|
|
241
|
+
return "Symbol()";
|
|
242
|
+
case "typedarray":
|
|
243
|
+
return "TypedArray";
|
|
244
|
+
case "weakmap":
|
|
245
|
+
return "WeakMap";
|
|
246
|
+
case "weakset":
|
|
247
|
+
return "WeakSet";
|
|
248
|
+
case "window":
|
|
249
|
+
return "Window";
|
|
250
|
+
}
|
|
197
251
|
}
|
|
198
252
|
function remoteObjectValue(remoteObject) {
|
|
199
253
|
if (remoteObject.type === "undefined")
|
|
@@ -212,7 +266,10 @@ function createHandle(context, remoteObject) {
|
|
|
212
266
|
return new dom.ElementHandle(context, remoteObject.handle);
|
|
213
267
|
}
|
|
214
268
|
const objectId = "handle" in remoteObject ? remoteObject.handle : void 0;
|
|
215
|
-
|
|
269
|
+
const preview = renderPreview(remoteObject);
|
|
270
|
+
const handle = new js.JSHandle(context, remoteObject.type, preview, objectId, remoteObjectValue(remoteObject));
|
|
271
|
+
handle._setPreview(preview);
|
|
272
|
+
return handle;
|
|
216
273
|
}
|
|
217
274
|
// Annotate the CommonJS export names for ESM import in node:
|
|
218
275
|
0 && (module.exports = {
|
|
@@ -263,7 +263,7 @@ ${params.stackTrace?.callFrames.map((f) => {
|
|
|
263
263
|
return;
|
|
264
264
|
const callFrame = params.stackTrace?.callFrames[0];
|
|
265
265
|
const location = callFrame ?? { url: "", lineNumber: 1, columnNumber: 1 };
|
|
266
|
-
this._page.addConsoleMessage(null, entry.method, entry.args.map((arg) => (0, import_bidiExecutionContext.createHandle)(context, arg)), location
|
|
266
|
+
this._page.addConsoleMessage(null, entry.method, entry.args.map((arg) => (0, import_bidiExecutionContext.createHandle)(context, arg)), location);
|
|
267
267
|
}
|
|
268
268
|
async _onFileDialogOpened(params) {
|
|
269
269
|
if (!params.element)
|
|
@@ -67,6 +67,7 @@ class PageDispatcher extends import_dispatcher.Dispatcher {
|
|
|
67
67
|
}
|
|
68
68
|
this._dispatchEvent("route", { route: new import_networkDispatchers3.RouteDispatcher(import_networkDispatchers.RequestDispatcher.from(this.parentScope(), request), route) });
|
|
69
69
|
};
|
|
70
|
+
this.addObjectListener(import_page.Page.Events.AgentTurn, (params) => this._dispatchEvent("agentTurn", params));
|
|
70
71
|
this.addObjectListener(import_page.Page.Events.Close, () => {
|
|
71
72
|
this._dispatchEvent("close");
|
|
72
73
|
this._dispose();
|
package/lib/server/page.js
CHANGED
|
@@ -56,6 +56,7 @@ var import_callLog = require("./callLog");
|
|
|
56
56
|
var rawBindingsControllerSource = __toESM(require("../generated/bindingsControllerSource"));
|
|
57
57
|
var import_screencast = require("./screencast");
|
|
58
58
|
const PageEvent = {
|
|
59
|
+
AgentTurn: "agentturn",
|
|
59
60
|
Close: "close",
|
|
60
61
|
Crash: "crash",
|
|
61
62
|
Download: "download",
|
package/lib/server/progress.js
CHANGED
|
@@ -44,9 +44,11 @@ class ProgressController {
|
|
|
44
44
|
await this._donePromise;
|
|
45
45
|
}
|
|
46
46
|
async run(task, timeout) {
|
|
47
|
+
const deadline = timeout ? (0, import_utils.monotonicTime)() + timeout : 0;
|
|
47
48
|
(0, import_utils.assert)(this._state === "before");
|
|
48
49
|
this._state = "running";
|
|
49
50
|
const progress = {
|
|
51
|
+
deadline,
|
|
50
52
|
log: (message) => {
|
|
51
53
|
if (this._state === "running")
|
|
52
54
|
this.metadata.log.push(message);
|
|
@@ -55,7 +57,9 @@ class ProgressController {
|
|
|
55
57
|
metadata: this.metadata,
|
|
56
58
|
race: (promise, options) => {
|
|
57
59
|
const promises = Array.isArray(promise) ? promise : [promise];
|
|
58
|
-
const
|
|
60
|
+
const mt = (0, import_utils.monotonicTime)();
|
|
61
|
+
const dl = options?.timeout ? mt + options.timeout : 0;
|
|
62
|
+
const timerPromise = dl && (!deadline || dl < deadline) ? new Promise((f) => setTimeout(f, dl - mt)) : null;
|
|
59
63
|
return Promise.race([...promises, ...timerPromise ? [timerPromise] : [], this._forceAbortPromise]);
|
|
60
64
|
},
|
|
61
65
|
wait: async (timeout2) => {
|
|
@@ -65,7 +69,7 @@ class ProgressController {
|
|
|
65
69
|
}
|
|
66
70
|
};
|
|
67
71
|
let timer;
|
|
68
|
-
if (
|
|
72
|
+
if (deadline) {
|
|
69
73
|
const timeoutError = new import_errors.TimeoutError(`Timeout ${timeout}ms exceeded.`);
|
|
70
74
|
timer = setTimeout(() => {
|
|
71
75
|
if (this.metadata.pauseStartTime && !this.metadata.pauseEndTime)
|
|
@@ -75,7 +79,7 @@ class ProgressController {
|
|
|
75
79
|
this._state = { error: timeoutError };
|
|
76
80
|
this._forceAbortPromise.reject(timeoutError);
|
|
77
81
|
}
|
|
78
|
-
},
|
|
82
|
+
}, deadline - (0, import_utils.monotonicTime)());
|
|
79
83
|
}
|
|
80
84
|
try {
|
|
81
85
|
const result = await task(progress);
|
|
@@ -47,20 +47,21 @@ var import_launchApp2 = require("../../launchApp");
|
|
|
47
47
|
var import_playwright = require("../../playwright");
|
|
48
48
|
var import_progress = require("../../progress");
|
|
49
49
|
const tracesDirMarker = "traces.dir";
|
|
50
|
-
function validateTraceUrl(
|
|
51
|
-
if (!
|
|
52
|
-
return
|
|
53
|
-
if (
|
|
54
|
-
return
|
|
55
|
-
|
|
56
|
-
|
|
50
|
+
function validateTraceUrl(traceFileOrUrl) {
|
|
51
|
+
if (!traceFileOrUrl)
|
|
52
|
+
return traceFileOrUrl;
|
|
53
|
+
if (traceFileOrUrl.startsWith("http://") || traceFileOrUrl.startsWith("https://"))
|
|
54
|
+
return traceFileOrUrl;
|
|
55
|
+
let traceFile = traceFileOrUrl;
|
|
56
|
+
if (traceFile.endsWith(".json"))
|
|
57
|
+
return toFilePathUrl(traceFile);
|
|
57
58
|
try {
|
|
58
|
-
const stat = import_fs.default.statSync(
|
|
59
|
+
const stat = import_fs.default.statSync(traceFile);
|
|
59
60
|
if (stat.isDirectory())
|
|
60
|
-
|
|
61
|
-
return
|
|
61
|
+
traceFile = import_path.default.join(traceFile, tracesDirMarker);
|
|
62
|
+
return toFilePathUrl(traceFile);
|
|
62
63
|
} catch {
|
|
63
|
-
throw new Error(`Trace file ${
|
|
64
|
+
throw new Error(`Trace file ${traceFileOrUrl} does not exist!`);
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
async function startTraceViewerServer(options) {
|
|
@@ -221,15 +222,18 @@ function traceDescriptor(traceDir, tracePrefix) {
|
|
|
221
222
|
};
|
|
222
223
|
for (const name of import_fs.default.readdirSync(traceDir)) {
|
|
223
224
|
if (!tracePrefix || name.startsWith(tracePrefix))
|
|
224
|
-
result.entries.push({ name, path: import_path.default.join(traceDir, name) });
|
|
225
|
+
result.entries.push({ name, path: toFilePathUrl(import_path.default.join(traceDir, name)) });
|
|
225
226
|
}
|
|
226
227
|
const resourcesDir = import_path.default.join(traceDir, "resources");
|
|
227
228
|
if (import_fs.default.existsSync(resourcesDir)) {
|
|
228
229
|
for (const name of import_fs.default.readdirSync(resourcesDir))
|
|
229
|
-
result.entries.push({ name: "resources/" + name, path: import_path.default.join(resourcesDir, name) });
|
|
230
|
+
result.entries.push({ name: "resources/" + name, path: toFilePathUrl(import_path.default.join(resourcesDir, name)) });
|
|
230
231
|
}
|
|
231
232
|
return result;
|
|
232
233
|
}
|
|
234
|
+
function toFilePathUrl(filePath) {
|
|
235
|
+
return `file?path=${encodeURIComponent(filePath)}`;
|
|
236
|
+
}
|
|
233
237
|
// Annotate the CommonJS export names for ESM import in node:
|
|
234
238
|
0 && (module.exports = {
|
|
235
239
|
installRootRedirect,
|