import { test, expect } from "@playwright/test"; import { PROJECT_URL } from "../../common/lib/project"; import { installRoutes } from "./helpers"; // The export app is built per-site; its SSR branding (document title, header, // home tagline, footer social links) comes from currentSite() reading the // SITE_ID's site.json. The playwright config points the dev server at the // `testsite` fixture (see export/e2e/fixtures/sites/testsite/site.json). test("document title and header come from the site config", async ({ page }) => { await installRoutes(page); await page.goto("/"); await expect(page).toHaveTitle(/Fixture Site Title/); await expect( page.getByRole("link", { name: "Fixture Header" }), ).toBeVisible(); }); test("home tagline comes from the site config", async ({ page }) => { await installRoutes(page); await page.goto("/"); await expect(page.getByText("Fixture tagline here")).toBeVisible(); }); test("footer renders the site's social links", async ({ page }) => { await installRoutes(page); await page.goto("/"); await expect( page.locator("footer").getByRole("link", { name: "GitHub" }), ).toHaveAttribute("href", "https://github.com/example"); }); test("footer credits the project, ungated and in the same tab", async ({ page, }) => { await installRoutes(page); await page.goto("/"); const footer = page.locator("footer"); const builtWith = footer.getByRole("link", { name: "Archilyzer" }); // Every archive carries this, on every instance mode, whether or not the // operator configured a hub. That sameness is the point of the back-link. await expect(builtWith).toBeVisible(); await expect(builtWith).toHaveAttribute("href", PROJECT_URL); await expect(builtWith).not.toHaveAttribute("target", "_blank"); await expect(footer).toContainText("Built with Archilyzer"); }); test("a site page has no link to ko-fi.com", async ({ page }) => { await installRoutes(page); await page.goto("/"); await expect(page.locator("footer")).toBeVisible(); await expect(page.locator('a[href*="ko-fi.com"]')).toHaveCount(0); }); test("the Downloads eyebrow appears only when something is behind it", async ({ page, }) => { await installRoutes(page); await page.goto("/"); const footer = page.locator("footer"); // The label used to be unconditional, because a `code.tar.gz` link always // followed it. With that link gone, the invariant is that the eyebrow and // the links it captions appear together or not at all. // // Asserted as an invariant rather than a fixed expectation on purpose: // hasArchives() reads the real export/public on disk, so whether this build // has archives depends on what was last composed. A test pinned to one // answer would pass or fail on the machine's state, not on the code. const archives = footer.getByRole("link", { name: "Transcripts & chat" }); const offline = footer.getByRole("link", { name: "Offline" }); const behind = (await archives.count()) + (await offline.count()); const eyebrow = footer.getByText("Downloads", { exact: true }); if (behind > 0) { await expect(eyebrow).toBeVisible(); } else { await expect(eyebrow).toHaveCount(0); } }); test("per-site accent is baked onto as data-accent", async ({ page, }) => { await installRoutes(page); await page.emulateMedia({ colorScheme: "light" }); await page.goto("/"); // site.json sets the custom hex #cc3366: the layout renders // data-accent="custom" plus the hex fitted to each base as inline // --accent-custom-* vars at prerender, so it is right on first paint. // The live attribute below is also written by ThemeProvider, so it cannot // tell the server's markup apart; the raw HTML can. const served = await (await page.request.get("/")).text(); expect(served).toMatch(/]*\sdata-accent="custom"/); expect(served).toMatch(/]*--accent-custom-light:\s*#cc3366/); const html = page.locator("html"); await expect(html).toHaveAttribute("data-accent", "custom"); await expect(html).toHaveAttribute("style", /--accent-custom-light:\s*#cc3366/); // #cc3366 already reads at 4.5:1 on the light ground, so it is kept as is. await expect .poll(() => page.evaluate(() => getComputedStyle(document.documentElement) .getPropertyValue("--brand") .trim(), ), ) .toBe("#cc3366"); });