import { expect, test } from "@playwright/test"; test.describe("public site", () => { test("home shows live statistics, latest papers and constellation", async ({ page }) => { await page.goto("/"); await expect(page).toHaveTitle(/Research Working Papers/); await expect(page.getByRole("heading", { level: 1 })).toHaveText("Research Working Papers"); const stats = await page.request.get("/api/v1/stats").then((r) => r.json()); await expect(page.locator("dl dd").first()).toHaveText(String(stats.papers)); await expect(page.getByRole("heading", { name: "Latest working papers" })).toBeVisible(); await expect(page.locator("svg[role=img]").first()).toBeVisible(); }); test("library filters, sorts and switches display modes", async ({ page }) => { await page.goto("/papers?domain=real-estate-economics&sort=number"); await expect(page.getByText(/^\d+ results?$/)).toBeVisible(); const n = await page.locator("article").count(); expect(n).toBeGreaterThan(3); await page.goto("/papers?view=table"); await expect(page.locator("table.data")).toBeVisible(); await page.goto("/papers?view=cards&q=hedonic"); expect(await page.locator("article").count()).toBeGreaterThan(0); await page.goto("/papers?q=zzzzqqqq-nothing"); await expect(page.getByText("No paper matches these filters.")).toBeVisible(); }); test("paper page: metadata, citation tools, versions, related, download", async ({ page }) => { await page.goto("/papers/wp38"); await expect(page.getByRole("heading", { level: 1 })).toContainText("Economic Geography"); await expect(page.locator('meta[name="citation_title"]')).toHaveAttribute("content", /Economic Geography/); await expect(page.locator('meta[name="citation_pdf_url"]')).toHaveAttribute("content", /\.pdf$/); await expect(page.locator('script[type="application/ld+json"]')).toHaveCount(1); await page.getByRole("button", { name: /Cite/ }).click(); await expect(page.getByText("Boucher, S.-P. (2026)")).toBeVisible(); await expect(page.getByRole("heading", { name: "Version history" })).toBeVisible(); await expect(page.getByRole("heading", { name: "Related research" })).toBeVisible(); await expect(page.getByText(/Why related:/).first()).toBeVisible(); const href = await page.getByRole("link", { name: /Download PDF/ }).first().getAttribute("href"); const r = await page.request.head(href!); expect(r.status()).toBe(200); expect(r.headers()["content-type"]).toContain("application/pdf"); }); test("identifier aliases redirect to the canonical slug; unknown and reserved numbers 404", async ({ page }) => { const r1 = await page.request.get("/papers/WP38", { maxRedirects: 0 }); expect(r1.status()).toBe(308); expect(r1.headers().location).toMatch(/\/papers\/wp38$/); expect((await page.request.get("/papers/wp999")).status()).toBe(404); expect((await page.request.get("/papers/wp39")).status()).toBe(404); const v = await page.request.get("/papers/wp38/v1"); expect(v.status()).toBe(200); }); test("french locale renders translated chrome on the same canonical path", async ({ page }) => { await page.goto("/fr/papers/wp38"); await expect(page.locator("html")).toHaveAttribute("lang", "fr"); await expect(page.getByRole("heading", { name: "Résumé" })).toBeVisible(); await expect(page.getByRole("link", { name: /Télécharger le PDF/ }).first()).toBeVisible(); await page.goto("/fr"); await expect(page.getByRole("heading", { level: 1 })).toHaveText("Cahiers de recherche"); }); test("search returns highlighted passages", async ({ page }) => { await page.goto("/search?q=hedonic+quebec&mode=lexical"); await expect(page.locator("ol li").first()).toBeVisible(); await expect(page.locator("mark").first()).toBeVisible(); }); test("discovery pages render", async ({ page }) => { for (const p of ["/topics", "/topics/real-estate-economics", "/methods", "/datasets", "/authors", "/authors/simon-pierre-boucher", "/series", "/timeline", "/research-map", "/about", "/citation", "/api/docs"]) { const r = await page.goto(p); expect(r?.status(), p).toBe(200); await expect(page.getByRole("heading", { level: 1 })).toBeVisible(); } }); test("machine endpoints", async ({ request }) => { const bib = await request.get("/api/v1/papers/wp7/citation.bib"); expect(bib.status()).toBe(200); expect(await bib.text()).toContain("@techreport{"); const oai = await request.get("/api/oai?verb=Identify"); expect(await oai.text()).toContain(""); const feed = await request.get("/feed.xml"); expect(feed.headers()["content-type"]).toContain("rss"); const sm = await request.get("/sitemap.xml"); expect(await sm.text()).toContain("/papers/wp38"); const g = await request.get("/api/v1/graph").then((r) => r.json()); expect(g.nodes.length).toBeGreaterThan(30); expect(g.edges.some((e: { inferred: boolean }) => e.inferred)).toBe(true); const h = await request.get("/api/health").then((r) => r.json()); expect(h.ok).toBe(true); }); test("mobile navigation works", async ({ page, isMobile }) => { test.skip(!isMobile, "mobile only"); await page.goto("/"); await page.getByRole("button", { name: "Menu" }).click(); await page.locator("#mobile-nav").getByRole("link", { name: "Papers", exact: true }).click(); await expect(page).toHaveURL(/\/papers$/); }); });