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 { AddressForm } from './forms/AddressForm'; import { faker } from '@faker-js/faker'; import { step } from '@business-logic/types/Step'; import { ITestData } from '@business-logic/types/ITestData'; export class ServiceLocationPage extends BasePage { readonly page: Page; readonly addressForm: AddressForm; // Initial selection readonly inShopButton: Locator; readonly mobileButton: Locator; readonly dropOffButton: Locator; readonly RecalWarningMessage1: Locator; readonly RecalWarningMessage2: Locator; readonly militaryWarningMessage: Locator; // For in-shop and drop off readonly selectAShopOptions: Locator; readonly firstAppointmentButton: Locator; readonly changeZipButton: Locator; readonly updateZipTextBox: Locator; readonly saveZipButton: Locator; // For mobile readonly enterServiceAddressButton: Locator; readonly serviceAddressTextBox: Locator; readonly aptNumberTextBox: Locator; readonly cityTextBox: Locator; readonly stateDropDown: Locator; readonly zipCodeTextBox: Locator; readonly vehicleProtectedYesButton: Locator; readonly vehicleProtectedNoButton: Locator; readonly saveAddressButton: Locator; readonly repeatedClicksModalCloseButton: Locator; url = process.env['BASE_URL']! + '/fmg/?fmgPage=service-location'; constructor(page: Page) { super(page); this.page = page; this.addressForm = new AddressForm(page); // Initial selection this.inShopButton = this.page.getByText(/In-shop/); this.mobileButton = this.page.getByText(/Mobile/); 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./); this.militaryWarningMessage = this.page.locator('[class*="widget-name-AlertMilitaryBaseZipWidget"]'); // For in-shop and drop off this.selectAShopOptions = this.page.locator('[class="shop-question"]'); this.firstAppointmentButton = this.page.locator('fieldset:has(legend#chooseShop)').locator('label').first(); this.changeZipButton = this.page.locator('a:has(span.sr-only:has-text("edit zip code"))'); this.updateZipTextBox = this.page.locator('#serviceZipCode'); this.saveZipButton = this.page.getByText('Save ZIP code', { exact: true }); // For mobile this.enterServiceAddressButton = this.page.getByRole('link', { name: 'Enter your service address' }); this.serviceAddressTextBox = this.page.getByRole('textbox', { name: 'Street Address' }); this.aptNumberTextBox = this.page.getByRole('textbox', { name: 'Apt. number'}); this.cityTextBox = this.page.getByRole('textbox', { name: 'City' }); this.stateDropDown = this.page.getByRole('combobox', { name: 'State' }); this.zipCodeTextBox = this.page.getByRole('textbox', { name: 'Zip code' }); this.vehicleProtectedYesButton = this.page.locator('label').filter({ hasText: 'Yes' }).locator('div'); this.vehicleProtectedNoButton = this.page.locator('label').filter({ hasText: 'No' }).locator('div'); this.saveAddressButton = this.page.getByRole('button', { name: 'Continue' }) this.repeatedClicksModalCloseButton = this.page.locator('.QSISlider').locator('img[src*=\'close\']'); } async selectLocation(appointmentDetails: IAppointmentDetails){ switch(appointmentDetails.serviceLocation) { case AppointmentType.Mobile: await this.scheduleMobile(appointmentDetails); break; case AppointmentType.InShop: await this.scheduleInShop(appointmentDetails); break; case AppointmentType.DropOff: await this.scheduleDropOff(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()); } else { await this.firstAppointmentButton.scrollIntoViewIfNeeded().then(async () => { await this.firstAppointmentButton.click(); if (appointmentDetails) { appointmentDetails.shopAddress = await this.firstAppointmentButton.locator('.row-two').innerText(); } }); } } async scheduleMobile(appointmentDetails: IAppointmentDetails){ if (appointmentDetails.serviceAddress) { await this.mobileButton.click(); await this.enterServiceAddressButton.click(); await this.addressForm.populateAddress({ address: appointmentDetails.serviceAddress! }); if (await this.repeatedClicksModalCloseButton.isVisible()) { await this.repeatedClicksModalCloseButton.click(); } if (faker.datatype.boolean()) { await this.vehicleProtectedYesButton.check(); } else { await this.vehicleProtectedNoButton.check(); } await this.saveAddressButton.click(); } else { console.error('ServiceLocationPage >> Please supply an address') } } async scheduleDropOff(appointmentDetails?: IAppointmentDetails){ await this.dropOffButton.click(); if (appointmentDetails && appointmentDetails.shopAddress) { await this.selectAShopOptions.locator(`[buttonbodycopy="${appointmentDetails.shopAddress}"]`).check(); } else { // await this.firstAppointmentButton.click(); // await this.clickWithRetry(this.firstAppointmentButton, this.page); await this.firstAppointmentButton.scrollIntoViewIfNeeded().then(async () => { await this.firstAppointmentButton.click(); if (appointmentDetails) { appointmentDetails.shopAddress = await this.firstAppointmentButton.locator('.row-two').innerText(); } }); } } async validateRecalWarning(){ await expect(this.RecalWarningMessage1).toBeVisible(); await expect(this.RecalWarningMessage2).toBeVisible(); } @step("ServiceLocationPage >> Select service location: ") async handleServiceLocationPage(testData: Partial) { const { appointmentDetails } = testData; await this.selectLocation(appointmentDetails!); await this.nextPage(); } }