feat(e2e): replicar patrón de autenticación de aduanasoft-hub
- fixtures/keycloak.ts: Admin REST API helpers (createTestUser, assignRoles, deleteTestUser) — copia exacta del hub - global-setup.ts: crea usuario e2e-anexo76 en KC antes de los tests - global-teardown.ts: elimina el usuario al finalizar - auth.setup.ts: navega a /login → Workspace → llena form → /dashboard (ya no usa password grant ni Direct Access Grants) - playwright.config.ts: agrega globalSetup y globalTeardown - Jenkinsfile: a76-e2e-credentials ahora son credenciales de KC admin; usuario de prueba es efímero (creado/borrado por global-setup/teardown) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
51
Jenkinsfile
vendored
51
Jenkinsfile
vendored
@@ -169,17 +169,21 @@ pipeline {
|
||||
|
||||
stage('E2E (Playwright)') {
|
||||
steps {
|
||||
// auth.setup.ts obtiene tokens directamente de Keycloak (password grant)
|
||||
// sin pasar por el browser ni por Workspace — /login hace 303 inmediato.
|
||||
// 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
|
||||
//
|
||||
// Credenciales requeridas en Jenkins:
|
||||
// a76-public-url-dev : URL pública base — la URL de KC se deriva de ella ({url}/kcauth)
|
||||
// a76-e2e-credentials : Username with password — cuenta de Workspace para pruebas E2E
|
||||
// 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)
|
||||
withCredentials([
|
||||
string(credentialsId: 'a76-public-url-dev', variable: 'A76_URL'),
|
||||
usernamePassword(
|
||||
credentialsId: 'a76-e2e-credentials',
|
||||
usernameVariable: 'E2E_USER',
|
||||
passwordVariable: 'E2E_PASS'
|
||||
usernameVariable: 'E2E_KC_ADMIN_USER',
|
||||
passwordVariable: 'E2E_KC_ADMIN_PASS'
|
||||
)
|
||||
]) {
|
||||
sh '''
|
||||
@@ -200,17 +204,19 @@ pipeline {
|
||||
-e PLAYWRIGHT_TEST_BASE_URL=http://localhost:5173 \
|
||||
-e "VITE_API_URL=${A76_URL}/api/" \
|
||||
-e "INTERNAL_API_URL=${A76_URL}/api/" \
|
||||
-e "VITE_KEYCLOAK_URL=${A76_URL}/kcauth" \
|
||||
-e "KEYCLOAK_URL=${A76_URL}/kcauth" \
|
||||
-e "VITE_KEYCLOAK_URL=${KC_URL}" \
|
||||
-e "KEYCLOAK_URL=${KC_URL}" \
|
||||
-e VITE_KEYCLOAK_REALM=master \
|
||||
-e KEYCLOAK_REALM=master \
|
||||
-e VITE_KEYCLOAK_CLIENT_ID=anexo76-frontend \
|
||||
-e KEYCLOAK_CLIENT_ID=anexo76-frontend \
|
||||
-e ORIGIN=http://localhost:5173 \
|
||||
-e "KC_URL=${KC_URL}" \
|
||||
-e "E2E_KEYCLOAK_URL=${KC_URL}" \
|
||||
-e "E2E_TEST_USER=${E2E_USER}" \
|
||||
-e "E2E_TEST_PASSWORD=${E2E_PASS}" \
|
||||
-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 \
|
||||
-w /workspace/frontend \
|
||||
"$C" bash -lc '
|
||||
set -euxo pipefail
|
||||
@@ -218,29 +224,6 @@ pipeline {
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm run i18n:compile
|
||||
|
||||
# Verificar que Keycloak acepta el password grant
|
||||
# (auth.setup.ts lo usa directamente — sin pasar por /login)
|
||||
echo "--- Verificando Keycloak token endpoint ---"
|
||||
KC_TOKEN_EP="${KC_URL}/realms/master/protocol/openid-connect/token"
|
||||
# -s sin -f: curl no falla en HTTP 4xx/5xx, devuelve el código real
|
||||
# || echo "000" solo se activa si curl no puede conectar (timeout, DNS, etc.)
|
||||
KC_STATUS=$(curl -s --max-time 10 -o /dev/null -w "%{http_code}" \
|
||||
-X POST "$KC_TOKEN_EP" \
|
||||
-d "grant_type=password&client_id=anexo76-frontend&username=${E2E_TEST_USER}&password=${E2E_TEST_PASSWORD}&scope=openid" \
|
||||
2>/dev/null || echo "000")
|
||||
echo "Keycloak token endpoint → HTTP ${KC_STATUS}"
|
||||
if [ "$KC_STATUS" = "401" ]; then
|
||||
echo "ERROR 401 — Credenciales inválidas o Direct Access Grants deshabilitado."
|
||||
echo " Verifica en Keycloak: Clients → anexo76-frontend → Settings → Direct access grants = ON"
|
||||
echo " Verifica que el usuario '${E2E_TEST_USER}' exista en el realm y la contraseña sea correcta."
|
||||
exit 1
|
||||
elif [ "$KC_STATUS" != "200" ]; then
|
||||
echo "ERROR: Keycloak no accesible (HTTP ${KC_STATUS})."
|
||||
echo "URL: ${KC_TOKEN_EP}"
|
||||
exit 1
|
||||
fi
|
||||
echo "Keycloak OK"
|
||||
|
||||
# Arrancar dev server
|
||||
pnpm run dev &
|
||||
DEV_PID=$!
|
||||
|
||||
@@ -1,124 +1,42 @@
|
||||
/**
|
||||
* Setup de autenticación para tests E2E de Playwright.
|
||||
*
|
||||
* El flujo de login de Anexo76 pasa por Workspace externo (workspace.aduanasoft.com)
|
||||
* y Keycloak SSO — el browser nunca renderiza un form propio. Por eso NO se puede
|
||||
* automatizar navegando a /login: ese route hace 303 inmediato al Workspace.
|
||||
* 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
|
||||
*
|
||||
* Estrategia: obtener tokens directamente del token endpoint de Keycloak
|
||||
* (Resource Owner Password Credentials grant) e inyectarlos como cookies en el
|
||||
* contexto de Playwright, replicando exactamente lo que hace /auth/callback.
|
||||
*
|
||||
* Requiere en Keycloak: cliente con "Direct Access Grants" habilitado.
|
||||
* Requiere en Jenkins: credenciales a76-e2e-keycloak-url, a76-e2e-test-user, a76-e2e-test-pass.
|
||||
* El usuario de prueba es creado y eliminado por globalSetup / globalTeardown.
|
||||
* Patrón idéntico al de aduanasoft-hub.
|
||||
*/
|
||||
import { test as setup } from '@playwright/test'
|
||||
import { test as setup, expect } from '@playwright/test'
|
||||
import { mkdirSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
// Lógica de chunking idéntica a access-token-cookie.shared.ts
|
||||
const ACCESS_TOKEN_MAX_SINGLE = 2800
|
||||
const ACCESS_TOKEN_CHUNK_SIZE = 2800
|
||||
const ACCESS_TOKEN_CHUNK_COUNT = 'access_token_chunks'
|
||||
const chunkName = (i: number) => `access_token_${i}`
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const authFile = path.join(__dirname, '.auth/user.json')
|
||||
|
||||
setup('autenticacion', async ({ page, request }) => {
|
||||
const baseUrl = (process.env.PLAYWRIGHT_TEST_BASE_URL ?? 'http://localhost:5173').replace(/\/$/, '')
|
||||
setup('autenticacion', async ({ page }) => {
|
||||
const username = process.env.E2E_TEST_USER ?? ''
|
||||
const password = process.env.E2E_TEST_PASSWORD ?? ''
|
||||
|
||||
// E2E_KEYCLOAK_URL: URL base de Keycloak accesible desde el agente Jenkins.
|
||||
// Puede diferir de VITE_KEYCLOAK_URL si ese apunta al dominio del propio servidor
|
||||
// y el contenedor Docker no puede resolver ese dominio (hairpin NAT).
|
||||
const keycloakUrl = (
|
||||
process.env.E2E_KEYCLOAK_URL ||
|
||||
process.env.KEYCLOAK_URL ||
|
||||
process.env.VITE_KEYCLOAK_URL ||
|
||||
''
|
||||
).replace(/\/$/, '')
|
||||
if (!username) throw new Error('E2E_TEST_USER no está configurado')
|
||||
if (!password) throw new Error('E2E_TEST_PASSWORD no está configurado')
|
||||
|
||||
const realm = process.env.KEYCLOAK_REALM || process.env.VITE_KEYCLOAK_REALM || 'master'
|
||||
const clientId = process.env.KEYCLOAK_CLIENT_ID || process.env.VITE_KEYCLOAK_CLIENT_ID || 'anexo76-frontend'
|
||||
const secret = process.env.KEYCLOAK_CLIENT_SECRET || ''
|
||||
const username = process.env.E2E_TEST_USER || ''
|
||||
const password = process.env.E2E_TEST_PASSWORD || ''
|
||||
// ── 1. Navegar a /login — Workspace intercepta y muestra su formulario ──
|
||||
await page.goto('/login')
|
||||
|
||||
if (!keycloakUrl) throw new Error('E2E_KEYCLOAK_URL / KEYCLOAK_URL no está configurado')
|
||||
if (!username) throw new Error('E2E_TEST_USER no está configurado')
|
||||
if (!password) throw new Error('E2E_TEST_PASSWORD no está configurado')
|
||||
// ── 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()
|
||||
|
||||
// ── 1. Obtener tokens vía password grant ──────────────────────────────────
|
||||
const tokenEndpoint = `${keycloakUrl}/realms/${realm}/protocol/openid-connect/token`
|
||||
// ── 3. Esperar redirect de vuelta a Anexo76 (/auth/callback → /dashboard) ──
|
||||
await expect(page).toHaveURL(/\/dashboard/, { timeout: 30_000 })
|
||||
|
||||
const form: Record<string, string> = {
|
||||
grant_type: 'password',
|
||||
client_id: clientId,
|
||||
username,
|
||||
password,
|
||||
scope: 'openid'
|
||||
}
|
||||
if (secret) form.client_secret = secret
|
||||
|
||||
const tokenRes = await request.post(tokenEndpoint, { form })
|
||||
|
||||
if (!tokenRes.ok()) {
|
||||
const body = await tokenRes.text()
|
||||
throw new Error(
|
||||
`Keycloak password grant falló (HTTP ${tokenRes.status()}).\n` +
|
||||
`Endpoint: ${tokenEndpoint}\n` +
|
||||
`Respuesta: ${body}\n` +
|
||||
`Verifica que el cliente "${clientId}" tenga "Direct Access Grants" habilitado en Keycloak.`
|
||||
)
|
||||
}
|
||||
|
||||
const tokens = await tokenRes.json() as {
|
||||
access_token: string
|
||||
refresh_token?: string
|
||||
id_token?: string
|
||||
}
|
||||
|
||||
// ── 2. Navegar al app para establecer origen de cookies ───────────────────
|
||||
await page.goto(baseUrl)
|
||||
|
||||
const hostname = new URL(baseUrl).hostname
|
||||
const secure = baseUrl.startsWith('https')
|
||||
const base = { domain: hostname, path: '/', sameSite: 'Lax' as const, secure }
|
||||
|
||||
// ── 3. Inyectar access_token respetando chunking (igual que setAccessTokenCookies) ──
|
||||
if (tokens.access_token.length <= ACCESS_TOKEN_MAX_SINGLE) {
|
||||
await page.context().addCookies([
|
||||
{ ...base, httpOnly: false, name: 'access_token', value: tokens.access_token }
|
||||
])
|
||||
} else {
|
||||
const parts: string[] = []
|
||||
for (let i = 0; i < tokens.access_token.length; i += ACCESS_TOKEN_CHUNK_SIZE) {
|
||||
parts.push(tokens.access_token.slice(i, i + ACCESS_TOKEN_CHUNK_SIZE))
|
||||
}
|
||||
await page.context().addCookies([
|
||||
{ ...base, httpOnly: false, name: ACCESS_TOKEN_CHUNK_COUNT, value: String(parts.length) },
|
||||
...parts.map((p, i) => ({ ...base, httpOnly: false, name: chunkName(i), value: p }))
|
||||
])
|
||||
}
|
||||
|
||||
// ── 4. Cookies httpOnly (el servidor las gestiona; Playwright puede setearlas en test) ──
|
||||
if (tokens.refresh_token) {
|
||||
await page.context().addCookies([
|
||||
{ ...base, httpOnly: true, name: 'refresh_token', value: tokens.refresh_token }
|
||||
])
|
||||
}
|
||||
if (tokens.id_token) {
|
||||
await page.context().addCookies([
|
||||
{ ...base, httpOnly: true, name: 'id_token', value: tokens.id_token }
|
||||
])
|
||||
}
|
||||
|
||||
// ── 5. Verificar que la sesión funciona navegando al dashboard ────────────
|
||||
await page.goto(`${baseUrl}/dashboard`)
|
||||
await page.waitForURL(/dashboard/, { timeout: 30000 })
|
||||
|
||||
// ── 6. Guardar estado para los tests dependientes ─────────────────────────
|
||||
// ── 4. Guardar estado para los tests dependientes ─────────────────────────
|
||||
mkdirSync(path.dirname(authFile), { recursive: true })
|
||||
await page.context().storageState({ path: authFile })
|
||||
})
|
||||
|
||||
108
frontend/e2e/fixtures/keycloak.ts
Normal file
108
frontend/e2e/fixtures/keycloak.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* 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<string> {
|
||||
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<string> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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()}`);
|
||||
}
|
||||
}
|
||||
50
frontend/e2e/global-setup.ts
Normal file
50
frontend/e2e/global-setup.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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})`);
|
||||
}
|
||||
37
frontend/e2e/global-teardown.ts
Normal file
37
frontend/e2e/global-teardown.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 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`);
|
||||
}
|
||||
@@ -9,9 +9,14 @@ 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: 60000,
|
||||
timeout: 60_000,
|
||||
use: {
|
||||
baseURL: process.env.PLAYWRIGHT_TEST_BASE_URL || 'http://localhost:5173',
|
||||
// Sin display en agentes de integración: obligatorio headless
|
||||
@@ -33,4 +38,4 @@ export default defineConfig({
|
||||
use: { storageState: authStatePath }
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user