Skip to content

Commit

Permalink
Add more e2e tests
Browse files Browse the repository at this point in the history
  • Loading branch information
annemirasol committed Feb 27, 2025
1 parent 75efdfd commit 55c841c
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 66 deletions.
129 changes: 129 additions & 0 deletions tests/e2e/tests/checkout/blocks/retries.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { test, expect } from '@playwright/test';
import config from 'config';
import { payments } from '../../../utils';

const {
emptyCart,
setupCart,
setupBlocksCheckout,
fillCreditCardDetails,
handleCheckout3DSChallenge,
clickPlaceOrder,
handleCheckoutCashAppPay,
} = payments;

test.beforeAll( 'enable Cash App Pay', async ( { browser } ) => {
const adminContext = await browser.newContext( {
storageState: process.env.ADMINSTATE,
} );
const page = await adminContext.newPage();

await page.goto(
'/wp-admin/admin.php?page=wc-settings&tab=checkout&section=stripe&panel=methods'
);
await page.getByLabel( 'Cash App Pay' ).check();
await page.click( 'text=Save changes' );

await expect( page.getByText( 'Settings saved.' ) ).toBeDefined();
await expect( page.getByLabel( 'Cash App Pay' ) ).toBeChecked();
} );

test.beforeEach( async ( { page } ) => {
await emptyCart( page );
await setupCart( page );
await setupBlocksCheckout(
page,
config.get( 'addresses.customer.billing' )
);
} );
/**
* When retrying payments, we will reuse a compatible payment intent, if the order already has one.
* In addition, the payment method ID is included when generating the idempotency key
* when creating a payment intent.
*
* This test verifies that the same payment method type can be used when retrying a payment, e.g.
* chaging from one credit card to another.
*/
test( 'customer can retry payment, with a different card @smoke', async ( {
page,
} ) => {
await fillCreditCardDetails( page, config.get( 'cards.declined' ) );
await clickPlaceOrder( page );

// Expect the order to fail
await expect(
page.locator( '.wc-block-store-notice.is-error' )
).toBeVisible();

// Change to a working card
await fillCreditCardDetails( page, config.get( 'cards.basic' ) );
await clickPlaceOrder( page );
await page.waitForURL( '**/order-received/**' );

// Expect the order to succeed
await expect( page.locator( 'h1.entry-title' ) ).toHaveText(
'Order received'
);
} );

/**
* When retrying payments, we will reuse a compatible payment intent, if the order already has one.
* In addition, the payment method ID is included when generating the idempotency key
* when creating a payment intent.
*
* This test verifies that the same payment method type can be used when retrying the same payment,
* after changing the billing details.
*/
test( 'customer can retry payment, with changed billing details @smoke', async ( {
page,
} ) => {
await fillCreditCardDetails( page, config.get( 'cards.3ds' ) );
await clickPlaceOrder( page );

// Fail the 3DS challenge
await handleCheckout3DSChallenge( page, 'fail' );

// Change billing details
await page.getByLabel( 'ZIP Code' ).fill( '12345' );

// Retry the payment
await clickPlaceOrder( page );

// Complete the 3DS challenge
await handleCheckout3DSChallenge( page );

// Expect the order to succeed
await page.waitForURL( '**/order-received/**' );

// Expect the order to succeed
await expect( page.locator( 'h1.entry-title' ) ).toHaveText(
'Order received'
);
} );

/**
* The idempotency key for creating a payment intent includes the payment method ID.
*
* This test verifies that a different payment method type can be used when retrying a payment
* for the same order.
*/
test( 'customer can retry payment, using a different payment method @smoke', async ( {
page,
} ) => {
await fillCreditCardDetails( page, config.get( 'cards.declined' ) );
await clickPlaceOrder( page );

// Expect the order to fail
await expect(
page.locator( '.wc-block-store-notice.is-error' )
).toBeVisible();

// Change to Cash App Pay
await handleCheckoutCashAppPay( page, '.wcstripe-payment-element' );

// Expect the order to succeed
await page.waitForURL( '**/order-received/**' );
await expect( page.locator( 'h1.entry-title' ) ).toHaveText(
'Order received'
);
} );
77 changes: 19 additions & 58 deletions tests/e2e/tests/checkout/shortcode/retries.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ const {
setupShortcodeCheckout,
fillCreditCardDetailsShortcode,
handleCheckout3DSChallenge,
clickPlaceOrder,
handleCheckoutCashAppPay,
} = payments;

