diff --git a/Dockerfile.playwright b/Dockerfile.playwright index 46a2114a2..bd66363f0 100644 --- a/Dockerfile.playwright +++ b/Dockerfile.playwright @@ -14,8 +14,5 @@ RUN npm install # Install Playwright browsers RUN npx playwright install chromium --with-deps -# Install jq -RUN apt-get install -y jq - # Copy the rest of the application code COPY . . diff --git a/azure-pipelines-automated-testing.yml b/azure-pipelines-automated-testing.yml index 746906a04..2d9c19d74 100644 --- a/azure-pipelines-automated-testing.yml +++ b/azure-pipelines-automated-testing.yml @@ -44,6 +44,28 @@ stages: shardNumber: 4 steps: + - script: | + echo "Starting Docker cleanup to free space..." + + # Remove all stopped containers + echo "Removing stopped containers..." + docker container prune -f + + # Remove dangling images (untagged) + echo "Removing dangling images..." + docker image prune -f + + # Remove unused volumes + echo "Removing unused volumes..." + docker volume prune -f + + # Show disk usage after cleanup + echo "Docker system usage after cleanup:" + docker system df + + echo "Docker cleanup completed!" + displayName: "Docker Cleanup" + - task: Docker@2 displayName: "Build Docker Image" inputs: diff --git a/playwright-tests/business-logic/constants/DefaultTestData.ts b/playwright-tests/business-logic/constants/DefaultTestData.ts index 197297b4b..a5929ce76 100644 --- a/playwright-tests/business-logic/constants/DefaultTestData.ts +++ b/playwright-tests/business-logic/constants/DefaultTestData.ts @@ -97,7 +97,6 @@ export function getDefaultTestData(): Partial { isPolicyDriver: false, isPolicyFound: false, isUseVehicleOnPolicy: true, - isRecalNotification: false, hasOemEndorsement: false, skipEstimatePage: false, isRecalVehicle: false, diff --git a/playwright-tests/business-logic/types/CustomerDetails.ts b/playwright-tests/business-logic/types/CustomerDetails.ts index 3ac2747a0..3c1f9841d 100644 --- a/playwright-tests/business-logic/types/CustomerDetails.ts +++ b/playwright-tests/business-logic/types/CustomerDetails.ts @@ -61,7 +61,7 @@ export interface IPaymentDetails { export interface IClaimDetails { client: string, policyNumber: string, - policyDeductible: number, + policyDeductible: any, policyZip?: string damageDate: string, damageCause: DamageCause diff --git a/playwright-tests/business-logic/types/IAlertFlags.ts b/playwright-tests/business-logic/types/IAlertFlags.ts index b13f30bbc..7943ed6be 100644 --- a/playwright-tests/business-logic/types/IAlertFlags.ts +++ b/playwright-tests/business-logic/types/IAlertFlags.ts @@ -1,5 +1,5 @@ export default interface IAlertFlags { - isHeavyTruckVehicle?: boolean, + isHeavyTruckVehicleAlert?: boolean, isRepairReplace?: boolean, isSplitWindshield?: boolean, isRepairOnly?: boolean, diff --git a/playwright-tests/business-logic/types/ITestData.ts b/playwright-tests/business-logic/types/ITestData.ts index 76d0e4534..61ed18128 100644 --- a/playwright-tests/business-logic/types/ITestData.ts +++ b/playwright-tests/business-logic/types/ITestData.ts @@ -25,12 +25,14 @@ export interface ITestData { isPolicyFound: boolean, otherVehiclesOnPolicy: IVehicleDetails[], // IF defined, we validate that the vehicles are present. isUseVehicleOnPolicy: boolean, // Should we use the vehicle on the policy? - isRecalNotification: boolean, // Does Recalibration Information page show up? endorsements: IEndorsementDetails[], hasOemEndorsement: boolean, // OEM Endorsement does not appear on endorsements page, so it has a separate flag.s skipEstimatePage: boolean, + isHeavyTruck: boolean, isRecalVehicle: boolean, canNotRecal: boolean, dynamicRecal: boolean, - promoCode: string + promoCode: string, + policyZip: string, + isPolicyUnverified: boolean } \ No newline at end of file diff --git a/playwright-tests/impl/reporter/JiraWritebackReporter.ts b/playwright-tests/impl/reporter/JiraWritebackReporter.ts index 6f228be00..62b2bdebd 100644 --- a/playwright-tests/impl/reporter/JiraWritebackReporter.ts +++ b/playwright-tests/impl/reporter/JiraWritebackReporter.ts @@ -23,7 +23,7 @@ const reportConfig: OrtoniReportConfig = { logo: 'playwright-tests/business-logic/data/logo.png', title: "Test Report", showProject: false, - projectName: "ISS-Nextgen-Playwright-Report", + projectName: "FMG-Nextgen-Playwright-Report", testType: `E2E- Environment: ${process.env.NODE_ENV} `, preferredTheme: "light", base64Image: true, diff --git a/playwright-tests/pages/BasePage.ts b/playwright-tests/pages/BasePage.ts index fbb71ccb6..0e8c65219 100644 --- a/playwright-tests/pages/BasePage.ts +++ b/playwright-tests/pages/BasePage.ts @@ -140,11 +140,18 @@ export class BasePage { let loaderElements = await this.page.locator('button .loader, .buy-loader, timeout, .modal-loader').all(); return !(await Promise.any(loaderElements.map(el => el.isVisible())).catch(() => false)); }); + + // Take a screenshot of the page before validating the progress bar + await this.page.screenshot({ path: `test-results\\ortoni-data\\progress-bar-${Date.now()}.png`, fullPage: true }); + // Wait until the progress bar element is attached and visible + const actualProgressPercentage = await this.progressBar.evaluate( - (element) => element.style.width || "Not Found" + async (element) => { + await new Promise(resolve => setTimeout(resolve, 200)); + return element.style.width || "Not Found"; + } ); Soft.expect(actualProgressPercentage).toBe(progressPercentage); console.log(`Progress Bar Percentage: Actual - ${actualProgressPercentage} vs Expected - ${progressPercentage}`); } - } \ No newline at end of file diff --git a/playwright-tests/pages/CoverageStatementPage.ts b/playwright-tests/pages/CoverageStatementPage.ts index 7085c6794..b529f21f1 100644 --- a/playwright-tests/pages/CoverageStatementPage.ts +++ b/playwright-tests/pages/CoverageStatementPage.ts @@ -9,7 +9,7 @@ export class CoverageStatementPage extends InsuranceBasePage { readonly scheduleOnlineButton: Locator; readonly cancelMyClaimButton: Locator; readonly deductibleAmount: Locator; - readonly verfiyingCoverageText: Locator; + readonly verifyingCoverageText: Locator; readonly continueButton: Locator; // For ITAC/NoComp url = process.env['BASE_URL']! + '/FixMyGlass/CoverageStatement.aspx'; @@ -19,7 +19,7 @@ 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.continueButton = page.getByRole('button', { name: 'Continue' }); // this.validateURL(this.url); } @@ -46,16 +46,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()) { + // 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 b3a3862a8..489ed1678 100644 --- a/playwright-tests/pages/OrderConfirmationPage.ts +++ b/playwright-tests/pages/OrderConfirmationPage.ts @@ -33,7 +33,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'); @@ -44,7 +44,7 @@ export class OrderConfirmationPage extends BasePage { async validateOrderConfirmationPage(testData: Partial) { // Destructure data we use const { vehicleDetails, customerDetails, servicePackage, promoCode, - isPolicyFound, claimDetails, paymentDetails, isUseVehicleOnPolicy, paymentMethod } = testData; + 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())); @@ -69,7 +69,7 @@ export class OrderConfirmationPage extends BasePage { expect.soft(servicePackageValue).toContain('New wiper blades'); } if (servicePackage === ServicePackage.Premium) { - expect.soft(servicePackageValue).toContain('Rain Defense™'); + expect.soft(servicePackageValue).toContain('Rain Repel Treatment'); } // Promo Code Validation @@ -87,6 +87,7 @@ export class OrderConfirmationPage extends BasePage { const finalAmountDueAmt = Number.parseFloat(finalAmountDueValue!.split('$')[1].replaceAll(',', '')); + expect.soft(subtotalAmt).toBeGreaterThan(0); // expect.soft(deductibleAmt).toEqual(0); @@ -94,36 +95,17 @@ export class OrderConfirmationPage extends BasePage { // 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 11ee47443..c57caaeef 100644 --- a/playwright-tests/pages/PaymentMethodPage.ts +++ b/playwright-tests/pages/PaymentMethodPage.ts @@ -148,7 +148,7 @@ export class PaymentMethodPage extends BasePage { expect.soft(servicePackageValue).toContain('New wiper blades'); } if (servicePackage === ServicePackage.Premium) { - expect.soft(servicePackageValue).toContain('Rain repel treatment'); + expect.soft(servicePackageValue).toContain('Rain Repel Treatment'); } // Promo Code Validation @@ -357,10 +357,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/PaypalPage.ts b/playwright-tests/pages/PaypalPage.ts index 7afda3d5b..4d957eb33 100644 --- a/playwright-tests/pages/PaypalPage.ts +++ b/playwright-tests/pages/PaypalPage.ts @@ -11,6 +11,7 @@ export class PaypalPage extends BasePage { readonly passwordTextBox: Locator; readonly paypalLoginButton: Locator; readonly completePurchaseButton: Locator; + readonly payWithRadioButton: Locator; readonly payButton: Locator; constructor(page: Page) { @@ -23,6 +24,7 @@ export class PaypalPage extends BasePage { this.passwordTextBox = page.getByPlaceholder('Password'); this.paypalLoginButton = page.getByRole('button', { name: 'Log In', exact: true }); this.completePurchaseButton = page.getByTestId('submit-button-initial') + this.payWithRadioButton = page.locator('.py-4').first(); this.payButton = page.getByRole('button', { name: 'Pay $' }); } @@ -42,6 +44,7 @@ export class PaypalPage extends BasePage { await this.usePasswordInsteadButton.click(); await this.passwordTextBox.fill(paymentDetails.password!); await this.paypalLoginButton.click(); + await this.payWithRadioButton.click(); await this.payButton.click(); } } diff --git a/playwright-tests/pages/VehicleSelectionPage.ts b/playwright-tests/pages/VehicleSelectionPage.ts index 38eaf5e63..c2d638d12 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,17 @@ 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 || isSplitWindshield) { await this.checkForAlertMessages(); throw new TestSuccessAlert('Both assertions are met successfully.'); } diff --git a/playwright-tests/pages/forms/AddressForm.ts b/playwright-tests/pages/forms/AddressForm.ts index afa9d9c85..892a56c9b 100644 --- a/playwright-tests/pages/forms/AddressForm.ts +++ b/playwright-tests/pages/forms/AddressForm.ts @@ -43,8 +43,8 @@ export class AddressForm extends BasePage { // await this.forceAddressFormToAppear(); await this.streetAddressTextBox.click(); await this.streetAddressTextBox.pressSequentially(`${customerDetails.address.street}, ${customerDetails.address.city}, ${customerDetails.address.state} ${customerDetails.address.postalCode}`).then( async() => { - await this.streetAddressTextBox.dispatchEvent('keydown', { key: 'ArrowDown' }); - await this.streetAddressTextBox.dispatchEvent('keyup', { key: 'ArrowDown' }); + await this.streetAddressTextBox.dispatchEvent('keydown', { key: 'ArrowLeft' } ); + await this.streetAddressTextBox.dispatchEvent('keyup', { key: 'ArrowLeft' }); }); await waitUntil(async () => { @@ -56,10 +56,20 @@ export class AddressForm extends BasePage { text.includes(customerDetails.address!.state) ); }); - await this.addressSuggestionList.dispatchEvent('mouseover'); - await this.addressSuggestionList.click(); + + await this.page.hover(".pac-container .pac-item"); + await new Promise(resolve => setTimeout(resolve, 1000)); + await this.addressSuggestionList.click(); // Select the first suggestion + //await this.streetAddressTextBox.press('Tab'); // Move focus to ZIP code field // Fill address + await this.stateDrpDwn.waitFor({ state: 'visible', timeout: 5000 }).then(async () => { + const selectedState = await this.stateDrpDwn.inputValue(); + if (selectedState !== customerDetails.address!.state) { + await this.stateDrpDwn.selectOption(customerDetails.address!.state); + } + }) + await this.fillAndValidate(this.streetAddressTextBox, customerDetails.address.street); await this.fillAndValidate(this.zipCodeTextBox, customerDetails.address.postalCode) await this.fillAndValidate(this.cityTextBox, customerDetails.address.city); diff --git a/playwright-tests/playwright.config.ts b/playwright-tests/playwright.config.ts index 7d2133cc4..230ad948d 100644 --- a/playwright-tests/playwright.config.ts +++ b/playwright-tests/playwright.config.ts @@ -64,7 +64,7 @@ export default defineConfig({ ['junit'], ['list'] ], - timeout: 180_000, + timeout: 300_000, /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ diff --git a/playwright-tests/tests/0000__M.test.ts b/playwright-tests/tests/0000__M.test.ts index 0076f61bd..8deb2535b 100644 --- a/playwright-tests/tests/0000__M.test.ts +++ b/playwright-tests/tests/0000__M.test.ts @@ -31,7 +31,10 @@ import cashRepairInShopAfterPayTests from "./CashRepairInShopAfterPay"; import cashRepairInShopPayPalTests from "./CashRepairInShopPayPal"; import cashReplaceMultiSlidingGlassDropoffTests from "./CashReplaceMultiSlidingGlassDropoff"; import cashReplaceMultiGlassMobileTests from "./CashReplaceMultiGlassMobile"; -import cashReplaceSwitchToInsuranceProgressiveNoCompTests from "./CashReplaceSwitchToInsuranceProgressiveNoComp" +import cashReplaceSwitchToInsuranceProgressiveNoCompTests from "./CashReplaceSwitchToInsuranceProgressiveNoComp"; +import insuranceBigTruckVerifiedTests from "./InsuranceBigTruckVerified"; +import insuranceUnverifiedTests from "./InsuranceUnverified"; + /** * Master Test Runner * @@ -68,6 +71,8 @@ const allStandardTests = [ {name: "InsuranceAcuityPaypal", tests: insuranceAcuityPaypalTests}, {name: "InsuranceITAC21stCentury", tests: insuranceITAC21stCenturyTests}, {name: "CashReplaceSwitchToInsuranceProgressiveNoComp", tests: cashReplaceSwitchToInsuranceProgressiveNoCompTests}, + {name: "InsuranceBigTruckVerified", tests: insuranceBigTruckVerifiedTests}, + {name: "InsuranceUnverified", tests: insuranceUnverifiedTests}, // {name: "InsuranceGeico", tests: insuranceGeicoTests}, // {name: "InsuranceITACOptimizedPriceValidationAllState", tests: insuranceITACOptimizedPriceValidationAllStateTests} @@ -297,7 +302,7 @@ async function runWorkflow(page: Page, testCase: TestCase) { } export async function handleInsuranceFlow(testCase: TestCase) { - const { isPolicyFound, isPolicyDriver, endorsements, isRecalNotification } = testCase.testData; + const { isPolicyFound, isPolicyDriver, endorsements, isRecalVehicle } = testCase.testData; // Check if the insurance policy has endorsements const hasEndorsements = endorsements && endorsements.length > 0; @@ -341,7 +346,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(); diff --git a/playwright-tests/tests/InsuranceBigTruckVerified.ts b/playwright-tests/tests/InsuranceBigTruckVerified.ts new file mode 100644 index 000000000..f06e9171d --- /dev/null +++ b/playwright-tests/tests/InsuranceBigTruckVerified.ts @@ -0,0 +1,81 @@ +//Imports here +import { ITestData } from "@business-logic/types/ITestData" +import { PaymentMethod, AppointmentType, DamageType, PaymentType } from "@business-logic/types/Enums"; +import TestCase from "@business-logic/types/TestCase"; +import { VehicleLookupType } from "@business-logic/types/Enums"; +import { getDefaultTestData, setFakerSeedFromTestName } from "@business-logic/constants/DefaultTestData"; + +// 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: AppointmentType.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: TestCase[] = []; + +const tc = new TestCase({ + name: `InsuranceBigTruckVerified`, + tags: ['@E2E','@InsuranceBigTruckVerified', '@test_report', '@Insurance'], + testData: insuranceBigTruckVerifiedData +}, undefined, 'InsuranceBigTruckVerified'); +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 70ce6b528..b97efd31c 100644 --- a/playwright-tests/tests/InsuranceITAC21stCentury.ts +++ b/playwright-tests/tests/InsuranceITAC21stCentury.ts @@ -19,7 +19,7 @@ const insuranceITAC21stCenturyData: Partial = { isDuplicateClaim: true, isPolicyFound: true, 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/InsuranceUnverified.ts b/playwright-tests/tests/InsuranceUnverified.ts new file mode 100644 index 000000000..f0fcf293a --- /dev/null +++ b/playwright-tests/tests/InsuranceUnverified.ts @@ -0,0 +1,86 @@ +//Imports here +import { ITestData } from "@business-logic/types/ITestData" +import { PaymentMethod, AppointmentType, DamageType, PartQuestionType } from "@business-logic/types/Enums"; +import TestCase from "@business-logic/types/TestCase"; +import { VehicleLookupType } from "@business-logic/types/Enums"; +import { getDefaultTestData, setFakerSeedFromTestName } from "@business-logic/constants/DefaultTestData"; + +// 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, + isRecalNotification: 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: AppointmentType.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: TestCase[] = []; + +const tc = new TestCase({ + name: `InsuranceUnverified`, + tags: ['@E2E','@InsuranceUnverified', '@test_report', '@Insurance'], + testData: insuranceUnverifiedData +}, undefined, 'InsuranceUnverified'); +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 dad46ecd5..9a881d4e5 100644 --- a/playwright-tests/tests/alert-validation/alert0001_HeavyTruck.ts +++ b/playwright-tests/tests/alert-validation/alert0001_HeavyTruck.ts @@ -18,7 +18,7 @@ const heavyTruckData: Partial = { style: 'conventional cab' }, alertFlags: { - isHeavyTruckVehicle: true + isHeavyTruckVehicleAlert: true }, vehicleDamage: [ VehicleDamage.WindshieldOneChip, diff --git a/playwright-tests/tests/alert-validation/alert0007_VinNotFound.ts b/playwright-tests/tests/alert-validation/alert0007_VinNotFound.ts index 0ee058903..fccf12662 100644 --- a/playwright-tests/tests/alert-validation/alert0007_VinNotFound.ts +++ b/playwright-tests/tests/alert-validation/alert0007_VinNotFound.ts @@ -75,9 +75,9 @@ const lookupTypesToTest: LookupTestCase[] = [ }, customerDetails: { address: { - street: '4076 Spectacle Dr', + street: '4076 Spectacle Drive', city: 'Columbus', - state: 'Ohio', + state: 'OH', postalCode: '59261', country: 'United States' } diff --git a/src/App.vue b/src/App.vue index 045a28bf8..cae31dd24 100644 --- a/src/App.vue +++ b/src/App.vue @@ -8,6 +8,7 @@ + diff --git a/src/experiment-components/donation-block.vue b/src/experiment-components/donation-block.vue index 30fe9f8ed..0e14588d7 100644 --- a/src/experiment-components/donation-block.vue +++ b/src/experiment-components/donation-block.vue @@ -208,8 +208,9 @@ export default { justify-content: center; background: $blue-100; border-radius: 3rem; - box-shadow: 0px 0px 4px 1px rgba(0, 112, 209, 1) inset; + box-shadow: inset 0px 2px 4px 0px rgba(0, 112, 209, 0.2); font-size: 0.875rem; + border: 1px solid $blue; .label { position: relative; display: inline-flex; @@ -231,7 +232,7 @@ export default { left: -2rem; background: $blue; border-radius: 3rem; - box-shadow: 0px 0px 0px 1px rgba(0, 112, 209, 1) inset; + box-shadow: 0px 3px 4px 0px rgba(66, 68, 90, 0.2); transition: transform 750ms cubic-bezier(0.02, 0.94, 0.09, 0.97); transform: translate3d(2rem, 0, 0); } 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/fmg-components/funnel-header/funnel-header.vue b/src/fmg-components/funnel-header/funnel-header.vue index d740cbc8b..f7d55b354 100644 --- a/src/fmg-components/funnel-header/funnel-header.vue +++ b/src/fmg-components/funnel-header/funnel-header.vue @@ -44,6 +44,7 @@ import { globalEvents } from "@/constants/events"; import menuModal from "@/fmg-components/funnel-header/menu-modal/menu-modal"; import progressBar from "@/fmg-components/funnel-header/progress-bar/progress-bar"; import { webchatHelper } from "@/helpers/webchat-helper"; +import store from "@/store"; // Constants const ALERT_DURATION = 3000; // millisecond time to display alert before dismissal @@ -53,6 +54,9 @@ export default { data() { return { globalAlertMessages: [], + sierraChatOpen: false, + salesforceChatOpen: false, + isSalesforceTransferInProgress: false, }; }, props: { @@ -71,9 +75,19 @@ export default { return this.getCmsContent(this.cmsWidgetName, "LogoImage"); }, shouldShowWebchatButton() { + // Hide the chat icon if Sierra chat is open + if (this.sierraChatOpen) { + return false; + } + // Hide the chat icon if Salesforce chat is open or transfer is in progress + if (this.salesforceChatOpen || this.isSalesforceTransferInProgress) { + return false; + } + // Show if either Sierra is enabled or Salesforce button is available and not hidden by page return ( - !this.shouldHideWebchatButtonOnPage && - this.webchatGlobalNonpersistedState.showWebchatButton + (this.webchatGlobalNonpersistedState.shouldLaunchSierra || + this.webchatGlobalNonpersistedState.showWebchatButton) && + !this.shouldHideWebchatButtonOnPage ); }, }, @@ -91,8 +105,21 @@ export default { this.globalAlertMessages.push(alertToPush); }, webchatClicked(event) { - event.preventDefault(); // avoid validation firing + event.preventDefault(); this.launchWebchat(); + // Only set sierraChatOpen if Sierra experiment is active + if (this.webchatGlobalNonpersistedState.shouldLaunchSierra) { + this.sierraChatOpen = true; + } + }, + syncSierraExperimentFlag() { + const experiments = store.getters.applicationUser.experiments; + const sierraExp = experiments?.find( + (exp) => + exp.universeName === "CONTENT_FMG_SierraWebchat" && + exp.settings?.UseSierraChat === "true" + ); + this.webchatGlobalNonpersistedState.shouldLaunchSierra = !!sierraExp; }, }, components: { @@ -101,6 +128,7 @@ export default { progressBar, }, mounted() { + this.syncSierraExperimentFlag(); // Check if alert event is on the bus const alertEvent = eventBus.readAndPopEventFromBus( globalEvents.Categories.GLOBAL_ALERT, @@ -122,6 +150,47 @@ export default { unknownAlertEvent.displayAlert = true; this.globalAlertMessages.push(unknownAlertEvent); } + // Listen for Sierra chat open/close events from sierra-webchat + this._handleSierraChatVisibility = (event) => { + this.sierraChatOpen = !!(event.detail && event.detail.open); + if (this.sierraChatOpen) { + this.isSalesforceTransferInProgress = false; + } + }; + window.addEventListener("sierra-chat-visibility", this._handleSierraChatVisibility); + + // Listen for Salesforce chat open/close events from salesforce-webchat + this._handleSalesforceChatVisibility = (event) => { + this.salesforceChatOpen = !!(event.detail && event.detail.open === true); + }; + window.addEventListener("salesforce-chat-visibility", this._handleSalesforceChatVisibility); + + // Listen for transfer event to set transfer-in-progress flag + this._handleSierraToSalesforceTransfer = () => { + this.isSalesforceTransferInProgress = true; + }; + window.addEventListener("sierra-chat-transfer", this._handleSierraToSalesforceTransfer); + + // Reset transfer flag when Salesforce chat opens + this._handleSalesforceChatOpened = (event) => { + if (event.detail && event.detail.open === true) { + this.isSalesforceTransferInProgress = false; + this.salesforceChatOpen = true; + } else { + this.salesforceChatOpen = false; + } + }; + window.addEventListener("salesforce-chat-visibility", this._handleSalesforceChatOpened); + }, + beforeUnmount() { + window.removeEventListener("sierra-chat-closed", this._handleSierraChatClosed); + window.removeEventListener("sierra-chat-visibility", this._handleSierraChatVisibility); + window.removeEventListener( + "salesforce-chat-visibility", + this._handleSalesforceChatVisibility + ); + window.removeEventListener("sierra-chat-transfer", this._handleSierraToSalesforceTransfer); + window.removeEventListener("salesforce-chat-visibility", this._handleSalesforceChatOpened); }, }; diff --git a/src/helpers/webchat-helper.js b/src/helpers/webchat-helper.js index f2b624916..917d6de0f 100644 --- a/src/helpers/webchat-helper.js +++ b/src/helpers/webchat-helper.js @@ -9,7 +9,7 @@ const webchatGlobalNonpersistedState = reactive({ export const webchatHelper = () => { function launchWebchat() { if (webchatGlobalNonpersistedState.shouldLaunchSierra) { - // do sierra logic here + window.dispatchEvent(new CustomEvent("launch-sierra-webchat")); // launches the sierra chat } else { window.embedded_svc.bootstrapEmbeddedService(); // launches the salesForce prechat form } diff --git a/src/layouts/customer-details/customer-details.vue b/src/layouts/customer-details/customer-details.vue index 8cb17673b..e3549b813 100644 --- a/src/layouts/customer-details/customer-details.vue +++ b/src/layouts/customer-details/customer-details.vue @@ -57,7 +57,7 @@ @ForwardClicked="forwardButtonAction" />
@@ -212,3 +212,10 @@ export default { }, }; + + 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/layouts/payment-method/afterpay-breakout/afterpay-breakout.vue b/src/layouts/payment-method/afterpay-breakout/afterpay-breakout.vue index abe6221ba..9e98eb9c3 100644 --- a/src/layouts/payment-method/afterpay-breakout/afterpay-breakout.vue +++ b/src/layouts/payment-method/afterpay-breakout/afterpay-breakout.vue @@ -1,6 +1,9 @@