vitest-dev/vitest · error · Error

expect.poll() must be called inside a test

Error message

expect.poll() must be called inside a test

What it means

`expect.poll()` needs the current test's runtime context (read from the chai `vitest-test` flag) to register its `onFinished` await-guard and to drive the retry loop tied to the test lifecycle. When no test is active there is no context to attach to, so it throws immediately. This keeps poll from silently no-oping or leaking unhandled rejections outside a test.

Source

Thrown at packages/vitest/src/integrations/chai/poll.ts:67

    const state = getWorkerState()
    const defaults = state.config.expect?.poll ?? {}
    const {
      interval = defaults.interval ?? 50,
      timeout = defaults.timeout ?? 1000,
      message,
    } = options
    // @ts-expect-error private poll access
    const assertion = expect(null, message).withContext({
      poll: true,
    }) as Assertion
    fn = fn.bind(assertion)
    // injected so that domain snapshot can take over poll implementation.
    chai.util.flag(assertion, '_poll.fn', fn)
    chai.util.flag(assertion, '_poll.timeout', timeout)
    chai.util.flag(assertion, '_poll.interval', interval)
    const test = chai.util.flag(assertion, 'vitest-test') as Test | undefined
    if (!test) {
      throw new Error('expect.poll() must be called inside a test')
    }
    const proxy: any = new Proxy(assertion, {
      get(target, key, receiver) {
        const assertionFunction = Reflect.get(target, key, receiver)

        if (typeof assertionFunction !== 'function') {
          return assertionFunction instanceof chai.Assertion ? proxy : assertionFunction
        }

        if (key === 'assert') {
          return assertionFunction
        }

        if (typeof key === 'string' && unsupported.includes(key)) {
          throw new SyntaxError(
            `expect.poll() is not supported in combination with .${key}(). Use vi.waitFor() if your assertion condition is unstable.`,
          )
        }

View on GitHub (pinned to d568f8ce37)

Solutions

  1. Move the `expect.poll(...)` call inside an `it()`/`test()` body.
  2. If polling is needed in setup/teardown, use `vi.waitFor()` or `vi.waitUntil()` instead which do not require a test context.
  3. Ensure the file is actually loaded as a test file by the runner (not imported as a library).

Example fix

// before (top-level)
await expect.poll(() => store.ready).toBe(true)
// after
it('becomes ready', async () => {
  await expect.poll(() => store.ready).toBe(true)
})
Defensive patterns

Strategy: validation

Validate before calling

import { getWorkerState } from 'vitest'
const inTest = (() => { try { return !!getWorkerState().current } catch { return false } })()
if (!inTest) throw new Error('call expect.poll inside an it()/test()')

Prevention

When it happens

Trigger: Calling `expect.poll(fn)` at module top level, inside `beforeAll`/`beforeEach`/`afterEach` hooks, in a plain script, or anywhere Vitest has not bound a `Test` object to the assertion's `vitest-test` flag.

Common situations: Hoisting poll usage outside tests, refactoring shared assertion helpers that run outside a test scope, or running Vitest expect API in a standalone Node script without the runner.

Related errors


AI-assisted analysis of vitest-dev/vitest@d568f8ce37 (2026-08-03). Data as JSON: /data/errors/6fc9b2afe8efa1e9.json. Report an issue: GitHub.