diff --git a/playwright-tests/package-lock.json b/playwright-tests/package-lock.json index 249e35dea..3d777e407 100644 --- a/playwright-tests/package-lock.json +++ b/playwright-tests/package-lock.json @@ -23,7 +23,7 @@ "luxon": "^3.6.1", "ortoni-report": "^3.0.2", "playwright-jira-reporter": "^1.0.4", - "safelite-playwright-core": "^1.0.21", + "safelite-playwright-core": "^1.0.22", "typescript": "^5.8.3" } }, @@ -3278,9 +3278,9 @@ "peer": true }, "node_modules/safelite-playwright-core": { - "version": "1.0.21", - "resolved": "https://pkgs.dev.azure.com/Safelite/Digital/_packaging/DigitalQA/npm/registry/safelite-playwright-core/-/safelite-playwright-core-1.0.21.tgz", - "integrity": "sha1-qvCxkMa7MWLpUNHHNklAD3osv8U=", + "version": "1.0.22", + "resolved": "https://pkgs.dev.azure.com/Safelite/Digital/_packaging/DigitalQA/npm/registry/safelite-playwright-core/-/safelite-playwright-core-1.0.22.tgz", + "integrity": "sha1-KIwwcw2+Uy3s9xV/9zhCyWfH/7Y=", "dev": true, "license": "ISC", "dependencies": { diff --git a/playwright-tests/package.json b/playwright-tests/package.json index eaae48ccc..db863c777 100644 --- a/playwright-tests/package.json +++ b/playwright-tests/package.json @@ -31,7 +31,7 @@ "luxon": "^3.6.1", "ortoni-report": "^3.0.2", "playwright-jira-reporter": "^1.0.4", - "safelite-playwright-core": "^1.0.21", + "safelite-playwright-core": "^1.0.22", "typescript": "^5.8.3" }, "private": "true" diff --git a/playwright-tests/pages/CoverageStatementPage.ts b/playwright-tests/pages/CoverageStatementPage.ts index c1c4369e5..2931f613b 100644 --- a/playwright-tests/pages/CoverageStatementPage.ts +++ b/playwright-tests/pages/CoverageStatementPage.ts @@ -9,7 +9,8 @@ export class CoverageStatementPage extends InsuranceBasePage { readonly scheduleOnlineButton: Locator; readonly cancelMyClaimButton: Locator; readonly deductibleAmount: Locator; - readonly verfiyingCoverageText: Locator; + readonly verifyingCoverageText: Locator; + readonly noCompText: Locator; readonly continueButton: Locator; // For ITAC/NoComp url = process.env['BASE_URL']! + '/FixMyGlass/CoverageStatement.aspx'; @@ -19,7 +20,8 @@ export class CoverageStatementPage extends InsuranceBasePage { this.scheduleOnlineButton = this.page.getByText('Continue to schedule online'); this.cancelMyClaimButton = this.page.getByText('Cancel my claim'); this.deductibleAmount = this.page.getByRole('heading', { name: '$' }).locator('span'); - this.verfiyingCoverageText = this.page.getByRole('heading', { name: 'We’re verifying your coverage' }); + this.verifyingCoverageText = this.page.getByRole('heading', { name: 'We\'re verifying your coverage' }); + this.noCompText = this.page.getByRole('heading', { name: 'Your policy doesn\'t cover this service' }); this.continueButton = page.getByRole('button', { name: 'Continue' }); // this.validateURL(this.url); } @@ -46,16 +48,20 @@ export class CoverageStatementPage extends InsuranceBasePage { "span.deductible-text-black[data-bind='text: deductibleFormatted']" ); + const unverifiedDeductibleElement = this.verifyingCoverageText; + // Check if the locator is visible before running the expectation if (await deductibleElement.isVisible()) { // Check if the page contains the properly formatted deductible amount await expect(deductibleElement).toContainText(`$${expectedDeductibleRegex}`); } - } - async validateUnverifiedText(){ - await expect(this.verfiyingCoverageText).toBeEnabled(); + if (await unverifiedDeductibleElement.isVisible() || await this.noCompText.isVisible()) { + // Click on continue button if unverified header is visible + await this.continueButton.click(); + } } + @step("CoverageStatementPage >> Next page: ") async handleCoverageStatementPage(testData: Partial) { diff --git a/playwright-tests/pages/OrderConfirmationPage.ts b/playwright-tests/pages/OrderConfirmationPage.ts index 4b06f81ae..35766efa5 100644 --- a/playwright-tests/pages/OrderConfirmationPage.ts +++ b/playwright-tests/pages/OrderConfirmationPage.ts @@ -35,7 +35,7 @@ export class OrderConfirmationPage extends BasePage { this.apptDateText = this.page.locator('[class="scheduleText"]'); this.amountDueText = this.page.getByLabel('expand cart'); this.viewCartButton = this.page.locator('#cart-dropdown-head'); - this.deductibleText = this.page.locator('#deductible-value'); + this.deductibleText = this.page.locator('.deductible'); this.subtotalText = this.page.locator('.sub-total'); this.finalAmountDue = this.page.locator('div.amount-due'); this.cartServicePackageText = this.cartServicePackageText = this.page.locator('.cart-panel'); @@ -45,8 +45,8 @@ export class OrderConfirmationPage extends BasePage { async validateOrderConfirmationPage(testData: Partial) { // Destructure data we use - const { vehicleDetails, customerDetails, servicePackage, - paymentDetails, paymentMethod } = testData; + const { vehicleDetails, customerDetails, servicePackage, promoCode, isCashInsuranceFlow, + isPolicyFound, claimDetails, paymentDetails, isUseVehicleOnPolicy, paymentMethod, isPolicyUnverified } = testData; await this.serviceText.waitFor({ state: "visible" }); expect.soft((await this.getActualAppointmentSummary()).map(item => item.toLowerCase())).toEqual((await this.getExpectedAppointmentSummary(testData)).map(item => item.toLowerCase())); @@ -63,7 +63,7 @@ export class OrderConfirmationPage extends BasePage { const servicePackageAmt = Number.parseFloat(servicePackageValue!.split('$')[1].replaceAll(',', '')); // General Validations - expect.soft(emailTextValue).toContain(customerDetails!.email); + expect.soft(emailTextValue?.toLowerCase()).toContain(customerDetails!.email); // Service package validations await expect.soft(this.cartServicePackageText).toContainText(`${servicePackage}`) @@ -89,43 +89,25 @@ export class OrderConfirmationPage extends BasePage { const finalAmountDueAmt = Number.parseFloat(finalAmountDueValue!.split('$')[1].replaceAll(',', '')); + expect.soft(subtotalAmt).toBeGreaterThan(0); // expect.soft(deductibleAmt).toEqual(0); - if (paymentDetails!.paymentType === PaymentType.PayAtService && (servicePackageAmt > 0)) { + if ((paymentDetails!.paymentType === PaymentType.PayAtService || isCashInsuranceFlow) && (servicePackageAmt > 0)) { // Verify amount due > 0 expect.soft(amountDueAmt).toBeGreaterThan(0); expect.soft(finalAmountDueAmt).toBeGreaterThan(0); + + if (isPolicyUnverified && PaymentType.PayWithInsurance){ + expect.soft(finalAmountDueAmt).toContain('Verifying coverage') + } } else { // Verify amount due 0 expect.soft(amountDueAmt).toEqual(0); expect.soft(finalAmountDueAmt).toEqual(0); } - } else { - // Price validations for insurance users - if (servicePackage === ServicePackage.GlassOnly) { - expect.soft(servicePackageAmt).toEqual(0); - } else { - expect.soft(servicePackageAmt).toBeGreaterThan(0); - } - - // Check for either "Verifying coverage" or "0.00" in price fields - expect.soft( - amountDueValue?.includes('Verifying coverage') || - amountDueValue?.includes('0.00') - ).toBeTruthy(); - - expect.soft( - subtotalTextValue?.includes('Verifying coverage') || - subtotalTextValue?.includes('0.00') - ).toBeTruthy(); - - expect.soft( - finalAmountDueValue?.includes('Verifying coverage') || - finalAmountDueValue?.includes('0.00') - ).toBeTruthy(); - } } +} async getFormattedAppointmentDate(appointmentDate: string) { diff --git a/playwright-tests/pages/PaymentMethodPage.ts b/playwright-tests/pages/PaymentMethodPage.ts index ce81a7462..0ac24500b 100644 --- a/playwright-tests/pages/PaymentMethodPage.ts +++ b/playwright-tests/pages/PaymentMethodPage.ts @@ -358,10 +358,12 @@ l } let isCaliforniaState = localStorage.order.serviceLocation.state as string == 'CA' ? true : false; let hasRecalPart: boolean = false; + let canSafeliteRecalibrate: boolean = false; if (!isRepair) { hasRecalPart = localStorage.order.lineItems.glassParts.find((item: any) => item.requiresRecalibration === true) ? true : false; + canSafeliteRecalibrate = localStorage.order.lineItems.glassParts.find((item: any) => item.canSafeliteRecalibrate === true) ? true : false; } - let recalRequired = !isRepair && (isInsurance || isCaliforniaState) && hasRecalPart + let recalRequired = !isRepair && (isInsurance || isCaliforniaState) && hasRecalPart && canSafeliteRecalibrate let stringForRepair: string[] = ["Expert windshield repair", "Exclusive resin sealant", "Nationwide lifetime guarantee"]; let stringIfRecal = isRepair ? "" diff --git a/playwright-tests/pages/ServicePackagesPage.ts b/playwright-tests/pages/ServicePackagesPage.ts index 27e780e53..d8888ccba 100644 --- a/playwright-tests/pages/ServicePackagesPage.ts +++ b/playwright-tests/pages/ServicePackagesPage.ts @@ -157,10 +157,20 @@ export class ServicePackagesPage extends BasePage { // Validate the presence of specific parts in the glassParts array const glassParts = vuexState.order?.lineItems?.glassParts; - + + const WINDSHIELD_TYPES = [ +   "SINGLE WINDSHIELD", +   "DRIVER SPLIT WINDSHIELD", +   "PASSENGER SPLIT WINDSHIELD" + ]; + if (glassParts?.length > 0) { for (const partType of vehicleDamage) { - const hasPartType = glassParts.some(glassPart => glassPart.partType === partType); + // Normalize part type to "WINDSHIELD" if it matches any of the defined types (Split Windshield types) +   const normalizedPartType = WINDSHIELD_TYPES.includes(partType) ? "WINDSHIELD" : partType; + + const hasPartType = glassParts.some(glassPart => glassPart.partType === normalizedPartType); + await expect(hasPartType, `Expected part type: ${partType}`).toBe(true); } } else { diff --git a/playwright-tests/pages/VehicleDamagePage.ts b/playwright-tests/pages/VehicleDamagePage.ts index 6f2f61c2c..f1d2e4fac 100644 --- a/playwright-tests/pages/VehicleDamagePage.ts +++ b/playwright-tests/pages/VehicleDamagePage.ts @@ -27,9 +27,13 @@ export class VehicleDamagePage extends BasePage { readonly rearStationaryBttn: Locator; readonly rearSlidingGlassBttn: Locator; readonly editVehicleLink: Locator; + readonly singleSplitWindshieldChkBox: Locator; + readonly driverSplitWindshieldChkBox: Locator; + readonly passengerSplitWindshieldChkBox: Locator; readonly noReplacementAvailableAlert: Locator; readonly bothReplaceRepairAlert: Locator; + readonly splitWindshieldAlert: Locator; url = process.env['BASE_URL']! + '/fmg/?fmgPage=vehicle-damage'; constructor(page: Page) { @@ -51,8 +55,12 @@ export class VehicleDamagePage extends BasePage { this.passengerVentGlassChkBox = this.page.locator('[aria-labelledby="passengerSideOptions"]').locator('[buttonlabel="Vent glass"]'); this.passengerBackDoorChkBox = this.page.locator('[aria-labelledby="passengerSideOptions"]').locator('[buttonlabel="Back door"]'); this.rearWindowChkBox = this.page.locator('[buttonlabel="Rear window"]'); + this.singleSplitWindshieldChkBox = this.page.locator('[aria-labelledby="WindshieldReplaceOptions"]').locator('[buttonlabel="Single windshield"]'); + this.driverSplitWindshieldChkBox = this.page.locator('[aria-labelledby="WindshieldReplaceOptions"]').locator('[buttonlabel="Split windshield, driver side"]'); + this.passengerSplitWindshieldChkBox = this.page.locator('[aria-labelledby="WindshieldReplaceOptions"]').locator('[buttonlabel="Split windshield, passenger side"]'); this.noReplacementAvailableAlert = this.page.locator('.alert-danger.widget-name-NoReplacementAvailableError'); this.bothReplaceRepairAlert = this.page.locator('div.alert-danger.widget-name-HasReplacementConflict'); + this.splitWindshieldAlert = this.page.locator('.alert-danger.widget-name-SplitSingleConflict'); this.rearStationaryBttn = this.page.locator('label').filter({ hasText: 'Stationary' }); this.rearSlidingGlassBttn = this.page.locator('label').filter({ hasText: 'Glass with slider' }); this.editVehicleLink = this.page.getByRole('link', { name: 'Edit vehicle' }); @@ -77,6 +85,21 @@ export class VehicleDamagePage extends BasePage { await this.windshieldChkBox.check(); await this.selectCrack(); break; + case VehicleDamage.SingleSplitWindshield: + await this.windshieldChkBox.check(); + await this.selectCrack(); + await this.singleSplitWindshieldChkBox.check(); + break; + case VehicleDamage.DriverSplitWindshield: + await this.windshieldChkBox.check(); + await this.selectCrack(); + await this.driverSplitWindshieldChkBox.check(); + break; + case VehicleDamage.PassengerSplitWindshield: + await this.windshieldChkBox.check(); + await this.selectCrack(); + await this.passengerSplitWindshieldChkBox.check(); + break; case VehicleDamage.DriverFrontDoor: await this.sideDoorButton.check(); await this.driverSideButton.check(); @@ -165,10 +188,17 @@ export class VehicleDamagePage extends BasePage { expect(alertMessage).toContain("Service not availableWe're sorry, but we currently offer only repair service for your vehicle type. Need help with next steps? Call us at800-394-0288.") } + async checkForSplitWindshieldAlertMessage(): Promise { + await expect(this.splitWindshieldAlert).toBeVisible(); + const alertMessage = await this.splitWindshieldAlert.textContent(); + console.log(`Alert encountered: ${alertMessage}`); + expect(alertMessage).toContain("Single-piece or split?Please select just one windshield option: single-piece or split.") + } + @step('VehicleDamagePage >> Select Damage') async handleVehicleDamagePage(testData: Partial): Promise { const {vehicleDamage} = testData; - const {isRepairReplace, isRepairOnly} = testData.alertFlags || {}; + const {isRepairReplace, isRepairOnly, isSplitWindshield} = testData.alertFlags || {}; await this.validateProgressBar(ProgressBarPercentages.VehicleDamagePage); await this.selectDamage(vehicleDamage!); @@ -181,6 +211,10 @@ export class VehicleDamagePage extends BasePage { await this.checkForRepairOnlyAlertMessage(); throw new TestSuccessAlert('Both assertions are met successfully.'); } + if (isSplitWindshield) { + await this.checkForSplitWindshieldAlertMessage(); + throw new TestSuccessAlert('Both assertions are met successfully.'); + } await this.nextPage(); } } \ No newline at end of file diff --git a/playwright-tests/pages/VehicleSelectionPage.ts b/playwright-tests/pages/VehicleSelectionPage.ts index 5dcd7fd25..d2e16c02f 100644 --- a/playwright-tests/pages/VehicleSelectionPage.ts +++ b/playwright-tests/pages/VehicleSelectionPage.ts @@ -12,6 +12,7 @@ export class VehicleSelectionPage extends BasePage { readonly makeDropdown: Locator; readonly modelDropdown: Locator; readonly styleDropdown: Locator; + readonly zipCodeTextBox: Locator; readonly discontinuedServiceAlert: Locator; url = process.env['BASE_URL']! + '/fmg/?fmgPage=vehicle'; @@ -23,6 +24,7 @@ export class VehicleSelectionPage extends BasePage { this.makeDropdown = this.page.locator('#makeQuestionField'); this.modelDropdown = this.page.locator('#modelQuestionField'); this.styleDropdown = this.page.locator('#styleQuestionField'); + this.zipCodeTextBox = this.page.getByRole('textbox', { name: 'Zip code' }); this.discontinuedServiceAlert = this.page.locator('.alert-danger.widget-name-AlertNoServiceWidget'); // this.validateURL(this.url); } @@ -49,13 +51,18 @@ export class VehicleSelectionPage extends BasePage { @step("VehicleSelectionPage >> Select Vehicle: ") async handleVehicleSelectionPage(testData: Partial) { await this.validateProgressBar(ProgressBarPercentages.VehicleSelectionPage); - const { vehicleDetails } = testData; - const { isHeavyTruckVehicle, isSplitWindshield } = testData.alertFlags || {}; + const { vehicleDetails, isHeavyTruck, customerDetails } = testData; + const { isHeavyTruckVehicleAlert, isSplitWindshield } = testData.alertFlags || {}; await this.selectVehicle(vehicleDetails!); + + if (isHeavyTruck) { + await this.zipCodeTextBox.fill(customerDetails?.address.postalCode!); + } // Handle alert conditions for vehicle selection - if (isHeavyTruckVehicle || isSplitWindshield) { + if (isHeavyTruckVehicleAlert) { + await this.continueButton.click(); await this.checkForAlertMessages(); throw new TestSuccessAlert('Both assertions are met successfully.'); } diff --git a/playwright-tests/tests/0000__M.test.ts b/playwright-tests/tests/0000__M.test.ts index 7d1d44829..9179bb1df 100644 --- a/playwright-tests/tests/0000__M.test.ts +++ b/playwright-tests/tests/0000__M.test.ts @@ -27,12 +27,17 @@ import insuranceAcuityPaypalTests from "./InsuranceAcuityPaypal"; import insuranceITAC21stCenturyTests from "./InsuranceITAC21stCentury"; import insuranceGeicoTests from "./InsuranceGeico"; import insuranceITACOptimizedPriceValidationAllStateTests from "./InsuranceITACOptimizedPriceValidationAllState"; +import insuranceNoCompProgressiveTests from "./InsuranceNoCompProgressive"; import cashRepairInShopAfterPayTests from "./CashRepairInShopAfterPay"; import cashRepairInShopPayPalTests from "./CashRepairInShopPayPal"; import cashReplaceMultiSlidingGlassDropoffTests from "./CashReplaceMultiSlidingGlassDropoff"; import cashReplaceMultiGlassMobileTests from "./CashReplaceMultiGlassMobile"; import { getTestObject, TestCase, prepareTest, RuleEngine, TestInfo } from 'framework/Typedefs'; import { createTestPages } from "framework/TestPages"; +import cashReplaceSwitchToInsuranceProgressiveNoCompTests from "./CashReplaceSwitchToInsuranceProgressiveNoComp"; +import insuranceBigTruckVerifiedTests from "./InsuranceBigTruckVerified"; +import insuranceUnverifiedTests from "./InsuranceUnverified"; +import CashReplaceSplitWindshieldTests from "./CashReplaceSplitWindshield"; const test = getTestObject(); @@ -69,8 +74,14 @@ const allStandardTests = [ {name: "CashReplaceVinMobile", tests: cashReplaceVinMobileTests}, {name: "CashReplaceWiperDropoff", tests: cashReplaceWiperDropoffTests}, {name: "CashReplaceWiperPromoInshop", tests: cashReplaceWiperPromoInShopTests}, + {name: "CashReplaceSwitchToInsuranceProgressiveNoComp", tests: cashReplaceSwitchToInsuranceProgressiveNoCompTests}, + // TODO: Uncomment when QA is ready to run heavy truck tests + // {name: "CashReplaceSplitWindshield", tests: CashReplaceSplitWindshieldTests}, {name: "InsuranceAcuityPaypal", tests: insuranceAcuityPaypalTests}, {name: "InsuranceITAC21stCentury", tests: insuranceITAC21stCenturyTests}, + {name: "InsuranceNoCompProgressive", tests: insuranceNoCompProgressiveTests}, + // {name: "InsuranceBigTruckVerified", tests: insuranceBigTruckVerifiedTests}, + {name: "InsuranceUnverified", tests: insuranceUnverifiedTests}, // {name: "InsuranceGeico", tests: insuranceGeicoTests}, // {name: "InsuranceITACOptimizedPriceValidationAllState", tests: insuranceITACOptimizedPriceValidationAllStateTests} @@ -78,9 +89,10 @@ const allStandardTests = [ // Alert validation scenarios const allAlertTests = [ - { name: "Alert Scenario 1: Heavy Truck", tests: heavyTruckTests }, + //TODO: Uncomment when QA is ready to run heavy truck tests + // { name: "Alert Scenario 1: Heavy Truck", tests: heavyTruckTests }, { name: "Alert Scenario 2: Repair and Replace", tests: repairAndReplaceTests }, - { name: "Alert Scenario 3: Split Windshield", tests: splitWindshieldTests }, + // { name: "Alert Scenario 3: Split Windshield", tests: splitWindshieldTests }, { name: "Alert Scenario 4: Repair Only", tests: repairOnlyTests }, { name: "Alert Scenario 5: Unserviceable Zip", tests: unserviceableZipTests }, { name: "Alert Scenario 6: Invalid Zip", tests: invalidZipTests }, @@ -166,10 +178,14 @@ async function runWorkflow(page: Page, testCase: TestCase) { } = testCase.testData; // Check if the vehicle damage includes a windshield crack - const hasWindshieldCrack = vehicleDamage!.some(damage => - damage === VehicleDamage.WindshieldCrack + const hasWindshieldCrack = vehicleDamage!.some(damage => + damage === VehicleDamage.WindshieldCrack || + damage === VehicleDamage.DriverSplitWindshield || + damage === VehicleDamage.PassengerSplitWindshield || + damage === VehicleDamage.SingleSplitWindshield ); + //============================= TEST WORKFLOW STEPS ============================= // Use Environment Variable to decide whether or not we want to skip content site aka home page @@ -273,6 +289,7 @@ async function runWorkflow(page: Page, testCase: TestCase) { let serviceLocationPage = testCase.pages.serviceLocationPage; await serviceLocationPage.handleServiceLocationPage(testCase.testData); + // Schedule appointment let schedulePage = testCase.pages.schedulePage; await schedulePage.handleSchedulePage(testCase.testData); @@ -300,7 +317,7 @@ async function runWorkflow(page: Page, testCase: TestCase) { } export async function handleInsuranceFlow(testCase: TestCase) { - const { flow, isPolicyDriver, endorsements, isRecalNotification } = testCase.testData; + const { isPolicyFound, isPolicyDriver, endorsements, isRecalVehicle, isCashInsuranceFlow, flow } = testCase.testData; // Check if the insurance policy has endorsements const hasEndorsements = endorsements && endorsements.length > 0; @@ -344,7 +361,7 @@ export async function handleInsuranceFlow(testCase: TestCase) { let policyInfoSubmittedPage = testCase.pages.policyInfoSubmittedPage; await policyInfoSubmittedPage.handlePolicyInfoSubmittedPage(); - if(isRecalNotification) + if(isRecalVehicle) { let recalibrationInfoPage = testCase.pages.recalibrationInfoPage; await recalibrationInfoPage.handleRecalibrationInfoPage(); @@ -353,4 +370,22 @@ export async function handleInsuranceFlow(testCase: TestCase) { //handle coveraage statement page let coverageStatementPage = testCase.pages.coverageStatementPage; await coverageStatementPage.handleCoverageStatementPage(testCase.testData); + + // Handle scenario where user selected Cash to Insurance and needs to go back through the flow + // right now we are choosing pay at appointment as payment method for this scenario + if (isCashInsuranceFlow) { + let serviceLocationPage = testCase.pages.serviceLocationPage; + await serviceLocationPage.nextPage(); + + //schedule + let schedulePage = testCase.pages.schedulePage; + await schedulePage.nextPage(); + //customer dertails + let contactDetailsPage = testCase.pages.contactDetailsPage; + await contactDetailsPage.nextPage(); + + //paymentmethods + let paymentMethodPage = testCase.pages.paymentMethodPage; + await paymentMethodPage.nextPage(); + } } \ No newline at end of file diff --git a/playwright-tests/tests/CashReplaceSplitWindshield.ts b/playwright-tests/tests/CashReplaceSplitWindshield.ts new file mode 100644 index 000000000..d1f12fef4 --- /dev/null +++ b/playwright-tests/tests/CashReplaceSplitWindshield.ts @@ -0,0 +1,74 @@ +//Imports here +import { ITestData } from 'framework/TestData' +import { PartQuestionType, PaymentType, VehicleDamage } from 'safelite-playwright-core'; +import { ITestCase } from '../framework/Typedefs' +import { VehicleLookupType } from 'safelite-playwright-core'; +import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core'; + +// Set the seed before generating any data +setFakerSeedFromTestName("CashReplaceSplitWindshield.ts"); + +// Now get the test data with the seeded faker +const CashReplaceSplitWindshieldData: Partial = { + ...getDefaultTestData(), // Get default data with current seed + + isHeavyTruck: true, // Flag for big truck + + customerDetails: { + ...getDefaultTestData().customerDetails!, + address: { + ...getDefaultTestData().customerDetails!.address, + postalCode: '55414' + } + }, + + // Override vehicle details + vehicleDetails: { + ...getDefaultTestData().vehicleDetails!, + year: '2006', + make: 'Navistar', + model: '5000 I', + style: '2 door conventional cab', + vehicleLookupType: VehicleLookupType.Zip + }, + + vehiclePartQuestions: [ + { + partQuestionType: PartQuestionType.DriverWindshield, + isOnPage: true, + optionToSelect: 'Green Tint', + secondaryQuestionOptionToSelect: 'driver side, encap, asymmetrically strengthen' + }, + { + partQuestionType: PartQuestionType.PassengerWindshield, + isOnPage: true, + optionToSelect: 'Green Tint', + secondaryQuestionOptionToSelect: 'passenger side, encap, asymmetrically strengthen' + }, + ], + + // Override default vehicle damage (Split Windshield) + vehicleDamage: [VehicleDamage.DriverSplitWindshield, VehicleDamage.PassengerSplitWindshield], + + // Override appointment details + appointmentDetails: { + ...getDefaultTestData().appointmentDetails!, + shopAddress: "504 Malcolm Ave Se, Minneapolis, MN 55414" + }, + + // Override payment details + paymentDetails: { + paymentType: PaymentType.PayAtService + } +} + +const CashReplaceSplitWindshieldTests: ITestCase[] = []; + +const tc = { + name: `CashReplaceSplitWindshield`, + tags: ['@E2E','@CashReplaceSplitWindshield', '@test_report', '@CASH'], + testData: CashReplaceSplitWindshieldData +}; +CashReplaceSplitWindshieldTests.push(tc); + +export default CashReplaceSplitWindshieldTests; \ No newline at end of file diff --git a/playwright-tests/tests/CashReplaceSwitchToInsuranceProgressiveNoComp.ts b/playwright-tests/tests/CashReplaceSwitchToInsuranceProgressiveNoComp.ts new file mode 100644 index 000000000..a6494792a --- /dev/null +++ b/playwright-tests/tests/CashReplaceSwitchToInsuranceProgressiveNoComp.ts @@ -0,0 +1,81 @@ +//Imports here +import { ITestData } from 'framework/TestData' +import { ServiceLocation } from "safelite-playwright-core"; +import { DamageType, PartQuestionType, PaymentType } from 'safelite-playwright-core' +import { ITestCase } from '../framework/Typedefs' +import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core'; + +// Set the seed for consistent data generation +setFakerSeedFromTestName("CashReplaceSwitchToInsuranceProgressiveNoComp"); + +// Define insurance test data for Progressive +const cashReplaceSwitchToInsuranceProgressiveNoCompData: Partial = { + ...getDefaultTestData(), + + isCashInsuranceFlow: true, + + // Override customer details based on provided ZIP code + customerDetails: { + ...getDefaultTestData().customerDetails!, + address: { + ...getDefaultTestData().customerDetails!.address, + postalCode: "70458", + }, + }, + + // Vehicle details for a 2012 Chrysler 300 + vehicleDetails: { + ...getDefaultTestData().vehicleDetails!, + year: "2012", + make: "Chrysler", + model: "300", + style: "4 door sedan" + }, + + partQuestions: [ + { + partQuestionType: PartQuestionType.GeneralQuestion1, + isOnPage: true, + optionToSelect: 'Yes' + }, + ], + + // Override payment details + paymentDetails: { + paymentType: PaymentType.PayWithInsurance, // Ensures insurance payment method is selected + }, + + // Insurance claim details + claimDetails: { + client: "Progressive", + policyNumber: "Mock495646B", + policyDeductible: 0, + damageDate: new Date(new Date().setDate(new Date().getDate() - 1)).toLocaleDateString("en-US", { month: "2-digit", day: "2-digit", year: "numeric" }), + damageCause: DamageType.Rock, + }, + + isPolicyFound: true, + isUseVehicleOnPolicy: true, + isPolicyDriver: true, + + // Appointment details for in-shop service + appointmentDetails: { + serviceLocation: ServiceLocation.InShop, + shopAddress: "8985 Yellow Brick Rd, Rosedale, MD 21237", + appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate, + }, + + +}; + +// Create and register the test case +const cashReplaceSwitchToInsuranceProgressiveNoCompTests: ITestCase[] = []; + +const tc = { + name: `CashReplaceSwitchToInsuranceProgressiveNoComp`, + tags: ["@E2E", "@CashReplaceSwitchToInsuranceProgressiveNoComp", "@test_report", "@CASH"], + testData: cashReplaceSwitchToInsuranceProgressiveNoCompData, +}; +cashReplaceSwitchToInsuranceProgressiveNoCompTests.push(tc); + +export default cashReplaceSwitchToInsuranceProgressiveNoCompTests; diff --git a/playwright-tests/tests/InsuranceBigTruckVerified.ts b/playwright-tests/tests/InsuranceBigTruckVerified.ts new file mode 100644 index 000000000..f31918279 --- /dev/null +++ b/playwright-tests/tests/InsuranceBigTruckVerified.ts @@ -0,0 +1,82 @@ +//Imports here +import { ITestData } from 'framework/TestData' +import { ServiceLocation, DamageType, PaymentType, PartQuestionType } from 'safelite-playwright-core'; +import { ITestCase } from '../framework/Typedefs' +import { PaymentMethod } from "framework/localTypes/Enums"; +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("InsuranceBigTruckVerified"); + +// Now get the test data with the seeded faker +const insuranceBigTruckVerifiedData: Partial = { + ...getDefaultTestData(), // Get default data with current seed + + // Key feature: Insurance flow with GEICO + paymentMethod: PaymentMethod.Insurance, + + // Insurance claim flags + isDuplicateClaim: true, + isPolicyFound: true, + isUseVehicleOnPolicy: true, + isHeavyTruck: true, + + // Override customer details with specific name and California location + customerDetails: { + ...getDefaultTestData().customerDetails!, + firstName: 'Big', + lastName: 'Truck', + address: { + ...getDefaultTestData().customerDetails!.address, + city: 'Ontario', + state: 'California', + postalCode: '43085' + } + }, + + + // Insurance claim details + claimDetails: { + client: 'USAA', + policyNumber: 'Mock900040BigTruck', + policyDeductible: 2000.00, + policyZip: '55414', + damageDate: new Date(new Date().setDate(new Date().getDate() - 1)).toLocaleDateString('en-US', {month: '2-digit', day: '2-digit', year: 'numeric'}), + damageCause: DamageType.Rock + }, + + // Hyundai vehicle details with VIN lookup + vehicleDetails: { + ...getDefaultTestData().vehicleDetails!, + year: '2025', + make: 'Peterbilt', + model: '579', + style: 'conventional cab', + vin: '1XPBDP9X6SD693446', + vehicleLookupType: VehicleLookupType.Vin, + }, + + // No need to override vehicleDamage as it already defaults to WindshieldCrack + + // Override for in-shop appointment + appointmentDetails: { + serviceLocation: ServiceLocation.InShop, + shopAddress: '5719 Brandt Pike, Dayton, OH 45424', + appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate + }, + + // Override payment details (empty because we skip payment method page in insurance flow) + paymentDetails: {} +} + +const insuranceBigTruckVerifiedTests: ITestCase[] = []; + +const tc = { + name: `InsuranceBigTruckVerified`, + tags: ['@E2E','@InsuranceBigTruckVerified', '@test_report', '@Insurance'], + testData: insuranceBigTruckVerifiedData +}; +insuranceBigTruckVerifiedTests.push(tc); + +export default insuranceBigTruckVerifiedTests; \ No newline at end of file diff --git a/playwright-tests/tests/InsuranceITAC21stCentury.ts b/playwright-tests/tests/InsuranceITAC21stCentury.ts index 09646d895..cb11b4083 100644 --- a/playwright-tests/tests/InsuranceITAC21stCentury.ts +++ b/playwright-tests/tests/InsuranceITAC21stCentury.ts @@ -20,7 +20,7 @@ const insuranceITAC21stCenturyData: Partial = { isDuplicateClaim: true, flow: Flow.Managed, isUseVehicleOnPolicy: true, - isRecalNotification: true, // Special flag for recalibration notification + isRecalVehicle: true, // Special flag for recalibration notification // Override customer details for California location customerDetails: { diff --git a/playwright-tests/tests/InsuranceNoCompProgressive.ts b/playwright-tests/tests/InsuranceNoCompProgressive.ts new file mode 100644 index 000000000..032456ea4 --- /dev/null +++ b/playwright-tests/tests/InsuranceNoCompProgressive.ts @@ -0,0 +1,90 @@ +//Imports here +import { ITestData } from 'framework/TestData' +import { ServiceLocation, DamageType, PaymentType, PartQuestionType } from 'safelite-playwright-core'; +import { ITestCase } from '../framework/Typedefs' +import { PaymentMethod } from "framework/localTypes/Enums"; +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("InsuranceNoCompProgressive"); + +// Now get the test data with the seeded faker +const insuranceNoCompProgressiveData: Partial = { + ...getDefaultTestData(), // Get default data with current seed + + // Key feature: Insurance flow with GEICO + paymentMethod: PaymentMethod.Insurance, + + // Insurance claim flags + isDuplicateClaim: true, + isPolicyFound: true, + isUseVehicleOnPolicy: true, + isHeavyTruck: false, + isPolicyDriver: true, + isRecalVehicle: true, + + // Override customer details with specific name and California location + customerDetails: { + ...getDefaultTestData().customerDetails!, + firstName: 'SHERI', + lastName: 'SCHATZ', + address: { + ...getDefaultTestData().customerDetails!.address, + city: 'Slidell', + state: 'Louisiana', + postalCode: '70458' + } + }, + + + // Insurance claim details + claimDetails: { + client: 'Progressive', + policyNumber: 'Mock495646B', + policyDeductible: 1722.20, + policyZip: '70458', + damageDate: new Date(new Date().setDate(new Date().getDate() - 1)).toLocaleDateString('en-US', {month: '2-digit', day: '2-digit', year: 'numeric'}), + damageCause: DamageType.Rock + }, + + // Hyundai vehicle details with VIN lookup + vehicleDetails: { + ...getDefaultTestData().vehicleDetails!, + year: '2015', + make: 'Acura', + model: 'MDX', + style: '4 door utility', + vehicleLookupType: VehicleLookupType.Zip, + }, + + // Part questions related to recalibration + partQuestions: [ + { + partQuestionType: PartQuestionType.GeneralQuestion1, + isOnPage: true, + optionToSelect: 'Yes' + }, + ], + + // Override for in-shop appointment + appointmentDetails: { + serviceLocation: ServiceLocation.InShop, + shopAddress: '56705 Garrett Road, Slidell, LA 70458', + appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate + }, + + // Override payment details (empty because we skip payment method page in insurance flow) + paymentDetails: {} +} + +const insuranceNoCompProgressiveTests: ITestCase[] = []; + +const tc = { + name: `InsuranceNoCompProgressive`, + tags: ['@E2E','@InsuranceNoCompProgressive', '@test_report', '@Insurance'], + testData: insuranceNoCompProgressiveData +}; +insuranceNoCompProgressiveTests.push(tc); + +export default insuranceNoCompProgressiveTests; \ No newline at end of file diff --git a/playwright-tests/tests/InsuranceUnverified.ts b/playwright-tests/tests/InsuranceUnverified.ts new file mode 100644 index 000000000..34d06697d --- /dev/null +++ b/playwright-tests/tests/InsuranceUnverified.ts @@ -0,0 +1,87 @@ +//Imports here +import { ITestData } from 'framework/TestData' +import { ServiceLocation, DamageType, PaymentType, PartQuestionType } from 'safelite-playwright-core'; +import { ITestCase } from '../framework/Typedefs' +import { PaymentMethod } from "framework/localTypes/Enums"; +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("InsuranceUnverified"); + +// Now get the test data with the seeded faker +const insuranceUnverifiedData: Partial = { + ...getDefaultTestData(), // Get default data with current seed + + // Key feature: Insurance flow with GEICO + paymentMethod: PaymentMethod.Insurance, + + // Insurance claim flags + isPolicyFound: false, + isPolicyUnverified: true, + isRecalVehicle: true, + + // Override customer details with specific name and California location + customerDetails: { + ...getDefaultTestData().customerDetails!, + firstName: 'Jane', + lastName: 'Unverified', + address: { + ...getDefaultTestData().customerDetails!.address, + city: 'Richmond', + state: 'Virginia', + postalCode: '23219' + } + }, + + + // Insurance claim details + claimDetails: { + client: '21st Century', + policyNumber: 'UnverifiedMock', + policyDeductible: "Unverified", + policyZip: '43123', + damageDate: new Date(new Date().setDate(new Date().getDate() - 1)).toLocaleDateString('en-US', {month: '2-digit', day: '2-digit', year: 'numeric'}), + damageCause: DamageType.Rock + }, + + // Hyundai vehicle details with VIN lookup + vehicleDetails: { + ...getDefaultTestData().vehicleDetails!, + year: '2014', + make: 'Honda', + model: 'Accord', + style: '4 door sedan', + vehicleLookupType: VehicleLookupType.Zip, + }, + + // Part questions related to recalibration + partQuestions: [ + { + partQuestionType: PartQuestionType.GeneralQuestion1, + isOnPage: true, + optionToSelect: 'Yes' + }, + ], + + // Override for in-shop appointment + appointmentDetails: { + serviceLocation: ServiceLocation.InShop, + shopAddress: '5719 Brandt Pike, Dayton, OH 45424', + appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate + }, + + // Override payment details (empty because we skip payment method page in insurance flow) + paymentDetails: {} +} + +const insuranceUnverifiedTests: ITestCase[] = []; + +const tc = { + name: `InsuranceUnverified`, + tags: ['@E2E','@InsuranceUnverified', '@test_report', '@Insurance'], + testData: insuranceUnverifiedData +}; +insuranceUnverifiedTests.push(tc); + +export default insuranceUnverifiedTests; \ No newline at end of file diff --git a/playwright-tests/tests/alert-validation/alert0001_HeavyTruck.ts b/playwright-tests/tests/alert-validation/alert0001_HeavyTruck.ts index 3e6eefc96..3ec0ff648 100644 --- a/playwright-tests/tests/alert-validation/alert0001_HeavyTruck.ts +++ b/playwright-tests/tests/alert-validation/alert0001_HeavyTruck.ts @@ -4,11 +4,23 @@ import { VehicleDamage, ServiceLocation } from 'safelite-playwright-core'; import { ITestCase } from '../../framework/Typedefs'; import { getNextWeekday } from 'safelite-playwright-core'; import { defaultTestData } from 'safelite-playwright-core'; +import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core'; // Heavy Truck (alert) Test Data const heavyTruckData: Partial = { ...defaultTestData, // Start with all defaults + + isHeavyTruck: true, // Flag for heavy truck vehicle + + customerDetails: { + ...getDefaultTestData().customerDetails!, + address: { + ...getDefaultTestData().customerDetails!.address, + postalCode: '21237' + } + }, + // Override specific fields with test-specific data vehicleDetails: { ...defaultTestData.vehicleDetails!, @@ -18,7 +30,7 @@ const heavyTruckData: Partial = { style: 'conventional cab' }, alertFlags: { - isHeavyTruckVehicle: true + isHeavyTruckVehicleAlert: true }, vehicleDamage: [ VehicleDamage.WindshieldOneChip, diff --git a/playwright-tests/tests/alert-validation/alert0003_SplitWindshield.ts b/playwright-tests/tests/alert-validation/alert0003_SplitWindshield.ts index b36a8af4c..e79c0c175 100644 --- a/playwright-tests/tests/alert-validation/alert0003_SplitWindshield.ts +++ b/playwright-tests/tests/alert-validation/alert0003_SplitWindshield.ts @@ -1,8 +1,9 @@ // Imports here import { ITestData } from 'framework/TestData'; -import { ITestCase } from '../../framework/Typedefs' +import { ITestCase, TestCase } from '../../framework/Typedefs' import { VehicleDamage } from 'safelite-playwright-core'; import { defaultTestData } from 'safelite-playwright-core'; +import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core'; // Windshield selected, when vehicle has split windshield (alert) Test Data const splitWindshieldData: Partial = { @@ -10,11 +11,21 @@ const splitWindshieldData: Partial = { // Override specific fields with test-specific data vehicleDamage: [ - VehicleDamage.WindshieldOneChip + VehicleDamage.SingleSplitWindshield, + VehicleDamage.DriverSplitWindshield, + VehicleDamage.PassengerSplitWindshield ], alertFlags: { isSplitWindshield: true - } + }, + customerDetails: { + ...getDefaultTestData().customerDetails!, + address: { + ...getDefaultTestData().customerDetails!.address, + postalCode: '55414' + } + }, + isHeavyTruck: true, // Flag for heavy truck vehicle }; const vehiclesToTest = [ @@ -35,20 +46,21 @@ const vehiclesToTest = [ make: 'Kenworth', model: 'T600', style: 'conventional cab' - } + }, ]; const splitWindshieldTests: ITestCase[] = []; // Generate test cases for each vehicle vehiclesToTest.forEach((vehicle, index) => { - const testData = { - ...splitWindshieldData, - vehicleDetails: { - ...defaultTestData.vehicleDetails!, - ...vehicle - } - }; + +    const testData = { +        ...splitWindshieldData, +        vehicleDetails: { +            ...defaultTestData.vehicleDetails!, +            ...vehicle +        }, +    } const tc = { name: `alert0003_${vehicle.make.toLowerCase()}_${vehicle.model.toLowerCase()} Windshield selected, when vehicle has split windshield - ${vehicle.make} ${vehicle.model} ${vehicle.year}`, diff --git a/src/digital-components/sierra-webchat/sierra-webchat.vue b/src/digital-components/sierra-webchat/sierra-webchat.vue index 2a114616a..8210e9075 100644 --- a/src/digital-components/sierra-webchat/sierra-webchat.vue +++ b/src/digital-components/sierra-webchat/sierra-webchat.vue @@ -94,8 +94,16 @@ export default { FirstName: transfer.data.first_name, LastName: transfer.data.last_name, Email: transfer.data.email, - Subject: transfer.data.chat_summary, // for now passing in chat_summery in Subject. But this will be changed in future. + Subject: transfer.data.chat_summary, }; + window.embedded_svc.settings.extraPrechatFormDetails = [ + { + label: "Scarlett AI Summary", + value: transfer.data.chat_summary, + transcriptFields: ["Scarlett_AI_Summary__c"], + displayToAgent: true, + }, + ]; if ( window.embedded_svc.liveAgentAPI && typeof window.embedded_svc.liveAgentAPI.startChat === "function" diff --git a/src/fmg-components/cart/cart.vue b/src/fmg-components/cart/cart.vue index 2a715bc93..ef3e0300c 100644 --- a/src/fmg-components/cart/cart.vue +++ b/src/fmg-components/cart/cart.vue @@ -117,6 +117,10 @@ {{ amountPaidText }} {{ getLineItemAmount(amountPaid) }} +
+ {{ donationCartItemName }} + {{ getLineItemAmount(donationCartItem.subTotal) }} +
{{ amountDueText }} {{ getLineItemAmount(amountDue, showCoverageAsPending) }} @@ -472,10 +476,6 @@ export default { }); } - if (this.donationCartItem) { - cartItems.push(this.donationCartItem); - } - return cartItems; }, }, @@ -1309,12 +1309,15 @@ export default { .sub-total, .sales-tax, .amount-due, - .amount-paid { - font-weight: 500; + .amount-paid, + .donation-amount { + font-family: + UrbanistSemibold, AvertaSemibold; //Okay to remove AvertaSemibold after 2025.06.19 merge/release color: $black; } .sub-total { border-top: 1px solid $green; + background-color: $green-100; } .service-type, .deductible { diff --git a/src/layouts/estimate/estimate.vue b/src/layouts/estimate/estimate.vue index 811cd1a86..2818d2ecb 100644 --- a/src/layouts/estimate/estimate.vue +++ b/src/layouts/estimate/estimate.vue @@ -62,7 +62,7 @@ import { saveSession } from "@/helpers/heritage-integration/order-helper.js"; import baseMixin from "@/mixins/base-mixin.js"; import { queryStrings } from "@/constants/query-strings"; import { nextTick } from "vue"; -import { consumeQueryFromStash } from "@/router/methods/helpers/querystring-stash"; +import { peekQueryFromStash } from "@/router/methods/helpers/querystring-stash"; // Define Validation Rules defineRule("zip-required", required(errorMessages.SERVICE_ZIP_REQUIRED)); @@ -100,7 +100,7 @@ export default { } const zip = - consumeQueryFromStash(queryStrings.ZIP_CODE) ?? + peekQueryFromStash(queryStrings.ZIP_CODE) ?? store.getters.order.serviceLocation.zipCode; var vinByAddressPromise; diff --git a/src/router/methods/helpers/querystring-stash.js b/src/router/methods/helpers/querystring-stash.js index 96b206572..b000f8feb 100644 --- a/src/router/methods/helpers/querystring-stash.js +++ b/src/router/methods/helpers/querystring-stash.js @@ -1,4 +1,5 @@ import { reactive } from "vue"; +import { debugLog } from "@/helpers/debug-log-helper"; const queryStash = reactive({ queries: [], @@ -27,10 +28,18 @@ export function stashAllQueries(toRoute) { } function getStashedQuery(key) { + debugLog(`Fetching querystring with key:`, key); const match = queryStash.queries.find( (entry) => entry?.key?.toLowerCase() === key?.toLowerCase() ); + if (match) { + debugLog(`Found result:`, match.value); + debugLog(`Already consumed:`, match.used); + } else { + debugLog(`Found no result`); + } + return match; } diff --git a/src/router/methods/route-logic/payment.js b/src/router/methods/route-logic/payment.js new file mode 100644 index 000000000..73f2b5cac --- /dev/null +++ b/src/router/methods/route-logic/payment.js @@ -0,0 +1,14 @@ +import store from "@/store"; +import { storeActions } from "@/constants/store-actions"; +import { paymentMethods } from "@/constants/payment-method-constants"; +import { debugLog } from "@/helpers/debug-log-helper"; + +export async function paymentBeforeEnter(to, from) { + const piaType = store.getters.order?.payment?.piaType; + debugLog(`Entering payment with type =`, piaType); + + if (piaType === paymentMethods.PAYPAL) { + debugLog(`Changing to payment type =`, paymentMethods.CREDIT_CARD); + await store.dispatch(storeActions.SAVE_PAYMENT_METHOD_CHOICE, paymentMethods.CREDIT_CARD); + } +} diff --git a/src/router/methods/routes.js b/src/router/methods/routes.js index 19c802aae..c029ce2cb 100644 --- a/src/router/methods/routes.js +++ b/src/router/methods/routes.js @@ -11,6 +11,7 @@ import { autoRouteBeforeEnter } from "@/router/methods/route-logic/auto-route"; import { errorBeforeEnter } from "@/router/methods/route-logic/error"; import { restartBeforeEnter } from "@/router/methods/route-logic/restart"; import { paymentMethodBeforeEnter } from "@/router/methods/route-logic/payment-method"; +import { paymentBeforeEnter } from "@/router/methods/route-logic/payment"; export const routes = [ // Non-virtual pages. @@ -32,7 +33,7 @@ export const routes = [ createRoute(routeData.SCHEDULE), createRoute(routeData.CUSTOMER_DETAILS), createRoute(routeData.PAYMENT_METHOD, paymentMethodBeforeEnter), - createRoute(routeData.PAYMENT), + createRoute(routeData.PAYMENT, paymentBeforeEnter), createRoute(routeData.PAYMENT_PIA_RETURN), createRoute(routeData.CONFIRMATION), createRoute(routeData.RETURN_USER), diff --git a/src/store/index.js b/src/store/index.js index c3ad4996e..073a50e23 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1072,33 +1072,6 @@ export const getters = { }, }; -function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) { - return (array ?? []).map((x) => x[propertyName]).filter((x) => x); -} - -function getTimeSlotsAdditionalEventData( - provisionalTriggers, - zipCode, - firstAvailableAppointmentDateString, - shopAppointmentType -) { - var numberOfDays = null; - if (firstAvailableAppointmentDateString) - numberOfDays = getDateDifferenceInDays( - new Date().toISOString().split("T")[0], - firstAvailableAppointmentDateString - ); - - if (shopAppointmentType) - return `FirstAvailableAppointment:${numberOfDays},Zip:${zipCode},ShopAppointmentType:${shopAppointmentType},ProvisionalTriggers:${provisionalTriggers.join( - "," - )}`; - else - return `FirstAvailableAppointment:${numberOfDays},Zip:${zipCode},ProvisionalTriggers:${provisionalTriggers.join( - "," - )}`; -} - // Export Actions export const actions = { // Vehicle API Actions @@ -2059,20 +2032,31 @@ export const actions = { }, }; - return globalMethods.callHttpClient({ + let hasCalled = timeSlotCallFlags.shop; + if (!hasCalled) { + timeSlotCallFlags.shop = true; + } + + const options = { method: endpoints.GetShopTimeSlots.method, endpoint: endpoints.GetShopTimeSlots.url, payload: payload, logApiCall: true, pageNameToLog: pageNameToLog, - additionalSuccessEventDataHandler: (response) => + }; + + // Only set handler if this is the very first call in this session + if (!hasCalled) { + options.additionalSuccessEventDataHandler = (response) => getTimeSlotsAdditionalEventData( response.data.provisionalTriggers, order.serviceLocation.zipCode, response.data.days?.[0]?.date, shopAppointmentType - ), - }); + ); + } + + return globalMethods.callHttpClient(options); }, getMobileTimeSlots(context, { payload: { startDate, endDate }, pageNameToLog }) { @@ -2116,19 +2100,30 @@ export const actions = { zipCode: order.serviceLocation.zipCode, }; - return globalMethods.callHttpClient({ + let hasCalled = timeSlotCallFlags.mobile; + if (!hasCalled) { + timeSlotCallFlags.mobile = true; + } + + const options = { method: endpoints.GetMobileTimeSlots.method, endpoint: endpoints.GetMobileTimeSlots.url, payload: payload, logApiCall: true, pageNameToLog: pageNameToLog, - additionalSuccessEventDataHandler: (response) => + }; + + // Only set handler if this is the very first call in this session + if (!hasCalled) { + options.additionalSuccessEventDataHandler = (response) => getTimeSlotsAdditionalEventData( response.data.provisionalTriggers, order.serviceLocation.zipCode, response.data.days?.[0]?.date - ), - }); + ); + } + + return globalMethods.callHttpClient(options); }, getMobilePremiumFee(context, { pageNameToLog }) { @@ -3457,100 +3452,6 @@ export default createStore({ actions, }); -// Private Functions - -function getHasRecalibrationPart(state) { - return containsRecalParts(state.order.lineItems); -} - -function sortArrayOfObjectsByPropertyValue(arrayOfObjects, propertyName) { - if (!arrayOfObjects) return null; - - return arrayOfObjects.sort((a, b) => { - if (a[propertyName] < b[propertyName]) return -1; - else if (a[propertyName] > b[propertyName]) return 1; - else return 0; - }); -} - -function convertGlassPieceNamingForApi(glassArray) { - if (!glassArray || glassArray.length === 0) return []; - - // check if array already converted. (likely when a session has been saved previously and then reloaded) - if (glassArray[0].location !== undefined) { - return glassArray; - } - - const converted = []; - glassArray.forEach((glass) => { - converted.push({ - location: glass.glassLocation, - name: glass.glassName, - }); - }); - return converted; -} - -function convertResultsForApi(resultsArray) { - if (!resultsArray) return []; - const converted = []; - resultsArray.forEach((answer) => { - converted.push({ - location: answer.glassLocation, - name: answer.glassName, - result: answer.result, - }); - }); - return converted; -} - -function convertGlassPieceNamingFromApi(glassArray) { - glassArray.forEach((glass) => { - glass.glassLocation = glass.glassPiece.location; - glass.glassName = glass.glassPiece.name; - delete glass.glassPiece; - return glass; - }); - return glassArray; -} - -function addPricesToLineItems(lineItems, pricingLineItems) { - lineItems.forEach((lineItem) => { - const lineItemIndex = pricingLineItems.findIndex( - (pricingLineItem) => pricingLineItem.partNumber === lineItem.partNumber - ); - - if (lineItem.childParts) { - addPricesToLineItems(lineItem.childParts, pricingLineItems); - } - - const pricedLineItem = pricingLineItems.splice(lineItemIndex, 1)[0]; - lineItem.laborAmount = pricedLineItem.laborAmount; - lineItem.sellingPrice = pricedLineItem.sellingPrice; - lineItem.kitPrice = pricedLineItem.kitPrice; - lineItem.salesTax = pricedLineItem.salesTax; - }); - - return lineItems; -} - -function addTaxesToPricedLineItems(pricedLineItems, taxingLineItems = []) { - pricedLineItems.forEach((pricedLineItem) => { - const lineItemIndex = taxingLineItems.findIndex( - (taxingLineItem) => taxingLineItem.partNumber === pricedLineItem.partNumber - ); - - if (pricedLineItem.childParts) { - addTaxesToPricedLineItems(pricedLineItem.childParts, taxingLineItems); - } - - const taxedLineItem = taxingLineItems.splice(lineItemIndex, 1)[0]; - pricedLineItem.salesTax = taxedLineItem?.salesTax ?? 0; - }); - - return pricedLineItems; -} - export function mapTaxedLineItemsToStoreFormat(availableLineItems, storeLineItems) { // clone the lineItems array because what we're passing in is referencing the store directly const lineItems = deepClone(storeLineItems); @@ -3658,6 +3559,127 @@ export function getArrayOfAllLineItemsAndChildParts(lineItems) { return consolidatedLineItemsArray; } +// Private Functions + +function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) { + return (array ?? []).map((x) => x[propertyName]).filter((x) => x); +} + +function getTimeSlotsAdditionalEventData( + provisionalTriggers, + zipCode, + firstAvailableAppointmentDateString, + shopAppointmentType +) { + var numberOfDays = null; + if (firstAvailableAppointmentDateString) + numberOfDays = getDateDifferenceInDays( + new Date().toISOString().split("T")[0], + firstAvailableAppointmentDateString + ); + + if (shopAppointmentType) + return `FirstAvailableAppointment:${numberOfDays},Zip:${zipCode},ShopAppointmentType:${shopAppointmentType},ProvisionalTriggers:${provisionalTriggers.join( + "," + )}`; + else + return `FirstAvailableAppointment:${numberOfDays},Zip:${zipCode},ProvisionalTriggers:${provisionalTriggers.join( + "," + )}`; +} + +function getHasRecalibrationPart(state) { + return containsRecalParts(state.order.lineItems); +} + +function sortArrayOfObjectsByPropertyValue(arrayOfObjects, propertyName) { + if (!arrayOfObjects) return null; + + return arrayOfObjects.sort((a, b) => { + if (a[propertyName] < b[propertyName]) return -1; + else if (a[propertyName] > b[propertyName]) return 1; + else return 0; + }); +} + +function convertGlassPieceNamingForApi(glassArray) { + if (!glassArray || glassArray.length === 0) return []; + + // check if array already converted. (likely when a session has been saved previously and then reloaded) + if (glassArray[0].location !== undefined) { + return glassArray; + } + + const converted = []; + glassArray.forEach((glass) => { + converted.push({ + location: glass.glassLocation, + name: glass.glassName, + }); + }); + return converted; +} + +function convertResultsForApi(resultsArray) { + if (!resultsArray) return []; + const converted = []; + resultsArray.forEach((answer) => { + converted.push({ + location: answer.glassLocation, + name: answer.glassName, + result: answer.result, + }); + }); + return converted; +} + +function convertGlassPieceNamingFromApi(glassArray) { + glassArray.forEach((glass) => { + glass.glassLocation = glass.glassPiece.location; + glass.glassName = glass.glassPiece.name; + delete glass.glassPiece; + return glass; + }); + return glassArray; +} + +function addPricesToLineItems(lineItems, pricingLineItems) { + lineItems.forEach((lineItem) => { + const lineItemIndex = pricingLineItems.findIndex( + (pricingLineItem) => pricingLineItem.partNumber === lineItem.partNumber + ); + + if (lineItem.childParts) { + addPricesToLineItems(lineItem.childParts, pricingLineItems); + } + + const pricedLineItem = pricingLineItems.splice(lineItemIndex, 1)[0]; + lineItem.laborAmount = pricedLineItem.laborAmount; + lineItem.sellingPrice = pricedLineItem.sellingPrice; + lineItem.kitPrice = pricedLineItem.kitPrice; + lineItem.salesTax = pricedLineItem.salesTax; + }); + + return lineItems; +} + +function addTaxesToPricedLineItems(pricedLineItems, taxingLineItems = []) { + pricedLineItems.forEach((pricedLineItem) => { + const lineItemIndex = taxingLineItems.findIndex( + (taxingLineItem) => taxingLineItem.partNumber === pricedLineItem.partNumber + ); + + if (pricedLineItem.childParts) { + addTaxesToPricedLineItems(pricedLineItem.childParts, taxingLineItems); + } + + const taxedLineItem = taxingLineItems.splice(lineItemIndex, 1)[0]; + pricedLineItem.salesTax = taxedLineItem?.salesTax ?? 0; + }); + + return pricedLineItems; +} + function getFlattenedArrayOfLineItemsWithChildParts(lineItems, childPartRecursiveCall = false) { let flattenedArray = []; lineItems?.forEach((lineItem) => { @@ -3966,3 +3988,8 @@ function getExternalParameterDefaultState() { function saveExternalParameterState(externalParameterState) { window.sessionStorage.setItem("externalParameterState", JSON.stringify(externalParameterState)); } + +const timeSlotCallFlags = { + shop: false, + mobile: false, +}; diff --git a/src/styles/ux-variables.scss b/src/styles/ux-variables.scss index 1f17a6cf1..9acab9499 100644 --- a/src/styles/ux-variables.scss +++ b/src/styles/ux-variables.scss @@ -121,8 +121,8 @@ $body-color: $gray-600; //Fonts $font-family-sans-serif: AvertaRegular, Arial, Helvetica, sans-serif; -$font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", - monospace; +$font-family-monospace: + SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; // stylelint-enable value-keyword-case $font-family-base: $font-family-sans-serif; $font-family-code: $font-family-monospace; @@ -170,8 +170,7 @@ $spacers: ( /* 16px */ 5: $spacer * 1.5, /* 24px */ 6: $spacer * 2, /* 32px */ 7: $spacer * 2.5, - /* 40px */ 8: $spacer * 3, - /* 48px */ + /* 40px */ 8: $spacer * 3 /* 48px */, ); //Enable negative spacing (does NOT work on padding)