-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d8d32b1
commit 55956c6
Showing
14 changed files
with
1,136 additions
and
1,112 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
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,6 @@ | ||
{ | ||
"label": "Best Practices", | ||
"position": 60, | ||
"collapsible": true, | ||
"collapsed": true | ||
} |
61 changes: 61 additions & 0 deletions
61
docs/guides/references/best-practices/assigning-return-values.mdx
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,61 @@ | ||
--- | ||
title: Assigning Return Values | ||
sidebar_position: 10 | ||
--- | ||
|
||
:::danger | ||
|
||
 <Icon name="exclamation-triangle" color="red" /> **Anti-Pattern:** Trying | ||
to assign the return value of Commands with `const`, `let`, or `var`. | ||
|
||
::: | ||
|
||
:::tip | ||
|
||
 <Icon name="check-circle" color="green" /> **Best Practice:** Use | ||
[aliases and closures to access and store](/guides/core-concepts/variables-and-aliases) | ||
what Commands yield you. | ||
|
||
::: | ||
|
||
Many first time users look at Cypress code and think it runs synchronously. | ||
|
||
We see new users commonly write code that looks like this: | ||
|
||
```js | ||
// DONT DO THIS. IT DOES NOT WORK | ||
// THE WAY YOU THINK IT DOES. | ||
const a = cy.get('a') | ||
|
||
cy.visit('https://example.cypress.io') | ||
|
||
// nope, fails | ||
a.first().click() | ||
|
||
// Instead, do this. | ||
cy.get('a').as('links') | ||
cy.get('@links').first().click() | ||
``` | ||
|
||
:::info | ||
|
||
<strong>Did you know?</strong> | ||
|
||
You rarely have to ever use `const`, `let`, or `var` in Cypress. If you're using | ||
them, you will want to do some refactoring. | ||
|
||
::: | ||
|
||
If you are new to Cypress and wanting to better understand how Commands work - | ||
[please read our Introduction to Cypress guide](/guides/core-concepts/introduction-to-cypress#Chains-of-Commands). | ||
|
||
If you're familiar with Cypress commands already, but find yourself using | ||
`const`, `let`, or `var` then you're typically trying to do one of two things: | ||
|
||
- You're trying to **store and compare** values such as **text**, **classes**, | ||
**attributes**. | ||
- You're trying to share **values** between tests and hooks like `before` and | ||
`beforeEach`. | ||
|
||
For working with either of these patterns, please read our | ||
[Variables and Aliases guide](/guides/core-concepts/variables-and-aliases). |
205 changes: 205 additions & 0 deletions
205
docs/guides/references/best-practices/before-and-after-hooks.mdx
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,205 @@ | ||
--- | ||
title: Using after Or afterEach Hooks | ||
sidebar_label: Avoid after or afterEach Hooks | ||
sidebar_position: 20 | ||
--- | ||
|
||
:::danger | ||
|
||
 <Icon name="exclamation-triangle" color="red" /> **Anti-Pattern:** Using | ||
`after` or `afterEach` hooks to clean up state. | ||
|
||
::: | ||
|
||
:::tip | ||
|
||
 <Icon name="check-circle" color="green" /> **Best Practice:** Clean up | ||
state **before** tests run. | ||
|
||
::: | ||
|
||
We see many of our users adding code to an `after` or `afterEach` hook in order | ||
to clean up the state generated by the current test(s). | ||
|
||
We most often see test code that looks like this: | ||
|
||
```js | ||
describe('logged in user', () => { | ||
beforeEach(() => { | ||
cy.login() | ||
}) | ||
|
||
afterEach(() => { | ||
cy.logout() | ||
}) | ||
|
||
it('tests', ...) | ||
it('more', ...) | ||
it('things', ...) | ||
}) | ||
``` | ||
|
||
Let's look at why this is not really necessary. | ||
|
||
### Dangling state is your friend: | ||
|
||
One of the **best** parts of Cypress is its emphasis on debuggability. Unlike | ||
other testing tools - when your tests end - you are left with your working | ||
application at the exact point where your test finished. | ||
|
||
This is an **excellent** opportunity for you to **use** your application in the | ||
state the tests finished! This enables you to write **partial tests** that drive | ||
your application step by step, writing your test and application code at the | ||
same time. | ||
|
||
We have built Cypress to support this use case. In fact, Cypress **does not** | ||
clean up its own internal state when the test ends. We **want** you to have | ||
dangling state at the end of the test! Things like [stubs](/api/commands/stub), | ||
[spies](/api/commands/spy), even [intercepts](/api/commands/intercept) are | ||
**not** removed at the end of the test. This means your application will behave | ||
identically while it is running Cypress commands or when you manually work with | ||
it after a test ends. | ||
|
||
If you remove your application's state after each test, then you instantly lose | ||
the ability to use your application in this mode. Logging out at the end would | ||
always leave you with the same login page at the end of the test. In order to | ||
debug your application or write a partial test, you would always be left | ||
commenting out your custom `cy.logout()` command. | ||
|
||
### It's all downside with no upside: | ||
|
||
For the moment, let's assume that for some reason your application desperately | ||
**needs** that last bit of `after` or `afterEach` code to run. Let's assume that | ||
if that code is not run - all is lost. | ||
|
||
That is fine - but even if this is the case, it should not go in an `after` or | ||
`afterEach` hook. Why? So far we have been talking about logging out, but let's | ||
use a different example. Let's use the pattern of needing to reset your | ||
database. | ||
|
||
**The idea goes like this:** | ||
|
||
> After each test I want to ensure the database is reset back to 0 records so | ||
> when the next test runs, it is run with a clean state. | ||
**With that in mind you write something like this:** | ||
|
||
```js | ||
afterEach(() => { | ||
cy.resetDb() | ||
}) | ||
``` | ||
|
||
Here is the problem: **there is no guarantee that this code will run.** | ||
|
||
If, hypothetically, you have written this command because it **has** to run | ||
before the next test does, then the absolute **worst place** to put it is in an | ||
`after` or `afterEach` hook. | ||
|
||
Why? Because if you refresh Cypress in the middle of the test - you will have | ||
built up partial state in the database, and your custom `cy.resetDb()` function | ||
**will never get called**. | ||
|
||
If this state cleanup is **truly** required, then the next test will instantly | ||
fail. Why? Because resetting the state never happened when you refreshed | ||
Cypress. | ||
|
||
### State reset should go before each test: | ||
|
||
The simplest solution here is to move your reset code to **before** the test | ||
runs. | ||
|
||
Code put in a `before` or `beforeEach` hook will **always** run prior to the | ||
test - even if you refreshed Cypress in the middle of an existing one! | ||
|
||
This is also a great opportunity to use | ||
[root level hooks in mocha](https://github.com/mochajs/mochajs.github.io/blob/master/index.md#root-level-hooks). | ||
|
||
<SupportFileConfiguration /> | ||
|
||
**Hooks you add to the root will always run on all suites!** | ||
|
||
```js | ||
// cypress/support/e2e.js or cypress/support/component.js | ||
|
||
beforeEach(() => { | ||
// now this runs prior to every test | ||
// across all files no matter what | ||
cy.resetDb() | ||
}) | ||
``` | ||
|
||
### Is resetting the state necessary? | ||
|
||
One final question you should ask yourself is - is resetting the state even | ||
necessary? Remember, Cypress already automatically enforces | ||
[test isolation](/guides/core-concepts/writing-and-organizing-tests#Test-Isolation) | ||
by clearing state before each test. Make sure you are not trying to clean up | ||
state that is already cleaned up by Cypress automatically. | ||
|
||
If the state you are trying to clean lives on the server - by all means, clean | ||
that state. You will need to run these types of routines! But if the state is | ||
related to your application currently under test - you likely do not even need | ||
to clear it. | ||
|
||
The only times you **ever** need to clean up state, is if the operations that | ||
one test runs affects another test downstream. In only those cases do you need | ||
state cleanup. | ||
|
||
#### <Icon name="graduation-cap" /> Real World Example | ||
|
||
The <Icon name="github" inline="true" contentType="rwa" /> resets and re-seeds | ||
its database via a custom [Cypress task](/api/commands/task) called `db:seed` in | ||
a `beforeEach` hook. This allows each test to start from a clean slate and a | ||
deterministic state. For example: | ||
|
||
```ts | ||
// cypress/tests/ui/auth.cy.ts | ||
|
||
beforeEach(function () { | ||
cy.task('db:seed') | ||
// ... | ||
}) | ||
``` | ||
|
||
:::note | ||
|
||
_<Icon name="github" /> Source: | ||
[cypress/tests/ui/auth.cy.ts](https://github.com/cypress-io/cypress-realworld-app/blob/develop/cypress/tests/ui/auth.spec.ts)_ | ||
|
||
::: | ||
|
||
The `db:seed` task is defined within the | ||
[setupNodeEvents](/guides/tooling/plugins-guide#Using-a-plugin) function of the | ||
project, and in this case sends a request to a dedicated back end API of the app | ||
to appropriately re-seed the database. | ||
|
||
:::cypress-config-plugin-example | ||
|
||
```ts | ||
on('task', { | ||
async 'db:seed'() { | ||
// Send request to backend API to re-seed database with test data | ||
const { data } = await axios.post(`${testDataApiEndpoint}/seed`) | ||
return data | ||
}, | ||
//... | ||
}) | ||
``` | ||
|
||
::: | ||
|
||
:::note | ||
|
||
_<Icon name="github" /> Source: | ||
[cypress/plugins/index.ts](https://github.com/cypress-io/cypress-realworld-app/blob/develop/cypress/plugins/index.ts)_ | ||
|
||
::: | ||
|
||
The same practice above can be used for any type of database (PostgreSQL, | ||
MongoDB, etc.). In this example, a request is sent to a back end API, but you | ||
could also interact directly with your database with direct queries, custom | ||
libraries, etc. If you already have non-JavaScript methods of handling or | ||
interacting with your database, you can use [`cy.exec`](/api/commands/exec), | ||
instead of [`cy.task`](/api/commands/task), to execute any system command or | ||
script. |
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,26 @@ | ||
--- | ||
title: Best Practices | ||
--- | ||
|
||
:::info | ||
|
||
### <Icon name="graduation-cap" /> Real World Practices | ||
|
||
The Cypress team maintains the | ||
<Icon name="github" inline="true" contentType="rwa" />, a full stack | ||
example application that demonstrates **best practices and scalable strategies | ||
with Cypress in practical and realistic scenarios**. | ||
|
||
The RWA achieves full [code-coverage](/guides/tooling/code-coverage) with | ||
end-to-end tests | ||
[across multiple browsers](/guides/guides/cross-browser-testing) and | ||
[device sizes](/api/commands/viewport), but also includes | ||
[visual regression tests](/guides/tooling/visual-testing), API tests, unit | ||
tests, and runs them all in an | ||
[efficient CI pipeline](https://cloud.cypress.io/projects/7s5okt). | ||
|
||
The app is bundled with everything you need, | ||
[just clone the repository](https://github.com/cypress-io/cypress-realworld-app) | ||
and start testing. | ||
|
||
::: |
Oops, something went wrong.