Introduction to Cypress | Cypress Documentation (2024)

info

What you'll learn

  • How Cypress queries the DOM
  • How Cypress manages subjects and chains of commands
  • What assertions look like and how they work
  • How timeouts are applied to commands

tip

Important!

This is the single most important guide for understanding how to test withCypress. Read it. Understand it. Ask questions about it so that we can improveit.

After you're done, we suggest watching some of our Tutorial Videos.

Cypress Can Be Simple (Sometimes)

Simplicity is all about getting more done with less typing. Let's look at anexample:

  • End-to-End Test
  • Component Test
describe('Post Resource', () => {
it('Creating a New Post', () => {
cy.visit('/posts/new') // 1.

cy.get("input.post-title") // 2.
.type("My First Post"); // 3.

cy.get("input.post-body") // 4.
.type("Hello, world!"); // 5.

cy.contains("Submit") // 6.
.click(); // 7.

cy.get("h1") // 8.
.should("contain", "My First Post");
});
});

Can you read this? If you did, it might sound something like this:

note

  1. Visit page at /posts/new (or mount the PostBuilder component).
  2. Find the <input> with class post-title.
  3. Type "My First Post" into it.
  4. Find the <input> with class post-body.
  5. Type "Hello, world!" into it.
  6. Find the element containing the text Submit.
  7. Click it.
  8. Find the h1 tag, ensure it contains the text "My First Post".

This is a relatively straightforward test, but consider how much code has beencovered by it, both on the client and the server!

For the remainder of this guide, we'll explore the basics of Cypress that makethis example work. We'll demystify the rules Cypress follows so you canproductively test your application to act as much like a user as possible, aswell as discuss how to take shortcuts when it's useful.

Querying Elements

Cypress is Like jQuery

If you've used jQuery before, you may be used to queryingfor elements like this:

$('.my-selector')

In Cypress, querying elements is the same:

cy.get('.my-selector')

In fact, Cypressbundles jQueryand exposes many of its DOM traversal methods to you so you can work withcomplex HTML structures with ease using APIs you're already familiar with.

// Each Cypress query is equivalent to its jQuery counterpart.
cy.get('#main-content').find('.article').children('img[src^="/static"]').first()

tip

Core Concept

Cypress leverages jQuery's powerful selector engine to help make tests familiarand readable for modern web developers.

Interested in the best practices for selecting elements?Read here.

Accessing the DOM elements returned from the query works differently, however:

// This is fine, jQuery returns the element synchronously.
const $jqElement = $('.element')

// This will not work! Cypress does not return the element synchronously.
const $cyElement = cy.get('.element')

Let's look at why this is...

Cypress is Not Like jQuery

Question: What happens when jQuery can't find any matching DOM elements fromits selector?

Answer: Oops! It returns an empty jQuery collection. We've got a realobject to work with, but it doesn't contain the element we wanted. So we startadding conditional checks and retrying our queries manually.

// $() returns immediately with an empty collection.
const $myElement = $('.element').first()

// Leads to ugly conditional checks
// and worse - flaky tests!
if ($myElement.length) {
doSomething($myElement)
}

Question: What happens when Cypress can't find any matching DOM elementsfrom its selector?

Answer: No big deal! Cypress automatically retries the query until either:

1. The element is found

cy
// cy.get() looks for '#element', repeating the query until...
.get('#element')

// ...it finds the element!
// You can now work with it by using .then
.then(($myElement) => {
doSomething($myElement)
})

2. A set timeout is reached

cy
// cy.get() looks for '#element-does-not-exist', repeating the query until...
// ...it doesn't find the element before its timeout.
// Cypress halts and fails the test.
.get('#element-does-not-exist')

// ...this code is never run...
.then(($myElement) => {
doSomething($myElement)
})

This makes Cypress robust and immune to dozens of common problems that occur inother testing tools. Consider all the circ*mstances that could cause querying aDOM element to fail:

  • The DOM has not loaded yet.
  • Your framework hasn't finished bootstrapping.
  • An XHR request hasn't responded.
  • An animation hasn't completed.
  • and on and on...

Before, you'd be forced to write custom code to protect against any and all ofthese issues: a nasty mashup of arbitrary waits, conditional retries, and nullchecks littering your tests. Not in Cypress! With built-in retrying andcustomizable timeouts, Cypresssidesteps all of these flaky issues.

tip

Core Concept

Cypress wraps all DOM queries with robust retry-and-timeout logic that bettersuits how real web apps work. We trade a minor change in how we find DOMelements for a major stability upgrade to all of our tests. Banishing flake forgood!

info

In Cypress, when you want to interact with a DOM element directly, call.then() with a callback function that receives theelement as its first argument. When you want to skip the retry-and-timeoutfunctionality entirely and perform traditional synchronous work, useCypress.$.

Querying by Text Content

Another way to locate things -- a more human way -- is to look them up by theircontent, by what the user would see on the page. For this, there's the handycy.contains() command, for example:

// Find an element in the document containing the text 'New Post'
cy.contains('New Post')

// Find an element within '.main' containing the text 'New Post'
cy.get('.main').contains('New Post')

This is helpful when writing tests from the perspective of a user interactingwith your app. They only know that they want to click the button labeled"Submit". They have no idea that it has a type attribute of submit, or a CSSclass of my-submit-button.

caution

Internationalization

If your app is translated into multiple languages for i18n, make sure youconsider the implications of using user-facing text to find DOM elements!

When Elements Are Missing

As we showed above, Cypress anticipates the asynchronous nature of webapplications and doesn't fail immediately the first time an element is notfound. Instead, Cypress gives your app a window of time to finish whatever itmay be doing!

This is known as a timeout, and most commands can be customized with specifictimeout periods(the default timeout is 4 seconds).These Commands will list a timeout option in their API documentation,detailing how to set the number of milliseconds you want to continue to tryfinding the element.

// Give this element 10 seconds to appear
cy.get('.my-slow-selector', { timeout: 10000 })

You can also set the timeout globally via theconfiguration setting: defaultCommandTimeout.

tip

Core Concept

To match the behavior of web applications, Cypress is asynchronous and relies ontimeouts to know when to stop waiting on an app to get into the expected state.Timeouts can be configured globally, or on a per-command basis.

info

Timeouts and Performance

There is a performance tradeoff here: tests that have longer timeout periodstake longer to fail. Commands always proceed as soon as their expectedcriteria is met, so working tests will be performed as fast as your applicationallows. A test that fails due to timeout will consume the entire timeout period,by design. This means that while you may want to increase your timeout periodto suit specific parts of your app, you don't want to make it "extra long,just in case".

Later in this guide we'll go into much more detail aboutImplicit Assertions and Timeouts.

Chains of Commands

It's very important to understand the mechanism Cypress uses to chain commandstogether. It manages a Promise chain on your behalf, with each command yieldinga 'subject' to the next command, until the chain ends or an error isencountered. The developer should not need to use Promises directly, butunderstanding how they work is helpful!

Interacting With Elements

As we saw in the initial example, Cypress allows you to click on and type intoelements on the page by using .click() and.type() action commands with acy.get() or cy.contains()query command. This is a great example of chaining in action. Let's see itagain:

cy.get('textarea.post-body').type('This is an excellent post.')

We're chaining .type() ontocy.get(), telling it to type into the subject yieldedfrom the cy.get() query, which will be a DOM element.

Here are even more action commands Cypress provides to interact with your app:

  • .blur() - Make a focused DOM element blur.
  • .focus() - Focus on a DOM element.
  • .clear() - Clear the value of an input or textarea.
  • .check() - Check checkbox(es) or radio(s).
  • .uncheck() - Uncheck checkbox(es).
  • .select() - Select an <option> within a<select>.
  • .dblclick() - Double-click a DOM element.
  • .rightclick() - Right-click a DOM element.

These commands ensuresome guarantees about whatthe state of the elements should be prior to performing their actions.

For example, when writing a .click() command, Cypressensures that the element is able to be interacted with (like a real user would).It will automatically wait until the element reaches an "actionable" state by:

  • Not being hidden
  • Not being covered
  • Not being disabled
  • Not animating

This also helps prevent flake when interacting with your application in tests.You can usually override this behavior with a force option.

tip

Core Concept

Cypress provides a simple but powerful algorithm when interacting with elements.

Asserting About Elements

Assertions let you do things like ensuring an element is visible or has aparticular attribute, CSS class, or state. Assertions are commands that enableyou to describe the desired state of your application. Cypress willautomatically wait until your elements reach this state, or fail the test if theassertions don't pass. Here's a quick look at assertions in action:

