waybetter/dev-tools/native-tab-stress.mjs
CRT 5c64f2f6f8 dev-tools: the generic development harnesses (perf probes, close check, tab and webview stress)
Moved from wayBetter for X; an app passes its own origin, process name, scripts and fixtures.
2026-09-30 14:48:06 +07:00

70 lines
3.1 KiB
JavaScript

// Native shell stress for a wayBetter app through WebView2 CDP. Run only against an app started with
// WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--remote-debugging-port=<port>.
// Opens 24 shell start tabs, measures responsiveness, then closes those tabs.
const port = process.argv[2] ?? '9344';
const count = Number(process.argv[3] ?? 24);
const list = await (await fetch(`http://127.0.0.1:${port}/json/list`)).json();
const target = list.find(item => item.type === 'page' && item.url.startsWith('http://tauri.localhost/'));
if (!target) throw new Error('Shell CDP target not found');
const socket = new WebSocket(target.webSocketDebuggerUrl);
const ready = new Promise(resolve => { socket.onopen = resolve; });
const pending = new Map();
let serial = 0;
socket.onmessage = event => {
const message = JSON.parse(event.data);
if (message.id) { pending.get(message.id)?.(message); pending.delete(message.id); }
};
await ready;
const send = (method, params = {}) => new Promise(resolve => {
const id = ++serial;
pending.set(id, resolve);
socket.send(JSON.stringify({ id, method, params }));
});
const evaluate = async expression => {
let timeout;
const result = await Promise.race([
send('Runtime.evaluate', { expression, awaitPromise: true, returnByValue: true }),
new Promise((_, reject) => { timeout = setTimeout(() => reject(new Error('Shell unresponsive for 20 s')), 20_000); }),
]).finally(() => clearTimeout(timeout));
if (result.result?.exceptionDetails) throw new Error(JSON.stringify(result.result.exceptionDetails));
return result.result?.result?.value;
};
const expression = `\n(async () => {
const count = ${count};
const before = document.querySelectorAll('.dv-tab').length;
const delays = [];
const longTasks = [];
const observer = new PerformanceObserver(list => {
for (const entry of list.getEntries()) longTasks.push(entry.duration);
});
observer.observe({ entryTypes: ['longtask'] });
let expected = performance.now() + 50;
const interval = setInterval(() => {
const now = performance.now();
delays.push(Math.max(0, now - expected));
expected = now + 50;
}, 50);
const key = key => window.dispatchEvent(new KeyboardEvent('keydown', { key, ctrlKey: true, bubbles: true }));
const start = performance.now();
for (let i = 0; i < count; i++) key('t');
const dispatchMs = performance.now() - start;
await new Promise(resolve => setTimeout(resolve, 1500));
const opened = document.querySelectorAll('.dv-tab').length;
for (let i = 0; i < count; i++) key('w');
await new Promise(resolve => setTimeout(resolve, 300));
clearInterval(interval);
observer.disconnect();
return {
before, opened, afterCleanup: document.querySelectorAll('.dv-tab').length,
dispatchMs: Math.round(dispatchMs),
maxDelayMs: Math.round(Math.max(0, ...delays)),
maxLongTaskMs: Math.round(Math.max(0, ...longTasks)),
};
})()`;
try {
const result = await evaluate(expression);
console.log(JSON.stringify(result, null, 2));
if (result.opened !== result.before + count || result.afterCleanup !== result.before) process.exitCode = 1;
} finally {
socket.close();
}