Implemented playwright core
This commit is contained in:
parent
20fb39ed48
commit
626dc854e7
108 changed files with 587 additions and 3653 deletions
|
|
@ -1,48 +0,0 @@
|
|||
import { IPaymentDetails } from "@business-logic/types/CustomerDetails";
|
||||
import { PaymentType } from "@business-logic/types/Enums";
|
||||
|
||||
const defaultCreditCardDetails: IPaymentDetails = {
|
||||
paymentType: PaymentType.Credit,
|
||||
cardNumber: '4111111111111111',
|
||||
expirationMonth: '12 - December',
|
||||
expirationYear: '2029',
|
||||
cvv: '555',
|
||||
billingAddress: {
|
||||
street: '2088 Tuller St',
|
||||
city: 'Columbus',
|
||||
state: 'OH',
|
||||
postalCode: '43028',
|
||||
country: 'US'
|
||||
}
|
||||
}
|
||||
|
||||
const defaultAfterpayDetails: IPaymentDetails = {
|
||||
paymentType: PaymentType.AfterPay,
|
||||
username: 'itqatest@safelite.com',
|
||||
password: 'Safelite1',
|
||||
cardNumber: '4111 1111 1111 1111',
|
||||
expirationMonth: '12',
|
||||
expirationYear: '34',
|
||||
cvv: '000'
|
||||
}
|
||||
|
||||
const defaultPaypalDetails: IPaymentDetails = {
|
||||
paymentType: PaymentType.Paypal,
|
||||
username: 'itqatest@safelite.com',
|
||||
password: 'Safelite1'
|
||||
}
|
||||
|
||||
export default class PaymentData {
|
||||
|
||||
static getDefaultCreditCardDetails() {
|
||||
return defaultCreditCardDetails;
|
||||
}
|
||||
|
||||
static getDefaultAfterpayDetails() {
|
||||
return defaultAfterpayDetails;
|
||||
}
|
||||
|
||||
static getDefaultPaypalDetails() {
|
||||
return defaultPaypalDetails;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
// DefaultTestData.ts
|
||||
import { faker } from "@faker-js/faker";
|
||||
import { PaymentMethod, ServicePackage, VehicleLookupType, AppointmentType, PaymentType, DamageType, VehicleDamage } from "@business-logic/types/Enums";
|
||||
import { ITestData } from "@business-logic/types/ITestData";
|
||||
import { getNextWeekday } from "@impl/utils/DateUtils";
|
||||
import { ICustomerDetails, IVehicleDetails, IAppointmentDetails, IPaymentDetails, IClaimDetails } from "@business-logic/types/CustomerDetails";
|
||||
|
||||
// Set a default seed for consistent data generation
|
||||
const DEFAULT_SEED = 1234;
|
||||
faker.seed(DEFAULT_SEED);
|
||||
|
||||
// Function to reset faker to the default seed
|
||||
export function resetFakerToDefaultSeed() {
|
||||
faker.seed(DEFAULT_SEED);
|
||||
}
|
||||
|
||||
// Simple hash function to convert a string to a numeric value
|
||||
function hashStringToNumber(str: string): number {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charCodeAt(i);
|
||||
hash = (hash << 5) - hash + char;
|
||||
hash = hash & hash; // Convert to 32bit integer
|
||||
}
|
||||
return Math.abs(hash);
|
||||
}
|
||||
|
||||
// Function to set a custom seed based on test name
|
||||
export function setFakerSeedFromTestName(testName: string) {
|
||||
const seed = hashStringToNumber(testName);
|
||||
faker.seed(seed);
|
||||
}
|
||||
|
||||
// Functions to generate data (rather than using pre-generated data)
|
||||
export function getCustomerDetails(): ICustomerDetails {
|
||||
return {
|
||||
firstName: faker.person.firstName(),
|
||||
lastName: faker.person.lastName(),
|
||||
email: "itqatest@safelite.com",
|
||||
phoneNumber: '614-254-4109',
|
||||
notes: 'Automated Test',
|
||||
address: {
|
||||
street: faker.location.streetAddress(),
|
||||
city: 'Columbus',
|
||||
state: 'Ohio',
|
||||
postalCode: '21237',
|
||||
country: 'United States'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function getVehicleDetails(): IVehicleDetails {
|
||||
return {
|
||||
year: '2020',
|
||||
make: 'Honda',
|
||||
model: 'Accord',
|
||||
style: '4 door sedan',
|
||||
vehicleLookupType: VehicleLookupType.Zip
|
||||
};
|
||||
}
|
||||
|
||||
export function getAppointmentDetails(): IAppointmentDetails {
|
||||
return {
|
||||
serviceLocation: AppointmentType.InShop,
|
||||
appointmentDate: getNextWeekday()
|
||||
};
|
||||
}
|
||||
|
||||
export function getPaymentDetails(): IPaymentDetails {
|
||||
return {
|
||||
paymentType: PaymentType.PayAtService
|
||||
};
|
||||
}
|
||||
|
||||
export function getClaimDetails(): IClaimDetails {
|
||||
return {
|
||||
client: 'ACUITY INSURANCE',
|
||||
policyNumber: 'Mock' + faker.string.alphanumeric(6).toUpperCase(),
|
||||
policyDeductible: 0,
|
||||
damageDate: new Date(new Date().setDate(new Date().getDate() - 1)).toLocaleDateString('en-US', {month: '2-digit', day: '2-digit', year: 'numeric'}),
|
||||
damageCause: DamageType.Hail
|
||||
};
|
||||
}
|
||||
|
||||
// Function to get the entire default test data set with current faker state
|
||||
export function getDefaultTestData(): Partial<ITestData> {
|
||||
return {
|
||||
paymentMethod: PaymentMethod.SelfPay,
|
||||
servicePackage: ServicePackage.GlassOnly,
|
||||
customerDetails: getCustomerDetails(),
|
||||
vehicleDetails: getVehicleDetails(),
|
||||
appointmentDetails: getAppointmentDetails(),
|
||||
paymentDetails: getPaymentDetails(),
|
||||
claimDetails: getClaimDetails(),
|
||||
vehicleDamage: [VehicleDamage.WindshieldCrack],
|
||||
isDuplicateClaim: false,
|
||||
isPolicyDriver: false,
|
||||
isPolicyFound: false,
|
||||
isUseVehicleOnPolicy: true,
|
||||
isRecalNotification: false,
|
||||
hasOemEndorsement: false,
|
||||
skipEstimatePage: false,
|
||||
isRecalVehicle: false,
|
||||
enterFunnelWithZip: false
|
||||
};
|
||||
}
|
||||
|
||||
// Keep the pre-generated data for backward compatibility
|
||||
export const defaultCustomerDetails = getCustomerDetails();
|
||||
export const defaultVehicleDetails = getVehicleDetails();
|
||||
export const defaultAppointmentDetails = getAppointmentDetails();
|
||||
export const defaultPaymentDetails = getPaymentDetails();
|
||||
export const defaultClaimDetails = getClaimDetails();
|
||||
export const defaultTestData = getDefaultTestData();
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
/**
|
||||
* Mapping of state names to their two-letter abbreviations
|
||||
*/
|
||||
export const STATE_ABBREVIATIONS: Record<string, string> = {
|
||||
'Alabama': 'AL',
|
||||
'Alaska': 'AK',
|
||||
'Arizona': 'AZ',
|
||||
'Arkansas': 'AR',
|
||||
'California': 'CA',
|
||||
'Colorado': 'CO',
|
||||
'Connecticut': 'CT',
|
||||
'Delaware': 'DE',
|
||||
'District of Columbia': 'DC',
|
||||
'Florida': 'FL',
|
||||
'Georgia': 'GA',
|
||||
'Hawaii': 'HI',
|
||||
'Idaho': 'ID',
|
||||
'Illinois': 'IL',
|
||||
'Indiana': 'IN',
|
||||
'Iowa': 'IA',
|
||||
'Kansas': 'KS',
|
||||
'Kentucky': 'KY',
|
||||
'Louisiana': 'LA',
|
||||
'Maine': 'ME',
|
||||
'Maryland': 'MD',
|
||||
'Massachusetts': 'MA',
|
||||
'Michigan': 'MI',
|
||||
'Minnesota': 'MN',
|
||||
'Mississippi': 'MS',
|
||||
'Missouri': 'MO',
|
||||
'Montana': 'MT',
|
||||
'Nebraska': 'NE',
|
||||
'Nevada': 'NV',
|
||||
'New Hampshire': 'NH',
|
||||
'New Jersey': 'NJ',
|
||||
'New Mexico': 'NM',
|
||||
'New York': 'NY',
|
||||
'North Carolina': 'NC',
|
||||
'North Dakota': 'ND',
|
||||
'Ohio': 'OH',
|
||||
'Oklahoma': 'OK',
|
||||
'Oregon': 'OR',
|
||||
'Pennsylvania': 'PA',
|
||||
'Rhode Island': 'RI',
|
||||
'South Carolina': 'SC',
|
||||
'South Dakota': 'SD',
|
||||
'Tennessee': 'TN',
|
||||
'Texas': 'TX',
|
||||
'Utah': 'UT',
|
||||
'Vermont': 'VT',
|
||||
'Virginia': 'VA',
|
||||
'Washington': 'WA',
|
||||
'West Virginia': 'WV',
|
||||
'Wisconsin': 'WI',
|
||||
'Wyoming': 'WY',
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the two-letter abbreviation for a state name
|
||||
* @param stateName Full state name
|
||||
* @returns Two-letter abbreviation
|
||||
* @throws Error if state name is not found
|
||||
*/
|
||||
export function getStateAbbreviation(stateName: string): string {
|
||||
const abbreviation = STATE_ABBREVIATIONS[stateName];
|
||||
if (!abbreviation) {
|
||||
throw new Error(`State "${stateName}" not found in mapping`);
|
||||
}
|
||||
return abbreviation;
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
import TestCase from "@business-logic/types/TestCase";
|
||||
import { Rule } from "../types/RuleEngine";
|
||||
import EnumUtils from "../../impl/utils/EnumUtils";
|
||||
|
||||
// File containing Rule Engine Builtin Rules for JSON Data Validation
|
||||
|
||||
export enum BuiltInRules {
|
||||
//Custom Rules
|
||||
}
|
||||
|
||||
// Built-Ins shouldn't depend on other rules, custom rules however are supposed to depend on them
|
||||
export const builtInRules: Rule<TestCase>[] =
|
||||
[
|
||||
//{
|
||||
// id: BuiltInRules.TransactionExists,
|
||||
// name: "Transaction Exists Rule",
|
||||
// check: (testCase: TestCase) => {
|
||||
// return testCase.transaction != null;
|
||||
// }
|
||||
// }, {
|
||||
// id: BuiltInRules.PricingExists,
|
||||
// name: "Pricing must exist on Transaction Rule",
|
||||
// check: (testCase: TestCase) => {
|
||||
// return testCase.transaction != null && testCase.transaction.pricing != null;
|
||||
// }
|
||||
// }, {
|
||||
// id: BuiltInRules.TerminationIsMod,
|
||||
// name: "OpportunityType Termination requires TransactionSubType Mod",
|
||||
// check: (testCase: TestCase) => {
|
||||
|
||||
// // Check old transactions
|
||||
// for (var transaction of testCase.oldTransactions) {
|
||||
// if (transaction.opportunityType == OpportunityTypes.Termination)
|
||||
// return transaction.subType == TransactionSubTypes.Mod;
|
||||
// }
|
||||
|
||||
// // Check transaction
|
||||
// if (testCase.transaction.opportunityType == OpportunityTypes.Termination)
|
||||
// return testCase.transaction.subType == TransactionSubTypes.Mod;
|
||||
// return true;
|
||||
// },
|
||||
// dependsOn: [BuiltInRules.TransactionExists]
|
||||
// }, {
|
||||
];
|
||||
|
|
@ -1 +0,0 @@
|
|||
// File for interfaces related to authentication
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
import { DamageType as DamageCause, WindshieldDamage, ServiceLocation, EndorsementType, VehicleLookupType, PartQuestionType, PaymentType, AppointmentType, AppointmentTimeslot } from "./Enums";
|
||||
import { IAddress } from "./IAddress";
|
||||
|
||||
export interface ICustomerDetails {
|
||||
firstName: string,
|
||||
lastName: string,
|
||||
email: string,
|
||||
phoneNumber: string,
|
||||
notes: string,
|
||||
address: IAddress,
|
||||
apptDate?: string,
|
||||
apptTime?: string,
|
||||
apptDuration?: string,
|
||||
packagePrice?: string
|
||||
}
|
||||
|
||||
export interface IVehicleDetails {
|
||||
year: string,
|
||||
make: string,
|
||||
model: string,
|
||||
style?: string,
|
||||
vin?: string,
|
||||
licensePlateNumber?: string,
|
||||
licensePlateState?: string,
|
||||
vehicleLookupType: VehicleLookupType,
|
||||
}
|
||||
|
||||
export interface IAppointmentDetails {
|
||||
serviceLocation: AppointmentType,
|
||||
appointmentDate?: Date,
|
||||
shopAddress?: string, // Used for in-shop
|
||||
serviceAddress?: IAddress, // Used for mobile
|
||||
isVehicleProtected?: boolean,
|
||||
appointmentTimeSlot?: AppointmentTimeslot // Used for mobile
|
||||
}
|
||||
|
||||
export interface IEndorsementDetails {
|
||||
endorsementType: EndorsementType,
|
||||
isOnPolicy: boolean, // Should we expect this endorsement to appear?
|
||||
isClickYes: boolean // Should we click Yes or No?
|
||||
}
|
||||
|
||||
export interface IPartQuestion {
|
||||
isOnPage: boolean,
|
||||
optionToSelect: string,
|
||||
partQuestionType: PartQuestionType,
|
||||
secondaryQuestionOptionToSelect?: string
|
||||
}
|
||||
|
||||
export interface IPaymentDetails {
|
||||
paymentType?: PaymentType,
|
||||
username?: string,
|
||||
password?: string,
|
||||
cardNumber?: string,
|
||||
expirationMonth?: string,
|
||||
expirationYear?: string,
|
||||
cvv?: string,
|
||||
billingAddress?: IAddress,
|
||||
}
|
||||
|
||||
export interface IClaimDetails {
|
||||
client: string,
|
||||
policyNumber: string,
|
||||
policyDeductible: number,
|
||||
policyZip?: string
|
||||
damageDate: string,
|
||||
damageCause: DamageCause
|
||||
}
|
||||
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
export interface IPartsOrQuestionsResponse {
|
||||
partsOrQuestions: IPartOrQuestion[]
|
||||
}
|
||||
|
||||
export interface IPartOrQuestion {
|
||||
glassPiece: IGlassPiece,
|
||||
parts: IPart[],
|
||||
partQuestions: IPartQuestion[] | null;
|
||||
}
|
||||
|
||||
export interface IGlassPiece {
|
||||
name: string,
|
||||
location: string
|
||||
}
|
||||
|
||||
export interface IPart {
|
||||
childPartQuestions: IPartQuestion[],
|
||||
basePartNumber: string,
|
||||
safelitePartNumber: string,
|
||||
color: string,
|
||||
requiresRecalibration: boolean,
|
||||
recalibrationType: null, // TODO: Add types
|
||||
canSafeliteRecalibrate: boolean,
|
||||
requiresCapabilityQuestions: boolean,
|
||||
childParts: IChildPart[],
|
||||
partNumber: string,
|
||||
description: string,
|
||||
partType: string
|
||||
}
|
||||
|
||||
export interface IPartQuestion {
|
||||
// TODO: Define
|
||||
}
|
||||
|
||||
export interface IChildPart {
|
||||
partNumber: string,
|
||||
safelitePartNumber: string
|
||||
}
|
||||
|
|
@ -1,155 +0,0 @@
|
|||
// Enums File
|
||||
|
||||
export enum ConsoleColor {
|
||||
Default = "",
|
||||
Red = "\x1b[31m",
|
||||
Green = "\x1b[32m",
|
||||
Yellow = "\x1b[33m",
|
||||
Orange = "\x1b[202m",
|
||||
Blue = "\x1b[34m",
|
||||
Magenta = "\x1b[35m",
|
||||
Cyan = "\x1b[36m",
|
||||
Reset = "\x1b[0m"
|
||||
}
|
||||
|
||||
export enum ResultTypes {
|
||||
None = 0,
|
||||
Skipped = 1,
|
||||
Success = 2,
|
||||
Failure = 3
|
||||
}
|
||||
|
||||
export enum VehicleLookupType {
|
||||
Vin,
|
||||
Address,
|
||||
LicensePlateNumber,
|
||||
Zip
|
||||
}
|
||||
|
||||
export enum VehicleDamage {
|
||||
WindshieldOneChip = "WINDSHIELD ONE CHIP",
|
||||
WindshieldTwoChips = "WINDSHIELD TWO CHIPS",
|
||||
WindshieldThreeChips = "WINDSHIELD THREE CHIPS",
|
||||
WindshieldCrack = "WINDSHIELD",
|
||||
RearWindow = "BACK GLASS",
|
||||
RearSliding = "SLIDER",
|
||||
DriverFrontDoor = "DRIVER FRONT DOOR GLASS",
|
||||
DriverRearDoor = "DRIVER REAR DOOR GLASS",
|
||||
DriverVentGlass = "DRIVER VENT GLASS",
|
||||
DriverQuarterPanel = "DRIVER REAR QUARTER GLASS",
|
||||
DriverSlidingDoor = "DRIVER SLIDING DOOR GLASS",
|
||||
PassengerFrontDoor = "PASSENGER FRONT DOOR GLASS",
|
||||
PassengerRearDoor = "PASSENGER REAR DOOR GLASS",
|
||||
PassengerVentGlass = "PASSENGER VENT GLASS",
|
||||
PassengerQuarterPanel = "PASSENGER REAR QUARTER GLASS"
|
||||
}
|
||||
|
||||
export enum WindshieldDamage{
|
||||
Crack,
|
||||
OneChip,
|
||||
TwoChips,
|
||||
ThreeChips
|
||||
}
|
||||
|
||||
export enum SideDoorDamage{
|
||||
Passenger = "Passenger",
|
||||
Driver = "Driver"
|
||||
}
|
||||
|
||||
export enum DamageType {
|
||||
Rock = 'Rock from road',
|
||||
Vandalism = 'Vandalism',
|
||||
Theft = 'Attempted theft or theft',
|
||||
Hail = 'Hail storm',
|
||||
HurricaneStorm = 'Hurricane/Storm',
|
||||
OtherWeather = 'Other weather',
|
||||
Collision = 'Collision',
|
||||
Object = 'Object hit glass',
|
||||
Other = 'Other/unknown'
|
||||
}
|
||||
|
||||
export enum ServiceLocation {
|
||||
Mobile,
|
||||
InShop,
|
||||
DropOff
|
||||
}
|
||||
|
||||
export enum ServicePackage {
|
||||
GlassOnly = 'Glass service only',
|
||||
Standard = 'Standard',
|
||||
Premium = 'Premium'
|
||||
}
|
||||
|
||||
export enum EndorsementType {
|
||||
Educator = '01',
|
||||
EmployeeParking = '03'
|
||||
}
|
||||
|
||||
export enum PartQuestionType {
|
||||
WindshieldColor = 'Windshield-Single',
|
||||
DriverFrontColor = 'Driver-Front',
|
||||
DriverQuarterColor = 'Driver-Quarter',
|
||||
DriverRearColor = 'Driver-Back',
|
||||
DriverVentColor = 'Driver-Vent',
|
||||
PassengerFrontColor = 'Passenger-Front',
|
||||
PassengerQuarterColor = 'Passenger-Quarter',
|
||||
PassengerRearColor = 'Passenger-Back',
|
||||
PassengerVentColor = 'Passenger-Vent',
|
||||
RearWindowColor = 'Rear-Stationary',
|
||||
RearSlidingWindowColor = 'Rear-Slider',
|
||||
DriverSideColor = 'Driver-SideDoor',
|
||||
// use generalized tag for other part questions
|
||||
GeneralQuestion1 = 'question-0-1',
|
||||
GeneralQuestion2 = 'question-0-2',
|
||||
GeneralQuestion3 = 'question-0-3',
|
||||
GeneralQuestion4 = 'question-0-4'
|
||||
}
|
||||
|
||||
export enum PaymentType{
|
||||
Credit = "Credit",
|
||||
AfterPay = "AfterPay",
|
||||
Paypal = "Paypal",
|
||||
PayAtService = "Pay at Service",
|
||||
PayWithInsurance = "Pay with Insurance"
|
||||
}
|
||||
|
||||
export enum PaymentMethod {
|
||||
Insurance = 'Insurance',
|
||||
SelfPay = 'SelfPay'
|
||||
}
|
||||
|
||||
export enum AppointmentType{
|
||||
InShop = "InShop",
|
||||
Mobile = "Mobile",
|
||||
DropOff = "Drop-off"
|
||||
}
|
||||
|
||||
export enum AppointmentTimeslot{
|
||||
EarlyBird = "EarlyBird",
|
||||
DropOff = "DropOff",
|
||||
overnight = "Overnight"
|
||||
}
|
||||
|
||||
export enum ProgressBarPercentages {
|
||||
VehicleSelectionPage = '4%',
|
||||
VehicleDamagePage = '16%',
|
||||
EstimatePage = '28%',
|
||||
ServiceZipPage = '32%',
|
||||
VehicleLookupAddressPage = '32%',
|
||||
VehicleLookupLicensePage = '32%',
|
||||
VinLookupPage = '32%',
|
||||
PartQuestionsPage = '40%',
|
||||
MoldingQuestionsPage = '40%',
|
||||
CapabilityQuestionsPage = '40%',
|
||||
VehiclePartsPage = '40%',
|
||||
ServicePackagePage = '48%',
|
||||
InsuranceCompanyPage = '52%',
|
||||
ServiceLocationPage = '60%',
|
||||
SchedulePage = '72%',
|
||||
ContactDetailsPage = '84%',
|
||||
PaymentMethodPage = '92%',
|
||||
OrderConfirmationPage = '100%'
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
// File for Framework Config
|
||||
|
||||
type FrameworkConfig = {
|
||||
createResources: boolean;
|
||||
destroyResources: boolean;
|
||||
maxAllotmentHours: number;
|
||||
};
|
||||
|
||||
export default FrameworkConfig;
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
// File with address interface
|
||||
|
||||
export interface IAddress {
|
||||
street: string,
|
||||
city: string,
|
||||
state: string,
|
||||
postalCode: string,
|
||||
country: string
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
export default interface IAlertFlags {
|
||||
isHeavyTruckVehicle?: boolean,
|
||||
isRepairReplace?: boolean,
|
||||
isSplitWindshield?: boolean,
|
||||
isRepairOnly?: boolean,
|
||||
isUnserviceableZip?: boolean,
|
||||
isInvalidZip?: boolean,
|
||||
isVinNotFound?: boolean
|
||||
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
import LoggingUtils from "@impl/utils/LoggingUtils";
|
||||
import { ConsoleColor } from "@business-logic/types/Enums";
|
||||
import TestCase from "@business-logic/types/TestCase";
|
||||
|
||||
// File with interfaces/classes related to Disposable
|
||||
|
||||
export interface IDisposable {
|
||||
disposeAll(): void;
|
||||
setupAll(): void;
|
||||
}
|
||||
|
||||
export abstract class DisposableBase implements IDisposable {
|
||||
protected abstract setup(): Promise<void>;
|
||||
protected abstract dispose(): Promise<void>;
|
||||
|
||||
public async setupAll(): Promise<void> {
|
||||
if (!TestCase.FrameworkConfig.destroyResources) {
|
||||
LoggingUtils.log(TestCase.Constants.CREATION_HALTED, ConsoleColor.Yellow);
|
||||
return;
|
||||
}
|
||||
await this.setup();
|
||||
}
|
||||
|
||||
public async disposeAll(): Promise<void> {
|
||||
if (!TestCase.FrameworkConfig.destroyResources || !TestCase.FrameworkConfig.createResources) {
|
||||
LoggingUtils.log(TestCase.Constants.DISPOSE_HALTED, ConsoleColor.Yellow);
|
||||
return;
|
||||
}
|
||||
await this.dispose();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
import ITestPages from "@business-logic/types/ITestPages";
|
||||
import Validations from "./Validations";
|
||||
import { ITestData } from "./ITestData";
|
||||
|
||||
// File containing Test Case Interface
|
||||
|
||||
export default interface ITestCase {
|
||||
readonly testID?: string;
|
||||
readonly name: string;
|
||||
readonly tags: string[];
|
||||
|
||||
readonly validations?: Validations;
|
||||
readonly tempData?: any[]
|
||||
|
||||
readonly testData: Partial<ITestData>;
|
||||
|
||||
pages?: ITestPages;
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
// File for the Test Data Structure
|
||||
|
||||
import { IAppointmentDetails, IClaimDetails, ICustomerDetails, IPaymentDetails, IVehicleDetails, IPartQuestion, IEndorsementDetails } from "./CustomerDetails";
|
||||
import { PaymentMethod, ServicePackage, VehicleDamage } from "./Enums";
|
||||
import IAlertFlags from "./IAlertFlags";
|
||||
|
||||
export interface ITestData {
|
||||
|
||||
paymentMethod: PaymentMethod
|
||||
servicePackage: ServicePackage,
|
||||
customerDetails: ICustomerDetails,
|
||||
vehicleDetails: IVehicleDetails,
|
||||
vehicleDamage: VehicleDamage[],
|
||||
appointmentDetails: IAppointmentDetails,
|
||||
paymentDetails: IPaymentDetails,
|
||||
claimDetails: IClaimDetails,
|
||||
alertFlags: IAlertFlags,
|
||||
partQuestions: IPartQuestion[], // part-questions page has unrelated part questions like leather seats
|
||||
vehiclePartQuestions: IPartQuestion[], // vehicle-parts page usually has glass question
|
||||
capabilityQuestions: IPartQuestion[], // Capability questions page usually has questions about autonomous driving features
|
||||
moldingQuestions: IPartQuestion[],
|
||||
enterFunnelWithZip: boolean,
|
||||
isDuplicateClaim: boolean,
|
||||
isPolicyDriver: boolean, // Does the Policy Driver Page show up for the insurance claim?
|
||||
isPolicyFound: boolean,
|
||||
otherVehiclesOnPolicy: IVehicleDetails[], // IF defined, we validate that the vehicles are present.
|
||||
isUseVehicleOnPolicy: boolean, // Should we use the vehicle on the policy?
|
||||
isRecalNotification: boolean, // Does Recalibration Information page show up?
|
||||
endorsements: IEndorsementDetails[],
|
||||
hasOemEndorsement: boolean, // OEM Endorsement does not appear on endorsements page, so it has a separate flag.s
|
||||
skipEstimatePage: boolean,
|
||||
isRecalVehicle: boolean,
|
||||
canNotRecal: boolean,
|
||||
dynamicRecal: boolean,
|
||||
promoCode: string
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
import { HomePage } from "../../pages/HomePage"
|
||||
import { LeadgenHomePage } from "../../pages/LeadgenHomePage"
|
||||
import { ServicePackagesPage } from "../../pages/ServicePackagesPage"
|
||||
import { VehicleDamagePage } from "../../pages/VehicleDamagePage"
|
||||
import { VehicleLookupAddressPage } from "../../pages/VehicleLookupAddressPage"
|
||||
import { VehicleLookupLicensePage } from "../../pages/VehicleLookupLicensePage"
|
||||
import { EstimatePage } from "../../pages/EstimatePage"
|
||||
import { VehicleSelectionPage } from "../../pages/VehicleSelectionPage"
|
||||
import { VinLookupPage } from "../../pages/VinLookupPage"
|
||||
import { ServiceLocationPage } from "../../pages/ServiceLocationPage"
|
||||
import { SchedulePage } from "../../pages/SchedulePage"
|
||||
import { ContactDetailsPage } from "../../pages/ContactDetailsPage"
|
||||
import { PaymentMethodPage } from "../../pages/PaymentMethodPage"
|
||||
import { ZipLookupPage } from "../../pages/ZipLookupPage"
|
||||
import { OrderConfirmationPage } from "../../pages/OrderConfirmationPage"
|
||||
import { PartQuestionsPage } from "../../pages/PartQuestionPage"
|
||||
import VehiclePartQuestionsPage from "../../pages/VehiclePartsPage"
|
||||
import CapabilityQuestionsPage from "../../pages/CapabilityQuestionsPage"
|
||||
import MoldingQuestionsPage from "../../pages/MoldingQuestionsPage"
|
||||
import { InsuranceCompanyPage } from "../../pages/InsuranceCompanyPage"
|
||||
import { CCPolicyInfoPage } from "../../pages/CCPolicyInfoPage"
|
||||
import { DuplicateCheckPage } from "../../pages/DuplicateCheckPage"
|
||||
import { PolicyVehiclesPage } from "../../pages/PolicyVehiclesPage"
|
||||
import { PolicyInfoSubmittedPage } from "../../pages/PolicyInfoSubmittedPage"
|
||||
import RecalibrationInfoPage from "../../pages/RecalibrationInfoPage"
|
||||
import { CoverageStatementPage } from "../../pages/CoverageStatementPage"
|
||||
import { VerifyDetailsPage } from "../../pages/VerifyDetailsPage"
|
||||
import { EndorsementsPage } from "../../pages/EndorsementsPage"
|
||||
import { PolicyDriverPage } from "../../pages/PolicyDriverPage"
|
||||
import { ServiceZipPage } from "../../pages/ServiceZipPage"
|
||||
// File containing interface for all Page Object Models
|
||||
|
||||
export default interface ITestPages {
|
||||
capabilityQuestionsPage: CapabilityQuestionsPage,
|
||||
ccPolicyInfoPage: CCPolicyInfoPage,
|
||||
contactDetailsPage: ContactDetailsPage,
|
||||
coverageStatementPage: CoverageStatementPage,
|
||||
duplicateCheckPage: DuplicateCheckPage,
|
||||
estimatePage: EstimatePage,
|
||||
homePage: HomePage,
|
||||
insuranceCompanyPage: InsuranceCompanyPage,
|
||||
leadgenHomePage: LeadgenHomePage,
|
||||
moldingQuestionsPage: MoldingQuestionsPage,
|
||||
orderConfirmationPage: OrderConfirmationPage,
|
||||
partQuestionsPage: PartQuestionsPage,
|
||||
paymentMethodPage: PaymentMethodPage,
|
||||
policyInfoSubmittedPage: PolicyInfoSubmittedPage,
|
||||
policyVehiclesPage: PolicyVehiclesPage,
|
||||
recalibrationInfoPage: RecalibrationInfoPage,
|
||||
schedulePage: SchedulePage,
|
||||
serviceLocationPage: ServiceLocationPage,
|
||||
servicePackagesPage: ServicePackagesPage,
|
||||
serviceZipPage: ServiceZipPage,
|
||||
vehicleDamagePage: VehicleDamagePage,
|
||||
vehicleLookupAddressPage: VehicleLookupAddressPage,
|
||||
vehicleLookupLicensePage: VehicleLookupLicensePage,
|
||||
vehiclePartsPage: VehiclePartQuestionsPage,
|
||||
vehicleSelectionPage: VehicleSelectionPage,
|
||||
verifyDetailsPage: VerifyDetailsPage,
|
||||
vinLookupPage: VinLookupPage,
|
||||
zipLookupPage: ZipLookupPage,
|
||||
endorsementsPage: EndorsementsPage,
|
||||
policyDriverPage: PolicyDriverPage,
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
// File containing interface for validations
|
||||
|
||||
export default interface IValidations {
|
||||
|
||||
}
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
export interface PutEditIssueRequestBody {
|
||||
fields: Partial<JiraIssueFields>
|
||||
}
|
||||
|
||||
export interface PostTransitionIssueRequestBody {
|
||||
transition: { id: string }
|
||||
}
|
||||
|
||||
export interface PostCreateIssueRequestBody extends JiraIssue {}
|
||||
|
||||
export interface PostCreateIssueResponse {
|
||||
id: string,
|
||||
key: string
|
||||
}
|
||||
|
||||
export interface PostBulkCreateIssueRequestBody {
|
||||
issueUpdates: PostCreateIssueRequestBody[]
|
||||
}
|
||||
|
||||
export interface BulkTransitionInput {
|
||||
selectedIssueIdsOrKeys: string[],
|
||||
transitionId: string
|
||||
}
|
||||
|
||||
export interface PostBulkTransitionIssuesRequestBody {
|
||||
bulkTransitionInputs: BulkTransitionInput[],
|
||||
sendBulkNotification: false
|
||||
}
|
||||
|
||||
export interface PostBulkCreateIssueResponse {
|
||||
issues: PostCreateIssueResponse[]
|
||||
}
|
||||
|
||||
export interface PostAddCommentResponse {
|
||||
body: JiraContent
|
||||
}
|
||||
|
||||
export interface JiraIssueFields {
|
||||
summary: string,
|
||||
description: JiraContent,
|
||||
project?: JiraProject,
|
||||
issuetype?: { id: string },
|
||||
parent?: JiraParent,
|
||||
fixVersions?: JiraVersion[],
|
||||
subtasks?: JiraSubTask[],
|
||||
customfield_14857?: JiraContent, // Test Steps field
|
||||
customfield_10007?: number // Sprint field,
|
||||
customfield_13100?: { id: string } // UAT Tester ID field
|
||||
|
||||
}
|
||||
|
||||
export interface JiraSubTask {
|
||||
id: string,
|
||||
key: string
|
||||
}
|
||||
|
||||
export interface JiraProject {
|
||||
key: string
|
||||
}
|
||||
|
||||
export interface JiraParent {
|
||||
key: string
|
||||
}
|
||||
|
||||
export interface JiraContent {
|
||||
type?: string,
|
||||
text?: string,
|
||||
version?: number
|
||||
content?: JiraContent[]
|
||||
}
|
||||
|
||||
export interface JiraVersion {
|
||||
id: string
|
||||
}
|
||||
|
||||
export interface JiraTransition {
|
||||
id: string,
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface JiraIssueType {
|
||||
id: string,
|
||||
self: string,
|
||||
description: string,
|
||||
iconUrl: string,
|
||||
name: string,
|
||||
untranslatedName: string,
|
||||
subtask: boolean,
|
||||
}
|
||||
|
||||
export interface GetIssueTransitionsResponse {
|
||||
transitions: JiraTransition[]
|
||||
}
|
||||
|
||||
export interface GetIssueTypesResponse {
|
||||
issueTypes: JiraIssueType[]
|
||||
}
|
||||
|
||||
export interface PostBulkFetchIssuesRequestBody {
|
||||
issueIdsOrKeys: string[]
|
||||
}
|
||||
|
||||
export interface PostBulkFetchIssuesResponse {
|
||||
issues: GetIssueResponse[];
|
||||
}
|
||||
|
||||
export interface JiraIssue {
|
||||
key?: string,
|
||||
id?: string,
|
||||
transition?: { id: string },
|
||||
fields: JiraIssueFields
|
||||
}
|
||||
|
||||
export interface GetIssueResponse extends JiraIssue {}
|
||||
|
||||
export interface GetBoardResponse {
|
||||
id: number,
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface GetSprintResponse {
|
||||
values: JiraSprint[]
|
||||
}
|
||||
|
||||
export interface GetJqlSearchIssueParams {
|
||||
jql: string
|
||||
}
|
||||
|
||||
export interface GetJqlSearchIssueResponse extends PostBulkFetchIssuesResponse {}
|
||||
|
||||
export interface JiraSprint {
|
||||
id: number,
|
||||
name: string,
|
||||
originBoardId: number
|
||||
}
|
||||
|
|
@ -1,243 +0,0 @@
|
|||
import { BuiltInRules, builtInRules } from "@business-logic/rules/RuleEngineBuiltins";
|
||||
import LoggingUtils from "@impl/utils/LoggingUtils";
|
||||
import { ConsoleColor, ResultTypes } from "@business-logic/types/Enums";
|
||||
|
||||
//File Containing Rule Engine Implementation
|
||||
|
||||
export type Rule<T> = {
|
||||
id: number;
|
||||
name: string;
|
||||
check: (obj: T) => boolean;
|
||||
dependsOn?: number[];
|
||||
};
|
||||
|
||||
export class ValidationOptions {
|
||||
public skipBuiltIns: boolean;
|
||||
public exclude: number[];
|
||||
public throwOnError: boolean = true;
|
||||
public errorsOnly: boolean = false;
|
||||
|
||||
constructor(options: {
|
||||
throwOnError?: boolean;
|
||||
errorsOnly?: boolean;
|
||||
skipBuiltIns?: boolean;
|
||||
exclude?: number[];
|
||||
} = {}) {
|
||||
this.throwOnError = options.throwOnError ?? true, this.errorsOnly = options.errorsOnly ?? true, this.skipBuiltIns = options.skipBuiltIns ?? false;
|
||||
this.exclude = options.exclude ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
export class RuleEngine<T> {
|
||||
private rules: Rule<T>[] = [];
|
||||
private nextCustomRuleId = 1;
|
||||
|
||||
static readonly BuiltInRuleIds: Record<BuiltInRules, number> = {} as Record<BuiltInRules, number>;
|
||||
|
||||
constructor() {
|
||||
this.addBuiltInRules(builtInRules as { id: BuiltInRules; name: string; check: (obj: T) => boolean; dependsOn?: BuiltInRules[]; }[]);
|
||||
}
|
||||
|
||||
private addBuiltInRules(builtInRules: { id: BuiltInRules; name: string; check: (obj: T) => boolean; dependsOn?: BuiltInRules[]; }[]): void {
|
||||
builtInRules.forEach(rule => this.addRuleInternal(rule.name, rule.check, rule.id, rule.dependsOn));
|
||||
}
|
||||
|
||||
private addRuleInternal(name: string, check: (obj: T) => boolean, builtInId: BuiltInRules, dependsOn?: BuiltInRules[]): number {
|
||||
const id = builtInId;
|
||||
const dependsOnIds = dependsOn?.map(dep => dep as number);
|
||||
this.rules.push({ id, name, check, dependsOn: dependsOnIds });
|
||||
RuleEngine.BuiltInRuleIds[builtInId] = id;
|
||||
return id;
|
||||
}
|
||||
|
||||
addRule(name: string, check: (obj: T) => boolean, options?: { dependsOn?: (number | BuiltInRules)[]; }): Rule<T> {
|
||||
if (this.nextCustomRuleId >= 1000)
|
||||
throw new Error("Maximum number of custom rules (999) has been reached.");
|
||||
|
||||
const id = this.nextCustomRuleId++;
|
||||
const dependsOn = options?.dependsOn?.map(dep => typeof dep === "number" ? dep : RuleEngine.BuiltInRuleIds[dep]);
|
||||
const rule: Rule<T> = { id, name, check, dependsOn };
|
||||
this.rules.push(rule);
|
||||
return rule;
|
||||
}
|
||||
|
||||
when(condition: (obj: T) => boolean): WhenClause<T> {
|
||||
return new WhenClause(this, condition);
|
||||
}
|
||||
|
||||
checkRuleById(id: number | BuiltInRules, obj: T): boolean {
|
||||
const ruleId = typeof id === "number" ? id : RuleEngine.BuiltInRuleIds[id];
|
||||
const rule = this.rules.find(r => r.id === ruleId);
|
||||
return rule ? rule.check(obj) : true;
|
||||
}
|
||||
|
||||
validate(obj: T): ValidationResult[] {
|
||||
const sortedRules = this.sortRules();
|
||||
const results = new Map<number, boolean>();
|
||||
const resultSummary: ValidationResult[] = [];
|
||||
|
||||
sortedRules.forEach(rule => {
|
||||
const dependenciesValid = rule.dependsOn ? rule.dependsOn.every(dep => results.get(dep)) : true;
|
||||
const result = dependenciesValid && rule.check(obj);
|
||||
results.set(rule.id, result);
|
||||
|
||||
const resultType: ResultTypes = dependenciesValid ? result ? ResultTypes.Success : ResultTypes.Failure : ResultTypes.Skipped;
|
||||
|
||||
resultSummary.push(ValidationResult.FromRule(rule.name, rule.id, resultType, `Rule: "${rule.name}". Result: ${ResultTypes[resultType]}`));
|
||||
});
|
||||
return resultSummary;
|
||||
}
|
||||
|
||||
private sortRules(): Rule<T>[] {
|
||||
const sortedRules: Rule<T>[] = [];
|
||||
const rulesMap = new Map(this.rules.map(rule => [rule.id, rule]));
|
||||
|
||||
const visit = (rule: Rule<T>, visited: Set<number>, stack: Set<number>) => {
|
||||
if (stack.has(rule.id))
|
||||
throw new Error(`Circular dependency detected in rule: ${rule.name}!`);
|
||||
|
||||
if (!visited.has(rule.id)) {
|
||||
stack.add(rule.id);
|
||||
if (rule.dependsOn) {
|
||||
for (const dependency of rule.dependsOn) {
|
||||
const dependencyRule = rulesMap.get(dependency);
|
||||
if (dependencyRule)
|
||||
visit(dependencyRule, visited, stack);
|
||||
}
|
||||
}
|
||||
stack.delete(rule.id);
|
||||
visited.add(rule.id);
|
||||
sortedRules.push(rule);
|
||||
}
|
||||
};
|
||||
|
||||
const visited = new Set<number>();
|
||||
for (const rule of this.rules)
|
||||
visit(rule, visited, new Set<number>());
|
||||
|
||||
return sortedRules;
|
||||
}
|
||||
|
||||
getRulesByName(name: string): Rule<T>[] {
|
||||
return this.rules.filter(rule => rule.name === name);
|
||||
}
|
||||
|
||||
getRulesByID(id: number): Rule<T>[] {
|
||||
return this.rules.filter(rule => rule.id === id);
|
||||
}
|
||||
|
||||
static PrintValidationResults(results: ValidationResult[], options: ValidationOptions = new ValidationOptions()) {
|
||||
if (options.skipBuiltIns)
|
||||
results = results.filter(x => x.ruleID < 1000);
|
||||
|
||||
if (options.exclude)
|
||||
results = results.filter(x => !options.exclude!.includes(x.ruleID));
|
||||
|
||||
if (options.errorsOnly)
|
||||
results = results.filter(x => x.result == ResultTypes.Failure);
|
||||
|
||||
if (results.length > 0) {
|
||||
LoggingUtils.log("Rule Validation:", ConsoleColor.Blue);
|
||||
results.forEach(x => LoggingUtils.log(` ${LoggingUtils.icon(x.result!)} Rule: [${x.ruleID}] "${x.ruleName}". Result: ${ResultTypes[x.result!]}`, x.result == ResultTypes.Success ? ConsoleColor.Green : ConsoleColor.Red));
|
||||
console.log();
|
||||
}
|
||||
|
||||
if (options.throwOnError && results.filter(x => x.result != ResultTypes.Success).length > 0)
|
||||
throw new Error("Rule Validation Errors");
|
||||
}
|
||||
}
|
||||
|
||||
export class WhenClause<T> {
|
||||
constructor(private ruleEngine: RuleEngine<T>, private condition: (obj: T) => boolean) { }
|
||||
|
||||
then(consequent: (obj: T) => boolean): DescriptionClause<T> {
|
||||
return new DescriptionClause(this.ruleEngine, (obj: T) => {
|
||||
return !this.condition(obj) || consequent(obj);
|
||||
}, []);
|
||||
}
|
||||
}
|
||||
|
||||
export class DescriptionClause<T> {
|
||||
private dependencies: (number | BuiltInRules)[] = [];
|
||||
|
||||
constructor(private ruleEngine: RuleEngine<T>, private check: (obj: T) => boolean, dependencies: (number | BuiltInRules)[] = []) {
|
||||
this.dependencies = dependencies;
|
||||
}
|
||||
|
||||
because(description: string): Rule<T> {
|
||||
return this.ruleEngine.addRule(description, this.check, { dependsOn: this.dependencies });
|
||||
}
|
||||
|
||||
dependsOn(...dependencies: (number | BuiltInRules | (number | BuiltInRules)[])[]): DescriptionClause<T> {
|
||||
const flatDependencies = dependencies.flat();
|
||||
const tmp = (obj: T) => {
|
||||
const dependenciesMet = flatDependencies.every(depId => this.ruleEngine.checkRuleById(depId, obj));
|
||||
return dependenciesMet && this.check(obj);
|
||||
};
|
||||
return new DescriptionClause(this.ruleEngine, tmp, [...this.dependencies, ...flatDependencies]);
|
||||
}
|
||||
}
|
||||
export class ValidationResult {
|
||||
value: any;
|
||||
ruleName: string;
|
||||
ruleID: number;
|
||||
error: string | null;
|
||||
message: string;
|
||||
result: ResultTypes | null;
|
||||
|
||||
public static FromRule(ruleName: string, ruleID: number, result: ResultTypes, message: string) {
|
||||
const retval = new ValidationResult();
|
||||
retval.ruleName = ruleName;
|
||||
retval.ruleID = ruleID;
|
||||
retval.result = result;
|
||||
retval.message = message;
|
||||
return retval;
|
||||
}
|
||||
|
||||
public static FromSuccess(value: any, message: string): ValidationResult {
|
||||
const retval = new ValidationResult();
|
||||
retval.value = value;
|
||||
retval.message = message;
|
||||
retval.result = ResultTypes.Success;
|
||||
return retval;
|
||||
}
|
||||
|
||||
public static FromFailure(error: string): ValidationResult {
|
||||
const retval = new ValidationResult();
|
||||
retval.error = error;
|
||||
retval.result = ResultTypes.Failure;
|
||||
return retval;
|
||||
}
|
||||
|
||||
public static PrintValidationResults(results: ValidationResult[], options: ValidationOptions) {
|
||||
const errors = results.filter(x => x.result == ResultTypes.Failure);
|
||||
if (errors.length > 0) {
|
||||
LoggingUtils.log("Type Validation Errors:", ConsoleColor.Red);
|
||||
errors.forEach((message) => LoggingUtils.log(` ${LoggingUtils.icon(false)} ${message.error}`));
|
||||
console.log();
|
||||
}
|
||||
|
||||
const successes = results.filter(x => x.result != ResultTypes.Failure);
|
||||
|
||||
const suffix = "is valid.";
|
||||
successes.sort((a, b) => {
|
||||
const aa = a.message.endsWith(suffix);
|
||||
const bb = b.message.endsWith(suffix);
|
||||
|
||||
return Number(bb) - Number(aa);
|
||||
});
|
||||
|
||||
if (!options.errorsOnly && successes.length > 0) {
|
||||
LoggingUtils.log("Type Validation Messages:", ConsoleColor.Green);
|
||||
successes.forEach((message) => LoggingUtils.log(` ${LoggingUtils.icon(true)} ${message.message}`));
|
||||
console.log();
|
||||
}
|
||||
|
||||
if (options.throwOnError && errors.length > 0)
|
||||
throw new Error("Validation Errors");
|
||||
}
|
||||
|
||||
public static HasError(results: ValidationResult[]): boolean {
|
||||
return results.filter(x => x.result == ResultTypes.Failure).length > 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
import { test as base } from "@playwright/test";
|
||||
import type { Page, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, TestInfo as PlaywrightTestInfo } from "@playwright/test";
|
||||
import ITestCase from "@business-logic/types/ITestCase";
|
||||
import { RuleEngine, ValidationOptions } from "@business-logic/types/RuleEngine";
|
||||
import TestCase from "@business-logic/types/TestCase";
|
||||
import Soft from "@business-logic/validations/Soft";
|
||||
import FakerUtils from "@impl/utils/FakerUtils";
|
||||
import LoggingUtils from "@impl/utils/LoggingUtils";
|
||||
|
||||
// File containing test function implementations
|
||||
|
||||
export type TestFunction = (args: PlaywrightTestArgs & PlaywrightTestOptions & PlaywrightWorkerArgs & PlaywrightWorkerOptions, testInfo: TestInfo) => void | Promise<void>;
|
||||
export type TestRunnerFunction = (page: Page, testInfo: TestInfo, /*testCase: TestCase*/) => void | Promise<void>;
|
||||
|
||||
export interface TestInfo extends PlaywrightTestInfo {
|
||||
testCase: TestCase;
|
||||
}
|
||||
|
||||
export const test = base.extend<{ testInfo: TestInfo; }>({
|
||||
// Do not use the fixture because it is not extended, and will cause a circular reference error
|
||||
// Use ({}, use, testInfo) not ({ testInfo }, use)
|
||||
testInfo: async ({}, use, testInfo) => {
|
||||
await use(testInfo as TestInfo);
|
||||
}
|
||||
});
|
||||
|
||||
export function addSmokeTagToRandomTest(testCases: ITestCase[]) {
|
||||
const index = Math.floor(Math.random() * testCases.length);
|
||||
testCases.at(index)?.tags.push("@smoke");
|
||||
}
|
||||
|
||||
export function prepareTest(testData: ITestCase, testRunner: TestRunnerFunction, validationOptions: ValidationOptions, ruleEngine: RuleEngine<TestCase>): [string, object, TestFunction] {
|
||||
const name = testData.name;
|
||||
const attributes = { tag: TestCase.getTags(testData) };
|
||||
const testFunction: TestFunction = ({ page }, testInfo) => {
|
||||
// Do not instantiate TestCase outside of this function,
|
||||
// otherwise it will be instantiated several times for each test case
|
||||
Soft.initialize(testInfo, page);
|
||||
testInfo.testCase = new TestCase(testData, validationOptions, FakerUtils.getRandomTestID());
|
||||
const results = ruleEngine.validate(testInfo.testCase);
|
||||
RuleEngine.PrintValidationResults(results, validationOptions);
|
||||
console.log(LoggingUtils.logValidate(`TestID: ${testInfo.testCase.testID}`, true));
|
||||
return testRunner(page, testInfo, /*testInfo.testCase*/);
|
||||
};
|
||||
return [name, attributes, testFunction];
|
||||
}
|
||||
|
|
@ -1,186 +0,0 @@
|
|||
import PropertyUtils from "@impl/utils/PropertyUtils";
|
||||
import { formatTag } from "@impl/utils/TaggingUtils";
|
||||
import { Page } from "@playwright/test";
|
||||
import { TestInfo } from "@business-logic/types/Test";
|
||||
import { ConsoleColor } from "@business-logic/types/Enums";
|
||||
import FrameworkConfig from "@business-logic/types/FrameworkConfig";
|
||||
import { DisposableBase } from "@business-logic/types/IDisposable";
|
||||
import ITestCase from "@business-logic/types/ITestCase";
|
||||
import ITestPages from "./ITestPages";
|
||||
import { ValidationOptions, ValidationResult } from "@business-logic/types/RuleEngine";
|
||||
import Soft, { SoftError } from "@business-logic/validations/Soft";
|
||||
import Validations from "./Validations";
|
||||
import FakerUtils from "@impl/utils/FakerUtils";
|
||||
import { ITestData } from "./ITestData";
|
||||
import { HomePage } from "../../pages/HomePage";
|
||||
import { VehicleSelectionPage } from "../../pages/VehicleSelectionPage";
|
||||
import { VehicleDamagePage } from "../../pages/VehicleDamagePage";
|
||||
import { EstimatePage } from "../../pages/EstimatePage";
|
||||
import { VehicleLookupAddressPage } from "../../pages/VehicleLookupAddressPage";
|
||||
import { VehicleLookupLicensePage } from "../../pages/VehicleLookupLicensePage";
|
||||
import { VinLookupPage } from "../../pages/VinLookupPage";
|
||||
import { ServicePackagesPage } from "../../pages/ServicePackagesPage";
|
||||
import { LeadgenHomePage } from "../../pages/LeadgenHomePage";
|
||||
import { ServiceLocationPage } from "../../pages/ServiceLocationPage";
|
||||
import { SchedulePage } from "../../pages/SchedulePage";
|
||||
import { ContactDetailsPage } from "../../pages/ContactDetailsPage";
|
||||
import { PaymentMethodPage } from "../../pages/PaymentMethodPage";
|
||||
import { ZipLookupPage } from "../../pages/ZipLookupPage";
|
||||
import { OrderConfirmationPage } from "../../pages/OrderConfirmationPage";
|
||||
import { PartQuestionsPage } from "../../pages/PartQuestionPage";
|
||||
import VehiclePartQuestionsPage from "../../pages/VehiclePartsPage";
|
||||
import CapabilityQuestionsPage from "../../pages/CapabilityQuestionsPage";
|
||||
import MoldingQuestionsPage from "../../pages/MoldingQuestionsPage";
|
||||
import { InsuranceCompanyPage } from "../../pages/InsuranceCompanyPage";
|
||||
import { CCPolicyInfoPage } from "../../pages/CCPolicyInfoPage";
|
||||
import { DuplicateCheckPage } from "../../pages/DuplicateCheckPage";
|
||||
import { PolicyVehiclesPage } from "../../pages/PolicyVehiclesPage";
|
||||
import { PolicyInfoSubmittedPage } from "../../pages/PolicyInfoSubmittedPage";
|
||||
import RecalibrationInfoPage from "../../pages/RecalibrationInfoPage";
|
||||
import { CoverageStatementPage } from "../../pages/CoverageStatementPage";
|
||||
import { VerifyDetailsPage } from "../../pages/VerifyDetailsPage";
|
||||
import { EndorsementsPage } from "../../pages/EndorsementsPage";
|
||||
import { PolicyDriverPage } from "../../pages/PolicyDriverPage";
|
||||
import { ServiceZipPage } from "../../pages/ServiceZipPage";
|
||||
|
||||
// File for Test Case Class
|
||||
|
||||
export default class TestCase extends DisposableBase implements ITestCase {
|
||||
public static FrameworkConfig: FrameworkConfig = {
|
||||
createResources: true, //process.env.FW_CREATE_RESOURCES! === "true",
|
||||
destroyResources: true, // process.env.FW_DESTROY_RESOURCES! === "true",
|
||||
maxAllotmentHours: Number(process.env.FW_MAX_ALLOTMENT_HOURS)
|
||||
};
|
||||
|
||||
public static readonly Constants = class {
|
||||
static readonly DISPOSE_HALTED: string = "FrameworkConfig is set to NOT destroy resources. Teardown halted!";
|
||||
static readonly CREATION_HALTED: string = "FrameworkConfig is set to NOT create resources. Preparation halted!";
|
||||
};
|
||||
|
||||
public readonly testID?: string;
|
||||
public readonly name: string;
|
||||
public readonly tags: string[];
|
||||
|
||||
public readonly testData: Partial<ITestData>;
|
||||
|
||||
public readonly validations?: Validations;
|
||||
public readonly tempData?: any[] = [];
|
||||
|
||||
|
||||
public location: Location;
|
||||
public alternateLocation?: Location;
|
||||
|
||||
public pages: ITestPages;
|
||||
|
||||
public constructor(data: ITestCase, validationOptions: ValidationOptions = new ValidationOptions(), testID: string) {
|
||||
super();
|
||||
Object.assign(this, data);
|
||||
this.testID = FakerUtils.getRandomTestID();
|
||||
|
||||
const validationResults: ValidationResult[] = [];
|
||||
|
||||
this.name = PropertyUtils.getValue<this>(data, TestCase.name, validationResults, { isRequired: true }, x => x.name);
|
||||
this.tags = PropertyUtils.getValue<this>(data, TestCase.name, validationResults, { isRequired: true }, x => x.tags);
|
||||
this.validations = PropertyUtils.getValue<this>(data, TestCase.name, validationResults, { isRequired: false }, x => x.validations);
|
||||
|
||||
// if (PropertyUtils.hasProperty<this>(data, TestCase.name, validationResults, { isRequired: false }, x => x.oldTransactions))
|
||||
// this.oldTransactions = data.oldTransactions.map((item: any) => new TransactionData(item, validationResults, this.testID));
|
||||
|
||||
// if (PropertyUtils.hasProperty<this>(data, TestCase.name, validationResults, { isRequired: true }, x => x.transaction))
|
||||
// this.transaction = new TransactionData(data.transaction, validationResults, this.testID);
|
||||
|
||||
ValidationResult.PrintValidationResults(validationResults, validationOptions);
|
||||
}
|
||||
|
||||
public static getTags(testCase: ITestCase): string[] {
|
||||
const retval = [
|
||||
...testCase.tags,
|
||||
formatTag(testCase.name),
|
||||
];
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
public static async afterEachMethod(page: Page, testInfo: TestInfo) {
|
||||
const originalStatus = testInfo.status;
|
||||
|
||||
if (Soft.hasFailedAssertions())
|
||||
testInfo.status = "failed";
|
||||
|
||||
for (const a of Soft.getFailedAssertions())
|
||||
testInfo.errors.push(new SoftError(a));
|
||||
|
||||
// NOTE: This try catch is here because the tests when locally, frequently
|
||||
// fail tests on screenshot, which we do not want, is it make it
|
||||
// harder to parse any other real errors we do care about. T.S. 9.5.2024
|
||||
try {
|
||||
await testInfo.attach("End of Test Screenshot", {
|
||||
body: await page.screenshot({ fullPage: true }),
|
||||
contentType: 'image/png'
|
||||
});
|
||||
} catch(error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
|
||||
if (testInfo.testCase)
|
||||
await testInfo.testCase.disposeAll();
|
||||
|
||||
const seconds: string = String(testInfo.duration / 1000);
|
||||
const minutes: string = (testInfo.duration / 1000 / 60).toFixed(2);
|
||||
const validationErrors: string = Soft.hasFailedAssertions() ? ` with ${Soft.getFailureCount()} validation errors` : "";
|
||||
|
||||
if (originalStatus == "failed")
|
||||
console.log(`${ConsoleColor.Red}Test failed after ${seconds} seconds, or roughly ${minutes} minutes${validationErrors}.${ConsoleColor.Reset}`);
|
||||
else {
|
||||
if (testInfo.status == "passed")
|
||||
console.log(`${ConsoleColor.Green}Test finished successfully in ${seconds} seconds, or roughly ${minutes} minutes.${ConsoleColor.Reset}`);
|
||||
else
|
||||
console.log(`${ConsoleColor.Orange}Test finished in ${seconds} seconds, or roughly ${minutes} minutes${validationErrors}.${ConsoleColor.Reset}`);
|
||||
}
|
||||
console.log(page.url());
|
||||
}
|
||||
|
||||
public async setup(): Promise<void> {
|
||||
//Setup for Test case
|
||||
}
|
||||
|
||||
public setupPages(page: Page): void {
|
||||
this.pages = {
|
||||
capabilityQuestionsPage: new CapabilityQuestionsPage(page),
|
||||
ccPolicyInfoPage: new CCPolicyInfoPage(page),
|
||||
contactDetailsPage: new ContactDetailsPage(page),
|
||||
coverageStatementPage: new CoverageStatementPage(page),
|
||||
duplicateCheckPage: new DuplicateCheckPage(page),
|
||||
estimatePage: new EstimatePage(page),
|
||||
homePage: new HomePage(page),
|
||||
insuranceCompanyPage: new InsuranceCompanyPage(page),
|
||||
leadgenHomePage: new LeadgenHomePage(page),
|
||||
moldingQuestionsPage: new MoldingQuestionsPage(page),
|
||||
orderConfirmationPage: new OrderConfirmationPage(page),
|
||||
partQuestionsPage: new PartQuestionsPage(page),
|
||||
paymentMethodPage: new PaymentMethodPage(page),
|
||||
policyInfoSubmittedPage: new PolicyInfoSubmittedPage(page),
|
||||
policyVehiclesPage: new PolicyVehiclesPage(page),
|
||||
recalibrationInfoPage: new RecalibrationInfoPage(page),
|
||||
schedulePage: new SchedulePage(page),
|
||||
serviceLocationPage: new ServiceLocationPage(page),
|
||||
servicePackagesPage: new ServicePackagesPage(page),
|
||||
serviceZipPage: new ServiceZipPage(page),
|
||||
vehicleDamagePage: new VehicleDamagePage(page),
|
||||
vehicleLookupAddressPage: new VehicleLookupAddressPage(page),
|
||||
vehicleLookupLicensePage: new VehicleLookupLicensePage(page),
|
||||
vehiclePartsPage: new VehiclePartQuestionsPage(page),
|
||||
vehicleSelectionPage: new VehicleSelectionPage(page),
|
||||
verifyDetailsPage: new VerifyDetailsPage(page),
|
||||
vinLookupPage: new VinLookupPage(page),
|
||||
zipLookupPage: new ZipLookupPage(page),
|
||||
endorsementsPage: new EndorsementsPage(page),
|
||||
policyDriverPage: new PolicyDriverPage(page)
|
||||
};
|
||||
}
|
||||
|
||||
protected async dispose(): Promise<void> {
|
||||
// Tear down for test case
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
export default class TestSuccessAlert extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = "TestSuccessAlert";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
import PropertyUtils from "@impl/utils/PropertyUtils";
|
||||
import IValidations from "./IValidations";
|
||||
import { ValidationResult } from "./RuleEngine";
|
||||
|
||||
// File containing validation class
|
||||
|
||||
export default class Validations implements IValidations {
|
||||
public readonly exampleValue: boolean;
|
||||
|
||||
public constructor(json: any, validationResults: ValidationResult[]) {
|
||||
this.exampleValue = PropertyUtils.getValue<this>(json, Validations.name, validationResults, { isRequired: true }, x => x.exampleValue);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,179 +0,0 @@
|
|||
import LoggingUtils from '@impl/utils/LoggingUtils';
|
||||
import { Page } from '@playwright/test';
|
||||
import { TestInfo } from '@business-logic/types/Test';
|
||||
import { TestInfoError } from "@playwright/test";
|
||||
import { DateTime } from 'luxon';
|
||||
import { expect as pw_expect } from '@playwright/test';
|
||||
|
||||
export default class Soft {
|
||||
private static _instance: Soft | null = null;
|
||||
private testInfo: TestInfo;
|
||||
private page: Page;
|
||||
private failedAssertions: string[] = [];
|
||||
private errorCounter: number = 0;
|
||||
private errorsOnly: boolean = false;
|
||||
|
||||
private constructor(testInfo: TestInfo, page: Page) {
|
||||
this.testInfo = testInfo;
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
public static initialize(testInfo: TestInfo, page: Page): void {
|
||||
Soft._instance = new Soft(testInfo, page);
|
||||
}
|
||||
|
||||
public static setOptions(options: { errorsOnly: boolean }): void {
|
||||
Soft.getInstance().errorsOnly = options.errorsOnly;
|
||||
}
|
||||
|
||||
public static getOptions(): { errorsOnly: boolean } {
|
||||
return { errorsOnly: Soft.getInstance().errorsOnly };
|
||||
}
|
||||
|
||||
private static getInstance(): Soft {
|
||||
if (!Soft._instance)
|
||||
throw new Error("Soft is not initialized. Call Soft.initialize(testInfo, page) first!");
|
||||
return Soft._instance;
|
||||
}
|
||||
|
||||
public async handleAssertion(
|
||||
matcherFull: string,
|
||||
matcherDisplay: string,
|
||||
matcherFunction: () => Promise<void>,
|
||||
reason?: string): Promise<void> {
|
||||
reason = reason ? reason : ''
|
||||
const reasonText = reason ? `'${reason}' ` : '';
|
||||
try {
|
||||
await matcherFunction();
|
||||
if (!this.errorsOnly)
|
||||
console.log(LoggingUtils.logValidate(`Validation ${reasonText}passed: ${matcherDisplay}!`, true));
|
||||
} catch (error) {
|
||||
console.log(LoggingUtils.logValidate(`Validation ${reasonText}failed: ${matcherDisplay}!`, false));
|
||||
//const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
this.failedAssertions.push(`\n${++this.errorCounter}_Validation ${reasonText}failed:\n${LoggingUtils.replaceEmptyLinesWithMiddleDot(matcherFull)}!\n${error.stack}\n`);
|
||||
// NOTE: I find it more helpful for the stack trace to be included here, so we know which line the validation is failing
|
||||
try {
|
||||
const screenshot: Buffer = await this.page.screenshot({ fullPage: true });
|
||||
const name: string = LoggingUtils.sanitizeFileName(`${this.errorCounter}_Validation_${reason}${DateTime.now().valueOf()}`);
|
||||
await this.testInfo.attach(name, {
|
||||
body: screenshot,
|
||||
contentType: 'image/png'
|
||||
});
|
||||
} catch(err){
|
||||
// ohwell
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static expect(value: any, reason?: string): ExpectationChain {
|
||||
return new ExpectationChain(value, Soft.getInstance(), reason);
|
||||
}
|
||||
|
||||
public static getFailedAssertions(): string[] {
|
||||
return Soft.getInstance().failedAssertions;
|
||||
}
|
||||
|
||||
public static hasFailedAssertions(): boolean {
|
||||
return Soft.getInstance().failedAssertions.length > 0;
|
||||
}
|
||||
|
||||
public static getFailureCount(): number {
|
||||
return Soft.getInstance().failedAssertions.length;
|
||||
}
|
||||
|
||||
public static clearFailedAssertions(): void {
|
||||
Soft.getInstance().failedAssertions = [];
|
||||
}
|
||||
}
|
||||
|
||||
export class SoftError implements TestInfoError {
|
||||
public readonly message?: string | undefined;
|
||||
constructor(msg: string) {
|
||||
this.message = msg;
|
||||
}
|
||||
}
|
||||
|
||||
export class ExpectationChain {
|
||||
constructor(private value: any, private soft: Soft, private reason?: string) { }
|
||||
|
||||
private formatValue(value: any): string {
|
||||
return LoggingUtils.truncateString(value);
|
||||
}
|
||||
|
||||
public async toBe(expected: any): Promise<ExpectationChain> {
|
||||
await this.soft.handleAssertion(
|
||||
`expect(${this.value}).toBe(${expected})`,
|
||||
`expect(${this.formatValue(this.value)}).toBe(${this.formatValue(expected)})`,
|
||||
async () => await pw_expect(this.value).toBe(expected),
|
||||
this.reason
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
public async toEqual(expected: any): Promise<ExpectationChain> {
|
||||
await this.soft.handleAssertion(
|
||||
`expect(${JSON.stringify(this.value)}).toEqual(${expected})`,
|
||||
`expect(${this.formatValue(JSON.stringify(this.value))}).toEqual(${this.formatValue(expected)})`,
|
||||
async () => await pw_expect(this.value).toEqual(expected),
|
||||
this.reason
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
public async toContain(expected: any): Promise<ExpectationChain> {
|
||||
await this.soft.handleAssertion(
|
||||
`expect(${this.value}).toContain(${expected})`,
|
||||
`expect(${this.formatValue(this.value)}).toContain(${this.formatValue(expected)})`,
|
||||
async () => await pw_expect(this.value).toContain(expected),
|
||||
this.reason
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
public async toHaveText(expected: string): Promise<ExpectationChain> {
|
||||
await this.soft.handleAssertion(
|
||||
`expect(${this.value}).toHaveText(${expected})`,
|
||||
`expect(${this.formatValue(this.value)}).toHaveText(${this.formatValue(expected)})`,
|
||||
async () => {
|
||||
if (typeof this.value.textContent !== 'function') {
|
||||
throw new Error('value does not have a textContent method');
|
||||
}
|
||||
const text = await this.value.textContent();
|
||||
await pw_expect(text).toHaveText(expected);
|
||||
}, this.reason
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
public async toBeGreaterThan(expected: number): Promise<ExpectationChain> {
|
||||
await this.soft.handleAssertion(
|
||||
`expect(${this.value}).toBeGreaterThan(${expected})`,
|
||||
`expect(${this.formatValue(this.value)}).toBeGreaterThan(${this.formatValue(expected)})`,
|
||||
async () => await pw_expect(this.value).toBeGreaterThan(expected),
|
||||
this.reason
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
public async toBeTruthy(): Promise<ExpectationChain> {
|
||||
await this.soft.handleAssertion(
|
||||
`expect(${this.value}).toBeTruthy`,
|
||||
`expect(${this.formatValue(this.value)}).toBeTruthy`,
|
||||
async () => await pw_expect(this.value).toBeTruthy(),
|
||||
this.reason
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
public async toBeFalsy(): Promise<ExpectationChain> {
|
||||
await this.soft.handleAssertion(
|
||||
`expect(${this.value}).toBeFalsy`,
|
||||
`expect(${this.formatValue(this.value)}).toBeFalsy`,
|
||||
async () => await pw_expect(this.value).toBeFalsy(),
|
||||
this.reason
|
||||
);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
7
playwright-tests/framework/TestData.ts
Normal file
7
playwright-tests/framework/TestData.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { ITestData as base } from 'safelite-playwright-core'
|
||||
import { PaymentMethod } from './localTypes/Enums'
|
||||
|
||||
export interface ITestData extends base {
|
||||
// Put any project-specific data here. Anything useful to other Safelite projects should be submitted as a pull request to safelite-playwright-core.
|
||||
paymentMethod: PaymentMethod
|
||||
}
|
||||
101
playwright-tests/framework/TestPages.ts
Normal file
101
playwright-tests/framework/TestPages.ts
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import { Page } from "@playwright/test";
|
||||
import { type TestPagesFactory } from 'safelite-playwright-core'
|
||||
import { HomePage } from "../pages/HomePage"
|
||||
import { LeadgenHomePage } from "../pages/LeadgenHomePage"
|
||||
import { ServicePackagesPage } from "../pages/ServicePackagesPage"
|
||||
import { VehicleDamagePage } from "../pages/VehicleDamagePage"
|
||||
import { VehicleLookupAddressPage } from "../pages/VehicleLookupAddressPage"
|
||||
import { VehicleLookupLicensePage } from "../pages/VehicleLookupLicensePage"
|
||||
import { EstimatePage } from "../pages/EstimatePage"
|
||||
import { VehicleSelectionPage } from "../pages/VehicleSelectionPage"
|
||||
import { VinLookupPage } from "../pages/VinLookupPage"
|
||||
import { ServiceLocationPage } from "../pages/ServiceLocationPage"
|
||||
import { SchedulePage } from "../pages/SchedulePage"
|
||||
import { ContactDetailsPage } from "../pages/ContactDetailsPage"
|
||||
import { PaymentMethodPage } from "../pages/PaymentMethodPage"
|
||||
import { ZipLookupPage } from "../pages/ZipLookupPage"
|
||||
import { OrderConfirmationPage } from "../pages/OrderConfirmationPage"
|
||||
import { PartQuestionsPage } from "../pages/PartQuestionPage"
|
||||
import VehiclePartQuestionsPage from "../pages/VehiclePartsPage"
|
||||
import CapabilityQuestionsPage from "../pages/CapabilityQuestionsPage"
|
||||
import MoldingQuestionsPage from "../pages/MoldingQuestionsPage"
|
||||
import { InsuranceCompanyPage } from "../pages/InsuranceCompanyPage"
|
||||
import { CCPolicyInfoPage } from "../pages/CCPolicyInfoPage"
|
||||
import { DuplicateCheckPage } from "../pages/DuplicateCheckPage"
|
||||
import { PolicyVehiclesPage } from "../pages/PolicyVehiclesPage"
|
||||
import { PolicyInfoSubmittedPage } from "../pages/PolicyInfoSubmittedPage"
|
||||
import RecalibrationInfoPage from "../pages/RecalibrationInfoPage"
|
||||
import { CoverageStatementPage } from "../pages/CoverageStatementPage"
|
||||
import { VerifyDetailsPage } from "../pages/VerifyDetailsPage"
|
||||
import { EndorsementsPage } from "../pages/EndorsementsPage"
|
||||
import { PolicyDriverPage } from "../pages/PolicyDriverPage"
|
||||
import { ServiceZipPage } from "../pages/ServiceZipPage"
|
||||
|
||||
export interface ITestPages {
|
||||
capabilityQuestionsPage: CapabilityQuestionsPage,
|
||||
ccPolicyInfoPage: CCPolicyInfoPage,
|
||||
contactDetailsPage: ContactDetailsPage,
|
||||
coverageStatementPage: CoverageStatementPage,
|
||||
duplicateCheckPage: DuplicateCheckPage,
|
||||
estimatePage: EstimatePage,
|
||||
homePage: HomePage,
|
||||
insuranceCompanyPage: InsuranceCompanyPage,
|
||||
leadgenHomePage: LeadgenHomePage,
|
||||
moldingQuestionsPage: MoldingQuestionsPage,
|
||||
orderConfirmationPage: OrderConfirmationPage,
|
||||
partQuestionsPage: PartQuestionsPage,
|
||||
paymentMethodPage: PaymentMethodPage,
|
||||
policyInfoSubmittedPage: PolicyInfoSubmittedPage,
|
||||
policyVehiclesPage: PolicyVehiclesPage,
|
||||
recalibrationInfoPage: RecalibrationInfoPage,
|
||||
schedulePage: SchedulePage,
|
||||
serviceLocationPage: ServiceLocationPage,
|
||||
servicePackagesPage: ServicePackagesPage,
|
||||
serviceZipPage: ServiceZipPage,
|
||||
vehicleDamagePage: VehicleDamagePage,
|
||||
vehicleLookupAddressPage: VehicleLookupAddressPage,
|
||||
vehicleLookupLicensePage: VehicleLookupLicensePage,
|
||||
vehiclePartsPage: VehiclePartQuestionsPage,
|
||||
vehicleSelectionPage: VehicleSelectionPage,
|
||||
verifyDetailsPage: VerifyDetailsPage,
|
||||
vinLookupPage: VinLookupPage,
|
||||
zipLookupPage: ZipLookupPage,
|
||||
endorsementsPage: EndorsementsPage,
|
||||
policyDriverPage: PolicyDriverPage,
|
||||
}
|
||||
|
||||
export const createTestPages: TestPagesFactory<ITestPages> = (page: Page) => {
|
||||
const pages: ITestPages = {
|
||||
capabilityQuestionsPage: new CapabilityQuestionsPage(page),
|
||||
ccPolicyInfoPage: new CCPolicyInfoPage(page),
|
||||
contactDetailsPage: new ContactDetailsPage(page),
|
||||
coverageStatementPage: new CoverageStatementPage(page),
|
||||
duplicateCheckPage: new DuplicateCheckPage(page),
|
||||
estimatePage: new EstimatePage(page),
|
||||
homePage: new HomePage(page),
|
||||
insuranceCompanyPage: new InsuranceCompanyPage(page),
|
||||
leadgenHomePage: new LeadgenHomePage(page),
|
||||
moldingQuestionsPage: new MoldingQuestionsPage(page),
|
||||
orderConfirmationPage: new OrderConfirmationPage(page),
|
||||
partQuestionsPage: new PartQuestionsPage(page),
|
||||
paymentMethodPage: new PaymentMethodPage(page),
|
||||
policyInfoSubmittedPage: new PolicyInfoSubmittedPage(page),
|
||||
policyVehiclesPage: new PolicyVehiclesPage(page),
|
||||
recalibrationInfoPage: new RecalibrationInfoPage(page),
|
||||
schedulePage: new SchedulePage(page),
|
||||
serviceLocationPage: new ServiceLocationPage(page),
|
||||
servicePackagesPage: new ServicePackagesPage(page),
|
||||
serviceZipPage: new ServiceZipPage(page),
|
||||
vehicleDamagePage: new VehicleDamagePage(page),
|
||||
vehicleLookupAddressPage: new VehicleLookupAddressPage(page),
|
||||
vehicleLookupLicensePage: new VehicleLookupLicensePage(page),
|
||||
vehiclePartsPage: new VehiclePartQuestionsPage(page),
|
||||
vehicleSelectionPage: new VehicleSelectionPage(page),
|
||||
verifyDetailsPage: new VerifyDetailsPage(page),
|
||||
vinLookupPage: new VinLookupPage(page),
|
||||
zipLookupPage: new ZipLookupPage(page),
|
||||
endorsementsPage: new EndorsementsPage(page),
|
||||
policyDriverPage: new PolicyDriverPage(page)
|
||||
};
|
||||
return pages;
|
||||
}
|
||||
12
playwright-tests/framework/Typedefs.ts
Normal file
12
playwright-tests/framework/Typedefs.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { getTestObject as getTestObject_T, prepareTest as prepareTest_T, RuleEngine as RuleEngine_T, TestCase as TestCase_T, TestInfo as TestInfo_T, ITestCase as ITestCase_T} from 'safelite-playwright-core'
|
||||
import { ITestPages } from './TestPages'
|
||||
import { ITestData } from './TestData'
|
||||
|
||||
export const getTestObject = getTestObject_T<ITestPages, ITestData>;
|
||||
export const prepareTest = prepareTest_T<ITestPages, ITestData>;
|
||||
|
||||
export const RuleEngine = RuleEngine_T<TestCase_T<ITestPages, ITestData>>;
|
||||
export class TestCase extends TestCase_T<ITestPages, ITestData> {};
|
||||
export type TestInfo = TestInfo_T<ITestPages, ITestData>;
|
||||
|
||||
export type ITestCase = ITestCase_T<ITestPages, ITestData>;
|
||||
26
playwright-tests/framework/localTypes/Enums.ts
Normal file
26
playwright-tests/framework/localTypes/Enums.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
|
||||
export enum PaymentMethod {
|
||||
Insurance = 'Insurance',
|
||||
SelfPay = 'SelfPay'
|
||||
}
|
||||
|
||||
export enum ProgressBarPercentages {
|
||||
VehicleSelectionPage = '4%',
|
||||
VehicleDamagePage = '16%',
|
||||
EstimatePage = '28%',
|
||||
ServiceZipPage = '32%',
|
||||
VehicleLookupAddressPage = '32%',
|
||||
VehicleLookupLicensePage = '32%',
|
||||
VinLookupPage = '32%',
|
||||
PartQuestionsPage = '40%',
|
||||
MoldingQuestionsPage = '40%',
|
||||
CapabilityQuestionsPage = '40%',
|
||||
VehiclePartsPage = '40%',
|
||||
ServicePackagePage = '48%',
|
||||
InsuranceCompanyPage = '52%',
|
||||
ServiceLocationPage = '60%',
|
||||
SchedulePage = '72%',
|
||||
ContactDetailsPage = '84%',
|
||||
PaymentMethodPage = '92%',
|
||||
OrderConfirmationPage = '100%'
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
import { test } from '@business-logic/types/Test';
|
||||
import { getTestObject } from "framework/Typedefs";
|
||||
|
||||
const test = getTestObject()
|
||||
|
||||
export type Context = {
|
||||
kind: string;
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
import { IPartsOrQuestionsResponse } from "@business-logic/types/DigitalAPI";
|
||||
import { ITestData } from "@business-logic/types/ITestData";
|
||||
import { expect, Response } from "@playwright/test";
|
||||
|
||||
export default class ApiResponseInterceptUtil {
|
||||
readonly testData: Partial<ITestData>;
|
||||
|
||||
constructor(testData: Partial<ITestData>) {
|
||||
this.testData = testData;
|
||||
|
||||
// Bind callback functions to the class instance so 'this' is usable within a callback
|
||||
this.handleInterceptResponse = this.handleInterceptResponse.bind(this);
|
||||
this.handlePartsOrQuestionsResponse = this.handlePartsOrQuestionsResponse.bind(this);
|
||||
}
|
||||
|
||||
async handleInterceptResponse(response: Response) {
|
||||
if (!response.url().includes('safelite.io')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const urlPath = response.url().split('safelite.io')[1]; // get api path
|
||||
|
||||
switch(urlPath) {
|
||||
case '/parts/api/v1/parts/parts-or-questions':
|
||||
await this.handlePartsOrQuestionsResponse(response);
|
||||
break;
|
||||
//TODO: add validation for other urls in the API
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (response.url().endsWith('/parts/parts-or-questions') && response.status() === 200) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
async handlePartsOrQuestionsResponse(response: Response) {
|
||||
if (this.testData.hasOemEndorsement) {
|
||||
const partsRes = (await response.json()) as IPartsOrQuestionsResponse;
|
||||
for ( const partOrQuestion of partsRes.partsOrQuestions) {
|
||||
for (const part of partOrQuestion.parts) {
|
||||
expect.soft(part.partNumber.endsWith('OEM'),
|
||||
`handlePartsOrQuestionsResponse>> OEM endorsement was expected, but "${part.partType}" with part number "${part.partNumber}" is not OEM.`
|
||||
).toEqual(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,194 +0,0 @@
|
|||
import { PostBulkFetchIssuesRequestBody, PostBulkFetchIssuesResponse, GetIssueResponse, GetIssueTransitionsResponse, GetIssueTypesResponse, PostAddCommentResponse, PostBulkCreateIssueRequestBody, PostBulkCreateIssueResponse, PostCreateIssueRequestBody, PostCreateIssueResponse, PostTransitionIssueRequestBody, PutEditIssueRequestBody, PostBulkTransitionIssuesRequestBody, GetSprintResponse, GetJqlSearchIssueParams, GetJqlSearchIssueResponse } from "@business-logic/types/JiraApi";
|
||||
import axios, { AxiosInstance } from "axios";
|
||||
import axiosRetry from "axios-retry";
|
||||
import FormData from 'form-data';
|
||||
import path from "path";
|
||||
import fs from 'fs';
|
||||
|
||||
const jiraUrl = process.env.JIRA_SERVER!;
|
||||
const jiraUsername = process.env.JIRA_USERNAME!;
|
||||
const jiraApiKey = process.env.JIRA_API_KEY!;
|
||||
const boardId = process.env.JIRA_BOARD_ID!;
|
||||
const encodedAuthKey = Buffer.from(`${jiraUsername}:${jiraApiKey}`).toString('base64');
|
||||
|
||||
export default class JiraApiUtil {
|
||||
readonly baseUrl: string;
|
||||
readonly issueUrl: string;
|
||||
readonly bulkIssueCreateUrl: string;
|
||||
readonly bulkIssueFetchUrl: string;
|
||||
readonly bulkTransitionIssuesUrl: string;
|
||||
readonly getCurrentSprintUrl: string;
|
||||
readonly getJqlSearchIssueUrl: string;
|
||||
|
||||
readonly axiosClient: AxiosInstance;
|
||||
|
||||
constructor() {
|
||||
this.baseUrl = jiraUrl;
|
||||
this.issueUrl = `${this.baseUrl}/rest/api/3/issue`;
|
||||
this.bulkIssueCreateUrl = `${this.issueUrl}/bulk`;
|
||||
this.bulkIssueFetchUrl = `${this.issueUrl}/bulkfetch`;
|
||||
this.bulkTransitionIssuesUrl = `${this.baseUrl}/rest/api/3/bulk/issues/transition`;
|
||||
this.getCurrentSprintUrl = `${this.baseUrl}/rest/agile/1.0/board/${boardId}/sprint?state=active`;
|
||||
this.getJqlSearchIssueUrl = `${this.baseUrl}/rest/api/3/search/jql`;
|
||||
this.axiosClient = axios.create();
|
||||
// interceptor to log error message from api
|
||||
this.axiosClient.interceptors.response.use(
|
||||
response => response,
|
||||
error => {
|
||||
console.error('Axios Error:', error?.response?.data || error.message);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
// Set up retries if requests are made too quickly
|
||||
axiosRetry(this.axiosClient, {
|
||||
retries: 4,
|
||||
retryDelay: (retryCount) => { return Math.pow(2, retryCount) * 1000; }, // Exponential backoff
|
||||
retryCondition: (error) => { return error.response?.status === 429 } // If rate-limit error
|
||||
});
|
||||
}
|
||||
|
||||
getIssue(issueKey: string) {
|
||||
const url = `${this.issueUrl}/${issueKey}`;
|
||||
return this.axiosClient.get<GetIssueResponse>(url, {
|
||||
headers: {
|
||||
Authorization: `Basic ${encodedAuthKey}`,
|
||||
Accept: 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async getCurrentSprint() {
|
||||
const res = await this.axiosClient.get<GetSprintResponse>(this.getCurrentSprintUrl, {
|
||||
headers: {
|
||||
Authorization: `Basic ${encodedAuthKey}`,
|
||||
Accept: 'application/json'
|
||||
}
|
||||
});
|
||||
const currentSprint = res.data.values.filter(sprint => {
|
||||
return `${sprint.originBoardId}` === boardId;
|
||||
});
|
||||
if (currentSprint.length === 1) {
|
||||
return currentSprint[0];
|
||||
} else {
|
||||
console.error(`JiraApiUtil >> Multiple active sprints found for board ${boardId}`);
|
||||
}
|
||||
}
|
||||
|
||||
getJqlSearchIssue(params: GetJqlSearchIssueParams) {
|
||||
return this.axiosClient.get<GetJqlSearchIssueResponse>(this.getJqlSearchIssueUrl, {
|
||||
params: params,
|
||||
headers: {
|
||||
Authorization: `Basic ${encodedAuthKey}`,
|
||||
Accept: 'application/json'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
postBulkFetchIssues(requestBody: PostBulkFetchIssuesRequestBody) {
|
||||
return this.axiosClient.post<PostBulkFetchIssuesResponse>(this.bulkIssueFetchUrl, requestBody, {
|
||||
headers: {
|
||||
Authorization: `Basic ${encodedAuthKey}`,
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getIssueTransitions(issueKey: string) {
|
||||
const url = `${this.issueUrl}/${issueKey}/transitions`
|
||||
return this.axiosClient.get<GetIssueTransitionsResponse>(url, {
|
||||
headers: {
|
||||
Authorization: `Basic ${encodedAuthKey}`,
|
||||
Accept: 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getIssueTypes(projectKey: string) {
|
||||
const url = `${this.baseUrl}/rest/api/3/issue/createmeta/${projectKey}/issuetypes`;
|
||||
return this.axiosClient.get<GetIssueTypesResponse>(url, {
|
||||
headers: {
|
||||
Authorization: `Basic ${encodedAuthKey}`,
|
||||
Accept: 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
postAddComment(issueKey: string, commentBody: PostAddCommentResponse) {
|
||||
const url = `${this.issueUrl}/${issueKey}/comment`;
|
||||
return this.axiosClient.post(url, commentBody, {
|
||||
headers: {
|
||||
Authorization: `Basic ${encodedAuthKey}`,
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
postTransitionIssue(issueKey: string, requestBody: PostTransitionIssueRequestBody) {
|
||||
const transitionUrl = `${this.issueUrl}/${issueKey}/transitions`
|
||||
return this.axiosClient.post(transitionUrl, requestBody, {
|
||||
headers: {
|
||||
Authorization: `Basic ${encodedAuthKey}`,
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
putEditIssue(issueKey: string, requestBody: PutEditIssueRequestBody) {
|
||||
const editIssueUrl = `${this.issueUrl}/${issueKey}`
|
||||
return this.axiosClient.put(editIssueUrl, requestBody, {
|
||||
headers: {
|
||||
Authorization: `Basic ${encodedAuthKey}`,
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
postBulkTransitionIssues(requestBody: PostBulkTransitionIssuesRequestBody) {
|
||||
return this.axiosClient.post(this.bulkTransitionIssuesUrl, requestBody, {
|
||||
headers: {
|
||||
Authorization: `Basic ${encodedAuthKey}`,
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
postCreateIssue(requestBody: PostCreateIssueRequestBody) {
|
||||
return this.axiosClient.post<PostCreateIssueResponse>(this.issueUrl, requestBody, {
|
||||
headers: {
|
||||
Authorization: `Basic ${encodedAuthKey}`,
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
postBulkCreateIssue(requestBody: PostBulkCreateIssueRequestBody) {
|
||||
return this.axiosClient.post<PostBulkCreateIssueResponse>(this.bulkIssueCreateUrl, requestBody, {
|
||||
headers: {
|
||||
Authorization: `Basic ${encodedAuthKey}`,
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
postUploadAttachment(issueKey: string, filePath: string) {
|
||||
const url = `${this.baseUrl}/rest/api/3/issue/${issueKey}/attachments`
|
||||
const form = new FormData();
|
||||
const fileName = path.basename(filePath);
|
||||
|
||||
form.append('file', fs.createReadStream(filePath), fileName);
|
||||
return this.axiosClient.post(url, form, {
|
||||
headers: {
|
||||
Authorization: `Basic ${encodedAuthKey}`,
|
||||
'X-Atlassian-Token': 'no-check',
|
||||
...form.getHeaders()
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,449 +0,0 @@
|
|||
import JiraApiUtil from "@impl/API/JiraApiUtil";
|
||||
import type { FullConfig, FullResult, Reporter, Suite, TestCase, TestError, TestResult } from "@playwright/test/reporter";
|
||||
import { formatDate, formatDateForFilename } from "@impl/utils/DateUtils";
|
||||
import { GetIssueResponse, JiraIssue, JiraIssueFields } from "@business-logic/types/JiraApi";
|
||||
import OrtoniReport, { OrtoniReportConfig } from "ortoni-report";
|
||||
|
||||
let jiraCardNumber = process.env.JIRA_CARD_NUMBER || '';
|
||||
const isRegressionRun = process.env.IS_REGRESSION === 'true'? true: false;
|
||||
const jiraProjectKey = process.env.JIRA_PROJECT_KEY!;
|
||||
const jiraEpicKey = process.env.JIRA_EPIC_KEY!;
|
||||
const passTransitionId = '111';
|
||||
const failTransitionId = '101';
|
||||
const inTestTransitionId = '271';
|
||||
const currentDate = formatDateForFilename(new Date());
|
||||
|
||||
// Ortoni config
|
||||
const reportName = `ortoni_report_${currentDate}.html`;
|
||||
const reportConfig: OrtoniReportConfig = {
|
||||
port: 1994,
|
||||
open: "never",
|
||||
folderPath: "ortoni-report",
|
||||
filename: reportName,
|
||||
logo: 'playwright-tests/business-logic/data/logo.png',
|
||||
title: "Test Report",
|
||||
showProject: false,
|
||||
projectName: "FMG-Nextgen-Playwright-Report",
|
||||
testType: `E2E- Environment: ${process.env.NODE_ENV} `,
|
||||
preferredTheme: "light",
|
||||
base64Image: true,
|
||||
};
|
||||
|
||||
export default class JiraWritebackReporter implements Reporter {
|
||||
readonly jiraApiUtil: JiraApiUtil = new JiraApiUtil();
|
||||
readonly ortoniReport: OrtoniReport = new OrtoniReport(reportConfig);
|
||||
readonly loadIssueCalls: (() => void)[] = []; // Calls to loadIssue must be deferred because they depend on information from the API.
|
||||
readonly issuesToPass: string[] = []; // Issues to transition to "Pass".
|
||||
readonly issuesToFail: string[] = []; // Issues to transition to "Fail".
|
||||
readonly issuesToCreate: JiraIssue[] = []; // Issues to be batch created.
|
||||
existingSubtasks: JiraIssue[] = []; // Array to hold existing subtasks of the dev card.
|
||||
testCaseTypeId: string|undefined = undefined; // ID of the test case subtask type in Jira. Will be filled by API call.
|
||||
bugTypeId:string|undefined = undefined; // ID of the bug subtask type.
|
||||
userStoryTypeId:string|undefined = undefined;
|
||||
parentCard: GetIssueResponse | undefined = undefined; // Variable to hold the parent card. Will be filled by API call.
|
||||
|
||||
// Will need this if we want to move ortoni report upload into this reporter.
|
||||
// readonly ortoniReport = new OrtoniReport(reportConfig);
|
||||
|
||||
/**
|
||||
* This function loads the IDs for the Jira Issue Types we use.
|
||||
*
|
||||
*/
|
||||
async loadJiraIssueTypes() {
|
||||
console.log(`JiraWritebackReporter >> Loading Jira Issue Types for project '${jiraProjectKey}'...`);
|
||||
const issueTypesRes = await this.jiraApiUtil.getIssueTypes(jiraProjectKey);
|
||||
const issueTypes = issueTypesRes.data;
|
||||
this.testCaseTypeId = issueTypes.issueTypes.find(issueType => {
|
||||
return issueType.subtask === true && issueType.name === 'Test Case Sub-task'
|
||||
})?.id;
|
||||
this.bugTypeId = issueTypes.issueTypes.find(issueType => {
|
||||
return issueType.subtask === true && issueType.name === 'Bug Sub-task'
|
||||
})?.id;
|
||||
this.userStoryTypeId = issueTypes.issueTypes.find(issueType => {
|
||||
return issueType.name === 'Story';
|
||||
})?.id;
|
||||
console.log('JiraWritebackReporter >> Loaded issue types.');
|
||||
}
|
||||
|
||||
/**
|
||||
* This function generates a test subtask based on the test results we pass in.
|
||||
* @param test TestCase from onTestEnd()
|
||||
* @param result TestResult from onTestEnd()
|
||||
* @returns Jira Test Subtask based on test result.
|
||||
*/
|
||||
getCreateTestSubtask(test: TestCase, result: TestResult) {
|
||||
if (result.status === 'skipped') {
|
||||
return undefined;
|
||||
}
|
||||
const testStepTitles = result.steps.map(step => {
|
||||
return `-\t${step.title}`;
|
||||
}).join('\n');
|
||||
const allErrors = result.errors.map(value => {
|
||||
return value.message
|
||||
}).join('\n');
|
||||
|
||||
const description = `Most recent test status: ${result.status}.\nDuration: ${result.duration/1000} seconds.\nErrors:\n${allErrors}`
|
||||
const testSubtaskBody: JiraIssueFields = {
|
||||
summary: test.title,
|
||||
description: {
|
||||
content: [
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"text": description,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"type": "paragraph"
|
||||
}
|
||||
],
|
||||
"type": "doc",
|
||||
"version": 1
|
||||
},
|
||||
project: {
|
||||
key: jiraProjectKey
|
||||
},
|
||||
issuetype: {
|
||||
id: this.testCaseTypeId!
|
||||
},
|
||||
parent: {
|
||||
key: jiraCardNumber
|
||||
},
|
||||
customfield_14857: {
|
||||
type: 'doc',
|
||||
version: 1,
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: testStepTitles
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
return testSubtaskBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function generates a bug subtask based on the test results we pass in.
|
||||
* @param test TestCase from onTestEnd()
|
||||
* @param result TestResult from onTestEnd()
|
||||
* @returns Jira Bug Subtask based on test result.
|
||||
*/
|
||||
getCreateBug(test: TestCase, result: TestResult) {
|
||||
if (result.status === 'passed' || result.status === 'skipped') {
|
||||
return undefined;
|
||||
}
|
||||
const allErrors = result.errors.map(value => {
|
||||
return value.message
|
||||
}).join('\n');
|
||||
|
||||
const bug: JiraIssueFields = {
|
||||
summary: `TEST FAILED ${formatDate(new Date())}: ${test.title}`,
|
||||
description: {
|
||||
content: [
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"text": allErrors,
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"type": "paragraph"
|
||||
}
|
||||
],
|
||||
"type": "doc",
|
||||
"version": 1
|
||||
},
|
||||
project: {
|
||||
key: jiraProjectKey
|
||||
},
|
||||
issuetype: {
|
||||
id: this.bugTypeId!
|
||||
},
|
||||
parent: {
|
||||
key: jiraCardNumber
|
||||
}
|
||||
};
|
||||
return bug;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function gives us the correct transition for a test subtask based on this test result.
|
||||
* @param result TestResult from onTestEnd()
|
||||
* @returns Correct transition to pass to the Jira API.
|
||||
*/
|
||||
getSubtaskTransition(result: TestResult): { id: string } | undefined {
|
||||
if (result.status === 'skipped') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (result.status === 'passed') {
|
||||
return {
|
||||
id: passTransitionId
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
id: failTransitionId
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param cardName
|
||||
* @param sprintId
|
||||
* @returns
|
||||
*/
|
||||
getCreateRegressionCardRequestBody(cardName: string, sprintId: number) {
|
||||
const body: JiraIssueFields = {
|
||||
summary: cardName,
|
||||
description: {
|
||||
content: [
|
||||
{
|
||||
"content": [
|
||||
{
|
||||
"text": 'Card to hold regression test results for this sprint.',
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"type": "paragraph"
|
||||
}
|
||||
],
|
||||
"type": "doc",
|
||||
"version": 1
|
||||
},
|
||||
project: {
|
||||
key: jiraProjectKey
|
||||
},
|
||||
issuetype: { id: this.userStoryTypeId!},
|
||||
parent: { key: jiraEpicKey },
|
||||
customfield_10007: sprintId,
|
||||
customfield_13100: { id: '557058:a314fc5b-aed9-4472-90f8-00f106e06207' } // Mark UAT Tester as N/A
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grabs parent card and existing subtasks.
|
||||
*/
|
||||
async loadReporterData() {
|
||||
let parentCard: GetIssueResponse;
|
||||
await this.loadJiraIssueTypes();
|
||||
if (isRegressionRun) {
|
||||
console.log('JiraWritebackReporter >> Searching for current regression card...');
|
||||
const sprint = await this.jiraApiUtil.getCurrentSprint();
|
||||
if (!sprint) {
|
||||
console.error('JiraWritebackReporter >> Could not find current sprint');
|
||||
} else {
|
||||
console.log(`JiraWritebackReporter >> Found sprint with id '${sprint.id}' called '${sprint.name}'`);
|
||||
}
|
||||
const regressionCardName = `Playwright Automated Regression Tests ${sprint?.name}`;
|
||||
const parentRes = await this.jiraApiUtil.getJqlSearchIssue({ jql: `sprint = ${sprint?.id} and summary ~ "${regressionCardName}"` });
|
||||
if (parentRes.data.issues.length > 1) {
|
||||
console.log('JiraWritebackReporter >> WARNING: Multiple regression cards found. Using first one.');
|
||||
parentCard = (await this.jiraApiUtil.getIssue(parentRes.data.issues[0].id!)).data;
|
||||
await this.jiraApiUtil.postTransitionIssue(parentCard.id!, {
|
||||
transition: {
|
||||
id: inTestTransitionId
|
||||
}
|
||||
});
|
||||
} else if (parentRes.data.issues.length < 1) {
|
||||
// NO regression card found. Make one.
|
||||
console.log('JiraWritebackReporter >> No regression card was found for this sprint. Creating one.');
|
||||
const fields = this.getCreateRegressionCardRequestBody(regressionCardName, sprint!.id);
|
||||
const regressionCard: JiraIssue = {
|
||||
fields: fields,
|
||||
transition: {
|
||||
id: inTestTransitionId
|
||||
}
|
||||
};
|
||||
const createRes = await this.jiraApiUtil.postCreateIssue(regressionCard);
|
||||
regressionCard.id = createRes.data.id;
|
||||
regressionCard.key = createRes.data.key;
|
||||
parentCard = regressionCard;
|
||||
} else {
|
||||
// Found one
|
||||
console.log('JiraWritebackReporter >> Found one regression card. Using it.');
|
||||
parentCard = (await this.jiraApiUtil.getIssue(parentRes.data.issues[0].id!)).data;
|
||||
await this.jiraApiUtil.postTransitionIssue(parentCard.id!, {
|
||||
transition: {
|
||||
id: inTestTransitionId
|
||||
}
|
||||
});
|
||||
}
|
||||
jiraCardNumber = parentCard.key!;
|
||||
console.log(`JiraWritebackReporter >> Retrieved current regression card: ${parentCard.key}: ${parentCard.fields.summary}`);
|
||||
} else {
|
||||
console.log('JiraWritebackReporter >> Searching for parent card...');
|
||||
const parentRes = await this.jiraApiUtil.getIssue(jiraCardNumber);
|
||||
parentCard = parentRes.data;
|
||||
console.log(`JiraWritebackReporter >> Retrieved parent card: ${parentCard.key}: ${parentCard.fields.summary}`);
|
||||
}
|
||||
|
||||
console.log(`JiraWritebackReporter >> Searching for all subtasks of parent card '${jiraCardNumber}'...`);
|
||||
const subtaskIds = parentCard.fields.subtasks?.map(subtask => {
|
||||
return subtask.id;
|
||||
});
|
||||
|
||||
if (subtaskIds && subtaskIds.length > 0) {
|
||||
for (let i = 0; i < subtaskIds.length; i+=50) {
|
||||
const batch = subtaskIds.slice(i, i+50);
|
||||
const subTasksRes = await this.jiraApiUtil.postBulkFetchIssues({ issueIdsOrKeys: batch });
|
||||
this.existingSubtasks.push(...subTasksRes.data.issues);
|
||||
}
|
||||
console.log('JiraWritebackReporter >> Subtasks retrieved.');
|
||||
} else {
|
||||
console.log('JiraWritebackReporter >> No subtasks were found.');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk creates test cases and bugs in batches of up to 50. Bulk transitions existing test case subtasks.
|
||||
*/
|
||||
async writeResults() {
|
||||
for (let i = 0; i < this.issuesToCreate.length; i+=50) {
|
||||
const batch = this.issuesToCreate.slice(i, i + 50);
|
||||
console.log('JiraWritebackReporter >> Creating issue batch...');
|
||||
// fire request
|
||||
await this.jiraApiUtil.postBulkCreateIssue({ issueUpdates: batch });
|
||||
console.log('JiraWritebackReporter >> Issue batch created.');
|
||||
}
|
||||
|
||||
if (this.issuesToPass.length > 0) {
|
||||
console.log('JiraWritebackReporter >> Transitioning passed test cases to "Pass"...')
|
||||
await this.jiraApiUtil.postBulkTransitionIssues( {
|
||||
bulkTransitionInputs: [{
|
||||
selectedIssueIdsOrKeys: this.issuesToPass,
|
||||
transitionId: passTransitionId
|
||||
}],
|
||||
sendBulkNotification: false
|
||||
});
|
||||
console.log('JiraWritebackReporter >> Transition success.');
|
||||
}
|
||||
|
||||
if (this.issuesToFail.length > 0) {
|
||||
console.log('JiraWritebackReporter >> Transitioning failed test cases to "Fail"...')
|
||||
await this.jiraApiUtil.postBulkTransitionIssues( {
|
||||
bulkTransitionInputs: [{
|
||||
selectedIssueIdsOrKeys: this.issuesToFail,
|
||||
transitionId: failTransitionId
|
||||
}],
|
||||
sendBulkNotification: false
|
||||
});
|
||||
console.log('JiraWritebackReporter >> Transition success.')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and loads appropriate response based on test result. This can be a test subtask/bug subtask or a call to transition a test subtask.
|
||||
* @param test TestCase from onTestEnd()
|
||||
* @param result TestResult from onTestEnd()
|
||||
* @returns Void
|
||||
*/
|
||||
loadIssue(test: TestCase, result: TestResult) {
|
||||
if (result.status === 'skipped') return;
|
||||
if (result.status !== 'passed' && result.retry < test.retries) return; // Skip if test case failed and this isn't the last retry.
|
||||
|
||||
let existingTestSubtask: JiraIssue | undefined = undefined;
|
||||
let existingBug: JiraIssue | undefined = undefined;
|
||||
const subtaskTransition = this.getSubtaskTransition(result)!; // Get either a Pass or Fail transition depending on test results.
|
||||
|
||||
console.log('JiraWritebackReporter >> Checking for existing subtasks for this test...');
|
||||
for (const card of this.existingSubtasks) {
|
||||
if (card.fields.issuetype?.id === this.testCaseTypeId) {
|
||||
if (card.fields.summary === test.title) {
|
||||
existingTestSubtask = card;
|
||||
console.log('JiraWritebackReporter >> Found existing test subtask.');
|
||||
}
|
||||
} else if (card.fields.issuetype?.id === this.bugTypeId) {
|
||||
if (new RegExp(`^TEST FAILED [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]: ${test.title}$`).test(card.fields.summary)) {
|
||||
existingBug = card;
|
||||
console.log('JiraWritebackReporter >> Found existing bug.');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!(existingTestSubtask || existingBug)) {
|
||||
console.log('JiraWritebackReporter >> No existing test subtask or bug was found.')
|
||||
}
|
||||
|
||||
const subTask = this.getCreateTestSubtask(test, result); // Will return undefined if test status is 'skipped'
|
||||
const bug = this.getCreateBug(test, result); // Will return undefined if we don't need one
|
||||
if (subTask) {
|
||||
// Create or Edit Subtask
|
||||
if (existingTestSubtask) {
|
||||
if (result.status === 'passed') {
|
||||
this.issuesToPass.push(existingTestSubtask.key!);
|
||||
} else {
|
||||
this.issuesToFail.push(existingTestSubtask.key!);
|
||||
}
|
||||
} else {
|
||||
this.issuesToCreate.push({
|
||||
fields: subTask,
|
||||
transition: subtaskTransition
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (bug) {
|
||||
// Create or Edit Bug
|
||||
if (existingBug) {
|
||||
// Leave it alone. This reporter should not modify existing bugs.
|
||||
} else {
|
||||
this.issuesToCreate.push({
|
||||
fields: bug
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function loads calls to loadIssue() in an array to be executed when the necessary information from the Jira API is available.
|
||||
* @param test
|
||||
* @param result
|
||||
*/
|
||||
onTestEnd(test: TestCase, result: TestResult) {
|
||||
this.ortoniReport.onTestEnd(test, result);
|
||||
this.loadIssueCalls.push(() => { this.loadIssue(test, result); });
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes calls to load and execute API calls to Jira.
|
||||
* @param result
|
||||
* @returns Promise to write results to Jira.
|
||||
*/
|
||||
onEnd(result: FullResult): Promise<{ status?: FullResult["status"]; } | undefined | void>|void {
|
||||
return this.ortoniReport.onEnd(result).then(async () => {
|
||||
await this.loadReporterData().then(async () => {
|
||||
this.loadIssueCalls.map(fn => fn());
|
||||
await this.writeResults();
|
||||
}).then(async () => {
|
||||
console.log('JiraWritebackReporter >> Uploading Ortoni HTML Report to Jira...');
|
||||
await this.jiraApiUtil.postUploadAttachment(jiraCardNumber, `${reportConfig.folderPath}/${reportConfig.filename}`);
|
||||
console.log('JiraWritebackReporter >> Uploaded Ortoni HTML Report to Jira.');
|
||||
console.log(`JiraWritebackReporter >> Posted necessary changes for '${jiraCardNumber}'`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
onBegin(config: FullConfig, suite: Suite): Promise<void> {
|
||||
return this.ortoniReport.onBegin(config, suite);
|
||||
}
|
||||
|
||||
onError(error: TestError): void {
|
||||
return this.ortoniReport.onError(error);
|
||||
}
|
||||
|
||||
onExit(): Promise<void> {
|
||||
return this.ortoniReport.onExit();
|
||||
}
|
||||
|
||||
onStdOut(chunk: string | Buffer, test: void | TestCase, result: void | TestResult): void {
|
||||
return this.ortoniReport.onStdOut(chunk, test, result);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
export function formatDate(date: Date) {
|
||||
const isoString = date.toISOString();
|
||||
return isoString.slice(0, 10);
|
||||
}
|
||||
|
||||
export function formatDateForFilename(date: Date) {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hour = String(date.getHours()).padStart(2, '0');
|
||||
const minute = String(date.getMinutes()).padStart(2, '0');
|
||||
return `${year}${month}${day}_${hour}${minute}`;
|
||||
}
|
||||
|
||||
export function formatTime(date: Date) {
|
||||
return date.toLocaleTimeString('en-US', {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
hour12: true
|
||||
});
|
||||
}
|
||||
|
||||
export function getNextWeekday(date?: Date) {
|
||||
if (!date) {
|
||||
date = new Date();
|
||||
date.setHours(8,0,0,0);
|
||||
}
|
||||
const dayOfWeek = date.getDay();
|
||||
const daysToAdd = dayOfWeek === 5? 3: 1; // Add 3 days if today is friday. Otherwise add 1.
|
||||
|
||||
const nextDay = new Date(date);
|
||||
nextDay.setDate(date.getDate() + daysToAdd);
|
||||
|
||||
return nextDay;
|
||||
}
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
function isFlags(enumObj: object): boolean {
|
||||
const values = Object.values(enumObj).filter(v => typeof v === "number");
|
||||
return values.some(v => v !== 0 && (v & (v - 1)) === 0);
|
||||
}
|
||||
|
||||
function isValidEnumValue(value: any, enumType: object): boolean {
|
||||
if (isFlags(enumType)) {
|
||||
const allFlags = Object.values(enumType).reduce((acc, val) => typeof val === "number" ? acc | val : acc, 0);
|
||||
return typeof value === "number" && (value & allFlags) === value;
|
||||
} else {
|
||||
return Object.values(enumType).includes(value);
|
||||
}
|
||||
}
|
||||
|
||||
function getEnumValues(enumObj: object): string[] | number[] {
|
||||
if (!isFlags(enumObj))
|
||||
return Object.values(enumObj);
|
||||
return Object.values(enumObj).filter(value => typeof value === "number") as number[];
|
||||
}
|
||||
|
||||
function getEnumString<T extends { [key: string]: string | number }>(enumObj: T, flags: number): string {
|
||||
if (flags === 0)
|
||||
return Object.keys(enumObj).find(key => enumObj[key] === 0) || 'None';
|
||||
|
||||
const attributes = Object.entries(enumObj).filter(([key, value]) =>
|
||||
typeof value === 'number' && value !== 0 && (flags & value) === value)
|
||||
.map(([key]) => key);
|
||||
|
||||
return attributes.length === 1 ? attributes[0] : attributes.join(', ');
|
||||
}
|
||||
|
||||
function validateEnumProperty(json: any, property: string, enumType: object): number | string | null {
|
||||
if (json.hasOwnProperty(property) && isValidEnumValue(json[property], enumType))
|
||||
return json[property];
|
||||
return null;
|
||||
}
|
||||
|
||||
function hasFlag(value: number | string, flag: number | string, enumType: object): boolean {
|
||||
if (isFlags(enumType))
|
||||
return typeof value === "number" && typeof flag === "number" && (value & flag) === flag;
|
||||
else
|
||||
return value === flag;
|
||||
}
|
||||
|
||||
function addFlag(value: number, flag: number, enumType: object): number {
|
||||
if (isFlags(enumType))
|
||||
return value | flag;
|
||||
else
|
||||
throw new Error("Attempted to add flag on non-flags enum");
|
||||
}
|
||||
|
||||
function removeFlag(value: number, flag: number, enumType: object): number {
|
||||
if (isFlags(enumType))
|
||||
return value & ~flag;
|
||||
else
|
||||
throw new Error("Attempted to remove flag on non-flags enum");
|
||||
}
|
||||
|
||||
function toggleFlag(value: number, flag: number, enumType: object): number {
|
||||
if (isFlags(enumType))
|
||||
return value ^ flag;
|
||||
else
|
||||
throw new Error("Attempted to toggle flag on non-flags enum");
|
||||
}
|
||||
|
||||
const EnumUtils = {
|
||||
hasFlag,
|
||||
addFlag,
|
||||
removeFlag,
|
||||
toggleFlag,
|
||||
validateEnumProperty,
|
||||
getEnumValues,
|
||||
getEnumString
|
||||
};
|
||||
|
||||
export default EnumUtils;
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
export default class FakerUtils {
|
||||
private static NUMBERS = '0123456789';
|
||||
private static UPPERCASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
private static LOWERCASE = 'abcdefghijklmnopqrstuvwxyz';
|
||||
private static ALPHABET = FakerUtils.UPPERCASE + FakerUtils.LOWERCASE;
|
||||
private static ALPHANUMERIC = FakerUtils.NUMBERS + FakerUtils.ALPHABET;
|
||||
|
||||
private static generateRandomString(length: number, characters: string): string {
|
||||
return Array.from(crypto.getRandomValues(new Uint8Array(length)))
|
||||
.map(byte => characters[byte % characters.length])
|
||||
.join('');
|
||||
}
|
||||
|
||||
public static generateRandomNumber(min: number, max: number): number {
|
||||
const range = max - min + 1;
|
||||
const bytesNeeded = Math.ceil(Math.log2(range) / 8);
|
||||
const randomBytes = new Uint8Array(bytesNeeded);
|
||||
crypto.getRandomValues(randomBytes);
|
||||
const randomValue = randomBytes.reduce((acc, byte) => (acc << 8) + byte, 0);
|
||||
return min + (randomValue % range);
|
||||
}
|
||||
|
||||
public static getRandomTestID(): string {
|
||||
return FakerUtils.generateRandomString(8, FakerUtils.ALPHANUMERIC);
|
||||
}
|
||||
|
||||
public static getRandomProperty(obj: Record<string, any>): string {
|
||||
const keys = Object.keys(obj);
|
||||
const randomIndex = FakerUtils.generateRandomNumber(0, keys.length - 1);
|
||||
return keys[randomIndex];
|
||||
}
|
||||
|
||||
public static getRandomTail(registrationPrefix: string = "XX", testID: string = ""): string {
|
||||
return FakerUtils.formatString(FakerUtils.generateRandomString(8, FakerUtils.ALPHANUMERIC));
|
||||
}
|
||||
|
||||
public static getRandomEmail(domainSuffix: string = "@test.com", testID: string = ""): string {
|
||||
const retval = FakerUtils.formatString("{0}{1}", FakerUtils.generateRandomString(8, FakerUtils.ALPHANUMERIC), domainSuffix);
|
||||
return retval;
|
||||
}
|
||||
|
||||
public static getRandomLastName(testID: string = " - "): string {
|
||||
return FakerUtils.formatString(" - {0}", FakerUtils.generateRandomString(21, FakerUtils.ALPHABET));
|
||||
}
|
||||
|
||||
public static getObjectName(prefix: string, testID: string = ""): string {
|
||||
return FakerUtils.formatString("{0} - {1}", prefix, FakerUtils.generateRandomString(21, FakerUtils.ALPHANUMERIC));
|
||||
}
|
||||
|
||||
private static formatString(template: string, ...args: (string | (() => string))[]): string {
|
||||
return template.replace(/\{(\d+)\}/g, (match, index) => {
|
||||
const arg = args[parseInt(index)];
|
||||
return typeof arg === 'function' ? arg() : arg || '';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export function writeFileToLocalCache(fileNameWithExtension: string, fileContents: string) {
|
||||
const folderPath = path.join(process.cwd(), '.debug', '.cache');
|
||||
const filePath = path.join(folderPath, fileNameWithExtension);
|
||||
|
||||
|
||||
try {
|
||||
// Create the folder if it doesn't exist
|
||||
if (!fs.existsSync(folderPath)) {
|
||||
fs.mkdirSync(folderPath, { recursive: true });
|
||||
}
|
||||
|
||||
// Write the data to the file
|
||||
fs.writeFileSync(filePath, fileContents);
|
||||
|
||||
console.log(`File "${filePath}" created successfully.`);
|
||||
} catch (error) {
|
||||
console.error('Error creating the file:', error);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
import { Page } from '@playwright/test';
|
||||
import { type AxiosInstance, type AxiosResponse } from 'axios';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
export async function httpGet<T>(client: AxiosInstance, url: string): Promise<T> {
|
||||
const [isSuccess, response] = await handleHttp(client.get<T>(url));
|
||||
if(isSuccess) {
|
||||
return response;
|
||||
}
|
||||
|
||||
console.error(`An error occurred calling GET ${url}\nError:${response}`);
|
||||
throw response;
|
||||
}
|
||||
|
||||
export async function httpPost<T, D>(client: AxiosInstance, url: string, data: D): Promise<T> {
|
||||
const [isSuccess, response] = await handleHttp(client.post<T>(url, data));
|
||||
if(isSuccess) {
|
||||
return response;
|
||||
}
|
||||
|
||||
console.error(`An error occurred calling POST ${url}\nError:${response}`);
|
||||
throw response;
|
||||
}
|
||||
|
||||
export function handleHttp<T>(request: Promise<AxiosResponse<T>>): Promise<[isSuccess: true, data: T] | [isSuccess: false, error: Error]> {
|
||||
return request.then(data => {
|
||||
return [true, data.data] as [true, T]
|
||||
}).catch((error: Error) => {
|
||||
return [false, error] as [false, Error]
|
||||
})
|
||||
}
|
||||
|
||||
export function buildQueryString<T>(data: T): URLSearchParams {
|
||||
const params: Record<string, string> = {};
|
||||
for (const key in data) {
|
||||
const value = data[key];
|
||||
params[key] = `${value}`;
|
||||
}
|
||||
return new URLSearchParams(params);
|
||||
}
|
||||
|
||||
export function forceAPIError(page: Page, endpoint: string) {
|
||||
page.route('**/*', (route) => {
|
||||
return route.request().url().includes(endpoint)
|
||||
? route.abort()
|
||||
: route.continue()
|
||||
});
|
||||
}
|
||||
|
||||
// Utility method to return mock response based on endpoint and scenario
|
||||
export function getMockedApiResponse(endpoint: string, scenario: string): object | null {
|
||||
const mockResponsesDir = path.resolve(__dirname, '../../tests/mockResponses');
|
||||
const configFilePath = path.join(mockResponsesDir, 'mockResponsesConfig.json');
|
||||
|
||||
if (fs.existsSync(configFilePath)) {
|
||||
const config = JSON.parse(fs.readFileSync(configFilePath, 'utf-8'));
|
||||
const scenarioConfig = config[scenario];
|
||||
const commonConfig = config['common'];
|
||||
|
||||
let mockFilePath = scenarioConfig ? scenarioConfig[endpoint] : null;
|
||||
if (!mockFilePath && commonConfig) {
|
||||
mockFilePath = commonConfig[endpoint];
|
||||
}
|
||||
|
||||
if (mockFilePath) {
|
||||
const filePath = path.join(mockResponsesDir, mockFilePath);
|
||||
if (fs.existsSync(filePath)) {
|
||||
const mockResponse = fs.readFileSync(filePath, 'utf-8');
|
||||
return JSON.parse(mockResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Utility method for mocking API responses
|
||||
export function mockApiResponse(page: Page, endpoint: string, scenario: string, mockTestingFlag: boolean) {
|
||||
const mockResponse = getMockedApiResponse(endpoint, scenario);
|
||||
page.route(`**/${endpoint}`, route => {
|
||||
if (mockResponse && mockTestingFlag) {
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(mockResponse)
|
||||
});
|
||||
} else {
|
||||
route.continue();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
import { ConsoleColor, ResultTypes } from "@business-logic/types/Enums";
|
||||
|
||||
export default class LoggingUtils {
|
||||
|
||||
public static CONSOLE_WIDTH: number = 100;
|
||||
//public static ICON_OK: string = "✅";
|
||||
public static ICON_OK: string = "\u2705";
|
||||
//public static ICON_SKIP: string = "⏩";
|
||||
public static ICON_SKIP: string = "\u23ED";
|
||||
//public static ICON_WARNING: string = "⚠️";
|
||||
public static ICON_WARNING: string = "\u26A0\uFE0F";
|
||||
//public static ICON_FAIL: string = "❗";
|
||||
public static ICON_FAIL: string = "\u2757";
|
||||
|
||||
public static log(message: string | null, color: ConsoleColor = ConsoleColor.Default): void {
|
||||
if (message == null)
|
||||
return;
|
||||
console.log(`${color}%s${ConsoleColor.Reset}`, message);
|
||||
}
|
||||
|
||||
public static icon(value: boolean): string;
|
||||
public static icon(value: ResultTypes): string;
|
||||
public static icon(value: any): string {
|
||||
if (typeof value === "boolean")
|
||||
return value ? this.ICON_OK : this.ICON_FAIL;
|
||||
|
||||
switch (value) {
|
||||
case ResultTypes.Failure:
|
||||
return this.ICON_FAIL;
|
||||
case ResultTypes.Success:
|
||||
return this.ICON_OK;
|
||||
case ResultTypes.Skipped:
|
||||
return this.ICON_SKIP;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static logFunc(name: string, value: string | null = null, result: boolean | null = null): string {
|
||||
let icon = this.ICON_SKIP;
|
||||
if (result != null)
|
||||
icon = result ? this.ICON_OK : this.ICON_FAIL;
|
||||
|
||||
if (value != null)
|
||||
return `${this.getShortDateTime()} [${this.centerPadString(`${name}: ${this.truncateString(value)}`)}] -> ${icon}`;
|
||||
|
||||
return `${this.getShortDateTime()} [${this.centerPadString(name)}] -> ${icon}`;
|
||||
}
|
||||
|
||||
public static logValidate(text: string, success: boolean) {
|
||||
return `${this.getShortDateTime()} [${this.centerPadString(text)}] -> ${success ? this.ICON_OK : this.ICON_FAIL}`;
|
||||
}
|
||||
|
||||
public static truncateString(value: any, maxLength: number = this.CONSOLE_WIDTH): string {
|
||||
let str = typeof value === 'string' ? value : String(value);
|
||||
str = str.replace(/\s+/g, ' ').trim();
|
||||
if (str.length <= maxLength) {
|
||||
return str;
|
||||
}
|
||||
return str.slice(0, maxLength - 2) + '..';
|
||||
}
|
||||
|
||||
public static sanitizeFileName(input: string): string {
|
||||
// Remove characters that are invalid in both Windows and Linux file systems
|
||||
let sanitized = input.replace(/[<>:"/\\|?*\x00-\x1F]/g, '');
|
||||
|
||||
// Remove leading and trailing spaces and dots
|
||||
sanitized = sanitized.trim().replace(/^\.+|\.+$/g, '');
|
||||
|
||||
// Replace remaining dots and spaces with underscores
|
||||
sanitized = sanitized.replace(/[\s.]+/g, '_');
|
||||
|
||||
// Ensure the name isn't empty after sanitization
|
||||
if (sanitized.length === 0) {
|
||||
sanitized = 'unnamed';
|
||||
}
|
||||
|
||||
// Truncate to a reasonable maximum length (e.g., 255 characters)
|
||||
sanitized = sanitized.slice(0, 255);
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
public static replaceEmptyLinesWithMiddleDot(input: string): string {
|
||||
const emptyLinesRegex: RegExp = /(.+?)(\n\s*\n)+/g;
|
||||
|
||||
return input.replace(emptyLinesRegex, (_match, line) => {
|
||||
return line + '·\n';
|
||||
}).replace(/\n$/, '');
|
||||
}
|
||||
|
||||
private static centerPadString(str: string, length: number = this.CONSOLE_WIDTH): string {
|
||||
if (str.length >= length) {
|
||||
return this.truncateString(str);
|
||||
}
|
||||
|
||||
str = this.truncateString(str);
|
||||
|
||||
const totalPadding = length - str.length;
|
||||
const leftPadding = Math.ceil(totalPadding / 2);
|
||||
const rightPadding = Math.floor(totalPadding / 2);
|
||||
|
||||
return ' '.repeat(leftPadding) + str + ' '.repeat(rightPadding);
|
||||
}
|
||||
|
||||
private static getShortDateTime() {
|
||||
return new Date().toLocaleString('en-US', {
|
||||
year: '2-digit',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false
|
||||
});
|
||||
}
|
||||
|
||||
public static normalizeSalesForceType(input: string, prefix: string = "Apttus_Config2__", suffix: string = "__c") {
|
||||
let result = input;
|
||||
|
||||
if (result.startsWith(prefix))
|
||||
result = result.slice(prefix.length);
|
||||
|
||||
if (result.endsWith(suffix))
|
||||
result = result.slice(0, -suffix.length);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
|
||||
/**
|
||||
* Parses a string containing a representation of currency, and returns a typed number. Can handle
|
||||
* different types of currency represenations.
|
||||
*
|
||||
* USD 12,345.65 -> 12345.65
|
||||
* (USD 12345.00) -> -12345
|
||||
*
|
||||
* @param text String containing currency representation
|
||||
* @param currencyCode Optionally define different currency code
|
||||
* @returns Parsed currency with type number
|
||||
*/
|
||||
export function parseCurrency(text: string, currencyCode: string = "USD"): number {
|
||||
// TODO: Add ability to handle null fields/not treat null as 0 - KK 9/12/24
|
||||
// Base case, if text can be cast as a number then work is done
|
||||
if (!isNaN(+text)) return +text;
|
||||
// Remove parentheses and continue parsing, multiply return by -1 to preserve negative value
|
||||
if (text.charAt(0) === '(') return -1 * parseCurrency(text.substring(1, text.length - 1));
|
||||
// Remove currency code prefix and continue parsing
|
||||
if (text.split(' ')[0] === currencyCode) return parseCurrency(text.split(' ')[1]);
|
||||
// Remove commas and cast to number
|
||||
return Number(text.split(',').join(''));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param text
|
||||
* @returns
|
||||
*/
|
||||
export function parseNumberOrCurrency(text: string): number | string {
|
||||
if (!isNaN(+text)) return +text;
|
||||
else if (text.charAt(0) === '(') return -1 * parseCurrency(text.substring(1, text.length - 1));
|
||||
else if (text.split(' ')[0] === "USD") return parseCurrency(text);
|
||||
else return text;
|
||||
}
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
import { ValidationResult } from "@business-logic/types/RuleEngine";
|
||||
import EnumUtils from "./EnumUtils";
|
||||
|
||||
export type ExtractName<T> = { [K in keyof T]: () => K; };
|
||||
|
||||
type GetValueOptions = {
|
||||
isRequired: boolean;
|
||||
};
|
||||
|
||||
function isNullOrWhiteSpace(input: unknown): boolean {
|
||||
if (typeof input !== "string")
|
||||
return input == null;
|
||||
return input.trim().length === 0;
|
||||
}
|
||||
|
||||
// Matches: '() => _Enums.*****', e.g.: '() => _Enums.ModificationTypes'
|
||||
// Matches: '() => *****', e.g.: '() => ModificationTypes'
|
||||
// Returns: EnumName, e.g.: 'ModificationTypes'
|
||||
function getEnumName(propertySelector: () => object): string {
|
||||
const functionString = propertySelector.toString();
|
||||
const match = functionString.match(/\(\s*\)\s*=>\s*(?:_?[A-Z]\w*\.)?(\w+)(?::)?/);
|
||||
return match ? match[1] : "Unknown";
|
||||
}
|
||||
|
||||
// Matches: 'x => x.*****', e.g.: 'x => x.name', 'x => x.tags'
|
||||
// Returns: PropertyName, e.g.: 'name', 'tags'
|
||||
function getNameof<T>(propertySelector: (obj: T) => any): string {
|
||||
const propertyString = propertySelector.toString();
|
||||
const match = propertyString.match(/(?:=>|return)\s*([\w\s.]+)/);
|
||||
|
||||
if (match && match[1]) {
|
||||
const parts = match[1].split('.');
|
||||
return parts[parts.length - 1].trim();
|
||||
}
|
||||
|
||||
throw new Error(`Invalid property selector: ${propertyString}`);
|
||||
}
|
||||
|
||||
function getValueOrNull<T>(json: any, propertySelector: (obj: T) => any): any | null {
|
||||
if (json == null)
|
||||
return null;
|
||||
|
||||
const property = getNameof(propertySelector);
|
||||
|
||||
if (json.hasOwnProperty(property))
|
||||
return json[property];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getValue<T>(json: any, typeName: string, validationResults: ValidationResult[], options: GetValueOptions, propertySelector: (obj: T) => any): any | null {
|
||||
const retval = getValueOrNull(json, propertySelector);
|
||||
|
||||
if (retval == null)
|
||||
validationResults.push(options.isRequired ? ValidationResult.FromFailure(`Value for ${typeName}.${getNameof(propertySelector)} is null but is required!`) : ValidationResult.FromSuccess(retval, `Value for ${typeName}.${getNameof(propertySelector)} is null but is NOT required.`));
|
||||
else {
|
||||
if (isNullOrWhiteSpace(retval))
|
||||
validationResults.push(ValidationResult.FromFailure(`Value for ${typeName}.${getNameof(propertySelector)} is empty!`));
|
||||
else
|
||||
validationResults.push(ValidationResult.FromSuccess(retval, `Value for ${typeName}.${getNameof(propertySelector)} is valid.`));
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
function hasProperty<T>(json: any, typeName: string, validationResults: ValidationResult[], options: GetValueOptions, propertySelector: (obj: ExtractName<T>) => () => keyof T): boolean {
|
||||
const retval = json.hasOwnProperty(getNameof(propertySelector));
|
||||
|
||||
if (retval)
|
||||
validationResults.push(ValidationResult.FromSuccess(retval, `Value for ${typeName}.${getNameof(propertySelector)} is valid.`));
|
||||
else
|
||||
validationResults.push(options.isRequired ? ValidationResult.FromSuccess(retval, `Value for ${typeName}.${getNameof(propertySelector)} is not defined but is required!`) : ValidationResult.FromFailure(`Value for ${typeName}.${getNameof(propertySelector)} is not defined but is NOT required!`));
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
function getEnumValueOrNull<T>(json: any, enumType: object, propertySelector: (obj: T) => any): any | null {
|
||||
if (json == null)
|
||||
return null;
|
||||
|
||||
const property = getNameof(propertySelector);
|
||||
const retval = EnumUtils.validateEnumProperty(json, property, enumType);
|
||||
|
||||
if (retval != null)
|
||||
return retval;
|
||||
else if (Object.values(enumType).includes(json[property]))
|
||||
return json[property];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getEnumValue<T>(json: any, typeName: string, validationResults: ValidationResult[], enumType: object, enumTypeInstance: () => object, options: GetValueOptions, propertySelector: (obj: T) => any): any | null {
|
||||
const retval = getEnumValueOrNull(json, enumType, propertySelector);
|
||||
|
||||
if (retval == null)
|
||||
validationResults.push(options.isRequired ? ValidationResult.FromFailure(`Value for ${typeName}.${getNameof(propertySelector)} [${getEnumName(enumTypeInstance)}] is null but is required!`) : ValidationResult.FromSuccess(retval, `Value for ${typeName}.${getNameof(propertySelector)} [${getEnumName(enumTypeInstance)}] is null but is NOT required.`));
|
||||
else
|
||||
validationResults.push(ValidationResult.FromSuccess(retval, `Value for ${typeName}.${getNameof(propertySelector)} is valid.`));
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
export { getEnumName, getEnumValueOrNull, getNameof, getValueOrNull };
|
||||
|
||||
const PropertyUtils = {
|
||||
hasProperty,
|
||||
getValue,
|
||||
getEnumValue
|
||||
};
|
||||
|
||||
export default PropertyUtils;
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
export function formatTag(text: string): string {
|
||||
return "@" + text.replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, (match, index) => {
|
||||
if (+match === 0) return ""; // Remove non-alphanumeric characters
|
||||
return index === 0 ? match.toLowerCase() : match.toUpperCase();
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @description - Use this to add calculated/standardized/randomized tags to scenarioData prior to running. Randomly adds a smoke tag to 1 of the testCases.
|
||||
* @param scenarioData - the data from your test case
|
||||
* @returns scenarioData, but with added tags.
|
||||
*/
|
||||
|
||||
export function getRandomTestName(scenarioData: any) : string {
|
||||
const testCaseNames: string[] = Object.keys(scenarioData);
|
||||
const totalKeys = testCaseNames.length
|
||||
const randomIndex = Math.floor(Math.random() * totalKeys) - 1;
|
||||
const randomKey = testCaseNames[randomIndex];
|
||||
|
||||
return randomKey;
|
||||
}
|
||||
|
||||
export function metaTags(currentTestName: string, randomTestName: string) : string [] {
|
||||
return currentTestName == randomTestName ? ["@smoke", "@standardRegression"] : ["@standardRegression"]
|
||||
}
|
||||
|
||||
export function matchAndReplaceContactDataTag(data: string[], replacement: string): string[] {
|
||||
return data.map((value) => value.replace(/[{]{2}contact[}]{2}/, replacement));
|
||||
}
|
||||
|
||||
export function matchAndReplaceAccountDataTag(data: string[], replacement: string): string[] {
|
||||
return data.map((value) => value.replace(/[{]{2}account[}]{2}/, replacement));
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
import { error } from "console";
|
||||
|
||||
|
||||
export function throwIf(conditionFunction: () => boolean, errorMessage: string): void {
|
||||
if (conditionFunction())
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
|
||||
export function throwNotYetImplemented(nameOfThingNotImplemented: string) {
|
||||
throw new Error(`${nameOfThingNotImplemented} has not yet been implemented.`)
|
||||
}
|
||||
|
|
@ -1,273 +0,0 @@
|
|||
import { expect, Locator, Page } from "@playwright/test";
|
||||
import LoggingUtils from "./LoggingUtils";
|
||||
import { DateTime } from "luxon";
|
||||
|
||||
|
||||
export type TimeoutOpts = {
|
||||
/**
|
||||
* @description timeout Commonly referred to for an entire method; exists to allow developers to specify their own timeout whout overriding defaults
|
||||
*/
|
||||
|
||||
timeout: number
|
||||
/**
|
||||
* @description timeout_tiny use for exceedingly small waits, as in, waiting for label to contain the test you just typed into it.
|
||||
*/
|
||||
timeoutTiny: number
|
||||
|
||||
/**
|
||||
* @description timeout_short for relatively quick opperations, such as waiting for a dropdown to render
|
||||
*/
|
||||
timeoutShort: number
|
||||
|
||||
/**
|
||||
* @description timeout_medium for moderately slow operations, such as a new modal rendering, or a calculated field value being updated, or an API Call
|
||||
*/
|
||||
timeoutMedium: number
|
||||
|
||||
/**
|
||||
* @description timeout_long for high-risk, slow operations. Waiting for the minicart to load, waiting for login, or waiting for screen-to-screen navigation.
|
||||
*/
|
||||
timeoutLong: number
|
||||
|
||||
/**
|
||||
* @description for when things are really, really bad.
|
||||
*/
|
||||
timeoutConga: number
|
||||
}
|
||||
|
||||
export const timeoutOptDefaults: TimeoutOpts = {
|
||||
timeout: 60_000,
|
||||
timeoutTiny: +(process.env.TIMEOUT_TINY ?? 500),
|
||||
timeoutShort: +(process.env.TIMEOUT_SHORT ?? 5000),
|
||||
timeoutMedium: +(process.env.TIMEOUT_MEDIUM ?? 30_000),
|
||||
timeoutLong: +(process.env.TIMEOUT_LONG ?? 180_000),
|
||||
timeoutConga: +(process.env.TIMEOUT_CONGA ?? 500_000),
|
||||
}
|
||||
|
||||
export type WaitUntilOpts = {
|
||||
delayBetweenChecks: number,
|
||||
continueOnTimeoutError: boolean,
|
||||
anticipatedConditionResult: boolean,
|
||||
conditionName: string,
|
||||
beginWaitingMessage: string,
|
||||
delayBetweenChecksMessage: string,
|
||||
timeoutErrorMessage: string,
|
||||
successMessage: string,
|
||||
ignoreErrorsFromConditionFunction: boolean,
|
||||
|
||||
}
|
||||
export const waitUntilOptDefaults: WaitUntilOpts = {
|
||||
delayBetweenChecks: 3000,
|
||||
continueOnTimeoutError: false,
|
||||
anticipatedConditionResult: true,
|
||||
conditionName: "",
|
||||
beginWaitingMessage: "",
|
||||
delayBetweenChecksMessage: "",
|
||||
timeoutErrorMessage: "Timed Out",
|
||||
successMessage: "",
|
||||
ignoreErrorsFromConditionFunction: true,
|
||||
}
|
||||
|
||||
/**
|
||||
* @description repeatedly execute an asynchronous conditional lambda until a given outcome occurs, or the method times-out. Useful for hedgning against GUI race conditions.
|
||||
* @param conditionFunction the condition lambda. Example: ()=>{await return myPage.someButton.isVisible()}
|
||||
* @param options standard Timeout and WaitUntil Options.
|
||||
* @returns true or false - the outcome of the waituntil.
|
||||
*/
|
||||
|
||||
export async function waitUntil(conditionFunction: (...args: any[]) => Promise<boolean>, options: Partial<TimeoutOpts & WaitUntilOpts> = {}): Promise<boolean> {
|
||||
const opts = { ...waitUntilOptDefaults, ...timeoutOptDefaults, ...options }
|
||||
|
||||
const timeoutAt = Date.now() + opts.timeout;
|
||||
let waitUntilHasTimedOut = false;
|
||||
let conditionHasBeenMet = false;
|
||||
do {
|
||||
try {
|
||||
const conditionResult = await conditionFunction()
|
||||
conditionHasBeenMet = conditionResult == opts.anticipatedConditionResult
|
||||
}
|
||||
catch (e) {
|
||||
if (!opts.ignoreErrorsFromConditionFunction) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
waitUntilHasTimedOut = Date.now() > timeoutAt
|
||||
if (!waitUntilHasTimedOut && !conditionHasBeenMet) {
|
||||
await delay(opts.delayBetweenChecks)
|
||||
}
|
||||
else if (waitUntilHasTimedOut && !opts.continueOnTimeoutError) {
|
||||
throw new Error(opts.timeoutErrorMessage)
|
||||
}
|
||||
}
|
||||
while (!conditionHasBeenMet || waitUntilHasTimedOut)
|
||||
return conditionHasBeenMet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Order Matters; WaitUntil each condition passes before moving to the next. All conditions must pass in the expected order.
|
||||
* @param sequentialConditionFunctions an array of async, boolean lambdas to be executed in sqeuance until all have passed.
|
||||
* @param options standard Timeout and WaitUntil options
|
||||
*/
|
||||
export async function waitUntilValueStopsChanging(mercurialValueFunction: (...args: any[]) => Promise<any>, options: Partial<TimeoutOpts & WaitUntilOpts> = {}): Promise<any> {
|
||||
const opts = { ...waitUntilOptDefaults, ...timeoutOptDefaults, ...options }
|
||||
|
||||
let lastFoundValue: any = undefined;
|
||||
const valueHasStoppedChanging = async () => {
|
||||
const newFoundValue = await mercurialValueFunction();
|
||||
if (opts.delayBetweenChecksMessage.length > 0) console.log(`${opts.delayBetweenChecksMessage} - Last Value: ${lastFoundValue}`)
|
||||
const valueIsStable = (newFoundValue != undefined) && (newFoundValue == lastFoundValue);
|
||||
lastFoundValue = newFoundValue;
|
||||
return valueIsStable;
|
||||
}
|
||||
await waitUntil(valueHasStoppedChanging, opts)
|
||||
return lastFoundValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo EXPIRIMENTAL! NO UNIT TESTS YET! TODO, add - DF, 5/23
|
||||
* @description Order Matters; WaitUntil each condition passes before moving to the next. All conditions must pass in the expected order.
|
||||
* @param sequentialConditionFunctions an array of async, boolean lambdas to be executed in sqeuance until all have passed.
|
||||
* @param options standard Timeout and WaitUntil options
|
||||
*/
|
||||
export async function waitUntilEach(sequentialConditionFunctions: ((...args: any[]) => Promise<boolean>)[], options: Partial<TimeoutOpts & WaitUntilOpts> = {}): Promise<any> {
|
||||
for (const conditionFunction of sequentialConditionFunctions) {
|
||||
await waitUntil(conditionFunction, options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo EXPIRIMENTAL! NO UNIT TESTS YET! TODO, add - DF, 5/23
|
||||
* @description Order Matters; WaitUntil each condition passes before moving to the next. All conditions must pass in the expected order.
|
||||
* @param sequentialConditionFunctions an array of async, boolean lambdas to be executed in sequence until all have passed.
|
||||
* @param options standard Timeout and WaitUntil options
|
||||
*/
|
||||
export async function waitUntilAll(sequentialConditionFunctions: ((...args: any[]) => Promise<boolean>)[], options: Partial<TimeoutOpts & WaitUntilOpts> = {}): Promise<any> {
|
||||
for (const conditionFunction of sequentialConditionFunctions) {
|
||||
await waitUntil(conditionFunction, options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo EXPIRIMENTAL! NO UNIT TESTS YET! TODO, add - DF, 5/23
|
||||
* @description Order DOES NOT Matter; WaitUntil ANY condition passes before completing. Use when multiple conditions can give confidence that sufficient waiting has occured.
|
||||
* @param sequentialConditionFunctions an array of async, boolean lambdas to be executed in psudo-parallel until at least one has passed.
|
||||
* @param options standard Timeout and WaitUntil options
|
||||
*/
|
||||
export async function waitUntilAny(multipleRequiredConditionFunctions: ((...args: any[]) => Promise<boolean>)[], options: Partial<TimeoutOpts & WaitUntilOpts> = {}): Promise<any> {
|
||||
const anyConditionMet = async (): Promise<boolean> => {
|
||||
return multipleRequiredConditionFunctions.filter(async (fun: (...args: any[]) => Promise<boolean>): Promise<boolean> => await Function.call(fun)).length > 0;
|
||||
};
|
||||
waitUntil(anyConditionMet, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param milliseconds delay duration
|
||||
* @param logDelay defaults to false; if true, logs a waiting message.
|
||||
*/
|
||||
export async function delay(milliseconds: number, logDelay = false): Promise<void> {
|
||||
if (logDelay) { console.log(`delaying ${milliseconds} milliseconds before continuing...`) }
|
||||
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the page url
|
||||
* @param partialUrl The string we are looking for in the url, to know we have transitioned to the correct page.
|
||||
* @param timeout the amount of milliseconds to wait before giving up.
|
||||
*/
|
||||
export async function waitForUrlPartialMatch(page: Page, firstPartialUrl: string, timeout = 120_000) {
|
||||
const startTime = Date.now();
|
||||
while (Date.now() - startTime < timeout) {
|
||||
if (page.url().includes(firstPartialUrl)) {
|
||||
return; // URL matches the partial string, exit the function
|
||||
}
|
||||
await page.waitForTimeout(100); // Wait for 100 milliseconds before checking again
|
||||
}
|
||||
throw new Error(`Timed out waiting for URL to match '${firstPartialUrl}'`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a function repeatedly until it returns true or the timeout is reached.
|
||||
*
|
||||
* This function executes the provided asynchronous block function in a loop until it returns true or the specified
|
||||
* timeout duration has elapsed. Between each attempt, it waits for a specified delay.
|
||||
*
|
||||
* @param block - An asynchronous function that returns a boolean value. This function will be executed repeatedly until it returns true.
|
||||
* @param timeout - The maximum duration to keep attempting to run the block function, in milliseconds. Default is 150000 (150 seconds).
|
||||
* @param delayMs - The delay duration between each attempt, in milliseconds. Default is 3000 (3 seconds).
|
||||
* @returns A promise that resolves to a boolean value indicating whether the block function eventually returned true.
|
||||
*
|
||||
* @example
|
||||
* // Example usage:
|
||||
* const blockFunction = async () => {
|
||||
* // Some asynchronous condition check
|
||||
* return await someConditionCheck();
|
||||
* };
|
||||
* const result = await runUntilTrue(blockFunction, 10000, 1000);
|
||||
* console.log(result); // Outputs true if blockFunction returned true within the timeout, otherwise false.
|
||||
*/
|
||||
export async function runUntilTrue(block: () => Promise<boolean>, timeout: number = 150000, delayMs: number = 3000){
|
||||
const startTime = DateTime.now();
|
||||
let attempts = 0;
|
||||
let evaluatesToTrue = false;
|
||||
|
||||
do {
|
||||
// If timeout duration has elapsed, stop making attempts
|
||||
if (DateTime.now().diff(startTime).as('milliseconds') > timeout) {
|
||||
break;
|
||||
}
|
||||
|
||||
attempts++;
|
||||
// If retrying, wait delay duration
|
||||
if (attempts > 1) await delay(delayMs);
|
||||
|
||||
evaluatesToTrue = await block();
|
||||
|
||||
} while (!evaluatesToTrue);
|
||||
|
||||
return evaluatesToTrue;
|
||||
}
|
||||
|
||||
// TODO move this to impl/utils/WaitingUtils when that related pr is available in devleop branch - T.S. 5/20/24
|
||||
export async function waitForEither(block1: () => Promise<any>, block2: () => Promise<any>, timeOut: number = 180_000): Promise<void> {
|
||||
const startTime = Date.now();
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
const result1 = await block1();
|
||||
const result2 = await block2();
|
||||
|
||||
// Check if either result is truthy (i.e., not falsy or undefined)
|
||||
if (result1 || result2) {
|
||||
// At least one block returned a truthy value, resolve the promise
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
// Handle errors thrown by either block
|
||||
console.error("An error occurred:", error);
|
||||
}
|
||||
|
||||
// Check if the timeout has been reached
|
||||
if (Date.now() - startTime >= timeOut) {
|
||||
throw new Error(`Timeout of ${timeOut} ms exceeded`);
|
||||
}
|
||||
|
||||
// Add some delay before checking again
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)); // Adjust delay as needed
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for a specific locator to show up on screen, then disappear. Typically used for things like progress bars.
|
||||
* @param locator The locator we want to become visible and then become hidden
|
||||
*/
|
||||
export async function waitToAppearAndDisappear(locator: Locator): Promise<void> {
|
||||
try {
|
||||
await expect(locator).toBeVisible({ timeout: 60000 });
|
||||
await expect(locator).toBeHidden({ timeout: 60000 });
|
||||
} catch (err) {
|
||||
if (err instanceof Error)
|
||||
console.log(LoggingUtils.logFunc(waitToAppearAndDisappear.name, err.message, false));
|
||||
else
|
||||
console.log(LoggingUtils.logFunc(waitToAppearAndDisappear.name, null, false));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,132 +0,0 @@
|
|||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
playwright-tests/package-lock.json
generated
16
playwright-tests/package-lock.json
generated
|
|
@ -17,13 +17,13 @@
|
|||
"@faker-js/faker": "^9.8.0",
|
||||
"@playwright/test": "^1.52.0",
|
||||
"@types/dotenv-safe": "^8.1.6",
|
||||
"@types/node": "^22.15.30",
|
||||
"@types/node": "^22.15.32",
|
||||
"dotenv-safe": "^9.1.0",
|
||||
"eslint": "^9.28.0",
|
||||
"luxon": "^3.6.1",
|
||||
"ortoni-report": "^3.0.2",
|
||||
"playwright-jira-reporter": "^1.0.4",
|
||||
"safelite-playwright-core": "^1.0.14",
|
||||
"safelite-playwright-core": "^1.0.20",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
},
|
||||
|
|
@ -354,9 +354,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.15.30",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.30.tgz",
|
||||
"integrity": "sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==",
|
||||
"version": "22.15.32",
|
||||
"resolved": "https://pkgs.dev.azure.com/Safelite/Digital/_packaging/DigitalQA/npm/registry/@types/node/-/node-22.15.32.tgz",
|
||||
"integrity": "sha1-wwHMInW1NaXlS7gdUWsdLpr+BuU=",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
@ -3278,9 +3278,9 @@
|
|||
"peer": true
|
||||
},
|
||||
"node_modules/safelite-playwright-core": {
|
||||
"version": "1.0.14",
|
||||
"resolved": "https://pkgs.dev.azure.com/Safelite/Digital/_packaging/DigitalQA/npm/registry/safelite-playwright-core/-/safelite-playwright-core-1.0.14.tgz",
|
||||
"integrity": "sha1-e+5FwglTzzRjZeLB4wOHGiOvsXI=",
|
||||
"version": "1.0.20",
|
||||
"resolved": "https://pkgs.dev.azure.com/Safelite/Digital/_packaging/DigitalQA/npm/registry/safelite-playwright-core/-/safelite-playwright-core-1.0.20.tgz",
|
||||
"integrity": "sha1-9y9mZfuQ1RRvwc3vgkyt/DL4EXk=",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -25,13 +25,13 @@
|
|||
"@faker-js/faker": "^9.8.0",
|
||||
"@playwright/test": "^1.52.0",
|
||||
"@types/dotenv-safe": "^8.1.6",
|
||||
"@types/node": "^22.15.30",
|
||||
"@types/node": "^22.15.32",
|
||||
"dotenv-safe": "^9.1.0",
|
||||
"eslint": "^9.28.0",
|
||||
"luxon": "^3.6.1",
|
||||
"ortoni-report": "^3.0.2",
|
||||
"playwright-jira-reporter": "^1.0.4",
|
||||
"safelite-playwright-core": "^1.0.14",
|
||||
"safelite-playwright-core": "^1.0.20",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"private": "true"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Locator, Page } from "@playwright/test";
|
||||
import { BasePage } from "./BasePage";
|
||||
import { IPaymentDetails } from "@business-logic/types/CustomerDetails";
|
||||
import { IPaymentDetails } from 'safelite-playwright-core';
|
||||
|
||||
export class AfterpayPage extends BasePage {
|
||||
readonly page: Page;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { IAppointmentDetails, ICustomerDetails } from '@business-logic/types/CustomerDetails';
|
||||
import Soft from '@business-logic/validations/Soft';
|
||||
import { waitUntil } from '@impl/utils/TimingUtils';
|
||||
import { ICustomerDetails } from 'safelite-playwright-core';
|
||||
import { Soft } from 'safelite-playwright-core';
|
||||
import { waitUntil } from 'safelite-playwright-core';
|
||||
import test, { expect, type Locator, type Page } from '@playwright/test';
|
||||
import { error } from 'console';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
import { IClaimDetails, ICustomerDetails } from '@business-logic/types/CustomerDetails';
|
||||
import { IClaimDetails, ICustomerDetails } from 'safelite-playwright-core';
|
||||
import { InsuranceBasePage } from './InsuranceBasePage';
|
||||
import { STATE_ABBREVIATIONS } from '@business-logic/constants/StateAbbreviation';
|
||||
import { ITestData } from '@business-logic/types/ITestData';
|
||||
import { step } from '@business-logic/types/Step';
|
||||
import { STATE_ABBREVIATIONS } from 'safelite-playwright-core';
|
||||
import { ITestData } from 'framework/TestData';
|
||||
import { step } from 'framework/localTypes/Step';
|
||||
|
||||
export class CCPolicyInfoPage extends InsuranceBasePage {
|
||||
readonly policyNumber: Locator;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { Page } from "@playwright/test";
|
||||
import { PartQuestionsPage } from "./PartQuestionPage";
|
||||
import { step } from "@business-logic/types/Step";
|
||||
import { ITestData } from "@business-logic/types/ITestData";
|
||||
import { ProgressBarPercentages } from "@business-logic/types/Enums";
|
||||
import { step } from 'framework/localTypes/Step';
|
||||
import { ITestData } from 'framework/TestData';
|
||||
import { ProgressBarPercentages } from 'framework/localTypes/Enums';
|
||||
|
||||
export default class CapabilityQuestionsPage extends PartQuestionsPage {
|
||||
url = process.env['BASE_URL']! + '/fmg/?fmgPage=capability-questions';
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
import { BasePage } from './BasePage';
|
||||
import { ICustomerDetails } from '@business-logic/types/CustomerDetails';
|
||||
import { step } from '@business-logic/types/Step';
|
||||
import { ITestData } from '@business-logic/types/ITestData';
|
||||
import { ProgressBarPercentages } from '@business-logic/types/Enums';
|
||||
import { ICustomerDetails } from 'safelite-playwright-core';
|
||||
import { step } from 'framework/localTypes/Step';
|
||||
import { ITestData } from 'framework/TestData';
|
||||
import { ProgressBarPercentages } from 'framework/localTypes/Enums';
|
||||
|
||||
export class ContactDetailsPage extends BasePage {
|
||||
readonly page: Page;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
import { InsuranceBasePage } from './InsuranceBasePage';
|
||||
import { IClaimDetails } from '@business-logic/types/CustomerDetails';
|
||||
import { step } from '@business-logic/types/Step';
|
||||
import { ITestData } from '@business-logic/types/ITestData';
|
||||
import { IClaimDetails } from 'safelite-playwright-core';
|
||||
import { step } from 'framework/localTypes/Step';
|
||||
import { ITestData } from 'framework/TestData';
|
||||
|
||||
export class CoverageStatementPage extends InsuranceBasePage {
|
||||
readonly page: Page;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
import { InsuranceBasePage } from './InsuranceBasePage';
|
||||
import { ITestData } from '@business-logic/types/ITestData';
|
||||
import { step } from '@business-logic/types/Step';
|
||||
import { ITestData } from 'framework/TestData';
|
||||
import { step } from 'framework/localTypes/Step';
|
||||
|
||||
export class DuplicateCheckPage extends InsuranceBasePage {
|
||||
readonly page: Page;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
import { BasePage } from './BasePage';
|
||||
import { IEndorsementDetails } from '@business-logic/types/CustomerDetails';
|
||||
import { EndorsementType } from '@business-logic/types/Enums';
|
||||
import { IEndorsementDetails } from 'safelite-playwright-core';
|
||||
import { EndorsementType } from 'safelite-playwright-core';
|
||||
import { InsuranceBasePage } from './InsuranceBasePage';
|
||||
import { step } from '@business-logic/types/Step';
|
||||
import { ITestData } from '@business-logic/types/ITestData';
|
||||
import { step } from 'framework/localTypes/Step';
|
||||
import { ITestData } from 'framework/TestData';
|
||||
|
||||
export class EndorsementsPage extends InsuranceBasePage {
|
||||
readonly page: Page;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import { type Locator, type Page } from '@playwright/test';
|
||||
import { BasePage } from './BasePage';
|
||||
import { ProgressBarPercentages, VehicleLookupType } from '@business-logic/types/Enums';
|
||||
import { IVehicleDetails } from '@business-logic/types/CustomerDetails';
|
||||
import { VehicleLookupType } from 'safelite-playwright-core';
|
||||
import { ProgressBarPercentages } from 'framework/localTypes/Enums';
|
||||
import { IVehicleDetails } from 'safelite-playwright-core';
|
||||
import { VinLookupPage } from './VinLookupPage';
|
||||
import { VehicleLookupAddressPage } from './VehicleLookupAddressPage';
|
||||
import { VehicleLookupLicensePage } from './VehicleLookupLicensePage';
|
||||
import { step } from '@business-logic/types/Step';
|
||||
import { ITestData } from '@business-logic/types/ITestData';
|
||||
import { step } from 'framework/localTypes/Step';
|
||||
import { ITestData } from 'framework/TestData';
|
||||
|
||||
export class EstimatePage extends BasePage {
|
||||
readonly page: Page;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import { type Locator, type Page } from '@playwright/test';
|
||||
import { BasePage } from './BasePage';
|
||||
import { IClaimDetails } from '@business-logic/types/CustomerDetails';
|
||||
import { step } from '@business-logic/types/Step';
|
||||
import TestCase from '@business-logic/types/TestCase';
|
||||
import { ITestData } from '@business-logic/types/ITestData';
|
||||
import { ProgressBarPercentages } from '@business-logic/types/Enums';
|
||||
import { step } from 'framework/localTypes/Step';
|
||||
import { ITestData } from 'framework/TestData';
|
||||
import { ProgressBarPercentages } from 'framework/localTypes/Enums';
|
||||
|
||||
|
||||
export class InsuranceCompanyPage extends BasePage {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import { type Locator, type Page, expect } from '@playwright/test';
|
||||
import { BasePage } from './BasePage';
|
||||
import TestSuccessAlert from '@business-logic/types/TestSuccessAlert';
|
||||
import IAlertFlags from '@business-logic/types/IAlertFlags';
|
||||
import { VehicleLookupType } from '@business-logic/types/Enums';
|
||||
import { IAlertFlags, TestSuccessAlert } from 'safelite-playwright-core';
|
||||
import { VehicleLookupType } from 'safelite-playwright-core';
|
||||
|
||||
export class LookupPage extends BasePage {
|
||||
protected serviceZipTextBox: Locator;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { Page } from "@playwright/test";
|
||||
import { PartQuestionsPage } from "./PartQuestionPage";
|
||||
import { step } from "@business-logic/types/Step";
|
||||
import { ITestData } from "@business-logic/types/ITestData";
|
||||
import { ProgressBarPercentages } from "@business-logic/types/Enums";
|
||||
import { step } from 'framework/localTypes/Step';
|
||||
import { ITestData } from 'framework/TestData';
|
||||
import { ProgressBarPercentages } from 'framework/localTypes/Enums';
|
||||
|
||||
export default class MoldingQuestionsPage extends PartQuestionsPage {
|
||||
url = process.env['BASE_URL']! + '/fmg/?fmgPage=molding-questions';
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
import { BasePage } from './BasePage';
|
||||
import { test } from '@business-logic/types/Test';
|
||||
import { ICustomerDetails, IVehicleDetails } from '@business-logic/types/CustomerDetails';
|
||||
import { ServicePackage, PaymentType, PaymentMethod, AppointmentType, ProgressBarPercentages } from '@business-logic/types/Enums';
|
||||
import { ITestData } from '@business-logic/types/ITestData';
|
||||
import { step } from '@business-logic/types/Step';
|
||||
import { getTestObject } from 'framework/Typedefs';
|
||||
import { ServicePackage, PaymentType, ServiceLocation } from 'safelite-playwright-core';
|
||||
import { PaymentMethod, ProgressBarPercentages } from "framework/localTypes/Enums";
|
||||
import { ITestData } from 'framework/TestData';
|
||||
import { step } from 'framework/localTypes/Step';
|
||||
|
||||
const test = getTestObject();
|
||||
|
||||
export class OrderConfirmationPage extends BasePage {
|
||||
readonly page: Page;
|
||||
|
|
@ -43,8 +45,8 @@ export class OrderConfirmationPage extends BasePage {
|
|||
|
||||
async validateOrderConfirmationPage(testData: Partial<ITestData>) {
|
||||
// Destructure data we use
|
||||
const { vehicleDetails, customerDetails, servicePackage, promoCode,
|
||||
isPolicyFound, claimDetails, paymentDetails, isUseVehicleOnPolicy, paymentMethod } = testData;
|
||||
const { vehicleDetails, customerDetails, servicePackage,
|
||||
paymentDetails, paymentMethod } = testData;
|
||||
await this.serviceText.waitFor({ state: "visible" });
|
||||
|
||||
expect.soft((await this.getActualAppointmentSummary()).map(item => item.toLowerCase())).toEqual((await this.getExpectedAppointmentSummary(testData)).map(item => item.toLowerCase()));
|
||||
|
|
@ -73,8 +75,8 @@ export class OrderConfirmationPage extends BasePage {
|
|||
}
|
||||
|
||||
// Promo Code Validation
|
||||
if (promoCode) {
|
||||
expect.soft(servicePackageValue).toContain(`Promo code ${promoCode} applied`);
|
||||
if (paymentDetails!.promoCode) {
|
||||
expect.soft(servicePackageValue).toContain(`Promo code ${paymentDetails!.promoCode} applied`);
|
||||
};
|
||||
|
||||
if (paymentMethod === PaymentMethod.SelfPay || (paymentDetails?.paymentType && paymentDetails?.paymentType !== PaymentType.PayWithInsurance)) {
|
||||
|
|
@ -156,15 +158,15 @@ export class OrderConfirmationPage extends BasePage {
|
|||
// Format the expected appointment date
|
||||
const formattedExpectedAppointmentDate = await this.getFormattedAppointmentDate(customerDetails!.apptDate!);
|
||||
appointmentSummary.push(
|
||||
appointmentDetails?.serviceLocation == AppointmentType.Mobile
|
||||
appointmentDetails?.serviceLocation == ServiceLocation.Mobile
|
||||
? formattedExpectedAppointmentDate + `${customerDetails!.apptTime?.replace("arriving between", "Between").replaceAll(":00", "")}`
|
||||
: appointmentDetails?.serviceLocation == AppointmentType.InShop
|
||||
: appointmentDetails?.serviceLocation == ServiceLocation.InShop
|
||||
? formattedExpectedAppointmentDate + `${customerDetails!.apptTime}`
|
||||
: formattedExpectedAppointmentDate + "Drop off before 9:30 AM"
|
||||
);
|
||||
appointmentSummary.push("Add to calendar");
|
||||
appointmentSummary.push(
|
||||
appointmentDetails?.serviceLocation == AppointmentType.Mobile
|
||||
appointmentDetails?.serviceLocation == ServiceLocation.Mobile
|
||||
? appointmentDetails?.serviceAddress
|
||||
? ("We're coming to you at" + appointmentDetails.serviceAddress.street + ", " + appointmentDetails.serviceAddress.city + ", " + appointmentDetails.serviceAddress.state + " " + appointmentDetails.serviceAddress.postalCode + "to service your " + `${vehicleDetails!.year} ${vehicleDetails!.make} ${vehicleDetails!.model}`)
|
||||
: ""
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
import { BasePage } from './BasePage';
|
||||
import { IPartQuestion } from '@business-logic/types/CustomerDetails';
|
||||
import { step } from '@business-logic/types/Step';
|
||||
import { ITestData } from '@business-logic/types/ITestData';
|
||||
import { ProgressBarPercentages } from '@business-logic/types/Enums';
|
||||
import { IPartQuestion } from 'safelite-playwright-core';
|
||||
import { step } from 'framework/localTypes/Step';
|
||||
import { ITestData } from 'framework/TestData';
|
||||
import { ProgressBarPercentages } from 'framework/localTypes/Enums';
|
||||
|
||||
export class PartQuestionsPage extends BasePage {
|
||||
readonly page: Page;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
import { BasePage } from './BasePage';
|
||||
import { IPaymentDetails } from '@business-logic/types/CustomerDetails';
|
||||
import { AppointmentTimeslot, AppointmentType, PaymentMethod, PaymentType, ProgressBarPercentages, ServicePackage, VehicleDamage } from '@business-logic/types/Enums';
|
||||
import { IPaymentDetails } from 'safelite-playwright-core';
|
||||
import { AppointmentTimeslot, ServiceLocation, PaymentType, ServicePackage, VehicleDamage } from 'safelite-playwright-core';
|
||||
import { PaymentMethod, ProgressBarPercentages } from 'framework/localTypes/Enums';
|
||||
import { PaymentPage } from './PaymentPage';
|
||||
import { AfterpayPage } from './AfterpayPage';
|
||||
import { PaypalPage } from './PaypalPage';
|
||||
import { ITestData } from '@business-logic/types/ITestData';
|
||||
import { step } from '@business-logic/types/Step';
|
||||
import { ITestData } from 'framework/TestData';
|
||||
import { step } from 'framework/localTypes/Step';
|
||||
|
||||
export class PaymentMethodPage extends BasePage {
|
||||
readonly page: Page;
|
||||
|
|
@ -85,8 +86,8 @@ export class PaymentMethodPage extends BasePage {
|
|||
|
||||
async validatePaymentDetailsPage(testData: Partial<ITestData>) {
|
||||
// Destructure data we use
|
||||
const { vehicleDetails, customerDetails, servicePackage, promoCode,
|
||||
isPolicyFound, claimDetails, paymentDetails, appointmentDetails,
|
||||
const { vehicleDetails, customerDetails, servicePackage,
|
||||
flow, claimDetails, paymentDetails, appointmentDetails,
|
||||
isUseVehicleOnPolicy, paymentMethod, vehicleDamage } = testData;
|
||||
|
||||
// Wait for review table to be visible to ensure page is loaded
|
||||
|
|
@ -152,8 +153,8 @@ export class PaymentMethodPage extends BasePage {
|
|||
}
|
||||
|
||||
// Promo Code Validation
|
||||
if (promoCode) {
|
||||
expect.soft(servicePackageValue).toContain(`Promo code ${promoCode} applied`);
|
||||
if (paymentDetails!.promoCode) {
|
||||
expect.soft(servicePackageValue).toContain(`Promo code ${paymentDetails!.promoCode} applied`);
|
||||
};
|
||||
|
||||
// Early Bird line item validation
|
||||
|
|
@ -395,13 +396,13 @@ l
|
|||
|
||||
async getExpectedServiceLocation(testData: Partial<ITestData>, expectedServicePackageDetails: Map<string, string[]>): Promise<any> {
|
||||
const { appointmentDetails } = testData;
|
||||
let serviceLocationTitle = appointmentDetails?.serviceLocation == AppointmentType.Mobile
|
||||
let serviceLocationTitle = appointmentDetails?.serviceLocation == ServiceLocation.Mobile
|
||||
? "We're coming to you"
|
||||
: "You're going to a Safelite shop";
|
||||
let serviceLocation: string[] = [];
|
||||
|
||||
serviceLocation.push(
|
||||
appointmentDetails?.serviceLocation == AppointmentType.Mobile
|
||||
appointmentDetails?.serviceLocation == ServiceLocation.Mobile
|
||||
? appointmentDetails?.serviceAddress
|
||||
? appointmentDetails.serviceAddress.street + ", " + appointmentDetails.serviceAddress.city + ", " + appointmentDetails.serviceAddress.state + " " + appointmentDetails.serviceAddress.postalCode
|
||||
: ""
|
||||
|
|
@ -417,7 +418,7 @@ l
|
|||
async getExpectedAppointmentDate(testData: Partial<ITestData>, expectedServicePackageDetails: Map<string, string[]>): Promise<any> {
|
||||
const { customerDetails, appointmentDetails } = testData;
|
||||
let appointmentDateText: string[] = [];
|
||||
const isMobileAppointment = appointmentDetails?.serviceLocation == AppointmentType.Mobile
|
||||
const isMobileAppointment = appointmentDetails?.serviceLocation == ServiceLocation.Mobile
|
||||
if (isMobileAppointment) {
|
||||
let localStorage= JSON.parse(await this.page.evaluate('localStorage.getItem(\'vuex\')'));
|
||||
let jobMinMinutes = localStorage.order.schedule.jobMinMinutes as number;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { type Locator, type Page } from '@playwright/test';
|
||||
import { BasePage } from './BasePage';
|
||||
import { IPaymentDetails } from '@business-logic/types/CustomerDetails';
|
||||
import { IPaymentDetails } from 'safelite-playwright-core';
|
||||
|
||||
export class PaymentPage extends BasePage {
|
||||
readonly page: Page;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
import { BasePage } from './BasePage';
|
||||
import { IPaymentDetails } from '@business-logic/types/CustomerDetails';
|
||||
import { IPaymentDetails } from 'safelite-playwright-core';
|
||||
|
||||
export class PaypalPage extends BasePage {
|
||||
readonly page: Page;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
import { InsuranceBasePage } from './InsuranceBasePage';
|
||||
import { ICustomerDetails } from '@business-logic/types/CustomerDetails';
|
||||
import { step } from '@business-logic/types/Step';
|
||||
import { ITestData } from '@business-logic/types/ITestData';
|
||||
import { ICustomerDetails } from 'safelite-playwright-core';
|
||||
import { step } from 'framework/localTypes/Step';
|
||||
import { ITestData } from 'framework/TestData';
|
||||
|
||||
export class PolicyDriverPage extends InsuranceBasePage {
|
||||
readonly page: Page;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
import { InsuranceBasePage } from './InsuranceBasePage';
|
||||
import { step } from '@business-logic/types/Step';
|
||||
import { step } from 'framework/localTypes/Step';
|
||||
|
||||
export class PolicyInfoSubmittedPage extends InsuranceBasePage {
|
||||
readonly page: Page;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
import { IVehicleDetails } from '@business-logic/types/CustomerDetails';
|
||||
import { IVehicleDetails } from 'safelite-playwright-core';
|
||||
import { InsuranceBasePage } from './InsuranceBasePage';
|
||||
import { ITestData } from '@business-logic/types/ITestData';
|
||||
import { step } from '@business-logic/types/Step';
|
||||
import { ITestData } from 'framework/TestData';
|
||||
import { step } from 'framework/localTypes/Step';
|
||||
|
||||
export class PolicyVehiclesPage extends InsuranceBasePage {
|
||||
readonly page: Page;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Locator, Page } from "@playwright/test";
|
||||
import { InsuranceBasePage } from "./InsuranceBasePage";
|
||||
import { step } from "@business-logic/types/Step";
|
||||
import { step } from 'framework/localTypes/Step';
|
||||
|
||||
export default class RecalibrationInfoPage extends InsuranceBasePage {
|
||||
url = process.env['BASE_URL']! + '/FixMyGlass/RecalibrationInfo.aspx';
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
import { BasePage } from './BasePage';
|
||||
import { IAppointmentDetails, ICustomerDetails } from '@business-logic/types/CustomerDetails';
|
||||
import { formatDate, formatTime } from '@impl/utils/DateUtils';
|
||||
import { AppointmentTimeslot, AppointmentType, ProgressBarPercentages, ServiceLocation } from '@business-logic/types/Enums';
|
||||
import { IAppointmentDetails } from 'safelite-playwright-core';
|
||||
import { formatDate, formatTime } from 'safelite-playwright-core';
|
||||
import { AppointmentTimeslot } from 'safelite-playwright-core';
|
||||
import { ProgressBarPercentages } from 'framework/localTypes/Enums';
|
||||
import { time } from 'console';
|
||||
import { step } from '@business-logic/types/Step';
|
||||
import { ITestData } from '@business-logic/types/ITestData';
|
||||
import { step } from 'framework/localTypes/Step';
|
||||
import { ITestData } from 'framework/TestData';
|
||||
|
||||
export class SchedulePage extends BasePage {
|
||||
readonly page: Page;
|
||||
|
|
@ -78,7 +79,7 @@ export class SchedulePage extends BasePage {
|
|||
});
|
||||
}
|
||||
|
||||
// appointmentmentDetails.serviceLocation === AppointmentType.DropOff ? await this.dropOffButton.click() : await this.firstAvailableTime.click();
|
||||
// appointmentmentDetails.serviceLocation === ServiceLocation.DropOff ? await this.dropOffButton.click() : await this.firstAvailableTime.click();
|
||||
customerDetails!.apptDuration = (await this.appointmentDuration.innerText()).replace("Duration: ", "");
|
||||
await this.nextPage();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
import { BasePage } from './BasePage';
|
||||
import { IAppointmentDetails } from '@business-logic/types/CustomerDetails';
|
||||
import { AppointmentTimeslot, AppointmentType, ProgressBarPercentages } from '@business-logic/types/Enums';
|
||||
import { IAppointmentDetails } from 'safelite-playwright-core';
|
||||
import { AppointmentTimeslot, ServiceLocation } from 'safelite-playwright-core';
|
||||
import { ProgressBarPercentages } from 'framework/localTypes/Enums';
|
||||
import { AddressForm } from './forms/AddressForm';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { step } from '@business-logic/types/Step';
|
||||
import { ITestData } from '@business-logic/types/ITestData';
|
||||
import { step } from 'framework/localTypes/Step';
|
||||
import { ITestData } from 'framework/TestData';
|
||||
|
||||
export class ServiceLocationPage extends BasePage {
|
||||
readonly page: Page;
|
||||
|
|
@ -78,13 +79,13 @@ export class ServiceLocationPage extends BasePage {
|
|||
const { appointmentDetails, customerDetails } = testData;
|
||||
|
||||
switch(appointmentDetails?.serviceLocation) {
|
||||
case AppointmentType.Mobile:
|
||||
case ServiceLocation.Mobile:
|
||||
await this.scheduleMobile(testData);
|
||||
break;
|
||||
case AppointmentType.InShop:
|
||||
case ServiceLocation.InShop:
|
||||
await this.scheduleInShop(appointmentDetails);
|
||||
break;
|
||||
case AppointmentType.DropOff:
|
||||
case ServiceLocation.DropOff:
|
||||
await this.scheduleDropOff(appointmentDetails);
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
import { BasePage } from './BasePage';
|
||||
import { ProgressBarPercentages, ServicePackage, VehicleDamage } from '@business-logic/types/Enums';
|
||||
import { PaymentMethod } from '@business-logic/types/Enums';
|
||||
import { step } from '@business-logic/types/Step';
|
||||
import { ITestData } from '@business-logic/types/ITestData';
|
||||
import { ServicePackage, VehicleDamage } from 'safelite-playwright-core';
|
||||
import { ProgressBarPercentages } from 'framework/localTypes/Enums';
|
||||
import { PaymentMethod } from "framework/localTypes/Enums";
|
||||
import { step } from 'framework/localTypes/Step';
|
||||
import { ITestData } from 'framework/TestData';
|
||||
|
||||
export class ServicePackagesPage extends BasePage {
|
||||
readonly page: Page;
|
||||
|
|
@ -183,7 +184,7 @@ export class ServicePackagesPage extends BasePage {
|
|||
|
||||
@step("ServicePackagePage >> Select Payment Method and Service Type: ")
|
||||
async handleServicePackagePage(testData: Partial<ITestData>) {
|
||||
const { customerDetails, paymentMethod, servicePackage, promoCode, canNotRecal, dynamicRecal, hasOemEndorsement, vehicleDamage } = testData;
|
||||
const { customerDetails, paymentMethod, paymentDetails, servicePackage, isCanNotRecal, isDynamicRecal, hasOemEndorsement, vehicleDamage } = testData;
|
||||
|
||||
await this.validateProgressBar(ProgressBarPercentages.ServicePackagePage);
|
||||
// Define repair damage types (vs. replacement types)
|
||||
|
|
@ -203,18 +204,18 @@ export class ServicePackagesPage extends BasePage {
|
|||
await this.selectServicePackage(servicePackage!);
|
||||
|
||||
// Enter promo code
|
||||
if (promoCode) {
|
||||
await this.enterPromo(promoCode);
|
||||
if (paymentDetails?.promoCode) {
|
||||
await this.enterPromo(paymentDetails.promoCode);
|
||||
}
|
||||
|
||||
// Backend Validations
|
||||
// Validate backend for can not recal if applicable
|
||||
if (canNotRecal) {
|
||||
if (isCanNotRecal) {
|
||||
await this.verifyCanNotRecal();
|
||||
}
|
||||
|
||||
// Validate backend for dynamic recal if applicable
|
||||
if (dynamicRecal) {
|
||||
if (isDynamicRecal) {
|
||||
await this.verifyDynamicRecal();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { Page } from "@playwright/test";
|
||||
import { LookupPage } from "./LookupPage";
|
||||
import { step } from "@business-logic/types/Step";
|
||||
import { ITestData } from "@business-logic/types/ITestData";
|
||||
import { ProgressBarPercentages } from "@business-logic/types/Enums";
|
||||
import { step } from 'framework/localTypes/Step';
|
||||
import { ITestData } from 'framework/TestData';
|
||||
import { ProgressBarPercentages } from 'framework/localTypes/Enums';
|
||||
|
||||
export class ServiceZipPage extends LookupPage {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { type Locator, type Page, expect, test } from '@playwright/test';
|
||||
import { BasePage } from './BasePage';
|
||||
import { ProgressBarPercentages, SideDoorDamage, VehicleDamage, WindshieldDamage } from '@business-logic/types/Enums';
|
||||
import TestSuccessAlert from '@business-logic/types/TestSuccessAlert';
|
||||
import { step } from '@business-logic/types/Step';
|
||||
import { ITestData } from '@business-logic/types/ITestData';
|
||||
import { VehicleDamage } from 'safelite-playwright-core';
|
||||
import { ProgressBarPercentages } from 'framework/localTypes/Enums';
|
||||
import { TestSuccessAlert } from 'safelite-playwright-core';
|
||||
import { step } from 'framework/localTypes/Step';
|
||||
import { ITestData } from 'framework/TestData';
|
||||
|
||||
export class VehicleDamagePage extends BasePage {
|
||||
readonly page: Page;
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ import { type Page, Locator } from '@playwright/test';
|
|||
import { LookupPage } from './LookupPage';
|
||||
import { AddressForm } from './forms/AddressForm';
|
||||
import { VehicleSelectionForm } from './forms/VehicleSelectionForm';
|
||||
import { ICustomerDetails, IVehicleDetails } from '@business-logic/types/CustomerDetails';
|
||||
import { step } from '@business-logic/types/Step';
|
||||
import { ITestData } from '@business-logic/types/ITestData';
|
||||
import { ProgressBarPercentages } from '@business-logic/types/Enums';
|
||||
import { ICustomerDetails, IVehicleDetails } from 'safelite-playwright-core';
|
||||
import { step } from 'framework/localTypes/Step';
|
||||
import { ITestData } from 'framework/TestData';
|
||||
import { ProgressBarPercentages } from 'framework/localTypes/Enums';
|
||||
|
||||
export class VehicleLookupAddressPage extends LookupPage {
|
||||
readonly addressForm: AddressForm;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { type Locator, type Page } from '@playwright/test';
|
||||
import { LookupPage } from './LookupPage';
|
||||
import { IVehicleDetails } from '@business-logic/types/CustomerDetails';
|
||||
import { step } from '@business-logic/types/Step';
|
||||
import { ITestData } from '@business-logic/types/ITestData';
|
||||
import { ProgressBarPercentages } from '@business-logic/types/Enums';
|
||||
import { IVehicleDetails } from 'safelite-playwright-core';
|
||||
import { step } from 'framework/localTypes/Step';
|
||||
import { ITestData } from 'framework/TestData';
|
||||
import { ProgressBarPercentages } from 'framework/localTypes/Enums';
|
||||
|
||||
export class VehicleLookupLicensePage extends LookupPage {
|
||||
readonly licensePlateNumTextBox: Locator;
|
||||
|
|
@ -25,7 +25,13 @@ export class VehicleLookupLicensePage extends LookupPage {
|
|||
}
|
||||
|
||||
async enterPlateDetails(vehicleDetails: IVehicleDetails) {
|
||||
await this.licensePlateNumTextBox.fill(vehicleDetails.licensePlateNumber || '');
|
||||
let licensePlateNumber: string;
|
||||
if (Array.isArray(vehicleDetails.licensePlate)) {
|
||||
licensePlateNumber = vehicleDetails.licensePlate[0].licensePlateNumber;
|
||||
} else {
|
||||
licensePlateNumber = vehicleDetails.licensePlate?.licensePlateNumber || '';
|
||||
}
|
||||
await this.licensePlateNumTextBox.fill(licensePlateNumber);
|
||||
}
|
||||
|
||||
@step("VehicleLookupLicensePage >> Lookup by license plate: ")
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { Page } from "@playwright/test";
|
||||
import { PartQuestionsPage } from "./PartQuestionPage";
|
||||
import { step } from "@business-logic/types/Step";
|
||||
import { ITestData } from "@business-logic/types/ITestData";
|
||||
import { ProgressBarPercentages } from "@business-logic/types/Enums";
|
||||
import { step } from 'framework/localTypes/Step';
|
||||
import { ITestData } from 'framework/TestData';
|
||||
import { ProgressBarPercentages } from 'framework/localTypes/Enums';
|
||||
|
||||
export default class VehiclePartQuestionsPage extends PartQuestionsPage{
|
||||
url = process.env['BASE_URL']! + '/fmg/?fmgPage=vehicle-parts';
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { type Locator, type Page, expect, test } from '@playwright/test';
|
||||
import { BasePage } from './BasePage';
|
||||
import { IVehicleDetails } from '@business-logic/types/CustomerDetails';
|
||||
import TestSuccessAlert from '@business-logic/types/TestSuccessAlert';
|
||||
import { step } from '@business-logic/types/Step';
|
||||
import { ITestData } from '@business-logic/types/ITestData';
|
||||
import { ProgressBarPercentages } from '@business-logic/types/Enums';
|
||||
import { IVehicleDetails } from 'safelite-playwright-core';
|
||||
import { TestSuccessAlert } from 'safelite-playwright-core';
|
||||
import { step } from 'framework/localTypes/Step';
|
||||
import { ITestData } from 'framework/TestData';
|
||||
import { ProgressBarPercentages } from 'framework/localTypes/Enums';
|
||||
|
||||
export class VehicleSelectionPage extends BasePage {
|
||||
readonly page: Page;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
import { IClaimDetails, ICustomerDetails } from '@business-logic/types/CustomerDetails';
|
||||
import { IClaimDetails, ICustomerDetails } from 'safelite-playwright-core';
|
||||
import { InsuranceBasePage } from './InsuranceBasePage';
|
||||
import { step } from '@business-logic/types/Step';
|
||||
import { ITestData } from '@business-logic/types/ITestData';
|
||||
import { step } from 'framework/localTypes/Step';
|
||||
import { ITestData } from 'framework/TestData';
|
||||
|
||||
export class VerifyDetailsPage extends InsuranceBasePage {
|
||||
readonly page: Page;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { type Locator, type Page } from '@playwright/test';
|
||||
import { LookupPage } from './LookupPage';
|
||||
import { step } from '@business-logic/types/Step';
|
||||
import { ITestData } from '@business-logic/types/ITestData';
|
||||
import { ProgressBarPercentages } from '@business-logic/types/Enums';
|
||||
import { step } from 'framework/localTypes/Step';
|
||||
import { ITestData } from 'framework/TestData';
|
||||
import { ProgressBarPercentages } from 'framework/localTypes/Enums';
|
||||
|
||||
export class VinLookupPage extends LookupPage {
|
||||
readonly vinLookupTextBox: Locator;
|
||||
|
|
@ -27,9 +27,16 @@ export class VinLookupPage extends LookupPage {
|
|||
@step("VinLookupPage >> Lookup by VIN: ")
|
||||
async handleVehicleLookupVinPage(testData: Partial<ITestData>) {
|
||||
const { customerDetails, vehicleDetails, alertFlags } = testData;
|
||||
let vin: string;
|
||||
|
||||
if (Array.isArray(vehicleDetails?.vin)) {
|
||||
vin = vehicleDetails.vin[0];
|
||||
} else {
|
||||
vin = vehicleDetails?.vin || '';
|
||||
}
|
||||
|
||||
await this.validateProgressBar(ProgressBarPercentages.VinLookupPage);
|
||||
await this.enterVin(vehicleDetails!.vin!);
|
||||
await this.enterVin(vin);
|
||||
await this.enterZip(customerDetails!.address.postalCode!);
|
||||
await this.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
import { BasePage } from '../BasePage';
|
||||
import { ICustomerDetails } from '@business-logic/types/CustomerDetails';
|
||||
import { waitUntil } from '@impl/utils/TimingUtils';
|
||||
import { ICustomerDetails } from 'safelite-playwright-core';
|
||||
import { waitUntil } from 'safelite-playwright-core';
|
||||
|
||||
export class AddressForm extends BasePage {
|
||||
readonly page: Page;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { type Page } from '@playwright/test';
|
||||
import { BasePage } from '../BasePage';
|
||||
import { IVehicleDetails } from '@business-logic/types/CustomerDetails';
|
||||
import { IVehicleDetails } from 'safelite-playwright-core';
|
||||
|
||||
export class VehicleSelectionForm extends BasePage {
|
||||
readonly page: Page;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { formatDateForFilename } from '@impl/utils/DateUtils';
|
||||
import { formatDateForFilename } from 'safelite-playwright-core';
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
import { JiraReporterConfig } from 'playwright-jira-reporter'
|
||||
import dotenv from 'dotenv-safe';
|
||||
|
|
@ -38,7 +38,6 @@ if (!process.env.CI) {
|
|||
const ortoniReportConfig: OrtoniReportConfig = {
|
||||
open: "never",
|
||||
folderPath: 'playwright-tests/test-results',
|
||||
logo: "../business-logic/data/logo.png",
|
||||
title: "Test Report",
|
||||
filename: `ortoni_report_${formatDateForFilename(new Date())}.html`,
|
||||
showProject: false,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import TestCase from "@business-logic/types/TestCase";
|
||||
import { Page } from "@playwright/test";
|
||||
import { addSmokeTagToRandomTest, prepareTest, test, TestInfo } from "@business-logic/types/Test";
|
||||
import { RuleEngine, ValidationOptions } from "@business-logic/types/RuleEngine";
|
||||
import { addSmokeTagToRandomTest, Flow } from 'safelite-playwright-core';
|
||||
import { ValidationOptions } from 'safelite-playwright-core';
|
||||
import heavyTruckTests from "./alert-validation/alert0001_HeavyTruck";
|
||||
import repairAndReplaceTests from "./alert-validation/alert0002_RepairAndReplace";
|
||||
import splitWindshieldTests from "./alert-validation/alert0003_SplitWindshield";
|
||||
|
|
@ -9,13 +8,14 @@ import repairOnlyTests from "./alert-validation/alert0004_RepairOnly";
|
|||
import unserviceableZipTests from "./alert-validation/alert0005_UnserviceableZip";
|
||||
import invalidZipTests from "./alert-validation/alert0006_InvalidZip";
|
||||
import vinNotFoundTests from "./alert-validation/alert0007_VinNotFound";
|
||||
import TestSuccessAlert from "@business-logic/types/TestSuccessAlert";
|
||||
import { AppointmentType, PaymentMethod, ServicePackage, VehicleLookupType, VehicleDamage, PaymentType } from "@business-logic/types/Enums";
|
||||
import { TestSuccessAlert } from 'safelite-playwright-core';
|
||||
import { VehicleLookupType, VehicleDamage, PaymentType } from 'safelite-playwright-core';
|
||||
import { PaymentMethod } from "framework/localTypes/Enums";
|
||||
import cashRepairMobileCCTests from "./CashRepairMobileCreditCard";
|
||||
import cashReplaceDynamicRecalMobileTests from "./CashReplaceDynamicRecalMobile";
|
||||
import cashReplaceGlassAddressLookupInshopAfterPayTests from "./CashReplaceGlassAddressLookupInshopAfterPay";
|
||||
import cashReplaceGlassLicensePlateLookupInshopPaypalTests from "./CashReplaceGlassLicensePlateLookupInshopPaypal";
|
||||
import ApiResponseInterceptUtil from "@impl/API/ApiResponseInterceptUtil";
|
||||
import { ApiResponseInterceptUtil } from 'safelite-playwright-core';
|
||||
import cashReplaceGlassPromoInshopTests from "./CashReplaceGlassPromoInshop";
|
||||
import cashReplaceMultiGlassPromoInshopTests from "./CashReplaceMultiGlassPromoInshop";
|
||||
import cashReplaceRainDefensePromoInshopTests from "./CashReplaceRainDefensePromoInshop";
|
||||
|
|
@ -31,6 +31,10 @@ import cashRepairInShopAfterPayTests from "./CashRepairInShopAfterPay";
|
|||
import cashRepairInShopPayPalTests from "./CashRepairInShopPayPal";
|
||||
import cashReplaceMultiSlidingGlassDropoffTests from "./CashReplaceMultiSlidingGlassDropoff";
|
||||
import cashReplaceMultiGlassMobileTests from "./CashReplaceMultiGlassMobile";
|
||||
import { getTestObject, TestCase, prepareTest, RuleEngine, TestInfo } from 'framework/Typedefs';
|
||||
import { createTestPages } from "framework/TestPages";
|
||||
|
||||
const test = getTestObject();
|
||||
|
||||
/**
|
||||
* Master Test Runner
|
||||
|
|
@ -41,7 +45,7 @@ import cashReplaceMultiGlassMobileTests from "./CashReplaceMultiGlassMobile";
|
|||
*/
|
||||
|
||||
// Initialize shared rule engine and validation options
|
||||
const ruleEngine = new RuleEngine<TestCase>();
|
||||
const ruleEngine = new RuleEngine();
|
||||
const options = new ValidationOptions();
|
||||
|
||||
// Add smoke tag to selected tests for CI/CD pipelines
|
||||
|
|
@ -87,7 +91,7 @@ const allAlertTests = [
|
|||
test.describe.parallel('Standard E2E Test Flows', () => {
|
||||
allStandardTests.forEach(scenario => {
|
||||
scenario.tests.forEach(testCase => {
|
||||
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||
test(...prepareTest(testCase, createTestPages, run, options, ruleEngine));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -97,7 +101,7 @@ test.describe.parallel('Alert Validation Tests', () => {
|
|||
allAlertTests.forEach(scenario => {
|
||||
test.describe(scenario.name, () => {
|
||||
scenario.tests.forEach(testCase => {
|
||||
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||
test(...prepareTest(testCase, createTestPages, run, options, ruleEngine));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -119,7 +123,7 @@ test.afterEach(async ({ page, testInfo }) => {
|
|||
async function run(page: Page, testInfo: TestInfo): Promise<void> {
|
||||
try {
|
||||
await testInfo.testCase.setup();
|
||||
testInfo.testCase.setupPages(page);
|
||||
testInfo.testCase.setupPages(page, createTestPages);
|
||||
await testInfo.testCase.pages.homePage.goto();
|
||||
await runWorkflow(page, testInfo.testCase);
|
||||
} catch (error) {
|
||||
|
|
@ -157,8 +161,8 @@ async function runWorkflow(page: Page, testCase: TestCase) {
|
|||
// Destructure test data for easier access
|
||||
const {
|
||||
paymentMethod, customerDetails, vehicleDetails, vehicleDamage,
|
||||
paymentDetails, partQuestions, enterFunnelWithZip, capabilityQuestions,
|
||||
vehiclePartQuestions, moldingQuestions, skipEstimatePage
|
||||
paymentDetails, partQuestions, isEnterFunnelWithZip, capabilityQuestions,
|
||||
vehiclePartQuestions, moldingQuestions, isSkipEstimatePage
|
||||
} = testCase.testData;
|
||||
|
||||
// Check if the vehicle damage includes a windshield crack
|
||||
|
|
@ -173,11 +177,11 @@ async function runWorkflow(page: Page, testCase: TestCase) {
|
|||
await test.step('HomePage >> Lets Get Started', async () => {
|
||||
let homePage = testCase.pages.homePage;
|
||||
console.log(`Customer for this test: ${customerDetails?.firstName} ${customerDetails?.lastName}`);
|
||||
await homePage.letsGetStarted(customerDetails?.address.postalCode!, !!enterFunnelWithZip!);
|
||||
await homePage.letsGetStarted(customerDetails?.address.postalCode!, !!isEnterFunnelWithZip!);
|
||||
});
|
||||
} else {
|
||||
// If enterFunnelWithZip is true, add zip code to url
|
||||
if (!!enterFunnelWithZip!) {
|
||||
if (!!isEnterFunnelWithZip!) {
|
||||
const currentUrl = page.url();
|
||||
const zipParam = `&zipCode=${customerDetails?.address.postalCode!}`;
|
||||
if (!currentUrl.includes('zipCode=')) {
|
||||
|
|
@ -196,7 +200,7 @@ async function runWorkflow(page: Page, testCase: TestCase) {
|
|||
await vehicleDamagePage.handleVehicleDamagePage(testCase.testData);
|
||||
|
||||
// If the vehicle has a windshield crack as part of its damage, go to estimate page and select lookup type
|
||||
if (hasWindshieldCrack && !skipEstimatePage) {
|
||||
if (hasWindshieldCrack && !isSkipEstimatePage) {
|
||||
let estimatePage = testCase.pages.estimatePage;
|
||||
await estimatePage.handleEstimatePage(testCase.testData);
|
||||
|
||||
|
|
@ -296,7 +300,7 @@ async function runWorkflow(page: Page, testCase: TestCase) {
|
|||
}
|
||||
|
||||
export async function handleInsuranceFlow(testCase: TestCase) {
|
||||
const { isPolicyFound, isPolicyDriver, endorsements, isRecalNotification } = testCase.testData;
|
||||
const { flow, isPolicyDriver, endorsements, isRecalNotification } = testCase.testData;
|
||||
|
||||
// Check if the insurance policy has endorsements
|
||||
const hasEndorsements = endorsements && endorsements.length > 0;
|
||||
|
|
@ -314,7 +318,7 @@ export async function handleInsuranceFlow(testCase: TestCase) {
|
|||
await duplicateCheckPage.handleDuplicateCheckPage(testCase.testData);
|
||||
}
|
||||
|
||||
if (isPolicyFound) {
|
||||
if ( flow === Flow.Managed ) {
|
||||
let policyVehiclesPage = testCase.pages.policyVehiclesPage;
|
||||
await policyVehiclesPage.handlePolicyVehiclesPage(testCase.testData);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
//Imports here
|
||||
import { ITestData } from "@business-logic/types/ITestData"
|
||||
import { VehicleDamage, AppointmentType, ServicePackage } from "@business-logic/types/Enums";
|
||||
import TestCase from "@business-logic/types/TestCase";
|
||||
import PaymentData from "@business-logic/Data/PaymentData";
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from "@business-logic/constants/DefaultTestData";
|
||||
import { ITestData } from 'framework/TestData'
|
||||
import { VehicleDamage, ServiceLocation, ServicePackage } from 'safelite-playwright-core';
|
||||
import { ITestCase } from '../framework/Typedefs'
|
||||
import { ClientData } from 'safelite-playwright-core';
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
|
||||
|
||||
// Set the seed before generating any data
|
||||
setFakerSeedFromTestName("CashRepairInShopAfterPay");
|
||||
|
|
@ -31,16 +31,16 @@ const cashRepairInShopAfterPayData : Partial<ITestData> = {
|
|||
},
|
||||
|
||||
// Use predefined payment data
|
||||
paymentDetails: PaymentData.getDefaultAfterpayDetails()
|
||||
paymentDetails: ClientData.getDefaultAfterpayDetails()
|
||||
}
|
||||
|
||||
const cashRepairInShopAfterPayTests: TestCase[] = [];
|
||||
const cashRepairInShopAfterPayTests: ITestCase[] = [];
|
||||
|
||||
const tc = new TestCase({
|
||||
const tc = {
|
||||
name: `CashRepairInShopAfterPay`,
|
||||
tags: ['@E2E','@CashRepairInShopAfterPay', '@test_report', '@CASH'],
|
||||
testData: cashRepairInShopAfterPayData
|
||||
}, undefined, 'CashRepairInShopAfterPay');
|
||||
};
|
||||
cashRepairInShopAfterPayTests.push(tc);
|
||||
|
||||
export default cashRepairInShopAfterPayTests;
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
//Imports here
|
||||
import { ITestData } from "@business-logic/types/ITestData"
|
||||
import { VehicleDamage, ServicePackage } from "@business-logic/types/Enums";
|
||||
import TestCase from "@business-logic/types/TestCase";
|
||||
import PaymentData from "@business-logic/Data/PaymentData";
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from "@business-logic/constants/DefaultTestData";
|
||||
import { ITestData } from 'framework/TestData'
|
||||
import { VehicleDamage, ServicePackage } from 'safelite-playwright-core';
|
||||
import { ITestCase } from '../framework/Typedefs'
|
||||
import { ClientData } from 'safelite-playwright-core';
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
|
||||
|
||||
// Set the seed before generating any data
|
||||
setFakerSeedFromTestName("CashRepairInShopPayPal");
|
||||
|
|
@ -31,16 +31,16 @@ const cashRepairInShopPayPalData : Partial<ITestData> = {
|
|||
},
|
||||
|
||||
// Use predefined payment data
|
||||
paymentDetails: PaymentData.getDefaultPaypalDetails()
|
||||
paymentDetails: ClientData.getDefaultPaypalDetails()
|
||||
}
|
||||
|
||||
const cashRepairInShopPayPalTests: TestCase[] = [];
|
||||
const cashRepairInShopPayPalTests: ITestCase[] = [];
|
||||
|
||||
const tc = new TestCase({
|
||||
const tc = {
|
||||
name: `CashRepairInShopPayPal`,
|
||||
tags: ['@E2E','@CashRepairInShopPayPal', '@test_report', '@CASH'],
|
||||
testData: cashRepairInShopPayPalData
|
||||
}, undefined, 'CashRepairInShopPayPal');
|
||||
};
|
||||
cashRepairInShopPayPalTests.push(tc);
|
||||
|
||||
export default cashRepairInShopPayPalTests;
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
//Imports here
|
||||
import { ITestData } from "@business-logic/types/ITestData"
|
||||
import { VehicleDamage, AppointmentType } from "@business-logic/types/Enums";
|
||||
import TestCase from "@business-logic/types/TestCase";
|
||||
import PaymentData from "@business-logic/Data/PaymentData";
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from "@business-logic/constants/DefaultTestData";
|
||||
import { ITestData } from 'framework/TestData'
|
||||
import { VehicleDamage, ServiceLocation } from 'safelite-playwright-core';
|
||||
import { ITestCase } from '../framework/Typedefs'
|
||||
import { ClientData } from 'safelite-playwright-core';
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
|
||||
|
||||
// Set the seed before generating any data
|
||||
setFakerSeedFromTestName("CashRepairMobileCreditCard");
|
||||
|
|
@ -32,7 +32,7 @@ const cashRepairMobileCCData : Partial<ITestData> = {
|
|||
|
||||
// Override appointment details
|
||||
appointmentDetails: {
|
||||
serviceLocation: AppointmentType.Mobile,
|
||||
serviceLocation: ServiceLocation.Mobile,
|
||||
appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate,
|
||||
serviceAddress: {
|
||||
street: '13735 San Antonio Ave',
|
||||
|
|
@ -44,16 +44,16 @@ const cashRepairMobileCCData : Partial<ITestData> = {
|
|||
},
|
||||
|
||||
// Use predefined payment data
|
||||
paymentDetails: PaymentData.getDefaultCreditCardDetails()
|
||||
paymentDetails: ClientData.getDefaultCreditCardDetails()
|
||||
}
|
||||
|
||||
const cashRepairMobileCCTests: TestCase[] = [];
|
||||
const cashRepairMobileCCTests: ITestCase[] = [];
|
||||
|
||||
const tc = new TestCase({
|
||||
const tc = {
|
||||
name: `CashRepairMobileCreditCard`,
|
||||
tags: ['@E2E','@CashRepairMobileCreditCard', '@test_report', '@CASH'],
|
||||
testData: cashRepairMobileCCData
|
||||
}, undefined, 'CashRepairMobileCreditCard');
|
||||
};
|
||||
cashRepairMobileCCTests.push(tc);
|
||||
|
||||
export default cashRepairMobileCCTests;
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
//Imports here
|
||||
import { ITestData } from "@business-logic/types/ITestData"
|
||||
import { AppointmentType, PaymentType } from "@business-logic/types/Enums";
|
||||
import TestCase from "@business-logic/types/TestCase";
|
||||
import { VehicleLookupType } from "@business-logic/types/Enums";
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from "@business-logic/constants/DefaultTestData";
|
||||
import { ITestData } from 'framework/TestData'
|
||||
import { ServiceLocation, PaymentType } from 'safelite-playwright-core';
|
||||
import { ITestCase } from '../framework/Typedefs'
|
||||
import { VehicleLookupType } from 'safelite-playwright-core';
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
|
||||
|
||||
// Set the seed before generating any data
|
||||
setFakerSeedFromTestName("CashReplaceDynamicRecalMobile");
|
||||
|
|
@ -16,7 +16,7 @@ const cashReplaceDynamicRecalMobileData: Partial<ITestData> = {
|
|||
isRecalVehicle: true,
|
||||
|
||||
// Flag for dynamic Recalibration vehicle
|
||||
dynamicRecal: true,
|
||||
isDynamicRecal: true,
|
||||
|
||||
customerDetails: {
|
||||
...getDefaultTestData().customerDetails!,
|
||||
|
|
@ -41,7 +41,7 @@ const cashReplaceDynamicRecalMobileData: Partial<ITestData> = {
|
|||
|
||||
// Override appointment details
|
||||
appointmentDetails: {
|
||||
serviceLocation: AppointmentType.Mobile,
|
||||
serviceLocation: ServiceLocation.Mobile,
|
||||
appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate,
|
||||
serviceAddress: {
|
||||
// Use street address from current faker seed
|
||||
|
|
@ -59,13 +59,13 @@ const cashReplaceDynamicRecalMobileData: Partial<ITestData> = {
|
|||
}
|
||||
}
|
||||
|
||||
const cashReplaceDynamicRecalMobileTests: TestCase[] = [];
|
||||
const cashReplaceDynamicRecalMobileTests: ITestCase[] = [];
|
||||
|
||||
const tc = new TestCase({
|
||||
const tc = {
|
||||
name: `CashReplaceDynamicRecalMobile`,
|
||||
tags: ['@E2E','@CashReplaceDynamicRecalMobile', '@test_report', '@CASH'],
|
||||
testData: cashReplaceDynamicRecalMobileData
|
||||
}, undefined, 'CashReplaceDynamicRecalMobile');
|
||||
};
|
||||
cashReplaceDynamicRecalMobileTests.push(tc);
|
||||
|
||||
export default cashReplaceDynamicRecalMobileTests;
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
//Imports here
|
||||
import { ITestData } from "@business-logic/types/ITestData"
|
||||
import TestCase from "@business-logic/types/TestCase";
|
||||
import { VehicleLookupType } from "@business-logic/types/Enums";
|
||||
import PaymentData from "@business-logic/Data/PaymentData";
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from "@business-logic/constants/DefaultTestData";
|
||||
import { ITestData } from 'framework/TestData'
|
||||
import { ITestCase } from '../framework/Typedefs'
|
||||
import { VehicleLookupType } from 'safelite-playwright-core';
|
||||
import { ClientData } from 'safelite-playwright-core';
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
|
||||
|
||||
// Set the seed before generating any data
|
||||
setFakerSeedFromTestName("CashReplaceGlassAddressLookupInshopAfterPay");
|
||||
|
|
@ -13,7 +13,7 @@ const cashReplaceGlassAddressLookupInshopAfterPayData: Partial<ITestData> = {
|
|||
...getDefaultTestData(), // Get default data with current seed
|
||||
|
||||
// Enable entering funnel with ZIP
|
||||
enterFunnelWithZip: true,
|
||||
isEnterFunnelWithZip: true,
|
||||
|
||||
// Override customer details
|
||||
customerDetails: {
|
||||
|
|
@ -47,16 +47,16 @@ const cashReplaceGlassAddressLookupInshopAfterPayData: Partial<ITestData> = {
|
|||
},
|
||||
|
||||
// Override payment details
|
||||
paymentDetails: PaymentData.getDefaultAfterpayDetails()
|
||||
paymentDetails: ClientData.getDefaultAfterpayDetails()
|
||||
}
|
||||
|
||||
const cashReplaceGlassAddressLookupInshopAfterPayTests: TestCase[] = [];
|
||||
const cashReplaceGlassAddressLookupInshopAfterPayTests: ITestCase[] = [];
|
||||
|
||||
const tc = new TestCase({
|
||||
const tc = {
|
||||
name: `CashReplaceGlassAddressLookupInshopAfterPay`,
|
||||
tags: ['@E2E','@CashReplaceGlassAddressLookupInshopAfterPay', '@test_report', '@CASH'],
|
||||
testData: cashReplaceGlassAddressLookupInshopAfterPayData
|
||||
}, undefined, 'CashReplaceGlassAddressLookupInshopAfterPay');
|
||||
};
|
||||
cashReplaceGlassAddressLookupInshopAfterPayTests.push(tc);
|
||||
|
||||
export default cashReplaceGlassAddressLookupInshopAfterPayTests;
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
//Imports here
|
||||
import { ITestData } from "@business-logic/types/ITestData"
|
||||
import TestCase from "@business-logic/types/TestCase";
|
||||
import { VehicleLookupType } from "@business-logic/types/Enums";
|
||||
import PaymentData from "@business-logic/Data/PaymentData";
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from "@business-logic/constants/DefaultTestData";
|
||||
import { ITestData } from 'framework/TestData'
|
||||
import { ITestCase } from '../framework/Typedefs'
|
||||
import { VehicleLookupType } from 'safelite-playwright-core';
|
||||
import { ClientData } from 'safelite-playwright-core';
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
|
||||
|
||||
// Set the seed before generating any data
|
||||
setFakerSeedFromTestName("CashReplaceGlassLicensePlateLookupInshopPaypal");
|
||||
|
|
@ -31,23 +31,23 @@ const cashReplaceGlassLicensePlateLookupInshopPaypalData: Partial<ITestData> = {
|
|||
make: 'Hyundai',
|
||||
model: 'Sonata',
|
||||
style: '4 door sedan',
|
||||
licensePlateNumber: 'FTY 7776',
|
||||
licensePlate: { licensePlateNumber: 'FTY 7776', licensePlateState: '' },
|
||||
vehicleLookupType: VehicleLookupType.LicensePlateNumber
|
||||
},
|
||||
|
||||
// No need to override vehicleDamage as it already defaults to WindshieldCrack
|
||||
|
||||
// Override payment details to use PayPal
|
||||
paymentDetails: PaymentData.getDefaultPaypalDetails()
|
||||
paymentDetails: ClientData.getDefaultPaypalDetails()
|
||||
}
|
||||
|
||||
const cashReplaceGlassLicensePlateLookupInshopPaypalTests: TestCase[] = [];
|
||||
const cashReplaceGlassLicensePlateLookupInshopPaypalTests: ITestCase[] = [];
|
||||
|
||||
const tc = new TestCase({
|
||||
const tc = {
|
||||
name: `CashReplaceGlassLicensePlateLookupInshopPaypal`,
|
||||
tags: ['@E2E','@CashReplaceGlassLicensePlateLookupInshopPaypal', '@test_report', '@CASH'],
|
||||
testData: cashReplaceGlassLicensePlateLookupInshopPaypalData
|
||||
}, undefined, 'CashReplaceGlassLicensePlateLookupInshopPaypal');
|
||||
};
|
||||
cashReplaceGlassLicensePlateLookupInshopPaypalTests.push(tc);
|
||||
|
||||
export default cashReplaceGlassLicensePlateLookupInshopPaypalTests;
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
//Imports here
|
||||
import { ITestData } from "@business-logic/types/ITestData"
|
||||
import { PaymentType } from "@business-logic/types/Enums";
|
||||
import TestCase from "@business-logic/types/TestCase";
|
||||
import { VehicleLookupType } from "@business-logic/types/Enums";
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from "@business-logic/constants/DefaultTestData";
|
||||
import { ITestData } from 'framework/TestData'
|
||||
import { PaymentType } from 'safelite-playwright-core';
|
||||
import { ITestCase } from '../framework/Typedefs'
|
||||
import { VehicleLookupType } from 'safelite-playwright-core';
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
|
||||
|
||||
// Set the seed before generating any data
|
||||
setFakerSeedFromTestName("CashReplaceGlassPromoInshop");
|
||||
|
|
@ -12,9 +12,6 @@ setFakerSeedFromTestName("CashReplaceGlassPromoInshop");
|
|||
const cashReplaceGlassPromoInshopData: Partial<ITestData> = {
|
||||
...getDefaultTestData(), // Get default data with current seed
|
||||
|
||||
// Add promo code - key feature of this test
|
||||
promoCode: '20CALL',
|
||||
|
||||
// Flag for recalibration vehicle
|
||||
isRecalVehicle: true,
|
||||
|
||||
|
|
@ -39,17 +36,19 @@ const cashReplaceGlassPromoInshopData: Partial<ITestData> = {
|
|||
|
||||
// Override payment details
|
||||
paymentDetails: {
|
||||
paymentType: PaymentType.PayAtService
|
||||
paymentType: PaymentType.PayAtService,
|
||||
// Add promo code - key feature of this test
|
||||
promoCode: '20CALL',
|
||||
}
|
||||
}
|
||||
|
||||
const cashReplaceGlassPromoInshopTests: TestCase[] = [];
|
||||
const cashReplaceGlassPromoInshopTests: ITestCase[] = [];
|
||||
|
||||
const tc = new TestCase({
|
||||
const tc = {
|
||||
name: `CashReplaceGlassPromoInshop`,
|
||||
tags: ['@E2E','@CashReplaceGlassPromoInshop', '@test_report', '@CASH'],
|
||||
testData: cashReplaceGlassPromoInshopData
|
||||
}, undefined, 'CashReplaceGlassPromoInshop');
|
||||
};
|
||||
cashReplaceGlassPromoInshopTests.push(tc);
|
||||
|
||||
export default cashReplaceGlassPromoInshopTests;
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
//Imports here
|
||||
import { ITestData } from "@business-logic/types/ITestData"
|
||||
import { VehicleDamage, PartQuestionType, PaymentType, ServicePackage, AppointmentType } from "@business-logic/types/Enums";
|
||||
import TestCase from "@business-logic/types/TestCase";
|
||||
import { VehicleLookupType } from "@business-logic/types/Enums";
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from "@business-logic/constants/DefaultTestData";
|
||||
import { ITestData } from 'framework/TestData'
|
||||
import { VehicleDamage, PartQuestionType, PaymentType, ServicePackage, ServiceLocation } from 'safelite-playwright-core';
|
||||
import { ITestCase } from '../framework/Typedefs'
|
||||
import { VehicleLookupType } from 'safelite-playwright-core';
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
|
||||
|
||||
// Set the seed before generating any data
|
||||
setFakerSeedFromTestName("CashReplaceMultiGlassMobile");
|
||||
|
|
@ -29,7 +29,7 @@ const cashReplaceMultiGlassMobileData: Partial<ITestData> = {
|
|||
|
||||
// Override appointment details
|
||||
appointmentDetails: {
|
||||
serviceLocation: AppointmentType.Mobile,
|
||||
serviceLocation: ServiceLocation.Mobile,
|
||||
appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate,
|
||||
serviceAddress: {
|
||||
// Use street address from current faker seed
|
||||
|
|
@ -87,13 +87,13 @@ const cashReplaceMultiGlassMobileData: Partial<ITestData> = {
|
|||
]
|
||||
}
|
||||
|
||||
const cashReplaceMultiGlassMobileTests: TestCase[] = [];
|
||||
const cashReplaceMultiGlassMobileTests: ITestCase[] = [];
|
||||
|
||||
const tc = new TestCase({
|
||||
const tc = {
|
||||
name: `CashReplaceMultiGlassMobile`,
|
||||
tags: ['@E2E','@CashReplaceMultiGlassMobile', '@test_report', '@CASH'],
|
||||
testData: cashReplaceMultiGlassMobileData
|
||||
}, undefined, 'CashReplaceMultiGlassMobile');
|
||||
};
|
||||
cashReplaceMultiGlassMobileTests.push(tc);
|
||||
|
||||
export default cashReplaceMultiGlassMobileTests;
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
//Imports here
|
||||
import { ITestData } from "@business-logic/types/ITestData"
|
||||
import { VehicleDamage, PartQuestionType, PaymentType } from "@business-logic/types/Enums";
|
||||
import TestCase from "@business-logic/types/TestCase";
|
||||
import { VehicleLookupType } from "@business-logic/types/Enums";
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from "@business-logic/constants/DefaultTestData";
|
||||
import { ITestData } from 'framework/TestData'
|
||||
import { VehicleDamage, PartQuestionType, PaymentType } from 'safelite-playwright-core';
|
||||
import { ITestCase } from '../framework/Typedefs'
|
||||
import { VehicleLookupType } from 'safelite-playwright-core';
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
|
||||
|
||||
// Set the seed before generating any data
|
||||
setFakerSeedFromTestName("CashReplaceMultiGlassPromoInshop");
|
||||
|
|
@ -12,9 +12,6 @@ setFakerSeedFromTestName("CashReplaceMultiGlassPromoInshop");
|
|||
const cashReplaceMultiGlassPromoInshopData: Partial<ITestData> = {
|
||||
...getDefaultTestData(), // Get default data with current seed
|
||||
|
||||
// Add promo code
|
||||
promoCode: '20CALL',
|
||||
|
||||
// Flag for recalibration vehicle
|
||||
isRecalVehicle: true,
|
||||
|
||||
|
|
@ -50,7 +47,9 @@ const cashReplaceMultiGlassPromoInshopData: Partial<ITestData> = {
|
|||
|
||||
// Override payment details
|
||||
paymentDetails: {
|
||||
paymentType: PaymentType.PayAtService
|
||||
paymentType: PaymentType.PayAtService,
|
||||
// Add promo code
|
||||
promoCode: '20CALL',
|
||||
},
|
||||
|
||||
// Vehicle part questions for multiple glass parts
|
||||
|
|
@ -94,13 +93,13 @@ const cashReplaceMultiGlassPromoInshopData: Partial<ITestData> = {
|
|||
]
|
||||
}
|
||||
|
||||
const cashReplaceMultiGlassPromoInshopTests: TestCase[] = [];
|
||||
const cashReplaceMultiGlassPromoInshopTests: ITestCase[] = [];
|
||||
|
||||
const tc = new TestCase({
|
||||
const tc = {
|
||||
name: `CashReplaceMultiGlassPromoInshop`,
|
||||
tags: ['@E2E','@CashReplaceMultiGlassPromoInshop', '@test_report', '@CASH'],
|
||||
testData: cashReplaceMultiGlassPromoInshopData
|
||||
}, undefined, 'CashReplaceMultiGlassPromoInshop');
|
||||
};
|
||||
cashReplaceMultiGlassPromoInshopTests.push(tc);
|
||||
|
||||
export default cashReplaceMultiGlassPromoInshopTests;
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
//Imports here
|
||||
import { ITestData } from "@business-logic/types/ITestData"
|
||||
import { VehicleDamage, PartQuestionType, PaymentType, ServicePackage, AppointmentType } from "@business-logic/types/Enums";
|
||||
import TestCase from "@business-logic/types/TestCase";
|
||||
import { VehicleLookupType } from "@business-logic/types/Enums";
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from "@business-logic/constants/DefaultTestData";
|
||||
import { ITestData } from 'framework/TestData'
|
||||
import { VehicleDamage, PartQuestionType, PaymentType, ServicePackage, ServiceLocation } from 'safelite-playwright-core';
|
||||
import { ITestCase } from '../framework/Typedefs'
|
||||
import { VehicleLookupType } from 'safelite-playwright-core';
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
|
||||
|
||||
// Set the seed before generating any data
|
||||
setFakerSeedFromTestName("CashReplaceMultiSlidingGlassDropoff");
|
||||
|
|
@ -29,7 +29,7 @@ const cashReplaceMultiSlidingGlassDropoffData: Partial<ITestData> = {
|
|||
|
||||
// Override for drop-off service
|
||||
appointmentDetails: {
|
||||
serviceLocation: AppointmentType.DropOff,
|
||||
serviceLocation: ServiceLocation.DropOff,
|
||||
appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate
|
||||
},
|
||||
|
||||
|
|
@ -103,13 +103,13 @@ const cashReplaceMultiSlidingGlassDropoffData: Partial<ITestData> = {
|
|||
]
|
||||
}
|
||||
|
||||
const cashReplaceMultiSlidingGlassDropoffTests: TestCase[] = [];
|
||||
const cashReplaceMultiSlidingGlassDropoffTests: ITestCase[] = [];
|
||||
|
||||
const tc = new TestCase({
|
||||
const tc = {
|
||||
name: `CashReplaceMultiSlidingGlassDropoff`,
|
||||
tags: ['@E2E','@CashReplaceMultiSlidingGlassDropoff', '@test_report', '@CASH'],
|
||||
testData: cashReplaceMultiSlidingGlassDropoffData
|
||||
}, undefined, 'CashReplaceMultiSlidingGlassDropoff');
|
||||
};
|
||||
cashReplaceMultiSlidingGlassDropoffTests.push(tc);
|
||||
|
||||
export default cashReplaceMultiSlidingGlassDropoffTests;
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
//Imports here
|
||||
import { ITestData } from "@business-logic/types/ITestData"
|
||||
import { ServicePackage, PaymentType } from "@business-logic/types/Enums";
|
||||
import TestCase from "@business-logic/types/TestCase";
|
||||
import { VehicleLookupType } from "@business-logic/types/Enums";
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from "@business-logic/constants/DefaultTestData";
|
||||
import { ITestData } from 'framework/TestData'
|
||||
import { ServicePackage, PaymentType } from 'safelite-playwright-core';
|
||||
import { ITestCase } from '../framework/Typedefs'
|
||||
import { VehicleLookupType } from 'safelite-playwright-core';
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
|
||||
|
||||
// Set the seed before generating any data
|
||||
setFakerSeedFromTestName("CashReplaceRainDefensePromoInshop");
|
||||
|
|
@ -15,9 +15,6 @@ const cashReplaceRainDefensePromoInshopData: Partial<ITestData> = {
|
|||
// Key feature: Premium package with Rain Defense
|
||||
servicePackage: ServicePackage.Premium,
|
||||
|
||||
// Rain Defense promo code
|
||||
promoCode: 'rd50',
|
||||
|
||||
// Flag for recalibration vehicle
|
||||
isRecalVehicle: true,
|
||||
|
||||
|
|
@ -45,17 +42,19 @@ const cashReplaceRainDefensePromoInshopData: Partial<ITestData> = {
|
|||
|
||||
// Payment at service
|
||||
paymentDetails: {
|
||||
paymentType: PaymentType.PayAtService
|
||||
paymentType: PaymentType.PayAtService,
|
||||
// Rain Defense promo code
|
||||
promoCode: 'rd50'
|
||||
},
|
||||
}
|
||||
|
||||
const cashReplaceRainDefensePromoInshopTests: TestCase[] = [];
|
||||
const cashReplaceRainDefensePromoInshopTests: ITestCase[] = [];
|
||||
|
||||
const tc = new TestCase({
|
||||
const tc = {
|
||||
name: `CashReplaceRainDefensePromoInshop`,
|
||||
tags: ['@E2E','@CashReplaceRainDefensePromoInshop', '@test_report', '@CASH'],
|
||||
testData: cashReplaceRainDefensePromoInshopData
|
||||
}, undefined, 'CashReplaceRainDefensePromoInshop');
|
||||
};
|
||||
cashReplaceRainDefensePromoInshopTests.push(tc);
|
||||
|
||||
export default cashReplaceRainDefensePromoInshopTests;
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
//Imports here
|
||||
import { ITestData } from "@business-logic/types/ITestData"
|
||||
import { AppointmentType, PartQuestionType, PaymentType } from "@business-logic/types/Enums";
|
||||
import TestCase from "@business-logic/types/TestCase";
|
||||
import { VehicleLookupType } from "@business-logic/types/Enums";
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from "@business-logic/constants/DefaultTestData";
|
||||
import { ITestData } from 'framework/TestData'
|
||||
import { ServiceLocation, PartQuestionType, PaymentType } from 'safelite-playwright-core';
|
||||
import { ITestCase } from '../framework/Typedefs'
|
||||
import { VehicleLookupType } from 'safelite-playwright-core';
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
|
||||
|
||||
// Set the seed before generating any data
|
||||
setFakerSeedFromTestName("CashReplaceSafeliteCanNotRecalMobile");
|
||||
|
|
@ -13,10 +13,10 @@ const cashReplaceSafeliteCanNotRecalMobileData: Partial<ITestData> = {
|
|||
...getDefaultTestData(), // Get default data with current seed
|
||||
|
||||
// Special flag to skip estimate page
|
||||
skipEstimatePage: true,
|
||||
isSkipEstimatePage: true,
|
||||
|
||||
// Special flag to verify safelite can not recalibrate in the backend
|
||||
canNotRecal: true,
|
||||
isCanNotRecal: true,
|
||||
|
||||
// Override customer postal code
|
||||
customerDetails: {
|
||||
|
|
@ -42,7 +42,7 @@ const cashReplaceSafeliteCanNotRecalMobileData: Partial<ITestData> = {
|
|||
|
||||
// Override for mobile service
|
||||
appointmentDetails: {
|
||||
serviceLocation: AppointmentType.Mobile,
|
||||
serviceLocation: ServiceLocation.Mobile,
|
||||
appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate,
|
||||
serviceAddress: {
|
||||
// Use street address from current faker seed
|
||||
|
|
@ -69,13 +69,13 @@ const cashReplaceSafeliteCanNotRecalMobileData: Partial<ITestData> = {
|
|||
]
|
||||
}
|
||||
|
||||
const cashReplaceSafeliteCanNotRecalMobileTests: TestCase[] = [];
|
||||
const cashReplaceSafeliteCanNotRecalMobileTests: ITestCase[] = [];
|
||||
|
||||
const tc = new TestCase({
|
||||
const tc = {
|
||||
name: `CashReplaceSafeliteCanNotRecalMobile`,
|
||||
tags: ['@E2E','@CashReplaceSafeliteCanNotRecalMobile', '@test_report', '@CASH'],
|
||||
testData: cashReplaceSafeliteCanNotRecalMobileData
|
||||
}, undefined, 'CashReplaceSafeliteCanNotRecalMobile');
|
||||
};
|
||||
cashReplaceSafeliteCanNotRecalMobileTests.push(tc);
|
||||
|
||||
export default cashReplaceSafeliteCanNotRecalMobileTests;
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
//Imports here
|
||||
import { ITestData } from "@business-logic/types/ITestData"
|
||||
import { AppointmentTimeslot, AppointmentType, PaymentType } from "@business-logic/types/Enums";
|
||||
import TestCase from "@business-logic/types/TestCase";
|
||||
import { VehicleLookupType } from "@business-logic/types/Enums";
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from "@business-logic/constants/DefaultTestData";
|
||||
import { ITestData } from 'framework/TestData'
|
||||
import { AppointmentTimeslot, ServiceLocation, PaymentType } from 'safelite-playwright-core';
|
||||
import { ITestCase } from '../framework/Typedefs'
|
||||
import { VehicleLookupType } from 'safelite-playwright-core';
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
|
||||
|
||||
// Set the seed based on test name for consistent but unique data
|
||||
setFakerSeedFromTestName("CashReplaceVinMobile");
|
||||
|
|
@ -39,7 +39,7 @@ const cashReplaceVinMobileData: Partial<ITestData> = {
|
|||
|
||||
// Override for mobile service
|
||||
appointmentDetails: {
|
||||
serviceLocation: AppointmentType.Mobile,
|
||||
serviceLocation: ServiceLocation.Mobile,
|
||||
appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate,
|
||||
serviceAddress: {
|
||||
// Use street address from current faker seed
|
||||
|
|
@ -58,13 +58,13 @@ const cashReplaceVinMobileData: Partial<ITestData> = {
|
|||
},
|
||||
}
|
||||
|
||||
const cashReplaceVinMobileTests: TestCase[] = [];
|
||||
const cashReplaceVinMobileTests: ITestCase[] = [];
|
||||
|
||||
const tc = new TestCase({
|
||||
const tc = {
|
||||
name: `CashReplaceVinMobile`,
|
||||
tags: ['@E2E','@CashReplaceVinMobile', '@test_report', '@CASH'],
|
||||
testData: cashReplaceVinMobileData
|
||||
}, undefined, 'CashReplaceVinMobile');
|
||||
};
|
||||
cashReplaceVinMobileTests.push(tc);
|
||||
|
||||
export default cashReplaceVinMobileTests;
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
//Imports here
|
||||
import { ITestData } from "@business-logic/types/ITestData"
|
||||
import { ServicePackage, AppointmentType, PartQuestionType, PaymentType } from "@business-logic/types/Enums";
|
||||
import TestCase from "@business-logic/types/TestCase";
|
||||
import { VehicleLookupType } from "@business-logic/types/Enums";
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from "@business-logic/constants/DefaultTestData";
|
||||
import { ITestData } from 'framework/TestData'
|
||||
import { ServicePackage, ServiceLocation, PartQuestionType, PaymentType } from 'safelite-playwright-core';
|
||||
import { ITestCase } from '../framework/Typedefs'
|
||||
import { VehicleLookupType } from 'safelite-playwright-core';
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
|
||||
|
||||
// Set the seed based on test name for consistent but unique data
|
||||
setFakerSeedFromTestName("CashReplaceWiperDropoff");
|
||||
|
|
@ -16,7 +16,7 @@ const cashReplaceWiperDropoffData: Partial<ITestData> = {
|
|||
servicePackage: ServicePackage.Standard,
|
||||
|
||||
// Special flags
|
||||
skipEstimatePage: true,
|
||||
isSkipEstimatePage: true,
|
||||
isRecalVehicle: true,
|
||||
|
||||
// Override customer postal code
|
||||
|
|
@ -43,7 +43,7 @@ const cashReplaceWiperDropoffData: Partial<ITestData> = {
|
|||
|
||||
// Override for drop-off service
|
||||
appointmentDetails: {
|
||||
serviceLocation: AppointmentType.DropOff,
|
||||
serviceLocation: ServiceLocation.DropOff,
|
||||
appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate
|
||||
},
|
||||
|
||||
|
|
@ -67,13 +67,13 @@ const cashReplaceWiperDropoffData: Partial<ITestData> = {
|
|||
]
|
||||
}
|
||||
|
||||
const cashReplaceWiperDropoffTests: TestCase[] = [];
|
||||
const cashReplaceWiperDropoffTests: ITestCase[] = [];
|
||||
|
||||
const tc = new TestCase({
|
||||
const tc = {
|
||||
name: `CashReplaceWiperDropoff`,
|
||||
tags: ['@E2E','@CashReplaceWiperDropoff', '@test_report', '@CASH'],
|
||||
testData: cashReplaceWiperDropoffData
|
||||
}, undefined, 'CashReplaceWiperDropoff');
|
||||
};
|
||||
cashReplaceWiperDropoffTests.push(tc);
|
||||
|
||||
export default cashReplaceWiperDropoffTests;
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
//Imports here
|
||||
import { ITestData } from "@business-logic/types/ITestData"
|
||||
import { ServicePackage, AppointmentType, PaymentType } from "@business-logic/types/Enums";
|
||||
import TestCase from "@business-logic/types/TestCase";
|
||||
import { VehicleLookupType } from "@business-logic/types/Enums";
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from "@business-logic/constants/DefaultTestData";
|
||||
import { ITestData } from 'framework/TestData'
|
||||
import { ServicePackage, ServiceLocation, PaymentType } from 'safelite-playwright-core';
|
||||
import { ITestCase } from '../framework/Typedefs'
|
||||
import { VehicleLookupType } from 'safelite-playwright-core';
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
|
||||
|
||||
// Set the seed based on test name for consistent but unique data
|
||||
setFakerSeedFromTestName("CashReplaceWiperPromoInShop");
|
||||
|
|
@ -15,9 +15,6 @@ const cashReplaceWiperPromoInShopData: Partial<ITestData> = {
|
|||
// Key feature: Standard package with wipers
|
||||
servicePackage: ServicePackage.Standard,
|
||||
|
||||
// Specific wiper promo code
|
||||
promoCode: '1WIPER0',
|
||||
|
||||
// Override customer postal code
|
||||
customerDetails: {
|
||||
...getDefaultTestData().customerDetails!,
|
||||
|
|
@ -42,17 +39,19 @@ const cashReplaceWiperPromoInShopData: Partial<ITestData> = {
|
|||
|
||||
// Payment at service
|
||||
paymentDetails: {
|
||||
paymentType: PaymentType.PayAtService
|
||||
paymentType: PaymentType.PayAtService,
|
||||
// Specific wiper promo code
|
||||
promoCode: '1WIPER0',
|
||||
}
|
||||
}
|
||||
|
||||
const cashReplaceWiperPromoInShopTests: TestCase[] = [];
|
||||
const cashReplaceWiperPromoInShopTests: ITestCase[] = [];
|
||||
|
||||
const tc = new TestCase({
|
||||
const tc = {
|
||||
name: `CashReplaceWiperPromoInShop`,
|
||||
tags: ['@E2E','@CashReplaceWiperPromoInShop', '@test_report', '@CASH'],
|
||||
testData: cashReplaceWiperPromoInShopData
|
||||
}, undefined, 'CashReplaceWiperPromoInShop');
|
||||
};
|
||||
cashReplaceWiperPromoInShopTests.push(tc);
|
||||
|
||||
export default cashReplaceWiperPromoInShopTests;
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
//Imports here
|
||||
import { ITestData } from "@business-logic/types/ITestData"
|
||||
import { PaymentMethod, AppointmentType, DamageType, PartQuestionType, VehicleDamage, PaymentType } from "@business-logic/types/Enums";
|
||||
import TestCase from "@business-logic/types/TestCase";
|
||||
import { VehicleLookupType } from "@business-logic/types/Enums";
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from "@business-logic/constants/DefaultTestData";
|
||||
import { ITestData } from 'framework/TestData'
|
||||
import { ServiceLocation, DamageType, PartQuestionType, VehicleDamage, PaymentType, Flow } from 'safelite-playwright-core';
|
||||
import { PaymentMethod } from "framework/localTypes/Enums";
|
||||
import { ITestCase } from '../framework/Typedefs'
|
||||
import { VehicleLookupType } from 'safelite-playwright-core';
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
|
||||
|
||||
// Set the seed based on test name for consistent but unique data
|
||||
setFakerSeedFromTestName("InsuranceAcuityPaypal");
|
||||
|
|
@ -17,7 +18,7 @@ const insuranceAcuityPaypalData: Partial<ITestData> = {
|
|||
|
||||
// Insurance claim flags
|
||||
isDuplicateClaim: true,
|
||||
isPolicyFound: true,
|
||||
flow: Flow.Managed,
|
||||
isUseVehicleOnPolicy: true,
|
||||
|
||||
// Override customer details for Kentucky location
|
||||
|
|
@ -56,7 +57,7 @@ const insuranceAcuityPaypalData: Partial<ITestData> = {
|
|||
|
||||
// Override for in-shop appointment
|
||||
appointmentDetails: {
|
||||
serviceLocation: AppointmentType.InShop,
|
||||
serviceLocation: ServiceLocation.InShop,
|
||||
shopAddress: '8985 Yellow Brick Rd, Rosedale, MD 21237',
|
||||
appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate
|
||||
},
|
||||
|
|
@ -85,13 +86,13 @@ const insuranceAcuityPaypalData: Partial<ITestData> = {
|
|||
paymentDetails: {}
|
||||
}
|
||||
|
||||
const insuranceAcuityPaypalTests: TestCase[] = [];
|
||||
const insuranceAcuityPaypalTests: ITestCase[] = [];
|
||||
|
||||
const tc = new TestCase({
|
||||
const tc = {
|
||||
name: `InsuranceAcuityPaypal`,
|
||||
tags: ['@E2E','@InsuranceAcuityPaypal', '@test_report', '@Insurance'],
|
||||
testData: insuranceAcuityPaypalData
|
||||
}, undefined, 'InsuranceAcuityPaypal');
|
||||
};
|
||||
insuranceAcuityPaypalTests.push(tc);
|
||||
|
||||
export default insuranceAcuityPaypalTests;
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
//Imports here
|
||||
import { ITestData } from "@business-logic/types/ITestData"
|
||||
import { PaymentMethod, AppointmentType, DamageType, PaymentType } from "@business-logic/types/Enums";
|
||||
import TestCase from "@business-logic/types/TestCase";
|
||||
import { VehicleLookupType } from "@business-logic/types/Enums";
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from "@business-logic/constants/DefaultTestData";
|
||||
import { ITestData } from 'framework/TestData'
|
||||
import { ServiceLocation, DamageType, Flow } from 'safelite-playwright-core';
|
||||
import { PaymentMethod } from "framework/localTypes/Enums";
|
||||
import { ITestCase } from '../framework/Typedefs'
|
||||
import { VehicleLookupType } from 'safelite-playwright-core';
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
|
||||
|
||||
// Set the seed based on test name for consistent but unique data
|
||||
setFakerSeedFromTestName("InsuranceGeico");
|
||||
|
|
@ -17,7 +18,7 @@ const insuranceGeicoData: Partial<ITestData> = {
|
|||
|
||||
// Insurance claim flags
|
||||
isDuplicateClaim: true,
|
||||
isPolicyFound: true,
|
||||
flow: Flow.Managed,
|
||||
isUseVehicleOnPolicy: true,
|
||||
|
||||
// Override customer details with specific name and California location
|
||||
|
|
@ -57,7 +58,7 @@ const insuranceGeicoData: Partial<ITestData> = {
|
|||
|
||||
// Override for in-shop appointment
|
||||
appointmentDetails: {
|
||||
serviceLocation: AppointmentType.InShop,
|
||||
serviceLocation: ServiceLocation.InShop,
|
||||
shopAddress: '8985 Yellow Brick Rd, Rosedale, MD 21237',
|
||||
appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate
|
||||
},
|
||||
|
|
@ -66,13 +67,13 @@ const insuranceGeicoData: Partial<ITestData> = {
|
|||
paymentDetails: {}
|
||||
}
|
||||
|
||||
const insuranceGeicoTests: TestCase[] = [];
|
||||
const insuranceGeicoTests: ITestCase[] = [];
|
||||
|
||||
const tc = new TestCase({
|
||||
const tc = {
|
||||
name: `InsuranceGeico`,
|
||||
tags: ['@E2E','@InsuranceGeico', '@test_report', '@Insurance'],
|
||||
testData: insuranceGeicoData
|
||||
}, undefined, 'InsuranceGeico');
|
||||
};
|
||||
insuranceGeicoTests.push(tc);
|
||||
|
||||
export default insuranceGeicoTests;
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
//Imports here
|
||||
import { ITestData } from "@business-logic/types/ITestData"
|
||||
import { PaymentMethod, AppointmentType, DamageType, PartQuestionType, PaymentType } from "@business-logic/types/Enums";
|
||||
import TestCase from "@business-logic/types/TestCase";
|
||||
import { VehicleLookupType } from "@business-logic/types/Enums";
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from "@business-logic/constants/DefaultTestData";
|
||||
import { ITestData } from 'framework/TestData'
|
||||
import { ServiceLocation, DamageType, PartQuestionType, Flow } from 'safelite-playwright-core';
|
||||
import { PaymentMethod } from "framework/localTypes/Enums";
|
||||
import { ITestCase } from '../framework/Typedefs'
|
||||
import { VehicleLookupType } from 'safelite-playwright-core';
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
|
||||
|
||||
// Set the seed based on test name for consistent but unique data
|
||||
setFakerSeedFromTestName("InsuranceITAC21stCentury");
|
||||
|
|
@ -17,7 +18,7 @@ const insuranceITAC21stCenturyData: Partial<ITestData> = {
|
|||
|
||||
// Insurance claim flags
|
||||
isDuplicateClaim: true,
|
||||
isPolicyFound: true,
|
||||
flow: Flow.Managed,
|
||||
isUseVehicleOnPolicy: true,
|
||||
isRecalNotification: true, // Special flag for recalibration notification
|
||||
|
||||
|
|
@ -55,7 +56,7 @@ const insuranceITAC21stCenturyData: Partial<ITestData> = {
|
|||
|
||||
// Override for in-shop appointment with specific shop
|
||||
appointmentDetails: {
|
||||
serviceLocation: AppointmentType.InShop,
|
||||
serviceLocation: ServiceLocation.InShop,
|
||||
appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate,
|
||||
shopAddress: "8160 Masi Dr, Rancho Cucamonga, CA 91730"
|
||||
},
|
||||
|
|
@ -70,13 +71,13 @@ const insuranceITAC21stCenturyData: Partial<ITestData> = {
|
|||
]
|
||||
}
|
||||
|
||||
const insuranceITAC21stCenturyTests: TestCase[] = [];
|
||||
const insuranceITAC21stCenturyTests: ITestCase[] = [];
|
||||
|
||||
const tc = new TestCase({
|
||||
const tc = {
|
||||
name: `InsuranceITAC21stCentury`,
|
||||
tags: ['@E2E','@InsuranceITAC21stCentury', '@test_report', '@Insurance'],
|
||||
testData: insuranceITAC21stCenturyData
|
||||
}, undefined, 'InsuranceITAC21stCentury');
|
||||
};
|
||||
insuranceITAC21stCenturyTests.push(tc);
|
||||
|
||||
export default insuranceITAC21stCenturyTests;
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
//Imports here
|
||||
import { ITestData } from "@business-logic/types/ITestData"
|
||||
import { VehicleDamage, PaymentMethod, AppointmentType, DamageType, PaymentType } from "@business-logic/types/Enums";
|
||||
import TestCase from "@business-logic/types/TestCase";
|
||||
import { VehicleLookupType } from "@business-logic/types/Enums";
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from "@business-logic/constants/DefaultTestData";
|
||||
import { ITestData } from 'framework/TestData'
|
||||
import { VehicleDamage, ServiceLocation, DamageType, Flow } from 'safelite-playwright-core';
|
||||
import { PaymentMethod } from "framework/localTypes/Enums";
|
||||
import { ITestCase } from '../framework/Typedefs'
|
||||
import { VehicleLookupType } from 'safelite-playwright-core';
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
|
||||
|
||||
// Set the seed based on test name for consistent but unique data
|
||||
setFakerSeedFromTestName("InsuranceITACOptimizedPriceValidationAllState");
|
||||
|
|
@ -17,7 +18,7 @@ const insuranceITACOptimizedPriceValidationAllStateData: Partial<ITestData> = {
|
|||
|
||||
// Insurance claim flags
|
||||
isDuplicateClaim: true,
|
||||
isPolicyFound: true,
|
||||
flow: Flow.Managed,
|
||||
isUseVehicleOnPolicy: true,
|
||||
|
||||
// Override customer details with specific name and location
|
||||
|
|
@ -59,7 +60,7 @@ const insuranceITACOptimizedPriceValidationAllStateData: Partial<ITestData> = {
|
|||
|
||||
// Override for in-shop appointment with specific shop
|
||||
appointmentDetails: {
|
||||
serviceLocation: AppointmentType.InShop,
|
||||
serviceLocation: ServiceLocation.InShop,
|
||||
shopAddress: '8985 Yellow Brick Rd, Rosedale, MD 21237',
|
||||
appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate
|
||||
},
|
||||
|
|
@ -67,13 +68,13 @@ const insuranceITACOptimizedPriceValidationAllStateData: Partial<ITestData> = {
|
|||
paymentDetails: {}
|
||||
}
|
||||
|
||||
const insuranceITACOptimizedPriceValidationAllStateTests: TestCase[] = [];
|
||||
const insuranceITACOptimizedPriceValidationAllStateTests: ITestCase[] = [];
|
||||
|
||||
const tc = new TestCase({
|
||||
const tc = {
|
||||
name: `InsuranceITACOptimizedPriceValidationAllState`,
|
||||
tags: ['@E2E','@InsuranceITACOptimizedPriceValidationAllState', '@test_report', '@Insurance'],
|
||||
testData: insuranceITACOptimizedPriceValidationAllStateData
|
||||
}, undefined, 'InsuranceITACOptimizedPriceValidationAllState');
|
||||
};
|
||||
insuranceITACOptimizedPriceValidationAllStateTests.push(tc);
|
||||
|
||||
export default insuranceITACOptimizedPriceValidationAllStateTests;
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue