From eb2c1ab511bce2c63fe79fdcb8021e9304752a40 Mon Sep 17 00:00:00 2001 From: kpatel8hs4io <31411746+kpatel8hs4io@users.noreply.github.com> Date: Fri, 18 Jul 2025 10:11:22 -0400 Subject: [PATCH 01/11] Adding New Schedule Parity Flow Changes --- playwright-tests/framework/TestPages.ts | 3 + .../framework/localTypes/Enums.ts | 3 +- playwright-tests/pages/MobileDetailsPage.ts | 54 ++++++++ playwright-tests/pages/SchedulePage.ts | 116 ++++++++++++++++-- playwright-tests/tests/0000__M.test.ts | 11 +- .../tests/CashReplaceGlassPromoInshop.ts | 12 +- .../tests/CashReplaceWiperDropoff.ts | 3 +- 7 files changed, 186 insertions(+), 16 deletions(-) create mode 100644 playwright-tests/pages/MobileDetailsPage.ts diff --git a/playwright-tests/framework/TestPages.ts b/playwright-tests/framework/TestPages.ts index cfc7940bd..e873a1c7b 100644 --- a/playwright-tests/framework/TestPages.ts +++ b/playwright-tests/framework/TestPages.ts @@ -30,6 +30,7 @@ import { VerifyDetailsPage } from "../pages/VerifyDetailsPage" import { EndorsementsPage } from "../pages/EndorsementsPage" import { PolicyDriverPage } from "../pages/PolicyDriverPage" import { ServiceZipPage } from "../pages/ServiceZipPage" +import { MobileDetailsPage } from "pages/MobileDetailsPage"; export interface ITestPages { capabilityQuestionsPage: CapabilityQuestionsPage, @@ -49,6 +50,7 @@ export interface ITestPages { policyVehiclesPage: PolicyVehiclesPage, recalibrationInfoPage: RecalibrationInfoPage, schedulePage: SchedulePage, + mobileDetailsPage: MobileDetailsPage, serviceLocationPage: ServiceLocationPage, servicePackagesPage: ServicePackagesPage, serviceZipPage: ServiceZipPage, @@ -83,6 +85,7 @@ export const createTestPages: TestPagesFactory = (page: Page) => { policyVehiclesPage: new PolicyVehiclesPage(page), recalibrationInfoPage: new RecalibrationInfoPage(page), schedulePage: new SchedulePage(page), + mobileDetailsPage: new MobileDetailsPage(page), serviceLocationPage: new ServiceLocationPage(page), servicePackagesPage: new ServicePackagesPage(page), serviceZipPage: new ServiceZipPage(page), diff --git a/playwright-tests/framework/localTypes/Enums.ts b/playwright-tests/framework/localTypes/Enums.ts index 067011f5e..0ab2796b5 100644 --- a/playwright-tests/framework/localTypes/Enums.ts +++ b/playwright-tests/framework/localTypes/Enums.ts @@ -19,7 +19,8 @@ export enum ProgressBarPercentages { ServicePackagePage = '48%', InsuranceCompanyPage = '52%', ServiceLocationPage = '60%', - SchedulePage = '72%', + SchedulePage = '64%', + MobileDetailsPage = '76%', ContactDetailsPage = '84%', PaymentMethodPage = '92%', OrderConfirmationPage = '100%' diff --git a/playwright-tests/pages/MobileDetailsPage.ts b/playwright-tests/pages/MobileDetailsPage.ts new file mode 100644 index 000000000..858e03823 --- /dev/null +++ b/playwright-tests/pages/MobileDetailsPage.ts @@ -0,0 +1,54 @@ +import { type Locator, type Page } from '@playwright/test'; +import { BasePage } from './BasePage'; +import { VehicleLookupType } from 'safelite-playwright-core'; +import { ProgressBarPercentages } from 'framework/localTypes/Enums'; +import { IVehicleDetails } from 'safelite-playwright-core'; +import { VinLookupPage } from './VinLookupPage'; +import { VehicleLookupAddressPage } from './VehicleLookupAddressPage'; +import { VehicleLookupLicensePage } from './VehicleLookupLicensePage'; +import { step } from 'framework/localTypes/Step'; +import { ITestData } from 'framework/TestData'; +import { faker } from '@faker-js/faker'; + +export class MobileDetailsPage extends BasePage { + readonly page: Page; + readonly streetAddressInputBox: Locator; + readonly CityInputBox: Locator; + readonly YesButton: Locator; + readonly NoButton: Locator; + + + constructor(page: Page) { + super(page); + this.page = page; + this.streetAddressInputBox = page.locator('#streetAddress'); + this.CityInputBox = page.locator('#city'); + this.YesButton = page.locator('label[buttonlabel="Yes"]'); + this.NoButton = page.locator('label[buttonlabel="No"]'); + } + + async enterMobileDetails(testData: Partial) { + const { appointmentDetails, customerDetails } = testData; + if (appointmentDetails?.serviceAddress) { + await this.streetAddressInputBox.fill(appointmentDetails.serviceAddress!.street); + await this.CityInputBox.fill(appointmentDetails.serviceAddress!.city); + } + else + { + console.error('ServiceLocationPage >> Please supply an address') + } + + if (faker.datatype.boolean()) { + await this.YesButton.check(); + } else { + await this.NoButton.check(); + } + } + + @step("MobileDetailsPage >> Enter service address: ") + async handleMobileDetailsPage(testData: Partial) { + await this.validateProgressBar(ProgressBarPercentages.MobileDetailsPage); + await this.enterMobileDetails(testData); + await this.nextPage(); + } +} diff --git a/playwright-tests/pages/SchedulePage.ts b/playwright-tests/pages/SchedulePage.ts index 0966e9984..80bc0486f 100644 --- a/playwright-tests/pages/SchedulePage.ts +++ b/playwright-tests/pages/SchedulePage.ts @@ -1,6 +1,6 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { BasePage } from './BasePage'; -import { IAppointmentDetails } from 'safelite-playwright-core'; +import { IAppointmentDetails, ServiceLocation } from 'safelite-playwright-core'; import { formatDate, formatTime } from 'safelite-playwright-core'; import { AppointmentTimeslot } from 'safelite-playwright-core'; import { ProgressBarPercentages } from 'framework/localTypes/Enums'; @@ -10,6 +10,17 @@ import { ITestData } from 'framework/TestData'; export class SchedulePage extends BasePage { readonly page: Page; + readonly inShopButton: Locator; + readonly mobileButton: Locator; + readonly RecalWarningMessage1: Locator; + readonly RecalWarningMessage2: Locator; + readonly militaryWarningMessage: Locator; + readonly changeZipButton: Locator; + readonly updateZipTextBox: Locator; + readonly saveZipButton: Locator; + readonly changeShopLocationLink: Locator; + readonly selectAShopOptions: Locator; + readonly firstAvailableDate: Locator; readonly firstAvailableTime: Locator; readonly modalContinueButton: Locator; @@ -18,11 +29,27 @@ export class SchedulePage extends BasePage { readonly viewMoreDatesLink: Locator; readonly appointmentDuration: Locator; readonly timeSlots: Locator; + readonly allDayDropOffButton: Locator; + readonly pickATimeButton: Locator; constructor(page: Page) { super(page); this.page = page; - this.firstAvailableDate = this.page.locator('.selectable-day').locator('nth=0'); + + this.inShopButton = this.page.getByText(/In-shop/); + this.mobileButton = this.page.locator('label[buttonlabel="Mobile"]'); + 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./); + this.militaryWarningMessage = this.page.locator('[class*="widget-name-AlertMilitaryBaseZipWidget"]'); + this.changeZipButton = this.page.locator('a:has(span.sr-only:has-text("edit zip code"))'); + this.updateZipTextBox = this.page.locator('#serviceZipCode').filter({ visible: true }); + this.saveZipButton = this.page.getByText('Save ZIP code', { exact: true }); + this.changeShopLocationLink = this.page.locator(".shop-question a").filter({ hasText: "Change shop location " }); + this.selectAShopOptions = this.page.locator('[class="shop-question"]'); + + this.allDayDropOffButton = this.page.locator("label[buttonlabel='Drop off all day']"); + this.pickATimeButton = this.page.locator("label[buttonlabel='Pick a time']"); + this.firstAvailableDate = this.page.locator('.selectable-day').filter({ visible: true}).locator('nth=0'); this.firstAvailableTime = this.page.locator('label').filter({ hasText: /AM|PM/ }).locator('div').locator('nth=0'); this.modalContinueButton = this.page.getByRole('dialog').getByRole('button', { name: 'Continue' }); this.dropOffButton = this.page.getByText('Drop off your vehicle', { exact: true }); @@ -32,6 +59,45 @@ export class SchedulePage extends BasePage { this.timeSlots = this.page.locator('fieldset[aria-labelledby=\'chooseTimeSlot\'] label'); } + async selectLocation(testData: Partial) { + const { appointmentDetails, customerDetails } = testData; + + switch(appointmentDetails?.serviceLocation) { + case ServiceLocation.Mobile: + await this.scheduleMobile(testData); + break; + case ServiceLocation.InShop: + case ServiceLocation.DropOff: + await this.scheduleInShop(appointmentDetails); + break; + } + } + + async scheduleInShop(appointmentDetails?: IAppointmentDetails) { + await this.inShopButton.click(); + if (appointmentDetails && appointmentDetails.shopAddress) { + const zipCodeMatch = appointmentDetails.shopAddress.match(/\b\d{5}$/); + if (zipCodeMatch) { + const zipCode = zipCodeMatch[0]; + // Enter the ZIP code into the updateZipTextBox + await this.changeZipButton.click(); + await this.page.waitForTimeout(500); + await this.updateZipTextBox.fill(zipCode); + await this.saveZipButton.click(); + } + + // await this.inShopButton.click(); + // await this.selectAShopOptions.locator(`[buttonbodycopy="${appointmentDetails.shopAddress}"]`).scrollIntoViewIfNeeded().then(() => this.selectAShopOptions.locator(`[buttonbodycopy="${appointmentDetails.shopAddress}"]`).click()); + } + } + + async scheduleMobile(testData: Partial) { + const { appointmentDetails, customerDetails } = testData; + if (appointmentDetails?.serviceAddress) { + await this.mobileButton.click(); + } + } + async scheduleAppointment(appointmentDetails: IAppointmentDetails) { const formattedDate = formatDate(appointmentDetails.appointmentDate!); const formattedTime = formatTime(appointmentDetails.appointmentDate!); @@ -51,24 +117,53 @@ export class SchedulePage extends BasePage { const { appointmentDetails, customerDetails } = testData; while (!(await this.firstAvailableDate.isVisible())) { - if (appointmentDetails?.appointmentTimeSlot == AppointmentTimeslot.EarlyBird) { + if (appointmentDetails?.appointmentTimeSlot === AppointmentTimeslot.EarlyBird) { await this.mockScheduleResponseForEarlyBird(customerDetails!); } await this.viewMoreDatesLink.click(); } + switch(appointmentDetails?.serviceLocation) { + case ServiceLocation.InShop: + await this.firstAvailableDate.click().then(async () => { + customerDetails!.apptDate = `${await this.firstAvailableDate.getAttribute("id")}` + }); + if (await this.pickATimeButton.isVisible()) { + await this.pickATimeButton.click(); + } + break; + case ServiceLocation.DropOff: + const inshopAvailableSlots = this.page.locator('.selectable-day').filter({ visible: true}).all(); + for (const inshopAvailableSlot of await inshopAvailableSlots) { + if (await this.allDayDropOffButton.isVisible()) { + await inshopAvailableSlot.click().then(async () => { + customerDetails!.apptDate = `${await inshopAvailableSlot.getAttribute("id")}` + }); + break; + } + } + break; + case ServiceLocation.Mobile: + await this.firstAvailableDate.click().then(async () => { + customerDetails!.apptDate = `${await this.firstAvailableDate.getAttribute("id")}`.replace("-mobile", ""); + }); + break; + } + 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 if (appointmentDetails?.serviceLocation == ServiceLocation.DropOff) + { + await this.allDayDropOffButton.click().then(async () => { + customerDetails!.apptTime = await this.getFormattedTimeSlot(this.allDayDropOffButton); }); } 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); @@ -80,7 +175,6 @@ export class SchedulePage extends BasePage { // appointmentmentDetails.serviceLocation === ServiceLocation.DropOff ? await this.dropOffButton.click() : await this.firstAvailableTime.click(); customerDetails!.apptDuration = (await this.appointmentDuration.innerText()).replace("Duration: ", ""); - await this.nextPage(); } async getFormattedTimeSlot(timeSlot: Locator) { @@ -103,8 +197,10 @@ export class SchedulePage extends BasePage { @step("SchedulePage >> Schedule appointment: ") async handleSchedulePage(testData: Partial) { - + const { appointmentDetails } = testData; await this.validateProgressBar(ProgressBarPercentages.SchedulePage); + await this.selectLocation(testData); await this.scheduleFirstAppointment(testData); + await this.nextPage(); } -} \ No newline at end of file +} diff --git a/playwright-tests/tests/0000__M.test.ts b/playwright-tests/tests/0000__M.test.ts index 86cc54c81..e7bf31e21 100644 --- a/playwright-tests/tests/0000__M.test.ts +++ b/playwright-tests/tests/0000__M.test.ts @@ -1,5 +1,5 @@ import { Page } from "@playwright/test"; -import { addSmokeTagToRandomTest, Flow } from 'safelite-playwright-core'; +import { addSmokeTagToRandomTest, Flow, ServiceLocation } from 'safelite-playwright-core'; import { ValidationOptions } from 'safelite-playwright-core'; import heavyTruckTests from "./alert-validation/alert0001_HeavyTruck"; import repairAndReplaceTests from "./alert-validation/alert0002_RepairAndReplace"; @@ -288,14 +288,19 @@ async function runWorkflow(page: Page, testCase: TestCase) { //============================= SERVICE SCHEDULING ============================= // Select service location - let serviceLocationPage = testCase.pages.serviceLocationPage; - await serviceLocationPage.handleServiceLocationPage(testCase.testData); + // let serviceLocationPage = testCase.pages.serviceLocationPage; + // await serviceLocationPage.handleServiceLocationPage(testCase.testData); // Schedule appointment let schedulePage = testCase.pages.schedulePage; await schedulePage.handleSchedulePage(testCase.testData); + if (testCase.testData.appointmentDetails?.serviceLocation === ServiceLocation.Mobile) { + let mobileDetailsPage = testCase.pages.mobileDetailsPage; + await mobileDetailsPage.handleMobileDetailsPage(testCase.testData); + } + // Enter contact details let contactDetailsPage = testCase.pages.contactDetailsPage; await contactDetailsPage.handleContactDetailsPage(testCase.testData); diff --git a/playwright-tests/tests/CashReplaceGlassPromoInshop.ts b/playwright-tests/tests/CashReplaceGlassPromoInshop.ts index 8d3679f1b..c7922e144 100644 --- a/playwright-tests/tests/CashReplaceGlassPromoInshop.ts +++ b/playwright-tests/tests/CashReplaceGlassPromoInshop.ts @@ -15,7 +15,17 @@ const cashReplaceGlassPromoInshopData: Partial = { // CASH Client paymentMethod: PaymentMethod.SelfPay, - + + customerDetails: { + ...getDefaultTestData().customerDetails!, + address: { + street: '4076 Spectacle Dr', + city: 'Columbus', + state: 'Ohio', + postalCode: '43235', + } + }, + // Flag for recalibration vehicle isRecalVehicle: true, diff --git a/playwright-tests/tests/CashReplaceWiperDropoff.ts b/playwright-tests/tests/CashReplaceWiperDropoff.ts index 695eaba9a..8080603f6 100644 --- a/playwright-tests/tests/CashReplaceWiperDropoff.ts +++ b/playwright-tests/tests/CashReplaceWiperDropoff.ts @@ -48,7 +48,8 @@ const cashReplaceWiperDropoffData: Partial = { // Override for drop-off service appointmentDetails: { serviceLocation: ServiceLocation.DropOff, - appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate + appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate, + shopAddress: "6826 Sawmill Rd, Columbus, OH 43235" }, // Payment at service From 7d04b9eda26927e8438176050f2743c7c41f5d3c Mon Sep 17 00:00:00 2001 From: kpatel8hs4io <31411746+kpatel8hs4io@users.noreply.github.com> Date: Mon, 21 Jul 2025 15:40:12 -0400 Subject: [PATCH 02/11] fix for shopAddress Issue if shop address is not provided in the test --- playwright-tests/pages/SchedulePage.ts | 8 +++++++- .../tests/CashReplaceMultiSlidingGlassDropoff.ts | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/playwright-tests/pages/SchedulePage.ts b/playwright-tests/pages/SchedulePage.ts index 80bc0486f..6956611b1 100644 --- a/playwright-tests/pages/SchedulePage.ts +++ b/playwright-tests/pages/SchedulePage.ts @@ -20,6 +20,7 @@ export class SchedulePage extends BasePage { readonly saveZipButton: Locator; readonly changeShopLocationLink: Locator; readonly selectAShopOptions: Locator; + readonly yourSafeliteShop: Locator; readonly firstAvailableDate: Locator; readonly firstAvailableTime: Locator; @@ -46,6 +47,7 @@ export class SchedulePage extends BasePage { this.saveZipButton = this.page.getByText('Save ZIP code', { exact: true }); this.changeShopLocationLink = this.page.locator(".shop-question a").filter({ hasText: "Change shop location " }); this.selectAShopOptions = this.page.locator('[class="shop-question"]'); + this.yourSafeliteShop = this.page.locator("fieldset:has(#chooseShop) label"); this.allDayDropOffButton = this.page.locator("label[buttonlabel='Drop off all day']"); this.pickATimeButton = this.page.locator("label[buttonlabel='Pick a time']"); @@ -86,9 +88,13 @@ export class SchedulePage extends BasePage { await this.saveZipButton.click(); } - // await this.inShopButton.click(); + await this.inShopButton.click(); // await this.selectAShopOptions.locator(`[buttonbodycopy="${appointmentDetails.shopAddress}"]`).scrollIntoViewIfNeeded().then(() => this.selectAShopOptions.locator(`[buttonbodycopy="${appointmentDetails.shopAddress}"]`).click()); } + + if (appointmentDetails && (appointmentDetails.shopAddress === "" || appointmentDetails.shopAddress === undefined )) { + appointmentDetails.shopAddress = await this.yourSafeliteShop.getAttribute("buttonbodycopy") || ""; + } } async scheduleMobile(testData: Partial) { diff --git a/playwright-tests/tests/CashReplaceMultiSlidingGlassDropoff.ts b/playwright-tests/tests/CashReplaceMultiSlidingGlassDropoff.ts index d2a54d58f..2da4c6f5e 100644 --- a/playwright-tests/tests/CashReplaceMultiSlidingGlassDropoff.ts +++ b/playwright-tests/tests/CashReplaceMultiSlidingGlassDropoff.ts @@ -34,7 +34,8 @@ const cashReplaceMultiSlidingGlassDropoffData: Partial = { // Override for drop-off service appointmentDetails: { serviceLocation: ServiceLocation.DropOff, - appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate + appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate, + shopAddress: "6826 Sawmill Rd, Columbus, OH 43235" }, // Override vehicle details From be3d7fd7c423507332c2c9a2b27960ffb785c3a9 Mon Sep 17 00:00:00 2001 From: maguire-arman Date: Tue, 22 Jul 2025 10:28:23 -0400 Subject: [PATCH 03/11] Changes pipeline pool to AmazonLinuxPool Updates the Azure Pipeline configuration to use the 'AmazonLinuxPool' instead of the 'Default' pool. This change likely aims to leverage the Amazon Linux environment for build and test processes, potentially to align with deployment targets or to utilize specific tools or dependencies available in that environment. --- azure-pipelines-automated-testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines-automated-testing.yml b/azure-pipelines-automated-testing.yml index 34cda2917..a44bb192d 100644 --- a/azure-pipelines-automated-testing.yml +++ b/azure-pipelines-automated-testing.yml @@ -6,7 +6,7 @@ schedules: branches: include: - develop -pool: 'Default' +pool: 'AmazonLinuxPool' variables: # - group: Digital-Infrastructure From b6b8d739f703a3fc9284f1c83ac4ef8c10d701f0 Mon Sep 17 00:00:00 2001 From: kpatel8hs4io <31411746+kpatel8hs4io@users.noreply.github.com> Date: Wed, 23 Jul 2025 22:17:59 -0400 Subject: [PATCH 04/11] Fixed playwright tests --- playwright-tests/pages/AfterpayPage.ts | 4 +- playwright-tests/pages/BasePage.ts | 39 +++++++++---------- .../pages/CoverageStatementPage.ts | 2 +- playwright-tests/pages/PaymentMethodPage.ts | 2 +- playwright-tests/pages/SchedulePage.ts | 20 ++++++---- playwright-tests/pages/ServicePackagesPage.ts | 8 +++- playwright-tests/tests/0000__M.test.ts | 12 +++--- .../tests/CashRepairInShopAfterPay.ts | 1 - 8 files changed, 48 insertions(+), 40 deletions(-) diff --git a/playwright-tests/pages/AfterpayPage.ts b/playwright-tests/pages/AfterpayPage.ts index b26949e50..ec6779e60 100644 --- a/playwright-tests/pages/AfterpayPage.ts +++ b/playwright-tests/pages/AfterpayPage.ts @@ -28,7 +28,7 @@ export class AfterpayPage extends BasePage { this.expirationDateTextBox = page.getByTestId('payment-method-cardExpiry-input'); this.cvvTextBox = page.getByTestId('payment-method-cardCvv-input'); - this.confirmButton = page.getByRole('button', { name: 'Confirm' }); + this.confirmButton = page.getByRole('button').filter({hasText: 'Confirm'}); } async login(password: string) { @@ -47,7 +47,7 @@ export class AfterpayPage extends BasePage { async executeAfterpayPayment(paymentDetails: IPaymentDetails) { await this.login(paymentDetails.password!); - await this.populateCardDetails(paymentDetails); + //await this.populateCardDetails(paymentDetails); await this.confirmButton.click(); } diff --git a/playwright-tests/pages/BasePage.ts b/playwright-tests/pages/BasePage.ts index 2d8d2b07a..ec6c5d767 100644 --- a/playwright-tests/pages/BasePage.ts +++ b/playwright-tests/pages/BasePage.ts @@ -103,32 +103,31 @@ export class BasePage { } 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(); + const currentDate = new Date().toISOString().split('T')[0]; // e.g., "2025-07-23" + if(route.request().postDataJSON().startDate === currentDate) { + 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; - } + 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; + 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), - }); - }); - } + // Mock the response + await route.fulfill({ + response, + body: JSON.stringify(responseBody), + }); + } + }); } async validateProgressBar(progressPercentage: string, timeout: number = 60000) { diff --git a/playwright-tests/pages/CoverageStatementPage.ts b/playwright-tests/pages/CoverageStatementPage.ts index 032104ec1..af0768951 100644 --- a/playwright-tests/pages/CoverageStatementPage.ts +++ b/playwright-tests/pages/CoverageStatementPage.ts @@ -56,7 +56,7 @@ export class CoverageStatementPage extends InsuranceBasePage { if (await unverifiedDeductibleElement.isVisible() || await this.noCompText.isVisible()) { // Click on continue button if unverified header is visible - await this.continueButton.click(); + // await this.continueButton.click(); } } diff --git a/playwright-tests/pages/PaymentMethodPage.ts b/playwright-tests/pages/PaymentMethodPage.ts index 87bf89c5e..2ba68dcc3 100644 --- a/playwright-tests/pages/PaymentMethodPage.ts +++ b/playwright-tests/pages/PaymentMethodPage.ts @@ -206,7 +206,7 @@ export class PaymentMethodPage extends BasePage { break; case PaymentType.AfterPay: await this.payInFourButton.click(); - await this.nextPage(); + await this.continueButton.click(); // Capture popup const afterpayPopup = await browserContext.waitForEvent('page'); diff --git a/playwright-tests/pages/SchedulePage.ts b/playwright-tests/pages/SchedulePage.ts index 6956611b1..1b2cc35de 100644 --- a/playwright-tests/pages/SchedulePage.ts +++ b/playwright-tests/pages/SchedulePage.ts @@ -121,6 +121,8 @@ export class SchedulePage extends BasePage { async scheduleFirstAppointment(testData: Partial) { const { appointmentDetails, customerDetails } = testData; + // await this.page.waitForTimeout(1000); + await this.page.waitForSelector('.date-picker', { state: 'visible' }); while (!(await this.firstAvailableDate.isVisible())) { if (appointmentDetails?.appointmentTimeSlot === AppointmentTimeslot.EarlyBird) { @@ -139,25 +141,27 @@ export class SchedulePage extends BasePage { } break; case ServiceLocation.DropOff: - const inshopAvailableSlots = this.page.locator('.selectable-day').filter({ visible: true}).all(); - for (const inshopAvailableSlot of await inshopAvailableSlots) { + const inshopAvailableDates = this.page.locator('.selectable-day').filter({ visible: true}).all(); + for (const inshopAvailableDate of await inshopAvailableDates) { + await inshopAvailableDate.click(); if (await this.allDayDropOffButton.isVisible()) { - await inshopAvailableSlot.click().then(async () => { - customerDetails!.apptDate = `${await inshopAvailableSlot.getAttribute("id")}` - }); + customerDetails!.apptDate = `${await inshopAvailableDate.getAttribute("id")}`; break; } } break; case ServiceLocation.Mobile: - await this.firstAvailableDate.click().then(async () => { + if (appointmentDetails?.appointmentTimeSlot === AppointmentTimeslot.EarlyBird) { + await this.page.locator(`.selectable-days, [id='${customerDetails?.apptDate}-mobile']`).click(); + } else { + await this.firstAvailableDate.click().then(async () => { customerDetails!.apptDate = `${await this.firstAvailableDate.getAttribute("id")}`.replace("-mobile", ""); - }); + }); + } break; } 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); diff --git a/playwright-tests/pages/ServicePackagesPage.ts b/playwright-tests/pages/ServicePackagesPage.ts index 71f246da5..34818f285 100644 --- a/playwright-tests/pages/ServicePackagesPage.ts +++ b/playwright-tests/pages/ServicePackagesPage.ts @@ -1,6 +1,6 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { BasePage } from './BasePage'; -import { ServicePackage, VehicleDamage } from 'safelite-playwright-core'; +import { AppointmentTimeslot, ServicePackage, VehicleDamage } from 'safelite-playwright-core'; import { ProgressBarPercentages } from 'framework/localTypes/Enums'; import { PaymentMethod } from "framework/localTypes/Enums"; import { step } from 'framework/localTypes/Step'; @@ -189,7 +189,7 @@ export class ServicePackagesPage extends BasePage { @step("ServicePackagePage >> Select Payment Method and Service Type: ") async handleServicePackagePage(testData: Partial) { - const { customerDetails, paymentMethod, paymentDetails, servicePackage, isCanNotRecal, isDynamicRecal, hasOemEndorsement, vehicleDamage } = testData; + const { customerDetails, paymentMethod, paymentDetails, servicePackage, isCanNotRecal, isDynamicRecal, hasOemEndorsement, vehicleDamage, appointmentDetails } = testData; await this.validateProgressBar(ProgressBarPercentages.ServicePackagePage); // Define repair damage types (vs. replacement types) @@ -235,6 +235,10 @@ export class ServicePackagesPage extends BasePage { if (hasOemEndorsement) { await this.verifyOEMPart(); } + + if (appointmentDetails?.appointmentTimeSlot === AppointmentTimeslot.EarlyBird) { + await this.mockScheduleResponseForEarlyBird(customerDetails!); + } await this.nextPage(); } diff --git a/playwright-tests/tests/0000__M.test.ts b/playwright-tests/tests/0000__M.test.ts index e7bf31e21..204648276 100644 --- a/playwright-tests/tests/0000__M.test.ts +++ b/playwright-tests/tests/0000__M.test.ts @@ -381,12 +381,14 @@ export async function handleInsuranceFlow(testCase: TestCase) { // Handle scenario where user selected Cash to Insurance and needs to go back through the flow // right now we are choosing pay at appointment as payment method for this scenario if (isCashInsuranceFlow) { - let serviceLocationPage = testCase.pages.serviceLocationPage; - await serviceLocationPage.nextPage(); - - //schedule + // Schedule appointment let schedulePage = testCase.pages.schedulePage; - await schedulePage.nextPage(); + await schedulePage.handleSchedulePage(testCase.testData); + + if (testCase.testData.appointmentDetails?.serviceLocation === ServiceLocation.Mobile) { + let mobileDetailsPage = testCase.pages.mobileDetailsPage; + await mobileDetailsPage.handleMobileDetailsPage(testCase.testData); + } //customer dertails let contactDetailsPage = testCase.pages.contactDetailsPage; await contactDetailsPage.nextPage(); diff --git a/playwright-tests/tests/CashRepairInShopAfterPay.ts b/playwright-tests/tests/CashRepairInShopAfterPay.ts index bd5ff6abd..e328ba7e8 100644 --- a/playwright-tests/tests/CashRepairInShopAfterPay.ts +++ b/playwright-tests/tests/CashRepairInShopAfterPay.ts @@ -31,7 +31,6 @@ const cashRepairInShopAfterPayData : Partial = { // Override appointment details appointmentDetails: { ...getDefaultTestData().appointmentDetails!, - shopAddress: "6826 Sawmill Rd, Columbus, OH 43235" }, // Use predefined payment data From d9360bc04f358c787ea4773fe88d3c197580b6a2 Mon Sep 17 00:00:00 2001 From: Chris Redelinghuys Date: Tue, 29 Jul 2025 10:18:13 -0400 Subject: [PATCH 05/11] CASH-1291: Calendar not reloading when switching inshop/mobile --- src/layouts/schedule/schedule.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index 649c93201..7073c5426 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -1173,7 +1173,7 @@ export default { if (this.preSelectedDate) this.selectedDate = this.preSelectedDate; - if (!this.selectedDate) { + if (!this.preSelectedDate) { // if no date is preselected on load, then select the first available let selectedDateMobile = this.getSelectedDateForMobile(); let selectedDateInshop = this.getSelectedDateForInshop(); From 92d111877cb927ca0ce333fe4397726d60574135 Mon Sep 17 00:00:00 2001 From: Scott Kiener Date: Tue, 29 Jul 2025 15:16:31 -0400 Subject: [PATCH 06/11] Revert some of 1291 --- .../schedule/time-slot-question/time-slot-question.vue | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/layouts/schedule/time-slot-question/time-slot-question.vue b/src/layouts/schedule/time-slot-question/time-slot-question.vue index df37d4e09..97b334821 100644 --- a/src/layouts/schedule/time-slot-question/time-slot-question.vue +++ b/src/layouts/schedule/time-slot-question/time-slot-question.vue @@ -187,6 +187,7 @@ export default { let appointmentTypeCmsWidgetName; if ( + this.selectedDate == null || this.appointmentType === AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF || this.appointmentType === AppointmentTypeStrings.IN_SHOP || this.appointmentType === AppointmentTypeStrings.DROP_OFF @@ -205,6 +206,10 @@ export default { // ); // } } else { + if (!this.selectedAnswerForTimeSlots && this.selectedRouteCode) { + // Clearing selectedRouteCode if no time slot is selected + this.resetSelectedTimeSlot(); + } appointmentTypeCmsWidgetName = this.selectedRouteCode?.includes( PREMIUM_TIME_SLOT_ID_FLAG ) From b7cad29c3d781274dd06bca71b06e20b19ca0990 Mon Sep 17 00:00:00 2001 From: Scott Kiener Date: Tue, 29 Jul 2025 16:24:36 -0400 Subject: [PATCH 07/11] CASH-1303 | Fix --- src/layouts/schedule/schedule.vue | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index 7073c5426..948662e91 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -1660,6 +1660,17 @@ export default { AppointmentTypeStrings.IN_SHOP_OR_DROP_OFF) && this.selectedProvider ) { + this.selectedTimeSlotInfo = { + timeSlot: { + date: null, + routeCode: null, + startTime: null, + endTime: null, + jobMaxMinutes: null, + jobMinMinutes: null, + }, + isPremiumAppointment: null, + }; this.lastSelectedInshopOrDropoffProvider = this.selectedProvider; } this.appointmentType = AppointmentTypeStrings.MOBILE; From 59e2f275dc4190f86afdc4209310b8682dc99953 Mon Sep 17 00:00:00 2001 From: kpatel8hs4io <31411746+kpatel8hs4io@users.noreply.github.com> Date: Wed, 30 Jul 2025 11:14:38 -0400 Subject: [PATCH 08/11] few minor changes for playwright tests --- playwright-tests/pages/SchedulePage.ts | 1 + .../tests/CashReplaceSwitchToInsuranceProgressiveNoComp.ts | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/playwright-tests/pages/SchedulePage.ts b/playwright-tests/pages/SchedulePage.ts index 1b2cc35de..d4d00b6f0 100644 --- a/playwright-tests/pages/SchedulePage.ts +++ b/playwright-tests/pages/SchedulePage.ts @@ -123,6 +123,7 @@ export class SchedulePage extends BasePage { const { appointmentDetails, customerDetails } = testData; // await this.page.waitForTimeout(1000); await this.page.waitForSelector('.date-picker', { state: 'visible' }); + await this.page.locator('#date-picker-fieldset .loader').filter({visible: true}).waitFor({ state: 'hidden' }); while (!(await this.firstAvailableDate.isVisible())) { if (appointmentDetails?.appointmentTimeSlot === AppointmentTimeslot.EarlyBird) { diff --git a/playwright-tests/tests/CashReplaceSwitchToInsuranceProgressiveNoComp.ts b/playwright-tests/tests/CashReplaceSwitchToInsuranceProgressiveNoComp.ts index a6494792a..91e997986 100644 --- a/playwright-tests/tests/CashReplaceSwitchToInsuranceProgressiveNoComp.ts +++ b/playwright-tests/tests/CashReplaceSwitchToInsuranceProgressiveNoComp.ts @@ -1,9 +1,10 @@ //Imports here import { ITestData } from 'framework/TestData' -import { ServiceLocation } from "safelite-playwright-core"; +import { Flow, ServiceLocation } from "safelite-playwright-core"; import { DamageType, PartQuestionType, PaymentType } from 'safelite-playwright-core' import { ITestCase } from '../framework/Typedefs' import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core'; +import { PaymentMethod } from 'framework/localTypes/Enums'; // Set the seed for consistent data generation setFakerSeedFromTestName("CashReplaceSwitchToInsuranceProgressiveNoComp"); @@ -13,6 +14,10 @@ const cashReplaceSwitchToInsuranceProgressiveNoCompData: Partial = { ...getDefaultTestData(), isCashInsuranceFlow: true, + flow: Flow.Managed, + + // CASH Client + paymentMethod: PaymentMethod.SelfPay, // Override customer details based on provided ZIP code customerDetails: { From a414436889a90f4b13a8079cb58d8633c0d96807 Mon Sep 17 00:00:00 2001 From: maguire-arman Date: Wed, 30 Jul 2025 11:30:35 -0400 Subject: [PATCH 09/11] Commits for pipeline to run Cleans up the Afterpay payment execution flow by removing unnecessary and commented-out code related to card details population. --- playwright-tests/pages/AfterpayPage.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/playwright-tests/pages/AfterpayPage.ts b/playwright-tests/pages/AfterpayPage.ts index ec6779e60..5b0acfe1a 100644 --- a/playwright-tests/pages/AfterpayPage.ts +++ b/playwright-tests/pages/AfterpayPage.ts @@ -46,9 +46,6 @@ export class AfterpayPage extends BasePage { async executeAfterpayPayment(paymentDetails: IPaymentDetails) { await this.login(paymentDetails.password!); - - //await this.populateCardDetails(paymentDetails); - await this.confirmButton.click(); } From ddce8dfe559df0eebaa918b8a625cface7d36b3e Mon Sep 17 00:00:00 2001 From: maguire-arman Date: Wed, 30 Jul 2025 11:48:55 -0400 Subject: [PATCH 10/11] Test commit 1 --- playwright-tests/pages/AfterpayPage.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/playwright-tests/pages/AfterpayPage.ts b/playwright-tests/pages/AfterpayPage.ts index 5b0acfe1a..7f54fe336 100644 --- a/playwright-tests/pages/AfterpayPage.ts +++ b/playwright-tests/pages/AfterpayPage.ts @@ -46,6 +46,7 @@ export class AfterpayPage extends BasePage { async executeAfterpayPayment(paymentDetails: IPaymentDetails) { await this.login(paymentDetails.password!); + await this.confirmButton.click(); } From 957b66ac1af8f3510ec10e664767c25e52992d0a Mon Sep 17 00:00:00 2001 From: Chris Redelinghuys Date: Wed, 30 Jul 2025 15:05:21 -0400 Subject: [PATCH 11/11] CASH-1235: Auto select if only one timeslot available --- .../schedule/time-slot-question/time-slot-question.vue | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/layouts/schedule/time-slot-question/time-slot-question.vue b/src/layouts/schedule/time-slot-question/time-slot-question.vue index 97b334821..3c83bd039 100644 --- a/src/layouts/schedule/time-slot-question/time-slot-question.vue +++ b/src/layouts/schedule/time-slot-question/time-slot-question.vue @@ -192,6 +192,9 @@ export default { this.appointmentType === AppointmentTypeStrings.IN_SHOP || this.appointmentType === AppointmentTypeStrings.DROP_OFF ) { + if (!this.selectedAnswerForTimeSlots && !this.selectedAnswerForDropOffOrInshop) { + this.autoSelectTimeSlotIfOnlyOneIsAvailable(); + } return null; // This is planned to be used again for drop-off // Wrote this to be ready for that eventuality, is untested and no HTML work done yet @@ -399,6 +402,8 @@ export default { this.setSelectedRouteCodeFromParent(); }, async setSelectedTimeSlot() { + this.selectedRouteCode = + this.selectedAnswerForTimeSlots || this.selectedAnswerForDropOffOrInshop; this.$emit( "update:modelValue", this.getSelectedTimeSlotInfoObject(this.selectedRouteCode) @@ -488,12 +493,13 @@ export default { }, autoSelectTimeSlotIfOnlyOneIsAvailable() { const numberOfOptions = this.timeSlotsForSelectedDate?.timeSlots?.length; - if (numberOfOptions === 1) { + if (this.appointmentType && numberOfOptions === 1) { if (this.availableTimeSlots.length > 0) { this.selectedAnswerForTimeSlots = this.availableTimeSlots[0].value; } else { this.selectedAnswerForDropOffOrInshop = this.answersForDropOffQuestion[0].value; } + this.setSelectedTimeSlot(); } }, addPremiumFlagToInput(routeCode) {