├── .babelrc ├── cypress ├── support │ └── e2e.js ├── unit.js ├── about.js ├── about.html ├── fixtures │ ├── example.json │ └── coverage.json ├── index.html ├── app.js ├── e2e │ ├── minimatch.cy.js │ ├── calculator.cy.js │ ├── calculator-po.js │ ├── spec.cy.js │ ├── combine.cy.js │ ├── merge.cy.js │ └── filtering.cy.js ├── plugins │ └── index.js ├── calculator │ ├── utils.js │ ├── index.html │ ├── styles.css │ └── app.js └── README.md ├── examples ├── all-files │ ├── README.md │ ├── cypress │ │ ├── support │ │ │ ├── e2e.js │ │ │ └── commands.js │ │ ├── plugins │ │ │ └── index.js │ │ └── integration │ │ │ └── spec.js │ ├── .babelrc │ ├── main.js │ ├── second.js │ ├── not-covered.js │ ├── index.html │ ├── cypress.config.js │ └── package.json ├── no-files │ ├── .babelrc │ ├── cypress │ │ ├── support │ │ │ ├── e2e.js │ │ │ └── commands.js │ │ ├── integration │ │ │ └── spec.js │ │ └── plugins │ │ │ └── index.js │ ├── main.js │ ├── README.md │ ├── index.html │ ├── cypress.config.js │ └── package.json ├── one-spec │ ├── .babelrc │ ├── cypress │ │ ├── support │ │ │ └── e2e.js │ │ ├── integration │ │ │ ├── spec-b.js │ │ │ └── spec-a.js │ │ └── plugins │ │ │ └── index.js │ ├── README.md │ ├── main.js │ ├── index.html │ ├── package.json │ ├── cypress.config.js │ └── main-instrumented.js ├── backend │ ├── cypress │ │ ├── support │ │ │ └── e2e.js │ │ ├── integration │ │ │ └── spec.js │ │ └── plugins │ │ │ └── index.js │ ├── server │ │ ├── index.html │ │ └── server.js │ ├── README.md │ ├── cypress.config.js │ └── package.json ├── exclude-files │ ├── .babelrc │ ├── cypress │ │ ├── support │ │ │ ├── e2e.js │ │ │ └── commands.js │ │ ├── plugins │ │ │ └── index.js │ │ └── integration │ │ │ └── spec.js │ ├── main.js │ ├── README.md │ ├── second.js │ ├── index.html │ ├── cypress.config.js │ └── package.json ├── filter-files │ ├── .babelrc │ ├── cypress │ │ ├── support │ │ │ ├── index.js │ │ │ └── commands.js │ │ ├── integration │ │ │ └── spec.js │ │ └── plugins │ │ │ └── index.js │ ├── main.js │ ├── README.md │ ├── cypress.json │ ├── index.html │ └── package.json ├── multiple-files │ ├── cypress │ │ ├── support │ │ │ ├── e2e.js │ │ │ └── commands.js │ │ ├── integration │ │ │ ├── first.js │ │ │ └── second.js │ │ └── plugins │ │ │ └── index.js │ ├── .babelrc │ ├── src │ │ ├── first.js │ │ ├── second.js │ │ └── third.js │ ├── second.html │ ├── index.html │ ├── README.md │ ├── cypress.config.js │ └── package.json ├── placeholders │ ├── .babelrc │ ├── cypress │ │ ├── support │ │ │ └── index.js │ │ ├── plugins │ │ │ └── index.js │ │ └── integration │ │ │ ├── spec-a.js │ │ │ └── spec-b.js │ ├── src │ │ ├── a.js │ │ └── b.js │ ├── cypress.config.js │ └── package.json ├── same-folder │ ├── .babelrc │ ├── support │ │ └── support.js │ ├── main.js │ ├── README.md │ ├── index.html │ ├── unit-utils.js │ ├── plugins.js │ ├── cypress │ │ └── fixtures │ │ │ └── example.json │ ├── package.json │ ├── cypress.config.js │ ├── spec.js │ └── main-instrumented.js ├── support-files │ ├── .babelrc │ ├── cypress │ │ ├── support │ │ │ ├── e2e.js │ │ │ └── commands.js │ │ ├── integration │ │ │ └── spec.js │ │ └── plugins │ │ │ └── index.js │ ├── README.md │ ├── main.js │ ├── index.html │ ├── cypress.config.js │ └── package.json ├── ts-example │ ├── .babelrc │ ├── cypress │ │ ├── support │ │ │ └── e2e.js │ │ ├── plugins │ │ │ └── index.js │ │ └── integration │ │ │ └── spec.js │ ├── calc.ts │ ├── index.html │ ├── main.ts │ ├── README.md │ ├── cypress.config.js │ └── package.json ├── unit-tests-js │ ├── .babelrc │ ├── cypress │ │ ├── support │ │ │ └── e2e.js │ │ ├── plugins │ │ │ └── index.js │ │ └── integration │ │ │ └── spec.js │ ├── README.md │ ├── src │ │ └── utils │ │ │ └── misc.js │ ├── package.json │ └── cypress.config.js ├── use-webpack │ ├── .babelrc │ ├── cypress.json │ ├── src │ │ ├── calc.js │ │ └── index.js │ ├── cypress │ │ ├── integration │ │ │ └── spec.js │ │ └── plugins │ │ │ └── index.js │ ├── README.md │ ├── package.json │ └── webpack.config.js ├── fullstack │ ├── cypress │ │ ├── support │ │ │ └── e2e.js │ │ ├── plugins │ │ │ └── index.js │ │ └── integration │ │ │ └── spec.js │ ├── main.js │ ├── server │ │ ├── index.html │ │ ├── server.js │ │ └── main-instrumented.js │ ├── images │ │ └── fullstack.png │ ├── string-utils.js │ ├── README.md │ ├── cypress.config.js │ └── package.json ├── merge-coverage │ ├── .babelrc │ ├── cypress │ │ ├── support │ │ │ └── e2e.js │ │ ├── e2e │ │ │ ├── sub.cy.js │ │ │ └── add.cy.js │ │ └── plugins │ │ │ └── index.js │ ├── README.md │ ├── src │ │ └── math.js │ ├── cypress.config.js │ └── package.json ├── unit-tests-ts │ ├── cypress │ │ ├── support │ │ │ └── e2e.js │ │ ├── integration │ │ │ └── spec.ts │ │ └── plugins │ │ │ └── index.js │ ├── .babelrc │ ├── tsconfig.json │ ├── src │ │ └── utils │ │ │ └── misc.ts │ ├── README.md │ ├── package.json │ └── cypress.config.js ├── before-all-visit │ ├── cypress │ │ ├── support │ │ │ └── e2e.js │ │ ├── fixtures │ │ │ └── example.json │ │ ├── plugins │ │ │ └── index.js │ │ └── integration │ │ │ └── spec.js │ ├── main.js │ ├── index.html │ ├── package-lock.json │ ├── README.md │ ├── cypress.config.js │ ├── package.json │ └── main-instrumented.js ├── before-each-visit │ ├── cypress │ │ ├── support │ │ │ └── e2e.js │ │ ├── fixtures │ │ │ └── example.json │ │ ├── plugins │ │ │ └── index.js │ │ └── integration │ │ │ └── spec.js │ ├── main.js │ ├── index.html │ ├── package-lock.json │ ├── README.md │ ├── cypress.config.js │ ├── package.json │ └── main-instrumented.js ├── component │ ├── src │ │ ├── calc.js │ │ ├── Hello.cy.jsx │ │ └── Hello.jsx │ ├── cc.json │ ├── cypress │ │ └── support │ │ │ ├── component.js │ │ │ └── component-index.html │ ├── package.json │ └── cypress.config.js ├── docker-paths │ ├── app │ │ ├── main.js │ │ ├── second.js │ │ └── index.html │ ├── images │ │ ├── file.png │ │ └── files.png │ ├── cypress.json │ ├── cypress │ │ └── integration │ │ │ └── spec.js │ ├── package.json │ └── README.md └── use-plugins-and-support │ ├── main.js │ ├── index.html │ ├── package-lock.json │ ├── README.md │ ├── cypress.config.js │ ├── cypress │ └── integration │ │ └── spec.js │ ├── package.json │ └── main-instrumented.js ├── images ├── gui.png ├── summary.png ├── warning.png ├── coverage.jpg ├── expect-backend.png ├── instrumented-code.png └── window-coverage-object.png ├── .nycrc.json ├── .prettierrc.json ├── plugins.js ├── cypress-backend.json ├── .gitignore ├── renovate.json ├── middleware ├── express.js ├── hapi.js └── nextjs.js ├── use-browserify-istanbul.js ├── use-babelrc.js ├── cypress.config.ts ├── .github └── workflows │ ├── badges.yml │ └── ci.yml ├── LICENSE.md ├── plugin.js ├── bin └── cc-merge.js ├── common-utils.js ├── src └── utils.js ├── package.json ├── support-utils.js ├── task.js ├── support.js └── task-utils.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | import '../../support' 2 | -------------------------------------------------------------------------------- /examples/all-files/README.md: -------------------------------------------------------------------------------- 1 | # example: all files 2 | -------------------------------------------------------------------------------- /cypress/unit.js: -------------------------------------------------------------------------------- 1 | export const add = (a, b) => a + b 2 | -------------------------------------------------------------------------------- /examples/all-files/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | require('./commands') 2 | -------------------------------------------------------------------------------- /examples/no-files/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/no-files/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | require('./commands') 2 | -------------------------------------------------------------------------------- /examples/one-spec/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/all-files/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/backend/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | import '../../../../support' 2 | -------------------------------------------------------------------------------- /examples/exclude-files/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/exclude-files/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | require('./commands') 2 | -------------------------------------------------------------------------------- /examples/filter-files/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/filter-files/cypress/support/index.js: -------------------------------------------------------------------------------- 1 | require('./commands') 2 | -------------------------------------------------------------------------------- /examples/multiple-files/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | require('./commands') 2 | -------------------------------------------------------------------------------- /examples/one-spec/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | import '../../../../support' 2 | -------------------------------------------------------------------------------- /examples/placeholders/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/same-folder/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/support-files/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/support-files/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | require('./commands') 2 | -------------------------------------------------------------------------------- /examples/ts-example/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/unit-tests-js/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/use-webpack/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/backend/server/index.html: -------------------------------------------------------------------------------- 1 | 2 | test backend 3 | 4 | -------------------------------------------------------------------------------- /examples/fullstack/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | import '../../../../support' 2 | -------------------------------------------------------------------------------- /examples/merge-coverage/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/multiple-files/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/ts-example/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | import '../../../../support' 2 | -------------------------------------------------------------------------------- /examples/unit-tests-js/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | import '../../../../support' 2 | -------------------------------------------------------------------------------- /examples/unit-tests-ts/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | import '../../../../support' 2 | -------------------------------------------------------------------------------- /examples/before-all-visit/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | import '../../../../support' 2 | -------------------------------------------------------------------------------- /examples/before-each-visit/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | import '../../../../support' 2 | -------------------------------------------------------------------------------- /examples/merge-coverage/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | import '../../../../support' 2 | -------------------------------------------------------------------------------- /examples/multiple-files/src/first.js: -------------------------------------------------------------------------------- 1 | console.log('this is the first script') 2 | -------------------------------------------------------------------------------- /examples/multiple-files/src/second.js: -------------------------------------------------------------------------------- 1 | console.log('this is the second script') 2 | -------------------------------------------------------------------------------- /examples/multiple-files/src/third.js: -------------------------------------------------------------------------------- 1 | console.log('this is the third script') 2 | -------------------------------------------------------------------------------- /examples/placeholders/cypress/support/index.js: -------------------------------------------------------------------------------- 1 | import '../../../../support' 2 | -------------------------------------------------------------------------------- /examples/one-spec/README.md: -------------------------------------------------------------------------------- 1 | # example: one-spec 2 | 3 | Only running a single spec 4 | -------------------------------------------------------------------------------- /examples/same-folder/support/support.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import '../../../support' 3 | -------------------------------------------------------------------------------- /examples/component/src/calc.js: -------------------------------------------------------------------------------- 1 | export const add = (a, b) => { 2 | return a + b 3 | } 4 | -------------------------------------------------------------------------------- /examples/all-files/main.js: -------------------------------------------------------------------------------- 1 | window.add = (a, b) => a + b 2 | 3 | window.sub = (a, b) => a - b 4 | -------------------------------------------------------------------------------- /examples/backend/README.md: -------------------------------------------------------------------------------- 1 | # example: backend 2 | 3 | > Getting code coverage from backend 4 | -------------------------------------------------------------------------------- /examples/fullstack/main.js: -------------------------------------------------------------------------------- 1 | window.add = (a, b) => a + b 2 | 3 | window.sub = (a, b) => a - b 4 | -------------------------------------------------------------------------------- /examples/no-files/main.js: -------------------------------------------------------------------------------- 1 | window.add = (a, b) => a + b 2 | 3 | window.sub = (a, b) => a - b 4 | -------------------------------------------------------------------------------- /examples/one-spec/main.js: -------------------------------------------------------------------------------- 1 | window.add = (a, b) => a + b 2 | 3 | window.sub = (a, b) => a - b 4 | -------------------------------------------------------------------------------- /examples/support-files/README.md: -------------------------------------------------------------------------------- 1 | # example: support-files 2 | 3 | Filtering out support files 4 | -------------------------------------------------------------------------------- /images/gui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bahmutov/cypress-code-coverage/HEAD/images/gui.png -------------------------------------------------------------------------------- /examples/exclude-files/main.js: -------------------------------------------------------------------------------- 1 | window.add = (a, b) => a + b 2 | 3 | window.sub = (a, b) => a - b 4 | -------------------------------------------------------------------------------- /examples/filter-files/main.js: -------------------------------------------------------------------------------- 1 | window.add = (a, b) => a + b 2 | 3 | window.sub = (a, b) => a - b 4 | -------------------------------------------------------------------------------- /examples/no-files/README.md: -------------------------------------------------------------------------------- 1 | # example: no-files 2 | 3 | All tests are skipped, no code coverage 4 | -------------------------------------------------------------------------------- /examples/same-folder/main.js: -------------------------------------------------------------------------------- 1 | window.add = (a, b) => a + b 2 | 3 | window.sub = (a, b) => a - b 4 | -------------------------------------------------------------------------------- /examples/support-files/main.js: -------------------------------------------------------------------------------- 1 | window.add = (a, b) => a + b 2 | 3 | window.sub = (a, b) => a - b 4 | -------------------------------------------------------------------------------- /examples/ts-example/calc.ts: -------------------------------------------------------------------------------- 1 | export const add = (a: number, b: number) => { 2 | return a + b 3 | } 4 | -------------------------------------------------------------------------------- /images/summary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bahmutov/cypress-code-coverage/HEAD/images/summary.png -------------------------------------------------------------------------------- /images/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bahmutov/cypress-code-coverage/HEAD/images/warning.png -------------------------------------------------------------------------------- /examples/before-all-visit/main.js: -------------------------------------------------------------------------------- 1 | window.add = (a, b) => a + b 2 | 3 | window.sub = (a, b) => a - b 4 | -------------------------------------------------------------------------------- /examples/before-each-visit/main.js: -------------------------------------------------------------------------------- 1 | window.add = (a, b) => a + b 2 | 3 | window.sub = (a, b) => a - b 4 | -------------------------------------------------------------------------------- /examples/component/cc.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "code-coverage", 3 | "main": "../../../../support.js" 4 | } 5 | -------------------------------------------------------------------------------- /examples/docker-paths/app/main.js: -------------------------------------------------------------------------------- 1 | window.add = (a, b) => a + b 2 | 3 | window.sub = (a, b) => a - b 4 | -------------------------------------------------------------------------------- /examples/ts-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | Page body 3 | 4 | 5 | -------------------------------------------------------------------------------- /images/coverage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bahmutov/cypress-code-coverage/HEAD/images/coverage.jpg -------------------------------------------------------------------------------- /examples/same-folder/README.md: -------------------------------------------------------------------------------- 1 | # example: same-folder 2 | 3 | Check if test files are correctly filtered out 4 | -------------------------------------------------------------------------------- /examples/use-plugins-and-support/main.js: -------------------------------------------------------------------------------- 1 | window.add = (a, b) => a + b 2 | 3 | window.sub = (a, b) => a - b 4 | -------------------------------------------------------------------------------- /.nycrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": ["support-utils.js", "task-utils.js"], 3 | "reporter": ["html", "text"] 4 | } 5 | -------------------------------------------------------------------------------- /images/expect-backend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bahmutov/cypress-code-coverage/HEAD/images/expect-backend.png -------------------------------------------------------------------------------- /cypress/about.js: -------------------------------------------------------------------------------- 1 | document 2 | .getElementById('content') 3 | .appendChild(document.createTextNode('Est. 2019')) 4 | -------------------------------------------------------------------------------- /examples/all-files/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | import '../../../../support' 2 | console.log('this is commands file') 3 | -------------------------------------------------------------------------------- /examples/filter-files/README.md: -------------------------------------------------------------------------------- 1 | # example: filter-files 2 | 3 | Testing explicit filtering of files from code coverage 4 | -------------------------------------------------------------------------------- /examples/filter-files/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | import '../../../../support' 2 | console.log('this is commands file') 3 | -------------------------------------------------------------------------------- /examples/multiple-files/second.html: -------------------------------------------------------------------------------- 1 | 2 | Second page 3 | 4 | 5 | -------------------------------------------------------------------------------- /examples/no-files/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | import '../../../../support' 2 | console.log('this is commands file') 3 | -------------------------------------------------------------------------------- /examples/same-folder/index.html: -------------------------------------------------------------------------------- 1 | 2 | Page body 3 | 4 | 5 | -------------------------------------------------------------------------------- /images/instrumented-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bahmutov/cypress-code-coverage/HEAD/images/instrumented-code.png -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "tabWidth": 2, 4 | "semi": false, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /examples/before-all-visit/index.html: -------------------------------------------------------------------------------- 1 | 2 | Page body 3 | 4 | 5 | -------------------------------------------------------------------------------- /examples/before-all-visit/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-before-all-visit", 3 | "lockfileVersion": 1 4 | } 5 | -------------------------------------------------------------------------------- /examples/before-each-visit/index.html: -------------------------------------------------------------------------------- 1 | 2 | Page body 3 | 4 | 5 | -------------------------------------------------------------------------------- /examples/before-each-visit/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-before-each-visit", 3 | "lockfileVersion": 1 4 | } 5 | -------------------------------------------------------------------------------- /examples/exclude-files/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | import '../../../../support' 2 | console.log('this is commands file') 3 | -------------------------------------------------------------------------------- /examples/fullstack/server/index.html: -------------------------------------------------------------------------------- 1 | 2 | Page body 3 | 4 | 5 | -------------------------------------------------------------------------------- /examples/multiple-files/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | import '../../../../support' 2 | console.log('this is commands file') 3 | -------------------------------------------------------------------------------- /examples/placeholders/src/a.js: -------------------------------------------------------------------------------- 1 | export const myFunc = () => { 2 | const a = 10 3 | const b = 20 4 | return a + b 5 | } 6 | -------------------------------------------------------------------------------- /examples/support-files/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | import '../../../../support' 2 | console.log('this is commands file') 3 | -------------------------------------------------------------------------------- /examples/same-folder/unit-utils.js: -------------------------------------------------------------------------------- 1 | export const reverse = s => 2 | s 3 | .split('') 4 | .reverse() 5 | .join('') 6 | -------------------------------------------------------------------------------- /examples/unit-tests-js/README.md: -------------------------------------------------------------------------------- 1 | # example: unit-tests-js 2 | 3 | Examples that only run unit tests written using JavaScript 4 | -------------------------------------------------------------------------------- /examples/use-plugins-and-support/index.html: -------------------------------------------------------------------------------- 1 | 2 | Page body 3 | 4 | 5 | -------------------------------------------------------------------------------- /examples/use-plugins-and-support/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-before-each-visit", 3 | "lockfileVersion": 1 4 | } 5 | -------------------------------------------------------------------------------- /images/window-coverage-object.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bahmutov/cypress-code-coverage/HEAD/images/window-coverage-object.png -------------------------------------------------------------------------------- /cypress/about.html: -------------------------------------------------------------------------------- 1 | 2 |

About

3 |
4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/unit-tests-ts/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"], 3 | "presets": [ 4 | "@babel/preset-typescript" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /examples/docker-paths/images/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bahmutov/cypress-code-coverage/HEAD/examples/docker-paths/images/file.png -------------------------------------------------------------------------------- /examples/docker-paths/images/files.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bahmutov/cypress-code-coverage/HEAD/examples/docker-paths/images/files.png -------------------------------------------------------------------------------- /examples/fullstack/images/fullstack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bahmutov/cypress-code-coverage/HEAD/examples/fullstack/images/fullstack.png -------------------------------------------------------------------------------- /examples/docker-paths/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginsFile": "../../plugins", 3 | "supportFile": "../../support", 4 | "fixturesFolder": false 5 | } 6 | -------------------------------------------------------------------------------- /examples/placeholders/src/b.js: -------------------------------------------------------------------------------- 1 | export const anotherFunction = () => { 2 | const hello = 'hello' 3 | return hello.split('').reverse().join('') 4 | } 5 | -------------------------------------------------------------------------------- /examples/ts-example/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('../../../../task')(on, config) 3 | return config 4 | } 5 | -------------------------------------------------------------------------------- /examples/component/cypress/support/component.js: -------------------------------------------------------------------------------- 1 | import 'code-coverage' 2 | import { mount } from 'cypress/react18' 3 | 4 | Cypress.Commands.add('mount', mount) 5 | -------------------------------------------------------------------------------- /examples/filter-files/cypress/integration/spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | it('works', () => { 3 | cy.visit('/') 4 | cy.contains('Page body') 5 | }) 6 | -------------------------------------------------------------------------------- /examples/no-files/cypress/integration/spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | it.skip('works', () => { 3 | cy.visit('/') 4 | cy.contains('Page body') 5 | }) 6 | -------------------------------------------------------------------------------- /examples/support-files/cypress/integration/spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | it('works', () => { 3 | cy.visit('/') 4 | cy.contains('Page body') 5 | }) 6 | -------------------------------------------------------------------------------- /examples/exclude-files/README.md: -------------------------------------------------------------------------------- 1 | # example: exclude files 2 | 3 | Exclude some files from final coverage report by using `nyc` object in [package.json](package.json) file. 4 | -------------------------------------------------------------------------------- /examples/multiple-files/index.html: -------------------------------------------------------------------------------- 1 | 2 |

First page

3 |

Second page

4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/use-plugins-and-support/README.md: -------------------------------------------------------------------------------- 1 | # example: use-plugins-and-support 2 | 3 | Using included plugins and support files 4 | 5 | See [cypress.json](cypress.json) file 6 | -------------------------------------------------------------------------------- /examples/backend/cypress/integration/spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | it('has backend code coverage', () => { 3 | cy.visit('/') 4 | cy.request('/hello') 5 | }) 6 | -------------------------------------------------------------------------------- /examples/one-spec/cypress/integration/spec-b.js: -------------------------------------------------------------------------------- 1 | /// 2 | it('spec b', () => { 3 | // should not run 4 | throw new Error('Spec b should not run') 5 | }) 6 | -------------------------------------------------------------------------------- /examples/multiple-files/README.md: -------------------------------------------------------------------------------- 1 | # example: multiple files 2 | 3 | The application has multiple pages which include multiple source files. The coverage increases as we run multiple specs. 4 | -------------------------------------------------------------------------------- /examples/same-folder/plugins.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('../../task')(on, config) 3 | on('file:preprocessor', require('../../use-babelrc')) 4 | return config 5 | } 6 | -------------------------------------------------------------------------------- /cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /examples/merge-coverage/cypress/e2e/sub.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | import { sub } from '../../src/math' 3 | 4 | it('subtracts two numbers', () => { 5 | expect(sub(2, 3)).to.equal(-1) 6 | }) 7 | -------------------------------------------------------------------------------- /examples/unit-tests-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "moduleResolution": "node" 6 | }, 7 | "files": ["src/utils/misc.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /examples/use-webpack/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:5000", 3 | "supportFile": "../../support", 4 | "fixturesFolder": false, 5 | "viewportHeight": 400, 6 | "viewportWidth": 400 7 | } 8 | -------------------------------------------------------------------------------- /plugins.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('./task')(on, config) 3 | // IMPORTANT to return the config object 4 | // with the any changed environment variables 5 | return config 6 | } 7 | -------------------------------------------------------------------------------- /examples/backend/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('../../../../task')(on, config) 3 | on('file:preprocessor', require('../../../../use-babelrc')) 4 | return config 5 | } 6 | -------------------------------------------------------------------------------- /examples/fullstack/string-utils.js: -------------------------------------------------------------------------------- 1 | // reverses a string 2 | const reverse = s => { 3 | return s 4 | .split('') 5 | .reverse() 6 | .join('') 7 | } 8 | module.exports = { 9 | reverse 10 | } 11 | -------------------------------------------------------------------------------- /cypress/index.html: -------------------------------------------------------------------------------- 1 | 2 | 5 |

Test page

6 |

Open the DevTools to see console messages

