Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 99bbfed4154710b645ccfe3f29a142e30886d8c9
parent 9b4b7f8ceacd6864e27d5409e8be9bfdb075a5d0
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Mon, 28 Sep 2026 02:52:54 -0400

homepage(e2e): a synthetic summary, so a fresh clone runs every spec; the cards' pale custom hex and the sixth slot checked in the browser

The e2e dev server now always reads e2e/.e2e-summary.json (gitignored, not
linked), written by playwright.config.ts from e2e/fixture-summary.ts: the real
buildHomepageSummary over made-up recordings — six public sites (Brass, a pale
custom hex, three with none, Vermilion), a 12,000-transcript week in May for
the All-time axis, every site busy every day of the last 200. Nothing is read
from public/ or from the primary. fixture-accents.ts is replaced by it.

- stats.spec: no skip; the site-line count is the fixture's six.
- marketing.spec: reads the fixture, the file the server reads.
- instance-colours.spec: the pale hex fitted per base (light #846974, sepia
  #7c636e, dark as published); Vermilion's card in its rust layer's hue; the
  six legend swatches are the six validated slots.

Fresh worktree, no homepage/public data: 30 passed, 0 skipped (release 10:
21 passed, 8 skipped).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Diffstat:
M.gitignore | 4++--
Dhomepage/e2e/fixture-accents.ts | 43-------------------------------------------
Ahomepage/e2e/fixture-summary.ts | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mhomepage/e2e/instance-colours.spec.ts | 102+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Mhomepage/e2e/marketing.spec.ts | 18+++++++-----------
Mhomepage/e2e/stats.spec.ts | 32+++++++++++---------------------
Mhomepage/playwright.config.ts | 21+++++++++------------
7 files changed, 225 insertions(+), 135 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -126,8 +126,8 @@ yarn-error.log* /homepage/test-results/ /homepage/playwright-report/ /homepage/out/ -# the e2e's copy of the summary with fixture accents (homepage/e2e/fixture-accents.ts) -/homepage/e2e/.e2e-summary.json +# the e2e's synthetic summary, and a killed run's temp copy (homepage/e2e/fixture-summary.ts) +/homepage/e2e/.e2e-summary.json* # site config /settings.json diff --git a/homepage/e2e/fixture-accents.ts b/homepage/e2e/fixture-accents.ts @@ -1,43 +0,0 @@ -import fs from "node:fs"; -import { ACCENTS } from "../../common/lib/brand"; - -// The instance cards' colours (release 10) need sites with known accents, and -// the summary on disk is the operator's: its sites may set none at all. So the -// e2e dev server reads a COPY of it (HOMEPAGE_SUMMARY_FILE, app/lib/summary.ts) -// with these accents laid over its first sites — a named accent, a custom hex, -// then no accent — and every other number untouched. playwright.config.ts -// writes it before the server starts; the specs import the same constants. - -// In the summary's order: site 0, site 1; every later site has none. -export const FIXTURE_ACCENTS = [ - { accent: ACCENTS.brass.onDark, accentId: "brass" as const }, - { accent: "#cc3366" }, -] as const; - -export const FIXTURE_SUMMARY_NAME = ".e2e-summary.json"; - -// Write the fixture copy of `source` to `dest`. False (and any stale copy -// removed) when there is no readable summary with sites: a source-only -// checkout, whose data specs skip. The write is a rename, because Playwright -// evaluates its config again in every worker it starts, while the dev server -// may be reading the file. -export function writeFixtureSummary(source: string, dest: string): boolean { - let summary: { sites?: Array<Record<string, unknown>> } | null = null; - try { - summary = JSON.parse(fs.readFileSync(source, "utf8")); - } catch { - summary = null; - } - if (!Array.isArray(summary?.sites) || summary.sites.length === 0) { - fs.rmSync(dest, { force: true }); - return false; - } - summary.sites = summary.sites.map((site, i) => { - const { accent: _a, accentId: _id, ...rest } = site; - return { ...rest, ...(FIXTURE_ACCENTS[i] ?? {}) }; - }); - const tmp = `${dest}.${process.pid}.tmp`; - fs.writeFileSync(tmp, JSON.stringify(summary)); - fs.renameSync(tmp, dest); - return true; -} diff --git a/homepage/e2e/fixture-summary.ts b/homepage/e2e/fixture-summary.ts @@ -0,0 +1,140 @@ +import fs from "node:fs"; +import { buildHomepageSummary } from "../../common/lib/homepageSummary"; +import type { Site } from "../../common/lib/site"; +import type { VideoStat } from "../../common/lib/stats"; + +// THE HOMEPAGE E2E READS A SYNTHETIC SUMMARY, never the operator's. +// +// The pages' numbers come from homepage-summary.json, which compose writes into +// public/ from a real corpus — gitignored, so a fresh clone or worktree has +// none, and the data specs used to skip there (release 10: 8 of 29). So the +// e2e dev server reads THIS instead (HOMEPAGE_SUMMARY_FILE, app/lib/summary.ts, +// honoured only outside a production build): the real builder +// (buildHomepageSummary) over made-up recordings, deterministic, written by +// playwright.config.ts to e2e/.e2e-summary.json (gitignored, not linked) before +// the server starts. Nothing is read from public/ or from another checkout, and +// every spec that reads numbers reads this file. +// +// What the data is shaped to exercise: +// • six public sites (the chart's six validated slots), in the order the +// builder sorts them (transcripts, most first); +// • their accents: a named accent (Brass), a PALE custom hex (fitted per +// base on the cards, 1.43:1 on the light ground as published), three with +// none, and Vermilion (the sixth slot's family); +// • the first site dwarfs the rest, with one week of 12,000 transcripts in +// May (the "megaspike", outside the six-week window), so /stats' All-time +// symlog axis reaches 10K while the Recent weeks stay linear and small; +// • every site transcribes every day of the last 200, so every line is full +// and every series has a non-zero start for Indexed; +// • uploads spread over 2019–2026, for the growth chart's months. + +export const FIXTURE_SUMMARY_NAME = ".e2e-summary.json"; + +// "Now", for the builder: the build month (September 2026) is partial and is +// not charted; the week and day buckets end here. +export const FIXTURE_NOW = new Date("2026-09-15T12:00:00Z"); + +// A pale custom hex: published as is it would be a ghost on light and sepia. +export const FIXTURE_PALE_HEX = "#f4c2d7"; + +// The six sites, in summary order. `accent` is the site.json setting (an +// accent id or a custom hex), as compose reads it; `daily` how many +// recordings each channel transcribes a day. +export const FIXTURE_SITES = [ + { siteId: "fixture-one", siteTitle: "Fixture One", accent: "brass", channels: 4, daily: 15 }, + { siteId: "fixture-two", siteTitle: "Fixture Two", accent: FIXTURE_PALE_HEX, channels: 3, daily: 9 }, + { siteId: "fixture-three", siteTitle: "Fixture Three", accent: undefined, channels: 3, daily: 7 }, + { siteId: "fixture-four", siteTitle: "Fixture Four", accent: undefined, channels: 2, daily: 8 }, + { siteId: "fixture-five", siteTitle: "Fixture Five", accent: undefined, channels: 2, daily: 5 }, + { siteId: "fixture-six", siteTitle: "Fixture Six", accent: "vermilion", channels: 2, daily: 3 }, +] as const; + +const DAY = 86_400_000; +const SPIKE = { site: 0, start: Date.UTC(2026, 4, 11), days: 7, count: 12_000 }; +const HISTORY_DAYS = 200; + +const ymd = (t: number) => new Date(t).toISOString().slice(0, 10).replace(/-/g, ""); + +// A recording's upload date: spread over the 92 months before the build month, +// deterministically (a multiplicative walk over the index, so every site has +// uploads in most months). +function uploadDate(n: number): string { + const back = 1 + ((n * 37) % 92); // months before September 2026 + const d = new Date(Date.UTC(2026, 8 - back, 1 + ((n * 11) % 28))); + return ymd(d.getTime()); +} + +export function buildFixtureSummary() { + const stats: VideoStat[] = []; + const channelSites: Record<string, string[]> = {}; + const sites: Site[] = []; + let n = 0; + const record = (siteIdx: number, slug: string, name: string, day: number) => { + n += 1; + stats.push({ + slug: `${slug}/v${n}`, + id: `v${n}`, + channelSlug: slug, + channel: name, + title: `Fixture recording ${n}`, + platform: "youtube", + uploadDate: uploadDate(n), + downloadedDate: ymd(day), + transcribedDate: ymd(day), + timestamp: null, + duration: 1200 + ((n * 97) % 5400), + viewCount: null, + likeCount: null, + commentCount: null, + channelFollowerCount: null, + categories: [], + tags: [], + language: "en", + isLivestream: false, + mediaType: "video", + // One in forty re-checked and found gone at the source. + status: n % 40 === siteIdx ? "deleted" : "available", + hasTranscript: true, + cueCount: null, + coverage: null, + }); + }; + const today = Date.UTC(2026, 8, 15); + FIXTURE_SITES.forEach((s, i) => { + const slugs = Array.from({ length: s.channels }, (_, c) => `${s.siteId}-ch${c + 1}`); + for (const slug of slugs) channelSites[slug] = [s.siteId]; + sites.push({ + siteId: s.siteId, + siteTitle: s.siteTitle, + siteDescription: `${s.siteTitle}, an e2e fixture archive.`, + siteUrl: `https://${s.siteId}.example`, + channels: slugs.map((slug) => ({ slug })), + ...(s.accent ? { accent: s.accent } : {}), + } as unknown as Site); + slugs.forEach((slug, c) => { + const name = `${s.siteTitle} Channel ${c + 1}`; + for (let d = 0; d < HISTORY_DAYS; d++) { + const day = today - d * DAY; + // A little day-to-day rhythm, never zero. + const count = Math.max(1, s.daily - c * 2 + ((d + c) % 3)); + for (let k = 0; k < count; k++) record(i, slug, name, day); + } + }); + }); + // The megaspike: one week of bulk transcription on the first site's first + // channel. + const spikeSite = FIXTURE_SITES[SPIKE.site]; + for (let k = 0; k < SPIKE.count; k++) { + record(SPIKE.site, `${spikeSite.siteId}-ch1`, `${spikeSite.siteTitle} Channel 1`, SPIKE.start + (k % SPIKE.days) * DAY); + } + return buildHomepageSummary(stats, channelSites, sites, FIXTURE_NOW); +} + +// Write the fixture summary to `dest`. The write is a rename, because +// Playwright evaluates its config again in every worker it starts, while the +// dev server may be reading the file. +export function writeFixtureSummary(dest: string): void { + const tmp = `${dest}.${process.pid}.tmp`; + fs.writeFileSync(tmp, JSON.stringify(buildFixtureSummary())); + fs.renameSync(tmp, dest); +} diff --git a/homepage/e2e/instance-colours.spec.ts b/homepage/e2e/instance-colours.spec.ts @@ -1,34 +1,31 @@ import fs from "node:fs"; import path from "node:path"; import { test, expect, type Page } from "@playwright/test"; +import { resolveAccent } from "../../common/lib/accent"; import { ACCENTS } from "../../common/lib/brand"; import { ACCENT_CHART_SLOT, siteChartColors } from "../../common/lib/siteColor"; -import { FIXTURE_ACCENTS, FIXTURE_SUMMARY_NAME } from "./fixture-accents"; +import { FIXTURE_PALE_HEX, FIXTURE_SITES, FIXTURE_SUMMARY_NAME } from "./fixture-summary"; // The Official Instances cards wear their site's accent (release 10): a named // accent at its value on the base in force (`var(--swatch-<id>)`), a custom hex -// as published, and a site with no accent its chart colour. The growth chart -// draws each site in the validated chart slot of its accent's HUE FAMILY -// (siteChartColors: Brass → amber, --chart-4), so a Brass card and its legend -// swatch are one hue; a card with no accent equals its legend swatch. The dev -// server reads the summary on disk with fixture accents laid over its first -// sites (fixture-accents.ts): site 0 Brass, site 1 the custom #cc3366, the rest -// none — so with five sites, site 3's own slot (chart-4) is Brass's and it -// takes the lowest free one. +// FITTED to each base as the site's own pages fit it (release 11), and a site +// with no accent its chart colour. The growth chart draws each site in the +// validated chart slot of its accent's HUE FAMILY (siteChartColors: Brass → +// amber, --chart-4; Vermilion → rust, --chart-6), so a family card and its +// legend swatch are one hue; a card with no accent equals its legend swatch. +// The dev server reads the synthetic summary (fixture-summary.ts): site 0 +// Brass, site 1 a pale custom hex, 2–4 none, 5 Vermilion — so site 3's own +// slot (chart-4) is Brass's and it takes the lowest free one, and six sites +// wear all six validated slots. const BASE_KEY = "ytdlp-tb:base"; -// The fixture copy the dev server reads: its sites, and whether it has the -// monthly series the growth chart (and its legend) is drawn from. -function fixture(): { sites: Array<{ accentId?: string }>; chart: boolean } | null { - try { - const file = path.resolve(test.info().project.testDir, FIXTURE_SUMMARY_NAME); - const s = JSON.parse(fs.readFileSync(file, "utf8")); - if (!Array.isArray(s?.sites) || s.sites.length === 0) return null; - return { sites: s.sites, chart: Array.isArray(s.monthly) && s.monthly.length > 0 }; - } catch { - return null; - } +// The summary the dev server reads: its sites, in its order. +function fixtureSites(): Array<{ siteId: string; accent?: string; accentId?: string }> { + const file = path.resolve(test.info().project.testDir, FIXTURE_SUMMARY_NAME); + const s = JSON.parse(fs.readFileSync(file, "utf8")); + expect(Array.isArray(s.monthly) && s.monthly.length > 0, "the fixture has monthly").toBe(true); + return s.sites; } // The colour a CSS colour resolves to on this page, as the browser reports it. @@ -79,51 +76,64 @@ async function useBase(page: Page, base: "dark" | "light" | "sepia") { test("each instance card wears its site's accent on every base, in its chart layer's hue", async ({ page, }) => { - const f = fixture(); - test.skip(!f, "no summary on disk — the page has no instance cards"); - const { sites, chart: hasChart } = f!; + const sites = fixtureSites(); const n = sites.length; - expect(FIXTURE_ACCENTS[0].accentId).toBe("brass"); - // The chart colours the page must wear, as the helper assigns them. + expect(sites.map((x) => x.siteId)).toEqual(FIXTURE_SITES.map((x) => x.siteId)); + expect(sites[0].accentId).toBe("brass"); + expect(sites[1]).toMatchObject({ accent: FIXTURE_PALE_HEX }); + expect(sites[1].accentId).toBeUndefined(); + expect(sites[5].accentId).toBe("vermilion"); + // The chart colours the page must wear, as the helper assigns them: the six + // validated slots, each once. const chart = siteChartColors(sites); expect(chart[0]).toBe(`var(--chart-${ACCENT_CHART_SLOT.brass! + 1})`); - expect(new Set(chart).size).toBe(n); + expect(chart[5]).toBe("var(--chart-6)"); + expect([...chart].sort()).toEqual([1, 2, 3, 4, 5, 6].map((k) => `var(--chart-${k})`)); await page.goto("/"); - const onBase = { - dark: ACCENTS.brass.onDark, - light: ACCENTS.brass.onLight, - sepia: ACCENTS.brass.onSepia, - } as const; + const on = { dark: "onDark", light: "onLight", sepia: "onSepia" } as const; + const pale = resolveAccent(FIXTURE_PALE_HEX); for (const base of ["dark", "light", "sepia"] as const) { await useBase(page, base); const { stripes, legend } = await colours(page); expect(stripes).toHaveLength(n); // The summary's monthly series draws the chart, legend and all: the - // legend (and so each layer) wears each site's chart colour. A summary - // with none draws no chart at all. - await expect( - page.getByRole("img", { name: /transcripts by the month/i }), - ).toHaveCount(hasChart ? 1 : 0); - expect(legend, "legend swatches").toHaveLength(hasChart ? n : 0); - for (let i = 0; i < legend.length; i++) { + // legend (and so each layer) wears each site's chart colour, six apart. + await expect(page.getByRole("img", { name: /transcripts by the month/i })).toHaveCount(1); + expect(legend, "legend swatches").toHaveLength(n); + for (let i = 0; i < n; i++) { expect(legend[i], `legend ${i}`).toBe(await resolve(page, chart[i])); } + expect(new Set(legend).size, "six layers, six colours").toBe(n); // Site 0: the NAMED accent at this base's value — not the published // on-dark hex on every base — and its legend swatch is the amber slot, // the same hue family (a few degrees apart), at a different value. - expect(stripes[0]).toBe(await resolve(page, onBase[base])); + expect(stripes[0]).toBe(await resolve(page, ACCENTS.brass[on[base]])); expect(await resolve(page, chart[0])).toBe(await resolve(page, "var(--chart-4)")); - expect(hueGap(stripes[0], await resolve(page, chart[0])), "brass vs its layer").toBeLessThan(25); - // Site 1 (when there is one): the custom hex as published. - if (n > 1) expect(stripes[1]).toBe(await resolve(page, FIXTURE_ACCENTS[1].accent)); - // Every other site has no accent: its chart colour, the same as its - // legend swatch. - for (let i = 2; i < n; i++) { + expect(hueGap(stripes[0], legend[0]), "brass vs its layer").toBeLessThan(25); + + // Site 1: the pale custom hex, FITTED to this base — on light and sepia + // darkened to 4.5:1 (as published it is ~1.4:1 there), on dark as is. + expect(stripes[1], "the pale hex, fitted").toBe(await resolve(page, pale[base])); + if (base !== "dark") { + expect(stripes[1], "not the pale hex as published").not.toBe( + await resolve(page, FIXTURE_PALE_HEX), + ); + } + + // Sites 2–4 have no accent: their chart colour, the same as their legend + // swatch. + for (let i = 2; i <= 4; i++) { expect(stripes[i], `site ${i}`).toBe(await resolve(page, chart[i])); - if (hasChart) expect(stripes[i], `site ${i} vs legend`).toBe(legend[i]); + expect(stripes[i], `site ${i} vs legend`).toBe(legend[i]); } + + // Site 5: Vermilion, the sixth slot's family — the rust layer, not the + // golden angle's hue 328 beside the magenta slot. + expect(stripes[5]).toBe(await resolve(page, ACCENTS.vermilion[on[base]])); + expect(legend[5]).toBe(await resolve(page, "var(--chart-6)")); + expect(hueGap(stripes[5], legend[5]), "vermilion vs its layer").toBeLessThan(25); } }); diff --git a/homepage/e2e/marketing.spec.ts b/homepage/e2e/marketing.spec.ts @@ -1,12 +1,13 @@ import fs from "node:fs"; import path from "node:path"; import { test, expect } from "@playwright/test"; +import { FIXTURE_SUMMARY_NAME } from "./fixture-summary"; // The home page's job is to say what Archilyzer is and offer the download. // Nothing asserted here names a count, a site or a headline number. The numeric // parts are gated on corpus data, so the specs that touch them read the summary -// on disk and branch on it — a source-only build is the build this page has to -// work on. +// the server reads — the e2e's synthetic one (fixture-summary.ts) — and branch +// on it, as a source-only build (no summary) is legal too. test.beforeEach(async ({ page }) => { await page.goto("/"); @@ -51,9 +52,9 @@ test("every nav destination resolves", async ({ page }) => { }); // The numeric parts of the page are gated on the build-time summary, so each -// assertion reads the summary on disk and expects exactly what it implies: the -// part present with data, absent without. A source-only build (no summary) is -// a legal state and must render none of them rather than zeroes. +// assertion reads the summary the server reads and expects exactly what it +// implies: the part present with data, absent without. A source-only build (no +// summary) is a legal state and must render none of them rather than zeroes. function readSummary(): { monthly?: unknown[]; sites?: unknown[]; @@ -61,12 +62,7 @@ function readSummary(): { generatedAt?: string; } | null { try { - const file = path.resolve( - test.info().project.testDir, - "..", - "public", - "homepage-summary.json", - ); + const file = path.resolve(test.info().project.testDir, FIXTURE_SUMMARY_NAME); return JSON.parse(fs.readFileSync(file, "utf8")); } catch { return null; diff --git a/homepage/e2e/stats.spec.ts b/homepage/e2e/stats.spec.ts @@ -1,16 +1,15 @@ import { test, expect, type Page } from "@playwright/test"; +import { FIXTURE_SITES } from "./fixture-summary"; // The /stats/ dashboard's cross-site chart: the three site-based presets, plus // the power-user Customize toolbox (chart types, scale, exclude-outlier, // indexed) and its guard rails. Asserts against the live recharts SVG. // -// This file HARD-REQUIRES corpus data — it asserts an exact site-line count, so -// there is nothing meaningful to check on a `build:nodata` tree. The whole file -// skips when /stats/ served its no-data panel instead of the dashboard, which -// keeps a source-only checkout green without weakening the assertions for a -// configured one. (The site-line count follows the corpus: it is read from the -// rendered legend rather than pinned at 3, because the operator's public-site -// list grows.) +// The e2e server reads the synthetic summary (fixture-summary.ts), so the +// dashboard is always there — in a fresh clone too, where these used to skip +// on "no corpus data" — and its numbers are known: six sites, the first with +// a 12,000-transcript week in May (the All-time axis reaches 10K) and every +// site's recent weeks in the hundreds. const pressedPreset = (page: Page) => page.locator('[aria-label="Chart preset"] button[aria-pressed="true"]'); @@ -30,24 +29,15 @@ const fullLines = (page: Page) => ).length, ); -// How many public sites this build's summary actually has. The suite pins its -// counts to THIS rather than a literal, because the operator's public-site list -// grows (it went 3 → 4 when Bonnellyzer shipped) and a literal would turn a new -// archive into a red suite. -let siteCount = 0; +// The fixture's public sites, one line each. +const siteCount = FIXTURE_SITES.length; test.beforeEach(async ({ page }) => { await page.goto("/stats/"); - // A build with no corpus data serves the honest no-data panel here instead of - // the dashboard. Nothing below is meaningful without numbers. - const noData = await page - .getByText("This build shipped without dashboard data") - .isVisible() - .catch(() => false); - test.skip(noData, "no corpus data in this build — /stats/ has no dashboard"); + // Never the no-data panel: the e2e always serves the fixture summary. + await expect(page.getByText("This build shipped without dashboard data")).toHaveCount(0); await page.waitForSelector(".recharts-surface"); - siteCount = await page.locator(".recharts-line-curve").count(); - expect(siteCount, "summary should carry at least two public sites").toBeGreaterThanOrEqual(2); + await expect(page.locator(".recharts-line-curve")).toHaveCount(siteCount); }); test("Recent is the default: every site line is full, on a linear axis", async ({ diff --git a/homepage/playwright.config.ts b/homepage/playwright.config.ts @@ -3,11 +3,10 @@ import { defineConfig, devices } from "@playwright/test"; import { FIXTURE_SUMMARY_NAME, writeFixtureSummary, -} from "./e2e/fixture-accents"; +} from "./e2e/fixture-summary"; // Homepage e2e. Runs against `next dev` (default mode) so it reflects uncommitted -// source — /stats/ reads its build-time summary from public/ on disk, which is -// present in dev. Kill any stale dev server on the port between runs. +// source. Kill any stale dev server on the port between runs. // // The port comes from HOMEPAGE_E2E_PORT, the block scripts/worktree.mjs already // allocates per worktree (3040 on main). It used to read `PORT`, which is the @@ -16,15 +15,13 @@ import { const PORT = Number(process.env.HOMEPAGE_E2E_PORT ?? 3040); const baseURL = `http://localhost:${PORT}`; -// The dev server reads a copy of the summary on disk with fixture accents on -// its first sites (e2e/fixture-accents.ts), so the instance cards' colours are -// exact whatever the operator's sites set. Gitignored; rewritten every run. No -// summary on disk, no copy: the server reads public/ as before and finds none. +// The dev server reads a SYNTHETIC summary (e2e/fixture-summary.ts), never +// public/homepage-summary.json: a fresh clone has none, and the operator's +// numbers move. Built by the real summary builder, deterministic, written +// here to e2e/.e2e-summary.json (gitignored) before the server starts; the +// specs that read numbers read the same file. const FIXTURE_SUMMARY = path.resolve(process.cwd(), "e2e", FIXTURE_SUMMARY_NAME); -const hasFixtureSummary = writeFixtureSummary( - path.resolve(process.cwd(), "public", "homepage-summary.json"), - FIXTURE_SUMMARY, -); +writeFixtureSummary(FIXTURE_SUMMARY); export default defineConfig({ testDir: "./e2e", @@ -39,7 +36,7 @@ export default defineConfig({ url: baseURL, timeout: 120_000, reuseExistingServer: !process.env.CI, - env: hasFixtureSummary ? { HOMEPAGE_SUMMARY_FILE: FIXTURE_SUMMARY } : {}, + env: { HOMEPAGE_SUMMARY_FILE: FIXTURE_SUMMARY }, }, use: { baseURL,