Flow-based visual regression dashboard for Playwright. One command, one HTML file β screenshots connected as user flows with diff slider.
Existing visual regression tools show screenshots as flat galleries. Flowshot shows them as user flows β screens connected with arrows, so you see the journey, not just the pages.
- No server, no account β generates a single HTML file (~63 KB, fully self-contained)
- Works with your existing Playwright snapshots β auto-detects the file naming from
playwright.config.* - Diff mode with drag slider + fullscreen
Compare / Expected / Actual / Difftabs - Dark/light theme, sidebar navigation, lightbox zoom
- Version badge in the report header β know which flowshot generated any report at a glance
# 1. Install
npm i -D flowshot
# 2. Create config
npx flowshot init
# 3. Edit flowshot.config.json β define your flows
# 4. Run Playwright visual tests
npx playwright test e2e/visual.spec.ts
# 5. Generate report
npx flowshotFlowshot integrates with Playwright's built-in visual comparison. Here's the full workflow:
Run your visual tests to capture baseline screenshots:
npx playwright test e2e/visual.spec.ts --update-snapshotsThis saves baseline PNGs to your snapshotDir (e.g. e2e/visual.spec.ts-snapshots/).
Edit components, styles, layouts β anything visual.
npx playwright test e2e/visual.spec.ts || trueWhen Playwright detects a difference, it generates three files in test-results/:
*-expected.pngβ the baseline*-actual.pngβ what the screen looks like now*-diff.pngβ pixel diff highlighted in red
npx flowshotFlowshot scans test-results/ for those diff files, copies them to .flowshot/diffs/, and generates an HTML report.
Open the report and click Diff in the top bar:
- CHANGED (red badge) β screens that differ from baseline
- OK (green badge) β screens that match
- Drag slider on each card to compare expected vs actual side-by-side
- Fullscreen button (top-right of each card) β opens fullscreen slider compare
- Sidebar shows warning icons on flows with changes
- Summary bar shows total changed vs unchanged count
# If the changes are intentional β update baselines:
npx playwright test e2e/visual.spec.ts --update-snapshots
# If something broke β fix your code and re-run:
npx playwright test e2e/visual.spec.tsCombine test + report in one step:
npx playwright test e2e/visual.spec.ts || true && npx flowshotOr add to your Makefile:
test-visual-review: ## Visual test + open flow dashboard
npx playwright test e2e/visual.spec.ts --project=chromium || true
npx flowshotPlaywright's default maxDiffPixelRatio is 0 (exact match). Common settings:
const screenshotOpts = {
maxDiffPixelRatio: 0.05, // allow 5% pixel diff
threshold: 0.2, // Playwright default color threshold
}Setting maxDiffPixelRatio too high (e.g. 0.35) will cause real changes to go undetected.
flowshot.config.json:
{
"snapshotDir": "e2e/visual.spec.ts-snapshots",
"testResultsDir": "test-results",
"platform": "chromium-darwin",
"views": ["mobile", "desktop"],
"outDir": ".flowshot",
"flows": [
{
"name": "Auth Flow",
"steps": [
{ "screen": "auth", "label": "Login", "path": "/auth" },
{ "screen": "home", "label": "Home", "path": "/" }
]
}
],
"components": [
{ "screen": "header-component", "label": "Header" }
]
}| Field | Description |
|---|---|
snapshotDir |
Where Playwright stores baseline screenshots |
testResultsDir |
Where Playwright writes test results (diffs on failure) |
platform |
Snapshot filename suffix, e.g. chromium-darwin |
views |
Viewport names matching your snapshot filenames |
outDir |
Output directory for report and collected diffs |
flows |
Array of user flows, each with ordered steps |
components |
Shared UI components (header, footer, etc.) |
filePattern |
Snapshot filename pattern (optional). Auto-detected from snapshotDir if omitted. Default: {screen}-{view}-{platform} |
By default, flowshot expects Playwright snapshots named as:
{screen}-{view}-{platform}.png
For example: home-mobile-chromium-darwin.png, auth-desktop-chromium-darwin.png
This matches Playwright's default {name}-{projectName}-{platform}.png when your project name equals the view name (mobile, desktop).
If you omit filePattern, flowshot resolves it in this priority order:
playwright.config.{ts,js,mjs,cjs}β parsessnapshotPathTemplate(or uses Playwright's default template if unset), extractsprojects[].nameto auto-populateviews, and setsplatform = process.platformwhen the template injects{-snapshotSuffix}- Disk scan β tries these candidate patterns against files in
snapshotDiruntil one matches:{screen}-{view}-{platform}(Playwright default on modern versions){screen}-{project}-{platform}{screen}-{view}{screen}-{project}{screen}
- Fallback to
{screen}-{view}-{platform}
Detected source + pattern is logged on report generation, e.g.:
π Detected [playwright.config] pattern: {screen}-{project}-{platform} (playwright.config.ts projects=[chromium,mobile] platform=darwin)
Set filePattern explicitly to skip detection entirely.
When Playwright uses its default template, baselines get an OS suffix (-darwin on Mac, -linux on Linux CI). Flowshot auto-sets platform = process.platform at runtime, so the same config works on both β each OS resolves its own baselines. This matches Playwright's per-OS baseline best practice (font rendering differs across platforms).
If auto-detect doesn't fit your setup, override filePattern in flowshot.config.json. Available tokens:
| Token | Value |
|---|---|
{screen} |
Screen name from flows[].steps[].screen |
{view} |
Viewport name from views |
{project} |
Alias for {view} (use whichever reads better) |
{platform} |
Value of platform config field |
Examples:
// Default β Playwright with project per viewport
"filePattern": "{screen}-{view}-{platform}"
// β home-mobile-chromium-darwin.png
// Project name only, no platform suffix
"filePattern": "{screen}-{project}"
// β home-mobile.png
// Screen + view, no platform
"filePattern": "{screen}-{view}"
// β home-mobile.pngFlowshot can automatically discover your app's screens and generate flows β two ways:
Scans your Playwright snapshot directory and test files to find screens and build flows:
flowshot detect # preview detected flows
flowshot detect --write # create flowshot.config.json from detected
flowshot detect --merge # add new flows to existing configHow it works:
- Scans
e2e/visual.spec.ts-snapshots/for screenshot files - Parses
e2e/*.spec.tsforpage.goto()andtoHaveScreenshot()patterns - Groups screens by section (heal-your-heart, know-your-self, etc.)
- Detects component screenshots (header, footer)
Opens your app in a real browser, finds links, clicks them, takes screenshots:
# Start your app first, then:
flowshot crawl --url http://localhost:3000
# Options:
flowshot crawl --url http://localhost:3000 --mobile # mobile viewport
flowshot crawl --url http://localhost:3000 --max-pages 20 # limit pages
flowshot crawl --url http://localhost:3000 --max-depth 2 # limit link depth
flowshot crawl --url http://localhost:3000 --ignore "/admin,/api" # skip paths
flowshot crawl --url http://localhost:3000 --write # save to configHow it works:
- Reads existing
flowshot.config.jsonforsnapshotDirandplatform(if present) - Opens the URL in Playwright Chromium
- Finds all
<a href>links on the page - Visits each link, takes a screenshot
- Follows links from discovered pages (up to max-depth)
- Groups pages by URL section into flows
- Screenshots saved to
snapshotDirwith naming{screen}-{view}-{platform}.png- If no config exists, falls back to
.flowshot/crawl-snapshots/
- If no config exists, falls back to
When using --write or --merge, existing config fields (snapshotDir, platform, views, components) are preserved β only flows are updated.
Requires playwright as a peer dependency: npm i -D playwright
When you run flowshot init, it automatically tries detect first. If snapshots exist, it generates config from them. Otherwise, creates an example config.
flowshot # collect diffs + generate report + open browser
flowshot init # auto-detect flows or create example config
flowshot detect # detect flows from snapshots + test files
flowshot crawl # discover pages by crawling your app
flowshot crawl --ignore "/content,/admin" # skip specific paths
flowshot collect # collect diff images from test-results/
flowshot report # generate HTML report
flowshot report --open # generate and open in browser
flowshot report --inline # embed images as base64 (portable for CI)
flowshot report --collect # collect diffs before generatingGenerate a portable report with embedded images:
npx flowshot report --collect --inlineUpload .flowshot/report.html as a CI artifact.
- name: Visual regression
run: npx playwright test e2e/visual.spec.ts || true
- name: Generate flow report
run: npx flowshot report --collect --inline
- uses: actions/upload-artifact@v4
with:
name: flowshot-report
path: .flowshot/report.html| Layer | Choice | Notes |
|---|---|---|
| Runtime dep | Commander | Only non-Node runtime dep. playwright is an optional peer dep used by the crawl command |
| Report UI | Svelte 5 (runes) | Compiled β no Svelte runtime shipped to users |
| UI build | Vite 8 + vite-plugin-singlefile | Bundles the Svelte app into one self-contained HTML (~63 KB / 22 KB gzip) |
| Library build | tsup (esbuild) | src/cli.ts + src/index.ts β CJS + ESM + .d.ts |
| Types | TypeScript 5.7 strict | Bundler resolution, resolveJsonModule |
| Tests | node --test + node:assert |
Built-in runner, zero test deps, ~50 ms for 11 tests |
| CI | GitHub Actions | ci.yml runs typecheck + build + tests on push/PR; release.yml also gates publish on tests |
| Task runner | Makefile | Short aliases over npm scripts |
Published package: 51.5 KB tarball, 190 KB unpacked, 1 runtime dep
Report output: single self-contained HTML file β no external requests, drop it anywhere
git clone https://github.com/thingnoy/flowshot
cd flowshot
npm installCommon tasks (via make or npm):
| Command | What it does |
|---|---|
make build |
Full build: tsup for CLI/lib + vite for the Svelte report template |
make dev-ui |
Vite dev server with HMR on the report template (mock data auto-loaded) |
make test |
Build + run integration tests (node --test) |
make typecheck |
tsc --noEmit |
make publish-check |
Build + test + npm pack --dry-run preview |
src/cli.tsβ Commander-based CLI entrysrc/report.tsβ image map builder + template renderersrc/detect-*.tsβ snapshot pattern detection (filePattern / playwright.config / disk scan)src/report-ui/β Svelte 5 app for the report UI, compiled by Vite to a single self-contained HTML file (dist/report-template.html). At runtimegenerateReport()reads this template and substitutes a/*FLOWSHOT_DATA*/marker with the JSON payload
Integration tests use Node's built-in test runner (no vitest/jest β zero test deps):
make test
# or
node --test test/*.test.mjsCoverage:
- Pattern detection β explicit
filePattern,playwright.config.*parsing, disk-scan fallback, defaults - Report resolver β component naming across 4 styles (project-based, legacy
{screen}-{platform}, bare{screen}, missing file), single-file HTML output,filePatternround-trip
CI runs tests on every push + PR to main; releases block on a failing build or test run.
MIT



