deckhearth/playwright.config.js
Randall Stillwell 1cc2e28423 Migrate Deck Hearth off Vercel/Neon to homelab Dokploy stack.
Replace @vercel/postgres, Blob, and Upstash with lib/sql.js, MinIO object
storage, and CT 102 Redis rate limits. Add Dockerfile for Dokploy deploy,
homelab runbooks, Neon data-copy helper, and point CI smoke/visual at the
homelab URL instead of Vercel previews.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-15 09:32:13 -05:00

114 lines
4.8 KiB
JavaScript

// Playwright config for smoke + visual workflows against the homelab
// deployment (or localhost in dev). ESM per package.json `"type": "module"`.
import { defineConfig } from '@playwright/test';
const BASE_URL = process.env.BASE_URL ?? 'http://localhost:3000';
const BYPASS_SECRET = process.env.VERCEL_AUTOMATION_BYPASS_SECRET;
const IS_CI = process.env.CI === 'true';
const USES_VERCEL_PREVIEW = BASE_URL.includes('vercel.app');
if (IS_CI && USES_VERCEL_PREVIEW && !BYPASS_SECRET) {
throw new Error(
'VERCEL_AUTOMATION_BYPASS_SECRET is required when BASE_URL targets a ' +
'Vercel preview (*.vercel.app). Homelab smoke uses deckhearth.stillwell.cloud ' +
'and does not need this secret.'
);
}
if (USES_VERCEL_PREVIEW && !BYPASS_SECRET && !IS_CI) {
console.warn(
'[playwright.config] VERCEL_AUTOMATION_BYPASS_SECRET unset — ' +
'protected Vercel previews may return the SSO challenge page.'
);
}
export default defineConfig({
// Both `tests/smoke/` and `tests/visual/` live under `tests/`.
// Project-level `testMatch` (below) partitions them so the
// two workflows (`--project=smoke` and `--project=visual`)
// each see only the specs they should run.
testDir: './tests',
// Smoke + visual specs are independent; parallelism within a
// single spec adds no value here and would complicate the
// per-test screenshot baseline lifecycle.
fullyParallel: false,
workers: IS_CI ? 1 : undefined,
// One retry in CI handles transient Vercel preview flakes
// (cold-start, DNS propagation). Local: zero retries — fail
// fast so the dev sees the issue immediately.
retries: IS_CI ? 1 : 0,
// 30s per test is plenty for the 3 smoke checks + 1 visual
// screenshot. The convoy file's success metric is < 5min
// total workflow runtime; per-test 30s is well inside that.
timeout: 30_000,
expect: { timeout: 10_000 },
// List reporter in dev for human readability; add HTML in CI
// so the `Upload Playwright report on failure` step
// (preview-smoke.yml line 104) has a populated `playwright-report/`
// to upload. `open: 'never'` keeps the HTML from auto-launching
// a browser tab in headless CI.
reporter: IS_CI ? [['list'], ['html', { open: 'never' }]] : 'list',
// Visual baselines live at `tests/visual/__screenshots__/<arg>{ext}`.
// Workflow `visual-diff.yml` uploads this exact path as the artifact —
// keep them aligned. {arg} is the snapshot name from
// `toHaveScreenshot('home.png')` without the extension; {ext} is
// the extension with the leading dot.
//
// Cross-platform CAUTION (still live): this template drops Playwright's
// default `-<browser>-<platform>` suffix. A Mac dev running
// `npm run test:visual:update` silently overwrites the Linux-CI
// baseline with a Mac-rendered PNG that the CT 111 runner will reject
// on the next diff. The seeded Linux baseline (PR #58 `83a358b`,
// 2026-06-02) is the canonical artifact. Re-seed via the Linux Docker
// recipe in `tests/visual/homepage.spec.ts`'s module docblock OR
// dispatch the (queued) `seed-visual-baselines` workflow on CT 111.
// Never via `npm run test:visual:update` on a Mac.
snapshotPathTemplate: 'tests/visual/__screenshots__/{arg}{ext}',
use: {
baseURL: BASE_URL,
// Headers apply to BOTH browser `page.goto(...)` calls AND the
// test-level `request` fixture's APIRequestContext (verified
// against Playwright docs: `testOptions.extraHTTPHeaders` is
// shared between browser context and APIRequestContext
// construction). This is why `tests/smoke/app.smoke.spec.ts`'s
// third test (`request.get('/api/health')`) reaches the
// protected preview without re-injecting the header in the
// spec body. If a future hotfix shows the header NOT
// propagating to APIRequestContext, see Risk R1 in the
// convoy file's Architecture section.
extraHTTPHeaders: USES_VERCEL_PREVIEW && BYPASS_SECRET
? { 'x-vercel-protection-bypass': BYPASS_SECRET }
: undefined,
// Trace OFF this convoy. Enabling it would land the bypass
// header in the HAR payload (Risk R6); a future polish convoy
// owns the trace-on + HAR-sanitization decision.
trace: 'off',
screenshot: 'off',
video: 'off',
},
projects: [
{
// `Preview smoke` workflow invokes `--project=smoke` (per
// preview-smoke.yml line 98). The testMatch keeps the
// visual specs out of this project.
name: 'smoke',
testMatch: 'smoke/**/*.spec.@(ts|js)',
},
{
// `Screenshot diff` workflow invokes `--project=visual`
// (per visual-diff.yml line 82). The testMatch keeps the
// smoke specs out of this project (so a `--project=visual`
// run doesn't redundantly execute the smoke tests).
name: 'visual',
testMatch: 'visual/**/*.spec.@(ts|js)',
},
],
});