widthwatchGitHub ↗

Documentation.

Scan the responsive continuum locally, compare a candidate against a baseline, and keep both machine-readable evidence and a visual report.

Quick start

Run the published package directly, or install it as a pinned development dependency in CI.

npx widthwatch https://example.com \
  --min-width 320 \
  --max-width 1600 \
  --max-samples 28 \
  --max-captures 8 \
  --output widthwatch.html \
  --json widthwatch.json

If Chromium is not already available, run npx playwright install chromium once. For deterministic results, generate and compare baselines in the same operating system, browser version, font set, color scheme, and device scale factor.

Project configuration

npm install --save-dev widthwatch
npx widthwatch init

This creates a versioned widthwatch.config.ts and a reusable GitHub Actions workflow. The workflow accepts your deployed preview URL, keeps repository permissions read-only, and uploads the portable report as an artifact.

TypeScript API

import {
  scanAtReportSchedule,
  compareReports,
  generateHtmlReport,
  type WidthWatchReport,
} from "widthwatch";

const candidate = await scanAtReportSchedule(
  "http://localhost:4173",
  baseline,
  {
  pageReady: (page) => page.waitForSelector("[data-app-ready]"),
  readinessKey: "app-ready-v1",
  },
);

const comparison = compareReports(baseline, candidate, {
  maxDiffRatio: 0.002,
  includeDiffImages: true,
});

Capture modes

visual is the correctness-first default. Adaptive scans first discover geometry across up to maxSamples widths, then open a fresh page and capture full-page evidence at no more than maxCaptureSamples selected widths. Exact baseline schedules always capture every requested width. Use mode: "layout" or CLI flag --layout-only for a faster single-pass viewport probe. --full-page is already the visual default; with --layout-only it captures the whole document while keeping scroll sweep disabled. reloadPerWidth reruns page initialization at every width; pageReady is a bounded Playwright hook whose context identifies the discovery or capture phase and requires a versioned readinessKey.

Stable output

WidthWatchReport.version is a schema discriminator. Minor releases add optional fields; a breaking schema requires a new version. probes preserves every geometry observation, frames contains screenshot evidence, and canonical issues unifies both sources without dropping discovery-only or capture-only findings. The optional sampling object records both schedules. Comparisons expose severity escalations and de-escalations separately, and fail closed when probe or evidence schedules, viewport dimensions, capture modes, rendering environments, or PNG dimensions are incompatible.

Migration

Baselines created by v0.3.0 or earlier use capture protocol 2 and must be recaptured. v0.3.1 baselines already use capture protocol 3 and remain compatible when the environment and report schedules match.

What it detects

FindingSignalCaveat
Document overflowscrollWidth > viewportHorizontal applications may allowlist this later.
Element overflowRendered box crosses the viewport edge.Decorative bleed can be intentional.
Clipped textText scroll size exceeds a hidden/clip box.Intentional ellipsis should be ignored by selector.
OverlapVisible leaf geometry intersects materially.Overlays and badges can be intentional.
Layout jumpAdaptive samples isolate a geometry discontinuity.Breakpoint changes are informational until they regress.
Visual diffPixel ratio exceeds the configured baseline threshold.Rendering environment must match.

Pull-request comparison

npx widthwatch "$CANDIDATE_URL" \
  --baseline .widthwatch/home.json \
  --json artifacts/home.json \
  --output artifacts/home.html \
  --fail-on-regression

Commit the typed baseline or download it from a trusted main-branch artifact. Never use pull_request_target to execute untrusted pull-request code with deployment secrets.

Hosted demo

The public surface is intentionally smaller than the local package: one page, 320–1440px, at most 5 geometry probes with compact JPEG captures, one browser at a time, three queued jobs, 200 requests, no media, and a 15-second navigation limit. Job status stays in memory for 30 minutes; deployments with the report runtime role enabled keep completed HTML reports in a private encrypted object store with a 7-day lifecycle. Anyone who has the public report link can open its screenshots and page evidence until expiry, so do not scan sensitive pages. The demo accepts public HTTP(S) pages without credentials or cookies.

Known limits

Canvas content, cross-origin iframe internals, intentional collisions, carousels, sticky transitions, content that changes between samples, and authenticated states need project-specific configuration. WidthWatch reports deterministic evidence; it does not pretend every geometric intersection is a product bug.