{"record":{"id":"090f7254db4489fb","repo":"grafana/k6","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":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/js/modules/k6/browser/common/selectors.go","lineNumber":67,"sourceCode":"func NewSelector(selector string) (*Selector, error) {\n\tif selector == \"\" {\n\t\treturn nil, errors.New(\"provided selector is empty\")\n\t}\n\n\ts := Selector{\n\t\tSelector: selector,\n\t\tParts:    make([]*SelectorPart, 0, 1),\n\t\tCapture:  nil,\n\t}\n\terr := s.parse()\n\treturn &s, err\n}\n\nfunc (s *Selector) appendPart(p *SelectorPart, capture bool) error {\n\ts.Parts = append(s.Parts, p)\n\tif capture {\n\t\tif s.Capture != nil {\n\t\t\treturn errors.New(\"only one of the selectors can capture using * modifier\")\n\t\t}\n\t\ts.Capture = new(int)\n\t\t*s.Capture = (len(s.Parts) - 1)\n\t}\n\treturn nil\n}\n\n// parse splits the selector into parts, separated by `>>`, and identifies\n// the query engine for each part.\n//\n//nolint:cyclop,funlen\nfunc (s *Selector) parse() error {\n\tparsePart := func(part string) (*SelectorPart, bool) {\n\t\tpart = strings.TrimSpace(part)\n\t\tif part == \"\" {\n\t\t\treturn nil, false\n\t\t}\n","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/grafana/k6/blob/93accf6570dcd306ca5e99cc44c393ee3797761b/internal/js/modules/k6/browser/common/selectors.go#L49-L85","documentation":"In chained selectors (parts joined by '>>'), prefixing one part with '*' captures the element matched by that intermediate part instead of the final one. appendPart (selectors.go:59-72) records the capture index only once; a second '*' prefix anywhere in the chain is rejected with 'only one of the selectors can capture using * modifier'.","triggerScenarios":"Selectors like '*css=div.container >> *text=Sign in' (two starred parts) passed to page.$, page.locator, waitForSelector, or query selectors in the browser module.","commonSituations":"Misunderstanding the capture modifier; assembling selector fragments programmatically where each fragment carries its own '*'; merging queries that each used capture.","solutions":["Keep exactly one '*' prefix per chained selector and remove it from all other parts","Split the query into two separate selectors when you need elements from multiple intermediate steps","Sanitize programmatically-built selectors to strip extra '*' prefixes before use"],"exampleFix":"// before\nconst handle = await page.$('*css=div.card >> *text=Buy');\n\n// after\nconst handle = await page.$('*css=div.card >> text=Buy');","handlingStrategy":"validation","validationCode":"const stars = selector.split('>>').filter(p => p.trim().startsWith('*')).length;\nif (stars > 1) throw new Error('only one selector part may use the * capture modifier');\nawait page.$(selector);","typeGuard":"const hasSingleCapture = (sel) => sel.split('>>').filter(p => p.trim().startsWith('*')).length === 1;","tryCatchPattern":null,"preventionTips":["Use at most one '*' prefix per chained selector","When assembling chains dynamically, strip '*' from all but the intended part","Split into separate queries if you need multiple intermediate elements"],"tags":["browser","selector","validation"],"backgroundTag":null,"analyzedSha":"93accf6570dcd306ca5e99cc44c393ee3797761b","analyzedAt":"2026-08-15T21:23:27.118Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}