`@rpcbase/test` ships the shared Playwright wiring we use across RPCBase packages: a preconfigured `test`/`expect`, automatic V8 coverage collection, and the `rb-test` CLI.
npm install @rpcbase/test@rpcbase/test ships the shared Playwright wiring we use across RPCBase packages: a preconfigured test/expect, automatic V8 coverage collection, and the rb-test CLI.
Create spec/coverage.config.ts (or .js / .json) in your package:
``ts`
export default {
thresholds: {
statements: 75,
lines: 75,
functions: 75,
branches: 60,
},
}
Only RB_DISABLE_COVERAGE=1 skips the hooks; every other option lives inside this config file.build/playwright/coverage/
Coverage artifacts are written to and build/vitest/coverage/, with the report in build/coverage/report/.
If you omit collectCoverageFrom, rb-test uses a reasonable default:
- src/*/.{ts,tsx,js,jsx,mjs,cjs} when src/ existssrc/
- if is missing, rb-test throws unless you set collectCoverageFrom
Unit test files are excluded by default: !*/.test.{ts,tsx,js,jsx,mjs,cjs}.
collectCoverageFrom supports negated globs (prefix with !) to exclude files.
Need stricter coverage in a sub-tree? Extend the same thresholds object with glob keys (mirroring Jest's coverageThreshold syntax):
`ts`
export default {
thresholds: {
global: {
statements: 90,
lines: 85,
functions: 85,
branches: 70,
},
"src/core/**": {
statements: 98,
lines: 95,
},
"src/components/**": {
functions: 92,
},
},
}
- When thresholds only has metric keys, it behaves exactly like before.thresholds.global
- Adding lets you keep a default floor while overriding specific directories.spec/
- Globs run against POSIX-style paths relative to the package root (parent folder of ). Absolute paths are not supported.
- Metrics you omit inside an override inherit from the global thresholds (or the 75/75/75/60 defaults).
- If a glob matches no files you'll get a warning and the override is skipped, so typos are easy to spot.
/ expect directly`ts
// spec/my-component.spec.ts
import { test, expect } from "@rpcbase/test"
test("renders", async ({ page }) => {
await page.goto("/playground")
await expect(page.locator("button"))..toBeVisible()
})
`
The exported test already records coverage via CDP and writes per-test payloads automatically.
Because CDP is Chromium-only, V8 coverage is collected only when running on Chromium (Chrome); other browsers skip coverage collection.
`ts
// playwright.config.ts
import { defineConfig, devices } from "@rpcbase/test"
export default defineConfig({
testDir: "./spec",
reporter: [["list"]],
use: {
baseURL: "http://localhost:9198",
launchOptions: { headless: true },
},
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
],
})
`
Whenever coverage is enabled, the wrapper appends the shared reporter so Istanbul aggregation + threshold enforcement run after the suite.
The wrapper also applies a default testMatch per project:
- *.spec.ts runs on all projects*.spec.desktop.ts
- runs on non-mobile projects only*.spec.mobile.ts
- runs on mobile projects only (project.use.isMobile === true)
To run mobile specs on iOS Safari, add a mobile WebKit project, for example:
`ts`
projects: [
{ name: "chromium", use: { ...devices["Desktop Chrome"] } },
{ name: "mobile-ios-safari", use: { ...devices["iPhone 15 Pro"] } },
],
Each package uses its npm test script as tsc --noEmit && rb-test. The CLI runs two stages:
- Playwright, with any CLI flags you pass through untouched; repo defaults are applied unless you provide your own --config.
Coverage is enforced separately:
- Playwright uses spec/coverage.config.* via the shared reporter and will fail the run if thresholds aren’t met.src/coverage.json
- Vitest only reads (JSON object). If that file is missing, Vitest coverage is skipped.
Need to debug without coverage? Set RB_DISABLE_COVERAGE=1 npm test` and the Playwright hooks short-circuit.