cy.get(':checkbox').should('be.disabled')

cy.get('form').should('have.class', 'form-horizontal')

cy.get('input').should('not.have.value', 'US')

In each of these examples, it's important to note that Cypress willautomatically wait until these assertions pass. This prevents you from havingto know or care about the precise moment your elements eventually do reach thisstate.

We will learn more about assertions later in this guide.

Subject Management

A new Cypress chain always starts with cy.[command], where what is yielded bythe command establishes what other commands can be called next (chained).

All commands yield a value.

Each command specifies what value it yields. For example,

  • cy.clearCookies() yields null. You can chainoff commands that yield null, as long as the next command doesn't expect toreceive a subject.
  • cy.contains() yields a DOM element, allowingfurther commands to be chained (assuming they expect a DOM subject) like.click() or evency.contains() again.
  • .click() yields the same subject it was originallygiven.

Some commands require a previous subject.

  • .click() requires a DOM element from the previouscommand.
  • .its() requires a subject, but it can be of any type.
  • cy.contains() behaves differently depending on theprevious subject. If chained directly off of cy, or if the previous commandyielded null, it will look at the entire document. But if the subject is aDOM element, it will only look inside that container.
  • cy.clearCookies() does not require a previoussubject - it can be chained off of anything, even.end().

Examples:

This is actually much more intuitive than it sounds.

cy.clearCookies() // Yields null
.visit('/fixtures/dom.html') // Does not care about the previous subject.

cy.get('.main-container') // Yields an array of matching DOM elements
.contains('Headlines') // Yields the first DOM element containing content
.click() // Yields same DOM element from previous command.

tip

Core Concept

Cypress commands do not return their subjects, they yield them.Remember: Cypress commands are asynchronous and get queued for execution at alater time. During execution, subjects are yielded from one command to the next,and a lot of helpful Cypress code runs between each command to ensure everythingis in order.

info

Don't continue a chain after acting on the DOM

While it's possible in Cypress to act on the DOM and then continue chaining,this is usually unsafe, and can lead to stale elements. See theRetry-ability Guide for more details.

But the rule of thumb is simple: If you perform an action, like navigating thepage, clicking a button or scrolling the viewport, end the chain of commandsthere and start fresh from cy.

info

To work around the need to reference elements, Cypress has a featureknown as aliasing. Aliasing helpsyou to store and save references for future use.

Using .then() To Act On A Subject

Want to jump into the command flow and get your hands on the subject directly?No problem, add a .then() to your command chain. When theprevious command resolves, it will call your callback function with the yieldedsubject as the first argument.

If you wish to continue chaining commands after your.then(), you'll need to specify the subject you want toyield to those commands, which you can achieve with a return value other thannull or undefined. Cypress will yield that to the next command for you.

Let's look at an example:

cy
// Find the el with id 'some-link'
.get('#some-link')

