diff --git a/packages/e2e-playwright/fixtures/card.fixture.ts b/packages/e2e-playwright/fixtures/card.fixture.ts index 4645cf1f5..4190b8922 100644 --- a/packages/e2e-playwright/fixtures/card.fixture.ts +++ b/packages/e2e-playwright/fixtures/card.fixture.ts @@ -3,10 +3,14 @@ import { Card } from '../models/card'; import { BCMC } from '../models/bcmc'; import { URL_MAP } from './URL_MAP'; import { CardWithAvs } from '../models/card-avs'; +import { CardWithKCP } from '../models/card-kcp'; +import { CardWithSSN } from '../models/card-ssn'; type Fixture = { card: Card; cardWithAvs: CardWithAvs; + cardWithKCP: CardWithKCP; + cardWithSSN: CardWithSSN; bcmc: BCMC; }; @@ -19,6 +23,14 @@ const test = base.extend({ const cardPage = new CardWithAvs(page); await use(cardPage); }, + cardWithKCP: async ({ page }, use) => { + const cardPage = new CardWithKCP(page); + await use(cardPage); + }, + cardWithSSN: async ({ page }, use) => { + const cardPage = new CardWithSSN(page); + await use(cardPage); + }, bcmc: async ({ page }, use) => { const bcmc = new BCMC(page); await bcmc.goto(URL_MAP.bcmc); diff --git a/packages/e2e-playwright/mocks/binLookup/binLookup.data.ts b/packages/e2e-playwright/mocks/binLookup/binLookup.data.ts index 9e205e10d..806bc3d5f 100644 --- a/packages/e2e-playwright/mocks/binLookup/binLookup.data.ts +++ b/packages/e2e-playwright/mocks/binLookup/binLookup.data.ts @@ -1,4 +1,18 @@ const optionalDateAndCvcMock = { + brands: [ + { + brand: 'mc', + enableLuhnCheck: true, + supported: true, + cvcPolicy: 'optional', + expiryDatePolicy: 'optional' + } + ], + issuingCountryCode: 'US', + requestedId: null +}; + +const optionalDateAndCvcWithPanLengthMock = { brands: [ { brand: 'mc', @@ -27,4 +41,88 @@ const hiddenDateAndCvcMock = { requestedId: null }; -export { optionalDateAndCvcMock, hiddenDateAndCvcMock }; +const optionalDateWithPanLengthMock = { + brands: [ + { + brand: 'mc', + enableLuhnCheck: true, + supported: true, + cvcPolicy: 'required', + expiryDatePolicy: 'optional', + panLength: 16 + } + ], + issuingCountryCode: 'US', + requestedId: null +}; + +const hiddenDateWithPanLengthMock = { + brands: [ + { + brand: 'mc', + enableLuhnCheck: true, + supported: true, + cvcPolicy: 'required', + expiryDatePolicy: 'hidden', + panLength: 16 + } + ], + issuingCountryCode: 'US', + requestedId: null +}; + +const multiLengthMaestroWithPanLengthMock = { + brands: [ + { + enableLuhnCheck: true, + supported: true, + brand: 'maestro', + cvcPolicy: 'required', + expiryDatePolicy: 'required', + panLength: 18 + } + ], + issuingCountryCode: 'US', + requestedId: null +}; + +const amexWithPanLengthMock = { + brands: [ + { + enableLuhnCheck: true, + supported: true, + brand: 'amex', + cvcPolicy: 'required', + expiryDatePolicy: 'required', + panLength: 15 + } + ], + issuingCountryCode: 'US', + requestedId: null +}; + +const kcpMockOptionalDateAndCvcWithPanLengthMock = { + brands: [ + { + enableLuhnCheck: true, + supported: true, + brand: 'korean_local_card', + cvcPolicy: 'optional', + expiryDatePolicy: 'optional', + panLength: 16 + } + ], + requestedId: null, + issuingCountryCode: 'KR' +}; + +export { + optionalDateAndCvcMock, + hiddenDateAndCvcMock, + optionalDateWithPanLengthMock, + hiddenDateWithPanLengthMock, + optionalDateAndCvcWithPanLengthMock, + multiLengthMaestroWithPanLengthMock, + amexWithPanLengthMock, + kcpMockOptionalDateAndCvcWithPanLengthMock +}; diff --git a/packages/e2e-playwright/models/card-kcp.ts b/packages/e2e-playwright/models/card-kcp.ts new file mode 100644 index 000000000..623269034 --- /dev/null +++ b/packages/e2e-playwright/models/card-kcp.ts @@ -0,0 +1,17 @@ +import { Card } from './card'; +import { type Locator, Page } from '@playwright/test'; + +class CardWithKCP extends Card { + readonly kcpTaxNumberField: Locator; + + constructor(page: Page) { + super(page); + this.kcpTaxNumberField = this.rootElement.locator('.adyen-checkout__field--kcp-taxNumber'); // Holder + } + + get taxNumberInput() { + return this.kcpTaxNumberField.getByRole('textbox', { name: /Cardholder birthdate/i }); + } +} + +export { CardWithKCP }; diff --git a/packages/e2e-playwright/models/card-ssn.ts b/packages/e2e-playwright/models/card-ssn.ts new file mode 100644 index 000000000..6448f5ba1 --- /dev/null +++ b/packages/e2e-playwright/models/card-ssn.ts @@ -0,0 +1,17 @@ +import { Card } from './card'; +import { type Locator, Page } from '@playwright/test'; + +class CardWithSSN extends Card { + readonly ssnField: Locator; + + constructor(page: Page) { + super(page); + this.ssnField = this.rootElement.locator('.adyen-checkout__field--socialSecurityNumber'); // Holder + } + + get ssnInput() { + return this.ssnField.getByRole('textbox', { name: /CPF\/CNPJ/i }); + } +} + +export { CardWithSSN }; diff --git a/packages/e2e-playwright/models/card.ts b/packages/e2e-playwright/models/card.ts index b013e479f..fc225ddf4 100644 --- a/packages/e2e-playwright/models/card.ts +++ b/packages/e2e-playwright/models/card.ts @@ -27,6 +27,7 @@ class Card extends Base { readonly brandingIcon: Locator; readonly expiryDateField: Locator; + readonly expiryDateLabelElement: Locator; readonly expiryDateLabelText: Locator; readonly expiryDateContextualElement: Locator; readonly expiryDateInput: Locator; @@ -34,12 +35,16 @@ class Card extends Base { readonly expiryDateErrorElement: Locator; readonly cvcField: Locator; + readonly cvcLabelElement: Locator; readonly cvcLabelText: Locator; readonly cvcErrorElement: Locator; readonly cvcContextualElement: Locator; readonly cvcInput: Locator; readonly cvcIframeContextualElement: Locator; + readonly holderNameField: Locator; + readonly holderNameInput: Locator; + readonly installmentsPaymentLabel: Locator; readonly revolvingPaymentLabel: Locator; readonly installmentsDropdown: Locator; @@ -72,6 +77,7 @@ class Card extends Base { * Expiry Date elements, in Checkout */ this.expiryDateField = this.rootElement.locator('.adyen-checkout__field--expiryDate'); // Holder + this.expiryDateLabelElement = this.expiryDateField.locator('.adyen-checkout__label'); this.expiryDateLabelText = this.expiryDateField.locator('.adyen-checkout__label__text'); this.expiryDateContextualElement = this.expiryDateField.locator('.adyen-checkout-contextual-text'); // Related contextual element this.expiryDateErrorElement = this.expiryDateField.locator('.adyen-checkout-contextual-text--error'); // Related error element @@ -87,6 +93,7 @@ class Card extends Base { * Security code elements, in Checkout */ this.cvcField = this.rootElement.locator('.adyen-checkout__field--securityCode'); // Holder + this.cvcLabelElement = this.cvcField.locator('.adyen-checkout__label'); this.cvcLabelText = this.cvcField.locator('.adyen-checkout__label__text'); this.cvcContextualElement = this.cvcField.locator('.adyen-checkout-contextual-text'); // Related contextual element this.cvcErrorElement = this.cvcField.locator('.adyen-checkout-contextual-text--error'); // Related error element @@ -98,6 +105,12 @@ class Card extends Base { this.cvcInput = cvcIframe.locator(`input[aria-label="${CVC_IFRAME_LABEL}"]`); this.cvcIframeContextualElement = cvcIframe.locator('.aria-context'); + /** + * HolderName elements, in Checkout + */ + this.holderNameField = this.rootElement.locator('.adyen-checkout__card__holderName'); // Holder + this.holderNameInput = this.holderNameField.getByRole('textbox', { name: /name on card/i }); + /** * Installments related elements */ diff --git a/packages/e2e-playwright/playwright.config.ts b/packages/e2e-playwright/playwright.config.ts index 7e4d8679b..9b02ffb09 100644 --- a/packages/e2e-playwright/playwright.config.ts +++ b/packages/e2e-playwright/playwright.config.ts @@ -51,7 +51,11 @@ const config: PlaywrightTestConfig = { { name: 'chromium', use: { - ...devices['Desktop Chrome'] + ...devices['Desktop Chrome'], + contextOptions: { + // chromium-specific permissions + permissions: ['clipboard-read', 'clipboard-write'] + } } }, diff --git a/packages/e2e-playwright/tests/a11y/card/avs.a11y.spec.ts b/packages/e2e-playwright/tests/a11y/card/avs.a11y.spec.ts index 4a58ded2f..81839bea5 100644 --- a/packages/e2e-playwright/tests/a11y/card/avs.a11y.spec.ts +++ b/packages/e2e-playwright/tests/a11y/card/avs.a11y.spec.ts @@ -1,6 +1,6 @@ import { test as base, expect } from '@playwright/test'; import { binLookupMock } from '../../../mocks/binLookup/binLookup.mock'; -import { optionalDateAndCvcMock } from '../../../mocks/binLookup/binLookup.data'; +import { optionalDateAndCvcWithPanLengthMock } from '../../../mocks/binLookup/binLookup.data'; import { REGULAR_TEST_CARD } from '../../utils/constants'; import { CardWithAvs } from '../../../models/card-avs'; import { getStoryUrl } from '../../utils/getStoryUrl'; @@ -15,7 +15,7 @@ const test = base.extend({ const cardPage = new CardWithAvs(page); const componentConfig = { billingAddressRequired: true, billingAddressRequiredFields: ['street', 'houseNumberOrName', 'postalCode', 'city'] }; await cardPage.goto(getStoryUrl({ baseUrl: URL_MAP.card, componentConfig })); - await binLookupMock(page, optionalDateAndCvcMock); + await binLookupMock(page, optionalDateAndCvcWithPanLengthMock); await use(cardPage); } }); diff --git a/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.avs.clientScripts.js b/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.avs.clientScripts.js deleted file mode 100644 index 0c0596a37..000000000 --- a/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.avs.clientScripts.js +++ /dev/null @@ -1,4 +0,0 @@ -window.cardConfig = { - billingAddressRequired: true, - billingAddressRequiredFields: ['street', 'houseNumberOrName', 'postalCode', 'city'] -}; diff --git a/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.focus.avs.spec.ts b/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.focus.avs.spec.ts index 2737152b9..7dcfce748 100644 --- a/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.focus.avs.spec.ts +++ b/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.focus.avs.spec.ts @@ -1,44 +1,27 @@ -import { test } from '@playwright/test'; -import { mocks } from './mocks'; -import { binLookupUrl, getBinLookupMock, turnOffSDKMocking } from '../../cardMocks'; +import { mergeTests, expect } from '@playwright/test'; +import { test as cardWithAvs } from '../../../../../fixtures/card.fixture'; +import { getStoryUrl } from '../../../../utils/getStoryUrl'; +import { URL_MAP } from '../../../../../fixtures/URL_MAP'; +import { binLookupMock } from '../../../../../mocks/binLookup/binLookup.mock'; +import { optionalDateAndCvcWithPanLengthMock } from '../../../../../mocks/binLookup/binLookup.data'; +import { REGULAR_TEST_CARD } from '../../../../utils/constants'; -/** - * NOTE - we are mocking the response until such time as we have a genuine card that returns the properties we want to test - */ +const test = mergeTests(cardWithAvs); -let currentMock = null; +test.describe('Test Card, binLookup w. panLength property & address fields', () => { + test('#1 Fill out PAN & see that focus moves to an address field since expiryDate & cvc are optional', async ({ cardWithAvs, page }) => { + await binLookupMock(page, optionalDateAndCvcWithPanLengthMock); -const getMock = val => { - const mock = mocks[val]; - currentMock = getBinLookupMock(binLookupUrl, mock); - return currentMock; -}; + const componentConfig = { billingAddressRequired: true, billingAddressRequiredFields: ['street', 'houseNumberOrName', 'postalCode', 'city'] }; -test.describe('Test how Card Component handles binLookup returning a panLength property for a card with address fields', () => { - // use config from panLength.avs.clientScripts.js + await cardWithAvs.goto(getStoryUrl({ baseUrl: URL_MAP.card, componentConfig })); - test.beforeEach(async () => { - // todo: go to the card page - // 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(); - }); + await cardWithAvs.isComponentVisible(); + + await cardWithAvs.typeCardNumber(REGULAR_TEST_CARD); - test('#1 Fill out PAN (binLookup w. panLength) see that focus moves to an address field since expiryDate & cvc are optional', async () => { - // use mock await t.addRequestHooks(getMock('optionalDateAndCVC')); - // Wait for field to appear in DOM - // await cardPage.numHolder(); - // - // const firstDigits = REGULAR_TEST_CARD.substring(0, 15); - // const lastDigits = REGULAR_TEST_CARD.substring(15, 16); - // - // await cardPage.cardUtils.fillCardNumber(t, firstDigits); - // - // await t.wait(INPUT_DELAY); - // - // await cardPage.cardUtils.fillCardNumber(t, lastDigits); - // - // // Expect focus to be place on address (street) field - // await t.expect(cardPage.addressLabelWithFocus.exists).ok(); + // Expect focus to be place on address (street) field + await expect(cardWithAvs.cardNumberInput).not.toBeFocused(); + await expect(cardWithAvs.billingAddress.streetInput).toBeFocused(); }); }); diff --git a/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.focus.kcp.spec.ts b/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.focus.kcp.spec.ts index dc2efe098..cc139dd89 100644 --- a/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.focus.kcp.spec.ts +++ b/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.focus.kcp.spec.ts @@ -1,58 +1,57 @@ -import { test } from '@playwright/test'; -import { mocks } from './mocks'; -import { binLookupUrl, getBinLookupMock, turnOffSDKMocking } from '../../cardMocks'; +import { mergeTests, expect } from '@playwright/test'; +import { test as cardWithKCP } from '../../../../../fixtures/card.fixture'; +import { getStoryUrl } from '../../../../utils/getStoryUrl'; +import { URL_MAP } from '../../../../../fixtures/URL_MAP'; +import { binLookupMock } from '../../../../../mocks/binLookup/binLookup.mock'; +import { kcpMockOptionalDateAndCvcWithPanLengthMock } from '../../../../../mocks/binLookup/binLookup.data'; +import { REGULAR_TEST_CARD } from '../../../../utils/constants'; -/** - * NOTE - we are mocking the response until such time as we have a genuine card that returns the properties we want to test - */ +const test = mergeTests(cardWithKCP); -let currentMock = null; - -const getMock = val => { - const mock = mocks[val]; - currentMock = getBinLookupMock(binLookupUrl, mock); - return currentMock; +const componentConfig = { + brands: ['mc', 'visa', 'amex', 'korean_local_card'], + configuration: { koreanAuthenticationRequired: true }, + countryCode: 'KR' }; test.describe('Test how Card Component handles binLookup returning a panLength property for a card with a KCP fields', () => { - test.beforeEach(async () => { - // use config from panLength.kcp.clientScripts.js - // await t.navigateTo(cardPage); - // 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(); - }); + test('#1 Fill out PAN (binLookup w. panLength) see that focus moves to tax number since expiryDate & cvc are optional', async ({ + cardWithKCP, + page + }) => { + await binLookupMock(page, kcpMockOptionalDateAndCvcWithPanLengthMock); + + await cardWithKCP.goto(getStoryUrl({ baseUrl: URL_MAP.card, componentConfig })); - test('#1 Fill out PAN (binLookup w. panLength) see that focus moves to tax number since expiryDate & cvc are optional', async () => { - // await t.addRequestHooks(getMock('kcpMock')); - // - // // Wait for field to appear in DOM - // await cardPage.numHolder(); - // - // const firstDigits = REGULAR_TEST_CARD.substring(0, 15); - // const lastDigits = REGULAR_TEST_CARD.substring(15, 16); - // - // await cardPage.cardUtils.fillCardNumber(t, firstDigits); - // - // await t.wait(INPUT_DELAY); - // - // await cardPage.cardUtils.fillCardNumber(t, lastDigits); - // - // // Expect focus to be place on tax number field - // await t.expect(cardPage.kcpTaxNumberLabelWithFocus.exists).ok(); + await cardWithKCP.isComponentVisible(); + + await cardWithKCP.typeCardNumber(REGULAR_TEST_CARD); + + // Expect UI change - tax number field has focus + await expect(cardWithKCP.cardNumberInput).not.toBeFocused(); + await expect(cardWithKCP.taxNumberInput).toBeFocused(); }); - test('#2 Paste non KCP PAN and see focus move to date field', async () => { - // await t.addRequestHooks(getMock('visaMock')); - // - // // Wait for field to appear in DOM - // await cardPage.numHolder(); - // - // await t.wait(1000); - // - // await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD, 'paste'); - // - // // Expect focus to be place on date field - // await t.expect(cardPage.dateLabelWithFocus.exists).ok(); + test('#2 Paste non KCP PAN and see focus move to date field', async ({ cardWithKCP, page }) => { + await cardWithKCP.goto(getStoryUrl({ baseUrl: URL_MAP.card, componentConfig })); + + await cardWithKCP.isComponentVisible(); + + // Place focus on the input + await cardWithKCP.cardNumberLabelElement.click(); + + // Copy text to clipboard + await page.evaluate(() => navigator.clipboard.writeText('4000620000000007')); // Can't use the constant for some reason + + await page.waitForTimeout(1000); + + // Paste text from clipboard + await page.keyboard.press('ControlOrMeta+V'); + + await page.waitForTimeout(1000); + + // Expect UI change - expiryDate field has focus + await expect(cardWithKCP.cardNumberInput).not.toBeFocused(); + await expect(cardWithKCP.expiryDateInput).toBeFocused(); }); }); diff --git a/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.focus.regular.spec.ts b/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.focus.regular.spec.ts index bcbf193a2..abd5b9bc0 100644 --- a/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.focus.regular.spec.ts +++ b/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.focus.regular.spec.ts @@ -1,260 +1,211 @@ -import { test } from '@playwright/test'; -import { mocks } from './mocks'; -import { binLookupUrl, getBinLookupMock, turnOffSDKMocking } from '../../cardMocks'; - -/** - * NOTE - we are mocking the response until such time as we have a genuine card that returns the properties we want to test - */ - -let currentMock = null; - -const getMock = val => { - const mock = mocks[val]; - currentMock = getBinLookupMock(binLookupUrl, mock); - return currentMock; -}; - -const removeRequestHook = async () => { - if (currentMock) { - // await t.removeRequestHooks(currentMock); // don't know if this is strictly necessary} - } -}; - -test.describe('Test how Card Component handles binLookup returning a panLength property (or not)', () => { - test.beforeEach(async () => { - // to config panLength.regular.clientScripts.js - //await t.navigateTo(cardPage); - // 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(); +import { test, expect } from '../../../../../fixtures/card.fixture'; +import { getStoryUrl } from '../../../../utils/getStoryUrl'; +import { URL_MAP } from '../../../../../fixtures/URL_MAP'; +import { AMEX_CARD, CARD_WITH_PAN_LENGTH, MULTI_LUHN_MAESTRO, REGULAR_TEST_CARD } from '../../../../utils/constants'; +import { binLookupMock } from '../../../../../mocks/binLookup/binLookup.mock'; +import { + hiddenDateWithPanLengthMock, + multiLengthMaestroWithPanLengthMock, + amexWithPanLengthMock, + optionalDateAndCvcWithPanLengthMock, + optionalDateWithPanLengthMock +} from '../../../../../mocks/binLookup/binLookup.data'; + +test.describe('Test Card, & binLookup w/o panLength property', () => { + test('#0 Fill out PAN & see that focus stays on number field', async ({ card }) => { + await card.goto(URL_MAP.card); + + await card.isComponentVisible(); + + // Check start state + await expect(card.cardNumberInput).not.toBeFocused(); + + await card.typeCardNumber(REGULAR_TEST_CARD); + + // Expect focus to be still be on number field + await expect(card.cardNumberInput).toBeFocused(); + await expect(card.expiryDateInput).not.toBeFocused(); }); - test("#1 Fill out PAN & see that focus stays on number field since binLookup doesn't return a panLength", async () => { - // await t.addRequestHooks(getMock('noPanLength')); - // - // // Wait for field to appear in DOM - // await cardPage.numHolder(); - // - // const firstDigits = REGULAR_TEST_CARD.substring(0, 15); - // const lastDigits = REGULAR_TEST_CARD.substring(15, 16); - // - // await cardPage.cardUtils.fillCardNumber(t, firstDigits); - // - // await t.wait(INPUT_DELAY); - // - // await cardPage.cardUtils.fillCardNumber(t, lastDigits); - // - // // Expect focus to be still be on number field - // await t.expect(cardPage.numLabelWithFocus.exists).ok(); - // await t.expect(cardPage.dateLabelWithFocus.exists).notOk(); +}); + +test.describe('Test Card, & binLookup w. panLength property', () => { + test('#1 Fill out PAN and see maxLength is set on cardNumber SF, and that focus moves to expiryDate', async ({ card }) => { + await card.goto(URL_MAP.card); + + await card.isComponentVisible(); + + await card.typeCardNumber(CARD_WITH_PAN_LENGTH); + + // Expect UI change - expiryDate field has focus + await expect(card.cardNumberInput).not.toBeFocused(); + await expect(card.expiryDateInput).toBeFocused(); + + // Expect iframe to exist in number field with maxlength attr set to 19 + let panInputMaxLength = await card.cardNumberInput.getAttribute('maxlength'); + expect(panInputMaxLength).toEqual('19'); + + // Delete number and see that the maxlength is reset on the iframe + await card.deleteCardNumber(); + panInputMaxLength = await card.cardNumberInput.getAttribute('maxlength'); + + expect(panInputMaxLength).toEqual('24'); }); - test('#2 Fill out PAN & see that since binLookup does return a panLength maxLength is set on number SF and that focus moves to expiryDate', async () => { - // await removeRequestHook(t); - // await t.addRequestHooks(getMock('panLength')); - // - // // Wait for field to appear in DOM - // await cardPage.numHolder(); - // - // const firstDigits = REGULAR_TEST_CARD.substring(0, 15); - // const lastDigits = REGULAR_TEST_CARD.substring(15, 16); - // - // await cardPage.cardUtils.fillCardNumber(t, firstDigits); - // - // await t.wait(INPUT_DELAY); - // - // await cardPage.cardUtils.fillCardNumber(t, lastDigits); - // - // // Expect iframe to exist in number field with maxlength attr set to 19 - // await t - // .switchToIframe(cardPage.iframeSelector.nth(0)) - // .expect(Selector('[data-fieldtype="encryptedCardNumber"]').getAttribute('maxlength')) - // .eql('19') // 4 blocks of 4 numbers with 3 spaces in between - // .switchToMainWindow(); - // - // // Expect focus to be place on Expiry date field - // await t.expect(cardPage.dateLabelWithFocus.exists).ok(); - // - // // Then delete card number and see that the maxlength is rest on the iframe - // await cardPage.cardUtils.deleteCardNumber(t); - // await t - // .switchToIframe(cardPage.iframeSelector.nth(0)) - // .expect(Selector('[data-fieldtype="encryptedCardNumber"]').getAttribute('maxlength')) - // .eql('24') - // .switchToMainWindow(); + test('#2 Fill out PAN & see that focus moves to CVC since expiryDate is optional', async ({ card, page }) => { + await binLookupMock(page, optionalDateWithPanLengthMock); + + await card.goto(URL_MAP.card); + + await card.isComponentVisible(); + + await card.typeCardNumber(REGULAR_TEST_CARD); + + // Expect UI change - cvc field has focus + await expect(card.cardNumberInput).not.toBeFocused(); + await expect(card.cvcInput).toBeFocused(); }); - test('#3 Fill out PAN (binLookup w. panLength) see that focus moves to CVC since expiryDate is optional', async () => { - // await removeRequestHook(t); - // await t.addRequestHooks(getMock('optionalDate')); - // - // // Wait for field to appear in DOM - // await cardPage.numHolder(); - // - // const firstDigits = REGULAR_TEST_CARD.substring(0, 15); - // const lastDigits = REGULAR_TEST_CARD.substring(15, 16); - // - // await cardPage.cardUtils.fillCardNumber(t, firstDigits); - // - // await t.wait(INPUT_DELAY); - // - // await cardPage.cardUtils.fillCardNumber(t, lastDigits); - // - // // Expect focus to be place on cvc field - // await t.expect(cardPage.cvcLabelWithFocus.exists).ok(); - // }); - // - // test('#4 Fill out PAN (binLookup w. panLength) see that focus moves to CVC since expiryDate is hidden', async () => { - // await removeRequestHook(t); - // await t.addRequestHooks(getMock('hiddenDate')); - // - // // Wait for field to appear in DOM - // await cardPage.numHolder(); - // - // const firstDigits = REGULAR_TEST_CARD.substring(0, 15); - // const lastDigits = REGULAR_TEST_CARD.substring(15, 16); - // - // await cardPage.cardUtils.fillCardNumber(t, firstDigits); - // - // await t.wait(INPUT_DELAY); - // - // await cardPage.cardUtils.fillCardNumber(t, lastDigits); - // - // // Expect focus to be place on cvc field - // await t.expect(cardPage.cvcLabelWithFocus.exists).ok(); + test('#3 Fill out PAN & see that focus moves to CVC since expiryDate is hidden', async ({ card, page }) => { + await binLookupMock(page, hiddenDateWithPanLengthMock); + + await card.goto(URL_MAP.card); + + await card.isComponentVisible(); + + await card.typeCardNumber(REGULAR_TEST_CARD); + + // Expect UI change - cvc field has focus + await expect(card.cardNumberInput).not.toBeFocused(); + await expect(card.cvcInput).toBeFocused(); }); - test('#5 Fill out PAN (binLookup w. panLength) see that focus moves to holderName since expiryDate & cvc are optional', async () => { - // await removeRequestHook(t); - // await t.addRequestHooks(getMock('optionalDateAndCVC')); - // - // // Wait for field to appear in DOM - // await cardPage.numHolder(); - // - // const firstDigits = REGULAR_TEST_CARD.substring(0, 15); - // const lastDigits = REGULAR_TEST_CARD.substring(15, 16); - // - // await cardPage.cardUtils.fillCardNumber(t, firstDigits); - // - // await t.wait(INPUT_DELAY); - // - // await cardPage.cardUtils.fillCardNumber(t, lastDigits); - // - // // Expect focus to be place on name field - // await t.expect(cardPage.holderNameLabelWithFocus.exists).ok(); + test('#4 Fill out PAN & see that focus moves to holderName since expiryDate & cvc are optional', async ({ card, page }) => { + await binLookupMock(page, optionalDateAndCvcWithPanLengthMock); + + const componentConfig = { hasHolderName: true, holderNameRequired: true }; + + await card.goto(getStoryUrl({ baseUrl: URL_MAP.card, componentConfig })); + + await card.isComponentVisible(); + + await card.typeCardNumber(REGULAR_TEST_CARD); + + // Expect UI change - holderName field has focus + await expect(card.cardNumberInput).not.toBeFocused(); + await expect(card.holderNameInput).toBeFocused(); }); - test('#6 Fill out invalid date, then fill PAN (binLookup w. panLength) see that focus moves to expiryDate since expiryDate is in error', async () => { - // await removeRequestHook(t); - // await t.addRequestHooks(getMock('optionalDate')); - // - // // Wait for field to appear in DOM - // await cardPage.numHolder(); - // - // // Card out of date - // await cardPage.cardUtils.fillDate(t, '12/90'); - // - // const firstDigits = REGULAR_TEST_CARD.substring(0, 15); - // const lastDigits = REGULAR_TEST_CARD.substring(15, 16); - // - // await cardPage.cardUtils.fillCardNumber(t, firstDigits); - // - // await t.wait(INPUT_DELAY); - // - // await cardPage.cardUtils.fillCardNumber(t, lastDigits); - // - // // Expect focus to be place on Expiry date field - // await t.expect(cardPage.dateLabelWithFocus.exists).ok(); + test('#5 Fill out invalid date on an optional date field, then fill & see that focus moves to (optional) expiryDate since expiryDate is in error', async ({ + card, + page + }) => { + await binLookupMock(page, optionalDateWithPanLengthMock); + + await card.goto(URL_MAP.card); + + await card.isComponentVisible(); + + // Card out of date + await card.fillExpiryDate('12/90'); + + await card.typeCardNumber(CARD_WITH_PAN_LENGTH); + + await page.waitForTimeout(500); + + // Expect UI change - expiryDate field has focus + await expect(card.cardNumberInput).not.toBeFocused(); + await expect(card.expiryDateInput).toBeFocused(); }); - test('#7 Fill out PAN by pasting number (binLookup w. panLength) & see that maxLength is set on number SF and that focus moves to expiryDate', async () => { - // await removeRequestHook(t); - // await t.addRequestHooks(getMock('panLength')); - // - // // Wait for field to appear in DOM - // await cardPage.numHolder(); - // - // await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD, 'paste'); - // - // // Expect iframe to exist in number field with maxlength attr set to 19 - // await t - // .switchToIframe(cardPage.iframeSelector.nth(0)) - // .expect(Selector('[data-fieldtype="encryptedCardNumber"]').getAttribute('maxlength')) - // .eql('19') // 4 blocks of 4 numbers with 3 spaces in between - // .switchToMainWindow(); - // - // // Expect focus to be place on Expiry date field - // await t.expect(cardPage.dateLabelWithFocus.exists).ok(); + test('#6 Fill out PAN by **pasting** number & see that that focus moves to expiryDate', async ({ card, page }) => { + await card.goto(URL_MAP.card); + + await card.isComponentVisible(); + + // Place focus on the input + await card.cardNumberLabelElement.click(); + + // Copy text to clipboard + await page.evaluate(() => navigator.clipboard.writeText('4000620000000007')); // Can't use the constant for some reason + + await page.waitForTimeout(1000); + + // Paste text from clipboard + await page.keyboard.press('ControlOrMeta+V'); + + await page.waitForTimeout(1000); + + // Expect UI change - expiryDate field has focus + await expect(card.cardNumberInput).not.toBeFocused(); + await expect(card.expiryDateInput).toBeFocused(); }); test( - "#8 Fill out PAN with binLookup panLength of 18 and see that when you fill in the 16th digit the focus doesn't jump " + + "#7 Fill out PAN with binLookup panLength of 18 and see that when you fill in the 16th digit the focus doesn't jump " + ' then complete the number to 18 digits and see the focus jump' + ' then delete the number and add an amex one and see the focus now jumps after 15 digits', - async () => { - // await removeRequestHook(t); - // await t.addRequestHooks(getMock('multiLengthMaestro')); - // - // // Wait for field to appear in DOM - // await cardPage.numHolder(); - // - // let firstDigits = MULTI_LUHN_MAESTRO.substring(0, 15); - // const middleDigits = MULTI_LUHN_MAESTRO.substring(15, 16); - // let lastDigits = MULTI_LUHN_MAESTRO.substring(16, 18); - // - // await cardPage.cardUtils.fillCardNumber(t, firstDigits); - // - // await t.wait(INPUT_DELAY); - // - // await cardPage.cardUtils.fillCardNumber(t, middleDigits); - // - // // Expect focus to be still be on number field - // await t.expect(cardPage.numLabelWithFocus.exists).ok(); - // await t.expect(cardPage.dateLabelWithFocus.exists).notOk(); - // await t.wait(INPUT_DELAY); - // await cardPage.cardUtils.fillCardNumber(t, lastDigits); - // - // // Expect focus to be placed on Expiry date field - // await t.expect(cardPage.dateLabelWithFocus.exists).ok(); - // - // // Then delete number & enter new number with a different binLookup response to see that focus now jumps after 15 digits - // await cardPage.cardUtils.deleteCardNumber(t); - // - // await removeRequestHook(t); - // await t.addRequestHooks(getMock('amexMock')); - // - // firstDigits = AMEX_CARD.substring(0, 14); - // let endDigits = AMEX_CARD.substring(14, 15); - // - // await cardPage.cardUtils.fillCardNumber(t, firstDigits); - // await t.wait(INPUT_DELAY); - // await cardPage.cardUtils.fillCardNumber(t, endDigits); - // - // // Expect focus to be place on Expiry date field - // await t.expect(cardPage.dateLabelWithFocus.exists).ok(); + async ({ card, page }) => { + await binLookupMock(page, multiLengthMaestroWithPanLengthMock); + + await card.goto(URL_MAP.card); + + await card.isComponentVisible(); + + const firstDigits = MULTI_LUHN_MAESTRO.substring(0, 16); + const lastDigits = MULTI_LUHN_MAESTRO.substring(16, 18); + + // Type first part of PAN + await card.typeCardNumber(firstDigits); + + // Expect focus to be still be on number field + await expect(card.cardNumberInput).toBeFocused(); + await expect(card.expiryDateInput).not.toBeFocused(); + + await page.waitForTimeout(100); + + // Type remaining digits + await card.typeCardNumber(lastDigits); + + // Expect UI change - expiryDate field has focus + await expect(card.cardNumberInput).not.toBeFocused(); + await expect(card.expiryDateInput).toBeFocused(); + + await page.waitForTimeout(100); + + // Delete number + await card.deleteCardNumber(); + + // Expect focus back on number field + await expect(card.cardNumberInput).toBeFocused(); + await expect(card.expiryDateInput).not.toBeFocused(); + + // Reset mock + await binLookupMock(page, amexWithPanLengthMock); + + // Type new PAN that will give different /binLookup response + await card.typeCardNumber(AMEX_CARD); + + // Expect UI change - expiryDate field has focus again + await expect(card.cardNumberInput).not.toBeFocused(); + await expect(card.expiryDateInput).toBeFocused(); } ); - test('#9 Fill out PAN with Visa num that binLookup says has a panLength of 16 - you should not then be able to type more digits in the card number field', async () => { - // await removeRequestHook(t); - // await t.addRequestHooks(getMock('visaMock')); - // - // // Wait for field to appear in DOM - // await cardPage.numHolder(); - // - // const firstDigits = REGULAR_TEST_CARD.substring(0, 15); - // const lastDigits = REGULAR_TEST_CARD.substring(15, 16); - // - // await cardPage.cardUtils.fillCardNumber(t, firstDigits); - // - // await t.wait(INPUT_DELAY); - // - // await cardPage.cardUtils.fillCardNumber(t, lastDigits); - // - // // Expect focus to be place on date field - // await t.expect(cardPage.dateLabelWithFocus.exists).ok(); - // - // // Should not be able to add more digits to the PAN - // await cardPage.cardUtils.fillCardNumber(t, '6'); - // await checkIframeInputContainsValue(t, cardPage.iframeSelector, 0, '.js-iframe-input', '5500 0000 0000 0004'); + test('#8 Fill out PAN with Visa num that binLookup says has a panLength of 16 - you should not then be able to type more digits in the card number field', async ({ + card + }) => { + await card.goto(URL_MAP.card); + + await card.isComponentVisible(); + + await card.typeCardNumber(CARD_WITH_PAN_LENGTH); + + // Should not be able to add more digits to the PAN + await card.cardNumberInput.press('End'); /** NOTE: how to add text at end */ + await card.typeCardNumber('6'); + + // Confirm PAN value has not had chars added + let val = await card.cardNumberInput.inputValue(); + expect(val).toEqual('4000 6200 0000 0007'); }); }); diff --git a/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.focus.ssn.spec.ts b/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.focus.ssn.spec.ts index 307545d2a..475bec38c 100644 --- a/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.focus.ssn.spec.ts +++ b/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.focus.ssn.spec.ts @@ -1,44 +1,27 @@ -import { test } from '@playwright/test'; -import { mocks } from './mocks'; -import { binLookupUrl, getBinLookupMock, turnOffSDKMocking } from '../../cardMocks'; +import { mergeTests, expect } from '@playwright/test'; +import { test as cardWithSSN } from '../../../../../fixtures/card.fixture'; +import { getStoryUrl } from '../../../../utils/getStoryUrl'; +import { URL_MAP } from '../../../../../fixtures/URL_MAP'; +import { binLookupMock } from '../../../../../mocks/binLookup/binLookup.mock'; +import { optionalDateAndCvcWithPanLengthMock } from '../../../../../mocks/binLookup/binLookup.data'; +import { REGULAR_TEST_CARD } from '../../../../utils/constants'; -/** - * NOTE - we are mocking the response until such time as we have a genuine card that returns the properties we want to test - */ +const test = mergeTests(cardWithSSN); -let currentMock = null; +test.describe('Test Card, binLookup w. panLength property & social security number', () => { + test('#1 Fill out PAN see that focus moves to social security number since expiryDate & cvc are optional', async ({ cardWithSSN, page }) => { + await binLookupMock(page, optionalDateAndCvcWithPanLengthMock); -const getMock = val => { - const mock = mocks[val]; - currentMock = getBinLookupMock(binLookupUrl, mock); - return currentMock; -}; + const componentConfig = { configuration: { socialSecurityNumberMode: 'show' } }; -test.describe('Test how Card Component handles binLookup returning a panLength property for a card with a social security number', () => { - test.beforeEach(async () => { - // use config from panLength.ssn.clientScripts.js - //await t.navigateTo(cardPage); - // 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(); - }); + await cardWithSSN.goto(getStoryUrl({ baseUrl: URL_MAP.card, componentConfig })); + + await cardWithSSN.isComponentVisible(); + + await cardWithSSN.typeCardNumber(REGULAR_TEST_CARD); - test('#1 Fill out PAN (binLookup w. panLength) see that focus moves to social security number since expiryDate & cvc are optional', async t => { - // await t.addRequestHooks(getMock('optionalDateAndCVC')); - // - // // Wait for field to appear in DOM - // await cardPage.numHolder(); - // - // const firstDigits = REGULAR_TEST_CARD.substring(0, 15); - // const lastDigits = REGULAR_TEST_CARD.substring(15, 16); - // - // await cardPage.cardUtils.fillCardNumber(t, firstDigits); - // - // await t.wait(INPUT_DELAY); - // - // await cardPage.cardUtils.fillCardNumber(t, lastDigits); - // - // // Expect focus to be place on ssn field - // await t.expect(cardPage.ssnLabelWithFocus.exists).ok(); + // Expect UI change - ssn field has focus + await expect(cardWithSSN.cardNumberInput).not.toBeFocused(); + await expect(cardWithSSN.ssnInput).toBeFocused(); }); }); diff --git a/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.kcp.clientScripts.js b/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.kcp.clientScripts.js deleted file mode 100644 index aa13b0ca9..000000000 --- a/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.kcp.clientScripts.js +++ /dev/null @@ -1,7 +0,0 @@ -window.cardConfig = { - brands: ['mc', 'visa', 'amex', 'korean_local_card'], - configuration: { - koreanAuthenticationRequired: true - }, - countryCode: 'KR' -}; diff --git a/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.regular.clientScripts.js b/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.regular.clientScripts.js deleted file mode 100644 index 0ebe21e5b..000000000 --- a/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.regular.clientScripts.js +++ /dev/null @@ -1,4 +0,0 @@ -window.cardConfig = { - hasHolderName: true, - holderNameRequired: true -}; diff --git a/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.ssn.clientScripts.js b/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.ssn.clientScripts.js deleted file mode 100644 index 587bfb453..000000000 --- a/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.ssn.clientScripts.js +++ /dev/null @@ -1,5 +0,0 @@ -window.cardConfig = { - configuration: { - socialSecurityNumberMode: 'show' - } -}; diff --git a/packages/e2e-playwright/tests/utils/constants.ts b/packages/e2e-playwright/tests/utils/constants.ts index cd2d567e1..e524d25ad 100644 --- a/packages/e2e-playwright/tests/utils/constants.ts +++ b/packages/e2e-playwright/tests/utils/constants.ts @@ -11,6 +11,7 @@ export const BCMC_DUAL_BRANDED_VISA = '4871049999999910'; // dual branded visa & export const BCMC_DUAL_BRANDED_MC = '5127880999999990'; // dual branded mc & bcmc export const DUAL_BRANDED_CARD_EXCLUDED = '4001230000000004'; // dual branded visa/star export const FAILS_LUHN_CARD = '4111111111111112'; +export const CARD_WITH_PAN_LENGTH = '4000620000000007'; export const THREEDS2_FRICTIONLESS_CARD = '5201281505129736'; export const THREEDS2_FULL_FLOW_CARD = '5000550000000029';