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] 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