vitest-dev/vitest · error · Error

Invalid command name

Error message

Invalid command name "${command}". Only alphanumeric characters, $ and _ are allowed.

What it means

projectParent iterates config.browser.commands (user-defined commands) and validates each name with the same identifier regex as registerCommand, because those names are emitted as JS identifier keys in the tester context. Invalid names are rejected at parent setup time, before any test runs.

Solutions

  1. Rename the command key to a valid identifier (camelCase or snake_case).
  2. Update every browser.commands.<name>() call site to match the new name.
  3. Lint the config at startup by asserting the regex against each key.

Example fix

// before
export default defineConfig({ test: { browser: { commands: { 'my-cmd': ctx => 1 } } } })
// after
export default defineConfig({ test: { browser: { commands: { myCmd: ctx => 1 } } } })
Defensive patterns

Strategy: validation

Validate before calling

function validateCommandConfig(commands: Record<string, unknown>): string[] {
  return Object.keys(commands).filter(k => !/^[a-z_$][\w$]*$/i.test(k))
}

Prevention

When it happens

Trigger: Defining browser.commands in vitest.config with a hyphenated or otherwise non-identifier key, e.g. commands: { 'my-cmd': (...) => ... }; using a quoted property name that contains a dot or space.

Common situations: Copy-pasting REST route names into browser.commands; naming commands after CLI flags; coming from a framework where hyphenated command names are conventional.

Related errors


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

Appendix: source

Thrown at packages/browser/src/node/projectParent.ts:118

        }
        // some browsers (looking at you, safari) don't report queries in stack traces
        // the next best thing is to try the first id that this file resolves to
        const files = moduleGraph.getModulesByFile(resolvedPath)
        if (files && files.size) {
          return files.values().next().value!.id!
        }
        return id
      },
    }

    for (const [name, command] of Object.entries(builtinCommands)) {
      this.commands[name] ??= command
    }

    // validate names because they can't be used as identifiers
    for (const command in this.config.browser.commands) {
      if (!/^[a-z_$][\w$]*$/i.test(command)) {
        throw new Error(
          `Invalid command name "${command}". Only alphanumeric characters, $ and _ are allowed.`,
        )
      }
      this.commands[command] = this.config.browser.commands[command]
    }

    this.prefixTesterUrl = `${base || '/'}`
    this.prefixOrchestratorUrl = `${base}__vitest_test__/`
    this.faviconUrl = `${base}__vitest__/favicon.svg`

    this.manifest = (async () => {
      return JSON.parse(
        await readFile(`${distRoot}/client/.vite/manifest.json`, 'utf8'),
      )
    })().then(manifest => (this.manifest = manifest))

    this.orchestratorHtml = (this.config.browser.ui
      ? readFile(resolve(uiClientRoot, 'index.html'), 'utf8')

View on GitHub (pinned to 1fa9837ec2)