vitest-dev/vitest · critical · Error

Browser provider is not defined for the project

Error message

Browser provider is not defined for the project "${child.project.name}". This is a bug in Vitest. Please, open a new issue with a reproduction.

What it means

pluginContext builds the cached browser-runner context module that is shared across a project's instances. It reads the provider name from the resolved config (child.config.browser.provider?.name) rather than the lazily-initialized provider instance. If that config field is absent, the framework considers it an internal invariant violation and asks the user to file a bug, because the config resolution layer should always populate provider.name before this code runs.

Solutions

  1. Verify you are on a released, mutually-compatible version of vitest and @vitest/browser (no version skew after a partial upgrade).
  2. Check that browser.provider is a known string ('playwright' | 'webdriverio' | 'preview') in your config, not a hand-rolled object.
  3. Reinstall node_modules to rule out a partially-linked monorepo build.
  4. If the issue reproduces on a clean install, file the bug report the error asks for, including vitest version, provider, and a minimal reproduction.

Example fix

// before - hand-rolled provider object, no name resolved
browser: { provider: { name: undefined } as any }
// after - use a known provider string and let Vitest resolve it
browser: { provider: 'playwright' }
Defensive patterns

Strategy: validation

Validate before calling

function hasResolvedProvider(cfg: { browser?: { provider?: { name?: string } | string } }): boolean {
  const p = cfg.browser?.provider
  return typeof p === 'string' ? p.length > 0 : !!p && typeof p.name === 'string' && p.name.length > 0
}

Type guard

function isKnownProviderName(name: unknown): name is 'playwright' | 'webdriverio' | 'preview' {
  return name === 'playwright' || name === 'webdriverio' || name === 'preview'
}

Prevention

When it happens

Trigger: Reaching pluginContext with a project whose config.browser.provider object exists but has no `name` field, or where config resolution was bypassed (e.g. a custom Vitest integration that constructs TestProject manually). The code path is hit when the orchestrator tester module is first requested by the browser.

Common situations: A regression in config resolution after upgrading Vitest; a third-party plugin that mutates config.browser.provider after resolution; running a canary/linked build of @vitest/browser where resolveConfig did not run; an edge case with a custom provider object literal missing the name property.

Related errors


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

Appendix: source

Thrown at packages/browser/src/node/plugins/pluginContext.ts:50

  }
}

async function generateContextFile(
  this: Rollup.PluginContext,
  globalServer: ParentBrowserProject,
) {
  const commands = Object.keys(globalServer.commands)
  // The provider instance is initialized lazily when a page opens, so reading
  // `child.provider` here races and can be `undefined` before the first page is
  // opened. The provider name is uniform across a project's instances and is
  // already resolved on the config, so read it from there for this shared
  // (cached) context module. The instance is still used below for the
  // preview-only user-event import.
  const child = [...globalServer.children][0]
  const provider = child.provider
  const providerName = child.config.browser.provider?.name
  if (!providerName) {
    throw new Error(
      `Browser provider is not defined for the project "${child.project.name}". This is a bug in Vitest. Please, open a new issue with a reproduction.`,
    )
  }

  const commandsCode = commands
    .filter(command => !command.startsWith('__vitest'))
    .map((command) => {
      return `    ["${command}"]: (...args) => __vitest_browser_runner__.commands.triggerCommand("${command}", args),`
    })
    .join('\n')

  const userEventNonProviderImport = await getUserEventImport(
    provider,
    this.resolve.bind(this),
  )
  const distContextPath = slash(`/@fs/${resolve(__dirname, 'context.js')}`)

  return `

View on GitHub (pinned to 1fa9837ec2)