Recently a user posted an example in one of Cypress GitHub repo issues showing a "problem" computing asynchronously the SHA-256 code from the element's HTML. The original test as I understand it tries to confirm that the table's HTML changes when the user types a search string, and the table updates in response. Here is the relevant code snippet as posted by the user:
1 | // 🚨 DOES NOT WORK, JUST A QUESTION |
Do not use SHA
Ok, let's put the entire async / await vs reactive streams for writing tests question aside. Does using SHA256 to confirm the table changes a good idea? No. It is a terrible idea. Here is a typical example. I will grab the SHA implementation from Mozilla docs:
1 | async function digestMessage(message) { |
The SHA256 can change for any reason and you have no way to say why it did change.
1 | cy.wrap(digestMessage('<div>Hello</div>')).then((sha1) => { |
Even worse, the HTML of the page can change for any reason. The assertion "SHA should be different" does not see why the change has happened. Let's take a page that suddenly shows an error:
1 | <button id="load">Load data</button> |
Our test can easily incorporate asynchronous functions into the Cypress chain. This code comes from cypress-examples site.
1 | async function digestMessage(message) { |
1 | cy.get('#output') |
Unfortunately, the test passes, yet the app is obviously broken.
Again, the SHA of the HTML can change for a billion reason. The assertion "SHA should change" accepts any of them. Your tests should instead verify the single expected behavior using positive assertions.
Use positive assertions
What can we use instead? Let's get back to the filtered table. I have prepared this example in the repo bahmutov/sorted-table-example.
1 | // cypress/e2e/filter.cy.js |
If you type in part of the first name, it filters the table - and there is a small delay between typing and filtering to simulate realistic application.
Ok, so what do we want to confirm in this case? If you write down instructions for a human tester, I think they would look something like this:
- visit the page
- there should be several rows in the table
- type "Jo" into the input box
- the table should filter rows
- there should be fewer rows
- each row's first cell should include string "Jo"
Let's write this test using Cypress and cypress-map plugin.
1 | // https://github.com/bahmutov/cypress-map |
A better test
Even the above test is far from perfect. We do not control the data, so we cannot guarantee several key moments:
- there might be zero rows in the table, so our assertion might fail
1 | cy.get('#people tbody tr') |
- every first name in the table might include "Jo", thus the middle assertion might fail
1 | // there should be fewer rows |
To really confirm the application works we need to control the data. Notice the application is making a network call to fetch the data using the GET /people
call. The call fails, so the app shows its local data. Let's change this. Let's stub the network call using cy.intercept command and then we will know exactly the data on the page.
1 | [ |
1 | it('controls the network data', () => { |
This is a much better and simpler test.
Of course, you can make the above test ever stricter by confirming the table cell contents, see Cypress Plugins course lessons, but you get my point:
- do NOT use "SHA should change" assertion
- use positive meaningful assertions to verify the application's behavior
- if you control the data the application receives, the test becomes clear and simple.
Happy testing!