我有一个Node.js Mocha测试套件(我已经基于我试图为其创建自动化测试的真实应用程序创建了一个最小复制).

package.json:

{
  "name": "puppeteer-mocha-hang-repro",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "chai": "4.3.7",
    "express": "4.18.2",
    "mocha": "10.2.0",
    "puppeteer": "19.6.2"
  }
}

index.spec.js:

const expect = require('chai').expect;
const express = require('express');
const puppeteer = require('puppeteer');

const webServerPort = 3001;

describe('test suite', function () {
    this.timeout(10000);

    let webServer;
    let browser;

    beforeEach(async () => {
        // Start web server using Express
        const app = express();
        app.get('/', (_, res) => {
            res.send('<html>Hello, World from the <span id="source">Express web server</span>!</html>');
        });
        webServer = app.listen(webServerPort, () => {
            console.log(`Web server listening on port ${webServerPort}.`);
        });

        // Start browser using Puppeteer
        browser = await puppeteer.launch();
        console.log('Browser launched.');
    });

    afterEach(async () => {
        // Stop browser
        await browser.close();
        console.log('Browser closed.');

        // Stop web server
        await webServer.close();
        console.log('Web server closed.');
    });

    it('should work', async () => {
        const page = await browser.newPage();

        await page.goto(`http://localhost:${webServerPort}/`);
        console.log('Went to root page of web server via Puppeteer.');

        if (process.env['PARSE_PAGE'] === 'true') {
            const sel = await page.waitForSelector('#source');
            const text = await sel.evaluate(el => el.textContent);
            console.log('According to Puppeteer, the text content of the #source element on the page is:', text);
            expect(text).eql('Express web server');
        }

        await page.close();
        console.log('Page closed.');
    });
});

如果我使用命令npx mocha index.spec.js运行测试套件,这将导致跳过第45-48行,测试套件通过,Mocha进程快速结束:

$ time npx mocha index.spec.js


  test suite
Web server listening on port 3001.
Browser launched.
Went to root page of web server via Puppeteer.
Page closed.
    ✔ should work (70ms)
Browser closed.
Web server closed.


  1 passing (231ms)


real    0m0.679s
user    0m0.476s
sys     0m0.159s

请注意,它以0.679秒结束.

如果我使用命令PARSE_PAGE=true npx mocha index.spec.js运行它,这会导致我的任何代码都不会被跳过,测试很快就会通过,但进程会挂起大约30秒:

$ time PARSE_PAGE=true npx mocha index.spec.js


  test suite
Web server listening on port 3001.
Browser launched.
Went to root page of web server via Puppeteer.
According to Puppeteer, the text content of the #source element on the page is: Express web server
Page closed.
    ✔ should work (79ms)
Browser closed.
Web server closed.


  1 passing (236ms)


real    0m30.631s
user    0m0.582s
sys     0m0.164s

请注意,它以30.631秒完成.

我怀疑这意味着我忘记了调用像close这样的函数.但是,我在Express Web服务器、Puppeteer浏览器和Puppeteer页面上呼叫close.我try 在不跳过seltext这两个代码的情况下,对我使用的对象调用close.但如果我try 这样做,我会收到错误消息,告诉我这些对象没有这样的功能.

系统详细信息:

$ node --version
v18.13.0
$ npm --version
9.4.0
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.1 LTS
Release:        22.04
Codename:       jammy
$ uname -r
5.10.16.3-microsoft-standard-WSL

推荐答案

更新:此行为是由#9612修复的回归,部署为19.6.3.要解决该问题,请升级到19.6.3(如果您出于某种原因正在使用较旧的 puppeteer 程序,则可以降级到<;=19.6.0).

请看下面的原始答案.


即使没有摩卡,我也能重现这段悬念.这似乎是Puppeteer版本19.6.1和19.6.2中的一个错误.下面是一个最小的例子:

const puppeteer = require("puppeteer"); // 19.6.1 or 19.6.2

const html = `<!DOCTYPE html><html><body><p>hi</p></body></html>`;

let browser;
(async () => {
  browser = await puppeteer.launch();
  const [page] = await browser.pages();
  await page.setContent(html);
  const el = await page.waitForSelector("p");
  console.log(await el.evaluate(el => el.textContent));
})()
  .catch(err => console.error(err))
  .finally(async () => {
    await browser?.close();
    console.log("browser closed");
  });

罪魁祸首是page.waitForSelector,它似乎在解析后运行了整整30秒的默认超时,以某种方式阻止了进程退出.我已经在Puppeteer的GitHub回购上开了issue #9610.

可能的解决方法:

  • 评级下调至19.6.0.
  • 避免使用waitForSelector,因为您想要的数据在静态HTML中(但可能不适用于您的实际页面).
  • 调用page.waitForSelector("#source", {timeout: 0})似乎解决了这个问题,如果在脚本中使用,可能会永远停滞(对于mocha来说不是问题,因为测试将超时).
  • 使用page.waitForSelector("#source", {timeout: 1000})调用可以减少延迟的影响,如果加载元素的时间超过一秒,则存在误报的风险.这似乎不是堆叠的,所以如果您在许多测试中使用1-3秒的延迟,那么mocha应该在所有测试完成后的几秒钟内退出,而不是所有waitForSelector个调用的所有延迟的总和.不过,这在大多数脚本中并不实用.
  • npx mocha --exit index.spec.js英里.不推荐--这会 suppress 该问题.

我不确定这种行为是否只适用于waitForTimeout,或者它是否适用于其他waitFor家族的方法.

顺便说一句,从技术上讲,您的服务器侦听和关闭呼叫都是竞争条件,因此:

await new Promise(resolve =>
  webServer = app.listen(webServerPort, () => {
    console.log(`Web server listening on port ${webServerPort}.`);
    resolve();
  })
);

await new Promise(resolve => webServer.close(() => resolve()));

系统详细信息:

$ node --version
v18.7.0
$ npm --version
9.3.0
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.1 LTS
Release:    22.04
Codename:   jammy
$ uname -r
5.15.0-56-generic

我也确认了在Windows 10上的行为.

Node.js相关问答推荐

GraphQL MongoDB Mongoose填充字段未获取多个类别

try 使用Express和连接池将数据插入MySQL数据库时出现拒绝访问错误

一个函数中的两个依赖的NodeJS数据库操作.如果第二个失败了怎么办?

FiRestore UPDATE方法引发错误:&Quot;错误:13内部:收到代码为1&Quot;的RST_STREAM

如何模拟 mysql2 `getConnection`

在全局对象上声明的函数

是否可以在 NodeJS 代码库中的每个函数之前和之后添加 console.log?

将 null 推入管道后,node.js 可写完成未发出

有没有办法判断 UUID 是否是使用 node.js 中的特定命名空间生成的?

为什么 nginx 不将我的 react index.html 作为后备服务

MongoDB - 查找查询以判断是否存在少量字段,结合字段上的 AND

每秒从套接字传来的数据有哪些存储方式?

NodeJS 中的流 API 数据如何流动?

如何监控 node.js 上的网络,类似于 chrome/firefox 开发者工具?

npm install 给出警告,npm audit fix 不起作用

如何解决 Socket.io 404(未找到)错误?

nodeJS - 如何使用 express 创建和读取会话

NodeJS 中的 HTTPS 请求

Puppeteer 等待所有图像加载然后截图

CORS 错误:预检响应中的 Access-Control-Allow-Headers 不允许请求标头字段授权