@alphabin/trx
Version:
TRX reporter for Playwright tests with Azure Blob Storage upload support
187 lines (186 loc) • 6.74 kB
JavaScript
;
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;