45 lines
4 KiB
JavaScript
45 lines
4 KiB
JavaScript
// Read-only visual QA of the isolated signed-out native WebView; theme choice is restored.
|
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
import { join } from 'node:path';
|
|
const [port = '9345', out = 'verification/theme-matrix/native-social-final'] = process.argv.slice(2);
|
|
await mkdir(out, { recursive: true });
|
|
const targets = await (await fetch(`http://127.0.0.1:${port}/json/list`)).json();
|
|
const page = targets.find(t => t.type === 'page' && t.url.startsWith('https://x.com/i/jf/onboarding'));
|
|
const shell = targets.find(t => t.type === 'page' && t.url.startsWith('http://tauri.localhost'));
|
|
if (!page || !shell) throw new Error('Isolated native onboarding and shell targets required');
|
|
function session(t) {
|
|
const ws = new WebSocket(t.webSocketDebuggerUrl); let id = 0; const pending = new Map();
|
|
ws.onmessage = event => { const msg = JSON.parse(event.data); pending.get(msg.id)?.(msg); pending.delete(msg.id); };
|
|
const ready = new Promise(resolve => ws.onopen = resolve);
|
|
const send = async (method, params = {}) => { await ready; return new Promise((resolve, reject) => {
|
|
const n = ++id; pending.set(n, msg => msg.error ? reject(new Error(msg.error.message)) : resolve(msg.result));
|
|
ws.send(JSON.stringify({ id: n, method, params }));
|
|
}); };
|
|
const evalJs = async expression => { const r = await send('Runtime.evaluate', { expression, awaitPromise: true, returnByValue: true });
|
|
if (r.exceptionDetails) throw new Error(r.exceptionDetails.exception?.description ?? r.exceptionDetails.text);
|
|
return r.result.value; };
|
|
return { send, evalJs, close: () => ws.close() };
|
|
}
|
|
const X = session(page), S = session(shell);
|
|
const invoke = (name, args = {}) => S.evalJs(`window.__TAURI_INTERNALS__.invoke(${JSON.stringify(name)},${JSON.stringify(args)})`);
|
|
const original = await invoke('theme_get');
|
|
const results = [];
|
|
try {
|
|
for (const preset of ['x', 'night-ink', 'classic', 'paper', 'terminal', 'high-contrast', 'solarized-light']) {
|
|
await invoke('theme_set', { theme: { ...original, preset, accent: null } });
|
|
await new Promise(resolve => setTimeout(resolve, 1700));
|
|
const state = await X.evalJs(`(() => {
|
|
const rgb = s => (s.match(/[\\d.]+/g) || []).slice(0,3).map(Number);
|
|
const lum = s => {const v=rgb(s).map(x=>x/255).map(x=>x<=.04045?x/12.92:((x+.055)/1.055)**2.4);return .2126*v[0]+.7152*v[1]+.0722*v[2]};
|
|
const ratio=(a,b)=>{const x=lum(a),y=lum(b);return Math.round((Math.max(x,y)+.05)/(Math.min(x,y)+.05)*100)/100};
|
|
const controls=['phone','Apple','Google'].map(name=>{const e=name==='Google'?document.querySelector('.jf-gsi-face'):[...document.querySelectorAll('button')].find(b=>b.textContent?.includes('Continue with '+name));const bg=name==='Google'?e:e?.querySelector(':scope > .jf-element.flex');const text=name==='Google'?e?.querySelector(':scope > span.jf-element'):e?.querySelector('.text-text > p.jf-element');const bs=bg&&getComputedStyle(bg),ts=text&&getComputedStyle(text);return {name,text:text?.textContent,bg:bs?.backgroundColor,color:ts?.color,textFill:ts?.webkitTextFillColor,contrast:bs&&ts?ratio(ts.webkitTextFillColor==='currentcolor'?ts.color:ts.webkitTextFillColor,bs.backgroundColor):null,transition:bs?.transitionDuration,animation:bs?.animationName}});
|
|
const back=[...document.querySelectorAll('button')].find(b=>b.querySelector('svg')&&!b.textContent?.trim());
|
|
const bs=back&&getComputedStyle(back);return {url:location.href,controls,back:back?{bg:bs.backgroundColor,color:bs.color,html:back.outerHTML.slice(0,500)}:null};
|
|
})()`);
|
|
const shot = await X.send('Page.captureScreenshot', { format: 'png', captureBeyondViewport: false });
|
|
await writeFile(join(out, `${preset}.png`), Buffer.from(shot.data, 'base64'));
|
|
results.push({ preset, ...state });
|
|
}
|
|
} finally { await invoke('theme_set', { theme: original }).catch(() => {}); X.close(); S.close(); }
|
|
await writeFile(join(out, 'results.json'), JSON.stringify(results, null, 2));
|
|
console.log(JSON.stringify(results.map(({preset,controls,back}) => ({preset,controls,back:back&&{bg:back.bg,color:back.color}}))));
|