October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan NowOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
Cypress

How to Click Submenus in Cypress (Click, Hover, CSS, and Troubleshooting)

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.

To click a submenu item in Cypress, open or reveal the menu the way your application does, query the item with a stable selector scoped to that menu, click it, and assert the resulting URL or UI state. For click-to-open menus, use a normal .click(). For JavaScript-driven hover behavior, use .trigger('mouseover'); Cypress has no built-in cy.hover(), and a synthetic event cannot activate CSS-only :hover rules.

The reliable pattern for a click-open submenu

Model the user journey in separate commands: open the parent menu, find the child link inside that menu, click it, then query the destination afresh. A dedicated application-owned attribute such as data-cy is usually more stable than a class name or a long CSS path.

cy.get('[data-cy="menu-toggle"]').click()

cy.get('[data-cy="products-menu"]')
  .should('be.visible')
  .contains('a', 'Analytics')
  .click()

cy.location('pathname').should('eq', '/products/analytics')

The names and route above are examples; replace them with the attributes, label and URL used by your application. Scoping the text query to [data-cy="products-menu"] prevents a second “Analytics” link elsewhere on the page from being selected.

Why query the result again?

A click can navigate, rerender the navigation tree, or remove the menu from the DOM. Cypress warns that chaining assertions from the old subject after .click() can be unsafe when the click changes the page. Start a new query for the expected result:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
cy.get('[data-cy="products-menu"]')
  .contains('a', 'Analytics')
  .click()

cy.get('[data-cy="analytics-heading"]')
  .should('be.visible')

Choosing selectors that survive UI changes

Prefer dedicated data attributes

Ask the application team to add attributes intended for tests, for example:

<button data-cy="menu-toggle">Products</button>
<nav data-cy="products-menu">
  <a data-cy="analytics-link" href="/products/analytics">Analytics</a>
</nav>

Then the test can use either the item’s own attribute or a scoped text query:

cy.get('[data-cy="menu-toggle"]').click()
cy.get('[data-cy="analytics-link"]').click()

Handle duplicate matches deliberately

A click normally targets one DOM element. If a query matches several elements, narrow it rather than clicking every match:

  • Scope with a menu container: cy.get('[data-cy="products-menu"]').contains('a', 'Analytics').
  • Use an exact, application-owned attribute for a unique item.
  • Use .first() or .eq(index) only when the ordering is part of the UI contract and is documented in the test.

A hidden mobile menu and a visible desktop menu can create duplicate labels. Scoping to the currently opened container is safer than blindly choosing the first match.

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

Hover-dependent submenus

First identify what actually reveals the submenu. Some applications listen for a JavaScript mouseover event; others rely solely on the browser’s CSS :hover state.

When JavaScript handles mouseover

For an event handler that responds to mouseover, Cypress’s documented workaround is .trigger('mouseover'):

cy.get('[data-cy="products-item"]')
  .trigger('mouseover')

cy.get('[data-cy="products-menu"]')
  .should('be.visible')
  .contains('a', 'Analytics')
  .click()

cy.location('pathname').should('eq', '/products/analytics')

Waiting for visibility before clicking makes the intermediate state explicit and lets Cypress retry the assertion while the application opens the menu.

When CSS :hover reveals the submenu

trigger('mouseover') dispatches a JavaScript event; it does not move a real pointer and therefore does not create CSS hover effects. A CSS-only submenu may remain hidden even though the event was triggered. Cypress documentation states that there is no built-in cy.hover() command. If genuine pointer movement is required, use a real-event solution such as the cypress-real-events plugin, after confirming that its current support matches your Cypress version and browser setup.

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

Keep the test focused on the behavior your product promises. If the requirement is “a pointer moving over this item opens a CSS submenu,” a real pointer event is the faithful test. If the requirement is an application JavaScript handler, trigger('mouseover') is sufficient and faster.

Keyboard-accessible alternatives

Many menus support keyboard navigation as well as hover. If the product specification says the submenu opens on focus or a key, test that contract directly instead of simulating hover:

cy.get('[data-cy="products-item"]').focus()
cy.get('[data-cy="products-menu"]').should('be.visible')
cy.get('[data-cy="products-menu"]').contains('a', 'Analytics').click()

Use the event or interaction that real users are expected to use; do not add a forced click merely because a menu is difficult to reach.

Actionability, retries, and timing

A normal .click() waits for Cypress’s actionability checks. These include whether the element is visible, covered, disabled or still moving. While waiting, Cypress can re-run the queries that produced the element. The click itself occurs once when the target becomes actionable; assertions after it are the parts that retry.

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.

For a menu with an animation, assert the opened state before clicking:

cy.get('[data-cy="menu-toggle"]').click()
cy.get('[data-cy="products-menu"]')
  .should('be.visible')
  .and('not.have.class', 'is-opening')
cy.get('[data-cy="products-menu"]').contains('a', 'Analytics').click()

Avoid arbitrary sleeps such as cy.wait(2000) when a meaningful state, selector or network condition can express readiness. A state assertion documents what the test needs and finishes as soon as that state exists.

When (and when not) to use force: true

click({ force: true }) bypasses actionability checks. It can fire an event on an element that is hidden, covered by an overlay, outside the viewport or still transitioning. That makes it an escape hatch, not a normal submenu technique.

cy.get('[data-cy="analytics-link"]')
  .click({ force: true })

