heroPhoto by 傅甬 华 on Unsplash

Fixing Cypress errors part 1: chromium out of memory crashes

After recently working on an existing project that adopted Cypress, I was both amazed at how pleasant the local developer experience was and how badly it runs on the CI pipeline. No joke, at one point I actually missed Selenium. To debug the issue, I must have run the pipeline over 100 if not 200 times and read many many Github issue threads. Finally, I was able to problem resolved and noted down a few things along the way which may help someone else in the same shoes. I am pretty confident these solutions will fix your problems, if not leave an angry comment and let me know that I failed πŸ˜‚

Blog Series:

Error: Out of memory, chromium renderer crashed

Chromium crashing is one of the issues I kept seeing in the pipeline. It would happen even when no code was changed and with all the tests passing locally.

cypress crashing due to out of memory

Cypress team was kind enough to give a very detailed error message in the console and even a link pointing to a potential fix. If you follow the link provided, you will find the same error description on Cypress' official website and yet another link to click on which takes you on this Github issue 350. A few pieces of advice were offered here on this thread, let's take a look at them separately.

Solution 1: Use --ipc=host Docker flag

The was a recommended fix from one of the Cypress authors.

This is actually not indicative of a memory leak inside of Cypress (nor your application). This has to do with the way that browsers work under the hood. Luckily, we have a found a simple one line fix for this problem - simply add the flag --ipc=host.

IPC stands for inter-process communication, and it is related to Docker networking. Docker allows a few different options and host is one of them. With this option, it is meant to enable the Docker container to use the host system’s IPC namespace. It seems to solve insufficient shared memory (shm) issues within worker (child containers). To be completely honest, it is a little over my head what the underlying issue is here. My understanding is that this config allows the child container to utilise its parent's memory resources which would be more than its own, and thereby eliminating the problem.

If networking is something you are interested or knows a lot about, maybe you will have better luck with the Docker doc, which explains it this in more detail.

So did this fix my problem? Nope. When I checked this workaround was already in place, but I was still getting those crashes reasonably consistently. But this might be worth trying if you do not have this in place already.

Solution 2: Use --disable-dev-shm-usage Cypress flag

If you continue to read in the comments in Github issue 350 you will see people suggest using --disable-dev-shm-usage flag with Cypress.

When I decided to try this, I found out that the project already had this fix in place, but it was using the wrong config. It was using chrome browser family with Cypress v4 (see below for more description on why this is wrong). Unfortunately, even after correcting this mistake, the pipeline was still crashing. Which meant I had to continue my search.

Given the number of positive emojis on the Github comment, it must have solved the problem for a lot of people. So don't let the fact it didn't work for me put you off, give this a try this by keep reading.

There are different ways to implement the same fix in Cypress, so we will talk about both here.

Using browser family

If you are using family property, make sure to use chromium if you are using Cypress v4 or later. If you are using v3 or earlier then use chrome.

As of Cypress v4, the property now supports two options: chromium or firefox.

// cypress/plugins/index.js

// v4
module.exports = (on, config) => {
  on('before:browser:launch', (browser = {}, launchOptions) => {
    if (browser.family === 'chromium') {
      launchOptions.push('--disable-dev-shm-usage');
    }

    return launchOptions;
  });
}

// v3
module.exports = (on, config) => {
  on('before:browser:launch', (browser = {}, args) => {
    if (browser.family === 'chrome') {
      args.push('--disable-dev-shm-usage');
    }

    return args;
  });
}

There was a breaking change introduced in Cypress v4.0.0.

We updated the Cypress browser objects of all Chromium-based browsers, including Electron, to have chromium set as their family field. Addresses #6243.

Source: https://docs.cypress.io/guides/references/changelog.html#4-0-0

See this Github comment on differences between v3 and v4.

Using browser name

Using browser name means you have to specify the exact browser name.

This property supports these options: chrome, electron, edge or firefox.

For example, if you wanted to use both chrome and electron then your snippet might look like this.

// cypress/plugins/index.js

module.exports = (on, config) => {
  on('before:browser:launch', (browser = {}, args) => {
    if (browser.name === 'chrome') {
      args.push('--disable-dev-shm-usage')
    } else if (browser.name === "electron") {
      args["disable-dev-shm-usage"] = true;
    }

    return args
  })
}

PS: notice how Electron sets the argument slightly differently. There is no better reason other than Electron takes in options different to other browsers. See this Github comment for more information.

Solution 3: Change over from Electron to Chrome

So where do we stand right now? The project I was working on already had the first two fixes in place (although I had to fix the browser family). Just before I was losing hope, I saw this Github comment asking when the memory issue will be fixed for Electron.

Could this be the issue?

Since the project I was working on was already using the cypress/included docker image, this meant the Docker image included Chrome browser already.

image: cypress/included:4.10.0

And I could simply use --headless --browser chrome flags to tell Cypress to use Chrome instead of Electron.

cypress run --headless --browser chrome

After pushing this change, I finally got some positive results!

E2E tests passing

This was the first time where I was able to get the pipeline to pass 5 times in a row. The previous best score was 3 out of 5 runs. Of course, all subsequent runs have all been running fine as well!

Solution 4: Reduce the number of tests kept in memory

As mentioned already, I was able to address all of the pipeline issues I was seeing using the above solutions. This solution was something I discovered, tried out and didn't have much luck with it. I thought I'd document it here anyway, in case it is the necessary fix for some people's problems.

In a more recent (and still open in time of writing) Github issue 4164 introduced me to this Cypress config called numTestsKeptInMemory.

You can set it in cypress.json file like this or pass it in as another flag.

// cypress.json
{
  "baseUrl": "http://localhost:8080/",
  "numTestsKeptInMemory": 0,
}

The default value of numTestsKeptInMemory is 50. For some people reducing this number seemed to have helped them. The recommendation is to try with a low value such as 1, and if that doesn't work then finally try 0.

Note: setting numTestsKeptInMemory to 0 will disable this feature altogether, and no tests will be kept in memory.

There is the official documentation for more information.

Final words

CI pipeline issues such as this are very annoying to fix because it is not reproducible locally. Which means it requires pushing changes and wait for the pipeline to finish to test anything. I hope you managed to resolve your issue using one of the solutions mentioned here, if not try why out Part 2 and Part 3 of this blog series, hopefully, one of them contains your answers!

Just a side point, as part of the research I found out about this open Github issue 349 which is looking at introducing auto-recovery to Cypress which may continue running even after a crash. However, since it was opened back in 16th Dec 2016, therefore I wouldn't hold my breath for it to be done any time soon.