Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ACH E2E tests #3931

Merged
merged 37 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
971bdb5
Close browser window after setup
rafaelzaleski Feb 21, 2025
030cce4
Enable ACH feature flag in the E2E setup
rafaelzaleski Feb 21, 2025
b32f102
Add tests for payment processing.
rafaelzaleski Feb 21, 2025
c0a14fb
Ensure billing fields are collapsed in Blocks Checkout
rafaelzaleski Feb 21, 2025
83b6acc
Add helper for ACH checkout setup and failure tests
rafaelzaleski Feb 21, 2025
165312b
Improvements to how the blocks checkout UI is handled.
rafaelzaleski Feb 21, 2025
fccca46
Add Stripe listener to E2E setup
rafaelzaleski Feb 22, 2025
9d36ce5
Add ACH tests for blocks
rafaelzaleski Feb 22, 2025
1bdb34a
Move ACH helpers to payments utils
rafaelzaleski Feb 22, 2025
614bb0d
Add util to handle admin interactions
rafaelzaleski Feb 22, 2025
2f37dcf
Fix admin util
rafaelzaleski Feb 22, 2025
be6c210
Add admin util to list of imports
rafaelzaleski Feb 22, 2025
ec749e4
Add support for shortcode checkout in ACH utils
rafaelzaleski Feb 22, 2025
012600f
Use ACH utils in blocks tests
rafaelzaleski Feb 22, 2025
58f0284
Merge branch 'develop' into e2e/3749-add-ach-tests
rafaelzaleski Feb 23, 2025
b8cac3a
Add ACH tests for shortcode checkout
rafaelzaleski Feb 22, 2025
1e7f1df
Skip webhook validation for E2E tests
rafaelzaleski Feb 25, 2025
71c6c49
Revert change from e2e setup
rafaelzaleski Feb 25, 2025
8df527c
Fix project reference in E2E commands
rafaelzaleski Feb 25, 2025
7962be3
Fix to run E2E setup in detached mode
rafaelzaleski Feb 25, 2025
aa1a5cf
Move tests to new folders
rafaelzaleski Feb 25, 2025
28d2e62
Revert "Revert change from e2e setup"
rafaelzaleski Feb 25, 2025
d609d43
Merge branch 'develop' into e2e/3749-add-ach-tests
rafaelzaleski Feb 25, 2025
a602a76
Fix setup in CI
rafaelzaleski Feb 25, 2025
33c3ae0
Debug containers in the CI
rafaelzaleski Feb 25, 2025
7dc27ac
Fix logic in the E2E setup
rafaelzaleski Feb 25, 2025
38e72b8
Skip webhook validation only in test mode
rafaelzaleski Feb 25, 2025
0db0ca6
Revert "Debug containers in the CI"
rafaelzaleski Feb 25, 2025
611235e
Merge branch 'develop' into e2e/3749-add-ach-tests
rafaelzaleski Feb 25, 2025
f4d9648
Standardize test steps in both shortcode and Blocks tests.
rafaelzaleski Feb 25, 2025
2af2187
Add util to toggle the ACH in settings
rafaelzaleski Feb 25, 2025
6dbb42d
Add improvements to performance
rafaelzaleski Feb 25, 2025
6ff6cfc
FIx flaky tests in shortcode UI
rafaelzaleski Feb 25, 2025
aa3d9de
Fix flaky tests shortcode UI
rafaelzaleski Feb 25, 2025
99fa8eb
Improve env variable check
rafaelzaleski Feb 27, 2025
c123216
Merge branch 'develop' into e2e/3749-add-ach-tests
rafaelzaleski Feb 27, 2025
103fdec
Merge branch 'develop' into e2e/3749-add-ach-tests
rafaelzaleski Feb 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions includes/class-wc-stripe-webhook-handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,14 @@ public function validate_request( $request_headers, $request_body ) {
return WC_Stripe_Webhook_State::VALIDATION_FAILED_EMPTY_BODY;
}

// Skip validation for E2E tests in test mode.
if (
filter_var( getenv( 'E2E_TESTING' ), FILTER_VALIDATE_BOOLEAN )
&& WC_Stripe_Mode::is_test()
) {
return WC_Stripe_Webhook_State::VALIDATION_SUCCEEDED;
}

