Traversal and Advanced Queries — parent, siblings, each, filter and within

Real web pages have complex DOM structures — nested lists, tables with header and body sections, sibling elements that need to be correlated, and dynamic content that requires filtering. Cypress provides a rich set of traversal commands that let you navigate the DOM tree from any starting point: moving to parents, children, siblings, and filtering collections by index, content, or custom predicates.

DOM Traversal and Advanced Query Patterns

Traversal commands start from a previously yielded element and navigate to related elements in the DOM tree.

// ── Parent / Ancestor traversal ──

// Get the immediate parent
cy.get('.error-message').parent();

// Get a specific ancestor by selector
cy.get('.error-message').parents('.form-group');

// Get the closest matching ancestor (like jQuery.closest)
cy.get('.error-message').closest('form');


// ── Child traversal ──

// Get direct children only
cy.get('ul.menu').children();              // All direct 
  • children cy.get('ul.menu').children('.active'); // Only children with .active class // Get all descendants matching selector (same as .find) cy.get('form').find('input'); // ── Sibling traversal ── // Next sibling cy.get('.current-step').next(); // Immediately next sibling cy.get('.current-step').next('.step'); // Next sibling matching selector // Previous sibling cy.get('.current-step').prev(); // All siblings (excluding self) cy.get('.highlighted').siblings(); // Next ALL siblings cy.get('.current-step').nextAll(); // All siblings after this one // ── Filtering collections ── // By index cy.get('.product-card').first(); // First element cy.get('.product-card').last(); // Last element cy.get('.product-card').eq(2); // Third element (0-indexed) // By selector cy.get('.product-card').filter('.on-sale'); // Only cards with .on-sale class cy.get('.product-card').not('.out-of-stock'); // Exclude out-of-stock cards // By content cy.get('.product-card').contains('Backpack'); // Card containing "Backpack" // ── .within() — scope all commands to a container ── // All commands inside .within() are scoped to the yielded element cy.get('[data-cy="shipping-form"]').within(() => { // These .get() calls search ONLY within the shipping form cy.get('[name="address"]').type('123 Main St'); cy.get('[name="city"]').type('London'); cy.get('[name="postcode"]').type('SW1A 1AA'); cy.get('button[type="submit"]').click(); }); // After .within(), scope returns to the full document // ── .each() — iterate over a collection ── // Verify every product card has a non-empty name and a valid price cy.get('.product-card').each(($card) => { // $card is a jQuery element — use jQuery methods or wrap const name = $card.find('.product-name').text(); const priceText = $card.find('.price').text(); const price = parseFloat(priceText.replace('$', '')); expect(name).to.have.length.greaterThan(0); expect(price).to.be.greaterThan(0); }); // ── .invoke() — call jQuery methods on yielded elements ── // Get text content cy.get('.total').invoke('text').should('eq', '$59.97'); // Get CSS property cy.get('.alert').invoke('css', 'background-color').should('eq', 'rgb(255, 0, 0)'); // Get attribute cy.get('a.docs').invoke('attr', 'href').should('include', '/documentation'); // Trigger a jQuery method cy.get('.accordion').invoke('slideDown'); // ── .its() — access properties of the yielded subject ── // Get length of a collection cy.get('.items').its('length').should('be.greaterThan', 0); // Access nested properties cy.wait('@apiCall').its('response.body.data').should('have.length', 10); cy.window().its('localStorage.token').should('exist'); // ── Practical pattern: table row verification ── cy.get('table.orders tbody tr').each(($row, index) => { cy.wrap($row).within(() => { cy.get('td').eq(0).should('contain.text', `ORD-${1000 + index}`); cy.get('td').eq(1).should('not.be.empty'); cy.get('td').eq(3).invoke('text').then((price) => { expect(parseFloat(price.replace('$', ''))).to.be.greaterThan(0); }); }); });
  • Note: .within() is one of Cypress’s most powerful scoping commands. Inside a .within() callback, all cy.get() calls are scoped to the parent element — they do not search the entire document. This eliminates the “cy.get() always searches from root” problem without needing .find() on every query. Use .within() when you have multiple interactions inside a single container (a form, a card, a modal) to keep all queries scoped and readable.
    Tip: Combine .each() with cy.wrap($element).within() for powerful table and list validation. This pattern lets you iterate over rows, scope into each row, and verify individual cells — all with retry logic. It is the Cypress equivalent of a Selenium loop over find_elements but with built-in waiting and cleaner syntax.
    Warning: .eq() is 0-indexed (like arrays), not 1-indexed (like XPath). cy.get('li').eq(0) returns the first item, .eq(1) returns the second. This is consistent with JavaScript conventions but catches people who are used to CSS’s :nth-child(1) (which is 1-indexed) or XPath’s [1] (also 1-indexed). Off-by-one errors between these systems are common — always verify your index is correct.

    Common Mistakes

    Mistake 1 — Not using .within() for multi-field form interactions

    ❌ Wrong: cy.get('[name="address"]').type('...') — matches the FIRST address field on the page, which might be in the billing form instead of the shipping form.

    ✅ Correct: cy.get('[data-cy="shipping-form"]').within(() => { cy.get('[name="address"]').type('...'); }) — scoped to the correct form.

    Mistake 2 — Using .parent() chains instead of .closest()

    ❌ Wrong: cy.get('.error').parent().parent().parent() — fragile chain that breaks when nesting changes.

    ✅ Correct: cy.get('.error').closest('.form-group') — finds the nearest ancestor matching the selector, regardless of nesting depth.

    🧠 Test Yourself

    You need to fill out a shipping form that is one of three forms on the page. All forms have inputs with the same name attributes. Which approach ensures you type into the correct form’s fields?