import { expect, test } from '@playwright/test'; import { Wait } from './helpers/wait'; import { getAdminToken, installAdminToken, navigateInBlazor, waitForAdminSection } 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.'); const token = await getAdminToken(page.request, baseUrl, username, password); await installAdminToken(page, token); }); 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 waitForAdminSection(page, '세무 프로필'); await expect(page.getByRole('button', { name: '새 프로필 추가' })).toBeVisible({ timeout: Wait.long }); await expect(page.locator('.admin-grid, .mud-alert')).toBeVisible({ timeout: Wait.long }); }); 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 waitForAdminSection(page, '신고 일정'); await expect(page.getByRole('button', { name: '새 일정 추가' })).toBeVisible({ timeout: Wait.long }); await expect(page.locator('.admin-grid, .mud-alert')).toBeVisible({ timeout: Wait.long }); }); test('Contracts page loads with MRR display', async ({ page }) => { await navigateInBlazor(page, `${baseUrl}/admin/contracts`); await expect(page).toHaveURL(/\/admin\/contracts$/); await waitForAdminSection(page, '계약 관리'); await expect(page.getByRole('button', { name: '새 계약 추가' })).toBeVisible({ timeout: Wait.long }); await expect(page.locator('.admin-grid, .mud-alert')).toBeVisible({ timeout: Wait.long }); }); test('ConsultingActivities page loads with activity records', async ({ page }) => { await navigateInBlazor(page, `${baseUrl}/admin/consulting-activities`); await expect(page).toHaveURL(/\/admin\/consulting-activities$/); await waitForAdminSection(page, '상담 활동 관리'); await expect(page.getByRole('button', { name: '새 활동 기록' })).toBeVisible({ timeout: Wait.long }); await expect(page.locator('.admin-grid, .mud-alert')).toBeVisible({ timeout: Wait.long }); }); 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 waitForAdminSection(page, '수익 추적 관리'); await expect(page.getByRole('button', { name: '새 청구 추가' })).toBeVisible({ timeout: Wait.long }); await expect(page.locator('.admin-grid, .mud-alert')).toBeVisible({ timeout: Wait.long }); }); test('CRM navigation group is visible and expandable', async ({ page }) => { await navigateInBlazor(page, `${baseUrl}/admin/dashboard`); // 좌측 패널 네비게이션 확인 const crmGroup = page.getByRole('button', { name: 'CRM & 세무관리' }); await expect(crmGroup).toBeVisible({ timeout: Wait.long }); // CRM 그룹의 모든 링크 확인 const expectedLinks = [ '세무 프로필', '신고 일정', '계약 관리', '상담 활동', '수익 추적' ]; for (const linkText of expectedLinks) { const link = page.getByText(linkText, { exact: true }); await expect(link).toBeVisible({ timeout: Wait.long }); } }); test('TaxProfiles editor panel is visible on add button click', async ({ page }) => { await navigateInBlazor(page, `${baseUrl}/admin/tax-profiles`); const addButton = page.getByText('새 프로필 추가'); await expect(addButton).toBeVisible(); await addButton.click(); await expect(page.locator('.admin-editor-panel')).toBeVisible({ timeout: Wait.medium }); }); test('No console errors on CRM page navigation', async ({ page }) => { const consoleErrors: string[] = []; page.on('console', message => { if (message.type() === 'error') { const text = message.text(); if ( text.includes("The value 'get' is not a function") || text.includes('mono_download_assets') || text.includes('Fetch API cannot load') ) { return; } consoleErrors.push(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 expect(page.locator('#blazor-loading')).toBeHidden({ timeout: Wait.medium }); } 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: Wait.long }); const addButton = page.getByText('새 프로필 추가'); await addButton.click(); // 분할 편집기(admin-editor-panel) 노출 대기 await expect(page.locator('.admin-editor-panel')).toBeVisible({ timeout: Wait.medium }); const select = page.locator('.admin-editor-panel .mud-select').filter({ hasText: '사업 유형' }).first(); await expect(select).toBeVisible({ timeout: Wait.medium }); await expect(select).toContainText('사업 유형', { timeout: Wait.medium }); const token = await getAdminToken(page.request, baseUrl, username, password!); const response = await page.request.get(`${baseUrl}/api/commoncode/group/BUSINESS_TYPE`, { headers: { Authorization: `Bearer ${token}` }, }); expect(response.status()).toBe(200); const body = await response.json(); const values = (body?.data ?? []).map((item: { codeValue: string }) => item.codeValue); expect(values).toEqual(expect.arrayContaining(['일반제조업', '도소매업', '서비스업'])); }); 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: Wait.long }); const addButton = page.getByText('새 일정 추가'); await addButton.click(); await expect(page.locator('.admin-editor-panel')).toBeVisible({ timeout: Wait.medium }); const select = page.locator('.admin-editor-panel .mud-select').filter({ hasText: '신고 유형' }).first(); await expect(select).toBeVisible({ timeout: Wait.medium }); await expect(select).toContainText('신고 유형', { timeout: Wait.medium }); const token = await getAdminToken(page.request, baseUrl, username, password!); const response = await page.request.get(`${baseUrl}/api/commoncode/group/FILING_TYPE`, { headers: { Authorization: `Bearer ${token}` }, }); expect(response.status()).toBe(200); const body = await response.json(); const values = (body?.data ?? []).map((item: { codeValue: string }) => item.codeValue); expect(values).toEqual(expect.arrayContaining(['종합소득세', '부가가치세'])); }); 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: Wait.long }); const addButton = page.getByRole('button', { name: '새 계약 추가' }); await addButton.click(); await expect(page.locator('.admin-editor-panel')).toBeVisible({ timeout: 5000 }); const select = page.locator('.admin-editor-panel .mud-select').filter({ hasText: '서비스 유형' }).first(); await expect(select).toBeVisible({ timeout: Wait.medium }); await expect(select).toContainText('서비스 유형', { timeout: Wait.medium }); const token = await getAdminToken(page.request, baseUrl, username, password!); const response = await page.request.get(`${baseUrl}/api/commoncode/group/CONTRACT_SERVICE_TYPE`, { headers: { Authorization: `Bearer ${token}` }, }); expect(response.status()).toBe(200); const body = await response.json(); const values = (body?.data ?? []).map((item: { codeValue: string }) => item.codeValue); expect(values).toEqual(expect.arrayContaining(['개인기장대리', '법인기장대리'])); }); } );