Skip to main content
aaronwilliams.me logo

Cypress Querying Commands

What are they?

A Cypress query command searches the DOM for elements and will keep retrying within a configured timeout until the element exists.

These are commands like .get(), .find(), and .contains(), which can be chained, yielding the subject to the next query command in the chain. If any query in the chain fails to find the element within the timeout, then the whole chain is restarted from the beginning.

For example, if two list items do not exist, Cypress will re-run the entire chain until two exist or the timeout is reached:

cy.get(".list")
  .find("li")
  .eq(2);

Notice how the yielded subject is passed down the chain. This is the nature of how Cypress commands work. You can't return an element and use it later — it must be chained.

In this post, I want to split querying commands into two buckets:

🪣 Querying commands: like cy.get() and cy.contains() query the full DOM without receiving a previous subject.

* Contains() is complicated and has its own section!

🪣 Traversal commands: like .children(), .closest(), .parents(), and filter() require a previous subject to find in the DOM and traverse from.

Example of query and traversal commands chained together:

cy.get(".user-card")
  .contains("h3", "Aaron Williams")
  .parents(".user-card")
  .find("button");

The example above shows .get() querying the full DOM and yielding the element with the class name “user-card”.

The .contains() command looks for an h3 tag within this element that has a substring value of “Aaron Williams”.

.parents() traverses up the DOM from the yielded value. It has a selector passed, so it will traverse up until this element is found.

Finally, .find() queries within the scope of the yielded subject.

Important Tip! .get() will always query the full DOM, even when chained. If you want to query within the subject of your chain, use .find() like in the above example.

Alternatively, you can use .within():

cy.get('.user-card')
  .contains('h3', "Aaron Williams")
  .parents('.user-card')
  .within(() => {
    cy.get("button")
      .click();
  });

See more about .within() in the .then() and .within() section of this post.

.contains()

cy.contains() deserves its own section...

The command .contains() is a query, not an assertion, and it works in a unique way. Think of this query as saying “Get me the first element that contains this text”.

Example Usage:

<a>
  <span class="teal">Aaron Williams</span>
</a>
cy.contains("Aaron Williams")
  .click();

But it’s certainly not that simple... 😬

Chained vs Unchained

If you use cy.contains() unchained, it will query the full DOM. If it's used as part of a chain like cy.get("a").contains("Aaron Williams"), then it will query within the yielded subject.

Element Preference Order

If you use .contains() and your matching element is a descendant of something you’re likely to interact with next like buttons, links, or submit inputs then that is the element which will be yielded.

You can override this behaviour by supplying a selector as the first argument. For example:

cy.contains("a span", "Aaron Williams")
  .should("have.class", "admin");

Commands that DON’T Query

Action commands don’t query the DOM — they operate on the subject yielded from a previous query. So cy.click(".class") or cy.type("#id") will not work unless a subject is already yielded.

Similarly, assertions must be chained off a previously yielded subject to assert against.

Query and Traversal Commands

See below for a few examples of common usages.

Query

// Query by regex to match button text case-insensitive
cy.contains("button", /submit/i);
 
// Gets the page <title> tag content
cy.title();
 
// Queries the DOM for elements with this data attribute
cy.get('[data-testid="nav-link"]');

Traversal

// Start with multiple .user-card elements
cy.get(".user-card")
  // Narrow down to the one that contains this name
  .filter(':contains("Aaron Williams")')
  // Then get the first match
  .first();
 
// .eq(n) selects one element by index
cy.get(".todo-item").eq(2); // Selects the third item
 
// .not() excludes elements from the current subject
cy.get(".users")
  // Selects only notifications that do NOT have the .banned class
  .not(".banned");

.then() and .within()

You can get access to the element you are querying by either using .then() or .within().

.then() Command

Use .then() to "unwrap" the element and work with it outside of the Cypress chain — e.g., for using it in a plain JavaScript function or custom logic.

Below we get the text value of an element to pass into a helper JavaScript function, which you cannot do using normal Cypress chaining.

cy.get("#user-role")
  .invoke("text")
  .then((userRole) => {
    const role = decryptUserRole(userRole);
    expect(role).to.equal("Administrator");
  });

If not used with .invoke(), you will yield the “raw” jQuery collection Cypress works with.

The callback in .then() is only ran once, Cypress will not rerun the earlier queires but the queries inside the command are retried.

.within() Command

Use .within() if you want to run commands on children of a specific element. Below is an example where you have multiple cards and want to target a specific one:

cy.get("#a-spesific-card").within(() => {
  cy.get('[aria-label="Full Name"]')
    .type("Aaron Williams");
  cy.get('[aria-label="Email Address"]')
    .type("aaron@awilliams.me");
  cy.get('[aria-label="Submit form"]')
    .click();
});