1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
// Playwright CLI function: one diagnostic run, no implementation changes.
async (page) => {
const onDialog = dialog => dialog.accept();
page.on('dialog', onDialog);
const routeApp = async route => {
const response = await route.fetch();
let body = await response.text();
const measured = (key, expression) => `measure('${key}', () => ${expression})`;
body = body.replace("const output = createOutput(document);", `const output = createOutput(document);
function measure(name, action) {
const start = performance.now();
try { return action(); } finally {
const all = globalThis.__phase ??= {};
const item = all[name] ??= {calls:0, ms:0};
item.calls++; item.ms += performance.now() - start;
}
}`);
body = body.replace('function refresh() {', "function refresh() { return measure('refresh', refreshBody); }\nfunction refreshBody() {");
body = body.replace('function draftChanged() {', "function draftChanged() { return measure('draft', draftBody); }\nfunction draftBody() {");
body = body.replace('runtime.present(output)', measured('present', 'runtime.present(output)'));
body = body.replace('output.costs(snapshot, runtime.currentProgram?.metrics, droppedTicks)',
measured('costs', 'output.costs(snapshot, runtime.currentProgram?.metrics, droppedTicks)'));
await route.fulfill({response, body});
};
const client = await page.context().newCDPSession(page);
const metrics = async () => Object.fromEntries((await client.send('Performance.getMetrics')).metrics.map(m => [m.name, m.value]));
const source = ['state x = 40', 'input move = KeyM',
'box moving(x, 100, 30, 30, "#7ed9ae")', 'text label(20, 40, x)',
...Array.from({length:22}, (_, i) => `box static${i}(${20 + (i % 7) * 70}, ${200 + Math.floor(i / 7) * 30}, 20, 20, "#8da5bb")`),
'on move.down { x = x + 0.25 }'].join('\n');
try {
await page.route('**/app.js', routeApp);
await page.reload(); await page.waitForFunction(() => window.pureWorkbench?.snapshot().ready);
await page.locator('#source').fill(source); await page.locator('#apply').click();
await page.locator('#scene').focus();
await page.evaluate(() => {
window.__tap = () => {
const scene = document.getElementById('scene');
scene.dispatchEvent(new KeyboardEvent('keydown', {code:'KeyM', bubbles:true}));
scene.dispatchEvent(new KeyboardEvent('keyup', {code:'KeyM', bubbles:true}));
};
for (let i = 0; i < 20; i++) window.__tap();
});
await client.send('Performance.enable');
const before = await metrics();
const run = await page.evaluate(() => {
window.__phase = {};
const start = performance.now();
for (let i = 0; i < 100; i++) window.__tap();
return {elapsedMs:performance.now()-start, phase:window.__phase};
});
const after = await metrics();
const delta = Object.fromEntries(['LayoutCount','LayoutDuration','RecalcStyleCount','RecalcStyleDuration','ScriptDuration']
.map(key => [key, after[key]-before[key]]));
const evidence = {utc:new Date().toISOString(), taps:100, ...run, metrics:delta, source,
scope:'Phase timings overlap: draft/present/costs are inside refresh. CDP counters bracket the synchronous batch and may include surrounding browser work.'};
await page.evaluate(value => {window.__pureRefreshProfile = value;}, evidence);
return evidence;
} catch (error) {
const evidence = {failed:true, message:error.stack};
await page.evaluate(value => {window.__pureRefreshProfile = value;}, evidence);
return evidence;
} finally {
await page.unroute('**/app.js', routeApp); page.off('dialog', onDialog); await client.detach();
}
}
|