/** * 화면 94(수주), 124(수주목록 리스트) 검증 스크립트 * - 로그인 후 각 화면 접속 * - 컴포넌트 배치, 테이블/필터/버튼, 가로 레이아웃 확인 */ import { chromium } from "playwright"; import * as path from "path"; import * as fs from "fs"; const BASE_URL = "http://localhost:9771"; const SCREENSHOT_DIR = path.join(__dirname, "../verification-screenshots"); interface ScreenResult { screenId: number; name: string; componentsOk: boolean; tableVisible: boolean; filterVisible: boolean; buttonsVisible: boolean; layoutHorizontal: boolean; noError: boolean; success: boolean; } type ScreenType = "form" | "list"; async function verifyScreen(page: any, screenId: number, name: string, type: ScreenType): Promise { console.log(`\n--- 화면 ${screenId} (${name}) 검증 ---`); await page.goto(`${BASE_URL}/screens/${screenId}`, { waitUntil: "domcontentloaded", timeout: 20000 }); // 로딩 완료 대기: "로딩중" 텍스트 사라질 때까지 최대 12초 await page.getByText("로딩중", { exact: false }).waitFor({ state: "hidden", timeout: 12000 }).catch(() => {}); // 리스트 화면: 테이블 로딩 대기. 폼 화면: 버튼/input 대기 if (type === "list") { await page.waitForSelector("table, [role='grid'], thead, tbody", { timeout: 8000 }).catch(() => {}); } else { await page.waitForSelector("button, input", { timeout: 5000 }).catch(() => {}); } await page.waitForTimeout(2000); const result: ScreenResult = { screenId, name, componentsOk: false, tableVisible: false, filterVisible: false, buttonsVisible: false, layoutHorizontal: false, noError: false, success: false, }; // 404/에러 메시지 확인 const has404 = (await page.locator('text="화면을 찾을 수 없습니다"').count()) > 0; const hasError = (await page.locator('text="오류 발생"').count()) > 0; result.noError = !has404; // 테이블 const tableSelectors = ["table", "[role='grid']", "thead", "tbody", ".table-mobile-fixed"]; for (const sel of tableSelectors) { if ((await page.locator(sel).count()) > 0) { result.tableVisible = true; break; } } // 필터/검색 const filterSelectors = ["input", "select", "button:has-text('검색')", "button:has-text('필터')"]; for (const sel of filterSelectors) { if ((await page.locator(sel).count()) > 0) { result.filterVisible = true; break; } } // 버튼 (사이드바 포함, 화면에 버튼이 있으면 OK) const buttonCount = await page.locator("button, [role='button']").count(); result.buttonsVisible = buttonCount > 0; // 가로 레이아웃: 사이드바+메인 구조, flex/grid, 또는 테이블이 있으면 가로 배치로 간주 const hasFlexRow = (await page.locator(".flex-row, .md\\:flex-row, .flex").count()) > 0; const hasGrid = (await page.locator(".grid, [class*='grid-cols']").count()) > 0; const hasMain = (await page.locator("main, [role='main'], .flex-1, [class*='flex-1']").count()) > 0; const hasSidebar = (await page.getByText("현재 관리 회사").count()) > 0 || (await page.getByText("VEXPLOR").count()) > 0; result.layoutHorizontal = (hasMain && (hasFlexRow || hasGrid || result.tableVisible)) || hasSidebar; // 컴포넌트 정상 배치 (테이블, 버튼, 또는 input/필터 중 하나라도 있으면 OK) result.componentsOk = result.tableVisible || result.buttonsVisible || result.filterVisible; // 성공: 폼 화면은 테이블 불필요, 리스트 화면은 테이블 필수 const baseOk = result.componentsOk && result.filterVisible && result.buttonsVisible && result.layoutHorizontal && result.noError; result.success = type === "form" ? baseOk : baseOk && result.tableVisible; return result; } async function main() { const browser = await chromium.launch({ headless: true }); const context = await browser.newContext({ viewport: { width: 1280, height: 900 } }); const page = await context.newPage(); const results: ScreenResult[] = []; try { // 로그인 (Playwright는 새 브라우저이므로) console.log("로그인..."); await page.goto(`${BASE_URL}/login`, { waitUntil: "domcontentloaded", timeout: 15000 }); await page.waitForTimeout(1000); await page.fill("#userId", "wace"); await page.fill("#password", "qlalfqjsgh11"); await page.locator('button[type="submit"]').first().click(); await page.waitForURL((url) => !url.includes("/login"), { timeout: 15000 }).catch(() => {}); await page.waitForTimeout(3000); // 화면 94 await page.screenshot({ path: path.join(SCREENSHOT_DIR, "s94-01-before.png"), fullPage: true, }); const r94 = await verifyScreen(page, 94, "수주", "form"); results.push(r94); await page.screenshot({ path: path.join(SCREENSHOT_DIR, "s94-02-after.png"), fullPage: true, }); // 화면 124 await page.screenshot({ path: path.join(SCREENSHOT_DIR, "s124-01-before.png"), fullPage: true, }); const r124 = await verifyScreen(page, 124, "수주목록 리스트", "list"); results.push(r124); await page.screenshot({ path: path.join(SCREENSHOT_DIR, "s124-02-after.png"), fullPage: true, }); // 결과 출력 console.log("\n=== 검증 결과 ==="); results.forEach((r) => { console.log( `화면 ${r.screenId} (${r.name}): ${r.success ? "성공" : "실패"}` + ` | 테이블:${r.tableVisible ? "O" : "X"} 필터:${r.filterVisible ? "O" : "X"} 버튼:${r.buttonsVisible ? "O" : "X"} 레이아웃:${r.layoutHorizontal ? "O" : "X"} 에러없음:${r.noError ? "O" : "X"}` ); }); const allSuccess = results.every((r) => r.success); console.log(`\n최종 판정: ${allSuccess ? "성공" : "실패"}`); fs.writeFileSync( path.join(SCREENSHOT_DIR, "s94-124-result.json"), JSON.stringify({ results, finalSuccess: allSuccess ? "성공" : "실패" }, null, 2) ); } catch (error: any) { console.error("오류:", error.message); await page.screenshot({ path: path.join(SCREENSHOT_DIR, "s94-124-error.png"), fullPage: true }).catch(() => {}); } finally { await browser.close(); } } main();