import test, { expect, type Locator, type Page } from '@playwright/test'; import { BasePage } from './BasePage'; import { ICustomerDetails, IVehicleDetails } from '@business-logic/types/CustomerDetails'; import { PaymentType, ServicePackage } from '@business-logic/types/Enums'; import { ITestData } from '@business-logic/types/ITestData'; export class OrderConfirmationPage extends BasePage { readonly page: Page; readonly successCheckmark: Locator; readonly successHeader: Locator; readonly serviceText: Locator; readonly workOrderNumberText: Locator; readonly confirmationText: Locator; readonly appointmentTypeText: Locator; readonly apptDetailsText: Locator; readonly deductibleText: Locator; readonly subtotalText: Locator; readonly totalAmountDueText: Locator; readonly amountPaidText: Locator; readonly finalAmountDue: Locator; readonly cartLineItemsText: Locator; readonly unverifiedCoverageDeductibleText: Locator; issPageValue = 'order-confirmation'; constructor(page: Page) { super(page); this.page = page; this.successCheckmark = this.page.locator('img[src*="-checkmark.svg"]'); this.successHeader = this.page.locator('[class="header-text"]'); this.confirmationText = this.page.locator('[class="subheader-text"]', { hasText: 'Your appointment is on Safelite\'s schedule and your confirmation email is on the way.' }); this.serviceText = this.page.locator('[class="service-description confirmation-section"]'); this.workOrderNumberText = this.page.locator('[class="work-order-description"]'); this.appointmentTypeText = this.page.locator('[class="appointment-title"]'); this.apptDetailsText = this.page.locator('[class="appointment-details confirmation-section"]'); // Order details section this.deductibleText = this.page.locator('span#deductible-value, span.deductible-value'); this.subtotalText = this.page.locator('#subtotal-value'); this.totalAmountDueText = this.page.locator('#total-value'); this.amountPaidText = this.page.locator('#amount-paid-value'); this.finalAmountDue = this.page.locator('#bottom-amount-due-value'); this.cartLineItemsText = this.page.locator('.cart-item-list'); this.unverifiedCoverageDeductibleText = this.page.locator('span#deductible-value', { hasText: 'Verifying coverage' }); } async validateOrderConfirmationPage(testData: Partial) { // Destructure data we use const { vehicleDetails, customerDetails, servicePackage, isItac, isNoComp, isPolicyFound, claimDetails, paymentDetails, isUseVehicleOnPolicy } = testData; // Grab text const serviceTextValue = await this.serviceText.textContent(); const apptDetailsValue = await this.apptDetailsText.textContent(); const appointmentTypeValue = await this.appointmentTypeText.textContent(); // Derived conditions const isItacOrNoComp = !!(isItac || isNoComp); const isUnverified = !isPolicyFound; const isUnverifiedPolicyAfterVehicleLookup = testData.isUnverifiedPolicyAfterVehicleLookup === true; const hasPremiumOrStandard = (claimDetails!.policyDeductible >= 0 || !isPolicyFound) && (servicePackage === ServicePackage.Premium || servicePackage === ServicePackage.Standard); const payAtService = (claimDetails!.policyDeductible >= 0 && paymentDetails?.paymentType === PaymentType.PayAtService) || !paymentDetails?.paymentType; const paidWithPIA = claimDetails!.policyDeductible >= 0 && paymentDetails?.paymentType !== PaymentType.PayAtService; // Helper: grab textContent only when the element is expected on-screen const textIf = async (condition: boolean, locator: Locator) => condition ? await locator.textContent() : null; // Grab text content for elements that are expected on-screen const lineItemsValue = await textIf(hasPremiumOrStandard, this.cartLineItemsText); const deductibleTextValue = await textIf(!isItacOrNoComp, this.deductibleText); const subtotalTextValue = await textIf((hasPremiumOrStandard || isItacOrNoComp) && !isUnverified && !isUnverifiedPolicyAfterVehicleLookup, this.subtotalText); const totalAmountDueValue = await textIf((hasPremiumOrStandard && !payAtService) || (isItacOrNoComp && !payAtService) && !isUnverified && !isUnverifiedPolicyAfterVehicleLookup, this.totalAmountDueText); const amountPaidTextValue = await textIf((hasPremiumOrStandard && !payAtService) || (isItacOrNoComp && !payAtService) && !isUnverified && !isUnverifiedPolicyAfterVehicleLookup, this.totalAmountDueText); const finalAmountDueValue = await textIf(hasPremiumOrStandard || isItacOrNoComp || isUnverified || isUnverifiedPolicyAfterVehicleLookup, this.finalAmountDue); const confirmationTextValue = await this.confirmationText.textContent(); // General Validations expect.soft(this.serviceText).toBeVisible();; expect.soft(this.successHeader).toBeVisible(); expect.soft(confirmationTextValue).toContain('Your appointment is on Safelite\'s schedule and your confirmation email is on the way.'); expect.soft(serviceTextValue).toContain(`${vehicleDetails!.year} ${vehicleDetails!.make} ${vehicleDetails!.model}`); // Validate order number and appointment type header await this.validateOrderNumber(); await this.validateAppointmentTypeHeader(); // Validate line items if (servicePackage === ServicePackage.Premium) { expect.soft(lineItemsValue).toContain('Front advanced beam blades'); expect.soft(lineItemsValue).toContain('Safelite Rain Repellent Treatment'); } else { if (servicePackage === ServicePackage.Standard) { expect.soft(lineItemsValue).toContain('Front advanced beam blades'); } } if (isPolicyFound && (claimDetails!.policyDeductible === 0 && (servicePackage === ServicePackage.GlassOnly))) { const zeroDeductibleText = await this.deductibleText.textContent(); expect.soft(zeroDeductibleText).toEqual('$0.00'); } else if (isPolicyFound && claimDetails!.policyDeductible > 0 && !(isItac || isNoComp) && servicePackage === ServicePackage.GlassOnly){ expect.soft(deductibleTextValue).not.toBeNull(); const deductibleAmt = deductibleTextValue ? Number.parseFloat(deductibleTextValue.split('$')[1].replaceAll(',', '')) : 0; expect.soft(deductibleAmt).toEqual(claimDetails!.policyDeductible); } else if ((isPolicyFound || isItac || (isNoComp && paymentDetails!.paymentType === PaymentType.PayAtService)) && !isUnverifiedPolicyAfterVehicleLookup && finalAmountDueValue !== null && subtotalTextValue !== null) { // Extract numbers const deductibleAmt = deductibleTextValue ? Number.parseFloat(deductibleTextValue.split('$')[1].replaceAll(',', '')) : null; const subtotalAmt = subtotalTextValue ? Number.parseFloat(subtotalTextValue!.split('$')[1].replaceAll(',', '')) : null; //const totalAmountDueAmt = Number.parseFloat(totalAmountDueValue!.split('$')[1].replaceAll(',', '')); const finalAmountDueAmt = finalAmountDueValue ? Number.parseFloat(finalAmountDueValue!.split('$')[1].replaceAll(',', '')) : null; if (!(isItac || isNoComp)) { expect.soft(deductibleAmt).toEqual(claimDetails!.policyDeductible); } else { expect.soft(subtotalAmt).toBeGreaterThan(0); //expect.soft(deductibleAmt).toEqual(0); } if ((paymentDetails!.paymentType === PaymentType.PayAtService) && (claimDetails!.policyDeductible >= 0 && (servicePackage === ServicePackage.Standard || servicePackage === ServicePackage.Premium))) { expect.soft(finalAmountDueAmt).toBeGreaterThan(0); } else { const totalAmountDueAmt = Number.parseFloat(totalAmountDueValue!.split('$')[1].replaceAll(',', '')); const amountPaidValue = Number.parseFloat(amountPaidTextValue!.split('$')[1].replaceAll(',', '')); // Verify amount due 0 usually when PIA is selected expect.soft(amountPaidValue).toEqual(totalAmountDueAmt); expect.soft(finalAmountDueAmt).toEqual(0); } } else { // essential flows will have deductible and amount due as "Verifying coverage" const verifyingCoverageText = this.unverifiedCoverageDeductibleText; expect.soft(verifyingCoverageText).toContainText('Verifying coverage'); expect.soft(finalAmountDueValue).toEqual('Verifying coverage'); } } async validateOrderNumber() { const sessionStorage = JSON.parse(await this.page.evaluate('sessionStorage.getItem(\'submittedOrder\')')); const workOrderNumber = sessionStorage.workOrderNumber; const workOrderNumberValue = await this.workOrderNumberText.textContent(); expect.soft(workOrderNumberValue).toEqual(workOrderNumber); } async validateAppointmentTypeHeader() { const sessionStorage = JSON.parse(await this.page.evaluate('sessionStorage.getItem(\'submittedOrder\')')); const appointmentType = sessionStorage.serviceLocation.appointmentType; const appointmentTypeValue = await this.appointmentTypeText.textContent(); const apptDetailsValue = await this.apptDetailsText.textContent(); if (appointmentTypeValue?.includes('going to a Safelite shop') && apptDetailsValue?.includes('Drop off before 9:30 AM')) { expect.soft(appointmentType).toEqual('Dropoff'); } else if (appointmentTypeValue?.includes('going to a Safelite shop')) { expect.soft(appointmentType).toEqual('Inshop'); } else { expect.soft(appointmentType).toEqual('Mobile'); } } async validateURL() { let pass = false; let currentUrl = this.page.url(); pass = currentUrl.includes(this.issPageValue); while(!pass && currentUrl.includes('payment')) { await this.waitForURLToChange(currentUrl); currentUrl = this.page.url(); pass = currentUrl.includes(this.issPageValue); } const isBailout = currentUrl.includes('bailout-page'); if(!pass && !isBailout) { await this.waitForURLToChange(currentUrl); currentUrl = this.page.url(); } expect(currentUrl).toContain(this.issPageValue); } }