diff --git a/.circleci/config.yml b/.circleci/config.yml index 733dc283..ee5db147 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -104,6 +104,38 @@ workflows: node ../../scripts/only-covered server.js working_directory: examples/backend + - cypress/run: + name: fullstack coverage + requires: + - cypress/install + # grab the workspace created by cypress/install job + attach-workspace: true + working_directory: examples/fullstack + start: npm start + wait-on: 'http://localhost:3003' + command: '../../node_modules/.bin/cypress run' + + # there are no jobs to follow this one + # so no need to save the workspace files (saves time) + no-workspace: true + post-steps: + # 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/backend/coverage + - run: + command: npm run report + working_directory: examples/fullstack + - run: + name: Check code coverage 📈 + command: | + node ../../scripts/check-coverage server.js + node ../../scripts/check-coverage main.js + node ../../scripts/check-coverage string-utils.js + node ../../scripts/only-covered server.js main.js string-utils.js + working_directory: examples/fullstack + - cypress/run: attach-workspace: true name: example-before-each-visit @@ -322,6 +354,7 @@ workflows: - unit - frontend coverage - backend coverage + - fullstack coverage - example-before-each-visit - example-before-all-visit - example-ts-example diff --git a/README.md b/README.md index 4d1eb56d..c7102a16 100644 --- a/README.md +++ b/README.md @@ -332,8 +332,13 @@ npm run dev:no:coverage ### Internal examples -- [examples/before-each-visit](examples/before-each-visit) checks if code coverage correctly keeps track of code when doing `cy.visit` before each test +Full examples we use for testing in this repository: + +- [examples/backend](examples/backend) only instruments the backend Node server and saves the coverage report +- [examples/fullstack](examples/fullstack) instruments and merges backend, e2e and unit test coverage into a single report - [examples/before-all-visit](examples/before-all-visit) checks if code coverage works when `cy.visit` is made once in the `before` hook +- [examples/before-each-visit](examples/before-each-visit) checks if code coverage correctly keeps track of code when doing `cy.visit` before each test +- [examples/one-spec.js](examples/one-spec.js) confirms that coverage is collected and filtered correctly if the user only executes a single Cypress test - [examples/ts-example](examples/ts-example) uses Babel + Parcel to instrument and serve TypeScript file ### External examples diff --git a/examples/fullstack/README.md b/examples/fullstack/README.md new file mode 100644 index 00000000..b2e554de --- /dev/null +++ b/examples/fullstack/README.md @@ -0,0 +1,21 @@ +# example: fullstack + +> Combined code coverage from the backend code, and e2e and unit tests + +This example runs instrumented server code, that serves instrumented frontend code, and instruments the unit tests on the fly. The final report combines all 3 sources of information. + +To run + +```sh +$ npm run dev +``` + +You should see messages from the plugin when it saves each coverage object + +![Coverage messages](images/fullstack.png) + +In the produced report, you should see + +- `server/server.js` coverage for backend +- `main.js` coverage from end-to-end tests +- `string-utils.js` coverage from unit tests diff --git a/examples/fullstack/cypress.json b/examples/fullstack/cypress.json new file mode 100644 index 00000000..3fdedda2 --- /dev/null +++ b/examples/fullstack/cypress.json @@ -0,0 +1,9 @@ +{ + "fixturesFolder": false, + "baseUrl": "http://localhost:3003", + "env": { + "codeCoverage": { + "url": "http://localhost:3003/__coverage__" + } + } +} diff --git a/examples/fullstack/cypress/integration/spec.js b/examples/fullstack/cypress/integration/spec.js new file mode 100644 index 00000000..7b4d9d7d --- /dev/null +++ b/examples/fullstack/cypress/integration/spec.js @@ -0,0 +1,23 @@ +/// + +// load extra files to instrument on the fly +const { reverse } = require('../../string-utils') + +it('uses frontend code and calls backend', () => { + cy.visit('/') + cy.contains('Page body').should('be.visible') + + cy.window() + .invoke('add', 2, 3) + .should('equal', 5) + + cy.window() + .invoke('sub', 2, 3) + .should('equal', -1) + + cy.log('**backend request**') + cy.request('/hello') + + cy.log('**unit test**') + expect(reverse('Hello')).to.equal('olleH') +}) diff --git a/examples/fullstack/cypress/plugins/index.js b/examples/fullstack/cypress/plugins/index.js new file mode 100644 index 00000000..e459c811 --- /dev/null +++ b/examples/fullstack/cypress/plugins/index.js @@ -0,0 +1,6 @@ +module.exports = (on, config) => { + require('../../../../task')(on, config) + // instrument loaded spec files (and the application code loaded from them) + on('file:preprocessor', require('../../../../use-browserify-istanbul')) + return config +} diff --git a/examples/fullstack/cypress/support/index.js b/examples/fullstack/cypress/support/index.js new file mode 100644 index 00000000..dd60efa2 --- /dev/null +++ b/examples/fullstack/cypress/support/index.js @@ -0,0 +1 @@ +import '../../../../support' diff --git a/examples/fullstack/images/fullstack.png b/examples/fullstack/images/fullstack.png new file mode 100644 index 00000000..7a59b082 Binary files /dev/null and b/examples/fullstack/images/fullstack.png differ diff --git a/examples/fullstack/main.js b/examples/fullstack/main.js new file mode 100644 index 00000000..5dd69be2 --- /dev/null +++ b/examples/fullstack/main.js @@ -0,0 +1,3 @@ +window.add = (a, b) => a + b + +window.sub = (a, b) => a - b diff --git a/examples/fullstack/package.json b/examples/fullstack/package.json new file mode 100644 index 00000000..b44811b0 --- /dev/null +++ b/examples/fullstack/package.json @@ -0,0 +1,11 @@ +{ + "name": "example-fullstack", + "description": "Combined code coverage from the backend code, and e2e and unit tests", + "devDependencies": {}, + "scripts": { + "start": "../../node_modules/.bin/nyc --silent node server/server", + "cy:open": "../../node_modules/.bin/cypress open", + "dev": "../../node_modules/.bin/start-test 3003 cy:open", + "report": "../../node_modules/.bin/nyc report --reporter text" + } +} diff --git a/examples/fullstack/server/index.html b/examples/fullstack/server/index.html new file mode 100644 index 00000000..b6691c8a --- /dev/null +++ b/examples/fullstack/server/index.html @@ -0,0 +1,4 @@ + + Page body + + diff --git a/examples/fullstack/server/main-instrumented.js b/examples/fullstack/server/main-instrumented.js new file mode 100644 index 00000000..0550e9bb --- /dev/null +++ b/examples/fullstack/server/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/fullstack/server/server.js b/examples/fullstack/server/server.js new file mode 100644 index 00000000..7e4786e7 --- /dev/null +++ b/examples/fullstack/server/server.js @@ -0,0 +1,21 @@ +const express = require('express') +const app = express() +const port = 3003 + +// if there is code coverage information +// then expose an endpoint that returns it +/* istanbul ignore next */ +if (global.__coverage__) { + console.log('have code coverage, will add middleware for express') + console.log(`to fetch: GET :${port}/__coverage__`) + require('../../../middleware/express')(app) +} + +app.use(express.static(__dirname)) + +app.get('/hello', (req, res) => { + console.log('sending hello world') + res.send('Hello World!') +}) + +app.listen(port, () => console.log(`Example app listening on port ${port}!`)) diff --git a/examples/fullstack/string-utils.js b/examples/fullstack/string-utils.js new file mode 100644 index 00000000..0d14f807 --- /dev/null +++ b/examples/fullstack/string-utils.js @@ -0,0 +1,10 @@ +// reverses a string +const reverse = s => { + return s + .split('') + .reverse() + .join('') +} +module.exports = { + reverse +}