webperf-snippets
v0.2.0
Published
Run curated WebPerf Snippets headlessly via Playwright. Diagnose Core Web Vitals beyond what Lighthouse exposes.
Downloads
418
Maintainers
Readme
webperf-snippets CLI
Run curated WebPerf Snippets headlessly via Playwright. Diagnose Core Web Vitals beyond what Lighthouse exposes and gate CI on real performance budgets.
Status: v0.2. Core Web Vitals, loading audit, and structural checks. See Roadmap for what's next.
Why
Lighthouse gives you a score. The DevTools snippets give you the diagnosis — TTFB / Resource Load Delay / Element Render Delay sub-parts, LoAF script attribution, render-blocking resources, etc. This CLI runs the same curated snippets in a headless browser so you can:
- Diagnose LCP regressions in CI without copy-pasting into DevTools.
- Gate pull requests on real performance budgets.
- Automate the snippets you already run by hand.
Install
Playwright is a peer dependency. Install both, plus the chromium browser:
npm install --save-dev webperf-snippets playwright
npx playwright install chromiumUsage
npx webperf-snippets <url> [options]Examples
Run the default Core Web Vitals workflow (LCP + CLS, plus LCP-Subparts if LCP > 2.5s):
npx webperf-snippets https://web.devLoading audit (TTFB, FCP, render-blocking, scripts, fonts):
npx webperf-snippets https://web.dev --workflow loadingStructural checks for CI (render-blocking, fonts, priority hints, resource hints):
npx webperf-snippets https://web.dev --workflow auditMarkdown output for PR comments:
npx webperf-snippets https://web.dev --markdownJSON output (for piping into jq or CI):
npx webperf-snippets https://web.dev --jsonSingle snippet:
npx webperf-snippets https://web.dev --snippet LCP-SubpartsSynthetic INP measurement with an interaction script:
npx webperf-snippets https://web.dev --snippet INP --interact-script interactions.jsonCI gating:
npx webperf-snippets https://web.dev --budget-lcp 2500 --budget-cls 0.1Options
| Option | Description |
| ---------------------------- | ---------------------------------------------------------------------- |
| --workflow <name> | Workflow to run. Default: core-web-vitals. Options: core-web-vitals, loading, audit. |
| --snippet <name> | Run a single snippet by alias or Category/Name path. |
| --json | Output JSON instead of formatted text. |
| --markdown | Output GitHub-renderable markdown (for PR comments). |
| --viewport <preset> | Viewport preset: mobile (default), tablet, desktop. |
| --wait <ms> | Post-load wait before evaluating snippets. Default: 3000. |
| --interact-script <path> | JSON file with interactions to run before evaluation (for INP). |
| --budget-lcp <ms> | Exit 1 if LCP exceeds this value. |
| --budget-cls <score> | Exit 1 if CLS exceeds this value. |
| --verbose | Show all items, including passing checks. |
| --headed | Show the browser window (debug). |
| -h, --help | Show help. |
Snippet aliases
| Alias | Snippet |
| ------------------ | ---------------------------------------------- |
| LCP | CoreWebVitals/LCP |
| CLS | CoreWebVitals/CLS |
| LCP-Subparts | CoreWebVitals/LCP-Subparts |
| fonts | Loading/Fonts-Preloaded-Loaded-and-used-above-the-fold |
| render-blocking | Loading/Find-render-blocking-resources |
| resource-hints | Loading/Resource-Hints-Validation |
| preload-scripts | Loading/Validate-Preload-Async-Defer-Scripts |
| priority-hints | Loading/Priority-Hints-Audit |
| critical-css | Loading/Critical-CSS-Detection |
| ttfb | Loading/TTFB-Sub-Parts |
| script-parties | Loading/First-And-Third-Party-Script-Info |
| script-loading | Loading/Script-Loading |
| lazy-atf | Loading/Find-Above-The-Fold-Lazy-Loaded-Images |
| lazy-conflict | Loading/Find-Images-With-Lazy-and-Fetchpriority |
| eager-below-fold | Loading/Find-non-Lazy-Loaded-Images-outside-of-the-viewport |
Exit codes
| Code | Meaning |
| ---- | --------------------------------------------- |
| 0 | All checks passed. |
| 1 | Budget violation, or a snippet errored. |
| 2 | Usage error (missing URL, unknown workflow). |
CI example
GitHub Actions, fail the PR if LCP exceeds 2.5s:
- run: |
npm install --no-save webperf-snippets playwright
npx playwright install --with-deps chromium
npx webperf-snippets https://staging.web.dev --budget-lcp 2500 --budget-cls 0.1Publishing
The CLI package is published to npm via a tag-based workflow. Publishing is explicit and intentional — it only happens when a cli-v* tag is pushed.
Release steps
- Bump the version in
cli/package.json. - Commit the version change.
- Tag and push:
git tag cli-v0.2.0 git push origin cli-v0.2.0 - The
publish-cliCI job runs, executes the full test suite, and publishes to npm.
Why tag-based and not path-based
An alternative is to publish automatically on every push to main that touches cli/, using a version check to skip republishes. Tag-based publishing was chosen instead because it keeps releases deliberate — a passing CI on main does not mean the package is ready to ship, and a tag communicates that intent explicitly.
Access control
Tag protection rules restrict who can push cli-v* tags. Configure them under Settings → Rules → New ruleset in the repository, targeting the cli-v* tag pattern and limiting push access to admins or maintainers. This ensures only authorized collaborators can trigger a publish.
Required secret
The NPM_TOKEN secret must be set in the repository settings with publish access to the webperf-snippets npm package.
Known limitations
- CLS in headless is conservative: layout shifts that only happen on scroll are missed unless you script the scroll.
- First navigation only: each
webperf-snippetsinvocation runs one URL. SPAs need the post-route URL passed directly. - Synthetic INP ≠ field INP:
--interact-scriptmeasures handler latency for a single scripted event. Real INP reflects the worst interaction across all user sessions — use RUM for field data.
Roadmap
- ~~v0.2: Loading workflow (TTFB, FCP, render-blocking, scripts, fonts), shared page session, synthetic interactions for INP, markdown reporter for PR comments.~~ ✓ Released
- v0.3: GitHub Action wrapper.
- v0.4: Auth flows (login + measure logged-in pages), CrUX field-data enrichment.
How it works
- Launches headless chromium via Playwright.
- Pre-registers
PerformanceObservers for LCP and layout-shift before navigation (Chrome doesn't expose these viagetEntriesByTypewithout a buffered observer, so the runner shims it). - Navigates, waits for the page to settle.
- Evaluates each snippet's IIFE in the page context, capturing the returned object.
- Applies the workflow's decision tree to enqueue follow-up snippets.
- Renders results (human or JSON) and exits with an appropriate code.
License
MIT — see LICENSE.
