├── .gitignore ├── LICENSE ├── README.md ├── babelrc.js ├── cypress.json ├── cypress ├── fixtures │ └── example.json ├── integration │ └── feature.js ├── plugins │ └── index.js └── support │ ├── commands.js │ └── index.js ├── package.json ├── public └── index.html ├── renovate.json ├── src ├── App.tsx └── index.tsx ├── tsconfig.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | /.nyc_output 9 | 10 | # misc 11 | .DS_Store 12 | .env.local 13 | .env.development.local 14 | .env.test.local 15 | .env.production.local 16 | 17 | npm-debug.log* 18 | yarn-debug.log* 19 | yarn-error.log* 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Lluis Agusti 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cypress + Typescript App + Code Coverage ❤ ️️ 2 | 3 | This example uses [@cypress/code-coverage](https://github.com/cypress-io/code-coverage) plugin for [Cypress.io](https://www.cypress.io) test runner. 4 | 5 | This repository aims [to document](https://github.com/cypress-io/code-coverage/issues/19) how to set up code coverage in Cypress against a Typescript app. 6 | 7 | The tricky part of this particular setup is to configure Istanbul to instrument your Typescript code. 8 | 9 | See [Cypress code coverage](https://on.cypress.io/code-coverage) docs for more insight on how it works. 10 | 11 | ## Running the example 🏃🏻‍ 12 | 13 | First, make sure you have [Yarn](https://yarnpkg.com/en/docs/install#mac-stable) installed, and then: 14 | 15 | ``` 16 | yarn 17 | ``` 18 | 19 | You can run the example Typescript app: 20 | 21 | ``` 22 | yarn start 23 | ``` 24 | 25 | Or run the Cypress tests with code coverage: 26 | 27 | ``` 28 | CODE_COVERAGE=true yarn test 29 | ``` 30 | 31 | ⚠️ 32 | 33 | _We're enabling code coverage behind an environment variable to **only** instrument our code in this scenario. Don't serve instrumented code to your users._ 34 | 35 | To see the code coverage report just do: 36 | 37 | ``` 38 | open coverage/lcov-report/index.html 39 | ``` 40 | 41 | ## How does it work 🤨 42 | 43 | After installing the handy [`@cypress/code-coverage` plugin](https://docs.cypress.io/guides/tooling/code-coverage.html#Install-the-plugin), we will need to [instrument our compiled TS code](https://docs.cypress.io/guides/tooling/code-coverage.html#Instrumenting-code) run by Cypress ( _see the ["Instrumenting the code"](#instrumenting-the-code) section below_ ). 44 | 45 | Once your instrumentation is set up, we need to install a few extra dependencies to make **Istanbul** understand your **Typescript** source files: 46 | 47 | ``` 48 | yarn add -D \ 49 | @istanbuljs/nyc-config-typescript \ 50 | source-map-support \ 51 | ts-node 52 | ``` 53 | 54 | and make sure that Istanbul takes advantage of it by adding this configuration in your `package.json` or in `.nycrc.json` 55 | 56 | ```json 57 | "nyc": { 58 | "extends": "@istanbuljs/nyc-config-typescript", 59 | "all": true 60 | }, 61 | ``` 62 | 63 | ## Instrumenting the code 64 | 65 | #### Babel 66 | 67 | If you're compiling TS though Babel, the easiest way is to use `babel-plugin-istanbul` which will instrument the transpiled code for us. ( _this is the approach taken on this example repo_ ): 68 | 69 | ``` 70 | yarn add -D babel-plugin-istanbul 71 | ``` 72 | 73 | #### Istanbul 74 | 75 | In case you're compiling TS through the TSC, you'll need to manually make Istanbul instrument your TS source files and serve that to Cypress: 76 | 77 | ``` 78 | yarn nyc instrument --compact=false src instrumented 79 | ``` 80 | -------------------------------------------------------------------------------- /babelrc.js: -------------------------------------------------------------------------------- 1 | const { CODE_COVERAGE } = process.env; 2 | const plugins = ['babel-plugin-styled-components']; 3 | 4 | if (CODE_COVERAGE === 'true') plugins.push('istanbul'); 5 | 6 | module.exports = { 7 | presets: [ 8 | '@babel/preset-env', 9 | '@babel/typescript', 10 | [ 11 | '@babel/preset-react', 12 | { 13 | development: true, 14 | }, 15 | ], 16 | ], 17 | plugins, 18 | babelrc: false, 19 | }; 20 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:8080" 3 | } 4 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /cypress/integration/feature.js: -------------------------------------------------------------------------------- 1 | describe('When landing on the application', () => { 2 | before(() => { 3 | cy.visit('/'); 4 | }); 5 | 6 | it('should greet the user', () => { 7 | cy.queryByText('Hello world').should('exist'); 8 | }); 9 | 10 | it('should show planet earth', () => { 11 | cy.queryByText('🌏').should('exist'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example plugins/index.js can be used to load plugins 3 | // 4 | // You can change the location of this file or turn off loading 5 | // the plugins file with the 'pluginsFile' configuration option. 6 | // 7 | // You can read more here: 8 | // https://on.cypress.io/plugins-guide 9 | // *********************************************************** 10 | 11 | // This function is called when a project is opened or re-opened (e.g. due to 12 | // the project's config changing) 13 | 14 | module.exports = on => { 15 | on('task', require('@cypress/code-coverage/task')); 16 | }; 17 | -------------------------------------------------------------------------------- /cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | import '@testing-library/cypress/add-commands'; 27 | -------------------------------------------------------------------------------- /cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js 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 | import './commands'; 16 | import '@cypress/code-coverage/support'; 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cypress-typescript-code-coverage-example", 3 | "version": "0.1.0", 4 | "dependencies": { 5 | "@types/styled-components": "4.4.3", 6 | "react": "16.14.0", 7 | "react-dom": "16.14.0" 8 | }, 9 | "scripts": { 10 | "start": "yarn webpack-dev-server", 11 | "test": "concurrently --kill-others-on-fail -p {name} --names App,Cypress --prefix-colors bgBlue.bold,bgGreen.bold \" BROWSER=none yarn start\" \"yarn cypress open\"" 12 | }, 13 | "nyc": { 14 | "extends": "@istanbuljs/nyc-config-typescript", 15 | "all": true 16 | }, 17 | "devDependencies": { 18 | "@babel/core": "7.26.9", 19 | "@babel/preset-env": "7.26.9", 20 | "@babel/preset-react": "7.26.3", 21 | "@babel/preset-typescript": "7.26.0", 22 | "@cypress/code-coverage": "1.14.0", 23 | "@istanbuljs/nyc-config-typescript": "0.1.3", 24 | "@testing-library/cypress": "5.3.1", 25 | "@types/react": "16.14.62", 26 | "@types/react-dom": "16.9.25", 27 | "babel-loader": "8.4.1", 28 | "babel-plugin-istanbul": "5.2.0", 29 | "babel-plugin-styled-components": "1.13.3", 30 | "concurrently": "5.3.0", 31 | "cypress": "3.8.3", 32 | "html-webpack-plugin": "3.2.0", 33 | "istanbul-lib-coverage": "2.0.5", 34 | "nyc": "14.1.1", 35 | "source-map-support": "0.5.21", 36 | "styled-components": "4.4.1", 37 | "ts-node": "8.10.2", 38 | "typescript": "3.9.10", 39 | "webpack": "4.47.0", 40 | "webpack-cli": "3.3.12", 41 | "webpack-dev-server": "3.11.3" 42 | }, 43 | "resolutions": { 44 | "lodash": "^4.17.13" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Cypress TS Code Coverage Example 8 | 9 | 10 | 11 |
12 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "commitBodyTable": true, 4 | "requiredStatusChecks": null, 5 | "packageRules": [ 6 | { 7 | "updateTypes": ["minor", "patch", "pin", "digest"], 8 | "automerge": true 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled, { createGlobalStyle } from 'styled-components'; 3 | 4 | const GlobalStyle = createGlobalStyle` 5 | body { 6 | margin: 0; 7 | background-color: tomato; 8 | font-family: 'Bangers', Arial Black, cursive; 9 | font-size: 80px; 10 | color: white; 11 | letter-spacing: 10px; 12 | } 13 | `; 14 | 15 | const Wrapper = styled.div` 16 | display: flex; 17 | align-items: center; 18 | justify-content: center; 19 | min-height: 100vh; 20 | `; 21 | 22 | const EarthEmoji = styled.span` 23 | display: inline-block; 24 | margin-left: 30px; 25 | `; 26 | 27 | function App() { 28 | return ( 29 | <> 30 | 31 | 32 | Hello world{' '} 33 | 34 | 🌏 35 | 36 | 37 | 38 | ); 39 | } 40 | 41 | export default App; 42 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | -------------------------------------------------------------------------------- /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 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "noEmit": true, 20 | "jsx": "preserve" 21 | }, 22 | "include": [ 23 | "src" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const babelConfig = require('./babelrc'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | 5 | function webpackConfig() { 6 | return { 7 | entry: path.resolve(__dirname, 'src', 'index.tsx'), 8 | module: { 9 | rules: [ 10 | { 11 | test: /\.(ts|tsx)$/, 12 | use: [ 13 | { 14 | loader: 'babel-loader', 15 | options: { ...babelConfig }, 16 | }, 17 | ], 18 | }, 19 | ], 20 | }, 21 | plugins: [ 22 | new HtmlWebpackPlugin({ 23 | template: path.resolve(__dirname, 'public', 'index.html'), 24 | }), 25 | ], 26 | resolve: { 27 | extensions: ['.js', '.jsx', '.tsx', '.ts', '.json'], 28 | }, 29 | }; 30 | } 31 | 32 | module.exports = webpackConfig; 33 | --------------------------------------------------------------------------------