Merge branch 'develop' into feature/kroell/INSR-8907

# Conflicts:
#	src/layouts/schedule-page/schedule-page.vue
This commit is contained in:
Katie Kroell 2026-03-30 10:42:12 -04:00
commit a9b6e91dc3
84 changed files with 1624 additions and 698 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

@ -272,6 +272,8 @@ stages:
__VUE_APP_CUSTOMER_PORTAL_URL__: $(__VUE_APP_CUSTOMER_PORTAL_URL__)
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
__VUE_APP_SAFELITE_HOP__: $(__VUE_APP_SAFELITE_HOP__)
__VUE_APP_ADYEN_ENVIRONMENT__: $(__VUE_APP_ADYEN_ENVIRONMENT__)
__VUE_APP_ADYEN_CLIENT_KEY__: $(__VUE_APP_ADYEN_CLIENT_KEY__)
indexDeployVariables:
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
@ -335,6 +337,8 @@ stages:
__VUE_APP_CUSTOMER_PORTAL_URL__: $(__VUE_APP_CUSTOMER_PORTAL_URL__)
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
__VUE_APP_SAFELITE_HOP__: $(__VUE_APP_SAFELITE_HOP__)
__VUE_APP_ADYEN_ENVIRONMENT__: $(__VUE_APP_ADYEN_ENVIRONMENT__)
__VUE_APP_ADYEN_CLIENT_KEY__: $(__VUE_APP_ADYEN_CLIENT_KEY__)
indexDeployVariables:
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
@ -398,6 +402,8 @@ stages:
__VUE_APP_CUSTOMER_PORTAL_URL__: $(__VUE_APP_CUSTOMER_PORTAL_URL__)
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
__VUE_APP_SAFELITE_HOP__: $(__VUE_APP_SAFELITE_HOP__)
__VUE_APP_ADYEN_ENVIRONMENT__: $(__VUE_APP_ADYEN_ENVIRONMENT__)
__VUE_APP_ADYEN_CLIENT_KEY__: $(__VUE_APP_ADYEN_CLIENT_KEY__)
indexDeployVariables:
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
@ -461,6 +467,8 @@ stages:
__VUE_APP_CUSTOMER_PORTAL_URL__: $(__VUE_APP_CUSTOMER_PORTAL_URL__)
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
__VUE_APP_SAFELITE_HOP__: $(__VUE_APP_SAFELITE_HOP__)
__VUE_APP_ADYEN_ENVIRONMENT__: $(__VUE_APP_ADYEN_ENVIRONMENT__)
__VUE_APP_ADYEN_CLIENT_KEY__: $(__VUE_APP_ADYEN_CLIENT_KEY__)
indexDeployVariables:
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)

34
package-lock.json generated
View file

