microsoft/playwright · error · InvalidCharacterError

Invalid character: the input contains U+0000.

Error message

Invalid character: the input contains U+0000.

What it means

escapeIdent() throws InvalidCharacterError when the string it is asked to escape contains a NUL character (U+0000). CSS identifiers cannot represent U+0000 even escaped, so the algorithm from CSSOM CSS.escape aborts. escapeIdent is used internally when serializing CSS token identifiers (element names, class names, idents in pseudo-classes).

Source

Thrown at packages/isomorphic/cssTokenizer.ts:899

    const source = this.repr;
    let unit = escapeIdent(this.unit);
    if (unit[0].toLowerCase() === 'e' && (unit[1] === '-' || between(unit.charCodeAt(1), 0x30, 0x39))) {
      // Unit is ambiguous with scinot
      // Remove the leading "e", replace with escape.
      unit = '\\65 ' + unit.slice(1, unit.length);
    }
    return source + unit;
  }
}

function escapeIdent(string: string) {
  string = '' + string;
  let result = '';
  const firstcode = string.charCodeAt(0);
  for (let i = 0; i < string.length; i++) {
    const code = string.charCodeAt(i);
    if (code === 0x0)
      throw new InvalidCharacterError('Invalid character: the input contains U+0000.');

    if (
      between(code, 0x1, 0x1f) || code === 0x7f ||
      (i === 0 && between(code, 0x30, 0x39)) ||
      (i === 1 && between(code, 0x30, 0x39) && firstcode === 0x2d)
    )
      result += '\\' + code.toString(16) + ' ';
    else if (
      code >= 0x80 ||
      code === 0x2d ||
      code === 0x5f ||
      between(code, 0x30, 0x39) ||
      between(code, 0x41, 0x5a) ||
      between(code, 0x61, 0x7a)
    )
      result += string[i];
    else
      result += '\\' + string[i];

View on GitHub (pinned to c8fc3bf8d3)

Solutions

  1. Strip or replace NUL bytes before passing the string into selector-building code: str.replace(/\0/g, '').
  2. Validate input at the boundary: if (str.includes('\u0000')) throw new TypeError('NUL not allowed').
  3. Use a locator API that treats the value as opaque data (getByText/getByRole with a string) rather than embedding it into CSS syntax.

Example fix

// before
const name = rawWithNul; // contains '\u0000'
await page.locator(`.${name}`).click();

// after
const clean = name.replace(/\u0000/g, '');
await page.getByText(clean).click();
Defensive patterns

Strategy: validation

Validate before calling

function hasNoNul(s: string): boolean { return !s.includes('\u0000'); }

Type guard

function isNulFreeString(s: unknown): s is string { return typeof s === 'string' && !s.includes('\u0000'); }

Try / catch

try { await page.locator(buildSelector(value)).click(); }
catch (e) { if (/U\+0000/.test(e.message)) { value = value.replace(/\u0000/g, ''); } else throw e; }

Prevention

When it happens

Trigger: Any API path that builds a CSS identifier from a runtime string containing '\0', '\u0000', or a raw NUL byte: text/role/attribute matching where the value reaches the CSS serializer, or constructing class/tag selectors from data containing NUL.

Common situations: Data scraped from a page or read from a file/database containing NUL bytes (common in legacy/CRLF/binary-corrupted text); test fixtures with embedded control characters; passing Buffer.toString() of binary data into a selector builder.

Understand the failure class

Related errors


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