fix: resolve 3 pre-existing issues found during test coverage expansion

1. passport-photo 404 vs 501: add base route at /api/v1/tools/passport-photo
   that returns 501 FEATURE_NOT_INSTALLED when the AI bundle is missing,
   matching other AI tools. The /generate sub-route is Sharp-only (no
   sidecar) so it correctly skips the isToolInstalled guard.

2. AuthGuard analytics consent race: don't evaluate shouldShowConsent()
   until analyticsConfig has been fetched (guard on analyticsConfig !== null).
   Prevents redirect to /analytics-consent before config is loaded.

3. Fragile sidebar Settings selector: add openSettings(page) helper to
   E2E helpers that checks sidebar visibility with fallback to button role.
   Replace all 134 occurrences of page.locator("aside").getByText("Settings")
   across 18 test files.
This commit is contained in:
SnapOtter
2026-05-01 13:44:04 +08:00
parent 7ce0f7b431
commit fa479dcee4
22 changed files with 195 additions and 207 deletions
+8 -15
View File
@@ -1,4 +1,4 @@
import { expect, test } from "./helpers";
import { expect, openSettings, test } from "./helpers";
// ---------------------------------------------------------------------------
// GUI Accessibility: ARIA semantics, focus management, keyboard navigation
@@ -188,7 +188,7 @@ test.describe("Heading Hierarchy - Tool Pages", () => {
// ---------------------------------------------------------------------------
test.describe("Settings Dialog Accessibility", () => {
test("settings dialog opens as a layered panel with backdrop", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
// Dialog content should be visible
await expect(page.locator("h2").filter({ hasText: "Settings" })).toBeVisible();
@@ -199,8 +199,7 @@ test.describe("Settings Dialog Accessibility", () => {
});
test("Escape key closes settings dialog", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await expect(page.locator("h2").filter({ hasText: "Settings" })).toBeVisible();
await openSettings(page);
await page.keyboard.press("Escape");
@@ -208,8 +207,7 @@ test.describe("Settings Dialog Accessibility", () => {
});
test("settings dialog can be closed via close button", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await expect(page.locator("h2").filter({ hasText: "Settings" })).toBeVisible();
await openSettings(page);
// Use the X close button
const closeBtn = page
@@ -222,8 +220,7 @@ test.describe("Settings Dialog Accessibility", () => {
});
test("settings dialog has a close button", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await expect(page.locator("h2").filter({ hasText: "Settings" })).toBeVisible();
await openSettings(page);
// Close button with X icon
const closeBtn = page.locator("button").filter({ has: page.locator("svg.lucide-x") });
@@ -234,8 +231,7 @@ test.describe("Settings Dialog Accessibility", () => {
});
test("settings dialog backdrop is marked aria-hidden", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await expect(page.locator("h2").filter({ hasText: "Settings" })).toBeVisible();
await openSettings(page);
// The backdrop overlay has aria-hidden="true" to keep it out of the a11y tree
const backdrop = page.locator("div[aria-hidden='true'].absolute.inset-0");
@@ -245,8 +241,7 @@ test.describe("Settings Dialog Accessibility", () => {
});
test("focus is contained inside settings dialog while open", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await expect(page.locator("h2").filter({ hasText: "Settings" })).toBeVisible();
await openSettings(page);
// Tab through several elements -- focus should stay within the dialog
for (let i = 0; i < 10; i++) {
@@ -430,9 +425,7 @@ test.describe("Focus Management", () => {
test.describe("Focus Management - Dialogs", () => {
test("closing settings dialog returns focus to page", async ({ loggedInPage: page }) => {
// Open settings
const settingsBtn = page.locator("aside").getByText("Settings");
await settingsBtn.click();
await expect(page.locator("h2").filter({ hasText: "Settings" })).toBeVisible();
await openSettings(page);
// Close via Escape
await page.keyboard.press("Escape");
+2 -3
View File
@@ -1,5 +1,5 @@
import { expect, type Page } from "@playwright/test";
import { getTestImagePath, test, waitForProcessing } from "./helpers";
import { getTestImagePath, openSettings, test, waitForProcessing } from "./helpers";
// ---------------------------------------------------------------------------
// Cross-browser smoke tests -- critical flows validated across browsers.
@@ -105,8 +105,7 @@ test.describe("Cross-browser smoke tests", () => {
const errors = collectConsoleErrors(page);
await page.waitForLoadState("networkidle");
await page.locator("aside").getByText("Settings").click();
await expect(page.getByRole("heading", { name: "General" })).toBeVisible();
await openSettings(page);
await page.getByRole("button", { name: "About" }).click();
await page.waitForTimeout(300);
+2 -2
View File
@@ -1,4 +1,4 @@
import { expect, test, uploadTestImage } from "./helpers";
import { expect, openSettings, test, uploadTestImage } from "./helpers";
// ---------------------------------------------------------------------------
// Login Page (unauthenticated)
@@ -345,7 +345,7 @@ test.describe("Sidebar Navigation", () => {
});
test("Settings button opens SettingsDialog modal", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await expect(page.getByRole("heading", { name: "General" })).toBeVisible();
await expect(page.getByRole("button", { name: "Security" })).toBeVisible();
+5 -8
View File
@@ -1,4 +1,4 @@
import { expect, test, uploadTestImage } from "./helpers";
import { expect, openSettings, test, uploadTestImage } from "./helpers";
// ---------------------------------------------------------------------------
// GUI Performance: Page load budgets, SPA navigation, interaction responsiveness
@@ -160,16 +160,14 @@ test.describe("Settings Dialog Timing", () => {
await page.waitForLoadState("networkidle");
const start = Date.now();
await page.locator("aside").getByText("Settings").click();
await page.locator("h2").filter({ hasText: "Settings" }).waitFor({ state: "visible" });
await openSettings(page);
const openTime = Date.now() - start;
expect(openTime).toBeLessThan(300);
});
test("settings dialog closes within 300ms", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await page.locator("h2").filter({ hasText: "Settings" }).waitFor({ state: "visible" });
await openSettings(page);
const start = Date.now();
await page.keyboard.press("Escape");
@@ -180,8 +178,7 @@ test.describe("Settings Dialog Timing", () => {
});
test("switching settings tabs renders within 200ms", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await page.locator("h2").filter({ hasText: "Settings" }).waitFor({ state: "visible" });
await openSettings(page);
// Switch to About tab
const start = Date.now();
@@ -362,7 +359,7 @@ test.describe("Repeated Operations Performance", () => {
for (let i = 0; i < 20; i++) {
const start = Date.now();
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.locator("h2").filter({ hasText: "Settings" }).waitFor({ state: "visible" });
timings.push(Date.now() - start);
+3 -9
View File
@@ -1,4 +1,4 @@
import { expect, test, uploadTestImage, waitForProcessing } from "./helpers";
import { expect, openSettings, test, uploadTestImage, waitForProcessing } from "./helpers";
// ---------------------------------------------------------------------------
// GUI Resilience: Error handling, form validation, state reset, stability
@@ -187,10 +187,7 @@ test.describe("Change Password Form Validation", () => {
test.describe("Add Member Form Validation", () => {
test("adding a duplicate username shows error", async ({ loggedInPage: page }) => {
// Open settings dialog and navigate to People section
await page.locator("aside").getByText("Settings").click();
await expect(page.locator("h2").filter({ hasText: "Settings" })).toBeVisible({
timeout: 5_000,
});
await openSettings(page);
// Navigate to People section
await page.getByRole("button", { name: /people/i }).click();
@@ -424,10 +421,7 @@ test.describe("Memory and Stability", () => {
const start = Date.now();
// Open settings
await page.locator("aside").getByText("Settings").click();
await expect(page.locator("h2").filter({ hasText: "Settings" })).toBeVisible({
timeout: 5_000,
});
await openSettings(page);
const openTime = Date.now() - start;
timings.push(openTime);
+3 -9
View File
@@ -1,4 +1,4 @@
import { expect, test } from "./helpers";
import { expect, openSettings, test } from "./helpers";
// ---------------------------------------------------------------------------
// Viewport definitions
@@ -83,8 +83,7 @@ test.describe("Responsive - Desktop (1280x720)", () => {
});
test("settings dialog fits within viewport", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await expect(page.getByRole("heading", { name: "General" })).toBeVisible();
await openSettings(page);
const dialogBox = page.locator("[class*='max-w']").filter({ hasText: "General" }).first();
const box = await dialogBox.boundingBox();
@@ -168,12 +167,7 @@ test.describe("Responsive - Tablet (768x1024)", () => {
});
test("settings dialog fits within tablet viewport", async ({ loggedInPage: page }) => {
const sidebar = page.locator("aside");
// On tablet, sidebar may or may not be visible; use it if visible, otherwise use menu
if (await sidebar.isVisible({ timeout: 2000 }).catch(() => false)) {
await sidebar.getByText("Settings").click();
}
await expect(page.getByRole("heading", { name: "General" })).toBeVisible({ timeout: 10_000 });
await openSettings(page);
const scrollWidth = await page.evaluate(() => document.documentElement.scrollWidth);
const clientWidth = await page.evaluate(() => document.documentElement.clientWidth);
+12 -12
View File
@@ -1,4 +1,4 @@
import { expect, test } from "./helpers";
import { expect, openSettings, test } from "./helpers";
// ---------------------------------------------------------------------------
// Settings Dialog -- API Keys tab (full coverage)
@@ -6,7 +6,7 @@ import { expect, test } from "./helpers";
test.describe("GUI Settings - API Keys Tab", () => {
test("navigates to API Keys tab and shows heading", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /api keys/i }).click();
await expect(page.locator("h3").filter({ hasText: "API Keys" })).toBeVisible();
@@ -14,7 +14,7 @@ test.describe("GUI Settings - API Keys Tab", () => {
});
test("shows Generate API Key button and name input", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /api keys/i }).click();
await expect(page.getByPlaceholder("Key name (optional)")).toBeVisible();
@@ -24,7 +24,7 @@ test.describe("GUI Settings - API Keys Tab", () => {
test("generating an API key displays the key with copy button", async ({
loggedInPage: page,
}) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /api keys/i }).click();
const keyName = `apiKeyTest-${Date.now()}`;
@@ -50,7 +50,7 @@ test.describe("GUI Settings - API Keys Tab", () => {
});
test("generated key appears in Existing Keys list", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /api keys/i }).click();
const keyName = `listTest-${Date.now()}`;
@@ -71,7 +71,7 @@ test.describe("GUI Settings - API Keys Tab", () => {
});
test("permission scoping toggle reveals checkboxes", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /api keys/i }).click();
const scopingToggle = page.getByText("Restrict permissions (optional)");
@@ -86,7 +86,7 @@ test.describe("GUI Settings - API Keys Tab", () => {
});
test("permission scoping checkboxes can be toggled", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /api keys/i }).click();
// Open scoping panel
@@ -105,7 +105,7 @@ test.describe("GUI Settings - API Keys Tab", () => {
});
test("deleting an API key removes it from the list", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /api keys/i }).click();
// Create a key to delete
@@ -126,7 +126,7 @@ test.describe("GUI Settings - API Keys Tab", () => {
});
test("expiration date input is available", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /api keys/i }).click();
await expect(page.getByText("Expires:")).toBeVisible();
@@ -134,7 +134,7 @@ test.describe("GUI Settings - API Keys Tab", () => {
});
test("expiration date Clear button works", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /api keys/i }).click();
const dateInput = page.locator("input[type='datetime-local']");
@@ -152,7 +152,7 @@ test.describe("GUI Settings - API Keys Tab", () => {
test("empty state shows no keys message or existing keys list", async ({
loggedInPage: page,
}) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /api keys/i }).click();
// Wait for the API Keys heading to confirm the section loaded
@@ -176,7 +176,7 @@ test.describe("GUI Settings - API Keys Tab", () => {
test("generating a key with scoped permissions creates a scoped key", async ({
loggedInPage: page,
}) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /api keys/i }).click();
// Set a key name
+33 -36
View File
@@ -1,4 +1,4 @@
import { expect, test } from "./helpers";
import { expect, openSettings, test } from "./helpers";
// ---------------------------------------------------------------------------
// Settings Dialog -- General, System Settings, About tabs
@@ -6,14 +6,14 @@ import { expect, test } from "./helpers";
test.describe("GUI Settings - Dialog Navigation", () => {
test("opens settings dialog from sidebar", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
// Dialog sidebar should render with the Settings heading
await expect(page.locator("h2").filter({ hasText: "Settings" })).toBeVisible();
});
test("dialog sidebar lists navigable section tabs", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
// All section tabs should be buttons in the dialog sidebar
for (const label of [
@@ -31,7 +31,7 @@ test.describe("GUI Settings - Dialog Navigation", () => {
});
test("clicking a tab switches the content pane", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
// Navigate to About section and confirm its heading
await page.getByRole("button", { name: /about/i }).click();
@@ -43,8 +43,7 @@ test.describe("GUI Settings - Dialog Navigation", () => {
});
test("closes dialog via the X button", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await expect(page.locator("h2").filter({ hasText: "Settings" })).toBeVisible();
await openSettings(page);
// The close button renders the X icon inside the dialog
const closeBtn = page.locator("button").filter({ has: page.locator("svg.lucide-x") });
@@ -55,8 +54,7 @@ test.describe("GUI Settings - Dialog Navigation", () => {
});
test("closes dialog via Escape key", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await expect(page.locator("h2").filter({ hasText: "Settings" })).toBeVisible();
await openSettings(page);
await page.keyboard.press("Escape");
@@ -64,8 +62,7 @@ test.describe("GUI Settings - Dialog Navigation", () => {
});
test("closes dialog by clicking the backdrop", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await expect(page.locator("h2").filter({ hasText: "Settings" })).toBeVisible();
await openSettings(page);
// Click the backdrop overlay (the semi-transparent div behind the dialog)
await page.locator(".bg-black\\/50").click({ position: { x: 10, y: 10 } });
@@ -76,7 +73,7 @@ test.describe("GUI Settings - Dialog Navigation", () => {
test.describe("GUI Settings - General Tab", () => {
test("displays username and role badge", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
// General section is the default; username and role should be visible
await expect(page.getByText("admin").first()).toBeVisible();
@@ -85,7 +82,7 @@ test.describe("GUI Settings - General Tab", () => {
});
test("shows Default Tool View dropdown with options", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await expect(page.getByText("Default Tool View")).toBeVisible();
const select = page.locator("select").first();
@@ -97,7 +94,7 @@ test.describe("GUI Settings - General Tab", () => {
});
test("shows App Version string", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await expect(page.getByText("App Version")).toBeVisible();
// Version is in a monospace span matching semver pattern
@@ -105,13 +102,13 @@ test.describe("GUI Settings - General Tab", () => {
});
test("has a Save Settings button", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await expect(page.getByRole("button", { name: /save settings/i })).toBeVisible();
});
test("logout button redirects to /login", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
const logoutBtn = page.getByRole("button", { name: /log out/i });
await expect(logoutBtn).toBeVisible();
@@ -126,7 +123,7 @@ test.describe("GUI Settings - General Tab", () => {
test.describe("GUI Settings - System Settings Tab", () => {
test("shows App Name input", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /system settings/i }).click();
await expect(page.getByText("App Name")).toBeVisible();
@@ -135,7 +132,7 @@ test.describe("GUI Settings - System Settings Tab", () => {
});
test("shows File Upload Limit input", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /system settings/i }).click();
await expect(page.getByText("File Upload Limit (MB)")).toBeVisible();
@@ -143,7 +140,7 @@ test.describe("GUI Settings - System Settings Tab", () => {
});
test("shows Default Theme dropdown", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /system settings/i }).click();
await expect(page.getByText("Default Theme")).toBeVisible();
@@ -159,14 +156,14 @@ test.describe("GUI Settings - System Settings Tab", () => {
});
test("shows Login Attempt Limit input", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /system settings/i }).click();
await expect(page.getByText("Login Attempt Limit")).toBeVisible();
});
test("Save Settings button persists changes", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /system settings/i }).click();
// Wait for section to load
@@ -182,7 +179,7 @@ test.describe("GUI Settings - System Settings Tab", () => {
test.describe("GUI Settings - About Tab", () => {
test("displays app description and version", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /about/i }).click();
await expect(page.locator("h3").filter({ hasText: "About" })).toBeVisible();
@@ -195,7 +192,7 @@ test.describe("GUI Settings - About Tab", () => {
});
test("shows GitHub and documentation links", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /about/i }).click();
await expect(page.getByRole("link", { name: /github repository/i })).toBeVisible();
@@ -203,14 +200,14 @@ test.describe("GUI Settings - About Tab", () => {
});
test("shows API Reference (Swagger) link", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /about/i }).click();
await expect(page.getByRole("link", { name: /api reference/i })).toBeVisible();
});
test("version displays a semver string", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /about/i }).click();
// Version text should match semver format (e.g., 1.2.3)
@@ -219,7 +216,7 @@ test.describe("GUI Settings - About Tab", () => {
});
test("about section has the Links heading", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /about/i }).click();
await expect(page.getByText("Links")).toBeVisible();
@@ -228,7 +225,7 @@ test.describe("GUI Settings - About Tab", () => {
test.describe("GUI Settings - AI Features Tab", () => {
test("displays AI Features heading and description", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /ai features/i }).click();
await expect(page.locator("h3").filter({ hasText: "AI Features" })).toBeVisible();
@@ -236,14 +233,14 @@ test.describe("GUI Settings - AI Features Tab", () => {
});
test("shows Install All button", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /ai features/i }).click();
await expect(page.getByRole("button", { name: /install all/i })).toBeVisible();
});
test("lists feature bundles with install status", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /ai features/i }).click();
// Each bundle card is rendered within a bordered div
@@ -272,7 +269,7 @@ test.describe("GUI Settings - AI Features Tab", () => {
});
test("bundles show estimated size", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /ai features/i }).click();
// Each bundle should display an estimated size like (~XXX MB)
@@ -282,7 +279,7 @@ test.describe("GUI Settings - AI Features Tab", () => {
test.describe("GUI Settings - Tools Tab (deep)", () => {
test("all tools are listed with enable/disable toggles", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /tools/i }).click();
await expect(page.locator("h3").filter({ hasText: "Tools" }).first()).toBeVisible();
@@ -297,7 +294,7 @@ test.describe("GUI Settings - Tools Tab (deep)", () => {
});
test("toggling a tool changes the disabled counter", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /tools/i }).click();
// Read initial disabled count
@@ -320,7 +317,7 @@ test.describe("GUI Settings - Tools Tab (deep)", () => {
});
test("search filters tools in settings dialog", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /tools/i }).click();
// Wait for the tools to load
@@ -353,7 +350,7 @@ test.describe("GUI Settings - Product Analytics Tab (deep)", () => {
test("displays description about anonymous data and image privacy", async ({
loggedInPage: page,
}) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /product analytics/i }).click();
await expect(page.getByText(/share anonymous usage data/i)).toBeVisible();
@@ -361,7 +358,7 @@ test.describe("GUI Settings - Product Analytics Tab (deep)", () => {
});
test("shows either consent toggle or admin-disabled message", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /product analytics/i }).click();
// Either the toggle button is present, or the admin-disabled message is shown
@@ -381,7 +378,7 @@ test.describe("GUI Settings - Save and Persistence", () => {
loggedInPage: page,
}) => {
// Navigate to System Settings tab
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /system settings/i }).click();
await expect(page.getByText("App Name")).toBeVisible({ timeout: 5_000 });
@@ -403,7 +400,7 @@ test.describe("GUI Settings - Save and Persistence", () => {
await expect(page.locator("h2").filter({ hasText: "Settings" })).not.toBeVisible();
// Reopen the dialog and go to System Settings
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /system settings/i }).click();
await expect(page.getByText("App Name")).toBeVisible();
+17 -17
View File
@@ -1,4 +1,4 @@
import { expect, test } from "./helpers";
import { expect, openSettings, test } from "./helpers";
const API = process.env.API_URL || "http://localhost:13490";
@@ -64,7 +64,7 @@ const UID = Date.now().toString(36);
test.describe("GUI Settings - People Tab", () => {
test("displays user count and user table", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /people/i }).click();
// User count
@@ -80,7 +80,7 @@ test.describe("GUI Settings - People Tab", () => {
});
test("search filters users and shows empty state", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /people/i }).click();
await page.waitForTimeout(500);
@@ -96,7 +96,7 @@ test.describe("GUI Settings - People Tab", () => {
test("Add Members opens form with username, password, role, team fields", async ({
loggedInPage: page,
}) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /people/i }).click();
await page.waitForTimeout(500);
@@ -124,7 +124,7 @@ test.describe("GUI Settings - People Tab", () => {
let adminToken: string;
try {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /people/i }).click();
await page.waitForTimeout(500);
@@ -155,7 +155,7 @@ test.describe("GUI Settings - People Tab", () => {
});
// Try to create same username via GUI
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /people/i }).click();
await page.waitForTimeout(500);
@@ -174,7 +174,7 @@ test.describe("GUI Settings - People Tab", () => {
test("three-dot menu shows Edit Role/Team, Reset Password, and Delete", async ({
loggedInPage: page,
}) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /people/i }).click();
await page.waitForTimeout(500);
@@ -187,7 +187,7 @@ test.describe("GUI Settings - People Tab", () => {
});
test("cannot delete yourself via the menu", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /people/i }).click();
await page.waitForTimeout(500);
@@ -216,7 +216,7 @@ test.describe("GUI Settings - People Tab", () => {
body: JSON.stringify({ username, password: "TestPass123!", role: "user" }),
});
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /people/i }).click();
await page.waitForTimeout(500);
@@ -238,7 +238,7 @@ test.describe("GUI Settings - People Tab", () => {
});
test("cannot demote your own admin role via Edit Role / Team", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /people/i }).click();
await page.waitForTimeout(500);
@@ -263,7 +263,7 @@ test.describe("GUI Settings - People Tab", () => {
});
test("Reset Password opens the reset form for a user", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /people/i }).click();
await page.waitForTimeout(500);
@@ -284,7 +284,7 @@ test.describe("GUI Settings - People Tab", () => {
test.describe("GUI Settings - Teams Tab", () => {
test("displays team list with Default team", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /teams/i }).click();
await expect(page.locator("h3").filter({ hasText: "Teams" })).toBeVisible();
@@ -300,7 +300,7 @@ test.describe("GUI Settings - Teams Tab", () => {
const adminToken = await getAdminToken();
try {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /teams/i }).click();
await page.getByRole("button", { name: /create new team/i }).click();
@@ -328,7 +328,7 @@ test.describe("GUI Settings - Teams Tab", () => {
body: JSON.stringify({ name: teamName }),
});
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /teams/i }).click();
await page.waitForTimeout(500);
@@ -353,7 +353,7 @@ test.describe("GUI Settings - Teams Tab", () => {
test.describe("GUI Settings - Roles Tab", () => {
test("displays built-in roles with Built-in badge", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /^roles$/i }).click();
await expect(page.locator("h3").filter({ hasText: "Roles" })).toBeVisible();
@@ -369,7 +369,7 @@ test.describe("GUI Settings - Roles Tab", () => {
});
test("built-in roles do not show edit or delete buttons", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /^roles$/i }).click();
await expect(page.getByText("Built-in").first()).toBeVisible();
@@ -386,7 +386,7 @@ test.describe("GUI Settings - Roles Tab", () => {
});
test("Create Custom Role button is visible", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /^roles$/i }).click();
await expect(page.getByRole("button", { name: /create custom role/i })).toBeVisible();
+15 -15
View File
@@ -1,5 +1,5 @@
import { test as base, expect } from "@playwright/test";
import { login } from "./helpers";
import { login, openSettings } from "./helpers";
const API = process.env.API_URL || "http://localhost:13490";
@@ -116,7 +116,7 @@ base.describe("RBAC Settings Visibility - Admin", () => {
base.test("admin sees all settings tabs including admin-only ones", async ({ page }) => {
await page.goto("/");
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
// Tabs visible to all roles
await expect(page.getByRole("button", { name: /general/i })).toBeVisible();
@@ -137,7 +137,7 @@ base.describe("RBAC Settings Visibility - Admin", () => {
base.test("admin can navigate to People tab and see user table", async ({ page }) => {
await page.goto("/");
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /people/i }).click();
await expect(page.getByText(/\d+ users?/)).toBeVisible({ timeout: 5_000 });
@@ -146,7 +146,7 @@ base.describe("RBAC Settings Visibility - Admin", () => {
base.test("admin can navigate to Audit Log tab and see entries", async ({ page }) => {
await page.goto("/");
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /audit log/i }).click();
await expect(page.locator("h3").filter({ hasText: "Audit Log" })).toBeVisible();
@@ -158,7 +158,7 @@ base.describe("RBAC Settings Visibility - Admin", () => {
base.test("admin sees all 12 nav items", async ({ page }) => {
await page.goto("/");
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
// Count the navigation buttons in the settings dialog sidebar
const navButtons = page.locator(".w-48 button");
@@ -183,7 +183,7 @@ base.describe("RBAC Settings Visibility - Editor", () => {
"editor sees general, security, api-keys, tools, analytics, about",
async ({ page }) => {
await login(page, EDITOR_USER, EDITOR_PASS);
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
// Should see these tabs
await expect(page.getByRole("button", { name: /general/i })).toBeVisible();
@@ -199,7 +199,7 @@ base.describe("RBAC Settings Visibility - Editor", () => {
"editor does NOT see system settings, people, teams, roles, audit log, ai features",
async ({ page }) => {
await login(page, EDITOR_USER, EDITOR_PASS);
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
// Wait for dialog to fully render
await expect(page.getByRole("button", { name: /general/i })).toBeVisible();
@@ -216,7 +216,7 @@ base.describe("RBAC Settings Visibility - Editor", () => {
base.test("editor sees exactly 6 nav items", async ({ page }) => {
await login(page, EDITOR_USER, EDITOR_PASS);
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await expect(page.getByRole("button", { name: /general/i })).toBeVisible();
const navButtons = page.locator(".w-48 button");
@@ -226,7 +226,7 @@ base.describe("RBAC Settings Visibility - Editor", () => {
base.test("editor can access Security tab and see change password form", async ({ page }) => {
await login(page, EDITOR_USER, EDITOR_PASS);
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /security/i }).click();
await expect(page.getByText("Change Password").first()).toBeVisible();
@@ -235,7 +235,7 @@ base.describe("RBAC Settings Visibility - Editor", () => {
base.test("editor can access API Keys tab and generate a key", async ({ page }) => {
await login(page, EDITOR_USER, EDITOR_PASS);
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /api keys/i }).click();
await expect(page.getByRole("button", { name: /generate api key/i })).toBeVisible();
@@ -256,7 +256,7 @@ base.describe("RBAC Settings Visibility - User", () => {
base.test("user sees general, security, api-keys, tools, analytics, about", async ({ page }) => {
await login(page, USER_USER, USER_PASS);
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await expect(page.getByRole("button", { name: /general/i })).toBeVisible();
await expect(page.getByRole("button", { name: /security/i })).toBeVisible();
@@ -270,7 +270,7 @@ base.describe("RBAC Settings Visibility - User", () => {
"user does NOT see system settings, people, teams, roles, audit log, ai features",
async ({ page }) => {
await login(page, USER_USER, USER_PASS);
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
// Wait for dialog to fully render
await expect(page.getByRole("button", { name: /general/i })).toBeVisible();
@@ -286,7 +286,7 @@ base.describe("RBAC Settings Visibility - User", () => {
base.test("user sees exactly 6 nav items", async ({ page }) => {
await login(page, USER_USER, USER_PASS);
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await expect(page.getByRole("button", { name: /general/i })).toBeVisible();
const navButtons = page.locator(".w-48 button");
@@ -296,7 +296,7 @@ base.describe("RBAC Settings Visibility - User", () => {
base.test("user can access About tab and see version", async ({ page }) => {
await login(page, USER_USER, USER_PASS);
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /about/i }).click();
await expect(page.locator("h3").filter({ hasText: "About" })).toBeVisible();
@@ -305,7 +305,7 @@ base.describe("RBAC Settings Visibility - User", () => {
base.test("user General tab shows correct username and role", async ({ page }) => {
await login(page, USER_USER, USER_PASS);
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
// General is the default tab; should show the user's username and role
await expect(page.getByText(USER_USER)).toBeVisible({ timeout: 5_000 });
+12 -12
View File
@@ -1,4 +1,4 @@
import { expect, test } from "./helpers";
import { expect, openSettings, test } from "./helpers";
// ---------------------------------------------------------------------------
// Settings Dialog -- Security (change password) and API Keys tabs
@@ -6,7 +6,7 @@ import { expect, test } from "./helpers";
test.describe("GUI Settings - Security Tab", () => {
test("shows Change Password form with required inputs", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /security/i }).click();
await expect(page.locator("h3").filter({ hasText: "Security" })).toBeVisible();
@@ -22,7 +22,7 @@ test.describe("GUI Settings - Security Tab", () => {
});
test("mismatched passwords show error message", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /security/i }).click();
await page.getByPlaceholder("Current Password").fill("admin");
@@ -35,7 +35,7 @@ test.describe("GUI Settings - Security Tab", () => {
});
test("password visibility toggles work", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /security/i }).click();
const currentPwInput = page.getByPlaceholder("Current Password");
@@ -49,7 +49,7 @@ test.describe("GUI Settings - Security Tab", () => {
});
test("short password shows validation error", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /security/i }).click();
await page.getByPlaceholder("Current Password").fill("admin");
@@ -63,7 +63,7 @@ test.describe("GUI Settings - Security Tab", () => {
});
test("security section shows login attempt limit reference", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /security/i }).click();
await expect(page.getByText(/login attempt limits/i)).toBeVisible();
@@ -75,7 +75,7 @@ test.describe("GUI Settings - Security Tab", () => {
test.describe("GUI Settings - API Keys Tab", () => {
test("shows Generate API Key button and name input", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /api keys/i }).click();
await expect(page.locator("h3").filter({ hasText: "API Keys" })).toBeVisible();
@@ -84,7 +84,7 @@ test.describe("GUI Settings - API Keys Tab", () => {
});
test("generating an API key displays the key once", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /api keys/i }).click();
// Give a unique name so we can identify and clean it up
@@ -108,7 +108,7 @@ test.describe("GUI Settings - API Keys Tab", () => {
});
test("generated key appears in existing keys list", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /api keys/i }).click();
const keyName = `listTest-${Date.now()}`;
@@ -129,7 +129,7 @@ test.describe("GUI Settings - API Keys Tab", () => {
});
test("permission scoping toggle reveals checkboxes", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /api keys/i }).click();
const scopingToggle = page.getByText("Restrict permissions (optional)");
@@ -144,7 +144,7 @@ test.describe("GUI Settings - API Keys Tab", () => {
});
test("deleting an API key removes it from the list", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /api keys/i }).click();
// Create a key to delete
@@ -165,7 +165,7 @@ test.describe("GUI Settings - API Keys Tab", () => {
});
test("expiration date input is available", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /api keys/i }).click();
await expect(page.getByText("Expires:")).toBeVisible();
+9 -9
View File
@@ -1,4 +1,4 @@
import { expect, test } from "./helpers";
import { expect, openSettings, test } from "./helpers";
// ---------------------------------------------------------------------------
// Settings Dialog -- Tools tab (enable/disable) and Product Analytics
@@ -6,7 +6,7 @@ import { expect, test } from "./helpers";
test.describe("GUI Settings - Tools Tab", () => {
test("displays tools list with category headings", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /tools/i }).click();
await expect(page.locator("h3").filter({ hasText: "Tools" }).first()).toBeVisible();
@@ -18,7 +18,7 @@ test.describe("GUI Settings - Tools Tab", () => {
});
test("Save Tool Settings button and disabled tools counter", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /tools/i }).click();
await expect(page.getByRole("button", { name: /save tool settings/i })).toBeVisible();
@@ -28,7 +28,7 @@ test.describe("GUI Settings - Tools Tab", () => {
});
test("saving tool settings shows restart banner", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /tools/i }).click();
await page.getByRole("button", { name: /save tool settings/i }).click();
@@ -41,7 +41,7 @@ test.describe("GUI Settings - Tools Tab", () => {
test.describe("GUI Settings - Tools Tab (additional)", () => {
test("each category has a heading", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /tools/i }).click();
await expect(page.locator("h3").filter({ hasText: "Tools" }).first()).toBeVisible();
@@ -56,7 +56,7 @@ test.describe("GUI Settings - Tools Tab (additional)", () => {
});
test("tools show both name and description", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /tools/i }).click();
// Wait for tools to load
@@ -70,7 +70,7 @@ test.describe("GUI Settings - Tools Tab (additional)", () => {
test.describe("GUI Settings - Product Analytics Tab", () => {
test("displays analytics consent section", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /product analytics/i }).click();
await expect(page.getByText("Product Analytics").first()).toBeVisible();
@@ -78,7 +78,7 @@ test.describe("GUI Settings - Product Analytics Tab", () => {
});
test("analytics toggle is present", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /product analytics/i }).click();
// Either the toggle button is present, or the admin-disabled message is shown
@@ -93,7 +93,7 @@ test.describe("GUI Settings - Product Analytics Tab", () => {
});
test("privacy policy link is present", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /product analytics/i }).click();
await expect(page.getByRole("link", { name: /learn more/i })).toBeVisible();
+3 -5
View File
@@ -1,4 +1,4 @@
import { expect, test, uploadTestImage } from "./helpers";
import { expect, openSettings, test, uploadTestImage } from "./helpers";
const isDocker = process.env.CI === "true" || process.env.DOCKER === "true";
@@ -113,8 +113,7 @@ test.describe("Visual Desktop (1280x720)", () => {
// ---- Settings dialog - General tab ----
test("settings dialog general tab - light and dark", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await expect(page.getByRole("heading", { name: "General" })).toBeVisible();
await openSettings(page);
await page.waitForTimeout(500);
await takeThemedScreenshots(page, "settings-general");
@@ -122,8 +121,7 @@ test.describe("Visual Desktop (1280x720)", () => {
// ---- Settings dialog - About tab ----
test("settings dialog about tab - light and dark", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await expect(page.getByRole("heading", { name: "General" })).toBeVisible();
await openSettings(page);
// Navigate to About tab
await page.getByRole("button", { name: "About" }).click();
+3 -25
View File
@@ -1,4 +1,4 @@
import { expect, test, uploadTestImage } from "./helpers";
import { expect, openSettings, test, uploadTestImage } from "./helpers";
const isDocker = process.env.CI === "true" || process.env.DOCKER === "true";
const MOD = process.platform === "darwin" ? "Meta" : "Control";
@@ -96,19 +96,7 @@ test.describe("Visual Tablet (768x1024)", () => {
// ---- Settings dialog - General tab ----
test("settings dialog general tab - light and dark", async ({ loggedInPage: page }) => {
// On tablet, the sidebar may or may not be visible; try sidebar first, fallback to nav
const sidebar = page.locator("aside");
const sidebarVisible = await sidebar.isVisible().catch(() => false);
if (sidebarVisible) {
await sidebar.getByText("Settings").click();
} else {
// Fallback: bottom nav bar
const bottomNav = page.locator("nav.fixed");
await bottomNav.getByText("Settings").click();
}
await expect(page.getByRole("heading", { name: "General" })).toBeVisible();
await openSettings(page);
await page.waitForTimeout(500);
await takeThemedScreenshots(page, "settings-general");
@@ -116,17 +104,7 @@ test.describe("Visual Tablet (768x1024)", () => {
// ---- Settings dialog - About tab ----
test("settings dialog about tab - light and dark", async ({ loggedInPage: page }) => {
const sidebar = page.locator("aside");
const sidebarVisible = await sidebar.isVisible().catch(() => false);
if (sidebarVisible) {
await sidebar.getByText("Settings").click();
} else {
const bottomNav = page.locator("nav.fixed");
await bottomNav.getByText("Settings").click();
}
await expect(page.getByRole("heading", { name: "General" })).toBeVisible();
await openSettings(page);
// Navigate to About tab
await page.getByRole("button", { name: "About" }).click();
+13
View File
@@ -165,4 +165,17 @@ export async function isAiSidecarRunning(page: Page): Promise<boolean> {
}
}
// ---------------------------------------------------------------------------
// openSettings() — reliably open the Settings dialog across all viewports
// ---------------------------------------------------------------------------
export async function openSettings(page: Page): Promise<void> {
const sidebar = page.locator("aside");
if (await sidebar.isVisible({ timeout: 2000 }).catch(() => false)) {
await sidebar.getByText("Settings").click();
} else {
await page.getByRole("button", { name: /settings/i }).click();
}
await page.getByRole("dialog").waitFor({ state: "visible", timeout: 5000 });
}
export { expect };
+2 -2
View File
@@ -1,4 +1,4 @@
import { expect, test } from "./helpers";
import { expect, openSettings, test } from "./helpers";
test.describe("Navigation", () => {
test("sidebar Tools link goes to home", async ({ loggedInPage: page }) => {
@@ -26,7 +26,7 @@ test.describe("Navigation", () => {
});
test("sidebar Settings button opens settings dialog", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
// Settings dialog should appear with section headings
await expect(page.getByRole("heading", { name: "General" })).toBeVisible();
await expect(page.getByRole("button", { name: "Security" })).toBeVisible();
+5 -5
View File
@@ -1,5 +1,5 @@
import { test as base, expect } from "@playwright/test";
import { test as uiTest } from "./helpers";
import { openSettings, test as uiTest } from "./helpers";
const API = process.env.API_URL || "http://localhost:13490";
@@ -323,7 +323,7 @@ base.describe("People Management — API", () => {
uiTest.describe("People Management — UI", () => {
uiTest("People section displays user count and table", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /people/i }).click();
await page.waitForTimeout(500);
@@ -342,7 +342,7 @@ uiTest.describe("People Management — UI", () => {
});
uiTest("Search filters users", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /people/i }).click();
await page.waitForTimeout(500);
@@ -356,7 +356,7 @@ uiTest.describe("People Management — UI", () => {
});
uiTest("Add Members button is visible and interactive", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /people/i }).click();
await page.waitForTimeout(500);
@@ -380,7 +380,7 @@ uiTest.describe("People Management — UI", () => {
uiTest(
"Three-dot menu shows edit, reset password, and delete options",
async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /people/i }).click();
await page.waitForTimeout(500);
+8 -8
View File
@@ -1,5 +1,5 @@
import { test as base, expect } from "@playwright/test";
import { login } from "./helpers";
import { login, openSettings } from "./helpers";
const API = process.env.API_URL || "http://localhost:13490";
@@ -143,7 +143,7 @@ base.describe("RBAC Full — People Management UI", () => {
"admin sees role dropdown with admin/editor/user options when adding members",
async ({ page }) => {
await page.goto("/");
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /people/i }).click();
// Click "Add Members" to reveal the form
@@ -174,14 +174,14 @@ base.describe("RBAC Full — Roles Management UI", () => {
base.test("admin sees Roles tab in settings", async ({ page }) => {
await page.goto("/");
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await expect(page.getByRole("button", { name: /^roles$/i })).toBeVisible();
});
base.test("roles section shows built-in roles with Built-in badge", async ({ page }) => {
await page.goto("/");
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /^roles$/i }).click();
// Wait for roles to load
@@ -207,14 +207,14 @@ base.describe("RBAC Full — Audit Log UI", () => {
base.test("admin sees Audit Log tab in settings", async ({ page }) => {
await page.goto("/");
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await expect(page.getByRole("button", { name: /audit log/i })).toBeVisible();
});
base.test("audit log displays LOGIN_SUCCESS entries", async ({ page }) => {
await page.goto("/");
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /audit log/i }).click();
// Wait for audit log section to load
@@ -244,7 +244,7 @@ base.describe("RBAC Full — API Key Scoping UI", () => {
base.test("API Keys section has permission scoping toggle", async ({ page }) => {
await page.goto("/");
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.getByRole("button", { name: /api keys/i }).click();
// The scoping toggle text should be visible
@@ -295,7 +295,7 @@ base.describe("RBAC Full — Custom Role User", () => {
base.test("custom role user only sees permitted tabs (no admin tabs)", async ({ page }) => {
await login(page, CUSTOM_USER, CUSTOM_PASSWORD);
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
// Should see these tabs (available to all authenticated users or matching permissions)
await expect(page.getByRole("button", { name: /general/i })).toBeVisible();
+4 -4
View File
@@ -1,5 +1,5 @@
import { test as base, expect } from "@playwright/test";
import { login } from "./helpers";
import { login, openSettings } from "./helpers";
const API = process.env.API_URL || "http://localhost:13490";
@@ -108,7 +108,7 @@ base.describe("RBAC - Admin sees all tabs", () => {
base.test("admin sees all settings tabs", async ({ page }) => {
await page.goto("/");
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await expect(page.getByRole("button", { name: /general/i })).toBeVisible();
await expect(page.getByRole("button", { name: /system settings/i })).toBeVisible();
@@ -138,7 +138,7 @@ base.describe("RBAC - User sees restricted tabs", () => {
base.test("user role only sees permitted settings tabs", async ({ page }) => {
await login(page, TEST_USER, TEST_PASSWORD);
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
// Should see these tabs
await expect(page.getByRole("button", { name: /general/i })).toBeVisible();
@@ -251,7 +251,7 @@ base.describe("RBAC - Editor sees collaborative tabs", () => {
"editor sees general, security, api-keys, tools, about but not admin tabs",
async ({ page }) => {
await login(page, "editortest", "EditorTest1");
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
// Should see these
await expect(page.getByRole("button", { name: /general/i })).toBeVisible();
+10 -10
View File
@@ -1,15 +1,15 @@
import { expect, test } from "./helpers";
import { expect, openSettings, test } from "./helpers";
test.describe("Settings Dialog", () => {
test("opens from sidebar", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
// Settings dialog should appear with sections
await expect(page.getByText("General").first()).toBeVisible();
});
test("General section shows user info", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
// Should show username and version info
await expect(page.getByText(/admin/i).first()).toBeVisible();
@@ -17,14 +17,14 @@ test.describe("Settings Dialog", () => {
});
test("General section has logout button", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
const logoutBtn = page.getByRole("button", { name: /logout|log out/i });
await expect(logoutBtn).toBeVisible();
});
test("Security section has change password form", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
// Navigate to Security section
await page.getByText("Security").click();
@@ -34,7 +34,7 @@ test.describe("Settings Dialog", () => {
});
test("People section shows user list", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
// Navigate to People section
await page.getByText("People").click();
@@ -54,7 +54,7 @@ test.describe("Settings Dialog", () => {
await page.goto("/");
await page.waitForLoadState("networkidle");
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await page.waitForTimeout(500);
const apiKeysBtn = page.getByRole("button", { name: /api keys/i });
@@ -70,7 +70,7 @@ test.describe("Settings Dialog", () => {
});
test("About section shows app info", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
// Navigate to About section
await page.getByText("About").click();
@@ -80,7 +80,7 @@ test.describe("Settings Dialog", () => {
});
test("System Settings section has configuration", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
// Navigate to System Settings section
await page.getByText("System Settings").click();
@@ -90,7 +90,7 @@ test.describe("Settings Dialog", () => {
});
test("settings dialog can be closed", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await openSettings(page);
await expect(page.getByText("General").first()).toBeVisible();
// Close by clicking X or outside