- 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.
168 lines
6.4 KiB
JavaScript
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);
|
|
});
|