@tontoko/fast-playwright-mcp 0.0.4
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/LICENSE +202 -0
- package/README.md +1047 -0
- package/cli.js +18 -0
- package/config.d.ts +124 -0
- package/index.d.ts +25 -0
- package/index.js +18 -0
- package/lib/actions.d.js +0 -0
- package/lib/batch/batch-executor.js +137 -0
- package/lib/browser-context-factory.js +252 -0
- package/lib/browser-server-backend.js +139 -0
- package/lib/config/constants.js +80 -0
- package/lib/config.js +405 -0
- package/lib/context.js +274 -0
- package/lib/diagnostics/common/diagnostic-base.js +63 -0
- package/lib/diagnostics/common/error-enrichment-utils.js +212 -0
- package/lib/diagnostics/common/index.js +56 -0
- package/lib/diagnostics/common/initialization-manager.js +210 -0
- package/lib/diagnostics/common/performance-tracker.js +132 -0
- package/lib/diagnostics/diagnostic-error.js +140 -0
- package/lib/diagnostics/diagnostic-level.js +123 -0
- package/lib/diagnostics/diagnostic-thresholds.js +347 -0
- package/lib/diagnostics/element-discovery.js +441 -0
- package/lib/diagnostics/enhanced-error-handler.js +376 -0
- package/lib/diagnostics/error-enrichment.js +157 -0
- package/lib/diagnostics/frame-reference-manager.js +179 -0
- package/lib/diagnostics/page-analyzer.js +639 -0
- package/lib/diagnostics/parallel-page-analyzer.js +129 -0
- package/lib/diagnostics/resource-manager.js +134 -0
- package/lib/diagnostics/smart-config.js +482 -0
- package/lib/diagnostics/smart-handle.js +118 -0
- package/lib/diagnostics/unified-system.js +717 -0
- package/lib/extension/cdp-relay.js +486 -0
- package/lib/extension/extension-context-factory.js +74 -0
- package/lib/extension/main.js +41 -0
- package/lib/file-utils.js +42 -0
- package/lib/generate-keys.js +75 -0
- package/lib/http-server.js +50 -0
- package/lib/in-process-client.js +64 -0
- package/lib/index.js +48 -0
- package/lib/javascript.js +90 -0
- package/lib/log.js +33 -0
- package/lib/loop/loop-claude.js +247 -0
- package/lib/loop/loop-open-ai.js +222 -0
- package/lib/loop/loop.js +174 -0
- package/lib/loop/main.js +46 -0
- package/lib/loopTools/context.js +76 -0
- package/lib/loopTools/main.js +65 -0
- package/lib/loopTools/perform.js +40 -0
- package/lib/loopTools/snapshot.js +37 -0
- package/lib/loopTools/tool.js +26 -0
- package/lib/manual-promise.js +125 -0
- package/lib/mcp/in-process-transport.js +91 -0
- package/lib/mcp/proxy-backend.js +127 -0
- package/lib/mcp/server.js +123 -0
- package/lib/mcp/transport.js +159 -0
- package/lib/package.js +28 -0
- package/lib/program.js +82 -0
- package/lib/response.js +493 -0
- package/lib/schemas/expectation.js +152 -0
- package/lib/session-log.js +210 -0
- package/lib/tab.js +417 -0
- package/lib/tools/base-tool-handler.js +141 -0
- package/lib/tools/batch-execute.js +150 -0
- package/lib/tools/common.js +65 -0
- package/lib/tools/console.js +60 -0
- package/lib/tools/diagnose/diagnose-analysis-runner.js +101 -0
- package/lib/tools/diagnose/diagnose-config-handler.js +130 -0
- package/lib/tools/diagnose/diagnose-report-builder.js +394 -0
- package/lib/tools/diagnose.js +147 -0
- package/lib/tools/dialogs.js +57 -0
- package/lib/tools/evaluate.js +67 -0
- package/lib/tools/files.js +53 -0
- package/lib/tools/find-elements.js +307 -0
- package/lib/tools/install.js +60 -0
- package/lib/tools/keyboard.js +93 -0
- package/lib/tools/mouse.js +110 -0
- package/lib/tools/navigate.js +82 -0
- package/lib/tools/network.js +50 -0
- package/lib/tools/pdf.js +46 -0
- package/lib/tools/screenshot.js +113 -0
- package/lib/tools/snapshot.js +158 -0
- package/lib/tools/tabs.js +97 -0
- package/lib/tools/tool.js +47 -0
- package/lib/tools/utils.js +131 -0
- package/lib/tools/wait.js +64 -0
- package/lib/tools.js +65 -0
- package/lib/types/batch.js +47 -0
- package/lib/types/diff.js +0 -0
- package/lib/types/performance.js +0 -0
- package/lib/types/threshold-base.js +0 -0
- package/lib/utils/array-utils.js +44 -0
- package/lib/utils/code-deduplication-utils.js +141 -0
- package/lib/utils/common-formatters.js +252 -0
- package/lib/utils/console-filter.js +64 -0
- package/lib/utils/diagnostic-report-utils.js +178 -0
- package/lib/utils/diff-formatter.js +126 -0
- package/lib/utils/disposable-manager.js +135 -0
- package/lib/utils/error-handler-middleware.js +77 -0
- package/lib/utils/image-processor.js +137 -0
- package/lib/utils/index.js +92 -0
- package/lib/utils/report-builder.js +189 -0
- package/lib/utils/request-logger.js +82 -0
- package/lib/utils/response-diff-detector.js +150 -0
- package/lib/utils/section-builder.js +62 -0
- package/lib/utils/tool-patterns.js +153 -0
- package/lib/utils.js +46 -0
- package/package.json +77 -0
package/lib/tab.js
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
|
+
|
|
20
|
+
// src/tab.ts
|
|
21
|
+
import { EventEmitter } from "node:events";
|
|
22
|
+
import debug from "debug";
|
|
23
|
+
import { TIMEOUTS } from "./config/constants.js";
|
|
24
|
+
import { logUnhandledError } from "./log.js";
|
|
25
|
+
import { ManualPromise } from "./manual-promise.js";
|
|
26
|
+
import { callOnPageNoTrace, waitForCompletion } from "./tools/utils.js";
|
|
27
|
+
var REF_VALUE_REGEX = /\[ref=([^\]]+)\]/;
|
|
28
|
+
var TabEvents = {
|
|
29
|
+
modalState: "modalState"
|
|
30
|
+
};
|
|
31
|
+
var snapshotDebug = debug("pw:mcp:snapshot");
|
|
32
|
+
|
|
33
|
+
class Tab extends EventEmitter {
|
|
34
|
+
context;
|
|
35
|
+
page;
|
|
36
|
+
_lastTitle = "about:blank";
|
|
37
|
+
_consoleMessages = [];
|
|
38
|
+
_recentConsoleMessages = [];
|
|
39
|
+
_requests = new Map;
|
|
40
|
+
_onPageClose;
|
|
41
|
+
_modalStates = [];
|
|
42
|
+
_downloads = [];
|
|
43
|
+
_navigationState = {
|
|
44
|
+
isNavigating: false,
|
|
45
|
+
lastNavigationStart: 0
|
|
46
|
+
};
|
|
47
|
+
constructor(context, page, onPageClose) {
|
|
48
|
+
super();
|
|
49
|
+
this.context = context;
|
|
50
|
+
this.page = page;
|
|
51
|
+
this._onPageClose = onPageClose;
|
|
52
|
+
page.on("console", (event) => this._handleConsoleMessage(messageToConsoleMessage(event)));
|
|
53
|
+
page.on("pageerror", (error) => this._handleConsoleMessage(pageErrorToConsoleMessage(error)));
|
|
54
|
+
page.on("request", (request) => this._requests.set(request, null));
|
|
55
|
+
page.on("response", (response) => this._requests.set(response.request(), response));
|
|
56
|
+
page.on("close", () => this._onClose());
|
|
57
|
+
page.on("filechooser", (chooser) => {
|
|
58
|
+
this.setModalState({
|
|
59
|
+
type: "fileChooser",
|
|
60
|
+
description: "File chooser",
|
|
61
|
+
fileChooser: chooser
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
page.on("dialog", (dialog) => this._dialogShown(dialog));
|
|
65
|
+
page.on("download", (download) => {
|
|
66
|
+
this._downloadStarted(download).catch(() => {});
|
|
67
|
+
});
|
|
68
|
+
page.on("framenavigated", (frame) => {
|
|
69
|
+
if (frame === page.mainFrame()) {
|
|
70
|
+
this._handleNavigationStart();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
page.on("load", () => {
|
|
74
|
+
this._handleNavigationComplete();
|
|
75
|
+
});
|
|
76
|
+
page.on("domcontentloaded", () => {
|
|
77
|
+
this._navigationState.isNavigating = true;
|
|
78
|
+
});
|
|
79
|
+
page.setDefaultNavigationTimeout(60000);
|
|
80
|
+
page.setDefaultTimeout(TIMEOUTS.DEFAULT_PAGE_TIMEOUT);
|
|
81
|
+
page[tabSymbol] = this;
|
|
82
|
+
}
|
|
83
|
+
static forPage(page) {
|
|
84
|
+
return page[tabSymbol];
|
|
85
|
+
}
|
|
86
|
+
modalStates() {
|
|
87
|
+
return this._modalStates;
|
|
88
|
+
}
|
|
89
|
+
setModalState(modalState) {
|
|
90
|
+
this._modalStates.push(modalState);
|
|
91
|
+
this.emit(TabEvents.modalState, modalState);
|
|
92
|
+
}
|
|
93
|
+
clearModalState(modalState) {
|
|
94
|
+
this._modalStates = this._modalStates.filter((state) => state !== modalState);
|
|
95
|
+
}
|
|
96
|
+
modalStatesMarkdown() {
|
|
97
|
+
return renderModalStates(this.context, this.modalStates());
|
|
98
|
+
}
|
|
99
|
+
_dialogShown(dialog) {
|
|
100
|
+
this.setModalState({
|
|
101
|
+
type: "dialog",
|
|
102
|
+
description: `"${dialog.type()}" dialog with message "${dialog.message()}"`,
|
|
103
|
+
dialog
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
async _downloadStarted(download) {
|
|
107
|
+
const entry = {
|
|
108
|
+
download,
|
|
109
|
+
finished: false,
|
|
110
|
+
outputFile: await this.context.outputFile(download.suggestedFilename())
|
|
111
|
+
};
|
|
112
|
+
this._downloads.push(entry);
|
|
113
|
+
await download.saveAs(entry.outputFile);
|
|
114
|
+
entry.finished = true;
|
|
115
|
+
}
|
|
116
|
+
_clearCollectedArtifacts() {
|
|
117
|
+
this._consoleMessages.length = 0;
|
|
118
|
+
this._recentConsoleMessages.length = 0;
|
|
119
|
+
this._requests.clear();
|
|
120
|
+
}
|
|
121
|
+
_handleConsoleMessage(message) {
|
|
122
|
+
this._consoleMessages.push(message);
|
|
123
|
+
this._recentConsoleMessages.push(message);
|
|
124
|
+
}
|
|
125
|
+
_onClose() {
|
|
126
|
+
this._clearCollectedArtifacts();
|
|
127
|
+
this._onPageClose(this);
|
|
128
|
+
}
|
|
129
|
+
async updateTitle() {
|
|
130
|
+
await this._raceAgainstModalStates(async () => {
|
|
131
|
+
this._lastTitle = await callOnPageNoTrace(this.page, (page) => page.title());
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
lastTitle() {
|
|
135
|
+
return this._lastTitle;
|
|
136
|
+
}
|
|
137
|
+
isCurrentTab() {
|
|
138
|
+
return this === this.context.currentTab();
|
|
139
|
+
}
|
|
140
|
+
async waitForLoadState(state, options) {
|
|
141
|
+
await callOnPageNoTrace(this.page, (page) => page.waitForLoadState(state, options).catch(logUnhandledError));
|
|
142
|
+
}
|
|
143
|
+
_handleNavigationStart() {
|
|
144
|
+
this._navigationState.isNavigating = true;
|
|
145
|
+
this._navigationState.lastNavigationStart = Date.now();
|
|
146
|
+
this._navigationState.navigationPromise = this._createNavigationPromise();
|
|
147
|
+
}
|
|
148
|
+
_handleNavigationComplete() {
|
|
149
|
+
this._navigationState.isNavigating = false;
|
|
150
|
+
}
|
|
151
|
+
_createNavigationPromise() {
|
|
152
|
+
return new Promise((resolve) => {
|
|
153
|
+
const checkComplete = () => {
|
|
154
|
+
if (!this._navigationState.isNavigating) {
|
|
155
|
+
resolve();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (Date.now() - this._navigationState.lastNavigationStart > getNavigationTimeouts().navigationTimeout) {
|
|
159
|
+
this._navigationState.isNavigating = false;
|
|
160
|
+
resolve();
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
setTimeout(checkComplete, getNavigationTimeouts().checkInterval);
|
|
164
|
+
};
|
|
165
|
+
setTimeout(checkComplete, getNavigationTimeouts().checkInterval);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
isNavigating() {
|
|
169
|
+
const isStale = Date.now() - this._navigationState.lastNavigationStart > getNavigationTimeouts().staleTimeout;
|
|
170
|
+
if (isStale && this._navigationState.isNavigating) {
|
|
171
|
+
this._navigationState.isNavigating = false;
|
|
172
|
+
}
|
|
173
|
+
return this._navigationState.isNavigating;
|
|
174
|
+
}
|
|
175
|
+
async waitForNavigationComplete() {
|
|
176
|
+
if (this._navigationState.navigationPromise) {
|
|
177
|
+
await this._navigationState.navigationPromise;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
async navigate(url) {
|
|
181
|
+
this._clearCollectedArtifacts();
|
|
182
|
+
const downloadEvent = callOnPageNoTrace(this.page, (page) => page.waitForEvent("download").catch(logUnhandledError));
|
|
183
|
+
try {
|
|
184
|
+
await this.page.goto(url, { waitUntil: "domcontentloaded" });
|
|
185
|
+
} catch (_e) {
|
|
186
|
+
const e = _e;
|
|
187
|
+
const mightBeDownload = e.message.includes("net::ERR_ABORTED") || e.message.includes("Download is starting");
|
|
188
|
+
if (!mightBeDownload) {
|
|
189
|
+
throw e;
|
|
190
|
+
}
|
|
191
|
+
const download = await Promise.race([
|
|
192
|
+
downloadEvent,
|
|
193
|
+
new Promise((resolve) => setTimeout(resolve, TIMEOUTS.LONG_DELAY))
|
|
194
|
+
]);
|
|
195
|
+
if (!download) {
|
|
196
|
+
throw e;
|
|
197
|
+
}
|
|
198
|
+
await new Promise((resolve) => setTimeout(resolve, TIMEOUTS.SHORT_DELAY));
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
await this.waitForLoadState("load", {
|
|
202
|
+
timeout: TIMEOUTS.LOAD_STATE_TIMEOUT
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
consoleMessages() {
|
|
206
|
+
return this._consoleMessages;
|
|
207
|
+
}
|
|
208
|
+
requests() {
|
|
209
|
+
return this._requests;
|
|
210
|
+
}
|
|
211
|
+
async captureSnapshot() {
|
|
212
|
+
return await this._captureSnapshotInternal();
|
|
213
|
+
}
|
|
214
|
+
async capturePartialSnapshot(selector, maxLength) {
|
|
215
|
+
return await this._captureSnapshotInternal(selector, maxLength);
|
|
216
|
+
}
|
|
217
|
+
async _captureSnapshotInternal(selector, maxLength) {
|
|
218
|
+
let tabSnapshot;
|
|
219
|
+
const modalStates = await this._raceAgainstModalStates(async () => {
|
|
220
|
+
let snapshot;
|
|
221
|
+
if (selector) {
|
|
222
|
+
const fullSnapshot = await this.page._snapshotForAI();
|
|
223
|
+
snapshot = this._extractPartialSnapshot(fullSnapshot, selector);
|
|
224
|
+
} else {
|
|
225
|
+
snapshot = await this.page._snapshotForAI();
|
|
226
|
+
}
|
|
227
|
+
if (maxLength && snapshot.length > maxLength) {
|
|
228
|
+
snapshot = this._truncateAtWordBoundary(snapshot, maxLength);
|
|
229
|
+
}
|
|
230
|
+
tabSnapshot = {
|
|
231
|
+
url: this.page.url(),
|
|
232
|
+
title: await this.page.title(),
|
|
233
|
+
ariaSnapshot: snapshot,
|
|
234
|
+
modalStates: [],
|
|
235
|
+
consoleMessages: [],
|
|
236
|
+
downloads: this._downloads
|
|
237
|
+
};
|
|
238
|
+
});
|
|
239
|
+
if (tabSnapshot) {
|
|
240
|
+
tabSnapshot.consoleMessages = this._recentConsoleMessages;
|
|
241
|
+
this._recentConsoleMessages = [];
|
|
242
|
+
}
|
|
243
|
+
return tabSnapshot ?? {
|
|
244
|
+
url: this.page.url(),
|
|
245
|
+
title: "",
|
|
246
|
+
ariaSnapshot: "",
|
|
247
|
+
modalStates,
|
|
248
|
+
consoleMessages: [],
|
|
249
|
+
downloads: []
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
_extractPartialSnapshot(fullSnapshot, selector) {
|
|
253
|
+
const lines = fullSnapshot.split(`
|
|
254
|
+
`);
|
|
255
|
+
const selectorToRole = {
|
|
256
|
+
main: "main",
|
|
257
|
+
header: "banner",
|
|
258
|
+
footer: "contentinfo",
|
|
259
|
+
nav: "navigation",
|
|
260
|
+
aside: "complementary",
|
|
261
|
+
section: "region",
|
|
262
|
+
article: "article"
|
|
263
|
+
};
|
|
264
|
+
const expectedRole = selectorToRole[selector] ?? selector;
|
|
265
|
+
let capturing = false;
|
|
266
|
+
let captureIndent = -1;
|
|
267
|
+
const capturedLines = [];
|
|
268
|
+
for (const line of lines) {
|
|
269
|
+
const indent = line.length - line.trimStart().length;
|
|
270
|
+
const trimmedLine = line.trim();
|
|
271
|
+
const rolePattern = new RegExp(`^- ${expectedRole}\\s*(?:\\[[^\\]]*\\])*\\s*:?`);
|
|
272
|
+
if (!capturing && rolePattern.test(trimmedLine)) {
|
|
273
|
+
capturing = true;
|
|
274
|
+
captureIndent = indent;
|
|
275
|
+
capturedLines.push(line);
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
if (capturing) {
|
|
279
|
+
if (indent > captureIndent) {
|
|
280
|
+
capturedLines.push(line);
|
|
281
|
+
} else {
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (capturedLines.length > 0) {
|
|
287
|
+
const minIndent = capturedLines[0].length - capturedLines[0].trimStart().length;
|
|
288
|
+
const normalizedLines = capturedLines.map((line) => {
|
|
289
|
+
if (line.trim() === "") {
|
|
290
|
+
return line;
|
|
291
|
+
}
|
|
292
|
+
const currentIndent = line.length - line.trimStart().length;
|
|
293
|
+
const newIndent = Math.max(0, currentIndent - minIndent);
|
|
294
|
+
return " ".repeat(newIndent) + line.trimStart();
|
|
295
|
+
});
|
|
296
|
+
return normalizedLines.join(`
|
|
297
|
+
`);
|
|
298
|
+
}
|
|
299
|
+
snapshotDebug('Selector "%s" not found in snapshot, returning full snapshot', selector);
|
|
300
|
+
return fullSnapshot;
|
|
301
|
+
}
|
|
302
|
+
_truncateAtWordBoundary(text, maxLength) {
|
|
303
|
+
if (text.length <= maxLength) {
|
|
304
|
+
return text;
|
|
305
|
+
}
|
|
306
|
+
let truncateIndex = maxLength;
|
|
307
|
+
if (text[maxLength] && text[maxLength] !== " " && text[maxLength] !== `
|
|
308
|
+
`) {
|
|
309
|
+
for (let i = maxLength - 1;i >= 0; i--) {
|
|
310
|
+
if (text[i] === " " || text[i] === `
|
|
311
|
+
`) {
|
|
312
|
+
truncateIndex = i;
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (maxLength - truncateIndex > 20) {
|
|
317
|
+
truncateIndex = maxLength;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
let result = text.substring(0, truncateIndex).trim();
|
|
321
|
+
if (result.length > maxLength) {
|
|
322
|
+
result = result.substring(0, maxLength);
|
|
323
|
+
}
|
|
324
|
+
return result;
|
|
325
|
+
}
|
|
326
|
+
_javaScriptBlocked() {
|
|
327
|
+
return this._modalStates.some((state) => state.type === "dialog");
|
|
328
|
+
}
|
|
329
|
+
async _raceAgainstModalStates(action) {
|
|
330
|
+
if (this.modalStates().length) {
|
|
331
|
+
return this.modalStates();
|
|
332
|
+
}
|
|
333
|
+
const promise = new ManualPromise;
|
|
334
|
+
const listener = (modalState) => promise.resolve([modalState]);
|
|
335
|
+
this.once(TabEvents.modalState, listener);
|
|
336
|
+
return await Promise.race([
|
|
337
|
+
action().then(() => {
|
|
338
|
+
this.off(TabEvents.modalState, listener);
|
|
339
|
+
return [];
|
|
340
|
+
}),
|
|
341
|
+
promise
|
|
342
|
+
]);
|
|
343
|
+
}
|
|
344
|
+
async waitForCompletion(callback) {
|
|
345
|
+
await this._raceAgainstModalStates(() => waitForCompletion(this, callback));
|
|
346
|
+
}
|
|
347
|
+
async refLocator(params) {
|
|
348
|
+
return (await this.refLocators([params]))[0];
|
|
349
|
+
}
|
|
350
|
+
async refLocators(params) {
|
|
351
|
+
const snapshot = await this.page._snapshotForAI();
|
|
352
|
+
return params.map((param) => {
|
|
353
|
+
if (!snapshot.includes(`[ref=${param.ref}]`)) {
|
|
354
|
+
const availableRefs = this._getAvailableRefs(snapshot);
|
|
355
|
+
throw new Error(`Ref ${param.ref} not found. Available refs: [${availableRefs.join(", ")}]. Element: ${param.element}. Consider capturing a new snapshot if the page has changed.`);
|
|
356
|
+
}
|
|
357
|
+
return this.page.locator(`aria-ref=${param.ref}`).describe(param.element);
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
async waitForTimeout(time) {
|
|
361
|
+
if (this._javaScriptBlocked()) {
|
|
362
|
+
await new Promise((f) => setTimeout(f, time));
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
await callOnPageNoTrace(this.page, (page) => {
|
|
366
|
+
return page.evaluate((timeout) => new Promise((f) => setTimeout(f, timeout)), time);
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
_getAvailableRefs(snapshot) {
|
|
370
|
+
const refMatches = snapshot.match(/\[ref=([^\]]+)\]/g);
|
|
371
|
+
if (!refMatches) {
|
|
372
|
+
return [];
|
|
373
|
+
}
|
|
374
|
+
return refMatches.map((match) => {
|
|
375
|
+
const refValue = REF_VALUE_REGEX.exec(match);
|
|
376
|
+
return refValue ? refValue[1] : "";
|
|
377
|
+
}).filter(Boolean);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
function messageToConsoleMessage(message) {
|
|
381
|
+
return {
|
|
382
|
+
type: message.type(),
|
|
383
|
+
text: message.text(),
|
|
384
|
+
toString: () => `[${message.type().toUpperCase()}] ${message.text()} @ ${message.location().url}:${message.location().lineNumber}`
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
function pageErrorToConsoleMessage(error) {
|
|
388
|
+
return {
|
|
389
|
+
type: undefined,
|
|
390
|
+
text: error.message,
|
|
391
|
+
toString: () => error.stack ?? error.message
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
function renderModalStates(context, modalStates) {
|
|
395
|
+
const result = ["### Modal state"];
|
|
396
|
+
if (modalStates.length === 0) {
|
|
397
|
+
result.push("- There is no modal state present");
|
|
398
|
+
}
|
|
399
|
+
for (const state of modalStates) {
|
|
400
|
+
const tool = context.tools.filter((t) => ("clearsModalState" in t)).find((t) => t.clearsModalState === state.type);
|
|
401
|
+
result.push(`- [${state.description}]: can be handled by the "${tool?.schema.name}" tool`);
|
|
402
|
+
}
|
|
403
|
+
return result;
|
|
404
|
+
}
|
|
405
|
+
var tabSymbol = Symbol("tabSymbol");
|
|
406
|
+
function getNavigationTimeouts() {
|
|
407
|
+
return {
|
|
408
|
+
navigationTimeout: TIMEOUTS.DEFAULT_PAGE_TIMEOUT,
|
|
409
|
+
checkInterval: 100,
|
|
410
|
+
staleTimeout: 1e4
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
export {
|
|
414
|
+
renderModalStates,
|
|
415
|
+
TabEvents,
|
|
416
|
+
Tab
|
|
417
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
|
+
|
|
20
|
+
// src/tools/base-tool-handler.ts
|
|
21
|
+
import { z } from "zod";
|
|
22
|
+
import { expectationSchema } from "../schemas/expectation.js";
|
|
23
|
+
import { getErrorMessage } from "../utils/common-formatters.js";
|
|
24
|
+
var baseElementSchema = z.object({
|
|
25
|
+
element: z.string().optional().describe("Human-readable element description used to obtain permission to interact with the element"),
|
|
26
|
+
ref: z.string().optional().describe("Exact target element reference from the page snapshot")
|
|
27
|
+
});
|
|
28
|
+
var baseExpectationSchema = z.object({
|
|
29
|
+
expectation: expectationSchema
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
class BaseToolHandler {
|
|
33
|
+
toolName;
|
|
34
|
+
constructor(toolName) {
|
|
35
|
+
this.toolName = toolName;
|
|
36
|
+
}
|
|
37
|
+
async handle(tab, params, response) {
|
|
38
|
+
try {
|
|
39
|
+
this.handleExpectation(params, response);
|
|
40
|
+
await this.executeToolLogic(tab, params, response);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
this.handleToolError(error, response, params);
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
handleExpectation(params, response) {
|
|
47
|
+
if (params.expectation) {
|
|
48
|
+
this.applyExpectationToResponse(params.expectation, response);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
applyExpectationToResponse(expectation, response) {
|
|
52
|
+
const exp = expectation;
|
|
53
|
+
if (exp.includeSnapshot !== false) {
|
|
54
|
+
response.setIncludeSnapshot();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
handleToolError(error, response, params) {
|
|
58
|
+
const errorMessage = getErrorMessage(error);
|
|
59
|
+
response.addError(`Error in ${this.toolName}: ${errorMessage}`);
|
|
60
|
+
if ("element" in params && params.element) {
|
|
61
|
+
response.addResult(`Element context: ${params.element}`);
|
|
62
|
+
}
|
|
63
|
+
if ("ref" in params && params.ref) {
|
|
64
|
+
response.addResult(`Reference: ${params.ref}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
addCodeComment(response, operation, context) {
|
|
68
|
+
const comment = context ? `// ${operation} - ${context}` : `// ${operation}`;
|
|
69
|
+
response.addCode(comment);
|
|
70
|
+
}
|
|
71
|
+
async waitForCompletion(tab, operation, operationName) {
|
|
72
|
+
try {
|
|
73
|
+
await tab.waitForCompletion(operation);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
const operation_name = operationName ?? "operation";
|
|
76
|
+
throw new Error(`${this.toolName} ${operation_name} failed: ${getErrorMessage(error)}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
class BaseElementToolHandler extends BaseToolHandler {
|
|
82
|
+
async resolveElementLocator(tab, params) {
|
|
83
|
+
if (params.ref && params.element) {
|
|
84
|
+
return await tab.refLocator({
|
|
85
|
+
ref: params.ref,
|
|
86
|
+
element: params.element
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
validateElementParams(params) {
|
|
91
|
+
if (params.ref && !params.element) {
|
|
92
|
+
throw new Error("Element description is required when ref is provided");
|
|
93
|
+
}
|
|
94
|
+
if (params.element && !params.ref) {
|
|
95
|
+
throw new Error("Element ref is required when element description is provided");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
handleToolError(error, response, params) {
|
|
99
|
+
super.handleToolError(error, response, params);
|
|
100
|
+
if (params.element && params.ref) {
|
|
101
|
+
response.addResult("Suggestion: Verify element selector is still valid");
|
|
102
|
+
response.addResult("Suggestion: Check if element is visible and interactable");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function createToolWithBaseHandler(config, handlerClass) {
|
|
107
|
+
const handler = new handlerClass(config.name);
|
|
108
|
+
return {
|
|
109
|
+
capability: config.capability ?? "core",
|
|
110
|
+
schema: {
|
|
111
|
+
name: config.name,
|
|
112
|
+
title: config.title,
|
|
113
|
+
description: config.description,
|
|
114
|
+
inputSchema: config.inputSchema,
|
|
115
|
+
type: config.type ?? "readOnly"
|
|
116
|
+
},
|
|
117
|
+
handle: async (tab, params, response) => {
|
|
118
|
+
await handler.handle(tab, params, response);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
var mouseCoordinateSchema = baseExpectationSchema.extend({
|
|
123
|
+
x: z.number().describe("X coordinate"),
|
|
124
|
+
y: z.number().describe("Y coordinate")
|
|
125
|
+
});
|
|
126
|
+
var navigationSchema = baseExpectationSchema.extend({
|
|
127
|
+
url: z.string().describe("The URL to navigate to")
|
|
128
|
+
});
|
|
129
|
+
var keyboardSchema = baseExpectationSchema.extend({
|
|
130
|
+
key: z.string().describe("Key to press")
|
|
131
|
+
});
|
|
132
|
+
export {
|
|
133
|
+
navigationSchema,
|
|
134
|
+
mouseCoordinateSchema,
|
|
135
|
+
keyboardSchema,
|
|
136
|
+
createToolWithBaseHandler,
|
|
137
|
+
baseExpectationSchema,
|
|
138
|
+
baseElementSchema,
|
|
139
|
+
BaseToolHandler,
|
|
140
|
+
BaseElementToolHandler
|
|
141
|
+
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
|
+
|
|
20
|
+
// src/tools/batch-execute.ts
|
|
21
|
+
import { batchExecuteSchema } from "../types/batch.js";
|
|
22
|
+
import { createBatchErrorHandler } from "../utils/error-handler-middleware.js";
|
|
23
|
+
import { defineTool } from "./tool.js";
|
|
24
|
+
var batchExecuteTool = defineTool({
|
|
25
|
+
capability: "core",
|
|
26
|
+
schema: {
|
|
27
|
+
name: "browser_batch_execute",
|
|
28
|
+
title: "Batch Execute Browser Actions",
|
|
29
|
+
description: `Execute multiple browser actions in sequence with optimized response handling.RECOMMENDED:Use this tool instead of individual actions when performing multiple operations to significantly reduce token usage and improve performance.BY DEFAULT use for:form filling(multiple type→click),multi-step navigation,any workflow with 2+ known steps.Saves 90% tokens vs individual calls.globalExpectation:{includeSnapshot:false,snapshotOptions:{selector:"#app"},diffOptions:{enabled:true}}.Per-step override:steps[].expectation.Example:[{tool:"browser_navigate",arguments:{url:"https://example.com"}},{tool:"browser_type",arguments:{element:"username",ref:"#user",text:"john"}},{tool:"browser_click",arguments:{element:"submit",ref:"#btn"}}].`,
|
|
30
|
+
inputSchema: batchExecuteSchema,
|
|
31
|
+
type: "destructive"
|
|
32
|
+
},
|
|
33
|
+
handle: async (context, params, response) => {
|
|
34
|
+
try {
|
|
35
|
+
const batchExecutor = getBatchExecutorOrError(context, response);
|
|
36
|
+
if (!batchExecutor) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const result = await batchExecutor.execute(params);
|
|
40
|
+
processExecutionResult(result, response);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
const errorHandler = createBatchErrorHandler("BatchExecute");
|
|
43
|
+
const enrichedError = errorHandler(error);
|
|
44
|
+
response.addError(enrichedError.message);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
function getBatchExecutorOrError(context, response) {
|
|
49
|
+
const batchExecutor = context.getBatchExecutor();
|
|
50
|
+
if (!batchExecutor) {
|
|
51
|
+
response.addError("Batch executor not available. Please ensure the browser context is properly initialized.");
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return batchExecutor;
|
|
55
|
+
}
|
|
56
|
+
function processExecutionResult(result, response) {
|
|
57
|
+
response.addResult(formatBatchResult(result));
|
|
58
|
+
if (result.steps.length > 0) {
|
|
59
|
+
addStepDetails(result, response);
|
|
60
|
+
}
|
|
61
|
+
addFinalStateIfNeeded(result, response);
|
|
62
|
+
handleExecutionErrors(result, response);
|
|
63
|
+
}
|
|
64
|
+
function addStepDetails(result, response) {
|
|
65
|
+
response.addResult("");
|
|
66
|
+
response.addResult("### Step Details");
|
|
67
|
+
for (const stepResult of result.steps) {
|
|
68
|
+
addStepResult(stepResult, response);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function addStepResult(stepResult, response) {
|
|
72
|
+
const status = stepResult.success ? "✅" : "❌";
|
|
73
|
+
const duration = `${stepResult.executionTimeMs}ms`;
|
|
74
|
+
response.addResult(`${status} Step ${stepResult.stepIndex + 1}: ${stepResult.toolName} (${duration})`);
|
|
75
|
+
if (stepResult.success && stepResult.result) {
|
|
76
|
+
addSuccessfulStepContent(stepResult, response);
|
|
77
|
+
} else if (!stepResult.success && stepResult.error) {
|
|
78
|
+
response.addResult(` Error: ${stepResult.error}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function addSuccessfulStepContent(stepResult, response) {
|
|
82
|
+
const stepContent = stepResult.result;
|
|
83
|
+
const textContent = stepContent.content?.[0]?.text;
|
|
84
|
+
if (typeof textContent === "string") {
|
|
85
|
+
const lines = textContent.split(`
|
|
86
|
+
`).slice(0, 3);
|
|
87
|
+
response.addResult(` ${lines.join(`
|
|
88
|
+
`)}`);
|
|
89
|
+
if (textContent.split(`
|
|
90
|
+
`).length > 3) {
|
|
91
|
+
response.addResult(" ...");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function addFinalStateIfNeeded(result, response) {
|
|
96
|
+
const successfulStepsWithContent = getSuccessfulStepsWithContent(result);
|
|
97
|
+
if (successfulStepsWithContent.length > 0 && result.stopReason === "completed") {
|
|
98
|
+
response.addResult("");
|
|
99
|
+
response.addResult("### Final State");
|
|
100
|
+
const lastStep = successfulStepsWithContent.at(-1);
|
|
101
|
+
const finalContent = extractFinalStepContent(lastStep);
|
|
102
|
+
if (finalContent) {
|
|
103
|
+
response.addResult(finalContent);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function getSuccessfulStepsWithContent(result) {
|
|
108
|
+
return result.steps.filter((s) => s.success && s.result && typeof s.result === "object" && ("content" in s.result) && Array.isArray(s.result.content) && s.result.content[0]?.text && !(("isError" in s.result) && s.result.isError));
|
|
109
|
+
}
|
|
110
|
+
function extractFinalStepContent(lastStep) {
|
|
111
|
+
if (lastStep?.result && typeof lastStep.result === "object" && "content" in lastStep.result && Array.isArray(lastStep.result.content) && lastStep.result.content[0]?.text) {
|
|
112
|
+
return lastStep.result.content[0].text;
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
function handleExecutionErrors(result, response) {
|
|
117
|
+
if (result.stopReason === "error" || result.failedSteps > 0) {
|
|
118
|
+
const errorMessage = result.stopReason === "error" ? "stopped due to error" : "completed with failures";
|
|
119
|
+
response.addError(`Batch execution ${errorMessage}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function formatBatchResult(result) {
|
|
123
|
+
const lines = [];
|
|
124
|
+
lines.push("### Batch Execution Summary");
|
|
125
|
+
lines.push(`- Status: ${getStatusDisplay(result.stopReason)}`);
|
|
126
|
+
lines.push(`- Total Steps: ${result.totalSteps}`);
|
|
127
|
+
lines.push(`- Successful: ${result.successfulSteps}`);
|
|
128
|
+
lines.push(`- Failed: ${result.failedSteps}`);
|
|
129
|
+
lines.push(`- Total Time: ${result.totalExecutionTimeMs}ms`);
|
|
130
|
+
if (result.stopReason === "error") {
|
|
131
|
+
lines.push("- Note: Execution stopped early due to error");
|
|
132
|
+
}
|
|
133
|
+
return lines.join(`
|
|
134
|
+
`);
|
|
135
|
+
}
|
|
136
|
+
function getStatusDisplay(stopReason) {
|
|
137
|
+
switch (stopReason) {
|
|
138
|
+
case "completed":
|
|
139
|
+
return "✅ Completed";
|
|
140
|
+
case "error":
|
|
141
|
+
return "❌ Stopped on Error";
|
|
142
|
+
case "stopped":
|
|
143
|
+
return "⏹️ Stopped";
|
|
144
|
+
default:
|
|
145
|
+
return "❓ Unknown";
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
export {
|
|
149
|
+
batchExecuteTool
|
|
150
|
+
};
|