diff --git a/azure-pipelines-automated-testing.yml b/azure-pipelines-automated-testing.yml index c1388bd1..d232fac9 100644 --- a/azure-pipelines-automated-testing.yml +++ b/azure-pipelines-automated-testing.yml @@ -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 diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 0ad8e535..b2134181 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -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__) diff --git a/package-lock.json b/package-lock.json index df67185e..9ecec093 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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": { diff --git a/playwright-tests/business-logic/data/ClientData.ts b/playwright-tests/business-logic/data/ClientData.ts index adce9400..1f1c2da6 100644 --- a/playwright-tests/business-logic/data/ClientData.ts +++ b/playwright-tests/business-logic/data/ClientData.ts @@ -235,6 +235,7 @@ const defaultAfterpayDetails: IPaymentDetails = { const defaultPaypalDetails: IPaymentDetails = { paymentType: PaymentType.Paypal, + username:'Itqatest@safelite.com', password: 'Safelite1' } diff --git a/playwright-tests/business-logic/types/CustomerDetails.ts b/playwright-tests/business-logic/types/CustomerDetails.ts index 1152f7a4..9a4abd95 100644 --- a/playwright-tests/business-logic/types/CustomerDetails.ts +++ b/playwright-tests/business-logic/types/CustomerDetails.ts @@ -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 { diff --git a/playwright-tests/business-logic/types/Enums.ts b/playwright-tests/business-logic/types/Enums.ts index 13557a92..9280ab00 100644 --- a/playwright-tests/business-logic/types/Enums.ts +++ b/playwright-tests/business-logic/types/Enums.ts @@ -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%', } \ No newline at end of file diff --git a/playwright-tests/business-logic/types/IBailoutFlags.ts b/playwright-tests/business-logic/types/IBailoutFlags.ts index 790d4209..9889e337 100644 --- a/playwright-tests/business-logic/types/IBailoutFlags.ts +++ b/playwright-tests/business-logic/types/IBailoutFlags.ts @@ -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 } \ No newline at end of file diff --git a/playwright-tests/business-logic/types/ITestData.ts b/playwright-tests/business-logic/types/ITestData.ts index 01df8990..05bcbabd 100644 --- a/playwright-tests/business-logic/types/ITestData.ts +++ b/playwright-tests/business-logic/types/ITestData.ts @@ -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? diff --git a/playwright-tests/impl/api/CcisApiUtil.ts b/playwright-tests/impl/api/CcisApiUtil.ts index d1d2dd87..e4f9c8a5 100644 --- a/playwright-tests/impl/api/CcisApiUtil.ts +++ b/playwright-tests/impl/api/CcisApiUtil.ts @@ -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' } }); } diff --git a/playwright-tests/pages/AddressLookupPage.ts b/playwright-tests/pages/AddressLookupPage.ts index 70f4f514..f64a17bc 100644 --- a/playwright-tests/pages/AddressLookupPage.ts +++ b/playwright-tests/pages/AddressLookupPage.ts @@ -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 didn’t return a VIN matchPlease re-enter the information below or'); + this.addressNoMatchTextBox = page.getByText('Your address didn’t return a VIN match'); this.stateRestrictionsTextBox = page.getByText('State restrictionsLooks like'); } diff --git a/playwright-tests/pages/AfterpayPage.ts b/playwright-tests/pages/AfterpayPage.ts index 4691163f..2f13ed04 100644 --- a/playwright-tests/pages/AfterpayPage.ts +++ b/playwright-tests/pages/AfterpayPage.ts @@ -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(); } - - +} } \ No newline at end of file diff --git a/playwright-tests/pages/BailoutPage.ts b/playwright-tests/pages/BailoutPage.ts index d387b15b..a75e98fe 100644 --- a/playwright-tests/pages/BailoutPage.ts +++ b/playwright-tests/pages/BailoutPage.ts @@ -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; } diff --git a/playwright-tests/pages/BasePage.ts b/playwright-tests/pages/BasePage.ts index 6d2d5462..1c8ba1da 100644 --- a/playwright-tests/pages/BasePage.ts +++ b/playwright-tests/pages/BasePage.ts @@ -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; } } diff --git a/playwright-tests/pages/ContactDetailsPage.ts b/playwright-tests/pages/ContactDetailsPage.ts index a942a12e..991cdff7 100644 --- a/playwright-tests/pages/ContactDetailsPage.ts +++ b/playwright-tests/pages/ContactDetailsPage.ts @@ -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 = {}; - 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) { diff --git a/playwright-tests/pages/CoverageStatementPage.ts b/playwright-tests/pages/CoverageStatementPage.ts index 9832cf9c..b5f2a5f0 100644 --- a/playwright-tests/pages/CoverageStatementPage.ts +++ b/playwright-tests/pages/CoverageStatementPage.ts @@ -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: 'We’re 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: "We’re 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(); + } + } -} \ No newline at end of file diff --git a/playwright-tests/pages/EndorsementsPage.ts b/playwright-tests/pages/EndorsementsPage.ts index 8e154119..47f2db64 100644 --- a/playwright-tests/pages/EndorsementsPage.ts +++ b/playwright-tests/pages/EndorsementsPage.ts @@ -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: diff --git a/playwright-tests/pages/OrderConfirmationPage.ts b/playwright-tests/pages/OrderConfirmationPage.ts index 8f30ad8c..9c8f22fd 100644 --- a/playwright-tests/pages/OrderConfirmationPage.ts +++ b/playwright-tests/pages/OrderConfirmationPage.ts @@ -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) { // 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() { diff --git a/playwright-tests/pages/PaymentMethodPage.ts b/playwright-tests/pages/PaymentMethodPage.ts index 57d560be..c715e02f 100644 --- a/playwright-tests/pages/PaymentMethodPage.ts +++ b/playwright-tests/pages/PaymentMethodPage.ts @@ -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}`); - // } } \ No newline at end of file diff --git a/playwright-tests/pages/PaymentPage.ts b/playwright-tests/pages/PaymentPage.ts index 51942f0e..4ba47ee6 100644 --- a/playwright-tests/pages/PaymentPage.ts +++ b/playwright-tests/pages/PaymentPage.ts @@ -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){ diff --git a/playwright-tests/pages/PaypalPage.ts b/playwright-tests/pages/PaypalPage.ts index 862a97ed..adb6ba5d 100644 --- a/playwright-tests/pages/PaypalPage.ts +++ b/playwright-tests/pages/PaypalPage.ts @@ -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(); + } } diff --git a/playwright-tests/pages/PolicyHolderDetailsPage.ts b/playwright-tests/pages/PolicyHolderDetailsPage.ts index 47f11031..0acde323 100644 --- a/playwright-tests/pages/PolicyHolderDetailsPage.ts +++ b/playwright-tests/pages/PolicyHolderDetailsPage.ts @@ -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' }); + } } } \ No newline at end of file diff --git a/playwright-tests/pages/PolicyVehiclesPage.ts b/playwright-tests/pages/PolicyVehiclesPage.ts index 23ba54c7..882341b6 100644 --- a/playwright-tests/pages/PolicyVehiclesPage.ts +++ b/playwright-tests/pages/PolicyVehiclesPage.ts @@ -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(){ diff --git a/playwright-tests/pages/ProviderPreferencePage.ts b/playwright-tests/pages/ProviderPreferencePage.ts index 7b6a53bc..97afe80f 100644 --- a/playwright-tests/pages/ProviderPreferencePage.ts +++ b/playwright-tests/pages/ProviderPreferencePage.ts @@ -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(); } } \ No newline at end of file diff --git a/playwright-tests/pages/SchedulePage.ts b/playwright-tests/pages/SchedulePage.ts index 0bd340dc..8c8f3f8c 100644 --- a/playwright-tests/pages/SchedulePage.ts +++ b/playwright-tests/pages/SchedulePage.ts @@ -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 { + 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 { + const timeSlots = this.page.locator('.time-slot-button'); + const nonDropOff = timeSlots.filter({ hasNotText: /Drop & Go/i }); + return nonDropOff.first(); } } \ No newline at end of file diff --git a/playwright-tests/pages/ServiceLocationPage.ts b/playwright-tests/pages/ServiceLocationPage.ts index d84f87df..12f9de32 100644 --- a/playwright-tests/pages/ServiceLocationPage.ts +++ b/playwright-tests/pages/ServiceLocationPage.ts @@ -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(){ diff --git a/playwright-tests/pages/ServicePackagesPage.ts b/playwright-tests/pages/ServicePackagesPage.ts index 9e8dd866..83c0b133 100644 --- a/playwright-tests/pages/ServicePackagesPage.ts +++ b/playwright-tests/pages/ServicePackagesPage.ts @@ -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(); } } \ No newline at end of file diff --git a/playwright-tests/pages/TpaConfirmationPage.ts b/playwright-tests/pages/TpaConfirmationPage.ts index fb0460e2..eda89d42 100644 --- a/playwright-tests/pages/TpaConfirmationPage.ts +++ b/playwright-tests/pages/TpaConfirmationPage.ts @@ -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() + ); + } } \ No newline at end of file diff --git a/playwright-tests/pages/TpaSearchPage.ts b/playwright-tests/pages/TpaSearchPage.ts index 8f4b8639..0919f2d8 100644 --- a/playwright-tests/pages/TpaSearchPage.ts +++ b/playwright-tests/pages/TpaSearchPage.ts @@ -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(); } diff --git a/playwright-tests/pages/TpaSubmitPage.ts b/playwright-tests/pages/TpaSubmitPage.ts index e563af7a..0439adbe 100644 --- a/playwright-tests/pages/TpaSubmitPage.ts +++ b/playwright-tests/pages/TpaSubmitPage.ts @@ -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 } \ No newline at end of file diff --git a/playwright-tests/pages/VehicleDamagePage.ts b/playwright-tests/pages/VehicleDamagePage.ts index 004ca3c1..96eb6711 100644 --- a/playwright-tests/pages/VehicleDamagePage.ts +++ b/playwright-tests/pages/VehicleDamagePage.ts @@ -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(); } diff --git a/playwright-tests/pages/VehicleLookupLicensePage.ts b/playwright-tests/pages/VehicleLookupLicensePage.ts index 433ed849..926be302 100644 --- a/playwright-tests/pages/VehicleLookupLicensePage.ts +++ b/playwright-tests/pages/VehicleLookupLicensePage.ts @@ -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 didn’t 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]); diff --git a/playwright-tests/pages/VehicleLookupPage.ts b/playwright-tests/pages/VehicleLookupPage.ts index 1dab391e..8e8433d3 100644 --- a/playwright-tests/pages/VehicleLookupPage.ts +++ b/playwright-tests/pages/VehicleLookupPage.ts @@ -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); diff --git a/playwright-tests/pages/VehiclePartsPage.ts b/playwright-tests/pages/VehiclePartsPage.ts index 9671b7f9..70d9a7ee 100644 --- a/playwright-tests/pages/VehiclePartsPage.ts +++ b/playwright-tests/pages/VehiclePartsPage.ts @@ -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(); + } + } + } } \ No newline at end of file diff --git a/playwright-tests/pages/WelcomePage.ts b/playwright-tests/pages/WelcomePage.ts index e7c8c4ae..fe3e0e76 100644 --- a/playwright-tests/pages/WelcomePage.ts +++ b/playwright-tests/pages/WelcomePage.ts @@ -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); + } } } \ No newline at end of file diff --git a/playwright-tests/playwright.config.ts b/playwright-tests/playwright.config.ts index 070a1c7b..94842ffb 100644 --- a/playwright-tests/playwright.config.ts +++ b/playwright-tests/playwright.config.ts @@ -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", diff --git a/playwright-tests/tests/0000__M.test.ts b/playwright-tests/tests/0000__M.test.ts index 9026a21a..537ecfe4 100644 --- a/playwright-tests/tests/0000__M.test.ts +++ b/playwright-tests/tests/0000__M.test.ts @@ -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 = { - 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(); + } + }); } diff --git a/playwright-tests/tests/0001_EssentialReplaceStatisAdas.ts b/playwright-tests/tests/0001_EssentialReplaceStatisAdas.ts index cc3da886..f3a4588a 100644 --- a/playwright-tests/tests/0001_EssentialReplaceStatisAdas.ts +++ b/playwright-tests/tests/0001_EssentialReplaceStatisAdas.ts @@ -21,10 +21,10 @@ const essentialReplaceStaticAdasData: Partial = { 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' } }, diff --git a/playwright-tests/tests/0002_EssentialRepairInShopAcura.ts b/playwright-tests/tests/0002_EssentialRepairInShopAcura.ts index f05482a4..a255853d 100644 --- a/playwright-tests/tests/0002_EssentialRepairInShopAcura.ts +++ b/playwright-tests/tests/0002_EssentialRepairInShopAcura.ts @@ -23,9 +23,9 @@ const essentialRepairInShopAcuraData: Partial = { 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' } diff --git a/playwright-tests/tests/0004_EssentialTpaEnabled.ts b/playwright-tests/tests/0004_EssentialTpaEnabled.ts index 4aed4e19..3d521235 100644 --- a/playwright-tests/tests/0004_EssentialTpaEnabled.ts +++ b/playwright-tests/tests/0004_EssentialTpaEnabled.ts @@ -28,10 +28,10 @@ const essentialTpaEnabledData: Partial = { 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 = { 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, diff --git a/playwright-tests/tests/0011_EssentialDoNotSeeShopBailout.ts b/playwright-tests/tests/0011_EssentialDoNotSeeShopBailout.ts index 40bdd106..9f9e2f13 100644 --- a/playwright-tests/tests/0011_EssentialDoNotSeeShopBailout.ts +++ b/playwright-tests/tests/0011_EssentialDoNotSeeShopBailout.ts @@ -16,7 +16,6 @@ const essentialDoNotSeeShopData: Partial = { partQuestions: undefined, isSafelite: false, bailoutFlags: { - isDoNotSeeMyShopBailout: true }, servicePackage: faker.helpers.enumValue(ServicePackage), customerDetails: { diff --git a/playwright-tests/tests/advanced/0001a_ReplaceInShopCredit.ts b/playwright-tests/tests/advanced/0001a_ReplaceInShopCredit.ts index dc64aa11..1351e326 100644 --- a/playwright-tests/tests/advanced/0001a_ReplaceInShopCredit.ts +++ b/playwright-tests/tests/advanced/0001a_ReplaceInShopCredit.ts @@ -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 = { @@ -31,7 +31,6 @@ const advancedScenario0001Data: Partial = { isDuplicateClaim: false, isPolicyFound: true, isNoComp: false, - hasStateLawPopup: true, endorsements: undefined, vehiclePartQuestions: [ @@ -60,7 +59,7 @@ const advancedScenario0001Data: Partial = { shopAddress: undefined, appointmentDate: nextWeekday }, - paymentDetails: ClientData.getDefaultPaypalDetails()//ClientData.getDefaultCreditCardDetails() + paymentDetails: ClientData.getDefaultCreditCardDetails() } // TODO: Add validation for deductible/covered amount diff --git a/playwright-tests/tests/advanced/0002a_ReplaceOemEndorsement.ts b/playwright-tests/tests/advanced/0002a_ReplaceOemEndorsement.ts index 2d6bf104..56d2f722 100644 --- a/playwright-tests/tests/advanced/0002a_ReplaceOemEndorsement.ts +++ b/playwright-tests/tests/advanced/0002a_ReplaceOemEndorsement.ts @@ -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); diff --git a/playwright-tests/tests/advanced/0003a_MobileAfterpay.ts b/playwright-tests/tests/advanced/0003a_MobileAfterpay.ts index 6e494225..f205e795 100644 --- a/playwright-tests/tests/advanced/0003a_MobileAfterpay.ts +++ b/playwright-tests/tests/advanced/0003a_MobileAfterpay.ts @@ -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 = { @@ -33,6 +33,7 @@ const advancedScenario0003Data: Partial = { isDuplicateClaim: false, isPolicyFound: true, isNoComp: false, + hasOemEndorsement: true, hasStateLawPopup: false, endorsements: undefined, vehiclePartQuestions: [ @@ -44,7 +45,7 @@ const advancedScenario0003Data: Partial = { { partQuestionType: PartQuestionType.PassengerRearColor, isOnPage: true, - optionToSelect: 'Green Tint' + optionToSelect: 'Solar, Passenger side, Rear' }, ], isSafelite: true, @@ -53,7 +54,7 @@ const advancedScenario0003Data: Partial = { claimDetails: { policyNumber: policyNumber, policyDeductible: 50, - damageDate: '2016-01-02', + damageDate: '2026-01-02', damageCause: faker.helpers.enumValue(DamageType) }, policySoap: policySoap, diff --git a/playwright-tests/tests/advanced/0004a_NoDeductibleAdas.ts b/playwright-tests/tests/advanced/0004a_NoDeductibleAdas.ts index b64aaac5..21015d9d 100644 --- a/playwright-tests/tests/advanced/0004a_NoDeductibleAdas.ts +++ b/playwright-tests/tests/advanced/0004a_NoDeductibleAdas.ts @@ -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 = { @@ -33,8 +33,7 @@ const advancedScenario0004aData: Partial = { isNoComp: false, hasStateLawPopup: false, endorsements: undefined, - vehiclePartQuestions: [ - ], + partQuestions: [], isSafelite: true, servicePackage: faker.helpers.enumValue(ServicePackage), customerDetails: customerDetails, diff --git a/playwright-tests/tests/advanced/0005a_CapabilityQuestions.ts b/playwright-tests/tests/advanced/0005a_CapabilityQuestions.ts index 64a1d1c6..0d0bc31f 100644 --- a/playwright-tests/tests/advanced/0005a_CapabilityQuestions.ts +++ b/playwright-tests/tests/advanced/0005a_CapabilityQuestions.ts @@ -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 = { diff --git a/playwright-tests/tests/advanced/0006a_RepairNoDeductibleMobile.ts b/playwright-tests/tests/advanced/0006a_RepairNoDeductibleMobile.ts index 7491c063..279f141c 100644 --- a/playwright-tests/tests/advanced/0006a_RepairNoDeductibleMobile.ts +++ b/playwright-tests/tests/advanced/0006a_RepairNoDeductibleMobile.ts @@ -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 = { diff --git a/playwright-tests/tests/advanced/0007a_RepairStateLanguage.ts b/playwright-tests/tests/advanced/0007a_RepairStateLanguage.ts index 6abec815..cfec1614 100644 --- a/playwright-tests/tests/advanced/0007a_RepairStateLanguage.ts +++ b/playwright-tests/tests/advanced/0007a_RepairStateLanguage.ts @@ -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 = { diff --git a/playwright-tests/tests/advanced/0008a_RepairTpa.ts b/playwright-tests/tests/advanced/0008a_RepairTpa.ts index c5b23efa..2404f831 100644 --- a/playwright-tests/tests/advanced/0008a_RepairTpa.ts +++ b/playwright-tests/tests/advanced/0008a_RepairTpa.ts @@ -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 = { @@ -33,7 +33,7 @@ const advancedScenario0008Data: Partial = { isDuplicateClaim: false, isPolicyFound: true, isNoComp: false, - hasStateLawPopup: true, + hasStateLawPopup: false, endorsements: undefined, vehiclePartQuestions: [ // { diff --git a/playwright-tests/tests/advanced/0009a_NoDeductibleFlorida.ts b/playwright-tests/tests/advanced/0009a_NoDeductibleFlorida.ts index 103f769c..7b0dae5b 100644 --- a/playwright-tests/tests/advanced/0009a_NoDeductibleFlorida.ts +++ b/playwright-tests/tests/advanced/0009a_NoDeductibleFlorida.ts @@ -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 = { @@ -38,7 +38,7 @@ const advancedScenario0009Data: Partial = { { endorsementType: EndorsementType.Educator, isOnPolicy: true, - isClickYes: false + isClickYes: true } ], vehiclePartQuestions: [ diff --git a/playwright-tests/tests/advanced/0010a_RearGlass.ts b/playwright-tests/tests/advanced/0010a_RearGlass.ts index de2fc541..c793e224 100644 --- a/playwright-tests/tests/advanced/0010a_RearGlass.ts +++ b/playwright-tests/tests/advanced/0010a_RearGlass.ts @@ -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 = { @@ -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'); diff --git a/playwright-tests/tests/advanced/0011a_ItacNoAdas.ts b/playwright-tests/tests/advanced/0011a_ItacNoAdas.ts index bf008707..8bffcbf0 100644 --- a/playwright-tests/tests/advanced/0011a_ItacNoAdas.ts +++ b/playwright-tests/tests/advanced/0011a_ItacNoAdas.ts @@ -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 = { @@ -33,13 +33,13 @@ const advancedScenario0011Data: Partial = { 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 = { customerDetails: customerDetails, claimDetails: { policyNumber: policyNumber, - policyDeductible: 500, + policyDeductible: 0, damageDate: '2023-03-01', damageCause: faker.helpers.enumValue(DamageType) }, diff --git a/playwright-tests/tests/advanced/0012a_ItacDropOff.ts b/playwright-tests/tests/advanced/0012a_ItacDropOff.ts index 3cfe4788..213ec490 100644 --- a/playwright-tests/tests/advanced/0012a_ItacDropOff.ts +++ b/playwright-tests/tests/advanced/0012a_ItacDropOff.ts @@ -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 = { @@ -48,7 +48,7 @@ const advancedScenario0012Data: Partial = { customerDetails: customerDetails, claimDetails: { policyNumber: policyNumber, - policyDeductible: 500, + policyDeductible: 9999, damageDate: '2023-09-01', damageCause: faker.helpers.enumValue(DamageType) }, diff --git a/playwright-tests/tests/advanced/0013a_ItacMobile.ts b/playwright-tests/tests/advanced/0013a_ItacMobile.ts index 3e0cf9cc..288f631e 100644 --- a/playwright-tests/tests/advanced/0013a_ItacMobile.ts +++ b/playwright-tests/tests/advanced/0013a_ItacMobile.ts @@ -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 = { @@ -33,13 +33,13 @@ const advancedScenario0013Data: Partial = { 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 = { 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 = { 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'); diff --git a/playwright-tests/tests/advanced/0014a_NoCompAdas.ts b/playwright-tests/tests/advanced/0014a_NoCompAdas.ts index c8a4c414..919833c0 100644 --- a/playwright-tests/tests/advanced/0014a_NoCompAdas.ts +++ b/playwright-tests/tests/advanced/0014a_NoCompAdas.ts @@ -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 = { @@ -33,13 +33,13 @@ const advancedScenario0014Data: Partial = { isDuplicateClaim: false, isPolicyFound: true, isNoComp: true, - isRecalWarning: true, + isRecalWarning: false, hasStateLawPopup: false, endorsements: [ { endorsementType: EndorsementType.Educator, isOnPolicy: true, - isClickYes: true + isClickYes: false } ], partQuestions: undefined, diff --git a/playwright-tests/tests/advanced/0015a_NoCompPartQuestions.ts b/playwright-tests/tests/advanced/0015a_NoCompPartQuestions.ts index c4108bb4..063059f9 100644 --- a/playwright-tests/tests/advanced/0015a_NoCompPartQuestions.ts +++ b/playwright-tests/tests/advanced/0015a_NoCompPartQuestions.ts @@ -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 = { @@ -46,16 +46,16 @@ const advancedScenario0015Data: Partial = { { 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, diff --git a/playwright-tests/tests/advanced/0016a_NoCompAllGlass.ts b/playwright-tests/tests/advanced/0016a_NoCompAllGlass.ts index 2b735a66..e336e3ea 100644 --- a/playwright-tests/tests/advanced/0016a_NoCompAllGlass.ts +++ b/playwright-tests/tests/advanced/0016a_NoCompAllGlass.ts @@ -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 = { @@ -39,38 +39,37 @@ const advancedScenario0016Data: Partial = { { 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 = { // 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() diff --git a/playwright-tests/tests/advanced/0017a_NoCompPremium.ts b/playwright-tests/tests/advanced/0017a_NoCompPremium.ts index 6852eb76..5c70f919 100644 --- a/playwright-tests/tests/advanced/0017a_NoCompPremium.ts +++ b/playwright-tests/tests/advanced/0017a_NoCompPremium.ts @@ -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 = { diff --git a/playwright-tests/tests/advanced/0018a_NoCompGlassOnly.ts b/playwright-tests/tests/advanced/0018a_NoCompGlassOnly.ts index 4208b0d9..94619a0b 100644 --- a/playwright-tests/tests/advanced/0018a_NoCompGlassOnly.ts +++ b/playwright-tests/tests/advanced/0018a_NoCompGlassOnly.ts @@ -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 = { diff --git a/playwright-tests/tests/advanced/0019a_NoCompEditVehicle.ts b/playwright-tests/tests/advanced/0019a_NoCompEditVehicle.ts index 5be94a82..d9b4c818 100644 --- a/playwright-tests/tests/advanced/0019a_NoCompEditVehicle.ts +++ b/playwright-tests/tests/advanced/0019a_NoCompEditVehicle.ts @@ -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 = { diff --git a/playwright-tests/tests/advanced/0020a_NoCompChangeLoc.ts b/playwright-tests/tests/advanced/0020a_NoCompChangeLoc.ts index 1f9f86fb..a565be44 100644 --- a/playwright-tests/tests/advanced/0020a_NoCompChangeLoc.ts +++ b/playwright-tests/tests/advanced/0020a_NoCompChangeLoc.ts @@ -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 = { @@ -60,12 +60,14 @@ const advancedScenario0020Data: Partial = { 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: { diff --git a/playwright-tests/tests/advanced/0021a_VehicleByPlate.ts b/playwright-tests/tests/advanced/0021a_VehicleByPlate.ts index 810e74bf..f5acaccf 100644 --- a/playwright-tests/tests/advanced/0021a_VehicleByPlate.ts +++ b/playwright-tests/tests/advanced/0021a_VehicleByPlate.ts @@ -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 = { clientTag: '', isDuplicateClaim: false, isPolicyFound: true, + isUnverifiedPolicyAfterVehicleLookup: true, isNoComp: false, hasStateLawPopup: true, endorsements: undefined, @@ -54,8 +55,8 @@ const advancedScenario0021Data: Partial = { 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, diff --git a/playwright-tests/tests/advanced/0022a_VehicleByVIN.ts b/playwright-tests/tests/advanced/0022a_VehicleByVIN.ts index 7866c3eb..dffddf16 100644 --- a/playwright-tests/tests/advanced/0022a_VehicleByVIN.ts +++ b/playwright-tests/tests/advanced/0022a_VehicleByVIN.ts @@ -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 = { clientTag: '', isDuplicateClaim: false, isPolicyFound: true, + isUnverifiedPolicyAfterVehicleLookup: true, isNoComp: false, hasStateLawPopup: true, endorsements: undefined, diff --git a/playwright-tests/tests/advanced/0023a_VehicleByVINPartsQns.ts b/playwright-tests/tests/advanced/0023a_VehicleByVINPartsQns.ts index f6d650bb..5a33473c 100644 --- a/playwright-tests/tests/advanced/0023a_VehicleByVINPartsQns.ts +++ b/playwright-tests/tests/advanced/0023a_VehicleByVINPartsQns.ts @@ -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 = { clientTag: '', isDuplicateClaim: false, isPolicyFound: true, + isUnverifiedPolicyAfterVehicleLookup: true, isNoComp: false, hasStateLawPopup: true, endorsements: undefined, diff --git a/playwright-tests/tests/advanced/0024a_VehicleByVINUnverifiedBailout.ts b/playwright-tests/tests/advanced/0024a_VehicleByVINUnverifiedBailout.ts index 7df0a5a4..26f1daa8 100644 --- a/playwright-tests/tests/advanced/0024a_VehicleByVINUnverifiedBailout.ts +++ b/playwright-tests/tests/advanced/0024a_VehicleByVINUnverifiedBailout.ts @@ -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 = { @@ -50,14 +50,14 @@ const advancedScenario0024Data: Partial = { 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: [ diff --git a/playwright-tests/tests/advanced/0025a_VehicleByVINUnverifiedBailout.ts b/playwright-tests/tests/advanced/0025a_VehicleByVINUnverifiedBailout.ts index f3e6c7a8..435d5a51 100644 --- a/playwright-tests/tests/advanced/0025a_VehicleByVINUnverifiedBailout.ts +++ b/playwright-tests/tests/advanced/0025a_VehicleByVINUnverifiedBailout.ts @@ -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 = { diff --git a/playwright-tests/tests/advanced/0026a_NoDeductibleAdasBailout5.ts b/playwright-tests/tests/advanced/0026a_NoDeductibleAdasBailout5.ts index ebfe3c79..c70be807 100644 --- a/playwright-tests/tests/advanced/0026a_NoDeductibleAdasBailout5.ts +++ b/playwright-tests/tests/advanced/0026a_NoDeductibleAdasBailout5.ts @@ -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 = { @@ -35,7 +35,6 @@ const advancedScenario0026aData: Partial = { isRecalNotification: true, endorsements: undefined, bailoutFlags: { - isDoNotSeeMyShopBailout: true }, vehiclePartQuestions: [ ], diff --git a/playwright-tests/tests/advanced/0028a_ItacCancelMyClaim.ts b/playwright-tests/tests/advanced/0028a_ItacCancelMyClaim.ts index 8479feb4..358dfbd5 100644 --- a/playwright-tests/tests/advanced/0028a_ItacCancelMyClaim.ts +++ b/playwright-tests/tests/advanced/0028a_ItacCancelMyClaim.ts @@ -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 = { @@ -36,7 +36,7 @@ const advancedScenario0028aData: Partial = { isItac: true, hasStateLawPopup: false, bailoutFlags: { - isRequestCallbackBailout: true, + isCoverageCancelledByUser: true, }, isVehicleSelectBailout: false, endorsements: [ diff --git a/playwright-tests/tests/advanced/0029a_HeavyVehicleBailout.ts b/playwright-tests/tests/advanced/0029a_HeavyVehicleBailout.ts index 6ecd2381..308ac97a 100644 --- a/playwright-tests/tests/advanced/0029a_HeavyVehicleBailout.ts +++ b/playwright-tests/tests/advanced/0029a_HeavyVehicleBailout.ts @@ -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 = { 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 = { make: 'Freightliner', model: '114sd', vehicleLookupType: VehicleLookupType.Vin, - vin: '1FUJG3DV5HHJH2985', + vin: '1FVMG3DV5HHJA1388', }, vehicleDamage: [ VehicleDamage.WindshieldCrack, diff --git a/playwright-tests/tests/advanced/0030a_PartsServiceBailout.ts b/playwright-tests/tests/advanced/0030a_APIErrorBailout.ts similarity index 87% rename from playwright-tests/tests/advanced/0030a_PartsServiceBailout.ts rename to playwright-tests/tests/advanced/0030a_APIErrorBailout.ts index 07752f51..e392adc2 100644 --- a/playwright-tests/tests/advanced/0030a_PartsServiceBailout.ts +++ b/playwright-tests/tests/advanced/0030a_APIErrorBailout.ts @@ -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 = { @@ -36,7 +36,7 @@ const advancedScenario0030aData: Partial = { 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'); diff --git a/playwright-tests/tests/advanced/0031a_ReplaceServiceableBigTruck.ts b/playwright-tests/tests/advanced/0031a_ReplaceServiceableBigTruck.ts index da53e68a..b4fb227e 100644 --- a/playwright-tests/tests/advanced/0031a_ReplaceServiceableBigTruck.ts +++ b/playwright-tests/tests/advanced/0031a_ReplaceServiceableBigTruck.ts @@ -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 = { @@ -33,19 +33,11 @@ const advancedScenario0031Data: Partial = { 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, diff --git a/playwright-tests/tests/advanced/0032a_DeclineNonServiceableBigTruck.ts b/playwright-tests/tests/advanced/0032a_DeclineNonServiceableBigTruck.ts index da064309..b2433ced 100644 --- a/playwright-tests/tests/advanced/0032a_DeclineNonServiceableBigTruck.ts +++ b/playwright-tests/tests/advanced/0032a_DeclineNonServiceableBigTruck.ts @@ -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 = { @@ -33,6 +33,9 @@ const advancedScenario0032Data: Partial = { isNoComp: false, hasStateLawPopup: false, endorsements: undefined, + bailoutFlags: { + isHeavyTruckVehicleBailout: true, + }, vehiclePartQuestions: [ ], diff --git a/playwright-tests/tests/mockResponses/mockResponsesConfig.json b/playwright-tests/tests/mockResponses/mockResponsesConfig.json index f121da14..a5061acc 100644 --- a/playwright-tests/tests/mockResponses/mockResponsesConfig.json +++ b/playwright-tests/tests/mockResponses/mockResponsesConfig.json @@ -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" diff --git a/src/constants/analytics.js b/src/constants/analytics.js index c0171209..8cf147e8 100644 --- a/src/constants/analytics.js +++ b/src/constants/analytics.js @@ -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({ diff --git a/src/constants/experiments.js b/src/constants/experiments.js index 9e959331..c336503b 100644 --- a/src/constants/experiments.js +++ b/src/constants/experiments.js @@ -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' }); diff --git a/src/helpers/adyen-helper.js b/src/helpers/adyen-helper.js index d1b5b571..70d2fe34 100644 --- a/src/helpers/adyen-helper.js +++ b/src/helpers/adyen-helper.js @@ -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, }; diff --git a/src/helpers/service-location-helper.js b/src/helpers/service-location-helper.js index 72572256..13aced81 100644 --- a/src/helpers/service-location-helper.js +++ b/src/helpers/service-location-helper.js @@ -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); -} \ No newline at end of file +} diff --git a/src/iss-components/continue-modal/continue-modal.vue b/src/iss-components/continue-modal/continue-modal.vue index def01d34..8bca85ce 100644 --- a/src/iss-components/continue-modal/continue-modal.vue +++ b/src/iss-components/continue-modal/continue-modal.vue @@ -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'); } } diff --git a/src/layouts/contact-details/contact-details.vue b/src/layouts/contact-details/contact-details.vue index b73d3fc4..a5c0c646 100644 --- a/src/layouts/contact-details/contact-details.vue +++ b/src/layouts/contact-details/contact-details.vue @@ -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: { diff --git a/src/layouts/schedule-page/schedule-page.vue b/src/layouts/schedule-page/schedule-page.vue index cfae7efd..f9f5ce08 100644 --- a/src/layouts/schedule-page/schedule-page.vue +++ b/src/layouts/schedule-page/schedule-page.vue @@ -73,6 +73,10 @@ + ({ + 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 diff --git a/src/layouts/schedule-page/suggest-timeslot-modal/suggest-timeslot-modal.vue b/src/layouts/schedule-page/suggest-timeslot-modal/suggest-timeslot-modal.vue new file mode 100644 index 00000000..710d8200 --- /dev/null +++ b/src/layouts/schedule-page/suggest-timeslot-modal/suggest-timeslot-modal.vue @@ -0,0 +1,259 @@ + + + + + \ No newline at end of file diff --git a/src/layouts/welcome-page/welcome-page.vue b/src/layouts/welcome-page/welcome-page.vue index 92d8dd10..2b80ca23 100644 --- a/src/layouts/welcome-page/welcome-page.vue +++ b/src/layouts/welcome-page/welcome-page.vue @@ -68,6 +68,7 @@ :options="DamageCauseOptions" disableAutoFill :validationRules="rules.damageOption" + isRequired placeHolderText="Select an option" class="form-group" /> { + 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); + } + } } }; diff --git a/src/mixins/experiment-mixin.js b/src/mixins/experiment-mixin.js index 5b1f7142..7f9a761e 100644 --- a/src/mixins/experiment-mixin.js +++ b/src/mixins/experiment-mixin.js @@ -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] diff --git a/src/store/index.js b/src/store/index.js index bfa6de66..48f3a06a 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -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); diff --git a/vue.release.config.js b/vue.release.config.js index 6e8ad2b7..43abce0e 100644 --- a/vue.release.config.js +++ b/vue.release.config.js @@ -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__";