gitoria
All repositories: gitoria
5.2 KB
// tests/firefox.mjs — a REAL Firefox for the gate (mission 033: the creator uses Firefox and saw full page loads).// Headless Firefox driven over WebDriver BiDi (Firefox's own protocol, `--remote-debugging-port`, ws …/session), no// driver or npm package. Host names are mapped to 127.0.0.1 with the pref `network.dns.localDomains` (exact names).// Clicks are REAL pointer input (input.performActions), not element.click().// const ff = await launchFirefox({ port: 8730, hosts: ['gitoria.test', 'alpha.gitoria.test'] });// await ff.goto(url); await ff.click('#navcode'); await ff.waitFor('expr'); await ff.evaluate('expr'); await ff.close();import { spawn } from 'node:child_process';import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';import { tmpdir } from 'node:os';import { join } from 'node:path';const sleep = (ms) => new Promise(r => setTimeout(r, ms));export async function launchFirefox({ port, hosts = [], bin = process.env.HL_FIREFOX || '/usr/bin/firefox' }) {const profile = mkdtempSync(join(tmpdir(), 'gitoria-ff-'));writeFileSync(join(profile, 'user.js'), [`user_pref("network.dns.localDomains", ${JSON.stringify(hosts.join(','))});`,'user_pref("browser.shell.checkDefaultBrowser", false);','user_pref("datareporting.policy.dataSubmissionEnabled", false);','user_pref("toolkit.telemetry.reportingpolicy.firstRun", false);','user_pref("browser.aboutwelcome.enabled", false);','user_pref("network.proxy.type", 0);',].join('\n') + '\n');const proc = spawn(bin, ['--headless', '--no-remote', '--profile', profile, `--remote-debugging-port=${port}`, 'about:blank'], { stdio: ['ignore', 'pipe', 'pipe'] });let out = '';proc.stdout.on('data', d => out += d); proc.stderr.on('data', d => out += d);let ws = null;for (let i = 0; i < 80 && !ws; i++) {await sleep(250);if (!/WebDriver BiDi listening/.test(out)) continue;ws = await new Promise((res) => { const w = new WebSocket(`ws://127.0.0.1:${port}/session`); w.onopen = () => res(w); w.onerror = () => res(null); });}if (!ws) { try { proc.kill('SIGKILL'); } catch {} rmSync(profile, { recursive: true, force: true }); throw new Error('firefox did not open BiDi\n' + out.slice(-800)); }let next = 0; const pending = new Map();const errors = [];ws.onmessage = (ev) => {const m = JSON.parse(ev.data);if (m.id != null && pending.has(m.id)) { const p = pending.get(m.id); pending.delete(m.id); m.type === 'error' ? p.rej(new Error(m.error + ': ' + m.message)) : p.res(m.result); return; }if (m.method === 'log.entryAdded' && m.params && m.params.level === 'error') errors.push(m.params.text || '');};const send = (method, params = {}) => new Promise((res, rej) => { const id = ++next; pending.set(id, { res, rej }); ws.send(JSON.stringify({ id, method, params })); });await send('session.new', { capabilities: {} });await send('session.subscribe', { events: ['log.entryAdded'] });const tree = await send('browsingContext.getTree', {});const context = tree.contexts[0].context;const evaluate = async (expression) => {const r = await send('script.evaluate', { expression, target: { context }, awaitPromise: true, resultOwnership: 'none' });if (r.type === 'exception') throw new Error('evaluate: ' + (r.exceptionDetails && r.exceptionDetails.text));// primitives only (string, number, boolean, null): an expression that wants to hand back more returns JSON.stringify(…)const v = r.result; return v && 'value' in v && typeof v.value !== 'object' ? v.value : null;};const waitFor = async (expression, { timeout = 15000, label = expression } = {}) => {const end = Date.now() + timeout;while (Date.now() < end) { try { const v = await evaluate(expression); if (v) return v; } catch {} await sleep(100); }throw new Error('firefox waitFor timed out: ' + label);};return {context, errors,evaluate,waitFor,goto: (url) => send('browsingContext.navigate', { context, url, wait: 'complete' }),setCookie: (name, value, domain) => send('storage.setCookie', { cookie: { name, value: { type: 'string', value }, domain, path: '/' } }),async click(selector, { timeout = 15000 } = {}) {const box = await waitFor(`(() => { const el = document.querySelector(${JSON.stringify(selector)}); if (!el) return null; el.scrollIntoView({ block: 'center' }); const b = el.getBoundingClientRect(); return b.width ? JSON.stringify({ x: Math.round(b.left + b.width / 2), y: Math.round(b.top + b.height / 2) }) : null; })()`, { timeout, label: 'click ' + selector });const { x, y } = JSON.parse(box);await send('input.performActions', { context, actions: [{ type: 'pointer', id: 'mouse', parameters: { pointerType: 'mouse' }, actions: [{ type: 'pointerMove', x, y }, { type: 'pointerDown', button: 0 }, { type: 'pointerUp', button: 0 }] }] });},async close() {try { await send('session.end', {}); } catch {}try { ws.close(); } catch {}try { proc.kill('SIGTERM'); } catch {}await new Promise(r => { if (proc.exitCode !== null || proc.signalCode !== null) return r(); proc.once('exit', r); setTimeout(() => { try { proc.kill('SIGKILL'); } catch {} r(); }, 4000); });rmSync(profile, { recursive: true, force: true });},};}
Branches
- mainmain branch
Latest commits
- 5b46ac84antcolony#40: LOG.md — missions 069/072 are antcolony missions (report paths on Byrodin)mre
- 5602ff41gitoria: Hybriel master 190aa11d (fc838894 GC correctness, #127 mountKids by reference, #126, #48) — tracker README flat; gates 200/0, 46/0, 44/0mre
- e85eaf01gitoria: 069 round 2 — hybriel 1a096ad3 not adopted (Markdown SSR still grows); browser gate waits for the server-side logout before restartmre
- 09ce4f3fgitoria: mission 069 re-vendor hybriel 8efba065 stopped (big SSR pages grow + slow down); lambda audit clean; old vendor keptmre
- 3dc43108antcolony#40: mission references point to the moved missionsmre
- 8d9450fdantcolony#40: history (LOG.md), worker briefs (missions/) and reports moved here from antcolony, numbered per project; old numbers in antcolony docs/mission-map.mdmre
- 205d5fe4gitoria: Hybriel master ff51cf46; ssh keys/tokens no double rows (session sync); gates follow #20mre
- 9b27cb26gitoria#21: installable app (manifest, service worker, offline start page), own iconmre
- 68dcb603deploy.sh: back up live storage/.sessions/.env before every deploy (newest 5 kept)mre
- e2deed6dgitoria#20: "Add code" only on the Code page of an empty repository, no collapsiblemre
- 8bb97ffddeploy.sh: never send .git or .gitignore to Byrodinmre
- fd981932State of 2026-09-27; bin/ no longer tracked (Hybriel commit is in README)mre
- 4a2d7125initial commitmre