【问题标题】:Race conditions in Puppeteer/Jest tests in DockerDocker 中 Puppeteer/Jest 测试中的竞争条件
【发布时间】:2025-12-07 16:40:01
【问题描述】:

我使用 jest & puppeteer 编写了一个大型测试,它在我的应用程序中做了很多事情。

当我在本地运行测试时 - 99% 的时间都通过了。

当我在 Docker 中运行测试时 - 测试在多个地方不一致地失败。几乎每次我运行测试时,它通常都会在等待选择器加载时失败。

很遗憾,我无法在此处分享我的代码,但我可以根据我所做的研究展示我为减少失败所做的工作。

  1. 在导航发生后或断言前调用 waitFor(< x ms>)
  2. 按照puppeteer docs 中针对点击事件的建议实现Promise.all 模式。像这样:
async singleClickElement(selector, page) {
    try {
      await Promise.all([
        page.waitForSelector(selector),
        page.click(selector)
      ]);
    } catch (error) {
      console.error(error)
    }
  }
  1. 搞乱sloMo 设置(考虑到我们的默认 css 转换是 0.4 秒,对我来说,似乎 17 是最成功的数字)

  2. 使用screenshots在测试失败的地方拍摄前后照片

我忘记提及的一些修改

  1. 我的参数--forceExit --runInBand --detectOpenHandles

  2. 我的超时时间增加到一分钟 jest.setTimeout(60000)

  3. 浏览器参数'--window-size=2560,1080', '--no-sandbox', '--disable-setuid-sandbox', '--enable-logging=stderr', '--v=1'

我的想法已经不多了,正在寻找有关在运行 UI 测试时如何缓解 Docker 中的竞争条件的建议。请随时提出您认为可能对我有帮助的任何建议。谢谢:D

【问题讨论】:

  • 我知道这个问题有点老了,但我遇到了完全相同的行为!我也尝试了您指出的所有组合,包括尝试许多不同风格的 docker 镜像,包括预构建的(例如 buildkite )和滚动我自己的。我也尝试过 mocha 和 jest,它们都表现出相同的行为,这让我相信它是 puppeteer 固有的。你曾经解决过这个问题吗?
  • @JohnZoidbergMD 在下面查看我的答案,希望对您有所帮助,干杯

标签: docker jestjs puppeteer race-condition


【解决方案1】:

经过数月的反复试验,我想我已经找到了一些在 Docker 中运行 Jest 和 Puppeteer 的好方法,而且几乎没有失败。

解决方案 #1:加载页面/组件时等待 HTTP 响应

Puppeteer 开箱即用,带有waitForResponse 功能,允许浏览器等待一些响应从服务器返回。在 Chrome 开发工具中,查看 Network 并重新加载您所在的页面并等待响应。

示例:假设我正在加载某个页面,并且我正在等待来自服务器的响应以包含有关一系列品牌的一些信息。当我导航到该页面时,我会调用

await page.waitForResponse(res => res.url().includes('brands'));

只有在收到此回复后,我才能继续进行测试。在所有其他方法中,我发现这很有效。

注意:每次重新加载页面时,您的响应可能会以不同的时间间隔出现。例如,我的品牌响应快到 300 毫秒,慢到 900 毫秒。 尝试查找来自服务器的带有数据的响应或最常出现的最后响应(重新加载 10 次并观看)

解决方案 #2:在继续之前检查某个元素的属性

假设您的页面将首先加载一个按钮。单击该按钮后,应在 0.5 秒的小过渡后出现另一个按钮。 您可以在继续之前检查按钮的文本内容

.$eval docs

const saveBtn = await page.$eval('.btn-selector', btn => btn.textContent);
        if (saveBtn.includes('Save')) {
    ...
    }

您还可以检查下拉列表的选定值是否在单击后出现在 DOM 中

.$ docs

        const brandsSelector = await page.$('select:nth-child(1)');
        const brandsSelectedValue = await page.evaluate(brandsSelector => brandsSelector.options[brandsSelector.selectedIndex].value, brandsSelector);

解决方案 #3:长 waitFor 调用

有时上述两种解决方案还不够,您克服困难的唯一方法是致电waitFor

await page.waitFor(3000);

我通常会尽量避免这些,但有时这是让我的测试通过并在 Docker 中的分阶段测试服务器上正常工作的唯一方法。

解决方案#4:使用waitForSelector时的visible属性

有时像waitForSelector 函数这样简单的事情可能会被忽略,因为它们非常明显,但这个函数需要一个您可以传递的options 对象

await page.waitForSelector('.selector', {visible: true});

这会等待页面中的某个元素首先可见,然后再对其执行任何其他操作。

解决方案 #5:检查您的云提供商的负载

例如,如果您使用 AWS,请确保您的实例在某个时间段内具有足够高的请求容量。我的分阶段测试一直崩溃,直到 AWS 负载最大化以每秒处理更多请求,这使得一些间歇性故障得以平息。

解决方案 #6:将所有这些解决方案一起使用(显然)

祝你好运,在差异环境中进行测试时,端到端测试可能会很痛苦,尤其是分阶段的服务器。

【讨论】: