/** * 구매관리 - 공급업체관리 / 구매품목정보 CRUD 브라우저 테스트 * 실행: node scripts/browser-test-purchase-supplier.js * 브라우저 표시: HEADLESS=0 node scripts/browser-test-purchase-supplier.js */ const { chromium } = require("playwright"); const BASE_URL = "http://localhost:9771"; const SCREENSHOT_DIR = "test-screenshots"; const CREDENTIALS = { userId: "topseal_admin", password: "qlalfqjsgh11" }; async function runTest() { const results = { success: [], failed: [], screenshots: [] }; const browser = await chromium.launch({ headless: process.env.HEADLESS !== "0" }); const context = await browser.newContext({ viewport: { width: 1280, height: 900 } }); const page = await context.newPage(); const fs = require("fs"); if (!fs.existsSync(SCREENSHOT_DIR)) fs.mkdirSync(SCREENSHOT_DIR, { recursive: true }); const screenshot = async (name) => { const path = `${SCREENSHOT_DIR}/${name}.png`; await page.screenshot({ path, fullPage: true }); results.screenshots.push(path); console.log(` [스크린샷] ${path}`); return path; }; const clickMenu = async (text) => { const loc = page.getByText(text, { exact: true }).first(); if ((await loc.count()) > 0) { await loc.click(); return true; } const alt = page.getByRole("link", { name: text }).or(page.locator(`a:has-text("${text}")`)).first(); if ((await alt.count()) > 0) { await alt.click(); return true; } return false; }; const clickRow = async () => { const rows = await page.$$('tbody tr, table tr, [role="row"]'); for (const r of rows) { const t = await r.textContent(); if (t && !t.includes("데이터가 없습니다") && !t.includes("로딩")) { await r.click(); return true; } } if (rows.length > 0) { await rows[0].click(); return true; } return false; }; const clickButton = async (regex) => { const btn = page.locator("button").filter({ hasText: regex }).first(); try { if ((await btn.count()) > 0 && !(await btn.isDisabled())) { await btn.click(); return true; } } catch (_) {} return false; }; try { console.log("\n=== 로그인 확인 ===\n"); await page.goto(`${BASE_URL}/login`, { waitUntil: "networkidle", timeout: 15000 }); if (page.url().includes("/login")) { await page.fill("#userId", CREDENTIALS.userId); await page.fill("#password", CREDENTIALS.password); await page.click('button[type="submit"]'); await page.waitForTimeout(3000); } results.success.push("세션 확인"); // ========== 테스트 1: 공급업체관리 ========== console.log("\n=== 테스트 1: 공급업체관리 ===\n"); console.log("단계 1: 구매관리 메뉴 열기"); if (await clickMenu("구매관리")) { await page.waitForTimeout(3000); results.success.push("구매관리 메뉴 클릭"); } else { results.failed.push("구매관리 메뉴 미발견"); } await screenshot("p1_01_purchase_menu"); console.log("단계 2: 공급업체관리 서브메뉴 클릭"); if (await clickMenu("공급업체관리")) { await page.waitForTimeout(8000); results.success.push("공급업체관리 메뉴 클릭"); } else { results.failed.push("공급업체관리 메뉴 미발견"); } await screenshot("p1_02_supplier_screen"); console.log("단계 3: 공급업체 선택"); if (await clickRow()) { await page.waitForTimeout(5000); results.success.push("공급업체 행 클릭"); } else { results.failed.push("공급업체 테이블 행 미발견"); } await screenshot("p1_03_after_supplier_select"); console.log("단계 4: 납품품목 탭/영역 확인"); const itemTab = page.getByText(/납품품목|품목/).first(); if ((await itemTab.count()) > 0) { await itemTab.click(); await page.waitForTimeout(3000); results.success.push("납품품목/품목 탭 클릭"); } else { results.failed.push("납품품목 탭 미발견"); } await screenshot("p1_04_item_tab"); console.log("단계 5: 품목 추가 시도"); const addBtn = page.locator("button").filter({ hasText: /추가|\+ 추가/ }).first(); let addBtnEnabled = false; try { addBtnEnabled = (await addBtn.count()) > 0 && !(await addBtn.isDisabled()); } catch (_) {} if (addBtnEnabled) { await addBtn.click(); await page.waitForTimeout(2000); const modal = await page.$('[role="dialog"], .modal, [class*="modal"]'); if (modal) { const modalRow = await page.$('[role="dialog"] tbody tr, .modal tbody tr'); if (modalRow) { await modalRow.click(); await page.waitForTimeout(1500); } } await page.waitForTimeout(1500); results.success.push("추가 버튼 클릭 및 품목 선택 시도"); } else { results.failed.push("추가 버튼 미발견 또는 비활성화"); } await screenshot("p1_05_add_item"); // ========== 테스트 2: 구매품목정보 ========== console.log("\n=== 테스트 2: 구매품목정보 ===\n"); console.log("단계 6: 구매품목정보 메뉴 클릭"); if (await clickMenu("구매품목정보")) { await page.waitForTimeout(8000); results.success.push("구매품목정보 메뉴 클릭"); } else { results.failed.push("구매품목정보 메뉴 미발견"); } await screenshot("p2_01_item_screen"); console.log("단계 7: 품목 선택 및 공급업체 확인"); if (await clickRow()) { await page.waitForTimeout(5000); results.success.push("구매품목 행 클릭"); } else { results.failed.push("구매품목 테이블 행 미발견"); } await screenshot("p2_02_after_item_select"); // SelectedItemsDetailInput 컴포넌트 확인 const hasDetailInput = await page.$('input[placeholder*="품번"], [class*="selected-items"], input[name*="품번"]'); results.success.push(hasDetailInput ? "SelectedItemsDetailInput 렌더링 확인" : "SelectedItemsDetailInput 미확인"); await screenshot("p2_03_final"); console.log("\n========== 테스트 결과 ==========\n"); console.log("성공:", results.success); console.log("실패:", results.failed); console.log("스크린샷:", results.screenshots); } catch (err) { results.failed.push(`예외: ${err.message}`); try { await page.screenshot({ path: `${SCREENSHOT_DIR}/error.png`, fullPage: true }); results.screenshots.push(`${SCREENSHOT_DIR}/error.png`); } catch (_) {} console.error(err); } finally { await browser.close(); } return results; } runTest() .then((r) => process.exit(r.failed.length > 0 ? 1 : 0)) .catch((e) => { console.error(e); process.exit(1); });