Skip to content

Commit

Permalink
Prevent duplicate order failure by selling different products on save…
Browse files Browse the repository at this point in the history
…d card E2E tests (#10230)
  • Loading branch information
tpaksu authored Jan 24, 2025
1 parent c08a83c commit 9376ce0
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 115 deletions.
4 changes: 4 additions & 0 deletions changelog/fix-playwright-saved-card-flakiness
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: dev

Fix flakiness in saved card tests caused by selling the same cart multiple times, triggering duplicate order protection
226 changes: 126 additions & 100 deletions tests/e2e-pw/specs/shopper/shopper-myaccount-saved-cards.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,43 @@ import {
} from '../../utils/shopper';
import RestAPI from '../../utils/rest-api';

type TestVariablesType = {
[ key: string ]: {
card: typeof config.cards.basic;
address: {
country: string;
postalCode: string;
};
products: [ string, number ][];
};
};

const cards = {
basic: config.cards.basic,
'3ds': config.cards[ '3ds' ],
'3ds2': config.cards[ '3ds2' ],
} as { [ key: string ]: typeof config.cards.basic };
basic: {
card: config.cards.basic,
address: {
country: 'US',
postalCode: '94110',
},
products: [ [ 'Beanie', 1 ] ],
},
'3ds': {
card: config.cards[ '3ds' ],
address: {
country: 'US',
postalCode: '94110',
},
products: [ [ 'Belt', 1 ] ],
},
'3ds2': {
card: config.cards[ '3ds2' ],
address: {
country: 'US',
postalCode: '94110',
},
products: [ [ 'Cap', 1 ] ],
},
} as TestVariablesType;

