Remove obsolete end-to-end test scripts and related files for screen and table components in the agent pipeline.

This commit is contained in:
DDD1542
2026-03-09 13:33:01 +09:00
parent d13884d572
commit 7910921c97
34 changed files with 3076 additions and 2001 deletions
+111
View File
@@ -0,0 +1,111 @@
/**
* 브라우저 검증 스크립트
* 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();
@@ -0,0 +1,156 @@
/**
* 회사 선택 → 메뉴 → 수주/구매관리 화면 검증
* 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();
+135
View File
@@ -0,0 +1,135 @@
/**
* 수주관리 화면(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();
@@ -0,0 +1,163 @@
/**
* 화면 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();
@@ -0,0 +1,130 @@
/**
* 화면 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();
+166
View File
@@ -0,0 +1,166 @@
/**
* 화면 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();
@@ -0,0 +1,126 @@
/**
* 반응형 렌더링 검증: 화면 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();
@@ -0,0 +1,103 @@
/**
* 화면 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();
+105
View File
@@ -0,0 +1,105 @@
/**
* 화면 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();
@@ -0,0 +1,156 @@
/**
* 화면 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();
@@ -0,0 +1,143 @@
/**
* 화면 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();
@@ -0,0 +1,142 @@
/**
* 화면 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();
+127
View File
@@ -0,0 +1,127 @@
/**
* 화면 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();
@@ -0,0 +1,162 @@
/**
* 화면 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();
+93
View File
@@ -0,0 +1,93 @@
/**
* 화면 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();
+111
View File
@@ -0,0 +1,111 @@
/**
* 화면 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();
@@ -0,0 +1,141 @@
/**
* 화면 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();
@@ -0,0 +1,115 @@
/**
* 화면 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();
+164
View File
@@ -0,0 +1,164 @@
/**
* 탑씰 회사 실제 데이터 화면 검증
* - 회사 선택 → 탑씰
* - 구매관리 > 발주관리
* - 수주관리
* - 테이블 가로 스크롤, 페이지네이션 확인
*/
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();