if ( empty( $this->secret ) ) {
return WC_Stripe_Webhook_State::VALIDATION_FAILED_EMPTY_SECRET;
}
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/bin/down.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ set -e
. ./tests/e2e/bin/common.sh

step "Stopping E2E docker containers"
CWD="$CWD" redirect_output docker-compose -p wcstripe-e2e down
CWD="$CWD" redirect_output docker compose -p wcstripe-e2e down
9 changes: 8 additions & 1 deletion tests/e2e/bin/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ if ! docker info > /dev/null 2>&1; then
fi

step "Starting E2E docker containers"
CWD="$CWD" E2E_ROOT="$E2E_ROOT" redirect_output docker compose -p wcstripe-e2e -f "$E2E_ROOT"/env/docker-compose.yml up --build --force-recreate -d wordpress
if [ "$CI" = "true" ]; then
CWD="$CWD" E2E_ROOT="$E2E_ROOT" redirect_output docker compose -p wcstripe-e2e -f "$E2E_ROOT"/env/docker-compose.yml up --build --force-recreate -d
else
CWD="$CWD" E2E_ROOT="$E2E_ROOT" redirect_output docker compose -p wcstripe-e2e --env-file "$E2E_ROOT"/config/local.env -f "$E2E_ROOT"/env/docker-compose.yml up --build --force-recreate -d
fi
Comment on lines +37 to +41
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Locally, the environment variables are defined in "$E2E_ROOT"/config/local.env, but in CI they’re set in the workflow configuration. We need these variables to start the Stripe listener container.


step "Configuring WordPress"
# Wait for containers to be started up before setup.
Expand Down Expand Up @@ -133,6 +137,9 @@ redirect_output cli wp plugin activate woocommerce-gateway-stripe
echo " - Updating WooCommerce Gateway Stripe settings"
redirect_output cli wp option set woocommerce_stripe_settings --format=json "{\"enabled\":\"yes\",\"title\":\"Credit Card (Stripe)\",\"description\":\"Pay with your credit card via Stripe.\",\"api_credentials\":\"\",\"testmode\":\"yes\",\"test_publishable_key\":\"${STRIPE_PUB_KEY}\",\"test_secret_key\":\"${STRIPE_SECRET_KEY}\",\"publishable_key\":\"\",\"secret_key\":\"\",\"webhook\":\"\",\"test_webhook_secret\":\"\",\"webhook_secret\":\"\",\"inline_cc_form\":\"no\",\"statement_descriptor\":\"\",\"short_statement_descriptor\":\"\",\"capture\":\"yes\",\"payment_request\":\"yes\",\"payment_request_button_type\":\"buy\",\"payment_request_button_theme\":\"dark\",\"payment_request_button_locations\":[\"product\",\"cart\",\"checkout\"],\"payment_request_button_size\":\"default\",\"saved_cards\":\"yes\",\"logging\":\"no\",\"upe_checkout_experience_enabled\":\"yes\"}"

echo " - Enabling the ACH feature flag"
redirect_output cli wp option update _wcstripe_feature_lpm_ach 'yes'

