diff --git a/environment-variables.js b/environment-variables.js index 4d894b006..17c797cfd 100644 --- a/environment-variables.js +++ b/environment-variables.js @@ -13,6 +13,8 @@ const environmentVariables = { ['__VUE_APP_SAFELITE_HOP__']: "https://sv2-safelitehop-dev.safelite.com/fmgCheckoutShared.aspx", ['__VUE_APP_SESSION_TIMEOUT_MINUTES__']: 30, ['__VUE_APP_SOLARWINDS_MONITORING_SCRIPT__']: "", + ['__VUE_APP_ADYEN_ENVIRONMENT__']: "test", + ['__VUE_APP_ADYEN_CLIENT_KEY__']: "test_WYGT4F5ZT5BRRL5MPWHK6QLYOIXZXROD", }; const keysOnly = Object.keys(environmentVariables); diff --git a/package-lock.json b/package-lock.json index 7d44b181a..2e45411d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "Safelite", "version": "0.1.0", "dependencies": { + "@adyen/adyen-web": "^6.27.0", "@iframe-resizer/child": "^5.3.3", "axios": "^0.30.2", "bootstrap": "^5.3.3", @@ -59,6 +60,18 @@ "node": "8 || 9 || 10 || 11 || 12 || 13 || 14 || 15 || 16 || 17 || 18 || 19 || 20 || 21 || 22" } }, + "node_modules/@adyen/adyen-web": { + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/@adyen/adyen-web/-/adyen-web-6.27.0.tgz", + "integrity": "sha512-6E6hqEkMkoY/FevjO53zhgeCWMt+i/PDc11MX6eVM7Fvd+Sdgc9cIwTBz+NKIXs4ZerG6n2gd+ZIZEywqF/aBQ==", + "license": "MIT", + "dependencies": { + "@types/applepayjs": "14.0.9", + "@types/googlepay": "0.7.8", + "classnames": "2.5.1", + "preact": "10.22.1" + } + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -3173,6 +3186,12 @@ "node": ">=10.13.0" } }, + "node_modules/@types/applepayjs": { + "version": "14.0.9", + "resolved": "https://registry.npmjs.org/@types/applepayjs/-/applepayjs-14.0.9.tgz", + "integrity": "sha512-xEprYbb0TEP/XIiDPbVnTYpDai8fTFpsQfVSfTd81Is2GOMUy7ie019eyX6Mz2ECxfjoUVKaiGSL577roIeHCg==", + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -3328,6 +3347,12 @@ "@types/send": "*" } }, + "node_modules/@types/googlepay": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/@types/googlepay/-/googlepay-0.7.8.tgz", + "integrity": "sha512-EHl7G7jIeit/Px/fi/Du/19SZDvqrRhy0DjJX40Vzs/97m3sro9mzVyAYNC0pfzpWGDY+zPjtTLEfhV+OYWUcQ==", + "license": "MIT" + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -5789,6 +5814,12 @@ "dev": true, "license": "MIT" }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, "node_modules/clean-css": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", @@ -13681,6 +13712,16 @@ "dev": true, "license": "MIT" }, + "node_modules/preact": { + "version": "10.22.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.22.1.tgz", + "integrity": "sha512-jRYbDDgMpIb5LHq3hkI0bbl+l/TQ9UnkdQ0ww+lp+4MMOdqaUYdFc5qeyP+IV8FAd/2Em7drVPeKdQxsiWCf/A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", diff --git a/package.json b/package.json index 7d06ad112..c55259920 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "lint": "vue-cli-service lint" }, "dependencies": { + "@adyen/adyen-web": "^6.27.0", "@iframe-resizer/child": "^5.3.3", "axios": "^0.30.2", "bootstrap": "^5.3.3", diff --git a/playwright-tests/framework/TestData.ts b/playwright-tests/framework/TestData.ts index 9ed648426..1af123c41 100644 --- a/playwright-tests/framework/TestData.ts +++ b/playwright-tests/framework/TestData.ts @@ -3,7 +3,8 @@ import { PaymentMethod } from './localTypes/Enums' export interface ITestData extends base { // Put any project-specific data here. Anything useful to other Safelite projects should be submitted as a pull request to safelite-playwright-core. - paymentMethod: PaymentMethod - isOptedInForTextMessages: boolean - totalAmount?: number + paymentMethod: PaymentMethod, + isOptedInForTextMessages: boolean, + totalAmount?: number, + isForcedOEM?: boolean } \ No newline at end of file diff --git a/playwright-tests/framework/TestPages.ts b/playwright-tests/framework/TestPages.ts index e873a1c7b..c2cc04b35 100644 --- a/playwright-tests/framework/TestPages.ts +++ b/playwright-tests/framework/TestPages.ts @@ -24,6 +24,7 @@ import { CCPolicyInfoPage } from "../pages/CCPolicyInfoPage" import { DuplicateCheckPage } from "../pages/DuplicateCheckPage" import { PolicyVehiclesPage } from "../pages/PolicyVehiclesPage" import { PolicyInfoSubmittedPage } from "../pages/PolicyInfoSubmittedPage" +import { HeritageProblemGlassQuestionsPage } from "../pages/HeritageProblemGlassQuestionsPage" import RecalibrationInfoPage from "../pages/RecalibrationInfoPage" import { CoverageStatementPage } from "../pages/CoverageStatementPage" import { VerifyDetailsPage } from "../pages/VerifyDetailsPage" @@ -48,6 +49,7 @@ export interface ITestPages { paymentMethodPage: PaymentMethodPage, policyInfoSubmittedPage: PolicyInfoSubmittedPage, policyVehiclesPage: PolicyVehiclesPage, + heritageProblemGlassQuestionsPage: HeritageProblemGlassQuestionsPage, recalibrationInfoPage: RecalibrationInfoPage, schedulePage: SchedulePage, mobileDetailsPage: MobileDetailsPage, @@ -83,6 +85,7 @@ export const createTestPages: TestPagesFactory = (page: Page) => { paymentMethodPage: new PaymentMethodPage(page), policyInfoSubmittedPage: new PolicyInfoSubmittedPage(page), policyVehiclesPage: new PolicyVehiclesPage(page), + heritageProblemGlassQuestionsPage: new HeritageProblemGlassQuestionsPage(page), recalibrationInfoPage: new RecalibrationInfoPage(page), schedulePage: new SchedulePage(page), mobileDetailsPage: new MobileDetailsPage(page), diff --git a/playwright-tests/pages/BasePage.ts b/playwright-tests/pages/BasePage.ts index 6596986df..1562677a2 100644 --- a/playwright-tests/pages/BasePage.ts +++ b/playwright-tests/pages/BasePage.ts @@ -169,4 +169,20 @@ export class BasePage { Soft.expect(actualProgressPercentage).toBe(progressPercentage); console.log(`Progress Bar Percentage: Actual - ${actualProgressPercentage} vs Expected - ${progressPercentage}`); } + + async validateOEMPart( isOEM: boolean): Promise { + if (isOEM!) { + // Get Vuex state from localStorage + const vuexState = JSON.parse(await this.page.evaluate('localStorage.getItem(\'vuex\')')); + + // Validate the presence of an OEM part + if (vuexState.order?.lineItems?.glassParts?.length > 0) { + const firstGlassPartNumber = vuexState.order.lineItems.glassParts[0].partNumber; + + await expect(firstGlassPartNumber.includes("OEM")).toBe(true); + } else { + throw new Error("No glass parts found in the order"); + } + } + } } \ No newline at end of file diff --git a/playwright-tests/pages/HeritageProblemGlassQuestionsPage.ts b/playwright-tests/pages/HeritageProblemGlassQuestionsPage.ts new file mode 100644 index 000000000..097fd5200 --- /dev/null +++ b/playwright-tests/pages/HeritageProblemGlassQuestionsPage.ts @@ -0,0 +1,42 @@ +import { expect, type Locator, type Page } from '@playwright/test'; +import { InsuranceBasePage } from './InsuranceBasePage'; +import { ITestData } from 'framework/TestData'; +import { step } from 'framework/localTypes/Step'; + +export class HeritageProblemGlassQuestionsPage extends InsuranceBasePage { + readonly provideVinManuallyButton: Locator; + readonly provideLicensePlateButton: Locator; + readonly provideHomeAddressButton: Locator; + readonly continueOnVinPopUpButton: Locator; + readonly dontShareVinButton: Locator; + + readonly yestoGlassQuestionButton: Locator; + readonly notoGlassQuestionButton: Locator; + readonly continueButton: Locator; + + + constructor(page: Page) { + super(page); + + this.provideVinManuallyButton = page.locator('label[for="VINManually"]'); + this.provideLicensePlateButton = page.locator('label[for="licensePlate"]'); + this.provideHomeAddressButton = page.locator('label[for="HomeAddress"]'); + this.continueOnVinPopUpButton = page.locator('button:has-text("Continue")'); + this.dontShareVinButton = page.getByRole('button', { name: "I would rather not share my VIN" }); + + this.yestoGlassQuestionButton = page.locator('label:has-text("Yes")'); + this.notoGlassQuestionButton = page.locator('label:has-text("No")'); + this.continueButton = page.locator('button[type="submit"].btn-success.nav-continue'); + + } + + + @step("HeritageProblemGlassQuestionsPage >> Handle VIN popup and answer glass question on heritage") + // Handles OEM scenario with vehicle that has only one glass question + async handleHeritageProblemGlassQuestionsPage(testData: Partial){ + + await this.dontShareVinButton.click(); + await this.yestoGlassQuestionButton.click(); + await this.continueButton.click(); + } +} \ No newline at end of file diff --git a/playwright-tests/pages/HomePage.ts b/playwright-tests/pages/HomePage.ts index b28dd75a4..3d44b023a 100644 --- a/playwright-tests/pages/HomePage.ts +++ b/playwright-tests/pages/HomePage.ts @@ -30,7 +30,7 @@ export class HomePage extends BasePage { super(page); this.page = page; - this.letsGetStartedButton = this.page.locator('.hero-content #zipCodeTextboxButton'); + this.letsGetStartedButton = this.page.locator('#zipCodeTextboxButton'); this.cusmodalPopup = this.page.locator('#Cusmodalpopup'); this.closePopupButton = this.page.getByRole('button', { name: '×' }); @@ -47,7 +47,7 @@ export class HomePage extends BasePage { //Zip Entry this.enterServiceZipTextBox = this.page.locator('#zipCodeTextbox'); - this.zipEntryLetsGetStartedButton = this.page.locator('#zipCodeTextboxButton'); + this.zipEntryLetsGetStartedButton = this.page.locator('#zipCodeTextbox'); this.getQuoteAndScheduleButton = this.page.getByLabel('main').getByRole('link', { name: 'Get quote + schedule' }); } @@ -61,10 +61,11 @@ export class HomePage extends BasePage { async letsGetStarted(zip: string, enterFunnelWithZip: boolean) { if (enterFunnelWithZip) { - await this.letsGetStartedButton.evaluate((element, zip) => { + await this.zipEntryLetsGetStartedButton.fill(zip); + /* await this.letsGetStartedButton.evaluate((element, zip) => { const currentHref = element.getAttribute('href') || ''; - element.setAttribute('href', `${currentHref}&zipCode=${zip}`); - }, zip); + element.setAttribute('href', `${currentHref}?zipCode=${zip}`); + }, zip);*/ } // Wait until modal pop up opens and close pop up diff --git a/playwright-tests/pages/LookupPage.ts b/playwright-tests/pages/LookupPage.ts index e5f2fca04..7d6c1df03 100644 --- a/playwright-tests/pages/LookupPage.ts +++ b/playwright-tests/pages/LookupPage.ts @@ -81,9 +81,9 @@ export class LookupPage extends BasePage { // Map of expected messages by lookup type const expectedMessages = { - 'address': "Your address didn't return a VIN match.Please re-enter the information below or provide your VIN in a different way.", - 'license plate': "Your license plate didn’t return a VIN match.Please re-enter the information above or provide your VIN in a different way.", - 'VIN': "Your VIN didn’t return a vehicle matchPlease re-enter your VIN or we can look up your VIN for you.", + 'address': "Your address didn't return a VIN match.Please re-enter the information below or provide your VIN in a different way.", + 'license plate': "Your license plate didn’t return a VIN match.Please re-enter the information above or provide your VIN in a different way.", + 'VIN': "Your VIN didn’t return a vehicle matchPlease re-enter your VIN or we can look up your VIN for you.", 'ZIP code': "Your ZIP code didn't return a match.Please re-enter the information above or provide your VIN in a different way." }; @@ -106,19 +106,19 @@ export class LookupPage extends BasePage { } } - async handleZipValidation(testData: Partial, zip: string, lookupType: VehicleLookupType, alertFlags?: IAlertFlags): Promise { + async handleZipValidation(testData: Partial): Promise { - let { vehicleDamage } = testData; + let { customerDetails,vehicleDamage, vehicleDetails, alertFlags } = testData; // Only proceed with validation if alertFlags is provided if (alertFlags) { if (alertFlags.isUnserviceableZip) { - await this.checkForUnserviceableZipAlertMessage(zip); + await this.checkForUnserviceableZipAlertMessage(customerDetails!.address.postalCode!); throw new TestSuccessAlert('Unserviceable ZIP validation successful.'); } else if (alertFlags.isInvalidZip) { await this.checkForInvalidZipAlertMessage(); throw new TestSuccessAlert('Invalid ZIP validation successful.'); } else if (alertFlags.isVinNotFound) { - await this.checkForVinNotFoundAlertMessage(lookupType); + await this.checkForVinNotFoundAlertMessage(vehicleDetails!.vehicleLookupType!); throw new TestSuccessAlert('Vin not found validation successful.'); } else { diff --git a/playwright-tests/pages/OrderConfirmationPage.ts b/playwright-tests/pages/OrderConfirmationPage.ts index 02951bdd2..a65c80fd2 100644 --- a/playwright-tests/pages/OrderConfirmationPage.ts +++ b/playwright-tests/pages/OrderConfirmationPage.ts @@ -47,7 +47,7 @@ export class OrderConfirmationPage extends BasePage { async validateOrderConfirmationPage(testData: Partial) { // Destructure data we use const { vehicleDetails, customerDetails, servicePackage, isCashInsuranceFlow, - isPolicyFound, claimDetails, paymentDetails, isUseVehicleOnPolicy, paymentMethod, isPolicyUnverified } = testData; + isPolicyFound, claimDetails, paymentDetails, isUseVehicleOnPolicy, paymentMethod, isPolicyUnverified} = testData; await this.serviceText.waitFor({ state: "visible" }); Soft.expect((await this.getActualAppointmentSummary()).map(item => item.toLowerCase())).toEqual((await this.getExpectedAppointmentSummary(testData)).map(item => item.toLowerCase())); diff --git a/playwright-tests/pages/PaymentMethodPage.ts b/playwright-tests/pages/PaymentMethodPage.ts index 4ea1a8b82..015a088ae 100644 --- a/playwright-tests/pages/PaymentMethodPage.ts +++ b/playwright-tests/pages/PaymentMethodPage.ts @@ -499,12 +499,16 @@ export class PaymentMethodPage extends BasePage { @step("PaymentMethodPage >> Select Payment Method: ") async handlePaymentMethodPage(testData: Partial) { - const { servicePackage, isRecalVehicle, paymentDetails } = testData; + const { servicePackage, isRecalVehicle, paymentDetails, isForcedOEM } = testData; await this.validateProgressBar(ProgressBarPercentages.PaymentMethodPage); await this.validatePaymentDetailsPage(testData); await this.ValidateAfterPayBreakOutSection(); + if (isForcedOEM){ + await this.validateOEMPart(isForcedOEM); + } + // Verify VAPS wipers on backend for standard and premium packages if (servicePackage === ServicePackage.Standard || servicePackage === ServicePackage.Premium) { await this.verifyVAPS(); diff --git a/playwright-tests/pages/PaypalPage.ts b/playwright-tests/pages/PaypalPage.ts index ce5547f3c..e67e0a57b 100644 --- a/playwright-tests/pages/PaypalPage.ts +++ b/playwright-tests/pages/PaypalPage.ts @@ -41,6 +41,8 @@ export class PaypalPage extends BasePage { await this.paypalLoginButton.click(); await this.completePurchaseButton.click(); } else { + await this.usernameTextBox.waitFor({ state: 'visible' }); + 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(); await this.tryAnotherWayButton.click(); diff --git a/playwright-tests/pages/SchedulePage.ts b/playwright-tests/pages/SchedulePage.ts index 77b330aff..944b84136 100644 --- a/playwright-tests/pages/SchedulePage.ts +++ b/playwright-tests/pages/SchedulePage.ts @@ -35,6 +35,7 @@ export class SchedulePage extends BasePage { readonly timeSlots: Locator; readonly allDayDropOffButton: Locator; readonly pickATimeButton: Locator; + readonly mobileFirstModalCloseButton: Locator; constructor(page: Page) { super(page); @@ -65,6 +66,7 @@ export class SchedulePage extends BasePage { this.viewMoreDatesLink = this.page.getByText(/View more dates/).first(); this.appointmentDuration = this.page.locator('.duration-text-block'); this.timeSlots = this.page.locator('fieldset:has(>legend#chooseTimeSlot) label').filter({ visible: true}); + this.mobileFirstModalCloseButton = this.page.getByRole('dialog').getByRole('button', { name: 'Close' }); } async selectLocation(testData: Partial) { @@ -229,6 +231,9 @@ export class SchedulePage extends BasePage { async handleSchedulePage(testData: Partial) { const { appointmentDetails } = testData; await this.validateProgressBar(ProgressBarPercentages.SchedulePage); + if (await this.mobileFirstModalCloseButton.isVisible()) { + await this.mobileFirstModalCloseButton.click(); + } await this.selectLocation(testData); await this.scheduleFirstAppointment(testData); await this.nextPage(); diff --git a/playwright-tests/pages/ServicePackagesPage.ts b/playwright-tests/pages/ServicePackagesPage.ts index 63a60a6e6..ba95f67cd 100644 --- a/playwright-tests/pages/ServicePackagesPage.ts +++ b/playwright-tests/pages/ServicePackagesPage.ts @@ -44,7 +44,7 @@ export class ServicePackagesPage extends BasePage { this.promoCodeTextbox = this.page.getByLabel('Enter a promo code'); this.applyPromoButton = this.page.getByRole('button', { name: 'Apply promo code' }); this.repeatedClicksModalCloseButton = this.page.locator('.QSISlider').locator('img[src*=\'close\']'); - this.afterPayBanner = this.page.locator('#afterpay-banner'); + this.afterPayBanner = this.page.locator('div.afterpay-modal-banner'); this.glassOnlypackagePrice = this.page.locator('fieldset:has(legend#ServicePackageQuestion) label').filter({ hasText: 'Glass service only' }).locator('.pricing-info'); } diff --git a/playwright-tests/pages/ServiceZipPage.ts b/playwright-tests/pages/ServiceZipPage.ts index 91393f892..6c1a178f5 100644 --- a/playwright-tests/pages/ServiceZipPage.ts +++ b/playwright-tests/pages/ServiceZipPage.ts @@ -12,9 +12,9 @@ export class ServiceZipPage extends LookupPage { @step("ZipLookupPage >> Lookup by service ZIP: ") async handleServiceZipPage(testData: Partial) { - const { customerDetails, vehicleDetails, alertFlags } = testData; + const { customerDetails } = testData; await this.validateProgressBar(ProgressBarPercentages.ServiceZipPage); await this.enterZip(customerDetails!.address.postalCode!); - await this.handleZipValidation(testData, customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); + await this.handleZipValidation(testData); } } \ No newline at end of file diff --git a/playwright-tests/pages/VehicleLookupAddressPage.ts b/playwright-tests/pages/VehicleLookupAddressPage.ts index c5f37ee3d..50a7f1b24 100644 --- a/playwright-tests/pages/VehicleLookupAddressPage.ts +++ b/playwright-tests/pages/VehicleLookupAddressPage.ts @@ -29,10 +29,10 @@ export class VehicleLookupAddressPage extends LookupPage { @step("VehicleLookupAddressPage >> Lookup by address: ") async handleVehicleLookupAddressPage(testData: Partial) { - const { customerDetails, vehicleDetails, alertFlags } = testData; + const { customerDetails, vehicleDetails } = testData; await this.validateProgressBar(ProgressBarPercentages.VehicleLookupAddressPage); await this.lookupVehicleByAddress(customerDetails!, vehicleDetails!); - await this.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); + await this.handleZipValidation(testData); } } \ No newline at end of file diff --git a/playwright-tests/pages/VehicleLookupLicensePage.ts b/playwright-tests/pages/VehicleLookupLicensePage.ts index e20828b6e..a19d2156b 100644 --- a/playwright-tests/pages/VehicleLookupLicensePage.ts +++ b/playwright-tests/pages/VehicleLookupLicensePage.ts @@ -34,11 +34,11 @@ export class VehicleLookupLicensePage extends LookupPage { @step("VehicleLookupLicensePage >> Lookup by license plate: ") async handleVehicleLookupLicensePage(testData: Partial) { - const { customerDetails, vehicleDetails, alertFlags } = testData; + const { customerDetails, vehicleDetails } = testData; await this.validateProgressBar(ProgressBarPercentages.VehicleLookupLicensePage); await this.enterPlateDetails(vehicleDetails!); await this.enterZip(customerDetails!.address.postalCode!); - await this.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); + await this.handleZipValidation(testData); } } \ No newline at end of file diff --git a/playwright-tests/pages/VinLookupPage.ts b/playwright-tests/pages/VinLookupPage.ts index 3b69fc2d6..2f508acda 100644 --- a/playwright-tests/pages/VinLookupPage.ts +++ b/playwright-tests/pages/VinLookupPage.ts @@ -24,11 +24,11 @@ export class VinLookupPage extends LookupPage { @step("VinLookupPage >> Lookup by VIN: ") async handleVehicleLookupVinPage(testData: Partial) { - const { customerDetails, vehicleDetails, alertFlags } = testData; + const { customerDetails, vehicleDetails } = testData; let vin: string; if (Array.isArray(vehicleDetails?.vin)) { - vin = vehicleDetails.vin[0]; + vin = vehicleDetails!.vin[0]; } else { vin = vehicleDetails?.vin || ''; } @@ -36,6 +36,6 @@ export class VinLookupPage extends LookupPage { await this.validateProgressBar(ProgressBarPercentages.VinLookupPage); await this.enterVin(vin); await this.enterZip(customerDetails!.address.postalCode!); - await this.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); + await this.handleZipValidation(testData); } } \ No newline at end of file diff --git a/playwright-tests/pages/forms/AddressForm.ts b/playwright-tests/pages/forms/AddressForm.ts index 4ddb6a512..92f270320 100644 --- a/playwright-tests/pages/forms/AddressForm.ts +++ b/playwright-tests/pages/forms/AddressForm.ts @@ -57,7 +57,7 @@ export class AddressForm extends BasePage { ); }); - await this.page.hover(".pac-container .pac-item"); + await this.addressSuggestionList.hover(); await new Promise(resolve => setTimeout(resolve, 1000)); await this.addressSuggestionList.click(); // Select the first suggestion //await this.streetAddressTextBox.press('Tab'); // Move focus to ZIP code field diff --git a/playwright-tests/tests/0000__M.test.ts b/playwright-tests/tests/0000__M.test.ts index c8788b04a..cd8270a8a 100644 --- a/playwright-tests/tests/0000__M.test.ts +++ b/playwright-tests/tests/0000__M.test.ts @@ -28,6 +28,7 @@ import insuranceITAC21stCenturyTests from "./InsuranceITAC21stCentury"; import insuranceGeicoTests from "./InsuranceGeico"; import insuranceITACOptimizedPriceValidationAllStateTests from "./InsuranceITACOptimizedPriceValidationAllState"; import insuranceNoCompProgressiveTests from "./InsuranceNoCompProgressive"; +import insuranceOEMAllstateTests from "./InsuranceOEMAllState"; import cashRepairInShopAfterPayTests from "./CashRepairInShopAfterPay"; import cashRepairInShopPayPalTests from "./CashRepairInShopPayPal"; import cashReplaceMultiSlidingGlassDropoffTests from "./CashReplaceMultiSlidingGlassDropoff"; @@ -82,6 +83,7 @@ const allStandardTests = [ {name: "InsuranceITAC21stCentury", tests: insuranceITAC21stCenturyTests}, {name: "InsuranceNoCompProgressive", tests: insuranceNoCompProgressiveTests}, // {name: "InsuranceBigTruckVerified", tests: insuranceBigTruckVerifiedTests}, + {name: "InsuranceOEMAllstate", tests: insuranceOEMAllstateTests}, {name: "InsuranceUnverified", tests: insuranceUnverifiedTests}, // {name: "InsuranceGeico", tests: insuranceGeicoTests}, // {name: "InsuranceITACOptimizedPriceValidationAllState", tests: insuranceITACOptimizedPriceValidationAllStateTests} @@ -323,7 +325,7 @@ async function runWorkflow(page: Page, testCase: TestCase) { } export async function handleInsuranceFlow(testCase: TestCase) { - const { isPolicyFound, isPolicyDriver, endorsements, isRecalVehicle, isCashInsuranceFlow, flow } = testCase.testData; + const { isPolicyFound, isPolicyDriver, endorsements, isRecalVehicle, isCashInsuranceFlow, isForcedOEM, flow } = testCase.testData; // Check if the insurance policy has endorsements const hasEndorsements = endorsements && endorsements.length > 0; @@ -332,6 +334,13 @@ export async function handleInsuranceFlow(testCase: TestCase) { let insuranceCompanyPage = testCase.pages.insuranceCompanyPage; await insuranceCompanyPage.handleInsuranceCompanyPage(testCase.testData); + // Handle OEM scenario + if (isForcedOEM) { + // User should be on heritage problem glass questions page after selecting Allstate on insurance company page + let HeritageProblemGlassQuestionsPage = testCase.pages.heritageProblemGlassQuestionsPage; + await HeritageProblemGlassQuestionsPage.handleHeritageProblemGlassQuestionsPage(testCase.testData); + } + // Handle ccPolicyInfoPage let ccPolicyInfoPage = testCase.pages.ccPolicyInfoPage; await ccPolicyInfoPage.handleCCPolicyInfoPage(testCase.testData); diff --git a/playwright-tests/tests/CashReplaceDynamicRecalMobile.ts b/playwright-tests/tests/CashReplaceDynamicRecalMobile.ts index 7b16f582b..5705a95a3 100644 --- a/playwright-tests/tests/CashReplaceDynamicRecalMobile.ts +++ b/playwright-tests/tests/CashReplaceDynamicRecalMobile.ts @@ -1,6 +1,6 @@ //Imports here import { ITestData } from 'framework/TestData' -import { ServiceLocation, PaymentType } from 'safelite-playwright-core'; +import { ServiceLocation, ServicePackage, PaymentType } from 'safelite-playwright-core'; import { ITestCase } from '../framework/Typedefs' import { VehicleLookupType } from 'safelite-playwright-core'; import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core'; @@ -29,7 +29,8 @@ const cashReplaceDynamicRecalMobileData: Partial = { postalCode: '21237' } }, - + + servicePackage: ServicePackage.Premium, // Override vehicle details vehicleDetails: { ...getDefaultTestData().vehicleDetails!, @@ -59,7 +60,8 @@ const cashReplaceDynamicRecalMobileData: Partial = { // Override payment details paymentDetails: { - paymentType: PaymentType.PayAtService + paymentType: PaymentType.PayAtService, + promoCode: 'digitalrain' } } diff --git a/playwright-tests/tests/CashReplaceMultiSlidingGlassDropoff.ts b/playwright-tests/tests/CashReplaceMultiSlidingGlassDropoff.ts index f488a3555..4042c67de 100644 --- a/playwright-tests/tests/CashReplaceMultiSlidingGlassDropoff.ts +++ b/playwright-tests/tests/CashReplaceMultiSlidingGlassDropoff.ts @@ -35,7 +35,7 @@ const cashReplaceMultiSlidingGlassDropoffData: Partial = { appointmentDetails: { serviceLocation: ServiceLocation.DropOff, appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate, - shopAddress: "6826 sawmill rd, Columbus, OH 43235" + shopAddress: "1343 cameron ave, lewis center, oh 43035" }, // Override vehicle details diff --git a/playwright-tests/tests/InsuranceOEMAllState.ts b/playwright-tests/tests/InsuranceOEMAllState.ts new file mode 100644 index 000000000..c55939a4c --- /dev/null +++ b/playwright-tests/tests/InsuranceOEMAllState.ts @@ -0,0 +1,88 @@ +//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("InsuranceOEMAllstate"); + +// Now get the test data with the seeded faker +const insuranceOEMAllstateData: 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, + isForcedOEM: true, + + // Override customer details with specific name and California location + customerDetails: { + ...getDefaultTestData().customerDetails!, + firstName: 'Jane', + lastName: 'OEMTest', + address: { + ...getDefaultTestData().customerDetails!.address, + city: 'Richmond', + state: 'Virginia', + postalCode: '23219' + } + }, + + + // Insurance claim details + claimDetails: { + client: 'Allstate', + policyNumber: 'MockOEMTest', + policyDeductible: "Unverified", + policyZip: '43085', + 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: '2024', + make: 'Jeep', + model: 'Wrangler', + 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: '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 insuranceOEMAllstateTests: ITestCase[] = []; + +const tc = { + name: `InsuranceOEMAllstate`, + tags: ['@E2E','@InsuranceOEMAllstate', '@test_report', '@Insurance'], + testData: insuranceOEMAllstateData +}; +insuranceOEMAllstateTests.push(tc); + +export default insuranceOEMAllstateTests; \ No newline at end of file diff --git a/playwright-tests/tests/alert-validation/alert0007_VinNotFound.ts b/playwright-tests/tests/alert-validation/alert0007_VinNotFound.ts index d88ceeab6..7c01188cb 100644 --- a/playwright-tests/tests/alert-validation/alert0007_VinNotFound.ts +++ b/playwright-tests/tests/alert-validation/alert0007_VinNotFound.ts @@ -75,10 +75,10 @@ const lookupTypesToTest: LookupTestCase[] = [ }, customerDetails: { address: { - street: '4076 Spectacle Drive', + street: '4219 Turpin Ln', city: 'Columbus', state: 'OH', - postalCode: '59261', + postalCode: '43230', country: 'United States' } }, diff --git a/public/static/error/index.html b/public/static/error/index.html index a4c141f85..53bb9fc08 100644 --- a/public/static/error/index.html +++ b/public/static/error/index.html @@ -114,7 +114,7 @@ body { debug: true, }, { - name: 'Test', + name: 'SysTest', hostName: 'www-test2.safelite.com', apiHostname: 'digitalapi.test.safelite.io', debug: true, @@ -142,83 +142,178 @@ body { return match; } + function getExperimentSettingsFromVuex(vuexData) { + const experiments = vuexData?.applicationUser?.experiments; + + if(experiments) { + return experiments + .filter((e) => !!e.isActive) + .map((e) => e.settings) + .reduce((prev, next) => Object.assign(prev, next), {}) ?? {}; + } + + return {}; + } + + function extractDataFromVuex(existingVuexData) { + let bailoutInfo = []; + let headerInfo = []; + + try { + // =============== Collect diagnostic data: + bailoutInfo = []; + + // App & Site Name + bailoutInfo.push({ name: 'App Name', value: 'FixMyGlass' }); + bailoutInfo.push({ name: 'Site Name', value: 'SafeliteDotcom' }); + + // Not included (yet) from heritage: + // - Site ID + // - Code + // - Module + // - Page + + bailoutInfo.push({ name: 'Timestamp', value: `${new Date()}` }); + + // - URL + // - Server + + const sessionId = getCookieValueByName('sid'); + bailoutInfo.push({ name: 'Session Id', value: sessionId }); + + const userAgent = window.navigator.userAgent; + bailoutInfo.push({ name: 'User Agent', value: userAgent }); + + // - IP + // - Session Log Sequence Number + + bailoutInfo.push({ name: 'Referral Seq Number', value: existingVuexData.order?.referralSequenceNumber }); + bailoutInfo.push({ name: 'Referral Number', value: existingVuexData.order?.referralNumber }); + bailoutInfo.push({ name: 'Referral Date', value: existingVuexData.order?.referralDate }); + bailoutInfo.push({ name: 'Referral Provider Number', value: existingVuexData.order?.serviceLocation?.provider?.providerNumber }); + bailoutInfo.push({ name: 'Referral CTU', value: existingVuexData.order?.serviceLocation?.provider?.address?.zipCodeCtu }); + + // - Referral Insured Zip + // - Referral Insured Service Zip + + bailoutInfo.push({ name: 'Referral CarID', value: existingVuexData.order?.vehicle?.carId }); + + const vehicle = existingVuexData.order?.vehicle; + const vehicleString = vehicle?.year + ? `${vehicle.year} ${vehicle.make} ${vehicle.model} - ${vehicle.style}` + : null; + bailoutInfo.push({ name: 'Referral Vehicle', value: vehicleString }); + bailoutInfo.push({ name: 'Work Order Number', value: existingVuexData.order?.workOrderNumber }); + bailoutInfo.push({ name: 'Work Order ID', value: existingVuexData.order?.workOrderId }); + bailoutInfo.push({ name: 'Parent Account Number', value: existingVuexData.order?.payment?.parentAccountNumber }); + + const environmentData = getCurrentEnvironmentData(); + const userIdCookieName = `FunnelUserId-${environmentData?.name}`; + bailoutInfo.push({ name: 'User Id', value: getCookieValueByName(userIdCookieName) }); + + // - Parent Account Name + // - Client GUID + + // =============== Collect header data: + headerInfo = []; + + headerInfo.push({ name: 'X-Experiment-Data', value: JSON.stringify(getExperimentSettingsFromVuex(existingVuexData)) }); + headerInfo.push({ name: 'X-Application-Name', value: 'FixMyGlass' }); + + const sessionKeyCookieName = `FunnelSessionKey-${environmentData?.name}`; + + headerInfo.push({ name: 'X-Session-Sequence-Number', value: getCookieValueByName(sessionKeyCookieName) }); + headerInfo.push({ name: 'X-Referral-Sequence-Number', value: existingVuexData?.order?.referralSequenceNumber }); + headerInfo.push({ name: 'X-Enterprise-Order-Number', value: existingVuexData?.order?.eon }); + headerInfo.push({ name: 'log-enabled', value: existingVuexData?.applicationUser?.loggingOption ?? false }); + } finally { + return { + headerInfo: headerInfo, + bailoutInfo: bailoutInfo + }; + } + } + + function generateRequestHeader(headerInfo) { + let headers = {}; + + headerInfo.forEach( + keyPair => { + headers[keyPair.name] = keyPair.value; + } + ); + + headers['X-Transaction-Id'] = crypto.randomUUID(); + + headers['Content-Type'] = 'application/json'; + + return headers; + } + + function getFieldWithName(info, name) { + const match = info?.find( + (row) => row.name === name + ); + + return match?.value; + } + + function clearApplicationData() { + window.localStorage.removeItem('vuex'); + + const environmentData = getCurrentEnvironmentData(); + const cookieNames = [ + `FunnelUserId-${environmentData?.name}`, + `FunnelSessionKey-${environmentData?.name}`, + `FunnelSessionInfo-${environmentData?.name}`, + `sid`, + `dxdev`, + ]; + + cookieNames.forEach( + (cookieName) => { + let cookieToAdd = `${cookieName}=undefined; path=/; domain=${environmentData.hostName}; max-age=0`; + document.cookie = cookieToAdd; + } + ) + } + window.onload = async () => { // =============== Check for session info: const existingVuexDataJSON = window.localStorage.getItem('vuex'); const existingVuexData = existingVuexDataJSON ? JSON.parse(existingVuexDataJSON) : null; const existingBailoutInfoJSON = window.sessionStorage.getItem('bailoutInfo'); const existingBailoutInfo = existingBailoutInfoJSON ? JSON.parse(existingBailoutInfoJSON) : null; + const existingHeaderInfoJSON = window.sessionStorage.getItem('headerInfo'); + const existingHeaderInfo = existingHeaderInfoJSON ? JSON.parse(existingHeaderInfoJSON) : null; console.log(`================ VUEX DATA`); console.log(existingVuexData); console.log(`================ PRIOR BAILOUT DATA`); console.log(existingBailoutInfo); + console.log(`================ PRIOR HEADER DATA`); + console.log(existingHeaderInfo); let bailoutInfo = null; + let headerInfo = null; if(existingVuexData) { - try { - // =============== Collect diagnostic data: - bailoutInfo = []; + const results = extractDataFromVuex(existingVuexData); + bailoutInfo = results.bailoutInfo; + headerInfo = results.headerInfo; - // App & Site Name - bailoutInfo.push({ name: 'App Name', value: 'FixMyGlass' }); - bailoutInfo.push({ name: 'Site Name', value: 'SafeliteDotcom' }); + clearApplicationData(); - // Not included (yet) from heritage: - // - Site ID - // - Code - // - Module - // - Page - - bailoutInfo.push({ name: 'Timestamp', value: `${new Date()}` }); - - // - URL - // - Server - - const sessionId = getCookieValueByName('sid'); - bailoutInfo.push({ name: 'Session Id', value: sessionId }); - - const userAgent = window.navigator.userAgent; - bailoutInfo.push({ name: 'User Agent', value: userAgent }); - - // - IP - // - Session Log Sequence Number - - bailoutInfo.push({ name: 'Referral Seq Number', value: existingVuexData.order?.referralSequenceNumber }); - bailoutInfo.push({ name: 'Referral Number', value: existingVuexData.order?.referralNumber }); - bailoutInfo.push({ name: 'Referral Date', value: existingVuexData.order?.referralDate }); - bailoutInfo.push({ name: 'Referral Provider Number', value: existingVuexData.order?.serviceLocation?.provider?.providerNumber }); - bailoutInfo.push({ name: 'Referral CTU', value: existingVuexData.order?.serviceLocation?.provider?.address?.zipCodeCtu }); - - // - Referral Insured Zip - // - Referral Insured Service Zip - - bailoutInfo.push({ name: 'Referral CarID', value: existingVuexData.order?.vehicle?.carId }); - - const vehicle = existingVuexData.order?.vehicle; - const vehicleString = vehicle?.year - ? `${vehicle.year} ${vehicle.make} ${vehicle.model} - ${vehicle.style}` - : null; - bailoutInfo.push({ name: 'Referral Vehicle', value: vehicleString }); - bailoutInfo.push({ name: 'Work Order Number', value: existingVuexData.order?.workOrderNumber }); - bailoutInfo.push({ name: 'Work Order ID', value: existingVuexData.order?.workOrderId }); - bailoutInfo.push({ name: 'Parent Account Number', value: existingVuexData.order?.payment?.parentAccountNumber }); - - // - Parent Account Name - // - Client GUID - } finally { - // If any information gathered, write to session storage. - if(bailoutInfo) { - window.sessionStorage.setItem('bailoutInfo', JSON.stringify(bailoutInfo)); - } - - // Then *always* clear vuex data. - window.localStorage.removeItem('vuex'); + if(bailoutInfo) { + window.sessionStorage.setItem('bailoutInfo', JSON.stringify(bailoutInfo)); } - } else if(existingBailoutInfo) { - // Proceed with prior data. - bailoutInfo = existingBailoutInfo; + + if(headerInfo) { + window.sessionStorage.setItem('headerInfo', JSON.stringify(headerInfo)); + } + } else { + bailoutInfo = existingBailoutInfo ?? []; + headerInfo = existingHeaderInfo ?? []; } const environmentInfo = getCurrentEnvironmentData(); @@ -275,11 +370,11 @@ body { ); const entryString = `User encountered bailout page.\n${new Date()}\n${infoString ?? 'No information recoverable'}`; + const headers = generateRequestHeader(headerInfo); + const request = new Request(endpointUrl, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, + headers: headers, body: JSON.stringify({ entry: entryString, }), @@ -292,6 +387,38 @@ body { console.error(`=== ERROR SENDING LOGGING`); console.error(e); } + + try { + const endpointUrl = `https://${apiHostname}/analytics/api/v1/analytics/log-page-view`; + + const headers = generateRequestHeader(headerInfo); + + const payload = { + AppName: "FixMyGlass", + action: "", + applicationName: "FixMyGlassNextGen", + event: "ENTRY", + experimentsForUser: [], + pageName: "static-error", + parentAccountNumber: getFieldWithName(bailoutInfo, 'Parent Account Number') ?? 0, + referralSequenceNumber: getFieldWithName(bailoutInfo, 'Referral Seq Number'), + sessionId: getFieldWithName(bailoutInfo, 'Session Id'), + sessionKey: getFieldWithName(headerInfo, 'X-Session-Sequence-Number'), + shouldUseSessionId: false, + userId: getFieldWithName(bailoutInfo, 'User Id'), + }; + + const request = new Request(endpointUrl, { + method: 'POST', + headers: headers, + body: JSON.stringify(payload), + }); + + const response = await fetch(request); + } catch(e) { + console.error(`=== ERROR SENDING PAGEVIEW`); + console.error(e); + } } }; diff --git a/public/static/healthcheck/index.html b/public/static/healthcheck/index.html index 9e20bde78..3f87a7587 100644 --- a/public/static/healthcheck/index.html +++ b/public/static/healthcheck/index.html @@ -509,7 +509,7 @@ 'AccountService': { baseUrl: apiUrl + '/account/api/v1', endpoints: [ - //{ path: '/healthcheck', method: 'GET' }, + { path: '/healthcheck', method: 'GET' }, { path: '/account/insurance-companies', method: 'GET' }, { path: '/account/account-details/373341', method: 'GET' }, { path: '/account/bill-to-account-number?ParentAccountNumber=373341&ProviderNumber=01820&IsItac=false&TypeOfClaim=Personal&LineOfBusiness=Commercial', method: 'GET' } @@ -518,7 +518,7 @@ 'LocationService': { baseUrl: apiUrl + '/location/api/v1', endpoints: [ - //{ path: '/healthcheck', method: 'GET' }, + { path: '/healthcheck', method: 'GET' }, { path: '/location/zip/43015/Replace', method: 'GET' }, { path: '/location/alert-reasons/03357', method: 'GET' }, { path: '/location/providers/43015/Replace/100/167132/true/CR00046193/false', method: 'GET' }, @@ -528,7 +528,7 @@ 'VehicleService': { baseUrl: apiUrl + '/vehicle/api/v1', endpoints: [ - //{ path: '/healthcheck', method: 'GET' }, + { path: '/healthcheck', method: 'GET' }, { path: '/vehicle/years', method: 'GET' }, { path: '/vehicle/makes/1997', method: 'GET' }, { path: '/vehicle/Models/1997/Toyota', method: 'GET' }, @@ -539,13 +539,12 @@ 'PartService': { baseUrl: apiUrl + '/parts/api/v1', endpoints: [ - //{ path: '/healthcheck', method: 'GET' }, + { path: '/healthcheck', method: 'GET' }, { path: '/parts/damage-options/CR00046193', method: 'GET' }, { path: '/parts/rain-repel', method: 'GET' }, { path: '/parts/parts-or-questions', method: 'POST', body: {"carId":"CR00046193","glassPieces":[{"location":"Windshield","name":"Single"}],"zip":"43015","vin":null,"serviceType":null,"referralSeqNumber":environmentContext.referralNumber,"AppName":"FixMyGlass"} }, { path: '/parts/wipers/CR00046193/43015', method: 'GET' }, { path: '/parts/mobile-premium-fee/Cash/Replace', method: 'GET' }, - { path: '/parts/service-package-discount', method: 'POST', body: {"glassPieces":[{"location":"Windshield","name":"Single"}],"damageType":"Replace","AppName":"FixMyGlass"} }, { path: '/parts/mobile-fee/?serviceType=Install&facilityType=Mobile&parentAccountNumber=167132&billToAccountNumber=87291&providerNumber=03357&isItacOptimized=false&zipCode=43015', method: 'GET' }, ] }, @@ -553,7 +552,7 @@ 'PriceService': { baseUrl: apiUrl + '/price/api/v1', endpoints: [ - //{ path: '/healthcheck', method: 'GET' }, + { path: '/healthcheck', method: 'GET' }, { path: '/price/taxed-order-items?ParentAccountNumber=167132&BillToAccountNumber=87291&ProviderNumber=003357&AppointmentType=Mobile&ServiceLocation.City=asdf&ServiceLocation.State=OH&ServiceLocation.ZipCode=43015&lineItems[0].partNumber=FW02018GBNN&lineItems[0].laborAmount=60&lineItems[0].kitPrice=0&lineItems[0].sellingPrice=297.33&lineItems[1].partNumber=GGG%202018&lineItems[1].laborAmount=0&lineItems[1].kitPrice=0&lineItems[1].sellingPrice=13.4&lineItems[2].partNumber=RECYCLE%20FEE&lineItems[2].laborAmount=39.99&lineItems[2].kitPrice=0&lineItems[2].sellingPrice=0&ServerData=' + encodeURIComponent(environmentContext.serverData), method: 'GET' }, { path: '/price/order-items', method: 'POST', body: { "parentAccountNumber": 167132, "billToAccountNumber": "87291", "providerNumber": "03357", "carId": "CR00046193", "year": 1997, "make": "Toyota", "model": "Camry", "eon": environmentContext.eon, "state": "OH", "referralNumber": environmentContext.referralNumber, "referralSequenceNumber": environmentContext.referralNumber, "zipCode": "43015", "serviceZipCode": "43015", "referralDate": "2025-10-29T10:55:27.18", "isReplacement": true, "deductible": 0, "coverageStatus": null, "lineItems": [ { "partNumber": "RECYCLE FEE" }, { "partNumber": "FW02018GBNN" }, { "partNumber": "GGG 2018" }, { "partNumber": "SBB19" }, { "partNumber": "SBB21" } ], "serverData": "", "AppName": "FixMyGlass" } }, { path: '/price/order-promo', method: 'POST', body: {"promoCode":"WEBSL45","addableVaps":[{"partNumber":"RAIN REPEL","description":null,"partType":"RAIN REPEL","isInsurable":null,"id":"ccb0865d-1c0a-4be7-8605-bc7b9cb3d221","isChildPart":false,"laborAmount":0,"sellingPrice":44.99,"kitPrice":0,"salesTax":null},{"partNumber":"SBB19","description":"SAFELITE BEAM BLADE 19","partType":"FRONT WIPER","isInsurable":null,"id":"4db19a8b-739f-4f7b-983e-f6308c5f5901","isChildPart":false,"laborAmount":0,"sellingPrice":34.99,"kitPrice":0,"salesTax":null},{"partNumber":"SBB21","description":"SAFELITE BEAM BLADE 21","partType":"FRONT WIPER","isInsurable":null,"id":"e27ae20a-3a60-4fdf-a738-4b56d2e761cb","isChildPart":false,"laborAmount":0,"sellingPrice":34.99,"kitPrice":0,"salesTax":null}],"order":{"appointmentType":"Mobile","carId":"CR00046193","correlationId":"b0261e48-1111-44fc-abc9-34b41380687c","eon":environmentContext.eon,"isInsurance":false,"isRepair":false,"glassToReplace":[{"location":"Windshield","name":"Single"}],"lineItemsOnOrder":[{"partNumber":"FW02018GBNN","basePartNumber":"FW02018","description":"solar","color":"Green Tint, Blue Shade","partType":"WINDSHIELD","canSafeliteRecalibrate":false,"requiresRecalibration":false,"requiresCapabilityQuestions":false,"recalibrationType":null,"childParts":[{"partNumber":"GGG 2018","safelitePartNumber":"GGG 2018","id":"329cf0ec-8106-4e62-ab66-de0da8f18a1d","isChildPart":true,"laborAmount":0,"sellingPrice":13.4,"kitPrice":0,"salesTax":0.94}],"kitPrice":0,"sellingPrice":368.94,"laborAmount":60,"id":"4326cee6-b39e-41ff-bb29-df9220e32298","isChildPart":false,"salesTax":30.02},{"partNumber":"GGG 2018","safelitePartNumber":"GGG 2018","id":"329cf0ec-8106-4e62-ab66-de0da8f18a1d","isChildPart":true,"laborAmount":0,"sellingPrice":13.4,"kitPrice":0,"salesTax":0.94},{"partNumber":"RECYCLE FEE","description":null,"partType":"REPLACE FEE","isInsurable":null,"isChildPart":false,"laborAmount":39.99,"sellingPrice":0,"kitPrice":0,"salesTax":2.8,"id":"3771aed5-0de1-4f0e-b1ad-394f73e08f38","cartItemType":"REPLACE FEE"},{"partNumber":"MOBILE FEE","description":null,"partType":"MOBILE FEE","isInsurable":true,"isChildPart":false,"laborAmount":0,"sellingPrice":0,"kitPrice":0,"salesTax":0,"id":"2bc936ec-ef5e-4fe2-95fb-e946792b1ae5"},{"partNumber":"SBB19","description":"SAFELITE BEAM BLADE 19","partType":"FRONT WIPER","isInsurable":null,"id":"4db19a8b-739f-4f7b-983e-f6308c5f5901","isChildPart":false,"laborAmount":0,"sellingPrice":34.99,"kitPrice":0,"salesTax":2.45,"cartItemType":"FRONT WIPERS"},{"partNumber":"SBB21","description":"SAFELITE BEAM BLADE 21","partType":"FRONT WIPER","isInsurable":null,"id":"e27ae20a-3a60-4fdf-a738-4b56d2e761cb","isChildPart":false,"laborAmount":0,"sellingPrice":34.99,"kitPrice":0,"salesTax":2.45,"cartItemType":"FRONT WIPERS"},{"partNumber":"RAIN REPEL","description":null,"partType":"RAIN REPEL","isInsurable":null,"id":"ccb0865d-1c0a-4be7-8605-bc7b9cb3d221","isChildPart":false,"laborAmount":0,"sellingPrice":44.99,"kitPrice":0,"salesTax":3.15,"cartItemType":"RAIN REPEL"}],"parentAccountNumber":"167132","referralSequenceNumber":environmentContext.referralNumber,"serviceState":"OH","serverData":environmentContext.serverData,"vehicleYear":"1997","zipCodeOrProviderCtu":"03357"},"AppName":"FixMyGlass"} }, @@ -563,7 +562,7 @@ 'ScheduleService': { baseUrl: apiUrl + '/schedule/api/v1', endpoints: [ - //{ path: '/healthcheck', method: 'GET' }, + { path: '/healthcheck', method: 'GET' }, { path: '/schedule/mobile-time-slots', method: 'POST', body: {"startDate":startDate,"endDate":endDate,"applicationName":"FixMyGlass","parentAccountNumber":"167132","carId":"CR00046193","lineItems":[{"partNumber":"RECYCLE FEE","partType":"REPLACE FEE","isGlassPart":false},{"partNumber":"SBB19","partType":"FRONT WIPER","isGlassPart":false},{"partNumber":"SBB21","partType":"FRONT WIPER","isGlassPart":false},{"partNumber":"RAIN REPEL","partType":"RAIN REPEL","isGlassPart":false},{"partNumber":"FW02018GBNN","partType":"WINDSHIELD","isGlassPart":true},{"partNumber":"GGG 2018","isGlassPart":false}],"glassPieces":[{"location":"Windshield","name":"Single"}],"eon":environmentContext.eon,"billToAccountNumber":"87291","coverage":{"status":null,"deductible":0,"additionalAuthFlag":null},"partSelection":{"hasAnsweredPartQuestions":false,"hasAnsweredMoldingQuestions":false,"hasAnsweredCapabilityQuestions":false,"hasManuallySelectedParts":false},"vehicle":{"year":1997,"make":"Toyota","model":"Camry","style":"4 door sedan","vin":""},"zipCode":"43015","AppName":"FixMyGlass"} }, { path: '/schedule/shop-time-slots', method: 'POST', body: {"providerNumber":"003417","startDate":startDate,"endDate":endDate,"shopAppointmentType":"InshopOrDropoff","applicationName":"FixMyGlass","parentAccountNumber":"167132","carId":"CR00046193","lineItems":[{"partNumber":"RECYCLE FEE","partType":"REPLACE FEE","isGlassPart":false},{"partNumber":"FW02018GBNN","partType":"WINDSHIELD","isGlassPart":true},{"partNumber":"GGG 2018","isGlassPart":false}],"glassPieces":[{"location":"Windshield","name":"Single"}],"eon":environmentContext.eon,"billToAccountNumber":"87291","coverage":{"status":null,"deductible":0,"additionalAuthFlag":null},"partSelection":{"hasAnsweredPartQuestions":false,"hasAnsweredMoldingQuestions":false,"hasAnsweredCapabilityQuestions":false,"hasManuallySelectedParts":false},"vehicle":{"year":1997,"make":"Toyota","model":"Camry","style":"4 door sedan","vin":""},"AppName":"FixMyGlass"} } ] @@ -571,25 +570,25 @@ 'AnalyticsService': { baseUrl: apiUrl + '/analytics/api/v1', endpoints: [ - //{ path: '/healthcheck', method: 'GET' }, + { path: '/healthcheck', method: 'GET' }, ] }, 'ExperimentService': { baseUrl: apiUrl + '/experiments/api/v1', endpoints: [ - //{ path: '/healthcheck', method: 'GET' }, + { path: '/healthcheck', method: 'GET' }, ] }, 'OrderService': { baseUrl: apiUrl + '/order/api/v1', endpoints: [ - //{ path: '/healthcheck', method: 'GET' }, + { path: '/healthcheck', method: 'GET' }, ] }, 'ContentService': { baseUrl: apiUrl + '/content/api/v1', endpoints: [ - //{ path: '/healthcheck', method: 'GET' }, + { path: '/healthcheck', method: 'GET' }, { path: '/content/fmg/vehicle', method: 'GET' }, ] } @@ -674,7 +673,7 @@ } function updateAtAGlanceText() { - const healthyCount = Object.values(healthData).filter(service => service.status === 'healthy').length; + const healthyCount = Object.values(healthData).filter(service => (service.status === 'healthy' || service.status === 'degraded')).length; const totalCount = Object.values(healthData).length; let text = 'Healthy'; let className = 'stat-pill stat-pill-healthy'; @@ -763,14 +762,15 @@ // Determine overall service status const endpoints = healthData[serviceName].endpoints; const healthyCount = endpoints.filter(ep => !isDegraded(ep) && !isUnhealthy(ep)).length; + const unhealthyCount = endpoints.filter(ep => isUnhealthy(ep)).length; const totalCount = endpoints.length; if (healthyCount === totalCount) { healthData[serviceName].status = 'healthy'; - } else if (healthyCount > 0) { - healthData[serviceName].status = 'degraded'; - } else { + } else if (unhealthyCount > 0) { healthData[serviceName].status = 'unhealthy'; + } else { + healthData[serviceName].status = 'degraded'; } } diff --git a/src/constants/analytics.js b/src/constants/analytics.js index fe43be06d..e156de11b 100644 --- a/src/constants/analytics.js +++ b/src/constants/analytics.js @@ -14,6 +14,8 @@ const GaCategories = { EVOX: "Evox", APPOINTMENT: "Appointment", SERVICE_LOCATION: "service-location", + CONFIRMATION_CLICKED: "confirmation clicked", + RECAL_DISCLAIMER: "Recalibration disclaimer", }; const GaActions = { @@ -26,6 +28,9 @@ const GaActions = { MOBILE_AVAILABLE: "mobile_available", SHOPS_FIRST_DISPLAYED: "shops_first_displayed", MORE_LOCATIONS_CLICKED: "more_locations_clicked", + YES: "yes", + NO: "no", + ACKNOWLEDGED: "Acknowledged", }; const GaLabels = { diff --git a/src/constants/application-config.js b/src/constants/application-config.js index 93a6f70d8..91873d455 100644 --- a/src/constants/application-config.js +++ b/src/constants/application-config.js @@ -22,6 +22,7 @@ const applicationConfig = { PIA_ERROR_URL: location.protocol + "//" + location.host + "/fmg/payment-pia-return?src=concept-funnel", CONFIRMATION_URL: location.protocol + "//" + location.host + "/fmg/confirmation", + PIA_ADYEN_RETURN_URL: location.protocol + "//" + location.host + "/fmg/virtual/payment/return", GOOGLE_CALENDAR: "https://www.google.com/calendar/render?action=TEMPLATE", YAHOO_CALENDAR: "https://calendar.yahoo.com/?v=60", OUTLOOK_CALENDAR: @@ -31,6 +32,8 @@ const applicationConfig = { COOKIE_NAME_LENGTH_MAX_LIMIT: 44, SESSION_TIMEOUT_MINUTES: process.env.__VUE_APP_SESSION_TIMEOUT_MINUTES__, RETURN_USER_PAGE: "/fmg/return-user", + ADYEN_CLIENT_KEY: process.env.__VUE_APP_ADYEN_CLIENT_KEY__, + ADYEN_ENVIRONMENT: process.env.__VUE_APP_ADYEN_ENVIRONMENT__, }; export { applicationConfig }; diff --git a/src/constants/cart-item-categories.js b/src/constants/cart-item-categories.js index 6bbb960b9..5c8faafcf 100644 --- a/src/constants/cart-item-categories.js +++ b/src/constants/cart-item-categories.js @@ -3,7 +3,7 @@ const cartItemCategories = { GLASS_PARTS: "glassParts", SUPPORTING_ITEMS: "supportingItems", PROMOS: "promos", - SERVICE_PACKAGE_DISCOUNT: "servicePackageDiscount", + QUOTE_PAGE_DISCOUNT: "quotePageDiscount", }; export { cartItemCategories }; diff --git a/src/constants/cart-item-types.js b/src/constants/cart-item-types.js index 0bb418d43..8f70aeaed 100644 --- a/src/constants/cart-item-types.js +++ b/src/constants/cart-item-types.js @@ -10,7 +10,7 @@ const cartItemTypes = { PROMOS: "PROMOS", EARLY_BIRD: "EARLY BIRD", SUPPLIES_REPAIR: "SUPPLIES-REPAIR", - SERVICE_PACKAGE_DISCOUNT: "SERVICE PACKAGE DISCOUNT", + QUOTE_PAGE_DISCOUNT: "QUOTE PAGE DISCOUNT", DONATION: "DONATION", }; diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index 17a8f9a08..d25fce6a3 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -76,10 +76,6 @@ const endpoints = { url: "/parts/api/v1/parts/pricing-by-day-part-number", method: "GET", }, - GetServicePackageDiscountPart: { - url: "/parts/api/v1/parts/service-package-discount", - method: "POST", - }, GetServiceabilityDetails: { url: "/location/api/v1/location/serviceability-details", method: "GET", @@ -124,6 +120,14 @@ const endpoints = { url: "/order/api/v1/order/save-session/fmg", method: "POST", }, + SubmitOrder: { + url: "/order/api/v1/order/submit-order", + method: "POST", + }, + SubmitOrderForPia: { + url: "/order/api/v1/order/submit-order-for-pia", + method: "POST", + }, LoadSession: { url: "/order/api/v1/order/load-session", method: "POST", @@ -154,7 +158,7 @@ const endpoints = { }, TaxOrderItems: { url: "/price/api/v1/price/taxed-order-items", - method: "GET", + method: "POST", }, LogExperimentExposureIfAssigned: { url: "/experiments/api/v1/experiments/log-exposure", @@ -208,6 +212,18 @@ const endpoints = { url: "/order/api/v1/order/add-donation-part", method: "POST", }, + GetOemAfterMarketParts: { + url: "/parts/api/v1/parts/oem-after-market-parts", + method: "POST", + }, + InitializeAdyenPayment: { + url: "/payment/api/v1/payment/payment/session/initialize-session", + method: "POST", + }, + GetAdyenSessionResult: { + url: "/payment/api/v1/payment/payment/session/session-result", + method: "POST", + }, }; export { endpoints }; diff --git a/src/constants/experiments.js b/src/constants/experiments.js index f358fc89f..1d689d727 100644 --- a/src/constants/experiments.js +++ b/src/constants/experiments.js @@ -12,6 +12,9 @@ const experimentSettings = { SUPPRESS_VIN_CAPTURE: "SuppressVinCapture", DISPLAY_AVAILABILITY_INDICATORS: "DisplayAvailabilityIndicators", PIA_EXPERIENCE: "PIA Experience", + CONDITIONAL_PROVIDER_NUMBERS: "ConditionalProviderNumbers", + CONDITIONAL_PROVIDER_NUMBERS_2: "ConditionalProviderNumbers2", + CONDITIONAL_PROVIDER_SHUTOFF_DATE: "ConditionalProviderShutoffDate", PIA_INSURANCE: "DisplayPIAInsurance", SUBMIT_ORDER_ENABLE_PIA: "SubmitOrder_Enable_PIA", IS_EMAIL_OPTIONAL: "isEmailOptional", @@ -33,6 +36,8 @@ const experimentSettings = { SHOW_PM_MOBILE_DAYS: "Show_PMmobileDays", SHOW_NO_PM_MOBILE_DAYS: "Show_NoPMmobileDays", SHOW_NO_MOBILE_AVAILABLE_DAYS: "Show_NoMobileAvailableDays", + OFFER_OEM: "OfferOem", + USE_ADYEN_PAYMENT: "UseAdyenPayment", }; const experimentTriggers = { diff --git a/src/constants/part-type-strings.js b/src/constants/part-type-strings.js index 741132e28..0118c0b2c 100644 --- a/src/constants/part-type-strings.js +++ b/src/constants/part-type-strings.js @@ -10,7 +10,9 @@ const partTypeStrings = { REPAIR_FEE: "REPAIR FEE", EARLY_BIRD: "EARLY BIRD", SERVICE_PACKAGE_DISCOUNT: "SERVICE PACKAGE DISCOUNT", + QUOTE_PAGE_DISCOUNT: "QUOTE PAGE DISCOUNT", DONATION: "DONATION", + WINDSHIELD: "WINDSHIELD", }; export { partTypeStrings }; diff --git a/src/constants/progress-bar-mapper.js b/src/constants/progress-bar-mapper.js index a6e41d31b..a57774c21 100644 --- a/src/constants/progress-bar-mapper.js +++ b/src/constants/progress-bar-mapper.js @@ -80,6 +80,10 @@ export const pageProgressMapper = { percent: 98.5, step: 4, }, + "payment-adyen": { + percent: 98.5, + step: 4, + }, confirmation: { percent: 100, step: 4, diff --git a/src/constants/quote-page-discounts.js b/src/constants/quote-page-discounts.js new file mode 100644 index 000000000..2dd32d915 --- /dev/null +++ b/src/constants/quote-page-discounts.js @@ -0,0 +1,80 @@ +import { packageNames } from "./package-names"; +import { partTypeStrings } from "./part-type-strings"; + +export const quotePageDiscountTable = [ + // Repair + { + name: "Repair Glass Only", + experimentCode: "Show_GlassOnly_Repair_Discount", + partNumber: "GL RPR CSH PKG", + partType: partTypeStrings.QUOTE_PAGE_DISCOUNT, + description: "GLASS RPR GEOTARGET DISC", + packageLevel: packageNames.TIER_ONE, + }, + { + name: "Repair Standard", + experimentCode: "Show_Standard_Repair_Discount", + partNumber: "STD RPR CSH PKG", + partType: partTypeStrings.QUOTE_PAGE_DISCOUNT, + description: "STD RPR GEOTARGET DISC", + packageLevel: packageNames.TIER_TWO, + }, + { + name: "Repair Premium", + experimentCode: "Show_Premium_Repair_Discount", + partNumber: "PRM RPR CSH PKG", + partType: partTypeStrings.QUOTE_PAGE_DISCOUNT, + description: "PREM RPR GEOTARGET DISC", + packageLevel: packageNames.TIER_THREE, + }, + // Replace - No recal + { + name: "Replace Glass Only", + experimentCode: "Show_GlassOnly_Replace_Discount", + partNumber: "GL RPL CSH PKG", + partType: partTypeStrings.QUOTE_PAGE_DISCOUNT, + description: "GLASS RPL GEOTARGET DISC", + packageLevel: packageNames.TIER_ONE, + }, + { + name: "Replace Standard", + experimentCode: "Show_Standard_Replace_Discount", + partNumber: "STD RPL CSH PKG", + partType: partTypeStrings.QUOTE_PAGE_DISCOUNT, + description: "STD RPL GEOTARGET DISC", + packageLevel: packageNames.TIER_TWO, + }, + { + name: "Replace Premium", + experimentCode: "Show_Premium_Replace_Discount", + partNumber: "PRM RPL CSH PKG", + partType: partTypeStrings.QUOTE_PAGE_DISCOUNT, + description: "PREM RPL GEOTARGET DISC", + packageLevel: packageNames.TIER_THREE, + }, + // Replace - with recal + { + name: "Recal Glass Only", + experimentCode: "Show_GlassOnly_Recal_Discount", + partNumber: "GL RCL CSH PKG", + partType: partTypeStrings.QUOTE_PAGE_DISCOUNT, + description: "GLASS RCL GEOTARGET DISC", + packageLevel: packageNames.TIER_ONE, + }, + { + name: "Recal Standard", + experimentCode: "Show_Standard_Recal_Discount", + partNumber: "STD RCL CSH PKG", + partType: partTypeStrings.QUOTE_PAGE_DISCOUNT, + description: "STD RCL GEOTARGET DISC", + packageLevel: packageNames.TIER_TWO, + }, + { + name: "Recal Premium", + experimentCode: "Show_Premium_Recal_Discount", + partNumber: "PRM RCL CSH PKG", + partType: partTypeStrings.QUOTE_PAGE_DISCOUNT, + description: "PREM RCL GEOTARGET DISC", + packageLevel: packageNames.TIER_THREE, + }, +]; diff --git a/src/constants/schedule-constants.js b/src/constants/schedule-constants.js index 2b626df18..2b6171075 100644 --- a/src/constants/schedule-constants.js +++ b/src/constants/schedule-constants.js @@ -11,6 +11,8 @@ const PREMIUM_FEE_PART_TYPE = "EARLY BIRD"; const PRICING_BY_DAY_PART_TYPE = "PREMAPPT GEN"; +const RECAL_ACK_YES = "Yes"; + const RouteCodeFlags = { ALL_DAY_DROP_OFF: "ALL DAY DROP OFF", OVERNIGHT_DROP_OFF: "OVERNIGHT DROP OFF", @@ -31,4 +33,5 @@ export { PRICING_BY_DAY_PART_TYPE, RouteCodeFlags, cmsWidgetFieldMappings, + RECAL_ACK_YES, }; diff --git a/src/constants/store-actions.js b/src/constants/store-actions.js index 513891ea1..3afd4df24 100644 --- a/src/constants/store-actions.js +++ b/src/constants/store-actions.js @@ -23,6 +23,7 @@ const storeActions = { GET_ALERT_REASONS_BY_CTU: "getAlertReasonsByCtu", GET_PARTS_OR_QUESTIONS: "getPartsOrQuestions", GET_PARTS: "getParts", + GET_OEM_AFTERMARKET_PARTS: "getOemAfterMarketParts", GET_WIPERS: "getWipers", GET_RAIN_REPEL: "getRainRepel", GET_SUPPORTING_ITEMS: "getSupportingItems", @@ -32,7 +33,6 @@ const storeActions = { GET_MOLDING_QUESTIONS: "getMoldingQuestions", GET_MOBILE_FEE_PART: "getMobileFeePart", GET_PRICING_BY_DAY_PART: "getPricingByDayPart", - GET_SERVICE_PACKAGE_DISCOUNT_PART: "getServicePackageDiscountPart", GET_SERVICEABILITY_DETAILS: "getServiceabilityDetails", GET_SHOP_TIME_SLOTS: "getShopTimeSlots", GET_MOBILE_TIME_SLOTS: "getMobileTimeSlots", @@ -111,6 +111,8 @@ const storeActions = { SAVE_PAYPAL_TOKEN: "savePaypalToken", SAVE_NEXTGEN_SETTLED_AMOUNT: "saveNextGenSettledAmount", SAVE_IS_RECAL_ACK_OPT_IN: "saveIsRecalAckOptIn", + SAVE_IS_RECAL_ACKNOWLEDGED_FOR_SCHEDULING: "saveIsRecalAcknowledgedForScheduling", + SAVE_IS_OEM_GLASS_SELECTED: "saveIsOemGlassSelected", SAVE_IS_MSR_FEE_APPLICABLE: "saveIsMSRFeeApplicable", CREATE_SUBMITTED_STATE: "createSubmittedState", diff --git a/src/constants/store-mutations.js b/src/constants/store-mutations.js index 2112c789c..700f527c2 100644 --- a/src/constants/store-mutations.js +++ b/src/constants/store-mutations.js @@ -69,6 +69,8 @@ const storeMutations = { LOCK_TOKEN: "updateLockToken", UPDATE_SETTLED_TENDER_AMOUNT: "updateSettledTenderAmount", UPDATE_IS_RECAL_ACK_OPT_IN: "updateIsRecalAckOptIn", + UPDATE_IS_RECAL_ACKNOWLEDGED_FOR_SCHEDULING: "updateIsRecalAcknowledgedForScheduling", + UPDATE_IS_OEM_GLASS_SELECTED: "updateIsOemGlassSelected", UPDATE_IS_MSR_FEE_APPLICABLE: "updateIsMSRFeeApplicable", UPDATE_CASH_PRICE_SUBTOTAL: "updateCashPriceSubTotal", diff --git a/src/digital-components/date-picker/date-picker.vue b/src/digital-components/date-picker/date-picker.vue index d586ff839..f48b1f43b 100644 --- a/src/digital-components/date-picker/date-picker.vue +++ b/src/digital-components/date-picker/date-picker.vue @@ -466,7 +466,7 @@ export default { const response = config.getSelectableDatesCallback({ startDateString: initialViewStartDate, endDateString: initialViewEndDate, - providerNumber: config.providerNumber, + inshopProviderNumber: config.inshopProviderNumber, zipCode: config.zipCode, includeMobileTimeSlots: config.includeMobileTimeSlots, includeInshopTimeSlots: config.includeInshopTimeSlots, diff --git a/src/digital-components/modal/modal.vue b/src/digital-components/modal/modal.vue index 867e73c75..9f2be7764 100644 --- a/src/digital-components/modal/modal.vue +++ b/src/digital-components/modal/modal.vue @@ -33,6 +33,7 @@