import { spawn } from 'node:child_process' import { mkdir, writeFile } from 'node:fs/promises' import path from 'node:path' import { fileURLToPath } from 'node:url' import { chromium } from '@playwright/test' const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') const output = process.argv[2] if (!output || !path.isAbsolute(output)) throw new Error('An absolute evidence output directory is required') await mkdir(output, { recursive: true }) const port = 5178 const server = spawn(process.execPath, [path.resolve(root, '../../node_modules/vite/bin/vite.js'), '--host', '127.0.0.1', '--port', String(port), '--strictPort'], { cwd: root, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] }) let log = '' const capture = chunk => { log = (log + String(chunk)).slice(-12000) } server.stdout.on('data', capture); server.stderr.on('data', capture) let browser try { let ready = false for (let i = 0; i < 100; i++) { if (server.exitCode !== null) throw new Error('Preview server exited: ' + log) try { ready = (await fetch(`http://127.0.0.1:${port}/visual-fixtures/codex-link-settings.html`)).ok } catch {} if (ready) break await new Promise(resolve => setTimeout(resolve, 300)) } if (!ready) throw new Error('Preview server not ready: ' + log) browser = await chromium.launch({ channel: 'chrome', headless: true }) const evidence = [] for (const width of [360, 390, 414, 1280]) { const page = await browser.newPage({ viewport: { width, height: 900 }, reducedMotion: 'reduce' }) const errors = [] page.on('pageerror', error => errors.push(error.message)) await page.goto(`http://127.0.0.1:${port}/visual-fixtures/codex-link-settings.html`, { waitUntil: 'domcontentloaded', timeout: 90000 }) await page.getByText('Proje sohbeti · örnek kayıt').waitFor({ timeout: 90000 }) await page.evaluate(() => document.fonts.ready) const layout = await page.evaluate(() => ({ width: innerWidth, scroll: document.documentElement.scrollWidth, buttons: [...document.querySelectorAll('button:not([role=switch]), input, select, label[for="codex-link-enabled"], label[for="codex-link-compression"]')].map(element => ({ text: element.textContent || element.id, height: element.getBoundingClientRect().height })) })) if (layout.scroll > width) throw new Error(`Horizontal overflow at ${width}: ${layout.scroll}`) await writeFile(path.join(output, `layout-${width}.json`), JSON.stringify(layout, null, 2) + '\n') await page.screenshot({ path: path.join(output, `codex-link-${width}.png`), fullPage: true }) if (layout.buttons.some(button => button.height < 43.5)) throw new Error(`Small touch target at ${width}: ${JSON.stringify(layout.buttons.filter(button => button.height < 43.5))}`) if (errors.length) throw new Error(errors.join('\n')) // Tap the label as a finger would, then verify the manager-confirmed value. await page.locator('label[for="codex-link-enabled"]').click() await page.waitForFunction(() => document.querySelector('#codex-link-enabled')?.getAttribute('aria-checked') === 'false') await page.getByRole('button', { name: 'Şimdi sıkıştır' }).scrollIntoViewIfNeeded() await page.screenshot({ path: path.join(output, `codex-link-${width}-history.png`), fullPage: true }) evidence.push({ width, horizontal_overflow: false, touch_targets_passed: true, preference_update_passed: true, errors }) await page.close() } await writeFile(path.join(output, 'layout-result.json'), JSON.stringify({ synthetic_fixture: true, real_manager: false, checks: evidence }, null, 2) + '\n') console.log(JSON.stringify({ passed: evidence.length, output })) } finally { await browser?.close() server.kill() await writeFile(path.join(output, 'preview-server.log'), log) }