From 6804b0a4c6c3ece514d881bab874a2d2b29a2882 Mon Sep 17 00:00:00 2001 From: Tamara Date: Wed, 25 Dec 2024 16:15:19 +0100 Subject: [PATCH 1/8] Add E2E tests in repository AETMTNR-1 --- .github/workflows/e2e-test.yml | 34 +- .../.env_example_shopware | 3 + .../adyen-integration-tools-tests/.gitignore | 7 + .../package.json | 23 ++ .../AdyenGivingComponents.js | 58 ++++ .../checkoutComponents/AmazonPayComponents.js | 13 + .../BancontactCardComponents.js | 42 +++ .../checkoutComponents/BoletoComponents.js | 17 + .../CreditCardComponents.js | 60 ++++ .../checkoutComponents/GiftcardComponents.js | 5 + .../checkoutComponents/IDealComponents.js | 25 ++ .../checkoutComponents/OneyComponents.js | 22 ++ .../checkoutComponents/PayPalComponents.js | 17 + .../SepaDirectDebitComponents.js | 20 ++ .../checkoutComponents/iDealComponents.js | 28 ++ .../common/redirect/AmazonPaymentPage.js | 56 ++++ .../common/redirect/ClearPayPaymentPage.js | 47 +++ .../common/redirect/GiftCardHPPage.js | 62 ++++ .../projects/common/redirect/GooglePayPage.js | 72 +++++ .../common/redirect/IdealIssuerPage.js | 36 +++ .../common/redirect/KlarnaPaymentPage.js | 54 ++++ .../common/redirect/OneyPaymentPage.js | 70 ++++ .../common/redirect/PayPalPaymentPage.js | 83 +++++ .../projects/common/redirect/QRPage.js | 18 ++ .../common/redirect/ThreeDS2PaymentPage.js | 32 ++ .../common/redirect/ThreeDSPaymentPage.js | 18 ++ .../projects/data/PaymentResources.js | 298 ++++++++++++++++++ .../shopware/helpers/PaymentHelper.js | 60 ++++ .../shopware/helpers/ScenarioHelper.js | 55 ++++ .../shopware/pageObjects/plugin/Base.page.js | 56 ++++ .../pageObjects/plugin/PaymentDetails.page.js | 156 +++++++++ .../pageObjects/plugin/ProductDetail.page.js | 36 +++ .../pageObjects/plugin/Result.page.js | 22 ++ .../pageObjects/plugin/SPRBase.page.js | 18 ++ .../plugin/ShippingDetails.page.js | 103 ++++++ .../projects/shopware/shopware.config.cjs | 147 +++++++++ .../shopware/shopwareCIContainer.config.cjs | 101 ++++++ .../shopware/tests/AdyenGiving.spec.js | 44 +++ .../tests/BancontactCardPayment.spec.js | 46 +++ .../shopware/tests/ClearPayPayment.spec.js | 36 +++ .../shopware/tests/CreditCardPayment.spec.js | 87 +++++ .../EditAddressChangePaymentList.spec.js | 33 ++ .../shopware/tests/IdealPayment.spec.js | 32 ++ .../shopware/tests/KlarnaPayment.spec.js | 77 +++++ .../shopware/tests/MultiBancoPayment.spec.js | 31 ++ .../shopware/tests/OneyPayment.spec.js | 34 ++ .../shopware/tests/PayPalPayment.spec.js | 49 +++ .../tests/SepaDirectDebitPayment.spec.js | 35 ++ .../tests/loggedIn/StoredCardPayment.spec.js | 58 ++++ .../renovate.json | 6 + .github/workflows/scripts/e2e.sh | 6 +- .../templates/docker-compose.playwright.yml | 6 +- .gitignore | 4 +- 53 files changed, 2543 insertions(+), 15 deletions(-) create mode 100755 .github/workflows/e2e/adyen-integration-tools-tests/.env_example_shopware create mode 100755 .github/workflows/e2e/adyen-integration-tools-tests/.gitignore create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/package.json create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/AdyenGivingComponents.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/AmazonPayComponents.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/BancontactCardComponents.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/BoletoComponents.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/CreditCardComponents.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/GiftcardComponents.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/IDealComponents.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/OneyComponents.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/PayPalComponents.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/SepaDirectDebitComponents.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/iDealComponents.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/AmazonPaymentPage.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/ClearPayPaymentPage.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/GiftCardHPPage.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/GooglePayPage.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/IdealIssuerPage.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/KlarnaPaymentPage.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/OneyPaymentPage.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/PayPalPaymentPage.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/QRPage.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/ThreeDS2PaymentPage.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/ThreeDSPaymentPage.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/data/PaymentResources.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/helpers/PaymentHelper.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/helpers/ScenarioHelper.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/pageObjects/plugin/Base.page.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/pageObjects/plugin/PaymentDetails.page.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/pageObjects/plugin/ProductDetail.page.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/pageObjects/plugin/Result.page.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/pageObjects/plugin/SPRBase.page.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/pageObjects/plugin/ShippingDetails.page.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/shopware.config.cjs create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/shopwareCIContainer.config.cjs create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/AdyenGiving.spec.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/BancontactCardPayment.spec.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/ClearPayPayment.spec.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/CreditCardPayment.spec.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/EditAddressChangePaymentList.spec.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/IdealPayment.spec.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/KlarnaPayment.spec.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/MultiBancoPayment.spec.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/OneyPayment.spec.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/PayPalPayment.spec.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/SepaDirectDebitPayment.spec.js create mode 100644 .github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/loggedIn/StoredCardPayment.spec.js create mode 100755 .github/workflows/e2e/adyen-integration-tools-tests/renovate.json diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index a81366c7..37ce2b2b 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -1,7 +1,8 @@ + name: E2E Test run-name: Headless E2E tests for Adyen Shopware Plugin -on: +on: pull_request: workflow_dispatch: @@ -14,8 +15,7 @@ jobs: timeout-minutes: 20 strategy: fail-fast: false - # if: ${{ github.actor != 'renovate[bot]' || github.actor != 'lgtm-com[bot]' }} - if: false + if: ${{ github.actor != 'renovate[bot]' && github.actor != 'lgtm-com[bot]' }} # Prevent bots from initiating E2E pipeline steps: - name: Clone Code @@ -32,14 +32,15 @@ jobs: docker exec shopware6 bash -c "sudo mysql -u root -proot shopware -e \"UPDATE sales_channel_domain SET url='https://local.shopware.shop' WHERE url NOT LIKE 'default.%';\"" docker exec shopware6 bash -c \ "sudo mysql -u root -proot shopware -e \"SELECT @RULE_ID := id FROM rule WHERE name = 'All customers'; UPDATE shipping_method SET availability_rule_id = @RULE_ID;\"" - + docker exec shopware6 bash -c "sudo mysql -u root -proot shopware -e \"UPDATE system_config SET configuration_value='{\"enabled\": false}' WHERE configuration_key='core.basicInformation.cookieConsent';\"" + docker network create localnetwork docker network connect --alias local.shopware.shop localnetwork shopware6 - name: Install/Configure Plugin run: | docker exec shopware6 bash -c "composer config --json repositories.local '{\"type\": \"path\", \"url\": \"/data/extensions/workdir\", \"options\": { \"symlink\": false } }'" - docker exec shopware6 bash -c 'composer require adyen/adyen-shopware6:*' + docker exec shopware6 bash -c 'composer require adyen/adyen-shopware6:*@RC' docker exec shopware6 bash -c 'php bin/console plugin:refresh' docker exec shopware6 bash -c 'php bin/console plugin:install AdyenPaymentShopware6 --activate' docker exec shopware6 bash -c 'php bin/console cache:clear' @@ -55,7 +56,6 @@ jobs: - name: Run E2E Tests run: docker compose -f .github/workflows/templates/docker-compose.playwright.yml run --rm playwright /e2e.sh env: - INTEGRATION_TESTS_BRANCH: develop SHOPWARE_BASE_URL: ${{secrets.SHOPWARE_BASE_URL}} PAYPAL_USERNAME: ${{secrets.PLAYWRIGHT_PAYPAL_USERNAME}} PAYPAL_PASSWORD: ${{secrets.PLAYWRIGHT_PAYPAL_PASSWORD}} @@ -66,3 +66,25 @@ jobs: with: name: html-report path: test-report + + - name: Copy Adyen api logs from container if available + if: always() + run: | + # Create a local directory for logs + mkdir -p logs + + # Check if the log file exists in the container + if docker exec shopware6 test -f /var/www/html/var/log/adyen/api.log; then + echo "Log file exists. Copying..." + docker cp shopware6:/var/www/html/var/log/adyen/api.log logs + else + echo "Log file does not exist. Skipping copy." + fi + shell: bash + + - name: Upload Adyen api Logs + if: always() + uses: actions/upload-artifact@v3 + with: + name: adyen-api-logs + path: logs/* \ No newline at end of file diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/.env_example_shopware b/.github/workflows/e2e/adyen-integration-tools-tests/.env_example_shopware new file mode 100755 index 00000000..e2a4c419 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/.env_example_shopware @@ -0,0 +1,3 @@ +SHOPWARE_BASE_URL="" +PAYPAL_USERNAME="" +PAYPAL_PASSWORD="" \ No newline at end of file diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/.gitignore b/.github/workflows/e2e/adyen-integration-tools-tests/.gitignore new file mode 100755 index 00000000..14ca7097 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +playwright-report/ +test-results.json +test-report/ +.DS_Store +.idea +.env diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/package.json b/.github/workflows/e2e/adyen-integration-tools-tests/package.json new file mode 100644 index 00000000..6227537e --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/package.json @@ -0,0 +1,23 @@ +{ + "name": "pluginstest", + "version": "1.0.0", + "description": "", + "main": "index.js", + "type": "module", + "scripts": { + "test:ci:shopware": "npx playwright test --config=projects/shopware/shopwareCIContainer.config.cjs", + "test:adyenlocal:shopware": "npx playwright test --workers=1 --headed --project=chromium --config=projects/shopware/shopware.config.cjs", + "test:adyenlocal:shopware:headless": "npx playwright test --workers=1 --project=chromium --config=projects/shopware/shopware.config.cjs", + "test:adyenlocal:shopware:parallel": "npx playwright test --headed --project=chromium --config=projects/shopware/shopware.config.cjs", + "test:adyenlocal:shopware:headless:parallel": "npx playwright test --project=chromium --config=projects/shopware/shopware.config.cjs" + }, + "keywords": [], + "author": "", + "license": "MIT", + "devDependencies": { + "@playwright/test": "^1.42.1" + }, + "dependencies": { + "dotenv": "^16.3.1" + } +} \ No newline at end of file diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/AdyenGivingComponents.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/AdyenGivingComponents.js new file mode 100644 index 00000000..dabe9711 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/AdyenGivingComponents.js @@ -0,0 +1,58 @@ +import { expect } from "@playwright/test"; +export class AdyenGivingComponents { + constructor(page) { + this.page = page; + + this.adyenGivingContainer = page.locator(".adyen-checkout__adyen-giving"); + this.adyenGivingActionsContainer = this.adyenGivingContainer.locator( + ".adyen-checkout__adyen-giving-actions" + ); + this.actionButtonsContainer = this.adyenGivingActionsContainer.locator( + ".adyen-checkout__amounts" + ); + + this.leastAmountButton = this.actionButtonsContainer + .locator(".adyen-checkout__button") + .nth(0); + this.midAmountButton = this.actionButtonsContainer + .locator(".adyen-checkout__button") + .nth(1); + this.mostAmountButton = this.actionButtonsContainer + .locator(".adyen-checkout__button") + .nth(2); + + this.donateButton = this.adyenGivingActionsContainer.locator( + ".adyen-checkout__button--donate" + ); + this.declinelButton = this.adyenGivingActionsContainer.locator( + ".adyen-checkout__button--decline" + ); + + this.DonationMessage = this.adyenGivingContainer.locator( + ".adyen-checkout__status__text" + ); + } + + async makeDonation(amount = "least") { + switch (amount) { + case "least": + await this.leastAmountButton.click(); + break; + case "mid": + await this.midAmountButton.click(); + break; + case "most": + await this.mostAmountButton.click(); + break; + } + await this.donateButton.click(); + } + + async declineDonation() { + await this.declinelButton.click(); + } + + async verifySuccessfulDonationMessage() { + await expect(this.DonationMessage).toHaveText("Thanks for your support!"); + } +} diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/AmazonPayComponents.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/AmazonPayComponents.js new file mode 100644 index 00000000..d60f61d3 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/AmazonPayComponents.js @@ -0,0 +1,13 @@ +export class AmazonPayComponents { + constructor(page) { + this.page = page; + + this.amazonPayContainer = page.locator("#amazonpayContainer"); + this.amazonPayButton = this.amazonPayContainer.getByLabel('Amazon Pay - Use your Amazon Pay Sandbox test account'); + + } + + async clickAmazonPayButton() { + await this.amazonPayButton.click(); + } +} diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/BancontactCardComponents.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/BancontactCardComponents.js new file mode 100644 index 00000000..2549e686 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/BancontactCardComponents.js @@ -0,0 +1,42 @@ +export class BancontactCardComponents { + constructor(page) { + this.page = page; + + this.cardNumberInput = page + .frameLocator( + ".adyen-checkout__card__cardNumber__input iframe" + ) + .locator(".input-field"); + + this.expDateInput = page + .frameLocator( + ".adyen-checkout__card__exp-date__input iframe" + ) + .locator(".input-field"); + + this.holderNameInput = page.locator( + "input.adyen-checkout__card__holderName__input" + ); + } + + async fillHolderName(holderName) { + await this.holderNameInput.click(); + await this.holderNameInput.fill(holderName); + } + async fillCardNumber(cardNumber) { + await this.cardNumberInput.click(); + await this.cardNumberInput.fill(cardNumber); + } + async fillExpDate(expDate) { + await this.expDateInput.click(); + await this.expDateInput.fill(expDate); + } + + async fillBancontacCardInfo( + cardNumber, + cardExpirationDate, + ) { + await this.fillCardNumber(cardNumber); + await this.fillExpDate(cardExpirationDate); + } +} diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/BoletoComponents.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/BoletoComponents.js new file mode 100644 index 00000000..7882597c --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/BoletoComponents.js @@ -0,0 +1,17 @@ +export class BoletoComponents { + constructor(page) { + this.page = page; + + this.socialSecurityNumberInput = page.locator( + "#adyen_boleto_social_security_number" + ); + this.firstNameInput = page.locator("#adyen_boleto_firstname"); + this.lastNameInput = page.locator("#adyen_boleto_lastname"); + } + + async fillBoletoDetails(socialSecurityNumber, firstName, lastName) { + await this.socialSecurityNumberInput.fill(socialSecurityNumber); + await this.firstNameInput.fill(firstName); + await this.lastNameInput.fill(lastName); + } +} diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/CreditCardComponents.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/CreditCardComponents.js new file mode 100644 index 00000000..c14cb53a --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/CreditCardComponents.js @@ -0,0 +1,60 @@ +export class CreditCardComponents { + constructor(page) { + this.page = page; + + this.holderNameInput = page.locator( + ".adyen-checkout__card__holderName input" + ); + + this.cardNumberInput = page + .frameLocator(".adyen-checkout__card__cardNumber__input iframe") + .locator(".input-field"); + + this.expDateInput = page + .frameLocator(".adyen-checkout__card__exp-date__input iframe") + .locator(".input-field"); + + this.cvcInput = page + .frameLocator(".adyen-checkout__card__cvc__input iframe") + .locator(".input-field"); + + this.typeDelay = 50; + } + + async fillHolderName(holderName) { + await this.holderNameInput.scrollIntoViewIfNeeded(); + await this.holderNameInput.click(); + await this.holderNameInput.type(holderName); + } + async fillCardNumber(cardNumber) { + await this.cardNumberInput.scrollIntoViewIfNeeded(); + await this.cardNumberInput.click(); + await this.cardNumberInput.type(cardNumber, { delay: this.typeDelay }); + } + async fillExpDate(expDate) { + await this.expDateInput.scrollIntoViewIfNeeded(); + await this.expDateInput.click(); + await this.expDateInput.type(expDate, { delay: this.typeDelay }); + } + async fillCVC(CVC) { + await this.cvcInput.scrollIntoViewIfNeeded(); + await this.cvcInput.click(); + await this.cvcInput.type(CVC, { delay: this.typeDelay }); + } + + async fillCreditCardInfo( + cardHolderName, + cardHolderLastName, + cardNumber, + cardExpirationDate, + cardCVC = undefined + ) { + await this.fillCardNumber(cardNumber); + await this.fillExpDate(cardExpirationDate); + if (cardCVC !== undefined ) { + await this.fillCVC(cardCVC); + } + await this.fillHolderName(cardHolderName); + await this.fillHolderName(` ${cardHolderLastName}`); + } +} diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/GiftcardComponents.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/GiftcardComponents.js new file mode 100644 index 00000000..e80c2b21 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/GiftcardComponents.js @@ -0,0 +1,5 @@ +export class GiftcardComponents { + constructor(page) { + // Abstract implementation is being extended in GiftcardComponentsMagento.js + } +} diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/IDealComponents.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/IDealComponents.js new file mode 100644 index 00000000..a25b25d4 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/IDealComponents.js @@ -0,0 +1,25 @@ +export class IDealComponents { + constructor(page) { + this.page = page; + + this.iDealDropDown = page.locator( + "#payment_form_adyen_hpp_ideal .adyen-checkout__dropdown__button" + ); + } + + iDealDropDownSelectorGenerator(issuerName) { + return this.page.locator( + `#payment_form_adyen_hpp_ideal .adyen-checkout__dropdown__list li [alt='${issuerName}']` + ); + } + + async selectIdealIssuer(issuerName) { + await this.iDealDropDown.click(); + await this.iDealDropDownSelectorGenerator(issuerName).click(); + } + + async selectRefusedIdealIssuer() { + await this.iDealDropDown.click(); + await this.iDealDropDownSelectorGenerator("Test Issuer Refused").click(); + } +} diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/OneyComponents.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/OneyComponents.js new file mode 100644 index 00000000..8d12dc4c --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/OneyComponents.js @@ -0,0 +1,22 @@ +export class OneyComponents { + constructor(page) { + this.activePaymentMethodSection = page; + + this.maleGenderRadioButton = this.activePaymentMethodSection + .locator(".adyen-checkout__radio_group__input-wrapper") + .nth(0); + this.birthdayInput = this.activePaymentMethodSection.locator( + ".adyen-checkout__input--dateOfBirth" + ); + this.telephoneNumberInput = this.activePaymentMethodSection.locator( + ".adyen-checkout__input--telephoneNumber"); + } + + async completeOneyForm(user) { + await this.maleGenderRadioButton.click(); + await this.birthdayInput.type(user.dateOfBirth); + + await this.telephoneNumberInput.fill(""); + await this.telephoneNumberInput.type(user.phoneNumber); + } +} diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/PayPalComponents.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/PayPalComponents.js new file mode 100644 index 00000000..de9a1518 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/PayPalComponents.js @@ -0,0 +1,17 @@ +export class PayPalComponents { + constructor(page) { + this.page = page; + + this.payPalButton = page + .frameLocator("iframe[title='PayPal']").last() + .locator(".paypal-button").first(); + } + + async proceedToPayPal() { + // The iframe which contains PayPal button may require extra time to load + await new Promise(r => setTimeout(r, 500)); + await this.payPalButton.scrollIntoViewIfNeeded(); + await this.payPalButton.hover(); + await this.payPalButton.click(); + } +} diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/SepaDirectDebitComponents.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/SepaDirectDebitComponents.js new file mode 100644 index 00000000..ca5bd06f --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/SepaDirectDebitComponents.js @@ -0,0 +1,20 @@ +export class SepaDirectDebitComponents { + constructor(page) { + this.page = page; + + this.accountHolderNameInput = this.page.locator( + "input[name='ownerName']" + ); + this.accountNumberInput = this.page.locator( + "input[name='ibanNumber']" + ); + } + + async fillSepaDirectDebitInfo(accountHolderName, accountNumber) { + await this.accountHolderNameInput.click(); + await this.accountHolderNameInput.fill(accountHolderName); + + await this.accountNumberInput.click(); + await this.accountNumberInput.fill(accountNumber); + } +} diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/iDealComponents.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/iDealComponents.js new file mode 100644 index 00000000..b2d917b9 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/checkoutComponents/iDealComponents.js @@ -0,0 +1,28 @@ +export class IDealComponents { + constructor(page) { + this.page = page; + + this.iDealDropDown = page.locator( + ".adyen-checkout__dropdown__button" + ); + } + + /** @deprecated on Ideal 2.0 */ + iDealDropDownSelectorGenerator(issuerName) { + return this.page.locator( + `.adyen-checkout__dropdown__list li [alt='${issuerName}']` + ); + } + + /** @deprecated on Ideal 2.0 */ + async selectIdealIssuer(issuerName) { + await this.iDealDropDown.click(); + await this.iDealDropDownSelectorGenerator(issuerName).click(); + } + + /** @deprecated on Ideal 2.0 */ + async selectRefusedIdealIssuer() { + await this.iDealDropDown.click(); + await this.iDealDropDownSelectorGenerator("Test Issuer Refused").click(); + } +} diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/AmazonPaymentPage.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/AmazonPaymentPage.js new file mode 100644 index 00000000..e7d20b79 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/AmazonPaymentPage.js @@ -0,0 +1,56 @@ +export class AmazonPayPaymentPage { + constructor(page) { + this.page = page; + + this.emailInput = this.page.locator("#ap_email"); + this.passwordInput = this.page.locator("#ap_password"); + this.loginButton = this.page.locator("#signInSubmit"); + this.payNowButton = this.page.locator('#continue-button'); + this.cancelPayment = this.page.locator('#return_back_to_merchant_link'); + + this.paymentMethodItem = this.page.locator(".buyer-list-item"); + + this.changePaymentButton = this.page.locator("#change-payment-button"); + this.confirmPaymentChangeButton = this.page.locator("#a-autoid-3"); + this.amazonCaptcha = this.page.locator('//img[contains(@alt,"captcha")]') + } + + async doLogin(amazonCredentials){ + await this.emailInput.click(); + await this.emailInput.type(amazonCredentials.username); + await this.passwordInput.click(); + await this.passwordInput.type(amazonCredentials.password); + await this.loginButton.click(); + + await this.page.waitForLoadState(); + } + + async selectPaymentMethod(testCase) { + await this.changePaymentButton.click(); + await this.page.waitForLoadState(); + + switch (testCase) { + case 'declined': + await this.paymentMethodItem.nth(5).click(); + break; + case '3ds2': + await this.paymentMethodItem.nth(4).click(); + break; + } + + await this.confirmPaymentChangeButton.click(); + await this.page.waitForLoadState(); + } + + async completePayment() { + await this.payNowButton.click(); + } + + async cancelTransaction() { + await this.cancelPayment.click(); + } + + async isCaptchaMounted() { + return !!(await this.amazonCaptcha.isVisible({timeout: 5000})); + } +} diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/ClearPayPaymentPage.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/ClearPayPaymentPage.js new file mode 100644 index 00000000..dbdbee04 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/ClearPayPaymentPage.js @@ -0,0 +1,47 @@ +import PaymentResources from "../../data/PaymentResources.js"; + +export class ClearPayPaymentPage { + constructor(page) { + this.page = page; + + this.numberEmailInput = page.locator("input[data-testid='login-identity-input']"); + this.loginButton = page.locator("button[data-testid='login-identity-button']"); + + this.passwordInput = page.locator("input[data-testid='login-password-input']"); + this.continueButton = page.locator("button[data-testid='login-password-button']"); + + this.confirmButton = page.locator("button[data-testid='summary-button']"); + + this.typeDelay = 50; + } + + async continueClearPayPayment() { + const italianUser = new PaymentResources().guestUser.clearPay.approved.it + + await this.page.waitForLoadState(); + + await this.numberEmailInput.waitFor({ + state: "visible", + timeout: 10000, + }); + await this.numberEmailInput.click(); + await this.numberEmailInput.fill(""); + await this.numberEmailInput.type(italianUser.phoneNumber, { delay: this.typeDelay }); + await this.loginButton.click(); + + await this.passwordInput.waitFor({ + state: "visible", + timeout: 10000, + }); + await this.passwordInput.click(); + await this.passwordInput.fill(""); + await this.passwordInput.type(italianUser.password, { delay:this.typeDelay }); + await this.continueButton.click(); + + await this.confirmButton.waitFor({ + state: "visible", + timeout: 10000, + }); + await this.confirmButton.click(); + } +} diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/GiftCardHPPage.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/GiftCardHPPage.js new file mode 100644 index 00000000..b4a88bd4 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/GiftCardHPPage.js @@ -0,0 +1,62 @@ +export class GiftCardHPPage { + constructor(page) { + this.page = page; + + this.cardHoldernameInput = page.locator( + "#genericgiftcard\\.cardHolderName" + ); + this.cardNumberInput = page.locator("#genericgiftcard\\.cardNumber"); + this.cardPinUnput = page.locator("#genericgiftcard\\.pin"); + + this.partialPaymentCheckBox = page.locator( + "#genericgiftcard\\.partialPayments" + ); + this.continueButton = page.locator("#mainSubmit[value='continue']"); + + this.previousButton = page.locator("#mainBack"); + + this.payButton = page.locator("#mainSubmit[value='pay']"); + + this.genericGiftCardButtonHPP = page.locator( + "input[value='Generic GiftCard']" + ); + this.idealButtonHPP = page.locator("input[value='iDEAL']"); + + this.idealIssuerButton = page + .locator("input.idealButton[name='idealIssuer']") + .first(); + this.iDealContinueButton = page.locator("input[value='Continue']"); + this.iDealCompleteButton = page.locator("button").first(); + } + + async fillGiftCardDetails( + cardHolderName, + cardNumber, + cardPin, + partialPayment = false + ) { + await this.cardHoldernameInput.waitFor({ + state: "visible", + timeout: 10000, + }) + await this.cardHoldernameInput.fill(cardHolderName); + await this.cardNumberInput.fill(cardNumber); + await this.cardPinUnput.fill(cardPin); + + if (partialPayment != false) { + this.partialPaymentCheckBox.click(); + } + } + + async clickContinue() { + await this.continueButton.click(); + } + + async clickPay() { + await this.payButton.click(); + } + + async clickPrevious() { + await this.previousButton.click(); + } +} diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/GooglePayPage.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/GooglePayPage.js new file mode 100644 index 00000000..69229488 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/GooglePayPage.js @@ -0,0 +1,72 @@ +import { expect } from "@playwright/test"; + +export class GooglePayPage { + constructor(page) { + this.page = page; + + this.loginHeader = page.locator("#headingText") + this.emailInput = page.locator("input[type='email']"); + this.nextButton = page.getByRole('button', { name: 'Next' }); + + this.passwordInput = page.locator("input[name='Passwd']"); + + this.paymentIframe = page.frameLocator("iframe[allow='camera']") + this.payButton = this.paymentIframe.locator(".jfk-button").first(); + } + + async assertNavigation(){ + await this.loginHeader.waitFor({ + state: "visible", + timeout: 10000, + }); + await expect(this.emailInput).toBeVisible(); + } + + async fillUsername(username) { + await this.loginHeader.waitFor({ + state: "visible", + timeout: 10000, + }); + await this.emailInput.click(); + await this.emailInput.type(username); + } + + async fillPassword(password) { + await this.passwordInput.waitFor({ + state: "visible", + timeout: 10000, + }); + await this.passwordInput.click(); + await this.passwordInput.type(password); + } + + async clickNext(){ + await this.nextButton.waitFor({ + state: "visible", + timeout: 10000, + }); + await this.nextButton.click(); + } + + async clickPay(){ + await this.payButton.waitFor({ + state: "visible", + timeout: 10000, + }); + await this.payButton.click(); + } + + async fillGoogleCredentials(username, password){ + await this.page.waitForLoadState(); + + await this.fillUsername(username); + await this.clickNext(); + + await this.fillPassword(password); + await this.clickNext(); + } + + async pay(){ + await this.clickPay(); + } + } diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/IdealIssuerPage.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/IdealIssuerPage.js new file mode 100644 index 00000000..82f9b3ce --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/IdealIssuerPage.js @@ -0,0 +1,36 @@ +export class IdealIssuerPage { + constructor(page, bankName) { + const PAYMENT_ACTION_BUTTON_TEST_ID = 'payment-action-button'; + const BANK_ITEM_TEST_ID_PREFIX = 'bank-item-'; + const bankSelector = "#" + BANK_ITEM_TEST_ID_PREFIX + bankName; + + this.iDealContinueButton = page.locator("button"); + this.selectYourBankButton = page.getByTestId(PAYMENT_ACTION_BUTTON_TEST_ID); + this.selectIssuerButton = page.locator(bankSelector); + + this.simulateSuccessButton = page.getByRole('button', {name: 'Success'}); + this.simulateFailureButton = page.getByRole('button', {name: 'Failure'}); + } + + /** @deprecated on Ideal 2.0 */ + async continuePayment() { + await this.iDealContinueButton.waitFor({ + state: "visible", + timeout: 7000, + }); + await this.iDealContinueButton.click(); + } + + async proceedWithSelectedBank(bankName) { + await this.selectYourBankButton.click(); + await this.selectIssuerButton.first().click(); + } + + async simulateSuccess() { + await this.simulateSuccessButton.click(); + } + + async simulateFailure() { + await this.simulateFailureButton.click(); + } +} diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/KlarnaPaymentPage.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/KlarnaPaymentPage.js new file mode 100644 index 00000000..da5cd6e6 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/KlarnaPaymentPage.js @@ -0,0 +1,54 @@ +export default class KlarnaPaymentPage { + constructor(page) { + this.page = page; + + this.phoneNumberVerificationDialog = page.getByTestId('kaf-root'); + this.genericInputField = page.getByTestId('kaf-field'); + this.genericButton = page.getByTestId('kaf-button'); + this.smsVerificationDialog = page.locator('#otp_field'); + this.closeButton = page.getByLabel('Close'); + this.cancelDialog = page.locator('#payment-cancel-dialog-express__container'); + this.confirmCancellationButton = page.getByRole('button', { name: 'Yes, cancel' }); + this.paymentTypeSelectButton = page.getByTestId('pick-plan'); + this.summaryPaymentPreview = page.getByTestId('summary') + this.confirmAndPayButton = page.getByTestId('confirm-and-pay'); + } + + async makeKlarnaPayment(phoneNumber, paynow = false){ + await this.waitForKlarnaLoad(); + await this.phoneNumberVerificationDialog.waitFor({state:'attached'}) + await this.genericInputField.click(); + await this.genericInputField.fill(phoneNumber); + await this.genericButton.click(); + await this.smsVerificationDialog.waitFor({state:'visible'}) + await this.genericInputField.click(); + await this.genericInputField.fill('111111'); + + if (paynow) { + await this.summaryPaymentPreview.waitFor({state:'attached'}) + await this.page.waitForLoadState("networkidle", { timeout: 10000 }); + + if (await this.paymentTypeSelectButton.isVisible()) { + await this.paymentTypeSelectButton.click(); + } + } + + await this.confirmAndPayButton.waitFor({state:'visible'}) + await this.confirmAndPayButton.click(); + } + + async cancelKlarnaPayment(){ + await this.waitForKlarnaLoad(); + await this.page.waitForLoadState("networkidle", { timeout: 10000 }); + await this.closeButton.click(); + await this.cancelDialog.waitFor({state:'visible'}) + await this.confirmCancellationButton.click(); + } + + async waitForKlarnaLoad() { + await this.page.waitForURL(/.*playground.klarna/, { + timeout:15000, + waitUntil: "load", + }) + } +} diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/OneyPaymentPage.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/OneyPaymentPage.js new file mode 100644 index 00000000..6450156e --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/OneyPaymentPage.js @@ -0,0 +1,70 @@ +import PaymentResources from "../../data/PaymentResources.js"; +export class OneyPaymentPage { + constructor(page) { + this.page = page; + + this.continueWithoutLoggingInButton = page.locator(".primary-button") + + this.genderSelector = page.locator(".civility-selectable").nth(2); + this.birthDateInput = page.locator("input#birthDate"); + + this.birthPlaceInput = page.locator("input#birthCity"); + this.birthPlaceList = page.locator("#cdk-overlay-1 mat-option").first(); + + this.birthDepartmentSelector = page.locator("#birthDepartment"); + + this.cardHolderNameInput = page.locator("#cardOwner"); + this.cardNumberInput = page.locator("#cardNumber"); + this.cardExpDateInput = page.locator("#cardExpirationDate"); + this.cardCvvInput = page.locator("#cvv"); + + this.termsAndConditionsCheckBox = page.locator( + "label[for='generalTermsAndConditions']" + ); + this.completePaymentButton = page.locator("button[type='submit']"); + + this.oneyAnimationLayer = page.locator(".loader-message"); + + this.returnToMerchantSiteButton = page.locator("#successRedirectLink"); + } + + async continueOneyPayment() { + await this.page.waitForURL(/.*oney*/, { timeout: 15000 }); + /* + Oney's sandboxes are ever inconsistent and flaky, checking only + redirection until further notice + + const paymentResources = new PaymentResources(); + const user = paymentResources.guestUser.oney.approved.fr; + + await this.page.waitForLoadState("networkidle", { timeout: 10000 }); + + await this.continueWithoutLoggingInButton.click(); + await this.genderSelector.click(); + await this.birthPlaceInput.click(); + await this.birthPlaceInput.type(user.city); + await this.birthPlaceList.click(); + + await this.cardHolderNameInput.type(`${user.firstName} ${user.lastName}`); + + await this.cardNumberInput.type(paymentResources.oneyCard); + await this.cardExpDateInput.type(paymentResources.expDate); + await this.cardCvvInput.type(paymentResources.cvc); + + await this.termsAndConditionsCheckBox.click(); + await this.completePaymentButton.click(); + + await this.waitForOneyAnimation(); + await this.waitForOneySuccessPageLoad(); + + await this.returnToMerchantSiteButton.click(); */ + } + + async waitForOneySuccessPageLoad() { + await this.page.waitForURL(/.*success*/, { timeout: 15000 }); + } + + async waitForOneyAnimation() { + await this.oneyAnimationLayer.waitFor({ state: "attached", timeout: 5000 }); + } +} diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/PayPalPaymentPage.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/PayPalPaymentPage.js new file mode 100644 index 00000000..a6647bf6 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/PayPalPaymentPage.js @@ -0,0 +1,83 @@ +export class PayPalPaymentPage { + constructor(page) { + this.page = page; + + this.changeEmailAddressButton = page.locator(".notYouLink"); + this.emailInput = page.locator("#email"); + this.nextButton = page.locator("#btnNext"); + this.passwordInput = page.locator("#password"); + this.loginButton = page.locator("#btnLogin"); + this.agreeAndPayNowButton = page.locator("#payment-submit-btn"); + this.cancelButton = page.locator("#cancelLink"); + this.changeShippingAddressButton = page.locator("#change-shipping"); + this.shippingAddressDropdown = page.locator("#shippingDropdown"); + this.shippingMethodsDropdown = page.locator("#shippingMethodsDropdown"); + + this.loggingInAnimation = page.locator("#app-loader"); + this.cookiesWrapper = page.locator("#ccpaCookieBanner"); + this.cookiesDeclineButton = this.cookiesWrapper.getByRole('button', { name: 'decline' }); + } + + async loginToPayPalAndHandleCookies(username, password) { + await this.waitForPopupLoad(this.page); + + await this.emailInput.fill(username); + await this.nextButton.click(); + await this.passwordInput.fill(password); + await this.loginButton.click(); + + await this.waitForAnimation(); + + await this.declineCookies(); + } + + async waitForAnimation() { + await this.loggingInAnimation.waitFor({state: "visible", timeout: 10000}); + await this.loggingInAnimation.waitFor({state: "hidden", timeout: 15000}); + } + + async agreeAndPay() { + await this.agreeAndPayNowButton.click(); + } + + async doLoginMakePayPalPayment(username, password) { + await this.loginToPayPalAndHandleCookies(username, password); + + await this.agreeAndPay(); + } + + async declineCookies() { + if (await this.cookiesDeclineButton.isVisible()) { + await this.cookiesDeclineButton.click(); + } + } + + async cancelAndGoToStore() { + await this.waitForPopupLoad(this.page); + await this.cancelButton.click(); + } + + async waitForPopupLoad(page) { + await page.waitForURL(/.*sandbox.paypal.com*/, + { + timeout: 10000, + waitUntil:"load" + }); + } + + async changeShippingAddress() { + await this.changeShippingAddressButton.click(); + + const newShippingAddressValue = await this.shippingAddressDropdown.first() + .getByRole("option") + .nth(1) + .getAttribute("value"); + await this.shippingAddressDropdown.first().selectOption(newShippingAddressValue); + + const newShippingMethodValue = await this.shippingMethodsDropdown.first() + .getByRole("option") + .nth(1) + .getAttribute("value"); + await this.shippingMethodsDropdown.first().selectOption(newShippingMethodValue); + } +} diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/QRPage.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/QRPage.js new file mode 100644 index 00000000..dd70e8ef --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/QRPage.js @@ -0,0 +1,18 @@ +import { expect } from "@playwright/test"; + +export class QRPage { + constructor(page) { + this.page = page; + this.QRCodeWrapper = page.locator(".adyen-checkout__qr-loader"); + this.QRCode = this.QRCodeWrapper.locator("img[alt='Scan QR code']"); + } + + async verifySuccessfulQRCode() { + await this.page.waitForLoadState( + "load", + { timeout: 10000 } + ); + + await expect(this.QRCode).toBeVisible({ timeout: 10000 }); + } +} diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/ThreeDS2PaymentPage.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/ThreeDS2PaymentPage.js new file mode 100644 index 00000000..c44c5115 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/ThreeDS2PaymentPage.js @@ -0,0 +1,32 @@ +export class ThreeDS2PaymentPage { + constructor(page) { + this.page = page; + this.threeDS2Iframe = page.frameLocator("iframe[name='threeDSIframe']"); + this.threeDS2PasswordInput = this.threeDS2Iframe.locator( + "input[name='answer']" + ); + this.threeDS2SubmitButton = this.threeDS2Iframe.locator( + "button[type='submit']" + ); + this.threeDS2CancelButton = this.threeDS2Iframe.locator( + "#buttonCancel" + ); + } + + async validate3DS2(answer) { + await this.fillThreeDS2PasswordAndSubmit(answer); + } + + async fillThreeDS2PasswordAndSubmit(answer) { + await this.threeDS2PasswordInput.waitFor({ state: "visible" }); + await this.threeDS2PasswordInput.click(); + await this.threeDS2PasswordInput.type(answer); + await this.threeDS2SubmitButton.click(); + } + + async clickCancel() { + await this.threeDS2CancelButton.waitFor({ state: "visible" }); + await this.threeDS2CancelButton.click(); + } + +} diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/ThreeDSPaymentPage.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/ThreeDSPaymentPage.js new file mode 100644 index 00000000..70c0952f --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/common/redirect/ThreeDSPaymentPage.js @@ -0,0 +1,18 @@ +export class ThreeDSPaymentPage { + constructor(page) { + this.page = page; + this.threeDSUsernameInput = page.locator("#username"); + this.threeDSPasswordInput = page.locator("#password"); + this.threeDSSubmit = page.locator('input[type="submit"]'); + } + + async validate3DS(username, password) { + await this.fillThreeDSCredentialsAndSubmit(username, password); + } + + async fillThreeDSCredentialsAndSubmit(username, password) { + await this.threeDSUsernameInput.type(username); + await this.threeDSPasswordInput.type(password); + await this.threeDSSubmit.click(); + } +} diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/data/PaymentResources.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/data/PaymentResources.js new file mode 100644 index 00000000..16d9ee6b --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/data/PaymentResources.js @@ -0,0 +1,298 @@ +export default class PaymentResources { + masterCardWithout3D = "5555341244441115"; + masterCard3DS2 = "5454545454545454"; + visa3DS1 = "4212345678901237"; + oneyCard = "4970101558744789"; + visa = "4111111111111111"; + + klarnaVerificationCode = "123456"; + + payPalUserName = process.env.PAYPAL_USERNAME; + payPalPassword = process.env.PAYPAL_PASSWORD; + + klarnaApprovedNLDateOfBirth = "10-07-1970"; + + afterPayApprovedNLGender = "M"; + afterPayApprovedNLDateOfBirth = "1990-01-01"; + afterPayApprovedNLPhoneNumber = "0644444444"; + afterPayApprovedNLHouseNumber = "80"; + afterPayDeniedNLHouseNumber = "999"; + + wrongExpDate = "1130"; + expDate = "0330"; + cvc = "737"; + cardTypes = { credit: "Credit Card", debit: "Debit Card" }; + + threeDSCorrectUser = "user"; + threeDSCorrectPassword = "password"; + threeDSWrongPassword = "wrong answer"; + + magentoAdminUser = { + username: process.env.MAGENTO_ADMIN_USERNAME, + password: process.env.MAGENTO_ADMIN_PASSWORD, + }; + + webhookCredentials = { + webhookUsername : process.env.WEBHOOK_USERNAME, + webhookPassword : process.env.WEBHOOK_PASSWORD, + } + + amazonCredentials = { + username: process.env.AMAZON_USERNAME, + password: process.env.AMAZON_PASSWORD, + } + + googleCredentials = { + username: process.env.GOOGLE_USERNAME, + password: process.env.GOOGLE_PASSWORD, + } + + apiCredentials = { + merchantAccount: process.env.ADYEN_MERCHANT, + clientKey: process.env.ADYEN_CLIENT_KEY, + apiKey: process.env.ADYEN_API_KEY, + }; + + bcmc = { + be: { + cardNumber: "6703444444444449", + expDate: "0330", + user: "user", + password: "password", + wrongUser: "wronguser", + wrongPassword: "wrongpassword", + }, + }; + + sepaDirectDebit = { + nl: { + accountName: "A. Klaassen", + iban: "NL13TEST0123456789", + }, + }; + + ideal2 = { + issuer: "TESTNL2A" + } + + giftCard = { + /* The test cards are broken as of 18th Jul 2022, + so using the VVVgiftcard codes instead, after discussing + with support + The amounts are respectively 560EUR and 60EUR for EUR50 card + and EUR20 cards*/ + + EUR50: { + cardHolderName: "BALANCE EUR 5000", + cardNumber: "6064364295385017424", + cardPIN: "73737", + cardBrand: "vvv" + }, + + EUR20: { + cardHolderName: "BALANCE EUR 2000", + cardNumber: "6064364295385017427", + cardPIN: "73737", + cardBrand: "vvv" + }, + + EUR50SVS: { + cardHolderName: "SVS TEST", + cardNumber: "6006490000000000", + cardPIN: "123", + cardBrand: "svs" + } + }; + + sampleRegisteredUser = { + email: "roni_cost@example.com", + password: "roni_cost3@example.com", + }; + + guestUser = { + regular: { + email: "guest@adyen.com", + firstName: "Guest", + lastName: "Test", + street: "Guest street", + houseNumber: "1", + city: "London", + postCode: "WC2N 5DU", + countryCode: "GB", + countryName: "United Kingdom", + phoneNumber: "06123456789", + stateCode: "NH", + password:"Ab223344!" + }, + dutch: { + email: "guest@adyen.com", + firstName: "Guest", + lastName: "Test", + street: "Simon Carmiggeltstraat 6-50", + houseNumber: "", + city: "Amsterdam", + postCode: "1011 DJ", + countryCode: "NL", + countryName: "Netherlands", + phoneNumber: "06123456789", + stateCode: "Noord-Holland", + }, + brazilian: { + email: "guest@adyen.com", + firstName: "Guest", + lastName: "Test", + street: "Guest street", + houseNumber: "1", + city: "São Paulo", + postCode: "12345-123", + countryCode: "BR", + countryName: "Brazil", + phoneNumber: "06123456789", + stateCode: "SP", + stateOrProvince: "São Paulo", + }, + portuguese: { + email: "guest@adyen.com", + firstName: "Portuguese", + lastName: "Test", + street: "Guest street", + houseNumber: "1", + city: "Lisbon", + postCode: "1234-123", + countryCode: "PT", + countryName: "Portugal", + stateOrProvince: "Aveiro", + phoneNumber: "06123456789", + }, + belgian: { + email: "guest@adyen.com", + firstName: "Belgian", + lastName: "Test", + street: "Guest street", + houseNumber: "1", + city: "Brussels", + postCode: "1000", + countryCode: "BE", + countryName: "Belgium", + phoneNumber: "+32456555720", + }, + klarna: { + approved: { + nl: { + email: "guest@adyen.com", + firstName: "Testperson-nl", + lastName: "Approved", + street: "Neherkade", + houseNumber: "1 XI", + city: "Gravenhage", + postCode: "2521VA", + countryCode: "NL", + countryName: "Netherlands", + stateCode: "NH", + phoneNumber: "0612345678", + dateOfBirth: "10071970", + gender: "M", + }, + }, + }, + boleto: { + email: "guest@adyen.com", + firstName: "Alex", + lastName: "De Souza", + street: "Guest street", + houseNumber: "1", + city: "São Paulo", + postCode: "12345-123", + countryCode: "BR", + countryName: "Brazil", + phoneNumber: "06123456789", + stateCode: "SP", + stateOrProvince: "São Paulo", + socialSecurityNumber: "56861752509", + }, + oney: { + approved: { + fr: { + email: "utilisateurinvite@adyen.fr", + firstName: "User", + lastName: "Test", + street: "Place Charles de Gaulle", + houseNumber: "1", + city: "Paris", + postCode: "75008", + countryCode: "FR", + countryName: "France", + phoneNumber: "+33700555497", + dateOfBirth: "07071970", + gender: "M", + }, + }, + }, + afterPay: { + approved: { + nl: { + email: "guest@adyen.com", + firstName: "Test", + lastName: "Acceptatie", + address: "Hoofdstraat", + houseNumber: "999", + city: "Heerenveen", + postCode: "8441ER", + phoneNumber: "0612345678", + dateOfBirth: "1990-01-01", + gender: "M", + countryCode: "NL", + }, + }, + denied: { + nl: { + email: "guest@adyen.com", + firstName: "Test T", + lastName: "Afwijzing", + address: "Hoofdstraat", + houseNumber: "999", + city: "Heerenveen", + postCode: "9999XX", + phoneNumber: "0612345678", + dateOfBirth: "1990-01-01", + gender: "M", + countryCode: "NL", + }, + }, + }, + clearPay: { + approved: { + it: { + email: "guestitalian@adyen.com", + firstName: "Test", + lastName: "Accepted", + street: "Vicolo Bianchetti", + houseNumber: "8", + city: "Bologna", + postCode: "40125", + phoneNumber: "+393777777781", + dateOfBirth: "1990-01-01", + gender: "M", + stateOrProvince: "Bologna", + countryCode: "IT", + countryName: "Italy", + password: "Abc1234567!" + }, + }, + }, + iDeal: { + nl: { + email: "guest@adyen.com", + firstName: "Test", + lastName: "Acceptatie", + address: "Hoofdstraat 80", + city: "Heerenveen", + countryName: "Netherlands", + postCode: "8441ER", + phoneNumber: "0612345678", + dateOfBirth: "1990-01-01", + gender: "M", + }, + } + }; +} diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/helpers/PaymentHelper.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/helpers/PaymentHelper.js new file mode 100644 index 00000000..1e550d1e --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/helpers/PaymentHelper.js @@ -0,0 +1,60 @@ +import { IdealIssuerPage } from "../../common/redirect/IdealIssuerPage.js"; +import { PaymentDetailsPage } from "../pageObjects/plugin/PaymentDetails.page.js"; + + +export async function makeCreditCardPayment( + page, + user, + creditCardNumber, + expDate, + cvc, + saveCard = false +) { + const paymentDetailPage = new PaymentDetailsPage(page); + const creditCardSection = await paymentDetailPage.selectCreditCard(); + await creditCardSection.fillCreditCardInfo( + user.firstName, + user.lastName, + creditCardNumber, + expDate, + cvc + ); + if (saveCard) { + await page.locator("text=Save for my next payment").click(); + } + await new PaymentDetailsPage(page).submitOrder(); +} + +/** @deprecated on Ideal 2.0 use makeIDeal2Payment() instead */ +export async function makeIDealPayment(page, issuerName) { + const paymentDetailPage = new PaymentDetailsPage(page); + const idealPaymentSection = await paymentDetailPage.selectIdeal(); + + await idealPaymentSection.selectIdealIssuer(issuerName); + await paymentDetailPage.scrollToCheckoutSummary(); + await paymentDetailPage.submitOrder(); + + await page.waitForNavigation(); + await new IdealIssuerPage(page).continuePayment(); +} + +export async function makeIDeal2Payment(page, bankName, success = true) { + const paymentDetailPage = new PaymentDetailsPage(page); + await paymentDetailPage.selectIdeal(); + + await paymentDetailPage.scrollToCheckoutSummary(); + await paymentDetailPage.submitOrder(); + await page.waitForNavigation(); + + const idealIssuerPage = new IdealIssuerPage(page, bankName); + + await idealIssuerPage.proceedWithSelectedBank(); + + if (success) { + await idealIssuerPage.simulateSuccess(); + } else { + await idealIssuerPage.simulateFailure(); + } + + await page.waitForLoadState("load", { timeout: 10000 }); +} \ No newline at end of file diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/helpers/ScenarioHelper.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/helpers/ScenarioHelper.js new file mode 100644 index 00000000..22c09006 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/helpers/ScenarioHelper.js @@ -0,0 +1,55 @@ +import { expect } from "@playwright/test"; +import { PaymentDetailsPage } from "../pageObjects/plugin/PaymentDetails.page.js"; +import { ProductDetailPage } from "../pageObjects/plugin/ProductDetail.page.js"; +import { ResultPage } from "../pageObjects/plugin/Result.page.js"; +import { ShippingDetailsPage } from "../pageObjects/plugin/ShippingDetails.page.js"; + +export async function goToShippingWithFullCart(page, quantity) { + const productDetailPage = new ProductDetailPage(page); + + await productDetailPage.addItemToCart("Main-product-free-shipping-with-highlighting/SWDEMO10006", + quantity); + expect.soft(await productDetailPage.alertMessage.textContent()).toContain("added to your shopping cart"); + await productDetailPage.clickProceedToCheckout(); +} + +export async function proceedToPaymentAs(page, user, saveUser = false) { + const shippingDetailsPage = new ShippingDetailsPage(page); + await shippingDetailsPage.fillShippingDetails(user, saveUser); + await shippingDetailsPage.clickContinue(); +} + +export async function doPrePaymentChecks(page, acceptTerms = true) { + const paymentDetailsPage = new PaymentDetailsPage(page); + await page.waitForLoadState("domcontentloaded", { timeout: 10000 }); + if(acceptTerms) { + await paymentDetailsPage.acceptTermsAndConditions(); + } + await paymentDetailsPage.loadAllPaymentDetails(); +} + +export async function verifySuccessfulPayment(page, redirect = true, timeout) { + const successfulResultPage = new ResultPage(page); + if (redirect) { + await successfulResultPage.waitForRedirection({ timeout:timeout }); + } + expect(await successfulResultPage.titleText()).toContain( + "Thank you for your order with" + ); +} + +export async function verifyFailedPayment(page, redirect = true) { + const failedResultPage = new PaymentDetailsPage(page); + if (redirect) { + await failedResultPage.waitForRedirection(); + } + expect(await failedResultPage.errorMessage).toContain( + "please change the payment method or try again" + ); +} + +export async function verifyVoucherCouponGeneration(page){ + const successfulResultPage = new ResultPage(page); + await expect(successfulResultPage.voucherCodeContainer).toBeVisible(); +} + diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/pageObjects/plugin/Base.page.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/pageObjects/plugin/Base.page.js new file mode 100644 index 00000000..97a2743e --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/pageObjects/plugin/Base.page.js @@ -0,0 +1,56 @@ +export class BasePage { + constructor(page) { + this.page = page; + + // Header section + this.header = page.locator(".header-main"); + this.headerLogo = this.header.locator(".header-logo-main"); + + this.cartButton = this.header.locator("a[aria-label='Shopping cart']"); + + this.currencyDropdownButton = this.header.locator(".currenciesDropdown-top-bar"); + this.currencylist = page.locator("div[aria-labelledby='currenciesDropdown-top-bar']"); + + // Shopping Cart Sidebar + this.sideBarContainer = page.locator(".cart-offcanvas"); + + this.continueShoppingButton = this.sideBarContainer.locator(".offcanvas-close"); + this.alertMessage = this.sideBarContainer.locator(".alert-content").first(); + this.proceedToCheckoutButton = this.sideBarContainer.getByRole('link', { name: 'Go to checkout' }); + + this.firstProductContainer = this.sideBarContainer.locator(".cart-item-product").first(); + this.deleteProductButton = this.firstProductContainer.locator(".cart-item-remove-button"); + this.sidebarProductQuantityDropdown = this.firstProductContainer.locator("select[name='quantity']"); + + } + + // General actions + async selectCurrency(currency) { + await this.currencyDropdownButton.click(); + await this.currencylist.locator(`div[title='${currency}'] label`).click(); + } + + + // Sidebar actions + async triggerShoppingCartSideBar() { + await this.cartButton.click(); + } + + async changeSidebarProductQuantity(quantity) { + await this.sidebarProductQuantityDropdown.selectOption(`${quantity}`); + } + + async closeShoppingCartSideBar() { + await this.continueShoppingButton.click(); + } + + async clickProceedToCheckout() { + await this.proceedToCheckoutButton.click(); + } + + async getSidebarAlertMessage() { + return await this.alertMessage.textContent(); + } + + +} \ No newline at end of file diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/pageObjects/plugin/PaymentDetails.page.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/pageObjects/plugin/PaymentDetails.page.js new file mode 100644 index 00000000..e1424ba9 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/pageObjects/plugin/PaymentDetails.page.js @@ -0,0 +1,156 @@ +import { BancontactCardComponents } from "../../../common/checkoutComponents/BancontactCardComponents.js"; +import { CreditCardComponents } from "../../../common/checkoutComponents/CreditCardComponents.js"; +import { IDealComponents } from "../../../common/checkoutComponents/iDealComponents.js"; +import { OneyComponents } from "../../../common/checkoutComponents/OneyComponents.js"; +import { PayPalComponents } from "../../../common/checkoutComponents/PayPalComponents.js"; +import { SepaDirectDebitComponents } from "../../../common/checkoutComponents/SepaDirectDebitComponents.js"; +import { SPRBasePage } from "./SPRBase.page.js"; + +export class PaymentDetailsPage extends SPRBasePage { + constructor(page) { + super(page) + this.page = page; + + // Terms and conditions Checkbox + this.termsAndConditionsCheckBox = page.locator("label[for='tos']"); + + // Show More button + this.showMoreButton = page.locator + ("//span[@class='confirm-checkout-collapse-trigger-label' and contains(text(),'Show more')]"); + + // Collapsed payment methods section + this.collapsedPaymentMethods = page.locator(".collapse.show"); + + // Payment Method Specifics + this.paymentDetailsList = page.locator("#changePaymentForm"); + this.cardSelector = this.paymentDetailsList.locator("img[alt='Cards']"); + this.payPalSelector = this.paymentDetailsList.locator("img[alt='PayPal']"); + this.idealWrapper = this.paymentDetailsList.locator("#adyen-payment-checkout-mask"); + this.idealSelector = this.paymentDetailsList.locator("img[alt='iDeal']"); + this.clearPaySelector = this.paymentDetailsList.locator("img[alt='Clearpay']"); + this.klarnaPayNowSelector = this.paymentDetailsList.locator("img[alt='Klarna Pay Now']"); + this.klarnaPayLaterSelector = this.paymentDetailsList.locator("img[alt='Klarna Pay Later']"); + this.klarnaPayAccountSelector = this.paymentDetailsList.locator("img[alt='Klarna Account']"); + this.sepaDirectDebitWrapper = this.paymentDetailsList.locator(".adyen-checkout__fieldset--iban-input"); + this.sepaDirectDebitSelector = this.paymentDetailsList.locator("img[alt='SEPA direct debit']"); + this.multiBancoSelector = this.paymentDetailsList.locator("img[alt='Multibanco']"); + this.oneySelector = this.paymentDetailsList.locator("img[alt='Oney 3x']"); + this.oneyWrapper = this.oneySelector.locator(".."); + this.bancontactCardSelector = this.paymentDetailsList.locator("img[title='Bancontact card']"); + this.bancontactCardWrapper = this.bancontactCardSelector.locator(".."); + this.storedCardSelector = this.paymentDetailsList.locator("img[alt='Stored Payment Methods']"); + this.storedCardWrapper = this.storedCardSelector.locator(".."); + + // Checkout Summary + this.checkoutSummaryContainer = page.locator(".checkout-aside-container"); + + // Submit Order button + this.submitOrderButton = page.locator("#confirmFormSubmit"); + + // Error message + this.errorMessageContainer = page.locator(".alert-content"); + + } + + // Redirect in case of an error + + async waitForRedirection() { + + await this.page.waitForNavigation({ + url: /ERROR/, + timeout: 15000, + }); + } + + get errorMessage() { + return this.errorMessageContainer.innerText(); + } + + // General actions + + async acceptTermsAndConditions() { + await this.termsAndConditionsCheckBox.click({ position: { x: 1, y: 1 } }); + } + + async submitOrder() { + await this.submitOrderButton.click(); + } + + async loadAllPaymentDetails() { + if (await this.showMoreButton.isVisible()) { + await this.showMoreButton.click(); + await this.collapsedPaymentMethods.waitFor({ + state: "visible", + timeout: 5000, + }) + } + + } + + async scrollToCheckoutSummary() { + await this.checkoutSummaryContainer.scrollIntoViewIfNeeded(); + } + + // Payment Method specific actions + async selectCreditCard() { + await this.getPaymentMethodReady(this.cardSelector); + return new CreditCardComponents(this.page); + } + + async selectPayPal() { + await this.getPaymentMethodReady(this.payPalSelector); + return new PayPalComponents(this.page); + + } + + async selectIdeal(){ + await this.getPaymentMethodReady(this.idealSelector); + return new IDealComponents(this.idealWrapper); + } + + async selectClearPay(){ + await this.getPaymentMethodReady(this.clearPaySelector); + } + + async selectKlarnaPayNow(){ + await this.getPaymentMethodReady(this.klarnaPayNowSelector); + } + + async selectKlarnaPayLater(){ + await this.getPaymentMethodReady(this.klarnaPayLaterSelector); + } + + async selectKlarnaPayAccount(){ + await this.getPaymentMethodReady(this.klarnaPayAccountSelector); + } + + async selectSepaDirectDebit(){ + await this.getPaymentMethodReady(this.sepaDirectDebitSelector); + return new SepaDirectDebitComponents(this.sepaDirectDebitWrapper); + } + + async selectMultiBanco(){ + await this.getPaymentMethodReady(this.multiBancoSelector); + } + + async selectOney(){ + await this.getPaymentMethodReady(this.oneySelector); + return new OneyComponents(this.oneyWrapper); + } + + async selectBancontactCard(){ + await this.getPaymentMethodReady(this.bancontactCardSelector); + return new BancontactCardComponents(this.bancontactCardWrapper); + } + + async selectStoredCard(){ + await this.getPaymentMethodReady(this.storedCardSelector); + return new CreditCardComponents(this.storedCardWrapper); + } + + async getPaymentMethodReady(locator) { + await locator.click(); + await this.page.waitForLoadState("networkidle", { timeout: 10000 }); + } + +} diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/pageObjects/plugin/ProductDetail.page.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/pageObjects/plugin/ProductDetail.page.js new file mode 100644 index 00000000..ca7e2512 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/pageObjects/plugin/ProductDetail.page.js @@ -0,0 +1,36 @@ + +import { BasePage } from "./Base.page.js"; + +export class ProductDetailPage extends BasePage { + constructor(page) { + super(page); + this.page = page; + + + this.productDetailContainer = page.locator(".product-detail-buy"); + + this.addToCartButton = this.productDetailContainer.locator(".btn-buy"); + this.increaseProductQuantityButton = this.productDetailContainer.locator(".js-btn-plus").first(); + + } + + async changeProductQuantity(quantity) { + while(quantity > 1){ + await this.increaseProductQuantityButton.click(); + quantity--; + } + } + + async clickAddToCart() { + await this.addToCartButton.click(); + } + + async addItemToCart(itemURL, count = 1) { + await this.page.goto(`/${itemURL}`); + if (count > 1) { + await this.changeProductQuantity(count); + } + await this.clickAddToCart(); + } + +} \ No newline at end of file diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/pageObjects/plugin/Result.page.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/pageObjects/plugin/Result.page.js new file mode 100644 index 00000000..a321e7c1 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/pageObjects/plugin/Result.page.js @@ -0,0 +1,22 @@ +import { SPRBasePage } from "./SPRBase.page.js"; + +export class ResultPage extends SPRBasePage { + constructor(page) { + super(page); + this.page = page; + + this.pageHeader = page.locator(".finish-header"); + this.voucherCodeContainer = page.locator(".adyen-checkout__voucher-result"); + } + + async titleText() { + return (await this.pageHeader.innerText()); + } + + async waitForRedirection() { + await this.page.waitForNavigation({ + url: / *\/checkout\/finish/, + timeout: 20000, + }); + } +} \ No newline at end of file diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/pageObjects/plugin/SPRBase.page.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/pageObjects/plugin/SPRBase.page.js new file mode 100644 index 00000000..89650b6f --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/pageObjects/plugin/SPRBase.page.js @@ -0,0 +1,18 @@ + +export class SPRBasePage { + constructor(page) { + this.page = page; + + // Header section + this.header = page.locator(".header-minimal"); + this.headerLogo = this.header.locator(".header-logo-main"); + + this.backToShopButton = this.header.locator(".header-minimal-back-to-shop-button"); + + } + + // General actions + async navigateBackToShop() { + await this.backToShopButton.click(); + } +} \ No newline at end of file diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/pageObjects/plugin/ShippingDetails.page.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/pageObjects/plugin/ShippingDetails.page.js new file mode 100644 index 00000000..24813ad8 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/pageObjects/plugin/ShippingDetails.page.js @@ -0,0 +1,103 @@ +import { SPRBasePage } from "./SPRBase.page.js"; + +export class ShippingDetailsPage extends SPRBasePage { + constructor(page) { + super(page); + this.page = page; + + //Alert message + this.alertMessage = page.locator(".alert-content"); + + //Change Billing Address Form + this.changeBillingAddressButton = page.locator("text=Change billing address"); + + this.currentAddressModal = page.locator(".address-editor-modal"); + this.editAddressButton = this.currentAddressModal.locator("text=Edit address").first(); + + this.editAddressEditorWrapper = page.locator(".address-editor-create-address-wrapper").first(); + + this.editSalutationDropDown = this.editAddressEditorWrapper.locator("#billing-addresspersonalSalutation"); + this.editFirstNameField = this.editAddressEditorWrapper.locator("#billing-addresspersonalFirstName"); + this.editLastNameField = this.editAddressEditorWrapper.locator("#billing-addresspersonalLastName"); + this.editAddressField = this.editAddressEditorWrapper.locator("#billing-addressAddressStreet"); + this.editPostCodeField = this.editAddressEditorWrapper.locator("#billing-addressAddressZipcode"); + this.editCityField = this.editAddressEditorWrapper.locator("#billing-addressAddressCity"); + this.editCountrySelectDropdown = this.editAddressEditorWrapper.locator("#billing-addressAddressCountry"); + this.editStateSelectDropDown = this.editAddressEditorWrapper.locator("#billing-addressAddressCountryState"); + + this.editSaveAddressButton = this.editAddressEditorWrapper.locator("button[type='submit']"); + + // Shipping details form + this.shippingFormContainer = page.locator(".register-form"); + + this.salutationDropDown = this.shippingFormContainer.locator("#personalSalutation"); + this.firstNameField = this.shippingFormContainer.locator("#personalFirstName"); + this.lastNameField = this.shippingFormContainer.locator("#personalLastName"); + this.createCustomerAccountCheckBox = this.shippingFormContainer.locator("label[for='personalGuest']"); + this.emailField = this.shippingFormContainer.locator("#personalMail"); + this.passwordField = this.shippingFormContainer.locator("#personalPassword"); + + this.addressField = this.shippingFormContainer.locator("#billingAddressAddressStreet"); + this.postCodeField = this.shippingFormContainer.locator("#billingAddressAddressZipcode"); + this.cityField = this.shippingFormContainer.locator("#billingAddressAddressCity"); + this.countrySelectDropdown = this.shippingFormContainer.locator("#billingAddressAddressCountry"); + this.stateSelectDropDown = this.shippingFormContainer.locator("#billingAddressAddressCountryState"); + + //Continue button + this.continueButton = page.locator(".register-submit button.btn-primary"); + + } + + // Shipping details actions + async fillShippingDetails(user, saveUser) { + await this.salutationDropDown.selectOption({ index: 1 }); + await this.firstNameField.fill(user.firstName); + await this.lastNameField.fill(user.lastName); + + await this.emailField.fill(user.email); + if (saveUser){ + await this.createCustomerAccountCheckBox.click(); + await this.passwordField.type(user.password); + } + + await this.addressField.fill(user.street); + await this.postCodeField.fill(user.postCode); + await this.cityField.fill(user.city); + + await this.countrySelectDropdown.scrollIntoViewIfNeeded(); + + const dropdownValue = await this.countrySelectDropdown.locator(`//option[contains(text(),'${user.countryName}')]`).getAttribute("value"); + await this.countrySelectDropdown.selectOption(dropdownValue); + + if (await this.stateSelectDropDown.isVisible()) { + await this.stateSelectDropDown.selectOption({ index: 2 }); + } + } + + async clickContinue() { + await this.continueButton.click(); + } + + async changeBillingAddress(user) { + await this.changeBillingAddressButton.click(); + await this.currentAddressModal.waitFor({ state: "visible", timeout: 10000}); + await this.editAddressButton.click(); + await this.editSaveAddressButton.waitFor({ state: "visible", timeout: 10000}); + + await this.editAddressField.fill(user.firstName); + await this.editPostCodeField.fill(user.postCode); + await this.editCityField.fill(user.city); + + const dropdownValue = await this.editCountrySelectDropdown.locator(`//option[contains(text(),'${user.countryName}')]`).getAttribute("value"); + await this.editCountrySelectDropdown.selectOption(dropdownValue); + + if (await this.editStateSelectDropDown.isVisible()) { + await this.editStateSelectDropDown.selectOption({ index: 2 }); + } + + await this.editSaveAddressButton.click(); + await this.currentAddressModal.waitFor({ state: "detached", timeout: 10000}); + } + + +} diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/shopware.config.cjs b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/shopware.config.cjs new file mode 100644 index 00000000..3399ffb7 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/shopware.config.cjs @@ -0,0 +1,147 @@ +// @ts-check +const { devices } = require("@playwright/test"); + +const VIEWPORT_WIDTH = 1600; +const VIEWPORT_HEIGHT = 900; + +/** + * @see https://playwright.dev/docs/test-configuration + * @type {import('@playwright/test').PlaywrightTestConfig} + */ +const config = { + testDir: "./tests/", + + /* Maximum time one test can run for. */ + timeout: 120 * 1000, + + expect: { + /** + * Maximum time expect() should wait for the condition to be met. + * For example in `await expect(locator).toHaveText();` + */ + timeout: 5000, + }, + + /* 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 ? 2 : undefined, + + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + + /* 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: 20000, + + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: process.env.SHOPWARE_BASE_URL, + ignoreHTTPSErrors: true, + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "retain-on-failure", + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + + /* Project-specific settings. */ + use: { + ...devices["Desktop Chrome"], + viewport: { + width: VIEWPORT_WIDTH, + height: VIEWPORT_HEIGHT, + }, + }, + }, + + { + name: "firefox", + use: { + ...devices["Desktop Firefox"], + viewport: { + width: VIEWPORT_WIDTH, + height: VIEWPORT_HEIGHT, + }, + }, + }, + + { + name: "webkit", + use: { + ...devices["Desktop Safari"], + viewport: { + width: VIEWPORT_WIDTH, + height: VIEWPORT_HEIGHT, + }, + }, + }, + + { + name: "chrome", + use: { + channel: "chrome", + viewport: { + width: VIEWPORT_WIDTH, + height: VIEWPORT_HEIGHT, + }, + }, + }, + + { + name: "edge", + use: { + channel: "msedge", + viewport: { + width: VIEWPORT_WIDTH, + height: VIEWPORT_HEIGHT, + }, + }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { + // ...devices['Pixel 5'], + // }, + // }, + // { + // name: 'Mobile Safari', + // use: { + // ...devices['iPhone 12'], + // }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { + // channel: 'msedge', + // }, + // }, + // { + // name: 'Google Chrome', + // use: { + // channel: 'chrome', + // }, + // }, + ], + + /* Folder for test artifacts such as screenshots, videos, traces, etc. */ + outputDir: "test-results/", + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // port: 3000, + // }, +}; +module.exports = config; diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/shopwareCIContainer.config.cjs b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/shopwareCIContainer.config.cjs new file mode 100644 index 00000000..e1d940df --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/shopwareCIContainer.config.cjs @@ -0,0 +1,101 @@ +// @ts-check +const { devices } = require("@playwright/test"); + +const VIEWPORT_WIDTH = 1600; +const VIEWPORT_HEIGHT = 900; + +/** + * @see https://playwright.dev/docs/test-configuration + * @type {import('@playwright/test').PlaywrightTestConfig} + */ +const config = { + testDir: "./tests/", + + /* Maximum time one test can run for. */ + timeout: 120 * 1000, + + expect: { + /** + * Maximum time expect() should wait for the condition to be met. + * For example in `await expect(locator).toHaveText();` + */ + timeout: 10000, + }, + + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + + /* Retry on CI only */ + retries: 1, + + /* Opt out of parallel tests on CI. */ + workers: 4, + + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: [["html", { outputFolder: "/tmp/test-report", open: "never" }]], + + /* 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: 20000, + + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: process.env.SHOPWARE_BASE_URL, + ignoreHTTPSErrors: true, + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: "all", + testDir: "./tests/", + use: { + browserName: "chromium", + trace: "retain-on-failure", + viewport: { + width: VIEWPORT_WIDTH, + height: VIEWPORT_HEIGHT, + }, + } + }, + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { + // ...devices['Pixel 5'], + // }, + // }, + // { + // name: 'Mobile Safari', + // use: { + // ...devices['iPhone 12'], + // }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { + // channel: 'msedge', + // }, + // }, + // { + // name: 'Google Chrome', + // use: { + // channel: 'chrome', + // }, + // }, + ], + + /* Folder for test artifacts such as screenshots, videos, traces, etc. */ + outputDir: "test-results/", + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // port: 3000, + // }, +}; +module.exports = config; diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/AdyenGiving.spec.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/AdyenGiving.spec.js new file mode 100644 index 00000000..603466fb --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/AdyenGiving.spec.js @@ -0,0 +1,44 @@ +import { test } from "@playwright/test"; +import { ThreeDS2PaymentPage } from "../../common/redirect/ThreeDS2PaymentPage.js"; +import PaymentResources from "../../data/PaymentResources.js"; +import { + doPrePaymentChecks, + goToShippingWithFullCart, + proceedToPaymentAs, + verifySuccessfulPayment +} from "../helpers/ScenarioHelper.js"; +import { makeCreditCardPayment } from "../helpers/PaymentHelper.js"; +import { AdyenGivingComponents } from "../../common/checkoutComponents/AdyenGivingComponents.js"; + +const paymentResources = new PaymentResources(); +const users = paymentResources.guestUser; + +test.describe.parallel("Adyen Giving payments", () => { + test.beforeEach(async ({ page }) => { + await goToShippingWithFullCart(page); + await proceedToPaymentAs(page, users.dutch); + await doPrePaymentChecks(page); + }); + + test("with 3Ds2 should succeed", async ({ page }) => { + + await makeCreditCardPayment( + page, + users.regular, + paymentResources.masterCard3DS2, + paymentResources.expDate, + paymentResources.cvc + ); + + await new ThreeDS2PaymentPage(page).validate3DS2( + paymentResources.threeDSCorrectPassword + ); + + await verifySuccessfulPayment(page); + const donationSection = new AdyenGivingComponents(page); + await donationSection.makeDonation("least"); + await donationSection.verifySuccessfulDonationMessage(); + }); +}); + + diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/BancontactCardPayment.spec.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/BancontactCardPayment.spec.js new file mode 100644 index 00000000..24773921 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/BancontactCardPayment.spec.js @@ -0,0 +1,46 @@ +import { test } from "@playwright/test"; +import PaymentResources from "../../data/PaymentResources.js"; +import { + doPrePaymentChecks, + goToShippingWithFullCart, + proceedToPaymentAs, + verifyFailedPayment, + verifySuccessfulPayment +} from "../helpers/ScenarioHelper.js"; +import { PaymentDetailsPage } from "../pageObjects/plugin/PaymentDetails.page.js"; +import { ThreeDS2PaymentPage } from "../../common/redirect/ThreeDS2PaymentPage.js"; + +const paymentResources = new PaymentResources(); +const user = paymentResources.guestUser.belgian; +const bancontactCard = paymentResources.bcmc.be; + +test.describe.parallel("Payment via credit card", () => { + test.beforeEach(async ({ page }) => { + await goToShippingWithFullCart(page); + await proceedToPaymentAs(page, user); + await doPrePaymentChecks(page); + const paymentDetailPage = new PaymentDetailsPage(page); + const bancontactCardSection = await paymentDetailPage.selectBancontactCard(); + + await bancontactCardSection.fillCardNumber(bancontactCard.cardNumber); + await bancontactCardSection.fillExpDate(bancontactCard.expDate); + + await paymentDetailPage.submitOrder(); + }); + + test("with 3Ds2 should succeed", async ({ page }) => { + await new ThreeDS2PaymentPage(page).validate3DS2( + paymentResources.threeDSCorrectPassword + ); + + await verifySuccessfulPayment(page); + }); + + test("with wrong 3Ds2 credentials should fail", async ({ page }) => { + await new ThreeDS2PaymentPage(page).validate3DS2( + paymentResources.threeDSWrongPassword + ); + + await verifyFailedPayment(page); + }); +}); diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/ClearPayPayment.spec.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/ClearPayPayment.spec.js new file mode 100644 index 00000000..235fa2ac --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/ClearPayPayment.spec.js @@ -0,0 +1,36 @@ +import { test } from "@playwright/test"; +import { ClearPayPaymentPage } from "../../common/redirect/ClearPayPaymentPage.js"; +import PaymentResources from "../../data/PaymentResources.js"; +import { + doPrePaymentChecks, + goToShippingWithFullCart, + proceedToPaymentAs, + verifySuccessfulPayment +} from "../helpers/ScenarioHelper.js"; +import { PaymentDetailsPage } from "../pageObjects/plugin/PaymentDetails.page.js"; + +const italianUser = new PaymentResources().guestUser.clearPay.approved.it; + +test.describe.parallel("Payment via ClearPay", () => { + test.beforeEach(async ({ page }) => { + await goToShippingWithFullCart(page); + await proceedToPaymentAs(page, italianUser); + await doPrePaymentChecks(page); + }); + + // Requires specific locations (VPN) + test.skip("should succeed", async ({ page }) => { + + await payViaClearPay(page); + await verifySuccessfulPayment(page); + + }); +}); + +export async function payViaClearPay(page){ + const paymentDetailPage = new PaymentDetailsPage(page); + await paymentDetailPage.selectClearPay(); + await paymentDetailPage.submitOrder(); + + await new ClearPayPaymentPage(page).continueClearPayPayment(); +} diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/CreditCardPayment.spec.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/CreditCardPayment.spec.js new file mode 100644 index 00000000..16a0996a --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/CreditCardPayment.spec.js @@ -0,0 +1,87 @@ +import { test } from "@playwright/test"; +import { ThreeDS2PaymentPage } from "../../common/redirect/ThreeDS2PaymentPage.js"; +import { ThreeDSPaymentPage } from "../../common/redirect/ThreeDSPaymentPage.js"; +import PaymentResources from "../../data/PaymentResources.js"; +import { + doPrePaymentChecks, + goToShippingWithFullCart, + proceedToPaymentAs, + verifyFailedPayment, + verifySuccessfulPayment +} from "../helpers/ScenarioHelper.js"; +import { makeCreditCardPayment } from "../helpers/PaymentHelper.js"; + +const paymentResources = new PaymentResources(); +const users = paymentResources.guestUser; + +test.describe.parallel("Payment via credit card", () => { + test.beforeEach(async ({ page }) => { + await goToShippingWithFullCart(page); + await proceedToPaymentAs(page, users.regular); + await doPrePaymentChecks(page); + }); + + test("without 3Ds should succeed", async ({ page }) => { + + await makeCreditCardPayment( + page, + users.regular, + paymentResources.masterCardWithout3D, + paymentResources.expDate, + paymentResources.cvc + ); + + await verifySuccessfulPayment(page); + + }); + + test("with 3Ds2 should succeed", async ({ page }) => { + + await makeCreditCardPayment( + page, + users.regular, + paymentResources.masterCard3DS2, + paymentResources.expDate, + paymentResources.cvc + ); + + await new ThreeDS2PaymentPage(page).validate3DS2( + paymentResources.threeDSCorrectPassword + ); + + await verifySuccessfulPayment(page); + }); + + test("with wrong 3Ds2 credentials should fail", async ({ page }) => { + + await makeCreditCardPayment( + page, + users.regular, + paymentResources.masterCard3DS2, + paymentResources.expDate, + paymentResources.cvc + ); + + await new ThreeDS2PaymentPage(page).validate3DS2( + paymentResources.threeDSWrongPassword + ); + + await verifyFailedPayment(page); + }); + + test("with 3Ds2 should abort the payment with correct message when cancelled", async ({ page }) => { + + await makeCreditCardPayment( + page, + users.regular, + paymentResources.masterCard3DS2, + paymentResources.expDate, + paymentResources.cvc + ); + + await new ThreeDS2PaymentPage(page).clickCancel(); + + await verifyFailedPayment(page); + }); + +}); diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/EditAddressChangePaymentList.spec.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/EditAddressChangePaymentList.spec.js new file mode 100644 index 00000000..0f7eb9d6 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/EditAddressChangePaymentList.spec.js @@ -0,0 +1,33 @@ +import { expect, test } from "@playwright/test"; +import PaymentResources from "../../data/PaymentResources.js"; +import { + doPrePaymentChecks, + goToShippingWithFullCart, + proceedToPaymentAs +} from "../helpers/ScenarioHelper.js"; +import { PaymentDetailsPage } from "../pageObjects/plugin/PaymentDetails.page.js"; +import { ShippingDetailsPage } from "../pageObjects/plugin/ShippingDetails.page.js"; + +const paymentResources = new PaymentResources(); +const dutchUser = paymentResources.guestUser.dutch; +const britishUser = paymentResources.guestUser.regular; + +test.describe("Payment methods", () => { + test.beforeEach(async ({ page }) => { + await goToShippingWithFullCart(page); + await proceedToPaymentAs(page, dutchUser); + await doPrePaymentChecks(page); + }); + + test("should be updated when the billing address is changed", async ({ page }) => { + const paymentDetailPage = new PaymentDetailsPage(page); + const shippingDetailPage = new ShippingDetailsPage(page); + + await expect(await paymentDetailPage.idealSelector).toBeVisible(); + await shippingDetailPage.changeBillingAddress(britishUser); + + await expect(await shippingDetailPage.alertMessage).toContainText("address has been changed"); + await doPrePaymentChecks(page); + await expect(await paymentDetailPage.idealSelector).not.toBeVisible(); + }); +}); diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/IdealPayment.spec.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/IdealPayment.spec.js new file mode 100644 index 00000000..6899f96a --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/IdealPayment.spec.js @@ -0,0 +1,32 @@ +import { test } from "@playwright/test"; +import PaymentResources from "../../data/PaymentResources.js"; +import { + doPrePaymentChecks, + goToShippingWithFullCart, + proceedToPaymentAs, + verifyFailedPayment, + verifySuccessfulPayment +} from "../helpers/ScenarioHelper.js"; +import { makeIDeal2Payment } from "../helpers/PaymentHelper.js"; + +const paymentResources = new PaymentResources(); +const users = paymentResources.guestUser; + +test.describe.parallel("Payment via iDeal", () => { + test.beforeEach(async ({ page }) => { + await goToShippingWithFullCart(page); + await proceedToPaymentAs(page, users.dutch); + await doPrePaymentChecks(page); + }); + + test("should succeed via Test Issuer", async ({ page }) => { + await makeIDeal2Payment(page, paymentResources.ideal2.issuer, true); + await verifySuccessfulPayment(page, true); + }); + + test("should fail via Failing Test Issuer", async ({ page }) => { + await makeIDeal2Payment(page, paymentResources.ideal2.issuer, false); + await verifyFailedPayment(page, true); + }); + +}); \ No newline at end of file diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/KlarnaPayment.spec.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/KlarnaPayment.spec.js new file mode 100644 index 00000000..0d4bdacc --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/KlarnaPayment.spec.js @@ -0,0 +1,77 @@ +import { test } from "@playwright/test"; +import KlarnaPaymentPage from "../../common/redirect/KlarnaPaymentPage.js"; +import PaymentResources from "../../data/PaymentResources.js"; +import { + doPrePaymentChecks, + goToShippingWithFullCart, + proceedToPaymentAs, + verifyFailedPayment, + verifySuccessfulPayment +} from "../helpers/ScenarioHelper.js"; +import { PaymentDetailsPage } from "../pageObjects/plugin/PaymentDetails.page.js"; + +const user = new PaymentResources().guestUser.klarna.approved.nl + +test.describe.parallel("Payment via Klarna", () => { + test.beforeEach(async ({ page }) => { + await goToShippingWithFullCart(page); + await proceedToPaymentAs(page, user); + await doPrePaymentChecks(page); + }); + + test("Pay Now should succeed via pay now", async ({ page }) => { + const klarnaPaymentPage = await proceedToKlarnaPayNow(page); + await klarnaPaymentPage.makeKlarnaPayment(user.phoneNumber, true); + await verifySuccessfulPayment(page, true, 25000); + }); + + test("Pay Now should fail gracefully when cancelled", async ({ page }) => { + const klarnaPaymentPage = await proceedToKlarnaPayNow(page); + await klarnaPaymentPage.cancelKlarnaPayment(); + await verifyFailedPayment(page, false); + }); + + test("Pay Later should succeed", async ({ page }) => { + const klarnaPaymentPage = await proceedToKlarnaPayLater(page); + await klarnaPaymentPage.makeKlarnaPayment(user.phoneNumber, false); + await verifySuccessfulPayment(page); + }); + +}); + +test.describe.parallel("Payment via Klarna", () => { + test.beforeEach(async ({ page }) => { + // Seperate flow for Account payments since minimum supported amount is 35 EUR + await goToShippingWithFullCart(page, 2); + await proceedToPaymentAs(page, user); + await doPrePaymentChecks(page); + }); + + test("Pay Klarna Account should succeed", async ({ page }) => { + const klarnaPaymentPage = await proceedToKlarnaPayAccount(page); + await klarnaPaymentPage.makeKlarnaPayment(user.phoneNumber, false); + await verifySuccessfulPayment(page); + }); + +}); + +async function proceedToKlarnaPayNow(page){ + const paymentDetailPage = new PaymentDetailsPage(page); + await paymentDetailPage.selectKlarnaPayNow(); + await paymentDetailPage.submitOrder(); + return new KlarnaPaymentPage(page); +} + +async function proceedToKlarnaPayLater(page){ + const paymentDetailPage = new PaymentDetailsPage(page); + await paymentDetailPage.selectKlarnaPayLater(); + await paymentDetailPage.submitOrder(); + return new KlarnaPaymentPage(page); +} + +async function proceedToKlarnaPayAccount(page){ + const paymentDetailPage = new PaymentDetailsPage(page); + await paymentDetailPage.selectKlarnaPayAccount(); + await paymentDetailPage.submitOrder(); + return new KlarnaPaymentPage(page); +} diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/MultiBancoPayment.spec.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/MultiBancoPayment.spec.js new file mode 100644 index 00000000..c191a601 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/MultiBancoPayment.spec.js @@ -0,0 +1,31 @@ +import { test } from "@playwright/test"; +import PaymentResources from "../../data/PaymentResources.js"; +import { + doPrePaymentChecks, + goToShippingWithFullCart, + proceedToPaymentAs, + verifySuccessfulPayment, + verifyVoucherCouponGeneration +} from "../helpers/ScenarioHelper.js"; +import { PaymentDetailsPage } from "../pageObjects/plugin/PaymentDetails.page.js"; + +const paymentResources = new PaymentResources(); +const users = paymentResources.guestUser; + +test.describe.parallel("Payment via MultiBanco", () => { + test.beforeEach(async ({ page }) => { + await goToShippingWithFullCart(page); + await proceedToPaymentAs(page, users.portuguese); + await doPrePaymentChecks(page); + }); + + test("should succeed", async ({ page }) => { + const paymentDetailPage = new PaymentDetailsPage(page); + await paymentDetailPage.selectMultiBanco(); + await paymentDetailPage.submitOrder(); + + await verifySuccessfulPayment(page); + await verifyVoucherCouponGeneration(page); + }); + +}); \ No newline at end of file diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/OneyPayment.spec.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/OneyPayment.spec.js new file mode 100644 index 00000000..4f13a94f --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/OneyPayment.spec.js @@ -0,0 +1,34 @@ +import { test } from "@playwright/test"; +import PaymentResources from "../../data/PaymentResources.js"; +import { + doPrePaymentChecks, + goToShippingWithFullCart, + proceedToPaymentAs, + verifySuccessfulPayment +} from "../helpers/ScenarioHelper.js"; +import { PaymentDetailsPage } from "../pageObjects/plugin/PaymentDetails.page.js"; +import { OneyPaymentPage } from "../../common/redirect/OneyPaymentPage.js"; + +const frenchUser = new PaymentResources().guestUser.oney.approved.fr; + +test.describe.parallel("Payment via Oney", () => { + test.beforeEach(async ({ page }) => { + await goToShippingWithFullCart(page, 8); + await proceedToPaymentAs(page, frenchUser); + await doPrePaymentChecks(page); + }); + + test("should succeed", async ({ page }) => { + await payViaOney(page); + }); +}); + +export async function payViaOney(page){ + const paymentDetailPage = new PaymentDetailsPage(page); + const oneyPaymentSection = await paymentDetailPage.selectOney(); + + await oneyPaymentSection.completeOneyForm(frenchUser) + await paymentDetailPage.submitOrder(); + + await new OneyPaymentPage(page).continueOneyPayment(); +} diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/PayPalPayment.spec.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/PayPalPayment.spec.js new file mode 100644 index 00000000..d9efd7d7 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/PayPalPayment.spec.js @@ -0,0 +1,49 @@ +import { test } from "@playwright/test"; +import PaymentResources from "../../data/PaymentResources.js"; +import { + doPrePaymentChecks, + goToShippingWithFullCart, + proceedToPaymentAs, + verifySuccessfulPayment +} from "../helpers/ScenarioHelper.js"; +import { PaymentDetailsPage } from "../pageObjects/plugin/PaymentDetails.page.js"; +import { PayPalPaymentPage } from "../../common/redirect/PayPalPaymentPage.js"; + +const paymentResources = new PaymentResources(); +const users = paymentResources.guestUser; + +test.describe("Payment via PayPal", () => { + test.beforeEach(async ({ page }) => { + await goToShippingWithFullCart(page); + await proceedToPaymentAs(page, users.regular); + await doPrePaymentChecks(page); + }); + + test("should succeed", async ({ page }) => { + + await payViaPayPal( + page, + paymentResources.payPalUserName, + paymentResources.payPalPassword + ); + + await verifySuccessfulPayment(page); + + }); + +}); + +async function payViaPayPal(page, username, password) { + const paymentDetailPage = new PaymentDetailsPage(page); + const payPalSection = await paymentDetailPage.selectPayPal(); + + await page.waitForLoadState("networkidle", { timeout: 10000 }); + await paymentDetailPage.scrollToCheckoutSummary(); + + const [popup] = await Promise.all([ + page.waitForEvent("popup"), + payPalSection.proceedToPayPal(), + ]); + + await new PayPalPaymentPage(popup).doLoginMakePayPalPayment(username, password); +} diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/SepaDirectDebitPayment.spec.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/SepaDirectDebitPayment.spec.js new file mode 100644 index 00000000..aa40a981 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/SepaDirectDebitPayment.spec.js @@ -0,0 +1,35 @@ +import { test } from "@playwright/test"; +import PaymentResources from "../../data/PaymentResources.js"; +import { + doPrePaymentChecks, + goToShippingWithFullCart, + proceedToPaymentAs, + verifySuccessfulPayment +} from "../helpers/ScenarioHelper.js"; +import { PaymentDetailsPage } from "../pageObjects/plugin/PaymentDetails.page.js"; + +const paymentResources = new PaymentResources(); +const dutchUser = paymentResources.guestUser.dutch; +const ibanDetails = paymentResources.sepaDirectDebit.nl; + +test.describe("Payment via SEPA Direct debit", () => { + test.beforeEach(async ({ page }) => { + await goToShippingWithFullCart(page); + await proceedToPaymentAs(page, dutchUser); + await doPrePaymentChecks(page); + }); + + // Depends on ECP-8525 + test("should succeed", async ({ page }) => { + const paymentDetailPage = new PaymentDetailsPage(page); + const sepaPaymentSection = await paymentDetailPage.selectSepaDirectDebit(); + + await sepaPaymentSection.fillSepaDirectDebitInfo( + ibanDetails.accountName, + ibanDetails.iban); + + await paymentDetailPage.submitOrder(); + await verifySuccessfulPayment(page); + + }); +}); diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/loggedIn/StoredCardPayment.spec.js b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/loggedIn/StoredCardPayment.spec.js new file mode 100644 index 00000000..5ca5a1f3 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/tests/loggedIn/StoredCardPayment.spec.js @@ -0,0 +1,58 @@ +import { test } from "@playwright/test"; +import { ThreeDS2PaymentPage } from "../../../common/redirect/ThreeDS2PaymentPage.js"; +import PaymentResources from "../../../data/PaymentResources.js"; +import { + doPrePaymentChecks, + goToShippingWithFullCart, + proceedToPaymentAs, + verifySuccessfulPayment +} from "../../helpers/ScenarioHelper.js"; +import { makeCreditCardPayment } from "../../helpers/PaymentHelper.js"; +import { PaymentDetailsPage } from "../../pageObjects/plugin/PaymentDetails.page.js"; + +const paymentResources = new PaymentResources(); +const user = paymentResources.guestUser.regular; + +test.describe.parallel("Payment via stored credit card", () => { + test.beforeEach(async ({ page }) => { + await goToShippingWithFullCart(page); + }); + + test("with 3Ds2 should succeed", async ({ page }) => { + + user.email = `${Math.floor(Math.random()*1000)}${user.email}`; + await proceedToPaymentAs(page, user, true); + await doPrePaymentChecks(page); + + await makeCreditCardPayment( + page, + user, + paymentResources.masterCard3DS2, + paymentResources.expDate, + paymentResources.cvc, + true + ); + + await new ThreeDS2PaymentPage(page).validate3DS2( + paymentResources.threeDSCorrectPassword + ); + + await verifySuccessfulPayment(page); + + await goToShippingWithFullCart(page); + await doPrePaymentChecks(page, false); + + const paymentDetailPage = new PaymentDetailsPage(page); + const storedCardSection = await paymentDetailPage.selectStoredCard(); + + await storedCardSection.fillCVC(paymentResources.cvc); + await paymentDetailPage.submitOrder(); + + await new ThreeDS2PaymentPage(page).validate3DS2( + paymentResources.threeDSCorrectPassword + ); + + await verifySuccessfulPayment(page); + + }); +}); diff --git a/.github/workflows/e2e/adyen-integration-tools-tests/renovate.json b/.github/workflows/e2e/adyen-integration-tools-tests/renovate.json new file mode 100755 index 00000000..39a2b6e9 --- /dev/null +++ b/.github/workflows/e2e/adyen-integration-tools-tests/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:base" + ] +} diff --git a/.github/workflows/scripts/e2e.sh b/.github/workflows/scripts/e2e.sh index 5c04e942..4d528295 100755 --- a/.github/workflows/scripts/e2e.sh +++ b/.github/workflows/scripts/e2e.sh @@ -2,11 +2,7 @@ set -euo pipefail # Checkout E2E tests -cd /tmp; -git clone https://github.com/Adyen/adyen-integration-tools-tests.git; -cd adyen-integration-tools-tests; -git checkout $INTEGRATION_TESTS_BRANCH; - +cd /tmp/adyen-integration-tools-tests; # Setup environment rm -rf package-lock.json; npm i; diff --git a/.github/workflows/templates/docker-compose.playwright.yml b/.github/workflows/templates/docker-compose.playwright.yml index 13973026..017672fa 100644 --- a/.github/workflows/templates/docker-compose.playwright.yml +++ b/.github/workflows/templates/docker-compose.playwright.yml @@ -2,7 +2,7 @@ version: '3' services: playwright: - image: mcr.microsoft.com/playwright:v1.48.0-focal + image: mcr.microsoft.com/playwright:v1.49.1-noble networks: - localnetwork shm_size: 1gb @@ -10,13 +10,13 @@ services: cap_add: - SYS_ADMIN environment: - - INTEGRATION_TESTS_BRANCH - SHOPWARE_BASE_URL - PAYPAL_USERNAME - PAYPAL_PASSWORD volumes: - ../scripts/e2e.sh:/e2e.sh - - ../../../test-report:/tmp/test-report + - ../e2e/adyen-integration-tools-tests/:/tmp/adyen-integration-tools-tests/ + - ../../test-report:/tmp/test-report networks: localnetwork: diff --git a/.gitignore b/.gitignore index 058cce53..99752ceb 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ .DS_Store #composer -vendor/ \ No newline at end of file +vendor/ +/.github/test-report/ +/.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/test-results/ From 073cf0614e6da26324f2885a11c3b1a94cea40c5 Mon Sep 17 00:00:00 2001 From: Tamara Date: Thu, 26 Dec 2024 12:35:14 +0100 Subject: [PATCH 2/8] Do not set company data when they do not exist --- src/Handlers/AbstractPaymentMethodHandler.php | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Handlers/AbstractPaymentMethodHandler.php b/src/Handlers/AbstractPaymentMethodHandler.php index f151b9fa..9e1490ec 100644 --- a/src/Handlers/AbstractPaymentMethodHandler.php +++ b/src/Handlers/AbstractPaymentMethodHandler.php @@ -291,13 +291,16 @@ public function pay( */ $stateData = $requestStateData ?? $storedStateData ?? []; + $billieData = []; $companyName = $dataBag->get('companyName'); $registrationNumber = $dataBag->get('registrationNumber'); - $billieData = [ - 'companyName' => $companyName, - 'registrationNumber' => $registrationNumber, - ]; + if ($companyName && $registrationNumber) { + $billieData = [ + 'companyName' => $companyName, + 'registrationNumber' => $registrationNumber, + ]; + } /* * If there are more than one stateData and /payments calls have been completed, @@ -773,7 +776,10 @@ private function getPaymentRequest( $billieData = [] ) { $transactionId = $transaction->getOrderTransaction()->getId(); - $stateData['billieData'] = $billieData; + if ($billieData !== []) { + $stateData['billieData'] = $billieData; + } + try { $request = $this->preparePaymentsRequest( $salesChannelContext, From 879dcb078f3d43e1c048521fdee3dd6f93b9dea9 Mon Sep 17 00:00:00 2001 From: Tamara Date: Thu, 26 Dec 2024 13:05:19 +0100 Subject: [PATCH 3/8] Change test report path --- .github/workflows/e2e-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index 37ce2b2b..330cabbf 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -65,7 +65,7 @@ jobs: uses: actions/upload-artifact@v3 with: name: html-report - path: test-report + path: ../test-report - name: Copy Adyen api logs from container if available if: always() From ca32b5a0d4c03a9d3c0cf8397045f30a0b72f228 Mon Sep 17 00:00:00 2001 From: Tamara Date: Thu, 26 Dec 2024 13:12:57 +0100 Subject: [PATCH 4/8] Change Shopware version --- .github/workflows/e2e-test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index 330cabbf..79566947 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -23,10 +23,10 @@ jobs: - name: Initiate Dockware run: | - docker pull dockware/dev:6.5.2.1 + docker pull dockware/dev:6.5.3.0 docker run --rm -p 443:443 --name shopware6 \ --mount type=bind,source="$(pwd)",target=/data/extensions/workdir \ - --env PHP_VERSION=8.2 -d dockware/dev:6.5.2.1 + --env PHP_VERSION=8.2 -d dockware/dev:6.5.3.0 sleep 30 docker logs shopware6 docker exec shopware6 bash -c "sudo mysql -u root -proot shopware -e \"UPDATE sales_channel_domain SET url='https://local.shopware.shop' WHERE url NOT LIKE 'default.%';\"" @@ -65,7 +65,7 @@ jobs: uses: actions/upload-artifact@v3 with: name: html-report - path: ../test-report + path: test-report - name: Copy Adyen api logs from container if available if: always() From fb6bb6f12195214d2dae9853c65badd8d0c20245 Mon Sep 17 00:00:00 2001 From: Tamara Date: Thu, 26 Dec 2024 13:52:36 +0100 Subject: [PATCH 5/8] Cheange Shopware version --- .github/workflows/e2e-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index 79566947..0c44fadb 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -23,10 +23,10 @@ jobs: - name: Initiate Dockware run: | - docker pull dockware/dev:6.5.3.0 + docker pull dockware/dev:6.5.8.14 docker run --rm -p 443:443 --name shopware6 \ --mount type=bind,source="$(pwd)",target=/data/extensions/workdir \ - --env PHP_VERSION=8.2 -d dockware/dev:6.5.3.0 + --env PHP_VERSION=8.2 -d dockware/dev:6.5.8.14 sleep 30 docker logs shopware6 docker exec shopware6 bash -c "sudo mysql -u root -proot shopware -e \"UPDATE sales_channel_domain SET url='https://local.shopware.shop' WHERE url NOT LIKE 'default.%';\"" From beb47b36925b54ebf29782293478fc8f9d3ab0e9 Mon Sep 17 00:00:00 2001 From: Tamara Date: Thu, 26 Dec 2024 14:11:05 +0100 Subject: [PATCH 6/8] Revert Shopware version --- .github/workflows/e2e-test.yml | 4 ++-- .github/workflows/templates/docker-compose.playwright.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index 0c44fadb..37ce2b2b 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -23,10 +23,10 @@ jobs: - name: Initiate Dockware run: | - docker pull dockware/dev:6.5.8.14 + docker pull dockware/dev:6.5.2.1 docker run --rm -p 443:443 --name shopware6 \ --mount type=bind,source="$(pwd)",target=/data/extensions/workdir \ - --env PHP_VERSION=8.2 -d dockware/dev:6.5.8.14 + --env PHP_VERSION=8.2 -d dockware/dev:6.5.2.1 sleep 30 docker logs shopware6 docker exec shopware6 bash -c "sudo mysql -u root -proot shopware -e \"UPDATE sales_channel_domain SET url='https://local.shopware.shop' WHERE url NOT LIKE 'default.%';\"" diff --git a/.github/workflows/templates/docker-compose.playwright.yml b/.github/workflows/templates/docker-compose.playwright.yml index 017672fa..fdbcf10b 100644 --- a/.github/workflows/templates/docker-compose.playwright.yml +++ b/.github/workflows/templates/docker-compose.playwright.yml @@ -16,7 +16,7 @@ services: volumes: - ../scripts/e2e.sh:/e2e.sh - ../e2e/adyen-integration-tools-tests/:/tmp/adyen-integration-tools-tests/ - - ../../test-report:/tmp/test-report + - ../../../test-report:/tmp/test-report networks: localnetwork: From 8a5e16f3e0364e4e9b4d3bca2a66d9f9749b108e Mon Sep 17 00:00:00 2001 From: Tamara Date: Thu, 26 Dec 2024 16:08:55 +0100 Subject: [PATCH 7/8] Fix the issue with Klarna payments --- .gitignore | 2 +- src/Handlers/AbstractPaymentMethodHandler.php | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 99752ceb..0abc0454 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,5 @@ #composer vendor/ -/.github/test-report/ /.github/workflows/e2e/adyen-integration-tools-tests/projects/shopware/test-results/ +/test-report/ diff --git a/src/Handlers/AbstractPaymentMethodHandler.php b/src/Handlers/AbstractPaymentMethodHandler.php index 9e1490ec..2448556b 100644 --- a/src/Handlers/AbstractPaymentMethodHandler.php +++ b/src/Handlers/AbstractPaymentMethodHandler.php @@ -324,7 +324,8 @@ public function pay( $this->paymentStateDataService->deletePaymentStateDataFromId($storedStateData['id']); } - $paymentMethodType = $stateData['paymentMethod']['type']; + $paymentMethodType = array_key_exists('paymentMethod', $stateData) ? + $stateData['paymentMethod']['type'] : ''; if ($paymentMethodType === RatepayPaymentMethod::RATEPAY_PAYMENT_METHOD_TYPE || $paymentMethodType === RatepayDirectdebitPaymentMethod::RATEPAY_DIRECTDEBIT_PAYMENT_METHOD_TYPE ) { @@ -364,7 +365,7 @@ public function pay( // Payment had no error, continue the process // If Bancontact mobile payment is used, redirect to proxy finalize transaction endpoint - if ($stateData['paymentMethod']['type'] === 'bcmc_mobile') { + if (array_key_exists('paymentMethod', $stateData) && $stateData['paymentMethod']['type'] === 'bcmc_mobile') { return new RedirectResponse($this->getReturnUrl($transaction)); } @@ -776,7 +777,7 @@ private function getPaymentRequest( $billieData = [] ) { $transactionId = $transaction->getOrderTransaction()->getId(); - if ($billieData !== []) { + if (!empty($billieData)) { $stateData['billieData'] = $billieData; } From be8488306fd1111e736823ecf91a560b1cd71682 Mon Sep 17 00:00:00 2001 From: Tamara Date: Fri, 27 Dec 2024 11:28:26 +0100 Subject: [PATCH 8/8] Version bump 3.17.4 AETMTNR-2 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 2c0bb78b..67d1f96a 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,7 @@ } ], "description": "Official Shopware 6 Plugin to connect to Payment Service Provider Adyen", - "version": "3.17.3", + "version": "3.17.4", "type": "shopware-platform-plugin", "license": "MIT", "require": {