step "Installing Woo Subscriptions"
echo " - Fetching latest version"
LATEST_RELEASE_ASSET_ID=$(curl -sH "Authorization: token $GITHUB_TOKEN" https://api.github.com/repos/woocommerce/woocommerce-subscriptions/releases/latest | jq -r '.assets[0].id')
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/bin/up.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ set -e
. ./tests/e2e/bin/common.sh

step "Starting E2E docker containers"
CWD="$CWD" redirect_output docker-compose -f "$E2E_ROOT/env/docker-compose.yml" up -d
CWD="$CWD" redirect_output docker compose -p wcstripe-e2e --env-file $E2E_ROOT/config/local.env -f "$E2E_ROOT/env/docker-compose.yml" up -d
9 changes: 9 additions & 0 deletions tests/e2e/env/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ volumes:
dockerdirectory:

services:
stripe:
container_name: wcstripe-e2e-stripe-listener
image: stripe/stripe-cli
restart: unless-stopped
depends_on:
- wordpress
command: "listen --api-key=${STRIPE_SECRET_KEY} --forward-to http://wordpress/?wc-api=wc_stripe"
wordpress:
build: .
image: wordpress
Expand All @@ -15,6 +22,8 @@ services:
- "8088:80"
env_file:
- default.env
environment:
- E2E_TESTING=true
volumes:
- ./docker/wordpress:/var/www/html/
- ./docker/logs/apache2/:/var/log/apache2
Expand Down
105 changes: 105 additions & 0 deletions tests/e2e/tests/checkout/blocks/lpms/ach.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { test, expect } from '@playwright/test';
import config from 'config';
import { admin, payments, api, user } from '../../../../utils';

const {
emptyCart,
setupCart,
setupBlocksCheckout,
fillACHBankDetails,
setupACHCheckout,
} = payments;

test.describe( 'ACH payment tests @blocks', () => {
let username, userEmail;

test.beforeAll( async ( { browser } ) => {
await test.step( 'Setup test environment', async () => {
// Create test user
const randomString = Date.now();
userEmail =
randomString + '+' + config.get( 'users.customer.email' );
username =
randomString + '.' + config.get( 'users.customer.username' );

const testUser = {
...config.get( 'users.customer' ),
...config.get( 'addresses.customer' ),
email: userEmail,
username,
};
await api.create.customer( testUser );

// Enable ACH in admin
await admin.togglePaymentMethod(
browser,
'ACH Direct Debit',
true
);
Comment on lines +34 to +38
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided not to disable ACH after the tests because another file (the shortcode tests) uses it. If one test disables the payment method in the middle of another test execution, it could cause issues. Since test environments aren’t intended to be reusable, this is acceptable.

} );
} );

test.describe.configure( { mode: 'parallel' } );

test( 'customer can pay with ACH using valid bank details @smoke', async ( {
page,
} ) => {
await setupACHCheckout( page, 'blocks' );
await fillACHBankDetails( page );
await page.locator( 'text=Place order' ).click();
await page.waitForURL( '**/checkout/order-received/**' );
await expect( page.locator( 'h1.entry-title' ) ).toHaveText(
'Order received'
);
} );

test( 'customer can save and reuse ACH payment method @smoke', async ( {
page,
} ) => {
// First order - Save the payment method
await test.step(
'Save payment method during first checkout',
async () => {
await user.login(
page,
username,
config.get( 'users.customer.password' )
);
await setupACHCheckout( page, 'blocks' );
await fillACHBankDetails( page );
await page
.locator(
'.wc-block-components-payment-methods__save-card-info'
)
.click();
await page.locator( 'text=Place order' ).click();
await page.waitForURL( '**/checkout/order-received/**' );
await expect( page.locator( 'h1.entry-title' ) ).toHaveText(
'Order received'
);
}
);

// Second order - Use saved payment method
await test.step(
'Use saved payment method for second checkout',
async () => {
await emptyCart( page );
await setupCart( page );
await setupBlocksCheckout(
page,
config.get( 'addresses.customer.billing' )
);
await page
.locator( 'label' )
.filter( { hasText: 'Checking account ending in' } )
.click();
await page.locator( 'text=Place order' ).click();
await page.waitForURL( '**/checkout/order-received/**' );
await expect( page.locator( 'h1.entry-title' ) ).toHaveText(
'Order received'
);
}
);
} );
} );
107 changes: 107 additions & 0 deletions tests/e2e/tests/checkout/shortcode/lpms/ach.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { test, expect } from '@playwright/test';
import config from 'config';
import { admin, payments, api, user } from '../../../../utils';

const {
emptyCart,
setupCart,
setupShortcodeCheckout,
fillACHBankDetails,
setupACHCheckout,
} = payments;

test.describe( 'ACH payment tests @shortcode', () => {
let username, userEmail;

test.beforeAll( async ( { browser } ) => {
await test.step( 'Setup test environment', async () => {
// Create test user
const randomString = Date.now();
userEmail =
randomString + '+' + config.get( 'users.customer.email' );
username =
randomString + '.' + config.get( 'users.customer.username' );

const testUser = {
...config.get( 'users.customer' ),
...config.get( 'addresses.customer' ),
email: userEmail,
username,
};
await api.create.customer( testUser );

// Enable ACH in admin
await admin.togglePaymentMethod(
browser,
'ACH Direct Debit',
true
);
} );
} );

test.describe.configure( { mode: 'parallel' } );

test( 'customer can pay with ACH using valid bank details @smoke', async ( {
page,
} ) => {
await setupACHCheckout( page, 'shortcode' );
await fillACHBankDetails( page );
await page.locator( 'text=Place order' ).click();
await page.waitForURL( '**/checkout/order-received/**' );
await expect( page.locator( 'h1.entry-title' ) ).toHaveText(
'Order received'
);
} );

test( 'customer can save and reuse ACH payment method @smoke', async ( {
page,
} ) => {
// First order - Save the payment method
await test.step(
'Save payment method during first checkout',
async () => {
await user.login(
page,
username,
config.get( 'users.customer.password' )
);
await setupACHCheckout( page, 'shortcode' );
await fillACHBankDetails( page );
await page
.getByRole( 'checkbox', {
name: 'Save payment information to',
} )
.click();
await page.locator( 'text=Place order' ).click();
await page.waitForURL( '**/checkout/order-received/**' );
await expect( page.locator( 'h1.entry-title' ) ).toHaveText(
'Order received'
);
}
);

// Second order - Use saved payment method
await test.step(
'Use saved payment method for second checkout',
async () => {
await emptyCart( page );
await setupCart( page );
await setupShortcodeCheckout(
page,
config.get( 'addresses.customer.billing' )
);
await page.getByText( 'ACH Direct Debit' ).click();
await page.waitForTimeout( 1000 );
await page
.locator( '.woocommerce-SavedPaymentMethods-token' )
.first()
.click();
await page.locator( 'text=Place order' ).click();
await page.waitForURL( '**/checkout/order-received/**' );
await expect( page.locator( 'h1.entry-title' ) ).toHaveText(
'Order received'
);
}
);
} );
} );
4 changes: 4 additions & 0 deletions tests/e2e/tests/default.setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ setup( 'Disable legacy checkout experience', async ( { browser } ) => {
await expect(
page.getByTestId( 'legacy-checkout-experience-checkbox' )
).not.toBeChecked();

await adminContext.close();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you run the tests in headed mode, these pages never closed. This fixes that issue.

} );

setup( 'enable Link', async ( { browser } ) => {
Expand All @@ -36,4 +38,6 @@ setup( 'enable Link', async ( { browser } ) => {

await expect( page.getByText( 'Settings saved.' ) ).toBeDefined();
await expect( page.getByLabel( 'Link by Stripe Input' ) ).toBeChecked();

await adminContext.close();
} );
53 changes: 53 additions & 0 deletions tests/e2e/utils/admin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { expect } from '@playwright/test';

/**
* Get a new admin page with admin context.
* @param {Browser} browser Playwright browser fixture.
* @returns {Promise<{context: BrowserContext, page: Page}>} The admin context and page.
*/
export const getAdminPage = async ( browser ) => {
const context = await browser.newContext( {
storageState: process.env.ADMINSTATE,
} );
const page = await context.newPage();
return { context, page };
};

/**
* Enable or disable a payment method in Stripe settings.
* @param {Browser} browser Playwright browser fixture.
* @param {string} methodName The payment method name as shown in admin.
* @param {boolean} enable Whether to enable or disable the payment method.
*/
export const togglePaymentMethod = async (
browser,
methodName,
enable = true
) => {
const { context, page } = await getAdminPage( browser );

try {
await page.goto(
'/wp-admin/admin.php?page=wc-settings&tab=checkout&section=stripe&panel=methods'
);

const checkbox = page.getByRole( 'checkbox', {
name: methodName,
} );
const isChecked = await checkbox.isChecked();

if ( ( enable && ! isChecked ) || ( ! enable && isChecked ) ) {
await checkbox.click();

// When disabling, we need to click the remove button
if ( ! enable ) {
await page.getByRole( 'button', { name: 'Remove' } ).click();
}

await page.click( 'text=Save changes' );
await expect( page.getByText( 'Settings saved.' ) ).toBeDefined();
}
} finally {
await context.close();
}
};
2 changes: 2 additions & 0 deletions tests/e2e/utils/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import * as api from './api';
import * as payments from './payments';
import * as user from './user';
import * as admin from './admin';

module.exports = {
admin,
api,
payments,
user,
Expand Down
Loading
Loading