├── .babelrc ├── .circleci └── config.yml ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md └── workflows │ ├── add-to-triage-board.yml │ ├── snyk_sca_scan.yaml │ ├── snyk_static_analysis_scan.yaml │ └── triage_closed_issue_comment.yml ├── .gitignore ├── .nycrc.json ├── .prettierrc.json ├── .releaserc ├── LICENSE.md ├── README.md ├── common-utils.js ├── cypress-backend.json ├── cypress.config.js ├── cypress ├── README.md ├── e2e │ ├── combine.cy.js │ ├── filtering.cy.js │ ├── fix-source-paths.cy.js │ └── merge.cy.js ├── fixtures │ ├── coverage.json │ └── example.json ├── index.html ├── plugins │ └── index.js └── support │ └── e2e.js ├── images ├── coverage.jpg ├── expect-backend.png ├── gui.png ├── instrumented-code.png ├── warning.png └── window-coverage-object.png ├── middleware ├── express.js ├── hapi.js └── nextjs.js ├── package-lock.json ├── package.json ├── plugins.js ├── renovate.json ├── support-utils.js ├── support.js ├── task-utils.js ├── task.d.ts ├── task.js ├── test-apps ├── all-files │ ├── .babelrc │ ├── README.md │ ├── cypress.config.js │ ├── cypress │ │ ├── e2e │ │ │ └── spec.cy.js │ │ ├── plugins │ │ │ └── index.js │ │ └── support │ │ │ ├── commands.js │ │ │ └── e2e.js │ ├── index.html │ ├── main.js │ ├── not-covered.js │ ├── package.json │ └── second.js ├── backend │ ├── .babelrc │ ├── README.md │ ├── cypress.config.js │ ├── cypress │ │ ├── e2e │ │ │ └── spec.cy.js │ │ ├── plugins │ │ │ └── index.js │ │ └── support │ │ │ └── e2e.js │ ├── package.json │ └── server │ │ ├── index.html │ │ └── server.js ├── batch-send-coverage │ ├── .babelrc │ ├── README.md │ ├── cypress.config.js │ ├── cypress │ │ ├── e2e │ │ │ └── spec.cy.js │ │ ├── plugins │ │ │ └── index.js │ │ └── support │ │ │ ├── commands.js │ │ │ └── e2e.js │ ├── index.html │ ├── main.js │ ├── package-lock.json │ ├── package.json │ ├── second.js │ └── third.js ├── before-all-visit │ ├── .babelrc │ ├── README.md │ ├── cypress.config.js │ ├── cypress │ │ ├── e2e │ │ │ └── spec.cy.js │ │ ├── fixtures │ │ │ └── example.json │ │ ├── plugins │ │ │ └── index.js │ │ └── support │ │ │ └── e2e.js │ ├── index.html │ ├── main.js │ └── package.json ├── before-each-visit │ ├── .babelrc │ ├── README.md │ ├── cypress.config.js │ ├── cypress │ │ ├── e2e │ │ │ └── spec.cy.js │ │ ├── fixtures │ │ │ └── example.json │ │ ├── plugins │ │ │ └── index.js │ │ └── support │ │ │ └── e2e.js │ ├── index.html │ ├── main.js │ └── package.json ├── cra-e2e-and-ct │ ├── .gitignore │ ├── README.md │ ├── cypress.config.ts │ ├── cypress │ │ ├── e2e │ │ │ └── spec.cy.ts │ │ ├── fixtures │ │ │ └── example.json │ │ └── support │ │ │ ├── commands.ts │ │ │ ├── component-index.html │ │ │ ├── component.ts │ │ │ └── e2e.ts │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── robots.txt │ ├── src │ │ ├── App.css │ │ ├── App.test.tsx │ │ ├── App.tsx │ │ ├── components │ │ │ ├── Button.cy.tsx │ │ │ ├── Button.tsx │ │ │ ├── Stepper.cy.tsx │ │ │ └── Stepper.tsx │ │ ├── index.css │ │ ├── index.tsx │ │ ├── logo.svg │ │ ├── react-app-env.d.ts │ │ ├── reportWebVitals.ts │ │ └── setupTests.ts │ └── tsconfig.json ├── exclude-files │ ├── .babelrc │ ├── README.md │ ├── cypress.config.js │ ├── cypress │ │ ├── e2e │ │ │ └── spec.cy.js │ │ ├── plugins │ │ │ └── index.js │ │ └── support │ │ │ ├── commands.js │ │ │ └── e2e.js │ ├── index.html │ ├── main.js │ ├── package.json │ └── second.js ├── frontend │ ├── .babelrc │ ├── README.md │ ├── about.html │ ├── about.js │ ├── app.js │ ├── cypress.config.js │ ├── cypress │ │ ├── e2e │ │ │ └── spec.cy.js │ │ ├── plugins │ │ │ └── index.js │ │ └── support │ │ │ ├── commands.js │ │ │ └── e2e.js │ ├── index.html │ ├── package.json │ └── unit.js ├── fullstack │ ├── .babelrc │ ├── README.md │ ├── cypress.config.js │ ├── cypress │ │ ├── e2e │ │ │ └── spec.cy.js │ │ ├── plugins │ │ │ └── index.js │ │ └── support │ │ │ └── e2e.js │ ├── images │ │ └── fullstack.png │ ├── index.html │ ├── main.js │ ├── package-lock.json │ ├── package.json │ ├── server │ │ └── server.js │ └── string-utils.js ├── multiple-backends │ ├── .babelrc │ ├── README.md │ ├── cypress.config.js │ ├── cypress │ │ ├── e2e │ │ │ └── spec.cy.js │ │ ├── plugins │ │ │ └── index.js │ │ └── support │ │ │ └── e2e.js │ ├── package.json │ └── server │ │ ├── index.html │ │ ├── server-3003.js │ │ └── server-3004.js ├── one-spec │ ├── .babelrc │ ├── README.md │ ├── cypress.config.js │ ├── cypress │ │ ├── e2e │ │ │ ├── spec-a.cy.js │ │ │ └── spec-b.cy.js │ │ ├── plugins │ │ │ └── index.js │ │ └── support │ │ │ └── e2e.js │ ├── index.html │ ├── main.js │ ├── package-lock.json │ └── package.json ├── redirect │ ├── .babelrc │ ├── .nycrc.json │ ├── README.md │ ├── app.js │ ├── cypress.config.js │ ├── cypress │ │ ├── e2e │ │ │ └── spec.cy.js │ │ └── support │ │ │ └── e2e.js │ ├── index.html │ ├── package.json │ └── utils.js ├── same-folder │ ├── .babelrc │ ├── README.md │ ├── cypress.config.js │ ├── cypress │ │ └── fixtures │ │ │ └── example.json │ ├── index.html │ ├── main.js │ ├── package-lock.json │ ├── package.json │ ├── plugins.js │ ├── spec.js │ ├── support.js │ └── unit-utils.js ├── support-files │ ├── .babelrc │ ├── README.md │ ├── cypress.config.js │ ├── cypress │ │ ├── e2e │ │ │ └── spec.cy.js │ │ ├── plugins │ │ │ └── index.js │ │ └── support │ │ │ ├── commands.js │ │ │ └── e2e.js │ ├── index.html │ ├── main.js │ └── package.json ├── ts-example │ ├── .babelrc │ ├── README.md │ ├── calc.ts │ ├── cypress.config.js │ ├── cypress │ │ ├── e2e │ │ │ └── spec.cy.js │ │ ├── plugins │ │ │ └── index.js │ │ └── support │ │ │ └── e2e.js │ ├── index.html │ ├── main.ts │ ├── package-lock.json │ └── package.json ├── unit-tests-js │ ├── .babelrc │ ├── README.md │ ├── cypress.config.js │ ├── cypress │ │ ├── e2e │ │ │ ├── spec-a.cy.js │ │ │ └── spec-b.cy.js │ │ ├── plugins │ │ │ └── index.js │ │ └── support │ │ │ └── e2e.js │ ├── package.json │ └── src │ │ └── utils │ │ ├── math.js │ │ └── misc.js └── use-webpack │ ├── .babelrc │ ├── README.md │ ├── cypress.config.js │ ├── cypress │ ├── e2e │ │ └── spec.cy.js │ ├── plugins │ │ └── index.js │ └── support │ │ └── e2e.js │ ├── index.html │ ├── package.json │ ├── src │ ├── calc.js │ └── index.js │ └── webpack.config.js └── use-babelrc.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # https://circleci.com/docs/2.0/configuration-reference/ 2 | version: 2.1 3 | orbs: 4 | # https://github.com/cypress-io/circleci-orb 5 | cypress: cypress-io/cypress@3.0.0 # used to run e2e tests 6 | win: circleci/windows@5.1.0 # run a test job on Windows 7 | 8 | jobs: 9 | lint: 10 | description: Checks the code formatting 11 | docker: 12 | - image: cimg/node:22.14.0 13 | environment: 14 | # we don't need Cypress to check code style 15 | CYPRESS_INSTALL_BINARY: '0' 16 | steps: 17 | - attach_workspace: 18 | at: ~/ 19 | - run: 20 | name: Code style check 🧹 21 | command: npm run format:check 22 | - run: npm run check:markdown 23 | 24 | install_and_persist: 25 | executor: cypress/default 26 | steps: 27 | - cypress/install 28 | - run: npm run check:markdown 29 | - persist_to_workspace: 30 | paths: 31 | - .cache/Cypress 32 | - project 33 | root: ~/ 34 | 35 | windows_test: 36 | executor: 37 | # executor comes from the "windows" orb 38 | name: win/default 39 | shell: bash.exe 40 | steps: 41 | - checkout 42 | - run: 43 | name: Install node 20 44 | command: nvm install 20.12.1 45 | - run: 46 | name: Use node 20 47 | command: nvm use 20.12.1 48 | - run: 49 | name: Install deps for code coverage 50 | command: npm ci 51 | - cypress/run-tests: 52 | # no-workspace: true 53 | start-command: npm run start:windows --prefix test-apps/all-files 54 | # wait-on: 'http://localhost:1234' 55 | cypress-command: npx cypress run --project test-apps/all-files 56 | # store screenshots and videos 57 | # store_artifacts: true 58 | - run: 59 | # make sure the examples captures 100% of code 60 | name: Verify Code Coverage 61 | command: npm run coverage:verify 62 | working_directory: test-apps/all-files 63 | - run: 64 | name: Check code coverage files 📈 65 | # we will check the final coverage report 66 | # to make sure it only has files we are interested in 67 | # because there are files covered at 0 in the report 68 | command: npm i -D check-code-coverage && npm run coverage:check-files 69 | working_directory: test-apps/all-files 70 | 71 | publish: 72 | description: Publishes the new version of the plugin to NPM 73 | docker: 74 | - image: cimg/node:22.14.0 75 | environment: 76 | # we don't need Cypress to do the release 77 | CYPRESS_INSTALL_BINARY: '0' 78 | # trick semantic-release into thinking this is NOT a pull request 79 | # (under the hood the module env-ci is used to check if this is a PR) 80 | CIRCLE_PR_NUMBER: '' 81 | CIRCLE_PULL_REQUEST: '' 82 | CI_PULL_REQUEST: '' 83 | steps: 84 | - attach_workspace: 85 | at: ~/ 86 | - run: npm run semantic-release 87 | 88 | cyrun: 89 | docker: 90 | - image: cypress/base:16.18.1 91 | parameters: 92 | jobname: 93 | type: string 94 | steps: 95 | - attach_workspace: 96 | at: ~/ 97 | - run: 98 | command: npm run test 99 | working_directory: test-apps/<< parameters.jobname >> 100 | - store_artifacts: 101 | path: test-apps/<< parameters.jobname >>/coverage 102 | - run: 103 | name: Verify Code Coverage 104 | command: npm run coverage:verify 105 | working_directory: test-apps/<< parameters.jobname >> 106 | - run: 107 | name: Check code coverage files 📈 108 | # we will check the final coverage report 109 | # to make sure it only has files we are interested in 110 | # because there are files covered at 0 in the report 111 | command: npm run coverage:check-files 112 | working_directory: test-apps/<< parameters.jobname >> 113 | 114 | test-code-coverage-plugin: 115 | docker: 116 | - image: cypress/base:16.18.1 117 | steps: 118 | - attach_workspace: 119 | at: ~/ 120 | - run: 121 | command: npm run test 122 | - store_artifacts: 123 | path: coverage 124 | - run: 125 | name: Verify Code Coverage 126 | command: npm run coverage:verify 127 | 128 | workflows: 129 | build: 130 | jobs: 131 | - install_and_persist 132 | - lint: 133 | requires: 134 | - install_and_persist 135 | 136 | - test-code-coverage-plugin: 137 | requires: 138 | - install_and_persist 139 | 140 | - cyrun: 141 | name: test-<< matrix.jobname>> 142 | requires: 143 | - install_and_persist 144 | matrix: 145 | parameters: 146 | jobname: 147 | - all-files 148 | - backend 149 | - batch-send-coverage 150 | - before-all-visit 151 | - before-each-visit 152 | - cra-e2e-and-ct 153 | - exclude-files 154 | - frontend 155 | - fullstack 156 | - multiple-backends 157 | - one-spec 158 | - same-folder 159 | - support-files 160 | - ts-example 161 | - unit-tests-js 162 | - use-webpack 163 | - redirect 164 | - windows_test 165 | - publish: 166 | filters: 167 | branches: 168 | only: 169 | - master 170 | - beta 171 | - next 172 | - dev 173 | requires: 174 | - lint 175 | - test-code-coverage-plugin 176 | - test-all-files 177 | - test-backend 178 | - test-batch-send-coverage 179 | - test-before-all-visit 180 | - test-before-each-visit 181 | - test-cra-e2e-and-ct 182 | - test-exclude-files 183 | - test-frontend 184 | - test-fullstack 185 | - test-multiple-backends 186 | - test-one-spec 187 | - test-same-folder 188 | - test-support-files 189 | - test-ts-example 190 | - test-unit-tests-js 191 | - test-use-webpack 192 | - test-redirect 193 | - windows_test 194 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | --- 8 | 9 | **Logs and screenshots** 10 | Please provide debug logs by running Cypress from the terminal with `DEBUG=code-coverage` environment variable set. See the [Debugging](https://github.com/cypress-io/code-coverage#debugging) section of the README file. 11 | 12 | **Versions** 13 | 14 | - What is this plugin's version? If this is NOT the latest [released version](https://github.com/cypress-io/code-coverage/releases), can you try the latest version, please? 15 | - If the plugin worked before in version X but stopped after upgrading to version Y, please try the [released versions](https://github.com/cypress-io/code-coverage/releases) between X and Y to see where the breaking change was. 16 | - What is the Cypress version? 17 | - What is your operating system? 18 | - What is the shell? 19 | - What is the Node version? 20 | - What is the NPM version? 21 | - How do you instrument your application? Cypress [does not instrument web application code](https://github.com/cypress-io/code-coverage#instrument-your-application), so you must do it yourself. 22 | - When running tests, if you open the web application in a regular browser and open DevTools, do you see `window.__coverage__` object? Can you paste a screenshot? 23 | - Is there a `.nyc_output` folder? Is there a `.nyc_output/out.json` file? Is it empty? Can you paste at least part of it so we can see the keys and file paths? 24 | - Do you have any custom NYC settings in `package.json` (`nyc` object) or in other [NYC config files](https://github.com/istanbuljs/nyc#configuration-files)? 25 | - Do you run Cypress tests in a Docker container? 26 | 27 | **Describe the bug** 28 | A clear and concise description of what the bug is. 29 | 30 | **Link to the repo** 31 | Bugs with a reproducible example, like an open-source repo showing the bug, are the most likely to be resolved. 32 | 33 | **Example** 34 | See [#217](https://github.com/cypress-io/code-coverage/issues/217) that is an excellent bug report example 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Cypress Community Forum 4 | url: https://on.cypress.io/chat 5 | about: Please ask and answer questions here. 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/add-to-triage-board.yml: -------------------------------------------------------------------------------- 1 | name: 'Add issue/PR to Triage Board' 2 | on: 3 | issues: 4 | types: 5 | - opened 6 | pull_request_target: 7 | types: 8 | - opened 9 | jobs: 10 | add-to-triage-project-board: 11 | uses: cypress-io/cypress/.github/workflows/triage_add_to_project.yml@develop 12 | secrets: inherit 13 | -------------------------------------------------------------------------------- /.github/workflows/snyk_sca_scan.yaml: -------------------------------------------------------------------------------- 1 | name: Snyk Software Composition Analysis Scan 2 | # This git workflow leverages Snyk actions to perform a Software Composition 3 | # Analysis scan on our Opensource libraries upon Pull Requests to Master & 4 | # Develop branches. We use this as a control to prevent vulnerable packages 5 | # from being introduced into the codebase. 6 | on: 7 | pull_request: 8 | branches: 9 | - master 10 | - develop 11 | jobs: 12 | Snyk_SCA_Scan: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | node-version: [20.x] 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Setting up Node 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | - name: Installing snyk-delta and dependencies 24 | run: npm i -g snyk-delta 25 | - uses: snyk/actions/setup@master 26 | - name: Perform SCA Scan 27 | continue-on-error: false 28 | run: | 29 | snyk test --all-projects --detection-depth=4 --exclude=docker,Dockerfile,test-apps --severity-threshold=critical 30 | env: 31 | SNYK_TOKEN: ${{ secrets.SNYK_API_TOKEN }} 32 | -------------------------------------------------------------------------------- /.github/workflows/snyk_static_analysis_scan.yaml: -------------------------------------------------------------------------------- 1 | name: Snyk Static Analysis Scan 2 | # This SAST (Static Application Security Testing) scan is used to scan 3 | # our first-party code for security vulnerabilities 4 | on: 5 | pull_request: 6 | branches: 7 | - master 8 | - develop 9 | jobs: 10 | Snyk_SAST_Scan: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: snyk/actions/setup@master 15 | - name: Perform Static Analysis Test 16 | env: 17 | SNYK_TOKEN: ${{ secrets.SNYK_API_TOKEN }} 18 | continue-on-error: true 19 | run: snyk code test --all-projects --strict-out-of-sync=false --detection-depth=6 --exclude=docker,Dockerfile --severity-threshold=high 20 | -------------------------------------------------------------------------------- /.github/workflows/triage_closed_issue_comment.yml: -------------------------------------------------------------------------------- 1 | name: 'Handle Comment Workflow' 2 | on: 3 | issue_comment: 4 | types: 5 | - created 6 | jobs: 7 | closed-issue-comment: 8 | uses: cypress-io/cypress/.github/workflows/triage_handle_new_comments.yml@develop 9 | secrets: inherit 10 | -------------------------------------------------------------------------------- /.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 | yarn.lock 11 | .parcel-cache 12 | -------------------------------------------------------------------------------- /.nycrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": [ 3 | "support.js", 4 | "task-utils.js" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "none", 3 | "tabWidth": 2, 4 | "semi": false, 5 | "singleQuote": true, 6 | "bracketSpacing": true 7 | } 8 | -------------------------------------------------------------------------------- /.releaserc: -------------------------------------------------------------------------------- 1 | { 2 | "branches": [ 3 | "master", 4 | "next", 5 | { 6 | "name": "besta", 7 | "prerelease": true 8 | }, 9 | { 10 | "name": "dev", 11 | "prerelease": true 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ## MIT License 2 | 3 | Copyright (c) 2019 Cypress.io https://www.cypress.io 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 | -------------------------------------------------------------------------------- /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 | module.exports = { 65 | combineNycOptions, 66 | defaultNycOptions, 67 | fileCoveragePlaceholder, 68 | removePlaceholders 69 | } 70 | -------------------------------------------------------------------------------- /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.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | viewportHeight: 200, 5 | viewportWidth: 200, 6 | e2e: { 7 | // We've imported your old cypress plugins here. 8 | // You may want to clean this up later by importing these. 9 | setupNodeEvents(on, config) { 10 | return require('./cypress/plugins/index.js')(on, config) 11 | }, 12 | baseUrl: 'http://localhost:1234' 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /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 | - [e2e](e2e) 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.config.js` file 15 | 16 | You can configure project options in the [../cypress.config.js](../cypress.config.js) 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /cypress/e2e/filtering.cy.js: -------------------------------------------------------------------------------- 1 | const { filterFilesFromCoverage } = require('../../support-utils') 2 | 3 | describe('minimatch', () => { 4 | it('string matches', () => { 5 | expect( 6 | Cypress.minimatch('/user/app/src/codeA.js', '/user/app/src/codeA.js'), 7 | 'matches full strings' 8 | ).to.be.true 9 | 10 | expect( 11 | Cypress.minimatch('/user/app/src/codeA.js', 'codeA.js'), 12 | 'does not match just the end' 13 | ).to.be.false 14 | 15 | expect( 16 | Cypress.minimatch('/user/app/src/codeA.js', '**/codeA.js'), 17 | 'matches using **' 18 | ).to.be.true 19 | }) 20 | }) 21 | 22 | describe('filtering specs', () => { 23 | describe('using integrationFolder and testFiles in Cypress < v10', () => { 24 | let config 25 | let env 26 | let spec 27 | 28 | beforeEach(() => { 29 | config = cy.stub() 30 | config.withArgs('integrationFolder').returns('/user/app/cypress/integration') 31 | config 32 | .withArgs('supportFile') 33 | .returns('/user/app/cypress/support/index.js') 34 | config 35 | .withArgs('supportFolder') 36 | .returns('/user/app/cypress/support') 37 | 38 | env = cy.stub().returns({}) 39 | 40 | spec = { 41 | absolute: '/user/app/cypress/integration/test.cy.js', 42 | relative: 'cypress/integration/test.cy.js' 43 | } 44 | }) 45 | 46 | it('filters list of specs by single string', () => { 47 | config.withArgs('testFiles').returns('specA.js') 48 | const totalCoverage = { 49 | '/user/app/cypress/integration/specA.js': {}, 50 | '/user/app/cypress/integration/specB.js': {} 51 | } 52 | const result = filterFilesFromCoverage(totalCoverage, config, env, spec) 53 | expect(result).to.deep.equal({ 54 | '/user/app/cypress/integration/specB.js': {} 55 | }) 56 | }) 57 | 58 | it('filters list of specs by single string in array', () => { 59 | config.withArgs('testFiles').returns(['codeA.js']) 60 | const totalCoverage = { 61 | '/user/app/src/codeA.js': {}, 62 | '/user/app/src/codeB.js': {} 63 | } 64 | const result = filterFilesFromCoverage(totalCoverage, config, env, spec) 65 | expect(result).to.deep.equal({ 66 | '/user/app/src/codeB.js': {} 67 | }) 68 | }) 69 | 70 | it('filters list of specs by pattern', () => { 71 | config.withArgs('testFiles').returns(['**/*B.js']) 72 | 73 | const totalCoverage = { 74 | '/user/app/src/codeA.js': {}, 75 | '/user/app/src/codeB.js': {} 76 | } 77 | const result = filterFilesFromCoverage(totalCoverage, config, env, spec) 78 | expect(result).to.deep.equal({ 79 | '/user/app/src/codeA.js': {} 80 | }) 81 | }) 82 | 83 | it('filters list of specs by pattern and single spec', () => { 84 | config.withArgs('testFiles').returns(['**/*B.js', 'codeA.js']) 85 | 86 | const totalCoverage = { 87 | '/user/app/src/codeA.js': {}, 88 | '/user/app/src/codeB.js': {} 89 | } 90 | const result = filterFilesFromCoverage(totalCoverage, config, env, spec) 91 | expect(result, 'all specs have been filtered out').to.deep.equal({}) 92 | }) 93 | 94 | it('filters specs from integration folder', () => { 95 | config.withArgs('testFiles').returns('**/*.*') // default pattern 96 | 97 | const totalCoverage = { 98 | '/user/app/src/codeA.js': {}, 99 | '/user/app/src/codeB.js': {}, 100 | // these files should be removed 101 | '/user/app/cypress/integration/spec1.js': {}, 102 | '/user/app/cypress/integration/spec2.js': {} 103 | } 104 | const result = filterFilesFromCoverage(totalCoverage, config, env, spec) 105 | expect(result).to.deep.equal({ 106 | '/user/app/src/codeA.js': {}, 107 | '/user/app/src/codeB.js': {} 108 | }) 109 | }) 110 | 111 | it('filters list of specs when testFiles specifies folder', () => { 112 | config.withArgs('testFiles').returns(['cypress/integration/**.*']) 113 | 114 | const totalCoverage = { 115 | '/user/app/cypress/integration/specA.js': {}, 116 | '/user/app/cypress/integration/specB.js': {}, 117 | // This file should be included in coverage 118 | 'src/my-code.js': {} 119 | } 120 | const result = filterFilesFromCoverage(totalCoverage, config, env, spec) 121 | expect(result).to.deep.equal({ 122 | 'src/my-code.js': {} 123 | }) 124 | }) 125 | 126 | it('filters files out of cypress support directory', () => { 127 | config.withArgs('testFiles').returns(['**/*.*']) // default pattern 128 | const totalCoverage = { 129 | '/user/app/cypress/support/index.js': {}, 130 | '/user/app/cypress/support/command.js': {}, 131 | '/user/app/cypress/integration/spec.js': {}, 132 | // This file should be included in coverage 133 | 'src/my-code.js': {} 134 | } 135 | const result = filterFilesFromCoverage(totalCoverage, config, env, spec) 136 | expect(result).to.deep.equal({ 137 | 'src/my-code.js': {} 138 | }) 139 | }) 140 | }) 141 | 142 | describe('using codeCoverage.exclude and specPattern in Cypress >= v10', () => { 143 | let config 144 | let env 145 | let spec 146 | 147 | beforeEach(() => { 148 | config = cy.stub() 149 | 150 | env = cy.stub().returns({ 151 | //filter out all files in the cypress folder 152 | codeCoverage: { 153 | exclude: 'cypress/**/*.*' 154 | } 155 | }) 156 | 157 | spec = { 158 | absolute: '/user/app/cypress/integration/test.cy.js', 159 | relative: 'cypress/integration/test.cy.js' 160 | } 161 | }) 162 | 163 | it('filters list of specs by single string', () => { 164 | config.withArgs('specPattern').returns('specA.cy.js') 165 | const totalCoverage = { 166 | '/user/app/src/specA.cy.js': {}, 167 | '/user/app/src/specB.cy.js': {} 168 | } 169 | const result = filterFilesFromCoverage(totalCoverage, config, env, spec) 170 | expect(result).to.deep.equal({ 171 | '/user/app/src/specB.cy.js': {} 172 | }) 173 | }) 174 | 175 | it('filters list of specs by single string in array', () => { 176 | config.withArgs('specPattern').returns(['specA.cy.js']) 177 | const totalCoverage = { 178 | '/user/app/src/specA.cy.js': {}, 179 | '/user/app/src/specB.cy.js': {} 180 | } 181 | const result = filterFilesFromCoverage(totalCoverage, config, env, spec) 182 | expect(result).to.deep.equal({ 183 | '/user/app/src/specB.cy.js': {} 184 | }) 185 | }) 186 | 187 | it('filters out file in codeCoverage.exclude', () => { 188 | config.withArgs('specPattern').returns(['**/*.cy.js']) 189 | const totalCoverage = { 190 | '/user/app/cypress/support/index.js': {}, 191 | '/user/app/cypress/commands/index.js': {}, 192 | //these files should be included 193 | '/user/app/src/codeA.js': {}, 194 | '/user/app/src/codeB.js': {} 195 | } 196 | const result = filterFilesFromCoverage(totalCoverage, config, env, spec) 197 | expect(result).to.deep.equal({ 198 | '/user/app/src/codeA.js': {}, 199 | '/user/app/src/codeB.js': {} 200 | }) 201 | }) 202 | 203 | it('filters list of specs by pattern', () => { 204 | config.withArgs('specPattern').returns(['**/*B.js']) 205 | 206 | const totalCoverage = { 207 | '/user/app/src/codeA.js': {}, 208 | '/user/app/src/codeB.js': {} 209 | } 210 | const result = filterFilesFromCoverage(totalCoverage, config, env, spec) 211 | expect(result).to.deep.equal({ 212 | '/user/app/src/codeA.js': {} 213 | }) 214 | }) 215 | 216 | it('filters list of specs by pattern and single spec', () => { 217 | config.withArgs('specPattern').returns(['**/*B.js', 'codeA.js']) 218 | 219 | const totalCoverage = { 220 | '/user/app/src/codeA.js': {}, 221 | '/user/app/src/codeB.js': {} 222 | } 223 | const result = filterFilesFromCoverage(totalCoverage, config, env, spec) 224 | expect(result, 'all specs have been filtered out').to.deep.equal({}) 225 | }) 226 | 227 | it('filters list of specs in integration folder', () => { 228 | config.withArgs('specPattern').returns('**/*.cy.{js,jsx,ts,tsx}') // default pattern 229 | 230 | const totalCoverage = { 231 | '/user/app/src/codeA.js': {}, 232 | '/user/app/src/codeB.js': {}, 233 | // these files should be removed 234 | '/user/app/cypress/integration/spec1.js': {}, 235 | '/user/app/cypress/integration/spec2.js': {} 236 | } 237 | const result = filterFilesFromCoverage(totalCoverage, config, env, spec) 238 | expect(result).to.deep.equal({ 239 | '/user/app/src/codeA.js': {}, 240 | '/user/app/src/codeB.js': {} 241 | }) 242 | }) 243 | 244 | it('filters list of specs when specPattern specifies folder', () => { 245 | config.withArgs('specPattern').returns(['src/**/*.cy.js']) 246 | 247 | const totalCoverage = { 248 | '/user/app/src/specA.cy.js': {}, 249 | '/user/app/src/specB.cy.js': {}, 250 | // This file should be included in coverage 251 | 'src/my-code.js': {} 252 | } 253 | const result = filterFilesFromCoverage(totalCoverage, config, env, spec) 254 | expect(result).to.deep.equal({ 255 | 'src/my-code.js': {} 256 | }) 257 | }) 258 | 259 | it('filters list of specs when exclude pattern is an array', () => { 260 | env = cy.stub().returns({ 261 | //filter out a.js and b.js in cypress folder 262 | codeCoverage: { 263 | exclude: ['cypress/**/a.js', 'cypress/**/b.js'] 264 | } 265 | }) 266 | 267 | config.withArgs('specPattern').returns(['src/**/*.cy.js']) 268 | 269 | const totalCoverage = { 270 | '/user/app/cypress/a.js': {}, 271 | '/user/app/cypress/b.js': {}, 272 | // These files should be included in coverage 273 | '/user/app/cypress/c.js': {}, 274 | 'src/my-code.js': {} 275 | } 276 | const result = filterFilesFromCoverage(totalCoverage, config, env, spec) 277 | expect(result).to.deep.equal({ 278 | '/user/app/cypress/c.js': {}, 279 | 'src/my-code.js': {} 280 | }) 281 | }) 282 | }) 283 | }) 284 | -------------------------------------------------------------------------------- /cypress/e2e/fix-source-paths.cy.js: -------------------------------------------------------------------------------- 1 | import { fixSourcePaths } from '../../support-utils' 2 | 3 | describe('fixSourcePaths', () => { 4 | it('fixes webpack loader source-map pathes', () => { 5 | const coverage = { 6 | '/absolute/src/component.vue': { 7 | path: '/absolute/src/component.vue', 8 | inputSourceMap: { 9 | sources: [ 10 | '/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&', 11 | 'otherFile.js' 12 | ], 13 | sourceRoot: 'src' 14 | } 15 | }, 16 | '/folder/module-without-sourcemap.js': { 17 | path: '/folder/module-without-sourcemap.js' 18 | } 19 | } 20 | 21 | fixSourcePaths(coverage) 22 | 23 | expect(coverage).to.deep.eq({ 24 | '/absolute/src/component.vue': { 25 | path: '/absolute/src/component.vue', 26 | inputSourceMap: { 27 | sources: ['/absolute/src/component.vue', 'otherFile.js'], 28 | sourceRoot: '' 29 | } 30 | }, 31 | '/folder/module-without-sourcemap.js': { 32 | path: '/folder/module-without-sourcemap.js' 33 | } 34 | }) 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /cypress/index.html: -------------------------------------------------------------------------------- 1 | 2 |

