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
相关产品推荐
相关产品推荐

