Files
vexplor/scripts/browser-test-customer-crud.js
DDD1542 43ead0e7f2 feat: Enhance SelectedItemsDetailInputComponent with sourceKeyField auto-detection and FK mapping
- Implemented automatic detection of sourceKeyField based on component configuration, improving flexibility in data handling.
- Enhanced the SelectedItemsDetailInputConfigPanel to support automatic FK detection and mapping, streamlining the configuration process.
- Updated the database connection logic to handle DATE types correctly, preventing timezone-related issues.
- Improved overall component performance by optimizing memoization and state management for better user experience.
2026-02-26 16:39:06 +09:00

168 lines
6.4 KiB
JavaScript

/**
* 거래처관리 화면 CRUD 브라우저 테스트
* 실행: node scripts/browser-test-customer-crud.js
* 브라우저 표시: HEADLESS=0 node scripts/browser-test-customer-crud.js
*/
const { chromium } = require("playwright");
const BASE_URL = "http://localhost:9771";
const SCREENSHOT_DIR = "test-screenshots";
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();
try {
// 스크린샷 디렉토리
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}`);
};
console.log("\n=== 1단계: 로그인 ===\n");
await page.goto(`${BASE_URL}/login`, { waitUntil: "networkidle", timeout: 15000 });
await page.fill('#userId', 'topseal_admin');
await page.fill('#password', 'qlalfqjsgh11');
await page.click('button[type="submit"]');
await page.waitForTimeout(3000);
await screenshot("01_after_login");
results.success.push("로그인 완료");
console.log("\n=== 2단계: 거래처관리 화면 이동 ===\n");
await page.goto(`${BASE_URL}/screens/227`, { waitUntil: "domcontentloaded", timeout: 20000 });
// 테이블 또는 메인 콘텐츠 로딩 대기 (API 호출 후 React 렌더링)
try {
await page.waitForSelector('table, tbody, [role="row"], .rt-tbody', { timeout: 25000 });
results.success.push("테이블 로드 감지");
} catch (e) {
console.log(" [경고] 테이블 대기 타임아웃, 계속 진행");
}
await page.waitForTimeout(3000);
await screenshot("02_screen_227");
results.success.push("화면 227 로드");
console.log("\n=== 3단계: 거래처 선택 (READ 테스트) ===\n");
// 좌측 테이블 행 선택 - 다양한 레이아웃 대응
const rowSelectors = [
'table tbody tr.cursor-pointer',
'tbody tr.hover\\:bg-accent',
'table tbody tr:has(td)',
'tbody tr',
];
let rows = [];
for (const sel of rowSelectors) {
rows = await page.$$(sel);
if (rows.length > 0) break;
}
if (rows.length > 0) {
await rows[0].click();
results.success.push("거래처 행 클릭");
} else {
results.failed.push("거래처 테이블 행을 찾을 수 없음");
// 디버그: 페이지 구조 저장
const bodyHtml = await page.evaluate(() => {
const tables = document.querySelectorAll('table, tbody, [role="grid"], [role="table"]');
return `Tables found: ${tables.length}\n` + document.body.innerHTML.slice(0, 8000);
});
require("fs").writeFileSync(`${SCREENSHOT_DIR}/debug_body.html`, bodyHtml);
console.log(" [디버그] body HTML 일부 저장: debug_body.html");
}
await page.waitForTimeout(3000);
await screenshot("03_after_customer_select");
// SelectedItemsDetailInput 영역 확인
const detailArea = await page.$('[data-component="selected-items-detail-input"], [class*="selected-items"], .selected-items-detail');
if (detailArea) {
results.success.push("SelectedItemsDetailInput 컴포넌트 렌더링 확인");
} else {
// 품목/입력 관련 영역이 있는지
const hasInputArea = await page.$('input[placeholder*="품번"], input[placeholder*="품목"], [class*="detail"]');
results.success.push(hasInputArea ? "입력 영역 확인됨" : "SelectedItemsDetailInput 영역 미확인");
}
console.log("\n=== 4단계: 품목 추가 (CREATE 테스트) ===\n");
const addBtnLoc = page.locator('button').filter({ hasText: /추가|품목/ }).first();
const addBtnExists = await addBtnLoc.count() > 0;
if (addBtnExists) {
await addBtnLoc.click();
await page.waitForTimeout(1500);
await screenshot("04_after_add_click");
// 모달/팝업에서 품목 선택
const modalItem = await page.$('[role="dialog"] tr, [role="listbox"] [role="option"], .modal tbody tr');
if (modalItem) {
await modalItem.click();
await page.waitForTimeout(1000);
}
// 필수 필드 입력
const itemCodeInput = await page.$('input[name*="품번"], input[placeholder*="품번"], input[id*="item"]');
if (itemCodeInput) {
await itemCodeInput.fill("TEST_BROWSER");
}
await screenshot("04_before_save");
const saveBtnLoc = page.locator('button').filter({ hasText: /저장/ }).first();
if (await saveBtnLoc.count() > 0) {
await saveBtnLoc.click();
await page.waitForTimeout(3000);
await screenshot("05_after_save");
results.success.push("저장 버튼 클릭");
const toast = await page.$('[data-sonner-toast], .toast, [role="alert"]');
if (toast) {
const toastText = await toast.textContent();
results.success.push(`토스트 메시지: ${toastText?.slice(0, 50)}`);
}
} else {
results.failed.push("저장 버튼을 찾을 수 없음");
}
} else {
results.failed.push("품목 추가/추가 버튼을 찾을 수 없음");
await screenshot("04_no_add_button");
}
console.log("\n=== 5단계: 최종 결과 ===\n");
await screenshot("06_final_state");
// 콘솔 에러 수집
const consoleErrors = [];
page.on("console", (msg) => {
const type = msg.type();
if (type === "error") {
consoleErrors.push(msg.text());
}
});
} 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 (_) {}
} finally {
await browser.close();
}
// 결과 출력
console.log("\n========== 테스트 결과 ==========\n");
console.log("성공:", results.success);
console.log("실패:", results.failed);
console.log("스크린샷:", results.screenshots);
return results;
}
runTest().then((r) => {
process.exit(r.failed.length > 0 ? 1 : 0);
}).catch((e) => {
console.error(e);
process.exit(1);
});