Skip to content

Commit fee07a8

Browse files
committed
perf(ci): eliminate redundant builds and extract shared test helpers
- test and configurator jobs now download the dist artifact from the build job instead of running npm run build a second time - add test:unit script so CI can run unit tests without triggering the pretest build hook - extract resolveTokenLuminance and contrastBetween browser helpers into tests/color-helpers.js; remove duplicated bodies from color-semantic.spec.js - fix paletteLuminances to define lin() once outside the map loop Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FrqAbbiHbj48rvTiXav54M
1 parent b55ab86 commit fee07a8

4 files changed

Lines changed: 73 additions & 63 deletions

File tree

.github/workflows/ci.yml

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ jobs:
139139
test:
140140
name: Regression tests
141141
runs-on: ubuntu-latest
142+
needs: [build]
142143
steps:
143144
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
144145
with:
@@ -148,14 +149,20 @@ jobs:
148149
node-version: 22
149150
cache: npm
150151
- run: npm ci
151-
# `npm test` runs the `pretest` hook (build + unit tests) before Playwright,
152-
# so an explicit build step here would just build twice.
152+
# Reuse the dist/ artifact from the build job — no need to rebuild.
153+
- uses: actions/download-artifact@v4
154+
with:
155+
name: dist
156+
path: dist/
157+
# Run unit tests directly (skipping the pretest build hook).
158+
- run: npm run test:unit
153159
- run: npx playwright install --with-deps chromium firefox webkit
154-
- run: npm test
160+
- run: npx playwright test
155161

156162
configurator:
157163
name: Configurator tests
158164
runs-on: ubuntu-latest
165+
needs: [build]
159166
steps:
160167
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
161168
with:
@@ -169,6 +176,13 @@ jobs:
169176
- run: npm ci
170177
- run: npm ci
171178
working-directory: configurator
179+
# Reuse the dist/ artifact from the build job so the configurator Vite
180+
# build can resolve PreviewPanel.svelte's ?raw CSS import without a
181+
# redundant root rebuild.
182+
- uses: actions/download-artifact@v4
183+
with:
184+
name: dist
185+
path: dist/
172186
# Unit suite — the sync tripwire: curated Basic controls, presets, fluid
173187
# engine scalars and knob defaults all pinned to the live catalogue —
174188
# plus the Vitest component suite (test runs both via test:unit/components).
@@ -180,14 +194,10 @@ jobs:
180194
# Type/diagnostics gate (kept at 0 errors / 0 warnings).
181195
- run: npm run check
182196
working-directory: configurator
183-
# Build root dist/ so PreviewPanel.svelte's ?raw CSS import resolves
184-
# during the Vite build. dist/*.css is gitignored and only produced by
185-
# `npm run build` at the repo root.
186-
- run: npm run build
187-
# Browser regression suite (test:e2e prebuilds; Playwright manages the
188-
# preview server via its webServer option). Cross-engine: the full suite
189-
# runs on Chromium; core behaviour + the dogfood/isolation contract also
190-
# run on Firefox and WebKit (see playwright.config.js projects).
197+
# Browser regression suite (test:e2e prebuilds the configurator via Vite;
198+
# Playwright manages the preview server via its webServer option).
199+
# Cross-engine: the full suite runs on Chromium; core behaviour + the
200+
# dogfood/isolation contract also run on Firefox and WebKit.
191201
- run: npx playwright install --with-deps chromium firefox webkit
192202
working-directory: configurator
193203
- run: npm run test:e2e

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"lint:css:fix": "stylelint \"**/*.css\" --fix",
5454
"pretest": "npm run build && node --test tests/tier1-p2-coverage.test.js tests/tier1-p7-oldengine.test.js tests/tier1-p10-contrast.test.js tests/api-index-sync.test.js",
5555
"test": "playwright test",
56+
"test:unit": "node --test tests/tier1-p2-coverage.test.js tests/tier1-p7-oldengine.test.js tests/tier1-p10-contrast.test.js tests/api-index-sync.test.js",
5657
"test:install": "playwright install --with-deps chromium firefox webkit",
5758
"release": "release-it",
5859
"release:minor": "release-it --increment minor",

