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

  1. Keep exactly one '*' prefix per chained selector and remove it from all other parts
  2. Split the query into two separate selectors when you need elements from multiple intermediate steps
  3. 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

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


AI-assisted analysis of grafana/k6@93accf6570 (2026-08-15). Data as JSON: /api/errors/090f7254db4489fb. Report an issue: GitHub.