@roxybrowser/playwright 2.0.2-beta.2 → 2.0.2-beta.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/dist/browser.d.ts +33 -5
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +127 -15
- package/dist/browser.js.map +1 -1
- package/dist/browserType.d.ts.map +1 -1
- package/dist/browserType.js +2 -1
- package/dist/browserType.js.map +1 -1
- package/dist/mcp/backend/network.d.ts.map +1 -1
- package/dist/mcp/backend/network.js +30 -4
- package/dist/mcp/backend/network.js.map +1 -1
- package/dist/mcp/backend/response.d.ts +2 -0
- package/dist/mcp/backend/response.d.ts.map +1 -1
- package/dist/mcp/backend/response.js +28 -4
- package/dist/mcp/backend/response.js.map +1 -1
- package/dist/mcp/connectedBrowser.d.ts.map +1 -1
- package/dist/mcp/connectedBrowser.js +327 -18
- package/dist/mcp/connectedBrowser.js.map +1 -1
- package/dist/mcp/runtime.d.ts +2 -0
- package/dist/mcp/runtime.d.ts.map +1 -1
- package/dist/mcp/runtime.js +20 -1
- package/dist/mcp/runtime.js.map +1 -1
- package/dist/mcp/types.d.ts +3 -0
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/page.d.ts +2 -0
- package/dist/page.d.ts.map +1 -1
- package/dist/page.js +12 -0
- package/dist/page.js.map +1 -1
- package/dist/roxybrowser.bundle.js +437 -38
- package/dist/roxybrowser.bundle.js.map +1 -1
- package/dist/types/api.d.ts +21 -2
- package/dist/types/api.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -449,6 +449,7 @@ class CdpConnectedBrowserSession {
|
|
|
449
449
|
pageConsoleStates = new Map();
|
|
450
450
|
pageNetworkStates = new Map();
|
|
451
451
|
pageDialogStates = new Map();
|
|
452
|
+
dialogWaiters = new Map();
|
|
452
453
|
activeTabId;
|
|
453
454
|
versionString = "Chromium/unknown";
|
|
454
455
|
constructor(browserClient, connection) {
|
|
@@ -469,10 +470,7 @@ class CdpConnectedBrowserSession {
|
|
|
469
470
|
});
|
|
470
471
|
const session = new CdpConnectedBrowserSession(browserClient, connection);
|
|
471
472
|
session.versionString = version.Browser;
|
|
472
|
-
|
|
473
|
-
if (tabs.length === 0) {
|
|
474
|
-
await session.newTab();
|
|
475
|
-
}
|
|
473
|
+
await session.refreshTabs();
|
|
476
474
|
await session.getActivePageClient().catch(() => undefined);
|
|
477
475
|
return session;
|
|
478
476
|
}
|
|
@@ -536,6 +534,7 @@ class CdpConnectedBrowserSession {
|
|
|
536
534
|
async click(target, options) {
|
|
537
535
|
const pageClient = await this.getActivePageClient();
|
|
538
536
|
const contextId = await this.getActiveUtilityContextId(pageClient);
|
|
537
|
+
const tabId = await this.getActiveTabId();
|
|
539
538
|
const source = "nodeToken" in target ? ACTION_POINT_EVALUATE_SOURCE : ACTION_POINT_BY_SELECTOR_SOURCE;
|
|
540
539
|
const arg = "nodeToken" in target ? { nodeToken: target.nodeToken } : { selector: target.selector };
|
|
541
540
|
const point = await evaluateCdp(pageClient, source, arg, contextId);
|
|
@@ -569,7 +568,7 @@ class CdpConnectedBrowserSession {
|
|
|
569
568
|
modifiers: modifiersMask
|
|
570
569
|
});
|
|
571
570
|
await delay(options.clickHoldMs);
|
|
572
|
-
|
|
571
|
+
const releasePromise = pageClient.Input.dispatchMouseEvent({
|
|
573
572
|
type: "mouseReleased",
|
|
574
573
|
x: point.x,
|
|
575
574
|
y: point.y,
|
|
@@ -577,6 +576,10 @@ class CdpConnectedBrowserSession {
|
|
|
577
576
|
clickCount,
|
|
578
577
|
modifiers: modifiersMask
|
|
579
578
|
});
|
|
579
|
+
await Promise.race([
|
|
580
|
+
releasePromise,
|
|
581
|
+
this.waitForDialog(tabId, options.clickHoldMs + 1000)
|
|
582
|
+
]);
|
|
580
583
|
}
|
|
581
584
|
}
|
|
582
585
|
async drag(start, end, options) {
|
|
@@ -833,26 +836,105 @@ class CdpConnectedBrowserSession {
|
|
|
833
836
|
}
|
|
834
837
|
}
|
|
835
838
|
async handleDialog(accept, promptText) {
|
|
836
|
-
const tabId =
|
|
839
|
+
const tabId = this.dialogTabId();
|
|
837
840
|
if (!this.pageDialogStates.has(tabId)) {
|
|
838
841
|
throw new McpToolError("no_dialog", "No dialog visible.");
|
|
839
842
|
}
|
|
840
|
-
const pageClient = await this.getActivePageClient();
|
|
843
|
+
const pageClient = this.pageClients.get(tabId) ?? await this.getActivePageClient();
|
|
841
844
|
this.pageDialogStates.delete(tabId);
|
|
842
845
|
await pageClient.Page.handleJavaScriptDialog({
|
|
843
846
|
accept,
|
|
844
847
|
...(promptText !== undefined ? { promptText } : {})
|
|
845
848
|
});
|
|
846
849
|
}
|
|
850
|
+
async hasDialog() {
|
|
851
|
+
return this.pageDialogStates.size > 0;
|
|
852
|
+
}
|
|
853
|
+
waitForDialog(tabId, timeoutMs) {
|
|
854
|
+
if (this.pageDialogStates.has(tabId)) {
|
|
855
|
+
return Promise.resolve();
|
|
856
|
+
}
|
|
857
|
+
return new Promise((resolve) => {
|
|
858
|
+
const waiter = {
|
|
859
|
+
resolve: () => {
|
|
860
|
+
if (waiter.timer) {
|
|
861
|
+
clearTimeout(waiter.timer);
|
|
862
|
+
}
|
|
863
|
+
this.removeDialogWaiter(tabId, waiter);
|
|
864
|
+
resolve();
|
|
865
|
+
}
|
|
866
|
+
};
|
|
867
|
+
waiter.timer = setTimeout(() => waiter.resolve(), timeoutMs);
|
|
868
|
+
const waiters = this.dialogWaiters.get(tabId) ?? new Set();
|
|
869
|
+
waiters.add(waiter);
|
|
870
|
+
this.dialogWaiters.set(tabId, waiters);
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
resolveDialogWaiters(tabId) {
|
|
874
|
+
const waiters = this.dialogWaiters.get(tabId);
|
|
875
|
+
if (!waiters) {
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
this.dialogWaiters.delete(tabId);
|
|
879
|
+
for (const waiter of waiters) {
|
|
880
|
+
waiter.resolve();
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
removeDialogWaiter(tabId, waiter) {
|
|
884
|
+
const waiters = this.dialogWaiters.get(tabId);
|
|
885
|
+
if (!waiters) {
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
waiters.delete(waiter);
|
|
889
|
+
if (waiters.size === 0) {
|
|
890
|
+
this.dialogWaiters.delete(tabId);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
847
893
|
async networkRequests() {
|
|
848
894
|
const tabId = await this.getActiveTabId();
|
|
895
|
+
await this.hydratePerformanceResourceRequests(tabId);
|
|
849
896
|
return this.ensureNetworkState(tabId).requests.map(cloneNetworkRequest);
|
|
850
897
|
}
|
|
851
898
|
async networkRequest(index) {
|
|
852
899
|
const tabId = await this.getActiveTabId();
|
|
900
|
+
await this.hydratePerformanceResourceRequests(tabId);
|
|
853
901
|
const request = this.ensureNetworkState(tabId).requests[index - 1];
|
|
854
902
|
return request ? cloneNetworkRequest(request) : undefined;
|
|
855
903
|
}
|
|
904
|
+
async fetchResponseBody(index) {
|
|
905
|
+
const tabId = await this.getActiveTabId();
|
|
906
|
+
const state = this.ensureNetworkState(tabId);
|
|
907
|
+
const request = state.requests[index - 1];
|
|
908
|
+
if (!request || !request.requestId) {
|
|
909
|
+
return request?.responseBody;
|
|
910
|
+
}
|
|
911
|
+
if (!canReadResponseBody(request)) {
|
|
912
|
+
return undefined;
|
|
913
|
+
}
|
|
914
|
+
if (request.responseBody !== undefined) {
|
|
915
|
+
return request.responseBody;
|
|
916
|
+
}
|
|
917
|
+
await waitForLoadingDone(state, request.requestId, 5_000).catch(() => undefined);
|
|
918
|
+
if (request.responseBody !== undefined) {
|
|
919
|
+
return request.responseBody;
|
|
920
|
+
}
|
|
921
|
+
if (state.bodyRead.has(request.requestId)) {
|
|
922
|
+
return undefined;
|
|
923
|
+
}
|
|
924
|
+
state.bodyRead.add(request.requestId);
|
|
925
|
+
const pageClient = this.pageClients.get(tabId) ?? await this.getActivePageClient();
|
|
926
|
+
const clientNetwork = pageClient.Network;
|
|
927
|
+
if (!clientNetwork) {
|
|
928
|
+
return undefined;
|
|
929
|
+
}
|
|
930
|
+
const body = await clientNetwork.getResponseBody({ requestId: request.requestId }).catch(() => undefined);
|
|
931
|
+
if (body) {
|
|
932
|
+
request.responseBody = body.base64Encoded
|
|
933
|
+
? Buffer.from(body.body, "base64").toString("utf8")
|
|
934
|
+
: body.body;
|
|
935
|
+
}
|
|
936
|
+
return request.responseBody;
|
|
937
|
+
}
|
|
856
938
|
async runCodeUnsafe(code) {
|
|
857
939
|
const pageClient = await this.getActivePageClient();
|
|
858
940
|
const contextId = await this.getActiveUtilityContextId(pageClient);
|
|
@@ -922,6 +1004,16 @@ class CdpConnectedBrowserSession {
|
|
|
922
1004
|
}
|
|
923
1005
|
return activeTab.id;
|
|
924
1006
|
}
|
|
1007
|
+
dialogTabId() {
|
|
1008
|
+
if (this.activeTabId && this.pageDialogStates.has(this.activeTabId)) {
|
|
1009
|
+
return this.activeTabId;
|
|
1010
|
+
}
|
|
1011
|
+
const tabId = this.pageDialogStates.keys().next().value;
|
|
1012
|
+
if (!tabId) {
|
|
1013
|
+
throw new McpToolError("no_dialog", "No dialog visible.");
|
|
1014
|
+
}
|
|
1015
|
+
return tabId;
|
|
1016
|
+
}
|
|
925
1017
|
async getActivePageClient() {
|
|
926
1018
|
const tabs = await this.refreshTabs();
|
|
927
1019
|
const activeTab = tabs.find((tab) => tab.active);
|
|
@@ -1016,6 +1108,7 @@ class CdpConnectedBrowserSession {
|
|
|
1016
1108
|
...(event.defaultPrompt !== undefined ? { defaultPrompt: event.defaultPrompt } : {}),
|
|
1017
1109
|
...(event.url !== undefined ? { url: event.url } : {})
|
|
1018
1110
|
});
|
|
1111
|
+
this.resolveDialogWaiters(tabId);
|
|
1019
1112
|
});
|
|
1020
1113
|
this.installNetworkCollection(tabId, client);
|
|
1021
1114
|
}
|
|
@@ -1063,13 +1156,15 @@ class CdpConnectedBrowserSession {
|
|
|
1063
1156
|
client.Network.loadingFinished(async (event) => {
|
|
1064
1157
|
const request = state.byRequestId.get(event.requestId);
|
|
1065
1158
|
if (!request) {
|
|
1159
|
+
resolveLoadingDone(state, event.requestId, true);
|
|
1066
1160
|
return;
|
|
1067
1161
|
}
|
|
1068
1162
|
const startedAt = state.startedAt.get(event.requestId);
|
|
1069
1163
|
if (startedAt !== undefined && event.timestamp !== undefined) {
|
|
1070
1164
|
request.durationMs = Math.round(event.timestamp * 1000 - startedAt);
|
|
1071
1165
|
}
|
|
1072
|
-
if (canReadResponseBody(request)) {
|
|
1166
|
+
if (canReadResponseBody(request) && !state.bodyRead.has(event.requestId)) {
|
|
1167
|
+
state.bodyRead.add(event.requestId);
|
|
1073
1168
|
const clientNetwork = client.Network;
|
|
1074
1169
|
const body = await clientNetwork?.getResponseBody({ requestId: event.requestId }).catch(() => undefined);
|
|
1075
1170
|
if (body) {
|
|
@@ -1078,10 +1173,12 @@ class CdpConnectedBrowserSession {
|
|
|
1078
1173
|
: body.body;
|
|
1079
1174
|
}
|
|
1080
1175
|
}
|
|
1176
|
+
resolveLoadingDone(state, event.requestId, true);
|
|
1081
1177
|
});
|
|
1082
1178
|
client.Network.loadingFailed((event) => {
|
|
1083
1179
|
const request = state.byRequestId.get(event.requestId);
|
|
1084
1180
|
if (!request) {
|
|
1181
|
+
resolveLoadingDone(state, event.requestId, false);
|
|
1085
1182
|
return;
|
|
1086
1183
|
}
|
|
1087
1184
|
request.failureText = event.errorText ?? "Unknown error";
|
|
@@ -1089,6 +1186,7 @@ class CdpConnectedBrowserSession {
|
|
|
1089
1186
|
if (startedAt !== undefined && event.timestamp !== undefined) {
|
|
1090
1187
|
request.durationMs = Math.round(event.timestamp * 1000 - startedAt);
|
|
1091
1188
|
}
|
|
1189
|
+
resolveLoadingDone(state, event.requestId, false);
|
|
1092
1190
|
});
|
|
1093
1191
|
}
|
|
1094
1192
|
ensureConsoleState(tabId) {
|
|
@@ -1110,7 +1208,10 @@ class CdpConnectedBrowserSession {
|
|
|
1110
1208
|
state = {
|
|
1111
1209
|
requests: [],
|
|
1112
1210
|
byRequestId: new Map(),
|
|
1113
|
-
startedAt: new Map()
|
|
1211
|
+
startedAt: new Map(),
|
|
1212
|
+
hydratedPerformanceResources: false,
|
|
1213
|
+
loadingDone: new Map(),
|
|
1214
|
+
bodyRead: new Set()
|
|
1114
1215
|
};
|
|
1115
1216
|
this.pageNetworkStates.set(tabId, state);
|
|
1116
1217
|
}
|
|
@@ -1126,10 +1227,82 @@ class CdpConnectedBrowserSession {
|
|
|
1126
1227
|
this.pageNetworkStates.set(tabId, {
|
|
1127
1228
|
requests: [],
|
|
1128
1229
|
byRequestId: new Map(),
|
|
1129
|
-
startedAt: new Map()
|
|
1230
|
+
startedAt: new Map(),
|
|
1231
|
+
hydratedPerformanceResources: false,
|
|
1232
|
+
loadingDone: new Map(),
|
|
1233
|
+
bodyRead: new Set()
|
|
1130
1234
|
});
|
|
1131
1235
|
this.pageDialogStates.delete(tabId);
|
|
1132
1236
|
}
|
|
1237
|
+
async hydratePerformanceResourceRequests(tabId) {
|
|
1238
|
+
const state = this.ensureNetworkState(tabId);
|
|
1239
|
+
if (state.hydratedPerformanceResources) {
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
state.hydratedPerformanceResources = true;
|
|
1243
|
+
const pageClient = await this.getActivePageClient();
|
|
1244
|
+
const contextId = await this.getActiveUtilityContextId(pageClient);
|
|
1245
|
+
const documentRequest = await evaluateCdp(pageClient, String.raw `() => {
|
|
1246
|
+
const navigation = performance.getEntriesByType("navigation")[0];
|
|
1247
|
+
return {
|
|
1248
|
+
url: String(location.href || ""),
|
|
1249
|
+
duration: navigation ? Math.round(Number(navigation.duration || 0)) : undefined
|
|
1250
|
+
};
|
|
1251
|
+
}`, undefined, contextId).catch(() => undefined);
|
|
1252
|
+
if (documentRequest?.url && !Array.from(state.byRequestId.values()).some((request) => request.url === documentRequest.url)) {
|
|
1253
|
+
const requestId = `performance:navigation:${documentRequest.url}`;
|
|
1254
|
+
const request = {
|
|
1255
|
+
index: state.requests.length + 1,
|
|
1256
|
+
requestId,
|
|
1257
|
+
method: "GET",
|
|
1258
|
+
url: documentRequest.url,
|
|
1259
|
+
resourceType: "document",
|
|
1260
|
+
requestHeaders: {},
|
|
1261
|
+
status: 200,
|
|
1262
|
+
statusText: "OK",
|
|
1263
|
+
...(documentRequest.duration !== undefined ? { durationMs: documentRequest.duration } : {})
|
|
1264
|
+
};
|
|
1265
|
+
state.requests.push(request);
|
|
1266
|
+
state.byRequestId.set(requestId, request);
|
|
1267
|
+
}
|
|
1268
|
+
const resources = await evaluateCdp(pageClient, String.raw `() => performance.getEntriesByType("resource").map((entry) => ({
|
|
1269
|
+
name: String(entry.name || ""),
|
|
1270
|
+
initiatorType: String(entry.initiatorType || "other"),
|
|
1271
|
+
duration: Math.round(Number(entry.duration || 0)),
|
|
1272
|
+
responseStatus: typeof entry.responseStatus === "number" ? entry.responseStatus : undefined
|
|
1273
|
+
}))`, undefined, contextId).catch(() => []);
|
|
1274
|
+
for (const resource of resources) {
|
|
1275
|
+
if (!resource.name || Array.from(state.byRequestId.values()).some((request) => request.url === resource.name)) {
|
|
1276
|
+
continue;
|
|
1277
|
+
}
|
|
1278
|
+
const status = resource.responseStatus && resource.responseStatus > 0
|
|
1279
|
+
? resource.responseStatus
|
|
1280
|
+
: await this.probeResourceStatus(pageClient, contextId, resource.name);
|
|
1281
|
+
const requestId = `performance:${resource.name}`;
|
|
1282
|
+
const request = {
|
|
1283
|
+
index: state.requests.length + 1,
|
|
1284
|
+
requestId,
|
|
1285
|
+
method: "GET",
|
|
1286
|
+
url: resource.name,
|
|
1287
|
+
resourceType: normalizeResourceType(resource.initiatorType),
|
|
1288
|
+
requestHeaders: {},
|
|
1289
|
+
...(status !== undefined ? { status, statusText: statusTextForStatus(status) } : {}),
|
|
1290
|
+
...(resource.duration !== undefined ? { durationMs: resource.duration } : {})
|
|
1291
|
+
};
|
|
1292
|
+
state.requests.push(request);
|
|
1293
|
+
state.byRequestId.set(requestId, request);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
async probeResourceStatus(pageClient, contextId, url) {
|
|
1297
|
+
return evaluateCdp(pageClient, String.raw `async (url) => {
|
|
1298
|
+
try {
|
|
1299
|
+
const response = await fetch(url, { method: "HEAD", cache: "no-store" });
|
|
1300
|
+
return response.status;
|
|
1301
|
+
} catch {
|
|
1302
|
+
return undefined;
|
|
1303
|
+
}
|
|
1304
|
+
}`, url, contextId).catch(() => undefined);
|
|
1305
|
+
}
|
|
1133
1306
|
addConsoleMessage(tabId, message) {
|
|
1134
1307
|
const state = this.ensureConsoleState(tabId);
|
|
1135
1308
|
if (!shouldIncludeConsoleMessage(message.type)) {
|
|
@@ -1299,6 +1472,59 @@ function canReadResponseBody(request) {
|
|
|
1299
1472
|
}
|
|
1300
1473
|
return request.status !== 204 && request.status !== 304 && !(request.status >= 100 && request.status < 200);
|
|
1301
1474
|
}
|
|
1475
|
+
function loadingDoneEntry(state, requestId) {
|
|
1476
|
+
let entry = state.loadingDone.get(requestId);
|
|
1477
|
+
if (!entry) {
|
|
1478
|
+
let resolve;
|
|
1479
|
+
let reject;
|
|
1480
|
+
const promise = new Promise((res, rej) => {
|
|
1481
|
+
resolve = res;
|
|
1482
|
+
reject = rej;
|
|
1483
|
+
});
|
|
1484
|
+
entry = { promise, resolve, reject };
|
|
1485
|
+
state.loadingDone.set(requestId, entry);
|
|
1486
|
+
}
|
|
1487
|
+
return entry;
|
|
1488
|
+
}
|
|
1489
|
+
function resolveLoadingDone(state, requestId, success) {
|
|
1490
|
+
const entry = state.loadingDone.get(requestId);
|
|
1491
|
+
if (!entry) {
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
state.loadingDone.delete(requestId);
|
|
1495
|
+
if (success) {
|
|
1496
|
+
entry.resolve();
|
|
1497
|
+
}
|
|
1498
|
+
else {
|
|
1499
|
+
entry.reject(new Error("Request failed before the response body was available."));
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
async function waitForLoadingDone(state, requestId, timeoutMs) {
|
|
1503
|
+
const entry = loadingDoneEntry(state, requestId);
|
|
1504
|
+
await Promise.race([
|
|
1505
|
+
entry.promise,
|
|
1506
|
+
new Promise((resolve) => setTimeout(resolve, timeoutMs))
|
|
1507
|
+
]);
|
|
1508
|
+
}
|
|
1509
|
+
function statusTextForStatus(status) {
|
|
1510
|
+
if (status === 200)
|
|
1511
|
+
return "OK";
|
|
1512
|
+
if (status === 204)
|
|
1513
|
+
return "No Content";
|
|
1514
|
+
if (status === 304)
|
|
1515
|
+
return "Not Modified";
|
|
1516
|
+
if (status === 400)
|
|
1517
|
+
return "Bad Request";
|
|
1518
|
+
if (status === 401)
|
|
1519
|
+
return "Unauthorized";
|
|
1520
|
+
if (status === 403)
|
|
1521
|
+
return "Forbidden";
|
|
1522
|
+
if (status === 404)
|
|
1523
|
+
return "Not Found";
|
|
1524
|
+
if (status === 500)
|
|
1525
|
+
return "Internal Server Error";
|
|
1526
|
+
return "";
|
|
1527
|
+
}
|
|
1302
1528
|
function cloneNetworkRequest(request) {
|
|
1303
1529
|
return {
|
|
1304
1530
|
...request,
|
|
@@ -1398,6 +1624,7 @@ class BidiConnectedBrowserSession {
|
|
|
1398
1624
|
pageConsoleStates = new Map();
|
|
1399
1625
|
pageNetworkStates = new Map();
|
|
1400
1626
|
pageDialogStates = new Map();
|
|
1627
|
+
dialogWaiters = new Map();
|
|
1401
1628
|
bidiListeners = new Map();
|
|
1402
1629
|
responseDataCollector;
|
|
1403
1630
|
activeTabId;
|
|
@@ -1420,10 +1647,7 @@ class BidiConnectedBrowserSession {
|
|
|
1420
1647
|
const session = new BidiConnectedBrowserSession(client);
|
|
1421
1648
|
session.ownsSession = await ensureMcpBiDiSession(client, args.endpoint, args.sessionId);
|
|
1422
1649
|
await session.initialize();
|
|
1423
|
-
|
|
1424
|
-
if (tabs.length === 0) {
|
|
1425
|
-
await session.newTab();
|
|
1426
|
-
}
|
|
1650
|
+
await session.refreshTabs();
|
|
1427
1651
|
return session;
|
|
1428
1652
|
}
|
|
1429
1653
|
async version() {
|
|
@@ -1482,10 +1706,14 @@ class BidiConnectedBrowserSession {
|
|
|
1482
1706
|
async snapshot(request = {}) {
|
|
1483
1707
|
const tabId = await this.getActiveTabId();
|
|
1484
1708
|
const result = await retryUntilReady(() => evaluateBiDi(this.client, tabId, ARIA_SNAPSHOT_EVALUATE_SOURCE, toAriaSnapshotPayload(request)));
|
|
1485
|
-
|
|
1709
|
+
const snapshot = toBrowserSnapshot(result, request, {
|
|
1486
1710
|
console: this.consoleSummary(tabId),
|
|
1487
1711
|
consoleLink: await this.takeConsoleLink(tabId)
|
|
1488
1712
|
});
|
|
1713
|
+
return {
|
|
1714
|
+
...snapshot,
|
|
1715
|
+
retryable: true
|
|
1716
|
+
};
|
|
1489
1717
|
}
|
|
1490
1718
|
async consoleMessages(level = "info", all = false) {
|
|
1491
1719
|
const activeTabId = await this.getActiveTabId();
|
|
@@ -1829,7 +2057,7 @@ class BidiConnectedBrowserSession {
|
|
|
1829
2057
|
}
|
|
1830
2058
|
}
|
|
1831
2059
|
async handleDialog(accept, promptText) {
|
|
1832
|
-
const tabId =
|
|
2060
|
+
const tabId = this.dialogTabId();
|
|
1833
2061
|
if (!this.pageDialogStates.has(tabId)) {
|
|
1834
2062
|
throw new McpToolError("no_dialog", "No dialog visible.");
|
|
1835
2063
|
}
|
|
@@ -1840,6 +2068,9 @@ class BidiConnectedBrowserSession {
|
|
|
1840
2068
|
...(promptText !== undefined ? { userText: promptText } : {})
|
|
1841
2069
|
});
|
|
1842
2070
|
}
|
|
2071
|
+
async hasDialog() {
|
|
2072
|
+
return this.pageDialogStates.size > 0;
|
|
2073
|
+
}
|
|
1843
2074
|
async networkRequests() {
|
|
1844
2075
|
const tabId = await this.getActiveTabId();
|
|
1845
2076
|
return this.ensureNetworkState(tabId).requests.map(cloneNetworkRequest);
|
|
@@ -1849,6 +2080,21 @@ class BidiConnectedBrowserSession {
|
|
|
1849
2080
|
const request = this.ensureNetworkState(tabId).requests[index - 1];
|
|
1850
2081
|
return request ? cloneNetworkRequest(request) : undefined;
|
|
1851
2082
|
}
|
|
2083
|
+
async fetchResponseBody(index) {
|
|
2084
|
+
const tabId = await this.getActiveTabId();
|
|
2085
|
+
const request = this.ensureNetworkState(tabId).requests[index - 1];
|
|
2086
|
+
if (!request || !request.requestId) {
|
|
2087
|
+
return request?.responseBody;
|
|
2088
|
+
}
|
|
2089
|
+
if (request.responseBody !== undefined) {
|
|
2090
|
+
return request.responseBody;
|
|
2091
|
+
}
|
|
2092
|
+
const body = await this.getResponseBody(request.requestId).catch(() => undefined);
|
|
2093
|
+
if (body !== undefined) {
|
|
2094
|
+
request.responseBody = body;
|
|
2095
|
+
}
|
|
2096
|
+
return request.responseBody;
|
|
2097
|
+
}
|
|
1852
2098
|
async runCodeUnsafe(code) {
|
|
1853
2099
|
return this.evaluate(`async () => {
|
|
1854
2100
|
const fn = eval(${JSON.stringify(`(${code})`)});
|
|
@@ -1921,6 +2167,63 @@ class BidiConnectedBrowserSession {
|
|
|
1921
2167
|
targetArg(target) {
|
|
1922
2168
|
return "nodeToken" in target ? { nodeToken: target.nodeToken } : { selector: target.selector };
|
|
1923
2169
|
}
|
|
2170
|
+
dialogTabId() {
|
|
2171
|
+
if (this.activeTabId && this.pageDialogStates.has(this.activeTabId)) {
|
|
2172
|
+
return this.activeTabId;
|
|
2173
|
+
}
|
|
2174
|
+
const tabId = this.pageDialogStates.keys().next().value;
|
|
2175
|
+
if (!tabId) {
|
|
2176
|
+
throw new McpToolError("no_dialog", "No dialog visible.");
|
|
2177
|
+
}
|
|
2178
|
+
return tabId;
|
|
2179
|
+
}
|
|
2180
|
+
waitForDialog(tabId, timeoutMs) {
|
|
2181
|
+
if (this.pageDialogStates.has(tabId)) {
|
|
2182
|
+
return Promise.resolve();
|
|
2183
|
+
}
|
|
2184
|
+
return new Promise((resolve, reject) => {
|
|
2185
|
+
const waiter = {
|
|
2186
|
+
resolve: () => {
|
|
2187
|
+
if (waiter.timer) {
|
|
2188
|
+
clearTimeout(waiter.timer);
|
|
2189
|
+
}
|
|
2190
|
+
this.removeDialogWaiter(tabId, waiter);
|
|
2191
|
+
resolve();
|
|
2192
|
+
},
|
|
2193
|
+
reject: (error) => {
|
|
2194
|
+
if (waiter.timer) {
|
|
2195
|
+
clearTimeout(waiter.timer);
|
|
2196
|
+
}
|
|
2197
|
+
this.removeDialogWaiter(tabId, waiter);
|
|
2198
|
+
reject(error);
|
|
2199
|
+
}
|
|
2200
|
+
};
|
|
2201
|
+
waiter.timer = setTimeout(() => waiter.reject?.(new Error("Timed out waiting for dialog.")), timeoutMs);
|
|
2202
|
+
const waiters = this.dialogWaiters.get(tabId) ?? new Set();
|
|
2203
|
+
waiters.add(waiter);
|
|
2204
|
+
this.dialogWaiters.set(tabId, waiters);
|
|
2205
|
+
});
|
|
2206
|
+
}
|
|
2207
|
+
resolveDialogWaiters(tabId) {
|
|
2208
|
+
const waiters = this.dialogWaiters.get(tabId);
|
|
2209
|
+
if (!waiters) {
|
|
2210
|
+
return;
|
|
2211
|
+
}
|
|
2212
|
+
this.dialogWaiters.delete(tabId);
|
|
2213
|
+
for (const waiter of waiters) {
|
|
2214
|
+
waiter.resolve();
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
removeDialogWaiter(tabId, waiter) {
|
|
2218
|
+
const waiters = this.dialogWaiters.get(tabId);
|
|
2219
|
+
if (!waiters) {
|
|
2220
|
+
return;
|
|
2221
|
+
}
|
|
2222
|
+
waiters.delete(waiter);
|
|
2223
|
+
if (waiters.size === 0) {
|
|
2224
|
+
this.dialogWaiters.delete(tabId);
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
1924
2227
|
async actionPoint(tabId, target) {
|
|
1925
2228
|
const source = "nodeToken" in target ? ACTION_POINT_EVALUATE_SOURCE : ACTION_POINT_BY_SELECTOR_SOURCE;
|
|
1926
2229
|
const point = await evaluateBiDi(this.client, tabId, source, this.targetArg(target));
|
|
@@ -2103,7 +2406,10 @@ class BidiConnectedBrowserSession {
|
|
|
2103
2406
|
state = {
|
|
2104
2407
|
requests: [],
|
|
2105
2408
|
byRequestId: new Map(),
|
|
2106
|
-
startedAt: new Map()
|
|
2409
|
+
startedAt: new Map(),
|
|
2410
|
+
hydratedPerformanceResources: false,
|
|
2411
|
+
loadingDone: new Map(),
|
|
2412
|
+
bodyRead: new Set()
|
|
2107
2413
|
};
|
|
2108
2414
|
this.pageNetworkStates.set(tabId, state);
|
|
2109
2415
|
}
|
|
@@ -2119,7 +2425,10 @@ class BidiConnectedBrowserSession {
|
|
|
2119
2425
|
this.pageNetworkStates.set(tabId, {
|
|
2120
2426
|
requests: [],
|
|
2121
2427
|
byRequestId: new Map(),
|
|
2122
|
-
startedAt: new Map()
|
|
2428
|
+
startedAt: new Map(),
|
|
2429
|
+
hydratedPerformanceResources: false,
|
|
2430
|
+
loadingDone: new Map(),
|
|
2431
|
+
bodyRead: new Set()
|
|
2123
2432
|
});
|
|
2124
2433
|
this.pageDialogStates.delete(tabId);
|
|
2125
2434
|
}
|