#15570·webdriverio

[错误]: 在 contextDestroyed 事件中,BiDi 缓存的浏览上下文永远不会被置为无效; 传统命令会掩盖此情况,而 switchToParentFrame() 无法恢复。

作者: mccmrunal创建于 2026年9月7日更新于 2026年9月7日
标签Bug 🐛help wantedgood first pickProtocol Related

环境

  • WebdriverIO: 9.30.1 (webdriverio 独立版,不需要 CLI/框架)
  • Node.js: 24.15.0
  • Chrome/chromedriver: 152.0.7977.76/匹配
  • 操作系统: Windows 11
  • 协议: WebDriver BiDi ("webSocketUrl: true")

发生的情况

ContextManager 缓存当前浏览上下文 ID 并将其传递给 script.callFunction(packages/webdriverio/src/session/context.ts)。当该上下文由页面而不是 WebdriverIO 命令销毁时,缓存永远不会失效,因此每次后续的 browser.execute() 都会永久失败:

WebDriver Bidi 命令 "script.callFunction" 失败,错误为:
  没有这样的框架 - 上下文 5D50FE58A74D41D74F22CEF6031213AF 不存在

以下三点使此问题难以诊断,且在用户层面无法从中恢复:

  1. 浏览器宣布销毁,而 WebdriverIO 忽略了它。 构造函数仅订阅 browsingContext.navigationStarted。我验证了 browsingContext.contextDestroyed 会以准确的 ID 触发,即将过期的上下文 ID — 信息已经传输到网络中了。
  2. 传统命令持续成功,并掩盖了错误状态。 getTitle() / getUrl() 使用传统 WebDriver 来处理"当前上下文为何",因此它们正常返回,而每个 BiDi 脚本调用都失败。因此,一个健康检查如 try { await browser.getTitle() } catch { /* 重新锚定 */ } 永远无法检测到此问题。
  3. switchToParentFrame() 不能恢复。 它被列入 COMMANDS_REQUIRING_RESET,但 #onCommand 在自己的分支中早早返回:
javascript
#onCommand(event) {
  if (event.command === 'switchToParentFrame') {
    if (!this.#currentContext) return
    return this.#browser.browsingContextGetTree({}).then(...)  // <-- 返回这里
  }
  ...
  if (COMMANDS_REQUIRING_RESET.includes(event.command)) {
    this.#currentContext = undefined  // <-- 永远没有到达
  }
}

findParentContext(#currentContext, contexts) 无法在树中找到不再存在的上下文的父节点,因此它返回而不设置任何值。缓存保持过期状态。

可重现的示例

npm i [email protected] && node repro.mjs — 确定性,验证每个声明,输出约 10 行。

repro.mjs ```js /** * Repro: WebdriverIO 缓存了一个浏览上下文 ID 并永远不会发现它已被销毁。 * * npm i [email protected] && node repro.mjs * * 步骤: 切换到一个 iframe,让页面删除该 iframe,然后继续使用浏览器。预期结果: WebdriverIO 会注意到上下文已消失并重新锚定(或者至少会显示可恢复的错误,以及 switchToParentFrame() 会恢复)。实际结果: 每个 browser.execute() 永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC 命令 (getTitle/getUrl) 继续成功,并掩盖了错误状态。 * 而 switchToParentFrame() 无法恢复。 * 因此,每个 browser.execute() 都会永久失败,错误为 * script.callFunction 失败,错误为: 没有这样的框架 - 上下文 不存在 * 而 CLASSIC

内容来源: webdriverio/webdriverio