const { chromium } = require('playwright'); const path = require('path'); const fs = require('fs'); const BASE = 'http://localhost:3000'; const OUT = path.join(__dirname, 'frontend/public/screenshots'); if (!fs.existsSync(OUT)) fs.mkdirSync(OUT, { recursive: true }); const PAGES = [ { name: '01_home', url: '/', label: '홈' }, { name: '02_guardia', url: '/solution/guardia', label: 'GUARDiA 소개' }, { name: '03_company', url: '/company/greeting', label: '회사소개' }, { name: '04_contact', url: '/support/contact', label: '문의하기' }, { name: '05_news', url: '/news/press', label: '뉴스' }, ]; const RESULTS = []; (async () => { const browser = await chromium.launch({ headless: true }); const context = await browser.newContext({ viewport: { width: 1440, height: 900 }, locale: 'ko-KR', }); for (const pg of PAGES) { const page = await context.newPage(); const errors = []; page.on('console', msg => { if (msg.type() === 'error') errors.push(msg.text()); }); page.on('pageerror', err => errors.push(err.message)); const t0 = Date.now(); let status = 'OK'; try { const resp = await page.goto(BASE + pg.url, { waitUntil: 'networkidle', timeout: 15000 }); const loadTime = Date.now() - t0; // 메인 컨텐츠 대기 await page.waitForSelector('#root > *', { timeout: 5000 }).catch(() => {}); // 풀페이지 스크린샷 const ssPath = path.join(OUT, pg.name + '.png'); await page.screenshot({ path: ssPath, fullPage: true }); // 뷰포트 스크린샷 (Above-the-fold) const vpPath = path.join(OUT, pg.name + '_viewport.png'); await page.screenshot({ path: vpPath, fullPage: false }); // 타이틀, 링크 수 확인 const title = await page.title(); const links = await page.$$eval('a[href]', els => els.length); const images = await page.$$eval('img', els => els.length); const h1Count = await page.$$eval('h1', els => els.length); RESULTS.push({ page: pg.label, url: pg.url, status: resp ? resp.status() : 'err', loadMs: loadTime, title, links, images, h1: h1Count, errors: errors.length, errorMsgs: errors.slice(0, 3), screenshot: ssPath, }); console.log(`[OK] ${pg.label} (${loadTime}ms) — title: "${title}" links:${links} imgs:${images}`); } catch (e) { status = 'FAIL'; RESULTS.push({ page: pg.label, url: pg.url, status: 'FAIL', error: e.message }); console.error(`[FAIL] ${pg.label}: ${e.message}`); } await page.close(); } // 모바일 홈 스크린샷 const mobilePage = await context.newPage(); await mobilePage.setViewportSize({ width: 390, height: 844 }); await mobilePage.goto(BASE + '/', { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {}); await mobilePage.screenshot({ path: path.join(OUT, '06_mobile_home.png'), fullPage: false }); await mobilePage.close(); console.log('[OK] 모바일 홈 스크린샷 완료'); await browser.close(); // 결과 출력 console.log('\n=== 테스트 결과 요약 ==='); const passed = RESULTS.filter(r => r.status !== 'FAIL').length; console.log(`통과: ${passed}/${RESULTS.length}`); RESULTS.forEach(r => { if (r.status === 'FAIL') { console.log(` FAIL ${r.page}: ${r.error}`); } else { const warn = r.errors > 0 ? ` [콘솔오류 ${r.errors}개]` : ''; console.log(` PASS ${r.page} HTTP${r.status} ${r.loadMs}ms${warn}`); } }); // JSON 저장 fs.writeFileSync(path.join(OUT, 'test-result.json'), JSON.stringify(RESULTS, null, 2)); console.log(`\n스크린샷 저장 위치: ${OUT}`); process.exit(passed === RESULTS.length ? 0 : 1); })();