From 9376ce05e40a1e8092326477bb4d15604b29d633 Mon Sep 17 00:00:00 2001 From: Taha Paksu <3295+tpaksu@users.noreply.github.com> Date: Fri, 24 Jan 2025 14:47:11 +0100 Subject: [PATCH] Prevent duplicate order failure by selling different products on saved card E2E tests (#10230) --- changelog/fix-playwright-saved-card-flakiness | 4 + .../shopper-myaccount-saved-cards.spec.ts | 226 ++++++++++-------- tests/e2e-pw/utils/shopper.ts | 33 +-- 3 files changed, 148 insertions(+), 115 deletions(-) create mode 100644 changelog/fix-playwright-saved-card-flakiness diff --git a/changelog/fix-playwright-saved-card-flakiness b/changelog/fix-playwright-saved-card-flakiness new file mode 100644 index 00000000000..ad10f013b9f --- /dev/null +++ b/changelog/fix-playwright-saved-card-flakiness @@ -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 diff --git a/tests/e2e-pw/specs/shopper/shopper-myaccount-saved-cards.spec.ts b/tests/e2e-pw/specs/shopper/shopper-myaccount-saved-cards.spec.ts index 13aed1d56ad..9bb74eb5e2c 100644 --- a/tests/e2e-pw/specs/shopper/shopper-myaccount-saved-cards.spec.ts +++ b/tests/e2e-pw/specs/shopper/shopper-myaccount-saved-cards.spec.ts @@ -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; @@ -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 }` @@ -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 ); + } ); } ); - } ); - } ); + } + ); } ); diff --git a/tests/e2e-pw/utils/shopper.ts b/tests/e2e-pw/utils/shopper.ts index 93e611707a7..ca3ceb08ede 100644 --- a/tests/e2e-pw/utils/shopper.ts +++ b/tests/e2e-pw/utils/shopper.ts @@ -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(); };