import { expect, test } from '@playwright/test'; import { loginThroughAdminUi } from './helpers/admin-auth'; // 테스트 계정 (실 admin 계정과 분리) const TEST_USERNAME = process.env.E2E_ADMIN_USERNAME || 'test_admin'; const TEST_PASSWORD = process.env.E2E_ADMIN_PASSWORD || 'TestAdmin@123456'; const baseUrl = (process.env.E2E_BASE_URL ?? 'http://178.104.200.7/taxbaik').replace(/\/$/, ''); test.describe('admin responsive design (test_admin account)', () => { const deviceTests = [ { name: 'Desktop (1920px)', viewport: { width: 1920, height: 1080 }, minElements: 4 }, { name: 'Desktop (1440px)', viewport: { width: 1440, height: 900 }, minElements: 4 }, { name: 'Laptop (1024px)', viewport: { width: 1024, height: 768 }, minElements: 4 }, { name: 'Tablet L (960px)', viewport: { width: 960, height: 600 }, minElements: 3 }, { name: 'Tablet M (768px)', viewport: { width: 768, height: 1024 }, minElements: 2 }, { name: 'Tablet S (600px)', viewport: { width: 600, height: 800 }, minElements: 1 }, { name: 'Mobile L (480px)', viewport: { width: 480, height: 853 }, minElements: 1 }, { name: 'Mobile S (375px)', viewport: { width: 375, height: 667 }, minElements: 1 } ]; test('dashboard loads correctly on all viewports', async ({ page }) => { // 브라우저 로그 출력 설정 page.on('console', msg => console.log(`[Browser Console] [${msg.type()}] ${msg.text()}`)); page.on('pageerror', err => console.log(`[Browser Exception] ${err.message}`)); // 1. UI 로그인 1회 수행 (서버 측 인증 쿠키 획득을 위해 필수) await loginThroughAdminUi(page, baseUrl, TEST_USERNAME, TEST_PASSWORD); // 2. 대시보드로 이동 및 최초 로드 대기 await page.goto(`${baseUrl}/admin/dashboard`); await expect(page.locator('.admin-page-hero')).toBeVisible({ timeout: 30_000 }); // 3. 각 디바이스별로 뷰포트를 변경하며 검증 (새로고침 없이 초고속 검증) for (const device of deviceTests) { console.log(`Running responsive check on: ${device.name}`); await page.setViewportSize(device.viewport); await page.waitForTimeout(150); // 레이아웃 렌더링 안정화 대기 // 대시보드 요소 확인 await expect(page.locator('.admin-page-hero')).toBeVisible(); await expect(page.locator('.admin-page-title')).toContainText(/대시보드|Dashboard/i); // 메트릭 카드 존재 확인 const metricCards = page.locator('.admin-metric-card'); const count = await metricCards.count(); expect(count, `Expected at least 1 metric card on ${device.name}`).toBeGreaterThanOrEqual(1); // 메트릭 카드 가시성 확인 for (let i = 0; i < Math.min(count, device.minElements); i++) { const card = metricCards.nth(i); await expect(card).toBeInViewport(); } // 테이블 체크 const tables = page.locator('.admin-table'); if (await tables.count() > 0) { for (let i = 0; i < await tables.count(); i++) { const table = tables.nth(i); const headerCells = table.locator('thead th'); expect(await headerCells.count()).toBeGreaterThan(0); } } // 오버플로우 체크 const bodyBounds = await page.evaluate(() => { const docWidth = document.documentElement.scrollWidth; const winWidth = window.innerWidth; return docWidth <= winWidth + 1; }); expect(bodyBounds, `No horizontal scroll on ${device.name}`).toBe(true); // 텍스트 가독성 const textElements = page.locator('p, span, .mud-typography'); for (let i = 0; i < Math.min(5, await textElements.count()); i++) { const fontSize = await textElements.nth(i).evaluate((el) => { return window.getComputedStyle(el).fontSize; }); const size = parseFloat(fontSize); expect(size).toBeGreaterThanOrEqual(9.5); } } }); // 드로어 반응형 테스트 test('drawer responsiveness on mobile', async ({ page }) => { // UI 로그인 1회 수행 await loginThroughAdminUi(page, baseUrl, TEST_USERNAME, TEST_PASSWORD); await page.setViewportSize({ width: 375, height: 667 }); await page.goto(`${baseUrl}/admin/dashboard`); await expect(page.locator('.admin-page-hero')).toBeVisible({ timeout: 30_000 }); // 모바일에서는 메뉴 토글 버튼이 있어야 함 const menuButton = page.locator('.admin-menu-button'); await expect(menuButton).toBeVisible(); // 토글 버튼 클릭 후 drawer 제어 가능 여부 확인 await menuButton.click(); expect(await menuButton.isVisible()).toBe(true); }); // 폼 요소 반응형 테스트 test('form inputs are accessible on mobile', async ({ page }) => { // UI 로그인 1회 수행 await loginThroughAdminUi(page, baseUrl, TEST_USERNAME, TEST_PASSWORD); await page.setViewportSize({ width: 480, height: 853 }); await page.goto(`${baseUrl}/admin/faqs/create`); await page.waitForSelector('input[type="text"], textarea, .mud-input-base, .mud-field', { timeout: 30_000 }); const inputs = page.locator('input[type="text"], textarea, .mud-input-base, .mud-field'); const inputCount = await inputs.count(); if (inputCount > 0) { const width = await inputs.first().evaluate((el) => { return el.getBoundingClientRect().width; }); expect(width).toBeGreaterThan(200); await inputs.first().scrollIntoViewIfNeeded(); await expect(inputs.first()).toBeInViewport(); } }); // 버튼 접근성 테스트 test('buttons are clickable on all viewports', async ({ page }) => { // UI 로그인 1회 수행 await loginThroughAdminUi(page, baseUrl, TEST_USERNAME, TEST_PASSWORD); const viewports = [ { width: 1920, height: 1080 }, { width: 768, height: 1024 }, { width: 375, height: 667 } ]; for (const viewport of viewports) { await page.setViewportSize(viewport); await page.goto(`${baseUrl}/admin/dashboard`); await expect(page.locator('.admin-page-hero')).toBeVisible({ timeout: 30_000 }); const logoutButton = page.locator('a:has-text("로그아웃"), button:has-text("로그아웃")').first(); if (await logoutButton.count() > 0) { const box = await logoutButton.boundingBox(); expect(box).not.toBeNull(); expect(box?.width).toBeGreaterThan(20); expect(box?.height).toBeGreaterThan(20); } } }); });