grafana/k6 · error

scrolling element into view: %w

Error message

scrolling element into view: %w

What it means

Before capturing an element, the screenshotter scrolls it into view and waits for it to be visible/stable (waitAndScrollIntoViewIfNeeded). This error wraps that wait failing — typically a timeout waiting for the element to become visible, the element becoming detached, or the execution context being destroyed by navigation.

Source

Thrown at internal/js/modules/k6/browser/common/screenshotter.go:281

				"This is non-standard behavior, if possible please report this issue (with a reproducible script) "+
				"to the https://github.com/grafana/k6/issues/new.",
			visualViewportScale, visualViewportPageX, visualViewportPageY,
		)
	}

	return visualViewportScale, visualViewportPageX, visualViewportPageY, nil
}

func (s *screenshotter) screenshotElement(h *ElementHandle, opts *ElementHandleScreenshotOptions) ([]byte, error) {
	format := opts.Format
	viewportSize, originalViewportSize, err := s.originalViewportSize(h.frame.page)
	if err != nil {
		return nil, fmt.Errorf("getting original viewport size: %w", err)
	}

	err = h.waitAndScrollIntoViewIfNeeded(h.ctx, false, true, opts.Timeout)
	if err != nil {
		return nil, fmt.Errorf("scrolling element into view: %w", err)
	}

	bbox, err := h.boundingBox()
	if err != nil {
		return nil, fmt.Errorf("node is either not visible or not an HTMLElement: %w", err)
	}
	if bbox.Width <= 0 {
		return nil, fmt.Errorf("node has 0 width")
	}
	if bbox.Height <= 0 {
		return nil, fmt.Errorf("node has 0 height")
	}

	var overriddenViewportSize *Size
	fitsViewport := bbox.Width <= viewportSize.Width && bbox.Height <= viewportSize.Height
	if !fitsViewport { //nolint:nestif
		overriddenViewportSize = Size{
			Width:  math.Max(viewportSize.Width, bbox.Width),

View on GitHub (pinned to 93accf6570)

Solutions

  1. Wait first: await locator.waitFor({ state: 'visible', timeout: 30000 }) before screenshot() (or pass a larger timeout option to screenshot).
  2. Expand/scroll the container in user code so the element is actually visible.
  3. Re-query the element close to the screenshot to avoid stale handles in SPAs.
  4. If the page navigates on a timer, block that navigation or screenshot sooner.

Example fix

// before
await page.$('#chart').then(el => el.screenshot({ path: 'c.png' })); // chart not rendered yet

// after
const chart = page.locator('#chart');
await chart.waitFor({ state: 'visible', timeout: 30000 });
await chart.screenshot({ path: 'c.png' });
Defensive patterns

Strategy: validation

Validate before calling

const loc = page.locator('#target');
await loc.waitFor({ state: 'visible', timeout: 30000 });

Type guard

async function isVisibleAndAttached(loc) {
  return (await loc.count()) > 0 && await loc.isVisible();
}

Try / catch

try { await el.screenshot({ path: 'e.png' }); }
catch (e) { if (String(e).includes('scrolling element into view')) { /* wait for visible, then retry */ } else throw e; }

Prevention

When it happens

Trigger: elementHandle.screenshot() where the element is display:none/visibility:hidden/offscreen-unscrollable, is removed from the DOM during the wait, the selector resolved to a stale handle, or the page navigates while waiting.

Common situations: Screenshotting lazy-rendered widgets (charts, carousels) before they render; elements inside collapsed accordions/tabs; SPA route changes invalidating the handle; default timeout too short on slow CI.

Related errors


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