diff --git a/playwright-tests/.env.dev b/playwright-tests/.env.dev index 8c064ba62..c89292674 100644 --- a/playwright-tests/.env.dev +++ b/playwright-tests/.env.dev @@ -23,4 +23,6 @@ CCIS_API_URL="https://api.test.belronus.io" ADMIN_SERVICE_API_URL="https://issadminapi.dev.sagaws.net/iss-admin/api/v1/" # API authentication (replace with actual value when running tests) -CCIS_API_AUTH="undefined" \ No newline at end of file +CCIS_API_AUTH="undefined" + +SKIP_CONTENT_SITE=false \ No newline at end of file diff --git a/playwright-tests/business-logic/types/CustomerDetails.ts b/playwright-tests/business-logic/types/CustomerDetails.ts index bb1f08b96..0ea0e9605 100644 --- a/playwright-tests/business-logic/types/CustomerDetails.ts +++ b/playwright-tests/business-logic/types/CustomerDetails.ts @@ -9,6 +9,8 @@ export interface ICustomerDetails { notes: string, address: IAddress, apptDate?: string, + apptTime?: string, + apptDuration?: string, packagePrice?: string } diff --git a/playwright-tests/business-logic/types/ITestPages.ts b/playwright-tests/business-logic/types/ITestPages.ts index 77b9689e7..d8ba7e5bd 100644 --- a/playwright-tests/business-logic/types/ITestPages.ts +++ b/playwright-tests/business-logic/types/ITestPages.ts @@ -27,6 +27,7 @@ import { CoverageStatementPage } from "../../pages/CoverageStatementPage" import { VerifyDetailsPage } from "../../pages/VerifyDetailsPage" import { EndorsementsPage } from "../../pages/EndorsementsPage" import { PolicyDriverPage } from "../../pages/PolicyDriverPage" +import { ServiceZipPage } from "../../pages/ServiceZipPage" // File containing interface for all Page Object Models export default interface ITestPages { @@ -48,7 +49,8 @@ export default interface ITestPages { recalibrationInfoPage: RecalibrationInfoPage, schedulePage: SchedulePage, serviceLocationPage: ServiceLocationPage, - servicePackagePage: ServicePackagesPage, + servicePackagesPage: ServicePackagesPage, + serviceZipPage: ServiceZipPage, vehicleDamagePage: VehicleDamagePage, vehicleLookupAddressPage: VehicleLookupAddressPage, vehicleLookupLicensePage: VehicleLookupLicensePage, diff --git a/playwright-tests/business-logic/types/Step.ts b/playwright-tests/business-logic/types/Step.ts new file mode 100644 index 000000000..693e34da2 --- /dev/null +++ b/playwright-tests/business-logic/types/Step.ts @@ -0,0 +1,27 @@ +import { test } from '@business-logic/types/Test'; + +export type Context = { + kind: string; + name: string | symbol; + access: { + get?(): unknown; + set?(value: unknown): void; + has?(value: unknown): boolean; + }; + private?: boolean; + static?: boolean; + addInitializer?(initializer: () => void): void; +}; + +export type Method = (...args: any[]) => any; + +export function step(label: string): Method { + return (value: T, context: Context): Method => { + return function (this: any, ...args: any[]): Promise { + // console.log(`Method ${value.name} has been decorated`, context, args, label); + return test.step(label, async () => { + return value.call(this, ...args); + }); + }; + }; +} \ No newline at end of file diff --git a/playwright-tests/business-logic/types/TestCase.ts b/playwright-tests/business-logic/types/TestCase.ts index 5fe3ae857..ab45a6078 100644 --- a/playwright-tests/business-logic/types/TestCase.ts +++ b/playwright-tests/business-logic/types/TestCase.ts @@ -41,6 +41,7 @@ import { CoverageStatementPage } from "../../pages/CoverageStatementPage"; import { VerifyDetailsPage } from "../../pages/VerifyDetailsPage"; import { EndorsementsPage } from "../../pages/EndorsementsPage"; import { PolicyDriverPage } from "../../pages/PolicyDriverPage"; +import { ServiceZipPage } from "../../pages/ServiceZipPage"; // File for Test Case Class @@ -164,7 +165,8 @@ export default class TestCase extends DisposableBase implements ITestCase { recalibrationInfoPage: new RecalibrationInfoPage(page), schedulePage: new SchedulePage(page), serviceLocationPage: new ServiceLocationPage(page), - servicePackagePage: new ServicePackagesPage(page), + servicePackagesPage: new ServicePackagesPage(page), + serviceZipPage: new ServiceZipPage(page), vehicleDamagePage: new VehicleDamagePage(page), vehicleLookupAddressPage: new VehicleLookupAddressPage(page), vehicleLookupLicensePage: new VehicleLookupLicensePage(page), diff --git a/playwright-tests/pages/CCPolicyInfoPage.ts b/playwright-tests/pages/CCPolicyInfoPage.ts index 42ee3632b..7151e3c06 100644 --- a/playwright-tests/pages/CCPolicyInfoPage.ts +++ b/playwright-tests/pages/CCPolicyInfoPage.ts @@ -2,6 +2,8 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { IClaimDetails, ICustomerDetails } from '@business-logic/types/CustomerDetails'; import { InsuranceBasePage } from './InsuranceBasePage'; import { STATE_ABBREVIATIONS } from '@business-logic/constants/StateAbbreviation'; +import { ITestData } from '@business-logic/types/ITestData'; +import { step } from '@business-logic/types/Step'; export class CCPolicyInfoPage extends InsuranceBasePage { readonly policyNumber: Locator; @@ -70,4 +72,12 @@ export class CCPolicyInfoPage extends InsuranceBasePage { throw error; } } + + @step("CCPolicyInfoPage >> Fill out claim information") + async handleCCPolicyInfoPage(testData: Partial) { + const { customerDetails, claimDetails } = testData; + + await this.populatePage(customerDetails!, claimDetails!, await this.hasCityInfo()); + await this.nextPage(); + } } \ No newline at end of file diff --git a/playwright-tests/pages/CapabilityQuestionsPage.ts b/playwright-tests/pages/CapabilityQuestionsPage.ts index e2b6260a3..092a424f3 100644 --- a/playwright-tests/pages/CapabilityQuestionsPage.ts +++ b/playwright-tests/pages/CapabilityQuestionsPage.ts @@ -1,5 +1,7 @@ import { Page } from "@playwright/test"; import { PartQuestionsPage } from "./PartQuestionPage"; +import { step } from "@business-logic/types/Step"; +import { ITestData } from "@business-logic/types/ITestData"; export default class CapabilityQuestionsPage extends PartQuestionsPage { url = process.env['BASE_URL']! + '/fmg/?fmgPage=capability-questions'; @@ -7,4 +9,18 @@ export default class CapabilityQuestionsPage extends PartQuestionsPage { constructor(page: Page) { super(page); } + + @step("CapabilityQuestionsPage >> Select Capability Questions: ") + async handleCapabilityQuestionsPage(testCase: Partial) { + const { capabilityQuestions } = testCase; + + // Validate the capability questions are on the page + await this.validatePartQuestions(capabilityQuestions!); + + // Select the capability question responses + await this.selectPartQuestionResponses(capabilityQuestions!); + + // Proceed to the next page + await this.nextPage(); + } } \ No newline at end of file diff --git a/playwright-tests/pages/ContactDetailsPage.ts b/playwright-tests/pages/ContactDetailsPage.ts index 0b10be42a..2641f6436 100644 --- a/playwright-tests/pages/ContactDetailsPage.ts +++ b/playwright-tests/pages/ContactDetailsPage.ts @@ -1,6 +1,8 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { BasePage } from './BasePage'; import { ICustomerDetails } from '@business-logic/types/CustomerDetails'; +import { step } from '@business-logic/types/Step'; +import { ITestData } from '@business-logic/types/ITestData'; export class ContactDetailsPage extends BasePage { readonly page: Page; @@ -39,4 +41,11 @@ export class ContactDetailsPage extends BasePage { await this.notesTextBox.fill(notes); } } + + @step("ContactDetailsPage >> Enter contact details: ") + async handleContactDetailsPage(testData: Partial) { + const { customerDetails } = testData; + await this.enterContactDetails(customerDetails!); + await this.nextPage(); + } } \ No newline at end of file diff --git a/playwright-tests/pages/CoverageStatementPage.ts b/playwright-tests/pages/CoverageStatementPage.ts index a140604f7..7085c6794 100644 --- a/playwright-tests/pages/CoverageStatementPage.ts +++ b/playwright-tests/pages/CoverageStatementPage.ts @@ -1,6 +1,8 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { InsuranceBasePage } from './InsuranceBasePage'; import { IClaimDetails } from '@business-logic/types/CustomerDetails'; +import { step } from '@business-logic/types/Step'; +import { ITestData } from '@business-logic/types/ITestData'; export class CoverageStatementPage extends InsuranceBasePage { readonly page: Page; @@ -54,5 +56,12 @@ export class CoverageStatementPage extends InsuranceBasePage { async validateUnverifiedText(){ await expect(this.verfiyingCoverageText).toBeEnabled(); } - + + @step("CoverageStatementPage >> Next page: ") + async handleCoverageStatementPage(testData: Partial) { + const { claimDetails } = testData; + + await this.validateDeductibleAmount(claimDetails!); + await this.nextPage(); + } } \ No newline at end of file diff --git a/playwright-tests/pages/DuplicateCheckPage.ts b/playwright-tests/pages/DuplicateCheckPage.ts index 58d5463dd..e5f44492c 100644 --- a/playwright-tests/pages/DuplicateCheckPage.ts +++ b/playwright-tests/pages/DuplicateCheckPage.ts @@ -1,5 +1,7 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { InsuranceBasePage } from './InsuranceBasePage'; +import { ITestData } from '@business-logic/types/ITestData'; +import { step } from '@business-logic/types/Step'; export class DuplicateCheckPage extends InsuranceBasePage { readonly page: Page; @@ -18,4 +20,9 @@ export class DuplicateCheckPage extends InsuranceBasePage { await this.page.waitForLoadState(); } + @step("DuplicateCheckPage >> Start a new claim: ") + async handleDuplicateCheckPage(testCase: Partial) { + await this.startNewClaim(); + await this.nextPage(); + } } \ No newline at end of file diff --git a/playwright-tests/pages/EndorsementsPage.ts b/playwright-tests/pages/EndorsementsPage.ts index 08d69e4e3..3b63dcac0 100644 --- a/playwright-tests/pages/EndorsementsPage.ts +++ b/playwright-tests/pages/EndorsementsPage.ts @@ -3,6 +3,8 @@ import { BasePage } from './BasePage'; import { IEndorsementDetails } from '@business-logic/types/CustomerDetails'; import { EndorsementType } from '@business-logic/types/Enums'; import { InsuranceBasePage } from './InsuranceBasePage'; +import { step } from '@business-logic/types/Step'; +import { ITestData } from '@business-logic/types/ITestData'; export class EndorsementsPage extends InsuranceBasePage { readonly page: Page; @@ -52,4 +54,12 @@ export class EndorsementsPage extends InsuranceBasePage { } } } + + @step("EndorsementsPage >> Select endorsements: ") + async handleEndorsementsPage(testData: Partial) { + const { endorsements } = testData; + await this.verifyEndorsements(endorsements!); + await this.selectEndorsements(endorsements!); + await this.nextPage(); + } } \ No newline at end of file diff --git a/playwright-tests/pages/EstimatePage.ts b/playwright-tests/pages/EstimatePage.ts index 141fa73a6..2beb507b3 100644 --- a/playwright-tests/pages/EstimatePage.ts +++ b/playwright-tests/pages/EstimatePage.ts @@ -5,6 +5,8 @@ import { IVehicleDetails } from '@business-logic/types/CustomerDetails'; import { VinLookupPage } from './VinLookupPage'; import { VehicleLookupAddressPage } from './VehicleLookupAddressPage'; import { VehicleLookupLicensePage } from './VehicleLookupLicensePage'; +import { step } from '@business-logic/types/Step'; +import { ITestData } from '@business-logic/types/ITestData'; export class EstimatePage extends BasePage { readonly page: Page; @@ -20,7 +22,7 @@ export class EstimatePage extends BasePage { constructor(page: Page) { super(page); this.page = page; - this.vinLookupButton = page.getByLabel('Provide my VIN', { exact: true }); + this.vinLookupButton = page.locator('label').filter({ hasText: 'Provide my VIN' }).locator('div'); this.zipLookupButton = page.locator('label').filter({ hasText: 'I\'d rather not share my VIN' }).locator('div'); this.addressLookupButton = page.getByLabel('Provide my home address', { exact: true }); this.licenseLookupButton = page.getByLabel('Provide my license plate #', { exact: true }); @@ -68,4 +70,10 @@ export class EstimatePage extends BasePage { async selectZipLookup(){ await this.zipLookupButton.click(); } + + @step("EstimatePage >> Select Lookup Type") + async handleEstimatePage(testData: Partial) { + const { vehicleDetails } = testData; + await this.vehicleLookup(vehicleDetails!); + } } diff --git a/playwright-tests/pages/InsuranceCompanyPage.ts b/playwright-tests/pages/InsuranceCompanyPage.ts index 6bc125225..e2d63ec2d 100644 --- a/playwright-tests/pages/InsuranceCompanyPage.ts +++ b/playwright-tests/pages/InsuranceCompanyPage.ts @@ -1,6 +1,9 @@ import { type Locator, type Page } from '@playwright/test'; import { BasePage } from './BasePage'; import { IClaimDetails } from '@business-logic/types/CustomerDetails'; +import { step } from '@business-logic/types/Step'; +import TestCase from '@business-logic/types/TestCase'; +import { ITestData } from '@business-logic/types/ITestData'; export class InsuranceCompanyPage extends BasePage { @@ -44,4 +47,12 @@ export class InsuranceCompanyPage extends BasePage { } } } + + @step("InsuranceCompanyPage >> Enter insurance company: ") + async handleInsuranceCompanyPage(testData: Partial) { + const { claimDetails } = testData; + + await this.enterInsuranceCompany(claimDetails!.client!); + await this.nextPage(); + } } \ No newline at end of file diff --git a/playwright-tests/pages/MoldingQuestionsPage.ts b/playwright-tests/pages/MoldingQuestionsPage.ts index 3221a35e2..5671900ff 100644 --- a/playwright-tests/pages/MoldingQuestionsPage.ts +++ b/playwright-tests/pages/MoldingQuestionsPage.ts @@ -1,5 +1,7 @@ import { Page } from "@playwright/test"; import { PartQuestionsPage } from "./PartQuestionPage"; +import { step } from "@business-logic/types/Step"; +import { ITestData } from "@business-logic/types/ITestData"; export default class MoldingQuestionsPage extends PartQuestionsPage { url = process.env['BASE_URL']! + '/fmg/?fmgPage=molding-questions'; @@ -7,4 +9,12 @@ export default class MoldingQuestionsPage extends PartQuestionsPage { constructor(page: Page) { super(page); } + + @step("MoldingQuestionsPage >> Select Molding Questions: ") + async handleMoldingQuestionsPage(testCase: Partial) { + const { moldingQuestions } = testCase; + await this.validatePartQuestions(moldingQuestions!); + await this.selectPartQuestionResponses(moldingQuestions!); + await this.nextPage(); + } } \ No newline at end of file diff --git a/playwright-tests/pages/OrderConfirmationPage.ts b/playwright-tests/pages/OrderConfirmationPage.ts index c8b5f63f6..1c6158c68 100644 --- a/playwright-tests/pages/OrderConfirmationPage.ts +++ b/playwright-tests/pages/OrderConfirmationPage.ts @@ -1,8 +1,10 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { BasePage } from './BasePage'; +import { test } from '@business-logic/types/Test'; import { ICustomerDetails, IVehicleDetails } from '@business-logic/types/CustomerDetails'; -import { ServicePackage, PaymentType, PaymentMethod } from '@business-logic/types/Enums'; +import { ServicePackage, PaymentType, PaymentMethod, AppointmentType } from '@business-logic/types/Enums'; import { ITestData } from '@business-logic/types/ITestData'; +import { step } from '@business-logic/types/Step'; export class OrderConfirmationPage extends BasePage { readonly page: Page; @@ -17,6 +19,7 @@ export class OrderConfirmationPage extends BasePage { readonly cartServicePackageText: Locator; readonly winshieldWiper: Locator; readonly rainDefense: Locator; + readonly appointmentSummarySection: Locator; url = process.env['BASE_URL']! + '/fmg/?fmgPage=confirmation'; @@ -34,6 +37,7 @@ export class OrderConfirmationPage extends BasePage { 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.validateURL(this.url); } @@ -42,10 +46,10 @@ export class OrderConfirmationPage extends BasePage { const { vehicleDetails, customerDetails, servicePackage, promoCode, isPolicyFound, claimDetails, paymentDetails, isUseVehicleOnPolicy, paymentMethod } = testData; await this.serviceText.waitFor({ state: "visible" }); + + expect.soft((await this.getActualAppointmentSummary()).map(item => item.toLowerCase())).toEqual((await this.getExpectedAppointmentSummary(testData)).map(item => item.toLowerCase())); // Grab text - const serviceTextValue = await this.serviceText.textContent(); - const apptDateValue = await this.apptDateText.textContent(); const emailTextValue = await this.emailText.textContent(); const servicePackageValue = await this.cartServicePackageText.textContent(); const amountDueValue = await this.amountDueText.textContent(); @@ -57,8 +61,6 @@ export class OrderConfirmationPage extends BasePage { const servicePackageAmt = Number.parseFloat(servicePackageValue!.split('$')[1].replaceAll(',', '')); // General Validations - expect.soft(serviceTextValue).toContain(`${vehicleDetails!.year} ${vehicleDetails!.make} ${vehicleDetails!.model}`); - expect.soft(apptDateValue).toContain(customerDetails!.apptDate); expect.soft(emailTextValue).toContain(customerDetails!.email); // Service package validations @@ -123,9 +125,63 @@ export class OrderConfirmationPage extends BasePage { } } + 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 logOrderNumber() { const sessionStorage = JSON.parse(await this.page.evaluate('sessionStorage.getItem(\'submittedState\')')); - return sessionStorage.workOrderNumber; + return sessionStorage.order.workOrderNumber; } - + + 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 == AppointmentType.Mobile + ? formattedExpectedAppointmentDate + `${customerDetails!.apptTime?.replace("arriving between", "Between").replaceAll(":00", "")}` + : appointmentDetails?.serviceLocation == AppointmentType.InShop + ? formattedExpectedAppointmentDate + `${customerDetails!.apptTime}` + : formattedExpectedAppointmentDate + "Drop off before 9:30 AM" + ); + appointmentSummary.push("Add to calendar"); + appointmentSummary.push( + appointmentDetails?.serviceLocation == AppointmentType.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.validateOrderConfirmationPage(testData); + const workOrderNumber = await this.logOrderNumber(); + await test.step(`Session Storage Work Order Number: ${workOrderNumber}`, async () => { + console.log(`Session Storage Work Order Number: ${workOrderNumber}`); + }); + } } \ No newline at end of file diff --git a/playwright-tests/pages/PartQuestionPage.ts b/playwright-tests/pages/PartQuestionPage.ts index 02c26490f..831a27dc1 100644 --- a/playwright-tests/pages/PartQuestionPage.ts +++ b/playwright-tests/pages/PartQuestionPage.ts @@ -1,6 +1,8 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { BasePage } from './BasePage'; import { IPartQuestion } from '@business-logic/types/CustomerDetails'; +import { step } from '@business-logic/types/Step'; +import { ITestData } from '@business-logic/types/ITestData'; export class PartQuestionsPage extends BasePage { readonly page: Page; @@ -56,4 +58,12 @@ export class PartQuestionsPage extends BasePage { } } } + + @step("PartQuestionsPage >> Select Vehicle Part Question Responses") + async handlePartQuestionsPage(testData: Partial) { + const { partQuestions } = testData; + await this.validatePartQuestions(partQuestions!); + await this.selectPartQuestionResponses(partQuestions!); + await this.nextPage(); + } } diff --git a/playwright-tests/pages/PaymentMethodPage.ts b/playwright-tests/pages/PaymentMethodPage.ts index c859aa916..92d0da651 100644 --- a/playwright-tests/pages/PaymentMethodPage.ts +++ b/playwright-tests/pages/PaymentMethodPage.ts @@ -1,11 +1,12 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { BasePage } from './BasePage'; import { IPaymentDetails } from '@business-logic/types/CustomerDetails'; -import { PaymentMethod, PaymentType, ServicePackage, VehicleDamage } from '@business-logic/types/Enums'; +import { AppointmentType, PaymentMethod, PaymentType, ServicePackage, VehicleDamage } from '@business-logic/types/Enums'; import { PaymentPage } from './PaymentPage'; import { AfterpayPage } from './AfterpayPage'; import { PaypalPage } from './PaypalPage'; import { ITestData } from '@business-logic/types/ITestData'; +import { step } from '@business-logic/types/Step'; export class PaymentMethodPage extends BasePage { readonly page: Page; @@ -17,6 +18,7 @@ export class PaymentMethodPage extends BasePage { readonly payInFourButton: Locator; readonly amountDueTextField: Locator; readonly amountDueDropDown: Locator; + readonly appointmentDetailsSection: Locator; readonly appointmentDetailsDropdown: Locator; readonly subtotalAmountTextField: Locator; readonly submitButton: Locator; @@ -48,6 +50,7 @@ export class PaymentMethodPage extends BasePage { this.payAtServiceButton = this.page.locator('[buttonlabel="Pay at my appointment"]'); //this.page.getByText('Pay at time of service'); this.amountDueTextField = this.page.getByLabel('expand cart').locator('.amount-due'); this.amountDueDropDown = this.page.getByLabel('expand cart'); + this.appointmentDetailsSection = this.page.locator('div .appt-details-snapshot'); this.appointmentDetailsDropdown = this.page.getByLabel('expand appointment details'); this.payWithInsuranceButton = this.page.locator('div').filter({ hasText: /^Pay with insurance$/ }).nth(1); this.subtotalAmountTextField = this.page.locator('.sub-total span').nth(1); @@ -88,110 +91,27 @@ export class PaymentMethodPage extends BasePage { // Wait for review table to be visible to ensure page is loaded await this.appointmentDetailsDropdown.waitFor({state: "visible"}); - await this.appointmentDetailsDropdown.click(); // Expand cart to see all details + await this.appointmentDetailsDropdown.click(); + + let expectedAppointmentDetails = new Map(); + expectedAppointmentDetails = await this.getExpectedVehicleDetails(testData, expectedAppointmentDetails); + expectedAppointmentDetails = await this.getExpectedVehicleDamage(testData, expectedAppointmentDetails); + expectedAppointmentDetails = await this.expectedServicePackageDetails(testData, expectedAppointmentDetails); + expectedAppointmentDetails = await this.getExpectedServiceLocation(testData, expectedAppointmentDetails); + expectedAppointmentDetails = await this.getExpectedAppointmentDate(testData, expectedAppointmentDetails); + expectedAppointmentDetails = await this.getExpectedCustomerDetails(testData, expectedAppointmentDetails); + + let actualAppointmentDetails = await this.getActualAppointmentDetails(); + + for (const key in expectedAppointmentDetails) { + expect.soft(actualAppointmentDetails[key]?.map(item => item.toLowerCase())) + .toEqual(expectedAppointmentDetails[key].map(item => item.toLowerCase())); + } + + // Expand cart to see all details await this.amountDueDropDown.click(); await this.reviewTable.waitFor({ state: "visible" }); - // Helper method to get content lines from a section - const getSectionContent = async (section: Locator) => { - // First make sure section exists - if (await section.count() === 0) return []; - - // Find all content lines within this section - const contentLines = await section.locator('div.small.review-block-content').allInnerTexts(); - return contentLines; - }; - - // Validate vehicle information - const vehicleContent = await getSectionContent(this.vehicleSection); - if (vehicleContent.length > 0) { - const vehicleText = vehicleContent[0]; - expect.soft(vehicleText).toContain(`${vehicleDetails?.year} ${vehicleDetails?.make} ${vehicleDetails?.model}`); - } - - // Validate damage type - - // TODO: Work out logic on how to verify vehicle Damage in payment details with vehicleDamage - - // const damageContent = await getSectionContent(this.damageSection); - // if (vehicleDamage && damageContent.length > 0) { - // const damageText = damageContent[0]; - - // // For each damage type in the array, check if its display text is in the damage content - // for (const damage of vehicleDamage) { - // const expectedDamageText = this.getDamageDisplayText(damage); - - // // If this is a single damage item, it should match exactly - // if (vehicleDamage.length === 1) { - // expect.soft(damageText).toContain(expectedDamageText); - // } else { - // // For multiple damages, check if any of the damage content lines contain this damage type - // const damageFound = damageContent.some(content => - // content.includes(expectedDamageText) - // ); - // expect.soft(damageFound).toBeTruthy(); - // } - // } - // } - - // Validate service details based on package - const serviceContent = await getSectionContent(this.serviceDetailsSection); - if (servicePackage && serviceContent.length > 0) { - //TODO: Add service package validation - // move over logic from Kishan's code - - // Additional validations for Standard and Premium packages - if (servicePackage === ServicePackage.Premium || servicePackage === ServicePackage.Standard) { - await expect.soft(this.wiperBladesText).toBeVisible(); - } - - if (servicePackage === ServicePackage.Premium) { - await expect.soft(this.rainDefenseText).toBeVisible(); - } - } - - // Validate service location - const locationContent = await getSectionContent(this.serviceLocationSection); - if (appointmentDetails?.serviceLocation && locationContent.length > 0) { - // Find the title element of the service location section - const serviceLocationTitle = this.serviceLocationSection.locator('span').first(); - const serviceLocationValue = await serviceLocationTitle.innerText(); - - // Check for mobile/inshop service wording - if (appointmentDetails.serviceLocation.toString().includes('Mobile')) { - expect.soft(serviceLocationValue).toContain("We're coming to you"); - } else if (appointmentDetails.serviceLocation.toString().includes('InShop')) { - expect.soft(serviceLocationValue).toContain("Bring to shop"); - } - - // Validate address if available - if (appointmentDetails.serviceAddress && locationContent.length > 0) { - const addressText = locationContent[0].toLowerCase(); - expect.soft(addressText).toContain(appointmentDetails.serviceAddress.street.toLowerCase()); - } - } - - // Validate appointment date/time - const appointmentContent = await getSectionContent(this.appointmentDateSection); - if (customerDetails?.apptDate && appointmentContent.length > 0) { - const appointmentText = appointmentContent[0]; - expect.soft(appointmentText).toContain(customerDetails.apptDate); - } - - // Validate contact details - const contactContent = await getSectionContent(this.contactDetailsSection); - if (customerDetails && contactContent.length > 0) { - const fullName = `${customerDetails.firstName} ${customerDetails.lastName}`; - const email = customerDetails.email; - const phone = customerDetails.phoneNumber; - - // Check if contact details are present - const contactTextJoined = contactContent.join(' '); - expect.soft(contactTextJoined).toContain(fullName); - expect.soft(contactTextJoined).toContain(email); - expect.soft(contactTextJoined).toContain(phone); - } - // Cart Validation // Get pricing information from cart panel if (await this.subtotalText.isVisible()) { @@ -339,4 +259,221 @@ l } } + async getActualAppointmentDetails(): Promise { + let actualAppointmentDetails = new Map(); + let appointmentDetailsSubSections = await this.appointmentDetailsSection.locator('div .py-3').all(); + + for (let element of appointmentDetailsSubSections) { + let label = await element.locator('div .text-block').innerText(); + //added to trim the text to remove any leading or trailing spaces (example: "expert installation " to "expert installation") + let value = (await element.locator('.review-block-content').allInnerTexts() as string[]).map(item => item.trim()); + actualAppointmentDetails[label] = value; + } + return actualAppointmentDetails; + } + + async getExpectedVehicleDetails(testdata: Partial, expectedServicePackageDetails: Map): Promise { + const { vehicleDetails } = testdata; + expectedServicePackageDetails["Vehicle"] = [vehicleDetails?.year + " " + vehicleDetails?.make + " " + vehicleDetails?.model]; + return expectedServicePackageDetails; + } + + async getExpectedVehicleDamage(testdata: Partial, expectedServicePackageDetails: Map): Promise { + + const {vehicleDamage} = testdata; + + let vehicleDamageText: string[] = []; + if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.WindshieldOneChip)) { + vehicleDamageText.push("Windshield repair - 1 chip"); + } + + else if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.WindshieldTwoChips)) { + vehicleDamageText.push("Windshield repair - 2 chips"); + } + + else if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.WindshieldThreeChips)) { + vehicleDamageText.push("Windshield repair - 3 chips"); + } + + if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.WindshieldCrack)) { + vehicleDamageText.push("Windshield crack"); + } + + if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.DriverVentGlass)) { + vehicleDamageText.includes("Side door - Driver side") ? vehicleDamageText[vehicleDamageText.length - 1 ] += "\nVent glass" : vehicleDamageText.push("Side door - Driver side", "Vent glass"); + } + + if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.DriverFrontDoor)) { + vehicleDamageText.includes("Side door - Driver side") ? vehicleDamageText[vehicleDamageText.length - 1 ] += "\nFront door" : vehicleDamageText.push("Side door - Driver side", "Front door"); + } + + if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.DriverRearDoor)) { + vehicleDamageText.includes("Side door - Driver side") ? vehicleDamageText[vehicleDamageText.length - 1 ] += "\nBack door" : vehicleDamageText.push("Side door - Driver side", "Back door"); + } + + if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.DriverQuarterPanel)) { + vehicleDamageText.includes("Side door - Driver side") ? vehicleDamageText[vehicleDamageText.length - 1 ] += "\nQuarter panel" : vehicleDamageText.push("Side door - Driver side", "Quarter panel"); + } + + if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.PassengerVentGlass)) { + vehicleDamageText.includes("Side door - Passenger side") ? vehicleDamageText[vehicleDamageText.length - 1 ] += "\nVent glass" : vehicleDamageText.push("Side door - Passenger side", "Vent glass"); + } + + if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.PassengerFrontDoor)) { + vehicleDamageText.includes("Side door - Passenger side") ? vehicleDamageText[vehicleDamageText.length - 1 ] += "\nFront door" : vehicleDamageText.push("Side door - Passenger side", "Front door"); + } + + if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.PassengerRearDoor)) { + vehicleDamageText.includes("Side door - Passenger side") ? vehicleDamageText[vehicleDamageText.length - 1 ] += "\nBack door" : vehicleDamageText.push("Side door - Passenger side", "Back door"); + } + + if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.PassengerQuarterPanel)) { + vehicleDamageText.includes("Side door - Passenger side") ? vehicleDamageText[vehicleDamageText.length - 1 ] += "\nQuarter panel" : vehicleDamageText.push("Side door - Passenger side", "Quarter panel"); + } + + if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.RearWindow || item == VehicleDamage.RearSliding)) { + vehicleDamageText.push("Rear window"); + } + + expectedServicePackageDetails["Damage"] = vehicleDamageText; + return expectedServicePackageDetails; + } + + async expectedServicePackageDetails(testData: Partial, expectedServicePackageDetails: Map): Promise { + const {servicePackage} = testData; + + let localStorage= JSON.parse(await this.page.evaluate('localStorage.getItem(\'vuex\')')); + let isRepair = localStorage.order.damage.isRepair as boolean; + let isInsurance = localStorage.order.payment.isInsurance as boolean; + let hasNonWindshieldGlass: boolean = false; + if (!isRepair) { + hasNonWindshieldGlass = localStorage.order.lineItems.glassParts.find((item: any) => item.partType !== "WINDSHIELD") ? true : false; + } + let isCaliforniaState = localStorage.order.serviceLocation.state as string == 'CA' ? true : false; + let hasRecalPart: boolean = false; + if (!isRepair) { + hasRecalPart = localStorage.order.lineItems.glassParts.find((item: any) => item.requiresRecalibration === true) ? true : false; + } + let recalRequired = !isRepair && (isInsurance || isCaliforniaState) && hasRecalPart + let stringForRepair: string[] = ["Expert windshield repair", "Exclusive resin sealant", "Nationwide lifetime guarantee"]; + let stringIfRecal = isRepair + ? "" + : recalRequired + ? " and recalibration" + : ""; + let stringForReplace: string[] = [hasNonWindshieldGlass ? "New replacement glass" : "New replacement windshield", "Expert installation" + `${stringIfRecal}`, "Nationwide lifetime warranty"]; + switch (servicePackage) + { + case ServicePackage.GlassOnly: + expectedServicePackageDetails["Glass service only"] = isRepair + ? stringForRepair + : stringForReplace; + break; + case ServicePackage.Standard: + stringForRepair.push("New wiper blades"); + stringForReplace.push("New wiper blades"); + expectedServicePackageDetails["Standard service"] = isRepair + ? stringForRepair + : stringForReplace; + break; + case ServicePackage.Premium: + stringForRepair.push("New wiper blades", "Rain Defense™"); + stringForReplace.push("New wiper blades", "Rain Defense™"); + expectedServicePackageDetails["Premium service"] = isRepair + ? stringForRepair + : stringForReplace; + break; + } + return expectedServicePackageDetails; + } + + async getExpectedServiceLocation(testData: Partial, expectedServicePackageDetails: Map): Promise { + const { appointmentDetails } = testData; + let serviceLocationTitle = appointmentDetails?.serviceLocation == AppointmentType.Mobile + ? "We're coming to you" + : "You're going to a Safelite shop"; + let serviceLocation: string[] = []; + + serviceLocation.push( + appointmentDetails?.serviceLocation == AppointmentType.Mobile + ? appointmentDetails?.serviceAddress + ? appointmentDetails.serviceAddress.street + ", " + appointmentDetails.serviceAddress.city + ", " + appointmentDetails.serviceAddress.state + " " + appointmentDetails.serviceAddress.postalCode + : "" + : appointmentDetails?.shopAddress + ? appointmentDetails.shopAddress + : "" + ); + + expectedServicePackageDetails[serviceLocationTitle] = serviceLocation; + return expectedServicePackageDetails; + } + + async getExpectedAppointmentDate(testData: Partial, expectedServicePackageDetails: Map): Promise { + const { customerDetails, appointmentDetails } = testData; + let appointmentDateText: string[] = []; + const isMobileAppointment = appointmentDetails?.serviceLocation == AppointmentType.Mobile + if (isMobileAppointment) { + let localStorage= JSON.parse(await this.page.evaluate('localStorage.getItem(\'vuex\')')); + let jobMinMinutes = localStorage.order.schedule.jobMinMinutes as number; + let jobMaxMinutes = localStorage.order.schedule.jobMaxMinutes as number; + + if (jobMinMinutes < 60) { + customerDetails!.apptDuration = `${jobMinMinutes} - ${jobMaxMinutes} minutes`; + } else { + const minHours = Math.floor(jobMinMinutes / 60); + const maxHours = Math.floor(jobMaxMinutes / 60); + customerDetails!.apptDuration = `${minHours} - ${maxHours} hours`; + } + } + + appointmentDateText.push( + customerDetails?.apptDate ? await this.getFormattedAppointmentDate(customerDetails.apptDate) + " " + customerDetails?.apptTime?.replace("-", "—") : "", + customerDetails?.apptDuration ? "Estimated appointment length: " + customerDetails.apptDuration : "" + ); + + expectedServicePackageDetails["Appointment date + time"] = appointmentDateText; + return expectedServicePackageDetails; + } + + async getExpectedCustomerDetails(testData: Partial, expectedServicePackageDetails: Map): Promise { + const { customerDetails } = testData; + let customerDetailsText: string[] = []; + + customerDetailsText.push( + customerDetails?.firstName.toUpperCase() + " " + customerDetails?.lastName.toUpperCase(), + customerDetails?.email ? customerDetails?.email.toUpperCase() : "", + customerDetails?.phoneNumber ? customerDetails?.phoneNumber : "", + "Opted out of text message updates" + ); + + expectedServicePackageDetails["Contact details"] = customerDetailsText; + return expectedServicePackageDetails; + } + + 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', year: 'numeric', month: 'long', day: 'numeric' }); + return updatedAppointmentDate; + } + + @step("PaymentMethodPage >> Select Payment Method: ") + async handlePaymentMethodPage(testData: Partial) { + const { servicePackage, isRecalVehicle, paymentDetails } = testData; + + await this.validatePaymentDetailsPage(testData); + + // Verify VAPS wipers on backend for standard and premium packages + if (servicePackage === ServicePackage.Standard || servicePackage === ServicePackage.Premium) { + await this.verifyVAPS(); + } + if (paymentDetails?.paymentType) { + await this.executePayment(paymentDetails!, isRecalVehicle!); + } else { + await this.nextPage(); + } + } } \ No newline at end of file diff --git a/playwright-tests/pages/PolicyDriverPage.ts b/playwright-tests/pages/PolicyDriverPage.ts index f6e56ca5f..1bb140088 100644 --- a/playwright-tests/pages/PolicyDriverPage.ts +++ b/playwright-tests/pages/PolicyDriverPage.ts @@ -1,6 +1,8 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { InsuranceBasePage } from './InsuranceBasePage'; import { ICustomerDetails } from '@business-logic/types/CustomerDetails'; +import { step } from '@business-logic/types/Step'; +import { ITestData } from '@business-logic/types/ITestData'; export class PolicyDriverPage extends InsuranceBasePage { readonly page: Page; @@ -47,5 +49,11 @@ export class PolicyDriverPage extends InsuranceBasePage { await this.driverNotListedOption.click(); } } - + + @step("PolicyDriverPage >> Select policy driver: ") + async handlePolicyDriverPage(testData: Partial) { + const { customerDetails } = testData; + await this.selectPolicyDriver(customerDetails!); + await this.nextPage(); + } } \ No newline at end of file diff --git a/playwright-tests/pages/PolicyInfoSubmittedPage.ts b/playwright-tests/pages/PolicyInfoSubmittedPage.ts index 6d5c96c5c..0a4bf79be 100644 --- a/playwright-tests/pages/PolicyInfoSubmittedPage.ts +++ b/playwright-tests/pages/PolicyInfoSubmittedPage.ts @@ -1,5 +1,6 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { InsuranceBasePage } from './InsuranceBasePage'; +import { step } from '@business-logic/types/Step'; export class PolicyInfoSubmittedPage extends InsuranceBasePage { readonly page: Page; @@ -20,5 +21,10 @@ export class PolicyInfoSubmittedPage extends InsuranceBasePage { const policyInfoMessage = await this.policyInfoMessage.textContent(); expect(policyInfoMessage).toBe("Your policy and vehicle information has been submitted for coverage verification") } - + + @step("PolicyInfoSubmittedPage >> Verify policy info submitted: ") + async handlePolicyInfoSubmittedPage() { + await this.verifyPolicyInfoSubmitted(); + await this.nextPage(); + } } \ No newline at end of file diff --git a/playwright-tests/pages/PolicyVehiclesPage.ts b/playwright-tests/pages/PolicyVehiclesPage.ts index 90882062d..67776adf6 100644 --- a/playwright-tests/pages/PolicyVehiclesPage.ts +++ b/playwright-tests/pages/PolicyVehiclesPage.ts @@ -1,6 +1,8 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { IVehicleDetails } from '@business-logic/types/CustomerDetails'; import { InsuranceBasePage } from './InsuranceBasePage'; +import { ITestData } from '@business-logic/types/ITestData'; +import { step } from '@business-logic/types/Step'; export class PolicyVehiclesPage extends InsuranceBasePage { readonly page: Page; @@ -26,4 +28,29 @@ export class PolicyVehiclesPage extends InsuranceBasePage { async selectVehicleNotListed(){ await this.page.getByText('Vehicle not listed').click(); } + + @step("PolicyVehiclesPage >> Select Vehicle: ") + async handlePolicyVehiclesPage(testCase: Partial) { + + const { vehicleDetails, otherVehiclesOnPolicy, isUseVehicleOnPolicy } = testCase; + + // Validate other vehicles on policy + if (otherVehiclesOnPolicy && otherVehiclesOnPolicy.length > 0) { + for (const vehicle of otherVehiclesOnPolicy) { + await this.validateVehicleIsOnPolicy(vehicle); + } + } + + // If vehicle is not on the policy, select new vehicle + if (!(isUseVehicleOnPolicy ?? true)) { + await this.selectVehicleNotListed(); + await this.nextPage(); + await this.selectVehicle(vehicleDetails!); + await this.nextPage(); + } else { + // Otherwise, select the vehicle entered in Safelite.com + await this.selectVehicle(vehicleDetails!); + await this.nextPage(); + } + } } \ No newline at end of file diff --git a/playwright-tests/pages/RecalibrationInfoPage.ts b/playwright-tests/pages/RecalibrationInfoPage.ts index 0af4b0a57..7310d6c16 100644 --- a/playwright-tests/pages/RecalibrationInfoPage.ts +++ b/playwright-tests/pages/RecalibrationInfoPage.ts @@ -1,5 +1,6 @@ import { Locator, Page } from "@playwright/test"; import { InsuranceBasePage } from "./InsuranceBasePage"; +import { step } from "@business-logic/types/Step"; export default class RecalibrationInfoPage extends InsuranceBasePage { url = process.env['BASE_URL']! + '/FixMyGlass/RecalibrationInfo.aspx'; @@ -11,4 +12,9 @@ export default class RecalibrationInfoPage extends InsuranceBasePage { this.continueButton = page.getByRole('button', { name: 'Continue' }); } + + @step("RecalibrationInfoPage >> Click continue button: ") + async handleRecalibrationInfoPage() { + await this.nextPage(); + } } \ No newline at end of file diff --git a/playwright-tests/pages/SchedulePage.ts b/playwright-tests/pages/SchedulePage.ts index bea9e82bd..5daa3060e 100644 --- a/playwright-tests/pages/SchedulePage.ts +++ b/playwright-tests/pages/SchedulePage.ts @@ -1,8 +1,11 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { BasePage } from './BasePage'; -import { IAppointmentDetails } from '@business-logic/types/CustomerDetails'; +import { IAppointmentDetails, ICustomerDetails } from '@business-logic/types/CustomerDetails'; import { formatDate, formatTime } from '@impl/utils/DateUtils'; import { AppointmentType, ServiceLocation } from '@business-logic/types/Enums'; +import { time } from 'console'; +import { step } from '@business-logic/types/Step'; +import { ITestData } from '@business-logic/types/ITestData'; export class SchedulePage extends BasePage { readonly page: Page; @@ -13,6 +16,8 @@ export class SchedulePage extends BasePage { readonly dropOffButton: Locator; readonly dateText: Locator; readonly viewMoreDatesLink: Locator; + readonly appointmentDuration: Locator; + readonly timeSlots: Locator; constructor(page: Page) { super(page); @@ -23,6 +28,8 @@ export class SchedulePage extends BasePage { 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 scheduleAppointment(appointmentDetails: IAppointmentDetails) { @@ -40,20 +47,47 @@ export class SchedulePage extends BasePage { await this.modalContinueButton.click(); } - async scheduleFirstAppointment(serviceLocation: AppointmentType) { - if (await this.firstAvailableDate.isVisible()) { - await this.firstAvailableDate.click(); - } else { + async scheduleFirstAppointment(customerDetails: ICustomerDetails) { + while (!(await this.firstAvailableDate.isVisible())) { await this.viewMoreDatesLink.click(); - while(await this.firstAvailableDate.isHidden()){ - await this.viewMoreDatesLink.click(); - } - await this.firstAvailableDate.click(); } + await this.firstAvailableDate.click().then(async () => { + customerDetails.apptDate = `${await this.firstAvailableDate.getAttribute("id")}` + }); - serviceLocation === AppointmentType.DropOff ? await this.dropOffButton.click() : await this.firstAvailableTime.click(); - const apptDate = `${await this.dateText.allInnerTexts()}` - await this.modalContinueButton.click(); - return (apptDate); + // 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 === AppointmentType.DropOff ? await this.dropOffButton.click() : await this.firstAvailableTime.click(); + customerDetails.apptDuration = (await this.appointmentDuration.innerText()).replace("Duration: ", ""); + await this.nextPage(); } -} \ No newline at end of file + + 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.toLowerCase().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 { customerDetails } = testData; + await this.scheduleFirstAppointment(customerDetails!); + } +} \ No newline at end of file diff --git a/playwright-tests/pages/ServiceLocationPage.ts b/playwright-tests/pages/ServiceLocationPage.ts index 609f2d1db..a31f38eb5 100644 --- a/playwright-tests/pages/ServiceLocationPage.ts +++ b/playwright-tests/pages/ServiceLocationPage.ts @@ -4,6 +4,8 @@ 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; @@ -119,6 +121,9 @@ export class ServiceLocationPage extends BasePage { 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 { @@ -135,7 +140,8 @@ export class ServiceLocationPage extends BasePage { if (appointmentDetails && appointmentDetails.shopAddress) { await this.selectAShopOptions.locator(`[buttonbodycopy="${appointmentDetails.shopAddress}"]`).check(); } else { - /// await this.clickWithRetry(this.firstAppointmentButton, this.page); + // await this.firstAppointmentButton.click(); + // await this.clickWithRetry(this.firstAppointmentButton, this.page); await this.firstAppointmentButton.scrollIntoViewIfNeeded().then(async () => { await this.firstAppointmentButton.click(); if (appointmentDetails) { @@ -150,4 +156,11 @@ export class ServiceLocationPage extends BasePage { await expect(this.RecalWarningMessage2).toBeVisible(); } + + @step("ServiceLocationPage >> Select service location: ") + async handleServiceLocationPage(testData: Partial) { + const { appointmentDetails } = testData; + await this.selectLocation(appointmentDetails!); + await this.nextPage(); + } } \ No newline at end of file diff --git a/playwright-tests/pages/ServicePackagesPage.ts b/playwright-tests/pages/ServicePackagesPage.ts index 6e09c003e..35872828b 100644 --- a/playwright-tests/pages/ServicePackagesPage.ts +++ b/playwright-tests/pages/ServicePackagesPage.ts @@ -2,6 +2,8 @@ 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; @@ -178,4 +180,55 @@ export class ServicePackagesPage extends BasePage { 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(); + } } \ No newline at end of file diff --git a/playwright-tests/pages/ServiceZipPage.ts b/playwright-tests/pages/ServiceZipPage.ts new file mode 100644 index 000000000..d11ffa139 --- /dev/null +++ b/playwright-tests/pages/ServiceZipPage.ts @@ -0,0 +1,20 @@ +import { Page } from "@playwright/test"; +import { LookupPage } from "./LookupPage"; +import { step } from "@business-logic/types/Step"; +import { ITestData } from "@business-logic/types/ITestData"; + +export class ServiceZipPage extends LookupPage { + + url = process.env['BASE_URL']! + '/fmg/?fmgPage=service-zip'; + + constructor(page: Page) { + super(page); + } + + @step("ZipLookupPage >> Lookup by service ZIP: ") + async handleServiceZipPage(testData: Partial) { + const { customerDetails, vehicleDetails, alertFlags } = testData; + await this.enterZip(customerDetails!.address.postalCode!); + await this.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); + } +} \ No newline at end of file diff --git a/playwright-tests/pages/VehicleDamagePage.ts b/playwright-tests/pages/VehicleDamagePage.ts index 33d778a79..1b48a1d4d 100644 --- a/playwright-tests/pages/VehicleDamagePage.ts +++ b/playwright-tests/pages/VehicleDamagePage.ts @@ -2,6 +2,8 @@ import { type Locator, type Page, expect, test } from '@playwright/test'; import { BasePage } from './BasePage'; import { SideDoorDamage, VehicleDamage, WindshieldDamage } from '@business-logic/types/Enums'; import TestSuccessAlert from '@business-logic/types/TestSuccessAlert'; +import { step } from '@business-logic/types/Step'; +import { ITestData } from '@business-logic/types/ITestData'; export class VehicleDamagePage extends BasePage { readonly page: Page; @@ -161,4 +163,22 @@ export class VehicleDamagePage extends BasePage { console.log(`Alert encountered: ${alertMessage}`); expect(alertMessage).toContain("Service not availableWe're sorry, but we currently offer only repair service for your vehicle type. Need help with next steps? Call us at800-394-0288.") } + + @step('VehicleDamagePage >> Select Damage') + async handleVehicleDamagePage(testData: Partial): Promise { + const {vehicleDamage} = testData; + const {isRepairReplace, isRepairOnly} = testData.alertFlags || {}; + + await this.selectDamage(vehicleDamage!); + // Handle alert conditions for vehicle damage + if (isRepairReplace) { + await this.checkForBothRepairReplaceAlertMessage(); + throw new TestSuccessAlert('Both assertions are met successfully.'); + } + if (isRepairOnly) { + await this.checkForRepairOnlyAlertMessage(); + throw new TestSuccessAlert('Both assertions are met successfully.'); + } + await this.nextPage(); + } } \ No newline at end of file diff --git a/playwright-tests/pages/VehicleLookupAddressPage.ts b/playwright-tests/pages/VehicleLookupAddressPage.ts index 23ba8521a..ab0ad86f8 100644 --- a/playwright-tests/pages/VehicleLookupAddressPage.ts +++ b/playwright-tests/pages/VehicleLookupAddressPage.ts @@ -3,6 +3,8 @@ import { LookupPage } from './LookupPage'; import { AddressForm } from './forms/AddressForm'; import { VehicleSelectionForm } from './forms/VehicleSelectionForm'; import { ICustomerDetails, IVehicleDetails } from '@business-logic/types/CustomerDetails'; +import { step } from '@business-logic/types/Step'; +import { ITestData } from '@business-logic/types/ITestData'; export class VehicleLookupAddressPage extends LookupPage { readonly addressForm: AddressForm; @@ -25,4 +27,11 @@ export class VehicleLookupAddressPage extends LookupPage { //await this.nextPage(); //await this.vehicleSelectionForm.selectVehicle(vehicleDetails); } + + @step("VehicleLookupAddressPage >> Lookup by address: ") + async handleVehicleLookupAddressPage(testData: Partial) { + const { customerDetails, vehicleDetails, alertFlags } = testData; + await this.lookupVehicleByAddress(customerDetails!, vehicleDetails!); + await this.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); + } } \ No newline at end of file diff --git a/playwright-tests/pages/VehicleLookupLicensePage.ts b/playwright-tests/pages/VehicleLookupLicensePage.ts index 492dfeffe..ef7f51f85 100644 --- a/playwright-tests/pages/VehicleLookupLicensePage.ts +++ b/playwright-tests/pages/VehicleLookupLicensePage.ts @@ -1,6 +1,8 @@ import { type Locator, type Page } from '@playwright/test'; import { LookupPage } from './LookupPage'; import { IVehicleDetails } from '@business-logic/types/CustomerDetails'; +import { step } from '@business-logic/types/Step'; +import { ITestData } from '@business-logic/types/ITestData'; export class VehicleLookupLicensePage extends LookupPage { readonly licensePlateNumTextBox: Locator; @@ -24,4 +26,12 @@ export class VehicleLookupLicensePage extends LookupPage { async enterPlateDetails(vehicleDetails: IVehicleDetails) { await this.licensePlateNumTextBox.fill(vehicleDetails.licensePlateNumber || ''); } + + @step("VehicleLookupLicensePage >> Lookup by license plate: ") + async handleVehicleLookupLicensePage(testData: Partial) { + const { customerDetails, vehicleDetails, alertFlags } = testData; + await this.enterPlateDetails(vehicleDetails!); + await this.enterZip(customerDetails!.address.postalCode!); + await this.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); + } } \ No newline at end of file diff --git a/playwright-tests/pages/VehiclePartsPage.ts b/playwright-tests/pages/VehiclePartsPage.ts index 1dc6b84f5..aabaa1f4e 100644 --- a/playwright-tests/pages/VehiclePartsPage.ts +++ b/playwright-tests/pages/VehiclePartsPage.ts @@ -1,5 +1,7 @@ import { Page } from "@playwright/test"; import { PartQuestionsPage } from "./PartQuestionPage"; +import { step } from "@business-logic/types/Step"; +import { ITestData } from "@business-logic/types/ITestData"; export default class VehiclePartQuestionsPage extends PartQuestionsPage{ url = process.env['BASE_URL']! + '/fmg/?fmgPage=vehicle-parts'; @@ -7,4 +9,12 @@ export default class VehiclePartQuestionsPage extends PartQuestionsPage{ constructor(page: Page) { super(page); } + + @step("VehiclePartsPage >> Select Vehicle Part Questions: ") + async handleVehiclePartsPage(testCase: Partial) { + const { vehiclePartQuestions } = testCase; + await this.validatePartQuestions(vehiclePartQuestions!); + await this.selectPartQuestionResponses(vehiclePartQuestions!); + await this.nextPage(); + } } \ No newline at end of file diff --git a/playwright-tests/pages/VehicleSelectionPage.ts b/playwright-tests/pages/VehicleSelectionPage.ts index b2d370ef8..049f9fb8d 100644 --- a/playwright-tests/pages/VehicleSelectionPage.ts +++ b/playwright-tests/pages/VehicleSelectionPage.ts @@ -2,6 +2,8 @@ import { type Locator, type Page, expect, test } from '@playwright/test'; import { BasePage } from './BasePage'; import { IVehicleDetails } from '@business-logic/types/CustomerDetails'; import TestSuccessAlert from '@business-logic/types/TestSuccessAlert'; +import { step } from '@business-logic/types/Step'; +import { ITestData } from '@business-logic/types/ITestData'; export class VehicleSelectionPage extends BasePage { readonly page: Page; @@ -42,4 +44,20 @@ export class VehicleSelectionPage extends BasePage { await console.log(`Alert encountered: ${alertMessage}`); await expect(alertMessage).toContain('Service not available in your areaWe do not offer glass service for your vehicle in your ZIP code. We apologize for the inconvenience.'); } + + @step("VehicleSelectionPage >> Select Vehicle: ") + async handleVehicleSelectionPage(testData: Partial) { + const { vehicleDetails } = testData; + const { isHeavyTruckVehicle, isSplitWindshield } = testData.alertFlags || {}; + + await this.selectVehicle(vehicleDetails!); + + // Handle alert conditions for vehicle selection + if (isHeavyTruckVehicle || isSplitWindshield) { + await this.checkForAlertMessages(); + throw new TestSuccessAlert('Both assertions are met successfully.'); + } + + await this.nextPage(); + } } \ No newline at end of file diff --git a/playwright-tests/pages/VerifyDetailsPage.ts b/playwright-tests/pages/VerifyDetailsPage.ts index b74eb251e..212ed7968 100644 --- a/playwright-tests/pages/VerifyDetailsPage.ts +++ b/playwright-tests/pages/VerifyDetailsPage.ts @@ -1,6 +1,8 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { IClaimDetails, ICustomerDetails } from '@business-logic/types/CustomerDetails'; import { InsuranceBasePage } from './InsuranceBasePage'; +import { step } from '@business-logic/types/Step'; +import { ITestData } from '@business-logic/types/ITestData'; export class VerifyDetailsPage extends InsuranceBasePage { readonly page: Page; @@ -74,4 +76,11 @@ export class VerifyDetailsPage extends InsuranceBasePage { // Return the original string if format is unknown return date; } + + @step("VerifyDetailsPage >> Verify policy details: ") + async handleVerifyDetailsPage(testData: Partial) { + const { customerDetails, claimDetails } = testData; + await this.verifyPolicyDetails(customerDetails!, claimDetails!); + await this.nextPage(); + } } \ No newline at end of file diff --git a/playwright-tests/pages/VinLookupPage.ts b/playwright-tests/pages/VinLookupPage.ts index 4ba2de260..1e96c14c5 100644 --- a/playwright-tests/pages/VinLookupPage.ts +++ b/playwright-tests/pages/VinLookupPage.ts @@ -1,5 +1,7 @@ import { type Locator, type Page } from '@playwright/test'; import { LookupPage } from './LookupPage'; +import { step } from '@business-logic/types/Step'; +import { ITestData } from '@business-logic/types/ITestData'; export class VinLookupPage extends LookupPage { readonly vinLookupTextBox: Locator; @@ -20,4 +22,12 @@ export class VinLookupPage extends LookupPage { async enterVin(vin: string) { await this.vinLookupTextBox.fill(vin); } + + @step("VinLookupPage >> Lookup by VIN: ") + async handleVehicleLookupVinPage(testData: Partial) { + const { customerDetails, vehicleDetails, alertFlags } = testData; + await this.enterVin(vehicleDetails!.vin!); + await this.enterZip(customerDetails!.address.postalCode!); + await this.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); + } } \ No newline at end of file diff --git a/playwright-tests/tests/0000__M.test.ts b/playwright-tests/tests/0000__M.test.ts index 524e93664..b51c8dd5e 100644 --- a/playwright-tests/tests/0000__M.test.ts +++ b/playwright-tests/tests/0000__M.test.ts @@ -156,35 +156,11 @@ async function runWorkflow(page: Page, testCase: TestCase) { // Destructure test data for easier access const { - servicePackage, paymentMethod, customerDetails, vehicleDetails, vehicleDamage, - appointmentDetails, paymentDetails, claimDetails, partQuestions, enterFunnelWithZip, - capabilityQuestions, vehiclePartQuestions, moldingQuestions, isPolicyFound, - otherVehiclesOnPolicy, isUseVehicleOnPolicy, isDuplicateClaim, isRecalNotification, - alertFlags, endorsements, isPolicyDriver, skipEstimatePage, isRecalVehicle, canNotRecal, - dynamicRecal, hasOemEndorsement, promoCode + paymentMethod, customerDetails, vehicleDetails, vehicleDamage, + paymentDetails, partQuestions, enterFunnelWithZip, capabilityQuestions, + vehiclePartQuestions, moldingQuestions, skipEstimatePage } = testCase.testData; - // Destructure alert flag data - const { - isHeavyTruckVehicle, isRepairReplace, isSplitWindshield, isRepairOnly, - isUnserviceableZip, isInvalidZip, isVinNotFound - } = testCase.testData.alertFlags || {}; - - // 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); - }); - - // Check if the insurance policy has endorsements - const hasEndorsements = endorsements && endorsements.length > 0; - // Check if the vehicle damage includes a windshield crack const hasWindshieldCrack = vehicleDamage!.some(damage => damage === VehicleDamage.WindshieldCrack @@ -211,414 +187,166 @@ async function runWorkflow(page: Page, testCase: TestCase) { console.log(`Customer for this test: ${customerDetails?.firstName} ${customerDetails?.lastName}`); } + // Handle vehicle selection page + let vehicleSelectionPage = testCase.pages.vehicleSelectionPage; + await vehicleSelectionPage.handleVehicleSelectionPage(testCase.testData); - await test.step('VehicleSelectionPage >> Select Vehicle', async () => { - let vehicleSelectionPage = testCase.pages.vehicleSelectionPage; - await vehicleSelectionPage.selectVehicle(vehicleDetails!); - - // Handle alert conditions for vehicle selection - if (isHeavyTruckVehicle || isSplitWindshield) { - await vehicleSelectionPage.checkForAlertMessages(); - throw new TestSuccessAlert('Both assertions are met successfully.'); - } - - await vehicleSelectionPage.nextPage(); - }); - - await test.step('VehicleDamagePage >> Select Damage', async () => { - let vehicleDamagePage = testCase.pages.vehicleDamagePage; - await vehicleDamagePage.selectDamage(vehicleDamage!); - - // Handle alert conditions for vehicle damage - if (isRepairReplace) { - await vehicleDamagePage.checkForBothRepairReplaceAlertMessage(); - throw new TestSuccessAlert('Both assertions are met successfully.'); - } - if (isRepairOnly) { - await vehicleDamagePage.checkForRepairOnlyAlertMessage(); - throw new TestSuccessAlert('Both assertions are met successfully.'); - } - - await vehicleDamagePage.nextPage(); - }); + // Handle vehicle damage page + let vehicleDamagePage = testCase.pages.vehicleDamagePage; + await vehicleDamagePage.handleVehicleDamagePage(testCase.testData); // If the vehicle has a windshield crack as part of its damage, go to estimate page and select lookup type if (hasWindshieldCrack && !skipEstimatePage) { - await test.step('EstimatePage >> Select Lookup Type', async () => { - let estimatePage = testCase.pages.estimatePage; - await estimatePage.vehicleLookup(vehicleDetails!); - }); + let estimatePage = testCase.pages.estimatePage; + await estimatePage.handleEstimatePage(testCase.testData); // Handle different vehicle lookup methods switch (vehicleDetails!.vehicleLookupType!) { case VehicleLookupType.Address: - await test.step('VehicleLookupAddressPage >> Lookup by address: ' + customerDetails!.address.street, async () => { - let vehicleLookupAddressPage = testCase.pages.vehicleLookupAddressPage; - await vehicleLookupAddressPage.lookupVehicleByAddress(customerDetails!, vehicleDetails!); - await vehicleLookupAddressPage.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); - }); + let vehicleLookupAddressPage = testCase.pages.vehicleLookupAddressPage; + await vehicleLookupAddressPage.handleVehicleLookupAddressPage(testCase.testData); break; case VehicleLookupType.LicensePlateNumber: - await test.step('VehicleLookupLicensePage >> Lookup by license plate: ' + vehicleDetails!.licensePlateNumber, async () => { - let vehicleLookupLicensePage = testCase.pages.vehicleLookupLicensePage; - await vehicleLookupLicensePage.enterPlateDetails(vehicleDetails!); - await vehicleLookupLicensePage.enterZip(customerDetails!.address.postalCode!); - await vehicleLookupLicensePage.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); - }); + let vehicleLookupLicensePage = testCase.pages.vehicleLookupLicensePage; + await vehicleLookupLicensePage.handleVehicleLookupLicensePage(testCase.testData); break; case VehicleLookupType.Vin: - await test.step('VinLookupPage >> Lookup by VIN: ' + vehicleDetails!.vin!, async () => { - let vinLookupPage = testCase.pages.vinLookupPage; - await vinLookupPage.enterVin(vehicleDetails!.vin!); - await vinLookupPage.enterZip(customerDetails!.address.postalCode!); - await vinLookupPage.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); - }); + let vinLookupPage = testCase.pages.vinLookupPage; + await vinLookupPage.handleVehicleLookupVinPage(testCase.testData); break; case VehicleLookupType.Zip: - await test.step('ZipLookupPage >> Lookup by service ZIP: ' + customerDetails!.address.postalCode!, async () => { - let zipLookupPage = testCase.pages.zipLookupPage; - let vinLookupPage = testCase.pages.vinLookupPage; - await vinLookupPage.enterZip(customerDetails!.address.postalCode!); - await zipLookupPage.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); - }); + let serviceZipPage = testCase.pages.serviceZipPage; + await serviceZipPage.handleServiceZipPage(testCase.testData); + break; } } else { - // Otherwise just use zip lookup - await test.step('ZipLookupPage >> Lookup by service ZIP: ' + customerDetails!.address.postalCode!, async () => { - let zipLookupPage = testCase.pages.zipLookupPage; - let vinLookupPage = testCase.pages.vinLookupPage; - await vinLookupPage.enterZip(customerDetails!.address.postalCode!); - await zipLookupPage.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); - }); + // Otherwise just use zip lookup for service zip page + let serviceZipPage = testCase.pages.serviceZipPage; + await serviceZipPage.handleServiceZipPage(testCase.testData); } // Handle part questions if applicable if (partQuestions && partQuestions.length > 0) { - await test.step('PartQuestionsPage >> Select Vehicle Part Question Responses', async () => { - let partQuestionsPage = testCase.pages.partQuestionsPage; - await partQuestionsPage.validatePartQuestions(partQuestions); - await partQuestionsPage.selectPartQuestionResponses(partQuestions); - await partQuestionsPage.nextPage(); - }); + let partQuestionsPage = testCase.pages.partQuestionsPage; + await partQuestionsPage.handlePartQuestionsPage(testCase.testData); } // Handle molding questions if applicable if (moldingQuestions && moldingQuestions.length > 0) { - await test.step('MoldingQuestionsPage >> Select Molding Question Responses', async () => { - let moldingQuestionsPage = testCase.pages.moldingQuestionsPage; - await moldingQuestionsPage.validatePartQuestions(moldingQuestions); - await moldingQuestionsPage.selectPartQuestionResponses(moldingQuestions); - await moldingQuestionsPage.nextPage(); - }); + let moldingQuestionsPage = testCase.pages.moldingQuestionsPage; + await moldingQuestionsPage.handleMoldingQuestionsPage(testCase.testData); } // Handle vehicle part questions if applicable if (vehiclePartQuestions && vehiclePartQuestions.length > 0) { - await test.step('VehiclePartsPage >> Select Vehicle Part Responses', async () => { - let vehiclePartsPage = testCase.pages.vehiclePartsPage; - await vehiclePartsPage.validatePartQuestions(vehiclePartQuestions); - await vehiclePartsPage.selectPartQuestionResponses(vehiclePartQuestions); - await vehiclePartsPage.nextPage(); - }); + let vehiclePartsPage = testCase.pages.vehiclePartsPage; + await vehiclePartsPage.handleVehiclePartsPage(testCase.testData); } // Handle capability questions if applicable if (capabilityQuestions && capabilityQuestions.length > 0) { - await test.step('CapabilityQuestionsPage >> Select Capability Question Responses', async () => { - let capabilityQuestionsPage = testCase.pages.capabilityQuestionsPage; - await capabilityQuestionsPage.validatePartQuestions(capabilityQuestions); - await capabilityQuestionsPage.selectPartQuestionResponses(capabilityQuestions); - await capabilityQuestionsPage.nextPage(); - }); + let capabilityQuestionsPage = testCase.pages.capabilityQuestionsPage; + await capabilityQuestionsPage.handleCapabilityQuestionsPage(testCase.testData); } // Select service package and payment method - await test.step('ServicePackagePage >> Select Payment Method and Service Type', async() => { - let servicePackagePage = testCase.pages.servicePackagePage; - await servicePackagePage.handleQuotePopup(customerDetails!.email!); - await servicePackagePage.selectPaymentMethod(paymentMethod!); - await servicePackagePage.selectServicePackage(servicePackage!); - // Enter promo code - if (promoCode) { - await servicePackagePage.enterPromo(promoCode); - } - - // Backend Validations - // Validate backend for can not recal if applicable - if (canNotRecal) { - await servicePackagePage.verifyCanNotRecal(); - } - // Validate backend for dynamic recal if applicable - if (dynamicRecal) { - await servicePackagePage.verifyDynamicRecal(); - } - // Validate backend for repair info (including chip verification) - await servicePackagePage.verifyIsRepair(!isReplace, vehicleDamage!); - if (isReplace) { - // Validate backend for parts info - await servicePackagePage.verifyVehicleParts(vehicleDamage!); - } - // Validate backend for OEM endorsement - if (hasOemEndorsement) { - await servicePackagePage.verifyOEMPart(); - } - - await servicePackagePage.nextPage(); - }); + let servicePackagesPage = testCase.pages.servicePackagesPage; + await servicePackagesPage.handleServicePackagePage(testCase.testData); //============================= INSURANCE FLOW =============================` // Insurance flow - if user selected Insurance as Payment Method if (paymentMethod == PaymentMethod.Insurance) { - await test.step('InsuranceCoveragePage >> Select your insurance', async() => { - let insuranceCompanyPage = testCase.pages.insuranceCompanyPage; - await insuranceCompanyPage.enterInsuranceCompany(claimDetails!.client!); - await insuranceCompanyPage.nextPage(); - }); - - await test.step('CCPolicyInfoPage >> Fill out claim information', async() => { - let ccPolicyInfoPage = testCase.pages.ccPolicyInfoPage; - await ccPolicyInfoPage.populatePage(customerDetails!, claimDetails!, await ccPolicyInfoPage.hasCityInfo()); - await ccPolicyInfoPage.nextPage(); - }); - - // Handle duplicate claim case if applicable - if (isDuplicateClaim) { - await test.step('DuplicateCheckPage >> Start New Claim', async () => { - let duplicateCheckPage = testCase.pages.duplicateCheckPage; - await duplicateCheckPage.startNewClaim(); - await duplicateCheckPage.nextPage(); - }); - } - - // Handle policy found vs. not found flows - if (isPolicyFound) { - await test.step('PolicyVehiclesPage >> Select vehicle', async () => { - let policyVehiclesPage = testCase.pages.policyVehiclesPage; - let vehicleSelectionPage = testCase.pages.vehicleSelectionPage; - - // Validate other vehicles on policy - if (otherVehiclesOnPolicy && otherVehiclesOnPolicy.length > 0) { - for (const vehicle of otherVehiclesOnPolicy) { - await policyVehiclesPage.validateVehicleIsOnPolicy(vehicle); - } - } - - // If vehicle is not on the policy, select new vehicle - if (!(isUseVehicleOnPolicy ?? true)) { - await policyVehiclesPage.selectVehicleNotListed(); - await policyVehiclesPage.nextPage(); - await vehicleSelectionPage.selectVehicle(vehicleDetails!); - await policyVehiclesPage.nextPage(); - } else { - // Otherwise, select the vehicle entered in Safelite.com - await policyVehiclesPage.selectVehicle(vehicleDetails!); - await policyVehiclesPage.nextPage(); - } - }); - - // Handle policy driver selection if applicable - if (isPolicyDriver) { - await test.step('PolicyDriverPage >> Confirm Driver at time of damage', async () => { - let policyDriverPage = testCase.pages.policyDriverPage; - await policyDriverPage.selectPolicyDriver(customerDetails!); - await policyDriverPage.nextPage(); - }); - } - - // Handle endorsements if applicable - if (hasEndorsements) { - await test.step('EndorsementsPage >> Select Endorsements', async () => { - let endorsementsPage = testCase.pages.endorsementsPage; - await endorsementsPage.verifyEndorsements(endorsements); - await endorsementsPage.selectEndorsements(endorsements); - await endorsementsPage.nextPage(); - }); - } - } else { - // Policy not found flow - await test.step('VerifyDetailsPage >> Verify Details', async () => { - let verifyDetailsPage = testCase.pages.verifyDetailsPage; - await verifyDetailsPage.verifyPolicyDetails(customerDetails!, claimDetails!); - await verifyDetailsPage.nextPage(); - }); - } - - // Continue with the insurance flow after policy information - await test.step('PolicyInfoSubmittedPage >> Continue With Safelite Autoglass', async () => { - let policyInfoSubmittedPage = testCase.pages.policyInfoSubmittedPage; - await policyInfoSubmittedPage.verifyPolicyInfoSubmitted(); - await policyInfoSubmittedPage.nextPage(); - }); - - // Handle recalibration notification if applicable - if (isRecalNotification) { - await test.step('RecallibrationInfoPage >> Continue With Recalibration Information', async () => { - let recalibrationInfoPage = testCase.pages.recalibrationInfoPage; - await recalibrationInfoPage.nextPage(); - }); - } - - // Continue to coverage statement - await test.step('CoverageStatementPage >> Next page', async () => { - let coverageStatementPage = testCase.pages.coverageStatementPage; - await coverageStatementPage.validateDeductibleAmount(claimDetails!); - await coverageStatementPage.nextPage(); - }); + await handleInsuranceFlow(testCase); } //============================= SERVICE SCHEDULING ============================= // Select service location - await test.step('ServiceLocationPage >> Select service location', async () => { - let serviceLocationPage = testCase.pages.serviceLocationPage; - await serviceLocationPage.selectLocation(appointmentDetails!); - await serviceLocationPage.nextPage(); - }); + let serviceLocationPage = testCase.pages.serviceLocationPage; + await serviceLocationPage.handleServiceLocationPage(testCase.testData); // Schedule appointment - await test.step('SchedulePage >> Select day and time', async () => { - let schedulePage = testCase.pages.schedulePage; - customerDetails!.apptDate! = await schedulePage.scheduleFirstAppointment(appointmentDetails!.serviceLocation); - }); + let schedulePage = testCase.pages.schedulePage; + await schedulePage.handleSchedulePage(testCase.testData); // Enter contact details - await test.step('ContactDetailsPage >> Enter contact details', async () => { - let contactDetailsPage = testCase.pages.contactDetailsPage; - await contactDetailsPage.enterContactDetails(customerDetails!); - await contactDetailsPage.nextPage(); - }); + let contactDetailsPage = testCase.pages.contactDetailsPage; + await contactDetailsPage.handleContactDetailsPage(testCase.testData); //============================= PAYMENT PROCESSING ============================= // Handle payment - await test.step('PaymentMethodPage >> Execute Payment', async () => { - let paymentMethodPage = testCase.pages.paymentMethodPage; - await paymentMethodPage.validatePaymentDetailsPage(testCase.testData); - // Verify VAPS wipers on backend for standard and premium packages - if (servicePackage === ServicePackage.Standard || servicePackage === ServicePackage.Premium) { - await paymentMethodPage.verifyVAPS(); - } - if (paymentDetails?.paymentType) { - await paymentMethodPage.executePayment(paymentDetails!, isRecalVehicle!); - } else { - await paymentMethodPage.nextPage(); - } - }); + let paymentMethodPage = testCase.pages.paymentMethodPage; + await paymentMethodPage.handlePaymentMethodPage(testCase.testData); // If user selected Pay with Insurance as Payment Method, Enter Insurance Flow if (paymentDetails?.paymentType === PaymentType.PayWithInsurance) { - await test.step('InsuranceCoveragePage >> Select your insurance', async() => { - let insuranceCompanyPage = testCase.pages.insuranceCompanyPage; - await insuranceCompanyPage.enterInsuranceCompany(claimDetails!.client!); - await insuranceCompanyPage.nextPage(); - }); - - await test.step('CCPolicyInfoPage >> Fill out claim information', async() => { - const ccPolicyInfoPage = testCase.pages.ccPolicyInfoPage; - await ccPolicyInfoPage.populatePage(customerDetails!, claimDetails!, await ccPolicyInfoPage.hasCityInfo()); - await ccPolicyInfoPage.nextPage(); - }); - - // Handle duplicate claim case if applicable - if (isDuplicateClaim) { - await test.step('DuplicateCheckPage >> Start New Claim', async () => { - const duplicateCheckPage = testCase.pages.duplicateCheckPage; - await duplicateCheckPage.startNewClaim(); - await duplicateCheckPage.nextPage(); - }); - } - - // Handle policy found vs. not found flows - if (isPolicyFound) { - await test.step('PolicyVehiclesPage >> Select vehicle', async () => { - const policyVehiclesPage = testCase.pages.policyVehiclesPage; - const vehicleSelectionPage = testCase.pages.vehicleSelectionPage; - - // Validate other vehicles on policy - if (otherVehiclesOnPolicy && otherVehiclesOnPolicy.length > 0) { - for (const vehicle of otherVehiclesOnPolicy) { - await policyVehiclesPage.validateVehicleIsOnPolicy(vehicle); - } - } - - // If vehicle is not on the policy, select new vehicle - if (!(isUseVehicleOnPolicy ?? true)) { - await policyVehiclesPage.selectVehicleNotListed(); - await policyVehiclesPage.nextPage(); - await vehicleSelectionPage.selectVehicle(vehicleDetails!); - await policyVehiclesPage.nextPage(); - } else { - // Otherwise, select the vehicle entered in Safelite.com - await policyVehiclesPage.selectVehicle(vehicleDetails!); - await policyVehiclesPage.nextPage(); - } - }); - - // Handle policy driver selection if applicable - if (isPolicyDriver) { - await test.step('PolicyDriverPage >> Confirm Driver at time of damage', async () => { - const policyDriverPage = testCase.pages.policyDriverPage; - await policyDriverPage.selectPolicyDriver(customerDetails!); - await policyDriverPage.nextPage(); - }); - } - - // Handle endorsements if applicable - if (hasEndorsements) { - await test.step('EndorsementsPage >> Select Endorsements', async () => { - const endorsementsPage = testCase.pages.endorsementsPage; - await endorsementsPage.verifyEndorsements(endorsements); - await endorsementsPage.selectEndorsements(endorsements); - await endorsementsPage.nextPage(); - }); - } - } else { - // Policy not found flow - await test.step('VerifyDetailsPage >> Verify Details', async () => { - const verifyDetailsPage = testCase.pages.verifyDetailsPage; - await verifyDetailsPage.verifyPolicyDetails(customerDetails!, claimDetails!); - await verifyDetailsPage.nextPage(); - }); - } - - // Continue with the insurance flow after policy information - await test.step('PolicyInfoSubmittedPage >> Continue With Safelite Autoglass', async () => { - const policyInfoSubmittedPage = testCase.pages.policyInfoSubmittedPage; - await policyInfoSubmittedPage.verifyPolicyInfoSubmitted(); - await policyInfoSubmittedPage.nextPage(); - }); - - // Handle recalibration notification if applicable - if (isRecalNotification) { - await test.step('RecallibrationInfoPage >> Continue With Recalibration Information', async () => { - const recalibrationInfoPage = testCase.pages.recalibrationInfoPage; - await recalibrationInfoPage.nextPage(); - }); - } - - // Continue to coverage statement - await test.step('CoverageStatementPage >> Next page', async () => { - const coverageStatementPage = testCase.pages.coverageStatementPage; - await coverageStatementPage.validateDeductibleAmount(claimDetails!); - await coverageStatementPage.nextPage(); - }); + await handleInsuranceFlow(testCase); } //============================= ORDER CONFIRMATION ============================= // Validate order confirmation - await test.step('OrderConfirmationPage >> Validate order', async () => { - let orderConfirmationPage = testCase.pages.orderConfirmationPage; - await orderConfirmationPage.validateOrderConfirmationPage(testCase.testData); - }); + let orderConfirmationPage = testCase.pages.orderConfirmationPage; + await orderConfirmationPage.verifyOrderConfirmationPage(testCase.testData); +} - // Get the order number and wrap it in a test step - const workOrderNumber = await testCase.pages.orderConfirmationPage.logOrderNumber(); - await test.step(`Session Storage Work Order Number: ${workOrderNumber}`, async () => { - console.log(`Session Storage Work Order Number: ${workOrderNumber}`); - }); +export async function handleInsuranceFlow(testCase: TestCase) { + const { isPolicyFound, isPolicyDriver, endorsements, isRecalNotification } = testCase.testData; + + // Check if the insurance policy has endorsements + const hasEndorsements = endorsements && endorsements.length > 0; + + // Handle insurance company page + let insuranceCompanyPage = testCase.pages.insuranceCompanyPage; + await insuranceCompanyPage.handleInsuranceCompanyPage(testCase.testData); + + // Handle ccPolicyInfoPage + let ccPolicyInfoPage = testCase.pages.ccPolicyInfoPage; + await ccPolicyInfoPage.handleCCPolicyInfoPage(testCase.testData); + + let duplicateCheckPage = testCase.pages.duplicateCheckPage; + if (duplicateCheckPage.page.url().includes('DuplicateCheck.aspx')) { + await duplicateCheckPage.handleDuplicateCheckPage(testCase.testData); + } + + if (isPolicyFound) { + let policyVehiclesPage = testCase.pages.policyVehiclesPage; + await policyVehiclesPage.handlePolicyVehiclesPage(testCase.testData); + + // Handle policy driver selection if applicable + if (isPolicyDriver) { + let policyDriverPage = testCase.pages.policyDriverPage; + await policyDriverPage.handlePolicyDriverPage(testCase.testData); + } + + // Handle endorsements if applicable + if (hasEndorsements) { + let endorsementsPage = testCase.pages.endorsementsPage; + await endorsementsPage.handleEndorsementsPage(testCase.testData); + } + }else { + + //Handle verify details page + let verifyDetailsPage = testCase.pages.verifyDetailsPage; + await verifyDetailsPage.handleVerifyDetailsPage(testCase.testData); + } + + // Handle Policy info submitted page + let policyInfoSubmittedPage = testCase.pages.policyInfoSubmittedPage; + await policyInfoSubmittedPage.handlePolicyInfoSubmittedPage(); + + if(isRecalNotification) + { + let recalibrationInfoPage = testCase.pages.recalibrationInfoPage; + await recalibrationInfoPage.handleRecalibrationInfoPage(); + } + + //handle coveraage statement page + let coverageStatementPage = testCase.pages.coverageStatementPage; + await coverageStatementPage.handleCoverageStatementPage(testCase.testData); } \ No newline at end of file diff --git a/playwright-tests/tests/CashRepairMobileCreditCard.ts b/playwright-tests/tests/CashRepairMobileCreditCard.ts index 3674159aa..892c8d71e 100644 --- a/playwright-tests/tests/CashRepairMobileCreditCard.ts +++ b/playwright-tests/tests/CashRepairMobileCreditCard.ts @@ -28,7 +28,7 @@ const cashRepairMobileCCData : Partial = { serviceAddress: { street: '13735 San Antonio Ave', city: 'Chino', - state: 'California', + state: 'CA', postalCode: '91710', country: 'United States' } diff --git a/playwright-tests/tests/CashReplaceDynamicRecalMobile.ts b/playwright-tests/tests/CashReplaceDynamicRecalMobile.ts index 50c1d72b5..25bb3bf50 100644 --- a/playwright-tests/tests/CashReplaceDynamicRecalMobile.ts +++ b/playwright-tests/tests/CashReplaceDynamicRecalMobile.ts @@ -39,7 +39,7 @@ const cashReplaceDynamicRecalMobileData: Partial = { // Use street address from current faker seed street: getDefaultTestData().customerDetails!.address.street, city: 'Rosedale', - state: 'Maryland', + state: 'MD', postalCode: '21237', country: 'United States' }, diff --git a/playwright-tests/tests/CashReplaceMultiGlassMobile.ts b/playwright-tests/tests/CashReplaceMultiGlassMobile.ts index 54da96bb6..eac5e2263 100644 --- a/playwright-tests/tests/CashReplaceMultiGlassMobile.ts +++ b/playwright-tests/tests/CashReplaceMultiGlassMobile.ts @@ -35,7 +35,7 @@ const cashReplaceMultiGlassMobileData: Partial = { // Use street address from current faker seed street: getDefaultTestData().customerDetails!.address.street, city: 'Rosedale', - state: 'Maryland', + state: 'MD', postalCode: '21237', country: 'United States' }, diff --git a/playwright-tests/tests/CashReplaceSafeliteCanNotRecalMobile.ts b/playwright-tests/tests/CashReplaceSafeliteCanNotRecalMobile.ts index 9ad33f23d..f7d301da1 100644 --- a/playwright-tests/tests/CashReplaceSafeliteCanNotRecalMobile.ts +++ b/playwright-tests/tests/CashReplaceSafeliteCanNotRecalMobile.ts @@ -48,7 +48,7 @@ const cashReplaceSafeliteCanNotRecalMobileData: Partial = { // Use street address from current faker seed street: getDefaultTestData().customerDetails!.address.street, city: 'Rosedale', - state: 'Maryland', + state: 'MD', postalCode: '21237', country: 'United States' }, diff --git a/playwright-tests/tests/CashReplaceVinMobile.ts b/playwright-tests/tests/CashReplaceVinMobile.ts index 59930b1ac..37470200e 100644 --- a/playwright-tests/tests/CashReplaceVinMobile.ts +++ b/playwright-tests/tests/CashReplaceVinMobile.ts @@ -45,7 +45,7 @@ const cashReplaceVinMobileData: Partial = { // Use street address from current faker seed street: getDefaultTestData().customerDetails!.address.street, city: 'Rosedale', - state: 'Maryland', + state: 'MD', postalCode: '21237', country: 'United States' }