test.describe( 'Shopper can save and delete cards', () => {
let timeAdded: number;
Expand Down Expand Up @@ -80,10 +112,6 @@ test.describe( 'Shopper can save and delete cards', () => {
)
).toBeVisible();

await expect(
shopperPage.getByText( 'Payment method successfully added' )
).not.toBeVisible();

await expect(
shopperPage.getByText(
`${ config.cards.basic2.expires.month }/${ config.cards.basic2.expires.year }`
Expand All @@ -96,104 +124,102 @@ test.describe( 'Shopper can save and delete cards', () => {
await deleteSavedCard( shopperPage, config.cards.basic );
} );

Object.entries( cards ).forEach( ( [ cardName, card ] ) => {
test.describe( 'Testing card: ' + cardName, () => {
test( `should add the ${ cardName } card as a new payment method`, async () => {
await goToMyAccount( shopperPage, 'payment-methods' );
await addSavedCard( shopperPage, card, 'US', '94110' );
// Take note of the time when we added this card
timeAdded = +Date.now();

if ( cardName === '3ds' || cardName === '3ds2' ) {
await confirmCardAuthentication( shopperPage );
}

await shopperPage.waitForURL( /\/my-account\/payment-methods/, {
waitUntil: 'load',
Object.entries( cards ).forEach(
( [ cardName, { card, address, products } ] ) => {
test.describe( 'Testing card: ' + cardName, () => {
test( `should add the ${ cardName } card as a new payment method`, async () => {
await goToMyAccount( shopperPage, 'payment-methods' );
await addSavedCard(
shopperPage,
card,
address.country,
address.postalCode
);
// Take note of the time when we added this card
timeAdded = +Date.now();

if ( cardName === '3ds' || cardName === '3ds2' ) {
await confirmCardAuthentication( shopperPage );
}

await shopperPage.waitForURL(
/\/my-account\/payment-methods/,
{
waitUntil: 'load',
}
);

// Verify that the card was added
await expect(
shopperPage.getByText(
'You cannot add a new payment method so soon after the previous one. Please wait for 20 seconds.'
)
).not.toBeVisible();

await expect(
shopperPage.getByText(
`${ card.expires.month }/${ card.expires.year }`
)
).toBeVisible();

await waitTwentySecondsSinceLastCardAdded( shopperPage );
} );

// Verify that the card was added
await expect(
shopperPage.getByText(
'You cannot add a new payment method so soon after the previous one. Please wait for 20 seconds.'
)
).not.toBeVisible();

await expect(
shopperPage.getByText( 'Payment method successfully added' )
).toBeVisible();

await expect(
shopperPage.getByText(
`${ card.expires.month }/${ card.expires.year }`
)
).toBeVisible();

await waitTwentySecondsSinceLastCardAdded( shopperPage );
} );

test( `should be able to purchase with the saved ${ cardName } card`, async () => {
await goToShop( shopperPage );
await setupProductCheckout( shopperPage );
await selectSavedCardOnCheckout( shopperPage, card );
if ( cardName === 'basic' ) {
test( `should be able to purchase with the saved ${ cardName } card`, async () => {
await goToShop( shopperPage );
await setupProductCheckout( shopperPage, products );
await selectSavedCardOnCheckout( shopperPage, card );
await placeOrder( shopperPage );
} else {
await shopperPage
.getByRole( 'button', { name: 'Place order' } )
.click();
await confirmCardAuthentication( shopperPage );
}

await shopperPage.waitForURL( /\/order-received\//, {
waitUntil: 'load',
if ( cardName !== 'basic' ) {
await confirmCardAuthentication( shopperPage );
}
await shopperPage.waitForURL( /\/order-received\//, {
waitUntil: 'load',
} );
await expect(
shopperPage.getByRole( 'heading', {
name: 'Order received',
} )
).toBeVisible();
} );
await expect(
shopperPage.getByRole( 'heading', {
name: 'Order received',
} )
).toBeVisible();
} );

test( `should be able to set the ${ cardName } card as default payment method`, async () => {
await goToMyAccount( shopperPage, 'payment-methods' );
await addSavedCard( shopperPage, card2, 'US', '94110' );
// Take note of the time when we added this card
timeAdded = +Date.now();

await expect(
shopperPage.getByText( 'Payment method successfully added' )
).toBeVisible();
await expect(
shopperPage.getByText(
`${ card2.expires.month }/${ card2.expires.year }`
)
).toBeVisible();
await setDefaultPaymentMethod( shopperPage, card2 );
// Verify that the card was set as default
await expect(
shopperPage.getByText(
'This payment method was successfully set as your default.'
)
).toBeVisible();
} );
test( `should be able to set the ${ cardName } card as default payment method`, async () => {
await goToMyAccount( shopperPage, 'payment-methods' );
await addSavedCard( shopperPage, card2, 'US', '94110' );
// Take note of the time when we added this card
timeAdded = +Date.now();

await expect(
shopperPage.getByText(
`${ card2.expires.month }/${ card2.expires.year }`
)
).toBeVisible();
await setDefaultPaymentMethod( shopperPage, card2 );
// Verify that the card was set as default
await expect(
shopperPage.getByText(
'This payment method was successfully set as your default.'
)
).toBeVisible();
} );

test( `should be able to delete ${ cardName } card`, async () => {
await goToMyAccount( shopperPage, 'payment-methods' );
await deleteSavedCard( shopperPage, card );
await expect(
shopperPage.getByText( 'Payment method deleted.' )
).toBeVisible();

await deleteSavedCard( shopperPage, card2 );
await expect(
shopperPage.getByText( 'Payment method deleted.' )
).toBeVisible();
} );
test( `should be able to delete ${ cardName } card`, async () => {
await goToMyAccount( shopperPage, 'payment-methods' );
await deleteSavedCard( shopperPage, card );
await expect(
shopperPage.getByText( 'Payment method deleted.' )
).toBeVisible();

await deleteSavedCard( shopperPage, card2 );
await expect(
shopperPage.getByText( 'Payment method deleted.' )
).toBeVisible();
} );

test.afterAll( async () => {
waitTwentySecondsSinceLastCardAdded( shopperPage );
test.afterAll( async () => {
await waitTwentySecondsSinceLastCardAdded( shopperPage );
} );
} );
} );
} );
}
);
} );
33 changes: 18 additions & 15 deletions tests/e2e-pw/utils/shopper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -437,32 +437,35 @@ export const deleteSavedCard = async (
page: Page,
card: typeof config.cards.basic
) => {
await page
.getByRole( 'row', { name: card.label } )
.first()
.getByRole( 'link', { name: 'Delete' } )
.click();
await page.waitForTimeout( 1000 );
await expect( page.getByText( 'Payment method deleted.' ) ).toBeVisible();
const row = page.getByRole( 'row', { name: card.label } ).first();
await expect( row ).toBeVisible( { timeout: 100 } );
const button = row.getByRole( 'link', { name: 'Delete' } );
await expect( button ).toBeVisible( { timeout: 100 } );
await expect( button ).toBeEnabled( { timeout: 100 } );
await button.click();
};

export const selectSavedCardOnCheckout = async (
page: Page,
card: typeof config.cards.basic
) =>
await page
) => {
const option = page
.getByText(
`${ card.label } (expires ${ card.expires.month }/${ card.expires.year })`
)
.click();
.first();
await expect( option ).toBeVisible( { timeout: 100 } );
option.click();
};

export const setDefaultPaymentMethod = async (
page: Page,
card: typeof config.cards.basic
) => {
await page
.getByRole( 'row', { name: card.label } )
.first()
.getByRole( 'link', { name: 'Make default' } )
.click();
const row = page.getByRole( 'row', { name: card.label } ).first();
await expect( row ).toBeVisible( { timeout: 100 } );
const button = row.getByRole( 'link', { name: 'Make default' } );
await expect( button ).toBeVisible( { timeout: 100 } );
await expect( button ).toBeEnabled( { timeout: 100 } );
button.click();
};

0 comments on commit 9376ce0

Please sign in to comment.