import { expect, type Locator, type Page } from '@playwright/test'; import { BasePage } from './BasePage'; import { getTestObject } from 'framework/Typedefs'; import { ServicePackage, PaymentType, ServiceLocation, Soft, waitUntil } from 'safelite-playwright-core'; import { PaymentMethod, ProgressBarPercentages } from "framework/localTypes/Enums"; import { ITestData } from 'framework/TestData'; import { step } from 'framework/localTypes/Step'; const test = getTestObject(); export class OrderConfirmationPage extends BasePage { readonly page: Page; readonly serviceText: Locator; readonly emailText: Locator; readonly apptDateText: Locator; readonly amountDueText: Locator; readonly viewCartButton: Locator; readonly deductibleText: Locator; readonly subtotalText: Locator; readonly finalAmountDue: Locator; readonly cartServicePackageText: Locator; readonly winshieldWiper: Locator; readonly rainDefense: Locator; readonly appointmentSummarySection: Locator; readonly orderDetailsSection: Locator; readonly amountPaid: Locator; constructor(page: Page) { super(page); this.page = page; this.serviceText = this.page.locator('p', { hasText: /to service your/ }); this.emailText = this.emailText = this.page.getByText('A confirmation email was sent'); this.apptDateText = this.page.locator('[class="scheduleText"]'); this.amountDueText = this.page.getByLabel('expand cart'); this.viewCartButton = this.page.locator('#cart-dropdown-head'); this.deductibleText = this.page.locator('.deductible'); this.subtotalText = this.page.locator('.sub-total'); this.finalAmountDue = this.page.locator('div.amount-due'); this.cartServicePackageText = this.cartServicePackageText = this.page.locator('.cart-panel'); this.appointmentSummarySection = this.page.locator('.main .scheduleText, .main .add-to-calendar-text, .main .appointment-text, .main .duration-text-block'); this.orderDetailsSection = this.page.locator('.cart .vin-toggle a'); this.amountPaid = this.page.locator('.amount-paid span').nth(1); } async validateOrderConfirmationPage(testData: Partial) { // Destructure data we use const { vehicleDetails, customerDetails, servicePackage, isCashInsuranceFlow, isPolicyFound, claimDetails, paymentDetails, isUseVehicleOnPolicy, paymentMethod, isPolicyUnverified} = testData; await this.serviceText.waitFor({ state: "visible" }); Soft.expect((await this.getActualAppointmentSummary()).map(item => item.toLowerCase())).toEqual((await this.getExpectedAppointmentSummary(testData)).map(item => item.toLowerCase())); // Grab text const emailTextValue = await this.emailText.textContent(); const servicePackageValue = await this.cartServicePackageText.textContent(); const amountDueValue = await this.amountDueText.textContent(); // const deductibleTextValue = await this.deductibleText.textContent(); const subtotalTextValue = await this.subtotalText.textContent(); const finalAmountDueValue = await this.finalAmountDue.textContent(); // Extract service package price const servicePackageAmt = Number.parseFloat(servicePackageValue!.split('$')[1].replaceAll(',', '')); // General Validations Soft.expect(emailTextValue?.toLowerCase()).toContain(customerDetails!.email); // Service package validations await Soft.expect(servicePackageValue).toContain(`${servicePackage}`) if (servicePackage === ServicePackage.Premium || servicePackage === ServicePackage.Standard) { Soft.expect(servicePackageValue).toContain('New wiper blades'); } if (servicePackage === ServicePackage.Premium) { Soft.expect(servicePackageValue).toContain('Rain Repel Treatment'); } // Promo Code Validation if (paymentDetails!.promoCode) { expect.soft(servicePackageValue).toContain(`Promo code ${paymentDetails!.promoCode} applied`); }; if (paymentMethod === PaymentMethod.SelfPay || (paymentDetails?.paymentType && paymentDetails?.paymentType !== PaymentType.PayWithInsurance)) { // Cart validation for non insurance users // Extract numbers const amountDueAmt = Number.parseFloat(amountDueValue!.split('$')[1].replaceAll(',', '')); // const deductibleAmt = deductibleTextValue? Number.parseFloat(deductibleTextValue.split('$')[1].replaceAll(',', '')): 0; const subtotalAmt = Number.parseFloat(subtotalTextValue!.split('$')[1].replaceAll(',', '')); subtotalTextValue?.replaceAll(',', '') const finalAmountDueAmt = Number.parseFloat(finalAmountDueValue!.split('$')[1].replaceAll(',', '')); Soft.expect(subtotalAmt).toBeGreaterThan(0); // Soft.expect(deductibleAmt).toEqual(0); if ((paymentDetails!.paymentType === PaymentType.PayAtService || isCashInsuranceFlow) && (servicePackageAmt > 0)) { // Verify amount due > 0 Soft.expect(amountDueAmt).toBeGreaterThan(0); Soft.expect(finalAmountDueAmt).toBeGreaterThan(0); if (isPolicyUnverified && PaymentType.PayWithInsurance) { Soft.expect(finalAmountDueAmt).toContain('Verifying coverage') } } else { // Verify amount due 0 Soft.expect(amountDueAmt).toEqual(0); Soft.expect(finalAmountDueAmt).toEqual(0); } } } /*async getFormattedAppointmentDate(appointmentDate: string) { // Parse the original date let parsedAppointmentDate = new Date(`${appointmentDate}` + 'T00:00:00'); // Format the new date as a string let updatedAppointmentDate = parsedAppointmentDate.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric' }); return updatedAppointmentDate; }*/ async getFormattedAppointmentDate(appointmentDate: string) { let formattedAppointmentDate: string; switch (true) { case /^\d{4}-\d{2}-\d{2}$/.test(appointmentDate): let parsedAppointmentDate = new Date(`${appointmentDate}` + 'T00:00:00'); formattedAppointmentDate = parsedAppointmentDate.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric' }); break; case /[A-Za-z]+,\s+[A-Za-z]+\s+\d{1,2}/.test(appointmentDate): formattedAppointmentDate = appointmentDate; break; default: throw new Error('Unsupported date format: ' + appointmentDate); } return formattedAppointmentDate; } async toggleOrderDetailsSection() { await this.orderDetailsSection.click().then( async () => { await waitUntil(async () => { return (await this.page.locator('.vin-toggle.expanded').evaluate(el => window.getComputedStyle(el, ':after').transform)) === 'matrix(-1, 0, 0, -1, 0, 0)'; }); } ); } async logOrderNumber() { const sessionStorage = JSON.parse(await this.page.evaluate('sessionStorage.getItem(\'submittedState\')')); return sessionStorage.order.workOrderNumber; } async validateSettledAmountForPia() { const sessionStorage = JSON.parse(await this.page.evaluate('sessionStorage.getItem(\'submittedState\')')); const isPia = sessionStorage.order.payment.isPia; if (isPia) { const nextGenSettledAmount = Number.parseFloat(sessionStorage.order.payment.nextGenSettledAmount); Soft.expect(nextGenSettledAmount, `NextGen Settled Amount > 0. NextGenSettledAmount: ${nextGenSettledAmount}`).toBeGreaterThan(0); await this.toggleOrderDetailsSection(); let amountPaid = await this.amountPaid.textContent(); const parsedAmount = amountPaid === null ? console.log("Amount paid is null. Check the locator.") : Number.parseFloat(amountPaid.replace(/[^0-9.]/g, '')); Soft.expect(parsedAmount).toEqual(nextGenSettledAmount); } } async getActualAppointmentSummary(): Promise { let appointmentSummary: string[] = []; for (let element of await this.appointmentSummarySection.all()) { let text = (await element.textContent() || "").replaceAll(/\u00A0| /g, ' '); appointmentSummary.push(text.trim()); } return appointmentSummary; } async getExpectedAppointmentSummary(testData: Partial): Promise { const { appointmentDetails, customerDetails, vehicleDetails } = testData; let appointmentSummary: string[] = []; // Format the expected appointment date const formattedExpectedAppointmentDate = await this.getFormattedAppointmentDate(customerDetails!.apptDate!); appointmentSummary.push( appointmentDetails?.serviceLocation == ServiceLocation.Mobile ? formattedExpectedAppointmentDate + `${customerDetails!.apptTime?.replace("arriving between", "Between").replaceAll(":00", "")}` : appointmentDetails?.serviceLocation == ServiceLocation.InShop ? formattedExpectedAppointmentDate + `${customerDetails!.apptTime}` : formattedExpectedAppointmentDate + "Drop off before 9:30 AM" // : formattedExpectedAppointmentDate + "before 9:30 AM" ); appointmentSummary.push("Add to calendar"); appointmentSummary.push( appointmentDetails?.serviceLocation == ServiceLocation.Mobile ? appointmentDetails?.serviceAddress ? ("We're coming to you at" + appointmentDetails.serviceAddress.street + ", " + appointmentDetails.serviceAddress.city + ", " + appointmentDetails.serviceAddress.state + " " + appointmentDetails.serviceAddress.postalCode + "to service your " + `${vehicleDetails!.year} ${vehicleDetails!.make} ${vehicleDetails!.model}`) : "" : appointmentDetails?.shopAddress ? ("You're going to a Safelite shop at" + appointmentDetails.shopAddress + "to service your " + `${vehicleDetails!.year} ${vehicleDetails!.make} ${vehicleDetails!.model}`) : ""); appointmentSummary.push("Duration: " + customerDetails!.apptDuration!); return appointmentSummary; } @step("OrderConfirmationPage >> Validate order") async verifyOrderConfirmationPage(testData: Partial) { await this.page.waitForURL(new RegExp('(.+)confirmation'), { timeout: 60000 }); await this.validateProgressBar(ProgressBarPercentages.OrderConfirmationPage); await this.validateOrderConfirmationPage(testData); const workOrderNumber = await this.logOrderNumber(); await this.validateSettledAmountForPia(); await test.step(`Session Storage Work Order Number: ${workOrderNumber}`, async () => { console.log(`Session Storage Work Order Number: ${workOrderNumber}`); }); } }