Skip to content

Commit 9db3058

Browse files
committed
test(e2e): run admin CRUD/api/publish/palette against the demo backend
These four suites self-skipped because they required a captured auth.json (the old Firebase model). DEMO_MODE bypasses Cloudflare Access (/admin/me returns an admin), so they can run against the seeded backend with no auth.json or seam. Rewrite each onto the current UI: - drop the auth.json skip guard + storageState - real routes (/admin/items?category=, /admin/settings/publishing|profile) instead of the obsolete ?s= query nav - current selectors/labels (h1 'Categorie menu', title='Modifica', 'Pubblica menu'/'Nascondi menu', 'Tavolozza', 'Salva modifiche') - real endpoints: PUT /admin/categories/:id, PUT /admin/publication - palette/publish describes run serial (they mutate single global state); public-menu palette check waits on the CSS var instead of networkidle Verified locally in CI mode: 108 passed, 0 skipped (was 95 passed / 17 skipped).
1 parent dc7e114 commit 9db3058

4 files changed

Lines changed: 116 additions & 224 deletions

File tree

web/e2e/admin/admin-api.spec.ts

Lines changed: 23 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,132 +1,52 @@
11
/**
2-
* Admin E2E tests — authenticated, against the D1 API.
2+
* Admin ↔ D1 API E2E — runs against the seeded DEMO_MODE backend.
33
*
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).
107
*/
118

129
import { test, expect } from "@playwright/test";
13-
import path from "path";
14-
import fs from "fs";
1510

16-
const AUTH_FILE = path.join(__dirname, "../fixtures/auth.json");
1711
const API_URL = process.env.NEXT_PUBLIC_API_URL;
1812

19-
// Skip entire suite if no API or no saved session
20-
const hasAuth = fs.existsSync(AUTH_FILE);
21-
2213
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");
3915

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+
);
4721

48-
test("can open and cancel category edit modal", async ({ page }) => {
4922
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();
5923

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");
6326
});
6427

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 }) => {
6829
await page.goto("/admin");
6930
await page.waitForLoadState("networkidle");
7031

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 });
8934

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 });
9637
});
9738

98-
test("entries page loads for first category", async ({ page }) => {
39+
test("items page loads for the first category", async ({ page }) => {
9940
await page.goto("/admin");
10041
await page.waitForLoadState("networkidle");
10142

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 });
10745
await categoryLink.click();
10846

47+
await expect(page).toHaveURL(/\/admin\/items\/?\?category=/, { timeout: 10000 });
10948
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 });
13151
});
13252
});

web/e2e/admin/admin-crud.spec.ts

Lines changed: 27 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,76 @@
11
/**
2-
* Admin CRUD smoke E2E — authenticated.
2+
* Admin CRUD smoke E2E — runs against the seeded DEMO_MODE backend.
33
*
4-
* Verifies the admin UI can edit catalog data via the D1 API.
5-
* Avoids creating/deleting categories to keep the seeded menu stable;
6-
* instead re-saves existing rows to prove the PUT hits the API.
4+
* In DEMO_MODE the backend bypasses Cloudflare Access (/admin/me returns an
5+
* admin), so the admin SPA loads real demo data and CRUD calls hit the real D1
6+
* API — no auth.json or test seam required.
7+
*
8+
* Re-saves existing rows rather than creating/deleting, to keep the seeded
9+
* menu stable for other specs.
710
*/
811

912
import { test, expect } from "@playwright/test";
10-
import path from "path";
11-
import fs from "fs";
1213

13-
const AUTH_FILE = path.join(__dirname, "../fixtures/auth.json");
1414
const API_URL = process.env.NEXT_PUBLIC_API_URL;
15-
const hasAuth = fs.existsSync(AUTH_FILE);
1615

