ERP-node/frontend/scripts/approval-flow-test.ts

172 lines
7.0 KiB
TypeScript
Raw Permalink Normal View History

/**
* E2E
* 실행: npx tsx scripts/approval-flow-test.ts
*/
import { chromium } from "playwright";
const BASE_URL = "http://localhost:9771";
const LOGIN_ID = "wace";
const LOGIN_PW = "1234";
const FALLBACK_PW = "qlalfqjsgh11"; // 마스터 패스워드 (1234 실패 시)
async function main() {
const results: string[] = [];
const consoleErrors: string[] = [];
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
viewport: { width: 1280, height: 800 }, // 데스크톱 뷰 (사이드바 표시)
});
const page = await context.newPage();
// 콘솔 에러 수집
page.on("console", (msg) => {
const type = msg.type();
if (type === "error") {
const text = msg.text();
consoleErrors.push(text);
}
});
try {
// 1. http://localhost:9771 이동
results.push("=== 1. http://localhost:9771 이동 ===");
await page.goto(BASE_URL, { waitUntil: "networkidle", timeout: 15000 });
results.push("OK: 페이지 로드 완료");
// 2. 로그인 여부 확인
results.push("\n=== 2. 로그인 상태 확인 ===");
const isLoginPage = await page.locator('#userId, input[name="userId"]').count() > 0;
if (isLoginPage) {
results.push("로그인 페이지 감지됨. 로그인 시도...");
await page.fill('#userId', LOGIN_ID);
await page.fill('#password', LOGIN_PW);
await page.click('button[type="submit"]');
await page.waitForTimeout(4000);
// 여전히 로그인 페이지면 마스터 패스워드로 재시도
const stillLoginPage = await page.locator('#userId').count() > 0;
if (stillLoginPage) {
results.push("1234 로그인 실패. 마스터 패스워드로 재시도...");
await page.fill('#userId', LOGIN_ID);
await page.fill('#password', FALLBACK_PW);
await page.click('button[type="submit"]');
await page.waitForTimeout(4000);
}
results.push("로그인 폼 제출 완료");
} else {
results.push("이미 로그인된 상태로 판단 (로그인 폼 없음)");
}
// 3. 사용자 프로필 아바타 클릭 (사이드바 하단)
results.push("\n=== 3. 사용자 프로필 아바타 클릭 ===");
await page.waitForTimeout(2000);
// 사이드바 하단 사용자 프로필 버튼 (border-t border-slate-200 내부의 button)
const sidebarAvatarBtn = page.locator('aside div.border-t.border-slate-200 button').first();
let avatarClicked = false;
if ((await sidebarAvatarBtn.count()) > 0) {
try {
// force: true - Next.js dev overlay가 클릭을 가로채는 경우 우회
await sidebarAvatarBtn.click({ timeout: 5000, force: true });
avatarClicked = true;
results.push("OK: 사이드바 하단 아바타 클릭 완료");
await page.waitForTimeout(500); // 드롭다운 열림 대기
} catch (e) {
results.push(`WARN: 사이드바 아바타 클릭 실패 - ${(e as Error).message}`);
}
}
if (!avatarClicked) {
// 모바일 헤더 아바타 또는 fallback
const headerAvatar = page.locator('header button:has(div.rounded-full)').first();
if ((await headerAvatar.count()) > 0) {
await headerAvatar.click({ force: true });
avatarClicked = true;
results.push("OK: 헤더 아바타 클릭 (모바일 뷰?)");
}
}
if (!avatarClicked) {
results.push("WARN: 아바타 클릭 실패. 직접 /admin/approvalBox로 이동하여 페이지 검증");
await page.goto(`${BASE_URL}/admin/approvalBox`, { waitUntil: "networkidle", timeout: 10000 });
}
await page.waitForTimeout(1000);
// 4. "결재함" 메뉴 확인 (드롭다운이 열린 경우)
results.push("\n=== 4. 결재함 메뉴 확인 ===");
const approvalMenuItem = page.locator('[role="menuitem"]:has-text("결재함"), [data-radix-collection-item]:has-text("결재함")').first();
const hasApprovalMenu = (await approvalMenuItem.count()) > 0;
if (hasApprovalMenu) {
results.push("OK: 결재함 메뉴가 보입니다.");
} else {
results.push("FAIL: 결재함 메뉴를 찾을 수 없습니다.");
}
// 5. 결재함 메뉴 클릭
results.push("\n=== 5. 결재함 메뉴 클릭 ===");
if (hasApprovalMenu) {
await approvalMenuItem.click({ force: true });
await page.waitForTimeout(3000);
results.push("OK: 결재함 메뉴 클릭 완료");
} else if (!avatarClicked) {
results.push("(직접 이동으로 스킵 - 이미 approvalBox 페이지)");
} else {
results.push("WARN: 드롭다운에서 결재함 메뉴 미발견. 직접 이동...");
await page.goto(`${BASE_URL}/admin/approvalBox`, { waitUntil: "networkidle", timeout: 10000 });
await page.waitForTimeout(2000);
}
// 6. /admin/approvalBox 페이지 렌더링 확인
results.push("\n=== 6. /admin/approvalBox 페이지 확인 ===");
const currentUrl = page.url();
const isApprovalBoxPage = currentUrl.includes("/admin/approvalBox");
results.push(`현재 URL: ${currentUrl}`);
results.push(isApprovalBoxPage ? "OK: approvalBox 페이지에 있습니다." : "FAIL: approvalBox 페이지가 아닙니다.");
// 제목 "결재함" 확인
const titleEl = page.locator('h1:has-text("결재함")');
const hasTitle = (await titleEl.count()) > 0;
results.push(hasTitle ? "OK: 제목 '결재함' 확인됨" : "FAIL: 제목 '결재함' 없음");
// 탭 확인: 수신함, 상신함
const receivedTab = page.locator('button[role="tab"], [role="tab"]').filter({ hasText: "수신함" });
const sentTab = page.locator('button[role="tab"], [role="tab"]').filter({ hasText: "상신함" });
const hasReceivedTab = (await receivedTab.count()) > 0;
const hasSentTab = (await sentTab.count()) > 0;
results.push(hasReceivedTab ? "OK: '수신함' 탭 확인됨" : "FAIL: '수신함' 탭 없음");
results.push(hasSentTab ? "OK: '상신함' 탭 확인됨" : "FAIL: '상신함' 탭 없음");
// 7. 콘솔 에러 확인
results.push("\n=== 7. 콘솔 에러 확인 ===");
if (consoleErrors.length === 0) {
results.push("OK: 콘솔 에러 없음");
} else {
results.push(`WARN: 콘솔 에러 ${consoleErrors.length}건 발견:`);
consoleErrors.slice(0, 10).forEach((err, i) => {
results.push(` [${i + 1}] ${err.substring(0, 200)}${err.length > 200 ? "..." : ""}`);
});
if (consoleErrors.length > 10) {
results.push(` ... 외 ${consoleErrors.length - 10}`);
}
}
// 스크린샷 저장 (프로젝트 내)
await page.screenshot({ path: "approval-box-result.png" }).catch(() => {});
} catch (err: any) {
results.push(`\nERROR: ${err.message}`);
} finally {
await browser.close();
}
// 결과 출력
console.log("\n" + "=".repeat(60));
console.log("결재함 플로우 테스트 결과");
console.log("=".repeat(60));
results.forEach((r) => console.log(r));
console.log("\n" + "=".repeat(60));
}
main();