mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
While getting the editor e2e suite green, three "stale test" failures turned out to be real bugs (per the reporter's hunch that tests might be catching real issues): - Layer effects (drop shadow, glows) never applied. The panel wrote effects into `attrs.effects` through updateObject, but the panel and renderer both read the object's top-level `effects`, so the toggle never persisted. Add a dedicated `setObjectEffects` store action and route the panel through it. - Object flip (transform tool) did nothing. No object renderer applied `scaleX`/`scaleY`, and the flip negated scale without compensating position. Apply scale in the renderers and flip in place: mirror points for stroke objects, negate scale + shift position for sized objects. (The paint-bucket / pixel-tool coordinate bug and the broken-at-non-100%-zoom export were fixed in the preceding #259 change.) Also adds a small "Beta" badge to the editor (welcome heading + nav link) and repairs ~18 stale editor e2e specs whose selectors/assertions had drifted from the current UI: the options bar is `h-9` not `h-10` (added a stable `data-testid`), the menu bar is `h-8`/`bg-background`, the flip button aria-labels are lowercase, the welcome "Image Editor" heading collides with an sr-only `<h1>`, the color-picker tabs need a role-scoped selector, and the magic-wand / flip tests now use deterministic setup and assert the actual effect instead of fragile screenshot diffs.
712 lines
23 KiB
TypeScript
712 lines
23 KiB
TypeScript
import path from "node:path";
|
|
import { expect, type Page, test } from "@playwright/test";
|
|
|
|
const SCREENSHOT_DIR = path.join(__dirname, "screenshots");
|
|
let screenshotIndex = 0;
|
|
|
|
async function snap(page: Page, name: string) {
|
|
screenshotIndex++;
|
|
const filename = `${String(screenshotIndex).padStart(2, "0")}-${name}.png`;
|
|
await page.screenshot({ path: path.join(SCREENSHOT_DIR, filename) });
|
|
}
|
|
|
|
async function login(page: Page) {
|
|
await page.goto("/login");
|
|
await page.waitForTimeout(2000);
|
|
await page.fill('input[placeholder*="username" i]', "admin");
|
|
await page.fill('input[placeholder*="password" i]', "admin");
|
|
await page.click('button:has-text("Login")');
|
|
await page.waitForURL("**/", { timeout: 10000 }).catch(() => {});
|
|
await page.waitForTimeout(2000);
|
|
}
|
|
|
|
async function createNewDocument(page: Page) {
|
|
const newDocBtn = page.getByText("New Document");
|
|
if (await newDocBtn.isVisible().catch(() => false)) {
|
|
await newDocBtn.click();
|
|
await page.waitForTimeout(500);
|
|
await page.locator('button:has-text("Create")').click();
|
|
await page.waitForTimeout(2000);
|
|
}
|
|
}
|
|
|
|
async function getCanvasBox(page: Page) {
|
|
const canvas = page.locator("canvas").first();
|
|
const box = await canvas.boundingBox();
|
|
if (!box) throw new Error("Canvas not found");
|
|
return box;
|
|
}
|
|
|
|
test.describe("Image Editor - Full GUI Test Suite", () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
screenshotIndex = 0;
|
|
await login(page);
|
|
});
|
|
|
|
test("01 - Navigation & Layout", async ({ page }) => {
|
|
// Check sidebar has Editor item
|
|
await page.goto("/");
|
|
await page.waitForTimeout(2000);
|
|
const editorLink = page.locator('a:has-text("Editor")');
|
|
await expect(editorLink).toBeVisible();
|
|
await snap(page, "sidebar-with-editor");
|
|
|
|
// Navigate to editor
|
|
await editorLink.click();
|
|
await page.waitForTimeout(2000);
|
|
await expect(page).toHaveURL(/\/editor/);
|
|
await snap(page, "editor-welcome-screen");
|
|
|
|
// Verify four-zone layout elements
|
|
const toolbar = page.locator('[data-tool="move"]');
|
|
await expect(toolbar).toBeVisible();
|
|
|
|
const layersTab = page.locator('[data-testid="tab-layers"]');
|
|
await expect(layersTab).toBeVisible();
|
|
|
|
const adjustmentsTab = page.locator('[data-testid="tab-adjustments"]');
|
|
await expect(adjustmentsTab).toBeVisible();
|
|
|
|
const historyTab = page.locator('[data-testid="tab-history"]');
|
|
await expect(historyTab).toBeVisible();
|
|
});
|
|
|
|
test("02 - Welcome Screen & New Document", async ({ page }) => {
|
|
await page.goto("/editor");
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Welcome screen visible (the visible <h2>; an sr-only <h1> shares the text)
|
|
await expect(page.getByRole("heading", { level: 2, name: "Image Editor" })).toBeVisible();
|
|
await expect(page.getByText("Open Image")).toBeVisible();
|
|
await expect(page.getByText("New Document")).toBeVisible();
|
|
await snap(page, "welcome-screen");
|
|
|
|
// Open new document dialog
|
|
await page.getByText("New Document").click();
|
|
await page.waitForTimeout(500);
|
|
await expect(page.getByText("Preset")).toBeVisible();
|
|
await snap(page, "new-doc-dialog");
|
|
|
|
// Change preset to Instagram
|
|
await page.locator("select").first().selectOption("1080x1080 (Instagram)");
|
|
await page.waitForTimeout(300);
|
|
await snap(page, "new-doc-instagram-preset");
|
|
|
|
// Select transparent background
|
|
await page.getByText("Transparent").click();
|
|
await page.waitForTimeout(300);
|
|
await snap(page, "new-doc-transparent");
|
|
|
|
// Create the document
|
|
await page.locator('button:has-text("Create")').click();
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Canvas should be visible with checkerboard (transparent)
|
|
const canvas = page.locator("canvas").first();
|
|
await expect(canvas).toBeVisible();
|
|
await snap(page, "canvas-after-create");
|
|
});
|
|
|
|
test("03 - Brush Tool", async ({ page }) => {
|
|
await page.goto("/editor");
|
|
await page.waitForTimeout(2000);
|
|
await createNewDocument(page);
|
|
|
|
// Activate brush via toolbar
|
|
await page.locator('[data-tool="brush"]').click();
|
|
await page.waitForTimeout(300);
|
|
await expect(page.locator('[data-tool-active="true"]')).toHaveAttribute("data-tool", "brush");
|
|
await snap(page, "brush-tool-active");
|
|
|
|
// Verify options bar shows size and opacity
|
|
await expect(page.getByText("Size", { exact: true }).first()).toBeVisible();
|
|
await expect(page.getByText("Opacity", { exact: true }).first()).toBeVisible();
|
|
|
|
// Draw a brush stroke
|
|
const box = await getCanvasBox(page);
|
|
const before = await page.locator("canvas").first().screenshot();
|
|
await page.mouse.move(box.x + 100, box.y + 200);
|
|
await page.mouse.down();
|
|
await page.mouse.move(box.x + 500, box.y + 300, { steps: 20 });
|
|
await page.mouse.up();
|
|
await page.waitForTimeout(500);
|
|
const after = await page.locator("canvas").first().screenshot();
|
|
expect(Buffer.compare(before, after)).not.toBe(0);
|
|
await snap(page, "brush-stroke-drawn");
|
|
|
|
// Change brush size via [ and ] keys
|
|
await page.keyboard.press("]");
|
|
await page.keyboard.press("]");
|
|
await page.keyboard.press("]");
|
|
await page.waitForTimeout(300);
|
|
await snap(page, "brush-size-increased");
|
|
});
|
|
|
|
test("04 - Eraser Tool", async ({ page }) => {
|
|
await page.goto("/editor");
|
|
await page.waitForTimeout(2000);
|
|
await createNewDocument(page);
|
|
|
|
// Draw something first with brush
|
|
await page.locator('[data-tool="brush"]').click();
|
|
await page.waitForTimeout(300);
|
|
const box = await getCanvasBox(page);
|
|
await page.mouse.move(box.x + 200, box.y + 200);
|
|
await page.mouse.down();
|
|
await page.mouse.move(box.x + 500, box.y + 200, { steps: 15 });
|
|
await page.mouse.up();
|
|
await page.waitForTimeout(500);
|
|
|
|
// Activate eraser via E key
|
|
await page.keyboard.press("e");
|
|
await page.waitForTimeout(300);
|
|
await expect(page.locator('[data-tool-active="true"]')).toHaveAttribute("data-tool", "eraser");
|
|
await snap(page, "eraser-tool-active");
|
|
|
|
// Erase over the stroke
|
|
await page.mouse.move(box.x + 300, box.y + 190);
|
|
await page.mouse.down();
|
|
await page.mouse.move(box.x + 400, box.y + 210, { steps: 10 });
|
|
await page.mouse.up();
|
|
await page.waitForTimeout(500);
|
|
await snap(page, "eraser-stroke");
|
|
});
|
|
|
|
test("05 - Shape Tools", async ({ page }) => {
|
|
await page.goto("/editor");
|
|
await page.waitForTimeout(2000);
|
|
await createNewDocument(page);
|
|
const box = await getCanvasBox(page);
|
|
|
|
// Rectangle
|
|
await page.locator('[data-tool="shape-rect"]').click();
|
|
await page.waitForTimeout(300);
|
|
await snap(page, "shape-rect-options");
|
|
|
|
await page.mouse.move(box.x + 100, box.y + 100);
|
|
await page.mouse.down();
|
|
await page.mouse.move(box.x + 300, box.y + 250, { steps: 10 });
|
|
await page.mouse.up();
|
|
await page.waitForTimeout(500);
|
|
await snap(page, "shape-rect-drawn");
|
|
|
|
// Switch to ellipse via shape dropdown if available
|
|
const shapeDropdown = page.locator("select").first();
|
|
if (await shapeDropdown.isVisible().catch(() => false)) {
|
|
await shapeDropdown.selectOption("Ellipse");
|
|
await page.waitForTimeout(300);
|
|
}
|
|
|
|
// Draw another shape
|
|
await page.mouse.move(box.x + 400, box.y + 100);
|
|
await page.mouse.down();
|
|
await page.mouse.move(box.x + 600, box.y + 250, { steps: 10 });
|
|
await page.mouse.up();
|
|
await page.waitForTimeout(500);
|
|
await snap(page, "shape-ellipse-drawn");
|
|
|
|
// Select and move with move tool
|
|
await page.locator('[data-tool="move"]').click();
|
|
await page.waitForTimeout(300);
|
|
await page.mouse.click(box.x + 200, box.y + 175);
|
|
await page.waitForTimeout(500);
|
|
await snap(page, "shape-selected-with-handles");
|
|
});
|
|
|
|
test("06 - Text Tool", async ({ page }) => {
|
|
await page.goto("/editor");
|
|
await page.waitForTimeout(2000);
|
|
await createNewDocument(page);
|
|
const box = await getCanvasBox(page);
|
|
|
|
// Activate text tool via T key
|
|
await page.keyboard.press("t");
|
|
await page.waitForTimeout(300);
|
|
await expect(page.locator('[data-tool-active="true"]')).toHaveAttribute("data-tool", "text");
|
|
await snap(page, "text-tool-active");
|
|
|
|
// Click on canvas to place text
|
|
await page.mouse.click(box.x + 200, box.y + 200);
|
|
await page.waitForTimeout(1000);
|
|
|
|
// Type some text (textarea should be active)
|
|
await page.keyboard.type("Hello SnapOtter!");
|
|
await page.waitForTimeout(500);
|
|
await snap(page, "text-typing");
|
|
|
|
// Click away to finalize
|
|
await page.mouse.click(box.x + 50, box.y + 50);
|
|
await page.waitForTimeout(500);
|
|
await snap(page, "text-finalized");
|
|
});
|
|
|
|
test("07 - Crop Tool", async ({ page }) => {
|
|
await page.goto("/editor");
|
|
await page.waitForTimeout(2000);
|
|
await createNewDocument(page);
|
|
const _box = await getCanvasBox(page);
|
|
|
|
// Activate crop via C key
|
|
await page.keyboard.press("c");
|
|
await page.waitForTimeout(500);
|
|
await expect(page.locator('[data-tool-active="true"]')).toHaveAttribute("data-tool", "crop");
|
|
await snap(page, "crop-tool-active");
|
|
|
|
// Verify crop options show aspect ratio
|
|
const _cropOptions = page.getByText(/Free|1:1|4:3|16:9/);
|
|
await snap(page, "crop-options-bar");
|
|
});
|
|
|
|
test("08 - Layer System", async ({ page }) => {
|
|
await page.goto("/editor");
|
|
await page.waitForTimeout(2000);
|
|
await createNewDocument(page);
|
|
|
|
// Layers tab should be active by default
|
|
await expect(page.locator('[data-testid="tab-layers"]')).toBeVisible();
|
|
await page.locator('[data-testid="tab-layers"]').click();
|
|
await page.waitForTimeout(300);
|
|
|
|
// Should see Layer 1
|
|
await expect(page.getByText("Layer 1")).toBeVisible();
|
|
await snap(page, "layers-panel-default");
|
|
|
|
// Add a new layer via + button
|
|
const _addLayerBtn = page
|
|
.locator("button")
|
|
.filter({ has: page.locator("svg") })
|
|
.locator("xpath=//button[contains(@class, 'items-center')]")
|
|
.first();
|
|
// Try clicking the + icon at bottom of layers panel
|
|
const _plusButtons = page
|
|
.locator('[data-testid="tab-layers"]')
|
|
.locator("..")
|
|
.locator("..")
|
|
.locator("button");
|
|
await snap(page, "layers-before-add");
|
|
|
|
// Use keyboard shortcut to add layer
|
|
await page.keyboard.press("Control+Shift+n");
|
|
await page.waitForTimeout(500);
|
|
await snap(page, "layers-after-add");
|
|
|
|
// Check blend mode dropdown
|
|
const blendDropdown = page.locator("select").filter({ hasText: /Normal/ });
|
|
if (await blendDropdown.isVisible().catch(() => false)) {
|
|
await blendDropdown.click();
|
|
await page.waitForTimeout(300);
|
|
await snap(page, "blend-mode-dropdown");
|
|
}
|
|
|
|
// Toggle layer visibility
|
|
const eyeIcons = page.locator(
|
|
'[aria-label*="visibility" i], [aria-label*="toggle" i], [title*="visibility" i]',
|
|
);
|
|
if (
|
|
await eyeIcons
|
|
.first()
|
|
.isVisible()
|
|
.catch(() => false)
|
|
) {
|
|
await eyeIcons.first().click();
|
|
await page.waitForTimeout(300);
|
|
await snap(page, "layer-visibility-toggled");
|
|
}
|
|
});
|
|
|
|
test("09 - Color System", async ({ page }) => {
|
|
await page.goto("/editor");
|
|
await page.waitForTimeout(2000);
|
|
await createNewDocument(page);
|
|
|
|
// Foreground/background swatches should be visible
|
|
await expect(page.getByText("Foreground")).toBeVisible();
|
|
await snap(page, "color-panel-default");
|
|
|
|
// Click foreground swatch to open picker
|
|
const _fgSwatch = page.locator('[data-testid="color-foreground"]').or(
|
|
page
|
|
.locator("button")
|
|
.filter({ hasText: /Foreground/ })
|
|
.locator("..")
|
|
.locator("button")
|
|
.first(),
|
|
);
|
|
|
|
// Try clicking the color swatch area
|
|
const colorArea = page.getByText("Foreground").locator("..").locator("button").first();
|
|
if (await colorArea.isVisible().catch(() => false)) {
|
|
await colorArea.click();
|
|
await page.waitForTimeout(500);
|
|
await snap(page, "color-picker-open");
|
|
}
|
|
|
|
// Test D key resets to black/white
|
|
await page.keyboard.press("d");
|
|
await page.waitForTimeout(300);
|
|
await snap(page, "colors-reset-to-default");
|
|
|
|
// Test X key swaps colors
|
|
await page.keyboard.press("x");
|
|
await page.waitForTimeout(300);
|
|
await snap(page, "colors-swapped");
|
|
});
|
|
|
|
test("10 - Adjustments Panel", async ({ page }) => {
|
|
await page.goto("/editor");
|
|
await page.waitForTimeout(2000);
|
|
await createNewDocument(page);
|
|
|
|
// Switch to Adjustments tab
|
|
await page.locator('[data-testid="tab-adjustments"]').click();
|
|
await page.waitForTimeout(500);
|
|
await snap(page, "adjustments-panel");
|
|
|
|
// Verify adjustment sliders present
|
|
await expect(page.getByText("Brightness", { exact: true })).toBeVisible();
|
|
await expect(page.getByText("Contrast", { exact: true })).toBeVisible();
|
|
await expect(page.getByText("Saturation", { exact: true })).toBeVisible();
|
|
await snap(page, "adjustments-sliders");
|
|
|
|
// Verify auto buttons
|
|
await expect(page.getByText("Auto Tone")).toBeVisible();
|
|
await expect(page.getByText("Auto Contrast")).toBeVisible();
|
|
await snap(page, "auto-buttons");
|
|
|
|
// Scroll to see levels section
|
|
const panel = page
|
|
.locator('[data-testid="tab-adjustments"]')
|
|
.locator("..")
|
|
.locator("..")
|
|
.locator("div.overflow-y-auto");
|
|
if (await panel.isVisible().catch(() => false)) {
|
|
await panel.evaluate((el) => (el.scrollTop = 400));
|
|
await page.waitForTimeout(500);
|
|
await snap(page, "levels-section");
|
|
|
|
await panel.evaluate((el) => (el.scrollTop = 800));
|
|
await page.waitForTimeout(500);
|
|
await snap(page, "curves-section");
|
|
|
|
await panel.evaluate((el) => (el.scrollTop = 1200));
|
|
await page.waitForTimeout(500);
|
|
await snap(page, "filters-section");
|
|
}
|
|
});
|
|
|
|
test("11 - History & Undo/Redo", async ({ page }) => {
|
|
await page.goto("/editor");
|
|
await page.waitForTimeout(2000);
|
|
await createNewDocument(page);
|
|
const box = await getCanvasBox(page);
|
|
|
|
// Draw a shape
|
|
await page.locator('[data-tool="shape-rect"]').click();
|
|
await page.waitForTimeout(300);
|
|
await page.mouse.move(box.x + 100, box.y + 100);
|
|
await page.mouse.down();
|
|
await page.mouse.move(box.x + 300, box.y + 250, { steps: 10 });
|
|
await page.mouse.up();
|
|
await page.waitForTimeout(500);
|
|
|
|
// Draw a brush stroke
|
|
await page.keyboard.press("b");
|
|
await page.waitForTimeout(300);
|
|
await page.mouse.move(box.x + 400, box.y + 150);
|
|
await page.mouse.down();
|
|
await page.mouse.move(box.x + 600, box.y + 300, { steps: 15 });
|
|
await page.mouse.up();
|
|
await page.waitForTimeout(500);
|
|
|
|
// Open history panel
|
|
await page.locator('[data-testid="tab-history"]').click();
|
|
await page.waitForTimeout(500);
|
|
await snap(page, "history-with-actions");
|
|
|
|
// Verify history entries exist
|
|
const _historyEntries = page
|
|
.locator('[data-testid="tab-history"]')
|
|
.locator("..")
|
|
.locator("..")
|
|
.locator("div.overflow-y-auto");
|
|
await snap(page, "history-panel-entries");
|
|
|
|
// Undo with Ctrl+Z
|
|
const beforeUndo = await page.locator("canvas").first().screenshot();
|
|
await page.keyboard.press("Control+z");
|
|
await page.waitForTimeout(500);
|
|
const afterUndo = await page.locator("canvas").first().screenshot();
|
|
expect(Buffer.compare(beforeUndo, afterUndo)).not.toBe(0);
|
|
await snap(page, "after-undo");
|
|
|
|
// Redo with Ctrl+Shift+Z
|
|
await page.keyboard.press("Control+Shift+z");
|
|
await page.waitForTimeout(500);
|
|
await snap(page, "after-redo");
|
|
});
|
|
|
|
test("12 - Keyboard Shortcuts", async ({ page }) => {
|
|
await page.goto("/editor");
|
|
await page.waitForTimeout(2000);
|
|
await createNewDocument(page);
|
|
|
|
const toolShortcuts: [string, string][] = [
|
|
["v", "move"],
|
|
["b", "brush"],
|
|
["e", "eraser"],
|
|
["u", "shape-rect"],
|
|
["t", "text"],
|
|
["c", "crop"],
|
|
["i", "eyedropper"],
|
|
["h", "hand"],
|
|
["z", "zoom"],
|
|
["m", "marquee-rect"],
|
|
["g", "fill"],
|
|
];
|
|
|
|
for (const [key, _expectedTool] of toolShortcuts) {
|
|
await page.keyboard.press(key);
|
|
await page.waitForTimeout(200);
|
|
const activeTool = page.locator('[data-tool-active="true"]');
|
|
const toolAttr = await activeTool.getAttribute("data-tool");
|
|
// Just verify the active tool changed (some tools may have different sub-types)
|
|
expect(toolAttr).toBeTruthy();
|
|
}
|
|
await snap(page, "keyboard-shortcuts-tested");
|
|
|
|
// Test D resets colors
|
|
await page.keyboard.press("d");
|
|
await page.waitForTimeout(200);
|
|
|
|
// Test X swaps
|
|
await page.keyboard.press("x");
|
|
await page.waitForTimeout(200);
|
|
await snap(page, "color-shortcuts-tested");
|
|
});
|
|
|
|
test("13 - Right Panel Toggle", async ({ page }) => {
|
|
await page.goto("/editor");
|
|
await page.waitForTimeout(2000);
|
|
await createNewDocument(page);
|
|
|
|
// Panel should be visible
|
|
await expect(page.locator('[data-testid="tab-layers"]')).toBeVisible();
|
|
await snap(page, "panel-visible");
|
|
|
|
// Press Tab to toggle panel
|
|
await page.keyboard.press("Tab");
|
|
await page.waitForTimeout(500);
|
|
await snap(page, "panel-collapsed");
|
|
|
|
// Press Tab again to restore
|
|
await page.keyboard.press("Tab");
|
|
await page.waitForTimeout(500);
|
|
await snap(page, "panel-restored");
|
|
});
|
|
|
|
test("14 - Multiple Shapes & Move", async ({ page }) => {
|
|
await page.goto("/editor");
|
|
await page.waitForTimeout(2000);
|
|
await createNewDocument(page);
|
|
const box = await getCanvasBox(page);
|
|
|
|
// Draw 3 shapes
|
|
await page.locator('[data-tool="shape-rect"]').click();
|
|
await page.waitForTimeout(300);
|
|
|
|
// Shape 1
|
|
await page.mouse.move(box.x + 50, box.y + 50);
|
|
await page.mouse.down();
|
|
await page.mouse.move(box.x + 200, box.y + 150, { steps: 5 });
|
|
await page.mouse.up();
|
|
await page.waitForTimeout(300);
|
|
|
|
// Shape 2
|
|
await page.mouse.move(box.x + 250, box.y + 50);
|
|
await page.mouse.down();
|
|
await page.mouse.move(box.x + 400, box.y + 150, { steps: 5 });
|
|
await page.mouse.up();
|
|
await page.waitForTimeout(300);
|
|
|
|
// Shape 3
|
|
await page.mouse.move(box.x + 450, box.y + 50);
|
|
await page.mouse.down();
|
|
await page.mouse.move(box.x + 600, box.y + 150, { steps: 5 });
|
|
await page.mouse.up();
|
|
await page.waitForTimeout(300);
|
|
await snap(page, "three-shapes-drawn");
|
|
|
|
// Switch to move tool and select shape
|
|
await page.keyboard.press("v");
|
|
await page.waitForTimeout(300);
|
|
await page.mouse.click(box.x + 125, box.y + 100);
|
|
await page.waitForTimeout(500);
|
|
await snap(page, "shape-selected");
|
|
|
|
// Drag it
|
|
await page.mouse.move(box.x + 125, box.y + 100);
|
|
await page.mouse.down();
|
|
await page.mouse.move(box.x + 125, box.y + 300, { steps: 10 });
|
|
await page.mouse.up();
|
|
await page.waitForTimeout(500);
|
|
await snap(page, "shape-moved");
|
|
|
|
// Delete with Delete key
|
|
await page.mouse.click(box.x + 325, box.y + 100);
|
|
await page.waitForTimeout(300);
|
|
await page.keyboard.press("Delete");
|
|
await page.waitForTimeout(500);
|
|
await snap(page, "shape-deleted");
|
|
});
|
|
|
|
test("15 - Export Dialog", async ({ page }) => {
|
|
await page.goto("/editor");
|
|
await page.waitForTimeout(2000);
|
|
await createNewDocument(page);
|
|
const box = await getCanvasBox(page);
|
|
|
|
// Draw something
|
|
await page.locator('[data-tool="shape-rect"]').click();
|
|
await page.waitForTimeout(300);
|
|
await page.mouse.move(box.x + 200, box.y + 150);
|
|
await page.mouse.down();
|
|
await page.mouse.move(box.x + 500, box.y + 350, { steps: 10 });
|
|
await page.mouse.up();
|
|
await page.waitForTimeout(500);
|
|
|
|
// Open export dialog via Ctrl+Shift+S
|
|
await page.keyboard.press("Control+Shift+s");
|
|
await page.waitForTimeout(1000);
|
|
await snap(page, "export-dialog");
|
|
|
|
// Check format options
|
|
const pngBtn = page.getByText("PNG");
|
|
const jpegBtn = page.getByText("JPEG");
|
|
const webpBtn = page.getByText("WebP");
|
|
|
|
if (await pngBtn.isVisible().catch(() => false)) {
|
|
await expect(pngBtn).toBeVisible();
|
|
await expect(jpegBtn).toBeVisible();
|
|
await expect(webpBtn).toBeVisible();
|
|
|
|
// Select JPEG to see quality slider
|
|
await jpegBtn.click();
|
|
await page.waitForTimeout(300);
|
|
await snap(page, "export-jpeg-quality");
|
|
}
|
|
});
|
|
|
|
test("16 - Clone Stamp Tool", async ({ page }) => {
|
|
await page.goto("/editor");
|
|
await page.waitForTimeout(2000);
|
|
await createNewDocument(page);
|
|
|
|
// Activate clone stamp via S key
|
|
await page.keyboard.press("s");
|
|
await page.waitForTimeout(300);
|
|
await expect(page.locator('[data-tool-active="true"]')).toHaveAttribute(
|
|
"data-tool",
|
|
"clone-stamp",
|
|
);
|
|
await snap(page, "clone-stamp-active");
|
|
|
|
// Verify options bar
|
|
await expect(page.getByText("Size")).toBeVisible();
|
|
});
|
|
|
|
test("17 - Dodge/Burn/Sponge Tool", async ({ page }) => {
|
|
await page.goto("/editor");
|
|
await page.waitForTimeout(2000);
|
|
await createNewDocument(page);
|
|
|
|
// Activate dodge via O key
|
|
await page.keyboard.press("o");
|
|
await page.waitForTimeout(300);
|
|
await snap(page, "dodge-tool-active");
|
|
|
|
// Check options show range and exposure
|
|
const _optionsBar = page
|
|
.locator("div")
|
|
.filter({ hasText: /Range|Exposure|Dodge|Burn|Sponge/ })
|
|
.first();
|
|
await snap(page, "dodge-burn-options");
|
|
});
|
|
|
|
test("18 - Selection Tools", async ({ page }) => {
|
|
await page.goto("/editor");
|
|
await page.waitForTimeout(2000);
|
|
await createNewDocument(page);
|
|
const box = await getCanvasBox(page);
|
|
|
|
// Marquee selection via M key
|
|
await page.keyboard.press("m");
|
|
await page.waitForTimeout(300);
|
|
await snap(page, "marquee-tool-active");
|
|
|
|
// Draw a selection
|
|
await page.mouse.move(box.x + 100, box.y + 100);
|
|
await page.mouse.down();
|
|
await page.mouse.move(box.x + 400, box.y + 300, { steps: 10 });
|
|
await page.mouse.up();
|
|
await page.waitForTimeout(500);
|
|
await snap(page, "marquee-selection-drawn");
|
|
|
|
// Lasso via L key
|
|
await page.keyboard.press("l");
|
|
await page.waitForTimeout(300);
|
|
await snap(page, "lasso-tool-active");
|
|
|
|
// Magic wand via W key
|
|
await page.keyboard.press("w");
|
|
await page.waitForTimeout(300);
|
|
await snap(page, "magic-wand-active");
|
|
});
|
|
|
|
test("19 - Gradient & Fill Tools", async ({ page }) => {
|
|
await page.goto("/editor");
|
|
await page.waitForTimeout(2000);
|
|
await createNewDocument(page);
|
|
|
|
// Fill tool via G key
|
|
await page.keyboard.press("g");
|
|
await page.waitForTimeout(300);
|
|
await snap(page, "fill-tool-active");
|
|
|
|
// Check tolerance option
|
|
await expect(page.getByText("Tolerance")).toBeVisible();
|
|
await snap(page, "fill-options");
|
|
});
|
|
|
|
test("20 - Navigator Panel", async ({ page }) => {
|
|
await page.goto("/editor");
|
|
await page.waitForTimeout(2000);
|
|
await createNewDocument(page);
|
|
|
|
// Navigator should show minimap
|
|
await page.waitForTimeout(1000);
|
|
await snap(page, "navigator-minimap");
|
|
});
|
|
|
|
test("21 - Status Bar", async ({ page }) => {
|
|
await page.goto("/editor");
|
|
await page.waitForTimeout(2000);
|
|
await createNewDocument(page);
|
|
|
|
// Verify zoom percentage
|
|
const zoomInput = page.locator('[data-testid="status-zoom"] input');
|
|
if (await zoomInput.isVisible().catch(() => false)) {
|
|
const zoomValue = await zoomInput.inputValue();
|
|
expect(Number(zoomValue)).toBeGreaterThan(0);
|
|
}
|
|
|
|
// Verify dimensions
|
|
const dimensions = page.locator('[data-testid="status-dimensions"]');
|
|
if (await dimensions.isVisible().catch(() => false)) {
|
|
const text = await dimensions.textContent();
|
|
expect(text).toContain("1920");
|
|
expect(text).toContain("1080");
|
|
}
|
|
await snap(page, "status-bar");
|
|
});
|
|
});
|