import { expect, openSettings, test } from "./helpers"; // --------------------------------------------------------------------------- // Helper: the correct modifier key for the OS running Playwright // On macOS Playwright uses "Meta", on others "Control". // --------------------------------------------------------------------------- const MOD = process.platform === "darwin" ? "Meta" : "Control"; // --------------------------------------------------------------------------- // Keyboard Shortcuts // --------------------------------------------------------------------------- test.describe("Keyboard Shortcuts", () => { test("Cmd/Ctrl+K focuses the search bar", async ({ loggedInPage: page }) => { // Ensure search bar exists but is not focused const searchInput = page.getByPlaceholder(/search/i).first(); await expect(searchInput).toBeVisible(); await page.keyboard.press(`${MOD}+k`); await expect(searchInput).toBeFocused(); }); test("Cmd/Ctrl+/ navigates to tools (home) page", async ({ loggedInPage: page }) => { await page.goto("/automate"); await expect(page).toHaveURL("/automate"); await page.keyboard.press(`${MOD}+/`); await expect(page).toHaveURL("/"); }); test("Cmd/Ctrl+Shift+D toggles the theme", async ({ loggedInPage: page }) => { // Check the initial dark class state const hadDarkBefore = await page.evaluate(() => document.documentElement.classList.contains("dark"), ); await page.keyboard.press(`${MOD}+Shift+d`); // Wait briefly for the class toggle to apply await page.waitForTimeout(300); const hasDarkAfter = await page.evaluate(() => document.documentElement.classList.contains("dark"), ); // Theme should have toggled expect(hasDarkAfter).not.toBe(hadDarkBefore); }); test("Cmd/Ctrl+Shift+D toggles theme back on second press", async ({ loggedInPage: page }) => { const initial = await page.evaluate(() => document.documentElement.classList.contains("dark")); // First toggle await page.keyboard.press(`${MOD}+Shift+d`); await page.waitForTimeout(300); // Second toggle await page.keyboard.press(`${MOD}+Shift+d`); await page.waitForTimeout(300); const afterDouble = await page.evaluate(() => document.documentElement.classList.contains("dark"), ); expect(afterDouble).toBe(initial); }); test("shortcuts do not fire when typing in an input field", async ({ loggedInPage: page }) => { // Focus the search input const searchInput = page.getByPlaceholder(/search/i).first(); await searchInput.click(); await searchInput.fill(""); // Press Cmd+/ which normally navigates to / // But since we're in an input, it should NOT navigate await page.goto("/fullscreen"); await page.waitForTimeout(300); const searchOnFullscreen = page.getByPlaceholder(/search/i); await searchOnFullscreen.click(); // Type the / character while focused on input - this should not navigate await page.keyboard.press(`${MOD}+/`); await page.waitForTimeout(300); // Should still be on fullscreen since the shortcut was suppressed await expect(page).toHaveURL("/fullscreen"); }); test("Cmd/Ctrl+K works even when focused on an input field", async ({ loggedInPage: page }) => { // Navigate to fullscreen which has its own search input await page.goto("/fullscreen"); const searchInput = page.getByPlaceholder(/search/i); await searchInput.click(); await searchInput.fill("test"); // Cmd+K is the exception - it should still fire in inputs await page.keyboard.press(`${MOD}+k`); // The search input should be focused and selected await expect(searchInput).toBeFocused(); }); }); // --------------------------------------------------------------------------- // Tool navigation shortcuts (Cmd+Alt+N) // --------------------------------------------------------------------------- test.describe("Keyboard Shortcuts - Tool Navigation", () => { test("Cmd/Ctrl+Alt+1 navigates to Resize", async ({ loggedInPage: page }) => { await page.keyboard.press(`${MOD}+Alt+1`); await expect(page).toHaveURL("/resize"); }); test("Cmd/Ctrl+Alt+2 navigates to Crop", async ({ loggedInPage: page }) => { await page.keyboard.press(`${MOD}+Alt+2`); await expect(page).toHaveURL("/crop"); }); test("Cmd/Ctrl+Alt+3 navigates to Compress", async ({ loggedInPage: page }) => { await page.keyboard.press(`${MOD}+Alt+3`); await expect(page).toHaveURL("/compress"); }); test("Cmd/Ctrl+Alt+4 navigates to Convert", async ({ loggedInPage: page }) => { await page.keyboard.press(`${MOD}+Alt+4`); await expect(page).toHaveURL("/convert"); }); test("Cmd/Ctrl+Alt+5 navigates to Remove Background", async ({ loggedInPage: page }) => { await page.keyboard.press(`${MOD}+Alt+5`); await expect(page).toHaveURL("/remove-background"); }); test("Cmd/Ctrl+Alt+6 navigates to Watermark Text", async ({ loggedInPage: page }) => { await page.keyboard.press(`${MOD}+Alt+6`); await expect(page).toHaveURL("/watermark-text"); }); test("Cmd/Ctrl+Alt+7 navigates to Strip Metadata", async ({ loggedInPage: page }) => { await page.keyboard.press(`${MOD}+Alt+7`); await expect(page).toHaveURL("/strip-metadata"); }); test("Cmd/Ctrl+Alt+8 navigates to Image Info", async ({ loggedInPage: page }) => { await page.keyboard.press(`${MOD}+Alt+8`); await expect(page).toHaveURL("/info"); }); }); // --------------------------------------------------------------------------- // Keyboard shortcut suppression in different input types // --------------------------------------------------------------------------- test.describe("Keyboard Shortcuts - Input Suppression", () => { test("Cmd/Ctrl+Shift+D does not toggle theme when focused on search input", async ({ loggedInPage: page, }) => { // Navigate to fullscreen which reliably shows the search input await page.goto("/fullscreen"); const searchInput = page.getByPlaceholder(/search/i); await expect(searchInput).toBeVisible(); await searchInput.click(); await searchInput.fill(""); const hadDark = await page.evaluate(() => document.documentElement.classList.contains("dark")); await page.keyboard.press(`${MOD}+Shift+d`); await page.waitForTimeout(300); const hasDark = await page.evaluate(() => document.documentElement.classList.contains("dark")); // Theme should NOT have changed since we were in an input expect(hasDark).toBe(hadDark); }); test("Cmd/Ctrl+Alt+1 does not navigate when focused on search input", async ({ loggedInPage: page, }) => { await page.goto("/fullscreen"); const searchInput = page.getByPlaceholder(/search/i); await expect(searchInput).toBeVisible(); await searchInput.click(); await page.keyboard.press(`${MOD}+Alt+1`); await page.waitForTimeout(300); // Should still be on fullscreen since shortcut was suppressed await expect(page).toHaveURL("/fullscreen"); }); }); // --------------------------------------------------------------------------- // Escape key behavior // --------------------------------------------------------------------------- test.describe("Keyboard Shortcuts - Escape Key", () => { test("Escape closes the settings dialog", async ({ loggedInPage: page }) => { await openSettings(page); await expect(page.getByRole("dialog")).toBeVisible(); await page.keyboard.press("Escape"); await page.waitForTimeout(300); await expect(page.getByRole("dialog")).not.toBeVisible(); }); test("Escape closes the help dialog", async ({ loggedInPage: page }) => { await page.locator("aside").getByText("Help").click(); await expect(page.getByRole("heading", { name: "Help" })).toBeVisible(); await page.keyboard.press("Escape"); await page.waitForTimeout(300); await expect(page.getByRole("heading", { name: "Help" })).not.toBeVisible(); }); }); // --------------------------------------------------------------------------- // Shortcut suppression in textarea and contenteditable // --------------------------------------------------------------------------- test.describe("Keyboard Shortcuts - Textarea Suppression", () => { test("Cmd/Ctrl+/ does not navigate when focused on textarea", async ({ loggedInPage: page }) => { // Navigate to a tool that has a textarea (watermark-text has text input) await page.goto("/watermark-text"); // Find a textarea or contenteditable element on the page const textarea = page.locator("textarea").first(); if (await textarea.isVisible({ timeout: 3000 }).catch(() => false)) { await textarea.click(); await page.keyboard.press(`${MOD}+/`); await page.waitForTimeout(300); // Should still be on watermark-text since shortcut was suppressed in textarea await expect(page).toHaveURL("/watermark-text"); } }); test("Cmd/Ctrl+Shift+D does not toggle theme when focused on a textarea", async ({ loggedInPage: page, }) => { await page.goto("/watermark-text"); const textarea = page.locator("textarea").first(); if (await textarea.isVisible({ timeout: 3000 }).catch(() => false)) { await textarea.click(); const hadDark = await page.evaluate(() => document.documentElement.classList.contains("dark"), ); await page.keyboard.press(`${MOD}+Shift+d`); await page.waitForTimeout(300); const hasDark = await page.evaluate(() => document.documentElement.classList.contains("dark"), ); // Theme should NOT have changed since we were in a textarea expect(hasDark).toBe(hadDark); } }); }); // --------------------------------------------------------------------------- // Keyboard accessibility - focus management // --------------------------------------------------------------------------- test.describe("Keyboard Accessibility", () => { test("Tab key cycles through interactive elements on home page", async ({ loggedInPage: page, }) => { // Press Tab several times and verify focus moves to interactive elements await page.keyboard.press("Tab"); await page.waitForTimeout(100); // After tabbing, some element should be focused const focusedTag = await page.evaluate(() => document.activeElement?.tagName?.toLowerCase()); expect(["a", "button", "input", "select", "textarea"]).toContain(focusedTag); }); test("search input is reachable via keyboard", async ({ loggedInPage: page }) => { // Cmd+K should focus the search bar without needing to Tab to it await page.keyboard.press(`${MOD}+k`); const searchInput = page.getByPlaceholder(/search/i).first(); await expect(searchInput).toBeFocused(); // Typing should filter tools await page.keyboard.type("resize"); await page.waitForTimeout(300); await expect(page.getByText("Resize").first()).toBeVisible(); }); });