microsoft/playwright · error · InvalidSelectorError

Error while parsing selector `${selector}` - cannot use ${op

Error message

Error while parsing selector `${selector}` - cannot use ${operator} in attribute with non-string matching value - ${value}

What it means

Thrown when a non-= operator (*=, ^=, $=, |=, ~=) is paired with a non-string value (number or boolean). Substring/prefix/suffix operators only make sense for strings, so the parser rejects unquoted numeric/boolean operands. Values parsed as `true`/`false` become booleans; others become numbers via `+value` when unquoted strings are disallowed.

Source

Thrown at packages/isomorphic/selectorParser.ts:448

      if (value === 'true') {
        value = true;
      } else if (value === 'false') {
        value = false;
      } else {
        if (!allowUnquotedStrings) {
          value = +value;
          if (Number.isNaN(value))
            syntaxError('parsing attribute value');
        }
      }
    }
    skipSpaces();
    if (next() !== ']')
      syntaxError('parsing attribute value');

    eat1();
    if (operator !== '=' && typeof value !== 'string')
      throw new InvalidSelectorError(`Error while parsing selector \`${selector}\` - cannot use ${operator} in attribute with non-string matching value - ${value}`);
    return { name: jsonPath.join('.'), jsonPath, op: operator, value, caseSensitive };
  }

  const result: AttributeSelector = {
    name: '',
    attributes: [],
  };
  result.name = readIdentifier();
  skipSpaces();
  while (next() === '[') {
    result.attributes.push(readAttribute());
    skipSpaces();
  }
  if (!EOL)
    syntaxError(undefined);
  if (!result.name && !result.attributes.length)
    throw new InvalidSelectorError(`Error while parsing selector \`${selector}\` - selector cannot be empty`);
  return result;

View on GitHub (pinned to c8fc3bf8d3)

Solutions

  1. Quote the value so it is parsed as a string: `[data-index*="42"]`.
  2. Use `=` if you want exact numeric/boolean matching.
  3. Coerce the dynamic value to a string before interpolating: `String(value)`.

Example fix

// before
page.locator(`[data-id*=${dynamicId}]`);  // numeric id -> rejected

// after
page.locator(`[data-id*="${String(dynamicId)}"]`);
Defensive patterns

Strategy: validation

Validate before calling

function assertSubstringOpValueIsString(sel: string) {
  const m = sel.match(/\[([^[\]]+?)([*^$|~])=(true|false|-?\d+(?:\.\d+)?)\]/);
  if (m) throw new Error(`Operator ${m[2]}= requires a quoted string, got ${m[3]}`);
}
assertSubstringOpValueIsString(selector);

Type guard

function substringOpValueIsQuoted(sel: string): boolean {
  return !/\[[^[\]]+?[*^$|~]=(true|false|-?\d)/.test(sel);
}

Try / catch

try {
  await page.locator(sel).click();
} catch (e) {
  if (/cannot use .* in attribute with non-string/.test(e.message)) {
    sel = sel.replace(/(\[\w+[*^$|~]=)(-?\d+|true|false)(\])/, '$1"$2"$3');
  }
  throw e;
}

Prevention

When it happens

Trigger: Writing `page.locator('[data-index*=42]')`, `[count^=true]`, or any `[attr OP <number|true|false>]` where OP is a substring-style operator. Only happens when allowUnquotedStrings is false (strict parse path).

Common situations: Treating a numeric attribute as text without quotes; using a boolean keyword (`true`/`false`) with a substring operator; data-driven selectors that interpolate a numeric ID with `*=`.

Related errors


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