{"record":{"id":"3d3632bbf129b588","repo":"microsoft/playwright","slug":"unexpected-end-of-selector-while-parsing-selector","errorCode":null,"errorMessage":"Unexpected end of selector while parsing selector `${selector}`","messagePattern":"Unexpected end of selector while parsing selector `(.+?)`","errorType":"exception","errorClass":"InvalidSelectorError","httpStatus":null,"severity":"error","filePath":"packages/isomorphic/selectorParser.ts","lineNumber":286,"sourceCode":"  attributes: AttributeSelectorPart[],\n};\n\n\nexport function parseAttributeSelector(selector: string, allowUnquotedStrings: boolean): AttributeSelector {\n  let wp = 0;\n  let EOL = selector.length === 0;\n\n  const next = () => selector[wp] || '';\n  const eat1 = () => {\n    const result = next();\n    ++wp;\n    EOL = wp >= selector.length;\n    return result;\n  };\n\n  const syntaxError = (stage: string|undefined) => {\n    if (EOL)\n      throw new InvalidSelectorError(`Unexpected end of selector while parsing selector \\`${selector}\\``);\n    throw new InvalidSelectorError(`Error while parsing selector \\`${selector}\\` - unexpected symbol \"${next()}\" at position ${wp}` + (stage ? ' during ' + stage : ''));\n  };\n\n  function skipSpaces() {\n    while (!EOL && /\\s/.test(next()))\n      eat1();\n  }\n\n  function isCSSNameChar(char: string) {\n    // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram\n    return (char >= '\\u0080')  // non-ascii\n        || (char >= '\\u0030' && char <= '\\u0039')  // digit\n        || (char >= '\\u0041' && char <= '\\u005a')  // uppercase letter\n        || (char >= '\\u0061' && char <= '\\u007a')  // lowercase letter\n        || (char >= '\\u0030' && char <= '\\u0039')  // digit\n        || char === '\\u005f'  // \"_\"\n        || char === '\\u002d';  // \"-\"\n  }","sourceCodeStart":268,"sourceCodeEnd":304,"githubUrl":"https://github.com/microsoft/playwright/blob/c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6/packages/isomorphic/selectorParser.ts#L268-L304","documentation":"Thrown by parseAttributeSelector when the parser hits end-of-string in the middle of a sub-parse stage (quoted string, regex, identifier, operator, or attribute value). It is an InvalidSelectorError indicating the selector text is truncated or unterminated. The parser expected more characters but ran out of input before completing the current token.","triggerScenarios":"Calling locator/query APIs with a truncated attribute selector such as `page.locator('button[text=')`, `page.locator('[aria-label')`, or `parseAttributeSelector('div[name=\"unterminated', false)`. Any input where eat1()/next() reach EOL while skipSpaces/readQuotedString/readOperator/readAttributeToken still expect a closing token.","commonSituations":"String concatenation that drops a closing bracket or quote; template literals with an undefined variable producing empty fragments; copy-paste of a CSS selector into a Playwright attribute-engine selector expecting quoted values; CI where a selector is built from config and a field is missing.","solutions":["Inspect the full selector string printed in the message and add the missing closing character (quote, ], =value).","If the selector is built dynamically, log it before the call and assert it is non-empty and well-formed.","Quote attribute values that may be empty or contain spaces: use \"value\" or 'value' inside [...].","Switch to a literal selector in tests to isolate whether the bug is in the dynamic value or the static structure."],"exampleFix":"// before\nconst sel = `button[data-test=${id}`;  // missing closing ] when id undefined/truncated\npage.locator(sel);\n\n// after\nconst sel = `button[data-test=\"${id}\"]`;\nif (!sel.includes('[]') && !sel.includes('=\"\"'))\n  throw new Error(`bad selector: ${sel}`);\npage.locator(sel);","handlingStrategy":"validation","validationCode":"function assertClosedSelector(sel: string) {\n  const open = (sel.match(/[\\['\"]/g) ?? []).length;\n  const close = (sel.match(/[\\]'\"]/g) ?? []).length;\n  if (open !== close) throw new Error(`Selector brackets/quotes unbalanced: ${sel}`);\n}\nassertClosedSelector(selector);","typeGuard":"function isValidSelectorStart(sel: string): boolean {\n  return typeof sel === 'string' && sel.trim().length > 0;\n}","tryCatchPattern":"import { InvalidSelectorError } from 'playwright-core';\ntry {\n  await page.locator(sel).click();\n} catch (e) {\n  if (e instanceof InvalidSelectorError || /Unexpected end of selector/.test(e.message)) {\n    logSelectorBuildFailure(sel, e);\n    throw new Error(`Refusing to act on malformed selector: ${sel}`);\n  }\n  throw e;\n}","preventionTips":["Always quote attribute values to avoid EOL mid-token.","Build selectors from small, individually-validated fragments and unit-test the builder.","Log the assembled selector string before using it in CI to catch truncation."],"tags":["selector","parsing","invalid-selector","user-input"],"backgroundTag":null,"analyzedSha":"c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6","analyzedAt":"2026-08-12T07:26:36.950Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}