Test page

3 | 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | import '../../support' 2 | -------------------------------------------------------------------------------- /images/coverage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypress-io/code-coverage/127b589b25e5a9a22657ac6c89277044886bfb9e/images/coverage.jpg -------------------------------------------------------------------------------- /images/expect-backend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypress-io/code-coverage/127b589b25e5a9a22657ac6c89277044886bfb9e/images/expect-backend.png -------------------------------------------------------------------------------- /images/gui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypress-io/code-coverage/127b589b25e5a9a22657ac6c89277044886bfb9e/images/gui.png -------------------------------------------------------------------------------- /images/instrumented-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypress-io/code-coverage/127b589b25e5a9a22657ac6c89277044886bfb9e/images/instrumented-code.png -------------------------------------------------------------------------------- /images/warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypress-io/code-coverage/127b589b25e5a9a22657ac6c89277044886bfb9e/images/warning.png -------------------------------------------------------------------------------- /images/window-coverage-object.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypress-io/code-coverage/127b589b25e5a9a22657ac6c89277044886bfb9e/images/window-coverage-object.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cypress/code-coverage", 3 | "version": "0.0.0-development", 4 | "description": "Saves the code coverage collected during Cypress tests", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "parcel serve cypress/index.html", 8 | "coverage:verify": "npx nyc report --check-coverage true --lines 80", 9 | "cy:open": "cypress open", 10 | "dev": "start-test 1234 cy:open", 11 | "semantic-release": "semantic-release", 12 | "test": "start-test 1234 'npx cypress run'", 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 | }, 20 | "peerDependencies": { 21 | "@babel/core": "^7.0.1", 22 | "@babel/preset-env": "^7.0.0", 23 | "babel-loader": "^8.3 || ^9 || ^10", 24 | "cypress": "*", 25 | "webpack": "^4 || ^5" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "https://github.com/cypress-io/code-coverage.git" 30 | }, 31 | "keywords": [ 32 | "cypress", 33 | "istanbul", 34 | "cypress-plugin", 35 | "code", 36 | "coverage" 37 | ], 38 | "author": "Cypress (http://www.cypress.io/)", 39 | "license": "MIT", 40 | "bugs": { 41 | "url": "https://github.com/cypress-io/code-coverage/issues" 42 | }, 43 | "homepage": "https://github.com/cypress-io/code-coverage#readme", 44 | "files": [ 45 | "*.d.ts", 46 | "*.js", 47 | "middleware" 48 | ], 49 | "publishConfig": { 50 | "access": "public" 51 | }, 52 | "private": false, 53 | "dependencies": { 54 | "@cypress/webpack-preprocessor": "^6.0.0", 55 | "chalk": "4.1.2", 56 | "dayjs": "1.11.13", 57 | "debug": "4.4.0", 58 | "execa": "4.1.0", 59 | "globby": "11.1.0", 60 | "istanbul-lib-coverage": "^3.0.0", 61 | "js-yaml": "4.1.0", 62 | "nyc": "15.1.0" 63 | }, 64 | "devDependencies": { 65 | "@babel/core": "^7.16.0", 66 | "@babel/preset-env": "^7.16.11", 67 | "@cypress/code-coverage": "file:.", 68 | "babel-loader": "^9.1.3", 69 | "babel-plugin-istanbul": "6.1.1", 70 | "check-code-coverage": "1.10.5", 71 | "console-log-div": "0.6.3", 72 | "cypress": "^13.1.0", 73 | "express": "^4.18.2", 74 | "lodash": "4.17.21", 75 | "markdown-link-check": "3.13.6", 76 | "parcel-bundler": "1.12.5", 77 | "prettier": "3.5.3", 78 | "rimraf": "6.0.1", 79 | "semantic-release": "17.4.7", 80 | "serve": "14.2.4", 81 | "start-server-and-test": "2.0.12", 82 | "webpack": "^5.68.0", 83 | "webpack-cli": "^5.1.4" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /plugins.js: -------------------------------------------------------------------------------- 1 | // common Cypress plugin file you can point at to have the 2 | // code coverage tasks registered correctly. From your "cypress.json" file 3 | // { 4 | // "pluginsFile": "@cypress/code-coverage/plugins", 5 | // "supportFile": "@cypress/code-coverage/support" 6 | // } 7 | // 8 | module.exports = (on, config) => { 9 | require('./task')(on, config) 10 | // IMPORTANT to return the config object 11 | // with the any changed environment variables 12 | return config 13 | } 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /support-utils.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // helper functions that are safe to use in the browser 3 | // from support.js file - no file system access 4 | 5 | /** excludes files that shouldn't be in code coverage report */ 6 | const filterFilesFromCoverage = ( 7 | totalCoverage, 8 | config = Cypress.config, 9 | env = Cypress.env, 10 | spec = Cypress.spec 11 | ) => { 12 | const withoutSpecs = filterSpecsFromCoverage(totalCoverage, config, env, spec) 13 | const appCoverageOnly = filterSupportFilesFromCoverage(withoutSpecs, config) 14 | return appCoverageOnly 15 | } 16 | 17 | /** 18 | * remove coverage for the spec files themselves, 19 | * only keep "external" application source file coverage 20 | */ 21 | const filterSpecsFromCoverage = ( 22 | totalCoverage, 23 | config = Cypress.config, 24 | env = Cypress.env, 25 | spec = Cypress.spec 26 | ) => { 27 | const testFilePatterns = getCypressExcludePatterns(config, env, spec) 28 | 29 | const isTestFile = (_, filePath) => { 30 | const workingDir = spec.absolute.replace(spec.relative, '') 31 | const filename = filePath.replace(workingDir, '') 32 | const matchedPattern = testFilePatterns.some((specPattern) => 33 | Cypress.minimatch(filename, specPattern, { debug: false }) 34 | ) 35 | const matchedEndOfPath = testFilePatterns.some((specPattern) => 36 | filename.endsWith(specPattern) 37 | ) 38 | return matchedPattern || matchedEndOfPath 39 | } 40 | 41 | const coverage = Cypress._.omitBy(totalCoverage, isTestFile) 42 | return coverage 43 | } 44 | 45 | /** 46 | * Reads Cypress config and exclude patterns and combines them into one array 47 | * @param {*} config 48 | * @param {*} env 49 | * @returns string[] 50 | */ 51 | function getCypressExcludePatterns(config, env, spec) { 52 | let testFilePatterns = [] 53 | 54 | const testFilePattern = config('specPattern') || config('testFiles') 55 | const excludePattern = env().codeCoverage && env().codeCoverage.exclude 56 | 57 | if (Array.isArray(testFilePattern)) { 58 | testFilePatterns = testFilePattern 59 | } else { 60 | testFilePatterns = [testFilePattern] 61 | } 62 | 63 | // combine test files pattern and exclude patterns into single exclude pattern 64 | if (Array.isArray(excludePattern)) { 65 | testFilePatterns = [...testFilePatterns, ...excludePattern] 66 | } else if (excludePattern) { 67 | testFilePatterns = [...testFilePatterns, excludePattern] 68 | } 69 | 70 | // Cypress { 77 | if (pattern === '**/*.*') { 78 | testFilePatterns[idx] = `${integrationFolder}/${pattern}`.replace( 79 | '//', 80 | '/' 81 | ) 82 | } 83 | }) 84 | } 85 | 86 | return testFilePatterns 87 | } 88 | 89 | /** 90 | * Removes support file from the coverage object. 91 | * If there are more files loaded from support folder, also removes them 92 | */ 93 | const filterSupportFilesFromCoverage = ( 94 | totalCoverage, 95 | config = Cypress.config 96 | ) => { 97 | const integrationFolder = config('integrationFolder') 98 | 99 | /** 100 | * Cypress v10 doesn't have an integrationFolder config value any more, so we nope out here if its undefined. 101 | * Instead, we rely on the new exclude option logic done in the getCypressExcludePatterns function. 102 | */ 103 | if (!integrationFolder) { 104 | return totalCoverage 105 | } 106 | 107 | const supportFile = config('supportFile') 108 | 109 | /** @type {string} Cypress run-time config has the support folder string */ 110 | // @ts-ignore 111 | const supportFolder = config('supportFolder') 112 | 113 | const isSupportFile = (filename) => filename === supportFile 114 | 115 | let coverage = Cypress._.omitBy(totalCoverage, (fileCoverage, filename) => 116 | isSupportFile(filename) 117 | ) 118 | 119 | // check the edge case 120 | // if we have files from support folder AND the support folder is not same 121 | // as the integration, or its prefix (this might remove all app source files) 122 | // then remove all files from the support folder 123 | if (!integrationFolder.startsWith(supportFolder)) { 124 | // remove all covered files from support folder 125 | coverage = Cypress._.omitBy(totalCoverage, (fileCoverage, filename) => 126 | filename.startsWith(supportFolder) 127 | ) 128 | } 129 | return coverage 130 | } 131 | 132 | /** 133 | * Replace source-map's path by the corresponding absolute file path 134 | * (coverage report wouldn't work with source-map path being relative 135 | * or containing Webpack loaders and query parameters) 136 | */ 137 | function fixSourcePaths(coverage) { 138 | Object.values(coverage).forEach((file) => { 139 | const { path: absolutePath, inputSourceMap } = file 140 | if (!inputSourceMap) return 141 | const fileName = /([^\/\\]+)$/.exec(absolutePath)[1] 142 | if (!fileName) return 143 | 144 | if (inputSourceMap.sourceRoot) inputSourceMap.sourceRoot = '' 145 | inputSourceMap.sources = inputSourceMap.sources.map((source) => 146 | source.includes(fileName) ? absolutePath : source 147 | ) 148 | }) 149 | } 150 | 151 | /** 152 | * Validates and returns the configured batch size for 153 | * sending coverage to the backend 154 | */ 155 | function getSendCoverageBatchSize() { 156 | const batchSize = Cypress.env('sendCoverageBatchSize') 157 | const parsedBatchSize = parseInt(batchSize) 158 | const isValid = !isNaN(parsedBatchSize) && parsedBatchSize > 0 159 | return isValid ? parsedBatchSize : null 160 | } 161 | 162 | module.exports = { 163 | fixSourcePaths, 164 | filterFilesFromCoverage, 165 | getSendCoverageBatchSize 166 | } 167 | -------------------------------------------------------------------------------- /support.js: -------------------------------------------------------------------------------- 1 | /// 2 | // @ts-check 3 | 4 | const dayjs = require('dayjs') 5 | var duration = require('dayjs/plugin/duration') 6 | const { 7 | filterFilesFromCoverage, 8 | getSendCoverageBatchSize 9 | } = require('./support-utils') 10 | 11 | dayjs.extend(duration) 12 | 13 | /** 14 | * Sends collected code coverage object to the backend code 15 | * via "cy.task". 16 | */ 17 | const sendCoverage = (coverage, pathname = '/') => { 18 | logMessage(`Saving code coverage for **${pathname}**`) 19 | 20 | const totalCoverage = filterFilesFromCoverage(coverage) 21 | 22 | const envBatchSize = getSendCoverageBatchSize() 23 | const keys = Object.keys(totalCoverage) 24 | 25 | if (envBatchSize && envBatchSize < keys.length) { 26 | sendBatchCoverage(totalCoverage, envBatchSize) 27 | } else { 28 | cy.task('combineCoverage', JSON.stringify(totalCoverage), { 29 | log: false 30 | }) 31 | } 32 | } 33 | 34 | /** 35 | * Sends collected code coverage object to the backend code 36 | * in batches via "cy.task". 37 | */ 38 | const sendBatchCoverage = (totalCoverage, batchSize) => { 39 | const keys = Object.keys(totalCoverage) 40 | 41 | for (let i = 0; i < keys.length; i += batchSize) { 42 | const batchKeys = keys.slice(i, i + batchSize) 43 | const batchCoverage = {} 44 | 45 | batchKeys.forEach((key) => { 46 | batchCoverage[key] = totalCoverage[key] 47 | }) 48 | 49 | cy.task('combineCoverage', JSON.stringify(batchCoverage), { 50 | log: false 51 | }) 52 | } 53 | } 54 | 55 | /** 56 | * Consistently logs the given string to the Command Log 57 | * so the user knows the log message is coming from this plugin. 58 | * @param {string} s Message to log. 59 | */ 60 | const logMessage = (s) => { 61 | cy.log(`${s} \`[@cypress/code-coverage]\``) 62 | } 63 | 64 | const registerHooks = () => { 65 | let windowCoverageObjects 66 | 67 | const hasE2ECoverage = () => Boolean(windowCoverageObjects.length) 68 | 69 | // @ts-ignore 70 | const hasUnitTestCoverage = () => Boolean(window.__coverage__) 71 | 72 | before(() => { 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 | const logInstance = Cypress.log({ 77 | name: 'Coverage', 78 | message: ['Reset [@cypress/code-coverage]'] 79 | }) 80 | 81 | cy.task( 82 | 'resetCoverage', 83 | { 84 | // @ts-ignore 85 | isInteractive: Cypress.config('isInteractive') 86 | }, 87 | { log: false } 88 | ).then(() => { 89 | logInstance.end() 90 | }) 91 | }) 92 | 93 | beforeEach(() => { 94 | // each object will have the coverage and url pathname 95 | // to let the user know the coverage has been collected 96 | windowCoverageObjects = [] 97 | 98 | const saveCoverageObject = (win) => { 99 | // if the application code has been instrumented, then the app iframe "win.__coverage__" will be available, 100 | // in addition, accessing win.__coverage__ can throw when testing cross-origin code, 101 | // because we don't control the cross-origin code, we can safely return 102 | let applicationSourceCoverage 103 | try { 104 | // Note that we are purposefully not supporting the optional chaining syntax here to 105 | // support a wide range of projects (some of which are not set up to support the optional 106 | // chaining syntax due to current Cypress limitations). See: 107 | // https://github.com/cypress-io/cypress/issues/20753 108 | if (win) { 109 | applicationSourceCoverage = win.__coverage__ 110 | } 111 | } catch {} 112 | 113 | if (!applicationSourceCoverage) { 114 | return 115 | } 116 | 117 | const existingCoverage = Cypress._.find(windowCoverageObjects, { 118 | coverage: applicationSourceCoverage 119 | }) 120 | if (existingCoverage) { 121 | // this application code coverage object is already known 122 | // which can happen when combining `window:load` and `before` callbacks, 123 | // it can also happen when the user navigates away and then returns to the page 124 | // in which case we need to use new applicationSourceCoverage, because the old will not be updated anymore. 125 | existingCoverage.coverage = applicationSourceCoverage 126 | return 127 | } 128 | 129 | windowCoverageObjects.push({ 130 | coverage: applicationSourceCoverage, 131 | pathname: win.location.pathname 132 | }) 133 | } 134 | 135 | // save reference to coverage for each app window loaded in the test 136 | cy.on('window:load', saveCoverageObject) 137 | 138 | // save reference if visiting a page inside a before() hook 139 | cy.window({ log: false }).then(saveCoverageObject) 140 | }) 141 | 142 | afterEach(() => { 143 | // save coverage after the test 144 | // because now the window coverage objects have been updated 145 | windowCoverageObjects.forEach((cover) => { 146 | sendCoverage(cover.coverage, cover.pathname) 147 | }) 148 | 149 | if (!hasE2ECoverage()) { 150 | if (hasUnitTestCoverage()) { 151 | logMessage(`👉 Only found unit test code coverage.`) 152 | } else { 153 | const expectBackendCoverageOnly = Cypress._.get( 154 | Cypress.env('codeCoverage'), 155 | 'expectBackendCoverageOnly', 156 | false 157 | ) 158 | if (!expectBackendCoverageOnly) { 159 | logMessage(` 160 | ⚠️ Could not find any coverage information in your application 161 | by looking at the window coverage object. 162 | Did you forget to instrument your application? 163 | See [code-coverage#instrument-your-application](https://github.com/cypress-io/code-coverage#instrument-your-application) 164 | `) 165 | } 166 | } 167 | } 168 | }) 169 | 170 | after(function collectBackendCoverage() { 171 | // I wish I could fail the tests if there is no code coverage information 172 | // but throwing an error here does not fail the test run due to 173 | // https://github.com/cypress-io/cypress/issues/2296 174 | 175 | // there might be server-side code coverage information 176 | // we should grab it once after all tests finish 177 | // @ts-ignore 178 | const baseUrl = Cypress.config('baseUrl') || cy.state('window').origin 179 | // @ts-ignore 180 | const runningEndToEndTests = baseUrl !== Cypress.config('proxyUrl') 181 | const expectFrontendCoverageOnly = Cypress._.get( 182 | Cypress.env('codeCoverage'), 183 | 'expectFrontendCoverageOnly', 184 | false 185 | ) 186 | const specType = Cypress._.get(Cypress.spec, 'specType', 'integration') 187 | const isIntegrationSpec = specType === 'integration' 188 | 189 | if ( 190 | !expectFrontendCoverageOnly && 191 | runningEndToEndTests && 192 | isIntegrationSpec 193 | ) { 194 | // we can only request server-side code coverage 195 | // if we are running end-to-end tests, 196 | // otherwise where do we send the request? 197 | const captureUrls = Cypress._.get( 198 | Cypress.env('codeCoverage'), 199 | 'url', 200 | '/__coverage__' 201 | ) 202 | function captureCoverage(url, suffix = '') { 203 | cy.request({ 204 | url, 205 | log: false, 206 | failOnStatusCode: false 207 | }) 208 | .then((r) => { 209 | return Cypress._.get(r, 'body.coverage', null) 210 | }) 211 | .then((coverage) => { 212 | if (!coverage) { 213 | // we did not get code coverage - this is the 214 | // original failed request 215 | const expectBackendCoverageOnly = Cypress._.get( 216 | Cypress.env('codeCoverage'), 217 | 'expectBackendCoverageOnly', 218 | false 219 | ) 220 | if (expectBackendCoverageOnly) { 221 | throw new Error( 222 | `Expected to collect backend code coverage from ${url}` 223 | ) 224 | } else { 225 | // we did not really expect to collect the backend code coverage 226 | return 227 | } 228 | } 229 | sendCoverage(coverage, `backend${suffix}`) 230 | }) 231 | } 232 | 233 | if (Array.isArray(captureUrls)) { 234 | for (const [index, url] of captureUrls.entries()) { 235 | captureCoverage(url, `_${index}`) 236 | } 237 | } else { 238 | captureCoverage(captureUrls) 239 | } 240 | } 241 | }) 242 | 243 | after(function mergeUnitTestCoverage() { 244 | // collect and merge frontend coverage 245 | 246 | // if spec bundle has been instrumented (using Cypress preprocessor) 247 | // then we will have unit test coverage 248 | // NOTE: spec iframe is NOT reset between the tests, so we can grab 249 | // the coverage information only once after all tests have finished 250 | // @ts-ignore 251 | const unitTestCoverage = window.__coverage__ 252 | if (unitTestCoverage) { 253 | sendCoverage(unitTestCoverage, 'unit') 254 | } 255 | }) 256 | 257 | after(function generateReport() { 258 | // when all tests finish, lets generate the coverage report 259 | const logInstance = Cypress.log({ 260 | name: 'Coverage', 261 | message: ['Generating report [@cypress/code-coverage]'] 262 | }) 263 | cy.task('coverageReport', null, { 264 | timeout: dayjs.duration(3, 'minutes').asMilliseconds(), 265 | log: false 266 | }).then((coverageReportFolder) => { 267 | logInstance.set('consoleProps', () => ({ 268 | 'coverage report folder': coverageReportFolder 269 | })) 270 | logInstance.end() 271 | return coverageReportFolder 272 | }) 273 | }) 274 | } 275 | 276 | // to disable code coverage commands and save time 277 | // pass environment variable coverage=false 278 | // cypress run --env coverage=false 279 | // or 280 | // CYPRESS_COVERAGE=false cypress run 281 | // see https://on.cypress.io/environment-variables 282 | 283 | // to avoid "coverage" env variable being case-sensitive, convert to lowercase 284 | const cyEnvs = Cypress._.mapKeys(Cypress.env(), (value, key) => 285 | key.toLowerCase() 286 | ) 287 | 288 | if (cyEnvs.coverage === false) { 289 | console.log('Skipping code coverage hooks') 290 | } else if (Cypress.env('codeCoverageTasksRegistered') !== true) { 291 | // register a hook just to log a message 292 | before(() => { 293 | logMessage(` 294 | ⚠️ Code coverage tasks were not registered by the plugins file. 295 | See [support issue](https://github.com/cypress-io/code-coverage/issues/179) 296 | for possible workarounds. 297 | `) 298 | }) 299 | } else { 300 | registerHooks() 301 | } 302 | -------------------------------------------------------------------------------- /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 nycConfigCommonJsFilename = join(workingDirectory, 'nyc.config.cjs') 66 | let nycConfigCommonJs = {} 67 | if (existsSync(nycConfigCommonJsFilename)) { 68 | try { 69 | nycConfigCommonJs = require(nycConfigCommonJsFilename) 70 | } catch (error) { 71 | throw new Error(`Failed to load nyc.config.cjs: ${error.message}`) 72 | } 73 | } 74 | 75 | const nycOptions = combineNycOptions( 76 | defaultNycOptions, 77 | nycrc, 78 | nycrcJson, 79 | nycrcYaml, 80 | nycrcYml, 81 | nycConfig, 82 | nycConfigCommonJs, 83 | pkgNycOptions 84 | ) 85 | debug('combined NYC options %o', nycOptions) 86 | 87 | return nycOptions 88 | } 89 | 90 | function checkAllPathsNotFound(nycFilename) { 91 | const nycCoverage = JSON.parse(readFileSync(nycFilename, 'utf8')) 92 | 93 | const coverageKeys = Object.keys(nycCoverage) 94 | if (!coverageKeys.length) { 95 | debug('⚠️ file %s has no coverage information', nycFilename) 96 | return 97 | } 98 | 99 | const allFilesAreMissing = coverageKeys.every((key, k) => { 100 | const coverage = nycCoverage[key] 101 | return !existsSync(coverage.path) 102 | }) 103 | 104 | debug( 105 | 'in file %s all files are not found? %o', 106 | nycFilename, 107 | allFilesAreMissing 108 | ) 109 | return allFilesAreMissing 110 | } 111 | 112 | /** 113 | * A small debug utility to inspect paths saved in NYC output JSON file 114 | */ 115 | function showNycInfo(nycFilename) { 116 | const nycCoverage = JSON.parse(readFileSync(nycFilename, 'utf8')) 117 | 118 | const coverageKeys = Object.keys(nycCoverage) 119 | if (!coverageKeys.length) { 120 | console.error( 121 | '⚠️ file %s has no coverage information', 122 | chalk.yellow(nycFilename) 123 | ) 124 | console.error( 125 | 'Did you forget to instrument your web application? Read %s', 126 | chalk.blue( 127 | 'https://github.com/cypress-io/code-coverage#instrument-your-application' 128 | ) 129 | ) 130 | return 131 | } 132 | debug('NYC file %s has %d key(s)', nycFilename, coverageKeys.length) 133 | 134 | const maxPrintKeys = 3 135 | const showKeys = coverageKeys.slice(0, maxPrintKeys) 136 | 137 | showKeys.forEach((key, k) => { 138 | const coverage = nycCoverage[key] 139 | 140 | // printing a few found keys and file paths from the coverage file 141 | // will make debugging any problems much much easier 142 | if (k < maxPrintKeys) { 143 | debug('%d key %s file path %s', k + 1, key, coverage.path) 144 | } 145 | }) 146 | } 147 | 148 | /** 149 | * Looks at all coverage objects in the given JSON coverage file 150 | * and if the file is relative, and exists, changes its path to 151 | * be absolute. 152 | */ 153 | function resolveRelativePaths(nycFilename) { 154 | const nycCoverage = JSON.parse(readFileSync(nycFilename, 'utf8')) 155 | 156 | const coverageKeys = Object.keys(nycCoverage) 157 | if (!coverageKeys.length) { 158 | debug('⚠️ file %s has no coverage information', nycFilename) 159 | return 160 | } 161 | debug('NYC file %s has %d key(s)', nycFilename, coverageKeys.length) 162 | 163 | let changed 164 | 165 | coverageKeys.forEach((key, k) => { 166 | const coverage = nycCoverage[key] 167 | 168 | if (!coverage.path) { 169 | debug('key %s does not have path', key) 170 | return 171 | } 172 | 173 | if (!isAbsolute(coverage.path)) { 174 | if (existsSync(coverage.path)) { 175 | debug('resolving path %s', coverage.path) 176 | coverage.path = resolve(coverage.path) 177 | changed = true 178 | } 179 | return 180 | } 181 | 182 | // path is absolute, let's check if it exists 183 | if (!existsSync(coverage.path)) { 184 | debug('⚠️ cannot find file %s with hash %s', coverage.path, coverage.hash) 185 | } 186 | }) 187 | 188 | if (changed) { 189 | debug('resolveRelativePaths saving updated file %s', nycFilename) 190 | debug('there are %d keys in the file', coverageKeys.length) 191 | writeFileSync( 192 | nycFilename, 193 | JSON.stringify(nycCoverage, null, 2) + '\n', 194 | 'utf8' 195 | ) 196 | } 197 | } 198 | 199 | /** 200 | * @param {string[]} filepaths 201 | * @returns {string | undefined} common prefix that corresponds to current folder 202 | */ 203 | function findCommonRoot(filepaths) { 204 | if (!filepaths.length) { 205 | debug('cannot find common root without any files') 206 | return 207 | } 208 | 209 | // assuming / as file separator 210 | const splitParts = filepaths.map((name) => name.split('/')) 211 | const lengths = splitParts.map((arr) => arr.length) 212 | const shortestLength = Math.min.apply(null, lengths) 213 | debug('shorted file path has %d parts', shortestLength) 214 | 215 | const cwd = process.cwd() 216 | let commonPrefix = [] 217 | let foundCurrentFolder 218 | 219 | for (let k = 0; k < shortestLength; k += 1) { 220 | const part = splitParts[0][k] 221 | const prefix = commonPrefix.concat(part).join('/') 222 | debug('testing prefix %o', prefix) 223 | const allFilesStart = filepaths.every((name) => name.startsWith(prefix)) 224 | if (!allFilesStart) { 225 | debug('stopped at non-common prefix %s', prefix) 226 | break 227 | } 228 | 229 | commonPrefix.push(part) 230 | 231 | const removedPrefixNames = filepaths.map((filepath) => 232 | filepath.slice(prefix.length) 233 | ) 234 | debug('removedPrefix %o', removedPrefixNames) 235 | const foundAllPaths = removedPrefixNames.every((filepath) => 236 | existsSync(join(cwd, filepath)) 237 | ) 238 | debug('all files found at %s? %o', prefix, foundAllPaths) 239 | if (foundAllPaths) { 240 | debug('found prefix that matches current folder: %s', prefix) 241 | foundCurrentFolder = prefix 242 | break 243 | } 244 | } 245 | 246 | return foundCurrentFolder 247 | } 248 | 249 | function tryFindingLocalFiles(nycFilename) { 250 | const nycCoverage = JSON.parse(readFileSync(nycFilename, 'utf8')) 251 | const coverageKeys = Object.keys(nycCoverage) 252 | const filenames = coverageKeys.map((key) => nycCoverage[key].path) 253 | const commonFolder = findCommonRoot(filenames) 254 | if (!commonFolder) { 255 | debug('could not find common folder %s', commonFolder) 256 | return 257 | } 258 | const cwd = process.cwd() 259 | debug( 260 | 'found common folder %s that matches current working directory %s', 261 | commonFolder, 262 | cwd 263 | ) 264 | const length = commonFolder.length 265 | let changed 266 | 267 | coverageKeys.forEach((key) => { 268 | const from = nycCoverage[key].path 269 | if (from.startsWith(commonFolder)) { 270 | const to = join(cwd, from.slice(length)) 271 | // ? Do we need to replace the "key" in the coverage object or can we just replace the "path"? 272 | nycCoverage[key].path = to 273 | debug('replaced %s -> %s', from, to) 274 | changed = true 275 | } 276 | }) 277 | 278 | if (changed) { 279 | debug('tryFindingLocalFiles saving updated file %s', nycFilename) 280 | debug('there are %d keys in the file', coverageKeys.length) 281 | writeFileSync( 282 | nycFilename, 283 | JSON.stringify(nycCoverage, null, 2) + '\n', 284 | 'utf8' 285 | ) 286 | } 287 | } 288 | 289 | /** 290 | * Tries to find source files to be included in the final coverage report 291 | * using NYC options: extension list, include and exclude. 292 | */ 293 | function findSourceFiles(nycOptions) { 294 | debug('include all files options: %o', { 295 | all: nycOptions.all, 296 | include: nycOptions.include, 297 | exclude: nycOptions.exclude, 298 | extension: nycOptions.extension 299 | }) 300 | 301 | if (!Array.isArray(nycOptions.extension)) { 302 | console.error( 303 | 'Expected NYC "extension" option to be a list of file extensions' 304 | ) 305 | console.error(nycOptions) 306 | return [] 307 | } 308 | 309 | let patterns = [] 310 | if (Array.isArray(nycOptions.include)) { 311 | patterns = patterns.concat(nycOptions.include) 312 | } else if (typeof nycOptions.include === 'string') { 313 | patterns.push(nycOptions.include) 314 | } else { 315 | debug('using default list of extensions') 316 | nycOptions.extension.forEach((extension) => { 317 | patterns.push('**/*' + extension) 318 | }) 319 | } 320 | 321 | if (Array.isArray(nycOptions.exclude)) { 322 | const negated = nycOptions.exclude.map((s) => '!' + s) 323 | patterns = patterns.concat(negated) 324 | } else if (typeof nycOptions.exclude === 'string') { 325 | patterns.push('!' + nycOptions.exclude) 326 | } 327 | // always exclude node_modules 328 | // https://github.com/istanbuljs/nyc#including-files-within-node_modules 329 | patterns.push('!**/node_modules/**') 330 | 331 | debug('searching files to include using patterns %o', patterns) 332 | 333 | const allFiles = globby.sync(patterns, { absolute: true }) 334 | return allFiles 335 | } 336 | /** 337 | * If the website or unit tests did not load ALL files we need to 338 | * include, then we should include the missing files ourselves 339 | * before generating the report. 340 | * 341 | * @see https://github.com/cypress-io/code-coverage/issues/207 342 | */ 343 | function includeAllFiles(nycFilename, nycOptions) { 344 | if (!nycOptions.all) { 345 | debug('NYC "all" option is not set, skipping including all files') 346 | return 347 | } 348 | 349 | const allFiles = findSourceFiles(nycOptions) 350 | if (debug.enabled) { 351 | debug('found %d file(s)', allFiles.length) 352 | console.error(allFiles.join('\n')) 353 | } 354 | if (!allFiles.length) { 355 | debug('no files found, hoping for the best') 356 | return 357 | } 358 | 359 | const nycCoverage = JSON.parse(readFileSync(nycFilename, 'utf8')) 360 | const coverageKeys = Object.keys(nycCoverage) 361 | const coveredPaths = coverageKeys.map((key) => 362 | nycCoverage[key].path.replace(/\\/g, '/') 363 | ) 364 | 365 | debug('coverage has %d record(s)', coveredPaths.length) 366 | // report on first couple of entries 367 | if (debug.enabled) { 368 | console.error('coverage has the following first paths') 369 | console.error(coveredPaths.slice(0, 4).join('\n')) 370 | } 371 | 372 | let changed 373 | allFiles.forEach((fullPath) => { 374 | if (coveredPaths.includes(fullPath)) { 375 | // all good, this file exists in coverage object 376 | return 377 | } 378 | debug('adding empty coverage for file %s', fullPath) 379 | changed = true 380 | // insert placeholder object for now 381 | const placeholder = fileCoveragePlaceholder(fullPath) 382 | nycCoverage[fullPath] = placeholder 383 | }) 384 | 385 | if (changed) { 386 | debug('includeAllFiles saving updated file %s', nycFilename) 387 | debug('there are %d keys in the file', Object.keys(nycCoverage).length) 388 | 389 | writeFileSync( 390 | nycFilename, 391 | JSON.stringify(nycCoverage, null, 2) + '\n', 392 | 'utf8' 393 | ) 394 | } 395 | } 396 | 397 | module.exports = { 398 | showNycInfo, 399 | resolveRelativePaths, 400 | checkAllPathsNotFound, 401 | tryFindingLocalFiles, 402 | readNycOptions, 403 | includeAllFiles 404 | } 405 | -------------------------------------------------------------------------------- /task.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | export default function registerCodeCoverageTasks( 4 | on: Cypress.PluginEvents, 5 | config: Cypress.PluginConfigOptions, 6 | ): void; 7 | -------------------------------------------------------------------------------- /task.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const istanbul = require('istanbul-lib-coverage') 3 | const { join, resolve } = require('path') 4 | const { existsSync, mkdirSync, readFileSync, writeFileSync } = require('fs') 5 | const execa = require('execa') 6 | const { 7 | showNycInfo, 8 | resolveRelativePaths, 9 | checkAllPathsNotFound, 10 | tryFindingLocalFiles, 11 | readNycOptions, 12 | includeAllFiles 13 | } = require('./task-utils') 14 | const { fixSourcePaths } = require('./support-utils') 15 | 16 | const debug = require('debug')('code-coverage') 17 | 18 | // these are standard folder and file names used by NYC tools 19 | const processWorkingDirectory = process.cwd() 20 | 21 | // there might be custom "nyc" options in the user package.json 22 | // see https://github.com/istanbuljs/nyc#configuring-nyc 23 | // potentially there might be "nyc" options in other configuration files 24 | // it allows, but for now ignore those options 25 | const pkgFilename = join(processWorkingDirectory, 'package.json') 26 | const pkg = existsSync(pkgFilename) 27 | ? JSON.parse(readFileSync(pkgFilename, 'utf8')) 28 | : {} 29 | const scripts = pkg.scripts || {} 30 | const DEFAULT_CUSTOM_COVERAGE_SCRIPT_NAME = 'coverage:report' 31 | const customNycReportScript = scripts[DEFAULT_CUSTOM_COVERAGE_SCRIPT_NAME] 32 | 33 | const nycReportOptions = (function getNycOption() { 34 | // https://github.com/istanbuljs/nyc#common-configuration-options 35 | const nycReportOptions = readNycOptions(processWorkingDirectory) 36 | 37 | if (nycReportOptions.exclude && !Array.isArray(nycReportOptions.exclude)) { 38 | console.error('NYC options: %o', nycReportOptions) 39 | throw new Error('Expected "exclude" to be an array') 40 | } 41 | 42 | if (nycReportOptions['temp-dir']) { 43 | nycReportOptions['temp-dir'] = resolve(nycReportOptions['temp-dir']) 44 | } else { 45 | nycReportOptions['temp-dir'] = join(processWorkingDirectory, '.nyc_output') 46 | } 47 | 48 | nycReportOptions.tempDir = nycReportOptions['temp-dir'] 49 | 50 | if (nycReportOptions['report-dir']) { 51 | nycReportOptions['report-dir'] = resolve(nycReportOptions['report-dir']) 52 | } 53 | // seems nyc API really is using camel cased version 54 | nycReportOptions.reportDir = nycReportOptions['report-dir'] 55 | 56 | return nycReportOptions 57 | })() 58 | 59 | const nycFilename = join(nycReportOptions['temp-dir'], 'out.json') 60 | 61 | let coverageMap = (() => { 62 | const previousCoverage = existsSync(nycFilename) 63 | ? JSON.parse(readFileSync(nycFilename, 'utf8')) 64 | : {} 65 | return istanbul.createCoverageMap(previousCoverage) 66 | })() 67 | 68 | function saveCoverage(coverage) { 69 | if (!existsSync(nycReportOptions.tempDir)) { 70 | mkdirSync(nycReportOptions.tempDir, { recursive: true }) 71 | debug('created folder %s for output coverage', nycReportOptions.tempDir) 72 | } 73 | 74 | writeFileSync(nycFilename, JSON.stringify(coverage, null, 2)) 75 | } 76 | 77 | function maybePrintFinalCoverageFiles(folder) { 78 | const jsonReportFilename = join(folder, 'coverage-final.json') 79 | if (!existsSync(jsonReportFilename)) { 80 | debug('Did not find final coverage file %s', jsonReportFilename) 81 | return 82 | } 83 | 84 | debug('Final coverage in %s', jsonReportFilename) 85 | const finalCoverage = JSON.parse(readFileSync(jsonReportFilename, 'utf8')) 86 | const finalCoverageKeys = Object.keys(finalCoverage) 87 | debug( 88 | 'There are %d key(s) in %s', 89 | finalCoverageKeys.length, 90 | jsonReportFilename 91 | ) 92 | 93 | finalCoverageKeys.forEach((key) => { 94 | const s = finalCoverage[key].s || {} 95 | const statements = Object.keys(s) 96 | const totalStatements = statements.length 97 | let coveredStatements = 0 98 | statements.forEach((statementKey) => { 99 | if (s[statementKey]) { 100 | coveredStatements += 1 101 | } 102 | }) 103 | 104 | const hasStatements = totalStatements > 0 105 | const allCovered = coveredStatements === totalStatements 106 | const coverageStatus = hasStatements ? (allCovered ? '✅' : '⚠️') : '❓' 107 | 108 | debug( 109 | '%s %s statements covered %d/%d', 110 | coverageStatus, 111 | key, 112 | coveredStatements, 113 | totalStatements 114 | ) 115 | }) 116 | } 117 | 118 | const tasks = { 119 | /** 120 | * Clears accumulated code coverage information. 121 | * 122 | * Interactive mode with "cypress open" 123 | * - running a single spec or "Run all specs" needs to reset coverage 124 | * Headless mode with "cypress run" 125 | * - runs EACH spec separately, so we cannot reset the coverage 126 | * or we will lose the coverage from previous specs. 127 | */ 128 | resetCoverage({ isInteractive }) { 129 | if (isInteractive) { 130 | debug('reset code coverage in interactive mode') 131 | coverageMap = istanbul.createCoverageMap({}) 132 | saveCoverage(coverageMap) 133 | } 134 | /* 135 | Else: 136 | in headless mode, assume the coverage file was deleted 137 | before the `cypress run` command was called 138 | example: rm -rf .nyc_output || true 139 | */ 140 | 141 | return null 142 | }, 143 | 144 | /** 145 | * Combines coverage information from single test 146 | * with previously collected coverage. 147 | * 148 | * @param {string} sentCoverage Stringified coverage object sent by the test runner 149 | * @returns {null} Nothing is returned from this task 150 | */ 151 | combineCoverage(sentCoverage) { 152 | const coverage = JSON.parse(sentCoverage) 153 | debug('parsed sent coverage') 154 | 155 | fixSourcePaths(coverage) 156 | 157 | coverageMap.merge(coverage) 158 | 159 | return null 160 | }, 161 | 162 | /** 163 | * Saves coverage information as a JSON file and calls 164 | * NPM script to generate HTML report 165 | */ 166 | coverageReport() { 167 | saveCoverage(coverageMap) 168 | if (!existsSync(nycFilename)) { 169 | console.warn('Cannot find coverage file %s', nycFilename) 170 | console.warn('Skipping coverage report') 171 | return null 172 | } 173 | 174 | showNycInfo(nycFilename) 175 | 176 | const allSourceFilesMissing = checkAllPathsNotFound(nycFilename) 177 | if (allSourceFilesMissing) { 178 | tryFindingLocalFiles(nycFilename) 179 | } 180 | 181 | resolveRelativePaths(nycFilename) 182 | 183 | if (customNycReportScript) { 184 | debug( 185 | 'saving coverage report using script "%s" from package.json, command: %s', 186 | DEFAULT_CUSTOM_COVERAGE_SCRIPT_NAME, 187 | customNycReportScript 188 | ) 189 | debug('current working directory is %s', process.cwd()) 190 | return execa('npm', ['run', DEFAULT_CUSTOM_COVERAGE_SCRIPT_NAME], { 191 | stdio: 'inherit' 192 | }) 193 | } 194 | 195 | if (nycReportOptions.all) { 196 | debug('nyc needs to report on all included files') 197 | includeAllFiles(nycFilename, nycReportOptions) 198 | } 199 | 200 | debug('calling NYC reporter with options %o', nycReportOptions) 201 | debug('current working directory is %s', process.cwd()) 202 | const NYC = require('nyc') 203 | const nyc = new NYC(nycReportOptions) 204 | 205 | const returnReportFolder = () => { 206 | const reportFolder = nycReportOptions['report-dir'] 207 | debug( 208 | 'after reporting, returning the report folder name %s', 209 | reportFolder 210 | ) 211 | 212 | maybePrintFinalCoverageFiles(reportFolder) 213 | 214 | return reportFolder 215 | } 216 | return nyc.report().then(returnReportFolder) 217 | } 218 | } 219 | 220 | /** 221 | * Registers code coverage collection and reporting tasks. 222 | * Sets an environment variable to tell the browser code that it can 223 | * send the coverage. 224 | * @example 225 | ``` 226 | // your plugins file 227 | module.exports = (on, config) => { 228 | require('cypress/code-coverage/task')(on, config) 229 | // IMPORTANT to return the config object 230 | // with the any changed environment variables 231 | return config 232 | } 233 | ``` 234 | */ 235 | function registerCodeCoverageTasks(on, config) { 236 | on('task', tasks) 237 | 238 | // set a variable to let the hooks running in the browser 239 | // know that they can send coverage commands 240 | config.env.codeCoverageTasksRegistered = true 241 | 242 | return config 243 | } 244 | 245 | module.exports = registerCodeCoverageTasks 246 | -------------------------------------------------------------------------------- /test-apps/all-files/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /test-apps/all-files/README.md: -------------------------------------------------------------------------------- 1 | # example: all files 2 | -------------------------------------------------------------------------------- /test-apps/all-files/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | fixturesFolder: false, 5 | e2e: { 6 | setupNodeEvents(on, config) { 7 | return require('./cypress/plugins/index.js')(on, config) 8 | }, 9 | baseUrl: 'http://localhost:1234' 10 | } 11 | }) 12 | -------------------------------------------------------------------------------- /test-apps/all-files/cypress/e2e/spec.cy.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 | -------------------------------------------------------------------------------- /test-apps/all-files/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('@cypress/code-coverage/task')(on, config) 3 | return config 4 | } 5 | -------------------------------------------------------------------------------- /test-apps/all-files/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | import '@cypress/code-coverage/support' 2 | -------------------------------------------------------------------------------- /test-apps/all-files/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | require('./commands') 2 | -------------------------------------------------------------------------------- /test-apps/all-files/index.html: -------------------------------------------------------------------------------- 1 | 2 | Page body 3 | 4 | 5 | 17 | 18 | -------------------------------------------------------------------------------- /test-apps/all-files/main.js: -------------------------------------------------------------------------------- 1 | window.add = (a, b) => a + b 2 | 3 | window.sub = (a, b) => a - b 4 | -------------------------------------------------------------------------------- /test-apps/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 | -------------------------------------------------------------------------------- /test-apps/all-files/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-all-files", 3 | "description": "Report all files", 4 | "private": true, 5 | "scripts": { 6 | "cy:run": "cypress run", 7 | "start": "parcel serve index.html", 8 | "start:windows": "npx bin-up parcel serve index.html", 9 | "pretest": "rimraf .nyc_output .cache coverage dist", 10 | "test": "start-test 1234 cy:run", 11 | "coverage:verify": "npx nyc report --check-coverage true --lines 100", 12 | "coverage:check-files": "check-coverage main.js && check-coverage second.js && check-coverage not-covered.js && check-coverage cypress.config.js && only-covered --from coverage/coverage-final.json main.js second.js not-covered.js cypress.config.js" 13 | }, 14 | "nyc": { 15 | "all": true, 16 | "include": "*.js" 17 | }, 18 | "devDependencies": { 19 | "@babel/core": "^7.22.15" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test-apps/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 | -------------------------------------------------------------------------------- /test-apps/backend/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /test-apps/backend/README.md: -------------------------------------------------------------------------------- 1 | # example: backend 2 | 3 | > Getting code coverage from backend 4 | -------------------------------------------------------------------------------- /test-apps/backend/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | fixturesFolder: false, 5 | env: { 6 | codeCoverage: { 7 | url: 'http://localhost:3003/__coverage__', 8 | expectBackendCoverageOnly: true, 9 | }, 10 | }, 11 | e2e: { 12 | setupNodeEvents(on, config) { 13 | return require('./cypress/plugins/index.js')(on, config) 14 | }, 15 | baseUrl: 'http://localhost:3003', 16 | }, 17 | }) 18 | -------------------------------------------------------------------------------- /test-apps/backend/cypress/e2e/spec.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | it('has backend code coverage', () => { 3 | cy.visit('/') 4 | cy.request('/hello') 5 | }) 6 | -------------------------------------------------------------------------------- /test-apps/backend/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('@cypress/code-coverage/task')(on, config) 3 | return config 4 | } 5 | -------------------------------------------------------------------------------- /test-apps/backend/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | import '@cypress/code-coverage/support' 2 | -------------------------------------------------------------------------------- /test-apps/backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-backend", 3 | "description": "Code coverage for backend", 4 | "private": true, 5 | "scripts": { 6 | "cy:run": "cypress run", 7 | "start": "nyc --silent node server/server", 8 | "pretest": "rimraf .nyc_output .cache coverage dist", 9 | "test": "start-test 3003 cy:run", 10 | "coverage:verify": "npx nyc report --check-coverage true --lines 100", 11 | "coverage:check-files": "check-coverage server.js && only-covered server.js" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test-apps/backend/server/index.html: -------------------------------------------------------------------------------- 1 | 2 | test backend 3 | 4 | -------------------------------------------------------------------------------- /test-apps/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('@cypress/code-coverage/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 | -------------------------------------------------------------------------------- /test-apps/batch-send-coverage/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /test-apps/batch-send-coverage/README.md: -------------------------------------------------------------------------------- 1 | # Test Case: Batch Send Coverage 2 | This test app tests that all expected files are covered when using 3 | the `sendCoverageBatchSize` environment variable in the Cypress 4 | configuration file. 5 | -------------------------------------------------------------------------------- /test-apps/batch-send-coverage/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | fixturesFolder: false, 5 | env: { 6 | sendCoverageBatchSize: 1 7 | }, 8 | e2e: { 9 | setupNodeEvents(on, config) { 10 | return require('./cypress/plugins/index.js')(on, config) 11 | }, 12 | baseUrl: 'http://localhost:1234' 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /test-apps/batch-send-coverage/cypress/e2e/spec.cy.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 | cy.window() 11 | .invoke('numsTimesTwo', [1, 2, 3]) 12 | .should('deep.equal', [2, 4, 6]) 13 | 14 | cy.window() 15 | .invoke('add', 2, 3) 16 | .should('equal', 5) 17 | 18 | cy.window() 19 | .invoke('sub', 5, 2) 20 | .should('equal', 3) 21 | 22 | // application's code should be instrumented 23 | cy.window().should('have.property', '__coverage__') 24 | }) 25 | -------------------------------------------------------------------------------- /test-apps/batch-send-coverage/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('@cypress/code-coverage/task')(on, config) 3 | return config 4 | } 5 | -------------------------------------------------------------------------------- /test-apps/batch-send-coverage/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | import '@cypress/code-coverage/support' 2 | -------------------------------------------------------------------------------- /test-apps/batch-send-coverage/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | require('./commands') 2 | -------------------------------------------------------------------------------- /test-apps/batch-send-coverage/index.html: -------------------------------------------------------------------------------- 1 | 2 | Page body 3 | 4 | 5 | 6 | // use functions creates in "main.js" 7 | if (add(2, 3) !== 5) { 8 | throw new Error('wrong addition') 9 | } 10 | if (sub(2, 3) !== -1) { 11 | throw new Error('wrong subtraction') 12 | } 13 | if (reverse('foo') !== 'oof') { 14 | throw new Error('wrong string reverse') 15 | } 16 | 17 | 18 | -------------------------------------------------------------------------------- /test-apps/batch-send-coverage/main.js: -------------------------------------------------------------------------------- 1 | window.add = (a, b) => a + b 2 | 3 | window.sub = (a, b) => a - b 4 | -------------------------------------------------------------------------------- /test-apps/batch-send-coverage/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-all-files", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "name": "example-all-files", 8 | "devDependencies": { 9 | "@babel/core": "^7.22.15" 10 | } 11 | }, 12 | "node_modules/@ampproject/remapping": { 13 | "version": "2.3.0", 14 | "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", 15 | "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", 16 | "dev": true, 17 | "license": "Apache-2.0", 18 | "dependencies": { 19 | "@jridgewell/gen-mapping": "^0.3.5", 20 | "@jridgewell/trace-mapping": "^0.3.24" 21 | }, 22 | "engines": { 23 | "node": ">=6.0.0" 24 | } 25 | }, 26 | "node_modules/@babel/code-frame": { 27 | "version": "7.27.1", 28 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", 29 | "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", 30 | "dev": true, 31 | "license": "MIT", 32 | "dependencies": { 33 | "@babel/helper-validator-identifier": "^7.27.1", 34 | "js-tokens": "^4.0.0", 35 | "picocolors": "^1.1.1" 36 | }, 37 | "engines": { 38 | "node": ">=6.9.0" 39 | } 40 | }, 41 | "node_modules/@babel/compat-data": { 42 | "version": "7.27.1", 43 | "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.1.tgz", 44 | "integrity": "sha512-Q+E+rd/yBzNQhXkG+zQnF58e4zoZfBedaxwzPmicKsiK3nt8iJYrSrDbjwFFDGC4f+rPafqRaPH6TsDoSvMf7A==", 45 | "dev": true, 46 | "license": "MIT", 47 | "engines": { 48 | "node": ">=6.9.0" 49 | } 50 | }, 51 | "node_modules/@babel/core": { 52 | "version": "7.27.1", 53 | "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz", 54 | "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==", 55 | "dev": true, 56 | "license": "MIT", 57 | "dependencies": { 58 | "@ampproject/remapping": "^2.2.0", 59 | "@babel/code-frame": "^7.27.1", 60 | "@babel/generator": "^7.27.1", 61 | "@babel/helper-compilation-targets": "^7.27.1", 62 | "@babel/helper-module-transforms": "^7.27.1", 63 | "@babel/helpers": "^7.27.1", 64 | "@babel/parser": "^7.27.1", 65 | "@babel/template": "^7.27.1", 66 | "@babel/traverse": "^7.27.1", 67 | "@babel/types": "^7.27.1", 68 | "convert-source-map": "^2.0.0", 69 | "debug": "^4.1.0", 70 | "gensync": "^1.0.0-beta.2", 71 | "json5": "^2.2.3", 72 | "semver": "^6.3.1" 73 | }, 74 | "engines": { 75 | "node": ">=6.9.0" 76 | }, 77 | "funding": { 78 | "type": "opencollective", 79 | "url": "https://opencollective.com/babel" 80 | } 81 | }, 82 | "node_modules/@babel/generator": { 83 | "version": "7.27.1", 84 | "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", 85 | "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==", 86 | "dev": true, 87 | "license": "MIT", 88 | "dependencies": { 89 | "@babel/parser": "^7.27.1", 90 | "@babel/types": "^7.27.1", 91 | "@jridgewell/gen-mapping": "^0.3.5", 92 | "@jridgewell/trace-mapping": "^0.3.25", 93 | "jsesc": "^3.0.2" 94 | }, 95 | "engines": { 96 | "node": ">=6.9.0" 97 | } 98 | }, 99 | "node_modules/@babel/helper-compilation-targets": { 100 | "version": "7.27.1", 101 | "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.1.tgz", 102 | "integrity": "sha512-2YaDd/Rd9E598B5+WIc8wJPmWETiiJXFYVE60oX8FDohv7rAUU3CQj+A1MgeEmcsk2+dQuEjIe/GDvig0SqL4g==", 103 | "dev": true, 104 | "license": "MIT", 105 | "dependencies": { 106 | "@babel/compat-data": "^7.27.1", 107 | "@babel/helper-validator-option": "^7.27.1", 108 | "browserslist": "^4.24.0", 109 | "lru-cache": "^5.1.1", 110 | "semver": "^6.3.1" 111 | }, 112 | "engines": { 113 | "node": ">=6.9.0" 114 | } 115 | }, 116 | "node_modules/@babel/helper-module-imports": { 117 | "version": "7.27.1", 118 | "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", 119 | "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", 120 | "dev": true, 121 | "license": "MIT", 122 | "dependencies": { 123 | "@babel/traverse": "^7.27.1", 124 | "@babel/types": "^7.27.1" 125 | }, 126 | "engines": { 127 | "node": ">=6.9.0" 128 | } 129 | }, 130 | "node_modules/@babel/helper-module-transforms": { 131 | "version": "7.27.1", 132 | "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz", 133 | "integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==", 134 | "dev": true, 135 | "license": "MIT", 136 | "dependencies": { 137 | "@babel/helper-module-imports": "^7.27.1", 138 | "@babel/helper-validator-identifier": "^7.27.1", 139 | "@babel/traverse": "^7.27.1" 140 | }, 141 | "engines": { 142 | "node": ">=6.9.0" 143 | }, 144 | "peerDependencies": { 145 | "@babel/core": "^7.0.0" 146 | } 147 | }, 148 | "node_modules/@babel/helper-string-parser": { 149 | "version": "7.27.1", 150 | "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", 151 | "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", 152 | "dev": true, 153 | "license": "MIT", 154 | "engines": { 155 | "node": ">=6.9.0" 156 | } 157 | }, 158 | "node_modules/@babel/helper-validator-identifier": { 159 | "version": "7.27.1", 160 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", 161 | "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", 162 | "dev": true, 163 | "license": "MIT", 164 | "engines": { 165 | "node": ">=6.9.0" 166 | } 167 | }, 168 | "node_modules/@babel/helper-validator-option": { 169 | "version": "7.27.1", 170 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", 171 | "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", 172 | "dev": true, 173 | "license": "MIT", 174 | "engines": { 175 | "node": ">=6.9.0" 176 | } 177 | }, 178 | "node_modules/@babel/helpers": { 179 | "version": "7.27.1", 180 | "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", 181 | "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", 182 | "dev": true, 183 | "license": "MIT", 184 | "dependencies": { 185 | "@babel/template": "^7.27.1", 186 | "@babel/types": "^7.27.1" 187 | }, 188 | "engines": { 189 | "node": ">=6.9.0" 190 | } 191 | }, 192 | "node_modules/@babel/parser": { 193 | "version": "7.27.1", 194 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.1.tgz", 195 | "integrity": "sha512-I0dZ3ZpCrJ1c04OqlNsQcKiZlsrXf/kkE4FXzID9rIOYICsAbA8mMDzhW/luRNAHdCNt7os/u8wenklZDlUVUQ==", 196 | "dev": true, 197 | "license": "MIT", 198 | "dependencies": { 199 | "@babel/types": "^7.27.1" 200 | }, 201 | "bin": { 202 | "parser": "bin/babel-parser.js" 203 | }, 204 | "engines": { 205 | "node": ">=6.0.0" 206 | } 207 | }, 208 | "node_modules/@babel/template": { 209 | "version": "7.27.1", 210 | "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.1.tgz", 211 | "integrity": "sha512-Fyo3ghWMqkHHpHQCoBs2VnYjR4iWFFjguTDEqA5WgZDOrFesVjMhMM2FSqTKSoUSDO1VQtavj8NFpdRBEvJTtg==", 212 | "dev": true, 213 | "license": "MIT", 214 | "dependencies": { 215 | "@babel/code-frame": "^7.27.1", 216 | "@babel/parser": "^7.27.1", 217 | "@babel/types": "^7.27.1" 218 | }, 219 | "engines": { 220 | "node": ">=6.9.0" 221 | } 222 | }, 223 | "node_modules/@babel/traverse": { 224 | "version": "7.27.1", 225 | "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz", 226 | "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==", 227 | "dev": true, 228 | "license": "MIT", 229 | "dependencies": { 230 | "@babel/code-frame": "^7.27.1", 231 | "@babel/generator": "^7.27.1", 232 | "@babel/parser": "^7.27.1", 233 | "@babel/template": "^7.27.1", 234 | "@babel/types": "^7.27.1", 235 | "debug": "^4.3.1", 236 | "globals": "^11.1.0" 237 | }, 238 | "engines": { 239 | "node": ">=6.9.0" 240 | } 241 | }, 242 | "node_modules/@babel/types": { 243 | "version": "7.27.1", 244 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", 245 | "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", 246 | "dev": true, 247 | "license": "MIT", 248 | "dependencies": { 249 | "@babel/helper-string-parser": "^7.27.1", 250 | "@babel/helper-validator-identifier": "^7.27.1" 251 | }, 252 | "engines": { 253 | "node": ">=6.9.0" 254 | } 255 | }, 256 | "node_modules/@jridgewell/gen-mapping": { 257 | "version": "0.3.5", 258 | "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", 259 | "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", 260 | "dev": true, 261 | "license": "MIT", 262 | "dependencies": { 263 | "@jridgewell/set-array": "^1.2.1", 264 | "@jridgewell/sourcemap-codec": "^1.4.10", 265 | "@jridgewell/trace-mapping": "^0.3.24" 266 | }, 267 | "engines": { 268 | "node": ">=6.0.0" 269 | } 270 | }, 271 | "node_modules/@jridgewell/resolve-uri": { 272 | "version": "3.1.2", 273 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 274 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 275 | "dev": true, 276 | "license": "MIT", 277 | "engines": { 278 | "node": ">=6.0.0" 279 | } 280 | }, 281 | "node_modules/@jridgewell/set-array": { 282 | "version": "1.2.1", 283 | "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", 284 | "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", 285 | "dev": true, 286 | "license": "MIT", 287 | "engines": { 288 | "node": ">=6.0.0" 289 | } 290 | }, 291 | "node_modules/@jridgewell/sourcemap-codec": { 292 | "version": "1.5.0", 293 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", 294 | "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", 295 | "dev": true, 296 | "license": "MIT" 297 | }, 298 | "node_modules/@jridgewell/trace-mapping": { 299 | "version": "0.3.25", 300 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", 301 | "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", 302 | "dev": true, 303 | "license": "MIT", 304 | "dependencies": { 305 | "@jridgewell/resolve-uri": "^3.1.0", 306 | "@jridgewell/sourcemap-codec": "^1.4.14" 307 | } 308 | }, 309 | "node_modules/browserslist": { 310 | "version": "4.24.0", 311 | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", 312 | "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", 313 | "dev": true, 314 | "funding": [ 315 | { 316 | "type": "opencollective", 317 | "url": "https://opencollective.com/browserslist" 318 | }, 319 | { 320 | "type": "tidelift", 321 | "url": "https://tidelift.com/funding/github/npm/browserslist" 322 | }, 323 | { 324 | "type": "github", 325 | "url": "https://github.com/sponsors/ai" 326 | } 327 | ], 328 | "license": "MIT", 329 | "dependencies": { 330 | "caniuse-lite": "^1.0.30001663", 331 | "electron-to-chromium": "^1.5.28", 332 | "node-releases": "^2.0.18", 333 | "update-browserslist-db": "^1.1.0" 334 | }, 335 | "bin": { 336 | "browserslist": "cli.js" 337 | }, 338 | "engines": { 339 | "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" 340 | } 341 | }, 342 | "node_modules/caniuse-lite": { 343 | "version": "1.0.30001666", 344 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001666.tgz", 345 | "integrity": "sha512-gD14ICmoV5ZZM1OdzPWmpx+q4GyefaK06zi8hmfHV5xe4/2nOQX3+Dw5o+fSqOws2xVwL9j+anOPFwHzdEdV4g==", 346 | "dev": true, 347 | "funding": [ 348 | { 349 | "type": "opencollective", 350 | "url": "https://opencollective.com/browserslist" 351 | }, 352 | { 353 | "type": "tidelift", 354 | "url": "https://tidelift.com/funding/github/npm/caniuse-lite" 355 | }, 356 | { 357 | "type": "github", 358 | "url": "https://github.com/sponsors/ai" 359 | } 360 | ], 361 | "license": "CC-BY-4.0" 362 | }, 363 | "node_modules/convert-source-map": { 364 | "version": "2.0.0", 365 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", 366 | "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", 367 | "dev": true, 368 | "license": "MIT" 369 | }, 370 | "node_modules/debug": { 371 | "version": "4.3.7", 372 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", 373 | "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", 374 | "dev": true, 375 | "license": "MIT", 376 | "dependencies": { 377 | "ms": "^2.1.3" 378 | }, 379 | "engines": { 380 | "node": ">=6.0" 381 | }, 382 | "peerDependenciesMeta": { 383 | "supports-color": { 384 | "optional": true 385 | } 386 | } 387 | }, 388 | "node_modules/electron-to-chromium": { 389 | "version": "1.5.31", 390 | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.31.tgz", 391 | "integrity": "sha512-QcDoBbQeYt0+3CWcK/rEbuHvwpbT/8SV9T3OSgs6cX1FlcUAkgrkqbg9zLnDrMM/rLamzQwal4LYFCiWk861Tg==", 392 | "dev": true, 393 | "license": "ISC" 394 | }, 395 | "node_modules/escalade": { 396 | "version": "3.2.0", 397 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", 398 | "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", 399 | "dev": true, 400 | "license": "MIT", 401 | "engines": { 402 | "node": ">=6" 403 | } 404 | }, 405 | "node_modules/gensync": { 406 | "version": "1.0.0-beta.2", 407 | "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", 408 | "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", 409 | "dev": true, 410 | "license": "MIT", 411 | "engines": { 412 | "node": ">=6.9.0" 413 | } 414 | }, 415 | "node_modules/globals": { 416 | "version": "11.12.0", 417 | "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", 418 | "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", 419 | "dev": true, 420 | "license": "MIT", 421 | "engines": { 422 | "node": ">=4" 423 | } 424 | }, 425 | "node_modules/js-tokens": { 426 | "version": "4.0.0", 427 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 428 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 429 | "dev": true, 430 | "license": "MIT" 431 | }, 432 | "node_modules/jsesc": { 433 | "version": "3.0.2", 434 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", 435 | "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", 436 | "dev": true, 437 | "license": "MIT", 438 | "bin": { 439 | "jsesc": "bin/jsesc" 440 | }, 441 | "engines": { 442 | "node": ">=6" 443 | } 444 | }, 445 | "node_modules/json5": { 446 | "version": "2.2.3", 447 | "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", 448 | "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", 449 | "dev": true, 450 | "license": "MIT", 451 | "bin": { 452 | "json5": "lib/cli.js" 453 | }, 454 | "engines": { 455 | "node": ">=6" 456 | } 457 | }, 458 | "node_modules/lru-cache": { 459 | "version": "5.1.1", 460 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", 461 | "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", 462 | "dev": true, 463 | "license": "ISC", 464 | "dependencies": { 465 | "yallist": "^3.0.2" 466 | } 467 | }, 468 | "node_modules/ms": { 469 | "version": "2.1.3", 470 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 471 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 472 | "dev": true, 473 | "license": "MIT" 474 | }, 475 | "node_modules/node-releases": { 476 | "version": "2.0.18", 477 | "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", 478 | "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", 479 | "dev": true, 480 | "license": "MIT" 481 | }, 482 | "node_modules/picocolors": { 483 | "version": "1.1.1", 484 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 485 | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 486 | "dev": true, 487 | "license": "ISC" 488 | }, 489 | "node_modules/semver": { 490 | "version": "6.3.1", 491 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", 492 | "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", 493 | "dev": true, 494 | "license": "ISC", 495 | "bin": { 496 | "semver": "bin/semver.js" 497 | } 498 | }, 499 | "node_modules/update-browserslist-db": { 500 | "version": "1.1.1", 501 | "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", 502 | "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", 503 | "dev": true, 504 | "funding": [ 505 | { 506 | "type": "opencollective", 507 | "url": "https://opencollective.com/browserslist" 508 | }, 509 | { 510 | "type": "tidelift", 511 | "url": "https://tidelift.com/funding/github/npm/browserslist" 512 | }, 513 | { 514 | "type": "github", 515 | "url": "https://github.com/sponsors/ai" 516 | } 517 | ], 518 | "license": "MIT", 519 | "dependencies": { 520 | "escalade": "^3.2.0", 521 | "picocolors": "^1.1.0" 522 | }, 523 | "bin": { 524 | "update-browserslist-db": "cli.js" 525 | }, 526 | "peerDependencies": { 527 | "browserslist": ">= 4.21.0" 528 | } 529 | }, 530 | "node_modules/yallist": { 531 | "version": "3.1.1", 532 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", 533 | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", 534 | "dev": true, 535 | "license": "ISC" 536 | } 537 | } 538 | } 539 | -------------------------------------------------------------------------------- /test-apps/batch-send-coverage/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-all-files", 3 | "description": "Report all files", 4 | "private": true, 5 | "scripts": { 6 | "cy:run": "cypress run", 7 | "start": "parcel serve index.html", 8 | "start:windows": "npx bin-up parcel serve index.html", 9 | "pretest": "rimraf .nyc_output .cache coverage dist", 10 | "test": "DEBUG=code-coverage DEBUG_DEPTH=10 start-test 1234 cy:run", 11 | "coverage:verify": "npx nyc report --check-coverage true --lines 100", 12 | "coverage:check-files": "check-coverage main.js second.js third.js cypress.config.js --from coverage/coverage-final.json" 13 | }, 14 | "nyc": { 15 | "all": true, 16 | "include": "*.js" 17 | }, 18 | "devDependencies": { 19 | "@babel/core": "^7.22.15" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test-apps/batch-send-coverage/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 | -------------------------------------------------------------------------------- /test-apps/batch-send-coverage/third.js: -------------------------------------------------------------------------------- 1 | window.numsTimesTwo = nums => nums.map(n => n * 2) -------------------------------------------------------------------------------- /test-apps/before-all-visit/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /test-apps/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 | -------------------------------------------------------------------------------- /test-apps/before-all-visit/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | fixturesFolder: false, 5 | e2e: { 6 | setupNodeEvents(on, config) { 7 | return require('./cypress/plugins/index.js')(on, config) 8 | }, 9 | baseUrl: 'http://localhost:1234', 10 | }, 11 | }) 12 | -------------------------------------------------------------------------------- /test-apps/before-all-visit/cypress/e2e/spec.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | describe('coverage information', () => { 3 | before(() => { 4 | cy.log('visiting /') 5 | cy.visit('/') 6 | }) 7 | 8 | it('calls add & sub', () => { 9 | cy.window() 10 | .invoke('add', 2, 3) 11 | .should('equal', 5) 12 | 13 | cy.window() 14 | .invoke('sub', 2, 3) 15 | .should('equal', -1) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /test-apps/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 | } -------------------------------------------------------------------------------- /test-apps/before-all-visit/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('@cypress/code-coverage/task')(on, config) 3 | // IMPORTANT to return the config object 4 | // with the any changed environment variables 5 | return config 6 | } 7 | -------------------------------------------------------------------------------- /test-apps/before-all-visit/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | import '@cypress/code-coverage/support' 2 | -------------------------------------------------------------------------------- /test-apps/before-all-visit/index.html: -------------------------------------------------------------------------------- 1 | 2 | Page body 3 | 4 | 13 | 14 | -------------------------------------------------------------------------------- /test-apps/before-all-visit/main.js: -------------------------------------------------------------------------------- 1 | window.add = (a, b) => a + b 2 | 3 | window.sub = (a, b) => a - b 4 | -------------------------------------------------------------------------------- /test-apps/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 | "scripts": { 5 | "start": "parcel serve index.html", 6 | "cy:run": "cypress run", 7 | "pretest": "rimraf .nyc_output .cache coverage dist", 8 | "test": "start-test 1234 cy:run", 9 | "coverage:verify": "npx nyc report --check-coverage true --lines 100", 10 | "coverage:check-files": "check-coverage main.js && only-covered main.js" 11 | }, 12 | "devDependencies": { 13 | "@babel/core": "^7.22.15" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test-apps/before-each-visit/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /test-apps/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 | -------------------------------------------------------------------------------- /test-apps/before-each-visit/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | fixturesFolder: false, 5 | e2e: { 6 | setupNodeEvents(on, config) { 7 | return require('./cypress/plugins/index.js')(on, config) 8 | }, 9 | baseUrl: 'http://localhost:1234', 10 | }, 11 | }) 12 | -------------------------------------------------------------------------------- /test-apps/before-each-visit/cypress/e2e/spec.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | describe('coverage information', () => { 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 | -------------------------------------------------------------------------------- /test-apps/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 | } -------------------------------------------------------------------------------- /test-apps/before-each-visit/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('@cypress/code-coverage/task')(on, config) 3 | // IMPORTANT to return the config object 4 | // with the any changed environment variables 5 | return config 6 | } 7 | -------------------------------------------------------------------------------- /test-apps/before-each-visit/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | import '@cypress/code-coverage/support' 2 | -------------------------------------------------------------------------------- /test-apps/before-each-visit/index.html: -------------------------------------------------------------------------------- 1 | 2 | Page body 3 | 4 | 13 | 14 | -------------------------------------------------------------------------------- /test-apps/before-each-visit/main.js: -------------------------------------------------------------------------------- 1 | window.add = (a, b) => a + b 2 | 3 | window.sub = (a, b) => a - b 4 | -------------------------------------------------------------------------------- /test-apps/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 | "scripts": { 5 | "start": "parcel serve index.html", 6 | "cy:run": "cypress run", 7 | "pretest": "rimraf .nyc_output .cache coverage dist", 8 | "test": "start-test 1234 cy:run", 9 | "coverage:verify": "npx nyc report --check-coverage true --lines 100", 10 | "coverage:check-files": "check-coverage main.js && only-covered --from coverage/coverage-final.json main.js" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/README.md: -------------------------------------------------------------------------------- 1 | # example: create react app with both e2e and component testing examples 2 | 3 | This example has both e2e and CT tests, which are run sequentially in the `cy:run` script. 4 | It uses the [@cypress/instrument-cra](https://github.com/cypress-io/instrument-cra) package to instrument the 5 | React code before the tests run. 6 | -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'cypress' 2 | import '@cypress/instrument-cra' 3 | 4 | export default defineConfig({ 5 | env: { 6 | codeCoverage: { 7 | exclude: 'cypress/**/*.*' 8 | } 9 | }, 10 | e2e: { 11 | setupNodeEvents(on, config) { 12 | require('@cypress/code-coverage/task')(on, config) 13 | return config 14 | } 15 | }, 16 | 17 | component: { 18 | devServer: { 19 | framework: 'create-react-app', 20 | bundler: 'webpack' 21 | }, 22 | setupNodeEvents(on, config) { 23 | require('@cypress/code-coverage/task')(on, config) 24 | return config 25 | } 26 | } 27 | }) 28 | -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/cypress/e2e/spec.cy.ts: -------------------------------------------------------------------------------- 1 | describe('empty spec', () => { 2 | it('passes', () => { 3 | cy.visit('http://localhost:3000') 4 | }) 5 | }) -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/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 | -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/cypress/support/commands.ts: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************** 3 | // This example commands.ts shows you how to 4 | // create various custom commands and overwrite 5 | // existing commands. 6 | // 7 | // For more comprehensive examples of custom 8 | // commands please read more here: 9 | // https://on.cypress.io/custom-commands 10 | // *********************************************** 11 | // 12 | // 13 | // -- This is a parent command -- 14 | // Cypress.Commands.add('login', (email, password) => { ... }) 15 | // 16 | // 17 | // -- This is a child command -- 18 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 19 | // 20 | // 21 | // -- This is a dual command -- 22 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 23 | // 24 | // 25 | // -- This will overwrite an existing command -- 26 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 27 | // 28 | // declare global { 29 | // namespace Cypress { 30 | // interface Chainable { 31 | // login(email: string, password: string): Chainable 32 | // drag(subject: string, options?: Partial): Chainable 33 | // dismiss(subject: string, options?: Partial): Chainable 34 | // visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable 35 | // } 36 | // } 37 | // } -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/cypress/support/component-index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Components App 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/cypress/support/component.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/component.ts is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | import '@cypress/code-coverage/support' 19 | 20 | // Alternatively you can use CommonJS syntax: 21 | // require('./commands') 22 | 23 | import { mount } from 'cypress/react' 24 | 25 | // Augment the Cypress namespace to include type definitions for 26 | // your custom command. 27 | // Alternatively, can be defined in cypress/support/component.d.ts 28 | // with a at the top of your spec. 29 | declare global { 30 | namespace Cypress { 31 | interface Chainable { 32 | mount: typeof mount 33 | } 34 | } 35 | } 36 | 37 | Cypress.Commands.add('mount', mount) 38 | 39 | // Example use: 40 | // cy.mount() -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/cypress/support/e2e.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/e2e.ts is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | import '@cypress/code-coverage/support' 19 | 20 | // Alternatively you can use CommonJS syntax: 21 | // require('./commands') -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cra-e2e-and-ct", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@cypress/instrument-cra": "^1.4.0", 7 | "@testing-library/jest-dom": "^5.16.4", 8 | "@testing-library/react": "^13.3.0", 9 | "@testing-library/user-event": "^13.5.0", 10 | "@types/jest": "^29.0.0", 11 | "@types/node": "^22.0.0", 12 | "@types/react": "^18.0.12", 13 | "@types/react-dom": "^18.0.5", 14 | "react": "^18.1.0", 15 | "react-dom": "^18.1.0", 16 | "react-scripts": "5.0.1", 17 | "typescript": "^4.7.3", 18 | "web-vitals": "^2.1.4" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts -r @cypress/instrument-cra start", 22 | "build": "react-scripts build", 23 | "cy:run": "cypress run --e2e && cypress run --component", 24 | "pretest": "npm ci && rimraf .nyc_output .cache coverage dist", 25 | "test": "start-test 3000 cy:run", 26 | "eject": "react-scripts eject", 27 | "coverage:verify": "npx nyc report --check-coverage true --lines 100", 28 | "coverage:check-files": "check-coverage src/index.tsx && check-coverage src/App.tsx && check-coverage src/components/Button.tsx && check-coverage src/components/Stepper.tsx && only-covered src/index.tsx src/App.tsx src/components/Button.tsx src/components/Stepper.tsx" 29 | }, 30 | "eslintConfig": { 31 | "extends": [ 32 | "react-app", 33 | "react-app/jest" 34 | ] 35 | }, 36 | "browserslist": { 37 | "production": [ 38 | ">0.2%", 39 | "not dead", 40 | "not op_mini all" 41 | ], 42 | "development": [ 43 | "last 1 chrome version", 44 | "last 1 firefox version", 45 | "last 1 safari version" 46 | ] 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypress-io/code-coverage/127b589b25e5a9a22657ac6c89277044886bfb9e/test-apps/cra-e2e-and-ct/public/favicon.ico -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypress-io/code-coverage/127b589b25e5a9a22657ac6c89277044886bfb9e/test-apps/cra-e2e-and-ct/public/logo192.png -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypress-io/code-coverage/127b589b25e5a9a22657ac6c89277044886bfb9e/test-apps/cra-e2e-and-ct/public/logo512.png -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | render(); 7 | const linkElement = screen.getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/src/App.tsx: -------------------------------------------------------------------------------- 1 | import './App.css'; 2 | import Stepper from './components/Stepper'; 3 | 4 | function App() { 5 | return ( 6 |
7 | 8 |
9 | ); 10 | } 11 | 12 | export default App; 13 | -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/src/components/Button.cy.tsx: -------------------------------------------------------------------------------- 1 | import Button from './Button' 2 | 3 | describe('Button', () => { 4 | it('should have text', () => { 5 | cy.mount() 6 | cy.get('button').should('contain.text', 'Click me!') 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/src/components/Button.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | interface ButtonProps extends React.ButtonHTMLAttributes {} 4 | 5 | const Button: React.FC = ({ children }) => { 6 | return 7 | } 8 | 9 | export default Button 10 | -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/src/components/Stepper.cy.tsx: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import Stepper from './Stepper' 4 | 5 | describe('Stepper.cy.js', () => { 6 | const stepperSelector = '[data-testid=stepper]' 7 | const incrementSelector = '[aria-label=increment]' 8 | const decrementSelector = '[aria-label=decrement]' 9 | 10 | it('playground', () => { 11 | cy.mount() 12 | }) 13 | 14 | it('stepper should default to 0', () => { 15 | // Arrange 16 | cy.mount() 17 | // Assert 18 | cy.get(stepperSelector).should('contain.text', 0) 19 | }) 20 | 21 | it('can be incremented', () => { 22 | // Arrange 23 | cy.mount() 24 | // Act 25 | cy.get(incrementSelector).click() 26 | // Assert 27 | cy.get(stepperSelector).should('contain.text', 1) 28 | }) 29 | 30 | it('can be decremented', () => { 31 | // Arrange 32 | cy.mount() 33 | // Act 34 | cy.get(decrementSelector).click() 35 | // Assert 36 | cy.get(stepperSelector).should('contain.text', -1) 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/src/components/Stepper.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | 3 | export default function Stepper({ initial = 0, onChange = (num: any) => {} }) { 4 | const [count, setCount] = useState(initial) 5 | 6 | const increment = () => { 7 | const newCount = count + 1 8 | setCount(newCount) 9 | onChange(newCount) 10 | } 11 | 12 | const decrement = () => { 13 | const newCount = count - 1 14 | setCount(newCount) 15 | onChange(newCount) 16 | } 17 | 18 | return ( 19 |
20 | 23 | {count} 24 | 27 |
28 | ) 29 | } -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | const root = ReactDOM.createRoot( 7 | document.getElementById('root') as HTMLElement 8 | ); 9 | root.render( 10 | 11 | 12 | 13 | ); 14 | 15 | -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /test-apps/cra-e2e-and-ct/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src", "cypress" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /test-apps/exclude-files/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /test-apps/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 | -------------------------------------------------------------------------------- /test-apps/exclude-files/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | fixturesFolder: false, 5 | e2e: { 6 | setupNodeEvents(on, config) { 7 | return require('./cypress/plugins/index.js')(on, config) 8 | }, 9 | baseUrl: 'http://localhost:1234', 10 | }, 11 | }) 12 | -------------------------------------------------------------------------------- /test-apps/exclude-files/cypress/e2e/spec.cy.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 | -------------------------------------------------------------------------------- /test-apps/exclude-files/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('@cypress/code-coverage/task')(on, config) 3 | return config 4 | } 5 | -------------------------------------------------------------------------------- /test-apps/exclude-files/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | import '@cypress/code-coverage/support' 2 | console.log('this is commands file') 3 | -------------------------------------------------------------------------------- /test-apps/exclude-files/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | require('./commands') 2 | -------------------------------------------------------------------------------- /test-apps/exclude-files/index.html: -------------------------------------------------------------------------------- 1 | 2 | Page body 3 | 4 | 5 | 17 | 18 | -------------------------------------------------------------------------------- /test-apps/exclude-files/main.js: -------------------------------------------------------------------------------- 1 | window.add = (a, b) => a + b 2 | 3 | window.sub = (a, b) => a - b 4 | -------------------------------------------------------------------------------- /test-apps/exclude-files/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-exclude-files", 3 | "description": "Exclude some files from final coverage report", 4 | "scripts": { 5 | "cy:run": "cypress run", 6 | "start": "parcel serve index.html", 7 | "pretest": "rimraf .nyc_output .cache coverage dist", 8 | "test": "start-test 1234 cy:run", 9 | "coverage:verify": "npx nyc report --check-coverage true --lines 100", 10 | "coverage:check-files": "check-coverage main.js && only-covered --from coverage/coverage-final.json main.js" 11 | }, 12 | "nyc": { 13 | "exclude": [ 14 | "second.js" 15 | ], 16 | "excludeAfterRemap": true 17 | }, 18 | "devDependencies": { 19 | "@babel/core": "7.27.1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test-apps/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 | -------------------------------------------------------------------------------- /test-apps/frontend/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"], 3 | "ignore": ["**/*.cy.js"] 4 | } 5 | -------------------------------------------------------------------------------- /test-apps/frontend/README.md: -------------------------------------------------------------------------------- 1 | # example: frontend 2 | 3 | Tests a frontend app 4 | -------------------------------------------------------------------------------- /test-apps/frontend/about.html: -------------------------------------------------------------------------------- 1 | 2 |

About

3 |
4 | 5 | 6 | -------------------------------------------------------------------------------- /test-apps/frontend/about.js: -------------------------------------------------------------------------------- 1 | document 2 | .getElementById('content') 3 | .appendChild(document.createTextNode('Est. 2019')) 4 | -------------------------------------------------------------------------------- /test-apps/frontend/app.js: -------------------------------------------------------------------------------- 1 | import { map } from 'lodash' 2 | 3 | const list = [{ name: 'joe' }, { name: 'mary' }] 4 | const names = map(list, 'name') 5 | console.log('just names', names) 6 | -------------------------------------------------------------------------------- /test-apps/frontend/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | fixturesFolder: false, 5 | e2e: { 6 | setupNodeEvents(on, config) { 7 | return require('./cypress/plugins/index.js')(on, config) 8 | }, 9 | hosts: { 10 | 'foobar.com': '127.0.0.1', 11 | }, 12 | baseUrl: 'http://localhost:1234', 13 | env: { 14 | codeCoverage: { 15 | exclude: ['cypress/**/*.*'] 16 | } 17 | } 18 | } 19 | }) 20 | -------------------------------------------------------------------------------- /test-apps/frontend/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 | 7 | context('Page test', () => { 8 | beforeEach(() => { 9 | cy.visit('/', { 10 | onBeforeLoad(win) { 11 | cy.spy(win.console, 'log').as('log') 12 | } 13 | }) 14 | }) 15 | 16 | it('logs names', function() { 17 | cy.get('@log') 18 | .should('have.been.calledOnce') 19 | .should('have.been.calledWith', 'just names', ['joe', 'mary']) 20 | }) 21 | 22 | it('loads About page', () => { 23 | cy.contains('About').click() 24 | cy.url().should('match', /\/about/) 25 | cy.contains('h2', 'About') 26 | cy.contains('Est. 2019') 27 | }) 28 | 29 | it('loads cross origin page using cy.origin', () => { 30 | cy.origin('http://foobar.com:1234', () => { 31 | cy.visit('/') 32 | }) 33 | }) 34 | 35 | it('loads cross origin page without cy.origin', () => { 36 | cy.visit('http://foobar.com:1234') 37 | }) 38 | }) 39 | 40 | context('Unit tests', () => { 41 | it('adds numbers', () => { 42 | expect(add(2, 3)).to.equal(5) 43 | }) 44 | 45 | it('concatenates strings', () => { 46 | expect(add('foo', 'Bar')).to.equal('fooBar') 47 | }) 48 | 49 | }) 50 | -------------------------------------------------------------------------------- /test-apps/frontend/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('@cypress/code-coverage/task')(on, config) 3 | //Used to instrument code tested like unit tests 4 | on('file:preprocessor', require('@cypress/code-coverage/use-babelrc')) 5 | return config 6 | } 7 | -------------------------------------------------------------------------------- /test-apps/frontend/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | import '@cypress/code-coverage/support' 2 | console.log('this is commands file') 3 | -------------------------------------------------------------------------------- /test-apps/frontend/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | require('./commands') 2 | -------------------------------------------------------------------------------- /test-apps/frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 5 |

Test page

6 |

Open the DevTools to see console messages

7 | 8 | 9 | -------------------------------------------------------------------------------- /test-apps/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-frontend", 3 | "description": "Tests a frontend app", 4 | "scripts": { 5 | "cy:run": "cypress run", 6 | "start": "parcel serve index.html", 7 | "pretest": "rimraf .nyc_output .cache coverage dist", 8 | "test": "start-test 1234 cy:run", 9 | "coverage:verify": "npx nyc report --check-coverage true --lines 100", 10 | "coverage:check-files": "check-coverage app.js && check-coverage about.js && check-coverage unit.js && only-covered app.js about.js unit.js" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test-apps/frontend/unit.js: -------------------------------------------------------------------------------- 1 | export const add = (a, b) => a + b 2 | -------------------------------------------------------------------------------- /test-apps/fullstack/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /test-apps/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 test 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 | -------------------------------------------------------------------------------- /test-apps/fullstack/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | fixturesFolder: false, 5 | env: { 6 | codeCoverage: { 7 | url: 'http://localhost:1234/__coverage__', 8 | exclude: 'cypress/**/*.*' 9 | } 10 | }, 11 | e2e: { 12 | setupNodeEvents(on, config) { 13 | return require('./cypress/plugins/index.js')(on, config) 14 | }, 15 | baseUrl: 'http://localhost:1234' 16 | } 17 | }) 18 | -------------------------------------------------------------------------------- /test-apps/fullstack/cypress/e2e/spec.cy.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 | -------------------------------------------------------------------------------- /test-apps/fullstack/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('@cypress/code-coverage/task')(on, config) 3 | //Used to instrument code ran like unit tests 4 | on('file:preprocessor', require('@cypress/code-coverage/use-babelrc')) 5 | return config 6 | } 7 | -------------------------------------------------------------------------------- /test-apps/fullstack/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | import '@cypress/code-coverage/support' 2 | -------------------------------------------------------------------------------- /test-apps/fullstack/images/fullstack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cypress-io/code-coverage/127b589b25e5a9a22657ac6c89277044886bfb9e/test-apps/fullstack/images/fullstack.png -------------------------------------------------------------------------------- /test-apps/fullstack/index.html: -------------------------------------------------------------------------------- 1 | 2 | Page body 3 | 4 | 5 | -------------------------------------------------------------------------------- /test-apps/fullstack/main.js: -------------------------------------------------------------------------------- 1 | window.add = (a, b) => a + b 2 | 3 | window.sub = (a, b) => a - b 4 | -------------------------------------------------------------------------------- /test-apps/fullstack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-fullstack", 3 | "description": "Combined code coverage from the backend code, and e2e and unit tests", 4 | "scripts": { 5 | "prestart": "parcel build index.html", 6 | "start": "nyc --silent node server/server", 7 | "cy:run": "cypress run", 8 | "pretest": "rimraf .nyc_output .cache coverage dist", 9 | "test": "start-test 1234 cy:run", 10 | "coverage:verify": "npx nyc report --check-coverage true --lines 100", 11 | "coverage:check-files": "check-coverage fullstack/server/server.js && check-coverage fullstack/main.js && check-coverage fullstack/string-utils.js && only-covered server.js main.js string-utils.js" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test-apps/fullstack/server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const path = require('path') 3 | const app = express() 4 | const port = 1234 5 | 6 | // if there is code coverage information 7 | // then expose an endpoint that returns it 8 | /* istanbul ignore next */ 9 | if (global.__coverage__) { 10 | console.log('have code coverage, will add middleware for express') 11 | console.log(`to fetch: GET :${port}/__coverage__`) 12 | require('@cypress/code-coverage/middleware/express')(app) 13 | } 14 | 15 | app.use(express.static(path.join(__dirname, '../dist'))) 16 | 17 | app.get('/hello', (req, res) => { 18 | console.log('sending hello world') 19 | res.send('Hello World!') 20 | }) 21 | 22 | app.listen(port, () => console.log(`Example app listening on port ${port}!`)) 23 | -------------------------------------------------------------------------------- /test-apps/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 | -------------------------------------------------------------------------------- /test-apps/multiple-backends/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /test-apps/multiple-backends/README.md: -------------------------------------------------------------------------------- 1 | # example: multiple backends 2 | 3 | > Getting code coverage from multiple backends 4 | -------------------------------------------------------------------------------- /test-apps/multiple-backends/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | fixturesFolder: false, 5 | env: { 6 | codeCoverage: { 7 | url: ['http://localhost:3003/__coverage__', 'http://localhost:3004/__coverage__'], 8 | expectBackendCoverageOnly: true, 9 | }, 10 | }, 11 | e2e: { 12 | setupNodeEvents(on, config) { 13 | return require('./cypress/plugins/index.js')(on, config) 14 | }, 15 | baseUrl: 'http://localhost:3003', 16 | }, 17 | }) 18 | -------------------------------------------------------------------------------- /test-apps/multiple-backends/cypress/e2e/spec.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | it('has multiple backends with code coverage', () => { 3 | cy.visit('/') 4 | cy.request('/hello') 5 | cy.request('http://localhost:3004/world') 6 | }) 7 | -------------------------------------------------------------------------------- /test-apps/multiple-backends/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('@cypress/code-coverage/task')(on, config) 3 | return config 4 | } 5 | -------------------------------------------------------------------------------- /test-apps/multiple-backends/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | import '@cypress/code-coverage/support' 2 | -------------------------------------------------------------------------------- /test-apps/multiple-backends/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-multiple-backends", 3 | "description": "Code coverage for multiple backends", 4 | "private": true, 5 | "scripts": { 6 | "cy:run": "cypress run", 7 | "start": "nyc --silent node server/server-3003 & nyc --silent node server/server-3004", 8 | "pretest": "rimraf .nyc_output .cache coverage dist", 9 | "test": "start-test \"3003|3004\" cy:run", 10 | "coverage:verify": "npx nyc report --check-coverage true --lines 100", 11 | "coverage:check-files": "check-coverage server-3003.js server-3004.js && only-covered server-3003.js server-3004.js" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test-apps/multiple-backends/server/index.html: -------------------------------------------------------------------------------- 1 | 2 | test multiple backends 3 | 4 | -------------------------------------------------------------------------------- /test-apps/multiple-backends/server/server-3003.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('@cypress/code-coverage/middleware/express')(app) 12 | } 13 | 14 | app.use(express.static(__dirname)) 15 | 16 | app.get('/hello', (req, res) => { 17 | console.log('sending hello') 18 | res.send('Hello') 19 | }) 20 | 21 | app.listen(port, () => console.log(`Example app listening on port ${port}!`)) 22 | -------------------------------------------------------------------------------- /test-apps/multiple-backends/server/server-3004.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const app = express() 3 | const port = 3004 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('@cypress/code-coverage/middleware/express')(app) 12 | } 13 | 14 | app.use(express.static(__dirname)) 15 | 16 | app.get('/world', (req, res) => { 17 | console.log('sending world') 18 | res.send('World!') 19 | }) 20 | 21 | app.listen(port, () => console.log(`Example app listening on port ${port}!`)) 22 | -------------------------------------------------------------------------------- /test-apps/one-spec/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /test-apps/one-spec/README.md: -------------------------------------------------------------------------------- 1 | # example: one-spec 2 | 3 | Only running a single spec using a specFiles pattern 4 | -------------------------------------------------------------------------------- /test-apps/one-spec/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | fixturesFolder: false, 5 | e2e: { 6 | setupNodeEvents(on, config) { 7 | return require('./cypress/plugins/index.js')(on, config) 8 | }, 9 | baseUrl: 'http://localhost:1234', 10 | specPattern: 'cypress/e2e/spec-a.cy.js', 11 | }, 12 | }) 13 | -------------------------------------------------------------------------------- /test-apps/one-spec/cypress/e2e/spec-a.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | it('spec a', () => { 3 | cy.visit('/') 4 | cy.contains('Page body') 5 | 6 | cy.window() 7 | .invoke('add', 2, 3) 8 | .should('equal', 5) 9 | 10 | cy.window() 11 | .invoke('sub', 2, 3) 12 | .should('equal', -1) 13 | }) 14 | -------------------------------------------------------------------------------- /test-apps/one-spec/cypress/e2e/spec-b.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | it('spec b', () => { 3 | // should not run 4 | throw new Error('Spec b should not run') 5 | }) 6 | -------------------------------------------------------------------------------- /test-apps/one-spec/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('@cypress/code-coverage/task')(on, config) 3 | return config 4 | } 5 | -------------------------------------------------------------------------------- /test-apps/one-spec/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | import '@cypress/code-coverage/support' 2 | -------------------------------------------------------------------------------- /test-apps/one-spec/index.html: -------------------------------------------------------------------------------- 1 | 2 | Page body 3 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /test-apps/one-spec/main.js: -------------------------------------------------------------------------------- 1 | window.add = (a, b) => a + b 2 | 3 | window.sub = (a, b) => a - b 4 | -------------------------------------------------------------------------------- /test-apps/one-spec/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-one-spec", 3 | "description": "Only running a single spec", 4 | "scripts": { 5 | "cy:run": "cypress run", 6 | "start": "parcel serve index.html", 7 | "pretest": "rimraf .nyc_output .cache coverage dist", 8 | "test": "start-test 1234 cy:run", 9 | "coverage:verify": "npx nyc report --check-coverage true --lines 100", 10 | "coverage:check-files": "check-coverage main.js && only-covered main.js" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test-apps/redirect/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /test-apps/redirect/.nycrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": [ 3 | "app.js" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test-apps/redirect/README.md: -------------------------------------------------------------------------------- 1 | # example: redirect 2 | 3 | Tests a frontend app that redirects, through un-instrumented code, back to itself. 4 | -------------------------------------------------------------------------------- /test-apps/redirect/app.js: -------------------------------------------------------------------------------- 1 | // This redirect code needs to be un-instrumented (excluded in .nycrc.json) 2 | // - If the redirect code is instrumented, Cypress would then treat them as different coverage objects and merge the code coverage (not testing what we want). 3 | // - If the redirect code is un-instrumented, Cypress can't tell them apart and will update the existing coverage object to point to the correct one (testing what we want). 4 | 5 | import { returnToApp } from './utils' 6 | 7 | // Timeouts are necessary to allow cypress to pick up the "initial" coverage object and compare it to the existing coverage objects. 8 | new Promise((resolve) => { 9 | if (window.location.port === '1234' && !localStorage.getItem('visited')) { 10 | localStorage.setItem('visited', true) 11 | console.log('Not visited. Redirecting') 12 | setTimeout(() => { 13 | window.location.href = 'http://localhost:1235' 14 | }, 500) 15 | } else if (window.location.port === '1235') { 16 | console.log('Redirecting back.') 17 | setTimeout(() => { 18 | window.location.href = 'http://localhost:1234' 19 | }, 500) 20 | } else { 21 | console.log('Visited'); 22 | setTimeout(() => { 23 | resolve() 24 | }, 500) 25 | } 26 | }).then(() => { 27 | returnToApp() 28 | }) 29 | -------------------------------------------------------------------------------- /test-apps/redirect/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | fixturesFolder: false, 5 | e2e: { 6 | setupNodeEvents(on, config) { 7 | require('@cypress/code-coverage/task')(on, config) 8 | return config 9 | }, 10 | baseUrl: 'http://localhost:1234', 11 | env: { 12 | codeCoverage: { 13 | exclude: ['cypress/**/*.*'] 14 | } 15 | }, 16 | chromeWebSecurity: false 17 | } 18 | }) 19 | -------------------------------------------------------------------------------- /test-apps/redirect/cypress/e2e/spec.cy.js: -------------------------------------------------------------------------------- 1 | // enables intelligent code completion for Cypress commands 2 | // https://on.cypress.io/intelligent-code-completion 3 | /// 4 | 5 | context('Page test', () => { 6 | it('redirects back to the app', function() { 7 | cy.clearLocalStorage() 8 | cy.visit("http://localhost:1234") 9 | cy.contains("Returned to app") 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /test-apps/redirect/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | import '@cypress/code-coverage/support' 2 | -------------------------------------------------------------------------------- /test-apps/redirect/index.html: -------------------------------------------------------------------------------- 1 | 2 |

Test page

3 | 4 | 5 | -------------------------------------------------------------------------------- /test-apps/redirect/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-redirect", 3 | "description": "Tests a frontend app that redirects, through un-instrumented code, back to itself.", 4 | "devDependencies": { 5 | "@babel/core": "^7.12.0" 6 | }, 7 | "scripts": { 8 | "cy:run": "cypress run", 9 | "start:app": "parcel serve -p 1234 index.html", 10 | "start:other-app": "parcel serve -p 1235 index.html", 11 | "pretest": "rimraf .nyc_output .cache coverage dist", 12 | "test": "start-test start:app http://localhost:1234 start:other-app http://localhost:1235 cy:run", 13 | "coverage:verify": "npx nyc report --check-coverage true --lines 100", 14 | "coverage:check-files": "check-coverage utils.js && only-covered utils.js" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test-apps/redirect/utils.js: -------------------------------------------------------------------------------- 1 | export const returnToApp = () => { 2 | document.body 3 | .appendChild(document.createTextNode('Returned to app')) 4 | } 5 | -------------------------------------------------------------------------------- /test-apps/same-folder/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /test-apps/same-folder/README.md: -------------------------------------------------------------------------------- 1 | # example: same-folder 2 | 3 | Check if test files are correctly filtered out 4 | -------------------------------------------------------------------------------- /test-apps/same-folder/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | e2e: { 5 | setupNodeEvents(on, config) { 6 | return require('./plugins.js')(on, config) 7 | }, 8 | specPattern: './**/spec.js', 9 | supportFile: 'support.js', 10 | baseUrl: 'http://localhost:1234', 11 | env: { 12 | codeCoverage: { 13 | exclude: ['spec.js', 'support.js'] 14 | } 15 | } 16 | }, 17 | }) 18 | -------------------------------------------------------------------------------- /test-apps/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 | } -------------------------------------------------------------------------------- /test-apps/same-folder/index.html: -------------------------------------------------------------------------------- 1 | 2 | Page body 3 | 4 | 5 | -------------------------------------------------------------------------------- /test-apps/same-folder/main.js: -------------------------------------------------------------------------------- 1 | window.add = (a, b) => a + b 2 | 3 | window.sub = (a, b) => a - b 4 | -------------------------------------------------------------------------------- /test-apps/same-folder/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-same-folder", 3 | "description": "Check if test files are correctly filtered out", 4 | "scripts": { 5 | "cy:run": "cypress run", 6 | "start": "parcel serve index.html", 7 | "pretest": "rimraf .nyc_output .cache coverage dist", 8 | "test": "start-test 1234 cy:run", 9 | "coverage:verify": "npx nyc report --check-coverage true --lines 100", 10 | "coverage:check-files": "check-coverage main.js && check-coverage unit-utils.js && only-covered main.js unit-utils.js" 11 | }, 12 | "devDependencies": { 13 | "@babel/core": "^7.22.15" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test-apps/same-folder/plugins.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('@cypress/code-coverage/task')(on, config) 3 | on('file:preprocessor', require('@cypress/code-coverage/use-babelrc')) 4 | return config 5 | } 6 | -------------------------------------------------------------------------------- /test-apps/same-folder/spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { reverse } from './unit-utils' 4 | 5 | describe('coverage information', () => { 6 | beforeEach(() => { 7 | cy.log('visiting /') 8 | cy.visit('/') 9 | }) 10 | 11 | it('calls add', () => { 12 | cy.window() 13 | .invoke('add', 2, 3) 14 | .should('equal', 5) 15 | }) 16 | 17 | it('calls sub', () => { 18 | cy.window() 19 | .invoke('sub', 2, 3) 20 | .should('equal', -1) 21 | }) 22 | 23 | it('reverses a string', () => { 24 | expect(reverse('Hello')).to.equal('olleH') 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /test-apps/same-folder/support.js: -------------------------------------------------------------------------------- 1 | import '@cypress/code-coverage/support' 2 | -------------------------------------------------------------------------------- /test-apps/same-folder/unit-utils.js: -------------------------------------------------------------------------------- 1 | export const reverse = s => 2 | s 3 | .split('') 4 | .reverse() 5 | .join('') 6 | -------------------------------------------------------------------------------- /test-apps/support-files/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /test-apps/support-files/README.md: -------------------------------------------------------------------------------- 1 | # example: support-files 2 | 3 | Filtering out support files 4 | -------------------------------------------------------------------------------- /test-apps/support-files/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | fixturesFolder: false, 5 | e2e: { 6 | setupNodeEvents(on, config) { 7 | return require('./cypress/plugins/index.js')(on, config) 8 | }, 9 | baseUrl: 'http://localhost:1234', 10 | env: { 11 | codeCoverage: { 12 | exclude: ['cypress/**/**.*'] 13 | } 14 | } 15 | }, 16 | }) 17 | -------------------------------------------------------------------------------- /test-apps/support-files/cypress/e2e/spec.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | it('works', () => { 3 | cy.visit('/') 4 | cy.contains('Page body') 5 | }) 6 | -------------------------------------------------------------------------------- /test-apps/support-files/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('@cypress/code-coverage/task')(on, config) 3 | on('file:preprocessor', require('@cypress/code-coverage/use-babelrc')) 4 | return config 5 | } 6 | -------------------------------------------------------------------------------- /test-apps/support-files/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | import '@cypress/code-coverage/support' 2 | console.log('this is commands file') 3 | -------------------------------------------------------------------------------- /test-apps/support-files/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | require('./commands') 2 | -------------------------------------------------------------------------------- /test-apps/support-files/index.html: -------------------------------------------------------------------------------- 1 | 2 | Page body 3 | 4 | 13 | 14 | -------------------------------------------------------------------------------- /test-apps/support-files/main.js: -------------------------------------------------------------------------------- 1 | window.add = (a, b) => a + b 2 | 3 | window.sub = (a, b) => a - b 4 | -------------------------------------------------------------------------------- /test-apps/support-files/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-support-files", 3 | "description": "Filtering out support files", 4 | "scripts": { 5 | "cy:run": "cypress run", 6 | "start": "parcel serve index.html", 7 | "pretest": "rimraf .nyc_output .cache coverage dist", 8 | "test": "start-test 1234 cy:run", 9 | "coverage:verify": "npx nyc report --check-coverage true --lines 100", 10 | "coverage:check-files": "check-coverage main.js && only-covered main.js" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test-apps/ts-example/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /test-apps/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 | -------------------------------------------------------------------------------- /test-apps/ts-example/calc.ts: -------------------------------------------------------------------------------- 1 | export const add = (a: number, b: number) => { 2 | return a + b 3 | } 4 | -------------------------------------------------------------------------------- /test-apps/ts-example/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | fixturesFolder: false, 5 | e2e: { 6 | setupNodeEvents(on, config) { 7 | return require('./cypress/plugins/index.js')(on, config) 8 | }, 9 | baseUrl: 'http://localhost:1234', 10 | }, 11 | }) 12 | -------------------------------------------------------------------------------- /test-apps/ts-example/cypress/e2e/spec.cy.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 | -------------------------------------------------------------------------------- /test-apps/ts-example/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('@cypress/code-coverage/task')(on, config) 3 | return config 4 | } 5 | -------------------------------------------------------------------------------- /test-apps/ts-example/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | import '@cypress/code-coverage/support' 2 | -------------------------------------------------------------------------------- /test-apps/ts-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | Page body 3 | 4 | 5 | -------------------------------------------------------------------------------- /test-apps/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 | -------------------------------------------------------------------------------- /test-apps/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 | "typescript": "^4.5.5" 6 | }, 7 | "scripts": { 8 | "cy:run": "cypress run", 9 | "start": "parcel serve index.html", 10 | "pretest": "rimraf .nyc_output .cache coverage dist", 11 | "test": "start-test 1234 cy:run", 12 | "coverage:verify": "npx nyc report --check-coverage true --lines 100", 13 | "coverage:check-files": "check-coverage main.ts && check-coverage calc.ts && only-covered main.ts calc.ts" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test-apps/unit-tests-js/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /test-apps/unit-tests-js/README.md: -------------------------------------------------------------------------------- 1 | # example: unit-tests-js 2 | 3 | Examples that only run unit tests written using JavaScript 4 | -------------------------------------------------------------------------------- /test-apps/unit-tests-js/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | fixturesFolder: false, 5 | e2e: { 6 | setupNodeEvents(on, config) { 7 | return require('./cypress/plugins/index.js')(on, config) 8 | }, 9 | env: { 10 | codeCoverage: { 11 | exclude: 'cypress/**/*.*' 12 | } 13 | } 14 | }, 15 | }) 16 | -------------------------------------------------------------------------------- /test-apps/unit-tests-js/cypress/e2e/spec-a.cy.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 | -------------------------------------------------------------------------------- /test-apps/unit-tests-js/cypress/e2e/spec-b.cy.js: -------------------------------------------------------------------------------- 1 | // enables intelligent code completion for Cypress commands 2 | // https://on.cypress.io/intelligent-code-completion 3 | /// 4 | 5 | import { sub } from '../../src/utils/math' 6 | 7 | describe('Unit tests', () => { 8 | it('subtracts numbers', () => { 9 | expect(sub(10, 4)).to.equal(6) 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /test-apps/unit-tests-js/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports = (on, config) => { 2 | require('@cypress/code-coverage/task')(on, config) 3 | //used to instrument files included as unit tests 4 | on('file:preprocessor', require('@cypress/code-coverage/use-babelrc')) 5 | return config 6 | } 7 | -------------------------------------------------------------------------------- /test-apps/unit-tests-js/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | import '@cypress/code-coverage/support' 2 | -------------------------------------------------------------------------------- /test-apps/unit-tests-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-unit-tests-js", 3 | "description": "Run unit tests written using JavaScript", 4 | "scripts": { 5 | "pretest": "rimraf .nyc_output .cache coverage dist", 6 | "test": "cypress run", 7 | "coverage:verify": "npx nyc report --check-coverage true --lines 100", 8 | "coverage:check-files": "check-coverage misc.js math.js && only-covered misc.js math.js" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test-apps/unit-tests-js/src/utils/math.js: -------------------------------------------------------------------------------- 1 | export const sub = (a, b) => a - b 2 | -------------------------------------------------------------------------------- /test-apps/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 | -------------------------------------------------------------------------------- /test-apps/use-webpack/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } 4 | -------------------------------------------------------------------------------- /test-apps/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 | -------------------------------------------------------------------------------- /test-apps/use-webpack/cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | fixturesFolder: false, 5 | viewportHeight: 400, 6 | viewportWidth: 400, 7 | e2e: { 8 | setupNodeEvents(on, config) { 9 | return require('./cypress/plugins/index.js')(on, config) 10 | }, 11 | baseUrl: 'http://localhost:1234', 12 | env: { 13 | codeCoverage: { 14 | exclude: ['cypress/**/*.*'] 15 | } 16 | } 17 | }, 18 | }) 19 | -------------------------------------------------------------------------------- /test-apps/use-webpack/cypress/e2e/spec.cy.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 | -------------------------------------------------------------------------------- /test-apps/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('@cypress/code-coverage/task')(on, config) 17 | return config 18 | } 19 | -------------------------------------------------------------------------------- /test-apps/use-webpack/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | 2 | import '@cypress/code-coverage/support' -------------------------------------------------------------------------------- /test-apps/use-webpack/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Webpack example 5 | 20 | 21 | 22 |

Webpack page

23 | 24 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /test-apps/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:run": "cypress run", 8 | "build": "webpack && cp ./index.html ./dist/index.html", 9 | "start": "serve -p 1234 dist", 10 | "pretest": "rimraf .nyc_output .cache coverage dist", 11 | "test": "npm run build && start-test 1234 cy:run", 12 | "coverage:verify": "npx nyc report --check-coverage true --lines 100", 13 | "coverage:check-files": "check-coverage src/index.js && check-coverage src/calc.js && only-covered src/index.js src/calc.js" 14 | }, 15 | "keywords": [], 16 | "author": "", 17 | "license": "ISC" 18 | } 19 | -------------------------------------------------------------------------------- /test-apps/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 | -------------------------------------------------------------------------------- /test-apps/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 | -------------------------------------------------------------------------------- /test-apps/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 | -------------------------------------------------------------------------------- /use-babelrc.js: -------------------------------------------------------------------------------- 1 | const webpackPreprocessor = require('@cypress/webpack-preprocessor') 2 | const defaults = webpackPreprocessor.defaultOptions 3 | // remove presets so the babelrc file will be used 4 | delete defaults.webpackOptions.module.rules[0].use[0].options.presets 5 | module.exports = webpackPreprocessor(defaults) 6 | --------------------------------------------------------------------------------