I am trying Cypress (with TypeScript) to see if it might be suitable for E2E testing a specific web app. One of the challenges I have is that I do not have a seeder to trivially recreate the database; it would be ideal to fully remote control the app from the tests, so that the database is fresh for every test. That is a team decision, and thus (for now) the strategy will be to test against a staging environment where the database is a recent copy of production.
This means that where I take an action in a test, it shall be regarded as a sufficient test for something in the DOM to have changed. For example, I am wanting to test a filtering screen, and when a filter is applied, the results must be different to what they were before. However, this is proving rather tricksy.
Here is the code to apply the filter:
visitCountryPage();
cy
.get('#sharedTrips .filtersContainer button')
.then((buttons: JQuery<HTMLElement>) => {
buttons[0].click();
})
// Now click on a menu option
.get('#sharedTrips .filtersContainer label[for="experiencesrelax"]')
.click();
Here is what happens:
get()
finds the button to open the menuthen()
to get the 0th element to click the filter optionget()
finds a menu optionTo give readers an idea of my thought process, here is what I have tried:
let initialIdeas: Chainable<JQuery<HTMLElement>> = getTripIdeas();
cy
.get('#sharedTrips .filtersContainer button')
.then((buttons: JQuery<HTMLElement>) => {
buttons[0].click();
})
// Now click on a menu option
.get('#sharedTrips .filtersContainer label[for="experiencesrelax"]')
.click()
.then(() => {
getTripIdeas().should('not.equal', initialIdeas);
});
You can see that getTripIdeas()
is an external function that returns a Chainable<JQuery<HTMLElement>>
via a DOM read. I appreciate that at the top of the code this is likely to be some kind of Promise that is not yet resolved, but I was hoping that in the final then()
, the not.equal
test would resolve both the new and old versions in order to do a meaningful comparison.
However, this passes green even if I take out the final click(), which should not happen.
I am wondering now whether the object references to these resolved Promises are always going to be different (which is why the test always passes), and thus I should convert them to the text contents of the elements within, so they are comparable. Or am I wrong in assuming that the last should()
is going to resolve the two Promises, and if so, how can I do that succinctly?
You have a number of anti-patterns, but since it's not the code review site I'll just focus on the the main idea.
What you're trying to do is use Chai assertions (the .should()
command is an implementation of expect()
) to compare Chainable result objects. Unfortunately there is no Chai method that does that.
Therefore you need to unpack the Chainable with .then((valueInsideChainable) =>
. To preserve the initialValues Cypress says you should use an alias instead of a Javascript variable.
Further, it should be a static
type alias. See api/commands/as#Arguments.
cy.wrap(getTripIdeas()).as('initialValues', {type:'static'});
// actions to change the DOM
cy.get('#sharedTrips .filtersContainer button')
.first()
.click(); // prefer Cypress click to jQuery click
// don't chain after click()
cy.get('#sharedTrips .filtersContainer label[for="experiencesrelax"]')
.click()
/*
Using {type:'query'} for the final values since we need
.should('not.deep.eq'...) to re-query until passing
*/
cy.wrap(getTripIdeas()).as('finalValues', {type:'query'});
// compare the variables
cy.get('@initialValues').then(initial => {
cy.get('@finalValues')
.invoke('toArray') // change JQuery<HTMLElement> to HTMLElement[]
.should('not.deep.eq', initial.toArray())
})
getTripIdeas()
would actually be better as a custom command, since you deal with the Chainable result. It could then be chained directly (would not need wrap()).
The not.deep.eq
comparison is a bit naive since we are dealing with JQuery<HTMLElement>
, if this is a React app the elements are likely to be re-rendered with new copies after the click actions.
It would be better to compare some aspect of the elements, such as the innerText
, or value
for HTMLInputElement
.