Screenshots and traces

Page screenshots

use ferridriver::options::ScreenshotFormat;

let png = page.screenshot().await?;
std::fs::write("home.png", png).map_err(|e| e.to_string())?;

// Full page (scrolling capture)
let png = page.screenshot().full_page(true).await?;

// JPEG with quality
let jpg = page.screenshot()
    .format(ScreenshotFormat::Jpeg)
    .quality(80)
    .await?;

Element screenshots

let png = page.locator(".chart").screenshot().await?;

Masking sensitive regions

Overlay a solid color over selected elements before snapshotting:

let png = page.screenshot()
    .full_page(true)
    .mask([
        page.locator(".user-email"),
        page.locator(".credit-card"),
    ])
    .mask_color("#FF00FF")
    .await?;

Disable animations for stable captures

use ferridriver::options::{AnimationsMode, CaretMode};

let png = page.screenshot()
    .animations(AnimationsMode::Disabled)
    .caret(CaretMode::Hide)
    .await?;

Snapshot assertions

Stored baseline; failures emit a diff image next to the snapshot.

use ferridriver_test::prelude::*;
use ferridriver_test::expect::LocatorSnapshotMatchers;

#[ferritest]
async fn dashboard_snapshot(page: Arc<Page>) {
    page.goto("https://app.example.com/dashboard").await?;
    expect(&page.locator(".main")).to_have_screenshot("dashboard.png").await?;
}

First run writes the baseline. Subsequent runs compare. Update with:

cargo test --test e2e -- --update-snapshots

ARIA / accessibility snapshots

YAML representation of the accessibility tree — readable, deterministic, and great for catching unintended a11y regressions.

use ferridriver_test::prelude::*;
use ferridriver_test::expect::PageSnapshotMatchers;

expect(&page).to_match_aria_snapshot(r#"
- banner:
  - link "Dashboard"
  - link "Settings"
- heading "Welcome, Ada" [level=1]
- button "Sign out"
"#).await?;

Playwright-compatible traces

Recorded in [test].trace mode and dropped into the output directory.

[test]
trace = "retain-on-failure"

Modes: off, on, retain-on-failure, on-first-retry.

Reading a trace

The trace viewer ships inside the binary — no npm, no download, works offline:

ferridriver trace view                    # the newest trace of the last run
ferridriver trace view path/to/trace.zip  # a specific one
ferridriver trace view --no-open --port 9323

npx playwright show-trace and trace.playwright.dev open the same files: the format is Playwright's, version 8.

Without a browser — over ssh, in a CI log, from an agent — read it as text:

ferridriver trace show                    # call tree, timings, failures
ferridriver trace show --errors           # only what went wrong
ferridriver trace show --json             # the whole model, for a script
ferridriver trace ls                      # what the last run recorded
$ ferridriver trace show --errors
checkout > pays
chromium · darwin · ferridriver/0.5.0 · 24 actions · 2 pages · 4.1s
  x locator.click #submit  1.2s
      TimeoutError Timeout 1000ms exceeded
      waiting for locator('#submit')

network 14 requests, 1 failed
  500 POST   https://app.local/api/pay

Manual tracing

context.tracing().start(TracingStartOptions {
    screenshots: true,
    snapshots: true,
    sources: true,
    ..Default::default()
}).await?;
page.goto("https://app.example.com").await?;
page.locator("button.cta").click().await?;
context.tracing().stop(TracingStopOptions { path: Some("trace.zip".into()) }).await?;
await context.tracing.start({ screenshots: true, snapshots: true, sources: true });
await context.tracing.group('checkout');   // nests what follows in the viewer
await page.goto('https://app.example.com');
await context.tracing.groupEnd();
await context.tracing.stop({ path: 'trace.zip' });

browserType.launch({ tracesDir }) decides where the loose trace files are written while recording; live: true flushes them as they happen, so a viewer can follow a recording that has not finished yet.

Video recording

Per-context:

[test.browser.useOptions.recordVideo]
dir  = "test-results/videos"
size = { width = 1280, height = 720 }

Modes:

[test.video]
mode = "retain-on-failure"   # off | on | retain-on-failure

Requires ffmpeg on PATH at runtime.

TypeScript

await page.screenshot({ path: 'home.png', fullPage: true });

// Locator.screenshot() returns the bytes (no path option) — write them yourself.
const chart = await page.locator('.chart').screenshot();
await Bun.write('chart.png', chart);