@roxybrowser/playwright 2.0.2-beta.4 → 2.0.2-beta.5

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.
@@ -6,7 +6,29 @@ export declare class RoxyBrowserType implements BrowserType {
6
6
  private readonly adapterFactories;
7
7
  constructor(browserName: NonNullable<BrowserConnectOptions["browserName"]>, adapterFactories: Record<SupportedProtocol, ProtocolBrowserAdapterFactory>);
8
8
  launch(options?: LaunchOptions): Promise<Browser>;
9
- connect(options: BrowserConnectOptions): Promise<Browser>;
9
+ /**
10
+ * Connect to an already-running browser over a WebSocket endpoint.
11
+ *
12
+ * ⚠️ DIVERGENCE FROM PLAYWRIGHT — DO NOT "FIX" THIS BACK TO CDP-ONLY:
13
+ *
14
+ * In upstream Playwright, `BrowserType.connect()` is a CDP-only operation: it
15
+ * connects to a running Chromium over the DevTools Protocol. Firefox/WebKit are
16
+ * not supported here (Playwright routes BiDi through `connectOverCDP`-style
17
+ * helpers or its own internal plumbing, not this method).
18
+ *
19
+ * Roxy intentionally changes that semantics. This method dispatches on
20
+ * `browserName` so a single entry point serves both browser families:
21
+ * - chromium → CDP (ws:// DevTools endpoint)
22
+ * - firefox → BiDi (ws:// WebDriver BiDi endpoint)
23
+ *
24
+ * This is deliberate, not a bug. If you are an agent "normalizing" this code to
25
+ * match Playwright upstream, STOP — the dual-protocol dispatch is the feature.
26
+ * To force CDP specifically, call `connectOverCDP()` instead, which remains
27
+ * chromium-only and CDP-only by design (see the guard below).
28
+ *
29
+ * `options.protocol` may still be passed to override the per-browser default.
30
+ */
31
+ connect(endpointURL: string, options?: ConnectOverCDPOptions): Promise<Browser>;
10
32
  connectOverCDP(endpointURL: string, options?: ConnectOverCDPOptions): Promise<Browser>;
11
33
  connectOverCDP(progress: Progress, endpointURL: string, options?: ConnectOverCDPOptions): Promise<Browser>;
12
34
  private connectBrowser;
@@ -1 +1 @@
1
- {"version":3,"file":"browserType.d.ts","sourceRoot":"","sources":["../src/browserType.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,uBAAuB,CAAC;AAC3E,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,KAAK,EACV,qBAAqB,EACrB,qBAAqB,EACrB,aAAa,EACb,QAAQ,EACR,iBAAiB,EAClB,MAAM,oBAAoB,CAAC;AAE5B,qBAAa,eAAgB,YAAW,WAAW;IAE/C,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,gBAAgB;gBADhB,WAAW,EAAE,WAAW,CAAC,qBAAqB,CAAC,aAAa,CAAC,CAAC,EAC9D,gBAAgB,EAAE,MAAM,CAAC,iBAAiB,EAAE,6BAA6B,CAAC;IAGvF,MAAM,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;IAQrD,OAAO,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,OAAO,CAAC;IAQzD,cAAc,CAClB,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,OAAO,CAAC;IACb,cAAc,CAClB,QAAQ,EAAE,QAAQ,EAClB,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,OAAO,CAAC;YAoCL,cAAc;CAqB7B;AAED,eAAO,MAAM,QAAQ,EAAE,WAGrB,CAAC;AAEH,eAAO,MAAM,OAAO,EAAE,WAGpB,CAAC"}
1
+ {"version":3,"file":"browserType.d.ts","sourceRoot":"","sources":["../src/browserType.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,uBAAuB,CAAC;AAC3E,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,KAAK,EACV,qBAAqB,EACrB,qBAAqB,EACrB,aAAa,EACb,QAAQ,EACR,iBAAiB,EAClB,MAAM,oBAAoB,CAAC;AAE5B,qBAAa,eAAgB,YAAW,WAAW;IAE/C,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,gBAAgB;gBADhB,WAAW,EAAE,WAAW,CAAC,qBAAqB,CAAC,aAAa,CAAC,CAAC,EAC9D,gBAAgB,EAAE,MAAM,CAAC,iBAAiB,EAAE,6BAA6B,CAAC;IAGvF,MAAM,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;IAQ3D;;;;;;;;;;;;;;;;;;;;;OAqBG;IACG,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,OAAO,CAAC;IAS/E,cAAc,CAClB,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,OAAO,CAAC;IACb,cAAc,CAClB,QAAQ,EAAE,QAAQ,EAClB,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,OAAO,CAAC;YAoCL,cAAc;CAqB7B;AAED,eAAO,MAAM,QAAQ,EAAE,WAGrB,CAAC;AAEH,eAAO,MAAM,OAAO,EAAE,WAGpB,CAAC"}
@@ -16,11 +16,34 @@ export class RoxyBrowserType {
16
16
  protocol: options.protocol ?? (this.browserName === "firefox" ? "bidi" : "cdp")
17
17
  });
18
18
  }
19
- async connect(options) {
19
+ /**
20
+ * Connect to an already-running browser over a WebSocket endpoint.
21
+ *
22
+ * ⚠️ DIVERGENCE FROM PLAYWRIGHT — DO NOT "FIX" THIS BACK TO CDP-ONLY:
23
+ *
24
+ * In upstream Playwright, `BrowserType.connect()` is a CDP-only operation: it
25
+ * connects to a running Chromium over the DevTools Protocol. Firefox/WebKit are
26
+ * not supported here (Playwright routes BiDi through `connectOverCDP`-style
27
+ * helpers or its own internal plumbing, not this method).
28
+ *
29
+ * Roxy intentionally changes that semantics. This method dispatches on
30
+ * `browserName` so a single entry point serves both browser families:
31
+ * - chromium → CDP (ws:// DevTools endpoint)
32
+ * - firefox → BiDi (ws:// WebDriver BiDi endpoint)
33
+ *
34
+ * This is deliberate, not a bug. If you are an agent "normalizing" this code to
35
+ * match Playwright upstream, STOP — the dual-protocol dispatch is the feature.
36
+ * To force CDP specifically, call `connectOverCDP()` instead, which remains
37
+ * chromium-only and CDP-only by design (see the guard below).
38
+ *
39
+ * `options.protocol` may still be passed to override the per-browser default.
40
+ */
41
+ async connect(endpointURL, options) {
20
42
  return this.connectBrowser({
21
- ...options,
22
- browserName: options.browserName ?? this.browserName,
23
- protocol: options.protocol ?? (this.browserName === "firefox" ? "bidi" : "cdp")
43
+ browserName: this.browserName,
44
+ protocol: this.browserName === "chromium" ? "cdp" : "bidi",
45
+ wsEndpoint: endpointURL,
46
+ ...options
24
47
  });
25
48
  }
26
49
  async connectOverCDP(progressOrEndpointURL, endpointURLOrOptions, maybeOptions = {}) {
@@ -1 +1 @@
1
- {"version":3,"file":"browserType.js","sourceRoot":"","sources":["../src/browserType.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"browserType.js","sourceRoot":"","sources":["../src/browserType.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAWrE,MAAM,OAAO,eAAe;IAEP;IACA;IAFnB,YACmB,WAA8D,EAC9D,gBAA0E;QAD1E,gBAAW,GAAX,WAAW,CAAmD;QAC9D,qBAAgB,GAAhB,gBAAgB,CAA0D;IAC1F,CAAC;IAEJ,KAAK,CAAC,MAAM,CAAC,UAAyB,EAAE;QACtC,OAAO,IAAI,CAAC,cAAc,CAAC;YACzB,GAAG,OAAO;YACV,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;SAChF,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,KAAK,CAAC,OAAO,CAAC,WAAmB,EAAE,OAA+B;QAChE,OAAO,IAAI,CAAC,cAAc,CAAC;YACzB,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,QAAQ,EAAE,IAAI,CAAC,WAAW,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM;YAC1D,UAAU,EAAE,WAAW;YACvB,GAAG,OAAO;SACX,CAAC,CAAC;IACL,CAAC;IAWD,KAAK,CAAC,cAAc,CAClB,qBAAwC,EACxC,oBAAqD,EACrD,eAAsC,EAAE;QAExC,MAAM,CAAC,QAAQ,EAAE,WAAW,EAAE,OAAO,CAAC,GACpC,OAAO,qBAAqB,KAAK,QAAQ;YACvC,CAAC,CAAC,CAAC,SAAS,EAAE,qBAAqB,EAAE,CAAC,oBAAoB,IAAI,EAAE,CAA0B,CAAC;YAC3F,CAAC,CAAC,CAAC,qBAAqB,EAAE,oBAA8B,EAAE,YAAY,CAAC,CAAC;QAE5E,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC;QACtC,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YACjD,MAAM,IAAI,KAAK,CACb,0EAA0E,QAAQ,CAAC,QAAQ,IAAI,CAChG,CAAC;QACJ,CAAC;QAED,IAAI,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,mEAAmE,CAAC,CAAC;QACvF,CAAC;QAED,IAAI,IAAI,CAAC,WAAW,KAAK,UAAU,EAAE,CAAC;YACpC,MAAM,IAAI,KAAK,CAAC,qEAAqE,CAAC,CAAC;QACzF,CAAC;QAED,MAAM,QAAQ,EAAE,GAAG,EAAE,CAAC,0BAA0B,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;QAEpE,OAAO,IAAI,CAAC,cAAc,CAAC;YACzB,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,QAAQ,EAAE,KAAK;YACf,UAAU,EAAE,WAAW;YACvB,GAAG,OAAO;SACX,CAAC,CAAC;IACL,CAAC;IAEO,KAAK,CAAC,cAAc,CAAC,OAA8B;QACzD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,KAAK,CAAC;QAC3C,MAAM,cAAc,GAAG,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QACvD,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC;YACpC,GAAG,OAAO;YACV,QAAQ;SACT,CAAC,CAAC;QAEH,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;QAExB,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;QACxC,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;QAC3C,OAAO,IAAI,WAAW,CACpB,OAAO,EACP,OAAO,EACP,0BAA0B,CAAC,OAAO,CAAC,KAAK,CAAC,EACzC,OAAO,CAAC,WAAW,IAAI,IAAI,CAAC,WAAW,EACvC,IAAI,EACJ,UAAU,CACX,CAAC;IACJ,CAAC;CACF;AAED,MAAM,CAAC,MAAM,QAAQ,GAAgB,IAAI,eAAe,CAAC,UAAU,EAAE;IACnE,GAAG,EAAE,IAAI,wBAAwB,EAAE;IACnC,IAAI,EAAE,IAAI,yBAAyB,EAAE;CACtC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,OAAO,GAAgB,IAAI,eAAe,CAAC,SAAS,EAAE;IACjE,GAAG,EAAE,IAAI,wBAAwB,EAAE;IACnC,IAAI,EAAE,IAAI,yBAAyB,EAAE;CACtC,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"connectedBrowser.d.ts","sourceRoot":"","sources":["../../src/mcp/connectedBrowser.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAOV,uBAAuB,EACvB,sBAAsB,EAOvB,MAAM,YAAY,CAAC;AAswGpB,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC,uBAAuB,CAAC,CAMlC"}
1
+ {"version":3,"file":"connectedBrowser.d.ts","sourceRoot":"","sources":["../../src/mcp/connectedBrowser.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAOV,uBAAuB,EACvB,sBAAsB,EAOvB,MAAM,YAAY,CAAC;AA+0GpB,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC,uBAAuB,CAAC,CAMlC"}
@@ -10,6 +10,21 @@ import { ACTION_POINT_EVALUATE_SOURCE, ACTION_POINT_BY_SELECTOR_SOURCE } from ".
10
10
  function delay(ms) {
11
11
  return new Promise((resolve) => setTimeout(resolve, ms));
12
12
  }
13
+ async function withBiDiTimeout(promise, timeoutMs) {
14
+ let timer;
15
+ try {
16
+ return await Promise.race([
17
+ promise,
18
+ new Promise((_, reject) => {
19
+ timer = setTimeout(() => reject(new Error(`Timed out after ${timeoutMs}ms.`)), timeoutMs);
20
+ })
21
+ ]);
22
+ }
23
+ finally {
24
+ if (timer)
25
+ clearTimeout(timer);
26
+ }
27
+ }
13
28
  const chromeRemoteInterface = ("default" in cdpModule
14
29
  ? cdpModule.default
15
30
  : cdpModule);
@@ -1803,10 +1818,28 @@ class BidiConnectedBrowserSession {
1803
1818
  parameters: { pointerType: "mouse" },
1804
1819
  actions: pointerActions
1805
1820
  });
1806
- await this.client.inputPerformActions({
1821
+ // TODO(bidi): A synchronous alert()/confirm()/prompt() opened by the click
1822
+ // blocks the page's main thread, and in Firefox that also wedges the BiDi
1823
+ // transport: inputPerformActions never resolves while the modal is open.
1824
+ // Unlike CDP (where only the mouse-release call blocks), BiDi dispatches
1825
+ // the whole pointer sequence as one atomic command, so we cannot split it.
1826
+ // Mitigation (NOT a full fix): race the action against the dialog waiter so
1827
+ // a dialog-opening click resolves instead of hanging forever. The residual
1828
+ // performPromise is intentionally left dangling; it resolves later once the
1829
+ // dialog is dismissed.
1830
+ //
1831
+ // KNOWN-UNRESOLVED: even with this race,后续的 BiDi 命令在模态框打开期间仍可能
1832
+ // 整体卡死(见 handleDialog 的 TODO)。Firefox/geckodriver 在 alert 模态下会
1833
+ // 阻塞几乎所有 BiDi 命令,这是浏览器/驱动层的限制,本适配器无法绕过。
1834
+ const performPromise = this.client.inputPerformActions({
1807
1835
  context: tabId,
1808
1836
  actions
1809
1837
  });
1838
+ await Promise.race([
1839
+ performPromise,
1840
+ this.waitForDialog(tabId, options.clickHoldMs + 5000)
1841
+ ]);
1842
+ performPromise.catch(() => { });
1810
1843
  await this.client.inputReleaseActions({ context: tabId }).catch(() => { });
1811
1844
  }
1812
1845
  async drag(start, end, options) {
@@ -2062,11 +2095,19 @@ class BidiConnectedBrowserSession {
2062
2095
  throw new McpToolError("no_dialog", "No dialog visible.");
2063
2096
  }
2064
2097
  this.pageDialogStates.delete(tabId);
2065
- await this.client.browsingContextHandleUserPrompt({
2098
+ // TODO(bidi): Firefox's browsingContext.handleUserPrompt can stall while a
2099
+ // modal is open — 实测在 alert/confirm 模态下 geckodriver 对该命令的响应会
2100
+ // 长时间不返回(实测 60s+ 不返回,最终靠 MCP 客户端超时才解脱)。这里用
2101
+ // withBiDiTimeout 兜底:最多等 5s 后强制 reject,避免工具调用无限挂起。
2102
+ // 这只是“快速失败”的缓解,并未真正解决“模态框打开时几乎所有 BiDi 命令都
2103
+ // 被卡死”的底层问题。CDP 下这一路径是可靠的,BiDi 暂只能参考 CDP 思路。
2104
+ // KNOWN-UNRESOLVED: 若在模态框打开期间调用本命令,前序的 refreshTabs
2105
+ // (browsingContextGetTree) 等也可能先一步卡死,导致整个 tool 调用超时。
2106
+ await withBiDiTimeout(this.client.browsingContextHandleUserPrompt({
2066
2107
  context: tabId,
2067
2108
  accept,
2068
2109
  ...(promptText !== undefined ? { userText: promptText } : {})
2069
- });
2110
+ }), 5_000);
2070
2111
  }
2071
2112
  async hasDialog() {
2072
2113
  return this.pageDialogStates.size > 0;
@@ -2110,6 +2151,15 @@ class BidiConnectedBrowserSession {
2110
2151
  }`);
2111
2152
  }
2112
2153
  async initialize() {
2154
+ // 顺序很关键:必须先 attachBiDiListeners(),再 sessionSubscribe()。
2155
+ // 实测 Firefox:sessionSubscribe 返回后事件会立即开始涌入;若此刻监听器
2156
+ // 还没注册,最早的一批 network.beforeRequestSent(页面导航/资源请求)会落进
2157
+ // “no registered listener” 分支被静默丢弃,导致 network 列表里缺首页请求。
2158
+ // 调试时观察到 [DEBUG bidi client no-listener] network.beforeRequestSent 连续
2159
+ // 出现几十次,正是此问题。先注册监听器即可避免丢事件。
2160
+ // TODO(bidi): 即便修了顺序,BiDi 网络捕获仍有状态时序问题,见
2161
+ // handleResponseCompleted / networkRequests 的 TODO。
2162
+ this.attachBiDiListeners();
2113
2163
  await this.client.sessionSubscribe({
2114
2164
  events: [
2115
2165
  "browsingContext.userPromptOpened",
@@ -2125,7 +2175,6 @@ class BidiConnectedBrowserSession {
2125
2175
  maxEncodedDataSize: 10_000_000
2126
2176
  }).catch(() => undefined);
2127
2177
  this.responseDataCollector = collectorResult?.collector;
2128
- this.attachBiDiListeners();
2129
2178
  }
2130
2179
  attachBiDiListeners() {
2131
2180
  this.attachBiDiListener("log.entryAdded", (payload) => this.handleLogEntry(payload));
@@ -2284,6 +2333,11 @@ class BidiConnectedBrowserSession {
2284
2333
  type: event.type ?? "alert",
2285
2334
  ...(event.defaultValue !== undefined ? { defaultPrompt: event.defaultValue } : {})
2286
2335
  });
2336
+ // 这里必须 resolve 对话框等待器:BiDi 的 click 会 race inputPerformActions
2337
+ // 与 waitForDialog(见 click 注释)。若不在此 resolve,alert() 触发后
2338
+ // waitForDialog 会一直 pending,click 永久挂起。CDP 侧的
2339
+ // javascriptDialogOpening 处理也调用了 resolveDialogWaiters,两侧需对齐。
2340
+ this.resolveDialogWaiters(event.context);
2287
2341
  }
2288
2342
  handleBeforeRequestSent(payload) {
2289
2343
  const event = parseBidiNetworkEvent(payload);
@@ -2304,6 +2358,12 @@ class BidiConnectedBrowserSession {
2304
2358
  request.url = event.request.url;
2305
2359
  request.resourceType = normalizeResourceType(event.request.destination);
2306
2360
  request.requestHeaders = normalizeHeaders(bidiHeadersToRecord(event.request.headers));
2361
+ // TODO(bidi): BiDi 的 network.beforeRequestSent 只给 bodySize,不内联 POST
2362
+ // body。这里只置了个空串占位(requestBody ??= ""),真实 POST 体从未填充,
2363
+ // 因此 browser_network_request 的 part="request-body" 在 BiDi 下只能拿到空串。
2364
+ // CDP 侧通过 Network.requestWillBeSent 的 request.postData 直接拿到 body。
2365
+ // BiDi 若要拿到 body 需另发 network.getRequestPostData 请求(Firefox 支持不稳),
2366
+ // 暂未实现 —— 这是已知缺口,等 geckodriver 稳定后再补。
2307
2367
  if (event.request.bodySize !== undefined && event.request.bodySize > 0) {
2308
2368
  request.requestBody ??= "";
2309
2369
  }
@@ -2340,6 +2400,17 @@ class BidiConnectedBrowserSession {
2340
2400
  if (startedAt !== undefined && event.timestamp !== undefined) {
2341
2401
  request.durationMs = Math.round(event.timestamp - startedAt);
2342
2402
  }
2403
+ // TODO(bidi): BiDi 网络事件是乱序/延迟到达的,且 status 与 body 的可用时机
2404
+ // 不可靠。实测 Firefox:
2405
+ // - beforeRequestSent 与 responseCompleted 之间存在竞态,waitForNetworkRequest
2406
+ // 在 body/status 尚未就绪时就可能匹配到请求并返回;
2407
+ // - 随后再次 browser_network_requests 时,同一个 POST /api 请求有时会从列表
2408
+ // 里“消失”(疑似 responseCompleted 到达途中 ensureNetworkRequest 重建了条目
2409
+ // 或上下文切换所致,未完全定位)。
2410
+ // 这导致 BiDi 下无法像 CDP 那样做强一致的网络契约断言(=> [status] OK 全匹配)。
2411
+ // 这里仅在 responseCompleted 时尽力补 body;status 缺失的窗口由调用方容忍。
2412
+ // KNOWN-UNRESOLVED: BiDi 网络捕获的一致性问题,需等 Firefox/geckodriver 事件
2413
+ // 时序稳定后再追,或改用 network.getDataCollector 统一采集。
2343
2414
  if (canReadResponseBody(request)) {
2344
2415
  const body = await this.getResponseBody(event.request.request).catch(() => undefined);
2345
2416
  if (body !== undefined) {