519 lines
No EOL
26 KiB
TypeScript
519 lines
No EOL
26 KiB
TypeScript
import { expect, type Locator, type Page } from '@playwright/test';
|
||
import { BasePage } from './BasePage';
|
||
import { IPaymentDetails, Soft, waitUntil } from 'safelite-playwright-core';
|
||
import { AppointmentTimeslot, ServiceLocation, PaymentType, ServicePackage, VehicleDamage, } from 'safelite-playwright-core';
|
||
import { PaymentMethod, ProgressBarPercentages } from 'framework/localTypes/Enums';
|
||
import { PaymentPage } from './PaymentPage';
|
||
import { AfterpayPage } from './AfterpayPage';
|
||
import { PaypalPage } from './PaypalPage';
|
||
import { ITestData } from 'framework/TestData';
|
||
import { step } from 'framework/localTypes/Step';
|
||
|
||
export class PaymentMethodPage extends BasePage {
|
||
readonly page: Page;
|
||
readonly payAtServiceButton: Locator;
|
||
// readonly paypalButton: Locator;
|
||
// readonly creditCardButton: Locator;
|
||
readonly payNowButton: Locator;
|
||
readonly payWithInsuranceButton: Locator;
|
||
readonly payInFourButton: Locator;
|
||
readonly amountDueTextField: Locator;
|
||
readonly amountDueDropDown: Locator;
|
||
readonly appointmentDetailsSection: Locator;
|
||
readonly appointmentDetailsDropdown: Locator;
|
||
readonly subtotalAmountTextField: Locator;
|
||
readonly submitButton: Locator;
|
||
readonly recalibrationCheckbox: Locator;
|
||
readonly paymentPage: PaymentPage;
|
||
readonly paypalPage: PaypalPage;
|
||
readonly afterPayBreakoutSection: Locator;
|
||
readonly afterPayToggle: Locator;
|
||
|
||
// Payment detail page validation locators
|
||
readonly reviewTable: Locator;
|
||
readonly serviceDetailsSection: Locator;
|
||
readonly serviceLocationDateandTimeSection: Locator;
|
||
readonly vehicleDamageLocationsSection: Locator;
|
||
readonly contactDetailsSection: Locator;
|
||
readonly cartPanelDetails: Locator;
|
||
readonly subtotalText: Locator;
|
||
readonly finalAmountDueText: Locator;
|
||
readonly glassServiceText: Locator;
|
||
readonly wiperBladesText: Locator;
|
||
readonly rainDefenseText: Locator;
|
||
readonly deductibleText: Locator;
|
||
|
||
constructor(page: Page) {
|
||
super(page);
|
||
this.page = page;
|
||
this.payAtServiceButton = this.page.locator('[buttonlabel="Pay at my appointment"]'); //this.page.getByText('Pay at time of service');
|
||
this.amountDueTextField = this.page.getByLabel('expand cart').locator('.amount-due');
|
||
this.amountDueDropDown = this.page.getByLabel('expand cart');
|
||
this.appointmentDetailsSection = this.page.locator('div .appt-details-snapshot');
|
||
this.appointmentDetailsDropdown = this.page.getByLabel('expand appointment details');
|
||
this.payWithInsuranceButton = this.page.locator('div').filter({ hasText: /^Pay with insurance$/ }).nth(1);
|
||
this.subtotalAmountTextField = this.page.locator('.sub-total span').nth(1);
|
||
this.payNowButton = this.page.locator('[buttonlabel="Pay now"]');
|
||
this.payInFourButton = this.page.locator('[buttonlabel="Pay in 4 installments"]');
|
||
this.submitButton = this.page.locator('[data-test-id="nav-bar-main-button"]');
|
||
this.recalibrationCheckbox = this.page.locator('label:has(>input[name=\'recalAckOptIn\'])');
|
||
// this.creditCardButton = page.locator('div').filter({ hasText: /^Credit or Debit$/ }).nth(1);
|
||
this.paymentPage = new PaymentPage(page);
|
||
this.paypalPage = new PaypalPage(page);
|
||
|
||
// Payment details validation locators
|
||
this.reviewTable = this.page.locator('div.review-table');
|
||
|
||
// Section locators - find by heading text
|
||
this.serviceLocationDateandTimeSection = this.page.locator('.review-table').locator('div .service-location');
|
||
this.vehicleDamageLocationsSection = this.page.locator('.review-table').locator('div.py-3[damagelocationswidgetname="DamageLocationsWidget"]');
|
||
this.contactDetailsSection = this.page.locator('.review-table').locator('div', { hasText: 'Contact details' }).first();
|
||
|
||
// Cart panel elements
|
||
this.cartPanelDetails = this.page.locator('.cart-panel');
|
||
this.subtotalText = this.page.locator('.sub-total');
|
||
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™$/ });
|
||
this.deductibleText = this.page.locator('#deductible-value');
|
||
this.afterPayBreakoutSection = this.page.locator('div.alert-info:has(.after-pay)');
|
||
this.afterPayToggle = this.page.locator('.after-pay-toggle');
|
||
}
|
||
|
||
async validatePaymentDetailsPage(testData: Partial<ITestData>) {
|
||
// Destructure data we use
|
||
const { vehicleDetails, customerDetails, servicePackage,
|
||
flow, claimDetails, paymentDetails, appointmentDetails,
|
||
isUseVehicleOnPolicy, paymentMethod, vehicleDamage } = testData;
|
||
|
||
// Wait for review table to be visible to ensure page is loaded
|
||
await this.appointmentDetailsDropdown.waitFor({ state: "visible" });
|
||
await this.appointmentDetailsDropdown.click().then(
|
||
async () => {
|
||
await waitUntil(async () => {
|
||
return (await this.page.locator('.review-toggle.expanded').evaluate(el => window.getComputedStyle(el, ':after').transform)) === 'matrix(-1, 0, 0, -1, 0, 0)';
|
||
});
|
||
}
|
||
);
|
||
|
||
let expectedAppointmentDetails = new Map<string, string[]>();
|
||
expectedAppointmentDetails = await this.getExpectedServiceLocationDateAndTimeSection(testData, expectedAppointmentDetails);
|
||
expectedAppointmentDetails = await this.getExpectedVehicleDamageAndVehicle(testData, expectedAppointmentDetails);
|
||
expectedAppointmentDetails = await this.getExpectedContactDetails(testData, expectedAppointmentDetails);
|
||
expectedAppointmentDetails = await this.getExpectedTextServiceUpdatesOptInOrOut(testData, expectedAppointmentDetails);
|
||
|
||
let actualAppointmentDetails = await this.getActualAppointmentDetails();
|
||
for (const key in expectedAppointmentDetails) {
|
||
Soft.expect(actualAppointmentDetails[key]?.map(item => item.toLowerCase()))
|
||
.toEqual(expectedAppointmentDetails[key].map(item => item.toLowerCase()));
|
||
}
|
||
|
||
// Expand cart to see all details
|
||
await this.amountDueDropDown.click();
|
||
await this.reviewTable.waitFor({ state: "visible" });
|
||
|
||
// Cart Validation
|
||
// Get pricing information from cart panel
|
||
if (await this.subtotalText.isVisible()) {
|
||
const subtotalValue = await this.subtotalText.innerText();
|
||
const finalAmountDueValue = await this.finalAmountDueText.innerText();
|
||
|
||
// Pricing validations differ by payment method
|
||
if (paymentMethod === PaymentMethod.SelfPay && paymentDetails?.paymentType !== PaymentType.PayWithInsurance) {
|
||
// Extract amounts for self-pay customers
|
||
const subtotalAmount = this.extractAmount(subtotalValue);
|
||
const finalAmountDueAmount = this.extractAmount(finalAmountDueValue);
|
||
|
||
// Subtotal should be greater than 0
|
||
Soft.expect(subtotalAmount).toBeGreaterThan(0);
|
||
|
||
// Final amount differs based on payment type
|
||
if (paymentDetails?.paymentType === PaymentType.PayAtService) {
|
||
Soft.expect(finalAmountDueAmount).toBeGreaterThan(0);
|
||
} else if (paymentDetails?.paymentType === PaymentType.Credit ||
|
||
paymentDetails?.paymentType === PaymentType.Paypal ||
|
||
paymentDetails?.paymentType === PaymentType.AfterPay) {
|
||
// For payment types that charge immediately, amount due could be 0
|
||
// This logic might need adjusting based on actual business rules
|
||
}
|
||
} else {
|
||
// For insurance payments, check for proper indicators
|
||
// TODO: Apply logic for deductible and insurance payment logic
|
||
}
|
||
|
||
// Service package validations
|
||
const servicePackageValue = await this.cartPanelDetails.textContent();
|
||
await Soft.expect(servicePackageValue).toContain(`${servicePackage}`)
|
||
if (servicePackage === ServicePackage.Premium || servicePackage === ServicePackage.Standard) {
|
||
Soft.expect(servicePackageValue).toContain('New wiper blades');
|
||
}
|
||
if (servicePackage === ServicePackage.Premium) {
|
||
Soft.expect(servicePackageValue).toContain('Rain Repel Treatment');
|
||
}
|
||
|
||
// Promo Code Validation
|
||
if (paymentDetails!.promoCode) {
|
||
expect.soft(servicePackageValue).toContain(`Promo code ${paymentDetails!.promoCode} applied`);
|
||
};
|
||
|
||
// Early Bird line item validation
|
||
if (appointmentDetails?.appointmentTimeSlot == AppointmentTimeslot.EarlyBird) {
|
||
|
||
Soft.expect(servicePackageValue).toContain('Early bird');
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Helper method to extract numeric amount from price strings like "$123.45"
|
||
*/
|
||
private extractAmount(valueString: string): number {
|
||
const matches = valueString.match(/\$([0-9,]+(\.[0-9]{2})?)/);
|
||
if (matches && matches[1]) {
|
||
return parseFloat(matches[1].replace(/,/g, ''));
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
async getAmountDue(): Promise<string> {
|
||
await expect(this.amountDueTextField).toBeVisible();
|
||
const text = await this.amountDueTextField.textContent();
|
||
if (!text) throw new Error("Amount due text is empty");
|
||
return text;
|
||
}
|
||
|
||
async getSubtotal(): Promise<string> {
|
||
await this.amountDueDropDown.click();
|
||
await expect(this.subtotalAmountTextField).toBeVisible();
|
||
const text = await this.subtotalAmountTextField.textContent();
|
||
if (!text) throw new Error("Subtotal text is empty");
|
||
return text;
|
||
}
|
||
|
||
async executePayment(paymentDetails: IPaymentDetails, isRecalVehicle: boolean) {
|
||
const browserContext = this.page.context();
|
||
|
||
switch (paymentDetails.paymentType) {
|
||
case PaymentType.Credit:
|
||
await this.selectCreditCard();
|
||
await this.nextPage();
|
||
await this.paymentPage.populateCreditCardDetails(paymentDetails);
|
||
break;
|
||
case PaymentType.AfterPay:
|
||
await this.payInFourButton.click();
|
||
await this.continueButton.click();
|
||
|
||
// Capture popup
|
||
const afterpayPopup = await browserContext.waitForEvent('page');
|
||
const afterpayPage = new AfterpayPage(afterpayPopup);
|
||
|
||
// Execute payment
|
||
await afterpayPage.executeAfterpayPayment(paymentDetails);
|
||
break;
|
||
case PaymentType.Paypal:
|
||
await this.selectPaypal();
|
||
// TODO: Click paypal button
|
||
await this.nextPage();
|
||
await this.paymentPage.navigateToPaypalCheckout();
|
||
await this.paypalPage.completePaypalPurchase(paymentDetails);
|
||
break;
|
||
case PaymentType.PayAtService:
|
||
await this.selectPayAtService(isRecalVehicle);
|
||
await this.nextPage();
|
||
break;
|
||
case PaymentType.PayWithInsurance:
|
||
await this.payWithInsuranceButton.click();
|
||
await this.nextPage();
|
||
break;
|
||
default:
|
||
console.error('PaymentMethodPage >> Logic for this payment method unimplemented');
|
||
break;
|
||
}
|
||
}
|
||
|
||
async selectPaypal() {
|
||
await this.payNowButton.click();
|
||
}
|
||
l
|
||
async selectCreditCard() {
|
||
await this.payNowButton.click();
|
||
}
|
||
|
||
async selectPayAtService(isRecalVehicle: boolean) {
|
||
if (await this.payAtServiceButton.isVisible()) {
|
||
await this.payAtServiceButton.click();
|
||
} else if (isRecalVehicle) {
|
||
await this.recalibrationCheckbox.click();
|
||
}
|
||
|
||
}
|
||
|
||
async verifyVAPS(): Promise<void> {
|
||
// Get Vuex state from localStorage
|
||
const vuexState = JSON.parse(await this.page.evaluate('localStorage.getItem(\'vuex\')'));
|
||
|
||
// Validate the count of FRONT WIPER parts
|
||
if (vuexState.order?.lineItems?.vaps?.length > 0) {
|
||
const frontWiperPartsCount = vuexState.order.lineItems.vaps.filter(vap =>
|
||
vap.partType === "FRONT WIPER"
|
||
).length;
|
||
|
||
await expect(frontWiperPartsCount).toBe(2);
|
||
} else {
|
||
throw new Error("No VAPS found in the order");
|
||
}
|
||
}
|
||
|
||
async getActualAppointmentDetails(): Promise<any> {
|
||
let actualAppointmentDetails = new Map<string, string[]>();
|
||
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();
|
||
//added to trim the text to remove any leading or trailing spaces (example: "expert installation " to "expert installation")
|
||
let value = (await element.locator('.review-block-content').allInnerTexts() as string[]).map(item => item.trim());
|
||
actualAppointmentDetails[label] = value;
|
||
}
|
||
return actualAppointmentDetails;
|
||
}
|
||
|
||
async getExpectedVehicleDetails(testdata: Partial<ITestData>, expectedServicePackageDetails: Map<string, string[]>): Promise<any> {
|
||
const { vehicleDetails } = testdata;
|
||
expectedServicePackageDetails["Vehicle"] = [vehicleDetails?.year + " " + vehicleDetails?.make + " " + vehicleDetails?.model];
|
||
return expectedServicePackageDetails;
|
||
}
|
||
|
||
async getExpectedVehicleDamageAndVehicle(testdata: Partial<ITestData>, expectedServicePackageDetails: Map<string, string[]>): Promise<any> {
|
||
|
||
const { vehicleDetails, vehicleDamage } = testdata;
|
||
|
||
let vehicleDamageText: string = '';
|
||
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.WindshieldOneChip || item == VehicleDamage.WindshieldTwoChips || item == VehicleDamage.WindshieldThreeChips)) {
|
||
vehicleDamageText = "Repair the windshield of your";
|
||
}
|
||
|
||
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.WindshieldCrack)) {
|
||
vehicleDamageText = vehicleDamage?.length > 1 ? "Replace the Windshield" : "Replace the windshield";
|
||
}
|
||
|
||
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.DriverVentGlass) && !vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.WindshieldCrack)) {
|
||
vehicleDamageText.includes("Replace") ? vehicleDamageText += ", Driver Vent Glass" : "Replace the Driver Vent Glass";
|
||
}
|
||
|
||
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.DriverFrontDoor)) {
|
||
vehicleDamageText.includes("Replace") ? vehicleDamageText += ", Driver Front Glass" : "Replace the Driver Front Glass";
|
||
}
|
||
|
||
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.DriverRearDoor)) {
|
||
vehicleDamageText.includes("Replace") ? vehicleDamageText += ", Driver Back Glass" : "Replace the Driver Back Glass";
|
||
}
|
||
|
||
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.DriverVentGlass) && vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.WindshieldCrack)) {
|
||
vehicleDamageText.includes("Replace") ? vehicleDamageText += ", Driver Vent Glass" : "Replace the Driver Vent Glass";
|
||
}
|
||
|
||
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.DriverQuarterPanel)) {
|
||
vehicleDamageText.includes("Replace") ? vehicleDamageText += ", Driver Quarter Glass" : "Replace the Driver Quarter Glass";
|
||
}
|
||
|
||
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.PassengerVentGlass)) {
|
||
vehicleDamageText.includes("Replace") ? vehicleDamageText += ", Passenger Vent Glass" : "Replace the Passenger Vent Glass";
|
||
}
|
||
|
||
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.PassengerFrontDoor)) {
|
||
vehicleDamageText.includes("Replace") ? vehicleDamageText += ", Passenger Front Glass" : "Replace the Passenger Front Glass";
|
||
}
|
||
|
||
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.PassengerRearDoor)) {
|
||
vehicleDamageText.includes("Replace") ? vehicleDamageText += ", Passenger Back Glass" : "Replace the Passenger Back Glass";
|
||
}
|
||
|
||
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.PassengerQuarterPanel)) {
|
||
vehicleDamageText.includes("Replace") ? vehicleDamageText += ", Passenger Quarter Glass" : "Replace the Passenger Quarter Glass";
|
||
}
|
||
|
||
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.RearWindow || item == VehicleDamage.RearSliding)) {
|
||
vehicleDamageText.includes("Replace") ? vehicleDamageText += ", Back Glass" : "Replace the Back Glass";
|
||
}
|
||
|
||
// Replace last comma with " and" if there are multiple damages
|
||
if (vehicleDamageText.includes(",") && vehicleDamageText !== "Replace the windshield") {
|
||
vehicleDamageText = vehicleDamageText.replace(/,([^,]*)$/, " and$1") + " of your";
|
||
}
|
||
|
||
expectedServicePackageDetails[vehicleDamageText] = [vehicleDetails?.year + " " + vehicleDetails?.make + " " + vehicleDetails?.model];
|
||
return expectedServicePackageDetails;
|
||
}
|
||
|
||
async expectedServicePackageDetails(testData: Partial<ITestData>, expectedServicePackageDetails: Map<string, string[]>): Promise<any> {
|
||
const { servicePackage } = testData;
|
||
|
||
let localStorage = JSON.parse(await this.page.evaluate('localStorage.getItem(\'vuex\')'));
|
||
let isRepair = localStorage.order.damage.isRepair as boolean;
|
||
let isInsurance = localStorage.order.payment.isInsurance as boolean;
|
||
let hasNonWindshieldGlass: boolean = false;
|
||
if (!isRepair) {
|
||
hasNonWindshieldGlass = localStorage.order.lineItems.glassParts.find((item: any) => item.partType !== "WINDSHIELD") ? true : false;
|
||
}
|
||
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 && canSafeliteRecalibrate
|
||
let stringForRepair: string[] = ["Expert windshield repair", "Exclusive resin sealant", "Nationwide lifetime guarantee"];
|
||
let stringIfRecal = isRepair
|
||
? ""
|
||
: recalRequired
|
||
? " and recalibration"
|
||
: "";
|
||
let stringForReplace: string[] = [hasNonWindshieldGlass ? "New replacement glass" : "New replacement windshield", "Expert installation" + `${stringIfRecal}`, "Nationwide lifetime warranty"];
|
||
switch (servicePackage) {
|
||
case ServicePackage.GlassOnly:
|
||
expectedServicePackageDetails["Glass service only"] = isRepair
|
||
? stringForRepair
|
||
: stringForReplace;
|
||
break;
|
||
case ServicePackage.Standard:
|
||
stringForRepair.push("New wiper blades");
|
||
stringForReplace.push("New wiper blades");
|
||
expectedServicePackageDetails["Standard service"] = isRepair
|
||
? stringForRepair
|
||
: stringForReplace;
|
||
break;
|
||
case ServicePackage.Premium:
|
||
stringForRepair.push("New wiper blades", "Rain repel treatment");
|
||
stringForReplace.push("New wiper blades", "Rain repel treatment");
|
||
expectedServicePackageDetails["Premium service"] = isRepair
|
||
? stringForRepair
|
||
: stringForReplace;
|
||
break;
|
||
}
|
||
return expectedServicePackageDetails;
|
||
}
|
||
|
||
async getExpectedServiceLocationDateAndTimeSection(testData: Partial<ITestData>, expectedServicePackageDetails: Map<string, string[]>): Promise<any> {
|
||
const { customerDetails, appointmentDetails } = testData;
|
||
let serviceLocationTitle = appointmentDetails?.serviceLocation == ServiceLocation.Mobile
|
||
? "We're coming to you"
|
||
: "You're coming to us";
|
||
let serviceLocationText = appointmentDetails?.serviceLocation == ServiceLocation.Mobile
|
||
? appointmentDetails?.serviceAddress
|
||
? appointmentDetails.serviceAddress.street + ", " + appointmentDetails.serviceAddress.city + ", " + appointmentDetails.serviceAddress.state + " " + appointmentDetails.serviceAddress.postalCode
|
||
: ""
|
||
: appointmentDetails?.shopAddress
|
||
? appointmentDetails.shopAddress
|
||
: "";
|
||
serviceLocationText += " on "
|
||
serviceLocationText += customerDetails?.apptDate ? await this.getFormattedAppointmentDate(customerDetails.apptDate) + " " + customerDetails?.apptTime?.replace("-", "—") : "";
|
||
|
||
expectedServicePackageDetails[serviceLocationTitle] = [serviceLocationText];
|
||
return expectedServicePackageDetails;
|
||
}
|
||
|
||
async getExpectedVehicleDamageLocations(testData: Partial<ITestData>, expectedServicePackageDetails: Map<string, string[]>): Promise<any> {
|
||
const { customerDetails } = testData;
|
||
let VehicleDamageLocationsText: string[] = [];
|
||
if (VehicleDamage.WindshieldCrack) {
|
||
let VehicleDamageLocationsText: string[] = [];
|
||
VehicleDamageLocationsText.push("Replace the windshield");
|
||
};
|
||
|
||
expectedServicePackageDetails["VehicleDamageLocationsText"] = VehicleDamageLocationsText;
|
||
return expectedServicePackageDetails;
|
||
|
||
}
|
||
|
||
async getExpectedContactDetails(testData: Partial<ITestData>, expectedServicePackageDetails: Map<string, string[]>): Promise<any> {
|
||
const { customerDetails } = testData;
|
||
let customerDetailsText: string[] = [];
|
||
|
||
customerDetailsText.push(
|
||
// customerDetails?.firstName.toUpperCase() + " " + customerDetails?.lastName.toUpperCase(),
|
||
customerDetails?.email ? customerDetails?.email.toUpperCase() : "",
|
||
customerDetails?.phoneNumber ? customerDetails?.phoneNumber : "",
|
||
);
|
||
|
||
expectedServicePackageDetails["Contact details"] = customerDetailsText
|
||
return expectedServicePackageDetails;
|
||
}
|
||
|
||
async getExpectedTextServiceUpdatesOptInOrOut(testData: Partial<ITestData>, expectedServicePackageDetails: Map<string, string[]>): Promise<any> {
|
||
const { customerDetails } = testData;
|
||
|
||
let isOptedInForTextMessages = testData.isOptedInForTextMessages ?? false;
|
||
|
||
let expectedText = isOptedInForTextMessages ? customerDetails?.phoneNumber : "Not opted in";
|
||
|
||
|
||
expectedServicePackageDetails["We’ll text service updates to"] = [expectedText];
|
||
return expectedServicePackageDetails;
|
||
}
|
||
|
||
async ValidateAfterPayBreakOutSection() {
|
||
/*const vuexState = JSON.parse(await this.page.evaluate('localStorage.getItem(\'vuex\')'));
|
||
|
||
const isInsurance = vuexState.order.payment.isInsurance;
|
||
const currentDeductible = vuexState.order.policy.currentDeductible;
|
||
const isVerified = vuexState.order.payment.insuranceCoverage.isVerified;*/
|
||
|
||
const amountDueText = await this.amountDueTextField.first().textContent();
|
||
|
||
const verifyAfterpayBreakout = amountDueText?.includes('$')
|
||
? Number.parseFloat(amountDueText.replace(/[^0-9.]/g, '')) > 0
|
||
: false
|
||
|
||
if (verifyAfterpayBreakout) {
|
||
const afterPayAmountString = await this.afterPayBreakoutSection.locator('.afterpay-amount').textContent();
|
||
const afterPayAmount = Number.parseFloat(afterPayAmountString!.replace(/[^0-9.]/g, ''));
|
||
Soft.expect(afterPayAmount, `AfterPayAmount (${afterPayAmount}) is > 0`).toBeGreaterThan(0);
|
||
await this.afterPayToggle.click().then(
|
||
async () => {
|
||
await waitUntil(async () => {
|
||
return (await this.page.locator('.after-pay-toggle.expanded').evaluate(el => window.getComputedStyle(el, ':after').transform)) === 'matrix(-1, 0, 0, -1, 0, 0)';
|
||
});
|
||
}
|
||
);
|
||
const afterPayCards = await this.page.locator('.after-pay-details .payment-card').all();
|
||
Soft.expect(afterPayCards.length, `There are 4 afterpay cards`).toBe(4);
|
||
for (const afterPayCard of afterPayCards) {
|
||
const afterPayAmountWithinCardString = await afterPayCard.locator('.amount-due').textContent();
|
||
const afterPayAmountWithinCard = Number.parseFloat(afterPayAmountWithinCardString!.replace(/[^0-9.]/g, ''));
|
||
Soft.expect(afterPayAmountWithinCard, `AfterPayAmount (${afterPayAmountWithinCard}) > 0`).toBeGreaterThan(0);
|
||
}
|
||
}
|
||
}
|
||
|
||
async getFormattedAppointmentDate(appointmentDate: string) {
|
||
|
||
// Parse the original date
|
||
let parsedAppointmentDate = new Date(`${appointmentDate}` + 'T00:00:00');
|
||
|
||
// Format the new date as a string
|
||
let updatedAppointmentDate = parsedAppointmentDate.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
|
||
return updatedAppointmentDate;
|
||
}
|
||
|
||
@step("PaymentMethodPage >> Select Payment Method: ")
|
||
async handlePaymentMethodPage(testData: Partial<ITestData>) {
|
||
const { servicePackage, isRecalVehicle, paymentDetails } = testData;
|
||
|
||
await this.validateProgressBar(ProgressBarPercentages.PaymentMethodPage);
|
||
await this.validatePaymentDetailsPage(testData);
|
||
await this.ValidateAfterPayBreakOutSection();
|
||
await this.validateOEMPart(testData.isOEM!);
|
||
|
||
// Verify VAPS wipers on backend for standard and premium packages
|
||
if (servicePackage === ServicePackage.Standard || servicePackage === ServicePackage.Premium) {
|
||
await this.verifyVAPS();
|
||
}
|
||
if (paymentDetails?.paymentType) {
|
||
await this.executePayment(paymentDetails!, isRecalVehicle!);
|
||
} else {
|
||
await this.nextPage();
|
||
}
|
||
}
|
||
} |