环境
- 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 不存在
以下三点使此问题难以诊断,且在用户层面无法从中恢复:
- 浏览器宣布销毁,而 WebdriverIO 忽略了它。 构造函数仅订阅
browsingContext.navigationStarted。我验证了 browsingContext.contextDestroyed 会以准确的 ID 触发,即将过期的上下文 ID — 信息已经传输到网络中了。
- 传统命令持续成功,并掩盖了错误状态。
getTitle() / getUrl() 使用传统 WebDriver 来处理"当前上下文为何",因此它们正常返回,而每个 BiDi 脚本调用都失败。因此,一个健康检查如 try { await browser.getTitle() } catch { /* 重新锚定 */ } 永远无法检测到此问题。
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