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
*/
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.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.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.