npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cevek/screentest

v0.3.5

Published

Local desktop tool for visual screenshot-test review — CLI, runner helpers, and review UI shipped as a single package.

Downloads

151

Readme

@cevek/screentest

Local desktop tool for visual screenshot-test review plus the runner helpers and Docker-based browser harness needed to capture the screenshots. Everything ships in one npm package — no copy-paste.

Install

npm  i -D @cevek/screentest playwright vitest && npx  playwright install firefox
# pnpm add -D @cevek/screentest playwright vitest && pnpm exec playwright install firefox
# yarn add -D @cevek/screentest playwright vitest && yarn playwright install firefox

playwright and vitest are peer-deps — keep them in your devDependencies.

Quick start

# 1. Scaffold vitest.screentest.config.ts + tests/example.test.ts
npx screentest init

# 2. Start the shared Firefox daemon (one-time per machine — image builds
#    on first run, ~2 min; cached forever after).
npx screentest serve

# 3. Run.
APP_URL=http://localhost:5050 npx vitest run --config vitest.screentest.config.ts

Add "screentest": "vitest run --config vitest.screentest.config.ts" to your package.json scripts so you can just npm run screentest.

The daemon lives outside your project — one container serves every project on your machine. It listens on ws://localhost:5180, holds ~300MB RAM at idle, and stays up across reboots-of-your-app until you screentest stop it.

Your tests run natively on the host (your normal vitest + TS + path aliases + watch mode); only the Firefox rendering happens in the container, over a WebSocket. Editing tests never needs an image rebuild.

screentest init creates:

  • vitest.screentest.config.ts — separate vitest config wired with the package's globalSetup. Kept under its own name so your normal vitest.config.ts (unit tests) is untouched.
  • tests/example.test.ts — minimal Playwright test using the runner helpers

Existing files are not overwritten — re-run init safely.

Pass --config vitest.screentest.config.ts to keep the screenshot run separate from your normal vitest run. The screenshot config wires up our globalSetup; plain vitest run skips it entirely — no Firefox launch.

Commands

screentest serve    # start the shared Firefox browser-server daemon
screentest stop     # stop it
screentest status   # is it running?
npx vitest run --config vitest.screentest.config.ts

That's the test run — vitest on the host, our globalSetup connects to the daemon. On failure (or new snapshots) the UI auto-launches in your browser. Set CI=1 to skip the auto-UI and just propagate the exit code; SCREENTEST_NO_UI=1 does the same locally.

screentest review

Skip vitest, regenerate doc.json from cached actuals, open the UI.

screentest init

Scaffold vitest config + example test (see above).

screentest <doc-json-path> [--port N] [--no-open]
                           [--worker-url URL] [--token TOKEN]

Open the review UI on a pre-built doc.json. Used internally by the globalSetup hook and by screentest review. With no --port, picks a random free port in [40000, 50000) so parallel test runs don't clash. The UI also beacons a shutdown back to the server on tab close, so you don't need Ctrl+C in the terminal.

Writing tests

import { afterAll, beforeAll, describe, it } from 'vitest';
import type { Page } from 'playwright';
import { compareSnapshot, freezeDate, newPage } from '@cevek/screentest/runner';

const APP_URL = (process.env.APP_URL || 'http://localhost:5050').replace(/\/+$/, '');

let page: Page;
let cleanup: () => Promise<void>;

beforeAll(async () => {
  const r = await newPage();
  await freezeDate(r.ctx, '2024-01-15T12:00:00Z'); // optional
  page = r.page;
  cleanup = r.cleanup;
});
afterAll(() => cleanup?.());

describe('team', () => {
  it('login flow', async () => {
    await page.goto(`${APP_URL}/team`);
    await page.locator('input[name="username"]').waitFor({ state: 'visible' });
    await compareSnapshot(page, 'login'); // → team / login flow / login

    // Snapshot just one element instead of the whole page:
    await compareSnapshot(page.locator('header'), 'header');
  });
});

Snapshot paths are auto-derived from describe(...) + it(...) plus the compareSnapshot leaf name. Hashes go into tests/snapshot.json by default; override with SCREENTEST_SNAPSHOT_FILE=path/to/snapshot.json. Actuals + the generated doc.json go to node_modules/.cache/screentest/ (gitignored by convention).

Upgrading from < 0.3: an existing <projectRoot>/snapshot.json keeps working — the runner picks it up if tests/snapshot.json isn't there yet.

Docker Desktop host networking

The daemon container uses --network=host so its Firefox can reach your app at localhost:*. On macOS / Windows Docker Desktop enable it once: Settings → Resources → Network → Enable host networking. On Linux Docker (CI) it works out of the box.

Cloudflare Worker (blob store)

Accepted screenshots are stored in your Cloudflare Worker (R2-backed). See the worker template for a 100-line implementation + wrangler deploy instructions.

Once deployed:

export CLOUDFLARE_WORKER_URL="https://screentests.your-account.workers.dev"
export CLOUDFLARE_TOKEN="<UPLOAD_TOKEN secret you set with wrangler>"

Or pass --worker-url / --token to screentest directly.

CI

- run: npx screentest serve # build image + start daemon
- run: CI=1 npx vitest run --config vitest.screentest.config.ts

In CI=1 the globalSetup exits with vitest's code and never tries to open the UI. Full recipe (GitHub Actions example, docker layer caching to skip the ~2 min cold-start build, downloading actuals for offline review, pnpm/yarn variants) is in the CI guide on GitHub.

Input format reference

If you need to construct your own doc.json for the UI (instead of going through vitest run / screentest review), see the format documentation in the source repo.

Security

  • The UI server listens only on 127.0.0.1. Never on 0.0.0.0.
  • actual files are served strictly from a whitelist computed from doc.json at startup — no path traversal possible.