[agent-pipeline] pipe-20260311151253-nyk7 round-1

This commit is contained in:
DDD1542
2026-03-12 00:16:20 +09:00
parent 6f311148a5
commit 0bb024ca05
35 changed files with 2191 additions and 2 deletions
@@ -0,0 +1,389 @@
import { chromium, Page, Browser } from "playwright";
import * as fs from "fs";
import * as path from "path";
const BASE_URL = "http://localhost:9771";
const API_URL = "http://localhost:8080/api";
const OUTPUT_DIR = path.join(__dirname);
interface ComponentTestResult {
componentType: string;
screenId: number;
status: "pass" | "fail" | "no_panel" | "not_found" | "error";
errorMessage?: string;
consoleErrors: string[];
hasConfigPanel: boolean;
screenshot?: string;
}
const COMPONENT_SCREEN_MAP: Record<string, number> = {
"v2-input": 60,
"v2-select": 71,
"v2-date": 77,
"v2-button-primary": 47,
"v2-text-display": 114,
"v2-table-list": 47,
"v2-table-search-widget": 79,
"v2-media": 74,
"v2-split-panel-layout": 74,
"v2-tabs-widget": 1011,
"v2-section-card": 1188,
"v2-section-paper": 202,
"v2-card-display": 83,
"v2-numbering-rule": 130,
"v2-repeater": 1188,
"v2-divider-line": 1195,
"v2-location-swap-selector": 1195,
"v2-category-manager": 135,
"v2-file-upload": 138,
"v2-pivot-grid": 2327,
"v2-rack-structure": 1575,
"v2-repeat-container": 2403,
"v2-split-line": 4151,
"v2-bom-item-editor": 4154,
"v2-process-work-standard": 4158,
"v2-aggregation-widget": 4119,
"flow-widget": 77,
"entity-search-input": 3986,
"select-basic": 4470,
"textarea-basic": 3986,
"selected-items-detail-input": 227,
"screen-split-panel": 1674,
"split-panel-layout2": 2089,
"universal-form-modal": 2180,
"v2-table-grouped": 79,
"v2-status-count": 4498,
"v2-timeline-scheduler": 79,
};
async function login(page: Page): Promise<string> {
await page.goto(`${BASE_URL}/login`);
await page.waitForLoadState("networkidle");
await page.getByPlaceholder("사용자 ID를 입력하세요").fill("wace");
await page.getByPlaceholder("비밀번호를 입력하세요").fill("qlalfqjsgh11");
await page.getByRole("button", { name: "로그인" }).click();
await page.waitForTimeout(5000);
const token = await page.evaluate(() => localStorage.getItem("authToken") || "");
console.log("Login token obtained:", token ? "YES" : "NO");
return token;
}
async function openDesigner(page: Page, screenId: number): Promise<boolean> {
await page.evaluate(() => {
sessionStorage.setItem(
"erp-tab-store",
JSON.stringify({
state: {
tabs: [
{
id: "tab-screenmng",
title: "화면 관리",
path: "/admin/screenMng/screenMngList",
isActive: true,
isPinned: false,
},
],
activeTabId: "tab-screenmng",
},
version: 0,
})
);
});
await page.goto(`${BASE_URL}/admin/screenMng/screenMngList?openDesigner=${screenId}`);
await page.waitForTimeout(8000);
const designerOpen = await page.locator('[class*="designer"], [class*="canvas"], [data-testid*="designer"]').count();
const hasComponents = await page.locator('[data-component-id], [class*="component-wrapper"]').count();
console.log(` Designer elements: ${designerOpen}, Components: ${hasComponents}`);
return designerOpen > 0 || hasComponents > 0;
}
async function testComponentConfigPanel(
page: Page,
componentType: string,
screenId: number
): Promise<ComponentTestResult> {
const result: ComponentTestResult = {
componentType,
screenId,
status: "error",
consoleErrors: [],
hasConfigPanel: false,
};
const consoleErrors: string[] = [];
const pageErrors: string[] = [];
page.on("console", (msg) => {
if (msg.type() === "error") {
consoleErrors.push(msg.text());
}
});
page.on("pageerror", (err) => {
pageErrors.push(err.message);
});
try {
const opened = await openDesigner(page, screenId);
if (!opened) {
result.status = "error";
result.errorMessage = "Designer failed to open";
return result;
}
// find component by its url or type attribute in the DOM
const componentUrl = componentType.startsWith("v2-")
? `@/lib/registry/components/${componentType}`
: `@/lib/registry/components/${componentType}`;
// Try clicking on a component of this type
// The screen designer renders components with data attributes
const componentSelector = `[data-component-type="${componentType}"], [data-component-url*="${componentType}"]`;
const componentCount = await page.locator(componentSelector).count();
if (componentCount === 0) {
// Try alternative: look for components in the canvas by clicking around
// First try to find any clickable component wrapper
const wrappers = page.locator('[data-component-id]');
const wrapperCount = await wrappers.count();
if (wrapperCount === 0) {
result.status = "not_found";
result.errorMessage = `No components found in screen ${screenId}`;
return result;
}
// Click the first component to see if panel opens
let foundTarget = false;
for (let i = 0; i < Math.min(wrapperCount, 20); i++) {
try {
await wrappers.nth(i).click({ force: true, timeout: 2000 });
await page.waitForTimeout(1000);
// Check if the properties panel shows the right component type
const panelText = await page.locator('[class*="properties"], [class*="config-panel"], [class*="setting"]').textContent().catch(() => "");
if (panelText && panelText.includes(componentType)) {
foundTarget = true;
break;
}
} catch {
continue;
}
}
if (!foundTarget) {
result.status = "not_found";
result.errorMessage = `Component type "${componentType}" not clickable in screen ${screenId}`;
}
} else {
await page.locator(componentSelector).first().click({ force: true });
await page.waitForTimeout(2000);
}
// Check for the config panel in the right sidebar
await page.waitForTimeout(2000);
// Look for config panel indicators
const configPanelVisible = await page.evaluate(() => {
// Check for error boundaries or error messages
const errorElements = document.querySelectorAll('[class*="error"], [class*="Error"]');
const errorTexts: string[] = [];
errorElements.forEach((el) => {
const text = el.textContent || "";
if (text.includes("로드 실패") || text.includes("에러") || text.includes("Error") || text.includes("Cannot read")) {
errorTexts.push(text.substring(0, 200));
}
});
// Check for config panel elements
const hasTabs = document.querySelectorAll('button[role="tab"]').length > 0;
const hasLabels = document.querySelectorAll("label").length > 0;
const hasInputs = document.querySelectorAll('input, select, [role="combobox"]').length > 0;
const hasConfigContent = document.querySelectorAll('[class*="config"], [class*="panel"], [class*="properties"]').length > 0;
const hasEditTab = Array.from(document.querySelectorAll("button")).some((b) => b.textContent?.includes("편집"));
return {
errorTexts,
hasTabs,
hasLabels,
hasInputs,
hasConfigContent,
hasEditTab,
};
});
result.hasConfigPanel = configPanelVisible.hasConfigContent || configPanelVisible.hasEditTab;
// Take screenshot
const screenshotName = `${componentType.replace(/[^a-zA-Z0-9-]/g, "_")}.png`;
const screenshotPath = path.join(OUTPUT_DIR, screenshotName);
await page.screenshot({ path: screenshotPath, fullPage: false });
result.screenshot = screenshotName;
// Collect errors
result.consoleErrors = [...consoleErrors, ...pageErrors];
if (configPanelVisible.errorTexts.length > 0) {
result.status = "fail";
result.errorMessage = configPanelVisible.errorTexts.join("; ");
} else if (pageErrors.length > 0) {
result.status = "fail";
result.errorMessage = pageErrors.join("; ");
} else if (consoleErrors.some((e) => e.includes("Cannot read") || e.includes("is not a function") || e.includes("undefined"))) {
result.status = "fail";
result.errorMessage = consoleErrors.filter((e) => e.includes("Cannot read") || e.includes("is not a function")).join("; ");
} else {
result.status = "pass";
}
} catch (err: any) {
result.status = "error";
result.errorMessage = err.message;
}
return result;
}
async function main() {
console.log("=== Config Panel Full Audit ===");
console.log(`Testing ${Object.keys(COMPONENT_SCREEN_MAP).length} component types\n`);
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({ viewport: { width: 1920, height: 1080 } });
const page = await context.newPage();
// Login
const token = await login(page);
if (!token) {
console.error("Login failed!");
await browser.close();
return;
}
const results: ComponentTestResult[] = [];
const componentTypes = Object.keys(COMPONENT_SCREEN_MAP);
for (let i = 0; i < componentTypes.length; i++) {
const componentType = componentTypes[i];
const screenId = COMPONENT_SCREEN_MAP[componentType];
console.log(`\n[${i + 1}/${componentTypes.length}] Testing: ${componentType} (screen: ${screenId})`);
const result = await testComponentConfigPanel(page, componentType, screenId);
results.push(result);
const statusEmoji = {
pass: "OK",
fail: "FAIL",
no_panel: "NO_PANEL",
not_found: "NOT_FOUND",
error: "ERROR",
}[result.status];
console.log(` Result: ${statusEmoji} ${result.errorMessage || ""}`);
// Clear console listeners for next iteration
page.removeAllListeners("console");
page.removeAllListeners("pageerror");
}
await browser.close();
// Write results
const reportPath = path.join(OUTPUT_DIR, "audit-results.json");
fs.writeFileSync(reportPath, JSON.stringify(results, null, 2));
// Summary
console.log("\n\n=== AUDIT SUMMARY ===");
const passed = results.filter((r) => r.status === "pass");
const failed = results.filter((r) => r.status === "fail");
const errors = results.filter((r) => r.status === "error");
const notFound = results.filter((r) => r.status === "not_found");
console.log(`PASS: ${passed.length}`);
console.log(`FAIL: ${failed.length}`);
console.log(`ERROR: ${errors.length}`);
console.log(`NOT_FOUND: ${notFound.length}`);
if (failed.length > 0) {
console.log("\n--- FAILED Components ---");
failed.forEach((r) => {
console.log(` ${r.componentType} (screen: ${r.screenId}): ${r.errorMessage}`);
});
}
if (errors.length > 0) {
console.log("\n--- ERROR Components ---");
errors.forEach((r) => {
console.log(` ${r.componentType} (screen: ${r.screenId}): ${r.errorMessage}`);
});
}
// Write markdown report
const mdReport = generateMarkdownReport(results);
fs.writeFileSync(path.join(OUTPUT_DIR, "audit-report.md"), mdReport);
console.log(`\nReport saved to ${OUTPUT_DIR}/audit-report.md`);
}
function generateMarkdownReport(results: ComponentTestResult[]): string {
const lines: string[] = [];
lines.push("# Config Panel Audit Report");
lines.push(`\nDate: ${new Date().toISOString()}`);
lines.push(`\nTotal: ${results.length} components tested\n`);
const passed = results.filter((r) => r.status === "pass");
const failed = results.filter((r) => r.status === "fail");
const errors = results.filter((r) => r.status === "error");
const notFound = results.filter((r) => r.status === "not_found");
lines.push(`| Status | Count |`);
lines.push(`|--------|-------|`);
lines.push(`| PASS | ${passed.length} |`);
lines.push(`| FAIL | ${failed.length} |`);
lines.push(`| ERROR | ${errors.length} |`);
lines.push(`| NOT_FOUND | ${notFound.length} |`);
lines.push(`\n## Failed Components\n`);
if (failed.length === 0) {
lines.push("None\n");
} else {
lines.push(`| Component | Screen ID | Error |`);
lines.push(`|-----------|-----------|-------|`);
failed.forEach((r) => {
lines.push(`| ${r.componentType} | ${r.screenId} | ${(r.errorMessage || "").substring(0, 100)} |`);
});
}
lines.push(`\n## Error Components\n`);
if (errors.length === 0) {
lines.push("None\n");
} else {
lines.push(`| Component | Screen ID | Error |`);
lines.push(`|-----------|-----------|-------|`);
errors.forEach((r) => {
lines.push(`| ${r.componentType} | ${r.screenId} | ${(r.errorMessage || "").substring(0, 100)} |`);
});
}
lines.push(`\n## Not Found Components\n`);
if (notFound.length === 0) {
lines.push("None\n");
} else {
notFound.forEach((r) => {
lines.push(`- ${r.componentType} (screen: ${r.screenId}): ${r.errorMessage}`);
});
}
lines.push(`\n## All Results\n`);
lines.push(`| # | Component | Screen | Status | Config Panel | Error |`);
lines.push(`|---|-----------|--------|--------|--------------|-------|`);
results.forEach((r, i) => {
const status = r.status.toUpperCase();
lines.push(
`| ${i + 1} | ${r.componentType} | ${r.screenId} | ${status} | ${r.hasConfigPanel ? "Yes" : "No"} | ${(r.errorMessage || "-").substring(0, 80)} |`
);
});
return lines.join("\n");
}
main().catch(console.error);
@@ -0,0 +1,139 @@
import { chromium } from "playwright";
import * as fs from "fs";
import * as path from "path";
const BASE = "http://localhost:9771";
const OUT = path.join(__dirname);
async function main() {
const browser = await chromium.launch({ headless: true });
const ctx = await browser.newContext({ viewport: { width: 1920, height: 1080 } });
const page = await ctx.newPage();
const jsErrors: string[] = [];
page.on("pageerror", (e) => jsErrors.push(e.message));
page.on("console", (msg) => {
if (msg.type() === "error") jsErrors.push("[console.error] " + msg.text().substring(0, 200));
});
// 1) Login
console.log("1) Logging in...");
await page.goto(`${BASE}/login`);
await page.waitForLoadState("networkidle");
await page.waitForTimeout(3000);
await page.fill('[placeholder="사용자 ID를 입력하세요"]', "wace");
await page.fill('[placeholder="비밀번호를 입력하세요"]', "qlalfqjsgh11");
await page.click('button:has-text("로그인")');
await page.waitForTimeout(6000);
await page.screenshot({ path: path.join(OUT, "01-after-login.png") });
console.log(" URL after login:", page.url());
// 2) Open designer for screen 60 (has v2-input)
console.log("\n2) Opening designer for screen 60...");
await page.evaluate(() => {
sessionStorage.setItem("erp-tab-store", JSON.stringify({
state: { tabs: [{ id: "t", title: "화면관리", path: "/admin/screenMng/screenMngList", isActive: true, isPinned: false }], activeTabId: "t" },
version: 0,
}));
});
await page.goto(`${BASE}/admin/screenMng/screenMngList?openDesigner=60`, { waitUntil: "domcontentloaded", timeout: 60000 });
await page.waitForTimeout(12000);
await page.screenshot({ path: path.join(OUT, "02-designer-loaded.png") });
// 3) Check DOM for components
const domInfo = await page.evaluate(() => {
const compWrappers = document.querySelectorAll("[data-component-id]");
const ids = Array.from(compWrappers).map(el => el.getAttribute("data-component-id"));
const bodyText = document.body.innerText.substring(0, 500);
const hasCanvas = !!document.querySelector('[class*="canvas"], [class*="designer-content"]');
const allClasses = Array.from(document.querySelectorAll("[class]"))
.map(el => el.className)
.filter(c => typeof c === "string" && (c.includes("canvas") || c.includes("designer") || c.includes("panel")))
.slice(0, 20);
return { componentIds: ids, hasCanvas, bodyTextPreview: bodyText, relevantClasses: allClasses };
});
console.log(" Components in DOM:", domInfo.componentIds.length);
console.log(" Component IDs:", domInfo.componentIds.slice(0, 10));
console.log(" Has canvas:", domInfo.hasCanvas);
console.log(" Relevant classes:", domInfo.relevantClasses.slice(0, 5));
// 4) If components found, click the first one
if (domInfo.componentIds.length > 0) {
const firstId = domInfo.componentIds[0];
console.log(`\n3) Clicking component: ${firstId}`);
try {
await page.click(`[data-component-id="${firstId}"]`, { force: true, timeout: 5000 });
await page.waitForTimeout(3000);
await page.screenshot({ path: path.join(OUT, "03-component-clicked.png") });
// Check right panel
const panelInfo = await page.evaluate(() => {
const labels = document.querySelectorAll("label");
const inputs = document.querySelectorAll("input");
const selects = document.querySelectorAll('[role="combobox"]');
const tabs = document.querySelectorAll('[role="tab"]');
const switches = document.querySelectorAll('[role="switch"]');
const rightPanel = document.querySelector('[class*="right"], [class*="sidebar"], [class*="properties"]');
return {
labels: labels.length,
inputs: inputs.length,
selects: selects.length,
tabs: tabs.length,
switches: switches.length,
hasRightPanel: !!rightPanel,
rightPanelText: rightPanel?.textContent?.substring(0, 300) || "(no panel found)",
errorTexts: Array.from(document.querySelectorAll('[class*="error"], [class*="destructive"]'))
.map(el => (el as HTMLElement).innerText?.substring(0, 100))
.filter(t => t && t.length > 0),
};
});
console.log(" Panel info:", JSON.stringify(panelInfo, null, 2));
} catch (e: any) {
console.log(" Click failed:", e.message.substring(0, 100));
}
} else {
console.log("\n No components found in DOM - designer may not have loaded");
console.log(" Body preview:", domInfo.bodyTextPreview.substring(0, 200));
}
// 5) Try clicking on different areas of the page to find components
if (domInfo.componentIds.length === 0) {
console.log("\n4) Trying to find canvas area by clicking...");
// Take full page screenshot to see what's there
await page.screenshot({ path: path.join(OUT, "04-full-page.png"), fullPage: true });
// Get viewport-based content
const pageStructure = await page.evaluate(() => {
const allElements = document.querySelectorAll("div, section, main, aside");
const structure: string[] = [];
allElements.forEach(el => {
const rect = el.getBoundingClientRect();
if (rect.width > 100 && rect.height > 100) {
const cls = el.className?.toString().substring(0, 50) || "";
const id = el.id || "";
structure.push(`${el.tagName}#${id}.${cls} [${Math.round(rect.x)},${Math.round(rect.y)} ${Math.round(rect.width)}x${Math.round(rect.height)}]`);
}
});
return structure.slice(0, 30);
});
console.log(" Large elements:");
pageStructure.forEach(s => console.log(" " + s));
}
// Summary
console.log("\n\n=== SUMMARY ===");
console.log(`JS Errors: ${jsErrors.length}`);
if (jsErrors.length > 0) {
const unique = [...new Set(jsErrors)];
console.log("Unique errors:");
unique.slice(0, 20).forEach(e => console.log(" " + e.substring(0, 200)));
}
await browser.close();
console.log("\nScreenshots saved to:", OUT);
}
main().catch(console.error);
+308
View File
@@ -0,0 +1,308 @@
[
{
"type": "v2-input",
"screenId": 60,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "v2-select",
"screenId": 71,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "v2-date",
"screenId": 77,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "flow-widget",
"screenId": 77,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "v2-button-primary",
"screenId": 50,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "v2-text-display",
"screenId": 114,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "v2-table-list",
"screenId": 68,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "v2-table-search-widget",
"screenId": 79,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "v2-media",
"screenId": 74,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "v2-split-panel-layout",
"screenId": 74,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "v2-tabs-widget",
"screenId": 1011,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "v2-section-card",
"screenId": 1188,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "v2-repeater",
"screenId": 1188,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "v2-section-paper",
"screenId": 202,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "v2-card-display",
"screenId": 83,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "v2-numbering-rule",
"screenId": 130,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "v2-divider-line",
"screenId": 1195,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "v2-location-swap-selector",
"screenId": 1195,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "v2-category-manager",
"screenId": 135,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "v2-file-upload",
"screenId": 138,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "v2-pivot-grid",
"screenId": 2327,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "v2-rack-structure",
"screenId": 1575,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "v2-repeat-container",
"screenId": 2403,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "v2-split-line",
"screenId": 4151,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "v2-bom-item-editor",
"screenId": 4154,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "v2-process-work-standard",
"screenId": 4158,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "v2-aggregation-widget",
"screenId": 4119,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "entity-search-input",
"screenId": 3986,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "textarea-basic",
"screenId": 3986,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "select-basic",
"screenId": 4470,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "selected-items-detail-input",
"screenId": 227,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "screen-split-panel",
"screenId": 1674,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "split-panel-layout2",
"screenId": 2089,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
},
{
"type": "universal-form-modal",
"screenId": 2180,
"compId": "",
"status": "NO_COMPONENT",
"jsErrors": [],
"panelDetails": "",
"errorMsg": "Component not found in layout API data"
}
]
+287
View File
@@ -0,0 +1,287 @@
import { chromium, Page } from "playwright";
import * as fs from "fs";
import * as path from "path";
const BASE = "http://localhost:9771";
const OUT = path.join(__dirname);
const TARGETS: [string, number][] = [
["v2-input", 60],
["v2-select", 71],
["v2-date", 77],
["v2-button-primary", 50],
["v2-text-display", 114],
["v2-table-list", 68],
["v2-table-search-widget", 79],
["v2-media", 74],
["v2-split-panel-layout", 74],
["v2-tabs-widget", 1011],
["v2-section-card", 1188],
["v2-section-paper", 202],
["v2-card-display", 83],
["v2-numbering-rule", 130],
["v2-repeater", 1188],
["v2-divider-line", 1195],
["v2-location-swap-selector", 1195],
["v2-category-manager", 135],
["v2-file-upload", 138],
["v2-pivot-grid", 2327],
["v2-rack-structure", 1575],
["v2-repeat-container", 2403],
["v2-split-line", 4151],
["v2-bom-item-editor", 4154],
["v2-process-work-standard", 4158],
["v2-aggregation-widget", 4119],
["flow-widget", 77],
["entity-search-input", 3986],
["select-basic", 4470],
["textarea-basic", 3986],
["selected-items-detail-input", 227],
["screen-split-panel", 1674],
["split-panel-layout2", 2089],
["universal-form-modal", 2180],
];
interface Result {
type: string;
screenId: number;
compId: string;
status: "PASS" | "FAIL" | "ERROR" | "NO_COMPONENT";
jsErrors: string[];
panelDetails: string;
errorMsg?: string;
}
async function login(page: Page) {
await page.goto(`${BASE}/login`);
await page.waitForLoadState("networkidle");
await page.waitForTimeout(2000);
await page.fill('[placeholder="사용자 ID를 입력하세요"]', "wace");
await page.fill('[placeholder="비밀번호를 입력하세요"]', "qlalfqjsgh11");
await Promise.all([
page.waitForResponse((r) => r.url().includes("/auth/login"), { timeout: 15000 }).catch(() => null),
page.click('button:has-text("로그인")'),
]);
await page.waitForTimeout(5000);
const hasToken = await page.evaluate(() => !!localStorage.getItem("authToken"));
console.log("Login:", hasToken ? "OK" : "FAILED");
return hasToken;
}
async function getLayoutComponents(page: Page, screenId: number): Promise<any[]> {
return page.evaluate(async (sid) => {
const token = localStorage.getItem("authToken") || "";
const host = window.location.hostname;
const apiBase = host === "localhost" || host === "127.0.0.1"
? "http://localhost:8080/api"
: "/api";
try {
const resp = await fetch(`${apiBase}/screen-management/screens/${sid}/layout-v2`, {
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
});
const data = await resp.json();
if (data.success && data.data?.components) return data.data.components;
if (data.success && Array.isArray(data.data)) {
const all: any[] = [];
for (const layer of data.data) {
if (layer.layout_data?.components) all.push(...layer.layout_data.components);
}
return all;
}
return [];
} catch { return []; }
}, screenId);
}
function findCompId(components: any[], targetType: string): string {
for (const c of components) {
const url: string = c.url || "";
const ctype: string = c.componentType || "";
if (url.endsWith("/" + targetType) || ctype === targetType) return c.id;
}
return "";
}
async function openDesigner(page: Page, screenId: number) {
await page.evaluate(() => {
sessionStorage.setItem("erp-tab-store", JSON.stringify({
state: {
tabs: [{ id: "tab-sm", title: "화면 관리", path: "/admin/screenMng/screenMngList", isActive: true, isPinned: false }],
activeTabId: "tab-sm",
},
version: 0,
}));
});
await page.goto(`${BASE}/admin/screenMng/screenMngList?openDesigner=${screenId}`, {
timeout: 60000, waitUntil: "domcontentloaded",
});
await page.waitForTimeout(10000);
}
async function checkPanel(page: Page): Promise<{ visible: boolean; hasError: boolean; detail: string }> {
return page.evaluate(() => {
const body = document.body.innerText || "";
const hasError = body.includes("로드 실패") || body.includes("Cannot read properties");
const labels = document.querySelectorAll("label").length;
const inputs = document.querySelectorAll('input:not([type="hidden"])').length;
const selects = document.querySelectorAll('select, [role="combobox"], [role="listbox"]').length;
const tabs = document.querySelectorAll('[role="tab"]').length;
const switches = document.querySelectorAll('[role="switch"]').length;
const total = labels + inputs + selects + tabs + switches;
const detail = `L=${labels} I=${inputs} S=${selects} T=${tabs} SW=${switches}`;
return { visible: total > 3, hasError, detail };
});
}
async function main() {
console.log("=== Config Panel Audit v3 ===\n");
const browser = await chromium.launch({ headless: true });
const ctx = await browser.newContext({ viewport: { width: 1920, height: 1080 } });
const page = await ctx.newPage();
const jsErrors: string[] = [];
page.on("pageerror", (err) => jsErrors.push(err.message.substring(0, 300)));
const ok = await login(page);
if (!ok) { await browser.close(); return; }
const groups = new Map<number, string[]>();
for (const [type, sid] of TARGETS) {
if (!groups.has(sid)) groups.set(sid, []);
groups.get(sid)!.push(type);
}
const allResults: Result[] = [];
const entries = Array.from(groups.entries());
for (let i = 0; i < entries.length; i++) {
const [screenId, types] = entries[i];
console.log(`\n[${i + 1}/${entries.length}] Screen ${screenId}: ${types.join(", ")}`);
const components = await getLayoutComponents(page, screenId);
console.log(` API: ${components.length} comps`);
await openDesigner(page, screenId);
const domCompCount = await page.locator('[data-component-id]').count();
console.log(` DOM: ${domCompCount} comp wrappers`);
for (const targetType of types) {
const errIdx = jsErrors.length;
const compId = findCompId(components, targetType);
if (!compId) {
console.log(` ${targetType}: NO_COMPONENT`);
allResults.push({ type: targetType, screenId, compId: "", status: "NO_COMPONENT", jsErrors: [], panelDetails: "", errorMsg: "Not in layout" });
continue;
}
// 컴포넌트 클릭
let clicked = false;
const sel = `[data-component-id="${compId}"], #component-${compId}`;
const elCount = await page.locator(sel).count();
if (elCount > 0) {
try {
await page.locator(sel).first().click({ force: true, timeout: 5000 });
await page.waitForTimeout(2000);
clicked = true;
} catch {
try {
await page.locator(sel).first().dispatchEvent("click");
await page.waitForTimeout(2000);
clicked = true;
} catch {}
}
}
if (!clicked) {
// fallback: 캔버스에서 위치 기반 클릭 시도
const comp = components.find((c: any) => c.id === compId);
if (comp?.position) {
const canvasEl = await page.locator('[class*="canvas"], [class*="designer"]').first().boundingBox();
if (canvasEl) {
const x = canvasEl.x + (comp.position.x || 100);
const y = canvasEl.y + (comp.position.y || 100);
await page.mouse.click(x, y);
await page.waitForTimeout(2000);
clicked = true;
}
}
}
if (!clicked) {
console.log(` ${targetType}: ERROR (click fail, id=${compId})`);
allResults.push({ type: targetType, screenId, compId, status: "ERROR", jsErrors: jsErrors.slice(errIdx), panelDetails: "", errorMsg: "Cannot click component" });
continue;
}
const panel = await checkPanel(page);
const newErrors = jsErrors.slice(errIdx);
const critical = newErrors.find((e) =>
e.includes("Cannot read") || e.includes("is not a function") || e.includes("is not defined") || e.includes("Minified React")
);
let status: Result["status"];
let errorMsg: string | undefined;
if (panel.hasError || critical) {
status = "FAIL";
errorMsg = critical || "Panel error";
} else if (!panel.visible) {
status = "FAIL";
errorMsg = `Panel not visible (${panel.detail})`;
} else {
status = "PASS";
}
const icon = { PASS: "OK", FAIL: "FAIL", ERROR: "ERR", NO_COMPONENT: "??" }[status];
console.log(` ${targetType}: ${icon} [${panel.detail}]${errorMsg ? " " + errorMsg.substring(0, 80) : ""}`);
if (status === "FAIL") {
await page.screenshot({ path: path.join(OUT, `fail-${targetType.replace(/\//g, "_")}.png`) }).catch(() => {});
}
allResults.push({ type: targetType, screenId, compId, status, jsErrors: newErrors, panelDetails: panel.detail, errorMsg });
}
}
await browser.close();
fs.writeFileSync(path.join(OUT, "results.json"), JSON.stringify(allResults, null, 2));
console.log("\n\n=== AUDIT SUMMARY ===");
const p = allResults.filter((r) => r.status === "PASS");
const f = allResults.filter((r) => r.status === "FAIL");
const e = allResults.filter((r) => r.status === "ERROR");
const n = allResults.filter((r) => r.status === "NO_COMPONENT");
console.log(`Total: ${allResults.length}`);
console.log(`PASS: ${p.length}`);
console.log(`FAIL: ${f.length}`);
console.log(`ERROR: ${e.length}`);
console.log(`NO_COMPONENT: ${n.length}`);
if (f.length > 0) {
console.log("\n--- FAILED ---");
f.forEach((r) => console.log(` ${r.type} (screen ${r.screenId}): ${r.errorMsg}`));
}
if (e.length > 0) {
console.log("\n--- ERRORS ---");
e.forEach((r) => console.log(` ${r.type} (screen ${r.screenId}): ${r.errorMsg}`));
}
const unique = [...new Set(jsErrors)];
if (unique.length > 0) {
console.log(`\n--- JS Errors (${unique.length} unique) ---`);
unique.slice(0, 15).forEach((err) => console.log(` ${err.substring(0, 150)}`));
}
console.log("\nDone.");
}
main().catch(console.error);
Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB