Merge pull request #1150 from Safelite/feature/jnou/fix-iss-regression

Fix ISS Playwright tests
This commit is contained in:
JennyNou 2026-03-26 23:13:35 -04:00 committed by GitHub
commit 5c22d0ba31
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
70 changed files with 1025 additions and 659 deletions

View file

@ -14,7 +14,7 @@ resources:
type: github
name: Safelite/AzureDevOps
endpoint: Safelite
ref: refs/tags/t5.7.40
ref: refs/tags/t5.7.53
variables:
# - group: Digital-Infrastructure

View file

@ -235,6 +235,7 @@ const defaultAfterpayDetails: IPaymentDetails = {
const defaultPaypalDetails: IPaymentDetails = {
paymentType: PaymentType.Paypal,
username:'Itqatest@safelite.com',
password: 'Safelite1'
}

View file

@ -44,6 +44,8 @@ export interface IAppointmentDetails {
serviceAddress?: IAddress, // Used for mobile
isVehicleProtected?: boolean // Used for mobile
alternateServiceZip?: string // Used for in-shop and drop-off if doing service in a different ZIP
alternateMobileCity?: string // Used for mobile if doing service in a different city then customers address
alternateMobileStreetAddress?: string // Used for mobile if doing service in a different street address then customers address
}
export interface IPartQuestion {

View file

@ -8,7 +8,7 @@ export enum DamageType {
Theft = 'Attempted theft or theft',
Hail = 'Hailstorm',
HurricaneStorm = 'Hurricane/storm',
Collision = 'Collision',
OtherWeather = 'Other weather',
Object = 'Object hit glass',
Other = 'Other/unknown'
}
@ -90,7 +90,11 @@ export enum PartQuestionType {
RearWindowColor = 'Rear-Stationary',
LeatherSeats = 'question-0-1',
DriverSideColor = 'Driver-SideDoor',
LaneKeepAssist = 'question-0-1'
LaneKeepAssist = 'question-0-1',
GeneralQuestion1 = "question-0-1",
GeneralQuestion2 = "question-0-2",
GeneralQuestion3 = "question-0-3",
GeneralQuestion4 = "question-0-4"
}
export enum PaymentType{
@ -133,19 +137,23 @@ export enum SideDoorDamage{
export enum BailoutCode {
Unknown = 0,
SaveSessionError,
VehicleNotFound,
VehicleLookupError,
CoverageStatementInvalidState,
DoNotSeeMyShop,
PricingResponseError,
TPANotEnabled,
RequestCallback,
HeavyTruckVehicle,
NoPartsAvailable,
PartsServiceError,
SafeliteNotTheProvider,
VehicleYMMSLookupError
SaveSessionError = 1,
VehicleNotFound = 2,
VehicleVinLookupError = 3,
CoverageStatementInvalidState = 4,
DoNotSeeMyShop = 5,
PricingResponseError = 6,
TPANotEnabled = 7,
RequestCallback = 8,
HeavyTruckVehicle = 9,
NoPartsAvailable = 10,
PartsServiceError = 11,
SafeliteNotTheProvider = 12,
VehicleYMMSLookupError = 13,
ApplicationError = 14,
ApiError = 15,
RouterError = 16,
CoverageCancelledByUser = 17
}
export enum SignatureAlgorithm {
@ -170,4 +178,33 @@ export enum Authentication {
RSATokenEncParams = 'RSATokenEncParams',
RSATokenEncParamsOneTimeUse = 'RSATokenEncParamsOneTimeUse',
Unknown = 'Unknown',
}
// TODO: Add validations for the percentages on each page like url validations
export enum ProgressBarPercentage {
DuplicateCheckPage = '15%',
PolicyVehiclesPage = '20%',
PolicyEndorsementsPage = '20%',
VehicleSelectionPage = '25%',
PolicyHolderDetailsPage = '30%',
VehicleDamagePage = '35%',
PartQuestionsPage = '40%',
MoldingQuestionsPage = '45%',
VehiclePartsPage = '45%',
CapabilityQuestionsPage = '45%',
VehicleLookupPage = '45%',
VinLookupPage = '50%',
LicensePlateLookupPage = '50%',
AddressLookupPage = '50%',
AddressVehiclesPage = '50%',
CoverageStatementPage = '55%',
ProviderPreferencePage = '60%',
SchedulePage = '70%',
ServicePackagesPage = '75%',
ContactDetailsPage = '85%',
PaymentMethodPage = '95%',
PaymentPage = '0%',
OrderConfirmationPage = '100%',
TpaSearchPage = '80%',
TpaSubmitPage = '95%',
}

View file

@ -1,11 +1,12 @@
export default interface IBailoutFlags {
isVehicleSelectBailout: boolean,
isDoNotSeeMyShopBailout: boolean,
isTpaNotEnabledBailout: boolean,
isRequestCallbackBailout: boolean,
isHeavyTruckVehicleBailout: boolean,
isPartsServiceErrorBailout: boolean,
isSafeliteNotTheProviderBailout: boolean,
isVehicleLookupBailout: boolean,
isPriceServiceErrorBailout: boolean
isPriceServiceErrorBailout: boolean,
isCoverageCancelledByUser: boolean,
isAPIErrorBailout: boolean
}

View file

@ -7,6 +7,7 @@ export interface ITestData {
clientTag: string,
isDuplicateClaim: boolean,
isPolicyFound: boolean, // Effective difference between advanced and essential
isUnverifiedPolicyAfterVehicleLookup: boolean, // Changing license plate can cause flow to change to unverified from verified
isUseVehicleOnPolicy: boolean, // Should we use the vehicle on the policy?
isVehicleLookupValidations: boolean, // Should we validate vehicle lookup?
isAddressLookupValidations: boolean, // Should we validate address lookup errors?

View file

@ -12,7 +12,8 @@ export default class CcisApiUtil {
createFakeResponse(requestBody: IPostSaveFakeResponseRequestBody) {
return axios.post(this.postCreateMockPolicyUrl, requestBody, {
headers: {
'X-Mule-Origin-Verify': authToken
'X-Mule-Origin-Verify': authToken,
'x-Application-Name': 'ISS Playwright'
}
});
}
@ -21,7 +22,8 @@ export default class CcisApiUtil {
const deleteUrl = `${this.deleteFakeResponseUrl}/${params.accountNumber}/${params.key}/${params.responseType}`;
return axios.delete(deleteUrl, {
headers: {
'X-Mule-Origin-Verify': authToken
'X-Mule-Origin-Verify': authToken,
'x-Application-Name': 'ISS Playwright'
}
});
}

View file

@ -15,7 +15,7 @@ export class AddressLookupPage extends BasePage {
super(page);
this.page = page;
this.addressForm = new AddressForm(page);
this.addressNoMatchTextBox = page.getByText('Your address didnt return a VIN matchPlease re-enter the information below or');
this.addressNoMatchTextBox = page.getByText('Your address didnt return a VIN match');
this.stateRestrictionsTextBox = page.getByText('State restrictionsLooks like');
}

View file

@ -1,34 +1,31 @@
import { Locator, Page } from "@playwright/test";
import { BasePage } from "./BasePage";
import { IPaymentDetails } from "@business-logic/types/CustomerDetails";
import { IClaimDetails, IPaymentDetails } from "@business-logic/types/CustomerDetails";
import { ServicePackage } from "@business-logic/types/Enums";
export class AfterpayPage extends BasePage {
readonly page: Page;
readonly submitButton: Locator;
// Login
readonly passwordTextBox: Locator;
// Card details
readonly cardholderNameTextBox: Locator;
readonly cardNumberTextBox: Locator;
readonly expirationDateTextBox: Locator;
readonly cvvTextBox: Locator;
readonly submitButton: Locator;
readonly confirmButton: Locator;
readonly selectAfterpayWithInterestButton: Locator;
readonly selectAfterpayWithoutInterestButton: Locator;
readonly continueAfterpayButton: Locator;
constructor(page: Page) {
super(page);
this.page = page;
this.passwordTextBox = page.getByRole('textbox', { name: 'Please enter your password' });
this.passwordTextBox = page.getByTestId('login-password-input');
this.submitButton = page.getByRole('button', { name: 'Continue' });
this.cardholderNameTextBox = page.getByTestId('payment-method-cardHolderName-input');
this.cardNumberTextBox = page.getByTestId('payment-method-cardNumber-input');
this.expirationDateTextBox = page.getByTestId('payment-method-cardExpiry-input');
this.cvvTextBox = page.getByTestId('payment-method-cardCvv-input');
this.confirmButton = page.getByRole('button', { name: 'Confirm' });
// Monthly with interest option
this.selectAfterpayWithInterestButton = page.getByRole('radio', { name: /month/ });
// Biweekly without interest option
this.selectAfterpayWithoutInterestButton = page.getByTestId('payment-types-label').filter({ hasText: /every 2 weeks/ });
this.continueAfterpayButton = page.getByRole('button', { name: 'Continue' });
}
async login(password: string) {
@ -36,21 +33,28 @@ export class AfterpayPage extends BasePage {
await this.submitButton.click();
}
async populateCardDetails(paymentDetails: IPaymentDetails) {
await this.cardholderNameTextBox.fill('Roberts'); // TODO: Add cardholder name field
await this.cardNumberTextBox.fill(paymentDetails.cardNumber!);
await this.expirationDateTextBox.fill(`${paymentDetails.expirationMonth}/${paymentDetails.expirationYear}`);
await this.cvvTextBox.fill(paymentDetails.cvv!);
await this.submitButton.click();
}
async executeAfterpayPayment(paymentDetails: IPaymentDetails, claimDetails: IClaimDetails, servicePackage: ServicePackage) {
async executeAfterpayPayment(paymentDetails: IPaymentDetails) {
const confirmButtonOrPaymentOptions = this.confirmButton.or(this.selectAfterpayWithoutInterestButton);
await this.login(paymentDetails.password!);
await this.populateCardDetails(paymentDetails);
await this.page.locator('div[data-testid=\'loading-icon-svg\']').filter({ visible: true}).first().waitFor({ state: 'hidden' });
await confirmButtonOrPaymentOptions.waitFor({ state: 'visible' });
if (await this.confirmButton.isVisible()) {
await this.confirmButton.waitFor({ state: 'attached' });
await this.confirmButton.click();
} else {
// For some orders, user will have 2 options to choose from: monthly with interest or biweekly without interest
if (await this.selectAfterpayWithoutInterestButton.isVisible()) {
await this.selectAfterpayWithoutInterestButton.click();
await this.continueAfterpayButton.click();
} else if (await this.selectAfterpayWithInterestButton.isVisible()) {
await this.selectAfterpayWithInterestButton.click();
await this.continueAfterpayButton.click();
}
await this.confirmButton.click();
}
}
}

View file

@ -28,8 +28,7 @@ export class BailoutPage extends BasePage {
await expect.soft(this.firstNameTextBox).toHaveValue(customerDetails.firstName);
await expect.soft(this.lastNameTextBox).toHaveValue(customerDetails.lastName);
await expect.soft(this.phoneNumberTextBox).toHaveValue(customerDetails.phoneNumber);
await expect.soft(this.emailAddressTextBox).toHaveValue(customerDetails.email);
await expect.soft(await this.getBailoutCode()).toEqual(bailoutCode);
expect.soft(await this.getBailoutCode()).toEqual(bailoutCode);
}
async validateBailoutDetailsNotNull(){
@ -41,7 +40,7 @@ export class BailoutPage extends BasePage {
}
async getBailoutCode() {
const mainLocalStorage = JSON.parse(await this.page.evaluate('localStorage.getItem(\'main\')'));
const mainLocalStorage = JSON.parse(await this.page.evaluate('sessionStorage.getItem(\'main\')'));
const bailoutCode = mainLocalStorage.applicationUser.pageData['bailout-page'].bailoutCode as number;
return bailoutCode;
}

View file

@ -17,7 +17,8 @@ export class BasePage {
constructor(page: Page) {
this.page = page;
this.continueButton = page.locator('[id="infoBox"]').getByRole('button');
this.continueButton = page.locator('button[data-test-id="site-footer-main-button"]')
.and(page.locator('button', { hasText: /get\s*started|continue|submit/i }));
this.pageSpinner = page.getByRole('status');
this.buttonLoadSpin = page.getByRole('alert');
}
@ -95,16 +96,16 @@ export class BasePage {
}
async logReferralNumber() {
let mainLocalStorage = JSON.parse(await this.page.evaluate('localStorage.getItem(\'main\')'));
let referralNumber = mainLocalStorage.order.referralNumber as number;
let referralSequenceNumber = mainLocalStorage.order.referralSequenceNumber as number;
let mainSessionStorage = JSON.parse(await this.page.evaluate('sessionStorage.getItem(\'main\')'));
let referralNumber = mainSessionStorage.order.referralNumber as number;
let referralSequenceNumber = mainSessionStorage.order.referralSequenceNumber as number;
if (referralNumber == null) {
for (let i = 1; i <= 20; i++) {
if (!referralNumber == null) break;
await this.page.waitForTimeout(500);
mainLocalStorage = JSON.parse(await this.page.evaluate('localStorage.getItem(\'main\')'));
referralNumber = mainLocalStorage.order.referralNumber as number;
referralSequenceNumber = mainLocalStorage.order.referralSequenceNumber as number;
mainSessionStorage = JSON.parse(await this.page.evaluate('localStorage.getItem(\'main\')'));
referralNumber = mainSessionStorage.order.referralNumber as number;
referralSequenceNumber = mainSessionStorage.order.referralSequenceNumber as number;
}
}

View file

@ -6,35 +6,57 @@ export class ContactDetailsPage extends BasePage {
readonly page: Page;
issPageValue = 'contact-details';
// Contact details form
// TODO: Check if we can consolidate
readonly firstNameTextBox: Locator;
readonly lastNameTextBox: Locator;
readonly emailAddressTextBox: Locator;
readonly phoneNumberTextBox: Locator;
readonly appointmentInformationAlert: Locator;
readonly changeZipCodeAlert: Locator;
readonly streetAddressTextBox: Locator;
readonly apartmentNumberTextBox: Locator;
readonly cityTextBox: Locator;
readonly stateTextBox: Locator;
readonly zipCodeTextBox: Locator;
readonly coveredLocationYesButton: Locator;
readonly coveredLocationNoButton: Locator
readonly notesTextBox: Locator;
constructor(page: Page) {
super(page);
this.page = page;
// Alerts
this.appointmentInformationAlert = this.page.locator('[class*="widget-name-AppointmentInformationAlertWidget"]');
this.changeZipCodeAlert = this.page.locator('[class*="widget-name-ChangeZipCodeAlertWidget"]');
// Contact details form
this.firstNameTextBox = this.page.getByRole('textbox', { name: 'First name' });
this.lastNameTextBox = this.page.getByRole('textbox', { name: 'Last name' });
this.emailAddressTextBox = this.page.getByRole('textbox', { name: 'Email address' });
this.phoneNumberTextBox = this.page.getByRole('textbox', { name: 'Phone number' });
this.notesTextBox = this.page.getByRole('textbox', { name: 'Notes' });
this.streetAddressTextBox = this.page.getByRole('textbox', { name: 'Street address' });
this.apartmentNumberTextBox = this.page.getByRole('textbox', { name: 'Apartment number or letter (Optional)' });
this.cityTextBox = this.page.getByRole('textbox', { name: 'City' });
this.stateTextBox = this.page.getByRole('textbox', { name: 'state' });
this.zipCodeTextBox = this.page.getByRole('textbox', { name: 'ZIP' });
this.coveredLocationYesButton = this.page.locator('#vehicleProtectedQuestion-true');
this.coveredLocationNoButton = this.page.locator('#vehicleProtectedQuestion-false');
this.notesTextBox = this.page.getByRole('textbox', { name: 'technicianNotes' });
}
async getContactDetails() {
const customerDetails: Partial<ICustomerDetails> = {};
customerDetails.firstName = await this.firstNameTextBox.inputValue();
customerDetails.lastName = await this.lastNameTextBox.inputValue();
customerDetails.email = await this.emailAddressTextBox.inputValue();
customerDetails.phoneNumber = await this.phoneNumberTextBox.inputValue();
async validateAlertsAreVisible() {
await expect(this.appointmentInformationAlert).toBeVisible();
await this.appointmentInformationAlert.click(); //
await expect(this.appointmentInformationAlert).toContainText('Please have your vehicle keys or key card available, as your technician needs access inside the vehicle and may need to move your vehicle to complete service.');
return customerDetails;
await expect(this.changeZipCodeAlert).toBeVisible();
await this.changeZipCodeAlert.click();
await expect(this.changeZipCodeAlert).toContainText('If your service ZIP code has changed, please go back and update it to ensure we have the correct information for your appointment.');
}
// TO-DO add method for when checkbox for same as policy address displays
async fillContactDetails(customerDetails: ICustomerDetails) {
await this.streetAddressTextBox.fill(customerDetails.address.street!);
await this.cityTextBox.fill(customerDetails.address.city!);
}
async fillAlternateMobileStreetAndCity(alternateMobileStreetAddress: string, alternateMobileCity: string) {
await this.streetAddressTextBox.fill(alternateMobileStreetAddress);
await this.cityTextBox.fill(alternateMobileCity);
}
async fillNotes(notes?: string) {

View file

@ -1,30 +1,46 @@
import { expect, type Locator, type Page } from '@playwright/test';
import { BasePage } from './BasePage';
import { IClaimDetails, ICustomerDetails } from '@business-logic/types/CustomerDetails';
import { defaultTestData, ITestData, VehicleDamage } from 'safelite-playwright-core';
export class CoverageStatementPage extends BasePage {
readonly page: Page;
readonly scheduleOnlineButton: Locator;
readonly cancelMyClaimButton: Locator;
readonly cancelMyClaimConfirm: Locator;
readonly deductibleAmount: Locator;
readonly verfiyingCoverageText: Locator;
readonly continueToScheduleButton: Locator; // For ITAC/NoComp
readonly noDeductibleRepairText: Locator; // For essential flow when there is no deductible and repair is covered
readonly youPayYourDeductibleText: Locator;
readonly verifiedCoverageHeader: Locator; // For essential flow when coverage is verified
readonly headerForITAC: Locator; // For ITAC when coverage is found
readonly headerForNoComp: Locator; // For NoComp when coverage is found
readonly unverifiedCoverageHeader: Locator; // For essential flows when verifying deductible
readonly oemBullet: Locator; // For OEM bullet when OEM endorsement is present
readonly continueSchedulingButton: Locator; // For ITAC/NoComp
readonly continueButton: Locator; // For essential flows
issPageValue = 'coverage-statement';
constructor(page: Page) {
super(page);
this.page = page;
this.scheduleOnlineButton = this.page.getByText('Continue to schedule online');
this.cancelMyClaimButton = this.page.getByText('Cancel my claim');
this.cancelMyClaimConfirm = this.page.getByText('No, I want to cancel');
this.cancelMyClaimButton = this.page.getByRole('link', { name: "No, I want to cancel" }); // seen for ITAC/NoComp flows
this.cancelMyClaimConfirm = this.page.locator('div.text-center', { hasText: "No, I want to cancel" }); // on modal for cancel claim
this.deductibleAmount = this.page.getByText('$');
this.verfiyingCoverageText = this.page.getByRole('heading', { name: 'Were verifying your coverage' });
this.continueToScheduleButton = page.locator('div[class*="button-content"]', {hasText:'Continue to schedule online'});
this.deductibleAmount = this.page.locator('.cost-underline');
this.noDeductibleRepairText = this.page.getByText('According to your policy, there is no deductible for a repair.'); // no deductible for repair text
this.youPayYourDeductibleText = this.page.getByText('You pay your deductible:'); // no deductible for replacement text
this.verifiedCoverageHeader = this.page.getByRole('heading', { name: 'We have verified your coverage' });
this.unverifiedCoverageHeader = this.page.getByRole('heading', { level: 5, name: "Were verifying your coverage" });
this.headerForITAC = this.page.getByRole('heading', { name: 'Good news! Safelite\'s price is lower than your deductible' });
this.headerForNoComp = this.page.getByRole('heading', { name: 'Looks like your policy doesn\'t include comprehensive coverage.\nSafelite can still get you fixed up and safely back on the road.' });
this.oemBullet = this.page.getByText('OEM (Original Equipment Manufacturer) glass replacement is included under your policy and will be installed in your vehicle.');
this.continueSchedulingButton = page.getByRole('button', { name: 'Continue scheduling' }); // continue button for ITAC/NoComp flow
this.continueButton = page.getByRole('button', { name: 'Continue' }); // continue button for essential flow
}
async scheduleOnline(){
await this.scheduleOnlineButton.click();
async scheduleOnline(){
await this.continueSchedulingButton.click();
}
async cancelMyClaim(){
@ -32,11 +48,70 @@ export class CoverageStatementPage extends BasePage {
await this.cancelMyClaimConfirm.click();
}
async validateDeductibleAmount(customer){
await expect(this.deductibleAmount). toContainText(`$${customer.deductibleAmount}`, {timeout: 60000});
async handleRepairWithNoDeductibleFlow() {
if (VehicleDamage.WindshieldOneChip || VehicleDamage.WindshieldTwoChips || VehicleDamage.WindshieldThreeChips) {
await expect(this.verifiedCoverageHeader).toBeVisible();
await expect(this.noDeductibleRepairText).toBeVisible();
}
}
async validateUnverifiedText(){
await expect(this.verfiyingCoverageText).toBeEnabled();
async handleReplacementOrRepairWithNoDeductibleFlow(claimDetails: IClaimDetails, VehicleDamage){
const noDeductibleFlow = claimDetails.policyDeductible === 0;
if (noDeductibleFlow && VehicleDamage === VehicleDamage.WindshieldOneChip || VehicleDamage.WindshieldTwoChips || VehicleDamage.WindshieldThreeChips) {
// await expect(this.noDeductibleRepairText).toBeVisible();
await this.continueButton.click();
} else {
await expect(this.youPayYourDeductibleText).toBeVisible();
await expect(this.deductibleAmount).toContainText(`$0`, {timeout: 60000});
await this.continueButton.click();
}
}
async handleReplacementWithDeductibleFlow(claimDetails: IClaimDetails){
const replaceWithDeductibleFlow = claimDetails.policyDeductible! > 0 && claimDetails.policyDeductible !== 9999;
if (replaceWithDeductibleFlow) {
await expect(this.youPayYourDeductibleText).toBeVisible();
await expect(this.deductibleAmount).toContainText(`$${claimDetails.policyDeductible?.toLocaleString()}`, {timeout: 60000});
await this.continueButton.click();
}
}
async handleUnverifiedFlow(claimDetails: IClaimDetails){
const unVerifiedCustomer = claimDetails.policyDeductible === -1; // if deductible is -1, we treat it as unverified flow
if (unVerifiedCustomer) {
await expect(this.unverifiedCoverageHeader).toBeVisible();
await this.continueButton.click();
}
}
async handleUnverifiedPolicyAfterVehicleLookupFlow(){
await expect(this.unverifiedCoverageHeader).toBeVisible();
await this.continueButton.click();
}
async handleITACFlow(){
await expect(this.headerForITAC).toBeVisible(); // verify ITAC header is visible
await this.continueSchedulingButton.click(); // happy path to scheduling for ITAC
}
async handleNoCompFlow(){
await expect(this.headerForNoComp).toBeVisible(); // verify NoComp header is visible
await this.continueSchedulingButton.click(); // happy path to scheduling for NoComp
}
async validateOEMBullet(){
await expect(this.oemBullet).toBeVisible();
}
}
}

View file

@ -12,11 +12,11 @@ export class EndorsementsPage extends BasePage {
constructor(page: Page) {
super(page);
this.page = page;
this.educatorYesButton = this.page.locator('label[for="schoolProperty-Yes"]');
this.educatorNoButton = this.page.locator('label[for="schoolProperty-No"]');
this.educatorYesButton = this.page.locator('label:has(div.button-content:has(span:has-text("Yes")))');
this.educatorNoButton = this.page.locator('label:has(div.button-content:has(span:has-text("No")))');
}
async verifyEndorsements(endorsements: IEndorsementDetails[]) {
async verifyEndorsements(endorsements: IEndorsementDetails[]) {
for (const endorsement of endorsements) {
switch(endorsement.endorsementType) {
case EndorsementType.Educator:

View file

@ -6,110 +6,175 @@ import { ITestData } from '@business-logic/types/ITestData';
export class OrderConfirmationPage extends BasePage {
readonly page: Page;
readonly successCheckmark: Locator;
readonly successHeader: Locator;
readonly serviceText: Locator;
readonly emailText: Locator;
readonly apptDateText: Locator;
readonly amountDueText: Locator;
readonly viewCartButton: Locator;
readonly workOrderNumberText: Locator;
readonly confirmationText: Locator;
readonly appointmentTypeText: Locator;
readonly apptDetailsText: Locator;
readonly deductibleText: Locator;
readonly subtotalText: Locator;
readonly totalAmountDueText: Locator;
readonly amountPaidText: Locator;
readonly finalAmountDue: Locator;
readonly cartServicePackageText: Locator;
readonly cartLineItemsText: Locator;
readonly unverifiedCoverageDeductibleText: Locator;
issPageValue = 'order-confirmation';
constructor(page: Page) {
super(page);
this.page = page;
this.serviceText = this.page.locator('[class="appointment-text text-center lh-base"]');
this.emailText = this.page.locator('[class="email-confirmation-text"]');
this.apptDateText = this.page.locator('[class="appointment-date-time text-center mt-4"]');
this.amountDueText = this.page.getByLabel('expand cart details');
this.viewCartButton = this.page.locator('#cart-dropdown-head');
this.deductibleText = this.page.locator('#deductible-value');
this.subtotalText = this.page.locator("#subtotal-value");
this.successCheckmark = this.page.locator('img[src*="-checkmark.svg"]');
this.successHeader = this.page.locator('[class="header-text"]');
this.confirmationText = this.page.locator('[class="subheader-text"]', { hasText: 'Your appointment is on Safelite\'s schedule and your confirmation email is on the way.' });
this.serviceText = this.page.locator('[class="service-description confirmation-section"]');
this.workOrderNumberText = this.page.locator('[class="work-order-description"]');
this.appointmentTypeText = this.page.locator('[class="appointment-title"]');
this.apptDetailsText = this.page.locator('[class="appointment-details confirmation-section"]');
// Order details section
this.deductibleText = this.page.locator('span#deductible-value, span.deductible-value');
this.subtotalText = this.page.locator('#subtotal-value');
this.totalAmountDueText = this.page.locator('#total-value');
this.amountPaidText = this.page.locator('#amount-paid-value');
this.finalAmountDue = this.page.locator('#bottom-amount-due-value');
this.cartServicePackageText = this.page.locator('#cart-service-package');
this.cartLineItemsText = this.page.locator('.cart-item-list');
this.unverifiedCoverageDeductibleText = this.page.locator('span#deductible-value', { hasText: 'Verifying coverage' });
}
async validateOrderConfirmationPage(testData: Partial<ITestData>) {
// Destructure data we use
const { vehicleDetails, customerDetails, servicePackage, isItac,
isNoComp, isPolicyFound, claimDetails, paymentDetails, isUseVehicleOnPolicy } = testData;
await this.serviceText.waitFor({ state: "visible" });
await this.logOrderNumber();
// Grab text
const serviceTextValue = await this.serviceText.textContent();
const apptDateValue = await this.apptDateText.textContent();
const emailTextValue = await this.emailText.textContent();
const servicePackageValue = await this.cartServicePackageText.textContent();
const amountDueValue = await this.amountDueText.textContent();
const deductibleTextValue = (isItac || isNoComp) ? null : await this.deductibleText.textContent();
const subtotalTextValue = await this.subtotalText.textContent();
const finalAmountDueValue = await this.finalAmountDue.textContent();
const apptDetailsValue = await this.apptDetailsText.textContent();
const appointmentTypeValue = await this.appointmentTypeText.textContent();
// Derived conditions
const isItacOrNoComp = !!(isItac || isNoComp);
const isUnverified = !isPolicyFound;
const isUnverifiedPolicyAfterVehicleLookup = testData.isUnverifiedPolicyAfterVehicleLookup === true;
const hasPremiumOrStandard =
(claimDetails!.policyDeductible >= 0 || !isPolicyFound) &&
(servicePackage === ServicePackage.Premium || servicePackage === ServicePackage.Standard);
const payAtService =
(claimDetails!.policyDeductible >= 0 &&
paymentDetails?.paymentType === PaymentType.PayAtService) || !paymentDetails?.paymentType;
const paidWithPIA =
claimDetails!.policyDeductible >= 0 &&
paymentDetails?.paymentType !== PaymentType.PayAtService;
// Helper: grab textContent only when the element is expected on-screen
const textIf = async (condition: boolean, locator: Locator) =>
condition ? await locator.textContent() : null;
// Grab text content for elements that are expected on-screen
const lineItemsValue = await textIf(hasPremiumOrStandard, this.cartLineItemsText);
const deductibleTextValue = await textIf(!isItacOrNoComp, this.deductibleText);
const subtotalTextValue = await textIf((hasPremiumOrStandard || isItacOrNoComp) && !isUnverified && !isUnverifiedPolicyAfterVehicleLookup, this.subtotalText);
const totalAmountDueValue = await textIf((hasPremiumOrStandard && !payAtService) || (isItacOrNoComp && !payAtService) && !isUnverified && !isUnverifiedPolicyAfterVehicleLookup, this.totalAmountDueText);
const amountPaidTextValue = await textIf((hasPremiumOrStandard && !payAtService) || (isItacOrNoComp && !payAtService) && !isUnverified && !isUnverifiedPolicyAfterVehicleLookup, this.totalAmountDueText);
const finalAmountDueValue = await textIf(hasPremiumOrStandard || isItacOrNoComp || isUnverified || isUnverifiedPolicyAfterVehicleLookup, this.finalAmountDue);
const confirmationTextValue = await this.confirmationText.textContent();
// Extract service package price
const servicePackageAmt = Number.parseFloat(servicePackageValue!.split('$')[1].replaceAll(',', ''));
// General Validations
expect.soft(this.serviceText).toBeVisible();;
expect.soft(this.successHeader).toBeVisible();
expect.soft(confirmationTextValue).toContain('Your appointment is on Safelite\'s schedule and your confirmation email is on the way.');
expect.soft(serviceTextValue).toContain(`${vehicleDetails!.year} ${vehicleDetails!.make} ${vehicleDetails!.model}`);
expect.soft(apptDateValue).toContain(customerDetails!.apptDate);
expect.soft(emailTextValue).toContain(customerDetails!.email);
// Service package validations
await expect.soft(this.cartServicePackageText).toContainText(`${servicePackage}`)
if ((servicePackage === ServicePackage.Premium && testData.isReplace === true) || servicePackage === ServicePackage.Standard) {
expect.soft(servicePackageValue).toContain('New wiper blades');
}
if (servicePackage === ServicePackage.Premium) {
expect.soft(servicePackageValue).toContain('Rain Defense™ treatment');
}
// Validate order number and appointment type header
await this.validateOrderNumber();
await this.validateAppointmentTypeHeader();
// Price validations
if (servicePackage === ServicePackage.GlassOnly) {
expect.soft(servicePackageAmt).toEqual(0);
} else {
expect.soft(servicePackageAmt).toBeGreaterThan(0);
}
// Validate line items
if (servicePackage === ServicePackage.Premium) {
expect.soft(lineItemsValue).toContain('Front advanced beam blades');
expect.soft(lineItemsValue).toContain('Safelite Rain Repellent Treatment');
} else {
if (servicePackage === ServicePackage.Standard) {
expect.soft(lineItemsValue).toContain('Front advanced beam blades');
}
}
if (isPolicyFound && (isUseVehicleOnPolicy ?? true)) {
// Extract numbers
const amountDueAmt = Number.parseFloat(amountDueValue!.split('$')[1].replaceAll(',', ''));
if (isPolicyFound && (claimDetails!.policyDeductible === 0 && (servicePackage === ServicePackage.GlassOnly))) {
const zeroDeductibleText = await this.deductibleText.textContent();
expect.soft(zeroDeductibleText).toEqual('$0.00');
} else if (isPolicyFound && claimDetails!.policyDeductible > 0 && !(isItac || isNoComp) && servicePackage === ServicePackage.GlassOnly){
expect.soft(deductibleTextValue).not.toBeNull();
const deductibleAmt = deductibleTextValue ? Number.parseFloat(deductibleTextValue.split('$')[1].replaceAll(',', '')) : 0;
const subtotalAmt = Number.parseFloat(subtotalTextValue!.split('$')[1].replaceAll(',', ''));
subtotalTextValue?.replaceAll(',', '')
const finalAmountDueAmt = Number.parseFloat(finalAmountDueValue!.split('$')[1].replaceAll(',', ''));
expect.soft(deductibleAmt).toEqual(claimDetails!.policyDeductible);
} else if ((isPolicyFound || isItac || (isNoComp && paymentDetails!.paymentType === PaymentType.PayAtService)) && !isUnverifiedPolicyAfterVehicleLookup && finalAmountDueValue !== null && subtotalTextValue !== null) {
// Extract numbers
const deductibleAmt = deductibleTextValue ? Number.parseFloat(deductibleTextValue.split('$')[1].replaceAll(',', '')) : null;
const subtotalAmt = subtotalTextValue ? Number.parseFloat(subtotalTextValue!.split('$')[1].replaceAll(',', '')) : null;
//const totalAmountDueAmt = Number.parseFloat(totalAmountDueValue!.split('$')[1].replaceAll(',', ''));
const finalAmountDueAmt = finalAmountDueValue ? Number.parseFloat(finalAmountDueValue!.split('$')[1].replaceAll(',', '')) : null;
if (!(isItac || isNoComp)) {
expect.soft(subtotalAmt).toEqual(claimDetails!.policyDeductible + servicePackageAmt);
expect.soft(deductibleAmt).toEqual(claimDetails!.policyDeductible);
} else {
expect.soft(subtotalAmt).toBeGreaterThan(0);
expect.soft(deductibleAmt).toEqual(0);
//expect.soft(deductibleAmt).toEqual(0);
}
if (paymentDetails!.paymentType === PaymentType.PayAtService && (claimDetails!.policyDeductible > 0 || servicePackageAmt > 0)) {
// Verify amount due > 0
expect.soft(amountDueAmt).toBeGreaterThan(0);
if ((paymentDetails!.paymentType === PaymentType.PayAtService) && (claimDetails!.policyDeductible >= 0 && (servicePackage === ServicePackage.Standard || servicePackage === ServicePackage.Premium))) {
expect.soft(finalAmountDueAmt).toBeGreaterThan(0);
} else {
// Verify amount due 0
expect.soft(amountDueAmt).toEqual(0);
const totalAmountDueAmt = Number.parseFloat(totalAmountDueValue!.split('$')[1].replaceAll(',', ''));
const amountPaidValue = Number.parseFloat(amountPaidTextValue!.split('$')[1].replaceAll(',', ''));
// Verify amount due 0 usually when PIA is selected
expect.soft(amountPaidValue).toEqual(totalAmountDueAmt);
expect.soft(finalAmountDueAmt).toEqual(0);
}
} else {
expect.soft(amountDueValue).toContain('Verifying coverage');
expect.soft(subtotalTextValue).toEqual('Verifying coverage');
// essential flows will have deductible and amount due as "Verifying coverage"
const verifyingCoverageText = this.unverifiedCoverageDeductibleText;
expect.soft(verifyingCoverageText).toContainText('Verifying coverage');
expect.soft(finalAmountDueValue).toEqual('Verifying coverage');
}
}
async logOrderNumber() {
async validateOrderNumber() {
const sessionStorage = JSON.parse(await this.page.evaluate('sessionStorage.getItem(\'submittedOrder\')'));
const workOrderNumber = sessionStorage.workOrderNumber;
await test.step(`SessionStorage Work Order Number:${workOrderNumber}`, async () => {
console.log(`SessionStorage Work Order Number:${workOrderNumber}`);
});
const workOrderNumberValue = await this.workOrderNumberText.textContent();
expect.soft(workOrderNumberValue).toEqual(workOrderNumber);
}
async validateAppointmentTypeHeader() {
const sessionStorage = JSON.parse(await this.page.evaluate('sessionStorage.getItem(\'submittedOrder\')'));
const appointmentType = sessionStorage.serviceLocation.appointmentType;
const appointmentTypeValue = await this.appointmentTypeText.textContent();
const apptDetailsValue = await this.apptDetailsText.textContent();
if (appointmentTypeValue?.includes('going to a Safelite shop') && apptDetailsValue?.includes('Drop off before 9:30 AM')) {
expect.soft(appointmentType).toEqual('Dropoff');
} else if (appointmentTypeValue?.includes('going to a Safelite shop')) {
expect.soft(appointmentType).toEqual('Inshop');
} else {
expect.soft(appointmentType).toEqual('Mobile');
}
}
async validateURL() {

View file

@ -1,60 +1,79 @@
import { expect, type Locator, type Page } from '@playwright/test';
import { BasePage } from './BasePage';
import { IPaymentDetails } from '@business-logic/types/CustomerDetails';
import { PaymentType } from '@business-logic/types/Enums';
import { IClaimDetails, IPaymentDetails } from '@business-logic/types/CustomerDetails';
import { PaymentType, ServicePackage } from '@business-logic/types/Enums';
import { PaymentPage } from './PaymentPage';
import { AfterpayPage } from './AfterpayPage';
import { PaypalPage } from './PaypalPage';
export class PaymentMethodPage extends BasePage {
readonly page: Page;
readonly payAtServiceButton: Locator;
readonly havePaymentReadyMsg: Locator;
readonly payNowButton: Locator;
readonly payInFourButton: Locator;
readonly paypalButton: Locator;
readonly paymentPage: PaymentPage;
readonly paypalPage: PaypalPage;
readonly paymentPage: PaymentPage;
readonly payNowButton: Locator;
readonly paypalButton: Locator;
readonly payInFourButton: Locator;
readonly payAtAppointmentButton: Locator;
readonly textReminderYesButton: Locator;
readonly textReminderNoButton: Locator;
readonly continueToCheckoutButton: Locator;
readonly submitButton: Locator;
issPageValue = 'payment-method';
constructor(page: Page) {
super(page);
this.page = page;
this.payAtServiceButton = this.page.locator('[buttonlabel="Pay at my appointment"]'); //this.page.getByText('Pay at time of service');
this.havePaymentReadyMsg = this.page.getByText('Please have payment ready during your appointment.'); // it will be displayed when there is no payment option
this.payNowButton = this.page.locator('[buttonlabel="Pay now"]');
this.payInFourButton = this.page.locator('[buttonlabel="Pay in 4 installments"]');
this.paypalButton = this.page.frameLocator('iframe[name="card-frame"]').locator('div[id="paypalParentDiv"]');
this.paymentPage = new PaymentPage(page);
this.paypalPage = new PaypalPage(page);
this.paymentPage = new PaymentPage(page);
this.payNowButton = this.page.locator('[buttonlabel="Pay now"]'); // credit and paypal options behind this button
this.paypalButton = this.page.frameLocator('iframe[name="card-frame"]').locator('div[id="paypalParentDiv"]');
this.payInFourButton = this.page.locator('[buttonlabel="Pay in 4 installments"]'); // Afterpay
this.payAtAppointmentButton = this.page.locator('[buttonlabel="Pay at my appointment"]');
this.textReminderYesButton = this.page.locator('div.button-content', { hasText: /^yes$/i });
this.textReminderNoButton = this.page.locator('div.button-content', { hasText: 'No' });
this.continueToCheckoutButton = this.page.getByRole('button', { name: 'Continue to checkout' });
this.submitButton = this.page.getByRole('button', { name: 'Submit' });
}
async executePayment(paymentDetails: IPaymentDetails) {
async executePayment(paymentDetails: IPaymentDetails, claimDetails: IClaimDetails, servicePackage: ServicePackage) {
const browserContext = this.page.context();
switch (paymentDetails.paymentType) {
case PaymentType.Credit:
await this.selectCreditCard();
await this.paymentPage.populateCreditCardDetails(paymentDetails);
await this.payNowButton.click();
await this.textReminderYesButton.click();
await this.continueToCheckoutButton.click();
await this.selectCreditCard(paymentDetails);
break;
case PaymentType.Paypal:
await this.payNowButton.click();
await this.textReminderYesButton.click();
await this.continueToCheckoutButton.click();
await this.selectPaypal();
await this.paypalPage.completePaypalPurchase(paymentDetails);
break;
case PaymentType.AfterPay:
await this.payInFourButton.click();
await this.nextPage();
await this.textReminderYesButton.click();
await this.continueToCheckoutButton.click();
// Capture popup
const afterpayPopup = await browserContext.waitForEvent('page');
const afterpayPage = new AfterpayPage(afterpayPopup);
// Execute payment
await afterpayPage.executeAfterpayPayment(paymentDetails);
await afterpayPage.executeAfterpayPayment(paymentDetails, claimDetails, servicePackage);
break;
case PaymentType.PayAtService:
await this.selectPayAtService();
await this.payAtAppointmentButton.click();
await this.textReminderYesButton.click();
await this.submitButton.click();
break;
default:
console.error('PaymentMethodPage >> Logic for this payment method unimplemented');
@ -63,22 +82,19 @@ export class PaymentMethodPage extends BasePage {
}
async selectPaypal() {
await this.payNowButton.click();
await this.nextPage();
await this.paypalButton.click();
}
async selectCreditCard() {
await this.payNowButton.click();
await this.nextPage();
async selectCreditCard(paymentDetails: IPaymentDetails) {
await this.paymentPage.populateCreditCardDetails(paymentDetails);
}
async selectPayAtAppointment() {
await this.payAtAppointmentButton.click();
}
async selectPayAtService() {
await this.payAtServiceButton.click();
async submitOrderWithoutPIA() {
await this.textReminderYesButton.click();
await this.submitButton.click();
}
// async validateAmountDue(customer){
// await this.amountDueDropDown.click();
// await expect(this.deductibleAmountTextField).toContainText(`${customer.deductibleAmount}`);
// }
}

View file

@ -1,5 +1,6 @@
import { type Locator, type Page } from '@playwright/test';
import { BasePage } from './BasePage';
import { AfterpayPage } from './AfterpayPage';
import { IPaymentDetails } from '@business-logic/types/CustomerDetails';
export class PaymentPage extends BasePage {
@ -26,8 +27,7 @@ export class PaymentPage extends BasePage {
this.cityTextField = page.frameLocator('iframe[name="card-frame"]').getByRole('textbox', { name: 'City' });
this.stateDropDown = page.frameLocator('iframe[name="card-frame"]').getByRole('combobox', { name: 'State' });
this.billingZipTextField = page.frameLocator('iframe[name="card-frame"]').getByRole('textbox', { name: 'Billing ZIP code' });;
this.submitPaymentButton = page.frameLocator('iframe[name="card-frame"]').locator('#buttonContainer');
// this.validateURL(this.url);
this.submitPaymentButton = page.getByRole('button', { name: 'Submit payment' });
}
async populateCreditCardDetails(paymentDetails: IPaymentDetails){

View file

@ -1,27 +1,33 @@
import { expect, type Locator, type Page } from '@playwright/test';
import { BasePage } from './BasePage';
import { IPaymentDetails } from '@business-logic/types/CustomerDetails';
import { expect, type Locator, type Page } from "@playwright/test";
import { BasePage } from "./BasePage";
import { IPaymentDetails } from "@business-logic/types/CustomerDetails";
export class PaypalPage extends BasePage {
readonly page: Page;
readonly loginWithPasswordButton: Locator;
readonly passwordTextBox: Locator;
readonly paypalLoginButton: Locator;
readonly completePurchaseButton: Locator;
readonly page: Page;
readonly usernameTextBox: Locator;
readonly nextButton: Locator;
readonly passwordTextBox: Locator;
readonly paypalLoginButton: Locator;
readonly payButton: Locator;
constructor(page: Page) {
super(page);
this.page = page;
this.loginWithPasswordButton = page.getByRole('link', { name: 'Log in with a password instead' });
this.passwordTextBox = page.getByPlaceholder('Password');
this.paypalLoginButton = page.getByRole('button', { name: 'Log In', exact: true });
this.completePurchaseButton = page.getByTestId('submit-button-initial');
}
constructor(page: Page) {
super(page);
this.page = page;
this.usernameTextBox = page.locator("#email");
this.nextButton = page.getByRole("button", { name: "Next" });
this.passwordTextBox = page.getByRole("textbox", { name: "Password" });
this.paypalLoginButton = page.getByRole("button", {
name: "Log In",
exact: true,
});
this.payButton = page.getByRole("button", { name: "Pay $" });
}
async completePaypalPurchase(paymentDetails: IPaymentDetails){
await this.loginWithPasswordButton.click();
await this.passwordTextBox.fill(paymentDetails.password!);
await this.paypalLoginButton.click();
await this.completePurchaseButton.click();
}
async completePaypalPurchase(paymentDetails: IPaymentDetails) {
await this.usernameTextBox.fill(paymentDetails.username!);
await this.nextButton.click();
await this.passwordTextBox.fill(paymentDetails.password!);
await this.paypalLoginButton.click();
await this.payButton.click();
}
}

View file

@ -1,20 +1,60 @@
import { type Locator, type Page } from '@playwright/test';
import { BasePage } from './BasePage';
import { ICustomerDetails } from '@business-logic/types/CustomerDetails';
import { AddressForm } from './forms/AddressForm';
export class PolicyHolderDetailsPage extends BasePage {
readonly page: Page;
readonly addressForm: AddressForm;
readonly firstNameTextBox: Locator;
readonly lastNameTextBox: Locator;
readonly addressInputBox: Locator;
readonly emailAddressTextBox: Locator;
readonly issPageValue = 'policy-holder-details';
constructor(page: Page) {
super(page);
this.page = page;
this.addressForm = new AddressForm(page);
this.firstNameTextBox = page.locator('#firstNameField');
this.lastNameTextBox = page.locator('#lastNameField');
this.emailAddressTextBox = page.locator('#emailField');
this.addressInputBox = page.locator('input[name="autocomplete"]'); // for essential flows
}
async fillCustomerDetails(customerDetails: ICustomerDetails){
await this.addressForm.populateAddress(customerDetails);
// Check if firstName field is empty, then fill it
const firstNameValue = await this.firstNameTextBox.inputValue();
if (!firstNameValue || firstNameValue.trim() === '') {
if (customerDetails.firstName) {
await this.firstNameTextBox.fill(customerDetails.firstName);
}
}
// Check if lastName field is empty, then fill it
const lastNameValue = await this.lastNameTextBox.inputValue();
if (!lastNameValue || lastNameValue.trim() === '') {
if (customerDetails.lastName) {
await this.lastNameTextBox.fill(customerDetails.lastName);
}
}
// Fill email address for both essential and advanced flows
await this.emailAddressTextBox.fill(customerDetails.email);
const addressValue = await this.addressInputBox.inputValue();
// For essential flows, when address fields are not pre-filled
if (!addressValue || addressValue.trim() === '') {
await this.addressInputBox.click();
await this.addressInputBox.fill(customerDetails.address.street);
// Wait for suggestions to load (Google Places has a slight delay)
await this.page.locator('.pac-item').first().waitFor({ state: 'visible', timeout: 5000 });
// Click the first result
await this.page.locator('.pac-item').first().click();
// Optional: wait for the input to be populated by Google Places
await this.addressInputBox.waitFor({ state: 'attached' });
}
}
}

View file

@ -5,11 +5,14 @@ import { IVehicleDetails } from '@business-logic/types/CustomerDetails';
export class PolicyVehiclesPage extends BasePage {
readonly page: Page;
issPageValue = 'policy-vehicles';
readonly addAnotherVehicleButton: Locator;
readonly selectVehicleOnPolicyButton: Locator;
constructor(page: Page) {
super(page);
this.page = page;
// this.validateURL(this.url);
this.addAnotherVehicleButton = this.page.getByText('Add another vehicle');
this.selectVehicleOnPolicyButton = this.page.locator(`input[type="radio"][name="policyVehiclesQuestionOption"]`).first();
}
async validateVehicleIsOnPolicy(vehicleDetails: IVehicleDetails) {
@ -21,8 +24,9 @@ export class PolicyVehiclesPage extends BasePage {
const vehicleRegExp = new RegExp(`${vehicleDetails.year} .+ ${vehicleDetails.model}`, 'i');
await this.page.locator('label').filter({hasText: vehicleRegExp}).locator('div').click();
}
async selectVehicleNotListed(){
await this.page.getByText('Vehicle not listed').click();
async addAnotherVehicle(){
await this.page.getByRole('button', {name: 'Add another vehicle'}).click();
}
async assertNonServiceableAlertBehavior(){

View file

@ -3,48 +3,78 @@ import { BasePage } from './BasePage';
export class ProviderPreferencePage extends BasePage {
readonly page: Page;
readonly scheduleWithSafelite: Locator;
readonly scheduleWithOther: Locator;
readonly acknowledgeAdasButton: Locator;
readonly scheduleNowButton: Locator;
readonly findAnotherShopButton: Locator;
readonly acknowledgeAdasCheckbox: Locator;
readonly gotItButton: Locator;
readonly tpaRecalModal: Locator;
readonly learnMoreLink: Locator;
readonly yesButton: Locator;
readonly noButton: Locator;
readonly acknowledgeCheckbox: Locator;
readonly stateLawModalHeading: Locator;
readonly tpaRecalModalContinueButton: Locator;
readonly stateLawModalText: Locator;
readonly stateLawRepairModalText: Locator;
readonly stateLawModalOkayButton: Locator;
issPageValue = 'provider-preference';
constructor(page: Page) {
super(page);
this.page = page;
// this.scheduleWithSafelite = this.page.locator('div').filter({ hasText: /Schedule online with |Safelite AutoGlass/ }).first();
this.scheduleWithSafelite = this.page.getByText(/Schedule online with|Safelite AutoGlass/).first();
this.scheduleWithOther = this.page.getByText(/Find another shop|Choose my own shop/).first();
this.acknowledgeAdasButton = this.page.getByLabel('I acknowledge that my vehicle');
this.gotItButton = this.page.getByRole('button', { name: 'Got it' });
this.acknowledgeCheckbox = this.page.locator('#tpaAcknowledgement');
this.stateLawModalHeading = this.page.getByRole('heading').filter({ hasText: /.* State Law/});
this.scheduleNowButton = this.page.getByRole('button', { name: 'Schedule now' });
this.findAnotherShopButton = this.page.getByRole('link', { name: 'Find another shop' });
//Recal modal
this.tpaRecalModal = this.page.locator('#TPARecalModal');
this.learnMoreLink = this.page.locator('#moreDetails');
this.yesButton = this.page.getByText('Yes', { exact: true }); // schedule with Safelite
this.noButton = this.page.getByText('No', { exact: true }); // schedule with TPA
this.acknowledgeAdasCheckbox = this.page.getByRole('checkbox', { name: /I acknowledge/i })
this.tpaRecalModalContinueButton = this.page.locator('#modalbtn', { hasText: 'Continue' });
// State law modal - is there a difference between repair and replacement modals? need to check
this.stateLawModalText = this.page.getByText(/law prohibits us from requiring you.*You have the right to select the motor vehicle repair shop of your choice\./is);
this.stateLawRepairModalText = this.page.getByText(/We are prohibited by law from requiring that repairs be done at a specific automotive repair dealer. You are entitled to select the auto body repair shop to repair damage covered by us\./is);
this.stateLawModalOkayButton = this.page.getByRole('button', { name: 'Okay' })
}
async selectProvider(isSafelite = true) {
if (isSafelite) {
await this.scheduleWithSafelite.click();
await this.scheduleNowButton.click();
await this.nextPage();
}
else {
await this.scheduleWithOther.waitFor({ state: 'visible' });
await this.scheduleWithOther.click();
await this.continueButton.click();
//if(await this.acknowledgeAdasButton.isEnabled({timeout: 2500})){
// await this.acknowledgeAdasButton.click();
// await this.gotItButton.click();
// await this.page.waitForTimeout(1000);
//}
await this.findAnotherShopButton.waitFor({ state: 'visible' });
await this.findAnotherShopButton.click();
await this.tpaRecalModal.waitFor({ state: 'visible' });
await this.scheduleTPAWithAdas();
}
}
async acknowledgeRecalNotificaiton() {
await this.acknowledgeAdasButton.click();
await this.gotItButton.click();
async scheduleWithSafeliteADAS() {
await this.learnMoreLink.click();
await this.yesButton.click();
await this.tpaRecalModalContinueButton.click();
}
async scheduleTPAWithAdas() {
await this.learnMoreLink.click();
await this.noButton.click();
await this.acknowledgeAdasCheckbox.click();
await this.tpaRecalModalContinueButton.click();
}
async scheduleTPAWithoutAdas() {
await this.findAnotherShopButton.click();
}
async validateStateLawModalIsVisible() {
await expect.soft(this.stateLawModalHeading).toBeVisible();
await expect.soft(this.stateLawModalText).toBeVisible();
}
async validateStateLawRepairModalIsVisible() {
await expect.soft(this.stateLawRepairModalText).toBeVisible();
}
}

View file

@ -3,56 +3,111 @@ import { BasePage } from './BasePage';
import { IAppointmentDetails } from '@business-logic/types/CustomerDetails';
import { formatDate, formatTime } from '@impl/utils/DateUtils';
import { ServiceLocation } from '@business-logic/types/Enums';
import { getCustomerDetails, ITestData } from 'safelite-playwright-core';
export class SchedulePage extends BasePage {
readonly page: Page;
issPageValue = 'schedule-page';
readonly firstAvailableDate: Locator;
readonly inShopButton: Locator;
readonly mobileButton: Locator;
readonly moreLocationsButton: Locator;
readonly zipCodeTextBox: Locator;
readonly findButton: Locator;
readonly selectAShopOptions: Locator;
readonly firstAppointmentButton : Locator;
readonly firstAvailableAMDate: Locator;
readonly firstAvailablePMDate: Locator;
readonly firstAvailableTime: Locator;
readonly firstAvailabeAMTime: Locator;
readonly modalContinueButton: Locator;
readonly dropOffButton: Locator;
readonly dateText: Locator;
readonly viewMoreDatesLink: Locator;
readonly continueButton: Locator;
constructor(page: Page) {
super(page);
this.page = page;
this.firstAvailableDate = this.page.locator('.selectable-day').locator('nth=0');
this.firstAvailableTime = this.page.locator('label').filter({ hasText: /AM|PM/ }).locator('div').locator('nth=0');
this.inShopButton = this.page.locator('[buttonlabel="At a Safelite shop"]');
this.mobileButton = this.page.locator('[buttonlabel="Have Safelite come to me"]');
// For in shop
this.moreLocationsButton = this.page.getByRole('button', { name: 'More Locations' });
// For mobile
this.zipCodeTextBox = this.page.locator('#serviceZipCode');
this.findButton = this.page.getByRole('button', { name: 'Find' });
// For in-shop and drop off
this.selectAShopOptions = this.page.locator('[class="shop-question"]');
this.firstAppointmentButton = this.page.locator('#availabilityIndicator').first();
this.firstAvailableAMDate = this.page.locator('morning-row-cell has-appointments selected-date').first();
this.firstAvailablePMDate = this.page.locator('afternoon-row-cell has-appointments selected-date').first();
this.firstAvailableTime = this.page.locator('.time-slot-button').first();
this.firstAvailabeAMTime = this.page.locator('.time-slot-button').nth(0);
this.modalContinueButton = this.page.locator('#modalbtn');
this.dropOffButton = this.page.getByText('Drop off your vehicle', { exact: true });
this.dropOffButton = this.page.locator('[class="dropoff-label"]');
this.dateText = this.page.locator('[class="modal-header mb-2 mt-2"]');
this.viewMoreDatesLink = this.page.getByText(/View more dates/).first();
this.viewMoreDatesLink = this.page.getByRole('button', { name: 'More Right arrow icon' });
this.continueButton = this.page.locator('#stacked').locator('button:has-text("Continue")');
}
async scheduleAppointment(appointmentDetails: IAppointmentDetails) {
const formattedDate = formatDate(appointmentDetails.appointmentDate!);
const formattedTime = formatTime(appointmentDetails.appointmentDate!);
const dateInput = this.page.locator(`div[id="${formattedDate}"]`);
const timeButton = this.page.locator(`div[aria-label="${formattedTime}"]`);
if (await dateInput.isVisible()) {
await dateInput.click();
} else {
await this.viewMoreDatesLink.click();
await dateInput.click();
}
await timeButton.click();
await this.modalContinueButton.click();
}
// Select in shop and schedule
async scheduleInShop(appointmentDetails?: IAppointmentDetails){
if (appointmentDetails?.serviceLocation === ServiceLocation.InShop) {
try {await this.inShopButton.waitFor({ state: 'visible', timeout: 5000 });
await this.inShopButton.isVisible()
await this.inShopButton.click();
await (await this.getFirstNonDropOffTimeSlot()).click();
} catch {
await this.inShopButton.isHidden()
await (await this.getFirstNonDropOffTimeSlot()).click();
}
} else if (appointmentDetails?.serviceLocation === ServiceLocation.DropOff) {
if (await this.inShopButton.isVisible()){
await this.inShopButton.waitFor({ state: 'visible', timeout: 5000 });
await this.inShopButton.click();
async scheduleFirstAppointment(serviceLocation: ServiceLocation) {
if (await this.firstAvailableDate.isVisible()) {
await this.firstAvailableDate.click();
} else {
await this.viewMoreDatesLink.click();
while(await this.firstAvailableDate.isHidden()){
await this.viewMoreDatesLink.click();
if (await this.dropOffButton.isVisible()) {
await this.dropOffButton.waitFor({ state: 'visible', timeout: 5000 });
await this.dropOffButton.click()
} else {
await (await this.getFirstNonDropOffTimeSlot()).click();
}
await this.firstAvailableDate.click();
} else {
await (await this.getFirstNonDropOffTimeSlot()).click();
}
} else {
await this.selectFirstAvailableTime();
}
await this.continueButton.click();
}
serviceLocation === ServiceLocation.DropOff ? await this.dropOffButton.click() : await this.firstAvailableTime.click();
await this.modalContinueButton.click();
return (`${await this.dateText.allInnerTexts()}`);
// Select mobile button, enter zip code, click find button, select first available time and continue
async scheduleMobile(appointmentDetails: IAppointmentDetails){
await this.mobileButton.click();
await this.zipCodeTextBox.fill(appointmentDetails.serviceAddress!.postalCode!);
await this.findButton.click();
await this.selectFirstAvailableTime();
await this.continueButton.click();
}
// Selects the first available time slot but if its not visible, click "More" first to load more dates and times
async selectFirstAvailableTime(): Promise<void> {
if (await this.firstAvailableTime.isVisible()) {
await this.firstAvailableTime.click();
} else {
await this.viewMoreDatesLink.click();
await this.firstAvailableTime.waitFor({ state: 'visible' });
await this.firstAvailableTime.click();
}
}
async getFirstNonDropOffTimeSlot(): Promise<Locator> {
const timeSlots = this.page.locator('.time-slot-button');
const nonDropOff = timeSlots.filter({ hasNotText: /Drop & Go/i });
return nonDropOff.first();
}
}

View file

@ -18,26 +18,6 @@ export class ServiceLocationPage extends BasePage {
readonly RecalWarningMessage2: Locator;
readonly militaryWarningMessage: Locator;
// For in-shop and drop off
readonly selectAShopOptions: Locator;
readonly firstAppointmentButton: Locator;
readonly changeZipButton: Locator;
readonly updateZipTextBox: Locator;
readonly saveZipButton: Locator;
// For mobile
readonly enterServiceAddressButton: Locator;
readonly serviceAddressTextBox: Locator;
readonly aptNumberTextBox: Locator;
readonly cityTextBox: Locator;
readonly stateDropDown: Locator;
readonly zipCodeTextBox: Locator;
readonly vehicleProtectedYesButton: Locator;
readonly vehicleProtectedNoButton: Locator;
readonly saveAddressButton: Locator;
issPageValue = 'service-location';
constructor(page: Page) {
super(page);
this.page = page;
@ -52,80 +32,6 @@ export class ServiceLocationPage extends BasePage {
this.RecalWarningMessage2 = this.page.getByText(/advanced safety system recalibration needs to be done in our shop./);
this.militaryWarningMessage = this.page.locator('[class*="widget-name-AlertMilitaryBaseZipWidget"]');
// For in-shop and drop off
this.selectAShopOptions = this.page.locator('[class="shop-question"]');
// this.firstAppointmentButton = this.page.locator('div').filter({ hasText: /Appts/}).first();
this.firstAppointmentButton = this.page.locator('#availabilityIndicator').first();
this.changeZipButton = this.page.locator('[id="serviceZipLinkPromptId"]');
this.updateZipTextBox = this.page.getByRole('textbox', { name: 'Update your service ZIP code'});
this.saveZipButton = this.page.getByRole('button', { name: 'Save ZIP code' });
// For mobile
this.enterServiceAddressButton = this.page.getByRole('link', { name: 'Enter your service address' });
this.serviceAddressTextBox = this.page.getByRole('textbox', { name: 'Street Address' });
this.aptNumberTextBox = this.page.getByRole('textbox', { name: 'Apt. number'});
this.cityTextBox = this.page.getByRole('textbox', { name: 'City' });
this.stateDropDown = this.page.getByRole('combobox', { name: 'State' });
this.zipCodeTextBox = this.page.getByRole('textbox', { name: 'Zip code' });
this.vehicleProtectedYesButton = this.page.locator('label').filter({ hasText: 'Yes' }).locator('div');
this.vehicleProtectedNoButton = this.page.locator('label').filter({ hasText: 'No' }).locator('div');
this.saveAddressButton = this.page.getByRole('button', { name: 'Save Address' });
}
async selectLocation(appointmentDetails: IAppointmentDetails){
if (appointmentDetails.alternateServiceZip) {
await this.changeZipButton.click();
await this.fillAndValidate(this.updateZipTextBox, appointmentDetails.alternateServiceZip);
await this.saveZipButton.click();
}
switch(appointmentDetails.serviceLocation) {
case ServiceLocation.Mobile:
await this.scheduleMobile(appointmentDetails);
break;
case ServiceLocation.InShop:
await this.scheduleInShop(appointmentDetails);
break;
case ServiceLocation.DropOff:
await this.scheduleDropOff(appointmentDetails);
break;
}
}
async scheduleInShop(appointmentDetails?: IAppointmentDetails) {
await this.inShopButton.click();
if (appointmentDetails && appointmentDetails.shopAddress) {
await this.selectAShopOptions.locator(`[buttonbodycopy="${appointmentDetails.shopAddress}"]`).check();
} else {
await this.firstAppointmentButton.click();
}
}
async scheduleMobile(appointmentDetails: IAppointmentDetails){
if (appointmentDetails.serviceAddress) {
await this.mobileButton.click();
await this.enterServiceAddressButton.click();
await this.addressForm.populateAddress({ address: appointmentDetails.serviceAddress! });
if (faker.datatype.boolean()) {
await this.vehicleProtectedYesButton.check();
} else {
await this.vehicleProtectedNoButton.check();
}
await this.saveAddressButton.click();
} else {
console.error('ServiceLocationPage >> Please supply an address')
}
}
async scheduleDropOff(appointmentDetails?: IAppointmentDetails){
await this.dropOffButton.click();
if (appointmentDetails && appointmentDetails.shopAddress) {
await this.selectAShopOptions.locator(`[buttonbodycopy="${appointmentDetails.shopAddress}"]`).check();
} else {
await this.firstAppointmentButton.click();
}
}
async validateRecalWarning(){

View file

@ -6,21 +6,37 @@ export class ServicePackagesPage extends BasePage {
readonly page: Page;
readonly standardPackageButton: Locator;
readonly premiumPackageButton: Locator;
readonly glassOnlyButton: Locator;
readonly glassOnlyPackageButton: Locator;
readonly wiperModal: Locator;
readonly wiperModalCloseButton: Locator;
readonly continueButton: Locator;
issPageValue = 'service-packages';
constructor(page: Page) {
super(page);
this.page = page;
this.standardPackageButton = this.page.locator('li').filter({ hasText: 'Standard' });
this.premiumPackageButton = this.page.locator('li').filter({ hasText: 'Premium' });
this.glassOnlyButton = this.page.locator('li').filter({ hasText: 'Glass service' });
this.standardPackageButton = this.page.locator('label[for="ServicePackageQuestion-TierTwo"]'); // standard package
this.premiumPackageButton = this.page.locator('label[for="ServicePackageQuestion-TierThree"]'); // premium package
this.glassOnlyPackageButton = this.page.locator('label[for="ServicePackageQuestion-TierOne"]'); // glass only package
this.wiperModal = this.page.locator('#FrontWiperModal #modalbtn');
this.wiperModalCloseButton = this.page.locator('#FrontWiperModal').getByLabel('Close')
this.continueButton = this.page.getByRole('button', { name: 'Continue' });
// this.validateURL(this.url);
}
async selectServicePackage(servicePackage: ServicePackage){
await this.page.getByText(servicePackage).click();
return (`${await this.page.locator('li').filter({ hasText: 'Premium' }).locator('[class="pricing-info"]').allInnerTexts()}`);
//return (`${await this.page.getByText(servicePackage).locator('[class="pricing-info"]').allInnerTexts()}`);
if (servicePackage === ServicePackage.Standard) {
await this.standardPackageButton.check();
} else if (servicePackage === ServicePackage.Premium) {
await this.premiumPackageButton.check();
try {await this.wiperModal.waitFor({ state: 'visible', timeout: 5000 });
await this.wiperModalCloseButton.click();
} catch {
await this.continueButton.click();
}
} else if (servicePackage === ServicePackage.GlassOnly) {
await this.glassOnlyPackageButton.check();
}
await this.continueButton.click();
}
}

View file

@ -4,16 +4,30 @@ import { BasePage } from './BasePage';
export class TpaConfirmationPage extends BasePage {
readonly page: Page;
readonly successMessage: Locator;
readonly shopConfirmationMessage: Locator;
readonly issPageValue = 'tpa-confirmation';
constructor(page: Page) {
super(page);
this.page = page;
this.successMessage = page.getByText('Success');
this.successMessage = page.getByText('Thank you for submitting your claim; there is one final step.');
this.shopConfirmationMessage = page.locator('div.subheader').first()
}
async getTpaShopSelectedName() {
const sessionStorage = JSON.parse(await this.page.evaluate('sessionStorage.getItem(\'submittedOrder\')'));
const tpaShopSelectedName = sessionStorage.serviceLocation.provider.companyName;
return tpaShopSelectedName;
}
async validateSuccessMessage() {
await expect(this.successMessage).toBeVisible();
}
const shopConfirmationMessageText = await this.shopConfirmationMessage.textContent();
const tpaShopSelectedNameText = await this.getTpaShopSelectedName();
await this.successMessage.waitFor({ state: 'visible' });
await expect(this.successMessage).toBeVisible();
expect.soft(shopConfirmationMessageText?.toLowerCase()).toContain(
('Your information has been sent to' + ' ' + tpaShopSelectedNameText).toLowerCase()
);
}
}

View file

@ -4,22 +4,14 @@ import { BasePage } from './BasePage';
export class TpaSearchPage extends BasePage {
readonly page: Page;
readonly firstLocationButton: Locator;
readonly doNotSeeMyShopButton: Locator;
issPageValue = 'tpa-search';
constructor(page: Page) {
super(page);
this.page = page;
// this.firstLocationButton = page.locator("fieldset[aria-labelledby='chooseShop']/span").first();
this.firstLocationButton = page.locator("#buttonLabelSpan").first();
this.doNotSeeMyShopButton = page.getByRole('link', { name: 'I don\'t see my shop' });
this.firstLocationButton = page.locator('div.button-content').first();
}
async selectDoNotSeeMyShop() {
await this.doNotSeeMyShopButton.click()
}
async selectFirstLocation() {
await this.firstLocationButton.click();
}

View file

@ -1,18 +1,29 @@
import { expect, type Locator, type Page } from '@playwright/test';
import { BasePage } from './BasePage';
import { IClaimDetails } from '@business-logic/types/CustomerDetails';
export class TpaSubmitPage extends BasePage {
readonly page: Page;
readonly deductible: Locator;
readonly recalAlert: Locator;
readonly learnMoreLink: Locator;
readonly recalModal: Locator;
readonly closeRecalModalButton: Locator;
issPageValue = 'tpa-submit';
constructor(page: Page) {
super(page);
this.page = page;
this.deductible = page.getByText('Deductible $');
this.recalAlert = page.getByRole('alert').filter({ hasText: 'Some vehicle safety features may not work properly.'});
this.learnMoreLink = page.getByRole('link', { name: 'Learn more' });
this.recalModal = page.getByRole('dialog').filter({ hasText: 'IMPORTANT SERVICE REQUIRED' }).first();
this.closeRecalModalButton = page.locator('.modalbtn', { hasText: 'Close' });
this.deductible = page.locator('span#deductible-value, span.deductible-value');
}
async validateDeductible(expDeductible) {
await expect(this.deductible).toContainText(expDeductible);
async validateDeductible(claimDetails: IClaimDetails) {
await expect(this.deductible).toContainText(claimDetails.policyDeductible.toLocaleString());
}
// TODO: Validate change preferred shop and contact details flows
}

View file

@ -4,13 +4,13 @@ import { VehicleDamage } from '@business-logic/types/Enums';
export class VehicleDamagePage extends BasePage {
readonly page: Page;
readonly windshieldChkBox: Locator;
readonly crackButton: Locator;
readonly chipButton: Locator;
readonly windshieldButton: Locator;
readonly sideDoorButton: Locator;
readonly replaceButton: Locator;
readonly repairButton: Locator;
readonly singleWindshieldButton: Locator;
readonly splitWindshieldDriverSideButton: Locator;
readonly splitWindshieldPassengerSideButton: Locator;
readonly sideDoorButton: Locator;
readonly driverSideButton: Locator;
readonly passengerSideButton: Locator;
readonly driverQuarterPanelChkBox: Locator;
@ -30,9 +30,11 @@ export class VehicleDamagePage extends BasePage {
constructor(page: Page) {
super(page);
this.page = page;
this.windshieldChkBox = this.page.locator('[buttonlabel="Windshield"]');
this.crackButton = this.page.locator('[buttonlabel="Crack"]');
this.chipButton = this.page.locator('[buttonlabel="Chip(s)"]');
this.windshieldButton = this.page.locator('[buttonlabel="Windshield"]');
this.sideDoorButton = this.page.locator('[buttonlabel="Side door"]');
this.rearWindowChkBox = this.page.locator('[buttonlabel="Rear window"]');
this.replaceButton = this.page.locator('div.list-card-content:has-text("Replace")');
this.repairButton = this.page.locator('div.list-card-content:has-text("Repair")');
this.singleWindshieldButton = this.page.locator('[buttonlabel="Single windshield"]');
this.splitWindshieldDriverSideButton = this.page.locator('[buttonlabel="Split windshield, driver side"]');
this.splitWindshieldPassengerSideButton = this.page.locator('[buttonlabel="Split windshield, passenger side"]');
@ -55,17 +57,17 @@ export class VehicleDamagePage extends BasePage {
async checkSeparateApptsWarning() {
// Select conflict
await this.windshieldChkBox.check();
await this.chipButton.check();
await this.windshieldButton.check();
await this.repairButton.check();
await this.rearWindowChkBox.check();
// Expect warning
await expect.soft(this.separateApptsWarning).toBeAttached();
// Undo changes
await this.crackButton.check();
await this.windshieldChkBox.uncheck();
await expect.soft(this.crackButton).not.toBeVisible();
await this.replaceButton.check();
await this.windshieldButton.uncheck();
await expect.soft(this.replaceButton).not.toBeVisible();
await this.rearWindowChkBox.uncheck();
}
@ -73,33 +75,33 @@ export class VehicleDamagePage extends BasePage {
for (const damage of vehicleDamage) {
switch(damage) {
case VehicleDamage.WindshieldOneChip:
await this.windshieldChkBox.check();
await this.windshieldButton.check();
await this.selectChips('1');
break;
case VehicleDamage.WindshieldTwoChips:
await this.windshieldChkBox.check();
await this.windshieldButton.check();
await this.selectChips('2');
break;
case VehicleDamage.WindshieldThreeChips:
await this.windshieldChkBox.check();
await this.windshieldButton.check();
await this.selectChips('3');
break;
case VehicleDamage.WindshieldCrack:
await this.windshieldChkBox.check();
await this.windshieldButton.check();
await this.selectCrack();
break;
case VehicleDamage.WindshieldCrackSingleWindshield:
await this.windshieldChkBox.check();
await this.windshieldButton.check();
await this.selectCrack();
await this.singleWindshieldButton.check();
break;
case VehicleDamage.WindshieldCrackDriverSide:
await this.windshieldChkBox.check();
await this.windshieldButton.check();
await this.selectCrack();
await this.splitWindshieldDriverSideButton.check();
break;
case VehicleDamage.WindshieldCrackPassengerSide:
await this.windshieldChkBox.check();
await this.windshieldButton.check();
await this.selectCrack();
await this.splitWindshieldPassengerSideButton.check();
break;
@ -156,12 +158,12 @@ export class VehicleDamagePage extends BasePage {
}
async selectCrack(){
await this.crackButton.check();
await this.replaceButton.click();
}
async selectChips(numChips: string){
const numChipsButton = this.page.getByText(numChips, {exact: true});
await this.chipButton.check();
await this.repairButton.check();
await numChipsButton.check();
}

View file

@ -16,7 +16,7 @@ export class VehicleLookupLicensePage extends BasePage {
this.licensePlateNumTextBox = page.getByRole('textbox', { name: 'License plate number' });
this.licensePlateStateDrpDwn = page.getByRole('combobox', { name: 'License plate state' });
this.plateNoMatchError = page.getByText('Your license plate didnt return a VIN match.Please re-enter the information');
this.plateMismatchAlert = page.getByRole('alert').locator('div');
this.plateMismatchAlert = page.getByText('We found a windshield, but the VIN associated with the license plate you provided is for a');
}
async enterPlateDetails(vehicleDetails: IVehicleDetails, isVehicleLookupValidations = false) {
@ -26,6 +26,7 @@ export class VehicleLookupLicensePage extends BasePage {
await this.continueButton.click({ timeout: 1000 });
await expect(this.plateNoMatchError).toBeVisible();
await this.licensePlateNumTextBox.fill(vehicleDetails.licensePlateNumber[1]);
await this.licensePlateStateDrpDwn.selectOption(vehicleDetails.licensePlateState![1]);
await this.continueButton.click({ timeout: 1000 });
await expect(this.plateMismatchAlert).toBeVisible();
await this.licensePlateNumTextBox.fill(vehicleDetails.licensePlateNumber[2]);

View file

@ -19,7 +19,7 @@ export class VehicleLookupPage extends BasePage {
constructor(page: Page) {
super(page);
this.page = page;
this.vinLookupButton = page.getByLabel('Provide my VIN manually', { exact: true });
this.vinLookupButton = page.getByLabel('Provide my VIN', { exact: true });
this.addressLookupButton = page.getByLabel('Provide my home address', { exact: true });
this.licenseLookupButton = page.getByLabel('Provide my license plate #', { exact: true });
this.vinLookupPage = new VinLookupPage(page);

View file

@ -1,5 +1,6 @@
import { Page } from "@playwright/test";
import { expect, type Locator, type Page } from "@playwright/test";
import { PartQuestionsPage } from "./PartQuestionsPage";
import { IPartQuestion } from '@business-logic/types/CustomerDetails';
export default class VehiclePartQuestionsPage extends PartQuestionsPage{
issPageValue = 'vehicle-parts';
@ -7,4 +8,16 @@ export default class VehiclePartQuestionsPage extends PartQuestionsPage{
constructor(page: Page) {
super(page);
}
async selectPartQuestionResponses(partQuestions: IPartQuestion[]) {
for (const pq of partQuestions) {
const parentobject=this.page.locator(`fieldset[aria-labelledby="${pq.partQuestionType}"]`);
const partQuestionOptionButton = parentobject.locator(`[buttonlabel="${pq.optionToSelect}"]`);
await partQuestionOptionButton.nth(0).click();
if (pq.secondaryQuestionOptionToSelect != null) {
const secondaryQuestionButton = parentobject.getByText(`${pq.secondaryQuestionOptionToSelect}`);
await secondaryQuestionButton.nth(1).click();
}
}
}
}

View file

@ -16,6 +16,7 @@ export class WelcomePage extends BasePage {
readonly city: Locator;
readonly state: Locator;
readonly cookieCloseButton: Locator;
readonly GetStartedButton: Locator;
url = process.env['BASE_URL']! + '/?issPage=welcome-page';
issPageValue = 'welcome-page';
@ -24,13 +25,14 @@ export class WelcomePage extends BasePage {
this.page = page;
this.policyNumber = page.getByRole('textbox', { name: 'Policy number' });
this.policyZip = page.getByRole('textbox', { name: 'Policy ZIP' });
this.damageDate = page.getByRole('textbox', { name: 'When did the damage occur?<' });
this.damageDate = page.locator('#dateOfLossField');
this.damageCause = page.locator('#damageCauseQuestionField');
this.phoneNumber = page.getByRole('textbox', { name: 'Best number to reach you' });
this.emailAddress = page.getByRole('textbox', { name: 'Email address' });
this.city = page.getByRole('textbox', { name: 'In which city did the damage' });
this.state = page.locator('select[name="\\38 fdf9dc2e13e430eb57529499dceb3eb"]');
this.cookieCloseButton = page.getByRole('button', { name: 'Close' });
this.GetStartedButton = page.getByRole('button', { name: 'Get Started' });
}
async goto(clientTag: string) {
@ -66,18 +68,17 @@ export class WelcomePage extends BasePage {
async populatePage(customerDetails: ICustomerDetails, claimDetails: IClaimDetails, isFillCityInfo: boolean) {
await this.policyNumber.fill(claimDetails.policyNumber);
await this.policyZip.fill(customerDetails.address.postalCode);
await this.phoneNumber.fill(customerDetails.phoneNumber);
await this.damageDate.click();
await this.damageDate.fill(claimDetails.damageDate);
await this.damageCause.selectOption(claimDetails.damageCause);
await this.damageCause.press('Tab');
await this.phoneNumber.fill(customerDetails.phoneNumber);
// Phone number fixed 12/5. Can't start with 1 or 0
await this.emailAddress.fill(customerDetails.email);
await this.policyZip.fill(customerDetails.address.postalCode);
if (isFillCityInfo) {
await this.city.fill(customerDetails.address.city);
await this.state.selectOption(customerDetails.address.state);
await this.city.fill(customerDetails.address.city);
}
}
}

View file

@ -88,6 +88,7 @@ export default defineConfig({
// baseURL: 'http://127.0.0.1:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
baseURL: process.env.BASE_URL || 'https://selfservice.test.glassclaim.com',
trace: 'on-first-retry',
headless: process.env.CI ? true : false,
screenshot: "only-on-failure",

View file

@ -1,11 +1,12 @@
import TestCase from "@business-logic/types/TestCase";
import { expect, Page } from "@playwright/test";
import { getDefaultTestData } from "safelite-playwright-core";
import { addSmokeTagToRandomTest, prepareTest, test, TestInfo } from "@business-logic/types/Test";
import { RuleEngine, ValidationOptions } from "@business-logic/types/RuleEngine";
import { ICustomerDetails } from "@business-logic/types/CustomerDetails";
import essentialHpTestCases from "./0016_EssentialRepairInShop";
import essentialReplaceTestCases from "./0013_EssentialReplace";
import { BailoutCode, VehicleDamage, VehicleLookupType } from "@business-logic/types/Enums";
import { BailoutCode, ServiceLocation, VehicleDamage, VehicleLookupType } from "@business-logic/types/Enums";
import essentialTpaNotEnabledTestCases from "./0003_EssentialTpaNotEnabledBailout";
import essentialDoNotSeeShopTestCases from "./0011_EssentialDoNotSeeShopBailout";
import essentialVehicleNotFoundTestCases from "./0010_EssentialVehicleNotFoundBailout";
@ -58,7 +59,7 @@ import advancedScenario0025TestCases from "./advanced/0025a_VehicleByVINUnverifi
import advancedScenario0026aTestCases from "./advanced/0026a_NoDeductibleAdasBailout5";
import advancedScenario0028aTestCases from "./advanced/0028a_ItacCancelMyClaim";
import advancedScenario0029aTestCases from "./advanced/0029a_HeavyVehicleBailout";
import advancedScenario0030aTestCases from "./advanced/0030a_PartsServiceBailout";
import advancedScenario0030aTestCases from "./advanced/0030a_APIErrorBailout";
import advancedScenario0031aTestCases from "./advanced/0031a_ReplaceServiceableBigTruck";
import advancedScenario0032aTestCases from "./advanced/0032a_DeclineNonServiceableBigTruck";
@ -382,8 +383,8 @@ async function runWorkflow(page: Page, testCase: TestCase) {
// Destructure data for easy access
const { customerDetails, claimDetails, vehicleDetails, vehicleDamage,
appointmentDetails, isSafelite, endorsements,
partQuestions, paymentDetails, isNoComp, isItac, isRecalNotification,
isRecalWarning, servicePackage, hasStateLawPopup, otherVehiclesOnPolicy,
partQuestions, paymentDetails, isNoComp, isItac, isRecalNotification, isUnverifiedPolicyAfterVehicleLookup,
isRecalWarning, servicePackage, hasOemEndorsement, hasStateLawPopup, otherVehiclesOnPolicy,
isSeparateApptsWarning, vehiclePartQuestions, editVehicleDetails, isAddressLookupValidations,
hasMilitaryWarning, capabilityQuestions, isUseVehicleOnPolicy,
isVehicleLookupValidations, isMoldingQuestion, isNonServiceable, isNonServiceableVin } = testCase.testData;
@ -401,9 +402,9 @@ async function runWorkflow(page: Page, testCase: TestCase) {
vehiclePartQuestionsPage, capabilityQuestionsPage, moldingQuestionsPage, addressLookupPage } = testCase.pages;
// Destructure bailout flags
const { isVehicleSelectBailout, isDoNotSeeMyShopBailout, isTpaNotEnabledBailout,
isPartsServiceErrorBailout, isVehicleLookupBailout, isPriceServiceErrorBailout,
isRequestCallbackBailout } = testCase.testData.bailoutFlags || {};
const { isVehicleSelectBailout, isTpaNotEnabledBailout,
isAPIErrorBailout, isVehicleLookupBailout, isPriceServiceErrorBailout,
isRequestCallbackBailout, isHeavyTruckVehicleBailout, isCoverageCancelledByUser } = testCase.testData.bailoutFlags || {};
const repairTypes: VehicleDamage[] = [
VehicleDamage.WindshieldOneChip,
@ -420,7 +421,7 @@ async function runWorkflow(page: Page, testCase: TestCase) {
await test.step('WelcomePage >> Populate Customer Details', async () => {
await welcomePage.populatePage(customerDetails!, claimDetails!, await welcomePage.hasCityInfo());
await welcomePage.nextPage();
await welcomePage.GetStartedButton.click();
});
if (testCase.testData.isDuplicateClaim) {
@ -445,26 +446,35 @@ async function runWorkflow(page: Page, testCase: TestCase) {
}
}
if (!(isUseVehicleOnPolicy ?? true)) {
await policyVehiclesPage.selectVehicleNotListed();
await policyVehiclesPage.addAnotherVehicle();
await policyVehiclesPage.nextPage();
await vehicleSelectionPage.selectVehicle(vehicleDetails!);
await policyVehiclesPage.nextPage();
await test.step('PolicyHolderDetailsPage >> Enter customer data', async () => {
await policyHolderDetailsPage.validateURL(policyHolderDetailsPage.issPageValue);
await policyHolderDetailsPage.fillCustomerDetails(customerDetails!);
await policyHolderDetailsPage.nextPage();
});
} else {
// Select vehicle
await policyVehiclesPage.selectVehicle(vehicleDetails!);
await policyVehiclesPage.nextPage();
await policyVehiclesPage.logReferralNumber();
if (isNonServiceable && isHeavyTruckVehicleBailout) {
await bailoutPage.validateURL(bailoutPage.issPageValue);
await bailoutPage.validateBailoutDetails(customerDetails!, BailoutCode.HeavyTruckVehicle);
return;
}
if (isNonServiceable) {
await policyVehiclesPage.assertNonServiceableAlertBehavior();
return;
}
await policyVehiclesPage.nextPage();
}
});
if (isNonServiceable) {
return;
}
if (hasEndorsements) {
if (hasEndorsements) {
await test.step('EndorsementsPage >> Select Endorsements', async () => {
await endorsementsPage.validateURL(endorsementsPage.issPageValue);
await endorsementsPage.verifyEndorsements(endorsements);
@ -472,29 +482,22 @@ async function runWorkflow(page: Page, testCase: TestCase) {
await endorsementsPage.nextPage();
});
}
} else {
await policyVehiclesPage.logReferralNumber();
await test.step('PolicyHolderDetailsPage >> Enter customer data', async () => {
await policyHolderDetailsPage.validateURL(policyHolderDetailsPage.issPageValue);
await policyHolderDetailsPage.fillCustomerDetails(customerDetails!);
await policyHolderDetailsPage.nextPage();
await test.step('PolicyHolderDetailsPage >> Enter customer data', async () => {
await policyHolderDetailsPage.validateURL(policyHolderDetailsPage.issPageValue);
await policyHolderDetailsPage.fillCustomerDetails(customerDetails!);
await policyHolderDetailsPage.nextPage();
});
}
});
if (isNonServiceable) {
return;
}
if (isVehicleLookupBailout) {
forceAPIError(page, '/vehicle/api/v1/vehicle/lookup')
}
await test.step('VehicleDetailsPage >> Select Vehicle', async () => {
await vehicleSelectionPage.validateURL(vehicleSelectionPage.issPageValue);
await vehicleSelectionPage.selectVehicle(vehicleDetails!);
if (isNonServiceable) {
await vehicleSelectionPage.assertNonServiceableAlertBehavior();
return;
}
await vehicleSelectionPage.nextPage();
});
if (isNonServiceable) {
return;
}
@ -508,20 +511,21 @@ async function runWorkflow(page: Page, testCase: TestCase) {
}
}
if (editVehicleDetails) {
isPolicyFound = false; // Flow proceeds as unverified
// Essential flow when policy is not found
if (isPolicyFound === false) {
testCase.testData.isPolicyFound = false;
await test.step('VehicleDamagePage >> Click Edit Vehicle', async () => {
await vehicleDamagePage.validateURL(vehicleDamagePage.issPageValue);
await vehicleDamagePage.editVehicleButton.click();
});
await test.step('VehicleDetailsPage >> Select edited vehicle', async () => {
await test.step('VehicleDetailsPage >> Select Vehicle', async () => {
await test.step('VehicleSelectionPage >> Select vehicle for essential flow', async () => {
await vehicleSelectionPage.validateURL(vehicleSelectionPage.issPageValue);
await vehicleSelectionPage.selectVehicle(editVehicleDetails);
await vehicleSelectionPage.selectVehicle(vehicleDetails!);
await vehicleSelectionPage.nextPage();
});
});
await test.step('PolicyHolderDetailsPage >> Enter customer data', async () => {
await policyHolderDetailsPage.validateURL(policyHolderDetailsPage.issPageValue);
await policyHolderDetailsPage.fillCustomerDetails(customerDetails!);
await policyHolderDetailsPage.nextPage();
});
}
@ -534,7 +538,7 @@ async function runWorkflow(page: Page, testCase: TestCase) {
await vehicleDamagePage.nextPage();
});
if (isPartsServiceErrorBailout) {
if (isAPIErrorBailout) {
await test.step('VehicleLookupPage >> Select Lookup Type', async () => {
await vehicleLookupPage.validateURL(vehicleLookupPage.issPageValue);
await vehicleLookupPage.vehicleLookup(vehicleDetails!);
@ -545,9 +549,9 @@ async function runWorkflow(page: Page, testCase: TestCase) {
forceAPIError(page, '/parts/api/v1/parts')
await vinLookupPage.nextPage();
});
await test.step('BailoutPage >> Parts Service Error Bailout', async () => {
await test.step('BailoutPage >> API error occurred bailout', async () => {
await bailoutPage.validateURL(bailoutPage.issPageValue);
await bailoutPage.validateBailoutDetails(customerDetails!, BailoutCode.PartsServiceError);
await bailoutPage.validateBailoutDetails(customerDetails!, BailoutCode.ApiError);
return;
});
return;
@ -584,10 +588,14 @@ async function runWorkflow(page: Page, testCase: TestCase) {
await test.step('Address Lookup validations >> Vehicle Lookup by address: ' + customerDetails!.address.street, async () => {
if (Array.isArray(vehicleDetails?.address)) {
await addressLookupPage.validateURL(addressLookupPage.issPageValue);
await addressLookupPage.validateAddressLookupAlerts(vehicleDetails?.address);
await addressLookupPage.validateAddressLookupAlerts(vehicleDetails?.address!);
await addressLookupPage.nextPage();
}
});
await test.step('Address Vehicles >> Next page', async () => {
await addressLookupPage.nextPage();
});
} else {
await test.step('VehicleLookupAddressPage >> Lookup by address: ' + customerDetails!.address.street, async () => {
await vehicleLookupAddressPage.validateURL(vehicleLookupAddressPage.issPageValue);
@ -627,6 +635,7 @@ async function runWorkflow(page: Page, testCase: TestCase) {
break;
}
}
if (isMoldingQuestion) {
await test.step('Molding Questions Page >> Select Yes', async () => {
await moldingQuestionsPage.validateURL(moldingQuestionsPage.issPageValue);
@ -637,6 +646,7 @@ async function runWorkflow(page: Page, testCase: TestCase) {
await moldingQuestionsPage.nextPage();
});
}
if (capabilityQuestions && capabilityQuestions.length > 0) {
await capabilityQuestionsPage.validateURL(capabilityQuestionsPage.issPageValue);
await capabilityQuestionsPage.validatePartQuestions(capabilityQuestions);
@ -645,6 +655,7 @@ async function runWorkflow(page: Page, testCase: TestCase) {
}
if (partQuestions && partQuestions.length > 0) {
let partQuestionsPage = testCase.pages.partQuestionsPage;
await partQuestionsPage.validateURL(partQuestionsPage.issPageValue);
await partQuestionsPage.validatePartQuestions(partQuestions);
await partQuestionsPage.selectPartQuestionResponses(partQuestions);
@ -653,60 +664,96 @@ async function runWorkflow(page: Page, testCase: TestCase) {
if (vehiclePartQuestions && vehiclePartQuestions.length > 0) {
await vehiclePartQuestionsPage.validateURL(vehiclePartQuestionsPage.issPageValue);
await vehiclePartQuestionsPage.validatePartQuestions(vehiclePartQuestions);
await vehiclePartQuestionsPage.selectPartQuestionResponses(vehiclePartQuestions);
await vehiclePartQuestionsPage.nextPage();
}
if (isRequestCallbackBailout) {
await test.step('CoverageStatementPage >> Cancel My claim', async () => {
if (isCoverageCancelledByUser) {
await test.step('CoverageStatementPage >> Cancel my claim', async () => {
await coverageStatementPage.validateURL(coverageStatementPage.issPageValue);
await coverageStatementPage.cancelMyClaim();
});
await test.step('BailoutPage >> Request Callback Bailout', async () => {
await test.step('BailoutPage >> User canceled claim on ITAC/No Comp coverage statement', async () => {
await bailoutPage.validateURL(bailoutPage.issPageValue);
await bailoutPage.validateBailoutDetails(customerDetails!, BailoutCode.RequestCallback);
await bailoutPage.validateBailoutDetails(customerDetails!, BailoutCode.CoverageCancelledByUser);
});
return;
}
await test.step('CoverageStatementPage >> Next page', async () => {
await coverageStatementPage.validateURL(coverageStatementPage.issPageValue);
// Confirm no coverage
if (isPolicyFound && (isItac || isNoComp)) {
await coverageStatementPage.continueToScheduleButton.click();
}
await coverageStatementPage.nextPage();
if (hasOemEndorsement) {
await coverageStatementPage.validateOEMBullet();
}
// For essential/unverified flows
if (!isPolicyFound) {
await coverageStatementPage.handleUnverifiedFlow(claimDetails!);
}
// For No Comp flows
if (isPolicyFound && isNoComp === true) {
await coverageStatementPage.handleNoCompFlow();
}
// For ITAC flows
if (isPolicyFound && isItac === true) {
await coverageStatementPage.handleITACFlow();
}
// For replacements with deductible
if (isPolicyFound && claimDetails?.policyDeductible! > 0 && claimDetails?.policyDeductible !== 9999 && testCase.testData.isUnverifiedPolicyAfterVehicleLookup !== true) {
await coverageStatementPage.handleReplacementWithDeductibleFlow(claimDetails!);
}
// For replacements or repairs with 0 deductible
if (isPolicyFound && claimDetails?.policyDeductible === 0) {
await coverageStatementPage.handleReplacementOrRepairWithNoDeductibleFlow(claimDetails!, VehicleDamage!);
}
// For unverified policy after vehicle lookup
if (isPolicyFound && testCase.testData.isUnverifiedPolicyAfterVehicleLookup === true) {
await coverageStatementPage.handleUnverifiedPolicyAfterVehicleLookupFlow();
}
});
if (hasStateLawPopup) {
await test.step('ProviderPreferencePage >> Dismiss state law popup', async () => {
await providerPreferencePage.validateURL(providerPreferencePage.issPageValue);
await providerPreferencePage.validateStateLawModalIsVisible();
await providerPreferencePage.gotItButton.click();
});
if (vehicleDamage!.includes(VehicleDamage.WindshieldCrack)) {
await providerPreferencePage.stateLawModalOkayButton.click();
} else {
await providerPreferencePage.stateLawModalOkayButton.click();
}
});
}
if (!isPolicyFound || !(isItac || isNoComp)) {
if (!isPolicyFound || !(isItac || isNoComp) && !isUnverifiedPolicyAfterVehicleLookup) {
await test.step('ProviderPreferencePage >> Select Provider ' + isSafelite ? "Safelite" : "Other shops(Non-Safelite)", async () => {
await providerPreferencePage.validateURL(providerPreferencePage.issPageValue);
await providerPreferencePage.selectProvider(isSafelite);
});
}
// validations for Recal warning mesage
if (isRecalWarning) {
await serviceLocationPage.validateURL(serviceLocationPage.issPageValue);
await serviceLocationPage.validateRecalWarning();
if (isSafelite && isUnverifiedPolicyAfterVehicleLookup) {
await test.step('ProviderPreferencePage >> Select Provider ' + isSafelite ? "Safelite" : "Other shops(Non-Safelite)", async () => {
await providerPreferencePage.validateURL(providerPreferencePage.issPageValue);
await providerPreferencePage.selectProvider(isSafelite);
});
}
// if isRecalNotifidation flag true additional step to acknowledge Recal notification.
if (isRecalNotification) {
await providerPreferencePage.validateURL(providerPreferencePage.issPageValue);
await providerPreferencePage.acknowledgeRecalNotificaiton();
if (!isSafelite && isUnverifiedPolicyAfterVehicleLookup) {
await test.step('ProviderPreferencePage >> Schedule TPA without Adas', async () => {
await providerPreferencePage.validateURL(providerPreferencePage.issPageValue);
await providerPreferencePage.scheduleTPAWithoutAdas();
});
}
// If No-Comp or ITAC, ProviderPreferencePage does not appear
if (!isPolicyFound || !(isItac || isNoComp)) {
if (!isPolicyFound || !(isItac || isNoComp) || isUnverifiedPolicyAfterVehicleLookup) {
if (!isSafelite) {
@ -728,16 +775,6 @@ async function runWorkflow(page: Page, testCase: TestCase) {
return;
}
if (isDoNotSeeMyShopBailout) {
await test.step('TpaSearchPage >> Select "Do Not See My Shop"', async () => {
await tpaSearchPage.validateURL(tpaSearchPage.issPageValue);
await tpaSearchPage.selectDoNotSeeMyShop();
});
await bailoutPage.validateURL(bailoutPage.issPageValue);
await bailoutPage.validateBailoutDetails(customerDetails!, BailoutCode.DoNotSeeMyShop);
return;
}
await test.step('TpaSearchPage >> TPA Search', async () => {
await tpaSearchPage.validateURL(tpaSearchPage.issPageValue);
await tpaSearchPage.selectFirstLocation();
@ -745,9 +782,12 @@ async function runWorkflow(page: Page, testCase: TestCase) {
});
await test.step('TpaSubmitPage >> TPA Submit', async () => {
// TODO: Validations
await tpaSubmitPage.validateURL(tpaSubmitPage.issPageValue);
if (!isUnverifiedPolicyAfterVehicleLookup) {
await tpaSubmitPage.validateDeductible(claimDetails!);
} else {
await tpaSubmitPage.nextPage();
}
});
await test.step('TpaConfirmationPage >> TPA Confirmation', async () => {
@ -756,43 +796,41 @@ async function runWorkflow(page: Page, testCase: TestCase) {
return;
});
return;
}
}
await test.step('ServiceLocationPage >> Select service location', async () => {
await serviceLocationPage.validateURL(serviceLocationPage.issPageValue);
await serviceLocationPage.selectLocation(appointmentDetails!);
if (hasMilitaryWarning) {
await expect.soft(serviceLocationPage.militaryWarningMessage).toBeVisible();
}
await serviceLocationPage.nextPage();
});
}}
await test.step('SchedulePage >> Select day and time', async () => {
await schedulePage.validateURL(schedulePage.issPageValue);
customerDetails!.apptDate = await schedulePage.scheduleFirstAppointment(appointmentDetails!.serviceLocation);
});
await test.step('ContactDetailsPage >> Validate contact details', async () => {
const expectedContactDetails: Partial<ICustomerDetails> = {
firstName: customerDetails!.firstName,
lastName: customerDetails!.lastName,
email: customerDetails!.email,
phoneNumber: customerDetails!.phoneNumber
};
await contactDetailsPage.validateURL(contactDetailsPage.issPageValue);
const actualContactDetails = await contactDetailsPage.getContactDetails();
expect.soft(actualContactDetails).toEqual(expectedContactDetails);
await contactDetailsPage.fillNotes(customerDetails!.notes);
if (isPriceServiceErrorBailout) {
forceAPIError(page, '/price/api/v1/price/combined-quote');
if (appointmentDetails?.serviceLocation === ServiceLocation.Mobile) {
await schedulePage.scheduleMobile(appointmentDetails);
}
await contactDetailsPage.nextPage();
if (appointmentDetails?.serviceLocation === ServiceLocation.InShop || appointmentDetails?.serviceLocation === ServiceLocation.DropOff) {
await schedulePage.scheduleInShop(appointmentDetails);
}
});
await test.step('ServicePackagesPage >> Choose service package', async () => {
await servicePackagesPage.validateURL(servicePackagesPage.issPageValue);
await servicePackagesPage.selectServicePackage(servicePackage!);
});
if (appointmentDetails?.serviceLocation === ServiceLocation.Mobile) {
await test.step('ContactDetailsPage >> Validate contact details', async () => {
await contactDetailsPage.validateURL(contactDetailsPage.issPageValue);
await contactDetailsPage.fillContactDetails(customerDetails!);
if (appointmentDetails?.alternateMobileCity) {
await contactDetailsPage.fillAlternateMobileStreetAndCity(appointmentDetails?.alternateMobileStreetAddress!, appointmentDetails?.alternateMobileCity!);
}
await contactDetailsPage.nextPage();
});
}
if (isPriceServiceErrorBailout) {
await test.step('BailoutPage >> Price Service Error Bailout', async () => {
await bailoutPage.validateURL(bailoutPage.issPageValue);
@ -801,22 +839,29 @@ async function runWorkflow(page: Page, testCase: TestCase) {
return;
}
await test.step('ServicePackagesPage >> Choose service package', async () => {
await servicePackagesPage.validateURL(servicePackagesPage.issPageValue);
customerDetails!.packagePrice = await servicePackagesPage.selectServicePackage(servicePackage!);
await servicePackagesPage.nextPage();
});
if (isPolicyFound && (isUseVehicleOnPolicy ?? true) && claimDetails!.policyDeductible > 0) {
await test.step('PaymentMethodPage >> Execute Payment', async () => {
await paymentMethodPage.validateURL(paymentMethodPage.issPageValue);
await paymentMethodPage.executePayment(paymentDetails!);
await paymentMethodPage.executePayment(paymentDetails!, claimDetails!, servicePackage!);
await paymentMethodPage.nextPage();
});
} else {
await test.step('PaymentMethodPage >> Skip to Order Confirmation', async () => {
await paymentMethodPage.validateURL(paymentMethodPage.issPageValue);
await paymentMethodPage.nextPage();
if (isPolicyFound && (isUseVehicleOnPolicy ?? true) && claimDetails!.policyDeductible === 0) {
await paymentMethodPage.submitOrderWithoutPIA();
}
if (!isPolicyFound) {
await paymentMethodPage.submitOrderWithoutPIA();
}
if (isPolicyFound && testCase.testData.isUnverifiedPolicyAfterVehicleLookup === true) {
await paymentMethodPage.submitOrderWithoutPIA();
}
});
}

View file

@ -21,10 +21,10 @@ const essentialReplaceStaticAdasData: Partial<ITestData> = {
phoneNumber: faker.helpers.fromRegExp(/[2-9][0-9][0-9]-[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]/),
notes: 'Automated Test',
address: {
street: faker.location.streetAddress(),
city: 'Knoxville',
state: 'Tennessee',
postalCode: '37996',
street: "123 Test Road",
city: 'Columbus',
state: 'OHIO',
postalCode: '43085',
country: 'United States'
}
},

View file

@ -23,9 +23,9 @@ const essentialRepairInShopAcuraData: Partial<ITestData> = {
phoneNumber: faker.helpers.fromRegExp(/[2-9][0-9][0-9]-[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]/),
notes: 'Automated Test',
address: {
street: faker.location.streetAddress(),
street: '200 W. Oak Street',
city: 'Fort Collins',
state: 'Colorado',
state: 'COLORADO',
postalCode: '80526',
country: 'United States'
}

View file

@ -28,10 +28,10 @@ const essentialTpaEnabledData: Partial<ITestData> = {
notes: 'Automated Test',
address: {
// street: faker.location.streetAddress(),
street: '134 Woodlands Place',
city: 'Dublin',
state: 'Florida',
postalCode: '32040',
street: '1343 Cameron Ave',
city: 'Lewis Center',
state: 'OH',
postalCode: '43035',
country: 'United States'
}
},
@ -42,10 +42,10 @@ const essentialTpaEnabledData: Partial<ITestData> = {
damageCause: DamageType.Other
},
vehicleDetails: {
year: '2022',
year: '2015',
make: 'Honda',
model: 'Civic',
style: '4 door hatchback'
model: 'Accord',
style: '4 door sedan'
},
vehicleDamage: [
VehicleDamage.WindshieldThreeChips,

View file

@ -16,7 +16,6 @@ const essentialDoNotSeeShopData: Partial<ITestData> = {
partQuestions: undefined,
isSafelite: false,
bailoutFlags: {
isDoNotSeeMyShopBailout: true
},
servicePackage: faker.helpers.enumValue(ServicePackage),
customerDetails: {

View file

@ -23,7 +23,7 @@ const customerDetails: ICustomerDetails = {
}
}
const policyNumber = `~AutomatedScenario0001a${faker.string.uuid().substring(0, 6)}`;
const policyNumber = `~AutomatedScenario0001a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0001a', customerDetails, policyNumber);
const advancedScenario0001Data: Partial<ITestData> = {
@ -31,7 +31,6 @@ const advancedScenario0001Data: Partial<ITestData> = {
isDuplicateClaim: false,
isPolicyFound: true,
isNoComp: false,
hasStateLawPopup: true,
endorsements: undefined,
vehiclePartQuestions: [
@ -60,7 +59,7 @@ const advancedScenario0001Data: Partial<ITestData> = {
shopAddress: undefined,
appointmentDate: nextWeekday
},
paymentDetails: ClientData.getDefaultPaypalDetails()//ClientData.getDefaultCreditCardDetails()
paymentDetails: ClientData.getDefaultCreditCardDetails()
}
// TODO: Add validation for deductible/covered amount

View file

@ -29,7 +29,7 @@ if ((process.env.ENABLE_MOCK_TESTING ?? 'false') === 'true') {
customerDetails.lastName = 'Cormier';
customerDetails.address.street = '69825 W Pine Street';
} else {
policyNumber = `~AutomatedScenario0002a${faker.string.uuid().substring(0, 6)}`;
policyNumber = `~AutomatedScenario0002a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`;
};
const policySoap = MockPolicyData.getPolicySoapByScenario('0002a', customerDetails, policyNumber);

View file

@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = {
address: customerAddress
}
const policyNumber = `~AutomatedScenario0003a${faker.string.uuid().substring(0,6)}`;
const policyNumber = `~AutomatedScenario0003a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`;
const policySoap = MockPolicyData.getPolicySoapByScenario('0003a', customerDetails, policyNumber);
const advancedScenario0003Data: Partial<ITestData> = {
@ -33,6 +33,7 @@ const advancedScenario0003Data: Partial<ITestData> = {
isDuplicateClaim: false,
isPolicyFound: true,
isNoComp: false,
hasOemEndorsement: true,
hasStateLawPopup: false,
endorsements: undefined,
vehiclePartQuestions: [
@ -44,7 +45,7 @@ const advancedScenario0003Data: Partial<ITestData> = {
{
partQuestionType: PartQuestionType.PassengerRearColor,
isOnPage: true,
optionToSelect: 'Green Tint'
optionToSelect: 'Solar, Passenger side, Rear'
},
],
isSafelite: true,
@ -53,7 +54,7 @@ const advancedScenario0003Data: Partial<ITestData> = {
claimDetails: {
policyNumber: policyNumber,
policyDeductible: 50,
damageDate: '2016-01-02',
damageDate: '2026-01-02',
damageCause: faker.helpers.enumValue(DamageType)
},
policySoap: policySoap,

View file

@ -23,7 +23,7 @@ const customerDetails: ICustomerDetails = {
}
}
const policyNumber = `~AutomatedScenario0004a${faker.string.uuid().substring(0, 6)}`;
const policyNumber = `~AutomatedScenario0004a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0004a', customerDetails, policyNumber);
const advancedScenario0004aData: Partial<ITestData> = {
@ -33,8 +33,7 @@ const advancedScenario0004aData: Partial<ITestData> = {
isNoComp: false,
hasStateLawPopup: false,
endorsements: undefined,
vehiclePartQuestions: [
],
partQuestions: [],
isSafelite: true,
servicePackage: faker.helpers.enumValue(ServicePackage),
customerDetails: customerDetails,

View file

@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = {
address: customerAddress
}
const policyNumber = `~AutomatedScenario0005a${faker.string.uuid().substring(0,6)}`;
const policyNumber = `~AutomatedScenario0005a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0005a', customerDetails, policyNumber);
const advancedScenario0005Data: Partial<ITestData> = {

View file

@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = {
address: customerAddress
}
const policyNumber = `~AutomatedScenario0006a${faker.string.uuid().substring(0,6)}`;
const policyNumber = `~AutomatedScenario0006a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0006a', customerDetails, policyNumber);
const advancedScenario0006Data: Partial<ITestData> = {

View file

@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = {
address: customerAddress
}
const policyNumber = `~AutomatedScenario0007a${faker.string.uuid().substring(0,6)}`;
const policyNumber = `~AutomatedScenario0007a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0007a', customerDetails, policyNumber);
const advancedScenario0007Data: Partial<ITestData> = {

View file

@ -12,7 +12,7 @@ const nextWeekday = getNextWeekday();
const customerAddress: IAddress = {
street: faker.location.streetAddress(),
city: 'Hamden',
state: 'CT',
state: 'CONNECTICUT',
postalCode: '06517',
country: 'United States'
}
@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = {
address: customerAddress
}
const policyNumber = `~AutomatedScenario0008a${faker.string.uuid().substring(0,6)}`;
const policyNumber = `~AutomatedScenario0008a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0008a', customerDetails, policyNumber);
const advancedScenario0008Data: Partial<ITestData> = {
@ -33,7 +33,7 @@ const advancedScenario0008Data: Partial<ITestData> = {
isDuplicateClaim: false,
isPolicyFound: true,
isNoComp: false,
hasStateLawPopup: true,
hasStateLawPopup: false,
endorsements: undefined,
vehiclePartQuestions: [
// {

View file

@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = {
address: customerAddress
}
const policyNumber = `~AutomatedScenario0009a${faker.string.uuid().substring(0,6)}`;
const policyNumber = `~AutomatedScenario0009a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0009a', customerDetails, policyNumber);
const advancedScenario0009Data: Partial<ITestData> = {
@ -38,7 +38,7 @@ const advancedScenario0009Data: Partial<ITestData> = {
{
endorsementType: EndorsementType.Educator,
isOnPolicy: true,
isClickYes: false
isClickYes: true
}
],
vehiclePartQuestions: [

View file

@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = {
address: customerAddress
}
const policyNumber = `~AutomatedScenario0010a${faker.string.uuid().substring(0,6)}`;
const policyNumber = `~AutomatedScenario0010a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0010a', customerDetails, policyNumber);
const advancedScenario0010Data: Partial<ITestData> = {
@ -92,7 +92,7 @@ for (const client of advancedClients) {
const data = {...advancedScenario0010Data};
data.clientTag = client.clientTag;
const tc = new TestCase({
name: `0010a Advanced Repair No Deductible FL Rear Client: "${client.accountName}"`,
name: `0010a Advanced Replace No Deductible FL Rear Client: "${client.accountName}"`,
tags: [`@${client.clientTag}`, `@${client.accountName}`, '@Advanced'],
testData: data
}, undefined, '0010a');

View file

@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = {
address: customerAddress
}
const policyNumber = `~AutomatedScenario0011a${faker.string.uuid().substring(0, 6)}`;
const policyNumber = `~AutomatedScenario0011a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0011a', customerDetails, policyNumber);
const advancedScenario0011Data: Partial<ITestData> = {
@ -33,13 +33,13 @@ const advancedScenario0011Data: Partial<ITestData> = {
isDuplicateClaim: false,
isPolicyFound: true,
isNoComp: false,
isItac: true,
isItac: false,
hasStateLawPopup: false,
endorsements: [
{
endorsementType: EndorsementType.Educator,
isOnPolicy: true,
isClickYes: false
isClickYes: true
}
],
partQuestions: undefined,
@ -48,7 +48,7 @@ const advancedScenario0011Data: Partial<ITestData> = {
customerDetails: customerDetails,
claimDetails: {
policyNumber: policyNumber,
policyDeductible: 500,
policyDeductible: 0,
damageDate: '2023-03-01',
damageCause: faker.helpers.enumValue(DamageType)
},

View file

@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = {
address: customerAddress
}
const policyNumber = `~AutomatedScenario0012a${faker.string.uuid().substring(0,6)}`;
const policyNumber = `~AutomatedScenario0012a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0012a', customerDetails, policyNumber);
const advancedScenario0012Data: Partial<ITestData> = {
@ -48,7 +48,7 @@ const advancedScenario0012Data: Partial<ITestData> = {
customerDetails: customerDetails,
claimDetails: {
policyNumber: policyNumber,
policyDeductible: 500,
policyDeductible: 9999,
damageDate: '2023-09-01',
damageCause: faker.helpers.enumValue(DamageType)
},

View file

@ -1,6 +1,6 @@
import ClientData from "@business-logic/data/ClientData";
import TestCase from "@business-logic/types/TestCase";
import { DamageType, EndorsementType, ServiceLocation, ServicePackage, VehicleDamage } from "@business-logic/types/Enums";
import { DamageType, EndorsementType, PaymentType, ServiceLocation, ServicePackage, VehicleDamage } from "@business-logic/types/Enums";
import { ITestData } from "@business-logic/types/ITestData"
import { faker } from "@faker-js/faker";
import { getNextWeekday } from "@impl/utils/DateUtils";
@ -11,9 +11,9 @@ import { IAddress } from "@business-logic/types/IAddress";
const nextWeekday = getNextWeekday();
const customerAddress: IAddress = {
street: faker.location.streetAddress(),
city: 'Birmingham',
state: 'AL',
postalCode: '35118',
city: 'Worthington',
state: 'OH',
postalCode: '43085',
country: 'United States'
}
const customerDetails: ICustomerDetails = {
@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = {
address: customerAddress
}
const policyNumber = `~AutomatedScenario0013a${faker.string.uuid().substring(0, 6)}`;
const policyNumber = `~AutomatedScenario0013a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0013a', customerDetails, policyNumber);
const advancedScenario0013Data: Partial<ITestData> = {
@ -33,13 +33,13 @@ const advancedScenario0013Data: Partial<ITestData> = {
isDuplicateClaim: false,
isPolicyFound: true,
isNoComp: false,
isItac: true,
isItac: false,
hasStateLawPopup: false,
endorsements: [
{
endorsementType: EndorsementType.Educator,
isOnPolicy: true,
isClickYes: false
isClickYes: true
}
],
vehiclePartQuestions: [
@ -49,7 +49,7 @@ const advancedScenario0013Data: Partial<ITestData> = {
customerDetails: customerDetails,
claimDetails: {
policyNumber: policyNumber,
policyDeductible: 9999,
policyDeductible: 0,
damageDate: '2023-08-01',
damageCause: faker.helpers.enumValue(DamageType)
},
@ -68,7 +68,9 @@ const advancedScenario0013Data: Partial<ITestData> = {
serviceAddress: customerAddress,
appointmentDate: nextWeekday
},
paymentDetails: ClientData.getDefaultCreditCardDetails()
paymentDetails: {
paymentType: PaymentType.PayAtService
}
}
@ -80,7 +82,7 @@ for (const client of advancedClients) {
const data = { ...advancedScenario0013Data };
data.clientTag = client.clientTag;
const tc = new TestCase({
name: `0013a Advanced ITAC Mobile Client: "${client.accountName}"`,
name: `0013a Advanced $0 Deductible with Vaps Mobile Client: "${client.accountName}"`,
tags: [`@${client.clientTag}`, `@${client.accountName}`, '@Advanced'],
testData: data
}, undefined, '0013a');

View file

@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = {
address: customerAddress
}
const policyNumber = `~AutomatedScenario0014a${faker.string.uuid().substring(0,6)}`;
const policyNumber = `~AutomatedScenario0014a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0014a', customerDetails, policyNumber);
const advancedScenario0014Data: Partial<ITestData> = {
@ -33,13 +33,13 @@ const advancedScenario0014Data: Partial<ITestData> = {
isDuplicateClaim: false,
isPolicyFound: true,
isNoComp: true,
isRecalWarning: true,
isRecalWarning: false,
hasStateLawPopup: false,
endorsements: [
{
endorsementType: EndorsementType.Educator,
isOnPolicy: true,
isClickYes: true
isClickYes: false
}
],
partQuestions: undefined,

View file

@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = {
address: customerAddress
}
const policyNumber = `~AutomatedScenario0015a${faker.string.uuid().substring(0,6)}`;
const policyNumber = `~AutomatedScenario0015a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0015a', customerDetails, policyNumber);
const advancedScenario0015Data: Partial<ITestData> = {
@ -46,16 +46,16 @@ const advancedScenario0015Data: Partial<ITestData> = {
{
partQuestionType: PartQuestionType.WindshieldColor,
isOnPage: true,
optionToSelect: 'Green Tint, Blue Shade'
optionToSelect: 'Heated glass, Heated glass Wiper Park'
},
{
partQuestionType: PartQuestionType.DriverFrontColor,
isOnPage: true,
optionToSelect: 'Green Tint'
optionToSelect: 'Driver side, Front'
}
],
isSafelite: true,
servicePackage: faker.helpers.enumValue(ServicePackage),
servicePackage: ServicePackage.Premium,
customerDetails: customerDetails,
claimDetails: {
policyNumber: policyNumber,

View file

@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = {
address: customerAddress
}
const policyNumber = `~AutomatedScenario0016a${faker.string.uuid().substring(0,6)}`;
const policyNumber = `~AutomatedScenario0016a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0016a', customerDetails, policyNumber);
const advancedScenario0016Data: Partial<ITestData> = {
@ -39,38 +39,37 @@ const advancedScenario0016Data: Partial<ITestData> = {
{
partQuestionType: PartQuestionType.WindshieldColor,
isOnPage: true,
optionToSelect: 'Green Tint'
optionToSelect: 'Solar, Third Visor Frit, soundproofing, Infrared Interlayer'
},
{
partQuestionType: PartQuestionType.DriverFrontColor,
isOnPage: true,
optionToSelect: 'Green Tint'
optionToSelect: 'Solar, Driver side, Front'
},
{
partQuestionType: PartQuestionType.DriverRearColor,
isOnPage: true,
optionToSelect: 'Green Tint'
optionToSelect: 'Solar, Driver side, Rear'
},
{
partQuestionType: PartQuestionType.DriverVentColor,
isOnPage: true,
optionToSelect: 'Green Tint',
secondaryQuestionOptionToSelect: 'solar, driver side, rear'
optionToSelect: 'Solar, Driver side, Rear'
},
{
partQuestionType: PartQuestionType.PassengerFrontColor,
isOnPage: true,
optionToSelect: 'Green Tint'
optionToSelect: 'Solar, Passenger side, Front'
},
{
partQuestionType: PartQuestionType.PassengerRearColor,
isOnPage: true,
optionToSelect: 'Green Tint'
optionToSelect: 'Solar, Passenger side, Rear'
},
{
partQuestionType: PartQuestionType.RearWindowColor,
isOnPage: true,
optionToSelect: 'Green Tint'
optionToSelect: 'Heated glass, Solar'
},
],
isSafelite: true,
@ -100,8 +99,8 @@ const advancedScenario0016Data: Partial<ITestData> = {
// VehicleDamage.PassengerVentGlass // TODO: Verify if this is intentionally not in UI
],
appointmentDetails: {
serviceLocation: ServiceLocation.Mobile,
serviceAddress: customerAddress,
serviceLocation: ServiceLocation.InShop,
serviceAddress: undefined,
appointmentDate: nextWeekday
},
paymentDetails: ClientData.getDefaultPaypalDetails()

View file

@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = {
address: customerAddress
}
const policyNumber = `~AutomatedScenario0017a${faker.string.uuid().substring(0,6)}`;
const policyNumber = `~AutomatedScenario0017a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0017a', customerDetails, policyNumber);
const advancedScenario0017Data: Partial<ITestData> = {

View file

@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = {
address: customerAddress
}
const policyNumber = `~AutomatedScenario0018a${faker.string.uuid().substring(0,6)}`;
const policyNumber = `~AutomatedScenario0018a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0018a', customerDetails, policyNumber);
const advancedScenario0018Data: Partial<ITestData> = {

View file

@ -31,7 +31,7 @@ const vehicleDetails: IVehicleDetails = {
style: '4 door sedan'
};
const policyNumber = `~AutomatedScenario0019a${faker.string.uuid().substring(0,6)}`;
const policyNumber = `~AutomatedScenario0019a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0019a', customerDetails, policyNumber);
const advancedScenario0019Data: Partial<ITestData> = {

View file

@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = {
address: customerAddress
}
const policyNumber = `~AutomatedScenario0020a${faker.string.uuid().substring(0,6)}`;
const policyNumber = `~AutomatedScenario0020a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0020a', customerDetails, policyNumber);
const advancedScenario0020Data: Partial<ITestData> = {
@ -60,12 +60,14 @@ const advancedScenario0020Data: Partial<ITestData> = {
serviceLocation: ServiceLocation.Mobile,
serviceAddress: {
street: faker.location.streetAddress(),
city: 'Goose Creek',
state: 'SC',
postalCode: '29445',
city: 'Worthington',
state: 'OH',
postalCode: '43085',
country: 'US'
},
alternateServiceZip: '29445',
alternateServiceZip: '43085',
alternateMobileCity: 'Worthington',
alternateMobileStreetAddress: '177 Franklin Ave',
appointmentDate: nextWeekday
},
paymentDetails: {

View file

@ -23,13 +23,14 @@ const customerDetails: ICustomerDetails = {
}
}
const policyNumber = `~AutomatedScenario0021a${faker.string.uuid().substring(0, 6)}`;
const policyNumber = `~AutomatedScenario0021a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0021a', customerDetails, policyNumber);
const advancedScenario0021Data: Partial<ITestData> = {
clientTag: '',
isDuplicateClaim: false,
isPolicyFound: true,
isUnverifiedPolicyAfterVehicleLookup: true,
isNoComp: false,
hasStateLawPopup: true,
endorsements: undefined,
@ -54,8 +55,8 @@ const advancedScenario0021Data: Partial<ITestData> = {
model: 'RX 300',
style: undefined,
vehicleLookupType: VehicleLookupType.LicensePlateNumber,
licensePlateNumber: ['NoPlate753', 'BQ40903','1111'],
licensePlateState: ['Illinois','Illinois','California'],
licensePlateNumber: ['NoPlate753', 'GAH2307','1111'],
licensePlateState: ['Illinois','Ohio','California'],
},
vehicleDamage: [
VehicleDamage.WindshieldCrack,

View file

@ -23,13 +23,14 @@ const customerDetails: ICustomerDetails = {
}
}
const policyNumber = `~AutomatedScenario0022a${faker.string.uuid().substring(0, 6)}`;
const policyNumber = `~AutomatedScenario0022a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0022a', customerDetails, policyNumber);
const advancedScenario0022Data: Partial<ITestData> = {
clientTag: '',
isDuplicateClaim: false,
isPolicyFound: true,
isUnverifiedPolicyAfterVehicleLookup: true,
isNoComp: false,
hasStateLawPopup: true,
endorsements: undefined,

View file

@ -23,13 +23,14 @@ const customerDetails: ICustomerDetails = {
}
}
const policyNumber = `~AutomatedScenario0023a${faker.string.uuid().substring(0, 6)}`;
const policyNumber = `~AutomatedScenario0023a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0023a', customerDetails, policyNumber);
const advancedScenario0023Data: Partial<ITestData> = {
clientTag: '',
isDuplicateClaim: false,
isPolicyFound: true,
isUnverifiedPolicyAfterVehicleLookup: true,
isNoComp: false,
hasStateLawPopup: true,
endorsements: undefined,

View file

@ -23,7 +23,7 @@ const customerDetails: ICustomerDetails = {
}
}
const policyNumber = `~AutomatedScenario0024a${faker.string.uuid().substring(0, 6)}`;
const policyNumber = `~AutomatedScenario0024a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0024a', customerDetails, policyNumber);
const advancedScenario0024Data: Partial<ITestData> = {
@ -50,14 +50,14 @@ const advancedScenario0024Data: Partial<ITestData> = {
policySoap: policySoap,
vehicleDetails: {
year: '2015',
make: 'Ford',
model: 'F Series F150',
style: '2 door super cab',
make: 'Audi',
model: 'A7',
style: '4 door hatchback',
vehicleLookupType: VehicleLookupType.Address,
address: [
{ street: '9 Test Street', city: 'Columbus', state: 'OH', postalCode: '43220', lastName: 'Test' },
{ street: '8621 GREENLEAF AVE', city: 'Whittier', state: 'California', postalCode: '90602', lastName: 'Johnson' },
{ street: '10212 JEWEL CT', city: 'Conroe', state: 'Texas', postalCode: '77385', lastName: 'Reed' }
{ street: '6531 CANYON RANCH', city: 'Frisco', state: 'Texas', postalCode: '75036', lastName: 'TRAINOR' }
],
},
vehicleDamage: [

View file

@ -15,7 +15,7 @@ const customerDetails: ICustomerDetails = {
phoneNumber: '614-531-0031',
notes: 'Automated Test',
address: {
street: faker.location.streetAddress(),
street: '5273 Berrywood Dr',
city: 'Columbus',
state: 'OH',
postalCode: '43220',
@ -23,7 +23,7 @@ const customerDetails: ICustomerDetails = {
}
}
const policyNumber = `~AutomatedScenario0025a${faker.string.uuid().substring(0, 6)}`;
const policyNumber = `~AutomatedScenario0025a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0025a', customerDetails, policyNumber);
const advancedScenario0025Data: Partial<ITestData> = {

View file

@ -23,7 +23,7 @@ const customerDetails: ICustomerDetails = {
}
}
const policyNumber = `~AutomatedScenario0026a${faker.string.uuid().substring(0, 6)}`;
const policyNumber = `~AutomatedScenario0026a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0026a', customerDetails, policyNumber);
const advancedScenario0026aData: Partial<ITestData> = {
@ -35,7 +35,6 @@ const advancedScenario0026aData: Partial<ITestData> = {
isRecalNotification: true,
endorsements: undefined,
bailoutFlags: {
isDoNotSeeMyShopBailout: true
},
vehiclePartQuestions: [
],

View file

@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = {
address: customerAddress
}
const policyNumber = `~AutomatedScenario0028a${faker.string.uuid().substring(0, 6)}`;
const policyNumber = `~AutomatedScenario0028a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0028a', customerDetails, policyNumber);
const advancedScenario0028aData: Partial<ITestData> = {
@ -36,7 +36,7 @@ const advancedScenario0028aData: Partial<ITestData> = {
isItac: true,
hasStateLawPopup: false,
bailoutFlags: {
isRequestCallbackBailout: true,
isCoverageCancelledByUser: true,
},
isVehicleSelectBailout: false,
endorsements: [

View file

@ -23,21 +23,27 @@ const customerDetails: ICustomerDetails = {
}
}
const policyNumber = `~AutomatedScenario0029a${faker.string.uuid().substring(0, 6)}`;
const policyNumber = `~AutomatedScenario0029a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0029a', customerDetails, policyNumber);
const advancedScenario0029Data: Partial<ITestData> = {
clientTag: '',
isDuplicateClaim: false,
isPolicyFound: true,
isUnverifiedPolicyAfterVehicleLookup: true,
hasStateLawPopup: true,
isNoComp: false,
endorsements: undefined,
vehiclePartQuestions: [
{
partQuestionType: PartQuestionType.WindshieldColor,
isOnPage: true,
optionToSelect: 'Heated glass, Encap, Asymmetrically Strengthen'
},
],
isUseVehicleOnPolicy: false,
servicePackage: faker.helpers.enumValue(ServicePackage),
customerDetails: customerDetails,
bailoutFlags: {
isHeavyTruckVehicleBailout: true,
},
claimDetails: {
policyNumber: policyNumber,
policyDeductible: 500,
@ -50,7 +56,7 @@ const advancedScenario0029Data: Partial<ITestData> = {
make: 'Freightliner',
model: '114sd',
vehicleLookupType: VehicleLookupType.Vin,
vin: '1FUJG3DV5HHJH2985',
vin: '1FVMG3DV5HHJA1388',
},
vehicleDamage: [
VehicleDamage.WindshieldCrack,

View file

@ -10,10 +10,10 @@ import { IAddress } from "@business-logic/types/IAddress";
const nextWeekday = getNextWeekday();
const customerAddress: IAddress = {
street: faker.location.streetAddress(),
city: 'Birmingham',
state: 'AL',
postalCode: '35118',
street: '474 1st St',
city: 'Lindsay',
state: 'CA',
postalCode: '93247',
country: 'United States'
}
const customerDetails: ICustomerDetails = {
@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = {
address: customerAddress
}
const policyNumber = `~AutomatedScenario0030a${faker.string.uuid().substring(0, 6)}`;
const policyNumber = `~AutomatedScenario0030a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0030a', customerDetails, policyNumber);
const advancedScenario0030aData: Partial<ITestData> = {
@ -36,7 +36,7 @@ const advancedScenario0030aData: Partial<ITestData> = {
isItac: false,
hasStateLawPopup: false,
bailoutFlags: {
isPartsServiceErrorBailout: true,
isAPIErrorBailout: true,
},
vehiclePartQuestions: [
@ -79,7 +79,7 @@ for (const client of advancedClients) {
const data = { ...advancedScenario0030aData };
data.clientTag = client.clientTag;
const tc = new TestCase({
name: `0030a Advanced ITAC Cancel My Claim: "${client.accountName}"`,
name: `0030a Advanced API Error Bailout: "${client.accountName}"`,
tags: [`@${client.clientTag}`, `@${client.accountName}`, '@Advanced', '@INSR-2057'],
testData: data
}, undefined, '0030a');

View file

@ -23,7 +23,7 @@ const customerDetails: ICustomerDetails = {
}
}
const policyNumber = `~AutomatedScenario0031a${faker.string.uuid().substring(0, 6)}`;
const policyNumber = `~AutomatedScenario0031a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0031a', customerDetails, policyNumber);
const advancedScenario0031Data: Partial<ITestData> = {
@ -33,19 +33,11 @@ const advancedScenario0031Data: Partial<ITestData> = {
isNoComp: false,
hasStateLawPopup: false,
endorsements: undefined,
partQuestions: [
{
partQuestionType: PartQuestionType.LaneKeepAssist,
isOnPage: true,
optionToSelect: 'Yes',
}
],
vehiclePartQuestions: [
{
partQuestionType: PartQuestionType.WindshieldColor,
isOnPage: true,
optionToSelect: 'Green Tint',
secondaryQuestionOptionToSelect: 'one-piece, w/lane departure warning system',
optionToSelect: 'One-Piece, With Lane Departure Warning System',
},
],
isSafelite: true,

View file

@ -23,7 +23,7 @@ const customerDetails: ICustomerDetails = {
}
}
const policyNumber = `~AutomatedScenario0032a${faker.string.uuid().substring(0, 6)}`;
const policyNumber = `~AutomatedScenario0032a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error
const policySoap = MockPolicyData.getPolicySoapByScenario('0032a', customerDetails, policyNumber);
const advancedScenario0032Data: Partial<ITestData> = {
@ -33,6 +33,9 @@ const advancedScenario0032Data: Partial<ITestData> = {
isNoComp: false,
hasStateLawPopup: false,
endorsements: undefined,
bailoutFlags: {
isHeavyTruckVehicleBailout: true,
},
vehiclePartQuestions: [
],

View file

@ -17,7 +17,6 @@
"coverage/api/v1/coverage/register-claim": "0002a_Advanced_Replace_Deductible_Client/coverage/api/v1/coverage/register-claim.json",
"price/api/v1/price/order-items-with-itac-pricing": "0002a_Advanced_Replace_Deductible_Client/price/api/v1/price/order-items-with-itac-pricing.json",
"location/api/v1/location/providers/75023/Replace/100/550036/true/CR00056707/DW01571GTNNOEM": "0002a_Advanced_Replace_Deductible_Client/location/api/v1/location/providers/75023/Replace/100/550036/true/CR00056707/DW01571GTNNOEM.json",
"schedule/api/v1/schedule/shop-time-slots": "0002a_Advanced_Replace_Deductible_Client/schedule/api/v1/schedule/shop-time-slots.json",
"location/api/v1/location/alert-reasons/01813": "0002a_Advanced_Replace_Deductible_Client/location/api/v1/location/alert-reasons/01813.json",
"parts/api/v1/parts/rain-repel": "0002a_Advanced_Replace_Deductible_Client/parts/api/v1/parts/rain-repel.json",
"price/api/v1/price/combined-quote": "0002a_Advanced_Replace_Deductible_Client/price/api/v1/price/combined-quote.json"