diff --git a/Jenkinsfile b/Jenkinsfile index 7dc41b47..65995822 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -169,21 +169,18 @@ pipeline { stage('E2E (Playwright)') { steps { - // Patrón idéntico al de aduanasoft-hub: - // globalSetup crea usuario de prueba en KC vía Admin API - // auth.setup.ts navega a /login → Workspace → llena form → /dashboard - // globalTeardown elimina el usuario al finalizar + // auth.setup.ts navega a /login → Workspace → llena form → /dashboard + // y guarda storageState para los tests dependientes. // // Credenciales requeridas en Jenkins: // a76-public-url-dev : URL pública base de Anexo76 - // a76-e2e-credentials : Username with password — admin de Keycloak - // (para que globalSetup pueda crear/eliminar usuarios) + // a76-e2e-credentials : Username with password — usuario de prueba en Workspace withCredentials([ string(credentialsId: 'a76-public-url-dev', variable: 'A76_URL'), usernamePassword( credentialsId: 'a76-e2e-credentials', - usernameVariable: 'E2E_KC_ADMIN_USER', - passwordVariable: 'E2E_KC_ADMIN_PASS' + usernameVariable: 'E2E_USER', + passwordVariable: 'E2E_PASS' ) ]) { sh ''' @@ -212,11 +209,8 @@ pipeline { -e KEYCLOAK_CLIENT_ID=hub-frontend \ -e ORIGIN=http://localhost:5173 \ -e "KC_URL=${KC_URL}" \ - -e "E2E_KC_ADMIN_USER=${E2E_KC_ADMIN_USER}" \ - -e "E2E_KC_ADMIN_PASSWORD=${E2E_KC_ADMIN_PASS}" \ - -e E2E_TEST_USER=e2e-anexo76 \ - -e E2E_TEST_PASSWORD=E2eAnexo76Test! \ - -e E2E_TEST_EMAIL=e2e-anexo76@test.local \ + -e "E2E_TEST_USER=${E2E_USER}" \ + -e "E2E_TEST_PASSWORD=${E2E_PASS}" \ -w /workspace/frontend \ "$C" bash -lc ' set -euxo pipefail diff --git a/frontend/e2e/fixtures/keycloak.ts b/frontend/e2e/fixtures/keycloak.ts deleted file mode 100644 index 8f465f51..00000000 --- a/frontend/e2e/fixtures/keycloak.ts +++ /dev/null @@ -1,108 +0,0 @@ -/** - * Helpers para gestionar usuarios de prueba en Keycloak vía Admin REST API. - * Se usan en globalSetup / globalTeardown de tests E2E. - * Patrón idéntico al de aduanasoft-hub. - */ - -export interface KeycloakAdminConfig { - url: string; // ej. https://workspace.aduanasoft.com/kcauth - realm: string; // ej. master - adminUser: string; - adminPassword: string; -} - -async function getAdminToken(cfg: KeycloakAdminConfig): Promise { - const url = `${cfg.url}/realms/master/protocol/openid-connect/token`; - const res = await fetch(url, { - method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: new URLSearchParams({ - grant_type: 'password', - client_id: 'admin-cli', - username: cfg.adminUser, - password: cfg.adminPassword - }) - }); - if (!res.ok) throw new Error(`Keycloak admin token failed: ${res.status} ${await res.text()}`); - const data = await res.json(); - return data.access_token; -} - -export async function createTestUser( - cfg: KeycloakAdminConfig, - user: { username: string; password: string; email: string } -): Promise { - const token = await getAdminToken(cfg); - const res = await fetch(`${cfg.url}/admin/realms/${cfg.realm}/users`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}` - }, - body: JSON.stringify({ - username: user.username, - email: user.email, - enabled: true, - emailVerified: true, - credentials: [{ type: 'password', value: user.password, temporary: false }] - }) - }); - // 409 = ya existe — idempotente - if (!res.ok && res.status !== 409) { - throw new Error(`createTestUser failed: ${res.status} ${await res.text()}`); - } - const searchRes = await fetch( - `${cfg.url}/admin/realms/${cfg.realm}/users?username=${encodeURIComponent(user.username)}&exact=true`, - { headers: { Authorization: `Bearer ${token}` } } - ); - const users = await searchRes.json(); - if (!users.length) throw new Error(`Usuario ${user.username} no encontrado tras creación`); - return users[0].id; -} - -export async function assignRoles( - cfg: KeycloakAdminConfig, - userId: string, - roleNames: string[] -): Promise { - const token = await getAdminToken(cfg); - - // Resolver los IDs de los roles por nombre - const roles: { id: string; name: string }[] = []; - for (const name of roleNames) { - const res = await fetch( - `${cfg.url}/admin/realms/${cfg.realm}/roles/${encodeURIComponent(name)}`, - { headers: { Authorization: `Bearer ${token}` } } - ); - if (!res.ok) throw new Error(`Rol '${name}' no encontrado: ${res.status}`); - const role = await res.json(); - roles.push({ id: role.id, name: role.name }); - } - - const assignRes = await fetch( - `${cfg.url}/admin/realms/${cfg.realm}/users/${userId}/role-mappings/realm`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}` - }, - body: JSON.stringify(roles) - } - ); - if (!assignRes.ok) { - throw new Error(`assignRoles failed: ${assignRes.status} ${await assignRes.text()}`); - } -} - -export async function deleteTestUser(cfg: KeycloakAdminConfig, userId: string): Promise { - const token = await getAdminToken(cfg); - const res = await fetch(`${cfg.url}/admin/realms/${cfg.realm}/users/${userId}`, { - method: 'DELETE', - headers: { Authorization: `Bearer ${token}` } - }); - // 404 = ya fue borrado — se ignora - if (!res.ok && res.status !== 404) { - throw new Error(`deleteTestUser failed: ${res.status} ${await res.text()}`); - } -} diff --git a/frontend/e2e/global-setup.ts b/frontend/e2e/global-setup.ts deleted file mode 100644 index 428d56e8..00000000 --- a/frontend/e2e/global-setup.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Playwright globalSetup — se ejecuta UNA vez antes de todos los tests E2E. - * - * Crea el usuario de prueba en Keycloak desde cero vía Admin REST API. - * No depende de que el usuario exista previamente — funciona con KC vacío - * siempre que el realm y los clientes estén configurados. - * - * Patrón idéntico al de aduanasoft-hub. - */ - -import { writeFileSync } from 'fs'; -import { join, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { createTestUser, assignRoles } from './fixtures/keycloak.js'; - -const __dirname = dirname(fileURLToPath(import.meta.url)); - -const KC_CFG = { - url: process.env.KC_URL ?? 'https://workspace.aduanasoft.com/kcauth', - realm: process.env.KEYCLOAK_REALM ?? 'master', - adminUser: process.env.E2E_KC_ADMIN_USER ?? 'admin', - adminPassword: process.env.E2E_KC_ADMIN_PASSWORD ?? '' -}; - -const TEST_USER = { - username: process.env.E2E_TEST_USER ?? 'e2e-anexo76', - password: process.env.E2E_TEST_PASSWORD ?? '', - email: process.env.E2E_TEST_EMAIL ?? 'e2e-anexo76@test.local' -}; - -export default async function globalSetup() { - console.log('\n[E2E setup] Creando usuario de prueba en Keycloak...'); - - if (!KC_CFG.adminPassword) { - throw new Error('E2E_KC_ADMIN_PASSWORD no está configurado'); - } - if (!TEST_USER.password) { - throw new Error('E2E_TEST_PASSWORD no está configurado'); - } - - const userId = await createTestUser(KC_CFG, TEST_USER); - // Roles de Keycloak necesarios para acceder a Anexo76 - await assignRoles(KC_CFG, userId, ['admin']); - - // Guardar el ID para que globalTeardown pueda borrar el usuario - const statePath = join(__dirname, '..', '.e2e-state.json'); - writeFileSync(statePath, JSON.stringify({ userId, username: TEST_USER.username })); - - console.log(`[E2E setup] Usuario '${TEST_USER.username}' listo (id: ${userId})`); -} diff --git a/frontend/e2e/global-teardown.ts b/frontend/e2e/global-teardown.ts deleted file mode 100644 index cf024db7..00000000 --- a/frontend/e2e/global-teardown.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Playwright globalTeardown — se ejecuta UNA vez después de todos los tests E2E. - * Elimina el usuario de prueba creado en globalSetup. - * - * Patrón idéntico al de aduanasoft-hub. - */ - -import { readFileSync, unlinkSync, existsSync } from 'fs'; -import { join, dirname } from 'path'; -import { fileURLToPath } from 'url'; -import { deleteTestUser } from './fixtures/keycloak.js'; - -const __dirname = dirname(fileURLToPath(import.meta.url)); - -const KC_CFG = { - url: process.env.KC_URL ?? 'https://workspace.aduanasoft.com/kcauth', - realm: process.env.KEYCLOAK_REALM ?? 'master', - adminUser: process.env.E2E_KC_ADMIN_USER ?? 'admin', - adminPassword: process.env.E2E_KC_ADMIN_PASSWORD ?? '' -}; - -export default async function globalTeardown() { - const statePath = join(__dirname, '..', '.e2e-state.json'); - - if (!existsSync(statePath)) { - console.log('[E2E teardown] Sin estado guardado — nada que limpiar'); - return; - } - - const { userId, username } = JSON.parse(readFileSync(statePath, 'utf8')); - console.log(`\n[E2E teardown] Eliminando usuario '${username}' de Keycloak...`); - - await deleteTestUser(KC_CFG, userId); - unlinkSync(statePath); - - console.log(`[E2E teardown] Usuario '${username}' eliminado`); -} diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts index c0d841f5..22d6b4a5 100644 --- a/frontend/playwright.config.ts +++ b/frontend/playwright.config.ts @@ -9,17 +9,11 @@ const authStatePath = path.join(__dirname, 'e2e/.auth/user.json'); const inCI = process.env.CI === 'true' || Boolean(process.env.JENKINS_URL); export default defineConfig({ - // Crea usuario de prueba en KC antes de los tests; lo elimina al finalizar - // Patrón idéntico al de aduanasoft-hub - globalSetup: './e2e/global-setup.ts', - globalTeardown: './e2e/global-teardown.ts', - // En CI, falla si queda un .only; en local no forbidOnly: inCI, timeout: 60_000, use: { baseURL: process.env.PLAYWRIGHT_TEST_BASE_URL || 'http://localhost:5173', - // Sin display en agentes de integración: obligatorio headless headless: inCI ? true : false }, workers: 1, @@ -28,7 +22,6 @@ export default defineConfig({ { name: 'setup', testMatch: '**/auth.setup.ts', - // Navegador limpio: user.json se genera aquí y no debe existir al primer run / en CI use: { storageState: { cookies: [], origins: [] } } }, {