2586fdb135
- live-mask.spec.ts: replace green-overlay avgAlpha assertions with
checkerboard + slab pixel detection; add green-overlay color rejection
check; fix hasColorVariance false-positive with monochrome test image
- mask-ux.spec.ts: rename test to match new rendering (no green overlay)
- playwright.config.ts: set baseURL when webServer is used so page.goto('/')
resolves correctly in local dev
Fixes: issue #55 mask preview
113 lines
4.2 KiB
TypeScript
113 lines
4.2 KiB
TypeScript
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 renders — canvas has content, not blank', 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);
|
|
});
|
|
});
|