-
Notifications
You must be signed in to change notification settings - Fork 216
Fix flaky E2E tests #4755
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
Fix flaky E2E tests #4755
Changes from 10 commits
f64ee72
3648deb
5587971
3ec91a2
1e9e785
6ea1fd7
a76b248
bd64ea4
1d9f7b5
eeee463
f04f9bc
5d8e638
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -90,6 +90,103 @@ | |||||
| } | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Wait for Stripe iframe to be fully loaded and ready for interaction. | ||||||
| * This helper addresses common race conditions with Stripe Elements. | ||||||
| * | ||||||
| * @param {Page} page Playwright page fixture. | ||||||
| * @param {string} iframeSelector The selector for the Stripe iframe. | ||||||
| * @param {number} timeout Maximum time to wait in milliseconds (default: 15000). | ||||||
| * @returns {Promise<Frame>} The loaded Stripe frame. | ||||||
| */ | ||||||
| export async function waitForStripeReady( | ||||||
| page, | ||||||
| iframeSelector, | ||||||
| timeout = 15000 | ||||||
| ) { | ||||||
| // Wait for iframe to be present and visible | ||||||
| await page.waitForSelector( iframeSelector, { | ||||||
| state: 'visible', | ||||||
| timeout, | ||||||
| } ); | ||||||
|
|
||||||
| // Get the frame handle and content frame | ||||||
| const frameHandle = await page.waitForSelector( iframeSelector, { | ||||||
| timeout, | ||||||
| } ); | ||||||
| const stripeFrame = await frameHandle.contentFrame(); | ||||||
|
|
||||||
| if ( ! stripeFrame ) { | ||||||
| throw new Error( | ||||||
| `Could not get content frame for: ${ iframeSelector }` | ||||||
| ); | ||||||
| } | ||||||
|
|
||||||
| // Wait for the frame to be fully loaded | ||||||
| await stripeFrame.waitForLoadState( 'networkidle', { timeout } ); | ||||||
|
Check failure on line 126 in tests/e2e/utils/payments.js
|
||||||
|
|
||||||
| // Additional wait for any loading indicators to disappear | ||||||
| const loadingIndicators = [ | ||||||
| '.__PrivateStripeElementLoader', | ||||||
| '.LightboxModalLoadingIndicator', | ||||||
| '[data-testid="loading"]', | ||||||
| ]; | ||||||
|
|
||||||
| for ( const indicator of loadingIndicators ) { | ||||||
| const loader = stripeFrame.locator( indicator ); | ||||||
| if ( await loader.isVisible().catch( () => false ) ) { | ||||||
| await expect( loader ).toBeHidden( { timeout: 10000 } ); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| return stripeFrame; | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Retry an async function with exponential backoff. | ||||||
| * Useful for flaky operations like iframe interactions or API calls. | ||||||
| * | ||||||
| * @param {Function} fn The async function to retry. | ||||||
| * @param {Object} options Retry configuration. | ||||||
| * @param {number} options.maxRetries Maximum number of retries (default: 3). | ||||||
| * @param {number} options.initialDelay Initial delay in milliseconds (default: 500). | ||||||
| * @param {number} options.maxDelay Maximum delay in milliseconds (default: 5000). | ||||||
| * @param {Function} options.shouldRetry Optional function to determine if error should trigger retry. | ||||||
| * @returns {Promise<any>} The result of the function call. | ||||||
| */ | ||||||
| export async function retryWithBackoff( fn, options = {} ) { | ||||||
| const { | ||||||
| maxRetries = 3, | ||||||
| initialDelay = 500, | ||||||
| maxDelay = 5000, | ||||||
| shouldRetry = () => true, | ||||||
| } = options; | ||||||
|
|
||||||
| let lastError; | ||||||
| let delay = initialDelay; | ||||||
|
|
||||||
| for ( let attempt = 0; attempt <= maxRetries; attempt++ ) { | ||||||
| try { | ||||||
| return await fn(); | ||||||
| } catch ( error ) { | ||||||
| lastError = error; | ||||||
|
|
||||||
| // Don't retry if we've exhausted attempts or if shouldRetry returns false | ||||||
| if ( attempt === maxRetries || ! shouldRetry( error ) ) { | ||||||
| break; | ||||||
| } | ||||||
|
|
||||||
| // Wait before retrying | ||||||
| await new Promise( ( resolve ) => setTimeout( resolve, delay ) ); | ||||||
|
|
||||||
| // Exponential backoff with max delay cap | ||||||
| delay = Math.min( delay * 2, maxDelay ); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| throw lastError; | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Fills in the credit card details on the default (blocks) checkout page. | ||||||
| * @param {Page} page Playwright page fixture. | ||||||
|
|
@@ -416,32 +513,24 @@ | |||||
| await emptyCart( page ); | ||||||
| await setupCart( page ); | ||||||
|
|
||||||
| let iframeSelector = 'iframe[src*="elements-inner-payment"]'; | ||||||
|
||||||
| let iframeSelector = 'iframe[src*="elements-inner-payment"]'; | |
| const rawIframeSelector = 'iframe[src*="elements-inner-payment"]'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in f04f9bc
malithsen marked this conversation as resolved.
Show resolved
Hide resolved
Check failure on line 555 in tests/e2e/utils/payments.js
GitHub Actions / Default WP=latest, WC=latest, PHP=7.4
[default] › checkout/shortcode/lpms/ach.spec.js:59:6 › ACH payment tests @shortcode › customer can save and reuse ACH payment method @smoke
3) [default] › checkout/shortcode/lpms/ach.spec.js:59:6 › ACH payment tests @shortcode › customer can save and reuse ACH payment method @smoke › Save payment method during first checkout
Retry #1 ───────────────────────────────────────────────────────────────────────────────────────
TimeoutError: locator.click: Timeout 15000ms exceeded.
Call log:
- waiting for frameLocator('.wc_payment_method.payment_method_stripe_us_bank_account iframe[src*="elements-inner-payment"]').getByText('Test Institution').first()
- locator resolved to <p class="u-lh u-text-truncate u-fs-3xs u-text-cent…>Test Institution</p>
- attempting click action
- waiting for element to be visible, enabled and stable
- element is visible, enabled and stable
- scrolling into view if needed
- done scrolling
- element is outside of the viewport
- retrying click action, attempt #1
- waiting for element to be visible, enabled and stable
- element is visible, enabled and stable
- scrolling into view if needed
- done scrolling
- element is outside of the viewport
- retrying click action, attempt #2
- waiting 20ms
- waiting for element to be visible, enabled and stable
- element is visible, enabled and stable
- scrolling into view if needed
- done scrolling
- element is outside of the viewport
- retrying click action, attempt #3
- waiting 100ms
- waiting for element to be visible, enabled and stable
- element is visible, enabled and stable
- scrolling into view if needed
- done scrolling
- element is outside of the viewport
- retrying click action, attempt #4
- waiting 100ms
- waiting for element to be visible, enabled and stable
- element is visible, enabled and stable
- scrolling into view if needed
- done scrolling
- element is outside of the viewport
- retrying click action, attempt #5
- waiting 500ms
- waiting for element to be visible, enabled and stable
- element is visible, enabled and stable
- scrolling into view if needed
- done scrolling
- element is outside of the viewport
- retrying click action, attempt #6
- waiting 500ms
- waiting for element to be visible, enabled and stable
- element is visible, enabled and stable
- scrolling into view if needed
- done scrolling
- element is outside of the viewport
- retrying click action, attempt #7
- waiting 500ms
- waiting for element to be visible, enabled and stable
- element is visible, enabled and stable
- scrolling into view if needed
- done scrolling
- element is outside of the viewport
- retrying click action, attempt #8
- waiting 500ms
- waiting for element to be visible, enabled and stable
- element is visible, enabled and stable
- scrolling into view if needed
- done scrolling
- element is outside of the viewport
- retrying click action, attempt #9
- waiting 500ms
- waiting for element to be visible, enabled and stable
- element is visible, enabled and stable
- scrolling into view if needed
- done scrolling
- element is outside of the viewport
- retrying click action, attempt #10
- waiting 500ms
- waiting for element to be visible, enabled and stable
- element is visible, enabled and stable
- scrolling into view if needed
- done scrolling
- element is outside of the viewport
- retrying click action, attempt #11
- waiting 500ms
- waiting for element to be visible, enabled and stable
- element is visible, enabled and stable
- scrolling into view if needed
- done scrolling
- element is outside of the viewport
- retrying click action, attempt #12
Check failure on line 555 in tests/e2e/utils/payments.js
GitHub Actions / Default WP=latest, WC=latest, PHP=7.4
[default] › checkout/blocks/lpms/ach.spec.js:45:6 › ACH payment tests @blocks › customer can pay with ACH using valid bank details @smoke
1) [default] › checkout/blocks/lpms/ach.spec.js:45:6 › ACH payment tests @blocks › customer can pay with ACH using valid bank details @smoke
TimeoutError: locator.click: Timeout 15000ms exceeded.
Call log:
- waiting for frameLocator('#radio-control-wc-payment-method-options-stripe_us_bank_account__content iframe[src*="elements-inner-payment"]').getByText('Test Institution').first()
- locator resolved to <p class="u-lh u-text-truncate u-fs-3xs u-text-cent…>Test Institution</p>
- attempting click action
- waiting for element to be visible, enabled and stable
- element is visible, enabled and stable
- scrolling into view if needed
- done scrolling
- element is outside of the viewport
- retrying click action, attempt #1
- waiting for element to be visible, enabled and stable
- element is visible, enabled and stable
- scrolling into view if needed
- done scrolling
- element is outside of the viewport
- retrying click action, attempt #2
- waiting 20ms
- waiting for element to be visible, enabled and stable
- element is visible, enabled and stable
- scrolling into view if needed
- done scrolling
- element is outside of the viewport
- retrying click action, attempt #3
- waiting 100ms
- waiting for element to be visible, enabled and stable
- element is visible, enabled and stable
- scrolling into view if needed
- done scrolling
- element is outside of the viewport
- retrying click action, attempt #4
- waiting 100ms
- waiting for element to be visible, enabled and stable
- element is visible, enabled and stable
- scrolling into view if needed
- done scrolling
- element is outside of the viewport
- retrying click action, attempt #5
- waiting 500ms
- waiting for element to be visible, enabled and stable
- element is visible, enabled and stable
- scrolling into view if needed
- done scrolling
- element is outside of the viewport
- retrying click action, attempt #6
- waiting 500ms
- waiting for element to be visible, enabled and stable
- element is visible, enabled and stable
- scrolling into view if needed
- done scrolling
- element is outside of the viewport
- retrying click action, attempt #7
- waiting 500ms
- waiting for element to be visible, enabled and stable
- element is visible, enabled and stable
- scrolling into view if needed
- done scrolling
- element is outside of the viewport
- retrying click action, attempt #8
- waiting 500ms
- waiting for element to be visible, enabled and stable
- element is visible, enabled and stable
- scrolling into view if needed
- done scrolling
- element is outside of the viewport
- retrying click action, attempt #9
- waiting 500ms
- waiting for element to be visible, enabled and stable
- element is visible, enabled and stable
- scrolling into view if needed
- done scrolling
- element is outside of the viewport
- retrying click action, attempt #10
- waiting 500ms
- waiting for element to be visible, enabled and stable
- element is visible, enabled and stable
- scrolling into view if needed
- done scrolling
- element is outside of the viewport
- retrying click action, attempt #11
- waiting 500ms
- waiting for element to be visible, enabled and stable
- element is visible, enabled and stable
- scrolling into view if needed
- done scrolling
- element is outside of the viewport
- retrying click action, attempt #12
- waiting 500ms
- waiting for element to be visible, enabled and stable
- element is visible, enabled and stable
Check failure on line 577 in tests/e2e/utils/payments.js
GitHub Actions / Default WP=latest, WC=latest, PHP=7.4
[default] › checkout/shortcode/lpms/ach.spec.js:46:6 › ACH payment tests @shortcode › customer can pay with ACH using valid bank details @smoke
2) [default] › checkout/shortcode/lpms/ach.spec.js:46:6 › ACH payment tests @shortcode › customer can pay with ACH using valid bank details @smoke
Retry #1 ───────────────────────────────────────────────────────────────────────────────────────
TimeoutError: locator.waitFor: Timeout 5000ms exceeded.
Call log:
- waiting for frameLocator('iframe[name^="__privateStripeFrame"]').first().getByTestId('link-not-now-button') to be visible
at ../utils/payments.js:577
575 | frame
576 | .getByTestId( 'link-not-now-button' )
> 577 | .waitFor( { state: 'visible', timeout: 5000 } )
| ^
578 | .then( async () => {
579 | await frame.getByTestId( 'link-not-now-button' ).click();
580 | } ),
at fillACHBankDetails (/home/runner/work/woocommerce-gateway-stripe/woocommerce-gateway-stripe/tests/e2e/utils/payments.js:577:5)
at /home/runner/work/woocommerce-gateway-stripe/woocommerce-gateway-stripe/tests/e2e/tests/checkout/shortcode/lpms/ach.spec.js:50:3
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two thoughts:
Promise.all()that allows us to perform these checks in parallel. We should either add a comment about why we're doing this sequentially, or we should look at handling this in parallel.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated to check the selector in parallel in f04f9bc