@tshifhiwa/ohrm-ui-automation-framework
Version:
Playwright and TypeScript–based test automation framework for validating core UI features and workflows of the OrangeHRM demo application.
184 lines (154 loc) • 5.79 kB
text/typescript
import type { ErrorDetails } from "./internals/error-handler.types.js";
import ErrorAnalyzer from "./internals/errorAnalyzer.js";
import { ErrorCacheManager } from "./internals/errorCacheManager.js";
import logger from "../logger/loggerManager.js";
export default class ErrorHandler {
public static captureError(error: unknown, source: string, context = ""): void {
if (!error || !ErrorCacheManager.shouldLogError(error)) return;
try {
const details = ErrorAnalyzer.createErrorDetails(error, source, context);
this.logStructuredError(details);
this.logAdditionalDetails(error, source);
} catch (loggingError) {
this.handleLoggingFailure(loggingError, source);
}
}
public static logAndThrow(source: string, message: string): never {
const error = new Error(message);
this.captureError(error, source);
throw error;
}
public static log(source: string, message: string): void {
const error = new Error(message);
this.captureError(error, source);
}
public static clearErrorCache(): void {
ErrorCacheManager.clearAll();
}
private static logStructuredError(details: ErrorDetails): void {
try {
logger.error(details);
} catch {
console.error("Error:", details);
}
}
private static readonly circularReplacer = (() => {
const seen = new WeakSet<object>();
return (key: string, value: unknown) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) return "[Circular]";
seen.add(value);
}
return value;
};
})();
private static logAdditionalDetails(error: unknown, source: string): void {
const extraDetails = ErrorAnalyzer.extractAdditionalErrorDetails(error);
// Enhanced: Always include stack trace and error metadata
const enhancedDetails = {
...extraDetails,
errorType: this.getErrorType(error),
stack: this.getStackTrace(error),
rawError: this.getRawErrorInfo(error),
};
// Remove undefined/null values
Object.keys(enhancedDetails).forEach((key) => {
if (
enhancedDetails[key as keyof typeof enhancedDetails] === undefined ||
enhancedDetails[key as keyof typeof enhancedDetails] === null
) {
delete enhancedDetails[key as keyof typeof enhancedDetails];
}
});
if (Object.keys(enhancedDetails).length === 0) return;
const sanitized = this.deepSanitizeObject(enhancedDetails);
const logPayload = {
source,
type: "Additional Details",
details: sanitized,
};
try {
logger.error(JSON.stringify(logPayload, this.circularReplacer, 2));
} catch {
logger.error("Fallback log:", logPayload);
}
}
private static getErrorType(error: unknown): string | undefined {
if (error instanceof Error) {
return error.constructor.name;
}
if (typeof error === "object" && error !== null && "name" in error) {
const name = error.name;
if (typeof name === "string") return name;
}
return typeof error;
}
private static getStackTrace(error: unknown): string | undefined {
if (error instanceof Error && error.stack) {
return error.stack.substring(0, 2000);
}
if (typeof error === "object" && error !== null && "stack" in error) {
const stack = error.stack;
if (typeof stack === "string") {
return stack.substring(0, 2000);
}
}
return undefined;
}
private static getRawErrorInfo(error: unknown): Record<string, unknown> | undefined {
if (typeof error !== "object" || error === null) return undefined;
const info: Record<string, unknown> = {};
const errorObj = error as Record<string, unknown>;
// Capture all enumerable properties
for (const key of Object.keys(errorObj)) {
const value = errorObj[key];
// Skip stack (already captured separately) and functions
if (key === "stack" || typeof value === "function") continue;
// Limit string lengths
if (typeof value === "string") {
info[key] = value.length > 500 ? value.substring(0, 500) + "..." : value;
} else if (typeof value === "object" && value !== null) {
// Shallow copy of nested objects
try {
info[key] = JSON.parse(JSON.stringify(value));
} catch {
info[key] = "[Complex Object]";
}
} else {
info[key] = value;
}
}
return Object.keys(info).length > 0 ? info : undefined;
}
private static deepSanitizeObject(obj: unknown): unknown {
if (typeof obj === "string") {
return ErrorCacheManager.getSanitizedMessage(obj);
}
if (obj == null || typeof obj !== "object") return obj;
if (Array.isArray(obj)) {
return obj.map((item) => this.deepSanitizeObject(item));
}
const result: Record<string, unknown> = {};
let hasChanges = false;
for (const [key, value] of Object.entries(obj)) {
const sanitizedValue = this.deepSanitizeObject(value);
result[key] = sanitizedValue;
if (sanitizedValue !== value) hasChanges = true;
}
return hasChanges ? result : obj;
}
private static handleLoggingFailure(loggingError: unknown, source: string): void {
const fallbackError = {
source,
context: "Error Handler Failure",
message: ErrorAnalyzer.getErrorMessage(loggingError),
timestamp: new Date().toISOString(),
stack: this.getStackTrace(loggingError),
};
try {
logger.error(fallbackError);
} catch {
console.error("ErrorHandler failure:", fallbackError);
}
}
}