Skip to content

Commit

Permalink
fix(cypress/react): add default webpack config path (#16813)
Browse files Browse the repository at this point in the history
Co-authored-by: Barthélémy Ledoux <bart@cypress.io>
  • Loading branch information
lmiller1990 and elevatebart committed Jun 4, 2021
1 parent 98fe58c commit 0348170
Show file tree
Hide file tree
Showing 28 changed files with 402 additions and 1 deletion.
1 change: 1 addition & 0 deletions npm/react/examples.env
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# /examples/nextjs-webpack-5
/examples/react-scripts
/examples/react-scripts-typescript
/examples/find-webpack
/examples/webpack-file
/examples/react-scripts-folder
/examples/using-babel-typescript
Expand Down
103 changes: 103 additions & 0 deletions npm/react/examples/find-webpack/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
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.

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.

You can get the source code for the example used in the blog post [here](https://github.com/lmiller1990/cypress-react-template).

## Creating a new React Project

Create a new React project to get started. Optionally add TypeScript - I'll be using it in this example.

```sh
yarn create react-app cypress-test-react --template typescript
```

## Configuring Cypress Component Testing

Once you've got a React project, you'll also need to install Cypress and the Webpack Dev Server and React adapters.

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).

```sh
yarn add cypress @cypress/react @cypress/webpack-dev-server --dev
```

Next, create a `cypress.json` with some basic configuration:

```json
{
"component": {
"testFiles": "**/*.test.{js,ts,jsx,tsx}",
"componentFolder": "src"
}
}
```

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.

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:

```js
const injectDevServer = require("@cypress/react/plugins/react-scripts")

module.exports = (on, config) => {
injectDevServer(on, config)
return config
}
```


This will configure the Cypress Webpack Dev Server to use the same Webpack configuration as Create React App uses.

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.

## Writing Some Tests

Let's migrate `src/App.test.tsx`, which comes with the Create React App template, to use Cypress. It's a simple migration:

```tsx
import React from 'react';
import { mount } from '@cypress/react';
import App from './App';

it('renders learn react link', () => {
mount(<App />);
cy.get('a').contains('Learn React');
});
```

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.

Open the component testing runner with:

```sh
yarn cypress open-ct
```

And select the spec to run.

![](https://raw.githubusercontent.com/lmiller1990/cypress-react-template/master/cy-react.png)

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.

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!

## Discussion

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:

- Runs in a real browser. This means your tests are closer to what your users will be experiencing.
- 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.
- Powered by Cypress - the most popular and reliable E2E testing tool out there.

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.

Cypress Component Testing is still in alpha but the product is quickly evolving and promises to change the landscape of Component Testing.

## Conclusion

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.

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.

You can get the source code for the blog post [here](https://github.com/lmiller1990/cypress-react-template).
13 changes: 13 additions & 0 deletions npm/react/examples/find-webpack/cypress.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"video": true,
"projectId": "jq5xpp",
"component": {
"testFiles": "**/*.spec.{js,ts,jsx,tsx}",
"componentFolder": "src"
},
"env": {
"cypress-react-selector": {
"root": "#__cy_root"
}
}
}
5 changes: 5 additions & 0 deletions npm/react/examples/find-webpack/cypress/fixtures/example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}
24 changes: 24 additions & 0 deletions npm/react/examples/find-webpack/cypress/plugins/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const findReactScriptsWebpackConfig = require('@cypress/react/plugins/react-scripts/findReactScriptsWebpackConfig')
const { startDevServer } = require('@cypress/webpack-dev-server')
const _ = require('lodash')

module.exports = (on, config) => {
const map = _.map([4, 8], (n) => n * 2)

console.log(map)
require('@cypress/code-coverage/task')(on, config)
const webpackConfig = findReactScriptsWebpackConfig(config)

const rules = webpackConfig.module.rules.find((rule) => !!rule.oneOf).oneOf
const babelRule = rules.find((rule) => /babel-loader/.test(rule.loader))

babelRule.options.plugins.push(require.resolve('babel-plugin-istanbul'))

on('dev-server:start', (options) => {
return startDevServer({ options, webpackConfig })
})

// IMPORTANT to return the config object
// with the any changed environment variables
return config
}
Empty file.
24 changes: 24 additions & 0 deletions npm/react/examples/find-webpack/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "find-webpack",
"version": "1.0.0",
"private": true,
"scripts": {
"cy:open": "node ../../../../scripts/cypress open-ct",
"test": "node ../../../../scripts/cypress run-ct"
},
"dependencies": {
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"lodash": "^4.17.21",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "4.0.3",
"typescript": "^4.2.4"
},
"devDependencies": {
"@cypress/react": "file:../../dist",
"html-webpack-plugin": "4",
"mocha-junit-reporter": "^2.0.0",
"mocha-multi-reporters": "^1.5.1"
}
}
Binary file not shown.
43 changes: 43 additions & 0 deletions npm/react/examples/find-webpack/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions npm/react/examples/find-webpack/public/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
3 changes: 3 additions & 0 deletions npm/react/examples/find-webpack/public/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
38 changes: 38 additions & 0 deletions npm/react/examples/find-webpack/src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
.App {
text-align: center;
}

.App-logo {
height: 40vmin;
pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}

.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}

.App-link {
color: #61dafb;
}

@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
31 changes: 31 additions & 0 deletions npm/react/examples/find-webpack/src/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react'
import logo from './logo.svg'
import './App.css'

function bar () {
return 'bar'
}

function App () {
return (
<div className="App">
<h1>{bar()}</h1>
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
)
}

export default App
8 changes: 8 additions & 0 deletions npm/react/examples/find-webpack/src/App.spec.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from 'react'
import { mount } from '@cypress/react'
import App from './App'

it('renders learn react link', () => {
mount(<App />)
cy.get('a').contains('Learn React')
})
13 changes: 13 additions & 0 deletions npm/react/examples/find-webpack/src/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
17 changes: 17 additions & 0 deletions npm/react/examples/find-webpack/src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import reportWebVitals from './reportWebVitals'

ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root'),
)

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals()
1 change: 1 addition & 0 deletions npm/react/examples/find-webpack/src/logo.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions npm/react/examples/find-webpack/src/react-app-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="react-scripts" />
15 changes: 15 additions & 0 deletions npm/react/examples/find-webpack/src/reportWebVitals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ReportHandler } from 'web-vitals'

const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry)
getFID(onPerfEntry)
getFCP(onPerfEntry)
getLCP(onPerfEntry)
getTTFB(onPerfEntry)
})
}
}

export default reportWebVitals
5 changes: 5 additions & 0 deletions npm/react/examples/find-webpack/src/setupTests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom'

0 comments on commit 0348170

Please sign in to comment.