Merge branch 'develop' into feature/CASH-2472

This commit is contained in:
mvalaiyapathi 2026-03-18 10:18:02 -04:00 committed by GitHub
commit 25929ed80c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 151 additions and 123 deletions

View file

@ -10,6 +10,7 @@ SKIP_CONTENT_SITE="false"
# Experiments Flag
IS_MOBILEFIRST="false"
IS_ADYENPAYMENTS="false"
IS_MULTILOCATIONPOPUP="false"
# Base URLs by environment
# qa

View file

@ -10,6 +10,7 @@ SKIP_CONTENT_SITE="false"
# Experiments Flag
IS_MOBILEFIRST="false"
IS_ADYENPAYMENTS="false"
IS_MULTILOCATIONPOPUP="false"
# Base URLs by environment
# qa

View file

@ -18,5 +18,6 @@ export function getDefaultExperimentsData(): IExperiments {
return {
isAdyenPayments: !!process.env.IS_ADYENPAYMENTS && process.env.IS_ADYENPAYMENTS !== "" ? process.env.IS_ADYENPAYMENTS === "true" : false,
isMobileFirst: !!process.env.IS_MOBILEFIRST && process.env.IS_MOBILEFIRST !== "" ? process.env.IS_MOBILEFIRST === "true" : false,
isMultiLocationPopup: !!process.env.IS_MULTILOCATIONPOPUP && process.env.IS_MULTILOCATIONPOPUP !== "" ? process.env.IS_MULTILOCATIONPOPUP === "true" : false,
}
}

View file

@ -1,4 +1,5 @@
export interface IExperiments {
isMobileFirst: boolean,
isAdyenPayments: boolean
isAdyenPayments: boolean,
isMultiLocationPopup: boolean
}

View file

@ -257,8 +257,12 @@ export class BasePage {
: "MobileFirstAppointment=MobileFirstAppt_V1=MobileFirstAppt_CONTROL=true";
experimentsURLExtension += experiments?.isAdyenPayments
? ",Adyen%20Payments=Adyen%20Payment%20Test=Adyen%20Payment%20(Test)"
: ",Adyen%20Payments=Adyen%20Payment%20Test=CyberSource%20(Control)";
? ",Adyen%20Payments=Adyen%20Payment%20Test=Adyen%20Payment%20(Test)=true"
: ",Adyen%20Payments=Adyen%20Payment%20Test=CyberSource%20(Control)=true";
experimentsURLExtension += experiments?.isMultiLocationPopup
? ",MultiLocationPopup=MultiLocationPopup_V1=MultiLocationPopup_TEST=true"
: ",MultiLocationPopup=MultiLocationPopup_V1=MultiLocationPopup_CONTROL=true";
} else {
console.log("Url extension without query string");
}

View file

