diff --git a/tests/e2e/admin-crm-pages.spec.ts b/tests/e2e/admin-crm-pages.spec.ts index 97b5311..b1e52b9 100644 --- a/tests/e2e/admin-crm-pages.spec.ts +++ b/tests/e2e/admin-crm-pages.spec.ts @@ -6,6 +6,8 @@ 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); @@ -124,11 +126,19 @@ test.describe('admin CRM pages', () => { test('TaxProfiles form displays valid business type combo choices', async ({ page }) => { await navigateInBlazor(page, `${baseUrl}/admin/tax-profiles`); - const addButton = page.getByRole('button', { name: /새 프로필 추가/ }); - await addButton.click(); + await expect(page.locator('.admin-grid, .mud-alert')).toBeVisible({ timeout: 15000 }); - // Label을 매개로 인풋 영역 클릭 - await page.getByLabel('사업 유형').first().click(); + 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'); @@ -139,10 +149,15 @@ test.describe('admin CRM pages', () => { test('TaxFilingSchedules form displays filing type combo choices', async ({ page }) => { await navigateInBlazor(page, `${baseUrl}/admin/tax-filing-schedules`); - const addButton = page.getByRole('button', { name: /새 일정 추가/ }); - await addButton.click(); + await expect(page.locator('.admin-grid, .mud-alert')).toBeVisible({ timeout: 15000 }); - await page.getByLabel('신고 유형').first().click(); + 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 }); @@ -151,10 +166,15 @@ test.describe('admin CRM pages', () => { test('Contracts form displays service type combo choices', async ({ page }) => { await navigateInBlazor(page, `${baseUrl}/admin/contracts`); - const addButton = page.getByRole('button', { name: /새 계약 추가/ }); - await addButton.click(); + await expect(page.locator('.admin-grid, .mud-alert')).toBeVisible({ timeout: 15000 }); - await page.getByLabel('서비스 유형').first().click(); + 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 }); diff --git a/tests/e2e/helpers/admin-auth.ts b/tests/e2e/helpers/admin-auth.ts index 385c4ec..7dd29b4 100644 --- a/tests/e2e/helpers/admin-auth.ts +++ b/tests/e2e/helpers/admin-auth.ts @@ -58,6 +58,19 @@ export async function navigateInBlazor(page: Page, targetUrl: string) { window.location.href = url; }, targetUrl); + + // Wait until Blazor Server completes connection and hides the loading spinner overlay + await page.locator('#blazor-loading').waitFor({ state: 'hidden', timeout: 15000 }).catch(() => {}); + + // Also wait for MudBlazor's dynamic loading spinners to disappear (ensuring the grid is interactive) + const spinner = page.locator('.mud-progress-circular, .mud-progress-linear-bar'); + try { + if (await spinner.count() > 0) { + await spinner.first().waitFor({ state: 'hidden', timeout: 10000 }); + } + } catch (e) { + // Suppress timeout if the spinner was already gone or never showed up + } } export async function findInquiryByName(