grafana/k6 · error

hovering on %q: %w

Error message

hovering on %q: %w

What it means

Thrown by Locator.Hover() when the wrapped frame.hover call fails. Strict mode is forced on and the call retries, so the element must resolve to exactly one element and become hoverable (visible, stable, receiving pointer events) before the timeout. Common failures: another element overlays the target and intercepts pointer events, strict-mode violation, or timeout.

Source

Thrown at internal/js/modules/k6/browser/common/locator.go:605

	opts.Strict = true
	if err := l.frame.typ(l.selector, text, opts); err != nil {
		return spanRecordErrorf(span, "typing %q in %q: %w", text, l.selector, err)
	}

	applySlowMo(l.ctx)

	return nil
}

// Hover moves the pointer over the element that matches the locator's
// selector with strict mode on.
func (l *Locator) Hover(opts *FrameHoverOptions) error {
	l.log.Debugf("Locator:Hover", "fid:%s furl:%q sel:%q opts:%+v", l.frame.ID(), l.frame.URL(), l.selector, opts)

	opts.Strict = true
	opts.retry = true
	if err := l.frame.hover(l.selector, opts); err != nil {
		return fmt.Errorf("hovering on %q: %w", l.selector, err)
	}

	applySlowMo(l.ctx)

	return nil
}

// Tap the element found that matches the locator's selector with strict mode on.
func (l *Locator) Tap(opts *FrameTapOptions) error {
	l.log.Debugf("Locator:Tap", "fid:%s furl:%q sel:%q opts:%+v", l.frame.ID(), l.frame.URL(), l.selector, opts)

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

	applySlowMo(l.ctx)

View on GitHub (pinned to 93accf6570)

Solutions

  1. Dismiss or wait for overlays (spinners, banners) to disappear before hovering
  2. Expand the parent container/tab so the element is visible and stable
  3. Use a unique selector matching exactly one element
  4. As a last resort pass force: true in opts to skip actionability checks, or increase timeout

Example fix

// before
await page.locator('.menu-item').hover(); // covered by cookie banner

// after
const banner = page.locator('#cookie-banner');
if (await banner.count()) await banner.locator('button.accept').click();
await page.locator('.menu-item').hover();
Defensive patterns

Strategy: try-catch

Validate before calling

const el = page.locator('a.menu-item');
await el.waitFor({ state: 'visible' });
await el.hover();

Type guard

async function isUnobstructed(page, sel) {
  const handle = await page.$(sel);
  if (!handle) return false;
  const box = await handle.boundingBox();
  if (!box) return false;
  const hit = await handle.evaluate((n, x, y) => {
    const t = document.elementFromPoint(x, y);
    return !!t && (t === n || n.contains(t));
  }, box.x + box.width / 2, box.y + box.height / 2);
  return hit;
}

Try / catch

try {
  await page.locator(sel).hover();
} catch (e) {
  if (/intercepts pointer events|not stable|timeout/i.test(e.message)) {
    await dismissOverlays(page); // app-specific helper
    await page.locator(sel).hover();
  } else throw e;
}

Prevention

When it happens

Trigger: locator.hover() where the selector matches 0 or >1 elements; the element is hidden or has zero size; an overlay (cookie banner, spinner, sticky header) covers it so pointer events are intercepted; the element keeps moving (animation) past the timeout.

Common situations: Hovering menu items under a sticky navbar or modal overlay; hovering elements inside accordions/tabs that are not yet expanded; CSS animations preventing the stability check.

Related errors


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