Querying the DOM — cy.get, cy.contains, cy.find and Chaining

Everything in Cypress starts with a query — finding the element you want to interact with or verify. Cypress provides three primary query commands: cy.get() for CSS selectors, cy.contains() for text-based search, and .find() for scoped queries within a parent. Unlike Selenium, these queries automatically retry until the element appears or the timeout expires, and they chain together into expressive pipelines that read like sentences.

Core Query Commands — cy.get, cy.contains and Chaining

Each query command returns a Cypress chainable that yields the matched DOM element(s) to the next command in the chain.

// ── cy.get() — find by CSS selector (most common) ──

// By data attribute (recommended — most stable)
cy.get('[data-cy="login-btn"]');

// By ID
cy.get('#username');

// By class
cy.get('.product-card');

// By complex CSS selector
cy.get('form.checkout input[type="email"]');

// By attribute
cy.get('input[name="password"]');

// Multiple matches — returns all, assert on count
cy.get('.inventory_item').should('have.length', 6);


// ── cy.contains() — find by text content ──

// Find element containing exact text
cy.contains('Add to Cart');

// Scoped: find text within a specific element type
cy.contains('button', 'Submit Order');

// Find within a parent — combines get + contains
cy.get('.product-card').contains('Sauce Labs Backpack');

// Case-insensitive with regex
cy.contains(/add to cart/i);


// ── .find() — scoped query within a parent ──

// Find child elements within a specific container
cy.get('.product-card').first().find('.product-name');
cy.get('[data-cy="cart"]').find('.cart-item').should('have.length', 3);

// Chaining .find() narrows scope — does NOT search the entire DOM
cy.get('form.shipping')        // Find the shipping form
  .find('input[name="city"]')  // Find city input WITHIN that form
  .type('London');             // Type into it


// ── Chaining — Cypress's superpower ──

// Commands chain naturally — each command yields to the next
cy.get('[data-cy="search"]')     // Query: find the search input
  .clear()                        // Action: clear existing text
  .type('Selenium book')          // Action: type search query
  .should('have.value', 'Selenium book');  // Assert: verify value

// Chain multiple assertions with .and()
cy.get('[data-cy="error-msg"]')
  .should('be.visible')
  .and('contain.text', 'Invalid credentials')
  .and('have.class', 'error-alert');


// ── cy.get() vs .find() — critical difference ──

/*
  cy.get('.btn')
    ↑ Searches the ENTIRE document from the root
    ↑ Always starts a new query chain

  .find('.btn')
    ↑ Searches WITHIN the previously yielded element only
    ↑ Must be chained from a parent command

  Example: page has 20 buttons, form has 3 buttons
    cy.get('.btn')               → returns all 20 buttons
    cy.get('form').find('.btn')  → returns only 3 buttons inside the form
*/
Note: All Cypress query commands automatically retry until the element is found or the defaultCommandTimeout expires (default 4 seconds). This means cy.get('[data-cy="results"]') does not fail instantly if the element has not rendered yet — Cypress polls the DOM every few milliseconds, re-running the query until it succeeds. This built-in retry is what eliminates the need for explicit waits in most cases. You only need cy.wait() when waiting for something Cypress cannot auto-detect, like a specific network request to complete.
Tip: Use cy.contains('button', 'Submit') with the element type as the first argument to ensure you match the right element. cy.contains('Submit') alone might match a <span> or <label> that also contains the word “Submit.” Adding the tag name narrows the search to only <button> elements — more specific, less fragile, and faster.
Warning: cy.get() always queries from the document root, even if the previous command yielded a specific element. If you chain cy.get('.card').get('.title'), the second .get() searches the entire page for .title, not just within .card. To search within a parent, use .find(): cy.get('.card').find('.title'). This is one of the most common Cypress mistakes and causes tests to match the wrong element silently.

Common Mistakes

Mistake 1 — Using cy.get() when .find() is needed for scoped queries

❌ Wrong: cy.get('.card').get('.price') — the second .get() searches the entire DOM, not within .card.

✅ Correct: cy.get('.card').find('.price').find() searches only within the yielded .card element.

Mistake 2 — Using fragile class-based selectors instead of data attributes

❌ Wrong: cy.get('.mt-4.text-blue-500.font-bold') — Tailwind utility classes change constantly.

✅ Correct: cy.get('[data-cy="product-title"]') — purpose-built test attribute, immune to styling changes.

🧠 Test Yourself

A page has 15 buttons. You need to find the “Submit” button inside a form with class checkout-form. Which query is most reliable?