DEV Community

Cover image for 10 Mistakes to Avoid When Using Cypress
Yevhen Laichenkov
Yevhen Laichenkov

Posted on

10 Mistakes to Avoid When Using Cypress

As a seasoned Cypress user, I've observed recurring patterns of errors among many engineers over the years. So, this article aims to shed light on 10 common but often overlooked pitfalls in Cypress usage.

Whether you're new to this powerful testing tool or have been using it for some time, you're likely to discover some insightful tips and tricks that haven't been widely discussed before. Get ready to elevate your Cypress skills with some valuable lessons!

1. Using .then() instead of .should() for multiple assertions

The .then() method in Cypress is pivotal for accessing and manipulating the yielded subject within a callback function. This method is particularly useful when you need to perform specific actions or transform values.

However, a common misconception is equating .then() with .should(). These methods function differently. The .should() method is designed to automatically wait and retry until all conditions within the callback function are met. Typically, .should() passes on the same subject it received from the preceding command. Crucially, any value returned from within a .should() callback is not utilized, a key difference to remember when writing your Cypress tests.

⚠️ Ensure idempotency in .should() callbacks

It's crucial to design callback functions within .should() to be resilient and side-effect-free, capable of enduring multiple executions. This is because Cypress employs a retry mechanism for these functions. In case of an assertion failure, Cypress will repeatedly attempt the assertions until a timeout occurs. To adapt to this behavior, your code must be idempotent.

Keep in mind that .and() is an alias for .should(). Hence, it waits and retries as well.


cy.get('.header')
  .then(($header) => {
    expect($header).to.have.length(1)
    expect($div[0].className).to.match(/heading-/)
  })
Enter fullscreen mode Exit fullscreen mode

cy.get('.header')
  .should(($header) => {
    expect($header).to.have.length(1)
    expect($div[0].className).to.match(/heading-/)
  })
Enter fullscreen mode Exit fullscreen mode

2. Explicitly clearing cookies, local, and session storage before each test

Cypress is designed to maintain test integrity by automatically wiping all cookies, local storage, and session storage before each test. This feature is essential for ensuring that no residual state is carried over between tests when test isolation is enabled. Typically, you won't need to manually clear these storages unless you're addressing a specific requirement within a single test, or if test isolation is turned off. Specifically, Cypress takes care of clearing the following before each test:

  • cookies across all domains
  • local storage across all domains
  • session storage across all domains

Understanding this automatic process is crucial for writing effective and isolated tests in Cypress.


before(() => {
  cy.clearAllCookies()
  cy.clearAllLocalStorage()
  cy.clearAllSessionStorage()
});
Enter fullscreen mode Exit fullscreen mode

before(() => {
  // perform actions you need
});
Enter fullscreen mode Exit fullscreen mode

3. Passing the --headless flag

Starting from version 8.0, Cypress has streamlined its operation by defaulting to headless test execution. This enhancement means that the --headless flag is no longer necessary when you use the cypress run command. This update simplifies the testing process, making it more efficient and user-friendly. However, it's worth noting that some users still add this flag, perhaps out of habit or for explicit confirmation, even though it's no longer required.

❌
cypress run --browser chrome --headless
Enter fullscreen mode Exit fullscreen mode
✅
cypress run --browser chrome
Enter fullscreen mode Exit fullscreen mode

4. Ignoring experimental features

If you're unfamiliar with experimental features in Cypress, it's essential to start with the official documentation. Staying updated with new releases and reading release notes can give you an edge by acquainting you with the latest experimental features. I highly recommend exploring these notable features, but remember to always refer back to the documentation for the most current information:

  • experimentalMemoryManagement - enhances memory management for Chromium-based browsers, potentially improving performance and stability.
  • experimentalCspAllowList - allows you to specify which Content-Security-Policy directives are allowed during test runs, offering more control over security settings.
  • retries.experimentalStrategy - by enabling this, you can implement a tailored strategy for test retries based on flake tolerance. Options include detect-flake-but-always-fail or detect-flake-and-pass-on-threshold, providing flexibility in handling test flakiness.
  • retries.experimentalOptions - lets you set specific options for your chosen retries strategy, such as maxRetries, passesRequired, and stopIfAnyPassed, allowing for more nuanced control over test retries.

To use these experimental features, you need to configure them in your cypress.config.js file. For instance, to enable a particular feature, you would add the relevant configuration entries like this:

const { defineConfig } = require('cypress')

module.exports = defineConfig({
  experimentalMemoryManagement: true,
  experimentalCspAllowList: true,
  retries: {
    experimentalStrategy: 'detect-flake-and-pass-on-threshold',
    experimentalOptions: {
      maxRetries: 2,
      passesRequired: 2,
    },
    openMode: true,
    runMode: true,
  },
})
Enter fullscreen mode Exit fullscreen mode

⚠️ Remember, experimental features might change or ultimately be removed without making it into the core product. So use them with caution and stay informed about their status and updates.

5. Asserting DOM elements with .should('exist')

A quick GitHub search for the .should('exist') assertion reveals its presence in over 49,000 files. While some of these instances are legitimately used for non-DOM element assertions, a significant number are applied to DOM commands, where their necessity is debatable.

github search

It's important to note that Cypress inherently waits for DOM commands to become present in the DOM. This automatic waiting mechanism means that explicitly writing .should('exist') for DOM element verification is often redundant. By understanding this built-in feature of Cypress, you can streamline your tests by omitting unnecessary assertions, leading to cleaner and more efficient tests.