.then(($myElement) => {
// ...massage the subject with some arbitrary code

// grab its href property
const href = $myElement.prop('href')

// strip out the 'hash' character and everything after it
return href.replace(/(#.*)/, '')
})
.then((href) => {
// href is now the new subject
// which we can work with now
})

tip

Core Concept

We have many more examples and use cases of cy.then() inour Core Concept Guide thatteaches you how to properly deal with asynchronous code, when to use variables,and what aliasing is.

Using Aliases to Refer to Previous Subjects

Cypress has some added functionality for quickly referring back to past subjectscalled Aliases. It lookssomething like this:

cy.get('.my-selector')
.as('myElement') // sets the alias
.click()

/* many more actions */

cy.get('@myElement') // re-queries the DOM as before
.click()

This lets us reuse our queries for more readable tests, and it automaticallyhandles re-querying the DOM for us as it updates. This is particularly helpfulwhen dealing with front end frameworks that do a lot of re-rendering!

Commands Are Asynchronous

It is very important to understand that Cypress commands don't do anything atthe moment they are invoked, but rather enqueue themselves to be run later. Thisis what we mean when we say Cypress commands are asynchronous.

Take this short test, for example:

  • End-to-End Test
  • Component Test
it('hides the thing when it is clicked', () => {
cy.visit('/my/resource/path') // Nothing happens yet

cy.get(".hides-when-clicked") // Still nothing happening
.should("be.visible") // Still absolutely nothing
.click() // Nope, nothing

cy.get('.hides-when-clicked') // Still nothing happening
.should('not.be.visible') // Definitely nothing happening yet
})

// Ok, the test function has finished executing...
// We've queued all of these commands and now
// Cypress will begin running them in order!

Cypress doesn't kick off the browser automation until the test function exits.

Mixing Async and Sync code

Remembering that Cypress commands run asynchronously is important if you areattempting to mix Cypress commands with synchronous code. Synchronous code willexecute immediately - not waiting for the Cypress commands above it to execute.

Incorrect usage

In the example below, the el evaluates immediately, before the cy.visit()has executed, so will always evaluate to an empty array.

it('does not work as we expect', () => {
cy.visit('/my/resource/path') // Nothing happens yet

cy.get('.awesome-selector') // Still nothing happening
.click() // Nope, nothing

// Cypress.$ is synchronous, so evaluates immediately
// there is no element to find yet because
// the cy.visit() was only queued to visit
// and did not actually visit the application
let el = Cypress.$('.new-el') // evaluates immediately as []

if (el.length) {
// evaluates immediately as 0
cy.get('.another-selector')
} else {
// this will always run
// because the 'el.length' is 0
// when the code executes
cy.get('.optional-selector')
}
})

// Ok, the test function has finished executing...
// We've queued all of these commands and now
// Cypress will begin running them in order!

Correct usage

Below is one way the code above could be rewritten in order to ensure thecommands run as expected.

it('does not work as we expect', () => {
cy.visit('/my/resource/path') // Nothing happens yet

cy.get('.awesome-selector') // Still nothing happening
.click() // Nope, nothing
.then(() => {
// placing this code inside the .then() ensures
// it runs after the cypress commands 'execute'
let el = Cypress.$('.new-el') // evaluates after .then()

if (el.length) {
cy.get('.another-selector')
} else {
cy.get('.optional-selector')
}
})
})

// Ok, the test function has finished executing...
// We've queued all of these commands and now
// Cypress will begin running them in order!

Incorrect usage

In the example below, the check on the username value gets evaluatedimmediately, before the cy.visit() has executed, so will always evaluate toundefined.

it('test', () => {
let username = undefined // evaluates immediately as undefined

cy.visit('https://example.cypress.io') // Nothing happens yet
cy.get('.user-name') // Still, nothing happens yet
.then(($el) => {
// Nothing happens yet
// this line evaluates after the .then executes
username = $el.text()
})

// this evaluates before the .then() above
// so the username is still undefined
if (username) {
// evaluates immediately as undefined
cy.contains(username).click()
} else {
// this will always run
// because username will always
// evaluate to undefined
cy.contains('My Profile').click()
}
})

// Ok, the test function has finished executing...
// We've queued all of these commands and now
// Cypress will begin running them in order!

Correct usage

Below is one way the code above could be rewritten in order to ensure thecommands run as expected.

it('test', () => {
let username = undefined // evaluates immediately as undefined

cy.visit('https://example.cypress.io') // Nothing happens yet
cy.get('.user-name') // Still, nothing happens yet
.then(($el) => {
// Nothing happens yet
// this line evaluates after the .then() executes
username = $el.text()

// evaluates after the .then() executes
// it's the correct value gotten from the $el.text()
if (username) {
cy.contains(username).click()
} else {
cy.get('My Profile').click()
}
})
})

// Ok, the test function has finished executing...
// We've queued all of these commands and now
// Cypress will begin running them in order!

tip

Core Concept

Each Cypress command (and chain of commands) returns immediately, having onlybeen appended to a queue to be executed at a later time.

You purposefully cannot do anything useful with the return value from acommand. Commands are enqueued and managed entirely behind the scenes.

We've designed our API this way because the DOM is a highly mutable object thatconstantly goes stale. For Cypress to prevent flake, and know when to proceed,we manage commands in a highly controlled deterministic way.

info

Why can't I use async / await?

If you're a modern JS programmer you might hear "asynchronous" and think: whycan't I just use async/await instead of learning some proprietary API?

Cypress's APIs are built very differently from what you're likely used to: butthese design patterns are incredibly intentional. We'll go into more detaillater in this guide.

Avoid loops

Using JavaScript loop commands like while can have unexpected effects. Let'ssay our application shows a random number on load.

Introduction to Cypress | Cypress Documentation (1)

We want the test to stop when it finds the number 7. If any other number isdisplayed the test reloads the page and checks again.

Note: you can find this application and the correct test in ourRecipes.

Incorrect test

The test written below WILL NOT work and most likely will crash your browser.

let found7 = false

while (!found7) {
// this schedules an infinite number
// of "cy.get..." commands, eventually crashing
// before any of them have a chance to run
// and set found7 to true
cy.get('#result')
.should('not.be.empty')
.invoke('text')
.then(parseInt)
.then((number) => {
if (number === 7) {
found7 = true
cy.log('lucky **7**')
} else {
cy.reload()
}
})
}

The above test keeps adding more cy.get('#result') commands to the test chainwithout executing any! The chain of commands keeps growing, but never executes -since the test function never finishes running. The while loop never allowsCypress to start executing even the very first cy.get(...) command.

Correct test

We need to give the test a chance to run a few commands before deciding if itneeds to continue. Thus the correct test would use recursion.

const checkAndReload = () => {
// get the element's text, convert into a number
cy.get('#result')
.should('not.be.empty')
.invoke('text')
.then(parseInt)
.then((number) => {
// if the expected number is found
// stop adding any more commands
if (number === 7) {
cy.log('lucky **7**')

return
}

// otherwise insert more Cypress commands
// by calling the function after reload
cy.wait(500, { log: false })
cy.reload()
checkAndReload()
})
}

cy.visit('public/index.html')
checkAndReload()

The test runs and correctly finishes.

Introduction to Cypress | Cypress Documentation (2)

You can see a short video going through this example at

https://www.youtube.com/watch?v=5Z8BaPNDfvA

Commands Run Serially

After a test function is finished running, Cypress goes to work executing thecommands that were enqueued using the cy.* command chains.

Let's take another look at an example

  • End-to-End Test
  • Component Test
it('hides the thing when it is clicked', () => {
cy.visit('/my/resource/path') // 1.

cy.get('.hides-when-clicked') // 2
.should('be.visible') // 3
.click() // 4

cy.get('.hides-when-clicked') // 5
.should('not.be.visible') // 6
});

The test above would cause an execution in this order:

  1. Visit the URL (or mount the component).
  2. Find an element by its selector.
  3. Assert that the element is visible.
  4. Perform a click action on that element.
  5. Find an element by its selector.
  6. Assert that the element is no longer visible.

These actions will always happen serially (one after the other), never inparallel (at the same time). Why?

To illustrate this, let's revisit that list of actions and expose some of thehidden ✨ magic ✨ Cypress does for us at each step:

  1. Visit the URL ✨ and wait for the page load event to fire after allexternal resources have loaded ✨ (or mount the component ✨ and wait forthe component to finish mounting ✨)
  2. Find an element by its selector ✨ and retry until it is found in the DOM
  3. Assert that the element is visible ✨ and retry until the assertionpasses
  4. Perform a click action on that element ✨ after we wait for the element toreach an actionable state
  5. Find an element by its selector ✨ and retry until it is found in the DOM
  6. Assert that the element is no longer visible ✨ and retry until theassertion passes

As you can see, Cypress does a lot of extra work to ensure the state of theapplication matches what our commands expect about it. Each command may resolvequickly (so fast you won't see them in a pending state) but others may takeseconds, or even dozens of seconds to resolve.

While most commands time out after a few seconds, other specialized commandsthat expect particular things to take much longer likecy.visit() will naturally wait longer before timingout.

These commands have their own particular timeout values which are documented inthe Cypress configuration.

tip

Core Concept

Any waiting or retrying that is necessary to ensure a step was successful mustcomplete before the next step begins. If they don't complete successfully beforethe timeout is reached, the test will fail.

The Cypress Command Queue

While the API may look similar to Promises, with its then() syntax, Cypresscommands and queries are not promises - they are serial commands passed into acentral queue, to be executed asynchronously at a later date. These commands aredesigned to deliver deterministic, repeatable and consistent tests.

Almost all commands come with built-inretry-ability. Withoutretry-ability, assertions wouldrandomly fail. This would lead to flaky, inconsistent results.

info

While Cypress does have a .then() command, Cypresscommands are not Promises and cannot be awaited. If you'd like to learn moreabout handling asynchronous Cypress Commands please read ourVariables and Aliases Guide.

Commands also have some design choices that developers who are used topromise-based testing may find unexpected. They are intentional decisions onCypress' part, not technical limitations.

  1. You cannot race or run multiple commands at the same time (in parallel).
  2. You cannot add a .catch error handler to a failed command.

The whole purpose of Cypress (and what makes it very different from othertesting tools) is to create consistent, non-flaky tests that perform identicallyfrom one run to the next. Making this happen isn't free - there are sometrade-offs we make that may initially seem unfamiliar to developers accustomedto working with Promises or other libraries.

Let's take a look at each trade-off in depth:

You cannot race or run multiple commands at the same time

Cypress guarantees that it will execute all of its commands and queriesdeterministically and identically every time they are run.

A lot of Cypress commands mutate the state of the browser in some way.

  • cy.request() automatically gets + sets cookies toand from the remote server.
  • cy.clearCookies() clears all of the browsercookies.
  • .click() causes your application to react to clickevents.

None of the above commands are idempotent; they all cause side effects. Racingcommands is not possible because commands must be run in a controlled, serialmanner in order to create consistency. Because integration and e2e testsprimarily mimic the actions of a real user, Cypress models its command executionmodel after a real user working step by step.

You cannot add a .catch error handler to a failed command

In Cypress there is no built in error recovery from a failed command. A commandeventually passes, or if it fails, all remaining commands are not executed,and the test as a whole fails.

You might be wondering:

How do I create conditional control flow, using if/else? So that if an elementdoes (or doesn't) exist, I choose what to do?

Cypress does not support this type of conditional control flow because it leadsto non-deterministic tests - different runs may behave differently, which makesthem less consistent and useful for verifying your application's correctness. Ingeneral, there are only a handful of very specific situations where you can orshould create control flow using Cypress commands.

With that said, as long as you are aware of the potential pitfalls with controlflow, it is possible to do this in Cypress! You can read all about how to doconditional testing here.

Assertions

As we mentioned previously in this guide:

note

Assertions describe the desired state of your elements, yourobjects, and your application.

What makes Cypress unique from other testing tools is that assertionsautomatically retry. Think of them as guards - assertions describe whatyour application should look like, and Cypress will automatically block, wait,and retry until it reaches that state.

Asserting in English

Let's look at how you'd describe an assertion in English:

note

After clicking on this <button>, I expect its class to be active.

To express this in Cypress you'd write:

cy.get('button').click()
cy.get('button').should('have.class', 'active')

This above test will pass even if the .active class is applied to the buttonasynchronously, after an indeterminate period of time or even if the button isremoved from the DOM entirely for a while (replaced with a waiting spinner, forexample).

// even though we are adding the class
// after two seconds...
// this test will still pass!
$('button').on('click', (e) => {
setTimeout(() => {
$(e.target).addClass('active')
}, 2000)
})

Here's another example.

note

After making an HTTP request to my server, I expect the response body to equal{name: 'Jane'}

To express this with an assertion you'd write:

cy.request('/users/1').its('body').should('deep.eq', { name: 'Jane' })

When To Assert?

Despite the dozens of assertions Cypress makes available to you, sometimes thebest test may make no assertions at all! How can this be? Aren't assertions abasic part of testing?

Consider this example:

  • End-to-End Test
  • Component Test
cy.visit('/home')

cy.get('.main-menu').contains('New Project').click()

cy.get('.title').type('My Awesome Project')

cy.get('form').submit()

Without a single explicit assertion, there are dozens of ways this test canfail. Here's a few:

  • The initial cy.mount() orcy.visit() could respond with something other thansuccess.
  • Any of the cy.get() queries could fail to find theirelements in the DOM.
  • The element we want to .click() on could be coveredby another element.
  • The input we want to .type() into could be disabled.
  • Form submission could result in a non-success status code.
  • The in-page JS (the application under test) or the component could throw anerror.

tip

Core Concept

With Cypress, you don't have to write explicit assertions to have a useful test.Without a single expect() or .should(), a few lines of Cypress can ensurethousands of lines of code are working properly across the client and server.

This is because many commands have built in Implicit Assertions which offeryou a high level of confidence that your application is working as expected.

Implicit Assertions

Many commands have default, built-in assertions, or rather have requirementsthat may cause it to fail without needing an explicit assertion you've added.

For instance:

  • cy.visit() expects the page to send text/htmlcontent with a 200 status code.
  • cy.request() expects the remote server to exist andprovide a response.
  • cy.contains() expects the element with content toeventually exist in the DOM.
  • cy.get() expects the element to eventually exist in theDOM.
  • .find() also expects the element to eventually existin the DOM.
  • .type() expects the element to eventually be in atypeable state.
  • .click() expects the element to eventually be in anactionable state.
  • .its() expects to eventually find a property on thecurrent subject.

Certain commands may have a specific requirement that causes them to immediatelyfail without retrying, such as cy.request().

Others, such as DOM queries automaticallyretry and wait for their correspondingelements to exist before failing.

Action commands automatically wait for their element to reach anactionable state beforefailing.

tip

Core Concept

All DOM commands automatically wait for their elements to exist in the DOM.

You never need to write .should('exist') afterquerying the DOM.

Most commands give you the flexibility to override or bypass the default waysthey can fail, typically by passing a {force: true} option.

Example #1: Existence and Actionability

cy
// there is an implicit assertion that this
// button must exist in the DOM before proceeding
.get('button')

// before issuing the click, this button must be "actionable"
// it cannot be disabled, covered, or hidden from view.
.click()

Cypress will automatically wait for elements to pass their implicitassertions. See Timeouts below for more on how timeouts aredetermined.

Example #2: Reversing the Implicit Assertion

Most of the time, when querying for elements, you expect them to eventuallyexist. But sometimes you wish to wait until they don't exist.

All you have to do is add that assertion and Cypress will skip implicitlywaiting for elements to exist.

cy.get('button.close').click()

// now Cypress will wait until this
// <button> is not in the DOM
cy.get('button.close').should('not.exist')

// and now make sure this #modal does not exist in the DOM
// and automatically wait until it's gone!
cy.get('#modal').should('not.exist')

tip

Core Concept

If you want to disable the default existence assertion, you can add.should('not.exist') to any DOM command.

Example #3: Other Implicit Assertions

Other commands have other implicit assertions not related to the DOM.

For instance, .its() requires that the property you'reasking about exists on the object.

// create an empty object
const obj = {}

// set the 'foo' property after 1 second
setTimeout(() => {
obj.foo = 'bar'
}, 1000)

// .its() will wait until the 'foo' property is on the object
cy.wrap(obj).its('foo')

List of Assertions

Cypress bundles Chai,Chai-jQuery, andSinon-Chai to providebuilt-in assertions. You can see a comprehensive list of them inthe list of assertions reference. You can alsowrite your own assertions as Chai plugins anduse them in Cypress.

Writing Assertions

There are two ways to write assertions in Cypress:

  1. As Cypress Commands: Using .should() or.and().
  2. As Mocha Assertions: Using expect.

Command Assertions

Using .should() or .and()commands is the preferred way of making assertions in Cypress. These are typicalCypress commands, which means they apply to the currently yielded subject in thecommand chain.

// The subject here is the first <tr>.
// This asserts that the <tr> has an .active class
cy.get('tbody tr:first').should('have.class', 'active')

You can chain multiple assertions together using .and(),which is another name for .should() that makes thingsmore readable:

cy.get('#header a')
.should('have.class', 'active')
.and('have.attr', 'href', '/users')

Because .should('have.class') does not change thesubject, .and('have.attr') is executed against the sameelement. This is handy when you need to assert multiple things against a singlesubject quickly.

Mocha Assertions

Using expect allows you to assert on any JavaScript object, not just thecurrent subject. This is probably how you're used to seeing assertions writtenin unit tests:

// the explicit subject here is the boolean: true
expect(true).to.be.true

info

Did you know you can write Unit Tests in Cypress?

Check out our example recipes for unit testing andunit testing React components.

Mocha assertions are great when you want to:

  • Perform custom logic prior to making the assertion.
  • Make multiple assertions against the same subject.

The .should() assertion allows us to pass a callbackfunction that takes the yielded subject as its first argument. This works like.then(), except Cypress automatically waits andretries for everything inside of the callback function to pass.

info

Complex Assertions

The example below is a use case where we are asserting across multiple elements.Using a .should() callback function is a great way toquery from a parent into multiple children elements and assert somethingabout their state.

Doing so enables you to block and guard Cypress by ensuring the state ofdescendants matches what you expect without needing to query them individuallywith regular Cypress DOM commands.

cy.get('p').should(($p) => {
// massage our subject from a DOM element
// into an array of texts from all of the p's
let texts = $p.map((i, el) => {
return Cypress.$(el).text()
})

// jQuery map returns jQuery object
// and .get() converts this to an array
texts = texts.get()

// array should have length of 3
expect(texts).to.have.length(3)

// with this specific content
expect(texts).to.deep.eq([
'Some text from first p',
'More text from second p',
'And even more text from third p',
])
})

danger

Make sure .should() is safe

When using a callback function with .should(), be surethat the entire function can be executed multiple times without side effects.Cypress applies its retry logic to thesefunctions: if there's a failure, it will repeatedly rerun the assertions untilthe timeout is reached. That means your code should be retry-safe. The technicalterm for this means your code must be idempotent.

Timeouts

Almost all commands can time out in some way.

All assertions, whether they're the default ones or whether they've been addedby you all share the same timeout values.

Applying Timeouts

You can modify a commands's timeout. This timeout affects both its defaultassertions (if any) and any specific assertions you've added.

Remember because assertions are used to describe a condition of the previouscommands - the timeout modification goes on the previous commands not theassertions.

Example #1: Implicit Assertion

// because .get() has an implicit assertion
// that this element exists, it can time out and fail
cy.get('.mobile-nav')

Under the hood Cypress:

  • Queries for the element .mobile-nav

    and waits up to 4 seconds for it to exist in the DOM

Example #2: Additional Assertions

// we've added 2 assertions to our test
cy.get('.mobile-nav').should('be.visible').and('contain', 'Home')

Under the hood Cypress:

  • Queries for the element .mobile-nav

    and waits up to 4 seconds for it to exist in the DOM✨ ✨and bevisible✨ ✨and contain the text: Home

The total amount of time Cypress will wait for all of the assertions to passis for the duration of the cy.get() timeout (which is 4seconds).

Timeouts can be modified per command and this will affect all implicitassertions and any assertions chained after that command.

Example #3: Modifying Timeouts

// we've modified the timeout which affects the implicit
// assertions as well as all explicit ones.
cy.get('.mobile-nav', { timeout: 10000 })
.should('be.visible')
.and('contain', 'Home')

Under the hood Cypress:

  • Gets the element .mobile-nav

    and waits up to 10 seconds for it to exist in the DOM✨ ✨and bevisible✨ ✨and contain the text: Home

Notice that this timeout has flowed down to all assertions and Cypress will nowwait up to 10 seconds total for all of them to pass.

danger

Note that you never change the timeout inside the assertion. The timeoutparameter always goes inside the command.

// 🚨 DOES NOT WORK
cy.get('.selector').should('be.visible', { timeout: 1000 })
// ✅ THE CORRECT WAY
cy.get('.selector', { timeout: 1000 }).should('be.visible')

Remember, you are retrying the command with attached assertions, not just theassertions!

Default Values

Cypress offers several different timeout values based on the type of command.

We've set their default timeout durations based on how long we expect certainactions to take.

For instance:

  • cy.visit() loads a remote page and does not resolveuntil all of the external resources complete their loading phase. This maytake awhile, so its default timeout is set to 60000ms.
  • cy.exec() runs a system command such as seeding adatabase. We expect this to potentially take a long time, and its defaulttimeout is set to 60000ms.
  • cy.wait() actually uses 2 different timeouts. Whenwaiting for arouting alias, we waitfor a matching request for 5000ms, and then additionally for the server'sresponse for 30000ms. We expect your application to make a matching requestquickly, but we expect the server's response to potentially take much longer.

That leaves most other commands including all DOM queries to time out by defaultafter 4000ms.

Introduction to Cypress | Cypress Documentation (2024)
Top Articles
Latest Posts
Article information

Author: Nathanael Baumbach

Last Updated:

Views: 6464

Rating: 4.4 / 5 (75 voted)

Reviews: 82% of readers found this page helpful

Author information

Name: Nathanael Baumbach

Birthday: 1998-12-02

Address: Apt. 829 751 Glover View, West Orlando, IN 22436

Phone: +901025288581

Job: Internal IT Coordinator

Hobby: Gunsmithing, Motor sports, Flying, Skiing, Hooping, Lego building, Ice skating

Introduction: My name is Nathanael Baumbach, I am a fantastic, nice, victorious, brave, healthy, cute, glorious person who loves writing and wants to share my knowledge and understanding with you.