B3 of TEST-INFRA-001 (RFC-WS-FRONTEND-PRIMEVUE Amendment A-1). - Add tests/playwright-ct/visual/static-server.mjs: 60-line Node http server that serves the canonical prototype directory. No new dependency added (vs. http-server / serve packages). - Wire static server into playwright-ct.config.ts via webServer; tests navigate to http://127.0.0.1:5179/crewli-timetable.html. - Add tests/playwright-ct/visual/prototype-smoke.spec.ts to verify the prototype loads in CT runner. - Add tests/playwright-ct/visual/prototype.spec.ts with 5 @visual composite baselines: canvas-friday.png — all status colors, b2b indicators, multi-lane stacking canvas-saturday.png — conflict ring + capacity warnings stage-row-multilane.png — first row in isolation wachtrij-populated.png — sidebar list with parked + pending popover.png — block-click popover layout 9 additional surfaces from RFC §A.3's enumerated list are documented as test.skip() with reasons (cancelled status absent from prototype data, isolated-block locators would lock to artist names, drag-mode flaky under simulated pointer events, empty Wachtrij/empty day not reachable from canonical seed). All deferred to F4 component-level Vue baselines that will use stable data-test-id attributes. - Baselines stored at tests/playwright-ct/__screenshots__/visual/ prototype.spec.ts/*.png; tracked via Git LFS (.gitattributes). Composite-over-isolated rationale: the prototype's DOM exposes status only via inline style.background, no data-* attributes. Isolated-block baselines would require artist-name locators that silently rot if prototype data changes. Composite captures yield the same visual vocabulary in fewer, more stable images. dev-docs/ARCH-TESTING.md (B5) documents this strategy and the F4 transition plan. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
73 lines
2.2 KiB
JavaScript
73 lines
2.2 KiB
JavaScript
/* eslint-disable no-console */
|
|
import { createReadStream, statSync } from 'node:fs'
|
|
import http from 'node:http'
|
|
import path from 'node:path'
|
|
|
|
// Tiny static-file server that serves the canonical Crewli prototype
|
|
// HTML so Playwright visual baselines can render it in real Chromium.
|
|
//
|
|
// Why not http-server / serve / similar?
|
|
// --------------------------------------
|
|
// They'd add ~5 MB and another supply-chain hop for a 30-line problem.
|
|
// The prototype directory has 9 files; we serve them with mime types
|
|
// for .html, .css, .js, .jsx (text/babel handles via the HTML loader).
|
|
// Node's built-in http + fs is sufficient.
|
|
//
|
|
// Started by playwright-ct.config.ts via webServer config.
|
|
|
|
const ROOT = path.resolve(
|
|
process.cwd(),
|
|
'..',
|
|
'..',
|
|
'resources',
|
|
'Crewli - Artist Timetable Management',
|
|
)
|
|
const PORT = Number(process.env.PROTOTYPE_PORT ?? 5179)
|
|
|
|
const MIME = {
|
|
'.html': 'text/html; charset=utf-8',
|
|
'.js': 'application/javascript; charset=utf-8',
|
|
'.jsx': 'text/babel; charset=utf-8',
|
|
'.mjs': 'application/javascript; charset=utf-8',
|
|
'.css': 'text/css; charset=utf-8',
|
|
'.json': 'application/json; charset=utf-8',
|
|
'.svg': 'image/svg+xml',
|
|
'.png': 'image/png',
|
|
'.ico': 'image/x-icon',
|
|
}
|
|
|
|
const server = http.createServer((req, res) => {
|
|
// Strip query / hash before path resolution.
|
|
const urlPath = (req.url ?? '/').split('?')[0].split('#')[0]
|
|
const safe = path.normalize(urlPath).replace(/^(\.\.[\\/])+/, '')
|
|
const filePath = path.join(ROOT, safe === '/' ? 'crewli-timetable.html' : safe)
|
|
|
|
// Reject path traversal.
|
|
if (!filePath.startsWith(ROOT)) {
|
|
res.statusCode = 403
|
|
res.end('Forbidden')
|
|
return
|
|
}
|
|
|
|
try {
|
|
const stat = statSync(filePath)
|
|
if (stat.isDirectory()) {
|
|
res.statusCode = 404
|
|
res.end('Not found')
|
|
return
|
|
}
|
|
const ext = path.extname(filePath).toLowerCase()
|
|
res.setHeader('Content-Type', MIME[ext] ?? 'application/octet-stream')
|
|
res.setHeader('Cache-Control', 'no-store')
|
|
createReadStream(filePath).pipe(res)
|
|
}
|
|
catch {
|
|
res.statusCode = 404
|
|
res.end(`Not found: ${urlPath}`)
|
|
}
|
|
})
|
|
|
|
server.listen(PORT, '127.0.0.1', () => {
|
|
console.log(`[prototype-server] http://127.0.0.1:${PORT}/ -> ${ROOT}`)
|
|
})
|