// End-to-end test of the phone check in a phone-sized Electron window with a phone user agent. // Usage: (serve the repo root on :8765) npx electron mobile/test/e2e.cjs const { app, BrowserWindow } = require('electron'); const fs = require('node:fs'); const UA = { ios: 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Mobile/15E148 Safari/604.1', android: 'Mozilla/5.0 (Linux; Android 15; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Mobile Safari/537.36', }; const [os, out] = process.argv.slice(-2); const PICK = `(() => { const box = document.getElementById('q-answers'); const buttons = [...box.querySelectorAll('button')]; const cont = buttons.find(b => b.textContent === 'Continue'); const text = box.querySelector('input[type=text]'), month = box.querySelector('input[type=month]'); const multi = box.querySelectorAll('input[type=checkbox]'); if (text) { if (!text.value) text.value = '${os === 'ios' ? '26.0.1' : '15'}'; cont.click(); return 'version'; } if (month) { month.value = '2026-06'; cont.click(); return 'month'; } if (multi.length) { multi[0].checked = true; multi[7].checked = true; cont.click(); return 'ai'; } (buttons.length > 2 ? buttons[1] : buttons[0]).click(); return 'choice'; })()`; if (!process.env.AI) app.disableHardwareAcceleration(); // AI=1: keep the GPU for WebLLM if (process.env.AI) app.commandLine.appendSwitch('enable-unsafe-webgpu'); app.whenReady().then(async () => { const win = new BrowserWindow({ show: false, width: 390, height: 844 }); win.webContents.setUserAgent(UA[os]); win.webContents.on('console-message', (e, level, message) => { if (level >= 2) console.log('[page]', message); }); const js = code => win.webContents.executeJavaScript(code); const until = async (code, ms = 60000) => { const t = Date.now(); while (!(await js(code))) { if (Date.now() - t > ms) throw new Error(`timeout: ${code}`); await new Promise(r => setTimeout(r, 200)); } }; try { await win.webContents.session.clearCache(); await win.loadURL(`${process.env.BASE_URL || 'http://127.0.0.1:8765/'}phone.html?test`); await until(`!document.getElementById('start').disabled`); const device = await js(`document.getElementById('device-card').innerText`); await js(`document.getElementById('start').click()`); await js(`document.getElementById('use-ai').checked = ${Boolean(process.env.AI)}; document.getElementById('consent-ok').click()`); let asked = []; for (let i = 0; i < 60; i++) { const inQuestions = await js(`!document.getElementById('s-question').classList.contains('hidden')`); if (!inQuestions) break; const q = await js(`document.getElementById('q-text').textContent`); asked.push(q); // Pick a mix of answers: the version as detected (or typed), a month, two AI apps, otherwise the 2nd option. await js(PICK); await new Promise(r => setTimeout(r, 50)); } await until(`!document.getElementById('s-done').classList.contains('hidden')`, process.env.AI ? 3600000 : 120000); const chip = await js(`document.getElementById('model-chip').textContent`); const evidence = await js(`window.__devseisState.report.evidenceText`); const result = await js(`document.getElementById('result').innerText`); const pdfSize = await js(`window.__devseisMakePdf().then(b => b.size)`); console.log('handoff:', await js(`(document.getElementById('ask-assistant')||{}).href + ' stored=' + !!localStorage.getItem('devseis.lastAudit')`)); const html = await js(`window.__devseisState.report.html`); fs.writeFileSync(out, evidence); fs.writeFileSync(out.replace(/\.json$/, '.html'), html); console.log(JSON.stringify({ os, device, model: chip, questions: asked.length, pdfBytes: pdfSize, result: result.slice(0, 900) }, null, 1)); } catch (error) { console.error('E2E FAILED:', error.message); process.exitCode = 1; } app.quit(); });