Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Cypress has no built-in cy.hover() command. For a hover state controlled by JavaScript, dispatch mouseover, assert that the expected UI is visible, then take a fresh element or application screenshot. For CSS-only :hover styling, .trigger() is not enough; use a native-event or browser-debugging approach instead.

The reliable JavaScript-event pattern is:

cy.get('[data-cy="menu-item"]').trigger('mouseover')
cy.get('[data-cy="popover"]').should('be.visible')
cy.get('[data-cy="menu-item"]').screenshot('menu-item-hover')

This follows Cypress guidance for hover behavior, event triggering, and screenshots. Re-query the element after .trigger(); Cypress warns that chaining subject-dependent commands after .trigger() is unsafe.

1. Identify what “hover” means in your application

The correct Cypress technique depends on how the interface implements the state. A JavaScript listener may show a menu, tooltip, or preview after receiving mouseover. A stylesheet may only change appearance while the browser’s pointer is over an element through the CSS :hover pseudo-class. Those are different behaviors, even if they look identical to a user.

Implementation Recommended approach What the screenshot proves
JavaScript handler listening for mouseover .trigger('mouseover'), assert the result, then capture The handler revealed the expected UI
CSS-only :hover rules Use Chrome remote debugging to set the pseudo-class, or a native-event tool such as the community cypress-real-events plugin listed in Cypress’s plugin directory The browser rendered its actual hover pseudo-class
Need one component only Call .screenshot() on a freshly queried element The selected element, with optional padding
Need the complete application viewport Call cy.screenshot() The application screenshot

The plugin directory listing establishes that cypress-real-events is available as a community option; it does not make the plugin a Cypress requirement or establish any commercial relationship.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

2. Capture a JavaScript-driven hover state

Use stable selectors

Prefer a selector designed for tests, such as data-cy, rather than a presentation class that may change with a redesign. The trigger target must yield a DOM element and be interactable for the documented mouseover example.

Trigger, verify, then capture

describe('menu hover screenshot', () => {
  it('captures the open menu state', () => {
    cy.visit('/navigation')

    cy.get('[data-cy="menu-item"]').trigger('mouseover')
    cy.get('[data-cy="popover"]').should('be.visible')

    // Re-query after trigger; do not chain a subject-dependent command
    // from the trigger result.
    cy.get('[data-cy="menu-item"]').screenshot('menu-item-hover')
  })
})

The visibility assertion is important. Cypress commands are asynchronous, and the page can change before a screenshot finishes. An assertion that represents the intended state gives Cypress a condition to wait for instead of assuming that the image captures an instantaneous frame.

Capture the whole application instead

cy.get('[data-cy="menu-item"]').trigger('mouseover')
cy.get('[data-cy="popover"]').should('be.visible')
cy.screenshot('menu-hover')

cy.screenshot() captures the application, while .screenshot() chained from a DOM query captures that element. For an element image with breathing room, pass padding:

cy.get('[data-cy="menu-item"]').screenshot('menu-item-hover', {
  padding: 10
})

3. Keep the command chain safe

.trigger() yields the same subject, but Cypress documents chaining commands that rely on that subject afterward as unsafe. This can become flaky when the event causes a re-render, replaces the node, or changes its position. Make the screenshot target explicit with a new query:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
// Less robust when the event re-renders the menu item
cy.get('.menu-item').trigger('mouseover').screenshot('hover')

// Robust: trigger, assert, and query again
cy.get('.menu-item').trigger('mouseover')
cy.get('.popover').should('be.visible')
cy.get('.menu-item').screenshot('hover')

If the application removes and recreates the target, the second cy.get() resolves the current DOM node rather than retaining a stale subject.

4. Make the event match the application

Use mouseover for the documented JavaScript workaround

Cypress’s hover documentation specifically presents .trigger('mouseover') when the behavior depends on a JavaScript event such as mouseover. Start with the event your component actually listens for. A listener attached to mouseenter, pointerover, or another event may require that corresponding event instead; confirm the component’s implementation rather than assuming all hover behavior uses the same event.

Understand synthetic-event limits

A triggered event is programmatic. It does not claim to reproduce every detail of a person moving a physical pointer through the browser. If the test is about a JavaScript callback, that distinction is usually the desired abstraction. If it is about browser input, pointer hit testing, or a CSS pseudo-class, choose a native-input method.

5. CSS-only hover: why .trigger() fails

Cypress explicitly warns that .trigger() affects JavaScript events and does not trigger CSS effects. Therefore this test may pass the event command while the visual rule never activates:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
cy.get('.card').trigger('mouseover')
// A CSS-only .card:hover rule is not guaranteed to apply here
cy.get('.card').screenshot('card-hover')

For CSS-only behavior, Cypress’s hover workaround documentation points to Chrome remote debugging to set the hover pseudo-class. That route operates on the browser’s debugging protocol rather than dispatching a JavaScript event. It is appropriate when the artifact must show the actual :hover rendering.

