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
+
+
+
+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
+}