import { expect, type Locator, type Page } from '@playwright/test'; import { BasePage } from './BasePage'; import { ServicePackage, VehicleDamage } from '@business-logic/types/Enums'; import { PaymentMethod } from '@business-logic/types/Enums'; import { step } from '@business-logic/types/Step'; import { ITestData } from '@business-logic/types/ITestData'; export class ServicePackagesPage extends BasePage { readonly page: Page; readonly standardPackageButton: Locator; readonly premiumPackageButton: Locator; readonly glassOnlyButton: Locator; readonly payOnMyOwnButton: Locator; readonly paywithInsuranceButton: Locator; readonly iHavePromoCodeButton: Locator; //Your quote is almost ready modal readonly skipQuoteEmailButton: Locator; readonly emailInput: Locator; readonly getMyQuoteButton: Locator; readonly closeButton: Locator; url = process.env['BASE_URL']! + '/fmg/?fmgPage=quote'; //Enter a promo code modal readonly promoCodeTextbox: Locator; readonly applyPromoButton: Locator; readonly repeatedClicksModalCloseButton: Locator; constructor(page: Page) { super(page); this.page = page; this.standardPackageButton = this.page.getByText('Standard'); this.premiumPackageButton = this.page.getByText('Premium'); this.glassOnlyButton = this.page.getByText('Glass service only', { exact: true }); this.payOnMyOwnButton = this.page.locator('label').filter({ hasText: 'Pay on my own' }).locator('div'); this.paywithInsuranceButton = this.page.locator('label').filter({ hasText: 'Pay with insurance' }).locator('div'); this.iHavePromoCodeButton = this.page.getByRole('link', { name: 'I have a promo code' }); this.skipQuoteEmailButton = this.page.getByRole('button', { name: 'Skip' }); this.emailInput = this.page.getByRole('textbox', { name: 'Enter your email address' }); this.getMyQuoteButton = this.page.getByRole('button', { name: 'Get my quote' }); this.closeButton = this.page. getByRole('dialog').locator('button').filter({ hasText: 'Close' }); this.promoCodeTextbox = this.page.getByLabel('Enter a promo code'); this.applyPromoButton = this.page.getByRole('button', { name: 'Apply promo code' }); this.repeatedClicksModalCloseButton = this.page.locator('.QSISlider').locator('img[src*=\'close\']'); // this.validateURL(this.url); } async selectPaymentMethod(method: PaymentMethod): Promise { const locators = { [PaymentMethod.Insurance]: this.paywithInsuranceButton, [PaymentMethod.SelfPay]: this.payOnMyOwnButton } await locators[method].click(); } async selectServicePackage(servicePackage: ServicePackage): Promise { const locators = { [ServicePackage.GlassOnly]: this.glassOnlyButton, [ServicePackage.Premium]: this.premiumPackageButton, [ServicePackage.Standard]: this.standardPackageButton, } if (servicePackage != null) { await locators[servicePackage].click(); } } async handleQuotePopup(email?: string): Promise { await this.emailInput.waitFor({ state: 'visible' }); if (await this.emailInput.isVisible()) { if (email) { await this.emailInput.fill(email); await this.getMyQuoteButton.click(); if (await this.repeatedClicksModalCloseButton.isVisible()) { await this.repeatedClicksModalCloseButton.click(); } await this.page.waitForTimeout(3000); await this.closeButton.click(); } else { await this.skipQuoteEmailButton.click(); } } } async enterPromo(promoCode: string): Promise { await this.iHavePromoCodeButton.click(); await this.promoCodeTextbox.fill(promoCode); await this.applyPromoButton.click(); } async verifyCanNotRecal(): Promise { // Get Vuex state from localStorage const vuexState = JSON.parse(await this.page.evaluate('localStorage.getItem(\'vuex\')')); // Validate in the backend to make sure the can safelite recalibrate data is correct if (vuexState.order?.lineItems?.glassParts?.length > 0) { for (const glassPart of vuexState.order.lineItems.glassParts) { await expect(glassPart.canSafeliteRecalibrate).toBe(false); await expect(glassPart.requiresRecalibration).toBe(true); } return true; } return false; } async verifyDynamicRecal(): Promise { // Get Vuex state from localStorage const vuexState = JSON.parse(await this.page.evaluate('localStorage.getItem(\'vuex\')')); // Validate in the backend to make sure the dynamic recalibration part is present if (vuexState.order?.lineItems?.glassParts?.length > 0) { const hasDynamicRecalPart = vuexState.order.lineItems.glassParts.some(glassPart => glassPart.childParts.some(childPart => childPart.partNumber.includes("RECAL DYNAMIC") ) ); await expect(hasDynamicRecalPart).toBe(true); console.log("Recal part line item is verified"); } else { throw new Error("No glass parts found in the order"); } } async verifyIsRepair(isRepair: boolean, vehicleDamage: VehicleDamage[]): Promise { // Get Vuex state from localStorage const vuexState = JSON.parse(await this.page.evaluate('localStorage.getItem(\'vuex\')')); // Validate in the backend to make sure isRepair is aligned with the data if (isRepair) { await expect(vuexState.order.damage.isRepair).toBe(true); // Check the number of chips based on vehicleDamage array if (vehicleDamage.includes(VehicleDamage.WindshieldOneChip)) { await expect(vuexState.order.damage.numberOfChips).toBe(1); } if (vehicleDamage.includes(VehicleDamage.WindshieldTwoChips)) { await expect(vuexState.order.damage.numberOfChips).toBe(2); } if (vehicleDamage.includes(VehicleDamage.WindshieldThreeChips)) { await expect(vuexState.order.damage.numberOfChips).toBe(3); } } else { await expect(vuexState.order.damage.isRepair).toBe(false); } } async verifyVehicleParts(vehicleDamage: VehicleDamage[]): Promise { // Get Vuex state from localStorage const vuexState = JSON.parse(await this.page.evaluate('localStorage.getItem(\'vuex\')')); // Validate the presence of specific parts in the glassParts array const glassParts = vuexState.order?.lineItems?.glassParts; if (glassParts?.length > 0) { for (const partType of vehicleDamage) { const hasPartType = glassParts.some(glassPart => glassPart.partType === partType); await expect(hasPartType, `Expected part type: ${partType}`).toBe(true); } } else { throw new Error("No glass parts found in the order"); } } async verifyOEMPart(): Promise { // Get Vuex state from localStorage const vuexState = JSON.parse(await this.page.evaluate('localStorage.getItem(\'vuex\')')); // Validate the presence of an OEM part if (vuexState.order?.lineItems?.glassParts?.length > 0) { const firstGlassPartNumber = vuexState.order.lineItems.glassParts[0].partNumber; await expect(firstGlassPartNumber.includes("OEM")).toBe(true); } else { throw new Error("No glass parts found in the order"); } } @step("ServicePackagePage >> Select Payment Method and Service Type: ") async handleServicePackagePage(testData: Partial) { const { customerDetails, paymentMethod, servicePackage, promoCode, canNotRecal, dynamicRecal, hasOemEndorsement, vehicleDamage } = testData; // Define repair damage types (vs. replacement types) const repairTypes: VehicleDamage[] = [ VehicleDamage.WindshieldOneChip, VehicleDamage.WindshieldTwoChips, VehicleDamage.WindshieldThreeChips ]; // Determine if we're replacing or repairing const isReplace = !repairTypes.some(damageType => { return vehicleDamage!.includes(damageType); }); await this.handleQuotePopup(customerDetails!.email!); await this.selectPaymentMethod(paymentMethod!); await this.selectServicePackage(servicePackage!); // Enter promo code if (promoCode) { await this.enterPromo(promoCode); } // Backend Validations // Validate backend for can not recal if applicable if (canNotRecal) { await this.verifyCanNotRecal(); } // Validate backend for dynamic recal if applicable if (dynamicRecal) { await this.verifyDynamicRecal(); } // Validate backend for repair info (including chip verification) await this.verifyIsRepair(!isReplace, vehicleDamage!); if (isReplace) { // Validate backend for parts info await this.verifyVehicleParts(vehicleDamage!); } // Validate backend for OEM endorsement if (hasOemEndorsement) { await this.verifyOEMPart(); } await this.nextPage(); } }