UNPKG

@alphabin/trx

Version:

TRX reporter for Playwright tests with Azure Blob Storage upload support

187 lines (186 loc) 6.74 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ErrorHandler = void 0; const logger_util_1 = __importDefault(require("./logger.util")); /** * Enhanced error handling utility for TRX operations */ class ErrorHandler { /** * Handles and categorizes errors with appropriate logging */ static handleError(error, context) { const err = this.normalizeError(error); logger_util_1.default.error(`Error in ${context.operation}`, err); if (context.details) { logger_util_1.default.debug('Error context details:', context.details); } return err; } /** * Executes an async operation with retry logic */ static async withRetry(operation, context, options = {}) { const retryOptions = { ...this.defaultRetryOptions, ...options }; let lastError; for (let attempt = 1; attempt <= retryOptions.maxAttempts; attempt++) { try { logger_util_1.default.debug(`${context.operation} - Attempt ${attempt}/${retryOptions.maxAttempts}`); return await operation(); } catch (error) { lastError = this.normalizeError(error); if (attempt === retryOptions.maxAttempts) { break; } // Check if error is retryable if (!this.isRetryableError(lastError)) { logger_util_1.default.debug(`Non-retryable error in ${context.operation}:`, lastError.message); break; } // Calculate delay with exponential backoff const delay = Math.min(retryOptions.baseDelay * Math.pow(retryOptions.backoffFactor, attempt - 1), retryOptions.maxDelay); logger_util_1.default.debug(`${context.operation} failed, retrying in ${delay}ms...`); await this.delay(delay); } } throw this.handleError(lastError, context); } /** * Safely executes an operation and returns result or null on failure */ static async safeExecute(operation, context, defaultValue = null) { try { return await operation(); } catch (error) { this.handleError(error, { ...context, recoverable: true }); return defaultValue; } } /** * Validates configuration and provides helpful error messages */ static validateConfig(config) { const errors = []; if (!config.serverUrl) { errors.push('serverUrl is required'); } else if (!this.isValidUrl(config.serverUrl)) { errors.push('serverUrl must be a valid URL'); } if (!config.apiKey) { errors.push('apiKey is required'); } else if (typeof config.apiKey !== 'string' || config.apiKey.length < 10) { errors.push('apiKey must be a valid string with at least 10 characters'); } if (config.timeout !== undefined && (typeof config.timeout !== 'number' || config.timeout <= 0)) { errors.push('timeout must be a positive number'); } if (config.retries !== undefined && (typeof config.retries !== 'number' || config.retries < 0)) { errors.push('retries must be a non-negative number'); } return errors; } /** * Checks if an error is likely to be retryable */ static isRetryableError(error) { const retryablePatterns = [ /timeout/i, /network/i, /connection/i, /econnreset/i, /enotfound/i, /socket hang up/i, /request failed/i ]; // Check error message const message = error.message.toLowerCase(); if (retryablePatterns.some(pattern => pattern.test(message))) { return true; } // Check for specific error codes const errorCode = error.code; if (errorCode) { const retryableCodes = ['ECONNRESET', 'ENOTFOUND', 'ETIMEDOUT', 'ECONNREFUSED']; return retryableCodes.includes(errorCode); } // Check for HTTP status codes (if available) const statusCode = error.response?.status; if (statusCode) { // Retry on 5xx server errors and some 4xx errors return statusCode >= 500 || statusCode === 408 || statusCode === 429; } return false; } /** * Normalizes different error types to Error instances */ static normalizeError(error) { if (error instanceof Error) { return error; } if (typeof error === 'string') { return new Error(error); } if (typeof error === 'object' && error !== null) { const message = error.message || JSON.stringify(error); return new Error(message); } return new Error(`Unknown error: ${String(error)}`); } /** * Validates URL format */ static isValidUrl(url) { try { new URL(url); return true; } catch { return false; } } /** * Creates a delay promise */ static delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Formats error for user display */ static formatErrorForUser(error, context) { let message = error.message; // Simplify common error messages if (message.includes('ENOTFOUND')) { message = 'Network connection failed - check your internet connection and server URL'; } else if (message.includes('ECONNREFUSED')) { message = 'Connection refused - the server may be down or unreachable'; } else if (message.includes('timeout')) { message = 'Request timed out - the server may be overloaded'; } else if (message.includes('401') || message.includes('unauthorized')) { message = 'Authentication failed - check your API key'; } else if (message.includes('403') || message.includes('forbidden')) { message = 'Access denied - your API key may not have the required permissions'; } return context ? `${context}: ${message}` : message; } } exports.ErrorHandler = ErrorHandler; ErrorHandler.defaultRetryOptions = { maxAttempts: 3, baseDelay: 1000, maxDelay: 10000, backoffFactor: 2 }; exports.default = ErrorHandler;