diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000..c84de820d2 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,15 @@ +# CODEOWNERS file for PayPal Payments Plugin +# This file defines code owners who will automatically be requested for review +# when a pull request touches files matching the specified patterns. + +# Global owners - will be requested for review on all changes +# @Dinamiko is listed first as dev lead, followed by team members in alphabetical order +* @Dinamiko @AlexP11223 @danieldudzic @hmouhtar @Narek13 @puntope @stracker-phil + +# You can also add specific patterns for different parts of the codebase: +# Examples (uncomment and modify as needed): +# /src/ @Dinamiko @stracker-phil +# /tests/ @AlexP11223 @hmouhtar +# /assets/ @danieldudzic @puntope +# /.github/workflows/ @Narek13 +# /docs/ @Dinamiko diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml new file mode 100644 index 0000000000..0c5ea73f92 --- /dev/null +++ b/.github/workflows/e2e-tests.yml @@ -0,0 +1,136 @@ +name: E2E Tests + +on: + workflow_dispatch: + inputs: + update-workflows: + description: 'Weather generate new snapshots' + type: boolean + default: false + wp-rc-version: + description: 'WordPress version for Release Candidate (i.e. 6.3-RC3)' + wc-rc-version: + description: 'WooCommerce version for Release Candidate (i.e. 8.0.0-rc.1)' + test-suite: + description: 'Test suite to run' + required: true + default: 'critical' + type: choice + options: + - critical + - onboarding + - all + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Build package + uses: inpsyde/reusable-workflows/.github/workflows/build-plugin-archive.yml@a9af34f34e95cbe18703198c7e972e97ebcd7473 + with: + PHP_VERSION: 7.4 + NODE_VERSION: 22 + PLUGIN_MAIN_FILE: ./woocommerce-paypal-payments.php + PLUGIN_VERSION: 3.0.0 + PLUGIN_FOLDER_NAME: woocommerce-paypal-payments + ARCHIVE_NAME: woocommerce-paypal-payments + COMPILE_ASSETS_ARGS: '-vv --env=root' + + E2ETests: + name: E2E Tests + needs: build + runs-on: ubuntu-latest + env: + FORCE_COLOR: 2 + steps: + - uses: actions/checkout@v2 + + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: woocommerce-paypal-payments + path: ./tests/qa/resources/files + + - name: Create and upload zip file + working-directory: ./tests/qa/resources/files + run: | + zip -r ./woocommerce-paypal-payments.zip ./woocommerce-paypal-payments + rm -rf woocommerce-paypal-payments + + - name: Configure npm for GitHub Packages + working-directory: ./tests/qa + run: | + # Create clean .npmrc + echo "@inpsyde:registry=https://npm.pkg.github.com" > .npmrc + echo "//npm.pkg.github.com/:_authToken=${{ secrets.NPM_SYDE }}" >> .npmrc + + - name: Install Environment + working-directory: ./tests/qa + run: npm run setup:env + env: + COMPOSER_AUTH: '${{ secrets.COMPOSER_AUTH_SYDE }}' + + - name: Install WP release candidate (optional) + if: github.event.inputs.wp-rc-version != '' + working-directory: ./tests/qa + env: + INPUT_WP_RC_VERSION: ${{ github.event.inputs.wp-rc-version }} + run: | + npm run -- wp-env run tests-cli -- wp core update --version="${INPUT_WP_RC_VERSION}" + npm run -- wp-env run tests-cli -- wp core update-db + + - name: Install WC release candidate (optional) + if: github.event.inputs.wc-rc-version != '' + working-directory: ./tests/qa + env: + INPUT_WC_RC_VERSION: ${{ github.event.inputs.wc-rc-version }} + run: | + npm run -- wp-env run tests-cli -- wp plugin update woocommerce --version="${INPUT_WC_RC_VERSION}" + npm run -- wp-env run tests-cli -- wp wc update + + - name: Install Tests + working-directory: ./tests/qa + run: npm run setup:tests + + - name: Create .env file + working-directory: ./tests/qa + run: | + echo "${{ secrets.QA_ENV_FILE }}" > .env + + - name: Run E2E tests + working-directory: ./tests/qa + run: | + if [ "${{ github.event.inputs.update_snapshots }}" == "true" ]; then + npm run tests:${{ github.event.inputs.test-suite }} -- --update-snapshots + else + npm run tests:${{ github.event.inputs.test-suite }} + fi + + - name: Archive updated snapshots + if: github.event.inputs.update_snapshots == 'true' + uses: actions/upload-artifact@v4 + with: + name: snapshots + path: tests/qa/snapshots + if-no-files-found: warn + retention-days: 5 + + - name: Archive Playwright Report + if: ${{ always() }} + uses: actions/upload-artifact@v4 + with: + name: playwright-test-results + path: tests/qa/playwright-report + if-no-files-found: ignore + retention-days: 5 + + - name: Archive Test Results + if: ${{ always() }} + uses: actions/upload-artifact@v4 + with: + name: test-results + path: tests/qa/test-results/ + if-no-files-found: ignore + retention-days: 5 diff --git a/modules/ppcp-axo/resources/js/AxoManager.js b/modules/ppcp-axo/resources/js/AxoManager.js index 7d6b2ff833..f42295734c 100644 --- a/modules/ppcp-axo/resources/js/AxoManager.js +++ b/modules/ppcp-axo/resources/js/AxoManager.js @@ -4,6 +4,7 @@ import DomElementCollection from './Components/DomElementCollection'; import ShippingView from './Views/ShippingView'; import BillingView from './Views/BillingView'; import CardView from './Views/CardView'; +import ButtonStateManager from './ButtonStateManager'; import PayPalInsights from './Insights/PayPalInsights'; import { disable, @@ -70,6 +71,8 @@ class AxoManager { hasCard: false, }; + this.buttonStateManager = null; + this.clearData(); // TODO - Do we need a public `states` property for this? @@ -251,41 +254,19 @@ class AxoManager { this.cardView.refresh(); } ); - // Prevents sending checkout form when pressing Enter key on input field - // and triggers customer lookup - this.$( 'form.woocommerce-checkout input' ).on( - 'keydown', - async ( ev ) => { - if ( - ev.key === 'Enter' && - getCurrentPaymentMethod() === 'ppcp-axo-gateway' - ) { - ev.preventDefault(); - log( - `Enter key attempt - emailInput: ${ this.emailInput.value }` - ); - log( - `this.lastEmailCheckedIdentity: ${ this.lastEmailCheckedIdentity }` - ); - this.validateEmail( this.el.fieldBillingEmail.selector ); - if ( - this.emailInput && - this.lastEmailCheckedIdentity !== this.emailInput.value - ) { - await this.onChangeEmail(); - } - } - } - ); - this.reEnableEmailInput(); // Clear last email checked identity when email field is focused. this.$( '#billing_email_field input' ).on( 'focus', ( ev ) => { - log( - `Clear the last email checked: ${ this.lastEmailCheckedIdentity }` - ); - this.lastEmailCheckedIdentity = ''; + if ( ! this.buttonStateManager?.isProcessing() ) { + this.buttonStateManager?.clearLastProcessedEmail(); + if ( + this.emailInput?.value && + this.validateEmailFormat( this.emailInput.value ) + ) { + this.buttonStateManager?.setReady(); + } + } } ); // Listening to status update event @@ -411,9 +392,6 @@ class AxoManager { if ( scenario.axoPaymentContainer ) { this.el.paymentContainer.show(); this.el.gatewayDescription.hide(); - document - .querySelector( this.el.billingEmailSubmitButton.selector ) - .setAttribute( 'disabled', 'disabled' ); } else { this.el.paymentContainer.hide(); } @@ -520,9 +498,6 @@ class AxoManager { this.readPhoneFromWoo(); log( `Attempt on activation - emailInput: ${ this.emailInput.value }` ); - log( - `this.lastEmailCheckedIdentity: ${ this.lastEmailCheckedIdentity }` - ); const urlParams = new URLSearchParams( window.location.search ); const hasErrorParam = urlParams.get( 'ppcp_fastlane_error' ) === '1'; @@ -531,11 +506,17 @@ class AxoManager { log( 'Payment failure detected, session restoration will be attempted' ); - } else if ( - this.emailInput && - this.lastEmailCheckedIdentity !== this.emailInput.value - ) { - this.onChangeEmail(); + } else if ( this.emailInput && this.emailInput.value ) { + if ( + this.buttonStateManager?.shouldProcessEmail( + this.emailInput.value, + this.validateEmailFormat.bind( this ) + ) + ) { + this.onChangeEmail(); + } else { + this.refreshFastlanePrefills(); + } } else { this.refreshFastlanePrefills(); } @@ -654,6 +635,75 @@ class AxoManager { } } + initButtonStateManager() { + if ( ! this.buttonStateManager ) { + this.buttonStateManager = new ButtonStateManager( + this.el.billingEmailSubmitButton.selector, + this.el.billingEmailSubmitButtonSpinner.selector + ); + log( 'Button state manager initialized' ); + } + } + + registerEmailEventHandlers() { + const emailInput = document.querySelector( + '#billing_email_field input' + ); + + if ( emailInput ) { + emailInput.addEventListener( 'keydown', async ( ev ) => { + if ( + ev.key === 'Enter' && + getCurrentPaymentMethod() === 'ppcp-axo-gateway' + ) { + ev.preventDefault(); + ev.stopPropagation(); + + log( + `Enter key on email field - value: ${ this.emailInput.value }` + ); + this.validateEmail( this.el.fieldBillingEmail.selector ); + + if ( + this.emailInput && + this.buttonStateManager?.shouldAllowRetry( + this.emailInput.value, + this.validateEmailFormat.bind( this ) + ) + ) { + await this.onChangeEmail(); + } + + return false; + } + } ); + } + + const submitButton = document.querySelector( + this.el.billingEmailSubmitButton.selector + ); + + if ( submitButton ) { + submitButton.addEventListener( 'click', async ( ev ) => { + ev.preventDefault(); + + log( + `Submit button clicked - email: ${ this.emailInput.value }` + ); + + if ( + this.emailInput && + this.buttonStateManager?.shouldAllowRetry( + this.emailInput.value, + this.validateEmailFormat.bind( this ) + ) + ) { + await this.onChangeEmail(); + } + } ); + } + } + async initFastlane() { if ( this.initialized ) { return; @@ -663,6 +713,8 @@ class AxoManager { await this.connect(); await this.renderWatermark(); this.renderEmailSubmitButton(); + this.initButtonStateManager(); + this.registerEmailEventHandlers(); this.watchEmail(); await this.restoreSessionAfterFailure(); @@ -735,7 +787,9 @@ class AxoManager { // Reorder button to ensure it's before the watermark container wrapper.insertBefore( buttonElement, watermarkContainer ); - buttonElement.offsetHeight; + // eslint-disable-next-line no-unused-expressions + buttonElement.offsetHeight; // Force layout reflow + buttonElement.classList.remove( 'ppcp-axo-billing-email-submit-button-hidden' ); @@ -753,30 +807,25 @@ class AxoManager { log( `Change event attempt - emailInput: ${ this.emailInput.value }` ); - log( - `this.lastEmailCheckedIdentity: ${ this.lastEmailCheckedIdentity }` - ); + if ( this.emailInput && - this.lastEmailCheckedIdentity !== this.emailInput.value + this.buttonStateManager?.shouldProcessEmail( + this.emailInput.value, + this.validateEmailFormat.bind( this ) + ) ) { this.validateEmail( this.el.fieldBillingEmail.selector ); this.onChangeEmail(); } } ); - log( - `Last, this.emailInput.value attempt - emailInput: ${ this.emailInput.value }` - ); - log( - `this.lastEmailCheckedIdentity: ${ this.lastEmailCheckedIdentity }` - ); + log( `Checking initial email value: ${ this.emailInput.value }` ); if ( this.emailInput.value ) { this.onChangeEmail(); } } } - /** * Locates the WooCommerce checkout "billing phone" field and adds event listeners to it. */ @@ -833,46 +882,50 @@ class AxoManager { return; } - if ( this.data.email === this.emailInput.value ) { - log( 'Email has not changed since last validation.' ); + const currentEmail = this.emailInput.value; + + // Check if we should process this email using ButtonStateManager + if ( + ! this.buttonStateManager?.shouldProcessEmail( + currentEmail, + this.validateEmailFormat.bind( this ) + ) + ) { + log( + 'Email processing skipped - already processing or same email' + ); return; } - log( - `Email changed: ${ - this.emailInput ? this.emailInput.value : '' - }` - ); - this.clearData(); + log( `Email changed: ${ currentEmail || '' }` ); - this.emailInput.value = this.stripSpaces( this.emailInput.value ); + this.buttonStateManager.markEmailAsProcessing( currentEmail ); + this.clearData(); + this.emailInput.value = this.stripSpaces( this.emailInput.value ); this.$( this.el.paymentContainer.selector + '-details' ).html( '' ); this.removeFastlaneComponent(); - this.setStatus( 'validEmail', false ); this.setStatus( 'hasProfile', false ); - this.hideGatewaySelection = false; - this.lastEmailCheckedIdentity = this.emailInput.value; - if ( ! this.emailInput.value || ! this.emailInput.checkValidity() || ! this.validateEmailFormat( this.emailInput.value ) ) { log( 'The email address is not valid.' ); + this.buttonStateManager?.setDisabled(); return; } this.data.email = this.emailInput.value; this.billingView.setData( this.data ); - this.readPhoneFromWoo(); if ( ! this.fastlane.identity ) { log( 'Not initialized.' ); + this.buttonStateManager?.setDisabled(); return; } @@ -881,18 +934,15 @@ class AxoManager { } ); this.disableGatewaySelection(); - this.spinnerToggleLoaderAndOverlay( - this.el.billingEmailSubmitButtonSpinner, - 'loader', - 'ppcp-axo-overlay' - ); - await this.lookupCustomerByEmail(); - this.spinnerToggleLoaderAndOverlay( - this.el.billingEmailSubmitButtonSpinner, - 'loader', - 'ppcp-axo-overlay' - ); - this.enableGatewaySelection(); + + try { + await this.lookupCustomerByEmail(); + } catch ( error ) { + log( `Email lookup failed: ${ error.message }`, 'error' ); + this.buttonStateManager?.handleEmailLookupFailure(); + } finally { + this.enableGatewaySelection(); + } } /** @@ -912,93 +962,49 @@ class AxoManager { } async lookupCustomerByEmail() { - const lookupResponse = - await this.fastlane.identity.lookupCustomerByEmail( - this.emailInput.value - ); - - log( `lookupCustomerByEmail: ${ JSON.stringify( lookupResponse ) }` ); + try { + const lookupResponse = + await this.fastlane.identity.lookupCustomerByEmail( + this.emailInput.value + ); - if ( lookupResponse.customerContextId ) { - // Email is associated with a Connect profile or a PayPal member. - // Authenticate the customer to get access to their profile. log( - 'Email is associated with a Connect profile or a PayPal member' + `lookupCustomerByEmail: ${ JSON.stringify( lookupResponse ) }` ); - const authResponse = - await this.fastlane.identity.triggerAuthenticationFlow( - lookupResponse.customerContextId + if ( lookupResponse.customerContextId ) { + log( + 'Email is associated with a Connect profile or a PayPal member' ); - log( - `AuthResponse - triggerAuthenticationFlow: ${ JSON.stringify( - authResponse - ) }` - ); + const authResponse = + await this.fastlane.identity.triggerAuthenticationFlow( + lookupResponse.customerContextId + ); - if ( authResponse.authenticationState === 'succeeded' ) { - const shippingData = authResponse.profileData.shippingAddress; - if ( shippingData ) { - this.setShipping( shippingData ); - } + log( + `AuthResponse - triggerAuthenticationFlow: ${ JSON.stringify( + authResponse + ) }` + ); - if ( authResponse.profileData.card ) { - this.setStatus( 'hasCard', true ); + if ( authResponse.authenticationState === 'succeeded' ) { + await this.handleSuccessfulAuth( authResponse ); + this.buttonStateManager?.handleSuccess(); } else { - await this.initializeFastlaneComponent(); - } - - const cardBillingAddress = - authResponse.profileData?.card?.paymentSource?.card - ?.billingAddress; - if ( cardBillingAddress ) { - this.setCard( authResponse.profileData.card ); - - const billingData = { - address: cardBillingAddress, - }; - const phoneNumber = - authResponse.profileData?.shippingAddress?.phoneNumber - ?.nationalNumber ?? ''; - if ( phoneNumber ) { - billingData.phoneNumber = phoneNumber; - } - - this.setBilling( billingData ); + log( 'Authentication Failed or Canceled' ); + await this.handleFailedAuth(); + this.buttonStateManager?.handleAuthFailureOrCancellation(); } - - this.setStatus( 'validEmail', true ); - this.setStatus( 'hasProfile', true ); - - this.hideGatewaySelection = true; - this.$( '.wc_payment_methods label' ).hide(); - this.$( '.wc_payment_methods input' ).hide(); - - await this.renderWatermark( false ); - - this.rerender(); } else { - // authentication failed or canceled by the customer - // set status as guest customer - log( 'Authentication Failed' ); - - this.setStatus( 'validEmail', true ); - this.setStatus( 'hasProfile', false ); - - await this.renderWatermark( true ); - await this.initializeFastlaneComponent(); + log( 'No profile found with this email address.' ); + await this.handleGuestCustomer(); + this.buttonStateManager?.handleSuccess(); } - } else { - // No profile found with this email address. - // This is a guest customer. - log( 'No profile found with this email address.' ); - - this.setStatus( 'validEmail', true ); - this.setStatus( 'hasProfile', false ); - - await this.renderWatermark( true ); - await this.initializeFastlaneComponent(); + } catch ( error ) { + log( `lookupCustomerByEmail error: ${ error.message }`, 'error' ); + this.buttonStateManager?.handleEmailLookupFailure(); + throw error; } } @@ -1375,11 +1381,12 @@ class AxoManager { reEnableEmailInput() { const reEnableInput = ( ev ) => { - const submitButton = document.querySelector( - this.el.billingEmailSubmitButton.selector - ); - if ( submitButton.hasAttribute( 'disabled' ) ) { - submitButton.removeAttribute( 'disabled' ); + if ( + ! this.buttonStateManager?.isProcessing() && + this.emailInput?.value && + this.validateEmailFormat( this.emailInput.value ) + ) { + this.buttonStateManager?.setReady(); } }; @@ -1413,6 +1420,11 @@ class AxoManager { `Restoring Fastlane session for email: ${ this.emailInput.value }` ); + // Set processing state for session restoration. + this.buttonStateManager?.markEmailAsProcessing( + this.emailInput.value + ); + const lookupResult = await this.fastlane.identity.lookupCustomerByEmail( this.emailInput.value @@ -1428,52 +1440,74 @@ class AxoManager { authenticatedCustomerResult?.authenticationState === 'succeeded' ) { - const { profileData } = authenticatedCustomerResult; - - if ( profileData?.shippingAddress ) { - this.setShipping( profileData.shippingAddress ); - } - - if ( profileData?.card ) { - this.setCard( profileData.card ); - this.setStatus( 'hasCard', true ); - - const cardBillingAddress = - profileData.card?.paymentSource?.card - ?.billingAddress; - if ( cardBillingAddress ) { - const billingData = { - address: cardBillingAddress, - }; - - const phoneNumber = - profileData.shippingAddress?.phoneNumber - ?.nationalNumber; - if ( phoneNumber ) { - billingData.phoneNumber = phoneNumber; - } - - this.setBilling( billingData ); - } - } - - this.setStatus( 'validEmail', true ); - this.setStatus( 'hasProfile', true ); - - this.hideGatewaySelection = true; - this.$( '.wc_payment_methods label' ).hide(); - this.$( '.wc_payment_methods input' ).hide(); - - await this.renderWatermark( false ); - + await this.handleSuccessfulAuth( + authenticatedCustomerResult + ); + this.buttonStateManager?.handleSuccess(); log( 'Fastlane session successfully restored' ); + } else { + await this.handleFailedAuth(); + this.buttonStateManager?.handleAuthFailureOrCancellation(); } + } else { + await this.handleGuestCustomer(); + this.buttonStateManager?.handleSuccess(); } } } catch ( error ) { log( 'Failed to restore Fastlane session', 'warn' ); - console.warn( 'Fastlane session restoration error:', error ); + log( 'Fastlane session restoration error:', 'error' ); + this.buttonStateManager?.handleEmailLookupFailure(); + } + } + + async handleSuccessfulAuth( authResponse ) { + const shippingData = authResponse.profileData.shippingAddress; + if ( shippingData ) { + this.setShipping( shippingData ); + } + + if ( authResponse.profileData.card ) { + this.setStatus( 'hasCard', true ); + } else { + await this.initializeFastlaneComponent(); + } + + const cardBillingAddress = + authResponse.profileData?.card?.paymentSource?.card?.billingAddress; + if ( cardBillingAddress ) { + this.setCard( authResponse.profileData.card ); + const billingData = { address: cardBillingAddress }; + const phoneNumber = + authResponse.profileData?.shippingAddress?.phoneNumber + ?.nationalNumber ?? ''; + if ( phoneNumber ) { + billingData.phoneNumber = phoneNumber; + } + this.setBilling( billingData ); } + + this.setStatus( 'validEmail', true ); + this.setStatus( 'hasProfile', true ); + this.hideGatewaySelection = true; + this.$( '.wc_payment_methods label' ).hide(); + this.$( '.wc_payment_methods input' ).hide(); + await this.renderWatermark( false ); + this.rerender(); + } + + async handleFailedAuth() { + this.setStatus( 'validEmail', true ); + this.setStatus( 'hasProfile', false ); + await this.renderWatermark( true ); + await this.initializeFastlaneComponent(); + } + + async handleGuestCustomer() { + this.setStatus( 'validEmail', true ); + this.setStatus( 'hasProfile', false ); + await this.renderWatermark( true ); + await this.initializeFastlaneComponent(); } } diff --git a/modules/ppcp-axo/resources/js/ButtonStateManager.js b/modules/ppcp-axo/resources/js/ButtonStateManager.js new file mode 100644 index 0000000000..92d453e999 --- /dev/null +++ b/modules/ppcp-axo/resources/js/ButtonStateManager.js @@ -0,0 +1,207 @@ +import { log } from './Helper/Debug'; + +/** + * Manages the state and UI of the email submit button. + * Handles processing states, validation, and duplicate submission prevention. + */ +class ButtonStateManager { + constructor( submitButtonSelector, spinnerSelector ) { + this.submitButtonSelector = submitButtonSelector; + this.spinnerSelector = spinnerSelector; + + this.state = { + isProcessing: false, + canSubmit: false, + lastProcessedEmail: null, + }; + } + + /** + * Get current button state (read-only). + */ + getState() { + return { ...this.state }; + } + + /** + * Centralized button UI management based on current state. + */ + updateButtonUI() { + const submitButton = document.querySelector( + this.submitButtonSelector + ); + if ( ! submitButton ) { + log( 'Submit button not found, skipping UI update', 'warn' ); + return; + } + + const spinner = document.querySelector( this.spinnerSelector ); + + if ( this.state.isProcessing ) { + // Processing state - disabled with spinner + submitButton.setAttribute( 'disabled', 'disabled' ); + spinner?.classList.add( 'loader', 'ppcp-axo-overlay' ); + submitButton.classList.add( 'processing' ); + log( 'Button set to processing state' ); + } else if ( this.state.canSubmit ) { + // Ready state - enabled + submitButton.removeAttribute( 'disabled' ); + spinner?.classList.remove( 'loader', 'ppcp-axo-overlay' ); + submitButton.classList.remove( 'processing' ); + log( 'Button set to ready state' ); + } else { + // Default/disabled state + submitButton.setAttribute( 'disabled', 'disabled' ); + spinner?.classList.remove( 'loader', 'ppcp-axo-overlay' ); + submitButton.classList.remove( 'processing' ); + log( 'Button set to disabled state' ); + } + } + + /** + * Set button to processing state (disabled with spinner). + */ + setProcessing() { + this.state.isProcessing = true; + this.state.canSubmit = false; + this.updateButtonUI(); + log( 'Button state changed to: processing' ); + } + + /** + * Set button to ready state (enabled and clickable). + */ + setReady() { + this.state.isProcessing = false; + this.state.canSubmit = true; + this.updateButtonUI(); + log( 'Button state changed to: ready' ); + } + + /** + * Set button to disabled state (disabled, no spinner). + */ + setDisabled() { + this.state.isProcessing = false; + this.state.canSubmit = false; + this.updateButtonUI(); + log( 'Button state changed to: disabled' ); + } + + /** + * Check if we should process the email (prevents duplicate processing). + * @param {string} email - Email to check. + * @param {Function} validateEmailFormat - Email validation function. + * @return {boolean} True if email should be processed. + */ + shouldProcessEmail( email, validateEmailFormat ) { + const shouldProcess = + ! this.state.isProcessing && + this.state.lastProcessedEmail !== email && + email && + validateEmailFormat( email ); + + log( + `shouldProcessEmail: ${ shouldProcess } (processing: ${ this.state.isProcessing }, lastEmail: ${ this.state.lastProcessedEmail }, currentEmail: ${ email })` + ); + return shouldProcess; + } + + /** + * Check if we should allow retry after failure/cancellation. + * @param {string} email - Email to check. + * @param {Function} validateEmailFormat - Email validation function. + * @return {boolean} True if retry should be allowed. + */ + shouldAllowRetry( email, validateEmailFormat ) { + const shouldRetry = + ! this.state.isProcessing && email && validateEmailFormat( email ); + + log( + `shouldAllowRetry: ${ shouldRetry } (processing: ${ this.state.isProcessing }, email: ${ email })` + ); + return shouldRetry; + } + + /** + * Mark email as being processed to prevent duplicates. + * @param {string} email - Email being processed. + */ + markEmailAsProcessing( email ) { + this.state.lastProcessedEmail = email; + this.setProcessing(); + log( `Email marked as processing: ${ email }` ); + } + + /** + * Clear the last processed email (allows retry). + */ + clearLastProcessedEmail() { + const previousEmail = this.state.lastProcessedEmail; + this.state.lastProcessedEmail = null; + log( `Cleared last processed email: ${ previousEmail }` ); + } + + /** + * Handle authentication failure or cancellation - allows retry. + */ + handleAuthFailureOrCancellation() { + log( 'Handling auth failure/cancellation - allowing retry' ); + this.clearLastProcessedEmail(); + this.setReady(); + + // Force UI update to ensure button is actually enabled. + setTimeout( () => { + this.updateButtonUI(); + log( 'Forced button UI update after cancellation' ); + }, 100 ); + } + + /** + * Handle email lookup failure - disables button. + */ + handleEmailLookupFailure() { + log( 'Handling email lookup failure - disabling button' ); + this.clearLastProcessedEmail(); + this.setDisabled(); + } + + /** + * Handle successful processing - enables button for next action. + */ + handleSuccess() { + log( 'Handling successful processing' ); + this.setReady(); + } + + /** + * Reset to initial state. + */ + reset() { + this.state = { + isProcessing: false, + canSubmit: false, + lastProcessedEmail: null, + }; + this.updateButtonUI(); + log( 'Button state reset to initial state' ); + } + + /** + * Check if currently processing. + * @return {boolean} True if the button is currently in the processing state. + */ + isProcessing() { + return this.state.isProcessing; + } + + /** + * Check if button can be submitted. + * @return {boolean} True if the button can be submitted and is not in the processing state. + */ + canSubmit() { + return this.state.canSubmit && ! this.state.isProcessing; + } +} + +export default ButtonStateManager; diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index c28c11868b..2a43aac988 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -347,6 +347,24 @@ function () use ( $c ) { } ); + add_filter( + 'ppcp_return_url_error_args', + /** + * Param types removed to avoid third-party issues. + * + * @psalm-suppress MissingClosureParamType + */ + function ( $args ) use ( $c ): array { + $axo_applies = $c->get( 'axo.service.axo-applies' ); + assert( $axo_applies instanceof AxoApplies ); + + if ( $axo_applies->should_render_fastlane() ) { + $args['ppcp_fastlane_error'] = '1'; + } + return $args; + }, + ); + // Remove Fastlane on the Pay for Order page. add_filter( 'woocommerce_available_payment_gateways', diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/MiniCartBootstrap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/MiniCartBootstrap.js index d6006d3022..c0b6f39a14 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/MiniCartBootstrap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/MiniCartBootstrap.js @@ -10,8 +10,17 @@ class MiniCartBootstrap { } init() { + /* + The context coming from the server can be inaccurate because the product + context takes precedence over the mini-cart context, so we hardcode it. + */ + const miniCartConfig = { + ...PayPalCommerceGateway, + context: 'mini-cart', + }; + this.actionHandler = new CartActionHandler( - PayPalCommerceGateway, + miniCartConfig, this.errorHandler ); this.render(); diff --git a/modules/ppcp-settings/services.php b/modules/ppcp-settings/services.php index 6b0f7eda1c..701b6f6ed5 100644 --- a/modules/ppcp-settings/services.php +++ b/modules/ppcp-settings/services.php @@ -562,6 +562,7 @@ * @param bool $is_enable_google_pay_eligible - Show if merchant has Google Pay capability but hasn't enabled the gateway. * @param bool $is_enable_installments_eligible - Show if merchant has installments capability and merchant country is MX. * @param bool $is_working_capital_eligible - Show if feature flag is enabled, merchant country is US and "Stay Updated" is turned On. + * @param bool $ */ return new TodosEligibilityService( $container->get( 'axo.eligible' ) && $capabilities['acdc'] && ! $gateways['axo'], // Enable Fastlane. @@ -583,7 +584,8 @@ $container->get( 'applepay.eligible' ) && $capabilities['apple_pay'] && ! $gateways['apple_pay'], // Enable Apple Pay. $container->get( 'googlepay.eligible' ) && $capabilities['google_pay'] && ! $gateways['google_pay'], ! $capabilities['installments'] && 'MX' === $container->get( 'settings.data.general' )->get_merchant_country(), // Enable Installments for Mexico. - $is_working_capital_feature_flag_enabled && $is_working_capital_eligible // Enable Working Capital. + $is_working_capital_feature_flag_enabled && $is_working_capital_eligible, // Enable Working Capital. + $capabilities['apm'] // Sign up for Pay with Crypto. ); }, 'settings.rest.features' => static function ( ContainerInterface $container ): FeaturesRestEndpoint { diff --git a/modules/ppcp-settings/src/Data/Definition/TodosDefinition.php b/modules/ppcp-settings/src/Data/Definition/TodosDefinition.php index 40b0ed059f..54bf8e6188 100644 --- a/modules/ppcp-settings/src/Data/Definition/TodosDefinition.php +++ b/modules/ppcp-settings/src/Data/Definition/TodosDefinition.php @@ -74,7 +74,7 @@ public function get(): array { 'section' => 'ppcp-axo-gateway', 'highlight' => 'ppcp-axo-gateway', ), - 'priority' => 1, + 'priority' => 2, ), 'enable_pay_later_messaging' => array( 'title' => __( 'Enable Pay Later messaging', 'woocommerce-paypal-payments' ), @@ -246,6 +246,18 @@ public function get(): array { ), ); + $todo_items['pwc_promo'] = array( + 'title' => __( 'Pay with Crypto is coming to your store!', 'woocommerce-paypal-payments' ), + 'description' => __( 'Sign up now to let customers pay with crypto immediately when it becomes available before the holidays and get 0.99% fees through July 2026', 'woocommerce-paypal-payments' ), + 'isEligible' => $eligibility_checks['pwc_promo'], + 'action' => array( + 'type' => 'external', + 'url' => 'https://www.paypal.com/bizsignup/add-product?product=CRYPTO_PYMTS', + 'completeOnClick' => true, + ), + 'priority' => 0, + ); + $todo_items['check_settings_after_migration'] = array( 'title' => __( "You're now using the new PayPal Payments interface!", 'woocommerce-paypal-payments' ), 'description' => __( 'Complete the items below to ensure your payment configuration is optimized for your store.', 'woocommerce-paypal-payments' ), @@ -254,7 +266,7 @@ public function get(): array { 'type' => 'tab', 'tab' => 'overview', ), - 'priority' => 0, + 'priority' => 1, ); return $todo_items; diff --git a/modules/ppcp-settings/src/Service/TodosEligibilityService.php b/modules/ppcp-settings/src/Service/TodosEligibilityService.php index 61dc2eb07e..73c652cb06 100644 --- a/modules/ppcp-settings/src/Service/TodosEligibilityService.php +++ b/modules/ppcp-settings/src/Service/TodosEligibilityService.php @@ -131,6 +131,13 @@ class TodosEligibilityService { private bool $is_working_capital_eligible; + /** + * Whether signing up for Pay is Crypto is eligible. + * + * @var bool + */ + private bool $is_pwc_promo_eligible; + /** * Constructor. * @@ -151,6 +158,7 @@ class TodosEligibilityService { * @param bool $is_enable_google_pay_eligible Whether enabling Google Pay is eligible. * @param bool $is_enable_installments_eligible Whether enabling Installments is eligible. * @param bool $is_working_capital_eligible Whether applying for Working Capital is eligible. + * @param bool $is_pwc_promo_eligible Whether signing up for Pay with Crypto is eligible. */ public function __construct( bool $is_fastlane_eligible, @@ -169,7 +177,8 @@ public function __construct( bool $is_enable_apple_pay_eligible, bool $is_enable_google_pay_eligible, bool $is_enable_installments_eligible, - bool $is_working_capital_eligible + bool $is_working_capital_eligible, + bool $is_pwc_promo_eligible ) { $this->is_fastlane_eligible = $is_fastlane_eligible; $this->is_pay_later_messaging_eligible = $is_pay_later_messaging_eligible; @@ -188,6 +197,7 @@ public function __construct( $this->is_enable_google_pay_eligible = $is_enable_google_pay_eligible; $this->is_enable_installments_eligible = $is_enable_installments_eligible; $this->is_working_capital_eligible = $is_working_capital_eligible; + $this->is_pwc_promo_eligible = $is_pwc_promo_eligible; } /** @@ -214,6 +224,7 @@ public function get_eligibility_checks(): array { 'enable_google_pay' => fn() => $this->is_enable_google_pay_eligible, 'enable_installments' => fn() => $this->is_enable_installments_eligible, 'apply_for_working_capital' => fn() => $this->is_working_capital_eligible, + 'pwc_promo' => fn() => $this->is_pwc_promo_eligible, ); } } diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 021b539f12..0e406c7721 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -2199,6 +2199,21 @@ static function ( ContainerInterface $container ): DisplayManager { ); return array( + $inbox_note_factory->create_note( + __( 'Pay with Crypto is coming to your store!', 'woocommerce-paypal-payments' ), + __( 'Sign up now to let customers pay with crypto immediately when it becomes available before the holidays and get 0.99% fees through July 2026.', 'woocommerce-paypal-payments' ), + Note::E_WC_ADMIN_NOTE_INFORMATIONAL, + 'ppcp-pwc-promo-inbox-note', + Note::E_WC_ADMIN_NOTE_UNACTIONED, + true, + new InboxNoteAction( + 'sign_up_now', + __( 'Sign up now', 'woocommerce-paypal-payments' ), + 'https://www.paypal.com/bizsignup/add-product?product=CRYPTO_PYMTS', + Note::E_WC_ADMIN_NOTE_UNACTIONED, + true + ) + ), $inbox_note_factory->create_note( __( 'PayPal Working Capital', 'woocommerce-paypal-payments' ), __( 'Fast funds with payments that flex with your PayPal sales The PayPal Working Capital business loan is primarily based on your PayPal account history. Apply for $1,000-$200,000 (and up to $300,000 for repeat borrowers) with no credit check.† If approved, loans are funded in minutes.', 'woocommerce-paypal-payments' ), diff --git a/modules/ppcp-wc-gateway/src/Endpoint/ReturnUrlEndpoint.php b/modules/ppcp-wc-gateway/src/Endpoint/ReturnUrlEndpoint.php index 08170494bb..294055857f 100644 --- a/modules/ppcp-wc-gateway/src/Endpoint/ReturnUrlEndpoint.php +++ b/modules/ppcp-wc-gateway/src/Endpoint/ReturnUrlEndpoint.php @@ -175,12 +175,19 @@ function ( $allowed_hosts ): array { } /** - * Get checkout URL with Fastlane error parameter. + * Get checkout URL with additional error parameters. * - * @return string + * Applies the 'ppcp_return_url_error_args' filter to allow external modules to add error parameters. + * + * @return string Checkout URL with error query arguments, if any. */ private function get_checkout_url_with_error(): string { - return add_query_arg( 'ppcp_fastlane_error', '1', wc_get_checkout_url() ); + $url = wc_get_checkout_url(); + $args = apply_filters( 'ppcp_return_url_error_args', array(), $this ); + if ( ! empty( $args ) ) { + $url = add_query_arg( $args, $url ); + } + return $url; } /** diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php index bd8bc9388b..a5ea7db15e 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php @@ -432,12 +432,16 @@ public function get_title() { */ public function get_description() { $gateway_settings = get_option( $this->get_option_key(), array() ); + $description = array_key_exists( 'description', $gateway_settings ) ? $gateway_settings['description'] : $this->description; - if ( array_key_exists( 'description', $gateway_settings ) ) { - return $gateway_settings['description']; - } - - return $this->description; + /** + * Filters the gateway description. + * + * @param string $description Gateway description (already sanitized with wp_kses_post). + * @param PayPalGateway $gateway Gateway instance. + * @return string Filtered gateway description. + */ + return apply_filters( 'woocommerce_paypal_payments_gateway_description', wp_kses_post( $description ), $this ); } /** diff --git a/tests/inc/inpsyde/.gitignore b/tests/inc/inpsyde/.gitignore new file mode 100644 index 0000000000..f7a6ed3134 --- /dev/null +++ b/tests/inc/inpsyde/.gitignore @@ -0,0 +1,4 @@ +* +!composer.json +!composer.lock +!.gitignore diff --git a/tests/inc/inpsyde/composer.json b/tests/inc/inpsyde/composer.json new file mode 100644 index 0000000000..4d5b9d0781 --- /dev/null +++ b/tests/inc/inpsyde/composer.json @@ -0,0 +1,20 @@ +{ + "name": "woocommerce-paypal-payments/tests", + "description": "Private test dependencies for WooCommerce PayPal Payments tests", + "type": "project", + "repositories": { + "packagist.org": false, + "private-packagist": { + "type": "composer", + "url": "https://repo.packagist.com/inpsyde/" + } + }, + "require": { + "inpsyde-mirror/woocommerce-subscriptions": "^7.9" + }, + "config": { + "allow-plugins": { + "composer/installers": true + } + } +} diff --git a/tests/inc/inpsyde/composer.lock b/tests/inc/inpsyde/composer.lock new file mode 100644 index 0000000000..295f210714 --- /dev/null +++ b/tests/inc/inpsyde/composer.lock @@ -0,0 +1,199 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "6ad1aceb7a63df87161c924efd379cc4", + "packages": [ + { + "name": "composer/installers", + "version": "v2.3.0", + "source": { + "type": "git", + "url": "https://github.com/composer/installers.git", + "reference": "12fb2dfe5e16183de69e784a7b84046c43d97e8e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/installers/zipball/12fb2dfe5e16183de69e784a7b84046c43d97e8e", + "reference": "12fb2dfe5e16183de69e784a7b84046c43d97e8e", + "shasum": "", + "mirrors": [ + { + "url": "https://repo.packagist.com/inpsyde/dists/%package%/%version%/r%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "composer/composer": "^1.10.27 || ^2.7", + "composer/semver": "^1.7.2 || ^3.4.0", + "phpstan/phpstan": "^1.11", + "phpstan/phpstan-phpunit": "^1", + "symfony/phpunit-bridge": "^7.1.1", + "symfony/process": "^5 || ^6 || ^7" + }, + "type": "composer-plugin", + "extra": { + "class": "Composer\\Installers\\Plugin", + "branch-alias": { + "dev-main": "2.x-dev" + }, + "plugin-modifies-install-path": true + }, + "autoload": { + "psr-4": { + "Composer\\Installers\\": "src/Composer/Installers" + } + }, + "notification-url": "https://repo.packagist.com/inpsyde/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kyle Robinson Young", + "email": "kyle@dontkry.com", + "homepage": "https://github.com/shama" + } + ], + "description": "A multi-framework Composer library installer", + "homepage": "https://composer.github.io/installers/", + "keywords": [ + "Dolibarr", + "Eliasis", + "Hurad", + "ImageCMS", + "Kanboard", + "Lan Management System", + "MODX Evo", + "MantisBT", + "Mautic", + "Maya", + "OXID", + "Plentymarkets", + "Porto", + "RadPHP", + "SMF", + "Starbug", + "Thelia", + "Whmcs", + "WolfCMS", + "agl", + "annotatecms", + "attogram", + "bitrix", + "cakephp", + "chef", + "cockpit", + "codeigniter", + "concrete5", + "concreteCMS", + "croogo", + "dokuwiki", + "drupal", + "eZ Platform", + "elgg", + "expressionengine", + "fuelphp", + "grav", + "installer", + "itop", + "known", + "kohana", + "laravel", + "lavalite", + "lithium", + "magento", + "majima", + "mako", + "matomo", + "mediawiki", + "miaoxing", + "modulework", + "modx", + "moodle", + "osclass", + "pantheon", + "phpbb", + "piwik", + "ppi", + "processwire", + "puppet", + "pxcms", + "reindex", + "roundcube", + "shopware", + "silverstripe", + "sydes", + "sylius", + "tastyigniter", + "wordpress", + "yawik", + "zend", + "zikula" + ], + "support": { + "issues": "https://github.com/composer/installers/issues", + "source": "https://github.com/composer/installers/tree/v2.3.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-06-24T20:46:46+00:00" + }, + { + "name": "inpsyde-mirror/woocommerce-subscriptions", + "version": "7.9.0", + "dist": { + "type": "zip", + "url": "https://packagist.inpsyde.com/satispress/woocommerce-subscriptions/7.9.0", + "shasum": "b18c72ed8041267e08298f3bfbed3956bdd3cc3c", + "mirrors": [ + { + "url": "https://repo.packagist.com/inpsyde/dists/%package%/%version%/r%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "composer/installers": "^1.0 || ^2.0" + }, + "type": "wordpress-plugin", + "notification-url": "https://repo.packagist.com/inpsyde/downloads/", + "authors": [ + { + "name": "WooCommerce", + "homepage": "https://woocommerce.com/" + } + ], + "description": "Sell products and services with recurring payments in your WooCommerce Store.", + "homepage": "https://www.woocommerce.com/products/woocommerce-subscriptions/" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/tests/qa-legacy-ui/.gitignore b/tests/qa-legacy-ui/.gitignore index ba47d3537d..3cd9c982b2 100644 --- a/tests/qa-legacy-ui/.gitignore +++ b/tests/qa-legacy-ui/.gitignore @@ -10,4 +10,6 @@ package-lock.json .env* !.env.example -.DS_Store \ No newline at end of file +.DS_Store + +woocommerce-subscriptions.zip diff --git a/tests/qa-legacy-ui/package.json b/tests/qa-legacy-ui/package.json index 92f66f39a8..a90a4fab63 100644 --- a/tests/qa-legacy-ui/package.json +++ b/tests/qa-legacy-ui/package.json @@ -3,10 +3,7 @@ "version": "1.0.0", "description": "Monorepo for Playwright tests", "main": "index.js", - "repository": { - "type": "git", - "url": "git+https://github.com/inpsyde/playwright-tests.git" - }, + "repository": "https://github.com/woocommerce/woocommerce-paypal-payments", "author": { "name": "Inpsyde GmbH", "email": "hello@inpsyde.com", diff --git a/tests/qa-legacy-ui/resources/files/woocommerce-subscriptions.zip b/tests/qa-legacy-ui/resources/files/woocommerce-subscriptions.zip deleted file mode 100644 index f9efa2a613..0000000000 Binary files a/tests/qa-legacy-ui/resources/files/woocommerce-subscriptions.zip and /dev/null differ diff --git a/tests/qa-legacy-ui/tests/03-plugin-settings/connection.spec.ts b/tests/qa-legacy-ui/tests/03-plugin-settings/connection.spec.ts index f6d0103028..4c668bfd49 100644 --- a/tests/qa-legacy-ui/tests/03-plugin-settings/connection.spec.ts +++ b/tests/qa-legacy-ui/tests/03-plugin-settings/connection.spec.ts @@ -17,7 +17,7 @@ test.describe( 'Сonnection', () => { const disconnectButton = connection.disconnectAccountButton(); await expect( disconnectButton ).toBeVisible(); await disconnectButton.click(); - await expect ( connection.page ).toHaveURL( connection.url ); + await expect( connection.page ).toHaveURL( connection.url ); await expect( connection.toggleToManualCredentialInputButton() ).toBeVisible(); diff --git a/tests/qa-legacy-ui/tests/03-plugin-settings/pay-later.spec.ts b/tests/qa-legacy-ui/tests/03-plugin-settings/pay-later.spec.ts index 602387162a..cacc0310d3 100644 --- a/tests/qa-legacy-ui/tests/03-plugin-settings/pay-later.spec.ts +++ b/tests/qa-legacy-ui/tests/03-plugin-settings/pay-later.spec.ts @@ -34,7 +34,9 @@ test.describe( 'Pay Later', async () => { test.beforeAll( async ( { payLater, standardPayments } ) => { await payLater.setup( { enableGateway: true } ); await standardPayments.visit(); - await standardPayments.disableAlternativePaymentMethods( [ 'Venmo' ] ); + await standardPayments.disableAlternativePaymentMethods( [ + 'Venmo', + ] ); await standardPayments.saveChanges(); } ); diff --git a/tests/qa-legacy-ui/tests/08-refund/_test-scenarios/refund.ts b/tests/qa-legacy-ui/tests/08-refund/_test-scenarios/refund.ts index 7f58619e3f..a20a70c3da 100644 --- a/tests/qa-legacy-ui/tests/08-refund/_test-scenarios/refund.ts +++ b/tests/qa-legacy-ui/tests/08-refund/_test-scenarios/refund.ts @@ -55,7 +55,9 @@ export const testRefund = ( tests ) => { ).toHaveText( `-${ formatMoney( 0, tested.currency ) }` ); await expect( wooCommerceOrderEdit.totalAvailableToRefund() - ).toHaveText( formatMoney( Number( refundAvailable ), tested.currency ) ); + ).toHaveText( + formatMoney( Number( refundAvailable ), tested.currency ) + ); await wooCommerceOrderEdit.makePayPalRefund( refundAmount ); await wooCommerceOrderEdit.assertUrl( order.id ); @@ -63,7 +65,10 @@ export const testRefund = ( tests ) => { wooCommerceOrderEdit.refundNumber() ).toContainText( `Refund #` ); await expect( wooCommerceOrderEdit.refundAmount() ).toHaveText( - `-${ formatMoney( Number( refundAmount ), tested.currency ) }` + `-${ formatMoney( + Number( refundAmount ), + tested.currency + ) }` ); order = await wooCommerceApi.getOrder( order.id ); diff --git a/tests/qa-legacy-ui/utils/admin/connection.ts b/tests/qa-legacy-ui/utils/admin/connection.ts index 28a7659f56..d96f6a9118 100644 --- a/tests/qa-legacy-ui/utils/admin/connection.ts +++ b/tests/qa-legacy-ui/utils/admin/connection.ts @@ -8,6 +8,9 @@ import urls from '../urls'; */ import { PcpMerchant } from '../../resources'; import { generateRandomString } from '../helpers'; +/** + * External dependencies + */ import { expect } from 'playwright/test'; export class Connection extends PcpSettingsPage { @@ -53,8 +56,7 @@ export class Connection extends PcpSettingsPage { this.page.locator( '#ppcp-merchant_email_sandbox' ); sandboxMerchantIdInput = () => this.page.locator( '#ppcp-merchant_id_sandbox' ); - sandboxClientIdInput = () => - this.page.locator( '#ppcp-client_id_sandbox' ); + sandboxClientIdInput = () => this.page.locator( '#ppcp-client_id_sandbox' ); sandboxSecretKeyInput = () => this.page.locator( 'input[name="ppcp[client_secret_sandbox]"]' ); @@ -124,34 +126,34 @@ export class Connection extends PcpSettingsPage { this.toggleToManualCredentialInputButton(); await expect( toggleToManualCredentialInputButton ).toBeVisible(); await toggleToManualCredentialInputButton.click( { force: true } ); - + const sandboxCheckbox = this.sandboxCheckbox(); await expect( sandboxCheckbox ).toBeVisible(); - await sandboxCheckbox.check({ force: true }); + await sandboxCheckbox.check( { force: true } ); await expect( this.sandboxCheckbox() ).toBeChecked(); - + const sandboxEmailAddressInput = this.sandboxEmailAddressInput(); await expect( sandboxEmailAddressInput ).toBeVisible(); await sandboxEmailAddressInput.fill( merchant.email ); - + const sandboxMerchantIdInput = this.sandboxMerchantIdInput(); await expect( sandboxMerchantIdInput ).toBeVisible(); await sandboxMerchantIdInput.fill( merchant.account_id ); - + const sandboxClientIdInput = this.sandboxClientIdInput(); await expect( sandboxClientIdInput ).toBeVisible(); await sandboxClientIdInput.fill( merchant.client_id ); - + const sandboxSecretKeyInput = this.sandboxSecretKeyInput(); await expect( sandboxSecretKeyInput ).toBeVisible(); await sandboxSecretKeyInput.fill( merchant.client_secret ); - + const saveChangesButton = this.saveChangesButton(); await expect( saveChangesButton ).toBeVisible(); await saveChangesButton.click(); await this.page.waitForLoadState(); // make sure Connection page has been loaded: - + await expect( this.disconnectAccountButton() ).toBeVisible(); await this.updateInvoicePrefix(); }; diff --git a/tests/qa-legacy-ui/utils/frontend/paypal-popup.ts b/tests/qa-legacy-ui/utils/frontend/paypal-popup.ts index afd8606fa6..48c958bfed 100644 --- a/tests/qa-legacy-ui/utils/frontend/paypal-popup.ts +++ b/tests/qa-legacy-ui/utils/frontend/paypal-popup.ts @@ -12,18 +12,38 @@ export class PayPalPopup { // Locators + usePasswordInsteadButton = () => + this.popup.getByRole( 'button', { name: 'Use Password Instead' } ); loginWithPasswordInsteadLink = () => this.popup.getByRole( 'link', { name: 'Log in with a password instead', } ); loginWithYourPasswordLink = () => this.popup.getByRole( 'link', { name: 'Login with password' } ); + loginWithPasswordInstead = () => + this.loginWithPasswordInsteadLink() + .or( this.usePasswordInsteadButton() ) + .or( this.loginWithYourPasswordLink() ); tryAnotherWayLink = () => this.popup.getByRole( 'link', { name: 'Try another way' } ); loginInput = () => this.popup.locator( '[name="login_email"]' ); passwordInput = () => this.popup.locator( '[name="login_password"]' ); - nextButton = () => this.popup.locator( '#btnNext' ); - loginButton = () => this.popup.locator( '#btnLogin' ); + nextButton = () => + this.popup + .locator( '#btnNext' ) + .or( + this.popup.locator( + 'button[data-atomic-wait-intent="Submit_Email"]' + ) + ); + loginButton = () => + this.popup + .locator( '#btnLogin' ) + .or( + this.popup.locator( + 'button[data-atomic-wait-intent="Submit_Password"]' + ) + ); submitPaymentButton = () => this.popup .locator( '#payment-submit-btn' ) @@ -47,6 +67,8 @@ export class PayPalPopup { 'You have read and agree to the Loan Agreement' ); agreeAndApplyButton = () => this.payLaterIframe().getByTestId( 'apply' ); + tryAgainLink = () => this.popup.getByRole( 'link', { name: 'Try again' } ); + // Actions /** @@ -60,10 +82,10 @@ export class PayPalPopup { await this.loginInput().fill( email ); - await this.tryLoginWithPasswordInstead(); - await this.tryClickNext(); + await this.tryLoginWithPasswordInstead(); + await this.tryAnotherWay(); await this.passwordInput().fill( password ); @@ -76,11 +98,12 @@ export class PayPalPopup { */ tryLoginWithPasswordInstead = async () => { try { - await this.loginWithPasswordInsteadLink().waitFor( { + await this.loginWithPasswordInstead().waitFor( { state: 'visible', timeout: 4000, } ); - await this.loginWithPasswordInsteadLink().click(); + await this.loginWithPasswordInstead().click(); + await this.popup.waitForLoadState(); } catch {} }; @@ -95,6 +118,7 @@ export class PayPalPopup { timeout: 4000, } ); await this.nextButton().click(); + await this.popup.waitForLoadState(); } catch {} }; @@ -118,15 +142,11 @@ export class PayPalPopup { await expect( this.loadSpinnerContainer() ).not.toBeVisible(); while ( ! this.popup.isClosed() ) { - const submitButton = this.submitPaymentButton(); - if ( ! ( await submitButton.isVisible() ) ) { - break; // No visible button, exit - } - // Race click with popup closure try { await Promise.race( [ - submitButton.click(), + this.submitPaymentButton().click(), + this.tryAgainLink().click(), this.popup.waitForEvent( 'close', { timeout: 30 * 1000 } ), // Short timeout to prevent hang ] ); } catch ( error ) { diff --git a/tests/qa-legacy-ui/utils/frontend/paypal-ui.ts b/tests/qa-legacy-ui/utils/frontend/paypal-ui.ts index 79c1dfdb4a..b21bb348c3 100644 --- a/tests/qa-legacy-ui/utils/frontend/paypal-ui.ts +++ b/tests/qa-legacy-ui/utils/frontend/paypal-ui.ts @@ -412,13 +412,15 @@ export class PayPalUI { let popup: PayPalPopup; switch ( data.payment.method ) { case 'PayPal': - if( await this.payPalGateway().isVisible() ) { + if ( await this.payPalGateway().isVisible() ) { await this.payPalGateway().click(); } // pay with vaulted account if ( data.payment.isVaulted ) { await expect( this.payPalButton() ).toBeVisible(); - await this.assertVaultedPaymentMethodIsDisplayed( data.payment ); + await this.assertVaultedPaymentMethodIsDisplayed( + data.payment + ); await this.payPalButton().click(); break; } @@ -436,7 +438,7 @@ export class PayPalUI { break; case 'PayLater': - if( await this.payPalGateway().isVisible() ) { + if ( await this.payPalGateway().isVisible() ) { await this.payPalGateway().click(); } popup = await this.openPayLaterPopup(); diff --git a/tests/qa-legacy-ui/utils/utils.ts b/tests/qa-legacy-ui/utils/utils.ts index 4605a1bd83..3dac0476b8 100644 --- a/tests/qa-legacy-ui/utils/utils.ts +++ b/tests/qa-legacy-ui/utils/utils.ts @@ -201,7 +201,6 @@ export class Utils { ); }; - /** * Onboard with Pay Upon Invoice (PUI) * Only for German merchant @@ -214,37 +213,42 @@ export class Utils { ); const response = await this.requestUtils.request.post( - '/?wc-ajax=ppc-update-signup-links', { + '/?wc-ajax=ppc-update-signup-links', + { data: { nonce, settings: { 'ppcp-onboarding-pui': true }, - } - }, + }, + } ); const result = response.ok(); await expect( result ).toBeTruthy(); return result; - } + }; /** * Connects merchant via form post request * + * @param merchant + * @param options */ connectMerchant = async ( merchant: PcpMerchant, options = { enablePayUponInvoice: false, } - ) => { + ) => { const ppcpNonce = await this.requestUtils.getRegexMatchValueOnPage( urls.pcp.connection, // ); - - const wpnonce = await this.requestUtils.getPageNonce( urls.pcp.connection ); - + + const wpnonce = await this.requestUtils.getPageNonce( + urls.pcp.connection + ); + const formData = { - '_wpnonce': wpnonce, + _wpnonce: wpnonce, 'ppcp-nonce': ppcpNonce, 'ppcp[sandbox_on]': '1', 'ppcp[merchant_email_production]': '', @@ -260,19 +264,22 @@ export class Utils { 'ppcp[stay_updated]': '1', 'ppcp[subtotal_mismatch_behavior]': 'extra_line', 'ppcp[subtotal_mismatch_line_name]': '', - 'save': 'Save changes', + save: 'Save changes', }; - if( options.enablePayUponInvoice === true ) { - formData[ 'ppcp_onboarding_dcc' ] = 'basic'; + if ( options.enablePayUponInvoice === true ) { + formData.ppcp_onboarding_dcc = 'basic'; await this.onboardWithPui(); } - - const response = await this.requestUtils.submitPageForm( urls.pcp.connection, formData ); + + const response = await this.requestUtils.submitPageForm( + urls.pcp.connection, + formData + ); const result = response.ok(); await expect( result ).toBeTruthy(); return result; - } + }; /** * Disconnects merchant via form post request @@ -283,9 +290,11 @@ export class Utils { urls.pcp.connection, // ); - const wpnonce = await this.requestUtils.getPageNonce( urls.pcp.connection ); + const wpnonce = await this.requestUtils.getPageNonce( + urls.pcp.connection + ); const formData = { - '_wpnonce': wpnonce, + _wpnonce: wpnonce, 'ppcp-nonce': ppcpNonce, 'ppcp[merchant_email_production]': '', 'ppcp[merchant_id_production]': '', @@ -300,13 +309,16 @@ export class Utils { 'ppcp[stay_updated]': '1', 'ppcp[subtotal_mismatch_behavior]': 'extra_line', 'ppcp[subtotal_mismatch_line_name]': '', - 'save': 'Save changes', + save: 'Save changes', }; - const response = await this.requestUtils.submitPageForm( urls.pcp.connection, formData ); + const response = await this.requestUtils.submitPageForm( + urls.pcp.connection, + formData + ); const result = response.ok(); await expect( result ).toBeTruthy(); return result; - } + }; /** * Clear PCP DB via request @@ -320,12 +332,12 @@ export class Utils { const response = await this.requestUtils.request.post( '/?wc-ajax=ppcp-clear-db', - { data: { nonce } }, + { data: { nonce } } ); const result = response.ok(); await expect( result ).toBeTruthy(); return result; - } + }; /** * Enable PayPal funding source @@ -460,10 +472,9 @@ export class Utils { if ( data.clearPCPDB ) { // Make sure merchant is connected to clear PCP DB await this.disconnectMerchant(); - await this.connectMerchant( - data.merchant, - { enablePayUponInvoice: !!data.enablePayUponInvoice }, - ); + await this.connectMerchant( data.merchant, { + enablePayUponInvoice: !! data.enablePayUponInvoice, + } ); await this.clearPcpDb(); } @@ -473,10 +484,9 @@ export class Utils { } await this.disconnectMerchant(); - await this.connectMerchant( - data.merchant, - { enablePayUponInvoice: !!data.enablePayUponInvoice }, - ); + await this.connectMerchant( data.merchant, { + enablePayUponInvoice: !! data.enablePayUponInvoice, + } ); } if ( data.standardPayments ) { diff --git a/tests/qa/.env.example b/tests/qa/.env.example index 283d4e1cfa..f90513958f 100644 --- a/tests/qa/.env.example +++ b/tests/qa/.env.example @@ -1,31 +1,60 @@ # JDoe test single site, PHP x.x # playwright-utils config -WP_BASE_URL='https://test.com' # baseUrl without trailing slash -WP_USERNAME=*** -WP_PASSWORD=*** -WP_BASIC_AUTH_USER=*** -WP_BASIC_AUTH_PASS=*** +WP_BASE_URL='http://localhost:8889' # WP ENV URL -- You can replace this with any URL +WP_USERNAME=admin +WP_PASSWORD=password +WP_BASIC_AUTH_USER=admin +WP_BASIC_AUTH_PASS=password STORAGE_STATE_PATH='./storage-states' STORAGE_STATE_PATH_ADMIN='./storage-states/admin.json' +WORDPRESS_DB_USER=root +WORDPRESS_DB_PASSWORD=passsword # WooCommerce -WC_API_KEY=*** -WC_API_SECRET=*** -# WC_DEFAULT_COUNTRY=usa -# WC_DEFAULT_CURRENCY=usa +# If WC_API_KEY and WC_API_SECRET are empty and WooCommerce has not set any keys, they will be populated automatically. +WC_API_KEY= +WC_API_SECRET= +WC_DEFAULT_COUNTRY=usa +WC_DEFAULT_CURRENCY=USD # Xray in Jira XRAY_CLIENT_ID= XRAY_CLIENT_SECRET= # TEST_EXEC_KEY='' -# MERCHANT_USA_EMAIL= -# MERCHANT_USA_CLIENT_ID= -# MERCHANT_USA_CLIENT_SECRET= -# MERCHANT_USA_ACCOUNT_ID= +PAYPAL_PERSONAL_EMAIL_DE= +PAYPAL_PERSONAL_PASS_DE= -# PAYPAL_PERSONAL_EMAIL_US= -# PAYPAL_PERSONAL_PASS_US= +PAYPAL_PERSONAL_EMAIL_US= +PAYPAL_PERSONAL_PASS_US= + +PAYPAL_PERSONAL_EMAIL_MX= +PAYPAL_PERSONAL_PASS_MX= + +MERCHANT_USA_EMAIL= +MERCHANT_USA_CLIENT_ID= +MERCHANT_USA_CLIENT_SECRET= +MERCHANT_USA_ACCOUNT_ID= + +MERCHANT_DE_EMAIL= +MERCHANT_DE_CLIENT_ID= +MERCHANT_DE_CLIENT_SECRET= +MERCHANT_DE_ACCOUNT_ID= + +MERCHANT_MX_EMAIL= +MERCHANT_MX_CLIENT_ID= +MERCHANT_MX_CLIENT_SECRET= +MERCHANT_MX_ACCOUNT_ID= + +MERCHANT_INVALID_EMAIL= +MERCHANT_INVALID_CLIENT_ID= +MERCHANT_INVALID_CLIENT_SECRET= +MERCHANT_INVALID_ACCOUNT_ID= + +MERCHANT_USA_NOREF_EMAIL='' +MERCHANT_USA_NOREF_CLIENT_ID='' +MERCHANT_USA_NOREF_CLIENT_SECRET='' +MERCHANT_USA_NOREF_ACCOUNT_ID='' # FASTLANE_EMAIL_RYAN= diff --git a/tests/qa/.gitignore b/tests/qa/.gitignore index f512d5b225..55cc63d1ef 100644 --- a/tests/qa/.gitignore +++ b/tests/qa/.gitignore @@ -7,7 +7,10 @@ playwright/.cache storage-states test-results package-lock.json - .env* !.env.example .DS_Store +/resources/files/woocommerce-subscriptions.zip + + + diff --git a/tests/qa/.wp-env.json b/tests/qa/.wp-env.json new file mode 100644 index 0000000000..e06c0643f6 --- /dev/null +++ b/tests/qa/.wp-env.json @@ -0,0 +1,14 @@ +{ + "phpVersion": "8.0", + "plugins": [ + "https://downloads.wordpress.org/plugin/wp-debugging.2.12.2.zip", + "./resources/e2e-snippets" + ], + "mappings": { + "wp-cli.yml": "./wp-cli.yml" + }, + "lifecycleScripts": { + "afterStart": "node bin/test-env-setup.js", + "afterClean": "node bin/test-env-clean.js" + } +} diff --git a/tests/qa/README.md b/tests/qa/README.md index ba1e6087d2..a9fec897a8 100644 --- a/tests/qa/README.md +++ b/tests/qa/README.md @@ -19,79 +19,39 @@ Detailed information about current test project can be found in [docs](./docs/RE ## Local installation -1. In VSCode open the terminal and clone PCP repository to your local PC: +1. Clone repository locally: ```bash git clone https://github.com/woocommerce/woocommerce-paypal-payments.git ``` -2. Change directory to newly cloned repo: - - ```bash - cd woocommerce-paypal-payments - ``` - -3. (Temporary, till autotests are not yet merged into main branch) Switch to `qa` branch: - - ```bash - git fetch origin - git checkout qa - ``` - -4. Change directory to `./tests/qa/`: - - ```bash - cd tests/qa - ``` - -## Installation of `node_modules` +## Installation of `wp env, PlayWright and PayPal plugin` > See also [@inpsyde/playwright-utils documentation](https://github.com/inpsyde/playwright-utils?tab=readme-ov-file#installation). 1. Make sure you're logged in the [Syde npm package registry](https://inpsyde.atlassian.net/wiki/spaces/AT/pages/3112894465/GitHub+Package+Registry+for+npm). -2. Make sure that `"workspaces": [ "playwright-utils" ]` node isn't present in `./tests/qa/package.json`. - -3. In the terminal change directory to `./tests/qa` and run following command: +2. In the terminal change directory to `./tests/qa` and run following command: ```bash - npm run setup:tests + npm run setup:all ``` -## Installation of `playwright-utils` for local development +This will run the next scripts: -> See also [@inpsyde/playwright-utils documentation](https://github.com/inpsyde/playwright-utils?tab=readme-ov-file#development). +- `setup:env` -- Setup `wp env` and required plugins for running tests in http://localhost:8889 +- `setup:plugin` -- Compile WooCommerce PayPal Payments plugin, generates a ZIP and moves it into resources/files to be used in tests +- `setup:tests` -- Setup required PlayWright libraries and utils -1. Add `"workspaces": [ "playwright-utils" ]` to `./tests/qa/package.json`. +## Project configuration (Devs) -2. Delete `@inpsyde/playwright-utils` from `./tests/qa/node_modules`. - -3. In the terminal change directory to `./tests/qa` and run following command: - - ```bash - git clone https://github.com/inpsyde/playwright-utils.git - ``` - - [`@inpsyde/playwright-utils`](https://github.com/inpsyde/playwright-utils) repository should be cloned as `playwright-utils` right inside the root directory of monorepo. - -4. Restart VSCode editor. This will create `playwright-utils` instance in the source control tab of VSCode editor. - -5. Run following command: - - ```bash - npm run setup:utils - ``` - -6. `@inpsyde/playwright-utils` should reappear in node_modules. Following message (coming from `tsc-watch`) should be displayed in the terminal: - - ```bash - 10:00:00 - Found 0 errors. Watching for file changes. - ``` - -7. If you plan to make changes in `playwright-utils` keep current terminal window opened and create another instance of terminal. +1. In the test project directory (`./tests/qa/`) create and configure `.env` file: +2. Set general variables following [these steps](https://github.com/inpsyde/playwright-utils?tab=readme-ov-file#env-variables). + +3. Set PayPal API keys and test credentials. See `.env.example`. The `.env` content with actual test users' credentials is [stored in 1Password](https://start.1password.com/open/i?a=UL7QZZ6P6JDVBI422AOVJXMEGU&v=uthlbcp4jkori6w6rhgxvsvfoe&i=klejf7rgcip76c7auhsnhvxcbi&h=inpsyde.1password.eu). -## Project configuration +## Project configuration (QA team) 1. [SSE setup](https://inpsyde.atlassian.net/wiki/spaces/AT/pages/3175907370/Self+Service+WordPress+Environment) - will be deprecated in Q1 of 2025. @@ -109,13 +69,11 @@ Detailed information about current test project can be found in [docs](./docs/RE 6. Additional website and WooCommerce configuration is done automatically via `setup-woocommerce` dependency project (see [`/tests/_setup/woocommerce.setup.ts`](./tests/_setup/woocommerce.setup.ts)). -## Run tests +## Running tests -To execute tests, in the terminal, navigate to the __qa__ directory of the project (e.g. `cd tests/qa`) and run following command: - -```bash -npx playwright test --project=all -``` +- `npm run tests:all` -- Runs all the tests +- `npm run tests:critical` -- Runs all the critical tests +- `npm run tests:onboarding` -- Runs all the onboarding tests ### Additional options to run tests from command line diff --git a/tests/qa/bin/test-env-clean.js b/tests/qa/bin/test-env-clean.js new file mode 100644 index 0000000000..0ddd50435f --- /dev/null +++ b/tests/qa/bin/test-env-clean.js @@ -0,0 +1,34 @@ +#!/usr/bin/env node +const { execSync } = require('child_process'); + +const commands = [ + { + description: 'Activate storefront theme', + command: 'wp-env run tests-cli wp theme deactivate storefront' + }, + { + description: 'Uninstall WooCommerce Payments', + command: 'wp-env run tests-cli -- wp plugin delete woocommerce-payments' + }, + { + description: 'Uninstall WooCommerce', + command: 'wp-env run tests-cli -- wp plugin delete woocommerce' + }, +]; + +console.log('Cleaning test environment...\n'); + +commands.forEach((item, index) => { + try { + console.log(`${index + 1}. ${item.description}`); + execSync(item.command, { stdio: 'inherit' }); + console.log('✅ Success\n'); + } catch (error) { + console.error(`❌ Failed: ${item.description}`); + console.error(`Command: ${item.command}`); + console.error(`Error: ${error.message}\n`); + process.exit(1); + } +}); + +console.log('🧹 Test environment clean complete!'); diff --git a/tests/qa/bin/test-env-setup.js b/tests/qa/bin/test-env-setup.js new file mode 100644 index 0000000000..d26b28bc08 --- /dev/null +++ b/tests/qa/bin/test-env-setup.js @@ -0,0 +1,61 @@ +#!/usr/bin/env node +const { execSync } = require('child_process'); + +const commands = [ + { + description: 'Install storefront theme', + command: 'wp-env run tests-cli -- wp theme install storefront' + }, + { + description: 'Activate storefront theme', + command: 'wp-env run tests-cli -- wp theme activate storefront' + }, + { + description: 'Install WooCommerce', + command: 'wp-env run tests-cli -- wp plugin install woocommerce' + }, + { + description: 'Activate WooCommerce', + command: 'wp-env run tests-cli -- wp plugin activate woocommerce' + }, + { + description: 'Install WooCommerce Payments', + command: 'wp-env run tests-cli -- wp plugin install woocommerce-payments' + }, + { + description: 'Update URL structure', + command: 'wp-env run tests-cli -- wp rewrite structure "/%postname%/" --hard' + }, + { + description: 'Update Blog Name', + command: 'wp-env run tests-cli -- wp option update blogname "WooCommerce PayPal Payments E2E Test Suite"' + }, + { + description: 'Set the store as live', + command: 'wp-env run tests-cli -- wp option update woocommerce_coming_soon "no"' + }, + { + description: 'PayPal - Set new UI (merchant flag)', + command: 'wp-env run tests-cli -- wp option update woocommerce-ppcp-is-new-merchant "yes"' + }, + { + description: 'PayPal - Set new UI (disable old UI)', + command: 'wp-env run tests-cli -- wp option update woocommerce_ppcp-settings-should-use-old-ui "no"' + } +]; + +console.log('Starting test environment setup...\n'); + +commands.forEach((item, index) => { + try { + console.log(`${index + 1}. ${item.description}`); + execSync(item.command, { stdio: 'inherit' }); + console.log('✅ Success\n'); + } catch (error) { + console.error(`❌ Failed: ${item.description}`); + console.error(`Command: ${item.command}`); + console.error(`Error: ${error.message}\n`); + } +}); + +console.log('🎉 Test environment setup complete!'); diff --git a/tests/qa/package.json b/tests/qa/package.json index 78e9c14b7c..6c57a09b58 100644 --- a/tests/qa/package.json +++ b/tests/qa/package.json @@ -1,65 +1,70 @@ { - "name": "woocommerce-paypal-payments-tests", - "version": "1.0.0", - "description": "WooCommerce Paypal Payments Playwright tests", - "main": "index.js", - "repository": { - "type": "git", - "url": "git+https://github.com/inpsyde/playwright-tests.git" - }, - "author": { - "name": "Syde GmbH", - "email": "hello@syde.com", - "url": "https://syde.com/" - }, - "license": "GPL-3.0-or-later", - "bugs": { - "url": "https://github.com/woocommerce/woocommerce-paypal-payments/issues" - }, - "homepage": "https://github.com/woocommerce/woocommerce-paypal-payments#readme", - "dependencies": { - "@inpsyde/playwright-utils": "^2.7.3", - "dotenv": "^16.3.1", - "dotenv-cli": "^7.3.0", - "playwright": "^1.49.1", - "yarn": "^1.22.21" - }, - "devDependencies": { - "@percy/cli": "^1.30.6", - "@percy/playwright": "^1.0.4", - "@playwright/test": "^1.50.0", - "@types/node": "^20.8.4", - "@wordpress/scripts": "^25.0.0" - }, - "scripts": { - "setup:tests": "npm install && npx playwright install", - "setup:utils": "npm run setup:tests && cd ./playwright-utils && yarn devLocal", - "lint:md": "wp-scripts lint-md-docs ./**/*.md README.md", - "lint:md:fix": "wp-scripts lint-md-docs --fix ./**/*.md README.md", - "lint:js": "wp-scripts lint-js --resolve-plugins-relative-to ./ **/*.{ts,tsx,mjs}", - "lint:js:fix": "wp-scripts lint-js --resolve-plugins-relative-to ./ --fix **/*.{ts,tsx,mjs}", - "all": "npx playwright test --workers=1 --project \"all\"", - "setup:store:default": "npx playwright test woocommerce.setup", - "setup:checkout:block": "npx playwright test --grep=\"setup:checkout:block;\"", - "setup:checkout:classic": "npx playwright test --grep=\"setup:checkout:classic;\"", - "setup:tax:inc": "npx playwright test --grep=\"setup:tax:inc;\"", - "setup:tax:exc": "npx playwright test --grep=\"setup:tax:exc;\"", - "setup:pcp:usa": "npx playwright test --grep=\"setup:pcp:usa;\"", - "setup:pcp:germany": "npx playwright test --grep \"setup:pcp:germany;\"", - "setup:pcp:mexico": "npx playwright test --grep \"setup:pcp:mexico;\"", - "setup:pcp:usa:vaulting": "npx playwright test --grep \"setup:pcp:usa:vaulting;\"", - "setup:pcp:usa:vaulting:classic": "npx playwright test --grep \"setup:pcp:usa:vaulting:classic;\"", - "setup:pcp:usa:vaulting:subscription": "npx playwright test --grep \"setup:pcp:usa:vaulting:subscription;\"", - "setup:pcp:usa:paypal:subscription": "npx playwright test --grep \"setup:pcp:usa:paypal:subscription;\"" - }, - "eslintConfig": { - "extends": [ - "plugin:@wordpress/eslint-plugin/recommended" - ], - "rules": { - "@wordpress/dependency-group": "error", - "@wordpress/no-unsafe-wp-apis": "off", - "no-console": "off" + "name": "woocommerce-paypal-payments-tests", + "version": "1.0.0", + "description": "WooCommerce Paypal Payments Playwright tests", + "main": "index.js", + "author": { + "name": "Syde GmbH", + "email": "hello@syde.com", + "url": "https://syde.com/" + }, + "license": "GPL-3.0-or-later", + "bugs": { + "url": "https://github.com/woocommerce/woocommerce-paypal-payments/issues" + }, + "homepage": "https://github.com/woocommerce/woocommerce-paypal-payments#readme", + "dependencies": { + "@inpsyde/playwright-utils": "^2.7.3", + "dotenv": "^16.3.1", + "dotenv-cli": "^7.3.0", + "playwright": "^1.49.1", + "yarn": "^1.22.21" + }, + "devDependencies": { + "@percy/cli": "^1.30.6", + "@percy/playwright": "^1.0.4", + "@playwright/test": "^1.50.0", + "@types/node": "^20.8.4", + "@wordpress/env": "^10.30.0", + "@wordpress/scripts": "^25.0.0" + }, + "scripts": { + "setup:all": "npm run setup:env && npm run setup:plugin && npm run setup:tests", + "setup:tests": "npm install && npx playwright install", + "setup:env": "npm install && npm run setup:wc-subscriptions && wp-env start --update", + "setup:wc-subscriptions": "cd ../inc/inpsyde && composer install && cd wp-content/plugins && zip -r ./woocommerce-subscriptions.zip ./woocommerce-subscriptions && cp woocommerce-subscriptions.zip ../../../../qa/resources/files", + "setup:reset": "wp-env stop && wp-env destroy", + "setup:plugin": "cd ../.. && npm run build && mv woocommerce-paypal-payments.zip tests/qa/resources/files", + "setup:utils": "npm run setup:tests && cd ./playwright-utils && yarn devLocal", + "wp-env": "wp-env", + "lint:md": "wp-scripts lint-md-docs ./**/*.md README.md", + "lint:md:fix": "wp-scripts lint-md-docs --fix ./**/*.md README.md", + "lint:js": "wp-scripts lint-js --resolve-plugins-relative-to ./ **/*.{ts,tsx,mjs}", + "lint:js:fix": "wp-scripts lint-js --resolve-plugins-relative-to ./ --fix **/*.{ts,tsx,mjs}", + "tests:all": "npx playwright test --workers=1 --project \"all\"", + "tests:onboarding": "npx playwright test 02-onboarding/", + "tests:critical": "npx playwright test --grep=\"@Critical\"", + "setup:store:default": "npx playwright test woocommerce.setup", + "setup:checkout:block": "npx playwright test --grep=\"setup:checkout:block;\"", + "setup:checkout:classic": "npx playwright test --grep=\"setup:checkout:classic;\"", + "setup:tax:inc": "npx playwright test --grep=\"setup:tax:inc;\"", + "setup:tax:exc": "npx playwright test --grep=\"setup:tax:exc;\"", + "setup:pcp:usa": "npx playwright test --grep=\"setup:pcp:usa;\"", + "setup:pcp:germany": "npx playwright test --grep \"setup:pcp:germany;\"", + "setup:pcp:mexico": "npx playwright test --grep \"setup:pcp:mexico;\"", + "setup:pcp:usa:vaulting": "npx playwright test --grep \"setup:pcp:usa:vaulting;\"", + "setup:pcp:usa:vaulting:classic": "npx playwright test --grep \"setup:pcp:usa:vaulting:classic;\"", + "setup:pcp:usa:vaulting:subscription": "npx playwright test --grep \"setup:pcp:usa:vaulting:subscription;\"", + "setup:pcp:usa:paypal:subscription": "npx playwright test --grep \"setup:pcp:usa:paypal:subscription;\"" + }, + "eslintConfig": { + "extends": [ + "plugin:@wordpress/eslint-plugin/recommended" + ], + "rules": { + "@wordpress/dependency-group": "error", + "@wordpress/no-unsafe-wp-apis": "off", + "no-console": "off" + } } - } } diff --git a/tests/qa/resources/cards.ts b/tests/qa/resources/cards.ts index 4e2be66f0a..37b07b0801 100644 --- a/tests/qa/resources/cards.ts +++ b/tests/qa/resources/cards.ts @@ -12,6 +12,13 @@ const visa2: WooCommerce.CreditCard = { card_type: 'Visa', }; +const visaFastlane: WooCommerce.CreditCard = { + card_number: '4000000000002701', // for Ryan's fastlane only last 4 digits matter + expiration_date: '12/30', + card_cvv: '123', + card_type: 'Visa', +}; + const visa3ds: WooCommerce.CreditCard = { card_number: '4020024518402084', expiration_date: '01/30', @@ -39,6 +46,7 @@ export const cards: { } = { visa, visa2, + visaFastlane, visa3ds, mastercard, declined, diff --git a/tests/qa/resources/e2e-snippets/e2e-snippets.php b/tests/qa/resources/e2e-snippets/e2e-snippets.php new file mode 100644 index 0000000000..ca081ea351 --- /dev/null +++ b/tests/qa/resources/e2e-snippets/e2e-snippets.php @@ -0,0 +1,42 @@ + { for ( const currency of currencies ) { - test.fixme( `(${ testKey }) Settings - ${ country } - ${ currency } - Onboarding - Badge values`, async ( { - wooCommerceApi, - pcpOnboarding, - }, testInfo ) => { - await wooCommerceApi.updateGeneralSettings( { - woocommerce_default_country: wooCommerceCountryCode, - woocommerce_currency: currency, - } ); - await pcpOnboarding.visit(); - await pcpOnboarding.gotoInitialOnboardingPage(); - await pcpOnboarding - .badgeContainer() - .waitFor( { state: 'visible' } ); - await pcpOnboarding.closeAdvancedOptions(); - await pcpOnboarding.page.waitForLoadState( 'load' ); - await pcpOnboarding.snapshotLocator( - pcpOnboarding.welcomeDocsContainer(), - `${ testInfo.title } - PayPal Settings`, - { - timeout: 3000, - } - ); + test.fixme( + `(${ testKey }) Settings - ${ country } - ${ currency } - Onboarding - Badge values`, + async ( { wooCommerceApi, pcpOnboarding }, testInfo ) => { + await wooCommerceApi.updateGeneralSettings( { + woocommerce_default_country: wooCommerceCountryCode, + woocommerce_currency: currency, + } ); + await pcpOnboarding.visit(); + await pcpOnboarding.gotoInitialOnboardingPage(); + await pcpOnboarding + .badgeContainer() + .waitFor( { state: 'visible' } ); + await pcpOnboarding.closeAdvancedOptions(); + await pcpOnboarding.page.waitForLoadState( 'load' ); + await pcpOnboarding.snapshotLocator( + pcpOnboarding.welcomeDocsContainer(), + `${ testInfo.title } - PayPal Settings`, + { + timeout: 3000, + } + ); - await pcpOnboarding.activatePayPalPaymentsButton().click(); - if ( country !== 'Germany' ) { - await pcpOnboarding.businessRadio().click(); + await pcpOnboarding.activatePayPalPaymentsButton().click(); + if ( country !== 'Germany' ) { + await pcpOnboarding.businessRadio().click(); + await pcpOnboarding.continueButton().click(); + } + await pcpOnboarding.virtualCheckbox().check(); await pcpOnboarding.continueButton().click(); + await pcpOnboarding + .disableOptionalPaymentMethodsRadio() + .click(); + await pcpOnboarding.page.waitForLoadState( 'load' ); + await pcpOnboarding.snapshotLocator( + pcpOnboarding.checkoutAlternativeOptionsContainer(), + `${ testInfo.title } - Choose checkout options`, + { + timeout: 3000, + } + ); } - await pcpOnboarding.virtualCheckbox().check(); - await pcpOnboarding.continueButton().click(); - await pcpOnboarding - .disableOptionalPaymentMethodsRadio() - .click(); - await pcpOnboarding.page.waitForLoadState( 'load' ); - await pcpOnboarding.snapshotLocator( - pcpOnboarding.checkoutAlternativeOptionsContainer(), - `${ testInfo.title } - Choose checkout options`, - { - timeout: 3000, - } - ); - } ); + ); } test.afterEach( async ( {}, testInfo ) => { @@ -77,7 +77,10 @@ for ( const testData of badgeTestsData ) { } ); } ); - test.fixme( `${ testKey } | Settings - ${ country } - Onboarding - Badge values`, async () => { - getTestResultsFromFile( testKey, TEST_RESULTS_FILE ); - } ); + test.fixme( + `${ testKey } | Settings - ${ country } - Onboarding - Badge values`, + async () => { + getTestResultsFromFile( testKey, TEST_RESULTS_FILE ); + } + ); } diff --git a/tests/qa/tests/03-plugin-settings/pay-later-messaging-configurator.spec.ts b/tests/qa/tests/03-plugin-settings/pay-later-messaging-configurator.spec.ts index b89a9a5ff1..0530eba686 100644 --- a/tests/qa/tests/03-plugin-settings/pay-later-messaging-configurator.spec.ts +++ b/tests/qa/tests/03-plugin-settings/pay-later-messaging-configurator.spec.ts @@ -142,183 +142,193 @@ test.describe( 'Subtests', () => { payLaterMessagingData.checkoutLocationSettings[ 'Product page' ]; for ( const settings of productPlm.settings ) { - test.fixme( `(PCP-0001) PLM - Product page${ summarizeSettings( - settings - ) }`, async ( { pcpPayLaterMessaging, product }, testInfo ) => { - const snapshotName = testInfo.title; - const { location } = productPlm; - await pcpPayLaterMessaging.visit(); - await pcpPayLaterMessaging.enableMessagingForLocation( location ); - await pcpPayLaterMessaging.updateLocationSettings( - location, - settings - ); - // await takePreviewSnapshots( pcpPayLaterMessaging, snapshotName ); // TODO: uncomment when fixed - await pcpPayLaterMessaging.saveChanges(); - await pcpPayLaterMessaging.page.reload(); - await pcpPayLaterMessaging.expandAccordionSection( location ); - // await pcpPayLaterMessaging.assertLocationSettings( settings ); // TODO: uncomment when fixed - await pcpPayLaterMessaging.snapshotPlmConfigurator( - `${ snapshotName } - After save` - ); - - await product.visit( products.simple10.slug ); - await snapshotPlmContainer( - product.payPalUi, - `${ snapshotName } - Frontend` - ); - } ); + test.fixme( + `(PCP-0001) PLM - Product page${ summarizeSettings( settings ) }`, + async ( { pcpPayLaterMessaging, product }, testInfo ) => { + const snapshotName = testInfo.title; + const { location } = productPlm; + await pcpPayLaterMessaging.visit(); + await pcpPayLaterMessaging.enableMessagingForLocation( + location + ); + await pcpPayLaterMessaging.updateLocationSettings( + location, + settings + ); + // await takePreviewSnapshots( pcpPayLaterMessaging, snapshotName ); // TODO: uncomment when fixed + await pcpPayLaterMessaging.saveChanges(); + await pcpPayLaterMessaging.page.reload(); + await pcpPayLaterMessaging.expandAccordionSection( location ); + // await pcpPayLaterMessaging.assertLocationSettings( settings ); // TODO: uncomment when fixed + await pcpPayLaterMessaging.snapshotPlmConfigurator( + `${ snapshotName } - After save` + ); + + await product.visit( products.simple100.slug ); + await snapshotPlmContainer( + product.payPalUi, + `${ snapshotName } - Frontend` + ); + } + ); } const cartPlm = payLaterMessagingData.checkoutLocationSettings.Cart; for ( const settings of cartPlm.settings ) { - test.fixme( `(PCP-0002) PLM - Cart${ summarizeSettings( - settings - ) }`, async ( { - utils, - pcpPayLaterMessaging, - cart, - classicCart, - }, testInfo ) => { - const snapshotName = testInfo.title; - const { location } = cartPlm; - await utils.fillVisitorsCart( [ products.simple10 ] ); - - await pcpPayLaterMessaging.visit(); - await pcpPayLaterMessaging.enableMessagingForLocation( location ); - await pcpPayLaterMessaging.updateLocationSettings( - location, - settings - ); - // await takePreviewSnapshots( pcpPayLaterMessaging, snapshotName ); // TODO: uncomment when fixed - await pcpPayLaterMessaging.saveChanges(); - await pcpPayLaterMessaging.page.reload(); - await pcpPayLaterMessaging.expandAccordionSection( location ); - // await pcpPayLaterMessaging.assertLocationSettings( settings ); // TODO: uncomment when fixed - await pcpPayLaterMessaging.snapshotPlmConfigurator( - `${ snapshotName } - After save` - ); - // Block cart - await cart.visit(); - await snapshotPlmContainer( - cart.payPalUi, - `${ snapshotName } - Frontend - Block cart` - ); - // Classic cart - await classicCart.visit(); - await snapshotPlmContainer( - classicCart.payPalUi, - `${ snapshotName } - Frontend - Classic cart` - ); - } ); + test.fixme( + `(PCP-0002) PLM - Cart${ summarizeSettings( settings ) }`, + async ( + { utils, pcpPayLaterMessaging, cart, classicCart }, + testInfo + ) => { + const snapshotName = testInfo.title; + const { location } = cartPlm; + await utils.fillVisitorsCart( [ products.simple100 ] ); + + await pcpPayLaterMessaging.visit(); + await pcpPayLaterMessaging.enableMessagingForLocation( + location + ); + await pcpPayLaterMessaging.updateLocationSettings( + location, + settings + ); + // await takePreviewSnapshots( pcpPayLaterMessaging, snapshotName ); // TODO: uncomment when fixed + await pcpPayLaterMessaging.saveChanges(); + await pcpPayLaterMessaging.page.reload(); + await pcpPayLaterMessaging.expandAccordionSection( location ); + // await pcpPayLaterMessaging.assertLocationSettings( settings ); // TODO: uncomment when fixed + await pcpPayLaterMessaging.snapshotPlmConfigurator( + `${ snapshotName } - After save` + ); + // Block cart + await cart.visit(); + await snapshotPlmContainer( + cart.payPalUi, + `${ snapshotName } - Frontend - Block cart` + ); + // Classic cart + await classicCart.visit(); + await snapshotPlmContainer( + classicCart.payPalUi, + `${ snapshotName } - Frontend - Classic cart` + ); + } + ); } const checkoutPlm = payLaterMessagingData.checkoutLocationSettings.Checkout; for ( const settings of checkoutPlm.settings ) { - test.fixme( `(PCP-0003) PLM - Checkout${ summarizeSettings( - settings - ) }`, async ( { - utils, - pcpPayLaterMessaging, - checkout, - classicCheckout, - }, testInfo ) => { - const snapshotName = testInfo.title; - const { location } = checkoutPlm; - await utils.fillVisitorsCart( [ products.simple10 ] ); - - await pcpPayLaterMessaging.visit(); - await pcpPayLaterMessaging.enableMessagingForLocation( location ); - await pcpPayLaterMessaging.updateLocationSettings( - location, - settings - ); - // await takePreviewSnapshots( pcpPayLaterMessaging, snapshotName ); // TODO: uncomment when fixed - await pcpPayLaterMessaging.saveChanges(); - await pcpPayLaterMessaging.page.reload(); - await pcpPayLaterMessaging.expandAccordionSection( location ); - // await pcpPayLaterMessaging.assertLocationSettings( settings ); // TODO: uncomment when fixed - await pcpPayLaterMessaging.snapshotPlmConfigurator( - `${ snapshotName } - After save` - ); - // Block checkout - await checkout.visit(); - await snapshotPlmContainer( - checkout.payPalUi, - `${ snapshotName } - Frontend - Block checkout` - ); - // Classic checkout - await classicCheckout.visit(); - await snapshotPlmContainer( - classicCheckout.payPalUi, - `${ snapshotName } - Frontend - Classic checkout` - ); - } ); + test.fixme( + `(PCP-0003) PLM - Checkout${ summarizeSettings( settings ) }`, + async ( + { utils, pcpPayLaterMessaging, checkout, classicCheckout }, + testInfo + ) => { + const snapshotName = testInfo.title; + const { location } = checkoutPlm; + await utils.fillVisitorsCart( [ products.simple100 ] ); + + await pcpPayLaterMessaging.visit(); + await pcpPayLaterMessaging.enableMessagingForLocation( + location + ); + await pcpPayLaterMessaging.updateLocationSettings( + location, + settings + ); + // await takePreviewSnapshots( pcpPayLaterMessaging, snapshotName ); // TODO: uncomment when fixed + await pcpPayLaterMessaging.saveChanges(); + await pcpPayLaterMessaging.page.reload(); + await pcpPayLaterMessaging.expandAccordionSection( location ); + // await pcpPayLaterMessaging.assertLocationSettings( settings ); // TODO: uncomment when fixed + await pcpPayLaterMessaging.snapshotPlmConfigurator( + `${ snapshotName } - After save` + ); + // Block checkout + await checkout.visit(); + await snapshotPlmContainer( + checkout.payPalUi, + `${ snapshotName } - Frontend - Block checkout` + ); + // Classic checkout + await classicCheckout.visit(); + await snapshotPlmContainer( + classicCheckout.payPalUi, + `${ snapshotName } - Frontend - Classic checkout` + ); + } + ); } const homePlm = payLaterMessagingData.bannerLocationSettings.Home; for ( const settings of homePlm.settings ) { - test.fixme( `(PCP-0004) PLM - Home${ summarizeSettings( - settings - ) }`, async ( { pcpPayLaterMessaging, payPalUiClassic }, testInfo ) => { - test.setTimeout( 10 * 60 * 1000 ); - const snapshotName = testInfo.title; - const { location } = homePlm; - await pcpPayLaterMessaging.visit(); - await pcpPayLaterMessaging.enableMessagingForLocation( location ); - await pcpPayLaterMessaging.updateLocationSettings( - location, - settings - ); - // await takePreviewSnapshots( pcpPayLaterMessaging, snapshotName ); // TODO: uncomment when fixed - await pcpPayLaterMessaging.saveChanges(); - await pcpPayLaterMessaging.page.reload(); - await pcpPayLaterMessaging.expandAccordionSection( location ); - // await pcpPayLaterMessaging.assertLocationSettings( settings ); // TODO: uncomment when fixed - await pcpPayLaterMessaging.snapshotPlmConfigurator( - `${ snapshotName } - After save` - ); - - await payPalUiClassic.page.goto( '/' ); - await snapshotPlmContainer( - payPalUiClassic, - `${ snapshotName } - Frontend` - ); - } ); + test.fixme( + `(PCP-0004) PLM - Home${ summarizeSettings( settings ) }`, + async ( { pcpPayLaterMessaging, payPalUiClassic }, testInfo ) => { + const snapshotName = testInfo.title; + const { location } = homePlm; + await pcpPayLaterMessaging.visit(); + await pcpPayLaterMessaging.enableMessagingForLocation( + location + ); + await pcpPayLaterMessaging.updateLocationSettings( + location, + settings + ); + // await takePreviewSnapshots( pcpPayLaterMessaging, snapshotName ); // TODO: uncomment when fixed + await pcpPayLaterMessaging.saveChanges(); + await pcpPayLaterMessaging.page.reload(); + await pcpPayLaterMessaging.expandAccordionSection( location ); + // await pcpPayLaterMessaging.assertLocationSettings( settings ); // TODO: uncomment when fixed + await pcpPayLaterMessaging.snapshotPlmConfigurator( + `${ snapshotName } - After save` + ); + + await payPalUiClassic.page.goto( '/' ); + await snapshotPlmContainer( + payPalUiClassic, + `${ snapshotName } - Frontend` + ); + } + ); } const shopPlm = payLaterMessagingData.bannerLocationSettings.Shop; for ( const settings of shopPlm.settings ) { - test.fixme( `(PCP-0005) PLM - Shop${ summarizeSettings( - settings - ) }`, async ( { pcpPayLaterMessaging, shop }, testInfo ) => { - const snapshotName = testInfo.title; - const { location } = shopPlm; - await pcpPayLaterMessaging.visit(); - await pcpPayLaterMessaging.enableMessagingForLocation( location ); - await pcpPayLaterMessaging.updateLocationSettings( - location, - settings - ); - // await takePreviewSnapshots( pcpPayLaterMessaging, snapshotName ); // TODO: uncomment when fixed - await pcpPayLaterMessaging.saveChanges(); - await pcpPayLaterMessaging.page.reload(); - await pcpPayLaterMessaging.expandAccordionSection( location ); - // await pcpPayLaterMessaging.assertLocationSettings( settings ); // TODO: uncomment when fixed - await pcpPayLaterMessaging.snapshotPlmConfigurator( - `${ snapshotName } - After save` - ); - - await shop.visit(); - await snapshotPlmContainer( - shop.payPalUi, - `${ snapshotName } - Frontend` - ); - } ); + test.fixme( + `(PCP-0005) PLM - Shop${ summarizeSettings( settings ) }`, + async ( { pcpPayLaterMessaging, shop }, testInfo ) => { + const snapshotName = testInfo.title; + const { location } = shopPlm; + await pcpPayLaterMessaging.visit(); + await pcpPayLaterMessaging.enableMessagingForLocation( + location + ); + await pcpPayLaterMessaging.updateLocationSettings( + location, + settings + ); + // await takePreviewSnapshots( pcpPayLaterMessaging, snapshotName ); // TODO: uncomment when fixed + await pcpPayLaterMessaging.saveChanges(); + await pcpPayLaterMessaging.page.reload(); + await pcpPayLaterMessaging.expandAccordionSection( location ); + // await pcpPayLaterMessaging.assertLocationSettings( settings ); // TODO: uncomment when fixed + await pcpPayLaterMessaging.snapshotPlmConfigurator( + `${ snapshotName } - After save` + ); + + await shop.visit(); + await snapshotPlmContainer( + shop.payPalUi, + `${ snapshotName } - Frontend` + ); + } + ); } test.afterEach( async ( {}, testInfo ) => { @@ -330,22 +340,37 @@ test.describe( 'Subtests', () => { } ); } ); -test.fixme( 'PCP-0001 | Pay Later Messaging - Customize on Product page', async () => { - getTestResultsFromFile( 'PCP-0001', TEST_RESULTS_FILE ); -} ); +test.fixme( + 'PCP-0001 | Pay Later Messaging - Customize on Product page', + async () => { + getTestResultsFromFile( 'PCP-0001', TEST_RESULTS_FILE ); + } +); -test.fixme( 'PCP-0002 | Pay Later Messaging - Customize on Cart (block and classic)', async () => { - getTestResultsFromFile( 'PCP-0002', TEST_RESULTS_FILE ); -} ); +test.fixme( + 'PCP-0002 | Pay Later Messaging - Customize on Cart (block and classic)', + async () => { + getTestResultsFromFile( 'PCP-0002', TEST_RESULTS_FILE ); + } +); -test.fixme( 'PCP-0003 | Pay Later Messaging - Customize on Checkout (block and classic)', async () => { - getTestResultsFromFile( 'PCP-0003', TEST_RESULTS_FILE ); -} ); +test.fixme( + 'PCP-0003 | Pay Later Messaging - Customize on Checkout (block and classic)', + async () => { + getTestResultsFromFile( 'PCP-0003', TEST_RESULTS_FILE ); + } +); -test.fixme( 'PCP-0004 | Pay Later Messaging - Customize on Home page', async () => { - getTestResultsFromFile( 'PCP-0004', TEST_RESULTS_FILE ); -} ); +test.fixme( + 'PCP-0004 | Pay Later Messaging - Customize on Home page', + async () => { + getTestResultsFromFile( 'PCP-0004', TEST_RESULTS_FILE ); + } +); -test.fixme( 'PCP-0005 | Pay Later Messaging - Customize on Shop page', async () => { - getTestResultsFromFile( 'PCP-0005', TEST_RESULTS_FILE ); -} ); +test.fixme( + 'PCP-0005 | Pay Later Messaging - Customize on Shop page', + async () => { + getTestResultsFromFile( 'PCP-0005', TEST_RESULTS_FILE ); + } +); diff --git a/tests/qa/tests/03-plugin-settings/pay-later-messaging-ui.spec.ts b/tests/qa/tests/03-plugin-settings/pay-later-messaging-ui.spec.ts index db1672f04b..523c58adfe 100644 --- a/tests/qa/tests/03-plugin-settings/pay-later-messaging-ui.spec.ts +++ b/tests/qa/tests/03-plugin-settings/pay-later-messaging-ui.spec.ts @@ -14,159 +14,184 @@ test.beforeAll( async ( { utils, pcpApi } ) => { ); } ); -test.fixme( 'PCP-0000 | Settings - Pay Later Messaging - Default UI', async ( { - utils, - payPalUi, - pcpPayLaterMessaging, - shop, - product, - cart, - classicCart, - checkout, - classicCheckout, -}, testInfo ) => { - const snapshotName = testInfo.title; - await utils.fillVisitorsCart( [ products.simple100 ] ); - - await pcpPayLaterMessaging.visit(); - await pcpPayLaterMessaging.waitForLoadingMaskRemoved(); - await pcpPayLaterMessaging.snapshotPlmConfigurator( - `${ snapshotName } - Initial view` - ); - - await pcpPayLaterMessaging.expandAccordionSection( 'Product page' ); - await pcpPayLaterMessaging.snapshotPlmConfigurator( - `${ snapshotName } - Product page config` - ); - - await pcpPayLaterMessaging.expandAccordionSection( 'Cart' ); - await pcpPayLaterMessaging.snapshotPlmConfigurator( - `${ snapshotName } - Cart config` - ); - - await pcpPayLaterMessaging.expandAccordionSection( 'Checkout' ); - await pcpPayLaterMessaging.snapshotPlmConfigurator( - `${ snapshotName } - Checkout config` - ); - - await pcpPayLaterMessaging.expandAccordionSection( 'Home' ); - await pcpPayLaterMessaging.snapshotPlmConfigurator( - `${ snapshotName } - Home config` - ); - - await pcpPayLaterMessaging.expandAccordionSection( 'Shop' ); - await pcpPayLaterMessaging.snapshotPlmConfigurator( - `${ snapshotName } - Shop config` - ); - - await product.visit( products.simple100.slug ); - await expect( product.payPalUi.payLaterMessageContainer() ).toBeVisible(); - - await cart.visit(); - await expect( cart.payPalUi.payLaterMessageContainer() ).toBeVisible(); - - await classicCart.visit(); - await expect( - classicCart.payPalUi.payLaterMessageContainer() - ).toBeVisible(); - - await checkout.visit(); - await expect( checkout.payPalUi.payLaterMessageContainer() ).toBeVisible(); - - await classicCheckout.visit(); - await expect( - classicCheckout.payPalUi.payLaterMessageContainer() - ).toBeVisible(); - - await shop.visit(); - await expect( shop.payPalUi.payLaterMessageContainer() ).not.toBeVisible(); - - await payPalUi.page.goto( '/' ); // home page - await expect( payPalUi.payLaterMessageContainer() ).not.toBeVisible(); -} ); - -test.fixme( 'PCP-0000 | Settings - Pay Later Messaging - Disabled on all pages', async ( { - utils, - payPalUi, - pcpPayLaterMessaging, - shop, - product, - cart, - classicCart, - checkout, - classicCheckout, -} ) => { - await utils.fillVisitorsCart( [ products.simple100 ] ); - - await pcpPayLaterMessaging.visit(); - await pcpPayLaterMessaging.disableMessagingForLocation( 'Product page' ); - await pcpPayLaterMessaging.disableMessagingForLocation( 'Cart' ); - await pcpPayLaterMessaging.disableMessagingForLocation( 'Checkout' ); - await pcpPayLaterMessaging.disableMessagingForLocation( 'Home' ); - await pcpPayLaterMessaging.disableMessagingForLocation( 'Shop' ); - await pcpPayLaterMessaging.saveChanges(); - await pcpPayLaterMessaging.page.reload(); - - expect - .soft( - await pcpPayLaterMessaging.isMessagingForLocationEnabled( - 'Product page' +test.fixme( + 'PCP-0000 | Settings - Pay Later Messaging - Default UI', + async ( + { + utils, + payPalUi, + pcpPayLaterMessaging, + shop, + product, + cart, + classicCart, + checkout, + classicCheckout, + }, + testInfo + ) => { + const snapshotName = testInfo.title; + await utils.fillVisitorsCart( [ products.simple100 ] ); + + await pcpPayLaterMessaging.visit(); + await pcpPayLaterMessaging.waitForLoadingMaskRemoved(); + await pcpPayLaterMessaging.snapshotPlmConfigurator( + `${ snapshotName } - Initial view` + ); + + await pcpPayLaterMessaging.expandAccordionSection( 'Product page' ); + await pcpPayLaterMessaging.snapshotPlmConfigurator( + `${ snapshotName } - Product page config` + ); + + await pcpPayLaterMessaging.expandAccordionSection( 'Cart' ); + await pcpPayLaterMessaging.snapshotPlmConfigurator( + `${ snapshotName } - Cart config` + ); + + await pcpPayLaterMessaging.expandAccordionSection( 'Checkout' ); + await pcpPayLaterMessaging.snapshotPlmConfigurator( + `${ snapshotName } - Checkout config` + ); + + await pcpPayLaterMessaging.expandAccordionSection( 'Home' ); + await pcpPayLaterMessaging.snapshotPlmConfigurator( + `${ snapshotName } - Home config` + ); + + await pcpPayLaterMessaging.expandAccordionSection( 'Shop' ); + await pcpPayLaterMessaging.snapshotPlmConfigurator( + `${ snapshotName } - Shop config` + ); + + await product.visit( products.simple100.slug ); + await expect( + product.payPalUi.payLaterMessageContainer() + ).toBeVisible(); + + await cart.visit(); + await expect( cart.payPalUi.payLaterMessageContainer() ).toBeVisible(); + + await classicCart.visit(); + await expect( + classicCart.payPalUi.payLaterMessageContainer() + ).toBeVisible(); + + await checkout.visit(); + await expect( + checkout.payPalUi.payLaterMessageContainer() + ).toBeVisible(); + + await classicCheckout.visit(); + await expect( + classicCheckout.payPalUi.payLaterMessageContainer() + ).toBeVisible(); + + await shop.visit(); + await expect( + shop.payPalUi.payLaterMessageContainer() + ).not.toBeVisible(); + + await payPalUi.page.goto( '/' ); // home page + await expect( payPalUi.payLaterMessageContainer() ).not.toBeVisible(); + } +); + +test.fixme( + 'PCP-0000 | Settings - Pay Later Messaging - Disabled on all pages', + async ( { + utils, + payPalUi, + pcpPayLaterMessaging, + shop, + product, + cart, + classicCart, + checkout, + classicCheckout, + } ) => { + await utils.fillVisitorsCart( [ products.simple100 ] ); + + await pcpPayLaterMessaging.visit(); + await pcpPayLaterMessaging.disableMessagingForLocation( + 'Product page' + ); + await pcpPayLaterMessaging.disableMessagingForLocation( 'Cart' ); + await pcpPayLaterMessaging.disableMessagingForLocation( 'Checkout' ); + await pcpPayLaterMessaging.disableMessagingForLocation( 'Home' ); + await pcpPayLaterMessaging.disableMessagingForLocation( 'Shop' ); + await pcpPayLaterMessaging.saveChanges(); + await pcpPayLaterMessaging.page.reload(); + + expect + .soft( + await pcpPayLaterMessaging.isMessagingForLocationEnabled( + 'Product page' + ) ) - ) - .toBeFalsy(); - expect - .soft( - await pcpPayLaterMessaging.isMessagingForLocationEnabled( 'Cart' ) - ) - .toBeFalsy(); - expect - .soft( - await pcpPayLaterMessaging.isMessagingForLocationEnabled( - 'Checkout' + .toBeFalsy(); + expect + .soft( + await pcpPayLaterMessaging.isMessagingForLocationEnabled( + 'Cart' + ) ) - ) - .toBeFalsy(); - expect - .soft( - await pcpPayLaterMessaging.isMessagingForLocationEnabled( 'Home' ) - ) - .toBeFalsy(); - expect - .soft( - await pcpPayLaterMessaging.isMessagingForLocationEnabled( 'Shop' ) - ) - .toBeFalsy(); - - await product.visit( products.simple100.slug ); - await expect - .soft( product.payPalUi.payLaterMessageContainer() ) - .not.toBeVisible(); - - await cart.visit(); - await expect - .soft( cart.payPalUi.payLaterMessageContainer() ) - .not.toBeVisible(); - - await classicCart.visit(); - await expect - .soft( classicCart.payPalUi.payLaterMessageContainer() ) - .not.toBeVisible(); - - await checkout.visit(); - await expect - .soft( checkout.payPalUi.payLaterMessageContainer() ) - .not.toBeVisible(); - - await classicCheckout.visit(); - await expect - .soft( classicCheckout.payPalUi.payLaterMessageContainer() ) - .not.toBeVisible(); - - await shop.visit(); - await expect - .soft( shop.payPalUi.payLaterMessageContainer() ) - .not.toBeVisible(); - - await payPalUi.page.goto( '/' ); // home page - await expect.soft( payPalUi.payLaterMessageContainer() ).not.toBeVisible(); -} ); + .toBeFalsy(); + expect + .soft( + await pcpPayLaterMessaging.isMessagingForLocationEnabled( + 'Checkout' + ) + ) + .toBeFalsy(); + expect + .soft( + await pcpPayLaterMessaging.isMessagingForLocationEnabled( + 'Home' + ) + ) + .toBeFalsy(); + expect + .soft( + await pcpPayLaterMessaging.isMessagingForLocationEnabled( + 'Shop' + ) + ) + .toBeFalsy(); + + await product.visit( products.simple100.slug ); + await expect + .soft( product.payPalUi.payLaterMessageContainer() ) + .not.toBeVisible(); + + await cart.visit(); + await expect + .soft( cart.payPalUi.payLaterMessageContainer() ) + .not.toBeVisible(); + + await classicCart.visit(); + await expect + .soft( classicCart.payPalUi.payLaterMessageContainer() ) + .not.toBeVisible(); + + await checkout.visit(); + await expect + .soft( checkout.payPalUi.payLaterMessageContainer() ) + .not.toBeVisible(); + + await classicCheckout.visit(); + await expect + .soft( classicCheckout.payPalUi.payLaterMessageContainer() ) + .not.toBeVisible(); + + await shop.visit(); + await expect + .soft( shop.payPalUi.payLaterMessageContainer() ) + .not.toBeVisible(); + + await payPalUi.page.goto( '/' ); // home page + await expect + .soft( payPalUi.payLaterMessageContainer() ) + .not.toBeVisible(); + } +); diff --git a/tests/qa/tests/03-plugin-settings/payment-methods-ui.spec.ts b/tests/qa/tests/03-plugin-settings/payment-methods-ui.spec.ts index 44277a1dd8..578388b379 100644 --- a/tests/qa/tests/03-plugin-settings/payment-methods-ui.spec.ts +++ b/tests/qa/tests/03-plugin-settings/payment-methods-ui.spec.ts @@ -18,111 +18,120 @@ test.beforeAll( async ( { utils, pcpApi } ) => { for ( const testData of paymentMethodsData.defaultUi ) { const { testKey, country, testGateways } = testData; - test.fixme( `${ testKey } | Settings - ${ country } - Payment Methods - Default UI`, async ( { - utils, - pcpPaymentMethods, - payPalUiClassic, - product, - cart, - classicCart, - checkout, - classicCheckout, - }, testInfo ) => { - const snapshotName = testInfo.title; - - await pcpPaymentMethods.visit(); - await expect( pcpPaymentMethods.onlineCardPaymentsContainer() ).toBeVisible(); - // Snapshot the full screen - await pcpPaymentMethods.snapshotContent( - `${ snapshotName } - Content` - ); - // Assert number of gateways - await expect - .soft( pcpPaymentMethods.paymentMethodContainers() ) - .toHaveCount( testGateways.length ); - - // Assert expected gateways one by one - for ( const testGateway of testGateways ) { - const gateway = gateways[ testGateway ]; - const { - titleInPcpSettings, - hasSettingsButton, - enabled: isGatewayEnabled, - dependsOn, - } = gateway; - - // current gateway toggle and settings button - const gatewayToggle = - pcpPaymentMethods.paymentMethodToggle( titleInPcpSettings ); - const gatewaySettingsButton = - pcpPaymentMethods.paymentMethodSettingsButton( - titleInPcpSettings - ); - - // Assert current gateway enabled state and settings button - await expect( gatewaySettingsButton ).toBeVisible( { - visible: hasSettingsButton, - } ); - await expect( gatewayToggle ).toBeChecked( { - checked: isGatewayEnabled, - } ); - - // Snapshot gateway settings modal window if it exists - if ( hasSettingsButton ) { - // Enable gateway if not by default - if ( ! isGatewayEnabled ) { - if ( dependsOn ) { - // Ensure the leading gateway is enabled - await pcpPaymentMethods - .paymentMethodToggle( - gateways[ dependsOn ].titleInPcpSettings - ) - .check(); + test.fixme( + `${ testKey } | Settings - ${ country } - Payment Methods - Default UI`, + async ( + { + utils, + pcpPaymentMethods, + payPalUiClassic, + product, + cart, + classicCart, + checkout, + classicCheckout, + }, + testInfo + ) => { + const snapshotName = testInfo.title; + const simpleProduct = products.simple100; + + await pcpPaymentMethods.visit(); + await expect( + pcpPaymentMethods.onlineCardPaymentsContainer() + ).toBeVisible(); + // Snapshot the full screen + await pcpPaymentMethods.snapshotContent( + `${ snapshotName } - Content` + ); + // Assert number of gateways + await expect + .soft( pcpPaymentMethods.paymentMethodContainers() ) + .toHaveCount( testGateways.length ); + + // Assert expected gateways one by one + for ( const testGateway of testGateways ) { + const gateway = gateways[ testGateway ]; + const { + titleInPcpSettings, + hasSettingsButton, + enabled: isGatewayEnabled, + dependsOn, + } = gateway; + + // current gateway toggle and settings button + const gatewayToggle = + pcpPaymentMethods.paymentMethodToggle( titleInPcpSettings ); + const gatewaySettingsButton = + pcpPaymentMethods.paymentMethodSettingsButton( + titleInPcpSettings + ); + + // Assert current gateway enabled state and settings button + await expect( gatewaySettingsButton ).toBeVisible( { + visible: hasSettingsButton, + } ); + await expect( gatewayToggle ).toBeChecked( { + checked: isGatewayEnabled, + } ); + + // Snapshot gateway settings modal window if it exists + if ( hasSettingsButton ) { + // Enable gateway if not by default + if ( ! isGatewayEnabled ) { + if ( dependsOn ) { + // Ensure the leading gateway is enabled + await pcpPaymentMethods + .paymentMethodToggle( + gateways[ dependsOn ].titleInPcpSettings + ) + .check(); + } + await gatewayToggle.check(); } - await gatewayToggle.check(); - } - // Open gateway settings modal window - await gatewaySettingsButton.click(); - // Snapshot gateway settings modal window - await pcpPaymentMethods.snapshotModalWindow( - `${ snapshotName } - Modal - ${ titleInPcpSettings }` - ); - await pcpPaymentMethods.modalCloseButton().click(); + // Open gateway settings modal window + await gatewaySettingsButton.click(); + // Snapshot gateway settings modal window + await pcpPaymentMethods.snapshotModalWindow( + `${ snapshotName } - Modal - ${ titleInPcpSettings }` + ); + await pcpPaymentMethods.modalCloseButton().click(); + } } - } - await utils.fillVisitorsCart( [ products.simple10 ] ); - - await product.visit( products.simple10.slug ); - await product.payPalUi.snapshotClassicPayPalButtons( - `${ snapshotName } - Frontend - Product` - ); - - await product.minicartContainer().hover(); - await expect - .soft( payPalUiClassic.miniCartButtonContainer() ) - .not.toBeVisible(); - - await cart.visit(); - await cart.payPalUi.snapshotBlockPayPalButtons( - `${ snapshotName } - Frontend - Cart` - ); - - await classicCart.visit(); - await classicCart.payPalUi.snapshotClassicPayPalButtons( - `${ snapshotName } - Frontend - Classic Cart` - ); - - await checkout.visit(); - await checkout.payPalUi.snapshotBlockPayPalButtons( - `${ snapshotName } - Frontend - Checkout` - ); - - await classicCheckout.visit(); - await classicCheckout.paymentOption( 'PayPal' ).click(); - await classicCheckout.payPalUi.snapshotClassicPayPalButtons( - `${ snapshotName } - Frontend - Classic checkout` - ); - } ); + await utils.fillVisitorsCart( [ simpleProduct ] ); + + await product.visit( simpleProduct.slug ); + await product.payPalUi.snapshotClassicPayPalButtons( + `${ snapshotName } - Frontend - Product` + ); + + await product.minicartContainer().hover(); + await expect + .soft( payPalUiClassic.miniCartButtonContainer() ) + .not.toBeVisible(); + + await cart.visit(); + await cart.payPalUi.snapshotBlockPayPalButtons( + `${ snapshotName } - Frontend - Cart` + ); + + await classicCart.visit(); + await classicCart.payPalUi.snapshotClassicPayPalButtons( + `${ snapshotName } - Frontend - Classic Cart` + ); + + await checkout.visit(); + await checkout.payPalUi.snapshotBlockPayPalButtons( + `${ snapshotName } - Frontend - Checkout` + ); + + await classicCheckout.visit(); + await classicCheckout.paymentOption( 'PayPal' ).click(); + await classicCheckout.payPalUi.snapshotClassicPayPalButtons( + `${ snapshotName } - Frontend - Classic checkout` + ); + } + ); } diff --git a/tests/qa/tests/03-plugin-settings/styling-ui.spec.ts b/tests/qa/tests/03-plugin-settings/styling-ui.spec.ts index 15a4c9bd45..a30e36dd3e 100644 --- a/tests/qa/tests/03-plugin-settings/styling-ui.spec.ts +++ b/tests/qa/tests/03-plugin-settings/styling-ui.spec.ts @@ -14,65 +14,72 @@ test.beforeAll( async ( { utils, pcpApi } ) => { ); } ); -test.fixme( 'PCP-0000 | Settings - Styling - Default UI', async ( { - utils, - pcpStyling, - product, - cart, - classicCart, - checkout, - classicCheckout, -}, testInfo ) => { - const snapshotName = testInfo.title; - const locations: Pcp.Admin.Styling.Location[] = [ - 'Cart', - 'Classic Checkout', - 'Express Checkout', - 'Mini Cart', - 'Product Page', - ]; +test.fixme( + 'PCP-0000 | Settings - Styling - Default UI', + async ( + { + utils, + pcpStyling, + product, + cart, + classicCart, + checkout, + classicCheckout, + }, + testInfo + ) => { + const snapshotName = testInfo.title; + const locations: Pcp.Admin.Styling.Location[] = [ + 'Cart', + 'Classic Checkout', + 'Express Checkout', + 'Mini Cart', + 'Product Page', + ]; + const simpleProduct = products.simple100; - await pcpStyling.visit(); - await expect( pcpStyling.configContainer() ).toBeVisible(); - await expect( pcpStyling.locationSelectbox() ).toBeVisible(); + await pcpStyling.visit(); + await expect( pcpStyling.configContainer() ).toBeVisible(); + await expect( pcpStyling.locationSelectbox() ).toBeVisible(); - for ( const location of locations ) { - await pcpStyling.locationSelectbox().selectOption( location ); - await pcpStyling.snapshotStylingConfigurator( - `${ snapshotName } - ${ location }` - ); - } + for ( const location of locations ) { + await pcpStyling.locationSelectbox().selectOption( location ); + await pcpStyling.snapshotStylingConfigurator( + `${ snapshotName } - ${ location }` + ); + } - await utils.fillVisitorsCart( [ products.simple10 ] ); + await utils.fillVisitorsCart( [ simpleProduct ] ); - await product.visit( products.simple10.slug ); - await product.payPalUi.snapshotClassicPayPalButtons( - `${ snapshotName } - Frontend - Product` - ); + await product.visit( simpleProduct.slug ); + await product.payPalUi.snapshotClassicPayPalButtons( + `${ snapshotName } - Frontend - Product` + ); - await product.minicartContainer().hover(); - await product.payPalUi.snapshotMinicartPayPalButtons( - `${ snapshotName } - Frontend - Minicart` - ); + await product.minicartContainer().hover(); + await product.payPalUi.snapshotMinicartPayPalButtons( + `${ snapshotName } - Frontend - Minicart` + ); - await cart.visit(); - await cart.payPalUi.snapshotBlockPayPalButtons( - `${ snapshotName } - Frontend - Cart` - ); + await cart.visit(); + await cart.payPalUi.snapshotBlockPayPalButtons( + `${ snapshotName } - Frontend - Cart` + ); - await classicCart.visit(); - await classicCart.payPalUi.snapshotClassicPayPalButtons( - `${ snapshotName } - Frontend - Classic Cart` - ); + await classicCart.visit(); + await classicCart.payPalUi.snapshotClassicPayPalButtons( + `${ snapshotName } - Frontend - Classic Cart` + ); - await checkout.visit(); - await checkout.payPalUi.snapshotBlockPayPalButtons( - `${ snapshotName } - Frontend - Checkout` - ); + await checkout.visit(); + await checkout.payPalUi.snapshotBlockPayPalButtons( + `${ snapshotName } - Frontend - Checkout` + ); - await classicCheckout.visit(); - await classicCheckout.paymentOption( 'PayPal' ).click(); - await classicCheckout.payPalUi.snapshotClassicPayPalButtons( - `${ snapshotName } - Frontend - Classic checkout` - ); -} ); + await classicCheckout.visit(); + await classicCheckout.paymentOption( 'PayPal' ).click(); + await classicCheckout.payPalUi.snapshotClassicPayPalButtons( + `${ snapshotName } - Frontend - Classic checkout` + ); + } +); diff --git a/tests/qa/tests/05-transactions/transaction-usa-classic.spec.ts b/tests/qa/tests/05-transactions/transaction-usa-classic.spec.ts index e41c1e8fe2..32502c94e0 100644 --- a/tests/qa/tests/05-transactions/transaction-usa-classic.spec.ts +++ b/tests/qa/tests/05-transactions/transaction-usa-classic.spec.ts @@ -21,7 +21,7 @@ import { venmoClassicProductUsa, } from './_test-data/venmo'; import { - payPalCheckoutExcludingTax, + payPalClassicCheckoutExcludingTax, payPalClassicCheckout, payPalClassicCheckoutIntentAuthorized, } from './_test-data/paypal'; @@ -76,7 +76,7 @@ test.describe( () => { await wooCommerceUtils.setTaxes( taxSettings.excluding ); } ); - transactionsOnClassicCheckout( payPalCheckoutExcludingTax ); + transactionsOnClassicCheckout( payPalClassicCheckoutExcludingTax ); transactionsOnClassicCheckout( acdcClassicCheckoutExcludingTax ); test.afterAll( async ( { wooCommerceUtils } ) => { diff --git a/tests/qa/tests/06-refund/_test-scenarios/refund.scenario.ts b/tests/qa/tests/06-refund/_test-scenarios/refund.scenario.ts index 4400e9aa22..5cc6b4e901 100644 --- a/tests/qa/tests/06-refund/_test-scenarios/refund.scenario.ts +++ b/tests/qa/tests/06-refund/_test-scenarios/refund.scenario.ts @@ -25,7 +25,7 @@ export const testRefund = ( testsData: ShopRefund[] ) => { wooCommerceApi, payPalApi, } ) => { - test.setTimeout( 1.5 * 60 * 1000 ); + test.setTimeout( 2 * 60 * 1000 ); let order: WooCommerce.Order; // TODO: fix type in playwright-utils const total = await countTotals( testData ); const refundAvailable = total.order; diff --git a/tests/qa/tests/07-vaulting/_test-data/vaulting-checkout.data.ts b/tests/qa/tests/07-vaulting/_test-data/vaulting-checkout.data.ts index 444fda654f..27431cb398 100644 --- a/tests/qa/tests/07-vaulting/_test-data/vaulting-checkout.data.ts +++ b/tests/qa/tests/07-vaulting/_test-data/vaulting-checkout.data.ts @@ -64,7 +64,6 @@ const acdcAdditionalCardData: ShopOrder[] = [ const vaultedPaymentMethodData: ShopOrder[] = [ { - // FAIL: vaulted PayPal button is not displayed. // https://inpsyde.atlassian.net/browse/PCP-2051 title: 'PCP-2051 | Vaulting - Transaction - Checkout - PayPal - Pay with vaulted account', ...orders.default, diff --git a/tests/qa/tests/07-vaulting/_test-scenarios/vaulting-classic-checkout.scenario.ts b/tests/qa/tests/07-vaulting/_test-scenarios/vaulting-classic-checkout.scenario.ts index 084658cbdb..355ade19b2 100644 --- a/tests/qa/tests/07-vaulting/_test-scenarios/vaulting-classic-checkout.scenario.ts +++ b/tests/qa/tests/07-vaulting/_test-scenarios/vaulting-classic-checkout.scenario.ts @@ -47,6 +47,7 @@ const testSavePaymentMethod = ( testOrder: ShopOrder ) => { await utils.fillVisitorsCart( products ); await classicCheckout.visit(); + await classicCheckout.payPalUi.expandPaymentGateway( payment ); if ( payment.saveToAccount === true ) { await classicCheckout.payPalUi.assertVaultedPaymentMethodIsDisplayed( payment @@ -110,6 +111,7 @@ const testAcdcAdditionalCard = ( testOrder: ShopOrder ) => { await utils.fillVisitorsCart( products ); await classicCheckout.visit(); + await classicCheckout.payPalUi.expandPaymentGateway( payment ); if ( payment.saveToAccount === true ) { await classicCheckout.payPalUi.assertVaultedPaymentMethodIsDisplayed( payment diff --git a/tests/qa/tests/07-vaulting/_test-scenarios/vaulting-pay-by-link.scenario.ts b/tests/qa/tests/07-vaulting/_test-scenarios/vaulting-pay-by-link.scenario.ts index 084208aef2..98b2a99568 100644 --- a/tests/qa/tests/07-vaulting/_test-scenarios/vaulting-pay-by-link.scenario.ts +++ b/tests/qa/tests/07-vaulting/_test-scenarios/vaulting-pay-by-link.scenario.ts @@ -48,6 +48,7 @@ const testSavePaymentMethod = ( testOrder: ShopOrder ) => { order = await wooCommerceUtils.createApiOrder( testOrder ); await payForOrder.visit( order.id, order.order_key ); + await payForOrder.payPalUi.expandPaymentGateway( payment ); if ( payment.saveToAccount === true ) { await payForOrder.payPalUi.assertVaultedPaymentMethodIsDisplayed( payment @@ -112,6 +113,7 @@ const testAcdcAdditionalCard = ( testOrder: ShopOrder ) => { order = await wooCommerceUtils.createApiOrder( testOrder ); await payForOrder.visit( order.id, order.order_key ); + await payForOrder.payPalUi.expandPaymentGateway( payment ); if ( payment.saveToAccount === true ) { await payForOrder.payPalUi.assertVaultedPaymentMethodIsDisplayed( payment diff --git a/tests/qa/tests/07-vaulting/vaulting-my-account.spec.ts b/tests/qa/tests/07-vaulting/vaulting-my-account.spec.ts index 0e57c43bfa..d720eebb71 100644 --- a/tests/qa/tests/07-vaulting/vaulting-my-account.spec.ts +++ b/tests/qa/tests/07-vaulting/vaulting-my-account.spec.ts @@ -78,8 +78,9 @@ for ( const testData of savePaymentMethodData ) { payment ); - await utils.fillVisitorsCart( [ products.simple10 ] ); + await utils.fillVisitorsCart( [ products.simple100 ] ); await classicCheckout.visit(); + await classicCheckout.payPalUi.expandPaymentGateway( payment ); await classicCheckout.payPalUi.assertVaultedPaymentMethodIsDisplayed( payment ); @@ -143,8 +144,9 @@ for ( const testData of deletePaymentMethodData ) { payment ); - await utils.fillVisitorsCart( [ products.simple10 ] ); + await utils.fillVisitorsCart( [ products.simple100 ] ); await classicCheckout.visit(); + await classicCheckout.payPalUi.expandPaymentGateway( payment ); await classicCheckout.payPalUi.assertVaultedPaymentMethodIsNotDisplayed( payment ); @@ -190,6 +192,7 @@ test.describe( () => { } ); test( + // Fail: 'PCP-5381 | Vaulting - My Account - Payment Methods - ACDC - Save additional card', annotateVisitor( customer ), async ( { utils, customerPaymentMethods, classicCheckout } ) => { @@ -207,8 +210,9 @@ test.describe( () => { await customerPaymentMethods.assertIsSavedPaymentMethod( acdc ); await customerPaymentMethods.assertIsSavedPaymentMethod( acdc2 ); - await utils.fillVisitorsCart( [ products.simple10 ] ); + await utils.fillVisitorsCart( [ products.simple100 ] ); await classicCheckout.visit(); + await classicCheckout.payPalUi.expandPaymentGateway( acdc ); await classicCheckout.payPalUi.assertVaultedPaymentMethodIsDisplayed( acdc ); diff --git a/tests/qa/tests/08-subscription/_test-data/subscription-classic-checkout.data.ts b/tests/qa/tests/08-subscription/_test-data/subscription-classic-checkout.data.ts index cb250471e4..4d255c816b 100644 --- a/tests/qa/tests/08-subscription/_test-data/subscription-classic-checkout.data.ts +++ b/tests/qa/tests/08-subscription/_test-data/subscription-classic-checkout.data.ts @@ -134,8 +134,8 @@ const payPalCustomer: ShopOrder[] = [ products: [ products.subscriptionPayPal ], }, { - // https://inpsyde.atlassian.net/browse/PCP-2894 - title: 'PCP-2894 | PayPal subscription - Transaction - Classic checkout - Free trial order by customer', + // https://inpsyde.atlassian.net/browse/PCP-4894 + title: 'PCP-4894 | PayPal subscription - Transaction - Classic checkout - Free trial order by customer', ...orders.default, payment: { ...payments.payPal, diff --git a/tests/qa/tests/08-subscription/_test-scenarios/subscription-classic-checkout.scenario.ts b/tests/qa/tests/08-subscription/_test-scenarios/subscription-classic-checkout.scenario.ts index 8d2a20d205..a1434aa5cf 100644 --- a/tests/qa/tests/08-subscription/_test-scenarios/subscription-classic-checkout.scenario.ts +++ b/tests/qa/tests/08-subscription/_test-scenarios/subscription-classic-checkout.scenario.ts @@ -32,7 +32,7 @@ const testSubscriptionOrderGuest = ( testOrder: ShopOrder ) => { orderReceived, customerSubscriptions, } ) => { - test.setTimeout( 1.5 * 60 * 1000 ); + test.setTimeout( 2 * 60 * 1000 ); await utils.fillVisitorsCart( products ); await classicCheckout.makeOrder( testOrder ); await orderReceived.assertOrderDetails( testOrder ); @@ -59,8 +59,9 @@ const testSubscriptionOrderGuest = ( testOrder: ShopOrder ) => { await utils.fillVisitorsCart( products ); await classicCheckout.visit(); + await classicCheckout.payPalUi.expandPaymentGateway( payment ); if ( payment.saveToAccount !== false ) { - await classicCheckout.payPalUi.assertVaultedPaymentMethodIsDisplayedOnClassicCheckout( + await classicCheckout.payPalUi.assertVaultedPaymentMethodIsDisplayed( payment ); } else { @@ -95,7 +96,7 @@ const testSubscriptionOrderCustomer = ( testOrder: ShopOrder ) => { orderReceived, customerSubscriptions, } ) => { - test.setTimeout( 1.5 * 60 * 1000 ); + test.setTimeout( 2 * 60 * 1000 ); // Preconditions await customerPaymentMethods.visit(); await customerPaymentMethods.assertIsNotSavedPaymentMethod( @@ -129,8 +130,9 @@ const testSubscriptionOrderCustomer = ( testOrder: ShopOrder ) => { await utils.fillVisitorsCart( products ); await classicCheckout.visit(); + await classicCheckout.payPalUi.expandPaymentGateway( payment ); if ( payment.saveToAccount !== false ) { - await classicCheckout.payPalUi.assertVaultedPaymentMethodIsDisplayedOnClassicCheckout( + await classicCheckout.payPalUi.assertVaultedPaymentMethodIsDisplayed( payment ); } else { diff --git a/tests/qa/tests/_setup/woocommerce.setup.ts b/tests/qa/tests/_setup/woocommerce.setup.ts index 5eb8692912..021c1f2ead 100644 --- a/tests/qa/tests/_setup/woocommerce.setup.ts +++ b/tests/qa/tests/_setup/woocommerce.setup.ts @@ -22,13 +22,6 @@ import { const country = process.env.WC_DEFAULT_COUNTRY || 'usa'; -/** - * Only for Kinsta -setup( 'Clear SSE cache', async ( { cli } ) => { - await cli.clearSseCache(); -} ); -*/ - setup( 'Setup Permalinks', async ( { requestUtils } ) => { await requestUtils.setPermalinks( '/%postname%/' ); } ); diff --git a/tests/qa/utils/admin/pcp-payment-methods.ts b/tests/qa/utils/admin/pcp-payment-methods.ts index 370630b10f..3f1a31ab95 100644 --- a/tests/qa/utils/admin/pcp-payment-methods.ts +++ b/tests/qa/utils/admin/pcp-payment-methods.ts @@ -17,6 +17,8 @@ export class PcpPaymentMethods extends PcpAdminPage { has: this.paymentMethodTitle( title ), } ); paymentMethodContainers = () => this.page.locator( '.ppcp--method-item' ); + onlineCardPaymentsContainer = () => + this.page.locator( '#ppcp-card-payments-card' ); paymentMethodContainer = ( title: string ) => this.paymentMethodContainers().filter( { has: this.paymentMethodTitleContainer( title ), diff --git a/tests/qa/utils/frontend/paypal-popup.ts b/tests/qa/utils/frontend/paypal-popup.ts index 4f63aa5634..80824dcf41 100644 --- a/tests/qa/utils/frontend/paypal-popup.ts +++ b/tests/qa/utils/frontend/paypal-popup.ts @@ -15,18 +15,39 @@ export class PayPalPopup { } // Locators + + usePasswordInsteadButton = () => + this.page.getByRole( 'button', { name: 'Use Password Instead' } ); loginWithPasswordInsteadLink = () => this.page.getByRole( 'link', { name: 'Log in with a password instead', } ); loginWithYourPasswordLink = () => this.page.getByRole( 'link', { name: 'Login with password' } ); + loginWithPasswordInstead = () => + this.loginWithPasswordInsteadLink() + .or( this.usePasswordInsteadButton() ) + .or( this.loginWithYourPasswordLink() ); tryAnotherWayLink = () => this.page.getByRole( 'link', { name: 'Try another way' } ); loginInput = () => this.page.locator( '[name="login_email"]' ); passwordInput = () => this.page.locator( '[name="login_password"]' ); - nextButton = () => this.page.locator( '#btnNext' ); - loginButton = () => this.page.locator( '#btnLogin' ); + nextButton = () => + this.page + .locator( '#btnNext' ) + .or( + this.page.locator( + 'button[data-atomic-wait-intent="Submit_Email"]' + ) + ); + loginButton = () => + this.page + .locator( '#btnLogin' ) + .or( + this.page.locator( + 'button[data-atomic-wait-intent="Submit_Password"]' + ) + ); submitPaymentButton = () => this.page .locator( '#payment-submit-btn' ) @@ -42,6 +63,7 @@ export class PayPalPopup { saveAndContinueButton = () => this.page.getByTestId( 'consentButton' ); cancelLink = () => this.page.locator( '#cancelLink' ); loadSpinnerContainer = () => this.page.locator( '#preloaderSpinner' ); + tryAgainLink = () => this.page.getByRole( 'link', { name: 'Try again' } ); // Actions @@ -56,10 +78,10 @@ export class PayPalPopup { await this.loginInput().fill( email ); - await this.tryLoginWithPasswordInstead(); - await this.tryClickNext(); + await this.tryLoginWithPasswordInstead(); + await this.tryAnotherWay(); await this.passwordInput().fill( password ); @@ -72,11 +94,12 @@ export class PayPalPopup { */ tryLoginWithPasswordInstead = async () => { try { - await this.loginWithPasswordInsteadLink().waitFor( { + await this.loginWithPasswordInstead().waitFor( { state: 'visible', timeout: 4000, } ); - await this.loginWithPasswordInsteadLink().click(); + await this.loginWithPasswordInstead().click(); + await this.page.waitForLoadState(); } catch {} }; @@ -91,6 +114,7 @@ export class PayPalPopup { timeout: 4000, } ); await this.nextButton().click(); + await this.page.waitForLoadState(); } catch {} }; @@ -114,15 +138,11 @@ export class PayPalPopup { await expect( this.loadSpinnerContainer() ).not.toBeVisible(); while ( ! this.page.isClosed() ) { - const submitButton = this.submitPaymentButton(); - if ( ! ( await submitButton.isVisible() ) ) { - break; // No visible button, exit - } - // Race click with popup closure try { await Promise.race( [ - submitButton.click(), + this.submitPaymentButton().click(), + this.tryAgainLink().click(), this.page.waitForEvent( 'close', { timeout: 30 * 1000 } ), // Short timeout to prevent hang ] ); } catch ( error ) { diff --git a/tests/qa/utils/frontend/paypal-ui-classic.ts b/tests/qa/utils/frontend/paypal-ui-classic.ts index fbed39d902..25dffabdd3 100644 --- a/tests/qa/utils/frontend/paypal-ui-classic.ts +++ b/tests/qa/utils/frontend/paypal-ui-classic.ts @@ -333,16 +333,22 @@ export class PayPalUiClassic extends PayPalUi { await this.acdcUseNewPaymentRadio().check(); } + await expect( this.acdcCardNumberInput() ).toBeVisible(); await this.acdcCardNumberInput().fill( card.card_number ); + + await expect( this.acdcCardExpirationInput() ).toBeVisible(); // trick to properly fill expiration date input await this.acdcCardExpirationInput().click(); for ( const char of card.expiration_date ) { await this.page.keyboard.type( char ); await this.page.waitForTimeout( 200 ); } + + await expect( this.acdcCardCvvInput() ).toBeVisible(); await this.acdcCardCvvInput().fill( card.card_cvv ); if ( saveToAccount ) { + await expect( this.acdcSaveToAccountCheckbox() ).toBeVisible(); await this.acdcSaveToAccountCheckbox().check(); } @@ -360,9 +366,14 @@ export class PayPalUiClassic extends PayPalUi { payment: Pcp.Payment, merchant: Pcp.Merchant ) => { - await expect( this.acdcGateway() ).toBeVisible(); - await this.acdcGateway().click(); - await this.acdcSavedCard( payment.card ).click(); + const acdcGateway = this.acdcGateway(); + await expect( acdcGateway ).toBeVisible(); + await acdcGateway.click(); + + const savedCard = this.acdcSavedCard( payment.card ); + await expect( savedCard ).toBeVisible(); + await savedCard.click(); + await this.submitOrder(); await this.replacePayPalAuthToken( merchant ); }; @@ -379,7 +390,9 @@ export class PayPalUiClassic extends PayPalUi { ) ).toBeVisible(); - const popupPromise = this.page.waitForEvent( 'popup' ); + const popupPromise = this.page.waitForEvent( 'popup', { + timeout: 20 * 1000, + } ); await this.submitOrder(); const popup = await popupPromise; const paypal = new PayPalPopup( popup ); @@ -401,11 +414,19 @@ export class PayPalUiClassic extends PayPalUi { ) => { await expect( this.debitOrCreditCardButton() ).toBeVisible(); await this.debitOrCreditCardButton().click(); + + await expect( this.debitOrCreditCardNumberInput() ).toBeVisible(); await this.debitOrCreditCardNumberInput().fill( card.card_number ); + + await expect( this.debitOrCreditCardExpirationInput() ).toBeVisible(); await this.debitOrCreditCardExpirationInput().fill( card.expiration_date ); + + await expect( this.debitOrCreditCardCSCInput() ).toBeVisible(); await this.debitOrCreditCardCSCInput().fill( card.card_cvv ); + + await expect( this.debitOrCreditCardPayNowButton() ).toBeVisible(); await this.debitOrCreditCardPayNowButton().click(); }; @@ -419,12 +440,22 @@ export class PayPalUiClassic extends PayPalUi { ) => { await expect( this.standardCardButtonGateway() ).toBeVisible(); await this.standardCardButtonGateway().click(); + + await expect( this.standardCardButton() ).toBeVisible(); await this.standardCardButton().click(); + + await expect( this.standardCardButtonNumberInput() ).toBeVisible(); await this.standardCardButtonNumberInput().fill( card.card_number ); + + await expect( this.standardCardButtonExpirationInput() ).toBeVisible(); await this.standardCardButtonExpirationInput().fill( card.expiration_date ); + + await expect( this.standardCardButtonCSCInput() ).toBeVisible(); await this.standardCardButtonCSCInput().fill( card.card_cvv ); + + await expect( this.standardCardButtonPayNowButton() ).toBeVisible(); await this.standardCardButtonPayNowButton().click(); }; @@ -436,111 +467,35 @@ export class PayPalUiClassic extends PayPalUi { completePayUponInvoicePayment = async ( birthDate: string ) => { await expect( this.payUponInvoiceGateway() ).toBeVisible(); await this.payUponInvoiceGateway().click(); + + await expect( this.payUponInvoiceBirthDateInput() ).toBeVisible(); await this.payUponInvoiceBirthDateInput().click(); - await this.page.keyboard.type( birthDate ); + await this.page.keyboard.type( birthDate ); // Trick to properly fill date + await this.submitOrder(); }; addCardPaymentMethod = async ( payment: Pcp.Payment ) => { + const { card } = payment; await expect( this.debitCreditCardsGateway() ).toBeVisible(); await this.debitCreditCardsGateway().click(); - await this.acdcCardNumberInput().fill( payment.card.card_number ); - await this.acdcCardExpirationInput().click(); - await this.page.keyboard.type( payment.card.expiration_date ); - await this.acdcCardCvvInput().fill( payment.card.card_cvv ); - await this.addPaymentMethodButton().click(); - }; - // Assertions + await expect( this.acdcCardNumberInput() ).toBeVisible(); + await this.acdcCardNumberInput().fill( card.card_number ); - /** - * Asserts the saved payment method is visible - * - * @param payment - */ - assertVaultedPaymentMethodIsDisplayed = async ( payment: Pcp.Payment ) => { - const { gateway, payPalAccount, card } = payment; - switch ( gateway.shortcut ) { - case 'paypal': - await this.payPalGateway().click(); - // await expect( this.payPalButton() ).toContainText( 'Pay Now' ); - await expect( - this.payPalButtonMoreOptions().or( - // Case on classic checkout - this.usingVaultedPayPalAccountText( - payPalAccount?.email - ) - ) - ).toBeVisible(); - break; - - case 'acdc': - await this.acdcGateway().click(); - await expect( this.acdcSavedCard( card ) ).toBeVisible(); - break; - } - }; + await expect( this.acdcCardExpirationInput() ).toBeVisible(); + await this.acdcCardExpirationInput().click(); + await this.page.keyboard.type( card.expiration_date ); // Trick to properly fill date - /** - * Asserts the saved payment method is visible - * - * @param payment - */ - assertVaultedPaymentMethodIsDisplayedOnClassicCheckout = async ( - payment: Pcp.Payment - ) => { - const { gateway, payPalAccount, card } = payment; - switch ( gateway.shortcut ) { - case 'paypal': - await this.payPalGateway().click(); - await expect( this.payPalButton() ).toContainText( 'Pay Now' ); - // Sometimes instead of vaulted PayPal button following is displayed: - // "Proceed to PayPal" button with text "Using PayPal." - // await expect( - // this.usingVaultedPayPalAccountText( payPalAccount?.email ) - // ).toBeVisible(); - break; - - case 'acdc': - await this.acdcGateway().click(); - await expect( this.acdcSavedCard( card ) ).toBeVisible(); - break; - } - }; + await expect( this.acdcCardCvvInput() ).toBeVisible(); + await this.acdcCardCvvInput().fill( card.card_cvv ); - /** - * Asserts the saved payment method is not visible - * - * @param payment - */ - assertVaultedPaymentMethodIsNotDisplayed = async ( - payment: Pcp.Payment - ) => { - switch ( payment.gateway.shortcut ) { - case 'paypal': - await this.payPalGateway().click(); - await expect( this.payPalButton() ).not.toContainText( - 'Pay Now' - ); - await expect( - this.payPalButtonMoreOptions() - ).not.toBeVisible(); - await expect( - this.usingVaultedPayPalAccountText( - payment.payPalAccount?.email - ) - ).not.toBeVisible(); - break; - - case 'acdc': - await this.acdcGateway().click(); - await expect( - this.acdcSavedCard( payment.card ) - ).not.toBeVisible(); - break; - } + await expect( this.addPaymentMethodButton() ).toBeVisible(); + await this.addPaymentMethodButton().click(); }; + // Assertions + /** * - Asserts PayPal buttons classic container is visible. * - Compares actual PayPal buttons container screenshot to expected. diff --git a/tests/qa/utils/frontend/paypal-ui.ts b/tests/qa/utils/frontend/paypal-ui.ts index 09dc19f9c7..13954d6fc1 100644 --- a/tests/qa/utils/frontend/paypal-ui.ts +++ b/tests/qa/utils/frontend/paypal-ui.ts @@ -107,7 +107,9 @@ export class PayPalUi { .frameLocator( '#braintree-hosted-field-expirationDate' ) .locator( '#expiration' ); fastlaneCvvInput = () => - this.page.frameLocator( '#braintree-hosted-field-cvv' ).locator( '#cvv' ); + this.page + .frameLocator( '#braintree-hosted-field-cvv' ) + .locator( '#cvv' ); fastlaneCardHolderInput = () => this.page .frameLocator( '#braintree-hosted-field-cardholderName' ) @@ -182,7 +184,9 @@ export class PayPalUi { * Clicks PayPal button to open popup */ openPayPalPupup = async (): Promise< PayPalPopup > => { - const popupPromise = this.page.waitForEvent( 'popup' ); + const popupPromise = this.page.waitForEvent( 'popup', { + timeout: 20 * 1000, + } ); await expect( this.payPalButton() ).toBeVisible(); await this.payPalButton().click(); @@ -195,7 +199,9 @@ export class PayPalUi { * Clicks Pay Later button to open popup */ openPayLaterPupup = async (): Promise< PayPalPopup > => { - const popupPromise = this.page.waitForEvent( 'popup' ); + const popupPromise = this.page.waitForEvent( 'popup', { + timeout: 20 * 1000, + } ); await expect( this.payLaterButton() ).toBeVisible(); await this.payLaterButton().click(); @@ -208,7 +214,9 @@ export class PayPalUi { * Clicks Venmo button to open popup */ openVenmoPupup = async (): Promise< PayPalPopup > => { - const popupPromise = this.page.waitForEvent( 'popup' ); + const popupPromise = this.page.waitForEvent( 'popup', { + timeout: 20 * 1000, + } ); await expect( this.venmoButton() ).toBeVisible(); await this.venmoButton().click(); @@ -235,7 +243,7 @@ export class PayPalUi { // Map to the tested method switch ( shortcut ) { case 'paypal': - await this.payPalGateway().click(); + // await this.payPalGateway().click(); popup = await this.openPayPalPupup(); if ( payment.isVaulted ) { @@ -262,7 +270,6 @@ export class PayPalUi { case 'acdc': if ( payment.isVaulted ) { - await this.assertVaultedPaymentMethodIsDisplayed( payment ); await this.completeAcdcVaultedPayment( payment, merchant ); break; } @@ -303,6 +310,7 @@ export class PayPalUi { * Submits order and waits for page load */ submitOrder = async () => { + await expect( this.placeOrderButton() ).toBeVisible(); await this.placeOrderButton().click(); await this.page.waitForLoadState(); }; @@ -347,16 +355,22 @@ export class PayPalUi { // Needed to assert payment via PayPal API // await this.acdcCardholderNameInput().fill( card.card_holder ); + await expect( this.acdcCardNumberInput() ).toBeVisible(); await this.acdcCardNumberInput().fill( card.card_number ); - // trick to properly fill expiration date input + + await expect( this.acdcCardExpirationInput() ).toBeVisible(); await this.acdcCardExpirationInput().click(); + // trick to properly fill expiration date input for ( const char of card.expiration_date ) { await this.page.keyboard.type( char ); await this.page.waitForTimeout( 200 ); } + + await expect( this.acdcCardCvvInput() ).toBeVisible(); await this.acdcCardCvvInput().fill( card.card_cvv ); if ( saveToAccount ) { + await expect( this.acdcSaveToAccountCheckbox() ).toBeVisible(); await this.acdcSaveToAccountCheckbox().check(); } @@ -407,10 +421,11 @@ export class PayPalUi { */ provideFastlaneEmail = async ( email: string ) => { await expect( this.fastlaneEmailInput() ).toBeVisible(); - await expect( this.fastlaneContinueButton() ).toBeVisible(); - await this.fastlaneEmailInput().fill( email ); + + await expect( this.fastlaneContinueButton() ).toBeVisible(); await this.fastlaneContinueButton().click(); + await this.page.waitForLoadState( 'networkidle' ); }; @@ -441,11 +456,18 @@ export class PayPalUi { const { card } = payment; await expect( this.fastlaneGateway() ).toBeVisible(); await this.fastlaneGateway().click(); + + await expect( this.fastlaneCardNumberInput() ).toBeVisible(); await this.fastlaneCardNumberInput().fill( card.card_number ); + + await expect( this.fastlaneExpirationDateInput() ).toBeVisible(); await this.fastlaneExpirationDateInput().pressSequentially( card.expiration_date ); + + await expect( this.fastlaneCvvInput() ).toBeVisible(); await this.fastlaneCvvInput().fill( card.card_cvv ); + // TODO: clarify Cardholder name presence (bug PCP-4623) if ( await this.fastlaneCardHolderInput().isVisible() ) { await this.fastlaneCardHolderInput().fill( 'Gary From-USA' ); @@ -469,6 +491,25 @@ export class PayPalUi { completePayUponInvoicePayment = async ( ...args ) => console.log( `TODO: completePayUponInvoicePayment for block pages` ); + /** + * Clicks payment gateway to make visible payment form or buttons + * + * @param payment + */ + expandPaymentGateway = async ( payment: Pcp.Payment ) => { + switch ( payment.gateway.shortcut ) { + case 'paypal': + await expect( this.payPalGateway() ).toBeVisible(); + await this.payPalGateway().click(); + break; + + case 'acdc': + await expect( this.acdcGateway() ).toBeVisible(); + await this.acdcGateway().click(); + break; + } + }; + // Assertions /** @@ -477,17 +518,17 @@ export class PayPalUi { * @param payment */ assertVaultedPaymentMethodIsDisplayed = async ( payment: Pcp.Payment ) => { - switch ( payment.gateway.shortcut ) { + const { gateway, card } = payment; + switch ( gateway.shortcut ) { case 'paypal': - // Uncomment when bug is fixed - // await expect( this.payPalButton() ).toContainText( 'Pay Now' ); - // await expect( this.payPalButtonMoreOptions() ).toBeVisible(); + await expect( this.payPalButton() ).toContainClass( + 'paypal-button-wallet' + ); // Class applied for vaulted button + await expect( this.payPalButtonMoreOptions() ).toBeVisible(); break; case 'acdc': - await expect( - this.acdcSavedCard( payment.card ) - ).toBeVisible(); + await expect( this.acdcSavedCard( card ) ).toBeVisible(); break; } }; @@ -502,9 +543,9 @@ export class PayPalUi { ) => { switch ( payment.gateway.shortcut ) { case 'paypal': - await expect( this.payPalButton() ).not.toContainText( - 'Pay Now' - ); + await expect( this.payPalButton() ).not.toContainClass( + 'paypal-button-wallet' + ); // Class applied for vaulted button await expect( this.payPalButtonMoreOptions() ).not.toBeVisible(); diff --git a/tests/qa/utils/pcp-api.ts b/tests/qa/utils/pcp-api.ts index c6311ddc50..1ffad3f1b9 100644 --- a/tests/qa/utils/pcp-api.ts +++ b/tests/qa/utils/pcp-api.ts @@ -2,6 +2,7 @@ * External dependencies */ import { + expect, RequestUtils, WooCommerceApi as WooCommerceApiBase, } from '@inpsyde/playwright-utils/build'; @@ -163,7 +164,7 @@ export class PcpApi extends WooCommerceApiBase { return []; } - const MAX_RETRY_COUNT = 6; + const MAX_RETRY_COUNT = 10; const RETRY_INTERVAL_MS = 1000; let retryCount = 0; @@ -217,19 +218,31 @@ export class PcpApi extends WooCommerceApiBase { const billingId = await this.getPayPalSubscriptionBillingId( subscriptionId ); + + const data = { + id: 'NOT-IMPORTANT', + event_type: 'PAYMENT.SALE.COMPLETED', + resource: { + billing_agreement_id: billingId, + id: 'NOT-IMPORTANT', + }, + }; + const response = await this.requestUtils.request.post( urls.payPalWebhook, - { - data: { - id: 'NOT-IMPORTANT', - event_type: 'PAYMENT.SALE.COMPLETED', - resource: { - billing_agreement_id: billingId, - id: 'NOT-IMPORTANT', - }, - }, - } + { data } ); - return response.ok(); + await expect( response.ok() ).toBeTruthy(); + console.log( await response.json() ); + + // 2nd request to trigger renewal (stopped working from v3.2.0) + const response2 = await this.requestUtils.request.post( + urls.payPalWebhook, + { data } + ); + await expect( response2.ok() ).toBeTruthy(); + console.log( await response2.json() ); + + return response.ok() && response2.ok(); }; } diff --git a/tests/qa/wp-cli.yml b/tests/qa/wp-cli.yml new file mode 100644 index 0000000000..3cf7565b3b --- /dev/null +++ b/tests/qa/wp-cli.yml @@ -0,0 +1,2 @@ +apache_modules: + - mod_rewrite