Files
AgentCompany/landing/tests/e2e/landing.spec.ts
T
ZacharyZhang-NY 4f0d03ad72 Add Playwright e2e suite for the landing page
- playwright.config.ts: desktop (1440) + mobile (Pixel 5) projects, static
  webServer over the exported out/; a PW_CHROME env override for the browser
- tests/e2e: render + all core sections, tracked download hrefs with no R2
  exposure, the live counter (mocked function), theme toggle, image loading,
  zero console errors, and responsive nav (desktop links / mobile menu reveal)
- navbar: hide the closed mobile menu with `invisible` so its links stay out of
  the tab order and the accessibility tree, not merely clipped
- add the test:e2e script and ignore test-results/ + playwright-report/
2026-07-07 22:30:33 -04:00

100 lines
3.6 KiB
TypeScript

import { test, expect, type Page } from "@playwright/test";
// The download counter endpoint is a Cloudflare Pages Function, absent from the
// static export served in tests; mock it so the client fetch resolves.
test.beforeEach(async ({ page }) => {
await page.route("**/api/download-stats", (route) =>
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ downloads: 12345 }),
}),
);
});
/** Step through the page so reveals fire and lazy images load. */
async function scrollThrough(page: Page): Promise<void> {
await page.evaluate(async () => {
const dwell = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
const step = Math.round(window.innerHeight * 0.7);
for (let y = 0; y < document.body.scrollHeight; y += step) {
window.scrollTo(0, y);
await dwell(150);
}
window.scrollTo(0, 0);
await dwell(200);
});
}
test("renders the hero and every core section", async ({ page }) => {
await page.goto("/");
await expect(page.getByRole("heading", { level: 1 })).toContainText("Autonomous AI Company");
for (const id of ["features", "how-it-works", "faq", "download"]) {
await expect(page.locator(`#${id}`)).toHaveCount(1);
}
await scrollThrough(page);
await expect(page.getByRole("heading", { name: "Launch your company now." })).toBeVisible();
});
test("download links use the tracked endpoint and never expose R2 URLs", async ({ page }) => {
await page.goto("/");
await scrollThrough(page);
const tracked = page.locator('#download a[href^="/api/download?asset="]');
expect(await tracked.count()).toBeGreaterThanOrEqual(9);
// Real release URLs must stay behind the server-side redirect.
expect(await page.locator('a[href*="r2.dev"]').count()).toBe(0);
await expect(page.getByText(/12,345 downloads/)).toBeVisible();
});
test("theme toggle flips light and dark", async ({ page }) => {
await page.goto("/");
const toggle = page.locator('button[aria-label*="mode"]:visible').first();
const wasDark = await page.evaluate(() => document.documentElement.classList.contains("dark"));
await toggle.click();
await expect
.poll(() => page.evaluate(() => document.documentElement.classList.contains("dark")))
.toBe(!wasDark);
});
test("all images load without breaking", async ({ page }) => {
await page.goto("/");
await scrollThrough(page);
const broken = await page.evaluate(() =>
[...document.querySelectorAll("img")]
.filter((img) => !img.complete || img.naturalWidth === 0)
.map((img) => img.currentSrc || img.src),
);
expect(broken).toEqual([]);
});
test("produces no console errors", async ({ page }) => {
const errors: string[] = [];
page.on("console", (message) => {
if (message.type() === "error") errors.push(message.text());
});
page.on("pageerror", (error) => errors.push(error.message));
await page.goto("/");
await scrollThrough(page);
expect(errors).toEqual([]);
});
test("desktop navigation exposes the section links", async ({ page }) => {
test.skip(test.info().project.name !== "desktop", "desktop layout only");
await page.goto("/");
for (const href of ["#features", "#how-it-works", "#faq"]) {
await expect(page.locator(`nav a[href="${href}"]:visible`)).toBeVisible();
}
});
test("mobile navigation reveals the menu on tap", async ({ page }) => {
test.skip(test.info().project.name !== "mobile", "mobile layout only");
await page.goto("/");
const links = page.locator('#mobile-nav a[href="#features"]');
await expect(links).toBeHidden();
await page.getByRole("button", { name: /open navigation menu/i }).click();
await expect(links).toBeVisible();
});