-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #177 from martinnormark/cypress
Add component- and e2e testing with Cypress
- Loading branch information
Showing
14 changed files
with
1,764 additions
and
15,501 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { defineConfig } from "cypress"; | ||
import getCompareSnapshotsPlugin from "cypress-image-diff-js/dist/plugin"; | ||
|
||
export default defineConfig({ | ||
e2e: { | ||
baseUrl: "http://localhost:3000", | ||
setupNodeEvents(on, config) { | ||
// implement node event listeners here | ||
getCompareSnapshotsPlugin(on, config); | ||
}, | ||
}, | ||
|
||
component: { | ||
devServer: { | ||
framework: "next", | ||
bundler: "webpack", | ||
viewportWidth: 500, | ||
viewportHeight: 500, | ||
}, | ||
setupNodeEvents(on, config) { | ||
// implement node event listeners here | ||
getCompareSnapshotsPlugin(on, config); | ||
}, | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
# Component and e2e testing with Cypress | ||
|
||
[Cypress](https://www.cypress.io/) is used for both component- and end-to-end testing. Below there's a few examples for the context of this site. To learn more, the [Cypress documentation](https://docs.cypress.io/guides/getting-started/opening-the-app) has it all. | ||
|
||
Don't get scared by the commercial offerings they offer. Their core is open source, the cloud offering is not necesarry at all and can be replaced by CI tooling and [community efforts](https://sorry-cypress.dev/). | ||
|
||
# Component testing | ||
|
||
To write a new component test, you either create a new `.tsx` adjacent to the component you want to test or you can use the guide presented yo you when running `npm run cypress` which allows you to easily create the skeleton test for an existing component. | ||
|
||
If you have a `Button.tsx` component, create a file next to it called `Button.cy.tsx` which could look like this: | ||
|
||
```typescript | ||
import React from "react"; | ||
import { Button } from "./Button"; | ||
|
||
describe("<Button />", () => { | ||
it("renders", () => { | ||
// see: https://on.cypress.io/mounting-react | ||
cy.mount(<Button className="border-gray-800 m-5">Test button</Button>); | ||
cy.get("button").compareSnapshot("button-element"); | ||
}); | ||
}); | ||
``` | ||
|
||
## What's happening here? | ||
|
||
First we use `cy.mount` to mount our component under test. Notive how we specify `className` and inner text - this is where we arrange our component with fake data that we could assert on later. | ||
|
||
In the example above, we also use `cy.get` to select the rendered `button` element. Cypress has multiple ways to [select elements](https://docs.cypress.io/guides/references/best-practices), `get` is just one of them (and often not recommended). | ||
|
||
At last, we use `captureSnapshot` which is a plugin that snaps a photo of the `button` element and compares it to a baseline located in the `./cypress-visual-screenshots/baseline/` folder. If there's too many unidentical pixels between the two, it will fail the test. | ||
|
||
# End-to-end (e2e) testing | ||
|
||
e2e tests are stored in the `./cypress/e2e` folder and should be named `{page}.cy.ts` and located in a relative folder structure that mirrors the page under test. | ||
|
||
When running `npm run cypress` and selecting e2e testing, we assume you have the NextJS site running at `localhost:3000`. | ||
|
||
An example test from this time of writing, could look as follows: | ||
|
||
```typescript | ||
describe("signin flow", () => { | ||
it("redirects to a confirmation page on submit of valid email address", () => { | ||
cy.visit("/auth/signin"); | ||
cy.get(".chakra-input").type(`test@example.com{enter}`); | ||
cy.url().should("contain", "/auth/verify"); | ||
}); | ||
}); | ||
|
||
export {}; | ||
``` | ||
|
||
## What's happening here? | ||
|
||
First we use [`cy.visit`](https://docs.cypress.io/api/commands/visit) to point the browser at the desired page. It appends relative paths to the configured `baseUrl` (found in `./cypress.config.ts`). | ||
|
||
Cypress will [automatically await](https://docs.cypress.io/guides/core-concepts/introduction-to-cypress#Timeouts) almost anything you do, but fail if the default timeout is reached. | ||
|
||
Then we get the email input field and type our email address. Notice the `{enter}` keyword, this will cause Cypress to hit the return key which we expect to submit the form. | ||
|
||
We then assert that the URL should contain `/auth/verify`. Again the timeout will make sure we are not waiting forever, and the test will fail if we do not manage to get there in a reasonable time. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
describe("signin flow", () => { | ||
it("redirects to a confirmation page on submit of valid email address", () => { | ||
cy.visit("/auth/signin"); | ||
cy.get(".chakra-input").type(`test@example.com`); | ||
cy.get(".chakra-stack > .chakra-button").click(); | ||
cy.url().should("contain", "/auth/verify"); | ||
}); | ||
}); | ||
|
||
export {}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/// <reference types="cypress" /> | ||
// *********************************************** | ||
// This example commands.ts shows you how to | ||
// create various custom commands and overwrite | ||
// existing commands. | ||
// | ||
// For more comprehensive examples of custom | ||
// commands please read more here: | ||
// https://on.cypress.io/custom-commands | ||
// *********************************************** | ||
// | ||
// | ||
// -- This is a parent command -- | ||
// Cypress.Commands.add('login', (email, password) => { ... }) | ||
// | ||
// | ||
// -- This is a child command -- | ||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) | ||
// | ||
// | ||
// -- This is a dual command -- | ||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) | ||
// | ||
// | ||
// -- This will overwrite an existing command -- | ||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) | ||
// | ||
// declare global { | ||
// namespace Cypress { | ||
// interface Chainable { | ||
// login(email: string, password: string): Chainable<void> | ||
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element> | ||
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element> | ||
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element> | ||
// } | ||
// } | ||
// } | ||
|
||
export {}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta charset="utf-8" /> | ||
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> | ||
<meta name="viewport" content="width=device-width,initial-scale=1.0" /> | ||
<title>Components App</title> | ||
<!-- Used by Next.js to inject CSS. --> | ||
<div id="__next_css__DO_NOT_USE__"></div> | ||
</head> | ||
<body> | ||
<div data-cy-root></div> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// *********************************************************** | ||
// This example support/component.ts is processed and | ||
// loaded automatically before your test files. | ||
// | ||
// This is a great place to put global configuration and | ||
// behavior that modifies Cypress. | ||
// | ||
// You can change the location of this file or turn off | ||
// automatically serving support files with the | ||
// 'supportFile' configuration option. | ||
// | ||
// You can read more here: | ||
// https://on.cypress.io/configuration | ||
// *********************************************************** | ||
|
||
// Import commands.js using ES2015 syntax: | ||
import "./commands"; | ||
import compareSnapshotCommand from "cypress-image-diff-js/dist/command"; | ||
import "../../src/styles/globals.css"; | ||
|
||
// Alternatively you can use CommonJS syntax: | ||
// require('./commands') | ||
|
||
import { mount } from "cypress/react18"; | ||
|
||
// Augment the Cypress namespace to include type definitions for | ||
// your custom command. | ||
// Alternatively, can be defined in cypress/support/component.d.ts | ||
// with a <reference path="./component" /> at the top of your spec. | ||
declare global { | ||
namespace Cypress { | ||
interface Chainable { | ||
mount: typeof mount; | ||
} | ||
} | ||
} | ||
|
||
Cypress.Commands.add("mount", mount); | ||
|
||
// Example use: | ||
// cy.mount(<MyComponent />) | ||
|
||
compareSnapshotCommand(); | ||
|
||
export {}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
// *********************************************************** | ||
// This example support/e2e.ts is processed and | ||
// loaded automatically before your test files. | ||
// | ||
// This is a great place to put global configuration and | ||
// behavior that modifies Cypress. | ||
// | ||
// You can change the location of this file or turn off | ||
// automatically serving support files with the | ||
// 'supportFile' configuration option. | ||
// | ||
// You can read more here: | ||
// https://on.cypress.io/configuration | ||
// *********************************************************** | ||
|
||
// Import commands.js using ES2015 syntax: | ||
import "./commands"; | ||
import compareSnapshotCommand from "cypress-image-diff-js/dist/command"; | ||
compareSnapshotCommand(); | ||
|
||
// Alternatively you can use CommonJS syntax: | ||
// require('./commands') | ||
|
||
export {}; |
Oops, something went wrong.