@ -3925,9 +3925,9 @@
}
},
"node_modules/@stylistic/eslint-plugin/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {
@ -8048,7 +8048,9 @@
}
},
"node_modules/cosmiconfig/node_modules/yaml": {
"version": "1.10.2",
"version": "1.10.3",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz",
"integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==",
"dev": true,
"license": "ISC",
"engines": {
@ -8341,7 +8343,9 @@
}
},
"node_modules/cssnano/node_modules/yaml": {
"version": "1.10.2",
"version": "1.10.3",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz",
"integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==",
"dev": true,
"license": "ISC",
"engines": {
@ -16002,7 +16006,9 @@
}
},
"node_modules/node-forge": {
"version": "1.3.2",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz",
"integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==",
"dev": true,
"license": "(BSD-3-Clause OR GPL-2.0)",
"engines": {
@ -16769,7 +16775,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
"version": "2.3.1",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"dev": true,
"license": "MIT",
"engines": {
@ -19734,7 +19742,9 @@
}
},
"node_modules/tinyglobby/node_modules/picomatch": {
"version": "4.0.3",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {
@ -20483,7 +20493,9 @@
}
},
"node_modules/vite/node_modules/picomatch": {
"version": "4.0.3",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {
@ -20565,7 +20577,9 @@
}
},
"node_modules/vitest/node_modules/picomatch": {
"version": "4.0.3",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {

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"

View file

@ -11,22 +11,29 @@ const GaEvents = Object.freeze({
const GaCategories = Object.freeze({
API_RESPONSE: 'Api_Response',
CONFIRMATION_CLICKED: "confirmation clicked",
CONFIRMATION_CLICKED_INSHOP: "inshop confirmation clicked",
CONFIRMATION_CLICKED_MOBILE: "mobile confirmation clicked",
EVOX: 'Evox'
});
const GaActions = Object.freeze({
RESULT: 'Result',
CLICKED: 'Clicked',
NO: "no",
RESULT: 'Result',
SUBMITTED: 'Submitted',
VIF: 'vif',
SUBMITTED: 'Submitted'
YES: "yes",
});
const GaLabels = Object.freeze({
SUCCESS: 'Success',
ADDRESS_LOOKUP: 'Address_Look_up',
ERROR: 'Error',
LICENSE_PLATE_LOOKUP: 'License_Plate_Look_Up',
NO: "no",
SUCCESS: 'Success',
VIN_LOOKUP: 'Vin_Look_Up',
ADDRESS_LOOKUP: 'Address_Look_up'
YES: "yes",
});
const ValueToLogTypes = Object.freeze({

View file

@ -2,6 +2,7 @@ const experimentUniverses = Object.freeze({
ISS_FUNNEL: 'ISSFunnel',
ISS_FEATURETOGGLE_AREFEES_HIDDEN: 'NextGenISS_FeatureToggle_AreFeesHidden',
ISS_FEATURETOGGLE_AREFEES_OVERRIDDEN: 'NextGenISS_FeatureToggle_AreFeesOverridden',
ISS_MOBILE_FIRST_APPOINTMENTS: 'ISS_Mobile_First_Appointments',
ADYEN_PAYMENT_TEST: 'NextGenAdyenPaymentTest'
});
@ -12,6 +13,9 @@ const experimentSettings = Object.freeze({
ISS_FEATURE_TOGGLE_IS_MOBILE_FEE_OVERRIDDEN: 'OverrideMobileFee',
ISS_FEATURE_TOGGLE_IS_RECYCLE_FEE_HIDDEN: 'HideRecycleFee',
ISS_FEATURE_TOGGLE_IS_RECYCLE_FEE_OVERRIDDEN: 'OverrideRecycleFee',
ISS_MOBILE_FIRST_MAX_MOBILE_DAYS: 'MaxMobileDays',
ISS_MOBILE_FIRST_MAX_PM_MOBILE_DAYS: 'MaxPmMobileDays',
ISS_MOBILE_FIRST_SHOW_FIRST_MOBILE_APPOINTMENT: 'ShowMobileFirstAppointment',
ISS_ENABLE_ADYEN_V1: 'ISS_Enable_Adyen_V1'
});

View file

@ -69,6 +69,7 @@ export function mapAdyenToIssPaymentMethod(adyenMethod) {
const paymentMethodMap = {
["scheme"]: paymentMethods.CREDIT_CARD,
["afterpaytouch_US"]: paymentMethods.AFTERPAY,
["afterpaytouch"]: paymentMethods.AFTERPAY,
["paypal"]: paymentMethods.PAYPAL,
["applepay"]: paymentMethods.APPLEPAY,
};

View file

@ -15,8 +15,23 @@ function processZipCodeResults(request) {
}));
}
export async function getZipCodeData(zipCode) {
return await processZipCodeResults(useMainStore().validateZip({ zip: zipCode }));
export async function getAvailabilityRating(
startDate,
endDate,
shopAppointmentType,
providerNumber
) {
// For a given shop provider number and date range, get the appointment time slots available
const shopTimeSlots = await useMainStore().getShopTimeSlots(startDate, endDate, shopAppointmentType, providerNumber);
const numberOfDaysToEvaluate = 2;
const isGoodAvailability =
shopTimeSlots.data.days.filter((x) => x.timeSlots.length > 0).length
>= numberOfDaysToEvaluate;
const shopStatus = isGoodAvailability ? 'high' : 'low';
return Promise.resolve(shopStatus);
}
export async function getMobileZipCodeData(zipCode) {
@ -42,25 +57,6 @@ export async function getPricedMobileFeePart(serviceZipCode) {
return Promise.resolve(pricingResults[0]);
}
export async function getAvailabilityRating(
startDate,
endDate,
shopAppointmentType,
providerNumber
) {
// For a given shop provider number and date range, get the appointment time slots available
const shopTimeSlots = await useMainStore().getShopTimeSlots(startDate, endDate, shopAppointmentType, providerNumber);
const numberOfDaysToEvaluate = 2;
const isGoodAvailability =
shopTimeSlots.data.days.filter((x) => x.timeSlots.length > 0).length
>= numberOfDaysToEvaluate;
const shopStatus = isGoodAvailability ? 'high' : 'low';
return Promise.resolve(shopStatus);
}
export function getProviderData() {
const mainStore = useMainStore();
const storeServiceLocation = mainStore.order.serviceLocation;
@ -73,6 +69,18 @@ export function getProviderData() {
return { storeServiceLocation, isMobileAppointment, storeSelectedProvider, serviceZipCode }
}
export async function getZipCodeData(zipCode) {
return await processZipCodeResults(useMainStore().validateZip({ zip: zipCode }));
}
export function mapTimeSlot(timeSlot, timeOfDay) {
return {
...timeSlot,
isDropoff: timeSlot.id.endsWith('DROP OFF'),
timeOfDay
};
}
export async function onProviderChanged() {
const { isMobileAppointment, storeSelectedProvider, serviceZipCode } = getProviderData();
if (!storeSelectedProvider) {
@ -101,4 +109,4 @@ export async function onProviderChanged() {
}
await settleAllPromises(promiseResultMap);
}
}

View file

@ -68,6 +68,7 @@ export default {
},
methods: {
openModal() {
this.pushEventToGA("existing_claim", "pop_up_displayed", this.mainStore.accountNameForEvents, true);
this.$refs.continueModal.openModal();
},
setModalStatus(isOpened) {
@ -77,9 +78,11 @@ export default {
this.$refs.continueModal.closeModal();
},
continueReferral() {
this.pushEventToGA("existing_claim", "finish_claim", this.mainStore.accountNameForEvents, true);
this.$emit('continue-previous-referral');
},
startNewReferral() {
this.pushEventToGA("existing_claim", "start_new_claim", this.mainStore.accountNameForEvents, true);
this.$emit('start-new-referral');
}
}

View file

@ -230,11 +230,15 @@ export default {
},
watch: {
isSameAsPolicyAddress(newValue) {
this.pushEventToGA("mobile_location", "same_as_policy_address", newValue ? "checked" : "unchecked", true);
if (newValue) {
this.copyPolicyAddressToServiceLocation();
} else {
this.clearAddressFields();
}
},
isVehicleProtected(newValue) {
this.pushEventToGA("mobile_location", "is_vehicle_protected", newValue, true);
}
},
methods: {

View file

@ -73,6 +73,10 @@
</div>
</div>
</div>
<suggestTimeslotModal
:ref="SUGGEST_TIMESLOT_MODAL_REF_NAME"
cmsWidgetName="SuggestTimeslotModal"
@confirmAppointmentClicked="autoSelectTimeslotConfirmed" />
<recalAckModal
ref="recalAckModal"
cmsWidgetName="RecalAckModalWidget"
@ -88,10 +92,12 @@ import locationAlerts from '@/layouts/schedule-page/location-alerts/location-ale
import datePicker from '@/digital-components/date-picker/date-picker.vue';
import serviceLocation from '@/layouts/schedule-page/service-location/service-location.vue';
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
import suggestTimeslotModal from '@/layouts/schedule-page/suggest-timeslot-modal/suggest-timeslot-modal.vue';
import modal from '@/digital-components/modal/modal.vue';
import recalAckModal from '@/layouts/schedule-page/recal-ack-modal/recal-ack-modal.vue';
// Supporting files
import { experimentSettings, experimentUniverses } from '@/constants/experiments';
import {
AppointmentTypeStrings,
GET_MOBILE_TIME_SLOTS,
@ -111,7 +117,9 @@ import settleAllPromises from '@/helpers/layout-helper';
import showIssLoadingModal from '@/helpers/loading-modal-helper.js';
import {
getPricedMobileFeePart,
getZipCodeData
getMobileZipCodeData,
getZipCodeData,
mapTimeSlot
} from '@/helpers/service-location-helper';
import { Form } from 'vee-validate';
import BaseFormMixin from '@/mixins/base-form-mixin.js';
@ -120,6 +128,7 @@ import { containsRecalParts, anyPartWithRequiresRecalFlag } from '@/helpers/reca
import errorMessages from '@/constants/error-messages';
// Define constants
const SUGGEST_TIMESLOT_MODAL_REF_NAME = 'SuggestTimeslotModal';
const TIME_SLOTS_CALL_DAYS_LIMIT = 15;
const getAvailableDates = async (
@ -241,6 +250,7 @@ export default {
datePicker,
serviceLocation,
siteFooter,
suggestTimeslotModal,
Form,
recalAckModal
},
@ -346,9 +356,16 @@ export default {
zipCodeCtu: resultMap.zipCodeData?.zipCodeCtu
};
const mobileFirstObject = {
mobileProviderNumber: resultMap.providers?.mobileProviderNumber,
serviceabilityDetails: resultMap.serviceabilityDetails,
zipCode: initialServiceLocationObj.zipCode
}
vm.setCmsContent(resultMap.cmsContent);
vm.$refs.serviceLocation.initializeComponent(serviceLocationData);
await vm.setData(resultMap.premiumFeeWithPrice, initialServiceLocationObj);
await vm.checkMobileFirstExperience(mobileFirstObject);
showIssLoadingModal(false);
});
},
@ -375,6 +392,7 @@ export default {
selectedServiceLocationCity: this.getServiceLocationCity(),
selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(),
showDatePickerError: false,
SUGGEST_TIMESLOT_MODAL_REF_NAME,
isAcknowledged: false
};
},
@ -471,6 +489,185 @@ export default {
arePagePrerequisitesValid() {
return useMainStore().order.serviceLocation.zipCode !== null;
},
async autoSelectTimeslotConfirmed(emittedValue) {
if (this.mainStore.isNoComp || this.mainStore.isITAC) {
console.log('User is No Comp or ITAC, skipping mobile fee update on auto select timeslot confirmed');
// this.mainStore.updateMobileFee(this.mobileFeePart);
}
const providerToUse = {
providerNumber: emittedValue.mobileProviderNumber,
address: {
streetAddress: null,
city: null,
state: null,
zipCode: null,
zipCodeCtu: null
}
};
this.mainStore.updateServiceLocation({
state: emittedValue.zipCodeData.state,
zipCode: emittedValue.zipCodeData.zipCode,
zipCodeCtu: emittedValue.zipCodeData.zipCodeCtu,
appointmentType: AppointmentTypeStrings.MOBILE,
provider: providerToUse
});
const timeSlotToUse = {
date: emittedValue.date,
routeCode: emittedValue.timeSlot.id,
startTime: emittedValue.timeSlot.startTime,
endTime: emittedValue.timeSlot.endTime,
jobMaxMinutes: emittedValue.estimatedServiceMinutesMax.toString(),
jobMinMinutes: emittedValue.estimatedServiceMinutesMin.toString(),
};
this.mainStore.saveSchedule(timeSlotToUse);
showIssLoadingModal(true);
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.$route
);
},
async checkMobileFirstExperience(mobileFirstObject = {}) {
const showMobileFirst = this.getSettingValue(experimentSettings.ISS_MOBILE_FIRST_SHOW_FIRST_MOBILE_APPOINTMENT);
if (showMobileFirst) {
const pmMobileDaysLimit = this.getSettingValue(experimentSettings.ISS_MOBILE_FIRST_MAX_PM_MOBILE_DAYS);
const mobileDaysLimit = this.getSettingValue(experimentSettings.ISS_MOBILE_FIRST_MAX_MOBILE_DAYS);
const hasSelectedDate = !!this.getSelectedDate();
const isServicableMobile = mobileFirstObject.serviceabilityDetails?.isGlassServiceableMobile;
if (!hasSelectedDate && isServicableMobile && mobileFirstObject.mobileProviderNumber && mobileFirstObject.zipCode) {
const todayDateObject = new Date();
const todayDateString = convertDateToDateString(todayDateObject);
const calendarViewDirection = 'future';
const initialDays = 15;
const initialViewStartDate = todayDateString;
const initialEndDate = new Date();
initialEndDate.setDate(initialEndDate.getDate() + (initialDays - 1));
const initialViewEndDate = convertDateToDateString(initialEndDate);
const preSelectedDate = this.selectedDate;
const initialData = await getAvailableDates(
initialViewStartDate,
initialViewEndDate,
AppointmentTypeStrings.MOBILE,
mobileFirstObject.mobileProviderNumber,
mobileFirstObject.zipCode
).then((response) => ({
todayDate: todayDateString,
initialViewStartDate,
initialViewEndDate,
calendarViewDirection,
initialShopTimeSlotsResponse: response,
preSelectedDate,
initialDaysLoaded: initialDays,
daysFromStart: null
}))
.catch(() => {
return {
todayDate: todayDateString,
initialViewStartDate,
initialViewEndDate,
calendarViewDirection,
initialShopTimeSlotsResponse: { days: [] },
preSelectedDate,
initialDaysLoaded: initialDays,
daysFromStart: null
};
});
this.mobileDatesData = initialData;
const estimatedServiceMinutesMax = initialData.initialShopTimeSlotsResponse?.estimatedServiceMinutesMaximum || 120;
const estimatedServiceMinutesMin = initialData.initialShopTimeSlotsResponse?.estimatedServiceMinutesMinimum || 90;
const retTimeSlotsResponse = [];
initialData.initialShopTimeSlotsResponse.days.forEach((selectableDate) => {
const dateObjectToPush = {
date: selectableDate.date,
timeSlots: selectableDate.timeSlots,
morningTimeSlots: selectableDate.timeSlots.filter((timeSlot) => {
const timeHour = parseInt(timeSlot.startTime.split(':')[0], 10);
return timeHour < 12;
}).map((timeSlot) => mapTimeSlot(timeSlot, 'morning')),
afternoonTimeSlots: selectableDate.timeSlots.filter((timeSlot) => {
const timeHour = parseInt(timeSlot.startTime.split(':')[0], 10);
return timeHour >= 12;
}).map((timeSlot) => mapTimeSlot(timeSlot, 'afternoon')),
isSelected: false
};
if (dateObjectToPush.morningTimeSlots.length > 0) {
const findIndex = dateObjectToPush.morningTimeSlots.findIndex((timeSlot) => timeSlot.id.endsWith('DROP OFF'));
if (findIndex !== -1) {
const dropOffSlot = dateObjectToPush.morningTimeSlots.splice(findIndex, 1)[0];
dateObjectToPush.morningTimeSlots.unshift(dropOffSlot);
}
}
if (dateObjectToPush.afternoonTimeSlots.length > 0) {
const findIndex = dateObjectToPush.afternoonTimeSlots.findIndex((timeSlot) => timeSlot.id.endsWith('DROP OFF'));
if (findIndex !== -1) {
const dropOffSlot = dateObjectToPush.afternoonTimeSlots.splice(findIndex, 1)[0];
dateObjectToPush.afternoonTimeSlots.push(dropOffSlot);
}
}
retTimeSlotsResponse.push(dateObjectToPush);
});
let dateDifferenceForPmMobile = null;
const firstPmMobileDateWithTimeSlots = retTimeSlotsResponse.find((date) => {
const hasPmMobileTimeSlots = date.afternoonTimeSlots?.length > 0;
if (hasPmMobileTimeSlots && pmMobileDaysLimit) {
dateDifferenceForPmMobile = calcDaysBetweenDates(todayDateString, date.date);
return dateDifferenceForPmMobile <= pmMobileDaysLimit;
}
return hasPmMobileTimeSlots;
});
let dateDifferenceForMobile = null;
const firstMobileDateWithTimeSlots = retTimeSlotsResponse.find((date) => {
const hasMobileTimeSlots = date.morningTimeSlots?.length > 0;
const hasPmMobileTimeSlots = date.afternoonTimeSlots?.length > 0;
if (hasMobileTimeSlots || hasPmMobileTimeSlots) {
if (mobileDaysLimit) {
dateDifferenceForMobile = calcDaysBetweenDates(todayDateString, date.date);
return mobileDaysLimit && dateDifferenceForMobile <= mobileDaysLimit;
}
return true;
}
return hasPmMobileTimeSlots;
});
const zipCodeData = await getMobileZipCodeData(mobileFirstObject.zipCode);
if ((firstPmMobileDateWithTimeSlots || firstMobileDateWithTimeSlots) && zipCodeData?.isValid) {
zipCodeData.zipCode = mobileFirstObject.zipCode;
const timeSlotForAutoSelectionMobile = {
date: firstPmMobileDateWithTimeSlots ? firstPmMobileDateWithTimeSlots.date : firstMobileDateWithTimeSlots.date,
estimatedServiceMinutesMax,
estimatedServiceMinutesMin,
isRepair: this.mainStore.order.damage.isRepair,
mobileProviderNumber: mobileFirstObject.mobileProviderNumber,
timeSlot: firstPmMobileDateWithTimeSlots ? firstPmMobileDateWithTimeSlots.afternoonTimeSlots[0]
: (firstMobileDateWithTimeSlots.morningTimeSlots[0] || firstMobileDateWithTimeSlots.afternoonTimeSlots[0]),
zipCodeData
};
const dateObjectToUse = new Date(`${timeSlotForAutoSelectionMobile.date}T${timeSlotForAutoSelectionMobile.timeSlot.startTime}:00`);
timeSlotForAutoSelectionMobile.dateObject = dateObjectToUse;
timeSlotForAutoSelectionMobile.dayOfWeek = dateObjectToUse.toLocaleDateString('en-US', { weekday: 'long' });
const experiment = this.getExperimentByName(experimentUniverses.ISS_MOBILE_FIRST_APPOINTMENTS);
if (experiment && !experiment.isExposed) {
this.mainStore.logMobileFirstExperimentExposure();
}
this.$refs[SUGGEST_TIMESLOT_MODAL_REF_NAME].openModal(timeSlotForAutoSelectionMobile);
}
}
}
},
async setData(premiumFeeWithPriceResponse, initialServiceLocationObj) {
this.selectedServiceLocation = initialServiceLocationObj;
this.mobilePremiumAppointmentFee = premiumFeeWithPriceResponse

View file

@ -0,0 +1,259 @@
<template>
<div class="suggest-timeslot-modal-container">
<modal
ref="suggestTimeslotModal"
:onModalClosedCallback="onModalClosed">
<div>
<div class="header">{{ modalHeaderText }}</div>
<div class="subheader">{{ modalHeaderSubText }}</div>
</div>
<div>
<div class="message"
v-html="modalBodyText">
</div>
<div class="appointment-info"
v-html="modalAppointmentInformationText">
</div>
</div>
<div class="footer-container">
<div>
<button type="button" aria-live="polite" aria-label="Confirm Appointment" class="btn btn-danger confirm-appointment"
@click="confirmAppointmentClicked">
{{ modalCloseButtonText }}
</button>
</div>
<div>
<button type="button" aria-live="polite" aria-label="See more options" class="btn btn-link see-more-options"
@click="seeMoreOptionsClicked">
{{ modalFooterSubText }}
</button>
</div>
</div>
</modal>
</div>
</template>
<script>
import modal from '@/digital-components/modal/modal.vue';
import {
convertDateToTwoDigitDay,
getDisplayTextForDurationLength,
militaryToTwelveHourTime
} from '@/helpers/date-helper';
export default {
name: 'suggest-timeslot-modal',
components: {
modal
},
emits: ['confirm-appointment-clicked'],
data() {
return {
confirmedAppointment: false,
timeSlotForAutoSelectionMobile: {}
};
},
props: {
cmsWidgetName: {
type: String,
default: 'SuggestTimeslotModal'
}
},
computed: {
customAppointmentEstimate() {
return getDisplayTextForDurationLength(this.timeSlotForAutoSelectionMobile.estimatedServiceMinutesMin, this.timeSlotForAutoSelectionMobile.estimatedServiceMinutesMax)
},
customAppointmentWindow() {
return `${militaryToTwelveHourTime(this.timeSlotForAutoSelectionMobile?.timeSlot?.startTime)} - ${militaryToTwelveHourTime(this.timeSlotForAutoSelectionMobile?.timeSlot?.endTime)}`;
},
customDayOfWeek() {
return this.timeSlotForAutoSelectionMobile.dayOfWeek ? this.timeSlotForAutoSelectionMobile.dayOfWeek : '';
},
customDayOfWeekMonthDayYear() {
if (!this.timeSlotForAutoSelectionMobile?.date) {
return '';
}
const dayOfWeekToUse = this.customDayOfWeek;
const year = this.timeSlotForAutoSelectionMobile.dateObject.getFullYear();
const month = this.timeSlotForAutoSelectionMobile.dateObject.toLocaleString('en-US', { month: 'long' });
const day = convertDateToTwoDigitDay(this.timeSlotForAutoSelectionMobile.dateObject);
return `${dayOfWeekToUse}, ${month} ${day}, ${year}`;
},
modalAppointmentInformationText() {
return this.getCmsContent(this.cmsWidgetName, 'BodyText2')
.replaceAll('{custom:dowMonthDayYear}', this.customDayOfWeekMonthDayYear)
.replaceAll('{custom:appointmentEstimate}', this.customAppointmentEstimate)
.replaceAll('{custom:appointmentWindow}', this.customAppointmentWindow);
},
modalBodyText() {
return this.getCmsContent(this.cmsWidgetName, 'BodyText')
.replaceAll('{custom:damageType}', this.timeSlotForAutoSelectionMobile.isRepair ? 'repair' : 'replace',)
.replaceAll('{custom:dayOfWeekLong}', this.customDayOfWeek);
},
modalCloseButtonText() {
return this.getCmsContent(this.cmsWidgetName, 'FooterText');
},
modalFooterSubText() {
return 'See more options';
},
modalHeaderSubText() {
return this.getCmsContent(this.cmsWidgetName, 'SubheaderText');
},
modalHeaderText() {
return this.getCmsContent(this.cmsWidgetName, 'HeaderText');
},
},
methods: {
confirmAppointmentClicked() {
this.confirmedAppointment = true;
this.$emit('confirm-appointment-clicked', this.timeSlotForAutoSelectionMobile);
this.$refs.suggestTimeslotModal.closeModal();
},
onModalClosed() {
this.pushEvent();
this.confirmedAppointment = false;
this.timeSlotForAutoSelectionMobile = {};
},
openModal(timeSlotForAutoSelectionMobile) {
this.timeSlotForAutoSelectionMobile = timeSlotForAutoSelectionMobile;
this.$refs.suggestTimeslotModal.openModal();
},
pushEvent() {
const gaAction = this.confirmedAppointment ? this.GaActions.YES : this.GaActions.NO;
if (this.timeSlotForAutoSelectionMobile && this.timeSlotForAutoSelectionMobile.timeSlot) {
this.pushEventToGA(
this.GaCategories.CONFIRMATION_CLICKED,
gaAction,
this.timeSlotForAutoSelectionMobile.timeSlot.startTime +
"-" +
this.timeSlotForAutoSelectionMobile.timeSlot.endTime +
" " +
this.timeSlotForAutoSelectionMobile.date,
true
);
}
},
seeMoreOptionsClicked() {
this.$refs.suggestTimeslotModal.closeModal();
},
footerButtonClick() {
this.$refs.suggestTimeslotModal.closeModal();
},
}
};
</script>
<style lang="scss" scoped>
.suggest-timeslot-modal-container {
.modal {
padding-right: 0;
}
:deep(.modal) {
.modal-dialog {
@include media-breakpoint-up(xs) {
width: auto;
max-width: unset;
}
@include media-breakpoint-up(xl) {
width: 37.5rem;
max-width: 37.5rem;
}
.modal-content {
width: 22rem;
max-width: 22rem;
padding: 1.5rem 0;
border-radius: 0.5rem;
line-height: 1.5rem;
.modal-header {
padding: 0;
}
.modal-body {
padding: 0 1rem;
}
.modal-footer {
display: none;
}
.header {
margin-top: 1rem;
color: #db0020;
text-align: center;
text-transform: uppercase;
letter-spacing: 0.03em;
font-weight: 600;
font-size: 0.875rem;
}
.subheader {
color: black;
text-align: center;
letter-spacing: 0.03em;
font-weight: 400;
font-size: 1.25rem;
line-height: 2rem;
}
.message {
margin-top: 1.5rem;
color: #525656;
letter-spacing: 0.03em;
font-weight: 400;
font-size: 1rem;
line-height: 1.625rem;
.emphasize {
color: black;
font-weight: 600;
}
}
.appointment-info {
margin-top: 1rem;
padding: 1rem;
background-color: #f4f4f4;
letter-spacing: 0.03em;
font-weight: 400;
font-size: 0.875rem;
.emphasize {
color: black;
font-weight: 600;
margin-right: 0.25rem;
}
}
.footer-container {
margin-top: 1.5rem;
padding: 0 1.5rem;
border: none;
.confirm-appointment {
width: 100%;
padding: .75rem 0;
border-radius: 4.5rem;
letter-spacing: 0.03em;
font-weight: 600;
font-size: 1rem;
}
.see-more-options {
text-align: center;
width: 100%;
margin-top: 1.5rem;
letter-spacing: 0.03em;
font-weight: 600;
font-size: 1rem;
line-height: 1.625rem;
}
}
}
}
}
}
</style>

View file

@ -68,6 +68,7 @@
:options="DamageCauseOptions"
disableAutoFill
:validationRules="rules.damageOption"
isRequired
placeHolderText="Select an option"
class="form-group" />
<textboxQuestion
@ -252,6 +253,35 @@ export default {
}
};
},
mounted() {
if (this.mainStore.applicationUser.firstHit) {
const isInfoPrefilled = !!(this.mainStore.order.policy.policyNumber
&& this.mainStore.order.policy.dateOfLoss
&& this.mainStore.order.policy.policyZipCode);
this.pushEventToGA("policy_info", "info_prefilled", isInfoPrefilled.toString(), true);
this.$nextTick(() => {
const formRoot = this.$el;
const requiredFieldCount = formRoot.querySelectorAll('[aria-required="true"]').length;
this.pushEventToGA("policy_info", "required_fields", requiredFieldCount.toString(), true);
});
this.mainStore.applicationUser.firstHit = false;
}
// TODO: Check if we're coming from SFA
// eslint-disable-next-line
if (false) {
this.pushEventToGA("co_branded", "welcome_clicked_cta", "yes_clicked", 0);
this.pushEventToGA("visitor_info_welcome", "referring_site", "SFA", null);
}
else {
this.pushEventToGA("visitor_info_welcome", "referring_site", "ClientSite", null);
}
this.pushEventToGA("visitor_info_welcome", "client_name", this.mainStore.accountNameForEvents, null);
},
computed: {
DamageCauseOptions() {
const damageCauseAnswers = this.getCmsContent(
@ -460,6 +490,20 @@ export default {
this.answeredContinueModal = true;
this.$refs.continueModal.closeModal();
}
},
watch: {
'welcomePageModel.policyZipCode': {
handler: async function (newZip) {
if (newZip.length === 5) {
this.pushEventToGA("policy_info", "policy_zip", newZip, true);
}
}
},
'welcomePageModel.damageCause': function (newCause) {
if (newCause) {
this.pushEventToGA("policy_info", "cause_of_loss", newCause, true);
}
}
}
};
</script>

View file

@ -8,6 +8,10 @@ export default {
hasSetting(settingName) {
return Object.hasOwn(useMainStore().experimentSettings, settingName);
},
getExperimentByName(experimentName) {
const experiment = useMainStore().applicationUser.experiments.find((e) => e.universeName === experimentName);
return experiment ? experiment : null;
},
getSettingValue(settingName) {
return this.hasSetting(settingName)
? useMainStore().experimentSettings[settingName]

View file

@ -240,7 +240,8 @@ export const getDefaultState = () => ({
triggeredSiteEntry: false, // TODO not in save session
duplicateOrders: [],
hasSentSaveQuoteEmail: null,
coverageLookupAttempts: 0
coverageLookupAttempts: 0,
firstHit: true
},
issConfig: {
clientName: 'Generic Insurance', // this is the default and will be overriden by the client's name
@ -446,7 +447,8 @@ export const useMainStore = defineStore({
.map((x) => x.settings)
.reduce((r, c) => Object.assign(r, c), {}) ?? {},
originalDeductible: (state) => (state.order.damage.isRepair ? state.order.originalDeductible.repair : state.order.originalDeductible.replace),
currentDeductible: (state) => (state.order.damage.isRepair ? state.order.currentDeductible.repair : state.order.currentDeductible.replace)
currentDeductible: (state) => (state.order.damage.isRepair ? state.order.currentDeductible.repair : state.order.currentDeductible.replace),
accountNameForEvents: (state) => state.issConfig.clientName.replaceAll(" ", "").replaceAll("&", "amp")
},
actions:
{
@ -2632,6 +2634,9 @@ export const useMainStore = defineStore({
}
);
},
logMobileFirstExperimentExposure() {
this.logExperimentIfExists(issPageValues.SCHEDULE_PAGE, experimentUniverses.ISS_MOBILE_FIRST_APPOINTMENTS);
},
logWelcomePageExperiments(issPage) {
this.logExperimentIfExists(issPage, experimentUniverses.ISS_FEATURETOGGLE_AREFEES_HIDDEN);
this.logExperimentIfExists(issPage, experimentUniverses.ISS_FEATURETOGGLE_AREFEES_OVERRIDDEN);

View file

@ -3,6 +3,8 @@ process.env.VUE_APP_CURRENT_ENVIRONMENT = "__VUE_APP_CURRENT_ENVIRONMENT__";
process.env.VUE_APP_CUSTOMER_PORTAL_URL = '__VUE_APP_CUSTOMER_PORTAL_URL__';
process.env.VUE_APP_GOOGLE_PLACES_API_KEY = "__VUE_APP_GOOGLE_PLACES_API_KEY__";
process.env.VUE_APP_SAFELITE_HOP = "__VUE_APP_SAFELITE_HOP__";
process.env.VUE_APP_ADYEN_ENVIRONMENT = '__VUE_APP_ADYEN_ENVIRONMENT__';
process.env.VUE_APP_ADYEN_CLIENT_KEY = '__VUE_APP_ADYEN_CLIENT_KEY__';
// GA & GTM
process.env.VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY = "__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__";