@ -0,0 +1,81 @@
import { type Locator, type Page } from '@playwright/test';
import { BasePage } from './BasePage';
import { IPaymentDetails } from 'safelite-playwright-core';
import { ITestData } from 'framework/TestData';
import { PaypalPage } from './PaypalPage';
export class PaymentAdyenPage extends BasePage {
readonly page: Page;
readonly creditOrDebitCardButton: Locator;
readonly nameOnCardTextField: Locator;
readonly cardNumberTextField: Locator;
readonly expiryDateTextField: Locator;
readonly cvvTextField: Locator;
readonly countryDropDown: Locator;
readonly billingAddressTextField: Locator;
readonly cityTextField: Locator;
readonly stateDropDown: Locator;
readonly billingZipTextField: Locator;
readonly submitPaymentButton: Locator;
readonly payPalButton: Locator;
readonly navigateToPaypalButton: Locator;
readonly afterPayButton: Locator;
readonly navigateToAfterPayButton: Locator;
constructor(page: Page) {
super(page);
this.page = page;
this.creditOrDebitCardButton = page.locator('.adyen-checkout__payment-method--credit button');
this.cardNumberTextField = page.frameLocator('iframe[title="Iframe for card number"]').locator('input[id*="adyen-checkout-encryptedCardNumber"]');
this.expiryDateTextField = page.frameLocator('iframe[title="Iframe for expiry date"]').locator('input[id*="adyen-checkout-encryptedExpiryDate"]');
this.cvvTextField = page.frameLocator('iframe[title="Iframe for security code"]').locator('input[id*="adyen-checkout-encryptedSecurityCode"]');
this.nameOnCardTextField = page .locator('input[id*="adyen-checkout-holderName"]');
this.countryDropDown = page.locator('input[id*="adyen-checkout-country"]');
this.billingAddressTextField = page.locator('input[id*="adyen-checkout-street"]');
this.cityTextField = page.locator('input[id*="adyen-checkout-city"]');
this.stateDropDown = page.locator('input[id*="adyen-checkout-stateOrProvince"]');
this.billingZipTextField = page.locator('input[id*="adyen-checkout-postalCode"]');
this.submitPaymentButton = page.locator('button.adyen-checkout__button');
this.payPalButton = page.locator('button[id*="button-paypal"]');
this.navigateToPaypalButton = page.frameLocator('iframe[title="PayPal-paypal"]:first-of-type').locator('div[role="link"][class*="paypal-button"]');
this.afterPayButton = page.locator('button[id*="button-redirect"]');
this.navigateToAfterPayButton = page.locator('div.adyen-checkout__payment-method--afterpaytouch_US button.adyen-checkout__button');
}
async populateAdyenCreditCardDetails(testData: Partial<ITestData>){
const { paymentDetails, customerDetails } = testData;
paymentDetails!.cardNumber = '5100 0600 0000 0002';
paymentDetails!.expirationMonth = "12 - December";
paymentDetails!.expirationYear = "2029";
paymentDetails!.cvv = "737";
await this.creditOrDebitCardButton.click();
await this.cardNumberTextField.fill(paymentDetails!.cardNumber || '');
const expirationDate = `${(paymentDetails!.expirationMonth!.split(' ')[0] || '').padStart(2, '0')} + ${paymentDetails!.expirationYear!.toString().slice(-2)}`;
await this.expiryDateTextField.pressSequentially(expirationDate);
await this.cvvTextField.fill(paymentDetails!.cvv!);
await this.nameOnCardTextField.fill(customerDetails!.firstName! + ' ' + customerDetails!.lastName!);
await this.billingAddressTextField.fill(paymentDetails!.billingAddress!.street);
await this.countryDropDown.pressSequentially("United States");
await this.countryDropDown.click();
await this.page.getByRole('option', { name: 'United States', exact: true }).click();
await this.cityTextField.fill(paymentDetails!.billingAddress!.city);
await this.stateDropDown.pressSequentially(paymentDetails!.billingAddress!.state);
await this.stateDropDown.click();
await this.page.locator(".adyen-checkout__field--stateOrProvince li").nth(0).click();
await this.billingZipTextField.fill(paymentDetails!.billingAddress!.postalCode);
await this.submitPaymentButton.click();
}
async navigateToAdyenPaypalCheckout(): Promise<PaypalPage> {
await this.payPalButton.click();
const paypalPage = this.page.waitForEvent('popup');
await this.navigateToPaypalButton.click();
return new PaypalPage(await paypalPage);
}
}

View file

