import { expect, type Locator, type Page } from '@playwright/test'; import { BasePage } from './BasePage'; 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'; import { time } from 'console'; import { step } from 'framework/localTypes/Step'; 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; readonly dropOffButton: Locator; readonly dateText: Locator; readonly viewMoreDatesLink: Locator; readonly appointmentDuration: Locator; readonly timeSlots: Locator; readonly allDayDropOffButton: Locator; readonly pickATimeButton: Locator; constructor(page: Page) { super(page); this.page = page; 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 }); this.dateText = this.page.locator('label.modal-title'); this.viewMoreDatesLink = this.page.getByText(/View more dates/).first(); this.appointmentDuration = this.page.locator('.duration-text-block'); 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!); 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(); } 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(); } 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 { // 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 === ServiceLocation.DropOff ? await this.dropOffButton.click() : await this.firstAvailableTime.click(); customerDetails!.apptDuration = (await this.appointmentDuration.innerText()).replace("Duration: ", ""); } async getFormattedTimeSlot(timeSlot: Locator) { const selectedTimeSlot = await timeSlot.innerText(); let formattedTimeSlot: string = ""; if (selectedTimeSlot.toLowerCase().includes("drop off")) { 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.includes("Earlybird")) { formattedTimeSlot = selectedTimeSlot.includes("Earlybird") ? "arriving between 8:00 AM - 12:00 PM" : `arriving between ${selectedTimeSlot}`; } else { formattedTimeSlot = `at ${selectedTimeSlot}`; } return formattedTimeSlot; } @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(); } }