├── .babelrc ├── .circleci └── config.yml ├── .github └── workflows │ └── main.yml ├── .gitignore ├── README.md ├── cy-react.png ├── cypress.json ├── cypress ├── fixtures │ └── example.json ├── integration │ └── examples │ │ └── actions.spec.js ├── plugins │ └── index.js └── support │ ├── index.d.ts │ └── index.ts ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── run-ct.js ├── src ├── App.css ├── App.spec.tsx ├── App.tsx ├── CustomCommand.spec.tsx ├── NewJsxTransform.spec.tsx ├── index.css ├── index.tsx ├── logo.svg ├── react-app-env.d.ts ├── reportWebVitals.ts └── setupTests.ts ├── tsconfig.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["istanbul"] 3 | } -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: cypress/base:14.15.0 6 | 7 | working_directory: ~/repo 8 | 9 | steps: 10 | - checkout 11 | 12 | # Download and cache dependencies 13 | - restore_cache: 14 | keys: 15 | - node-14-dependencies-{{ checksum "package.json" }} 16 | # fallback to using the latest cache if no exact match is found 17 | - node-14-dependencies- 18 | 19 | - run: yarn install 20 | 21 | - save_cache: 22 | paths: 23 | - node_modules 24 | key: node-14-dependencies-{{ checksum "package.json" }} 25 | 26 | # run tests! 27 | - run: yarn cypress run-ct --record --key f23ae75a-f00c-422e-aab4-7454cb9469e9 28 | - run: yarn cypress run --record --key f23ae75a-f00c-422e-aab4-7454cb9469e9 29 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [14.x] 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - run: yarn install 25 | - run: yarn cypress run-ct --record --key f23ae75a-f00c-422e-aab4-7454cb9469e9 26 | # - run: yarn cypress run --record --key f23ae75a-f00c-422e-aab4-7454cb9469e9 27 | 28 | # steps: 29 | # - uses: actions/checkout@v2 30 | # - name: Use Node.js ${{ matrix.node-version }} 31 | # uses: actions/setup-node@v1 32 | # with: 33 | # node-version: ${{ matrix.node-version }} 34 | # - run: yarn install 35 | # - run: node run-ct.js 36 | 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | cypress/videos 7 | .pnp.js 8 | 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | .nyc_output 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | As of Cypress 7.0, the new Component Testing runner is now bundled with the Cypress! It takes inspiration and builds on the learnings from the original Component Testing implementation, which was hidden behind the `experimentalComponentTesting` flag. 2 | 3 | In this blog post we will see how to set up Cypress Component Testing in a new React app created via Create React App using TypeScript. 4 | 5 | You can get the source code for the example used in the blog post [here](https://github.com/lmiller1990/cypress-react-template). 6 | 7 | ## Creating a new React Project 8 | 9 | Create a new React project to get started. Optionally add TypeScript - I'll be using it in this example. 10 | 11 | ```sh 12 | yarn create react-app cypress-test-react --template typescript 13 | ``` 14 | 15 | ## Configuring Cypress Component Testing 16 | 17 | Once you've got a React project, you'll also need to install Cypress and the Webpack Dev Server and React adapters. 18 | 19 | Create React App projects are Webpack based; that's why we are installing the relevant Webpack adapter. You also need `@cypress/react`, which is the primary way to mount and interact with components (similar to `mount` in Enzyme or `render` in Testing Library). 20 | 21 | ```sh 22 | yarn add cypress @cypress/react @cypress/webpack-dev-server --dev 23 | ``` 24 | 25 | Next, create a `cypress.json` with some basic configuration: 26 | 27 | ```json 28 | { 29 | "component": { 30 | "testFiles": "**/*.test.{js,ts,jsx,tsx}", 31 | "componentFolder": "src" 32 | } 33 | } 34 | ``` 35 | 36 | Here we are adding some Component Testing specific options, hence the `"component"` key. `"componentFolder"` is where all the components and tests are located, and `"testFiles"` is the pattern to search for test files. 37 | 38 | The last thing we need to is tell Cypress to use `@cypress/webpack-dev-server` for component tests. Plugins are explained in detail in the [Cypress documentation](https://docs.cypress.io/guides/tooling/plugins-guide#Installing-plugins). By default plugins are loaded from `cypress/plugins/index.js`. Create that file and add: 39 | 40 | ```js 41 | const injectDevServer = require("@cypress/react/plugins/react-scripts") 42 | 43 | module.exports = (on, config) => { 44 | injectDevServer(on, config) 45 | return config 46 | } 47 | ``` 48 | 49 | 50 | This will configure the Cypress Webpack Dev Server to use the same Webpack configuration as Create React App uses. 51 | 52 | If you are using a different template, like Next.js, we have some other [adapters available](https://github.com/cypress-io/cypress/tree/develop/npm/react/plugins). It's also possible to create your own adapter. 53 | 54 | ## Writing Some Tests 55 | 56 | Let's migrate `src/App.test.tsx`, which comes with the Create React App template, to use Cypress. It's a simple migration: 57 | 58 | ```tsx 59 | import React from 'react'; 60 | import { mount } from '@cypress/react'; 61 | import App from './App'; 62 | 63 | it('renders learn react link', () => { 64 | mount(); 65 | cy.get('a').contains('Learn React'); 66 | }); 67 | ``` 68 | 69 | Most tests will start with `mount` from `@cypress/react`. This is similar to `render` in Testing Library. Once you've mounted your component, you can use Cypress' extensive [query and assertion APIs](https://docs.cypress.io/api/table-of-contents) to ensure everything behaves correctly. This example asserts an anchor tag with the text "Learn React" is rendered. 70 | 71 | Open the component testing runner with: 72 | 73 | ```sh 74 | yarn cypress open-ct 75 | ``` 76 | 77 | And select the spec to run. 78 | 79 | ![](https://raw.githubusercontent.com/lmiller1990/cypress-react-template/master/cy-react.png) 80 | 81 | Try making a change - the tests will re-run instantly. You not only immediately know if the test passed or failed, but be able to visually inspect and debug any changes. 82 | 83 | You can run all the specs with `yarn cypress run-ct`. This is useful for executing all the specs in a CI environment, or one last check before you commit and push your code! 84 | 85 | ## Discussion 86 | 87 | Cypress Component Testing is an alternative to a jsdom based testing environment, such as Jest and Vue Test Utils. Cypress Component Testing offers many benefits: 88 | 89 | - Runs in a real browser. This means your tests are closer to what your users will be experiencing. 90 | - Visual. You can see exactly what is rendered. No more scrolling through a cryptic terminal log to figure out what is rendered or to debug - just open the devtools and browse the DOM. 91 | - Powered by Cypress - the most popular and reliable E2E testing tool out there. 92 | 93 | It also doubles as a *design environment*. You can see the component as you develop it, and hot reload give you a near instance feedback loop. It can potentially take the place of not only your Jest based test infrastructure, but your Storybook based design infrastructure as well. 94 | 95 | Cypress Component Testing is still in alpha but the product is quickly evolving and promises to change the landscape of Component Testing. 96 | 97 | ## Conclusion 98 | 99 | Cypress Component Testing brings everything that is great about Cypress to Component Testing. Since the underlying adapters are built on libraries like Webpack, you don't need to throw away your entire test suite - incremental migration is more than possible. 100 | 101 | The visual aspect united testing and design in a single tool. My days of grepping a messy console output to figure out what the user will see are over - I can see exactly what the component will look like as my tests run. 102 | 103 | You can get the source code for the blog post [here](https://github.com/lmiller1990/cypress-react-template). 104 | -------------------------------------------------------------------------------- /cy-react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmiller1990/cypress-react-template/c187b96d84b1266fb6f8d86790ec48e52521443b/cy-react.png -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "video": true, 3 | "projectId": "jq5xpp", 4 | "component": { 5 | "testFiles": "**/*.spec.{js,ts,jsx,tsx}", 6 | "componentFolder": "src" 7 | }, 8 | "env": { 9 | "cypress-react-selector": { 10 | "root": "#__cy_root" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /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/integration/examples/actions.spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | context('Actions', () => { 4 | beforeEach(() => { 5 | cy.visit('https://example.cypress.io/commands/actions') 6 | }) 7 | 8 | // https://on.cypress.io/interacting-with-elements 9 | 10 | it('.type() - type into a DOM element', () => { 11 | // https://on.cypress.io/type 12 | cy.get('.action-email') 13 | .type('fake@email.com').should('have.value', 'fake@email.com') 14 | 15 | // .type() with special character sequences 16 | .type('{leftarrow}{rightarrow}{uparrow}{downarrow}') 17 | .type('{del}{selectall}{backspace}') 18 | 19 | // .type() with key modifiers 20 | .type('{alt}{option}') //these are equivalent 21 | .type('{ctrl}{control}') //these are equivalent 22 | .type('{meta}{command}{cmd}') //these are equivalent 23 | .type('{shift}') 24 | 25 | // Delay each keypress by 0.1 sec 26 | .type('slow.typing@email.com', { delay: 100 }) 27 | .should('have.value', 'slow.typing@email.com') 28 | 29 | cy.get('.action-disabled') 30 | // Ignore error checking prior to type 31 | // like whether the input is visible or disabled 32 | .type('disabled error checking', { force: true }) 33 | .should('have.value', 'disabled error checking') 34 | }) 35 | 36 | it('.focus() - focus on a DOM element', () => { 37 | // https://on.cypress.io/focus 38 | cy.get('.action-focus').focus() 39 | .should('have.class', 'focus') 40 | .prev().should('have.attr', 'style', 'color: orange;') 41 | }) 42 | 43 | it('.blur() - blur off a DOM element', () => { 44 | // https://on.cypress.io/blur 45 | cy.get('.action-blur').type('About to blur').blur() 46 | .should('have.class', 'error') 47 | .prev().should('have.attr', 'style', 'color: red;') 48 | }) 49 | 50 | it('.clear() - clears an input or textarea element', () => { 51 | // https://on.cypress.io/clear 52 | cy.get('.action-clear').type('Clear this text') 53 | .should('have.value', 'Clear this text') 54 | .clear() 55 | .should('have.value', '') 56 | }) 57 | 58 | it('.submit() - submit a form', () => { 59 | // https://on.cypress.io/submit 60 | cy.get('.action-form') 61 | .find('[type="text"]').type('HALFOFF') 62 | 63 | cy.get('.action-form').submit() 64 | .next().should('contain', 'Your form has been submitted!') 65 | }) 66 | 67 | it('.click() - click on a DOM element', () => { 68 | // https://on.cypress.io/click 69 | cy.get('.action-btn').click() 70 | 71 | // You can click on 9 specific positions of an element: 72 | // ----------------------------------- 73 | // | topLeft top topRight | 74 | // | | 75 | // | | 76 | // | | 77 | // | left center right | 78 | // | | 79 | // | | 80 | // | | 81 | // | bottomLeft bottom bottomRight | 82 | // ----------------------------------- 83 | 84 | // clicking in the center of the element is the default 85 | cy.get('#action-canvas').click() 86 | 87 | cy.get('#action-canvas').click('topLeft') 88 | cy.get('#action-canvas').click('top') 89 | cy.get('#action-canvas').click('topRight') 90 | cy.get('#action-canvas').click('left') 91 | cy.get('#action-canvas').click('right') 92 | cy.get('#action-canvas').click('bottomLeft') 93 | cy.get('#action-canvas').click('bottom') 94 | cy.get('#action-canvas').click('bottomRight') 95 | 96 | // .click() accepts an x and y coordinate 97 | // that controls where the click occurs :) 98 | 99 | cy.get('#action-canvas') 100 | .click(80, 75) // click 80px on x coord and 75px on y coord 101 | .click(170, 75) 102 | .click(80, 165) 103 | .click(100, 185) 104 | .click(125, 190) 105 | .click(150, 185) 106 | .click(170, 165) 107 | 108 | // click multiple elements by passing multiple: true 109 | cy.get('.action-labels>.label').click({ multiple: true }) 110 | 111 | // Ignore error checking prior to clicking 112 | cy.get('.action-opacity>.btn').click({ force: true }) 113 | }) 114 | 115 | it('.dblclick() - double click on a DOM element', () => { 116 | // https://on.cypress.io/dblclick 117 | 118 | // Our app has a listener on 'dblclick' event in our 'scripts.js' 119 | // that hides the div and shows an input on double click 120 | cy.get('.action-div').dblclick().should('not.be.visible') 121 | cy.get('.action-input-hidden').should('be.visible') 122 | }) 123 | 124 | it('.rightclick() - right click on a DOM element', () => { 125 | // https://on.cypress.io/rightclick 126 | 127 | // Our app has a listener on 'contextmenu' event in our 'scripts.js' 128 | // that hides the div and shows an input on right click 129 | cy.get('.rightclick-action-div').rightclick().should('not.be.visible') 130 | cy.get('.rightclick-action-input-hidden').should('be.visible') 131 | }) 132 | 133 | it('.check() - check a checkbox or radio element', () => { 134 | // https://on.cypress.io/check 135 | 136 | // By default, .check() will check all 137 | // matching checkbox or radio elements in succession, one after another 138 | cy.get('.action-checkboxes [type="checkbox"]').not('[disabled]') 139 | .check().should('be.checked') 140 | 141 | cy.get('.action-radios [type="radio"]').not('[disabled]') 142 | .check().should('be.checked') 143 | 144 | // .check() accepts a value argument 145 | cy.get('.action-radios [type="radio"]') 146 | .check('radio1').should('be.checked') 147 | 148 | // .check() accepts an array of values 149 | cy.get('.action-multiple-checkboxes [type="checkbox"]') 150 | .check(['checkbox1', 'checkbox2']).should('be.checked') 151 | 152 | // Ignore error checking prior to checking 153 | cy.get('.action-checkboxes [disabled]') 154 | .check({ force: true }).should('be.checked') 155 | 156 | cy.get('.action-radios [type="radio"]') 157 | .check('radio3', { force: true }).should('be.checked') 158 | }) 159 | 160 | it('.uncheck() - uncheck a checkbox element', () => { 161 | // https://on.cypress.io/uncheck 162 | 163 | // By default, .uncheck() will uncheck all matching 164 | // checkbox elements in succession, one after another 165 | cy.get('.action-check [type="checkbox"]') 166 | .not('[disabled]') 167 | .uncheck().should('not.be.checked') 168 | 169 | // .uncheck() accepts a value argument 170 | cy.get('.action-check [type="checkbox"]') 171 | .check('checkbox1') 172 | .uncheck('checkbox1').should('not.be.checked') 173 | 174 | // .uncheck() accepts an array of values 175 | cy.get('.action-check [type="checkbox"]') 176 | .check(['checkbox1', 'checkbox3']) 177 | .uncheck(['checkbox1', 'checkbox3']).should('not.be.checked') 178 | 179 | // Ignore error checking prior to unchecking 180 | cy.get('.action-check [disabled]') 181 | .uncheck({ force: true }).should('not.be.checked') 182 | }) 183 | 184 | it('.select() - select an option in a