7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/filter-files/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('../../../../task')(on, config) 3 | on('file:preprocessor', require('../../../../use-babelrc')) 4 | return config 5 | } 6 | -------------------------------------------------------------------------------- /examples/merge-coverage/README.md: -------------------------------------------------------------------------------- 1 | # example: merge-coverage 2 | 3 | Run each spec one by one, producing partial report. Then use cc-merge to merge them into a single combined report, and then produce the final report. 4 | -------------------------------------------------------------------------------- /examples/one-spec/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('../../../../task')(on, config) 3 | on('file:preprocessor', require('../../../../use-babelrc')) 4 | return config 5 | } 6 | -------------------------------------------------------------------------------- /examples/same-folder/cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } -------------------------------------------------------------------------------- /examples/use-webpack/src/calc.js: -------------------------------------------------------------------------------- 1 | export const add = (a, b) => { 2 | return a + b 3 | } 4 | 5 | export const reverse = s => { 6 | return s 7 | .split('') 8 | .reverse() 9 | .join('') 10 | } 11 | -------------------------------------------------------------------------------- /examples/before-all-visit/cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } -------------------------------------------------------------------------------- /examples/before-each-visit/cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } -------------------------------------------------------------------------------- /examples/exclude-files/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('../../../../task')(on, config) 3 | on('file:preprocessor', require('../../../../use-babelrc')) 4 | return config 5 | } 6 | -------------------------------------------------------------------------------- /examples/merge-coverage/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('../../../../plugin')(on, config) 3 | on('file:preprocessor', require('../../../../use-babelrc')) 4 | return config 5 | } 6 | -------------------------------------------------------------------------------- /examples/support-files/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('../../../../task')(on, config) 3 | on('file:preprocessor', require('../../../../use-babelrc')) 4 | return config 5 | } 6 | -------------------------------------------------------------------------------- /examples/unit-tests-js/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('../../../../task')(on, config) 3 | on('file:preprocessor', require('../../../../use-babelrc')) 4 | return config 5 | } 6 | -------------------------------------------------------------------------------- /examples/multiple-files/cypress/integration/first.js: -------------------------------------------------------------------------------- 1 | /// 2 | describe('first page', () => { 3 | it('loads', () => { 4 | cy.visit('/').should('have.property', '__coverage__') 5 | }) 6 | }) 7 | -------------------------------------------------------------------------------- /examples/multiple-files/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('../../../../plugin')(on, config) 3 | on('file:preprocessor', require('../../../../use-babelrc')) 4 | 5 | return config 6 | } 7 | -------------------------------------------------------------------------------- /examples/one-spec/index.html: -------------------------------------------------------------------------------- 1 | 2 |

One spec

3 |

Page body

4 |

cypress.tips

