// Before the screenshots: what the pages show, from fixtures.json, played // back in front of the pages from web/build. With SHOTS_LIVE=1, or with no // recording, a world instead: the one `just shots-up` keeps up if it is // there, else a new one for this run, and knotless-serve from this build in // front of it. The pages are written to // target/shots/views.json, and those whose HTML is as it was when their // baselines were taken, in every project, to target/shots/unchanged.json: // views.spec.mjs skips them before a browser page is opened. So too the // pages whose cards are shot, to cards.json, and the cards whose URL is // as it was, as `card:`. knotless-serve draws the cards, so they are // shot only from a world: from fixtures, cards.json is null and // cards.spec.mjs skips them. Returns the teardown. import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs' import { join } from 'node:path' import { base, create, page, root, saved, serve, until } from './world.mjs' import { card, digest } from './digest.mjs' import { front, recording } from './fixtures.mjs' import config from './playwright.config.mjs' const here = join(root, 'harness/shots') const projects = config.projects.map((p) => p.name).filter((p) => p !== 'card') const kept = async () => { if (!existsSync(saved)) return null const world = JSON.parse(readFileSync(saved, 'utf8')) // Kept up from before cards were shot, it is seeded differently. if (!world.cards) return null const up = await fetch(`${world.net.pds}/xrpc/com.atproto.server.describeServer`).then((r) => r.ok, () => false) return up ? world : null } export default async function setup() { const recorded = process.env.SHOTS_LIVE ? null : recording() let world = recorded ? { views: recorded.views } : await kept() let teardown if (recorded) { const stand = await front({ recorded: recorded.answers }) teardown = async () => stand.kill() } else if (world) { const server = await serve(world) teardown = async () => server.kill() // The index is on disk already; this waits only for the mirrors. await until('the pages', async () => (await page(world.views.pull)).includes('Merged as')) } else { world = await create() teardown = async () => world.stop() } mkdirSync(join(root, 'target/shots'), { recursive: true }) writeFileSync(join(root, 'target/shots/views.json'), JSON.stringify(world.views, null, 2)) const cards = recorded ? null : world.cards writeFileSync(join(root, 'target/shots/cards.json'), JSON.stringify(cards, null, 2)) const unchanged = [] if (!process.env.SHOTS_ALL) { const at = (name, project, ext) => join(here, 'baseline', process.platform, `${name}-${project}.${ext}`) const same = (name, project, sum) => existsSync(at(name, project, 'png')) && existsSync(at(name, project, 'sha256')) && readFileSync(at(name, project, 'sha256'), 'utf8') === sum for (const [name, path] of Object.entries(world.views)) { const sum = digest(await (await fetch(base + path)).text()) if (projects.every((p) => same(name, p, sum))) unchanged.push(name) } for (const [name, path] of Object.entries(cards ?? {})) { const sum = digest(card(await (await fetch(base + path)).text())) if (same(name, 'card', sum)) unchanged.push(`card:${name}`) } } writeFileSync(join(root, 'target/shots/unchanged.json'), JSON.stringify(unchanged)) return teardown }