test.beforeAll( 'enable Cash App Pay', async ( { browser } ) => {
Expand All @@ -26,6 +28,14 @@ test.beforeAll( 'enable Cash App Pay', async ( { browser } ) => {
await expect( page.getByLabel( 'Cash App Pay' ) ).toBeChecked();
} );

test.beforeEach( async ( { page } ) => {
await emptyCart( page );
await setupCart( page );
await setupShortcodeCheckout(
page,
config.get( 'addresses.customer.billing' )
);
} );
/**
* When retrying payments, we will reuse a compatible payment intent, if the order already has one.
* In addition, the payment method ID is included when generating the idempotency key
Expand All @@ -37,28 +47,18 @@ test.beforeAll( 'enable Cash App Pay', async ( { browser } ) => {
test( 'customer can retry payment, with a different card @smoke', async ( {
page,
} ) => {
await emptyCart( page );
await setupCart( page );
await setupShortcodeCheckout(
page,
config.get( 'addresses.customer.billing' )
);
await fillCreditCardDetailsShortcode(
page,
config.get( 'cards.declined' )
);
await page
.getByRole( 'button', { name: 'Place order' } )
.dispatchEvent( 'click' );
await clickPlaceOrder( page );

// Expect the order to fail
await expect( page.locator( '.woocommerce-error' ) ).toBeVisible();

// Change to a working card
// Change to a working card, and retry the payment.
await fillCreditCardDetailsShortcode( page, config.get( 'cards.basic' ) );
await page
.getByRole( 'button', { name: 'Place order' } )
.dispatchEvent( 'click' );
await clickPlaceOrder( page );
await page.waitForURL( '**/order-received/**' );

// Expect the order to succeed
Expand All @@ -78,16 +78,8 @@ test( 'customer can retry payment, with a different card @smoke', async ( {
test( 'customer can retry payment, with changed billing details @smoke', async ( {
page,
} ) => {
await emptyCart( page );
await setupCart( page );
await setupShortcodeCheckout(
page,
config.get( 'addresses.customer.billing' )
);
await fillCreditCardDetailsShortcode( page, config.get( 'cards.3ds' ) );
await page
.getByRole( 'button', { name: 'Place order' } )
.dispatchEvent( 'click' );
await clickPlaceOrder( page );

// Fail the 3DS challenge
await handleCheckout3DSChallenge( page, 'fail' );
Expand All @@ -96,9 +88,7 @@ test( 'customer can retry payment, with changed billing details @smoke', async (
await page.fill( '#billing_postcode', '12345' );

// Retry the payment
await page
.getByRole( 'button', { name: 'Place order' } )
.dispatchEvent( 'click' );
await clickPlaceOrder( page );

// Complete the 3DS challenge
await handleCheckout3DSChallenge( page );
Expand All @@ -121,50 +111,21 @@ test( 'customer can retry payment, with changed billing details @smoke', async (
test( 'customer can retry payment, using a different payment method @smoke', async ( {
page,
} ) => {
await emptyCart( page );
await setupCart( page );
await setupShortcodeCheckout(
page,
config.get( 'addresses.customer.billing' )
);
await fillCreditCardDetailsShortcode(
page,
config.get( 'cards.declined' )
);
await page
.getByRole( 'button', { name: 'Place order' } )
.dispatchEvent( 'click' );
await clickPlaceOrder( page );

// Expect the order to fail
await expect( page.locator( '.woocommerce-error' ) ).toBeVisible();

// Change to Cash App Pay
await page.getByText( 'Cash App Pay' ).click();
await page
.getByRole( 'button', { name: 'Place order' } )
.dispatchEvent( 'click' );

// Expect a modal to appear
const simulateScanButton = await page
.frameLocator( 'iframe[name^="__privateStripeFrame"]' )
.first()
.frameLocator( 'iframe[title="QR Code Instructions"]' )
.getByRole( 'button', { name: 'Simulate scan' } );

const context = await page.context();
const [ paymentPage ] = await Promise.all( [
context.waitForEvent( 'page' ),
simulateScanButton.dispatchEvent( 'click' ),
] );

await paymentPage.waitForLoadState();
await paymentPage
.getByRole( 'link', { name: 'Authorize Test Payment' } )
.click();
await paymentPage.waitForURL( '**/order-received/**' );
await handleCheckoutCashAppPay( page );

// Expect the order to succeed
await expect( paymentPage.locator( 'h1.entry-title' ) ).toHaveText(
await page.waitForURL( '**/order-received/**' );
await expect( page.locator( 'h1.entry-title' ) ).toHaveText(
'Order received'
);
} );
80 changes: 72 additions & 8 deletions tests/e2e/utils/payments.js
Original file line number Diff line number Diff line change
Expand Up @@ -390,27 +390,91 @@ export const fillACHBankDetails = async ( page ) => {
export async function handleCheckout3DSChallenge( page, action = 'authorize' ) {
const outerFrameLocator = page
.locator( 'iframe[name^="__privateStripeFrame"]' )
.first()
.contentFrame();
.contentFrame()
.first();
const innerFrameLocator = outerFrameLocator.frameLocator(
'iframe[name="stripe-challenge-frame"]'
);

// Wait for the challenge modal to be ready -- the inner frame is "visible"
// and the loading indicator is hidden.
await innerFrameLocator.owner().waitFor();
await outerFrameLocator
.locator( '.LightboxModalLoadingIndicator' )
.waitFor( { state: 'hidden' } );
await expect( innerFrameLocator.owner() ).toBeVisible();
await expect(
outerFrameLocator.locator( '.LightboxModalLoadingIndicator' )
).toBeHidden();

const buttonId =
action === 'authorize'
? '#test-source-authorize-3ds'
: '#test-source-fail-3ds';
await innerFrameLocator.locator( buttonId ).waitFor( { state: 'visible' } );
await expect( innerFrameLocator.locator( buttonId ) ).toBeVisible();
await innerFrameLocator.locator( buttonId ).click();

if ( action === 'fail' ) {
await innerFrameLocator.owner().waitFor( { state: 'detached' } );
await expect( innerFrameLocator.owner() ).toBeHidden();
}
}

/**
* This roundabout way of clicking the Place Order button is an
* attempt to reduce the flakiness.
* @param {Page} page Playwright page fixture.
*/
export async function clickPlaceOrder( page ) {
// Wait for the button to be enabled (i.e. clickable), to wait
// for any logic we are potentially depending on.
await expect(
page.getByRole( 'button', { name: 'Place order' } )
).toBeEnabled();

// Dispatch a click event, instead of clicking the button directly,
// to reduce "missed" clicks.
await page
.getByRole( 'button', { name: 'Place order' } )
.dispatchEvent( 'click' );
}

/**
* Handles the Cash App Pay payment on the checkout page.
* @param {Page} page Playwright page fixture.
*/
export async function handleCheckoutCashAppPay(
page,
paymentElementSelector = '#wc-stripe_cashapp-upe-form'
) {
await page.getByText( 'Cash App Pay' ).click();
await expect(
page
.frameLocator(
`${ paymentElementSelector } iframe[name^="__privateStripeFrame"]`
)
.locator( '.__PrivateStripeElementLoader' )
).toBeHidden();
await expect(
page
.frameLocator(
`${ paymentElementSelector } iframe[name^="__privateStripeFrame"]`
)
.getByText( 'Cash App Pay selected.' )
).toBeVisible();
await clickPlaceOrder( page );

// Expect a modal to appear
const simulateScanButton = await page
.locator( 'iframe[name^="__privateStripeFrame"]' )
.contentFrame()
.first()
.frameLocator( 'iframe[title="QR Code Instructions"]' )
.getByRole( 'button', { name: 'Simulate scan' } );

const context = await page.context();
const [ paymentPage ] = await Promise.all( [
context.waitForEvent( 'page' ),
simulateScanButton.dispatchEvent( 'click' ),
] );

await paymentPage.waitForLoadState();
await paymentPage
.getByRole( 'link', { name: 'Authorize Test Payment' } )
.click();
}

0 comments on commit 55c841c

Please sign in to comment.