grafana/k6 · error

tapping on %q: %w

Error message

tapping on %q: %w

What it means

Frame.Tap() wraps the internal pointer action as 'tapping on "<selector>": <cause>'. The cause is the real signal: wait-for-element timeout, actionability failure (not visible, not stable, hit-target check fails because something else covers the point), or context cancellation.

Source

Thrown at internal/js/modules/k6/browser/common/frame.go:1801

// SetInputFiles sets input files for the selected element.
func (f *Frame) SetInputFiles(selector string, pfiles *Files, popts *FrameSetInputFilesOptions) error {
	f.log.Debugf("Frame:SetInputFiles", "fid:%s furl:%q sel:%q", f.ID(), f.URL(), selector)

	if err := f.setInputFiles(selector, pfiles, popts); err != nil {
		return fmt.Errorf("setting input files on %q: %w", selector, err)
	}

	applySlowMo(f.ctx)

	return nil
}

// Tap the first element that matches the selector.
func (f *Frame) Tap(selector string, opts *FrameTapOptions) error {
	f.log.Debugf("Frame:Tap", "fid:%s furl:%q sel:%q", f.ID(), f.URL(), selector)

	if err := f.tap(selector, opts); err != nil {
		return fmt.Errorf("tapping on %q: %w", selector, err)
	}

	applySlowMo(f.ctx)

	return nil
}

func (f *Frame) tap(selector string, opts *FrameTapOptions) error {
	tap := func(apiCtx context.Context, handle *ElementHandle, p *Position) (any, error) {
		return nil, handle.tap(apiCtx, p)
	}
	act := f.newPointerAction(
		selector, DOMElementStateAttached, opts.Strict, tap, &opts.ElementHandleBasePointerOptions,
	)
	if _, err := call(f.ctx, act, opts.Timeout); err != nil {
		return errorFromDOMError(err)
	}

View on GitHub (pinned to 93accf6570)

Solutions

  1. Wait for visibility and stability: await frame.waitForSelector(sel, { state: 'visible' }) and let animations finish before tapping.
  2. Scroll the element into view (locator.scrollIntoViewIfNeeded or evaluate scrollIntoView) first.
  3. Pass { force: true } to bypass hit-target/actionability checks when the overlay is intentional.
  4. Use a unique selector or { strict: false } to avoid ambiguity.

Example fix

// before
await frame.tap('#checkout'); // sticky header covers it

// after
await frame.waitForSelector('#checkout', { state: 'visible' });
await frame.tap('#checkout', { force: true });
Defensive patterns

Strategy: validation

Validate before calling

await frame.waitForSelector(sel, { state: 'visible' });
await frame.evaluate(s => document.querySelector(s)?.scrollIntoView({ block: 'center' }), sel);

Try / catch

try { await frame.tap(sel); }
catch (e) { if (/tapping on/.test(e.message) && /timeout|not visible|hit target/i.test(e.message)) { /* retry after scroll or force */ } throw e; }

Prevention

When it happens

Trigger: frame.tap(sel) with no touch emulation metadata is fine, but fails when the element is off-viewport or covered; selector matches multiple elements with strict:true; element detaches between resolution and the tap; page/frame closed.

Common situations: Mobile-emulation tests tapping buttons under sticky headers/cookie banners; elements that animate (hit-target check sees them moving); tapping elements that only appear after async load without waiting.

Related errors


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