How to click buttons and links in Cypress

How to click buttons and links in Cypress

When it comes to clicking elements in Cypress, the cy.click() command isn’t just a basic mouse event simulation—it’s a finely tuned interaction that mimics a real user’s click as closely as possible. It waits for the element to be actionable, checks visibility, and fires the right series of native events behind the scenes, making tests less flaky and more reliable.

For example, a simple click works like this:

cy.get('button.submit').click()

This goes beyond what a plain JavaScript element.click() would do in the browser developer console. Cypress waits until the button is visible, enabled, and not covered by other elements. It sequentially triggers mousedown, mouseup, and click events, respecting the browser event model perfectly.

You can pass options to click() to modify behavior. For instance, simulating a right-click is as easy as:

cy.get('div.context-menu-area').click({ button: 'right' })

This fires the equivalent of a context menu event.

Modifiers are supported too, so if you need to test Shift+Click to, say, select multiple items, you can simulate that without jumping through hoops:

cy.get('.multi-select-item').click({ shiftKey: true })

If your target is hidden inside a scrollable container, Cypress will automatically scroll the element into view beforehand. That’s important—most UI frameworks expect elements to be visible for clicks to make sense. But if you’re deliberately testing hidden elements or tricky UI states, you can override that with force: true, which clicks regardless of element visibility. Use cautiously.

Here’s how to force a click:

cy.get('button#secret').click({ force: true })

One weird edge case is when you want to click at a specific coordinate within an element, like near the bottom-right corner of a canvas or a tiny icon inside a bigger button. Cypress supports position coordinates like topLeft, bottomRight, or even explicit x and y offsets:

cy.get('canvas#signature').click('bottomRight')

or custom pixel offsets:

cy.get('.icon-close').click(5, 5)

Finally, if your click triggers a navigation or page reload, Cypress automatically waits for the new page load to complete, so you don’t have to manually chain waits or delays. This asynchronous handling is part of what makes Cypress commands robust and readable.

Understanding these nuances transforms cy.click() from a simple utility into a powerful tool for replicating user behavior faithfully, crucial for writing solid, resilient UI tests.

Before moving on, remember that clicking isn’t just about triggering events; it’s about replicating the *human* experience of clicking. If the user can’t interact with a button because it’s covered by a loading spinner, forcing clicks can mask real bugs in your app. Test faithfully, but also thoughtfully.

Now, next up is making user interactions more realistic and less brittle by combining clicks with other commands and using best practices to avoid flaky tests. For instance, chaining type() after click() or waiting for API responses before clicking, but that’s a topic for—

Best practices for simulating user interactions

To improve the realism of your tests, consider combining Cypress commands judiciously. For instance, after clicking a button, it’s often necessary to type into a field. You can achieve this seamlessly:

cy.get('button#submit').click().then(() => {
  cy.get('input#username').type('myUsername')
})

This approach ensures that the input is only targeted after the button click has occurred, preserving the logical flow of user interactions.

Another best practice is to wait for API responses before proceeding with further actions. That’s particularly important in applications where subsequent interactions depend on data fetched from a server. Using cy.intercept() to stub or spy on network requests can help manage this:

cy.intercept('GET', '/api/data').as('fetchData')
cy.get('button#loadData').click()
cy.wait('@fetchData')
cy.get('div#dataDisplay').should('contain', 'Expected Data')

By waiting for the API call to resolve, you ensure that your tests reflect the actual user experience, preventing them from failing due to timing issues.

In addition to chaining commands, it is useful to leverage Cypress’s built-in assertions to validate that the application is in the expected state before moving on. This not only reduces flakiness but also provides clear feedback during test execution:

cy.get('input#email').type('[email protected]')
cy.get('button#submit').click()
cy.get('h1').should('contain', 'Welcome')

Here, you’re not just clicking and typing; you’re asserting that the application responds as expected after each interaction. This step especially important for maintaining the integrity of your tests.

Moreover, consider using cy.focused() to ensure that the right element is focused before performing actions like typing. This can help avoid issues where commands are sent to the wrong element:

cy.get('input#password').focus().type('securePassword')

Focusing on the right element before typing ensures that your actions align with user expectations, as users naturally focus on fields before entering data.

Lastly, remember to use the timeout option in your commands where necessary. This helps in situations where elements are dynamically loaded or take time to appear, thus giving your tests a better chance of passing consistently:

cy.get('button#dynamicButton', { timeout: 10000 }).click()

By extending the timeout, you allow your test to wait longer for conditions to be met, which is especially useful in complex applications where loading times can be unpredictable.

Best practices for simulating user interactions in Cypress hinge on thoughtful combinations of commands, effective waiting strategies, and thorough assertions. This leads to tests that not only pass consistently but also reflect the true user experience, making your test suite a reliable safeguard for your application’s functionality.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

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