[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
12

Configure Feed

Select the types of activity you want to include in your feed.

fix: close the pool before the Vite servers (#10725)

authored by

Vladimir and committed by
GitHub
(Jul 8, 2026, 1:28 PM +0200) 96fa6d73 325559e9

+48 -21
+4 -2
packages/browser/src/client/tester/tester-utils.ts
··· 247 247 const remainingTime = Math.floor(endTime - currentTime) 248 248 // keep some buffer to process the timeout, but always hand the provider a 249 249 // positive value so it surfaces a descriptive, source-mapped locator error 250 - // instead of letting the task timer win the race with a generic timeout 251 - options_.timeout = Math.max(remainingTime - 100, 1) 250 + // instead of letting the task timer win the race with a generic timeout; 251 + // the buffer covers the provider->server->client round-trip of the rejection, 252 + // which can exceed 100ms on loaded CI machines running several browsers 253 + options_.timeout = Math.max(remainingTime - 250, 1) 252 254 return options_ 253 255 } 254 256
+20 -10
packages/vitest/src/node/core.ts
··· 168 168 /** @internal */ _tmpDir = join(tmpdir(), nanoid()) 169 169 /** @internal */ _traces!: Traces 170 170 /** @internal */ _harness: PluginHarness 171 + /** @internal */ _exitTimeout: ReturnType<typeof setTimeout> | undefined 171 172 172 173 private isFirstRun = true 173 174 private restartsCount = 0 ··· 1515 1516 }) 1516 1517 } 1517 1518 1519 + // close the pool (and the browser pages with it) BEFORE the Vite 1520 + // servers: closing a server releases its port while automated pages may 1521 + // still be alive — a page's websocket client would auto-reconnect onto 1522 + // the next server that binds the same port and fail with "Unknown session id" 1523 + if (this.pool) { 1524 + try { 1525 + await this.pool.close?.() 1526 + } 1527 + catch (error) { 1528 + teardownErrors.push(error) 1529 + } 1530 + 1531 + this.pool = undefined 1532 + } 1533 + 1518 1534 const closePromises: unknown[] = this.projects.map(w => w.close()) 1519 1535 // close the core workspace server only once 1520 1536 // it's possible that it's not initialized at all because it's not running any tests ··· 1522 1538 closePromises.push(this.coreWorkspaceProject.close().then(() => this.vite = undefined as any)) 1523 1539 } 1524 1540 1525 - if (this.pool) { 1526 - closePromises.push((async () => { 1527 - await this.pool?.close?.() 1528 - 1529 - this.pool = undefined 1530 - })()) 1531 - } 1532 - 1533 1541 closePromises.push(...this._onClose.map(fn => fn())) 1534 1542 1535 1543 await Promise.allSettled(closePromises).then((results) => { ··· 1550 1558 * @param force If true, the process will exit immediately after closing the projects. 1551 1559 */ 1552 1560 public async exit(force = false): Promise<void> { 1553 - setTimeout(() => { 1561 + clearTimeout(this._exitTimeout) 1562 + this._exitTimeout = setTimeout(() => { 1554 1563 this.report('onProcessTimeout').then(() => { 1555 1564 console.warn(`close timed out after ${this.config.teardownTimeout}ms`) 1556 1565 ··· 1574 1583 1575 1584 process.exit() 1576 1585 }) 1577 - }, this.config.teardownTimeout).unref() 1586 + }, this.config.teardownTimeout) 1587 + this._exitTimeout.unref() 1578 1588 1579 1589 await this.close() 1580 1590 if (force) {
+1 -2
test/browser/fixtures/user-event/wheel.test.ts
··· 55 55 56 56 await (testType === 'userEvent' ? userEvent.wheel(selector, options) : selector.wheel(options)) 57 57 58 - // the wheel event is dispatched asynchronously in the browser, so poll for 59 - // it instead of asserting synchronously (matches the default-delta case above) 58 + // the browser dispatches the event asynchronously, poll like the tests above 60 59 await expect.poll(() => wheel).toHaveBeenCalledOnce() 61 60 expect(wheel.mock.calls[0][0].deltaX).toBe(deltaX) 62 61 expect(wheel.mock.calls[0][0].deltaY).toBe(deltaY)
+7 -5
test/browser/test/commands.test.ts
··· 4 4 const { readFile, writeFile, removeFile, myCustomCommand } = server.commands 5 5 6 6 it('can manipulate files', async () => { 7 - const file = './test.txt' 7 + // all browser instances run this file against the same cwd in parallel, 8 + // so the file name must be unique per instance to avoid races 9 + const file = `./test-${server.browser}.txt` 8 10 9 11 try { 10 12 await readFile(file) ··· 13 15 catch (err) { 14 16 expect(err.message).toMatch(`ENOENT: no such file or directory, open`) 15 17 if (server.platform === 'win32') { 16 - expect(err.message).toMatch('test\\browser\\test.txt') 18 + expect(err.message).toMatch(`test\\browser\\test-${server.browser}.txt`) 17 19 } 18 20 else { 19 - expect(err.message).toMatch('test/browser/test.txt') 21 + expect(err.message).toMatch(`test/browser/test-${server.browser}.txt`) 20 22 } 21 23 } 22 24 ··· 34 36 catch (err) { 35 37 expect(err.message).toMatch(`ENOENT: no such file or directory, open`) 36 38 if (server.platform === 'win32') { 37 - expect(err.message).toMatch('test\\browser\\test.txt') 39 + expect(err.message).toMatch(`test\\browser\\test-${server.browser}.txt`) 38 40 } 39 41 else { 40 - expect(err.message).toMatch('test/browser/test.txt') 42 + expect(err.message).toMatch(`test/browser/test-${server.browser}.txt`) 41 43 } 42 44 } 43 45 })
+10 -2
test/e2e/test/cancel-run.test.ts
··· 21 21 include: ['blocked-thread.test.ts'], 22 22 reporters: [{ onTestModuleStart: () => onTestModuleStart.resolve() }], 23 23 }) 24 - onTestFinished(() => vitest.close()) 24 + onTestFinished(async () => { 25 + await vitest.close() 26 + // this test stubs `process.exit` to survive `vitest.exit()`, so it also has 27 + // to disarm the force-exit watchdog that `exit()` armed — otherwise the 28 + // timer would `process.exit()` this worker `teardownTimeout` later 29 + clearTimeout(vitest._exitTimeout) 30 + }) 25 31 26 32 const stdin = new Readable({ read: () => '' }) as NodeJS.ReadStream 27 33 stdin.isTTY = true ··· 46 52 stdin.emit('data', CTRL_C) 47 53 await promise 48 54 49 - expect(onExit).toHaveBeenCalled() 55 + // `exit()` calls `process.exit` only after `close()` finishes — poll instead 56 + // of racing the teardown 57 + await expect.poll(() => onExit).toHaveBeenCalled() 50 58 }) 51 59 52 60 test('cancelling test run stops test execution immediately', async () => {
+6
test/test-utils/index.ts
··· 265 265 exitCode = process.exitCode 266 266 process.exitCode = 0 267 267 268 + // tests emulating CLI shortcuts (`q`, double CTRL+C) trigger `vitest.exit()`, 269 + // which arms an unref'd force-exit watchdog; it must be disarmed before the 270 + // real `process.exit` is restored, or it would kill this worker 271 + // `teardownTimeout` later, in the middle of a subsequent test file 268 272 if (TestRunner.getCurrentTest()) { 269 273 onTestFinished(async () => { 274 + clearTimeout(ctx?._exitTimeout) 270 275 await ctx?.close() 271 276 process.exit = exit 272 277 }) 273 278 } 274 279 else { 275 280 afterEach(async () => { 281 + clearTimeout(ctx?._exitTimeout) 276 282 await ctx?.close() 277 283 process.exit = exit 278 284 })