From 7342d9d8fdd6c6d5cdd196551aff349e0e8ce4cf Mon Sep 17 00:00:00 2001 From: SnapOtter Date: Fri, 1 May 2026 04:17:19 +0800 Subject: [PATCH] test: add tablet visual regression baselines and cross-browser tests --- playwright.config.ts | 18 ++++ tests/e2e/gui-cross-browser.spec.ts | 37 ++++++- tests/e2e/gui-visual-tablet.spec.ts | 155 ++++++++++++++++++++++++++++ 3 files changed, 207 insertions(+), 3 deletions(-) create mode 100644 tests/e2e/gui-visual-tablet.spec.ts diff --git a/playwright.config.ts b/playwright.config.ts index 5d3d1b37..eb27c96e 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -40,6 +40,24 @@ export default defineConfig({ }, dependencies: ["setup"], }, + { + name: "firefox", + use: { + ...devices["Desktop Firefox"], + storageState: authFile, + }, + testMatch: /gui-cross-browser\.spec\.ts/, + dependencies: ["setup"], + }, + { + name: "webkit", + use: { + ...devices["Desktop Safari"], + storageState: authFile, + }, + testMatch: /gui-cross-browser\.spec\.ts/, + dependencies: ["setup"], + }, ], webServer: [ { diff --git a/tests/e2e/gui-cross-browser.spec.ts b/tests/e2e/gui-cross-browser.spec.ts index a8e0ba39..a7abde07 100644 --- a/tests/e2e/gui-cross-browser.spec.ts +++ b/tests/e2e/gui-cross-browser.spec.ts @@ -1,9 +1,9 @@ import { expect, type Page } from "@playwright/test"; -import { getTestImagePath, test } from "./helpers"; +import { getTestImagePath, test, waitForProcessing } from "./helpers"; // --------------------------------------------------------------------------- -// Cross-browser smoke tests -- critical flows validated on Chromium. -// To add Firefox/WebKit, configure additional projects in playwright.config.ts. +// Cross-browser smoke tests -- critical flows validated across browsers. +// Firefox and WebKit projects are scoped to this file in playwright.config.ts. // --------------------------------------------------------------------------- const MOD = process.platform === "darwin" ? "Meta" : "Control"; @@ -48,6 +48,27 @@ test.describe("Cross-browser smoke tests", () => { expect(errors).toHaveLength(0); }); + test("resize E2E: upload, set dimensions, verify settings", async ({ loggedInPage: page }) => { + const errors = collectConsoleErrors(page); + + await page.goto("/resize"); + await page.waitForLoadState("networkidle"); + + await uploadImage(page); + + // Verify settings panel appeared after upload + await expect(page.getByText("Settings").first()).toBeVisible(); + + // Check that width/height inputs are present and interactable + const widthInput = page.locator("input[type='number']").first(); + await expect(widthInput).toBeVisible(); + await widthInput.fill("200"); + + await waitForProcessing(page); + + expect(errors).toHaveLength(0); + }); + test("theme toggle: click toggle, verify theme changes", async ({ loggedInPage: page }) => { const errors = collectConsoleErrors(page); await page.waitForLoadState("networkidle"); @@ -67,6 +88,16 @@ test.describe("Cross-browser smoke tests", () => { expect(hasDarkAfter).not.toBe(hadDarkBefore); + // Toggle back and verify it reverts + await themeBtn.click(); + await page.waitForTimeout(300); + + const hasDarkFinal = await page.evaluate(() => + document.documentElement.classList.contains("dark"), + ); + + expect(hasDarkFinal).toBe(hadDarkBefore); + expect(errors).toHaveLength(0); }); diff --git a/tests/e2e/gui-visual-tablet.spec.ts b/tests/e2e/gui-visual-tablet.spec.ts new file mode 100644 index 00000000..9840e0dc --- /dev/null +++ b/tests/e2e/gui-visual-tablet.spec.ts @@ -0,0 +1,155 @@ +import { expect, test, uploadTestImage } from "./helpers"; + +const isDocker = process.env.CI === "true" || process.env.DOCKER === "true"; +const MOD = process.platform === "darwin" ? "Meta" : "Control"; + +// --------------------------------------------------------------------------- +// Helper: toggle theme via keyboard shortcut (Cmd/Ctrl+Shift+D) +// The keyboard shortcut works reliably on all viewports. +// --------------------------------------------------------------------------- +async function setTheme(page: import("@playwright/test").Page, theme: "light" | "dark") { + const isDark = await page.evaluate(() => document.documentElement.classList.contains("dark")); + const wantDark = theme === "dark"; + if (isDark !== wantDark) { + await page.keyboard.press(`${MOD}+Shift+d`); + await page.waitForTimeout(300); + } +} + +// --------------------------------------------------------------------------- +// Helper: take a themed screenshot pair (light + dark) +// --------------------------------------------------------------------------- +async function takeThemedScreenshots(page: import("@playwright/test").Page, baseName: string) { + // Light theme + await setTheme(page, "light"); + await expect(page).toHaveScreenshot(`tablet-${baseName}-light.png`, { + fullPage: false, + }); + + // Dark theme + await setTheme(page, "dark"); + await expect(page).toHaveScreenshot(`tablet-${baseName}-dark.png`, { + fullPage: false, + }); + + // Reset to light for next test + await setTheme(page, "light"); +} + +// --------------------------------------------------------------------------- +// Tablet visual regression: 768x1024 +// --------------------------------------------------------------------------- +test.describe("Visual Tablet (768x1024)", () => { + test.skip(!isDocker, "Visual regression baselines are Docker-specific"); + test.use({ viewport: { width: 768, height: 1024 } }); + + // ---- Login page (unauthenticated) ---- + test.describe("Login page", () => { + test.use({ storageState: { cookies: [], origins: [] } }); + + test("login page - light and dark", async ({ page }) => { + await page.goto("/login"); + await page.waitForLoadState("networkidle"); + await page.waitForTimeout(500); + + await takeThemedScreenshots(page, "login-empty"); + }); + }); + + // ---- Home page (empty, no file uploaded) ---- + test("home page empty - light and dark", async ({ loggedInPage: page }) => { + await page.waitForLoadState("networkidle"); + await page.waitForTimeout(500); + + await takeThemedScreenshots(page, "home-empty"); + }); + + // ---- Home page (file uploaded, Quick Actions visible) ---- + test("home page with file uploaded - light and dark", async ({ loggedInPage: page }) => { + await uploadTestImage(page); + await page.waitForTimeout(500); + + await expect(page.getByText("Quick Actions").first()).toBeVisible(); + + await takeThemedScreenshots(page, "home-uploaded"); + }); + + // ---- Fullscreen grid page ---- + test("fullscreen grid - light and dark", async ({ loggedInPage: page }) => { + await page.goto("/fullscreen"); + await page.waitForLoadState("networkidle"); + await page.waitForTimeout(500); + + await takeThemedScreenshots(page, "fullscreen-grid"); + }); + + // ---- Automate page (empty pipeline) ---- + test("automate page empty pipeline - light and dark", async ({ loggedInPage: page }) => { + await page.goto("/automate"); + await page.waitForLoadState("networkidle"); + await page.waitForTimeout(500); + + await expect(page.getByText(/pipeline|automate/i).first()).toBeVisible(); + + await takeThemedScreenshots(page, "automate-empty"); + }); + + // ---- 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 page.waitForTimeout(500); + + await takeThemedScreenshots(page, "settings-general"); + }); + + // ---- 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(); + + // Navigate to About tab + await page.getByRole("button", { name: "About" }).click(); + await page.waitForTimeout(500); + + await takeThemedScreenshots(page, "settings-about"); + }); + + // ---- Tool page - resize (empty, no file) ---- + test("resize tool empty - light and dark", async ({ loggedInPage: page }) => { + await page.goto("/resize"); + await page.waitForLoadState("networkidle"); + await page.waitForTimeout(500); + + await takeThemedScreenshots(page, "resize-empty"); + }); + + // ---- Tool page - resize (file uploaded, settings visible) ---- + test("resize tool with file - light and dark", async ({ loggedInPage: page }) => { + await page.goto("/resize"); + await uploadTestImage(page); + await page.waitForTimeout(500); + + await takeThemedScreenshots(page, "resize-uploaded"); + }); +});