diff --git a/examples/multiple-backends/README.md b/examples/multiple-backends/README.md new file mode 100644 index 00000000..b10305d7 --- /dev/null +++ b/examples/multiple-backends/README.md @@ -0,0 +1,22 @@ +# example: multiple-backends + +> Combined code coverage from multiple backend processes, and e2e and unit tests + +This example runs instrumented server code on two processes, that serves instrumented frontend code, and instruments the unit tests on the fly. The final report combines all 4 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/multiple-backends.png) + +In the produced report, you should see + +- `server1/server.js` coverage for backend +- `server2/server.js` coverage for backend +- `main.js` coverage from end-to-end tests +- `string-utils.js` coverage from unit tests diff --git a/examples/multiple-backends/cypress.json b/examples/multiple-backends/cypress.json new file mode 100644 index 00000000..e99370d8 --- /dev/null +++ b/examples/multiple-backends/cypress.json @@ -0,0 +1,9 @@ +{ + "fixturesFolder": false, + "baseUrl": "http://localhost:3003", + "env": { + "codeCoverage": { + "url": "http://localhost:3003/__coverage__,http://localhost:3004/__coverage__" + } + } +} diff --git a/examples/multiple-backends/cypress/integration/spec.js b/examples/multiple-backends/cypress/integration/spec.js new file mode 100644 index 00000000..900a7ff2 --- /dev/null +++ b/examples/multiple-backends/cypress/integration/spec.js @@ -0,0 +1,26 @@ +/// + +// 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('**other backend request**') + cy.request('http://localhost:3004/goodbye') + + cy.log('**unit test**') + expect(reverse('Hello')).to.equal('olleH') +}) diff --git a/examples/multiple-backends/cypress/plugins/index.js b/examples/multiple-backends/cypress/plugins/index.js new file mode 100644 index 00000000..e459c811 --- /dev/null +++ b/examples/multiple-backends/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/multiple-backends/cypress/support/index.js b/examples/multiple-backends/cypress/support/index.js new file mode 100644 index 00000000..dd60efa2 --- /dev/null +++ b/examples/multiple-backends/cypress/support/index.js @@ -0,0 +1 @@ +import '../../../../support' diff --git a/examples/multiple-backends/images/multiple-backends.png b/examples/multiple-backends/images/multiple-backends.png new file mode 100644 index 00000000..020b8237 Binary files /dev/null and b/examples/multiple-backends/images/multiple-backends.png differ diff --git a/examples/multiple-backends/main.js b/examples/multiple-backends/main.js new file mode 100644 index 00000000..5dd69be2 --- /dev/null +++ b/examples/multiple-backends/main.js @@ -0,0 +1,3 @@ +window.add = (a, b) => a + b + +window.sub = (a, b) => a - b diff --git a/examples/multiple-backends/package.json b/examples/multiple-backends/package.json new file mode 100644 index 00000000..779b0f12 --- /dev/null +++ b/examples/multiple-backends/package.json @@ -0,0 +1,15 @@ +{ + "name": "example-multiple-backends", + "description": "Combined code coverage from multiple backend code bases, and e2e and unit tests", + "devDependencies": { + "npm-run-all": "^4.1.5" + }, + "scripts": { + "start": "npm-run-all -p start:server1 start:server2", + "start:server1": "../../node_modules/.bin/nyc --silent node server1/server", + "start:server2": "../../node_modules/.bin/nyc --silent node server2/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/multiple-backends/server1/index.html b/examples/multiple-backends/server1/index.html new file mode 100644 index 00000000..b6691c8a --- /dev/null +++ b/examples/multiple-backends/server1/index.html @@ -0,0 +1,4 @@ + + Page body + + diff --git a/examples/multiple-backends/server1/main-instrumented.js b/examples/multiple-backends/server1/main-instrumented.js new file mode 100644 index 00000000..0550e9bb --- /dev/null +++ b/examples/multiple-backends/server1/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/multiple-backends/server1/server.js b/examples/multiple-backends/server1/server.js new file mode 100644 index 00000000..7e4786e7 --- /dev/null +++ b/examples/multiple-backends/server1/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/multiple-backends/server2/main-instrumented.js b/examples/multiple-backends/server2/main-instrumented.js new file mode 100644 index 00000000..0550e9bb --- /dev/null +++ b/examples/multiple-backends/server2/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/multiple-backends/server2/server.js b/examples/multiple-backends/server2/server.js new file mode 100644 index 00000000..a94fb354 --- /dev/null +++ b/examples/multiple-backends/server2/server.js @@ -0,0 +1,21 @@ +const express = require('express') +const app = express() +const port = 3004 + +// 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('/goodbye', (req, res) => { + console.log('sending goodby world') + res.send('Boodbye World!') +}) + +app.listen(port, () => console.log(`Example service listening on port ${port}!`)) diff --git a/examples/multiple-backends/string-utils.js b/examples/multiple-backends/string-utils.js new file mode 100644 index 00000000..0d14f807 --- /dev/null +++ b/examples/multiple-backends/string-utils.js @@ -0,0 +1,10 @@ +// reverses a string +const reverse = s => { + return s + .split('') + .reverse() + .join('') +} +module.exports = { + reverse +} diff --git a/support.js b/support.js index c6458da4..4cd90f96 100644 --- a/support.js +++ b/support.js @@ -160,27 +160,34 @@ const registerHooks = () => { // we can only request server-side code coverage // if we are running end-to-end tests, // otherwise where do we send the request? - const url = Cypress._.get( + const urls = Cypress._.get( Cypress.env('codeCoverage'), 'url', '/__coverage__' ) - cy.request({ - url, - log: false, - failOnStatusCode: false - }) - .then(r => { - return Cypress._.get(r, 'body.coverage', null) - }) - .then(coverage => { - if (!coverage) { - // we did not get code coverage - this is the - // original failed request - return - } - sendCoverage(coverage, 'backend') + .split(',') + .map(u => u.trim()) + urls.map(url => { + logMessage(`Requesting coverage for **${url}**`) + cy.request({ + url, + log: false, + failOnStatusCode: false }) + .then(r => { + return Cypress._.get(r, 'body.coverage', null) + }) + .then(coverage => { + if (!coverage) { + // we did not get code coverage - this is the + // original failed request + logMessage(`No coverage for: **${url}**`) + return + } + sendCoverage(coverage, `backend: ${url}`) + }) + }) + logMessage('Finished collecting coverage') } })