Here is a question that came from (deprecated) Cypress Gitter chat channel. The user provided a repo with a reproducible test that tried to assert that window.dataLayer.push method was called with an object with the field event: lead. This is just one particular call among hundreds of calls the application is making to track its various events.

The user is struggling to write the correct test and asked me to help.
The original test
1 | describe('testin datalayer', () => { |
A few observations about this test right away:
- the base URL is hardcoded in the test, which is an anti-pattern. I prefer setting the base URL in the
cypress.jsonfile, watch the video How to correctly use the baseUrl to visit a site in Cypress - there are several hard-coded
cy.wait(ms)commands, probably to let the page load the tracking library and create thewindow.dataLayerobject. We can specifically wait for thewindow.dataLayerto exist, avoiding the hard-coded waits
Updated test
Let's start by moving the base URL to the cypress.json file and removing the cy.wait(ms) commands. Instead we will wait for the window object to have a property dataLayer with the method push. See the inline code comments for the full explanation and links to more documentation.
1 | describe('testing datalayer', () => { |

Spying
Ok, what about our spy? Sure, let's add a spy on the method push of the dataLayer object. The most interesting thing here is how to check if the spy function was called by the application. We have complex calls, and are only interested in the call where the first argument is an object with property event: "lead". Luckily, Chai-Sinon assertions included with Cypress provide a way to use a custom matcher to check the calls.
1 | describe('testing datalayer', () => { |
For more Chai-Sinon assertion examples, see my cy.stub, cy.spy, and cy.clock examples. For now, the test works, even if it is pretty noisy - there are lots of dataLayer.push calls!

Cut the noise
We only want to be informed about dataLayer.push(lead) events, not every call. Unfortunately, the built-in Sinon mechanism to create a targeted spy cy.spy(dataLayer, 'push').withArgs({ event: 'lead' }).as('lead') expects the exact argument match, not part of the object. We could use the same match via predicate when creating a spy:
1 | cy.spy(dataLayer, 'push') |
It does simplify the assertion
1 | cy.get('@lead').should('have.been.calledOnce') |
Unfortunately, this still records other calls to dataLayer.push method âšī¸

We really need to avoid using cy.spy for calls we are not interested in. We can do this by constructing a "plain" Sinon.js stub function avoiding the cy.stub or cy.spy command to avoid logging every call. Here is the entire test, including printing the event data to the Command Log.
1 | describe('testing datalayer', () => { |
The test is beautiful đĨ° and takes only four seconds đ instead of 20+ seconds of the original test with hard-coded waits.

See also
- Spy On DOM Methods And Properties
- Cypress Stubs, Spies, and Clocks Guide
- my
cy.stub,cy.spy, andcy.clockexamples - find more stubbing and spying examples by searching using my cypress.tips/search page
- Sinon.js documentation