Oregami
Repositories/oxedyne/daimond

oxedyne/daimond/dev/probe_focusrestore.mjs

3.7 KiB, 1 run

created by r2519314175:47, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1// probe_focusrestore.mjs — who takes the focus after an admin dialog closes?
2//
3// `verify_focus` reports that three admin-home dialogs leave the focus on the
4// document body. This drives the SAME interaction the verifier does -- its
5// `BOX_OF`/`press`/`pressLabel` are copied verbatim below, because an earlier
6// version of this probe used its own and passed while the verifier failed -- and
7// records every mutation of the panel alongside where the focus was at the time.
8//
9// node dev/probe_focusrestore.mjs
10import { open, scratch } from './harness.mjs';
11
12// ── The verifier's helpers, copied so the interaction is identical ──
13const BOX_OF = ({ rootSel, text }) => {
14 const root = rootSel ? document.querySelector(rootSel) : document;
15 if (!root) return null;
16 const el = text
17 ? [...root.querySelectorAll('button')].find((x) => (x.textContent || '').trim() === text)
18 : root;
19 if (!el) return null;
20 el.scrollIntoView({ block: 'center', inline: 'center' });
21 const r = el.getBoundingClientRect();
22 if (!r.width || !r.height) return null;
23 return { x: r.left + r.width / 2, y: r.top + r.height / 2 };
24};
25async function press(page, sel) {
26 await page.waitForSelector(sel, { timeout: 10000 });
27 const box = await page.evaluate(BOX_OF, { rootSel: sel, text: '' });
28 if (!box) throw new Error(`${sel} has no box to click`);
29 await page.mouse.click(box.x, box.y);
30 await page.waitForTimeout(400);
31}
32async function pressLabel(page, rootSel, text) {
33 await page.waitForSelector(rootSel, { timeout: 10000 });
34 const box = await page.evaluate(BOX_OF, { rootSel, text });
35 if (!box) throw new Error(`no control labelled "${text}" in ${rootSel}`);
36 await page.mouse.click(box.x, box.y);
37 await page.waitForTimeout(450);
38}
39
40const NAME = () => {
41 const a = document.activeElement;
42 return !a ? '(none)'
43 : a.tagName + (a.id ? '#' + a.id : '')
44 + (a.className && typeof a.className === 'string'
45 ? '.' + a.className.trim().split(/\s+/)[0] : '');
46};
47
48const dir = scratch('pw', 'foc-probe-' + Math.random().toString(36).slice(2, 10));
49const s = await open({ name: 'focusrestore', connect: false, profile: dir });
50const { page } = s;
51try {
52 await page.waitForTimeout(1500);
53
54 // Watch the panel, and note where the focus is at every mutation of it.
55 await page.evaluate(() => {
56 window.__trace = [];
57 window.__t0 = performance.now();
58 const nm = () => {
59 const a = document.activeElement;
60 return !a ? '(none)' : a.tagName + '.' + String(a.className || '').split(/\s+/)[0];
61 };
62 const root = document.querySelector('#admin-home');
63 if (!root) { window.__trace.push('no #admin-home'); return; }
64 new MutationObserver((recs) => {
65 window.__trace.push(`+${Math.round(performance.now() - window.__t0)}ms `
66 + `${recs.length} mutation(s) on #admin-home, focus now ${nm()}`);
67 }).observe(root, { childList: true, subtree: true });
68 // And a plain sampler, so a focus loss with no mutation is still visible.
69 let last = nm();
70 setInterval(() => {
71 const now = nm();
72 if (now !== last) {
73 window.__trace.push(`+${Math.round(performance.now() - window.__t0)}ms `
74 + `focus moved ${last} -> ${now}`);
75 last = now;
76 }
77 }, 40);
78 });
79
80 await press(page, '#user-row');
81 await pressLabel(page, '#admin-home', 'Change name…');
82 console.log('focus once the dialog is up:', await page.evaluate(NAME));
83
84 await press(page, '.modal.dlg .dlg-cancel');
85 await page.waitForTimeout(500); // exactly what the verifier waits
86 console.log('focus at the verifier\'s sampling point:', await page.evaluate(NAME));
87 await page.waitForTimeout(1500);
88 console.log('focus 2s after closing:', await page.evaluate(NAME));
89
90 console.log('\n---- trace ----');
91 for (const line of await page.evaluate(() => window.__trace)) console.log(' ' + line);
92} finally {
93 await s.close();
94}