test(web): add Playwright E2E and admin API test suite for RCA-19

Prepares the full QA test infrastructure for the admin frontend before
all prerequisite feature tasks (RCA-12–18) are complete.

- playwright.config.ts: 6 browser/device projects (Chromium, Firefox,
  WebKit, mobile Chrome, mobile Safari, tablet)
- tests/e2e/01-login.spec.ts: login form, route guards, setup wizard
- tests/e2e/02-dashboard.spec.ts: stats cards, device list, quick actions
- tests/e2e/03-cookies.spec.ts: cookie list, search, detail panel, delete
- tests/e2e/04-devices.spec.ts: device cards, revoke flow, status filter
- tests/e2e/05-settings.spec.ts: three-tab layout, save/error toasts
- tests/e2e/06-responsive.spec.ts: no horizontal scroll on mobile/tablet
- tests/api/admin-api.spec.ts: REST API contract tests for all /admin/* endpoints
- helpers/auth.ts: loginViaUI + loginViaAPI helpers
- helpers/mock-api.ts: route intercept fixtures for all pages

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
徐枫
2026-03-17 20:24:22 +08:00
parent e3a9d9f63c
commit f4144c96f1
11 changed files with 1529 additions and 1 deletions

View File

@@ -0,0 +1,116 @@
import { test, expect } from "@playwright/test";
import { loginViaAPI } from "./helpers/auth.js";
import { mockDashboard, mockAPIError } from "./helpers/mock-api.js";
/**
* RCA-15: Dashboard
*
* Covers:
* - Stats cards render with correct values
* - Device status list
* - Quick-action links navigate to correct routes
* - Data refresh works
* - Error state when API fails
*/
test.describe("Dashboard", () => {
test.beforeEach(async ({ page, request }) => {
await loginViaAPI(page, request);
await mockDashboard(page);
});
test("shows all four stats cards", async ({ page }) => {
await page.goto("/dashboard");
// Connected devices
await expect(page.getByText(/connected devices|devices/i).first()).toBeVisible();
// Cookie count
await expect(page.getByText(/cookie|cookies/i).first()).toBeVisible();
// Sync count
await expect(page.getByText(/sync/i).first()).toBeVisible();
// Uptime
await expect(page.getByText(/uptime|running/i).first()).toBeVisible();
});
test("stats cards display values from the API", async ({ page }) => {
await page.goto("/dashboard");
// Our mock returns: devices total=3, cookies total=142, syncCount=57
await expect(page.getByText("3")).toBeVisible();
await expect(page.getByText("142")).toBeVisible();
await expect(page.getByText("57")).toBeVisible();
});
test("device status list shows online/offline badges", async ({ page }) => {
await page.route("**/admin/devices*", (route) =>
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({
devices: [
{ id: "d1", name: "Chrome on macOS", platform: "chrome", online: true, lastSeen: new Date().toISOString() },
{ id: "d2", name: "Firefox on Windows", platform: "firefox", online: false, lastSeen: "2026-03-15T10:00:00Z" },
],
}),
}),
);
await page.goto("/dashboard");
await expect(page.getByText("Chrome on macOS")).toBeVisible();
await expect(page.getByText("Firefox on Windows")).toBeVisible();
// At least one online/offline indicator
const badges = page.getByText(/online|offline/i);
await expect(badges.first()).toBeVisible();
});
test("quick action 'View all cookies' navigates to /cookies", async ({ page }) => {
await page.goto("/dashboard");
await page.getByRole("link", { name: /view all cookie|all cookie|cookie/i }).first().click();
await expect(page).toHaveURL(/\/cookies/);
});
test("quick action 'Manage devices' navigates to /devices", async ({ page }) => {
await page.goto("/dashboard");
await page.getByRole("link", { name: /manage device|devices/i }).first().click();
await expect(page).toHaveURL(/\/devices/);
});
test("quick action 'Settings' navigates to /settings", async ({ page }) => {
await page.goto("/dashboard");
await page.getByRole("link", { name: /setting|settings/i }).first().click();
await expect(page).toHaveURL(/\/settings/);
});
test("refresh button re-fetches dashboard data", async ({ page }) => {
let callCount = 0;
await page.route("**/admin/dashboard", (route) => {
callCount++;
return route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ devices: {}, cookies: {}, syncCount: callCount, uptimeSeconds: 0 }),
});
});
await page.goto("/dashboard");
const refreshBtn = page.getByRole("button", { name: /refresh/i });
if (await refreshBtn.isVisible()) {
const before = callCount;
await refreshBtn.click();
expect(callCount).toBeGreaterThan(before);
}
});
test("shows error message when dashboard API fails", async ({ page }) => {
await page.unroute("**/admin/dashboard");
await mockAPIError(page, "**/admin/dashboard", 500, "Server error");
await page.goto("/dashboard");
await expect(
page
.getByRole("alert")
.or(page.getByText(/error|failed|unavailable/i))
.first(),
).toBeVisible();
});
});