Refactors the date picker component to support pricing by day experiment, including new styling and logic for displaying prices. Adds a progress bar to the funnel header to visually represent the user's progress through the workflow. Updates dependencies including axios, and adds axios mock adapter and retry. Adds new icons and updates styling for various UI components. Improves logic for handling heavy trucks and closest shops.
235 lines
No EOL
9.6 KiB
TypeScript
235 lines
No EOL
9.6 KiB
TypeScript
import { expect, type Locator, type Page } from '@playwright/test';
|
|
import { BasePage } from './BasePage';
|
|
import { ServicePackage, VehicleDamage } from '@business-logic/types/Enums';
|
|
import { PaymentMethod } from '@business-logic/types/Enums';
|
|
import { step } from '@business-logic/types/Step';
|
|
import { ITestData } from '@business-logic/types/ITestData';
|
|
|
|
export class ServicePackagesPage extends BasePage {
|
|
readonly page: Page;
|
|
readonly standardPackageButton: Locator;
|
|
readonly premiumPackageButton: Locator;
|
|
readonly glassOnlyButton: Locator;
|
|
readonly payOnMyOwnButton: Locator;
|
|
readonly paywithInsuranceButton: Locator;
|
|
readonly iHavePromoCodeButton: Locator;
|
|
|
|
//Your quote is almost ready modal
|
|
readonly skipQuoteEmailButton: Locator;
|
|
readonly emailInput: Locator;
|
|
readonly getMyQuoteButton: Locator;
|
|
readonly closeButton: Locator;
|
|
url = process.env['BASE_URL']! + '/fmg/?fmgPage=quote';
|
|
|
|
//Enter a promo code modal
|
|
readonly promoCodeTextbox: Locator;
|
|
readonly applyPromoButton: Locator;
|
|
|
|
readonly repeatedClicksModalCloseButton: Locator;
|
|
|
|
|
|
constructor(page: Page) {
|
|
super(page);
|
|
this.page = page;
|
|
this.standardPackageButton = this.page.getByText('Standard');
|
|
this.premiumPackageButton = this.page.getByText('Premium');
|
|
this.glassOnlyButton = this.page.getByText('Glass service only', { exact: true });
|
|
this.payOnMyOwnButton = this.page.locator('label').filter({ hasText: 'Pay on my own' }).locator('div');
|
|
this.paywithInsuranceButton = this.page.locator('label').filter({ hasText: 'Pay with insurance' }).locator('div');
|
|
this.iHavePromoCodeButton = this.page.getByRole('link', { name: 'I have a promo code' });
|
|
this.skipQuoteEmailButton = this.page.getByRole('button', { name: 'Skip' });
|
|
this.emailInput = this.page.getByRole('textbox', { name: 'Enter your email address' });
|
|
this.getMyQuoteButton = this.page.getByRole('button', { name: 'Get my quote' });
|
|
this.closeButton = this.page. getByRole('dialog').locator('button').filter({ hasText: 'Close' });
|
|
this.promoCodeTextbox = this.page.getByLabel('Enter a promo code');
|
|
this.applyPromoButton = this.page.getByRole('button', { name: 'Apply promo code' });
|
|
this.repeatedClicksModalCloseButton = this.page.locator('.QSISlider').locator('img[src*=\'close\']');
|
|
|
|
// this.validateURL(this.url);
|
|
}
|
|
|
|
async selectPaymentMethod(method: PaymentMethod): Promise<void> {
|
|
const locators = {
|
|
[PaymentMethod.Insurance]: this.paywithInsuranceButton,
|
|
[PaymentMethod.SelfPay]: this.payOnMyOwnButton
|
|
}
|
|
|
|
await locators[method].click();
|
|
}
|
|
|
|
async selectServicePackage(servicePackage: ServicePackage): Promise<void> {
|
|
const locators = {
|
|
[ServicePackage.GlassOnly]: this.glassOnlyButton,
|
|
[ServicePackage.Premium]: this.premiumPackageButton,
|
|
[ServicePackage.Standard]: this.standardPackageButton,
|
|
}
|
|
|
|
if (servicePackage != null) {
|
|
await locators[servicePackage].click();
|
|
}
|
|
}
|
|
|
|
async handleQuotePopup(email?: string): Promise<void> {
|
|
await this.emailInput.waitFor({ state: 'visible' });
|
|
if (await this.emailInput.isVisible()) {
|
|
if (email) {
|
|
await this.emailInput.fill(email);
|
|
await this.getMyQuoteButton.click();
|
|
if (await this.repeatedClicksModalCloseButton.isVisible()) {
|
|
await this.repeatedClicksModalCloseButton.click();
|
|
}
|
|
await this.page.waitForTimeout(3000);
|
|
await this.closeButton.click();
|
|
|
|
} else {
|
|
await this.skipQuoteEmailButton.click();
|
|
}
|
|
}
|
|
}
|
|
|
|
async enterPromo(promoCode: string): Promise<void> {
|
|
await this.iHavePromoCodeButton.click();
|
|
await this.promoCodeTextbox.fill(promoCode);
|
|
await this.applyPromoButton.click();
|
|
}
|
|
|
|
async verifyCanNotRecal(): Promise<boolean> {
|
|
// Get Vuex state from localStorage
|
|
const vuexState = JSON.parse(await this.page.evaluate('localStorage.getItem(\'vuex\')'));
|
|
|
|
// Validate in the backend to make sure the can safelite recalibrate data is correct
|
|
if (vuexState.order?.lineItems?.glassParts?.length > 0) {
|
|
for (const glassPart of vuexState.order.lineItems.glassParts) {
|
|
await expect(glassPart.canSafeliteRecalibrate).toBe(false);
|
|
await expect(glassPart.requiresRecalibration).toBe(true);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
async verifyDynamicRecal(): Promise<void> {
|
|
// Get Vuex state from localStorage
|
|
const vuexState = JSON.parse(await this.page.evaluate('localStorage.getItem(\'vuex\')'));
|
|
|
|
// Validate in the backend to make sure the dynamic recalibration part is present
|
|
if (vuexState.order?.lineItems?.glassParts?.length > 0) {
|
|
const hasDynamicRecalPart = vuexState.order.lineItems.glassParts.some(glassPart =>
|
|
glassPart.childParts.some(childPart =>
|
|
childPart.partNumber.includes("RECAL DYNAMIC")
|
|
)
|
|
);
|
|
|
|
await expect(hasDynamicRecalPart).toBe(true);
|
|
console.log("Recal part line item is verified");
|
|
} else {
|
|
throw new Error("No glass parts found in the order");
|
|
}
|
|
}
|
|
|
|
async verifyIsRepair(isRepair: boolean, vehicleDamage: VehicleDamage[]): Promise<void> {
|
|
// Get Vuex state from localStorage
|
|
const vuexState = JSON.parse(await this.page.evaluate('localStorage.getItem(\'vuex\')'));
|
|
|
|
// Validate in the backend to make sure isRepair is aligned with the data
|
|
if (isRepair) {
|
|
await expect(vuexState.order.damage.isRepair).toBe(true);
|
|
|
|
// Check the number of chips based on vehicleDamage array
|
|
if (vehicleDamage.includes(VehicleDamage.WindshieldOneChip)) {
|
|
await expect(vuexState.order.damage.numberOfChips).toBe(1);
|
|
}
|
|
if (vehicleDamage.includes(VehicleDamage.WindshieldTwoChips)) {
|
|
await expect(vuexState.order.damage.numberOfChips).toBe(2);
|
|
}
|
|
if (vehicleDamage.includes(VehicleDamage.WindshieldThreeChips)) {
|
|
await expect(vuexState.order.damage.numberOfChips).toBe(3);
|
|
}
|
|
} else {
|
|
await expect(vuexState.order.damage.isRepair).toBe(false);
|
|
}
|
|
}
|
|
|
|
|
|
async verifyVehicleParts(vehicleDamage: VehicleDamage[]): Promise<void> {
|
|
// Get Vuex state from localStorage
|
|
const vuexState = JSON.parse(await this.page.evaluate('localStorage.getItem(\'vuex\')'));
|
|
|
|
// Validate the presence of specific parts in the glassParts array
|
|
const glassParts = vuexState.order?.lineItems?.glassParts;
|
|
|
|
if (glassParts?.length > 0) {
|
|
for (const partType of vehicleDamage) {
|
|
const hasPartType = glassParts.some(glassPart => glassPart.partType === partType);
|
|
await expect(hasPartType, `Expected part type: ${partType}`).toBe(true);
|
|
}
|
|
} else {
|
|
throw new Error("No glass parts found in the order");
|
|
}
|
|
}
|
|
|
|
async verifyOEMPart(): Promise<void> {
|
|
// Get Vuex state from localStorage
|
|
const vuexState = JSON.parse(await this.page.evaluate('localStorage.getItem(\'vuex\')'));
|
|
|
|
// Validate the presence of an OEM part
|
|
if (vuexState.order?.lineItems?.glassParts?.length > 0) {
|
|
const firstGlassPartNumber = vuexState.order.lineItems.glassParts[0].partNumber;
|
|
|
|
await expect(firstGlassPartNumber.includes("OEM")).toBe(true);
|
|
} else {
|
|
throw new Error("No glass parts found in the order");
|
|
}
|
|
}
|
|
|
|
@step("ServicePackagePage >> Select Payment Method and Service Type: ")
|
|
async handleServicePackagePage(testData: Partial<ITestData>) {
|
|
const { customerDetails, paymentMethod, servicePackage, promoCode, canNotRecal, dynamicRecal, hasOemEndorsement, vehicleDamage } = testData;
|
|
|
|
// Define repair damage types (vs. replacement types)
|
|
const repairTypes: VehicleDamage[] = [
|
|
VehicleDamage.WindshieldOneChip,
|
|
VehicleDamage.WindshieldTwoChips,
|
|
VehicleDamage.WindshieldThreeChips
|
|
];
|
|
|
|
// Determine if we're replacing or repairing
|
|
const isReplace = !repairTypes.some(damageType => {
|
|
return vehicleDamage!.includes(damageType);
|
|
});
|
|
|
|
await this.handleQuotePopup(customerDetails!.email!);
|
|
await this.selectPaymentMethod(paymentMethod!);
|
|
await this.selectServicePackage(servicePackage!);
|
|
|
|
// Enter promo code
|
|
if (promoCode) {
|
|
await this.enterPromo(promoCode);
|
|
}
|
|
|
|
// Backend Validations
|
|
// Validate backend for can not recal if applicable
|
|
if (canNotRecal) {
|
|
await this.verifyCanNotRecal();
|
|
}
|
|
|
|
// Validate backend for dynamic recal if applicable
|
|
if (dynamicRecal) {
|
|
await this.verifyDynamicRecal();
|
|
}
|
|
|
|
// Validate backend for repair info (including chip verification)
|
|
await this.verifyIsRepair(!isReplace, vehicleDamage!);
|
|
if (isReplace) {
|
|
// Validate backend for parts info
|
|
await this.verifyVehicleParts(vehicleDamage!);
|
|
}
|
|
|
|
// Validate backend for OEM endorsement
|
|
if (hasOemEndorsement) {
|
|
await this.verifyOEMPart();
|
|
}
|
|
|
|
await this.nextPage();
|
|
}
|
|
} |