@procyon-creative/procyon-vrt
v0.1.0
Published
Platform-agnostic visual regression testing harness built on Playwright. Parameterized pages/viewports, pluggable login, persisted auth with TTL.
Readme
procyon-vrt
Shared visual regression testing harness, platform-agnostic. Thin wrapper around Playwright's toHaveScreenshot() with:
- Parameterized
vrt.config.ts(pages × viewports) - Pluggable login middleware (WordPress, Drupal, anything — you write the callback)
- Per-user persisted session storage with TTL + force-refresh
- Playwright "project" integration (splice into your existing
playwright.config.ts)
Install
npm install --save-dev @procyon-creative/procyon-vrtIf installing from a local path during development, set install-links=true in .npmrc to avoid symlink-dup issues with nested @playwright/test:
install-links=trueConfig surface
Three files per consuming project:
1. tests/e2e/vrt.config.ts — what to screenshot
import type { VrtConfig } from '@procyon-creative/procyon-vrt';
import { admin, customer } from './vrt.login';
const config: VrtConfig = {
logins: { admin, customer },
pages: [
{ name: 'home', path: '/' },
{ name: 'about', path: '/about/', mask: ['.timestamp'] },
{ name: 'my-account', path: '/my-account/', auth: 'customer' },
{ name: 'admin', path: '/wp-admin/', auth: 'admin' },
],
viewports: [
{ name: 'desktop', width: 1280, height: 800 },
{ name: 'mobile', width: 375, height: 667 },
],
mask: ['#wpadminbar'],
remove: ['.cookie-notice'],
};
export default config;2. tests/e2e/vrt.login.ts — how to authenticate (platform-specific, you write it)
The package exposes a LoginDefinition interface — implement it for whatever CMS/framework you're on. Nothing is hardcoded.
WordPress:
import type { LoginDefinition } from '@procyon-creative/procyon-vrt';
export const admin: LoginDefinition = {
name: 'admin',
login: async (page) => {
await page.goto('/wp-login.php');
await page.fill('#user_login', process.env.WP_USER!);
await page.fill('#user_pass', process.env.WP_PASS!);
await page.click('#wp-submit');
await page.waitForURL(/wp-admin/);
},
isValid: async (page) => {
const resp = await page.goto('/wp-admin/', { waitUntil: 'domcontentloaded' });
return !!resp?.ok() && !page.url().includes('wp-login.php');
},
};Drupal:
import type { LoginDefinition } from '@procyon-creative/procyon-vrt';
export const admin: LoginDefinition = {
name: 'admin',
login: async (page) => {
await page.goto('/user/login');
await page.fill('input[name="name"]', process.env.DRUPAL_USER!);
await page.fill('input[name="pass"]', process.env.DRUPAL_PASS!);
await page.click('#edit-submit');
await page.waitForURL(/\/user\/\d+/);
},
isValid: async (page) => {
const resp = await page.goto('/user', { waitUntil: 'domcontentloaded' });
return !!resp?.ok() && !page.url().includes('/user/login');
},
};3. tests/e2e/vrt.spec.ts — one line
import { registerVrtTests } from '@procyon-creative/procyon-vrt';
import config from './vrt.config';
registerVrtTests(config);4. Wire into Playwright — option A: single project in your existing config (recommended)
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
import { vrtProject } from '@procyon-creative/procyon-vrt';
export default defineConfig({
// ... your existing config ...
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
testIgnore: ['**/vrt.spec.ts'],
},
vrtProject({
testDir: './tests/e2e',
snapshotPathTemplate: './tests/e2e/vrt-snapshots/{arg}{ext}',
authDir: './tests/e2e/.vrt-auth',
use: {
...devices['Desktop Chrome'],
baseURL: process.env.VRT_BASE_URL || 'https://example.lndo.site',
},
}),
],
});Option B: standalone VRT config file
// playwright.vrt.config.ts
import { defineVrtPlaywrightConfig } from '@procyon-creative/procyon-vrt';
export default defineVrtPlaywrightConfig({
testDir: './tests/e2e',
authDir: './tests/e2e/.vrt-auth',
fallbackBaseURL: 'https://example.lndo.site',
});Run
# Project-based config:
npx playwright test --project=vrt
npx playwright test --project=vrt -g "home" # grep by test title
npx playwright test --project=vrt --update-snapshots # write baselines
# Force fresh logins (ignore TTL cache):
VRT_FORCE_LOGIN=1 npx playwright test --project=vrt
# Point at a different env:
VRT_BASE_URL=https://staging.example.com npx playwright test --project=vrtAuth session caching
- Persisted to
{authDir}/{loginName}.json(PlaywrightstorageState) - TTL: 12h default, configurable via
authTtlMsinvrtProject()orVRT_AUTH_TTL_MSenv VRT_FORCE_LOGIN=1bypasses cache entirely- Optional
isValid(page)probe verifies restored sessions before using them — catches server-side invalidations that TTL alone can't detect - gitignore the auth dir; it contains session cookies
Per-page flags (VrtPage)
| Flag | Type | Purpose |
|------|------|---------|
| name | string | Test title + snapshot filename prefix |
| path | string | URL path relative to baseURL |
| auth | string \| false | Login name from config.logins (or false to force anonymous) |
| before | (page) => Promise<void> | Run before screenshot (clicks, modals) |
| mask | string[] | Selectors rendered as solid blocks |
| remove | string[] | Selectors removed from DOM |
| waitFor | string[] | Selectors to wait for before screenshot |
| fullPage | boolean | Full page vs viewport (default true) |
| element | string | Screenshot only this element |
| delay | number | ms wait before screenshot |
| skip / only | boolean | Skip / focus |
| waitUntil | 'networkidle' \| 'load' \| … | Navigation strategy |
| maxDiffPixelRatio / maxDiffPixels | number | Per-page tolerance |
Build
npm run build