Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add playwright tests dualBranding and avs #2952

Merged
merged 5 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 46 additions & 2 deletions packages/e2e-playwright/models/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ class Address {
readonly rootElement: Locator;
readonly rootElementSelector: string;

constructor(public readonly page: Page, rootElementSelector: string = '.adyen-checkout__fieldset--billingAddress') {
constructor(
public readonly page: Page,
rootElementSelector: string = '.adyen-checkout__fieldset--billingAddress'
) {
this.rootElement = page.locator(rootElementSelector);
this.rootElementSelector = rootElementSelector;
}
Expand All @@ -13,8 +16,12 @@ class Address {
return this.rootElement.getByRole('combobox', { name: /country\/region/i });
}

get stateError() {
return this.rootElement.locator('.adyen-checkout__field--stateOrProvince').locator('.adyen-checkout-contextual-text--error');
}

get streetInput() {
return this.rootElement.getByRole('textbox', { name: /street/i });
return this.rootElement.locator('.adyen-checkout__input--street');
}

get streetInputError() {
Expand All @@ -29,19 +36,33 @@ class Address {
return this.rootElement.getByRole('textbox', { name: /city/i });
}

get cityError() {
return this.rootElement.locator('.adyen-checkout__field--city').locator('.adyen-checkout-contextual-text--error');
}

get postalCodeInput() {
return this.rootElement.getByRole('textbox', { exact: false, name: /code/i }); // US uses 'Zip Code', the rest uses 'Postal Code';
}

get postalCodeError() {
return this.rootElement.locator('.adyen-checkout__field--postalCode').locator('.adyen-checkout-contextual-text--error');
}

get stateInput() {
return this.rootElement.getByRole('combobox', { name: /state/i });
}

get addressSearchInput() {
return this.rootElement.getByRole('combobox', { name: /address/i });
}

async fillInPostCode(postCode: string) {
await this.postalCodeInput.waitFor({ state: 'visible' });
await this.postalCodeInput.fill(postCode);
}

async selectState(options: { name?: RegExp | string }) {
await this.stateInput.waitFor({ state: 'visible' });
await this.stateInput.click();
await this.rootElement.getByRole('option', options).click();
}
Expand All @@ -50,6 +71,29 @@ class Address {
await this.countrySelector.click();
await this.rootElement.getByRole('option', options).click();
}

async fillInStreet(street: string) {
await this.streetInput.waitFor({ state: 'visible' });
await this.streetInput.fill(street);
}

async fillInHouseNumber(houseNumber: string) {
await this.houseNumberInput.fill(houseNumber);
}

async fillInCity(city: string) {
await this.cityInput.waitFor({ state: 'visible' });
await this.cityInput.fill(city);
}

async searchAddressAndChooseTheFirst(query: string) {
await this.addressSearchInput.click();
await this.addressSearchInput.fill(query);
await this.rootElement
.getByRole('option', { name: new RegExp(query, 'i') })
.first()
.click();
}
}

export { Address };
2 changes: 1 addition & 1 deletion packages/e2e-playwright/models/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export abstract class Base {
await this.isComponentVisible();
}

async pay(options: { name?: RegExp | string } = { name: /Pay/i }): Promise<void> {
async pay(options: { name?: RegExp | string } = { name: /pay/i }): Promise<void> {
if (this.payButton) {
await this.payButton.click();
} else {
Expand Down
19 changes: 19 additions & 0 deletions packages/e2e-playwright/models/bcmc.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,29 @@
import { Card } from './card';

class BCMC extends Card {
get brands() {
return this.cardNumberField.locator('.adyen-checkout__card__cardNumber__brandIcon').all();
}

async waitForVisibleDualBrands() {
return await this.page.waitForFunction(
expectedLength => [...document.querySelectorAll('.adyen-checkout__card__cardNumber__brandIcon')].length === expectedLength,
2
);
}

async isComponentVisible() {
await this.cardNumberInput.waitFor({ state: 'visible' });
await this.expiryDateInput.waitFor({ state: 'visible' });
}
async selectBrand(
text: string | RegExp,
options?: {
exact?: boolean;
}
) {
await this.cardNumberField.getByAltText(text, options).click();
}
}

export { BCMC };
4 changes: 0 additions & 4 deletions packages/e2e-playwright/models/card-avs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ class CardWithAvs extends Card {
super(page);
this.billingAddress = new Address(page);
}

async fillInPostCode(postCode: string) {
await this.billingAddress.fillInPostCode(postCode);
}
}

export { CardWithAvs };
4 changes: 4 additions & 0 deletions packages/e2e-playwright/models/card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { USER_TYPE_DELAY } from '../tests/utils/constants';
import LANG from '../../server/translations/en-US.json';
import { Base } from './base';
import { URL_MAP } from '../fixtures/URL_MAP';
import { ThreeDs2Challenge } from './threeds2Challenge';

const CARD_IFRAME_TITLE = LANG['creditCard.encryptedCardNumber.aria.iframeTitle'];
const EXPIRY_DATE_IFRAME_TITLE = LANG['creditCard.encryptedExpiryDate.aria.iframeTitle'];
Expand Down Expand Up @@ -43,6 +44,7 @@ class Card extends Base {
readonly revolvingPaymentLabel: Locator;
readonly installmentsDropdown: Locator;
readonly selectorList: Locator;
readonly threeDs2Challenge: ThreeDs2Challenge;

constructor(
public readonly page: Page,
Expand Down Expand Up @@ -103,6 +105,8 @@ class Card extends Base {
this.revolvingPaymentLabel = this.rootElement.getByText(REVOLVING_PAYMENT);
this.installmentsDropdown = this.rootElement.locator('.adyen-checkout__dropdown__button');
this.selectorList = this.rootElement.getByRole('listbox');

this.threeDs2Challenge = new ThreeDs2Challenge(page);
}

get availableBrands() {
Expand Down
3 changes: 2 additions & 1 deletion packages/e2e-playwright/models/dropin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ class Dropin extends Base {
return this.pmList.locator(`.adyen-checkout__payment-method:has-text("${pmLabel}")`);
}

// Non stored payment methods
async selectPaymentMethod(pmType: string) {
const pmLabel = this.paymentMethods.find((pm: { type: string }) => pm.type === pmType).name;
this.page.getByRole('radio', { name: pmLabel }).check();
this.page.locator('.adyen-checkout__payment-methods-list--otherPayments').getByRole('radio', { name: pmLabel }).check();
}

async saveDetails() {
Expand Down
34 changes: 34 additions & 0 deletions packages/e2e-playwright/models/threeds2Challenge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Locator, Page } from '@playwright/test';

class ThreeDs2Challenge {
readonly rootElement: Locator;

constructor(
public readonly page: Page,
rootElementSelector: string = '.adyen-checkout__threeds2__challenge'
) {
this.rootElement = page.locator(rootElementSelector);
}

get threeDSIframe() {
return this.page.locator('iframe[name="threeDSIframe"]').contentFrame();
}

get passwordInput() {
return this.threeDSIframe.getByLabel('Password');
}

get submitButton() {
return this.threeDSIframe.locator('#buttonSubmit');
}

async fillInPassword(password: string) {
await this.passwordInput.fill(password);
}

async submit() {
await this.submitButton.click();
}
}

export { ThreeDs2Challenge };
127 changes: 80 additions & 47 deletions packages/e2e-playwright/tests/e2e/card/avs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,81 +3,114 @@ import { PAYMENT_RESULT, REGULAR_TEST_CARD, TEST_CVC_VALUE, TEST_DATE_VALUE, TES
import { CardWithAvs } from '../../../models/card-avs';
import { URL_MAP } from '../../../fixtures/URL_MAP';

const fullAvsWithoutPrefilledDataUrl = '/iframe.html?args=componentConfiguration.data:!undefined&globals=&id=cards-card--with-avs&viewMode=story';
const fullAvsWithPrefilledDataUrl = '/iframe.html?globals=&args=&id=cards-card--with-avs&viewMode=story';
const addressLookupUrl = '/iframe.html?id=cards-card--with-avs-address-lookup&viewMode=story';
longyulongyu marked this conversation as resolved.
Show resolved Hide resolved

type Fixture = {
cardAvsPage: CardWithAvs;
cardWithAvs: CardWithAvs;
};

const test = base.extend<Fixture>({
cardAvsPage: async ({ page }, use) => {
const cardAvcPage = new CardWithAvs(page);
await cardAvcPage.goto(URL_MAP.cardWithPartialAvs);
await use(cardAvcPage);
cardWithAvs: async ({ page }, use) => {
await use(new CardWithAvs(page));
}
});

test.describe('Card payments with address lookup', () => {});
test.describe('Card payments with address lookup', () => {
test('should make a successful card payment', async ({ cardWithAvs }) => {
await cardWithAvs.goto(addressLookupUrl);
await cardWithAvs.fillCardNumber(REGULAR_TEST_CARD);
await cardWithAvs.fillExpiryDate(TEST_DATE_VALUE);
await cardWithAvs.fillCvc(TEST_CVC_VALUE);

await cardWithAvs.billingAddress.searchAddressAndChooseTheFirst('1');
await cardWithAvs.pay();
await cardWithAvs.paymentResult.waitFor({ state: 'visible' });
await expect(cardWithAvs.paymentResult).toContainText(PAYMENT_RESULT.authorised);
});
});

test.describe('Card payments with partial avs', () => {
test.describe('When fill in a valid the post code', () => {
test('should make a successful card payment', async ({ cardAvsPage }) => {
await cardAvsPage.fillCardNumber(REGULAR_TEST_CARD);
await cardAvsPage.fillExpiryDate(TEST_DATE_VALUE);
await cardAvsPage.fillCvc(TEST_CVC_VALUE);
await cardAvsPage.fillInPostCode(TEST_POSTCODE);
await cardAvsPage.pay();
await cardAvsPage.paymentResult.waitFor({ state: 'visible' });
await expect(cardAvsPage.paymentResult).toContainText(PAYMENT_RESULT.authorised);
test('should make a successful card payment', async ({ cardWithAvs }) => {
await cardWithAvs.goto(URL_MAP.cardWithPartialAvs);
await cardWithAvs.fillCardNumber(REGULAR_TEST_CARD);
await cardWithAvs.fillExpiryDate(TEST_DATE_VALUE);
await cardWithAvs.fillCvc(TEST_CVC_VALUE);
await cardWithAvs.billingAddress.fillInPostCode(TEST_POSTCODE);
await cardWithAvs.pay();
await cardWithAvs.paymentResult.waitFor({ state: 'visible' });
await expect(cardWithAvs.paymentResult).toContainText(PAYMENT_RESULT.authorised);
});
});

test.describe('When fill in an invalid post code ', () => {
// in the fixture, do not provide country code
test('should not submit the payment', async ({ page }) => {
// fill in card number
// fill in expiry date
// fill in cvc
// fill in post code
// click pay btn
// expect to see error message under the post code field
test.describe('When not fill in a post code ', () => {
test('should not submit the payment', async ({ cardWithAvs }) => {
await cardWithAvs.goto(URL_MAP.cardWithPartialAvs);
await cardWithAvs.fillCardNumber(REGULAR_TEST_CARD);
await cardWithAvs.fillExpiryDate(TEST_DATE_VALUE);
await cardWithAvs.fillCvc(TEST_CVC_VALUE);
await cardWithAvs.pay();
await expect(cardWithAvs.billingAddress.postalCodeError).toContainText('Enter the zip code');
});
});
});

test.describe('Card payments with full avs', () => {
test.describe('When fill in the valid address data', () => {
test('should make a successful card payment', async ({ page }) => {
// fill in card number
// fill in expiry date
// fill in cvc
// fill in address ...
// click pay btn
// expect to see success result
test('should make a successful card payment', async ({ cardWithAvs }) => {
await cardWithAvs.goto(fullAvsWithoutPrefilledDataUrl);
await cardWithAvs.fillCardNumber(REGULAR_TEST_CARD);
await cardWithAvs.fillExpiryDate(TEST_DATE_VALUE);
await cardWithAvs.fillCvc(TEST_CVC_VALUE);

await cardWithAvs.billingAddress.selectCountry({ name: 'United States' });
await cardWithAvs.billingAddress.fillInStreet('Test address');
await cardWithAvs.billingAddress.fillInCity('Test city');
await cardWithAvs.billingAddress.selectState({ name: 'Florida' });
await cardWithAvs.billingAddress.fillInPostCode('12345');
await cardWithAvs.pay();
await cardWithAvs.paymentResult.waitFor({ state: 'visible' });
await expect(cardWithAvs.paymentResult).toContainText(PAYMENT_RESULT.authorised);
});
});

test.describe('When fill in the invalid address data', () => {
test('should not submit the payment', async ({ page }) => {
// fill in card number
// fill in expiry date
// fill in cvc
// skip required fields in address section
// click pay btn
// expect to see error message
test('should not submit the payment', async ({ cardWithAvs }) => {
await cardWithAvs.goto(fullAvsWithoutPrefilledDataUrl);
await cardWithAvs.fillCardNumber(REGULAR_TEST_CARD);
await cardWithAvs.fillExpiryDate(TEST_DATE_VALUE);
await cardWithAvs.fillCvc(TEST_CVC_VALUE);

await cardWithAvs.billingAddress.selectCountry({ name: 'United States' });
await cardWithAvs.pay();
await expect(cardWithAvs.billingAddress.streetInputError).toContainText('Enter the address');
await expect(cardWithAvs.billingAddress.cityError).toContainText('Enter the city');
await expect(cardWithAvs.billingAddress.stateError).toContainText('Enter the state');
await expect(cardWithAvs.billingAddress.postalCodeError).toContainText('Enter the zip code');
});
});

test.describe('When switching to a different delivery country', () => {
test('should make a successful card payment', async ({ page }) => {
// prefilled wrong address
// user sees error msg
// change the country code
// submit payment successfully
test('should make a successful card payment', async ({ cardWithAvs }) => {
const url =
'/iframe.html?globals=&args=componentConfiguration.billingAddressAllowedCountries:!undefined;componentConfiguration.data.billingAddress.postalCode:A9A9A9&id=cards-card--with-avs&viewMode=story';
await cardWithAvs.goto(url);
await expect(cardWithAvs.billingAddress.postalCodeError).toContainText('Invalid format. Expected format');
await cardWithAvs.billingAddress.selectCountry({ name: 'Japan' });
await cardWithAvs.fillCardNumber(REGULAR_TEST_CARD);
await cardWithAvs.fillExpiryDate(TEST_DATE_VALUE);
await cardWithAvs.fillCvc(TEST_CVC_VALUE);
await cardWithAvs.pay();
await cardWithAvs.paymentResult.waitFor({ state: 'visible' });
await expect(cardWithAvs.paymentResult).toContainText(PAYMENT_RESULT.authorised);
});
test('should not submit the payment', async ({ page }) => {
// prefilled correct address
// change the country code
// user sees error msg
// user cannot submit payment

test('should not submit the payment', async ({ cardWithAvs }) => {
await cardWithAvs.goto(fullAvsWithPrefilledDataUrl);
await cardWithAvs.billingAddress.selectCountry({ name: 'Canada' });
await expect(cardWithAvs.billingAddress.postalCodeError).toContainText('Invalid format. Expected format');
});
});
});
Loading
Loading