Files
SnapOtter/tests/e2e/gui-settings-general.spec.ts
T
SnapOtter 03f82567d0 test: expand API and GUI test coverage across all tools
Add ~500 new E2E tests and ~300 new integration tests covering:

- 24 new GUI E2E specs: navigation, responsive layout, keyboard shortcuts,
  tool UI for all 35 non-AI tools, batch/pipeline workflows, settings/RBAC,
  visual regression, accessibility, and performance budgets
- 3 new E2E-Docker specs: batch workflows, advanced pipelines, cross-format
- 1 new adversarial integration test: memory pressure, corrupted files,
  unicode filenames, extreme dimensions, pipeline/batch edge cases
- 29 expanded integration test files: HEIC/HEIF input, large files, parameter
  boundaries, batch processing, format edge cases across all tools
- Cross-format matrix expanded: 641 tests covering every tool x 18 formats
- AI bridge unit tests expanded: lifecycle, tool modules, error propagation
- Unit test gaps filled: analytics, tool-registry, web stores

Also fixes:
- vitest.config.ts: exclude e2e-docs and e2e-landing from Vitest runner
- AI E2E specs: add sidecar health check to skip gracefully when Python
  AI backend is not running instead of timing out
2026-04-29 01:39:25 +08:00

205 lines
8.3 KiB
TypeScript

import { expect, test } from "./helpers";
// ---------------------------------------------------------------------------
// Settings Dialog -- General, System Settings, About tabs
// ---------------------------------------------------------------------------
test.describe("GUI Settings - Dialog Navigation", () => {
test("opens settings dialog from sidebar", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
// 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();
// All section tabs should be buttons in the dialog sidebar
for (const label of [
"General",
"System Settings",
"Security",
"People",
"Teams",
"API Keys",
"Tools",
"About",
]) {
await expect(page.getByRole("button", { name: new RegExp(label, "i") })).toBeVisible();
}
});
test("clicking a tab switches the content pane", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
// Navigate to About section and confirm its heading
await page.getByRole("button", { name: /about/i }).click();
await expect(page.locator("h3").filter({ hasText: "About" })).toBeVisible();
// Navigate back to General and confirm its heading
await page.getByRole("button", { name: /general/i }).click();
await expect(page.locator("h3").filter({ hasText: "General" })).toBeVisible();
});
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();
// The close button renders the X icon inside the dialog
const closeBtn = page.locator("button").filter({ has: page.locator("svg.lucide-x") });
await closeBtn.click();
// Dialog content should disappear
await expect(page.locator("h2").filter({ hasText: "Settings" })).not.toBeVisible();
});
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 page.keyboard.press("Escape");
await expect(page.locator("h2").filter({ hasText: "Settings" })).not.toBeVisible();
});
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();
// Click the backdrop overlay (the semi-transparent div behind the dialog)
await page.locator(".bg-black\\/50").click({ position: { x: 10, y: 10 } });
await expect(page.locator("h2").filter({ hasText: "Settings" })).not.toBeVisible();
});
});
test.describe("GUI Settings - General Tab", () => {
test("displays username and role badge", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
// General section is the default; username and role should be visible
await expect(page.getByText("admin").first()).toBeVisible();
// The role is displayed as a capitalize text below the username
await expect(page.getByText(/admin/i).first()).toBeVisible();
});
test("shows Default Tool View dropdown with options", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await expect(page.getByText("Default Tool View")).toBeVisible();
const select = page.locator("select").first();
await expect(select).toBeVisible();
// Verify the two options
await expect(select.locator("option[value='sidebar']")).toHaveText("Sidebar");
await expect(select.locator("option[value='fullscreen']")).toHaveText("Fullscreen Grid");
});
test("shows App Version string", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await expect(page.getByText("App Version")).toBeVisible();
// Version is in a monospace span matching semver pattern
await expect(page.locator(".font-mono").filter({ hasText: /^\d+\.\d+\.\d+/ })).toBeVisible();
});
test("has a Save Settings button", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
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();
const logoutBtn = page.getByRole("button", { name: /log out/i });
await expect(logoutBtn).toBeVisible();
await logoutBtn.click();
await page.waitForURL("/login", { timeout: 10_000 });
// Should be on the login page
expect(page.url()).toContain("/login");
});
});
test.describe("GUI Settings - System Settings Tab", () => {
test("shows App Name input", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await page.getByRole("button", { name: /system settings/i }).click();
await expect(page.getByText("App Name")).toBeVisible();
const appNameInput = page.locator("input[type='text']").first();
await expect(appNameInput).toBeVisible();
});
test("shows File Upload Limit input", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await page.getByRole("button", { name: /system settings/i }).click();
await expect(page.getByText("File Upload Limit (MB)")).toBeVisible();
await expect(page.locator("input[type='number']").first()).toBeVisible();
});
test("shows Default Theme dropdown", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await page.getByRole("button", { name: /system settings/i }).click();
await expect(page.getByText("Default Theme")).toBeVisible();
const themeSelect = page
.locator("select")
.filter({ has: page.locator("option[value='dark']") });
await expect(themeSelect).toBeVisible();
// Verify theme options
await expect(themeSelect.locator("option[value='light']")).toHaveText("Light");
await expect(themeSelect.locator("option[value='dark']")).toHaveText("Dark");
await expect(themeSelect.locator("option[value='system']")).toHaveText("System");
});
test("shows Login Attempt Limit input", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
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 page.getByRole("button", { name: /system settings/i }).click();
// Wait for section to load
await expect(page.getByText("App Name")).toBeVisible();
// Click save
await page.getByRole("button", { name: /save settings/i }).click();
// Should show a success message
await expect(page.getByText("Settings saved.")).toBeVisible({ timeout: 5_000 });
});
});
test.describe("GUI Settings - About Tab", () => {
test("displays app description and version", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await page.getByRole("button", { name: /about/i }).click();
await expect(page.locator("h3").filter({ hasText: "About" })).toBeVisible();
// SnapOtter branding
await expect(page.getByText("SnapOtter").first()).toBeVisible();
// Description text
await expect(page.getByText(/self-hosted.*privacy/i).first()).toBeVisible();
// Version label and value
await expect(page.getByText("Version:")).toBeVisible();
});
test("shows GitHub and documentation links", async ({ loggedInPage: page }) => {
await page.locator("aside").getByText("Settings").click();
await page.getByRole("button", { name: /about/i }).click();
await expect(page.getByRole("link", { name: /github repository/i })).toBeVisible();
await expect(page.getByRole("link", { name: /documentation/i })).toBeVisible();
});
});