{"record":{"id":"e8f3f992bf7ba1d7","repo":"microsoft/playwright","slug":"only-one-of-the-selectors-can-capture-using-modi","errorCode":null,"errorMessage":"Only one of the selectors can capture using * modifier","messagePattern":"Only one of the selectors can capture using \\* modifier","errorType":"exception","errorClass":"InvalidSelectorError","httpStatus":null,"severity":"error","filePath":"packages/isomorphic/selectorParser.ts","lineNumber":217,"sourceCode":"    } else if (/^\\(*\\/\\//.test(part) || part.startsWith('..')) {\n      // If selector starts with '//' or '//' prefixed with multiple opening\n      // parenthesis, consider xpath. @see https://github.com/microsoft/playwright/issues/817\n      // If selector starts with '..', consider xpath as well.\n      name = 'xpath';\n      body = part;\n    } else {\n      name = 'css';\n      body = part;\n    }\n    let capture = false;\n    if (name[0] === '*') {\n      capture = true;\n      name = name.substring(1);\n    }\n    result.parts.push({ name, body });\n    if (capture) {\n      if (result.capture !== undefined)\n        throw new InvalidSelectorError(`Only one of the selectors can capture using * modifier`);\n      result.capture = result.parts.length - 1;\n    }\n  };\n\n  if (!selector.includes('>>')) {\n    index = selector.length;\n    append();\n    return result;\n  }\n\n  const shouldIgnoreTextSelectorQuote = () => {\n    const prefix = selector.substring(start, index);\n    const match = prefix.match(/^\\s*text\\s*=(.*)$/);\n    // Must be a text selector with some text before the quote.\n    return !!match && !!match[1];\n  };\n\n  while (index < selector.length) {","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/microsoft/playwright/blob/c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6/packages/isomorphic/selectorParser.ts#L199-L235","documentation":"parseSelectorString throws when more than one selector part is marked with the '*' capture modifier. The capture marker designates the single element Playwright should resolve out of a compound selector; multiple captures are ambiguous and therefore rejected.","triggerScenarios":"Authoring a selector with two '*' prefixes, e.g. '*div >> *span' or '*css=div >> *internal:attr=[id=\"x\"]'. Each '*' tries to register a capture; the second one trips the check.","commonSituations":"Misunderstanding that '*' designates the result element (only one allowed); building selectors dynamically and prepending '*' to multiple fragments; copy-paste errors duplicating the modifier.","solutions":["Use exactly one '*' — on the part whose element should be the resolved result.","If you need elements from multiple parts, run separate locators and resolve each independently.","Audit dynamically built selectors to ensure '*' is inserted at most once."],"exampleFix":"// before\nawait page.locator('*div >> *span').click();\n\n// after\nawait page.locator('div >> *span').click(); // capture only the span","handlingStrategy":"validation","validationCode":"function atMostOneCapture(sel: string): boolean {\n  const matches = sel.match(/(^|>>)\\s*\\*/g);\n  return !matches || matches.length <= 1;\n}","typeGuard":null,"tryCatchPattern":"try { await page.locator(sel).click(); }\ncatch (e) { if (isInvalidSelectorError(e) && /Only one of the selectors can capture/.test(e.message)) { /* remove extra '*' */ } else throw e; }","preventionTips":["Use at most one '*' per selector, on the part whose element you want resolved.","Run separate locators if you need elements from multiple parts.","Audit dynamic selector builders to insert '*' at most once."],"tags":["selector","capture","invalid-selector"],"backgroundTag":null,"analyzedSha":"c8fc3bf8d31542d59b4d4d9eaab1df93ff541dc6","analyzedAt":"2026-08-12T07:26:36.950Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}