diff --git a/playwright-tests/.env.dev b/playwright-tests/.env.dev index c89292674..76f1b93ae 100644 --- a/playwright-tests/.env.dev +++ b/playwright-tests/.env.dev @@ -9,6 +9,8 @@ SKIP_CONTENT_SITE=false # Base URLs by environment (uncomment the one you need) # qa BASE_URL="https://www-qa2.safelite.com/" +# local version of FMG (after running the local server) +# BASE_URL="http://localhost:8080/fmg/" # qa with skipToInsurance Turned Off # BASE_URL="https://fixmyglassqa.safelite.com/?cns=all&experiments=ConceptFunnel=ConceptFunnel_V1=ConceptFunnel_VinAndEmailOptional=true,NextGen_IGQSkipToInsurance=NextGen_IGQSkipToInsurance_V1=NextGen_IGQSkipToInsurance_CONTROL=true" # sys @@ -25,4 +27,3 @@ ADMIN_SERVICE_API_URL="https://issadminapi.dev.sagaws.net/iss-admin/api/v1/" # API authentication (replace with actual value when running tests) CCIS_API_AUTH="undefined" -SKIP_CONTENT_SITE=false \ No newline at end of file diff --git a/playwright-tests/business-logic/Data/PaymentData.ts b/playwright-tests/business-logic/Data/PaymentData.ts index ae733d755..699a2eeb9 100644 --- a/playwright-tests/business-logic/Data/PaymentData.ts +++ b/playwright-tests/business-logic/Data/PaymentData.ts @@ -28,6 +28,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 0ea0e9605..3ac2747a0 100644 --- a/playwright-tests/business-logic/types/CustomerDetails.ts +++ b/playwright-tests/business-logic/types/CustomerDetails.ts @@ -1,4 +1,4 @@ -import { DamageType as DamageCause, WindshieldDamage, ServiceLocation, EndorsementType, VehicleLookupType, PartQuestionType, PaymentType, AppointmentType } from "./Enums"; +import { DamageType as DamageCause, WindshieldDamage, ServiceLocation, EndorsementType, VehicleLookupType, PartQuestionType, PaymentType, AppointmentType, AppointmentTimeslot } from "./Enums"; import { IAddress } from "./IAddress"; export interface ICustomerDetails { @@ -30,7 +30,8 @@ export interface IAppointmentDetails { appointmentDate?: Date, shopAddress?: string, // Used for in-shop serviceAddress?: IAddress, // Used for mobile - isVehicleProtected?: boolean // Used for mobile + isVehicleProtected?: boolean, + appointmentTimeSlot?: AppointmentTimeslot // Used for mobile } export interface IEndorsementDetails { diff --git a/playwright-tests/business-logic/types/Enums.ts b/playwright-tests/business-logic/types/Enums.ts index 782d21d45..91b0248ce 100644 --- a/playwright-tests/business-logic/types/Enums.ts +++ b/playwright-tests/business-logic/types/Enums.ts @@ -124,5 +124,11 @@ export enum AppointmentType{ DropOff = "Drop-off" } +export enum AppointmentTimeslot{ + EarlyBird = "EarlyBird", + DropOff = "DropOff", + overnight = "Overnight" +} + diff --git a/playwright-tests/pages/BasePage.ts b/playwright-tests/pages/BasePage.ts index 3c6722bd8..348ee9476 100644 --- a/playwright-tests/pages/BasePage.ts +++ b/playwright-tests/pages/BasePage.ts @@ -1,3 +1,5 @@ +import { IAppointmentDetails, ICustomerDetails } from '@business-logic/types/CustomerDetails'; +import Soft from '@business-logic/validations/Soft'; import test, { expect, type Locator, type Page } from '@playwright/test'; import { error } from 'console'; @@ -13,6 +15,7 @@ export class BasePage { readonly pageSpinner: Locator; readonly buttonLoadSpin: Locator; readonly hamburgerMenu: Locator; + readonly progressBar: Locator; constructor(page: Page){ this.page = page; @@ -21,6 +24,7 @@ export class BasePage { this.pageSpinner = page.getByRole('status'); this.buttonLoadSpin = page.getByRole('alert'); this.hamburgerMenu = this.page.getByRole('button', { name: 'Hamburger Menu (modal window)' }); + this.progressBar = this.page.locator('#progress-bar-container progress'); } async nextPage() { @@ -94,6 +98,42 @@ export class BasePage { console.log(`Referral Number:${referralNumber}`); console.log(`Referral Sequence Number:${referralSequenceNumber}`); }); - } + + async mockScheduleResponseForEarlyBird(customerDetails: ICustomerDetails) { + { + const apiUrl = `https://digitalapi.${process.env['NODE_ENV']!.replace('sys', 'test').toLowerCase()}.safelite.io/schedule/api/v1/schedule/mobile-time-slots`; + await this.page.route(apiUrl, async (route) => { + const response = await route.fetch(); + const responseBody = await response.json(); + + + responseBody.days.forEach((day: any) => { + day.timeSlots.forEach((slot: any) => { + if (slot.id.includes("AM")) { + slot.offerPremium = true; + } + }); + }); + + customerDetails.apptDate = responseBody.days.find((day: any) => + day.timeSlots.some((slot: any) => slot.offerPremium === true) + ).date || undefined; + + // Mock the response + await route.fulfill({ + response, + body: JSON.stringify(responseBody), + }); + }); + } + } + + async validateProgressBar(progressPercentage: string) { + await this.page.locator('button .loader').waitFor({ state: 'hidden', timeout: 60000 }); + const actualProgressPercentage = await this.progressBar.getAttribute("value") || "Not Found"; + Soft.expect(actualProgressPercentage).toBe(progressPercentage); + console.log(`Progress Bar Percentage: Actual - ${actualProgressPercentage} vs Expected - ${progressPercentage}`); + } + } \ No newline at end of file diff --git a/playwright-tests/pages/CapabilityQuestionsPage.ts b/playwright-tests/pages/CapabilityQuestionsPage.ts index 092a424f3..9416d534a 100644 --- a/playwright-tests/pages/CapabilityQuestionsPage.ts +++ b/playwright-tests/pages/CapabilityQuestionsPage.ts @@ -14,6 +14,7 @@ export default class CapabilityQuestionsPage extends PartQuestionsPage { async handleCapabilityQuestionsPage(testCase: Partial) { const { capabilityQuestions } = testCase; + await this.validateProgressBar("40"); // Validate the capability questions are on the page await this.validatePartQuestions(capabilityQuestions!); diff --git a/playwright-tests/pages/ContactDetailsPage.ts b/playwright-tests/pages/ContactDetailsPage.ts index 2641f6436..8dbd29f50 100644 --- a/playwright-tests/pages/ContactDetailsPage.ts +++ b/playwright-tests/pages/ContactDetailsPage.ts @@ -45,6 +45,8 @@ export class ContactDetailsPage extends BasePage { @step("ContactDetailsPage >> Enter contact details: ") async handleContactDetailsPage(testData: Partial) { const { customerDetails } = testData; + + await this.validateProgressBar("84"); await this.enterContactDetails(customerDetails!); await this.nextPage(); } diff --git a/playwright-tests/pages/EstimatePage.ts b/playwright-tests/pages/EstimatePage.ts index 2beb507b3..6a605293b 100644 --- a/playwright-tests/pages/EstimatePage.ts +++ b/playwright-tests/pages/EstimatePage.ts @@ -74,6 +74,7 @@ export class EstimatePage extends BasePage { @step("EstimatePage >> Select Lookup Type") async handleEstimatePage(testData: Partial) { const { vehicleDetails } = testData; + await this.validateProgressBar("28"); await this.vehicleLookup(vehicleDetails!); } } diff --git a/playwright-tests/pages/InsuranceCompanyPage.ts b/playwright-tests/pages/InsuranceCompanyPage.ts index e2d63ec2d..594075292 100644 --- a/playwright-tests/pages/InsuranceCompanyPage.ts +++ b/playwright-tests/pages/InsuranceCompanyPage.ts @@ -52,6 +52,7 @@ export class InsuranceCompanyPage extends BasePage { async handleInsuranceCompanyPage(testData: Partial) { const { claimDetails } = testData; + await this.validateProgressBar("52"); await this.enterInsuranceCompany(claimDetails!.client!); await this.nextPage(); } diff --git a/playwright-tests/pages/MoldingQuestionsPage.ts b/playwright-tests/pages/MoldingQuestionsPage.ts index 5671900ff..ab12cf9be 100644 --- a/playwright-tests/pages/MoldingQuestionsPage.ts +++ b/playwright-tests/pages/MoldingQuestionsPage.ts @@ -13,6 +13,8 @@ export default class MoldingQuestionsPage extends PartQuestionsPage { @step("MoldingQuestionsPage >> Select Molding Questions: ") async handleMoldingQuestionsPage(testCase: Partial) { const { moldingQuestions } = testCase; + + await this.validateProgressBar("40"); await this.validatePartQuestions(moldingQuestions!); await this.selectPartQuestionResponses(moldingQuestions!); await this.nextPage(); diff --git a/playwright-tests/pages/OrderConfirmationPage.ts b/playwright-tests/pages/OrderConfirmationPage.ts index 1c6158c68..6e513d7d2 100644 --- a/playwright-tests/pages/OrderConfirmationPage.ts +++ b/playwright-tests/pages/OrderConfirmationPage.ts @@ -178,6 +178,8 @@ export class OrderConfirmationPage extends BasePage { @step("OrderConfirmationPage >> Validate order") async verifyOrderConfirmationPage(testData: Partial) { + + await this.validateProgressBar("100"); await this.validateOrderConfirmationPage(testData); const workOrderNumber = await this.logOrderNumber(); await test.step(`Session Storage Work Order Number: ${workOrderNumber}`, async () => { diff --git a/playwright-tests/pages/PartQuestionPage.ts b/playwright-tests/pages/PartQuestionPage.ts index 831a27dc1..8ecced581 100644 --- a/playwright-tests/pages/PartQuestionPage.ts +++ b/playwright-tests/pages/PartQuestionPage.ts @@ -62,6 +62,8 @@ export class PartQuestionsPage extends BasePage { @step("PartQuestionsPage >> Select Vehicle Part Question Responses") async handlePartQuestionsPage(testData: Partial) { const { partQuestions } = testData; + + await this.validateProgressBar("40"); await this.validatePartQuestions(partQuestions!); await this.selectPartQuestionResponses(partQuestions!); await this.nextPage(); diff --git a/playwright-tests/pages/PaymentMethodPage.ts b/playwright-tests/pages/PaymentMethodPage.ts index 92d0da651..0705955f9 100644 --- a/playwright-tests/pages/PaymentMethodPage.ts +++ b/playwright-tests/pages/PaymentMethodPage.ts @@ -1,7 +1,7 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { BasePage } from './BasePage'; import { IPaymentDetails } from '@business-logic/types/CustomerDetails'; -import { AppointmentType, PaymentMethod, PaymentType, ServicePackage, VehicleDamage } from '@business-logic/types/Enums'; +import { AppointmentTimeslot, AppointmentType, PaymentMethod, PaymentType, ServicePackage, VehicleDamage } from '@business-logic/types/Enums'; import { PaymentPage } from './PaymentPage'; import { AfterpayPage } from './AfterpayPage'; import { PaypalPage } from './PaypalPage'; @@ -76,7 +76,7 @@ export class PaymentMethodPage extends BasePage { // Cart panel elements this.cartPanelDetails = this.page.locator('.cart-panel'); this.subtotalText = this.page.locator('.sub-total'); - this.finalAmountDueText = this.page.locator('div.amount-due'); + this.finalAmountDueText = this.cartPanelDetails.locator('div.amount-due'); this.glassServiceText = this.page.locator('div', { hasText: /^Glass service only$/ }); this.wiperBladesText = this.page.locator('div', { hasText: /^New wiper blades$/ }); this.rainDefenseText = this.page.locator('div', { hasText: /^Rain Defense™$/ }); @@ -148,13 +148,19 @@ export class PaymentMethodPage extends BasePage { expect.soft(servicePackageValue).toContain('New wiper blades'); } if (servicePackage === ServicePackage.Premium) { - expect.soft(servicePackageValue).toContain('Rain Defense™'); + expect.soft(servicePackageValue).toContain('Rain repel treatment'); } // Promo Code Validation if (promoCode) { expect.soft(servicePackageValue).toContain(`Promo code ${promoCode} applied`); }; + + // Early Bird line item validation + if (appointmentDetails?.appointmentTimeSlot == AppointmentTimeslot.EarlyBird) { + + expect.soft(servicePackageValue).toContain('Early Bird'); + } } } @@ -261,7 +267,7 @@ l async getActualAppointmentDetails(): Promise { let actualAppointmentDetails = new Map(); - let appointmentDetailsSubSections = await this.appointmentDetailsSection.locator('div .py-3').all(); + let appointmentDetailsSubSections = await this.appointmentDetailsSection.locator('.review-table .py-3').all(); for (let element of appointmentDetailsSubSections) { let label = await element.locator('div .text-block').innerText(); @@ -377,8 +383,8 @@ l : stringForReplace; break; case ServicePackage.Premium: - stringForRepair.push("New wiper blades", "Rain Defense™"); - stringForReplace.push("New wiper blades", "Rain Defense™"); + stringForRepair.push("New wiper blades", "Rain repel treatment"); + stringForReplace.push("New wiper blades", "Rain repel treatment"); expectedServicePackageDetails["Premium service"] = isRepair ? stringForRepair : stringForReplace; @@ -464,6 +470,7 @@ l async handlePaymentMethodPage(testData: Partial) { const { servicePackage, isRecalVehicle, paymentDetails } = testData; + await this.validateProgressBar("92"); await this.validatePaymentDetailsPage(testData); // Verify VAPS wipers on backend for standard and premium packages diff --git a/playwright-tests/pages/PaypalPage.ts b/playwright-tests/pages/PaypalPage.ts index 862a97edf..25a5be26b 100644 --- a/playwright-tests/pages/PaypalPage.ts +++ b/playwright-tests/pages/PaypalPage.ts @@ -5,6 +5,9 @@ import { IPaymentDetails } from '@business-logic/types/CustomerDetails'; export class PaypalPage extends BasePage { readonly page: Page; readonly loginWithPasswordButton: Locator; + readonly usernameTextBox: Locator; + readonly nextButton: Locator; + readonly usePasswordInsteadButton: Locator; readonly passwordTextBox: Locator; readonly paypalLoginButton: Locator; readonly completePurchaseButton: Locator; @@ -12,13 +15,19 @@ export class PaypalPage extends BasePage { constructor(page: Page) { super(page); this.page = page; - this.loginWithPasswordButton = page.getByRole('link', { name: 'Log in with a password instead' }); + this.usernameTextBox = page.getByPlaceholder('Email'); + this.nextButton = page.getByRole('button', { name: 'Next' }); + this.loginWithPasswordButton = page.getByRole('button', { name: 'Use Password Instead' }); this.passwordTextBox = page.getByPlaceholder('Password'); this.paypalLoginButton = page.getByRole('button', { name: 'Log In', exact: true }); - this.completePurchaseButton = page.getByTestId('submit-button-initial'); + this.completePurchaseButton = page.getByRole('button', { name: 'Pay $' }); } async completePaypalPurchase(paymentDetails: IPaymentDetails){ + if (!await this.loginWithPasswordButton.isVisible()) { + await this.usernameTextBox.fill(paymentDetails.username!); + await this.nextButton.click(); + } await this.loginWithPasswordButton.click(); await this.passwordTextBox.fill(paymentDetails.password!); await this.paypalLoginButton.click(); diff --git a/playwright-tests/pages/SchedulePage.ts b/playwright-tests/pages/SchedulePage.ts index 5daa3060e..16f70b333 100644 --- a/playwright-tests/pages/SchedulePage.ts +++ b/playwright-tests/pages/SchedulePage.ts @@ -2,7 +2,7 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { BasePage } from './BasePage'; import { IAppointmentDetails, ICustomerDetails } from '@business-logic/types/CustomerDetails'; import { formatDate, formatTime } from '@impl/utils/DateUtils'; -import { AppointmentType, ServiceLocation } from '@business-logic/types/Enums'; +import { AppointmentTimeslot, AppointmentType, ServiceLocation } from '@business-logic/types/Enums'; import { time } from 'console'; import { step } from '@business-logic/types/Step'; import { ITestData } from '@business-logic/types/ITestData'; @@ -47,23 +47,39 @@ export class SchedulePage extends BasePage { await this.modalContinueButton.click(); } - async scheduleFirstAppointment(customerDetails: ICustomerDetails) { + async scheduleFirstAppointment(testData: Partial) { + const { appointmentDetails, customerDetails } = testData; while (!(await this.firstAvailableDate.isVisible())) { + + if (appointmentDetails?.appointmentTimeSlot == AppointmentTimeslot.EarlyBird) { + await this.mockScheduleResponseForEarlyBird(customerDetails!); + } await this.viewMoreDatesLink.click(); } - await this.firstAvailableDate.click().then(async () => { - customerDetails.apptDate = `${await this.firstAvailableDate.getAttribute("id")}` - }); - // const timeSlots = this.timeSlots; - const timeSlotCount = await this.timeSlots.count(); - const randomIndex = Math.floor(Math.random() * timeSlotCount); - const timeSlot = this.timeSlots.nth(randomIndex); - await timeSlot.click().then(async () => { - customerDetails.apptTime = await this.getFormattedTimeSlot(timeSlot); - }); + if (appointmentDetails?.appointmentTimeSlot == AppointmentTimeslot.EarlyBird) { + await this.page.locator(`.selectable-days, [id='${customerDetails?.apptDate}']`).click(); + const earlyBirdTimeSlot = this.timeSlots.filter({ hasText: "Earlybird" }).first(); + await earlyBirdTimeSlot.click().then(async () => { + customerDetails!.apptTime = await this.getFormattedTimeSlot(earlyBirdTimeSlot); + }); + } + else { + await this.firstAvailableDate.click().then(async () => { + customerDetails!.apptDate = `${await this.firstAvailableDate.getAttribute("id")}` + }); + + // const timeSlots = this.timeSlots; + const timeSlotCount = await this.timeSlots.count(); + const randomIndex = Math.floor(Math.random() * timeSlotCount); + const timeSlot = this.timeSlots.nth(randomIndex); + await timeSlot.click().then(async () => { + customerDetails!.apptTime = await this.getFormattedTimeSlot(timeSlot); + }); + } + // appointmentmentDetails.serviceLocation === AppointmentType.DropOff ? await this.dropOffButton.click() : await this.firstAvailableTime.click(); - customerDetails.apptDuration = (await this.appointmentDuration.innerText()).replace("Duration: ", ""); + customerDetails!.apptDuration = (await this.appointmentDuration.innerText()).replace("Duration: ", ""); await this.nextPage(); } @@ -74,7 +90,7 @@ export class SchedulePage extends BasePage { { formattedTimeSlot = selectedTimeSlot.includes("overnight") ? "drop off by 5:30 pm on the night of your scheduled appointment. Pick-up time dependent on shop schedule" : "drop off before 9:30 AM"; } - else if (selectedTimeSlot.includes("-") || selectedTimeSlot.toLowerCase().includes("Earlybird")) + else if (selectedTimeSlot.includes("-") || selectedTimeSlot.includes("Earlybird")) { formattedTimeSlot = selectedTimeSlot.includes("Earlybird") ? "arriving between 8:00 AM - 12:00 PM" : `arriving between ${selectedTimeSlot}`; } @@ -87,7 +103,8 @@ export class SchedulePage extends BasePage { @step("SchedulePage >> Schedule appointment: ") async handleSchedulePage(testData: Partial) { - const { customerDetails } = testData; - await this.scheduleFirstAppointment(customerDetails!); + + await this.validateProgressBar("72"); + await this.scheduleFirstAppointment(testData); } } \ No newline at end of file diff --git a/playwright-tests/pages/ServiceLocationPage.ts b/playwright-tests/pages/ServiceLocationPage.ts index a31f38eb5..375566c04 100644 --- a/playwright-tests/pages/ServiceLocationPage.ts +++ b/playwright-tests/pages/ServiceLocationPage.ts @@ -1,7 +1,7 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { BasePage } from './BasePage'; import { IAppointmentDetails } from '@business-logic/types/CustomerDetails'; -import { AppointmentType } from '@business-logic/types/Enums'; +import { AppointmentTimeslot, AppointmentType } from '@business-logic/types/Enums'; import { AddressForm } from './forms/AddressForm'; import { faker } from '@faker-js/faker'; import { step } from '@business-logic/types/Step'; @@ -36,7 +36,6 @@ export class ServiceLocationPage extends BasePage { readonly zipCodeTextBox: Locator; readonly vehicleProtectedYesButton: Locator; readonly vehicleProtectedNoButton: Locator; - readonly saveAddressButton: Locator; readonly repeatedClicksModalCloseButton: Locator; url = process.env['BASE_URL']! + '/fmg/?fmgPage=service-location'; @@ -49,7 +48,7 @@ export class ServiceLocationPage extends BasePage { // Initial selection this.inShopButton = this.page.getByText(/In-shop/); - this.mobileButton = this.page.getByText(/Mobile/); + this.mobileButton = this.page.getByText(/Mobile/).nth(0); this.dropOffButton = this.page.getByText(/Drop-off/); this.RecalWarningMessage1 = this.page.getByText(/We're not able to provide mobile service/); this.RecalWarningMessage2 = this.page.getByText(/advanced safety system recalibration needs to be done in our shop./); @@ -72,15 +71,15 @@ export class ServiceLocationPage extends BasePage { 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: 'Continue' }) this.repeatedClicksModalCloseButton = this.page.locator('.QSISlider').locator('img[src*=\'close\']'); } - async selectLocation(appointmentDetails: IAppointmentDetails){ + async selectLocation(testData: Partial) { + const { appointmentDetails, customerDetails } = testData; - switch(appointmentDetails.serviceLocation) { - case AppointmentType.Mobile: - await this.scheduleMobile(appointmentDetails); + switch(appointmentDetails?.serviceLocation) { + case AppointmentType.Mobile: + await this.scheduleMobile(testData); break; case AppointmentType.InShop: await this.scheduleInShop(appointmentDetails); @@ -91,7 +90,6 @@ export class ServiceLocationPage extends BasePage { } } - async scheduleInShop(appointmentDetails?: IAppointmentDetails) { await this.inShopButton.click(); if (appointmentDetails && appointmentDetails.shopAddress) { @@ -116,10 +114,13 @@ export class ServiceLocationPage extends BasePage { } } - async scheduleMobile(appointmentDetails: IAppointmentDetails){ - if (appointmentDetails.serviceAddress) { + async scheduleMobile(testData: Partial) { + const { appointmentDetails, customerDetails } = testData; + if (appointmentDetails?.serviceAddress) { await this.mobileButton.click(); - await this.enterServiceAddressButton.click(); + if (!(await this.serviceAddressTextBox.isVisible())) { + await this.enterServiceAddressButton.click(); + } await this.addressForm.populateAddress({ address: appointmentDetails.serviceAddress! }); if (await this.repeatedClicksModalCloseButton.isVisible()) { await this.repeatedClicksModalCloseButton.click(); @@ -129,7 +130,10 @@ export class ServiceLocationPage extends BasePage { } else { await this.vehicleProtectedNoButton.check(); } - await this.saveAddressButton.click(); + if (appointmentDetails.appointmentTimeSlot == AppointmentTimeslot.EarlyBird) + { + await this.mockScheduleResponseForEarlyBird(customerDetails!); + } } else { console.error('ServiceLocationPage >> Please supply an address') } @@ -159,8 +163,9 @@ export class ServiceLocationPage extends BasePage { @step("ServiceLocationPage >> Select service location: ") async handleServiceLocationPage(testData: Partial) { - const { appointmentDetails } = testData; - await this.selectLocation(appointmentDetails!); + + await this.validateProgressBar("60"); + await this.selectLocation(testData); await this.nextPage(); } } \ No newline at end of file diff --git a/playwright-tests/pages/ServicePackagesPage.ts b/playwright-tests/pages/ServicePackagesPage.ts index 35872828b..6a464d77e 100644 --- a/playwright-tests/pages/ServicePackagesPage.ts +++ b/playwright-tests/pages/ServicePackagesPage.ts @@ -185,6 +185,7 @@ export class ServicePackagesPage extends BasePage { async handleServicePackagePage(testData: Partial) { const { customerDetails, paymentMethod, servicePackage, promoCode, canNotRecal, dynamicRecal, hasOemEndorsement, vehicleDamage } = testData; + await this.validateProgressBar("48"); // Define repair damage types (vs. replacement types) const repairTypes: VehicleDamage[] = [ VehicleDamage.WindshieldOneChip, diff --git a/playwright-tests/pages/ServiceZipPage.ts b/playwright-tests/pages/ServiceZipPage.ts index d11ffa139..cf2574b22 100644 --- a/playwright-tests/pages/ServiceZipPage.ts +++ b/playwright-tests/pages/ServiceZipPage.ts @@ -14,6 +14,7 @@ export class ServiceZipPage extends LookupPage { @step("ZipLookupPage >> Lookup by service ZIP: ") async handleServiceZipPage(testData: Partial) { const { customerDetails, vehicleDetails, alertFlags } = testData; + await this.validateProgressBar("32"); await this.enterZip(customerDetails!.address.postalCode!); await this.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); } diff --git a/playwright-tests/pages/VehicleDamagePage.ts b/playwright-tests/pages/VehicleDamagePage.ts index 1b48a1d4d..6e56553e5 100644 --- a/playwright-tests/pages/VehicleDamagePage.ts +++ b/playwright-tests/pages/VehicleDamagePage.ts @@ -169,6 +169,7 @@ export class VehicleDamagePage extends BasePage { const {vehicleDamage} = testData; const {isRepairReplace, isRepairOnly} = testData.alertFlags || {}; + await this.validateProgressBar("16"); await this.selectDamage(vehicleDamage!); // Handle alert conditions for vehicle damage if (isRepairReplace) { diff --git a/playwright-tests/pages/VehicleLookupAddressPage.ts b/playwright-tests/pages/VehicleLookupAddressPage.ts index ab0ad86f8..9fe02ea6f 100644 --- a/playwright-tests/pages/VehicleLookupAddressPage.ts +++ b/playwright-tests/pages/VehicleLookupAddressPage.ts @@ -31,6 +31,8 @@ export class VehicleLookupAddressPage extends LookupPage { @step("VehicleLookupAddressPage >> Lookup by address: ") async handleVehicleLookupAddressPage(testData: Partial) { const { customerDetails, vehicleDetails, alertFlags } = testData; + + await this.validateProgressBar("32"); await this.lookupVehicleByAddress(customerDetails!, vehicleDetails!); await this.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); } diff --git a/playwright-tests/pages/VehicleLookupLicensePage.ts b/playwright-tests/pages/VehicleLookupLicensePage.ts index ef7f51f85..6045fc4cc 100644 --- a/playwright-tests/pages/VehicleLookupLicensePage.ts +++ b/playwright-tests/pages/VehicleLookupLicensePage.ts @@ -30,6 +30,8 @@ export class VehicleLookupLicensePage extends LookupPage { @step("VehicleLookupLicensePage >> Lookup by license plate: ") async handleVehicleLookupLicensePage(testData: Partial) { const { customerDetails, vehicleDetails, alertFlags } = testData; + + await this.validateProgressBar("32"); await this.enterPlateDetails(vehicleDetails!); await this.enterZip(customerDetails!.address.postalCode!); await this.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); diff --git a/playwright-tests/pages/VehiclePartsPage.ts b/playwright-tests/pages/VehiclePartsPage.ts index aabaa1f4e..ca07ffd53 100644 --- a/playwright-tests/pages/VehiclePartsPage.ts +++ b/playwright-tests/pages/VehiclePartsPage.ts @@ -13,6 +13,8 @@ export default class VehiclePartQuestionsPage extends PartQuestionsPage{ @step("VehiclePartsPage >> Select Vehicle Part Questions: ") async handleVehiclePartsPage(testCase: Partial) { const { vehiclePartQuestions } = testCase; + + await this.validateProgressBar("40"); await this.validatePartQuestions(vehiclePartQuestions!); await this.selectPartQuestionResponses(vehiclePartQuestions!); await this.nextPage(); diff --git a/playwright-tests/pages/VehicleSelectionPage.ts b/playwright-tests/pages/VehicleSelectionPage.ts index 049f9fb8d..f554639a5 100644 --- a/playwright-tests/pages/VehicleSelectionPage.ts +++ b/playwright-tests/pages/VehicleSelectionPage.ts @@ -47,6 +47,7 @@ export class VehicleSelectionPage extends BasePage { @step("VehicleSelectionPage >> Select Vehicle: ") async handleVehicleSelectionPage(testData: Partial) { + await this.validateProgressBar("4"); const { vehicleDetails } = testData; const { isHeavyTruckVehicle, isSplitWindshield } = testData.alertFlags || {}; diff --git a/playwright-tests/pages/VinLookupPage.ts b/playwright-tests/pages/VinLookupPage.ts index 1e96c14c5..acbeade7f 100644 --- a/playwright-tests/pages/VinLookupPage.ts +++ b/playwright-tests/pages/VinLookupPage.ts @@ -26,6 +26,8 @@ export class VinLookupPage extends LookupPage { @step("VinLookupPage >> Lookup by VIN: ") async handleVehicleLookupVinPage(testData: Partial) { const { customerDetails, vehicleDetails, alertFlags } = testData; + + await this.validateProgressBar("32"); await this.enterVin(vehicleDetails!.vin!); await this.enterZip(customerDetails!.address.postalCode!); await this.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); diff --git a/playwright-tests/tests/CashReplaceVinMobile.ts b/playwright-tests/tests/CashReplaceVinMobile.ts index 37470200e..b9acf3542 100644 --- a/playwright-tests/tests/CashReplaceVinMobile.ts +++ b/playwright-tests/tests/CashReplaceVinMobile.ts @@ -1,6 +1,6 @@ //Imports here import { ITestData } from "@business-logic/types/ITestData" -import { AppointmentType, PaymentType } from "@business-logic/types/Enums"; +import { AppointmentTimeslot, AppointmentType, PaymentType } from "@business-logic/types/Enums"; import TestCase from "@business-logic/types/TestCase"; import { VehicleLookupType } from "@business-logic/types/Enums"; import { getDefaultTestData, setFakerSeedFromTestName } from "@business-logic/constants/DefaultTestData"; @@ -48,7 +48,8 @@ const cashReplaceVinMobileData: Partial = { state: 'MD', postalCode: '21237', country: 'United States' - } + }, + appointmentTimeSlot: AppointmentTimeslot.EarlyBird }, // Payment at service diff --git a/src/experiment-components/service-package-radio-for-afterpay.vue b/src/experiment-components/service-package-radio-for-afterpay.vue index f38621462..7e4e4551a 100644 --- a/src/experiment-components/service-package-radio-for-afterpay.vue +++ b/src/experiment-components/service-package-radio-for-afterpay.vue @@ -117,6 +117,9 @@ export default { ); return displayPrice != this.additionalButtonData.strikeThroughPrice; }, + totalAfterpayPrice() { + return parseFloat(this.buttonAuxillaryCopy.replace("$", "")); + }, }, methods: { getRouterLinkDisplayTextFromCopy, @@ -134,11 +137,12 @@ export default { .filter((lineItem) => lineItem); }, afterpayPrice() { - const decimalPrice = parseFloat(this.buttonAuxillaryCopy.replace("$", "")); + const decimalPrice = this.totalAfterpayPrice; return "$" + (decimalPrice / 4).toFixed(2); }, truncatedSinglePayment() { - return "$" + Math.trunc(this.buttonAuxillaryCopy.replace("$", "")); + const decimalPrice = this.totalAfterpayPrice; + return "$" + decimalPrice.toFixed(2); }, hasPackageDiscount() { return ( diff --git a/src/layouts/payment/payment.vue b/src/layouts/payment/payment.vue index 745d5ebe1..91e3c04c5 100644 --- a/src/layouts/payment/payment.vue +++ b/src/layouts/payment/payment.vue @@ -62,6 +62,23 @@ :isNoComp="isNoComp" :isExpandedOnLoad="true" /> +
+ +
+
{ + return { + buttonLabel: answer.Text, + altText: answer.Text, + groupName: "payment-method", + value: answer.Name, + buttonImage: answer.AnswerImageUrl, + }; + }); + + return tempItems; + }, }, methods: { arePagePrerequisitesValid() { @@ -594,6 +652,15 @@ export default { return preReqResult; }, + getAnswersNullSafe(widgetName) { + const rawData = this.getCmsContent(widgetName, "Answers"); + + if (!rawData) { + return []; + } else { + return rawData; + } + }, getWOrkOrderNumber() { if (store.getters.order.workOrderNumber) { const items = store.getters.order.workOrderNumber.split("-"); @@ -797,6 +864,18 @@ export default { ); } }, + switchToAfterpay() { + this.dispatchStoreAction( + storeActions.SAVE_PAYMENT_METHOD_CHOICE, + paymentMethods.AFTERPAY, + false + ); + const iframe = this.$refs.paymentFrame; + if (iframe) { + iframe.contentWindow.postMessage("afterpay", "*"); + window.scrollTo({ top: 0, left: 0, behavior: "smooth" }); + } + }, setIFrameListener() { window.addEventListener("message", (event) => this.handleIFrameContentWindowMessage(event) @@ -820,6 +899,7 @@ export default { loadingModal, cart, alert, + buttonQuestion, }, }; diff --git a/src/layouts/quote/service-package-question/service-package-question.vue b/src/layouts/quote/service-package-question/service-package-question.vue index 546bb573a..2dc3e4028 100644 --- a/src/layouts/quote/service-package-question/service-package-question.vue +++ b/src/layouts/quote/service-package-question/service-package-question.vue @@ -292,7 +292,7 @@ export default { }) ).toFixed(2); - if (this.hasPricingByDay()) { + if (this.hasPricingByDay() && !this.isAfterpayBreakoutDisplay()) { return "$" + formattedPriceFloat; } return (this.isInsuranceSelected ? "As little as $" : "$") + formattedPriceFloat;