grafana/k6 · error

persisting screenshot: %w

Error message

persisting screenshot: %w

What it means

The screenshot was captured successfully, but writing it to disk through the ScreenshotPersister failed. This is a filesystem/IO error on the k6 host side — the bytes in memory are fine, only persistence failed.

Source

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

	}

	// Capture screenshot
	buf, err = capture.Do(cdp.WithExecutor(s.ctx, sess))
	if err != nil {
		return nil, fmt.Errorf("capturing screenshot: %w", err)
	}

	if shouldSetDefaultBackground {
		action := emulation.SetDefaultBackgroundColorOverride()
		if err := action.Do(cdp.WithExecutor(s.ctx, sess)); err != nil {
			return nil, fmt.Errorf("resetting screenshot background color: %w", err)
		}
	}

	// Save screenshot capture to file
	if path != "" {
		if err := s.persister.Persist(s.ctx, path, bytes.NewBuffer(buf)); err != nil {
			return nil, fmt.Errorf("persisting screenshot: %w", err)
		}
	}

	return buf, nil
}

func getViewPortDimensions(ctx context.Context, sess session, logger *log.Logger) (float64, float64, float64, error) {
	visualViewportScale := 1.0
	visualViewportPageX, visualViewportPageY := 0.0, 0.0

	// Add clip region
	//nolint:dogsled
	_, _, _, _, cssVisualViewport, _, err := cdppage.GetLayoutMetrics().Do(cdp.WithExecutor(ctx, sess))
	if err != nil {
		return 0, 0, 0, fmt.Errorf("getting layout metrics for screenshot: %w", err)
	}

	// we had a null pointer panic cases, when visualViewport is nil

View on GitHub (pinned to 93accf6570)

Solutions

  1. Write to an existing, writable directory — use a relative path like 'screenshots/shot.png' after creating the folder, or '.'.
  2. Check permissions of the target directory for the user running k6 (and mount a writable volume in Docker).
  3. If you only need the bytes, drop the path option and handle the returned Buffer yourself.
  4. Verify disk space on the target filesystem.

Example fix

// before
await page.screenshot({ path: '/var/data/shots/home.png' }); // dir may not exist / no perms

// after (shell): mkdir -p screenshots
await page.screenshot({ path: 'screenshots/home.png' });
Defensive patterns

Strategy: validation

Validate before calling

import { execSync } from 'k6/x/...' // not available; instead verify writability in the shell before the run:
// mkdir -p screenshots && touch screenshots/.probe && rm screenshots/.probe

Try / catch

try { await page.screenshot({ path: 'screenshots/s.png' }); }
catch (e) { if (String(e).includes('persisting screenshot')) { const buf = await page.screenshot(); /* use buf */ } else throw e; }

Prevention

When it happens

Trigger: page.screenshot({path: '...'}) where the path is unwritable: directory does not exist, permission denied, read-only filesystem, path too long, or disk full. Also container setups where the working dir is not writable.

Common situations: Using an absolute path like /home/... or /root/... that the k6 process cannot write; assuming mkdir happens automatically; running k6 in Docker with no mounted writable volume; CI sandboxes with read-only workspaces; Windows path separators in scripts written on Linux.

Related errors


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