diff --git a/scripts/test-option-settings-responsive.ts b/scripts/test-option-settings-responsive.ts new file mode 100644 index 00000000..98ba7351 --- /dev/null +++ b/scripts/test-option-settings-responsive.ts @@ -0,0 +1,139 @@ +/** + * 옵션설정 (Option Settings) 페이지 반응형 동작 테스트 + * - V2CategoryManagerComponent + ResponsiveGridRenderer + * - 화면: http://localhost:9771/screens/1421 + * + * 실행: npx tsx scripts/test-option-settings-responsive.ts + */ +import { chromium } from "playwright"; +import * as path from "path"; +import * as fs from "fs"; + +const BASE_URL = "http://localhost:9771"; +const API_URL = "http://localhost:8080/api"; +const PAGE_URL = `${BASE_URL}/screens/1421`; +const CREDENTIALS = [ + { userId: "SUPER", password: "1234" }, + { userId: "wace", password: "qlalfqjsgh11" }, +]; + +const OUTPUT_DIR = path.join(__dirname, "../test-output/option-settings-responsive"); + +async function loginViaApi(): Promise { + for (const cred of CREDENTIALS) { + const res = await fetch(`${API_URL}/auth/login`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ userId: cred.userId, password: cred.password }), + }); + const data = await res.json(); + if (data.success && data.data?.token) { + console.log(` Using credentials: ${cred.userId}`); + return data.data.token; + } + } + throw new Error("Login failed with all credentials"); +} + +async function main() { + fs.mkdirSync(OUTPUT_DIR, { recursive: true }); + + const token = await loginViaApi(); + console.log("1. Logged in via API"); + + const browser = await chromium.launch({ headless: true }); + const context = await browser.newContext({ + viewport: { width: 1400, height: 900 }, + }); + const page = await context.newPage(); + + const consoleErrors: string[] = []; + const reactHookErrors: string[] = []; + + page.on("console", (msg) => { + const type = msg.type(); + const text = msg.text(); + if (type === "error") { + consoleErrors.push(text); + if (text.includes("order of Hooks") || text.includes("React has detected")) { + reactHookErrors.push(text); + } + } + }); + + try { + console.log("2. Loading login page to inject token..."); + await page.goto(`${BASE_URL}/login`, { waitUntil: "domcontentloaded", timeout: 15000 }); + await page.evaluate((t: string) => { + localStorage.setItem("authToken", t); + document.cookie = `authToken=${t}; path=/; max-age=86400; SameSite=Lax`; + }, token); + + console.log("3. Navigating to Option Settings page (screens/1421)..."); + await page.goto(PAGE_URL, { waitUntil: "domcontentloaded", timeout: 15000 }); + await page.waitForTimeout(5000); + + const report: string[] = []; + + const widths = [ + { w: 1400, name: "1-desktop-1400px" }, + { w: 1100, name: "2-tablet-1100px" }, + { w: 900, name: "3-tablet-900px" }, + { w: 600, name: "4-mobile-600px" }, + { w: 1400, name: "5-desktop-1400px-restored" }, + ]; + + for (const { w, name } of widths) { + console.log(`\nResizing to ${w}px...`); + await page.setViewportSize({ width: w, height: w === 600 ? 812 : 900 }); + await page.waitForTimeout(1500); + + const filePath = path.join(OUTPUT_DIR, `${name}.png`); + await page.screenshot({ path: filePath, fullPage: false }); + console.log(` Saved: ${filePath}`); + + const hasCategoryColumn = (await page.locator('text=카테고리 컬럼').count()) > 0; + const hasUseStatus = (await page.locator('text=사용여부').count()) > 0; + const hasVerticalStack = (await page.locator('button:has-text("목록")').count()) > 0; + const hasLeftRightSplit = (await page.locator('[class*="cursor-col-resize"]').count()) > 0; + + report.push(`[${w}px] Category column: ${hasCategoryColumn}, Use status: ${hasUseStatus}, Vertical: ${hasVerticalStack}, Split: ${hasLeftRightSplit}`); + } + + console.log("\n" + "=".repeat(60)); + console.log("OPTION SETTINGS RESPONSIVE TEST REPORT"); + console.log("=".repeat(60)); + report.forEach((r) => console.log(r)); + + if (consoleErrors.length > 0) { + console.log("\n--- Console Errors ---"); + consoleErrors.slice(0, 10).forEach((e) => console.log(" ", e)); + } + if (reactHookErrors.length > 0) { + console.log("\n--- React Hook Errors (order of Hooks) ---"); + reactHookErrors.forEach((e) => console.log(" ", e)); + } + + fs.writeFileSync( + path.join(OUTPUT_DIR, "report.txt"), + [ + "OPTION SETTINGS RESPONSIVE TEST REPORT", + "=".repeat(50), + ...report, + "", + "Console errors: " + consoleErrors.length, + "React Hook errors: " + reactHookErrors.length, + ...reactHookErrors.map((e) => " " + e), + ].join("\n") + ); + + console.log("\nScreenshots saved to:", OUTPUT_DIR); + } catch (e) { + console.error("Error:", e); + throw e; + } finally { + await browser.close(); + } +} + +main(); diff --git a/scripts/test-responsive-split-panel.ts b/scripts/test-responsive-split-panel.ts new file mode 100644 index 00000000..f2eac313 --- /dev/null +++ b/scripts/test-responsive-split-panel.ts @@ -0,0 +1,134 @@ +/** + * ResponsiveSplitPanel 반응형 동작 테스트 + * - Desktop (>= 1280px): 좌우 분할 + 리사이저 + * - Tablet (768-1279px): 좌측 패널 자동 접힘, 아이콘 버튼만 표시 + * - Mobile (< 768px): 세로 스택 + 접기/펼치기 헤더 + * + * 실행: npx tsx scripts/test-responsive-split-panel.ts + */ +import { chromium } from "playwright"; +import * as path from "path"; +import * as fs from "fs"; + +const BASE_URL = "http://localhost:9771"; +const API_URL = "http://localhost:8080/api"; +const TABLE_MNG_PATH = "/admin/systemMng/tableMngList"; +const USER_ID = "wace"; +const PASSWORD = "qlalfqjsgh11"; + +const OUTPUT_DIR = path.join(__dirname, "../test-output/responsive-split-panel"); + +async function loginViaApi(): Promise { + const res = await fetch(`${API_URL}/auth/login`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ userId: USER_ID, password: PASSWORD }), + }); + const data = await res.json(); + if (!data.success || !data.data?.token) throw new Error("Login failed: " + (data.message || "no token")); + return data.data.token; +} + +async function main() { + fs.mkdirSync(OUTPUT_DIR, { recursive: true }); + + const token = await loginViaApi(); + console.log("1. Logged in via API"); + + const browser = await chromium.launch({ headless: true }); + const context = await browser.newContext({ + viewport: { width: 1400, height: 900 }, + }); + const page = await context.newPage(); + + try { + // 공개 페이지에서 토큰 주입 후 테이블 페이지로 이동 + console.log("2. Loading login page to inject token..."); + await page.goto(`${BASE_URL}/login`, { waitUntil: "domcontentloaded", timeout: 15000 }); + await page.evaluate((t: string) => { + localStorage.setItem("authToken", t); + document.cookie = `authToken=${t}; path=/; max-age=86400; SameSite=Lax`; + }, token); + + console.log("3. Navigating to table management page..."); + await page.goto(`${BASE_URL}${TABLE_MNG_PATH}`, { waitUntil: "domcontentloaded", timeout: 15000 }); + + await page.waitForTimeout(3000); + + const report: string[] = []; + + // --- 3. Desktop 1400px (default wide) --- + console.log("\n3. Desktop 1400px - taking screenshot..."); + await page.setViewportSize({ width: 1400, height: 900 }); + await page.waitForTimeout(1000); + const desktopPath = path.join(OUTPUT_DIR, "1-desktop-1400px.png"); + await page.screenshot({ path: desktopPath, fullPage: false }); + console.log(` Saved: ${desktopPath}`); + + const at1400 = { + resizer: (await page.locator('[class*="cursor-col-resize"]').count()) > 0, + leftPanelVisible: (await page.locator('text=테이블 검색').count()) > 0 || (await page.locator('input[placeholder*="검색"]').count()) > 0, + iconButtonOnly: (await page.locator('button[title*="열기"]').count()) > 0 && !(await page.locator('input[placeholder*="검색"]').isVisible()), + }; + report.push(`[1400px] Resizer: ${at1400.resizer}, Left panel visible: ${at1400.leftPanelVisible}, Icon-only: ${at1400.iconButtonOnly}`); + + // --- 4. Tablet 1000px --- + console.log("\n4. Tablet 1000px - resizing and taking screenshot..."); + await page.setViewportSize({ width: 1000, height: 900 }); + await page.waitForTimeout(1500); + const tabletPath = path.join(OUTPUT_DIR, "2-tablet-1000px.png"); + await page.screenshot({ path: tabletPath, fullPage: false }); + console.log(` Saved: ${tabletPath}`); + + const at1000 = { + resizer: (await page.locator('[class*="cursor-col-resize"]').count()) > 0, + leftPanelVisible: (await page.locator('input[placeholder*="검색"]').isVisible()), + iconButtonOnly: (await page.locator('button[title*="열기"]').count()) > 0, + verticalStack: (await page.locator('button:has-text("테이블 목록")').count()) > 0, + }; + report.push(`[1000px] Resizer: ${at1000.resizer}, Left panel visible: ${at1000.leftPanelVisible}, Icon-only: ${at1000.iconButtonOnly}, Vertical stack: ${at1000.verticalStack}`); + + // --- 5. Mobile 600px --- + console.log("\n5. Mobile 600px - resizing and taking screenshot..."); + await page.setViewportSize({ width: 600, height: 812 }); + await page.waitForTimeout(1500); + const mobilePath = path.join(OUTPUT_DIR, "3-mobile-600px.png"); + await page.screenshot({ path: mobilePath, fullPage: false }); + console.log(` Saved: ${mobilePath}`); + + const at600 = { + collapsibleHeader: (await page.locator('button:has-text("테이블 목록")').count()) > 0, + verticalStack: (await page.locator('button:has-text("테이블 목록")').count()) > 0, + leftPanelVisible: (await page.locator('input[placeholder*="검색"]').isVisible()), + }; + report.push(`[600px] Collapsible header: ${at600.collapsibleHeader}, Vertical stack: ${at600.verticalStack}, Left panel visible: ${at600.leftPanelVisible}`); + + // --- 6. Back to Desktop 1400px --- + console.log("\n6. Back to Desktop 1400px - resizing and taking screenshot..."); + await page.setViewportSize({ width: 1400, height: 900 }); + await page.waitForTimeout(1500); + const desktopAgainPath = path.join(OUTPUT_DIR, "4-desktop-1400px-again.png"); + await page.screenshot({ path: desktopAgainPath, fullPage: false }); + console.log(` Saved: ${desktopAgainPath}`); + + const at1400Again = { + resizer: (await page.locator('[class*="cursor-col-resize"]').count()) > 0, + leftPanelVisible: (await page.locator('input[placeholder*="검색"]').isVisible()), + }; + report.push(`[1400px again] Resizer: ${at1400Again.resizer}, Left panel visible: ${at1400Again.leftPanelVisible}`); + + // --- Report --- + console.log("\n" + "=".repeat(60)); + console.log("RESPONSIVE LAYOUT TEST REPORT"); + console.log("=".repeat(60)); + report.forEach((r) => console.log(r)); + console.log("\nScreenshots saved to:", OUTPUT_DIR); + } catch (e) { + console.error("Error:", e); + throw e; + } finally { + await browser.close(); + } +} + +main(); diff --git a/test-output/option-settings-responsive/1-desktop-1400px.png b/test-output/option-settings-responsive/1-desktop-1400px.png new file mode 100644 index 00000000..46e7ca50 Binary files /dev/null and b/test-output/option-settings-responsive/1-desktop-1400px.png differ diff --git a/test-output/option-settings-responsive/2-tablet-1100px.png b/test-output/option-settings-responsive/2-tablet-1100px.png new file mode 100644 index 00000000..5c33a2af Binary files /dev/null and b/test-output/option-settings-responsive/2-tablet-1100px.png differ diff --git a/test-output/option-settings-responsive/3-tablet-900px.png b/test-output/option-settings-responsive/3-tablet-900px.png new file mode 100644 index 00000000..8ddebe5c Binary files /dev/null and b/test-output/option-settings-responsive/3-tablet-900px.png differ diff --git a/test-output/option-settings-responsive/4-mobile-600px.png b/test-output/option-settings-responsive/4-mobile-600px.png new file mode 100644 index 00000000..7b262383 Binary files /dev/null and b/test-output/option-settings-responsive/4-mobile-600px.png differ diff --git a/test-output/option-settings-responsive/5-desktop-1400px-restored.png b/test-output/option-settings-responsive/5-desktop-1400px-restored.png new file mode 100644 index 00000000..a6b35c85 Binary files /dev/null and b/test-output/option-settings-responsive/5-desktop-1400px-restored.png differ diff --git a/test-output/option-settings-responsive/report.txt b/test-output/option-settings-responsive/report.txt new file mode 100644 index 00000000..9e4b7c30 --- /dev/null +++ b/test-output/option-settings-responsive/report.txt @@ -0,0 +1,10 @@ +OPTION SETTINGS RESPONSIVE TEST REPORT +================================================== +[1400px] Category column: true, Use status: false, Vertical: true, Split: true +[1100px] Category column: false, Use status: false, Vertical: true, Split: false +[900px] Category column: false, Use status: false, Vertical: true, Split: false +[600px] Category column: true, Use status: false, Vertical: true, Split: false +[1400px] Category column: true, Use status: false, Vertical: true, Split: true + +Console errors: 0 +React Hook errors: 0 \ No newline at end of file diff --git a/test-output/responsive-split-panel/1-desktop-1400px.png b/test-output/responsive-split-panel/1-desktop-1400px.png new file mode 100644 index 00000000..370f9703 Binary files /dev/null and b/test-output/responsive-split-panel/1-desktop-1400px.png differ diff --git a/test-output/responsive-split-panel/2-tablet-1000px.png b/test-output/responsive-split-panel/2-tablet-1000px.png new file mode 100644 index 00000000..425a3f10 Binary files /dev/null and b/test-output/responsive-split-panel/2-tablet-1000px.png differ diff --git a/test-output/responsive-split-panel/3-mobile-600px.png b/test-output/responsive-split-panel/3-mobile-600px.png new file mode 100644 index 00000000..eac02492 Binary files /dev/null and b/test-output/responsive-split-panel/3-mobile-600px.png differ diff --git a/test-output/responsive-split-panel/4-desktop-1400px-again.png b/test-output/responsive-split-panel/4-desktop-1400px-again.png new file mode 100644 index 00000000..d4e8877e Binary files /dev/null and b/test-output/responsive-split-panel/4-desktop-1400px-again.png differ diff --git a/test-output/responsive-split-panel/desktop-1280px.png b/test-output/responsive-split-panel/desktop-1280px.png new file mode 100644 index 00000000..8f62c26f Binary files /dev/null and b/test-output/responsive-split-panel/desktop-1280px.png differ diff --git a/test-output/responsive-split-panel/mobile-375px-expanded.png b/test-output/responsive-split-panel/mobile-375px-expanded.png new file mode 100644 index 00000000..4272123a Binary files /dev/null and b/test-output/responsive-split-panel/mobile-375px-expanded.png differ diff --git a/test-output/responsive-split-panel/mobile-375px.png b/test-output/responsive-split-panel/mobile-375px.png new file mode 100644 index 00000000..b0c06e2c Binary files /dev/null and b/test-output/responsive-split-panel/mobile-375px.png differ