DigitalConsumer.ISS/playwright-tests/impl/utils/TryUtils.ts
chase-safelite 5d3ab0ef59
Added playwright tests into repo (#923)
* Initial import of playwright tests

* Pipeline changes for automated tests

* Modified pipeline for testing

* Attempt #2

* Attempt #3

* Added missing paren

* Removed debug stuff from pipeline

* Changes from playwright repo

* Changed pipeline for debugging

* Fix for ServiceLocationPage playwright locators

* Change pipeline to run with TEST APIs

* Moved more of Siraj's changes to this repo

* Changed where updating env occurs

* Changed location of env update again

* Escaped double quotes

* Added visible report in Azure

* Moved changes into main pipeline

* Made it so dotenv only runs config in local
2025-02-14 09:54:11 -05:00

132 lines
No EOL
5.9 KiB
TypeScript

import LoggingUtils from "./LoggingUtils";
import { delay, timeoutOptDefaults } from "./TimingUtils";
/**
* @description For situations where a Test Framework Exception (i.e., a locator timeout) could be incorrectly thrown based on the state of the Target-Application (i.e., a missing tail number). Use this to throw clearer 'Application Exception' errors under such circumstances.
* @param actionVerb What are you trying to do? Could be 'gotoCatalog', 'addAnEnhancement', etc. Logged as `Attempting to ${actionVerb}`
* @param failureExplenation Be descriptive. What is the context of the failure? If someone unfamiliar with the code base were to see this, how would they know if the error was caused by their code, or by an underlying problem with the Target Application?
* @param actionToAttempt a lambda for the flaky action.
* @returns
*/
export async function tryBusinessAction(actionVerb: string, failureExplenation: string, actionToAttempt: (...args: any[]) => Promise<any>): Promise<any> {
let actionResult: any;
console.log(`Attempting to ${actionVerb}...`)
try {
actionResult = await actionToAttempt();
} catch (e) {
if (e instanceof Error) {
e.message = e.message + ">>Failure Explenation>> " + failureExplenation;
throw (e);
} else {
throw (new Error("Unknown 'AttemptBusinessAction' State..."))
}
}
console.log(`Successfully executed ${actionVerb}`)
return actionResult;
}
/**
* @description Brute-Force Flaky GUI activities by reseting and retrying.
* @param actionToTry lambda for whatever flaky action you're trying to take
* @param resetAction lambda for backing out of the problem and returning to a known state. Often, refreshing a browser, or closing a popup.
* @param maxRetries number of times to retry the action
* @param delayBetweenRetries milliseconds between retries
*/
export async function tryResetAndRetry(
actionVerb: string,
actionToTry: (...args: any[]) => Promise<any>,
resetAction: (...args: any[]) => Promise<any>,
maxRetries = 2,
delayBetweenRetries = timeoutOptDefaults.timeoutMedium): Promise<any> {
for (let i = 1; i <= maxRetries; i++) {
try {
await actionToTry()
} catch {
await delay(delayBetweenRetries);
resetAction();
}
}
}
/**
* @description For situations where a Test Framework Exception (i.e., a locator timeout) could be incorrectly thrown based on the state of the Target-Application (i.e., a missing tail number). Use this to throw clearer 'Application Exception' errors under such circumstances.
* @param actionVerb What are you trying to do? Could be 'gotoCatalog', 'addAnEnhancement', etc. Logged as `Attempting to ${actionVerb}`
* @param failureExplenation Be descriptive. What is the context of the failure? If someone unfamiliar with the code base were to see this, how would they know if the error was caused by their code, or by an underlying problem with the Target Application?
* @param actionToAttempt a lambda for the flaky action.
* @returns
*/
export async function tryBusinessActionWithRetries(actionVerb: string, failureExplenation: string, actionToAttempt: (...args: any[]) => Promise<any>, attempts = 3): Promise<any> {
let actionResult: any;
let isSuccessful: boolean = false;
//actionVerb is already a logFunc string
console.log(actionVerb);
for (let i = 0; i < attempts; i++) {
try {
actionResult = await actionToAttempt();
isSuccessful = true;
break; // Break loop if actionToAttempt is successful
} catch (e) {
if (e instanceof Error) {
e.message = e.message + ">>Failure Explanation>> " + failureExplenation;
} else {
throw (new Error("Unknown 'AttemptBusinessAction' State"))
}
}
}
if (!isSuccessful) {
throw new Error(`Failed to ${actionVerb}`);
}
//actionVerb is already a logFunc string
console.log(actionVerb);
return actionResult;
}
/**
* Tries to execute a block of code with chances to retry.
* @param {Function} block The block of code to be executed.
* @param {string} [blockDescription=''] A text description of the block (optional).
* @param {number} [maxRetries=3] The maximum number of retry attempts (optional).
* @param {number} [delayMs=1000] The delay between retry attempts in milliseconds (optional).
*/
export async function retry<T>(block: () => Promise<T>, blockDescription: string = '', maxRetries: number = 3, delayMs: number = 1000): Promise<T> {
let retries: number = 0;
if (!blockDescription.length) {
blockDescription = block.toString();
}
while (retries < maxRetries) {
console.log(LoggingUtils.logFunc(retry.name, blockDescription));
try {
return await block();
} catch (error) {
if (retries === maxRetries - 1) {
throw new Error(`Max retries (${maxRetries}) exceeded. Last error: ${error}`);
}
// wait between retries
await new Promise(resolve => setTimeout(resolve, delayMs));
retries++;
}
}
// This should not be reached, but just in case
throw new Error(`Unexpected code execution. Max retries (${maxRetries}) exceeded.`);
}
export async function tryWithRetries(actionBlock: Function, attempts = 3, waitInterval = 1000) {
for (let attempt = 1; attempt <= attempts; attempt++) {
try {
await actionBlock();
if (attempt > 1)
console.warn(`Had to retry but attempt ${attempt} succeeded!`);
break; // Exit the loop if the action is successful
} catch (error) {
console.error(`Attempt ${attempt} failed!`);
if (attempt < attempts) {
await new Promise(resolve => setTimeout(resolve, waitInterval));
}
}
}
}