How to load the React DevTools extension in Cypress

January 7, 2020

•

By Gleb Bahmutov

DevTools is invaluable for debugging an application, or understanding why an end-to-end test goes astray. During Cypress tests, you can click on any command to see additional information for that command, inspect DOM elements, and observe network calls. Many frameworks provide their own extensions to DevTools to show framework-specific information during run-time. In this blog post I will show how to automatically load the React DevTools Chrome extension when launching the browser during Cypress tests.

Get the extension

First, we need the extension itself. The source code for the extension is part of the larger facebook/react monorepo located inside the packages/react-devtools folder. But this is not the compiled Chrome extension distribution. To get the unpacked extension suitable for loading, I will grab it from the existing installation. My Chrome already has this extension installed. Open chrome://extensions/ to see it:

Installed Chrome extensions

Notice the unique ID fmka... of the extension highlighted in the above screenshot. All extensions are stored in the browser's data folder. We can find that folder's location by opening the special url chrome://version and searching for "Profile Path"

Chrome profile path holds installed extensions

There are a lot of extensions usually inside the Profile Path/extensions, and this is why we need to remember the extension's id.

$ ls /Users/gleb/Library/Application\ Support/Google/Chrome/Default/Extensions/f*
/Users/gleb/Library/Application Support/Google/Chrome/Default/Extensions/fahmaaghhglfmonjliepjlchgpgfmobi:
1.396.0_0

/Users/gleb/Library/Application Support/Google/Chrome/Default/Extensions/fdpohaocaechififmbbbbbknoalclacl:
6.5_0

/Users/gleb/Library/Application Support/Google/Chrome/Default/Extensions/felcaaldnbdncclmgdcncolpebgiejap:
1.2_0

/Users/gleb/Library/Application Support/Google/Chrome/Default/Extensions/fkkaebihfmbofclegkcfkkemepfehibg:
1.5_0

/Users/gleb/Library/Application Support/Google/Chrome/Default/Extensions/fmkadmapgofadopljbjfkapdkoienihi:
4.2.1_0

Our Chrome extension has version 4.2.1_0 - we can copy the folder Profile Path/extensions/fmka.../4.2.1_0 and store it alongside Cypress tests.

Note: you can find the full source code for this blog post in the "Blogs" section of cypress-example-recipes. This includes the React DevTools extension folder.

Load the extension

To load the extension from a folder, we will use Chrome's command line argument --load-extension=<folder path> We can add this argument to the long list of "standard" arguments Cypress uses to launch Chrome under its control from the plugins file. Here is the relevant cypress/plugins/index.js file:

const path = require('path')
module.exports = (on, config) => {
  on('before:browser:launch', (browser, launchOptions) => {
    console.log('launching browser %o', browser)

    // only load React DevTools extension 
    // when opening Chrome in interactive mode
    if (browser.family === 'chromium') {
      // we could also restrict the extension 
      // to only load when "browser.isHeaded" is true
      const extensionFolder = path.resolve(__dirname, '..', '..', '4.2.1_0')

      console.log('adding React DevTools extension from', extensionFolder)
      launchOptions.args.push(`--load-extension=${extensionFolder}`)

      return launchOptions
    }
  })
}

Note: this blog post shows the code needed to load the extension in Cypress v3.8.1. Check the up-to-date recipe using the latest released version of Cypress in the "Blogs" section of cypress-example-recipes repository.

Cypress starts, and when the user selects a spec file and Chrome browser, the event before:browser:launch is emitted, giving our code a chance to modify the browser's launch arguments.

Clicking on spec.js while Chrome Canary browser is selected

The terminal prints the following:

launching browser {
  displayName: 'Canary',
  family: 'chromium',
  isChosen: true,
  majorVersion: 81,
  name: 'canary',
  path: '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
  version: '81.0.4011.0',
  isHeaded: true,
  isHeadless: false
}
adding React DevTools extension from 
/Users/gleb/git/cypress-example-recipes/examples/blogs__use-react-devtools/4.2.1_0

The loaded browser shows that the React DevTools extension is present and has detected the React library on the page.

The React DevTools icon shows that React has been detected

Before using React DevTools we must do one more thing: the Cypress Test Runner UI is a React app itself; the actual web application under test runs in an iframe. Thus we need to "tell" the extension this fact. We can do this for a single cy.visit

it('loads React DevTools extension', () => {
  cy.visit('localhost:3000', {
    onBeforeLoad (win) {
      // this lets React DevTools "see" components inside application's iframe
      win.__REACT_DEVTOOLS_GLOBAL_HOOK__ = window.top.__REACT_DEVTOOLS_GLOBAL_HOOK__
    },
  })
})

Or you can set the hook for every window automatically using an event, and the snippet below could be even placed in the cypress/support/index.js file to make sure it is applied automatically to every visit in every spec.

Cypress.on('window:before:load', (win) => {
  // this lets React DevTools "see" components inside application's iframe
  win.__REACT_DEVTOOLS_GLOBAL_HOOK__ = window.top.__REACT_DEVTOOLS_GLOBAL_HOOK__
})

After a test like this one we can inspect the components:

/// <reference types="cypress" />
it('loads React DevTools extension', () => {
  cy.visit('localhost:3000')
  cy.get('.board-row').eq(0).find('.square').eq(0).click()
  cy.get('.board-row').eq(0).find('.square').eq(1).click()
})
The React DevTools extension showing Tic-Tac-Toe components

Limitations

Currently, I see several shortcomings when using the loaded DevTools. First, the extension does not clear the components loaded from previous test runs. This is why the screenshot above shows multiple "Game" components. It would be nice to communicate somehow with the extension to send the "reset" command before each test.

Second, the extension does not take into the account the position and scaling of the iframe used to show the application. Thus the component's highlight in the above image is offset from its true position, and has incorrect scale.