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,63 @@
import { test, expect, devices } from "@playwright/test";
import { loginViaAPI } from "./helpers/auth.js";
import { mockDashboard, mockCookies, mockDevices, mockSettings } from "./helpers/mock-api.js";
/**
* Responsive layout tests
*
* These run on the default desktop viewport; the Playwright projects
* in playwright.config.ts also exercise mobile-chrome, mobile-safari,
* and tablet viewports automatically.
*
* This file adds explicit viewport-override tests for key layout expectations.
*/
const PAGES = [
{ path: "/dashboard", name: "Dashboard" },
{ path: "/cookies", name: "Cookies" },
{ path: "/devices", name: "Devices" },
{ path: "/settings", name: "Settings" },
];
for (const { path, name } of PAGES) {
test.describe(`Responsive — ${name}`, () => {
test.beforeEach(async ({ page, request }) => {
await loginViaAPI(page, request);
await mockDashboard(page);
await mockCookies(page);
await mockDevices(page);
await mockSettings(page);
});
test("renders without horizontal scroll on mobile (375px)", async ({ page }) => {
await page.setViewportSize({ width: 375, height: 812 });
await page.goto(path);
await page.waitForLoadState("networkidle");
const scrollWidth = await page.evaluate(() => document.body.scrollWidth);
const clientWidth = await page.evaluate(() => document.body.clientWidth);
expect(scrollWidth).toBeLessThanOrEqual(clientWidth + 1); // 1px tolerance
});
test("renders without horizontal scroll on tablet (768px)", async ({ page }) => {
await page.setViewportSize({ width: 768, height: 1024 });
await page.goto(path);
await page.waitForLoadState("networkidle");
const scrollWidth = await page.evaluate(() => document.body.scrollWidth);
const clientWidth = await page.evaluate(() => document.body.clientWidth);
expect(scrollWidth).toBeLessThanOrEqual(clientWidth + 1);
});
test("navigation is reachable on mobile", async ({ page }) => {
await page.setViewportSize({ width: 375, height: 812 });
await page.goto(path);
// On mobile there's typically a hamburger menu or bottom nav
const nav = page
.getByRole("navigation")
.or(page.getByRole("button", { name: /menu|nav/i }));
await expect(nav.first()).toBeVisible();
});
});
}