docs: add test report and database reset scripts
- Add comprehensive test report (TEST_REPORT.md) - Add database reset scripts for testing - Update .gitignore to exclude temporary files - Add frontend e2e test utilities and configuration
This commit is contained in:
@@ -0,0 +1,288 @@
|
||||
export class RetryHelper {
|
||||
static async retry<T>(
|
||||
fn: () => Promise<T>,
|
||||
options: {
|
||||
maxAttempts?: number;
|
||||
delay?: number;
|
||||
backoff?: boolean;
|
||||
onRetry?: (attempt: number, error: Error) => void;
|
||||
} = {}
|
||||
): Promise<T> {
|
||||
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<T>(
|
||||
fn: () => Promise<T>,
|
||||
condition: (result: T) => boolean,
|
||||
options: {
|
||||
maxAttempts?: number;
|
||||
delay?: number;
|
||||
timeout?: number;
|
||||
onRetry?: (attempt: number, lastResult: T) => void;
|
||||
} = {}
|
||||
): Promise<T> {
|
||||
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<T>(
|
||||
fn: () => Promise<T>,
|
||||
options: {
|
||||
maxAttempts?: number;
|
||||
delay?: number;
|
||||
ignoreErrors?: string[];
|
||||
} = {}
|
||||
): Promise<T> {
|
||||
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<T>(
|
||||
fn: () => Promise<T>,
|
||||
options: {
|
||||
maxAttempts?: number;
|
||||
delay?: number;
|
||||
retryableStatuses?: number[];
|
||||
} = {}
|
||||
): Promise<T> {
|
||||
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<void>,
|
||||
options: {
|
||||
maxAttempts?: number;
|
||||
delay?: number;
|
||||
} = {}
|
||||
): Promise<void> {
|
||||
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<void>,
|
||||
options: {
|
||||
maxAttempts?: number;
|
||||
delay?: number;
|
||||
} = {}
|
||||
): Promise<void> {
|
||||
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<void>,
|
||||
options: {
|
||||
maxAttempts?: number;
|
||||
delay?: number;
|
||||
} = {}
|
||||
): Promise<void> {
|
||||
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<T>(
|
||||
assertionFn: () => Promise<T>,
|
||||
options: {
|
||||
maxAttempts?: number;
|
||||
delay?: number;
|
||||
} = {}
|
||||
): Promise<T> {
|
||||
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<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
static createRetryPolicy<T>(
|
||||
fn: () => Promise<T>,
|
||||
policy: {
|
||||
maxAttempts: number;
|
||||
initialDelay: number;
|
||||
maxDelay?: number;
|
||||
backoffMultiplier?: number;
|
||||
retryCondition?: (error: Error) => boolean;
|
||||
}
|
||||
): () => Promise<T> {
|
||||
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<T>(
|
||||
fn: () => Promise<T>,
|
||||
timeout: number,
|
||||
options: {
|
||||
maxAttempts?: number;
|
||||
delay?: number;
|
||||
} = {}
|
||||
): Promise<T> {
|
||||
const { maxAttempts = 3, delay = 1000 } = options;
|
||||
|
||||
return Promise.race([
|
||||
this.retry(fn, { maxAttempts, delay }),
|
||||
new Promise<T>((_, reject) =>
|
||||
setTimeout(() => reject(new Error(`Operation timed out after ${timeout}ms`)), timeout)
|
||||
)
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user