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:
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,