You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调用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,如果进程不再挂起,就实锤是异步资源未清理的问题(这是临时方案,不建议长期依赖)。
  • 打印当前活跃资源定位问题:在测试代码末尾添加这段代码,就能看到哪些资源没释放:
    process.on('beforeExit', () => {
      console.log('活跃句柄:', process._getActiveHandles());
      console.log('活跃请求:', process._getActiveRequests());
    });
    
    比如输出里如果有Puppeteer的WebSocket连接,就知道是浏览器没关干净;如果有Express的连接,就针对性处理服务器的问题。

修复示例代码

给你一个参考的测试结构,重点是所有关闭操作都加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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 01:55:34