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

使用Jest和Puppeteer测试事件处理器抛错时无法捕获的问题

问题:捕获事件监听器抛出的错误失败

问题描述

我需要测试元素的事件监听器是否会抛出错误,希望捕获该错误避免测试自动失败,但始终无法实现。简化场景如下:

  • 有一个.other-container元素,addClickListener函数会为其添加点击事件处理器,处理器直接抛出错误
  • 测试中触发该元素的click事件,尝试捕获错误并断言,但点击处理器的错误始终无法被捕获,测试会因该错误直接失败

尝试过将page.evaluate包裹在try..catch中,甚至添加延迟,但要么抓不到错误,要么抓到的是断言失败的错误。

相关代码及配置

测试代码(my-test.test.js)

test('other container', async () => {
    await page.goto('http://localhost:3333/index.html')
  await page.setViewport({ width: 1280, height: 800 })

  await page.waitForFunction('window.addClickListener')
  await page.waitForSelector('.other-container')

  page.on('console', message =>
      console.log(`${message.type().substring(0, 3).toUpperCase()} ${message.text()}`))

  await page.evaluate(() => { addClickListener() })

  let errorMessage = ""
  try {
    await page.evaluate(() => {
      const clickEvent = new MouseEvent('click', { bubbles: true });
      const otherContainer = document.querySelector('.other-container')
      otherContainer.dispatchEvent(clickEvent)
    })

    // 尝试添加延迟等待错误抛出
    // await page.evaluate(() => {
    //   return new Promise((resolve) => setTimeout(resolve, 1000))
    // })
  } catch (error) {
    errorMessage = error.message
  }

  expect(!!errorMessage).toBe(true)
})

业务代码(index.js)

export function addClickListener () {
  document.querySelector('.other-container').addEventListener('click', () => {
    throw new Error('other container error')
  })
}

页面文件(index.html)

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>Random Page</title>

  <link rel="stylesheet" href="http://localhost:3333/index.css">
  <script type="module">
    import { addClickListener } from 'http://localhost:3333/index.js'
    window.addClickListener = addClickListener
  </script>
</head>
<body>
  <div class="other-container"></div>
</body>
</html>

样式文件(index.css)

.other-container {
  width: 50px;
  height: 50px;
  background-color: blue;
}

依赖及配置

// dependencies
{
    "@babel/preset-env": "^7.28.0",
    "http-server": "^14.1.1",
    "jest": "^30.0.4",
    "jest-puppeteer": "^11.0.0",
    "puppeteer": "^24.17.0"
}
// jest-puppeteer.config.js
module.exports = {
  launch: {
    headless: true,
    product: 'chrome',
  },
  browserContext: 'default',
  server: {
    command: 'http-server -p 3333',
    port: 3333,
    launchTimeout: 10000,
    debug: true
  }
}

补充背景:实际是开发数据可视化组件,监听自定义init事件,未配置必填属性时触发init会抛出错误,上述是简化场景。

原因分析

事件处理器抛出的错误不会中断dispatchEvent的执行,也不会让dispatchEvent调用本身抛出异常。这类错误会被浏览器视为全局未捕获错误,触发window.onerror事件,但不会被page.evaluate外部的try/catch捕获。Puppeteer会检测到浏览器中的全局未处理错误,直接导致测试失败。

解决方案

方案一:在浏览器上下文内部捕获错误

在page.evaluate中,主动监听全局错误事件,捕获事件处理器抛出的错误并返回:

test('other container', async () => {
    await page.goto('http://localhost:3333/index.html')
    await page.setViewport({ width: 1280, height: 800 })

    await page.waitForFunction('window.addClickListener')
    await page.waitForSelector('.other-container')

    await page.evaluate(() => { addClickListener() })

    // 在浏览器上下文内部捕获错误
    const errorMessage = await page.evaluate(() => {
      return new Promise((resolve) => {
        // 监听全局错误,只触发一次
        window.addEventListener('error', (event) => {
          resolve(event.error.message);
        }, { once: true });

        // 触发点击事件
        const clickEvent = new MouseEvent('click', { bubbles: true });
        const otherContainer = document.querySelector('.other-container')
        otherContainer.dispatchEvent(clickEvent)
      })
    })

    expect(errorMessage).toBe('other container error')
})

方案二:监听Puppeteer的page.error事件

通过监听Puppeteer页面的error事件,捕获浏览器中的全局错误:

test('other container', async () => {
    await page.goto('http://localhost:3333/index.html')
    await page.setViewport({ width: 1280, height: 800 })

    await page.waitForFunction('window.addClickListener')
    await page.waitForSelector('.other-container')

    await page.evaluate(() => { addClickListener() })

    let errorMessage = "";

    // 监听page的error事件,只触发一次
    const errorPromise = new Promise((resolve) => {
      page.once('error', (error) => {
        resolve(error.message);
      });
    });

    // 触发点击事件
    await page.evaluate(() => {
      const clickEvent = new MouseEvent('click', { bubbles: true });
      const otherContainer = document.querySelector('.other-container')
      otherContainer.dispatchEvent(clickEvent)
    })

    // 等待错误被捕获
    errorMessage = await errorPromise;

    expect(errorMessage).toBe('other container error')
})

注意事项

  • 方案一中使用{ once: true }确保错误监听器只触发一次,避免内存泄漏
  • 方案二中使用page.once而不是page.on,防止后续测试受到影响
  • 两种方案都能避免测试因全局未处理错误而直接失败,同时可以捕获错误信息用于断言

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 11:14:49