microsoft/playwright · error · InvalidSelectorError
Error while parsing css selector "${selector}". Did you mean
Error message
Error while parsing css selector "${selector}". Did you mean to CSS.escape it? What it means
Thrown at the end of parseCSS after function arguments are consumed. Every argument of a pseudo-class function (like :has(), :is(), :not()) is expected to be a CSSComplexSelector — an object containing a 'simples' array. If any argument is instead a bare string or number (i.e. the function was called with a non-selector argument shape), the structural check fails and this error is raised.
Source
Thrown at packages/isomorphic/cssParser.ts:249
let s = '';
let balance = 1; // First open paren is a part of a function token.
while (!isEOF()) {
if (isOpenParen() || isFunction())
balance++;
if (isCloseParen())
balance--;
if (!balance)
break;
s += tokens[pos++].toSource();
}
return s;
}
const result = consumeFunctionArguments();
if (!isEOF())
throw unexpected();
if (result.some(arg => typeof arg !== 'object' || !('simples' in arg)))
throw new InvalidSelectorError(`Error while parsing css selector "${selector}". Did you mean to CSS.escape it?`);
return { selector: result as CSSComplexSelector[], names: Array.from(names) };
}
export function serializeSelector(args: CSSFunctionArgument[]) {
return args.map(arg => {
if (typeof arg === 'string')
return `"${arg}"`;
if (typeof arg === 'number')
return String(arg);
return arg.simples.map(({ selector, combinator }) => {
let s = selector.css || '';
s = s + selector.functions.map(func => `:${func.name}(${serializeSelector(func.args)})`).join('');
if (combinator)
s += ' ' + combinator;
return s;
}).join(' ');
}).join(', ');
}View on GitHub (pinned to c8fc3bf8d3)
Solutions
- Inspect the selector in the message: each function argument that is a selector must be a valid selector expression, not a literal.
- Replace literal arguments with proper sub-selectors, e.g. change :has(foo) so 'foo' is itself a valid selector.
- If you need text matching use the dedicated engines: :has-text("..."), :text("..."), :text-is("...").
- Run the selector through page.locator() in isolation to reproduce, then simplify until it parses.
Example fix
// before
await page.locator(':has(div)').click(); // missing base + arg shape
// after
await page.locator('div:has(span)').click(); // base 'div' + valid sub-selector 'span' Defensive patterns
Strategy: try-catch
Validate before calling
function looksLikeValidFunctionArg(sel: string): boolean {
// crude: pseudo-class function args that are selectors should not be bare literals
return !/^\s*"|\d+\s*$/.test(sel);
} Try / catch
try { await page.locator(sel).click(); }
catch (e) { if (isInvalidSelectorError(e)) handleBadSelector(sel); else throw e; } Prevention
- Use Playwright's high-level pseudo-class APIs (:has-text, has, filter) instead of hand-writing function args.
- Reproduce suspect selectors in isolation before composing them into larger expressions.
- Keep nested pseudo-class arguments as valid sub-selectors, never literals.
When it happens
Trigger: Using a Playwright custom pseudo-class with a non-selector argument, e.g. :has-text() is fine (string) but :has(123) or :is("str") where a complex selector is required; malformed nested selectors where the closing of an inner complex selector is missing so the parser falls back to treating the arg as a primitive.
Common situations: Hand-writing selectors with custom engines (:not(...), :has(...), :is(...), :where(...)) and putting a literal where a sub-selector goes; refactoring a selector and dropping a nested combinator; using a string where the grammar requires brackets/selector syntax.
Related errors
- Unsupported token "${unsupportedToken.toSource()}" while par
- Malformed selector: ${part.name}=${part.body}
- Unexpected end of selector while parsing selector `${selecto
- Error while parsing selector `${selector}`: ${e.message}
- Error while parsing selector `${selector}` - cannot use ${op
AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12).
Data as JSON: /api/errors/e456513d2fed950d.
Report an issue: GitHub.