ERP-node/test-output/config-panel-audit/audit-config-panels.ts

390 lines
13 KiB
TypeScript

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);