vitest-dev/vitest · error · Error

[vitest] The provider was closed.

Error message

[vitest] The provider was closed.

What it means

_throwIfClosing is called at multiple checkpoints (createContext, openBrowserPage, openPage) to abort work if the provider has begun closing. When this.closing is true it disposes the in-flight disposable, clears pages/contexts/browser, and throws so callers stop using a half-torn-down provider. close() sets this.closing = true during shutdown.

Solutions

  1. Increase test timeouts if tests are being killed mid-flight, or shorten the tests so they finish before teardown.
  2. Avoid starting new browser operations in afterAll/global teardown; the provider may already be closing.
  3. Handle SIGTERM in your CI gracefully (do not send a second kill -9 immediately).
  4. If reproducible, file a Vitest issue with the timing — operations should drain before close.
Defensive patterns

Strategy: try-catch

Validate before calling

// you cannot reliably pre-validate a race with close(), but you can avoid it:
// do not start browser operations in afterAll/global teardown.

Try / catch

try {
  await provider.openPage(sessionId, url, { parallel: false })
} catch (err) {
  if (err instanceof Error && err.message === '[vitest] The provider was closed.') {
    // provider is shutting down; abandon the operation gracefully
    return
  }
  throw err
}

Prevention

When it happens

Trigger: Any browser operation (creating a context, opening/navigating a page) racing with provider.close() — e.g., a test still running when vitest is shutting down, SIGTERM handling, or a parallel test starting after close began.

Common situations: Long-running tests interrupted by SIGTERM/SIGINT or a test timeout; forceful teardown; flaky CI where the worker is killed mid-test; calling browser APIs in a hook that outlives teardown.

Related errors


AI-assisted analysis of vitest-dev/vitest@1fa9837ec2 (2026-08-11). Data as JSON: /api/errors/dd742fa71633de0d. Report an issue: GitHub.

Appendix: source

Thrown at packages/browser-playwright/src/playwright.ts:656

  }

  async openPage(sessionId: string, url: string, options: { parallel: boolean }): Promise<void> {
    debug?.('[%s][%s] creating the browser page for %s', sessionId, this.browserName, url)
    const browserPage = await this.openBrowserPage(sessionId, options)
    debug?.('[%s][%s] browser page is created, opening %s', sessionId, this.browserName, url)
    await browserPage.goto(url, { timeout: 0 })
    await this._throwIfClosing(browserPage)
  }

  private async _throwIfClosing(disposable?: { close: () => Promise<void> }) {
    if (this.closing) {
      debug?.('[%s] provider was closed, cannot perform the action on %s', this.browserName, String(disposable))
      await disposable?.close()
      this.pages.clear()
      this.contexts.clear()
      this.browser = null
      this.browserPromise = null
      throw new Error(`[vitest] The provider was closed.`)
    }
  }

  async getCDPSession(sessionid: string): Promise<CDPSession> {
    const page = this.getPage(sessionid)
    const cdp = await page.context().newCDPSession(page)
    return {
      send: cdp.send.bind(cdp),
      on: cdp.on.bind(cdp),
      off: cdp.off.bind(cdp),
      once: cdp.once.bind(cdp),
    } as any // overloaded CDPSession type is too tricky in monorepo
  }

  async close(): Promise<void> {
    process.off('SIGTERM', this.onSIGTERM)

    debug?.('[%s] closing provider', this.browserName)

View on GitHub (pinned to 1fa9837ec2)