1716
test.describe("Admin CRUD smoke", () => {
1817
test.skip(!API_URL, "Skipped: NEXT_PUBLIC_API_URL not set");
19-
test.skip(!hasAuth, "Skipped: run auth-setup first");
20-
21-
test.use({ storageState: AUTH_FILE });
2218

2319
test("category list renders and shows at least one category", async ({ page }) => {
2420
await page.goto("/admin");
2521
await page.waitForLoadState("networkidle");
2622

27-
await expect(page.locator("h2")).toContainText("Categorie", { timeout: 10000 });
23+
await expect(page.getByRole("heading", { name: "Categorie menu" })).toBeVisible({ timeout: 15000 });
2824

29-
const editButtons = page.locator("button[title='Modifica nome']");
25+
const editButtons = page.locator("button[title='Modifica']");
3026
await expect(editButtons.first()).toBeVisible({ timeout: 10000 });
31-
const count = await editButtons.count();
32-
expect(count).toBeGreaterThan(0);
27+
expect(await editButtons.count()).toBeGreaterThan(0);
3328
});
3429

35-
test("clicking a category navigates to its entries page", async ({ page }) => {
30+
test("clicking a category navigates to its items page", async ({ page }) => {
3631
await page.goto("/admin");
3732
await page.waitForLoadState("networkidle");
3833

39-
const categoryLink = page
40-
.locator("a[href*='s=entries']")
41-
.first();
42-
await categoryLink.waitFor({ timeout: 10000 });
34+
const categoryLink = page.locator("a[href*='/admin/items/?category=']").first();
35+
await categoryLink.waitFor({ timeout: 15000 });
4336
await categoryLink.click();
4437

45-
await expect(page).toHaveURL(/\/admin\/entries\?category=/, { timeout: 10000 });
46-
await page.waitForLoadState("networkidle");
38+
await expect(page).toHaveURL(/\/admin\/items\/?\?category=/, { timeout: 10000 });
4739
});
4840

4941
test("edit modal opens and closes without saving", async ({ page }) => {
5042
await page.goto("/admin");
5143
await page.waitForLoadState("networkidle");
5244

53-
const editBtn = page.locator("button[title='Modifica nome']").first();
54-
await editBtn.waitFor({ timeout: 10000 });
45+
const editBtn = page.locator("button[title='Modifica']").first();
46+
await editBtn.waitFor({ timeout: 15000 });
5547
await editBtn.click();
5648

57-
await expect(page.locator("text=Modifica Categoria")).toBeVisible();
58-
await page.locator("button:has-text('Annulla')").click();
59-
await expect(page.locator("text=Modifica Categoria")).not.toBeVisible();
49+
await expect(page.getByRole("heading", { name: "Modifica categoria" })).toBeVisible();
50+
await page.getByRole("button", { name: "Annulla" }).click();
51+
await expect(page.getByRole("heading", { name: "Modifica categoria" })).not.toBeVisible();
6052
});
6153

6254
test("saving a category PUTs to the D1 backend", async ({ page }) => {
6355
await page.goto("/admin");
6456
await page.waitForLoadState("networkidle");
6557

66-
const apiCallPromise = page.waitForRequest(
67-
(req) =>
68-
req.url().includes("/admin/restaurants/") &&
69-
req.url().includes("/categories/") &&
70-
req.method() === "PUT",
58+
const putRequest = page.waitForRequest(
59+
(req) => /\/admin\/categories\/[^/]+$/.test(req.url()) && req.method() === "PUT",
7160
{ timeout: 15000 },
7261
);
7362

74-
const editBtn = page.locator("button[title='Modifica nome']").first();
75-
await editBtn.waitFor({ timeout: 10000 });
63+
const editBtn = page.locator("button[title='Modifica']").first();
64+
await editBtn.waitFor({ timeout: 15000 });
7665
await editBtn.click();
7766

78-
const nameInput = page.locator("input[type='text']").first();
67+
// Re-save the existing name unchanged (stable for other specs).
68+
const nameInput = page.locator(".adm-input").first();
7969
const currentName = await nameInput.inputValue();
8070
await nameInput.fill(currentName);
81-
await page.locator("button:has-text('Salva')").click();
71+
await page.getByRole("button", { name: "Salva modifiche" }).click();
8272

83-
const req = await apiCallPromise;
73+
const req = await putRequest;
8474
expect(req.url()).toContain("localhost:8787");
8575
});
8676
});

0 commit comments

Comments
 (0)