diff --git a/Dockerfile.playwright b/Dockerfile.playwright index 46a2114a2..bd66363f0 100644 --- a/Dockerfile.playwright +++ b/Dockerfile.playwright @@ -14,8 +14,5 @@ RUN npm install # Install Playwright browsers RUN npx playwright install chromium --with-deps -# Install jq -RUN apt-get install -y jq - # Copy the rest of the application code COPY . . diff --git a/playwright-tests/business-logic/types/Enums.ts b/playwright-tests/business-logic/types/Enums.ts index 91b0248ce..a2d633dab 100644 --- a/playwright-tests/business-logic/types/Enums.ts +++ b/playwright-tests/business-logic/types/Enums.ts @@ -130,5 +130,26 @@ export enum AppointmentTimeslot{ overnight = "Overnight" } +export enum ProgressBarPercentages { + VehicleSelectionPage = '4%', + VehicleDamagePage = '16%', + EstimatePage = '28%', + ServiceZipPage = '32%', + VehicleLookupAddressPage = '32%', + VehicleLookupLicensePage = '32%', + VinLookupPage = '32%', + PartQuestionsPage = '40%', + MoldingQuestionsPage = '40%', + CapabilityQuestionsPage = '40%', + VehiclePartsPage = '40%', + ServicePackagePage = '48%', + InsuranceCompanyPage = '52%', + ServiceLocationPage = '60%', + SchedulePage = '72%', + ContactDetailsPage = '84%', + PaymentMethodPage = '92%', + OrderConfirmationPage = '100%' + } + diff --git a/playwright-tests/impl/reporter/JiraWritebackReporter.ts b/playwright-tests/impl/reporter/JiraWritebackReporter.ts index 6f228be00..62b2bdebd 100644 --- a/playwright-tests/impl/reporter/JiraWritebackReporter.ts +++ b/playwright-tests/impl/reporter/JiraWritebackReporter.ts @@ -23,7 +23,7 @@ const reportConfig: OrtoniReportConfig = { logo: 'playwright-tests/business-logic/data/logo.png', title: "Test Report", showProject: false, - projectName: "ISS-Nextgen-Playwright-Report", + projectName: "FMG-Nextgen-Playwright-Report", testType: `E2E- Environment: ${process.env.NODE_ENV} `, preferredTheme: "light", base64Image: true, diff --git a/playwright-tests/pages/BasePage.ts b/playwright-tests/pages/BasePage.ts index 348ee9476..0e8c65219 100644 --- a/playwright-tests/pages/BasePage.ts +++ b/playwright-tests/pages/BasePage.ts @@ -1,5 +1,6 @@ import { IAppointmentDetails, ICustomerDetails } from '@business-logic/types/CustomerDetails'; import Soft from '@business-logic/validations/Soft'; +import { waitUntil } from '@impl/utils/TimingUtils'; import test, { expect, type Locator, type Page } from '@playwright/test'; import { error } from 'console'; @@ -24,7 +25,7 @@ export class BasePage { this.pageSpinner = page.getByRole('status'); this.buttonLoadSpin = page.getByRole('alert'); this.hamburgerMenu = this.page.getByRole('button', { name: 'Hamburger Menu (modal window)' }); - this.progressBar = this.page.locator('#progress-bar-container progress'); + this.progressBar = this.page.locator('.progress-bar-inner'); } async nextPage() { @@ -56,8 +57,11 @@ export class BasePage { async fillAndValidate(element: Locator, value: string){ await expect(async () => { - await element.clear(); - await element.fill(value); + var text = await element.textContent(); + if(text !== value) { + await element.clear(); + await element.fill(value); + } await expect(element).toHaveValue(value); }).toPass(); } @@ -129,11 +133,25 @@ export class BasePage { } } - async validateProgressBar(progressPercentage: string) { - await this.page.locator('button .loader').waitFor({ state: 'hidden', timeout: 60000 }); - const actualProgressPercentage = await this.progressBar.getAttribute("value") || "Not Found"; + async validateProgressBar(progressPercentage: string, timeout: number = 60000) { + + + await waitUntil(async () => { + let loaderElements = await this.page.locator('button .loader, .buy-loader, timeout, .modal-loader').all(); + return !(await Promise.any(loaderElements.map(el => el.isVisible())).catch(() => false)); + }); + + // Take a screenshot of the page before validating the progress bar + await this.page.screenshot({ path: `test-results\\ortoni-data\\progress-bar-${Date.now()}.png`, fullPage: true }); + // Wait until the progress bar element is attached and visible + + const actualProgressPercentage = await this.progressBar.evaluate( + async (element) => { + await new Promise(resolve => setTimeout(resolve, 200)); + return element.style.width || "Not Found"; + } + ); Soft.expect(actualProgressPercentage).toBe(progressPercentage); console.log(`Progress Bar Percentage: Actual - ${actualProgressPercentage} vs Expected - ${progressPercentage}`); } - } \ No newline at end of file diff --git a/playwright-tests/pages/CapabilityQuestionsPage.ts b/playwright-tests/pages/CapabilityQuestionsPage.ts index 9416d534a..3aed26223 100644 --- a/playwright-tests/pages/CapabilityQuestionsPage.ts +++ b/playwright-tests/pages/CapabilityQuestionsPage.ts @@ -2,6 +2,7 @@ import { Page } from "@playwright/test"; import { PartQuestionsPage } from "./PartQuestionPage"; import { step } from "@business-logic/types/Step"; import { ITestData } from "@business-logic/types/ITestData"; +import { ProgressBarPercentages } from "@business-logic/types/Enums"; export default class CapabilityQuestionsPage extends PartQuestionsPage { url = process.env['BASE_URL']! + '/fmg/?fmgPage=capability-questions'; @@ -14,7 +15,7 @@ export default class CapabilityQuestionsPage extends PartQuestionsPage { async handleCapabilityQuestionsPage(testCase: Partial) { const { capabilityQuestions } = testCase; - await this.validateProgressBar("40"); + await this.validateProgressBar(ProgressBarPercentages.CapabilityQuestionsPage); // Validate the capability questions are on the page await this.validatePartQuestions(capabilityQuestions!); diff --git a/playwright-tests/pages/ContactDetailsPage.ts b/playwright-tests/pages/ContactDetailsPage.ts index 8dbd29f50..41ce9de09 100644 --- a/playwright-tests/pages/ContactDetailsPage.ts +++ b/playwright-tests/pages/ContactDetailsPage.ts @@ -3,6 +3,7 @@ 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'; +import { ProgressBarPercentages } from '@business-logic/types/Enums'; export class ContactDetailsPage extends BasePage { readonly page: Page; @@ -46,7 +47,7 @@ export class ContactDetailsPage extends BasePage { async handleContactDetailsPage(testData: Partial) { const { customerDetails } = testData; - await this.validateProgressBar("84"); + await this.validateProgressBar(ProgressBarPercentages.ContactDetailsPage); await this.enterContactDetails(customerDetails!); await this.nextPage(); } diff --git a/playwright-tests/pages/EstimatePage.ts b/playwright-tests/pages/EstimatePage.ts index 6a605293b..4eac6dc1f 100644 --- a/playwright-tests/pages/EstimatePage.ts +++ b/playwright-tests/pages/EstimatePage.ts @@ -1,6 +1,6 @@ import { type Locator, type Page } from '@playwright/test'; import { BasePage } from './BasePage'; -import { VehicleLookupType } from '@business-logic/types/Enums'; +import { ProgressBarPercentages, VehicleLookupType } from '@business-logic/types/Enums'; import { IVehicleDetails } from '@business-logic/types/CustomerDetails'; import { VinLookupPage } from './VinLookupPage'; import { VehicleLookupAddressPage } from './VehicleLookupAddressPage'; @@ -74,7 +74,7 @@ export class EstimatePage extends BasePage { @step("EstimatePage >> Select Lookup Type") async handleEstimatePage(testData: Partial) { const { vehicleDetails } = testData; - await this.validateProgressBar("28"); + await this.validateProgressBar(ProgressBarPercentages.EstimatePage); await this.vehicleLookup(vehicleDetails!); } } diff --git a/playwright-tests/pages/InsuranceCompanyPage.ts b/playwright-tests/pages/InsuranceCompanyPage.ts index 594075292..05b88f82a 100644 --- a/playwright-tests/pages/InsuranceCompanyPage.ts +++ b/playwright-tests/pages/InsuranceCompanyPage.ts @@ -4,6 +4,7 @@ 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'; +import { ProgressBarPercentages } from '@business-logic/types/Enums'; export class InsuranceCompanyPage extends BasePage { @@ -52,7 +53,7 @@ export class InsuranceCompanyPage extends BasePage { async handleInsuranceCompanyPage(testData: Partial) { const { claimDetails } = testData; - await this.validateProgressBar("52"); + await this.validateProgressBar(ProgressBarPercentages.InsuranceCompanyPage); await this.enterInsuranceCompany(claimDetails!.client!); await this.nextPage(); } diff --git a/playwright-tests/pages/MoldingQuestionsPage.ts b/playwright-tests/pages/MoldingQuestionsPage.ts index ab12cf9be..1473234eb 100644 --- a/playwright-tests/pages/MoldingQuestionsPage.ts +++ b/playwright-tests/pages/MoldingQuestionsPage.ts @@ -2,6 +2,7 @@ import { Page } from "@playwright/test"; import { PartQuestionsPage } from "./PartQuestionPage"; import { step } from "@business-logic/types/Step"; import { ITestData } from "@business-logic/types/ITestData"; +import { ProgressBarPercentages } from "@business-logic/types/Enums"; export default class MoldingQuestionsPage extends PartQuestionsPage { url = process.env['BASE_URL']! + '/fmg/?fmgPage=molding-questions'; @@ -14,7 +15,7 @@ export default class MoldingQuestionsPage extends PartQuestionsPage { async handleMoldingQuestionsPage(testCase: Partial) { const { moldingQuestions } = testCase; - await this.validateProgressBar("40"); + await this.validateProgressBar(ProgressBarPercentages.MoldingQuestionsPage); await this.validatePartQuestions(moldingQuestions!); await this.selectPartQuestionResponses(moldingQuestions!); await this.nextPage(); diff --git a/playwright-tests/pages/OrderConfirmationPage.ts b/playwright-tests/pages/OrderConfirmationPage.ts index c66f95497..588ecb196 100644 --- a/playwright-tests/pages/OrderConfirmationPage.ts +++ b/playwright-tests/pages/OrderConfirmationPage.ts @@ -2,7 +2,7 @@ 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, AppointmentType } from '@business-logic/types/Enums'; +import { ServicePackage, PaymentType, PaymentMethod, AppointmentType, ProgressBarPercentages } from '@business-logic/types/Enums'; import { ITestData } from '@business-logic/types/ITestData'; import { step } from '@business-logic/types/Step'; @@ -69,7 +69,7 @@ export class OrderConfirmationPage extends BasePage { expect.soft(servicePackageValue).toContain('New wiper blades'); } if (servicePackage === ServicePackage.Premium) { - expect.soft(servicePackageValue).toContain('Rain Defense™'); + expect.soft(servicePackageValue).toContain('Rain Repel Treatment'); } // Promo Code Validation @@ -178,12 +178,12 @@ export class OrderConfirmationPage extends BasePage { @step("OrderConfirmationPage >> Validate order") async verifyOrderConfirmationPage(testData: Partial) { - - await this.validateProgressBar("100"); + await this.page.waitForURL(new RegExp('(.+)confirmation'), {timeout: 60000}); + await this.validateProgressBar(ProgressBarPercentages.OrderConfirmationPage); 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 8ecced581..46b4b2440 100644 --- a/playwright-tests/pages/PartQuestionPage.ts +++ b/playwright-tests/pages/PartQuestionPage.ts @@ -3,6 +3,7 @@ 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'; +import { ProgressBarPercentages } from '@business-logic/types/Enums'; export class PartQuestionsPage extends BasePage { readonly page: Page; @@ -63,7 +64,7 @@ export class PartQuestionsPage extends BasePage { async handlePartQuestionsPage(testData: Partial) { const { partQuestions } = testData; - await this.validateProgressBar("40"); + await this.validateProgressBar(ProgressBarPercentages.PartQuestionsPage); 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 0705955f9..240d0e03e 100644 --- a/playwright-tests/pages/PaymentMethodPage.ts +++ b/playwright-tests/pages/PaymentMethodPage.ts @@ -1,7 +1,7 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { BasePage } from './BasePage'; import { IPaymentDetails } from '@business-logic/types/CustomerDetails'; -import { AppointmentTimeslot, AppointmentType, PaymentMethod, PaymentType, ServicePackage, VehicleDamage } from '@business-logic/types/Enums'; +import { AppointmentTimeslot, AppointmentType, PaymentMethod, PaymentType, ProgressBarPercentages, ServicePackage, VehicleDamage } from '@business-logic/types/Enums'; import { PaymentPage } from './PaymentPage'; import { AfterpayPage } from './AfterpayPage'; import { PaypalPage } from './PaypalPage'; @@ -148,7 +148,7 @@ export class PaymentMethodPage extends BasePage { expect.soft(servicePackageValue).toContain('New wiper blades'); } if (servicePackage === ServicePackage.Premium) { - expect.soft(servicePackageValue).toContain('Rain repel treatment'); + expect.soft(servicePackageValue).toContain('Rain Repel Treatment'); } // Promo Code Validation @@ -159,7 +159,7 @@ export class PaymentMethodPage extends BasePage { // Early Bird line item validation if (appointmentDetails?.appointmentTimeSlot == AppointmentTimeslot.EarlyBird) { - expect.soft(servicePackageValue).toContain('Early Bird'); + expect.soft(servicePackageValue).toContain('Early bird'); } } } @@ -470,7 +470,7 @@ l async handlePaymentMethodPage(testData: Partial) { const { servicePackage, isRecalVehicle, paymentDetails } = testData; - await this.validateProgressBar("92"); + await this.validateProgressBar(ProgressBarPercentages.PaymentMethodPage); await this.validatePaymentDetailsPage(testData); // Verify VAPS wipers on backend for standard and premium packages diff --git a/playwright-tests/pages/PaypalPage.ts b/playwright-tests/pages/PaypalPage.ts index 7afda3d5b..4d957eb33 100644 --- a/playwright-tests/pages/PaypalPage.ts +++ b/playwright-tests/pages/PaypalPage.ts @@ -11,6 +11,7 @@ export class PaypalPage extends BasePage { readonly passwordTextBox: Locator; readonly paypalLoginButton: Locator; readonly completePurchaseButton: Locator; + readonly payWithRadioButton: Locator; readonly payButton: Locator; constructor(page: Page) { @@ -23,6 +24,7 @@ export class PaypalPage extends BasePage { this.passwordTextBox = page.getByPlaceholder('Password'); this.paypalLoginButton = page.getByRole('button', { name: 'Log In', exact: true }); this.completePurchaseButton = page.getByTestId('submit-button-initial') + this.payWithRadioButton = page.locator('.py-4').first(); this.payButton = page.getByRole('button', { name: 'Pay $' }); } @@ -42,6 +44,7 @@ export class PaypalPage extends BasePage { await this.usePasswordInsteadButton.click(); await this.passwordTextBox.fill(paymentDetails.password!); await this.paypalLoginButton.click(); + await this.payWithRadioButton.click(); await this.payButton.click(); } } diff --git a/playwright-tests/pages/SchedulePage.ts b/playwright-tests/pages/SchedulePage.ts index 16f70b333..5fdcf5ce3 100644 --- a/playwright-tests/pages/SchedulePage.ts +++ b/playwright-tests/pages/SchedulePage.ts @@ -2,7 +2,7 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { BasePage } from './BasePage'; import { IAppointmentDetails, ICustomerDetails } from '@business-logic/types/CustomerDetails'; import { formatDate, formatTime } from '@impl/utils/DateUtils'; -import { AppointmentTimeslot, AppointmentType, ServiceLocation } from '@business-logic/types/Enums'; +import { AppointmentTimeslot, AppointmentType, ProgressBarPercentages, ServiceLocation } from '@business-logic/types/Enums'; import { time } from 'console'; import { step } from '@business-logic/types/Step'; import { ITestData } from '@business-logic/types/ITestData'; @@ -104,7 +104,7 @@ export class SchedulePage extends BasePage { @step("SchedulePage >> Schedule appointment: ") async handleSchedulePage(testData: Partial) { - await this.validateProgressBar("72"); + await this.validateProgressBar(ProgressBarPercentages.SchedulePage); await this.scheduleFirstAppointment(testData); } } \ No newline at end of file diff --git a/playwright-tests/pages/ServiceLocationPage.ts b/playwright-tests/pages/ServiceLocationPage.ts index 375566c04..747534501 100644 --- a/playwright-tests/pages/ServiceLocationPage.ts +++ b/playwright-tests/pages/ServiceLocationPage.ts @@ -1,7 +1,7 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { BasePage } from './BasePage'; import { IAppointmentDetails } from '@business-logic/types/CustomerDetails'; -import { AppointmentTimeslot, AppointmentType } from '@business-logic/types/Enums'; +import { AppointmentTimeslot, AppointmentType, ProgressBarPercentages } from '@business-logic/types/Enums'; import { AddressForm } from './forms/AddressForm'; import { faker } from '@faker-js/faker'; import { step } from '@business-logic/types/Step'; @@ -48,7 +48,7 @@ export class ServiceLocationPage extends BasePage { // Initial selection this.inShopButton = this.page.getByText(/In-shop/); - this.mobileButton = this.page.getByText(/Mobile/).nth(0); + this.mobileButton = this.page.locator('label[buttonlabel="Mobile"]'); this.dropOffButton = this.page.getByText(/Drop-off/); this.RecalWarningMessage1 = this.page.getByText(/We're not able to provide mobile service/); this.RecalWarningMessage2 = this.page.getByText(/advanced safety system recalibration needs to be done in our shop./); @@ -63,7 +63,7 @@ export class ServiceLocationPage extends BasePage { // For mobile - this.enterServiceAddressButton = this.page.getByRole('link', { name: 'Enter your service address' }); + this.enterServiceAddressButton = this.page.getByRole('link', { name: 'Enter your service address ' }); this.serviceAddressTextBox = this.page.getByRole('textbox', { name: 'Street Address' }); this.aptNumberTextBox = this.page.getByRole('textbox', { name: 'Apt. number'}); this.cityTextBox = this.page.getByRole('textbox', { name: 'City' }); @@ -118,9 +118,7 @@ export class ServiceLocationPage extends BasePage { const { appointmentDetails, customerDetails } = testData; if (appointmentDetails?.serviceAddress) { await this.mobileButton.click(); - if (!(await this.serviceAddressTextBox.isVisible())) { - await this.enterServiceAddressButton.click(); - } + // await this.enterServiceAddressButton.click(); await this.addressForm.populateAddress({ address: appointmentDetails.serviceAddress! }); if (await this.repeatedClicksModalCloseButton.isVisible()) { await this.repeatedClicksModalCloseButton.click(); @@ -164,7 +162,7 @@ export class ServiceLocationPage extends BasePage { @step("ServiceLocationPage >> Select service location: ") async handleServiceLocationPage(testData: Partial) { - await this.validateProgressBar("60"); + await this.validateProgressBar(ProgressBarPercentages.ServiceLocationPage); await this.selectLocation(testData); await this.nextPage(); } diff --git a/playwright-tests/pages/ServicePackagesPage.ts b/playwright-tests/pages/ServicePackagesPage.ts index 6a464d77e..e670ff31b 100644 --- a/playwright-tests/pages/ServicePackagesPage.ts +++ b/playwright-tests/pages/ServicePackagesPage.ts @@ -1,6 +1,6 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { BasePage } from './BasePage'; -import { ServicePackage, VehicleDamage } from '@business-logic/types/Enums'; +import { ProgressBarPercentages, 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'; @@ -185,7 +185,7 @@ export class ServicePackagesPage extends BasePage { async handleServicePackagePage(testData: Partial) { const { customerDetails, paymentMethod, servicePackage, promoCode, canNotRecal, dynamicRecal, hasOemEndorsement, vehicleDamage } = testData; - await this.validateProgressBar("48"); + await this.validateProgressBar(ProgressBarPercentages.ServicePackagePage); // Define repair damage types (vs. replacement types) const repairTypes: VehicleDamage[] = [ VehicleDamage.WindshieldOneChip, diff --git a/playwright-tests/pages/ServiceZipPage.ts b/playwright-tests/pages/ServiceZipPage.ts index cf2574b22..eb2d901cf 100644 --- a/playwright-tests/pages/ServiceZipPage.ts +++ b/playwright-tests/pages/ServiceZipPage.ts @@ -2,6 +2,7 @@ import { Page } from "@playwright/test"; import { LookupPage } from "./LookupPage"; import { step } from "@business-logic/types/Step"; import { ITestData } from "@business-logic/types/ITestData"; +import { ProgressBarPercentages } from "@business-logic/types/Enums"; export class ServiceZipPage extends LookupPage { @@ -14,7 +15,7 @@ export class ServiceZipPage extends LookupPage { @step("ZipLookupPage >> Lookup by service ZIP: ") async handleServiceZipPage(testData: Partial) { const { customerDetails, vehicleDetails, alertFlags } = testData; - await this.validateProgressBar("32"); + await this.validateProgressBar(ProgressBarPercentages.ServiceZipPage); await this.enterZip(customerDetails!.address.postalCode!); await this.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); } diff --git a/playwright-tests/pages/VehicleDamagePage.ts b/playwright-tests/pages/VehicleDamagePage.ts index 6e56553e5..da83d63fc 100644 --- a/playwright-tests/pages/VehicleDamagePage.ts +++ b/playwright-tests/pages/VehicleDamagePage.ts @@ -1,6 +1,6 @@ import { type Locator, type Page, expect, test } from '@playwright/test'; import { BasePage } from './BasePage'; -import { SideDoorDamage, VehicleDamage, WindshieldDamage } from '@business-logic/types/Enums'; +import { ProgressBarPercentages, 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'; @@ -169,7 +169,7 @@ export class VehicleDamagePage extends BasePage { const {vehicleDamage} = testData; const {isRepairReplace, isRepairOnly} = testData.alertFlags || {}; - await this.validateProgressBar("16"); + await this.validateProgressBar(ProgressBarPercentages.VehicleDamagePage); await this.selectDamage(vehicleDamage!); // Handle alert conditions for vehicle damage if (isRepairReplace) { diff --git a/playwright-tests/pages/VehicleLookupAddressPage.ts b/playwright-tests/pages/VehicleLookupAddressPage.ts index 9fe02ea6f..d21edebbf 100644 --- a/playwright-tests/pages/VehicleLookupAddressPage.ts +++ b/playwright-tests/pages/VehicleLookupAddressPage.ts @@ -5,6 +5,7 @@ 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'; +import { ProgressBarPercentages } from '@business-logic/types/Enums'; export class VehicleLookupAddressPage extends LookupPage { readonly addressForm: AddressForm; @@ -32,7 +33,7 @@ export class VehicleLookupAddressPage extends LookupPage { async handleVehicleLookupAddressPage(testData: Partial) { const { customerDetails, vehicleDetails, alertFlags } = testData; - await this.validateProgressBar("32"); + await this.validateProgressBar(ProgressBarPercentages.VehicleLookupAddressPage); await this.lookupVehicleByAddress(customerDetails!, vehicleDetails!); await this.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); } diff --git a/playwright-tests/pages/VehicleLookupLicensePage.ts b/playwright-tests/pages/VehicleLookupLicensePage.ts index 6045fc4cc..3aea24670 100644 --- a/playwright-tests/pages/VehicleLookupLicensePage.ts +++ b/playwright-tests/pages/VehicleLookupLicensePage.ts @@ -3,6 +3,7 @@ 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'; +import { ProgressBarPercentages } from '@business-logic/types/Enums'; export class VehicleLookupLicensePage extends LookupPage { readonly licensePlateNumTextBox: Locator; @@ -31,7 +32,7 @@ export class VehicleLookupLicensePage extends LookupPage { async handleVehicleLookupLicensePage(testData: Partial) { const { customerDetails, vehicleDetails, alertFlags } = testData; - await this.validateProgressBar("32"); + await this.validateProgressBar(ProgressBarPercentages.VehicleLookupLicensePage); await this.enterPlateDetails(vehicleDetails!); await this.enterZip(customerDetails!.address.postalCode!); await this.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); diff --git a/playwright-tests/pages/VehiclePartsPage.ts b/playwright-tests/pages/VehiclePartsPage.ts index ca07ffd53..4bb801481 100644 --- a/playwright-tests/pages/VehiclePartsPage.ts +++ b/playwright-tests/pages/VehiclePartsPage.ts @@ -2,6 +2,7 @@ import { Page } from "@playwright/test"; import { PartQuestionsPage } from "./PartQuestionPage"; import { step } from "@business-logic/types/Step"; import { ITestData } from "@business-logic/types/ITestData"; +import { ProgressBarPercentages } from "@business-logic/types/Enums"; export default class VehiclePartQuestionsPage extends PartQuestionsPage{ url = process.env['BASE_URL']! + '/fmg/?fmgPage=vehicle-parts'; @@ -14,7 +15,7 @@ export default class VehiclePartQuestionsPage extends PartQuestionsPage{ async handleVehiclePartsPage(testCase: Partial) { const { vehiclePartQuestions } = testCase; - await this.validateProgressBar("40"); + await this.validateProgressBar(ProgressBarPercentages.VehiclePartsPage); await this.validatePartQuestions(vehiclePartQuestions!); await this.selectPartQuestionResponses(vehiclePartQuestions!); await this.nextPage(); diff --git a/playwright-tests/pages/VehicleSelectionPage.ts b/playwright-tests/pages/VehicleSelectionPage.ts index 6e78cfe6d..21be579d3 100644 --- a/playwright-tests/pages/VehicleSelectionPage.ts +++ b/playwright-tests/pages/VehicleSelectionPage.ts @@ -4,6 +4,7 @@ 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'; +import { ProgressBarPercentages } from '@business-logic/types/Enums'; export class VehicleSelectionPage extends BasePage { readonly page: Page; @@ -49,9 +50,9 @@ export class VehicleSelectionPage extends BasePage { @step("VehicleSelectionPage >> Select Vehicle: ") async handleVehicleSelectionPage(testData: Partial) { - await this.validateProgressBar("4"); - const { vehicleDetails, isHeavyTruck, customerDetails } = testData; - const { isHeavyTruckVehicleAlert, isSplitWindshield } = testData.alertFlags || {}; + await this.validateProgressBar(ProgressBarPercentages.VehicleSelectionPage); + const { vehicleDetails } = testData; + const { isHeavyTruckVehicle, isSplitWindshield } = testData.alertFlags || {}; await this.selectVehicle(vehicleDetails!); diff --git a/playwright-tests/pages/VinLookupPage.ts b/playwright-tests/pages/VinLookupPage.ts index acbeade7f..52f156c17 100644 --- a/playwright-tests/pages/VinLookupPage.ts +++ b/playwright-tests/pages/VinLookupPage.ts @@ -2,6 +2,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'; +import { ProgressBarPercentages } from '@business-logic/types/Enums'; export class VinLookupPage extends LookupPage { readonly vinLookupTextBox: Locator; @@ -27,7 +28,7 @@ export class VinLookupPage extends LookupPage { async handleVehicleLookupVinPage(testData: Partial) { const { customerDetails, vehicleDetails, alertFlags } = testData; - await this.validateProgressBar("32"); + await this.validateProgressBar(ProgressBarPercentages.VinLookupPage); await this.enterVin(vehicleDetails!.vin!); await this.enterZip(customerDetails!.address.postalCode!); await this.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); diff --git a/playwright-tests/pages/forms/AddressForm.ts b/playwright-tests/pages/forms/AddressForm.ts index acbdadec5..892a56c9b 100644 --- a/playwright-tests/pages/forms/AddressForm.ts +++ b/playwright-tests/pages/forms/AddressForm.ts @@ -1,7 +1,7 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { BasePage } from '../BasePage'; import { ICustomerDetails } from '@business-logic/types/CustomerDetails'; -import { faker } from '@faker-js/faker/locale/en'; +import { waitUntil } from '@impl/utils/TimingUtils'; export class AddressForm extends BasePage { readonly page: Page; @@ -12,6 +12,7 @@ export class AddressForm extends BasePage { readonly firstNameTextBox: Locator; readonly lastNameTextBox: Locator; readonly addressNotFoundMsg: Locator; + readonly addressSuggestionList: Locator; constructor(page: Page) { super(page); @@ -23,6 +24,7 @@ export class AddressForm extends BasePage { this.firstNameTextBox = page.getByRole('textbox', { name: 'First name' }); this.lastNameTextBox = page.getByRole('textbox', { name: 'Last name' }); this.addressNotFoundMsg = page.getByText('Address not found.'); + this.addressSuggestionList = page.locator('.pac-container .pac-item').first(); } async forceAddressFormToAppear() { @@ -38,13 +40,39 @@ export class AddressForm extends BasePage { if (customerDetails.address) { // Force address form to appear - await this.forceAddressFormToAppear(); + // await this.forceAddressFormToAppear(); + await this.streetAddressTextBox.click(); + await this.streetAddressTextBox.pressSequentially(`${customerDetails.address.street}, ${customerDetails.address.city}, ${customerDetails.address.state} ${customerDetails.address.postalCode}`).then( async() => { + await this.streetAddressTextBox.dispatchEvent('keydown', { key: 'ArrowLeft' } ); + await this.streetAddressTextBox.dispatchEvent('keyup', { key: 'ArrowLeft' }); + }); + + await waitUntil(async () => { + await this.addressSuggestionList.waitFor({ state: 'visible', timeout: 5000 }); + let text = await this.addressSuggestionList.textContent() || ''; + return ( + text.includes(customerDetails.address!.street) && + text.includes(customerDetails.address!.city) && + text.includes(customerDetails.address!.state) + ); + }); + + await this.page.hover(".pac-container .pac-item"); + await new Promise(resolve => setTimeout(resolve, 1000)); + await this.addressSuggestionList.click(); // Select the first suggestion + //await this.streetAddressTextBox.press('Tab'); // Move focus to ZIP code field // Fill address + await this.stateDrpDwn.waitFor({ state: 'visible', timeout: 5000 }).then(async () => { + const selectedState = await this.stateDrpDwn.inputValue(); + if (selectedState !== customerDetails.address!.state) { + await this.stateDrpDwn.selectOption(customerDetails.address!.state); + } + }) + await this.fillAndValidate(this.streetAddressTextBox, customerDetails.address.street); await this.fillAndValidate(this.zipCodeTextBox, customerDetails.address.postalCode) await this.fillAndValidate(this.cityTextBox, customerDetails.address.city); - await this.stateDrpDwn.selectOption(customerDetails.address.state); } if (customerDetails.firstName) { diff --git a/playwright-tests/playwright.config.ts b/playwright-tests/playwright.config.ts index 7d2133cc4..230ad948d 100644 --- a/playwright-tests/playwright.config.ts +++ b/playwright-tests/playwright.config.ts @@ -64,7 +64,7 @@ export default defineConfig({ ['junit'], ['list'] ], - timeout: 180_000, + timeout: 300_000, /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ diff --git a/playwright-tests/tests/CashRepairMobileCreditCard.ts b/playwright-tests/tests/CashRepairMobileCreditCard.ts index 892c8d71e..db8ebd8ce 100644 --- a/playwright-tests/tests/CashRepairMobileCreditCard.ts +++ b/playwright-tests/tests/CashRepairMobileCreditCard.ts @@ -12,9 +12,18 @@ setFakerSeedFromTestName("CashRepairMobileCreditCard"); const cashRepairMobileCCData : Partial = { ...getDefaultTestData(), // Get default data with current seed - //Override default vehicle damage (Windshield Crack) + // Override default vehicle damage (Windshield Crack) vehicleDamage: [VehicleDamage.WindshieldOneChip], - + + // Override customer postal code + customerDetails: { + ...getDefaultTestData().customerDetails!, + address: { + ...getDefaultTestData().customerDetails!.address, + postalCode: '91710' + } + }, + // Override specific fields with test-specific data vehicleDetails: { ...getDefaultTestData().vehicleDetails!, diff --git a/playwright-tests/tests/CashReplaceDynamicRecalMobile.ts b/playwright-tests/tests/CashReplaceDynamicRecalMobile.ts index 25bb3bf50..1f3d2e785 100644 --- a/playwright-tests/tests/CashReplaceDynamicRecalMobile.ts +++ b/playwright-tests/tests/CashReplaceDynamicRecalMobile.ts @@ -17,6 +17,14 @@ const cashReplaceDynamicRecalMobileData: Partial = { // Flag for dynamic Recalibration vehicle dynamicRecal: true, + + customerDetails: { + ...getDefaultTestData().customerDetails!, + address: { + ...getDefaultTestData().customerDetails!.address, + postalCode: '21237' + } + }, // Override vehicle details vehicleDetails: { @@ -37,7 +45,7 @@ const cashReplaceDynamicRecalMobileData: Partial = { appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate, serviceAddress: { // Use street address from current faker seed - street: getDefaultTestData().customerDetails!.address.street, + street: "5050 Silver Oak Dr", city: 'Rosedale', state: 'MD', postalCode: '21237', diff --git a/playwright-tests/tests/CashReplaceMultiGlassMobile.ts b/playwright-tests/tests/CashReplaceMultiGlassMobile.ts index eac5e2263..2974d41d8 100644 --- a/playwright-tests/tests/CashReplaceMultiGlassMobile.ts +++ b/playwright-tests/tests/CashReplaceMultiGlassMobile.ts @@ -12,13 +12,13 @@ setFakerSeedFromTestName("CashReplaceMultiGlassMobile"); const cashReplaceMultiGlassMobileData: Partial = { ...getDefaultTestData(), // Get default data with current seed - // Override customer postal code customerDetails: { ...getDefaultTestData().customerDetails!, address: { ...getDefaultTestData().customerDetails!.address, - postalCode: '43085' - } + postalCode: '21237' + }// Override customer postal code + }, // Flag for recalibration vehicle @@ -33,7 +33,7 @@ const cashReplaceMultiGlassMobileData: Partial = { appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate, serviceAddress: { // Use street address from current faker seed - street: getDefaultTestData().customerDetails!.address.street, + street: "5050 Silver Oak Dr", city: 'Rosedale', state: 'MD', postalCode: '21237', diff --git a/playwright-tests/tests/CashReplaceSafeliteCanNotRecalMobile.ts b/playwright-tests/tests/CashReplaceSafeliteCanNotRecalMobile.ts index f7d301da1..e1a8f577d 100644 --- a/playwright-tests/tests/CashReplaceSafeliteCanNotRecalMobile.ts +++ b/playwright-tests/tests/CashReplaceSafeliteCanNotRecalMobile.ts @@ -23,7 +23,7 @@ const cashReplaceSafeliteCanNotRecalMobileData: Partial = { ...getDefaultTestData().customerDetails!, address: { ...getDefaultTestData().customerDetails!.address, - postalCode: '43085' + postalCode: '21237' } }, @@ -46,7 +46,7 @@ const cashReplaceSafeliteCanNotRecalMobileData: Partial = { appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate, serviceAddress: { // Use street address from current faker seed - street: getDefaultTestData().customerDetails!.address.street, + street: "5050 Silver Oak Dr", city: 'Rosedale', state: 'MD', postalCode: '21237', diff --git a/playwright-tests/tests/CashReplaceVinMobile.ts b/playwright-tests/tests/CashReplaceVinMobile.ts index b9acf3542..cf6a9b515 100644 --- a/playwright-tests/tests/CashReplaceVinMobile.ts +++ b/playwright-tests/tests/CashReplaceVinMobile.ts @@ -20,7 +20,7 @@ const cashReplaceVinMobileData: Partial = { ...getDefaultTestData().customerDetails!, address: { ...getDefaultTestData().customerDetails!.address, - postalCode: '43085' + postalCode: '21237' } }, @@ -43,7 +43,7 @@ const cashReplaceVinMobileData: Partial = { appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate, serviceAddress: { // Use street address from current faker seed - street: getDefaultTestData().customerDetails!.address.street, + street: "5050 Silver Oak Dr", city: 'Rosedale', state: 'MD', postalCode: '21237', diff --git a/playwright-tests/tests/alert-validation/alert0007_VinNotFound.ts b/playwright-tests/tests/alert-validation/alert0007_VinNotFound.ts index 0ee058903..fccf12662 100644 --- a/playwright-tests/tests/alert-validation/alert0007_VinNotFound.ts +++ b/playwright-tests/tests/alert-validation/alert0007_VinNotFound.ts @@ -75,9 +75,9 @@ const lookupTypesToTest: LookupTestCase[] = [ }, customerDetails: { address: { - street: '4076 Spectacle Dr', + street: '4076 Spectacle Drive', city: 'Columbus', - state: 'Ohio', + state: 'OH', postalCode: '59261', country: 'United States' } diff --git a/src/App.vue b/src/App.vue index f4c5754aa..cae31dd24 100644 --- a/src/App.vue +++ b/src/App.vue @@ -7,6 +7,8 @@ + + diff --git a/src/fmg-components/funnel-header/progress-bar/progres-bar.spec.js b/src/fmg-components/funnel-header/progress-bar/progres-bar.spec.js deleted file mode 100644 index 9970b266c..000000000 --- a/src/fmg-components/funnel-header/progress-bar/progres-bar.spec.js +++ /dev/null @@ -1,81 +0,0 @@ -import { shallowMount } from "@vue/test-utils"; -import progressBar from "./progress-bar"; -import store from "@/store"; - -describe("progressBar", () => { - test("progress should be 0", () => { - // Arrange - mockMixin.computed = { - pageName: () => undefined, - }; - - // Act - const wrapper = shallowMount(progressBar, { - mixins: [mockMixin], - }); - - // Assert - expect(wrapper.vm.progress).toBe(0); - wrapper.unmount(); - }); -}); - -describe("progressBar", () => { - test("progress should be 4%", () => { - // Arrange - mockMixin.computed = { - pageName: () => "vehicle", - }; - - // Act - const wrapper = shallowMount(progressBar, { - mixins: [mockMixin], - }); - - // Assert - expect(wrapper.vm.progress).toBe(4); - wrapper.unmount(); - }); -}); - -describe("progressBar", () => { - test("progress should be 48%", () => { - // Arrange - mockMixin.computed = { - pageName: () => "quote", - }; - - // Act - const wrapper = shallowMount(progressBar, { - mixins: [mockMixin], - }); - - // Assert - expect(wrapper.vm.progress).toBe(48); - wrapper.unmount(); - }); -}); - -describe("progressBar", () => { - test("progress should be 100%", () => { - // Arrange - mockMixin.computed = { - pageName: () => "confirmation", - }; - - // Act - const wrapper = shallowMount(progressBar, { - mixins: [mockMixin], - }); - - // Assert - expect(wrapper.vm.progress).toBe(100); - wrapper.unmount(); - }); -}); - -const mockMixin = { - methods: { - getProgress: jest.fn(), - }, -}; diff --git a/src/fmg-components/funnel-header/progress-bar/progress-bar.spec.js b/src/fmg-components/funnel-header/progress-bar/progress-bar.spec.js new file mode 100644 index 000000000..22c24ed4a --- /dev/null +++ b/src/fmg-components/funnel-header/progress-bar/progress-bar.spec.js @@ -0,0 +1,68 @@ +import { mount } from "@vue/test-utils"; +import ProgressBar from "./progress-bar.vue"; + +jest.mock("@/constants/progress-bar-mapper", () => ({ + getProgressBarPercentage: jest.fn(), +})); + +import { getProgressBarPercentage } from "@/constants/progress-bar-mapper"; + +describe("progress-bar.vue", () => { + beforeEach(() => { + jest.useFakeTimers(); + getProgressBarPercentage.mockReset(); + }); + + afterEach(() => { + jest.clearAllTimers(); + jest.useRealTimers(); + }); + + it("renders with correct initial progress", async () => { + getProgressBarPercentage.mockReturnValueOnce(30); + const wrapper = mount(ProgressBar, { + props: { page: "page1" }, + }); + // Immediately after mount, width should be lastProgress (0) + expect(wrapper.find(".progress-bar-inner").attributes("style")).toContain("width: 0%"); + // Advance timer to trigger animation + jest.runAllTimers(); + await wrapper.vm.$nextTick(); + expect(wrapper.find(".progress-bar-inner").attributes("style")).toContain("width: 30%"); + }); + + it("animates to new progress when page prop changes", async () => { + getProgressBarPercentage.mockReturnValueOnce(30); + const wrapper = mount(ProgressBar, { + props: { page: "page1" }, + }); + jest.runAllTimers(); + await wrapper.vm.$nextTick(); + expect(wrapper.find(".progress-bar-inner").attributes("style")).toContain("width: 30%"); + + getProgressBarPercentage.mockReturnValueOnce(60); + await wrapper.setProps({ page: "page2" }); + // Before timer, still old value + expect(wrapper.find(".progress-bar-inner").attributes("style")).toContain("width: 30%"); + jest.runAllTimers(); + await wrapper.vm.$nextTick(); + expect(wrapper.find(".progress-bar-inner").attributes("style")).toContain("width: 60%"); + }); + + it("clears timeout on unmount", async () => { + getProgressBarPercentage.mockReturnValueOnce(50); + const wrapper = mount(ProgressBar, { + props: { page: "page1" }, + }); + const clearTimeoutSpy = jest.spyOn(window, "clearTimeout"); + wrapper.unmount(); + expect(clearTimeoutSpy).toHaveBeenCalled(); + clearTimeoutSpy.mockRestore(); + }); +}); + +const mockMixin = { + methods: { + getProgress: jest.fn(), + }, +}; diff --git a/src/fmg-components/funnel-header/progress-bar/progress-bar.vue b/src/fmg-components/funnel-header/progress-bar/progress-bar.vue index ebce6e0de..f8b654e7f 100644 --- a/src/fmg-components/funnel-header/progress-bar/progress-bar.vue +++ b/src/fmg-components/funnel-header/progress-bar/progress-bar.vue @@ -1,6 +1,6 @@ @@ -8,20 +8,69 @@ import store from "@/store"; import { getProgressBarPercentage } from "@/constants/progress-bar-mapper"; +// Module-level variable to persist progress across component lifecycles +let lastProgress = 0; + export default { - name: "progressBar", - data() {}, + name: "progress-bar", + props: { + page: { + type: String, + required: true, + }, + }, + data() { + return { + displayedProgress: lastProgress, + timeoutId: null, + }; + }, + watch: { + page: { + immediate: true, + handler(newPage) { + const target = getProgressBarPercentage(newPage); + if (this.timeoutId) clearTimeout(this.timeoutId); + + // Animate from current value to new value + this.timeoutId = setTimeout(() => { + this.displayedProgress = target; + lastProgress = target; // Update the module-level variable + }, 150); + }, + }, + }, + beforeUnmount() { + if (this.timeoutId) clearTimeout(this.timeoutId); + }, computed: { - progress() { - return getProgressBarPercentage(this.pageName); + progressStyle() { + return { + width: this.displayedProgress + "%", + }; }, }, }; diff --git a/src/layouts/payment-method/afterpay-breakout/afterpay-breakout.vue b/src/layouts/payment-method/afterpay-breakout/afterpay-breakout.vue index abe6221ba..9e98eb9c3 100644 --- a/src/layouts/payment-method/afterpay-breakout/afterpay-breakout.vue +++ b/src/layouts/payment-method/afterpay-breakout/afterpay-breakout.vue @@ -1,6 +1,9 @@