Recommended Free Tools
To test an event your app emits, attach an observer to the app’s actual event interface, trigger the relevant user flow, and assert the meaningful parts of the emitted event. If the event can fire during startup, install the observer in cy.visit()’s onBeforeLoad callback—before application code runs. Cypress does not automatically record every custom event; your test must hook the method, DOM event, message API, or other interface the app uses.
First identify which events you want to test
“Cypress events” can mean two different things. An application may emit its own events—for example, calling window.postMessage() or dispatching a custom DOM event. Cypress also emits events related to the browser, test lifecycle, and runner. The right listener depends on which stream you mean: observe the app’s interface to test app behavior, and use Cypress’s event APIs when you need Cypress or lifecycle notifications. See Cypress’s Catalog of Events.
There is no universal Cypress listener for arbitrary application events. Find the code path or public interface through which the app emits the event, then attach the observer there.
Observe startup emissions with onBeforeLoad
If app code may emit an event as the page starts, observing the window after cy.visit() can be too late. Cypress’s onBeforeLoad callback runs before the application code, so it is a suitable place to spy on a window method the app uses. Cypress documents this callback in its stub API and demonstrates it for postMessage in its tutorial, Using Events Emitted from Your Application during End-to-End Tests.
Free tools Windows power users keep installed
One-click scans. No signup required.
#1 Best Overall
cy.visit('/', {
onBeforeLoad(win) {
cy.spy(win, 'postMessage').as('postMessage')
},
})
cy.get('.new-todo').type('learn testing{enter}')
cy.get('.todo-list li').should('have.length', 1)
cy.get('@postMessage').should('be.called')
This example spies on window.postMessage, triggers a visible UI action, checks the resulting UI, and verifies that the method was called. The Cypress tutorial’s sample app uses Redux and Kuker to send registration and action messages. That is an example of one app’s instrumentation; it does not mean every app needs Kuker.
Check calls and stable payload fields
A call assertion confirms that the method ran, but it may not prove the app emitted the event you care about. Check the call count and inspect the arguments or payload for stable, meaningful values. For example, an event name or action type may be part of the contract, while a timestamp may vary on every run. Avoid comparing an entire event object if it contains unpredictable fields.
Rank #2
Use the actual argument shape and event values from your application. The following illustrates the assertion structure; replace the example values with the contract your app emits:
cy.get('@postMessage').should('have.been.called')
cy.get('@postMessage').its('callCount').should('be.greaterThan', 0)
cy.get('@postMessage').then((spy) => {
const message = spy.lastCall.args[0]
expect(message).to.have.property('type', 'ACTION')
})
Do not assume the first argument is an object with a type property: postMessage payload formats are application-specific. Inspect the method’s actual arguments and assert the fields your app guarantees.
Rank #3
Choose the right observation or event technique
| Need | Approach | Trade-off |
|---|---|---|
| Observe a method call without changing its behavior | cy.spy(object, method) |
The original method still runs. Attach the spy before the first call if startup emissions matter. |
| Replace a method to control its result or simulate a condition | cy.stub(object, method) |
A stub replaces behavior, so it is not the right choice when you need to verify that the real operation runs. |
| Access the active app window after navigation | cy.window() |
Useful for post-load inspection, but may miss events emitted during startup. Cypress calls this the AUT window; see cy.window(). |
| Invoke a DOM event handler with a chosen event | .trigger(eventName, options) |
It fires the event but does not perform the browser’s default action. See cy.trigger(). |
| Listen for Cypress events | cy.on() or Cypress.on() |
Use the relevant Cypress event API, not an app-method spy; account for listener scope and callback restrictions. |
Spy or stub?
A spy records calls while allowing the original function to run. A stub replaces the function, which is useful when the test needs to control a response or simulate a failure. For a test whose purpose is to confirm that the real app operation occurred, use a spy. Cypress explains the distinction in Stubs, spies, and clocks in Cypress and documents cy.stub().
Startup hook or cy.window()?
Use onBeforeLoad when the test must catch startup activity or replace a window method with a spy before app code uses it. Use cy.window() when the app is already running and the event has not already occurred. A post-load hook cannot recover a call that has already happened.
Rank #4
Real interaction or trigger()?
Prefer Cypress interactions such as typing or clicking when testing a user flow: they exercise the interface as the test user would. Use .trigger() when the specific purpose is to invoke a handler with a chosen event. A triggered event does not reproduce browser default behavior, so it is not a substitute for a real interaction when that behavior matters.
Capture custom DOM events and other app interfaces
For an app-owned DOM event, register a listener on the target that emits it, save only the data the test needs, and make assertions in Cypress’s normal command chain. Attach the listener before the app dispatches the event if startup timing matters. The exact target and event name depend on the app’s implementation; Cypress cannot infer them automatically.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Repair Windows errors before they cause bigger problems3Scan for outdated or missing drivers - takes under a minuteCypress event callbacks run outside the normal Cypress command queue. Do not issue Cypress commands or assertions from those callbacks. Instead, capture the callback data in a variable or another test-scoped structure, then assert later in the test body. Cypress’s event catalog describes the event APIs and callback constraints.
Keep event assertions useful and resilient
- Assert a meaningful contract. Prefer an event name, action type, or state value that matters to the application over incidental implementation details.
- Check visible behavior too. If the user-facing result matters, assert it independently of the internal event. An event assertion can supplement, not replace, the UI check.
- Avoid volatile fields. Timestamps and other changing metadata can make whole-object comparisons brittle. Check stable fields instead.
- Choose the right call count. Assert an exact count only when the number of emissions is part of the behavior being tested; otherwise, verify the relevant call or payload without over-constraining unrelated emissions.
Whether an internal event belongs in an E2E test depends on the application’s intended contract. Cypress tutorial author Gleb Bahmutov wrote, “It is up to me, the web application developer, to decide if and how to use the events emitted by the application in my end-to-end tests.”
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Handle Cypress listener scope correctly
cy.on() listeners are scoped to the current test and are removed when that test ends. Cypress.on() listeners persist, so registering them repeatedly can accumulate listeners across tests. Cypress event callbacks also run outside its normal command queue: capture data in the callback and assert later in the test body rather than calling Cypress commands there. Use the Catalog of Events to select the appropriate event and API.
Troubleshoot missed or unreliable event assertions
- The spy is never called: Confirm the app uses the method you spied on, that the test triggered the relevant behavior, and that the spy was installed before the call. For startup emissions, install it in
onBeforeLoad. - The event fires before the listener is attached: Move observation earlier in the lifecycle. A listener attached after
cy.visit()cannot capture a startup event that already occurred. - The test passes on call count but checks the wrong event: Inspect the method’s arguments and assert the relevant payload field, not merely that some call happened.
- The assertion is flaky: Look for timestamps or other changing values in a whole-object comparison. Assert only stable fields that matter.
- A Cypress command fails inside a listener: Cypress event callbacks are outside the command queue. Save the data in the callback and make the assertion afterward in the test chain.
- A listener fires more than once across tests: Check whether it was registered with persistent
Cypress.on(). Use test-scopedcy.on()when the listener only belongs to one test. - A triggered event does not behave like a click or key press:
.trigger()invokes the handler but does not perform the browser default action. Use a normal Cypress interaction when default behavior is part of the test.
Or skip the browser setup
If your goal is a screenshot rather than an event assertion, ScreenshotNeo offers a one-request screenshot API. For example, this cURL request saves a page capture as WebP:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo API documentation for request options. It accepts cookie and consent banners and removes 60+ known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response reports the page verdict and billing status in headers. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents. The free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots.
Sign up for ScreenshotNeo’s free plan to get 1,000 screenshots a month with no card.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