Use force only when the test intentionally verifies an event despite presentation—for example, a lower-level component contract where visibility is tested separately—and leave a comment explaining why. In an end-to-end navigation test, a forced click can make a broken menu pass while real users cannot reach the link.

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

Diagnosing a failed submenu click

“Element not found”

  • Confirm the parent menu was opened before querying its child.
  • Check spelling, case and whitespace in the visible label.
  • Inspect the rendered DOM for a portal: the submenu may be mounted outside the parent element, so scope to the actual submenu container instead.
  • Check responsive conditions; the test viewport may render a different navigation.

“Element is not visible”

  • For a click-open menu, assert the menu’s visible state after opening.
  • For a hover menu, determine whether the reveal is JavaScript or CSS. A synthetic event cannot activate CSS-only hover.
  • Look for a hidden duplicate matching the same text and narrow the selector to the visible menu.

“Element is covered” or “not receiving pointer events”

  • Identify cookie notices, dialogs, sticky headers or loading masks covering the item.
  • Close the overlay through the same control a user would use, or wait for the application’s loaded state.
  • Check whether an animation has finished before clicking.
  • Do not jump straight to force: true; coverage often indicates a real usability defect.

Timeout after trigger(‘mouseover’)

  • Verify that the application listens for mouseover, not only mouseenter or a pointer event.
  • If the reveal is CSS-driven, switch to a real pointer-event approach.
  • Ensure the event is triggered on the element that owns the handler, not a child icon inside it.

Click succeeds but the route assertion fails

  • Use the application’s actual destination, including a trailing slash, hash or locale prefix where applicable.
  • Assert the pathname, query string or a destination heading according to the contract; do not assume every navigation is a full page load.
  • Query the destination after the click rather than asserting against the stale menu subject.

Complete examples for common menu implementations

Button opens an inline submenu

describe('Products submenu', () => {
  it('opens and navigates to Analytics', () => {
    cy.visit('/dashboard')
    cy.get('[data-cy="menu-toggle"]').click()
    cy.get('[data-cy="products-menu"]').should('be.visible')
    cy.get('[data-cy="products-menu"]')
      .contains('a', 'Analytics')
      .click()
    cy.location('pathname').should('eq', '/products/analytics')
  })
})

JavaScript mouseover reveal

it('navigates through the mouseover submenu', () => {
  cy.visit('/dashboard')
  cy.get('[data-cy="products-item"]').trigger('mouseover')
  cy.get('[data-cy="products-menu"]').should('be.visible')
  cy.get('[data-cy="products-menu"]')
    .contains('a', 'Analytics')
    .click()
  cy.location('pathname').should('eq', '/products/analytics')
})

Multiple menus with the same child label

cy.get('[data-cy="admin-menu"]').within(() => {
  cy.contains('a', 'Settings').click()
})
cy.location('pathname').should('eq', '/admin/settings')

.within() keeps every subsequent query inside the selected menu, which is useful when several navigation groups contain identically named links.

Performance and reliability practices

  • Use short, stable selectors rather than deeply nested CSS chains.
  • Open one menu per test path and assert the opened state once.
  • Prefer deterministic state assertions to fixed delays.
  • Keep route assertions specific enough to catch a wrong destination, but do not couple them to unrelated rendering details.
  • Separate accessibility or keyboard tests from pointer-specific tests so a failure identifies the interaction that regressed.
  • Capture diagnostic screenshots or videos from your Cypress runner when an intermittent overlay or animation is suspected.
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 your goal is a clean image of a menu state or page rather than an interaction assertion, ScreenshotNeo provides a single website-screenshot API call. 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.

For an ordinary page capture, use the documented API parameters (the API also accepts the parameter names used by other screenshot services):

curl -G "https://api.screenshotneo.com/v1/shot" 
  -d access_key=YOUR_API_KEY 
  --data-urlencode url=https://stripe.com 
  -o shot.webp

Python:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://stripe.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const buffer = Buffer.from(await res.arrayBuffer());
await require('fs').promises.writeFile('shot.webp', buffer);

See the complete parameter reference and response details in the ScreenshotNeo documentation. For submenu-specific visual states, you can combine a custom script or click option with waits, selector targeting and hidden elements; the service also supports full-page captures with lazy images, CSS-selector element captures, dark mode, device presets, arbitrary viewports, retina scale, custom CSS and JavaScript, request blocking, cookies, headers, user agents, authorization, timezone and geolocation. PDF output, HTML/CSS-to-image, signed links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, caching with a chosen TTL, a usage API and an OpenAPI specification are available on every plan.

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

ScreenshotNeo includes an MCP server with take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients. The Free plan includes 1,000 shots each month without a card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to try it.

FAQ

Can I use cy.contains() without cy.get()?

Yes. cy.contains('a', 'Analytics') can locate a link directly, but scoping it to the opened submenu is safer when labels repeat.

Does trigger(‘mouseover’) move the mouse?

No. It dispatches a JavaScript event and does not apply CSS :hover effects or emulate physical pointer movement.

Should every submenu test use force: true?

No. A normal click checks that the item is user-actionable. Force is appropriate only for a deliberate, separately justified exception.

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

What should I assert after clicking?

Assert the contract that matters: a pathname, query string, destination heading, selected state or another newly queried result. Choose the assertion that distinguishes the intended submenu destination from nearby links.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

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

Read next

Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.