const { chromium } = require('playwright'); const fs = require('fs'); const BASE = process.env.PLAYWRIGHT_BASE_URL || 'http://192.168.0.251:13000'; const EMAIL = process.env.VITE_TEST_TEACHER_EMAIL || 'physics@greenfieldacademy.test'; const PASSWORD = process.env.VITE_TEST_TEACHER_PASSWORD || process.env.SEED_TEACHER_PASSWORD; const TEMPLATE_ID = process.env.TEMPLATE_ID || '31d92cf3-9bbd-4a7e-b2dc-b37f8b69bc34'; const OUT = process.env.OUT || '/out'; async function run(mode) { const browser = await chromium.launch({ headless: true }); const context = await browser.newContext({ colorScheme: mode, viewport: { width: 1440, height: 980 } }); const page = await context.newPage(); const messages = []; page.on('console', msg => messages.push(`${msg.type()}: ${msg.text()}`)); page.on('pageerror', err => messages.push(`pageerror: ${err.message}`)); await page.goto(`${BASE}/login`, { waitUntil: 'domcontentloaded' }); if (await page.getByLabel('Email').count()) { if (!PASSWORD) throw new Error('missing password env'); await page.getByLabel('Email').fill(EMAIL); await page.getByLabel('Password').fill(PASSWORD); await page.getByRole('button', { name: 'Login' }).click(); await page.waitForURL(url => /\/dashboard|\/exam-marker|\/node\//.test(url.pathname), { timeout: 20000 }); } await page.goto(`${BASE}/exam-marker/${TEMPLATE_ID}/setup`, { waitUntil: 'domcontentloaded' }); await page.waitForSelector('[data-testid="exam-template-setup-canvas"]', { timeout: 20000 }); await page.waitForTimeout(3500); const text = await page.locator('body').innerText(); const checks = { hasBoundaryTool: text.includes('Boundary'), hasPartTool: text.includes('Part'), hasResponseTool: text.includes('Response'), hasContextTool: text.includes('Context'), hasQuestionNumberTool: text.includes('Q Number'), hasMarkAreaTool: text.includes('Mark Area'), hasReferenceTool: text.includes('Reference'), hasFurnitureTool: text.includes('Furniture'), hasHintPanel: text.includes('Setup guide') && text.includes('Boundary pairing preview'), hasMultiPageCopy: text.includes('later page') || text.includes('multi-page'), hasPdfStatus: text.includes('PDF backdrop:'), crashOverlay: text.includes('Template setup canvas crashed'), }; await page.screenshot({ path: `${OUT}/s4-9c-${mode}.png`, fullPage: true }); await browser.close(); return { mode, url: `${BASE}/exam-marker/${TEMPLATE_ID}/setup`, checks, consoleMessages: messages }; } (async () => { fs.mkdirSync(OUT, { recursive: true }); const results = []; for (const mode of ['light', 'dark']) results.push(await run(mode)); fs.writeFileSync(`${OUT}/s4-9c-smoke.json`, JSON.stringify(results, null, 2)); const failures = results.flatMap(r => Object.entries(r.checks).filter(([k,v]) => k === 'crashOverlay' ? v : !v).map(([k]) => `${r.mode}:${k}`)); const consoleErrors = results.flatMap(r => r.consoleMessages.filter(m => /^(error|pageerror):/i.test(m) && !/Failed to load resource/i.test(m))); console.log(JSON.stringify({ results, failures, consoleErrors }, null, 2)); if (failures.length || consoleErrors.length) process.exit(1); })().catch(err => { console.error(err); process.exit(1); });