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

如何在Jest中优雅复用Puppeteer模拟代码?

可复用的Puppeteer Jest模拟方案

针对你遇到的模拟代码冗长、复用性差且无法监听方法调用的问题,这里有两个适配Node.js v18.13.0和Jest v25.3.0环境的实用优化方案:

方案一:增强__mocks__全局模拟,支持调用监听

直接在项目根目录的__mocks__文件夹下创建puppeteer.js,用jest.fn()实现链式API,既全局复用,又能跟踪所有调用情况:

// __mocks__/puppeteer.js
// 模拟Page对象的核心方法
const mockPage = {
  screenshot: jest.fn().mockResolvedValue(Buffer.from('mock-screenshot-data')),
  goto: jest.fn().mockResolvedValue(true),
  // 按需添加其他需要模拟的Page方法,如setViewport、evaluate等
};

// 模拟Browser对象
const mockBrowser = {
  newPage: jest.fn().mockResolvedValue(mockPage),
  close: jest.fn().mockResolvedValue(true),
};

// 模拟Puppeteer顶层API
const mockPuppeteer = {
  launch: jest.fn().mockResolvedValue(mockBrowser),
  // 按需添加其他顶层方法,如connect、defaultArgs等
};

module.exports = mockPuppeteer;

使用方式

在测试文件开头启用全局模拟,即可直接断言调用:

jest.mock('puppeteer');
const puppeteer = require('puppeteer');
const takeScreenshot = require('./your-screenshot-function');

test('截图函数正确调用Puppeteer API', async () => {
  await takeScreenshot('https://example.com', './output.png');
  
  // 断言launch方法被调用
  expect(puppeteer.launch).toHaveBeenCalled();
  // 断言浏览器实例调用了newPage
  expect(puppeteer.launch().newPage).toHaveBeenCalled();
  // 断言截图方法使用了正确参数
  expect(mockPage.screenshot).toHaveBeenCalledWith({ path: './output.png' });
});

如果需要在单个测试中覆盖默认模拟行为,直接修改对应方法的模拟即可:

test('截图失败时抛出错误', async () => {
  mockPage.screenshot.mockRejectedValue(new Error('截图超时'));
  
  await expect(takeScreenshot('https://example.com')).rejects.toThrow('截图超时');
});

方案二:工厂函数生成定制化模拟实例

如果不同测试场景需要不同的模拟配置(比如部分测试不需要模拟goto,部分需要自定义返回值),可以写一个工厂函数来生成灵活的模拟对象:

// test/utils/mock-puppeteer.js
function createMockPuppeteer(customOverrides = {}) {
  // 合并默认配置与自定义覆盖项
  const mockPage = {
    screenshot: jest.fn().mockResolvedValue(Buffer.from('default-screenshot')),
    goto: jest.fn().mockResolvedValue(true),
    ...customOverrides.page,
  };

  const mockBrowser = {
    newPage: jest.fn().mockResolvedValue(mockPage),
    close: jest.fn().mockResolvedValue(true),
    ...customOverrides.browser,
  };

  const mockPuppeteer = {
    launch: jest.fn().mockResolvedValue(mockBrowser),
    ...customOverrides.puppeteer,
  };

  // 返回所有模拟对象,方便测试中直接断言
  return { puppeteer: mockPuppeteer, browser: mockBrowser, page: mockPage };
}

module.exports = createMockPuppeteer;

使用方式

在测试中导入工厂函数,生成符合当前场景的模拟:

const createMockPuppeteer = require('./test/utils/mock-puppeteer');
const takeScreenshot = require('./your-screenshot-function');

test('自定义截图参数时的调用逻辑', async () => {
  // 生成带自定义覆盖的模拟实例
  const { puppeteer, page } = createMockPuppeteer({
    page: {
      screenshot: jest.fn().mockResolvedValue(Buffer.from('custom-size-screenshot')),
    },
  });

  // 模拟puppeteer模块返回我们的定制实例
  jest.mock('puppeteer', () => puppeteer);
  const puppeteerMock = require('puppeteer');

  await takeScreenshot('https://example.com', './fullpage.png', { fullPage: true });
  
  expect(page.screenshot).toHaveBeenCalledWith({ path: './fullpage.png', fullPage: true });
});

关键注意事项

  • 所有异步方法(如launch、screenshot)必须用mockResolvedValue/mockRejectedValue返回Promise,适配Puppeteer的异步API规范。
  • Jest 25.3.0完全支持jest.fn()的链式断言和模拟覆盖,无需额外配置。
  • 如果项目使用ES模块(import/export),将module.exports改为export default即可,Jest会自动识别。

内容的提问来源于stack exchange,提问作者ytnkgt

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 20:15:55