Moved from wayBetter for X; an app passes its own origin, process name, scripts and fixtures.
70 lines
3.1 KiB
JavaScript
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();
|
|
}
|