Add environment variable example and update .gitignore
- Created a new .env.example file to provide a template for environment variables, including database connection details, JWT settings, encryption keys, and external API keys. - Updated .gitignore to include additional test output directories and archive files, ensuring that unnecessary files are not tracked by Git. - Removed outdated approval test reports and scripts that are no longer needed, streamlining the project structure. These changes improve the clarity of environment configuration and maintain a cleaner repository.
This commit is contained in:
@@ -1,171 +0,0 @@
|
||||
/**
|
||||
* 결재함 플로우 E2E 테스트 스크립트
|
||||
* 실행: npx tsx scripts/approval-flow-test.ts
|
||||
*/
|
||||
import { chromium } from "playwright";
|
||||
|
||||
const BASE_URL = "http://localhost:9771";
|
||||
const LOGIN_ID = "wace";
|
||||
const LOGIN_PW = "1234";
|
||||
const FALLBACK_PW = "qlalfqjsgh11"; // 마스터 패스워드 (1234 실패 시)
|
||||
|
||||
async function main() {
|
||||
const results: string[] = [];
|
||||
const consoleErrors: string[] = [];
|
||||
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const context = await browser.newContext({
|
||||
viewport: { width: 1280, height: 800 }, // 데스크톱 뷰 (사이드바 표시)
|
||||
});
|
||||
const page = await context.newPage();
|
||||
|
||||
// 콘솔 에러 수집
|
||||
page.on("console", (msg) => {
|
||||
const type = msg.type();
|
||||
if (type === "error") {
|
||||
const text = msg.text();
|
||||
consoleErrors.push(text);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
// 1. http://localhost:9771 이동
|
||||
results.push("=== 1. http://localhost:9771 이동 ===");
|
||||
await page.goto(BASE_URL, { waitUntil: "networkidle", timeout: 15000 });
|
||||
results.push("OK: 페이지 로드 완료");
|
||||
|
||||
// 2. 로그인 여부 확인
|
||||
results.push("\n=== 2. 로그인 상태 확인 ===");
|
||||
const isLoginPage = await page.locator('#userId, input[name="userId"]').count() > 0;
|
||||
if (isLoginPage) {
|
||||
results.push("로그인 페이지 감지됨. 로그인 시도...");
|
||||
await page.fill('#userId', LOGIN_ID);
|
||||
await page.fill('#password', LOGIN_PW);
|
||||
await page.click('button[type="submit"]');
|
||||
await page.waitForTimeout(4000);
|
||||
|
||||
// 여전히 로그인 페이지면 마스터 패스워드로 재시도
|
||||
const stillLoginPage = await page.locator('#userId').count() > 0;
|
||||
if (stillLoginPage) {
|
||||
results.push("1234 로그인 실패. 마스터 패스워드로 재시도...");
|
||||
await page.fill('#userId', LOGIN_ID);
|
||||
await page.fill('#password', FALLBACK_PW);
|
||||
await page.click('button[type="submit"]');
|
||||
await page.waitForTimeout(4000);
|
||||
}
|
||||
results.push("로그인 폼 제출 완료");
|
||||
} else {
|
||||
results.push("이미 로그인된 상태로 판단 (로그인 폼 없음)");
|
||||
}
|
||||
|
||||
// 3. 사용자 프로필 아바타 클릭 (사이드바 하단)
|
||||
results.push("\n=== 3. 사용자 프로필 아바타 클릭 ===");
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// 사이드바 하단 사용자 프로필 버튼 (border-t border-slate-200 내부의 button)
|
||||
const sidebarAvatarBtn = page.locator('aside div.border-t.border-slate-200 button').first();
|
||||
let avatarClicked = false;
|
||||
if ((await sidebarAvatarBtn.count()) > 0) {
|
||||
try {
|
||||
// force: true - Next.js dev overlay가 클릭을 가로채는 경우 우회
|
||||
await sidebarAvatarBtn.click({ timeout: 5000, force: true });
|
||||
avatarClicked = true;
|
||||
results.push("OK: 사이드바 하단 아바타 클릭 완료");
|
||||
await page.waitForTimeout(500); // 드롭다운 열림 대기
|
||||
} catch (e) {
|
||||
results.push(`WARN: 사이드바 아바타 클릭 실패 - ${(e as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (!avatarClicked) {
|
||||
// 모바일 헤더 아바타 또는 fallback
|
||||
const headerAvatar = page.locator('header button:has(div.rounded-full)').first();
|
||||
if ((await headerAvatar.count()) > 0) {
|
||||
await headerAvatar.click({ force: true });
|
||||
avatarClicked = true;
|
||||
results.push("OK: 헤더 아바타 클릭 (모바일 뷰?)");
|
||||
}
|
||||
}
|
||||
|
||||
if (!avatarClicked) {
|
||||
results.push("WARN: 아바타 클릭 실패. 직접 /admin/approvalBox로 이동하여 페이지 검증");
|
||||
await page.goto(`${BASE_URL}/admin/approvalBox`, { waitUntil: "networkidle", timeout: 10000 });
|
||||
}
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// 4. "결재함" 메뉴 확인 (드롭다운이 열린 경우)
|
||||
results.push("\n=== 4. 결재함 메뉴 확인 ===");
|
||||
const approvalMenuItem = page.locator('[role="menuitem"]:has-text("결재함"), [data-radix-collection-item]:has-text("결재함")').first();
|
||||
const hasApprovalMenu = (await approvalMenuItem.count()) > 0;
|
||||
if (hasApprovalMenu) {
|
||||
results.push("OK: 결재함 메뉴가 보입니다.");
|
||||
} else {
|
||||
results.push("FAIL: 결재함 메뉴를 찾을 수 없습니다.");
|
||||
}
|
||||
|
||||
// 5. 결재함 메뉴 클릭
|
||||
results.push("\n=== 5. 결재함 메뉴 클릭 ===");
|
||||
if (hasApprovalMenu) {
|
||||
await approvalMenuItem.click({ force: true });
|
||||
await page.waitForTimeout(3000);
|
||||
results.push("OK: 결재함 메뉴 클릭 완료");
|
||||
} else if (!avatarClicked) {
|
||||
results.push("(직접 이동으로 스킵 - 이미 approvalBox 페이지)");
|
||||
} else {
|
||||
results.push("WARN: 드롭다운에서 결재함 메뉴 미발견. 직접 이동...");
|
||||
await page.goto(`${BASE_URL}/admin/approvalBox`, { waitUntil: "networkidle", timeout: 10000 });
|
||||
await page.waitForTimeout(2000);
|
||||
}
|
||||
|
||||
// 6. /admin/approvalBox 페이지 렌더링 확인
|
||||
results.push("\n=== 6. /admin/approvalBox 페이지 확인 ===");
|
||||
const currentUrl = page.url();
|
||||
const isApprovalBoxPage = currentUrl.includes("/admin/approvalBox");
|
||||
results.push(`현재 URL: ${currentUrl}`);
|
||||
results.push(isApprovalBoxPage ? "OK: approvalBox 페이지에 있습니다." : "FAIL: approvalBox 페이지가 아닙니다.");
|
||||
|
||||
// 제목 "결재함" 확인
|
||||
const titleEl = page.locator('h1:has-text("결재함")');
|
||||
const hasTitle = (await titleEl.count()) > 0;
|
||||
results.push(hasTitle ? "OK: 제목 '결재함' 확인됨" : "FAIL: 제목 '결재함' 없음");
|
||||
|
||||
// 탭 확인: 수신함, 상신함
|
||||
const receivedTab = page.locator('button[role="tab"], [role="tab"]').filter({ hasText: "수신함" });
|
||||
const sentTab = page.locator('button[role="tab"], [role="tab"]').filter({ hasText: "상신함" });
|
||||
const hasReceivedTab = (await receivedTab.count()) > 0;
|
||||
const hasSentTab = (await sentTab.count()) > 0;
|
||||
results.push(hasReceivedTab ? "OK: '수신함' 탭 확인됨" : "FAIL: '수신함' 탭 없음");
|
||||
results.push(hasSentTab ? "OK: '상신함' 탭 확인됨" : "FAIL: '상신함' 탭 없음");
|
||||
|
||||
// 7. 콘솔 에러 확인
|
||||
results.push("\n=== 7. 콘솔 에러 확인 ===");
|
||||
if (consoleErrors.length === 0) {
|
||||
results.push("OK: 콘솔 에러 없음");
|
||||
} else {
|
||||
results.push(`WARN: 콘솔 에러 ${consoleErrors.length}건 발견:`);
|
||||
consoleErrors.slice(0, 10).forEach((err, i) => {
|
||||
results.push(` [${i + 1}] ${err.substring(0, 200)}${err.length > 200 ? "..." : ""}`);
|
||||
});
|
||||
if (consoleErrors.length > 10) {
|
||||
results.push(` ... 외 ${consoleErrors.length - 10}건`);
|
||||
}
|
||||
}
|
||||
|
||||
// 스크린샷 저장 (프로젝트 내)
|
||||
await page.screenshot({ path: "approval-box-result.png" }).catch(() => {});
|
||||
} catch (err: any) {
|
||||
results.push(`\nERROR: ${err.message}`);
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
// 결과 출력
|
||||
console.log("\n" + "=".repeat(60));
|
||||
console.log("결재함 플로우 테스트 결과");
|
||||
console.log("=".repeat(60));
|
||||
results.forEach((r) => console.log(r));
|
||||
console.log("\n" + "=".repeat(60));
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,111 +0,0 @@
|
||||
/**
|
||||
* 브라우저 검증 스크립트
|
||||
* 1. 로그인 페이지 접속
|
||||
* 2. 로그인
|
||||
* 3. /screens/29 접속
|
||||
* 4. 화면 렌더링 검증 (버튼, 테이블, 검색 필터)
|
||||
*/
|
||||
|
||||
import { chromium } from "playwright";
|
||||
import * as path from "path";
|
||||
|
||||
const BASE_URL = "http://localhost:9771";
|
||||
const SCREENSHOT_DIR = path.join(__dirname, "../verification-screenshots");
|
||||
|
||||
async function main() {
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const context = await browser.newContext({ viewport: { width: 1280, height: 800 } });
|
||||
const page = await context.newPage();
|
||||
|
||||
const results: { step: string; success: boolean; message?: string }[] = [];
|
||||
|
||||
try {
|
||||
// Step 1: 로그인 페이지 접속
|
||||
console.log("Step 1: 로그인 페이지 접속...");
|
||||
await page.goto(`${BASE_URL}/login`, { waitUntil: "networkidle", timeout: 10000 });
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "01-login-page.png"), fullPage: true });
|
||||
results.push({ step: "1. 로그인 페이지 접속", success: true });
|
||||
|
||||
// Step 2: 로그인
|
||||
console.log("Step 2: 로그인...");
|
||||
await page.fill('#userId', "wace");
|
||||
await page.fill('#password', "qlalfqjsgh11");
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "02-login-filled.png"), fullPage: true });
|
||||
|
||||
const loginButton = page.locator('button[type="submit"]').first();
|
||||
await loginButton.click();
|
||||
await page.waitForURL((url) => !url.pathname.includes("/login") || url.pathname === "/", { timeout: 10000 }).catch(() => {});
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
const currentUrl = page.url();
|
||||
if (currentUrl.includes("/login") && !currentUrl.includes("/screens")) {
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "02-login-result.png"), fullPage: true });
|
||||
const errorText = await page.locator('[role="alert"], .error, .text-destructive, [class*="error"]').first().textContent().catch(() => "");
|
||||
results.push({ step: "2. 로그인", success: false, message: errorText || "로그인 실패 - 여전히 로그인 페이지에 있음" });
|
||||
} else {
|
||||
results.push({ step: "2. 로그인", success: true });
|
||||
}
|
||||
|
||||
// Step 3: /screens/29 접속
|
||||
console.log("Step 3: /screens/29 접속...");
|
||||
await page.goto(`${BASE_URL}/screens/29`, { waitUntil: "networkidle", timeout: 15000 });
|
||||
await page.waitForTimeout(3000);
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "03-screen-29.png"), fullPage: true });
|
||||
results.push({ step: "3. /screens/29 접속", success: true });
|
||||
|
||||
// Step 4: 화면 렌더링 검증
|
||||
console.log("Step 4: 화면 렌더링 검증...");
|
||||
const checks: { name: string; selector: string; found: boolean }[] = [];
|
||||
|
||||
// 버튼 확인
|
||||
const buttons = page.locator("button, [role='button'], input[type='submit'], input[type='button']");
|
||||
const buttonCount = await buttons.count();
|
||||
checks.push({ name: "버튼", selector: "button, [role='button']", found: buttonCount > 0 });
|
||||
|
||||
// 테이블 확인
|
||||
const tables = page.locator("table, [role='grid'], [role='table'], .ag-root");
|
||||
const tableCount = await tables.count();
|
||||
checks.push({ name: "테이블", selector: "table, [role='grid']", found: tableCount > 0 });
|
||||
|
||||
// 검색 필터 확인 (input, select 등)
|
||||
const searchFilters = page.locator('input[type="text"], input[type="search"], input[placeholder*="검색"], input[placeholder*="Search"], select, [class*="filter"], [class*="search"]');
|
||||
const filterCount = await searchFilters.count();
|
||||
checks.push({ name: "검색/필터", selector: "input, select, filter", found: filterCount > 0 });
|
||||
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "04-screen-29-verified.png"), fullPage: true });
|
||||
|
||||
const allPassed = checks.every((c) => c.found);
|
||||
results.push({
|
||||
step: "4. 화면 렌더링 검증",
|
||||
success: allPassed,
|
||||
message: checks.map((c) => `${c.name}: ${c.found ? "O" : "X"}`).join(", "),
|
||||
});
|
||||
|
||||
// 결과 출력
|
||||
console.log("\n=== 검증 결과 ===");
|
||||
results.forEach((r) => {
|
||||
console.log(`${r.step}: ${r.success ? "성공" : "실패"}${r.message ? ` - ${r.message}` : ""}`);
|
||||
});
|
||||
checks.forEach((c) => {
|
||||
console.log(` - ${c.name}: ${c.found ? "보임" : "없음"}`);
|
||||
});
|
||||
|
||||
const finalSuccess = results.every((r) => r.success);
|
||||
console.log(`\n최종 판정: ${finalSuccess ? "성공" : "실패"}`);
|
||||
|
||||
// 결과를 JSON 파일로 저장
|
||||
const fs = await import("fs");
|
||||
fs.writeFileSync(
|
||||
path.join(SCREENSHOT_DIR, "verification-result.json"),
|
||||
JSON.stringify({ results, checks, finalSuccess: finalSuccess ? "성공" : "실패" }, null, 2)
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error("오류 발생:", error.message);
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "99-error.png"), fullPage: true }).catch(() => {});
|
||||
results.push({ step: "오류", success: false, message: error.message });
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,156 +0,0 @@
|
||||
/**
|
||||
* 회사 선택 → 메뉴 → 수주/구매관리 화면 검증
|
||||
* 1. 로그인 (topseal7 또는 wace)
|
||||
* 2. 회사 선택 → 탑씰
|
||||
* 3. 영업관리 > 수주관리 또는 구매관리
|
||||
* 4. 데이터 화면 스크린샷
|
||||
* 5. 테이블 가로 스크롤 확인
|
||||
*/
|
||||
|
||||
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");
|
||||
|
||||
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 steps: string[] = [];
|
||||
|
||||
try {
|
||||
// Step 1: 로그인 페이지
|
||||
console.log("Step 1: 로그인 페이지 접속...");
|
||||
await page.goto(`${BASE_URL}/login`, { waitUntil: "domcontentloaded", timeout: 15000 });
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "flow-01-login-page.png"), fullPage: true });
|
||||
steps.push("01-login-page");
|
||||
|
||||
// Step 2: 로그인 시도 (topseal7 먼저)
|
||||
console.log("Step 2: 로그인 (topseal7 시도)...");
|
||||
await page.fill("#userId", "topseal7");
|
||||
await page.fill("#password", "qlalfqjsgh11");
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "flow-02-login-topseal7.png"), fullPage: true });
|
||||
await page.locator('button[type="submit"]').first().click();
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
const urlAfterLogin = page.url();
|
||||
const isStillLogin = urlAfterLogin.includes("/login");
|
||||
|
||||
if (isStillLogin) {
|
||||
console.log("topseal7 로그인 실패, wace 시도...");
|
||||
await page.fill("#userId", "wace");
|
||||
await page.fill("#password", "qlalfqjsgh11");
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "flow-02b-login-wace.png"), fullPage: true });
|
||||
await page.locator('button[type="submit"]').first().click();
|
||||
await page.waitForTimeout(3000);
|
||||
}
|
||||
await page.waitForURL((url) => !url.includes("/login"), { timeout: 15000 }).catch(() => {});
|
||||
await page.waitForTimeout(3000);
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "flow-03-after-login.png"), fullPage: true });
|
||||
steps.push("03-after-login");
|
||||
|
||||
// Step 3: 회사 선택 → 탑씰 (SUPER_ADMIN만 보임, 메인 앱 로드 대기)
|
||||
console.log("Step 3: 회사 선택 클릭...");
|
||||
await page.getByText("현재 관리 회사").waitFor({ timeout: 8000 }).catch(() => {});
|
||||
await page.waitForTimeout(1000);
|
||||
const companyBtn = page.getByText("회사 선택").first();
|
||||
if ((await companyBtn.count()) > 0) {
|
||||
await companyBtn.click();
|
||||
await page.waitForTimeout(1500);
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "flow-04-company-dropdown.png"), fullPage: true });
|
||||
|
||||
const tapsealOption = page.getByText("탑씰", { exact: true }).first();
|
||||
if ((await tapsealOption.count()) > 0) {
|
||||
await tapsealOption.click();
|
||||
await page.waitForTimeout(2000);
|
||||
console.log("탑씰 선택됨");
|
||||
} else {
|
||||
console.log("탑씰 옵션 없음 - 스킵");
|
||||
}
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "flow-05-after-company.png"), fullPage: true });
|
||||
} else {
|
||||
console.log("회사 선택 버튼 없음");
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "flow-05-no-company-btn.png"), fullPage: true });
|
||||
}
|
||||
steps.push("05-after-company");
|
||||
|
||||
// Step 4: 영업관리 > 수주관리 또는 구매관리
|
||||
console.log("Step 4: 메뉴 클릭 (영업관리 > 수주관리)...");
|
||||
const salesMgmt = page.getByText("영업관리").first();
|
||||
if ((await salesMgmt.count()) > 0) {
|
||||
await salesMgmt.click();
|
||||
await page.waitForTimeout(1000);
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "flow-06-sales-expanded.png"), fullPage: true });
|
||||
|
||||
const orderMgmt = page.getByText("수주관리").first();
|
||||
if ((await orderMgmt.count()) > 0) {
|
||||
await orderMgmt.click();
|
||||
await page.waitForTimeout(3000);
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "flow-07-order-screen.png"), fullPage: true });
|
||||
} else {
|
||||
const purchaseMgmt = page.getByText("구매관리").first();
|
||||
if ((await purchaseMgmt.count()) > 0) {
|
||||
await purchaseMgmt.click();
|
||||
await page.waitForTimeout(3000);
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "flow-07-purchase-screen.png"), fullPage: true });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const purchaseMgmt = page.getByText("구매관리").first();
|
||||
if ((await purchaseMgmt.count()) > 0) {
|
||||
await purchaseMgmt.click();
|
||||
await page.waitForTimeout(3000);
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "flow-07-purchase-direct.png"), fullPage: true });
|
||||
}
|
||||
}
|
||||
steps.push("07-menu-screen");
|
||||
|
||||
// Step 5: /screens/1244 직접 접속 시도
|
||||
console.log("Step 5: /screens/1244 직접 접속...");
|
||||
await page.goto(`${BASE_URL}/screens/1244`, { waitUntil: "domcontentloaded", timeout: 15000 });
|
||||
await page.waitForTimeout(5000);
|
||||
await page.getByText("로딩중", { exact: false }).waitFor({ state: "hidden", timeout: 10000 }).catch(() => {});
|
||||
await page.waitForTimeout(2000);
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "flow-08-screen-1244.png"), fullPage: true });
|
||||
steps.push("08-screen-1244");
|
||||
|
||||
// Step 6: 테이블 가로 스크롤 확인
|
||||
console.log("Step 6: 테이블 가로 스크롤 확인...");
|
||||
const tableContainer = page.locator("table").locator("..").first();
|
||||
const table = page.locator("table").first();
|
||||
if ((await table.count()) > 0) {
|
||||
const tableBox = await table.boundingBox();
|
||||
const hasOverflowX = await table.evaluate((el) => {
|
||||
const parent = el.closest("[style*='overflow'], [class*='overflow']");
|
||||
return parent ? getComputedStyle(parent as Element).overflowX !== "visible" : false;
|
||||
}).catch(() => false);
|
||||
const scrollWidth = await table.evaluate((el) => el.scrollWidth);
|
||||
const clientWidth = await table.evaluate((el) => el.clientWidth);
|
||||
const canScroll = scrollWidth > clientWidth;
|
||||
console.log(`테이블: scrollWidth=${scrollWidth}, clientWidth=${clientWidth}, 가로스크롤가능=${canScroll}`);
|
||||
}
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "flow-09-table-scroll-check.png"), fullPage: true });
|
||||
steps.push("09-table-scroll");
|
||||
|
||||
// Step 7: 최종 스크린샷
|
||||
console.log("Step 7: 최종 스크린샷...");
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "flow-10-final.png"), fullPage: true });
|
||||
steps.push("10-final");
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(SCREENSHOT_DIR, "flow-result.json"),
|
||||
JSON.stringify({ steps, timestamp: new Date().toISOString() }, null, 2)
|
||||
);
|
||||
console.log("\n완료. 스크린샷:", SCREENSHOT_DIR);
|
||||
} catch (error: any) {
|
||||
console.error("오류:", error.message);
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "flow-99-error.png"), fullPage: true }).catch(() => {});
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,71 +0,0 @@
|
||||
/**
|
||||
* 대시보드 검증 스크립트
|
||||
* 1. 로그인
|
||||
* 2. /main으로 강제 이동 (reload)
|
||||
* 3. 대시보드 스크린샷
|
||||
*/
|
||||
|
||||
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");
|
||||
|
||||
async function main() {
|
||||
if (!fs.existsSync(SCREENSHOT_DIR)) {
|
||||
fs.mkdirSync(SCREENSHOT_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const context = await browser.newContext({ viewport: { width: 1280, height: 800 } });
|
||||
const page = await context.newPage();
|
||||
|
||||
try {
|
||||
// Step 1: 로그인 페이지 접속
|
||||
console.log("Step 1: 로그인 페이지 접속...");
|
||||
await page.goto(`${BASE_URL}/login`, { waitUntil: "commit", timeout: 30000 });
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
// Step 2: 로그인
|
||||
console.log("Step 2: 로그인...");
|
||||
await page.fill("#userId", "wace");
|
||||
await page.fill("#password", "qlalfqjsgh11");
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Step 3: 리다이렉트 대기 (최대 15초)
|
||||
console.log("Step 3: 페이지 로드 대기 (최대 15초)...");
|
||||
await page.waitForURL((url) => !url.pathname.includes("/login"), { timeout: 15000 }).catch(() => {});
|
||||
await page.waitForTimeout(4000); // 쿠키/토큰 설정 완료 대기
|
||||
|
||||
// Step 4: /main으로 강제 이동 (reload)
|
||||
console.log("Step 4: /main으로 강제 이동...");
|
||||
await page.goto(`${BASE_URL}/main`, { waitUntil: "commit", timeout: 30000 });
|
||||
await page.waitForTimeout(3000); // 페이지 렌더링 대기
|
||||
|
||||
// Step 5: 페이지 내용 검증 및 스크린샷
|
||||
const heading = await page.locator("h1").first().textContent().catch(() => "");
|
||||
const url = page.url();
|
||||
console.log("Step 5: 현재 URL:", url);
|
||||
console.log(" -> h1 제목:", heading?.trim() || "(없음)");
|
||||
|
||||
await page.screenshot({
|
||||
path: path.join(SCREENSHOT_DIR, "main-dashboard.png"),
|
||||
fullPage: true,
|
||||
});
|
||||
console.log(" -> main-dashboard.png 저장됨");
|
||||
|
||||
await browser.close();
|
||||
console.log("\n검증 완료. 스크린샷:", path.join(SCREENSHOT_DIR, "main-dashboard.png"));
|
||||
} catch (error) {
|
||||
console.error("오류:", error);
|
||||
await page.screenshot({
|
||||
path: path.join(SCREENSHOT_DIR, "dashboard-error.png"),
|
||||
fullPage: true,
|
||||
}).catch(() => {});
|
||||
await browser.close();
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,458 +0,0 @@
|
||||
/**
|
||||
* 🔥 버튼 제어관리 성능 검증 스크립트
|
||||
*
|
||||
* 실제 환경에서 성능 목표 달성 여부를 확인합니다.
|
||||
*
|
||||
* 사용법:
|
||||
* npm run performance-test
|
||||
*/
|
||||
|
||||
import { optimizedButtonDataflowService } from "../lib/services/optimizedButtonDataflowService";
|
||||
import { dataflowConfigCache } from "../lib/services/dataflowCache";
|
||||
import { dataflowJobQueue } from "../lib/services/dataflowJobQueue";
|
||||
import { PerformanceBenchmark } from "../lib/services/__tests__/buttonDataflowPerformance.test";
|
||||
import { ButtonActionType, ButtonTypeConfig } from "../types/screen";
|
||||
|
||||
// 🔥 성능 목표 상수
|
||||
const PERFORMANCE_TARGETS = {
|
||||
IMMEDIATE_RESPONSE: 200, // ms
|
||||
CACHE_HIT: 10, // ms
|
||||
SIMPLE_VALIDATION: 50, // ms
|
||||
QUEUE_ENQUEUE: 5, // ms
|
||||
CACHE_HIT_RATE: 80, // %
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 🔥 메인 성능 테스트 실행
|
||||
*/
|
||||
async function runPerformanceTests() {
|
||||
console.log("🔥 Button Dataflow Performance Verification");
|
||||
console.log("==========================================\n");
|
||||
|
||||
const benchmark = new PerformanceBenchmark();
|
||||
let totalTests = 0;
|
||||
let passedTests = 0;
|
||||
|
||||
try {
|
||||
// 1. 캐시 성능 테스트
|
||||
console.log("📊 Testing Cache Performance...");
|
||||
const cacheResults = await testCachePerformance(benchmark);
|
||||
totalTests += cacheResults.total;
|
||||
passedTests += cacheResults.passed;
|
||||
|
||||
// 2. 버튼 실행 성능 테스트
|
||||
console.log("\n⚡ Testing Button Execution Performance...");
|
||||
const buttonResults = await testButtonExecutionPerformance(benchmark);
|
||||
totalTests += buttonResults.total;
|
||||
passedTests += buttonResults.passed;
|
||||
|
||||
// 3. 큐 성능 테스트
|
||||
console.log("\n🚀 Testing Job Queue Performance...");
|
||||
const queueResults = await testJobQueuePerformance(benchmark);
|
||||
totalTests += queueResults.total;
|
||||
passedTests += queueResults.passed;
|
||||
|
||||
// 4. 통합 성능 테스트
|
||||
console.log("\n🔧 Testing Integration Performance...");
|
||||
const integrationResults = await testIntegrationPerformance(benchmark);
|
||||
totalTests += integrationResults.total;
|
||||
passedTests += integrationResults.passed;
|
||||
|
||||
// 최종 결과 출력
|
||||
console.log("\n" + "=".repeat(50));
|
||||
console.log("🎯 PERFORMANCE TEST SUMMARY");
|
||||
console.log("=".repeat(50));
|
||||
console.log(`Total Tests: ${totalTests}`);
|
||||
console.log(`Passed: ${passedTests} (${((passedTests / totalTests) * 100).toFixed(1)}%)`);
|
||||
console.log(`Failed: ${totalTests - passedTests}`);
|
||||
|
||||
// 벤치마크 리포트
|
||||
benchmark.printReport();
|
||||
|
||||
// 성공/실패 판정
|
||||
const successRate = (passedTests / totalTests) * 100;
|
||||
if (successRate >= 90) {
|
||||
console.log("\n🎉 PERFORMANCE VERIFICATION PASSED!");
|
||||
console.log("All performance targets have been met.");
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.log("\n⚠️ PERFORMANCE VERIFICATION FAILED!");
|
||||
console.log("Some performance targets were not met.");
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("\n❌ Performance test failed:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 캐시 성능 테스트
|
||||
*/
|
||||
async function testCachePerformance(benchmark: PerformanceBenchmark) {
|
||||
let total = 0;
|
||||
let passed = 0;
|
||||
|
||||
// 캐시 초기화
|
||||
dataflowConfigCache.clearAllCache();
|
||||
|
||||
// 1. 첫 번째 로드 성능 (서버 호출)
|
||||
total++;
|
||||
try {
|
||||
const time = await benchmark.measure("Cache First Load", async () => {
|
||||
return await dataflowConfigCache.getConfig("perf-test-1");
|
||||
});
|
||||
|
||||
// 첫 로드는 1초 이내면 통과
|
||||
if (benchmark.getResults().details.slice(-1)[0].time < 1000) {
|
||||
passed++;
|
||||
console.log(" ✅ First load performance: PASSED");
|
||||
} else {
|
||||
console.log(" ❌ First load performance: FAILED");
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(" ❌ First load test: ERROR -", error.message);
|
||||
}
|
||||
|
||||
// 2. 캐시 히트 성능
|
||||
total++;
|
||||
try {
|
||||
await benchmark.measure("Cache Hit Performance", async () => {
|
||||
return await dataflowConfigCache.getConfig("perf-test-1");
|
||||
});
|
||||
|
||||
const hitTime = benchmark.getResults().details.slice(-1)[0].time;
|
||||
if (hitTime < PERFORMANCE_TARGETS.CACHE_HIT) {
|
||||
passed++;
|
||||
console.log(` ✅ Cache hit performance: PASSED (${hitTime.toFixed(2)}ms < ${PERFORMANCE_TARGETS.CACHE_HIT}ms)`);
|
||||
} else {
|
||||
console.log(` ❌ Cache hit performance: FAILED (${hitTime.toFixed(2)}ms >= ${PERFORMANCE_TARGETS.CACHE_HIT}ms)`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(" ❌ Cache hit test: ERROR -", error.message);
|
||||
}
|
||||
|
||||
// 3. 캐시 히트율 테스트
|
||||
total++;
|
||||
try {
|
||||
// 여러 버튼에 대해 캐시 로드 및 히트 테스트
|
||||
const buttonIds = Array.from({ length: 10 }, (_, i) => `perf-test-${i}`);
|
||||
|
||||
// 첫 번째 로드 (캐시 채우기)
|
||||
await Promise.all(buttonIds.map((id) => dataflowConfigCache.getConfig(id)));
|
||||
|
||||
// 두 번째 로드 (캐시 히트)
|
||||
await Promise.all(buttonIds.map((id) => dataflowConfigCache.getConfig(id)));
|
||||
|
||||
const metrics = dataflowConfigCache.getMetrics();
|
||||
if (metrics.hitRate >= PERFORMANCE_TARGETS.CACHE_HIT_RATE) {
|
||||
passed++;
|
||||
console.log(
|
||||
` ✅ Cache hit rate: PASSED (${metrics.hitRate.toFixed(1)}% >= ${PERFORMANCE_TARGETS.CACHE_HIT_RATE}%)`,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
` ❌ Cache hit rate: FAILED (${metrics.hitRate.toFixed(1)}% < ${PERFORMANCE_TARGETS.CACHE_HIT_RATE}%)`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(" ❌ Cache hit rate test: ERROR -", error.message);
|
||||
}
|
||||
|
||||
return { total, passed };
|
||||
}
|
||||
|
||||
/**
|
||||
* 버튼 실행 성능 테스트
|
||||
*/
|
||||
async function testButtonExecutionPerformance(benchmark: PerformanceBenchmark) {
|
||||
let total = 0;
|
||||
let passed = 0;
|
||||
|
||||
const mockConfig: ButtonTypeConfig = {
|
||||
actionType: "save" as ButtonActionType,
|
||||
enableDataflowControl: true,
|
||||
dataflowTiming: "after",
|
||||
dataflowConfig: {
|
||||
controlMode: "simple",
|
||||
selectedDiagramId: 1,
|
||||
selectedRelationshipId: "rel-123",
|
||||
},
|
||||
};
|
||||
|
||||
// 1. After 타이밍 성능 테스트
|
||||
total++;
|
||||
try {
|
||||
await benchmark.measure("Button Execution (After)", async () => {
|
||||
return await optimizedButtonDataflowService.executeButtonWithDataflow(
|
||||
"perf-button-1",
|
||||
"save",
|
||||
mockConfig,
|
||||
{ testData: "value" },
|
||||
"DEFAULT",
|
||||
);
|
||||
});
|
||||
|
||||
const execTime = benchmark.getResults().details.slice(-1)[0].time;
|
||||
if (execTime < PERFORMANCE_TARGETS.IMMEDIATE_RESPONSE) {
|
||||
passed++;
|
||||
console.log(
|
||||
` ✅ After timing execution: PASSED (${execTime.toFixed(2)}ms < ${PERFORMANCE_TARGETS.IMMEDIATE_RESPONSE}ms)`,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
` ❌ After timing execution: FAILED (${execTime.toFixed(2)}ms >= ${PERFORMANCE_TARGETS.IMMEDIATE_RESPONSE}ms)`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(" ❌ After timing test: ERROR -", error.message);
|
||||
}
|
||||
|
||||
// 2. Before 타이밍 (간단한 검증) 성능 테스트
|
||||
total++;
|
||||
try {
|
||||
const beforeConfig = {
|
||||
...mockConfig,
|
||||
dataflowTiming: "before" as const,
|
||||
dataflowConfig: {
|
||||
controlMode: "advanced" as const,
|
||||
directControl: {
|
||||
sourceTable: "test_table",
|
||||
triggerType: "insert" as const,
|
||||
conditions: [
|
||||
{
|
||||
id: "cond1",
|
||||
type: "condition" as const,
|
||||
field: "status",
|
||||
operator: "=" as const,
|
||||
value: "active",
|
||||
},
|
||||
],
|
||||
actions: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
await benchmark.measure("Button Execution (Before Simple)", async () => {
|
||||
return await optimizedButtonDataflowService.executeButtonWithDataflow(
|
||||
"perf-button-2",
|
||||
"save",
|
||||
beforeConfig,
|
||||
{ status: "active" },
|
||||
"DEFAULT",
|
||||
);
|
||||
});
|
||||
|
||||
const execTime = benchmark.getResults().details.slice(-1)[0].time;
|
||||
if (execTime < PERFORMANCE_TARGETS.SIMPLE_VALIDATION) {
|
||||
passed++;
|
||||
console.log(
|
||||
` ✅ Before simple validation: PASSED (${execTime.toFixed(2)}ms < ${PERFORMANCE_TARGETS.SIMPLE_VALIDATION}ms)`,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
` ❌ Before simple validation: FAILED (${execTime.toFixed(2)}ms >= ${PERFORMANCE_TARGETS.SIMPLE_VALIDATION}ms)`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(" ❌ Before timing test: ERROR -", error.message);
|
||||
}
|
||||
|
||||
// 3. 제어관리 없는 실행 성능
|
||||
total++;
|
||||
try {
|
||||
const noDataflowConfig = {
|
||||
...mockConfig,
|
||||
enableDataflowControl: false,
|
||||
};
|
||||
|
||||
await benchmark.measure("Button Execution (No Dataflow)", async () => {
|
||||
return await optimizedButtonDataflowService.executeButtonWithDataflow(
|
||||
"perf-button-3",
|
||||
"save",
|
||||
noDataflowConfig,
|
||||
{ testData: "value" },
|
||||
"DEFAULT",
|
||||
);
|
||||
});
|
||||
|
||||
const execTime = benchmark.getResults().details.slice(-1)[0].time;
|
||||
if (execTime < 100) {
|
||||
// 제어관리 없으면 더 빨라야 함
|
||||
passed++;
|
||||
console.log(` ✅ No dataflow execution: PASSED (${execTime.toFixed(2)}ms < 100ms)`);
|
||||
} else {
|
||||
console.log(` ❌ No dataflow execution: FAILED (${execTime.toFixed(2)}ms >= 100ms)`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(" ❌ No dataflow test: ERROR -", error.message);
|
||||
}
|
||||
|
||||
return { total, passed };
|
||||
}
|
||||
|
||||
/**
|
||||
* 작업 큐 성능 테스트
|
||||
*/
|
||||
async function testJobQueuePerformance(benchmark: PerformanceBenchmark) {
|
||||
let total = 0;
|
||||
let passed = 0;
|
||||
|
||||
const mockConfig: ButtonTypeConfig = {
|
||||
actionType: "save" as ButtonActionType,
|
||||
enableDataflowControl: true,
|
||||
dataflowTiming: "after",
|
||||
dataflowConfig: {
|
||||
controlMode: "simple",
|
||||
selectedDiagramId: 1,
|
||||
selectedRelationshipId: "rel-123",
|
||||
},
|
||||
};
|
||||
|
||||
// 큐 초기화
|
||||
dataflowJobQueue.clearQueue();
|
||||
|
||||
// 1. 단일 작업 큐잉 성능
|
||||
total++;
|
||||
try {
|
||||
await benchmark.measure("Job Queue Enqueue (Single)", async () => {
|
||||
return dataflowJobQueue.enqueue("queue-perf-1", "save", mockConfig, {}, "DEFAULT", "normal");
|
||||
});
|
||||
|
||||
const queueTime = benchmark.getResults().details.slice(-1)[0].time;
|
||||
if (queueTime < PERFORMANCE_TARGETS.QUEUE_ENQUEUE) {
|
||||
passed++;
|
||||
console.log(
|
||||
` ✅ Single job enqueue: PASSED (${queueTime.toFixed(2)}ms < ${PERFORMANCE_TARGETS.QUEUE_ENQUEUE}ms)`,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
` ❌ Single job enqueue: FAILED (${queueTime.toFixed(2)}ms >= ${PERFORMANCE_TARGETS.QUEUE_ENQUEUE}ms)`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(" ❌ Single enqueue test: ERROR -", error.message);
|
||||
}
|
||||
|
||||
// 2. 대량 작업 큐잉 성능
|
||||
total++;
|
||||
try {
|
||||
const jobCount = 50;
|
||||
await benchmark.measure("Job Queue Enqueue (Batch)", async () => {
|
||||
const promises = Array.from({ length: jobCount }, (_, i) =>
|
||||
dataflowJobQueue.enqueue(`queue-perf-batch-${i}`, "save", mockConfig, {}, "DEFAULT", "normal"),
|
||||
);
|
||||
return Promise.resolve(promises);
|
||||
});
|
||||
|
||||
const batchTime = benchmark.getResults().details.slice(-1)[0].time;
|
||||
const averageTime = batchTime / jobCount;
|
||||
|
||||
if (averageTime < PERFORMANCE_TARGETS.QUEUE_ENQUEUE) {
|
||||
passed++;
|
||||
console.log(
|
||||
` ✅ Batch job enqueue: PASSED (avg ${averageTime.toFixed(2)}ms < ${PERFORMANCE_TARGETS.QUEUE_ENQUEUE}ms)`,
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
` ❌ Batch job enqueue: FAILED (avg ${averageTime.toFixed(2)}ms >= ${PERFORMANCE_TARGETS.QUEUE_ENQUEUE}ms)`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(" ❌ Batch enqueue test: ERROR -", error.message);
|
||||
}
|
||||
|
||||
// 3. 우선순위 처리 확인
|
||||
total++;
|
||||
try {
|
||||
// 일반 우선순위 작업들
|
||||
const normalJobs = Array.from({ length: 5 }, (_, i) =>
|
||||
dataflowJobQueue.enqueue(`normal-${i}`, "save", mockConfig, {}, "DEFAULT", "normal"),
|
||||
);
|
||||
|
||||
// 높은 우선순위 작업
|
||||
const highJob = dataflowJobQueue.enqueue("high-priority", "save", mockConfig, {}, "DEFAULT", "high");
|
||||
|
||||
const queueInfo = dataflowJobQueue.getQueueInfo();
|
||||
|
||||
// 높은 우선순위 작업이 맨 앞에 있는지 확인
|
||||
if (queueInfo.pending[0].id === highJob && queueInfo.pending[0].priority === "high") {
|
||||
passed++;
|
||||
console.log(" ✅ Priority handling: PASSED");
|
||||
} else {
|
||||
console.log(" ❌ Priority handling: FAILED");
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(" ❌ Priority test: ERROR -", error.message);
|
||||
}
|
||||
|
||||
return { total, passed };
|
||||
}
|
||||
|
||||
/**
|
||||
* 통합 성능 테스트
|
||||
*/
|
||||
async function testIntegrationPerformance(benchmark: PerformanceBenchmark) {
|
||||
let total = 0;
|
||||
let passed = 0;
|
||||
|
||||
// 실제 사용 시나리오 시뮬레이션
|
||||
total++;
|
||||
try {
|
||||
const scenarios = [
|
||||
{ timing: "after", count: 10, actionType: "save" },
|
||||
{ timing: "before", count: 5, actionType: "delete" },
|
||||
{ timing: "replace", count: 3, actionType: "submit" },
|
||||
];
|
||||
|
||||
await benchmark.measure("Integration Load Test", async () => {
|
||||
for (const scenario of scenarios) {
|
||||
const promises = Array.from({ length: scenario.count }, async (_, i) => {
|
||||
const config: ButtonTypeConfig = {
|
||||
actionType: scenario.actionType as ButtonActionType,
|
||||
enableDataflowControl: true,
|
||||
dataflowTiming: scenario.timing as any,
|
||||
dataflowConfig: {
|
||||
controlMode: "simple",
|
||||
selectedDiagramId: 1,
|
||||
selectedRelationshipId: `rel-${i}`,
|
||||
},
|
||||
};
|
||||
|
||||
return await optimizedButtonDataflowService.executeButtonWithDataflow(
|
||||
`integration-${scenario.timing}-${i}`,
|
||||
scenario.actionType as ButtonActionType,
|
||||
config,
|
||||
{ testData: `value-${i}` },
|
||||
"DEFAULT",
|
||||
);
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
});
|
||||
|
||||
const totalTime = benchmark.getResults().details.slice(-1)[0].time;
|
||||
const totalRequests = scenarios.reduce((sum, s) => sum + s.count, 0);
|
||||
const averageTime = totalTime / totalRequests;
|
||||
|
||||
// 통합 테스트에서는 평균 300ms 이내면 통과
|
||||
if (averageTime < 300) {
|
||||
passed++;
|
||||
console.log(` ✅ Integration load test: PASSED (avg ${averageTime.toFixed(2)}ms < 300ms)`);
|
||||
} else {
|
||||
console.log(` ❌ Integration load test: FAILED (avg ${averageTime.toFixed(2)}ms >= 300ms)`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(" ❌ Integration test: ERROR -", error.message);
|
||||
}
|
||||
|
||||
return { total, passed };
|
||||
}
|
||||
|
||||
// 스크립트가 직접 실행될 때만 테스트 실행
|
||||
if (require.main === module) {
|
||||
runPerformanceTests();
|
||||
}
|
||||
|
||||
export { runPerformanceTests };
|
||||
@@ -1,179 +0,0 @@
|
||||
/**
|
||||
* COMPANY_7 사용자(topseal_admin) 발주관리 결재 시스템 테스트
|
||||
* 실행: npx tsx frontend/scripts/po-approval-company7-test.ts
|
||||
*/
|
||||
import { chromium } from "playwright";
|
||||
import { writeFileSync } from "fs";
|
||||
|
||||
const BASE_URL = "http://localhost:9771";
|
||||
const LOGIN_ID = "topseal_admin";
|
||||
const LOGIN_PW = "qlalfqjsgh11";
|
||||
const SCREEN_URL = `${BASE_URL}/screen/COMPANY_7_064`;
|
||||
|
||||
const results: string[] = [];
|
||||
const screenshotDir = "/Users/gbpark/ERP-node/approval-company7-screenshots";
|
||||
|
||||
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 screenshot = async (name: string) => {
|
||||
const path = `${screenshotDir}/${name}.png`;
|
||||
await page.screenshot({ path, fullPage: true });
|
||||
results.push(` 스크린샷: ${name}.png`);
|
||||
};
|
||||
|
||||
try {
|
||||
// Step 1: 로그인
|
||||
results.push("\n=== Step 1: 로그인 (topseal_admin) ===");
|
||||
await page.goto(BASE_URL, { waitUntil: "domcontentloaded", timeout: 30000 });
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
const loginPage = page.locator('input[type="text"], input[name="userId"], #userId').first();
|
||||
if ((await loginPage.count()) > 0) {
|
||||
await page.getByPlaceholder("사용자 ID를 입력하세요").or(page.locator('#userId, input[name="userId"]')).first().fill(LOGIN_ID);
|
||||
await page.getByPlaceholder("비밀번호를 입력하세요").or(page.locator('#password, input[name="password"]')).first().fill(LOGIN_PW);
|
||||
await page.getByRole("button", { name: "로그인" }).or(page.locator('button[type="submit"]')).first().click();
|
||||
await page.waitForTimeout(3000);
|
||||
try {
|
||||
await page.waitForURL((url) => !url.toString().includes("/login"), { timeout: 25000 });
|
||||
} catch {
|
||||
results.push(" WARN: 로그인 후 URL 변경 없음 - 로그인 실패 가능");
|
||||
}
|
||||
}
|
||||
await page.waitForTimeout(3000);
|
||||
const urlAfterLogin = page.url();
|
||||
results.push(` 현재 URL: ${urlAfterLogin}`);
|
||||
await screenshot("01-after-login");
|
||||
if (urlAfterLogin.includes("/login")) {
|
||||
results.push(" FAIL: 로그인 실패 - 여전히 로그인 페이지에 있음");
|
||||
} else {
|
||||
results.push(" OK: 로그인 완료");
|
||||
}
|
||||
|
||||
// Step 2: 구매관리 메뉴 또는 직접 URL
|
||||
results.push("\n=== Step 2: 발주관리 화면 이동 ===");
|
||||
const purchaseMenu = page.locator('text="구매관리"').first();
|
||||
const hasPurchaseMenu = (await purchaseMenu.count()) > 0;
|
||||
if (hasPurchaseMenu) {
|
||||
await purchaseMenu.click();
|
||||
await page.waitForTimeout(800);
|
||||
const poMenu = page.locator('text="발주관리"').or(page.locator('text="발주 관리"')).first();
|
||||
if ((await poMenu.count()) > 0) {
|
||||
await poMenu.click();
|
||||
await page.waitForTimeout(3000);
|
||||
} else {
|
||||
await page.goto(SCREEN_URL, { waitUntil: "domcontentloaded", timeout: 20000 });
|
||||
await page.waitForTimeout(5000);
|
||||
}
|
||||
} else {
|
||||
results.push(" INFO: 구매관리 메뉴 없음, 직접 URL 이동");
|
||||
await page.goto(SCREEN_URL, { waitUntil: "domcontentloaded", timeout: 20000 });
|
||||
await page.waitForTimeout(5000);
|
||||
}
|
||||
await screenshot("02-po-screen");
|
||||
results.push(" OK: 발주관리 화면 로드");
|
||||
|
||||
// Step 3: 그리드 컬럼 상세 확인
|
||||
results.push("\n=== Step 3: 그리드 컬럼 및 데이터 확인 ===");
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
const headers = await page.locator("table th, [role='columnheader']").allTextContents();
|
||||
const headerTexts = headers.map((h) => h.trim()).filter((h) => h.length > 0);
|
||||
results.push(` 컬럼 헤더 (전체): ${JSON.stringify(headerTexts)}`);
|
||||
|
||||
const firstCol = headerTexts[0] || "";
|
||||
const isFirstColKorean = firstCol === "결재상태";
|
||||
const isFirstColEnglish = firstCol === "approval_status" || firstCol.toLowerCase().includes("approval");
|
||||
results.push(` 첫 번째 컬럼: "${firstCol}"`);
|
||||
results.push(isFirstColKorean ? " 결재상태(한글) 표시됨" : isFirstColEnglish ? " approval_status(영문) 표시됨" : ` 기타: ${firstCol}`);
|
||||
|
||||
const rows = await page.locator("table tbody tr, [role='row']").count();
|
||||
const hasEmptyMsg = (await page.locator('text="데이터가 없습니다"').count()) > 0;
|
||||
results.push(` 데이터 행 수: ${rows}`);
|
||||
results.push(hasEmptyMsg ? " 빈 그리드: '데이터가 없습니다' 메시지 표시" : " 데이터 있음");
|
||||
|
||||
if (rows > 0 && !hasEmptyMsg) {
|
||||
const firstColCells = await page.locator("table tbody tr td:first-child").allTextContents();
|
||||
results.push(` 첫 번째 컬럼 값(샘플): ${JSON.stringify(firstColCells.slice(0, 5))}`);
|
||||
|
||||
const poNumbers = await page.locator("table tbody td").filter({ hasText: /PO-|발주/ }).allTextContents();
|
||||
results.push(` 발주번호 형식 데이터: ${poNumbers.length > 0 ? JSON.stringify(poNumbers.slice(0, 5)) : "없음"}`);
|
||||
}
|
||||
|
||||
await screenshot("03-grid-detail");
|
||||
results.push(" OK: 그리드 상세 스크린샷 저장");
|
||||
|
||||
// Step 4: 결재 요청 버튼 확인
|
||||
results.push("\n=== Step 4: 결재 요청 버튼 확인 ===");
|
||||
const approvalBtn = page.getByRole("button", { name: "결재 요청" }).or(page.locator('button:has-text("결재 요청")'));
|
||||
const hasApprovalBtn = (await approvalBtn.count()) > 0;
|
||||
results.push(hasApprovalBtn ? " OK: '결재 요청' 파란색 버튼 확인됨" : " FAIL: '결재 요청' 버튼 없음");
|
||||
await screenshot("04-approval-button");
|
||||
|
||||
// Step 5: 행 선택 후 결재 요청 클릭
|
||||
results.push("\n=== Step 5: 행 선택 후 결재 요청 ===");
|
||||
const firstRow = page.locator("table tbody tr").first();
|
||||
const checkbox = page.locator("table tbody tr input[type='checkbox']").first();
|
||||
const hasRows = (await firstRow.count()) > 0;
|
||||
const hasCheckbox = (await checkbox.count()) > 0;
|
||||
|
||||
if (hasRows) {
|
||||
if (hasCheckbox) {
|
||||
await checkbox.click();
|
||||
await page.waitForTimeout(300);
|
||||
} else {
|
||||
await firstRow.click();
|
||||
await page.waitForTimeout(300);
|
||||
}
|
||||
results.push(" OK: 행 선택 완료");
|
||||
} else {
|
||||
results.push(" INFO: 데이터 행 없음, 행 선택 없이 진행");
|
||||
}
|
||||
|
||||
if (hasApprovalBtn) {
|
||||
await approvalBtn.first().click({ force: true });
|
||||
await page.waitForTimeout(2000);
|
||||
await screenshot("05-approval-modal");
|
||||
|
||||
const modal = page.locator('[role="dialog"]');
|
||||
const modalOpened = (await modal.count()) > 0;
|
||||
results.push(modalOpened ? " OK: 결재 모달 열림" : " FAIL: 결재 모달 열리지 않음");
|
||||
|
||||
if (modalOpened) {
|
||||
const searchInput = page.getByPlaceholder("이름 또는 사번으로 검색...").or(page.locator('[role="dialog"] input[placeholder*="검색"]'));
|
||||
if ((await searchInput.count()) > 0) {
|
||||
await searchInput.first().fill("김");
|
||||
await page.waitForTimeout(2000);
|
||||
await screenshot("06-approver-search-results");
|
||||
|
||||
const searchResults = page.locator('[role="dialog"] div.max-h-48 button, [role="dialog"] div.overflow-y-auto button');
|
||||
const resultCount = await searchResults.count();
|
||||
const resultTexts = await searchResults.allTextContents();
|
||||
results.push(` 결재자 검색 결과: ${resultCount}명`);
|
||||
if (resultTexts.length > 0) {
|
||||
results.push(` 결재자 목록: ${JSON.stringify(resultTexts.slice(0, 10))}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await screenshot("07-final");
|
||||
} catch (err: any) {
|
||||
results.push(`\nERROR: ${err.message}`);
|
||||
await page.screenshot({ path: `${screenshotDir}/error.png`, fullPage: true }).catch(() => {});
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
const output = results.join("\n");
|
||||
console.log("\n" + "=".repeat(60));
|
||||
console.log("COMPANY_7 (topseal_admin) 발주관리 결재 테스트 결과");
|
||||
console.log("=".repeat(60));
|
||||
console.log(output);
|
||||
console.log("=".repeat(60));
|
||||
writeFileSync("/Users/gbpark/ERP-node/approval-company7-report.txt", output);
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,174 +0,0 @@
|
||||
/**
|
||||
* 발주관리 화면 결재 시스템 E2E 테스트
|
||||
* 메뉴: 구매관리 → 발주관리
|
||||
* 실행: npx tsx frontend/scripts/purchase-order-approval-test.ts
|
||||
*/
|
||||
import { chromium } from "playwright";
|
||||
import { writeFileSync } from "fs";
|
||||
|
||||
const BASE_URL = "http://localhost:9771";
|
||||
const LOGIN_ID = "wace";
|
||||
const LOGIN_PW = "qlalfqjsgh11";
|
||||
|
||||
const results: string[] = [];
|
||||
const screenshotDir = "/Users/gbpark/ERP-node/approval-test-screenshots";
|
||||
|
||||
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 screenshot = async (name: string) => {
|
||||
const path = `${screenshotDir}/${name}.png`;
|
||||
await page.screenshot({ path, fullPage: true });
|
||||
results.push(` 스크린샷: ${name}.png`);
|
||||
};
|
||||
|
||||
try {
|
||||
// Step 1: 로그인
|
||||
results.push("\n=== Step 1: 로그인 ===");
|
||||
await page.goto(BASE_URL, { waitUntil: "domcontentloaded", timeout: 30000 });
|
||||
await screenshot("01-login-page");
|
||||
|
||||
const userIdInput = page.getByPlaceholder("사용자 ID를 입력하세요").or(page.locator('#userId, input[name="userId"]'));
|
||||
const pwInput = page.getByPlaceholder("비밀번호를 입력하세요").or(page.locator('#password, input[name="password"]'));
|
||||
const loginBtn = page.getByRole("button", { name: "로그인" }).or(page.locator('button[type="submit"]'));
|
||||
|
||||
await userIdInput.first().fill(LOGIN_ID);
|
||||
await pwInput.first().fill(LOGIN_PW);
|
||||
await loginBtn.first().click();
|
||||
await page.waitForURL((url) => !url.toString().includes("/login"), { timeout: 30000 });
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
await page.waitForTimeout(5000); // 메뉴 로드 대기
|
||||
await screenshot("02-after-login");
|
||||
results.push(" OK: 로그인 완료, 대시보드 로드");
|
||||
|
||||
// Step 2: 구매관리 → 발주관리 메뉴 이동 (또는 직접 URL)
|
||||
results.push("\n=== Step 2: 구매관리 → 발주관리 메뉴 이동 ===");
|
||||
const purchaseMenu = page.locator('text="구매관리"').first();
|
||||
const hasPurchaseMenu = (await purchaseMenu.count()) > 0;
|
||||
let poScreenLoaded = false;
|
||||
|
||||
if (hasPurchaseMenu) {
|
||||
await purchaseMenu.click();
|
||||
await page.waitForTimeout(800);
|
||||
await screenshot("03-purchase-menu-expanded");
|
||||
|
||||
const poMenu = page.locator('text="발주관리"').or(page.locator('text="발주 관리"')).first();
|
||||
const hasPoMenu = (await poMenu.count()) > 0;
|
||||
if (hasPoMenu) {
|
||||
await poMenu.click();
|
||||
await page.waitForTimeout(3000);
|
||||
await screenshot("04-po-screen-loaded");
|
||||
poScreenLoaded = true;
|
||||
results.push(" OK: 메뉴로 발주관리 화면 이동 완료");
|
||||
}
|
||||
}
|
||||
|
||||
if (!poScreenLoaded) {
|
||||
results.push(" INFO: 메뉴에서 발주관리 미발견, 직접 URL로 이동");
|
||||
const allMenuTexts = await page.locator("aside a, aside button, aside [role='menuitem']").allTextContents();
|
||||
results.push(` 메뉴 목록: ${JSON.stringify(allMenuTexts.slice(0, 30))}`);
|
||||
await page.goto(`${BASE_URL}/screen/COMPANY_7_064`, { waitUntil: "domcontentloaded", timeout: 20000 });
|
||||
await page.waitForTimeout(4000);
|
||||
await screenshot("04-po-screen-loaded");
|
||||
results.push(" OK: /screen/COMPANY_7_064 직접 이동 완료");
|
||||
}
|
||||
|
||||
// Step 3: 그리드 컬럼 확인
|
||||
results.push("\n=== Step 3: 그리드 컬럼 확인 ===");
|
||||
await page.waitForTimeout(2000);
|
||||
await screenshot("05-grid-columns");
|
||||
|
||||
const headers = await page.locator("table th, [role='columnheader']").allTextContents();
|
||||
const headerTexts = headers.map((h) => h.trim()).filter((h) => h.length > 0);
|
||||
results.push(` 컬럼 목록: ${JSON.stringify(headerTexts)}`);
|
||||
|
||||
const hasApprovalColumn = headerTexts.some((h) => h.includes("결재상태"));
|
||||
results.push(hasApprovalColumn ? " OK: '결재상태' 컬럼 확인됨" : " FAIL: '결재상태' 컬럼 없음");
|
||||
|
||||
// 결재상태 값 확인 (작성중 등)
|
||||
const statusCellTexts = await page.locator("table tbody td").allTextContents();
|
||||
const approvalValues = statusCellTexts.filter((t) =>
|
||||
["작성중", "결재중", "결재완료", "반려"].some((s) => t.includes(s))
|
||||
);
|
||||
results.push(` 결재상태 값: ${approvalValues.length > 0 ? approvalValues.join(", ") : "데이터 없음 또는 해당 값 없음"}`);
|
||||
|
||||
// Step 4: 행 선택 후 결재 요청 버튼 클릭
|
||||
results.push("\n=== Step 4: 행 선택 및 결재 요청 버튼 클릭 ===");
|
||||
const firstRow = page.locator("table tbody tr, [role='row']").first();
|
||||
const hasRows = (await firstRow.count()) > 0;
|
||||
if (hasRows) {
|
||||
await firstRow.click({ force: true });
|
||||
await page.waitForTimeout(500);
|
||||
await screenshot("06-row-selected");
|
||||
results.push(" OK: 첫 번째 행 선택");
|
||||
} else {
|
||||
results.push(" INFO: 데이터 행 없음, 행 선택 없이 진행");
|
||||
}
|
||||
|
||||
const approvalBtn = page.getByRole("button", { name: "결재 요청" }).or(page.locator('button:has-text("결재 요청")'));
|
||||
const hasApprovalBtn = (await approvalBtn.count()) > 0;
|
||||
if (!hasApprovalBtn) {
|
||||
results.push(" FAIL: '결재 요청' 버튼 없음");
|
||||
} else {
|
||||
await approvalBtn.first().click({ force: true });
|
||||
await page.waitForTimeout(2000);
|
||||
await screenshot("07-approval-modal-opened");
|
||||
|
||||
const modal = page.locator('[role="dialog"]');
|
||||
const modalOpened = (await modal.count()) > 0;
|
||||
results.push(modalOpened ? " OK: 결재 모달 열림" : " FAIL: 결재 모달 열리지 않음");
|
||||
}
|
||||
|
||||
// Step 5: 결재자 검색 테스트
|
||||
results.push("\n=== Step 5: 결재자 검색 테스트 ===");
|
||||
const searchInput = page.getByPlaceholder("이름 또는 사번으로 검색...").or(
|
||||
page.locator('input[placeholder*="검색"]')
|
||||
);
|
||||
const hasSearchInput = (await searchInput.count()) > 0;
|
||||
if (!hasSearchInput) {
|
||||
results.push(" FAIL: 결재자 검색 입력 필드 없음");
|
||||
} else {
|
||||
await searchInput.first().fill("김");
|
||||
await page.waitForTimeout(2000);
|
||||
await screenshot("08-approver-search-results");
|
||||
|
||||
// 검색 결과 확인 (ApprovalRequestModal: div.max-h-48 내부 button)
|
||||
const searchResults = page.locator(
|
||||
'[role="dialog"] div.max-h-48 button, [role="dialog"] div.overflow-y-auto button'
|
||||
);
|
||||
const resultCount = await searchResults.count();
|
||||
const resultTexts = await searchResults.allTextContents();
|
||||
results.push(` 검색 결과 수: ${resultCount}명`);
|
||||
if (resultTexts.length > 0) {
|
||||
const names = resultTexts.map((t) => t.trim()).filter((t) => t.length > 0);
|
||||
results.push(` 결재자 목록: ${JSON.stringify(names.slice(0, 10))}`);
|
||||
}
|
||||
|
||||
// "검색 결과가 없습니다" 또는 "검색 중" 메시지 확인
|
||||
const noResultsMsg = page.locator('text="검색 결과가 없습니다"');
|
||||
const searchingMsg = page.locator('text="검색 중"');
|
||||
if ((await noResultsMsg.count()) > 0) results.push(" (검색 결과 없음 메시지 표시됨)");
|
||||
if ((await searchingMsg.count()) > 0) results.push(" (검색 중 메시지 표시됨 - 대기 부족 가능)");
|
||||
}
|
||||
|
||||
// 최종 스크린샷
|
||||
await screenshot("09-final-state");
|
||||
} catch (err: any) {
|
||||
results.push(`\nERROR: ${err.message}`);
|
||||
await page.screenshot({ path: `${screenshotDir}/error.png`, fullPage: true }).catch(() => {});
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
const output = results.join("\n");
|
||||
console.log("\n" + "=".repeat(60));
|
||||
console.log("발주관리 결재 시스템 테스트 결과");
|
||||
console.log("=".repeat(60));
|
||||
console.log(output);
|
||||
console.log("=".repeat(60));
|
||||
writeFileSync("/Users/gbpark/ERP-node/approval-test-report.txt", output);
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,101 +0,0 @@
|
||||
/**
|
||||
* 결재 모달 테스트: 버튼 클릭 vs CustomEvent 직접 발송
|
||||
* 실행: npx tsx frontend/scripts/screen-approval-modal-test.ts
|
||||
*/
|
||||
import { chromium } from "playwright";
|
||||
import { writeFileSync } from "fs";
|
||||
|
||||
const BASE_URL = "http://localhost:9771";
|
||||
const LOGIN_ID = "wace";
|
||||
const LOGIN_PW = "qlalfqjsgh11";
|
||||
const SCREEN_URL = `${BASE_URL}/screen/COMPANY_7_064`;
|
||||
|
||||
const results: string[] = [];
|
||||
|
||||
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();
|
||||
|
||||
try {
|
||||
// 1. 로그인
|
||||
results.push("=== 1. 로그인 ===");
|
||||
await page.goto(`${BASE_URL}/login`, { waitUntil: "networkidle", timeout: 15000 });
|
||||
await page.getByPlaceholder("사용자 ID를 입력하세요").or(page.locator('#userId, input[name="userId"]')).first().fill(LOGIN_ID);
|
||||
await page.getByPlaceholder("비밀번호를 입력하세요").or(page.locator('#password, input[name="password"]')).first().fill(LOGIN_PW);
|
||||
await page.getByRole("button", { name: "로그인" }).or(page.locator('button[type="submit"]')).first().click();
|
||||
await page.waitForURL((url) => !url.toString().includes("/login"), { timeout: 30000 });
|
||||
await page.waitForLoadState("networkidle");
|
||||
results.push("OK: 로그인 성공");
|
||||
|
||||
// 2. 화면 이동 및 대기
|
||||
results.push("\n=== 2. 화면 COMPANY_7_064 이동 ===");
|
||||
await page.goto(SCREEN_URL, { waitUntil: "networkidle", timeout: 20000 });
|
||||
await page.waitForTimeout(3000);
|
||||
results.push("OK: 페이지 로드 완료");
|
||||
|
||||
// 3. 전체 페이지 스크린샷
|
||||
results.push("\n=== 3. 전체 페이지 스크린샷 ===");
|
||||
await page.screenshot({ path: "/Users/gbpark/ERP-node/approval-test-1-full-page.png", fullPage: true });
|
||||
results.push("OK: approval-test-1-full-page.png 저장");
|
||||
|
||||
// 4. "결재 요청" 버튼 클릭
|
||||
results.push("\n=== 4. 결재 요청 버튼 클릭 ===");
|
||||
const approvalBtn = page.getByRole("button", { name: "결재 요청" }).or(page.locator('button:has-text("결재 요청")'));
|
||||
await approvalBtn.first().click({ force: true });
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// 5. 클릭 후 스크린샷
|
||||
results.push("\n=== 5. 클릭 후 스크린샷 ===");
|
||||
await page.screenshot({ path: "/Users/gbpark/ERP-node/approval-test-2-after-button-click.png", fullPage: true });
|
||||
results.push("OK: approval-test-2-after-button-click.png 저장");
|
||||
|
||||
// 6. 모달 등장 여부 확인
|
||||
results.push("\n=== 6. 버튼 클릭 후 모달 확인 ===");
|
||||
const modalAfterClick = page.locator('[role="dialog"]');
|
||||
const modalVisibleAfterClick = (await modalAfterClick.count()) > 0;
|
||||
results.push(modalVisibleAfterClick ? "OK: 버튼 클릭으로 모달 열림" : "FAIL: 버튼 클릭 후 모달 없음");
|
||||
|
||||
// 7. CustomEvent 직접 발송 (모달이 없었을 때)
|
||||
results.push("\n=== 7. CustomEvent 직접 발송 ===");
|
||||
await page.evaluate(() => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("open-approval-modal", {
|
||||
detail: { targetTable: "purchase_order_mng", targetRecordId: "test-123" },
|
||||
})
|
||||
);
|
||||
});
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// 8. CustomEvent 발송 후 스크린샷
|
||||
results.push("\n=== 8. CustomEvent 발송 후 스크린샷 ===");
|
||||
await page.screenshot({ path: "/Users/gbpark/ERP-node/approval-test-3-after-customevent.png", fullPage: true });
|
||||
results.push("OK: approval-test-3-after-customevent.png 저장");
|
||||
|
||||
// 9. CustomEvent 발송 후 모달 확인
|
||||
results.push("\n=== 9. CustomEvent 발송 후 모달 확인 ===");
|
||||
const modalAfterEvent = page.locator('[role="dialog"]');
|
||||
const modalVisibleAfterEvent = (await modalAfterEvent.count()) > 0;
|
||||
results.push(modalVisibleAfterEvent ? "OK: CustomEvent 발송으로 모달 열림" : "FAIL: CustomEvent 발송 후에도 모달 없음");
|
||||
|
||||
// 10. 최종 요약
|
||||
results.push("\n=== 10. 최종 요약 ===");
|
||||
results.push(`버튼 클릭 → 모달: ${modalVisibleAfterClick ? "YES" : "NO"}`);
|
||||
results.push(`CustomEvent 발송 → 모달: ${modalVisibleAfterEvent ? "YES" : "NO"}`);
|
||||
} catch (err: any) {
|
||||
results.push(`\nERROR: ${err.message}`);
|
||||
await page.screenshot({ path: "/Users/gbpark/ERP-node/approval-test-error.png", fullPage: true }).catch(() => {});
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
const output = results.join("\n");
|
||||
console.log("\n" + "=".repeat(60));
|
||||
console.log("결재 모달 테스트 결과");
|
||||
console.log("=".repeat(60));
|
||||
console.log(output);
|
||||
console.log("=".repeat(60));
|
||||
writeFileSync("/Users/gbpark/ERP-node/approval-modal-test-result.txt", output);
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,135 +0,0 @@
|
||||
/**
|
||||
* 수주관리 화면(68) 검증 스크립트
|
||||
* - 로그인 상태 확인 후 필요시 로그인
|
||||
* - /screens/68 접속
|
||||
* - 테이블, 검색 필터, 버튼 확인
|
||||
*/
|
||||
|
||||
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");
|
||||
|
||||
async function main() {
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const context = await browser.newContext({
|
||||
viewport: { width: 1280, height: 900 },
|
||||
storageState: undefined, // 새 세션 (쿠키 유지 안 함 - 이전 세션 로그인 상태 확인용)
|
||||
});
|
||||
const page = await context.newPage();
|
||||
|
||||
const steps: { step: string; success: boolean; message?: string }[] = [];
|
||||
|
||||
try {
|
||||
// Step 1: 로그인 페이지 접속 및 로그인 (Playwright는 매번 새 브라우저이므로 항상 로그인 필요)
|
||||
console.log("Step 1: 로그인...");
|
||||
await page.goto(`${BASE_URL}/login`, { waitUntil: "domcontentloaded", timeout: 15000 });
|
||||
await page.waitForTimeout(1000);
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "s68-01-login-page.png"), fullPage: true });
|
||||
|
||||
await page.fill("#userId", "wace");
|
||||
await page.fill("#password", "qlalfqjsgh11");
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "s68-02-login-filled.png"), fullPage: true });
|
||||
await page.locator('button[type="submit"]').first().click();
|
||||
await page.waitForTimeout(3000);
|
||||
steps.push({ step: "로그인", success: true });
|
||||
|
||||
// Step 2: /screens/68 접속
|
||||
console.log("Step 2: /screens/68 접속...");
|
||||
await page.goto(`${BASE_URL}/screens/68`, { waitUntil: "domcontentloaded", timeout: 15000 });
|
||||
|
||||
// 5초 대기 (페이지 완전 로드)
|
||||
console.log("Step 3: 5초 대기 (페이지 완전 로드)...");
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "s68-03-screen-loaded.png"), fullPage: true });
|
||||
steps.push({ step: "/screens/68 접속 및 5초 대기", success: true });
|
||||
|
||||
// Step 3: 요소 검증
|
||||
console.log("Step 3: 요소 검증...");
|
||||
|
||||
const hasError = await page.locator('text="화면을 찾을 수 없습니다"').count() > 0;
|
||||
if (hasError) {
|
||||
steps.push({ step: "화면 로드", success: false, message: "404 - 화면을 찾을 수 없습니다" });
|
||||
} else {
|
||||
// 테이블 (TableListComponent: role=grid, table, thead/tbody)
|
||||
const tableSelectors = [
|
||||
"table",
|
||||
"[role='grid']",
|
||||
"[role='table']",
|
||||
"thead",
|
||||
"tbody",
|
||||
".table-mobile-fixed",
|
||||
"[class*='ag-']",
|
||||
"[class*='table-list']",
|
||||
];
|
||||
let tableFound = false;
|
||||
for (const sel of tableSelectors) {
|
||||
if ((await page.locator(sel).count()) > 0) {
|
||||
tableFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 검색/필터 (input, select, 테이블 툴바 검색/필터 버튼)
|
||||
const filterSelectors = [
|
||||
"input",
|
||||
"select",
|
||||
'input[type="text"]',
|
||||
'input[type="search"]',
|
||||
'input[placeholder*="검색"]',
|
||||
"button:has-text('검색')",
|
||||
"button:has-text('필터')",
|
||||
"[class*='filter']",
|
||||
"[class*='search']",
|
||||
];
|
||||
let filterFound = false;
|
||||
for (const sel of filterSelectors) {
|
||||
if ((await page.locator(sel).count()) > 0) {
|
||||
filterFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 버튼
|
||||
const buttonCount = await page.locator("button, [role='button'], input[type='submit']").count();
|
||||
const buttonsFound = buttonCount > 0;
|
||||
|
||||
steps.push({
|
||||
step: "화면 요소 검증",
|
||||
success: tableFound && filterFound && buttonsFound,
|
||||
message: `테이블: ${tableFound ? "O" : "X"}, 검색: ${filterFound ? "O" : "X"}, 버튼: ${buttonsFound ? "O" : "X"}`,
|
||||
});
|
||||
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "s68-04-verified.png"), fullPage: true });
|
||||
|
||||
const finalSuccess = tableFound && filterFound && buttonsFound && !hasError;
|
||||
console.log("\n=== 검증 결과 ===");
|
||||
steps.forEach((s) => console.log(`${s.step}: ${s.success ? "성공" : "실패"}${s.message ? ` - ${s.message}` : ""}`));
|
||||
console.log(`\n최종 판정: ${finalSuccess ? "성공" : "실패"}`);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(SCREENSHOT_DIR, "s68-result.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
steps,
|
||||
checks: { table: tableFound, filter: filterFound, buttons: buttonsFound },
|
||||
finalSuccess: finalSuccess ? "성공" : "실패",
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("오류:", error.message);
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "s68-99-error.png"), fullPage: true }).catch(() => {});
|
||||
steps.push({ step: "오류", success: false, message: error.message });
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,163 +0,0 @@
|
||||
/**
|
||||
* 화면 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<ScreenResult> {
|
||||
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();
|
||||
@@ -1,94 +0,0 @@
|
||||
/**
|
||||
* 카드 목록 컴포넌트 E2E 테스트
|
||||
* 실행: npx tsx scripts/test-card-list-e2e.ts
|
||||
*/
|
||||
import { chromium } from "playwright";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
|
||||
const BASE_URL = "http://localhost:9771";
|
||||
const SCREEN_URL = "/pop/screens/4114";
|
||||
const SCREENSHOT_DIR = path.join(process.cwd(), "test-screenshots");
|
||||
|
||||
async function ensureDir(dir: string) {
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log("카드 목록 컴포넌트 E2E 테스트 시작...");
|
||||
await ensureDir(SCREENSHOT_DIR);
|
||||
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const context = await browser.newContext({ viewport: { width: 1280, height: 800 } });
|
||||
const page = await context.newPage();
|
||||
|
||||
const results: string[] = [];
|
||||
|
||||
try {
|
||||
// 1. 페이지 로드
|
||||
console.log("1. 페이지 로드 중...");
|
||||
await page.goto(`${BASE_URL}${SCREEN_URL}`, { waitUntil: "networkidle", timeout: 15000 });
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// 카드 목록 컴포넌트 확인
|
||||
const cardContainer = await page.locator('[class*="grid"]').first();
|
||||
const cardCount = await page.locator(".rounded-lg.border.bg-card").count();
|
||||
const hasCards = cardCount > 0;
|
||||
results.push(`1. 카드 목록 표시: ${hasCards ? "OK" : "FAIL"} (카드 ${cardCount}개)`);
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "01-loaded.png") });
|
||||
|
||||
// 2. "더보기" 버튼 클릭
|
||||
const moreBtn = page.getByRole("button", { name: /더보기/ });
|
||||
const moreBtnCount = await moreBtn.count();
|
||||
|
||||
if (moreBtnCount > 0) {
|
||||
console.log("2. 더보기 버튼 클릭...");
|
||||
await moreBtn.first().click();
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
const cardCountAfter = await page.locator(".rounded-lg.border.bg-card").count();
|
||||
const expanded = cardCountAfter > cardCount;
|
||||
results.push(`2. 더보기 클릭 후 확장: ${expanded ? "OK" : "카드 수 변화 없음"} (${cardCount} -> ${cardCountAfter})`);
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "02-expanded.png") });
|
||||
|
||||
// 3. 페이지네이션 확인
|
||||
const prevBtn = page.getByRole("button", { name: /이전/ });
|
||||
const nextBtn = page.getByRole("button", { name: /다음/ });
|
||||
const hasPagination = (await prevBtn.count() > 0) || (await nextBtn.count() > 0);
|
||||
results.push(`3. 페이지네이션 버튼: ${hasPagination ? "OK" : "없음 (데이터 적음 시 정상)"}`);
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "03-pagination.png") });
|
||||
|
||||
// 4. 접기 버튼 클릭
|
||||
const collapseBtn = page.getByRole("button", { name: /접기/ });
|
||||
if (await collapseBtn.count() > 0) {
|
||||
console.log("4. 접기 버튼 클릭...");
|
||||
await collapseBtn.first().click();
|
||||
await page.waitForTimeout(1000);
|
||||
const cardCountCollapsed = await page.locator(".rounded-lg.border.bg-card").count();
|
||||
results.push(`4. 접기 후: OK (카드 ${cardCountCollapsed}개로 복원)`);
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "04-collapsed.png") });
|
||||
} else {
|
||||
results.push("4. 접기 버튼: 없음 (확장 안됐을 수 있음)");
|
||||
}
|
||||
} else {
|
||||
results.push("2. 더보기 버튼: 없음 (카드가 적거나 모두 표시됨)");
|
||||
results.push("3. 페이지네이션: N/A");
|
||||
results.push("4. 접기: N/A");
|
||||
}
|
||||
|
||||
// 결과 출력
|
||||
console.log("\n=== 테스트 결과 ===");
|
||||
results.forEach((r) => console.log(r));
|
||||
console.log(`\n스크린샷 저장: ${SCREENSHOT_DIR}`);
|
||||
} catch (err) {
|
||||
console.error("테스트 실패:", err);
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "error.png") });
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,135 +0,0 @@
|
||||
/**
|
||||
* formData 로그 테스트 스크립트
|
||||
* - http://localhost:9771/screens/1599 접속
|
||||
* - P003 행 선택 → 추가 버튼 클릭 → 장비 선택 → 저장 전/후 콘솔 로그 수집
|
||||
*
|
||||
* 실행: npx tsx scripts/test-formdata-logs.ts
|
||||
* (Playwright 필요: npx playwright install chromium)
|
||||
*/
|
||||
|
||||
import { chromium } from "playwright";
|
||||
|
||||
const TARGET_URL = "http://localhost:9771/screens/1599?menuObjid=1762422235300";
|
||||
const LOGIN = { userId: "topseal_admin", password: "1234" };
|
||||
|
||||
const TARGET_LOGS = ["🔵", "🟡", "🔴", "process_code", "splitPanelParentData"];
|
||||
|
||||
async function main() {
|
||||
const browser = await chromium.launch({ headless: false });
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
|
||||
const consoleLogs: string[] = [];
|
||||
const errors: string[] = [];
|
||||
|
||||
page.on("console", (msg) => {
|
||||
const text = msg.text();
|
||||
const type = msg.type();
|
||||
if (type === "error") {
|
||||
errors.push(`[CONSOLE ERROR] ${text}`);
|
||||
}
|
||||
const hasTarget = TARGET_LOGS.some((t) => text.includes(t));
|
||||
if (hasTarget || type === "error") {
|
||||
consoleLogs.push(`[${type}] ${text}`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log("1. 페이지 이동:", TARGET_URL);
|
||||
await page.goto(TARGET_URL, { waitUntil: "domcontentloaded", timeout: 20000 });
|
||||
|
||||
// 로그인 필요 여부 확인
|
||||
const userIdInput = page.locator('input[name="userId"]').first();
|
||||
if (await userIdInput.isVisible().catch(() => false)) {
|
||||
console.log("2. 로그인 페이지 감지 - 로그인 진행");
|
||||
await page.fill('input[name="userId"]', LOGIN.userId);
|
||||
await page.fill('input[name="password"]', LOGIN.password);
|
||||
await page.click('button[type="submit"]').catch(() => page.click('button:has-text("로그인")'));
|
||||
await page.waitForTimeout(4000);
|
||||
}
|
||||
|
||||
console.log("3. 5초 대기 (페이지 로드)");
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
// 탭 확인 - 공정 마스터 (첫 번째 탭)
|
||||
const firstTab = page.getByRole("tab", { name: /공정 마스터/i }).or(page.locator('button:has-text("공정 마스터")')).first();
|
||||
if (await firstTab.isVisible().catch(() => false)) {
|
||||
console.log("4. '공정 마스터' 탭 클릭");
|
||||
await firstTab.click();
|
||||
await page.waitForTimeout(1500);
|
||||
}
|
||||
|
||||
// 좌측 패널 테이블 데이터 로드 대기
|
||||
console.log("5. 좌측 패널 데이터 로드 대기");
|
||||
await page.locator("table tbody tr").first().waitFor({ state: "visible", timeout: 25000 }).catch(() => {
|
||||
throw new Error("좌측 테이블에 데이터가 없습니다. process_mng에 P003 등 데이터가 있는지 확인하세요.");
|
||||
});
|
||||
|
||||
// P003 행 또는 첫 번째 행 클릭
|
||||
let rowToClick = page.locator('table tbody tr:has(td:has-text("P003"))').first();
|
||||
const hasP003 = await rowToClick.isVisible().catch(() => false);
|
||||
if (!hasP003) {
|
||||
console.log(" P003 미발견 - 첫 번째 행 클릭");
|
||||
rowToClick = page.locator("table tbody tr").first();
|
||||
}
|
||||
await rowToClick.click();
|
||||
await page.waitForTimeout(800);
|
||||
|
||||
// 우측 패널에서 '추가' 버튼 클릭 (모달 열기)
|
||||
console.log("6. '추가' 버튼 클릭");
|
||||
const addBtn = page.locator('button:has-text("추가")').first();
|
||||
await addBtn.click();
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// 모달이 열렸는지 확인
|
||||
const modal = page.locator('[role="dialog"], [data-state="open"]').first();
|
||||
await modal.waitFor({ state: "visible", timeout: 5000 }).catch(() => {});
|
||||
|
||||
// 모달 내 설비 드롭다운/콤보박스 선택 (v2-select, entity-search-input 등)
|
||||
console.log("7. 모달 내 설비 선택");
|
||||
const trigger = page.locator('[role="combobox"], button:has-text("선택"), button:has-text("설비")').first();
|
||||
if (await trigger.isVisible().catch(() => false)) {
|
||||
await trigger.click();
|
||||
await page.waitForTimeout(500);
|
||||
const option = page.locator('[role="option"], li[role="option"]').first();
|
||||
if (await option.isVisible().catch(() => false)) {
|
||||
await option.click();
|
||||
}
|
||||
} else {
|
||||
// select 태그인 경우
|
||||
const selectEl = page.locator('select').first();
|
||||
if (await selectEl.isVisible().catch(() => false)) {
|
||||
await selectEl.selectOption({ index: 1 });
|
||||
}
|
||||
}
|
||||
await page.waitForTimeout(800);
|
||||
|
||||
// 저장 전 콘솔 스냅샷
|
||||
console.log("\n=== 저장 전 콘솔 로그 (formData 관련) ===");
|
||||
consoleLogs.forEach((l) => console.log(l));
|
||||
if (errors.length) {
|
||||
console.log("\n=== 에러 ===");
|
||||
errors.forEach((e) => console.log(e));
|
||||
}
|
||||
|
||||
// 저장 버튼 클릭 (모달 내부의 저장 버튼)
|
||||
console.log("\n8. '저장' 버튼 클릭");
|
||||
const saveBtn = page.locator('[role="dialog"] button:has-text("저장"), [data-state="open"] button:has-text("저장")').first();
|
||||
await saveBtn.click();
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
// 저장 후 로그 수집
|
||||
console.log("\n=== 저장 후 콘솔 로그 (formData 관련) ===");
|
||||
consoleLogs.forEach((l) => console.log(l));
|
||||
if (errors.length) {
|
||||
console.log("\n=== 에러 ===");
|
||||
errors.forEach((e) => console.log(e));
|
||||
}
|
||||
|
||||
await page.waitForTimeout(2000);
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error("테스트 실패:", e);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,112 +0,0 @@
|
||||
/**
|
||||
* UI 리디자인 검증 스크립트
|
||||
* 1. 로그인 페이지 스크린샷
|
||||
* 2. 로그인
|
||||
* 3. 대시보드 스크린샷
|
||||
* 4. 사이드바 스크린샷
|
||||
*/
|
||||
|
||||
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");
|
||||
|
||||
async function main() {
|
||||
if (!fs.existsSync(SCREENSHOT_DIR)) {
|
||||
fs.mkdirSync(SCREENSHOT_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const context = await browser.newContext({ viewport: { width: 1280, height: 800 } });
|
||||
const page = await context.newPage();
|
||||
|
||||
try {
|
||||
// Step 1: 로그인 페이지 접속 및 스크린샷
|
||||
console.log("Step 1: 로그인 페이지 접속...");
|
||||
await page.goto(`${BASE_URL}/login`, { waitUntil: "commit", timeout: 30000 });
|
||||
await page.waitForTimeout(1500);
|
||||
await page.screenshot({
|
||||
path: path.join(SCREENSHOT_DIR, "01-login-page.png"),
|
||||
fullPage: true,
|
||||
});
|
||||
console.log(" -> 01-login-page.png 저장됨");
|
||||
|
||||
// Step 2: 로그인
|
||||
console.log("Step 2: 로그인...");
|
||||
await page.fill("#userId", "admin");
|
||||
await page.fill("#password", "1234");
|
||||
await page.click('button[type="submit"]');
|
||||
await page.waitForURL((url) => !url.pathname.includes("/login"), { timeout: 10000 }).catch(() => {});
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
const currentUrl = page.url();
|
||||
if (currentUrl.includes("/login")) {
|
||||
console.log(" -> 로그인 실패, 현재 URL:", currentUrl);
|
||||
} else {
|
||||
console.log(" -> 로그인 성공, 리다이렉트:", currentUrl);
|
||||
|
||||
// Step 3: 메인 페이지로 이동 (대시보드)
|
||||
if (!currentUrl.includes("/main") && !currentUrl.includes("/admin")) {
|
||||
await page.goto(`${BASE_URL}/main`, { waitUntil: "load", timeout: 20000 });
|
||||
await page.waitForTimeout(2000);
|
||||
}
|
||||
|
||||
// 대시보드 전체 스크린샷
|
||||
await page.screenshot({
|
||||
path: path.join(SCREENSHOT_DIR, "02-dashboard.png"),
|
||||
fullPage: true,
|
||||
});
|
||||
console.log(" -> 02-dashboard.png 저장됨");
|
||||
|
||||
// Step 4: 사이드바 포커스 스크린샷 (좌측 영역)
|
||||
const sidebar = page.locator("aside");
|
||||
if ((await sidebar.count()) > 0) {
|
||||
await sidebar.first().screenshot({
|
||||
path: path.join(SCREENSHOT_DIR, "03-sidebar.png"),
|
||||
});
|
||||
console.log(" -> 03-sidebar.png 저장됨");
|
||||
}
|
||||
|
||||
// Step 5: 테이블/그리드 화면으로 이동하여 스타일 확인
|
||||
console.log("Step 5: 테이블 화면 탐색...");
|
||||
const menuLinks = page.locator('aside a[href*="/screens/"], aside [role="button"]');
|
||||
const linkCount = await menuLinks.count();
|
||||
if (linkCount > 0) {
|
||||
await menuLinks.first().click();
|
||||
await page.waitForTimeout(2500);
|
||||
await page.screenshot({
|
||||
path: path.join(SCREENSHOT_DIR, "04-table-screen.png"),
|
||||
fullPage: false,
|
||||
});
|
||||
console.log(" -> 04-table-screen.png 저장됨");
|
||||
} else {
|
||||
// 메뉴 클릭으로 화면 이동 시도
|
||||
const firstMenu = page.locator('aside [class*="cursor-pointer"]').first();
|
||||
if ((await firstMenu.count()) > 0) {
|
||||
await firstMenu.click();
|
||||
await page.waitForTimeout(2500);
|
||||
await page.screenshot({
|
||||
path: path.join(SCREENSHOT_DIR, "04-table-screen.png"),
|
||||
fullPage: false,
|
||||
});
|
||||
console.log(" -> 04-table-screen.png 저장됨");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await browser.close();
|
||||
console.log("\n검증 완료. 스크린샷:", SCREENSHOT_DIR);
|
||||
} catch (error) {
|
||||
console.error("오류:", error);
|
||||
await page.screenshot({
|
||||
path: path.join(SCREENSHOT_DIR, "error.png"),
|
||||
fullPage: true,
|
||||
}).catch(() => {});
|
||||
await browser.close();
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,130 +0,0 @@
|
||||
/**
|
||||
* 화면 156, 4155, 1053 검증: 버튼 레이아웃 및 가시성
|
||||
*/
|
||||
|
||||
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");
|
||||
|
||||
async function loginIfNeeded(page: any) {
|
||||
if (page.url().includes("/login")) {
|
||||
await page.fill("#userId", "wace");
|
||||
await page.fill("#password", "qlalfqjsgh11");
|
||||
await page.locator('button[type="submit"]').first().click();
|
||||
await page.waitForURL((u: URL) => !u.includes("/login"), { timeout: 15000 }).catch(() => {});
|
||||
await page.waitForTimeout(2000);
|
||||
}
|
||||
}
|
||||
|
||||
async function verifyScreen(
|
||||
page: any,
|
||||
screenId: number,
|
||||
report: Record<string, any>
|
||||
) {
|
||||
await page.goto(`${BASE_URL}/screens/${screenId}`, { waitUntil: "load", timeout: 30000 });
|
||||
await page.waitForTimeout(2000);
|
||||
if (page.url().includes("/login")) {
|
||||
await loginIfNeeded(page);
|
||||
await page.goto(`${BASE_URL}/screens/${screenId}`, { waitUntil: "load", timeout: 30000 });
|
||||
await page.waitForTimeout(2000);
|
||||
}
|
||||
await page.getByText("로딩중", { exact: false }).waitFor({ state: "hidden", timeout: 10000 }).catch(() => {});
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
const info = await page.evaluate(() => {
|
||||
const buttons = Array.from(document.querySelectorAll("button"));
|
||||
const buttonDetails = buttons.slice(0, 20).map((btn) => {
|
||||
const text = (btn as HTMLElement).innerText?.trim() || "";
|
||||
const rect = (btn as HTMLElement).getBoundingClientRect();
|
||||
const style = window.getComputedStyle(btn);
|
||||
return {
|
||||
text: text.substring(0, 50),
|
||||
hasText: text.length > 0,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
visible: rect.width > 0 && rect.height > 0,
|
||||
};
|
||||
});
|
||||
const buttonsWithText = buttonDetails.filter((b) => b.hasText);
|
||||
const table = document.querySelector("table");
|
||||
const pagination = document.body.innerText.includes("표시") || document.body.innerText.includes("1/");
|
||||
const splitPanel = document.querySelector("[class*='split'], [class*='Split'], [class*='border-r']");
|
||||
return {
|
||||
pageLoadsWithoutErrors: !document.body.innerText.includes("화면을 찾을 수 없습니다"),
|
||||
totalButtons: buttons.length,
|
||||
buttonsWithTextCount: buttonsWithText.length,
|
||||
buttonsVisibleWithText: buttonsWithText.length > 0,
|
||||
buttonDetails: buttonDetails.slice(0, 10),
|
||||
tableVisible: !!table,
|
||||
paginationVisible: !!pagination,
|
||||
splitPanelVisible: !!splitPanel,
|
||||
bodyScrollWidth: document.body.scrollWidth,
|
||||
viewportWidth: window.innerWidth,
|
||||
viewportHeight: window.innerHeight,
|
||||
hasHorizontalOverflow: document.body.scrollWidth > window.innerWidth,
|
||||
layoutFitsViewport: document.body.scrollWidth <= window.innerWidth,
|
||||
};
|
||||
});
|
||||
|
||||
report.pageLoadsWithoutErrors = info.pageLoadsWithoutErrors;
|
||||
report.buttonsVisibleWithText = info.buttonsVisibleWithText;
|
||||
report.buttonsWithTextCount = info.buttonsWithTextCount;
|
||||
report.buttonDetails = info.buttonDetails;
|
||||
report.tableVisible = info.tableVisible;
|
||||
report.paginationVisible = info.paginationVisible;
|
||||
report.splitPanelVisible = info.splitPanelVisible;
|
||||
report.layoutFitsViewport = info.layoutFitsViewport;
|
||||
report.hasHorizontalOverflow = info.hasHorizontalOverflow;
|
||||
report.details = {
|
||||
bodyScrollWidth: info.bodyScrollWidth,
|
||||
viewportWidth: info.viewportWidth,
|
||||
viewportHeight: info.viewportHeight,
|
||||
};
|
||||
|
||||
await page.screenshot({
|
||||
path: path.join(SCREENSHOT_DIR, `screen-${screenId}-buttons.png`),
|
||||
fullPage: true,
|
||||
});
|
||||
console.log(`screen-${screenId}-buttons.png saved`);
|
||||
}
|
||||
|
||||
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 report: Record<string, any> = { screen156: {}, screen4155: {}, screen1053: {} };
|
||||
|
||||
try {
|
||||
await page.goto(`${BASE_URL}/login`, { waitUntil: "load", timeout: 30000 });
|
||||
await page.waitForTimeout(1000);
|
||||
await loginIfNeeded(page);
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await verifyScreen(page, 156, report.screen156);
|
||||
await verifyScreen(page, 4155, report.screen4155);
|
||||
await verifyScreen(page, 1053, report.screen1053);
|
||||
|
||||
console.log("\n=== Report ===");
|
||||
console.log(JSON.stringify(report, null, 2));
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(SCREENSHOT_DIR, "button-layout-screens-report.json"),
|
||||
JSON.stringify(report, null, 2)
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error("Error:", error.message);
|
||||
report.error = error.message;
|
||||
await page.screenshot({
|
||||
path: path.join(SCREENSHOT_DIR, "button-layout-error.png"),
|
||||
fullPage: true,
|
||||
}).catch(() => {});
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,166 +0,0 @@
|
||||
/**
|
||||
* 화면 1053, 156 버튼 위치 검증
|
||||
* 1053: overlay buttons within split panel
|
||||
* 156: buttons in separate row above table
|
||||
*/
|
||||
|
||||
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");
|
||||
|
||||
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 report: Record<string, any> = { screen1053: {}, screen156: {} };
|
||||
|
||||
try {
|
||||
await page.goto(`${BASE_URL}/login`, { waitUntil: "domcontentloaded", timeout: 60000 });
|
||||
await page.waitForTimeout(2000);
|
||||
await page.fill("#userId", "wace");
|
||||
await page.fill("#password", "qlalfqjsgh11");
|
||||
await page.locator('button[type="submit"]').first().click();
|
||||
await page.waitForURL((u: URL) => !u.includes("/login"), { timeout: 20000 }).catch(() => {});
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
await page.goto(`${BASE_URL}/screens/156`, { waitUntil: "domcontentloaded", timeout: 45000 });
|
||||
await page.waitForTimeout(2000);
|
||||
if (page.url().includes("/login")) {
|
||||
await page.fill("#userId", "wace");
|
||||
await page.fill("#password", "qlalfqjsgh11");
|
||||
await page.locator('button[type="submit"]').first().click();
|
||||
await page.waitForURL((u: URL) => !u.includes("/login"), { timeout: 20000 }).catch(() => {});
|
||||
await page.waitForTimeout(3000);
|
||||
}
|
||||
|
||||
// Screen 1053
|
||||
await page.goto(`${BASE_URL}/screens/1053?menuObjid=1762421920304`, { waitUntil: "domcontentloaded", timeout: 60000 });
|
||||
await page.waitForTimeout(3000);
|
||||
if (page.url().includes("/login")) {
|
||||
await page.fill("#userId", "wace");
|
||||
await page.fill("#password", "qlalfqjsgh11");
|
||||
await page.locator('button[type="submit"]').first().click();
|
||||
await page.waitForURL((u: URL) => !u.includes("/login"), { timeout: 25000 }).catch(() => {});
|
||||
await page.waitForTimeout(5000);
|
||||
await page.goto(`${BASE_URL}/screens/1053?menuObjid=1762421920304`, { waitUntil: "domcontentloaded", timeout: 60000 });
|
||||
await page.waitForTimeout(3000);
|
||||
}
|
||||
await page.getByText("화면을 불러오는 중", { exact: false }).waitFor({ state: "hidden", timeout: 35000 }).catch(() => {});
|
||||
await page.waitForTimeout(40000);
|
||||
|
||||
const splitPanelEl = page.locator("[class*='border-r'], [class*='split']").first();
|
||||
await splitPanelEl.waitFor({ state: "visible", timeout: 15000 }).catch(() => {});
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
const info1053 = await page.evaluate(() => {
|
||||
const splitPanel = document.querySelector("[class*='border-r']") || document.querySelector("main");
|
||||
const mainContent = document.querySelector("main") || document.body;
|
||||
const allBtns = Array.from(document.querySelectorAll("button"));
|
||||
const buttons = allBtns.filter((b) => {
|
||||
const t = (b as HTMLElement).innerText?.trim() || "";
|
||||
const r = (b as HTMLElement).getBoundingClientRect();
|
||||
return t.length > 1 && r.x > 250 && t.match(/등록|수정|삭제|품목|테이블|결재|수주|출하/);
|
||||
});
|
||||
const splitRect = splitPanel ? (splitPanel as HTMLElement).getBoundingClientRect() : null;
|
||||
const mainRect = mainContent ? (mainContent as HTMLElement).getBoundingClientRect() : null;
|
||||
|
||||
const buttonPositions = buttons.map((b) => {
|
||||
const r = (b as HTMLElement).getBoundingClientRect();
|
||||
const text = (b as HTMLElement).innerText?.trim().substring(0, 20);
|
||||
return {
|
||||
text,
|
||||
x: r.x,
|
||||
y: r.y,
|
||||
right: r.right,
|
||||
width: r.width,
|
||||
height: r.height,
|
||||
isWithinSplitPanel: splitRect
|
||||
? r.y >= splitRect.top - 20 && r.y <= splitRect.bottom + 20
|
||||
: null,
|
||||
isAboveMain: mainRect ? r.y < mainRect.top + 100 : null,
|
||||
};
|
||||
});
|
||||
|
||||
const table = document.querySelector("table");
|
||||
const tableRect = table ? (table as HTMLElement).getBoundingClientRect() : null;
|
||||
const buttonsAboveTable = buttonPositions.every((p) => tableRect && p.y < tableRect.top - 10);
|
||||
|
||||
return {
|
||||
splitPanelVisible: !!splitPanel,
|
||||
splitPanelRect: splitRect ? { top: splitRect.top, bottom: splitRect.bottom, left: splitRect.left, right: splitRect.right } : null,
|
||||
mainRect: mainRect ? { top: mainRect.top, bottom: mainRect.bottom } : null,
|
||||
buttonCount: buttons.length,
|
||||
buttonPositions,
|
||||
buttonsOverlaidOnSplitPanel: buttonPositions.some((p) => p.isWithinSplitPanel),
|
||||
buttonsInSeparateRowAbove: buttonsAboveTable,
|
||||
tableTop: tableRect?.top ?? null,
|
||||
};
|
||||
});
|
||||
|
||||
report.screen1053 = info1053;
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "overlay-1053.png"), fullPage: true });
|
||||
console.log("overlay-1053.png saved");
|
||||
|
||||
// Screen 156
|
||||
await page.goto(`${BASE_URL}/screens/156?menuObjid=1762421920156`, { waitUntil: "domcontentloaded", timeout: 45000 });
|
||||
await page.waitForTimeout(3000);
|
||||
await page.getByText("화면을 불러오는 중", { exact: false }).waitFor({ state: "hidden", timeout: 35000 }).catch(() => {});
|
||||
await page.waitForTimeout(40000);
|
||||
|
||||
const table156 = page.locator("table tbody tr").first();
|
||||
await table156.waitFor({ state: "visible", timeout: 15000 }).catch(() => {});
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
const info156 = await page.evaluate(() => {
|
||||
const table = document.querySelector("table");
|
||||
const tableRect = table ? (table as HTMLElement).getBoundingClientRect() : null;
|
||||
const buttons = Array.from(document.querySelectorAll("button")).filter(
|
||||
(b) => ((b as HTMLElement).innerText?.trim() || "").match(/결재|수주|수정|삭제|출하|테이블/)
|
||||
);
|
||||
const buttonPositions = buttons.map((b) => {
|
||||
const r = (b as HTMLElement).getBoundingClientRect();
|
||||
const text = (b as HTMLElement).innerText?.trim().substring(0, 20);
|
||||
return {
|
||||
text,
|
||||
x: r.x,
|
||||
y: r.y,
|
||||
right: r.right,
|
||||
width: r.width,
|
||||
isAboveTable: tableRect ? r.y < tableRect.top - 5 : null,
|
||||
};
|
||||
});
|
||||
const allButtonsAboveTable = buttonPositions.every((p) => p.isAboveTable);
|
||||
|
||||
return {
|
||||
tableVisible: !!table,
|
||||
tableTop: tableRect?.top ?? null,
|
||||
buttonCount: buttons.length,
|
||||
buttonPositions,
|
||||
buttonsInSeparateRowAboveTable: allButtonsAboveTable,
|
||||
};
|
||||
});
|
||||
|
||||
report.screen156 = info156;
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "overlay-156.png"), fullPage: true });
|
||||
console.log("overlay-156.png saved");
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(SCREENSHOT_DIR, "overlay-report.json"),
|
||||
JSON.stringify(report, null, 2)
|
||||
);
|
||||
console.log("\n=== Report ===");
|
||||
console.log(JSON.stringify(report, null, 2));
|
||||
} catch (error: any) {
|
||||
console.error("Error:", error.message);
|
||||
report.error = error.message;
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "overlay-error.png"), fullPage: true }).catch(() => {});
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,126 +0,0 @@
|
||||
/**
|
||||
* 반응형 렌더링 검증: 화면 1053, 2089, 156, 4155
|
||||
* 로그인: admin / wace1234!
|
||||
*/
|
||||
|
||||
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");
|
||||
|
||||
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 report: Record<string, any> = {};
|
||||
|
||||
try {
|
||||
// 1-3: Login
|
||||
await page.goto(`${BASE_URL}/login`, { waitUntil: "load", timeout: 30000 });
|
||||
await page.waitForTimeout(1500);
|
||||
await page.fill("#userId", "admin");
|
||||
await page.fill("#password", "wace1234!");
|
||||
await page.locator('button[type="submit"]').first().click();
|
||||
await page.waitForTimeout(3000);
|
||||
if (page.url().includes("/login")) {
|
||||
await page.fill("#userId", "wace");
|
||||
await page.fill("#password", "qlalfqjsgh11");
|
||||
await page.locator('button[type="submit"]').first().click();
|
||||
await page.waitForTimeout(3000);
|
||||
}
|
||||
await page.waitForURL((u: URL) => !u.includes("/login"), { timeout: 15000 }).catch(() => {});
|
||||
await page.waitForTimeout(2000);
|
||||
await page.getByText("로딩중", { exact: false }).waitFor({ state: "hidden", timeout: 10000 }).catch(() => {});
|
||||
|
||||
async function captureAndVerify(screenId: number, screenName: string) {
|
||||
await page.goto(`${BASE_URL}/screens/${screenId}`, { waitUntil: "domcontentloaded", timeout: 45000 });
|
||||
await page.waitForTimeout(2000);
|
||||
if (page.url().includes("/login")) {
|
||||
await page.fill("#userId", "wace");
|
||||
await page.fill("#password", "qlalfqjsgh11");
|
||||
await page.locator('button[type="submit"]').first().click();
|
||||
await page.waitForURL((u: URL) => !u.includes("/login"), { timeout: 15000 }).catch(() => {});
|
||||
await page.waitForTimeout(2000);
|
||||
await page.goto(`${BASE_URL}/screens/${screenId}`, { waitUntil: "domcontentloaded", timeout: 45000 });
|
||||
await page.waitForTimeout(2000);
|
||||
}
|
||||
await page.getByText("로딩중", { exact: false }).waitFor({ state: "hidden", timeout: 10000 }).catch(() => {});
|
||||
await page.getByText("화면을 불러오는 중", { exact: false }).waitFor({ state: "hidden", timeout: 25000 }).catch(() => {});
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
const info = await page.evaluate(() => {
|
||||
const buttons = Array.from(document.querySelectorAll("button"));
|
||||
const btnWithText = buttons.filter((b) => (b as HTMLElement).innerText?.trim().length > 0);
|
||||
const splitPanel = document.querySelector("[class*='split'], [class*='Split'], [class*='border-r']");
|
||||
const leftPanel = document.querySelector("[class*='border-r']");
|
||||
const table = document.querySelector("table");
|
||||
const thead = document.querySelector("thead");
|
||||
const tbody = document.querySelector("tbody");
|
||||
const pagination = document.body.innerText.includes("표시") || document.body.innerText.includes("1/");
|
||||
const bodyText = document.body.innerText;
|
||||
const hasOverlap = bodyText.includes("화면을 찾을 수 없습니다") ? false : null;
|
||||
|
||||
const btnDetails = btnWithText.slice(0, 5).map((b) => ({
|
||||
text: (b as HTMLElement).innerText?.trim().substring(0, 30),
|
||||
rect: (b as HTMLElement).getBoundingClientRect(),
|
||||
}));
|
||||
|
||||
return {
|
||||
pageLoadsWithoutErrors: !bodyText.includes("화면을 찾을 수 없습니다"),
|
||||
buttonsVisible: btnWithText.length > 0,
|
||||
buttonsCount: btnWithText.length,
|
||||
buttonDetails: btnDetails,
|
||||
splitPanelVisible: !!splitPanel,
|
||||
leftPanelVisible: !!leftPanel,
|
||||
tableVisible: !!table && !!thead && !!tbody,
|
||||
paginationVisible: !!pagination,
|
||||
bodyScrollWidth: document.body.scrollWidth,
|
||||
viewportWidth: window.innerWidth,
|
||||
viewportHeight: window.innerHeight,
|
||||
hasHorizontalOverflow: document.body.scrollWidth > window.innerWidth,
|
||||
};
|
||||
});
|
||||
|
||||
await page.screenshot({
|
||||
path: path.join(SCREENSHOT_DIR, `responsive-${screenId}.png`),
|
||||
fullPage: true,
|
||||
});
|
||||
console.log(`responsive-${screenId}.png saved`);
|
||||
|
||||
return { screenId, screenName, ...info };
|
||||
}
|
||||
|
||||
// 4: Screen 1053 - 거래처관리
|
||||
report.screen1053 = await captureAndVerify(1053, "거래처관리 - split panel custom mode");
|
||||
|
||||
// 5: Screen 2089 - BOM관리
|
||||
report.screen2089 = await captureAndVerify(2089, "BOM관리 - split panel");
|
||||
|
||||
// 6: Screen 156 - 수주관리
|
||||
report.screen156 = await captureAndVerify(156, "수주관리 - regular screen");
|
||||
|
||||
// 7: Screen 4155 - 작업지시
|
||||
report.screen4155 = await captureAndVerify(4155, "작업지시 - buttons at bottom");
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(SCREENSHOT_DIR, "responsive-report.json"),
|
||||
JSON.stringify(report, null, 2)
|
||||
);
|
||||
console.log("\n=== Report ===");
|
||||
console.log(JSON.stringify(report, null, 2));
|
||||
} catch (error: any) {
|
||||
console.error("Error:", error.message);
|
||||
report.error = error.message;
|
||||
await page.screenshot({
|
||||
path: path.join(SCREENSHOT_DIR, "responsive-error.png"),
|
||||
fullPage: true,
|
||||
}).catch(() => {});
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,103 +0,0 @@
|
||||
/**
|
||||
* 화면 1053 검증 - admin/1234 로그인
|
||||
*/
|
||||
|
||||
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");
|
||||
|
||||
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 report: Record<string, any> = {};
|
||||
|
||||
try {
|
||||
await page.goto(`${BASE_URL}/login`, { waitUntil: "domcontentloaded", timeout: 60000 });
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.fill("#userId", "admin");
|
||||
await page.fill("#password", "1234");
|
||||
await page.locator('button[type="submit"]').first().click();
|
||||
await page.waitForURL((u: URL) => !u.includes("/login"), { timeout: 20000 }).catch(() => {});
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
if (page.url().includes("/login")) {
|
||||
await page.fill("#userId", "wace");
|
||||
await page.fill("#password", "qlalfqjsgh11");
|
||||
await page.locator('button[type="submit"]').first().click();
|
||||
await page.waitForURL((u: URL) => !u.includes("/login"), { timeout: 20000 }).catch(() => {});
|
||||
await page.waitForTimeout(3000);
|
||||
}
|
||||
|
||||
await page.goto(`${BASE_URL}/screens/1053`, { waitUntil: "domcontentloaded", timeout: 60000 });
|
||||
await page.waitForTimeout(3000);
|
||||
if (page.url().includes("/login")) {
|
||||
await page.fill("#userId", "wace");
|
||||
await page.fill("#password", "qlalfqjsgh11");
|
||||
await page.locator('button[type="submit"]').first().click();
|
||||
await page.waitForURL((u: URL) => !u.includes("/login"), { timeout: 20000 }).catch(() => {});
|
||||
await page.waitForTimeout(3000);
|
||||
await page.goto(`${BASE_URL}/screens/1053`, { waitUntil: "domcontentloaded", timeout: 60000 });
|
||||
await page.waitForTimeout(3000);
|
||||
}
|
||||
|
||||
await page.getByText("화면을 불러오는 중", { exact: false }).waitFor({ state: "hidden", timeout: 35000 }).catch(() => {});
|
||||
await page.waitForTimeout(30000);
|
||||
|
||||
const table = page.locator("table tbody tr").first();
|
||||
await table.waitFor({ state: "visible", timeout: 20000 }).catch(() => {});
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
const info = await page.evaluate(() => {
|
||||
const buttons = Array.from(document.querySelectorAll("button")).filter((b) => {
|
||||
const t = (b as HTMLElement).innerText?.trim() || "";
|
||||
const r = (b as HTMLElement).getBoundingClientRect();
|
||||
return t.length > 1 && r.x > 250;
|
||||
});
|
||||
const leftPanel = document.querySelector("[class*='border-r']");
|
||||
const tables = document.querySelectorAll("table");
|
||||
const bodyText = document.body.innerText;
|
||||
|
||||
return {
|
||||
buttonCount: buttons.length,
|
||||
buttonDetails: buttons.slice(0, 15).map((b) => {
|
||||
const r = (b as HTMLElement).getBoundingClientRect();
|
||||
return {
|
||||
text: (b as HTMLElement).innerText?.trim().substring(0, 30),
|
||||
x: Math.round(r.x),
|
||||
y: Math.round(r.y),
|
||||
width: Math.round(r.width),
|
||||
height: Math.round(r.height),
|
||||
};
|
||||
}),
|
||||
splitPanelVisible: !!leftPanel || bodyText.includes("공급처") || bodyText.includes("좌측에서"),
|
||||
tableCount: tables.length,
|
||||
hasExcelDownload: bodyText.includes("엑셀") || bodyText.includes("다운로드") || bodyText.includes("업로드"),
|
||||
};
|
||||
});
|
||||
|
||||
report.screen1053 = info;
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "screen-1053-admin.png"), fullPage: true });
|
||||
console.log("screen-1053-admin.png saved");
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(SCREENSHOT_DIR, "screen-1053-admin-report.json"),
|
||||
JSON.stringify(report, null, 2)
|
||||
);
|
||||
console.log("\n=== Report ===");
|
||||
console.log(JSON.stringify(report, null, 2));
|
||||
} catch (error: any) {
|
||||
console.error("Error:", error.message);
|
||||
report.error = error.message;
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "screen-1053-admin-error.png"), fullPage: true }).catch(() => {});
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,105 +0,0 @@
|
||||
/**
|
||||
* 화면 1053 검증: split-panel 레이아웃
|
||||
* - 좌/우 패널, 버튼 오버레이, 높이 채움, overflow 확인
|
||||
*/
|
||||
|
||||
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");
|
||||
|
||||
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 report: Record<string, any> = {};
|
||||
|
||||
try {
|
||||
await page.goto(`${BASE_URL}/screens/1053`, { waitUntil: "load", timeout: 30000 });
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
if (page.url().includes("/login")) {
|
||||
await page.fill("#userId", "wace");
|
||||
await page.fill("#password", "qlalfqjsgh11");
|
||||
await page.locator('button[type="submit"]').first().click();
|
||||
await page.waitForURL((u) => !u.includes("/login"), { timeout: 15000 }).catch(() => {});
|
||||
await page.waitForTimeout(2000);
|
||||
await page.goto(`${BASE_URL}/screens/1053`, { waitUntil: "load", timeout: 30000 });
|
||||
await page.waitForTimeout(2000);
|
||||
}
|
||||
|
||||
await page.getByText("로딩중", { exact: false }).waitFor({ state: "hidden", timeout: 10000 }).catch(() => {});
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
report.pageLoadsWithoutErrors = (await page.locator('text="화면을 찾을 수 없습니다"').count()) === 0;
|
||||
|
||||
const splitPanel = await page.evaluate(() => {
|
||||
const leftPanel = document.querySelector("[class*='split'][class*='left'], [data-panel='left'], [class*='left-panel']");
|
||||
const rightPanel = document.querySelector("[class*='split'][class*='right'], [data-panel='right'], [class*='right-panel']");
|
||||
const resizable = document.querySelector("[class*='resize'], [class*='ResizablePanel]");
|
||||
const panels = document.querySelectorAll("[class*='panel'], [data-panel]");
|
||||
return {
|
||||
hasLeftPanel: !!leftPanel || document.body.innerText.includes("left") || panels.length >= 2,
|
||||
hasRightPanel: !!rightPanel || panels.length >= 2,
|
||||
panelCount: panels.length,
|
||||
resizableCount: document.querySelectorAll("[class*='ResizablePanel'], [class*='resize']").length,
|
||||
};
|
||||
});
|
||||
report.splitPanelVisible = splitPanel.panelCount >= 2 || splitPanel.resizableCount > 0;
|
||||
|
||||
const twoPanels = await page.locator("[class*='panel'], [data-panel], [class*='split']").count();
|
||||
report.twoPanelsFound = twoPanels >= 2;
|
||||
|
||||
const buttons = await page.locator("button").count();
|
||||
const buttonsTopRight = await page.evaluate(() => {
|
||||
const btns = Array.from(document.querySelectorAll("button"));
|
||||
const viewportW = window.innerWidth;
|
||||
return btns.filter((b) => {
|
||||
const r = b.getBoundingClientRect();
|
||||
return r.right > viewportW * 0.5 && r.top < 200;
|
||||
}).length;
|
||||
});
|
||||
report.buttonsVisible = buttons > 0;
|
||||
report.buttonsInTopRightArea = buttonsTopRight;
|
||||
|
||||
const layoutFillsHeight = await page.evaluate(() => {
|
||||
const main = document.querySelector("main") || document.body;
|
||||
const h = (main as HTMLElement).offsetHeight;
|
||||
return h >= window.innerHeight * 0.8;
|
||||
});
|
||||
report.layoutFillsHeight = layoutFillsHeight;
|
||||
|
||||
const overflow = await page.evaluate(() => ({
|
||||
bodyScrollWidth: document.body.scrollWidth,
|
||||
bodyScrollHeight: document.body.scrollHeight,
|
||||
viewportWidth: window.innerWidth,
|
||||
viewportHeight: window.innerHeight,
|
||||
hasHorizontalOverflow: document.body.scrollWidth > window.innerWidth,
|
||||
hasVerticalOverflow: document.body.scrollHeight > window.innerHeight,
|
||||
}));
|
||||
report.noContentOverflowsViewport = !overflow.hasHorizontalOverflow;
|
||||
report.overflowDetails = overflow;
|
||||
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "screen-1053-snapshot.png"), fullPage: true });
|
||||
console.log("screen-1053-snapshot.png saved");
|
||||
|
||||
console.log("\n=== Report ===");
|
||||
console.log(JSON.stringify(report, null, 2));
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(SCREENSHOT_DIR, "screen-1053-report.json"),
|
||||
JSON.stringify(report, null, 2)
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error("Error:", error.message);
|
||||
report.error = error.message;
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "screen-1053-error.png"), fullPage: true }).catch(() => {});
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,156 +0,0 @@
|
||||
/**
|
||||
* 화면 1244 검증: table-list 레이아웃 (데스크톱 + 모바일)
|
||||
*/
|
||||
|
||||
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");
|
||||
|
||||
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 report: Record<string, any> = { desktop: {}, mobile: {} };
|
||||
|
||||
try {
|
||||
await page.goto(`${BASE_URL}/screens/1244`, { waitUntil: "load", timeout: 30000 });
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
if (page.url().includes("/login")) {
|
||||
await page.fill("#userId", "wace");
|
||||
await page.fill("#password", "qlalfqjsgh11");
|
||||
await page.locator('button[type="submit"]').first().click();
|
||||
await page.waitForURL((u) => !u.includes("/login"), { timeout: 15000 }).catch(() => {});
|
||||
await page.waitForTimeout(2000);
|
||||
await page.goto(`${BASE_URL}/screens/1244`, { waitUntil: "load", timeout: 30000 });
|
||||
await page.waitForTimeout(2000);
|
||||
}
|
||||
|
||||
await page.getByText("로딩중", { exact: false }).waitFor({ state: "hidden", timeout: 10000 }).catch(() => {});
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
report.desktop.pageLoadsWithoutErrors =
|
||||
(await page.locator('text="화면을 찾을 수 없습니다"').count()) === 0;
|
||||
|
||||
const desktopInfo = await page.evaluate(() => {
|
||||
const table = document.querySelector("table");
|
||||
const thead = document.querySelector("thead");
|
||||
const tbody = document.querySelector("tbody");
|
||||
const ths = document.querySelectorAll("thead th");
|
||||
const buttons = document.querySelectorAll("button");
|
||||
const searchInputs = document.querySelectorAll('input[type="text"], input[type="search"], select');
|
||||
const pagination = document.body.innerText.includes("표시") ||
|
||||
document.body.innerText.includes("1/") ||
|
||||
document.body.innerText.includes("페이지") ||
|
||||
document.querySelector("[class*='pagination'], [class*='Pagination']");
|
||||
|
||||
let buttonsBetweenSearchAndTable = 0;
|
||||
const searchY = searchInputs.length > 0
|
||||
? (searchInputs[0] as HTMLElement).getBoundingClientRect().bottom
|
||||
: 0;
|
||||
const tableY = table ? (table as HTMLElement).getBoundingClientRect().top : 0;
|
||||
|
||||
buttons.forEach((btn) => {
|
||||
const rect = (btn as HTMLElement).getBoundingClientRect();
|
||||
if (rect.top >= searchY - 20 && rect.top <= tableY + 100) buttonsBetweenSearchAndTable++;
|
||||
});
|
||||
|
||||
return {
|
||||
tableVisible: !!table && !!thead && !!tbody,
|
||||
columnCount: ths.length,
|
||||
buttonsVisible: buttons.length > 0,
|
||||
buttonsBetweenSearchAndTable,
|
||||
paginationVisible: !!pagination,
|
||||
bodyScrollWidth: document.body.scrollWidth,
|
||||
viewportWidth: window.innerWidth,
|
||||
hasHorizontalOverflow: document.body.scrollWidth > window.innerWidth,
|
||||
tableScrollWidth: table ? (table as HTMLElement).scrollWidth : 0,
|
||||
tableClientWidth: table ? (table as HTMLElement).clientWidth : 0,
|
||||
};
|
||||
});
|
||||
|
||||
report.desktop.buttonsVisible = desktopInfo.buttonsVisible;
|
||||
report.desktop.buttonsBetweenSearchAndTable = desktopInfo.buttonsBetweenSearchAndTable;
|
||||
report.desktop.tableVisible = desktopInfo.tableVisible;
|
||||
report.desktop.columnCount = desktopInfo.columnCount;
|
||||
report.desktop.paginationVisible = desktopInfo.paginationVisible;
|
||||
report.desktop.noHorizontalOverflow = !desktopInfo.hasHorizontalOverflow;
|
||||
report.desktop.overflowDetails = {
|
||||
bodyScrollWidth: desktopInfo.bodyScrollWidth,
|
||||
viewportWidth: desktopInfo.viewportWidth,
|
||||
tableScrollWidth: desktopInfo.tableScrollWidth,
|
||||
tableClientWidth: desktopInfo.tableClientWidth,
|
||||
};
|
||||
|
||||
await page.screenshot({
|
||||
path: path.join(SCREENSHOT_DIR, "screen-1244-desktop.png"),
|
||||
fullPage: true,
|
||||
});
|
||||
console.log("screen-1244-desktop.png saved");
|
||||
|
||||
await page.setViewportSize({ width: 768, height: 900 });
|
||||
await page.waitForTimeout(1500);
|
||||
|
||||
const mobileInfo = await page.evaluate(() => {
|
||||
const table = document.querySelector("table");
|
||||
const thead = document.querySelector("thead");
|
||||
const tbody = document.querySelector("tbody");
|
||||
const ths = document.querySelectorAll("thead th");
|
||||
const buttons = document.querySelectorAll("button");
|
||||
const pagination = document.body.innerText.includes("표시") ||
|
||||
document.body.innerText.includes("1/") ||
|
||||
document.body.innerText.includes("페이지") ||
|
||||
document.querySelector("[class*='pagination'], [class*='Pagination']");
|
||||
|
||||
return {
|
||||
tableVisible: !!table && !!thead && !!tbody,
|
||||
columnCount: ths.length,
|
||||
buttonsVisible: buttons.length > 0,
|
||||
paginationVisible: !!pagination,
|
||||
bodyScrollWidth: document.body.scrollWidth,
|
||||
viewportWidth: window.innerWidth,
|
||||
hasHorizontalOverflow: document.body.scrollWidth > window.innerWidth,
|
||||
};
|
||||
});
|
||||
|
||||
report.mobile.pageLoadsWithoutErrors = report.desktop.pageLoadsWithoutErrors;
|
||||
report.mobile.buttonsVisible = mobileInfo.buttonsVisible;
|
||||
report.mobile.tableVisible = mobileInfo.tableVisible;
|
||||
report.mobile.columnCount = mobileInfo.columnCount;
|
||||
report.mobile.paginationVisible = mobileInfo.paginationVisible;
|
||||
report.mobile.noHorizontalOverflow = !mobileInfo.hasHorizontalOverflow;
|
||||
report.mobile.overflowDetails = {
|
||||
bodyScrollWidth: mobileInfo.bodyScrollWidth,
|
||||
viewportWidth: mobileInfo.viewportWidth,
|
||||
};
|
||||
|
||||
await page.screenshot({
|
||||
path: path.join(SCREENSHOT_DIR, "screen-1244-mobile-768.png"),
|
||||
fullPage: true,
|
||||
});
|
||||
console.log("screen-1244-mobile-768.png saved");
|
||||
|
||||
console.log("\n=== Report ===");
|
||||
console.log(JSON.stringify(report, null, 2));
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(SCREENSHOT_DIR, "screen-1244-layout-report.json"),
|
||||
JSON.stringify(report, null, 2)
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error("Error:", error.message);
|
||||
report.error = error.message;
|
||||
await page.screenshot({
|
||||
path: path.join(SCREENSHOT_DIR, "screen-1244-error.png"),
|
||||
fullPage: true,
|
||||
}).catch(() => {});
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,143 +0,0 @@
|
||||
/**
|
||||
* 화면 1244 새로고침 후 상세 검증
|
||||
* - data-screen-runtime, 테이블, body의 scrollWidth/clientWidth 확인
|
||||
*/
|
||||
|
||||
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");
|
||||
|
||||
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: Record<string, number | string | null> = {};
|
||||
|
||||
try {
|
||||
// 로그인
|
||||
await page.goto(`${BASE_URL}/login`, { waitUntil: "load", timeout: 30000 });
|
||||
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((u) => !u.includes("/login"), { timeout: 15000 }).catch(() => {});
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// /screens/1244 접속
|
||||
await page.goto(`${BASE_URL}/screens/1244`, { waitUntil: "load", timeout: 45000 });
|
||||
await page.getByText("로딩중", { exact: false }).waitFor({ state: "hidden", timeout: 10000 }).catch(() => {});
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Step 1: 새로고침 (Ctrl+Shift+R - hard refresh)
|
||||
console.log("Step 1: 새로고침 (Ctrl+Shift+R)...");
|
||||
await page.keyboard.press("Control+Shift+r");
|
||||
await page.waitForLoadState("load");
|
||||
await page.waitForTimeout(3000);
|
||||
await page.getByText("로딩중", { exact: false }).waitFor({ state: "hidden", timeout: 10000 }).catch(() => {});
|
||||
|
||||
// Step 2: 첫 스크린샷
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "verify-06.png"), fullPage: true });
|
||||
console.log("verify-06.png 저장");
|
||||
|
||||
// Step 3: JavaScript로 dimension 확인
|
||||
const dims = await page.evaluate(() => {
|
||||
const screenRuntime = document.querySelector("[data-screen-runtime]");
|
||||
const table = document.querySelector("table");
|
||||
const body = document.body;
|
||||
const html = document.documentElement;
|
||||
|
||||
const tableContainer = table?.closest("[style*='overflow'], [class*='overflow']") || table?.parentElement;
|
||||
|
||||
return {
|
||||
screenRuntime: screenRuntime
|
||||
? {
|
||||
offsetWidth: (screenRuntime as HTMLElement).offsetWidth,
|
||||
scrollWidth: (screenRuntime as HTMLElement).scrollWidth,
|
||||
clientWidth: (screenRuntime as HTMLElement).clientWidth,
|
||||
}
|
||||
: null,
|
||||
table: table
|
||||
? {
|
||||
offsetWidth: table.offsetWidth,
|
||||
scrollWidth: table.scrollWidth,
|
||||
clientWidth: table.clientWidth,
|
||||
}
|
||||
: null,
|
||||
tableContainer: tableContainer
|
||||
? {
|
||||
clientWidth: (tableContainer as HTMLElement).clientWidth,
|
||||
scrollWidth: (tableContainer as HTMLElement).scrollWidth,
|
||||
offsetWidth: (tableContainer as HTMLElement).offsetWidth,
|
||||
}
|
||||
: null,
|
||||
body: {
|
||||
scrollWidth: body.scrollWidth,
|
||||
clientWidth: body.clientWidth,
|
||||
offsetWidth: body.offsetWidth,
|
||||
},
|
||||
html: {
|
||||
scrollWidth: html.scrollWidth,
|
||||
clientWidth: html.clientWidth,
|
||||
},
|
||||
viewport: {
|
||||
innerWidth: window.innerWidth,
|
||||
innerHeight: window.innerHeight,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
results.screenRuntime_offsetWidth = dims.screenRuntime?.offsetWidth ?? null;
|
||||
results.screenRuntime_scrollWidth = dims.screenRuntime?.scrollWidth ?? null;
|
||||
results.screenRuntime_clientWidth = dims.screenRuntime?.clientWidth ?? null;
|
||||
results.table_offsetWidth = dims.table?.offsetWidth ?? null;
|
||||
results.table_scrollWidth = dims.table?.scrollWidth ?? null;
|
||||
results.table_clientWidth = dims.table?.clientWidth ?? null;
|
||||
results.tableContainer_clientWidth = dims.tableContainer?.clientWidth ?? null;
|
||||
results.tableContainer_scrollWidth = dims.tableContainer?.scrollWidth ?? null;
|
||||
results.body_scrollWidth = dims.body.scrollWidth;
|
||||
results.body_clientWidth = dims.body.clientWidth;
|
||||
results.viewport_innerWidth = dims.viewport.innerWidth;
|
||||
|
||||
// Step 4: 가로 overflow 확인
|
||||
const hasOverflow = dims.body.scrollWidth > dims.viewport.innerWidth;
|
||||
results.bodyOverflowX = hasOverflow;
|
||||
|
||||
// Step 5: 두 번째 스크린샷
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "verify-07.png"), fullPage: true });
|
||||
console.log("verify-07.png 저장");
|
||||
|
||||
console.log("\n=== 검증 결과 ===");
|
||||
console.log("data-screen-runtime div:");
|
||||
console.log(" offsetWidth:", results.screenRuntime_offsetWidth);
|
||||
console.log(" scrollWidth:", results.screenRuntime_scrollWidth);
|
||||
console.log(" clientWidth:", results.screenRuntime_clientWidth);
|
||||
console.log("테이블:");
|
||||
console.log(" offsetWidth:", results.table_offsetWidth);
|
||||
console.log(" scrollWidth:", results.table_scrollWidth);
|
||||
console.log(" clientWidth:", results.table_clientWidth);
|
||||
console.log("테이블 컨테이너:");
|
||||
console.log(" clientWidth:", results.tableContainer_clientWidth);
|
||||
console.log(" scrollWidth:", results.tableContainer_scrollWidth);
|
||||
console.log("body:");
|
||||
console.log(" scrollWidth:", results.body_scrollWidth);
|
||||
console.log(" clientWidth:", results.body_clientWidth);
|
||||
console.log("뷰포트 innerWidth:", results.viewport_innerWidth);
|
||||
console.log("가로 overflow:", results.bodyOverflowX);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(SCREENSHOT_DIR, "verify-refresh-result.json"),
|
||||
JSON.stringify({ ...results, rawDims: dims }, null, 2)
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error("오류:", error.message);
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "verify-99-error.png"), fullPage: true }).catch(() => {});
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,142 +0,0 @@
|
||||
/**
|
||||
* 화면 1244 새로고침 검증 (2차)
|
||||
* - 3초 대기 후 스크린샷
|
||||
* - data-screen-runtime, 테이블 관련 div width 확인
|
||||
* - 가로 스크롤 가능 여부 확인
|
||||
*/
|
||||
|
||||
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");
|
||||
|
||||
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();
|
||||
|
||||
try {
|
||||
// 로그인
|
||||
await page.goto(`${BASE_URL}/login`, { waitUntil: "load", timeout: 30000 });
|
||||
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((u) => !u.includes("/login"), { timeout: 15000 }).catch(() => {});
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// /screens/1244 접속
|
||||
await page.goto(`${BASE_URL}/screens/1244`, { waitUntil: "load", timeout: 45000 });
|
||||
await page.getByText("로딩중", { exact: false }).waitFor({ state: "hidden", timeout: 10000 }).catch(() => {});
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Step 1: 새로고침 (Ctrl+Shift+R)
|
||||
console.log("Step 1: 새로고침 (Ctrl+Shift+R)...");
|
||||
await page.keyboard.press("Control+Shift+r");
|
||||
await page.waitForLoadState("load");
|
||||
console.log("Step 2: 3초 대기...");
|
||||
await page.waitForTimeout(3000);
|
||||
await page.getByText("로딩중", { exact: false }).waitFor({ state: "hidden", timeout: 8000 }).catch(() => {});
|
||||
|
||||
// Step 3: 첫 스크린샷
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "verify-08.png"), fullPage: true });
|
||||
console.log("verify-08.png 저장");
|
||||
|
||||
// Step 4: JavaScript로 width 확인 (순수 함수로 작성)
|
||||
const dims = await page.evaluate(() => {
|
||||
const screenRuntime = document.querySelector("[data-screen-runtime]");
|
||||
const table = document.querySelector("table");
|
||||
const tableContainer = table?.closest("[style*='overflow'], [class*='overflow']");
|
||||
const overflowHiddenDiv = table?.closest("[style*='overflow-hidden'], [class*='overflow-hidden']");
|
||||
|
||||
const tableAncestors: Array<{ level: number; tag: string; class: string; offsetWidth: number; scrollWidth: number; clientWidth: number; overflowX: string }> = [];
|
||||
let p = table?.parentElement;
|
||||
let idx = 0;
|
||||
while (p && idx < 6) {
|
||||
const s = window.getComputedStyle(p);
|
||||
tableAncestors.push({
|
||||
level: idx,
|
||||
tag: p.tagName,
|
||||
class: (p.className && typeof p.className === "string" ? p.className : "").slice(0, 60),
|
||||
offsetWidth: p.offsetWidth,
|
||||
scrollWidth: p.scrollWidth,
|
||||
clientWidth: p.clientWidth,
|
||||
overflowX: s.overflowX,
|
||||
});
|
||||
p = p.parentElement;
|
||||
idx++;
|
||||
}
|
||||
|
||||
return {
|
||||
screenRuntime: screenRuntime
|
||||
? { offsetWidth: (screenRuntime as HTMLElement).offsetWidth, scrollWidth: (screenRuntime as HTMLElement).scrollWidth, clientWidth: (screenRuntime as HTMLElement).clientWidth }
|
||||
: null,
|
||||
table: table
|
||||
? { offsetWidth: (table as HTMLElement).offsetWidth, scrollWidth: (table as HTMLElement).scrollWidth, clientWidth: (table as HTMLElement).clientWidth }
|
||||
: null,
|
||||
tableContainer: tableContainer
|
||||
? { offsetWidth: (tableContainer as HTMLElement).offsetWidth, scrollWidth: (tableContainer as HTMLElement).scrollWidth, clientWidth: (tableContainer as HTMLElement).clientWidth }
|
||||
: null,
|
||||
overflowHiddenDiv: overflowHiddenDiv
|
||||
? { offsetWidth: (overflowHiddenDiv as HTMLElement).offsetWidth, scrollWidth: (overflowHiddenDiv as HTMLElement).scrollWidth, clientWidth: (overflowHiddenDiv as HTMLElement).clientWidth }
|
||||
: null,
|
||||
tableAncestors,
|
||||
viewport: { innerWidth: window.innerWidth },
|
||||
};
|
||||
});
|
||||
|
||||
console.log("\n=== JavaScript 실행 결과 ===");
|
||||
console.log("data-screen-runtime div:");
|
||||
if (dims.screenRuntime) {
|
||||
console.log(" offsetWidth:", dims.screenRuntime.offsetWidth);
|
||||
console.log(" scrollWidth:", dims.screenRuntime.scrollWidth);
|
||||
console.log(" clientWidth:", dims.screenRuntime.clientWidth);
|
||||
} else {
|
||||
console.log(" (없음)");
|
||||
}
|
||||
console.log("\n테이블:");
|
||||
if (dims.table) {
|
||||
console.log(" offsetWidth:", dims.table.offsetWidth);
|
||||
console.log(" scrollWidth:", dims.table.scrollWidth);
|
||||
}
|
||||
console.log("\n테이블 컨테이너 (overflow):");
|
||||
if (dims.tableContainer) {
|
||||
console.log(" offsetWidth:", dims.tableContainer.offsetWidth);
|
||||
console.log(" scrollWidth:", dims.tableContainer.scrollWidth);
|
||||
console.log(" clientWidth:", dims.tableContainer.clientWidth);
|
||||
}
|
||||
console.log("\noverflow-hidden div:");
|
||||
if (dims.overflowHiddenDiv) {
|
||||
console.log(" offsetWidth:", dims.overflowHiddenDiv.offsetWidth);
|
||||
console.log(" scrollWidth:", dims.overflowHiddenDiv.scrollWidth);
|
||||
} else {
|
||||
console.log(" (없음)");
|
||||
}
|
||||
console.log("\n테이블 조상 div들 (width):");
|
||||
dims.tableAncestors?.forEach((a) => {
|
||||
console.log(` L${a.level} ${a.tag} overflow=${a.overflowX} offsetW=${a.offsetWidth} scrollW=${a.scrollWidth} clientW=${a.clientWidth}`);
|
||||
});
|
||||
|
||||
// Step 5: 가로 스크롤 가능 여부
|
||||
const canScroll = dims.table && dims.tableContainer && dims.table.scrollWidth > dims.tableContainer.clientWidth;
|
||||
console.log("\n가로 스크롤 가능:", canScroll, "(테이블 scrollWidth > 컨테이너 clientWidth)");
|
||||
|
||||
// Step 6: 최종 스크린샷
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "verify-09.png"), fullPage: true });
|
||||
console.log("\nverify-09.png 저장");
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(SCREENSHOT_DIR, "verify-refresh2-result.json"),
|
||||
JSON.stringify({ ...dims, canScroll }, null, 2)
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error("오류:", error.message);
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "verify-99-error.png"), fullPage: true }).catch(() => {});
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,127 +0,0 @@
|
||||
/**
|
||||
* 화면 1244 검증: 테이블, 가로 스크롤, 페이지네이션 확인
|
||||
*/
|
||||
|
||||
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");
|
||||
|
||||
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: Record<string, boolean | string> = {};
|
||||
|
||||
try {
|
||||
// Step 1: 로그인 먼저 (Playwright는 새 브라우저)
|
||||
console.log("Step 1: 로그인...");
|
||||
await page.goto(`${BASE_URL}/login`, { waitUntil: "load", timeout: 30000 });
|
||||
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((u) => !u.includes("/login"), { timeout: 15000 }).catch(() => {});
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Step 2: /screens/1244 접속
|
||||
console.log("Step 2: /screens/1244 접속...");
|
||||
await page.goto(`${BASE_URL}/screens/1244`, { waitUntil: "load", timeout: 45000 });
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.getByText("로딩중", { exact: false }).waitFor({ state: "hidden", timeout: 10000 }).catch(() => {});
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Step 2: 화면 로드 스크린샷
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "verify-01-initial.png"), fullPage: true });
|
||||
console.log("verify-01-initial.png 저장");
|
||||
|
||||
// Step 3: 테이블 확인
|
||||
const table = page.locator("table").first();
|
||||
const tableCount = await table.count();
|
||||
results.tableVisible = tableCount > 0;
|
||||
console.log("테이블 보임:", results.tableVisible);
|
||||
|
||||
// Step 4: 가로 스크롤바 확인
|
||||
const scrollContainer = page.locator("[class*='overflow'], .overflow-x-auto, [style*='overflow']").first();
|
||||
const hasScrollContainer = (await scrollContainer.count()) > 0;
|
||||
let scrollWidth = 0;
|
||||
let clientWidth = 0;
|
||||
if (results.tableVisible) {
|
||||
scrollWidth = await table.evaluate((el) => el.scrollWidth);
|
||||
clientWidth = await table.evaluate((el) => el.clientWidth);
|
||||
results.tableScrollWidth = String(scrollWidth);
|
||||
results.tableClientWidth = String(clientWidth);
|
||||
results.horizontalScrollNeeded = scrollWidth > clientWidth;
|
||||
}
|
||||
console.log("테이블 scrollWidth:", scrollWidth, "clientWidth:", clientWidth);
|
||||
|
||||
// Step 5: 테이블 영역 오른쪽 스크롤 시도 (overflow-auto인 조상 요소 찾기)
|
||||
if (results.tableVisible) {
|
||||
try {
|
||||
const scrollableAncestor = await table.evaluateHandle((el) => {
|
||||
let parent: HTMLElement | null = el.parentElement;
|
||||
while (parent) {
|
||||
const style = getComputedStyle(parent);
|
||||
if (style.overflowX === "auto" || style.overflowX === "scroll" || style.overflow === "auto") {
|
||||
return parent;
|
||||
}
|
||||
parent = parent.parentElement;
|
||||
}
|
||||
return el.parentElement;
|
||||
});
|
||||
const scrollEl = scrollableAncestor.asElement();
|
||||
if (scrollEl) {
|
||||
await scrollEl.evaluate((el) => (el.scrollLeft = 300));
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "verify-02-after-scroll.png"), fullPage: true });
|
||||
console.log("verify-02-after-scroll.png 저장");
|
||||
|
||||
// Step 6: 페이지네이션 확인
|
||||
const paginationText = page.getByText("표시", { exact: false }).or(page.getByText("1/1", { exact: false }));
|
||||
results.paginationVisible = (await paginationText.count()) > 0;
|
||||
console.log("페이지네이션 보임:", results.paginationVisible);
|
||||
|
||||
// Step 7: 테이블이 뷰포트에 맞는지 (overflow 확인)
|
||||
const bodyOverflow = await page.evaluate(() => {
|
||||
const main = document.querySelector("main") || document.body;
|
||||
return window.getComputedStyle(main).overflowX;
|
||||
});
|
||||
results.bodyOverflowX = bodyOverflow;
|
||||
|
||||
// Step 8: 중간 스크린샷 (테이블 + 페이지네이션 영역)
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "verify-03-mid.png"), fullPage: true });
|
||||
console.log("verify-03-mid.png 저장");
|
||||
|
||||
// Step 9: 페이지 하단으로 스크롤 (페이지네이션 바 확인)
|
||||
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "verify-04-pagination-area.png"), fullPage: true });
|
||||
console.log("verify-04-pagination-area.png 저장");
|
||||
|
||||
// Step 10: 최종 스크린샷
|
||||
await page.evaluate(() => window.scrollTo(0, 0));
|
||||
await page.waitForTimeout(300);
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "verify-05-final.png"), fullPage: true });
|
||||
console.log("verify-05-final.png 저장");
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(SCREENSHOT_DIR, "verify-result.json"),
|
||||
JSON.stringify(results, null, 2)
|
||||
);
|
||||
console.log("\n검증 결과:", results);
|
||||
} catch (error: any) {
|
||||
console.error("오류:", error.message);
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "verify-99-error.png"), fullPage: true }).catch(() => {});
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,162 +0,0 @@
|
||||
/**
|
||||
* 화면 150 검증 - 탑씰 영업 거래처관리
|
||||
*/
|
||||
|
||||
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");
|
||||
|
||||
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 report: Record<string, any> = {};
|
||||
|
||||
try {
|
||||
await page.goto(`${BASE_URL}/login`, { waitUntil: "load", timeout: 90000 });
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await page.fill("#userId", "admin");
|
||||
await page.fill("#password", "1234");
|
||||
await page.locator('button[type="submit"]').first().click();
|
||||
await page.waitForURL((u: URL) => !u.includes("/login"), { timeout: 20000 }).catch(() => {});
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
if (page.url().includes("/login")) {
|
||||
await page.fill("#userId", "wace");
|
||||
await page.fill("#password", "qlalfqjsgh11");
|
||||
await page.locator('button[type="submit"]').first().click();
|
||||
await page.waitForURL((u: URL) => !u.includes("/login"), { timeout: 20000 }).catch(() => {});
|
||||
await page.waitForTimeout(3000);
|
||||
}
|
||||
|
||||
const companyBtn = page.getByText("회사 선택").first();
|
||||
if ((await companyBtn.count()) > 0) {
|
||||
const currentCompany = await page.getByText("현재 관리 회사").locator("..").textContent().catch(() => "");
|
||||
if (!currentCompany?.includes("탑씰") && !currentCompany?.includes("COMPANY_7")) {
|
||||
await companyBtn.click();
|
||||
await page.waitForTimeout(1500);
|
||||
const tapseal = page.getByText("탑씰", { exact: true }).first();
|
||||
const company7 = page.getByText("COMPANY_7", { exact: true }).first();
|
||||
if ((await tapseal.count()) > 0) {
|
||||
await tapseal.click();
|
||||
} else if ((await company7.count()) > 0) {
|
||||
await company7.click();
|
||||
}
|
||||
await page.waitForTimeout(2000);
|
||||
}
|
||||
}
|
||||
|
||||
await page.goto(`${BASE_URL}/screens/150`, { waitUntil: "domcontentloaded", timeout: 60000 });
|
||||
await page.waitForTimeout(3000);
|
||||
if (page.url().includes("/login")) {
|
||||
await page.fill("#userId", "wace");
|
||||
await page.fill("#password", "qlalfqjsgh11");
|
||||
await page.locator('button[type="submit"]').first().click();
|
||||
await page.waitForURL((u: URL) => !u.includes("/login"), { timeout: 20000 }).catch(() => {});
|
||||
await page.waitForTimeout(3000);
|
||||
await page.goto(`${BASE_URL}/screens/150`, { waitUntil: "domcontentloaded", timeout: 60000 });
|
||||
await page.waitForTimeout(3000);
|
||||
}
|
||||
|
||||
await page.getByText("화면을 불러오는 중", { exact: false }).waitFor({ state: "hidden", timeout: 25000 }).catch(() => {});
|
||||
await page.waitForTimeout(8000);
|
||||
|
||||
const info = await page.evaluate(() => {
|
||||
const buttons = Array.from(document.querySelectorAll("button")).filter((b) => {
|
||||
const t = (b as HTMLElement).innerText?.trim() || "";
|
||||
const r = (b as HTMLElement).getBoundingClientRect();
|
||||
return t.length > 1 && r.x > 250 && r.width > 0;
|
||||
});
|
||||
const viewportWidth = window.innerWidth;
|
||||
const leftThird = viewportWidth * 0.33;
|
||||
const rightThird = viewportWidth * 0.66;
|
||||
|
||||
const btnDetails = buttons.map((b) => {
|
||||
const r = (b as HTMLElement).getBoundingClientRect();
|
||||
const text = (b as HTMLElement).innerText?.trim().substring(0, 40);
|
||||
let group = "center";
|
||||
if (r.x < leftThird) group = "left";
|
||||
else if (r.x > rightThird) group = "right";
|
||||
return {
|
||||
text,
|
||||
x: Math.round(r.x),
|
||||
y: Math.round(r.y),
|
||||
width: Math.round(r.width),
|
||||
height: Math.round(r.height),
|
||||
right: Math.round(r.right),
|
||||
group,
|
||||
};
|
||||
});
|
||||
|
||||
const leftPanel = document.querySelector("[class*='border-r']");
|
||||
const tables = document.querySelectorAll("table");
|
||||
const rightPanel = document.querySelector("main")?.querySelectorAll("[class*='overflow'], [style*='overflow']");
|
||||
const leftRect = leftPanel ? (leftPanel as HTMLElement).getBoundingClientRect() : null;
|
||||
const mainRect = document.querySelector("main")?.getBoundingClientRect();
|
||||
const contentWidth = mainRect ? mainRect.width : viewportWidth;
|
||||
const leftWidthPercent = leftRect && contentWidth > 0 ? (leftRect.width / contentWidth) * 100 : null;
|
||||
|
||||
let overlaps = false;
|
||||
for (let i = 0; i < btnDetails.length; i++) {
|
||||
for (let j = i + 1; j < btnDetails.length; j++) {
|
||||
const a = btnDetails[i];
|
||||
const b = btnDetails[j];
|
||||
if (Math.abs(a.y - b.y) < 30 && !(a.right < b.x || b.right < a.x)) overlaps = true;
|
||||
}
|
||||
}
|
||||
|
||||
const rightTable = tables.length > 1 ? tables[1] : tables[0];
|
||||
const rightTableRect = rightTable ? (rightTable as HTMLElement).getBoundingClientRect() : null;
|
||||
const rightTableScrollable = rightTable
|
||||
? (() => {
|
||||
let el: Element | null = rightTable;
|
||||
while (el) {
|
||||
const s = window.getComputedStyle(el);
|
||||
if (s.overflowY === "auto" || s.overflowY === "scroll" || s.overflow === "auto") return true;
|
||||
el = el.parentElement;
|
||||
}
|
||||
return false;
|
||||
})()
|
||||
: null;
|
||||
|
||||
return {
|
||||
buttonCount: buttons.length,
|
||||
buttonDetails: btnDetails,
|
||||
leftGroup: btnDetails.filter((b) => b.group === "left"),
|
||||
centerGroup: btnDetails.filter((b) => b.group === "center"),
|
||||
rightGroup: btnDetails.filter((b) => b.group === "right"),
|
||||
splitPanelVisible: !!leftPanel,
|
||||
leftWidthPercent: leftWidthPercent ? Math.round(leftWidthPercent) : null,
|
||||
rightWidthPercent: leftWidthPercent ? Math.round(100 - leftWidthPercent) : null,
|
||||
tableCount: tables.length,
|
||||
rightPanelHasTable: !!rightTable,
|
||||
rightTableScrollable,
|
||||
buttonsOverlap: overlaps,
|
||||
};
|
||||
});
|
||||
|
||||
report.screen150 = info;
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "screen-150-tapseal.png"), fullPage: true });
|
||||
console.log("screen-150-tapseal.png saved");
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(SCREENSHOT_DIR, "screen-150-tapseal-report.json"),
|
||||
JSON.stringify(report, null, 2)
|
||||
);
|
||||
console.log("\n=== Report ===");
|
||||
console.log(JSON.stringify(report, null, 2));
|
||||
} catch (error: any) {
|
||||
console.error("Error:", error.message);
|
||||
report.error = error.message;
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "screen-150-tapseal-error.png"), fullPage: true }).catch(() => {});
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,93 +0,0 @@
|
||||
/**
|
||||
* 화면 1556 검증: tabs-widget 레이아웃
|
||||
*/
|
||||
|
||||
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");
|
||||
|
||||
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 report: Record<string, any> = {};
|
||||
|
||||
try {
|
||||
await page.goto(`${BASE_URL}/screens/1556`, { waitUntil: "load", timeout: 30000 });
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
if (page.url().includes("/login")) {
|
||||
await page.fill("#userId", "wace");
|
||||
await page.fill("#password", "qlalfqjsgh11");
|
||||
await page.locator('button[type="submit"]').first().click();
|
||||
await page.waitForURL((u) => !u.includes("/login"), { timeout: 15000 }).catch(() => {});
|
||||
await page.waitForTimeout(2000);
|
||||
await page.goto(`${BASE_URL}/screens/1556`, { waitUntil: "load", timeout: 30000 });
|
||||
await page.waitForTimeout(2000);
|
||||
}
|
||||
|
||||
await page.getByText("로딩중", { exact: false }).waitFor({ state: "hidden", timeout: 10000 }).catch(() => {});
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
report.pageLoadsWithoutErrors = (await page.locator('text="화면을 찾을 수 없습니다"').count()) === 0;
|
||||
|
||||
const tabInfo = await page.evaluate(() => {
|
||||
const tabs = document.querySelectorAll("[role='tab'], [data-state='active'], [class*='TabsTrigger'], [class*='tab']");
|
||||
const tabList = document.querySelector("[role='tablist']");
|
||||
const loading = document.body.innerText.includes("로딩중") || document.body.innerText.includes("로딩 중");
|
||||
return {
|
||||
tabCount: tabs.length,
|
||||
hasTabList: !!tabList,
|
||||
tabHeadersVisible: tabs.length > 0 || !!tabList,
|
||||
stuckOnLoading: loading,
|
||||
};
|
||||
});
|
||||
report.tabHeadersVisible = tabInfo.tabHeadersVisible;
|
||||
report.tabCount = tabInfo.tabCount;
|
||||
report.tabContentLoadsProperly = !tabInfo.stuckOnLoading;
|
||||
|
||||
const loadingText = await page.getByText("로딩중", { exact: false }).count();
|
||||
report.stuckOnLoading = loadingText > 0;
|
||||
|
||||
const layoutFillsHeight = await page.evaluate(() => {
|
||||
const main = document.querySelector("main") || document.body;
|
||||
const h = (main as HTMLElement).offsetHeight;
|
||||
return h >= window.innerHeight * 0.8;
|
||||
});
|
||||
report.layoutFillsHeight = layoutFillsHeight;
|
||||
|
||||
const overflow = await page.evaluate(() => ({
|
||||
bodyScrollWidth: document.body.scrollWidth,
|
||||
bodyScrollHeight: document.body.scrollHeight,
|
||||
viewportWidth: window.innerWidth,
|
||||
viewportHeight: window.innerHeight,
|
||||
hasHorizontalOverflow: document.body.scrollWidth > window.innerWidth,
|
||||
hasVerticalOverflow: document.body.scrollHeight > window.innerHeight,
|
||||
}));
|
||||
report.noContentOverflowsViewport = !overflow.hasHorizontalOverflow;
|
||||
report.overflowDetails = overflow;
|
||||
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "screen-1556-snapshot.png"), fullPage: true });
|
||||
console.log("screen-1556-snapshot.png saved");
|
||||
|
||||
console.log("\n=== Report ===");
|
||||
console.log(JSON.stringify(report, null, 2));
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(SCREENSHOT_DIR, "screen-1556-report.json"),
|
||||
JSON.stringify(report, null, 2)
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error("Error:", error.message);
|
||||
report.error = error.message;
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "screen-1556-error.png"), fullPage: true }).catch(() => {});
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,111 +0,0 @@
|
||||
/**
|
||||
* 화면 156 검증: 로드, 버튼, 테이블, 페이지네이션, 레이아웃
|
||||
*/
|
||||
|
||||
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");
|
||||
|
||||
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 report: Record<string, any> = {};
|
||||
|
||||
try {
|
||||
await page.goto(`${BASE_URL}/screens/156`, { waitUntil: "load", timeout: 30000 });
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
const url = page.url();
|
||||
if (url.includes("/login")) {
|
||||
report.loginRequired = true;
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "screen-156-login.png"), fullPage: true });
|
||||
console.log("Login page - logging in with wace...");
|
||||
await page.fill("#userId", "wace");
|
||||
await page.fill("#password", "qlalfqjsgh11");
|
||||
await page.locator('button[type="submit"]').first().click();
|
||||
await page.waitForURL((u) => !u.includes("/login"), { timeout: 15000 }).catch(() => {});
|
||||
await page.waitForTimeout(2000);
|
||||
await page.goto(`${BASE_URL}/screens/156`, { waitUntil: "load", timeout: 30000 });
|
||||
await page.waitForTimeout(2000);
|
||||
}
|
||||
|
||||
const currentUrl = page.url();
|
||||
report.loginRequired = currentUrl.includes("/login");
|
||||
if (!currentUrl.includes("/login")) {
|
||||
await page.getByText("로딩중", { exact: false }).waitFor({ state: "hidden", timeout: 10000 }).catch(() => {});
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
const hasError = (await page.locator('text="화면을 찾을 수 없습니다"').count()) > 0;
|
||||
report.pageLoadsWithoutErrors = !hasError;
|
||||
|
||||
const buttonCount = await page.locator("button").count();
|
||||
const buttonsBetween = await page.evaluate(() => {
|
||||
const searchWidget = document.querySelector("[class*='search'], [class*='filter']");
|
||||
const table = document.querySelector("table");
|
||||
const buttons = document.querySelectorAll("button");
|
||||
let between = 0;
|
||||
buttons.forEach((btn) => {
|
||||
const rect = btn.getBoundingClientRect();
|
||||
if (searchWidget && table) {
|
||||
const sRect = searchWidget.getBoundingClientRect();
|
||||
const tRect = table.getBoundingClientRect();
|
||||
if (rect.top > sRect.bottom && rect.top < tRect.top) between++;
|
||||
}
|
||||
});
|
||||
return between;
|
||||
});
|
||||
report.buttonsVisible = buttonCount > 0;
|
||||
report.buttonsBetweenSearchAndTable = buttonsBetween;
|
||||
|
||||
const table = page.locator("table").first();
|
||||
const tableVisible = (await table.count()) > 0;
|
||||
report.tableVisible = tableVisible;
|
||||
|
||||
let tableOverflow = false;
|
||||
if (tableVisible) {
|
||||
const dims = await table.evaluate((el) => ({
|
||||
scrollWidth: el.scrollWidth,
|
||||
clientWidth: el.clientWidth,
|
||||
}));
|
||||
tableOverflow = dims.scrollWidth > dims.clientWidth;
|
||||
report.tableScrollWidth = dims.scrollWidth;
|
||||
report.tableClientWidth = dims.clientWidth;
|
||||
}
|
||||
report.tableOverflowsHorizontally = tableOverflow;
|
||||
|
||||
const paginationVisible = (await page.getByText("표시", { exact: false }).count()) > 0 ||
|
||||
(await page.getByText("1/", { exact: false }).count()) > 0;
|
||||
report.paginationBarVisible = paginationVisible;
|
||||
|
||||
const bodyScrollWidth = await page.evaluate(() => document.body.scrollWidth);
|
||||
const viewportWidth = 1280;
|
||||
report.bodyScrollWidth = bodyScrollWidth;
|
||||
report.hasHorizontalScrollbar = bodyScrollWidth > viewportWidth;
|
||||
report.layoutResponsive = !report.hasHorizontalScrollbar;
|
||||
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "screen-156-snapshot.png"), fullPage: true });
|
||||
console.log("screen-156-snapshot.png saved");
|
||||
}
|
||||
|
||||
console.log("\n=== Report ===");
|
||||
console.log(JSON.stringify(report, null, 2));
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(SCREENSHOT_DIR, "screen-156-report.json"),
|
||||
JSON.stringify(report, null, 2)
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error("Error:", error.message);
|
||||
report.error = error.message;
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "screen-156-error.png"), fullPage: true }).catch(() => {});
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,141 +0,0 @@
|
||||
/**
|
||||
* 화면 156, 1053 재검증 - 로딩 완료 후 스크린샷
|
||||
*/
|
||||
|
||||
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");
|
||||
|
||||
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 report: Record<string, any> = { screen156: {}, screen1053: {} };
|
||||
|
||||
try {
|
||||
// 1-2: Ensure logged in - goto login first, then login
|
||||
await page.goto(`${BASE_URL}/login`, { waitUntil: "domcontentloaded", timeout: 45000 });
|
||||
await page.waitForTimeout(2000);
|
||||
await page.fill("#userId", "wace");
|
||||
await page.fill("#password", "qlalfqjsgh11");
|
||||
await page.locator('button[type="submit"]').first().click();
|
||||
await page.waitForURL((u: URL) => !u.includes("/login"), { timeout: 20000 }).catch(() => {});
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
await page.goto(`${BASE_URL}/screens/156`, { waitUntil: "domcontentloaded", timeout: 45000 });
|
||||
await page.waitForTimeout(2000);
|
||||
if (page.url().includes("/login")) {
|
||||
await page.fill("#userId", "wace");
|
||||
await page.fill("#password", "qlalfqjsgh11");
|
||||
await page.locator('button[type="submit"]').first().click();
|
||||
await page.waitForURL((u: URL) => !u.includes("/login"), { timeout: 20000 }).catch(() => {});
|
||||
await page.waitForTimeout(3000);
|
||||
}
|
||||
|
||||
// 3: Company selection if present
|
||||
const companyBtn = page.getByText("회사 선택").first();
|
||||
if ((await companyBtn.count()) > 0) {
|
||||
await companyBtn.click();
|
||||
await page.waitForTimeout(1500);
|
||||
const companyOption = page.getByText("company7", { exact: true }).first();
|
||||
if ((await companyOption.count()) > 0) {
|
||||
await companyOption.click();
|
||||
} else {
|
||||
const anyOption = page.locator("[role='menuitem'], [role='option'], button").filter({ hasText: /회사|company/i }).first();
|
||||
if ((await anyOption.count()) > 0) await anyOption.click();
|
||||
}
|
||||
await page.waitForTimeout(2000);
|
||||
}
|
||||
|
||||
// 4: Screen 156 with menuObjid
|
||||
await page.goto(`${BASE_URL}/screens/156?menuObjid=1762421920156`, { waitUntil: "domcontentloaded", timeout: 45000 });
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
if (page.url().includes("/login")) {
|
||||
await page.fill("#userId", "wace");
|
||||
await page.fill("#password", "qlalfqjsgh11");
|
||||
await page.locator('button[type="submit"]').first().click();
|
||||
await page.waitForURL((u: URL) => !u.includes("/login"), { timeout: 15000 }).catch(() => {});
|
||||
await page.waitForTimeout(2000);
|
||||
await page.goto(`${BASE_URL}/screens/156?menuObjid=1762421920156`, { waitUntil: "domcontentloaded", timeout: 45000 });
|
||||
await page.waitForTimeout(3000);
|
||||
}
|
||||
|
||||
await page.getByText("화면을 불러오는 중", { exact: false }).waitFor({ state: "hidden", timeout: 35000 }).catch(() => {});
|
||||
await page.getByText("로딩중", { exact: false }).waitFor({ state: "hidden", timeout: 15000 }).catch(() => {});
|
||||
await page.waitForTimeout(30000);
|
||||
|
||||
const table156 = page.locator("table tbody tr");
|
||||
await table156.first().waitFor({ state: "visible", timeout: 15000 }).catch(() => {});
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
const info156 = await page.evaluate(() => {
|
||||
const table = document.querySelector("table");
|
||||
const tbody = document.querySelector("tbody");
|
||||
const rows = document.querySelectorAll("tbody tr");
|
||||
const buttons = Array.from(document.querySelectorAll("button")).filter((b) => (b as HTMLElement).innerText?.trim().length > 2);
|
||||
const pagination = document.body.innerText.includes("표시") || document.body.innerText.includes("1/");
|
||||
return {
|
||||
tableVisible: !!table && !!tbody,
|
||||
dataRowCount: rows.length,
|
||||
buttonsWithText: buttons.length,
|
||||
paginationVisible: !!pagination,
|
||||
buttonLabels: buttons.slice(0, 8).map((b) => (b as HTMLElement).innerText?.trim().substring(0, 20)),
|
||||
};
|
||||
});
|
||||
|
||||
report.screen156 = info156;
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "responsive-156-v2.png"), fullPage: true });
|
||||
console.log("responsive-156-v2.png saved");
|
||||
|
||||
// 5: Screen 1053 with menuObjid
|
||||
await page.goto(`${BASE_URL}/screens/1053?menuObjid=1762421920304`, { waitUntil: "domcontentloaded", timeout: 45000 });
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
await page.getByText("화면을 불러오는 중", { exact: false }).waitFor({ state: "hidden", timeout: 35000 }).catch(() => {});
|
||||
await page.getByText("로딩중", { exact: false }).waitFor({ state: "hidden", timeout: 15000 }).catch(() => {});
|
||||
await page.waitForTimeout(30000);
|
||||
|
||||
const splitPanel = page.locator("[class*='border-r'], [class*='split']").first();
|
||||
await splitPanel.waitFor({ state: "visible", timeout: 15000 }).catch(() => {});
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
const info1053 = await page.evaluate(() => {
|
||||
const leftPanel = document.querySelector("[class*='border-r']");
|
||||
const rightPanel = document.querySelector("main")?.querySelectorAll("div") || [];
|
||||
const buttons = Array.from(document.querySelectorAll("button")).filter((b) => (b as HTMLElement).innerText?.trim().length > 2);
|
||||
const table = document.querySelector("table");
|
||||
const bodyText = document.body.innerText;
|
||||
const hasSplitContent = bodyText.includes("좌측에서") || bodyText.includes("공급처") || bodyText.includes("품목");
|
||||
return {
|
||||
splitPanelVisible: !!leftPanel || hasSplitContent,
|
||||
buttonsWithText: buttons.length,
|
||||
tableVisible: !!table,
|
||||
buttonLabels: buttons.slice(0, 8).map((b) => (b as HTMLElement).innerText?.trim().substring(0, 25)),
|
||||
};
|
||||
});
|
||||
|
||||
report.screen1053 = info1053;
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "responsive-1053-v2.png"), fullPage: true });
|
||||
console.log("responsive-1053-v2.png saved");
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(SCREENSHOT_DIR, "verify-156-1053-v2-report.json"),
|
||||
JSON.stringify(report, null, 2)
|
||||
);
|
||||
console.log("\n=== Report ===");
|
||||
console.log(JSON.stringify(report, null, 2));
|
||||
} catch (error: any) {
|
||||
console.error("Error:", error.message);
|
||||
report.error = error.message;
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "verify-v2-error.png"), fullPage: true }).catch(() => {});
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,115 +0,0 @@
|
||||
/**
|
||||
* 화면 1722, 2089 검증: split-panel-layout2
|
||||
*/
|
||||
|
||||
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");
|
||||
|
||||
async function loginIfNeeded(page: any) {
|
||||
if (page.url().includes("/login")) {
|
||||
await page.fill("#userId", "wace");
|
||||
await page.fill("#password", "qlalfqjsgh11");
|
||||
await page.locator('button[type="submit"]').first().click();
|
||||
await page.waitForURL((u: URL) => !u.includes("/login"), { timeout: 15000 }).catch(() => {});
|
||||
await page.waitForTimeout(2000);
|
||||
}
|
||||
}
|
||||
|
||||
async function verifyScreen(
|
||||
page: any,
|
||||
screenId: number,
|
||||
report: Record<string, any>
|
||||
) {
|
||||
await page.goto(`${BASE_URL}/screens/${screenId}`, { waitUntil: "load", timeout: 30000 });
|
||||
await page.waitForTimeout(2000);
|
||||
if (page.url().includes("/login")) {
|
||||
await loginIfNeeded(page);
|
||||
await page.goto(`${BASE_URL}/screens/${screenId}`, { waitUntil: "load", timeout: 30000 });
|
||||
await page.waitForTimeout(2000);
|
||||
}
|
||||
|
||||
await page.getByText("로딩중", { exact: false }).waitFor({ state: "hidden", timeout: 10000 }).catch(() => {});
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
const info = await page.evaluate(() => {
|
||||
const splitPanels = document.querySelectorAll("[class*='split'], [class*='Split'], [data-panel], [class*='panel']");
|
||||
const hasSplitLayout = document.querySelector("[class*='split-panel'], [class*='SplitPanel'], [data-split]") !== null;
|
||||
const panels = document.querySelectorAll("[class*='panel'], [class*='Panel'], [class*='resize']");
|
||||
const leftPanelBorder = document.querySelectorAll("[class*='border-r']");
|
||||
const bodyText = document.body.innerText;
|
||||
const hasLeftRightPanels = bodyText.includes("왼쪽 목록에서") || bodyText.includes("품목 목록") || bodyText.includes("선택하세요");
|
||||
const buttons = document.querySelectorAll("button");
|
||||
const main = document.querySelector("main") || document.body;
|
||||
const mainHeight = (main as HTMLElement).offsetHeight;
|
||||
|
||||
return {
|
||||
pageLoadsWithoutErrors: !document.body.innerText.includes("화면을 찾을 수 없습니다"),
|
||||
splitPanelCount: splitPanels.length,
|
||||
panelCount: panels.length,
|
||||
leftPanelBorderCount: leftPanelBorder.length,
|
||||
bothPanelsVisible: panels.length >= 2 || splitPanels.length >= 2 || hasSplitLayout || (leftPanelBorder.length >= 1 && hasLeftRightPanels),
|
||||
buttonsVisible: buttons.length > 0,
|
||||
layoutFillsHeight: mainHeight >= window.innerHeight * 0.7,
|
||||
bodyScrollWidth: document.body.scrollWidth,
|
||||
bodyScrollHeight: document.body.scrollHeight,
|
||||
viewportWidth: window.innerWidth,
|
||||
viewportHeight: window.innerHeight,
|
||||
hasHorizontalOverflow: document.body.scrollWidth > window.innerWidth,
|
||||
hasVerticalOverflow: document.body.scrollHeight > window.innerHeight,
|
||||
};
|
||||
});
|
||||
|
||||
report.pageLoadsWithoutErrors = info.pageLoadsWithoutErrors;
|
||||
report.splitPanelVisible = info.bothPanelsVisible || info.splitPanelCount > 0 || info.panelCount >= 2 || (info.leftPanelBorderCount >= 1 && info.pageLoadsWithoutErrors);
|
||||
report.buttonsVisible = info.buttonsVisible;
|
||||
report.layoutFillsHeight = info.layoutFillsHeight;
|
||||
report.noContentOverflowsViewport = !info.hasHorizontalOverflow;
|
||||
report.details = info;
|
||||
|
||||
await page.screenshot({
|
||||
path: path.join(SCREENSHOT_DIR, `screen-${screenId}-snapshot.png`),
|
||||
fullPage: true,
|
||||
});
|
||||
console.log(`screen-${screenId}-snapshot.png saved`);
|
||||
}
|
||||
|
||||
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 report: Record<string, any> = { screen1722: {}, screen2089: {} };
|
||||
|
||||
try {
|
||||
await page.goto(`${BASE_URL}/login`, { waitUntil: "load", timeout: 30000 });
|
||||
await page.waitForTimeout(1000);
|
||||
await loginIfNeeded(page);
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
await verifyScreen(page, 1722, report.screen1722);
|
||||
await verifyScreen(page, 2089, report.screen2089);
|
||||
|
||||
console.log("\n=== Report ===");
|
||||
console.log(JSON.stringify(report, null, 2));
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(SCREENSHOT_DIR, "split-panel-screens-report.json"),
|
||||
JSON.stringify(report, null, 2)
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error("Error:", error.message);
|
||||
report.error = error.message;
|
||||
await page.screenshot({
|
||||
path: path.join(SCREENSHOT_DIR, "split-panel-error.png"),
|
||||
fullPage: true,
|
||||
}).catch(() => {});
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,164 +0,0 @@
|
||||
/**
|
||||
* 탑씰 회사 실제 데이터 화면 검증
|
||||
* - 회사 선택 → 탑씰
|
||||
* - 구매관리 > 발주관리
|
||||
* - 수주관리
|
||||
* - 테이블 가로 스크롤, 페이지네이션 확인
|
||||
*/
|
||||
|
||||
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");
|
||||
|
||||
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: Record<string, any> = {};
|
||||
|
||||
try {
|
||||
// Step 1: 접속
|
||||
console.log("Step 1: 접속...");
|
||||
await page.goto(BASE_URL, { waitUntil: "load", timeout: 30000 });
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
const url = page.url();
|
||||
if (url.includes("/login")) {
|
||||
console.log("로그인 필요...");
|
||||
await page.fill("#userId", "wace");
|
||||
await page.fill("#password", "qlalfqjsgh11");
|
||||
await page.locator('button[type="submit"]').first().click();
|
||||
await page.waitForURL((u) => !u.includes("/login"), { timeout: 15000 }).catch(() => {});
|
||||
await page.waitForTimeout(3000);
|
||||
}
|
||||
|
||||
await page.getByText("현재 관리 회사").waitFor({ timeout: 8000 }).catch(() => {});
|
||||
|
||||
// Step 2: 회사 선택 → 탑씰
|
||||
console.log("Step 2: 회사 선택 → 탑씰...");
|
||||
const companyBtn = page.getByText("회사 선택").first();
|
||||
if ((await companyBtn.count()) > 0) {
|
||||
await companyBtn.click();
|
||||
await page.waitForTimeout(1500);
|
||||
const tapseal = page.getByText("탑씰", { exact: true }).first();
|
||||
if ((await tapseal.count()) > 0) {
|
||||
await tapseal.click();
|
||||
await page.waitForTimeout(2500);
|
||||
console.log("탑씰 선택됨");
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: 구매관리 > 발주관리
|
||||
console.log("Step 3: 구매관리 > 발주관리 클릭...");
|
||||
const purchaseMgmt = page.getByText("구매관리").first();
|
||||
if ((await purchaseMgmt.count()) > 0) {
|
||||
await purchaseMgmt.click();
|
||||
await page.waitForTimeout(800);
|
||||
const orderMgmt = page.getByText("발주관리").first();
|
||||
if ((await orderMgmt.count()) > 0) {
|
||||
await orderMgmt.click();
|
||||
await page.waitForTimeout(4000);
|
||||
}
|
||||
}
|
||||
await page.getByText("로딩중", { exact: false }).waitFor({ state: "hidden", timeout: 10000 }).catch(() => {});
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Step 4: 발주관리 스크린샷
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "verify-10.png"), fullPage: true });
|
||||
console.log("verify-10.png 저장 (발주관리)");
|
||||
|
||||
// Step 5: 발주관리 - 테이블/스크롤/페이지네이션 확인
|
||||
const orderDims = await page.evaluate(() => {
|
||||
const table = document.querySelector("table");
|
||||
const tableContainer = table?.closest("[style*='overflow'], [class*='overflow']");
|
||||
const pagination = document.body.innerText.includes("표시") || document.body.innerText.includes("1/");
|
||||
return {
|
||||
tableScrollWidth: table ? (table as HTMLElement).scrollWidth : 0,
|
||||
tableClientWidth: table ? (table as HTMLElement).clientWidth : 0,
|
||||
containerClientWidth: tableContainer ? (tableContainer as HTMLElement).clientWidth : 0,
|
||||
hasPagination: pagination,
|
||||
dataRows: table ? table.querySelectorAll("tbody tr").length : 0,
|
||||
};
|
||||
});
|
||||
results.orderMgmt = orderDims;
|
||||
console.log("발주관리 - 테이블:", orderDims.tableScrollWidth, "x", orderDims.tableClientWidth, "데이터행:", orderDims.dataRows, "페이지네이션:", orderDims.hasPagination);
|
||||
|
||||
// Step 6: 테이블 가로 스크롤 시도
|
||||
const scrollResult = await page.evaluate(() => {
|
||||
const table = document.querySelector("table");
|
||||
const scrollable = table?.closest("[style*='overflow'], [class*='overflow']") as HTMLElement;
|
||||
if (scrollable && scrollable.scrollWidth > scrollable.clientWidth) {
|
||||
scrollable.scrollLeft = 200;
|
||||
return { scrolled: true, scrollLeft: scrollable.scrollLeft };
|
||||
}
|
||||
return { scrolled: false };
|
||||
});
|
||||
results.orderScroll = scrollResult;
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Step 7: 수주관리 메뉴 클릭
|
||||
console.log("Step 7: 수주관리 클릭...");
|
||||
const salesMgmt = page.getByText("영업관리").first();
|
||||
if ((await salesMgmt.count()) > 0) {
|
||||
await salesMgmt.click();
|
||||
await page.waitForTimeout(600);
|
||||
}
|
||||
const orderScreen = page.getByText("수주관리").first();
|
||||
if ((await orderScreen.count()) > 0) {
|
||||
await orderScreen.click();
|
||||
await page.waitForTimeout(4000);
|
||||
}
|
||||
await page.getByText("로딩중", { exact: false }).waitFor({ state: "hidden", timeout: 10000 }).catch(() => {});
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Step 8: 수주관리 스크린샷
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "verify-11.png"), fullPage: true });
|
||||
console.log("verify-11.png 저장 (수주관리)");
|
||||
|
||||
// Step 9: 수주관리 - 테이블/페이지네이션 확인
|
||||
const salesDims = await page.evaluate(() => {
|
||||
const table = document.querySelector("table");
|
||||
const pagination = document.body.innerText.includes("표시") || document.body.innerText.includes("1/");
|
||||
return {
|
||||
tableScrollWidth: table ? (table as HTMLElement).scrollWidth : 0,
|
||||
tableClientWidth: table ? (table as HTMLElement).clientWidth : 0,
|
||||
hasPagination: pagination,
|
||||
dataRows: table ? table.querySelectorAll("tbody tr").length : 0,
|
||||
};
|
||||
});
|
||||
results.salesOrderMgmt = salesDims;
|
||||
console.log("수주관리 - 테이블:", salesDims.tableScrollWidth, "x", salesDims.tableClientWidth, "데이터행:", salesDims.dataRows, "페이지네이션:", salesDims.hasPagination);
|
||||
|
||||
// 이전 문제 해결 여부
|
||||
const orderTableFits = orderDims.tableScrollWidth <= (orderDims.containerClientWidth || orderDims.tableClientWidth + 100);
|
||||
const salesTableFits = salesDims.tableScrollWidth <= salesDims.tableClientWidth + 100;
|
||||
results.issuesResolved = {
|
||||
orderTableOverflow: orderTableFits,
|
||||
orderPaginationVisible: orderDims.hasPagination,
|
||||
salesTableOverflow: salesTableFits,
|
||||
salesPaginationVisible: salesDims.hasPagination,
|
||||
};
|
||||
|
||||
console.log("\n=== 이전 문제 해결 여부 ===");
|
||||
console.log("발주관리 - 테이블 넘침 해결:", orderTableFits);
|
||||
console.log("발주관리 - 페이지네이션 보임:", orderDims.hasPagination);
|
||||
console.log("수주관리 - 테이블 넘침 해결:", salesTableFits);
|
||||
console.log("수주관리 - 페이지네이션 보임:", salesDims.hasPagination);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(SCREENSHOT_DIR, "verify-tapseal-result.json"),
|
||||
JSON.stringify(results, null, 2)
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error("오류:", error.message);
|
||||
await page.screenshot({ path: path.join(SCREENSHOT_DIR, "verify-99-error.png"), fullPage: true }).catch(() => {});
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user