Playwright脚本随机报错page.evaluate:目标页面/上下文/浏览器已关闭
问题分析与解决方案
核心原因推测
长时间无交互确实是最大的可疑诱因,除此之外还有几个可能的点:
- 浏览器自动回收机制:不管是Chrome这类浏览器的后台标签页冻结策略,还是Playwright自身的隐性闲置超时配置,都会对长时间没操作的页面/上下文进行资源回收,哪怕你没主动调用
browser.close()。 - 自定义sleep的隐患:如果你的sleep只是单纯的延迟Promise,没给页面做任何"续命"操作,浏览器很容易判定页面闲置触发回收。要是sleep的实现本身有问题(比如Promise状态没处理对),也可能间接搞崩上下文。
- 页面自身的异常跳转:模态框出现前,页面可能因为自身的JS逻辑(比如会话超时自动跳转、接口报错导致页面刷新)关闭或重载,这种情况下
page.close事件可能没被正确捕获(比如页面用location.replace跳转,没触发unload事件)。 - 循环资源泄漏:多次循环后,未清理的弹窗、残留的网络请求等堆多了,浏览器触发资源限制,强制销毁上下文。
针对性解决办法
- 别直接死等26分钟:每隔几分钟做个轻量操作维持页面活跃,比如移动鼠标或者模拟点击空白区域:
await page.mouse.move(10, 10); - 调整Playwright的超时和浏览器参数:
- 初始化上下文时禁用默认超时:
const context = await browser.newContext({ defaultNavigationTimeout: 0, defaultTimeout: 0 }); - 启动浏览器时加参数关闭后台标签页自动回收:
const browser = await playwright.chromium.launch({ args: ['--disable-features=AutomaticTabDiscarding'] });
- 初始化上下文时禁用默认超时:
- 修复或替换自定义sleep:用标准的sleep实现,再加上周期性的页面活跃操作:
之后把原来的function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function sleepWithKeepAlive(page, totalMs) { const interval = 5 * 60 * 1000; // 每5分钟活跃一次 let remaining = totalMs; while (remaining > 0) { const wait = Math.min(interval, remaining); await sleep(wait); remaining -= wait; // 做个微小操作续命 await page.mouse.move(0, 0); await page.mouse.move(10, 10); } }sleep(26*60*1000)换成sleepWithKeepAlive(page, 26*60*1000)。 - 提前检查页面状态:在调用
page.evaluate前先确认页面没被关闭:if (page.isClosed()) { // 这里可以重新初始化页面或者直接进入下一轮循环 continue; } - 循环内清理资源:每次循环结束后,关掉可能残留的对话框:
try { await page.dismissDialog(); } catch (e) { // 没有对话框就忽略 }
额外排查方向
- 把浏览器改成非无头模式(
headless: false),打开开发者工具看Console和Network,有没有页面级的报错(比如会话超时、JS崩溃); - 排查网络波动或者服务器端的问题,比如服务器会不会主动断开长时间闲置的连接导致页面异常。
内容的提问来源于stack exchange,提问作者newbie
相关产品推荐
相关产品推荐