5 | 6 | 7 | -------------------------------------------------------------------------------- /examples/all-files/second.js: -------------------------------------------------------------------------------- 1 | // this file should be excluded from the final coverage numbers 2 | // using "nyc.exclude" list in package.json 3 | window.reverse = s => 4 | s 5 | .split('') 6 | .reverse() 7 | .join('') 8 | -------------------------------------------------------------------------------- /examples/exclude-files/second.js: -------------------------------------------------------------------------------- 1 | // this file should be excluded from the final coverage numbers 2 | // using "nyc.exclude" list in package.json 3 | window.reverse = s => 4 | s 5 | .split('') 6 | .reverse() 7 | .join('') 8 | -------------------------------------------------------------------------------- /examples/docker-paths/app/second.js: -------------------------------------------------------------------------------- 1 | // this file should be excluded from the final coverage numbers 2 | // using "nyc.exclude" list in package.json 3 | window.reverse = s => 4 | s 5 | .split('') 6 | .reverse() 7 | .join('') 8 | -------------------------------------------------------------------------------- /examples/merge-coverage/cypress/e2e/add.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | import { add } from '../../src/math' 3 | 4 | it('adds two numbers', () => { 5 | expect(add(1, 3)).to.equal(4) 6 | expect(add(2, 3)).to.equal(5) 7 | }) 8 | -------------------------------------------------------------------------------- /examples/multiple-files/cypress/integration/second.js: -------------------------------------------------------------------------------- 1 | /// 2 | describe('second page', () => { 3 | it('loads', () => { 4 | cy.visit('/second.html').should('have.property', '__coverage__') 5 | }) 6 | }) 7 | -------------------------------------------------------------------------------- /cypress-backend.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:3003", 3 | "integrationFolder": "cypress/test-backend", 4 | "env": { 5 | "codeCoverage": { 6 | "url": "http://localhost:3003/__coverage__" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /cypress/app.js: -------------------------------------------------------------------------------- 1 | import { map } from 'lodash' 2 | 3 | const list = [{ name: 'joe' }, { name: 'mary' }] 4 | const names = map(list, 'name') 5 | if (true) { 6 | console.log('just names', names) 7 | } else { 8 | console.error('never reached') 9 | } 10 | -------------------------------------------------------------------------------- /examples/all-files/not-covered.js: -------------------------------------------------------------------------------- 1 | // this file is NOT included from "index.html" 2 | // thus it is not instrumented and not included 3 | // in the final code coverage numbers 4 | function throwsError() { 5 | throw new Error('NO') 6 | } 7 | throwsError() 8 | -------------------------------------------------------------------------------- /examples/before-all-visit/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('../../../../task')(on, config) 3 | // IMPORTANT to return the config object 4 | // with the any changed environment variables 5 | return config 6 | } 7 | -------------------------------------------------------------------------------- /examples/before-each-visit/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('../../../../task')(on, config) 3 | // IMPORTANT to return the config object 4 | // with the any changed environment variables 5 | return config 6 | } 7 | -------------------------------------------------------------------------------- /examples/no-files/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('../../../../plugin')(on, config) 3 | // do not instrument the spec files 4 | // on('file:preprocessor', require('../../../../use-babelrc')) 5 | return config 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | cypress/videos 3 | cypress/screenshots 4 | coverage/ 5 | .nyc_output/ 6 | dist/ 7 | .cache/ 8 | .vscode/ 9 | cypress-coverage/ 10 | examples/*/cypress/videos 11 | examples/*/cypress/screenshots 12 | yarn.lock 13 | cc1 14 | cc2 15 | -------------------------------------------------------------------------------- /examples/component/src/Hello.cy.jsx: -------------------------------------------------------------------------------- 1 | import Hello from './Hello' 2 | 3 | describe('Hello', () => { 4 | it('shows the greeting', () => { 5 | cy.mount() 6 | cy.contains('Hello, World').should('include.text', 'sum is 5') 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /examples/filter-files/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "fixturesFolder": false, 3 | "pluginsFile": "cypress/plugins/index.js", 4 | "baseUrl": "http://localhost:1234", 5 | "env": { 6 | "coverage": { 7 | "exclude": "support/commands.js" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/placeholders/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('../../../../task')(on, config) 3 | // instrument the specs and any source files loaded from specs 4 | on('file:preprocessor', require('../../../../use-babelrc')) 5 | return config 6 | } 7 | -------------------------------------------------------------------------------- /examples/all-files/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('../../../../task')(on, config) 3 | // instrument the specs and any source files loaded from specs 4 | on('file:preprocessor', require('../../../../use-babelrc')) 5 | 6 | return config 7 | } 8 | -------------------------------------------------------------------------------- /examples/component/src/Hello.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { add } from './calc' 3 | 4 | const sum = add(2, 3) 5 | 6 | const Hello = ({ greeting }) => ( 7 |
8 | Hello, {greeting}! sum is {sum} 9 |
10 | ) 11 | 12 | export default Hello 13 | -------------------------------------------------------------------------------- /examples/fullstack/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('../../../../task')(on, config) 3 | // instrument loaded spec files (and the application code loaded from them) 4 | on('file:preprocessor', require('../../../../use-browserify-istanbul')) 5 | return config 6 | } 7 | -------------------------------------------------------------------------------- /examples/placeholders/cypress/integration/spec-a.js: -------------------------------------------------------------------------------- 1 | /// 2 | // this spec only loads "src/a" module 3 | import { myFunc } from '../../src/a.js' 4 | describe('spec-a', () => { 5 | it('exercises src/a.js', () => { 6 | expect(myFunc(), 'always returns 30').to.equal(30) 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /examples/unit-tests-ts/src/utils/misc.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Very simple check, returns true for arrays as well 3 | */ 4 | export function isObject(object: any) { 5 | return object != null && typeof object === 'object' 6 | } 7 | 8 | /** 9 | * Adds two numbers together 10 | */ 11 | export const add = (a: number, b: number) => a + b 12 | -------------------------------------------------------------------------------- /examples/docker-paths/cypress/integration/spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | describe('docker-paths', () => { 3 | it('works', () => { 4 | cy.visit('dist/index.html') 5 | cy.contains('Page body') 6 | 7 | cy.window() 8 | .invoke('reverse', 'super') 9 | .should('equal', 'repus') 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /examples/unit-tests-ts/README.md: -------------------------------------------------------------------------------- 1 | # example: unit-tests-ts 2 | 3 | Examples that only run unit tests to test code written in TypeScript 4 | 5 | **NOT WORKING** seems the TS code goes through `tsify` plugin, and does not pass via Istanbul plugin no matter what I try. See [issue #361](https://github.com/cypress-io/code-coverage/issues/361). 6 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "automerge": true, 6 | "major": { 7 | "automerge": false 8 | }, 9 | "prHourlyLimit": 2, 10 | "updateNotScheduled": false, 11 | "timezone": "America/New_York", 12 | "schedule": [ 13 | "every weekend" 14 | ], 15 | "masterIssue": true 16 | } 17 | -------------------------------------------------------------------------------- /middleware/express.js: -------------------------------------------------------------------------------- 1 | // for Express.js 2 | module.exports = app => { 3 | // expose "GET __coverage__" endpoint that just returns 4 | // global coverage information (if the application has been instrumented) 5 | app.get('/__coverage__', (req, res) => { 6 | res.json({ 7 | coverage: global.__coverage__ || null 8 | }) 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /examples/no-files/index.html: -------------------------------------------------------------------------------- 1 | 2 | Page body 3 | 4 | 13 | 14 | -------------------------------------------------------------------------------- /use-browserify-istanbul.js: -------------------------------------------------------------------------------- 1 | const browserify = require('@cypress/browserify-preprocessor') 2 | 3 | const options = browserify.defaultOptions 4 | // transform[1][1] is "babelify" 5 | // so we just add our code instrumentation plugin to the list 6 | options.browserifyOptions.transform[1][1].plugins.push('babel-plugin-istanbul') 7 | module.exports = browserify(options) 8 | -------------------------------------------------------------------------------- /examples/filter-files/index.html: -------------------------------------------------------------------------------- 1 | 2 | Page body 3 | 4 | 13 | 14 | -------------------------------------------------------------------------------- /examples/support-files/index.html: -------------------------------------------------------------------------------- 1 | 2 | Page body 3 | 4 | 13 | 14 | -------------------------------------------------------------------------------- /examples/unit-tests-js/src/utils/misc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Very simple check, returns true for arrays as well 3 | */ 4 | export function isObject(object) { 5 | return object != null && typeof object === 'object' 6 | } 7 | 8 | /** 9 | * Adds two numbers together 10 | * @param {number} a 11 | * @param {number} b 12 | */ 13 | export const add = (a, b) => a + b 14 | -------------------------------------------------------------------------------- /examples/placeholders/cypress/integration/spec-b.js: -------------------------------------------------------------------------------- 1 | /// 2 | // this spec loads "src/b" module 3 | import { anotherFunction } from '../../src/b.js' 4 | describe('spec-b', () => { 5 | it('exercises src/b.js', () => { 6 | expect(anotherFunction(), 'always returns hello backwards').to.equal( 7 | 'olleh' 8 | ) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /examples/all-files/cypress/integration/spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | it('works', () => { 3 | cy.visit('/') 4 | cy.contains('Page body') 5 | 6 | cy.window() 7 | .invoke('reverse', 'super') 8 | .should('equal', 'repus') 9 | 10 | // application's code should be instrumented 11 | cy.window().should('have.property', '__coverage__') 12 | }) 13 | -------------------------------------------------------------------------------- /use-babelrc.js: -------------------------------------------------------------------------------- 1 | const browserify = require('@cypress/browserify-preprocessor') 2 | 3 | // TODO check if there is .babelrc file 4 | // if not, maybe create one? 5 | 6 | // Tells Cypress to use .babelrc when bundling spec code 7 | const options = browserify.defaultOptions 8 | options.browserifyOptions.transform[1][1].babelrc = true 9 | module.exports = browserify(options) 10 | -------------------------------------------------------------------------------- /examples/ts-example/main.ts: -------------------------------------------------------------------------------- 1 | import { add } from './calc' 2 | 3 | const sub = (a: number, b: number) => { 4 | return a - b 5 | } 6 | 7 | function abs(x: number) { 8 | if (x >= 0) { 9 | return x 10 | } else { 11 | return -x 12 | } 13 | } 14 | 15 | // @ts-ignore 16 | window.add = add 17 | // @ts-ignore 18 | window.sub = sub 19 | // @ts-ignore 20 | window.abs = abs 21 | -------------------------------------------------------------------------------- /examples/component/cypress/support/component-index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Components App 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /examples/use-plugins-and-support/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | e2e: { 5 | fixturesFolder: false, 6 | video: false, 7 | specPattern: 'cypress/integration/spec.js', 8 | supportFile: '../../support.js', 9 | setupNodeEvents(on, config) { 10 | return require('../../plugins')(on, config) 11 | }, 12 | }, 13 | }) 14 | -------------------------------------------------------------------------------- /examples/unit-tests-js/cypress/integration/spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | import { isObject, add } from '../../src/utils/misc' 3 | 4 | describe('unit tests', () => { 5 | it('adds two numbers', () => { 6 | expect(add(2, 3)).to.equal(5) 7 | }) 8 | 9 | it('checks for object', () => { 10 | expect(isObject({}), '{}').to.be.true 11 | expect(isObject([]), '[]').to.be.true 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /examples/unit-tests-ts/cypress/integration/spec.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { isObject, add } from '../../src/utils/misc' 3 | 4 | describe('unit tests', () => { 5 | it('adds two numbers', () => { 6 | expect(add(2, 3)).to.equal(5) 7 | }) 8 | 9 | it('checks for object', () => { 10 | expect(isObject({}), '{}').to.be.true 11 | expect(isObject([]), '[]').to.be.true 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /examples/one-spec/cypress/integration/spec-a.js: -------------------------------------------------------------------------------- 1 | /// 2 | it('spec a', () => { 3 | cy.visit('index.html') 4 | cy.contains('Page body') 5 | 6 | cy.window().invoke('add', 2, 3).should('equal', 5) 7 | 8 | cy.window().invoke('sub', 2, 3).should('equal', -1) 9 | }) 10 | 11 | it('goes to another origin', () => { 12 | cy.visit('index.html') 13 | cy.get('a[title="another site"]').click() 14 | }) 15 | -------------------------------------------------------------------------------- /examples/ts-example/README.md: -------------------------------------------------------------------------------- 1 | # example: ts-example 2 | 3 | Code coverage for TypeScript code. See [nyc TS support](https://github.com/istanbuljs/nyc#typescript-projects) docs too. 4 | 5 | Code is instrumented on the fly using [.babelrc](.babelrc) and Parcel to run it. 6 | 7 | ## Use 8 | 9 | - start the server and Cypress with `npm run dev` 10 | - run the Cypress tests 11 | - look at the generated reports in folder `coverage` 12 | -------------------------------------------------------------------------------- /examples/filter-files/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-filter-files", 3 | "description": "Filtering out files from the code coverage", 4 | "devDependencies": { 5 | "@babel/core": "7.9.0" 6 | }, 7 | "scripts": { 8 | "start": "../../node_modules/.bin/parcel serve index.html", 9 | "cy:open": "../../node_modules/.bin/cypress open", 10 | "dev": "../../node_modules/.bin/start-test 1234 cy:open" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/docker-paths/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-docker-paths", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "Instrumented files in Docker container are found locally", 6 | "main": "index.js", 7 | "scripts": { 8 | "cy:open": "../../node_modules/.bin/cypress open", 9 | "cy:run": "../../node_modules/.bin/cypress run" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC" 14 | } 15 | -------------------------------------------------------------------------------- /middleware/hapi.js: -------------------------------------------------------------------------------- 1 | // for Hapi.js 2 | module.exports = server => { 3 | // expose "GET __coverage__" endpoint that just returns 4 | // global coverage information (if the application has been instrumented) 5 | 6 | // https://hapijs.com/tutorials/routing?lang=en_US 7 | server.route({ 8 | method: 'GET', 9 | path: '/__coverage__', 10 | handler () { 11 | return { coverage: global.__coverage__ } 12 | } 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /examples/before-each-visit/cypress/integration/spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | describe('coverage information', () => { 3 | beforeEach(() => { 4 | cy.visit('index.html') 5 | }) 6 | 7 | it('calls add', () => { 8 | cy.window() 9 | .invoke('add', 2, 3) 10 | .should('equal', 5) 11 | }) 12 | 13 | it('calls sub', () => { 14 | cy.window() 15 | .invoke('sub', 2, 3) 16 | .should('equal', -1) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /examples/use-plugins-and-support/cypress/integration/spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | describe('coverage information', () => { 3 | beforeEach(() => { 4 | cy.visit('index.html') 5 | }) 6 | 7 | it('calls add', () => { 8 | cy.window() 9 | .invoke('add', 2, 3) 10 | .should('equal', 5) 11 | }) 12 | 13 | it('calls sub', () => { 14 | cy.window() 15 | .invoke('sub', 2, 3) 16 | .should('equal', -1) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /examples/before-all-visit/README.md: -------------------------------------------------------------------------------- 1 | # example: before-all-visit 2 | 3 | Code coverage example where the `cy.visit` happens in `before` hook 4 | 5 | Code was instrumented with 6 | 7 | ```shell 8 | npx nyc instrument --compact false main.js > main-instrumented.js 9 | ``` 10 | 11 | and then removed absolute folder paths, leaving just relative path `main.js` in the produced file. 12 | 13 | The code uses custom coverage report command in [package.json](package.json) to call `nyc` 14 | -------------------------------------------------------------------------------- /examples/use-webpack/src/index.js: -------------------------------------------------------------------------------- 1 | import { reverse } from './calc' 2 | 3 | if (window.Cypress) { 4 | require('console-log-div') 5 | console.log('attaching event listeners') 6 | } 7 | 8 | document.getElementById('user-input').addEventListener('change', e => { 9 | const s = e.target.value 10 | console.log(`input string "${s}"`) 11 | const reversed = reverse(s) 12 | document.getElementById('reversed').innerText = reversed 13 | }) 14 | console.log('added event listener') 15 | -------------------------------------------------------------------------------- /examples/before-each-visit/README.md: -------------------------------------------------------------------------------- 1 | # example: before-each-visit 2 | 3 | Code coverage example where the `cy.visit` happens in `beforeEach` hook 4 | 5 | Code was instrumented with 6 | 7 | ```shell 8 | npx nyc instrument --compact false main.js > main-instrumented.js 9 | ``` 10 | 11 | and then removed absolute folder paths, leaving just relative path `main.js` in the produced file. 12 | 13 | The code uses custom coverage report command in [package.json](package.json) to call `nyc` 14 | -------------------------------------------------------------------------------- /examples/merge-coverage/src/math.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Adds two numbers together 3 | * @param {number} a 4 | * @param {number} b 5 | */ 6 | export const add = (a, b) => { 7 | console.log('add %d to %d', a, b) 8 | if (a === 2) { 9 | console.log('a is 2') 10 | } 11 | return a + b 12 | } 13 | 14 | /** 15 | * subtracts numbers 16 | * @param {number} a 17 | * @param {number} b 18 | */ 19 | export const sub = (a, b) => { 20 | console.log('sub %d to %d', a, b) 21 | return a - b 22 | } 23 | -------------------------------------------------------------------------------- /examples/all-files/index.html: -------------------------------------------------------------------------------- 1 | 2 | Page body 3 | 4 | 5 | 17 | 18 | -------------------------------------------------------------------------------- /examples/before-all-visit/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | e2e: { 5 | specPattern: 'cypress/integration/*.js', 6 | coverage: { 7 | exclude: true, 8 | }, 9 | // We've imported your old cypress plugins here. 10 | // You may want to clean this up later by importing these. 11 | setupNodeEvents(on, config) { 12 | return require('./cypress/plugins/index.js')(on, config) 13 | }, 14 | }, 15 | }) 16 | -------------------------------------------------------------------------------- /examples/before-each-visit/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | e2e: { 5 | specPattern: 'cypress/integration/*.js', 6 | coverage: { 7 | exclude: true, 8 | }, 9 | // We've imported your old cypress plugins here. 10 | // You may want to clean this up later by importing these. 11 | setupNodeEvents(on, config) { 12 | return require('./cypress/plugins/index.js')(on, config) 13 | }, 14 | }, 15 | }) 16 | -------------------------------------------------------------------------------- /examples/docker-paths/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | Page body 3 | 4 | 5 | 17 | 18 | -------------------------------------------------------------------------------- /examples/exclude-files/index.html: -------------------------------------------------------------------------------- 1 | 2 | Page body 3 | 4 | 5 | 17 | 18 | -------------------------------------------------------------------------------- /examples/one-spec/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-one-spec", 3 | "description": "Only running a single spec", 4 | "scripts": { 5 | "cy:open": "../../node_modules/.bin/cypress open", 6 | "cy:run": "../../node_modules/.bin/cypress run", 7 | "check:covered": "../../node_modules/.bin/check-coverage main.js", 8 | "check:extras": "../../node_modules/.bin/only-covered --from coverage/coverage-final.json main.js", 9 | "check": "npm run check:covered && npm run check:extras" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/use-webpack/cypress/integration/spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | import { add } from '../../src/calc' 3 | 4 | describe('Webpack example', () => { 5 | it('loads', () => { 6 | cy.visit('/') 7 | cy.contains('Webpack page').should('be.visible') 8 | cy.get('#user-input').type('Hello{enter}') 9 | cy.contains('olleH').should('be.visible') 10 | }) 11 | 12 | it('has add function', () => { 13 | // test "add" via this unit test 14 | expect(add(2, 3)).to.equal(5) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /examples/unit-tests-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-unit-tests-js", 3 | "description": "Run unit tests written using JavaScript", 4 | "scripts": { 5 | "cy:open": "../../node_modules/.bin/cypress open", 6 | "cy:run": "../../node_modules/.bin/cypress run", 7 | "check:covered": "../../node_modules/.bin/check-coverage misc.js", 8 | "check:extras": "../../node_modules/.bin/only-covered --from coverage/coverage-final.json misc.js", 9 | "check": "npm run check:covered && npm run check:extras" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/unit-tests-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-unit-tests-ts", 3 | "description": "Run unit tests written using TypeScript", 4 | "scripts": { 5 | "cy:open": "../../node_modules/.bin/cypress open", 6 | "cy:run": "../../node_modules/.bin/cypress run", 7 | "check:covered": "../../node_modules/.bin/check-coverage misc.ts", 8 | "check:extras": "../../node_modules/.bin/only-covered --from coverage/coverage-final.json misc.ts", 9 | "check": "npm run check:covered && npm run check:extras" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /cypress/e2e/minimatch.cy.js: -------------------------------------------------------------------------------- 1 | describe('minimatch', () => { 2 | it('string matches', () => { 3 | expect( 4 | Cypress.minimatch('/path/to/specA.js', '/path/to/specA.js'), 5 | 'matches full strings', 6 | ).to.be.true 7 | 8 | expect( 9 | Cypress.minimatch('/path/to/specA.js', 'specA.js'), 10 | 'does not match just the end', 11 | ).to.be.false 12 | 13 | expect( 14 | Cypress.minimatch('/path/to/specA.js', '**/specA.js'), 15 | 'matches using **', 16 | ).to.be.true 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /examples/ts-example/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | e2e: { 5 | fixturesFolder: false, 6 | baseUrl: 'http://localhost:1234', 7 | video: false, 8 | specPattern: 'cypress/integration/spec.js', 9 | // We've imported your old cypress plugins here. 10 | // You may want to clean this up later by importing these. 11 | setupNodeEvents(on, config) { 12 | return require('./cypress/plugins/index.js')(on, config) 13 | }, 14 | }, 15 | }) 16 | -------------------------------------------------------------------------------- /examples/placeholders/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | e2e: { 5 | fixturesFolder: false, 6 | video: false, 7 | specPattern: 'cypress/integration/*.js', 8 | supportFile: 'cypress/support/index.js', 9 | // We've imported your old cypress plugins here. 10 | // You may want to clean this up later by importing these. 11 | setupNodeEvents(on, config) { 12 | return require('./cypress/plugins/index.js')(on, config) 13 | }, 14 | }, 15 | }) 16 | -------------------------------------------------------------------------------- /examples/merge-coverage/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | e2e: { 5 | env: { 6 | coverage: { 7 | exclude: '**/cypress/**', 8 | }, 9 | }, 10 | fixturesFolder: false, 11 | video: false, 12 | // We've imported your old cypress plugins here. 13 | // You may want to clean this up later by importing these. 14 | setupNodeEvents(on, config) { 15 | return require('./cypress/plugins/index.js')(on, config) 16 | }, 17 | }, 18 | }) 19 | -------------------------------------------------------------------------------- /examples/before-all-visit/cypress/integration/spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | describe( 3 | 'coverage information', 4 | { testIsolation: false, viewportHeight: 100, viewportWidth: 100 }, 5 | () => { 6 | before(() => { 7 | cy.log('visiting index.html') 8 | cy.visit('index.html') 9 | }) 10 | 11 | it('calls add', () => { 12 | cy.window().invoke('add', 2, 3).should('equal', 5) 13 | }) 14 | 15 | it('calls sub', () => { 16 | cy.window().invoke('sub', 2, 3).should('equal', -1) 17 | }) 18 | }, 19 | ) 20 | -------------------------------------------------------------------------------- /examples/use-webpack/README.md: -------------------------------------------------------------------------------- 1 | # use-webpack 2 | 3 | > Instruments the built bundle using Webpack 4 | 5 | Webpack uses [webpack.config.js](webpack.config.js) to build the bundle from [src/index.js](src/index.js) into `dist/main.js`, loaded from [dist/index.html](dist/index.html). The [cypress/integration/spec.js](cypress/integration/spec.js) also uses one of the functions from [src/calc.js](src/calc.js) directly. The final coverage includes both E2E and unit test coverage information. 6 | 7 | **Note:** this project requires `npm run build` before running `npm run dev`. 8 | -------------------------------------------------------------------------------- /examples/exclude-files/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | e2e: { 5 | baseUrl: 'http://localhost:1234', 6 | specPattern: 'cypress/integration/*.js', 7 | env: { 8 | coverage: { 9 | exclude: true, 10 | }, 11 | }, 12 | // We've imported your old cypress plugins here. 13 | // You may want to clean this up later by importing these. 14 | setupNodeEvents(on, config) { 15 | return require('./cypress/plugins/index.js')(on, config) 16 | }, 17 | }, 18 | }) 19 | -------------------------------------------------------------------------------- /examples/multiple-files/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | e2e: { 5 | baseUrl: 'http://localhost:1234', 6 | specPattern: 'cypress/integration/*.js', 7 | env: { 8 | coverage: { 9 | exclude: false, 10 | }, 11 | }, 12 | // We've imported your old cypress plugins here. 13 | // You may want to clean this up later by importing these. 14 | setupNodeEvents(on, config) { 15 | return require('./cypress/plugins/index.js')(on, config) 16 | }, 17 | }, 18 | }) 19 | -------------------------------------------------------------------------------- /examples/use-plugins-and-support/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "use-plugins-and-support", 3 | "description": "Using included plugins and support files", 4 | "devDependencies": {}, 5 | "scripts": { 6 | "cy:open": "../../node_modules/.bin/cypress open", 7 | "cy:run": "../../node_modules/.bin/cypress run", 8 | "check:covered": "../../node_modules/.bin/check-coverage main.js", 9 | "check:extras": "../../node_modules/.bin/only-covered --from coverage/coverage-final.json main.js", 10 | "check": "npm run check:covered && npm run check:extras" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/one-spec/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | e2e: { 5 | fixturesFolder: false, 6 | video: false, 7 | specPattern: 'cypress/integration/spec-a.js', 8 | env: { 9 | coverage: { 10 | exclude: true, 11 | }, 12 | }, 13 | // We've imported your old cypress plugins here. 14 | // You may want to clean this up later by importing these. 15 | setupNodeEvents(on, config) { 16 | return require('./cypress/plugins/index.js')(on, config) 17 | }, 18 | }, 19 | }) 20 | -------------------------------------------------------------------------------- /examples/before-all-visit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-before-all-visit", 3 | "description": "Getting code coverage when cy.visit is used in before hook", 4 | "devDependencies": {}, 5 | "scripts": { 6 | "cy:open": "../../node_modules/.bin/cypress open", 7 | "coverage:report using npx": "npx nyc report --report-dir ./coverage --temp-dir .nyc_output --reporter=lcov --reporter=clover --reporter=json", 8 | "coverage:report using bin-up": "bin-up nyc report --report-dir ./coverage --temp-dir .nyc_output --reporter=lcov --reporter=clover --reporter=json" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/unit-tests-js/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | e2e: { 5 | fixturesFolder: false, 6 | video: false, 7 | specPattern: 'cypress/integration/spec.js', 8 | env: { 9 | coverage: { 10 | exclude: true, 11 | }, 12 | }, 13 | // We've imported your old cypress plugins here. 14 | // You may want to clean this up later by importing these. 15 | setupNodeEvents(on, config) { 16 | return require('./cypress/plugins/index.js')(on, config) 17 | }, 18 | }, 19 | }) 20 | -------------------------------------------------------------------------------- /examples/unit-tests-ts/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | e2e: { 5 | fixturesFolder: false, 6 | video: false, 7 | specPattern: 'cypress/integration/spec.ts', 8 | env: { 9 | coverage: { 10 | exclude: true, 11 | }, 12 | }, 13 | // We've imported your old cypress plugins here. 14 | // You may want to clean this up later by importing these. 15 | setupNodeEvents(on, config) { 16 | return require('./cypress/plugins/index.js')(on, config) 17 | }, 18 | }, 19 | }) 20 | -------------------------------------------------------------------------------- /examples/before-each-visit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-before-each-visit", 3 | "description": "Getting code coverage when cy.visit is used in beforeEach hook", 4 | "devDependencies": {}, 5 | "scripts": { 6 | "cy:open": "../../node_modules/.bin/cypress open", 7 | "coverage:report using npx": "npx nyc report --report-dir ./coverage --temp-dir .nyc_output --reporter=lcov --reporter=clover --reporter=json", 8 | "coverage:report using bin-up": "bin-up nyc report --report-dir ./coverage --temp-dir .nyc_output --reporter=lcov --reporter=clover --reporter=json" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/same-folder/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-same-folder", 3 | "description": "Check if test files are correctly filtered out", 4 | "devDependencies": {}, 5 | "scripts": { 6 | "cy:open": "../../node_modules/.bin/cypress open", 7 | "cy:run": "../../node_modules/.bin/cypress run", 8 | "check:covered": "../../node_modules/.bin/check-coverage main.js unit-utils.js", 9 | "check:extras": "../../node_modules/.bin/only-covered --from coverage/coverage-final.json main.js unit-utils.js", 10 | "check": "npm run check:covered && npm run check:extras" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/same-folder/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | e2e: { 5 | fixturesFolder: false, 6 | video: false, 7 | specPattern: '**/spec.js', 8 | supportFile: 'support/support.js', 9 | env: { 10 | coverage: { 11 | exclude: true, 12 | }, 13 | }, 14 | // We've imported your old cypress plugins here. 15 | // You may want to clean this up later by importing these. 16 | setupNodeEvents(on, config) { 17 | return require('./plugins.js')(on, config) 18 | }, 19 | }, 20 | }) 21 | -------------------------------------------------------------------------------- /cypress/fixtures/coverage.json: -------------------------------------------------------------------------------- 1 | { 2 | "/src/index.js": { 3 | "path": "/src/index.js", 4 | "statementMap": { 5 | "0": { 6 | "start": { 7 | "line": 7, 8 | "column": 0 9 | }, 10 | "end": { 11 | "line": 14, 12 | "column": 2 13 | } 14 | } 15 | }, 16 | "fnMap": {}, 17 | "branchMap": {}, 18 | "s": { 19 | "0": 1 20 | }, 21 | "f": {}, 22 | "b": {}, 23 | "_coverageSchema": "1a1c01bbd47fc00a2c39e90264f33305004495a9", 24 | "hash": "46f2efd10038593ec768c6cc815a6d6ee2924243" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /examples/same-folder/spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { reverse } from './unit-utils' 4 | 5 | describe('coverage information', { testIsolation: false }, () => { 6 | before(() => { 7 | cy.log('visiting index.html') 8 | cy.visit('index.html') 9 | }) 10 | 11 | it('calls add', () => { 12 | cy.window().invoke('add', 2, 3).should('equal', 5) 13 | }) 14 | 15 | it('calls sub', () => { 16 | cy.window().invoke('sub', 2, 3).should('equal', -1) 17 | }) 18 | 19 | it('reverses a string', () => { 20 | expect(reverse('Hello')).to.equal('olleH') 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /examples/no-files/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | e2e: { 5 | fixturesFolder: false, 6 | video: false, 7 | baseUrl: 'http://localhost:1234', 8 | specPattern: 'cypress/integration/*.js', 9 | env: { 10 | coverage: { 11 | exclude: true, 12 | }, 13 | }, 14 | // We've imported your old cypress plugins here. 15 | // You may want to clean this up later by importing these. 16 | setupNodeEvents(on, config) { 17 | return require('./cypress/plugins/index.js')(on, config) 18 | }, 19 | }, 20 | }) 21 | -------------------------------------------------------------------------------- /examples/use-webpack/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | /// 2 | const webpack = require('@cypress/webpack-preprocessor') 3 | 4 | /** 5 | * @type {Cypress.PluginConfig} 6 | */ 7 | module.exports = (on, config) => { 8 | const options = { 9 | // use the same Webpack options to bundle spec files as your app does "normally" 10 | // which should instrument the spec files in this project 11 | webpackOptions: require('../../webpack.config'), 12 | watchOptions: {} 13 | } 14 | on('file:preprocessor', webpack(options)) 15 | 16 | require('../../../../task')(on, config) 17 | return config 18 | } 19 | -------------------------------------------------------------------------------- /examples/use-webpack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-use-webpack", 3 | "version": "1.0.0", 4 | "description": "Code coverage from webpack", 5 | "private": true, 6 | "scripts": { 7 | "cy:open": "../../node_modules/.bin/cypress open", 8 | "cy:run": "../../node_modules/.bin/cypress run", 9 | "dev": "../../node_modules/.bin/start-test 5000 cy:open", 10 | "build": "../../node_modules/.bin/webpack", 11 | "start": "../../node_modules/.bin/serve dist", 12 | "test:ci": "../../node_modules/.bin/start-test 5000 cy:run" 13 | }, 14 | "keywords": [], 15 | "author": "", 16 | "license": "ISC" 17 | } 18 | -------------------------------------------------------------------------------- /examples/no-files/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-filter-files", 3 | "description": "Filtering out files from the code coverage", 4 | "devDependencies": { 5 | "@babel/core": "7.9.0" 6 | }, 7 | "scripts": { 8 | "start": "../../node_modules/.bin/parcel serve index.html", 9 | "cy:open": "../../node_modules/.bin/cypress open", 10 | "cy:run": "../../node_modules/.bin/cypress run", 11 | "dev": "../../node_modules/.bin/start-test 1234 cy:open", 12 | "clean": "rm -rf .nyc_output coverage", 13 | "e2e": "../../node_modules/.bin/start-test 1234 cy:run", 14 | "pree2e": "npm run clean" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/support-files/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | e2e: { 5 | fixturesFolder: false, 6 | baseUrl: 'http://localhost:1234', 7 | video: false, 8 | specPattern: 'cypress/integration/spec.js', 9 | env: { 10 | coverage: { 11 | exclude: true, 12 | }, 13 | }, 14 | // We've imported your old cypress plugins here. 15 | // You may want to clean this up later by importing these. 16 | setupNodeEvents(on, config) { 17 | return require('./cypress/plugins/index.js')(on, config) 18 | }, 19 | }, 20 | }) 21 | -------------------------------------------------------------------------------- /examples/fullstack/cypress/integration/spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // load extra files to instrument on the fly 4 | const { reverse } = require('../../string-utils') 5 | 6 | it('uses frontend code and calls backend', () => { 7 | cy.visit('/') 8 | cy.contains('Page body').should('be.visible') 9 | 10 | cy.window() 11 | .invoke('add', 2, 3) 12 | .should('equal', 5) 13 | 14 | cy.window() 15 | .invoke('sub', 2, 3) 16 | .should('equal', -1) 17 | 18 | cy.log('**backend request**') 19 | cy.request('/hello') 20 | 21 | cy.log('**unit test**') 22 | expect(reverse('Hello')).to.equal('olleH') 23 | }) 24 | -------------------------------------------------------------------------------- /examples/ts-example/cypress/integration/spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | describe('ts-example', () => { 3 | beforeEach(() => { 4 | cy.visit('/') 5 | }) 6 | 7 | it('calls add', () => { 8 | cy.window() 9 | .invoke('add', 2, 3) 10 | .should('equal', 5) 11 | }) 12 | 13 | it('calls sub', () => { 14 | cy.window() 15 | .invoke('sub', 2, 3) 16 | .should('equal', -1) 17 | }) 18 | 19 | it('calls abs twice', () => { 20 | cy.window() 21 | .invoke('abs', 2) 22 | .should('equal', 2) 23 | 24 | cy.window() 25 | .invoke('abs', -5) 26 | .should('equal', 5) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('../../task')(on, config) 3 | 4 | // also use .babelrc file when bundling spec files 5 | // to get the code coverage from unit tests 6 | // https://glebbahmutov.com/blog/combined-end-to-end-and-unit-test-coverage/ 7 | on('file:preprocessor', require('../../use-babelrc')) 8 | 9 | // or use browserify and just push babel-plugin-istanbul 10 | // directory to the list of babelify plugins 11 | // on('file:preprocessor', require('../../use-browserify-istanbul')) 12 | 13 | // IMPORTANT to return the config object with changed environment variable 14 | return config 15 | } 16 | -------------------------------------------------------------------------------- /examples/all-files/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | viewportHeight: 200, 5 | viewportWidth: 200, 6 | e2e: { 7 | fixturesFolder: false, 8 | baseUrl: 'http://localhost:1234', 9 | specPattern: 'cypress/integration/*.js', 10 | env: { 11 | coverage: { 12 | exclude: true, 13 | }, 14 | }, 15 | // We've imported your old cypress plugins here. 16 | // You may want to clean this up later by importing these. 17 | setupNodeEvents(on, config) { 18 | return require('./cypress/plugins/index.js')(on, config) 19 | }, 20 | }, 21 | }) 22 | -------------------------------------------------------------------------------- /examples/exclude-files/cypress/integration/spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | it('works', () => { 3 | cy.visit('/') 4 | cy.contains('Page body') 5 | 6 | cy.window().invoke('reverse', 'super').should('equal', 'repus') 7 | }) 8 | 9 | it('uses minimatch', () => { 10 | expect(Cypress.minimatch('/path/to/file.js', 'to/*.js'), 'relative').to.be 11 | .false 12 | expect(Cypress.minimatch('/path/to/file.js', '/path/to/*.js'), 'absolute').to 13 | .be.true 14 | expect(Cypress.minimatch('/path/to/file.js', '**/to/*.js'), '** prefix').to.be 15 | .true 16 | cy.wrap(Cypress) 17 | .invoke({ timeout: 0 }, 'minimatch', '/path/to/file.js', '**/to/*.js') 18 | .should('be.true') 19 | }) 20 | -------------------------------------------------------------------------------- /examples/fullstack/README.md: -------------------------------------------------------------------------------- 1 | # example: fullstack 2 | 3 | > Combined code coverage from the backend code, and e2e and unit tests 4 | 5 | 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. 6 | 7 | To run 8 | 9 | ```sh 10 | $ npm run dev 11 | ``` 12 | 13 | You should see messages from the plugin when it saves each coverage object 14 | 15 | ![Coverage messages](images/fullstack.png) 16 | 17 | In the produced report, you should see 18 | 19 | - `server/server.js` coverage for backend 20 | - `main.js` coverage from end-to-end tests 21 | - `string-utils.js` coverage from unit tests 22 | -------------------------------------------------------------------------------- /examples/backend/server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const app = express() 3 | const port = 3003 4 | 5 | // if there is code coverage information 6 | // then expose an endpoint that returns it 7 | /* istanbul ignore next */ 8 | if (global.__coverage__) { 9 | console.log('have code coverage, will add middleware for express') 10 | console.log(`to fetch: GET :${port}/__coverage__`) 11 | require('../../../middleware/express')(app) 12 | } 13 | 14 | app.use(express.static(__dirname)) 15 | 16 | app.get('/hello', (req, res) => { 17 | console.log('sending hello world') 18 | res.send('Hello World!') 19 | }) 20 | 21 | app.listen(port, () => console.log(`Example app listening on port ${port}!`)) 22 | -------------------------------------------------------------------------------- /examples/fullstack/server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const app = express() 3 | const port = 3003 4 | 5 | // if there is code coverage information 6 | // then expose an endpoint that returns it 7 | /* istanbul ignore next */ 8 | if (global.__coverage__) { 9 | console.log('have code coverage, will add middleware for express') 10 | console.log(`to fetch: GET :${port}/__coverage__`) 11 | require('../../../middleware/express')(app) 12 | } 13 | 14 | app.use(express.static(__dirname)) 15 | 16 | app.get('/hello', (req, res) => { 17 | console.log('sending hello world') 18 | res.send('Hello World!') 19 | }) 20 | 21 | app.listen(port, () => console.log(`Example app listening on port ${port}!`)) 22 | -------------------------------------------------------------------------------- /examples/fullstack/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | e2e: { 5 | fixturesFolder: false, 6 | baseUrl: 'http://localhost:3003', 7 | env: { 8 | codeCoverage: { 9 | url: 'http://localhost:3003/__coverage__', 10 | }, 11 | coverage: { 12 | exclude: true, 13 | }, 14 | }, 15 | video: false, 16 | specPattern: 'cypress/integration/spec.js', 17 | // We've imported your old cypress plugins here. 18 | // You may want to clean this up later by importing these. 19 | setupNodeEvents(on, config) { 20 | return require('./cypress/plugins/index.js')(on, config) 21 | }, 22 | }, 23 | }) 24 | -------------------------------------------------------------------------------- /cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'cypress' 2 | 3 | export default defineConfig({ 4 | viewportHeight: 200, 5 | viewportWidth: 200, 6 | e2e: { 7 | env: { 8 | coverage: { 9 | // set to true to hide the messages in the Command Log 10 | quiet: false, 11 | // intercept and instrument scripts matching these URLs 12 | instrument: '**/calculator/**/*.js', 13 | }, 14 | }, 15 | // We've imported your old cypress plugins here. 16 | // You may want to clean this up later by importing these. 17 | setupNodeEvents(on, config) { 18 | return require('./cypress/plugins/index.js')(on, config) 19 | }, 20 | baseUrl: 'http://localhost:1234', 21 | }, 22 | }) 23 | -------------------------------------------------------------------------------- /examples/all-files/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-all-files", 3 | "description": "Report all files", 4 | "private": true, 5 | "scripts": { 6 | "start": "../../node_modules/.bin/parcel serve index.html", 7 | "start:windows": "npx bin-up parcel serve index.html", 8 | "cy:open": "../../node_modules/.bin/cypress open", 9 | "cy:run": "../../node_modules/.bin/cypress run", 10 | "dev": "../../node_modules/.bin/start-test 1234 cy:open", 11 | "e2e": "../../node_modules/.bin/start-test 1234 cy:run", 12 | "report": "../../node_modules/.bin/nyc report" 13 | }, 14 | "nyc": { 15 | "all": true, 16 | "include": "*.js" 17 | }, 18 | "devDependencies": { 19 | "@babel/core": "7.9.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/backend/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | e2e: { 5 | fixturesFolder: false, 6 | baseUrl: 'http://localhost:3003', 7 | env: { 8 | codeCoverage: { 9 | url: 'http://localhost:3003/__coverage__', 10 | expectBackendCoverageOnly: true, 11 | }, 12 | coverage: { 13 | exclude: true, 14 | }, 15 | }, 16 | video: false, 17 | specPattern: 'cypress/integration/spec.js', 18 | // We've imported your old cypress plugins here. 19 | // You may want to clean this up later by importing these. 20 | setupNodeEvents(on, config) { 21 | return require('./cypress/plugins/index.js')(on, config) 22 | }, 23 | }, 24 | }) 25 | -------------------------------------------------------------------------------- /examples/support-files/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-support-files", 3 | "description": "Filtering out support files", 4 | "devDependencies": { 5 | "@babel/core": "7.9.0" 6 | }, 7 | "scripts": { 8 | "start": "../../node_modules/.bin/parcel serve index.html", 9 | "cy:open": "../../node_modules/.bin/cypress open", 10 | "cy:run": "../../node_modules/.bin/cypress run", 11 | "dev": "../../node_modules/.bin/start-test 1234 cy:open", 12 | "e2e": "../../node_modules/.bin/start-test 1234 cy:run", 13 | "check:covered": "../../node_modules/.bin/check-coverage main.js", 14 | "check:extras": "../../node_modules/.bin/only-covered --from coverage/coverage-final.json main.js", 15 | "check": "npm run check:covered && npm run check:extras" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-backend", 3 | "description": "Code coverage for backend", 4 | "devDependencies": {}, 5 | "private": true, 6 | "scripts": { 7 | "start": "../../node_modules/.bin/nyc --silent node server/server", 8 | "cy:open": "../../node_modules/.bin/cypress open", 9 | "cy:run": "../../node_modules/.bin/cypress run", 10 | "dev": "../../node_modules/.bin/start-test 3003 cy:open", 11 | "e2e": "../../node_modules/.bin/start-test 3003 cy:run", 12 | "coverage:report": "../../node_modules/.bin/nyc report", 13 | "check:covered": "../../node_modules/.bin/check-coverage server.js", 14 | "check:extras": "../../node_modules/.bin/only-covered server.js", 15 | "check": "npm run check:covered && npm run check:extras" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/use-webpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | // https://webpack.js.org/guides/development/ 4 | module.exports = { 5 | entry: './src/index.js', 6 | mode: 'development', 7 | devtool: 'inline-source-map', 8 | output: { 9 | filename: 'main.js', 10 | path: path.resolve(__dirname, 'dist') 11 | }, 12 | module: { 13 | rules: [ 14 | { 15 | // when bundling application's own source code 16 | // transpile using Babel which uses .babelrc file 17 | // and instruments code using babel-plugin-istanbul 18 | test: /\.js/, 19 | exclude: /(node_modules|bower_components)/, 20 | use: [ 21 | { 22 | loader: 'babel-loader' 23 | } 24 | ] 25 | } 26 | ] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /cypress/e2e/calculator.cy.js: -------------------------------------------------------------------------------- 1 | import { CalculatorPage } from './calculator-po' 2 | 3 | it( 4 | 'collects code coverage on the fly', 5 | { viewportWidth: 1200, viewportHeight: 1000, baseUrl: null }, 6 | () => { 7 | cy.visit('cypress/calculator/index.html') 8 | // after instrumenting the coverage should be collected in the window object 9 | // under window.__coverage__ key 10 | // There should be two keys: one for the main script and one for the spec 11 | cy.window() 12 | .should('have.property', '__coverage__') 13 | .should('have.keys', [ 14 | 'cypress/calculator/app.js', 15 | 'cypress/calculator/utils.js', 16 | ]) 17 | 18 | // compute an expression and see the increased code coverage 19 | CalculatorPage.compute('1+2.1', '3.1') 20 | }, 21 | ) 22 | -------------------------------------------------------------------------------- /cypress/calculator/utils.js: -------------------------------------------------------------------------------- 1 | // pure functions used during computation 2 | 3 | export function appendDot(expression) { 4 | // check if we are trying a number right now 5 | // this will split expressions like 6 | // "1+2.3" into ["1", "2.3"] 7 | const parts = expression.split(/[\+\-\*\/]/) 8 | // the last part is the current number 9 | // the user is appending to 10 | const last = parts[parts.length - 1] 11 | // 12 | // if the last part already has a dot, do nothing 13 | if (last.includes('.')) { 14 | return expression 15 | } 16 | // if the last part is empty, add a zero before the dot 17 | // this will cover starting to type a number with a dot 18 | // or typing a dot after an expression like "1+" 19 | 20 | if (last === '') { 21 | return expression + '0.' 22 | } 23 | // otherwise, add the dot and return the expression 24 | return expression + '.' 25 | } 26 | -------------------------------------------------------------------------------- /examples/fullstack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-fullstack", 3 | "description": "Combined code coverage from the backend code, and e2e and unit tests", 4 | "devDependencies": {}, 5 | "scripts": { 6 | "start": "../../node_modules/.bin/nyc --silent node server/server", 7 | "cy:open": "../../node_modules/.bin/cypress open", 8 | "cy:run": "../../node_modules/.bin/cypress run", 9 | "dev": "../../node_modules/.bin/start-test 3003 cy:open", 10 | "e2e": "../../node_modules/.bin/start-test 3003 cy:run", 11 | "report": "../../node_modules/.bin/nyc report --reporter text", 12 | "check:covered": "../../node_modules/.bin/check-coverage server.js main.js string-utils.js", 13 | "check:extras": "../../node_modules/.bin/only-covered --from coverage/coverage-final.json server.js main.js string-utils.js", 14 | "check": "npm run check:covered && npm run check:extras" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /middleware/nextjs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Middleware for returning server-side code coverage 3 | * for Next.js API route. To use, create new `pages/api/coverage.js` file 4 | * and re-export this default middleware function. 5 | * 6 | ``` 7 | // in your pages/api/coverage.js 8 | module.exports = require('@cypress/code-coverage/middleware/nextjs') 9 | // then add to your cypress.json an environment variable pointing at the API 10 | { 11 | "baseUrl": "http://localhost:3000", 12 | "env": { 13 | "codeCoverage": { 14 | "url": "/api/coverage" 15 | } 16 | } 17 | } 18 | ``` 19 | * 20 | * @see https://nextjs.org/docs#api-routes 21 | * @see https://github.com/cypress-io/code-coverage 22 | */ 23 | module.exports = function returnCodeCoverageNext (req, res) { 24 | // only GET is supported 25 | res.status(200).json({ 26 | coverage: global.__coverage__ || null 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /examples/exclude-files/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-exclude-files", 3 | "description": "Exclude some files from final coverage report", 4 | "scripts": { 5 | "start": "../../node_modules/.bin/parcel serve index.html", 6 | "cy:open": "../../node_modules/.bin/cypress open", 7 | "cy:run": "../../node_modules/.bin/cypress run", 8 | "dev": "../../node_modules/.bin/start-test 1234 cy:open", 9 | "e2e": "../../node_modules/.bin/start-test 1234 cy:run", 10 | "check:covered": "../../node_modules/.bin/check-coverage main.js", 11 | "check:extras": "../../node_modules/.bin/only-covered --from coverage/coverage-final.json main.js", 12 | "check": "npm run check:covered && npm run check:extras" 13 | }, 14 | "nyc": { 15 | "exclude": [ 16 | "second.js" 17 | ], 18 | "excludeAfterRemap": true 19 | }, 20 | "devDependencies": { 21 | "@babel/core": "7.9.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/merge-coverage/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-merge-coverage", 3 | "description": "Merge partial code coverage reports", 4 | "scripts": { 5 | "cy:open": "../../node_modules/.bin/cypress open", 6 | "cy:run1": "npm run clean && rm -rf cc1 && ../../node_modules/.bin/cypress run --spec cypress/e2e/add.cy.js && mv coverage cc1", 7 | "cy:run2": "npm run clean && rm -rf cc2 && ../../node_modules/.bin/cypress run --spec cypress/e2e/sub.cy.js && mv coverage cc2", 8 | "check:covered": "../../node_modules/.bin/check-coverage --from coverage/coverage-final.json math.js", 9 | "check:extras": "../../node_modules/.bin/only-covered --from coverage/coverage-final.json math.js", 10 | "check": "npm run check:covered && npm run check:extras", 11 | "clean": "rm -rf .nyc_output && rm -rf coverage", 12 | "merge": "DEBUG=code-coverage ../../bin/cc-merge.js .", 13 | "report": "../../node_modules/.bin/nyc report" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/placeholders/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-placeholders", 3 | "description": "Fills placeholder values", 4 | "private": true, 5 | "scripts": { 6 | "cy:open": "../../node_modules/.bin/cypress open", 7 | "cy:run": "../../node_modules/.bin/cypress run", 8 | "report": "../../node_modules/.bin/nyc report", 9 | "clean": "rm -rf .nyc_output || true", 10 | "test:a": "../../node_modules/.bin/cypress run --spec cypress/integration/spec-a.js", 11 | "test:b": "../../node_modules/.bin/cypress run --spec cypress/integration/spec-b.js", 12 | "check:covered": "../../node_modules/.bin/check-coverage src/a.js src/b.js", 13 | "check:extras": "../../node_modules/.bin/only-covered --from coverage/coverage-final.json src/a.js src/b.js", 14 | "check": "npm run check:covered && npm run check:extras" 15 | }, 16 | "nyc": { 17 | "all": true, 18 | "include": "src/*.js" 19 | }, 20 | "devDependencies": { 21 | "@babel/core": "7.9.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/multiple-files/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-multiple-files", 3 | "description": "Multiple specs", 4 | "scripts": { 5 | "start": "../../node_modules/.bin/parcel serve index.html", 6 | "cy:open": "../../node_modules/.bin/cypress open", 7 | "cy:run": "../../node_modules/.bin/cypress run", 8 | "dev": "../../node_modules/.bin/start-test 1234 cy:open", 9 | "e2e": "../../node_modules/.bin/start-test 1234 cy:run", 10 | "report": "../../node_modules/.bin/nyc report --text", 11 | "check:covered": "../../node_modules/.bin/check-coverage first.js second.js third.js", 12 | "check:extras": "../../node_modules/.bin/only-covered --from coverage/coverage-final.json first.js second.js third.js", 13 | "check": "npm run check:covered && npm run check:extras" 14 | }, 15 | "nyc": { 16 | "all": true, 17 | "include": [ 18 | "src" 19 | ], 20 | "excludeAfterRemap": true 21 | }, 22 | "devDependencies": { 23 | "@babel/core": "7.9.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/badges.yml: -------------------------------------------------------------------------------- 1 | name: badges 2 | on: 3 | push: 4 | # update README badge only if the README file changes 5 | # or if the package.json file changes, or this file changes 6 | branches: 7 | - main 8 | paths: 9 | - README.md 10 | - package.json 11 | - .github/workflows/badges.yml 12 | schedule: 13 | # update badges every night 14 | # because we have a few badges that are linked 15 | # to the external repositories 16 | - cron: '0 3 * * *' 17 | 18 | jobs: 19 | badges: 20 | name: Badges 21 | runs-on: ubuntu-20.04 22 | steps: 23 | - name: Checkout 🛎 24 | uses: actions/checkout@v4 25 | 26 | - name: Update version badges 🏷 27 | run: npx -p dependency-version-badge update-badge cypress 28 | 29 | - name: Commit any changed files 💾 30 | uses: stefanzweifel/git-auto-commit-action@v4 31 | with: 32 | commit_message: Updated badges 33 | branch: main 34 | file_pattern: README.md 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ## MIT License 2 | 3 | Copyright (c) 2019 Cypress.io https://www.cypress.io, 2022 Gleb Bahmutov 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /examples/component/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-component-spec", 3 | "description": "Only running a single component spec", 4 | "scripts": { 5 | "postinstall": "mkdir -p node_modules/code-coverage && cp cc.json node_modules/code-coverage/package.json", 6 | "cy:open": "../../node_modules/.bin/cypress open --component", 7 | "cy:run": "../../node_modules/.bin/cypress run --component", 8 | "check:covered": "../../node_modules/.bin/check-coverage Hello.jsx calc.js", 9 | "check:extras": "../../node_modules/.bin/only-covered Hello.jsx calc.js", 10 | "check": "npm run check:covered && npm run check:extras", 11 | "coverage:report": "../../node_modules/.bin/nyc report", 12 | "clean": "rm -rf .nyc_output coverage" 13 | }, 14 | "devDependencies": { 15 | "react": "^18.2.0", 16 | "react-dom": "^18.2.0", 17 | "react-scripts": "^5.0.1" 18 | }, 19 | "browserslist": { 20 | "production": [ 21 | ">0.2%", 22 | "not dead", 23 | "not op_mini all", 24 | "ie 11" 25 | ], 26 | "development": [ 27 | "last 1 chrome version", 28 | "last 1 firefox version", 29 | "last 1 safari version", 30 | "ie 11" 31 | ] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /cypress/README.md: -------------------------------------------------------------------------------- 1 | # Cypress.io end-to-end tests 2 | 3 | [Cypress.io](https://www.cypress.io) is an open source, MIT licensed end-to-end test runner 4 | 5 | ## Folder structure 6 | 7 | These folders hold end-to-end tests and supporting files for the Cypress Test Runner. 8 | 9 | - [fixtures](fixtures) holds optional JSON data for mocking, [read more](https://on.cypress.io/fixture) 10 | - [integration](integration) holds the actual test files, [read more](https://on.cypress.io/writing-and-organizing-tests) 11 | - [plugins](plugins) allow you to customize how tests are loaded, [read more](https://on.cypress.io/plugins) 12 | - [support](support) file runs before all tests and is a great place to write or load additional custom commands, [read more](https://on.cypress.io/writing-and-organizing-tests#Support-file) 13 | 14 | ## `cypress.json` file 15 | 16 | You can configure project options in the [../cypress.json](../cypress.json) file, see [Cypress configuration doc](https://on.cypress.io/configuration). 17 | 18 | ## More information 19 | 20 | - [https://github.com/cypress.io/cypress](https://github.com/cypress.io/cypress) 21 | - [https://docs.cypress.io/](https://docs.cypress.io/) 22 | - [Writing your first Cypress test](http://on.cypress.io/intro) 23 | -------------------------------------------------------------------------------- /examples/ts-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-before-each-visit", 3 | "description": "Getting code coverage when cy.visit is used in beforeEach hook", 4 | "devDependencies": { 5 | "@babel/core": "7.9.0" 6 | }, 7 | "scripts": { 8 | "start": "../../node_modules/.bin/parcel serve index.html", 9 | "build": "../../node_modules/.bin/parcel build index.html", 10 | "serve": "../../node_modules/.bin/serve dist", 11 | "cy:open": "../../node_modules/.bin/cypress open", 12 | "cy:run": "../../node_modules/.bin/cypress run", 13 | "coverage": "../../node_modules/.bin/nyc report --report-dir ./coverage --temp-dir .nyc_output --reporter=lcov", 14 | "coverage:check": "../../node_modules/.bin/nyc report --check-coverage true --lines 100", 15 | "dev": "../../node_modules/.bin/start-test 1234 cy:open", 16 | "dev:dist": "CYPRESS_baseUrl=http://localhost:5000 ../../node_modules/.bin/start-test serve 5000 cy:open", 17 | "e2e": "../../node_modules/.bin/start-test 1234 cy:run", 18 | "check:covered": "../../node_modules/.bin/check-coverage main.ts calc.ts", 19 | "check:extras": "../../node_modules/.bin/only-covered --from coverage/coverage-final.json main.ts calc.ts", 20 | "check": "npm run check:covered && npm run check:extras" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/unit-tests-ts/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | // using browserify 2 | const browserify = require('@cypress/browserify-preprocessor') 3 | 4 | module.exports = (on, config) => { 5 | require('../../../../task')(on, config) 6 | 7 | const options = browserify.defaultOptions 8 | // options.browserifyOptions.transform[1][1].babelrc = true 9 | options.browserifyOptions.extensions.push('.ts') 10 | // options.browserifyOptions.transform.push([ 11 | // require.resolve('browserify-istanbul'), 12 | // {} 13 | // ]) 14 | options.typescript = require.resolve('typescript') 15 | // on('file:preprocessor', require('../../../../use-babelrc')) 16 | console.log('browserify options') 17 | console.log(JSON.stringify(options, null, 2)) 18 | 19 | on('file:preprocessor', browserify(options)) 20 | return config 21 | } 22 | 23 | // using webpack 24 | /// 25 | // const webpack = require('@cypress/webpack-preprocessor') 26 | 27 | // /** 28 | // * @type {Cypress.PluginConfig} 29 | // */ 30 | // module.exports = (on, config) => { 31 | // const options = { 32 | // // use the same Webpack options to bundle spec files as your app does "normally" 33 | // // which should instrument the spec files in this project 34 | // webpackOptions: require('../../webpack.config'), 35 | // watchOptions: {} 36 | // } 37 | // on('file:preprocessor', webpack(options)) 38 | 39 | // require('../../../../task')(on, config) 40 | // return config 41 | // } 42 | -------------------------------------------------------------------------------- /examples/docker-paths/README.md: -------------------------------------------------------------------------------- 1 | # example-docker-paths 2 | 3 | In this example, the source files are "instrumented" as if they were instrumented inside a Docker container. Still, Cypress code coverage plugin should find the matching current folder where same files exist and update `.nyc_output/out.json` file before generating reports. 4 | 5 | Source files from `app` folder were instrumented into `dist` folder with command 6 | 7 | ```shell 8 | $ npx nyc instrument app dist 9 | ``` 10 | 11 | Then the `index.html` file was copied into `dist` folder. 12 | 13 | Then the source paths in [dist/main.js](dist/main.js) and [dist/second.js](dist/second.js) were changed to non-existent prefix folder `/var/www/test/site`. 14 | 15 | When Cypress runs, the `.nyc_output/out.json` is updated, so the path is valid local path like: 16 | 17 | ``` 18 | { 19 | "/var/www/test/site/app/main.js": { 20 | "path": "/Users/gleb/git/code-coverage/examples/docker-paths/app/main.js", 21 | "statementMap": { 22 | ... 23 | ``` 24 | 25 | And the report has valid HTML with sources 26 | 27 | ![All files](images/files.png) 28 | 29 | ![Single file](images/file.png) 30 | 31 | **Note:** remember to remove existing `.nyc_output` folder if running Cypress in non-interactive mode `rm -rf .nyc_output/`. 32 | 33 | When running with [debug logs](https://github.com/cypress-io/code-coverage#debugging) you should see messages: 34 | 35 | ``` 36 | found common folder /var/www/test/site that matches 37 | current working directory /Users/gleb/git/code-coverage/examples/docker-paths 38 | ``` 39 | -------------------------------------------------------------------------------- /examples/component/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | component: { 5 | framework: 'react-scripts', 6 | bundler: 'webpack', 7 | fixturesFolder: false, 8 | video: false, 9 | }, 10 | 11 | component: { 12 | viewportHeight: 100, 13 | viewportWidth: 100, 14 | fixturesFolder: false, 15 | video: false, 16 | // We've imported your old cypress plugins here. 17 | // You may want to clean this up later by importing these. 18 | setupNodeEvents(on, config) { 19 | return require('../../plugins')(on, config) 20 | }, 21 | devServer: { 22 | framework: 'create-react-app', 23 | bundler: 'webpack', 24 | webpackConfig: { 25 | mode: 'development', 26 | devtool: false, 27 | module: { 28 | rules: [ 29 | // application and Cypress files are bundled like React components 30 | // and instrumented using the babel-plugin-istanbul 31 | { 32 | test: /\.jsx?$/, 33 | // do not instrument node_modules 34 | // or Cypress component specs 35 | exclude: /node_modules|\.cy\.jsx/, 36 | use: { 37 | loader: 'babel-loader', 38 | options: { 39 | presets: ['@babel/preset-env', '@babel/preset-react'], 40 | plugins: ['istanbul'], 41 | }, 42 | }, 43 | }, 44 | ], 45 | }, 46 | }, 47 | }, 48 | }, 49 | }) 50 | -------------------------------------------------------------------------------- /cypress/e2e/calculator-po.js: -------------------------------------------------------------------------------- 1 | function enterExpression(expression) { 2 | cy.log(`Entering expression "**${expression}**"`) 3 | expression.split('').forEach((char) => { 4 | cy.contains('#buttons button', char, { log: false }).click({ 5 | log: false, 6 | }) 7 | }) 8 | } 9 | 10 | export const CalculatorPage = { 11 | visit() { 12 | cy.visit('public/index.html') 13 | }, 14 | 15 | /** 16 | * @param {string} expression to enter into the calculator, like "1+2+3" 17 | */ 18 | enterExpression, 19 | 20 | /** 21 | * @param {string} expression to be evaluated, like "1+2" 22 | * @param {string} expectedResult the expected result, like "3" 23 | */ 24 | compute(expression, expectedResult) { 25 | enterExpression(expression) 26 | cy.contains('#buttons button', '=', { log: false }).click({ 27 | log: false, 28 | }) 29 | cy.get('#display').should('have.text', expectedResult) 30 | return this 31 | }, 32 | 33 | clear() { 34 | cy.log('**clearing the calculator**') 35 | cy.contains('#buttons button', 'C', { log: false }).click({ 36 | log: false, 37 | }) 38 | return this 39 | }, 40 | 41 | /** 42 | * @param {string[]} items list of history items to check 43 | */ 44 | checkHistory(...items) { 45 | cy.log(`**checking history with ${items.length} items**`) 46 | cy.get('#history li', { log: false }).should('have.length', items.length) 47 | items.forEach((item, k) => { 48 | cy.get('#history li', { log: false }) 49 | .eq(k, { log: false }) 50 | .should('have.text', item) 51 | }) 52 | }, 53 | } 54 | -------------------------------------------------------------------------------- /cypress/calculator/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | TDD Calculator 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | 35 | 36 | 37 | 38 | 41 |
42 |
43 |
44 |
45 |
    46 |
    47 |
    48 |
    49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: push 3 | permissions: 4 | contents: write 5 | issues: write 6 | pull-requests: write 7 | jobs: 8 | test: 9 | runs-on: ubuntu-22.04 10 | steps: 11 | - name: Checkout 🛎 12 | uses: actions/checkout@v4 13 | 14 | - name: Install dependencies 📦 15 | # https://github.com/cypress-io/github-action 16 | uses: cypress-io/github-action@v6 17 | with: 18 | runTests: false 19 | 20 | - name: Check types 🧾 21 | run: npm run types 22 | 23 | - name: Catch it.only 🫴 24 | run: npm run stop-only 25 | 26 | - name: Run tests 🧪 27 | uses: cypress-io/github-action@v6 28 | with: 29 | install: false 30 | start: npm start 31 | 32 | test-merge-coverage: 33 | runs-on: ubuntu-20.04 34 | env: 35 | DEBUG: code-coverage 36 | steps: 37 | - name: Checkout 🛎 38 | uses: actions/checkout@v4 39 | 40 | - name: Install dependencies 🧪 41 | # https://github.com/cypress-io/github-action 42 | uses: cypress-io/github-action@v6 43 | with: 44 | runTests: false 45 | 46 | - name: Merge coverage tests 47 | working-directory: examples/merge-coverage 48 | run: | 49 | npm run cy:run1 50 | npm run cy:run2 51 | npm run merge 52 | 53 | release: 54 | needs: [test, test-merge-coverage] 55 | runs-on: ubuntu-22.04 56 | if: github.ref == 'refs/heads/main' 57 | steps: 58 | - name: Checkout 🛎 59 | uses: actions/checkout@v4 60 | 61 | - name: Install only the semantic release 📦 62 | run: npm install semantic-release 63 | 64 | - name: Semantic Release 🚀 65 | uses: cycjimmy/semantic-release-action@v4 66 | with: 67 | branch: main 68 | env: 69 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 70 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 71 | -------------------------------------------------------------------------------- /cypress/e2e/spec.cy.js: -------------------------------------------------------------------------------- 1 | // enables intelligent code completion for Cypress commands 2 | // https://on.cypress.io/intelligent-code-completion 3 | /// 4 | 5 | import { add } from '../unit' 6 | const { fixSourcePaths } = require('../../support-utils') 7 | 8 | context('Page test', () => { 9 | beforeEach(() => { 10 | cy.visit('/', { 11 | onBeforeLoad(win) { 12 | cy.spy(win.console, 'log').as('log') 13 | } 14 | }) 15 | }) 16 | 17 | it('logs names', function() { 18 | cy.get('@log') 19 | .should('have.been.calledOnce') 20 | .should('have.been.calledWith', 'just names', ['joe', 'mary']) 21 | }) 22 | 23 | it('loads About page', () => { 24 | cy.contains('About').click() 25 | cy.url().should('match', /\/about/) 26 | cy.contains('h2', 'About') 27 | cy.contains('Est. 2019') 28 | }) 29 | }) 30 | 31 | context('Unit tests', () => { 32 | it('adds numbers', () => { 33 | expect(add(2, 3)).to.equal(5) 34 | }) 35 | 36 | it('concatenates strings', () => { 37 | expect(add('foo', 'Bar')).to.equal('fooBar') 38 | }) 39 | 40 | it('fixes webpack loader source-map pathes', () => { 41 | const coverage = { 42 | '/absolute/src/component.vue': { 43 | path: '/absolute/src/component.vue', 44 | inputSourceMap: { 45 | sources: [ 46 | '/folder/node_modules/cache-loader/dist/cjs.js??ref--0-0!/folder/node_modules/vue-loader/lib/index.js??vue-loader-options!component.vue?vue&type=script&lang=ts&', 47 | 'otherFile.js' 48 | ], 49 | sourceRoot: 'src' 50 | } 51 | }, 52 | '/folder/module-without-sourcemap.js': { 53 | path: '/folder/module-without-sourcemap.js' 54 | } 55 | } 56 | 57 | fixSourcePaths(coverage) 58 | 59 | expect(coverage).to.deep.eq({ 60 | '/absolute/src/component.vue': { 61 | path: '/absolute/src/component.vue', 62 | inputSourceMap: { 63 | sources: ['/absolute/src/component.vue', 'otherFile.js'], 64 | sourceRoot: '' 65 | } 66 | }, 67 | '/folder/module-without-sourcemap.js': { 68 | path: '/folder/module-without-sourcemap.js' 69 | } 70 | }) 71 | }) 72 | }) 73 | -------------------------------------------------------------------------------- /plugin.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | require('console.table') 3 | const { getNycReportFilename } = require('./task-utils') 4 | const { existsSync } = require('fs') 5 | const NYC = require('nyc') 6 | const debug = require('debug')('code-coverage') 7 | const { reportCodeCoverageGHACallback } = require('./src/utils') 8 | const { isPluginDisabled } = require('./common-utils') 9 | 10 | const nycFilename = getNycReportFilename(process.cwd()) 11 | 12 | function registerCodeCoveragePlugin(on, config) { 13 | require('./task')(on, config) 14 | 15 | // the user can report the code coverage after each spec 16 | // reportAfterEachSpec: false = disabled reporting 17 | // reportAfterEachSpec: true = enabled reporting, default reporter 18 | // reportAfterEachSpec: 'text' = enabled "text" code coverage reporter 19 | // typical values: 'text-summary', 'text' 20 | let reportAfterEachSpec = 'text-summary' 21 | let shouldReport = !isPluginDisabled(config.env) 22 | if ( 23 | config.env && 24 | typeof config.env.coverage === 'object' && 25 | 'reportAfterEachSpec' in config.env.coverage 26 | ) { 27 | if (config.env.coverage.reportAfterEachSpec === false) { 28 | shouldReport = false 29 | } else if (config.env.coverage.reportAfterEachSpec === true) { 30 | // use the default code coverage reporter 31 | } else { 32 | reportAfterEachSpec = config.env.coverage.reportAfterEachSpec 33 | debug('') 34 | } 35 | } 36 | 37 | debug('should report? %s', shouldReport) 38 | if (shouldReport) { 39 | const nyc = new NYC({ 40 | cwd: process.cwd(), 41 | reporter: reportAfterEachSpec, 42 | }) 43 | on('after:spec', (t) => { 44 | console.log('code coverage after spec %s', t.relative) 45 | if (existsSync(nycFilename)) { 46 | return nyc.report() 47 | } else { 48 | console.warn('Could not find coverage file %s', nycFilename) 49 | } 50 | }) 51 | 52 | if (process.env.GITHUB_ACTIONS) { 53 | debug('will report code coverage on GitHub Actions') 54 | on('after:run', reportCodeCoverageGHACallback) 55 | } 56 | } 57 | 58 | // IMPORTANT to return the config object 59 | // with the any changed environment variables 60 | return config 61 | } 62 | 63 | module.exports = registerCodeCoveragePlugin 64 | -------------------------------------------------------------------------------- /bin/cc-merge.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const debug = require('debug')('code-coverage') 4 | 5 | // finds all "coverage-final.json" files in the give folder 6 | // and merges them into a single "cc-merged/out.json" file 7 | // Use: 8 | // npx cc-merge 9 | const topCoverageFolder = process.argv[2] 10 | if (!topCoverageFolder) { 11 | console.error('use: npx cc-merge ') 12 | process.exit(1) 13 | } 14 | 15 | const coverageFilename = 'coverage-final.json' 16 | debug('looking for %s files in %s', coverageFilename, topCoverageFolder) 17 | 18 | const fs = require('fs') 19 | const rimraf = require('rimraf') 20 | const path = require('path') 21 | const globby = require('globby') 22 | const allFiles = globby.sync(`**/${coverageFilename}`, { 23 | absolute: true, 24 | cwd: topCoverageFolder, 25 | }) 26 | debug('found %d coverage files', allFiles.length) 27 | debug(allFiles.join(',')) 28 | 29 | if (!allFiles.length) { 30 | console.error( 31 | 'Could not find any %s files in the folder %s', 32 | coverageFilename, 33 | topCoverageFolder, 34 | ) 35 | process.exit(1) 36 | } 37 | 38 | const nycOutputFolder = '.nyc_output' 39 | if (fs.existsSync(nycOutputFolder)) { 40 | debug('deleting the existing folder %s', nycOutputFolder) 41 | rimraf.nativeSync(nycOutputFolder) 42 | } 43 | 44 | if (!fs.existsSync(nycOutputFolder)) { 45 | debug('creating folder %s', nycOutputFolder) 46 | fs.mkdirSync(nycOutputFolder) 47 | } 48 | allFiles.forEach((filename, k) => { 49 | const outputFilename = path.join(nycOutputFolder, `c-${k + 1}.json`) 50 | fs.copyFileSync(filename, outputFilename) 51 | debug('%d: copied %s to %s', k + 1, filename, outputFilename) 52 | }) 53 | 54 | const { getNycOptions } = require('../task-utils') 55 | const { reportCodeCoverageGHA } = require('../src/utils') 56 | 57 | const processWorkingDirectory = process.cwd() 58 | const nycReportOptions = getNycOptions(processWorkingDirectory) 59 | debug('calling NYC reporter with options %o', nycReportOptions) 60 | debug('current working directory is %s', processWorkingDirectory) 61 | const NYC = require('nyc') 62 | const nyc = new NYC(nycReportOptions) 63 | 64 | nyc.report().then(() => { 65 | if (process.env.GITHUB_ACTIONS) { 66 | debug('will report combined code coverage on GitHub Actions') 67 | reportCodeCoverageGHA('Combined code coverage') 68 | } 69 | }) 70 | -------------------------------------------------------------------------------- /common-utils.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | function stringToArray(prop, obj) { 3 | if (typeof obj[prop] === 'string') { 4 | obj[prop] = [obj[prop]] 5 | } 6 | 7 | return obj 8 | } 9 | 10 | function combineNycOptions(...options) { 11 | // last option wins 12 | const nycOptions = Object.assign({}, ...options) 13 | 14 | // normalize string and [string] props 15 | stringToArray('reporter', nycOptions) 16 | stringToArray('extension', nycOptions) 17 | stringToArray('exclude', nycOptions) 18 | 19 | return nycOptions 20 | } 21 | 22 | const defaultNycOptions = { 23 | 'report-dir': './coverage', 24 | reporter: ['lcov', 'clover', 'json', 'json-summary'], 25 | extension: ['.js', '.cjs', '.mjs', '.ts', '.tsx', '.jsx'], 26 | excludeAfterRemap: false, 27 | } 28 | 29 | /** 30 | * Returns an object with placeholder properties for files we 31 | * do not have coverage yet. The result can go into the coverage object 32 | * 33 | * @param {string} fullPath Filename 34 | */ 35 | const fileCoveragePlaceholder = (fullPath) => { 36 | return { 37 | path: fullPath, 38 | statementMap: {}, 39 | fnMap: {}, 40 | branchMap: {}, 41 | s: {}, 42 | f: {}, 43 | b: {}, 44 | } 45 | } 46 | 47 | const isPlaceholder = (entry) => { 48 | // when the file has been instrumented, its entry has "hash" property 49 | return !('hash' in entry) 50 | } 51 | 52 | /** 53 | * Given a coverage object with potential placeholder entries 54 | * inserted instead of covered files, removes them. Modifies the object in place 55 | */ 56 | const removePlaceholders = (coverage) => { 57 | Object.keys(coverage).forEach((key) => { 58 | if (isPlaceholder(coverage[key])) { 59 | delete coverage[key] 60 | } 61 | }) 62 | } 63 | 64 | /** 65 | * Returns true if the user disabled the plugin using the env object. 66 | */ 67 | function isPluginDisabled(cyEnv) { 68 | if (cyEnv.coverage === false) { 69 | return true 70 | } 71 | if (typeof cyEnv.coverage === 'object') { 72 | // the user explicitly disabled the plugin 73 | // be kind and accept both "disable" and "disabled" options 74 | return cyEnv.coverage.disable === true || cyEnv.coverage.disabled === true 75 | } 76 | 77 | // by default the plugin is enabled 78 | return false 79 | } 80 | 81 | module.exports = { 82 | combineNycOptions, 83 | defaultNycOptions, 84 | fileCoveragePlaceholder, 85 | removePlaceholders, 86 | isPluginDisabled, 87 | } 88 | -------------------------------------------------------------------------------- /cypress/calculator/styles.css: -------------------------------------------------------------------------------- 1 | /* calculator styles */ 2 | 3 | body { 4 | margin: 0; 5 | padding: 0; 6 | display: flex; 7 | justify-content: center; 8 | align-items: center; 9 | height: 100vh; 10 | background-color: hsl(0, 0%, 95%); 11 | } 12 | 13 | #calculator { 14 | overflow: hidden; 15 | font-family: Arial, sans-serif; 16 | background-color: hsl(0, 0%, 15%); 17 | border-radius: 15px; 18 | display: flex; 19 | flex-direction: row; 20 | } 21 | 22 | #display { 23 | width: 100%; 24 | padding: 20px; 25 | height: 80px; 26 | font-size: 80px; 27 | text-align: left; 28 | border: none; 29 | background-color: hsl(0, 0%, 20%); 30 | color: white; 31 | /* color: hsl(0, 0%, 30%); */ 32 | } 33 | 34 | #buttons { 35 | /* use CSS grid to place all buttons into 4 columns */ 36 | display: grid; 37 | grid-template-columns: repeat(4, 1fr); 38 | /* use 10 pixel gap between the items */ 39 | /* and put some padding around the buttons */ 40 | gap: 10px; 41 | padding: 25px; 42 | } 43 | 44 | button { 45 | /* make each button a circle with 100px diameter */ 46 | width: 100px; 47 | height: 100px; 48 | border-radius: 50px; 49 | border: none; 50 | background-color: hsl(0, 0%, 30%); 51 | color: white; 52 | font-size: 3rem; 53 | font-weight: bold; 54 | cursor: pointer; 55 | } 56 | 57 | button:hover { 58 | /* the button background becomes slightly lighter on hover */ 59 | background-color: hsl(0, 0%, 40%); 60 | } 61 | 62 | /* active state for all buttons */ 63 | button:active { 64 | background-color: hsl(0, 0%, 60%); 65 | } 66 | .operator-btn:active { 67 | background-color: hsl(35, 100%, 85%); 68 | } 69 | 70 | .operator-btn { 71 | background-color: hsl(35, 100%, 55%); 72 | color: hsl(0, 0%, 20%); 73 | } 74 | 75 | .operator-btn:hover { 76 | /* the orange button background becomes slightly lighter on hover */ 77 | background-color: hsl(35, 100%, 65%); 78 | } 79 | 80 | #right-column { 81 | width: 500px; 82 | background-color: hsl(0, 0%, 35%); 83 | } 84 | 85 | #history { 86 | width: 100%; 87 | height: 95vh; 88 | min-height: 100px; 89 | overflow-y: scroll; 90 | display: flex; 91 | flex-direction: column; 92 | font-size: 3rem; 93 | text-align: right; 94 | } 95 | 96 | #history-list { 97 | padding-right: 30px; 98 | } 99 | 100 | #history li { 101 | list-style-type: none; 102 | padding: 10px; 103 | color: white; 104 | margin: 5px; 105 | } 106 | 107 | @media screen and (max-width: 500px) { 108 | /* hide the right column element on smaller screens */ 109 | #right-column { 110 | display: none; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /cypress/e2e/combine.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | const { combineNycOptions, defaultNycOptions } = require('../../common-utils') 3 | describe('Combine NYC options', () => { 4 | it('overrides defaults', () => { 5 | const pkgNycOptions = { 6 | extends: '@istanbuljs/nyc-config-typescript', 7 | all: true 8 | } 9 | const combined = combineNycOptions(defaultNycOptions, pkgNycOptions) 10 | cy.wrap(combined).should('deep.equal', { 11 | extends: '@istanbuljs/nyc-config-typescript', 12 | all: true, 13 | 'report-dir': './coverage', 14 | reporter: ['lcov', 'clover', 'json', 'json-summary'], 15 | extension: ['.js', '.cjs', '.mjs', '.ts', '.tsx', '.jsx'], 16 | excludeAfterRemap: false 17 | }) 18 | }) 19 | 20 | it('allows to specify reporter, but changes to array', () => { 21 | const pkgNycOptions = { 22 | reporter: 'text' 23 | } 24 | const combined = combineNycOptions(defaultNycOptions, pkgNycOptions) 25 | cy.wrap(combined).should('deep.equal', { 26 | 'report-dir': './coverage', 27 | reporter: ['text'], 28 | extension: ['.js', '.cjs', '.mjs', '.ts', '.tsx', '.jsx'], 29 | excludeAfterRemap: false 30 | }) 31 | }) 32 | 33 | it('combines multiple options', () => { 34 | const pkgNycOptions = { 35 | all: true, 36 | extension: '.js' 37 | } 38 | const nycrc = { 39 | include: ['foo.js'] 40 | } 41 | const nycrcJson = { 42 | exclude: ['bar.js'], 43 | reporter: ['json'] 44 | } 45 | const nycConfig = { 46 | 'report-dir': './report' 47 | } 48 | const combined = combineNycOptions( 49 | defaultNycOptions, 50 | nycrc, 51 | nycrcJson, 52 | nycConfig, 53 | pkgNycOptions 54 | ) 55 | cy.wrap(combined).should('deep.equal', { 56 | all: true, 57 | 'report-dir': './report', 58 | reporter: ['json'], 59 | extension: ['.js'], 60 | excludeAfterRemap: false, 61 | include: ['foo.js'], 62 | exclude: ['bar.js'] 63 | }) 64 | }) 65 | 66 | it('converts exclude to array', () => { 67 | // https://github.com/cypress-io/code-coverage/issues/248 68 | const pkgNycOptions = { 69 | all: true, 70 | extension: '.js' 71 | } 72 | const nycrc = { 73 | include: ['foo.js'] 74 | } 75 | const nycrcJson = { 76 | exclude: 'bar.js', 77 | reporter: ['json'] 78 | } 79 | const combined = combineNycOptions( 80 | defaultNycOptions, 81 | nycrc, 82 | nycrcJson, 83 | pkgNycOptions 84 | ) 85 | cy.wrap(combined).should('deep.equal', { 86 | all: true, 87 | 'report-dir': './coverage', 88 | reporter: ['json'], 89 | extension: ['.js'], 90 | excludeAfterRemap: false, 91 | include: ['foo.js'], 92 | exclude: ['bar.js'] 93 | }) 94 | }) 95 | }) 96 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const debug = require('debug')('code-coverage') 4 | const ghCore = require('@actions/core') 5 | const path = require('path') 6 | const { existsSync, readFileSync } = require('fs') 7 | 8 | function pickCoverageEmoji(percentage) { 9 | if (percentage >= 95) { 10 | return '✅' 11 | } 12 | if (percentage >= 90) { 13 | return '🏆' 14 | } 15 | if (percentage >= 80) { 16 | return '🥇' 17 | } 18 | if (percentage >= 70) { 19 | return '🥈' 20 | } 21 | if (percentage >= 60) { 22 | return '🥉' 23 | } 24 | if (percentage >= 50) { 25 | return '📈' 26 | } 27 | if (percentage >= 40) { 28 | return '⚠️' 29 | } 30 | return '🪫' 31 | } 32 | 33 | function reportCodeCoverageGHA(heading = 'Code coverage') { 34 | if (typeof heading !== 'string') { 35 | debug(heading) 36 | throw new Error('Expected a string heading when reporting CC on GHA') 37 | } 38 | const summaryFilename = path.join('coverage', 'coverage-summary.json') 39 | if (!existsSync(summaryFilename)) { 40 | debug('cannot find summary file %s', summaryFilename) 41 | } else { 42 | const summary = JSON.parse(readFileSync(summaryFilename, 'utf8')) 43 | if (summary.total) { 44 | debug('code coverage summary totals %o', summary.total) 45 | const s = summary.total.statements 46 | const b = summary.total.branches 47 | const f = summary.total.functions 48 | const l = summary.total.lines 49 | const row = [ 50 | String(s.pct), 51 | `${s.covered}/${s.total}`, 52 | String(b.pct), 53 | `${b.covered}/${b.total}`, 54 | String(f.pct), 55 | `${f.covered}/${f.total}`, 56 | String(l.pct), 57 | `${l.covered}/${l.total}`, 58 | ] 59 | debug(row) 60 | 61 | ghCore.summary 62 | .addHeading(heading) 63 | .addTable([ 64 | [ 65 | { data: 'Statements %', header: true }, 66 | { data: pickCoverageEmoji(s.pct), header: true }, 67 | { data: 'Branches %', header: true }, 68 | { data: pickCoverageEmoji(b.pct), header: true }, 69 | { data: 'Functions %', header: true }, 70 | { data: pickCoverageEmoji(f.pct), header: true }, 71 | { data: 'Lines %', header: true }, 72 | { data: pickCoverageEmoji(l.pct), header: true }, 73 | ], 74 | row, 75 | ]) 76 | .addLink( 77 | '@bahmutov/cypress-code-coverage', 78 | 'https://github.com/bahmutov/cypress-code-coverage', 79 | ) 80 | .write() 81 | } else { 82 | debug('could not find totals in %s', summaryFilename) 83 | } 84 | } 85 | } 86 | 87 | function reportCodeCoverageGHACallback() { 88 | // we could use the test run summary passed in "after:run" event 89 | return reportCodeCoverageGHA() 90 | } 91 | 92 | module.exports = { 93 | pickCoverageEmoji, 94 | reportCodeCoverageGHA, 95 | reportCodeCoverageGHACallback, 96 | } 97 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bahmutov/cypress-code-coverage", 3 | "version": "0.0.0-development", 4 | "description": "My version of Cypress code coverage plugin", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "cypress run", 8 | "start": "parcel serve cypress/index.html", 9 | "cy:open": "cypress open", 10 | "dev": "start-test 1234 cy:open", 11 | "semantic-release": "semantic-release", 12 | "test:ci": "start-test 1234", 13 | "report:coverage": "nyc report --reporter=html", 14 | "dev:no:coverage": "start-test 1234 'cypress open --env coverage=false'", 15 | "format": "prettier --write '*.js'", 16 | "format:check": "prettier --check '*.js'", 17 | "check:markdown": "find *.md -exec npx markdown-link-check {} \\;", 18 | "effective:config": "circleci config process .circleci/config.yml | sed /^#/d", 19 | "types": "tsc --noEmit --allowJs *.js cypress/e2e/*.js", 20 | "clean": "rm -rf .nyc_output coverage", 21 | "stop-only": "stop-only --folder cypress/e2e --folder examples --skip node_modules" 22 | }, 23 | "peerDependencies": { 24 | "cypress": ">=10.0.0" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/bahmutov/cypress-code-coverage.git" 29 | }, 30 | "keywords": [ 31 | "cypress", 32 | "istanbul", 33 | "cypress-plugin", 34 | "code", 35 | "coverage" 36 | ], 37 | "author": "Gleb Bahmutov ", 38 | "license": "MIT", 39 | "bugs": { 40 | "url": "https://github.com/bahmutov/cypress-code-coverage/issues" 41 | }, 42 | "homepage": "https://github.com/bahmutov/cypress-code-coverage#readme", 43 | "files": [ 44 | "*.js", 45 | "src", 46 | "middleware", 47 | "bin" 48 | ], 49 | "bin": { 50 | "cc-merge": "bin/cc-merge.js" 51 | }, 52 | "publishConfig": { 53 | "access": "public" 54 | }, 55 | "private": false, 56 | "dependencies": { 57 | "@actions/core": "^1.10.0", 58 | "@cypress/browserify-preprocessor": "3.0.2", 59 | "chalk": "4.1.2", 60 | "console.table": "^0.10.0", 61 | "dayjs": "1.10.7", 62 | "debug": "4.3.7", 63 | "execa": "4.1.0", 64 | "globby": "11.1.0", 65 | "istanbul-lib-coverage": "3.0.0", 66 | "js-yaml": "3.14.1", 67 | "nyc": "17.1.0", 68 | "rimraf": "6.0.1", 69 | "sort-array": "^4.1.5" 70 | }, 71 | "devDependencies": { 72 | "@babel/core": "7.16.0", 73 | "@babel/preset-typescript": "7.16.0", 74 | "@cypress/webpack-preprocessor": "5.10.0", 75 | "babel-loader": "8.2.3", 76 | "babel-plugin-istanbul": "6.1.1", 77 | "browserify-istanbul": "3.0.1", 78 | "check-code-coverage": "1.10.0", 79 | "console-log-div": "0.6.3", 80 | "cypress": "14.2.1", 81 | "express": "4.17.1", 82 | "lodash": "4.17.21", 83 | "markdown-link-check": "3.9.0", 84 | "parcel-bundler": "1.12.5", 85 | "prettier": "2.5.0", 86 | "semantic-release": "^19.0.2", 87 | "serve": "11.3.2", 88 | "start-server-and-test": "1.14.0", 89 | "stop-only": "^3.3.1", 90 | "ts-loader": "8.3.0", 91 | "typescript": "4.5.2", 92 | "webpack": "4.46.0", 93 | "webpack-cli": "3.3.12" 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /cypress/e2e/merge.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | const istanbul = require('istanbul-lib-coverage') 3 | const coverage = require('../fixtures/coverage.json') 4 | const { 5 | fileCoveragePlaceholder, 6 | removePlaceholders 7 | } = require('../../common-utils') 8 | 9 | /** 10 | * Extracts just the data from the coverage map object 11 | * @param {*} cm 12 | */ 13 | const coverageMapToCoverage = (cm) => { 14 | return JSON.parse(JSON.stringify(cm)) 15 | } 16 | 17 | describe('merging coverage', () => { 18 | const filename = '/src/index.js' 19 | 20 | before(() => { 21 | expect(coverage, 'initial coverage has this file').to.have.property( 22 | filename 23 | ) 24 | }) 25 | 26 | it('combines an empty coverage object', () => { 27 | const previous = istanbul.createCoverageMap({}) 28 | const coverageMap = istanbul.createCoverageMap(previous) 29 | coverageMap.merge(Cypress._.cloneDeep(coverage)) 30 | 31 | const merged = coverageMapToCoverage(coverageMap) 32 | 33 | expect(merged, 'merged coverage').to.deep.equal(coverage) 34 | }) 35 | 36 | it('combines the same full coverage twice', () => { 37 | const previous = istanbul.createCoverageMap(Cypress._.cloneDeep(coverage)) 38 | const coverageMap = istanbul.createCoverageMap(previous) 39 | coverageMap.merge(Cypress._.cloneDeep(coverage)) 40 | 41 | const merged = coverageMapToCoverage(coverageMap) 42 | // it is almost the same - only the statement count has been doubled 43 | const expected = Cypress._.cloneDeep(coverage) 44 | expected[filename].s[0] = 2 45 | expect(merged, 'merged coverage').to.deep.equal(expected) 46 | }) 47 | 48 | it('does not merge correctly placeholders', () => { 49 | const coverageWithPlaceHolder = Cypress._.cloneDeep(coverage) 50 | const placeholder = fileCoveragePlaceholder(filename) 51 | coverageWithPlaceHolder[filename] = placeholder 52 | 53 | expect(coverageWithPlaceHolder, 'placeholder').to.deep.equal({ 54 | [filename]: placeholder 55 | }) 56 | 57 | // now lets merge full info 58 | const previous = istanbul.createCoverageMap(coverageWithPlaceHolder) 59 | const coverageMap = istanbul.createCoverageMap(previous) 60 | coverageMap.merge(coverage) 61 | 62 | const merged = coverageMapToCoverage(coverageMap) 63 | const expected = Cypress._.cloneDeep(coverage) 64 | // the merge against the placeholder without valid statement map 65 | // removes the statement map and sets the counter to null 66 | expected[filename].s = { 0: null } 67 | expected[filename].statementMap = {} 68 | // and no hashes :( 69 | delete expected[filename].hash 70 | delete expected[filename]._coverageSchema 71 | expect(merged).to.deep.equal(expected) 72 | }) 73 | 74 | it('removes placeholders', () => { 75 | const inputCoverage = Cypress._.cloneDeep(coverage) 76 | removePlaceholders(inputCoverage) 77 | expect(inputCoverage, 'nothing to remove').to.deep.equal(coverage) 78 | 79 | // add placeholder 80 | const placeholder = fileCoveragePlaceholder(filename) 81 | inputCoverage[filename] = placeholder 82 | 83 | removePlaceholders(inputCoverage) 84 | expect(inputCoverage, 'the placeholder has been removed').to.deep.equal({}) 85 | }) 86 | }) 87 | -------------------------------------------------------------------------------- /cypress/e2e/filtering.cy.js: -------------------------------------------------------------------------------- 1 | const { filterSpecsFromCoverage } = require('../../support-utils') 2 | 3 | describe('filtering specs', () => { 4 | it('filters out the config filename', () => { 5 | const config = cy.stub() 6 | config.withArgs('specPattern').returns([]) 7 | config.withArgs('configFile').returns('/root/path/cypress.config.js') 8 | config.withArgs('projectRoot').returns('/') 9 | 10 | const totalCoverage = { 11 | '/path/to/specA.js': {}, 12 | '/path/to/specB.js': {}, 13 | // the config file should be filtered out 14 | 'cypress.config.js': {}, 15 | } 16 | const result = filterSpecsFromCoverage(totalCoverage, config) 17 | expect(result).to.deep.equal({ 18 | '/path/to/specA.js': {}, 19 | '/path/to/specB.js': {}, 20 | }) 21 | }) 22 | 23 | it('filters list of specs by single string', () => { 24 | const config = cy.stub() 25 | config.withArgs('specPattern').returns(['**/specA.js']) 26 | config.withArgs('configFile').returns('/root/path/cypress.config.js') 27 | config.withArgs('projectRoot').returns('/') 28 | 29 | const totalCoverage = { 30 | '/path/to/specA.js': {}, 31 | '/path/to/specB.js': {}, 32 | } 33 | const result = filterSpecsFromCoverage(totalCoverage, config) 34 | expect(result).to.deep.equal({ 35 | '/path/to/specB.js': {}, 36 | }) 37 | }) 38 | 39 | it('filters list of specs by pattern', () => { 40 | const config = cy.stub() 41 | config.withArgs('specPattern').returns(['**/*B.js']) 42 | config.withArgs('configFile').returns('/root/path/cypress.config.js') 43 | config.withArgs('projectRoot').returns('/') 44 | 45 | const totalCoverage = { 46 | '/path/to/specA.js': {}, 47 | '/path/to/specB.js': {}, 48 | } 49 | const result = filterSpecsFromCoverage(totalCoverage, config) 50 | expect(result).to.deep.equal({ 51 | '/path/to/specA.js': {}, 52 | }) 53 | }) 54 | 55 | it('filters list of specs by pattern and single spec', () => { 56 | const config = cy.stub() 57 | config.withArgs('specPattern').returns(['**/*B.js', '**/specA.js']) 58 | config.withArgs('configFile').returns('/root/path/cypress.config.js') 59 | config.withArgs('projectRoot').returns('/') 60 | 61 | const totalCoverage = { 62 | '/path/to/specA.js': {}, 63 | '/path/to/specB.js': {}, 64 | } 65 | const result = filterSpecsFromCoverage(totalCoverage, config) 66 | expect(result, 'all specs have been filtered out').to.deep.equal({}) 67 | }) 68 | 69 | it('filters list of specs in integration folder', () => { 70 | const config = cy.stub() 71 | config.withArgs('specPattern').returns('**/*.cy.*') 72 | config.withArgs('configFile').returns('/root/path/cypress.config.js') 73 | config.withArgs('projectRoot').returns('/') 74 | 75 | const totalCoverage = { 76 | '/path/to/specA.js': {}, 77 | '/path/to/specB.js': {}, 78 | // these files should be removed 79 | '/path/to/e2e/spec1.cy.js': {}, 80 | '/path/to/e2e/feature/spec2.cy.js': {}, 81 | } 82 | const result = filterSpecsFromCoverage(totalCoverage, config) 83 | expect(result).to.deep.equal({ 84 | '/path/to/specA.js': {}, 85 | '/path/to/specB.js': {}, 86 | }) 87 | }) 88 | }) 89 | -------------------------------------------------------------------------------- /examples/one-spec/main-instrumented.js: -------------------------------------------------------------------------------- 1 | function cov_6k5v991cn() { 2 | var path = 'main.js' 3 | var hash = 'd384017ecd51a8d90283ba0dec593332209519de' 4 | var global = new Function('return this')() 5 | var gcv = '__coverage__' 6 | var coverageData = { 7 | path: 'main.js', 8 | statementMap: { 9 | '0': { 10 | start: { 11 | line: 1, 12 | column: 0 13 | }, 14 | end: { 15 | line: 1, 16 | column: 28 17 | } 18 | }, 19 | '1': { 20 | start: { 21 | line: 1, 22 | column: 23 23 | }, 24 | end: { 25 | line: 1, 26 | column: 28 27 | } 28 | }, 29 | '2': { 30 | start: { 31 | line: 3, 32 | column: 0 33 | }, 34 | end: { 35 | line: 3, 36 | column: 28 37 | } 38 | }, 39 | '3': { 40 | start: { 41 | line: 3, 42 | column: 23 43 | }, 44 | end: { 45 | line: 3, 46 | column: 28 47 | } 48 | } 49 | }, 50 | fnMap: { 51 | '0': { 52 | name: '(anonymous_0)', 53 | decl: { 54 | start: { 55 | line: 1, 56 | column: 13 57 | }, 58 | end: { 59 | line: 1, 60 | column: 14 61 | } 62 | }, 63 | loc: { 64 | start: { 65 | line: 1, 66 | column: 23 67 | }, 68 | end: { 69 | line: 1, 70 | column: 28 71 | } 72 | }, 73 | line: 1 74 | }, 75 | '1': { 76 | name: '(anonymous_1)', 77 | decl: { 78 | start: { 79 | line: 3, 80 | column: 13 81 | }, 82 | end: { 83 | line: 3, 84 | column: 14 85 | } 86 | }, 87 | loc: { 88 | start: { 89 | line: 3, 90 | column: 23 91 | }, 92 | end: { 93 | line: 3, 94 | column: 28 95 | } 96 | }, 97 | line: 3 98 | } 99 | }, 100 | branchMap: {}, 101 | s: { 102 | '0': 0, 103 | '1': 0, 104 | '2': 0, 105 | '3': 0 106 | }, 107 | f: { 108 | '0': 0, 109 | '1': 0 110 | }, 111 | b: {}, 112 | _coverageSchema: '1a1c01bbd47fc00a2c39e90264f33305004495a9', 113 | hash: 'd384017ecd51a8d90283ba0dec593332209519de' 114 | } 115 | var coverage = global[gcv] || (global[gcv] = {}) 116 | 117 | if (!coverage[path] || coverage[path].hash !== hash) { 118 | coverage[path] = coverageData 119 | } 120 | 121 | var actualCoverage = coverage[path] 122 | 123 | cov_6k5v991cn = function() { 124 | return actualCoverage 125 | } 126 | 127 | return actualCoverage 128 | } 129 | 130 | cov_6k5v991cn() 131 | cov_6k5v991cn().s[0]++ 132 | 133 | window.add = (a, b) => { 134 | cov_6k5v991cn().f[0]++ 135 | cov_6k5v991cn().s[1]++ 136 | return a + b 137 | } 138 | 139 | cov_6k5v991cn().s[2]++ 140 | 141 | window.sub = (a, b) => { 142 | cov_6k5v991cn().f[1]++ 143 | cov_6k5v991cn().s[3]++ 144 | return a - b 145 | } 146 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm1haW4uanMiXSwibmFtZXMiOlsid2luZG93IiwiYWRkIiwiYSIsImIiLCJzdWIiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBQSxNQUFNLENBQUNDLEdBQVAsR0FBYSxDQUFDQyxDQUFELEVBQUlDLENBQUosS0FBVTtBQUFBO0FBQUE7QUFBQSxTQUFBRCxDQUFDLEdBQUdDLENBQUo7QUFBSyxDQUE1Qjs7OztBQUVBSCxNQUFNLENBQUNJLEdBQVAsR0FBYSxDQUFDRixDQUFELEVBQUlDLENBQUosS0FBVTtBQUFBO0FBQUE7QUFBQSxTQUFBRCxDQUFDLEdBQUdDLENBQUo7QUFBSyxDQUE1QiIsInNvdXJjZXNDb250ZW50IjpbIndpbmRvdy5hZGQgPSAoYSwgYikgPT4gYSArIGJcblxud2luZG93LnN1YiA9IChhLCBiKSA9PiBhIC0gYlxuIl19 147 | -------------------------------------------------------------------------------- /examples/same-folder/main-instrumented.js: -------------------------------------------------------------------------------- 1 | function cov_6k5v991cn() { 2 | var path = 'main.js' 3 | var hash = 'd384017ecd51a8d90283ba0dec593332209519de' 4 | var global = new Function('return this')() 5 | var gcv = '__coverage__' 6 | var coverageData = { 7 | path: 'main.js', 8 | statementMap: { 9 | '0': { 10 | start: { 11 | line: 1, 12 | column: 0 13 | }, 14 | end: { 15 | line: 1, 16 | column: 28 17 | } 18 | }, 19 | '1': { 20 | start: { 21 | line: 1, 22 | column: 23 23 | }, 24 | end: { 25 | line: 1, 26 | column: 28 27 | } 28 | }, 29 | '2': { 30 | start: { 31 | line: 3, 32 | column: 0 33 | }, 34 | end: { 35 | line: 3, 36 | column: 28 37 | } 38 | }, 39 | '3': { 40 | start: { 41 | line: 3, 42 | column: 23 43 | }, 44 | end: { 45 | line: 3, 46 | column: 28 47 | } 48 | } 49 | }, 50 | fnMap: { 51 | '0': { 52 | name: '(anonymous_0)', 53 | decl: { 54 | start: { 55 | line: 1, 56 | column: 13 57 | }, 58 | end: { 59 | line: 1, 60 | column: 14 61 | } 62 | }, 63 | loc: { 64 | start: { 65 | line: 1, 66 | column: 23 67 | }, 68 | end: { 69 | line: 1, 70 | column: 28 71 | } 72 | }, 73 | line: 1 74 | }, 75 | '1': { 76 | name: '(anonymous_1)', 77 | decl: { 78 | start: { 79 | line: 3, 80 | column: 13 81 | }, 82 | end: { 83 | line: 3, 84 | column: 14 85 | } 86 | }, 87 | loc: { 88 | start: { 89 | line: 3, 90 | column: 23 91 | }, 92 | end: { 93 | line: 3, 94 | column: 28 95 | } 96 | }, 97 | line: 3 98 | } 99 | }, 100 | branchMap: {}, 101 | s: { 102 | '0': 0, 103 | '1': 0, 104 | '2': 0, 105 | '3': 0 106 | }, 107 | f: { 108 | '0': 0, 109 | '1': 0 110 | }, 111 | b: {}, 112 | _coverageSchema: '1a1c01bbd47fc00a2c39e90264f33305004495a9', 113 | hash: 'd384017ecd51a8d90283ba0dec593332209519de' 114 | } 115 | var coverage = global[gcv] || (global[gcv] = {}) 116 | 117 | if (!coverage[path] || coverage[path].hash !== hash) { 118 | coverage[path] = coverageData 119 | } 120 | 121 | var actualCoverage = coverage[path] 122 | 123 | cov_6k5v991cn = function() { 124 | return actualCoverage 125 | } 126 | 127 | return actualCoverage 128 | } 129 | 130 | cov_6k5v991cn() 131 | cov_6k5v991cn().s[0]++ 132 | 133 | window.add = (a, b) => { 134 | cov_6k5v991cn().f[0]++ 135 | cov_6k5v991cn().s[1]++ 136 | return a + b 137 | } 138 | 139 | cov_6k5v991cn().s[2]++ 140 | 141 | window.sub = (a, b) => { 142 | cov_6k5v991cn().f[1]++ 143 | cov_6k5v991cn().s[3]++ 144 | return a - b 145 | } 146 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm1haW4uanMiXSwibmFtZXMiOlsid2luZG93IiwiYWRkIiwiYSIsImIiLCJzdWIiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBQSxNQUFNLENBQUNDLEdBQVAsR0FBYSxDQUFDQyxDQUFELEVBQUlDLENBQUosS0FBVTtBQUFBO0FBQUE7QUFBQSxTQUFBRCxDQUFDLEdBQUdDLENBQUo7QUFBSyxDQUE1Qjs7OztBQUVBSCxNQUFNLENBQUNJLEdBQVAsR0FBYSxDQUFDRixDQUFELEVBQUlDLENBQUosS0FBVTtBQUFBO0FBQUE7QUFBQSxTQUFBRCxDQUFDLEdBQUdDLENBQUo7QUFBSyxDQUE1QiIsInNvdXJjZXNDb250ZW50IjpbIndpbmRvdy5hZGQgPSAoYSwgYikgPT4gYSArIGJcblxud2luZG93LnN1YiA9IChhLCBiKSA9PiBhIC0gYlxuIl19 147 | -------------------------------------------------------------------------------- /examples/before-all-visit/main-instrumented.js: -------------------------------------------------------------------------------- 1 | function cov_6k5v991cn() { 2 | var path = 'main.js' 3 | var hash = 'd384017ecd51a8d90283ba0dec593332209519de' 4 | var global = new Function('return this')() 5 | var gcv = '__coverage__' 6 | var coverageData = { 7 | path: 'main.js', 8 | statementMap: { 9 | '0': { 10 | start: { 11 | line: 1, 12 | column: 0 13 | }, 14 | end: { 15 | line: 1, 16 | column: 28 17 | } 18 | }, 19 | '1': { 20 | start: { 21 | line: 1, 22 | column: 23 23 | }, 24 | end: { 25 | line: 1, 26 | column: 28 27 | } 28 | }, 29 | '2': { 30 | start: { 31 | line: 3, 32 | column: 0 33 | }, 34 | end: { 35 | line: 3, 36 | column: 28 37 | } 38 | }, 39 | '3': { 40 | start: { 41 | line: 3, 42 | column: 23 43 | }, 44 | end: { 45 | line: 3, 46 | column: 28 47 | } 48 | } 49 | }, 50 | fnMap: { 51 | '0': { 52 | name: '(anonymous_0)', 53 | decl: { 54 | start: { 55 | line: 1, 56 | column: 13 57 | }, 58 | end: { 59 | line: 1, 60 | column: 14 61 | } 62 | }, 63 | loc: { 64 | start: { 65 | line: 1, 66 | column: 23 67 | }, 68 | end: { 69 | line: 1, 70 | column: 28 71 | } 72 | }, 73 | line: 1 74 | }, 75 | '1': { 76 | name: '(anonymous_1)', 77 | decl: { 78 | start: { 79 | line: 3, 80 | column: 13 81 | }, 82 | end: { 83 | line: 3, 84 | column: 14 85 | } 86 | }, 87 | loc: { 88 | start: { 89 | line: 3, 90 | column: 23 91 | }, 92 | end: { 93 | line: 3, 94 | column: 28 95 | } 96 | }, 97 | line: 3 98 | } 99 | }, 100 | branchMap: {}, 101 | s: { 102 | '0': 0, 103 | '1': 0, 104 | '2': 0, 105 | '3': 0 106 | }, 107 | f: { 108 | '0': 0, 109 | '1': 0 110 | }, 111 | b: {}, 112 | _coverageSchema: '1a1c01bbd47fc00a2c39e90264f33305004495a9', 113 | hash: 'd384017ecd51a8d90283ba0dec593332209519de' 114 | } 115 | var coverage = global[gcv] || (global[gcv] = {}) 116 | 117 | if (!coverage[path] || coverage[path].hash !== hash) { 118 | coverage[path] = coverageData 119 | } 120 | 121 | var actualCoverage = coverage[path] 122 | 123 | cov_6k5v991cn = function() { 124 | return actualCoverage 125 | } 126 | 127 | return actualCoverage 128 | } 129 | 130 | cov_6k5v991cn() 131 | cov_6k5v991cn().s[0]++ 132 | 133 | window.add = (a, b) => { 134 | cov_6k5v991cn().f[0]++ 135 | cov_6k5v991cn().s[1]++ 136 | return a + b 137 | } 138 | 139 | cov_6k5v991cn().s[2]++ 140 | 141 | window.sub = (a, b) => { 142 | cov_6k5v991cn().f[1]++ 143 | cov_6k5v991cn().s[3]++ 144 | return a - b 145 | } 146 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm1haW4uanMiXSwibmFtZXMiOlsid2luZG93IiwiYWRkIiwiYSIsImIiLCJzdWIiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBQSxNQUFNLENBQUNDLEdBQVAsR0FBYSxDQUFDQyxDQUFELEVBQUlDLENBQUosS0FBVTtBQUFBO0FBQUE7QUFBQSxTQUFBRCxDQUFDLEdBQUdDLENBQUo7QUFBSyxDQUE1Qjs7OztBQUVBSCxNQUFNLENBQUNJLEdBQVAsR0FBYSxDQUFDRixDQUFELEVBQUlDLENBQUosS0FBVTtBQUFBO0FBQUE7QUFBQSxTQUFBRCxDQUFDLEdBQUdDLENBQUo7QUFBSyxDQUE1QiIsInNvdXJjZXNDb250ZW50IjpbIndpbmRvdy5hZGQgPSAoYSwgYikgPT4gYSArIGJcblxud2luZG93LnN1YiA9IChhLCBiKSA9PiBhIC0gYlxuIl19 147 | -------------------------------------------------------------------------------- /examples/before-each-visit/main-instrumented.js: -------------------------------------------------------------------------------- 1 | function cov_6k5v991cn() { 2 | var path = 'main.js' 3 | var hash = 'd384017ecd51a8d90283ba0dec593332209519de' 4 | var global = new Function('return this')() 5 | var gcv = '__coverage__' 6 | var coverageData = { 7 | path: 'main.js', 8 | statementMap: { 9 | '0': { 10 | start: { 11 | line: 1, 12 | column: 0 13 | }, 14 | end: { 15 | line: 1, 16 | column: 28 17 | } 18 | }, 19 | '1': { 20 | start: { 21 | line: 1, 22 | column: 23 23 | }, 24 | end: { 25 | line: 1, 26 | column: 28 27 | } 28 | }, 29 | '2': { 30 | start: { 31 | line: 3, 32 | column: 0 33 | }, 34 | end: { 35 | line: 3, 36 | column: 28 37 | } 38 | }, 39 | '3': { 40 | start: { 41 | line: 3, 42 | column: 23 43 | }, 44 | end: { 45 | line: 3, 46 | column: 28 47 | } 48 | } 49 | }, 50 | fnMap: { 51 | '0': { 52 | name: '(anonymous_0)', 53 | decl: { 54 | start: { 55 | line: 1, 56 | column: 13 57 | }, 58 | end: { 59 | line: 1, 60 | column: 14 61 | } 62 | }, 63 | loc: { 64 | start: { 65 | line: 1, 66 | column: 23 67 | }, 68 | end: { 69 | line: 1, 70 | column: 28 71 | } 72 | }, 73 | line: 1 74 | }, 75 | '1': { 76 | name: '(anonymous_1)', 77 | decl: { 78 | start: { 79 | line: 3, 80 | column: 13 81 | }, 82 | end: { 83 | line: 3, 84 | column: 14 85 | } 86 | }, 87 | loc: { 88 | start: { 89 | line: 3, 90 | column: 23 91 | }, 92 | end: { 93 | line: 3, 94 | column: 28 95 | } 96 | }, 97 | line: 3 98 | } 99 | }, 100 | branchMap: {}, 101 | s: { 102 | '0': 0, 103 | '1': 0, 104 | '2': 0, 105 | '3': 0 106 | }, 107 | f: { 108 | '0': 0, 109 | '1': 0 110 | }, 111 | b: {}, 112 | _coverageSchema: '1a1c01bbd47fc00a2c39e90264f33305004495a9', 113 | hash: 'd384017ecd51a8d90283ba0dec593332209519de' 114 | } 115 | var coverage = global[gcv] || (global[gcv] = {}) 116 | 117 | if (!coverage[path] || coverage[path].hash !== hash) { 118 | coverage[path] = coverageData 119 | } 120 | 121 | var actualCoverage = coverage[path] 122 | 123 | cov_6k5v991cn = function() { 124 | return actualCoverage 125 | } 126 | 127 | return actualCoverage 128 | } 129 | 130 | cov_6k5v991cn() 131 | cov_6k5v991cn().s[0]++ 132 | 133 | window.add = (a, b) => { 134 | cov_6k5v991cn().f[0]++ 135 | cov_6k5v991cn().s[1]++ 136 | return a + b 137 | } 138 | 139 | cov_6k5v991cn().s[2]++ 140 | 141 | window.sub = (a, b) => { 142 | cov_6k5v991cn().f[1]++ 143 | cov_6k5v991cn().s[3]++ 144 | return a - b 145 | } 146 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm1haW4uanMiXSwibmFtZXMiOlsid2luZG93IiwiYWRkIiwiYSIsImIiLCJzdWIiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBQSxNQUFNLENBQUNDLEdBQVAsR0FBYSxDQUFDQyxDQUFELEVBQUlDLENBQUosS0FBVTtBQUFBO0FBQUE7QUFBQSxTQUFBRCxDQUFDLEdBQUdDLENBQUo7QUFBSyxDQUE1Qjs7OztBQUVBSCxNQUFNLENBQUNJLEdBQVAsR0FBYSxDQUFDRixDQUFELEVBQUlDLENBQUosS0FBVTtBQUFBO0FBQUE7QUFBQSxTQUFBRCxDQUFDLEdBQUdDLENBQUo7QUFBSyxDQUE1QiIsInNvdXJjZXNDb250ZW50IjpbIndpbmRvdy5hZGQgPSAoYSwgYikgPT4gYSArIGJcblxud2luZG93LnN1YiA9IChhLCBiKSA9PiBhIC0gYlxuIl19 147 | -------------------------------------------------------------------------------- /examples/fullstack/server/main-instrumented.js: -------------------------------------------------------------------------------- 1 | function cov_6k5v991cn() { 2 | var path = 'main.js' 3 | var hash = 'd384017ecd51a8d90283ba0dec593332209519de' 4 | var global = new Function('return this')() 5 | var gcv = '__coverage__' 6 | var coverageData = { 7 | path: 'main.js', 8 | statementMap: { 9 | '0': { 10 | start: { 11 | line: 1, 12 | column: 0 13 | }, 14 | end: { 15 | line: 1, 16 | column: 28 17 | } 18 | }, 19 | '1': { 20 | start: { 21 | line: 1, 22 | column: 23 23 | }, 24 | end: { 25 | line: 1, 26 | column: 28 27 | } 28 | }, 29 | '2': { 30 | start: { 31 | line: 3, 32 | column: 0 33 | }, 34 | end: { 35 | line: 3, 36 | column: 28 37 | } 38 | }, 39 | '3': { 40 | start: { 41 | line: 3, 42 | column: 23 43 | }, 44 | end: { 45 | line: 3, 46 | column: 28 47 | } 48 | } 49 | }, 50 | fnMap: { 51 | '0': { 52 | name: '(anonymous_0)', 53 | decl: { 54 | start: { 55 | line: 1, 56 | column: 13 57 | }, 58 | end: { 59 | line: 1, 60 | column: 14 61 | } 62 | }, 63 | loc: { 64 | start: { 65 | line: 1, 66 | column: 23 67 | }, 68 | end: { 69 | line: 1, 70 | column: 28 71 | } 72 | }, 73 | line: 1 74 | }, 75 | '1': { 76 | name: '(anonymous_1)', 77 | decl: { 78 | start: { 79 | line: 3, 80 | column: 13 81 | }, 82 | end: { 83 | line: 3, 84 | column: 14 85 | } 86 | }, 87 | loc: { 88 | start: { 89 | line: 3, 90 | column: 23 91 | }, 92 | end: { 93 | line: 3, 94 | column: 28 95 | } 96 | }, 97 | line: 3 98 | } 99 | }, 100 | branchMap: {}, 101 | s: { 102 | '0': 0, 103 | '1': 0, 104 | '2': 0, 105 | '3': 0 106 | }, 107 | f: { 108 | '0': 0, 109 | '1': 0 110 | }, 111 | b: {}, 112 | _coverageSchema: '1a1c01bbd47fc00a2c39e90264f33305004495a9', 113 | hash: 'd384017ecd51a8d90283ba0dec593332209519de' 114 | } 115 | var coverage = global[gcv] || (global[gcv] = {}) 116 | 117 | if (!coverage[path] || coverage[path].hash !== hash) { 118 | coverage[path] = coverageData 119 | } 120 | 121 | var actualCoverage = coverage[path] 122 | 123 | cov_6k5v991cn = function() { 124 | return actualCoverage 125 | } 126 | 127 | return actualCoverage 128 | } 129 | 130 | cov_6k5v991cn() 131 | cov_6k5v991cn().s[0]++ 132 | 133 | window.add = (a, b) => { 134 | cov_6k5v991cn().f[0]++ 135 | cov_6k5v991cn().s[1]++ 136 | return a + b 137 | } 138 | 139 | cov_6k5v991cn().s[2]++ 140 | 141 | window.sub = (a, b) => { 142 | cov_6k5v991cn().f[1]++ 143 | cov_6k5v991cn().s[3]++ 144 | return a - b 145 | } 146 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm1haW4uanMiXSwibmFtZXMiOlsid2luZG93IiwiYWRkIiwiYSIsImIiLCJzdWIiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBQSxNQUFNLENBQUNDLEdBQVAsR0FBYSxDQUFDQyxDQUFELEVBQUlDLENBQUosS0FBVTtBQUFBO0FBQUE7QUFBQSxTQUFBRCxDQUFDLEdBQUdDLENBQUo7QUFBSyxDQUE1Qjs7OztBQUVBSCxNQUFNLENBQUNJLEdBQVAsR0FBYSxDQUFDRixDQUFELEVBQUlDLENBQUosS0FBVTtBQUFBO0FBQUE7QUFBQSxTQUFBRCxDQUFDLEdBQUdDLENBQUo7QUFBSyxDQUE1QiIsInNvdXJjZXNDb250ZW50IjpbIndpbmRvdy5hZGQgPSAoYSwgYikgPT4gYSArIGJcblxud2luZG93LnN1YiA9IChhLCBiKSA9PiBhIC0gYlxuIl19 147 | -------------------------------------------------------------------------------- /examples/use-plugins-and-support/main-instrumented.js: -------------------------------------------------------------------------------- 1 | function cov_6k5v991cn() { 2 | var path = 'main.js' 3 | var hash = 'd384017ecd51a8d90283ba0dec593332209519de' 4 | var global = new Function('return this')() 5 | var gcv = '__coverage__' 6 | var coverageData = { 7 | path: 'main.js', 8 | statementMap: { 9 | '0': { 10 | start: { 11 | line: 1, 12 | column: 0 13 | }, 14 | end: { 15 | line: 1, 16 | column: 28 17 | } 18 | }, 19 | '1': { 20 | start: { 21 | line: 1, 22 | column: 23 23 | }, 24 | end: { 25 | line: 1, 26 | column: 28 27 | } 28 | }, 29 | '2': { 30 | start: { 31 | line: 3, 32 | column: 0 33 | }, 34 | end: { 35 | line: 3, 36 | column: 28 37 | } 38 | }, 39 | '3': { 40 | start: { 41 | line: 3, 42 | column: 23 43 | }, 44 | end: { 45 | line: 3, 46 | column: 28 47 | } 48 | } 49 | }, 50 | fnMap: { 51 | '0': { 52 | name: '(anonymous_0)', 53 | decl: { 54 | start: { 55 | line: 1, 56 | column: 13 57 | }, 58 | end: { 59 | line: 1, 60 | column: 14 61 | } 62 | }, 63 | loc: { 64 | start: { 65 | line: 1, 66 | column: 23 67 | }, 68 | end: { 69 | line: 1, 70 | column: 28 71 | } 72 | }, 73 | line: 1 74 | }, 75 | '1': { 76 | name: '(anonymous_1)', 77 | decl: { 78 | start: { 79 | line: 3, 80 | column: 13 81 | }, 82 | end: { 83 | line: 3, 84 | column: 14 85 | } 86 | }, 87 | loc: { 88 | start: { 89 | line: 3, 90 | column: 23 91 | }, 92 | end: { 93 | line: 3, 94 | column: 28 95 | } 96 | }, 97 | line: 3 98 | } 99 | }, 100 | branchMap: {}, 101 | s: { 102 | '0': 0, 103 | '1': 0, 104 | '2': 0, 105 | '3': 0 106 | }, 107 | f: { 108 | '0': 0, 109 | '1': 0 110 | }, 111 | b: {}, 112 | _coverageSchema: '1a1c01bbd47fc00a2c39e90264f33305004495a9', 113 | hash: 'd384017ecd51a8d90283ba0dec593332209519de' 114 | } 115 | var coverage = global[gcv] || (global[gcv] = {}) 116 | 117 | if (!coverage[path] || coverage[path].hash !== hash) { 118 | coverage[path] = coverageData 119 | } 120 | 121 | var actualCoverage = coverage[path] 122 | 123 | cov_6k5v991cn = function() { 124 | return actualCoverage 125 | } 126 | 127 | return actualCoverage 128 | } 129 | 130 | cov_6k5v991cn() 131 | cov_6k5v991cn().s[0]++ 132 | 133 | window.add = (a, b) => { 134 | cov_6k5v991cn().f[0]++ 135 | cov_6k5v991cn().s[1]++ 136 | return a + b 137 | } 138 | 139 | cov_6k5v991cn().s[2]++ 140 | 141 | window.sub = (a, b) => { 142 | cov_6k5v991cn().f[1]++ 143 | cov_6k5v991cn().s[3]++ 144 | return a - b 145 | } 146 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm1haW4uanMiXSwibmFtZXMiOlsid2luZG93IiwiYWRkIiwiYSIsImIiLCJzdWIiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBQSxNQUFNLENBQUNDLEdBQVAsR0FBYSxDQUFDQyxDQUFELEVBQUlDLENBQUosS0FBVTtBQUFBO0FBQUE7QUFBQSxTQUFBRCxDQUFDLEdBQUdDLENBQUo7QUFBSyxDQUE1Qjs7OztBQUVBSCxNQUFNLENBQUNJLEdBQVAsR0FBYSxDQUFDRixDQUFELEVBQUlDLENBQUosS0FBVTtBQUFBO0FBQUE7QUFBQSxTQUFBRCxDQUFDLEdBQUdDLENBQUo7QUFBSyxDQUE1QiIsInNvdXJjZXNDb250ZW50IjpbIndpbmRvdy5hZGQgPSAoYSwgYikgPT4gYSArIGJcblxud2luZG93LnN1YiA9IChhLCBiKSA9PiBhIC0gYlxuIl19 147 | -------------------------------------------------------------------------------- /support-utils.js: -------------------------------------------------------------------------------- 1 | /// 2 | // @ts-check 3 | // helper functions that are safe to use in the browser 4 | // from support.js file - no file system access 5 | const debug = require('debug')('code-coverage') 6 | 7 | /** 8 | * remove coverage for the spec files themselves, 9 | * only keep "external" application source file coverage. 10 | * Config has keys with absolute path names for each source file 11 | */ 12 | const filterSpecsFromCoverage = (totalCoverage, config = Cypress.config) => { 13 | /** @type {string|string[]} Cypress run-time config has test files string pattern */ 14 | const specPattern = config('specPattern') 15 | const configFilename = config('configFile') 16 | 17 | // test files could be: 18 | // wild card string "**/*.*" (default) 19 | // wild card string "**/*spec.js" 20 | // list of wild card strings or names ["**/*spec.js", "spec-one.js"] 21 | const rootFolder = config('projectRoot') 22 | if (typeof rootFolder === 'undefined') { 23 | throw new Error('Cypress projectRoot folder cannot be undefined') 24 | } 25 | const testFilePatterns = ( 26 | Array.isArray(specPattern) ? specPattern : [specPattern] 27 | ).map((pattern) => { 28 | // we want absolute paths 29 | return rootFolder + '/' + pattern 30 | }) 31 | debug({ specPattern, testFilePatterns, configFilename }) 32 | 33 | const isTestFile = (filename) => { 34 | debug('testing filename', filename) 35 | const matchedPattern = testFilePatterns.some((specPattern) => { 36 | debug('minimatch %s against %s', filename, specPattern) 37 | return Cypress.minimatch(filename, specPattern) 38 | }) 39 | const matchedEndOfPath = testFilePatterns.some((specPattern) => 40 | filename.endsWith(specPattern), 41 | ) 42 | const matchedConfig = configFilename.endsWith(filename) 43 | debug({ matchedPattern, matchedEndOfPath, matchedConfig }) 44 | 45 | return matchedPattern || matchedEndOfPath || matchedConfig 46 | } 47 | 48 | const coverage = Cypress._.omitBy(totalCoverage, (fileCoverage, filename) => 49 | isTestFile(filename), 50 | ) 51 | // debug(Object.keys(coverage)) 52 | 53 | return coverage 54 | } 55 | 56 | /** 57 | * Replace source-map's path by the corresponding absolute file path 58 | * (coverage report wouldn't work with source-map path being relative 59 | * or containing Webpack loaders and query parameters) 60 | */ 61 | function fixSourcePaths(coverage) { 62 | Object.values(coverage).forEach((file) => { 63 | const { path: absolutePath, inputSourceMap } = file 64 | // @ts-ignore 65 | const fileName = /([^\/\\]+)$/.exec(absolutePath)[1] 66 | if (!inputSourceMap || !fileName) return 67 | 68 | if (inputSourceMap.sourceRoot) inputSourceMap.sourceRoot = '' 69 | inputSourceMap.sources = inputSourceMap.sources.map((source) => 70 | source.includes(fileName) ? absolutePath : source, 71 | ) 72 | }) 73 | } 74 | 75 | /** 76 | * by default we do not filter anything from the code coverage object 77 | * if the user gives a list of patters to filter, we filter the coverage object. 78 | * @param {boolean|string|string[]} exclude What to exclude (default files or specific list) 79 | * @param {object} coverage Each key is an absolute filepath 80 | */ 81 | function excludeByUser(exclude, coverage) { 82 | if (!exclude) { 83 | return coverage 84 | } 85 | 86 | debug('excludeByUser config exclude', exclude) 87 | debug(Object.keys(coverage)) 88 | 89 | if (exclude === true) { 90 | // try excluding spec and support files 91 | const withoutSpecs = filterSpecsFromCoverage(coverage) 92 | debug('exclude specs', Object.keys(withoutSpecs)) 93 | const filteredCoverage = filterSupportFilesFromCoverage(withoutSpecs) 94 | debug('exclude true filtered', Object.keys(filteredCoverage)) 95 | return filteredCoverage 96 | } 97 | 98 | const filterOut = Cypress._.isString(exclude) ? [exclude] : exclude 99 | // debug({ filterOut }) 100 | 101 | const filteredCoverage = Cypress._.omitBy( 102 | coverage, 103 | (fileCoverage, filename) => { 104 | return filterOut.some((pattern) => { 105 | if (pattern.includes('*')) { 106 | return Cypress.minimatch(filename, pattern) 107 | } 108 | return filename.endsWith(pattern) 109 | }) 110 | }, 111 | ) 112 | debug('exclude masks filtered', Object.keys(filteredCoverage)) 113 | return filteredCoverage 114 | } 115 | 116 | /** 117 | * Removes support file from the coverage object. 118 | * If there are more files loaded from support folder, also removes them 119 | */ 120 | const filterSupportFilesFromCoverage = (totalCoverage) => { 121 | const supportFile = Cypress.config('supportFile') 122 | 123 | /** @type {string} Cypress run-time config has the support folder string */ 124 | const supportFolder = Cypress.config('supportFolder') 125 | 126 | const isSupportFile = (filename) => filename === supportFile 127 | const isInSupportFolder = (filename) => filename.startsWith(supportFolder) 128 | 129 | const coverage = Cypress._.omitBy( 130 | totalCoverage, 131 | (fileCoverage, filename) => 132 | isSupportFile(filename) || isInSupportFolder(filename), 133 | ) 134 | 135 | return coverage 136 | } 137 | 138 | module.exports = { 139 | fixSourcePaths, 140 | filterSpecsFromCoverage, 141 | filterSupportFilesFromCoverage, 142 | excludeByUser, 143 | } 144 | -------------------------------------------------------------------------------- /cypress/calculator/app.js: -------------------------------------------------------------------------------- 1 | // calculator logic 2 | 3 | import { appendDot } from './utils.js' 4 | 5 | // the current list of history entries 6 | const history = [] 7 | 8 | // on page visit 9 | // load the last expression from the localStorage (if any) 10 | // use the local storage key "calculator_data" 11 | try { 12 | const data = JSON.parse(localStorage.getItem('calculator_data')) 13 | if (data.version === 'v1') { 14 | const lastExpression = data.expression 15 | if (lastExpression) { 16 | document.getElementById('display').innerText = lastExpression 17 | // push the last expression to the history 18 | const historyListElement = 19 | document.getElementById('history-list') 20 | historyListElement.innerHTML = `
  • ${lastExpression}=${lastExpression}
  • ` 21 | history.push(`${lastExpression}=${lastExpression}`) 22 | // save the migrated data into the localStorage 23 | const migratedData = { 24 | version: 'v2', 25 | expression: lastExpression, 26 | history, 27 | } 28 | localStorage.setItem( 29 | 'calculator_data', 30 | JSON.stringify(migratedData), 31 | ) 32 | } 33 | } else if (data.version === 'v2') { 34 | // set the DOM elements based on the data stored in the item 35 | // - expression 36 | // - history items 37 | const lastExpression = data.expression 38 | if (lastExpression) { 39 | document.getElementById('display').innerText = lastExpression 40 | } 41 | if (Array.isArray(data.history)) { 42 | const historyListElement = 43 | document.getElementById('history-list') 44 | historyListElement.innerHTML = data.history 45 | .map((item) => `
  • ${item}
  • `) 46 | .join('\n') 47 | history.length = 0 48 | history.push(...data.history) 49 | } 50 | } 51 | } catch { 52 | // ignore serialization errors 53 | } 54 | 55 | // we probably want to keep around the reference to the 56 | // LI element with ID "history-list" to append new history items 57 | const historyListElement = document.getElementById('history-list') 58 | 59 | /** 60 | * Function that receives a digit to append to the currently displayed text 61 | * @param {number|'+'|'-'|'*'|'/'} digit A single digit to append to the display text 62 | */ 63 | function enterDigit(digit) { 64 | const display = document.getElementById('display') 65 | 66 | if (digit === '.') { 67 | // special logic for adding the "." character 68 | display.innerText = appendDot(display.innerText) 69 | } else { 70 | display.innerText += digit 71 | } 72 | 73 | // store the current expression in the localStorage 74 | const data = { 75 | version: 'v2', 76 | expression: display.innerText, 77 | history, 78 | } 79 | localStorage.setItem('calculator_data', JSON.stringify(data)) 80 | } 81 | 82 | /** 83 | * Compute the current display text as a JavaScript arithmetic expression 84 | * using the `eval` function and replace the display text with the result 85 | * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval 86 | */ 87 | function calculate() { 88 | const display = document.getElementById('display') 89 | const expression = display.innerText 90 | 91 | // check the expression to be a simple numerical expression 92 | // without any extra characters 93 | // we only allow the following characters 94 | // digits, "+", "-", "*", "/", "." 95 | if (!/^[\d\-\+\*\/\.]+?$/.test(expression)) { 96 | display.innerText = 'INVALID' 97 | return 98 | } 99 | 100 | try { 101 | const result = eval(expression) 102 | display.innerText = result 103 | } catch (err) { 104 | display.innerText = 'ERROR' 105 | // after 1 second, put the original 106 | // expression back in the display 107 | setTimeout(() => { 108 | display.innerText = expression 109 | }, 1000) 110 | } 111 | 112 | // append the new expression and result to the history list 113 | const historyItem = `${expression}=${display.innerText}` 114 | historyListElement.innerHTML += `
  • ${historyItem}
  • ` 115 | // store the history in the localStorage 116 | history.push(historyItem) 117 | 118 | // store the current expression in the localStorage 119 | const data = { 120 | version: 'v2', 121 | expression: display.innerText, 122 | history, 123 | } 124 | localStorage.setItem('calculator_data', JSON.stringify(data)) 125 | } 126 | 127 | /** 128 | * Clears the current display text 129 | */ 130 | function clearDisplay() { 131 | const display = document.getElementById('display') 132 | display.innerText = '' 133 | } 134 | 135 | // attach event handlers 136 | // - button calculate should call the calculate function 137 | // - button clear should call the clearDisplay function 138 | // - all digit buttons should call the enterDigit function 139 | document 140 | .querySelector('#buttons button[title=calculate]') 141 | .addEventListener('click', calculate) 142 | document 143 | .querySelector('#buttons button[title="clear display"]') 144 | .addEventListener('click', clearDisplay) 145 | document 146 | .querySelectorAll('#buttons button[title="enter digit"]') 147 | .forEach((button) => { 148 | button.addEventListener('click', (e) => { 149 | // get the character from the event target 150 | const digit = e.target.innerText 151 | enterDigit(digit) 152 | }) 153 | }) 154 | -------------------------------------------------------------------------------- /task.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const istanbul = require('istanbul-lib-coverage') 3 | const sortArray = require('sort-array') 4 | const { join, relative } = require('path') 5 | const { existsSync, mkdirSync, readFileSync, writeFileSync } = require('fs') 6 | const execa = require('execa') 7 | const { 8 | showNycInfo, 9 | resolveRelativePaths, 10 | checkAllPathsNotFound, 11 | tryFindingLocalFiles, 12 | getNycOptions, 13 | includeAllFiles, 14 | getCoverage, 15 | updateSpecCovers, 16 | } = require('./task-utils') 17 | const { fixSourcePaths } = require('./support-utils') 18 | const { removePlaceholders } = require('./common-utils') 19 | 20 | const debug = require('debug')('code-coverage') 21 | 22 | // these are standard folder and file names used by NYC tools 23 | const processWorkingDirectory = process.cwd() 24 | 25 | // there might be custom "nyc" options in the user package.json 26 | // see https://github.com/istanbuljs/nyc#configuring-nyc 27 | // potentially there might be "nyc" options in other configuration files 28 | // it allows, but for now ignore those options 29 | const pkgFilename = join(processWorkingDirectory, 'package.json') 30 | const pkg = existsSync(pkgFilename) 31 | ? JSON.parse(readFileSync(pkgFilename, 'utf8')) 32 | : {} 33 | const scripts = pkg.scripts || {} 34 | const DEFAULT_CUSTOM_COVERAGE_SCRIPT_NAME = 'coverage:report' 35 | const customNycReportScript = scripts[DEFAULT_CUSTOM_COVERAGE_SCRIPT_NAME] 36 | 37 | const nycReportOptions = getNycOptions(processWorkingDirectory) 38 | 39 | const nycFilename = join(nycReportOptions['temp-dir'], 'out.json') 40 | 41 | function saveCoverage(coverage) { 42 | if (!existsSync(nycReportOptions.tempDir)) { 43 | mkdirSync(nycReportOptions.tempDir, { recursive: true }) 44 | debug('created folder %s for output coverage', nycReportOptions.tempDir) 45 | } 46 | 47 | writeFileSync(nycFilename, JSON.stringify(coverage, null, 2)) 48 | } 49 | 50 | function maybePrintFinalCoverageFiles(folder) { 51 | const jsonReportFilename = join(folder, 'coverage-final.json') 52 | if (!existsSync(jsonReportFilename)) { 53 | debug('Did not find final coverage file %s', jsonReportFilename) 54 | return 55 | } 56 | 57 | debug('Final coverage in %s', jsonReportFilename) 58 | const finalCoverage = JSON.parse(readFileSync(jsonReportFilename, 'utf8')) 59 | const finalCoverageKeys = Object.keys(finalCoverage) 60 | debug( 61 | 'There are %d key(s) in %s', 62 | finalCoverageKeys.length, 63 | jsonReportFilename, 64 | ) 65 | 66 | finalCoverageKeys.forEach((key) => { 67 | const s = finalCoverage[key].s || {} 68 | const statements = Object.keys(s) 69 | const totalStatements = statements.length 70 | let coveredStatements = 0 71 | statements.forEach((statementKey) => { 72 | if (s[statementKey]) { 73 | coveredStatements += 1 74 | } 75 | }) 76 | 77 | const hasStatements = totalStatements > 0 78 | const allCovered = coveredStatements === totalStatements 79 | const coverageStatus = hasStatements ? (allCovered ? '✅' : '⚠️') : '❓' 80 | 81 | debug( 82 | '%s %s statements covered %d/%d', 83 | coverageStatus, 84 | key, 85 | coveredStatements, 86 | totalStatements, 87 | ) 88 | }) 89 | } 90 | 91 | const tasks = { 92 | /** 93 | * Clears accumulated code coverage information. 94 | * 95 | * Interactive mode with "cypress open" 96 | * - running a single spec or "Run all specs" needs to reset coverage 97 | * Headless mode with "cypress run" 98 | * - runs EACH spec separately, so we cannot reset the coverage 99 | * or we will lose the coverage from previous specs. 100 | */ 101 | resetCoverage(options = {}) { 102 | const { isInteractive, specCovers } = options 103 | debug('reset coverage %o', options) 104 | 105 | if (isInteractive || specCovers) { 106 | debug('reset code coverage in interactive mode') 107 | const coverageMap = istanbul.createCoverageMap({}) 108 | saveCoverage(coverageMap) 109 | } else { 110 | debug('not resetting code coverage') 111 | } 112 | 113 | /* 114 | Else: 115 | in headless mode, assume the coverage file was deleted 116 | before the `cypress run` command was called 117 | example: rm -rf .nyc_output || true 118 | */ 119 | 120 | return null 121 | }, 122 | 123 | /** 124 | * Combines coverage information from single test 125 | * with previously collected coverage. 126 | * 127 | * @param {string} sentCoverage Stringified coverage object sent by the test runner 128 | * @returns {null} Nothing is returned from this task 129 | */ 130 | combineCoverage(sentCoverage) { 131 | const coverage = JSON.parse(sentCoverage) 132 | debug('parsed sent coverage') 133 | 134 | fixSourcePaths(coverage) 135 | 136 | const previousCoverage = existsSync(nycFilename) 137 | ? JSON.parse(readFileSync(nycFilename, 'utf8')) 138 | : {} 139 | 140 | // previous code coverage object might have placeholder entries 141 | // for files that we have not seen yet, 142 | // but the user expects to include in the coverage report 143 | // the merge function messes up, so we should remove any placeholder entries 144 | // and re-insert them again when creating the report 145 | removePlaceholders(previousCoverage) 146 | 147 | const coverageMap = istanbul.createCoverageMap(previousCoverage) 148 | coverageMap.merge(coverage) 149 | saveCoverage(coverageMap) 150 | debug('wrote coverage file %s', nycFilename) 151 | 152 | return null 153 | }, 154 | 155 | reportSpecCovers(options) { 156 | debug('report spec covers %o', options) 157 | const { specCovers, spec } = options 158 | if (!specCovers) { 159 | return null 160 | } 161 | 162 | const specNumbers = [] 163 | const coverage = getCoverage() 164 | const coverageKeys = Object.keys(coverage) 165 | const cwd = process.cwd() 166 | coverageKeys.forEach((key) => { 167 | const fileCoverage = coverage[key] 168 | const sourceRelative = relative(cwd, fileCoverage.path) 169 | const s = fileCoverage.s || {} 170 | const statements = Object.keys(s) 171 | const totalStatements = statements.length 172 | let coveragePercentage = 0 173 | if (totalStatements > 0) { 174 | let covered = 0 175 | statements.forEach((k) => { 176 | const count = s[k] 177 | if (count > 0) { 178 | covered += 1 179 | } 180 | }) 181 | coveragePercentage = Math.round((covered / totalStatements) * 100) 182 | } 183 | specNumbers.push({ 184 | name: sourceRelative, 185 | covered: coveragePercentage, 186 | }) 187 | // console.log('%s - %d', sourceRelative, ) 188 | }) 189 | 190 | const sorted = sortArray(specNumbers, { 191 | by: ['covered', 'name'], 192 | order: ['desc'], 193 | }) 194 | 195 | console.table(`spec ${spec.relative} covers`, sorted) 196 | // console.log('spec %s covers:', spec.relative) 197 | 198 | updateSpecCovers(spec.relative, sorted) 199 | 200 | return null 201 | }, 202 | 203 | /** 204 | * Saves coverage information as a JSON file and calls 205 | * NPM script to generate HTML report 206 | */ 207 | coverageReport(options = {}) { 208 | debug('coverage report %o', options) 209 | const { specCovers } = options 210 | if (specCovers) { 211 | debug('when using spec covers, skipping final report') 212 | return null 213 | } 214 | 215 | if (!existsSync(nycFilename)) { 216 | console.warn('Cannot find coverage file %s', nycFilename) 217 | console.warn('Skipping coverage report') 218 | return null 219 | } 220 | 221 | showNycInfo(nycFilename) 222 | 223 | const allSourceFilesMissing = checkAllPathsNotFound(nycFilename) 224 | if (allSourceFilesMissing) { 225 | tryFindingLocalFiles(nycFilename) 226 | } 227 | 228 | resolveRelativePaths(nycFilename) 229 | 230 | if (customNycReportScript) { 231 | debug( 232 | 'saving coverage report using script "%s" from package.json, command: %s', 233 | DEFAULT_CUSTOM_COVERAGE_SCRIPT_NAME, 234 | customNycReportScript, 235 | ) 236 | debug('current working directory is %s', process.cwd()) 237 | return execa('npm', ['run', DEFAULT_CUSTOM_COVERAGE_SCRIPT_NAME], { 238 | stdio: 'inherit', 239 | }) 240 | } 241 | 242 | if (nycReportOptions.all) { 243 | debug('nyc needs to report on all included files') 244 | includeAllFiles(nycFilename, nycReportOptions) 245 | } 246 | 247 | debug('calling NYC reporter with options %o', nycReportOptions) 248 | debug('current working directory is %s', process.cwd()) 249 | const NYC = require('nyc') 250 | const nyc = new NYC(nycReportOptions) 251 | 252 | const returnReportFolder = () => { 253 | const reportFolder = nycReportOptions['report-dir'] 254 | debug( 255 | 'after reporting, returning the report folder name %s', 256 | reportFolder, 257 | ) 258 | 259 | maybePrintFinalCoverageFiles(reportFolder) 260 | 261 | return reportFolder 262 | } 263 | return nyc.report().then(returnReportFolder) 264 | }, 265 | } 266 | 267 | /** 268 | * Registers code coverage collection and reporting tasks. 269 | * Sets an environment variable to tell the browser code that it can 270 | * send the coverage. 271 | * @example 272 | ``` 273 | // your plugins file 274 | module.exports = (on, config) => { 275 | require('cypress/code-coverage/task')(on, config) 276 | // IMPORTANT to return the config object 277 | // with the any changed environment variables 278 | return config 279 | } 280 | ``` 281 | */ 282 | function registerCodeCoverageTasks(on, config) { 283 | debug('registering code coverage tasks') 284 | on('task', tasks) 285 | 286 | // set a variable to let the hooks running in the browser 287 | // know that they can send coverage commands 288 | config.env.codeCoverageTasksRegistered = true 289 | 290 | return config 291 | } 292 | 293 | module.exports = registerCodeCoverageTasks 294 | -------------------------------------------------------------------------------- /support.js: -------------------------------------------------------------------------------- 1 | /// 2 | // @ts-check 3 | 4 | const dayjs = require('dayjs') 5 | var duration = require('dayjs/plugin/duration') 6 | const { 7 | excludeByUser, 8 | filterSupportFilesFromCoverage, 9 | } = require('./support-utils') 10 | const { isPluginDisabled } = require('./common-utils') 11 | 12 | dayjs.extend(duration) 13 | 14 | function getCoverageConfig() { 15 | const env = Cypress.env() 16 | return env.coverage || {} 17 | } 18 | 19 | /** 20 | * Sends collected code coverage object to the backend code 21 | * via "cy.task". 22 | */ 23 | const sendCoverage = (coverage, pathname = '/') => { 24 | const config = getCoverageConfig() 25 | 26 | if (!config.quiet) { 27 | logMessage(`Saving code coverage for **${pathname}**`) 28 | } 29 | 30 | let filteredCoverage = coverage 31 | 32 | // console.log({ config }) 33 | 34 | // by default we do not filter anything from the code coverage object 35 | // if the user gives a list of patters to filter, we filter the coverage object 36 | if (config.exclude) { 37 | filteredCoverage = excludeByUser(config.exclude, coverage) 38 | } else if (Cypress.spec.specType === 'component') { 39 | filteredCoverage = filterSupportFilesFromCoverage(coverage) 40 | } 41 | 42 | // stringify coverage object for speed 43 | cy.task('combineCoverage', JSON.stringify(filteredCoverage), { 44 | log: false, 45 | }) 46 | } 47 | 48 | /** 49 | * Consistently logs the given string to the Command Log 50 | * so the user knows the log message is coming from this plugin. 51 | * @param {string} message String message to log. 52 | */ 53 | const logMessage = (message) => { 54 | const logInstance = Cypress.log({ 55 | name: 'Coverage', 56 | message, 57 | }) 58 | } 59 | 60 | const registerHooks = () => { 61 | let windowCoverageObjects 62 | 63 | const hasE2ECoverage = () => Boolean(windowCoverageObjects.length) 64 | 65 | // @ts-ignore 66 | const hasUnitTestCoverage = () => Boolean(window.__coverage__) 67 | 68 | before(() => { 69 | const config = getCoverageConfig() 70 | let logInstance 71 | 72 | if (!config.quiet) { 73 | // we need to reset the coverage when running 74 | // in the interactive mode, otherwise the counters will 75 | // keep increasing every time we rerun the tests 76 | logInstance = Cypress.log({ 77 | name: 'Coverage', 78 | message: ['Reset [@bahmutov/cypress-code-coverage]'], 79 | }) 80 | } 81 | 82 | cy.task( 83 | 'resetCoverage', 84 | { 85 | // @ts-ignore 86 | isInteractive: Cypress.config('isInteractive'), 87 | specCovers: Cypress.env('specCovers'), 88 | }, 89 | { log: false }, 90 | ).then(() => { 91 | if (logInstance) { 92 | logInstance.end() 93 | } 94 | }) 95 | }) 96 | 97 | beforeEach(() => { 98 | const instrumentScripts = Cypress.env('coverage')?.instrument 99 | 100 | if (instrumentScripts) { 101 | // the user wants Cypress to instrument the application code 102 | // by intercepting the script requests and instrumenting them on the fly 103 | 104 | // https://github.com/istanbuljs/istanbuljs 105 | // @ts-ignore 106 | const { createInstrumenter } = require('istanbul-lib-instrument') 107 | 108 | const instrumenter = createInstrumenter({ 109 | esModules: true, 110 | compact: false, 111 | preserveComments: true, 112 | }) 113 | 114 | const baseUrl = Cypress.config('baseUrl') 115 | // @ts-ignore 116 | const proxyServer = Cypress.config('proxyServer') + '/' 117 | 118 | cy.intercept( 119 | { 120 | method: 'GET', 121 | resourceType: 'script', 122 | url: instrumentScripts, 123 | }, 124 | (req) => { 125 | // remove the cache headers to force the server 126 | // to return the script source code 127 | // Idea: we could cache the instrumented code 128 | // and let the browser receive 304 status code 129 | delete req.headers['if-none-match'] 130 | delete req.headers['if-modified-since'] 131 | 132 | // @ts-ignore 133 | req.continue((res) => { 134 | const relativeUrl = req.url 135 | // @ts-ignore 136 | .replace(baseUrl, '') 137 | .replace(proxyServer, '') 138 | // console.log('instrumenting', relativeUrl) 139 | 140 | // @ts-ignore 141 | const instrumented = instrumenter.instrumentSync( 142 | res.body, 143 | relativeUrl, 144 | ) 145 | res.body = instrumented 146 | return res 147 | }) 148 | }, 149 | ) 150 | } 151 | 152 | // each object will have the coverage and url pathname 153 | // to let the user know the coverage has been collected 154 | windowCoverageObjects = [] 155 | 156 | const saveCoverageObject = (win) => { 157 | // if application code has been instrumented, the app iframe "window" has an object 158 | try { 159 | const applicationSourceCoverage = win.__coverage__ 160 | if (!applicationSourceCoverage) { 161 | return 162 | } 163 | 164 | if ( 165 | Cypress._.find(windowCoverageObjects, { 166 | coverage: applicationSourceCoverage, 167 | }) 168 | ) { 169 | // this application code coverage object is already known 170 | // which can happen when combining `window:load` and `before` callbacks 171 | return 172 | } 173 | 174 | if (Cypress.spec.specType === 'component') { 175 | windowCoverageObjects.push({ 176 | coverage: applicationSourceCoverage, 177 | pathname: Cypress.spec.relative, 178 | }) 179 | } else { 180 | windowCoverageObjects.push({ 181 | coverage: applicationSourceCoverage, 182 | pathname: win.location.pathname, 183 | }) 184 | } 185 | } catch { 186 | // do nothing, probably cross-origin access 187 | } 188 | } 189 | 190 | // save reference to coverage for each app window loaded in the test 191 | cy.on('window:load', saveCoverageObject) 192 | 193 | // save reference if visiting a page inside a before() hook 194 | cy.window({ log: false }).then(saveCoverageObject) 195 | }) 196 | 197 | afterEach(() => { 198 | // save coverage after the test 199 | // because now the window coverage objects have been updated 200 | windowCoverageObjects.forEach((cover) => { 201 | sendCoverage(cover.coverage, cover.pathname) 202 | }) 203 | 204 | const taskOptions = { spec: Cypress.spec } 205 | if (Cypress.env('specCovers')) { 206 | taskOptions.specCovers = Cypress.env('specCovers') 207 | } 208 | 209 | const config = getCoverageConfig() 210 | if (!config.quiet) { 211 | cy.log(`**Reporting coverage for** ${Cypress.spec.relative}`) 212 | } 213 | cy.task('reportSpecCovers', taskOptions, { log: false }) 214 | 215 | if (!hasE2ECoverage()) { 216 | if (hasUnitTestCoverage()) { 217 | if (!config.quiet) { 218 | logMessage(`👉 Only found unit test code coverage.`) 219 | } 220 | } else { 221 | const expectBackendCoverageOnly = Cypress._.get( 222 | Cypress.env('codeCoverage'), 223 | 'expectBackendCoverageOnly', 224 | false, 225 | ) 226 | if (!expectBackendCoverageOnly) { 227 | logMessage(` 228 | ⚠️ Could not find any coverage information in your application 229 | by looking at the window coverage object. 230 | Did you forget to instrument your application? 231 | See [code-coverage#instrument-your-application](https://github.com/cypress-io/code-coverage#instrument-your-application) 232 | `) 233 | } 234 | } 235 | } 236 | }) 237 | 238 | after(function collectBackendCoverage() { 239 | let runningEndToEndTests, isIntegrationSpec 240 | 241 | try { 242 | // I wish I could fail the tests if there is no code coverage information 243 | // but throwing an error here does not fail the test run due to 244 | // https://github.com/cypress-io/cypress/issues/2296 245 | 246 | // there might be server-side code coverage information 247 | // we should grab it once after all tests finish 248 | // @ts-ignore 249 | const baseUrl = Cypress.config('baseUrl') || cy.state('window').origin 250 | // @ts-ignore 251 | runningEndToEndTests = baseUrl !== Cypress.config('proxyUrl') 252 | const specType = Cypress._.get(Cypress.spec, 'specType', 'integration') 253 | isIntegrationSpec = specType === 'integration' 254 | } catch { 255 | // probably cross-origin request 256 | // cannot access the window 257 | } 258 | 259 | if (runningEndToEndTests && isIntegrationSpec) { 260 | const baseUrl = Cypress.config('baseUrl') 261 | if (baseUrl) { 262 | // can only fetch server-side code coverage if we have a baseUrl 263 | 264 | // we can only request server-side code coverage 265 | // if we are running end-to-end tests, 266 | // otherwise where do we send the request? 267 | const url = Cypress._.get( 268 | Cypress.env('codeCoverage'), 269 | 'url', 270 | '/__coverage__', 271 | ) 272 | cy.request({ 273 | url, 274 | log: false, 275 | failOnStatusCode: false, 276 | }) 277 | .then((r) => { 278 | return Cypress._.get(r, 'body.coverage', null) 279 | }) 280 | .then((coverage) => { 281 | if (!coverage) { 282 | // we did not get code coverage - this is the 283 | // original failed request 284 | const expectBackendCoverageOnly = Cypress._.get( 285 | Cypress.env('codeCoverage'), 286 | 'expectBackendCoverageOnly', 287 | false, 288 | ) 289 | if (expectBackendCoverageOnly) { 290 | throw new Error( 291 | `Expected to collect backend code coverage from ${url}`, 292 | ) 293 | } else { 294 | // we did not really expect to collect the backend code coverage 295 | return 296 | } 297 | } 298 | sendCoverage(coverage, 'backend') 299 | }) 300 | } 301 | } 302 | }) 303 | 304 | after(function mergeUnitTestCoverage() { 305 | // collect and merge frontend coverage 306 | 307 | // if spec bundle has been instrumented (using Cypress preprocessor) 308 | // then we will have unit test coverage 309 | // NOTE: spec iframe is NOT reset between the tests, so we can grab 310 | // the coverage information only once after all tests have finished 311 | // @ts-ignore 312 | if (window.__coverage__) { 313 | const unitTestCoverage = filterSupportFilesFromCoverage( 314 | // @ts-ignore 315 | window.__coverage__, 316 | ) 317 | sendCoverage(unitTestCoverage, 'component tests') 318 | } 319 | }) 320 | 321 | after(function generateReport() { 322 | const config = getCoverageConfig() 323 | let logInstance 324 | 325 | if (!config.quiet) { 326 | // when all tests finish, lets generate the coverage report 327 | logInstance = Cypress.log({ 328 | name: 'Coverage', 329 | message: ['Generating report [@bahmutov/cypress-code-coverage]'], 330 | }) 331 | } 332 | 333 | const options = { 334 | specCovers: Cypress.env('specCovers'), 335 | } 336 | cy.task('coverageReport', options, { 337 | timeout: dayjs.duration(3, 'minutes').asMilliseconds(), 338 | log: false, 339 | }).then((coverageReportFolder) => { 340 | if (logInstance) { 341 | logInstance.set('consoleProps', () => ({ 342 | 'coverage report folder': coverageReportFolder, 343 | })) 344 | logInstance.end() 345 | } 346 | return coverageReportFolder 347 | }) 348 | }) 349 | } 350 | 351 | // to disable code coverage commands and save time 352 | // pass environment variable coverage=false 353 | // cypress run --env coverage=false 354 | // or 355 | // CYPRESS_coverage=false cypress run 356 | // see https://on.cypress.io/environment-variables 357 | 358 | // to avoid "coverage" env variable being case-sensitive, convert to lowercase 359 | const cyEnvs = Cypress._.mapKeys(Cypress.env(), (value, key) => 360 | key.toLowerCase(), 361 | ) 362 | 363 | const pluginDisabled = isPluginDisabled(cyEnvs) 364 | 365 | if (pluginDisabled) { 366 | console.log('Skipping code coverage hooks') 367 | } else if (Cypress.env('codeCoverageTasksRegistered') !== true) { 368 | // register a hook just to log a message 369 | before(() => { 370 | logMessage(` 371 | ⚠️ Code coverage tasks were not registered by the plugins file. 372 | See [support issue](https://github.com/cypress-io/code-coverage/issues/179) 373 | for possible workarounds. 374 | `) 375 | }) 376 | } else { 377 | registerHooks() 378 | } 379 | -------------------------------------------------------------------------------- /task-utils.js: -------------------------------------------------------------------------------- 1 | // helper functions to use from "task.js" plugins code 2 | // that need access to the file system 3 | 4 | // @ts-check 5 | /// 6 | const { readFileSync, writeFileSync, existsSync } = require('fs') 7 | const { isAbsolute, resolve, join } = require('path') 8 | const debug = require('debug')('code-coverage') 9 | const chalk = require('chalk') 10 | const globby = require('globby') 11 | const yaml = require('js-yaml') 12 | const { 13 | combineNycOptions, 14 | defaultNycOptions, 15 | fileCoveragePlaceholder, 16 | } = require('./common-utils') 17 | 18 | function readNycOptions(workingDirectory) { 19 | const pkgFilename = join(workingDirectory, 'package.json') 20 | const pkg = existsSync(pkgFilename) 21 | ? JSON.parse(readFileSync(pkgFilename, 'utf8')) 22 | : {} 23 | const pkgNycOptions = pkg.nyc || {} 24 | 25 | const nycrcFilename = join(workingDirectory, '.nycrc') 26 | const nycrc = existsSync(nycrcFilename) 27 | ? JSON.parse(readFileSync(nycrcFilename, 'utf8')) 28 | : {} 29 | 30 | const nycrcJsonFilename = join(workingDirectory, '.nycrc.json') 31 | const nycrcJson = existsSync(nycrcJsonFilename) 32 | ? JSON.parse(readFileSync(nycrcJsonFilename, 'utf8')) 33 | : {} 34 | 35 | const nycrcYamlFilename = join(workingDirectory, '.nycrc.yaml') 36 | let nycrcYaml = {} 37 | if (existsSync(nycrcYamlFilename)) { 38 | try { 39 | nycrcYaml = yaml.safeLoad(readFileSync(nycrcYamlFilename, 'utf8')) 40 | } catch (error) { 41 | throw new Error(`Failed to load .nycrc.yaml: ${error.message}`) 42 | } 43 | } 44 | 45 | const nycrcYmlFilename = join(workingDirectory, '.nycrc.yml') 46 | let nycrcYml = {} 47 | if (existsSync(nycrcYmlFilename)) { 48 | try { 49 | nycrcYml = yaml.safeLoad(readFileSync(nycrcYmlFilename, 'utf8')) 50 | } catch (error) { 51 | throw new Error(`Failed to load .nycrc.yml: ${error.message}`) 52 | } 53 | } 54 | 55 | const nycConfigFilename = join(workingDirectory, 'nyc.config.js') 56 | let nycConfig = {} 57 | if (existsSync(nycConfigFilename)) { 58 | try { 59 | nycConfig = require(nycConfigFilename) 60 | } catch (error) { 61 | throw new Error(`Failed to load nyc.config.js: ${error.message}`) 62 | } 63 | } 64 | 65 | const nycOptions = combineNycOptions( 66 | defaultNycOptions, 67 | nycrc, 68 | nycrcJson, 69 | nycrcYaml, 70 | nycrcYml, 71 | nycConfig, 72 | pkgNycOptions, 73 | ) 74 | debug('combined NYC options %o', nycOptions) 75 | 76 | return nycOptions 77 | } 78 | 79 | function getNycOptions(workingDirectory) { 80 | if (!workingDirectory) { 81 | workingDirectory = process.cwd() 82 | } 83 | 84 | // https://github.com/istanbuljs/nyc#common-configuration-options 85 | const nycReportOptions = readNycOptions(workingDirectory) 86 | 87 | if (nycReportOptions.exclude && !Array.isArray(nycReportOptions.exclude)) { 88 | console.error('NYC options: %o', nycReportOptions) 89 | throw new Error('Expected "exclude" to by an array') 90 | } 91 | 92 | if (nycReportOptions['temp-dir']) { 93 | nycReportOptions['temp-dir'] = resolve(nycReportOptions['temp-dir']) 94 | } else { 95 | nycReportOptions['temp-dir'] = join(workingDirectory, '.nyc_output') 96 | } 97 | 98 | nycReportOptions.tempDir = nycReportOptions['temp-dir'] 99 | 100 | if (nycReportOptions['report-dir']) { 101 | nycReportOptions['report-dir'] = resolve(nycReportOptions['report-dir']) 102 | } 103 | // seems nyc API really is using camel cased version 104 | nycReportOptions.reportDir = nycReportOptions['report-dir'] 105 | 106 | return nycReportOptions 107 | } 108 | 109 | function getNycReportFilename(workingDirectory) { 110 | const nycReportOptions = getNycOptions(workingDirectory) 111 | 112 | const nycFilename = join(nycReportOptions['temp-dir'], 'out.json') 113 | return nycFilename 114 | } 115 | 116 | function checkAllPathsNotFound(nycFilename) { 117 | const nycCoverage = JSON.parse(readFileSync(nycFilename, 'utf8')) 118 | 119 | const coverageKeys = Object.keys(nycCoverage) 120 | if (!coverageKeys.length) { 121 | debug('⚠️ file %s has no coverage information', nycFilename) 122 | return 123 | } 124 | 125 | const allFilesAreMissing = coverageKeys.every((key, k) => { 126 | const coverage = nycCoverage[key] 127 | return !existsSync(coverage.path) 128 | }) 129 | 130 | debug( 131 | 'in file %s all files are not found? %o', 132 | nycFilename, 133 | allFilesAreMissing, 134 | ) 135 | return allFilesAreMissing 136 | } 137 | 138 | /** 139 | * A small debug utility to inspect paths saved in NYC output JSON file 140 | */ 141 | function showNycInfo(nycFilename) { 142 | const nycCoverage = JSON.parse(readFileSync(nycFilename, 'utf8')) 143 | 144 | const coverageKeys = Object.keys(nycCoverage) 145 | if (!coverageKeys.length) { 146 | console.error( 147 | '⚠️ file %s has no coverage information', 148 | chalk.yellow(nycFilename), 149 | ) 150 | console.error( 151 | 'Did you forget to instrument your web application? Read %s', 152 | chalk.blue( 153 | 'https://github.com/cypress-io/code-coverage#instrument-your-application', 154 | ), 155 | ) 156 | return 157 | } 158 | debug('NYC file %s has %d key(s)', nycFilename, coverageKeys.length) 159 | 160 | const maxPrintKeys = 3 161 | const showKeys = coverageKeys.slice(0, maxPrintKeys) 162 | 163 | showKeys.forEach((key, k) => { 164 | const coverage = nycCoverage[key] 165 | 166 | // printing a few found keys and file paths from the coverage file 167 | // will make debugging any problems much much easier 168 | if (k < maxPrintKeys) { 169 | debug('%d key %s file path %s', k + 1, key, coverage.path) 170 | } 171 | }) 172 | } 173 | 174 | /** 175 | * Looks at all coverage objects in the given JSON coverage file 176 | * and if the file is relative, and exists, changes its path to 177 | * be absolute. 178 | */ 179 | function resolveRelativePaths(nycFilename) { 180 | const nycCoverage = JSON.parse(readFileSync(nycFilename, 'utf8')) 181 | 182 | const coverageKeys = Object.keys(nycCoverage) 183 | if (!coverageKeys.length) { 184 | debug('⚠️ file %s has no coverage information', nycFilename) 185 | return 186 | } 187 | debug('NYC file %s has %d key(s)', nycFilename, coverageKeys.length) 188 | 189 | let changed 190 | 191 | coverageKeys.forEach((key, k) => { 192 | const coverage = nycCoverage[key] 193 | 194 | if (!coverage.path) { 195 | debug('key %s does not have path', key) 196 | return 197 | } 198 | 199 | if (!isAbsolute(coverage.path)) { 200 | if (existsSync(coverage.path)) { 201 | debug('resolving path %s', coverage.path) 202 | coverage.path = resolve(coverage.path) 203 | changed = true 204 | } 205 | return 206 | } 207 | 208 | // path is absolute, let's check if it exists 209 | if (!existsSync(coverage.path)) { 210 | debug('⚠️ cannot find file %s with hash %s', coverage.path, coverage.hash) 211 | } 212 | }) 213 | 214 | if (changed) { 215 | debug('resolveRelativePaths saving updated file %s', nycFilename) 216 | debug('there are %d keys in the file', coverageKeys.length) 217 | writeFileSync( 218 | nycFilename, 219 | JSON.stringify(nycCoverage, null, 2) + '\n', 220 | 'utf8', 221 | ) 222 | } 223 | } 224 | 225 | /** 226 | * @param {string[]} filepaths 227 | * @returns {string | undefined} common prefix that corresponds to current folder 228 | */ 229 | function findCommonRoot(filepaths) { 230 | if (!filepaths.length) { 231 | debug('cannot find common root without any files') 232 | return 233 | } 234 | 235 | // assuming / as file separator 236 | const splitParts = filepaths.map((name) => name.split('/')) 237 | const lengths = splitParts.map((arr) => arr.length) 238 | const shortestLength = Math.min.apply(null, lengths) 239 | debug('shorted file path has %d parts', shortestLength) 240 | 241 | const cwd = process.cwd() 242 | let commonPrefix = [] 243 | let foundCurrentFolder 244 | 245 | for (let k = 0; k < shortestLength; k += 1) { 246 | const part = splitParts[0][k] 247 | const prefix = commonPrefix.concat(part).join('/') 248 | debug('testing prefix %o', prefix) 249 | const allFilesStart = filepaths.every((name) => name.startsWith(prefix)) 250 | if (!allFilesStart) { 251 | debug('stopped at non-common prefix %s', prefix) 252 | break 253 | } 254 | 255 | commonPrefix.push(part) 256 | 257 | const removedPrefixNames = filepaths.map((filepath) => 258 | filepath.slice(prefix.length), 259 | ) 260 | debug('removedPrefix %o', removedPrefixNames) 261 | 262 | let foundCount = 0 263 | removedPrefixNames.forEach((filepath) => { 264 | if (existsSync(join(cwd, filepath))) { 265 | foundCount += 1 266 | } 267 | }) 268 | const foundMostPaths = foundCount >= removedPrefixNames.length / 2 269 | debug('found %d files out of %d', foundCount, removedPrefixNames.length) 270 | debug('found most files found at "%s"? %o', prefix, foundMostPaths) 271 | if (foundMostPaths) { 272 | foundCurrentFolder = prefix 273 | debug( 274 | 'found prefix that matches current folder: "%s"', 275 | foundCurrentFolder, 276 | ) 277 | break 278 | } 279 | } 280 | 281 | return foundCurrentFolder 282 | } 283 | 284 | function tryFindingLocalFiles(nycFilename) { 285 | const nycCoverage = JSON.parse(readFileSync(nycFilename, 'utf8')) 286 | const coverageKeys = Object.keys(nycCoverage) 287 | const filenames = coverageKeys.map((key) => nycCoverage[key].path) 288 | const commonFolder = findCommonRoot(filenames) 289 | if (commonFolder === undefined) { 290 | debug('could not find common folder %s', commonFolder) 291 | return 292 | } 293 | const cwd = process.cwd() 294 | debug( 295 | 'found common folder %s that matches current working directory %s', 296 | commonFolder, 297 | cwd, 298 | ) 299 | let changed 300 | 301 | if (commonFolder === '') { 302 | // we just need to prefix the paths with the current folder 303 | coverageKeys.forEach((key) => { 304 | const filePath = nycCoverage[key].path 305 | const to = join(cwd, filePath) 306 | // ? Do we need to replace the "key" in the coverage object or can we just replace the "path"? 307 | nycCoverage[key].path = to 308 | debug('formed %s -> %s', filePath, to) 309 | changed = true 310 | }) 311 | } else { 312 | // we need to replace part of the paths with the current folder 313 | const length = commonFolder.length 314 | coverageKeys.forEach((key) => { 315 | const from = nycCoverage[key].path 316 | if (from.startsWith(commonFolder)) { 317 | const to = join(cwd, from.slice(length)) 318 | // ? Do we need to replace the "key" in the coverage object or can we just replace the "path"? 319 | nycCoverage[key].path = to 320 | debug('replaced %s -> %s', from, to) 321 | changed = true 322 | } 323 | }) 324 | } 325 | 326 | if (changed) { 327 | debug('tryFindingLocalFiles saving updated file %s', nycFilename) 328 | debug('there are %d keys in the file', coverageKeys.length) 329 | writeFileSync( 330 | nycFilename, 331 | JSON.stringify(nycCoverage, null, 2) + '\n', 332 | 'utf8', 333 | ) 334 | } 335 | } 336 | 337 | /** 338 | * Tries to find source files to be included in the final coverage report 339 | * using NYC options: extension list, include and exclude. 340 | */ 341 | function findSourceFiles(nycOptions) { 342 | debug('include all files options: %o', { 343 | all: nycOptions.all, 344 | include: nycOptions.include, 345 | exclude: nycOptions.exclude, 346 | extension: nycOptions.extension, 347 | }) 348 | 349 | if (!Array.isArray(nycOptions.extension)) { 350 | console.error( 351 | 'Expected NYC "extension" option to be a list of file extensions', 352 | ) 353 | console.error(nycOptions) 354 | return [] 355 | } 356 | 357 | let patterns = [] 358 | if (Array.isArray(nycOptions.include)) { 359 | patterns = patterns.concat(nycOptions.include) 360 | } else if (typeof nycOptions.include === 'string') { 361 | patterns.push(nycOptions.include) 362 | } else { 363 | debug('using default list of extensions') 364 | nycOptions.extension.forEach((extension) => { 365 | patterns.push('**/*' + extension) 366 | }) 367 | } 368 | 369 | if (Array.isArray(nycOptions.exclude)) { 370 | const negated = nycOptions.exclude.map((s) => '!' + s) 371 | patterns = patterns.concat(negated) 372 | } else if (typeof nycOptions.exclude === 'string') { 373 | patterns.push('!' + nycOptions.exclude) 374 | } 375 | // always exclude node_modules 376 | // https://github.com/istanbuljs/nyc#including-files-within-node_modules 377 | patterns.push('!**/node_modules/**') 378 | // and exclude the Cypress config file 379 | patterns.push('!cypress.config.*') 380 | 381 | debug('searching files to include using patterns %o', patterns) 382 | 383 | const allFiles = globby.sync(patterns, { absolute: true }) 384 | return allFiles 385 | } 386 | /** 387 | * If the website or unit tests did not load ALL files we need to 388 | * include, then we should include the missing files ourselves 389 | * before generating the report. 390 | * 391 | * @see https://github.com/cypress-io/code-coverage/issues/207 392 | */ 393 | function includeAllFiles(nycFilename, nycOptions) { 394 | if (!nycOptions.all) { 395 | debug('NYC "all" option is not set, skipping including all files') 396 | return 397 | } 398 | 399 | const allFiles = findSourceFiles(nycOptions) 400 | if (debug.enabled) { 401 | debug('found %d file(s)', allFiles.length) 402 | console.error(allFiles.join('\n')) 403 | } 404 | if (!allFiles.length) { 405 | debug('no files found, hoping for the best') 406 | return 407 | } 408 | 409 | const nycCoverage = JSON.parse(readFileSync(nycFilename, 'utf8')) 410 | const coverageKeys = Object.keys(nycCoverage) 411 | const coveredPaths = coverageKeys.map((key) => 412 | nycCoverage[key].path.replace(/\\/g, '/'), 413 | ) 414 | 415 | debug('coverage has %d record(s)', coveredPaths.length) 416 | // report on first couple of entries 417 | if (debug.enabled) { 418 | console.error('coverage has the following first paths') 419 | console.error(coveredPaths.slice(0, 4).join('\n')) 420 | } 421 | 422 | let changed 423 | allFiles.forEach((fullPath) => { 424 | if (coveredPaths.includes(fullPath)) { 425 | // all good, this file exists in coverage object 426 | return 427 | } 428 | debug('adding empty coverage for file %s', fullPath) 429 | changed = true 430 | // insert placeholder object for now 431 | const placeholder = fileCoveragePlaceholder(fullPath) 432 | nycCoverage[fullPath] = placeholder 433 | }) 434 | 435 | if (changed) { 436 | debug('includeAllFiles saving updated file %s', nycFilename) 437 | debug('there are %d keys in the file', Object.keys(nycCoverage).length) 438 | 439 | writeFileSync( 440 | nycFilename, 441 | JSON.stringify(nycCoverage, null, 2) + '\n', 442 | 'utf8', 443 | ) 444 | } 445 | } 446 | 447 | function getCoverage() { 448 | const jsonFilename = join('.', '.nyc_output', 'out.json') 449 | const json = JSON.parse(readFileSync(jsonFilename, 'utf8')) 450 | return json 451 | } 452 | 453 | function updateSpecCovers(specName, specCovers) { 454 | const jsonFilename = join('.', '.nyc_output', 'spec-covers.json') 455 | const json = existsSync(jsonFilename) 456 | ? JSON.parse(readFileSync(jsonFilename, 'utf8')) 457 | : {} 458 | json[specName] = specCovers 459 | const text = JSON.stringify(json, null, 2) + '\n' 460 | writeFileSync(jsonFilename, text, 'utf8') 461 | return json 462 | } 463 | 464 | module.exports = { 465 | showNycInfo, 466 | resolveRelativePaths, 467 | checkAllPathsNotFound, 468 | tryFindingLocalFiles, 469 | readNycOptions, 470 | getNycOptions, 471 | getNycReportFilename, 472 | includeAllFiles, 473 | getCoverage, 474 | updateSpecCovers, 475 | } 476 | --------------------------------------------------------------------------------