/** * Setup de autenticación para tests E2E de Playwright. * * El flujo de login de Anexo76 pasa por Workspace (workspace.aduanasoft.com): * 1. Navegar a /login → 303 redirect al formulario de Workspace * 2. Llenar el form de Workspace con el usuario de prueba (creado en globalSetup) * 3. Workspace/Keycloak redirige de vuelta a /auth/callback → /dashboard * 4. Guardar storageState para todos los tests dependientes * * El usuario de prueba es creado y eliminado por globalSetup / globalTeardown. * Patrón idéntico al de aduanasoft-hub. */ import { test as setup, expect } from '@playwright/test' import { mkdirSync } from 'node:fs' import path from 'node:path' import { fileURLToPath } from 'node:url' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const authFile = path.join(__dirname, '.auth/user.json') setup('autenticacion', async ({ page }) => { const username = process.env.E2E_TEST_USER ?? '' const password = process.env.E2E_TEST_PASSWORD ?? '' if (!username) throw new Error('E2E_TEST_USER no está configurado') if (!password) throw new Error('E2E_TEST_PASSWORD no está configurado') // ── 1. Navegar a /login — Workspace intercepta y muestra su formulario ── await page.goto('/login') // ── 2. Llenar el formulario de Workspace (mismos selectores que hub/login.spec.ts) ── await page.getByRole('textbox', { name: /usuario o email/i }).fill(username) await page.getByLabel(/contraseña/i).fill(password) await page.getByRole('button', { name: /continuar/i }).click() // ── 3. Si Workspace muestra el app-launcher (usuario con múltiples apps), // elegir anexo76-dev — la app del launcher cuya URL apunta al frontend bajo prueba. // Cuando hay match directo de dominio, Workspace salta el launcher y este paso no ejecuta. try { await page.waitForURL(/\/(app-launcher|dashboard|auth\/callback)/, { timeout: 30_000 }) } catch (e) { // Si seguimos en /login de Workspace, las credenciales fueron rechazadas o el form no aceptó. // Imprimir diagnóstico antes de fallar: URL actual + posibles mensajes de error visibles. const currentUrl = page.url() const errorMessages = await page.locator('.text-destructive, [role="alert"], .error, .invalid-feedback') .allTextContents() .catch(() => [] as string[]) const visibleText = await page.locator('body').innerText().catch(() => '') console.log(`[auth.setup][DIAG] URL: ${currentUrl}`) console.log(`[auth.setup][DIAG] Mensajes de error: ${JSON.stringify(errorMessages)}`) console.log(`[auth.setup][DIAG] Body (primeros 800 chars): ${visibleText.slice(0, 800)}`) throw e } if (/\/app-launcher/.test(page.url())) { // Nuevo botón de Fixed Assets en el launcher de Workspace. // Intentar primero el botón de Fixed Asset y, si no existe (compatibilidad), // caer al botón legacy de anexo76-dev. const fixedAssetButton = page.getByRole('button', { name: /fixed asset|activo fijo/i }) if (await fixedAssetButton.isVisible().catch(() => false)) { await fixedAssetButton.click() } else { await page.getByRole('button', { name: /^anexo76-dev/i }).click() } } // ── 4. Esperar redirect SSO con active_system=fixed_asset y luego /dashboard ── await page.waitForURL(/\/auth\/sso\?.*active_system=fixed_asset/, { timeout: 30_000 }) await page.waitForURL(/\/dashboard/, { timeout: 30_000 }) // ── 5. ExchangeRateGuard abre un dialog modal en cada carga del dashboard // si no existe un tipo de cambio para hoy. El guard espera a que // companyStore.activeCompany?.id esté disponible (API async) antes de // consultar la DB y abrir el dialog. Esperar networkidle para que ese // flujo se complete antes de buscar el dialog. await page.waitForLoadState('networkidle', { timeout: 30_000 }).catch(() => {}) const tcDialog = page.getByRole('dialog', { name: /Nuevo Tipo de Cambio/i }) if (await tcDialog.isVisible({ timeout: 10_000 }).catch(() => false)) { const valueInput = tcDialog.getByRole('spinbutton', { name: /Tipo de Cambio/i }) await tcDialog.getByRole('button', { name: /Consultar DOF/i }).click() // El backend consulta el DOF y llena el campo. Esperar a que tenga un valor. await expect(valueInput).not.toHaveValue('', { timeout: 15_000 }) // "Ok" abre un AlertDialog de confirmación; el TC se crea hasta "Confirmar". await tcDialog.getByRole('button', { name: /^Ok$/ }).click() await page.getByRole('button', { name: /^Confirmar$/ }).click() await tcDialog.waitFor({ state: 'hidden', timeout: 10_000 }) } // ── 6. Guardar estado para los tests dependientes ───────────────────────── mkdirSync(path.dirname(authFile), { recursive: true }) await page.context().storageState({ path: authFile }) })