@ -8,6 +8,8 @@ import { AfterpayPage } from './AfterpayPage';
import { PaypalPage } from './PaypalPage';
import { ITestData } from 'framework/TestData';
import { step } from 'framework/localTypes/Step';
import { IExperiments } from 'framework/localTypes/IExperiments';
import { PaymentAdyenPage } from './PaymentAdyenPage';
export class PaymentMethodPage extends BasePage {
readonly page: Page;
@ -25,7 +27,9 @@ export class PaymentMethodPage extends BasePage {
readonly submitButton: Locator;
readonly recalibrationCheckbox: Locator;
readonly paymentPage: PaymentPage;
readonly paymentAdyenPage: PaymentAdyenPage;
readonly paypalPage: PaypalPage;
readonly afterpayPage: AfterpayPage
readonly afterPayBreakoutSection: Locator;
readonly afterPayToggle: Locator;
@ -59,7 +63,9 @@ export class PaymentMethodPage extends BasePage {
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.paymentAdyenPage = new PaymentAdyenPage(page);
this.paypalPage = new PaypalPage(page);
this.afterpayPage = new AfterpayPage(page);
// Payment details validation locators
this.reviewTable = this.page.locator('div.review-table');
@ -191,35 +197,57 @@ export class PaymentMethodPage extends BasePage {
return text;
}
async executePayment(paymentDetails: IPaymentDetails, isRecalVehicle: boolean) {
async executePayment(testData: Partial<ITestData>) {
const browserContext = this.page.context();
const { paymentDetails, isRecalVehicle, experiments } = testData;
switch (paymentDetails.paymentType) {
switch (paymentDetails!.paymentType) {
case PaymentType.Credit:
await this.selectCreditCard();
await this.nextPage();
await this.paymentPage.populateCreditCardDetails(paymentDetails);
experiments!.isAdyenPayments
? await this.paymentAdyenPage.populateAdyenCreditCardDetails(testData)
: 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);
if (experiments?.isAdyenPayments) {
await this.paymentAdyenPage.afterPayButton.click();
await this.paymentAdyenPage.navigateToAfterPayButton.click();
await this.afterpayPage.executeAfterpayPayment(paymentDetails!);
} else {
// Capture popup
const afterpayPopup = await browserContext.waitForEvent('page');
const afterpayPage = new AfterpayPage(afterpayPopup);
// Execute payment
await afterpayPage.executeAfterpayPayment(paymentDetails!);
}
// 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);
const paypalPage = experiments?.isAdyenPayments
? await this.paymentAdyenPage.navigateToAdyenPaypalCheckout()
: await this.paymentPage.navigateToPaypalCheckout();
/*if (experiments?.isAdyenPayments) {
// Capture popup
const payPalPopup = await this.page.waitForEvent('popup');
const paypalPage = new PaypalPage(payPalPopup);
paypalPage.completePaypalPurchase(testData);
} else {
await this.paypalPage.completePaypalPurchase(testData);
}*/
await paypalPage.completePaypalPurchase(testData);
break;
case PaymentType.PayAtService:
await this.selectPayAtService(isRecalVehicle);
await this.selectPayAtService(isRecalVehicle!);
await this.nextPage();
break;
case PaymentType.PayWithInsurance:
@ -527,7 +555,7 @@ export class PaymentMethodPage extends BasePage {
@step("PaymentMethodPage >> Select Payment Method: ")
async handlePaymentMethodPage(testData: Partial<ITestData>) {
const { servicePackage, isRecalVehicle, paymentDetails, isForcedOEM } = testData;
const { servicePackage, isRecalVehicle, paymentDetails, isForcedOEM, experiments } = testData;
await this.validateProgressBar(ProgressBarPercentages.PaymentMethodPage);
await this.validatePaymentDetailsPage(testData);
@ -542,7 +570,8 @@ export class PaymentMethodPage extends BasePage {
await this.verifyVAPS();
}
if (paymentDetails?.paymentType) {
await this.executePayment(paymentDetails!, isRecalVehicle!);
// await this.executePayment(paymentDetails!, isRecalVehicle!, experiments!);
await this.executePayment(testData);
} else {
await this.nextPage();
}

View file

@ -1,6 +1,7 @@
import { type Locator, type Page } from '@playwright/test';
import { BasePage } from './BasePage';
import { IPaymentDetails } from 'safelite-playwright-core';
import { PaypalPage } from './PaypalPage';
export class PaymentPage extends BasePage {
readonly page: Page;
@ -43,8 +44,8 @@ export class PaymentPage extends BasePage {
await this.submitPaymentButton.click();
}
async navigateToPaypalCheckout() {
async navigateToPaypalCheckout(): Promise<PaypalPage> {
await this.payPalButton.click();
return new PaypalPage(this.page);
}
}

View file

@ -1,6 +1,6 @@
import { expect, type Locator, type Page } from '@playwright/test';
import { BasePage } from './BasePage';
import { IPaymentDetails } from 'safelite-playwright-core';
import { ITestData } from 'framework/TestData';
export class PaypalPage extends BasePage {
readonly page: Page;
@ -30,24 +30,28 @@ export class PaypalPage extends BasePage {
this.tryAnotherWayButton = page.getByRole('button', { name: 'Try another way' });
}
async completePaypalPurchase(paymentDetails: IPaymentDetails){
async completePaypalPurchase(testData: Partial<ITestData>){
const {paymentDetails, experiments } = testData;
// Wait for the PayPal login page to load
await this.page.waitForFunction(() => window.location.href.includes('sandbox.paypal.com'), null, { timeout: 10000 });
// handle flow with and without the "Log in with a password instead" link
if (await this.loginWithPasswordButton.isVisible()) {
await this.loginWithPasswordButton.click();
await this.passwordTextBox.fill(paymentDetails.password!);
await this.passwordTextBox.fill(paymentDetails!.password!);
await this.paypalLoginButton.click();
await this.completePurchaseButton.click();
} else {
await this.usernameTextBox.waitFor({ state: 'visible' });
await this.page.screenshot({ path: `test-results\\ortoni-data\\paypal-username-${Date.now()}.png`, fullPage: true });
await this.usernameTextBox.fill(paymentDetails.username!);
await this.usernameTextBox.fill(paymentDetails!.username!);
await this.nextButton.click();
await this.tryAnotherWayButton.click();
await this.usePasswordInsteadButton.click();
await this.passwordTextBox.fill(paymentDetails.password!);
if (!experiments!.isAdyenPayments){
await this.tryAnotherWayButton.click();
await this.usePasswordInsteadButton.click();
}
await this.passwordTextBox.fill(paymentDetails!.password!);
await this.paypalLoginButton.click();
await this.payWithRadioButton.click();
await this.page.waitForTimeout(2000);

View file

@ -19,8 +19,6 @@ const experimentSettings = {
PIA_INSURANCE: "DisplayPIAInsurance",
SUBMIT_ORDER_ENABLE_PIA: "SubmitOrder_Enable_PIA",
IS_EMAIL_OPTIONAL: "isEmailOptional",
SERVICE_PACKAGE_DISCOUNT: "OfferServicePackageDiscount",
PROMO_ON_PACKAGE: "Offer_Promo_On_Pkg",
RECAL_PRICE_REMOVE: "RecalPriceRemove",
INSURANCE_TAB_TO_DISPLAY_THRESHOLD_INTERNAL: "NextGen_InternalInsuranceTabDisplayThreshold",
INSURANCE_TAB_TO_DISPLAY_THRESHOLD_EXTERNAL: "NextGen_ExternalInsuranceTabDisplayThreshold",

View file

@ -9,7 +9,6 @@ const partTypeStrings = {
MOBILE_FEE: "MOBILE FEE",
REPAIR_FEE: "REPAIR FEE",
EARLY_BIRD: "EARLY BIRD",
SERVICE_PACKAGE_DISCOUNT: "SERVICE PACKAGE DISCOUNT",
QUOTE_PAGE_DISCOUNT: "QUOTE PAGE DISCOUNT",
DONATION: "DONATION",
WINDSHIELD: "WINDSHIELD",

View file

@ -100,9 +100,6 @@ const mockExperimentSettings = experimentSettings;
jest.mock("@/mixins/experiment-mixin.js", () => ({
methods: {
getSettingValue(settingName) {
if (settingName === mockExperimentSettings.SERVICE_PACKAGE_DISCOUNT) {
return true;
}
if (
settingName === mockExperimentSettings.INSURANCE_TAB_TO_DISPLAY_THRESHOLD_INTERNAL
) {
@ -367,9 +364,6 @@ describe("quote.vue", () => {
vehicle: {
cardId: "123",
},
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
};
const { wrapper } = setupMocks({});
@ -391,59 +385,6 @@ describe("quote.vue", () => {
// This should have its own test
//expect(vm.isInsuranceSelected !== null).toBe(true);
});
test("Returns true if service package discount setting is true", async () => {
//Arrange
store.getters = {
lineItems: {
glassParts: ["item", "item2"],
},
pageData: jest.fn((page) => {
if (page === "quote") {
return { saveProgressPopupSkipped: true };
}
return {};
}),
applicationUser: {
experiments: [],
},
order: {
lineItems: {
glassParts: ["item", "item2"],
},
payment: {},
customer: {
emailAddress: "test@test.com",
},
serviceLocation: {
zipCode: "12345",
zipCodeCtu: "value",
},
},
vehicle: {
cardId: "123",
},
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
};
const { wrapper } = setupMocks({});
wrapper.vm.$route = { query: { isInsurance: "false" } };
const isServicePackageDiscount = experimentMixin.methods.getSettingValue(
experimentSettings.SERVICE_PACKAGE_DISCOUNT
);
//Act
await quote.beforeRouteEnter.call(
wrapper.vm,
{ query: { fmgPage: "quote" } },
undefined,
(c) => c(wrapper.vm)
);
//Assert
expect(isServicePackageDiscount).toBe(true);
});
test("should default to insurance if query param 'isInsurance' is true", async () => {
//Arrange
store.getters = {
@ -476,9 +417,6 @@ describe("quote.vue", () => {
vehicle: {
cardId: "123",
},
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
};
const { wrapper } = setupMocks({});
wrapper.vm.$route = { query: { isInsurance: "true" } };
@ -526,9 +464,6 @@ describe("quote.vue", () => {
vehicle: {
cardId: "123",
},
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
};
mockTierOnePrice = 200;
const { wrapper } = setupMocks({});
@ -577,9 +512,6 @@ describe("quote.vue", () => {
vehicle: {
cardId: "123",
},
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
};
const { wrapper } = setupMocks({});
// Ensure that query param isn't overriding selection
@ -628,9 +560,6 @@ describe("quote.vue", () => {
vehicle: {
cardId: "123",
},
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
};
const { wrapper } = setupMocks({});
// Ensure that query param isn't overriding selection
@ -680,9 +609,6 @@ describe("quote.vue", () => {
vehicle: {
cardId: "123",
},
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
};
mockTierOnePrice = 200;
const { wrapper } = setupMocks({});
@ -734,9 +660,6 @@ describe("quote.vue", () => {
vehicle: {
cardId: "123",
},
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
};
mockTierOnePrice = 505;
const { wrapper } = setupMocks({});
@ -786,9 +709,6 @@ describe("quote.vue", () => {
vehicle: {
cardId: "123",
},
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
};
mockTierOnePrice = 505;
const { wrapper } = setupMocks({});
@ -839,9 +759,6 @@ describe("quote.vue", () => {
vehicle: {
cardId: "123",
},
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
};
const { wrapper } = setupMocks({});
wrapper.vm.$route = { query: null };
@ -962,9 +879,6 @@ describe("quote.vue", () => {
vehicle: {
cardId: "123",
},
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
};
// Set up the component

View file

@ -31,14 +31,10 @@ const mockExperimentSettings = experimentSettings;
jest.mock("@/mixins/experiment-mixin.js", () => ({
methods: {
getSettingValue(settingName) {
if (settingName === mockExperimentSettings.PROMO_ON_PACKAGE) {
return true;
}
return false;
},
hasSetting(settingName) {
if (settingName === mockExperimentSettings.PROMO_ON_PACKAGE) {
return false;
}
return false;
},
hasSettingEqualTo(settingName, settingValue) {
return false;

View file

@ -807,9 +807,7 @@ export default {
coupons = promoString;
}
const additionalDiscounts = submittedOrder.lineItems?.supportingItems?.find(
(x) =>
x.partType == partTypeStrings.SERVICE_PACKAGE_DISCOUNT ||
x.partType == partTypeStrings.QUOTE_PAGE_DISCOUNT
(x) => x.partType == partTypeStrings.QUOTE_PAGE_DISCOUNT
);
if (isDefined(additionalDiscounts)) {
if (coupons) {