Circle CI中Enzyme浅渲染测试引发Puppeteer Promise收集协议错误
问题背景
在Circle CI环境里运行Meteor测试(基于meteortesting:mocha和Puppeteer驱动)时,偶发会在所有测试执行完成后出现未处理的Promise拒绝异常,还伴随CI超时错误。异常日志如下:
W20200612-14:11:02.429(0)? (STDERR) (node:642) UnhandledPromiseRejectionWarning: Error: Protocol error (Runtime.callFunctionOn): Promise was collected
W20200612-14:11:02.429(0)? (STDERR) at Promise (/home/jenkins/humly-home/packages/hrm/node_modules/puppeteer/lib/Connection.js:183:56)
...(省略完整栈信息)
Too long with no output (exceeded 1m0s): context deadline exceeded
排查后发现,移除使用Enzyme浅渲染的单元测试后异常就消失,但测试代码本身没发现问题,而且这个异常是偶发的——每2-3次运行才会出现一次。
环境与代码信息
核心依赖版本
- meteor 1.8.3
- node 8.17.0
- puppeteer 1.20.0
- meteortesting:browser-tests@1.3.1
- meteortesting:mocha@1.1.3
- react 16.12.0
- enzyme 3.10.0
引发问题的典型Enzyme测试代码
import React from "react"; import { expect } from "chai"; import { shallow, configure } from "enzyme"; import Adapter from "enzyme-adapter-react-16"; import ExampleView from "./ExampleView"; configure({ adapter: new Adapter() }); const defaultProps = { name: "test name", text: "test value", }; describe(".../ExampleView", () => { before(() => { // methods stubs }); after(() => { // restores }); it("rendering component", () => { const props = { ...defaultProps, }; const wrapper = shallow(<ExampleView {...props} />); const specificElement = wrapper.find(".specific-class-name"); expect(specificElement.text()).to.be.equal(defaultProps.text); }); });
CI测试执行脚本
#!/bin/bash -eu cd "$(dirname "${BASH_SOURCE[0]}")"/.. export PORT="${PORT:-5000}" export ROOT_URL="http://localhost:${PORT}" export TEST_BROWSER_DRIVER=puppeteer export MOCHA_TIMEOUT=6000 cd node_modules/puppeteer/.local-chromium/linux-*/chrome-linux/ sudo chown root:root chrome_sandbox sudo chmod 4755 chrome_sandbox sudo cp -p chrome_sandbox /usr/local/sbin/chrome-devel-sandbox export CHROME_DEVEL_SANDBOX=/usr/local/sbin/chrome-devel-sandbox cd ../../../../../ meteor test --once --driver-package meteortesting:mocha --port "${PORT}" "$@"
已经尝试在最后一条命令添加-- --exit参数强制Mocha测试完成后退出,但没有效果。
问题分析
这个偶发异常大概率是meteortesting:mocha包和Puppeteer的交互冲突导致的:
- Enzyme浅渲染测试运行在Node环境中,但
meteortesting:mocha搭配Puppeteer时会启动浏览器环境,两者的Promise上下文可能存在未被正确清理的情况。 - 测试完成后,Puppeteer的CDP会话尝试调用函数时,对应的Promise已经被垃圾回收,触发Protocol错误,进而导致Node进程因未处理的Promise拒绝而挂起,最终引发CI超时。
解决方案建议
1. 分离单元测试与浏览器测试
把不需要浏览器的Enzyme单元测试和依赖Puppeteer的浏览器测试分开执行,从根源上避免环境冲突:
- 给单元测试单独写一个执行脚本,使用
meteortesting:mocha的无浏览器模式:
export TEST_BROWSER_DRIVER=none meteor test --once --driver-package meteortesting:mocha --port "${PORT}" --raw-logs
- 浏览器测试继续使用原来的Puppeteer驱动脚本,这样两种测试环境互不干扰。
2. 手动清理Puppeteer会话
在全局测试文件(比如tests/setup.js)中添加全局after钩子,手动关闭Puppeteer浏览器实例,确保所有Promise都被正确处理:
import { PuppeteerBrowser } from 'meteortesting:browser-tests'; after(async () => { // 强制关闭Puppeteer实例,清理所有会话 if (PuppeteerBrowser) { await PuppeteerBrowser.close(); } });
3. 升级兼容的依赖版本
尝试升级相关依赖到更稳定兼容的版本:
- 把
meteortesting:mocha升级到v2.x+版本,该版本对Puppeteer的兼容性有优化 - 升级Puppeteer到v2.x+版本,修复旧版本中的Promise处理bug
- 确保Enzyme和React适配器版本完全匹配(当前enzyme 3.10.0建议搭配
enzyme-adapter-react-16.11.0)
4. 调整CI资源与超时配置
在Circle CI配置中增加测试步骤的超时时间,同时给容器分配足够的CPU/内存资源——偶发问题有时和资源不足导致的垃圾回收时机异常有关:
# .circleci/config.yml示例片段 jobs: test: docker: - image: circleci/node:8.17.0-browsers steps: - checkout - run: npm install - run: name: Run Tests command: ./scripts/test.sh no_output_timeout: 2m # 延长超时时间
总结
优先尝试分离单元测试与浏览器测试的方案,这是最彻底的解决思路;其次可以通过升级依赖、手动清理会话来修复交互问题,同时调整CI资源配置降低偶发概率。
内容的提问来源于stack exchange,提问作者benjamin

