Merge branch 'CASH-631' into kpatel/Misc_Fix
This commit is contained in:
commit
b6d7c014ea
28 changed files with 243 additions and 48 deletions
|
|
@ -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
|
||||
|
|
@ -28,6 +28,7 @@ const defaultAfterpayDetails: IPaymentDetails = {
|
|||
|
||||
const defaultPaypalDetails: IPaymentDetails = {
|
||||
paymentType: PaymentType.Paypal,
|
||||
username: 'itqatest@safelite.com',
|
||||
password: 'Safelite1'
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -124,5 +124,11 @@ export enum AppointmentType{
|
|||
DropOff = "Drop-off"
|
||||
}
|
||||
|
||||
export enum AppointmentTimeslot{
|
||||
EarlyBird = "EarlyBird",
|
||||
DropOff = "DropOff",
|
||||
overnight = "Overnight"
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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}`);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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!);
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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!);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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!);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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!);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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!);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 || {};
|
||||
|
||||
|
|
|
|||
|
|
@ -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!);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue