1import * as Playwright from "@alchemy.run/cloudflare-test-tools/e2e/Playwright";
2import { expect, test } from "@playwright/test";
3import { readFile } from "node:fs/promises";
4
5// The static-output path: a pure `output: "static"` Astro site deploys
6// ASSETS-ONLY (no worker, `BuildOutput.serverModules` undefined/empty).
7//
8// The live-only assertions live INSIDE the loop's `live` describe (not a
9// separate describe block): each describe can land on its own Playwright
10// worker, and the worker-scoped `server` fixture builds when
11// `dist/build.json` is missing — two describes owning `live` servers means
12// two workers racing the same astro build in the same dist/ on a fresh
13// checkout (astro's prerender cleanup in one build deletes chunks the
14// other's manifest injection still reads).
15for (const mode of Playwright.SERVER_METHODS) {
16 test.describe(mode, () => {
17 const it = Playwright.make(mode);
18
19 it("serves the prerendered home page", async ({ page, server }) => {
20 const response = await page.goto(server.url.toString());
21 expect(response?.status()).toBe(200);
22 await expect(page.locator("#title")).toHaveText("Astro Static Fixture");
23 await expect(page.locator("#mode")).toHaveText("static");
24 });
25
26 it("navigates the whole site through the nav (sitemap-ish)", async ({
27 page,
28 server,
29 }) => {
30 await page.goto(server.url.toString());
31 await page.locator("#nav-about").click();
32 await expect(page.locator("#title")).toHaveText("About");
33 await page.locator("#nav-blog").click();
34 await expect(page.locator("#title")).toHaveText("Blog");
35 await expect(page.locator("#post-list li")).toHaveCount(2);
36 await page.locator("#nav-first-post").click();
37 await expect(page.locator("#slug")).toHaveText("first-post");
38 await page.locator("#nav-second-post").click();
39 await expect(page.locator("#slug")).toHaveText("second-post");
40 await page.locator("#nav-home").click();
41 await expect(page.locator("#title")).toHaveText("Astro Static Fixture");
42 });
43
44 it("serves getStaticPaths-enumerated routes", async ({ server }) => {
45 const first = await server.fetch("/blog/first-post/");
46 expect(first.status).toBe(200);
47 expect(await first.text()).toContain("First post");
48
49 const second = await server.fetch("/blog/second-post/");
50 expect(second.status).toBe(200);
51 expect(await second.text()).toContain("Second post");
52 });
53
54 it("hydrates the client-side island", async ({ page, server }) => {
55 await page.goto(new URL("/counter/", server.url).toString());
56 // The prerendered HTML says "no"; the bundled client script flips it.
57 await expect(page.locator("#hydrated")).toHaveText("yes");
58 await expect(page.locator("#count")).toHaveText("0");
59 await page.locator("#increment").click();
60 await page.locator("#increment").click();
61 await expect(page.locator("#count")).toHaveText("2");
62 });
63
64 it("serves public assets", async ({ server }) => {
65 const response = await server.fetch("/robots.txt");
66 expect(response.status).toBe(200);
67 expect(await response.text()).toContain("User-agent");
68 });
69
70 it("returns the custom 404 page for unmatched routes", async ({
71 server,
72 }) => {
73 const response = await server.fetch("/definitely-not-a-route");
74 expect(response.status).toBe(404);
75 expect(await response.text()).toContain("Page not found");
76 });
77
78 // Live-only assertions: the production artifact itself.
79 if (mode === "live") {
80 it("serves build-frozen HTML (no per-request rendering)", async ({
81 server,
82 }) => {
83 // `#built-at` is stamped at BUILD time; two requests must be
84 // byte-identical. (Dev renders on demand by design, so live-only.)
85 const first = await (await server.fetch("/")).text();
86 const second = await (await server.fetch("/")).text();
87 expect(first).toBe(second);
88 expect(first).toContain('id="built-at"');
89 });
90
91 // THE POINT of this fixture: a fully-static build must be assets-only.
92 it("build output contains NO server modules (assets-only)", async ({
93 server,
94 }) => {
95 // The harness builds before serving live mode, so dist/build.json
96 // exists once the worker-scoped server fixture is up.
97 void server;
98 const raw = await readFile(
99 new URL("../dist/build.json", import.meta.url),
100 "utf8",
101 );
102 const build = JSON.parse(raw) as {
103 serverModules?: Array<{ name: string }> | undefined;
104 };
105 expect(build.serverModules ?? []).toEqual([]);
106 });
107 }
108 });
109}