Merge branch 'CASH-631' into kpatel/Misc_Fix

This commit is contained in:
kpatel8hs4io 2025-05-20 09:20:07 -04:00
commit b6d7c014ea
28 changed files with 243 additions and 48 deletions

View file

@ -9,6 +9,8 @@ SKIP_CONTENT_SITE=false
# Base URLs by environment (uncomment the one you need)
# qa
BASE_URL="https://www-qa2.safelite.com/"
# local version of FMG (after running the local server)
# BASE_URL="http://localhost:8080/fmg/"
# qa with skipToInsurance Turned Off
# BASE_URL="https://fixmyglassqa.safelite.com/?cns=all&experiments=ConceptFunnel=ConceptFunnel_V1=ConceptFunnel_VinAndEmailOptional=true,NextGen_IGQSkipToInsurance=NextGen_IGQSkipToInsurance_V1=NextGen_IGQSkipToInsurance_CONTROL=true"
# sys
@ -25,4 +27,3 @@ ADMIN_SERVICE_API_URL="https://issadminapi.dev.sagaws.net/iss-admin/api/v1/"
# API authentication (replace with actual value when running tests)
CCIS_API_AUTH="undefined"
SKIP_CONTENT_SITE=false

View file

@ -28,6 +28,7 @@ const defaultAfterpayDetails: IPaymentDetails = {
const defaultPaypalDetails: IPaymentDetails = {
paymentType: PaymentType.Paypal,
username: 'itqatest@safelite.com',
password: 'Safelite1'
}

View file

@ -1,4 +1,4 @@
import { DamageType as DamageCause, WindshieldDamage, ServiceLocation, EndorsementType, VehicleLookupType, PartQuestionType, PaymentType, AppointmentType } from "./Enums";
import { DamageType as DamageCause, WindshieldDamage, ServiceLocation, EndorsementType, VehicleLookupType, PartQuestionType, PaymentType, AppointmentType, AppointmentTimeslot } from "./Enums";
import { IAddress } from "./IAddress";
export interface ICustomerDetails {
@ -30,7 +30,8 @@ export interface IAppointmentDetails {
appointmentDate?: Date,
shopAddress?: string, // Used for in-shop
serviceAddress?: IAddress, // Used for mobile
isVehicleProtected?: boolean // Used for mobile
isVehicleProtected?: boolean,
appointmentTimeSlot?: AppointmentTimeslot // Used for mobile
}
export interface IEndorsementDetails {

View file

@ -124,5 +124,11 @@ export enum AppointmentType{
DropOff = "Drop-off"
}
export enum AppointmentTimeslot{
EarlyBird = "EarlyBird",
DropOff = "DropOff",
overnight = "Overnight"
}

View file

@ -1,3 +1,5 @@
import { IAppointmentDetails, ICustomerDetails } from '@business-logic/types/CustomerDetails';
import Soft from '@business-logic/validations/Soft';
import test, { expect, type Locator, type Page } from '@playwright/test';
import { error } from 'console';
@ -13,6 +15,7 @@ export class BasePage {
readonly pageSpinner: Locator;
readonly buttonLoadSpin: Locator;
readonly hamburgerMenu: Locator;
readonly progressBar: Locator;
constructor(page: Page){
this.page = page;
@ -21,6 +24,7 @@ export class BasePage {
this.pageSpinner = page.getByRole('status');
this.buttonLoadSpin = page.getByRole('alert');
this.hamburgerMenu = this.page.getByRole('button', { name: 'Hamburger Menu (modal window)' });
this.progressBar = this.page.locator('#progress-bar-container progress');
}
async nextPage() {
@ -94,6 +98,42 @@ export class BasePage {
console.log(`Referral Number:${referralNumber}`);
console.log(`Referral Sequence Number:${referralSequenceNumber}`);
});
}
async mockScheduleResponseForEarlyBird(customerDetails: ICustomerDetails) {
{
const apiUrl = `https://digitalapi.${process.env['NODE_ENV']!.replace('sys', 'test').toLowerCase()}.safelite.io/schedule/api/v1/schedule/mobile-time-slots`;
await this.page.route(apiUrl, async (route) => {
const response = await route.fetch();
const responseBody = await response.json();
responseBody.days.forEach((day: any) => {
day.timeSlots.forEach((slot: any) => {
if (slot.id.includes("AM")) {
slot.offerPremium = true;
}
});
});
customerDetails.apptDate = responseBody.days.find((day: any) =>
day.timeSlots.some((slot: any) => slot.offerPremium === true)
).date || undefined;
// Mock the response
await route.fulfill({
response,
body: JSON.stringify(responseBody),
});
});
}
}
async validateProgressBar(progressPercentage: string) {
await this.page.locator('button .loader').waitFor({ state: 'hidden', timeout: 60000 });
const actualProgressPercentage = await this.progressBar.getAttribute("value") || "Not Found";
Soft.expect(actualProgressPercentage).toBe(progressPercentage);
console.log(`Progress Bar Percentage: Actual - ${actualProgressPercentage} vs Expected - ${progressPercentage}`);
}
}

View file

@ -14,6 +14,7 @@ export default class CapabilityQuestionsPage extends PartQuestionsPage {
async handleCapabilityQuestionsPage(testCase: Partial<ITestData>) {
const { capabilityQuestions } = testCase;
await this.validateProgressBar("40");
// Validate the capability questions are on the page
await this.validatePartQuestions(capabilityQuestions!);

View file

@ -45,6 +45,8 @@ export class ContactDetailsPage extends BasePage {
@step("ContactDetailsPage >> Enter contact details: ")
async handleContactDetailsPage(testData: Partial<ITestData>) {
const { customerDetails } = testData;
await this.validateProgressBar("84");
await this.enterContactDetails(customerDetails!);
await this.nextPage();
}

View file

@ -74,6 +74,7 @@ export class EstimatePage extends BasePage {
@step("EstimatePage >> Select Lookup Type")
async handleEstimatePage(testData: Partial<ITestData>) {
const { vehicleDetails } = testData;
await this.validateProgressBar("28");
await this.vehicleLookup(vehicleDetails!);
}
}

View file

@ -52,6 +52,7 @@ export class InsuranceCompanyPage extends BasePage {
async handleInsuranceCompanyPage(testData: Partial<ITestData>) {
const { claimDetails } = testData;
await this.validateProgressBar("52");
await this.enterInsuranceCompany(claimDetails!.client!);
await this.nextPage();
}

View file

@ -13,6 +13,8 @@ export default class MoldingQuestionsPage extends PartQuestionsPage {
@step("MoldingQuestionsPage >> Select Molding Questions: ")
async handleMoldingQuestionsPage(testCase: Partial<ITestData>) {
const { moldingQuestions } = testCase;
await this.validateProgressBar("40");
await this.validatePartQuestions(moldingQuestions!);
await this.selectPartQuestionResponses(moldingQuestions!);
await this.nextPage();

View file

@ -178,6 +178,8 @@ export class OrderConfirmationPage extends BasePage {
@step("OrderConfirmationPage >> Validate order")
async verifyOrderConfirmationPage(testData: Partial<ITestData>) {
await this.validateProgressBar("100");
await this.validateOrderConfirmationPage(testData);
const workOrderNumber = await this.logOrderNumber();
await test.step(`Session Storage Work Order Number: ${workOrderNumber}`, async () => {

View file

@ -62,6 +62,8 @@ export class PartQuestionsPage extends BasePage {
@step("PartQuestionsPage >> Select Vehicle Part Question Responses")
async handlePartQuestionsPage(testData: Partial<ITestData>) {
const { partQuestions } = testData;
await this.validateProgressBar("40");
await this.validatePartQuestions(partQuestions!);
await this.selectPartQuestionResponses(partQuestions!);
await this.nextPage();

View file

@ -1,7 +1,7 @@
import { expect, type Locator, type Page } from '@playwright/test';
import { BasePage } from './BasePage';
import { IPaymentDetails } from '@business-logic/types/CustomerDetails';
import { AppointmentType, PaymentMethod, PaymentType, ServicePackage, VehicleDamage } from '@business-logic/types/Enums';
import { AppointmentTimeslot, AppointmentType, PaymentMethod, PaymentType, ServicePackage, VehicleDamage } from '@business-logic/types/Enums';
import { PaymentPage } from './PaymentPage';
import { AfterpayPage } from './AfterpayPage';
import { PaypalPage } from './PaypalPage';
@ -76,7 +76,7 @@ export class PaymentMethodPage extends BasePage {
// Cart panel elements
this.cartPanelDetails = this.page.locator('.cart-panel');
this.subtotalText = this.page.locator('.sub-total');
this.finalAmountDueText = this.page.locator('div.amount-due');
this.finalAmountDueText = this.cartPanelDetails.locator('div.amount-due');
this.glassServiceText = this.page.locator('div', { hasText: /^Glass service only$/ });
this.wiperBladesText = this.page.locator('div', { hasText: /^New wiper blades$/ });
this.rainDefenseText = this.page.locator('div', { hasText: /^Rain Defense™$/ });
@ -148,13 +148,19 @@ export class PaymentMethodPage 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
if (promoCode) {
expect.soft(servicePackageValue).toContain(`Promo code ${promoCode} applied`);
};
// Early Bird line item validation
if (appointmentDetails?.appointmentTimeSlot == AppointmentTimeslot.EarlyBird) {
expect.soft(servicePackageValue).toContain('Early Bird');
}
}
}
@ -261,7 +267,7 @@ l
async getActualAppointmentDetails(): Promise<any> {
let actualAppointmentDetails = new Map<string, string[]>();
let appointmentDetailsSubSections = await this.appointmentDetailsSection.locator('div .py-3').all();
let appointmentDetailsSubSections = await this.appointmentDetailsSection.locator('.review-table .py-3').all();
for (let element of appointmentDetailsSubSections) {
let label = await element.locator('div .text-block').innerText();
@ -377,8 +383,8 @@ l
: stringForReplace;
break;
case ServicePackage.Premium:
stringForRepair.push("New wiper blades", "Rain Defense™");
stringForReplace.push("New wiper blades", "Rain Defense™");
stringForRepair.push("New wiper blades", "Rain repel treatment");
stringForReplace.push("New wiper blades", "Rain repel treatment");
expectedServicePackageDetails["Premium service"] = isRepair
? stringForRepair
: stringForReplace;
@ -464,6 +470,7 @@ l
async handlePaymentMethodPage(testData: Partial<ITestData>) {
const { servicePackage, isRecalVehicle, paymentDetails } = testData;
await this.validateProgressBar("92");
await this.validatePaymentDetailsPage(testData);
// Verify VAPS wipers on backend for standard and premium packages

View file

@ -5,6 +5,9 @@ import { IPaymentDetails } from '@business-logic/types/CustomerDetails';
export class PaypalPage extends BasePage {
readonly page: Page;
readonly loginWithPasswordButton: Locator;
readonly usernameTextBox: Locator;
readonly nextButton: Locator;
readonly usePasswordInsteadButton: Locator;
readonly passwordTextBox: Locator;
readonly paypalLoginButton: Locator;
readonly completePurchaseButton: Locator;
@ -12,13 +15,19 @@ export class PaypalPage extends BasePage {
constructor(page: Page) {
super(page);
this.page = page;
this.loginWithPasswordButton = page.getByRole('link', { name: 'Log in with a password instead' });
this.usernameTextBox = page.getByPlaceholder('Email');
this.nextButton = page.getByRole('button', { name: 'Next' });
this.loginWithPasswordButton = page.getByRole('button', { name: 'Use Password Instead' });
this.passwordTextBox = page.getByPlaceholder('Password');
this.paypalLoginButton = page.getByRole('button', { name: 'Log In', exact: true });
this.completePurchaseButton = page.getByTestId('submit-button-initial');
this.completePurchaseButton = page.getByRole('button', { name: 'Pay $' });
}
async completePaypalPurchase(paymentDetails: IPaymentDetails){
if (!await this.loginWithPasswordButton.isVisible()) {
await this.usernameTextBox.fill(paymentDetails.username!);
await this.nextButton.click();
}
await this.loginWithPasswordButton.click();
await this.passwordTextBox.fill(paymentDetails.password!);
await this.paypalLoginButton.click();

View file

@ -2,7 +2,7 @@ import { expect, type Locator, type Page } from '@playwright/test';
import { BasePage } from './BasePage';
import { IAppointmentDetails, ICustomerDetails } from '@business-logic/types/CustomerDetails';
import { formatDate, formatTime } from '@impl/utils/DateUtils';
import { AppointmentType, ServiceLocation } from '@business-logic/types/Enums';
import { AppointmentTimeslot, AppointmentType, ServiceLocation } from '@business-logic/types/Enums';
import { time } from 'console';
import { step } from '@business-logic/types/Step';
import { ITestData } from '@business-logic/types/ITestData';
@ -47,23 +47,39 @@ export class SchedulePage extends BasePage {
await this.modalContinueButton.click();
}
async scheduleFirstAppointment(customerDetails: ICustomerDetails) {
async scheduleFirstAppointment(testData: Partial<ITestData>) {
const { appointmentDetails, customerDetails } = testData;
while (!(await this.firstAvailableDate.isVisible())) {
if (appointmentDetails?.appointmentTimeSlot == AppointmentTimeslot.EarlyBird) {
await this.mockScheduleResponseForEarlyBird(customerDetails!);
}
await this.viewMoreDatesLink.click();
}
await this.firstAvailableDate.click().then(async () => {
customerDetails.apptDate = `${await this.firstAvailableDate.getAttribute("id")}`
});
// const timeSlots = this.timeSlots;
const timeSlotCount = await this.timeSlots.count();
const randomIndex = Math.floor(Math.random() * timeSlotCount);
const timeSlot = this.timeSlots.nth(randomIndex);
await timeSlot.click().then(async () => {
customerDetails.apptTime = await this.getFormattedTimeSlot(timeSlot);
});
if (appointmentDetails?.appointmentTimeSlot == AppointmentTimeslot.EarlyBird) {
await this.page.locator(`.selectable-days, [id='${customerDetails?.apptDate}']`).click();
const earlyBirdTimeSlot = this.timeSlots.filter({ hasText: "Earlybird" }).first();
await earlyBirdTimeSlot.click().then(async () => {
customerDetails!.apptTime = await this.getFormattedTimeSlot(earlyBirdTimeSlot);
});
}
else {
await this.firstAvailableDate.click().then(async () => {
customerDetails!.apptDate = `${await this.firstAvailableDate.getAttribute("id")}`
});
// const timeSlots = this.timeSlots;
const timeSlotCount = await this.timeSlots.count();
const randomIndex = Math.floor(Math.random() * timeSlotCount);
const timeSlot = this.timeSlots.nth(randomIndex);
await timeSlot.click().then(async () => {
customerDetails!.apptTime = await this.getFormattedTimeSlot(timeSlot);
});
}
// appointmentmentDetails.serviceLocation === AppointmentType.DropOff ? await this.dropOffButton.click() : await this.firstAvailableTime.click();
customerDetails.apptDuration = (await this.appointmentDuration.innerText()).replace("Duration: ", "");
customerDetails!.apptDuration = (await this.appointmentDuration.innerText()).replace("Duration: ", "");
await this.nextPage();
}
@ -74,7 +90,7 @@ export class SchedulePage extends BasePage {
{
formattedTimeSlot = selectedTimeSlot.includes("overnight") ? "drop off by 5:30 pm on the night of your scheduled appointment. Pick-up time dependent on shop schedule" : "drop off before 9:30 AM";
}
else if (selectedTimeSlot.includes("-") || selectedTimeSlot.toLowerCase().includes("Earlybird"))
else if (selectedTimeSlot.includes("-") || selectedTimeSlot.includes("Earlybird"))
{
formattedTimeSlot = selectedTimeSlot.includes("Earlybird") ? "arriving between 8:00 AM - 12:00 PM" : `arriving between ${selectedTimeSlot}`;
}
@ -87,7 +103,8 @@ export class SchedulePage extends BasePage {
@step("SchedulePage >> Schedule appointment: ")
async handleSchedulePage(testData: Partial<ITestData>) {
const { customerDetails } = testData;
await this.scheduleFirstAppointment(customerDetails!);
await this.validateProgressBar("72");
await this.scheduleFirstAppointment(testData);
}
}

View file

@ -1,7 +1,7 @@
import { expect, type Locator, type Page } from '@playwright/test';
import { BasePage } from './BasePage';
import { IAppointmentDetails } from '@business-logic/types/CustomerDetails';
import { AppointmentType } from '@business-logic/types/Enums';
import { AppointmentTimeslot, AppointmentType } from '@business-logic/types/Enums';
import { AddressForm } from './forms/AddressForm';
import { faker } from '@faker-js/faker';
import { step } from '@business-logic/types/Step';
@ -36,7 +36,6 @@ export class ServiceLocationPage extends BasePage {
readonly zipCodeTextBox: Locator;
readonly vehicleProtectedYesButton: Locator;
readonly vehicleProtectedNoButton: Locator;
readonly saveAddressButton: Locator;
readonly repeatedClicksModalCloseButton: Locator;
url = process.env['BASE_URL']! + '/fmg/?fmgPage=service-location';
@ -49,7 +48,7 @@ export class ServiceLocationPage extends BasePage {
// Initial selection
this.inShopButton = this.page.getByText(/In-shop/);
this.mobileButton = this.page.getByText(/Mobile/);
this.mobileButton = this.page.getByText(/Mobile/).nth(0);
this.dropOffButton = this.page.getByText(/Drop-off/);
this.RecalWarningMessage1 = this.page.getByText(/We're not able to provide mobile service/);
this.RecalWarningMessage2 = this.page.getByText(/advanced safety system recalibration needs to be done in our shop./);
@ -72,15 +71,15 @@ export class ServiceLocationPage extends BasePage {
this.zipCodeTextBox = this.page.getByRole('textbox', { name: 'Zip code' });
this.vehicleProtectedYesButton = this.page.locator('label').filter({ hasText: 'Yes' }).locator('div');
this.vehicleProtectedNoButton = this.page.locator('label').filter({ hasText: 'No' }).locator('div');
this.saveAddressButton = this.page.getByRole('button', { name: 'Continue' })
this.repeatedClicksModalCloseButton = this.page.locator('.QSISlider').locator('img[src*=\'close\']');
}
async selectLocation(appointmentDetails: IAppointmentDetails){
async selectLocation(testData: Partial<ITestData>) {
const { appointmentDetails, customerDetails } = testData;
switch(appointmentDetails.serviceLocation) {
case AppointmentType.Mobile:
await this.scheduleMobile(appointmentDetails);
switch(appointmentDetails?.serviceLocation) {
case AppointmentType.Mobile:
await this.scheduleMobile(testData);
break;
case AppointmentType.InShop:
await this.scheduleInShop(appointmentDetails);
@ -91,7 +90,6 @@ export class ServiceLocationPage extends BasePage {
}
}
async scheduleInShop(appointmentDetails?: IAppointmentDetails) {
await this.inShopButton.click();
if (appointmentDetails && appointmentDetails.shopAddress) {
@ -116,10 +114,13 @@ export class ServiceLocationPage extends BasePage {
}
}
async scheduleMobile(appointmentDetails: IAppointmentDetails){
if (appointmentDetails.serviceAddress) {
async scheduleMobile(testData: Partial<ITestData>) {
const { appointmentDetails, customerDetails } = testData;
if (appointmentDetails?.serviceAddress) {
await this.mobileButton.click();
await this.enterServiceAddressButton.click();
if (!(await this.serviceAddressTextBox.isVisible())) {
await this.enterServiceAddressButton.click();
}
await this.addressForm.populateAddress({ address: appointmentDetails.serviceAddress! });
if (await this.repeatedClicksModalCloseButton.isVisible()) {
await this.repeatedClicksModalCloseButton.click();
@ -129,7 +130,10 @@ export class ServiceLocationPage extends BasePage {
} else {
await this.vehicleProtectedNoButton.check();
}
await this.saveAddressButton.click();
if (appointmentDetails.appointmentTimeSlot == AppointmentTimeslot.EarlyBird)
{
await this.mockScheduleResponseForEarlyBird(customerDetails!);
}
} else {
console.error('ServiceLocationPage >> Please supply an address')
}
@ -159,8 +163,9 @@ export class ServiceLocationPage extends BasePage {
@step("ServiceLocationPage >> Select service location: ")
async handleServiceLocationPage(testData: Partial<ITestData>) {
const { appointmentDetails } = testData;
await this.selectLocation(appointmentDetails!);
await this.validateProgressBar("60");
await this.selectLocation(testData);
await this.nextPage();
}
}

View file

@ -185,6 +185,7 @@ export class ServicePackagesPage extends BasePage {
async handleServicePackagePage(testData: Partial<ITestData>) {
const { customerDetails, paymentMethod, servicePackage, promoCode, canNotRecal, dynamicRecal, hasOemEndorsement, vehicleDamage } = testData;
await this.validateProgressBar("48");
// Define repair damage types (vs. replacement types)
const repairTypes: VehicleDamage[] = [
VehicleDamage.WindshieldOneChip,

View file

@ -14,6 +14,7 @@ export class ServiceZipPage extends LookupPage {
@step("ZipLookupPage >> Lookup by service ZIP: ")
async handleServiceZipPage(testData: Partial<ITestData>) {
const { customerDetails, vehicleDetails, alertFlags } = testData;
await this.validateProgressBar("32");
await this.enterZip(customerDetails!.address.postalCode!);
await this.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!);
}

View file

@ -169,6 +169,7 @@ export class VehicleDamagePage extends BasePage {
const {vehicleDamage} = testData;
const {isRepairReplace, isRepairOnly} = testData.alertFlags || {};
await this.validateProgressBar("16");
await this.selectDamage(vehicleDamage!);
// Handle alert conditions for vehicle damage
if (isRepairReplace) {

View file

@ -31,6 +31,8 @@ export class VehicleLookupAddressPage extends LookupPage {
@step("VehicleLookupAddressPage >> Lookup by address: ")
async handleVehicleLookupAddressPage(testData: Partial<ITestData>) {
const { customerDetails, vehicleDetails, alertFlags } = testData;
await this.validateProgressBar("32");
await this.lookupVehicleByAddress(customerDetails!, vehicleDetails!);
await this.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!);
}

View file

@ -30,6 +30,8 @@ export class VehicleLookupLicensePage extends LookupPage {
@step("VehicleLookupLicensePage >> Lookup by license plate: ")
async handleVehicleLookupLicensePage(testData: Partial<ITestData>) {
const { customerDetails, vehicleDetails, alertFlags } = testData;
await this.validateProgressBar("32");
await this.enterPlateDetails(vehicleDetails!);
await this.enterZip(customerDetails!.address.postalCode!);
await this.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!);

View file

@ -13,6 +13,8 @@ export default class VehiclePartQuestionsPage extends PartQuestionsPage{
@step("VehiclePartsPage >> Select Vehicle Part Questions: ")
async handleVehiclePartsPage(testCase: Partial<ITestData>) {
const { vehiclePartQuestions } = testCase;
await this.validateProgressBar("40");
await this.validatePartQuestions(vehiclePartQuestions!);
await this.selectPartQuestionResponses(vehiclePartQuestions!);
await this.nextPage();

View file

@ -47,6 +47,7 @@ export class VehicleSelectionPage extends BasePage {
@step("VehicleSelectionPage >> Select Vehicle: ")
async handleVehicleSelectionPage(testData: Partial<ITestData>) {
await this.validateProgressBar("4");
const { vehicleDetails } = testData;
const { isHeavyTruckVehicle, isSplitWindshield } = testData.alertFlags || {};

View file

@ -26,6 +26,8 @@ export class VinLookupPage extends LookupPage {
@step("VinLookupPage >> Lookup by VIN: ")
async handleVehicleLookupVinPage(testData: Partial<ITestData>) {
const { customerDetails, vehicleDetails, alertFlags } = testData;
await this.validateProgressBar("32");
await this.enterVin(vehicleDetails!.vin!);
await this.enterZip(customerDetails!.address.postalCode!);
await this.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!);

View file

@ -1,6 +1,6 @@
//Imports here
import { ITestData } from "@business-logic/types/ITestData"
import { AppointmentType, PaymentType } from "@business-logic/types/Enums";
import { AppointmentTimeslot, AppointmentType, 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";
@ -48,7 +48,8 @@ const cashReplaceVinMobileData: Partial<ITestData> = {
state: 'MD',
postalCode: '21237',
country: 'United States'
}
},
appointmentTimeSlot: AppointmentTimeslot.EarlyBird
},
// Payment at service

View file

@ -117,6 +117,9 @@ export default {
);
return displayPrice != this.additionalButtonData.strikeThroughPrice;
},
totalAfterpayPrice() {
return parseFloat(this.buttonAuxillaryCopy.replace("$", ""));
},
},
methods: {
getRouterLinkDisplayTextFromCopy,
@ -134,11 +137,12 @@ export default {
.filter((lineItem) => lineItem);
},
afterpayPrice() {
const decimalPrice = parseFloat(this.buttonAuxillaryCopy.replace("$", ""));
const decimalPrice = this.totalAfterpayPrice;
return "$" + (decimalPrice / 4).toFixed(2);
},
truncatedSinglePayment() {
return "$" + Math.trunc(this.buttonAuxillaryCopy.replace("$", ""));
const decimalPrice = this.totalAfterpayPrice;
return "$" + decimalPrice.toFixed(2);
},
hasPackageDiscount() {
return (

View file

@ -62,6 +62,23 @@
:isNoComp="isNoComp"
:isExpandedOnLoad="true" />
<div class="payment-method-question">
<buttonQuestion
v-if="!isAfterpay"
groupName="payment-method"
buttonTypeString="payment-method-list-button"
:buttonTypeObject="paymentMethodListButton"
isWide
:questionText="paymentMethodQuestionText"
:answers="paymentMethodAnswerData"
isRequired
:validationRules="validationRules"
:showApplePay="false"
:isInsurance="isInsurance"
v-model="switchedPaymentMethod"
textPosition="text-start" />
</div>
<hr class="mb-5" />
<navbar
@ -239,6 +256,8 @@ import {
getSalesTax,
} from "@/helpers/pricing-helper.js";
import { debugLog } from "@/helpers/debug-log-helper.js";
import buttonQuestion from "@/digital-components/button-question/button-question";
import paymentMethodListButton from "@/layouts/payment-method/payment-method-question/payment-method-list-button/payment-method-list-button";
export default {
name: "payment",
@ -272,8 +291,12 @@ export default {
lineItems: [],
availableVaps: [],
shouldBlockInteraction: false,
paymentMethodListButton: paymentMethodListButton,
};
},
props: {
switchedpaymentTypeValue: String,
},
directives: {
resize: {
mounted: function (el) {
@ -419,6 +442,17 @@ export default {
});
},
computed: {
switchedPaymentMethod: {
get: function () {
return this.switchedpaymentTypeValue;
},
set: function (switchedPaymentMethod) {
this.$emit("update:switchedpaymentTypeValue", switchedPaymentMethod);
if (switchedPaymentMethod === paymentMethods.AFTERPAY) {
this.switchToAfterpay();
}
},
},
parentNomainName() {
return window.location.hostname;
},
@ -480,12 +514,36 @@ export default {
}
return false;
},
isAfterpay() {
if (this.paymentType == "ap") {
return true;
}
return false;
},
shouldDisplayPiaCCAlert() {
return this.shouldDisplayPiaAlert(paymentMethods.CREDIT_CARD);
},
shouldDisplayPiaAPAlert() {
return this.shouldDisplayPiaAlert(paymentMethods.AFTERPAY);
},
paymentMethodQuestionText() {
return this.getCmsContent("SwitchPaymentMethodWidget", "QuestionText");
},
paymentMethodAnswerData() {
var cmsData = this.getAnswersNullSafe("SwitchPaymentMethodWidget");
var tempItems = cmsData.map((answer) => {
return {
buttonLabel: answer.Text,
altText: answer.Text,
groupName: "payment-method",
value: answer.Name,
buttonImage: answer.AnswerImageUrl,
};
});
return tempItems;
},
},
methods: {
arePagePrerequisitesValid() {
@ -594,6 +652,15 @@ export default {
return preReqResult;
},
getAnswersNullSafe(widgetName) {
const rawData = this.getCmsContent(widgetName, "Answers");
if (!rawData) {
return [];
} else {
return rawData;
}
},
getWOrkOrderNumber() {
if (store.getters.order.workOrderNumber) {
const items = store.getters.order.workOrderNumber.split("-");
@ -797,6 +864,18 @@ export default {
);
}
},
switchToAfterpay() {
this.dispatchStoreAction(
storeActions.SAVE_PAYMENT_METHOD_CHOICE,
paymentMethods.AFTERPAY,
false
);
const iframe = this.$refs.paymentFrame;
if (iframe) {
iframe.contentWindow.postMessage("afterpay", "*");
window.scrollTo({ top: 0, left: 0, behavior: "smooth" });
}
},
setIFrameListener() {
window.addEventListener("message", (event) =>
this.handleIFrameContentWindowMessage(event)
@ -820,6 +899,7 @@ export default {
loadingModal,
cart,
alert,
buttonQuestion,
},
};
</script>

View file

@ -292,7 +292,7 @@ export default {
})
).toFixed(2);
if (this.hasPricingByDay()) {
if (this.hasPricingByDay() && !this.isAfterpayBreakoutDisplay()) {
return "$" + formattedPriceFloat;
}
return (this.isInsuranceSelected ? "As little as $" : "$") + formattedPriceFloat;