@roxybrowser/playwright 2.0.2-beta.1 → 2.0.2-beta.2

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.
@@ -16005,7 +16005,7 @@ var BidiPageAdapter = class BidiPageAdapter {
16005
16005
  resultOwnership: "none"
16006
16006
  }));
16007
16007
  if (response.type === "exception") throw new Error(response.exceptionDetails.text || "BiDi evaluation failed.");
16008
- return parseSerializedEvaluationResult(extractBiDiValue(response.result));
16008
+ return parseSerializedEvaluationResult(extractBiDiValue$1(response.result));
16009
16009
  }
16010
16010
  async evaluateFunction(expression, arg) {
16011
16011
  const serializedArg = arg === void 0 ? "" : serializeForEvaluation$1(arg);
@@ -17249,11 +17249,11 @@ var BidiElementHandleAdapter = class BidiElementHandleAdapter {
17249
17249
  return this.page.selectOptionReference(this.reference(), values, options);
17250
17250
  }
17251
17251
  };
17252
- function extractBiDiValue(value) {
17253
- if (value.type === "array" && Array.isArray(value.value)) return value.value.map((entry) => extractBiDiValue(entry));
17252
+ function extractBiDiValue$1(value) {
17253
+ if (value.type === "array" && Array.isArray(value.value)) return value.value.map((entry) => extractBiDiValue$1(entry));
17254
17254
  if (value.type === "object" && Array.isArray(value.value)) {
17255
17255
  const obj = {};
17256
- for (const [key, val] of value.value) obj[key] = extractBiDiValue(val);
17256
+ for (const [key, val] of value.value) obj[key] = extractBiDiValue$1(val);
17257
17257
  return obj;
17258
17258
  }
17259
17259
  return value.value;
@@ -17645,9 +17645,9 @@ function buildFirefoxLaunchArgs(options, userDataDir, port) {
17645
17645
  ...options.args ?? []
17646
17646
  ];
17647
17647
  }
17648
- function resolveFirefoxExecutableCandidates(options, platform = currentPlatform$1(), fileExistsFn = fileExists$1) {
17648
+ function resolveFirefoxExecutableCandidates(options, platform = currentPlatform$1(), playwrightFirefoxExecutablePath, fileExistsFn = fileExists$1) {
17649
17649
  if (options.executablePath) return [options.executablePath];
17650
- return filterExistingFirefoxExecutableCandidates(defaultFirefoxExecutableCandidates(platform), platform, fileExistsFn);
17650
+ return filterExistingFirefoxExecutableCandidates([...playwrightFirefoxExecutablePath ? [playwrightFirefoxExecutablePath] : [], ...defaultFirefoxExecutableCandidates(platform)], platform, fileExistsFn);
17651
17651
  }
17652
17652
  function defaultFirefoxExecutableCandidates(platform) {
17653
17653
  const candidates = [];
@@ -73364,16 +73364,21 @@ var Response$1 = class {
73364
73364
  sections.push("### Code", "```js", ...this.code, "```");
73365
73365
  }
73366
73366
  if (this.includeSnapshot === "full") {
73367
- const snapshot = await this.context.runtime.snapshot();
73367
+ const snapshot = await reconcileSnapshotWithTabs(this.context, await this.context.runtime.snapshot());
73368
73368
  if (sections.length) sections.push("");
73369
73369
  sections.push(formatSnapshot(snapshot));
73370
73370
  }
73371
73371
  if (this.fullSnapshot) {
73372
- const snapshot = await this.context.runtime.snapshot({
73372
+ let snapshot = await reconcileSnapshotWithTabs(this.context, await this.context.runtime.snapshot({
73373
73373
  ...this.fullSnapshot.target !== void 0 ? { target: this.fullSnapshot.target } : {},
73374
73374
  ...this.fullSnapshot.depth !== void 0 ? { depth: this.fullSnapshot.depth } : {},
73375
73375
  ...this.fullSnapshot.boxes !== void 0 ? { boxes: this.fullSnapshot.boxes } : {}
73376
- });
73376
+ }));
73377
+ if (!this.fullSnapshot.filename && snapshot.text.trim().length === 0 && snapshot.url && snapshot.url !== "about:blank") snapshot = await reconcileSnapshotWithTabs(this.context, await this.context.runtime.snapshot({
73378
+ ...this.fullSnapshot.target !== void 0 ? { target: this.fullSnapshot.target } : {},
73379
+ ...this.fullSnapshot.depth !== void 0 ? { depth: this.fullSnapshot.depth } : {},
73380
+ ...this.fullSnapshot.boxes !== void 0 ? { boxes: this.fullSnapshot.boxes } : {}
73381
+ }));
73377
73382
  if (this.fullSnapshot.filename) {
73378
73383
  const resolvedFilename = await this.context.resolveOutputFile(this.fullSnapshot.filename);
73379
73384
  await writeFile(resolvedFilename, snapshot.text);
@@ -73400,6 +73405,15 @@ var Response$1 = class {
73400
73405
  };
73401
73406
  }
73402
73407
  };
73408
+ async function reconcileSnapshotWithTabs(context, snapshot) {
73409
+ const activeTab = (await context.runtime.listTabs()).find((tab) => tab.active);
73410
+ if (!activeTab) return snapshot;
73411
+ return {
73412
+ ...snapshot,
73413
+ title: activeTab.title || snapshot.title,
73414
+ url: activeTab.url || snapshot.url
73415
+ };
73416
+ }
73403
73417
  //#endregion
73404
73418
  //#region src/mcp/backend/tool.ts
73405
73419
  function defineTool$1(tool) {
@@ -73498,7 +73512,8 @@ var connect_default = [defineTool$1({
73498
73512
  description: "Attach to an existing browser and seed the active tab snapshot.",
73499
73513
  inputSchema: object({
73500
73514
  endpoint: string().min(1),
73501
- browser: _enum(["chrome", "firefox"]).default("chrome")
73515
+ browser: _enum(["chrome", "firefox"]).default("chrome"),
73516
+ sessionId: string().min(1).optional()
73502
73517
  }),
73503
73518
  type: "action"
73504
73519
  },
@@ -73507,7 +73522,8 @@ var connect_default = [defineTool$1({
73507
73522
  const result = await context.runtime.connect({
73508
73523
  protocol,
73509
73524
  endpoint: params.endpoint,
73510
- browser: params.browser === "chrome" ? "chromium" : params.browser
73525
+ browser: params.browser === "chrome" ? "chromium" : params.browser,
73526
+ ...params.sessionId ? { sessionId: params.sessionId } : {}
73511
73527
  });
73512
73528
  response.addTextResult(formatConnectResult({
73513
73529
  ...result,
@@ -74666,13 +74682,13 @@ var CDP_KEY_MAP = {
74666
74682
  async function evaluateBiDi(client, contextId, functionSource, arg) {
74667
74683
  const expression = arg === void 0 ? `(${functionSource})()` : `(${functionSource})(${JSON.stringify(arg)})`;
74668
74684
  const response = await client.scriptEvaluate({
74669
- expression,
74685
+ expression: wrapWithSerializedEvaluationResult(expression),
74670
74686
  target: { context: contextId },
74671
74687
  awaitPromise: true,
74672
74688
  resultOwnership: "none"
74673
74689
  });
74674
74690
  if (response.type === "exception") throw new Error(response.exceptionDetails?.text || "BiDi runtime evaluation failed.");
74675
- return response.result?.value;
74691
+ return parseSerializedEvaluationResult(extractBiDiValue(response.result));
74676
74692
  }
74677
74693
  async function evaluateBiDiRef(client, contextId, functionSource, arg) {
74678
74694
  const expression = arg === void 0 ? `(${functionSource})()` : `(${functionSource})(${JSON.stringify(arg)})`;
@@ -74694,6 +74710,16 @@ async function evaluateBiDiRef(client, contextId, functionSource, arg) {
74694
74710
  ...response.result?.value?.handle !== void 0 ? { handle: response.result.value.handle } : {}
74695
74711
  };
74696
74712
  }
74713
+ function extractBiDiValue(value) {
74714
+ if (!value) return;
74715
+ if (value.type === "array" && Array.isArray(value.value)) return value.value.map((entry) => extractBiDiValue(entry));
74716
+ if (value.type === "object" && Array.isArray(value.value)) {
74717
+ const obj = {};
74718
+ for (const [key, val] of value.value) obj[key] = extractBiDiValue(val);
74719
+ return obj;
74720
+ }
74721
+ return value.value;
74722
+ }
74697
74723
  function toAriaSnapshotPayload(request = {}) {
74698
74724
  return {
74699
74725
  options: normalizeAriaSnapshotOptions({
@@ -75571,10 +75597,10 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
75571
75597
  if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") throw new McpToolError("unsupported_protocol_input", `BiDi endpoint must be a ws(s) URL. Received "${parsed.protocol}".`);
75572
75598
  const client = await getBidiClientFactory()({
75573
75599
  browserName: "firefox",
75574
- webSocketUrl: normalizeFirefoxBidiEndpoint(args.endpoint)
75600
+ webSocketUrl: normalizeFirefoxBidiEndpoint(args.endpoint, args.sessionId)
75575
75601
  });
75576
75602
  const session = new BidiConnectedBrowserSession(client);
75577
- session.ownsSession = await ensureMcpBiDiSession(client, args.endpoint);
75603
+ session.ownsSession = await ensureMcpBiDiSession(client, args.endpoint, args.sessionId);
75578
75604
  await session.initialize();
75579
75605
  if ((await session.refreshTabs()).length === 0) await session.newTab();
75580
75606
  return session;
@@ -75619,7 +75645,7 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
75619
75645
  }
75620
75646
  async snapshot(request = {}) {
75621
75647
  const tabId = await this.getActiveTabId();
75622
- return toBrowserSnapshot(await evaluateBiDi(this.client, tabId, PLAYWRIGHT_ARIA_SNAPSHOT_EVALUATE_SOURCE, toAriaSnapshotPayload(request)), request, {
75648
+ return toBrowserSnapshot(await retryUntilReady(() => evaluateBiDi(this.client, tabId, PLAYWRIGHT_ARIA_SNAPSHOT_EVALUATE_SOURCE, toAriaSnapshotPayload(request))), request, {
75623
75649
  console: this.consoleSummary(tabId),
75624
75650
  consoleLink: await this.takeConsoleLink(tabId)
75625
75651
  });
@@ -76322,14 +76348,18 @@ var BidiConnectedBrowserSession = class BidiConnectedBrowserSession {
76322
76348
  return `${path.relative(process.cwd(), state.logFile)}${fromLine === state.logLine ? `#L${fromLine}` : `#L${fromLine}-L${state.logLine}`}`;
76323
76349
  }
76324
76350
  };
76325
- function normalizeFirefoxBidiEndpoint(endpoint) {
76351
+ function normalizeFirefoxBidiEndpoint(endpoint, sessionId) {
76326
76352
  const url = new URL(endpoint);
76353
+ if (sessionId) {
76354
+ url.pathname = `/session/${sessionId}`;
76355
+ return url.toString();
76356
+ }
76327
76357
  if (url.pathname === "/" || url.pathname === "") url.pathname = "/session";
76328
76358
  return url.toString();
76329
76359
  }
76330
- async function ensureMcpBiDiSession(client, endpoint) {
76360
+ async function ensureMcpBiDiSession(client, endpoint, sessionId) {
76331
76361
  await client.sessionStatus({});
76332
- if (isSessionSpecificFirefoxBidiEndpoint(endpoint)) return false;
76362
+ if (sessionId || isSessionSpecificFirefoxBidiEndpoint(endpoint)) return false;
76333
76363
  try {
76334
76364
  await client.browsingContextGetTree({});
76335
76365
  return false;
@@ -76414,6 +76444,7 @@ var McpRuntime = class {
76414
76444
  this.invalidateSnapshot();
76415
76445
  this.pendingFileUploadTarget = void 0;
76416
76446
  this.tabs = await session.newTab(url);
76447
+ if (this.snapshotMode === "none") return { tabs: this.tabs };
76417
76448
  const snapshot = this.tabs.some((tab) => tab.active) ? await this.snapshot() : void 0;
76418
76449
  return snapshot ? {
76419
76450
  tabs: this.tabs,
@@ -76427,6 +76458,7 @@ var McpRuntime = class {
76427
76458
  this.invalidateSnapshot();
76428
76459
  this.pendingFileUploadTarget = void 0;
76429
76460
  this.tabs = await session.selectTab(tab.id);
76461
+ if (this.snapshotMode === "none") return { tabs: this.tabs };
76430
76462
  const snapshot = await this.snapshot();
76431
76463
  return {
76432
76464
  tabs: this.tabs,
@@ -76440,6 +76472,7 @@ var McpRuntime = class {
76440
76472
  this.invalidateSnapshot();
76441
76473
  this.pendingFileUploadTarget = void 0;
76442
76474
  this.tabs = await session.closeTab(tab.id);
76475
+ if (this.snapshotMode === "none") return { tabs: this.tabs };
76443
76476
  const snapshot = this.tabs.some((candidate) => candidate.active) ? await this.snapshot() : void 0;
76444
76477
  return snapshot ? {
76445
76478
  tabs: this.tabs,
@@ -76448,26 +76481,49 @@ var McpRuntime = class {
76448
76481
  }
76449
76482
  async snapshot(args = {}) {
76450
76483
  const session = this.requireConnected();
76451
- this.tabs = await session.listTabs();
76452
- const activeTab = this.requireActiveTab();
76453
76484
  const requestKey = this.snapshotRequestKey(args);
76454
76485
  const request = {
76455
76486
  ...args.boxes !== void 0 ? { boxes: args.boxes } : {},
76456
76487
  ...args.depth !== void 0 ? { depth: args.depth } : {},
76457
76488
  ...args.target ? { target: this.resolveSnapshotTarget(args.target) } : {}
76458
76489
  };
76459
- const snapshot = await session.snapshot(request);
76490
+ const { activeTab, currentActiveTab, snapshot } = await this.captureStableSnapshot(session, request);
76460
76491
  this.snapshotCache = {
76461
- tabId: activeTab.id,
76492
+ tabId: currentActiveTab.id,
76462
76493
  requestKey,
76463
76494
  text: snapshot.text,
76464
76495
  refs: { ...snapshot.refs },
76465
- title: snapshot.title,
76466
- url: snapshot.url,
76496
+ title: currentActiveTab.title || snapshot.title,
76497
+ url: currentActiveTab.url || snapshot.url,
76467
76498
  ...snapshot.console ? { console: { ...snapshot.console } } : {},
76468
76499
  ...snapshot.consoleLink ? { consoleLink: snapshot.consoleLink } : {}
76469
76500
  };
76470
- return snapshot;
76501
+ return {
76502
+ ...snapshot,
76503
+ title: currentActiveTab.title || snapshot.title,
76504
+ url: currentActiveTab.url || snapshot.url
76505
+ };
76506
+ }
76507
+ async captureStableSnapshot(session, request) {
76508
+ let lastAttempt;
76509
+ for (let attempt = 0; attempt < 4; attempt += 1) {
76510
+ this.tabs = await session.listTabs();
76511
+ const activeTab = this.requireActiveTab();
76512
+ const snapshot = await session.snapshot(request);
76513
+ const refreshedTabs = await session.listTabs();
76514
+ this.tabs = refreshedTabs;
76515
+ const currentActiveTab = refreshedTabs.find((tab) => tab.active) ?? refreshedTabs.find((tab) => tab.id === activeTab.id) ?? activeTab;
76516
+ const captured = {
76517
+ activeTab,
76518
+ currentActiveTab,
76519
+ snapshot
76520
+ };
76521
+ lastAttempt = captured;
76522
+ if (snapshot.text.trim().length > 0 || currentActiveTab.url === "about:blank") return captured;
76523
+ await delay(150 * (attempt + 1));
76524
+ }
76525
+ if (!lastAttempt) throw new McpToolError("action_failed", "Unable to capture page snapshot.");
76526
+ return lastAttempt;
76471
76527
  }
76472
76528
  async click(target, opts) {
76473
76529
  const session = this.requireConnected();
@@ -78306,6 +78362,7 @@ async function createRoxyBrowserMcpInMemory(options = {}) {
78306
78362
  await bundle.server.connect(serverTransport);
78307
78363
  return {
78308
78364
  server: bundle.server,
78365
+ runtimeManager: bundle.runtimeManager,
78309
78366
  serverTransport,
78310
78367
  clientTransport,
78311
78368
  close: async () => {