|
1 | 1 | /** |
2 | | - * Admin E2E tests — authenticated, against the D1 API. |
| 2 | + * Admin ↔ D1 API E2E — runs against the seeded DEMO_MODE backend. |
3 | 3 | * |
4 | | - * Prerequisites: |
5 | | - * 1. wrangler dev --remote running on port 8787 |
6 | | - * 2. Next.js dev running on port 3000 with NEXT_PUBLIC_API_URL=http://localhost:8787 |
7 | | - * 3. Session saved: npx playwright test --project=auth-setup |
8 | | - * |
9 | | - * These tests use the saved session from e2e/fixtures/auth.json. |
| 4 | + * DEMO_MODE bypasses Cloudflare Access (/admin/me returns an admin), so the |
| 5 | + * admin SPA loads real data from the D1-backed API with no auth.json or seam. |
| 6 | + * Verifies the admin app talks to the local API (not the old Firestore path). |
10 | 7 | */ |
11 | 8 |
|
12 | 9 | import { test, expect } from "@playwright/test"; |
13 | | -import path from "path"; |
14 | | -import fs from "fs"; |
15 | 10 |
|
16 | | -const AUTH_FILE = path.join(__dirname, "../fixtures/auth.json"); |
17 | 11 | const API_URL = process.env.NEXT_PUBLIC_API_URL; |
18 | 12 |
|
19 | | -// Skip entire suite if no API or no saved session |
20 | | -const hasAuth = fs.existsSync(AUTH_FILE); |
21 | | - |
22 | 13 | test.describe("Admin — D1 API", () => { |
23 | | - test.skip( |
24 | | - !API_URL, |
25 | | - "Skipped: NEXT_PUBLIC_API_URL not set (not in API mode)" |
26 | | - ); |
27 | | - test.skip(!hasAuth, "Skipped: run auth-setup first (npx playwright test --project=auth-setup)"); |
28 | | - |
29 | | - test.use({ storageState: AUTH_FILE }); |
30 | | - |
31 | | - test("categories page loads and shows list from D1", async ({ page }) => { |
32 | | - await page.goto("/admin"); |
33 | | - await page.waitForLoadState("networkidle"); |
34 | | - |
35 | | - // Should NOT show the login gate |
36 | | - await expect( |
37 | | - page.locator("text=Accedi per gestire il ristorante") |
38 | | - ).not.toBeVisible({ timeout: 5000 }); |
| 14 | + test.skip(!API_URL, "Skipped: NEXT_PUBLIC_API_URL not set"); |
39 | 15 |
|
40 | | - // Should show the categories heading |
41 | | - await expect(page.locator("h2")).toContainText("Categorie"); |
42 | | - |
43 | | - // At least one category should be visible |
44 | | - const categories = page.locator(".space-y-2 > div, [class*='rounded-lg']"); |
45 | | - await expect(categories.first()).toBeVisible({ timeout: 10000 }); |
46 | | - }); |
| 16 | + test("admin loads the catalog from the local API", async ({ page }) => { |
| 17 | + const catalogReq = page.waitForRequest( |
| 18 | + (req) => req.url().includes("localhost:8787") && req.url().includes("/admin/catalog"), |
| 19 | + { timeout: 15000 }, |
| 20 | + ); |
47 | 21 |
|
48 | | - test("can open and cancel category edit modal", async ({ page }) => { |
49 | 22 | await page.goto("/admin"); |
50 | | - await page.waitForLoadState("networkidle"); |
51 | | - |
52 | | - // Click the edit (pencil) button on the first category |
53 | | - const editBtn = page.locator("button[title='Modifica nome']").first(); |
54 | | - await editBtn.waitFor({ timeout: 10000 }); |
55 | | - await editBtn.click(); |
56 | | - |
57 | | - // Modal should appear |
58 | | - await expect(page.locator("text=Modifica Categoria")).toBeVisible(); |
59 | 23 |
|
60 | | - // Cancel button closes it |
61 | | - await page.locator("button:has-text('Annulla')").click(); |
62 | | - await expect(page.locator("text=Modifica Categoria")).not.toBeVisible(); |
| 24 | + const req = await catalogReq; |
| 25 | + expect(req.url()).toContain("/admin/catalog"); |
63 | 26 | }); |
64 | 27 |
|
65 | | - test("category save goes to D1 API (check network request)", async ({ |
66 | | - page, |
67 | | - }) => { |
| 28 | + test("categories page loads the list from D1", async ({ page }) => { |
68 | 29 | await page.goto("/admin"); |
69 | 30 | await page.waitForLoadState("networkidle"); |
70 | 31 |
|
71 | | - // Intercept the API PUT call |
72 | | - const apiCallPromise = page.waitForRequest( |
73 | | - (req) => |
74 | | - req.url().includes("/admin/restaurants/") && |
75 | | - req.url().includes("/categories/") && |
76 | | - req.method() === "PUT", |
77 | | - { timeout: 15000 } |
78 | | - ); |
79 | | - |
80 | | - // Open edit modal |
81 | | - const editBtn = page.locator("button[title='Modifica nome']").first(); |
82 | | - await editBtn.waitFor({ timeout: 10000 }); |
83 | | - await editBtn.click(); |
84 | | - |
85 | | - // Get current name and re-save (no actual change needed) |
86 | | - const nameInput = page.locator("input[type='text']").first(); |
87 | | - const currentName = await nameInput.inputValue(); |
88 | | - await nameInput.fill(currentName); // touch it to ensure save is enabled |
| 32 | + // Not the access-denied / invalid-session gate. |
| 33 | + await expect(page.getByRole("heading", { name: "Categorie menu" })).toBeVisible({ timeout: 15000 }); |
89 | 34 |
|
90 | | - // Click save |
91 | | - await page.locator("button:has-text('Salva')").click(); |
92 | | - |
93 | | - // Verify the PUT hit our backend, not Firestore |
94 | | - const req = await apiCallPromise; |
95 | | - expect(req.url()).toContain("localhost:8787"); |
| 35 | + // Seeded categories render with edit controls. |
| 36 | + await expect(page.locator("button[title='Modifica']").first()).toBeVisible({ timeout: 10000 }); |
96 | 37 | }); |
97 | 38 |
|
98 | | - test("entries page loads for first category", async ({ page }) => { |
| 39 | + test("items page loads for the first category", async ({ page }) => { |
99 | 40 | await page.goto("/admin"); |
100 | 41 | await page.waitForLoadState("networkidle"); |
101 | 42 |
|
102 | | - // Click the first category link |
103 | | - const categoryLink = page |
104 | | - .locator("a[href*='s=entries']") |
105 | | - .first(); |
106 | | - await categoryLink.waitFor({ timeout: 10000 }); |
| 43 | + const categoryLink = page.locator("a[href*='/admin/items/?category=']").first(); |
| 44 | + await categoryLink.waitFor({ timeout: 15000 }); |
107 | 45 | await categoryLink.click(); |
108 | 46 |
|
| 47 | + await expect(page).toHaveURL(/\/admin\/items\/?\?category=/, { timeout: 10000 }); |
109 | 48 | await page.waitForLoadState("networkidle"); |
110 | | - |
111 | | - // Should show entries (or "no entries" message) |
112 | | - await expect(page.locator("h2, [class*='font-bold']").first()).toBeVisible({ |
113 | | - timeout: 10000, |
114 | | - }); |
115 | | - }); |
116 | | - |
117 | | - test("settings page loads restaurant data from API", async ({ page }) => { |
118 | | - // Intercept the catalog or settings load |
119 | | - const catalogReq = page.waitForRequest( |
120 | | - (req) => |
121 | | - req.url().includes("localhost:8787") && |
122 | | - req.url().includes("/catalog/"), |
123 | | - { timeout: 10000 } |
124 | | - ); |
125 | | - |
126 | | - await page.goto("/admin"); |
127 | | - |
128 | | - // Should have hit the API, not just Firestore |
129 | | - const req = await catalogReq; |
130 | | - expect(req.url()).toContain("demo-restaurant"); |
| 49 | + // The items page renders the seeded entries for the category (no auth gate / crash). |
| 50 | + await expect(page.getByRole("heading", { level: 4 }).first()).toBeVisible({ timeout: 10000 }); |
131 | 51 | }); |
132 | 52 | }); |
0 commit comments