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>
117 lines
4.0 KiB
TypeScript
117 lines
4.0 KiB
TypeScript
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();
|
|
});
|
|
});
|