diff --git a/playwright-tests/.env b/playwright-tests/.env index 179424e5e..e9de46d32 100644 --- a/playwright-tests/.env +++ b/playwright-tests/.env @@ -9,8 +9,9 @@ SKIP_CONTENT_SITE="false" # Experiments Flag IS_MOBILEFIRST="false" -IS_ADYENPAYMENTS="false" +IS_ADYENPAYMENTS="true" IS_MULTILOCATIONPOPUP="false" +IS_MSRSPLITPAY="false" # Base URLs by environment # qa diff --git a/playwright-tests/.env.dev b/playwright-tests/.env.dev index de2c3c0f2..80841fd14 100644 --- a/playwright-tests/.env.dev +++ b/playwright-tests/.env.dev @@ -2,15 +2,16 @@ # Environment configuration # Environment type -PLAYWRIGHT_ENV="qa" +PLAYWRIGHT_ENV="QA" # Skip content site SKIP_CONTENT_SITE="false" # Experiments Flag IS_MOBILEFIRST="false" -IS_ADYENPAYMENTS="false" +IS_ADYENPAYMENTS="true" IS_MULTILOCATIONPOPUP="false" +IS_MSRSPLITPAY="false" # Base URLs by environment # qa diff --git a/playwright-tests/framework/TestData.ts b/playwright-tests/framework/TestData.ts index 6a87dfd8f..332e3dc26 100644 --- a/playwright-tests/framework/TestData.ts +++ b/playwright-tests/framework/TestData.ts @@ -11,6 +11,10 @@ export interface ITestData extends base { mockFirstInshopCallNoSchedule?: boolean, handleMobileFirstModal?: boolean, experiments?: IExperiments + isMSRGlassPart?: boolean, + isMSRZip?: boolean, + isPIAEnabled?: boolean, + isDualRecal?: boolean, } @@ -19,5 +23,6 @@ export function getDefaultExperimentsData(): IExperiments { isAdyenPayments: !!process.env.IS_ADYENPAYMENTS && process.env.IS_ADYENPAYMENTS !== "" ? process.env.IS_ADYENPAYMENTS === "true" : false, isMobileFirst: !!process.env.IS_MOBILEFIRST && process.env.IS_MOBILEFIRST !== "" ? process.env.IS_MOBILEFIRST === "true" : false, isMultiLocationPopup: !!process.env.IS_MULTILOCATIONPOPUP && process.env.IS_MULTILOCATIONPOPUP !== "" ? process.env.IS_MULTILOCATIONPOPUP === "true" : false, + isMsrSplitPay: !!process.env.IS_MSRSPLITPAY && process.env.IS_MSRSPLITPAY !== "" ? process.env.IS_MSRSPLITPAY === "true" : false, } } diff --git a/playwright-tests/framework/TestPages.ts b/playwright-tests/framework/TestPages.ts index c2cc04b35..bc98e44b5 100644 --- a/playwright-tests/framework/TestPages.ts +++ b/playwright-tests/framework/TestPages.ts @@ -20,6 +20,7 @@ import VehiclePartQuestionsPage from "../pages/VehiclePartsPage" import CapabilityQuestionsPage from "../pages/CapabilityQuestionsPage" import MoldingQuestionsPage from "../pages/MoldingQuestionsPage" import { InsuranceCompanyPage } from "../pages/InsuranceCompanyPage" +import { InsuranceDetailsPage } from "../pages/InsuranceDetailsPage" import { CCPolicyInfoPage } from "../pages/CCPolicyInfoPage" import { DuplicateCheckPage } from "../pages/DuplicateCheckPage" import { PolicyVehiclesPage } from "../pages/PolicyVehiclesPage" @@ -42,6 +43,7 @@ export interface ITestPages { estimatePage: EstimatePage, homePage: HomePage, insuranceCompanyPage: InsuranceCompanyPage, + insuranceDetailsPage: InsuranceDetailsPage, leadgenHomePage: LeadgenHomePage, moldingQuestionsPage: MoldingQuestionsPage, orderConfirmationPage: OrderConfirmationPage, @@ -78,6 +80,7 @@ export const createTestPages: TestPagesFactory = (page: Page) => { estimatePage: new EstimatePage(page), homePage: new HomePage(page), insuranceCompanyPage: new InsuranceCompanyPage(page), + insuranceDetailsPage: new InsuranceDetailsPage(page), leadgenHomePage: new LeadgenHomePage(page), moldingQuestionsPage: new MoldingQuestionsPage(page), orderConfirmationPage: new OrderConfirmationPage(page), diff --git a/playwright-tests/framework/localTypes/Enums.ts b/playwright-tests/framework/localTypes/Enums.ts index d112f9110..3e69f19e7 100644 --- a/playwright-tests/framework/localTypes/Enums.ts +++ b/playwright-tests/framework/localTypes/Enums.ts @@ -18,6 +18,7 @@ export enum ProgressBarPercentages { VehiclePartsPage = '20%', ServicePackagePage = '60%', InsuranceCompanyPage = '60%', + InsuranceDetailsPage = '55%', ServiceLocationPage = '76%', SchedulePage = '76%', MobileDetailsPage = '76%', diff --git a/playwright-tests/framework/localTypes/IExperiments.ts b/playwright-tests/framework/localTypes/IExperiments.ts index bfe13c864..ec0d15180 100644 --- a/playwright-tests/framework/localTypes/IExperiments.ts +++ b/playwright-tests/framework/localTypes/IExperiments.ts @@ -1,5 +1,6 @@ export interface IExperiments { isMobileFirst: boolean, isAdyenPayments: boolean, - isMultiLocationPopup: boolean + isMultiLocationPopup: boolean, + isMsrSplitPay: boolean } \ No newline at end of file diff --git a/playwright-tests/pages/BasePage.ts b/playwright-tests/pages/BasePage.ts index e9fe835dc..4e6a9e93c 100644 --- a/playwright-tests/pages/BasePage.ts +++ b/playwright-tests/pages/BasePage.ts @@ -19,6 +19,7 @@ export class BasePage { readonly hamburgerMenu: Locator; readonly progressBar: Locator; readonly loaders: Locator; + readonly yellowAlertMessage: Locator; constructor(page: Page) { this.page = page; @@ -29,6 +30,7 @@ export class BasePage { this.hamburgerMenu = this.page.getByRole('button', { name: 'Hamburger Menu (modal window)' }); this.progressBar = this.page.locator('.progress-bar-outer .progress-bar-inner'); this.loaders = this.page.locator('.spinner-border, .loader'); + this.yellowAlertMessage = this.page.locator('.alert-warning'); } async nextPage() { @@ -135,42 +137,30 @@ export class BasePage { async mockScheduleResponseForFirstInshopCallNoSchedule(customerDetails: ICustomerDetails) { - let fistInshopScheduleCall = true; + // let fistInshopScheduleCall = true; const apiUrl = `https://digitalapi.${process.env['PLAYWRIGHT_ENV']!.replace('sys', 'test').toLowerCase()}.safelite.io/schedule/api/v1/schedule/shop-time-slots`; await this.page.route(apiUrl, async (route) => { - if (!fistInshopScheduleCall) { + /*if (!fistInshopScheduleCall) { return route.continue(); - } - - // const fmt = (d: Date) => `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,"0")}-${String(d.getDate()).padStart(2,"0")}`; - // const startDate = new Date(); - //const beginningOfWeek = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate() - startDate.getDay()); - // const endDate = new Date(beginningOfWeek.getFullYear(), beginningOfWeek.getMonth(), beginningOfWeek.getDate() + 13); - // e.g., "2025-07-23" - /*if (route.request().postDataJSON().startDate === fmt(startDate) && route.request().postDataJSON().endDate === fmt(endDate)) { - const response = await route.fetch(); - const responseBody = await response.json(); - - responseBody.days = []; - // Mock the response - await route.fulfill({ - response, - body: JSON.stringify(responseBody), - }); }*/ const response = await route.fetch(); const responseBody = await response.json(); - responseBody.days = []; + // Remove up to the first five items from the days array, keeping the rest; if empty, leave as is + if (Array.isArray(responseBody.days) && responseBody.days.length > 0) { + responseBody.days = responseBody.days.slice(5); + } else { + responseBody.days = []; + } // Mock the response await route.fulfill({ response, body: JSON.stringify(responseBody), }); - fistInshopScheduleCall = false; + // fistInshopScheduleCall = false; }); } @@ -252,9 +242,9 @@ export class BasePage { if (experiments !== undefined) { experimentsURLExtension += "?cns=all&experiments=" - experimentsURLExtension += experiments?.isMobileFirst + /*experimentsURLExtension += experiments?.isMobileFirst ? "MobileFirstAppointment=MobileFirstAppt_V1=MobileFirstAppt_TEST=true" - : "MobileFirstAppointment=MobileFirstAppt_V1=MobileFirstAppt_CONTROL=true"; + : "MobileFirstAppointment=MobileFirstAppt_V1=MobileFirstAppt_CONTROL=true";*/ experimentsURLExtension += experiments?.isAdyenPayments ? ",Adyen%20Payments=Adyen%20Payment%20Test=Adyen%20Payment%20(Test)=true" @@ -263,11 +253,15 @@ export class BasePage { experimentsURLExtension += experiments?.isMultiLocationPopup ? ",MultiLocationPopup=MultiLocationPopup_V1=MultiLocationPopup_TEST=true" : ",MultiLocationPopup=MultiLocationPopup_V1=MultiLocationPopup_CONTROL=true"; + + experimentsURLExtension += experiments?.isMsrSplitPay + ? ",MSR=MSR_With_Splitpay=YesShowMSR_TEST=true" + : ",MSR=MSR_With_Splitpay=NoShowMSR_CONTROL=true"; } else { console.log("Url extension without query string"); } // Convert to HTML encoding before returning - return experimentsURLExtension; + return process.env.PLAYWRIGHT_ENV == 'sys' ? '' : experimentsURLExtension; } } \ No newline at end of file diff --git a/playwright-tests/pages/HomePage.ts b/playwright-tests/pages/HomePage.ts index 52ceca875..d4703c29c 100644 --- a/playwright-tests/pages/HomePage.ts +++ b/playwright-tests/pages/HomePage.ts @@ -21,7 +21,7 @@ export class HomePage extends BasePage { readonly viewQuoteButton: Locator; //Zip Entry - readonly widgetZipEntryField: Locator; + readonly widgetZipEntryInputField: Locator; readonly getQuoteAndScheduleButton: Locator; url = process.env['BASE_URL']!; @@ -30,7 +30,7 @@ export class HomePage extends BasePage { super(page); this.page = page; - this.letsGetStartedButton = this.page.getByRole('button', { name: 'Let\'s get started' }); + this.letsGetStartedButton = this.page.locator('a.zip-submit-button'); this.cusmodalPopup = this.page.locator('#Cusmodalpopup'); this.closePopupButton = this.page.getByRole('button', { name: '×' }); @@ -46,7 +46,7 @@ export class HomePage extends BasePage { this.viewQuoteButton = this.page.locator('#ctaSubmit'); //Zip Entry - this.widgetZipEntryField = this.page.getByRole('textbox', { name: 'Enter service ZIP code' }); + this.widgetZipEntryInputField = this.page.locator('input#zipcode'); this.getQuoteAndScheduleButton = this.page.getByLabel('main').getByRole('link', { name: 'Get quote + schedule' }); } @@ -61,7 +61,7 @@ export class HomePage extends BasePage { async letsGetStarted(zip: string, enterFunnelWithZip: boolean) { if (enterFunnelWithZip) { - await this.widgetZipEntryField.fill(zip); + await this.widgetZipEntryInputField.fill(zip); /* await this.letsGetStartedButton.evaluate((element, zip) => { const currentHref = element.getAttribute('href') || ''; element.setAttribute('href', `${currentHref}?zipCode=${zip}`); diff --git a/playwright-tests/pages/InsuranceDetailsPage.ts b/playwright-tests/pages/InsuranceDetailsPage.ts new file mode 100644 index 000000000..4b00e98a3 --- /dev/null +++ b/playwright-tests/pages/InsuranceDetailsPage.ts @@ -0,0 +1,61 @@ +import { type Locator, type Page } from '@playwright/test'; +import { BasePage } from './BasePage'; +import { step } from 'framework/localTypes/Step'; +import { ITestData } from 'framework/TestData'; +import { ProgressBarPercentages } from 'framework/localTypes/Enums'; + +export class InsuranceDetailsPage extends BasePage { + + readonly page: Page; + readonly insuranceCompany: Locator; + readonly policyNumber: Locator; + readonly dateOfLoss: Locator; + readonly damageCause: Locator; + readonly claimNumber: Locator; // conditional — only shown for certain insurers like Kentucky Farm Bureau + readonly streetAddress: Locator; + readonly apartment: Locator; + readonly city: Locator; + readonly policyState: Locator; + readonly policyZip: Locator; + + constructor(page: Page) { + super(page); + this.page = page; + + this.insuranceCompany = page.getByLabel('Insurance company'); + this.policyNumber = page.getByLabel('Policy number'); + this.dateOfLoss = page.getByLabel('Approximate date of loss'); + this.claimNumber = page.getByLabel('Claim Number'); + this.damageCause = page.getByLabel('How did the damage occur?'); + this.streetAddress = page.getByLabel('Street address'); + this.apartment = page.getByLabel('Apt. number (optional)'); + this.city = page.getByLabel('City'); + this.policyState = page.getByLabel('State'); + this.policyZip = page.getByLabel('Zip'); + } + + private async populatePage(testData: Partial): Promise { + const { claimDetails, customerDetails } = testData; + + await this.policyNumber.fill(claimDetails!.policyNumber); + await this.dateOfLoss.fill(claimDetails!.damageDate); + await this.damageCause.selectOption(claimDetails!.damageCause); + await this.streetAddress.fill(customerDetails!.address.street); + await this.apartment.fill(customerDetails!.address.aptNumber ?? ''); + await this.city.fill(customerDetails!.address.city); + await this.policyState.selectOption(customerDetails!.address.state); + await this.policyZip.fill(customerDetails!.address.postalCode); + + if (await this.claimNumber.isVisible()) { + await this.claimNumber.fill(claimDetails!.claimNumber ?? ''); + } + } + + @step("InsuranceDetailsPage >> Fill out insurance details for non integrated insurance flow") + async handleInsuranceDetailsPage(testData: Partial): Promise { + await this.validateProgressBar(ProgressBarPercentages.InsuranceDetailsPage); + await this.populatePage(testData); + await this.nextPage(); + } + +} \ No newline at end of file diff --git a/playwright-tests/pages/PaymentMethodPage.ts b/playwright-tests/pages/PaymentMethodPage.ts index 215ccff30..d0ef408a6 100644 --- a/playwright-tests/pages/PaymentMethodPage.ts +++ b/playwright-tests/pages/PaymentMethodPage.ts @@ -272,9 +272,10 @@ export class PaymentMethodPage extends BasePage { if (await this.payAtServiceButton.isVisible()) { await this.payAtServiceButton.click(); } else if (isRecalVehicle) { - await this.recalibrationCheckbox.click(); + if (await this.recalibrationCheckbox.isVisible()) { + await this.recalibrationCheckbox.click(); + } } - } async verifyVAPS(): Promise { @@ -559,8 +560,10 @@ export class PaymentMethodPage extends BasePage { await this.validateProgressBar(ProgressBarPercentages.PaymentMethodPage); await this.validatePaymentDetailsPage(testData); - await this.ValidateAfterPayBreakOutSection(); - + if (testData.isPIAEnabled) { + await this.ValidateAfterPayBreakOutSection(); + } + if (isForcedOEM) { await this.validateOEMPart(isForcedOEM); } diff --git a/playwright-tests/pages/PaypalPage.ts b/playwright-tests/pages/PaypalPage.ts index 650acc27a..1c4bef5b6 100644 --- a/playwright-tests/pages/PaypalPage.ts +++ b/playwright-tests/pages/PaypalPage.ts @@ -47,10 +47,10 @@ export class PaypalPage extends BasePage { await this.page.screenshot({ path: `test-results\\ortoni-data\\paypal-username-${Date.now()}.png`, fullPage: true }); await this.usernameTextBox.fill(paymentDetails!.username!); await this.nextButton.click(); - if (!experiments!.isAdyenPayments){ + /*if (!experiments!.isAdyenPayments){ await this.tryAnotherWayButton.click(); await this.usePasswordInsteadButton.click(); - } + }*/ await this.passwordTextBox.fill(paymentDetails!.password!); await this.paypalLoginButton.click(); await this.payWithRadioButton.click(); diff --git a/playwright-tests/pages/SchedulePage.ts b/playwright-tests/pages/SchedulePage.ts index 30fc830d0..088049693 100644 --- a/playwright-tests/pages/SchedulePage.ts +++ b/playwright-tests/pages/SchedulePage.ts @@ -80,6 +80,15 @@ export class SchedulePage extends BasePage { async selectLocation(testData: Partial) { const { appointmentDetails, customerDetails } = testData; + if (testData.isMSRGlassPart || testData.isDualRecal) { + if (testData.isMSRZip) { + expect(await this.mobileButton.isVisible()).toBe(true); + } else { + expect(await this.mobileButton.isVisible()).toBe(false); + expect(await this.yellowAlertMessage.locator('.alert-heading').filter({ hasText: ' We\'re not able to provide mobile service.' }).isVisible()).toBe(true); + } + } + switch(appointmentDetails?.serviceLocation) { case ServiceLocation.Mobile: await this.scheduleMobile(testData); diff --git a/playwright-tests/pages/ServicePackagesPage.ts b/playwright-tests/pages/ServicePackagesPage.ts index 2f16cb7b4..8fbc22a87 100644 --- a/playwright-tests/pages/ServicePackagesPage.ts +++ b/playwright-tests/pages/ServicePackagesPage.ts @@ -5,6 +5,7 @@ import { ProgressBarPercentages } from 'framework/localTypes/Enums'; import { PaymentMethod } from "framework/localTypes/Enums"; import { step } from 'framework/localTypes/Step'; import { ITestData } from 'framework/TestData'; +import { PaymentMethodPage } from './PaymentMethodPage'; export class ServicePackagesPage extends BasePage { readonly page: Page; @@ -227,6 +228,32 @@ export class ServicePackagesPage extends BasePage { } } + async verifyGlassCashQuoteEvent() { + // Get displayed price text for Glass Only package (e.g. "$41.25in 4 interest-free payments\nor $164.99 in single payment") + const expectedGlassOnlyPrice = await this.glassOnlypackagePrice.innerText(); + + // Extract the single payment amount (e.g. 164.99) from the end of the string using regex + const singlePaymentRegex = /\$([\d,]+\.\d{2})\s*in single payment/; + const match = expectedGlassOnlyPrice.match(singlePaymentRegex); + if (!match) { + throw new Error(`Expected single payment price to be present in: ${expectedGlassOnlyPrice}`); + } + const expectedSinglePayAmount = parseFloat(match[1].replace(',', '')); + + // Retrieve the dataLayer from the browser. (Assumes dataLayer is in global scope. Adjust if needed.) + const dataLayer = await this.page.evaluate(() => (window as any).dataLayer); + + // Find the first object with a GlassCashQuote property and extract its value. + const glassCashQuote = dataLayer?.find((item: any) => item && item.GlassCashQuote)?.GlassCashQuote; + if (glassCashQuote === undefined || glassCashQuote === null) { + throw new Error("No GlassCashQuote found in dataLayer"); + } + + // Validate that the GlassCashQuote value matches the extracted expectedSinglePayAmount (with currency float precision) + Soft.expect(Number.parseFloat(glassCashQuote)).toBe(expectedSinglePayAmount); + + } + @step("ServicePackagePage >> Select Payment Method and Service Type: ") async handleServicePackagePage(testData: Partial) { const { customerDetails, paymentMethod, paymentDetails, servicePackage, isCanNotRecal, isDynamicRecal, hasOemEndorsement, vehicleDamage, appointmentDetails, totalAmount } = testData; @@ -289,6 +316,10 @@ export class ServicePackagesPage extends BasePage { await this.verifyVehicleParts(vehicleDamage!); } + if (testData.paymentMethod == PaymentMethod.SelfPay) { + await this.verifyGlassCashQuoteEvent(); + } + // Validate backend for OEM endorsement if (hasOemEndorsement) { await this.verifyOEMPart(); diff --git a/playwright-tests/playwright.config.ts b/playwright-tests/playwright.config.ts index 026f5b2d9..f5295bc1c 100644 --- a/playwright-tests/playwright.config.ts +++ b/playwright-tests/playwright.config.ts @@ -100,7 +100,8 @@ export default defineConfig({ headless: process.env.CI ? true : false, screenshot: "only-on-failure", actionTimeout: 60_000, - navigationTimeout: 60_000 + navigationTimeout: 60_000, + bypassCSP: true }, /* Configure projects for major browsers */ diff --git a/playwright-tests/tests/0000__M.test.ts b/playwright-tests/tests/0000__M.test.ts index 0dcb5155e..79cfc89f8 100644 --- a/playwright-tests/tests/0000__M.test.ts +++ b/playwright-tests/tests/0000__M.test.ts @@ -19,12 +19,14 @@ import { ApiResponseInterceptUtil } from 'safelite-playwright-core'; import cashReplaceGlassPromoInshopTests from "./CashReplaceGlassPromoInshop"; import cashReplaceMultiGlassPromoInshopTests from "./CashReplaceMultiGlassPromoInshop"; import cashReplaceStaticInshopTests from "./CashReplaceStaticInshop"; +import cashReplaceStaticMobileMSRTests from "./CashReplaceStaticMobileMSR"; import cashReplaceRainDefensePromoInshopTests from "./CashReplaceRainDefensePromoInshop"; import cashReplaceSafeliteCanNotRecalMobileTests from "./CashReplaceSafeliteCanNotRecalMobile"; import cashReplaceVinMobileTests from "./CashReplaceVinMobile"; import cashReplaceWiperDropoffTests from "./CashReplaceWiperDropoff"; import cashReplaceWiperPromoInShopTests from "./CashReplaceWiperPromoInshop"; import insuranceAcuityPaypalTests from "./InsuranceAcuityPaypal"; +// import insuranceUSAAMsrTests from "./InsuranceUSAAMsr"; import insuranceITAC21stCenturyTests from "./InsuranceITAC21stCentury"; import insuranceGeicoTests from "./InsuranceGeico"; import insuranceITACOptimizedPriceValidationAllStateTests from "./InsuranceITACOptimizedPriceValidationAllState"; @@ -42,6 +44,9 @@ import insuranceBigTruckVerifiedTests from "./InsuranceBigTruckVerified"; import insuranceUnverifiedTests from "./InsuranceUnverified"; import CashReplaceSplitWindshieldTests from "./CashReplaceSplitWindshield"; import insuranceMeemicNearSchoolVerifiedTests from "./InsuranceMeemicNearSchoolVerified"; +import cashReplaceDualMobileMSRTests from "./CashReplaceDualMobileMSR"; +import cashReplaceDualNonMSRInshopTests from "./CashReplaceDualNonMSRInshop"; + const test = getTestObject(); @@ -78,12 +83,16 @@ const allStandardTests = [ { name: "CashReplaceVinMobile", tests: cashReplaceVinMobileTests }, { name: "CashReplaceWiperDropoff", tests: cashReplaceWiperDropoffTests }, { name: "CashReplaceStaticInshop", tests: cashReplaceStaticInshopTests }, + { name: "CashReplaceDualMobileMSR", tests: cashReplaceDualMobileMSRTests }, + { name: "CashReplaceDualNonMSRInshop", tests: cashReplaceDualNonMSRInshopTests }, + { name: "CashReplaceStaticMobileMSR", tests: cashReplaceStaticMobileMSRTests }, { name: "CashReplaceWiperPromoInshop", tests: cashReplaceWiperPromoInShopTests }, { name: "CashReplaceSwitchToInsuranceProgressiveNoComp", tests: cashReplaceSwitchToInsuranceProgressiveNoCompTests }, - { name: "CashReplaceMobileFirstModal", tests: cashReplaceMobileFirstModalTests }, + // { name: "CashReplaceMobileFirstModal", tests: cashReplaceMobileFirstModalTests }, // TODO: Uncomment when QA is ready to run heavy truck tests // {name: "CashReplaceSplitWindshield", tests: CashReplaceSplitWindshieldTests}, { name: "InsuranceAcuityPaypal", tests: insuranceAcuityPaypalTests }, + // { name: "InsuranceUSAAMsr", tests: insuranceUSAAMsrTests }, { name: "InsuranceITAC21stCentury", tests: insuranceITAC21stCenturyTests }, { name: "InsuranceNoCompProgressive", tests: insuranceNoCompProgressiveTests }, // {name: "InsuranceBigTruckVerified", tests: insuranceBigTruckVerifiedTests}, diff --git a/playwright-tests/tests/CashRepairInShopAfterPay.ts b/playwright-tests/tests/CashRepairInShopAfterPay.ts index dc34404e5..71bce3f25 100644 --- a/playwright-tests/tests/CashRepairInShopAfterPay.ts +++ b/playwright-tests/tests/CashRepairInShopAfterPay.ts @@ -38,7 +38,8 @@ const cashRepairInShopAfterPayData : Partial = { // Experiments experiments: { - ...getDefaultExperimentsData() + ...getDefaultExperimentsData(), + isAdyenPayments: true } } diff --git a/playwright-tests/tests/CashReplaceDualMobileMSR.ts b/playwright-tests/tests/CashReplaceDualMobileMSR.ts new file mode 100644 index 000000000..454f4d2a8 --- /dev/null +++ b/playwright-tests/tests/CashReplaceDualMobileMSR.ts @@ -0,0 +1,99 @@ +//Imports here +import { ITestData, getDefaultExperimentsData } from 'framework/TestData' +import { ServicePackage, PaymentType, PartQuestionType, ServiceLocation } from 'safelite-playwright-core'; +import { ITestCase } from '../framework/Typedefs' +import { VehicleLookupType } from 'safelite-playwright-core'; +import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core'; +import { PaymentMethod } from 'framework/localTypes/Enums'; + +// Set the seed before generating any data +setFakerSeedFromTestName("CashReplaceDualMobileMSR"); + +// Now get the test data with the seeded faker +const CashReplaceDualMobileMSRData: Partial = { + ...getDefaultTestData(), // Get default data with current seed + + // CASH Client + paymentMethod: PaymentMethod.SelfPay, + + // Key feature: Premium package with Rain Defense + servicePackage: ServicePackage.Standard, + + // Flag for recalibration vehicle + isRecalVehicle: true, + + // Override customer details + customerDetails: { + ...getDefaultTestData().customerDetails!, + address: { + ...getDefaultTestData().customerDetails!.address, + postalCode: '43085' + // postalCode: '21237' + } + }, + + // Override appointment details + appointmentDetails: { + serviceLocation: ServiceLocation.Mobile, + appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate, + serviceAddress: { + // Use street address from current faker seed + street: "649 High Street", + city: 'Worthington', + state: 'OH', + postalCode: '43085', // + country: 'United States' + }, + }, + + // override part questions + partQuestions: [ + { + partQuestionType: PartQuestionType.GeneralQuestion1, + isOnPage: true, + optionToSelect: 'Yes' + } + ], + + + // Override vehicle details + vehicleDetails: { + ...getDefaultTestData().vehicleDetails!, + year: '2020', + make: 'Honda', + model: 'Pilot', + style: '4 door utility', + vehicleLookupType: VehicleLookupType.Zip + }, + + mockFirstInshopCallNoSchedule: true, + + isMSRGlassPart: true, + isMSRZip: true, + + // No need to override vehicleDamage as it already defaults to WindshieldCrack + + // Payment at service + paymentDetails: { + paymentType: PaymentType.PayAtService + }, + + // Flag for PIA disabled + isPIAEnabled: false, + + // Experiments + experiments: { + ...getDefaultExperimentsData() + } +} + +const cashReplaceDualMobileMSRTests: ITestCase[] = []; + +const tc = { + name: `CashReplaceDualMobileMSR`, + tags: ['@E2E', '@CashReplaceDualMobileMSR', '@test_report', '@CASH'], + testData: CashReplaceDualMobileMSRData +}; +cashReplaceDualMobileMSRTests.push(tc); + +export default cashReplaceDualMobileMSRTests; \ No newline at end of file diff --git a/playwright-tests/tests/CashReplaceDualNonMSRInshop.ts b/playwright-tests/tests/CashReplaceDualNonMSRInshop.ts new file mode 100644 index 000000000..8abea0f00 --- /dev/null +++ b/playwright-tests/tests/CashReplaceDualNonMSRInshop.ts @@ -0,0 +1,82 @@ +//Imports here +import { ITestData, getDefaultExperimentsData } from 'framework/TestData' +import { ServicePackage, PaymentType, PartQuestionType, ServiceLocation } from 'safelite-playwright-core'; +import { ITestCase } from '../framework/Typedefs' +import { VehicleLookupType } from 'safelite-playwright-core'; +import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core'; +import { PaymentMethod } from 'framework/localTypes/Enums'; + +// Set the seed before generating any data +setFakerSeedFromTestName("CashReplaceDualNonMSRInshop"); + +// Now get the test data with the seeded faker +const CashReplaceDualMobileMSRData: Partial = { + ...getDefaultTestData(), // Get default data with current seed + + // CASH Client + paymentMethod: PaymentMethod.SelfPay, + + // Key feature: Premium package with Rain Defense + servicePackage: ServicePackage.Standard, + + // Flag for recalibration vehicle + isRecalVehicle: true, + + // Override customer details + customerDetails: { + ...getDefaultTestData().customerDetails!, + address: { + ...getDefaultTestData().customerDetails!.address, + postalCode: '43085' + // postalCode: '21237' + } + }, + + // override part questions + partQuestions: [ + { + partQuestionType: PartQuestionType.GeneralQuestion1, + isOnPage: true, + optionToSelect: 'Yes' + } + ], + + // Override vehicle details + vehicleDetails: { + ...getDefaultTestData().vehicleDetails!, + year: '2024', + make: 'Subaru', + model: 'Ascent', + style: '4 door utility', + vehicleLookupType: VehicleLookupType.Zip + }, + + mockFirstInshopCallNoSchedule: true, + + isMSRGlassPart: false, + isMSRZip: true, + + // Payment at service + paymentDetails: { + paymentType: PaymentType.PayAtService + }, + + // Flag for PIA disabled + isPIAEnabled: false, + + // Experiments + experiments: { + ...getDefaultExperimentsData() + } +} + +const cashReplaceDualNonMSRInshopTests: ITestCase[] = []; + +const tc = { + name: `CashReplaceDualNonMSRInshop`, + tags: ['@E2E', '@CashReplaceDualNonMSRInshop', '@test_report', '@CASH'], + testData: CashReplaceDualMobileMSRData +}; +cashReplaceDualNonMSRInshopTests.push(tc); + +export default cashReplaceDualNonMSRInshopTests; \ No newline at end of file diff --git a/playwright-tests/tests/CashReplaceGlassAddressLookupInshopAfterPay.ts b/playwright-tests/tests/CashReplaceGlassAddressLookupInshopAfterPay.ts index 50f885f96..6feb3c651 100644 --- a/playwright-tests/tests/CashReplaceGlassAddressLookupInshopAfterPay.ts +++ b/playwright-tests/tests/CashReplaceGlassAddressLookupInshopAfterPay.ts @@ -35,10 +35,10 @@ const cashReplaceGlassAddressLookupInshopAfterPayData: Partial = { // Override vehicle details vehicleDetails: { ...getDefaultTestData().vehicleDetails!, - year: '2013', - make: 'Hyundai', - model: 'Sonata', - style: '4 door sedan', + year: '2024', + make: 'Acura', + model: 'MDX', + style: '4 door utility', vehicleLookupType: VehicleLookupType.Address }, @@ -48,7 +48,7 @@ const cashReplaceGlassAddressLookupInshopAfterPayData: Partial = { // Experiments experiments: { ...getDefaultExperimentsData() - } + } } const cashReplaceGlassAddressLookupInshopAfterPayTests: ITestCase[] = []; diff --git a/playwright-tests/tests/CashReplaceMultiGlassPromoInshop.ts b/playwright-tests/tests/CashReplaceMultiGlassPromoInshop.ts index 89406bc29..c0ca40afa 100644 --- a/playwright-tests/tests/CashReplaceMultiGlassPromoInshop.ts +++ b/playwright-tests/tests/CashReplaceMultiGlassPromoInshop.ts @@ -35,9 +35,12 @@ const cashReplaceMultiGlassPromoInshopData: Partial = { make: 'Subaru', model: 'Outback', style: '4 door station wagon', - vin: '4S4BSENC4K3221004', - vehicleLookupType: VehicleLookupType.Vin + // vin: '4S4BSENC4K3221004', + vehicleLookupType: VehicleLookupType.Zip }, + + // Special flag to skip estimate page + isSkipEstimatePage: true, // Key feature: multiple damaged glasses vehicleDamage: [ @@ -55,6 +58,15 @@ const cashReplaceMultiGlassPromoInshopData: Partial = { // Add promo code promoCode: '20CALL', }, + + // override part questions + partQuestions: [ + { + partQuestionType: PartQuestionType.GeneralQuestion1, + isOnPage: true, + optionToSelect: 'Yes' + } + ], // Vehicle part questions for multiple glass parts vehiclePartQuestions: [ @@ -62,7 +74,7 @@ const cashReplaceMultiGlassPromoInshopData: Partial = { partQuestionType: PartQuestionType.WindshieldColor, isOnPage: true, optionToSelect: 'Green Tint, Blue Shade', - secondaryQuestionOptionToSelect: 'solar, lane departure warning system, heated glass wiper park, high beam assist, soundproofing' + secondaryQuestionOptionToSelect: 'solar, lane departure warning system, hwp, high beam assist, soundproofing' }, { partQuestionType: PartQuestionType.DriverFrontColor, diff --git a/playwright-tests/tests/CashReplaceMultiSlidingGlassDropoff.ts b/playwright-tests/tests/CashReplaceMultiSlidingGlassDropoff.ts index c687861a8..67b8ab76e 100644 --- a/playwright-tests/tests/CashReplaceMultiSlidingGlassDropoff.ts +++ b/playwright-tests/tests/CashReplaceMultiSlidingGlassDropoff.ts @@ -85,7 +85,7 @@ const cashReplaceMultiSlidingGlassDropoffData: Partial = { partQuestionType: PartQuestionType.WindshieldColor, isOnPage: true, optionToSelect: 'Green Tint', - secondaryQuestionOptionToSelect: 'rain sensor, solar, soundproofing, third visor frit, lane departure warning system, heated glass wiper park, w/combination bracket' + secondaryQuestionOptionToSelect: 'rain sensor, solar, soundproofing, third visor frit, lane departure warning system, hwp, w/combination bracket' }, { partQuestionType: PartQuestionType.PassengerFrontColor, diff --git a/playwright-tests/tests/CashReplaceStaticInshop.ts b/playwright-tests/tests/CashReplaceStaticInshop.ts index 107b625a1..f7304ca46 100644 --- a/playwright-tests/tests/CashReplaceStaticInshop.ts +++ b/playwright-tests/tests/CashReplaceStaticInshop.ts @@ -15,19 +15,26 @@ const CashReplaceStaticInshopData: Partial = { // CASH Client paymentMethod: PaymentMethod.SelfPay, - + // Key feature: Premium package with Rain Defense servicePackage: ServicePackage.Standard, - + // Flag for recalibration vehicle isRecalVehicle: true, - + + // Flag for MSR Glass Part + isMSRGlassPart: true, + + // Flag for MSR Zip + isMSRZip: false, + // Override customer details customerDetails: { ...getDefaultTestData().customerDetails!, address: { ...getDefaultTestData().customerDetails!.address, - postalCode: '43085' + postalCode: '55113' // Non MSR zip + // postalCode: '21237' } }, @@ -37,34 +44,32 @@ const CashReplaceStaticInshopData: Partial = { partQuestionType: PartQuestionType.GeneralQuestion1, isOnPage: true, optionToSelect: 'Yes' - }, - { - partQuestionType: PartQuestionType.GeneralQuestion2, - isOnPage: true, - optionToSelect: 'Yes' } ], - + + // Override vehicle details vehicleDetails: { ...getDefaultTestData().vehicleDetails!, - year: '2022', - make: 'Mazda', - model: 'CX-9', - style: '4 door utility', - vin: '3FA6P0HD8LR234510', + year: '2023', + make: 'Hyundai', + model: 'Elantra', + style: '4 door sedan', vehicleLookupType: VehicleLookupType.Zip }, mockFirstInshopCallNoSchedule: true, - + // No need to override vehicleDamage as it already defaults to WindshieldCrack - + // Payment at service paymentDetails: { paymentType: PaymentType.PayAtService }, + // Flag for PIA disabled + isPIAEnabled: false, + // Experiments experiments: { ...getDefaultExperimentsData() @@ -75,7 +80,7 @@ const cashReplaceStaticInshopTests: ITestCase[] = []; const tc = { name: `CashReplaceStaticInshop`, - tags: ['@E2E','@CashReplaceStaticInshop', '@test_report', '@CASH'], + tags: ['@E2E', '@CashReplaceStaticInshop', '@test_report', '@CASH'], testData: CashReplaceStaticInshopData }; cashReplaceStaticInshopTests.push(tc); diff --git a/playwright-tests/tests/CashReplaceStaticMobileMSR.ts b/playwright-tests/tests/CashReplaceStaticMobileMSR.ts new file mode 100644 index 000000000..f2839e101 --- /dev/null +++ b/playwright-tests/tests/CashReplaceStaticMobileMSR.ts @@ -0,0 +1,92 @@ +//Imports here +import { ITestData, getDefaultExperimentsData } from 'framework/TestData' +import { ServicePackage, PaymentType, PartQuestionType, ServiceLocation } from 'safelite-playwright-core'; +import { ITestCase } from '../framework/Typedefs' +import { VehicleLookupType } from 'safelite-playwright-core'; +import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core'; +import { PaymentMethod } from 'framework/localTypes/Enums'; + +// Set the seed before generating any data +setFakerSeedFromTestName("CashReplaceStaticMobileMSR"); + +// Now get the test data with the seeded faker +const CashReplaceStaticMobileMSRData: Partial = { + ...getDefaultTestData(), // Get default data with current seed + + // CASH Client + paymentMethod: PaymentMethod.SelfPay, + + // Key feature: Premium package with Rain Defense + servicePackage: ServicePackage.Standard, + + // Flag for recalibration vehicle + isRecalVehicle: true, + + // Override customer details + customerDetails: { + ...getDefaultTestData().customerDetails!, + address: { + ...getDefaultTestData().customerDetails!.address, + postalCode: '43085' + // postalCode: '21237' + } + }, + + // override part questions + partQuestions: [ + { + partQuestionType: PartQuestionType.GeneralQuestion1, + isOnPage: true, + optionToSelect: 'Yes' + } + ], + + // Override vehicle details + vehicleDetails: { + ...getDefaultTestData().vehicleDetails!, + year: '2023', + make: 'Hyundai', + model: 'Elantra', + style: '4 door sedan', + vin: '3FA6P0HD8LR234510', + vehicleLookupType: VehicleLookupType.Zip + }, + + mockFirstInshopCallNoSchedule: true, + + // No need to override vehicleDamage as it already defaults to WindshieldCrack + // Override appointment details + appointmentDetails: { + serviceLocation: ServiceLocation.Mobile, + appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate, + serviceAddress: { + // Use street address from current faker seed + street: "649 High Street", + city: 'Worthington', + state: 'OH', + postalCode: '43085', // + country: 'United States' + }, + }, + + // Payment at service + paymentDetails: { + paymentType: PaymentType.PayAtService + }, + + // Experiments + experiments: { + ...getDefaultExperimentsData() + } +} + +const cashReplaceStaticMobileMSRTests: ITestCase[] = []; + +const tc = { + name: `cashReplaceStaticMobileMSR`, + tags: ['@E2E','@cashReplaceStaticMobileMSR', '@test_report', '@CASH'], + testData: CashReplaceStaticMobileMSRData +}; +cashReplaceStaticMobileMSRTests.push(tc); + +export default cashReplaceStaticMobileMSRTests; \ No newline at end of file diff --git a/playwright-tests/tests/CashReplaceSwitchToInsuranceProgressiveNoComp.ts b/playwright-tests/tests/CashReplaceSwitchToInsuranceProgressiveNoComp.ts index 98c965b0c..7261cc238 100644 --- a/playwright-tests/tests/CashReplaceSwitchToInsuranceProgressiveNoComp.ts +++ b/playwright-tests/tests/CashReplaceSwitchToInsuranceProgressiveNoComp.ts @@ -37,6 +37,8 @@ const cashReplaceSwitchToInsuranceProgressiveNoCompData: Partial = { style: "4 door sedan" }, + isSkipEstimatePage: true, + partQuestions: [ { partQuestionType: PartQuestionType.GeneralQuestion1, diff --git a/playwright-tests/tests/InsuranceUSAAMsr.ts b/playwright-tests/tests/InsuranceUSAAMsr.ts new file mode 100644 index 000000000..052fdc387 --- /dev/null +++ b/playwright-tests/tests/InsuranceUSAAMsr.ts @@ -0,0 +1,101 @@ +//Imports here +import { ITestData, getDefaultExperimentsData } from 'framework/TestData' +import { ServiceLocation, DamageType, PartQuestionType, VehicleDamage, PaymentType, Flow } from 'safelite-playwright-core'; +import { PaymentMethod } from "framework/localTypes/Enums"; +import { ITestCase } from '../framework/Typedefs' +import { VehicleLookupType } from 'safelite-playwright-core'; +import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core'; + +// Set the seed based on test name for consistent but unique data +setFakerSeedFromTestName("InsuranceUSAAMsr"); + +// Now get the test data with the seeded faker +const insuranceUSAAMsrData: Partial = { + ...getDefaultTestData(), // Get default data with current seed + + // Key feature: Insurance flow with Acuity + paymentMethod: PaymentMethod.Insurance, + + // Insurance claim flags + isDuplicateClaim: true, + flow: Flow.Managed, + isUseVehicleOnPolicy: true, + + // Override customer details for Kentucky location + customerDetails: { + ...getDefaultTestData().customerDetails!, + address: { + street: getDefaultTestData().customerDetails!.address.street, + city: 'Cleaveland', + state: 'Ohio', + postalCode: '44125', + country: 'United States' + } + }, + + // Insurance claim details + claimDetails: { + client: 'USAA', + policyNumber: 'MOCK900040MSR', + policyDeductible: 100.00, + damageDate: new Date(new Date().setDate(new Date().getDate() - 1)).toLocaleDateString('en-US', {month: '2-digit', day: '2-digit', year: 'numeric'}), + damageCause: DamageType.Rock + }, + + // Heavy duty truck details with VIN lookup + vehicleDetails: { + ...getDefaultTestData().vehicleDetails!, + year: '2023', + make: 'Hyundai', + model: 'Elantra', + style: '4 door sedan' + }, + + isRecalVehicle: true, + + // No need to override vehicleDamage as it already defaults to WindshieldCrack + + // Override for mobile service + appointmentDetails: { + serviceLocation: ServiceLocation.Mobile, + appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate, + serviceAddress: { + // Use street address from current faker seed + street: "13300 Carpenter Rd", + city: 'Cleveland', + state: 'OH', + postalCode: '44125', + country: 'United States' + } + }, + + // Part questions for windshield + partQuestions: [ + { + partQuestionType: PartQuestionType.GeneralQuestion1, + isOnPage: true, + optionToSelect: 'Yes' + }, + ], + + // Override payment details (empty because we skip payment method page in insurance flow) + paymentDetails: { + paymentType: PaymentType.PayAtService + }, + + // Experiments + experiments: { + ...getDefaultExperimentsData() + } +} + +const insuranceUSAAMsrTests: ITestCase[] = []; + +const tc = { + name: `InsuranceUSAAMsr`, + tags: ['@E2E','@InsuranceAcuityPaypal', '@test_report', '@Insurance', '@CASH-1187', '@CASH-848'], + testData: insuranceUSAAMsrData +}; +insuranceUSAAMsrTests.push(tc); + +export default insuranceUSAAMsrTests; \ No newline at end of file diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index 9adfc0613..efb32ffc6 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -25,7 +25,7 @@ const endpoints = { method: "GET", }, GetDamageOptions: { - url: "/parts/api/v1/parts/damage-options", + url: "/parts/api/v2/parts/damage-options", method: "GET", }, LookupVehicleByYmms: { diff --git a/src/constants/insurance.js b/src/constants/insurance.js index f7550513b..b23e1fddd 100644 --- a/src/constants/insurance.js +++ b/src/constants/insurance.js @@ -78,3 +78,7 @@ export function coverageTypeEnum(strCoverageType) { return null; } } + +export const NON_MANAGED_SHOW_CLAIM_NUMBER_PARENTS = [ + { name: "KentuckyFarmBureau", value: "223499" }, +]; diff --git a/src/constants/store-actions.js b/src/constants/store-actions.js index 702f7c737..ad31474c3 100644 --- a/src/constants/store-actions.js +++ b/src/constants/store-actions.js @@ -123,6 +123,7 @@ const storeActions = { CREATE_SUBMITTED_STATE: "createSubmittedState", RESET_SUBMITTED_STATE: "resetSubmittedState", + RESET_INSURANCE_DETAILS: "resetInsuranceDetails", ADD_DONATION_TO_SUBMITTED_STATE: "addDonationToSubmittedState", RESET_EXTERNAL_PARAMETER_STATE: "resetExternalParameterState", UPDATE_EXTERNAL_PARAMETER_MMS: "updateExternalParameterMMS", diff --git a/src/constants/store-mutations.js b/src/constants/store-mutations.js index c43e737d3..e8bde83a5 100644 --- a/src/constants/store-mutations.js +++ b/src/constants/store-mutations.js @@ -100,6 +100,7 @@ const storeMutations = { RESET_SERVICE_LOCATION_MOBILE_ADDRESS: "resetServiceLocationMobileAddress", RESET_SCHEDULE: "resetSchedule", RESET_PAYMENT_METHOD_CHOICE: "resetPaymentMethodChoice", + RESET_INSURANCE_DETAILS: "resetInsuranceDetails", // OTHER MUTATIONS UPDATE_PAGE_DATA: "updatePageData", diff --git a/src/digital-components/date-picker-popup/date-picker-popup.vue b/src/digital-components/date-picker-popup/date-picker-popup.vue index 3c666e775..7f3e109ac 100644 --- a/src/digital-components/date-picker-popup/date-picker-popup.vue +++ b/src/digital-components/date-picker-popup/date-picker-popup.vue @@ -27,6 +27,7 @@ :max-date="maxDate" :auto-apply="autoApply" :text-input="textInput" + :prevent-min-max-navigation="preventMinMaxNavigation" :teleport="true" :aria-labels="{ input: questionText }" @update:model-value="onDateChange" @@ -106,6 +107,10 @@ export default { type: Boolean, default: false, }, + preventMinMaxNavigation: { + type: Boolean, + default: false, + }, questionAlignment: String, labelBold: { type: Boolean, diff --git a/src/fmg-components/cart/cart.vue b/src/fmg-components/cart/cart.vue index 9a6f55576..faa28376d 100644 --- a/src/fmg-components/cart/cart.vue +++ b/src/fmg-components/cart/cart.vue @@ -962,7 +962,8 @@ export default { return this.supportingItems.find( (lineItem) => lineItem.partType == partTypeStrings.MOBILE_FEE && - lineItem.partNumber == partNumberStrings.MOBILE_STATIC_RECAL_FEE + (lineItem.partNumber == partNumberStrings.MOBILE_STATIC_RECAL_FEE || + lineItem.partNumber == partNumberStrings.MOBILE_DUAL_RECAL_FEE) ); }, msrFeeCartItem() { diff --git a/src/helpers/damage-helper.js b/src/helpers/damage-helper.js index 1763ffbea..5c7a2de7c 100644 --- a/src/helpers/damage-helper.js +++ b/src/helpers/damage-helper.js @@ -103,7 +103,7 @@ export function getSideDoorGlassString(glassLocation, glassName) { export function getDamageInfoWordingText(damageInfo) { const glassTextArray = []; - damageInfo.glassToReplace.forEach((item) => { + damageInfo.glassToReplace?.forEach((item) => { let string = ""; if (item.glassLocation === glassLocations.WINDSHIELD) { string = item.glassLocation; diff --git a/src/helpers/pricing-helper.js b/src/helpers/pricing-helper.js index 9564b5c1f..fff87d82f 100644 --- a/src/helpers/pricing-helper.js +++ b/src/helpers/pricing-helper.js @@ -108,7 +108,9 @@ export async function getPricingByDayPartWithPrice(pageNameToLog) { export function getMSRFeePartPrice(supportingItems, includeTax) { let msrFeePrice = 0; const msrFeeLineItem = supportingItems?.find( - (lineItem) => lineItem.partNumber === partNumberStrings.MOBILE_STATIC_RECAL_FEE + (lineItem) => + lineItem.partNumber === partNumberStrings.MOBILE_STATIC_RECAL_FEE || + lineItem.partNumber === partNumberStrings.MOBILE_DUAL_RECAL_FEE ); if (msrFeeLineItem) { msrFeePrice = baseMixin?.methods?.getTotalPriceOfAllLineItemsAndChildParts( diff --git a/src/layouts/insurance-details/insurance-details.vue b/src/layouts/insurance-details/insurance-details.vue index 67670b9cc..a675e0871 100644 --- a/src/layouts/insurance-details/insurance-details.vue +++ b/src/layouts/insurance-details/insurance-details.vue @@ -39,7 +39,18 @@ format="MM/dd/yyyy" modelType="MM/dd/yyyy" :enableTimePicker="false" - :clearable="false" /> + :clearable="false" + :textInput="true" + :maxDate="today" + :preventMinMaxNavigation="true" /> + + item.value === order.payment?.parentAccountNumber?.toString() + ); + return !!parent; + }, stateOptions() { return stateOptions; }, @@ -231,6 +256,9 @@ export default { getPolicyZipFromStore() { return store.getters.order.policy.zipCode ?? ""; }, + getClaimNumberFromStore() { + return store.getters.order.policy.claimNumber ?? ""; + }, backButtonAction() { this.$router.navigateWithoutSaving( this.navigationScenarios.CLICKED_BACK, @@ -247,6 +275,7 @@ export default { city: this.city, state: this.policyState, zipCode: this.policyZip, + claimNumber: this.claimNumber, }, false ); diff --git a/src/layouts/payment-adyen/payment-adyen.vue b/src/layouts/payment-adyen/payment-adyen.vue index 5b3a663ea..fc3ad668d 100644 --- a/src/layouts/payment-adyen/payment-adyen.vue +++ b/src/layouts/payment-adyen/payment-adyen.vue @@ -35,6 +35,7 @@ :insuranceCompanyName="insuranceCompanyName" :showInsuranceCoverageAs="showInsuranceCoverageAs" :isMSRFeeApplicable="isMSRFeeApplicable" + :IsMSRFeeCoveredByInsurance="isMSRFeeCoveredByInsurance" :isItac="isItac" :isNoComp="isNoComp" :isCollapsible="false" /> @@ -77,6 +78,7 @@ import { createAdyenCheckout } from "@/helpers/adyen-helper"; import { Dropin } from "@adyen/adyen-web/auto"; import { applicationConfig } from "@/constants/application-config"; import { paymentMethods } from "@/constants/payment-method-constants"; +import analyticsMixin from "@/mixins/analytics-mixin"; import "@adyen/adyen-web/styles/adyen.css"; import { mapAdyenToFmgPaymentMethod, mapFmgToAdyenPaymentMethod } from "../../helpers/adyen-helper"; @@ -133,6 +135,25 @@ export default { }, methods: { + async initializeAdyenWithErrorHandling() { + try { + await this.initializeAdyen(); + } catch (error) { + console.error("Failed to initialize Adyen payment:", error); + + const errorJson = JSON.stringify(error, Object.getOwnPropertyNames(Object(error))); + analyticsMixin.methods.pushFmgSessionData(errorJson); + await global.$logger.logError(errorJson); + + this.$router.navigateWithoutSaving( + this.navigationScenarios.PIA_ERROR, + this.pageName + ); + + return; + } + }, + async backButtonAction() { // Stub, for navigating back via nav-bar. this.$router.navigateWithoutSaving( @@ -203,6 +224,12 @@ export default { }, onError: (error, component) => { console.log(`Error from Adyen`); + const errorJson = JSON.stringify( + error, + Object.getOwnPropertyNames(Object(error)) + ); + analyticsMixin.methods.pushFmgSessionData(errorJson); + this.handleError(error); }, }, @@ -371,7 +398,12 @@ export default { await global.$logger.logError(stringToLog); - this.hasPaymentFailureError = true; + this.$router.navigateWithoutSaving( + this.navigationScenarios.PIA_ERROR, + this.pageName + ); + + return; } this.dropinComponent?.update(); @@ -560,13 +592,16 @@ export default { isMSRFeeApplicable() { return this.$store.getters.order.isMSRFeeApplicable; }, + isMSRFeeCoveredByInsurance() { + return this.$store.getters.order.isMSRFeeCoveredByInsurance; + }, lineItems() { return deepClone(this.$store.getters.lineItems); }, }, mounted() { - this.initializeAdyen(); + this.initializeAdyenWithErrorHandling(); }, components: { diff --git a/src/layouts/payment-method/payment-method.vue b/src/layouts/payment-method/payment-method.vue index da49aa039..b0347f581 100644 --- a/src/layouts/payment-method/payment-method.vue +++ b/src/layouts/payment-method/payment-method.vue @@ -834,11 +834,12 @@ export default { return getBoolFromString(this.$route?.query?.piaError); }, shouldDisplayUnverifiedInsuranceAlert() { - const shouldDisplayUnverifiedDeduct = experimentMixin.methods.hasSettingEqualTo( + const hasExperimentSetting = experimentMixin.methods.hasSettingEqualTo( experimentSettings.SHOW_UNVERIFIED_DEDUCT_ENTRY, "true" ); - return shouldDisplayUnverifiedDeduct; + + return hasExperimentSetting && this.isInsurance; }, // Necessary to make the watcher of lineItems work // JavaScript does not keep a record of the old value, only a reference to it's location in the memory. diff --git a/src/layouts/quote/quote.vue b/src/layouts/quote/quote.vue index d9f238733..206ff2050 100644 --- a/src/layouts/quote/quote.vue +++ b/src/layouts/quote/quote.vue @@ -799,6 +799,7 @@ export default { false, false ); + this.dispatchStoreAction(this.storeActions.RESET_INSURANCE_DETAILS, false, false); this.dispatchStoreAction(this.storeActions.SAVE_CAN_SCHEDULE_ONLINE, false, false); this.dispatchStoreAction(this.storeActions.SAVE_ACCOUNT_NAME, null, false); } else { diff --git a/src/layouts/return-user/return-user.vue b/src/layouts/return-user/return-user.vue index ad307080d..6ce0b2528 100644 --- a/src/layouts/return-user/return-user.vue +++ b/src/layouts/return-user/return-user.vue @@ -47,6 +47,7 @@ import { Form } from "vee-validate"; import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper"; import { deleteFunnelCookie } from "@/helpers/heritage-integration/cookie-helper"; import { storeActions } from "@/constants/store-actions"; +import baseMixin from "@/mixins/base-mixin"; export default { name: "return-user", @@ -74,6 +75,13 @@ export default { startOverImage: null, }; }, + mounted() { + // The global baseMixin.mounted() skips hiding the loading modal when + // external-parameter state is active, but those parameters have no + // consumer on this interstitial. Clear that state and ensure the loader + // is hidden so the user can never get stuck behind it on this page. + baseMixin.methods.ResetExternalParamsAndHideModal(); + }, methods: { arePagePrerequisitesValid() { return getFunnelCookie() !== null; diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index c483c90df..6f2db110b 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -706,10 +706,16 @@ export default { isMobileStaticRecalibrationApplicable() { return ( this.displayMSR && - this.mobileFeePart?.partNumber == partNumberStrings.MOBILE_STATIC_RECAL_FEE && + this.isMSRFeePartNumber && (this.enableMSRSplitPay || this.isCashItacNoComp || this.mobileFeePart?.isInsurable) ); }, + isMSRFeePartNumber() { + return ( + this.mobileFeePart?.partNumber === partNumberStrings.MOBILE_STATIC_RECAL_FEE || + this.mobileFeePart?.partNumber === partNumberStrings.MOBILE_DUAL_RECAL_FEE + ); + }, displayMSR() { return ( experimentMixin.methods diff --git a/src/mixins/analytics-mixin.js b/src/mixins/analytics-mixin.js index 3ab575f58..1ea812c49 100644 --- a/src/mixins/analytics-mixin.js +++ b/src/mixins/analytics-mixin.js @@ -160,12 +160,16 @@ export default { // and aws FireHose stream, DigitalConsumer-Session-Firehose, to push data to an // S3 bucket, safelite-dev-digitalconsumer-session-data-us-east-2/1. // This bucket data is then picked up by snowflake for analytics use. - async pushFmgSessionData() { + async pushFmgSessionData(errorJson = null) { var currentPageName = getPageNameFromRouter(true); if (!currentPageName) { return; } + if (errorJson) { + currentPageName += ` ERROR: ${errorJson}`; + } + const hasSubmittedOrder = baseMixin.methods.hasSubmittedOrder(); const submittedOrder = baseMixin.methods.getSubmittedOrder(); const order = hasSubmittedOrder ? submittedOrder : store.getters.order; @@ -209,6 +213,7 @@ export default { var appointment = `${order?.schedule?.date ?? ""} ${order?.schedule?.startTime ?? ""}`; var sessionData = {}; + sessionData.currentPage = currentPageName; sessionData.sid = getSessionIdValue(); sessionData.deviceId = getDeviceIdValue(); @@ -931,6 +936,11 @@ function getPageNameFromRouter(useDefaultUrl = false) { router.currentRoute.value && router.currentRoute.value.name ) { + const query = router.currentRoute.value.query; + if (query && Object.keys(query).length > 0 && useDefaultUrl === true) { + const queryString = new URLSearchParams(query).toString(); + return `${router.currentRoute.value.name}?${queryString}`; + } return router.currentRoute.value.name; } diff --git a/src/store/index.js b/src/store/index.js index a58200a96..18b34f023 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -170,6 +170,7 @@ const getDefaultState = () => { currentDeductible: 0, originalDeductible: 0, policyNumber: null, + claimNumber: null, isItac: false, additionalAuthFlag: null, isNoComp: false, @@ -355,6 +356,9 @@ export const mutations = { updateZipCode(state, zipCode) { state.order.policy.zipCode = zipCode; }, + updateClaimNumber(state, claimNumber) { + state.order.policy.claimNumber = claimNumber; + }, updateIsClaimAndCoverage(state, isClaimAndCoverage) { state.order.payment.isClaimAndCoverage = isClaimAndCoverage; }, @@ -370,6 +374,26 @@ export const mutations = { updateIsInsurance(state, isInsurance) { state.order.payment.isInsurance = isInsurance; }, + resetInsuranceDetails(state) { + state.order.payment.insuranceCoverage = { + isVerified: null, + coverageStatus: null, + coverageSubStatus: null, + coverageType: null, + coverageVerificationType: null, + }; + + state.order.policy.policyNumber = null; + state.order.policy.claimNumber = null; + state.order.policy.insuranceCompanyName = null; + state.order.policy.streetAddress = null; + state.order.policy.apartment = null; + state.order.policy.city = null; + state.order.policy.state = null; + state.order.policy.zipCode = null; + state.order.damage.dateOfLoss = null; + state.order.damage.damageCause = null; + }, updateIsPia(state, isPia) { state.order.payment.isPia = isPia; }, @@ -523,6 +547,7 @@ export const mutations = { state.order.policy.city = insuranceDetails.city; state.order.policy.state = insuranceDetails.state; state.order.policy.zipCode = insuranceDetails.zipCode; + state.order.policy.claimNumber = insuranceDetails.claimNumber; } }, updateDamageDetails(state, damageDetails) { @@ -3111,6 +3136,9 @@ export const actions = { saveBillToAccountNumber(context, billToAccountNumber) { context.commit(storeMutations.UPDATE_BILL_TO_ACCT_NUMBER, billToAccountNumber); }, + resetInsuranceDetails(context) { + context.commit(storeMutations.RESET_INSURANCE_DETAILS); + }, saveSupportingItems(context, supportingItems) { syncLineItemIds(supportingItems, context.state.order.lineItems.supportingItems);