Merge pull request #3101 from Safelite/feature/CASH-1252

adding adyen payment flow for playwright tests
This commit is contained in:
kpatel8hs4io 2026-03-16 17:34:40 -04:00 committed by GitHub
commit f9dfea40b3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 148 additions and 25 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);