cypress-io / code-coverage

Saves the code coverage collected during Cypress tests
MIT License
433 stars 108 forks source link

Component tests with create-react-app (react-scripts-v4) don't instrument #461

Open twhoff opened 3 years ago

twhoff commented 3 years ago

I've been trying to get code coverage working for the last few days using the fragments of documentation I can find throughout the Cypress offical site and code repos...

App is CRA Using @cypress/instrument-cra@latest

Using example react-scripts-v4

Simple test using Card.js

import React from 'react'

const Card = () => <div>Card</div>

export default Card

Card.test.js

import React from 'react'
import { mount } from '@cypress/react'
import Card from './Card'

it('renders learn react link', () => {
    mount(<Card />)
    expect(cy.get('div').contains('Card'))
})

cypress.json

{
  "testFiles": "**/*.test.{js,ts,jsx,tsx}",
  "componentFolder": "src"
}

plugins/index.js

const injectDevServer = require('@cypress/react/plugins/react-scripts')

module.exports = (on, config) => {
    injectDevServer(on, config)
    require('@cypress/code-coverage/task')(on, config)

    // add other tasks to be registered here

    // IMPORTANT to return the config object
    // with the any changed environment variables
    return config
}

support/index.js

require('@cypress/code-coverage/support')

package.json

{
  "name": "react-scripts-v4",
  "description": "Instrumented code for react-scripts v4",
  "private": true,
  "scripts": {
    "start": "react-scripts -r @cypress/instrument-cra start",
    "test": "cypress open-ct",
    "cy:open": "cypress open",
    "check-coverage": "check-coverage src/App.js src/calc.js src/Child.js src/index.js",
    "only-covered": "only-covered src/App.js src/calc.js src/Child.js src/index.js"
  },
  "devDependencies": {
    "@cypress/code-coverage": "^3.9.6",
    "@cypress/instrument-cra": "^1.4.0",
    "@cypress/react": "^5.9.1",
    "@cypress/webpack-dev-server": "^1.3.1",
    "check-code-coverage": "^1.10.0",
    "cypress": "^7.5.0",
    "cypress-expect": "^2.4.1",
    "html-webpack-plugin": "4",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-scripts": "^4.0.3"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

Basically I do this:

> yarn start

> yarn test

The out.json in .nyc_output/out.json just contains an empty object {}

I should also note that when I do yarn start and check window.__coverage__ in the web console, it shows the expected output. The issue is when I run the component tests, the nyc output doesn't have any coverage info.

twhoff commented 3 years ago

UPDATE: After a bit of digging, I've found that the window.coverage object is missing from the output of the mount() function used in mounting React components. So it looks like the instrumentation needs to be baked into this somehow... Any ideas?

twhoff commented 3 years ago

I've tried using the browserify pre-processor with the use-browserify-istanbul plugin added onBundle but still no effect:

const injectDevServer = require('@cypress/react/plugins/react-scripts')
const browserify = require('@cypress/browserify-preprocessor')

module.exports = (on, config) => {
    injectDevServer(on, config)
    require('@cypress/code-coverage/task')(on, config)
    on(
        'file:preprocessor',
        browserify({
            onBundle(bundle) {
                bundle.plugin('@cypress/code-coverage/use-browserify-istanbul')
            },
        })
    )

    console.log('THE CONFIG: ', config)

    return config
}

Same goes for the webpack pre-processor with istanbul added as a babel-loader rule plugin... will keep trying.

twhoff commented 3 years ago

Okay, I've resolved my issue!

Following the docs, you most likely end up with a plugins/index.js file which looks like this:

// cypress/plugins/index.js

const injectDevServer = require('@cypress/react/plugins/react-scripts')

module.exports = (on, config) => {
  // Webpack Dev Server for rendering React components using the `mount()` function
  injectDevServer(on, config)

  // Cypress code coverage
  require('@cypress/code-coverage/task')(on, config)

  // Supposedly instrument unit tests (component) specs
  on(
    'file:preprocessor',
    require('@cypress/code-coverage/use-browserify-istanbul')
  )
  return config
}

The file:preprocessor step does not work.

Instead, I found that spinning up a custom webpack dev server using the Cypress presets and loading the babel-plugin-istanbul (similar to how @cypress/instrument-cra works) works a treat.

Here is my current plugins/index.js:

const { startDevServer } = require('@cypress/webpack-dev-server')
const findReactScriptsWebpackConfig = require('@cypress/react/plugins/react-scripts/findReactScriptsWebpackConfig')

const customDevServer = (
    on,
    config,
    { webpackConfigPath } = {
        webpackConfigPath: 'react-scripts/config/webpack.config',
    }
) => {
    on('dev-server:start', async (options) => {
        const webpackConfig = findReactScriptsWebpackConfig(config, {
            webpackConfigPath,
        })
        const rules = webpackConfig.module.rules.find(
            (rule) => !!rule.oneOf
        ).oneOf
        const babelRule = rules.find((rule) => /babel-loader/.test(rule.loader))
        babelRule.options.plugins.push(require.resolve('babel-plugin-istanbul'))
        return startDevServer({
            options,
            webpackConfig,
        })
    })

    config.env.reactDevtools = true

    return config
}

module.exports = (on, config) => {
    customDevServer(on, config)
    require('@cypress/code-coverage/task')(on, config)

    return config
}
EricPalmer22 commented 3 years ago

@twhoff thank you so much for this post! Just got coverage working for my component tests using your strat here :)

twhoff commented 3 years ago

@twhoff thank you so much for this post! Just got coverage working for my component tests using your strat here :)

Glad I could help!

tomjeatt commented 3 years ago

Another thank you here @twhoff! Spent all morning trying to get this to work before coming across your config file.

chrisrzhou commented 3 years ago

@twhoff, your resource was the most relevant and best one in an internet scattered with loose solutions on this problem. I took some ideas and tried them out but could not get it working in my repro (small differences in that it's not a CRA and just a component library using Cypress for unit tests). I know you are busy, but if there are any similarities and your deeper experience on the problem, could you point me on what else I may have to do to get my minimal repro working in https://github.com/cypress-io/code-coverage/issues/472?

Thanks a lot!

EH-SrdjanDjakovic commented 3 years ago

@twhoff Thank you for example, it works like a charm.

web-bert commented 3 years ago

Thank you @twhoff I have wasted so much time and this worked perfectly - I should have checked the issues as soon as it didn't work!