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

@review-kit/playwright

v0.3.13

Published

Playwright capture SDK for review-kit: reporter, capture contract and annotation helper.

Readme

@review-kit/playwright

Playwright capture SDK for review-kit. It turns passing Playwright tests into before/after review captures: a reporter writes a scenarios.json sidecar plus the screenshots, and a small contract (one annotation, one attachment per capture) tells it what each screenshot is.

There are two ways to integrate. Both produce the exact same output ; pick whichever fits your repo.

| Mode | Install | Wiring | Per-test call | | --- | --- | --- | --- | | Imported (devDep) | @review-kit/playwright | reporter by package specifier | attachReviewCapture(...) | | Contract (zero-dep) | nothing | reporter by injected path | raw annotation + attachment |

Mode 1: imported (devDependency)

pnpm add -D @review-kit/playwright

Reference the reporter by its package specifier in playwright.config.ts. Your own html reporter still owns the HTML report ; this one only writes the sidecar + assets.

import { defineConfig } from '@playwright/test'

export default defineConfig({
  reporter: [['html', { open: 'never' }], ['@review-kit/playwright/reporter']],
})

In a test, attach one capture with the helper. meta is the scenario descriptor ; persona is null for anonymous scenarios.

import { test } from '@playwright/test'
import { attachReviewCapture } from '@review-kit/playwright'

test('login', async ({ page }, testInfo) => {
  await page.goto('/login')
  const shot = testInfo.outputPath('login.png')
  await page.screenshot({ path: shot })
  await attachReviewCapture(testInfo, { id: 'login', app: 'web', title: 'Login', persona: null, viewport: 'mobile' }, shot)
})

A fourth argument, statePath, is optional : the path to a file describing the screen's state at capture time. review-kit copies that file next to the screenshot and references it from the sidecar ; it never reads it, so its format is yours. Below, page.ariaSnapshot() is one possible way to produce it, not a required one.

import { writeFile } from 'node:fs/promises'

test('login', async ({ page }, testInfo) => {
  await page.goto('/login')
  const shot = testInfo.outputPath('login.png')
  await page.screenshot({ path: shot })
  const state = testInfo.outputPath('login.state.txt')
  await writeFile(state, await page.ariaSnapshot())
  const meta = { id: 'login', app: 'web', title: 'Login', persona: null, viewport: 'mobile' }
  await attachReviewCapture(testInfo, meta, shot, state)
})

A fifth argument, page, is optional : pass the Playwright page and the SDK serializes an anchor map sidecar at screenshot time, the map of annotable elements (bounding boxes, multi-signal anchors, ARIA roles, stacking order). It also records the buffered console errors and page errors for that scenario. This mode requires Playwright 1.56 or newer. The review page uses the anchor map to resolve a click on the capture into a real element. An invalid map fails the capture, never a silently absent sidecar.

test('login', async ({ page }, testInfo) => {
  await page.goto('/login')
  const shot = testInfo.outputPath('login.png')
  await page.screenshot({ path: shot })
  const meta = { id: 'login', app: 'web', title: 'Login', persona: null, viewport: 'mobile' }
  await attachReviewCapture(testInfo, meta, shot, undefined, page)
})

resolveClick(map, x, y) resolves a point on the capture to the deepest element under it plus the ordered stack of candidates ; parseAnchorMap validates a sidecar read back from disk. The anchoring engine itself (generateAnchor, resolveAnchor) is exported too : it is vendored from SitePing (MIT) and re-anchors an element across re-renders with a reported confidence.

Author your review-kit.config.ts with defineConfig for inference and inline errors. Keep it inside your e2e package (the one carrying this devDependency) : the CLI discovers it by walking your workspace packages, so there is no root artifact to wire.

// packages/e2e/review-kit.config.ts
import { defineConfig } from '@review-kit/playwright'

export default defineConfig({
  capture: 'pnpm capture',
  seed: 'pnpm seed',
})

Two fields only. capture and seed are the names of scripts the CLI runs from this file's directory ; repoSlug is optional and defaults to the repo folder name.

Each field references one named script, never an inline shell pipeline. A multi-step pipeline belongs in that package's scripts (or, better, in code owned by your app), so the config stays a stable pointer :

// packages/e2e/package.json : the pipeline lives here, the config just names it
{ "scripts": { "seed": "pnpm db:reset && pnpm db:seed:e2e", "capture": "playwright test" } }

review-kit init scaffolds the config. If you prefer, import type { ReviewKitConfig } and type the object yourself.

Optional: the environment block

By default review-kit runs seed and capture with the current environment. If a checkout needs to be provisioned first, or the capture needs runtime variables (where services listen, which database), add an optional environment block. Two tokens are substituted in either command : {path}, the checkout's absolute path, and {branch}, its branch name.

import { defineConfig } from '@review-kit/playwright'

