const { test, expect } = require('@playwright/test'); const { login } = require('./helpers/auth'); /** * 전체 점검 스윕 — 주요 화면 콘솔에러·차단 오버레이 점검 + 매뉴얼/도움말/워크스페이스 통합 검증. */ async function selectDaegu(page) { await page.goto('/admin/select-local-government'); await page.evaluate(() => { const r = document.querySelector('input[name="lg_idx"][value="1"]'); if (r) { r.checked = true; r.form.submit(); } }); await page.waitForTimeout(700); } // kakao 외부 SDK 관련(도메인 미등록 환경) 잡음은 제외 function appError(msg) { return !/kakao|dapi\.kakao|sdk\.js|OPEN_MAP_AND_LOCAL|appkey/i.test(msg); } const PAGES = [ '/', '/bag/inventory', '/bag/order/create', '/bag/bag-orders', '/bag/receiving/scanner', '/bag/receiving/batch', '/bag/sale/designated', '/bag/issue/create', '/bag/issue', '/bag/flow', '/bag/sales', '/bag/reports/daily-summary', '/bag/reports/lot-flow', '/bag/reports/returns', '/bag/bag-prices', '/bag/packaging-units', '/bag/code-kinds', '/bag/designated-shops', '/bag/designated-shops/browse', '/bag/number-lookup', '/bag/manual', '/admin', '/admin/menus', '/admin/users', '/admin/access/login-history', ]; test.describe('QA 스윕', () => { test('주요 화면 콘솔 에러·차단 오버레이 점검', async ({ page }) => { await login(page, 'admin'); await selectDaegu(page); const problems = []; for (const url of PAGES) { const errs = []; page.removeAllListeners('pageerror'); page.on('pageerror', (e) => { if (appError(String(e))) errs.push(String(e)); }); const res = await page.goto(url, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(700); const status = res ? res.status() : 0; const cover = await page.evaluate(() => { const cx = Math.floor(innerWidth / 2), cy = Math.floor(innerHeight / 2); let n = 0; document.querySelectorAll('*').forEach((el) => { const s = getComputedStyle(el); if ((s.position === 'fixed' || s.position === 'absolute') && s.display !== 'none' && s.visibility !== 'hidden' && parseFloat(s.opacity || '1') > 0.1 && s.pointerEvents !== 'none') { const r = el.getBoundingClientRect(); if (r.width >= innerWidth * 0.85 && r.height >= innerHeight * 0.7 && !/portal-header|sidebar|ws-/.test(el.className || '')) n++; } }); return n; }); if (status >= 400) problems.push(`${url} → HTTP ${status}`); if (errs.length) problems.push(`${url} → JS오류: ${errs[0]}`); if (cover > 0) problems.push(`${url} → 화면 덮는 오버레이 ${cover}개`); } console.log('>>> SWEEP problems=' + JSON.stringify(problems, null, 0)); expect(problems, problems.join('\n')).toEqual([]); }); test('매뉴얼 전체 페이지 렌더', async ({ page }) => { await login(page, 'user'); const slugs = ['overview', 'flow', 'order', 'inventory', 'sales', 'reports', 'basic', 'codes', 'faq']; for (const s of slugs) { const res = await page.goto('/bag/manual/' + s, { waitUntil: 'domcontentloaded' }); expect(res.status(), s).toBe(200); await expect(page.locator('.manual-prose')).not.toBeEmpty(); } // 미등록 slug → 404 const bad = await page.goto('/bag/manual/zzz-none', { waitUntil: 'domcontentloaded' }); expect(bad.status()).toBe(404); }); test('이 화면 설명 매핑 정확', async ({ page }) => { await login(page, 'admin'); await selectDaegu(page); const cases = [ ['/bag/inventory', 'inventory'], ['/bag/order/create', 'order'], ['/bag/sale/designated', 'sales'], ['/bag/flow', 'reports'], ['/bag/bag-prices', 'basic'], ['/bag/number-lookup', 'codes'], ]; for (const [url, slug] of cases) { await page.goto(url, { waitUntil: 'domcontentloaded' }); const href = await page.locator('a.no-print', { hasText: '이 화면 설명' }).first().getAttribute('href'); expect(href, url).toContain('/bag/manual/' + slug); } }); });