diff --git a/.circleci/config.yml b/.circleci/config.yml index c2a1fa2a1..58e7a99cb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -251,6 +251,35 @@ workflows: node ../../scripts/only-covered main.js working_directory: examples/support-files + - cypress/run: + attach-workspace: true + name: example-use-plugins-and-support + requires: + - cypress/install + # there are no jobs to follow this one + # so no need to save the workspace files (saves time) + no-workspace: true + command: npx cypress run --project examples/use-plugins-and-support + # store screenshots and videos + store_artifacts: true + post-steps: + - run: cat examples/use-plugins-and-support/.nyc_output/out.json + # store the created coverage report folder + # you can click on it in the CircleCI UI + # to see live static HTML site + - store_artifacts: + path: examples/use-plugins-and-support/coverage + # make sure the examples captures 100% of code + - run: + command: npx nyc report --check-coverage true --lines 100 + working_directory: examples/use-plugins-and-support + - run: + name: Check code coverage 📈 + command: | + node ../../scripts/check-coverage main.js + node ../../scripts/only-covered main.js + working_directory: examples/use-plugins-and-support + - publish: filters: branches: @@ -266,3 +295,4 @@ workflows: - example-ts-example - example-same-folder - example-support-files + - example-use-plugins-and-support diff --git a/README.md b/README.md index b65e42dc0..5cdc0009c 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,6 @@ > Saves the code coverage collected during Cypress tests -**⚠️ Performance Warning** -This plugin will slow down your tests. There will be more web application JavaScript code to execute due to instrumentation, and there will be code coverage information to merge and save after each test. Track issue [#26](https://github.com/cypress-io/code-coverage/issues/26) for current progress. - ## Install ```shell @@ -23,7 +20,10 @@ Register tasks in your `cypress/plugins/index.js` file ```js module.exports = (on, config) => { - on('task', require('@cypress/code-coverage/task')) + require('@cypress/code-coverage/task')(on, config) + // IMPORTANT to return the config object + // with the any changed environment variables + return config } ``` @@ -109,8 +109,9 @@ Put the following in `cypress/plugins/index.js` file to use `.babelrc` file ```js module.exports = (on, config) => { - on('task', require('@cypress/code-coverage/task')) + require('@cypress/code-coverage/task')(on, config) on('file:preprocessor', require('@cypress/code-coverage/use-babelrc')) + return config } ``` @@ -122,11 +123,12 @@ If you cannot use `.babelrc` for some reason (maybe it is used by other tools?), ```js module.exports = (on, config) => { - on('task', require('@cypress/code-coverage/task')) + require('@cypress/code-coverage/task')(on, config) on( 'file:preprocessor', require('@cypress/code-coverage/use-browserify-istanbul') ) + return config } ``` @@ -349,6 +351,37 @@ npm run dev:no:coverage - [bahmutov/code-coverage-subfolder-example](https://github.com/bahmutov/code-coverage-subfolder-example) shows how to instrument `app` folder using `nyc instrument` as a separate step before running E2E tests - [bahmutov/docker-with-cypress-included-code-coverage-example](https://github.com/bahmutov/docker-with-cypress-included-code-coverage-example) runs tests inside pre-installed Cypress using [cypress/included:x.y.z](https://github.com/cypress-io/cypress-docker-images/tree/master/included) Docker image and reports code coverage. +## Migrations + +### v2 to v3 + +Change the plugins file `cypress/plugins/index.js` + +```js +// BEFORE +module.exports = (on, config) => { + on('task', require('@cypress/code-coverage/task')) +} +// AFTER +module.exports = (on, config) => { + require('@cypress/code-coverage/task')(on, config) + // IMPORTANT to return the config object + // with the any changed environment variables + return config +} +``` + +**Tip:** we include [plugins.js](plugins.js) file you can point at from your code in simple cases. From your `cypress.json` file: + +```json +{ + "pluginsFile": "node_modules/@cypress/code-coverage/plugins", + "supportFile": "node_modules/@cypress/code-coverage/support" +} +``` + +See [examples/use-plugins-and-support](examples/use-plugins-and-support) + ## Debugging This plugin uses [debug](https://github.com/visionmedia/debug) module to output additional logging messages from its [task.js](task.js) file. This can help with debugging errors while saving code coverage or reporting. In order to see these messages, run Cypress from the terminal with environment variable `DEBUG=code-coverage`. Example using Unix syntax to set the variable: diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js index f45185fc4..689350e64 100644 --- a/cypress/plugins/index.js +++ b/cypress/plugins/index.js @@ -1,5 +1,5 @@ module.exports = (on, config) => { - on('task', require('../../task')) + require('../../task')(on, config) // also use .babelrc file when bundling spec files // to get the code coverage from unit tests @@ -9,4 +9,7 @@ module.exports = (on, config) => { // or use browserify and just push babel-plugin-istanbul // directory to the list of babelify plugins // on('file:preprocessor', require('../../use-browserify-istanbul')) + + // IMPORTANT to return the config object with changed environment variable + return config } diff --git a/examples/before-all-visit/cypress/plugins/index.js b/examples/before-all-visit/cypress/plugins/index.js index 172deda45..a7fb752b7 100644 --- a/examples/before-all-visit/cypress/plugins/index.js +++ b/examples/before-all-visit/cypress/plugins/index.js @@ -1,3 +1,6 @@ module.exports = (on, config) => { - on('task', require('../../../../task')) + require('../../../../task')(on, config) + // IMPORTANT to return the config object + // with the any changed environment variables + return config } diff --git a/examples/before-each-visit/cypress/plugins/index.js b/examples/before-each-visit/cypress/plugins/index.js index 172deda45..a7fb752b7 100644 --- a/examples/before-each-visit/cypress/plugins/index.js +++ b/examples/before-each-visit/cypress/plugins/index.js @@ -1,3 +1,6 @@ module.exports = (on, config) => { - on('task', require('../../../../task')) + require('../../../../task')(on, config) + // IMPORTANT to return the config object + // with the any changed environment variables + return config } diff --git a/examples/same-folder/plugins.js b/examples/same-folder/plugins.js index 723dd9e11..2df3f068a 100644 --- a/examples/same-folder/plugins.js +++ b/examples/same-folder/plugins.js @@ -1,4 +1,5 @@ module.exports = (on, config) => { - on('task', require('../../task')) + require('../../task')(on, config) on('file:preprocessor', require('../../use-babelrc')) + return config } diff --git a/examples/support-files/cypress/plugins/index.js b/examples/support-files/cypress/plugins/index.js index 42aa38ea8..b17c48db1 100644 --- a/examples/support-files/cypress/plugins/index.js +++ b/examples/support-files/cypress/plugins/index.js @@ -1,4 +1,5 @@ module.exports = (on, config) => { - on('task', require('../../../../task')) + require('../../../../task')(on, config) on('file:preprocessor', require('../../../../use-babelrc')) + return config } diff --git a/examples/ts-example/cypress/plugins/index.js b/examples/ts-example/cypress/plugins/index.js index 172deda45..fa838f18c 100644 --- a/examples/ts-example/cypress/plugins/index.js +++ b/examples/ts-example/cypress/plugins/index.js @@ -1,3 +1,4 @@ module.exports = (on, config) => { - on('task', require('../../../../task')) + require('../../../../task')(on, config) + return config } diff --git a/examples/use-plugins-and-support/README.md b/examples/use-plugins-and-support/README.md new file mode 100644 index 000000000..0f1251b3b --- /dev/null +++ b/examples/use-plugins-and-support/README.md @@ -0,0 +1,5 @@ +# example: use-plugins-and-support + +Using included plugins and support files + +See [cypress.json](cypress.json) file diff --git a/examples/use-plugins-and-support/cypress.json b/examples/use-plugins-and-support/cypress.json new file mode 100644 index 000000000..3681940e8 --- /dev/null +++ b/examples/use-plugins-and-support/cypress.json @@ -0,0 +1,5 @@ +{ + "pluginsFile": "../../plugins", + "supportFile": "../../support", + "fixturesFolder": false +} diff --git a/examples/use-plugins-and-support/cypress/integration/spec.js b/examples/use-plugins-and-support/cypress/integration/spec.js new file mode 100644 index 000000000..401618d11 --- /dev/null +++ b/examples/use-plugins-and-support/cypress/integration/spec.js @@ -0,0 +1,18 @@ +/// +describe('coverage information', () => { + beforeEach(() => { + cy.visit('index.html') + }) + + it('calls add', () => { + cy.window() + .invoke('add', 2, 3) + .should('equal', 5) + }) + + it('calls sub', () => { + cy.window() + .invoke('sub', 2, 3) + .should('equal', -1) + }) +}) diff --git a/examples/use-plugins-and-support/index.html b/examples/use-plugins-and-support/index.html new file mode 100644 index 000000000..b6691c8af --- /dev/null +++ b/examples/use-plugins-and-support/index.html @@ -0,0 +1,4 @@ + + Page body + + diff --git a/examples/use-plugins-and-support/main-instrumented.js b/examples/use-plugins-and-support/main-instrumented.js new file mode 100644 index 000000000..0550e9bb7 --- /dev/null +++ b/examples/use-plugins-and-support/main-instrumented.js @@ -0,0 +1,146 @@ +function cov_6k5v991cn() { + var path = 'main.js' + var hash = 'd384017ecd51a8d90283ba0dec593332209519de' + var global = new Function('return this')() + var gcv = '__coverage__' + var coverageData = { + path: 'main.js', + statementMap: { + '0': { + start: { + line: 1, + column: 0 + }, + end: { + line: 1, + column: 28 + } + }, + '1': { + start: { + line: 1, + column: 23 + }, + end: { + line: 1, + column: 28 + } + }, + '2': { + start: { + line: 3, + column: 0 + }, + end: { + line: 3, + column: 28 + } + }, + '3': { + start: { + line: 3, + column: 23 + }, + end: { + line: 3, + column: 28 + } + } + }, + fnMap: { + '0': { + name: '(anonymous_0)', + decl: { + start: { + line: 1, + column: 13 + }, + end: { + line: 1, + column: 14 + } + }, + loc: { + start: { + line: 1, + column: 23 + }, + end: { + line: 1, + column: 28 + } + }, + line: 1 + }, + '1': { + name: '(anonymous_1)', + decl: { + start: { + line: 3, + column: 13 + }, + end: { + line: 3, + column: 14 + } + }, + loc: { + start: { + line: 3, + column: 23 + }, + end: { + line: 3, + column: 28 + } + }, + line: 3 + } + }, + branchMap: {}, + s: { + '0': 0, + '1': 0, + '2': 0, + '3': 0 + }, + f: { + '0': 0, + '1': 0 + }, + b: {}, + _coverageSchema: '1a1c01bbd47fc00a2c39e90264f33305004495a9', + hash: 'd384017ecd51a8d90283ba0dec593332209519de' + } + var coverage = global[gcv] || (global[gcv] = {}) + + if (!coverage[path] || coverage[path].hash !== hash) { + coverage[path] = coverageData + } + + var actualCoverage = coverage[path] + + cov_6k5v991cn = function() { + return actualCoverage + } + + return actualCoverage +} + +cov_6k5v991cn() +cov_6k5v991cn().s[0]++ + +window.add = (a, b) => { + cov_6k5v991cn().f[0]++ + cov_6k5v991cn().s[1]++ + return a + b +} + +cov_6k5v991cn().s[2]++ + +window.sub = (a, b) => { + cov_6k5v991cn().f[1]++ + cov_6k5v991cn().s[3]++ + return a - b +} +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm1haW4uanMiXSwibmFtZXMiOlsid2luZG93IiwiYWRkIiwiYSIsImIiLCJzdWIiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBQSxNQUFNLENBQUNDLEdBQVAsR0FBYSxDQUFDQyxDQUFELEVBQUlDLENBQUosS0FBVTtBQUFBO0FBQUE7QUFBQSxTQUFBRCxDQUFDLEdBQUdDLENBQUo7QUFBSyxDQUE1Qjs7OztBQUVBSCxNQUFNLENBQUNJLEdBQVAsR0FBYSxDQUFDRixDQUFELEVBQUlDLENBQUosS0FBVTtBQUFBO0FBQUE7QUFBQSxTQUFBRCxDQUFDLEdBQUdDLENBQUo7QUFBSyxDQUE1QiIsInNvdXJjZXNDb250ZW50IjpbIndpbmRvdy5hZGQgPSAoYSwgYikgPT4gYSArIGJcblxud2luZG93LnN1YiA9IChhLCBiKSA9PiBhIC0gYlxuIl19 diff --git a/examples/use-plugins-and-support/main.js b/examples/use-plugins-and-support/main.js new file mode 100644 index 000000000..5dd69be2f --- /dev/null +++ b/examples/use-plugins-and-support/main.js @@ -0,0 +1,3 @@ +window.add = (a, b) => a + b + +window.sub = (a, b) => a - b diff --git a/examples/use-plugins-and-support/package-lock.json b/examples/use-plugins-and-support/package-lock.json new file mode 100644 index 000000000..6ec504937 --- /dev/null +++ b/examples/use-plugins-and-support/package-lock.json @@ -0,0 +1,4 @@ +{ + "name": "example-before-each-visit", + "lockfileVersion": 1 +} diff --git a/examples/use-plugins-and-support/package.json b/examples/use-plugins-and-support/package.json new file mode 100644 index 000000000..c56dbb885 --- /dev/null +++ b/examples/use-plugins-and-support/package.json @@ -0,0 +1,8 @@ +{ + "name": "use-plugins-and-support", + "description": "Using included plugins and support files", + "devDependencies": {}, + "scripts": { + "cy:open": "../../node_modules/.bin/cypress open" + } +} diff --git a/plugins.js b/plugins.js new file mode 100644 index 000000000..0ba5ce57f --- /dev/null +++ b/plugins.js @@ -0,0 +1,13 @@ +// common Cypress plugin file you can point at to have the +// code coverage tasks registered correctly. From your "cypress.json" file +// { +// "pluginsFile": "@cypress/code-coverage/plugins", +// "supportFile": "@cypress/code-coverage/support" +// } +// +module.exports = (on, config) => { + require('./task')(on, config) + // IMPORTANT to return the config object + // with the any changed environment variables + return config +} diff --git a/support.js b/support.js index 0f71ca4f1..fc2c052de 100644 --- a/support.js +++ b/support.js @@ -75,13 +75,7 @@ const filterSpecsFromCoverage = totalCoverage => { return coverage } -// to disable code coverage commands and save time -// pass environment variable coverage=false -// cypress run --env coverage=false -// see https://on.cypress.io/environment-variables -if (Cypress.env('coverage') === false) { - console.log('Skipping code coverage hooks') -} else { +const registerHooks = () => { let windowCoverageObjects const hasE2ECoverage = () => Boolean(windowCoverageObjects.length) @@ -206,3 +200,22 @@ if (Cypress.env('coverage') === false) { }) }) } + +// to disable code coverage commands and save time +// pass environment variable coverage=false +// cypress run --env coverage=false +// see https://on.cypress.io/environment-variables +if (Cypress.env('coverage') === false) { + console.log('Skipping code coverage hooks') +} else if (Cypress.env('codeCoverageTasksRegistered') !== true) { + // register a hook just to log a message + before(() => { + logMessage(` + ⚠️ Code coverage tasks were not registered by the plugins file. + See [support issue](https://github.com/cypress-io/code-coverage/issues/179) + for possible workarounds. + `) + }) +} else { + registerHooks() +} diff --git a/task.js b/task.js index b597c909a..206548c36 100644 --- a/task.js +++ b/task.js @@ -37,7 +37,7 @@ function saveCoverage(coverage) { writeFileSync(nycFilename, JSON.stringify(coverage, null, 2)) } -module.exports = { +const tasks = { /** * Clears accumulated code coverage information. * @@ -133,3 +133,30 @@ module.exports = { return nyc.report().then(returnReportFolder) } } + +/** + * Registers code coverage collection and reporting tasks. + * Sets an environment variable to tell the browser code that it can + * send the coverage. + * @example + ``` + // your plugins file + module.exports = (on, config) => { + require('cypress/code-coverage/task')(on, config) + // IMPORTANT to return the config object + // with the any changed environment variables + return config + } + ``` +*/ +function registerCodeCoverageTasks(on, config) { + on('task', tasks) + + // set a variable to let the hooks running in the browser + // know that they can send coverage commands + config.env.codeCoverageTasksRegistered = true + + return config +} + +module.exports = registerCodeCoverageTasks