问题
For thorough testing, my service (UI+Backend) migrates through multiple deployment environments: ex DEV, STAGE, DEMO, and PROD. As this service migrates these test environments, its UI waits for asynchronous updates from its Backend, which as a test stressor it can be unpredictably slow; thereby, speeding it up is not an option nor is mocking the response.
UI test involves waiting for an asynchronous update response to determine which React components will exclusively be presented. These React components will be section/div with unpredictable content/text, so Cypress contains is not an option.
The typical solution for handling slow asynchronous updates is using fixed delays with Cypress wait: a bunch of milliseconds, tenths of a second, or even seconds. However, the longer are fixed delays leads your test to be more brittle.
Here is an example of my brittle Cypress test code:
cy.get('div.main')
.then( ($divMain) => {
cy.wait(5000) // wait 5 seconds
const boolStateA = $divMain.find('section.A').length > 0;
const boolStateB = $divMain.find('section.B').length > 0;
if (boolStateA( {
// Do ...
} else if ( boolStateB ) {
// Do something else ...
}
})
However.. after waiting for 5 seconds, it is not absolutely certain, that either one of the components actually rendered. If neither or both rendered, then that is an unexpected state.
So how can I wait for either of these components to render using neither cy.wait()
nor cy.contains
?
Thank you
回答1:
The recommendation from cypress is to make your testing deterministic. Meaning, figure out how to ensure your product is always in a known state when your tests run. This is typically done with mocks, routes or massaging the backend to provide (or perhaps get) a pre-determined state.
An inline timeout could be a bit better than cy.wait() but that doesn't resolve your unknown state problem.
cy.get('.mobile-nav', { timeout: 10000 })
.should('be.visible')
.and('contain', 'Home')
Alternatively, you could update your app to include a hidden done state that your tests wait for.
Example:
<div data-cy="SectionLoaded">SectionA</div>
In this manner, the test only needs to wait for one thing and it's always going to be there. Your test can have a condition that gets the .text() of this element and then acts on it. That might look something like this provided you wanted to click on one of them.
cy.get('[data-cy=SectionLoaded]', { timeout: 10000 }).then(($loadedSection) => {
cy.get(`[data-cy=${$loadedSection.text()}]`).click()
More on this from cypress.io including additional best practices and workarounds:
- https://docs.cypress.io/guides/core-concepts/conditional-testing.html#Element-existence
- https://docs.cypress.io/guides/core-concepts/conditional-testing.html#Dynamic-text
- https://docs.cypress.io/guides/core-concepts/retry-ability.html#Increase-time-to-retry
回答2:
@GrayDwarf: Thank you for your response
I did use Cypress contains
cy.get('div[data-cy="sales_listing_section"]')
.contains(
'h1.header-title[data-cy="sale_items_header"]',
'Items for Sale',
{timeout: 10000}
)
.then(() => {
As well as module npm cypress-wait-until
cy.get('div.main-content')
.then(($sectionBody) => {
// @ts-ignore
cy.waitUntil(() => {
return ($sectionBody.find('div[data-cy="sales_listing_section"]').length > 0)
}, {
errorMsg: `ERROR: Sales section has not loaded.`,
timeout: 10000, // waits up to 10000 ms, default to 5000
interval: 500, // performs the check every 500 ms, default to 200
verbose: true,
customCheckMessage: `CHECK: If Sales section has loaded`
})
.then(() => {
cy.log('Sales section', 'Loading finished');
})
})
来源:https://stackoverflow.com/questions/64421563/cypress-wait-for-unpredictable-component-to-render-from-an-exclusive-set