grafana/k6 · error

unpacking scroll offset: %w

Error message

unpacking scroll offset: %w

What it means

The final conversion in elementHandle.screenshot(): the evaluated scroll offset {x: window.scrollX, y: window.scrollY} is converted into a Position struct via the JSON round-trip helper, and that conversion failed — the evaluated value did not deserialize into {x,y} numeric fields.

Source

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

		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")
		}
	}

	scrollOffset, err := h.Evaluate(`() => { return {x: window.scrollX, y: window.scrollY};}`)
	if err != nil {
		return nil, fmt.Errorf("evaluating scroll offset: %w", err)
	}

	var returnVal Position
	if err := convert(scrollOffset, &returnVal); err != nil {
		return nil, fmt.Errorf("unpacking scroll offset: %w", err)
	}

	documentRect := bbox
	documentRect.X += returnVal.X
	documentRect.Y += returnVal.Y

	buf, err := s.screenshot(
		h.frame.page.session,
		documentRect.enclosingIntRect(),
		nil, // viewportRect
		format,
		opts.OmitBackground,
		opts.Quality,
		opts.Path,
	)
	if err != nil {
		return nil, err
	}

View on GitHub (pinned to 93accf6570)

Solutions

  1. Screenshot with page.screenshot({clip: boundingBox}) instead of element screenshot to bypass the scroll-offset math.
  2. If you control the page, remove scripts that override window.scrollX/scrollY.
  3. Scroll to top first (page.evaluate('window.scrollTo(0,0)')) so offsets are plain zeros.
  4. Upgrade k6 and report a grafana/k6 issue if it reproduces on a vanilla page.

Example fix

// before
await page.$('#el').then(el => el.screenshot({ path: 'e.png' })); // scrollX patched to a string

// after
await page.evaluate('window.scrollTo(0, 0)');
const bb = await page.locator('#el').boundingBox();
await page.screenshot({ path: 'e.png', clip: bb });
Defensive patterns

Strategy: try-catch

Validate before calling

await page.evaluate('window.scrollTo(0, 0)'); // make scroll offsets plain zeros before element capture

Type guard

function isFinitePosition(v) {
  return v != null && typeof v === 'object'
    && Number.isFinite(Number(v.x)) && Number.isFinite(Number(v.y));
}

Try / catch

try { await el.screenshot({ path: 'e.png' }); }
catch (e) {
  if (String(e).includes('unpacking scroll offset')) {
    const bb = await page.locator(sel).boundingBox();
    await page.screenshot({ path: 'e.png', clip: bb }); // bypass scroll-offset math
  } else throw e;
}

Prevention

When it happens

Trigger: The scrollOffset evaluate succeeded but returned a non-numeric/oddly-shaped value (a string, undefined, or an object whose x/y are not numbers — e.g. a page script overriding window.scrollX/scrollY with non-numeric values).

Common situations: Anti-automation or analytics scripts monkey-patching window.scrollX/scrollY; unusual document types; extremely rare in normal pages since these are built-in number properties.

Related errors


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