Reputation: 835
I have a typical search where the user types some text in an input, async work is done and a table with the results is properly updated.
I have tests that must wait for this search step and then assert business rules regarding the results, like if the table records are eligible for edit.
Every time I ran a complete test battery (something like 80 test files), one or two of the tests involving that search inevitably fail. But if immediately after that, I run the same test alone, the test passes. It's excruciating and makes e2e testing in CI/CD pointless for the project.
I've read the Cypress documentation about flaky tests and searched questions in StackOverflow and GitHub with only complete failure. It's a drama.
Here is one of the tests:
import { searchList } from '../helpers';
import { createFluxoIniciado, randomFluxoNome } from './common';
import { fluxoSelectors } from './selectors';
describe('fluxos finish', () => {
it('can manually finish a fluxo INICIADO', () => {
// feed data to be searched
const fluxoNome = randomFluxoNome();
createFluxoIniciado({ fluxoNome });
// search
searchList(fluxoNome);
// do something with search results
fluxoSelectors.fluxos.view().click();
fluxoSelectors.finish().click();
fluxoSelectors.confirm().click();
// serach again
searchList(fluxoNome);
cy.contains('FINALIZADO');
});
});
The code in searchList
is where trouble emerge sometimes. It uses the callback strategy recommended here. The code attempts to cause retries if not all rows have the searched text.
export function searchList (text) {
cy.get('#searchText')
.scrollIntoView()
.type(text)
.blur();
cy.get('tbody tr').should($trs => {
$trs.each((i, $tr) => {
expect($tr).to.contain(text);
});
}, { timeout: 15000 });
}
Here is an example of a test failure inside a run all test execution:
Upvotes: 1
Views: 1396
Reputation: 1108
The problem is obviously caused by the async fetch between .blur()
and testing the rows.
You are correctly trying to use Cypress retry with .should(callback)
, but if the callback is complex or there are multiple steps it may not retry the element that is changing (the table rows).
Ideally you want to keep the cy.get(...).should(...)
as simple as possible, and start by testing that the table loading has completed.
// wait for expected number of rows
cy.get('tbody tr', {timeout: 15000}).should('have.length', 5)
cy.get('tbody tr').each($tr => {
expect($tr).to.contain(text);
})
But you have a randomizer there, so maybe it's not possible to test explicitly the row count.
Another approach, test the whole table for text
(.contains()
checks child text also)
// wait for text to appear somewhere
cy.get('tbody tr', {timeout: 15000}).should('contain', text)
cy.get('tbody tr').each($tr => {
expect($tr).to.contain(text);
})
You can also add an intercept between start and end of api call
export function searchList (text) {
cy.intercept('search/api/endpoint').as('search')
cy.get('#searchText')
.scrollIntoView()
.type(text)
.blur();
cy.wait('@search') // wait for api response
cy.get('tbody tr', {timeout: 15000}).should('contain', text)
cy.get('tbody tr').each($tr => {
expect($tr).to.contain(text);
})
}
I just noticed you have the {timeout}
option on .should()
, but that's the wrong place,
see Timeouts
cy.get('input', { timeout: 10000 }).should('have.value', '10') // timeout here will be passed down to the '.should()' // and it will retry for up to 10 secs
This may be successful
cy.get('tbody tr', { timeout: 15000 })
.should($trs => {
$trs.each((i, $tr) => {
expect($tr).to.contain(text);
});
})
Upvotes: 1