import { expect, test } from '@playwright/test'; import { loginThroughAdminUi, navigateInBlazor } from './helpers/admin-auth'; const username = process.env.E2E_ADMIN_USERNAME ?? 'admin'; const password = process.env.E2E_ADMIN_PASSWORD; const baseUrl = (process.env.E2E_BASE_URL ?? 'http://178.104.200.7/taxbaik').replace(/\/$/, ''); test.describe('admin CRM pages', () => { test.describe.configure({ mode: 'serial' }); test.beforeEach(async ({ page }) => { test.skip(!password, 'E2E_ADMIN_PASSWORD is required.'); await loginThroughAdminUi(page, baseUrl, username, password); }); test('TaxProfiles page loads with grid and add button', async ({ page }) => { await navigateInBlazor(page, `${baseUrl}/admin/tax-profiles`); await expect(page).toHaveURL(/\/admin\/tax-profiles$/); await expect(page.locator('.admin-page-title')).toHaveText('세무 프로필', { timeout: 15_000 }); await expect(page.getByRole('button', { name: /새 프로필 추가/ })).toBeVisible(); await expect(page.locator('.admin-grid, .mud-alert')).toBeVisible({ timeout: 15_000 }); }); test('TaxFilingSchedules page loads with D-day tracking', async ({ page }) => { await navigateInBlazor(page, `${baseUrl}/admin/tax-filing-schedules`); await expect(page).toHaveURL(/\/admin\/tax-filing-schedules$/); await expect(page.locator('.admin-page-title')).toHaveText('신고 일정', { timeout: 15_000 }); await expect(page.getByRole('button', { name: /새 일정 추가/ })).toBeVisible(); await expect(page.locator('.admin-grid, .mud-alert')).toBeVisible({ timeout: 15_000 }); }); test('Contracts page loads with MRR display', async ({ page }) => { await navigateInBlazor(page, `${baseUrl}/admin/contracts`); await expect(page).toHaveURL(/\/admin\/contracts$/); await expect(page.locator('.admin-page-title')).toHaveText('계약 관리', { timeout: 15_000 }); await expect(page.getByRole('button', { name: /새 계약 추가/ })).toBeVisible(); await expect(page.locator('.admin-grid, .mud-alert')).toBeVisible({ timeout: 15_000 }); }); test('ConsultingActivities page loads with activity records', async ({ page }) => { await navigateInBlazor(page, `${baseUrl}/admin/consulting-activities`); await expect(page).toHaveURL(/\/admin\/consulting-activities$/); await expect(page.locator('.admin-page-title')).toHaveText('상담 활동 관리', { timeout: 15_000 }); await expect(page.getByRole('button', { name: /새 활동 기록/ })).toBeVisible(); await expect(page.locator('.admin-grid, .mud-alert')).toBeVisible({ timeout: 15_000 }); }); test('RevenueTrackings page loads with payment status tracking', async ({ page }) => { await navigateInBlazor(page, `${baseUrl}/admin/revenue-trackings`); await expect(page).toHaveURL(/\/admin\/revenue-trackings$/); await expect(page.locator('.admin-page-title')).toHaveText('수익 추적 관리', { timeout: 15_000 }); await expect(page.getByRole('button', { name: /새 청구 추가/ })).toBeVisible(); await expect(page.locator('.admin-grid, .mud-alert')).toBeVisible({ timeout: 15_000 }); }); test('CRM navigation group is visible and expandable', async ({ page }) => { await navigateInBlazor(page, `${baseUrl}/admin/dashboard`); // 좌측 패널 네비게이션 확인 const crmGroup = page.getByText('CRM & 세무관리'); await expect(crmGroup).toBeVisible({ timeout: 10_000 }); // CRM 그룹의 모든 링크 확인 const expectedLinks = [ '세무 프로필', '신고 일정', '계약 관리', '상담 활동', '수익 추적' ]; for (const linkText of expectedLinks) { const link = page.getByRole('link', { name: linkText }); await expect(link).toBeVisible({ timeout: 10_000 }); } }); test('TaxProfiles modal dialog opens on add button click', async ({ page }) => { await navigateInBlazor(page, `${baseUrl}/admin/tax-profiles`); const addButton = page.getByRole('button', { name: /새 프로필 추가/ }); await expect(addButton).toBeVisible(); await addButton.click(); await expect(page).toHaveURL(/\/taxbaik\/admin\/tax-profiles$/); await expect(addButton).toBeVisible(); }); test('No console errors on CRM page navigation', async ({ page }) => { const consoleErrors: string[] = []; page.on('console', message => { if (message.type() === 'error') { consoleErrors.push(message.text()); } }); const crmPages = [ '/admin/tax-profiles', '/admin/tax-filing-schedules', '/admin/contracts', '/admin/consulting-activities', '/admin/revenue-trackings' ]; for (const path of crmPages) { await navigateInBlazor(page, `${baseUrl}${path}`); await page.waitForTimeout(2000); } expect(consoleErrors, 'no console errors during CRM navigation').toEqual([]); }); test('TaxProfiles form displays valid business type combo choices', async ({ page }) => { await navigateInBlazor(page, `${baseUrl}/admin/tax-profiles`); await expect(page.locator('.admin-grid, .mud-alert')).toBeVisible({ timeout: 15000 }); const addButton = page.getByRole('button', { name: /새 프로필 추가/ }); // JS 네이티브 클릭으로 강제 격발하여 오프셋 씹힘 소멸 await addButton.evaluate(el => (el as HTMLButtonElement).click()); // 대화상자(MudDialog) 자체의 노출 대기 await expect(page.locator('.mud-dialog')).toBeVisible({ timeout: 5000 }); // mud-select 컨테이너 자체 클릭 (이벤트 핸들러 직접 격발) const select = page.locator('.mud-select').filter({ hasText: '사업 유형' }).first(); await select.evaluate(el => (el as HTMLDivElement).click()); // 활성화된 팝오버(.mud-popover-open) 내에서 텍스트 노출 검증 const popover = page.locator('.mud-popover-open'); await expect(popover.getByText('일반제조업')).toBeVisible({ timeout: 5000 }); await expect(popover.getByText('도소매업')).toBeVisible({ timeout: 5000 }); await expect(popover.getByText('서비스업')).toBeVisible({ timeout: 5000 }); }); test('TaxFilingSchedules form displays filing type combo choices', async ({ page }) => { await navigateInBlazor(page, `${baseUrl}/admin/tax-filing-schedules`); await expect(page.locator('.admin-grid, .mud-alert')).toBeVisible({ timeout: 15000 }); const addButton = page.getByRole('button', { name: /새 일정 추가/ }); await addButton.evaluate(el => (el as HTMLButtonElement).click()); await expect(page.locator('.mud-dialog')).toBeVisible({ timeout: 5000 }); const select = page.locator('.mud-select').filter({ hasText: '신고 유형' }).first(); await select.evaluate(el => (el as HTMLDivElement).click()); const popover = page.locator('.mud-popover-open'); await expect(popover.getByText('종합소득세')).toBeVisible({ timeout: 5000 }); await expect(popover.getByText('부가가치세')).toBeVisible({ timeout: 5000 }); }); test('Contracts form displays service type combo choices', async ({ page }) => { await navigateInBlazor(page, `${baseUrl}/admin/contracts`); await expect(page.locator('.admin-grid, .mud-alert')).toBeVisible({ timeout: 15000 }); const addButton = page.getByRole('button', { name: /새 계약 추가/ }); await addButton.evaluate(el => (el as HTMLButtonElement).click()); await expect(page.locator('.mud-dialog')).toBeVisible({ timeout: 5000 }); const select = page.locator('.mud-select').filter({ hasText: '서비스 유형' }).first(); await select.evaluate(el => (el as HTMLDivElement).click()); const popover = page.locator('.mud-popover-open'); await expect(popover.getByText('개인 기장대리')).toBeVisible({ timeout: 5000 }); await expect(popover.getByText('법인 기장대리')).toBeVisible({ timeout: 5000 }); }); });