export class RetryHelper { static async retry( fn: () => Promise, options: { maxAttempts?: number; delay?: number; backoff?: boolean; onRetry?: (attempt: number, error: Error) => void; } = {} ): Promise { const { maxAttempts = 3, delay = 1000, backoff = true, onRetry } = options; let lastError: Error | undefined; for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { return await fn(); } catch (error) { lastError = error as Error; if (attempt === maxAttempts) { throw lastError; } if (onRetry) { onRetry(attempt, lastError); } const currentDelay = backoff ? delay * attempt : delay; await this.sleep(currentDelay); } } throw lastError!; } static async retryWithCondition( fn: () => Promise, condition: (result: T) => boolean, options: { maxAttempts?: number; delay?: number; timeout?: number; onRetry?: (attempt: number, lastResult: T) => void; } = {} ): Promise { const { maxAttempts = 10, delay = 500, timeout = 10000, onRetry } = options; const startTime = Date.now(); let lastResult: T | undefined; for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { lastResult = await fn(); if (condition(lastResult)) { return lastResult; } if (Date.now() - startTime > timeout) { throw new Error(`Timeout after ${timeout}ms waiting for condition to be met`); } if (onRetry && lastResult !== undefined) { onRetry(attempt, lastResult); } await this.sleep(delay); } catch (error) { if (Date.now() - startTime > timeout) { throw new Error(`Timeout after ${timeout}ms: ${error}`); } await this.sleep(delay); } } throw new Error(`Condition not met after ${maxAttempts} attempts`); } static async retryElementAction( fn: () => Promise, options: { maxAttempts?: number; delay?: number; ignoreErrors?: string[]; } = {} ): Promise { const { maxAttempts = 3, delay = 1000, ignoreErrors = ['Timeout', 'Element not found', 'Element not visible'] } = options; return this.retry(fn, { maxAttempts, delay, backoff: true, onRetry: (attempt, error) => { const shouldIgnore = ignoreErrors.some(ignoredError => error.message.includes(ignoredError) ); if (shouldIgnore) { console.log(`Attempt ${attempt} failed with ignorable error: ${error.message}`); } } }); } static async retryNetworkRequest( fn: () => Promise, options: { maxAttempts?: number; delay?: number; retryableStatuses?: number[]; } = {} ): Promise { const { maxAttempts = 3, delay = 2000, retryableStatuses = [408, 429, 500, 502, 503, 504] } = options; return this.retry(fn, { maxAttempts, delay, backoff: true, onRetry: (attempt, error) => { console.log(`Network request attempt ${attempt} failed: ${error.message}`); } }); } static async retryClick( clickFn: () => Promise, options: { maxAttempts?: number; delay?: number; } = {} ): Promise { const { maxAttempts = 3, delay = 500 } = options; return this.retry(clickFn, { maxAttempts, delay, backoff: false, onRetry: (attempt, error) => { console.log(`Click attempt ${attempt} failed: ${error.message}`); } }); } static async retryFill( fillFn: () => Promise, options: { maxAttempts?: number; delay?: number; } = {} ): Promise { const { maxAttempts = 3, delay = 500 } = options; return this.retry(fillFn, { maxAttempts, delay, backoff: false, onRetry: (attempt, error) => { console.log(`Fill attempt ${attempt} failed: ${error.message}`); } }); } static async retryNavigation( navigateFn: () => Promise, options: { maxAttempts?: number; delay?: number; } = {} ): Promise { const { maxAttempts = 3, delay = 1000 } = options; return this.retry(navigateFn, { maxAttempts, delay, backoff: true, onRetry: (attempt, error) => { console.log(`Navigation attempt ${attempt} failed: ${error.message}`); } }); } static async retryAssertion( assertionFn: () => Promise, options: { maxAttempts?: number; delay?: number; } = {} ): Promise { const { maxAttempts = 5, delay = 500 } = options; return this.retry(assertionFn, { maxAttempts, delay, backoff: false, onRetry: (attempt, error) => { console.log(`Assertion attempt ${attempt} failed: ${error.message}`); } }); } private static sleep(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); } static createRetryPolicy( fn: () => Promise, policy: { maxAttempts: number; initialDelay: number; maxDelay?: number; backoffMultiplier?: number; retryCondition?: (error: Error) => boolean; } ): () => Promise { const { maxAttempts, initialDelay, maxDelay = 30000, backoffMultiplier = 2, retryCondition } = policy; return async () => { let currentDelay = initialDelay; let lastError: Error | undefined; for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { return await fn(); } catch (error) { lastError = error as Error; if (retryCondition && !retryCondition(lastError)) { throw lastError; } if (attempt === maxAttempts) { throw lastError; } console.log(`Attempt ${attempt}/${maxAttempts} failed: ${lastError.message}`); await this.sleep(currentDelay); currentDelay = Math.min(currentDelay * backoffMultiplier, maxDelay); } } throw lastError!; }; } static async retryWithTimeout( fn: () => Promise, timeout: number, options: { maxAttempts?: number; delay?: number; } = {} ): Promise { const { maxAttempts = 3, delay = 1000 } = options; return Promise.race([ this.retry(fn, { maxAttempts, delay }), new Promise((_, reject) => setTimeout(() => reject(new Error(`Operation timed out after ${timeout}ms`)), timeout) ) ]); } }