50 lines
2.9 KiB
JavaScript
50 lines
2.9 KiB
JavaScript
// Read-only, isolated browser check of X's public signed-out login page and theme injection.
|
|
// node scripts/live-signedout-theme.mjs [outDir]
|
|
import { chromium } from 'playwright';
|
|
import { build } from 'esbuild';
|
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { join } from 'node:path';
|
|
|
|
const root = fileURLToPath(new URL('../', import.meta.url));
|
|
const out = process.argv[2] ?? join(root, '../../verification/theme-matrix/live-signedout');
|
|
const url = process.argv[3] ?? 'https://x.com/i/jf/onboarding/web?mode=login';
|
|
await mkdir(out, { recursive: true });
|
|
const bundle = (await build({
|
|
stdin: { contents: "export { installTheme } from './src/inject/theme.ts';", resolveDir: root, loader: 'ts' },
|
|
bundle: true, format: 'iife', globalName: 'BT', write: false, target: 'chrome120', minify: true,
|
|
})).outputFiles[0].text;
|
|
|
|
const browser = await chromium.launch({ headless: true });
|
|
const context = await browser.newContext({ viewport: { width: 1440, height: 900 }, colorScheme: 'dark', bypassCSP: true });
|
|
const page = await context.newPage();
|
|
const results = [];
|
|
try {
|
|
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 45000 });
|
|
await page.waitForTimeout(5000);
|
|
const inspect = async theme => {
|
|
const state = await page.evaluate(() => {
|
|
const visible = e => { const r = e.getBoundingClientRect(); return r.width > 0 && r.height > 0; };
|
|
const buttons = [...document.querySelectorAll('button,[role="button"]')].filter(visible).slice(0, 25).map(e => {
|
|
const s = getComputedStyle(e);
|
|
const children = [...e.querySelectorAll('span,p,div')].filter(visible).slice(0, 5).map(c => ({ text: c.textContent?.trim().slice(0, 80), color: getComputedStyle(c).color }));
|
|
return { text: e.textContent?.trim().slice(0, 80), aria: e.getAttribute('aria-label'), bg: s.backgroundColor, color: s.color, children };
|
|
});
|
|
return { url: location.href, title: document.title, h1: [...document.querySelectorAll('h1,h2')].map(e => e.textContent?.trim()), buttons };
|
|
});
|
|
await page.screenshot({ path: join(out, `${theme}.png`) });
|
|
results.push({ theme, ...state });
|
|
};
|
|
await inspect('before');
|
|
if (!results[0].buttons.length) throw new Error(`X did not render signed-out controls at ${url}; saved before.png for diagnosis`);
|
|
await page.addScriptTag({ content: `${bundle}\nwindow.__bt = BT.installTheme(window);` });
|
|
for (const theme of ['x', 'paper', 'terminal', 'high-contrast', 'night-ink']) {
|
|
await page.evaluate(preset => window.__bt.apply({ preset }), theme);
|
|
await page.waitForTimeout(500);
|
|
await inspect(theme);
|
|
}
|
|
} finally {
|
|
await browser.close();
|
|
}
|
|
await writeFile(join(out, 'results.json'), JSON.stringify(results, null, 2));
|
|
console.log(JSON.stringify(results.map(r => ({ theme: r.theme, url: r.url, title: r.title, h1: r.h1, buttons: r.buttons.map(b => ({ text: b.text, bg: b.bg, color: b.color, children: b.children.filter(c => c.text) })) })), null, 2));
|