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
[E2E] Stabilize a flaky Price Filter test #44690
Changes from all commits
2b37eb2
9fa14c0
286ee4f
233683c
33e4245
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 |
---|---|---|
|
@@ -35,7 +35,7 @@ | |
await editor.publishPost(); | ||
const url = new URL( page.url() ); | ||
const postId = url.searchParams.get( 'post' ); | ||
await page.goto( `/?p=${ postId }`, { waitUntil: 'commit' } ); | ||
await page.goto( `/?p=${ postId }` ); | ||
} ); | ||
|
||
test( 'should show all products', async ( { frontendUtils } ) => { | ||
|
@@ -59,24 +59,30 @@ | |
page, | ||
frontendUtils, | ||
} ) => { | ||
// The price filter input is initially enabled, but it becomes disabled | ||
// for the time it takes to fetch the data. To avoid setting the filter | ||
// value before the input is properly initialized, we wait for the input | ||
// to be disabled first. This is a safeguard to avoid flakiness which | ||
// should be addressed in the code, but All Products block will be | ||
// deprecated in the future, so we are not going to optimize it. | ||
await page | ||
.getByRole( 'textbox', { | ||
name: 'Filter products by maximum price', | ||
disabled: true, | ||
} ) | ||
.waitFor( { timeout: 3000 } ) | ||
.catch( () => { | ||
// Do not throw in case Playwright doesn't make it in time for the | ||
// initial (pre-request) render. | ||
} ); | ||
|
||
const maxPriceInput = page.getByRole( 'textbox', { | ||
name: 'Filter products by maximum price', | ||
} ); | ||
|
||
// All Products block will be deprecated in the future, so we are not going to optimize it. | ||
|
||
// eslint-disable-next-line playwright/no-networkidle | ||
await page.waitForLoadState( 'networkidle' ); | ||
|
||
await frontendUtils.selectTextInput( maxPriceInput ); | ||
await maxPriceInput.fill( '$10', { | ||
// eslint-disable-next-line playwright/no-force-option | ||
force: true, | ||
} ); | ||
await maxPriceInput.dblclick(); | ||
await maxPriceInput.fill( '$10' ); | ||
Comment on lines
-71
to
+84
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Double click seems to be working just as well and is likely closer to how a user would select the value. 😄 |
||
await maxPriceInput.press( 'Tab' ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This feels weird, not from the test but from the feature implementation perspective. Hitting Tab changes the focus to another element and loses the filter from the sight, so is that how it's intended to work? Why not update on value change or Enter? Not sure who to mention here, actually 😛 cc: @gigitux |
||
await page.waitForResponse( ( response ) => | ||
response.url().includes( blockData.endpointAPI ) | ||
); | ||
Comment on lines
-77
to
-79
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's generally a bad practice to call and await The Waiting for event guide section has some good examples of using that API correctly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, this is not necessary for the test to succeed so I removed it 😄 |
||
|
||
const allProductsBlock = await frontendUtils.getBlockByName( | ||
'woocommerce/all-products' | ||
|
@@ -89,9 +95,9 @@ | |
blockData.placeholderUrl | ||
); | ||
|
||
const products = await allProductsBlock.getByRole( 'listitem' ).all(); | ||
const allProducts = allProductsBlock.getByRole( 'listitem' ); | ||
|
||
expect( products ).toHaveLength( 1 ); | ||
await expect( allProducts ).toHaveCount( 1 ); | ||
Comment on lines
-94
to
+100
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For locators, we can use the dedicated |
||
expect( page.url() ).toContain( | ||
blockData.urlSearchParamWhenFilterIsApplied | ||
); | ||
|
@@ -141,7 +147,7 @@ | |
.locator( '.product' ) | ||
.all(); | ||
|
||
expect( products ).toHaveLength( 16 ); | ||
Check failure on line 150 in plugins/woocommerce-blocks/tests/e2e/tests/price-filter/price-filter.block_theme.side_effects.spec.ts GitHub Actions / Playwright E2E tests - SideEffects[blockThemeWithGlobalSideEffects] › price-filter/price-filter.block_theme.side_effects.spec.ts:138:6 › woocommerce/price-filter Block - with PHP classic template › should show all products
|
||
} ); | ||
|
||
// eslint-disable-next-line playwright/no-skipped-test | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
Significance: patch | ||
Type: dev | ||
|
||
Stabilize a flaky Price Filter test by addressing the unstable nature of the filter input. |
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.
So, initially, we had flaky tests because we got the price input field, but it became disabled at the time we tried to change the price.
With this code, we try to get a disabled input first, for three seconds. Then we wait and get the active one.
But if there is an issue with the test environment, after three seconds, the input becomes disabled again, will we get the original flakiness again?
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.
Not exactly. I'll try to explain using some example scenarios:
Failure Example Due to Race Condition:
Success Example Despite Race Condition:
Fortunately, a disabled input is not an issue for Playwright as it will enter a waiting state until the input is enabled.
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.
I still have another question 😄 .
Just theoretically, if there is an issue that delays the data fetching to more than three seconds after load:
Is this a valid assumption?
I can see this may not even be possible and your updates here will solve most of the flakiness we have. So please consider my comment here is just for learning and curiosity. The code change in this PR is LGTM.
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.
Playwright will time out and fail the test if the data fetching is delayed to over 3 seconds since the filter input has become visible in the DOM (which is when Playwright starts the
disabled
locator timer). Therefore, nothing after step no. 3 from the sequence you provided would happen.I do think that this is an edge-case scenario at most! 😄 While my fix addresses the flakiness, the actual fix should be provided in the app logic instead. The input should be rendered disabled, and only enabled once the data is initialized. That way we wouldn't need any extra handling in the test. Does that make sense?
Thanks for diving into this PR and sharing your thoughts! 😊 I totally get where you're coming from with your questions. Glad to hear the changes look good to you. Happy to answer any more questions or ideas.
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.
Thanks for your reply! It's clear now