feat(abut-rebar): ③ 교대 벽체 카드
- 보기 탭(교대축 직각 단면 / 계단 단면) · 삽도 · 구간 메모 · 범례 - 입력: 기본간격 3종, 피복, 첫 철근 최소 여유, W7 배수(편집). 자동 값: 연직 격자, W7 간격, skew 고정, l_d (fck 교대 벽체 등) - 철근표: 선택 · 구분 · 번호 · 규격 · 간격 · 실간격(묶음 수정 W3 / W5 / W9) · 형상 · 길이 · 근거(규칙/가정) · 개수 · 위치 - 부재별 경고 수 · 알림(RB-AB), 행 선택 함수를 저판 카드와 공용 - scripts/verify-abut-wall.cjs: 헤드리스 Chrome 화면 확인 스크립트 Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
079748e5e7
commit
a17c9ebbb4
2 files changed
+365
-26
No files matched your search
@@ -0,0 +1,90 @@
|
||||
// ③ 교대 벽체 카드 화면 확인 (2026-11-04): 헤드리스 Chrome(CDP)으로 입력 파일을 불러와 A1 → ③ → 벽체를 열고 스크린샷을 남긴다.
|
||||
// 사용 (WSL Node ≥ 22, 개발 서버 실행 중):
|
||||
// chrome-headless-shell --headless --remote-debugging-port=9444 & (~/.cache/ms-playwright/chromium_headless_shell-*/)
|
||||
// node scripts/verify-abut-wall.cjs <input.xlsx> <out dir>
|
||||
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, OUT_DIR] = process.argv.slice(2);
|
||||
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');
|
||||
const ws = new WebSocket(page.webSocketDebuggerUrl);
|
||||
await new Promise((res, rej) => ((ws.onopen = res), (ws.onerror = rej)));
|
||||
let seq = 0;
|
||||
const waiting = new Map();
|
||||
const consoleEvents = [];
|
||||
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);
|
||||
} else if (m.method === 'Runtime.exceptionThrown') consoleEvents.push(`exception: ${m.params.exceptionDetails.exception?.description || m.params.exceptionDetails.text}`);
|
||||
else if (m.method === 'Runtime.consoleAPICalled' && m.params.type === 'error') consoleEvents.push(`console.error: ${m.params.args.map((a) => a.value ?? a.description ?? '').join(' ')}`);
|
||||
};
|
||||
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, full = false) => {
|
||||
const params = { format: 'png' };
|
||||
if (full) {
|
||||
const m = await send('Page.getLayoutMetrics');
|
||||
params.clip = { x: 0, y: 0, width: 1600, height: Math.min(m.result.cssContentSize.height, 6000), scale: 1 };
|
||||
params.captureBeyondViewport = true;
|
||||
}
|
||||
const r = await send('Page.captureScreenshot', params);
|
||||
fs.writeFileSync(path.join(OUT_DIR, name), Buffer.from(r.result.data, 'base64'));
|
||||
console.log(`SHOT ${name}`);
|
||||
};
|
||||
const click = (js) => evalJs(`(() => { const e = ${js}; if (!e) throw new Error('없음: ${js.replace(/'/g, '')}'); e.click(); return true; })()`);
|
||||
|
||||
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);
|
||||
await click(`document.getElementById('open-import')`);
|
||||
await sleep(500);
|
||||
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);
|
||||
await click(`document.getElementById('dlg-apply')`);
|
||||
await sleep(2500);
|
||||
await click(`document.getElementById('nav-rebar')`);
|
||||
await sleep(1500);
|
||||
await click(`[...document.querySelectorAll('#rebar-pier-items button')].find((b) => b.textContent.includes('교대'))`);
|
||||
await sleep(2000);
|
||||
await click(`[...document.querySelectorAll('#rebar-tabs button')].find((b) => b.textContent.startsWith('벽체'))`);
|
||||
await sleep(2000);
|
||||
console.log('STATUS', await evalJs(`document.getElementById('step3-status').textContent`));
|
||||
console.log('TABS', await evalJs(`[...document.querySelectorAll('#rebar-tabs button')].map((b) => b.textContent).join(' | ')`));
|
||||
console.log('NOTE', await evalJs(`document.getElementById('rebar-note').textContent`));
|
||||
console.log('ROWS', await evalJs(`[...document.querySelectorAll('#rebar-table table.rebar-table tbody tr')].filter((r) => r.cells.length > 2).map((r) => [r.cells[2].textContent, r.cells[7].textContent, r.cells[8].textContent].join(' ')).join(' | ')`));
|
||||
console.log('ISSUES', await evalJs(`document.getElementById('rebar-issues').innerText.replace(/\\n+/g, ' / ')`));
|
||||
console.log('BARS3D', await evalJs(`document.querySelectorAll('canvas').length`));
|
||||
await shot('wall-1-section.png', true);
|
||||
await click(`[...document.querySelectorAll('.abut-optt')].find((b) => b.textContent.startsWith('교대축 단면'))`);
|
||||
await sleep(1200);
|
||||
await shot('wall-2-elevation.png', true);
|
||||
// W9 행 선택 → 선택만 보기.
|
||||
await click(`[...document.querySelectorAll('#rebar-table table.rebar-table tbody tr')].find((r) => r.cells[2] && r.cells[2].textContent === 'W9')`);
|
||||
await sleep(800);
|
||||
await click(`[...document.querySelectorAll('#rebar-table .rebar-select button')].find((b) => b.textContent === '선택만 보기')`);
|
||||
await sleep(1500);
|
||||
console.log('SELECTED', await evalJs(`document.querySelector('#rebar-table .rebar-select span').textContent`));
|
||||
await shot('wall-3-w9-only.png');
|
||||
console.log(`CONSOLE_EVENTS ${consoleEvents.length}`);
|
||||
for (const e of consoleEvents) console.log(` ${e}`);
|
||||
ws.close();
|
||||
}
|
||||
main().catch((e) => { console.error('FAILED', e); process.exit(1); });
|
||||
Reference in new issue
Block a user