The Cypress command cy.log is bad. Not like "ohh, it will print nothing unless you understand the asynchronous nature of Cypress command chains", but in the sense "cy.log does not do what you expect it to do, and when it does it is broken" kind-of-bad. Let me explain.
cy.log problems
Let's say, the page has an element with the user's age. You want to print the age to the Command Log. You might try doing the following:
1 | it('logs the user age (NOT)', () => { |
You might think that chaining .log
command at the end of the chain:
1 | it('logs the user age (NOT)', () => { |
Right, it does nothing.
Ok, my trick to print the current subject is to assert it.
1 | it('logs the user age (NOT)', () => { |
Tip: the more assertions you sprinkle through your command chains, the less test flake you will have.
Ok, that works, here is where cy.log
can stub you again. Let's say you want to log the text before converting it to a number. You might think that adding .then(cy.log)
into the chain would work.
1 | it('logs the user age (NOT)', () => { |
"Get out of here!" screams the cy.log
. Weird, right? Why is the "age" zero? Let's replace .then(cy.log)
with .then(console.log)
1 | it('logs the user age (NOT)', () => { |
Strange, this works correctly.
Ohhh, yes. Cypress v10 broke cy.then + cy.log
combination. The command cy.then
yields the original subject IF the callback function yields undefined
. For some reason, Cypress v10 switched cy.log
from returning undefined
to returning null
, breaking all the code in my tests.
cy.log does not try too hard
Or at all. The cy.log
command is not a query command, thus it will break any retries. Here is a test that works - it automatically retries getting the property name
of the given object until the assertion passes.
1 | it('retries (NOT)', () => { |
If we insert cy.log
between the object and the cy.its
command, our retries will break.
1 | cy.wrap(person).log().its('name').should('equal', 'Ann') |
Same if we use cy.wrap(person).cy.then(cy.log)
- cy.then
is not a query and stops retries.
Do not try to log an object
Let's say you spied on the network call and watch to log the server response object.
1 | it('logs the object (NOT)', () => { |
The endpoint returns an array of small objects
1 | [ |
Here is how cy.log
prints them
Unfortunately, you cannot control serialization. My trick is to use an assertion again with the custom long truncate threshold.
1 | chai.config.truncateThreshold = 200 |
cy.print to the rescue
Cypress v12 has introduced the concept of query commands, which are a big deal. I have written an entire NPM package cypress-map of useful queries you can start using today. The latest addition to cypress-map
is the cy.print
command. Install the plugin as a dev dependency:
1 | $ npm i -D cypress-map |
Let's see if we can just insert cy.print
into our first test.
1 | it('prints the user age', () => { |
Even better, we can format the message using printf
syntax, and the current subject value is inserted automatically
1 | it('prints the user age', () => { |
Let's say we want to log the array from the network intercept. By default cy.print
serializes even long objects.
1 | cy.wait('@users').its('response.body').print().should('be.an', 'array') |
But we probably do not want to see the entire array. Maybe we want to just see its length, and maybe the first object. cy.print
lets you access the properties inside using {0.}
notation.
1 | cy.wait('@users') |
Finally, cy.print
is a query, so it can be part of the chain that is retried, and it prints the current value.
1 | it('retries', () => { |
Bonus feature: pass your own callback to return the string to print. For anything more complicated, you can pass the callback to cy.print
to return the string to print into the Command Log. Want to print just the first names from the array returned by the intercept?
1 | cy.visit('cypress/log-examples.html') |
Nice!