Skip to content

Commit d6d52d9

Browse files
authored
feat(e2e): comprehensive e2e coverage with screenshots, video, and CRG integration (#10)
New test suites: - manual-tracking.spec.ts — Dashboard navigation, section rendering, sign out - live-mode.spec.ts — Live scoreboard connection, API mocking, error handling - live-tracker/graph.spec.ts — D3 score graph rendering, data points, reset - live-tracker/visual-regression.spec.ts — Pixel-level baselines (toHaveScreenshot) - integration/full-stack.spec.ts — Full CRG → API → web app pipeline (@integration) New helpers: - helpers/supabase-mock.ts — Mock PostgREST responses for dashboard pages Playwright config: - Enable screenshots on every test (was only-on-failure) - Enable video retain-on-failure - Add snapshot path template for visual regression - Configure toHaveScreenshot with 1% pixel diff tolerance CI pipeline: - Add integration test job (CRG + scoreboard-api, main branch only) - Upload visual regression snapshots as artifacts - Upload all test results (not just on failure) - Integration gate required before deploy to production
1 parent 26f76b1 commit d6d52d9

8 files changed

Lines changed: 1381 additions & 15 deletions

File tree

.github/workflows/ci.yml

Lines changed: 149 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -121,21 +121,29 @@ jobs:
121121
env:
122122
BASE_URL: http://localhost:5173
123123

124-
- name: Upload Playwright report
124+
- name: Upload Playwright HTML report
125125
uses: actions/upload-artifact@v4
126126
if: always()
127127
with:
128128
name: playwright-report
129129
path: e2e/playwright-report/
130130
retention-days: 14
131131

132-
- name: Upload Playwright test results
132+
- name: Upload Playwright test results (screenshots & videos)
133133
uses: actions/upload-artifact@v4
134-
if: failure()
134+
if: always()
135135
with:
136136
name: playwright-test-results
137137
path: e2e/test-results/
138-
retention-days: 7
138+
retention-days: 14
139+
140+
- name: Upload visual regression snapshots
141+
uses: actions/upload-artifact@v4
142+
if: always()
143+
with:
144+
name: visual-regression-snapshots
145+
path: e2e/tests/**/*-snapshots/
146+
retention-days: 14
139147

140148
# ─────────────────────────────────────────────
141149
# 4. Type-check live-bridge service
@@ -162,12 +170,146 @@ jobs:
162170
working-directory: services/live-bridge
163171

164172
# ─────────────────────────────────────────────
165-
# 5. Deploy to Vercel (Production — main branch)
173+
# 5. CRG Integration Tests (main branch only)
174+
#
175+
# Spins up the real CRG scoreboard + the
176+
# Python scoreboard-api proxy and runs
177+
# integration-tagged Playwright tests against
178+
# the full stack.
179+
#
180+
# This job is required to merge PRs into main
181+
# but does NOT run on dev pushes or PRs to dev.
182+
# ─────────────────────────────────────────────
183+
integration:
184+
name: Integration Tests (CRG + API)
185+
runs-on: ubuntu-latest
186+
needs: [build, e2e]
187+
if: github.ref == 'refs/heads/main' || github.base_ref == 'main'
188+
189+
env:
190+
VITE_SUPABASE_URL: https://placeholder.supabase.co
191+
VITE_SUPABASE_ANON_KEY: placeholder-anon-key
192+
CI: true
193+
194+
steps:
195+
- name: Checkout code
196+
uses: actions/checkout@v4
197+
198+
- name: Checkout CRG Scoreboard
199+
uses: actions/checkout@v4
200+
with:
201+
repository: rollerderby/scoreboard
202+
path: scoreboard
203+
ref: v2025.9
204+
205+
- name: Checkout Scoreboard API
206+
uses: actions/checkout@v4
207+
with:
208+
repository: a1ly404/derby-scoreboard-api
209+
path: scoreboard-api
210+
211+
# ── Java (for CRG) ──────────────────────────
212+
- name: Setup Java 17
213+
uses: actions/setup-java@v4
214+
with:
215+
distribution: temurin
216+
java-version: "17"
217+
218+
- name: Build CRG Scoreboard
219+
run: ant compile
220+
working-directory: scoreboard
221+
222+
# ── Python (for scoreboard-api) ──────────────
223+
- name: Setup Python 3.12
224+
uses: actions/setup-python@v5
225+
with:
226+
python-version: "3.12"
227+
228+
- name: Install scoreboard-api dependencies
229+
run: pip install -r requirements.txt
230+
working-directory: scoreboard-api
231+
232+
# ── Node (for Playwright + monorepo) ─────────
233+
- name: Setup Node.js
234+
uses: actions/setup-node@v4
235+
with:
236+
node-version: "22"
237+
cache: "npm"
238+
239+
- name: Install dependencies (workspace root)
240+
run: npm ci
241+
242+
- name: Install Playwright browsers
243+
run: npx playwright install --with-deps chromium
244+
working-directory: e2e
245+
246+
# ── Start services ───────────────────────────
247+
- name: Start CRG Scoreboard (background)
248+
run: |
249+
java -Done-jar.silent=true \
250+
-Dorg.eclipse.jetty.server.LEVEL=WARN \
251+
-jar lib/crg-scoreboard.jar --port=8000 &
252+
echo "Waiting for CRG to start..."
253+
for i in $(seq 1 30); do
254+
if curl -s http://localhost:8000/ > /dev/null 2>&1; then
255+
echo "CRG is up on :8000"
256+
break
257+
fi
258+
sleep 1
259+
done
260+
working-directory: scoreboard
261+
262+
- name: Start Scoreboard API (background)
263+
run: |
264+
python main.py \
265+
--scoreboard-host localhost \
266+
--scoreboard-port 8000 \
267+
--host 0.0.0.0 \
268+
--port 5001 &
269+
echo "Waiting for Scoreboard API to start..."
270+
for i in $(seq 1 15); do
271+
if curl -s http://localhost:5001/health > /dev/null 2>&1; then
272+
echo "Scoreboard API is up on :5001"
273+
break
274+
fi
275+
sleep 1
276+
done
277+
working-directory: scoreboard-api
278+
279+
# ── Run integration tests ────────────────────
280+
- name: Run integration Playwright tests
281+
run: npx playwright test --grep @integration
282+
working-directory: e2e
283+
env:
284+
BASE_URL: http://localhost:5173
285+
SCOREBOARD_API_URL: http://localhost:5001
286+
CRG_URL: http://localhost:8000
287+
INTEGRATION: "true"
288+
289+
# ── Artifacts ────────────────────────────────
290+
- name: Upload integration test report
291+
uses: actions/upload-artifact@v4
292+
if: always()
293+
with:
294+
name: integration-report
295+
path: e2e/playwright-report/
296+
retention-days: 14
297+
298+
- name: Upload integration test results
299+
uses: actions/upload-artifact@v4
300+
if: always()
301+
with:
302+
name: integration-test-results
303+
path: e2e/test-results/
304+
retention-days: 14
305+
306+
# ─────────────────────────────────────────────
307+
# 6. Deploy to Vercel (Production — main branch)
166308
# ─────────────────────────────────────────────
167309
deploy:
168310
name: Deploy to Vercel Production
169311
runs-on: ubuntu-latest
170-
needs: [test, e2e]
312+
needs: [test, e2e, integration]
171313
if: github.ref == 'refs/heads/main'
172314

173315
steps:
@@ -185,7 +327,7 @@ jobs:
185327
# vercel.json lives at apps/web/vercel.json
186328

187329
# ─────────────────────────────────────────────
188-
# 6. Deploy to Vercel (Preview — dev branch / PRs)
330+
# 7. Deploy to Vercel (Preview — dev branch / PRs)
189331
# ─────────────────────────────────────────────
190332
deploy-preview:
191333
name: Deploy to Vercel Preview

e2e/playwright.config.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,7 @@ export default defineConfig({
2525
// ── Timeouts ─────────────────────────────────────────────────────────────
2626
/** Max time one test can run before it is considered failed */
2727
timeout: 30_000,
28-
expect: {
29-
/** Max time expect() auto-retries before failing */
30-
timeout: 5_000,
31-
},
28+
3229

3330
// ── Parallelism & retries ─────────────────────────────────────────────────
3431
fullyParallel: true,
@@ -57,16 +54,33 @@ export default defineConfig({
5754
*/
5855
baseURL: process.env.BASE_URL ?? 'http://localhost:5173',
5956

60-
/** Capture a screenshot automatically on test failure */
61-
screenshot: 'only-on-failure',
57+
/** Capture a screenshot on every test so we have a visual record */
58+
screenshot: 'on',
6259

63-
/** Record video only when a test is retried (keeps storage reasonable) */
64-
video: 'on-first-retry',
60+
/**
61+
* Record video on failure + first retry. Set to 'on' locally if you want
62+
* full video coverage (larger artifacts).
63+
*/
64+
video: 'retain-on-failure',
6565

6666
/** Collect a Playwright trace on first retry for easier debugging */
6767
trace: 'on-first-retry',
6868
},
6969

70+
/**
71+
* Visual regression settings for toHaveScreenshot().
72+
* Baselines live in e2e/tests/<spec>.spec.ts-snapshots/.
73+
*/
74+
snapshotPathTemplate:
75+
'{testDir}/{testFileDir}/{testFileName}-snapshots/{arg}{-projectName}{ext}',
76+
expect: {
77+
timeout: 5_000,
78+
toHaveScreenshot: {
79+
/** Allow up to 1% pixel diff to absorb font-rendering jitter across OS / CI */
80+
maxDiffPixelRatio: 0.01,
81+
},
82+
},
83+
7084
// ── Browser projects ──────────────────────────────────────────────────────
7185
projects: [
7286
{

e2e/tests/helpers/supabase-mock.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { type Page } from '@playwright/test'
2+
3+
// ---------------------------------------------------------------------------
4+
// Supabase REST (PostgREST) mock
5+
// ---------------------------------------------------------------------------
6+
// The manual-tracking views (Dashboard, Players, Bouts, Teams) fetch data
7+
// from Supabase via the PostgREST proxy at `/rest/v1/<table>`. In E2E tests
8+
// we don't have a real database, so we intercept every REST request and return
9+
// empty arrays (or the appropriate empty-result shape when `count` is used).
10+
//
11+
// This helper is designed to work alongside `mockSupabaseAuth` from
12+
// `./auth.ts` — install both before navigating so all network calls are
13+
// handled before the first render.
14+
// ---------------------------------------------------------------------------
15+
16+
/**
17+
* Tables the web app queries. Each entry maps to an empty-array response
18+
* so the UI renders its "empty state" instead of crashing on a network error.
19+
*/
20+
const MOCKED_TABLES = [
21+
'bouts',
22+
'players',
23+
'teams',
24+
'jams',
25+
'player_teams',
26+
'player_stats',
27+
] as const
28+
29+
/**
30+
* Intercept all Supabase PostgREST endpoints (`/rest/v1/*`) and return empty
31+
* results for every table the app queries.
32+
*
33+
* Handles:
34+
* - Regular SELECT queries → `[]`
35+
* - HEAD / count-only queries (`Prefer: count=exact`) → empty body with
36+
* `content-range: 0-0/0` header so the SDK reads `count` as 0
37+
* - INSERT / UPDATE / DELETE → 200 with `[]` (no-op)
38+
*
39+
* Call this **before** navigating so intercepts are active from the first
40+
* request.
41+
*
42+
* @example
43+
* ```ts
44+
* import { mockSupabaseAuth, mockAuthAndNavigate } from './helpers/auth'
45+
* import { mockSupabaseData } from './helpers/supabase-mock'
46+
*
47+
* test.beforeEach(async ({ page }) => {
48+
* await mockSupabaseData(page)
49+
* await mockAuthAndNavigate(page)
50+
* })
51+
* ```
52+
*/
53+
export async function mockSupabaseData(page: Page): Promise<void> {
54+
// A single wildcard route covers all tables and query-string variants.
55+
await page.route('**/rest/v1/**', async (route) => {
56+
const request = route.request()
57+
const method = request.method().toUpperCase()
58+
59+
// For HEAD requests (used by Supabase `select('id', { count: 'exact', head: true })`)
60+
// return an empty range so the SDK derives `count === 0`.
61+
if (method === 'HEAD') {
62+
await route.fulfill({
63+
status: 200,
64+
contentType: 'application/json',
65+
headers: {
66+
'content-range': '*/0',
67+
'access-control-expose-headers': 'content-range',
68+
},
69+
body: '',
70+
})
71+
return
72+
}
73+
74+
// Check whether the caller asked for a count via the Prefer header.
75+
const prefer = request.headers()['prefer'] ?? ''
76+
const wantsCount = prefer.includes('count=exact')
77+
78+
const headers: Record<string, string> = {}
79+
if (wantsCount) {
80+
headers['content-range'] = '*/0'
81+
headers['access-control-expose-headers'] = 'content-range'
82+
}
83+
84+
// GET, POST, PATCH, DELETE — always return an empty array.
85+
await route.fulfill({
86+
status: 200,
87+
contentType: 'application/json',
88+
headers,
89+
body: JSON.stringify([]),
90+
})
91+
})
92+
}

0 commit comments

Comments
 (0)