Removes the explicit URL declaration from the page classes. The URL validation and navigation are now handled directly within the tests, providing greater flexibility and control over test execution and removing the need to manage URLs within each page object.
493 lines
No EOL
25 KiB
TypeScript
493 lines
No EOL
25 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;
|
|
|
|
// Payment detail page validation locators
|
|
readonly reviewTable: Locator;
|
|
readonly vehicleSection: Locator;
|
|
readonly damageSection: Locator;
|
|
readonly serviceDetailsSection: Locator;
|
|
readonly serviceLocationSection: Locator;
|
|
readonly appointmentDateSection: 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.getByLabel('I understand after windshield');
|
|
// 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.vehicleSection = this.page.locator('.review-table').locator('div', { hasText: 'Vehicle' }).first();
|
|
this.damageSection = this.page.locator('.review-table').locator('div', { hasText: 'Damage' }).first();
|
|
this.serviceDetailsSection = this.page.locator('.review-table').locator('div', { hasText: 'Glass service only' }).first();
|
|
this.serviceLocationSection = this.page.locator('.review-table').locator('div', { hasText: "We're coming to you" }).first();
|
|
this.appointmentDateSection = this.page.locator('.review-table').locator('div', { hasText: 'Appointment date + time' }).first();
|
|
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');
|
|
}
|
|
|
|
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.getExpectedVehicleDetails(testData, expectedAppointmentDetails);
|
|
expectedAppointmentDetails = await this.getExpectedVehicleDamage(testData, expectedAppointmentDetails);
|
|
expectedAppointmentDetails = await this.expectedServicePackageDetails(testData, expectedAppointmentDetails);
|
|
expectedAppointmentDetails = await this.getExpectedServiceLocation(testData, expectedAppointmentDetails);
|
|
expectedAppointmentDetails = await this.getExpectedAppointmentDate(testData, expectedAppointmentDetails);
|
|
expectedAppointmentDetails = await this.getExpectedCustomerDetails(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);
|
|
await this.nextPage();
|
|
break;
|
|
case PaymentType.AfterPay:
|
|
await this.payInFourButton.click();
|
|
await this.nextPage();
|
|
|
|
// 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 getExpectedVehicleDamage(testdata: Partial<ITestData>, expectedServicePackageDetails: Map<string, string[]>): Promise<any> {
|
|
|
|
const {vehicleDamage} = testdata;
|
|
|
|
let vehicleDamageText: string[] = [];
|
|
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.WindshieldOneChip)) {
|
|
vehicleDamageText.push("Windshield repair - 1 chip");
|
|
}
|
|
|
|
else if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.WindshieldTwoChips)) {
|
|
vehicleDamageText.push("Windshield repair - 2 chips");
|
|
}
|
|
|
|
else if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.WindshieldThreeChips)) {
|
|
vehicleDamageText.push("Windshield repair - 3 chips");
|
|
}
|
|
|
|
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.WindshieldCrack)) {
|
|
vehicleDamageText.push("Windshield crack");
|
|
}
|
|
|
|
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.DriverVentGlass)) {
|
|
vehicleDamageText.includes("Side door - Driver side") ? vehicleDamageText[vehicleDamageText.length - 1 ] += "\nVent glass" : vehicleDamageText.push("Side door - Driver side", "Vent glass");
|
|
}
|
|
|
|
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.DriverFrontDoor)) {
|
|
vehicleDamageText.includes("Side door - Driver side") ? vehicleDamageText[vehicleDamageText.length - 1 ] += "\nFront door" : vehicleDamageText.push("Side door - Driver side", "Front door");
|
|
}
|
|
|
|
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.DriverRearDoor)) {
|
|
vehicleDamageText.includes("Side door - Driver side") ? vehicleDamageText[vehicleDamageText.length - 1 ] += "\nBack door" : vehicleDamageText.push("Side door - Driver side", "Back door");
|
|
}
|
|
|
|
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.DriverQuarterPanel)) {
|
|
vehicleDamageText.includes("Side door - Driver side") ? vehicleDamageText[vehicleDamageText.length - 1 ] += "\nQuarter panel" : vehicleDamageText.push("Side door - Driver side", "Quarter panel");
|
|
}
|
|
|
|
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.PassengerVentGlass)) {
|
|
vehicleDamageText.includes("Side door - Passenger side") ? vehicleDamageText[vehicleDamageText.length - 1 ] += "\nVent glass" : vehicleDamageText.push("Side door - Passenger side", "Vent glass");
|
|
}
|
|
|
|
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.PassengerFrontDoor)) {
|
|
vehicleDamageText.includes("Side door - Passenger side") ? vehicleDamageText[vehicleDamageText.length - 1 ] += "\nFront door" : vehicleDamageText.push("Side door - Passenger side", "Front door");
|
|
}
|
|
|
|
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.PassengerRearDoor)) {
|
|
vehicleDamageText.includes("Side door - Passenger side") ? vehicleDamageText[vehicleDamageText.length - 1 ] += "\nBack door" : vehicleDamageText.push("Side door - Passenger side", "Back door");
|
|
}
|
|
|
|
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.PassengerQuarterPanel)) {
|
|
vehicleDamageText.includes("Side door - Passenger side") ? vehicleDamageText[vehicleDamageText.length - 1 ] += "\nQuarter panel" : vehicleDamageText.push("Side door - Passenger side", "Quarter panel");
|
|
}
|
|
|
|
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.RearWindow || item == VehicleDamage.RearSliding)) {
|
|
vehicleDamageText.push("Rear window");
|
|
}
|
|
|
|
expectedServicePackageDetails["Damage"] = vehicleDamageText;
|
|
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 getExpectedServiceLocation(testData: Partial<ITestData>, expectedServicePackageDetails: Map<string, string[]>): Promise<any> {
|
|
const { appointmentDetails } = testData;
|
|
let serviceLocationTitle = appointmentDetails?.serviceLocation == ServiceLocation.Mobile
|
|
? "We're coming to you"
|
|
: "You're going to a Safelite shop";
|
|
let serviceLocation: string[] = [];
|
|
|
|
serviceLocation.push(
|
|
appointmentDetails?.serviceLocation == ServiceLocation.Mobile
|
|
? appointmentDetails?.serviceAddress
|
|
? appointmentDetails.serviceAddress.street + ", " + appointmentDetails.serviceAddress.city + ", " + appointmentDetails.serviceAddress.state + " " + appointmentDetails.serviceAddress.postalCode
|
|
: ""
|
|
: appointmentDetails?.shopAddress
|
|
? appointmentDetails.shopAddress
|
|
: ""
|
|
);
|
|
|
|
expectedServicePackageDetails[serviceLocationTitle] = serviceLocation;
|
|
return expectedServicePackageDetails;
|
|
}
|
|
|
|
async getExpectedAppointmentDate(testData: Partial<ITestData>, expectedServicePackageDetails: Map<string, string[]>): Promise<any> {
|
|
const { customerDetails, appointmentDetails } = testData;
|
|
let appointmentDateText: string[] = [];
|
|
const isMobileAppointment = appointmentDetails?.serviceLocation == ServiceLocation.Mobile
|
|
if (isMobileAppointment) {
|
|
let localStorage= JSON.parse(await this.page.evaluate('localStorage.getItem(\'vuex\')'));
|
|
let jobMinMinutes = localStorage.order.schedule.jobMinMinutes as number;
|
|
let jobMaxMinutes = localStorage.order.schedule.jobMaxMinutes as number;
|
|
|
|
if (jobMinMinutes < 60) {
|
|
customerDetails!.apptDuration = `${jobMinMinutes} - ${jobMaxMinutes} minutes`;
|
|
} else {
|
|
const minHours = Math.floor(jobMinMinutes / 60);
|
|
const maxHours = Math.floor(jobMaxMinutes / 60);
|
|
customerDetails!.apptDuration = `${minHours} - ${maxHours} hours`;
|
|
}
|
|
}
|
|
|
|
appointmentDateText.push(
|
|
customerDetails?.apptDate ? await this.getFormattedAppointmentDate(customerDetails.apptDate) + " " + customerDetails?.apptTime?.replace("-", "—") : "",
|
|
customerDetails?.apptDuration ? "Estimated appointment length: " + customerDetails.apptDuration : ""
|
|
);
|
|
|
|
expectedServicePackageDetails["Appointment date + time"] = appointmentDateText;
|
|
return expectedServicePackageDetails;
|
|
}
|
|
|
|
async getExpectedCustomerDetails(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 : "",
|
|
"Opted out of text message updates"
|
|
);
|
|
|
|
expectedServicePackageDetails["Contact details"] = customerDetailsText;
|
|
return expectedServicePackageDetails;
|
|
}
|
|
|
|
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);
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
} |