diff --git a/packages/e2e-playwright/app/config/webpack.config.js b/packages/e2e-playwright/app/config/webpack.config.js index 68a565175c..292371fee0 100644 --- a/packages/e2e-playwright/app/config/webpack.config.js +++ b/packages/e2e-playwright/app/config/webpack.config.js @@ -19,8 +19,8 @@ const htmlPages = fs.readdirSync(basePageDir).map(fileName => ({ const htmlPageGenerator = ({ id }, index) => { console.log('htmlPageGenerator', id, index); return new HTMLWebpackPlugin({ - // make card index.html the rest of the pages will have page .html - filename: `${id !== 'Cards' ? `${id.toLowerCase()}/` : ''}index.html`, + // make Dropin index.html the rest of the pages will have page .html + filename: `${id !== 'Dropin' ? `${id.toLowerCase()}/` : ''}index.html`, template: path.join(__dirname, `../src/pages/${id}/${id}.html`), templateParameters: () => ({ htmlWebpackPlugin: { htmlPages } }), inject: 'body', diff --git a/packages/e2e-playwright/app/src/pages/CustomCards/CustomCards.html b/packages/e2e-playwright/app/src/pages/CustomCards/CustomCards.html index 43cd8d1cab..7ace7ce413 100644 --- a/packages/e2e-playwright/app/src/pages/CustomCards/CustomCards.html +++ b/packages/e2e-playwright/app/src/pages/CustomCards/CustomCards.html @@ -39,18 +39,18 @@

CustomCard #1

/> @@ -60,6 +60,57 @@

CustomCard #1

+ +
+
+
+

CustomCard #2

+
+
+ + + + + + + + + + + +
+
+
+ + + diff --git a/packages/e2e-playwright/app/src/pages/Dropin/Dropin.js b/packages/e2e-playwright/app/src/pages/Dropin/Dropin.js new file mode 100644 index 0000000000..52c42e08e8 --- /dev/null +++ b/packages/e2e-playwright/app/src/pages/Dropin/Dropin.js @@ -0,0 +1,27 @@ +import AdyenCheckout from '@adyen/adyen-web'; +import '@adyen/adyen-web/dist/es/adyen.css'; +import { getPaymentMethods } from '../../services'; +import { amount, shopperLocale, countryCode } from '../../services/commonConfig'; +import { handleSubmit, handleAdditionalDetails, handleError } from '../../handlers'; +import '../../style.scss'; + +const initCheckout = async () => { + const paymentMethodsResponse = await getPaymentMethods({ amount, shopperLocale }); + + window.checkout = await AdyenCheckout({ + amount, + countryCode, + clientKey: process.env.__CLIENT_KEY__, + paymentMethodsResponse, + locale: shopperLocale, + environment: 'test', + onSubmit: handleSubmit, + onAdditionalDetails: handleAdditionalDetails, + onError: handleError, + ...window.mainConfiguration + }); + + window.dropin = checkout.create('dropin', window.dropinConfig).mount('#dropin-container'); +}; + +initCheckout(); diff --git a/packages/e2e-playwright/mocks/binLookup/binLookup.data.ts b/packages/e2e-playwright/mocks/binLookup/binLookup.data.ts index dad8b80f5b..9e205e10d2 100644 --- a/packages/e2e-playwright/mocks/binLookup/binLookup.data.ts +++ b/packages/e2e-playwright/mocks/binLookup/binLookup.data.ts @@ -13,4 +13,18 @@ const optionalDateAndCvcMock = { requestedId: null }; -export { optionalDateAndCvcMock }; +const hiddenDateAndCvcMock = { + brands: [ + { + brand: 'mc', + enableLuhnCheck: true, + supported: true, + cvcPolicy: 'hidden', + expiryDatePolicy: 'hidden' + } + ], + issuingCountryCode: 'US', + requestedId: null +}; + +export { optionalDateAndCvcMock, hiddenDateAndCvcMock }; diff --git a/packages/e2e-playwright/models/card.ts b/packages/e2e-playwright/models/card.ts index e1b66caafa..da95d6d54b 100644 --- a/packages/e2e-playwright/models/card.ts +++ b/packages/e2e-playwright/models/card.ts @@ -10,23 +10,39 @@ const CARD_IFRAME_LABEL = LANG['creditCard.encryptedCardNumber.aria.label']; const EXPIRY_DATE_IFRAME_LABEL = LANG['creditCard.encryptedExpiryDate.aria.label']; const CVC_IFRAME_LABEL = LANG['creditCard.encryptedSecurityCode.aria.label']; +const INSTALLMENTS_PAYMENTS = LANG['installments.installments']; +const REVOLVING_PAYMENT = LANG['installments.revolving']; + class Card { + readonly page: Page; + readonly rootElement: Locator; readonly rootElementSelector: string; readonly cardNumberField: Locator; + readonly cardNumberLabelElement: Locator; readonly cardNumberErrorElement: Locator; readonly cardNumberInput: Locator; + readonly brandingIcon: Locator; readonly expiryDateField: Locator; - readonly expiryDateErrorElement: Locator; + readonly expiryDateLabelText: Locator; readonly expiryDateInput: Locator; + readonly expiryDateErrorElement: Locator; readonly cvcField: Locator; + readonly cvcLabelText: Locator; readonly cvcErrorElement: Locator; readonly cvcInput: Locator; + readonly installmentsPaymentLabel: Locator; + readonly revolvingPaymentLabel: Locator; + readonly installmentsDropdown: Locator; + readonly selectorList: Locator; + constructor(page: Page, rootElementSelector = '.adyen-checkout__card-input') { + this.page = page; + this.rootElement = page.locator(rootElementSelector); this.rootElementSelector = rootElementSelector; @@ -34,8 +50,11 @@ class Card { * Card Number elements, in Checkout */ this.cardNumberField = this.rootElement.locator('.adyen-checkout__field--cardNumber'); // Holder + this.cardNumberLabelElement = this.cardNumberField.locator('.adyen-checkout__label'); this.cardNumberErrorElement = this.cardNumberField.locator('.adyen-checkout__error-text'); + this.brandingIcon = this.rootElement.locator('.adyen-checkout__card__cardNumber__brandIcon'); + /** * Card Number elements, in iframe */ @@ -46,7 +65,9 @@ class Card { * Expiry Date elements, in Checkout */ this.expiryDateField = this.rootElement.locator('.adyen-checkout__field--expiryDate'); // Holder + this.expiryDateLabelText = this.expiryDateField.locator('.adyen-checkout__label__text'); this.expiryDateErrorElement = this.expiryDateField.locator('.adyen-checkout__error-text'); // Related error element + // Related error element /** * Expiry Date elements, in iframe @@ -58,6 +79,7 @@ class Card { * Security code elements, in Checkout */ this.cvcField = this.rootElement.locator('.adyen-checkout__field--securityCode'); // Holder + this.cvcLabelText = this.cvcField.locator('.adyen-checkout__label__text'); this.cvcErrorElement = this.cvcField.locator('.adyen-checkout__error-text'); // Related error element /** @@ -65,6 +87,14 @@ class Card { */ const cvcIframe = this.rootElement.frameLocator(`[title="${CVC_IFRAME_TITLE}"]`); this.cvcInput = cvcIframe.locator(`input[aria-label="${CVC_IFRAME_LABEL}"]`); + + /** + * Installments related elements + */ + this.installmentsPaymentLabel = this.rootElement.getByText(INSTALLMENTS_PAYMENTS); + this.revolvingPaymentLabel = this.rootElement.getByText(REVOLVING_PAYMENT); + this.installmentsDropdown = this.rootElement.locator('.adyen-checkout__dropdown__button'); + this.selectorList = this.rootElement.getByRole('listbox'); } async isComponentVisible() { @@ -81,6 +111,14 @@ class Card { await this.cardNumberInput.clear(); } + async deleteExpiryDate() { + await this.expiryDateInput.clear(); + } + + async deleteCvc() { + await this.cvcInput.clear(); + } + async typeExpiryDate(expiryDate: string) { await this.expiryDateInput.type(expiryDate, { delay: USER_TYPE_DELAY }); } @@ -88,6 +126,11 @@ class Card { async typeCvc(cvc: string) { await this.cvcInput.type(cvc, { delay: USER_TYPE_DELAY }); } + + async selectListItem(who: string) { + const listItem = this.selectorList.locator(`#listItem-${who}`); + return listItem; + } } export { Card }; diff --git a/packages/e2e-playwright/models/customCard.ts b/packages/e2e-playwright/models/customCard.ts new file mode 100644 index 0000000000..de8fc79438 --- /dev/null +++ b/packages/e2e-playwright/models/customCard.ts @@ -0,0 +1,183 @@ +import { Locator, Page } from '@playwright/test'; +import { USER_TYPE_DELAY } from '../tests/utils/constants'; +import LANG from '../../lib/src/language/locales/en-US.json'; + +const CARD_IFRAME_TITLE = LANG['creditCard.encryptedCardNumber.aria.iframeTitle']; +const EXPIRY_DATE_IFRAME_TITLE = LANG['creditCard.encryptedExpiryDate.aria.iframeTitle']; +const EXPIRY_MONTH_IFRAME_TITLE = LANG['creditCard.encryptedExpiryMonth.aria.iframeTitle']; +const EXPIRY_YEAR_IFRAME_TITLE = LANG['creditCard.encryptedExpiryYear.aria.iframeTitle']; +const CVC_IFRAME_TITLE = LANG['creditCard.encryptedSecurityCode.aria.iframeTitle']; + +const CARD_IFRAME_LABEL = LANG['creditCard.numberField.title']; +const EXPIRY_DATE_IFRAME_LABEL = LANG['creditCard.expiryDateField.title']; +const EXPIRY_MONTH_IFRAME_LABEL = LANG['creditCard.encryptedExpiryMonth.aria.label']; +const EXPIRY_YEAR_IFRAME_LABEL = LANG['creditCard.encryptedExpiryYear.aria.label']; +const CVC_IFRAME_LABEL = LANG['creditCard.cvcField.title']; + +class CustomCard { + readonly page: Page; + + readonly rootElement: Locator; + readonly rootElementSelector: string; + + readonly cardNumberField: Locator; + readonly cardNumberLabelElement: Locator; + readonly cardNumberErrorElement: Locator; + readonly cardNumberInput: Locator; + readonly brandingIcon: Locator; + + readonly expiryDateField: Locator; + readonly expiryDateLabelText: Locator; + readonly expiryDateInput: Locator; + readonly expiryDateIframeContextualElement: Locator; + readonly expiryDateErrorElement: Locator; + + readonly expiryMonthField: Locator; + readonly expiryMonthLabelText: Locator; + readonly expiryMonthErrorElement: Locator; + readonly expiryMonthInput: Locator; + readonly expiryMonthIframeContextualElement: Locator; + + readonly expiryYearField: Locator; + readonly expiryYearLabelText: Locator; + readonly expiryYearErrorElement: Locator; + readonly expiryYearInput: Locator; + readonly expiryYearIframeContextualElement: Locator; + + readonly cvcField: Locator; + readonly cvcLabelText: Locator; + readonly cvcErrorElement: Locator; + readonly cvcInput: Locator; + readonly cvcIframeContextualElement: Locator; + + constructor(page: Page, rootElementSelector = '.secured-fields') { + this.page = page; + + this.rootElement = page.locator(rootElementSelector); + this.rootElementSelector = rootElementSelector; + + /** + * Card Number elements, in Checkout + */ + this.cardNumberField = this.rootElement.locator('.pm-form-label-pan'); // Holder + this.cardNumberLabelElement = this.cardNumberField.locator('.pm-form-label__text'); + this.cardNumberErrorElement = this.cardNumberField.locator('.pm-form-label__error-text'); + + this.brandingIcon = this.rootElement.locator('.adyen-checkout__card__cardNumber__brandIcon'); + + /** + * Card Number elements, in iframe + */ + const cardNumberIframe = this.rootElement.frameLocator(`[title="${CARD_IFRAME_TITLE}"]`); + this.cardNumberInput = cardNumberIframe.locator(`input[aria-label="${CARD_IFRAME_LABEL}"]`); + + /** + * Expiry Date elements, in Checkout + */ + this.expiryDateField = this.rootElement.locator('.pm-form-label--exp-date'); // Holder + this.expiryDateLabelText = this.expiryDateField.locator('.pm-form-label__text'); + this.expiryDateErrorElement = this.expiryDateField.locator('.pm-form-label__error-text'); // Related error element + + /** + * Expiry Date elements, in iframe + */ + const expiryDateIframe = this.rootElement.frameLocator(`[title="${EXPIRY_DATE_IFRAME_TITLE}"]`); + this.expiryDateInput = expiryDateIframe.locator(`input[aria-label="${EXPIRY_DATE_IFRAME_LABEL}"]`); + this.expiryDateIframeContextualElement = expiryDateIframe.locator('.aria-context'); + + /** + * Expiry Month elements, in Checkout + */ + this.expiryMonthField = this.rootElement.locator('.pm-form-label--exp-month'); // Holder + this.expiryMonthLabelText = this.expiryMonthField.locator('.pm-form-label__text'); + this.expiryMonthErrorElement = this.expiryMonthField.locator('.pm-form-label__error-text'); // Related error element + + /** + * Expiry Month elements, in iframe + */ + const expiryMonthIframe = this.rootElement.frameLocator(`[title="${EXPIRY_MONTH_IFRAME_TITLE}"]`); + this.expiryMonthInput = expiryMonthIframe.locator(`input[aria-label="${EXPIRY_MONTH_IFRAME_LABEL}"]`); + this.expiryMonthIframeContextualElement = expiryMonthIframe.locator('.aria-context'); + + /** + * Expiry Year elements, in Checkout + */ + this.expiryYearField = this.rootElement.locator('.pm-form-label--exp-year'); // Holder + this.expiryYearLabelText = this.expiryYearField.locator('.pm-form-label__text'); + this.expiryYearErrorElement = this.expiryYearField.locator('.pm-form-label__error-text'); + + /** + * Expiry Month elements, in iframe + */ + const expiryYearIframe = this.rootElement.frameLocator(`[title="${EXPIRY_YEAR_IFRAME_TITLE}"]`); + this.expiryYearInput = expiryYearIframe.locator(`input[aria-label="${EXPIRY_YEAR_IFRAME_LABEL}"]`); + this.expiryYearIframeContextualElement = expiryYearIframe.locator('.aria-context'); + + /** + * Security code elements, in Checkout + */ + this.cvcField = this.rootElement.locator('.pm-form-label--cvc'); // Holder + this.cvcLabelText = this.cvcField.locator('.pm-form-label__text'); + this.cvcErrorElement = this.cvcField.locator('.pm-form-label__error-text'); // Related error element + + /** + * Security code elements, in iframe + */ + const cvcIframe = this.rootElement.frameLocator(`[title="${CVC_IFRAME_TITLE}"]`); + this.cvcInput = cvcIframe.locator(`input[aria-label="${CVC_IFRAME_LABEL}"]`); + this.cvcIframeContextualElement = cvcIframe.locator('.aria-context'); + } + + async isComponentVisible() { + await this.cardNumberInput.waitFor({ state: 'visible' }); + await this.expiryDateInput.waitFor({ state: 'visible' }); + await this.cvcInput.waitFor({ state: 'visible' }); + } + + async isSeparateComponentVisible() { + await this.cardNumberInput.waitFor({ state: 'visible' }); + await this.expiryMonthInput.waitFor({ state: 'visible' }); + await this.cvcInput.waitFor({ state: 'visible' }); + } + + async typeCardNumber(cardNumber: string) { + await this.cardNumberInput.type(cardNumber, { delay: USER_TYPE_DELAY }); + } + + async deleteCardNumber() { + await this.cardNumberInput.clear(); + } + + async deleteExpiryDate() { + await this.expiryDateInput.clear(); + } + + async deleteExpiryMonth() { + await this.expiryMonthInput.clear(); + } + + async deleteExpiryYear() { + await this.expiryYearInput.clear(); + } + + async deleteCvc() { + await this.cvcInput.clear(); + } + + async typeExpiryDate(expiryDate: string) { + await this.expiryDateInput.type(expiryDate, { delay: USER_TYPE_DELAY }); + } + + async typeExpiryMonth(expiryMonth: string) { + await this.expiryMonthInput.type(expiryMonth, { delay: USER_TYPE_DELAY }); + } + async typeExpiryYear(expiryYear: string) { + await this.expiryYearInput.type(expiryYear, { delay: USER_TYPE_DELAY }); + } + + async typeCvc(cvc: string) { + await this.cvcInput.type(cvc, { delay: USER_TYPE_DELAY }); + } +} + +export { CustomCard }; diff --git a/packages/e2e-playwright/models/dropin.ts b/packages/e2e-playwright/models/dropin.ts new file mode 100644 index 0000000000..7ca07b27c4 --- /dev/null +++ b/packages/e2e-playwright/models/dropin.ts @@ -0,0 +1,31 @@ +import { Locator, Page } from '@playwright/test'; + +class Dropin { + readonly page: Page; + + readonly rootElement: Locator; + readonly rootElementSelector: string; + + readonly pmList: Locator; + readonly creditCard: Locator; + readonly brandsHolder: Locator; + + constructor(page: Page, rootElementSelector = '.adyen-checkout__dropin') { + this.page = page; + + this.rootElement = page.locator(rootElementSelector); + this.rootElementSelector = rootElementSelector; + + this.pmList = this.rootElement.locator('.adyen-checkout__payment-methods-list'); + } + + async isComponentVisible() { + await this.pmList.waitFor({ state: 'visible' }); + } + + getPaymentMethodItem(pmName: string) { + return this.pmList.locator(`.adyen-checkout__payment-method:has-text("${pmName}")`); + } +} + +export { Dropin }; diff --git a/packages/e2e-playwright/models/dropinModelUtils/getDropinCardComp.ts b/packages/e2e-playwright/models/dropinModelUtils/getDropinCardComp.ts new file mode 100644 index 0000000000..b436357794 --- /dev/null +++ b/packages/e2e-playwright/models/dropinModelUtils/getDropinCardComp.ts @@ -0,0 +1,20 @@ +import { Dropin } from '../dropin'; +import { getImageCount } from '../../tests/utils/image'; + +export const getCreditCardPM = (dropin: Dropin) => { + const creditCard = dropin.getPaymentMethodItem('Credit Card'); + + const brandsHolder = creditCard.locator('.adyen-checkout__payment-method__brands'); + + const brandsText = brandsHolder.locator('.adyen-checkout__payment-method__brand-number'); + + const componentBrandsHolder = creditCard.locator('.adyen-checkout__card__brands'); + + return { + pm: creditCard, + brandsHolder, + brandsText, + componentBrandsHolder, + getImageCount + }; +}; diff --git a/packages/e2e-playwright/models/issuer-list.ts b/packages/e2e-playwright/models/issuer-list.ts index 90f2a6f8ec..8574bf3e73 100644 --- a/packages/e2e-playwright/models/issuer-list.ts +++ b/packages/e2e-playwright/models/issuer-list.ts @@ -2,7 +2,6 @@ import { Locator, Page } from '@playwright/test'; import { USER_TYPE_DELAY } from '../tests/utils/constants'; const SELECTOR_DELAY = 300; -const KEYBOARD_DELAY = 300; class IssuerList { readonly rootElement: Locator; @@ -43,18 +42,6 @@ class IssuerList { async typeOnSelectorField(filter: string) { await this.selectorCombobox.type(filter, { delay: USER_TYPE_DELAY }); } - - async pressKeyboardToNextItem() { - await this.page.keyboard.press('ArrowDown', { delay: KEYBOARD_DELAY }); - } - - async pressKeyboardToPreviousItem() { - await this.page.keyboard.press('ArrowDown', { delay: KEYBOARD_DELAY }); - } - - async pressKeyboardToSelectItem() { - await this.page.keyboard.press('Enter', { delay: KEYBOARD_DELAY }); - } } export { IssuerList }; diff --git a/packages/e2e-playwright/pages/cards/card.avs.page.ts b/packages/e2e-playwright/pages/cards/card.avs.page.ts index ff2d31dd40..86c07e81ea 100644 --- a/packages/e2e-playwright/pages/cards/card.avs.page.ts +++ b/packages/e2e-playwright/pages/cards/card.avs.page.ts @@ -14,7 +14,7 @@ class CardAvsPage { } async goto(url?: string) { - await this.page.goto('http://localhost:3024/'); + await this.page.goto('http://localhost:3024/cards'); } } diff --git a/packages/e2e-playwright/pages/cards/card.fixture.ts b/packages/e2e-playwright/pages/cards/card.fixture.ts index 8bf38d6efb..98cbc94807 100644 --- a/packages/e2e-playwright/pages/cards/card.fixture.ts +++ b/packages/e2e-playwright/pages/cards/card.fixture.ts @@ -1,4 +1,4 @@ -import { test as base, expect } from '@playwright/test'; +import { test as base, expect, Page } from '@playwright/test'; import { CardPage } from './card.page'; import { CardAvsPage } from './card.avs.page'; import { binLookupMock } from '../../mocks/binLookup/binLookup.mock'; @@ -8,13 +8,14 @@ type Fixture = { cardPage: CardPage; cardAvsPage: CardAvsPage; cardLegacyInputModePage: CardPage; + cardBrandingPage: CardPage; + cardExpiryDatePoliciesPage: CardPage; + cardInstallmentsPage: CardPage; }; const test = base.extend({ cardPage: async ({ page }, use) => { - const cardPage = new CardPage(page); - await cardPage.goto(); - await use(cardPage); + await useCardPage(page, use); }, cardAvsPage: async ({ page }, use) => { @@ -24,9 +25,8 @@ const test = base.extend({ "window.cardConfig = { billingAddressRequired: true, billingAddressRequiredFields: ['street', 'houseNumberOrName', 'postalCode', 'city']};" }); - const cardAvsPage = new CardAvsPage(page); - await cardAvsPage.goto(); - await use(cardAvsPage); + // @ts-ignore + await useCardPage(page, use, CardAvsPage); }, cardLegacyInputModePage: async ({ page }, use) => { @@ -34,10 +34,57 @@ const test = base.extend({ content: 'window.cardConfig = { legacyInputMode: true}' }); - const cardPage = new CardPage(page); - await cardPage.goto(); - await use(cardPage); + await useCardPage(page, use); + }, + + cardBrandingPage: async ({ page }, use) => { + const brands = JSON.stringify({ brands: ['mc', 'visa', 'amex', 'maestro', 'bcmc'] }); + await page.addInitScript({ + content: `window.cardConfig = ${brands}` + }); + + await useCardPage(page, use); + }, + + cardExpiryDatePoliciesPage: async ({ page }, use) => { + const mainConfig = JSON.stringify({ + srConfig: { + moveFocus: false + } + }); + await page.addInitScript({ + content: `window.mainConfiguration = ${mainConfig}` + }); + + const brands = JSON.stringify({ brands: ['mc', 'visa', 'amex', 'synchrony_plcc'] }); + await page.addInitScript({ + content: `window.cardConfig = ${brands}` + }); + + await useCardPage(page, use); + }, + + cardInstallmentsPage: async ({ page }, use) => { + const installmentsConfig = JSON.stringify({ + installmentOptions: { + mc: { + values: [1, 2, 3], + plans: ['regular', 'revolving'] + } + } + }); + await page.addInitScript({ + content: `window.cardConfig = ${installmentsConfig}` + }); + + await useCardPage(page, use); } }); +const useCardPage = async (page: Page, use: any, PageType = CardPage) => { + const cardPage = new PageType(page); + await cardPage.goto(); + await use(cardPage); +}; + export { test, expect }; diff --git a/packages/e2e-playwright/pages/cards/card.page.ts b/packages/e2e-playwright/pages/cards/card.page.ts index f0e5ade2b0..7ce9955314 100644 --- a/packages/e2e-playwright/pages/cards/card.page.ts +++ b/packages/e2e-playwright/pages/cards/card.page.ts @@ -14,7 +14,7 @@ class CardPage { } async goto(url?: string) { - await this.page.goto('http://localhost:3024/'); + await this.page.goto('http://localhost:3024/cards'); } async pay() { diff --git a/packages/e2e-playwright/pages/customCard/customCard.fixture.ts b/packages/e2e-playwright/pages/customCard/customCard.fixture.ts index 37e88b8f56..8538634e93 100644 --- a/packages/e2e-playwright/pages/customCard/customCard.fixture.ts +++ b/packages/e2e-playwright/pages/customCard/customCard.fixture.ts @@ -3,6 +3,7 @@ import { CustomCardPage } from './customCard.page'; type Fixture = { customCardPage: CustomCardPage; + customCardPageSeparate: CustomCardPage; }; const test = base.extend({ @@ -10,6 +11,12 @@ const test = base.extend({ const cardPage = new CustomCardPage(page); await cardPage.goto(); await use(cardPage); + }, + + customCardPageSeparate: async ({ page }, use) => { + const cardPage = new CustomCardPage(page, '.secured-fields-2'); + await cardPage.goto(); + await use(cardPage); } }); diff --git a/packages/e2e-playwright/pages/customCard/customCard.page.ts b/packages/e2e-playwright/pages/customCard/customCard.page.ts index c3aaa87e3c..da586105ad 100644 --- a/packages/e2e-playwright/pages/customCard/customCard.page.ts +++ b/packages/e2e-playwright/pages/customCard/customCard.page.ts @@ -1,24 +1,27 @@ import { Locator, Page } from '@playwright/test'; -import { Card } from '../../models/card'; +import { CustomCard } from '../../models/customCard'; class CustomCardPage { readonly page: Page; - readonly card: Card; - readonly payButton: Locator; + readonly card: CustomCard; + readonly payButtonRegular: Locator; + readonly payButtonSeparate: Locator; - constructor(page: Page) { + constructor(page: Page, selector?: string) { this.page = page; - this.card = new Card(page, '.secured-fields'); - this.payButton = page.getByRole('button', { name: /Pay/i }); + this.card = new CustomCard(page, selector); + this.payButtonRegular = page.getByTestId('pay-customCardRegular'); + this.payButtonSeparate = page.getByTestId('pay-customCardSeparate'); } async goto(url?: string) { await this.page.goto('http://localhost:3024/customcards'); } - async pay() { - await this.payButton.click(); + async pay(which: string = 'Regular') { + await this[`payButton${which}`].scrollIntoViewIfNeeded(); + await this[`payButton${which}`].click(); } } diff --git a/packages/e2e-playwright/pages/dropin/dropin.fixture.ts b/packages/e2e-playwright/pages/dropin/dropin.fixture.ts new file mode 100644 index 0000000000..c157f2d5bf --- /dev/null +++ b/packages/e2e-playwright/pages/dropin/dropin.fixture.ts @@ -0,0 +1,90 @@ +import { test as base, expect, Page } from '@playwright/test'; +import { DropinPage } from './dropin.page'; + +type Fixture = { + dropinPage: DropinPage; + dropinPage_cardBrands_defaultView: DropinPage; + dropinPage_cardBrands_defaultView_withExcluded: DropinPage; + dropinPage_cardBrands_compactView: DropinPage; + dropinPage_cardBrands_compactView_withExcluded: DropinPage; +}; + +const test = base.extend({ + dropinPage: async ({ page }, use) => { + await useDropinPage(page, use); + }, + + dropinPage_cardBrands_defaultView: async ({ page }, use) => { + const pmsConfig = JSON.stringify({ + paymentMethodsConfiguration: { + card: { + showBrandsUnderCardNumber: false, + brands: ['visa', 'mc', 'amex', 'discover', 'cup', 'maestro', 'bijcard', 'diners', 'jcb', 'synchrony_cbcc'], + _disableClickToPay: true + } + } + }); + await page.addInitScript({ + content: `window.mainConfiguration = ${pmsConfig}` + }); + + await useDropinPage(page, use); + }, + + dropinPage_cardBrands_defaultView_withExcluded: async ({ page }, use) => { + const pmsConfig = JSON.stringify({ + paymentMethodsConfiguration: { + card: { + showBrandsUnderCardNumber: false, + brands: ['visa', 'mc', 'amex', 'discover', 'cup', 'maestro', 'nyce', 'accel', 'star', 'pulse'], + _disableClickToPay: true + } + } + }); + await page.addInitScript({ + content: `window.mainConfiguration = ${pmsConfig}` + }); + + await useDropinPage(page, use); + }, + + dropinPage_cardBrands_compactView: async ({ page }, use) => { + const pmsConfig = JSON.stringify({ + paymentMethodsConfiguration: { + card: { + brands: ['visa', 'mc', 'amex', 'discover', 'cup', 'maestro', 'bijcard', 'diners', 'jcb', 'synchrony_cbcc'], + _disableClickToPay: true + } + } + }); + await page.addInitScript({ + content: `window.mainConfiguration = ${pmsConfig}` + }); + + await useDropinPage(page, use); + }, + + dropinPage_cardBrands_compactView_withExcluded: async ({ page }, use) => { + const pmsConfig = JSON.stringify({ + paymentMethodsConfiguration: { + card: { + brands: ['visa', 'mc', 'amex', 'discover', 'cup', 'maestro', 'nyce', 'accel', 'star', 'pulse'], + _disableClickToPay: true + } + } + }); + await page.addInitScript({ + content: `window.mainConfiguration = ${pmsConfig}` + }); + + await useDropinPage(page, use); + } +}); + +const useDropinPage = async (page: Page, use: any, PageType = DropinPage) => { + const dropinPage = new PageType(page); + await dropinPage.goto(); + await use(dropinPage); +}; + +export { test, expect }; diff --git a/packages/e2e-playwright/pages/dropin/dropin.page.ts b/packages/e2e-playwright/pages/dropin/dropin.page.ts new file mode 100644 index 0000000000..3f5923a549 --- /dev/null +++ b/packages/e2e-playwright/pages/dropin/dropin.page.ts @@ -0,0 +1,25 @@ +import { Locator, Page } from '@playwright/test'; +import { Dropin } from '../../models/dropin'; + +class DropinPage { + readonly page: Page; + + readonly dropin: Dropin; + // readonly payButton: Locator; + + constructor(page: Page) { + this.page = page; + this.dropin = new Dropin(page); + // this.payButton = page.getByRole('button', { name: /Pay/i }); + } + + async goto(url?: string) { + await this.page.goto('http://localhost:3024'); + } + + // async pay() { + // await this.payButton.click(); + // } +} + +export { DropinPage }; diff --git a/packages/e2e-playwright/pages/issuerList/issuer-list.page.ts b/packages/e2e-playwright/pages/issuerList/issuer-list.page.ts index ca03835f00..654c4dfb74 100644 --- a/packages/e2e-playwright/pages/issuerList/issuer-list.page.ts +++ b/packages/e2e-playwright/pages/issuerList/issuer-list.page.ts @@ -2,7 +2,7 @@ import { Page } from '@playwright/test'; import { IssuerList } from '../../models/issuer-list'; class IssuerListPage { - private readonly page: Page; + readonly page: Page; public readonly issuerList: IssuerList; diff --git a/packages/e2e-playwright/tests/card/branding/card.branding.spec.ts b/packages/e2e-playwright/tests/card/branding/card.branding.spec.ts new file mode 100644 index 0000000000..527366d4e9 --- /dev/null +++ b/packages/e2e-playwright/tests/card/branding/card.branding.spec.ts @@ -0,0 +1,163 @@ +import { test, expect } from '../../../pages/cards/card.fixture'; +import { MAESTRO_CARD, TEST_CVC_VALUE, TEST_DATE_VALUE } from '../../utils/constants'; +import LANG from '../../../../lib/src/language/locales/en-US.json'; + +const CVC_LABEL = LANG['creditCard.cvcField.title']; +const CVC_LABEL_OPTIONAL = LANG['creditCard.cvcField.title.optional']; + +test.describe('Testing branding - especially regarding optional and hidden cvc fields', () => { + test( + '#1 Test for generic card icon & required CVC field' + + 'then enter number recognised as maestro (by our regEx), ' + + 'then add digit so it will be seen as a bcmc card (by our regEx) ,' + + 'then delete number (back to generic card)', + async ({ cardBrandingPage }) => { + const { card, page } = cardBrandingPage; + + await card.isComponentVisible(); + + // generic card + let brandingIconSrc = await card.brandingIcon.getAttribute('src'); + await expect(brandingIconSrc).toContain('nocard.svg'); + + // visible & required cvc field + await expect(card.cvcField).toBeVisible(); + await expect(card.cvcField).toHaveClass(/adyen-checkout__field__cvc/); // Note: "relaxed" regular expression to detect one class amongst several that are set on the element + await expect(card.cvcField).not.toHaveClass(/adyen-checkout__field__cvc--optional/); + + // with regular text + await expect(card.cvcLabelText).toHaveText(CVC_LABEL); + + // Partially fill card field with digits that will be recognised as maestro + await card.typeCardNumber('670'); + + // maestro card icon + brandingIconSrc = await card.brandingIcon.getAttribute('src'); + await expect(brandingIconSrc).toContain('maestro.svg'); + + // with "optional" text + await expect(card.cvcLabelText).toHaveText(CVC_LABEL_OPTIONAL); + // and optional class + await expect(card.cvcField).toHaveClass(/adyen-checkout__field__cvc--optional/); + + // Add digit so card is recognised as bcmc + await card.cardNumberInput.press('End'); /** NOTE: how to add text at end */ + await card.typeCardNumber('3'); + + // bcmc card icon + brandingIconSrc = await card.brandingIcon.getAttribute('src'); + await expect(brandingIconSrc).toContain('bcmc.svg'); + + // hidden cvc field + await expect(card.cvcField).not.toBeVisible(); + + // Delete number + await card.deleteCardNumber(); + + // Card is reset + brandingIconSrc = await card.brandingIcon.getAttribute('src'); + await expect(brandingIconSrc).toContain('nocard.svg'); + + // Visible cvc field + await expect(card.cvcField).toBeVisible(); + + // with regular text + await expect(card.cvcLabelText).toHaveText(CVC_LABEL); + + // and required cvc field + await expect(card.cvcField).toHaveClass(/adyen-checkout__field__cvc/); + await expect(card.cvcField).not.toHaveClass(/adyen-checkout__field__cvc--optional/); + } + ); + + test( + '#2 Test card is valid with maestro details (cvc optional)' + 'then test it is invalid (& brand reset) when number deleted', + async ({ cardBrandingPage }) => { + const { card, page } = cardBrandingPage; + + await card.isComponentVisible(); + + // Maestro + await card.typeCardNumber(MAESTRO_CARD); + await card.typeExpiryDate(TEST_DATE_VALUE); + + // maestro card icon + let brandingIconSrc = await card.brandingIcon.getAttribute('src'); + await expect(brandingIconSrc).toContain('maestro.svg'); + + // with "optional" text + await expect(card.cvcLabelText).toHaveText(CVC_LABEL_OPTIONAL); + // and optional class + await expect(card.cvcField).toHaveClass(/adyen-checkout__field__cvc--optional/); + + // Is valid + let cardValid = await page.evaluate('window.card.isValid'); + await expect(cardValid).toEqual(true); + + await card.typeCvc(TEST_CVC_VALUE); + + // Headless test seems to need time for UI reset to register on state + await page.waitForTimeout(500); + + // Is valid + cardValid = await page.evaluate('window.card.isValid'); + await expect(cardValid).toEqual(true); + + // Delete number + await card.deleteCardNumber(); + + // Card is reset to generic card + brandingIconSrc = await card.brandingIcon.getAttribute('src'); + await expect(brandingIconSrc).toContain('nocard.svg'); + + // Headless test seems to need time for UI change to register on state + await page.waitForTimeout(500); + + // Is not valid + cardValid = await page.evaluate('window.card.isValid'); + await expect(cardValid).toEqual(false); + } + ); + + test( + '#3 Test card is invalid if filled with maestro details but optional cvc field is left "in error" (partially filled)' + + 'then test it is valid if cvc completed' + + 'then test it is valid if cvc deleted', + async ({ cardBrandingPage }) => { + const { card, page } = cardBrandingPage; + + await card.isComponentVisible(); + + // Maestro + await card.typeCardNumber(MAESTRO_CARD); + await card.typeExpiryDate(TEST_DATE_VALUE); + + // Partial cvc + await card.typeCvc('73'); + + // Force blur event to fire + await card.cardNumberLabelElement.click(); + + // Wait for UI to render + await page.waitForTimeout(300); + + // Is not valid + let cardValid = await page.evaluate('window.card.isValid'); + await expect(cardValid).toEqual(false); + + // Complete cvc + await card.cvcInput.press('End'); /** NOTE: how to add text at end */ + await card.typeCvc('7'); + + // Is valid + cardValid = await page.evaluate('window.card.isValid'); + await expect(cardValid).toEqual(true); + + await card.deleteCvc(); + + // Is valid + cardValid = await page.evaluate('window.card.isValid'); + await expect(cardValid).toEqual(true); + } + ); +}); diff --git a/packages/e2e-playwright/tests/card/card.spec.ts b/packages/e2e-playwright/tests/card/card.spec.ts index 915268ee8f..f5b5a6d58a 100644 --- a/packages/e2e-playwright/tests/card/card.spec.ts +++ b/packages/e2e-playwright/tests/card/card.spec.ts @@ -6,45 +6,47 @@ const PAN_ERROR_NOT_VALID = LANG['error.va.sf-cc-num.01']; const PAN_ERROR_EMPTY = LANG['error.va.sf-cc-num.02']; const PAN_ERROR_NOT_COMPLETE = LANG['error.va.sf-cc-num.04']; -test('#1 Should fill in card fields and complete the payment', async ({ cardPage }) => { - const { card, page } = cardPage; +test.describe('Card - Standard flow', () => { + test('#1 Should fill in card fields and complete the payment', async ({ cardPage }) => { + const { card, page } = cardPage; - await card.typeCardNumber(REGULAR_TEST_CARD); - await card.typeCvc(TEST_CVC_VALUE); - await card.typeExpiryDate(TEST_DATE_VALUE); + await card.typeCardNumber(REGULAR_TEST_CARD); + await card.typeCvc(TEST_CVC_VALUE); + await card.typeExpiryDate(TEST_DATE_VALUE); - await cardPage.pay(); + await cardPage.pay(); - await expect(page.locator('#result-message')).toHaveText('Authorised'); -}); + await expect(page.locator('#result-message')).toHaveText('Authorised'); + }); -test('#2 PAN that consists of the same digit (but passes luhn) causes an error', async ({ cardPage }) => { - const { card, page } = cardPage; + test('#2 PAN that consists of the same digit (but passes luhn) causes an error', async ({ cardPage }) => { + const { card, page } = cardPage; - await card.typeCardNumber('3333 3333 3333 3333 3333'); + await card.typeCardNumber('3333 3333 3333 3333 3333'); - await cardPage.pay(); + await cardPage.pay(); - await expect(card.cardNumberErrorElement).toBeVisible(); - await expect(card.cardNumberErrorElement).toHaveText(PAN_ERROR_NOT_VALID); -}); + await expect(card.cardNumberErrorElement).toBeVisible(); + await expect(card.cardNumberErrorElement).toHaveText(PAN_ERROR_NOT_VALID); + }); -test('#3 Clicking pay button with an empty PAN causes an "empty" error on the PAN field', async ({ cardPage }) => { - const { card, page } = cardPage; + test('#3 Clicking pay button with an empty PAN causes an "empty" error on the PAN field', async ({ cardPage }) => { + const { card, page } = cardPage; - await cardPage.pay(); + await cardPage.pay(); - await expect(card.cardNumberErrorElement).toBeVisible(); - await expect(card.cardNumberErrorElement).toHaveText(PAN_ERROR_EMPTY); -}); + await expect(card.cardNumberErrorElement).toBeVisible(); + await expect(card.cardNumberErrorElement).toHaveText(PAN_ERROR_EMPTY); + }); -test('#4 PAN that consists of only 1 digit causes a "wrong length" error ', async ({ cardPage }) => { - const { card, page } = cardPage; + test('#4 PAN that consists of only 1 digit causes a "wrong length" error ', async ({ cardPage }) => { + const { card, page } = cardPage; - await card.typeCardNumber('4'); + await card.typeCardNumber('4'); - await cardPage.pay(); + await cardPage.pay(); - await expect(card.cardNumberErrorElement).toBeVisible(); - await expect(card.cardNumberErrorElement).toHaveText(PAN_ERROR_NOT_COMPLETE); + await expect(card.cardNumberErrorElement).toBeVisible(); + await expect(card.cardNumberErrorElement).toHaveText(PAN_ERROR_NOT_COMPLETE); + }); }); diff --git a/packages/e2e-playwright/tests/card/expiryDate/card.expiryDatePolicies.hidden.spec.ts b/packages/e2e-playwright/tests/card/expiryDate/card.expiryDatePolicies.hidden.spec.ts new file mode 100644 index 0000000000..abcaae00a4 --- /dev/null +++ b/packages/e2e-playwright/tests/card/expiryDate/card.expiryDatePolicies.hidden.spec.ts @@ -0,0 +1,124 @@ +import { test, expect } from '../../../pages/cards/card.fixture'; +import { SYNCHRONY_PLCC_NO_DATE, TEST_CVC_VALUE, ENCRYPTED_CARD_NUMBER, ENCRYPTED_EXPIRY_DATE, ENCRYPTED_SECURITY_CODE } from '../../utils/constants'; +import LANG from '../../../../lib/src/language/locales/en-US.json'; + +const PAN_ERROR = LANG['error.va.sf-cc-num.02']; +const DATE_INVALID_ERROR = LANG['error.va.sf-cc-dat.01']; +const DATE_EMPTY_ERROR = LANG['error.va.sf-cc-dat.04']; +const CVC_ERROR = LANG['error.va.sf-cc-cvc.01']; + +test.describe('Test how Card Component handles hidden expiryDate policy', () => { + test('#1 how UI & state respond', async ({ cardExpiryDatePoliciesPage }) => { + const { card, page } = cardExpiryDatePoliciesPage; + + await card.isComponentVisible(); + + // Fill number to provoke binLookup response + await card.typeCardNumber(SYNCHRONY_PLCC_NO_DATE); + + // UI reflects that binLookup says expiryDate is hidden + await expect(card.expiryDateField).not.toBeVisible(); + + await card.typeCvc(TEST_CVC_VALUE); + + // Card seen as valid + let cardValid = await page.evaluate('window.card.isValid'); + await expect(cardValid).toEqual(true); + + // Clear number + await card.deleteCardNumber(); + + // UI is reset + await expect(card.expiryDateField).toBeVisible(); + + // Card seen as invalid + cardValid = await page.evaluate('window.card.isValid'); + await expect(cardValid).toEqual(false); + }); + + test('#2 Validating fields first should see visible errors and then entering PAN should see errors cleared from state', async ({ + cardExpiryDatePoliciesPage + }) => { + const { card, page } = cardExpiryDatePoliciesPage; + + await card.isComponentVisible(); + + await cardExpiryDatePoliciesPage.pay(); + + // Expect errors in UI + await expect(card.cardNumberErrorElement).toBeVisible(); + await expect(card.cardNumberErrorElement).toHaveText(PAN_ERROR); + await expect(card.expiryDateErrorElement).toBeVisible(); + await expect(card.expiryDateErrorElement).toHaveText(DATE_EMPTY_ERROR); + await expect(card.cvcErrorElement).toBeVisible(); + await expect(card.cvcErrorElement).toHaveText(CVC_ERROR); + + // Expect errors in state + let cardErrors: any = await page.evaluate('window.card.state.errors'); + await expect(cardErrors[ENCRYPTED_CARD_NUMBER]).not.toBe(undefined); + await expect(cardErrors[ENCRYPTED_EXPIRY_DATE]).not.toBe(undefined); + await expect(cardErrors[ENCRYPTED_SECURITY_CODE]).not.toBe(undefined); + + // Fill number to provoke binLookup response + await card.typeCardNumber(SYNCHRONY_PLCC_NO_DATE); + + // Headless test seems to need time for UI reset to register on state + await page.waitForTimeout(500); + + // Expect card & date errors to be cleared - since the fields were in error because they were empty + // but now the PAN field is filled and the date field is hidden & so these fields have re-rendered and updated state + cardErrors = await page.evaluate('window.card.state.errors'); + await expect(cardErrors[ENCRYPTED_CARD_NUMBER]).toBe(null); + await expect(cardErrors[ENCRYPTED_EXPIRY_DATE]).toBe(null); + + // The cvc field should remain in error since it is required under this card brand's BIN + await expect(cardErrors[ENCRYPTED_SECURITY_CODE]).not.toBe(null); + }); + + test('#3 Hidden date field in error does not stop card becoming valid', async ({ cardExpiryDatePoliciesPage }) => { + const { card, page } = cardExpiryDatePoliciesPage; + + await card.isComponentVisible(); + + // Card out of date + await card.typeExpiryDate('12/90'); + + // Expect error in UI + await expect(card.expiryDateField).toBeVisible(); + await expect(card.expiryDateErrorElement).toBeVisible(); + await expect(card.expiryDateErrorElement).toHaveText(DATE_INVALID_ERROR); + + // Force blur event to fire on date field + await card.cardNumberLabelElement.click(); + + // Fill number to provoke binLookup response + await card.typeCardNumber(SYNCHRONY_PLCC_NO_DATE); + + // UI reflects that binLookup says expiryDate is hidden + await expect(card.expiryDateField).not.toBeVisible(); + await expect(card.expiryDateErrorElement).not.toBeVisible(); + + // complete fields + await card.typeCvc(TEST_CVC_VALUE); + + // Card seen as valid (despite date field technically being in error) + let cardValid = await page.evaluate('window.card.isValid'); + await expect(cardValid).toEqual(true); + + // Expect errors in state to remain + let cardErrors: any = await page.evaluate('window.card.state.errors'); + await expect(cardErrors[ENCRYPTED_EXPIRY_DATE]).not.toBe(undefined); + + // Clear number + await card.deleteCardNumber(); + + // Errors in UI visible again + await expect(card.expiryDateField).toBeVisible(); + await expect(card.expiryDateErrorElement).toBeVisible(); + await expect(card.expiryDateErrorElement).toHaveText(DATE_INVALID_ERROR); + + // Card is not valid + cardValid = await page.evaluate('window.card.isValid'); + await expect(cardValid).toEqual(false); + }); +}); diff --git a/packages/e2e-playwright/tests/card/expiryDate/card.expiryDatePolicies.optional.spec.ts b/packages/e2e-playwright/tests/card/expiryDate/card.expiryDatePolicies.optional.spec.ts new file mode 100644 index 0000000000..7895e1d722 --- /dev/null +++ b/packages/e2e-playwright/tests/card/expiryDate/card.expiryDatePolicies.optional.spec.ts @@ -0,0 +1,176 @@ +import { test, expect } from '../../../pages/cards/card.fixture'; +import { ENCRYPTED_CARD_NUMBER, ENCRYPTED_EXPIRY_DATE, ENCRYPTED_SECURITY_CODE, REGULAR_TEST_CARD } from '../../utils/constants'; +import { binLookupMock } from '../../../mocks/binLookup/binLookup.mock'; +import { optionalDateAndCvcMock } from '../../../mocks/binLookup/binLookup.data'; +import LANG from '../../../../lib/src/language/locales/en-US.json'; + +const DATE_LABEL = LANG['creditCard.expiryDateField.title']; +const CVC_LABEL = LANG['creditCard.cvcField.title']; +const CVC_LABEL_OPTIONAL = LANG['creditCard.cvcField.title.optional']; +const OPTIONAL = LANG['field.title.optional']; +const PAN_ERROR = LANG['error.va.sf-cc-num.02']; +const DATE_INVALID_ERROR = LANG['error.va.sf-cc-dat.01']; +const DATE_EMPTY_ERROR = LANG['error.va.sf-cc-dat.04']; +const CVC_ERROR = LANG['error.va.sf-cc-cvc.01']; + +test.describe('Test how Card Component handles optional expiryDate policy', () => { + test('#1 how UI & state respond', async ({ cardExpiryDatePoliciesPage }) => { + const { card, page } = cardExpiryDatePoliciesPage; + + await binLookupMock(page, optionalDateAndCvcMock); + + await card.isComponentVisible(); + + // Regular date label + await expect(card.expiryDateLabelText).toHaveText(DATE_LABEL); + + // Fill number to provoke (mock) binLookup response + await card.typeCardNumber(REGULAR_TEST_CARD); + + // UI reflects that binLookup says expiryDate is optional + await expect(card.expiryDateLabelText).toHaveText(`${DATE_LABEL} ${OPTIONAL}`); + + // ...and cvc is optional too + await expect(card.cvcLabelText).toHaveText(CVC_LABEL_OPTIONAL); + + // Card seen as valid + let cardValid = await page.evaluate('window.card.isValid'); + await expect(cardValid).toEqual(true); + + // Clear number and see UI & state reset + await card.deleteCardNumber(); + + // date and cvc labels don't contain 'optional' + await expect(card.expiryDateLabelText).toHaveText(DATE_LABEL); + await expect(card.cvcLabelText).toHaveText(CVC_LABEL); + + // Headless test seems to need time for UI reset to register on state + await page.waitForTimeout(500); + + // Card seen as invalid + cardValid = await page.evaluate('window.card.isValid'); + await expect(cardValid).toEqual(false); + }); + + test('#2 how securedFields responds', async ({ cardExpiryDatePoliciesPage }) => { + const { card, page } = cardExpiryDatePoliciesPage; + + await binLookupMock(page, optionalDateAndCvcMock); + + await card.isComponentVisible(); + + // Expect iframe's expiryDate (& cvc) input fields to have an aria-required attr set to true + let dateAriaRequired = await card.expiryDateInput.getAttribute('aria-required'); + await expect(dateAriaRequired).toEqual('true'); + + let cvcAriaRequired = await card.cvcInput.getAttribute('aria-required'); + await expect(cvcAriaRequired).toEqual('true'); + + // Fill number to provoke (mock) binLookup response + await card.typeCardNumber(REGULAR_TEST_CARD); + + // Expect iframe's expiryDate (& cvc) input fields to have an aria-required attr set to false + dateAriaRequired = await card.expiryDateInput.getAttribute('aria-required'); + await expect(dateAriaRequired).toEqual('false'); + + cvcAriaRequired = await card.cvcInput.getAttribute('aria-required'); + await expect(cvcAriaRequired).toEqual('false'); + + // Clear number and see SF's aria-required reset + await card.deleteCardNumber(); + + dateAriaRequired = await card.expiryDateInput.getAttribute('aria-required'); + await expect(dateAriaRequired).toEqual('true'); + + cvcAriaRequired = await card.cvcInput.getAttribute('aria-required'); + await expect(cvcAriaRequired).toEqual('true'); + }); + + test('#3 validating fields first and then entering PAN should see errors cleared from both UI & state', async ({ + cardExpiryDatePoliciesPage + }) => { + const { card, page } = cardExpiryDatePoliciesPage; + + await binLookupMock(page, optionalDateAndCvcMock); + + await card.isComponentVisible(); + + // press pay to generate errors + await cardExpiryDatePoliciesPage.pay(); + + // Expect errors in UI + await expect(card.cardNumberErrorElement).toBeVisible(); + await expect(card.cardNumberErrorElement).toHaveText(PAN_ERROR); + await expect(card.expiryDateErrorElement).toBeVisible(); + await expect(card.expiryDateErrorElement).toHaveText(DATE_EMPTY_ERROR); + await expect(card.cvcErrorElement).toBeVisible(); + await expect(card.cvcErrorElement).toHaveText(CVC_ERROR); + + // Expect errors in state + let cardErrors: any = await page.evaluate('window.card.state.errors'); + await expect(cardErrors[ENCRYPTED_CARD_NUMBER]).not.toBe(undefined); + await expect(cardErrors[ENCRYPTED_EXPIRY_DATE]).not.toBe(undefined); + await expect(cardErrors[ENCRYPTED_SECURITY_CODE]).not.toBe(undefined); + + // Fill number to provoke (mock) binLookup response + await card.typeCardNumber(REGULAR_TEST_CARD); + + // await page.waitForTimeout(5000); + + // Expect errors to be cleared - since the fields were in error because they were empty + // but now the PAN field is filled and the date & cvc field are optional & the fields have re-rendered and updated state + + // No errors in UI + await expect(card.cardNumberErrorElement).not.toBeVisible(); + await expect(card.expiryDateErrorElement).not.toBeVisible(); + await expect(card.cvcErrorElement).not.toBeVisible(); + + // No errors in state + cardErrors = await page.evaluate('window.card.state.errors'); + await expect(cardErrors[ENCRYPTED_CARD_NUMBER]).toBe(null); + await expect(cardErrors[ENCRYPTED_EXPIRY_DATE]).toBe(null); + await expect(cardErrors[ENCRYPTED_SECURITY_CODE]).toBe(null); + }); + + test('#4 date field in error DOES stop card becoming valid', async ({ cardExpiryDatePoliciesPage }) => { + const { card, page } = cardExpiryDatePoliciesPage; + + await binLookupMock(page, optionalDateAndCvcMock); + + await card.isComponentVisible(); + + // Card out of date + await card.typeExpiryDate('12/90'); + + // Expect error in UI + await expect(card.expiryDateErrorElement).toBeVisible(); + await expect(card.expiryDateErrorElement).toHaveText(DATE_INVALID_ERROR); + + // Force blur event to fire on date field + await card.cardNumberLabelElement.click(); + + // Fill number to provoke (mock) binLookup response + await card.typeCardNumber(REGULAR_TEST_CARD); + + // UI reflects that binLookup says expiryDate is optional + await expect(card.expiryDateLabelText).toHaveText(`${DATE_LABEL} ${OPTIONAL}`); + + // Visual errors persist in UI + await expect(card.expiryDateErrorElement).toBeVisible(); + await expect(card.expiryDateErrorElement).toHaveText(DATE_INVALID_ERROR); + + // Card seen as invalid + let cardValid = await page.evaluate('window.card.isValid'); + await expect(cardValid).toEqual(false); + + // Delete erroneous date + await card.deleteExpiryDate(); + + // Headless test seems to need time for UI reset to register on state + await page.waitForTimeout(500); + + // Card now seen as valid + cardValid = await page.evaluate('window.card.isValid'); + await expect(cardValid).toEqual(true); + }); +}); diff --git a/packages/e2e-playwright/tests/card/installments/card.installments.spec.ts b/packages/e2e-playwright/tests/card/installments/card.installments.spec.ts new file mode 100644 index 0000000000..4ca0ac8f0e --- /dev/null +++ b/packages/e2e-playwright/tests/card/installments/card.installments.spec.ts @@ -0,0 +1,87 @@ +import { test, expect } from '../../../pages/cards/card.fixture'; +import { pressKeyboardToNextItem } from '../../utils/keyboard'; +import { REGULAR_TEST_CARD, TEST_CVC_VALUE, TEST_DATE_VALUE } from '../../utils/constants'; + +test.describe('Cards (Installments)', () => { + test('#1 should not add installments property to payload if one-time payment is selected (default selection)', async ({ + cardInstallmentsPage + }) => { + const { card, page } = cardInstallmentsPage; + + await card.isComponentVisible(); + + await card.typeCardNumber(REGULAR_TEST_CARD); + await card.typeExpiryDate(TEST_DATE_VALUE); + await card.typeCvc(TEST_CVC_VALUE); + + // Inspect card.data + const paymentDataInstallments: any = await page.evaluate('window.card.data.installments'); + await expect(paymentDataInstallments).toBe(undefined); + }); + + test('#2 should not add installments property to payload if 1x installment is selected', async ({ cardInstallmentsPage }) => { + const { card, page } = cardInstallmentsPage; + + await card.isComponentVisible(); + + await card.typeCardNumber(REGULAR_TEST_CARD); + await card.typeExpiryDate(TEST_DATE_VALUE); + await card.typeCvc(TEST_CVC_VALUE); + + // Select option + await card.installmentsPaymentLabel.click(); + + // Inspect card.data + const paymentDataInstallments: any = await page.evaluate('window.card.data.installments'); + await expect(paymentDataInstallments).toBe(undefined); + }); + + test('#3 should add revolving plan to payload if selected', async ({ cardInstallmentsPage }) => { + const { card, page } = cardInstallmentsPage; + + await card.isComponentVisible(); + + await card.typeCardNumber(REGULAR_TEST_CARD); + await card.typeExpiryDate(TEST_DATE_VALUE); + await card.typeCvc(TEST_CVC_VALUE); + + // Select option + await card.revolvingPaymentLabel.click(); + + // Headless test seems to need time for click to register on state + await page.waitForTimeout(500); + + // Inspect card.data + const paymentDataInstallments: any = await page.evaluate('window.card.data.installments'); + await expect(paymentDataInstallments.value).toEqual(1); + await expect(paymentDataInstallments.plan).toEqual('revolving'); + }); + + test('#4 should add installments value property if regular installment > 1 is selected', async ({ cardInstallmentsPage }) => { + const { card, page } = cardInstallmentsPage; + + await card.isComponentVisible(); + + await card.typeCardNumber(REGULAR_TEST_CARD); + await card.typeExpiryDate(TEST_DATE_VALUE); + await card.typeCvc(TEST_CVC_VALUE); + + // Select option + await card.installmentsPaymentLabel.click(); + + await card.installmentsDropdown.click(); + await pressKeyboardToNextItem(page); + await pressKeyboardToNextItem(page); + // await pressKeyboardToSelectItem(page); + + const listItem = await card.selectListItem('2'); + await listItem.click(); + + // Headless test seems to need time for UI interaction to register on state + await page.waitForTimeout(500); + + // Inspect card.data + const paymentDataInstallments: any = await page.evaluate('window.card.data.installments'); + await expect(paymentDataInstallments.value).toEqual(2); + }); +}); diff --git a/packages/e2e-playwright/tests/customCard/expiryDate/customCard.regular.expiryDatePolicies.hidden.spec.ts b/packages/e2e-playwright/tests/customCard/expiryDate/customCard.regular.expiryDatePolicies.hidden.spec.ts new file mode 100644 index 0000000000..e9365271e3 --- /dev/null +++ b/packages/e2e-playwright/tests/customCard/expiryDate/customCard.regular.expiryDatePolicies.hidden.spec.ts @@ -0,0 +1,128 @@ +import { test, expect } from '../../../pages/customCard/customCard.fixture'; +import { ENCRYPTED_CARD_NUMBER, ENCRYPTED_EXPIRY_DATE, ENCRYPTED_SECURITY_CODE, REGULAR_TEST_CARD } from '../../utils/constants'; +import { binLookupMock } from '../../../mocks/binLookup/binLookup.mock'; +import { hiddenDateAndCvcMock } from '../../../mocks/binLookup/binLookup.data'; +import LANG from '../../../../lib/src/language/locales/en-US.json'; + +const PAN_ERROR = LANG['error.va.sf-cc-num.02']; +const DATE_INVALID_ERROR = LANG['error.va.sf-cc-dat.01']; +const DATE_EMPTY_ERROR = LANG['error.va.sf-cc-dat.04']; +const CVC_ERROR = LANG['error.va.sf-cc-cvc.01']; + +test.describe('Test how Custom Card Component with regular date field handles hidden expiryDate policy', () => { + test('#1 how UI & state respond', async ({ customCardPage }) => { + const { card, page } = customCardPage; + + await binLookupMock(page, hiddenDateAndCvcMock); + + await card.isComponentVisible(); + + // Fill number to provoke (mock) binLookup response + await card.typeCardNumber(REGULAR_TEST_CARD); + + // UI reflects that binLookup says expiryDate & cvc are hidden + await expect(card.expiryDateField).not.toBeVisible(); + await expect(card.cvcField).not.toBeVisible(); + + // Card seen as valid + let cardValid = await page.evaluate('window.customCard.isValid'); + await expect(cardValid).toEqual(true); + + // Clear number and see UI & state reset + await card.deleteCardNumber(); + await expect(card.expiryDateField).toBeVisible(); + await expect(card.cvcField).toBeVisible(); + }); + + test('#2 validating fields first and then entering PAN should see errors cleared from state', async ({ customCardPage }) => { + const { card, page } = customCardPage; + + await binLookupMock(page, hiddenDateAndCvcMock); + + await card.isComponentVisible(); + + // Click pay + await customCardPage.pay(); + + // Expect errors in UI + await expect(card.cardNumberErrorElement).toBeVisible(); + await expect(card.cardNumberErrorElement).toHaveText(PAN_ERROR); + await expect(card.expiryDateErrorElement).toBeVisible(); + await expect(card.expiryDateErrorElement).toHaveText(DATE_EMPTY_ERROR); + await expect(card.cvcErrorElement).toBeVisible(); + await expect(card.cvcErrorElement).toHaveText(CVC_ERROR); + + // Expect errors in state + let cardErrors: any = await page.evaluate('window.customCard.state.errors'); + await expect(cardErrors[ENCRYPTED_CARD_NUMBER]).not.toBe(undefined); + await expect(cardErrors[ENCRYPTED_EXPIRY_DATE]).not.toBe(undefined); + await expect(cardErrors[ENCRYPTED_SECURITY_CODE]).not.toBe(undefined); + + // Fill number to provoke (mock) binLookup response + await card.typeCardNumber(REGULAR_TEST_CARD); + + // Expect errors to be cleared - since the fields were in error because they were empty + // but now the PAN field is filled and the other fields are hidden, so the fields have re-rendered and updated state + cardErrors = await page.evaluate('window.customCard.state.errors'); + await expect(cardErrors[ENCRYPTED_CARD_NUMBER]).toBe(null); + await expect(cardErrors[ENCRYPTED_EXPIRY_DATE]).toBe(null); + await expect(cardErrors[ENCRYPTED_SECURITY_CODE]).toBe(null); + + // Card seen as valid + let cardValid = await page.evaluate('window.customCard.isValid'); + await expect(cardValid).toEqual(true); + }); + + test('#3 date field in error does not stop card becoming valid', async ({ customCardPage }) => { + const { card, page } = customCardPage; + + await binLookupMock(page, hiddenDateAndCvcMock); + + await card.isComponentVisible(); + + // Card out of date + await card.typeExpiryDate('12/90'); + + // Expect error in UI + await expect(card.expiryDateField).toBeVisible(); + await expect(card.expiryDateErrorElement).toBeVisible(); + await expect(card.expiryDateErrorElement).toHaveText(DATE_INVALID_ERROR); + + // Force blur event to fire on date field + await card.cardNumberLabelElement.click(); + + // Fill number to provoke (mock) binLookup response + await card.typeCardNumber(REGULAR_TEST_CARD); + + // UI reflects that binLookup says expiryDate & cvc are hidden + await expect(card.expiryDateField).not.toBeVisible(); + await expect(card.cvcField).not.toBeVisible(); + + // Card seen as valid (despite date field technically being in error) + let cardValid = await page.evaluate('window.customCard.isValid'); + await expect(cardValid).toEqual(true); + + // Expect error in state to remain + let cardErrors: any = await page.evaluate('window.customCard.state.errors'); + await expect(cardErrors[ENCRYPTED_EXPIRY_DATE]).not.toBe(undefined); + + await card.deleteCardNumber(); + + // Errors in UI visible again + await expect(card.expiryDateField).toBeVisible(); + await expect(card.expiryDateErrorElement).toBeVisible(); + await expect(card.expiryDateErrorElement).toHaveText(DATE_INVALID_ERROR); + + // CVC visible again + await expect(card.cvcField).toBeVisible(); + + // Headless test seems to need time for UI change to register on state + await page.waitForTimeout(500); + + // Card is not valid + cardValid = await page.evaluate('window.customCard.isValid'); + await expect(cardValid).toEqual(false); + + // await page.waitForTimeout(5000); + }); +}); diff --git a/packages/e2e-playwright/tests/customCard/expiryDate/customCard.regular.expiryDatePolicies.optional.spec.ts b/packages/e2e-playwright/tests/customCard/expiryDate/customCard.regular.expiryDatePolicies.optional.spec.ts new file mode 100644 index 0000000000..eb184d274b --- /dev/null +++ b/packages/e2e-playwright/tests/customCard/expiryDate/customCard.regular.expiryDatePolicies.optional.spec.ts @@ -0,0 +1,175 @@ +import { test, expect } from '../../../pages/customCard/customCard.fixture'; +import { ENCRYPTED_CARD_NUMBER, ENCRYPTED_EXPIRY_DATE, ENCRYPTED_SECURITY_CODE, REGULAR_TEST_CARD } from '../../utils/constants'; +import { binLookupMock } from '../../../mocks/binLookup/binLookup.mock'; +import { optionalDateAndCvcMock } from '../../../mocks/binLookup/binLookup.data'; +import LANG from '../../../../lib/src/language/locales/en-US.json'; + +const DATE_LABEL = LANG['creditCard.expiryDateField.title']; +const CVC_LABEL = LANG['creditCard.cvcField.title']; +const CVC_LABEL_OPTIONAL = LANG['creditCard.cvcField.title.optional']; +const OPTIONAL = LANG['field.title.optional']; + +const PAN_ERROR = LANG['error.va.sf-cc-num.02']; +const DATE_INVALID_ERROR = LANG['error.va.sf-cc-dat.01']; +const DATE_EMPTY_ERROR = LANG['error.va.sf-cc-dat.04']; +const CVC_ERROR = LANG['error.va.sf-cc-cvc.01']; + +test.describe('Test how Custom Card Component with regular date field handles hidden expiryDate policy', () => { + test('#1 how UI & state respond', async ({ customCardPage }) => { + const { card, page } = customCardPage; + + await binLookupMock(page, optionalDateAndCvcMock); + + await card.isComponentVisible(); + + // Regular date label + await expect(card.expiryDateLabelText).toHaveText(DATE_LABEL); + + // Fill number to provoke (mock) binLookup response + await card.typeCardNumber(REGULAR_TEST_CARD); + + // UI reflects that binLookup says expiryDate is optional + await expect(card.expiryDateLabelText).toHaveText(`${DATE_LABEL} ${OPTIONAL}`); + + // ...and cvc is optional too + await expect(card.cvcLabelText).toHaveText(CVC_LABEL_OPTIONAL); + + // Card seen as valid + let cardValid = await page.evaluate('window.customCard.isValid'); + await expect(cardValid).toEqual(true); + + // Clear number and see UI & state reset + await card.deleteCardNumber(); + + // Headless test seems to need time for UI change to register on state + await page.waitForTimeout(500); + + // date and cvc labels don't contain 'optional' + await expect(card.expiryDateLabelText).toHaveText(DATE_LABEL); + await expect(card.cvcLabelText).toHaveText(CVC_LABEL); + + // Card seen as invalid + cardValid = await page.evaluate('window.customCard.isValid'); + await expect(cardValid).toEqual(false); + + // await page.waitForTimeout(3000); + }); + + test('#2 how securedField responds', async ({ customCardPage }) => { + const { card, page } = customCardPage; + + await binLookupMock(page, optionalDateAndCvcMock); + + await card.isComponentVisible(); + + // Expect iframe's expiryDate (& cvc) input fields to have an aria-required attr set to true + let dateAriaRequired = await card.expiryDateInput.getAttribute('aria-required'); + await expect(dateAriaRequired).toEqual('true'); + + let cvcAriaRequired = await card.cvcInput.getAttribute('aria-required'); + await expect(cvcAriaRequired).toEqual('true'); + + // Fill number to provoke (mock) binLookup response + await card.typeCardNumber(REGULAR_TEST_CARD); + + // Expect iframe's expiryDate (& cvc) input fields to have an aria-required attr set to false + dateAriaRequired = await card.expiryDateInput.getAttribute('aria-required'); + await expect(dateAriaRequired).toEqual('false'); + + cvcAriaRequired = await card.cvcInput.getAttribute('aria-required'); + await expect(cvcAriaRequired).toEqual('false'); + + // Clear number and see SF's aria-required reset + await card.deleteCardNumber(); + + dateAriaRequired = await card.expiryDateInput.getAttribute('aria-required'); + await expect(dateAriaRequired).toEqual('true'); + + cvcAriaRequired = await card.cvcInput.getAttribute('aria-required'); + await expect(cvcAriaRequired).toEqual('true'); + }); + + test('#3 validating fields first and then entering PAN should see errors cleared from both UI & state', async ({ customCardPage }) => { + const { card, page } = customCardPage; + + await binLookupMock(page, optionalDateAndCvcMock); + + await card.isComponentVisible(); + + // press pay to generate errors + await customCardPage.pay(); + + // Expect errors in UI + await expect(card.cardNumberErrorElement).toBeVisible(); + await expect(card.cardNumberErrorElement).toHaveText(PAN_ERROR); + await expect(card.expiryDateErrorElement).toBeVisible(); + await expect(card.expiryDateErrorElement).toHaveText(DATE_EMPTY_ERROR); + await expect(card.cvcErrorElement).toBeVisible(); + await expect(card.cvcErrorElement).toHaveText(CVC_ERROR); + + // Expect errors in state + let cardErrors: any = await page.evaluate('window.customCard.state.errors'); + await expect(cardErrors[ENCRYPTED_CARD_NUMBER]).not.toBe(undefined); + await expect(cardErrors[ENCRYPTED_EXPIRY_DATE]).not.toBe(undefined); + await expect(cardErrors[ENCRYPTED_SECURITY_CODE]).not.toBe(undefined); + + // Fill number to provoke (mock) binLookup response + await card.typeCardNumber(REGULAR_TEST_CARD); + + // Expect errors to be cleared - since the fields were in error because they were empty + // but now the PAN field is filled and the date & cvc field are optional & the fields have re-rendered and updated state + + // No errors in UI + await expect(card.cardNumberErrorElement).not.toBeVisible(); + await expect(card.expiryDateErrorElement).not.toBeVisible(); + await expect(card.cvcErrorElement).not.toBeVisible(); + + // No errors in state + cardErrors = await page.evaluate('window.customCard.state.errors'); + await expect(cardErrors[ENCRYPTED_CARD_NUMBER]).toBe(null); + await expect(cardErrors[ENCRYPTED_EXPIRY_DATE]).toBe(null); + await expect(cardErrors[ENCRYPTED_SECURITY_CODE]).toBe(null); + }); + + test('#4 date field in error DOES stop card becoming valid', async ({ customCardPage }) => { + const { card, page } = customCardPage; + + await binLookupMock(page, optionalDateAndCvcMock); + + await card.isComponentVisible(); + + // Card out of date + await card.typeExpiryDate('12/90'); + + // Expect error in UI + await expect(card.expiryDateErrorElement).toBeVisible(); + await expect(card.expiryDateErrorElement).toHaveText(DATE_INVALID_ERROR); + + // Force blur event to fire on date field + await card.cardNumberLabelElement.click(); + + // Fill number to provoke (mock) binLookup response + await card.typeCardNumber(REGULAR_TEST_CARD); + + // UI reflects that binLookup says expiryDate is optional + await expect(card.expiryDateLabelText).toHaveText(`${DATE_LABEL} ${OPTIONAL}`); + + // Visual errors persist in UI + await expect(card.expiryDateErrorElement).toBeVisible(); + await expect(card.expiryDateErrorElement).toHaveText(DATE_INVALID_ERROR); + + // Card seen as invalid + let cardValid = await page.evaluate('window.customCard.isValid'); + await expect(cardValid).toEqual(false); + + // Delete erroneous date + await card.deleteExpiryDate(); + + // Headless test seems to need time for UI reset to register on state + await page.waitForTimeout(500); + + // Card now seen as valid + cardValid = await page.evaluate('window.customCard.isValid'); + await expect(cardValid).toEqual(true); + }); +}); diff --git a/packages/e2e-playwright/tests/customCard/expiryDate/customCard.separate.expiryDatePolicies.hidden.spec.ts b/packages/e2e-playwright/tests/customCard/expiryDate/customCard.separate.expiryDatePolicies.hidden.spec.ts new file mode 100644 index 0000000000..8729823aab --- /dev/null +++ b/packages/e2e-playwright/tests/customCard/expiryDate/customCard.separate.expiryDatePolicies.hidden.spec.ts @@ -0,0 +1,143 @@ +import { test, expect } from '../../../pages/customCard/customCard.fixture'; +import { + ENCRYPTED_CARD_NUMBER, + ENCRYPTED_EXPIRY_MONTH, + ENCRYPTED_EXPIRY_YEAR, + ENCRYPTED_SECURITY_CODE, + REGULAR_TEST_CARD +} from '../../utils/constants'; +import { binLookupMock } from '../../../mocks/binLookup/binLookup.mock'; +import { hiddenDateAndCvcMock } from '../../../mocks/binLookup/binLookup.data'; +import LANG from '../../../../lib/src/language/locales/en-US.json'; + +const PAN_ERROR = LANG['error.va.sf-cc-num.02']; +const MONTH_EMPTY_ERROR = LANG['error.va.sf-cc-mth.01']; +const YEAR_EMPTY_ERROR = LANG['error.va.sf-cc-yr.01']; +const DATE_INVALID_ERROR = LANG['error.va.sf-cc-dat.02']; +const CVC_ERROR = LANG['error.va.sf-cc-cvc.01']; + +test.describe('Test how Custom Card Component with separate date field handles hidden expiryDate policy', () => { + test('#1 how UI & state respond', async ({ customCardPageSeparate }) => { + const { card, page } = customCardPageSeparate; + + await binLookupMock(page, hiddenDateAndCvcMock); + + await card.isSeparateComponentVisible(); + + // Fill number to provoke (mock) binLookup response + await card.typeCardNumber(REGULAR_TEST_CARD); + + // UI reflects that binLookup says date fields & cvc are hidden + await expect(card.expiryMonthField).not.toBeVisible(); + await expect(card.expiryYearField).not.toBeVisible(); + await expect(card.cvcField).not.toBeVisible(); + + // Card seen as valid + let cardValid = await page.evaluate('window.customCardSeparate.isValid'); + await expect(cardValid).toEqual(true); + + // Clear number and see UI & state reset + await card.deleteCardNumber(); + await expect(card.expiryMonthField).toBeVisible(); + await expect(card.expiryYearField).toBeVisible(); + await expect(card.cvcField).toBeVisible(); + }); + + test('#2 validating fields first and then entering PAN should see errors cleared from state', async ({ customCardPageSeparate }) => { + const { card, page } = customCardPageSeparate; + + await binLookupMock(page, hiddenDateAndCvcMock); + + await card.isSeparateComponentVisible(); + + // Click pay + await customCardPageSeparate.pay('Separate'); + + // Expect errors in UI + await expect(card.cardNumberErrorElement).toBeVisible(); + await expect(card.cardNumberErrorElement).toHaveText(PAN_ERROR); + await expect(card.expiryMonthErrorElement).toBeVisible(); + await expect(card.expiryMonthErrorElement).toHaveText(MONTH_EMPTY_ERROR); + await expect(card.expiryYearErrorElement).toBeVisible(); + await expect(card.expiryYearErrorElement).toHaveText(YEAR_EMPTY_ERROR); + await expect(card.cvcErrorElement).toBeVisible(); + await expect(card.cvcErrorElement).toHaveText(CVC_ERROR); + + // Expect errors in state + let cardErrors: any = await page.evaluate('window.customCardSeparate.state.errors'); + await expect(cardErrors[ENCRYPTED_CARD_NUMBER]).not.toBe(undefined); + await expect(cardErrors[ENCRYPTED_EXPIRY_MONTH]).not.toBe(undefined); + await expect(cardErrors[ENCRYPTED_EXPIRY_YEAR]).not.toBe(undefined); + await expect(cardErrors[ENCRYPTED_SECURITY_CODE]).not.toBe(undefined); + + // Fill number to provoke (mock) binLookup response + await card.typeCardNumber(REGULAR_TEST_CARD); + + // Expect errors to be cleared - since the fields were in error because they were empty + // but now the PAN field is filled and the other fields are hidden, so the fields have re-rendered and updated state + cardErrors = await page.evaluate('window.customCardSeparate.state.errors'); + await expect(cardErrors[ENCRYPTED_CARD_NUMBER]).toBe(null); + await expect(cardErrors[ENCRYPTED_EXPIRY_MONTH]).toBe(null); + await expect(cardErrors[ENCRYPTED_EXPIRY_YEAR]).toBe(null); + await expect(cardErrors[ENCRYPTED_SECURITY_CODE]).toBe(null); + + // Card seen as valid + let cardValid = await page.evaluate('window.customCardSeparate.isValid'); + await expect(cardValid).toEqual(true); + }); + + test('#3 date field in error does not stop card becoming valid', async ({ customCardPageSeparate }) => { + const { card, page } = customCardPageSeparate; + + await binLookupMock(page, hiddenDateAndCvcMock); + + await card.isSeparateComponentVisible(); + + // Card out of date + await card.typeExpiryMonth('12'); + await card.typeExpiryYear('90'); + + // Expect error in UI + await expect(card.expiryYearErrorElement).toBeVisible(); + await expect(card.expiryYearErrorElement).toHaveText(DATE_INVALID_ERROR); + + // Force blur event to fire on year field + await card.cardNumberLabelElement.click(); + + // Fill number to provoke (mock) binLookup response + await card.typeCardNumber(REGULAR_TEST_CARD); + + // UI reflects that binLookup says date fields & cvc are hidden + await expect(card.expiryMonthField).not.toBeVisible(); + await expect(card.expiryYearField).not.toBeVisible(); + await expect(card.cvcField).not.toBeVisible(); + + // Card seen as valid (despite date technically being in error) + let cardValid = await page.evaluate('window.customCardSeparate.isValid'); + await expect(cardValid).toEqual(true); + + // Expect error in state to remain + let cardErrors: any = await page.evaluate('window.customCardSeparate.state.errors'); + await expect(cardErrors[ENCRYPTED_EXPIRY_YEAR]).not.toBe(undefined); + + await card.deleteCardNumber(); + + // Errors in UI visible again + await expect(card.expiryYearField).toBeVisible(); + await expect(card.expiryYearErrorElement).toBeVisible(); + await expect(card.expiryYearErrorElement).toHaveText(DATE_INVALID_ERROR); + + // Other fields visible again + await expect(card.expiryMonthField).toBeVisible(); + await expect(card.cvcField).toBeVisible(); + + // Headless test seems to need time for UI change to register on state + await page.waitForTimeout(500); + + // Card is not valid + cardValid = await page.evaluate('window.customCardSeparate.isValid'); + await expect(cardValid).toEqual(false); + + // await page.waitForTimeout(3000); + }); +}); diff --git a/packages/e2e-playwright/tests/customCard/expiryDate/customCard.separate.expiryDatePolicies.optional.spec.ts b/packages/e2e-playwright/tests/customCard/expiryDate/customCard.separate.expiryDatePolicies.optional.spec.ts new file mode 100644 index 0000000000..84a5f64d2b --- /dev/null +++ b/packages/e2e-playwright/tests/customCard/expiryDate/customCard.separate.expiryDatePolicies.optional.spec.ts @@ -0,0 +1,208 @@ +import { test, expect } from '../../../pages/customCard/customCard.fixture'; +import { + ENCRYPTED_CARD_NUMBER, + ENCRYPTED_EXPIRY_MONTH, + ENCRYPTED_EXPIRY_YEAR, + ENCRYPTED_SECURITY_CODE, + REGULAR_TEST_CARD +} from '../../utils/constants'; +import { binLookupMock } from '../../../mocks/binLookup/binLookup.mock'; +import { optionalDateAndCvcMock } from '../../../mocks/binLookup/binLookup.data'; +import LANG from '../../../../lib/src/language/locales/en-US.json'; + +const MONTH_LABEL = 'Expiry month'; +const YEAR_LABEL = 'Expiry year'; +const CVC_LABEL = 'Security code'; +const OPTIONAL = LANG['field.title.optional']; + +const PAN_ERROR = LANG['error.va.sf-cc-num.02']; +const MONTH_EMPTY_ERROR = LANG['error.va.sf-cc-mth.01']; +const YEAR_EMPTY_ERROR = LANG['error.va.sf-cc-yr.01']; +const CVC_ERROR = LANG['error.va.sf-cc-cvc.01']; +const DATE_INVALID_ERROR = LANG['error.va.sf-cc-dat.02']; + +test.describe('Test how Custom Card Component with separate date fields handles hidden expiryDate policy', () => { + test('#1 how UI & state respond', async ({ customCardPageSeparate }) => { + const { card, page } = customCardPageSeparate; + + await binLookupMock(page, optionalDateAndCvcMock); + + await card.isSeparateComponentVisible(); + + // Regular date labels + await expect(card.expiryMonthLabelText).toHaveText(MONTH_LABEL); + await expect(card.expiryYearLabelText).toHaveText(YEAR_LABEL); + + // Fill number to provoke (mock) binLookup response + await card.typeCardNumber(REGULAR_TEST_CARD); + + // await page.waitForTimeout(5000); + + // UI reflects that binLookup says date fields are optional + await expect(card.expiryMonthLabelText).toHaveText(`${MONTH_LABEL} ${OPTIONAL}`); + await expect(card.expiryYearLabelText).toHaveText(`${YEAR_LABEL} ${OPTIONAL}`); + + // ...and cvc is optional too + await expect(card.cvcLabelText).toHaveText(`${CVC_LABEL} ${OPTIONAL}`); + + // Card seen as valid + let cardValid = await page.evaluate('window.customCardSeparate.isValid'); + await expect(cardValid).toEqual(true); + + // Clear number and see UI & state reset + await card.deleteCardNumber(); + + // Headless test seems to need time for UI change to register on state + await page.waitForTimeout(500); + + // date and cvc labels don't contain 'optional' + await expect(card.expiryMonthLabelText).toHaveText(MONTH_LABEL); + await expect(card.expiryYearLabelText).toHaveText(YEAR_LABEL); + await expect(card.cvcLabelText).toHaveText(CVC_LABEL); + + // Card seen as invalid + cardValid = await page.evaluate('window.customCard.isValid'); + await expect(cardValid).toEqual(false); + }); + + test('#2 how securedField responds', async ({ customCardPageSeparate }) => { + const { card, page } = customCardPageSeparate; + + await binLookupMock(page, optionalDateAndCvcMock); + + await card.isSeparateComponentVisible(); + + // Expect iframe's date (& cvc) input fields to have an aria-required attr set to true + let monthAriaRequired = await card.expiryMonthInput.getAttribute('aria-required'); + await expect(monthAriaRequired).toEqual('true'); + + let yearAriaRequired = await card.expiryYearInput.getAttribute('aria-required'); + await expect(yearAriaRequired).toEqual('true'); + + let cvcAriaRequired = await card.cvcInput.getAttribute('aria-required'); + await expect(cvcAriaRequired).toEqual('true'); + + // Fill number to provoke (mock) binLookup response + await card.typeCardNumber(REGULAR_TEST_CARD); + + // Expect iframe's date (& cvc) input fields to have an aria-required attr set to false + monthAriaRequired = await card.expiryMonthInput.getAttribute('aria-required'); + await expect(monthAriaRequired).toEqual('false'); + + yearAriaRequired = await card.expiryYearInput.getAttribute('aria-required'); + await expect(yearAriaRequired).toEqual('false'); + + cvcAriaRequired = await card.cvcInput.getAttribute('aria-required'); + await expect(cvcAriaRequired).toEqual('false'); + + // Clear number and see SF's aria-required reset + await card.deleteCardNumber(); + + monthAriaRequired = await card.expiryMonthInput.getAttribute('aria-required'); + await expect(monthAriaRequired).toEqual('true'); + + yearAriaRequired = await card.expiryYearInput.getAttribute('aria-required'); + await expect(yearAriaRequired).toEqual('true'); + + cvcAriaRequired = await card.cvcInput.getAttribute('aria-required'); + await expect(cvcAriaRequired).toEqual('true'); + }); + + test('#3 validating fields first and then entering PAN should see errors cleared from both UI & state', async ({ customCardPageSeparate }) => { + const { card, page } = customCardPageSeparate; + + await binLookupMock(page, optionalDateAndCvcMock); + + await card.isSeparateComponentVisible(); + + // press pay to generate errors + await customCardPageSeparate.pay('Separate'); + + // Expect errors in UI + await expect(card.cardNumberErrorElement).toBeVisible(); + await expect(card.cardNumberErrorElement).toHaveText(PAN_ERROR); + await expect(card.expiryMonthErrorElement).toBeVisible(); + await expect(card.expiryMonthErrorElement).toHaveText(MONTH_EMPTY_ERROR); + await expect(card.expiryYearErrorElement).toBeVisible(); + await expect(card.expiryYearErrorElement).toHaveText(YEAR_EMPTY_ERROR); + await expect(card.cvcErrorElement).toBeVisible(); + await expect(card.cvcErrorElement).toHaveText(CVC_ERROR); + + // await page.waitForTimeout(3000); + + // Expect errors in state + let cardErrors: any = await page.evaluate('window.customCardSeparate.state.errors'); + await expect(cardErrors[ENCRYPTED_CARD_NUMBER]).not.toBe(undefined); + await expect(cardErrors[ENCRYPTED_EXPIRY_MONTH]).not.toBe(undefined); + await expect(cardErrors[ENCRYPTED_EXPIRY_YEAR]).not.toBe(undefined); + await expect(cardErrors[ENCRYPTED_SECURITY_CODE]).not.toBe(undefined); + + // Fill number to provoke (mock) binLookup response + await card.typeCardNumber(REGULAR_TEST_CARD); + + // Expect errors to be cleared - since the fields were in error because they were empty + // but now the PAN field is filled and the date & cvc field are optional & the fields have re-rendered and updated state + + // No errors in UI + await expect(card.cardNumberErrorElement).not.toBeVisible(); + await expect(card.expiryMonthErrorElement).not.toBeVisible(); + await expect(card.expiryYearErrorElement).not.toBeVisible(); + await expect(card.cvcErrorElement).not.toBeVisible(); + + // No errors in state + cardErrors = await page.evaluate('window.customCardSeparate.state.errors'); + await expect(cardErrors[ENCRYPTED_CARD_NUMBER]).toBe(null); + await expect(cardErrors[ENCRYPTED_EXPIRY_MONTH]).toBe(null); + await expect(cardErrors[ENCRYPTED_EXPIRY_YEAR]).toBe(null); + await expect(cardErrors[ENCRYPTED_SECURITY_CODE]).toBe(null); + + // Card seen as valid - now we have a PAN and all the other fields are optional + let cardValid = await page.evaluate('window.customCardSeparate.isValid'); + await expect(cardValid).toEqual(true); + }); + + test('#4 date field in error DOES stop card becoming valid', async ({ customCardPageSeparate }) => { + const { card, page } = customCardPageSeparate; + + await binLookupMock(page, optionalDateAndCvcMock); + + await card.isSeparateComponentVisible(); + + // Card out of date + await card.typeExpiryMonth('12'); + await card.typeExpiryYear('90'); + + // Expect error in UI + await expect(card.expiryYearErrorElement).toBeVisible(); + await expect(card.expiryYearErrorElement).toHaveText(DATE_INVALID_ERROR); + + // Force blur event to fire on date field + await card.cardNumberLabelElement.click(); + + // Fill number to provoke (mock) binLookup response + await card.typeCardNumber(REGULAR_TEST_CARD); + + // UI reflects that binLookup says expiryDate is optional + await expect(card.expiryMonthLabelText).toHaveText(`${MONTH_LABEL} ${OPTIONAL}`); + await expect(card.expiryYearLabelText).toHaveText(`${YEAR_LABEL} ${OPTIONAL}`); + + // Visual errors persist in UI + await expect(card.expiryYearErrorElement).toBeVisible(); + await expect(card.expiryYearErrorElement).toHaveText(DATE_INVALID_ERROR); + + // Card seen as invalid + let cardValid = await page.evaluate('window.customCardSeparate.isValid'); + await expect(cardValid).toEqual(false); + + // Delete erroneous date + await card.deleteExpiryMonth(); + await card.deleteExpiryYear(); + + // Headless test seems to need time for UI reset to register on state + await page.waitForTimeout(500); + + // Card now seen as valid + cardValid = await page.evaluate('window.customCardSeparate.isValid'); + await expect(cardValid).toEqual(true); + }); +}); diff --git a/packages/e2e-playwright/tests/dropin/cardBrands/cardBrands.compact.excluded.spec.ts b/packages/e2e-playwright/tests/dropin/cardBrands/cardBrands.compact.excluded.spec.ts new file mode 100644 index 0000000000..d05bde957e --- /dev/null +++ b/packages/e2e-playwright/tests/dropin/cardBrands/cardBrands.compact.excluded.spec.ts @@ -0,0 +1,45 @@ +import { test, expect } from '../../../pages/dropin/dropin.fixture'; +import { getCreditCardPM } from '../../../models/dropinModelUtils/getDropinCardComp'; + +test.describe('Dropin: How Credit Card brand logos display with "showBrandsUnderCardNumber" set to its default, true; when "excluded" brands exists', () => { + test('#1 A truncated list of brands show up on the Card Payment Method and excluded items are also removed', async ({ + dropinPage_cardBrands_compactView_withExcluded + }) => { + const { dropin, page } = dropinPage_cardBrands_compactView_withExcluded; + + await dropin.isComponentVisible(); + + const creditCard = getCreditCardPM(dropin); + await creditCard.pm.scrollIntoViewIfNeeded(); + + const imgCount = await creditCard.getImageCount(creditCard.brandsHolder); + + await expect(creditCard.brandsHolder).toBeVisible(); + + await expect(imgCount).toEqual(3); + + await expect(creditCard.brandsText).toBeVisible(); + await expect(creditCard.brandsText).toHaveText('+3'); + }); + + test('#2 After clicking on the Card PaymentMethodItem, the brands disappear from the header and show beneath Card Number (with excluded brands absent)', async ({ + dropinPage_cardBrands_compactView_withExcluded + }) => { + const { dropin, page } = dropinPage_cardBrands_compactView_withExcluded; + + await dropin.isComponentVisible(); + + const creditCard = getCreditCardPM(dropin); + await creditCard.pm.scrollIntoViewIfNeeded(); + + await creditCard.pm.click(); + + await expect(creditCard.brandsHolder).not.toBeVisible(); + await expect(creditCard.brandsText).not.toBeVisible(); + + // Brands inside actual Credit Card component + const imgCount = await creditCard.getImageCount(creditCard.componentBrandsHolder); + + await expect(imgCount).toEqual(6); + }); +}); diff --git a/packages/e2e-playwright/tests/dropin/cardBrands/cardBrands.compact.spec.ts b/packages/e2e-playwright/tests/dropin/cardBrands/cardBrands.compact.spec.ts new file mode 100644 index 0000000000..5336fee14d --- /dev/null +++ b/packages/e2e-playwright/tests/dropin/cardBrands/cardBrands.compact.spec.ts @@ -0,0 +1,43 @@ +import { test, expect } from '../../../pages/dropin/dropin.fixture'; +import { getCreditCardPM } from '../../../models/dropinModelUtils/getDropinCardComp'; + +test.describe('Dropin: How Credit Card brand logos display with "showBrandsUnderCardNumber" set to its default, true', () => { + test('#1 A truncated list of brands show up on the Card Payment Method', async ({ dropinPage_cardBrands_compactView }) => { + const { dropin, page } = dropinPage_cardBrands_compactView; + + await dropin.isComponentVisible(); + + const creditCard = getCreditCardPM(dropin); + await creditCard.pm.scrollIntoViewIfNeeded(); + + const imgCount = await creditCard.getImageCount(creditCard.brandsHolder); + + await expect(creditCard.brandsHolder).toBeVisible(); + + await expect(imgCount).toEqual(3); + + await expect(creditCard.brandsText).toBeVisible(); + await expect(creditCard.brandsText).toHaveText('+7'); + }); + + test('#2 After clicking on the Card PaymentMethodItem, the brands disappear from the header and show beneath Card Number', async ({ + dropinPage_cardBrands_compactView + }) => { + const { dropin, page } = dropinPage_cardBrands_compactView; + + await dropin.isComponentVisible(); + + const creditCard = getCreditCardPM(dropin); + await creditCard.pm.scrollIntoViewIfNeeded(); + + await creditCard.pm.click(); + + await expect(creditCard.brandsHolder).not.toBeVisible(); + await expect(creditCard.brandsText).not.toBeVisible(); + + // Brands inside actual Credit Card component + const imgCount = await creditCard.getImageCount(creditCard.componentBrandsHolder); + + await expect(imgCount).toEqual(10); + }); +}); diff --git a/packages/e2e-playwright/tests/dropin/cardBrands/cardBrands.default.excluded.spec.ts b/packages/e2e-playwright/tests/dropin/cardBrands/cardBrands.default.excluded.spec.ts new file mode 100644 index 0000000000..1fc7e21113 --- /dev/null +++ b/packages/e2e-playwright/tests/dropin/cardBrands/cardBrands.default.excluded.spec.ts @@ -0,0 +1,43 @@ +import { test, expect } from '../../../pages/dropin/dropin.fixture'; +import { getCreditCardPM } from '../../../models/dropinModelUtils/getDropinCardComp'; + +test.describe('Dropin: How Credit Card brand logos display with "showBrandsUnderCardNumber" equals false when "excluded" brands exists', () => { + test('#1 Only a subset of brands show up on the Card PaymentMethodItem', async ({ dropinPage_cardBrands_defaultView_withExcluded }) => { + const { dropin, page } = dropinPage_cardBrands_defaultView_withExcluded; + + await dropin.isComponentVisible(); + + const creditCard = getCreditCardPM(dropin); + await creditCard.pm.scrollIntoViewIfNeeded(); + + const imgCount = await creditCard.getImageCount(creditCard.brandsHolder); + + await expect(creditCard.brandsHolder).toBeVisible(); + + await expect(imgCount).toEqual(6); + + await expect(creditCard.brandsText).not.toBeVisible(); + }); + + test('#2 Only a subset of brands are kept in the Card PaymentMethodItem after clicking on it', async ({ + dropinPage_cardBrands_defaultView_withExcluded + }) => { + const { dropin, page } = dropinPage_cardBrands_defaultView_withExcluded; + + await dropin.isComponentVisible(); + + const creditCard = getCreditCardPM(dropin); + await creditCard.pm.scrollIntoViewIfNeeded(); + + await creditCard.pm.click(); + + const imgCount = await creditCard.getImageCount(creditCard.brandsHolder); + + await expect(creditCard.brandsHolder).toBeVisible(); + + await expect(imgCount).toEqual(6); + + // Brands inside actual Credit Card component + await expect(creditCard.componentBrandsHolder).not.toBeVisible(); + }); +}); diff --git a/packages/e2e-playwright/tests/dropin/cardBrands/cardBrands.default.spec.ts b/packages/e2e-playwright/tests/dropin/cardBrands/cardBrands.default.spec.ts new file mode 100644 index 0000000000..d498df0349 --- /dev/null +++ b/packages/e2e-playwright/tests/dropin/cardBrands/cardBrands.default.spec.ts @@ -0,0 +1,41 @@ +import { test, expect } from '../../../pages/dropin/dropin.fixture'; +import { getCreditCardPM } from '../../../models/dropinModelUtils/getDropinCardComp'; + +test.describe('Dropin: How Credit Card brand logos display with "showBrandsUnderCardNumber" equals false', () => { + test('#1 All available brands show up on the Card PaymentMethodItem', async ({ dropinPage_cardBrands_defaultView }) => { + const { dropin, page } = dropinPage_cardBrands_defaultView; + + await dropin.isComponentVisible(); + + const creditCard = getCreditCardPM(dropin); + await creditCard.pm.scrollIntoViewIfNeeded(); + + const imgCount = await creditCard.getImageCount(creditCard.brandsHolder); + + await expect(creditCard.brandsHolder).toBeVisible(); + + await expect(imgCount).toEqual(10); + + await expect(creditCard.brandsText).not.toBeVisible(); + }); + + test('#2 All available brands are kept in the Card PaymentMethodItem after clicking on it', async ({ dropinPage_cardBrands_defaultView }) => { + const { dropin, page } = dropinPage_cardBrands_defaultView; + + await dropin.isComponentVisible(); + + const creditCard = getCreditCardPM(dropin); + await creditCard.pm.scrollIntoViewIfNeeded(); + + await creditCard.pm.click(); + + const imgCount = await creditCard.getImageCount(creditCard.brandsHolder); + + await expect(creditCard.brandsHolder).toBeVisible(); + + await expect(imgCount).toEqual(10); + + // Brands inside actual Credit Card component + await expect(creditCard.componentBrandsHolder).not.toBeVisible(); + }); +}); diff --git a/packages/e2e-playwright/tests/issuerList/issue-list.spec.ts b/packages/e2e-playwright/tests/issuerList/issue-list.spec.ts deleted file mode 100644 index cce6a5ba1b..0000000000 --- a/packages/e2e-playwright/tests/issuerList/issue-list.spec.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { test, expect } from '../../pages/issuerList/issuer-list.fixture'; - -test('should select highlighted issuer and update pay button label', async ({ issuerListPage }) => { - const { issuerList } = issuerListPage; - - await issuerList.selectHighlightedIssuer('Test Issuer 5'); - await expect(issuerList.submitButton).toHaveText('Continue to Test Issuer 5'); - - await issuerList.selectHighlightedIssuer('Test Issuer 4'); - await expect(issuerList.submitButton).toHaveText('Continue to Test Issuer 4'); - - await expect(issuerList.highlightedIssuerButtonGroup.getByRole('button', { pressed: true })).toHaveText('Test Issuer 4'); - await expect(issuerList.selectorCombobox).toHaveValue('Select your bank'); -}); - -test('it should be able to filter and select using the keyboard', async ({ issuerListPage }) => { - const { issuerList } = issuerListPage; - - await expect(issuerList.submitButton).toHaveText('Continue'); - - await issuerList.clickOnSelector(); - await expect(issuerList.selectorList).toContainText('SNS'); - - await issuerList.typeOnSelectorField('Test'); - await expect(issuerList.selectorList).not.toContainText('SNS'); - - await issuerList.pressKeyboardToNextItem(); - await issuerList.pressKeyboardToNextItem(); - await issuerList.pressKeyboardToSelectItem(); - - await expect(issuerList.submitButton).toHaveText('Continue to Test Issuer 5'); - - // 1st press opens the dropdown - await issuerList.pressKeyboardToNextItem(); - // 2nd selects next items - await issuerList.pressKeyboardToNextItem(); - await issuerList.pressKeyboardToSelectItem(); - - await expect(issuerList.submitButton).toHaveText('Continue to Test Issuer 4'); -}); - -test('it should load a default when pressing enter', async ({ issuerListPage }) => { - const { issuerList } = issuerListPage; - - await issuerList.clickOnSelector(); - await issuerList.typeOnSelectorField('Test'); - await issuerList.pressKeyboardToSelectItem(); - - await expect(issuerList.submitButton).toHaveText('Continue to Test Issuer'); -}); diff --git a/packages/e2e-playwright/tests/issuerList/issuer-list.spec.ts b/packages/e2e-playwright/tests/issuerList/issuer-list.spec.ts new file mode 100644 index 0000000000..3f48ae0743 --- /dev/null +++ b/packages/e2e-playwright/tests/issuerList/issuer-list.spec.ts @@ -0,0 +1,52 @@ +import { test, expect } from '../../pages/issuerList/issuer-list.fixture'; +import { pressKeyboardToNextItem, pressKeyboardToSelectItem } from '../utils/keyboard'; + +test.describe('Issuer List', () => { + test('should select highlighted issuer and update pay button label', async ({ issuerListPage }) => { + const { issuerList } = issuerListPage; + + await issuerList.selectHighlightedIssuer('Test Issuer 5'); + await expect(issuerList.submitButton).toHaveText('Continue to Test Issuer 5'); + + await issuerList.selectHighlightedIssuer('Test Issuer 4'); + await expect(issuerList.submitButton).toHaveText('Continue to Test Issuer 4'); + + await expect(issuerList.highlightedIssuerButtonGroup.getByRole('button', { pressed: true })).toHaveText('Test Issuer 4'); + }); + + test('it should be able to filter and select using the keyboard', async ({ issuerListPage }) => { + const { issuerList, page } = issuerListPage; + + await expect(issuerList.submitButton).toHaveText('Continue'); + + await issuerList.clickOnSelector(); + await expect(issuerList.selectorList).toContainText('SNS'); + + await issuerList.typeOnSelectorField('Test'); + await expect(issuerList.selectorList).not.toContainText('SNS'); + + await pressKeyboardToNextItem(page); + await pressKeyboardToNextItem(page); + await pressKeyboardToSelectItem(page); + + await expect(issuerList.submitButton).toHaveText('Continue to Test Issuer 5'); + + // 1st press opens the dropdown + await pressKeyboardToNextItem(page); + // 2nd selects next items + await pressKeyboardToNextItem(page); + await pressKeyboardToSelectItem(page); + + await expect(issuerList.submitButton).toHaveText('Continue to Test Issuer 4'); + }); + + test('it should load a default when pressing enter', async ({ issuerListPage }) => { + const { issuerList, page } = issuerListPage; + + await issuerList.clickOnSelector(); + await issuerList.typeOnSelectorField('Test'); + await pressKeyboardToSelectItem(page); + + await expect(issuerList.submitButton).toHaveText('Continue to Test Issuer'); + }); +}); diff --git a/packages/e2e-playwright/tests/utils/constants.ts b/packages/e2e-playwright/tests/utils/constants.ts index e57f34aa13..f2f25375a5 100644 --- a/packages/e2e-playwright/tests/utils/constants.ts +++ b/packages/e2e-playwright/tests/utils/constants.ts @@ -1,6 +1,13 @@ export const BIN_LOOKUP_VERSION = 'v3'; export const REGULAR_TEST_CARD = '5500000000000004'; +export const AMEX_CARD = '370000000000002'; + +export const MAESTRO_CARD = '5000550000000029'; + +export const SYNCHRONY_PLCC_NO_LUHN = '6044100018023838'; // also, no date +export const SYNCHRONY_PLCC_WITH_LUHN = '6044141000018769'; // also, no date +export const SYNCHRONY_PLCC_NO_DATE = SYNCHRONY_PLCC_NO_LUHN; // no date export const TEST_DATE_VALUE = '03/30'; export const TEST_CVC_VALUE = '737'; @@ -8,9 +15,16 @@ export const TEST_CVC_VALUE = '737'; export const BIN_LOOKUP_URL = `https://checkoutshopper-test.adyen.com/checkoutshopper/${BIN_LOOKUP_VERSION}/bin/binLookup?token=${process.env.CLIENT_KEY}`; export const USER_TYPE_DELAY = 150; +export const KEYBOARD_DELAY = 300; export const SESSION_DATA_MOCK = 'AAAADEMOSESSIONDATAAAA'; export const ORDER_DATA_MOCK = 'BBBBORDERDATABBBB'; export const SESSION_RESULT_MOCK = 'CCCCSESIONRESULTCCCC'; + +export const ENCRYPTED_CARD_NUMBER = 'encryptedCardNumber'; +export const ENCRYPTED_EXPIRY_DATE = 'encryptedExpiryDate'; +export const ENCRYPTED_EXPIRY_MONTH = 'encryptedExpiryMonth'; +export const ENCRYPTED_EXPIRY_YEAR = 'encryptedExpiryYear'; +export const ENCRYPTED_SECURITY_CODE = 'encryptedSecurityCode'; diff --git a/packages/e2e-playwright/tests/utils/image.ts b/packages/e2e-playwright/tests/utils/image.ts new file mode 100644 index 0000000000..48bbca666a --- /dev/null +++ b/packages/e2e-playwright/tests/utils/image.ts @@ -0,0 +1,5 @@ +import { Locator } from '@playwright/test'; + +export const getImageCount = async (who: Locator) => { + return await who.getByRole('img').count(); +}; diff --git a/packages/e2e-playwright/tests/utils/keyboard.ts b/packages/e2e-playwright/tests/utils/keyboard.ts new file mode 100644 index 0000000000..125fdcc4c2 --- /dev/null +++ b/packages/e2e-playwright/tests/utils/keyboard.ts @@ -0,0 +1,14 @@ +import { Page } from '@playwright/test'; +import { KEYBOARD_DELAY } from './constants'; + +export const pressKeyboardToNextItem = async (page: Page) => { + await page.keyboard.press('ArrowDown', { delay: KEYBOARD_DELAY }); +}; + +export const pressKeyboardToPreviousItem = async (page: Page) => { + await page.keyboard.press('ArrowUp', { delay: KEYBOARD_DELAY }); +}; + +export const pressKeyboardToSelectItem = async (page: Page) => { + await page.keyboard.press('Enter', { delay: KEYBOARD_DELAY }); +}; diff --git a/packages/e2e/tests/cards/availableBrands/dropin/availableBrands.clientScripts.js b/packages/e2e/tests/cards/availableBrands/dropin/availableBrands.clientScripts.js deleted file mode 100644 index 03d2c88fcf..0000000000 --- a/packages/e2e/tests/cards/availableBrands/dropin/availableBrands.clientScripts.js +++ /dev/null @@ -1,7 +0,0 @@ -window.mainConfiguration = { - paymentMethodsConfiguration: { - card: { - showBrandsUnderCardNumber: false - } - } -}; diff --git a/packages/e2e/tests/cards/availableBrands/dropin/availableBrands.compactView.excluded.test.js b/packages/e2e/tests/cards/availableBrands/dropin/availableBrands.compactView.excluded.test.js deleted file mode 100644 index dcccce02a3..0000000000 --- a/packages/e2e/tests/cards/availableBrands/dropin/availableBrands.compactView.excluded.test.js +++ /dev/null @@ -1,32 +0,0 @@ -import { Selector } from 'testcafe'; -import { DROPIN_SESSIONS_URL } from '../../../pages'; -import { getMock } from './availableBrands.mocks'; -import DropinPage from '../../../_models/Dropin.page'; - -let dropinPage = null; - -fixture`Cards - Available Brands ("with" excluded brands) (Compact view)` - .page(DROPIN_SESSIONS_URL) - .requestHooks([getMock('setupResponseWithExcludedBrands')]) - .beforeEach(() => { - dropinPage = new DropinPage({}); - }); - -test('#1 Not all brands show on the header of the payment method item, and excluded items are also removed', async t => { - await dropinPage.brandsHolder(); - const paymentItem = dropinPage.getPaymentMethodItemSelector('Credit Card'); - - await t.expect(paymentItem.hasBrands).ok(); - await t.expect(paymentItem.extraBrandsText).eql('+3'); - await t.expect(paymentItem.numberOfBrandImages).eql(3); -}); - -test('#2 Clicking on the Payment method, brands disappear from header and show beneath Card Number (with excluded brands absent)', async t => { - await dropinPage.brandsHolder(); - const paymentItem = dropinPage.getPaymentMethodItemSelector('Credit Card'); - await paymentItem.click(); - - await t.expect(paymentItem.hasBrands).notOk(); - const brandsInsidePaymentMethod = Selector('.adyen-checkout__card__brands'); - await t.expect(brandsInsidePaymentMethod.find('img').count).eql(6); -}); diff --git a/packages/e2e/tests/cards/availableBrands/dropin/availableBrands.compactView.test.js b/packages/e2e/tests/cards/availableBrands/dropin/availableBrands.compactView.test.js deleted file mode 100644 index 8a161d2713..0000000000 --- a/packages/e2e/tests/cards/availableBrands/dropin/availableBrands.compactView.test.js +++ /dev/null @@ -1,30 +0,0 @@ -import { Selector } from 'testcafe'; -import { DROPIN_SESSIONS_URL } from '../../../pages'; -import { getMock } from './availableBrands.mocks'; -import DropinPage from '../../../_models/Dropin.page'; - -let dropinPage = null; - -fixture`Cards - Available Brands (Compact view)` - .page(DROPIN_SESSIONS_URL) - .requestHooks([getMock()]) - .beforeEach(() => { - dropinPage = new DropinPage({}); - }); - -test('Not all brands show on the header of the payment method item', async t => { - const paymentItem = dropinPage.getPaymentMethodItemSelector('Credit Card'); - - await t.expect(paymentItem.hasBrands).ok(); - await t.expect(paymentItem.extraBrandsText).eql('+7'); - await t.expect(paymentItem.numberOfBrandImages).eql(3); -}); - -test('Clicking on the Payment method, brands disappear from header and show beneath Card Number', async t => { - const paymentItem = dropinPage.getPaymentMethodItemSelector('Credit Card'); - await paymentItem.click(); - - await t.expect(paymentItem.hasBrands).notOk(); - const brandsInsidePaymentMethod = Selector('.adyen-checkout__card__brands'); - await t.expect(brandsInsidePaymentMethod.find('img').count).eql(10); -}); diff --git a/packages/e2e/tests/cards/availableBrands/dropin/availableBrands.defaultView.excluded.test.js b/packages/e2e/tests/cards/availableBrands/dropin/availableBrands.defaultView.excluded.test.js deleted file mode 100644 index 7738918e85..0000000000 --- a/packages/e2e/tests/cards/availableBrands/dropin/availableBrands.defaultView.excluded.test.js +++ /dev/null @@ -1,35 +0,0 @@ -import { Selector } from 'testcafe'; -import { DROPIN_SESSIONS_URL } from '../../../pages'; -import { getMock } from './availableBrands.mocks'; -import DropinPage from '../../../_models/Dropin.page'; - -let dropinPage = null; - -fixture`Cards - Available Brands ("with" excluded brands) (Default view)` - .page(DROPIN_SESSIONS_URL) - .requestHooks([getMock('setupResponseWithExcludedBrands')]) - .clientScripts('./availableBrands.clientScripts.js') - .beforeEach(() => { - dropinPage = new DropinPage({}); - }); - -test('All available brands show up on the Payment Method Item', async t => { - await dropinPage.brandsHolder(); - const paymentItem = dropinPage.getPaymentMethodItemSelector('Credit Card'); - - await t.expect(paymentItem.hasBrands).ok(); - await t.expect(paymentItem.extraBrandsText).eql(''); - await t.expect(paymentItem.numberOfBrandImages).eql(6); -}); - -test('Brands are kept in the Payment Method Item after clicking on it', async t => { - await dropinPage.brandsHolder(); - const paymentItem = dropinPage.getPaymentMethodItemSelector('Credit Card'); - await paymentItem.click(); - - await t.expect(paymentItem.hasBrands).ok(); - await t.expect(paymentItem.numberOfBrandImages).eql(6); - - const brandsInsidePaymentMethod = Selector('.adyen-checkout__card__brands'); - await t.expect(brandsInsidePaymentMethod.find('img').count).eql(0); -}); diff --git a/packages/e2e/tests/cards/availableBrands/dropin/availableBrands.defaultView.test.js b/packages/e2e/tests/cards/availableBrands/dropin/availableBrands.defaultView.test.js deleted file mode 100644 index ea4db24f0b..0000000000 --- a/packages/e2e/tests/cards/availableBrands/dropin/availableBrands.defaultView.test.js +++ /dev/null @@ -1,33 +0,0 @@ -import { Selector } from 'testcafe'; -import { DROPIN_SESSIONS_URL } from '../../../pages'; -import { getMock } from './availableBrands.mocks'; -import DropinPage from '../../../_models/Dropin.page'; - -let dropinPage = null; - -fixture`Cards - Available Brands (Default view)` - .page(DROPIN_SESSIONS_URL) - .requestHooks([getMock()]) - .clientScripts('./availableBrands.clientScripts.js') - .beforeEach(() => { - dropinPage = new DropinPage({}); - }); - -test('#1 All available brands show up on the Payment Method Item', async t => { - const paymentItem = dropinPage.getPaymentMethodItemSelector('Credit Card'); - - await t.expect(paymentItem.hasBrands).ok(); - await t.expect(paymentItem.extraBrandsText).eql(''); - await t.expect(paymentItem.numberOfBrandImages).eql(10); -}); - -test('#2 Brands are kept in the Payment Method Item after clicking on it', async t => { - const paymentItem = dropinPage.getPaymentMethodItemSelector('Credit Card'); - await paymentItem.click(); - - await t.expect(paymentItem.hasBrands).ok(); - await t.expect(paymentItem.numberOfBrandImages).eql(10); - - const brandsInsidePaymentMethod = Selector('.adyen-checkout__card__brands'); - await t.expect(brandsInsidePaymentMethod.find('img').count).eql(0); -}); diff --git a/packages/e2e/tests/cards/availableBrands/dropin/availableBrands.mocks.js b/packages/e2e/tests/cards/availableBrands/dropin/availableBrands.mocks.js deleted file mode 100644 index 42eab31f34..0000000000 --- a/packages/e2e/tests/cards/availableBrands/dropin/availableBrands.mocks.js +++ /dev/null @@ -1,93 +0,0 @@ -import { RequestMock } from 'testcafe'; -import { BASE_URL } from '../../../pages'; - -const path = require('path'); -require('dotenv').config({ path: path.resolve('../../', '.env') }); - -const MOCK_SESSION_ID = 'CS616D08FC28573F9C'; -const MOCK_SESSION_DATA = 'Ab02b4c0!BQABAgChW9EQ6U'; - -const sessionsUrl = 'http://localhost:3024/sessions'; -const setupUrl = `https://checkoutshopper-test.adyen.com/checkoutshopper/v1/sessions/${MOCK_SESSION_ID}/setup?clientKey=${process.env.CLIENT_KEY}`; - -const sessionsResponse = { - amount: { - currency: 'USD', - value: 25900 - }, - countryCode: 'US', - expiresAt: '2021-10-15T13:02:27+02:00', - id: MOCK_SESSION_ID, - merchantAccount: 'TestMerchantCheckout', - reference: 'ABC123', - returnUrl: 'http://localhost:3024/result', - shopperLocale: 'en-US', - shopperReference: 'newshoppert', - sessionData: MOCK_SESSION_DATA -}; - -const setupResponseRegular = { - amount: { - currency: 'USD', - value: 25900 - }, - countryCode: 'US', - expiresAt: '2021-10-15T13:02:27+02:00', - id: MOCK_SESSION_ID, - returnUrl: 'http://localhost:3024/result', - shopperLocale: 'en-US', - paymentMethods: { - paymentMethods: [ - { - brand: 'genericgiftcard', - name: 'Generic GiftCard', - type: 'giftcard' - }, - { - brands: ['visa', 'mc', 'amex', 'discover', 'cup', 'maestro', 'bijcard', 'diners', 'jcb', 'synchrony_cbcc'], - name: 'Credit Card', - type: 'scheme' - } - ] - }, - sessionData: MOCK_SESSION_DATA -}; - -const setupResponseWithExcludedBrands = { - amount: { - currency: 'USD', - value: 25900 - }, - countryCode: 'US', - expiresAt: '2021-10-15T13:02:27+02:00', - id: MOCK_SESSION_ID, - returnUrl: 'http://localhost:3024/result', - shopperLocale: 'en-US', - paymentMethods: { - paymentMethods: [ - { - brand: 'genericgiftcard', - name: 'Generic GiftCard', - type: 'giftcard' - }, - { - brands: ['visa', 'mc', 'amex', 'discover', 'cup', 'maestro', 'nyce', 'accel', 'star', 'pulse'], - name: 'Credit Card', - type: 'scheme' - } - ] - }, - sessionData: MOCK_SESSION_DATA -}; - -const setUpResponseObject = { setupResponseRegular, setupResponseWithExcludedBrands }; - -const getMock = (setupResponse = 'setupResponseRegular') => { - return RequestMock() - .onRequestTo(request => request.url === sessionsUrl) - .respond(sessionsResponse, 200, { 'Access-Control-Allow-Origin': BASE_URL }) - .onRequestTo(request => request.url === setupUrl && request.method === 'post') - .respond(setUpResponseObject[setupResponse], 200, { 'Access-Control-Allow-Origin': BASE_URL }); -}; - -export { getMock, MOCK_SESSION_DATA }; diff --git a/packages/e2e/tests/cards/branding/branding.clientScripts.js b/packages/e2e/tests/cards/branding/branding.clientScripts.js deleted file mode 100644 index f23d9a56d8..0000000000 --- a/packages/e2e/tests/cards/branding/branding.clientScripts.js +++ /dev/null @@ -1,4 +0,0 @@ -window.cardConfig = { - type: 'scheme', - brands: ['mc', 'visa', 'amex', 'maestro', 'bcmc'] -}; diff --git a/packages/e2e/tests/cards/branding/branding.test.js b/packages/e2e/tests/cards/branding/branding.test.js deleted file mode 100644 index 7890e1e649..0000000000 --- a/packages/e2e/tests/cards/branding/branding.test.js +++ /dev/null @@ -1,224 +0,0 @@ -import { ClientFunction, Selector } from 'testcafe'; -import { start, getIframeSelector, getIsValid } from '../../utils/commonUtils'; -import cu from '../utils/cardUtils'; -import { CARDS_URL } from '../../pages'; -import { MAESTRO_CARD, TEST_DATE_VALUE, TEST_CVC_VALUE, BCMC_CARD } from '../utils/constants'; - -const cvcSpan = Selector('.card-field .adyen-checkout__field__cvc'); -const optionalCVCSpan = Selector('.card-field .adyen-checkout__field__cvc--optional'); -const cvcLabel = Selector('.card-field .adyen-checkout__label__text'); -const brandingIcon = Selector('.card-field .adyen-checkout__card__cardNumber__brandIcon'); - -const dualBrandingIconHolderActive = Selector('.card-field .adyen-checkout__card__dual-branding__buttons--active'); - -const getPropFromPMData = ClientFunction(prop => { - return window.card.formatData().paymentMethod[prop]; -}); - -/** - * Needed for hack detailed in 3rd test, below - */ -const setForceClick = ClientFunction(val => { - window.testCafeForceClick = val; -}); - -const TEST_SPEED = 1; - -const iframeSelector = getIframeSelector('.card-field iframe'); - -const cardUtils = cu(iframeSelector); - -fixture`Testing branding - especially regarding optional and hidden cvc fields`.page(CARDS_URL).clientScripts('branding.clientScripts.js'); - -test( - 'Test for generic card icon, ' + - 'then enter number recognised as maestro (by our regEx), ' + - 'then add digit so it will be seen as a bcmc card (by our regEx) ,' + - 'then delete number (back to generic card)', - async t => { - // Start, allow time for iframes to load - await start(t, 2000, TEST_SPEED); - - await t - // generic card icon - .expect(brandingIcon.getAttribute('src')) - .contains('nocard.svg') - - // visible cvc field - .expect(cvcSpan.filterVisible().exists) - .ok() - - // with regular text - .expect(cvcLabel.withExactText('Security code').exists) - .ok() - - // and not optional - .expect(optionalCVCSpan.exists) - .notOk(); - - // Partially fill card field with digits that will be recognised as maestro - await cardUtils.fillCardNumber(t, '670'); - - await t - // maestro card icon - .expect(brandingIcon.getAttribute('src')) - .contains('maestro.svg') - - // with "optional" text - .expect(cvcLabel.withExactText('Security code (optional)').exists) - .ok() - // and optional class - .expect(optionalCVCSpan.exists) - .ok(); - - await t.wait(500); - - // Add digit so card is recognised as bcmc - await cardUtils.fillCardNumber(t, '3'); - - await t - // bcmc icon - .expect(brandingIcon.getAttribute('src')) - .contains('bcmc.svg') - - // hidden cvc field - .expect(cvcSpan.filterHidden().exists) - .ok(); - - await t.wait(500); - - // Delete number - await cardUtils.deleteCardNumber(t); - - // Card is reset - await t - // generic card icon - .expect(brandingIcon.getAttribute('src')) - .contains('nocard.svg') - - // visible cvc field - .expect(cvcSpan.filterVisible().exists) - .ok() - - // with regular text - .expect(cvcLabel.withExactText('Security code').exists) - .ok() - - // and not optional - .expect(optionalCVCSpan.exists) - .notOk(); - } -); - -test('Test card is valid with maestro details (cvc optional) ' + 'then test it is invalid (& brand reset) when number deleted', async t => { - // Start, allow time for iframes to load - await start(t, 2000, TEST_SPEED); - - // generic card - await t.expect(brandingIcon.getAttribute('src')).contains('nocard.svg'); - - // Maestro - await cardUtils.fillCardNumber(t, MAESTRO_CARD); - await cardUtils.fillDate(t, TEST_DATE_VALUE); - - await t - // maestro card icon - .expect(brandingIcon.getAttribute('src')) - .contains('maestro.svg') - - // with "optional" text - .expect(cvcLabel.withExactText('Security code (optional)').exists) - .ok() - // and optional class - .expect(optionalCVCSpan.exists) - .ok(); - - await t.expect(getIsValid('card')).eql(true); - - // add cvc - await cardUtils.fillCVC(t, TEST_CVC_VALUE); - - // Is valid - await t.expect(getIsValid('card')).eql(true); - - // Delete number - await cardUtils.deleteCardNumber(t); - - // Card is reset - await t - // generic card icon - .expect(brandingIcon.getAttribute('src')) - .contains('nocard.svg'); - - // Is not valid - await t.expect(getIsValid('card')).eql(false); -}); - -/** - * 3rd Test - * - * NOTE: test doesn't work properly - the click away from the CVC field is not triggering a blur event within the securedField - * so the error event & focus:false event from the iframe are never sent. - * - * However if you run localhost:3024 and recreate the steps in the test, it does create the expected error - so this seems to be be a bug with TestCafe. - * - * The solution is a HORRIBLE HORRIBLE HACK - the setForceClick function - which sets a flag var that SecuredField.ts will look for if the url - * is running at the test port of 3024. Then SecuredField will call it's own onClickCallback - which was created to solve problems with iOS not - * registering iframes losing focus - */ -test( - 'Test card is invalid if filled with maestro details but optional cvc field is left "in error" (partially filled) ' + - 'then test it is valid if cvc completed' + - 'then test it is valid if cvc deleted', - async t => { - // Start, allow time for iframes to load - await start(t, 2000, TEST_SPEED); - - // generic card - await t.expect(brandingIcon.getAttribute('src')).contains('nocard.svg'); - - // Maestro - await cardUtils.fillCardNumber(t, MAESTRO_CARD); - await cardUtils.fillDate(t, TEST_DATE_VALUE); - - await t - // maestro card icon - .expect(brandingIcon.getAttribute('src')) - .contains('maestro.svg') - - // with "optional" text - .expect(cvcLabel.withExactText('Security code (optional)').exists) - .ok() - // and optional class - .expect(optionalCVCSpan.exists) - .ok(); - - // Partial cvc - await cardUtils.fillCVC(t, '73'); - - await setForceClick(true); - - // Click label - to force blur event that will trigger error and reset card.isValid - await t - .click('.adyen-checkout__label__text') - // Expect error - .expect(Selector('.adyen-checkout__field--error').exists) - .ok() - .wait(2000); - - // Is not valid - await t.expect(getIsValid('card')).eql(false); - - // Complete cvc - await cardUtils.fillCVC(t, '7'); - - // Is valid - await t.expect(getIsValid('card')).eql(true); - - // Delete CVC - await cardUtils.deleteCVC(t); - - // Is valid - await t.expect(getIsValid('card')).eql(true); - } -); diff --git a/packages/e2e/tests/cards/expiryDate/expiryDatePolicies.hidden.test.js b/packages/e2e/tests/cards/expiryDate/expiryDatePolicies.hidden.test.js deleted file mode 100644 index d268e3e965..0000000000 --- a/packages/e2e/tests/cards/expiryDate/expiryDatePolicies.hidden.test.js +++ /dev/null @@ -1,107 +0,0 @@ -import CardComponentPage from '../../_models/CardComponent.page'; -import { SYNCHRONY_PLCC_NO_DATE, TEST_CVC_VALUE } from '../utils/constants'; -import { turnOffSDKMocking } from '../../_common/cardMocks'; - -const cardPage = new CardComponentPage(); - -fixture`Test how Card Component handles hidden expiryDate policy` - .beforeEach(async t => { - await t.navigateTo(cardPage.pageUrl); - await turnOffSDKMocking(); - }) - .clientScripts('./expiryDate.clientScripts.js'); - -test('#1 Testing hidden expiryDatePolicy - how UI & state respond', async t => { - // Wait for field to appear in DOM - await cardPage.numHolder(); - - // Fill number to provoke binLookup response - await cardPage.cardUtils.fillCardNumber(t, SYNCHRONY_PLCC_NO_DATE); - - // UI reflects that binLookup says expiryDate is hidden - await t.expect(cardPage.dateHolder.filterHidden().exists).ok(); - - // Card seen as valid (since CVC is hidden too) - await t.expect(cardPage.getFromState('isValid')).eql(false); - - // Clear number and see UI & state reset - await cardPage.cardUtils.deleteCardNumber(t); - await t - .expect(cardPage.dateHolder.filterVisible().exists) - .ok() - .expect(cardPage.cvcHolder.filterVisible().exists) - .ok() - .expect(cardPage.getFromState('isValid')) - .eql(false); -}); - -test('#2 Testing hidden expiryDatePolicy - validating fields first and then entering PAN should see errors cleared from state', async t => { - // Start, allow time for iframes to load so isValidated call to SF won't fail - await t.wait(1000); - - // Click pay - await t.click(cardPage.payButton); - - // Expect errors in UI - await t - .expect(cardPage.numLabelTextError.exists) - .ok() - .expect(cardPage.dateLabelTextError.exists) - .ok() - .expect(cardPage.cvcLabelTextError.exists) - .ok(); - - // Expect errors in (mapped) state - await t - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedExpiryDate')) - .notEql(null) - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedExpiryDate')) - .notEql(undefined) - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedSecurityCode')) - .notEql(null); - - // Fill number to provoke binLookup response - await cardPage.cardUtils.fillCardNumber(t, SYNCHRONY_PLCC_NO_DATE, 'paste'); // TODO - shouldn't have to 'paste' here... but Testcafe is being flaky, again! - - // Expect errors to be cleared - since the fields were in error because they were empty - // and now the PAN field is filled and the date fields is now hidden... - - // ...State errors cleared for date - await t.expect(cardPage.getFromWindow('mappedStateErrors.encryptedExpiryDate')).eql(null); -}); - -test('#3 Testing hidden expiryDatePolicy - date field in error does not stop card becoming valid', async t => { - // Wait for field to appear in DOM - await cardPage.numHolder(); - - // Card out of date - await cardPage.cardUtils.fillDate(t, '12/90'); - - // Expect errors in UI - await t.expect(cardPage.dateLabelTextError.exists).ok(); - - // Force blur event to fire on date field - await cardPage.setForceClick(true); - - // Fill number to provoke binLookup response - await cardPage.cardUtils.fillCardNumber(t, SYNCHRONY_PLCC_NO_DATE); - await cardPage.cardUtils.fillCVC(t, TEST_CVC_VALUE); - - // UI reflects that binLookup says expiryDate is hidden - await t.expect(cardPage.dateHolder.filterHidden().exists).ok(); - - // Card seen as valid (despite date field technically being in error) - await t.expect(cardPage.getFromState('isValid')).eql(true); - - // Expect errors in (mapped) state to remain - await t.expect(cardPage.getFromWindow('mappedStateErrors.encryptedExpiryDate')).notEql(null); - - // Delete number - await cardPage.cardUtils.deleteCardNumber(t); - - // Errors in UI visible again - await t.expect(cardPage.dateLabelTextError.exists).ok(); - - // Card is not valid - await t.expect(cardPage.getFromState('isValid')).eql(false); -}); diff --git a/packages/e2e/tests/cards/expiryDate/expiryDatePolicies.optional.test.js b/packages/e2e/tests/cards/expiryDate/expiryDatePolicies.optional.test.js deleted file mode 100644 index 047b60dc26..0000000000 --- a/packages/e2e/tests/cards/expiryDate/expiryDatePolicies.optional.test.js +++ /dev/null @@ -1,175 +0,0 @@ -import { binLookupUrl, getBinLookupMock, turnOffSDKMocking } from '../../_common/cardMocks'; -import CardComponentPage from '../../_models/CardComponent.page'; -import { REGULAR_TEST_CARD } from '../utils/constants'; -import { Selector } from 'testcafe'; - -const cardPage = new CardComponentPage(); - -/** - * NOTE - we are mocking the response until such time as we have a genuine card, - * that's not in our local RegEx, that returns the properties we want to test - */ -const mockedResponse = { - brands: [ - { - brand: 'cup', // keep as a recognised card brand (cup) until we have a genuine brand w. optional expiryDate - cvcPolicy: 'optional', - enableLuhnCheck: true, - expiryDatePolicy: 'optional', - supported: true - } - ], - issuingCountryCode: 'US', - requestId: null -}; - -const mock = getBinLookupMock(binLookupUrl, mockedResponse); - -fixture`Test how Card Component handles optional expiryDate policy` - .beforeEach(async t => { - await t.navigateTo(cardPage.pageUrl); - // For individual test suites (that rely on binLookup & perhaps are being run in isolation) - // - provide a way to ensure SDK bin mocking is turned off - await turnOffSDKMocking(); - }) - .requestHooks(mock) - .clientScripts('./expiryDate.clientScripts.js'); - -test('#1 Testing optional expiryDatePolicy - how UI & state respond', async t => { - // Wait for field to appear in DOM - await cardPage.numHolder(); - - // Regular date label - await t.expect(cardPage.dateLabelText.withText('(optional)').exists).notOk(); - - // Fill number to provoke (mock) binLookup response - await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); - - // UI reflects that binLookup says expiryDate is optional - await t.expect(cardPage.dateLabelText.withText('(optional)').exists).ok(); - - // ...and cvc is optional too - await t.expect(cardPage.cvcLabelText.withText('(optional)').exists).ok(); - - // Card seen as valid (since CVC is optional too) - await t.expect(cardPage.getFromState('isValid')).eql(true); - - // Clear number and see UI & state reset - await cardPage.cardUtils.deleteCardNumber(t); - await t - .expect(cardPage.dateLabelText.withText('(optional)').exists) - .notOk() - .expect(cardPage.cvcLabelText.withText('(optional)').exists) - .notOk() - .expect(cardPage.getFromState('isValid')) - .eql(false); -}); - -test('#2 Testing optional expiryDatePolicy - how securedField responds', async t => { - // Wait for field to appear in DOM - await cardPage.numHolder(); - - // Expect iframe to exist in expiryDate field with aria-required attr set to true - await t - .switchToIframe(cardPage.iframeSelector.nth(1)) - .expect(Selector('[data-fieldtype="encryptedExpiryDate"]').getAttribute('aria-required')) - .eql('true') - .switchToMainWindow(); - - // Fill number to provoke (mock) binLookup response - await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); - - // Expect iframe to exist in expiryDate field and with aria-required attr set to false - await t - .switchToIframe(cardPage.iframeSelector.nth(1)) - .expect(Selector('[data-fieldtype="encryptedExpiryDate"]').getAttribute('aria-required')) - .eql('false') - .switchToMainWindow(); - - // Clear number and see SF's aria-required reset - await cardPage.cardUtils.deleteCardNumber(t); - - await t - .switchToIframe(cardPage.iframeSelector.nth(1)) - .expect(Selector('[data-fieldtype="encryptedExpiryDate"]').getAttribute('aria-required')) - .eql('true') - .switchToMainWindow(); -}); - -test('#3 Testing optional expiryDatePolicy - validating fields first and then entering PAN should see errors cleared from both UI & state', async t => { - // Start, allow time for iframes to load so isValidated call to SF won't fail - await t.wait(1000); - - // Click pay - await t.click(cardPage.payButton); - - // Expect errors in UI - await t - .expect(cardPage.numLabelTextError.exists) - .ok() - .expect(cardPage.dateLabelTextError.exists) - .ok() - .expect(cardPage.cvcLabelTextError.exists) - .ok(); - - // Expect errors in (mapped) state - await t - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedExpiryDate')) - .notEql(null) - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedSecurityCode')) - .notEql(null); - - // Fill number to provoke (mock) binLookup response - await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD, 'paste'); // TODO - shouldn't have to 'paste' here... but Testcafe is being flaky, again! - - // Expect errors to be cleared - since the fields were in error because they were empty - // and now the PAN field is filled and the date & cvc fields are now optional... - - // ...UI errors cleared... - await t - .expect(cardPage.numLabelTextError.exists) - .notOk() - .expect(cardPage.dateLabelTextError.exists) - .notOk() - .expect(cardPage.cvcLabelTextError.exists) - .notOk(); - - // ...State errors cleared - await t - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedExpiryDate')) - .eql(null) - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedSecurityCode')) - .eql(null); -}); - -test('#4 Testing optional expiryDatePolicy - date field in error DOES stop card becoming valid', async t => { - // Wait for field to appear in DOM - await cardPage.numHolder(); - - // Card out of date - await cardPage.cardUtils.fillDate(t, '12/90'); - - // Expect errors in UI - await t.expect(cardPage.dateLabelTextError.exists).ok(); - - // Force blur event to fire on date field - await cardPage.setForceClick(true); - - // Fill number to provoke (mock) binLookup response - await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); - - // UI reflects that binLookup says expiryDate is optional - await t.expect(cardPage.dateLabelText.withText('(optional)').exists).ok(); - - // Visual errors persist in UI - await t.expect(cardPage.dateLabelTextError.exists).ok(); - - // Card not seen as valid - await t.expect(cardPage.getFromState('isValid')).eql(false); - - // Delete erroneous date - await cardPage.cardUtils.deleteDate(t); - - // Card is now valid - await t.expect(cardPage.getFromState('isValid')).eql(true); -}); diff --git a/packages/e2e/tests/cards/installments/cards.installments.clientScripts.js b/packages/e2e/tests/cards/installments/cards.installments.clientScripts.js deleted file mode 100644 index a0af3a0f6d..0000000000 --- a/packages/e2e/tests/cards/installments/cards.installments.clientScripts.js +++ /dev/null @@ -1,8 +0,0 @@ -window.cardConfig = { - installmentOptions: { - mc: { - values: [1, 2, 3], - plans: ['regular', 'revolving'] - } - } -}; diff --git a/packages/e2e/tests/cards/installments/cards.installments.mocks.js b/packages/e2e/tests/cards/installments/cards.installments.mocks.js deleted file mode 100644 index a31b42fce2..0000000000 --- a/packages/e2e/tests/cards/installments/cards.installments.mocks.js +++ /dev/null @@ -1,16 +0,0 @@ -import { RequestMock, RequestLogger } from 'testcafe'; -import { BASE_URL } from '../../pages'; - -const paymentUrl = `http://localhost:3024/payments`; - -const paymentResponse = { - resultCode: 'Authorised' -}; - -const paymentLogger = RequestLogger({ url: paymentUrl, method: 'post' }, { logRequestBody: true }); - -const mock = RequestMock() - .onRequestTo(request => request.url === paymentUrl && request.method === 'post') - .respond(paymentResponse, 200, { 'Access-Control-Allow-Origin': BASE_URL }); - -export { mock, paymentLogger }; diff --git a/packages/e2e/tests/cards/installments/cards.installments.test.js b/packages/e2e/tests/cards/installments/cards.installments.test.js deleted file mode 100644 index 943d48e817..0000000000 --- a/packages/e2e/tests/cards/installments/cards.installments.test.js +++ /dev/null @@ -1,88 +0,0 @@ -import { CARDS_URL } from '../../pages'; -import CardComponentPage from '../../_models/CardComponent.page'; -import { REGULAR_TEST_CARD } from '../utils/constants'; -import { mock, paymentLogger } from './cards.installments.mocks'; -import InstallmentsComponent from '../../_models/Installments.component'; - -let cardComponent = null; - -fixture`Cards (Installments)` - .page(CARDS_URL) - .clientScripts('./cards.installments.clientScripts.js') - .requestHooks([mock, paymentLogger]) - .beforeEach(async t => { - // handler for alert that's triggered on successful payment - await t.setNativeDialogHandler(() => true); - cardComponent = new CardComponentPage('.card-field', { installments: new InstallmentsComponent() }); - }); - -test('#1 should not add installments property to payload if one-time payment is selected', async t => { - await cardComponent.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); - await cardComponent.cardUtils.fillDateAndCVC(t); - - await t - .click(cardComponent.payButton) - .expect(paymentLogger.count(() => true)) - .eql(1) - .expect( - paymentLogger.contains(record => { - const { installments } = JSON.parse(record.request.body); - return installments === undefined; - }) - ) - .ok('payment payload has the expected payload'); -}); - -test('#2 should not add installments property to payload if 1x installment is selected', async t => { - await cardComponent.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); - await cardComponent.cardUtils.fillDateAndCVC(t); - await cardComponent.installments.selectInstallment(1); - - await t - .click(cardComponent.payButton) - .expect(paymentLogger.count(() => true)) - .eql(1) - .expect( - paymentLogger.contains(record => { - const { installments } = JSON.parse(record.request.body); - return installments === undefined; - }) - ) - .ok('payment payload has the expected payload'); -}); - -test('#3 should add revolving plan to payload if selected', async t => { - await cardComponent.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); - await cardComponent.cardUtils.fillDateAndCVC(t); - await cardComponent.installments.selectRevolving(); - - await t - .click(cardComponent.payButton) - .expect(paymentLogger.count(() => true)) - .eql(1) - .expect( - paymentLogger.contains(record => { - const { installments } = JSON.parse(record.request.body); - return installments.value === 1 && installments.plan === 'revolving'; - }) - ) - .ok('payment payload has the expected payload'); -}); - -test('#4 should add installments value property if regular installment > 1 is selected', async t => { - await cardComponent.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); - await cardComponent.cardUtils.fillDateAndCVC(t); - await cardComponent.installments.selectInstallment(2); - - await t - .click(cardComponent.payButton) - .expect(paymentLogger.count(() => true)) - .eql(1) - .expect( - paymentLogger.contains(record => { - const { installments } = JSON.parse(record.request.body); - return installments.value === 2; - }) - ) - .ok('payment payload has the expected payload'); -}); diff --git a/packages/e2e/tests/customcard/expiryDate/expiryDatePolicies.hidden.regular.test.js b/packages/e2e/tests/customcard/expiryDate/expiryDatePolicies.hidden.regular.test.js deleted file mode 100644 index 06b25f9b36..0000000000 --- a/packages/e2e/tests/customcard/expiryDate/expiryDatePolicies.hidden.regular.test.js +++ /dev/null @@ -1,134 +0,0 @@ -import { binLookupUrl, getBinLookupMock, turnOffSDKMocking } from '../../_common/cardMocks'; -import CustomCardComponentPage from '../../_models/CustomCardComponent.page'; -import { REGULAR_TEST_CARD } from '../../cards/utils/constants'; - -const cardPage = new CustomCardComponentPage(); - -const BASE_REF = 'securedFields'; - -const mockedResponse = { - brands: [ - { - brand: 'cup', // keep as a recognised card brand (cup) until we have a genuine brand w. optional expiryDate - cvcPolicy: 'hidden', - enableLuhnCheck: true, - expiryDatePolicy: 'hidden', - supported: true - } - ], - issuingCountryCode: 'US', - requestId: null -}; - -const mock = getBinLookupMock(binLookupUrl, mockedResponse); - -fixture`Test how Custom Card Component with regular date field handles hidden expiryDate policy` - .beforeEach(async t => { - await t.navigateTo(cardPage.pageUrl); - // For individual test suites (that rely on binLookup & perhaps are being run in isolation) - // - provide a way to ensure SDK bin mocking is turned off - await turnOffSDKMocking(); - }) - .requestHooks(mock) - .clientScripts('./expiryDate.clientScripts.js'); - -test('#1 Testing hidden expiryDatePolicy - how UI & state respond', async t => { - // Wait for field to appear in DOM - await cardPage.numHolder(); - - // Fill number to provoke (mock) binLookup response - await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); - - // UI reflects that binLookup says expiryDate is hidden - await t.expect(cardPage.dateHolder.filterHidden().exists).ok(); - - // ...and cvc is hidden too - await t.expect(cardPage.cvcHolder.filterHidden().exists).ok(); - - // Card seen as valid (since CVC is hidden too) - await t.expect(cardPage.getFromState(BASE_REF, 'isValid')).eql(true); - - // Clear number and see UI & state reset - await cardPage.cardUtils.deleteCardNumber(t); - await t - .expect(cardPage.dateHolder.filterVisible().exists) - .ok() - .expect(cardPage.cvcHolder.filterVisible().exists) - .ok() - .expect(cardPage.getFromState(BASE_REF, 'isValid')) - .eql(false); -}); - -test('#2 Testing hidden expiryDatePolicy - validating fields first and then entering PAN should see errors cleared from state', async t => { - // Start, allow time for iframes to load so isValidated call to SF won't fail - await t.wait(1000); - - // Click pay - await t.click(cardPage.payButton); - - // Expect errors in UI - await t - .expect(cardPage.numErrorText.filterVisible().exists) - .ok() - .expect(cardPage.dateErrorText.filterVisible().exists) - .ok() - .expect(cardPage.cvcErrorText.filterVisible().exists) - .ok(); - - // Expect errors in (mapped) state - await t - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedExpiryDate')) - .notEql(null) - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedExpiryDate')) - .notEql(undefined) - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedSecurityCode')) - .notEql(null); - - // Fill number to provoke (mock) binLookup response - await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); - - // Expect errors to be cleared - since the fields were in error because they were empty - // and now the PAN field is filled and the date & cvc fields are now hidden... - - // ...State errors cleared - await t - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedExpiryDate')) - .eql(null) - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedSecurityCode')) - .eql(null); -}); - -test('#3 Testing hidden expiryDatePolicy - date field in error does not stop card becoming valid', async t => { - // Wait for field to appear in DOM - await cardPage.numHolder(); - - // Card out of date - await cardPage.cardUtils.fillDate(t, '12/90'); - - // Expect errors in UI - await t.expect(cardPage.dateErrorText.filterVisible().exists).ok(); - - // Force blur event to fire on date field - await cardPage.setForceClick(true); - - // Fill number to provoke (mock) binLookup response - await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); - - // UI reflects that binLookup says expiryDate is hidden - await t.expect(cardPage.dateHolder.filterHidden().exists).ok(); - - // Card seen as valid (despite date field technically being in error) - await t.expect(cardPage.getFromState(BASE_REF, 'isValid')).eql(true); - - // Expect errors in (mapped) state to remain - await t.expect(cardPage.getFromWindow('mappedStateErrors.encryptedExpiryDate')).notEql(null); - - // Delete number - await cardPage.cardUtils.deleteCardNumber(t); - - // Errors in UI visible again - await t.expect(cardPage.dateErrorText.filterVisible().exists).ok(); - - // Card is not valid - await t.expect(cardPage.getFromState(BASE_REF, 'isValid')).eql(false); -}); diff --git a/packages/e2e/tests/customcard/expiryDate/expiryDatePolicies.hidden.separate.test.js b/packages/e2e/tests/customcard/expiryDate/expiryDatePolicies.hidden.separate.test.js deleted file mode 100644 index 4a89536640..0000000000 --- a/packages/e2e/tests/customcard/expiryDate/expiryDatePolicies.hidden.separate.test.js +++ /dev/null @@ -1,127 +0,0 @@ -import { turnOffSDKMocking } from '../../_common/cardMocks'; -import CustomCardComponentPage from '../../_models/CustomCardComponent.page'; -import { SYNCHRONY_PLCC_NO_DATE, TEST_CVC_VALUE } from '../../cards/utils/constants'; - -const cardPage = new CustomCardComponentPage('.secured-fields-2'); - -const BASE_REF = 'securedFields2'; - -fixture`Test how Custom Card Component with separate date fields handles hidden expiryDate policy` - .beforeEach(async t => { - await t.navigateTo(cardPage.pageUrl); - // For individual test suites (that rely on binLookup & perhaps are being run in isolation) - // - provide a way to ensure SDK bin mocking is turned off - await turnOffSDKMocking(); - }) - .clientScripts('./expiryDate.clientScripts.js'); - -test('#1 Testing hidden expiryDatePolicy - how UI & state respond', async t => { - // Wait for field to appear in DOM - await cardPage.numHolder(); - - // Fill number to provoke binLookup response - await cardPage.cardUtils.fillCardNumber(t, SYNCHRONY_PLCC_NO_DATE); - - // UI reflects that binLookup says expiryDate is hidden - await t.expect(cardPage.monthHolder.filterHidden().exists).ok(); - await t.expect(cardPage.yearHolder.filterHidden().exists).ok(); - - await cardPage.customCardUtils.fillCVC(t, TEST_CVC_VALUE); - - // Card seen as valid - await t.expect(cardPage.getFromState(BASE_REF, 'isValid')).eql(true); - - // Clear number and see UI & state reset - await cardPage.cardUtils.deleteCardNumber(t); - await t - .expect(cardPage.monthHolder.filterVisible().exists) - .ok() - .expect(cardPage.yearHolder.filterVisible().exists) - .ok() - .expect(cardPage.cvcHolder.filterVisible().exists) - .ok() - .expect(cardPage.getFromState(BASE_REF, 'isValid')) - .eql(false); -}); - -test('#2 Testing hidden expiryDatePolicy - validating fields first and then entering PAN should see errors cleared from state', async t => { - // Start, allow time for iframes to load so isValidated call to SF won't fail - await t.wait(1000); - - // Click pay - await t.click(cardPage.payButton); - - // Expect errors in UI - await t - .expect(cardPage.numErrorText.filterVisible().exists) - .ok() - .expect(cardPage.monthErrorText.filterVisible().exists) - .ok() - .expect(cardPage.yearErrorText.filterVisible().exists) - .ok() - .expect(cardPage.cvcErrorText.filterVisible().exists) - .ok(); - - // Expect errors in (mapped) state - await t - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedExpiryMonth')) - .notEql(null) - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedExpiryMonth')) - .notEql(undefined) - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedExpiryYear')) - .notEql(null) - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedSecurityCode')) - .notEql(null); - - // Fill number to provoke binLookup response - await cardPage.cardUtils.fillCardNumber(t, SYNCHRONY_PLCC_NO_DATE); - - // Expect errors to be cleared - since the fields were in error because they were empty - // and now the PAN field is filled and the date & cvc fields are now hidden... - - // ...State errors cleared - await t - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedExpiryMonth')) - .eql(null) - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedExpiryYear')) - .eql(null); -}); - -test('#3 Testing hidden expiryDatePolicy - date field in error does not stop card becoming valid', async t => { - // Wait for field to appear in DOM - await cardPage.numHolder(); - - // Card out of date - await cardPage.customCardUtils.fillMonth(t, '12'); - await cardPage.customCardUtils.fillYear(t, '90'); - - // Expect errors in UI - await t.expect(cardPage.yearErrorText.filterVisible().exists).ok(); - - // Force blur event to fire on date field - await cardPage.setForceClick(true); - - // Fill number to provoke binLookup response - await cardPage.cardUtils.fillCardNumber(t, SYNCHRONY_PLCC_NO_DATE); - - // UI reflects that binLookup says expiryDate is hidden - await t.expect(cardPage.monthHolder.filterHidden().exists).ok(); - await t.expect(cardPage.yearHolder.filterHidden().exists).ok(); - - await cardPage.customCardUtils.fillCVC(t, TEST_CVC_VALUE); - - // Card seen as valid (despite date field technically being in error) - await t.expect(cardPage.getFromState(BASE_REF, 'isValid')).eql(true); - - // Expect errors in (mapped) state to remain - await t.expect(cardPage.getFromWindow('mappedStateErrors.encryptedExpiryYear')).notEql(null); - - // Delete number - await cardPage.cardUtils.deleteCardNumber(t); - - // Errors in UI visible again - await t.expect(cardPage.yearErrorText.filterVisible().exists).ok(); - - // Card is not valid - await t.expect(cardPage.getFromState(BASE_REF, 'isValid')).eql(false); -}); diff --git a/packages/e2e/tests/customcard/expiryDate/expiryDatePolicies.optional.regular.test.js b/packages/e2e/tests/customcard/expiryDate/expiryDatePolicies.optional.regular.test.js deleted file mode 100644 index 904f597e66..0000000000 --- a/packages/e2e/tests/customcard/expiryDate/expiryDatePolicies.optional.regular.test.js +++ /dev/null @@ -1,166 +0,0 @@ -import { binLookupUrl, getBinLookupMock, turnOffSDKMocking } from '../../_common/cardMocks'; -import CustomCardComponentPage from '../../_models/CustomCardComponent.page'; -import { REGULAR_TEST_CARD } from '../../cards/utils/constants'; - -const cardPage = new CustomCardComponentPage(); - -const BASE_REF = 'securedFields'; - -/** - * NOTE - we are mocking the response until such time as we have a genuine card, - * that's not in our local RegEx, that returns the properties we want to test - */ -const mockedResponse = { - brands: [ - { - brand: 'cup', // keep as a recognised card brand (cup) until we have a genuine brand w. optional expiryDate - cvcPolicy: 'optional', - enableLuhnCheck: true, - expiryDatePolicy: 'optional', - supported: true - } - ], - issuingCountryCode: 'US', - requestId: null -}; - -const mock = getBinLookupMock(binLookupUrl, mockedResponse); - -fixture`Test how regular Custom Card Component handles optional expiryDate policy` - .beforeEach(async t => { - await t.navigateTo(cardPage.pageUrl); - // For individual test suites (that rely on binLookup & perhaps are being run in isolation) - // - provide a way to ensure SDK bin mocking is turned off - await turnOffSDKMocking(); - }) - .requestHooks(mock) - .clientScripts('./expiryDate.clientScripts.js'); - -test('#1 Testing optional expiryDatePolicy - how UI & state respond', async t => { - // Wait for field to appear in DOM - await cardPage.numHolder(); - - // Regular date label - await t.expect(cardPage.dateLabelText.withText('(optional)').exists).notOk(); - - // Fill number to provoke (mock) binLookup response - await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); - - // UI reflects that binLookup says expiryDate is optional - await t.expect(cardPage.dateLabelText.withText('(optional)').exists).ok(); - - // ...and cvc is optional too - await t.expect(cardPage.cvcLabelText.withText('(optional)').exists).ok(); - - // Card seen as valid (since CVC is optional too) - await t.expect(cardPage.getFromState(BASE_REF, 'isValid')).eql(true); - - // Clear number and see UI & state reset - await cardPage.cardUtils.deleteCardNumber(t); - await t - .expect(cardPage.dateLabelText.withText('(optional)').exists) - .notOk() - .expect(cardPage.cvcLabelText.withText('(optional)').exists) - .notOk() - .expect(cardPage.getFromState(BASE_REF, 'isValid')) - .eql(false); -}); - -test('#2 Testing optional expiryDatePolicy, in regular Custom Card - how securedField responds', async t => { - // Wait for field to appear in DOM - await cardPage.numHolder(); - - // Expect iframe to exist in expiryDate field with aria-required attr set to true - await cardPage.cardUtils.checkIframeForAttrVal(t, 1, 'encryptedExpiryDate', 'aria-required', 'true'); - - // Fill number to provoke (mock) binLookup response - await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); - - // Expect iframe to exist in expiryDate field and with aria-required attr set to false - await cardPage.cardUtils.checkIframeForAttrVal(t, 1, 'encryptedExpiryDate', 'aria-required', 'false'); - - // Clear number and see SF's aria-required reset - await cardPage.cardUtils.deleteCardNumber(t); - - await cardPage.cardUtils.checkIframeForAttrVal(t, 1, 'encryptedExpiryDate', 'aria-required', 'true'); -}); - -test('#3 Testing optional expiryDatePolicy - validating fields first and then entering PAN should see errors cleared from both UI & state', async t => { - // Start, allow time for iframes to load so isValidated call to SF won't fail - await t.wait(1000); - - // Click pay - await t.click(cardPage.payButton); - - // Expect errors in UI - await t - .expect(cardPage.numErrorText.filterVisible().exists) - .ok() - .expect(cardPage.dateErrorText.filterVisible().exists) - .ok() - .expect(cardPage.cvcErrorText.filterVisible().exists) - .ok(); - - // Expect errors in (mapped) state - await t - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedExpiryDate')) - .notEql(null) - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedExpiryDate')) - .notEql(undefined) - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedSecurityCode')) - .notEql(null); - - // Fill number to provoke (mock) binLookup response - await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); - - // Expect errors to be cleared - since the fields were in error because they were empty - // and now the PAN field is filled and the date & cvc fields are now optional... - - // ...UI errors cleared... - await t - .expect(cardPage.numErrorText.filterHidden().exists) - .ok() - .expect(cardPage.dateErrorText.filterHidden().exists) - .ok() - .expect(cardPage.cvcErrorText.filterHidden().exists) - .ok(); - - // ...State errors cleared - await t - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedExpiryDate')) - .eql(null) - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedSecurityCode')) - .eql(null); -}); - -test('#4 Testing optional expiryDatePolicy - date field in error DOES stop card becoming valid', async t => { - // Wait for field to appear in DOM - await cardPage.numHolder(); - - // Card out of date - await cardPage.cardUtils.fillDate(t, '12/90'); - - // Expect errors in UI - await t.expect(cardPage.dateErrorText.filterVisible().exists).ok(); - - // Force blur event to fire on date field - await cardPage.setForceClick(true); - - // Fill number to provoke (mock) binLookup response - await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); - - // UI reflects that binLookup says expiryDate is optional - await t.expect(cardPage.dateLabelText.withText('(optional)').exists).ok(); - - // Visual errors persist in UI - await t.expect(cardPage.dateErrorText.filterVisible().exists).ok(); - - // Card not seen as valid - await t.expect(cardPage.getFromState(BASE_REF, 'isValid')).eql(false); - - // Delete erroneous date - await cardPage.cardUtils.deleteDate(t); - - // Card is now valid - await t.expect(cardPage.getFromState(BASE_REF, 'isValid')).eql(true); -}); diff --git a/packages/e2e/tests/customcard/expiryDate/expiryDatePolicies.optional.separate.test.js b/packages/e2e/tests/customcard/expiryDate/expiryDatePolicies.optional.separate.test.js deleted file mode 100644 index 9541c35bc1..0000000000 --- a/packages/e2e/tests/customcard/expiryDate/expiryDatePolicies.optional.separate.test.js +++ /dev/null @@ -1,197 +0,0 @@ -import { binLookupUrl, getBinLookupMock, turnOffSDKMocking } from '../../_common/cardMocks'; -import CustomCardComponentPage from '../../_models/CustomCardComponent.page'; -import { REGULAR_TEST_CARD } from '../../cards/utils/constants'; - -const cardPage = new CustomCardComponentPage('.secured-fields-2'); - -const BASE_REF = 'securedFields2'; - -/** - * NOTE - we are mocking the response until such time as we have a genuine card, - * that's not in our local RegEx, that returns the properties we want to test - */ -const mockedResponse = { - brands: [ - { - brand: 'cup', // keep as a recognised card brand (cup) until we have a genuine brand w. optional expiryDate - cvcPolicy: 'optional', - enableLuhnCheck: true, - expiryDatePolicy: 'optional', - supported: true - } - ], - issuingCountryCode: 'US', - requestId: null -}; - -const mock = getBinLookupMock(binLookupUrl, mockedResponse); - -fixture`Test how Custom Card Component with separate date fields handles optional expiryDate policy` - .beforeEach(async t => { - await t.navigateTo(cardPage.pageUrl); - // For individual test suites (that rely on binLookup & perhaps are being run in isolation) - // - provide a way to ensure SDK bin mocking is turned off - await turnOffSDKMocking(); - }) - .requestHooks(mock) - .clientScripts('./expiryDate.clientScripts.js'); - -test('#1 Testing optional expiryDatePolicy - how UI & state respond', async t => { - // Wait for field to appear in DOM - await cardPage.numHolder(); - - // Regular date labels - await t - .expect(cardPage.monthLabelText.withText('(optional)').exists) - .notOk() - .expect(cardPage.yearLabelText.withText('(optional)').exists) - .notOk(); - - // Fill number to provoke (mock) binLookup response - await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); - - // UI reflects that binLookup says expiryDate is optional - await t - .expect(cardPage.monthLabelText.withText('(optional)').exists) - .ok() - .expect(cardPage.yearLabelText.withText('(optional)').exists) - .ok(); - - // ...and cvc is optional too - await t.expect(cardPage.cvcLabelText.withText('(optional)').exists).ok(); - - // Card seen as valid (since CVC is optional too) - await t.expect(cardPage.getFromState(BASE_REF, 'isValid')).eql(true); - - // Clear number and see UI & state reset - await cardPage.cardUtils.deleteCardNumber(t); - await t - .expect(cardPage.monthLabelText.withText('(optional)').exists) - .notOk() - .expect(cardPage.yearLabelText.withText('(optional)').exists) - .notOk() - .expect(cardPage.cvcLabelText.withText('(optional)').exists) - .notOk() - .expect(cardPage.getFromState(BASE_REF, 'isValid')) - .eql(false); -}); - -test('#2 Testing optional expiryDatePolicy, in Custom Card w. separate date fields - how securedFields respond', async t => { - // Wait for field to appear in DOM - await cardPage.numHolder(); - - // Expect iframe to exist in expiryMonth field with aria-required attr set to true - await cardPage.cardUtils.checkIframeForAttrVal(t, 1, 'encryptedExpiryMonth', 'aria-required', 'true'); - - // Expect iframe to exist in expiryYear field with aria-required attr set to true - await cardPage.cardUtils.checkIframeForAttrVal(t, 2, 'encryptedExpiryYear', 'aria-required', 'true'); - - // Fill number to provoke (mock) binLookup response - await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); - - // Expect iframe to exist in expiryMonth field and with aria-required attr set to false - await cardPage.cardUtils.checkIframeForAttrVal(t, 1, 'encryptedExpiryMonth', 'aria-required', 'false'); - - // Expect iframe to exist in expiryYear field and with aria-required attr set to false - await cardPage.cardUtils.checkIframeForAttrVal(t, 2, 'encryptedExpiryYear', 'aria-required', 'false'); - - // Clear number and see SF's aria-required reset - await cardPage.cardUtils.deleteCardNumber(t); - - await cardPage.cardUtils.checkIframeForAttrVal(t, 1, 'encryptedExpiryMonth', 'aria-required', 'true'); - await cardPage.cardUtils.checkIframeForAttrVal(t, 2, 'encryptedExpiryYear', 'aria-required', 'true'); -}); - -test('#3 Testing optional expiryDatePolicy - validating fields first and then entering PAN should see errors cleared from both UI & state', async t => { - // Start, allow time for iframes to load so isValidated call to SF won't fail - await t.wait(1000); - - // Click pay - await t.click(cardPage.payButton); - - // Expect errors in UI - await t - .expect(cardPage.numErrorText.filterVisible().exists) - .ok() - .expect(cardPage.monthErrorText.filterVisible().exists) - .ok() - .expect(cardPage.yearErrorText.filterVisible().exists) - .ok() - .expect(cardPage.cvcErrorText.filterVisible().exists) - .ok(); - - // Expect errors in (mapped) state - await t - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedExpiryMonth')) - .notEql(null) - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedExpiryMonth')) - .notEql(undefined) - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedExpiryYear')) - .notEql(null) - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedSecurityCode')) - .notEql(null); - - // Fill number to provoke (mock) binLookup response - await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); - - // Expect errors to be cleared - since the fields were in error because they were empty - // and now the PAN field is filled and the date & cvc fields are now optional... - - // ...UI errors cleared... - await t - .expect(cardPage.numErrorText.filterHidden().exists) - .ok() - .expect(cardPage.monthErrorText.filterHidden().exists) - .ok() - .expect(cardPage.yearErrorText.filterHidden().exists) - .ok() - .expect(cardPage.cvcErrorText.filterHidden().exists) - .ok(); - - // ...State errors cleared - await t - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedExpiryMonth')) - .eql(null) - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedExpiryYear')) - .eql(null) - .expect(cardPage.getFromWindow('mappedStateErrors.encryptedSecurityCode')) - .eql(null); -}); - -test('#4 Testing optional expiryDatePolicy - date field in error DOES stop card becoming valid', async t => { - // Wait for field to appear in DOM - await cardPage.numHolder(); - - // Card out of date - await cardPage.customCardUtils.fillMonth(t, '12'); - await cardPage.customCardUtils.fillYear(t, '90'); - - // Expect errors in UI - await t.expect(cardPage.yearErrorText.filterVisible().exists).ok(); - - // Force blur event to fire on date field - await cardPage.setForceClick(true); - - // Fill number to provoke (mock) binLookup response - await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); - - // UI reflects that binLookup says expiryDate is optional - await t - .expect(cardPage.monthLabelText.withText('(optional)').exists) - .ok() - .expect(cardPage.yearLabelText.withText('(optional)').exists) - .ok(); - - // Visual errors persist in UI - await t.expect(cardPage.yearErrorText.filterVisible().exists).ok(); - - // Card not seen as valid - await t.expect(cardPage.getFromState(BASE_REF, 'isValid')).eql(false); - - // Delete erroneous date - await cardPage.customCardUtils.deleteMonth(t); - await cardPage.customCardUtils.deleteYear(t); - - // Card is now valid - await t.expect(cardPage.getFromState(BASE_REF, 'isValid')).eql(true); -});