Arx / mobile /test /e2e.cjs
umer-wasim's picture
Security assistant chat as the front page (Nox-style, WebLLM on device, grounded in the Devseis knowledge base, Run audit from chat); phone check moved to phone.html
f1bdfc3 verified
Raw History Blame Contribute Delete
4.02 kB
// 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 <ios|android> <out.json>
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();
});