diff --git a/.circleci/config.yml b/.circleci/config.yml index c2a1fa2a..58e7a99c 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 b65e42dc..dbf0c532 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,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 +112,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 +126,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 +354,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 f45185fc..689350e6 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 172deda4..a7fb752b 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 172deda4..a7fb752b 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 723dd9e1..2df3f068 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 42aa38ea..b17c48db 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 172deda4..fa838f18 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 00000000..0f1251b3 --- /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 00000000..3681940e --- /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 00000000..401618d1 --- /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 00000000..b6691c8a --- /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 00000000..0550e9bb --- /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 00000000..5dd69be2 --- /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 00000000..6ec50493 --- /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 00000000..c56dbb88 --- /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 00000000..0ba5ce57 --- /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 0f71ca4f..fc2c052d 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 b597c909..206548c3 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