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);
});
});
});
.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..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..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.