microsoft/playwright · error · Error

Selector engine name may only contain [a-zA-Z0-9_] character

Error message

Selector engine name may only contain [a-zA-Z0-9_] characters

What it means

Thrown by Selectors.register when the engine name does not match the regex /^[a-zA-Z_0-9-]+$/. The guard ensures selector engine names are safe identifier characters before they are stored and used in selector parsing. Note: the error message text says [a-zA-Z0-9_] but the regex actually permits hyphens as well — a minor doc/string mismatch.

Source

Thrown at packages/playwright-core/src/server/selectors.ts:60

      'internal:has', 'internal:has-not',
      'internal:has-text', 'internal:has-not-text',
      'internal:and', 'internal:or', 'internal:chain',
      'role', 'internal:attr', 'internal:label', 'internal:text',
      'internal:role', 'internal:testid', 'internal:describe',
      'aria-ref'
    ]);
    this._builtinEnginesInMainWorld = new Set([
      '_react', '_vue',
    ]);
    this._engines = new Map();
    this._testIdAttributeName = testIdAttributeName ?? 'data-testid';
    for (const engine of engines)
      this.register(engine);
  }

  register(engine: channels.SelectorEngine) {
    if (!engine.name.match(/^[a-zA-Z_0-9-]+$/))
      throw new Error('Selector engine name may only contain [a-zA-Z0-9_] characters');
    // Note: we keep 'zs' for future use.
    if (this._builtinEngines.has(engine.name) || engine.name === 'zs' || engine.name === 'zs:light')
      throw new Error(`"${engine.name}" is a predefined selector engine`);
    if (this._engines.has(engine.name))
      throw new Error(`"${engine.name}" selector engine has been already registered`);
    this._engines.set(engine.name, engine);
  }

  testIdAttributeName(): string {
    return this._testIdAttributeName;
  }

  setTestIdAttributeName(testIdAttributeName: string) {
    this._testIdAttributeName = testIdAttributeName;
  }

  parseSelector(selector: string | ParsedSelector, strict: boolean) {
    const parsed = typeof selector === 'string' ? parseSelector(selector) : selector;

View on GitHub (pinned to c8fc3bf8d3)

Solutions

  1. Rename the engine to use only letters, digits, underscores, and hyphens.
  2. Sanitize the name with name.replace(/[^a-zA-Z0-9_-]/g, '') before registering.
  3. If you need namespacing, use a single hyphen (allowed) rather than a colon.

Example fix

// before
selectors.register({ name: 'my:engine', query: () => {...} });

// after
selectors.register({ name: 'my-engine', query: () => {...} });
Defensive patterns

Strategy: validation

Validate before calling

function sanitizeEngineName(name: string): string {
  const cleaned = name.replace(/[^a-zA-Z0-9_-]/g, '');
  if (!cleaned) throw new Error('Engine name is empty after sanitization');
  return cleaned;
}

const name = sanitizeEngineName(rawName);
selectors.register({ name, query: () => {/*...*/} });

Type guard

function isValidEngineName(name: string): boolean {
  return /^[a-zA-Z_0-9-]+$/.test(name);
}

Prevention

When it happens

Trigger: Calling selectors.register(engine) with engine.name containing spaces, colons, dots, unicode, or any character outside [A-Za-z0-9_-]; e.g. registering a custom engine named 'my engine' or 'foo:bar'.

Common situations: User-generated or dynamic engine names that were not sanitized; copying an engine name that includes a namespace separator like ':'; whitespace accidentally included in the name string.

Related errors


AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12). Data as JSON: /api/errors/468136fa003b5ed7. Report an issue: GitHub.