Files
2026-10-13 22:27:11 -06:00

63 lines
5.0 KiB
JavaScript

// Run: PLAYWRIGHT_MODULE=/path/to/playwright node tests/compare-browser.cjs
// Isolated, read-only API harness. Never writes to ComfyUI or library state.
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const http = require('node:http');
const { chromium } = require(process.env.PLAYWRIGHT_MODULE || 'playwright');
const root = path.resolve(__dirname, '..');
const scratch = process.env.COMPARE_QA_DIR || path.join(process.env.LOCALAPPDATA || root, 'hermes/cache/scratch/image-browser-compare');
fs.mkdirSync(scratch, { recursive: true });
const images = ['A.png', 'B.png', 'Portrait.png'].map(name => ({name, path: name, folder_id: 'default', is_favorite: false}));
const fixture = name => {
const [w,h] = name === 'Portrait.png' ? [480,800] : name === 'B.png' ? [1280,960] : [640,480];
return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 640 480"><rect width="640" height="480" fill="#283b52"/><path d="M0 0L640 480M0 480L640 0" stroke="#eeeeee" stroke-width="8"/><rect x="240" y="180" width="160" height="120" fill="${name === 'B.png' ? '#d95757' : '#45ac90'}"/></svg>`;
};
const server = http.createServer((req,res) => {
const url = new URL(req.url, 'http://localhost');
if(req.method !== 'GET') { res.writeHead(405); res.end('Read only'); return; }
if(url.pathname === '/gemini-image-browser/ui') { res.setHeader('Content-Type','text/html'); res.end(fs.readFileSync(path.join(root,'ui.html'))); }
else if(url.pathname === '/gemini-image-browser/js') { res.setHeader('Content-Type','text/javascript'); res.end(fs.readFileSync(path.join(root,'browser.js'))); }
else if(url.pathname === '/gemini-image-browser/list') { res.setHeader('Content-Type','application/json'); res.end(JSON.stringify({images, has_more:false,total:images.length, folders:[{id:'default',name:'Fixtures',path:'read-only fixtures'}]})); }
else if(url.pathname === '/gemini-image-browser/metadata') { res.setHeader('Content-Type','application/json');res.end('{}'); }
else if(url.pathname === '/gemini-image-browser/thumbnail') { res.setHeader('Content-Type','image/svg+xml');res.end(fixture(url.searchParams.get('filename'))); }
else { res.writeHead(404);res.end(); }
});
(async () => {
await new Promise(resolve => server.listen(0,'127.0.0.1',resolve));
const browser = await chromium.launch({headless:true, ...(process.env.COMPARE_BROWSER ? {executablePath:process.env.COMPARE_BROWSER} : {})});
try {
const page = await browser.newPage({viewport:{width:1280,height:900}});
const errors = [];page.on('pageerror', error => errors.push(error.message));
await page.goto(`http://127.0.0.1:${server.address().port}/gemini-image-browser/ui`);
await page.locator('.image-card img').first().waitFor();
await page.locator('#compareModeBtn').click();
await page.locator('.image-card img').nth(0).click();
await page.locator('.image-card img').nth(1).click();
await page.locator('#compareBtn').click();
assert.equal(await page.locator('[data-compare-view]').count(),4,'compare exposes Wipe, Side by side, Difference and Overlay controls');
console.log('PASS comparison exposes four visible modes');
await page.locator('[data-compare-view="side-by-side"]').click();
const stage = page.locator('#compareImageContainer');
await page.evaluate(() => document.getElementById('compareImageContainer').addEventListener('wheel', event => { window.observedWheel = {x:event.clientX,y:event.clientY}; }));
const box = await stage.boundingBox();
await page.mouse.move(box.x + box.width * .17, box.y + box.height * .35);
await page.mouse.wheel(0,-120);
await page.waitForFunction(() => document.getElementById('compareZoomLevel').textContent === '110%');
const anchored = await page.evaluate(() => {
const stage = document.getElementById('compareImageContainer');
const rect = stage.getBoundingClientRect();
const width = stage.clientWidth / 2;
const x = (window.observedWheel.x - rect.left) * stage.offsetWidth / rect.width - stage.clientLeft;
const y = (window.observedWheel.y - rect.top) * stage.offsetHeight / rect.height - stage.clientTop;
const matrix = new DOMMatrix(document.querySelector('.compare-img-front').style.transform);
return {actualX:matrix.e,actualY:matrix.f,expectedX:-(x-width/2)*.1,expectedY:-(y-stage.clientHeight/2)*.1};
});
assert.ok(Math.abs(anchored.actualX-anchored.expectedX)<.001 && Math.abs(anchored.actualY-anchored.expectedY)<.001, `wheel keeps corresponding image point under pointer: ${JSON.stringify(anchored)}`);
console.log('PASS pointer-anchored side-by-side zoom');
const additional = await require('./compare-acceptance.cjs')(page,browser,scratch);
assert.deepEqual(errors,[],'no page errors');
console.log(`RESULT ${additional+2} acceptance checks passed; 0 page errors; screenshots: ${scratch}`);
} finally {await browser.close();server.close();}
})().catch(error => {console.error(error);server.close();process.exitCode=1;});