import { expect, type APIRequestContext, type Page } from '@playwright/test'; export type InquiryListItem = { id: number; name: string; phone: string; message: string; }; export async function getAdminToken( request: APIRequestContext, baseUrl: string, username: string, password: string, ) { const response = await request.post(`${baseUrl}/api/auth/login`, { data: { username, password }, }); expect(response.status(), 'login API should accept the configured admin credentials').toBe(200); const body = await response.json(); expect(body?.token, 'login API should return a token').toBeTruthy(); return body.token as string; } export async function installAdminToken(page: Page, token: string) { await page.addInitScript(value => { localStorage.setItem('accessToken', value); localStorage.setItem('refreshToken', 'ci-test-refresh-token'); // Calculate C# Ticks for 1 hour from now: (JS_ms * 10000) + 621355968000000000 const expiryMs = Date.now() + 3600 * 1000; const ticks = (expiryMs * 10000) + 621355968000000000; localStorage.setItem('tokenExpiry', ticks.toString()); }, token); } export async function loginThroughAdminUi( page: Page, baseUrl: string, username: string, password: string, ) { await page.goto(`${baseUrl}/admin/login`); await page.locator('input[placeholder="사용자명"]').fill(username); await page.locator('input[placeholder="비밀번호"]').fill(password); await page.getByRole('button', { name: '로그인' }).click(); await expect(page).toHaveURL(/\/taxbaik\/admin\/dashboard$/); await expect(page.getByRole('heading', { name: '대시보드' }).first()).toBeVisible({ timeout: 20_000 }); } export async function navigateInBlazor(page: Page, targetUrl: string) { await page.evaluate(url => { const blazor = (window as typeof window & { Blazor?: { navigateTo: (target: string) => void } }).Blazor; if (blazor) { blazor.navigateTo(url); return; } 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(() => {}); // Give the SPA router a brief window to unmount the previous page and mount the loading spinner await page.waitForTimeout(500); // 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( request: APIRequestContext, baseUrl: string, token: string, name: string, ) { const response = await request.get(`${baseUrl}/api/inquiry?page=1&pageSize=100`, { headers: { Authorization: `Bearer ${token}` }, }); expect(response.status(), 'admin inquiry list API should be accessible with the token').toBe(200); const body = await response.json(); const items = (body?.data ?? []) as InquiryListItem[]; const inquiry = items.find(item => item.name === name); expect(inquiry, `created inquiry ${name} should appear in the admin inquiry API`).toBeTruthy(); return inquiry!; }