tests/color-helpers.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Browser-serializable WCAG colour helpers.
2+
// These functions are passed to page.evaluate() — they must be self-contained
3+
// (no imports, no closures over Node.js variables). Playwright serialises each
4+
// function via .toString() before sending it to the browser context.
5+
6+
// Returns WCAG relative luminance for one CSS custom property token.
7+
// Usage: page.evaluate(resolveTokenLuminance, '--sf-color-bg')
8+
export function resolveTokenLuminance(tok) {
9+
const cv = document.createElement('canvas'); cv.width = cv.height = 1;
10+
const ctx = cv.getContext('2d', { willReadFrequently: true });
11+
const el = document.createElement('div'); el.style.backgroundColor = `var(${tok})`;
12+
document.body.appendChild(el);
13+
ctx.clearRect(0, 0, 1, 1); ctx.fillStyle = '#000';
14+
ctx.fillStyle = getComputedStyle(el).backgroundColor; el.remove();
15+
ctx.fillRect(0, 0, 1, 1);
16+
const [r, g, b] = ctx.getImageData(0, 0, 1, 1).data;
17+
const lin = v => { v /= 255; return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4; };
18+
return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
19+
}
20+
21+
// Returns WCAG contrast ratio between two CSS custom property tokens.
22+
// Accepts a two-element array [token1, token2] — page.evaluate only allows one argument.
23+
// Usage: page.evaluate(contrastBetween, ['--sf-color-text', '--sf-color-bg'])
24+
export function contrastBetween([token1, token2]) {
25+
const cv = document.createElement('canvas'); cv.width = cv.height = 1;
26+
const ctx = cv.getContext('2d', { willReadFrequently: true });
27+
const toLum = color => {
28+
ctx.clearRect(0, 0, 1, 1); ctx.fillStyle = '#000'; ctx.fillStyle = color; ctx.fillRect(0, 0, 1, 1);
29+
const [r, g, b] = ctx.getImageData(0, 0, 1, 1).data;
30+
const lin = v => { v /= 255; return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4; };
31+
return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
32+
};
33+
const resolve = tok => {
34+
const el = document.createElement('div'); el.style.backgroundColor = `var(${tok})`;
35+
document.body.appendChild(el); const c = getComputedStyle(el).backgroundColor; el.remove(); return c;
36+
};
37+
const a = toLum(resolve(token1));
38+
const b = toLum(resolve(token2));
39+
return (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05);
40+
}

tests/color-semantic.spec.js

Lines changed: 11 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5,54 +5,12 @@
55
import { test, expect } from '@playwright/test';
66
import path from 'node:path';
77
import { pathToFileURL } from 'node:url';
8+
import { resolveTokenLuminance, contrastBetween } from './color-helpers.js';
89

910
// fixture.html loads slashed.full.css (palette tokens are core)
1011
const FIXTURE = pathToFileURL(path.join(import.meta.dirname, 'fixture.html')).href;
1112

