vitest-dev/vitest · error · Error

Invalid command name "${command}". Only alphanumeric charact

Error message

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

What it means

`ParentBrowserProject` constructor iterates over every key in `config.browser.commands` and validates each matches `/^[a-z_$][\w$]*$/i`. Because command names become JS identifiers in the generated context module, non-identifier names (kebab-case, dotted, leading digit) are rejected at construction time before the server starts.

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 d568f8ce37)

Solutions

  1. Rename the config keys to valid identifiers — `myCommand` instead of `my-command`.
  2. If you must keep a UI-facing name, keep the config key valid and alias inside the implementation.
  3. Run `vitest --config` validation early to catch this before the server boots.

Example fix

// before
export default defineConfig({
  test: {
    browser: {
      commands: { 'login-as': ({ page }) => page.fill('#user', 'x') },
    },
  },
})

// after
export default defineConfig({
  test: {
    browser: {
      commands: { loginAs: ({ page }) => page.fill('#user', 'x') },
    },
  },
})
Defensive patterns

Strategy: validation

Validate before calling

function validateCommandConfig(commands: Record<string, unknown>): void {
  for (const name of Object.keys(commands)) {
    if (!/^[a-z_$][\w$]*$/i.test(name)) {
      throw new Error(`browser.commands key "${name}" is not a valid identifier`)
    }
  }
}

Type guard

const hasValidCommandKeys = (commands: Record<string, unknown>): boolean =>
  Object.keys(commands).every(n => /^[a-z_$][\w$]*$/i.test(n))

Prevention

When it happens

Trigger: Configuring `browser.commands` with at least one invalid key, e.g. `{ 'my-command': ({ page }) => ... }` or `{ '1action': fn }`.

Common situations: Migrating from a convention that used kebab-case; copy-pasted command names from docs of another framework; programmatic config generation that joins tokens with dashes.

Related errors


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