cy.get('button').should('exist').click()
Enter fullscreen mode Exit fullscreen mode

cy.get('button').click()
Enter fullscreen mode Exit fullscreen mode

6. Not setting up ESLint

From my experience, incorporating ESLint in all Cypress projects is not just a best practice, it's essential. However, it's crucial to go beyond just the official Cypress ESLint Plugin. I strongly recommend integrating additional plugins like chai-friendly and mocha plugins to enhance your project's code quality and maintainability.

Starting with the recommended configurations is a wise choice for most projects. But don’t stop there. Dive into the documentation and understand the full potential of these rules. Customize them to suit the specific needs of your project for optimal results. To give you a head start, here's the ESLint configuration file that I've successfully implemented across various projects:

{
  "root": true,
  "extends": [
    "plugin:mocha/recommended",
    "plugin:chai-friendly/recommended"
  ],
  "plugins": [
    "cypress"
  ],
  "rules": {
    "cypress/no-assigning-return-values": "error",
    "cypress/no-unnecessary-waiting": "error",
    "cypress/no-force": "error",
    "cypress/no-async-tests": "error",
    "cypress/no-pause": "error",
    "cypress/unsafe-to-chain-command": "off",
    "mocha/no-mocha-arrows": "off",
    "mocha/no-exclusive-tests": "error",
    "mocha/prefer-arrow-callback": "error"
  },
  "env": {
    "cypress/globals": true
  }
}
Enter fullscreen mode Exit fullscreen mode

💡 It's kind of odd that the official Cypress docs don't make a big deal about setting up ESLint. It's super important for keeping your code clean and making coding a smoother ride. In my last article, I pointed out that ESLint is the top dog in the Cypress world when it comes to downloads from NPM. That just shows how much people rely on it to keep their code in check. So, yeah, setting up ESLint in your Cypress projects? Definitely a good move.

7. Overusing { force: true }

Yes, I know that the { force: true } is super handy when you just want to skip actionability checks and get things done. But, hear me out – it's really worth it to act like a real user. Doing the actual steps a user would take to interact with your application is key. Plus, when you force things, you might miss some real bugs, because Cypress will breeze past a bunch of functional checks. That's exactly why I've got the cypress/no-force rule set up in my ESLint config.


cy.get('button').click({ force: true })
Enter fullscreen mode Exit fullscreen mode

cy.get('button').click()
Enter fullscreen mode Exit fullscreen mode

8. Using .then() to access fixture data and stub response

I've noticed folks using .then() for all sorts of stuff, but a lot of times, it's just not needed. It's like using a fancy tool when a simple one will do the job. Sure, .then() can be useful for many things, but when it comes to accessing fixture data or stubbing responses, sometimes keeping it straightforward is the way to go. Let's not overcomplicate things. Less code is better, right?


cy.fixture('users').then((json) => {
  cy.intercept('GET', '/users/**', json)
})
Enter fullscreen mode Exit fullscreen mode

cy.intercept('GET', '/users/**', { fixture: 'users' })
Enter fullscreen mode Exit fullscreen mode

9. Avoiding blockHosts

There's this super straightforward config option in Cypress – blockHosts. I've noticed it's often ignored, but it's actually pretty handy. All you gotta do is add the hosts you want to block to an array in your cypress.config.js file. Here's how it goes:

const { defineConfig } = require('cypress')

module.exports = defineConfig({
  blockHosts: [
    "*.google-analytics.com",
    "*.codesandbox.io",
    "*.loom.com",
    "*.youtube.com",
    "*.github.com",
    "*.googletagmanager.com",
  ],
})
Enter fullscreen mode Exit fullscreen mode

If you're not sure which host to block, the URL strings and URL objects guide is a great place to start. It'll help you nail down the exact host you're after.

⚠️ Cypress blocks a request to a host you've listed, it automatically whips back a 503 status code. Plus, it slaps on a x-cypress-matched-blocked-host header, so you can easily tell which rule got hit. Neat, right?

Image description

10. Using the wrong Docker image

So, I've been noticing that a lot of projects use the cypress/included docker image, which comes packed with a bunch of browsers and Cypress pre-installed. But here's the twist – they're not even testing against all those browsers, and they don't really need Cypress installed globally. Let's break down which image you actually need for your tests.

image system deps cypress browsers
cypress/factory ❗️ (if specified) ❗️ (if specified)
cypress/base
cypress/browsers
cypress/included

Most of the time, you just need the image with Node, system dependencies, and the browser you're testing with. No need to pull all the browsers. Like, if you're only testing in Chrome, cypress/browsers is your go-to. Want to build a custom image? cypress/factory is great for picking the exact versions you want. If you've got browsers installed already or want to add them yourself, cypress/base is all about those system dependencies. And if you're into the whole all-inclusive vibe, cypress/included has got you covered.

Conclusion

Cypress is a great and powerful framework for testing, but it's the little things that count. Remember the insights on .then() vs .should(), using the right Docker image, and setting up ESLint. Avoid common traps like overusing { force: true } and unnecessary assertions. This article aimed to give you the lowdown on these key details to sharpen your Cypress skills.

Hope you found some helpful nuggets here. Happy testing with Cypress!

Follow me on GitHub and Twitter.

Top comments (0)