12-
// ── Serialisable in-browser helpers ─────────────────────────────
13-
// Functions passed directly to page.evaluate() must be self-contained
14-
// (no closure over Node.js variables). Single-token helpers are called
15-
// from Node.js with page.evaluate(fn, tokenName) so callers can compose
16-
// them without duplicating the canvas boilerplate.
17-
18-
// Returns WCAG luminance of the resolved value of one CSS custom property.
19-
// Self-contained: safe to pass to page.evaluate(resolveTokenLuminance, tok).
20-
function resolveTokenLuminance(tok) {
21-
const cv = document.createElement('canvas'); cv.width = cv.height = 1;
22-
const ctx = cv.getContext('2d', { willReadFrequently: true });
23-
const el = document.createElement('div'); el.style.backgroundColor = `var(${tok})`;
24-
document.body.appendChild(el);
25-
ctx.clearRect(0,0,1,1); ctx.fillStyle='#000'; ctx.fillStyle=getComputedStyle(el).backgroundColor; el.remove();
26-
ctx.fillRect(0,0,1,1);
27-
const [r,g,b] = ctx.getImageData(0,0,1,1).data;
28-
const lin = v => { v/=255; return v<=0.03928 ? v/12.92 : ((v+0.055)/1.055)**2.4; };
29-
return 0.2126*lin(r) + 0.7152*lin(g) + 0.0722*lin(b);
30-
}
31-
32-
// Returns WCAG contrast ratio between two CSS custom property tokens.
33-
// Accepts a two-element array [token1, token2] for Playwright 1.60 compatibility
34-
// (page.evaluate only accepts a single argument).
35-
function contrastBetween([token1, token2]) {
36-
// Must be self-contained: re-declare helpers (page.evaluate serialises the fn body).
37-
const cv = document.createElement('canvas'); cv.width = cv.height = 1;
38-
const ctx = cv.getContext('2d', { willReadFrequently: true });
39-
const toLum = (color) => {
40-
ctx.clearRect(0,0,1,1); ctx.fillStyle='#000'; ctx.fillStyle=color; ctx.fillRect(0,0,1,1);
41-
const [r,g,b] = ctx.getImageData(0,0,1,1).data;
42-
const lin = v => { v/=255; return v<=0.03928 ? v/12.92 : ((v+0.055)/1.055)**2.4; };
43-
return 0.2126*lin(r) + 0.7152*lin(g) + 0.0722*lin(b);
44-
};
45-
const resolve = (tok) => {
46-
const el = document.createElement('div'); el.style.backgroundColor = `var(${tok})`;
47-
document.body.appendChild(el); const c = getComputedStyle(el).backgroundColor; el.remove(); return c;
48-
};
49-
const a = toLum(resolve(token1));
50-
const b = toLum(resolve(token2));
51-
return (Math.max(a,b) + 0.05) / (Math.min(a,b) + 0.05);
52-
}
53-
54-
// Node.js async helpers — compose page.evaluate(resolveTokenLuminance) calls
55-
// so the canvas boilerplate lives in exactly one place.
13+
// Node.js async helpers — compose page.evaluate(resolveTokenLuminance) calls.
5614

5715
async function getSurfaceLuminances(page) {
5816
const [bg, raised, inset] = await Promise.all([
@@ -63,18 +21,19 @@ async function getSurfaceLuminances(page) {
6321
return { bg, raised, inset };
6422
}
6523

66-
// Returns {step, lum} pairs for the primary palette steps provided.
24+
// Returns {step, lum} pairs for the primary palette steps.
25+
// Self-contained for page.evaluate. One canvas reused across all steps.
6726
function paletteLuminances(steps) {
6827
const cv = document.createElement('canvas'); cv.width = cv.height = 1;
6928
const ctx = cv.getContext('2d', { willReadFrequently: true });
29+
const lin = v => { v /= 255; return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4; };
7030
return steps.map(s => {
71-
ctx.clearRect(0,0,1,1); ctx.fillStyle='#000';
72-
const el=document.createElement('div'); el.style.backgroundColor=`var(--sf-color-primary-${s})`;
73-
document.body.appendChild(el); ctx.fillStyle=getComputedStyle(el).backgroundColor; el.remove();
74-
ctx.fillRect(0,0,1,1);
75-
const [r,g,b]=ctx.getImageData(0,0,1,1).data;
76-
const lin=v=>{v/=255;return v<=0.03928?v/12.92:((v+0.055)/1.055)**2.4;};
77-
return { step: s, lum: 0.2126*lin(r)+0.7152*lin(g)+0.0722*lin(b) };
31+
ctx.clearRect(0, 0, 1, 1); ctx.fillStyle = '#000';
32+
const el = document.createElement('div'); el.style.backgroundColor = `var(--sf-color-primary-${s})`;
33+
document.body.appendChild(el); ctx.fillStyle = getComputedStyle(el).backgroundColor; el.remove();
34+
ctx.fillRect(0, 0, 1, 1);
35+
const [r, g, b] = ctx.getImageData(0, 0, 1, 1).data;
36+
return { step: s, lum: 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b) };
7837
});
7938
}
8039

0 commit comments

Comments
 (0)