Despite being among Cypress’s most requested features, native support
for triggering hover actions remains unsupported in Cypress. In other words, there’s no cy.hover()
command that you
can call to get the element to act like you hovered over it. If you try to use cy.hover()
, you’ll get a detailed error
message that looks like the one below:
Not to fear: in this article we’ll cover several different workarounds that allow you to test end-to-end scenarios that require hover actions.
Why isn’t there a cy.hover()?
Since the Cypress test runner runs within the browser process, Cypress must simulate user actions and the corresponding events via JavaScript. This means that unlike other tools like Selenium and Playwright, all Cypress events are untrusted events, and lack their associated privileges.
But what exactly is the difference between a trusted and an untrusted event? This is what w3.org has to say:
Events that are generated by the user agent, either as a result of user interaction, or as a direct result of changes to the DOM, are trusted by the user agent with privileges that are not afforded to events generated by script through the
createEvent()
method, modified using theinitEvent()
method, or dispatched via thedispatchEvent()
method. TheisTrusted
attribute of trusted events has a value oftrue
, while untrusted events have an isTrusted attribute value offalse
.
Most untrusted events will not trigger default actions, with the exception of the
click
event. This event always triggers the default action, even if theisTrusted
attribute isfalse
(this behavior is retained for backward-compatibility). All other untrusted events behave as if thepreventDefault()
method had been called on that event.”
Source: w3.org’s DOM Level 3 Events documentation
In other words, any event not generated by a browser acts like preventDefault()
was called on it, with the exception
of click
.
When you combine the fact that all native Cypress events are simulated with the fact that there’s no way to directly
trigger CSS pseudo classes like :hover
from JavaScript, it becomes clear why cy.hover()
has been missing since 2015.
Workarounds
While Cypress doesn’t natively support simulating hover, you can still accomplish your testing goals, with some caveats. Generally, there are a couple of reasons why you might want to simulate hover in your tests:
- You want to act on an element that will be invisible until hovered over (like a dropdown or a button).
- You want to test styling changes/page state changes that happen on hover.
We’ll cover how to implement each of these scenarios using the sample page below:
|
|
To follow along, simply set up a local dev server that contains this page and add Cypress to that project.
Option 1: The force
attribute
If the reason you want cy.hover()
is to interact with hidden elements, you have a couple of options. The first option
is to force the event to occur even though the target element is not visible. Here’s an example of how to do that:
|
|
On our web page, the <button>
element is hidden and only shows up on the page when the user hovers over its parent
element. Normally, this action would fail, because Cypress performs
visibility checks on elements
before performing any actions on them. If the element fails any of those checks, the action will fail and Cypress will
display an error like this:
By passing {force: true}
to click()
, you tell Cypress it should ignore whether or not the element is visible and
instead just click it anyway. This works, but it isn’t always a good idea - Cypress does those visibility checks for a
good reason. For example, imagine if this button were hidden not because the user hasn’t hovered, but rather because the
user has not completed some prior part of the workflow. By passing { force: true }
, we run the risk of completing
actions that a real user could never complete, and thus miss out on potential bugs lurking in our application.
Option 2: The invoke()
command
An alternate workaround is to explicitly make the element visible and then perform your desired action on it. Here’s how you do that:
|
|
The Cypress invoke()
command allows you to call a function. In this case, the function you’re calling is the jQuery
show
method. Calling this function forces the element to be displayed on the page by setting the display
CSS
property to true
. It then returns the associated element, allowing you to invoke click()
on it without first
triggering a hover.
As a final improvement, we can make sure our target is invisible before we force it into visibility like this:
|
|
If the element is visible prior to calling invoke()
, the should
assertion will fail.
Option 3: Simulate the hover via custom JavaScript
Unfortunately, many hover scenarios are not as straightforward as marking an element as visible prior to executing the
next action. As we’ve already discussed, Cypress can’t trigger the :hover
pseudo class, but there are many other
potential situations that wouldn’t be covered by the two previous workarounds.
A more robust approach to simulating hovers is to emit simulated mouse events via custom JavaScript. Consider the following JavaScript snippet from the test page above:
|
|
On our example page, an element with an id of message
is updated whenever the user hovers over it.
We’ll test this behavior by triggering events in our Cypress code using cy.trigger()
. Here’s a code example that tests
whether the message
element has the correct content:
|
|
In this example we first force the buttons to be visible, then click the element to ensure the element has some initial text, and finally trigger the associated mouse event before asserting the final text of the element.
This method works, but in the end it’s not a fool-proof solution, since these simulated events are untrusted. Thankfully, there’s one last workaround: using real events instead of simulated events in JavaScript.
How is this possible? Using the Chrome DevTools Protocol (CDP). The CDP is a set of APIs that allow you to control the various operations of the browser at a very low level. Essentially, it’s an interface that allows you to programmatically control a browser instance.
Cypress already uses the CDP for a variety of tasks under the hood, so you don’t need to install anything extra to use it. The only thing you need to add to your project is a plugin called cypress-real-events. Adding that provides a set of custom Cypress commands you can use to fire real browser events, hover being one of them.
It does come with a few caveats though:
- As you can probably guess from the name, it only works in Chromium based browsers
- Much like real events, if the event fails to trigger, no error message will be reported, so you have to be careful how you use it
- The hover state isn’t visible in DOM snapshots
That said, let’s see how to use it. Install the library by running the following in your terminal:
|
|
And then add the following line in your cypress/support/index.{js,ts}
or your cypress/support/e2e.js
file:
|
|
And you’re good to go! Let’s use this package to test for hover in our page:
|
|
It’s as simple as that! You get the element you want, you call .realHover()
to simulate a hover event, and then you
assert that the page is in the state we expect it to be. In the first assertion, we’re testing that the paragraph has
the color red then hovered over, and in the second, we’re testing that the button is visible and can be clicked when the
<div>
is hovered over.
Reflect: A testing tool with support for generating real hover events
Reflect is a no-code testing tool that records your actions and turns them into a repeatable test. Virtually any action you can take on a browser is detected by Reflect, including hover actions. And since Reflect uses standards-based browser APIs to control the browser, every action appears as if it’s run from a real end user.
Get automated test coverage for your app today — try it for free.