diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..4e9a7cbc --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "workbench.colorTheme": "Tomorrow Night Blue" +} \ No newline at end of file diff --git a/README.md b/README.md index a5228bca..f9607218 100644 --- a/README.md +++ b/README.md @@ -63,10 +63,65 @@ module.exports = (on, config) => { Now the code coverage from spec files will be combined with end-to-end coverage. +## Instrument backend code + +You can also instrument your server-side code and produce combined coverage report that covers both the backend and frontend code. + +1. Run the server code with instrumentation. The simplest way is to use [nyc](https://github.com/istanbuljs/nyc). If normally you run `node src/server` then to run instrumented version you can do `nyc --silent node src/server`. +2. Add an endpoint that returns collected coverage. If you are using Express, you can simply do + +```js +const express = require('express') +const app = express() +require('@cypress/code-coverage/middleware')(app) +``` + +**Tip:** you can register the endpoint only if there is global code coverage object, and you can exclude the middleware code from the coverage numbers + +```js +// https://github.com/gotwarlost/istanbul/blob/master/ignoring-code-for-coverage.md +/* istanbul ignore next */ +if (global.__coverage__) { + require('@cypress/code-coverage/middleware')(app) +} +``` + +If you use Hapi server, define the endpoint yourself and return the object + +```js +// https://github.com/gotwarlost/istanbul/blob/master/ignoring-code-for-coverage.md +/* istanbul ignore next */ +if (global.__coverage__) { + // https://hapijs.com/tutorials/routing?lang=en_US + server.route({ + method: 'GET', + path: '/__coverage__', + handler () { + return { coverage: global.__coverage__ } + } + }) +} +``` + +3. Save the API coverage endpoint in `cypress.json` file to let the plugin know where to call to receive the code coverage data from the server. Place it in `env.codeCoverage` object: + +```json +{ + "env": { + "codeCoverage": { + "url": "http://localhost:3000/__coverage__" + } + } +} +``` + +That should be enough - the code coverage from the server will be requested at the end of the test run and merged with the client-side code coverage, producing a combined report + ## Examples - [Cypress code coverage guide](http://on.cypress.io/code-coverage) - [cypress-example-todomvc-redux](https://github.com/cypress-io/cypress-example-todomvc-redux) +- Full frontend + backend code coverage in [bahmutov/realworld](https://github.com/bahmutov/realworld) repo - Read ["Code Coverage by Parcel Bundler"](https://glebbahmutov.com/blog/code-coverage-by-parcel/) blog post - Read ["Combined End-to-end and Unit Test Coverage"](https://glebbahmutov.com/blog/combined-end-to-end-and-unit-test-coverage/) diff --git a/middleware.js b/middleware.js new file mode 100644 index 00000000..560a529b --- /dev/null +++ b/middleware.js @@ -0,0 +1,9 @@ +module.exports = app => { + // expose "GET __coverage__" endpoint that just returns + // global coverage information (if the application has been instrumented) + app.get('/__coverage__', (req, res) => { + res.json({ + coverage: global.__coverage__ || null + }) + }) +} diff --git a/support.js b/support.js index 2acaa6d3..e827ca0e 100644 --- a/support.js +++ b/support.js @@ -21,6 +21,25 @@ afterEach(() => { }) after(() => { + // there might be server-side code coverage information + // we should grab it once after all tests finish + const url = Cypress._.get(Cypress.env('codeCoverage'), 'url', '/__coverage__') + cy.request({ + url, + log: false, + failOnStatusCode: false + }) + .then(r => Cypress._.get(r, 'body.coverage', null), { log: false }) + .then(coverage => { + if (!coverage) { + // we did not get code coverage - this is the + // original failed request + return + } + cy.task('combineCoverage', coverage) + }) + + // collect and merge frontend coverage const specFolder = Cypress.config('integrationFolder') const supportFolder = Cypress.config('supportFolder')