Approval Testing
Overview
Section titled “Overview”@averspec/approvals provides two ways to lock in behavior as a baseline:
approve(value) — Serializes a value (object, string, array) to text and compares it against a stored .approved.txt file. On first run there’s no baseline, so the test fails. You review the output, approve it, and future runs diff against that baseline. Any structural change to the output fails the test until you explicitly approve the new version. Good for API responses, computed results, configuration snapshots — anything you can serialize.
approve.visual('name') — Takes a screenshot via the protocol’s screenshotter (e.g., Playwright) and compares it pixel-by-pixel against a stored .approved.png. Same workflow: first run captures, subsequent runs diff. Produces a visual diff image highlighting changed pixels. Good for UI regressions — layout shifts, missing elements, style changes.
Both follow the same cycle: baseline → compare → diff → approve.
import { approve } from '@averspec/approvals'
// Structural: serialize and diff a data valueawait approve(taskList, { name: 'tasks' })
// Visual: screenshot and pixel-diff the current screenawait approve.visual('board-with-task')from averspec.approvals import approve
# Structural: serialize and diff a data valueapprove(task_list, name="tasks")
# Visual: screenshot and pixel-diff the current screenapprove.visual("board-with-task")require "averspec/approvals"
# Structural: serialize and diff a data valueAver.approve(task_list, name: "tasks")
# Visual: screenshot and pixel-diff the current screenAver.approve_visual("board-with-task")import "github.com/averspec/aver-go/approvals"
// Structural: serialize and diff a data valueapprovals.Approve(taskList, approvals.Opts{Name: "tasks"})
// Visual: screenshot and pixel-diff the current screenapprovals.Visual("board-with-task")use averspec::approvals::{approve, approve_visual};
// Structural: serialize and diff a data valueapprove("tasks", &task_list, &[]);
// Visual: screenshot and pixel-diff the current screenapprove_visual("board-with-task");import dev.averspec.approvals.approveimport dev.averspec.approvals.approveVisual
// Structural: serialize and diff a data valueapprove(taskList, "tasks")
// Visual: screenshot and pixel-diff the current screenapproveVisual("board-with-task")characterize() vs approve()
Section titled “characterize() vs approve()”characterize() and approve() are the same function — import { characterize } from '@averspec/approvals'. The difference is intent. Use characterize() when you’re locking in behavior you haven’t fully validated yet: “I don’t know if this output is correct, but I want to know if it changes.” Use approve() when you’ve reviewed the baseline and confirmed it’s the desired behavior.
One workflow that can emerge from this: start with characterize() on legacy code, and as you gain understanding, rename calls to approve() to mark that the baseline has been deliberately validated. Whether your team adopts that convention depends on how much you value the signal in your test code — it’s there if you want it.
Workflow
Section titled “Workflow”- First run: test fails with “Baseline missing”
- Run
npx aver approveto create the baseline - Subsequent runs: auto-compare against baseline
- On mismatch: diff files generated, test fails
- Run
npx aver approveagain to update the baseline
Visual Approvals
Section titled “Visual Approvals”Visual approvals use the screenshotter protocol extension. Protocols that can take screenshots (e.g., Playwright) provide this automatically.
import { playwright } from '@averspec/protocol-playwright'
const proto = playwright({ regions: { 'board': '.board', 'backlog': '[data-testid="column-backlog"]', },})// Full page screenshotawait approve.visual('board-state')
// Scoped to a named regionawait approve.visual({ name: 'backlog', region: 'backlog' })# Full page screenshotapprove.visual("board-state")
# Scoped to a named regionapprove.visual(name="backlog", region="backlog")# Full page screenshotAver.approve_visual("board-state")
# Scoped to a named regionAver.approve_visual(name: "backlog", region: "backlog")// Full page screenshotapprovals.Visual("board-state")
// Scoped to a named regionapprovals.Visual("backlog", approvals.VisualOpts{Region: "backlog"})// Full page screenshotapprove_visual("board-state");
// Scoped to a named regionapprove_visual_with("backlog", VisualOpts { region: "backlog" });// Full page screenshotapproveVisual("board-state")
// Scoped to a named regionapproveVisual("backlog", region = "backlog")On protocols without a screenshotter (unit, http), approve.visual() throws an error. Only use it with visual protocols like Playwright.
Visual Diff Demo (Playwright)
Section titled “Visual Diff Demo (Playwright)”From the example app:
cd examples/task-board1) Create the initial baseline
Section titled “1) Create the initial baseline”AVER_DEMO_APPROVAL=1 pnpm aver approve --adapter playwright tests/task-board.spec.tsThis writes:
tests/__approvals__/visual-approval-of-task-board/board-with-task.approved.png
2) Run again to verify it matches
Section titled “2) Run again to verify it matches”AVER_DEMO_APPROVAL=1 pnpm aver run --adapter playwright tests/task-board.spec.tsApproval artifacts live in tests/__approvals__/<test-name>/:
board-with-task.approved.png ← committed (baseline)board-with-task.received.png ← gitignored (transient)board-with-task.diff.png ← gitignored (transient)Recommended .gitignore
Section titled “Recommended .gitignore”**/__approvals__/**/*.received.***/__approvals__/**/*.diff.*