diff --git a/.changeset/config.json b/.changeset/config.json index 53c593809..80b20e1d6 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -7,5 +7,5 @@ "access": "public", "baseBranch": "main", "updateInternalDependencies": "patch", - "ignore": ["@adyen/adyen-web-e2e","@adyen/adyen-web-playwright","@adyen/adyen-web-playground","@adyen/adyen-web-server"] + "ignore": ["@adyen/adyen-web-playwright","@adyen/adyen-web-playground","@adyen/adyen-web-server"] } diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 2a2ed531c..60b7fe171 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -13,10 +13,17 @@ jobs: fail-fast: false matrix: node-version: [20.12.2] - api-version: ["v68", "v69", "v70", "v71"] + api-version: ["v69", "v70", "v71"] # node-version: [16.x, 18.x, 19.x] # Currently 18 and 19 are not supported, still keeping it # as a reminder for compatibility check + env: + CHECKOUT_API_KEY: ${{secrets.ADYEN_CHECKOUT_API_KEY}} + MERCHANT_ACCOUNT: ${{secrets.ADYEN_CHECKOUT_MERCHANT_ACCOUNT}} + CLIENT_KEY: ${{secrets.ADYEN_CHECKOUT_CLIENT_KEY}} + CLIENT_ENV: test + TESTING_ENVIRONMENT: https://checkout-test.adyen.com/checkout/${{matrix.api-version}} + API_VERSION: ${{matrix.api-version}} steps: - name: Checkout uses: actions/checkout@v4 @@ -26,21 +33,11 @@ jobs: node-version: ${{ matrix.node-version }} - name: Install Project Dependencies run: yarn install - - name: Build Project - run: yarn build - name: Install Playwright Dependencies working-directory: packages/e2e-playwright/ run: yarn install && npx playwright install --with-deps - name: Run E2E Tests - working-directory: packages/e2e-playwright/ - run: yarn test:headless - env: - CHECKOUT_API_KEY: ${{secrets.ADYEN_CHECKOUT_API_KEY}} - MERCHANT_ACCOUNT: ${{secrets.ADYEN_CHECKOUT_MERCHANT_ACCOUNT}} - CLIENT_KEY: ${{secrets.ADYEN_CHECKOUT_CLIENT_KEY}} - CLIENT_ENV: test - TESTING_ENVIRONMENT: https://checkout-test.adyen.com/checkout/${{matrix.api-version}} - API_VERSION: ${{matrix.api-version}} + run: yarn test:e2e - name: Archive test result artifacts if: always() uses: actions/upload-artifact@v4 diff --git a/package.json b/package.json index c34c579bf..a554aabd9 100644 --- a/package.json +++ b/package.json @@ -14,16 +14,16 @@ ], "scripts": { "start": "concurrently --kill-others-on-fail \"yarn workspace @adyen/adyen-web start\" \"yarn workspace @adyen/adyen-web-playground start\" --names \"lib,playground\"", - "build": "yarn workspace @adyen/adyen-web build", "start:storybook": "yarn workspace @adyen/adyen-web start:storybook", + "start:prod-storybook": "yarn workspace @adyen/adyen-web-server start:storybook", + "build": "yarn workspace @adyen/adyen-web build", "build:storybook": "yarn workspace @adyen/adyen-web build:storybook", "format": "yarn workspace @adyen/adyen-web format", "lint": "yarn workspace @adyen/adyen-web lint", "test": "yarn workspace @adyen/adyen-web test", "test:watch": "yarn workspace @adyen/adyen-web test:watch", "test:coverage": "yarn workspace @adyen/adyen-web test:coverage", - "test:e2e": "yarn build && yarn workspace @adyen/adyen-web-playwright test:headless", - "test:e2e-testcafe": "yarn build && yarn workspace @adyen/adyen-web-e2e test:e2e", + "test:e2e": "yarn workspace @adyen/adyen-web-playwright test:headless", "type-check": "yarn workspace @adyen/adyen-web type-check", "prepare": "yarn workspace @adyen/adyen-web prepare", "changeset": "changeset", diff --git a/packages/e2e-playwright/app/config/postcss.config.js b/packages/e2e-playwright/app/config/postcss.config.js deleted file mode 100644 index 986069731..000000000 --- a/packages/e2e-playwright/app/config/postcss.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - plugins: [require('autoprefixer'), require('cssnano')] -}; diff --git a/packages/e2e-playwright/app/config/webpack.config.js b/packages/e2e-playwright/app/config/webpack.config.js deleted file mode 100644 index 292371fee..000000000 --- a/packages/e2e-playwright/app/config/webpack.config.js +++ /dev/null @@ -1,116 +0,0 @@ -const webpack = require('webpack'); -const path = require('path'); -const fs = require('fs'); -const HTMLWebpackPlugin = require('html-webpack-plugin'); -const checkoutDevServer = require('@adyen/adyen-web-server'); -const host = process.env.HOST || '0.0.0.0'; -const port = '3024'; -const resolve = dir => path.resolve(__dirname, dir); - -const basePageDir = path.join(__dirname, `../src/pages/`); -// NOTE: The first page in the array will be considered the index page. -// Automatically get -const htmlPages = fs.readdirSync(basePageDir).map(fileName => ({ - id: fileName -})); - -//console.log('htmlPages', htmlPages); - -const htmlPageGenerator = ({ id }, index) => { - console.log('htmlPageGenerator', id, index); - return new HTMLWebpackPlugin({ - // 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', - chunks: [`AdyenDemo${id}`], - chunksSortMode: 'manual' - }); -}; - -const entriesReducer = (acc, { id }) => { - acc[`AdyenDemo${id}`] = path.join(__dirname, `../src/pages/${id}/${id}.js`); - return acc; -}; - -module.exports = { - mode: 'development', - - resolve: { - extensions: ['.js', '.jsx', '.ts', '.tsx', '.scss'] - }, - - plugins: [ - ...htmlPages.map(htmlPageGenerator), - new webpack.HotModuleReplacementPlugin(), - new webpack.DefinePlugin({ - 'process.env': { - __SF_ENV__: JSON.stringify(process.env.SF_ENV || 'build'), - __CLIENT_KEY__: JSON.stringify(process.env.CLIENT_KEY || null), - __CLIENT_ENV__: JSON.stringify(process.env.CLIENT_ENV || 'test') - } - }) - ], - - devtool: 'cheap-module-source-map', - - entry: { - ...htmlPages.reduce(entriesReducer, {}) - }, - - watchOptions: { - ignored: ['/node_modules/', '/!(@adyen/adyen-web/dist)/'], - aggregateTimeout: 200, - poll: 500 - }, - - module: { - rules: [ - { - oneOf: [ - { - test: [/\.js?$/], - include: [resolve('../src')], - exclude: /node_modules/, - use: [ - { - loader: 'ts-loader', - options: { configFile: resolve('../../tsconfig.json') } - } - ] - }, - { - test: [/\.scss$/, /\.css$/], - resolve: { extensions: ['.scss', '.css'] }, - use: [ - { - loader: 'style-loader' - }, - { - loader: 'css-loader' - }, - { - loader: 'sass-loader' - } - ] - } - ] - } - ] - }, - - devServer: { - port, - host: '0.0.0.0', - https: false, - hot: true, - compress: true, - onBeforeSetupMiddleware: devServer => { - if (!devServer) { - throw new Error('webpack-dev-server is not defined'); - } - checkoutDevServer(devServer.app); - } - } -}; diff --git a/packages/e2e-playwright/app/src/handlers.js b/packages/e2e-playwright/app/src/handlers.js deleted file mode 100644 index a15976feb..000000000 --- a/packages/e2e-playwright/app/src/handlers.js +++ /dev/null @@ -1,81 +0,0 @@ -import { makePayment, makeDetailsCall, createOrder } from './services'; - -export function showAuthorised(message = 'Authorised') { - const resultElement = document.getElementById('result-message'); - resultElement.classList.remove('hide'); - resultElement.innerText = message; -} - -export function showResult(message, isError) { - const resultElement = document.getElementById('result-message'); - resultElement.classList.remove('hide'); - if (isError) { - resultElement.classList.add('error'); - } - resultElement.innerText = message; -} - -export function handleError(obj) { - const resultElement = document.getElementById('result-message'); - resultElement.classList.remove('hide'); - resultElement.classList.add('error'); - resultElement.innerText = obj; - - // SecuredField related errors should not go straight to console.error - if (obj.type === 'card') { - console.log('### Card::onError:: obj=', obj); - } else { - console.error(obj); - } -} - -export async function handleSubmit(state, component, actions) { - try { - const { action, order, resultCode, donationToken } = await makePayment(state.data); - - if (!resultCode) actions.reject(); - - actions.resolve({ - resultCode, - action, - order, - donationToken - }); - } catch (error) { - console.error('## onSubmit - critical error', error); - actions.reject(); - } -} - -export async function handleAdditionalDetails(state, component, actions) { - try { - const { resultCode, action, order, donationToken } = await makeDetailsCall(state.data); - - if (!resultCode) actions.reject(); - - actions.resolve({ - resultCode, - action, - order, - donationToken - }); - } catch (error) { - console.error('## onAdditionalDetails - critical error', error); - actions.reject(); - } -} - -export function handlePaymentCompleted(data, component) { - component.remove(); - showAuthorised(); -} - -export function handleOrderRequest(resolve, reject, data) { - return createOrder(data) - .then(response => { - resolve(response); - }) - .catch(error => { - reject(error); - }); -} diff --git a/packages/e2e-playwright/app/src/pages/ANCV/ANCV.html b/packages/e2e-playwright/app/src/pages/ANCV/ANCV.html deleted file mode 100644 index ab7c551cc..000000000 --- a/packages/e2e-playwright/app/src/pages/ANCV/ANCV.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - Adyen Web | ANCV - - - - -
-
-
-
-

ANCV

-
-
-
-
-
-
- -
- -
- - - - diff --git a/packages/e2e-playwright/app/src/pages/ANCV/ANCV.js b/packages/e2e-playwright/app/src/pages/ANCV/ANCV.js deleted file mode 100644 index 1b9f758e6..000000000 --- a/packages/e2e-playwright/app/src/pages/ANCV/ANCV.js +++ /dev/null @@ -1,44 +0,0 @@ -import { AdyenCheckout, ANCV } from '@adyen/adyen-web'; -import '@adyen/adyen-web/styles/adyen.css'; -import { handleError, handlePaymentCompleted, showAuthorised } from '../../handlers'; -import { shopperLocale, countryCode } from '../../services/commonConfig'; -import '../../style.scss'; -import { createSession } from '../../services'; - -const initCheckout = async () => { - const successTestAmount = { currency: 'EUR', value: 2000 }; - - const session = await createSession({ - amount: successTestAmount, - shopperLocale, - countryCode, - reference: 'mock-playwright', - returnUrl: 'http://localhost:3024/' - }); - - const checkout = await AdyenCheckout({ - environment: process.env.__CLIENT_ENV__, - analytics: { - enabled: false - }, - amount: successTestAmount, - session, - clientKey: process.env.__CLIENT_KEY__, - locale: shopperLocale, - showPayButton: true, - _environmentUrls: { - cdn: { - translations: '/' - } - }, - onPaymentCompleted: handlePaymentCompleted, - onOrderUpdated: data => { - showAuthorised('Partially Authorised'); - }, - onError: handleError - }); - - window.paymentMethod = new ANCV(checkout).mount('.ancv-field'); -}; - -initCheckout(); diff --git a/packages/e2e-playwright/app/src/pages/Cards/Cards.html b/packages/e2e-playwright/app/src/pages/Cards/Cards.html deleted file mode 100644 index c865080e8..000000000 --- a/packages/e2e-playwright/app/src/pages/Cards/Cards.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - Adyen Web | Cards - - - - -
-
-
-
-

Card

-
-
-
-
-
- -
-
- -
- - - - diff --git a/packages/e2e-playwright/app/src/pages/Cards/Cards.js b/packages/e2e-playwright/app/src/pages/Cards/Cards.js deleted file mode 100644 index 2ea536def..000000000 --- a/packages/e2e-playwright/app/src/pages/Cards/Cards.js +++ /dev/null @@ -1,53 +0,0 @@ -import { AdyenCheckout, Card } from '@adyen/adyen-web'; -import '@adyen/adyen-web/styles/adyen.css'; -import { handleSubmit, handleAdditionalDetails, handleError, handlePaymentCompleted } from '../../handlers'; -import { amount, shopperLocale, countryCode } from '../../services/commonConfig'; -import '../../style.scss'; - -const initCheckout = async () => { - // window.TextEncoder = null; // Comment in to force use of "compat" version - window.checkout = await AdyenCheckout({ - amount, - clientKey: process.env.__CLIENT_KEY__, - locale: shopperLocale, - countryCode, - environment: 'test', - _environmentUrls: { - cdn: { - translations: '/' - } - }, - showPayButton: true, - onSubmit: handleSubmit, - onAdditionalDetails: handleAdditionalDetails, - onPaymentCompleted: handlePaymentCompleted, - onError: handleError, - ...window.mainConfiguration - }); - - // Credit card with installments - window.card = new Card(checkout, { - brands: ['mc', 'visa', 'amex', 'maestro', 'bcmc'], - onChange: state => { - /** - * Needed now that, for v5, we enhance the securedFields state.errors object with a rootNode prop - * - Testcafe doesn't like a ClientFunction retrieving an object with a DOM node in it!? - * - * AND, for some reason, if you place this onChange function in expiryDate.clientScripts.js it doesn't always get read. - * It'll work when it's part of a small batch but if part of the full test suite it gets ignored - so the tests that rely on - * window.mappedStateErrors fail - */ - if (!!Object.keys(state.errors).length) { - // Replace any rootNode values in the objects in state.errors with an empty string - const nuErrors = Object.entries(state.errors).reduce((acc, [fieldType, error]) => { - acc[fieldType] = error ? { ...error, rootNode: '' } : error; - return acc; - }, {}); - window.mappedStateErrors = nuErrors; - } - }, - ...window.cardConfig - }).mount('.card-field'); -}; - -initCheckout(); diff --git a/packages/e2e-playwright/app/src/pages/CustomCards/CustomCards.html b/packages/e2e-playwright/app/src/pages/CustomCards/CustomCards.html deleted file mode 100644 index 7ace7ce41..000000000 --- a/packages/e2e-playwright/app/src/pages/CustomCards/CustomCards.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - Adyen Web | Custom Cards - - - - - -
-
- -
-
-
-

CustomCard #1

-
-
- - card - - - - - - - - - -
- -
-
-
- - -
-
-
-

CustomCard #2

-
-
- - - - - - - - - - - -
-
-
- -
- - - - diff --git a/packages/e2e-playwright/app/src/pages/CustomCards/CustomCards.js b/packages/e2e-playwright/app/src/pages/CustomCards/CustomCards.js deleted file mode 100644 index da861006c..000000000 --- a/packages/e2e-playwright/app/src/pages/CustomCards/CustomCards.js +++ /dev/null @@ -1,93 +0,0 @@ -import { AdyenCheckout, CustomCard } from '@adyen/adyen-web'; -import '@adyen/adyen-web/styles/adyen.css'; -import { handleSubmit, handleAdditionalDetails, handlePaymentCompleted, showAuthorised } from '../../handlers'; -import { amount, shopperLocale, countryCode } from '../../services/commonConfig'; -import '../../style.scss'; -import './customcards.style.scss'; -import { setFocus, onBrand, onConfigSuccess, onBinLookup, onChange } from './customCards.config'; -import { makePayment } from '../../services'; - -const initCheckout = async () => { - // window.TextEncoder = null; // Comment in to force use of "compat" version - window.checkout = await AdyenCheckout({ - amount, - clientKey: process.env.__CLIENT_KEY__, - locale: shopperLocale, - countryCode, - _environmentUrls: { - cdn: { - translations: '/' - } - }, - environment: 'test', - showPayButton: true, - onPaymentCompleted: handlePaymentCompleted, - ...window.mainConfiguration - }); - - window.customCard = new CustomCard(checkout, { - type: 'card', - brands: ['mc', 'visa', 'synchrony_plcc'], - onConfigSuccess, - onBrand, - onFocus: setFocus, - onBinLookup, - onChange, - ...window.cardConfig - }).mount('.secured-fields'); - - createPayButton('.secured-fields', window.customCard, 'customCardRegular'); - - window.customCardSeparate = new CustomCard(checkout, { - type: 'card', - brands: ['mc', 'visa', 'synchrony_plcc'], - onConfigSuccess, - onBrand, - onFocus: setFocus, - onBinLookup, - onChange, - ...window.cardConfig - }).mount('.secured-fields-2'); - - createPayButton('.secured-fields-2', window.customCardSeparate, 'customCardSeparate'); - - function createPayButton(parent, component, attribute) { - const payBtn = document.createElement('button'); - - payBtn.textContent = 'Pay'; - payBtn.name = `pay-${attribute}`; - payBtn.setAttribute('data-testid', `pay-${attribute}`); - payBtn.classList.add('adyen-checkout__button', 'js-components-button--one-click', `js-pay-${attribute}`); - - payBtn.addEventListener('click', async e => { - e.preventDefault(); - - console.log('### CustomCards::createPayButton:: click attribut', attribute); - - if (!component.isValid) return component.showValidation(); - - // formatData - const paymentMethod = { - type: 'scheme', - ...component.state.data - }; - component.state.data = { paymentMethod }; - - const response = await makePayment(component.state.data); - component.setStatus('ready'); - - if (response.action) { - component.handleAction(response.action, window.actionConfigObject || {}); - } else if (response.resultCode) { - component.remove(); - showAuthorised(); - } - }); - - document.querySelector(parent).appendChild(payBtn); - - return payBtn; - } -}; - -initCheckout(); diff --git a/packages/e2e-playwright/app/src/pages/CustomCards/customCards.config.js b/packages/e2e-playwright/app/src/pages/CustomCards/customCards.config.js deleted file mode 100644 index 4e06d1455..000000000 --- a/packages/e2e-playwright/app/src/pages/CustomCards/customCards.config.js +++ /dev/null @@ -1,279 +0,0 @@ -let hideCVC = false; -let optionalCVC = false; -let hideDate = false; -let optionalDate = false; -let isDualBranding = false; - -function setAttributes(el, attrs) { - for (const key in attrs) { - el.setAttribute(key, attrs[key]); - } -} - -function setLogosActive(rootNode, mode) { - const imageHolder = rootNode.querySelector('.pm-image'); - const dualBrandingImageHolder = rootNode.querySelector('.pm-image-dual'); - - switch (mode) { - case 'dualBranding_notValid': - Object.assign(imageHolder.style, { display: 'none' }); - Object.assign(dualBrandingImageHolder.style, { display: 'block', 'pointer-events': 'none', opacity: 0.5 }); - break; - - case 'dualBranding_valid': - Object.assign(imageHolder.style, { display: 'none' }); - Object.assign(dualBrandingImageHolder.style, { display: 'block', 'pointer-events': 'auto', opacity: 1 }); - break; - - default: - // reset - Object.assign(imageHolder.style, { display: 'block' }); - Object.assign(dualBrandingImageHolder.style, { display: 'none' }); - } -} - -export function onConfigSuccess(pCallbackObj) { - /** - * Set the UI to it's starting state - */ - pCallbackObj.rootNode.style.display = 'block'; - - pCallbackObj.rootNode.querySelector('.pm-image-dual').style.display = 'none'; - - setLogosActive(pCallbackObj.rootNode); -} - -export function setCCErrors(pCallbackObj) { - if (!pCallbackObj.rootNode) return; - - const sfNode = pCallbackObj.rootNode.querySelector(`[data-cse="${pCallbackObj.fieldType}"]`); - const errorNode = sfNode.parentNode.querySelector('.pm-form-label__error-text'); - - if (errorNode.innerText === '' && pCallbackObj.error === '') return; - - if (pCallbackObj.error !== '') { - errorNode.style.display = 'block'; - errorNode.innerText = pCallbackObj.errorI18n; - - // Add error classes - setErrorClasses(sfNode, true); - return; - } - - // Else: error === '' - errorNode.style.display = 'none'; - errorNode.innerText = ''; - - // Remove error classes - setErrorClasses(sfNode, false); -} - -export function setFocus(pCallbackObj) { - const sfNode = pCallbackObj.rootNode.querySelector(`[data-cse="${pCallbackObj.fieldType}"]`); - setFocusClasses(sfNode, pCallbackObj.focus); -} - -export function onBrand(pCallbackObj) { - /** - * If not in dual branding mode - add card brand to first image element - */ - if (!isDualBranding) { - const brandLogo1 = pCallbackObj.rootNode.querySelector('img'); - setAttributes(brandLogo1, { - src: pCallbackObj.brandImageUrl, - alt: pCallbackObj.brand - }); - } - - /** - * Deal with showing/hiding CVC field - */ - const cvcNode = pCallbackObj.rootNode.querySelector('.pm-form-label--cvc'); - - if (pCallbackObj.cvcPolicy === 'hidden' && !hideCVC) { - hideCVC = true; - cvcNode.style.display = 'none'; - } - - if (hideCVC && pCallbackObj.cvcPolicy !== 'hidden') { - hideCVC = false; - cvcNode.style.display = 'block'; - } - - // Optional cvc fields - if (pCallbackObj.cvcPolicy === 'optional' && !optionalCVC) { - optionalCVC = true; - if (cvcNode) cvcNode.querySelector('.pm-form-label__text').innerText = 'Security code (optional)'; - } - - if (optionalCVC && pCallbackObj.cvcPolicy !== 'optional') { - optionalCVC = false; - if (cvcNode) cvcNode.querySelector('.pm-form-label__text').innerText = 'Security code'; - } - - /** - * Deal with showing/hiding date field(s) - */ - const dateNode = pCallbackObj.rootNode.querySelector('.pm-form-label--exp-date'); - const monthNode = pCallbackObj.rootNode.querySelector('.pm-form-label--exp-month'); - const yearNode = pCallbackObj.rootNode.querySelector('.pm-form-label--exp-year'); - - if (pCallbackObj.expiryDatePolicy === 'hidden' && !hideDate) { - hideDate = true; - if (dateNode) dateNode.style.display = 'none'; - if (monthNode) monthNode.style.display = 'none'; - if (yearNode) yearNode.style.display = 'none'; - } - - if (hideDate && pCallbackObj.expiryDatePolicy !== 'hidden') { - hideDate = false; - if (dateNode) dateNode.style.display = 'block'; - if (monthNode) monthNode.style.display = 'block'; - if (yearNode) yearNode.style.display = 'block'; - } - - // Optional date fields - if (pCallbackObj.expiryDatePolicy === 'optional' && !optionalDate) { - optionalDate = true; - if (dateNode) dateNode.querySelector('.pm-form-label__text').innerText = 'Expiry date (optional)'; - if (monthNode) monthNode.querySelector('.pm-form-label__text').innerText = 'Expiry month (optional)'; - if (yearNode) yearNode.querySelector('.pm-form-label__text').innerText = 'Expiry year (optional)'; - } - - if (optionalDate && pCallbackObj.expiryDatePolicy !== 'optional') { - optionalDate = false; - if (dateNode) dateNode.querySelector('.pm-form-label__text').innerText = 'Expiry date'; - if (monthNode) monthNode.querySelector('.pm-form-label__text').innerText = 'Expiry month'; - if (yearNode) yearNode.querySelector('.pm-form-label__text').innerText = 'Expiry year'; - } -} - -function dualBrandListener(e) { - securedFields.dualBrandingChangeHandler(e); -} - -function resetDualBranding(rootNode) { - isDualBranding = false; - - setLogosActive(rootNode); - - const brandLogo1 = rootNode.querySelector('.pm-image-dual-1'); - brandLogo1.removeEventListener('click', dualBrandListener); - - const brandLogo2 = rootNode.querySelector('.pm-image-dual-2'); - brandLogo2.removeEventListener('click', dualBrandListener); -} - -/** - * Implementing dual branding - */ -function onDualBrand(pCallbackObj) { - const brandLogo1 = pCallbackObj.rootNode.querySelector('.pm-image-dual-1'); - const brandLogo2 = pCallbackObj.rootNode.querySelector('.pm-image-dual-2'); - - isDualBranding = true; - - const supportedBrands = pCallbackObj.supportedBrandsRaw; - - /** - * Set first brand icon (and, importantly also add alt &/or data-value attrs); and add event listener - */ - setAttributes(brandLogo1, { - src: supportedBrands[0].brandImageUrl, - alt: supportedBrands[0].brand, - 'data-value': supportedBrands[0].brand - }); - - brandLogo1.addEventListener('click', dualBrandListener); - - /** - * Set second brand icon (and, importantly also add alt &/or data-value attrs); and add event listener - */ - setAttributes(brandLogo2, { - src: supportedBrands[1].brandImageUrl, - alt: supportedBrands[1].brand, - 'data-value': supportedBrands[1].brand - }); - brandLogo2.addEventListener('click', dualBrandListener); -} - -export function onBinLookup(pCallbackObj) { - /** - * Dual branded result... - */ - if (pCallbackObj.supportedBrandsRaw?.length > 1) { - onDualBrand(pCallbackObj); - return; - } - - /** - * ...else - binLookup 'reset' result or binLookup result with only one brand - */ - resetDualBranding(pCallbackObj.rootNode); -} - -export function onChange(state, component) { - // From v5 the onError handler is no longer only for card comp related errors - so watch state.errors and call the card specific setCCErrors based on this - if (!!Object.keys(state.errors).length) { - const errors = Object.entries(state.errors).map(([fieldType, error]) => { - return { - fieldType, - ...(error ? error : { error: '', rootNode: component._node }) - }; - }); - errors.forEach(setCCErrors); - } - - /** - * If we're in a dual branding scenario & the number field becomes valid or is valid and become invalid - * - set the brand logos to the required 'state' - */ - if (isDualBranding) { - const mode = state.valid.encryptedCardNumber ? 'dualBranding_valid' : 'dualBranding_notValid'; - setLogosActive(component._node, mode); - } - - /** - * For running the e2e tests in testcafe - we need a mapped version of state.errors - * since, for v5, we enhance the securedFields state.errors object with a rootNode prop - * & Testcafe doesn't like a ClientFunction retrieving an object with a DOM node in it!? - */ - if (!!Object.keys(state.errors).length) { - // Replace any rootNode values in the objects in state.errors with an empty string - const nuErrors = Object.entries(state.errors).reduce((acc, [fieldType, error]) => { - acc[fieldType] = error ? { ...error, rootNode: '' } : error; - return acc; - }, {}); - window.mappedStateErrors = nuErrors; - } -} - -const setErrorClasses = function (pNode, pSetErrors) { - if (pSetErrors) { - if (pNode.className.indexOf('pm-input-field--error') === -1) { - pNode.className += ' pm-input-field--error'; - } - return; - } - - // Remove errors - if (pNode.className.indexOf('pm-input-field--error') > -1) { - const newClassName = pNode.className.replace('pm-input-field--error', ''); - pNode.className = newClassName.trim(); - } -}; - -const setFocusClasses = function (pNode, pSetFocus) { - if (pSetFocus) { - if (pNode.className.indexOf('pm-input-field--focus') === -1) { - pNode.className += ' pm-input-field--focus'; - } - return; - } - - // Remove focus - if (pNode.className.indexOf('pm-input-field--focus') > -1) { - const newClassName = pNode.className.replace('pm-input-field--focus', ''); - pNode.className = newClassName.trim(); - } -}; diff --git a/packages/e2e-playwright/app/src/pages/CustomCards/customcards.style.scss b/packages/e2e-playwright/app/src/pages/CustomCards/customcards.style.scss deleted file mode 100644 index 2eb27b23b..000000000 --- a/packages/e2e-playwright/app/src/pages/CustomCards/customcards.style.scss +++ /dev/null @@ -1,99 +0,0 @@ -.merchant-checkout__payment-method{ - padding-top: 20px; -} - -.secured-fields, -.secured-fields-2 { - position: relative; - font-family: 'Open Sans', sans-serif; - font-size: 14px; - padding: 0 24px; -} -.pm-image, .pm-image-dual{ - background-color: #ffffff; - border-radius: 4px; - -moz-boder-radius: 4px; - -webkit-border-radius: 4px; - float: right; - line-height: 0; - position: relative; - overflow: hidden; -} -.pm-form-label { - float: left; - padding-bottom: 1em; - position: relative; - width: 100%; -} -.pm-form-label--exp-date { - width: 40%; -} -.pm-form-label--cvc { - float: right; - width: 40%; -} -.pm-form-label__text { - color: #00112c; - float: left; - font-size: 0.93333em; - padding-bottom: 6px; - position: relative; -} -.pm-input-field { - background: white; - border: 1px solid #d8d8d8; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - border-radius: 4px; - box-sizing: border-box; - clear: left; - font-size: 0.93333333333em; - float: left; - padding: 8px; - position: relative; - width: 100%; - height: 35px; -} - -.pm-form-label__error-text { - color: #ff7d00; - display: none; - float: left; - font-size: 13px; - padding-top: 0.4em; - position: relative; - width: 100%; -} - -/* Set dynamically */ -.pm-input-field--error, -.secured-fields-si.pm-input-field--error { - border: 1px solid #ff7d00; -} - -.pm-input-field--focus { - border: 1px solid #969696; - outline: none; -} -.pm-input-field--error.pm-input-field--focus { - border: 1px solid #ff7d00; -} - -.card-input__spinner__holder { - position: relative; - top: 40px; -} - -.card-input__spinner { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 1; - display: none; -} - -.card-input__spinner--active { - display: block; -} diff --git a/packages/e2e-playwright/app/src/pages/Dropin/Dropin.html b/packages/e2e-playwright/app/src/pages/Dropin/Dropin.html deleted file mode 100644 index f43e81f87..000000000 --- a/packages/e2e-playwright/app/src/pages/Dropin/Dropin.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - Adyen Web | Drop-in - - - - - -
-
-
-
-
- - - - diff --git a/packages/e2e-playwright/app/src/pages/Dropin/Dropin.js b/packages/e2e-playwright/app/src/pages/Dropin/Dropin.js deleted file mode 100644 index d03a1d431..000000000 --- a/packages/e2e-playwright/app/src/pages/Dropin/Dropin.js +++ /dev/null @@ -1,33 +0,0 @@ -import { AdyenCheckout, Dropin } from '@adyen/adyen-web/auto'; -import '@adyen/adyen-web/styles/adyen.css'; -import { getPaymentMethods } from '../../services'; -import { amount, shopperLocale, countryCode } from '../../services/commonConfig'; -import { handleSubmit, handleAdditionalDetails, handleError, handlePaymentCompleted } 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, - _environmentUrls: { - cdn: { - translations: '/' - } - }, - environment: 'test', - onSubmit: handleSubmit, - onAdditionalDetails: handleAdditionalDetails, - onPaymentCompleted: handlePaymentCompleted, - onError: handleError, - ...window.mainConfiguration - }); - - window.dropin = new Dropin(window.checkout, { ...window.dropinConfig }).mount('#dropin-container'); -}; - -initCheckout(); diff --git a/packages/e2e-playwright/app/src/pages/DropinSessions/DropinSessions.html b/packages/e2e-playwright/app/src/pages/DropinSessions/DropinSessions.html deleted file mode 100644 index 11b9bc076..000000000 --- a/packages/e2e-playwright/app/src/pages/DropinSessions/DropinSessions.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - Adyen Web | Drop-in - - - - - -
-
-
-
-
-
- - - - diff --git a/packages/e2e-playwright/app/src/pages/DropinSessions/DropinSessions.js b/packages/e2e-playwright/app/src/pages/DropinSessions/DropinSessions.js deleted file mode 100644 index f009e5b9a..000000000 --- a/packages/e2e-playwright/app/src/pages/DropinSessions/DropinSessions.js +++ /dev/null @@ -1,35 +0,0 @@ -import { AdyenCheckout, Dropin } from '@adyen/adyen-web/auto'; -import '@adyen/adyen-web/styles/adyen.css'; -import { createSession, getPaymentMethods } from '../../services'; -import { amount, shopperLocale, countryCode } from '../../services/commonConfig'; -import { handleError, handlePaymentCompleted } from '../../handlers'; -import '../../style.scss'; - -const initCheckout = async () => { - const session = await createSession({ - amount, - shopperLocale, - countryCode, - reference: 'mock-playwright', - returnUrl: 'http://localhost:3024/dropinsessions', - ...window.sessionConfig - }); - - window.checkout = await AdyenCheckout({ - clientKey: process.env.__CLIENT_KEY__, - session, - environment: 'test', - _environmentUrls: { - cdn: { - translations: '/' - } - }, - onPaymentCompleted: handlePaymentCompleted, - onError: handleError, - ...window.mainConfiguration - }); - - window.dropin = new Dropin(window.checkout, { ...window.dropinConfig }).mount('#dropin-container'); -}; - -initCheckout(); diff --git a/packages/e2e-playwright/app/src/pages/IssuerLists/IssuerLists.html b/packages/e2e-playwright/app/src/pages/IssuerLists/IssuerLists.html deleted file mode 100644 index 040ae49f5..000000000 --- a/packages/e2e-playwright/app/src/pages/IssuerLists/IssuerLists.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - Adyen Web | Issuer Lists - - - - -
-
-
-
-

IssuerList

-
-
-
-
-
-
-
- - - - diff --git a/packages/e2e-playwright/app/src/pages/IssuerLists/IssuerLists.js b/packages/e2e-playwright/app/src/pages/IssuerLists/IssuerLists.js deleted file mode 100644 index 49863779d..000000000 --- a/packages/e2e-playwright/app/src/pages/IssuerLists/IssuerLists.js +++ /dev/null @@ -1,65 +0,0 @@ -import { AdyenCheckout, Dotpay } from '@adyen/adyen-web'; -import '@adyen/adyen-web/styles/adyen.css'; -import { handleSubmit, handleAdditionalDetails, handleError, handlePaymentCompleted } from '../../handlers'; -import { amount, shopperLocale, countryCode } from '../../services/commonConfig'; -import '../../style.scss'; - -const initCheckout = async () => { - const checkout = await AdyenCheckout({ - analytics: { - enabled: false - }, - amount, - // These tests are very conditional on the expected issuers being listed and in the existing order, so we mock the pmResponse - paymentMethodsResponse: { - paymentMethods: [ - { - issuers: [ - { - id: '73', - name: 'BLIK' - }, - - { - id: '81', - name: 'Idea Cloud' - }, - - { - id: '68', - name: 'mRaty' - }, - { - id: '1', - name: 'mTransfer' - }, - { - id: '91', - name: 'Nest Bank' - } - ], - name: 'Local Polish Payment Methods', - type: 'dotpay' - } - ] - }, - clientKey: process.env.__CLIENT_KEY__, - locale: shopperLocale, - _environmentUrls: { - cdn: { - translations: '/' - } - }, - countryCode, - environment: 'test', - showPayButton: true, - onSubmit: handleSubmit, - onAdditionalDetails: handleAdditionalDetails, - onPaymentCompleted: handlePaymentCompleted, - onError: handleError - }); - - window.dotpay = new Dotpay(checkout, { highlightedIssuers: ['73', '81', '68'] }).mount('.issuer-field'); -}; - -initCheckout(); diff --git a/packages/e2e-playwright/app/src/pages/OpenInvoices/OpenInvoices.html b/packages/e2e-playwright/app/src/pages/OpenInvoices/OpenInvoices.html deleted file mode 100644 index 604426a7b..000000000 --- a/packages/e2e-playwright/app/src/pages/OpenInvoices/OpenInvoices.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - Adyen Web | Open Invoices - - - - - -
-
-
-
-
-
-
- - - - diff --git a/packages/e2e-playwright/app/src/pages/OpenInvoices/OpenInvoices.js b/packages/e2e-playwright/app/src/pages/OpenInvoices/OpenInvoices.js deleted file mode 100644 index 30942e850..000000000 --- a/packages/e2e-playwright/app/src/pages/OpenInvoices/OpenInvoices.js +++ /dev/null @@ -1,33 +0,0 @@ -import { AdyenCheckout, Riverty } from '@adyen/adyen-web'; -import '@adyen/adyen-web/styles/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, - _environmentUrls: { - cdn: { - translations: '/' - } - }, - environment: 'test', - onSubmit: handleSubmit, - onAdditionalDetails: handleAdditionalDetails, - onError: handleError, - showPayButton: true, - ...window.mainConfiguration - }); - - window.riverty = new Riverty(checkout, window.rivertyConfig).mount('#rivertyContainer'); -}; - -initCheckout(); diff --git a/packages/e2e-playwright/app/src/pages/Redirects/Redirects.html b/packages/e2e-playwright/app/src/pages/Redirects/Redirects.html deleted file mode 100644 index 0338b5950..000000000 --- a/packages/e2e-playwright/app/src/pages/Redirects/Redirects.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - Adyen Web | Redirects - - - - -
-
-
-
-

iDeal

-
-
-
-
-
-
-
- - - - diff --git a/packages/e2e-playwright/app/src/pages/Redirects/Redirects.js b/packages/e2e-playwright/app/src/pages/Redirects/Redirects.js deleted file mode 100644 index 84e0a7cd0..000000000 --- a/packages/e2e-playwright/app/src/pages/Redirects/Redirects.js +++ /dev/null @@ -1,45 +0,0 @@ -import { AdyenCheckout, AmazonPay, Redirect } from '@adyen/adyen-web'; -import '@adyen/adyen-web/styles/adyen.css'; -import { handleSubmit, handleAdditionalDetails, handleError, handlePaymentCompleted } from '../../handlers'; -import { amount, shopperLocale, countryCode } from '../../services/commonConfig'; -import '../../style.scss'; -import { getPaymentMethods } from '../../services'; - -const initCheckout = async () => { - const paymentMethodsResponse = await getPaymentMethods({ - amount, - shopperLocale - }); - - const onSubmit = (state, component, actions) => { - state.data.returnUrl = 'http://localhost:3024/result'; - handleSubmit(state, component, actions); - }; - - const checkout = await AdyenCheckout({ - analytics: { - enabled: false - }, - amount, - paymentMethodsResponse, - clientKey: process.env.__CLIENT_KEY__, - locale: shopperLocale, - _environmentUrls: { - cdn: { - translations: '/' - } - }, - countryCode, - environment: 'test', - showPayButton: true, - onSubmit, //: handleSubmit, - onAdditionalDetails: handleAdditionalDetails, - onPaymentCompleted: handlePaymentCompleted, - onError: handleError - // ...window.mainConfiguration - }); - - window.ideal = new Redirect(checkout, { ...window.redirectConfig }).mount('.redirect-field'); -}; - -initCheckout(); diff --git a/packages/e2e-playwright/app/src/pages/Result/Result.html b/packages/e2e-playwright/app/src/pages/Result/Result.html deleted file mode 100644 index 795885016..000000000 --- a/packages/e2e-playwright/app/src/pages/Result/Result.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - Adyen Web | Result - - - - - -
-
-
-
- Loading... -
-
-
-
- - - - - diff --git a/packages/e2e-playwright/app/src/pages/Result/Result.js b/packages/e2e-playwright/app/src/pages/Result/Result.js deleted file mode 100644 index 212b9583c..000000000 --- a/packages/e2e-playwright/app/src/pages/Result/Result.js +++ /dev/null @@ -1,44 +0,0 @@ -import { AdyenCheckout } from '@adyen/adyen-web'; -import '@adyen/adyen-web/styles/adyen.css'; -import '../../style.scss'; - -import { countryCode } from '../../services/commonConfig'; -import { handleAdditionalDetails, showResult } from '../../handlers'; - -import { getSearchParameters } from '../../../../../playground/src/utils'; - -async function handleRedirectResult(redirectResult) { - window.checkout = await AdyenCheckout({ - countryCode, - clientKey: process.env.__CLIENT_KEY__, - environment: process.env.__CLIENT_ENV__, - onAdditionalDetails: handleAdditionalDetails, - - // Called for: Authorised (Success), Received (Expired) - onPaymentCompleted: result => { - document.querySelector('#result-container ').innerHTML = ''; - - showResult(result.resultCode, false); - }, - - // Called for: Refused (Failed), Cancelled (Cancelled) - onPaymentFailed: result => { - document.querySelector('#result-container ').innerHTML = ''; - - showResult(result.resultCode, true); - }, - onError: result => { - document.querySelector('#result-container ').innerHTML = JSON.stringify(result, null, '\t'); - } - }); - - checkout.submitDetails({ details: { redirectResult } }); -} - -const { redirectResult } = getSearchParameters(window.location.search); - -if (!redirectResult) { - window.location.href = '/'; -} else { - await handleRedirectResult(redirectResult); -} diff --git a/packages/e2e-playwright/app/src/services/commonConfig.js b/packages/e2e-playwright/app/src/services/commonConfig.js deleted file mode 100644 index 38abc45c0..000000000 --- a/packages/e2e-playwright/app/src/services/commonConfig.js +++ /dev/null @@ -1,26 +0,0 @@ -import getCurrency from '../utils/getCurrency'; -import { getSearchParameters } from '../utils/utils'; - -const DEFAULT_LOCALE = 'en-US'; -const DEFAULT_COUNTRY = 'US'; - -const urlParams = getSearchParameters(window.location.search); -export const shopperLocale = DEFAULT_LOCALE; -export const countryCode = urlParams.countryCode || DEFAULT_COUNTRY; -export const currency = getCurrency(countryCode); -export const amountValue = urlParams.amount || 25900; -export const amount = { - currency, - value: Number(amountValue) -}; - -export const returnUrl = 'http://localhost:3024/result'; -export const shopperReference = 'newshoppert'; - -export default { - amount, - countryCode, - shopperLocale, - channel: 'Web', - shopperReference: 'newshoppert' -}; diff --git a/packages/e2e-playwright/app/src/services/index.js b/packages/e2e-playwright/app/src/services/index.js deleted file mode 100644 index 4beb8268a..000000000 --- a/packages/e2e-playwright/app/src/services/index.js +++ /dev/null @@ -1,71 +0,0 @@ -import paymentMethodsConfig from './paymentMethodsConfig'; -import paymentsConfig from './paymentsConfig'; -import { httpPost } from '../utils/utils'; - -export const createSession = (data, config = {}) => { - return httpPost('sessions', data) - .then(response => { - if (response.error) throw 'Session initiation failed'; - return response; - }) - .catch(console.error); -}; - -export const getPaymentMethods = configuration => - httpPost('paymentMethods', { ...paymentMethodsConfig, ...configuration }) - .then(response => { - if (response.error) throw 'No paymentMethods available'; - return response; - }) - .catch(console.error); - -export const makePayment = (data, config = {}) => { - // NOTE: Merging data object. DO NOT do this in production. - const paymentRequest = { ...paymentsConfig, ...config, ...data }; - return httpPost('payments', paymentRequest) - .then(response => { - if (response.error) throw 'Payment initiation failed'; - return response; - }) - .catch(console.error); -}; - -export const makeDetailsCall = data => - httpPost('details', data) - .then(response => { - if (response.error) throw 'Details call failed'; - return response; - }) - .catch(err => console.error(err)); - -export const getOriginKey = (originKeyOrigin = document.location.origin) => - httpPost('originKeys', { originDomains: [originKeyOrigin] }).then(response => response.originKeys[originKeyOrigin]); - -export const checkBalance = data => { - return httpPost('paymentMethods/balance', data) - .then(response => { - if (response.error) throw 'Balance call failed'; - return response; - }) - .catch(err => console.error(err)); -}; - -export const createOrder = data => { - const reference = `order-reference-${Date.now()}`; - - return httpPost('orders', { reference, ...data }) - .then(response => { - if (response.error) throw 'Orders call failed'; - return response; - }) - .catch(err => console.error(err)); -}; - -export const cancelOrder = data => { - return httpPost('orders/cancel', data) - .then(response => { - if (response.error) throw 'Orders call failed'; - return response; - }) - .catch(err => console.error(err)); -}; diff --git a/packages/e2e-playwright/app/src/services/paymentMethodsConfig.js b/packages/e2e-playwright/app/src/services/paymentMethodsConfig.js deleted file mode 100644 index 549b1534f..000000000 --- a/packages/e2e-playwright/app/src/services/paymentMethodsConfig.js +++ /dev/null @@ -1,15 +0,0 @@ -import commonConfiguration from './commonConfig'; - -const paymentMethodsConfig = { - ...commonConfiguration, - shopperName: { - firstName: 'Jan', - lastName: 'Jansen', - gender: 'MALE' - }, - telephoneNumber: '0612345678', - shopperEmail: 'test@adyen.com', - dateOfBirth: '1970-07-10' -}; - -export default paymentMethodsConfig; diff --git a/packages/e2e-playwright/app/src/services/paymentsConfig.js b/packages/e2e-playwright/app/src/services/paymentsConfig.js deleted file mode 100644 index 06d2077c0..000000000 --- a/packages/e2e-playwright/app/src/services/paymentsConfig.js +++ /dev/null @@ -1,34 +0,0 @@ -import commonConfiguration from './commonConfig'; - -const identifier = new Date().getMilliseconds(); -const { origin = 'http://localhost:3024', search } = window.location; -const returnUrl = origin + search; - -const paymentsConfig = { - ...commonConfiguration, - origin, - returnUrl, - reference: `${identifier}-checkout-components-ref`, - additionalData: { - allow3DS2: true - }, - shopperEmail: 'test@adyen.com', - shopperIP: '172.30.0.1', - channel: 'Web', - browserInfo: { - acceptHeader: 'http' - }, - lineItems: [ - { - taxPercentage: 0, - id: 'item1', - taxAmount: 0, - description: 'Test Item 1', - amountIncludingTax: 75900, - quantity: 1, - taxCategory: 'None', - amountExcludingTax: 75900 - } - ] -}; -export default paymentsConfig; diff --git a/packages/e2e-playwright/app/src/style.scss b/packages/e2e-playwright/app/src/style.scss deleted file mode 100644 index e4ecd2a02..000000000 --- a/packages/e2e-playwright/app/src/style.scss +++ /dev/null @@ -1,32 +0,0 @@ -*, -*:after, -*:before { - box-sizing: border-box; -} -html, -body { - font: 16px/1.21 -apple-system, BlinkMacSystemFont, sans-serif; - font-weight: 400; - margin: 0; -} - -.merchant-checkout__form { - max-width: 540px; - margin: 5vh auto; -} - -.merchant-checkout__result { - background-color: #6abf6a; - color: white; - padding: 20px; - border-radius: 4px; - text-align: center; -} - -.hide { - display: none; -} - -.error{ - background-color: #f00; -} diff --git a/packages/e2e-playwright/app/src/utils/getCurrency.js b/packages/e2e-playwright/app/src/utils/getCurrency.js deleted file mode 100644 index 6b861cf32..000000000 --- a/packages/e2e-playwright/app/src/utils/getCurrency.js +++ /dev/null @@ -1,36 +0,0 @@ -const currencies = { - AR: 'ARS', - AU: 'AUD', - BR: 'BRL', - CA: 'CAD', - CH: 'CHF', - CN: 'CNY', - DK: 'DKK', - GB: 'GBP', - HK: 'HKD', - HU: 'HUN', - ID: 'IDR', - IN: 'INR', - JP: 'JPY', - KR: 'KRW', - MG: 'MGA', - MX: 'MXN', - MY: 'MYR', - NO: 'NOK', - NZ: 'NZD', - PH: 'PHP', - PL: 'PLN', - RO: 'RON', - RU: 'RUB', - SE: 'SEK', - SG: 'SGD', - TH: 'THB', - TW: 'TWD', - US: 'USD', - VN: 'VND', - default: 'EUR' -}; - -const getCurrency = countryCode => currencies[countryCode] || currencies.default; - -export default getCurrency; diff --git a/packages/e2e-playwright/app/src/utils/handlers.js b/packages/e2e-playwright/app/src/utils/handlers.js deleted file mode 100644 index 35b8c29ab..000000000 --- a/packages/e2e-playwright/app/src/utils/handlers.js +++ /dev/null @@ -1,37 +0,0 @@ -import { makePayment, makeDetailsCall } from '../services'; - -export function handleResponse(response, component) { - if (response.action) { - component.handleAction(response.action); - } else if (response.resultCode) { - alert(response.resultCode); - } -} - -export function handleError(obj) { - console.error(obj); -} - -export function handleSubmit(state, component) { - component.setStatus('loading'); - - return makePayment(state.data) - .then(response => { - component.setStatus('ready'); - handleResponse(response, component); - }) - .catch(error => { - throw Error(error); - }); -} - -export function handleAdditionalDetails(details, component) { - return makeDetailsCall(details.data) - .then(response => { - component.setStatus('ready'); - handleResponse(response, component); - }) - .catch(error => { - throw Error(error); - }); -} diff --git a/packages/e2e-playwright/app/src/utils/utils.js b/packages/e2e-playwright/app/src/utils/utils.js deleted file mode 100644 index f329d9e57..000000000 --- a/packages/e2e-playwright/app/src/utils/utils.js +++ /dev/null @@ -1,21 +0,0 @@ -const { host, protocol } = window.location; - -export const httpPost = (endpoint, data) => - fetch(`${protocol}//${host}/${endpoint}`, { - method: 'POST', - headers: { - Accept: 'application/json, text/plain, */*', - 'Content-Type': 'application/json' - }, - body: JSON.stringify(data) - }).then(response => response.json()); - -export const getSearchParameters = (search = window.location.search) => - search - .replace(/\?/g, '') - .split('&') - .reduce((acc, cur) => { - const [key, prop = ''] = cur.split('='); - acc[key] = decodeURIComponent(prop); - return acc; - }, {}); diff --git a/packages/e2e-playwright/environment-variables.js b/packages/e2e-playwright/environment-variables.js new file mode 100644 index 000000000..ec139d0bf --- /dev/null +++ b/packages/e2e-playwright/environment-variables.js @@ -0,0 +1,7 @@ +import * as dotenv from 'dotenv'; +import * as path from 'path'; +dotenv.config({ path: path.resolve('../../', '.env') }); + +const protocol = process.env.IS_HTTPS === 'true' ? 'https' : 'http'; + +export { protocol }; diff --git a/packages/e2e-playwright/fixtures/URL_MAP.ts b/packages/e2e-playwright/fixtures/URL_MAP.ts new file mode 100644 index 000000000..0d8b12c4e --- /dev/null +++ b/packages/e2e-playwright/fixtures/URL_MAP.ts @@ -0,0 +1,29 @@ +export const URL_MAP = { + /* Drop-in */ + dropinWithSession: '/iframe.html?args=&globals=&id=dropin-default--auto&viewMode=story', + dropinWithAdvanced: '/iframe.html?globals=&args=useSessions:!false&id=dropin-default--auto&viewMode=story', + dropinSessions_zeroAuthCard_success: + '/iframe.html?globals=&args=amount:0;sessionData.recurringProcessingModel:CardOnFile;sessionData.storePaymentMethodMode:askForConsent&id=dropin-default--auto&viewMode=story', + dropinSessions_zeroAuthCard_fail: + '/iframe.html?globals=&args=amount:0;sessionData.recurringProcessingModel:CardOnFile;sessionData.storePaymentMethodMode:askForConsent;sessionData.enableOneClick:!true&id=dropin-default--auto&viewMode=story', + /* Card */ + card: '/iframe.html?args=&id=cards-card--default&viewMode=story', + cardWithAvs: '/iframe.html?args=&globals=&id=cards-card--with-avs&viewMode=story', + cardWithPartialAvs: '/iframe.html?args=&globals=&id=cards-card--with-partial-avs&viewMode=story', + cardWithInstallments: '/iframe.html?args=&id=cards-card--with-installments&viewMode=story', + cardWithKcp: '/iframe.html?args=&id=cards-card--kcp&viewMode=story', + cardWithClickToPay: '/iframe.html?args=&id=cards-card--with-click-to-pay&viewMode=story', + bcmc: '/iframe.html?args=&globals=&id=cards-bancontact--default&viewMode=story', + /* Custom card */ + customCard: '/iframe.html?globals=&args=&id=cards-custom-card--default&viewMode=story', + customCardSeparateExpiryDate: '/iframe.html?globals=&args=&id=cards-custom-card--variant&viewMode=story', + /* Await */ + ancv: '/iframe.html?args=useSessions:!true&globals=&id=components-ancv--default&viewMode=story', + /* Issuer list */ + onlineBankingPL: '/iframe.html?args=&globals=&id=issuerlist-onlinebankingpl--default&viewMode=story', + /* Open invoice */ + riverty: '/iframe.html?globals=&args=&id=components-riverty--default&viewMode=story', + rivertyWithVisibleSrPanel: '/iframe.html?args=srConfig.showPanel:!true&globals=&id=components-riverty--default&viewMode=story', + /* Redirect */ + ideal: '/iframe.html?globals=&id=components-ideal--default&viewMode=story' +}; diff --git a/packages/e2e-playwright/fixtures/card.fixture.ts b/packages/e2e-playwright/fixtures/card.fixture.ts new file mode 100644 index 000000000..4645cf1f5 --- /dev/null +++ b/packages/e2e-playwright/fixtures/card.fixture.ts @@ -0,0 +1,29 @@ +import { test as base, expect } from '@playwright/test'; +import { Card } from '../models/card'; +import { BCMC } from '../models/bcmc'; +import { URL_MAP } from './URL_MAP'; +import { CardWithAvs } from '../models/card-avs'; + +type Fixture = { + card: Card; + cardWithAvs: CardWithAvs; + bcmc: BCMC; +}; + +const test = base.extend({ + card: async ({ page }, use) => { + const cardPage = new Card(page); + await use(cardPage); + }, + cardWithAvs: async ({ page }, use) => { + const cardPage = new CardWithAvs(page); + await use(cardPage); + }, + bcmc: async ({ page }, use) => { + const bcmc = new BCMC(page); + await bcmc.goto(URL_MAP.bcmc); + await use(bcmc); + } +}); + +export { test, expect }; diff --git a/packages/e2e-playwright/fixtures/customCard.fixture.ts b/packages/e2e-playwright/fixtures/customCard.fixture.ts new file mode 100644 index 000000000..ea03f4962 --- /dev/null +++ b/packages/e2e-playwright/fixtures/customCard.fixture.ts @@ -0,0 +1,25 @@ +import { test as base, expect } from '@playwright/test'; +import { URL_MAP } from './URL_MAP'; +import { CustomCard } from '../models/customCard'; +import { CustomCardSeparateExpiryDate } from '../models/customCardSeparateExpiryDate'; + +type Fixture = { + customCard: CustomCard; + customCardSeparateExpiryDate: CustomCardSeparateExpiryDate; +}; + +const test = base.extend({ + customCard: async ({ page }, use) => { + const cardPage = new CustomCard(page); + await cardPage.goto(URL_MAP.customCard); + await use(cardPage); + }, + + customCardSeparateExpiryDate: async ({ page }, use) => { + const cardPage = new CustomCardSeparateExpiryDate(page); + await cardPage.goto(URL_MAP.customCardSeparateExpiryDate); + await use(cardPage); + } +}); + +export { test, expect }; diff --git a/packages/e2e-playwright/fixtures/dropin.fixture.ts b/packages/e2e-playwright/fixtures/dropin.fixture.ts new file mode 100644 index 000000000..55be304af --- /dev/null +++ b/packages/e2e-playwright/fixtures/dropin.fixture.ts @@ -0,0 +1,24 @@ +import { test as base, expect, mergeTests } from '@playwright/test'; +import { test as card } from './card.fixture'; +import { DropinWithSession } from '../models/dropinWithSession'; +import { Dropin } from '../models/dropin'; + +type Fixture = { + dropin: Dropin; + dropinWithSession: DropinWithSession; +}; + +const test = base.extend({ + dropin: async ({ page }, use) => { + const dropin = new Dropin(page); + await use(dropin); + }, + dropinWithSession: async ({ page }, use) => { + const dropin = new DropinWithSession(page); + await use(dropin); + } +}); + +const cardInDropin = mergeTests(card, test); + +export { test, expect, cardInDropin }; diff --git a/packages/e2e-playwright/fixtures/issuer-list.fixture.ts b/packages/e2e-playwright/fixtures/issuer-list.fixture.ts new file mode 100644 index 000000000..dfa655eb8 --- /dev/null +++ b/packages/e2e-playwright/fixtures/issuer-list.fixture.ts @@ -0,0 +1,36 @@ +import { test as base, expect } from '@playwright/test'; +import { IssuerList } from '../models/issuer-list'; +import { getStoryUrl } from '../tests/utils/getStoryUrl'; +import { URL_MAP } from './URL_MAP'; + +type Fixture = { + onlineBankingPL: IssuerList; +}; + +const test = base.extend({ + onlineBankingPL: async ({ page }, use) => { + const issuerListPage = new IssuerList(page); + const issuers = [ + { + id: '154', + name: 'BLIK' + }, + + { + id: '141', + name: 'e-transfer Pocztowy24' + } + ]; + await issuerListPage.goto( + getStoryUrl({ + baseUrl: URL_MAP.onlineBankingPL, + componentConfig: { + highlightedIssuers: issuers.map(issuer => issuer.id) + } + }) + ); + await use(issuerListPage); + } +}); + +export { test, expect }; diff --git a/packages/e2e-playwright/fixtures/openInvoices.fixture.ts b/packages/e2e-playwright/fixtures/openInvoices.fixture.ts new file mode 100644 index 000000000..9e27be000 --- /dev/null +++ b/packages/e2e-playwright/fixtures/openInvoices.fixture.ts @@ -0,0 +1,23 @@ +import { test as base, expect } from '@playwright/test'; +import { OpenInvoices } from '../models/openInvoices'; +import { URL_MAP } from './URL_MAP'; + +type Fixture = { + //openInvoicesPage: OpenInvoices; + riverty: OpenInvoices; +}; + +const test = base.extend({ + /* openInvoicesPage: async ({ page }, use) => { + const openInvoicePage = new OpenInvoices(page); + await use(openInvoicePage); + },*/ + + riverty: async ({ page }, use) => { + const openInvoicePage = new OpenInvoices(page); + await openInvoicePage.goto(URL_MAP.rivertyWithVisibleSrPanel); + await use(openInvoicePage); + } +}); + +export { test, expect }; diff --git a/packages/e2e-playwright/fixtures/redirect.fixture.ts b/packages/e2e-playwright/fixtures/redirect.fixture.ts new file mode 100644 index 000000000..f03fa1cab --- /dev/null +++ b/packages/e2e-playwright/fixtures/redirect.fixture.ts @@ -0,0 +1,17 @@ +import { test as base, expect, Page } from '@playwright/test'; +import { Redirect } from '../models/redirect'; +import { URL_MAP } from './URL_MAP'; + +type Fixture = { + ideal: Redirect; +}; + +const test = base.extend({ + ideal: async ({ page }, use) => { + const redirect = new Redirect(page); + await redirect.goto(URL_MAP.ideal); + await use(redirect); + } +}); + +export { test, expect }; diff --git a/packages/e2e-playwright/fixtures/srPanel.fixture.ts b/packages/e2e-playwright/fixtures/srPanel.fixture.ts new file mode 100644 index 000000000..461ad08db --- /dev/null +++ b/packages/e2e-playwright/fixtures/srPanel.fixture.ts @@ -0,0 +1,14 @@ +import { test as base, expect } from '@playwright/test'; +import { SRPanel } from '../models/srPanel'; + +type Fixture = { + srPanel: SRPanel; +}; + +const test = base.extend({ + srPanel: async ({ page }, use) => { + await use(new SRPanel(page)); + } +}); + +export { test, expect }; diff --git a/packages/e2e-playwright/mocks/sessions/sessions.data.ts b/packages/e2e-playwright/mocks/sessions/sessions.data.ts index c999b6d81..55eb33696 100644 --- a/packages/e2e-playwright/mocks/sessions/sessions.data.ts +++ b/packages/e2e-playwright/mocks/sessions/sessions.data.ts @@ -1,4 +1,5 @@ import { SESSION_DATA_MOCK } from '../../tests/utils/constants'; +import { protocol } from '../../environment-variables'; const sessionsMockData = { amount: { currency: 'EUR', value: 2000 }, @@ -6,7 +7,7 @@ const sessionsMockData = { countryCode: 'FR', id: 'CSFF69355B6EAD2F68', merchantAccount: 'TestMerchantCheckout', - returnUrl: 'http://localhost:3024/', + returnUrl: `${protocol}://localhost:3020/`, shopperLocale: 'en-US', sessionData: SESSION_DATA_MOCK }; diff --git a/packages/e2e-playwright/mocks/sessions/sessions.mock.ts b/packages/e2e-playwright/mocks/sessions/sessions.mock.ts index c0508dd0d..0026399c0 100644 --- a/packages/e2e-playwright/mocks/sessions/sessions.mock.ts +++ b/packages/e2e-playwright/mocks/sessions/sessions.mock.ts @@ -1,6 +1,7 @@ import { Page } from '@playwright/test'; +import { protocol } from '../../environment-variables'; -const SESSION_URL = 'http://localhost:3024/sessions'; +const SESSION_URL = `${protocol}://localhost:3020/sessions`; const sessionsMock = async (page: Page, mockedResponse: any): Promise => { await page.route(SESSION_URL, (route, request) => { diff --git a/packages/e2e-playwright/mocks/setup/setup.data.ts b/packages/e2e-playwright/mocks/setup/setup.data.ts index 3f43c12b0..8a5d241d3 100644 --- a/packages/e2e-playwright/mocks/setup/setup.data.ts +++ b/packages/e2e-playwright/mocks/setup/setup.data.ts @@ -1,11 +1,12 @@ import { SESSION_DATA_MOCK } from '../../tests/utils/constants'; +import { protocol } from '../../environment-variables'; const setupMockData = { amount: { currency: 'EUR', value: 2000 }, countryCode: 'FR', expiresAt: '2023-10-10T14:27:12+02:00', id: 'CSC9B0D869C74EC53D', - returnUrl: 'http://localhost:3024/', + returnUrl: `${protocol}://localhost:3020/`, shopperLocale: 'en-US', configuration: { enableStoreDetails: false }, paymentMethods: { @@ -47,7 +48,7 @@ const setupWithAncvOrderMockData = { countryCode: 'FR', expiresAt: '2023-10-10T15:12:59+02:00', id: 'CS9094FF58AB7D9B23', - returnUrl: 'http://localhost:3020/result', + returnUrl: `${protocol}://localhost:3020/result`, shopperLocale: 'en-US', configuration: { enableStoreDetails: false diff --git a/packages/e2e-playwright/models/address.ts b/packages/e2e-playwright/models/address.ts index 0bf2ef0a9..823ecc303 100644 --- a/packages/e2e-playwright/models/address.ts +++ b/packages/e2e-playwright/models/address.ts @@ -4,21 +4,45 @@ class Address { readonly rootElement: Locator; readonly rootElementSelector: string; - readonly countrySelector: Locator; - readonly addressInput: Locator; - readonly houseNumberInput: Locator; - readonly postalCodeInput: Locator; - readonly cityInput: Locator; - - constructor(page: Page, rootElementSelector: string = '.adyen-checkout__fieldset--billingAddress') { + constructor( + public readonly page: Page, + rootElementSelector: string = '.adyen-checkout__fieldset--billingAddress' + ) { this.rootElement = page.locator(rootElementSelector); this.rootElementSelector = rootElementSelector; + } + + get countrySelector() { + return this.rootElement.getByRole('combobox', { name: /country\/region/i }); + } + + get streetInput() { + return this.rootElement.getByRole('textbox', { name: /street/i }); + } + + get streetInputError() { + return this.rootElement.locator('.adyen-checkout__field--street').locator('.adyen-checkout-contextual-text--error'); + } + + get houseNumberInput() { + return this.rootElement.getByRole('textbox', { name: /house number/i }); + } + + get cityInput() { + return this.rootElement.getByRole('textbox', { name: /city/i }); + } + + get postalCodeInput() { + return this.rootElement.getByRole('textbox', { exact: false, name: /code/i }); // US uses 'Zip Code', the rest uses 'Postal Code'; + } + + async fillInPostCode(postCode: string) { + await this.postalCodeInput.fill(postCode); + } - this.countrySelector = this.rootElement.getByLabel('Country'); - this.addressInput = this.rootElement.getByLabel('Street'); - this.houseNumberInput = this.rootElement.getByLabel('House number'); - this.postalCodeInput = this.rootElement.getByLabel('Postal code'); - this.cityInput = this.rootElement.getByLabel('City'); + async selectCountry(options: { name?: RegExp | string }) { + await this.countrySelector.click(); + await this.rootElement.getByRole('option', options).click(); } } diff --git a/packages/e2e-playwright/models/ancv.ts b/packages/e2e-playwright/models/ancv.ts index 41f88fbcd..77bd06592 100644 --- a/packages/e2e-playwright/models/ancv.ts +++ b/packages/e2e-playwright/models/ancv.ts @@ -1,9 +1,10 @@ import { Locator, Page } from '@playwright/test'; import { USER_TYPE_DELAY } from '../tests/utils/constants'; +import { Base } from './base'; const SELECTOR_DELAY = 300; -class ANCV { +class ANCV extends Base { readonly rootElement: Locator; readonly rootElementSelector: string; @@ -12,10 +13,11 @@ class ANCV { readonly awaitText: Locator; - readonly page: Page; - - constructor(page: Page, rootElementSelector = '.ancv-field') { - this.page = page; + constructor( + public readonly page: Page, + rootElementSelector = '.adyen-checkout__ancv' + ) { + super(page); this.rootElement = page.locator(rootElementSelector); this.rootElementSelector = rootElementSelector; @@ -37,6 +39,10 @@ class ANCV { async clickOnSubmit() { await this.submitButton.click({ delay: SELECTOR_DELAY }); } + + get paymentResult() { + return this.page.locator('.adyen-checkout__await__subtitle--result'); + } } export { ANCV }; diff --git a/packages/e2e-playwright/models/base.ts b/packages/e2e-playwright/models/base.ts new file mode 100644 index 000000000..8e9fc6bc9 --- /dev/null +++ b/packages/e2e-playwright/models/base.ts @@ -0,0 +1,28 @@ +import type { Page, Locator } from '@playwright/test'; + +export abstract class Base { + readonly payButton: Locator; + + protected constructor(public readonly page: Page) {} + + get paymentResult() { + return this.page.getByTestId('result-message'); + } + + async goto(url: string) { + await this.page.goto(url); + await this.isComponentVisible(); + } + + async pay(options: { name?: RegExp | string } = { name: /Pay/i }): Promise { + if (this.payButton) { + await this.payButton.click(); + } else { + await this.page.getByRole('button', options).click(); + } + } + + async isComponentVisible() { + await Promise.resolve(); + } +} diff --git a/packages/e2e-playwright/models/bcmc.ts b/packages/e2e-playwright/models/bcmc.ts new file mode 100644 index 000000000..66839dcc9 --- /dev/null +++ b/packages/e2e-playwright/models/bcmc.ts @@ -0,0 +1,10 @@ +import { Card } from './card'; + +class BCMC extends Card { + async isComponentVisible() { + await this.cardNumberInput.waitFor({ state: 'visible' }); + await this.expiryDateInput.waitFor({ state: 'visible' }); + } +} + +export { BCMC }; diff --git a/packages/e2e-playwright/models/card-avs.ts b/packages/e2e-playwright/models/card-avs.ts index 17b89d7b2..ce6e16798 100644 --- a/packages/e2e-playwright/models/card-avs.ts +++ b/packages/e2e-playwright/models/card-avs.ts @@ -1,13 +1,17 @@ import { Card } from './card'; -import { Locator, Page } from '@playwright/test'; +import { Page } from '@playwright/test'; import { Address } from './address'; class CardWithAvs extends Card { readonly billingAddress: Address; - constructor(page: Page, rootElementSelector: string = '.adyen-checkout__card-input') { - super(page, rootElementSelector); - this.billingAddress = new Address(page, `${rootElementSelector} .adyen-checkout__fieldset--billingAddress`); + constructor(page: Page) { + super(page); + this.billingAddress = new Address(page); + } + + async fillInPostCode(postCode: string) { + await this.billingAddress.fillInPostCode(postCode); } } diff --git a/packages/e2e-playwright/models/card.ts b/packages/e2e-playwright/models/card.ts index 7d9376020..af74a73eb 100644 --- a/packages/e2e-playwright/models/card.ts +++ b/packages/e2e-playwright/models/card.ts @@ -1,6 +1,8 @@ -import { Locator, Page } from '@playwright/test'; +import type { Page, Locator } from '@playwright/test'; import { USER_TYPE_DELAY } from '../tests/utils/constants'; import LANG from '../../server/translations/en-US.json'; +import { Base } from './base'; +import { URL_MAP } from '../fixtures/URL_MAP'; const CARD_IFRAME_TITLE = LANG['creditCard.encryptedCardNumber.aria.iframeTitle']; const EXPIRY_DATE_IFRAME_TITLE = LANG['creditCard.encryptedExpiryDate.aria.iframeTitle']; @@ -13,9 +15,7 @@ const CVC_IFRAME_LABEL = LANG['creditCard.securityCode.label']; const INSTALLMENTS_PAYMENTS = LANG['installments.installments']; const REVOLVING_PAYMENT = LANG['installments.revolving']; -class Card { - readonly page: Page; - +class Card extends Base { readonly rootElement: Locator; readonly rootElementSelector: string; @@ -44,10 +44,12 @@ class Card { readonly installmentsDropdown: Locator; readonly selectorList: Locator; - constructor(page: Page, rootElementSelector = '.adyen-checkout__card-input') { - this.page = page; - - this.rootElement = page.locator(rootElementSelector); + constructor( + public readonly page: Page, + rootElementSelector = '.adyen-checkout__card-input' + ) { + super(page); + this.rootElement = this.page.locator(rootElementSelector); this.rootElementSelector = rootElementSelector; /** @@ -58,9 +60,6 @@ class Card { this.cardNumberErrorElement = this.cardNumberField.locator('.adyen-checkout-contextual-text--error'); this.brandingIcon = this.rootElement.locator('.adyen-checkout__card__cardNumber__brandIcon'); - - this.brandingIcon = this.rootElement.locator('.adyen-checkout__card__cardNumber__brandIcon'); - /** * Card Number elements, in iframe */ @@ -106,12 +105,27 @@ class Card { this.selectorList = this.rootElement.getByRole('listbox'); } + get availableBrands() { + return this.rootElement.locator('.adyen-checkout__card__brands').getByRole('img').all(); + } + + async goto(url: string = URL_MAP.card) { + await this.page.goto(url); + await this.isComponentVisible(); + } + async isComponentVisible() { await this.cardNumberInput.waitFor({ state: 'visible' }); await this.expiryDateInput.waitFor({ state: 'visible' }); await this.cvcInput.waitFor({ state: 'visible' }); } + async fillCardNumber(cardNumber: string) { + // reason: https://playwright.dev/docs/api/class-locator#locator-type + // use-case when we don't need to inspect keyboard events + await this.cardNumberInput.fill(cardNumber); + } + async typeCardNumber(cardNumber: string) { await this.cardNumberInput.type(cardNumber, { delay: USER_TYPE_DELAY }); } @@ -128,17 +142,28 @@ class Card { await this.cvcInput.clear(); } + async fillExpiryDate(expiryDate: string) { + // reason: https://playwright.dev/docs/api/class-locator#locator-type + // use-case when we don't need to inspect keyboard events + await this.expiryDateInput.fill(expiryDate); + } + async typeExpiryDate(expiryDate: string) { await this.expiryDateInput.type(expiryDate, { delay: USER_TYPE_DELAY }); } + async fillCvc(cvc: string) { + // reason: https://playwright.dev/docs/api/class-locator#locator-type + // use-case when we don't need to inspect keyboard events + await this.cvcInput.fill(cvc); + } + 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; + return this.selectorList.locator(`#listItem-${who}`); } } diff --git a/packages/e2e-playwright/models/customCard.ts b/packages/e2e-playwright/models/customCard.ts index fe812b893..55b777671 100644 --- a/packages/e2e-playwright/models/customCard.ts +++ b/packages/e2e-playwright/models/customCard.ts @@ -1,22 +1,17 @@ import { Locator, Page } from '@playwright/test'; import { USER_TYPE_DELAY } from '../tests/utils/constants'; import LANG from '../../server/translations/en-US.json'; +import { Base } from './base'; 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.cardNumber.label']; const EXPIRY_DATE_IFRAME_LABEL = LANG['creditCard.expiryDate.label']; -const EXPIRY_MONTH_IFRAME_LABEL = LANG['creditCard.expiryMonth.label'] ?? 'creditCard.expiryMonth.label'; // TODO add translation key -const EXPIRY_YEAR_IFRAME_LABEL = LANG['creditCard.expiryYear.label'] ?? 'creditCard.expiryYear.label'; // TODO add translation key const CVC_IFRAME_LABEL = LANG['creditCard.securityCode.label']; -class CustomCard { - readonly page: Page; - +class CustomCard extends Base { readonly rootElement: Locator; readonly rootElementSelector: string; @@ -32,28 +27,19 @@ class CustomCard { 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; + constructor( + public readonly page: Page, + rootElementSelector = '.secured-fields' + ) { + super(page); - this.rootElement = page.locator(rootElementSelector); + this.rootElement = this.page.locator(rootElementSelector); this.rootElementSelector = rootElementSelector; /** @@ -85,34 +71,6 @@ class CustomCard { 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 */ @@ -134,12 +92,6 @@ class CustomCard { 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 }); } @@ -152,29 +104,10 @@ class CustomCard { 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 }); } diff --git a/packages/e2e-playwright/models/customCardSeparateExpiryDate.ts b/packages/e2e-playwright/models/customCardSeparateExpiryDate.ts new file mode 100644 index 000000000..5f298e3d8 --- /dev/null +++ b/packages/e2e-playwright/models/customCardSeparateExpiryDate.ts @@ -0,0 +1,82 @@ +import { Locator, Page } from '@playwright/test'; +import { USER_TYPE_DELAY } from '../tests/utils/constants'; +import LANG from '../../server/translations/en-US.json'; +import { CustomCard } from './customCard'; + +const EXPIRY_MONTH_IFRAME_TITLE = LANG['creditCard.encryptedExpiryMonth.aria.iframeTitle']; +const EXPIRY_YEAR_IFRAME_TITLE = LANG['creditCard.encryptedExpiryYear.aria.iframeTitle']; + +const EXPIRY_MONTH_IFRAME_LABEL = LANG['creditCard.expiryMonth.label'] ?? 'creditCard.expiryMonth.label'; // TODO add translation key +const EXPIRY_YEAR_IFRAME_LABEL = LANG['creditCard.expiryYear.label'] ?? 'creditCard.expiryYear.label'; // TODO add translation key + +class CustomCardSeparateExpiryDate extends CustomCard { + 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; + + constructor( + public readonly page: Page, + rootElementSelector = '.secured-fields-1' + ) { + super(page, rootElementSelector); + + /** + * 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'); + } + + async isComponentVisible() { + await this.cardNumberInput.waitFor({ state: 'visible' }); + await this.expiryMonthInput.waitFor({ state: 'visible' }); + await this.cvcInput.waitFor({ state: 'visible' }); + } + + async deleteExpiryMonth() { + await this.expiryMonthInput.clear(); + } + + async deleteExpiryYear() { + await this.expiryYearInput.clear(); + } + + 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 }); + } +} + +export { CustomCardSeparateExpiryDate }; diff --git a/packages/e2e-playwright/models/dropin.ts b/packages/e2e-playwright/models/dropin.ts index 242448d1a..ecda1416f 100644 --- a/packages/e2e-playwright/models/dropin.ts +++ b/packages/e2e-playwright/models/dropin.ts @@ -1,36 +1,63 @@ import { Locator, Page } from '@playwright/test'; -import { DropinPage } from '../pages/dropin/dropin.page'; -import { DropinSessionsPage } from '../pages/dropin/dropin.sessions.page'; - -class Dropin { - readonly page: Page; - readonly dropinPage: DropinPage | DropinSessionsPage; - +import { Base } from './base'; +// Non session +class Dropin extends Base { readonly rootElement: Locator; readonly rootElementSelector: string; readonly pmList: Locator; - readonly creditCard: Locator; - readonly brandsHolder: Locator; + readonly saveDetailsButton: Locator; + readonly payButton: Locator; - constructor(page: Page, dropinPage: DropinPage | DropinSessionsPage, rootElementSelector = '.adyen-checkout__dropin') { - this.page = page; - this.dropinPage = dropinPage; - this.rootElement = page.locator(rootElementSelector); + protected _paymentMethods: Array<{ name: string; type: string }>; + + constructor( + public readonly page: Page, + rootElementSelector = '.adyen-checkout__dropin' + ) { + super(page); + this.rootElement = this.page.locator(rootElementSelector); this.rootElementSelector = rootElementSelector; this.pmList = this.rootElement.locator('.adyen-checkout__payment-methods-list').last(); + this.payButton = this.pmList.getByRole('button', { name: /Pay/i }); + this.saveDetailsButton = this.page.getByRole('button', { name: /Save details/i }); } async isComponentVisible() { await this.pmList.waitFor({ state: 'visible' }); } - getPaymentMethodItemByType(pmType: string) { - // @ts-ignore - const pmLabel = this.dropinPage.paymentMethods.find((pm: { type: string }) => pm.type === pmType).name; + async goto(url?: string) { + await this.page.goto(url); + // Wait for payment methods from the payments call + const responsePromise = this.page.waitForResponse(response => response.url().includes('paymentMethods') && response.status() === 200); + const response = await responsePromise; + this._paymentMethods = (await response.json()).paymentMethods.map(({ name, type }: { name: string; type: string }) => ({ name, type })); + await this.isComponentVisible(); + } + + getPaymentMethodLabelByType(pmType: string) { + const pmLabel = this.paymentMethods.find((pm: { type: string }) => pm.type === pmType).name; return this.pmList.locator(`.adyen-checkout__payment-method:has-text("${pmLabel}")`); } + + async selectPaymentMethod(pmType: string) { + const pmLabel = this.paymentMethods.find((pm: { type: string }) => pm.type === pmType).name; + this.page.getByRole('radio', { name: pmLabel }).check(); + } + + async saveDetails() { + await this.saveDetailsButton.click(); + } + + get paymentMethods() { + return this._paymentMethods; + } + + get paymentResult() { + return this.page.locator('.adyen-checkout__status'); + } } export { Dropin }; diff --git a/packages/e2e-playwright/models/dropinWithSession.ts b/packages/e2e-playwright/models/dropinWithSession.ts new file mode 100644 index 000000000..6befb5e0b --- /dev/null +++ b/packages/e2e-playwright/models/dropinWithSession.ts @@ -0,0 +1,19 @@ +import { Dropin } from './dropin'; + +class DropinWithSession extends Dropin { + async goto(url?: string) { + await this.page.goto(url); + // Wait for payment methods from the setup call + const regex = /\/checkoutshopper\/.*\/sessions/; + const responsePromise = this.page.waitForResponse(response => regex.test(response.url()) && response.status() === 200); + const response = await responsePromise; + this._paymentMethods = (await response.json()).paymentMethods.paymentMethods.map(({ name, type }: { name: string; type: string }) => ({ + name, + type + })); + + await this.isComponentVisible(); + } +} + +export { DropinWithSession }; diff --git a/packages/e2e-playwright/models/issuer-list.ts b/packages/e2e-playwright/models/issuer-list.ts index 8574bf3e7..e758f27c5 100644 --- a/packages/e2e-playwright/models/issuer-list.ts +++ b/packages/e2e-playwright/models/issuer-list.ts @@ -1,30 +1,36 @@ import { Locator, Page } from '@playwright/test'; import { USER_TYPE_DELAY } from '../tests/utils/constants'; +import { Base } from './base'; const SELECTOR_DELAY = 300; -class IssuerList { +class IssuerList extends Base { readonly rootElement: Locator; readonly rootElementSelector: string; readonly selectorList: Locator; readonly selectorCombobox: Locator; - readonly submitButton: Locator; + readonly payButton: Locator; readonly highlightedIssuerButtonGroup: Locator; - readonly page: Page; - - constructor(page: Page, rootElementSelector: string = '.adyen-checkout__issuer-list') { - this.page = page; - this.rootElement = page.locator(rootElementSelector); + constructor( + public readonly page: Page, + rootElementSelector: string = '.adyen-checkout__issuer-list' + ) { + super(page); + this.rootElement = this.page.locator(rootElementSelector); this.rootElementSelector = rootElementSelector; this.selectorList = this.rootElement.getByRole('listbox'); this.selectorCombobox = this.rootElement.getByRole('combobox'); - this.submitButton = this.rootElement.getByRole('button', { name: /Continue/i }); + this.payButton = this.rootElement.getByRole('button', { name: /Continue/i }); this.highlightedIssuerButtonGroup = this.rootElement.getByRole('group'); } + get issuers() { + return this.rootElement.getByRole('option').allTextContents(); + } + async clickOnSelector() { await this.selectorCombobox.click({ delay: SELECTOR_DELAY }); } diff --git a/packages/e2e-playwright/models/openInvoices.ts b/packages/e2e-playwright/models/openInvoices.ts index 8b0a59d01..7ef167a5c 100644 --- a/packages/e2e-playwright/models/openInvoices.ts +++ b/packages/e2e-playwright/models/openInvoices.ts @@ -1,21 +1,22 @@ import { Locator, Page } from '@playwright/test'; +import { Base } from './base'; -class OpenInvoices { - readonly page: Page; - +class OpenInvoices extends Base { readonly rootElement: Locator; readonly rootElementSelector: string; readonly riverty: Locator; readonly rivertyDeliveryAddressCheckbox: Locator; - constructor(page: Page, rootElementSelector = '#openInvoicesContainer') { - this.page = page; - - this.rootElement = page.locator(rootElementSelector); + constructor( + public readonly page: Page, + rootElementSelector = '#component-root' + ) { + super(page); + this.rootElement = this.page.locator(rootElementSelector); this.rootElementSelector = rootElementSelector; - this.riverty = this.rootElement.locator('#rivertyContainer'); + this.riverty = this.rootElement.locator('.adyen-checkout__open-invoice'); this.rivertyDeliveryAddressCheckbox = this.riverty .locator('.adyen-checkout__checkbox') @@ -23,7 +24,11 @@ class OpenInvoices { } async isComponentVisible() { - await this.rootElement.waitFor({ state: 'visible' }); + await this.riverty.waitFor({ state: 'visible' }); + } + + async pay(options: { name?: RegExp } = { name: /confirm purchase/i }): Promise { + await super.pay(options); } } diff --git a/packages/e2e-playwright/models/redirect.ts b/packages/e2e-playwright/models/redirect.ts index 9a7bb3cb2..16cb9183c 100644 --- a/packages/e2e-playwright/models/redirect.ts +++ b/packages/e2e-playwright/models/redirect.ts @@ -1,5 +1,6 @@ import { Locator, Page } from '@playwright/test'; import { capitalizeFirstLetter } from '../../lib/src/utils/textUtils'; +import { Base } from './base'; const SELECT_YOUR_BANK = 'Select your Bank'; const TEST_BANK_NAME = 'TESTNL2A'; @@ -9,7 +10,8 @@ export const SIMULATION_TYPE_FAILURE = 'Failure'; export const SIMULATION_TYPE_EXPIRATION = 'Expiration'; export const SIMULATION_TYPE_CANCELLATION = 'Cancellation'; -class Redirect { +// todo: maybe consider changing the name to ideal, as it's iDeal specific +class Redirect extends Base { readonly rootElement: Locator; readonly rootElementSelector: string; @@ -20,21 +22,22 @@ class Redirect { readonly simulateExpirationButton: Locator; readonly simulateCancellationButton: Locator; - readonly page: Page; - - constructor(page: Page, rootElementSelector: string = '.redirect-field') { - this.page = page; - this.rootElement = page.locator(rootElementSelector); + constructor( + public readonly page: Page, + rootElementSelector: string = '.component-wrapper' + ) { + super(page); + this.rootElement = this.page.locator(rootElementSelector); this.rootElementSelector = rootElementSelector; - this.selectYourBankButton = page.getByRole('button', { name: SELECT_YOUR_BANK }); + this.selectYourBankButton = this.page.getByRole('button', { name: SELECT_YOUR_BANK }); - this.selectTestBankButton = page.getByText(TEST_BANK_NAME); + this.selectTestBankButton = this.page.getByText(TEST_BANK_NAME); - this.simulateSuccessButton = page.getByRole('button', { name: SIMULATION_TYPE_SUCCESS }); - this.simulateFailureButton = page.getByRole('button', { name: SIMULATION_TYPE_FAILURE }); - this.simulateExpirationButton = page.getByRole('button', { name: SIMULATION_TYPE_EXPIRATION }); - this.simulateCancellationButton = page.getByRole('button', { name: SIMULATION_TYPE_CANCELLATION, exact: true }); + this.simulateSuccessButton = this.page.getByRole('button', { name: SIMULATION_TYPE_SUCCESS }); + this.simulateFailureButton = this.page.getByRole('button', { name: SIMULATION_TYPE_FAILURE }); + this.simulateExpirationButton = this.page.getByRole('button', { name: SIMULATION_TYPE_EXPIRATION }); + this.simulateCancellationButton = this.page.getByRole('button', { name: SIMULATION_TYPE_CANCELLATION, exact: true }); } async isComponentVisible() { @@ -66,6 +69,10 @@ class Redirect { simType = capitalizeFirstLetter(simType); await this[`simulate${simType}Button`].click(); } + + async redirect(options: { name?: RegExp | string } = { name: /Continue to iDEAL/i }) { + await super.pay(options); + } } export { Redirect }; diff --git a/packages/e2e-playwright/models/srPanel.ts b/packages/e2e-playwright/models/srPanel.ts new file mode 100644 index 000000000..6ce146669 --- /dev/null +++ b/packages/e2e-playwright/models/srPanel.ts @@ -0,0 +1,15 @@ +import { Locator, Page } from '@playwright/test'; + +class SRPanel { + readonly rootElement: Locator; + + constructor(page: Page, rootElementSelector: string = '.adyen-checkout-sr-panel') { + this.rootElement = page.locator(rootElementSelector); + } + + get allMessages() { + return this.rootElement.locator('.adyen-checkout-sr-panel__msg').all(); + } +} + +export { SRPanel }; diff --git a/packages/e2e-playwright/models/utils.ts b/packages/e2e-playwright/models/utils.ts deleted file mode 100644 index 6e216be3f..000000000 --- a/packages/e2e-playwright/models/utils.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Dropin } from './dropin'; -import { getImageCount } from '../tests/utils/image'; -import { USER_TYPE_DELAY } from '../tests/utils/constants'; - -export const getCreditCardPM_withBrandsInfo = (dropin: Dropin) => { - const creditCard = dropin.getPaymentMethodItemByType('scheme'); - - 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 - }; -}; - -export const typeIntoSecuredField = async (pm, iframeTitle, iframeInputLabel, text) => { - const sfIframe = pm.frameLocator(`[title="${iframeTitle}"]`); - const sfInput = sfIframe.locator(`input[aria-label="${iframeInputLabel}"]`); - - await sfInput.type(text, { delay: USER_TYPE_DELAY }); -}; diff --git a/packages/e2e-playwright/package.json b/packages/e2e-playwright/package.json index ab4ebb542..1f248b911 100644 --- a/packages/e2e-playwright/package.json +++ b/packages/e2e-playwright/package.json @@ -5,27 +5,16 @@ "repository": "github:Adyen/adyen-web", "license": "MIT", "scripts": { - "test:start-playground": "cross-env NODE_ENV=test webpack-dev-server --config app/config/webpack.config.js", "test:headless": "npx playwright test", "test:headed": "npx playwright test --headed", "test:ui-mode": "npx playwright test --ui" }, "devDependencies": { "@adyen/adyen-web-server": "1.0.0", - "@playwright/test": "1.48.0", + "@playwright/test": "1.48.2", "cross-env": "^7.0.3", - "css-loader": "^6.0.0", "dotenv": "16.4.4", - "html-webpack-plugin": "5.5.1", - "sass-loader": "^10.2.0", - "style-loader": "^2.0.0", "ts-loader": "9.4.4", - "typescript": "5.2.2", - "webpack": "5.94.0", - "webpack-cli": "5.1.4", - "webpack-dev-server": "4.15.1" - }, - "dependencies": { - "@adyen/adyen-web": "6.5.0" + "typescript": "5.2.2" } } diff --git a/packages/e2e-playwright/pages/ancv/ancv.fixture.ts b/packages/e2e-playwright/pages/ancv/ancv.fixture.ts deleted file mode 100644 index 771f06041..000000000 --- a/packages/e2e-playwright/pages/ancv/ancv.fixture.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { test as base, expect } from '@playwright/test'; -import { AncvPage } from './ancv.page'; -import { sessionsMock } from '../../mocks/sessions/sessions.mock'; -import { sessionsMockData } from '../../mocks/sessions/sessions.data'; -import { setupMock } from '../../mocks/setup/setup.mock'; -import { setupMockData } from '../../mocks/setup/setup.data'; -import { Card } from '../../models/card'; - -type Fixture = { - ancvPage: AncvPage; - card: Card; -}; - -const test = base.extend({ - ancvPage: async ({ page }, use) => { - const ancvPage = new AncvPage(page); - - await sessionsMock(page, sessionsMockData); - await setupMock(page, setupMockData); - await ancvPage.goto(); - await use(ancvPage); - } -}); - -export { test, expect }; diff --git a/packages/e2e-playwright/pages/ancv/ancv.page.ts b/packages/e2e-playwright/pages/ancv/ancv.page.ts deleted file mode 100644 index 11869cb9f..000000000 --- a/packages/e2e-playwright/pages/ancv/ancv.page.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Page } from '@playwright/test'; -import { ANCV } from '../../models/ancv'; - -class AncvPage { - public readonly page: Page; - - public readonly ancv: ANCV; - - constructor(page: Page) { - this.page = page; - this.ancv = new ANCV(page); - } - - async goto(url?: string) { - const gotoUrl = url ? url : 'http://localhost:3024/ancv?countryCode=NL'; - await this.page.goto(gotoUrl); - } -} - -export { AncvPage }; diff --git a/packages/e2e-playwright/pages/cards/card.avs.page.ts b/packages/e2e-playwright/pages/cards/card.avs.page.ts deleted file mode 100644 index 86c07e81e..000000000 --- a/packages/e2e-playwright/pages/cards/card.avs.page.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Locator, Page } from '@playwright/test'; -import { CardWithAvs } from '../../models/card-avs'; - -class CardAvsPage { - readonly page: Page; - - readonly cardWithAvs: CardWithAvs; - readonly payButton: Locator; - - constructor(page: Page) { - this.page = page; - this.cardWithAvs = new CardWithAvs(page); - this.payButton = page.getByRole('button', { name: /Pay/i }); - } - - async goto(url?: string) { - await this.page.goto('http://localhost:3024/cards'); - } -} - -export { CardAvsPage }; diff --git a/packages/e2e-playwright/pages/cards/card.fixture.ts b/packages/e2e-playwright/pages/cards/card.fixture.ts deleted file mode 100644 index 2992e0659..000000000 --- a/packages/e2e-playwright/pages/cards/card.fixture.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { test as base, expect, Page } from '@playwright/test'; -import { CardPage } from './card.page'; -import { CardAvsPage } from './card.avs.page'; - -type Fixture = { - cardPage: CardPage; - cardAvsPage: CardAvsPage; - cardNoContextualElementPage: CardPage; - cardLegacyInputModePage: CardPage; - cardBrandingPage: CardPage; - cardExpiryDatePoliciesPage: CardPage; - cardInstallmentsPage: CardPage; - cardInstallmentsFullWidthPage: CardPage; -}; - -const test = base.extend({ - cardPage: async ({ page }, use) => { - await useCardPage(page, use); - }, - - cardAvsPage: async ({ page }, use) => { - // TODO: to be replaced with a proper page loading Card with AVS inside Storybook - await page.addInitScript({ - content: - "window.cardConfig = { billingAddressRequired: true, billingAddressRequiredFields: ['street', 'houseNumberOrName', 'postalCode', 'city']};" - }); - - // @ts-ignore - await useCardPage(page, use, CardAvsPage); - }, - - cardNoContextualElementPage: async ({ page }, use) => { - await page.addInitScript({ - content: 'window.cardConfig = { showContextualElement: false }' - }); - - await useCardPage(page, use); - }, - - cardLegacyInputModePage: async ({ page }, use) => { - await page.addInitScript({ - content: 'window.cardConfig = { legacyInputMode: true}' - }); - - 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); - }, - - cardInstallmentsFullWidthPage: async ({ page }, use) => { - const installmentsConfig = JSON.stringify({ - installmentOptions: { - mc: { - values: [1, 2, 3] - } - } - }); - 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 deleted file mode 100644 index 7ce995531..000000000 --- a/packages/e2e-playwright/pages/cards/card.page.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Locator, Page } from '@playwright/test'; -import { Card } from '../../models/card'; - -class CardPage { - readonly page: Page; - - readonly card: Card; - readonly payButton: Locator; - - constructor(page: Page) { - this.page = page; - this.card = new Card(page); - this.payButton = page.getByRole('button', { name: /Pay/i }); - } - - async goto(url?: string) { - await this.page.goto('http://localhost:3024/cards'); - } - - async pay() { - await this.payButton.click(); - } -} - -export { CardPage }; diff --git a/packages/e2e-playwright/pages/customCard/customCard.fixture.ts b/packages/e2e-playwright/pages/customCard/customCard.fixture.ts deleted file mode 100644 index 8538634e9..000000000 --- a/packages/e2e-playwright/pages/customCard/customCard.fixture.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { test as base, expect } from '@playwright/test'; -import { CustomCardPage } from './customCard.page'; - -type Fixture = { - customCardPage: CustomCardPage; - customCardPageSeparate: CustomCardPage; -}; - -const test = base.extend({ - customCardPage: async ({ page }, use) => { - 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); - } -}); - -export { test, expect }; diff --git a/packages/e2e-playwright/pages/customCard/customCard.page.ts b/packages/e2e-playwright/pages/customCard/customCard.page.ts deleted file mode 100644 index da586105a..000000000 --- a/packages/e2e-playwright/pages/customCard/customCard.page.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Locator, Page } from '@playwright/test'; -import { CustomCard } from '../../models/customCard'; - -class CustomCardPage { - readonly page: Page; - - readonly card: CustomCard; - readonly payButtonRegular: Locator; - readonly payButtonSeparate: Locator; - - constructor(page: Page, selector?: string) { - this.page = page; - 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(which: string = 'Regular') { - await this[`payButton${which}`].scrollIntoViewIfNeeded(); - await this[`payButton${which}`].click(); - } -} - -export { CustomCardPage }; diff --git a/packages/e2e-playwright/pages/dropin/dropin.fixture.ts b/packages/e2e-playwright/pages/dropin/dropin.fixture.ts deleted file mode 100644 index a5b968d3e..000000000 --- a/packages/e2e-playwright/pages/dropin/dropin.fixture.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { test as base, expect, Page } from '@playwright/test'; -import { DropinPage } from './dropin.page'; -import { DropinSessionsPage } from './dropin.sessions.page'; - -type Fixture = { - dropinPage: DropinPage; - dropinPage_cardBrands: DropinPage; - dropinPage_cardBrands_withExcluded: DropinPage; - dropinSessions_regular: DropinSessionsPage; - dropinSessions_zeroAuthCard_success: DropinSessionsPage; - dropinSessions_zeroAuthCard_fail: DropinSessionsPage; -}; - -const test = base.extend({ - dropinPage: async ({ page }, use) => { - await useDropinPage(page, use); - }, - - dropinPage_cardBrands: 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.dropinConfig = ${pmsConfig}` - }); - - await useDropinPage(page, use); - }, - - dropinPage_cardBrands_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.dropinConfig = ${pmsConfig}` - }); - - await useDropinPage(page, use); - }, - - dropinSessions_regular: async ({ page }, use) => { - const mainConfig = JSON.stringify({ - allowPaymentMethods: ['scheme'] - }); - - const pmsConfig = JSON.stringify({ - paymentMethodsConfiguration: { - card: { - _disableClickToPay: true - } - } - }); - - await page.addInitScript({ - content: `window.mainConfiguration = ${mainConfig}` - }); - - await page.addInitScript({ - content: `window.dropinConfig = ${pmsConfig}` - }); - - await useDropinPage(page, use, DropinSessionsPage); - }, - - dropinSessions_zeroAuthCard_success: async ({ page }, use) => { - const sessionConfig = JSON.stringify({ - amount: { currency: 'USD', value: 0 }, - recurringProcessingModel: 'CardOnFile', - storePaymentMethodMode: 'askForConsent' - }); - - const mainConfig = JSON.stringify({ - allowPaymentMethods: ['scheme'] - }); - - const pmsConfig = JSON.stringify({ - paymentMethodsConfiguration: { - card: { - _disableClickToPay: true - } - } - }); - - await page.addInitScript({ - content: `window.sessionConfig = ${sessionConfig}` - }); - - await page.addInitScript({ - content: `window.mainConfiguration = ${mainConfig}` - }); - - await page.addInitScript({ - content: `window.dropinConfig = ${pmsConfig}` - }); - - await useDropinPage(page, use, DropinSessionsPage); - }, - - dropinSessions_zeroAuthCard_fail: async ({ page }, use) => { - const sessionConfig = JSON.stringify({ - amount: { currency: 'USD', value: 0 }, - recurringProcessingModel: 'CardOnFile', - storePaymentMethodMode: 'askForConsent', - enableOneClick: true // this will conflict with storePaymentMethod in the /payments request and cause the payment to fail - }); - - const mainConfig = JSON.stringify({ - allowPaymentMethods: ['scheme'] - }); - - const pmsConfig = JSON.stringify({ - paymentMethodsConfiguration: { - card: { - _disableClickToPay: true - } - } - }); - - await page.addInitScript({ - content: `window.sessionConfig = ${sessionConfig}` - }); - - await page.addInitScript({ - content: `window.mainConfiguration = ${mainConfig}` - }); - - await page.addInitScript({ - content: `window.dropinConfig = ${pmsConfig}` - }); - await useDropinPage(page, use, DropinSessionsPage); - } -}); - -const useDropinPage = async (page: Page, use: any, PageType: any = 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 deleted file mode 100644 index e26dc074f..000000000 --- a/packages/e2e-playwright/pages/dropin/dropin.page.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Page } from '@playwright/test'; -import { Dropin } from '../../models/dropin'; - -class DropinPage { - readonly page: Page; - - readonly dropin: Dropin; - private _paymentMethods: Array<{ name: string; type: string }>; - - constructor(page: Page) { - this.page = page; - this.dropin = new Dropin(page, this); - } - - async goto(url?: string) { - await this.page.goto('http://localhost:3024'); - const response = await this.page.waitForResponse(response => response.url().includes('paymentMethods') && response.status() === 200); - this._paymentMethods = (await response.json()).paymentMethods.map(({ name, type }: { name: string; type: string }) => ({ name, type })); - } - - get paymentMethods() { - return this._paymentMethods; - } -} - -export { DropinPage }; diff --git a/packages/e2e-playwright/pages/dropin/dropin.sessions.page.ts b/packages/e2e-playwright/pages/dropin/dropin.sessions.page.ts deleted file mode 100644 index 4df15f684..000000000 --- a/packages/e2e-playwright/pages/dropin/dropin.sessions.page.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Locator, Page } from '@playwright/test'; -import { Dropin } from '../../models/dropin'; - -class DropinSessionsPage { - readonly page: Page; - - readonly dropin: Dropin; - readonly payButton: Locator; - readonly saveDetailsButton: Locator; - private _paymentMethods: Array<{ name: string; type: string }>; - - constructor(page: Page) { - this.page = page; - this.dropin = new Dropin(page, this); - this.payButton = page.getByRole('button', { name: /Pay/i }); - this.saveDetailsButton = page.getByRole('button', { name: /Save details/i }); - } - - async goto(url?: string) { - await this.page.goto('http://localhost:3024/dropinsessions'); - const response = await this.page.waitForResponse(response => response.url().includes('setup') && response.status() === 200); - this._paymentMethods = (await response.json()).paymentMethods.paymentMethods.map(({ name, type }: { name: string; type: string }) => ({ - name, - type - })); - } - - async pay() { - await this.payButton.click(); - } - - async saveDetails() { - await this.saveDetailsButton.click(); - } - - get paymentMethods() { - return this._paymentMethods; - } -} - -export { DropinSessionsPage }; diff --git a/packages/e2e-playwright/pages/issuerList/issuer-list.fixture.ts b/packages/e2e-playwright/pages/issuerList/issuer-list.fixture.ts deleted file mode 100644 index 5af53188c..000000000 --- a/packages/e2e-playwright/pages/issuerList/issuer-list.fixture.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { test as base, expect } from '@playwright/test'; -import { IssuerListPage } from './issuer-list.page'; - -type Fixture = { - issuerListPage: IssuerListPage; -}; - -const test = base.extend({ - issuerListPage: async ({ page }, use) => { - const issuerListPage = new IssuerListPage(page); - await issuerListPage.goto(); - await use(issuerListPage); - } -}); - -export { test, expect }; diff --git a/packages/e2e-playwright/pages/issuerList/issuer-list.page.ts b/packages/e2e-playwright/pages/issuerList/issuer-list.page.ts deleted file mode 100644 index dcf747c9c..000000000 --- a/packages/e2e-playwright/pages/issuerList/issuer-list.page.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Page } from '@playwright/test'; -import { IssuerList } from '../../models/issuer-list'; - -class IssuerListPage { - readonly page: Page; - - public readonly issuerList: IssuerList; - - constructor(page: Page) { - this.page = page; - this.issuerList = new IssuerList(page); - } - - async goto(url?: string) { - await this.page.goto('http://localhost:3024/issuerlists?countryCode=PL'); - } -} - -export { IssuerListPage }; diff --git a/packages/e2e-playwright/pages/openInvoices/openInvoices.fixture.ts b/packages/e2e-playwright/pages/openInvoices/openInvoices.fixture.ts deleted file mode 100644 index c2a2075af..000000000 --- a/packages/e2e-playwright/pages/openInvoices/openInvoices.fixture.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { test as base, expect, Page } from '@playwright/test'; -import { OpenInvoicesPage } from './openInvoices.page'; -type Fixture = { - openInvoicesPage: OpenInvoicesPage; - openInvoicesPage_riverty: OpenInvoicesPage; -}; - -const test = base.extend({ - openInvoicesPage: async ({ page }, use) => { - await useOpenInvoicesPage(page, use); - }, - - openInvoicesPage_riverty: async ({ page }, use) => { - const mainConfig = JSON.stringify({ - srConfig: { - showPanel: true - } - }); - await page.addInitScript({ - content: `window.mainConfiguration = ${mainConfig}` - }); - - const rivertyConfig = JSON.stringify({ - countryCode: 'DE' - }); - await page.addInitScript({ - content: `window.rivertyConfig = ${rivertyConfig}` - }); - - await useOpenInvoicesPage(page, use); - } -}); - -const useOpenInvoicesPage = async (page: Page, use: any, PageType = OpenInvoicesPage) => { - const openInvoicesPage = new PageType(page); - await openInvoicesPage.goto(); - await use(openInvoicesPage); -}; - -export { test, expect }; diff --git a/packages/e2e-playwright/pages/openInvoices/openInvoices.page.ts b/packages/e2e-playwright/pages/openInvoices/openInvoices.page.ts deleted file mode 100644 index 666f6b2d1..000000000 --- a/packages/e2e-playwright/pages/openInvoices/openInvoices.page.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Locator, Page } from '@playwright/test'; -import { OpenInvoices } from '../../models/openInvoices'; - -class OpenInvoicesPage { - readonly page: Page; - - readonly openInvoices: OpenInvoices; - readonly payButton: Locator; - - constructor(page: Page) { - this.page = page; - this.openInvoices = new OpenInvoices(page); - this.payButton = page.getByRole('button', { name: /Confirm/i }); - } - - async goto(url?: string) { - await this.page.goto('http://localhost:3024/openinvoices'); - } - - async pay() { - await this.payButton.click(); - } -} - -export { OpenInvoicesPage }; diff --git a/packages/e2e-playwright/pages/redirects/redirect.fixture.ts b/packages/e2e-playwright/pages/redirects/redirect.fixture.ts deleted file mode 100644 index bf87438c5..000000000 --- a/packages/e2e-playwright/pages/redirects/redirect.fixture.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { test as base, expect, Page } from '@playwright/test'; -import { RedirectPage } from './redirect.page'; - -type Fixture = { - redirectPage: RedirectPage; - redirectPageIdeal: RedirectPage; -}; - -const test = base.extend({ - redirectPage: async ({ page }, use) => { - await useRedirectPage(page, use); - }, - - //{ type: 'ideal' } - redirectPageIdeal: async ({ page }, use) => { - const redirectConfig = JSON.stringify({ type: 'ideal' }); - await page.addInitScript({ - content: `window.redirectConfig = ${redirectConfig}` - }); - - await useRedirectPage(page, use); - } -}); - -const useRedirectPage = async (page: Page, use: any, PageType: any = RedirectPage) => { - const redirectPage = new PageType(page); - await redirectPage.goto(); - await use(redirectPage); -}; - -export { test, expect }; diff --git a/packages/e2e-playwright/pages/redirects/redirect.page.ts b/packages/e2e-playwright/pages/redirects/redirect.page.ts deleted file mode 100644 index e786db4cb..000000000 --- a/packages/e2e-playwright/pages/redirects/redirect.page.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Locator, Page } from '@playwright/test'; -import { Redirect } from '../../models/redirect'; - -class RedirectPage { - readonly page: Page; - - public readonly redirectModel: Redirect; - - readonly redirectButton: Locator; - - constructor(page: Page) { - this.page = page; - this.redirectModel = new Redirect(page); - this.redirectButton = page.getByRole('button', { name: /Continue to iDEAL/i }); - } - - async goto(url?: string) { - await this.page.goto('http://localhost:3024/redirects?countryCode=NL'); - } - - async redirect() { - await this.redirectButton.click(); - } -} - -export { RedirectPage }; diff --git a/packages/e2e-playwright/playwright.config.ts b/packages/e2e-playwright/playwright.config.ts index cbf9755d2..7e4d8679b 100644 --- a/packages/e2e-playwright/playwright.config.ts +++ b/packages/e2e-playwright/playwright.config.ts @@ -2,8 +2,10 @@ import type { PlaywrightTestConfig } from '@playwright/test'; import { devices } from '@playwright/test'; import * as dotenv from 'dotenv'; import * as path from 'path'; +import { protocol } from './environment-variables'; dotenv.config({ path: path.resolve('../../', '.env') }); +const playgroundBaseUrl = `${protocol}://localhost:3020`; /** * See https://playwright.dev/docs/test-configuration. @@ -11,7 +13,7 @@ dotenv.config({ path: path.resolve('../../', '.env') }); const config: PlaywrightTestConfig = { testDir: './tests/', /* Maximum time one test can run for. */ - timeout: 10 * 2000, + timeout: 60 * 1000, expect: { /** * Maximum time expect() should wait for the condition to be met. @@ -24,9 +26,9 @@ const config: PlaywrightTestConfig = { /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, /* Retry on CI only */ - retries: process.env.CI ? 1 : 0, - /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : 1, + retries: process.env.CI ? 2 : 1, + /* Opt out of parallel tests on CI. Use default locally */ + workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: [['html', { open: 'never' }], ['list']], @@ -34,12 +36,14 @@ const config: PlaywrightTestConfig = { /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ - actionTimeout: 15000, + actionTimeout: 30000, /* Base URL to use in actions like `await page.goto('/')`. */ - // baseURL: 'http://localhost:3000', + baseURL: playgroundBaseUrl, /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on-first-retry' + trace: 'on-first-retry', + ignoreHTTPSErrors: true, + screenshot: 'only-on-failure' }, /* Configure projects for major browsers */ @@ -70,11 +74,15 @@ const config: PlaywrightTestConfig = { // outputDir: 'test-results/', /* Run your local dev server before starting the tests */ - webServer: { - command: 'npm run test:start-playground', - port: 3024, - reuseExistingServer: !process.env.CI - } + webServer: [ + { + command: 'npm run build:storybook && npm run start:prod-storybook', + cwd: '../..', + port: 3020, + reuseExistingServer: !process.env.CI, + timeout: 120 * 1000 + } + ] }; export default config; diff --git a/packages/e2e-playwright/tests/a11y/bcmc/bancontact.visa.a11y.spec.ts b/packages/e2e-playwright/tests/a11y/bcmc/bancontact.visa.a11y.spec.ts new file mode 100644 index 000000000..b089b5c6a --- /dev/null +++ b/packages/e2e-playwright/tests/a11y/bcmc/bancontact.visa.a11y.spec.ts @@ -0,0 +1,70 @@ +import { test, expect } from '../../../fixtures/card.fixture'; +import { BCMC_DUAL_BRANDED_VISA, DUAL_BRANDED_CARD, TEST_CVC_VALUE, TEST_DATE_VALUE, VISA_CARD } from '../../utils/constants'; + +test('BCMC logo should have correct alt text', async ({ bcmc }) => { + await bcmc.typeCardNumber('41'); + expect(bcmc.rootElement.getByAltText(/bancontact card/i)).toBeTruthy(); +}); + +test('Visa logo should have correct alt text', async ({ bcmc }) => { + await bcmc.typeCardNumber(VISA_CARD); + expect(bcmc.rootElement.getByAltText(/visa/i)).toBeTruthy(); +}); + +test( + '#4 Enter card number (co-branded bcmc/visa) ' + + 'then complete expiryDate and expect comp to be valid' + + 'then click Visa logo and expect comp to not be valid' + + 'then click BCMC logo and expect comp to be valid again', + async ({ page, bcmc }) => { + await bcmc.typeCardNumber(BCMC_DUAL_BRANDED_VISA); + await bcmc.typeExpiryDate(TEST_DATE_VALUE); + expect(bcmc.cvcField).toBeHidden(); + await page.waitForFunction(() => globalThis.component.isValid === true); + + await bcmc.rootElement.getByAltText(/visa/i).first().click(); + await bcmc.cvcInput.waitFor({ state: 'visible' }); + expect(bcmc.cvcInput).toHaveAttribute('aria-required', 'true'); + await page.waitForFunction(() => globalThis.component.isValid === false); + + await bcmc.rootElement + .getByAltText(/bancontact card/i) + .first() + .click(); + await bcmc.cvcField.waitFor({ state: 'hidden' }); + await page.waitForFunction(() => globalThis.component.isValid === true); + } +); + +test( + '#5 Enter card number, that we mock to co-branded bcmc/visa ' + + 'then complete expiryDate and expect comp to be valid' + + 'then click Visa logo and expect comp to not be valid' + + 'then enter CVC and expect comp to be valid', + async ({ bcmc, page }) => { + await bcmc.typeCardNumber(BCMC_DUAL_BRANDED_VISA); + await bcmc.typeExpiryDate(TEST_DATE_VALUE); + await page.waitForFunction(() => globalThis.component.isValid === true); + + await bcmc.rootElement.getByAltText(/visa/i).first().click(); + await page.waitForFunction(() => globalThis.component.isValid === false); + + await bcmc.typeCvc(TEST_CVC_VALUE); + await page.waitForFunction(() => globalThis.component.isValid === true); + } +); + +test( + '#6 Enter Visa card number ' + + 'then delete it' + + 'then re-add it' + + 'and expect Visa logo to be shown a second time (showing CSF has reset state)', + async ({ bcmc }) => { + await bcmc.typeCardNumber(DUAL_BRANDED_CARD); + expect(bcmc.rootElement.getByAltText(/visa/i)).toBeTruthy(); + await bcmc.deleteCardNumber(); + expect(bcmc.rootElement.getByAltText(/bancontact card/i)).toBeTruthy(); + await bcmc.typeCardNumber(DUAL_BRANDED_CARD); + expect(bcmc.rootElement.getByAltText(/visa/i)).toBeTruthy(); + } +); diff --git a/packages/e2e-playwright/tests/a11y/card/avs.a11y.spec.ts b/packages/e2e-playwright/tests/a11y/card/avs.a11y.spec.ts new file mode 100644 index 000000000..4a58ded2f --- /dev/null +++ b/packages/e2e-playwright/tests/a11y/card/avs.a11y.spec.ts @@ -0,0 +1,32 @@ +import { test as base, expect } from '@playwright/test'; +import { binLookupMock } from '../../../mocks/binLookup/binLookup.mock'; +import { optionalDateAndCvcMock } from '../../../mocks/binLookup/binLookup.data'; +import { REGULAR_TEST_CARD } from '../../utils/constants'; +import { CardWithAvs } from '../../../models/card-avs'; +import { getStoryUrl } from '../../utils/getStoryUrl'; +import { URL_MAP } from '../../../fixtures/URL_MAP'; + +type Fixture = { + cardAvsPage: CardWithAvs; +}; + +const test = base.extend({ + cardAvsPage: async ({ page }, use) => { + 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 use(cardPage); + } +}); + +test.describe('Card - AVS', () => { + test('should move the focus to the address field since expiryDate & cvc are optional', async ({ page, cardAvsPage }) => { + const firstDigits = REGULAR_TEST_CARD.substring(0, 15); + const lastDigits = REGULAR_TEST_CARD.substring(15, 16); + await cardAvsPage.typeCardNumber(firstDigits); + await cardAvsPage.typeCardNumber(lastDigits); + + await expect(cardAvsPage.billingAddress.streetInput).toBeFocused(); + }); +}); diff --git a/packages/e2e-playwright/tests/card/card.contextualTexts.spec.ts b/packages/e2e-playwright/tests/a11y/card/card.contextualTexts.spec.ts similarity index 85% rename from packages/e2e-playwright/tests/card/card.contextualTexts.spec.ts rename to packages/e2e-playwright/tests/a11y/card/card.contextualTexts.spec.ts index 3a853b7ca..22d8b6458 100644 --- a/packages/e2e-playwright/tests/card/card.contextualTexts.spec.ts +++ b/packages/e2e-playwright/tests/a11y/card/card.contextualTexts.spec.ts @@ -1,6 +1,8 @@ -import { test, expect } from '../../pages/cards/card.fixture'; -import { AMEX_CARD } from '../utils/constants'; -import LANG from '../../../server/translations/en-US.json'; +import { test, expect } from '../../../fixtures/card.fixture'; +import LANG from '../../../../server/translations/en-US.json'; +import { AMEX_CARD } from '../../utils/constants'; +import { getStoryUrl } from '../../utils/getStoryUrl'; +import { URL_MAP } from '../../../fixtures/URL_MAP'; const EXPIRY_DATE_CONTEXTUAL_TEXT = LANG['creditCard.expiryDate.contextualText']; const CVC_CONTEXTUAL_TEXT_3_DIGITS = LANG['creditCard.securityCode.contextualText.3digits']; @@ -8,10 +10,8 @@ const CVC_CONTEXTUAL_TEXT_4_DIGITS = LANG['creditCard.securityCode.contextualTex const CVC_ERROR = LANG['cc.cvc.920']; test.describe('Card - Contextual text', () => { - test('#1 Should inspect the card inputs and see they have contextual elements set', async ({ cardPage }) => { - const { card, page } = cardPage; - - await card.isComponentVisible(); + test('#1 Should inspect the card inputs and see they have contextual elements set', async ({ card }) => { + await card.goto(URL_MAP.card); // checkout expiryDate element await expect(card.expiryDateContextualElement).toHaveText(EXPIRY_DATE_CONTEXTUAL_TEXT); @@ -42,11 +42,8 @@ test.describe('Card - Contextual text', () => { await expect(card.cvcIframeContextualElement).toHaveText(CVC_CONTEXTUAL_TEXT_3_DIGITS); }); - test('#2 Should inspect the cvc input for a contextual text set, then it should be replaced by an error, then reset', async ({ cardPage }) => { - const { card, page } = cardPage; - - await card.isComponentVisible(); - + test('#2 Should inspect the cvc input for a contextual text set, then it should be replaced by an error, then reset', async ({ page, card }) => { + await card.goto(URL_MAP.card); // checkout security code contextual element await expect(card.cvcContextualElement).toHaveText(CVC_CONTEXTUAL_TEXT_3_DIGITS); let cvcAriaHidden = await card.cvcContextualElement.getAttribute('aria-hidden'); @@ -59,7 +56,7 @@ test.describe('Card - Contextual text', () => { await expect(card.cvcIframeContextualElement).toHaveText(CVC_CONTEXTUAL_TEXT_3_DIGITS); // press pay to generate errors - await cardPage.pay(); + await card.pay(); // checkout security code error element await expect(card.cvcErrorElement).toBeVisible(); @@ -88,10 +85,15 @@ test.describe('Card - Contextual text', () => { await expect(card.cvcIframeContextualElement).toHaveText(CVC_CONTEXTUAL_TEXT_3_DIGITS); }); - test('#3 Should find no contextualElements because the config says to not show them', async ({ cardNoContextualElementPage }) => { - const { card, page } = cardNoContextualElementPage; - - await card.isComponentVisible(); + test('#3 Should find no contextualElements because the config says to not show them', async ({ card }) => { + await card.goto( + getStoryUrl({ + baseUrl: URL_MAP.card, + componentConfig: { + showContextualElement: false + } + }) + ); // checkout contextual elements not present await expect(card.expiryDateContextualElement).not.toBeVisible(); diff --git a/packages/e2e-playwright/tests/a11y/card/card.keyboard.spec.ts b/packages/e2e-playwright/tests/a11y/card/card.keyboard.spec.ts new file mode 100644 index 000000000..16caa8a0a --- /dev/null +++ b/packages/e2e-playwright/tests/a11y/card/card.keyboard.spec.ts @@ -0,0 +1,31 @@ +import { test, expect } from '../../../fixtures/card.fixture'; +import { pressEnter } from '../../utils/keyboard'; +import { PAYMENT_RESULT, REGULAR_TEST_CARD, TEST_CVC_VALUE, TEST_DATE_VALUE } from '../../utils/constants'; +import { URL_MAP } from '../../../fixtures/URL_MAP'; +import LANG from '../../../../server/translations/en-US.json'; + +const EXPIRY_DATE_ERROR_EMPTY = LANG['cc.dat.910']; +const CVC_ERROR_EMPTY = LANG['cc.cvc.920']; + +test('#1 Filling PAN then pressing Enter will trigger validation ', async ({ page, card }) => { + await card.goto(URL_MAP.card); + await card.typeCardNumber(REGULAR_TEST_CARD); + await pressEnter(page); + + await expect(card.expiryDateErrorElement).toBeVisible(); + await expect(card.expiryDateErrorElement).toHaveText(EXPIRY_DATE_ERROR_EMPTY); + + await expect(card.cvcErrorElement).toBeVisible(); + await expect(card.cvcErrorElement).toHaveText(CVC_ERROR_EMPTY); +}); + +test('#2 Filling in card fields then pressing Enter will trigger submission ', async ({ page, card }) => { + await card.goto(URL_MAP.card); + await card.typeCardNumber(REGULAR_TEST_CARD); + await card.typeCvc(TEST_CVC_VALUE); + await card.typeExpiryDate(TEST_DATE_VALUE); + + await pressEnter(page); + + await expect(card.paymentResult).toHaveText(PAYMENT_RESULT.authorised); +}); diff --git a/packages/e2e-playwright/tests/a11y/card/errorPanel.avs.spec.ts b/packages/e2e-playwright/tests/a11y/card/errorPanel.avs.spec.ts new file mode 100644 index 000000000..29785ceed --- /dev/null +++ b/packages/e2e-playwright/tests/a11y/card/errorPanel.avs.spec.ts @@ -0,0 +1,175 @@ +import { mergeTests, expect } from '@playwright/test'; +import { test as cardWithAvs } from '../../../fixtures/card.fixture'; +import { test as srPanel } from '../../../fixtures/srPanel.fixture'; +import { REGULAR_TEST_CARD, TEST_CVC_VALUE, TEST_DATE_VALUE } from '../../utils/constants'; + +const test = mergeTests(cardWithAvs, srPanel); +// Card with AVS, show srPanel, no prefilled data +const url = '/iframe.html?args=srConfig.showPanel:!true;componentConfiguration.data:!undefined&globals=&id=cards-card--with-avs&viewMode=story'; + +test('#1 avsCard error fields and inputs should have correct aria attributes', async ({ cardWithAvs }) => { + await cardWithAvs.goto(url); + await cardWithAvs.pay(); + await expect(cardWithAvs.cvcErrorElement).toHaveAttribute('aria-hidden', 'true'); + await expect(cardWithAvs.cvcErrorElement).not.toHaveAttribute('aria-live'); + await expect(cardWithAvs.cardNumberInput).toHaveAttribute('aria-describedby', /^adyen-checkout-encryptedCardNumber.*ariaContext$/); + await expect(cardWithAvs.billingAddress.streetInput).toHaveAttribute('aria-describedby', /^adyen-checkout-street.*ariaError$/); + await expect(cardWithAvs.billingAddress.streetInputError).not.toHaveAttribute('aria-describedby', /^adyen-checkout-street.*ariaError$/); +}); + +test('#2 Click pay with empty fields and error panel in avsCard is populated', async ({ page, cardWithAvs, srPanel }) => { + const expectedSRPanelTexts = [ + 'Enter the card number-sr', + 'Enter the expiry date-sr', + 'Enter the security code-sr', + 'Enter the Country/Region-sr', + 'Enter the Street-sr', + 'Enter the House number-sr', + 'Enter the Postal code-sr', + 'Enter the City-sr' + ]; + await cardWithAvs.goto(url); + await cardWithAvs.pay(); + // Wait for all sr panel messages + await page.waitForFunction( + expectedLength => [...document.querySelectorAll('.adyen-checkout-sr-panel__msg')].map(el => el.textContent).length === expectedLength, + expectedSRPanelTexts.length + ); + // check individual messages + const srErrorMessages = await srPanel.allMessages; + srErrorMessages.forEach((retrievedText, index) => { + expect(retrievedText).toHaveText(expectedSRPanelTexts[index]); + }); +}); + +test('#3 fill out credit card fields & see that first error in error panel is country related', async ({ page, cardWithAvs, srPanel }) => { + const expectedSRPanelTexts = [ + 'Enter the Country/Region-sr', + 'Enter the Street-sr', + 'Enter the House number-sr', + 'Enter the Postal code-sr', + 'Enter the City-sr' + ]; + await cardWithAvs.goto(url); + await cardWithAvs.fillCardNumber(REGULAR_TEST_CARD); + await cardWithAvs.fillExpiryDate(TEST_DATE_VALUE); + await cardWithAvs.fillCvc(TEST_CVC_VALUE); + + await cardWithAvs.pay(); + // Wait for all sr panel messages + await page.waitForFunction( + expectedLength => [...document.querySelectorAll('.adyen-checkout-sr-panel__msg')].map(el => el.textContent).length === expectedLength, + expectedSRPanelTexts.length + ); + // check individual messages + const srErrorMessages = await srPanel.allMessages; + srErrorMessages.forEach((retrievedText, index) => { + expect(retrievedText).toHaveText(expectedSRPanelTexts[index]); + }); +}); + +test('#4 Switch country to US, click pay with empty fields and error panel in avsCard is populated US style', async ({ + page, + cardWithAvs, + srPanel +}) => { + const expectedSRPanelTexts = [ + 'Enter the card number-sr', + 'Enter the expiry date-sr', + 'Enter the security code-sr', + 'Enter the Address-sr', + 'Enter the City-sr', + 'Enter the State-sr', + 'Enter the Zip code-sr' + ]; + + await cardWithAvs.goto(url); + await cardWithAvs.billingAddress.selectCountry({ name: 'United States' }); + await cardWithAvs.pay(); + // Wait for all sr panel messages + await page.waitForFunction( + expectedLength => [...document.querySelectorAll('.adyen-checkout-sr-panel__msg')].map(el => el.textContent).length === expectedLength, + expectedSRPanelTexts.length + ); + const srErrorMessages = await srPanel.allMessages; + srErrorMessages.forEach((retrievedText, index) => { + expect(retrievedText).toHaveText(expectedSRPanelTexts[index]); + }); +}); + +test('#5 Switch country to US, fill out credit card fields & see that first error in error panel is address related', async ({ + page, + cardWithAvs, + srPanel +}) => { + const expectedSRPanelTexts = ['Enter the Address-sr', 'Enter the City-sr', 'Enter the State-sr', 'Enter the Zip code-sr']; + + await cardWithAvs.goto(url); + await cardWithAvs.fillCardNumber(REGULAR_TEST_CARD); + await cardWithAvs.fillExpiryDate(TEST_DATE_VALUE); + await cardWithAvs.fillCvc(TEST_CVC_VALUE); + await cardWithAvs.billingAddress.selectCountry({ name: 'United States' }); + await cardWithAvs.pay(); + // Wait for all sr panel messages + await page.waitForFunction( + expectedLength => [...document.querySelectorAll('.adyen-checkout-sr-panel__msg')].map(el => el.textContent).length === expectedLength, + expectedSRPanelTexts.length + ); + const srErrorMessages = await srPanel.allMessages; + srErrorMessages.forEach((retrievedText, index) => { + expect(retrievedText).toHaveText(expectedSRPanelTexts[index]); + }); +}); + +test('#6 Switch country to UK, click pay with empty fields and error panel in avsCard is populated UK style', async ({ + page, + cardWithAvs, + srPanel +}) => { + const expectedSRPanelTexts = [ + 'Enter the card number-sr', + 'Enter the expiry date-sr', + 'Enter the security code-sr', + 'Enter the House number-sr', + 'Enter the Street-sr', + 'Enter the City / Town-sr', + 'Enter the Postal code-sr' + ]; + + await cardWithAvs.goto(url); + await cardWithAvs.billingAddress.selectCountry({ name: 'United Kingdom' }); + await cardWithAvs.pay(); + // Wait for all sr panel messages + await page.waitForFunction( + expectedLength => [...document.querySelectorAll('.adyen-checkout-sr-panel__msg')].map(el => el.textContent).length === expectedLength, + expectedSRPanelTexts.length + ); + const srErrorMessages = await srPanel.allMessages; + srErrorMessages.forEach((retrievedText, index) => { + expect(retrievedText).toHaveText(expectedSRPanelTexts[index]); + }); +}); + +test('#7 Switch country to UK, fill out credit card fields & see that first error in error panel is address related', async ({ + page, + cardWithAvs, + srPanel +}) => { + const expectedSRPanelTexts = ['Enter the House number-sr', 'Enter the Street-sr', 'Enter the City / Town-sr', 'Enter the Postal code-sr']; + + await cardWithAvs.goto(url); + await cardWithAvs.fillCardNumber(REGULAR_TEST_CARD); + await cardWithAvs.fillExpiryDate(TEST_DATE_VALUE); + await cardWithAvs.fillCvc(TEST_CVC_VALUE); + await cardWithAvs.billingAddress.selectCountry({ name: 'United Kingdom' }); + await cardWithAvs.pay(); + // Wait for all sr panel messages + await page.waitForFunction( + expectedLength => [...document.querySelectorAll('.adyen-checkout-sr-panel__msg')].map(el => el.textContent).length === expectedLength, + expectedSRPanelTexts.length + ); + const srErrorMessages = await srPanel.allMessages; + srErrorMessages.forEach((retrievedText, index) => { + expect(retrievedText).toHaveText(expectedSRPanelTexts[index]); + }); +}); diff --git a/packages/e2e-playwright/tests/a11y/card/errorPanel.card.visible.spec.ts b/packages/e2e-playwright/tests/a11y/card/errorPanel.card.visible.spec.ts new file mode 100644 index 000000000..f007e73eb --- /dev/null +++ b/packages/e2e-playwright/tests/a11y/card/errorPanel.card.visible.spec.ts @@ -0,0 +1,28 @@ +import { test } from '@playwright/test'; + +test('#1 Error panel is present at start, when there are no errors, but is empty', async () => { + // Wait for field to appear in DOM + // error panel exists but is empty +}); + +test('#2 Click pay with empty fields and error panel is populated', async () => { + // Wait for field to appear in DOM + // click pay, to validate & generate errors + // Expect 3 elements, in order, with specific text + // expect(cardPage.errorPanelEls.nth(0).withExactText(CARD_NUMBER_EMPTY).exists) + // expect(cardPage.errorPanelEls.nth(1).withExactText(EXPIRY_DATE_EMPTY).exists) + // expect(cardPage.errorPanelEls.nth(2).withExactText(CVC_EMPTY).exists) + // no 4th element + // Expect focus to be place on Card number field - since SRConfig for this card comp says it should be +}); + +test('#3 Fill out PAN & see that first error in error panel is date related', async () => { + // Wait for field to appear in DOM + // await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); + // click pay, to validate & generate errors + // Expect 2 elements, in order, with specific text + // expect(cardPage.errorPanelEls.nth(0).withExactText(EXPIRY_DATE_EMPTY).exists) + // expect(cardPage.errorPanelEls.nth(1).withExactText(CVC_EMPTY).exists) + // no 3rd element + // Expect focus to be place on Expiry date field +}); diff --git a/packages/e2e-playwright/tests/a11y/card/errorPanel.disabled.avs.spec.ts b/packages/e2e-playwright/tests/a11y/card/errorPanel.disabled.avs.spec.ts new file mode 100644 index 000000000..f976c3b73 --- /dev/null +++ b/packages/e2e-playwright/tests/a11y/card/errorPanel.disabled.avs.spec.ts @@ -0,0 +1,13 @@ +import { test } from '@playwright/test'; + +test('#1 avsCard error fields and inputs should have correct aria attributes', async () => { + // error panel does not exist at startup + // Wait for field to appear in DOM + // click pay, to validate & generate errors + // PAN's error field should have correct aria attrs + // PAN input should have aria-describedby attr + // Address input's error field should have correct aria attrs + // expect(cardPage.addressLabelErrorText.getAttribute('aria-live')) + // expect(cardPage.addressLabelErrorText.getAttribute('aria-hidden')) + // Address should have aria-describedby attr & it should equals the error field's id +}); diff --git a/packages/e2e-playwright/tests/a11y/card/errorPanel.hidden.spec.ts b/packages/e2e-playwright/tests/a11y/card/errorPanel.hidden.spec.ts new file mode 100644 index 000000000..883f959e3 --- /dev/null +++ b/packages/e2e-playwright/tests/a11y/card/errorPanel.hidden.spec.ts @@ -0,0 +1,15 @@ +import { test } from '@playwright/test'; + +test('#1 Click pay with empty fields and hidden error panel is populated', async () => { + // error panel exists at startup but is not visible + // Wait for field to appear in DOM + // click pay, to validate & generate errors + // error panel exists but is not visible + // Expect 4 elements, in order, with specific text + // expect(cardPage.errorPanelEls.nth(0).withExactText(INVALID_NAME).exists) + // expect(cardPage.errorPanelEls.nth(1).withExactText(CARD_NUMBER_EMPTY).exists) + // expect(cardPage.errorPanelEls.nth(2).withExactText(EXPIRY_DATE_EMPTY).exists) + // expect(cardPage.errorPanelEls.nth(3).withExactText(CVC_EMPTY).exists) + // no 5th element + // Expect focus not to be place on holder name field - since SRConfig for this card comp says it shouldn't be +}); diff --git a/packages/e2e-playwright/tests/a11y/card/errorPanel.kcpCard.visible.spec.ts b/packages/e2e-playwright/tests/a11y/card/errorPanel.kcpCard.visible.spec.ts new file mode 100644 index 000000000..2e6ac0faf --- /dev/null +++ b/packages/e2e-playwright/tests/a11y/card/errorPanel.kcpCard.visible.spec.ts @@ -0,0 +1,29 @@ +import { test } from '@playwright/test'; + +test('#1 Click pay with empty fields and error panel is populated', async () => { + // error panel exists at startup (in its hidden state) + // Wait for field to appear in DOM + // click pay, to validate & generate errors + // Expect 6 elements, in order, with specific text + // expect(cardPage.errorPanelEls.nth(0).withExactText(CARD_NUMBER_EMPTY).exists) + // expect(cardPage.errorPanelEls.nth(1).withExactText(EXPIRY_DATE_EMPTY).exists) + // expect(cardPage.errorPanelEls.nth(2).withExactText(CVC_EMPTY).exists) + // expect(cardPage.errorPanelEls.nth(3).withExactText(INVALID_NAME).exists) + // expect(cardPage.errorPanelEls.nth(4).withExactText(INVALID_TAX_NUMBER).exists) + // expect(cardPage.errorPanelEls.nth(5).withExactText(PWD_EMPTY).exists) + // no 7th element + // Expect focus to be place on Card number field - since SRConfig for this card comp says it should be +}); + +test('#2 Fill out PAN & name and see that first error in error panel is tax number related', async () => { + // Wait for field to appear in DOM + // await cardPage.cardUtils.fillCardNumber(t, KOREAN_TEST_CARD, 'paste'); // TODO - shouldn't have to 'paste' here... but Testcafe is being flaky, again! + // await cardPage.cardUtils.fillDateAndCVC(t); + // await t.typeText(cardPage.holderNameInput, 'j smith'); + // click pay, to validate & generate errors + // Expect 2 elements, in order, with specific text + // expect(cardPage.errorPanelEls.nth(0).withExactText(INVALID_TAX_NUMBER).exists) + // expect(cardPage.errorPanelEls.nth(1).withExactText(PWD_EMPTY).exists) + // no 3rd element + // Expect focus to be place on tax number field +}); diff --git a/packages/e2e-playwright/tests/a11y/customCard/expiryDate/customCard.regular.expiryDatePolicies.optional.spec.ts b/packages/e2e-playwright/tests/a11y/customCard/expiryDate/customCard.regular.expiryDatePolicies.optional.spec.ts new file mode 100644 index 000000000..64fe887af --- /dev/null +++ b/packages/e2e-playwright/tests/a11y/customCard/expiryDate/customCard.regular.expiryDatePolicies.optional.spec.ts @@ -0,0 +1,36 @@ +import { test, expect } from '../../../../fixtures/customCard.fixture'; +import { REGULAR_TEST_CARD } from '../../../utils/constants'; +import { binLookupMock } from '../../../../mocks/binLookup/binLookup.mock'; +import { optionalDateAndCvcMock } from '../../../../mocks/binLookup/binLookup.data'; + +test.describe('Test how Custom Card Component with regular date field handles hidden expiryDate policy', () => { + test('#2 how securedField responds', async ({ page, customCard }) => { + await binLookupMock(page, optionalDateAndCvcMock); + + // Expect iframe's expiryDate (& cvc) input fields to have an aria-required attr set to true + let dateAriaRequired = await customCard.expiryDateInput.getAttribute('aria-required'); + await expect(dateAriaRequired).toEqual('true'); + + let cvcAriaRequired = await customCard.cvcInput.getAttribute('aria-required'); + await expect(cvcAriaRequired).toEqual('true'); + + // Fill number to provoke (mock) binLookup response + await customCard.typeCardNumber(REGULAR_TEST_CARD); + + // Expect iframe's expiryDate (& cvc) input fields to have an aria-required attr set to false + dateAriaRequired = await customCard.expiryDateInput.getAttribute('aria-required'); + await expect(dateAriaRequired).toEqual('false'); + + cvcAriaRequired = await customCard.cvcInput.getAttribute('aria-required'); + await expect(cvcAriaRequired).toEqual('false'); + + // Clear number and see SF's aria-required reset + await customCard.deleteCardNumber(); + + dateAriaRequired = await customCard.expiryDateInput.getAttribute('aria-required'); + await expect(dateAriaRequired).toEqual('true'); + + cvcAriaRequired = await customCard.cvcInput.getAttribute('aria-required'); + await expect(cvcAriaRequired).toEqual('true'); + }); +}); diff --git a/packages/e2e-playwright/tests/a11y/customCard/expiryDate/customCard.separate.expiryDatePolicies.optional.spec.ts b/packages/e2e-playwright/tests/a11y/customCard/expiryDate/customCard.separate.expiryDatePolicies.optional.spec.ts new file mode 100644 index 000000000..b79fbb5e0 --- /dev/null +++ b/packages/e2e-playwright/tests/a11y/customCard/expiryDate/customCard.separate.expiryDatePolicies.optional.spec.ts @@ -0,0 +1,45 @@ +import { test, expect } from '../../../../fixtures/customCard.fixture'; +import { REGULAR_TEST_CARD } from '../../../utils/constants'; +import { binLookupMock } from '../../../../mocks/binLookup/binLookup.mock'; +import { optionalDateAndCvcMock } from '../../../../mocks/binLookup/binLookup.data'; + +test.describe('Test how Custom Card Component with separate date fields handles hidden expiryDate policy', () => { + test('#2 how securedField responds', async ({ page, customCardSeparateExpiryDate }) => { + await binLookupMock(page, optionalDateAndCvcMock); + + // Expect iframe's date (& cvc) input fields to have an aria-required attr set to true + let monthAriaRequired = await customCardSeparateExpiryDate.expiryMonthInput.getAttribute('aria-required'); + await expect(monthAriaRequired).toEqual('true'); + + let yearAriaRequired = await customCardSeparateExpiryDate.expiryYearInput.getAttribute('aria-required'); + await expect(yearAriaRequired).toEqual('true'); + + let cvcAriaRequired = await customCardSeparateExpiryDate.cvcInput.getAttribute('aria-required'); + await expect(cvcAriaRequired).toEqual('true'); + + // Fill number to provoke (mock) binLookup response + await customCardSeparateExpiryDate.typeCardNumber(REGULAR_TEST_CARD); + + // Expect iframe's date (& cvc) input fields to have an aria-required attr set to false + monthAriaRequired = await customCardSeparateExpiryDate.expiryMonthInput.getAttribute('aria-required'); + await expect(monthAriaRequired).toEqual('false'); + + yearAriaRequired = await customCardSeparateExpiryDate.expiryYearInput.getAttribute('aria-required'); + await expect(yearAriaRequired).toEqual('false'); + + cvcAriaRequired = await customCardSeparateExpiryDate.cvcInput.getAttribute('aria-required'); + await expect(cvcAriaRequired).toEqual('false'); + + // Clear number and see SF's aria-required reset + await customCardSeparateExpiryDate.deleteCardNumber(); + + monthAriaRequired = await customCardSeparateExpiryDate.expiryMonthInput.getAttribute('aria-required'); + await expect(monthAriaRequired).toEqual('true'); + + yearAriaRequired = await customCardSeparateExpiryDate.expiryYearInput.getAttribute('aria-required'); + await expect(yearAriaRequired).toEqual('true'); + + cvcAriaRequired = await customCardSeparateExpiryDate.cvcInput.getAttribute('aria-required'); + await expect(cvcAriaRequired).toEqual('true'); + }); +}); diff --git a/packages/e2e-playwright/tests/a11y/issuerList/issuerList.spec.ts b/packages/e2e-playwright/tests/a11y/issuerList/issuerList.spec.ts new file mode 100644 index 000000000..d8932e34d --- /dev/null +++ b/packages/e2e-playwright/tests/a11y/issuerList/issuerList.spec.ts @@ -0,0 +1,54 @@ +import { test, expect } from '../../../fixtures/issuer-list.fixture'; +import { pressEnter, pressKeyboardToNextItem } from '../../utils/keyboard'; + +test.describe('Issuer list - onlineBankingPL keyboard navigation', () => { + test('it should be able to filter and select using the keyboard', async ({ page, onlineBankingPL }) => { + await expect(onlineBankingPL.payButton).toHaveText('Continue'); + + await onlineBankingPL.clickOnSelector(); + const issuers = await onlineBankingPL.issuers; + expect(issuers.length).toBeGreaterThan(0); + + const [firstIssuer, secondIssuer] = issuers; + await onlineBankingPL.typeOnSelectorField(firstIssuer); // filtered content + await expect(onlineBankingPL.selectorList).not.toContainText(secondIssuer); + + // select one of the filtered option + await pressKeyboardToNextItem(page); // Arrow down + await pressEnter(page); // Enter key + await expect(onlineBankingPL.payButton).toHaveText(`Continue to ${firstIssuer}`); + + // 1st press opens the dropdown + await pressKeyboardToNextItem(page); + // 2nd selects next item + await pressKeyboardToNextItem(page); + await pressEnter(page); + + await expect(onlineBankingPL.payButton).toHaveText(`Continue to ${secondIssuer}`); + }); + + test('it should load a default, from the filtered items, when pressing enter', async ({ page, onlineBankingPL }) => { + await onlineBankingPL.clickOnSelector(); + const issuers = await onlineBankingPL.issuers; + const [firstIssuer] = issuers; + await onlineBankingPL.typeOnSelectorField(firstIssuer); // filtered content + await pressEnter(page); + + await expect(onlineBankingPL.payButton).toHaveText(`Continue to ${firstIssuer}`); + }); + + test('it should have the expected data in state, ready for the /payments call', async ({ page, onlineBankingPL }) => { + // Open the drop down and select an item + await onlineBankingPL.clickOnSelector(); + await pressKeyboardToNextItem(page); // Arrow down + await pressEnter(page); // Enter key + + let issuerListData = await page.evaluate('window.component.data'); + + // @ts-ignore it can be flaky + expect(issuerListData.paymentMethod).toMatchObject({ + type: 'onlineBanking_PL', + issuer: '154' + }); + }); +}); diff --git a/packages/e2e-playwright/tests/a11y/openInvoices/riverty.spec.ts b/packages/e2e-playwright/tests/a11y/openInvoices/riverty.spec.ts new file mode 100644 index 000000000..9a17a5a3d --- /dev/null +++ b/packages/e2e-playwright/tests/a11y/openInvoices/riverty.spec.ts @@ -0,0 +1,90 @@ +import { mergeTests, expect } from '@playwright/test'; +import LANG from '../../../../server/translations/en-US.json'; +import { USER_TYPE_DELAY } from '../../utils/constants'; +import { test as riverty } from '../../../fixtures/openInvoices.fixture'; +import { test as srPanel } from '../../../fixtures/srPanel.fixture'; + +const test = mergeTests(riverty, srPanel); + +const REQUIRED_FIELD = LANG['field.error.required'].replace('%{label}', ''); + +const BILLING_ADDRESS = LANG['billingAddress']; +const DELIVERY_ADDRESS = LANG['deliveryAddress']; + +const EMPTY_POST_CODE = `${REQUIRED_FIELD}${BILLING_ADDRESS} ${LANG['postalCode']}`; +const INVALID_FORMAT_EXPECTS = LANG['invalid.format.expects'].replace(/%{label}|%{format}/g, ''); +const INVALID_POST_CODE = `${BILLING_ADDRESS} ${LANG['postalCode']}${INVALID_FORMAT_EXPECTS}99999`; + +const expectedSRPanelTexts = [ + `${LANG['firstName.invalid']}`, + `${LANG['lastName.invalid']}`, + `${REQUIRED_FIELD}${LANG['dateOfBirth']}`, + `${REQUIRED_FIELD}${LANG['shopperEmail']}`, + `${REQUIRED_FIELD}${LANG['telephoneNumber']}`, + `${REQUIRED_FIELD}${BILLING_ADDRESS} ${LANG['street']}`, + `${REQUIRED_FIELD}${BILLING_ADDRESS} ${LANG['houseNumberOrName']}`, + EMPTY_POST_CODE, + `${REQUIRED_FIELD}${BILLING_ADDRESS} ${LANG['city']}`, + `${REQUIRED_FIELD}${DELIVERY_ADDRESS} ${LANG['deliveryAddress.firstName']}`, + `${REQUIRED_FIELD}${DELIVERY_ADDRESS} ${LANG['deliveryAddress.lastName']}`, + `${REQUIRED_FIELD}${DELIVERY_ADDRESS} ${LANG['street']}`, + `${REQUIRED_FIELD}${DELIVERY_ADDRESS} ${LANG['houseNumberOrName']}`, + `${REQUIRED_FIELD}${DELIVERY_ADDRESS} ${LANG['postalCode']}`, + `${REQUIRED_FIELD}${DELIVERY_ADDRESS} ${LANG['city']}`, + `${LANG['consent.checkbox.invalid']}` +]; + +test.describe('Test Riverty Component', () => { + test('#1 Test how Riverty component handles SRPanel messages', async ({ page, riverty, srPanel }) => { + await riverty.rivertyDeliveryAddressCheckbox.click(); + await riverty.pay(); + // Wait for all sr messages to show otherwise srErrorMessages.forEach fails to match one by one + await page.waitForFunction( + expectedLength => [...document.querySelectorAll('.adyen-checkout-sr-panel__msg')].map(el => el.textContent).length === expectedLength, + expectedSRPanelTexts.length + ); + // Inspect SRPanel errors: all expected errors, in the expected order + // check individual messages + const srErrorMessages = await srPanel.allMessages; + srErrorMessages.forEach((retrievedText, index) => { + expect(retrievedText).toContainText(expectedSRPanelTexts[index]); + }); + + // KEEP - handy for debugging test + // await expect(page.getByText('Enter your first name-sr', { exact: true })).toBeVisible(); + }); + + test('#2 Test how Riverty component handles SRPanel messages, specifically a postal code with an invalid format', async ({ + page, + riverty, + srPanel + }) => { + await riverty.rivertyDeliveryAddressCheckbox.click(); + + expectedSRPanelTexts.splice(7, 1, INVALID_POST_CODE); + + await riverty.riverty + .getByRole('group', { name: 'Billing address' }) + .getByLabel('Postal code') + // .locator('.adyen-checkout__field--postalCode .adyen-checkout__input--postalCode') + .type('3', { delay: USER_TYPE_DELAY }); + + await riverty.pay(); // first click need to trigger blur event on postCode field + await riverty.pay(); // second click to trigger validation + + // Wait for all sr messages to show otherwise srErrorMessages.forEach fails to match one by one + await page.waitForFunction( + expectedLength => [...document.querySelectorAll('.adyen-checkout-sr-panel__msg')].map(el => el.textContent).length === expectedLength, + expectedSRPanelTexts.length + ); + + const srErrorMessages = await srPanel.allMessages; + // Inspect SRPanel errors: all expected errors, in the expected order + srErrorMessages.forEach((retrievedText, index) => { + expect(retrievedText).toContainText(expectedSRPanelTexts[index]); + }); + + // Restore array to start state + expectedSRPanelTexts.splice(7, 1, EMPTY_POST_CODE); + }); +}); diff --git a/packages/e2e-playwright/tests/ancv/ancv.spec.ts b/packages/e2e-playwright/tests/ancv/ancv.spec.ts deleted file mode 100644 index a806f5294..000000000 --- a/packages/e2e-playwright/tests/ancv/ancv.spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { test, expect } from '../../pages/ancv/ancv.fixture'; -import { createOrderMock } from '../../mocks/createOrder/createOrder.mock'; -import { orderCreatedMockData } from '../../mocks/createOrder/createOrder.data'; -import { paymentsMock } from '../../mocks/payments/payments.mock'; -import { paymentsActionAncvMockData } from '../../mocks/payments/payments.data'; -import { paymentDetailsMock } from '../../mocks/paymentDetails/paymentDetails.mock'; -import { paymentDetailsPartiallyAuthorisedAncvMockData } from '../../mocks/paymentDetails/paymentDetails.data'; -import { setupWithAncvOrderMockData } from '../../mocks/setup/setup.data'; -import { statusMockData } from '../../mocks/status/status.data'; -import { setupMock } from '../../mocks/setup/setup.mock'; -import { statusMock } from '../../mocks/status/status.mock'; - -test.describe('ANCV - Sessions', () => { - test('should call onOrderUpdated when payment is partially authorised (Sessions flow)', async ({ ancvPage }) => { - const { ancv, page } = ancvPage; - - await createOrderMock(page, orderCreatedMockData); - await paymentsMock(page, paymentsActionAncvMockData); - await statusMock(page, statusMockData); - await paymentDetailsMock(page, paymentDetailsPartiallyAuthorisedAncvMockData); - await setupMock(page, setupWithAncvOrderMockData); - - await ancv.fillInID('ancv-id@example.com'); - await ancv.clickOnSubmit(); - - await expect(page.locator('#result-message')).toHaveText('Partially Authorised'); - }); -}); diff --git a/packages/e2e-playwright/tests/card/branding/card.branding.spec.ts b/packages/e2e-playwright/tests/card/branding/card.branding.spec.ts deleted file mode 100644 index 1d527d72f..000000000 --- a/packages/e2e-playwright/tests/card/branding/card.branding.spec.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { test, expect } from '../../../pages/cards/card.fixture'; -import { MAESTRO_CARD, TEST_CVC_VALUE, TEST_DATE_VALUE } from '../../utils/constants'; -import LANG from '../../../../server/translations/en-US.json'; - -const CVC_LABEL = LANG['creditCard.securityCode.label']; -const CVC_LABEL_OPTIONAL = LANG['creditCard.securityCode.label.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.avs.spec.ts b/packages/e2e-playwright/tests/card/card.avs.spec.ts deleted file mode 100644 index a31cf1588..000000000 --- a/packages/e2e-playwright/tests/card/card.avs.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { test, expect } from '../../pages/cards/card.fixture'; -import { REGULAR_TEST_CARD } from '../utils/constants'; -import { binLookupMock } from '../../mocks/binLookup/binLookup.mock'; -import { optionalDateAndCvcMock } from '../../mocks/binLookup/binLookup.data'; - -test.describe('Card - AVS', () => { - test('should move the focus to the address field since expiryDate & cvc are optional', async ({ cardAvsPage }) => { - const { cardWithAvs, page } = cardAvsPage; - - await binLookupMock(page, optionalDateAndCvcMock); - - await cardWithAvs.isComponentVisible(); - - const firstDigits = REGULAR_TEST_CARD.substring(0, 15); - const lastDigits = REGULAR_TEST_CARD.substring(15, 16); - - await cardWithAvs.typeCardNumber(firstDigits); - await cardWithAvs.typeCardNumber(lastDigits); - - await expect(cardWithAvs.billingAddress.addressInput).toBeFocused(); - }); -}); diff --git a/packages/e2e-playwright/tests/card/card.spec.ts b/packages/e2e-playwright/tests/card/card.spec.ts deleted file mode 100644 index 90592d52f..000000000 --- a/packages/e2e-playwright/tests/card/card.spec.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { test, expect } from '../../pages/cards/card.fixture'; -import { REGULAR_TEST_CARD, TEST_CVC_VALUE, TEST_DATE_VALUE } from '../utils/constants'; -import LANG from '../../../server/translations/en-US.json'; -import { pressEnter } from '../utils/keyboard'; - -const PAN_ERROR_NOT_VALID = LANG['cc.num.902']; -const PAN_ERROR_EMPTY = LANG['cc.num.900']; -const PAN_ERROR_NOT_COMPLETE = LANG['cc.num.901']; - -const EXPIRY_DATE_ERROR_EMPTY = LANG['cc.dat.910']; -const CVC_ERROR_EMPTY = LANG['cc.cvc.920']; - -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 cardPage.pay(); - - 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; - - await card.typeCardNumber('3333 3333 3333 3333 3333'); - - await cardPage.pay(); - - 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; - - await cardPage.pay(); - - 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; - - await card.typeCardNumber('4'); - - await cardPage.pay(); - - await expect(card.cardNumberErrorElement).toBeVisible(); - await expect(card.cardNumberErrorElement).toHaveText(PAN_ERROR_NOT_COMPLETE); - }); - - test('#5 Filling PAN then pressing Enter will trigger validation ', async ({ cardPage }) => { - const { card, page } = cardPage; - - await card.isComponentVisible(); - await card.typeCardNumber(REGULAR_TEST_CARD); - - await pressEnter(page); - - await page.waitForTimeout(500); // wait for UI to show errors - - await expect(card.expiryDateErrorElement).toBeVisible(); - await expect(card.expiryDateErrorElement).toHaveText(EXPIRY_DATE_ERROR_EMPTY); - - await expect(card.cvcErrorElement).toBeVisible(); - await expect(card.cvcErrorElement).toHaveText(CVC_ERROR_EMPTY); - }); - - test('#6 Filling in card fields then pressing Enter will trigger submission ', async ({ cardPage }) => { - const { card, page } = cardPage; - - await card.isComponentVisible(); - - await card.typeCardNumber(REGULAR_TEST_CARD); - await card.typeCvc(TEST_CVC_VALUE); - await card.typeExpiryDate(TEST_DATE_VALUE); - - await pressEnter(page); - - await expect(page.locator('#result-message')).toHaveText('Authorised'); - }); -}); diff --git a/packages/e2e-playwright/tests/customCard/customCard.spec.ts b/packages/e2e-playwright/tests/customCard/customCard.spec.ts deleted file mode 100644 index 8eeaf3873..000000000 --- a/packages/e2e-playwright/tests/customCard/customCard.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { test, expect } from '../../pages/customCard/customCard.fixture'; -import { REGULAR_TEST_CARD, TEST_CVC_VALUE, TEST_DATE_VALUE } from '../utils/constants'; - -test.describe('Custom Card - Standard flow', () => { - test('should fill in card fields and complete the payment', async ({ customCardPage }) => { - const { card, page } = customCardPage; - - await card.isComponentVisible(); - - await card.typeCardNumber(REGULAR_TEST_CARD); - await card.typeExpiryDate(TEST_DATE_VALUE); - await card.typeCvc(TEST_CVC_VALUE); - - await customCardPage.pay(); - - await expect(page.locator('#result-message')).toHaveText('Authorised'); - }); -}); 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 deleted file mode 100644 index 66ac09706..000000000 --- a/packages/e2e-playwright/tests/customCard/expiryDate/customCard.regular.expiryDatePolicies.optional.spec.ts +++ /dev/null @@ -1,176 +0,0 @@ -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 '../../../../server/translations/en-US.json'; - -const DATE_LABEL = LANG['creditCard.expiryDate.label']; -const CVC_LABEL = LANG['creditCard.securityCode.label']; -const CVC_LABEL_OPTIONAL = LANG['creditCard.securityCode.label.optional']; - -const OPTIONAL = LANG['field.title.optional']; - -const PAN_ERROR = LANG['cc.num.900']; -const DATE_INVALID_ERROR = LANG['cc.dat.912']; -const DATE_EMPTY_ERROR = LANG['cc.dat.910']; -const CVC_ERROR = LANG['cc.cvc.920']; - -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.optional.spec.ts b/packages/e2e-playwright/tests/customCard/expiryDate/customCard.separate.expiryDatePolicies.optional.spec.ts deleted file mode 100644 index 98a0031e9..000000000 --- a/packages/e2e-playwright/tests/customCard/expiryDate/customCard.separate.expiryDatePolicies.optional.spec.ts +++ /dev/null @@ -1,208 +0,0 @@ -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 '../../../../server/translations/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['cc.num.900']; -const MONTH_EMPTY_ERROR = LANG['cc.mth.915']; -const YEAR_EMPTY_ERROR = LANG['cc.yr.917']; -const CVC_ERROR = LANG['cc.cvc.920']; -const DATE_INVALID_ERROR = LANG['cc.dat.913']; - -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(500); // wait for UI to show errors - - // 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.spec.ts b/packages/e2e-playwright/tests/dropin/cardBrands/cardBrands.spec.ts deleted file mode 100644 index 34546bae4..000000000 --- a/packages/e2e-playwright/tests/dropin/cardBrands/cardBrands.spec.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { test, expect } from '../../../pages/dropin/dropin.fixture'; -import { getCreditCardPM_withBrandsInfo } from '../../../models/utils'; - -test.describe('Dropin - Card brands displayed in the Payment Method List and underneath the PAN field', () => { - test('should display the 3 logos and left over amount of brands, and then display all available brands under the PAN field', async ({ - dropinPage_cardBrands - }) => { - await dropinPage_cardBrands.goto(); - const { dropin, page } = dropinPage_cardBrands; - await dropin.isComponentVisible(); - - const creditCard = getCreditCardPM_withBrandsInfo(dropin); - await creditCard.pm.scrollIntoViewIfNeeded(); - const imgCount = await creditCard.getImageCount(creditCard.brandsHolder); - - /** - * Display the right amount in the payment method header - */ - await expect(creditCard.brandsHolder).toBeVisible(); - await expect(imgCount).toEqual(3); - await expect(creditCard.brandsText).toBeVisible(); - await expect(creditCard.brandsText).toHaveText('+7'); - - /** - * When clicking in the Component, it displays the right amount underneath the card number field - */ - await creditCard.pm.click(); - - await expect(creditCard.brandsHolder).not.toBeVisible(); - await expect(creditCard.brandsText).not.toBeVisible(); - - // Brands inside actual Credit Card component - const brandsInsideCardComponent = await creditCard.getImageCount(creditCard.componentBrandsHolder); - await expect(brandsInsideCardComponent).toEqual(10); - }); - - test('should exclude non-valid brands and display only the right amount in the payment header and underneath the PAN field', async ({ - dropinPage_cardBrands_withExcluded - }) => { - await dropinPage_cardBrands_withExcluded.goto(); - const { dropin, page } = dropinPage_cardBrands_withExcluded; - - await dropin.isComponentVisible(); - - const creditCard = getCreditCardPM_withBrandsInfo(dropin); - await creditCard.pm.scrollIntoViewIfNeeded(); - const imgCount = await creditCard.getImageCount(creditCard.brandsHolder); - - /** - * Display the right amount in the payment method header - */ - await expect(creditCard.brandsHolder).toBeVisible(); - await expect(imgCount).toEqual(3); - await expect(creditCard.brandsText).toBeVisible(); - await expect(creditCard.brandsText).toHaveText('+3'); - - /** - * When clicking in the Component, it displays the right amount underneath the card number field - */ - await creditCard.pm.click(); - - await expect(creditCard.brandsHolder).not.toBeVisible(); - await expect(creditCard.brandsText).not.toBeVisible(); - - // Brands inside actual Credit Card component - const brandsInsideCardComponent = await creditCard.getImageCount(creditCard.componentBrandsHolder); - await expect(brandsInsideCardComponent).toEqual(6); - }); -}); diff --git a/packages/e2e-playwright/tests/dropin/sessions/dropin.sessions.spec.ts b/packages/e2e-playwright/tests/dropin/sessions/dropin.sessions.spec.ts deleted file mode 100644 index d263dd218..000000000 --- a/packages/e2e-playwright/tests/dropin/sessions/dropin.sessions.spec.ts +++ /dev/null @@ -1,87 +0,0 @@ -import dotenv from 'dotenv'; -import { test, expect } from '../../../pages/dropin/dropin.fixture'; -import { REGULAR_TEST_CARD, TEST_CVC_VALUE, TEST_DATE_VALUE } from '../../utils/constants'; -import { typeIntoSecuredField } from '../../../models/utils'; - -import LANG from '../../../../server/translations/en-US.json'; - -const CARD_IFRAME_TITLE = LANG['creditCard.encryptedCardNumber.aria.iframeTitle']; -const EXPIRY_DATE_IFRAME_TITLE = LANG['creditCard.encryptedExpiryDate.aria.iframeTitle']; -const CVC_IFRAME_TITLE = LANG['creditCard.encryptedSecurityCode.aria.iframeTitle']; - -const CARD_IFRAME_LABEL = LANG['creditCard.cardNumber.label']; -const EXPIRY_DATE_IFRAME_LABEL = LANG['creditCard.expiryDate.label']; -const CVC_IFRAME_LABEL = LANG['creditCard.securityCode.label']; -dotenv.config(); - -const apiVersion = Number(process.env.API_VERSION.substr(1)); - -test.describe('Dropin Sessions flow', () => { - test('#1 Should succeed in making a payment', async ({ dropinSessions_regular }) => { - const { dropin, page } = dropinSessions_regular; - - await dropin.isComponentVisible(); - - console.log('### dropin.sessions.spec:::: apiVersion', apiVersion); - - const creditCard = dropin.getPaymentMethodItemByType('scheme'); - await creditCard.scrollIntoViewIfNeeded(); - - await page.waitForTimeout(500); // needs this else card number isn't guaranteed to fill correctly !? - - await typeIntoSecuredField(creditCard, CARD_IFRAME_TITLE, CARD_IFRAME_LABEL, REGULAR_TEST_CARD); - await typeIntoSecuredField(creditCard, CVC_IFRAME_TITLE, CVC_IFRAME_LABEL, TEST_CVC_VALUE); - await typeIntoSecuredField(creditCard, EXPIRY_DATE_IFRAME_TITLE, EXPIRY_DATE_IFRAME_LABEL, TEST_DATE_VALUE); - - await dropinSessions_regular.pay(); - - await expect(page.locator('#result-message')).toHaveText('Authorised'); - }); - - if (apiVersion >= 70) { - test('#2 Should succeed in making a zeroAuth payment since there is no conflicting configuration on the session', async ({ - dropinSessions_zeroAuthCard_success - }) => { - const { dropin, page } = dropinSessions_zeroAuthCard_success; - await dropin.isComponentVisible(); - - const creditCard = dropin.getPaymentMethodItemByType('scheme'); - await creditCard.scrollIntoViewIfNeeded(); - - await page.waitForTimeout(500); - - await typeIntoSecuredField(creditCard, CARD_IFRAME_TITLE, CARD_IFRAME_LABEL, REGULAR_TEST_CARD); - await typeIntoSecuredField(creditCard, CVC_IFRAME_TITLE, CVC_IFRAME_LABEL, TEST_CVC_VALUE); - await typeIntoSecuredField(creditCard, EXPIRY_DATE_IFRAME_TITLE, EXPIRY_DATE_IFRAME_LABEL, TEST_DATE_VALUE); - - // A payment successfully registered as a zero-auth payment should have a "save details" button instead of "pay" - await dropinSessions_zeroAuthCard_success.saveDetails(); - - await expect(page.locator('#result-message')).toHaveText('Authorised'); - }); - - test('#3 Should fail in making a zeroAuth payment since there is conflicting configuration on the session', async ({ - dropinSessions_zeroAuthCard_fail - }) => { - const { dropin, page } = dropinSessions_zeroAuthCard_fail; - await dropin.isComponentVisible(); - - const creditCard = dropin.getPaymentMethodItemByType('scheme'); - await creditCard.scrollIntoViewIfNeeded(); - - await page.waitForTimeout(500); - - await typeIntoSecuredField(creditCard, CARD_IFRAME_TITLE, CARD_IFRAME_LABEL, REGULAR_TEST_CARD); - await typeIntoSecuredField(creditCard, CVC_IFRAME_TITLE, CVC_IFRAME_LABEL, TEST_CVC_VALUE); - await typeIntoSecuredField(creditCard, EXPIRY_DATE_IFRAME_TITLE, EXPIRY_DATE_IFRAME_LABEL, TEST_DATE_VALUE); - - await dropinSessions_zeroAuthCard_fail.saveDetails(); - - await expect(page.locator('#result-message')).toHaveText( - /NETWORK_ERROR: Field 'storePaymentMethod' overrides 'enableRecurring' and 'enableOneClick'. Please provide either one./ - ); - }); - } else { - test(`Skipping tests #2 & #3 for Dropin Sessions flow since api version is too low (v${apiVersion})`, async t => {}); - } -}); diff --git a/packages/e2e-playwright/tests/e2e/ancv/ancv.spec.ts b/packages/e2e-playwright/tests/e2e/ancv/ancv.spec.ts new file mode 100644 index 000000000..f8950b1c6 --- /dev/null +++ b/packages/e2e-playwright/tests/e2e/ancv/ancv.spec.ts @@ -0,0 +1,44 @@ +import { test as base, expect } from '@playwright/test'; +import { paymentDetailsMock } from '../../../mocks/paymentDetails/paymentDetails.mock'; +import { paymentDetailsPartiallyAuthorisedAncvMockData } from '../../../mocks/paymentDetails/paymentDetails.data'; +import { createOrderMock } from '../../../mocks/createOrder/createOrder.mock'; +import { orderCreatedMockData } from '../../../mocks/createOrder/createOrder.data'; +import { paymentsMock } from '../../../mocks/payments/payments.mock'; +import { paymentsActionAncvMockData } from '../../../mocks/payments/payments.data'; +import { statusMock } from '../../../mocks/status/status.mock'; +import { statusMockData } from '../../../mocks/status/status.data'; +import { setupMock } from '../../../mocks/setup/setup.mock'; +import { setupMockData, setupWithAncvOrderMockData } from '../../../mocks/setup/setup.data'; +import { ANCV } from '../../../models/ancv'; +import { sessionsMock } from '../../../mocks/sessions/sessions.mock'; +import { sessionsMockData } from '../../../mocks/sessions/sessions.data'; +import { URL_MAP } from '../../../fixtures/URL_MAP'; + +type Fixture = { + ancvPage: ANCV; +}; + +const test = base.extend({ + ancvPage: async ({ page }, use) => { + const ancvPage = new ANCV(page); + await sessionsMock(page, sessionsMockData); + await setupMock(page, setupMockData); + await ancvPage.goto(URL_MAP.ancv); + await use(ancvPage); + } +}); + +//todo fix +test.describe('ANCV - Sessions', () => { + test.fixme('should call onOrderUpdated when payment is partially authorised (Sessions flow)', async ({ page, ancvPage }) => { + await createOrderMock(page, orderCreatedMockData); + await paymentsMock(page, paymentsActionAncvMockData); + await statusMock(page, statusMockData); + await paymentDetailsMock(page, paymentDetailsPartiallyAuthorisedAncvMockData); + await setupMock(page, setupWithAncvOrderMockData); + await ancvPage.fillInID('ancv-id@example.com'); + await ancvPage.clickOnSubmit(); + + await expect(ancvPage.paymentResult).toContainText('Partially Authorised'); + }); +}); diff --git a/packages/e2e-playwright/tests/e2e/card/avs.spec.ts b/packages/e2e-playwright/tests/e2e/card/avs.spec.ts new file mode 100644 index 000000000..be4b5e2fd --- /dev/null +++ b/packages/e2e-playwright/tests/e2e/card/avs.spec.ts @@ -0,0 +1,83 @@ +import { test as base, expect } from '@playwright/test'; +import { PAYMENT_RESULT, REGULAR_TEST_CARD, TEST_CVC_VALUE, TEST_DATE_VALUE, TEST_POSTCODE } from '../../utils/constants'; +import { CardWithAvs } from '../../../models/card-avs'; +import { URL_MAP } from '../../../fixtures/URL_MAP'; + +type Fixture = { + cardAvsPage: CardWithAvs; +}; + +const test = base.extend({ + cardAvsPage: async ({ page }, use) => { + const cardAvcPage = new CardWithAvs(page); + await cardAvcPage.goto(URL_MAP.cardWithPartialAvs); + await use(cardAvcPage); + } +}); + +test.describe('Card payments with address lookup', () => {}); + +test.describe('Card payments with partial avs', () => { + test.describe('When fill in a valid the post code', () => { + test('should make a successful card payment', async ({ cardAvsPage }) => { + await cardAvsPage.fillCardNumber(REGULAR_TEST_CARD); + await cardAvsPage.fillExpiryDate(TEST_DATE_VALUE); + await cardAvsPage.fillCvc(TEST_CVC_VALUE); + await cardAvsPage.fillInPostCode(TEST_POSTCODE); + await cardAvsPage.pay(); + await cardAvsPage.paymentResult.waitFor({ state: 'visible' }); + await expect(cardAvsPage.paymentResult).toContainText(PAYMENT_RESULT.authorised); + }); + }); + + test.describe('When fill in an invalid post code ', () => { + // in the fixture, do not provide country code + test('should not submit the payment', async ({ page }) => { + // fill in card number + // fill in expiry date + // fill in cvc + // fill in post code + // click pay btn + // expect to see error message under the post code field + }); + }); +}); + +test.describe('Card payments with full avs', () => { + test.describe('When fill in the valid address data', () => { + test('should make a successful card payment', async ({ page }) => { + // fill in card number + // fill in expiry date + // fill in cvc + // fill in address ... + // click pay btn + // expect to see success result + }); + }); + + test.describe('When fill in the invalid address data', () => { + test('should not submit the payment', async ({ page }) => { + // fill in card number + // fill in expiry date + // fill in cvc + // skip required fields in address section + // click pay btn + // expect to see error message + }); + }); + + test.describe('When switching to a different delivery country', () => { + test('should make a successful card payment', async ({ page }) => { + // prefilled wrong address + // user sees error msg + // change the country code + // submit payment successfully + }); + test('should not submit the payment', async ({ page }) => { + // prefilled correct address + // change the country code + // user sees error msg + // user cannot submit payment + }); + }); +}); diff --git a/packages/e2e-playwright/tests/e2e/card/bcmc/dualBranding.spec.ts b/packages/e2e-playwright/tests/e2e/card/bcmc/dualBranding.spec.ts new file mode 100644 index 000000000..46b0ea8ac --- /dev/null +++ b/packages/e2e-playwright/tests/e2e/card/bcmc/dualBranding.spec.ts @@ -0,0 +1,92 @@ +import { test } from '@playwright/test'; + +test.describe('Bcmc payments with dual branding', () => { + test.describe('Selecting the Bancontact brand', () => { + test('should submit the bcmc payment', async () => { + // should see the bcmc logo on init + // fill in dual brand card maestro + // expect to see 2 logos with correct order + // select bcmc logo + // expect to see the bcmc logo highlighted + // click pay btn + // expect to see success msg + }); + + test('should not submit the bcmc payment with incomplete form data', async () => { + // should see the bcmc logo on init + // do not fill the expiry date + // should see error msg + }); + + test('should not submit the bcmc payment with invalid bcmc card number', async () => { + // should see the bcmc logo on init + // do not fill the expiry date + // should see error msg + }); + }); + + test.describe('Selecting the maestro brand', () => { + test('should submit the maestro payment', async () => { + // should see the bcmc logo on init + // fill in dual brand card maestro + // expect to see 2 logos with correct order + // select maestro logo + // expect to see the maestro logo highlighted + // click pay btn + // expect to see success msg + }); + test('should not submit the maestro payment with incomplete form data', async () => { + // should see the bcmc logo on init + // do not fill the expiry date + // should see error msg + }); + + test('should not submit the maestro payment with invalid maestro card number', async () => { + // should see the bcmc logo on init + // wrong maestro card number + // should see error msg + }); + }); + + test.describe('Selecting the visa brand', () => { + test('should submit the visa payment', async () => { + // should see the bcmc logo on init + // fill in dual brand card visa + // expect to see 2 logos with correct order + // select visa logo + // expect to see the visa logo highlighted + // fill in the rest required fields + // click pay btn + // expect to see success msg + }); + + test('should not submit the visa payment with incomplete form data', async () => { + // should see the bcmc logo on init + // do not fill the expiry date + // should see error msg + }); + + test('should not submit the visa payment with invalid visa card number', async () => { + // should see the bcmc logo on init + // wrong maestro card number + // should see error msg + }); + }); + + test.describe('Selecting the mc brand', () => { + test('should submit the mc payment', async () => { + // should see the bcmc logo on init + // fill in dual brand card mc + // expect to see 2 logos with correct order + // select mc logo + // expect to see the mc logo highlighted + // fill in the rest required fields + // click pay btn + // expect to see success msg + }); + + test('should not submit the mc payment with incomplete form data', async () => {}); + + test('should not submit the mc payment with invalid mc card number', async () => {}); + }); +}); diff --git a/packages/e2e-playwright/tests/e2e/card/card.spec.ts b/packages/e2e-playwright/tests/e2e/card/card.spec.ts new file mode 100644 index 000000000..024aa6fa9 --- /dev/null +++ b/packages/e2e-playwright/tests/e2e/card/card.spec.ts @@ -0,0 +1,15 @@ +import { test, expect } from '../../../fixtures/card.fixture'; +import { PAYMENT_RESULT, REGULAR_TEST_CARD, TEST_CVC_VALUE, TEST_DATE_VALUE } from '../../utils/constants'; +import { URL_MAP } from '../../../fixtures/URL_MAP'; + +test.describe('Card - Standard flow', () => { + test('#1 Should fill in card fields and complete the payment', async ({ card }) => { + await card.goto(URL_MAP.card); + await card.typeCardNumber(REGULAR_TEST_CARD); + await card.typeCvc(TEST_CVC_VALUE); + await card.typeExpiryDate(TEST_DATE_VALUE); + await card.pay(); + + await expect(card.paymentResult).toContainText(PAYMENT_RESULT.authorised); + }); +}); diff --git a/packages/e2e-playwright/tests/e2e/card/kcpAuthentication.spec.ts b/packages/e2e-playwright/tests/e2e/card/kcpAuthentication.spec.ts new file mode 100644 index 000000000..ff443eca8 --- /dev/null +++ b/packages/e2e-playwright/tests/e2e/card/kcpAuthentication.spec.ts @@ -0,0 +1,27 @@ +import { test } from '../../../fixtures/card.fixture'; + +test.describe('Card payments with KCP enabled feature', () => { + test('should submit the korea issue card payment', async () => { + // fill in KOREAN_TEST_CARD + // expect to see the non-branded logo + // fill in the required fields + // click pay btn + // expect to see success msg + }); + + test('should not submit the korea issue card payment', async () => { + // fill in KOREAN_TEST_CARD + // expect to see the non-branded logo + // fill in partial fields + // click pay btn + // expect to see error msg + }); + + test('should submit the regular non-korean card payment', async () => { + // fill in NON_KOREAN_TEST_CARD + // expect to see the brand logo + // fill in required fields + // click pay btn + // expect to see success msg + }); +}); diff --git a/packages/e2e-playwright/tests/card/legacyInputMode/card.legacyInputMode.spec.ts b/packages/e2e-playwright/tests/e2e/card/legacyInputMode/card.legacyInputMode.spec.ts similarity index 76% rename from packages/e2e-playwright/tests/card/legacyInputMode/card.legacyInputMode.spec.ts rename to packages/e2e-playwright/tests/e2e/card/legacyInputMode/card.legacyInputMode.spec.ts index 774c1f8f4..a15e2c72e 100644 --- a/packages/e2e-playwright/tests/card/legacyInputMode/card.legacyInputMode.spec.ts +++ b/packages/e2e-playwright/tests/e2e/card/legacyInputMode/card.legacyInputMode.spec.ts @@ -1,10 +1,9 @@ -import { test, expect } from '../../../pages/cards/card.fixture'; - -test('#1 By default expect all securedFields to have inputs with type="text" & inputmode="numeric"', async ({ cardPage }) => { - const { card } = cardPage; - - await card.isComponentVisible(); +import { test, expect } from '../../../../fixtures/card.fixture'; +import { URL_MAP } from '../../../../fixtures/URL_MAP'; +import { getStoryUrl } from '../../../utils/getStoryUrl'; +test('#1 By default expect all securedFields to have inputs with type="text" & inputmode="numeric"', async ({ card }) => { + await card.goto(URL_MAP.card); const panInputType = await card.cardNumberInput.getAttribute('type'); await expect(panInputType).toEqual('text'); @@ -24,10 +23,15 @@ test('#1 By default expect all securedFields to have inputs with type="text" & i await expect(cvcInputMode).toEqual('numeric'); }); -test('#2 Set legacyInputMode and expect all securedFields to have inputs with type="tel"', async ({ cardLegacyInputModePage }) => { - const { card, page } = cardLegacyInputModePage; - - await card.isComponentVisible(); +test('#2 Set legacyInputMode and expect all securedFields to have inputs with type="tel"', async ({ page, card }) => { + await card.goto( + getStoryUrl({ + baseUrl: URL_MAP.card, + componentConfig: { + legacyInputMode: true + } + }) + ); const panInputType = await card.cardNumberInput.getAttribute('type'); await expect(panInputType).toEqual('tel'); diff --git a/packages/e2e-playwright/tests/e2e/customCard/customCard.spec.ts b/packages/e2e-playwright/tests/e2e/customCard/customCard.spec.ts new file mode 100644 index 000000000..afeeb18bb --- /dev/null +++ b/packages/e2e-playwright/tests/e2e/customCard/customCard.spec.ts @@ -0,0 +1,12 @@ +import { test, expect } from '../../../fixtures/customCard.fixture'; +import { PAYMENT_RESULT, REGULAR_TEST_CARD, TEST_CVC_VALUE, TEST_DATE_VALUE } from '../../utils/constants'; + +test.describe('Custom Card - Standard flow', () => { + test('should fill in card fields and complete the payment', async ({ customCard }) => { + await customCard.typeCardNumber(REGULAR_TEST_CARD); + await customCard.typeExpiryDate(TEST_DATE_VALUE); + await customCard.typeCvc(TEST_CVC_VALUE); + await customCard.pay(); + await expect(customCard.paymentResult).toContainText(PAYMENT_RESULT.authorised); + }); +}); diff --git a/packages/e2e-playwright/tests/e2e/directDebit/bacsDD/bacsDD.spec.ts b/packages/e2e-playwright/tests/e2e/directDebit/bacsDD/bacsDD.spec.ts new file mode 100644 index 000000000..1774953fa --- /dev/null +++ b/packages/e2e-playwright/tests/e2e/directDebit/bacsDD/bacsDD.spec.ts @@ -0,0 +1,108 @@ +import { test } from '@playwright/test'; + +test('should make a bacs direct debit payment', async () => { + // fill in the info + // no further edit +}); + +test('should make a bacs direct debit payment by filling in Bacs details, check "Edit" phase and then complete payment', async () => { + // fill in the info + // Start, allow time to load + // await start(t, 2000, TEST_SPEED); + // + // await t + // // Select Bacs PM + // .click('.adyen-checkout__payment-method--directdebit_GB') + // + // /** + // * Fill phase 1 form + // */ + // // Check holderName doesn't have a readonly attribute + // .expect(nonEditableHolderName.exists) + // .notOk() + // + // // Fill fields + // .typeText(holderName, 'S Dooley') + // .typeText('.adyen-checkout__bacs--bank-account-number', '40308669') + // .typeText('.adyen-checkout__bacs--bank-location-id', '560036') + // .typeText('.adyen-checkout__bacs-input--shopper-email', 'da@ddog.co.uk') + // + // // Expect to see green "valid" ticks + // .expect(validTicks.filterVisible().exists) + // .ok() + // + // // Click Pay + // .click(payButton) + // // Expect errors + // .expect(Selector('.adyen-checkout__field--error').exists) + // .ok() + // + // // Click checkboxes (in reality click their labels - for some reason clicking the actual checkboxes takes ages) + // .click(checkboxLabel.nth(0)) + // // .expect(checkboxAmount.checked).ok() + // .click(checkboxLabel.nth(1)) + // // .expect(checkboxAccount.checked).ok() + // + // // Click Pay + // .click(payButton) + // // Expect no errors + // .expect(Selector('.adyen-checkout__field--error').exists) + // .notOk() + // + // // Expect dropin to be valid + // .expect(getIsValid('dropin')) + // .eql(true) + // + // /** + // * Form in phase 2 + // */ + // // Check holderName is now readonly + // .expect(nonEditableHolderName.exists) + // .ok() + // + // // Expect not to see green "valid" ticks (should be display:none) + // .expect(validTicks.filterHidden().exists) + // .ok() + // + // // Check we now have an Edit button + // .expect(editButton.exists) + // .ok() + // + // // Click Edit + // .click(editButton) + // + // /** + // * Form in phase 1 again + // */ + // // Check holderName doesn't have a readonly attribute + // .expect(nonEditableHolderName.exists) + // .notOk() + // + // // Change holder name + // .typeText(holderName, 'David Archer', { replace: true }) + // + // // Click Pay + // .click(payButton) + // + // /** + // * Form in phase 2 again + // */ + // // Click Pay + // .click(payButton) + // + // /** + // * Final phase: voucher action has been handled + // */ + // .wait(3000) + // .expect(voucher.exists) + // .ok() + // .expect(voucherButton.exists) + // .ok(); + // edit +}); +test('should not submit the bacs direct debit payment due to wrong email', async () => { + // wrong email +}); +test('should not submit the bacs direct debit payment due to not check the consent checkbox', async () => { + // not check the consent +}); diff --git a/packages/e2e-playwright/tests/e2e/dropin/sessions/dropin.sessions.spec.ts b/packages/e2e-playwright/tests/e2e/dropin/sessions/dropin.sessions.spec.ts new file mode 100644 index 000000000..8a4e72f55 --- /dev/null +++ b/packages/e2e-playwright/tests/e2e/dropin/sessions/dropin.sessions.spec.ts @@ -0,0 +1,59 @@ +import dotenv from 'dotenv'; +import { test, cardInDropin, expect } from '../../../../fixtures/dropin.fixture'; +import { PAYMENT_RESULT, REGULAR_TEST_CARD, TEST_CVC_VALUE, TEST_DATE_VALUE } from '../../../utils/constants'; +import { URL_MAP } from '../../../../fixtures/URL_MAP'; +dotenv.config(); +const apiVersion = Number(process.env.API_VERSION.substring(1)); + +cardInDropin('#1 Should succeed in making a payment', async ({ dropinWithSession, card }) => { + await dropinWithSession.goto(URL_MAP.dropinWithSession); + await dropinWithSession.selectPaymentMethod('scheme'); + + await card.isComponentVisible(); + await card.typeCardNumber(REGULAR_TEST_CARD); + await card.typeExpiryDate(TEST_DATE_VALUE); + await card.typeCvc(TEST_CVC_VALUE); + + await dropinWithSession.pay(); + + await expect(dropinWithSession.paymentResult).toContainText(PAYMENT_RESULT.success); +}); + +if (apiVersion >= 70) { + cardInDropin( + '#2 Should succeed in making a zeroAuth payment since there is no conflicting configuration on the session', + async ({ dropinWithSession, card }) => { + await dropinWithSession.goto(URL_MAP.dropinSessions_zeroAuthCard_success); + await dropinWithSession.selectPaymentMethod('scheme'); + + await card.isComponentVisible(); + await card.typeCardNumber(REGULAR_TEST_CARD); + await card.typeExpiryDate(TEST_DATE_VALUE); + await card.typeCvc(TEST_CVC_VALUE); + + // A payment successfully registered as a zero-auth payment should have a "save details" button instead of "pay" + await dropinWithSession.saveDetails(); + + await expect(dropinWithSession.paymentResult).toContainText(PAYMENT_RESULT.detailsSaved); + } + ); + + cardInDropin( + '#3 Should fail in making a zeroAuth payment since there is conflicting configuration on the session', + async ({ dropinWithSession, card }) => { + await dropinWithSession.goto(URL_MAP.dropinSessions_zeroAuthCard_fail); + await dropinWithSession.selectPaymentMethod('scheme'); + await card.isComponentVisible(); + await card.typeCardNumber(REGULAR_TEST_CARD); + await card.typeExpiryDate(TEST_DATE_VALUE); + await card.typeCvc(TEST_CVC_VALUE); + + // A payment successfully registered as a zero-auth payment should have a "save details" button instead of "pay" + await dropinWithSession.saveDetails(); + + await expect(dropinWithSession.paymentResult).toContainText(PAYMENT_RESULT.fail); + } + ); +} else { + test(`Skipping tests #2 & #3 for Dropin Sessions flow since api version is too low (v${apiVersion})`, async t => {}); +} diff --git a/packages/e2e-playwright/tests/e2e/dropin/storedCard/stored-card-ach.spec.ts b/packages/e2e-playwright/tests/e2e/dropin/storedCard/stored-card-ach.spec.ts new file mode 100644 index 000000000..ec62e718b --- /dev/null +++ b/packages/e2e-playwright/tests/e2e/dropin/storedCard/stored-card-ach.spec.ts @@ -0,0 +1,7 @@ +import { test } from '@playwright/test'; + +test.describe('Stored ach card', () => { + test('should make a successful payment', async () => { + // One click, pay button + }); +}); diff --git a/packages/e2e-playwright/tests/e2e/dropin/storedCard/stored-card-amex.spec.ts b/packages/e2e-playwright/tests/e2e/dropin/storedCard/stored-card-amex.spec.ts new file mode 100644 index 000000000..d85edc905 --- /dev/null +++ b/packages/e2e-playwright/tests/e2e/dropin/storedCard/stored-card-amex.spec.ts @@ -0,0 +1,61 @@ +import { test } from '@playwright/test'; + +test.describe('Stored Amex card - cvc required', () => { + test('#1 Can fill out the cvc fields in the stored card and make a successful payment', async () => { + // Wait for field to appear in DOM + // await cardPage.cvcHolder(); + // + // // handler for alert that's triggered on successful payment + // await t.setNativeDialogHandler(() => true); + // + // // expiry date field is readonly + // await t.expect(cardPage.storedCardExpiryDate.withAttribute('disabled').exists).ok(); + // + // await cardPage.cardUtils.fillCVC(t, TEST_CVC_VALUE, 'add', 0); + // + // // click pay + // await t.click(cardPage.payButton).expect(cardPage.cvcLabelTextError.exists).notOk().wait(3000); + // + // // Check the value of the alert text + // const history = await t.getNativeDialogHistory(); + // await t.expect(history[0].text).eql('Authorised', { timeout: 5000 }); + }); + + test('#2 Pressing pay without filling the cvc should generate a translated error ("empty")', async () => { + // Wait for field to appear in DOM + // await cardPage.cvcHolder(); + // + // // click pay + // await t + // .click(cardPage.payButton) + // .expect(cardPage.cvcLabelTextError.exists) + // .ok() + // // with text + // .expect(cardPage.cvcErrorText.withExactText(EMPTY_FIELD).exists) + // .ok(); + }); + + test('#3 A storedCard with no expiry date field still can be used for a successful payment', async () => { + // use window.cardConfig = { + // expiryMonth: null, + // expiryYear: null + // }; + // Wait for field to appear in DOM + // await cardPage.cvcHolder(); + // + // // handler for alert that's triggered on successful payment + // await t.setNativeDialogHandler(() => true); + // + // // expiry date field is not visible + // await t.expect(cardPage.storedCardExpiryDate.exists).notOk(); + // + // await cardPage.cardUtils.fillCVC(t, TEST_CVC_VALUE, 'add', 0); + // + // // click pay + // await t.click(cardPage.payButton).expect(cardPage.cvcLabelTextError.exists).notOk().wait(3000); + // + // // Check the value of the alert text + // const history = await t.getNativeDialogHistory(); + // await t.expect(history[0].text).eql('Authorised', { timeout: 5000 }); + }); +}); diff --git a/packages/e2e-playwright/tests/e2e/dropin/storedCard/stored-card-bancontact.spec.ts b/packages/e2e-playwright/tests/e2e/dropin/storedCard/stored-card-bancontact.spec.ts new file mode 100644 index 000000000..7f4ba9d34 --- /dev/null +++ b/packages/e2e-playwright/tests/e2e/dropin/storedCard/stored-card-bancontact.spec.ts @@ -0,0 +1,7 @@ +import { test } from '@playwright/test'; + +test.describe('Stored Bancontact card', () => { + test('should make a successful payment', async () => { + // One click Pay button + }); +}); diff --git a/packages/e2e-playwright/tests/e2e/dropin/storedCard/stored-card-maestro.spec.ts b/packages/e2e-playwright/tests/e2e/dropin/storedCard/stored-card-maestro.spec.ts new file mode 100644 index 000000000..c8d965fd2 --- /dev/null +++ b/packages/e2e-playwright/tests/e2e/dropin/storedCard/stored-card-maestro.spec.ts @@ -0,0 +1,9 @@ +import { test } from '@playwright/test'; + +test.describe('Stored Maestro card - cvc optional', () => { + // When user do not fill in the cvc + test('should make a successful payment without the cvc code', async () => {}); + // When user fills in the cvc + test('should make a successful payment after filling in the correct 3ds challenge password', async () => {}); + test('should decline the payment after filling in the wrong 3ds challenge password', async () => {}); +}); diff --git a/packages/e2e-playwright/tests/e2e/dropin/storedCard/stored-card-mc.spec.ts b/packages/e2e-playwright/tests/e2e/dropin/storedCard/stored-card-mc.spec.ts new file mode 100644 index 000000000..60ed8f16e --- /dev/null +++ b/packages/e2e-playwright/tests/e2e/dropin/storedCard/stored-card-mc.spec.ts @@ -0,0 +1,61 @@ +import { test } from '@playwright/test'; + +test.describe('Stored master card - cvc required', () => { + test('#1 Can fill out the cvc fields in the stored card and make a successful payment', async () => { + // Wait for field to appear in DOM + // await cardPage.cvcHolder(); + // + // // handler for alert that's triggered on successful payment + // await t.setNativeDialogHandler(() => true); + // + // // expiry date field is readonly + // await t.expect(cardPage.storedCardExpiryDate.withAttribute('disabled').exists).ok(); + // + // await cardPage.cardUtils.fillCVC(t, TEST_CVC_VALUE, 'add', 0); + // + // // click pay + // await t.click(cardPage.payButton).expect(cardPage.cvcLabelTextError.exists).notOk().wait(3000); + // + // // Check the value of the alert text + // const history = await t.getNativeDialogHistory(); + // await t.expect(history[0].text).eql('Authorised', { timeout: 5000 }); + }); + + test('#2 Pressing pay without filling the cvc should generate a translated error ("empty")', async () => { + // Wait for field to appear in DOM + // await cardPage.cvcHolder(); + // + // // click pay + // await t + // .click(cardPage.payButton) + // .expect(cardPage.cvcLabelTextError.exists) + // .ok() + // // with text + // .expect(cardPage.cvcErrorText.withExactText(EMPTY_FIELD).exists) + // .ok(); + }); + + test('#3 A storedCard with no expiry date field still can be used for a successful payment', async () => { + // use window.cardConfig = { + // expiryMonth: null, + // expiryYear: null + // }; + // Wait for field to appear in DOM + // await cardPage.cvcHolder(); + // + // // handler for alert that's triggered on successful payment + // await t.setNativeDialogHandler(() => true); + // + // // expiry date field is not visible + // await t.expect(cardPage.storedCardExpiryDate.exists).notOk(); + // + // await cardPage.cardUtils.fillCVC(t, TEST_CVC_VALUE, 'add', 0); + // + // // click pay + // await t.click(cardPage.payButton).expect(cardPage.cvcLabelTextError.exists).notOk().wait(3000); + // + // // Check the value of the alert text + // const history = await t.getNativeDialogHistory(); + // await t.expect(history[0].text).eql('Authorised', { timeout: 5000 }); + }); +}); diff --git a/packages/e2e-playwright/tests/e2e/dropin/storedCard/stored-card-visa.spec.ts b/packages/e2e-playwright/tests/e2e/dropin/storedCard/stored-card-visa.spec.ts new file mode 100644 index 000000000..16c870881 --- /dev/null +++ b/packages/e2e-playwright/tests/e2e/dropin/storedCard/stored-card-visa.spec.ts @@ -0,0 +1,61 @@ +import { test } from '@playwright/test'; + +test.describe('Stored visa card - cvc required', () => { + test('#1 Can fill out the cvc fields in the stored card and make a successful payment', async () => { + // Wait for field to appear in DOM + // await cardPage.cvcHolder(); + // + // // handler for alert that's triggered on successful payment + // await t.setNativeDialogHandler(() => true); + // + // // expiry date field is readonly + // await t.expect(cardPage.storedCardExpiryDate.withAttribute('disabled').exists).ok(); + // + // await cardPage.cardUtils.fillCVC(t, TEST_CVC_VALUE, 'add', 0); + // + // // click pay + // await t.click(cardPage.payButton).expect(cardPage.cvcLabelTextError.exists).notOk().wait(3000); + // + // // Check the value of the alert text + // const history = await t.getNativeDialogHistory(); + // await t.expect(history[0].text).eql('Authorised', { timeout: 5000 }); + }); + + test('#2 Pressing pay without filling the cvc should generate a translated error ("empty")', async () => { + // Wait for field to appear in DOM + // await cardPage.cvcHolder(); + // + // // click pay + // await t + // .click(cardPage.payButton) + // .expect(cardPage.cvcLabelTextError.exists) + // .ok() + // // with text + // .expect(cardPage.cvcErrorText.withExactText(EMPTY_FIELD).exists) + // .ok(); + }); + + test('#3 A storedCard with no expiry date field still can be used for a successful payment', async () => { + // use window.cardConfig = { + // expiryMonth: null, + // expiryYear: null + // }; + // Wait for field to appear in DOM + // await cardPage.cvcHolder(); + // + // // handler for alert that's triggered on successful payment + // await t.setNativeDialogHandler(() => true); + // + // // expiry date field is not visible + // await t.expect(cardPage.storedCardExpiryDate.exists).notOk(); + // + // await cardPage.cardUtils.fillCVC(t, TEST_CVC_VALUE, 'add', 0); + // + // // click pay + // await t.click(cardPage.payButton).expect(cardPage.cvcLabelTextError.exists).notOk().wait(3000); + // + // // Check the value of the alert text + // const history = await t.getNativeDialogHistory(); + // await t.expect(history[0].text).eql('Authorised', { timeout: 5000 }); + }); +}); diff --git a/packages/e2e-playwright/tests/e2e/issuerList/issuer-list.spec.ts b/packages/e2e-playwright/tests/e2e/issuerList/issuer-list.spec.ts new file mode 100644 index 000000000..8346733b3 --- /dev/null +++ b/packages/e2e-playwright/tests/e2e/issuerList/issuer-list.spec.ts @@ -0,0 +1,24 @@ +import { test, expect } from '../../../fixtures/issuer-list.fixture'; + +test.describe('Online banking PL', () => { + test('should select highlighted issuer, update pay button label, and see the expected data in the payment request', async ({ + page, + onlineBankingPL + }) => { + await onlineBankingPL.selectHighlightedIssuer('BLIK'); + await expect(onlineBankingPL.payButton).toHaveText('Continue to BLIK'); + + await onlineBankingPL.selectHighlightedIssuer('e-transfer Pocztowy24'); + await expect(onlineBankingPL.payButton).toHaveText('Continue to e-transfer Pocztowy24'); + await expect(onlineBankingPL.highlightedIssuerButtonGroup.getByRole('button', { pressed: true })).toHaveText('e-transfer Pocztowy24'); + const requestPromise = page.waitForRequest(request => request.url().includes('/payments') && request.method() === 'POST'); + + await onlineBankingPL.pay(); + const request = await requestPromise; + + expect(await request.postDataJSON().paymentMethod).toMatchObject({ + type: 'onlineBanking_PL', + issuer: '141' + }); + }); +}); diff --git a/packages/e2e-playwright/tests/e2e/openInvoices/afterpay.spec.ts b/packages/e2e-playwright/tests/e2e/openInvoices/afterpay.spec.ts new file mode 100644 index 000000000..e9d456081 --- /dev/null +++ b/packages/e2e-playwright/tests/e2e/openInvoices/afterpay.spec.ts @@ -0,0 +1,20 @@ +import { test } from '../../../fixtures/openInvoices.fixture'; + +test('should make an AfterPay payment', async () => { + // Fill in the form data + // Check the consent checkbox + // Submit the payment + // Expect success payment +}); + +test('should not submit an AfterPay payment if the form in not valid', async () => { + // Fill in the wrong form data + // Click pay button + // Expect error +}); + +test('should not submit an AfterPay payment if the consent checkbox is not checked', async () => { + // Fill in the wrong form data + // Click pay button + // Expect error +}); diff --git a/packages/e2e-playwright/tests/e2e/openInvoices/ratepay.spec.ts b/packages/e2e-playwright/tests/e2e/openInvoices/ratepay.spec.ts new file mode 100644 index 000000000..9b0334917 --- /dev/null +++ b/packages/e2e-playwright/tests/e2e/openInvoices/ratepay.spec.ts @@ -0,0 +1,54 @@ +import { test } from '../../../fixtures/openInvoices.fixture'; + +const getComponentData = () => { + return globalThis.component.data; +}; + +const mockAddressGermany = { + city: 'City', + country: 'DE', + houseNumberOrName: '123', + postalCode: '12345', + stateOrProvince: 'N/A', + street: 'Street' +}; + +test('should make a Ratepay payment', async () => { + // Fill in the form data + // Check the consent checkbox + // Submit the payment + // Expect success payment + // const checkboxLabelGender = Selector('.ratepay-direct-field .adyen-checkout__field--gender .adyen-checkout__radio_group__label'); + // const payButton = Selector('.ratepay-direct-field adyen-checkout__button--pay'); + // + // // Opens dropdown + // await t + // .typeText('.ratepay-direct-field .adyen-checkout__input--firstName', 'First') + // .typeText('.ratepay-direct-field .adyen-checkout__input--lastName', 'Last') + // // Click checkbox (in reality click its label - for some reason clicking the actual checkboxes takes ages) + // .click(checkboxLabelGender.nth(0)) + // .typeText('.ratepay-direct-field .adyen-checkout__input--dateOfBirth', '01011990', { caretPos: 1 }) + // .typeText('.ratepay-direct-field .adyen-checkout__input--shopperEmail', 'test@test.com') + // .typeText('.ratepay-direct-field .adyen-checkout__input--telephoneNumber', '612345678') + // .typeText('.ratepay-direct-field .adyen-checkout__iban-input__owner-name', 'A. Schneider') + // .typeText('.ratepay-direct-field .adyen-checkout__iban-input__iban-number', 'DE87123456781234567890') + // .typeText('.ratepay-direct-field .adyen-checkout__input--street', mockAddressGermany.street) + // .typeText('.ratepay-direct-field .adyen-checkout__input--houseNumberOrName', mockAddressGermany.houseNumberOrName) + // .typeText('.ratepay-direct-field .adyen-checkout__input--city', mockAddressGermany.city) + // .typeText('.ratepay-direct-field .adyen-checkout__input--postalCode', mockAddressGermany.postalCode) + // // Can't use the checkboxLabelGender trick to speed up the click 'cos this label contains a link - so use a Selector with a timeout + // .click(payButton) + // .wait(5000); +}); + +test('should not submit a Ratepay payment if the form in not valid', async () => { + // Fill in the wrong form data + // Click pay button + // Expect error +}); + +test('should not submit a Ratepay payment if the consent checkbox is not checked', async () => { + // Fill in the wrong form data + // Click pay button + // Expect error +}); diff --git a/packages/e2e-playwright/tests/e2e/openInvoices/riverty.spec.ts b/packages/e2e-playwright/tests/e2e/openInvoices/riverty.spec.ts new file mode 100644 index 000000000..672515379 --- /dev/null +++ b/packages/e2e-playwright/tests/e2e/openInvoices/riverty.spec.ts @@ -0,0 +1,22 @@ +import { test } from '../../../fixtures/openInvoices.fixture'; + +test.describe('Test Riverty Component', () => { + test('should make a Riverty payment', async () => { + // Fill in the form data + // Check the consent checkbox + // Submit the payment + // Expect success payment + }); + + test('should not submit a Riverty payment if the form in not valid', async () => { + // Fill in the wrong form data + // Click pay button + // Expect error + }); + + test('should not submit a Riverty payment if the consent checkbox is not checked', async () => { + // Fill in the wrong form data + // Click pay button + // Expect error + }); +}); diff --git a/packages/e2e-playwright/tests/redirects/redirects.spec.ts b/packages/e2e-playwright/tests/e2e/redirects/redirects.spec.ts similarity index 53% rename from packages/e2e-playwright/tests/redirects/redirects.spec.ts rename to packages/e2e-playwright/tests/e2e/redirects/redirects.spec.ts index 342272a33..4859ffcc8 100644 --- a/packages/e2e-playwright/tests/redirects/redirects.spec.ts +++ b/packages/e2e-playwright/tests/e2e/redirects/redirects.spec.ts @@ -1,31 +1,27 @@ -import { test, expect } from '../../pages/redirects/redirect.fixture'; -import { SIMULATION_TYPE_CANCELLATION, SIMULATION_TYPE_EXPIRATION, SIMULATION_TYPE_FAILURE, SIMULATION_TYPE_SUCCESS } from '../../models/redirect'; +import { test, expect } from '../../../fixtures/redirect.fixture'; +import { SIMULATION_TYPE_CANCELLATION, SIMULATION_TYPE_EXPIRATION, SIMULATION_TYPE_FAILURE, SIMULATION_TYPE_SUCCESS } from '../../../models/redirect'; // const WAIT_FOR_SIMULATOR_MS = 2000; -test.describe('Redirects', () => { - test('#1 Should succeed in making an iDeal payment', async ({ redirectPageIdeal }) => { - const { redirectModel, page } = redirectPageIdeal; - - await redirectModel.isComponentVisible(); - +test.describe('Redirects - Ideal', () => { + test('#1 Should succeed in making an iDeal payment', async ({ ideal, page }) => { // press our redirect button - await redirectPageIdeal.redirect(); + await ideal.redirect(); // wait for iDeal redirect page to show "Select your bank" option, then press it - await redirectModel.isSelectYourBankVisible(); + await ideal.isSelectYourBankVisible(); - await redirectModel.selectYourBank(); + await ideal.selectYourBank(); // wait for iDeal redirect page to give option to choose the test bank - await redirectModel.isSelectTestBankVisible(); + await ideal.isSelectTestBankVisible(); - await redirectModel.selectTestBank(); + await ideal.selectTestBank(); // Select chosen simulation - await redirectModel.areSimulationButtonsVisible(); + await ideal.areSimulationButtonsVisible(); - await redirectModel.selectSimulation(SIMULATION_TYPE_SUCCESS); + await ideal.selectSimulation(SIMULATION_TYPE_SUCCESS); // Inspect iDeal simulator page for expected outcome await expect(page.getByText('SUCCESS', { exact: true })).toBeVisible(); @@ -44,28 +40,24 @@ test.describe('Redirects', () => { // await expect(page.locator('#result-message')).toHaveText('Authorised'); }); - test('#2 Should fail in making an iDeal payment', async ({ redirectPageIdeal }) => { - const { redirectModel, page } = redirectPageIdeal; - - await redirectModel.isComponentVisible(); - + test('#2 Should fail in making an iDeal payment', async ({ ideal, page }) => { // press our redirect button - await redirectPageIdeal.redirect(); + await ideal.redirect(); // wait for iDeal redirect page to show "Select your bank" option, then press it - await redirectModel.isSelectYourBankVisible(); + await ideal.isSelectYourBankVisible(); - await redirectModel.selectYourBank(); + await ideal.selectYourBank(); // wait for iDeal redirect page to give option to choose the test bank - await redirectModel.isSelectTestBankVisible(); + await ideal.isSelectTestBankVisible(); - await redirectModel.selectTestBank(); + await ideal.selectTestBank(); // Select chosen simulation - await redirectModel.areSimulationButtonsVisible(); + await ideal.areSimulationButtonsVisible(); - await redirectModel.selectSimulation(SIMULATION_TYPE_FAILURE); + await ideal.selectSimulation(SIMULATION_TYPE_FAILURE); // Inspect iDeal simulator page for expected outcome await expect(page.getByText('FAILURE', { exact: true })).toBeVisible(); @@ -73,28 +65,24 @@ test.describe('Redirects', () => { // translates to a 'Refused' response from the /details call }); - test('#3 Should timeout in making an iDeal payment', async ({ redirectPageIdeal }) => { - const { redirectModel, page } = redirectPageIdeal; - - await redirectModel.isComponentVisible(); - + test('#3 Should timeout in making an iDeal payment', async ({ ideal, page }) => { // press our redirect button - await redirectPageIdeal.redirect(); + await ideal.redirect(); // wait for iDeal redirect page to show "Select your bank" option, then press it - await redirectModel.isSelectYourBankVisible(); + await ideal.isSelectYourBankVisible(); - await redirectModel.selectYourBank(); + await ideal.selectYourBank(); // wait for iDeal redirect page to give option to choose the test bank - await redirectModel.isSelectTestBankVisible(); + await ideal.isSelectTestBankVisible(); - await redirectModel.selectTestBank(); + await ideal.selectTestBank(); // Select chosen simulation - await redirectModel.areSimulationButtonsVisible(); + await ideal.areSimulationButtonsVisible(); - await redirectModel.selectSimulation(SIMULATION_TYPE_EXPIRATION); + await ideal.selectSimulation(SIMULATION_TYPE_EXPIRATION); // Inspect iDeal simulator page for expected outcome await expect(page.getByText('TIMEOUT', { exact: true })).toBeVisible(); @@ -102,28 +90,24 @@ test.describe('Redirects', () => { // translates to a 'Received' response from the /details call }); - test('#4 Should cancel an iDeal payment', async ({ redirectPageIdeal }) => { - const { redirectModel, page } = redirectPageIdeal; - - await redirectModel.isComponentVisible(); - + test('#4 Should cancel an iDeal payment', async ({ ideal, page }) => { // press our redirect button - await redirectPageIdeal.redirect(); + await ideal.redirect(); // wait for iDeal redirect page to show "Select your bank" option, then press it - await redirectModel.isSelectYourBankVisible(); + await ideal.isSelectYourBankVisible(); - await redirectModel.selectYourBank(); + await ideal.selectYourBank(); // wait for iDeal redirect page to give option to choose the test bank - await redirectModel.isSelectTestBankVisible(); + await ideal.isSelectTestBankVisible(); - await redirectModel.selectTestBank(); + await ideal.selectTestBank(); // Select chosen simulation - await redirectModel.areSimulationButtonsVisible(); + await ideal.areSimulationButtonsVisible(); - await redirectModel.selectSimulation(SIMULATION_TYPE_CANCELLATION); + await ideal.selectSimulation(SIMULATION_TYPE_CANCELLATION); // Inspect iDeal simulator page for expected outcome await expect(page.getByText('CANCEL', { exact: true })).toBeVisible(); diff --git a/packages/e2e-playwright/tests/e2e/vouchers/boleto/boleto.spec.ts b/packages/e2e-playwright/tests/e2e/vouchers/boleto/boleto.spec.ts new file mode 100644 index 000000000..766d86fd4 --- /dev/null +++ b/packages/e2e-playwright/tests/e2e/vouchers/boleto/boleto.spec.ts @@ -0,0 +1,75 @@ +import { test } from '@playwright/test'; + +const getComponentData = () => { + return globalThis.boletoInput.data; +}; + +const mockData = { + shopperName: { + firstName: 'Tom', + lastName: 'Jobim' + }, + socialSecurityNumber: '32553325916', + billingAddress: { + country: 'BR', + street: 'Fake street', + houseNumberOrName: '123', + city: 'Sao Paulo', + postalCode: '11111555', + stateOrProvince: 'SP' + }, + shopperEmail: 'shopper@adyen.nl' +}; + +async function fillBoleto(t) { + // await t + // .typeText('.boleto-input .adyen-checkout__field--firstName .adyen-checkout__input', mockData.shopperName.firstName) + // .typeText('.boleto-input .adyen-checkout__field--lastName .adyen-checkout__input', mockData.shopperName.lastName) + // .typeText('.boleto-input .adyen-checkout__field--socialSecurityNumber .adyen-checkout__input', mockData.socialSecurityNumber) + // .typeText('.boleto-input .adyen-checkout__input--street', mockData.billingAddress.street) + // .typeText('.boleto-input .adyen-checkout__input--houseNumberOrName', mockData.billingAddress.houseNumberOrName) + // .typeText('.boleto-input .adyen-checkout__input--city', mockData.billingAddress.city) + // .typeText('.boleto-input .adyen-checkout__input--postalCode', mockData.billingAddress.postalCode) + // .click(Selector('.boleto-input .adyen-checkout__field--stateOrProvince .adyen-checkout__dropdown__button')) + // .click(Selector('.boleto-input [data-value=SP]')); +} + +test('should make a Boleto payment', async () => { + // await fillBoleto(t); + // const stateData = await getComponentData(); + // + // await t + // .expect(stateData.paymentMethod.type) + // .eql('boletobancario') + // .expect(stateData.shopperName) + // .eql(mockData.shopperName) + // .expect(stateData.socialSecurityNumber) + // .eql(mockData.socialSecurityNumber) + // .expect(stateData.billingAddress) + // .eql(mockData.billingAddress) + // .expect(getIsValid('boletoInput')) + // .eql(true); +}); + +test('should not submit a Boleto payment if the form in not valid', async () => {}); + +test('should allow shoppers to send a copy to their email and make a Boleto payment', async () => { + // await fillBoleto(t); + // await t.expect(getIsValid('boletoInput')).eql(true); + // + // await t.click(Selector('.boleto-input .adyen-checkout__field--sendCopyToEmail .adyen-checkout__checkbox')); + // await t.expect(getIsValid('boletoInput')).eql(false); + // + // await t.typeText('.boleto-input .adyen-checkout__input--email', mockData.shopperEmail); + // await t.expect(getIsValid('boletoInput')).eql(true); + // + // const stateData = await getComponentData(); + // + // await t + // .expect(stateData.paymentMethod.type) + // .eql('boletobancario') + // .expect(stateData.shopperEmail) + // .eql(mockData.shopperEmail) + // .expect(getIsValid('boletoInput')) + // .eql(true); +}); diff --git a/packages/e2e-playwright/tests/issuerList/issuer-list.spec.ts b/packages/e2e-playwright/tests/issuerList/issuer-list.spec.ts deleted file mode 100644 index 4ebcab6ea..000000000 --- a/packages/e2e-playwright/tests/issuerList/issuer-list.spec.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { test, expect } from '../../pages/issuerList/issuer-list.fixture'; -import { pressKeyboardToNextItem, pressEnter } from '../utils/keyboard'; - -test.describe('Issuer List', () => { - 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('mTransfer'); // full list - - await issuerList.typeOnSelectorField('Idea'); // filtered content - await expect(issuerList.selectorList).not.toContainText('mTransfer'); - - // select one of the filtered option - await pressKeyboardToNextItem(page); // Arrow down - await pressEnter(page); // Enter key - - await expect(issuerList.submitButton).toHaveText('Continue to Idea Cloud'); - - // 1st press opens the dropdown - await pressKeyboardToNextItem(page); - // 2nd selects next item - await pressKeyboardToNextItem(page); - await pressEnter(page); - - await expect(issuerList.submitButton).toHaveText('Continue to mRaty'); - }); - - test('it should load a default, from the filtered items, when pressing enter', async ({ issuerListPage }) => { - const { issuerList, page } = issuerListPage; - - await issuerList.clickOnSelector(); - await issuerList.typeOnSelectorField('Nest'); - await pressEnter(page); - - await expect(issuerList.submitButton).toHaveText('Continue to Nest Bank'); - }); - - test('it should have the expected data in state, ready for the /payments call', async ({ issuerListPage }) => { - const { issuerList, page } = issuerListPage; - - // Open the drop down and select an item - await issuerList.clickOnSelector(); - await pressKeyboardToNextItem(page); // Arrow down - await pressEnter(page); // Enter key - - let issuerListData = await page.evaluate('window.dotpay.data'); - - // @ts-ignore - const { checkoutAttemptId, ...rest } = issuerListData.paymentMethod; // strip checkoutAttemptId since we can't know its value - - expect(rest).toEqual({ - type: 'dotpay', - issuer: '73' - }); - }); - - test('should select highlighted issuer, update pay button label, and see the expected data in state', async ({ issuerListPage }) => { - const { issuerList, page } = issuerListPage; - - await issuerList.selectHighlightedIssuer('BLIK'); - await expect(issuerList.submitButton).toHaveText('Continue to BLIK'); - - await issuerList.selectHighlightedIssuer('Idea Cloud'); - await expect(issuerList.submitButton).toHaveText('Continue to Idea Cloud'); - - await expect(issuerList.highlightedIssuerButtonGroup.getByRole('button', { pressed: true })).toHaveText('Idea Cloud'); - - let issuerListData = await page.evaluate('window.dotpay.data'); - - // @ts-ignore - const { checkoutAttemptId, ...rest } = issuerListData.paymentMethod; // strip checkoutAttemptId since we can't know its value - - expect(rest).toEqual({ - type: 'dotpay', - issuer: '81' - }); - }); -}); diff --git a/packages/e2e-playwright/tests/openInvoices/riverty.spec.ts b/packages/e2e-playwright/tests/openInvoices/riverty.spec.ts deleted file mode 100644 index 22b14b8b0..000000000 --- a/packages/e2e-playwright/tests/openInvoices/riverty.spec.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { test, expect } from '../../pages/openInvoices/openInvoices.fixture'; - -import LANG from '../../../server/translations/en-US.json'; -import { USER_TYPE_DELAY } from '../utils/constants'; - -const REQUIRED_FIELD = LANG['field.error.required'].replace('%{label}', ''); - -const BILLING_ADDRESS = LANG['billingAddress']; -const DELIVERY_ADDRESS = LANG['deliveryAddress']; - -const EMPTY_POST_CODE = `${REQUIRED_FIELD}${BILLING_ADDRESS} ${LANG['postalCode']}`; -const INVALID_FORMAT_EXPECTS = LANG['invalid.format.expects'].replace(/%{label}|%{format}/g, ''); -const INVALID_POST_CODE = `${BILLING_ADDRESS} ${LANG['postalCode']}${INVALID_FORMAT_EXPECTS}99999`; - -const expectedSRPanelTexts = [ - `${LANG['firstName.invalid']}`, - `${LANG['lastName.invalid']}`, - `${REQUIRED_FIELD}${LANG['dateOfBirth']}`, - `${REQUIRED_FIELD}${LANG['shopperEmail']}`, - `${REQUIRED_FIELD}${LANG['telephoneNumber']}`, - `${REQUIRED_FIELD}${BILLING_ADDRESS} ${LANG['street']}`, - `${REQUIRED_FIELD}${BILLING_ADDRESS} ${LANG['houseNumberOrName']}`, - EMPTY_POST_CODE, - `${REQUIRED_FIELD}${BILLING_ADDRESS} ${LANG['city']}`, - `${REQUIRED_FIELD}${DELIVERY_ADDRESS} ${LANG['deliveryAddress.firstName']}`, - `${REQUIRED_FIELD}${DELIVERY_ADDRESS} ${LANG['deliveryAddress.lastName']}`, - `${REQUIRED_FIELD}${DELIVERY_ADDRESS} ${LANG['street']}`, - `${REQUIRED_FIELD}${DELIVERY_ADDRESS} ${LANG['houseNumberOrName']}`, - `${REQUIRED_FIELD}${DELIVERY_ADDRESS} ${LANG['postalCode']}`, - `${REQUIRED_FIELD}${DELIVERY_ADDRESS} ${LANG['city']}`, - `${LANG['consent.checkbox.invalid']}` -]; - -test.describe('Test Riverty Component', () => { - test('#1 Test how Riverty component handles SRPanel messages', async ({ openInvoicesPage_riverty }) => { - const { openInvoices, page } = openInvoicesPage_riverty; - - await openInvoices.isComponentVisible(); - - await openInvoices.rivertyDeliveryAddressCheckbox.click(); - - await openInvoicesPage_riverty.pay(); - - // Need to wait so that the expected elements can be found by locator.allInnerTexts - await page.waitForTimeout(100); - - // Inspect SRPanel errors: all expected errors, in the expected order - await page - .locator('.adyen-checkout-sr-panel__msg') - .allInnerTexts() - .then(retrievedSRPanelTexts => { - // check we have srPanel errors - expect(retrievedSRPanelTexts.length).toBeGreaterThan(0); - - // check individual messages - retrievedSRPanelTexts.forEach((retrievedText, index) => { - // KEEP - handy for debugging test - // console.log('\n### riverty.spec:::: retrievedText', retrievedText); - // console.log('### riverty.spec:::: expectedTexts', expectedSRPanelTexts[index]); - - expect(retrievedText).toContain(expectedSRPanelTexts[index]); - }); - }); - - // KEEP - handy for debugging test - // await expect(page.getByText('Enter your first name-sr', { exact: true })).toBeVisible(); - }); - - test('#2 Test how Riverty component handles SRPanel messages, specifically a postal code with an invalid format', async ({ - openInvoicesPage_riverty - }) => { - const { openInvoices, page } = openInvoicesPage_riverty; - - await openInvoices.isComponentVisible(); - - await openInvoices.rivertyDeliveryAddressCheckbox.click(); - - expectedSRPanelTexts.splice(7, 1, INVALID_POST_CODE); - - await openInvoices.riverty - .getByRole('group', { name: 'Billing address' }) - .getByLabel('Postal code') - // .locator('.adyen-checkout__field--postalCode .adyen-checkout__input--postalCode') - .type('3', { delay: USER_TYPE_DELAY }); - - await openInvoicesPage_riverty.pay(); // first click need to trigger blur event on postCode field - await openInvoicesPage_riverty.pay(); // second click to trigger validation - - // Need to wait so that the expected elements can be found by locator.allInnerTexts - await page.waitForTimeout(100); - - // Inspect SRPanel errors: all expected errors, in the expected order - await page - .locator('.adyen-checkout-sr-panel__msg') - .allInnerTexts() - .then(retrievedSRPanelTexts => { - // check we have srPanel errors - expect(retrievedSRPanelTexts.length).toBeGreaterThan(0); - - // check individual messages - retrievedSRPanelTexts.forEach((retrievedText, index) => { - // KEEP - handy for debugging test - // console.log('\n### riverty.spec:::: retrievedText', retrievedText); - // console.log('### riverty.spec:::: expectedTexts', expectedSRPanelTexts[index]); - - expect(retrievedText).toContain(expectedSRPanelTexts[index]); - }); - }); - - // Restore array to start state - expectedSRPanelTexts.splice(7, 1, EMPTY_POST_CODE); - }); -}); diff --git a/packages/e2e/tests/cards/avs/avs.clientScripts.js b/packages/e2e-playwright/tests/ui/card/avs/avs.clientScripts.js similarity index 100% rename from packages/e2e/tests/cards/avs/avs.clientScripts.js rename to packages/e2e-playwright/tests/ui/card/avs/avs.clientScripts.js diff --git a/packages/e2e/tests/cards/avs/avs.partial.clientScripts.js b/packages/e2e-playwright/tests/ui/card/avs/avs.partial.clientScripts.js similarity index 100% rename from packages/e2e/tests/cards/avs/avs.partial.clientScripts.js rename to packages/e2e-playwright/tests/ui/card/avs/avs.partial.clientScripts.js diff --git a/packages/e2e-playwright/tests/ui/card/avs/avs.partial.spec.ts b/packages/e2e-playwright/tests/ui/card/avs/avs.partial.spec.ts new file mode 100644 index 000000000..2d839f591 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/card/avs/avs.partial.spec.ts @@ -0,0 +1,40 @@ +import { test } from '@playwright/test'; + +const INVALID_POSTALCODE = 'aaaaaaaaaa'; + +test.describe('Card with Partial AVS', () => { + test.beforeEach(async () => { + // await t.navigateTo(CARDS_URL); + }); + + test('should validate Postal Code if property data.billingAddress.country is provided', async t => { + // use CLIENTSCRIPT_PARTIAL_AVS_WITH_COUNTRY + // await start(t, 2000, TEST_SPEED); + // + // await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); + // await cardPage.cardUtils.fillDateAndCVC(t); + // + // await t.typeText(cardPage.postalCodeInput, INVALID_POSTALCODE); + // await t.click(cardPage.payButton); + // + // await t.expect(cardPage.postalCodeErrorText.innerText).contains('Invalid format. Expected format: 12345678 or 12345-678'); + }); + + test('should not validate Postal Code if property data.billingAddress.country is not provided', async t => { + // use CLIENTSCRIPT_PARTIAL_AVS_WITHOUT_COUNTRY + // await t.setNativeDialogHandler(() => true); + // await start(t, 2000, TEST_SPEED); + // + // await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); + // await cardPage.cardUtils.fillDateAndCVC(t); + // + // await t.typeText(cardPage.postalCodeInput, INVALID_POSTALCODE); + // await t.click(cardPage.payButton); + // + // await t.wait(3000); + // + // // Check the value of the alert text + // const history = await t.getNativeDialogHistory(); + // await t.expect(history[0].text).eql('Authorised'); + }); +}); diff --git a/packages/e2e-playwright/tests/ui/card/avs/avs.spec.ts b/packages/e2e-playwright/tests/ui/card/avs/avs.spec.ts new file mode 100644 index 000000000..936d59f19 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/card/avs/avs.spec.ts @@ -0,0 +1,103 @@ +import { test } from '@playwright/test'; + +const iframeSelector = '.card-field iframe'; + +const mockAddress = { + city: 'Cupertino', + country: 'US', + houseNumberOrName: '1', + postalCode: '90210', + stateOrProvince: 'CA', + street: 'Infinite Loop' +}; + +test.describe('Card with AVS', () => { + test.beforeEach(async () => { + // await t.navigateTo(CARDS_URL); + // use avs.clientScripts.js + }); + + test('Fill in card with AVS', async () => { + // Start, allow time for iframes to load + // await start(t, 2000, TEST_SPEED); + // + // // Fill card field with non-korean card + // await cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); + // + // // Complete form + // await cardUtils.fillDateAndCVC(t); + // + // // Fill billing address + // await t + // .click(Selector('.adyen-checkout__field--country .adyen-checkout__dropdown__button')) + // .click(Selector('.adyen-checkout__field--country .adyen-checkout__dropdown__list').child(1)) + // .typeText('.adyen-checkout__input--street', mockAddress.street) + // .typeText('.adyen-checkout__input--houseNumberOrName', mockAddress.houseNumberOrName) + // .typeText('.adyen-checkout__input--city', mockAddress.city) + // .typeText('.adyen-checkout__input--postalCode', mockAddress.postalCode) + // .click(Selector('.adyen-checkout__field--stateOrProvince .adyen-checkout__dropdown__button')) + // .click(Selector('.adyen-checkout__field--stateOrProvince .adyen-checkout__dropdown__list').child(1)); + // + // // Expect card to now be valid + // await t.expect(getIsValid()).eql(true); + }); + + test('Fill in card with AVS with an optional field', async () => { + // Start, allow time for iframes to load + // await start(t, 2000, TEST_SPEED); + // + // // Fill card field with non-korean card + // await cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); + // + // // Complete form + // await cardUtils.fillDateAndCVC(t); + // + // // Fill billing address + // await t + // .click(Selector('.adyen-checkout__field--country .adyen-checkout__dropdown__button')) + // .click(Selector('.adyen-checkout__field--country .adyen-checkout__dropdown__list').child(1)) + // .typeText('.adyen-checkout__input--street', mockAddress.street) + // .typeText('.adyen-checkout__input--city', mockAddress.city) + // .typeText('.adyen-checkout__input--postalCode', mockAddress.postalCode) + // .click(Selector('.adyen-checkout__field--stateOrProvince .adyen-checkout__dropdown__button')) + // .click(Selector('.adyen-checkout__field--stateOrProvince .adyen-checkout__dropdown__list').child(1)); + // + // // Expect card to now be valid + // await t.expect(getIsValid()).eql(true); + }); + + test('Switch between addresses with different formats', async () => { + // Start, allow time for iframes to load + // await start(t, 2000, TEST_SPEED); + // + // // Fill card field with non-korean card + // await cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); + // + // // Complete form + // await cardUtils.fillDateAndCVC(t); + // + // // Fill billing address + // await t + // .click(Selector('.adyen-checkout__field--country .adyen-checkout__dropdown__button')) + // .click(Selector('.adyen-checkout__field--country .adyen-checkout__dropdown__list').child(1)) + // .typeText('.adyen-checkout__input--street', mockAddress.street) + // .typeText('.adyen-checkout__input--city', mockAddress.city) + // .typeText('.adyen-checkout__input--postalCode', mockAddress.postalCode) + // .click(Selector('.adyen-checkout__field--stateOrProvince .adyen-checkout__dropdown__button')) + // .click(Selector('.adyen-checkout__field--stateOrProvince .adyen-checkout__dropdown__list').child(0)); + // + // // Expect card to now be valid + // await t.expect(getIsValid()).eql(true); + // + // // Switch country to NL + // await t + // .click(Selector('.adyen-checkout__field--country .adyen-checkout__dropdown__button')) + // .click(Selector('.adyen-checkout__field--country .adyen-checkout__dropdown__list').child(0)); + // + // // Expect card to now be invalid + // await t.expect(getIsValid()).eql(false); + // + // // Expect to not have a State field + // await t.expect(Selector('.adyen-checkout__field--stateOrProvince').exists).notOk(); + }); +}); diff --git a/packages/e2e/tests/cards/binLookup/ui/handlingErrors/unsupportedCardErrors.clientScripts.js b/packages/e2e-playwright/tests/ui/card/binLookup/handlingErrors/unsupportedCardErrors.clientScripts.js similarity index 100% rename from packages/e2e/tests/cards/binLookup/ui/handlingErrors/unsupportedCardErrors.clientScripts.js rename to packages/e2e-playwright/tests/ui/card/binLookup/handlingErrors/unsupportedCardErrors.clientScripts.js diff --git a/packages/e2e-playwright/tests/ui/card/binLookup/mocks/binLookup.cvc.hidden.spec.ts b/packages/e2e-playwright/tests/ui/card/binLookup/mocks/binLookup.cvc.hidden.spec.ts new file mode 100644 index 000000000..442bf2d63 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/card/binLookup/mocks/binLookup.cvc.hidden.spec.ts @@ -0,0 +1,32 @@ +import { test } from '@playwright/test'; + +const cvcSpan = '.card-field .adyen-checkout__field__cvc'; +const optionalCVCSpan = '.card-field .adyen-checkout__field__cvc--optional'; +const cvcLabel = '.card-field .adyen-checkout__label__text'; +const brandingIcon = '.card-field .adyen-checkout__card__cardNumber__brandIcon'; +const iframeSelector = '.card-field iframe'; + +test.describe('Testing a card for a response that should indicate hidden cvc', () => { + // Use config from: binLookup.mocks.clientScripts.js + test( + 'Test card has hidden cvc field ' + 'then complete date and see card is valid ' + ' then delete card number and see card reset', + async () => { + // Start, allow time for iframes to load + // Expect generic card icon: expect(brandingIcon.getAttribute('src')).contains('nocard.svg') + // Expect visible cvc field: expect(cvcSpan.filterVisible().exists) + // Expect with regular text: expect(cvcLabel.withExactText('Security code').exists) + // Expect not optional: expect(optionalCVCSpan.exists).notOk(); + // Fill in Unknown card: cardUtils.fillCardNumber(t, BCMC_CARD); + // Expect bcmc card icon: expect(brandingIcon.getAttribute('src')).contains('bcmc.svg') + // Expect hidden cvc field: expect(cvcSpan.filterHidden().exists) + // Fill date: cardUtils.fillDate(t); + // Expect card is valid: t.expect(getIsValid('card')).eql(true); + // Delete number: cardUtils.deleteCardNumber(t); + // Expect generic card icon: expect(brandingIcon.getAttribute('src')).contains('nocard.svg') + // Expect visible cvc field: expect(cvcSpan.filterVisible().exists) + // Expect with regular text: expect(cvcLabel.withExactText('Security code').exists) + // Expect not optional: expect(optionalCVCSpan.exists).notOk(); + // Expect card is not valid: t.expect(getIsValid('card')).eql(false); + } + ); +}); diff --git a/packages/e2e-playwright/tests/ui/card/binLookup/mocks/binLookup.cvc.optional.spec.ts b/packages/e2e-playwright/tests/ui/card/binLookup/mocks/binLookup.cvc.optional.spec.ts new file mode 100644 index 000000000..b4fb6ab80 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/card/binLookup/mocks/binLookup.cvc.optional.spec.ts @@ -0,0 +1,104 @@ +import { test } from '@playwright/test'; +import { BIN_LOOKUP_VERSION } from '../../../../utils/constants'; + +const cvcSpan = '.card-field .adyen-checkout__field__cvc'; +const optionalCVCSpan = '.card-field .adyen-checkout__field__cvc--optional'; +const cvcLabel = '.card-field .adyen-checkout__label__text'; +const brandingIcon = '.card-field .adyen-checkout__card__cardNumber__brandIcon'; +const requestURL = `https://checkoutshopper-test.adyen.com/checkoutshopper/${BIN_LOOKUP_VERSION}/bin/binLookup?token=${process.env.CLIENT_KEY}`; + +/** + * 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 + */ +// use the following mock: +const mockedResponse = { + brands: [ + { + brand: 'bcmc', // keep as a recognised card brand (bcmc) until we have a genuine card - to avoid logo loading errors + cvcPolicy: 'optional', + enableLuhnCheck: true, + showExpiryDate: true, + supported: true + } + ], + issuingCountryCode: 'US', + requestId: null +}; + +const iframe = '.card-field iframe'; + +test.describe('Testing a card, as detected by a mock/binLookup, for a response that should indicate optional cvc)', () => { + // use config from binLookup.mocks.clientScripts.js + // use mock + test( + 'Test card has optional cvc field ' + 'then complete date and see card is valid ' + ' then delete card number and see card reset', + async t => { + // Start, allow time for iframes to load + // 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(); + // + // // Unknown card + // await cardUtils.fillCardNumber(t, UNKNOWN_BIN_CARD); + // + // await t + // // bcmc card icon + // .expect(brandingIcon.getAttribute('src')) + // .contains('bcmc.svg') + // + // // visible cvc field + // .expect(cvcSpan.filterVisible().exists) + // .ok() + // + // // with "optional" text + // .expect(cvcLabel.withExactText('Security code (optional)').exists) + // .ok() + // // and optional class + // .expect(optionalCVCSpan.exists) + // .ok(); + // + // // Fill date + // await cardUtils.fillDate(t); + // + // // 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') + // + // // 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(); + // + // await t.expect(getIsValid('card')).eql(false); + } + ); +}); diff --git a/packages/e2e/tests/cards/binLookup/ui/mocks/binLookup.mocks.clientScripts.js b/packages/e2e-playwright/tests/ui/card/binLookup/mocks/binLookup.mocks.clientScripts.js similarity index 100% rename from packages/e2e/tests/cards/binLookup/ui/mocks/binLookup.mocks.clientScripts.js rename to packages/e2e-playwright/tests/ui/card/binLookup/mocks/binLookup.mocks.clientScripts.js diff --git a/packages/e2e/tests/cards/binLookup/ui/mocks/plcc.clientScripts.js b/packages/e2e-playwright/tests/ui/card/binLookup/mocks/plcc.clientScripts.js similarity index 100% rename from packages/e2e/tests/cards/binLookup/ui/mocks/plcc.clientScripts.js rename to packages/e2e-playwright/tests/ui/card/binLookup/mocks/plcc.clientScripts.js diff --git a/packages/e2e-playwright/tests/ui/card/binLookup/mocks/plcc.expiryDate.spec.ts b/packages/e2e-playwright/tests/ui/card/binLookup/mocks/plcc.expiryDate.spec.ts new file mode 100644 index 000000000..8bdd0a5ea --- /dev/null +++ b/packages/e2e-playwright/tests/ui/card/binLookup/mocks/plcc.expiryDate.spec.ts @@ -0,0 +1,51 @@ +import { test } from '@playwright/test'; + +const brandingIcon = '.card-field .adyen-checkout__card__cardNumber__brandIcon'; + +const dateSpan = '.card-field .adyen-checkout__card__exp-date__input'; + +const iframeSelector = '.card-field iframe'; + +test.describe('Testing a PLCC, for a response that should indicate hidden expiryDate field', () => { + // use config from plcc.clientScripts.js + test( + 'Test plcc card hides date field ' + 'then complete date and see card is valid ' + ' then delete card number and see card reset', + async t => { + // Start, allow time for iframes to load + // Generic card + // await t.expect(brandingIcon.getAttribute('src')).contains('nocard.svg'); + // + // // Unknown card + // await cardUtils.fillCardNumber(t, SYNCHRONY_PLCC_NO_LUHN); + // + // await t + // // hidden date field + // .expect(dateSpan.filterHidden().exists) + // .ok(); + // + // // Is not valid + // await t.expect(getIsValid('card')).eql(false); + // + // // Fill 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') + // + // // visible date field + // .expect(dateSpan.filterVisible().exists) + // .ok(); + // + // await t.expect(getIsValid('card')).eql(false); + } + ); +}); diff --git a/packages/e2e-playwright/tests/ui/card/binLookup/mocks/plcc.luhn.spec.ts b/packages/e2e-playwright/tests/ui/card/binLookup/mocks/plcc.luhn.spec.ts new file mode 100644 index 000000000..6f52ec49f --- /dev/null +++ b/packages/e2e-playwright/tests/ui/card/binLookup/mocks/plcc.luhn.spec.ts @@ -0,0 +1,60 @@ +import { test } from '@playwright/test'; +import { BIN_LOOKUP_VERSION } from '../../../../utils/constants'; + +const brandingIcon = '.card-field .adyen-checkout__card__cardNumber__brandIcon'; +const requestURL = `https://checkoutshopper-test.adyen.com/checkoutshopper/${BIN_LOOKUP_VERSION}/bin/binLookup?token=${process.env.CLIENT_KEY}`; + +/** + * 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 + */ +// use the mock on requestURL, POST +const mockedResponse = { + brands: [ + { + brand: 'bcmc', // keep as a recognised card brand (bcmc) until we have a genuine plcc - to avoid logo loading errors + cvcPolicy: 'required', + enableLuhnCheck: false, + showExpiryDate: true, + supported: true + } + ], + issuingCountryCode: 'US', + requestId: null +}; + +const iframeSelector = '.card-field iframe'; + +test.describe('Testing a PLCC, as detected by a mock/binLookup, for a response that should indicate luhn check is not required)', () => { + // use mock, use plcc.clientScripts.js config + test('Test plcc card becomes valid with number that fails luhn check ', async () => { + // Start, allow time for iframes to load + // generic card + // await t.expect(brandingIcon.getAttribute('src')).contains('nocard.svg'); + // + // // Unknown card + // await cardUtils.fillCardNumber(t, FAILS_LUHN_CARD); + // + // await t + // // bcmc card icon + // .expect(brandingIcon.getAttribute('src')) + // .contains('bcmc.svg'); + // + // // Fill cvc + // await cardUtils.fillDateAndCVC(t); + // + // // 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'); + // + // await t.expect(getIsValid('card')).eql(false); + }); +}); diff --git a/packages/e2e/tests/cards/binLookup/ui/panLength/mocks.js b/packages/e2e-playwright/tests/ui/card/binLookup/panLength/mocks.js similarity index 100% rename from packages/e2e/tests/cards/binLookup/ui/panLength/mocks.js rename to packages/e2e-playwright/tests/ui/card/binLookup/panLength/mocks.js diff --git a/packages/e2e/tests/cards/binLookup/ui/panLength/panLength.avs.clientScripts.js b/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.avs.clientScripts.js similarity index 100% rename from packages/e2e/tests/cards/binLookup/ui/panLength/panLength.avs.clientScripts.js rename to packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.avs.clientScripts.js 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 new file mode 100644 index 000000000..2737152b9 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.focus.avs.spec.ts @@ -0,0 +1,44 @@ +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; +}; + +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 + + 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(); + }); + + 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(); + }); +}); 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 new file mode 100644 index 000000000..dc2efe098 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.focus.kcp.spec.ts @@ -0,0 +1,58 @@ +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; +}; + +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 () => { + // 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(); + }); + + 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(); + }); +}); 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 new file mode 100644 index 000000000..bcbf193a2 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.focus.regular.spec.ts @@ -0,0 +1,260 @@ +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(); + }); + 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('#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('#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('#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('#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('#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( + "#8 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(); + } + ); + + 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'); + }); +}); 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 new file mode 100644 index 000000000..307545d2a --- /dev/null +++ b/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.focus.ssn.spec.ts @@ -0,0 +1,44 @@ +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; +}; + +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(); + }); + + 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(); + }); +}); diff --git a/packages/e2e/tests/cards/binLookup/ui/panLength/panLength.kcp.clientScripts.js b/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.kcp.clientScripts.js similarity index 100% rename from packages/e2e/tests/cards/binLookup/ui/panLength/panLength.kcp.clientScripts.js rename to packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.kcp.clientScripts.js diff --git a/packages/e2e/tests/cards/binLookup/ui/panLength/panLength.regular.clientScripts.js b/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.regular.clientScripts.js similarity index 100% rename from packages/e2e/tests/cards/binLookup/ui/panLength/panLength.regular.clientScripts.js rename to packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.regular.clientScripts.js diff --git a/packages/e2e/tests/cards/binLookup/ui/panLength/panLength.ssn.clientScripts.js b/packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.ssn.clientScripts.js similarity index 100% rename from packages/e2e/tests/cards/binLookup/ui/panLength/panLength.ssn.clientScripts.js rename to packages/e2e-playwright/tests/ui/card/binLookup/panLength/panLength.ssn.clientScripts.js diff --git a/packages/e2e-playwright/tests/ui/card/branding/branding.bcmc.spec.ts b/packages/e2e-playwright/tests/ui/card/branding/branding.bcmc.spec.ts new file mode 100644 index 000000000..eb7ebe6fe --- /dev/null +++ b/packages/e2e-playwright/tests/ui/card/branding/branding.bcmc.spec.ts @@ -0,0 +1,25 @@ +import { test } from '@playwright/test'; + +test.describe('Testing branding, as detected by /binLookup, for bcmc (hidden cvc field)', () => { + // use the config: + // window.cardConfig = { + // type: 'scheme', + // brands: ['mc', 'visa', 'amex', 'bcmc'] + // }; + + test('Test card is valid with bcmc details (no cvc) ' + 'then test it is invalid (& brand reset) when number deleted', async t => { + // Wait for Card to load + // expect(brandingIcon.getAttribute('src')).contains('nocard.svg') + // cardUtils.fillCardNumber(t, BCMC_CARD); BCMC_CARD = '6703444444444449' + // cardUtils.fillDate(t, TEST_DATE_VALUE); TEST_DATE_VALUE = '03/30'; + // expect bcmc card icon brandingIcon.getAttribute('src') = 'bcmc.svg' + // expect hidden cvc field + // expect the card is valid + // deleteCardNumber + // Card is reset - expect the branding icon src = 'nocard.svg' + // expect visible cvc field + // expect cvc label = Security code + // expect cvc is NOT optional - optionalCVCSpan does not exist + // expect the card is NOT valid + }); +}); diff --git a/packages/e2e-playwright/tests/ui/card/branding/branding.exotic.spec.ts b/packages/e2e-playwright/tests/ui/card/branding/branding.exotic.spec.ts new file mode 100644 index 000000000..d616cfa79 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/card/branding/branding.exotic.spec.ts @@ -0,0 +1,19 @@ +import { test } from '@playwright/test'; + +test.describe('Testing card component dedicated to a single, "exotic", txVariant that we don\'t recognise internally but that is recognised by /binLookup', () => { + // use the config: + // window.cardConfig = { + // type: 'scheme', + // brands: ['korean_local_card'] + // }; + + test('Input details for "exotic" brand - card should still become valid, with no errors', async t => { + // Wait for Card to load + // cardUtils.fillCardNumber(t, KOREAN_TEST_CARD); + // Blur the card number input field + // Expect no errors Selector('.adyen-checkout__field--error') not exists + // Fill in date cardUtils.fillDate(t, TEST_DATE_VALUE); + // Fill cardUtils.fillCVC(t, TEST_CVC_VALUE); + // Expect the card is valid + }); +}); diff --git a/packages/e2e-playwright/tests/ui/card/branding/card.branding.spec.ts b/packages/e2e-playwright/tests/ui/card/branding/card.branding.spec.ts new file mode 100644 index 000000000..c2c687245 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/card/branding/card.branding.spec.ts @@ -0,0 +1,169 @@ +import { test as base, expect } from '@playwright/test'; +import { MAESTRO_CARD, TEST_CVC_VALUE, TEST_DATE_VALUE } from '../../../utils/constants'; +import LANG from '../../../../../server/translations/en-US.json'; +import { Card } from '../../../../models/card'; +import { getStoryUrl } from '../../../utils/getStoryUrl'; +import { URL_MAP } from '../../../../fixtures/URL_MAP'; + +const CVC_LABEL = LANG['creditCard.securityCode.label']; +const CVC_LABEL_OPTIONAL = LANG['creditCard.securityCode.label.optional']; + +type Fixture = { + cardBrandingPage: Card; +}; + +const test = base.extend({ + cardBrandingPage: async ({ page }, use) => { + const cardPage = new Card(page); + const componentConfig = { + brands: ['mc', 'visa', 'amex', 'maestro', 'bcmc'] + }; + await cardPage.goto(getStoryUrl({ baseUrl: URL_MAP.card, componentConfig })); + await use(cardPage); + } +}); + +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 }) => { + // generic card + let brandingIconSrc = await cardBrandingPage.brandingIcon.getAttribute('src'); + expect(brandingIconSrc).toContain('nocard.svg'); + + // visible & required cvc field + await expect(cardBrandingPage.cvcField).toBeVisible(); + await expect(cardBrandingPage.cvcField).toHaveClass(/adyen-checkout__field__cvc/); // Note: "relaxed" regular expression to detect one class amongst several that are set on the element + await expect(cardBrandingPage.cvcField).not.toHaveClass(/adyen-checkout__field__cvc--optional/); + + // with regular text + await expect(cardBrandingPage.cvcLabelText).toHaveText(CVC_LABEL); + + // Partially fill card field with digits that will be recognised as maestro + await cardBrandingPage.typeCardNumber('670'); + + // maestro card icon + brandingIconSrc = await cardBrandingPage.brandingIcon.getAttribute('src'); + expect(brandingIconSrc).toContain('maestro.svg'); + + // with "optional" text + await expect(cardBrandingPage.cvcLabelText).toHaveText(CVC_LABEL_OPTIONAL); + // and optional class + await expect(cardBrandingPage.cvcField).toHaveClass(/adyen-checkout__field__cvc--optional/); + + // Add digit so card is recognised as bcmc + await cardBrandingPage.cardNumberInput.press('End'); /** NOTE: how to add text at end */ + await cardBrandingPage.typeCardNumber('3'); + + // bcmc card icon + brandingIconSrc = await cardBrandingPage.brandingIcon.getAttribute('src'); + expect(brandingIconSrc).toContain('bcmc.svg'); + + // hidden cvc field + await expect(cardBrandingPage.cvcField).not.toBeVisible(); + + // Delete number + await cardBrandingPage.deleteCardNumber(); + + // Card is reset + brandingIconSrc = await cardBrandingPage.brandingIcon.getAttribute('src'); + expect(brandingIconSrc).toContain('nocard.svg'); + + // Visible cvc field + await expect(cardBrandingPage.cvcField).toBeVisible(); + + // with regular text + await expect(cardBrandingPage.cvcLabelText).toHaveText(CVC_LABEL); + + // and required cvc field + await expect(cardBrandingPage.cvcField).toHaveClass(/adyen-checkout__field__cvc/); + await expect(cardBrandingPage.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 ({ page, cardBrandingPage }) => { + // Maestro + await cardBrandingPage.typeCardNumber(MAESTRO_CARD); + await cardBrandingPage.typeExpiryDate(TEST_DATE_VALUE); + + // maestro card icon + let brandingIconSrc = await cardBrandingPage.brandingIcon.getAttribute('src'); + expect(brandingIconSrc).toContain('maestro.svg'); + + // with "optional" text + await expect(cardBrandingPage.cvcLabelText).toHaveText(CVC_LABEL_OPTIONAL); + // and optional class + await expect(cardBrandingPage.cvcField).toHaveClass(/adyen-checkout__field__cvc--optional/); + + // Is valid + let cardValid = await page.evaluate('window.component.isValid'); + await expect(cardValid).toEqual(true); + + await cardBrandingPage.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.component.isValid'); + await expect(cardValid).toEqual(true); + + // Delete number + await cardBrandingPage.deleteCardNumber(); + + // Card is reset to generic card + brandingIconSrc = await cardBrandingPage.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.component.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 ({ page, cardBrandingPage }) => { + // Maestro + await cardBrandingPage.typeCardNumber(MAESTRO_CARD); + await cardBrandingPage.typeExpiryDate(TEST_DATE_VALUE); + + // Partial cvc + await cardBrandingPage.typeCvc('73'); + + // Force blur event to fire + await cardBrandingPage.cardNumberLabelElement.click(); + + // Wait for UI to render + await page.waitForTimeout(300); + + // Is not valid + let cardValid = await page.evaluate('window.component.isValid'); + await expect(cardValid).toEqual(false); + + // Complete cvc + await cardBrandingPage.cvcInput.press('End'); /** NOTE: how to add text at end */ + await cardBrandingPage.typeCvc('7'); + + // Is valid + cardValid = await page.evaluate('window.component.isValid'); + await expect(cardValid).toEqual(true); + + await cardBrandingPage.deleteCvc(); + + // Is valid + cardValid = await page.evaluate('window.component.isValid'); + await expect(cardValid).toEqual(true); + } + ); +}); diff --git a/packages/e2e-playwright/tests/ui/card/cardMocks.js b/packages/e2e-playwright/tests/ui/card/cardMocks.js new file mode 100644 index 000000000..12959987d --- /dev/null +++ b/packages/e2e-playwright/tests/ui/card/cardMocks.js @@ -0,0 +1,40 @@ +import { BIN_LOOKUP_VERSION } from '../../utils/constants'; + +export const binLookupUrl = `https://checkoutshopper-test.adyen.com/checkoutshopper/${BIN_LOOKUP_VERSION}/bin/binLookup?token=${process.env.CLIENT_KEY}`; + +/** + * Functionality for mocking a /binLookup API response via testcafe's fixture.requestHooks() + * + * @param requestURL + * @param mockedResponse + * @returns {RequestMock} + */ +export const getBinLookupMock = (requestURL, mockedResponse) => { + // return RequestMock() + // .onRequestTo(request => { + // return request.url === requestURL && request.method === 'post'; + // }) + // .respond( + // (req, res) => { + // const body = JSON.parse(req.body); + // mockedResponse.requestId = body.requestId; + // res.setBody(mockedResponse); + // }, + // 200, + // { + // 'Access-Control-Allow-Origin': BASE_URL + // } + // ); +}; + +// For the tests as a whole - throw an error if SDK binLookup mocking is turned on +export const checkSDKMocking = () => { + if (globalThis.mockBinCount > 0) { + throw new Error('SDK bin mocking is turned on - this will affect/break the tests - so turn it off in triggerBinLookup.ts'); + } +}; + +// For individual test suites (perhaps being run in isolation) - provide a way to ensure SDK bin mocking is turned off +export const turnOffSDKMocking = () => { + globalThis.mockBinCount = 0; +}; diff --git a/packages/e2e-playwright/tests/ui/card/cardNumber/card.number.spec.ts b/packages/e2e-playwright/tests/ui/card/cardNumber/card.number.spec.ts new file mode 100644 index 000000000..4578c1c96 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/card/cardNumber/card.number.spec.ts @@ -0,0 +1,33 @@ +import { test, expect } from '../../../../fixtures/card.fixture'; +import LANG from '../../../../../server/translations/en-US.json'; +import { URL_MAP } from '../../../../fixtures/URL_MAP'; + +const PAN_ERROR_NOT_VALID = LANG['cc.num.902']; +const PAN_ERROR_EMPTY = LANG['cc.num.900']; +const PAN_ERROR_NOT_COMPLETE = LANG['cc.num.901']; + +test('#2 PAN that consists of the same digit (but passes luhn) causes an error', async ({ page, card }) => { + await card.goto(URL_MAP.card); + await card.typeCardNumber('3333 3333 3333 3333 3333'); + await card.pay(); + + 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 ({ card }) => { + await card.goto(URL_MAP.card); + await card.pay(); + + 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 ({ card }) => { + await card.goto(URL_MAP.card); + await card.typeCardNumber('4'); + await card.pay(); + + await expect(card.cardNumberErrorElement).toBeVisible(); + await expect(card.cardNumberErrorElement).toHaveText(PAN_ERROR_NOT_COMPLETE); +}); diff --git a/packages/e2e-playwright/tests/ui/card/cardNumber/unsupported.card.number.spec.ts b/packages/e2e-playwright/tests/ui/card/cardNumber/unsupported.card.number.spec.ts new file mode 100644 index 000000000..bf42e2415 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/card/cardNumber/unsupported.card.number.spec.ts @@ -0,0 +1,45 @@ +import { test } from '../../../../fixtures/card.fixture'; + +test( + '#1 Enter number of unsupported card, ' + 'then check UI shows an error ' + 'then PASTE supported card & check UI error is cleared', + async () => { + // Wait for field to appear in DOM + // Fill card field with unsupported number + // Test UI shows "Unsupported card" error + // Past card field with supported number + // Test UI shows "Unsupported card" error has gone + } +); + +test( + '#2 Enter number of unsupported card, ' + + 'then check UI shows an error ' + + 'then press the Pay button ' + + 'then check UI shows more errors ' + + 'then PASTE supported card & check PAN UI errors are cleared whilst others persist', + async () => { + // Wait for field to appear in DOM + // Fill card field with unsupported number + // Test UI shows "Unsupported card" error + // Click Pay (which will call showValidation on all fields) + // Past card field with supported number + // Test UI shows "Unsupported card" error has gone + // PAN error cleared but other errors persist + } +); + +test('#3 Enter number of unsupported card, ' + 'then check UI shows an error ' + 'then PASTE card not in db check UI error is cleared', async () => { + // Wait for field to appear in DOM + // Fill card field with unsupported number + // Test UI shows "Unsupported card" error + // Past card field with supported number + // Test UI shows "Unsupported card" error has gone +}); + +test('#4 Enter number of unsupported card, ' + 'then check UI shows an error ' + 'then delete PAN & check UI error is cleared', async () => { + // Wait for field to appear in DOM + // Fill card field with unsupported number + // Test UI shows "Unsupported card" error + // delete card number + // Test UI shows "Unsupported card" error has gone +}); diff --git a/packages/e2e-playwright/tests/ui/card/dualBranding/binLookup.branding.reset.spec.ts b/packages/e2e-playwright/tests/ui/card/dualBranding/binLookup.branding.reset.spec.ts new file mode 100644 index 000000000..f521f8ac6 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/card/dualBranding/binLookup.branding.reset.spec.ts @@ -0,0 +1,132 @@ +import { test } from '@playwright/test'; + +// @ts-ignore +const getPropFromPMData = prop => window.dropin.dropinRef.state.activePaymentMethod.formatData().paymentMethod[prop]; + +test.describe('Testing Card component, in Dropin, resetting brand after failed binLookup', () => { + // use the config: + // window.cardConfig = { + // type: 'scheme', + // brands: ['mc', 'visa', 'amex', 'cartebancaire'] + // }; + // + // window.dropinConfig = { + // showStoredPaymentMethods: false, // hide stored PMs so credit card is first on list + // paymentMethodsConfiguration: { + // card: { brands: ['mc', 'amex', 'visa', 'cartebancaire', 'bcmc', 'maestro'], _disableClickToPay: true } + // } + // }; + // + // window.mainConfiguration = { + // removePaymentMethods: ['paywithgoogle', 'applepay', 'clicktopay'] + // }; + + test( + '#1 Fill in regular MC card then ' + + 'check that a brand has been set on PM data, then' + + 'paste in a card unrecognised by binLookup, ' + + 'check that the brand has been reset on paymentMethod data and ' + + 'that the internal regex have recognised the unrecognised card as Visa', + async () => { + // Start, allow time to load + // await cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); // mc + // await t.expect(getPropFromPMData('brand')).eql('mc'); + // Paste number not recognised by binLookup: paste into card number field UNKNOWN_VISA_CARD; + // Brand property in the PM data should be reset: t.expect(getPropFromPMData('brand')).eql(undefined); + // expect visa card icon + } + ); + + test( + '#2 Fill in regular MC card then ' + + 'check that a brand has been set on PM data, then' + + 'delete digits and , ' + + 'check that the brand has been reset on paymentMethod data ', + async () => { + // Start, allow time to load + // card.fillCardNumber(t, REGULAR_TEST_CARD); // mc + // Expect mc brand property in the PM data: expect(getPropFromPMData('brand')).eql('mc') + // Delete number: cardUtils.deleteCardNumber(t); + // Expect brand property in the PM data should be reset: expect(getPropFromPMData('brand')).eql(undefined); + // Expect generic card icon: expect(brandingIcon.getAttribute('alt')).contains('card') + } + ); + + /** + * DUAL BRANDING RESETS (similar to those done in bancontact.dualbranding.reset.test, but applied to the regular card component, in Dropin) + */ + test( + '#3 Fill in dual branded card then ' + + 'check no sorting has occurred to place bcmc first, then' + + 'ensure only generic card logo shows after deleting digits', + async () => { + // Start, allow time to load + // cardUtils.fillCardNumber(t, BCMC_CARD); // dual branded with maestro + // Expect Maestro first, Bcmc second + // expect(dualBrandingIconHolderActive.exists) + // expect(dualBrandingIconHolderActive.find('img').nth(0).getAttribute('data-value')).eql('maestro') + // expect(dualBrandingIconHolderActive.find('img').nth(1).getAttribute('data-value')).eql('bcmc'); + // await cardUtils.deleteCardNumber(t); + // Expect generic card icon: expect(brandingIcon.getAttribute('alt')).contains('card') + } + ); + + test( + '#4 Fill in dual branded card then ' + + 'select bcmc then' + + 'ensure cvc field is hidden' + + 'ensure only generic card logo shows after deleting digits and ' + + 'that the brand has been reset on paymentMethod data ' + + ' and the cvc field is visble again', + async () => { + // Start, allow time to load + // cardUtils.fillCardNumber(t, BCMC_CARD); // dual branded with maestro + //expect(dualBrandingIconHolderActive.exists) + //expect(dualBrandingIconHolderActive.find('img').nth(0).getAttribute('data-value')).eql('maestro') + //expect(dualBrandingIconHolderActive.find('img').nth(1).getAttribute('data-value')).eql('bcmc'); + // Click BCMC brand icon: t.click(dualBrandingIconHolderActive.find('img').nth(1)); + // Expect a brand property in the PM data: expect(getPropFromPMData('brand')).eql('bcmc'); + // Expect hidden cvc field: expect(cvcSpan.filterHidden().exists).ok(); + // cardUtils.deleteCardNumber(t); + // Expect generic card icon: expect(brandingIcon.getAttribute('alt')).contains('card'); + // Not expect a brand property in the PM data: t.expect(getPropFromPMData('brand')).eql(undefined); + // Expect Visible cvc field: t.expect(cvcSpan.filterVisible().exists).ok(); + } + ); + + test( + '#5 Fill in dual branded card then ' + + 'paste in number not recognised by binLookup (but that internally is recognised as Visa) ' + + 'ensure that Visa logo shows', + async () => { + // Start, allow time to load + // cardUtils.fillCardNumber(t, BCMC_CARD); // dual branded with maestro + // Expect the logo, with the correct order + //expect(dualBrandingIconHolderActive.exists) + //expect(dualBrandingIconHolderActive.find('img').nth(0).getAttribute('data-value')).eql('maestro') + //expect(dualBrandingIconHolderActive.find('img').nth(1).getAttribute('data-value')).eql('bcmc'); + // cardUtils.fillCardNumber(t, UNKNOWN_VISA_CARD, 'paste'); // number not recognised by binLookup + // Expect visa card icon: expect(brandingIcon.getAttribute('alt')).contains('VISA'); + } + ); + + test( + '#6 Fill in dual branded card then ' + + 'select maestro then ' + + 'paste in number not recognised by binLookup (but that internally is recognised as Visa)' + + 'ensure that visa logo shows', + async () => { + // Start, allow time to load + // cardUtils.fillCardNumber(t, BCMC_CARD); // dual branded with maestro + // Expect the logo, with the correct order + //expect(dualBrandingIconHolderActive.exists) + //expect(dualBrandingIconHolderActive.find('img').nth(0).getAttribute('data-value')).eql('maestro') + //expect(dualBrandingIconHolderActive.find('img').nth(1).getAttribute('data-value')).eql('bcmc'); + // Click Maestro brand icon: t.click(dualBrandingIconHolderActive.find('img').nth(0)); + // Expect hidden cvc field: t.expect(cvcSpan.filterHidden().exists).ok(); + // cardUtils.fillCardNumber(t, UNKNOWN_VISA_CARD, 'paste'); // number not recognised by binLookup + // Expect visa card icon: expect(brandingIcon.getAttribute('alt')).contains('VISA'); + // Expect visible cvc field: t.expect(cvcSpan.filterVisible().exists).ok(); + } + ); +}); diff --git a/packages/e2e-playwright/tests/ui/card/dualBranding/dualBranding.spec.ts b/packages/e2e-playwright/tests/ui/card/dualBranding/dualBranding.spec.ts new file mode 100644 index 000000000..6a0f2f009 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/card/dualBranding/dualBranding.spec.ts @@ -0,0 +1,188 @@ +import { test } from '@playwright/test'; + +// selectors: +const dualBrandingIconHolder = '.card-field .adyen-checkout__card__dual-branding__buttons'; +const dualBrandingIconHolderActive = '.card-field .adyen-checkout__card__dual-branding__buttons--active'; +const NOT_SELECTED_CLASS = 'adyen-checkout__card__cardNumber__brandIcon--not-selected'; + +// @ts-ignore +const getPropFromPMData = prop => window.card.formatData().paymentMethod[prop]; + +test.describe('Testing dual branding', () => { + // use config: + // window.cardConfig = { + // type: 'scheme', + // brands: ['mc', 'visa', 'amex', 'cartebancaire', 'star'] + // }; + // + // window.dropinConfig = { + // showStoredPaymentMethods: false, // hide stored PMs so credit card is first on list + // paymentMethodsConfiguration: { + // card: { brands: ['mc', 'amex', 'visa', 'cartebancaire', 'star'], _disableClickToPay: true } + // } + // }; + // + // window.mainConfiguration = { + // removePaymentMethods: ['paywithgoogle', 'applepay', 'clicktopay'] + // }; + + test( + '#1 Fill in card number that will get dual branding result from binLookup, ' + 'then check that the expected icons/buttons are shown', + async () => { + // Start, allow time for iframes to load + // Fill card field with dual branded card (visa/cb): cardUtils.fillCardNumber(t, DUAL_BRANDED_CARD) + // Expect the logo, with the correct order + // expect(dualBrandingIconHolderActive.exists) + // expect(dualBrandingIconHolderActive.find('img').nth(0).getAttribute('data-value')).eql('visa') + // expect(dualBrandingIconHolderActive.find('img').nth(1).getAttribute('data-value')).eql('cartebancaire'); + } + ); + + test( + '#2 Fill in card number that will get dual branding result from binLookup, ' + + 'then complete card without selecting dual brand,' + + 'then check it is valid,' + + 'then check PM data does not have a brand property', + async () => { + // Start, allow time for iframes to load + // Fill card field with dual branded card (visa/cb): cardUtils.fillCardNumber(t, DUAL_BRANDED_CARD); + // Fill date and cvc: cardUtils.fillDateAndCVC(t); + // Expect card to now be valid: t.expect(getIsValid()).eql(true); + // Should not be a brand property in the PM data: t.expect(getPropFromPMData('brand')).eql(undefined); + } + ); + + test( + '#3 Fill in card number that will get dual branding result from binLookup, ' + + 'then complete card,' + + 'then check it is valid,' + + 'then select the dual brands,' + + 'then check PM data does have a corresponding brand property', + async () => { + // Start, allow time for iframes to load + // Fill card field with dual branded card (visa/cb): cardUtils.fillCardNumber(t, DUAL_BRANDED_CARD); + // Fill date and cvc cardUtils.fillDateAndCVC(t); + // Expect card to now be valid: t.expect(getIsValid()).eql(true); + // Click brand icon: click(dualBrandingIconHolderActive.find('img').nth(1)) + // Expect brand value: expect(getPropFromPMData('brand')).eql('cartebancaire') + // Click brand icon: click(dualBrandingIconHolderActive.find('img').nth(0)) + // Expect brand value: expect(getPropFromPMData('brand')).eql('visa') + } + ); + + test( + '#4 Fill in partial card number that will get dual branding result from binLookup, ' + + 'then check that the expected icons/buttons are shown but inactive,' + + 'then complete the number & check that the icons/buttons are active', + async () => { + // Start, allow time for iframes to load + // const firstDigits = DUAL_BRANDED_CARD.substring(0, 11); + // const lastDigits = DUAL_BRANDED_CARD.substring(11, 16); + // Partially fill card field with dual branded card (visa/cb): cardUtils.fillCardNumber(t, firstDigits); + // Expect dualBrandingIconHolder exists + // Expect dualBrandingIconHolderActive exists + // Complete field: cardUtils.fillCardNumber(t, lastDigits); + // Expect expect(dualBrandingIconHolderActive.exists) + } + ); + + test( + '#5 Fill in card number that will get dual branding result from binLookup, ' + + 'then check that the icons/buttons are active,' + + 'then delete the number,' + + 'then check that the icons/buttons have gone', + async () => { + // Start, allow time for iframes to load + // Fill card field with dual branded card (visa/cb): cardUtils.fillCardNumber(t, DUAL_BRANDED_CARD); + // Expect dualBrandingIconHolder exists + // Expect dualBrandingIconHolderActive exists + // cardUtils.deleteCardNumber(t); + // Expect dualBrandingIconHolder not exists + // Expect dualBrandingIconHolderActive not exists + } + ); + + test( + '#6 Fill in card number that will get dual branding result from binLookup, ' + + 'then select one of the dual brands,' + + 'then check the other brand icon is at reduced alpha,' + + 'then repeat with the other icon', + async () => { + // Start, allow time for iframes to load + // Fill card field with dual branded card (visa/cb): cardUtils.fillCardNumber(t, DUAL_BRANDED_CARD); + // click first icon: click(dualBrandingIconHolderActive.find('img').nth(0)) + // first icon SHOULDN'T have the "not selected" class: expect(dualBrandingIconHolderActive.find('img').nth(0).hasClass(NOT_SELECTED_CLASS)).eql(false) + // second icon SHOULD have the "not selected" class: expect(dualBrandingIconHolderActive.find('img').nth(1).hasClass(NOT_SELECTED_CLASS)) + // click second icon: click(dualBrandingIconHolderActive.find('img').nth(1)) + // second icon SHOULDN'T have the "not selected" class expect(dualBrandingIconHolderActive.find('img').nth(1).hasClass(NOT_SELECTED_CLASS)).eql(false) + // first icon SHOULD have the "not selected" class: expect(dualBrandingIconHolderActive.find('img').nth(0).hasClass(NOT_SELECTED_CLASS)) + } + ); + + test( + '#7 Fill in card number that will get single branding result from binLookup, ' + + 'then enter a dual branded card number,' + + 'check both brand icons are at full alpha,' + + 'then click icons and make sure they go to the expected alpha', + async () => { + // Start, allow time for iframes to load + // cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); + // Paste dual branded card (visa/cb) into card field: cardUtils.fillCardNumber(t, DUAL_BRANDED_CARD, 'paste'); + // Expect buttons are active: t.expect(dualBrandingIconHolderActive.exists).ok(); + // first icon SHOULDN'T have the "not selected" class: expect(dualBrandingIconHolderActive.find('img').nth(0).hasClass(NOT_SELECTED_CLASS)).eql(false) + // second icon SHOULDN'T have the "not selected" class: expect(dualBrandingIconHolderActive.find('img').nth(1).hasClass(NOT_SELECTED_CLASS)).eql(false); + // click first icon: click(dualBrandingIconHolderActive.find('img').nth(0)) + // first icon SHOULDN'T have the "not selected" class: expect(dualBrandingIconHolderActive.find('img').nth(0).hasClass(NOT_SELECTED_CLASS)).eql(false) + // second icon SHOULD have the "not selected" class: expect(dualBrandingIconHolderActive.find('img').nth(1).hasClass(NOT_SELECTED_CLASS)).eql(true) + // click second icon: click(dualBrandingIconHolderActive.find('img').nth(1)) + // second icon SHOULDN'T have the "not selected" class: expect(dualBrandingIconHolderActive.find('img').nth(1).hasClass(NOT_SELECTED_CLASS)).eql(false) + // first icon SHOULD have the "not selected" class: expect(dualBrandingIconHolderActive.find('img').nth(0).hasClass(NOT_SELECTED_CLASS)).eql(true); + } + ); + + test( + '#8 Fill in card number that will get single branding result from binLookup, ' + + 'then enter a partial dual branded card number,' + + 'check both brand icons are at reduced alpha,' + + 'complete card number,' + + 'check both brand icons are at full alpha,' + + 'then click icons and make sure they go to the expected alpha', + async () => { + // Start, allow time for iframes to load + // cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); + // firstDigits = DUAL_BRANDED_CARD.substring(0, 11); + // lastDigits = DUAL_BRANDED_CARD.substring(11, 16); + // Paste partial dual branded card (visa/cb) into card field: cardUtils.fillCardNumber(t, firstDigits, 'paste'); + // Check buttons are present but NOT active (which will mean the holding element is at 25% opacity): expect(dualBrandingIconHolder.exists).ok().expect(dualBrandingIconHolderActive.exists).notOk(); + // Complete field: cardUtils.fillCardNumber(t, lastDigits); + // Check buttons are active: t.expect(dualBrandingIconHolderActive.exists).ok(); + // Check icon opacities (should be 100%) + // first icon SHOULDN'T have the "not selected" class: expect(dualBrandingIconHolderActive.find('img').nth(0).hasClass(NOT_SELECTED_CLASS)).eql(false) + // second icon SHOULDN'T have the "not selected" class: expect(dualBrandingIconHolderActive.find('img').nth(1).hasClass(NOT_SELECTED_CLASS)).eql(false); + // Click brand icons + // click first icon: click(dualBrandingIconHolderActive.find('img').nth(0)) + // first icon SHOULDN'T have the "not selected" class: expect(dualBrandingIconHolderActive.find('img').nth(0).hasClass(NOT_SELECTED_CLASS)).eql(false) + // second icon SHOULD have the "not selected" class: expect(dualBrandingIconHolderActive.find('img').nth(1).hasClass(NOT_SELECTED_CLASS)).eql(true) + // click second icon: click(dualBrandingIconHolderActive.find('img').nth(1)) + // second icon SHOULDN'T have the "not selected" class: expect(dualBrandingIconHolderActive.find('img').nth(1).hasClass(NOT_SELECTED_CLASS)).eql(false) + // first icon SHOULD have the "not selected" class: expect(dualBrandingIconHolderActive.find('img').nth(0).hasClass(NOT_SELECTED_CLASS)).eql(true); + } + ); + + test( + '#9 Fill in card number that will get dual branding result from binLookup, ' + + 'but one of the brands should be excluded from the UI, ' + + '(but meaning also that no brand should be set in the PM data), ' + + 'then check it is valid,' + + 'then check PM data does not have a brand property,' + + 'and check there are no dual branding icons/buttons', + async () => { + // Start, allow time for iframes to load + // Fill card field with dual branded card (visa/cb): cardUtils.fillCardNumber(t, DUAL_BRANDED_CARD_EXCLUDED); + // Fill in data and cvc: cardUtils.fillDateAndCVC(t); + // Expect card to now be valid: expect(getIsValid()).eql(true); + // Should not be a brand property in the PM data: t.expect(getPropFromPMData('brand')).eql(undefined); + // Should be no dual branding icons/buttons: expect(dualBrandingIconHolder.exists).notOk().expect(dualBrandingIconHolderActive.exists).notOk(); + } + ); +}); diff --git a/packages/e2e-playwright/tests/card/expiryDate/card.expiryDatePolicies.hidden.spec.ts b/packages/e2e-playwright/tests/ui/card/expiryDate/card.expiryDatePolicies.hidden.spec.ts similarity index 74% rename from packages/e2e-playwright/tests/card/expiryDate/card.expiryDatePolicies.hidden.spec.ts rename to packages/e2e-playwright/tests/ui/card/expiryDate/card.expiryDatePolicies.hidden.spec.ts index 24efe296e..68a1ba48a 100644 --- a/packages/e2e-playwright/tests/card/expiryDate/card.expiryDatePolicies.hidden.spec.ts +++ b/packages/e2e-playwright/tests/ui/card/expiryDate/card.expiryDatePolicies.hidden.spec.ts @@ -1,17 +1,25 @@ -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 '../../../../server/translations/en-US.json'; +import { test, expect } from '../../../../fixtures/card.fixture'; +import LANG from '../../../../../server/translations/en-US.json'; +import { + ENCRYPTED_CARD_NUMBER, + ENCRYPTED_EXPIRY_DATE, + ENCRYPTED_SECURITY_CODE, + SYNCHRONY_PLCC_NO_DATE, + TEST_CVC_VALUE +} from '../../../utils/constants'; +import { getStoryUrl } from '../../../utils/getStoryUrl'; const PAN_ERROR = LANG['cc.num.900']; const DATE_INVALID_ERROR = LANG['cc.dat.912']; const DATE_EMPTY_ERROR = LANG['cc.dat.910']; const CVC_ERROR = LANG['cc.cvc.920']; -test.describe('Test how Card Component handles hidden expiryDate policy', () => { - test('#1 how UI & state respond', async ({ cardExpiryDatePoliciesPage }) => { - const { card, page } = cardExpiryDatePoliciesPage; +const urlNoAutoFocus = '/iframe.html?args=srConfig.moveFocus:!false&globals=&id=cards-card--default&viewMode=story'; +const url = getStoryUrl({ baseUrl: urlNoAutoFocus, componentConfig: { brands: ['mc', 'visa', 'amex', 'synchrony_plcc'] } }); - await card.isComponentVisible(); +test.describe('Test how Card Component handles hidden expiryDate policy', () => { + test('#1 how UI & state respond', async ({ page, card }) => { + await card.goto(url); // Fill number to provoke binLookup response await card.typeCardNumber(SYNCHRONY_PLCC_NO_DATE); @@ -22,7 +30,7 @@ test.describe('Test how Card Component handles hidden expiryDate policy', () => await card.typeCvc(TEST_CVC_VALUE); // Card seen as valid - let cardValid = await page.evaluate('window.card.isValid'); + let cardValid = await page.evaluate('window.component.isValid'); await expect(cardValid).toEqual(true); // Clear number @@ -32,18 +40,16 @@ test.describe('Test how Card Component handles hidden expiryDate policy', () => await expect(card.expiryDateField).toBeVisible(); // Card seen as invalid - cardValid = await page.evaluate('window.card.isValid'); + cardValid = await page.evaluate('window.component.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 + page, + card }) => { - const { card, page } = cardExpiryDatePoliciesPage; - - await card.isComponentVisible(); - - await cardExpiryDatePoliciesPage.pay(); + await card.goto(url); + await card.pay(); // Expect errors in UI await expect(card.cardNumberErrorElement).toBeVisible(); @@ -54,7 +60,7 @@ test.describe('Test how Card Component handles hidden expiryDate policy', () => await expect(card.cvcErrorElement).toHaveText(CVC_ERROR); // Expect errors in state - let cardErrors: any = await page.evaluate('window.card.state.errors'); + let cardErrors: any = await page.evaluate('window.component.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); @@ -67,7 +73,7 @@ test.describe('Test how Card Component handles hidden expiryDate policy', () => // 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'); + cardErrors = await page.evaluate('window.component.state.errors'); await expect(cardErrors[ENCRYPTED_CARD_NUMBER]).toBe(null); await expect(cardErrors[ENCRYPTED_EXPIRY_DATE]).toBe(null); @@ -75,11 +81,8 @@ test.describe('Test how Card Component handles hidden expiryDate policy', () => 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(); - + test('#3 Hidden date field in error does not stop card becoming valid', async ({ page, card }) => { + await card.goto(url); // Card out of date await card.typeExpiryDate('12/90'); @@ -102,11 +105,11 @@ test.describe('Test how Card Component handles hidden expiryDate policy', () => 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'); + let cardValid = await page.evaluate('window.component.isValid'); await expect(cardValid).toEqual(true); // Expect errors in state to remain - let cardErrors: any = await page.evaluate('window.card.state.errors'); + let cardErrors: any = await page.evaluate('window.component.state.errors'); await expect(cardErrors[ENCRYPTED_EXPIRY_DATE]).not.toBe(undefined); // Clear number @@ -118,7 +121,7 @@ test.describe('Test how Card Component handles hidden expiryDate policy', () => await expect(card.expiryDateErrorElement).toHaveText(DATE_INVALID_ERROR); // Card is not valid - cardValid = await page.evaluate('window.card.isValid'); + cardValid = await page.evaluate('window.component.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/ui/card/expiryDate/card.expiryDatePolicies.optional.spec.ts similarity index 79% rename from packages/e2e-playwright/tests/card/expiryDate/card.expiryDatePolicies.optional.spec.ts rename to packages/e2e-playwright/tests/ui/card/expiryDate/card.expiryDatePolicies.optional.spec.ts index 82895e674..fe23a9d45 100644 --- a/packages/e2e-playwright/tests/card/expiryDate/card.expiryDatePolicies.optional.spec.ts +++ b/packages/e2e-playwright/tests/ui/card/expiryDate/card.expiryDatePolicies.optional.spec.ts @@ -1,8 +1,9 @@ -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 '../../../../server/translations/en-US.json'; +import { test, expect } from '../../../../fixtures/card.fixture'; +import { binLookupMock } from '../../../../mocks/binLookup/binLookup.mock'; +import { optionalDateAndCvcMock } from '../../../../mocks/binLookup/binLookup.data'; +import LANG from '../../../../../server/translations/en-US.json'; +import { ENCRYPTED_CARD_NUMBER, ENCRYPTED_EXPIRY_DATE, ENCRYPTED_SECURITY_CODE, REGULAR_TEST_CARD } from '../../../utils/constants'; +import { getStoryUrl } from '../../../utils/getStoryUrl'; const DATE_LABEL = LANG['creditCard.expiryDate.label']; const CVC_LABEL = LANG['creditCard.securityCode.label']; @@ -14,14 +15,14 @@ const DATE_INVALID_ERROR = LANG['cc.dat.912']; const DATE_EMPTY_ERROR = LANG['cc.dat.910']; const CVC_ERROR = LANG['cc.cvc.920']; -test.describe('Test how Card Component handles optional expiryDate policy', () => { - test('#1 how UI & state respond', async ({ cardExpiryDatePoliciesPage }) => { - const { card, page } = cardExpiryDatePoliciesPage; +const urlNoAutoFocus = '/iframe.html?args=srConfig.moveFocus:!false&globals=&id=cards-card--default&viewMode=story'; +const url = getStoryUrl({ baseUrl: urlNoAutoFocus, componentConfig: { brands: ['mc', 'visa', 'amex', 'synchrony_plcc'] } }); +test.describe('Test how Card Component handles optional expiryDate policy', () => { + test('#1 how UI & state respond', async ({ page, card }) => { await binLookupMock(page, optionalDateAndCvcMock); - await card.isComponentVisible(); - + await card.goto(url); // Regular date label await expect(card.expiryDateLabelText).toHaveText(DATE_LABEL); @@ -35,7 +36,7 @@ test.describe('Test how Card Component handles optional expiryDate policy', () = await expect(card.cvcLabelText).toHaveText(CVC_LABEL_OPTIONAL); // Card seen as valid - let cardValid = await page.evaluate('window.card.isValid'); + let cardValid = await page.evaluate('window.component.isValid'); await expect(cardValid).toEqual(true); // Clear number and see UI & state reset @@ -49,17 +50,14 @@ test.describe('Test how Card Component handles optional expiryDate policy', () = await page.waitForTimeout(500); // Card seen as invalid - cardValid = await page.evaluate('window.card.isValid'); + cardValid = await page.evaluate('window.component.isValid'); await expect(cardValid).toEqual(false); }); - test('#2 how securedFields responds', async ({ cardExpiryDatePoliciesPage }) => { - const { card, page } = cardExpiryDatePoliciesPage; - + test('#2 how securedFields responds', async ({ page, card }) => { await binLookupMock(page, optionalDateAndCvcMock); - await card.isComponentVisible(); - + await card.goto(url); // 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'); @@ -87,17 +85,12 @@ test.describe('Test how Card Component handles optional expiryDate policy', () = 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; - + test('#3 validating fields first and then entering PAN should see errors cleared from both UI & state', async ({ page, card }) => { await binLookupMock(page, optionalDateAndCvcMock); - await card.isComponentVisible(); - + await card.goto(url); // press pay to generate errors - await cardExpiryDatePoliciesPage.pay(); + await card.pay(); // Expect errors in UI await expect(card.cardNumberErrorElement).toBeVisible(); @@ -108,7 +101,7 @@ test.describe('Test how Card Component handles optional expiryDate policy', () = await expect(card.cvcErrorElement).toHaveText(CVC_ERROR); // Expect errors in state - let cardErrors: any = await page.evaluate('window.card.state.errors'); + let cardErrors: any = await page.evaluate('window.component.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); @@ -127,19 +120,16 @@ test.describe('Test how Card Component handles optional expiryDate policy', () = await expect(card.cvcErrorElement).not.toBeVisible(); // No errors in state - cardErrors = await page.evaluate('window.card.state.errors'); + cardErrors = await page.evaluate('window.component.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; - + test('#4 date field in error DOES stop card becoming valid', async ({ page, card }) => { await binLookupMock(page, optionalDateAndCvcMock); - await card.isComponentVisible(); - + await card.goto(url); // Card out of date await card.typeExpiryDate('12/90'); @@ -161,7 +151,7 @@ test.describe('Test how Card Component handles optional expiryDate policy', () = await expect(card.expiryDateErrorElement).toHaveText(DATE_INVALID_ERROR); // Card seen as invalid - let cardValid = await page.evaluate('window.card.isValid'); + let cardValid = await page.evaluate('window.component.isValid'); await expect(cardValid).toEqual(false); // Delete erroneous date @@ -171,7 +161,7 @@ test.describe('Test how Card Component handles optional expiryDate policy', () = await page.waitForTimeout(500); // Card now seen as valid - cardValid = await page.evaluate('window.card.isValid'); + cardValid = await page.evaluate('window.component.isValid'); await expect(cardValid).toEqual(true); }); }); diff --git a/packages/e2e-playwright/tests/ui/card/expiryDate/card.minimumExpiryDate.spec.ts b/packages/e2e-playwright/tests/ui/card/expiryDate/card.minimumExpiryDate.spec.ts new file mode 100644 index 000000000..d8072c00b --- /dev/null +++ b/packages/e2e-playwright/tests/ui/card/expiryDate/card.minimumExpiryDate.spec.ts @@ -0,0 +1,69 @@ +import { test } from '@playwright/test'; +import LANG from '../../../../../server/translations/en-US.json'; + +const CARD_TOO_OLD = LANG['cc.dat.912']; +const CARD_TOO_FAR = LANG['cc.dat.913']; +const CARD_EXPIRES_BEFORE = LANG['cc.dat.914']; + +// todo: original test file for reference: https://github.com/Adyen/adyen-web/blob/main/packages/e2e/tests/cards/expiryDate/minimumExpiryDate.test.js +// use config: +// window.cardConfig = { +// type: 'scheme', +// brands: ['mc', 'visa', 'amex', 'synchrony_plcc'], +// minimumExpiryDate: '09/24' +// }; +// description: Testing setting minimumExpiryDate - that it is recognised but doesn't override the other checks on date for a card being too old or too far in the future + +test('#1 With minimumExpiryDate set - input an expiry date that is too old & expect the correct error ', async () => { + // wait for the Date element to present + // fillDate(t, '12/20'); + // Test UI shows "Card too old" error: CARD_TOO_OLD +}); + +test('#2 With minimumExpiryDate set - input an expiry date that is 1 month before it & expect the correct error', async () => { + // wait for the Date element to present + // fillDate(t, '08/24'); + // Test UI shows "Card expires before..." error - CARD_EXPIRES_BEFORE +}); + +test('#3 With minimumExpiryDate set - input an expiry date that is matches it & expect no error ', async () => { + // wait for the Date element to present + // fillDate(t, '09/24'); + // Test UI shows no error +}); + +test('#4 With minimumExpiryDate set - input an expiry date that exceeds it (a bit) & expect no error', async () => { + // wait for the Date element to present + // fillDate(t, '04/25'); + // Test UI shows no error +}); + +test('#5 With minimumExpiryDate set - input an expiry date that is too far in the future, & expect the correct error ', async () => { + // wait for the Date element to present + // fillDate(t, '12/90'); + // Test UI shows "Card too far in the future" error - CARD_TOO_FAR +}); + +test( + '#6 General "date edit" bug: with minimumExpiryDate set - input an expiry date that is matches it & expect no error ' + + 'then edit the date to be before the minimumExpiryDate and expect that to register as an error', + async () => { + // wait for the Date element to present + // fillDate(t, '09/24'); + // Test UI shows no error + // paste new date: await cardUtils.fillDate(t, '08/24', 'paste'); + // Test UI shows "Card expires before..." error - CARD_EXPIRES_BEFORE + } +); + +test( + '#7 General "date edit" bug: input a valid expiry date & expect no error ' + + 'then edit the date to be before invalid and expect that to immediately register as an error', + async () => { + // wait for the Date element to present + // fillDate(t, '04/30'); + // Test UI shows no error + // paste new date cardUtils.fillDate(t, '04/10', 'paste'); + // Test UI shows "Card too old" error- CARD_TOO_OLD + } +); diff --git a/packages/e2e-playwright/tests/card/installments/card.installments.spec.ts b/packages/e2e-playwright/tests/ui/card/installments/card.installments.spec.ts similarity index 73% rename from packages/e2e-playwright/tests/card/installments/card.installments.spec.ts rename to packages/e2e-playwright/tests/ui/card/installments/card.installments.spec.ts index 8c57db563..40991fe8c 100644 --- a/packages/e2e-playwright/tests/card/installments/card.installments.spec.ts +++ b/packages/e2e-playwright/tests/ui/card/installments/card.installments.spec.ts @@ -1,28 +1,34 @@ -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'; +import { test, expect } from '../../../../fixtures/card.fixture'; +import { pressKeyboardToNextItem } from '../../../utils/keyboard'; +import { REGULAR_TEST_CARD, TEST_CVC_VALUE, TEST_DATE_VALUE } from '../../../utils/constants'; +import { getStoryUrl } from '../../../utils/getStoryUrl'; +import { URL_MAP } from '../../../../fixtures/URL_MAP'; + +const componentConfig = { + installmentOptions: { + mc: { + values: [1, 2, 3], + plans: ['regular', 'revolving'] + } + } +}; +const url = getStoryUrl({ baseUrl: URL_MAP.card, componentConfig }); 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(); + test('#1 should not add installments property to payload if one-time payment is selected (default selection)', async ({ card, page }) => { + await card.goto(url); 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'); + const paymentDataInstallments: any = await page.evaluate('window.component.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(); + test('#2 should not add installments property to payload if 1x installment is selected', async ({ card, page }) => { + await card.goto(url); await card.typeCardNumber(REGULAR_TEST_CARD); await card.typeExpiryDate(TEST_DATE_VALUE); @@ -32,14 +38,12 @@ test.describe('Cards (Installments)', () => { await card.installmentsPaymentLabel.click(); // Inspect card.data - const paymentDataInstallments: any = await page.evaluate('window.card.data.installments'); + const paymentDataInstallments: any = await page.evaluate('window.component.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(); + test('#3 should add revolving plan to payload if selected', async ({ card, page }) => { + await card.goto(url); await card.typeCardNumber(REGULAR_TEST_CARD); await card.typeExpiryDate(TEST_DATE_VALUE); @@ -52,15 +56,13 @@ test.describe('Cards (Installments)', () => { await page.waitForTimeout(500); // Inspect card.data - const paymentDataInstallments: any = await page.evaluate('window.card.data.installments'); + const paymentDataInstallments: any = await page.evaluate('window.component.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(); + test('#4 should add installments value property if regular installment > 1 is selected', async ({ card, page }) => { + await card.goto(url); await card.typeCardNumber(REGULAR_TEST_CARD); await card.typeExpiryDate(TEST_DATE_VALUE); @@ -81,21 +83,22 @@ test.describe('Cards (Installments)', () => { await page.waitForTimeout(500); // Inspect card.data - const paymentDataInstallments: any = await page.evaluate('window.card.data.installments'); + const paymentDataInstallments: any = await page.evaluate('window.component.data.installments'); await expect(paymentDataInstallments.value).toEqual(2); }); test('#5 installments with full width dropdown: should add installments value property if regular installment > 1 is selected', async ({ - cardInstallmentsFullWidthPage + card, + page }) => { - const { card, page } = cardInstallmentsFullWidthPage; - - await card.isComponentVisible(); + await card.goto(url); 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); @@ -107,7 +110,7 @@ test.describe('Cards (Installments)', () => { await page.waitForTimeout(500); // Inspect card.data - const paymentDataInstallments: any = await page.evaluate('window.card.data.installments'); + const paymentDataInstallments: any = await page.evaluate('window.component.data.installments'); await expect(paymentDataInstallments.value).toEqual(2); }); }); diff --git a/packages/e2e/tests/cards/kcp/startWithKCP/startWithKCP.clientScripts.js b/packages/e2e-playwright/tests/ui/card/kcp/startWithKCP/startWithKCP.clientScripts.js similarity index 100% rename from packages/e2e/tests/cards/kcp/startWithKCP/startWithKCP.clientScripts.js rename to packages/e2e-playwright/tests/ui/card/kcp/startWithKCP/startWithKCP.clientScripts.js diff --git a/packages/e2e-playwright/tests/ui/card/kcp/startWithKCP/startWithKCP.spec.ts b/packages/e2e-playwright/tests/ui/card/kcp/startWithKCP/startWithKCP.spec.ts new file mode 100644 index 000000000..face759bd --- /dev/null +++ b/packages/e2e-playwright/tests/ui/card/kcp/startWithKCP/startWithKCP.spec.ts @@ -0,0 +1,137 @@ +import { test } from '@playwright/test'; +import { turnOffSDKMocking } from '../../cardMocks'; + +test.describe('Starting with KCP fields', () => { + test.beforeEach(async () => { + // await t.navigateTo(cardPage.pageUrl); + await turnOffSDKMocking(); + //use startWithKCP.clientScripts.js + }); + test( + '#1 Fill in card number that will hide KCP fields, ' + + 'then check password iframe is hidden, ' + + 'then complete the form & check component becomes valid', + async () => { + // Wait for field to appear in DOM + // await cardPage.numHolder(); + // + // // Fill card field with non-korean card + // await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); + // + // // Does the password securedField get removed + // await t.expect(cardPage.pwdSpan.exists).notOk(); + // + // // Complete form + // await cardPage.cardUtils.fillDateAndCVC(t); + // + // // Expect card to now be valid + // await t.expect(cardPage.getFromState('isValid')).eql(true); + } + ); + + test( + '#2 Fill in all KCP details, ' + + 'then check card state for taxNumber & password entries, ' + + 'then replace card number with non-korean card and check taxNumber and password state are cleared', + async () => { + // For some reason, at full speed, testcafe can fail to fill in the taxNumber correctly + // await t.setTestSpeed(0.9); + // + // await cardPage.numHolder(); + // + // // Complete form with korean card number + // await cardPage.cardUtils.fillCardNumber(t, KOREAN_TEST_CARD); + // await cardPage.cardUtils.fillDateAndCVC(t); + // await cardPage.kcpUtils.fillTaxNumber(t); + // await cardPage.kcpUtils.fillPwd(t); + // + // // Expect card to now be valid + // await t.expect(cardPage.getFromState('isValid')).eql(true); + // + // // Expect card state to have tax and pwd elements + // await t.expect(cardPage.getFromState('data.taxNumber')).eql(TEST_TAX_NUMBER_VALUE); + // + // // Extract & decode JWE header + // const JWEToken = await cardPage.getFromState('data.encryptedPassword'); + // const JWETokenArr = JWEToken.split('.'); + // const blobHeader = JWETokenArr[0]; + // const base64Decoded = await cardPage.decodeBase64(blobHeader); + // const headerObj = JSON.parse(base64Decoded); + // + // // Look for expected properties + // await t.expect(JWETokenArr.length).eql(5); // Expected number of components in the JWE token + // + // await t.expect(headerObj.alg).eql(JWE_ALG).expect(headerObj.enc).eql(JWE_CONTENT_ALG).expect(headerObj.version).eql(JWE_VERSION); + // + // // await t.expect(cardPage.getFromState('data.encryptedPassword')).contains('adyenjs_0_1_'); + // + // await t.expect(cardPage.getFromState('valid.taxNumber')).eql(true); + // await t.expect(cardPage.getFromState('valid.encryptedPassword')).eql(true); + // + // // Replace number + // await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD, 'replace'); + // + // // (Does the password securedField get removed) + // await t.expect(cardPage.pwdSpan.exists).notOk(); + // + // // Expect card state's tax and pwd elements to have been cleared/reset + // await t.expect(cardPage.getFromState('data.taxNumber')).eql(undefined); + // await t.expect(cardPage.getFromState('data.encryptedPassword')).eql(undefined); + // + // await t.expect(cardPage.getFromState('valid.taxNumber')).eql(false); + // await t.expect(cardPage.getFromState('valid.encryptedPassword')).eql(false); + // + // // Expect card to still be valid (get it from window.card this time - just to check that is also set) + // await t.expect(cardPage.getFromWindow('card.isValid')).eql(true); + } + ); + + test( + '#3 Fill in card number that will hide KCP fields, ' + + 'then complete form and expect component to be valid & to be able to pay,' + + 'then replace card number with korean card and expect component to be valid & to be able to pay', + async () => { + // For some reason, at full speed, testcafe can fail to fill in the taxNumber correctly + // await t.setTestSpeed(0.9); + // + // await cardPage.numHolder(); + // + // // handler for alert that's triggered on successful payment + // await t.setNativeDialogHandler(() => true); + // + // // Complete form with regular card number + // await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); + // await cardPage.cardUtils.fillDateAndCVC(t); + // + // // Expect card to now be valid + // await t.expect(cardPage.getFromState('isValid')).eql(true); + // + // // Click pay - except we can't... + // await t; + // // .click(cardPage.payButton) // Can't do this in testing scenario - for some reason it triggers a redirect to a hpp/collectKcpAuthentication page + // // ... & no errors + // await t.expect(cardPage.numLabelTextError.exists).notOk(); + // + // // Replace number with korean card (pasting works better than replacing in textcafe >1.13.0) + // await cardPage.cardUtils.fillCardNumber(t, KOREAN_TEST_CARD, 'paste'); // 'replace' + // + // // Expect card to now be invalid + // await t.expect(cardPage.getFromState('isValid')).eql(false); + // + // // Complete form + // await cardPage.kcpUtils.fillTaxNumber(t); + // await cardPage.kcpUtils.fillPwd(t); + // + // // Expect card to now be valid + // await t.expect(cardPage.getFromState('isValid')).eql(true); + // + // // Click pay + // await t + // .click(cardPage.payButton) + // // no errors + // .expect(cardPage.numLabelTextError.exists) + // .notOk() + // .wait(1000); + } + ); +}); diff --git a/packages/e2e/tests/cards/kcp/startWithoutKCP/startWithoutKCP.clientScripts.js b/packages/e2e-playwright/tests/ui/card/kcp/startWithoutKCP/startWithoutKCP.clientScripts.js similarity index 100% rename from packages/e2e/tests/cards/kcp/startWithoutKCP/startWithoutKCP.clientScripts.js rename to packages/e2e-playwright/tests/ui/card/kcp/startWithoutKCP/startWithoutKCP.clientScripts.js diff --git a/packages/e2e-playwright/tests/ui/card/kcp/startWithoutKCP/startWithoutKCP.spec.ts b/packages/e2e-playwright/tests/ui/card/kcp/startWithoutKCP/startWithoutKCP.spec.ts new file mode 100644 index 000000000..1ff8e7526 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/card/kcp/startWithoutKCP/startWithoutKCP.spec.ts @@ -0,0 +1,174 @@ +import { test } from '@playwright/test'; +import { turnOffSDKMocking } from '../../cardMocks'; + +test.describe('Starting without KCP fields', () => { + test.beforeEach(async () => { + // await t.navigateTo(cardPage.pageUrl); + await turnOffSDKMocking(); + //use startWithoutKCP.clientScripts.js + }); + + test( + '#1 Fill in card number that will trigger addition of KCP fields, ' + + 'then check new iframe field is correctly set up, ' + + 'then complete the form & check component becomes valid', + async () => { + // For some reason, at full speed, testcafe can fail to fill in the taxNumber correctly + // await t.setTestSpeed(0.5); + // + // // Wait for field to appear in DOM + // await cardPage.numHolder(); + // + // // Fill card field with korean card + // await cardPage.cardUtils.fillCardNumber(t, KOREAN_TEST_CARD); + // + // // Does a newly added password securedField now exist with a state.valid entry, a holder and an iframe...? + // await t + // .expect(cardPage.getFromState('valid.encryptedPassword')) + // .eql(false) + // .expect(cardPage.pwdSpan.exists) + // .ok() + // .expect(cardPage.pwdSpan.find('iframe').getAttribute('src')) + // .contains('securedFields.html'); + // + // // ...and can we can type into the iframe? + // await cardPage.kcpUtils.fillPwd(t); + // + // // Check pwd field for value + // await cardPage.kcpUtils.checkPwd(t, TEST_PWD_VALUE); + // + // // // Complete form + // await cardPage.cardUtils.fillDateAndCVC(t); + // await cardPage.kcpUtils.fillTaxNumber(t); + // + // // // Expect card to now be valid + // await t.expect(cardPage.getFromState('isValid')).eql(true); + } + ); + + test( + '#2 Fill in card number that will trigger addition of KCP fields, ' + + 'then fill in all KCP details & check card state for taxNumber & password entries, ' + + 'then delete card number and check taxNumber and password state are cleared', + async () => { + // For some reason, at full speed, testcafe can fail to fill in the taxNumber correctly + // await t.setTestSpeed(0.5); + // + // await cardPage.numHolder(); + // + // // Complete form with korean card number + // await cardPage.cardUtils.fillCardNumber(t, KOREAN_TEST_CARD); + // await cardPage.cardUtils.fillDateAndCVC(t); + // await cardPage.kcpUtils.fillTaxNumber(t); + // await cardPage.kcpUtils.fillPwd(t); + // + // // Expect card to now be valid + // await t.expect(cardPage.getFromState('isValid')).eql(true); + // + // // Expect card state to have tax and pwd elements + // await t.expect(cardPage.getFromState('data.taxNumber')).eql(TEST_TAX_NUMBER_VALUE); + // + // // Extract & decode JWE header + // const JWEToken = await cardPage.getFromState('data.encryptedPassword'); + // const JWETokenArr = JWEToken.split('.'); + // const blobHeader = JWETokenArr[0]; + // const base64Decoded = await cardPage.decodeBase64(blobHeader); + // const headerObj = JSON.parse(base64Decoded); + // + // // Look for expected properties + // await t.expect(JWETokenArr.length).eql(5); // Expected number of components in the JWE token + // + // await t.expect(headerObj.alg).eql(JWE_ALG).expect(headerObj.enc).eql(JWE_CONTENT_ALG).expect(headerObj.version).eql(JWE_VERSION); + // + // // await t.expect(cardPage.getFromState('data.encryptedPassword')).contains('adyenjs_0_1_'); + // + // await t.expect(cardPage.getFromState('valid.taxNumber')).eql(true); + // await t.expect(cardPage.getFromState('valid.encryptedPassword')).eql(true); + // + // // Delete number + // await cardPage.cardUtils.deleteCardNumber(t); + // + // // Expect card state's tax and pwd elements to have been cleared/reset + // await t.expect(cardPage.getFromState('data.taxNumber')).eql(undefined); + // await t.expect(cardPage.getFromState('data.encryptedPassword')).eql(undefined); + // + // await t.expect(cardPage.getFromState('valid.taxNumber')).eql(false); + // await t.expect(cardPage.getFromState('valid.encryptedPassword')).eql(false); + // + // // Expect card to no longer be valid + // await t.expect(cardPage.getFromState('isValid')).eql(false); + } + ); + + test( + '#3 Fill in card number that will trigger addition of KCP fields, ' + + 'then complete form and expect component to be valid & to be able to pay,' + + 'then replace card number with non-korean card and expect component to be valid & to be able to pay', + async () => { + // For some reason, at full speed, testcafe can fail to fill in the taxNumber correctly + // await t.setTestSpeed(0.5); + // + // await cardPage.numHolder(); + // + // // handler for alert that's triggered on successful payment + // await t.setNativeDialogHandler(() => true); + // + // // Complete form with korean card number + // await cardPage.cardUtils.fillCardNumber(t, KOREAN_TEST_CARD); + // await cardPage.cardUtils.fillDateAndCVC(t); + // await cardPage.kcpUtils.fillTaxNumber(t); + // await cardPage.kcpUtils.fillPwd(t); + // + // // Expect card to now be valid + // await t.expect(cardPage.getFromState('isValid')).eql(true); + // + // // click pay + // await t + // .click(cardPage.payButton) + // // no errors + // .expect(cardPage.numLabelTextError.exists) + // .notOk(); + // + // // Replace number with non-korean card + // await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD, 'replace'); + // + // // Expect card to now be valid + // await t.expect(cardPage.getFromState('isValid')).eql(true); + // + // // click pay + // await t + // .click(cardPage.payButton) + // // no errors + // .expect(cardPage.numLabelTextError.exists) + // .notOk() + // .wait(1000); + } + ); + + test( + '#4 Fill in card number that will trigger addition of KCP fields, ' + + 'then complete form except for password field,' + + 'expect component not to be valid and for password field to show error', + async () => { + // For some reason, at full speed, testcafe can fail to fill in the taxNumber correctly + // await t.setTestSpeed(0.5); + // + // await cardPage.numHolder(); + // + // // Complete form with korean card number + // await cardPage.cardUtils.fillCardNumber(t, KOREAN_TEST_CARD); + // await cardPage.cardUtils.fillDateAndCVC(t); + // await cardPage.kcpUtils.fillTaxNumber(t); + // + // // Expect card to not be valid + // await t.expect(cardPage.getFromState('isValid')).eql(false); + // + // // click pay + // await t + // .click(cardPage.payButton) + // // Expect error on password field + // .expect(cardPage.pwdErrorText.exists) + // .ok(); + } + ); +}); diff --git a/packages/e2e/tests/cards/socialSecurityNumber/autoMode/autoMode.clientScripts.js b/packages/e2e-playwright/tests/ui/card/socialSecurityNumber/autoMode/autoMode.clientScripts.js similarity index 100% rename from packages/e2e/tests/cards/socialSecurityNumber/autoMode/autoMode.clientScripts.js rename to packages/e2e-playwright/tests/ui/card/socialSecurityNumber/autoMode/autoMode.clientScripts.js diff --git a/packages/e2e-playwright/tests/ui/card/socialSecurityNumber/autoMode/autoMode.spec.ts b/packages/e2e-playwright/tests/ui/card/socialSecurityNumber/autoMode/autoMode.spec.ts new file mode 100644 index 000000000..89406b7c1 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/card/socialSecurityNumber/autoMode/autoMode.spec.ts @@ -0,0 +1,50 @@ +import { test } from '@playwright/test'; +import { BIN_LOOKUP_VERSION, TEST_CPF_VALUE } from '../../../../utils/constants'; + +const getCardState = (what, prop) => globalThis.component.state[what][prop]; +const iframeSelector = '.card-field iframe'; + +const fillSSN = async (t, ssnValue = TEST_CPF_VALUE) => { + return t.switchToMainWindow().typeText('.adyen-checkout__field--socialSecurityNumber input', ssnValue, { speed: 0.5 }); +}; + +const requestURL = `https://checkoutshopper-test.adyen.com/checkoutshopper/${BIN_LOOKUP_VERSION}/bin/binLookup?token=${process.env.CLIENT_KEY}`; + +const mockedResponse = { + brands: [ + { + brand: 'visa', + cvcPolicy: 'required', + enableLuhnCheck: true, + showExpiryDate: true, + supported: true, + showSocialSecurityNumber: true + } + ], + issuingCountryCode: 'BR', + requestId: null +}; + +test.describe('Starting with SSN (auto) field', () => { + test.beforeEach(async () => { + // use mock: mockedResponse for requestURL + // await t.navigateTo(cardPage.pageUrl); + //use autoMode.clientScripts.js + }); + + test('Fill in card number with a socialSecurityNumber (CPF) field (socialSecurityMode: auto)', async () => { + // Start, allow time for iframes to load + // await start(t, 2000, TEST_SPEED); + // + // // Fill card field with non-korean card + // await cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); + // + // // Complete form + // await cardUtils.fillDateAndCVC(t); + // + // await fillSSN(t); + // + // // Expect card to now be valid + // await t.expect(getIsValid()).eql(true); + }); +}); diff --git a/packages/e2e/tests/cards/socialSecurityNumber/showMode/showMode.clientScripts.js b/packages/e2e-playwright/tests/ui/card/socialSecurityNumber/showMode/showMode.clientScripts.js similarity index 100% rename from packages/e2e/tests/cards/socialSecurityNumber/showMode/showMode.clientScripts.js rename to packages/e2e-playwright/tests/ui/card/socialSecurityNumber/showMode/showMode.clientScripts.js diff --git a/packages/e2e-playwright/tests/ui/card/socialSecurityNumber/showMode/showMode.spec.ts b/packages/e2e-playwright/tests/ui/card/socialSecurityNumber/showMode/showMode.spec.ts new file mode 100644 index 000000000..d0ad6999a --- /dev/null +++ b/packages/e2e-playwright/tests/ui/card/socialSecurityNumber/showMode/showMode.spec.ts @@ -0,0 +1,60 @@ +import { test } from '@playwright/test'; +import { TEST_CPF_VALUE } from '../../../../utils/constants'; + +const passwordHolder = '.card-field [data-cse="encryptedPassword"]'; + +const getCardState = (what, prop) => { + return globalThis.card.state[what][prop]; +}; + +const iframeSelector = '.card-field iframe'; + +const fillSSN = async (t, ssnValue = TEST_CPF_VALUE) => { + return t.switchToMainWindow().typeText('.adyen-checkout__field--socialSecurityNumber input', ssnValue, { speed: 0.5 }); +}; + +test.describe('Starting with SSN (show) field', () => { + test.beforeEach(async () => { + // use mock: mockedResponse for requestURL + // await t.navigateTo(cardPage.pageUrl); + //use showMode.clientScripts.js + }); + + test('Fill in card number with a socialSecurityNumber (CPF) field (socialSecurityMode: show)', async t => { + // Start, allow time for iframes to load + // await start(t, 2000, TEST_SPEED); + // + // // Fill card field with non-korean card + // await cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); + // + // // Does the password securedField get removed + // await t.expect(passwordHolder.exists).notOk(); + // + // // Complete form + // await cardUtils.fillDateAndCVC(t); + // + // await fillSSN(t); + // + // // Expect card to now be valid + // await t.expect(getIsValid()).eql(true); + }); + + test('Fill in card number with a wrong socialSecurityNumber (CPF) field (socialSecurityMode: show)', async t => { + // Start, allow time for iframes to load + // await start(t, 2000, TEST_SPEED); + // + // // Fill card field with non-korean card + // await cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); + // + // // Does the password securedField get removed + // await t.expect(passwordHolder.exists).notOk(); + // + // // Complete form + // await cardUtils.fillDateAndCVC(t); + // + // await fillSSN(t, '1234'); + // + // // Expect card to now be valid + // await t.expect(getIsValid()).eql(false); + }); +}); diff --git a/packages/e2e/tests/cards/threeDS2/threeDS2.clientScripts.js b/packages/e2e-playwright/tests/ui/card/threeDS2/threeDS2.clientScripts.js similarity index 100% rename from packages/e2e/tests/cards/threeDS2/threeDS2.clientScripts.js rename to packages/e2e-playwright/tests/ui/card/threeDS2/threeDS2.clientScripts.js diff --git a/packages/e2e/tests/cards/threeDS2/threeDS2.default.size.clientScripts.js b/packages/e2e-playwright/tests/ui/card/threeDS2/threeDS2.default.size.clientScripts.js similarity index 100% rename from packages/e2e/tests/cards/threeDS2/threeDS2.default.size.clientScripts.js rename to packages/e2e-playwright/tests/ui/card/threeDS2/threeDS2.default.size.clientScripts.js diff --git a/packages/e2e-playwright/tests/ui/card/threeDS2/threeDS2.default.size.spec.ts b/packages/e2e-playwright/tests/ui/card/threeDS2/threeDS2.default.size.spec.ts new file mode 100644 index 000000000..f4446e704 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/card/threeDS2/threeDS2.default.size.spec.ts @@ -0,0 +1,112 @@ +import { test } from '@playwright/test'; + +const detailsURL = `/details`; + +// const loggerDetails = RequestLogger( +// { detailsURL, method: 'post' }, +// { +// logResponseHeaders: true, +// logResponseBody: true +// } +// ); + +const submitThreeDS2FingerprintURL = `https://checkoutshopper-test.adyen.com/checkoutshopper/v1/submitThreeDS2Fingerprint?token=${process.env.CLIENT_KEY}`; + +// const loggerSubmitThreeDS2 = RequestLogger( +// { submitThreeDS2FingerprintURL, method: 'post' }, +// { +// logResponseHeaders: true, +// logResponseBody: true +// } +// ); + +test.describe('Testing default size of the 3DS2 challenge window, & the challenge flows, on the Card component, since all other tests are for Dropin', () => { + test.beforeEach(async () => { + // check if playwright supports logResponseHeaders and logResponseBody or the equivalents: requestHooks([loggerDetails, loggerSubmitThreeDS2]) + // await t.navigateTo(cardPage.pageUrl); + // use threeDS2.default.size.clientScripts.js + }); + + test('#1 Fill in card number that will trigger full flow (fingerprint & challenge)', async t => { + // loggerDetails.clear(); + // + // // Wait for field to appear in DOM + // await cardPage.numHolder(); + // + // // Set handler for the alert window + // await t.setNativeDialogHandler(() => true); + // + // // Fill card fields + // await cardPage.cardUtils.fillCardNumber(t, THREEDS2_FULL_FLOW_CARD); + // await cardPage.cardUtils.fillDateAndCVC(t); + // + // // Expect card to now be valid + // await t.expect(cardPage.getFromState('isValid')).eql(true); + // + // // Click pay + // await t + // .click(cardPage.payButton) + // // Expect no errors + // .expect(cardPage.numLabelTextError.exists) + // .notOk() + // .expect(loggerSubmitThreeDS2.contains(r => r.response.statusCode === 200)) + // // Allow time for the /submitThreeDS2Fingerprint call, which we expect to be successful + // .ok({ timeout: 5000 }); + // + // // console.log(logger.requests[0].response.headers); + // + // // Check challenge window size is read from default config prop + // await t.expect(cardPage.challengeWindowSize02.exists).ok({ timeout: 5000 }); + // + // // Complete challenge + // await fillChallengeField(t); + // await submitChallenge(t); + // + // await t + // .expect(loggerDetails.contains(r => r.response.statusCode === 200)) + // // Allow time for the /details call, which we expect to be successful + // .ok({ timeout: 5000 }) + // .wait(3000); + // + // // Check the value of the alert text + // const history = await t.getNativeDialogHistory(); + // await t.expect(history[0].text).eql('Authorised', { timeout: 5000 }); + }); + + test('#2 Fill in card number that will trigger challenge-only flow', async t => { + // loggerDetails.clear(); + // + // // Wait for field to appear in DOM + // await cardPage.numHolder(); + // + // // Set handler for the alert window + // await t.setNativeDialogHandler(() => true); + // + // // Fill card fields + // await cardPage.cardUtils.fillCardNumber(t, THREEDS2_CHALLENGE_ONLY_CARD); + // await cardPage.cardUtils.fillDateAndCVC(t); + // + // // Expect card to now be valid + // await t.expect(cardPage.getFromState('isValid')).eql(true); + // + // // Click pay + // await t.click(cardPage.payButton); + // + // // Check challenge window size is read from config prop + // await t.expect(cardPage.challengeWindowSize02.exists).ok({ timeout: 5000 }); + // + // // Complete challenge + // await fillChallengeField(t); + // await submitChallenge(t); + // + // await t + // .expect(loggerDetails.contains(r => r.response.statusCode === 200)) + // // Allow time for the ONLY details call, which we expect to be successful + // .ok({ timeout: 5000 }) + // .wait(3000); + // + // // Check the value of the alert text + // const history = await t.getNativeDialogHistory(); + // await t.expect(history[0].text).eql('Authorised', { timeout: 5000 }); + }); +}); diff --git a/packages/e2e/tests/cards/threeDS2/threeDS2.handleAction.clientScripts.js b/packages/e2e-playwright/tests/ui/card/threeDS2/threeDS2.handleAction.clientScripts.js similarity index 100% rename from packages/e2e/tests/cards/threeDS2/threeDS2.handleAction.clientScripts.js rename to packages/e2e-playwright/tests/ui/card/threeDS2/threeDS2.handleAction.clientScripts.js diff --git a/packages/e2e-playwright/tests/ui/card/threeDS2/threeDS2.handleAction.spec.ts b/packages/e2e-playwright/tests/ui/card/threeDS2/threeDS2.handleAction.spec.ts new file mode 100644 index 000000000..717ec42ed --- /dev/null +++ b/packages/e2e-playwright/tests/ui/card/threeDS2/threeDS2.handleAction.spec.ts @@ -0,0 +1,120 @@ +import { test } from '@playwright/test'; + +const cardSelector = '.adyen-checkout__payment-method--scheme'; +const detailsURL = `/details`; +const submitThreeDS2FingerprintURL = `https://checkoutshopper-test.adyen.com/checkoutshopper/v1/submitThreeDS2Fingerprint?token=${process.env.CLIENT_KEY}`; + +// const logger = RequestLogger( +// [ +// { url: detailsURL, method: 'post' }, +// { url: submitThreeDS2FingerprintURL, method: 'post' } +// ], +// { +// logRequestBody: true, +// logResponseHeaders: true, +// logResponseBody: true +// } +// ); + +const apiVersion = Number(process.env.API_VERSION.substring(1)); + +test.describe('Testing new (v67) 3DS2 Flow (custom challengeWindowSize config)', () => { + test.beforeEach(async () => { + // await t.navigateTo(dropinPage.pageUrl); + // await turnOffSDKMocking(); + // use threeDS2.handleAction.clientScripts.js + // requestHooks(logger) or the playwright equivalent + }); + + test('#1 Fill in card number that will trigger full flow (fingerprint & challenge) with challenge window size set in the component configuration', async () => { + // logger.clear(); + // + // await dropinPage.cc.numSpan(); + // + // // Set handler for the alert window + // await t.setNativeDialogHandler(() => true); + // + // // Fill card fields + // await dropinPage.cc.cardUtils.fillCardNumber(t, THREEDS2_FULL_FLOW_CARD); + // await dropinPage.cc.cardUtils.fillDateAndCVC(t); + // + // // Expect card to now be valid + // await t.expect(dropinPage.getFromWindow('dropin.isValid')).eql(true); + // + // // Click pay + // await t + // .click(dropinPage.cc.payButton) + // // Expect no errors + // .expect(dropinPage.cc.numLabelTextError.exists) + // .notOk() + // /** + // * Allow time for the /submitThreeDS2Fingerprint call, which we expect to be successful + // */ + // .wait(2000) + // .expect(logger.contains(r => r.request.url.indexOf('/submitThreeDS2Fingerprint') > -1 && r.response.statusCode === 200)) + // .ok(); + // + // // console.log('logger.requests[0].response', logger.requests[0].response); + // + // // Check challenge window size is read from config prop set in handleAction call + // await t.expect(dropinPage.challengeWindowSize04.exists).ok({ timeout: 3000 }); + // + // // Complete challenge + // await fillChallengeField(t); + // await submitChallenge(t); + // + // await t + // // Allow time for the /details call, which we expect to be successful + // .wait(2000) + // .expect(logger.contains(r => r.request.url.indexOf('/details') > -1 && r.response.statusCode === 200)) + // .ok() + // .wait(3000); + // + // // Check request body is in the expected form + // const requestBodyBuffer = logger.requests[1].request.body; + // const requestBody = JSON.parse(requestBodyBuffer); + // + // await t.expect(requestBody.details.threeDSResult).ok().expect(requestBody.paymentData).notOk(); + // + // // Check the value of the alert text + // const history = await t.getNativeDialogHistory(); + // await t.expect(history[0].text).eql('Authorised'); + }); + + test('#2 Fill in card number that will trigger challenge-only flow with challenge window size set in component configuration', async () => { + // logger.clear(); + // + // await dropinPage.cc.numSpan(); + // + // // Set handler for the alert window + // await t.setNativeDialogHandler(() => true); + // + // // Fill card fields + // await dropinPage.cc.cardUtils.fillCardNumber(t, THREEDS2_CHALLENGE_ONLY_CARD); + // await dropinPage.cc.cardUtils.fillDateAndCVC(t); + // + // // Expect card to now be valid + // await t.expect(dropinPage.getFromWindow('dropin.isValid')).eql(true); + // + // // Click pay + // await t.click(dropinPage.cc.payButton); + // + // // Check challenge window size is read from config prop set in handleAction call + // await t.expect(dropinPage.challengeWindowSize04.exists).ok({ timeout: 3000 }); + // + // // Complete challenge + // await fillChallengeField(t); + // await submitChallenge(t); + // + // await t + // // Allow time for the ONLY details call, which we expect to be successful + // .wait(2000) + // .expect(logger.contains(r => r.request.url.indexOf('/details') > -1 && r.response.statusCode === 200)) + // .ok() + // .wait(3000); + // + // // Check the value of the alert text + // const history = await t.getNativeDialogHistory(); + // await t.expect(history[0].text).eql('Authorised'); + }); +}); diff --git a/packages/e2e-playwright/tests/ui/card/threeDS2/threeDS2.redirect.spec.ts b/packages/e2e-playwright/tests/ui/card/threeDS2/threeDS2.redirect.spec.ts new file mode 100644 index 000000000..7f9407473 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/card/threeDS2/threeDS2.redirect.spec.ts @@ -0,0 +1,63 @@ +/** + * NOTE: the difference between the old and new flow is that ThreeDS2DeviceFingerprint.tsx calls /submitThreeDS2Fingerprint rather than /details as + * its onComplete function and ThreeDS2/components/utils.ts has to provide the createFingerprintResolveData & createChallengeResolveData functions + * that will prepare the data in a way acceptable to the /submitThreeDS2Fingerprint endpoint + */ + +import { test } from '@playwright/test'; + +const url = `https://checkoutshopper-test.adyen.com/checkoutshopper/v1/submitThreeDS2Fingerprint?token=${process.env.CLIENT_KEY}`; + +// const logger = RequestLogger( +// { url, method: 'post' }, +// { +// logResponseHeaders: true, +// logResponseBody: true +// } +// ); + +const apiVersion = Number(process.env.API_VERSION.substring(1)); + +test.describe('Testing new (v67) 3DS2 Flow (redirect)', () => { + test.beforeEach(async () => { + // await t.navigateTo(`${dropinPage.pageUrl}?amount=12003`); + // await turnOffSDKMocking(); + // use 'threeDS2.clientScripts.js'; + // use logger + }); + + if (apiVersion >= 67) { + test('Fill in card number that will trigger redirect flow', async t => { + // await dropinPage.cc.numSpan(); + // + // // Set handler for the alert window + // await t.setNativeDialogHandler(() => true); + // + // // Fill card fields + // await dropinPage.cc.cardUtils.fillCardNumber(t, THREEDS2_FULL_FLOW_CARD); + // await dropinPage.cc.cardUtils.fillDateAndCVC(t); + // + // // Expect card to now be valid + // await t.expect(dropinPage.getFromWindow('dropin.isValid')).eql(true); + // + // // Click pay + // await t + // .click(dropinPage.cc.payButton) + // // Expect no errors + // .expect(dropinPage.cc.numLabelTextError.exists) + // .notOk() + // // Allow time for the ONLY /submitThreeDS2Fingerprint call, which we expect to be successful + // .expect(logger.contains(r => r.response.statusCode === 200)) + // .ok({ timeout: 10000 }) + // // Allow time for redirect to occur + // .wait(2000); + // + // // Inspect page for Redirect elements + // await t.expect(Selector('title').innerText).eql('3D Authentication Host'); + }); + } else { + test(`Skip testing new 3DS2 redirect flow since api version is too low (v${apiVersion})`, async t => { + // await t.wait(250); + }); + } +}); diff --git a/packages/e2e-playwright/tests/ui/card/threeDS2/threeDS2.spec.ts b/packages/e2e-playwright/tests/ui/card/threeDS2/threeDS2.spec.ts new file mode 100644 index 000000000..f8f4b409a --- /dev/null +++ b/packages/e2e-playwright/tests/ui/card/threeDS2/threeDS2.spec.ts @@ -0,0 +1,140 @@ +import { test } from '@playwright/test'; + +const cardSelector = '.adyen-checkout__payment-method--scheme'; +const detailsURL = `/details`; +const submitThreeDS2FingerprintURL = `https://checkoutshopper-test.adyen.com/checkoutshopper/v1/submitThreeDS2Fingerprint?token=${process.env.CLIENT_KEY}`; + +// const logger = RequestLogger( +// [ +// { url: detailsURL, method: 'post' }, +// { url: submitThreeDS2FingerprintURL, method: 'post' } +// ], +// { +// logRequestBody: true, +// logResponseHeaders: true, +// logResponseBody: true +// } +// ); + +test.describe('Testing new (v67) 3DS2 Flow', () => { + test.beforeEach(async () => { + // await t.navigateTo(dropinPage.pageUrl); + // await turnOffSDKMocking(); + // use 'threeDS2.clientScripts.js'; + // use logger + }); + + test('#1 Fill in card number that will trigger frictionless flow', async t => { + // await dropinPage.cc.numSpan(); + // + // // Set handler for the alert window + // await t.setNativeDialogHandler(() => true); + // + // // Fill card fields + // await dropinPage.cc.cardUtils.fillCardNumber(t, THREEDS2_FRICTIONLESS_CARD, 'paste'); + // await dropinPage.cc.cardUtils.fillDateAndCVC(t); + // + // // Expect card to now be valid + // await t.expect(dropinPage.getFromWindow('dropin.isValid')).eql(true); + // + // // Click pay + // await t + // .click(dropinPage.cc.payButton) + // // Allow time for the ONLY details call, which we expect to be successful + // .expect(logger.contains(r => r.request.url.indexOf('/details') > -1 && r.response.statusCode === 200)) + // .ok({ timeout: 10000 }) + // // Allow time for the alert to manifest + // .wait(2000); + // + // // Check the value of the alert text + // const history = await t.getNativeDialogHistory(); + // await t.expect(history[0].text).eql('Authorised'); + }); + + test('#2 Fill in card number that will trigger full flow (fingerprint & challenge)', async t => { + // logger.clear(); + // + // await dropinPage.cc.numSpan(); + // + // // Set handler for the alert window + // await t.setNativeDialogHandler(() => true); + // + // // Fill card fields + // await dropinPage.cc.cardUtils.fillCardNumber(t, THREEDS2_FULL_FLOW_CARD, 'paste'); + // await dropinPage.cc.cardUtils.fillDateAndCVC(t); + // + // // Expect card to now be valid + // await t.expect(dropinPage.getFromWindow('dropin.isValid')).eql(true); + // + // // Click pay + // await t + // .click(dropinPage.cc.payButton) + // /** + // * Allow time for the /submitThreeDS2Fingerprint call, which we expect to be successful + // */ + // .expect(logger.contains(r => r.request.url.indexOf('/submitThreeDS2Fingerprint') > -1 && r.response.statusCode === 200)) + // .ok({ timeout: 10000 }); + // + // // console.log('logger.requests[0].response', logger.requests[0].response); + // + // // Check challenge window size is read from config prop + // await t.expect(dropinPage.challengeWindowSize04.exists).ok({ timeout: 3000 }); + // + // // Complete challenge + // await fillChallengeField(t); + // await submitChallenge(t); + // + // await t + // // Allow time for the /details call, which we expect to be successful + // .expect(logger.contains(r => r.request.url.indexOf('/details') > -1 && r.response.statusCode === 200)) + // .ok({ timeout: 10000 }) + // .wait(1000); + // + // // Check request body is in the expected form + // const requestBodyBuffer = logger.requests[1].request.body; + // const requestBody = JSON.parse(requestBodyBuffer); + // + // await t.expect(requestBody.details.threeDSResult).ok().expect(requestBody.paymentData).notOk(); + // + // // Check the value of the alert text + // const history = await t.getNativeDialogHistory(); + // await t.expect(history[0].text).eql('Authorised'); + }); + + test('#3 Fill in card number that will trigger challenge-only flow', async t => { + // logger.clear(); + // + // await dropinPage.cc.numSpan(); + // + // // Set handler for the alert window + // await t.setNativeDialogHandler(() => true); + // + // // Fill card fields + // await dropinPage.cc.cardUtils.fillCardNumber(t, THREEDS2_CHALLENGE_ONLY_CARD, 'paste'); + // await dropinPage.cc.cardUtils.fillDateAndCVC(t); + // + // // Expect card to now be valid + // // await t.expect(getIsValid('dropin')).eql(true); + // await t.expect(dropinPage.getFromWindow('dropin.isValid')).eql(true); + // + // // Click pay + // await t.click(dropinPage.cc.payButton); + // + // // Check challenge window size is read from config prop + // await t.expect(dropinPage.challengeWindowSize04.exists).ok({ timeout: 3000 }); + // + // // Complete challenge + // await fillChallengeField(t); + // await submitChallenge(t); + // + // await t + // // Allow time for the ONLY details call, which we expect to be successful + // .expect(logger.contains(r => r.request.url.indexOf('/details') > -1 && r.response.statusCode === 200)) + // .ok({ timeout: 10000 }) + // .wait(2000); + // + // // Check the value of the alert text + // const history = await t.getNativeDialogHistory(); + // await t.expect(history[0].text).eql('Authorised'); + }); +}); diff --git a/packages/e2e/tests/cards/utils/cardUtils.js b/packages/e2e-playwright/tests/ui/card/utils/cardUtils.js similarity index 100% rename from packages/e2e/tests/cards/utils/cardUtils.js rename to packages/e2e-playwright/tests/ui/card/utils/cardUtils.js diff --git a/packages/e2e/tests/giftcards/enoughBalance/enoughBalance.clientScripts.js b/packages/e2e-playwright/tests/ui/card/utils/constants.js similarity index 100% rename from packages/e2e/tests/giftcards/enoughBalance/enoughBalance.clientScripts.js rename to packages/e2e-playwright/tests/ui/card/utils/constants.js diff --git a/packages/e2e/tests/cards/utils/kcpUtils.js b/packages/e2e-playwright/tests/ui/card/utils/kcpUtils.js similarity index 100% rename from packages/e2e/tests/cards/utils/kcpUtils.js rename to packages/e2e-playwright/tests/ui/card/utils/kcpUtils.js diff --git a/packages/e2e/tests/cards/utils/threeDS2Utils.js b/packages/e2e-playwright/tests/ui/card/utils/threeDS2Utils.js similarity index 100% rename from packages/e2e/tests/cards/utils/threeDS2Utils.js rename to packages/e2e-playwright/tests/ui/card/utils/threeDS2Utils.js diff --git a/packages/e2e/tests/customcard/branding/customcard.dualBranding.clientScripts.js b/packages/e2e-playwright/tests/ui/customCard/branding/customcard.dualBranding.clientScripts.js similarity index 100% rename from packages/e2e/tests/customcard/branding/customcard.dualBranding.clientScripts.js rename to packages/e2e-playwright/tests/ui/customCard/branding/customcard.dualBranding.clientScripts.js diff --git a/packages/e2e-playwright/tests/ui/customCard/branding/customcard.dualBranding.resets.spec.ts b/packages/e2e-playwright/tests/ui/customCard/branding/customcard.dualBranding.resets.spec.ts new file mode 100644 index 000000000..8ff1a209b --- /dev/null +++ b/packages/e2e-playwright/tests/ui/customCard/branding/customcard.dualBranding.resets.spec.ts @@ -0,0 +1,163 @@ +import { test } from '@playwright/test'; + +const singleBrandingIconHolder = '.secured-fields .pm-image'; +const dualBrandingIconHolder = '.secured-fields .pm-image-dual'; + +const cvcField = '.secured-fields .pm-form-label--cvc'; + +const getPropFromPMData = prop => { + return globalThis.securedFields.formatData().paymentMethod[prop]; +}; + +test.describe('Testing dual branding resets in custom card', () => { + test.beforeEach(async () => { + // await t.navigateTo(CUSTOMCARDS_URL); + // use 'customcard.dualBranding.clientScripts.js'; + }); + test( + 'Fill in dual branded card then ' + + 'check no sorting has occurred to place bcmc first, then' + + 'ensure only generic card logo shows after deleting digits', + async t => { + // Start, allow time to load + // await start(t, 2000, TEST_SPEED); + // + // await cardUtils.fillCardNumber(t, BCMC_CARD); // dual branded with maestro + // + // // Maestro first, Bcmc second + // await t + // // hidden single brand icon holder + // .expect(singleBrandingIconHolder.filterHidden().exists) + // .ok() + // // visible dual brand icon holder + // .expect(dualBrandingIconHolder.filterVisible().exists) + // .ok() + // .expect(dualBrandingIconHolder.find('img').nth(0).getAttribute('data-value')) + // .eql('maestro') + // .expect(dualBrandingIconHolder.find('img').nth(1).getAttribute('data-value')) + // .eql('bcmc'); + // + // await cardUtils.deleteCardNumber(t); + // + // await t + // // visible single brand icon holder + // .expect(singleBrandingIconHolder.filterVisible().exists) + // .ok() + // // hidden dual brand icon holder + // .expect(dualBrandingIconHolder.filterHidden().exists) + // .ok() + // .expect(singleBrandingIconHolder.find('img').nth(0).getAttribute('alt')) + // .eql('card'); + } + ); + + test( + 'Fill in dual branded card then ' + + 'select bcmc then ' + + 'ensure cvc field is hidden ' + + 'ensure only generic card logo shows after deleting digits and ' + + 'that the brand has been reset on paymentMethod data ' + + 'and the cvc field is shown again', + async t => { + // Start, allow time to load + // await start(t, 2000, TEST_SPEED); + // + // await cardUtils.fillCardNumber(t, BCMC_CARD); // dual branded with maestro + // + // await t + // // hidden single brand icon holder + // .expect(singleBrandingIconHolder.filterHidden().exists) + // .ok() + // // visible dual brand icon holder + // .expect(dualBrandingIconHolder.filterVisible().exists) + // .ok() + // .expect(dualBrandingIconHolder.find('img').nth(0).getAttribute('data-value')) + // .eql('maestro') + // .expect(dualBrandingIconHolder.find('img').nth(1).getAttribute('data-value')) + // .eql('bcmc'); + // + // // hidden cvc holder + // await t.expect(cvcField.filterHidden().exists).ok(); + // + // // Click BCMC brand icon + // await t.click(dualBrandingIconHolder.find('img').nth(1)); + // + // // Should be a brand property in the PM data + // await t.expect(getPropFromPMData('brand')).eql('bcmc'); + // + // await cardUtils.deleteCardNumber(t); + // + // // Generic card icon + // await t.expect(singleBrandingIconHolder.find('img').nth(0).getAttribute('alt')).eql('card'); + // + // // Should not be a brand property in the PM data + // await t.expect(getPropFromPMData('brand')).eql(undefined); + // + // // visible cvc holder + // await t.expect(cvcField.filterVisible().exists).ok(); + } + ); + + test( + 'Fill in dual branded card then ' + + 'paste in number not recognised by binLookup (but that internally is recognised as Visa)' + + 'ensure that Visa logo shows', + async t => { + // Start, allow time to load + // await start(t, 2000, TEST_SPEED); + // + // await cardUtils.fillCardNumber(t, BCMC_CARD); // dual branded with maestro + // + // await t + // // visible dual brand icon holder + // .expect(dualBrandingIconHolder.filterVisible().exists) + // .ok() + // .expect(dualBrandingIconHolder.find('img').nth(0).getAttribute('data-value')) + // .eql('maestro') + // .expect(dualBrandingIconHolder.find('img').nth(1).getAttribute('data-value')) + // .eql('bcmc'); + // + // await cardUtils.fillCardNumber(t, UNKNOWN_VISA_CARD, 'paste'); // number not recognised by binLookup + // + // // visa card icon + // await t.expect(singleBrandingIconHolder.find('img').nth(0).getAttribute('alt')).eql('visa'); + } + ); + + test( + 'Fill in dual branded card then ' + + 'select maestro then ' + + 'paste in number not recognised by binLookup (but that internally is recognised as Visa)' + + 'ensure that Visa logo shows ' + + 'that the brand has been reset on paymentMethod data ', + async t => { + // Start, allow time to load + // await start(t, 2000, TEST_SPEED); + // + // await cardUtils.fillCardNumber(t, BCMC_CARD); // dual branded with maestro + // + // await t + // // visible dual brand icon holder + // .expect(dualBrandingIconHolder.filterVisible().exists) + // .ok() + // .expect(dualBrandingIconHolder.find('img').nth(0).getAttribute('data-value')) + // .eql('maestro') + // .expect(dualBrandingIconHolder.find('img').nth(1).getAttribute('data-value')) + // .eql('bcmc'); + // + // // Click maestro brand icon + // await t.click(dualBrandingIconHolder.find('img').nth(0)); + // + // // Should be a brand property in the PM data + // await t.expect(getPropFromPMData('brand')).eql('maestro'); + // + // await cardUtils.fillCardNumber(t, UNKNOWN_VISA_CARD, 'paste'); // number not recognised by binLookup + // + // // visa card icon + // await t.expect(singleBrandingIconHolder.find('img').nth(0).getAttribute('alt')).eql('visa'); + // + // // Should not be a brand property in the PM data + // await t.expect(getPropFromPMData('brand')).eql(undefined); + } + ); +}); diff --git a/packages/e2e-playwright/tests/ui/customCard/branding/customcard.dualBranding.spec.ts b/packages/e2e-playwright/tests/ui/customCard/branding/customcard.dualBranding.spec.ts new file mode 100644 index 000000000..c49337423 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/customCard/branding/customcard.dualBranding.spec.ts @@ -0,0 +1,96 @@ +import { test } from '@playwright/test'; + +const singleBrandingIconHolder = '.secured-fields .pm-image'; +const dualBrandingIconHolder = '.secured-fields .pm-image-dual'; + +const getPropFromPMData = prop => { + return globalThis.securedFields.formatData().paymentMethod[prop]; +}; + +test.describe('Testing dual branding in custom card', () => { + test.beforeEach(async () => { + // await t.navigateTo(CUSTOMCARDS_URL); + // use 'customcard.dualBranding.clientScripts.js'; + }); + + test( + 'Fill in card number that will get dual branding result from binLookup, ' + 'then check that the expected icons/buttons are shown', + async () => { + // Start, allow time for iframes to load + // await start(t, 2000, TEST_SPEED); + // + // await t + // // default generic card logo + // .expect(singleBrandingIconHolder.find('img').nth(0).getAttribute('alt')) + // .eql('card'); + // + // // Fill card field with dual branded card (visa/cb) + // await cardUtils.fillCardNumber(t, DUAL_BRANDED_CARD); + // + // await t + // // hidden single brand icon holder + // .expect(singleBrandingIconHolder.filterHidden().exists) + // .ok() + // // visible dual brand icon holder + // .expect(dualBrandingIconHolder.filterVisible().exists) + // .ok() + // // with expected brand icons + // .expect(dualBrandingIconHolder.find('img').nth(0).getAttribute('alt')) + // .eql('visa') + // .expect(dualBrandingIconHolder.find('img').nth(1).getAttribute('alt')) + // .eql('cartebancaire'); + } + ); + + test( + 'Fill in card number that will get dual branding result from binLookup, ' + + 'then complete card without selecting dual brand,' + + 'then check it is valid,' + + 'then check PM data does not have a brand property', + async () => { + // Start, allow time for iframes to load + // await start(t, 2000, TEST_SPEED); + // + // // Fill card field with dual branded card (visa/cb) + // await cardUtils.fillCardNumber(t, DUAL_BRANDED_CARD); + // + // await cardUtils.fillDateAndCVC(t); + // + // // Expect card to now be valid + // await t.expect(getIsValid('securedFields')).eql(true); + // + // // Should not be a brand property in the PM data + // await t.expect(getPropFromPMData('brand')).eql(undefined); + } + ); + + test( + 'Fill in card number that will get dual branding result from binLookup, ' + + 'then complete card,' + + 'then check it is valid,' + + 'then select the dual brands,' + + 'then check PM data does have a corresponding brand property', + async () => { + // Start, allow time for iframes to load + // await start(t, 2000, TEST_SPEED); + // + // // Fill card field with dual branded card (visa/cb) + // await cardUtils.fillCardNumber(t, DUAL_BRANDED_CARD); + // + // await cardUtils.fillDateAndCVC(t); + // + // // Expect card to now be valid + // await t.expect(getIsValid('securedFields')).eql(true); + // + // // Click brand icons + // await t + // .click(dualBrandingIconHolder.find('img').nth(0)) + // .expect(getPropFromPMData('brand')) + // .eql('visa') + // .wait(1000) + // .click(dualBrandingIconHolder.find('img').nth(1)) + // .expect(getPropFromPMData('brand')) + // .eql('cartebancaire'); + } + ); +}); diff --git a/packages/e2e/tests/customcard/branding/customcard.plcc.clientScripts.js b/packages/e2e-playwright/tests/ui/customCard/branding/customcard.plcc.clientScripts.js similarity index 100% rename from packages/e2e/tests/customcard/branding/customcard.plcc.clientScripts.js rename to packages/e2e-playwright/tests/ui/customCard/branding/customcard.plcc.clientScripts.js diff --git a/packages/e2e-playwright/tests/ui/customCard/branding/customcard.plcc.luhn.spec.ts b/packages/e2e-playwright/tests/ui/customCard/branding/customcard.plcc.luhn.spec.ts new file mode 100644 index 000000000..95ddb547b --- /dev/null +++ b/packages/e2e-playwright/tests/ui/customCard/branding/customcard.plcc.luhn.spec.ts @@ -0,0 +1,83 @@ +import { test } from '@playwright/test'; + +const getPropFromStateValid = prop => { + return globalThis.securedFields.state.valid[prop]; +}; + +// Applies to regular, single date field custom card +const iframeSelectorRegular = '.secured-fields iframe'; + +test.describe('Testing PLCCs, in the custom card component)', () => { + test.beforeEach(async () => { + // await t.navigateTo(CUSTOMCARDS_URL); + // use customcard.plcc.clientScripts.js + }); + test( + 'Test with a plcc no. that does NOT need a luhn check and see the number become valid ' + + 'then with a plcc no. that DOES need a luhn check but alter last digit so it fails luhn check to ensure the luhn check "reset" has occurred ' + + 'then edit the number to the correct form and see that it passes luhn check and is valid', + async () => { + // Start, allow time for iframes to load + // await start(t, 2000, TEST_SPEED); + // + // // Enter num + // await cardUtilsRegular.fillCardNumber(t, SYNCHRONY_PLCC_NO_LUHN); + // // num is valid w/o luhn check + // await t.expect(getPropFromStateValid('encryptedCardNumber')).eql(true); + // + // // Enter bin that needs luhn check but replace last digit to one that will mean number fails luhn check + // const partialNum = SYNCHRONY_PLCC_WITH_LUHN.substr(0, 15); + // await cardUtilsRegular.fillCardNumber(t, partialNum + '8', 'replace'); + // + // // num is not valid + // await t.expect(getPropFromStateValid('encryptedCardNumber')).eql(false); + // + // // delete last digit + // cardUtilsRegular.deleteDigitsFromCardNumber(t, 18, 19); + // + // // now enter correct last digit + // const endNum = SYNCHRONY_PLCC_WITH_LUHN.substr(15, 16); + // await cardUtilsRegular.fillCardNumber(t, endNum); + // + // // num is valid w. luhn check + // await t.expect(getPropFromStateValid('encryptedCardNumber')).eql(true); + } + ); + + test( + 'Test with a plcc no. that DOES need a luhn check and see the number become valid ' + + 'then paste a plcc no. that does NOT need a luhn check and see the number become valid because the luhn check "reset" has occurred even after pasting ' + + 'then paste the first number but with an altered last digit that will fail the luhn check and will not be valid because the luhn check "reset" has occurred again' + + 'then edit the number to the correct form and see that it passes luhn check and is valid', + async () => { + // Start, allow time for iframes to load + // await start(t, 2000, TEST_SPEED); + // + // // Enter num + // await cardUtilsRegular.fillCardNumber(t, SYNCHRONY_PLCC_WITH_LUHN); + // // num is valid + // await t.expect(getPropFromStateValid('encryptedCardNumber')).eql(true); + // + // // Paste num w/o luhn check + // await cardUtilsRegular.fillCardNumber(t, SYNCHRONY_PLCC_NO_LUHN, 'paste'); + // // num is valid + // await t.expect(getPropFromStateValid('encryptedCardNumber')).eql(true); + // + // // Enter bin that needs luhn check but replace last digit to one that will mean number fails luhn check + // const partialNum = SYNCHRONY_PLCC_WITH_LUHN.substr(0, 15); + // await cardUtilsRegular.fillCardNumber(t, partialNum + '8', 'paste'); + // // num is not valid + // await t.expect(getPropFromStateValid('encryptedCardNumber')).eql(false); + // + // // delete last digit + // cardUtilsRegular.deleteDigitsFromCardNumber(t, 18, 19); + // + // // now enter correct last digit + // const endNum = SYNCHRONY_PLCC_WITH_LUHN.substr(15, 16); + // await cardUtilsRegular.fillCardNumber(t, endNum); + // + // // num is valid w. luhn check + // await t.expect(getPropFromStateValid('encryptedCardNumber')).eql(true); + } + ); +}); diff --git a/packages/e2e-playwright/tests/ui/customCard/branding/customcard.singleBranding.resets.spec.ts b/packages/e2e-playwright/tests/ui/customCard/branding/customcard.singleBranding.resets.spec.ts new file mode 100644 index 000000000..8a76ac0c0 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/customCard/branding/customcard.singleBranding.resets.spec.ts @@ -0,0 +1,90 @@ +import { test } from '@playwright/test'; + +const singleBrandingIconHolder = '.secured-fields .pm-image'; +const dualBrandingIconHolder = '.secured-fields .pm-image-dual'; + +const getPropFromPMData = prop => { + return globalThis.securedFields.formatData().paymentMethod[prop]; +}; + +const iframe = '.secured-fields iframe'; + +test.describe('Testing single branding resets in custom card', () => { + test.beforeEach(async () => { + // await t.navigateTo(CUSTOMCARDS_URL); + // use customcard.dualBranding.clientScripts.js + }); + test( + 'Fill in regular MC card then ' + + 'check brand logo is shown, then ' + + 'check that a brand has been set on PM data, then' + + 'paste in a card unrecognised by binLookup, ' + + 'check that the brand has been reset on paymentMethod data and ' + + 'that the internal regex have recognised the unrecognised card as Visa', + async () => { + // Start, allow time to load + // await start(t, 2000, TEST_SPEED); + // + // await cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); + // + // // mc logo shown + // await t + // // visible single brand icon holder + // .expect(singleBrandingIconHolder.filterVisible().exists) + // .ok() + // // hidden dual brand icon holder + // .expect(dualBrandingIconHolder.filterHidden().exists) + // .ok() + // .expect(singleBrandingIconHolder.find('img').nth(0).getAttribute('alt')) + // .eql('mc'); + // + // // Should be a brand property in the PM data + // await t.expect(getPropFromPMData('brand')).eql('mc'); + // + // await cardUtils.fillCardNumber(t, UNKNOWN_VISA_CARD, 'paste'); // number not recognised by binLookup + // + // // Should not be a brand property in the PM data + // await t.expect(getPropFromPMData('brand')).eql(undefined); + // + // // visa card icon (internal regex has run) + // await t.expect(singleBrandingIconHolder.find('img').nth(0).getAttribute('alt')).eql('visa'); + } + ); + + test( + 'Fill in regular MC card then ' + + 'check that a brand has been set on PM data, then ' + + 'delete digits and , ' + + 'check that the brand has been reset on paymentMethod data ' + + 'and the generic card icon is shown', + async () => { + // Start, allow time to load + // await start(t, 2000, TEST_SPEED); + // + // await cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); + // + // // mc logo shown + // await t + // // visible single brand icon holder + // .expect(singleBrandingIconHolder.filterVisible().exists) + // .ok() + // // hidden dual brand icon holder + // .expect(dualBrandingIconHolder.filterHidden().exists) + // .ok() + // .expect(singleBrandingIconHolder.find('img').nth(0).getAttribute('alt')) + // .eql('mc'); + // + // // Should be a brand property in the PM data + // await t.expect(getPropFromPMData('brand')).eql('mc'); + // + // // Delete number + // await cardUtils.deleteCardNumber(t); + // + // // Brand property in the PM data should be reset + // await t.expect(getPropFromPMData('brand')).eql(undefined); + // + // // Generic card icon + // await t.expect(singleBrandingIconHolder.find('img').nth(0).getAttribute('alt')).eql('card'); + } + ); +}); diff --git a/packages/e2e/tests/customcard/branding/customcard.unsupportedCard.clientScripts.js b/packages/e2e-playwright/tests/ui/customCard/branding/customcard.unsupportedCard.clientScripts.js similarity index 100% rename from packages/e2e/tests/customcard/branding/customcard.unsupportedCard.clientScripts.js rename to packages/e2e-playwright/tests/ui/customCard/branding/customcard.unsupportedCard.clientScripts.js diff --git a/packages/e2e-playwright/tests/ui/customCard/branding/customcard.unsupportedCard.spec.ts b/packages/e2e-playwright/tests/ui/customCard/branding/customcard.unsupportedCard.spec.ts new file mode 100644 index 000000000..33058881a --- /dev/null +++ b/packages/e2e-playwright/tests/ui/customCard/branding/customcard.unsupportedCard.spec.ts @@ -0,0 +1,201 @@ +import { test } from '@playwright/test'; +import LANG from '@adyen/adyen-web-server/translations/en-US.json'; + +const BASE_REF = 'securedFields'; +const UNSUPPORTED_CARD = LANG['cc.num.903']; + +test.describe('Testing persistence of "Unsupported card" error and state at both component & securedField level', () => { + test.beforeEach(async () => { + //await t.navigateTo(CustomCardComponentPage); + // 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(); + // use customcard.unsupportedCard.clientScripts.js + }); + + test( + 'Enter partial card number that is not supported then ' + + 'check resulting error is processed correctly at merchant and SF level then ' + + 'add digits to number and check error states persist then ' + + 'complete number to make it "valid" (right length, passes luhn then ' + + 'check error states persist then ' + + 'delete number & check error states clear then ' + + 'add supported number and check field is seen as valid at merchant and SF levels', + async () => { + // Start, allow time to load + // await start(t, 1000, TEST_SPEED); + // + // // Hidden error field + // await t.expect(cardPage.numErrorText.filterHidden().exists).ok(); + // + // // Add partial number + // await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD.substr(0, 11)); + // + // // Components level error field visible & text set + // await t + // .expect(cardPage.numErrorText.filterVisible().exists) + // .ok() + // .expect(cardPage.numErrorText.withExactText(UNSUPPORTED_CARD).exists) + // .ok(); + // + // /** + // * Error received & processed at SF level + // */ + // // Expect input in iframe to have aria-invalid set to true + // await cardPage.cardUtils.checkIframeForAttrVal(t, 0, 'encryptedCardNumber', 'aria-invalid', 'true'); + // + // // Expect error field in iframe to be filled + // await checkIframeElHasExactText(t, cardPage.iframeSelector, 0, getAriaErrorField('encryptedCardNumber'), UNSUPPORTED_CARD); + // + // // Click button - to force blur event + // await t.click(cardPage.payButton); + // + // // Expect unsupported card error in iframe to persist (and not become 'incomplete field') + // await checkIframeElHasExactText(t, cardPage.iframeSelector, 0, getAriaErrorField('encryptedCardNumber'), UNSUPPORTED_CARD); + // + // // Add more digits + // await cardPage.cardUtils.fillCardNumber(t, '0000'); + // + // // Components level error field persists + // await t.expect(cardPage.numErrorText.withExactText(UNSUPPORTED_CARD).exists).ok(); + // + // /** + // * Expect errors received & processed at SF level to persist + // */ + // // Expect input in iframe to have aria-invalid set to true + // await cardPage.cardUtils.checkIframeForAttrVal(t, 0, 'encryptedCardNumber', 'aria-invalid', 'true'); + // + // // Expect error field in iframe to be filled + // await checkIframeElHasExactText(t, cardPage.iframeSelector, 0, getAriaErrorField('encryptedCardNumber'), UNSUPPORTED_CARD); + // + // // Add last digit to complete a number that passes luhn + // await cardPage.cardUtils.fillCardNumber(t, '4'); + // + // // Field should be marked in state as not valid + // await t.expect(cardPage.getFromState(BASE_REF, 'valid.encryptedCardNumber')).eql(false); + // + // // Merchant/components level error field persists + // await t.expect(cardPage.numErrorText.withExactText(UNSUPPORTED_CARD).exists).ok(); + // + // // Expect errors received & processed at SF level to persist + // await cardPage.cardUtils.checkIframeForAttrVal(t, 0, 'encryptedCardNumber', 'aria-invalid', 'true'); + // + // await checkIframeElHasExactText(t, cardPage.iframeSelector, 0, getAriaErrorField('encryptedCardNumber'), UNSUPPORTED_CARD); + // + // // Delete number + // await cardPage.cardUtils.deleteCardNumber(t); + // + // // Components level error field hidden + // await t.expect(cardPage.numErrorText.filterHidden().exists).ok(); + // + // /** + // * Expect error clearing received & processed at SF level + // */ + // // Expect input in iframe to have aria-invalid set to true + // await cardPage.cardUtils.checkIframeForAttrVal(t, 0, 'encryptedCardNumber', 'aria-invalid', 'false'); + // + // // Expect error field in iframe to be unfilled + // await checkIframeElHasExactText(t, cardPage.iframeSelector, 0, getAriaErrorField('encryptedCardNumber'), ''); + // + // // Add supported number + // await cardPage.cardUtils.fillCardNumber(t, MAESTRO_CARD); + // + // // Field should be marked in state as valid... + // await t.expect(cardPage.getFromState(BASE_REF, 'valid.encryptedCardNumber')).eql(true); + // + // // ...with encrypted blob + // + // // Extract & decode JWE header + // const JWEToken = await cardPage.getFromState(BASE_REF, 'data.encryptedCardNumber'); + // const JWETokenArr = JWEToken.split('.'); + // const blobHeader = JWETokenArr[0]; + // const base64Decoded = await cardPage.decodeBase64(blobHeader); + // const headerObj = JSON.parse(base64Decoded); + // + // // Look for expected properties + // await t.expect(JWETokenArr.length).eql(5); // Expected number of components in the JWE token + // + // await t.expect(headerObj.alg).eql(JWE_ALG).expect(headerObj.enc).eql(JWE_CONTENT_ALG).expect(headerObj.version).eql(JWE_VERSION); + // + // // await t.expect(cardPage.getFromState(BASE_REF, 'data.encryptedCardNumber')).contains('adyenjs_0_1_'); + // + // /** + // * Validity received & processed at SF level + // */ + // // Expect input in iframe to have aria-invalid set to false + // await cardPage.cardUtils.checkIframeForAttrVal(t, 0, 'encryptedCardNumber', 'aria-invalid', 'false'); + // + // // Expect error field in iframe to be unfilled + // await checkIframeElHasExactText(t, cardPage.iframeSelector, 0, getAriaErrorField('encryptedCardNumber'), ''); + } + ); + + test( + 'Enter unsupported card and expect similar errors in UI, state & SF as previous test, then ' + + 'when adding supported number we paste it straight in then ' + + 'check error states are removed at merchant and SF levels then ' + + 'check field is seen as valid at merchant and SF levels', + async () => { + // Start, allow time to load + // await start(t, 1000, TEST_SPEED); + // + // // Add unsupported number + // await cardPage.cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); + // + // // Components level error field visible & text set + // await t + // .expect(cardPage.numErrorText.filterVisible().exists) + // .ok() + // .expect(cardPage.numErrorText.withExactText(UNSUPPORTED_CARD).exists) + // .ok(); + // + // /** + // * Error received & processed at SF level + // */ + // // Expect input in iframe to have aria-invalid set to true + // await cardPage.cardUtils.checkIframeForAttrVal(t, 0, 'encryptedCardNumber', 'aria-invalid', 'true'); + // + // // Expect error field in iframe to be filled + // await checkIframeElHasExactText(t, cardPage.iframeSelector, 0, getAriaErrorField('encryptedCardNumber'), UNSUPPORTED_CARD); + // + // // Field should be marked in state as not valid + // await t.expect(cardPage.getFromState(BASE_REF, 'valid.encryptedCardNumber')).eql(false); + // + // /** + // * Paste in supported number + // */ + // await cardPage.cardUtils.fillCardNumber(t, MAESTRO_CARD, 'paste'); + // + // // Merchant/components level error field hidden + // await t.expect(cardPage.numErrorText.filterHidden().exists).ok(); + // + // // Field should be marked in state as valid... + // await t.expect(cardPage.getFromState(BASE_REF, 'valid.encryptedCardNumber')).eql(true); + // + // // ...with encrypted blob + // + // // Extract & decode JWE header + // const JWEToken = await cardPage.getFromState(BASE_REF, 'data.encryptedCardNumber'); + // const JWETokenArr = JWEToken.split('.'); + // const blobHeader = JWETokenArr[0]; + // const base64Decoded = await cardPage.decodeBase64(blobHeader); + // const headerObj = JSON.parse(base64Decoded); + // + // // Look for expected properties + // await t.expect(JWETokenArr.length).eql(5); // Expected number of components in the JWE token + // + // await t.expect(headerObj.alg).eql(JWE_ALG).expect(headerObj.enc).eql(JWE_CONTENT_ALG).expect(headerObj.version).eql(JWE_VERSION); + // + // // await t.expect(cardPage.getFromState(BASE_REF, 'data.encryptedCardNumber')).contains('adyenjs_0_1_'); + // + // /** + // * Validity received & processed at SF level + // */ + // // Expect input in iframe to have aria-invalid set to false + // await cardPage.cardUtils.checkIframeForAttrVal(t, 0, 'encryptedCardNumber', 'aria-invalid', 'false'); + // + // // Expect error field in iframe to be unfilled + // await checkIframeElHasExactText(t, cardPage.iframeSelector, 0, getAriaErrorField('encryptedCardNumber'), ''); + } + ); +}); diff --git a/packages/e2e-playwright/tests/customCard/expiryDate/customCard.regular.expiryDatePolicies.hidden.spec.ts b/packages/e2e-playwright/tests/ui/customCard/expiryDate/customCard.regular.expiryDatePolicies.hidden.spec.ts similarity index 55% rename from packages/e2e-playwright/tests/customCard/expiryDate/customCard.regular.expiryDatePolicies.hidden.spec.ts rename to packages/e2e-playwright/tests/ui/customCard/expiryDate/customCard.regular.expiryDatePolicies.hidden.spec.ts index 315a53446..70acd7fdf 100644 --- a/packages/e2e-playwright/tests/customCard/expiryDate/customCard.regular.expiryDatePolicies.hidden.spec.ts +++ b/packages/e2e-playwright/tests/ui/customCard/expiryDate/customCard.regular.expiryDatePolicies.hidden.spec.ts @@ -1,8 +1,14 @@ -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 '../../../../server/translations/en-US.json'; +import { test, expect } from '../../../../fixtures/customCard.fixture'; +import { + ENCRYPTED_CARD_NUMBER, + ENCRYPTED_EXPIRY_DATE, + ENCRYPTED_SECURITY_CODE, + INVALID_TEST_DATE_VALUE, + REGULAR_TEST_CARD +} from '../../../utils/constants'; +import { binLookupMock } from '../../../../mocks/binLookup/binLookup.mock'; +import { hiddenDateAndCvcMock } from '../../../../mocks/binLookup/binLookup.data'; +import LANG from '../../../../../server/translations/en-US.json'; const PAN_ERROR = LANG['cc.num.900']; const DATE_INVALID_ERROR = LANG['cc.dat.912']; @@ -10,47 +16,39 @@ const DATE_EMPTY_ERROR = LANG['cc.dat.910']; const CVC_ERROR = LANG['cc.cvc.920']; 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; - + test('#1 how UI & state respond', async ({ page, customCard }) => { await binLookupMock(page, hiddenDateAndCvcMock); - await card.isComponentVisible(); - // Fill number to provoke (mock) binLookup response - await card.typeCardNumber(REGULAR_TEST_CARD); + await customCard.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(); + await expect(customCard.expiryDateField).not.toBeVisible(); + await expect(customCard.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(); + await customCard.deleteCardNumber(); + await expect(customCard.expiryDateField).toBeVisible(); + await expect(customCard.cvcField).toBeVisible(); }); - test('#2 validating fields first and then entering PAN should see errors cleared from state', async ({ customCardPage }) => { - const { card, page } = customCardPage; - + test('#2 validating fields first and then entering PAN should see errors cleared from state', async ({ page, customCard }) => { await binLookupMock(page, hiddenDateAndCvcMock); - await card.isComponentVisible(); - // Click pay - await customCardPage.pay(); + await customCard.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); + await expect(customCard.cardNumberErrorElement).toBeVisible(); + await expect(customCard.cardNumberErrorElement).toHaveText(PAN_ERROR); + await expect(customCard.expiryDateErrorElement).toBeVisible(); + await expect(customCard.expiryDateErrorElement).toHaveText(DATE_EMPTY_ERROR); + await expect(customCard.cvcErrorElement).toBeVisible(); + await expect(customCard.cvcErrorElement).toHaveText(CVC_ERROR); await page.waitForTimeout(500); // wait for UI to show errors @@ -61,7 +59,7 @@ test.describe('Test how Custom Card Component with regular date field handles hi await expect(cardErrors[ENCRYPTED_SECURITY_CODE]).not.toBe(undefined); // Fill number to provoke (mock) binLookup response - await card.typeCardNumber(REGULAR_TEST_CARD); + await customCard.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 @@ -74,31 +72,29 @@ test.describe('Test how Custom Card Component with regular date field handles hi 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; + // flaky + test('#3 date field in error does not stop card becoming valid', async ({ browserName, page, customCard }) => { + test.skip(browserName === 'webkit', 'Skipping tests for WebKit'); await binLookupMock(page, hiddenDateAndCvcMock); - await card.isComponentVisible(); - // Card out of date - await card.typeExpiryDate('12/90'); + await customCard.typeExpiryDate(INVALID_TEST_DATE_VALUE); // Expect error in UI - await expect(card.expiryDateField).toBeVisible(); - await expect(card.expiryDateErrorElement).toBeVisible(); - await expect(card.expiryDateErrorElement).toHaveText(DATE_INVALID_ERROR); + await expect(customCard.expiryDateField).toBeVisible(); + await expect(customCard.expiryDateErrorElement).toBeVisible(); + await expect(customCard.expiryDateErrorElement).toHaveText(DATE_INVALID_ERROR); // Force blur event to fire on date field - await card.cardNumberLabelElement.click(); + await customCard.cardNumberLabelElement.click(); // Fill number to provoke (mock) binLookup response - await card.typeCardNumber(REGULAR_TEST_CARD); + await customCard.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(); + await expect(customCard.expiryDateField).not.toBeVisible(); + await expect(customCard.cvcField).not.toBeVisible(); // Card seen as valid (despite date field technically being in error) let cardValid = await page.evaluate('window.customCard.isValid'); @@ -108,15 +104,15 @@ test.describe('Test how Custom Card Component with regular date field handles hi let cardErrors: any = await page.evaluate('window.customCard.state.errors'); await expect(cardErrors[ENCRYPTED_EXPIRY_DATE]).not.toBe(undefined); - await card.deleteCardNumber(); + await customCard.deleteCardNumber(); // Errors in UI visible again - await expect(card.expiryDateField).toBeVisible(); - await expect(card.expiryDateErrorElement).toBeVisible(); - await expect(card.expiryDateErrorElement).toHaveText(DATE_INVALID_ERROR); + await expect(customCard.expiryDateField).toBeVisible(); + await expect(customCard.expiryDateErrorElement).toBeVisible(); + await expect(customCard.expiryDateErrorElement).toHaveText(DATE_INVALID_ERROR); // CVC visible again - await expect(card.cvcField).toBeVisible(); + await expect(customCard.cvcField).toBeVisible(); // Headless test seems to need time for UI change to register on state await page.waitForTimeout(500); diff --git a/packages/e2e-playwright/tests/ui/customCard/expiryDate/customCard.regular.expiryDatePolicies.optional.spec.ts b/packages/e2e-playwright/tests/ui/customCard/expiryDate/customCard.regular.expiryDatePolicies.optional.spec.ts new file mode 100644 index 000000000..7816a2194 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/customCard/expiryDate/customCard.regular.expiryDatePolicies.optional.spec.ts @@ -0,0 +1,136 @@ +import { test, expect } from '../../../../fixtures/customCard.fixture'; +import { + ENCRYPTED_CARD_NUMBER, + ENCRYPTED_EXPIRY_DATE, + ENCRYPTED_SECURITY_CODE, + INVALID_TEST_DATE_VALUE, + REGULAR_TEST_CARD +} from '../../../utils/constants'; +import LANG from '../../../../../server/translations/en-US.json'; +import { binLookupMock } from '../../../../mocks/binLookup/binLookup.mock'; +import { optionalDateAndCvcMock } from '../../../../mocks/binLookup/binLookup.data'; + +const DATE_LABEL = LANG['creditCard.expiryDate.label']; +const CVC_LABEL = LANG['creditCard.securityCode.label']; +const CVC_LABEL_OPTIONAL = LANG['creditCard.securityCode.label.optional']; + +const OPTIONAL = LANG['field.title.optional']; + +const PAN_ERROR = LANG['cc.num.900']; +const DATE_INVALID_ERROR = LANG['cc.dat.912']; +const DATE_EMPTY_ERROR = LANG['cc.dat.910']; +const CVC_ERROR = LANG['cc.cvc.920']; + +test.describe('Test how Custom Card Component with regular date field handles hidden expiryDate policy', () => { + test('#1 how UI & state respond', async ({ page, customCard }) => { + await binLookupMock(page, optionalDateAndCvcMock); + + // Regular date label + await expect(customCard.expiryDateLabelText).toHaveText(DATE_LABEL); + + // Fill number to provoke (mock) binLookup response + await customCard.typeCardNumber(REGULAR_TEST_CARD); + + // UI reflects that binLookup says expiryDate is optional + await expect(customCard.expiryDateLabelText).toContainText(`${DATE_LABEL} ${OPTIONAL}`); + + // ...and cvc is optional too + await expect(customCard.cvcLabelText).toContainText(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 customCard.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(customCard.expiryDateLabelText).toContainText(DATE_LABEL); + await expect(customCard.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('#3 validating fields first and then entering PAN should see errors cleared from both UI & state', async ({ page, customCard }) => { + await binLookupMock(page, optionalDateAndCvcMock); + + // press pay to generate errors + await customCard.pay(); + + // Expect errors in UI + await expect(customCard.cardNumberErrorElement).toBeVisible(); + await expect(customCard.cardNumberErrorElement).toHaveText(PAN_ERROR); + await expect(customCard.expiryDateErrorElement).toBeVisible(); + await expect(customCard.expiryDateErrorElement).toHaveText(DATE_EMPTY_ERROR); + await expect(customCard.cvcErrorElement).toBeVisible(); + await expect(customCard.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 customCard.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(customCard.cardNumberErrorElement).not.toBeVisible(); + await expect(customCard.expiryDateErrorElement).not.toBeVisible(); + await expect(customCard.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); + }); + // todo: flaky + test.fixme('#4 date field in error DOES stop card becoming valid', async ({ page, customCard }) => { + await binLookupMock(page, optionalDateAndCvcMock); + + // Card out of date + await customCard.typeExpiryDate(INVALID_TEST_DATE_VALUE); + + // Expect error in UI + await expect(customCard.expiryDateErrorElement).toBeVisible(); + await expect(customCard.expiryDateErrorElement).toHaveText(DATE_INVALID_ERROR); + + // Force blur event to fire on date field + await customCard.cardNumberLabelElement.click(); + + // Fill number to provoke (mock) binLookup response + await customCard.typeCardNumber(REGULAR_TEST_CARD); + + // UI reflects that binLookup says expiryDate is optional + await expect(customCard.expiryDateLabelText).toContainText(`${DATE_LABEL} ${OPTIONAL}`); + + // Visual errors persist in UI + await expect(customCard.expiryDateErrorElement).toBeVisible(); + await expect(customCard.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 customCard.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/ui/customCard/expiryDate/customCard.separate.expiryDatePolicies.hidden.spec.ts similarity index 50% rename from packages/e2e-playwright/tests/customCard/expiryDate/customCard.separate.expiryDatePolicies.hidden.spec.ts rename to packages/e2e-playwright/tests/ui/customCard/expiryDate/customCard.separate.expiryDatePolicies.hidden.spec.ts index 6c2903dad..9dae5c285 100644 --- a/packages/e2e-playwright/tests/customCard/expiryDate/customCard.separate.expiryDatePolicies.hidden.spec.ts +++ b/packages/e2e-playwright/tests/ui/customCard/expiryDate/customCard.separate.expiryDatePolicies.hidden.spec.ts @@ -1,14 +1,14 @@ -import { test, expect } from '../../../pages/customCard/customCard.fixture'; +import { test, expect } from '../../../../fixtures/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 '../../../../server/translations/en-US.json'; +} from '../../../utils/constants'; +import LANG from '../../../../../server/translations/en-US.json'; +import { binLookupMock } from '../../../../mocks/binLookup/binLookup.mock'; +import { hiddenDateAndCvcMock } from '../../../../mocks/binLookup/binLookup.data'; const PAN_ERROR = LANG['cc.num.900']; const MONTH_EMPTY_ERROR = LANG['cc.mth.915']; @@ -17,51 +17,43 @@ const DATE_INVALID_ERROR = LANG['cc.dat.913']; const CVC_ERROR = LANG['cc.cvc.920']; 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; - + test('#1 how UI & state respond', async ({ page, customCardSeparateExpiryDate }) => { await binLookupMock(page, hiddenDateAndCvcMock); - await card.isSeparateComponentVisible(); - // Fill number to provoke (mock) binLookup response - await card.typeCardNumber(REGULAR_TEST_CARD); + await customCardSeparateExpiryDate.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(); + await expect(customCardSeparateExpiryDate.expiryMonthField).not.toBeVisible(); + await expect(customCardSeparateExpiryDate.expiryYearField).not.toBeVisible(); + await expect(customCardSeparateExpiryDate.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(); + await customCardSeparateExpiryDate.deleteCardNumber(); + await expect(customCardSeparateExpiryDate.expiryMonthField).toBeVisible(); + await expect(customCardSeparateExpiryDate.expiryYearField).toBeVisible(); + await expect(customCardSeparateExpiryDate.cvcField).toBeVisible(); }); - test('#2 validating fields first and then entering PAN should see errors cleared from state', async ({ customCardPageSeparate }) => { - const { card, page } = customCardPageSeparate; - + test('#2 validating fields first and then entering PAN should see errors cleared from state', async ({ page, customCardSeparateExpiryDate }) => { await binLookupMock(page, hiddenDateAndCvcMock); - await card.isSeparateComponentVisible(); - // Click pay - await customCardPageSeparate.pay('Separate'); + await customCardSeparateExpiryDate.pay(); // 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 expect(customCardSeparateExpiryDate.cardNumberErrorElement).toBeVisible(); + await expect(customCardSeparateExpiryDate.cardNumberErrorElement).toHaveText(PAN_ERROR); + await expect(customCardSeparateExpiryDate.expiryMonthErrorElement).toBeVisible(); + await expect(customCardSeparateExpiryDate.expiryMonthErrorElement).toHaveText(MONTH_EMPTY_ERROR); + await expect(customCardSeparateExpiryDate.expiryYearErrorElement).toBeVisible(); + await expect(customCardSeparateExpiryDate.expiryYearErrorElement).toHaveText(YEAR_EMPTY_ERROR); + await expect(customCardSeparateExpiryDate.cvcErrorElement).toBeVisible(); + await expect(customCardSeparateExpiryDate.cvcErrorElement).toHaveText(CVC_ERROR); // Expect errors in state let cardErrors: any = await page.evaluate('window.customCardSeparate.state.errors'); @@ -71,7 +63,7 @@ test.describe('Test how Custom Card Component with separate date field handles h await expect(cardErrors[ENCRYPTED_SECURITY_CODE]).not.toBe(undefined); // Fill number to provoke (mock) binLookup response - await card.typeCardNumber(REGULAR_TEST_CARD); + await customCardSeparateExpiryDate.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 @@ -85,32 +77,28 @@ test.describe('Test how Custom Card Component with separate date field handles h 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; - + // todo: flaky + test.fixme('#3 date field in error does not stop card becoming valid', async ({ page, customCardSeparateExpiryDate }) => { await binLookupMock(page, hiddenDateAndCvcMock); - await card.isSeparateComponentVisible(); - // Card out of date - await card.typeExpiryMonth('12'); - await card.typeExpiryYear('90'); + await customCardSeparateExpiryDate.typeExpiryMonth('12'); + await customCardSeparateExpiryDate.typeExpiryYear('90'); // Expect error in UI - await expect(card.expiryYearErrorElement).toBeVisible(); - await expect(card.expiryYearErrorElement).toHaveText(DATE_INVALID_ERROR); + await expect(customCardSeparateExpiryDate.expiryYearErrorElement).toBeVisible(); + await expect(customCardSeparateExpiryDate.expiryYearErrorElement).toHaveText(DATE_INVALID_ERROR); // Force blur event to fire on year field - await card.cardNumberLabelElement.click(); + await customCardSeparateExpiryDate.cardNumberLabelElement.click(); // Fill number to provoke (mock) binLookup response - await card.typeCardNumber(REGULAR_TEST_CARD); + await customCardSeparateExpiryDate.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(); + await expect(customCardSeparateExpiryDate.expiryMonthField).not.toBeVisible(); + await expect(customCardSeparateExpiryDate.expiryYearField).not.toBeVisible(); + await expect(customCardSeparateExpiryDate.cvcField).not.toBeVisible(); // Card seen as valid (despite date technically being in error) let cardValid = await page.evaluate('window.customCardSeparate.isValid'); @@ -120,16 +108,16 @@ test.describe('Test how Custom Card Component with separate date field handles h let cardErrors: any = await page.evaluate('window.customCardSeparate.state.errors'); await expect(cardErrors[ENCRYPTED_EXPIRY_YEAR]).not.toBe(undefined); - await card.deleteCardNumber(); + await customCardSeparateExpiryDate.deleteCardNumber(); // Errors in UI visible again - await expect(card.expiryYearField).toBeVisible(); - await expect(card.expiryYearErrorElement).toBeVisible(); - await expect(card.expiryYearErrorElement).toHaveText(DATE_INVALID_ERROR); + await expect(customCardSeparateExpiryDate.expiryYearField).toBeVisible(); + await expect(customCardSeparateExpiryDate.expiryYearErrorElement).toBeVisible(); + await expect(customCardSeparateExpiryDate.expiryYearErrorElement).toHaveText(DATE_INVALID_ERROR); // Other fields visible again - await expect(card.expiryMonthField).toBeVisible(); - await expect(card.cvcField).toBeVisible(); + await expect(customCardSeparateExpiryDate.expiryMonthField).toBeVisible(); + await expect(customCardSeparateExpiryDate.cvcField).toBeVisible(); // Headless test seems to need time for UI change to register on state await page.waitForTimeout(500); diff --git a/packages/e2e-playwright/tests/ui/customCard/expiryDate/customCard.separate.expiryDatePolicies.optional.spec.ts b/packages/e2e-playwright/tests/ui/customCard/expiryDate/customCard.separate.expiryDatePolicies.optional.spec.ts new file mode 100644 index 000000000..6b9942501 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/customCard/expiryDate/customCard.separate.expiryDatePolicies.optional.spec.ts @@ -0,0 +1,156 @@ +import { test, expect } from '../../../../fixtures/customCard.fixture'; +import { + ENCRYPTED_CARD_NUMBER, + ENCRYPTED_EXPIRY_MONTH, + ENCRYPTED_EXPIRY_YEAR, + ENCRYPTED_SECURITY_CODE, + REGULAR_TEST_CARD +} from '../../../utils/constants'; +import LANG from '../../../../../server/translations/en-US.json'; +import { binLookupMock } from '../../../../mocks/binLookup/binLookup.mock'; +import { optionalDateAndCvcMock } from '../../../../mocks/binLookup/binLookup.data'; + +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['cc.num.900']; +const MONTH_EMPTY_ERROR = LANG['cc.mth.915']; +const YEAR_EMPTY_ERROR = LANG['cc.yr.917']; +const CVC_ERROR = LANG['cc.cvc.920']; +const DATE_INVALID_ERROR = LANG['cc.dat.913']; + +test.describe('Test how Custom Card Component with separate date fields handles hidden expiryDate policy', () => { + test('#1 how UI & state respond', async ({ page, customCardSeparateExpiryDate }) => { + await binLookupMock(page, optionalDateAndCvcMock); + + // Regular date labels + await expect(customCardSeparateExpiryDate.expiryMonthLabelText).toContainText(MONTH_LABEL); + await expect(customCardSeparateExpiryDate.expiryYearLabelText).toContainText(YEAR_LABEL); + + // Fill number to provoke (mock) binLookup response + await customCardSeparateExpiryDate.typeCardNumber(REGULAR_TEST_CARD); + + // await page.waitForTimeout(5000); + + // UI reflects that binLookup says date fields are optional + await expect(customCardSeparateExpiryDate.expiryMonthLabelText).toContainText(`${MONTH_LABEL} ${OPTIONAL}`); + await expect(customCardSeparateExpiryDate.expiryYearLabelText).toContainText(`${YEAR_LABEL} ${OPTIONAL}`); + + // ...and cvc is optional too + await expect(customCardSeparateExpiryDate.cvcLabelText).toContainText(`${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 customCardSeparateExpiryDate.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(customCardSeparateExpiryDate.expiryMonthLabelText).toContainText(MONTH_LABEL); + await expect(customCardSeparateExpiryDate.expiryYearLabelText).toContainText(YEAR_LABEL); + await expect(customCardSeparateExpiryDate.cvcLabelText).toContainText(CVC_LABEL); + + // Card seen as invalid + cardValid = await page.evaluate('window.customCardSeparate.isValid'); + await expect(cardValid).toEqual(false); + }); + + test('#3 validating fields first and then entering PAN should see errors cleared from both UI & state', async ({ + page, + customCardSeparateExpiryDate + }) => { + await binLookupMock(page, optionalDateAndCvcMock); + + // press pay to generate errors + await customCardSeparateExpiryDate.pay(); + + // Expect errors in UI + await expect(customCardSeparateExpiryDate.cardNumberErrorElement).toBeVisible(); + await expect(customCardSeparateExpiryDate.cardNumberErrorElement).toContainText(PAN_ERROR); + await expect(customCardSeparateExpiryDate.expiryMonthErrorElement).toBeVisible(); + await expect(customCardSeparateExpiryDate.expiryMonthErrorElement).toContainText(MONTH_EMPTY_ERROR); + await expect(customCardSeparateExpiryDate.expiryYearErrorElement).toBeVisible(); + await expect(customCardSeparateExpiryDate.expiryYearErrorElement).toContainText(YEAR_EMPTY_ERROR); + await expect(customCardSeparateExpiryDate.cvcErrorElement).toBeVisible(); + await expect(customCardSeparateExpiryDate.cvcErrorElement).toContainText(CVC_ERROR); + + await page.waitForTimeout(500); // wait for UI to show errors + + // 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 customCardSeparateExpiryDate.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(customCardSeparateExpiryDate.cardNumberErrorElement).not.toBeVisible(); + await expect(customCardSeparateExpiryDate.expiryMonthErrorElement).not.toBeVisible(); + await expect(customCardSeparateExpiryDate.expiryYearErrorElement).not.toBeVisible(); + await expect(customCardSeparateExpiryDate.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); + }); + // todo: flaky. + test.fixme('#4 date field in error DOES stop card becoming valid', async ({ page, customCardSeparateExpiryDate }) => { + await binLookupMock(page, optionalDateAndCvcMock); + + // Card out of date + await customCardSeparateExpiryDate.typeExpiryMonth('12'); + await customCardSeparateExpiryDate.typeExpiryYear('90'); + + // Expect error in UI + await expect(customCardSeparateExpiryDate.expiryYearErrorElement).toBeVisible(); + await expect(customCardSeparateExpiryDate.expiryYearErrorElement).toContainText(DATE_INVALID_ERROR); + + // Force blur event to fire on date field + await customCardSeparateExpiryDate.cardNumberLabelElement.click(); + + // Fill number to provoke (mock) binLookup response + await customCardSeparateExpiryDate.typeCardNumber(REGULAR_TEST_CARD); + + // UI reflects that binLookup says expiryDate is optional + await expect(customCardSeparateExpiryDate.expiryMonthLabelText).toContainText(`${MONTH_LABEL} ${OPTIONAL}`); + await expect(customCardSeparateExpiryDate.expiryYearLabelText).toContainText(`${YEAR_LABEL} ${OPTIONAL}`); + + // Visual errors persist in UI + await expect(customCardSeparateExpiryDate.expiryYearErrorElement).toBeVisible(); + await expect(customCardSeparateExpiryDate.expiryYearErrorElement).toContainText(DATE_INVALID_ERROR); + + // Card seen as invalid + let cardValid = await page.evaluate('window.customCardSeparate.isValid'); + await expect(cardValid).toEqual(false); + + // Delete erroneous date + await customCardSeparateExpiryDate.deleteExpiryMonth(); + await customCardSeparateExpiryDate.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('globalThis.customCardSeparate.isValid'); + await expect(cardValid).toEqual(true); + }); +}); diff --git a/packages/e2e/tests/customcard/expiryDate/expiryDate.clientScripts.js b/packages/e2e-playwright/tests/ui/customCard/expiryDate/expiryDate.clientScripts.js similarity index 100% rename from packages/e2e/tests/customcard/expiryDate/expiryDate.clientScripts.js rename to packages/e2e-playwright/tests/ui/customCard/expiryDate/expiryDate.clientScripts.js diff --git a/packages/e2e-playwright/tests/ui/customCard/expiryDate/minimumExpiryDate.regular.spec.ts b/packages/e2e-playwright/tests/ui/customCard/expiryDate/minimumExpiryDate.regular.spec.ts new file mode 100644 index 000000000..4b45d55bd --- /dev/null +++ b/packages/e2e-playwright/tests/ui/customCard/expiryDate/minimumExpiryDate.regular.spec.ts @@ -0,0 +1,113 @@ +import { test } from '@playwright/test'; +import LANG from '@adyen/adyen-web-server/translations/en-US.json'; + +const errorHolder = '.pm-form-label__error-text'; +const CARD_TOO_OLD = LANG['cc.dat.912']; +const CARD_TOO_FAR = LANG['cc.dat.913']; +const CARD_EXPIRES_BEFORE = LANG['cc.dat.914']; +// Applies to regular, single date field custom card +const iframeRegular = '.secured-fields iframe'; + +test.describe("Testing setting minimumExpiryDate (on regular Custom card component) - that it is recognised but doesn't override the other checks on date for a card being too old or too far in the future", () => { + test.beforeEach(async () => { + //await t.navigateTo(CUSTOMCARDS_URL); + // use expiryDate.clientScripts.js + }); + + test('With minimumExpiryDate set - input an expiry date that is too old & expect the correct error ', async () => { + // Start, allow time for iframes to load + // await start(t, 2000, TEST_SPEED); + // + // // Card out of date + // await cardUtilsRegular.fillDate(t, '12/20'); + // + // // Expect visible error: "Card too old" + // await t.expect(errorHolder.filterVisible().exists).ok().expect(errorHolder.withExactText(CARD_TOO_OLD).exists).ok(); + }); + + test('With minimumExpiryDate set - input an expiry date that is 1 month before it & expect the correct error', async () => { + // Start, allow time for iframes to load + // await start(t, 2000, TEST_SPEED); + // + // // Card out of date + // await cardUtilsRegular.fillDate(t, '08/24'); + // + // // Expect visible error: "Card expires before..." + // await t.expect(errorHolder.filterVisible().exists).ok().expect(errorHolder.withExactText(CARD_EXPIRES_BEFORE).exists).ok(); + }); + + test('With minimumExpiryDate set - input an expiry date that is matches it & expect no error ', async () => { + // Start, allow time for iframes to load + // await start(t, 2000, TEST_SPEED); + // + // // Card in date + // await cardUtilsRegular.fillDate(t, '09/24'); + // + // // Expect NO errors + // await t.expect(errorHolder.filterHidden().exists).ok(); + }); + + test('With minimumExpiryDate set - input an expiry date that exceeds it (a bit) & expect no error', async () => { + // Start, allow time for iframes to load + // await start(t, 2000, TEST_SPEED); + // + // // Card in date + // await cardUtilsRegular.fillDate(t, '04/25'); + // + // // Expect NO errors + // await t.expect(errorHolder.filterHidden().exists).ok(); + }); + + test('With minimumExpiryDate set - input an expiry date that is too far in the future, & expect the correct error ', async () => { + // Start, allow time for iframes to load + // await start(t, 2000, TEST_SPEED); + // + // // Card out of date + // await cardUtilsRegular.fillDate(t, '12/90'); + // + // // Expect visible error: "Card too far in the future" + // await t.expect(errorHolder.filterVisible().exists).ok().expect(errorHolder.withExactText(CARD_TOO_FAR).exists).ok(); + }); + + test( + 'General "date edit" bug: with minimumExpiryDate set - input an expiry date that is matches it & expect no error ' + + 'then edit the date to be before the minimumExpiryDate and expect that to register as an error', + async () => { + // Start, allow time for iframes to load + // await start(t, 2000, TEST_SPEED); + // + // // Card in date + // await cardUtilsRegular.fillDate(t, '09/24'); + // + // // Expect NO errors + // await t.expect(errorHolder.filterHidden().exists).ok(); + // + // // Card out of date + // await cardUtilsRegular.fillDate(t, '08/24', 'paste'); + // + // // Expect visible error: "Card expires before..." + // await t.expect(errorHolder.filterVisible().exists).ok().expect(errorHolder.withExactText(CARD_EXPIRES_BEFORE).exists).ok(); + } + ); + + test( + 'General "date edit" bug: input a valid expiry date & expect no error ' + + 'then edit the date to be before invalid and expect that to immediately register as an error', + async () => { + // Start, allow time for iframes to load + // await start(t, 2000, TEST_SPEED); + // + // // Card in date + // await cardUtilsRegular.fillDate(t, '04/30'); + // + // // Expect NO errors + // await t.expect(errorHolder.filterHidden().exists).ok(); + // + // // Card out of date + // await cardUtilsRegular.fillDate(t, '04/10', 'paste'); + // + // // Expect visible error: "Card too old" + // await t.expect(errorHolder.filterVisible().exists).ok().expect(errorHolder.withExactText(CARD_TOO_OLD).exists).ok(); + } + ); +}); diff --git a/packages/e2e-playwright/tests/ui/customCard/expiryDate/minimumExpiryDate.separate.spec.ts b/packages/e2e-playwright/tests/ui/customCard/expiryDate/minimumExpiryDate.separate.spec.ts new file mode 100644 index 000000000..77a8b164b --- /dev/null +++ b/packages/e2e-playwright/tests/ui/customCard/expiryDate/minimumExpiryDate.separate.spec.ts @@ -0,0 +1,120 @@ +import { test } from '@playwright/test'; +import LANG from '@adyen/adyen-web-server/translations/en-US.json'; + +const errorHolder = '.pm-form-label__error-text'; +const CARD_TOO_OLD = LANG['cc.dat.912']; +const CARD_TOO_FAR = LANG['cc.dat.913']; +const CARD_EXPIRES_BEFORE = LANG['cc.dat.914']; + +// Applies to custom card with separate date fields +const iframeSeparate = '.secured-fields-2 iframe'; + +test.describe("Testing setting minimumExpiryDate (on regular Custom card component) - that it is recognised but doesn't override the other checks on date for a card being too old or too far in the future", () => { + test.beforeEach(async () => { + //await t.navigateTo(CUSTOMCARDS_URL); + // use expiryDate.clientScripts.js + }); + test('With minimumExpiryDate set - input an expiry date that is too old & expect the correct error ', async () => { + // Start, allow time for iframes to load + // await start(t, 2000, TEST_SPEED); + // + // // Card out of date + // await customCardUtils.fillMonth(t, '12'); + // await customCardUtils.fillYear(t, '20'); + // + // // Expect visible error: "Card too old" + // await t.expect(errorHolder.filterVisible().exists).ok().expect(errorHolder.withExactText(CARD_TOO_OLD).exists).ok(); + }); + + test('With minimumExpiryDate set - input an expiry date that is 1 month before it & expect the correct error', async () => { + // Start, allow time for iframes to load + // await start(t, 2000, TEST_SPEED); + // + // // Card out of date + // await customCardUtils.fillMonth(t, '08'); + // await customCardUtils.fillYear(t, '24'); + // + // // Expect visible error: "Card expires before..." + // await t.expect(errorHolder.filterVisible().exists).ok().expect(errorHolder.withExactText(CARD_EXPIRES_BEFORE).exists).ok(); + }); + + test('With minimumExpiryDate set - input an expiry date that is matches it & expect no error ', async () => { + // Start, allow time for iframes to load + // await start(t, 2000, TEST_SPEED); + // + // // Card in date + // await customCardUtils.fillMonth(t, '09'); + // await customCardUtils.fillYear(t, '24'); + // + // // Expect NO errors + // await t.expect(errorHolder.filterHidden().exists).ok(); + }); + + test('With minimumExpiryDate set - input an expiry date that exceeds it (a bit) & expect no error', async () => { + // Start, allow time for iframes to load + // await start(t, 2000, TEST_SPEED); + // + // // Card in date + // await customCardUtils.fillMonth(t, '04'); + // await customCardUtils.fillYear(t, '25'); + // + // // Expect NO errors + // await t.expect(errorHolder.filterHidden().exists).ok(); + }); + + test('With minimumExpiryDate set - input an expiry date that is too far in the future, & expect the correct error ', async () => { + // Start, allow time for iframes to load + // await start(t, 2000, TEST_SPEED); + // + // // Card out of date + // await customCardUtils.fillMonth(t, '12'); + // await customCardUtils.fillYear(t, '90'); + // + // // Expect visible error: "Card too far in the future" + // await t.expect(errorHolder.filterVisible().exists).ok().expect(errorHolder.withExactText(CARD_TOO_FAR).exists).ok(); + }); + + test( + 'General "date edit" bug: with minimumExpiryDate set - input an expiry date that is matches it & expect no error ' + + 'then edit the date to be before the minimumExpiryDate and expect that to register as an error', + async () => { + // Start, allow time for iframes to load + // await start(t, 2000, TEST_SPEED); + // + // // Card in date + // await customCardUtils.fillMonth(t, '09'); + // await customCardUtils.fillYear(t, '24'); + // + // // Expect NO errors + // await t.expect(errorHolder.filterHidden().exists).ok(); + // + // // Card out of date + // await customCardUtils.fillMonth(t, '08', 'paste'); + // + // // Expect visible error: "Card expires before..." + // await t.expect(errorHolder.filterVisible().exists).ok().expect(errorHolder.withExactText(CARD_EXPIRES_BEFORE).exists).ok(); + } + ); + + test( + 'General "date edit" bug: input a valid expiry date & expect no error ' + + 'then edit the date to be before invalid and expect that to immediately register as an error', + async () => { + // Start, allow time for iframes to load + // await start(t, 2000, TEST_SPEED); + // + // // Card in date + // await customCardUtils.fillMonth(t, '04'); + // await customCardUtils.fillYear(t, '30'); + // + // // Expect NO errors + // await t.expect(errorHolder.filterHidden().exists).ok(); + // + // // Card out of date + // await customCardUtils.fillYear(t, '10', 'paste'); + // + // // Expect visible error: "Card too old" + // await t.expect(errorHolder.filterVisible().exists).ok().expect(errorHolder.withExactText(CARD_TOO_OLD).exists).ok(); + } + ); +}); diff --git a/packages/e2e-playwright/tests/ui/customCard/general/general.spec.ts b/packages/e2e-playwright/tests/ui/customCard/general/general.spec.ts new file mode 100644 index 000000000..6991546c9 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/customCard/general/general.spec.ts @@ -0,0 +1,62 @@ +import { test } from '@playwright/test'; + +// Applies to regular, single date field custom card +const iframeSelectorRegular = '.secured-fields iframe'; +// Applies to custom card with separate date fields +const iframeSelectorSeparate = '.secured-fields-2 iframe'; + +test.describe('Testing Custom Cards completion and payment', () => { + test.beforeEach(async () => { + //await t.navigateTo(CUSTOMCARDS_URL); + }); + test('Can fill out the fields in the regular custom card and make a successful payment', async t => { + // Start, allow time for iframes to load + // await start(t, 2000, TEST_SPEED); + // + // // handler for alert that's triggered on successful payment + // await t.setNativeDialogHandler(() => true); + // + // await cardUtilsRegular.fillCardNumber(t, REGULAR_TEST_CARD); + // + // await cardUtilsRegular.fillDateAndCVC(t); + // + // // click pay + // await t + // .click('.secured-fields .adyen-checkout__button') + // // no visible errors + // .expect(Selector('.pm-form-label__error-text').filterHidden().exists) + // .ok() + // .wait(2000); + // + // // Check the value of the alert text + // const history = await t.getNativeDialogHistory(); + // await t.expect(history[0].text).eql('Authorised'); + }); + + test('Can fill out the fields in the separate custom card and make a successful payment', async t => { + // Start, allow time for iframes to load + // await start(t, 2000, TEST_SPEED); + // + // // handler for alert that's triggered on successful payment + // await t.setNativeDialogHandler(() => true); + // + // await cardUtilsSeparate.fillCardNumber(t, REGULAR_TEST_CARD); + // + // await customCardUtils.fillMonth(t, TEST_MONTH_VALUE); + // await customCardUtils.fillYear(t, TEST_YEAR_VALUE); + // + // await customCardUtils.fillCVC(t, TEST_CVC_VALUE); + // + // // click pay + // await t + // .click('.secured-fields-2 .adyen-checkout__button') + // // no visible errors + // .expect(Selector('.pm-form-label__error-text').filterHidden().exists) + // .ok() + // .wait(2000); + // + // // Check the value of the alert text + // const history = await t.getNativeDialogHistory(); + // await t.expect(history[0].text).eql('Authorised'); + }); +}); diff --git a/packages/e2e/tests/customcard/utils/customCardUtils.js b/packages/e2e-playwright/tests/ui/customCard/utils/customCardUtils.js similarity index 96% rename from packages/e2e/tests/customcard/utils/customCardUtils.js rename to packages/e2e-playwright/tests/ui/customCard/utils/customCardUtils.js index 8e127ddc6..71fef6757 100644 --- a/packages/e2e/tests/customcard/utils/customCardUtils.js +++ b/packages/e2e-playwright/tests/ui/customCard/utils/customCardUtils.js @@ -1,5 +1,3 @@ -import { getInputSelector, deleteFromIFrame, fillIFrame } from '../../utils/commonUtils'; - /** * These utils provide a 'friendly' wrapper around the more generic functions in commonUtils * - prefilling the iframe selector, an iframe index and the iframe input element selector diff --git a/packages/e2e-playwright/tests/ui/dropin/bcmc/bancontact.dualbranding.reset.spec.ts b/packages/e2e-playwright/tests/ui/dropin/bcmc/bancontact.dualbranding.reset.spec.ts new file mode 100644 index 000000000..0f30e50f5 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/dropin/bcmc/bancontact.dualbranding.reset.spec.ts @@ -0,0 +1,195 @@ +import { test } from '@playwright/test'; + +const cvcSpan = '.adyen-checkout__dropin .adyen-checkout__field__cvc'; +const brandingIcon = '.adyen-checkout__dropin .adyen-checkout__card__cardNumber__brandIcon'; +const dualBrandingIconHolderActive = '.adyen-checkout__payment-method--bcmc .adyen-checkout__card__dual-branding__buttons--active'; + +const getPropFromPMData = prop => { + return globalThis.dropin.dropinRef.state.activePaymentMethod.formatData().paymentMethod[prop]; +}; + +const iframe = '.adyen-checkout__payment-method--bcmc iframe'; + +test.describe('Testing Bancontact, with dual branded cards, in Dropin, resetting after failed binLookup', () => { + // todo: create fixture + test.beforeEach(async () => { + // use config bancontact.clientScripts.js to construct the url & countryCode=BE + // await t.navigateTo(url); + // preselect the Bancontact payment method item from the dropin - .adyen-checkout__payment-method--bcmc + }); + test( + '#1 Fill in dual branded card then ' + + 'check that brands have been sorted to place Bcmc first then ' + + 'ensure only bcmc logo shows after deleting digits', + async () => { + // Start, allow time to load + // await start(t, 2000, TEST_SPEED); + // + // await cardUtils.fillCardNumber(t, BCMC_CARD); // dual branded with maestro + // + // // Bcmc first, Maestro second + // await t + // .expect(dualBrandingIconHolderActive.exists) + // .ok() + // .expect(dualBrandingIconHolderActive.find('img').nth(0).getAttribute('data-value')) + // .eql('bcmc') + // .expect(dualBrandingIconHolderActive.find('img').nth(1).getAttribute('data-value')) + // .eql('maestro'); + // + // // TODO delete action fails in Safari - but only if the "Click BCMC brand icon" action takes place!? + // await cardUtils.deleteCardNumber(t); + // + // await t + // .expect(dualBrandingIconHolderActive.exists) + // .notOk() + // // single bcmc card icon + // .expect(brandingIcon.getAttribute('alt')) + // .contains('Bancontact card'); + } + ); + + test( + '#2 Fill in dual branded card then ' + + 'select bcmc, as first item (brand sorting has occurred), then' + + 'ensure only bcmc logo shows after deleting digits and ' + + 'that the brand has been reset on paymentMethod data', + async () => { + // Start, allow time to load + // await start(t, 2000, TEST_SPEED); + // + // await cardUtils.fillCardNumber(t, BCMC_CARD); // dual branded with maestro + // + // await t + // .expect(dualBrandingIconHolderActive.exists) + // .ok() + // .expect(dualBrandingIconHolderActive.find('img').nth(0).getAttribute('data-value')) + // .eql('bcmc') + // .expect(dualBrandingIconHolderActive.find('img').nth(1).getAttribute('data-value')) + // .eql('maestro'); + // + // // Click BCMC brand icon + // await t.click(dualBrandingIconHolderActive.find('img').nth(0)); + // + // // Should be a brand property in the PM data + // await t.expect(getPropFromPMData('brand')).eql('bcmc'); + // + // // TODO delete action fails in Safari + // await cardUtils.deleteCardNumber(t); + // + // await t + // .expect(dualBrandingIconHolderActive.exists) + // .notOk() + // // bcmc card icon + // .expect(brandingIcon.getAttribute('alt')) + // .contains('Bancontact card'); + // + // // Should not be a brand property in the PM data + // await t.expect(getPropFromPMData('brand')).eql(undefined); + } + ); + + test( + '#3 Fill in dual branded card then ' + + 'select maestro then' + + 'ensure cvc field is hidden even though it is maestro (brand sorting has occurred)' + + 'ensure only bcmc logo shows after deleting digits and ' + + 'that the brand has been reset on paymentMethod data', + async () => { + // Start, allow time to load + // await start(t, 2000, TEST_SPEED); + // + // await cardUtils.fillCardNumber(t, BCMC_CARD); // dual branded with maestro + // + // await t + // .expect(dualBrandingIconHolderActive.exists) + // .ok() + // .expect(dualBrandingIconHolderActive.find('img').nth(0).getAttribute('data-value')) + // .eql('bcmc') + // .expect(dualBrandingIconHolderActive.find('img').nth(1).getAttribute('data-value')) + // .eql('maestro'); + // + // // Click Maestro brand icon + // await t.click(dualBrandingIconHolderActive.find('img').nth(1)); + // + // // Should be a brand property in the PM data + // await t.expect(getPropFromPMData('brand')).eql('maestro'); + // + // // Hidden cvc field + // await t.expect(cvcSpan.filterHidden().exists).ok(); + // + // // TODO delete action fails in Safari - but only if the "Click BCMC brand icon" action takes place + // await cardUtils.deleteCardNumber(t); + // + // await t + // // bcmc card icon + // .expect(brandingIcon.getAttribute('alt')) + // .contains('Bancontact card'); + // + // // Should not be a brand property in the PM data + // await t.expect(getPropFromPMData('brand')).eql(undefined); + } + ); + + test( + '#4 Fill in dual branded card then ' + + 'paste in number not recognised by binLookup (but that internally is recognised as Visa)' + + 'ensure that bcmc logo shows', + async () => { + // Start, allow time to load + // await start(t, 2000, TEST_SPEED); + // + // await cardUtils.fillCardNumber(t, BCMC_CARD); // dual branded with maestro + // + // await t + // .expect(dualBrandingIconHolderActive.exists) + // .ok() + // .expect(dualBrandingIconHolderActive.find('img').nth(0).getAttribute('data-value')) + // .eql('bcmc') + // .expect(dualBrandingIconHolderActive.find('img').nth(1).getAttribute('data-value')) + // .eql('maestro'); + // + // await cardUtils.fillCardNumber(t, UNKNOWN_VISA_CARD, 'paste'); // number not recognised by binLookup + // + // await t + // // bcmc card icon + // .expect(brandingIcon.getAttribute('alt')) + // .contains('Bancontact card'); + } + ); + + test( + '#5 Fill in dual branded card then ' + + 'select maestro then' + + 'paste in number not recognised by binLookup (but that internally is recognised as Visa)' + + 'ensure that bcmc logo shows', + async () => { + // Start, allow time to load + // await start(t, 2000, TEST_SPEED); + // + // await cardUtils.fillCardNumber(t, BCMC_CARD); // dual branded with maestro + // + // await t + // .expect(dualBrandingIconHolderActive.exists) + // .ok() + // .expect(dualBrandingIconHolderActive.find('img').nth(0).getAttribute('data-value')) + // .eql('bcmc') + // .expect(dualBrandingIconHolderActive.find('img').nth(1).getAttribute('data-value')) + // .eql('maestro'); + // + // // Click Maestro brand icon + // await t.click(dualBrandingIconHolderActive.find('img').nth(1)); + // + // // Hidden cvc field + // await t.expect(cvcSpan.filterHidden().exists).ok(); + // + // await cardUtils.fillCardNumber(t, UNKNOWN_VISA_CARD, 'paste'); // number not recognised by binLookup + // + // await t + // // bcmc card icon + // .expect(brandingIcon.getAttribute('alt')) + // .contains('Bancontact card'); + // + // await t.wait(2000); + } + ); +}); diff --git a/packages/e2e-playwright/tests/ui/dropin/bcmc/bancontact.visa.reset.spec.ts b/packages/e2e-playwright/tests/ui/dropin/bcmc/bancontact.visa.reset.spec.ts new file mode 100644 index 000000000..2ced5cfe2 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/dropin/bcmc/bancontact.visa.reset.spec.ts @@ -0,0 +1,99 @@ +import { test } from '../../../../fixtures/dropin.fixture'; + +const cvcSpan = '.adyen-checkout__dropin .adyen-checkout__field__cvc'; +const brandingIcon = '.adyen-checkout__dropin .adyen-checkout__card__cardNumber__brandIcon'; +const dualBrandingIconHolderActive = '.adyen-checkout__payment-method--bcmc .adyen-checkout__card__dual-branding__buttons--active'; +const iframe = '.adyen-checkout__payment-method--bcmc iframe'; + +test.describe('Testing Bancontact in Dropin', () => { + // todo: create fixture + test.beforeEach(async () => { + // Navigate to drop in page, with correct dropin config from bancontact.clientScripts.js to construct the URL + // await t.navigateTo(`${dropinPage.pageUrl}?countryCode=BE`); + // preselect the Bancontact payment method item from the dropin - selector .adyen-checkout__payment-method--bcmc + }); + test( + '#1 Enter card number, that we mock to co-branded bcmc/visa ' + + 'then click Visa logo and expect CVC field to show, then' + + 'paste in number not recognised by binLookup (but that internally is recognised as Visa)' + + 'ensure that bcmc logo shows & CVC field is hidden', + async () => { + // await start(t, 2000, TEST_SPEED); + // + // await cardUtils.fillCardNumber(t, BCMC_DUAL_BRANDED_VISA); + // + // await t + // .expect(dualBrandingIconHolderActive.exists) + // .ok() + // .expect(dualBrandingIconHolderActive.find('img').nth(0).getAttribute('data-value')) + // .eql('bcmc') + // .expect(dualBrandingIconHolderActive.find('img').nth(1).getAttribute('data-value')) + // .eql('visa'); + // + // // Click Visa brand icon + // await t.click(dualBrandingIconHolderActive.find('img').nth(1)); + // + // // Visible CVC field + // await t.expect(cvcSpan.filterVisible().exists).ok(); + // + // // Expect iframe to exist in CVC field and with aria-required set to true + // await t + // .switchToIframe(iframe.nth(2)) + // .expect('[data-fieldtype="encryptedSecurityCode"]'.getAttribute('aria-required')) + // .eql('true') + // .switchToMainWindow(); + // + // await cardUtils.fillCardNumber(t, UNKNOWN_VISA_CARD, 'paste'); // number not recognised by binLookup + // + // // Hidden CVC field + // await t.expect(cvcSpan.filterHidden().exists).ok(); + // + // await t + // // bcmc card icon + // .expect(brandingIcon.getAttribute('alt')) + // .contains('Bancontact card'); + } + ); + test( + '#2 Enter card number, that we mock to co-branded bcmc/visa ' + + 'then click Visa logo and expect CVC field to show, then' + + 'delete card number and ' + + 'ensure that bcmc logo shows & CVC field is hidden', + async () => { + // await start(t, 2000, TEST_SPEED); + // + // await cardUtils.fillCardNumber(t, BCMC_DUAL_BRANDED_VISA); + // + // await t + // .expect(dualBrandingIconHolderActive.exists) + // .ok() + // .expect(dualBrandingIconHolderActive.find('img').nth(0).getAttribute('data-value')) + // .eql('bcmc') + // .expect(dualBrandingIconHolderActive.find('img').nth(1).getAttribute('data-value')) + // .eql('visa'); + // + // // Click Visa brand icon + // await t.click(dualBrandingIconHolderActive.find('img').nth(1)); + // + // // Visible CVC field + // await t.expect(cvcSpan.filterVisible().exists).ok(); + // + // // Expect iframe to exist in CVC field and with aria-required set to true + // await t + // .switchToIframe(iframe.nth(2)) + // .expect('[data-fieldtype="encryptedSecurityCode"]'.getAttribute('aria-required')) + // .eql('true') + // .switchToMainWindow(); + // + // await cardUtils.deleteCardNumber(t); + // + // // Hidden CVC field + // await t.expect(cvcSpan.filterHidden().exists).ok(); + // + // await t + // // bcmc card icon + // .expect(brandingIcon.getAttribute('alt')) + // .contains('Bancontact card'); + } + ); +}); diff --git a/packages/e2e-playwright/tests/ui/dropin/bcmc/bancontact.visa.spec.ts b/packages/e2e-playwright/tests/ui/dropin/bcmc/bancontact.visa.spec.ts new file mode 100644 index 000000000..1ff75c1f6 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/dropin/bcmc/bancontact.visa.spec.ts @@ -0,0 +1,70 @@ +import { test } from '../../../../fixtures/dropin.fixture'; + +test.describe('Testing Bancontact in Dropin', () => { + // todo: create fixture + test.beforeEach(async () => { + // Navigate to drop in page, with correct dropin config from bancontact.clientScripts.js to construct the URL + // await t.navigateTo(`${dropinPage.pageUrl}?countryCode=BE`); + // preselect the Bancontact payment method item from the dropin - selector .adyen-checkout__payment-method--bcmc + }); + + test('#1 Check Bancontact comp is correctly presented at startup', async () => { + // Wait for field to appear in DOM + // await t.wait(1000); + // + // const brandsInsidePaymentMethod = Selector('.adyen-checkout__card__brands'); + // const images = brandsInsidePaymentMethod.find('img'); + // + // // Expect 4 card brand logos to be displayed (not concerned about order) + // await t.expect(images.count).eql(4); + // await t + // .expect(images.nth(0).getAttribute('src')) + // .contains('bcmc.svg') + // .expect(images.nth(1).getAttribute('src')) + // .contains('mc.svg') + // .expect(images.nth(2).getAttribute('src')) + // .contains('visa.svg') + // .expect(images.nth(3).getAttribute('src')) + // .contains('maestro.svg'); + // + // // Hidden cvc field + // await t.expect(dropinPage.cc.cvcHolder.filterHidden().exists).ok(); + // + // // BCMC logo in number field + // await t.expect(dropinPage.cc.numSpan.exists).ok().expect(dropinPage.cc.brandingIcon.withAttribute('alt', 'Bancontact card').exists).ok(); + }); + + test('#2 Entering digits that our local regEx will recognise as Visa does not affect the UI', async () => { + // await dropinPage.cc.numSpan(); + // + // await dropinPage.cc.cardUtils.fillCardNumber(t, '41'); + // + // // BCMC logo still in number field + // await t.expect(dropinPage.cc.brandingIcon.withAttribute('alt', 'Bancontact card').exists).ok(); + // + // // Hidden cvc field + // await t.expect(dropinPage.cc.cvcHolder.filterHidden().exists).ok(); + }); + + test('#3 Enter card number, that we mock to co-branded bcmc/visa ' + 'then complete expiryDate and expect comp to be valid', async () => { + // await dropinPage.cc.numSpan(); + // + // await dropinPage.cc.cardUtils.fillCardNumber(t, BCMC_DUAL_BRANDED_VISA); + // + // // Dual branded with bcmc logo shown first + // await t + // .expect(dropinPage.dualBrandingIconHolderActive.exists) + // .ok() + // .expect(dropinPage.dualBrandingImages.nth(0).withAttribute('data-value', 'bcmc').exists) + // .ok() + // .expect(dropinPage.dualBrandingImages.nth(1).withAttribute('data-value', 'visa').exists) + // .ok(); + // + // await dropinPage.cc.cardUtils.fillDate(t, TEST_DATE_VALUE); + // + // // Expect comp to now be valid + // await t.expect(dropinPage.getFromWindow('dropin.isValid')).eql(true, { timeout: 3000 }); + }); + + /** #4, #5, #6 in a11y/bcmc/bancontact.visa.a11y.spec.ts */ +}); diff --git a/packages/e2e/tests/cards/Bancontact/bancontact.clientScripts.js b/packages/e2e-playwright/tests/ui/dropin/bcmc/config.js similarity index 100% rename from packages/e2e/tests/cards/Bancontact/bancontact.clientScripts.js rename to packages/e2e-playwright/tests/ui/dropin/bcmc/config.js diff --git a/packages/e2e-playwright/tests/ui/dropin/card/cardBrands/cardBrands.spec.ts b/packages/e2e-playwright/tests/ui/dropin/card/cardBrands/cardBrands.spec.ts new file mode 100644 index 000000000..b846cb4b1 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/dropin/card/cardBrands/cardBrands.spec.ts @@ -0,0 +1,43 @@ +import { test, expect } from './dropinWithCard.fixture'; +import { getStoryUrl } from '../../../../utils/getStoryUrl'; +import { URL_MAP } from '../../../../../fixtures/URL_MAP'; + +test.describe('Dropin - Card brands displayed in the Payment Method List and underneath the PAN field', () => { + test('should display the 3 logos and left over amount of brands, and then display all available brands under the PAN field', async ({ + dropinWithCard, + card + }) => { + await dropinWithCard.goto(URL_MAP.dropinWithSession); + expect(await dropinWithCard.visibleCardBrands).toHaveLength(3); + expect(dropinWithCard.remainingCardBrandsNumber).toContainText('+11'); + await dropinWithCard.card.scrollIntoViewIfNeeded(); + await dropinWithCard.selectPaymentMethod('scheme'); + await card.isComponentVisible(); + expect(await dropinWithCard.visibleCardBrands).toHaveLength(0); + expect(dropinWithCard.remainingCardBrandsNumber).toBeHidden(); + expect(await card.availableBrands).toHaveLength(14); + }); + + test('should exclude non-valid brands and display only the right amount in the payment header and underneath the PAN field', async ({ + dropinWithCard, + card + }) => { + const dropinConfig = { + paymentMethodsConfiguration: { + card: { + brands: ['visa', 'mc', 'amex', 'discover', 'cup', 'maestro', 'nyce', 'accel', 'star', 'pulse'], + _disableClickToPay: true + } + } + }; + await dropinWithCard.goto(getStoryUrl({ baseUrl: URL_MAP.dropinWithSession, componentConfig: dropinConfig })); + expect(await dropinWithCard.visibleCardBrands).toHaveLength(3); + expect(dropinWithCard.remainingCardBrandsNumber).toContainText('+3'); + await dropinWithCard.card.scrollIntoViewIfNeeded(); + await dropinWithCard.selectPaymentMethod('scheme'); + await card.isComponentVisible(); + expect(await dropinWithCard.visibleCardBrands).toHaveLength(0); + expect(dropinWithCard.remainingCardBrandsNumber).toBeHidden(); + expect(await card.availableBrands).toHaveLength(6); + }); +}); diff --git a/packages/e2e-playwright/tests/ui/dropin/card/cardBrands/dropinWithCard.fixture.ts b/packages/e2e-playwright/tests/ui/dropin/card/cardBrands/dropinWithCard.fixture.ts new file mode 100644 index 000000000..c0bd3f340 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/dropin/card/cardBrands/dropinWithCard.fixture.ts @@ -0,0 +1,32 @@ +import { test as base, mergeTests, expect } from '@playwright/test'; +import { DropinWithSession } from '../../../../../models/dropinWithSession'; +import { test as card } from '../../../../../fixtures/card.fixture'; + +class DropinWithCard extends DropinWithSession { + get card() { + return super.getPaymentMethodLabelByType('scheme'); + } + + get visibleCardBrands() { + return this.card.locator('.adyen-checkout__payment-method__brands').getByRole('img').all(); + } + + get remainingCardBrandsNumber() { + return this.card.locator('.adyen-checkout__payment-method__brand-number'); + } +} + +type Fixture = { + dropinWithCard: DropinWithCard; +}; + +const dropin = base.extend({ + dropinWithCard: async ({ page }, use) => { + const dropin = new DropinWithCard(page); + await use(dropin); + } +}); + +const test = mergeTests(card, dropin); + +export { test, expect }; diff --git a/packages/e2e-playwright/tests/ui/dropin/card/cardBrands/dualBranding.dropin.spec.ts b/packages/e2e-playwright/tests/ui/dropin/card/cardBrands/dualBranding.dropin.spec.ts new file mode 100644 index 000000000..8c133e654 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/dropin/card/cardBrands/dualBranding.dropin.spec.ts @@ -0,0 +1,197 @@ +import { test } from '@playwright/test'; + +const dualBrandingIconHolder = '.adyen-checkout__payment-method--scheme .adyen-checkout__card__dual-branding__buttons'; +const dualBrandingIconHolderActive = '.adyen-checkout__payment-method--scheme .adyen-checkout__card__dual-branding__buttons--active'; +const NOT_SELECTED_CLASS = 'adyen-checkout__card__cardNumber__brandIcon--not-selected'; + +const getPropFromPMData = prop => { + return globalThis.dropin.dropinRef.state.activePaymentMethod.formatData().paymentMethod[prop]; +}; + +test.describe('Testing dual branding in dropin', () => { + // Use the config: + // window.cardConfig = { + // type: 'scheme', + // brands: ['mc', 'visa', 'amex', 'cartebancaire', 'star'] + // }; + // + // window.dropinConfig = { + // showStoredPaymentMethods: false, // hide stored PMs so credit card is first on list + // paymentMethodsConfiguration: { + // card: { brands: ['mc', 'amex', 'visa', 'cartebancaire', 'star'], _disableClickToPay: true } + // } + // }; + // + // window.mainConfiguration = { + // removePaymentMethods: ['paywithgoogle', 'applepay', 'clicktopay'] + // }; + + test( + '#1 Fill in card number that will get dual branding result from binLookup, ' + 'then check that the expected icons/buttons are shown', + async () => { + // Start, allow time for iframes to load + // Fill card field with dual branded card (visa/cb): cardUtils.fillCardNumber(t, DUAL_BRANDED_CARD); + // expect(dualBrandingIconHolderActive.exists) + // expect(dualBrandingIconHolderActive.find('img').nth(0).getAttribute('data-value')).eql('visa') + // expect(dualBrandingIconHolderActive.find('img').nth(1).getAttribute('data-value')).eql('cartebancaire'); + } + ); + + test( + '#2 Fill in card number that will get dual branding result from binLookup, ' + + 'then complete card without selecting dual brand,' + + 'then check it is valid,' + + 'then check PM data does not have a brand property', + async () => { + // Start, allow time for iframes to load + // Fill card field with dual branded card (visa/cb): cardUtils.fillCardNumber(t, DUAL_BRANDED_CARD); + // cardUtils.fillDateAndCVC(t); + // Expect card to now be valid: expect(getIsValid('dropin')).eql(true); + // Should not be a brand property in the PM data: t.expect(getPropFromPMData('brand')).eql(undefined); + } + ); + + test( + '#3 Fill in card number that will get dual branding result from binLookup, ' + + 'then complete card,' + + 'then check it is valid,' + + 'then select the dual brands,' + + 'then check PM data does have a corresponding brand property', + async () => { + // Start, allow time for iframes to load + // Fill card field with dual branded card (visa/cb): cardUtils.fillCardNumber(t, DUAL_BRANDED_CARD); + // cardUtils.fillDateAndCVC(t); + // Expect card to now be valid: t.expect(getIsValid('dropin')).eql(true); + // Click brand icons + // click(dualBrandingIconHolderActive.find('img').nth(1)) + //expect(getPropFromPMData('brand')).eql('cartebancaire') + //click(dualBrandingIconHolderActive.find('img').nth(0)) + //expect(getPropFromPMData('brand')).eql('visa'); + } + ); + + test( + '#4 Fill in partial card number that will get dual branding result from binLookup, ' + + 'then check that the expected icons/buttons are shown but inactive,' + + 'then complete the number & check that the icons/buttons are active', + async () => { + // Start, allow time for iframes to load + //const firstDigits = DUAL_BRANDED_CARD.substring(0, 11); + //const lastDigits = DUAL_BRANDED_CARD.substring(11, 16); + // Partially fill card field with dual branded card (visa/cb): cardUtils.fillCardNumber(t, firstDigits); + // t.expect(dualBrandingIconHolder.exists).ok().expect(dualBrandingIconHolderActive.exists).notOk(); + // Complete field: cardUtils.fillCardNumber(t, lastDigits); + // t.expect(dualBrandingIconHolderActive.exists).ok(); + } + ); + + test( + '#5 Fill in card number that will get dual branding result from binLookup, ' + + 'then select one of the dual brands,' + + 'then check the other brand icon is at reduced alpha,' + + 'then repeat with the other icon', + async () => { + // Start, allow time for iframes to load + // Fill card field with dual branded card (visa/cb): cardUtils.fillCardNumber(t, DUAL_BRANDED_CARD); + // click first icon: click(dualBrandingIconHolderActive.find('img').nth(0)) + // first icon shouldn't have the "not selected" class: expect(dualBrandingIconHolderActive.find('img').nth(0).hasClass(NOT_SELECTED_CLASS)).eql(false) + // second icon should have the "not selected" class: expect(dualBrandingIconHolderActive.find('img').nth(1).hasClass(NOT_SELECTED_CLASS)).eql(true) + // click second icon: click(dualBrandingIconHolderActive.find('img').nth(1)) + // second icon shouldn't have the "not selected" class: expect(dualBrandingIconHolderActive.find('img').nth(1).hasClass(NOT_SELECTED_CLASS)).eql(false) + // first icon should have the "not selected" class: expect(dualBrandingIconHolderActive.find('img').nth(0).hasClass(NOT_SELECTED_CLASS)).eql(true); + } + ); + + test( + '#6 Fill in card number that will get single branding result from binLookup, ' + + 'then enter a dual branded card number,' + + 'check both brand icons are at full alpha,' + + 'then click icons and make sure they go to the expected alpha', + async () => { + // Start, allow time for iframes to load + // cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); + // Paste dual branded card (visa/cb) into card field: cardUtils.fillCardNumber(t, DUAL_BRANDED_CARD, 'paste'); + // Check buttons are active: t.expect(dualBrandingIconHolderActive.exists).ok(); + // Check icon opacities (should be 100%) + // first icon SHOULDN'T have the "not selected" class: expect(dualBrandingIconHolderActive.find('img').nth(0).hasClass(NOT_SELECTED_CLASS)).eql(false) + // second icon SHOULDN'T have the "not selected" class: expect(dualBrandingIconHolderActive.find('img').nth(1).hasClass(NOT_SELECTED_CLASS)).eql(false); + // click first icon: click(dualBrandingIconHolderActive.find('img').nth(0)) + // first icon SHOULDN'T have the "not selected" class: expect(dualBrandingIconHolderActive.find('img').nth(0).hasClass(NOT_SELECTED_CLASS)).eql(false) + // second icon SHOULD have the "not selected" class: expect(dualBrandingIconHolderActive.find('img').nth(1).hasClass(NOT_SELECTED_CLASS)).eql(true) + // click second icon: click(dualBrandingIconHolderActive.find('img').nth(1)) + // second icon SHOULDN'T have the "not selected" class: expect(dualBrandingIconHolderActive.find('img').nth(1).hasClass(NOT_SELECTED_CLASS)).eql(false) + // first icon SHOULD have the "not selected" class: expect(dualBrandingIconHolderActive.find('img').nth(0).hasClass(NOT_SELECTED_CLASS)).eql(true); + } + ); + + test( + '#7 Fill in card number that will get single branding result from binLookup, ' + + 'then enter a partial dual branded card number,' + + 'check both brand icons are at reduced alpha,' + + 'complete card number,' + + 'check both brand icons are at full alpha,' + + 'then click icons and make sure they go to the expected alpha', + async () => { + // Start, allow time for iframes to load + // await cardUtils.fillCardNumber(t, REGULAR_TEST_CARD); + // + // const firstDigits = DUAL_BRANDED_CARD.substring(0, 11); + // const lastDigits = DUAL_BRANDED_CARD.substring(11, 16); + // + // // Paste partial dual branded card (visa/cb) into card field + // await cardUtils.fillCardNumber(t, firstDigits, 'paste'); + // + // // Check buttons are present but NOT active (which will mean the holding element is at 25% opacity) + // await t.expect(dualBrandingIconHolder.exists).ok().expect(dualBrandingIconHolderActive.exists).notOk(); + // + // // Complete field + // await cardUtils.fillCardNumber(t, lastDigits); + // + // // Check buttons are active + // await t.expect(dualBrandingIconHolderActive.exists).ok(); + // + // // Check icon opacities (should be 100%) + // await t + // // first icon SHOULDN'T have the "not selected" class + // .expect(dualBrandingIconHolderActive.find('img').nth(0).hasClass(NOT_SELECTED_CLASS)) + // .eql(false) + // // second icon SHOULDN'T have the "not selected" class + // .expect(dualBrandingIconHolderActive.find('img').nth(1).hasClass(NOT_SELECTED_CLASS)) + // .eql(false); + // + // // Click brand icons + // await t + // // click first icon + // .click(dualBrandingIconHolderActive.find('img').nth(0)) + // // first icon SHOULDN'T have the "not selected" class + // .expect(dualBrandingIconHolderActive.find('img').nth(0).hasClass(NOT_SELECTED_CLASS)) + // .eql(false) + // // second icon SHOULD have the "not selected" class + // .expect(dualBrandingIconHolderActive.find('img').nth(1).hasClass(NOT_SELECTED_CLASS)) + // .eql(true) + // // click second icon + // .click(dualBrandingIconHolderActive.find('img').nth(1)) + // // second icon SHOULDN'T have the "not selected" class + // .expect(dualBrandingIconHolderActive.find('img').nth(1).hasClass(NOT_SELECTED_CLASS)) + // .eql(false) + // // first icon SHOULD have the "not selected" class + // .expect(dualBrandingIconHolderActive.find('img').nth(0).hasClass(NOT_SELECTED_CLASS)) + // .eql(true); + } + ); + + test( + '#8 Fill in card number that will get dual branding result from binLookup, ' + + 'but one of the brands should be excluded from the UI, ' + + '(but meaning also that no brand should be set in the PM data), ' + + 'then check it is valid,' + + 'then check PM data does not have a brand property', + async () => { + // Start, allow time for iframes to load + // Fill card field with dual branded card (visa/cb): cardUtils.fillCardNumber(t, DUAL_BRANDED_CARD_EXCLUDED); + // cardUtils.fillDateAndCVC(t); + // Expect card to now be valid: t.expect(getIsValid('dropin')).eql(true); + // Should not be a brand property in the PM data: t.expect(getPropFromPMData('brand')).eql(undefined); + } + ); +}); diff --git a/packages/e2e/tests/giftcards/brandsConfiguration/brandsConfiguration.clientScripts.js b/packages/e2e-playwright/tests/ui/giftcards/brandsConfiguration/giftcard.dropin.brandsConfiguration.spec.ts similarity index 56% rename from packages/e2e/tests/giftcards/brandsConfiguration/brandsConfiguration.clientScripts.js rename to packages/e2e-playwright/tests/ui/giftcards/brandsConfiguration/giftcard.dropin.brandsConfiguration.spec.ts index 061af6722..cc3db5314 100644 --- a/packages/e2e/tests/giftcards/brandsConfiguration/brandsConfiguration.clientScripts.js +++ b/packages/e2e-playwright/tests/ui/giftcards/brandsConfiguration/giftcard.dropin.brandsConfiguration.spec.ts @@ -1,3 +1,6 @@ +import { test } from '@playwright/test'; +// todo: need to set up a fixture +/* Config used: window.mainConfiguration = { allowPaymentMethods: ['giftcard'] }; @@ -12,4 +15,10 @@ window.dropinConfig = { } } } -}; +};*/ + +test('#1 Check Giftcard comp receives custom name and icon from brandsConfiguration object', async t => { + // Wait for el to appear in DOM + // Expect to see Custom name + // Expect to see Custom card icon +}); diff --git a/packages/e2e-playwright/tests/ui/giftcards/enoughBalance/enoughBalance.spec.ts b/packages/e2e-playwright/tests/ui/giftcards/enoughBalance/enoughBalance.spec.ts new file mode 100644 index 000000000..3c2cbdbae --- /dev/null +++ b/packages/e2e-playwright/tests/ui/giftcards/enoughBalance/enoughBalance.spec.ts @@ -0,0 +1,19 @@ +import { test } from '@playwright/test'; +/* Mock used: +const mock = RequestMock() + .onRequestTo('http://localhost:3020/paymentMethods/balance') + .respond({ + balance: { currency: 'USD', value: 999999 } + }) + .onRequestTo('http://localhost:3020/payments') + .respond({ + resultCode: 'Authorised' + }); + */ + +test('Should prompt a confirmation when using a gift card with enough balance', async t => { + // Wait for gift card to appear in DOM + // await giftCard.cardUtils.fillCardNumber(t, GIFTCARD_NUMBER); + // await fillIFrame(t, giftCard.iframeSelector, 1, getInputSelector('encryptedSecurityCode'), GIFTCARD_PIN); + //await t.click(giftCard.payButton).expect(giftCard.balanceDisplay.exists).ok(); +}); diff --git a/packages/e2e/tests/giftcards/onOrderUpdated/onOrderUpdated.clientScripts.js b/packages/e2e-playwright/tests/ui/giftcards/onOrderUpdated/onOrderUpdated.clientScripts.js similarity index 100% rename from packages/e2e/tests/giftcards/onOrderUpdated/onOrderUpdated.clientScripts.js rename to packages/e2e-playwright/tests/ui/giftcards/onOrderUpdated/onOrderUpdated.clientScripts.js diff --git a/packages/e2e/tests/giftcards/onOrderUpdated/onOrderUpdated.mocks.js b/packages/e2e-playwright/tests/ui/giftcards/onOrderUpdated/onOrderUpdated.mocks.js similarity index 98% rename from packages/e2e/tests/giftcards/onOrderUpdated/onOrderUpdated.mocks.js rename to packages/e2e-playwright/tests/ui/giftcards/onOrderUpdated/onOrderUpdated.mocks.js index a0d97df8f..c359b070f 100644 --- a/packages/e2e/tests/giftcards/onOrderUpdated/onOrderUpdated.mocks.js +++ b/packages/e2e-playwright/tests/ui/giftcards/onOrderUpdated/onOrderUpdated.mocks.js @@ -1,5 +1,5 @@ import { RequestMock, RequestLogger } from 'testcafe'; -import { BASE_URL } from '../../pages'; +import { BASE_URL } from '@adyen/adyen-web-e2e/tests/pages'; const path = require('path'); require('dotenv').config({ path: path.resolve('../../', '.env') }); diff --git a/packages/e2e-playwright/tests/ui/giftcards/onOrderUpdated/onOrderUpdated.spec.ts b/packages/e2e-playwright/tests/ui/giftcards/onOrderUpdated/onOrderUpdated.spec.ts new file mode 100644 index 000000000..895975d71 --- /dev/null +++ b/packages/e2e-playwright/tests/ui/giftcards/onOrderUpdated/onOrderUpdated.spec.ts @@ -0,0 +1,20 @@ +import { test } from '@playwright/test'; +// todo: need to configure the mock +// use mock +test('Test if orderStatus is retrieved on success', async () => { + // Wait for cardholder el to appear in DOM + // fillCardNumber(t, GIFTCARD_NUMBER); + // fill in pin: await fillIFrame(t, giftCard.iframeSelector, 1, getInputSelector('encryptedSecurityCode'), GIFTCARD_PIN); + // click pay button + // expect to send correct data to check balance? -> sessionData === MOCK_SESSION_DATA; + // expect onOrderUpdated callback to pass correct data +}); + +// use noCallbackMock +test('Test if onOrderCreated is not called if giftcard has enough balance for the payment', async () => { + // Wait for cardholder el to appear in DOM + // fillCardNumber(t, GIFTCARD_NUMBER); + // fill in pin -> await fillIFrame(t, giftCard.iframeSelector, 1, getInputSelector('encryptedSecurityCode'), GIFTCARD_PIN); + // click pay button + // expect onOrderUpdated is not called +}); diff --git a/packages/e2e-playwright/tests/utils/constants.ts b/packages/e2e-playwright/tests/utils/constants.ts index f2f25375a..dccbff6a9 100644 --- a/packages/e2e-playwright/tests/utils/constants.ts +++ b/packages/e2e-playwright/tests/utils/constants.ts @@ -1,16 +1,36 @@ export const BIN_LOOKUP_VERSION = 'v3'; export const REGULAR_TEST_CARD = '5500000000000004'; +export const VISA_CARD = '4111111111111111'; export const AMEX_CARD = '370000000000002'; - +export const KOREAN_TEST_CARD = '9490220006611406'; // 9490220006611406 works against Test. For localhost:8080 use: 5067589608564358 + hack in triggerBinLookup +export const BCMC_CARD = '6703444444444449'; // actually dual branded bcmc & maestro export const MAESTRO_CARD = '5000550000000029'; +export const DUAL_BRANDED_CARD = '4035501000000008'; // dual branded visa & cartebancaire +export const BCMC_DUAL_BRANDED_VISA = '4871049999999910'; // dual branded visa & bcmc +export const DUAL_BRANDED_CARD_EXCLUDED = '4001230000000004'; // dual branded visa/star +export const FAILS_LUHN_CARD = '4111111111111112'; + +export const THREEDS2_FRICTIONLESS_CARD = '5201281505129736'; +export const THREEDS2_FULL_FLOW_CARD = '5000550000000029'; +export const THREEDS2_CHALLENGE_ONLY_CARD = '4212345678910006'; +export const MULTI_LUHN_MAESTRO = '6771830999991239343'; // maestro that passes luhn check at 16, 18 & 19 digits + +export const UNKNOWN_BIN_CARD = '135410014004955'; // card that is not in the test DBs (uatp) +export const UNKNOWN_VISA_CARD = '41111111'; // card is now in the test DBs (visa) - so keep it short to stop it firing binLookup 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 +// intersolve (plastix) +export const GIFTCARD_NUMBER = '4010100000000000000'; +export const GIFTCARD_PIN = '73737'; + +export const INVALID_TEST_DATE_VALUE = '0390'; export const TEST_DATE_VALUE = '03/30'; export const TEST_CVC_VALUE = '737'; +export const TEST_POSTCODE = '1234'; export const BIN_LOOKUP_URL = `https://checkoutshopper-test.adyen.com/checkoutshopper/${BIN_LOOKUP_VERSION}/bin/binLookup?token=${process.env.CLIENT_KEY}`; @@ -28,3 +48,23 @@ export const ENCRYPTED_EXPIRY_DATE = 'encryptedExpiryDate'; export const ENCRYPTED_EXPIRY_MONTH = 'encryptedExpiryMonth'; export const ENCRYPTED_EXPIRY_YEAR = 'encryptedExpiryYear'; export const ENCRYPTED_SECURITY_CODE = 'encryptedSecurityCode'; + +export const TEST_MONTH_VALUE = '03'; +export const TEST_YEAR_VALUE = '30'; +export const TEST_PWD_VALUE = '12'; +export const TEST_TAX_NUMBER_VALUE = '123456'; +export const TEST_CPF_VALUE = '22936094488'; + +export const JWE_VERSION = '1'; +export const JWE_CONTENT_ALG = 'A256CBC-HS512'; +export const JWE_ALG = 'RSA-OAEP'; + +export const SR_INDICATOR_PREFIX = '-sr'; + +export const PAYMENT_RESULT = { + authorised: 'Authorised', + refused: 'Refused', + detailsSaved: 'Details saved', + success: 'Payment Successful', + fail: 'An unknown error occurred' +}; diff --git a/packages/e2e-playwright/tests/utils/getStoryUrl.ts b/packages/e2e-playwright/tests/utils/getStoryUrl.ts new file mode 100644 index 000000000..701d1957d --- /dev/null +++ b/packages/e2e-playwright/tests/utils/getStoryUrl.ts @@ -0,0 +1,12 @@ +// todo: add type +interface IGetStoryUrl { + baseUrl: string; + componentConfig?: any; + checkoutConfig?: any; + sessionPayload?: any; +} + +export const getStoryUrl = ({ baseUrl, componentConfig, checkoutConfig }: IGetStoryUrl) => { + const query = (config, key) => (config ? `&${key}=${JSON.stringify(config)}` : ''); + return `${baseUrl}${query(checkoutConfig, 'checkoutConfiguration')}${query(componentConfig, 'componentConfiguration')}`; +}; diff --git a/packages/e2e/app/config/postcss.config.js b/packages/e2e/app/config/postcss.config.js deleted file mode 100644 index 986069731..000000000 --- a/packages/e2e/app/config/postcss.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - plugins: [require('autoprefixer'), require('cssnano')] -}; diff --git a/packages/e2e/app/config/webpack.config.js b/packages/e2e/app/config/webpack.config.js deleted file mode 100644 index 619aa8ec7..000000000 --- a/packages/e2e/app/config/webpack.config.js +++ /dev/null @@ -1,117 +0,0 @@ -const webpack = require('webpack'); -const path = require('path'); -const HTMLWebpackPlugin = require('html-webpack-plugin'); -const checkoutDevServer = require('@adyen/adyen-web-server'); -const host = process.env.HOST || '0.0.0.0'; -const port = '3024'; -const resolve = dir => path.resolve(__dirname, dir); - -// NOTE: The first page in the array will be considered the index page. -const htmlPages = [ - { name: 'Drop-in', id: 'Dropin' }, - { name: 'Address', id: 'Address' }, - { name: 'Cards', id: 'Cards' }, - { name: 'CustomCards', id: 'CustomCards' }, - { name: 'Gift Cards', id: 'GiftCards' }, - { name: 'Issuer Lists', id: 'IssuerLists' }, - { name: 'Open Invoices', id: 'OpenInvoices' }, - { name: 'Drop-in Sessions', id: 'DropinSessions' }, - { name: 'Gift Cards Sessions', id: 'GiftCardsSessions' }, - { name: 'Vouchers', id: 'Vouchers' }, - { name: 'StoredCards', id: 'StoredCards' } -]; - -const htmlPageGenerator = ({ id }, index) => - new HTMLWebpackPlugin({ - filename: `${index ? `${id.toLowerCase()}/` : ''}index.html`, - template: path.join(__dirname, `../src/pages/${id}/${id}.html`), - templateParameters: () => ({ htmlWebpackPlugin: { htmlPages } }), - inject: 'body', - chunks: [`AdyenDemo${id}`], - chunksSortMode: 'manual' - }); - -const entriesReducer = (acc, { id }) => { - acc[`AdyenDemo${id}`] = path.join(__dirname, `../src/pages/${id}/${id}.js`); - return acc; -}; - -module.exports = { - mode: 'development', - - resolve: { - extensions: ['.js', '.jsx', '.ts', '.tsx', '.scss'] - }, - - plugins: [ - ...htmlPages.map(htmlPageGenerator), - new webpack.HotModuleReplacementPlugin(), - new webpack.DefinePlugin({ - 'process.env': { - __SF_ENV__: JSON.stringify(process.env.SF_ENV || 'build'), - __CLIENT_KEY__: JSON.stringify(process.env.CLIENT_KEY || null) - } - }) - ], - - devtool: 'cheap-module-source-map', - - entry: { - ...htmlPages.reduce(entriesReducer, {}) - }, - - watchOptions: { - ignored: ['/node_modules/', '/!(@adyen/adyen-web/dist)/'], - aggregateTimeout: 200, - poll: 500 - }, - - module: { - rules: [ - { - oneOf: [ - { - test: [/\.js?$/], - include: [resolve('../src')], - exclude: /node_modules/, - use: [ - { - loader: 'ts-loader', - options: { configFile: resolve('../../tsconfig.json') } - } - ] - }, - { - test: [/\.scss$/, /\.css$/], - resolve: { extensions: ['.scss', '.css'] }, - use: [ - { - loader: 'style-loader' - }, - { - loader: 'css-loader' - }, - { - loader: 'sass-loader' - } - ] - } - ] - } - ] - }, - - devServer: { - port, - host, - https: false, - hot: true, - compress: true, - onBeforeSetupMiddleware: devServer => { - if (!devServer) { - throw new Error('webpack-dev-server is not defined'); - } - checkoutDevServer(devServer.app); - } - } -}; diff --git a/packages/e2e/app/src/handlers.js b/packages/e2e/app/src/handlers.js deleted file mode 100644 index 26a7b5d31..000000000 --- a/packages/e2e/app/src/handlers.js +++ /dev/null @@ -1,59 +0,0 @@ -import { makePayment, makeDetailsCall } from './services'; - -export function handleResponse(response, component) { - if (response.action) { - component.handleAction(response.action, window.actionConfigObject || {}); - } else if (response.resultCode) { - alert(response.resultCode); - } -} - -export function handleError(obj) { - // SecuredField related errors should not go straight to console.error - if (obj.type === 'card') { - console.log('### Card::onError:: obj=', obj); - } else { - console.error(obj); - } -} - -export async function handleSubmit(state, component, actions) { - try { - const { action, order, resultCode, donationToken } = await makePayment(state.data); - - if (!resultCode) actions.reject(); - - actions.resolve({ - resultCode, - action, - order, - donationToken - }); - } catch (error) { - console.error('## onSubmit - critical error', error); - actions.reject(); - } -} - -export async function handleAdditionalDetails(state, component, actions) { - try { - const { resultCode, action, order, donationToken } = await makeDetailsCall(state.data); - - if (!resultCode) actions.reject(); - - actions.resolve({ - resultCode, - action, - order, - donationToken - }); - } catch (error) { - console.error('## onAdditionalDetails - critical error', error); - actions.reject(); - } -} - -export function handlePaymentCompleted(data, component) { - component.remove(); - alert(data.resultCode); -} diff --git a/packages/e2e/app/src/pages/Address/Address.html b/packages/e2e/app/src/pages/Address/Address.html deleted file mode 100644 index 0c7855b58..000000000 --- a/packages/e2e/app/src/pages/Address/Address.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - Adyen Web | Address - - - - -
-
-
-
-

Address

-
-
-
-
-
-
-
- - - - diff --git a/packages/e2e/app/src/pages/Address/Address.js b/packages/e2e/app/src/pages/Address/Address.js deleted file mode 100644 index 17510f7c1..000000000 --- a/packages/e2e/app/src/pages/Address/Address.js +++ /dev/null @@ -1,18 +0,0 @@ -import { AdyenCheckout, Address } from '@adyen/adyen-web'; -import '@adyen/adyen-web/styles/adyen.css'; -import '../../style.scss'; -import { countryCode } from '../../services/commonConfig'; - -const initCheckout = async () => { - window.checkout = await AdyenCheckout({ - countryCode, - _environmentUrls: { - cdn: { - translations: '/' - } - } - }); - window.address = new Address(checkout).mount('.address-field'); -}; - -initCheckout(); diff --git a/packages/e2e/app/src/pages/Cards/Cards.html b/packages/e2e/app/src/pages/Cards/Cards.html deleted file mode 100644 index e020ee698..000000000 --- a/packages/e2e/app/src/pages/Cards/Cards.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - Adyen Web | Cards - - - - -
-
-
-
-

Card

-
-
-
-
-
-
-
- - - - diff --git a/packages/e2e/app/src/pages/Cards/Cards.js b/packages/e2e/app/src/pages/Cards/Cards.js deleted file mode 100644 index 64dceec65..000000000 --- a/packages/e2e/app/src/pages/Cards/Cards.js +++ /dev/null @@ -1,52 +0,0 @@ -import { AdyenCheckout, Card } from '@adyen/adyen-web'; -import '@adyen/adyen-web/styles/adyen.css'; -import { handleSubmit, handleAdditionalDetails, handleError, handlePaymentCompleted } from '../../handlers'; -import { amount, shopperLocale, countryCode } from '../../services/commonConfig'; -import '../../style.scss'; - -const initCheckout = async () => { - // window.TextEncoder = null; // Comment in to force use of "compat" version - window.checkout = await AdyenCheckout({ - amount, - clientKey: process.env.__CLIENT_KEY__, - locale: shopperLocale, - countryCode, - environment: 'test', - _environmentUrls: { - cdn: { - translations: '/' - } - }, - onSubmit: handleSubmit, - onAdditionalDetails: handleAdditionalDetails, - onPaymentCompleted: handlePaymentCompleted, - onError: handleError, - ...window.mainConfiguration - }); - - // Credit card with installments - window.card = new Card(checkout, { - brands: ['mc', 'visa', 'amex', 'maestro', 'bcmc'], - onChange: state => { - /** - * Needed now that, for v5, we enhance the securedFields state.errors object with a rootNode prop - * - Testcafe doesn't like a ClientFunction retrieving an object with a DOM node in it!? - * - * AND, for some reason, if you place this onChange function in expiryDate.clientScripts.js it doesn't always get read. - * It'll work when it's part of a small batch but if part of the full test suite it gets ignored - so the tests that rely on - * window.mappedStateErrors fail - */ - if (!!Object.keys(state.errors).length) { - // Replace any rootNode values in the objects in state.errors with an empty string - const nuErrors = Object.entries(state.errors).reduce((acc, [fieldType, error]) => { - acc[fieldType] = error ? { ...error, rootNode: '' } : error; - return acc; - }, {}); - window.mappedStateErrors = nuErrors; - } - }, - ...window.cardConfig - }).mount('.card-field'); -}; - -initCheckout(); diff --git a/packages/e2e/app/src/pages/CustomCards/CustomCards.html b/packages/e2e/app/src/pages/CustomCards/CustomCards.html deleted file mode 100644 index 145529c41..000000000 --- a/packages/e2e/app/src/pages/CustomCards/CustomCards.html +++ /dev/null @@ -1,119 +0,0 @@ - - - - - - Adyen Web | Custom Cards - - - - - -
-
- -
-
-
-

CustomCard #1

-
-
- - card - - - - - - - - - -
-
-
- - -
-
-
-

CustomCard #2

-
-
- - - - - - - - - - - -
-
-
- - -
- - - - diff --git a/packages/e2e/app/src/pages/CustomCards/CustomCards.js b/packages/e2e/app/src/pages/CustomCards/CustomCards.js deleted file mode 100644 index c73dbb127..000000000 --- a/packages/e2e/app/src/pages/CustomCards/CustomCards.js +++ /dev/null @@ -1,89 +0,0 @@ -import { AdyenCheckout, CustomCard } from '@adyen/adyen-web'; -import '@adyen/adyen-web/styles/adyen.css'; -import { handleSubmit, handleAdditionalDetails } from '../../handlers'; -import { amount, shopperLocale, countryCode } from '../../services/commonConfig'; -import '../../style.scss'; -import './customcards.style.scss'; -import { setFocus, onBrand, onConfigSuccess, onBinLookup, onChange } from './customCards.config'; -import { makePayment } from '@adyen/adyen-web-playwright/app/src/services'; - -const initCheckout = async () => { - // window.TextEncoder = null; // Comment in to force use of "compat" version - window.checkout = await AdyenCheckout({ - amount, - clientKey: process.env.__CLIENT_KEY__, - locale: shopperLocale, - countryCode, - environment: 'test', - _environmentUrls: { - cdn: { - translations: '/' - } - }, - onSubmit: handleSubmit, - onAdditionalDetails: handleAdditionalDetails, - ...window.mainConfiguration - }); - - window.securedFields = new CustomCard(checkout, { - type: 'card', - brands: ['mc', 'visa', 'amex', 'bcmc', 'maestro', 'cartebancaire'], - onConfigSuccess, - onBrand, - onFocus: setFocus, - onBinLookup, - onChange, - ...window.cardConfig - }).mount('.secured-fields'); - - createPayButton('.secured-fields', window.securedFields, 'securedfields'); - - window.securedFields2 = new CustomCard(checkout, { - // type: 'card',// Deliberately exclude to ensure a default value is set - brands: ['mc', 'visa', 'amex', 'bcmc', 'maestro', 'cartebancaire'], - onConfigSuccess, - onBrand, - onFocus: setFocus, - onBinLookup, - onChange, - ...window.cardConfig - }).mount('.secured-fields-2'); - - createPayButton('.secured-fields-2', window.securedFields2, 'securedfields2'); - - function createPayButton(parent, component, attribute) { - const payBtn = document.createElement('button'); - - payBtn.textContent = 'Pay'; - payBtn.name = 'pay'; - payBtn.classList.add('adyen-checkout__button', 'js-components-button--one-click', `js-${attribute}`); - - payBtn.addEventListener('click', async e => { - e.preventDefault(); - - if (!component.isValid) return component.showValidation(); - - // formatData - const paymentMethod = { - type: 'scheme', - ...component.state.data - }; - component.state.data = { paymentMethod }; - - const response = await makePayment(component.state.data); - component.setStatus('ready'); - - if (response.action) { - component.handleAction(response.action, window.actionConfigObject || {}); - } else if (response.resultCode) { - alert(response.resultCode); - } - }); - - document.querySelector(parent).appendChild(payBtn); - - return payBtn; - } -}; - -initCheckout(); diff --git a/packages/e2e/app/src/pages/CustomCards/customCards.config.js b/packages/e2e/app/src/pages/CustomCards/customCards.config.js deleted file mode 100644 index 4f9bcb7f1..000000000 --- a/packages/e2e/app/src/pages/CustomCards/customCards.config.js +++ /dev/null @@ -1,279 +0,0 @@ -let hideCVC = false; -let optionalCVC = false; -let hideDate = false; -let optionalDate = false; -let isDualBranding = false; - -function setAttributes(el, attrs) { - for (const key in attrs) { - el.setAttribute(key, attrs[key]); - } -} - -function setLogosActive(rootNode, mode) { - const imageHolder = rootNode.querySelector('.pm-image'); - const dualBrandingImageHolder = rootNode.querySelector('.pm-image-dual'); - - switch (mode) { - case 'dualBranding_notValid': - Object.assign(imageHolder.style, { display: 'none' }); - Object.assign(dualBrandingImageHolder.style, { display: 'block', 'pointer-events': 'none', opacity: 0.5 }); - break; - - case 'dualBranding_valid': - Object.assign(imageHolder.style, { display: 'none' }); - Object.assign(dualBrandingImageHolder.style, { display: 'block', 'pointer-events': 'auto', opacity: 1 }); - break; - - default: - // reset - Object.assign(imageHolder.style, { display: 'block' }); - Object.assign(dualBrandingImageHolder.style, { display: 'none' }); - } -} - -export function onConfigSuccess(pCallbackObj) { - /** - * Set the UI to it's starting state - */ - pCallbackObj.rootNode.style.display = 'block'; - - pCallbackObj.rootNode.querySelector('.pm-image-dual').style.display = 'none'; - - setLogosActive(pCallbackObj.rootNode); -} - -export function setCCErrors(pCallbackObj) { - if (!pCallbackObj.rootNode) return; - - const sfNode = pCallbackObj.rootNode.querySelector(`[data-cse="${pCallbackObj.fieldType}"]`); - const errorNode = sfNode.parentNode.querySelector('.pm-form-label__error-text'); - - if (errorNode.innerText === '' && pCallbackObj.error === '') return; - - if (pCallbackObj.error !== '') { - errorNode.style.display = 'block'; - errorNode.innerText = pCallbackObj.errorI18n; - - // Add error classes - setErrorClasses(sfNode, true); - return; - } - - // Else: error === '' - errorNode.style.display = 'none'; - errorNode.innerText = ''; - - // Remove error classes - setErrorClasses(sfNode, false); -} - -export function setFocus(pCallbackObj) { - const sfNode = pCallbackObj.rootNode.querySelector(`[data-cse="${pCallbackObj.fieldType}"]`); - setFocusClasses(sfNode, pCallbackObj.focus); -} - -export function onBrand(pCallbackObj) { - /** - * If not in dual branding mode - add card brand to first image element - */ - if (!isDualBranding) { - const brandLogo1 = pCallbackObj.rootNode.querySelector('img'); - setAttributes(brandLogo1, { - src: pCallbackObj.brandImageUrl, - alt: pCallbackObj.brand - }); - } - - /** - * Deal with showing/hiding CVC field - */ - const cvcNode = pCallbackObj.rootNode.querySelector('.pm-form-label--cvc'); - - if (pCallbackObj.cvcPolicy === 'hidden' && !hideCVC) { - hideCVC = true; - cvcNode.style.display = 'none'; - } - - if (hideCVC && pCallbackObj.cvcPolicy !== 'hidden') { - hideCVC = false; - cvcNode.style.display = 'block'; - } - - // Optional cvc fields - if (pCallbackObj.cvcPolicy === 'optional' && !optionalCVC) { - optionalCVC = true; - if (cvcNode) cvcNode.querySelector('.pm-form-label__text').innerText = 'CVV/CVC (optional):'; - } - - if (optionalCVC && pCallbackObj.cvcPolicy !== 'optional') { - optionalCVC = false; - if (cvcNode) cvcNode.querySelector('.pm-form-label__text').innerText = 'CVV/CVC:'; - } - - /** - * Deal with showing/hiding date field(s) - */ - const dateNode = pCallbackObj.rootNode.querySelector('.pm-form-label--exp-date'); - const monthNode = pCallbackObj.rootNode.querySelector('.pm-form-label--exp-month'); - const yearNode = pCallbackObj.rootNode.querySelector('.pm-form-label--exp-year'); - - if (pCallbackObj.expiryDatePolicy === 'hidden' && !hideDate) { - hideDate = true; - if (dateNode) dateNode.style.display = 'none'; - if (monthNode) monthNode.style.display = 'none'; - if (yearNode) yearNode.style.display = 'none'; - } - - if (hideDate && pCallbackObj.expiryDatePolicy !== 'hidden') { - hideDate = false; - if (dateNode) dateNode.style.display = 'block'; - if (monthNode) monthNode.style.display = 'block'; - if (yearNode) yearNode.style.display = 'block'; - } - - // Optional date fields - if (pCallbackObj.expiryDatePolicy === 'optional' && !optionalDate) { - optionalDate = true; - if (dateNode) dateNode.querySelector('.pm-form-label__text').innerText = 'Expiry date (optional):'; - if (monthNode) monthNode.querySelector('.pm-form-label__text').innerText = 'Expiry month (optional):'; - if (yearNode) yearNode.querySelector('.pm-form-label__text').innerText = 'Expiry year (optional):'; - } - - if (optionalDate && pCallbackObj.expiryDatePolicy !== 'optional') { - optionalDate = false; - if (dateNode) dateNode.querySelector('.pm-form-label__text').innerText = 'Expiry date:'; - if (monthNode) monthNode.querySelector('.pm-form-label__text').innerText = 'Expiry month:'; - if (yearNode) yearNode.querySelector('.pm-form-label__text').innerText = 'Expiry year:'; - } -} - -function dualBrandListener(e) { - securedFields.dualBrandingChangeHandler(e); -} - -function resetDualBranding(rootNode) { - isDualBranding = false; - - setLogosActive(rootNode); - - const brandLogo1 = rootNode.querySelector('.pm-image-dual-1'); - brandLogo1.removeEventListener('click', dualBrandListener); - - const brandLogo2 = rootNode.querySelector('.pm-image-dual-2'); - brandLogo2.removeEventListener('click', dualBrandListener); -} - -/** - * Implementing dual branding - */ -function onDualBrand(pCallbackObj) { - const brandLogo1 = pCallbackObj.rootNode.querySelector('.pm-image-dual-1'); - const brandLogo2 = pCallbackObj.rootNode.querySelector('.pm-image-dual-2'); - - isDualBranding = true; - - const supportedBrands = pCallbackObj.supportedBrandsRaw; - - /** - * Set first brand icon (and, importantly also add alt &/or data-value attrs); and add event listener - */ - setAttributes(brandLogo1, { - src: supportedBrands[0].brandImageUrl, - alt: supportedBrands[0].brand, - 'data-value': supportedBrands[0].brand - }); - - brandLogo1.addEventListener('click', dualBrandListener); - - /** - * Set second brand icon (and, importantly also add alt &/or data-value attrs); and add event listener - */ - setAttributes(brandLogo2, { - src: supportedBrands[1].brandImageUrl, - alt: supportedBrands[1].brand, - 'data-value': supportedBrands[1].brand - }); - brandLogo2.addEventListener('click', dualBrandListener); -} - -export function onBinLookup(pCallbackObj) { - /** - * Dual branded result... - */ - if (pCallbackObj.supportedBrandsRaw?.length > 1) { - onDualBrand(pCallbackObj); - return; - } - - /** - * ...else - binLookup 'reset' result or binLookup result with only one brand - */ - resetDualBranding(pCallbackObj.rootNode); -} - -export function onChange(state, component) { - // From v5 the onError handler is no longer only for card comp related errors - so watch state.errors and call the card specific setCCErrors based on this - if (!!Object.keys(state.errors).length) { - const errors = Object.entries(state.errors).map(([fieldType, error]) => { - return { - fieldType, - ...(error ? error : { error: '', rootNode: component._node }) - }; - }); - errors.forEach(setCCErrors); - } - - /** - * If we're in a dual branding scenario & the number field becomes valid or is valid and become invalid - * - set the brand logos to the required 'state' - */ - if (isDualBranding) { - const mode = state.valid.encryptedCardNumber ? 'dualBranding_valid' : 'dualBranding_notValid'; - setLogosActive(component._node, mode); - } - - /** - * For running the e2e tests in testcafe - we need a mapped version of state.errors - * since, for v5, we enhance the securedFields state.errors object with a rootNode prop - * & Testcafe doesn't like a ClientFunction retrieving an object with a DOM node in it!? - */ - if (!!Object.keys(state.errors).length) { - // Replace any rootNode values in the objects in state.errors with an empty string - const nuErrors = Object.entries(state.errors).reduce((acc, [fieldType, error]) => { - acc[fieldType] = error ? { ...error, rootNode: '' } : error; - return acc; - }, {}); - window.mappedStateErrors = nuErrors; - } -} - -const setErrorClasses = function(pNode, pSetErrors) { - if (pSetErrors) { - if (pNode.className.indexOf('pm-input-field--error') === -1) { - pNode.className += ' pm-input-field--error'; - } - return; - } - - // Remove errors - if (pNode.className.indexOf('pm-input-field--error') > -1) { - const newClassName = pNode.className.replace('pm-input-field--error', ''); - pNode.className = newClassName.trim(); - } -}; - -const setFocusClasses = function(pNode, pSetFocus) { - if (pSetFocus) { - if (pNode.className.indexOf('pm-input-field--focus') === -1) { - pNode.className += ' pm-input-field--focus'; - } - return; - } - - // Remove focus - if (pNode.className.indexOf('pm-input-field--focus') > -1) { - const newClassName = pNode.className.replace('pm-input-field--focus', ''); - pNode.className = newClassName.trim(); - } -}; diff --git a/packages/e2e/app/src/pages/CustomCards/customcards.style.scss b/packages/e2e/app/src/pages/CustomCards/customcards.style.scss deleted file mode 100644 index 2eb27b23b..000000000 --- a/packages/e2e/app/src/pages/CustomCards/customcards.style.scss +++ /dev/null @@ -1,99 +0,0 @@ -.merchant-checkout__payment-method{ - padding-top: 20px; -} - -.secured-fields, -.secured-fields-2 { - position: relative; - font-family: 'Open Sans', sans-serif; - font-size: 14px; - padding: 0 24px; -} -.pm-image, .pm-image-dual{ - background-color: #ffffff; - border-radius: 4px; - -moz-boder-radius: 4px; - -webkit-border-radius: 4px; - float: right; - line-height: 0; - position: relative; - overflow: hidden; -} -.pm-form-label { - float: left; - padding-bottom: 1em; - position: relative; - width: 100%; -} -.pm-form-label--exp-date { - width: 40%; -} -.pm-form-label--cvc { - float: right; - width: 40%; -} -.pm-form-label__text { - color: #00112c; - float: left; - font-size: 0.93333em; - padding-bottom: 6px; - position: relative; -} -.pm-input-field { - background: white; - border: 1px solid #d8d8d8; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - border-radius: 4px; - box-sizing: border-box; - clear: left; - font-size: 0.93333333333em; - float: left; - padding: 8px; - position: relative; - width: 100%; - height: 35px; -} - -.pm-form-label__error-text { - color: #ff7d00; - display: none; - float: left; - font-size: 13px; - padding-top: 0.4em; - position: relative; - width: 100%; -} - -/* Set dynamically */ -.pm-input-field--error, -.secured-fields-si.pm-input-field--error { - border: 1px solid #ff7d00; -} - -.pm-input-field--focus { - border: 1px solid #969696; - outline: none; -} -.pm-input-field--error.pm-input-field--focus { - border: 1px solid #ff7d00; -} - -.card-input__spinner__holder { - position: relative; - top: 40px; -} - -.card-input__spinner { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 1; - display: none; -} - -.card-input__spinner--active { - display: block; -} diff --git a/packages/e2e/app/src/pages/Dropin/Dropin.html b/packages/e2e/app/src/pages/Dropin/Dropin.html deleted file mode 100644 index f43e81f87..000000000 --- a/packages/e2e/app/src/pages/Dropin/Dropin.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - Adyen Web | Drop-in - - - - - -
-
-
-
-
- - - - diff --git a/packages/e2e/app/src/pages/Dropin/Dropin.js b/packages/e2e/app/src/pages/Dropin/Dropin.js deleted file mode 100644 index dba2b07f9..000000000 --- a/packages/e2e/app/src/pages/Dropin/Dropin.js +++ /dev/null @@ -1,33 +0,0 @@ -import { AdyenCheckout, Dropin } from '@adyen/adyen-web/auto'; -import '@adyen/adyen-web/styles/adyen.css'; -import { getPaymentMethods } from '../../services'; -import { amount, shopperLocale, countryCode } from '../../services/commonConfig'; -import { handleSubmit, handleAdditionalDetails, handleError, handlePaymentCompleted } 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, - _environmentUrls: { - cdn: { - translations: '/' - } - }, - environment: 'test', - onSubmit: handleSubmit, - onAdditionalDetails: handleAdditionalDetails, - onPaymentCompleted: handlePaymentCompleted, - onError: handleError, - ...window.mainConfiguration - }); - - window.dropin = new Dropin(checkout, { ...window.dropinConfig }).mount('#dropin-container'); -}; - -initCheckout(); diff --git a/packages/e2e/app/src/pages/DropinSessions/DropinSessions.html b/packages/e2e/app/src/pages/DropinSessions/DropinSessions.html deleted file mode 100644 index 7357e0f68..000000000 --- a/packages/e2e/app/src/pages/DropinSessions/DropinSessions.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - Adyen Web | Drop-in Sessions - - - - - -
-
-
-
-
- - - - diff --git a/packages/e2e/app/src/pages/DropinSessions/DropinSessions.js b/packages/e2e/app/src/pages/DropinSessions/DropinSessions.js deleted file mode 100644 index e4b97d22e..000000000 --- a/packages/e2e/app/src/pages/DropinSessions/DropinSessions.js +++ /dev/null @@ -1,39 +0,0 @@ -import { AdyenCheckout, Dropin, Card, Giftcard } from '@adyen/adyen-web'; -import '@adyen/adyen-web/styles/adyen.css'; -import { createSession } from '../../services'; -import { amount, shopperLocale, countryCode, returnUrl, shopperReference } from '../../services/commonConfig'; -import '../../style.scss'; - -const initCheckout = async () => { - const session = await createSession({ - amount, - reference: 'ABC123', - returnUrl, - shopperLocale, - shopperReference, - countryCode - }); - - const checkout = await AdyenCheckout({ - environment: 'test', - clientKey: process.env.__CLIENT_KEY__, - session, - _environmentUrls: { - cdn: { - translations: '/' - } - }, - - onPaymentCompleted: (result, component) => { - console.info(result, component); - }, - onError: (error, component) => { - console.error(error.message, component); - }, - ...window.mainConfiguration - }); - - window.dropin = new Dropin(checkout, { paymentMethodComponents: [Card, Giftcard], ...window.dropinConfig }).mount('#dropin-sessions-container'); -}; - -initCheckout(); diff --git a/packages/e2e/app/src/pages/GiftCards/GiftCards.html b/packages/e2e/app/src/pages/GiftCards/GiftCards.html deleted file mode 100644 index e6579579c..000000000 --- a/packages/e2e/app/src/pages/GiftCards/GiftCards.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - Adyen Web | Gift Cards - - - - -
-
-
-
-

Gift Card

-
-
-
-
-
-
-
- - - - diff --git a/packages/e2e/app/src/pages/GiftCards/GiftCards.js b/packages/e2e/app/src/pages/GiftCards/GiftCards.js deleted file mode 100644 index bd6847c2d..000000000 --- a/packages/e2e/app/src/pages/GiftCards/GiftCards.js +++ /dev/null @@ -1,40 +0,0 @@ -import { AdyenCheckout, Giftcard } from '@adyen/adyen-web'; -import '@adyen/adyen-web/styles/adyen.css'; -import { handleSubmit, handleAdditionalDetails, handleError, handlePaymentCompleted } from '../../handlers'; -import { checkBalance, createOrder } from '../../services'; -import { amount, shopperLocale, countryCode } from '../../services/commonConfig'; -import '../../style.scss'; - -const initCheckout = async () => { - window.checkout = await AdyenCheckout({ - amount, - clientKey: process.env.__CLIENT_KEY__, - locale: shopperLocale, - _environmentUrls: { - cdn: { - translations: '/' - } - }, - countryCode, - environment: 'test', - showPayButton: true, - onSubmit: handleSubmit, - onAdditionalDetails: handleAdditionalDetails, - onPaymentCompleted: handlePaymentCompleted, - onError: handleError, - ...window.mainConfiguration - }); - - window.giftcard = new Giftcard(window.checkout, { - type: 'giftcard', - brand: 'valuelink', - onBalanceCheck: async (resolve, reject, data) => { - resolve(await checkBalance(data)); - }, - onOrderRequest: async (resolve, reject) => { - resolve(await createOrder({ amount })); - } - }).mount('.card-field'); -}; - -initCheckout(); diff --git a/packages/e2e/app/src/pages/GiftCardsSessions/GiftCardsSessions.html b/packages/e2e/app/src/pages/GiftCardsSessions/GiftCardsSessions.html deleted file mode 100644 index e6579579c..000000000 --- a/packages/e2e/app/src/pages/GiftCardsSessions/GiftCardsSessions.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - Adyen Web | Gift Cards - - - - -
-
-
-
-

Gift Card

-
-
-
-
-
-
-
- - - - diff --git a/packages/e2e/app/src/pages/GiftCardsSessions/GiftCardsSessions.js b/packages/e2e/app/src/pages/GiftCardsSessions/GiftCardsSessions.js deleted file mode 100644 index d8ecd2681..000000000 --- a/packages/e2e/app/src/pages/GiftCardsSessions/GiftCardsSessions.js +++ /dev/null @@ -1,59 +0,0 @@ -import { AdyenCheckout, Giftcard } from '@adyen/adyen-web'; -import '@adyen/adyen-web/styles/adyen.css'; -import { createSession } from '../../services'; -import { amount, shopperLocale, countryCode, returnUrl, shopperReference } from '../../services/commonConfig'; -import '../../style.scss'; - -const initCheckout = async () => { - const session = await createSession({ - amount, - reference: 'ABC123', - returnUrl, - shopperLocale, - shopperReference, - countryCode - }); - - window.sessionCheckout = await AdyenCheckout({ - environment: 'test', - clientKey: process.env.__CLIENT_KEY__, - showPayButton: true, - session, - _environmentUrls: { - cdn: { - translations: '/' - } - }, - - // Events - beforeSubmit: (data, component, actions) => { - actions.resolve(data); - }, - onPaymentCompleted: (result, component) => { - console.info(result, component); - }, - onError: (error, component) => { - console.log('arg', error); - console.error(error.message, component); - } - }); - - window.giftcard = new Giftcard(window.sessionCheckout, { - type: 'giftcard', - brand: 'valuelink', - onOrderUpdated: data => { - window.onOrderUpdatedTestData = data; - }, - onRequiringConfirmation: () => { - window.onRequiringConfirmationTestData = true; - }, - brandsConfiguration: { - genericgiftcard: { - icon: 'https://checkoutshopper-test.adyen.com/checkoutshopper/images/logos/mc.svg', - name: 'Gifty mcGiftface' - } - } - }).mount('.card-field'); -}; - -initCheckout(); diff --git a/packages/e2e/app/src/pages/IssuerLists/IssuerLists.html b/packages/e2e/app/src/pages/IssuerLists/IssuerLists.html deleted file mode 100644 index ba1ca8c08..000000000 --- a/packages/e2e/app/src/pages/IssuerLists/IssuerLists.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - Adyen Web | Issuer Lists - - - - -
-
-
-
-

iDeal

-
-
-
-
-
-
-
- - - - diff --git a/packages/e2e/app/src/pages/IssuerLists/IssuerLists.js b/packages/e2e/app/src/pages/IssuerLists/IssuerLists.js deleted file mode 100644 index c9752c282..000000000 --- a/packages/e2e/app/src/pages/IssuerLists/IssuerLists.js +++ /dev/null @@ -1,40 +0,0 @@ -import { AdyenCheckout, Redirect } from '@adyen/adyen-web'; -import '@adyen/adyen-web/styles/adyen.css'; -import { handleSubmit, handleAdditionalDetails, handleError, handlePaymentCompleted } from '../../handlers'; -import { amount, shopperLocale, countryCode } from '../../services/commonConfig'; -import '../../style.scss'; -import { getPaymentMethods } from '../../services'; - -const initCheckout = async () => { - const paymentMethodsResponse = await getPaymentMethods({ - amount, - shopperLocale - }); - - const checkout = await AdyenCheckout({ - analytics: { - enabled: false - }, - amount, - paymentMethodsResponse, - clientKey: process.env.__CLIENT_KEY__, - locale: shopperLocale, - _environmentUrls: { - cdn: { - translations: '/' - } - }, - countryCode, - environment: 'test', - showPayButton: true, - onSubmit: handleSubmit, - onAdditionalDetails: handleAdditionalDetails, - onPaymentCompleted: handlePaymentCompleted, - onError: handleError - // ...window.mainConfiguration - }); - - window.ideal = new Redirect(checkout, { type: 'ideal' }).mount('.ideal-field'); -}; - -initCheckout(); diff --git a/packages/e2e/app/src/pages/OpenInvoices/OpenInvoices.html b/packages/e2e/app/src/pages/OpenInvoices/OpenInvoices.html deleted file mode 100644 index 8381e3aa2..000000000 --- a/packages/e2e/app/src/pages/OpenInvoices/OpenInvoices.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - Adyen Web | Issuer Lists - - - - -
-
-
-
-

AfterPay

-
-
-
-
-
-
-
- - - - diff --git a/packages/e2e/app/src/pages/OpenInvoices/OpenInvoices.js b/packages/e2e/app/src/pages/OpenInvoices/OpenInvoices.js deleted file mode 100644 index ec3b3778e..000000000 --- a/packages/e2e/app/src/pages/OpenInvoices/OpenInvoices.js +++ /dev/null @@ -1,37 +0,0 @@ -import { AdyenCheckout, AfterPay } from '@adyen/adyen-web'; -import '@adyen/adyen-web/styles/adyen.css'; -import { handleSubmit, handleAdditionalDetails, handleError, handlePaymentCompleted } from '../../handlers'; -import { amount, shopperLocale, countryCode } from '../../services/commonConfig'; -import '../../style.scss'; -import { getPaymentMethods } from '../../services'; - -const initCheckout = async () => { - const paymentMethodsResponse = await getPaymentMethods({ - amount, - shopperLocale - }); - - const checkout = await AdyenCheckout({ - amount, - paymentMethodsResponse, - clientKey: process.env.__CLIENT_KEY__, - locale: shopperLocale, - _environmentUrls: { - cdn: { - translations: '/' - } - }, - countryCode, - environment: 'test', - showPayButton: true, - onSubmit: handleSubmit, - onAdditionalDetails: handleAdditionalDetails, - onPaymentCompleted: handlePaymentCompleted, - onError: handleError - // ...window.mainConfiguration - }); - - window.afterpay = new AfterPay(checkout).mount('.afterpay-field'); -}; - -initCheckout(); diff --git a/packages/e2e/app/src/pages/StoredCards/StoredCards.html b/packages/e2e/app/src/pages/StoredCards/StoredCards.html deleted file mode 100644 index 66a5c61f9..000000000 --- a/packages/e2e/app/src/pages/StoredCards/StoredCards.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - Adyen Web | StoredCards - - - - -
-
-
-
-

StoredCard

-
-
-
-
-
-
-
- - - - diff --git a/packages/e2e/app/src/pages/StoredCards/StoredCards.js b/packages/e2e/app/src/pages/StoredCards/StoredCards.js deleted file mode 100644 index 9b51b9a8b..000000000 --- a/packages/e2e/app/src/pages/StoredCards/StoredCards.js +++ /dev/null @@ -1,46 +0,0 @@ -import { AdyenCheckout, Card } from '@adyen/adyen-web'; -import '@adyen/adyen-web/styles/adyen.css'; -import { handleSubmit, handleAdditionalDetails, handleError, handlePaymentCompleted } from '../../handlers'; -import { amount, shopperLocale, countryCode } from '../../services/commonConfig'; -import '../../style.scss'; - -const initCheckout = async () => { - window.checkout = await AdyenCheckout({ - amount, - clientKey: process.env.__CLIENT_KEY__, - _environmentUrls: { - cdn: { - translations: '/' - } - }, - locale: shopperLocale, - countryCode, - environment: 'test', - showPayButton: true, - onSubmit: handleSubmit, - onAdditionalDetails: handleAdditionalDetails, - onPaymentCompleted: handlePaymentCompleted, - onError: handleError, - ...window.mainConfiguration - }); - - const storedCardData = { - brand: 'visa', - expiryMonth: '03', - expiryYear: '2030', - holderName: 'Checkout Shopper PlaceHolder', - id: '8415611088427239', - lastFour: '1111', - name: 'VISA', - networkTxReference: '059172561886790', - supportedShopperInteractions: ['Ecommerce', 'ContAuth'], - type: 'scheme', - storedPaymentMethodId: '8415611088427239', - ...window.cardConfig - }; - - // Stored Credit card - window.storedCard = new Card(checkout, { ...storedCardData }).mount('.stored-card-field'); -}; - -initCheckout(); diff --git a/packages/e2e/app/src/pages/Vouchers/Vouchers.html b/packages/e2e/app/src/pages/Vouchers/Vouchers.html deleted file mode 100644 index 756be534a..000000000 --- a/packages/e2e/app/src/pages/Vouchers/Vouchers.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - Adyen Web | Vouchers - - - - - -
-
-

Boleto Bancario

-
-
-
-
- - - - diff --git a/packages/e2e/app/src/pages/Vouchers/Vouchers.js b/packages/e2e/app/src/pages/Vouchers/Vouchers.js deleted file mode 100644 index de600f92a..000000000 --- a/packages/e2e/app/src/pages/Vouchers/Vouchers.js +++ /dev/null @@ -1,26 +0,0 @@ -import { AdyenCheckout, Boleto } from '@adyen/adyen-web'; -import '@adyen/adyen-web/styles/adyen.css'; -import { shopperLocale, countryCode } from '../../services/commonConfig'; -import '../../style.scss'; - -const initCheckout = async () => { - window.checkout = await AdyenCheckout({ - clientKey: process.env.__CLIENT_KEY__, - locale: shopperLocale, - _environmentUrls: { - cdn: { - translations: '/' - } - }, - environment: 'test', - countryCode - }); - - // Boleto Input - window.boletoInput = new Boleto(window.checkout, { - type: 'boletobancario', - ...window.boletoConfig - }).mount('#boleto-input-container'); -}; - -initCheckout(); diff --git a/packages/e2e/app/src/services/commonConfig.js b/packages/e2e/app/src/services/commonConfig.js deleted file mode 100644 index 38abc45c0..000000000 --- a/packages/e2e/app/src/services/commonConfig.js +++ /dev/null @@ -1,26 +0,0 @@ -import getCurrency from '../utils/getCurrency'; -import { getSearchParameters } from '../utils/utils'; - -const DEFAULT_LOCALE = 'en-US'; -const DEFAULT_COUNTRY = 'US'; - -const urlParams = getSearchParameters(window.location.search); -export const shopperLocale = DEFAULT_LOCALE; -export const countryCode = urlParams.countryCode || DEFAULT_COUNTRY; -export const currency = getCurrency(countryCode); -export const amountValue = urlParams.amount || 25900; -export const amount = { - currency, - value: Number(amountValue) -}; - -export const returnUrl = 'http://localhost:3024/result'; -export const shopperReference = 'newshoppert'; - -export default { - amount, - countryCode, - shopperLocale, - channel: 'Web', - shopperReference: 'newshoppert' -}; diff --git a/packages/e2e/app/src/services/index.js b/packages/e2e/app/src/services/index.js deleted file mode 100644 index c35733daa..000000000 --- a/packages/e2e/app/src/services/index.js +++ /dev/null @@ -1,81 +0,0 @@ -import paymentMethodsConfig from './paymentMethodsConfig'; -import paymentsConfig from './paymentsConfig'; -import { httpPost } from '../utils/utils'; - -export const createSession = (data, config = {}) => { - return httpPost('sessions', data) - .then(response => { - if (response.error) throw 'Session initiation failed'; - return response; - }) - .catch(console.error); -}; - -export const getPaymentMethods = configuration => - httpPost('paymentMethods', { ...paymentMethodsConfig, ...configuration }) - .then(response => { - if (response.error) throw 'No paymentMethods available'; - return response; - }) - .catch(console.error); - -export const makePayment = (data, config = {}) => { - if (data.paymentMethod.storedPaymentMethodId) { - config = { recurringProcessingModel: 'CardOnFile', ...config }; - } - - // NOTE: Merging data object. DO NOT do this in production. - - // Needed for storedPMs in v70 if a standalone comp, or, in Dropin, advanced flow. (Sessions, v70, works with or without this prop) - if (data.paymentMethod.storedPaymentMethodId) { - config = { recurringProcessingModel: 'CardOnFile', ...config }; - } - - const paymentRequest = { ...paymentsConfig, ...config, ...data }; - return httpPost('payments', paymentRequest) - .then(response => { - if (response.error) throw 'Payment initiation failed'; - return response; - }) - .catch(console.error); -}; - -export const makeDetailsCall = data => - httpPost('details', data) - .then(response => { - if (response.error) throw 'Details call failed'; - return response; - }) - .catch(err => console.error(err)); - -export const getOriginKey = (originKeyOrigin = document.location.origin) => - httpPost('originKeys', { originDomains: [originKeyOrigin] }).then(response => response.originKeys[originKeyOrigin]); - -export const checkBalance = data => { - return httpPost('paymentMethods/balance', data) - .then(response => { - if (response.error) throw 'Balance call failed'; - return response; - }) - .catch(err => console.error(err)); -}; - -export const createOrder = data => { - const reference = `order-reference-${Date.now()}`; - - return httpPost('orders', { reference, ...data }) - .then(response => { - if (response.error) throw 'Orders call failed'; - return response; - }) - .catch(err => console.error(err)); -}; - -export const cancelOrder = data => { - return httpPost('orders/cancel', data) - .then(response => { - if (response.error) throw 'Orders call failed'; - return response; - }) - .catch(err => console.error(err)); -}; diff --git a/packages/e2e/app/src/services/paymentMethodsConfig.js b/packages/e2e/app/src/services/paymentMethodsConfig.js deleted file mode 100644 index 549b1534f..000000000 --- a/packages/e2e/app/src/services/paymentMethodsConfig.js +++ /dev/null @@ -1,15 +0,0 @@ -import commonConfiguration from './commonConfig'; - -const paymentMethodsConfig = { - ...commonConfiguration, - shopperName: { - firstName: 'Jan', - lastName: 'Jansen', - gender: 'MALE' - }, - telephoneNumber: '0612345678', - shopperEmail: 'test@adyen.com', - dateOfBirth: '1970-07-10' -}; - -export default paymentMethodsConfig; diff --git a/packages/e2e/app/src/services/paymentsConfig.js b/packages/e2e/app/src/services/paymentsConfig.js deleted file mode 100644 index 06d2077c0..000000000 --- a/packages/e2e/app/src/services/paymentsConfig.js +++ /dev/null @@ -1,34 +0,0 @@ -import commonConfiguration from './commonConfig'; - -const identifier = new Date().getMilliseconds(); -const { origin = 'http://localhost:3024', search } = window.location; -const returnUrl = origin + search; - -const paymentsConfig = { - ...commonConfiguration, - origin, - returnUrl, - reference: `${identifier}-checkout-components-ref`, - additionalData: { - allow3DS2: true - }, - shopperEmail: 'test@adyen.com', - shopperIP: '172.30.0.1', - channel: 'Web', - browserInfo: { - acceptHeader: 'http' - }, - lineItems: [ - { - taxPercentage: 0, - id: 'item1', - taxAmount: 0, - description: 'Test Item 1', - amountIncludingTax: 75900, - quantity: 1, - taxCategory: 'None', - amountExcludingTax: 75900 - } - ] -}; -export default paymentsConfig; diff --git a/packages/e2e/app/src/style.scss b/packages/e2e/app/src/style.scss deleted file mode 100644 index 1484d55ff..000000000 --- a/packages/e2e/app/src/style.scss +++ /dev/null @@ -1,16 +0,0 @@ -*, -*:after, -*:before { - box-sizing: border-box; -} -html, -body { - font: 16px/1.21 -apple-system, BlinkMacSystemFont, sans-serif; - font-weight: 400; - margin: 0; -} - -.merchant-checkout__form { - max-width: 540px; - margin: 5vh auto; -} diff --git a/packages/e2e/app/src/utils/getCurrency.js b/packages/e2e/app/src/utils/getCurrency.js deleted file mode 100644 index 6b861cf32..000000000 --- a/packages/e2e/app/src/utils/getCurrency.js +++ /dev/null @@ -1,36 +0,0 @@ -const currencies = { - AR: 'ARS', - AU: 'AUD', - BR: 'BRL', - CA: 'CAD', - CH: 'CHF', - CN: 'CNY', - DK: 'DKK', - GB: 'GBP', - HK: 'HKD', - HU: 'HUN', - ID: 'IDR', - IN: 'INR', - JP: 'JPY', - KR: 'KRW', - MG: 'MGA', - MX: 'MXN', - MY: 'MYR', - NO: 'NOK', - NZ: 'NZD', - PH: 'PHP', - PL: 'PLN', - RO: 'RON', - RU: 'RUB', - SE: 'SEK', - SG: 'SGD', - TH: 'THB', - TW: 'TWD', - US: 'USD', - VN: 'VND', - default: 'EUR' -}; - -const getCurrency = countryCode => currencies[countryCode] || currencies.default; - -export default getCurrency; diff --git a/packages/e2e/app/src/utils/utils.js b/packages/e2e/app/src/utils/utils.js deleted file mode 100644 index f329d9e57..000000000 --- a/packages/e2e/app/src/utils/utils.js +++ /dev/null @@ -1,21 +0,0 @@ -const { host, protocol } = window.location; - -export const httpPost = (endpoint, data) => - fetch(`${protocol}//${host}/${endpoint}`, { - method: 'POST', - headers: { - Accept: 'application/json, text/plain, */*', - 'Content-Type': 'application/json' - }, - body: JSON.stringify(data) - }).then(response => response.json()); - -export const getSearchParameters = (search = window.location.search) => - search - .replace(/\?/g, '') - .split('&') - .reduce((acc, cur) => { - const [key, prop = ''] = cur.split('='); - acc[key] = decodeURIComponent(prop); - return acc; - }, {}); diff --git a/packages/e2e/package.json b/packages/e2e/package.json deleted file mode 100644 index b1dfce769..000000000 --- a/packages/e2e/package.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "@adyen/adyen-web-e2e", - "private": true, - "keywords": [ - "adyen", - "adyen-web", - "checkout", - "payment", - "payments", - "components" - ], - "version": "1.0.0", - "license": "MIT", - "homepage": "https://docs.adyen.com/checkout", - "repository": "github:Adyen/adyen-web", - "bugs": { - "url": "https://support.adyen.com/" - }, - "scripts": { - "start": "npm run dev-server", - "dev-server": "cross-env NODE_ENV=test webpack-dev-server --config app/config/webpack.config.js", - "test:e2e": "concurrently --kill-others --success first --names \"app,e2e\" \"npm run start\" \"node tests/index.js\"", - "test:e2e-remote": "concurrently --kill-others --success first --names \"app,e2e\" \"npm run start\" \"node tests/index.js --remote\"" - }, - "devDependencies": { - "@adyen/adyen-web-server": "1.0.0", - "concurrently": "8.2.2", - "cross-env": "^7.0.3", - "css-loader": "^6.0.0", - "dotenv": "16.4.4", - "html-webpack-plugin": "5.5.1", - "sass-loader": "^10.2.0", - "source-map-loader": "^1.1.3", - "style-loader": "^2.0.0", - "testcafe": "^3.6.2", - "ts-loader": "^8.1.0", - "typescript": "^4.4.4", - "webpack": "5.94.0", - "webpack-cli": "5.1.4", - "webpack-dev-server": "4.15.1", - "whatwg-fetch": "^3.6.2" - }, - "dependencies": { - "@adyen/adyen-web": "6.5.0" - } -} diff --git a/packages/e2e/tests/_common/cardMocks.js b/packages/e2e/tests/_common/cardMocks.js deleted file mode 100644 index 94ed80a88..000000000 --- a/packages/e2e/tests/_common/cardMocks.js +++ /dev/null @@ -1,45 +0,0 @@ -import { ClientFunction, RequestMock } from 'testcafe'; -import { BASE_URL } from '../pages'; -import { BIN_LOOKUP_VERSION } from '../cards/utils/constants'; - -import path from 'path'; -require('dotenv').config({ path: path.resolve('../../', '.env') }); - -export const binLookupUrl = `https://checkoutshopper-test.adyen.com/checkoutshopper/${BIN_LOOKUP_VERSION}/bin/binLookup?token=${process.env.CLIENT_KEY}`; - -/** - * Functionality for mocking a /binLookup API response via testcafe's fixture.requestHooks() - * - * @param requestURL - * @param mockedResponse - * @returns {RequestMock} - */ -export const getBinLookupMock = (requestURL, mockedResponse) => { - return RequestMock() - .onRequestTo(request => { - return request.url === requestURL && request.method === 'post'; - }) - .respond( - (req, res) => { - const body = JSON.parse(req.body); - mockedResponse.requestId = body.requestId; - res.setBody(mockedResponse); - }, - 200, - { - 'Access-Control-Allow-Origin': BASE_URL - } - ); -}; - -// For the tests as a whole - throw an error if SDK binLookup mocking is turned on -export const checkSDKMocking = ClientFunction(() => { - if (window.mockBinCount > 0) { - throw new Error('SDK bin mocking is turned on - this will affect/break the tests - so turn it off in triggerBinLookup.ts'); - } -}); - -// For individual test suites (perhaps being run in isolation) - provide a way to ensure SDK bin mocking is turned off -export const turnOffSDKMocking = ClientFunction(() => { - window.mockBinCount = 0; -}); diff --git a/packages/e2e/tests/_common/checkMocking.test.js b/packages/e2e/tests/_common/checkMocking.test.js deleted file mode 100644 index 5456e348d..000000000 --- a/packages/e2e/tests/_common/checkMocking.test.js +++ /dev/null @@ -1,14 +0,0 @@ -import { checkSDKMocking } from './cardMocks'; -import CardComponentPage from '../_models/CardComponent.page'; - -const cardPage = new CardComponentPage(); - -fixture`Test that bin mocking isn't turned on in the SDK`.page(cardPage.pageUrl); - -/** - * Check that bin mocking isn't turned on in the SDK - * - this is used for testing (in triggerBinLookup.ts) but if left on will break or skew the tests - */ -test('Check for SDK Bin mocking', async () => { - await checkSDKMocking(); -}); diff --git a/packages/e2e/tests/_models/Address.component.js b/packages/e2e/tests/_models/Address.component.js deleted file mode 100644 index 81ada81f5..000000000 --- a/packages/e2e/tests/_models/Address.component.js +++ /dev/null @@ -1,25 +0,0 @@ -import { t, ClientFunction, Selector } from 'testcafe'; - -export default class AddressComponent { - constructor(baseEl = '.adyen-checkout__fieldset') { - this.baseEl = Selector(baseEl); - - this.countrySelector = this.baseEl.find('.adyen-checkout__field--country .adyen-checkout__dropdown'); - - this.streetInput = this.baseEl.find('.adyen-checkout__input--street'); - - this.postalCodeLabel = this.baseEl.find('.adyen-checkout__field--postalCode .adyen-checkout__label__text'); - this.postalCodeInput = this.baseEl.find('.adyen-checkout__input--postalCode'); - this.postalCodeInputError = this.baseEl.find('.adyen-checkout__field--postalCode .adyen-checkout-contextual-text--error'); - } - - async fillPostalCode(value = '') { - await t.typeText(this.postalCodeInput, value); - } - - async selectCountry(value = '') { - await t.click(this.countrySelector); - const countryDropdownItem = this.countrySelector.find('.adyen-checkout__dropdown__element').withText(value); - await t.click(countryDropdownItem); - } -} diff --git a/packages/e2e/tests/_models/BasePage.js b/packages/e2e/tests/_models/BasePage.js deleted file mode 100644 index a541d1cc6..000000000 --- a/packages/e2e/tests/_models/BasePage.js +++ /dev/null @@ -1,29 +0,0 @@ -import { BASE_URL } from '../pages'; -import { ClientFunction } from 'testcafe'; - -export default class BasePage { - constructor(url) { - this.pageUrl = `${BASE_URL}/${url}`; - } - - /** - * Client function that accesses properties on the window object - */ - getFromWindow = ClientFunction(path => { - const splitPath = path.split('.'); - const reducer = (xs, x) => (xs && xs[x] !== undefined ? xs[x] : undefined); - - return splitPath.reduce(reducer, window); - }); - - /** - * Hack to force testcafe to fire expected blur events as (securedFields) switch focus - */ - setForceClick = ClientFunction(val => { - window.testCafeForceClick = val; - }); - - decodeBase64 = ClientFunction(val => { - return window.atob(val); - }); -} diff --git a/packages/e2e/tests/_models/CardComponent.page.js b/packages/e2e/tests/_models/CardComponent.page.js deleted file mode 100644 index e8863ace4..000000000 --- a/packages/e2e/tests/_models/CardComponent.page.js +++ /dev/null @@ -1,161 +0,0 @@ -import { ClientFunction, Selector } from 'testcafe'; -import BasePage from './BasePage'; -import { getIframeSelector } from '../utils/commonUtils'; -import cu from '../cards/utils/cardUtils'; -import kcp from '../cards/utils/kcpUtils'; - -/** - * The Page Model Pattern is a test automation pattern that allows you to create an - * abstraction of the tested page, and use it in test code to refer to page elements - */ -export default class CardPage extends BasePage { - /** - * @type {InstallmentsComponent} - */ - installments = null; - - constructor(baseEl = '.card-field', internalComponents = {}, url = 'cards') { - super(url); - - Object.assign(this, internalComponents); - - const BASE_EL = baseEl; - - /** - * CardNumber - */ - // Top level
- this.numHolder = Selector(`${BASE_EL} .adyen-checkout__field--cardNumber`); - // this.numHolderWithErrorCls = Selector(`${BASE_EL} .adyen-checkout__field--cardNumber.adyen-checkout__field--error`); - - this.numLabel = Selector(`${BASE_EL} .adyen-checkout__field--cardNumber .adyen-checkout__label`); - this.numLabelWithFocus = Selector(`${BASE_EL} .adyen-checkout__field--cardNumber .adyen-checkout__label--focused`); - // The that holds the label text (first child of the