调用close后用Puppeteer的waitForSelector仍致Mocha测试套件挂起30秒?
Mocha测试进程挂起30秒的原因及解决
核心问题:未清理的异步资源
你遇到的30秒挂起刚好是Mocha的默认超时时间,说明测试结束后还有未释放的异步资源,Mocha会一直等待这些资源收尾才会退出进程。以下是最可能的几个原因:
1. Puppeteer资源没彻底关干净
你调用了page.close()和browser.close(),但这两个都是异步方法,如果没加await,代码会跳过等待直接执行后续逻辑,导致浏览器/页面进程还在后台运行:
- 必须用
await page.close()和await browser.close()确保关闭操作完全完成。 - 另外,页面可能存在未终止的请求(比如WebSocket、后台API请求),即使关闭页面,这些请求仍会挂起占用资源。可以在关闭前主动终止所有请求:
await page.setRequestInterception(true); page.on('request', req => req.abort());
2. Express服务器没彻底终止
Express的close()方法不会主动销毁正在处理的连接,如果测试过程中有未完成的请求,服务器会一直等待这些请求结束才关闭,拖慢进程退出。解决方法:
- 关闭服务器时,手动销毁所有活跃连接:
await new Promise(resolve => { server.close(() => { server.getConnections((err, conns) => { if (err) throw err; conns.forEach(conn => conn.destroy()); resolve(); }); }); });
3. 自定义对象(sel/text)持有未释放资源
你提到关闭sel和text报错,说明这两个对象可能没有close()方法,但它们内部可能持有定时器、文件句柄或网络连接未清理:
- 先明确sel/text是什么库的实例:如果是cheerio这类内存DOM解析器,不需要关闭;如果是带IO操作的解析器,查官方文档找正确的销毁方法,不要强行调用不存在的
close()。 - 检查测试中是否使用了
setTimeout/setInterval,如果有,必须用clearTimeout/clearInterval手动清除。
4. Mocha异步处理不规范
如果测试用例里的异步操作没正确用async/await或done回调,Mocha会误以为还有任务在运行,一直等到超时。确保所有异步步骤都用async/await包裹,不要混用done和async/await。
快速排查步骤
- 先加
--exit参数跑测试:PARSE_PAGE=true npx mocha index.spec.js --exit,如果进程不再挂起,就实锤是异步资源未清理的问题(这是临时方案,不建议长期依赖)。 - 打印当前活跃资源定位问题:在测试代码末尾添加这段代码,就能看到哪些资源没释放:
比如输出里如果有Puppeteer的WebSocket连接,就知道是浏览器没关干净;如果有Express的连接,就针对性处理服务器的问题。process.on('beforeExit', () => { console.log('活跃句柄:', process._getActiveHandles()); console.log('活跃请求:', process._getActiveRequests()); });
修复示例代码
给你一个参考的测试结构,重点是所有关闭操作都加await,并清理请求和连接:
const puppeteer = require('puppeteer'); const express = require('express'); const app = express(); describe('页面解析测试', () => { let browser, page, server; before(async () => { // 启动服务器 server = await app.listen(3000); // 启动浏览器 browser = await puppeteer.launch(); page = await browser.newPage(); }); it('验证页面解析逻辑', async () => { if (process.env.PARSE_PAGE) { await page.goto('http://localhost:3000'); await page.waitForSelector('.target'); // 你的解析操作 } // 断言逻辑 }); after(async () => { if (process.env.PARSE_PAGE) { // 终止页面所有请求 await page.setRequestInterception(true); page.on('request', req => req.abort()); // 等待页面、浏览器关闭完成 await page.close(); await browser.close(); } // 关闭服务器并销毁连接 await new Promise(resolve => { server.close(() => { server.getConnections((err, conns) => { if (err) throw err; conns.forEach(conn => conn.destroy()); resolve(); }); }); }); }); });
内容的提问来源于stack exchange,提问作者Matt Welke
相关产品推荐
相关产品推荐