export default defineConfig({
  capture: 'pnpm capture',
  seed: 'pnpm seed',
  environment: {
    setup: 'my-tool provision {path} --branch {branch}', // idempotent, run once per checkout before seed/capture
    env: 'my-tool env {path}',                           // prints KEY=VALUE lines, merged over the current env
    refresh: 'pnpm install && pnpm -r build',            // base worktree only : rebuild what the detached checkout left stale
  },
})
  • {branch} : the branch of the checkout being readied. The PR side gets the PR's branch ; the base side is a worktree detached on the merge-base, which has no branch to derive, so review-kit names it review-base. A tool that keys an environment per branch (a database per branch, say) therefore gives each side its own, which is what makes the base/branch comparison meaningful.
  • setup (optional) : an idempotent command review-kit runs once per checkout (base worktree and branch) before seeding, to ready its environment.
  • env (optional) : a command that prints the checkout's environment as dotenv (one KEY=VALUE per line ; blank lines and # comments allowed). review-kit merges those variables over the current env and hands them to seed and capture. It reads no key by name and builds none : it is a pipe.
  • refresh (optional) : a command review-kit runs only on the base worktree, right after it is (re)materialized and before setup/seed. The base is a detached checkout on the merge-base, so its git-ignored artifacts (node_modules, dist) are stale or absent : refresh rebuilds them (install + build) and clears any reference-only baseline. It never runs on the PR branch checkout, whose artifacts the developer already keeps current. It runs on every base cache miss.

Both are optional. A repo with no provisioning step just omits the block. Any tool that provisions a checkout and prints its environment as dotenv fits, including a script of your own :

export default defineConfig({
  capture: 'pnpm capture',
  seed: 'pnpm seed',
  environment: {
    setup: 'pnpm provision {path} --branch {branch}',
    env: 'pnpm print-env {path}',
  },
})

Optional: the live env (serve + personas)

review-kit can bring a PR's captured world back up as an interactive environment (review-kit env start --pr <n>). It is opt-in and needs infrastructure on the review host (wildcard DNS, TLS, a reverse proxy, the daemon) : see the CLI's live env guide. Two optional fields drive it, both degrading cleanly when absent:

export default defineConfig({
  capture: 'pnpm capture',
  seed: 'pnpm seed',
  serve: { command: 'pnpm serve', urlVar: 'APP_URL' },
  environment: { env: 'my-env-tool env {path}' },
  personas: [
    { label: 'Admin', login: '[email protected]', password: 'review-only' },
    { label: 'Anonymous' },
  ],
})
  • serve (optional) : { command, urlVar }. command starts your app's services in the foreground (review-kit kills its process group on idle-stop). urlVar is the name of the environment variable (from your environment.env output) that carries the local URL your app listens on ; review-kit reads it, reverse-proxies the public URL to it, and allocates nothing. Your app binds the port it already owns (nothing is injected). serve receives the same merged environment as seed/capture (the environment.env output over the current env), so it reads its own URL from that same variable.
  • personas (optional) : credentials review-kit shows on the review page so a reviewer logs in by hand. label is required ; login/password are optional. Never put production secrets here.

If serve is absent the live-env button is simply not offered. Re-running review-kit run stops the PR's live env (its world is re-seeded).

Mode 2: contract (zero dependency)

No install. The review-kit CLI injects REVIEW_KIT_REPORTER (an absolute path to the reporter) and REVIEW_KIT_CAPTURE_OUT when it runs your capture command. Load the reporter from that path:

import { defineConfig } from '@playwright/test'

const reviewKit = process.env.REVIEW_KIT_REPORTER
export default defineConfig({
  reporter: [['html', { open: 'never' }], ...(reviewKit ? [[reviewKit]] : [])],
})

Emit the same contract by hand: a review-kit:scenario annotation whose description is the JSON of the scenario meta, plus a review-kit:capture attachment for the screenshot. The optional review-kit:state attachment carries the state file (here written from page.ariaSnapshot(), one possible source among others).

test('login', async ({ page }, testInfo) => {
  await page.goto('/login')
  const shot = testInfo.outputPath('login.png')
  await page.screenshot({ path: shot })
  const state = testInfo.outputPath('login.state.txt')
  await writeFile(state, await page.ariaSnapshot())
  testInfo.annotations.push({
    type: 'review-kit:scenario',
    description: JSON.stringify({ id: 'login', app: 'web', title: 'Login', persona: null, viewport: 'mobile' }),
  })
  await testInfo.attach('review-kit:capture', { path: shot, contentType: 'image/png' })
  await testInfo.attach('review-kit:state', { path: state, contentType: 'text/plain' })
})

Capture contract

  • Annotation review-kit:scenario : description is JSON.stringify(ScenarioMeta).
  • Attachment review-kit:capture : the screenshot (required for a passing annotated test).
  • Attachment video : Playwright's native video, picked up automatically when present.
  • Attachment review-kit:state (optional) : a file describing the screen's state at capture time. review-kit transports it and never parses it : any format works, and a capture without it stays valid.
  • Attachment review-kit:anchors (optional) : the anchor map sidecar as an in-memory JSON body (never a file path). attachReviewCapture(..., page) emits it ; a capture without it stays valid.
  • A capture identity is ${id}-${viewport}, so one scenario captured at several viewports yields distinct entries.
  • Each captured scenario in scenarios.json carries image, plus video, state and anchors when those attachments were present : assets/${id}-${viewport}.png, .webm, .state.txt and .anchors.json.
  • ScenarioMeta.route (optional) : the app path the scenario visited (e.g. /login). It flows to the manifest so the live env can deep-link a scenario to its screen.

License

MIT