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
- Strip or replace NUL bytes before passing the string into selector-building code: str.replace(/\0/g, '').
- Validate input at the boundary: if (str.includes('\u0000')) throw new TypeError('NUL not allowed').
- 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
- Sanitize scraped/external text to strip NUL at ingestion.
- Use data-based locators (getByText/getByRole with a string) instead of embedding values into CSS identifiers.
- Add boundary validation rejecting control chars before selector construction.
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Unsupported token "${unsupportedToken.toSource()}" while par
- Error while parsing css selector "${selector}". Did you mean
- Unexpected end of selector while parsing selector `${selecto
- Error while parsing selector `${selector}` - selector cannot
- Invalid viewport size format: use "width,height", for exampl
AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12).
Data as JSON: /api/errors/3bbb694591cfe4cf.
Report an issue: GitHub.