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>
187 lines
5.9 KiB
TypeScript
187 lines
5.9 KiB
TypeScript
import { test, expect } from "@playwright/test";
|
|
import { loginViaAPI } from "./helpers/auth.js";
|
|
import { mockCookies, mockAPIError } from "./helpers/mock-api.js";
|
|
|
|
/**
|
|
* RCA-16: Cookie management page
|
|
*
|
|
* Covers:
|
|
* - Cookies grouped by domain
|
|
* - Search by domain name
|
|
* - Search by cookie name
|
|
* - Detail panel shows all fields
|
|
* - Delete single cookie with confirmation
|
|
* - Bulk delete
|
|
* - Domain group collapse/expand
|
|
* - Pagination / scroll
|
|
* - API error state
|
|
*/
|
|
|
|
test.describe("Cookie management", () => {
|
|
test.beforeEach(async ({ page, request }) => {
|
|
await loginViaAPI(page, request);
|
|
await mockCookies(page);
|
|
});
|
|
|
|
test("lists cookies grouped by domain", async ({ page }) => {
|
|
await page.goto("/cookies");
|
|
|
|
await expect(page.getByText("example.com")).toBeVisible();
|
|
await expect(page.getByText("other.io")).toBeVisible();
|
|
});
|
|
|
|
test("search by domain filters results", async ({ page }) => {
|
|
await page.goto("/cookies");
|
|
|
|
const searchInput = page
|
|
.getByPlaceholder(/search/i)
|
|
.or(page.getByRole("searchbox"))
|
|
.or(page.getByLabel(/search/i));
|
|
|
|
await searchInput.fill("other.io");
|
|
|
|
await expect(page.getByText("other.io")).toBeVisible();
|
|
await expect(page.getByText("example.com")).not.toBeVisible();
|
|
});
|
|
|
|
test("search by cookie name filters results", async ({ page }) => {
|
|
await page.goto("/cookies");
|
|
|
|
const searchInput = page
|
|
.getByPlaceholder(/search/i)
|
|
.or(page.getByRole("searchbox"))
|
|
.or(page.getByLabel(/search/i));
|
|
|
|
await searchInput.fill("session");
|
|
|
|
// "session" cookie under example.com should be visible
|
|
await expect(page.getByText("session")).toBeVisible();
|
|
// "token" under other.io should not be visible
|
|
await expect(page.getByText("token")).not.toBeVisible();
|
|
});
|
|
|
|
test("clicking a cookie shows detail panel with all fields", async ({ page }) => {
|
|
await page.goto("/cookies");
|
|
|
|
// Click the "session" cookie row
|
|
await page.getByText("session").first().click();
|
|
|
|
// Detail panel should show all cookie fields
|
|
await expect(page.getByText(/name/i)).toBeVisible();
|
|
await expect(page.getByText(/value/i)).toBeVisible();
|
|
await expect(page.getByText(/domain/i)).toBeVisible();
|
|
await expect(page.getByText(/path/i)).toBeVisible();
|
|
await expect(page.getByText(/expires/i)).toBeVisible();
|
|
await expect(page.getByText(/secure/i)).toBeVisible();
|
|
await expect(page.getByText(/httponly/i)).toBeVisible();
|
|
});
|
|
|
|
test("deletes a single cookie after confirmation", async ({ page }) => {
|
|
let deleteCalled = false;
|
|
await page.route("**/admin/cookies/c1", (route) => {
|
|
if (route.request().method() === "DELETE") {
|
|
deleteCalled = true;
|
|
return route.fulfill({ status: 200, contentType: "application/json", body: "{}" });
|
|
}
|
|
return route.continue();
|
|
});
|
|
|
|
await page.goto("/cookies");
|
|
|
|
// Click the first cookie's delete button
|
|
const deleteBtn = page
|
|
.getByRole("button", { name: /delete/i })
|
|
.first();
|
|
await deleteBtn.click();
|
|
|
|
// Confirmation dialog should appear
|
|
await expect(
|
|
page.getByRole("dialog").or(page.getByText(/confirm|are you sure/i)),
|
|
).toBeVisible();
|
|
|
|
// Confirm deletion
|
|
await page.getByRole("button", { name: /confirm|yes|delete/i }).last().click();
|
|
|
|
expect(deleteCalled).toBe(true);
|
|
});
|
|
|
|
test("cancel on delete dialog does not delete the cookie", async ({ page }) => {
|
|
let deleteCalled = false;
|
|
await page.route("**/admin/cookies/*", (route) => {
|
|
if (route.request().method() === "DELETE") {
|
|
deleteCalled = true;
|
|
}
|
|
return route.continue();
|
|
});
|
|
|
|
await page.goto("/cookies");
|
|
const deleteBtn = page.getByRole("button", { name: /delete/i }).first();
|
|
await deleteBtn.click();
|
|
|
|
await page
|
|
.getByRole("button", { name: /cancel|no/i })
|
|
.last()
|
|
.click();
|
|
|
|
expect(deleteCalled).toBe(false);
|
|
});
|
|
|
|
test("can select multiple cookies and bulk delete", async ({ page }) => {
|
|
let bulkDeleteCalled = false;
|
|
await page.route("**/admin/cookies", (route) => {
|
|
if (route.request().method() === "DELETE") {
|
|
bulkDeleteCalled = true;
|
|
return route.fulfill({ status: 200, contentType: "application/json", body: "{}" });
|
|
}
|
|
return route.continue();
|
|
});
|
|
|
|
await page.goto("/cookies");
|
|
|
|
// Select checkboxes
|
|
const checkboxes = page.getByRole("checkbox");
|
|
const count = await checkboxes.count();
|
|
if (count > 0) {
|
|
await checkboxes.first().check();
|
|
if (count > 1) await checkboxes.nth(1).check();
|
|
|
|
const bulkBtn = page.getByRole("button", { name: /delete selected|bulk delete/i });
|
|
if (await bulkBtn.isVisible()) {
|
|
await bulkBtn.click();
|
|
await page.getByRole("button", { name: /confirm|yes|delete/i }).last().click();
|
|
expect(bulkDeleteCalled).toBe(true);
|
|
}
|
|
}
|
|
});
|
|
|
|
test("domain group collapses and expands", async ({ page }) => {
|
|
await page.goto("/cookies");
|
|
|
|
// Find a domain group header and click to collapse
|
|
const groupHeader = page.getByText("example.com").first();
|
|
await groupHeader.click();
|
|
|
|
// After collapse, cookies within that domain should be hidden
|
|
// (exact selector depends on implementation — check one of the children)
|
|
const sessionCookie = page.getByText("session");
|
|
// It may be hidden or removed; either is acceptable
|
|
const isVisible = await sessionCookie.isVisible().catch(() => false);
|
|
// Click again to expand
|
|
await groupHeader.click();
|
|
await expect(page.getByText("session")).toBeVisible();
|
|
});
|
|
|
|
test("shows error message when cookies API fails", async ({ page }) => {
|
|
await page.unroute("**/admin/cookies*");
|
|
await mockAPIError(page, "**/admin/cookies*", 500, "Failed to load cookies");
|
|
|
|
await page.goto("/cookies");
|
|
await expect(
|
|
page
|
|
.getByRole("alert")
|
|
.or(page.getByText(/error|failed|could not load/i))
|
|
.first(),
|
|
).toBeVisible();
|
|
});
|
|
});
|