mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
The e2e specs predate the 2.0 section-route migration and navigated to
single-segment tool URLs (/resize) that now 404 (App.tsx only mounts
/:section/:toolId). This broke the whole e2e suite (Cross-Browser, Device
Matrix, E2E Full, Visual) - part of the known stale-spec backlog.
- Sweep 929 goto("/<tool>") -> goto("/<section>/<tool>") across 45 spec
files, using the authoritative TOOLS + toolSection() mapping. Two-segment
routes, top-level routes (/automate, /editor, ...), and intentional 404
tests (/nonexistent-*) are untouched.
- Implement the legacy redirects the specs already assert: the 1.x color
tools (brightness-contrast, saturation, color-channels, color-effects)
redirect to /image/adjust-colors (App.tsx). Good for old bookmarks too.
- Remove the analytics-consent page tests (gui-navigation + gui-visual);
#336 deleted that page.
Mechanical + biome-clean + web typecheck passes. Browser-specific behavior
can only be confirmed by the nightly e2e jobs.
108 lines
4.1 KiB
TypeScript
108 lines
4.1 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
test.describe("QA Fixes Verification", () => {
|
|
test.use({ storageState: ".playwright/.auth/qa-user.json" });
|
|
|
|
test("home page loads after login", async ({ page }) => {
|
|
await page.goto("/");
|
|
await page.waitForLoadState("networkidle");
|
|
await expect(page.locator("body")).not.toBeEmpty();
|
|
// Should see the main app, not the login page
|
|
await expect(page.locator("text=Login").first())
|
|
.not.toBeVisible({ timeout: 5_000 })
|
|
.catch(() => {});
|
|
});
|
|
|
|
test("tool page loads correctly", async ({ page }) => {
|
|
await page.goto("/image/resize");
|
|
await page.waitForLoadState("networkidle");
|
|
// Should see resize tool content
|
|
await expect(page.locator("text=Resize").first()).toBeVisible({ timeout: 10_000 });
|
|
});
|
|
|
|
test("/tools/:toolId redirects to /:toolId", async ({ page }) => {
|
|
await page.goto("/tools/resize");
|
|
await page.waitForTimeout(2_000);
|
|
const url = page.url();
|
|
// After the fix, /tools/resize should redirect to /resize
|
|
// On pre-fix builds, it stays at /tools/resize (treated as unknown tool)
|
|
expect(url).toContain("/resize");
|
|
});
|
|
|
|
test("invalid tool slug shows not-found state", async ({ page }) => {
|
|
await page.goto("/zzz-nonexistent-tool-xyz");
|
|
await page.waitForLoadState("networkidle");
|
|
// Should show "Tool not found" or 404 text
|
|
const notFound = page.locator("text=not found").or(page.locator("text=404"));
|
|
await expect(notFound.first()).toBeVisible({ timeout: 10_000 });
|
|
});
|
|
|
|
test("multi-segment invalid URL shows 404 page", async ({ page }) => {
|
|
await page.goto("/some/deep/nested/invalid/path");
|
|
await page.waitForLoadState("networkidle");
|
|
// After the fix, should show 404 page. On pre-fix, shows blank.
|
|
// At minimum, verify the page has some visible content (not completely blank)
|
|
const has404 = await page
|
|
.locator("text=404")
|
|
.or(page.locator("text=not found"))
|
|
.or(page.locator("text=Page not found"))
|
|
.first()
|
|
.isVisible({ timeout: 5_000 })
|
|
.catch(() => false);
|
|
if (!has404) {
|
|
test.skip(true, "404 catch-all route not present in this build (pre-fix)");
|
|
}
|
|
});
|
|
|
|
test("privacy page renders", async ({ page }) => {
|
|
await page.goto("/privacy");
|
|
await page.waitForLoadState("networkidle");
|
|
// Should show privacy policy content, not redirect away
|
|
const content = page.locator("text=Privacy").first();
|
|
await expect(content).toBeVisible({ timeout: 10_000 });
|
|
});
|
|
|
|
test("automate page loads and shows tool palette", async ({ page }) => {
|
|
await page.goto("/automate");
|
|
await page.waitForLoadState("networkidle");
|
|
// Should see the pipeline builder
|
|
const palette = page.locator("text=Resize").first();
|
|
await expect(palette).toBeVisible({ timeout: 10_000 });
|
|
});
|
|
|
|
test("files page loads", async ({ page }) => {
|
|
await page.goto("/files");
|
|
await page.waitForLoadState("networkidle");
|
|
// Should see the files interface (even if empty)
|
|
await expect(page.locator("body")).not.toBeEmpty();
|
|
});
|
|
|
|
test("settings dialog opens and shows tabs", async ({ page }) => {
|
|
await page.goto("/");
|
|
await page.waitForLoadState("networkidle");
|
|
|
|
// Click settings in sidebar
|
|
const settingsBtn = page.locator("text=Settings").first();
|
|
if (await settingsBtn.isVisible({ timeout: 3_000 }).catch(() => false)) {
|
|
await settingsBtn.click();
|
|
// Should see settings dialog with tabs
|
|
await expect(page.locator("text=General").first()).toBeVisible({ timeout: 5_000 });
|
|
}
|
|
});
|
|
|
|
test("editor page loads", async ({ page }) => {
|
|
await page.goto("/editor");
|
|
await page.waitForLoadState("networkidle");
|
|
// Should see the editor interface
|
|
await expect(page.locator("body")).not.toBeEmpty();
|
|
});
|
|
|
|
test("dropzone uses i18n strings (no hardcoded 'or')", async ({ page }) => {
|
|
await page.goto("/image/resize");
|
|
await page.waitForLoadState("networkidle");
|
|
// The dropzone should be visible with upload button
|
|
const uploadBtn = page.locator("text=Upload").first();
|
|
await expect(uploadBtn).toBeVisible({ timeout: 10_000 });
|
|
});
|
|
});
|