estructura_automation
Version:
Paquete de estructura de automation de paguetodo
743 lines (653 loc) • 27.7 kB
text/typescript
/* eslint-disable no-unused-vars */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable no-console */
import { Reporter, TestCase, TestResult, FullConfig, Suite, FullResult, TestError } from '@playwright/test/reporter';
import { relative } from 'path';
/**
* ========================================================================================
* REPORTER PERSONALIZADO
* ========================================================================================
*
* Este reporter personalizado proporciona una salida detallada y estructurada de las
* pruebas de Playwright, con manejo especializado para diferentes tipos de errores
* y un formato similar al reporter "list" nativo pero con características adicionales.
*
* Características principales:
* - Detección automática de errores de selectores y timeouts
* - Resaltado de líneas importantes en stack traces
* - Manejo de errores globales y de configuración
* - Estadísticas detalladas y tasa de éxito
*
* ========================================================================================
*/
// ========================================================================================
// TYPES & INTERFACES
// ========================================================================================
/**
* Información de un test que ha fallado
*/
interface FailedTestInfo {
/** Título completo del test */
readonly title: string;
/** Mensaje de error (si está disponible) */
readonly error?: string;
/** Ubicación del archivo del test */
readonly location?: string;
/** Duración del test en segundos */
readonly duration?: number;
}
/**
* Estadísticas de ejecución de tests
*/
interface TestStats {
/** Número de tests que pasaron */
passed: number;
/** Número de tests que fallaron */
failed: number;
/** Número de tests omitidos */
skipped: number;
}
/**
* Información de tiempo de ejecución
*/
interface TimeInfo {
/** Minutos transcurridos */
readonly minutes: number;
/** Segundos transcurridos (resto) */
readonly seconds: number;
/** Total de segundos transcurridos */
readonly totalSeconds: number;
}
/**
* Tipos de errores que puede detectar el reporter
*/
type ErrorType = 'timeout' | 'selector' | 'element' | 'business' | 'expect' | 'unknown';
// ========================================================================================
// ENUMS & CONSTANTS
// ========================================================================================
/**
* Estados posibles de un test en Playwright
*/
enum TestStatus {
PASSED = 'passed',
FAILED = 'failed',
SKIPPED = 'skipped',
TIMED_OUT = 'timedOut'
}
/**
* Patrones de texto para detectar diferentes tipos de errores
*/
const ERROR_PATTERNS: Record<ErrorType, readonly string[]> = {
/** Errores relacionados con timeouts */
timeout: ['Test timeout', 'timeout', 'exceeded'] as const,
/** Errores relacionados con selectores y locators */
selector: ['locator.', 'waiting for', 'Locator', 'getByText', 'getBy', 'selector'] as const,
/** Errores relacionados con elementos no encontrados */
element: ['not found', 'not visible', 'not attached'] as const,
/** Errores de assertions/expect */
expect: [
'expect(', 'toBeVisible', 'toHaveText', 'toHaveValue', 'toBeChecked', 'toBeEnabled',
'toHaveURL', 'toHaveTitle', 'toContainText', 'toBe', 'toEqual', 'toBeDisabled',
'toBeEditable', 'toBeEmpty', 'toBeHidden', 'toHaveAttribute', 'toHaveClass',
'toHaveCSS', 'toHaveId', 'toHaveJSProperty', 'toHaveScreenshot', 'toHaveValues',
'toBeAttached', 'toBeInViewport', 'toHaveAccessibleDescription', 'toHaveAccessibleName',
'toHaveRole', 'toBeChecked', 'toBeDisabled', 'toBeEditable', 'toBeEmpty', 'toBeEnabled',
'toBeHidden', 'toBeVisible', 'toBeFocused'
] as const,
/** Errores de lógica de negocio (sin patrones específicos) */
business: [] as const,
/** Errores desconocidos */
unknown: [] as const
} as const;
/**
* Patrones para resaltar líneas importantes en el stack trace
* Estas líneas contienen información crucial sobre qué selector o acción falló
*/
const STACK_HIGHLIGHT_PATTERNS = [
'waiting for',
'locator.',
'getByText',
'getBy',
'click',
'fill'
] as const;
/**
* Símbolos de consola para mejorar la legibilidad de la salida
*/
const CONSOLE_SYMBOLS = {
SUCCESS: '✅',
ERROR: '❌',
SKIP: '⏩',
WARNING: '⚠️',
GLOBAL_ERROR: '🚨',
STATS: '📊',
LOCATION: '📍',
TIME: '⏱️',
SUCCESS_RATE: '🎯',
STATUS: '📌',
DETAIL: '🔍',
ROCKET: '🚀',
SPARKLES: '✨'
} as const;
// ========================================================================================
// MAIN REPORTER CLASS
// ========================================================================================
/**
* Reporter personalizado que proporciona salida detallada
* con manejo especializado de diferentes tipos de errores
*/
class customReporter implements Reporter {
/** Estadísticas de ejecución de tests */
private readonly stats: TestStats = { passed: 0, failed: 0, skipped: 0 };
/** Lista de tests que han fallado con información detallada */
private readonly failedTests: FailedTestInfo[] = [];
/** Número del test actual (para mostrar progreso) */
private currentTestNumber = 0;
/** Número total de tests a ejecutar */
private totalTests = 0;
/** Timestamp de inicio de la ejecución */
private readonly startTime: number = Date.now();
/**
* Contador de reintentos para cada test
*/
private readonly retryCounts: Record<string, number> = {};
/**
* Tests que están siendo reintentados (para evitar mostrar encabezado duplicado)
*/
private readonly testsBeingRetried: Set<string> = new Set();
// ========================================================================================
// PUBLIC REPORTER METHODS - Métodos requeridos por la interfaz Reporter
// ========================================================================================
/**
* Se ejecuta al inicio de la suite de tests
* @param config - Configuración de Playwright
* @param suite - Suite de tests a ejecutar
*/
onBegin(config: FullConfig, suite: Suite): void {
void config;
this.totalTests = suite.allTests().length;
this.logInitialMessage();
}
/**
* Se ejecuta cuando un test individual comienza
* @param test - Información del test que está comenzando
*/
onTestBegin(test: TestCase): void {
// Solo incrementar el contador si no es un reintento
// Playwright llama a onTestBegin para cada intento del mismo test
const testKey = `${test.title}`;
const isRetry = this.testsBeingRetried.has(testKey);
if (!this.retryCounts[testKey]) {
this.currentTestNumber++;
this.retryCounts[testKey] = 1; // Marcar que ya contamos este test
}
// Solo mostrar encabezado si no es un reintento
if (!isRetry) {
const testInfo = this.extractTestInfo(test);
this.logTestStart(testInfo);
}
}
/**
* Se ejecuta cuando un test individual termina
* @param test - Información del test que terminó
* @param result - Resultado del test (passed, failed, skipped, etc.)
*/
onTestEnd(test: TestCase, result: TestResult): void {
const duration = this.calculateDuration(result.duration);
// Registrar información de depuración
if (process.env.NODE_ENV === 'development') {
this.logDebugInfo(result);
}
// Detectar configuración de reintentos
const maxRetries = test.retries || 0;
const totalAttempts = maxRetries + 1;
// Si el test falló O hizo timeout Y aún hay reintentos disponibles, solo mostrar mensaje de reintento
const isFailedOrTimedOut = result.status === TestStatus.FAILED || result.status === TestStatus.TIMED_OUT;
if (isFailedOrTimedOut && result.retry < maxRetries) {
const nextAttempt = result.retry + 2; // El próximo intento que se ejecutará
const statusText = result.status === TestStatus.TIMED_OUT ? 'timeout' : 'fallo';
console.log(` 🔄 Reintentando test por ${statusText} (${test.title}), intento: ${nextAttempt}/${totalAttempts}`);
// Marcar este test como siendo reintentado
const testKey = `${test.title}`;
this.testsBeingRetried.add(testKey);
return; // No procesar como resultado final, esperamos el siguiente intento
}
// Procesar resultado final (último intento, sin importar si pasó o falló)
// Limpiar el marcador de reintento ya que este es el resultado final
const testKey = `${test.title}`;
this.testsBeingRetried.delete(testKey);
switch (result.status as TestStatus) {
case TestStatus.PASSED:
this.handlePassedTest(duration);
break;
case TestStatus.FAILED:
this.handleFailedTest(test, result, duration);
break;
case TestStatus.SKIPPED:
this.handleSkippedTest();
break;
case TestStatus.TIMED_OUT:
this.handleTimedOutTest(duration);
break;
default:
this.handleUnknownStatus(result);
break;
}
}
/**
* Se ejecuta cuando ocurre un error global (no específico de un test)
* @param error - Error global que ocurrió
*/
onError(error: TestError): void {
this.logGlobalError(error);
}
/**
* Se ejecuta al final de toda la suite de tests
* @param result - Resultado final de la ejecución
*/
onEnd(result: FullResult): void {
const timeInfo = this.calculateTimeInfo();
this.logFinalSummary(result, timeInfo);
}
// ========================================================================================
// PRIVATE HELPER METHODS - Métodos auxiliares para organizar la lógica
// ========================================================================================
/**
* Muestra el mensaje inicial cuando comienzan las pruebas
*/
private logInitialMessage(): void {
console.log(`\n=== ${CONSOLE_SYMBOLS.ROCKET} INICIANDO PRUEBAS ===`);
console.log('=== Por favor no toque el PC, hasta que terminen las pruebas, si no explotamos todos ===');
console.log(`📋 Total de pruebas a ejecutar: ${this.totalTests}`);
console.log('==========================================\n');
}
/**
* Extrae información relevante de un test para mostrar en la consola
* @param test - Test del cual extraer información
* @returns Objeto con información formateada del test
*/
private extractTestInfo(test: TestCase): { browser: string; location: string; line: string; column: string; fullTitle: string } {
// Obtener el nombre real del proyecto desde la configuración
const projectName = test.parent?.project?.name || 'unknown';
const realProjectName = projectName === 'project' ? 'pos-admin' : projectName;
const location = test.location?.file ? relative(process.cwd(), test.location.file) : 'Unknown location';
const line = test.location?.line?.toString() || '?';
const column = test.location?.column?.toString() || '?';
// Construir el título completo del test incluyendo describe y test
const fullTitle = this.buildFullTestTitle(test);
return { browser: realProjectName, location, line, column, fullTitle };
}
/**
* Construye el título completo del test incluyendo todos los describe anidados
* @param test - Test del cual extraer el título
* @returns Título completo del test
*/
private buildFullTestTitle(test: TestCase): string {
const titles: string[] = [];
let current: any = test.parent;
// Recorrer hacia arriba para obtener todos los describe
while (current && current.title) {
titles.unshift(current.title);
current = current.parent;
}
// Agregar el título del test al final
titles.push(test.title);
return titles.join(' › ');
}
/**
* Muestra información del test que está comenzando
* @param testInfo - Información del test formateada
*/
private logTestStart(testInfo: { browser: string; location: string; line: string; column: string; fullTitle: string }): void {
console.log(`\n${this.currentTestNumber}/${this.totalTests}) [${testInfo.browser}] › ${testInfo.location}:${testInfo.line}:${testInfo.column} › ${testInfo.fullTitle}`);
}
/**
* Calcula la duración de un test en segundos
* @param durationMs - Duración en milisegundos
* @returns Objeto con duración en segundos y formato string
*/
private calculateDuration(durationMs: number): { seconds: number; formatted: string } {
const seconds = durationMs / 1000;
return {
seconds,
formatted: seconds.toFixed(2)
};
}
/**
* Muestra información de debug sobre el resultado del test
* @param result - Resultado del test
*/
private logDebugInfo(result: TestResult): void {
const statusMessages = {
passed: '✅ Completado exitosamente',
failed: '❌ Falló durante la ejecución',
skipped: '⏩ Omitido',
timedOut: '⏰ Tiempo excedido'
};
const statusMessage = statusMessages[result.status as keyof typeof statusMessages] || `⚠️ Estado: ${result.status}`;
console.log(` ${statusMessage}`);
if (result.error && result.status === 'failed') {
console.log(` 💬 Razón: ${result.error.message}`);
}
}
/**
* Maneja un test que pasó exitosamente
* @param duration - Información de duración del test
*/
private handlePassedTest(duration: { formatted: string }): void {
this.stats.passed++;
console.log(` ${CONSOLE_SYMBOLS.SUCCESS} Pasó (${duration.formatted}s)`);
}
/**
* Maneja un test que falló
* @param test - Información del test que falló
* @param result - Resultado del test fallido
* @param duration - Información de duración del test
*/
private handleFailedTest(test: TestCase, result: TestResult, duration: { seconds: number; formatted: string }): void {
this.stats.failed++;
const location = test.location?.file ? relative(process.cwd(), test.location.file) : 'Unknown location';
const fullTitle = this.buildFullTestTitle(test);
this.addFailedTest(fullTitle, result.error?.message, location, duration.seconds);
const errorType = this.detectErrorType(result.error?.message || '');
this.logFailedTest(errorType, duration.formatted, test.timeout);
if (result.error) {
this.logErrorDetails(result.error, errorType);
} else if (this.hasMultipleErrors(result)) {
this.logMultipleErrors(result);
} else {
this.logUnknownError();
}
}
/**
* Maneja un test que fue omitido
*/
private handleSkippedTest(): void {
this.stats.skipped++;
console.log(` ${CONSOLE_SYMBOLS.SKIP} Omitido`);
}
/**
* Maneja un test que excedió el tiempo límite
* @param duration - Información de duración del test
*/
private handleTimedOutTest(duration: { formatted: string }): void {
this.stats.failed++;
console.log(`\n ${CONSOLE_SYMBOLS.ERROR} Test timeout (${duration.formatted}s)\n`);
console.error(` Error: Test excedió el tiempo límite`);
}
/**
* Maneja un test con estado desconocido
* @param result - Resultado del test con estado desconocido
*/
private handleUnknownStatus(result: TestResult): void {
console.log(` ${CONSOLE_SYMBOLS.WARNING} Status desconocido: ${result.status}`);
if (result.error) {
console.error(` Error: ${result.error.message}`);
if (result.error.stack) {
console.error(` Stack: ${result.error.stack}`);
}
}
}
/**
* Agrega un test fallido a la lista para el resumen final
* @param title - Título del test
* @param error - Mensaje de error (opcional)
* @param location - Ubicación del archivo (opcional)
* @param duration - Duración en segundos (opcional)
*/
private addFailedTest(title: string, error?: string, location?: string, duration?: number): void {
this.failedTests.push({ title, error, location, duration });
}
/**
* Detecta el tipo de error basado en el mensaje
* @param errorMessage - Mensaje de error a analizar
* @returns Tipo de error detectado
*/
private detectErrorType(errorMessage: string): ErrorType {
for (const [type, patterns] of Object.entries(ERROR_PATTERNS)) {
if (patterns.some(pattern => errorMessage.includes(pattern))) {
return type as ErrorType;
}
}
return 'business';
}
/**
* Muestra el mensaje apropiado para un test fallido según el tipo de error
* @param errorType - Tipo de error detectado
* @param durationFormatted - Duración formateada como string
* @param testTimeout - Timeout configurado para el test (opcional)
*/
private logFailedTest(errorType: ErrorType, durationFormatted: string, testTimeout?: number): void {
const messages: Record<ErrorType, string> = {
timeout: `\n ${CONSOLE_SYMBOLS.ERROR} Test timeout of ${testTimeout || 30000}ms exceeded.\n`,
selector: `\n ${CONSOLE_SYMBOLS.ERROR} Selector/Element error (${durationFormatted}s)\n`,
element: `\n ${CONSOLE_SYMBOLS.ERROR} Selector/Element error (${durationFormatted}s)\n`,
expect: `\n ${CONSOLE_SYMBOLS.ERROR} Assertion failed (${durationFormatted}s)\n`,
business: `\n ${CONSOLE_SYMBOLS.ERROR} Test falló (${durationFormatted}s)\n`,
unknown: `\n ${CONSOLE_SYMBOLS.ERROR} Test falló (${durationFormatted}s)\n`
};
console.log(messages[errorType]);
}
/**
* Muestra los detalles de un error específico
* @param error - Error a mostrar
* @param errorType - Tipo de error para formateo especial
*/
private logErrorDetails(error: TestError, errorType: ErrorType): void {
console.error(` Error: ${error.message || 'Sin mensaje de error'}`);
// Extraer y mostrar el selector que falló
const failedSelector = this.extractFailedSelector(error.message || '', error.stack || '');
if (failedSelector) {
console.error(` 🎯 Selector que falló: ${failedSelector}`);
}
if (error.stack) {
console.error('\n Call log:');
this.logStackTrace(error.stack, errorType);
}
}
/**
* Extrae el selector específico que falló del mensaje de error o stack trace
* @param errorMessage - Mensaje de error
* @param stackTrace - Stack trace completo
* @returns El selector que falló o null si no se puede determinar
*/
private extractFailedSelector(errorMessage: string, stackTrace: string): string | null {
// Patrones para extraer TODOS los selectores de Playwright
const selectorPatterns = [
// Todos los métodos getBy* de Playwright
{ pattern: /waiting for getByText\('([^']+)'\)/, method: 'getByText' },
{ pattern: /waiting for getByText\("([^"]+)"\)/, method: 'getByText' },
{ pattern: /waiting for getByRole\('([^']+)',\s*\{\s*name:\s*'([^']+)'\s*\}\)/, method: 'getByRole' },
{ pattern: /waiting for getByRole\("([^"]+)",\s*\{\s*name:\s*"([^"]+)"\s*\}\)/, method: 'getByRole' },
{ pattern: /waiting for getByRole\('([^']+)'\)/, method: 'getByRole' },
{ pattern: /waiting for getByRole\("([^"]+)"\)/, method: 'getByRole' },
{ pattern: /waiting for getByPlaceholder\('([^']+)'\)/, method: 'getByPlaceholder' },
{ pattern: /waiting for getByPlaceholder\("([^"]+)"\)/, method: 'getByPlaceholder' },
{ pattern: /waiting for getByTestId\('([^']+)'\)/, method: 'getByTestId' },
{ pattern: /waiting for getByTestId\("([^"]+)"\)/, method: 'getByTestId' },
{ pattern: /waiting for getByLabel\('([^']+)'\)/, method: 'getByLabel' },
{ pattern: /waiting for getByLabel\("([^"]+)"\)/, method: 'getByLabel' },
{ pattern: /waiting for getByAltText\('([^']+)'\)/, method: 'getByAltText' },
{ pattern: /waiting for getByAltText\("([^"]+)"\)/, method: 'getByAltText' },
{ pattern: /waiting for getByTitle\('([^']+)'\)/, method: 'getByTitle' },
{ pattern: /waiting for getByTitle\("([^"]+)"\)/, method: 'getByTitle' },
// Para selectores CSS, XPath y otros
{ pattern: /waiting for locator\('([^']+)'\)/, method: 'locator' },
{ pattern: /waiting for locator\("([^"]+)"\)/, method: 'locator' },
{ pattern: /locator\('([^']+)'\)/, method: 'locator' },
{ pattern: /locator\("([^"]+)"\)/, method: 'locator' },
// Para frameLocator
{ pattern: /waiting for frameLocator\('([^']+)'\)/, method: 'frameLocator' },
{ pattern: /waiting for frameLocator\("([^"]+)"\)/, method: 'frameLocator' },
// Para errores de expect/assertions con todos los getBy*
{ pattern: /expect\(.*?getByText\('([^']+)'\)\)/, method: 'getByText' },
{ pattern: /expect\(.*?getByText\("([^"]+)"\)\)/, method: 'getByText' },
{ pattern: /expect\(.*?getByRole\('([^']+)',\s*\{\s*name:\s*'([^']+)'\s*\}\)\)/, method: 'getByRole' },
{ pattern: /expect\(.*?getByRole\('([^']+)'\)\)/, method: 'getByRole' },
{ pattern: /expect\(.*?getByTestId\('([^']+)'\)\)/, method: 'getByTestId' },
{ pattern: /expect\(.*?getByLabel\('([^']+)'\)\)/, method: 'getByLabel' },
{ pattern: /expect\(.*?getByPlaceholder\('([^']+)'\)\)/, method: 'getByPlaceholder' },
{ pattern: /expect\(.*?getByAltText\('([^']+)'\)\)/, method: 'getByAltText' },
{ pattern: /expect\(.*?getByTitle\('([^']+)'\)\)/, method: 'getByTitle' },
{ pattern: /expect\(.*?locator\('([^']+)'\)\)/, method: 'locator' },
];
// Intentar extraer del mensaje de error primero
for (const { pattern, method } of selectorPatterns) {
const match = errorMessage.match(pattern);
if (match) {
if (match[2] && method === 'getByRole') {
// Para casos como getByRole con name
if (errorMessage.includes('expect(')) {
return `expect(page.getByRole('${match[1]}', { name: '${match[2]}' }))`;
}
return `page.getByRole('${match[1]}', { name: '${match[2]}' })`;
}
// Si es un error de expect, mostrar con expect()
if (errorMessage.includes('expect(')) {
return `expect(page.${method}('${match[1]}'))`;
}
return `page.${method}('${match[1]}')`;
}
}
// Si no se encuentra en el mensaje, buscar en el stack trace
const stackLines = stackTrace.split('\n');
for (const line of stackLines) {
// Buscar líneas que contengan información del selector
if (line.includes('waiting for') || line.includes('locator(') || line.includes('expect(')) {
for (const { pattern, method } of selectorPatterns) {
const match = line.match(pattern);
if (match) {
if (match[2] && method === 'getByRole') {
if (line.includes('expect(')) {
return `expect(page.getByRole('${match[1]}', { name: '${match[2]}' }))`;
}
return `page.getByRole('${match[1]}', { name: '${match[2]}' })`;
}
if (line.includes('expect(')) {
return `expect(page.${method}('${match[1]}'))`;
}
return `page.${method}('${match[1]}')`;
}
}
}
}
return null;
}
/**
* Muestra el stack trace con resaltado especial para errores de selector
* @param stack - Stack trace completo
* @param errorType - Tipo de error para determinar si resaltar líneas
*/
private logStackTrace(stack: string, errorType: ErrorType): void {
const stackLines = stack.split('\n');
const shouldHighlight = errorType === 'selector' || errorType === 'element';
stackLines.forEach(line => {
if (!line.trim()) return;
if (shouldHighlight && this.shouldHighlightLine(line)) {
console.error(` - ${line.trim()}`);
} else {
console.error(` ${line.trim()}`);
}
});
}
/**
* Determina si una línea del stack trace debe ser resaltada
* @param line - Línea del stack trace a evaluar
* @returns true si la línea debe ser resaltada
*/
private shouldHighlightLine(line: string): boolean {
return STACK_HIGHLIGHT_PATTERNS.some(pattern => line.includes(pattern));
}
/**
* Verifica si un resultado tiene múltiples errores
* @param result - Resultado del test a verificar
* @returns true si tiene múltiples errores
*/
private hasMultipleErrors(result: TestResult): boolean {
return Array.isArray((result as any).errors) && (result as any).errors.length > 0;
}
/**
* Muestra múltiples errores de un test
* @param result - Resultado del test con múltiples errores
*/
private logMultipleErrors(result: TestResult): void {
const errors = (result as any).errors as any[];
errors.forEach((err: any, idx: number) => {
console.error(` [${idx + 1}] Error: ${err.message || err}`);
if (err.stack) {
console.error('\n Call log:');
this.logStackTrace(err.stack, 'unknown');
}
});
}
/**
* Muestra mensaje para errores desconocidos
*/
private logUnknownError(): void {
console.error(` ${CONSOLE_SYMBOLS.ERROR} Error desconocido - No se pudo obtener información del error`);
}
/**
* Muestra información de errores globales
* @param error - Error global que ocurrió
*/
private logGlobalError(error: TestError): void {
console.error(`\n${CONSOLE_SYMBOLS.GLOBAL_ERROR} ERROR GLOBAL DETECTADO:`);
console.error(` Mensaje: ${error.message || 'Sin mensaje'}`);
if (error.stack) {
console.error('\n Stack trace:');
this.logStackTrace(error.stack, 'unknown');
}
console.error('');
}
/**
* Calcula información de tiempo transcurrido
* @returns Objeto con información de tiempo formateada
*/
private calculateTimeInfo(): TimeInfo {
const totalSeconds = (Date.now() - this.startTime) / 1000;
const minutes = Math.floor(totalSeconds / 60);
const seconds = Math.floor(totalSeconds % 60);
return { minutes, seconds, totalSeconds };
}
/**
* Calcula la tasa de éxito de los tests
* @returns Porcentaje de éxito (0-100)
*/
private calculateSuccessRate(): number {
const totalExecuted = this.totalTests - this.stats.skipped;
return totalExecuted > 0 ? (this.stats.passed / totalExecuted) * 100 : 0;
}
/**
* Muestra el resumen final de la ejecución
* @param result - Resultado final de la suite
* @param timeInfo - Información de tiempo transcurrido
*/
private logFinalSummary(result: FullResult, timeInfo: TimeInfo): void {
console.log(`\n=== ${CONSOLE_SYMBOLS.STATS} RESUMEN FINAL ===`);
console.log(`${CONSOLE_SYMBOLS.SUCCESS} Pruebas exitosas: ${this.stats.passed}`);
console.log(`${CONSOLE_SYMBOLS.ERROR} Pruebas fallidas: ${this.stats.failed}`);
console.log(`${CONSOLE_SYMBOLS.SKIP} Pruebas omitidas: ${this.stats.skipped}`);
console.log(`${CONSOLE_SYMBOLS.TIME} Tiempo total: ${timeInfo.minutes}m ${timeInfo.seconds}s`);
console.log(`${CONSOLE_SYMBOLS.SUCCESS_RATE} Tasa de éxito: ${this.calculateSuccessRate().toFixed(1)}%`);
console.log(`${CONSOLE_SYMBOLS.STATUS} Estado final: ${result.status.toUpperCase()}`);
if (this.stats.failed > 0) {
this.logFailedTestsDetails();
}
console.log(`\n${CONSOLE_SYMBOLS.SPARKLES} Ejecución completada ${CONSOLE_SYMBOLS.SPARKLES}\n`);
}
/**
* Muestra los detalles de todos los tests que fallaron
*/
private logFailedTestsDetails(): void {
console.log(`\n=== ${CONSOLE_SYMBOLS.ERROR} DETALLE DE FALLOS ===`);
this.failedTests.forEach((test, index) => {
console.log(`\n${index + 1}. Test: ${test.title}`);
console.log(` ${CONSOLE_SYMBOLS.LOCATION} Ubicación: ${test.location}`);
console.log(` ${CONSOLE_SYMBOLS.TIME} Duración: ${test.duration?.toFixed(2)}s`);
if (test.error) {
console.error(` ${CONSOLE_SYMBOLS.DETAIL} Error: ${test.error}`);
}
});
}
}
export default customReporter;