import { test, expect } from '@playwright/test'; import { resolve, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; import * as fs from 'node:fs'; import * as os from 'node:os'; const __dirname = dirname(fileURLToPath(import.meta.url)); const TEST_IMAGE = resolve(__dirname, 'test-slab.png'); const evidenceDir = resolve(__dirname, '../../test-results/mask-ux-evidence'); function ensureDir(dir: string) { if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); } test.describe('Mask UX timing & overlay', () => { test('overlay is translucent — photo visible under green mask', async ({ page }) => { await page.goto('/'); const fileInput = page.locator('#photo-file-input'); await fileInput.setInputFiles(TEST_IMAGE); await expect(page.locator('h2')).toContainText('Mask & Knobs'); // Wait for the mask canvas to be drawn const maskCanvas = page.locator('.mask-canvas'); await maskCanvas.waitFor({ state: 'visible', timeout: 10000 }); // Canvas should have actual content (not a blank white canvas) const canvasImg = await maskCanvas.screenshot({ type: 'png' }); expect(canvasImg.length).toBeGreaterThan(0); // Screenshot the full mask step area for visual evidence ensureDir(evidenceDir); await page.locator('.mask-step').screenshot({ path: resolve(evidenceDir, 'mask-overlay-translucent.png'), }); console.log('Saved mask overlay screenshot to', resolve(evidenceDir, 'mask-overlay-translucent.png')); }); test('sensitivity knob change is fast via programmatic dispatch', async ({ page }) => { await page.goto('/'); const fileInput = page.locator('#photo-file-input'); await fileInput.setInputFiles(TEST_IMAGE); await expect(page.locator('h2')).toContainText('Mask & Knobs'); const sensitivitySlider = page.locator('input[type="range"]').first(); await sensitivitySlider.waitFor({ state: 'visible', timeout: 10000 }); const initialValue = await sensitivitySlider.inputValue(); const startTime = Date.now(); // Dispatch input + change events programmatically (range inputs don't support fill/setInputValue) await sensitivitySlider.evaluate((el: HTMLInputElement) => { el.value = '0.75'; el.dispatchEvent(new Event('input', { bubbles: true })); el.dispatchEvent(new Event('change', { bubbles: true })); }); const elapsed = Date.now() - startTime; console.log(`Sensitivity knob change elapsed: ${elapsed}ms`); expect(elapsed).toBeLessThan(300); const newValue = await sensitivitySlider.inputValue(); expect(newValue).not.toBe(initialValue); }); test('edge offset knob change is fast via programmatic dispatch', async ({ page }) => { await page.goto('/'); const fileInput = page.locator('#photo-file-input'); await fileInput.setInputFiles(TEST_IMAGE); await expect(page.locator('h2')).toContainText('Mask & Knobs'); const edgeOffsetSlider = page.locator('input[type="range"]').nth(1); await edgeOffsetSlider.waitFor({ state: 'visible', timeout: 10000 }); const initialVal = await edgeOffsetSlider.inputValue(); const startTime = Date.now(); await edgeOffsetSlider.evaluate((el: HTMLInputElement) => { el.value = '5'; el.dispatchEvent(new Event('input', { bubbles: true })); el.dispatchEvent(new Event('change', { bubbles: true })); }); const elapsed = Date.now() - startTime; console.log(`Edge offset knob change elapsed: ${elapsed}ms`); expect(elapsed).toBeLessThan(300); const newVal = await edgeOffsetSlider.inputValue(); expect(newVal).not.toBe(initialVal); }); test('sheet mode switch updates overlay within 1s', async ({ page }) => { await page.goto('/'); const fileInput = page.locator('#photo-file-input'); await fileInput.setInputFiles(TEST_IMAGE); await expect(page.locator('h2')).toContainText('Mask & Knobs'); const sheetSelect = page.locator('.knob-group select'); await sheetSelect.waitFor({ state: 'visible', timeout: 10000 }); const startTime = Date.now(); await sheetSelect.selectOption({ label: 'Green' }); const elapsed = Date.now() - startTime; console.log(`Sheet switch elapsed: ${elapsed}ms`); expect(elapsed).toBeLessThan(1000); }); });