When a native-event plugin is appropriate

The official Cypress plugin directory lists the community cypress-real-events extension for native system events such as hover. Treat it as an optional dependency: add it only when native pointer behavior is part of what you need to verify, and keep the JavaScript-event workaround for components whose contract is an event handler. Native tooling can add setup and browser-environment requirements, so it is not a universal replacement.

6. Control what gets captured

Element versus application

  • Use an element screenshot when reviewing a tooltip trigger, card, menu item, or component in isolation.
  • Use cy.screenshot() when layout, overlay positioning, dimmed backgrounds, or the complete viewport matters.

Padding and naming

Give screenshots deterministic names that describe the state, such as menu-item-hover. Element screenshots support a padding option, which is useful when a shadow, popover edge, or focus ring sits just outside the element’s box.

Output location and automatic failures

Cypress’s screenshot guide describes manual screenshots in both cypress open and cypress run, automatic failure screenshots during cypress run, and cypress/screenshots as the default folder. Project configuration can change that location, so inspect the project’s screenshot settings before hard-coding paths in CI or documentation.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

7. A complete example with a delayed popover

When a hover action schedules work, assert the final UI rather than inserting an arbitrary delay:

describe('account menu', () => {
  it('captures the delayed popover after hover', () => {
    cy.visit('/account')

    cy.get('[data-cy="account-menu"]')
      .trigger('mouseover')

    cy.get('[data-cy="account-popover"]')
      .should('be.visible')
      .and('contain', 'Settings')

    cy.get('[data-cy="account-menu"]')
      .screenshot('account-menu-hover', { padding: 10 })
  })
})

The text assertion makes the captured state more specific than visibility alone. If the popover appears with the wrong content, the test fails before producing a misleading “hover” artifact.

8. Troubleshoot common failures

“cy.hover is not a function”

Cypress does not provide a built-in cy.hover() command. Replace it with the documented event workaround for JavaScript-driven behavior, or use a native-event/debugging approach for CSS and real-pointer requirements.

The popover never becomes visible

  • Check that the selector identifies the element with the handler.
  • Confirm the component listens for mouseover; it may use another event.
  • Ensure the target is mounted, visible, and interactable before triggering.
  • Verify that the popover assertion is querying the correct container and is not hidden by an animation or test fixture state.

The screenshot shows the normal, not hovered, style

This commonly means the effect is CSS-only. A synthetic JavaScript event does not activate CSS :hover. Use Chrome remote debugging or the optional native-event plugin.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The test is flaky after triggering

Do not rely on the subject yielded by .trigger(). Assert the resulting state, then issue a fresh cy.get() for the element to capture. Also prefer a state assertion over a fixed sleep.

The screenshot is cropped unexpectedly

Decide whether you need the element or the full application. For an element image, include padding when an overlay edge or shadow extends beyond the element’s box. For a page-level composition, use cy.screenshot().

The image is not where CI expects it

The documented default is cypress/screenshots, but configuration may override it. Check the project’s screenshot configuration and the command mode (cypress open versus cypress run).

9. Reliability and maintenance checklist

  • Model the implementation: JavaScript event, CSS pseudo-class, or native pointer behavior.
  • Use stable test selectors.
  • Trigger the event only after the target is available and interactable.
  • Assert the exact hover result before capturing.
  • Re-query the screenshot subject after .trigger().
  • Use descriptive, deterministic screenshot names.
  • Capture the element or application according to the review goal.
  • Keep browser-debugging or native-event dependencies limited to tests that truly need them.
  • Verify screenshot output paths in the project configuration.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If you need a rendered screenshot outside a Cypress test, ScreenshotNeo returns an image or PDF from one request. It can accept consent banners before capture and remove more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For a direct call, see the ScreenshotNeo API documentation:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo includes full-page and element capture, device and viewport controls, custom CSS and JavaScript, waits, request blocking, cookies and headers, caching, signed links, asynchronous jobs, bulk capture, and PDF options. One thousand screenshots per month are free with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account to try it.

Frequently Asked Questions

Can I assert a hover style without taking a screenshot?

Yes. Assert the visible menu, tooltip, class, or text that represents the state. Add a screenshot only when you need a visual artifact for review or debugging.

Should I use mouseover or mouseenter?

Use the event your component handles. The documented Cypress workaround uses mouseover; a component implemented with a different event must be tested with that event or an appropriate native-input method.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Does an element screenshot include an opened popover positioned elsewhere in the DOM?

It captures the selected element’s rendered bounds. If the popover is outside those bounds, capture the application with cy.screenshot() or select a container that includes both pieces.

Will Cypress screenshots replace visual-regression tooling?

Cypress can create the screenshot artifact, but comparison, baselines, and review depend on the visual-testing workflow you add around those files.

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.