grafana/k6 · error
only one of the selectors can capture using * modifier
Error message
only one of the selectors can capture using * modifier
What it means
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'.
Source
Thrown at internal/js/modules/k6/browser/common/selectors.go:67
func NewSelector(selector string) (*Selector, error) {
if selector == "" {
return nil, errors.New("provided selector is empty")
}
s := Selector{
Selector: selector,
Parts: make([]*SelectorPart, 0, 1),
Capture: nil,
}
err := s.parse()
return &s, err
}
func (s *Selector) appendPart(p *SelectorPart, capture bool) error {
s.Parts = append(s.Parts, p)
if capture {
if s.Capture != nil {
return errors.New("only one of the selectors can capture using * modifier")
}
s.Capture = new(int)
*s.Capture = (len(s.Parts) - 1)
}
return nil
}
// parse splits the selector into parts, separated by `>>`, and identifies
// the query engine for each part.
//
//nolint:cyclop,funlen
func (s *Selector) parse() error {
parsePart := func(part string) (*SelectorPart, bool) {
part = strings.TrimSpace(part)
if part == "" {
return nil, false
}
View on GitHub (pinned to 93accf6570)
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
Example fix
// before
const handle = await page.$('*css=div.card >> *text=Buy');
// after
const handle = await page.$('*css=div.card >> text=Buy'); Defensive patterns
Strategy: validation
Validate before calling
const stars = selector.split('>>').filter(p => p.trim().startsWith('*')).length;
if (stars > 1) throw new Error('only one selector part may use the * capture modifier');
await page.$(selector); Type guard
const hasSingleCapture = (sel) => sel.split('>>').filter(p => p.trim().startsWith('*')).length === 1; Prevention
- 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
When it happens
Trigger: Selectors like '*css=div.container >> *text=Sign in' (two starred parts) passed to page.$, page.locator, waitForSelector, or query selectors in the browser module.
Common situations: Misunderstanding the capture modifier; assembling selector fragments programmatically where each fragment carries its own '*'; merging queries that each used capture.
Related errors
- provided selector is empty
- parsing selector %q: %w
- predicate function is not callable
- "handler" argument cannot be nil
- clip area is either empty or outside the viewport
AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15).
Data as JSON: /api/errors/090f7254db4489fb.
Report an issue: GitHub.