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

NextJS应用Jest端到端测试afterAll超时与退出异常问题

NextJS端到端测试afterAll钩子超时及Jest未正常退出问题

我给NextJS应用编写了一套端到端测试,所有测试用例均已通过,但afterAll钩子触发了Jest did not exit one second after the test run has completed.的问题,其中T0003.users.test.tsx还报出钩子超时错误。

文件结构

src/
  tests/
    end-to-end/
      - T0001.login.test.tsx
      - T0002.nodes.test.tsx
      - T0003.users.test.tsx

测试脚本(package.json)

{
  "scripts": {
    "test:e2e": "jest ./src/tests/end-to-end/*.test.tsx --coverage --runInBand --verbose"
  }
}

运行测试后的错误日志

FAIL  src/tests/end-to-end/T0003.users.test.tsx (32.743 s)

  ● Test suite failed to run

    thrown: "Exceeded timeout of 20000 ms for a hook.
    Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

      203 |     });
      204 |
    > 205 |     afterAll(async () => {
          |     ^
      206 |             // Click the button to show the logout menu item
      207 |             await page.waitForSelector('#btn-header-user');
      208 |             await page.click('#btn-header-user');

      at afterAll (src/tests/end-to-end/T0003.users.test.tsx:205:2)
      at Object.describe (src/tests/end-to-end/T0003.users.test.tsx:5:1)

Test Suites: 1 failed, 2 passed, 3 total
Tests:       22 passed, 22 total
Snapshots:   0 total
Time:        61.993 s

Jest did not exit one second after the test run has completed.

This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with `--detectOpenHandles` to troubleshoot this issue.

现有情况与推测

  • T0001.login.test.tsx的afterAll仅执行browser.close(),无异常
  • T0002.nodes.test.tsx和T0003.users.test.tsx的beforeAll包含登录逻辑,afterAll包含退出+关闭浏览器逻辑,仅第三个文件出现问题
  • 推测原因:afterAll中的退出UI操作耗时超过Jest默认的20秒钩子超时,同时可能存在未终止的异步操作导致Jest无法正常退出

解决方法

1. 给afterAll钩子设置更长超时时间

直接在afterAll中指定超时参数,适配退出逻辑的耗时:

afterAll(async () => {
  // 执行退出UI操作
  await page.waitForSelector('#btn-header-user');
  await page.click('#btn-header-user');
  // 等待退出跳转或状态变更
  await page.waitForNavigation();
  // 关闭浏览器
  await browser.close();
}, 30000); // 设置30秒超时

2. 简化退出逻辑,优先保证浏览器关闭

如果UI退出操作不稳定,可加入异常捕获,失败时直接强制关闭浏览器:

afterAll(async () => {
  try {
    // 尝试执行正常退出,设置短超时避免卡住
    await page.waitForSelector('#btn-header-user', { timeout: 5000 });
    await page.click('#btn-header-user');
    await page.waitForNavigation({ timeout: 10000 });
  } catch (err) {
    console.log('退出UI操作超时,直接关闭浏览器');
  } finally {
    // 无论退出是否成功,都确保浏览器关闭
    await browser.close();
  }
}, 25000);

3. 排查未终止的异步操作

按照提示给测试脚本加上--detectOpenHandles参数,定位未关闭的资源:

{
  "scripts": {
    "test:e2e": "jest ./src/tests/end-to-end/*.test.tsx --coverage --runInBand --verbose --detectOpenHandles"
  }
}

运行后会输出未关闭的句柄信息,针对性处理这些残留异步操作。

4. 统一测试环境清理逻辑

把浏览器启动、登录、退出、关闭等逻辑抽成公共工具函数,避免重复代码,方便统一处理超时:
创建src/tests/end-to-end/utils.ts:

export async function setupBrowser() {
  const browser = await launch();
  const page = await browser.newPage();
  return { browser, page };
}

export async function cleanupTest(browser, page, timeout = 25000) {
  return new Promise(async (resolve) => {
    try {
      await page.waitForSelector('#btn-header-user', { timeout: 5000 });
      await page.click('#btn-header-user');
      await page.waitForNavigation({ timeout: 10000 });
    } catch (err) {
      console.log('清理失败,强制关闭浏览器');
    } finally {
      await browser.close();
      resolve(true);
    }
  });
}

在测试文件中引用:

import { setupBrowser, cleanupTest } from './utils';

let browser;
let page;

beforeAll(async () => {
  ({ browser, page } = await setupBrowser());
  // 执行登录逻辑
});

afterAll(async () => {
  await cleanupTest(browser, page, 30000);
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 10:05:28