test(ui): CDP verification of the import dialog and four views

- scripts/verify-ui.cjs drives Chrome over DevTools (run with Windows Node,
  same machine as Chrome): loads the user's three-sheet workbook, reads the
  sheet report, applies P1, and captures the four view presets
- verify/shots-2026-10-21: screenshots used as evidence for V10 2 and V11

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-10-21 18:09:55 +09:00
1 parent 727cd3b2e2
commit 353931c767
7 files changed
+100

No files matched your search

+100
View File
@@ -0,0 +1,100 @@
// UI verification driven through the Chrome DevTools Protocol.
// Run with Windows Node (same machine as Chrome), e.g.:
// node verify-ui.cjs "C:\\Users\\...\\input.xlsx" "\\\\wsl.localhost\\Ubuntu\\...\\shots"
// Chrome must be started with --remote-debugging-port=9444 (see DOC/05_검증).
const fs = require('fs');
const path = require('path');
const APP_URL = process.env.APP_URL || 'http://127.0.0.1:5173/';
const CDP = process.env.CDP || 'http://127.0.0.1:9444';
const INPUT_FILE = process.argv[2];
const OUT_DIR = process.argv[3];
if (!INPUT_FILE || !OUT_DIR) throw new Error('INPUT_FILE and OUT_DIR are required');
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function main() {
const targets = await (await fetch(`${CDP}/json`)).json();
const page = targets.find((t) => t.type === 'page');
if (!page) throw new Error('no page target');
const ws = new WebSocket(page.webSocketDebuggerUrl);
await new Promise((res, rej) => {
ws.onopen = res;
ws.onerror = rej;
});
let seq = 0;
const waiting = new Map();
ws.onmessage = (ev) => {
const m = JSON.parse(ev.data);
if (m.id && waiting.has(m.id)) {
waiting.get(m.id)(m);
waiting.delete(m.id);
}
};
const send = (method, params = {}) =>
new Promise((res) => {
const id = ++seq;
waiting.set(id, res);
ws.send(JSON.stringify({ id, method, params }));
});
const evalJs = async (expression) => {
const r = await send('Runtime.evaluate', { expression, awaitPromise: true, returnByValue: true });
if (r.result.exceptionDetails) throw new Error(JSON.stringify(r.result.exceptionDetails).slice(0, 400));
return r.result.result.value;
};
const shot = async (name) => {
const r = await send('Page.captureScreenshot', { format: 'png' });
fs.writeFileSync(path.join(OUT_DIR, name), Buffer.from(r.result.data, 'base64'));
console.log(`SHOT ${name}`);
};
await send('Page.enable');
await send('Runtime.enable');
await send('DOM.enable');
await send('Emulation.setDeviceMetricsOverride', { width: 1600, height: 1000, deviceScaleFactor: 1, mobile: false });
await send('Page.navigate', { url: APP_URL });
await sleep(4000);
// V11 — dialog with the user's three-sheet workbook.
await evalJs(`document.getElementById('open-import').click()`);
await sleep(500);
await evalJs(`(() => { const i = document.getElementById('project-name'); i.value = '운곡교 검증';
i.dispatchEvent(new Event('input')); return true; })()`);
const doc = await send('DOM.getDocument', { depth: -1 });
const q = await send('DOM.querySelector', { nodeId: doc.result.root.nodeId, selector: '#file' });
await send('DOM.setFileInputFiles', { nodeId: q.result.nodeId, files: [INPUT_FILE] });
await sleep(3000);
console.log('SHEETS_TEXT_BEGIN');
console.log(await evalJs(`document.getElementById('sheets').innerText`));
console.log('SHEETS_TEXT_END');
console.log('SELECTED', await evalJs(`document.getElementById('dlg-selected').textContent`));
console.log('APPLY', await evalJs(`(() => { const b = document.getElementById('dlg-apply');
return b.textContent + ' | disabled=' + b.disabled; })()`));
await shot('v11_dialog_user_file.png');
await evalJs(`document.getElementById('dlg-apply').click()`);
await sleep(2000);
console.log('STATUS', await evalJs(`document.getElementById('status').textContent`));
console.log('PIERS', await evalJs(`[...document.querySelectorAll('#pier-items li')].map(li => li.textContent.trim()).join(' | ')`));
console.log('RESULT_AXIS', await evalJs(`document.getElementById('res-axis').innerText.replace(/\\n+/g, ' / ')`));
console.log('RESULT_FOUND', await evalJs(`document.getElementById('res-found').innerText.replace(/\\n+/g, ' / ')`));
await shot('v11_main_user_file.png');
// V10 ② — the four view presets.
for (const label of ['정면', '측면', '평면', '아이소']) {
await evalJs(`[...document.querySelectorAll('#views button')].find((b) => b.textContent === '${label}').click()`);
await sleep(1500);
await shot(`v10_view_${label}.png`);
console.log('PRESSED', label, await evalJs(`[...document.querySelectorAll('#views button')].filter((b) => b.getAttribute('aria-pressed') === 'true').map((b) => b.textContent).join(',')`));
}
// V10 ③ — console errors surfaced by the page (checked via a reload with error capture).
ws.close();
}
main().catch((e) => {
console.error('FAILED', e);
process.exit(1);
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB