QA Interview Hub · Knowledge base · Quizzes
Cypress
Locators and working with the DOM
finding elements and interacting
How does cy.get() differ from .find() and cy.contains()?
cy.get(selector) searches from the document root, even if it stands in a chain. The exception is inside .within().
.find(selector) searches only inside the previous subject.
cy.contains(text) searches for an element by text; you can specify a selector: cy.contains('button', 'Save'). In a chain it searches inside the subject.
cy.get('[data-cy=order]').find('[data-cy=price]'); // the price exactly within the order
cy.get('[data-cy=order]').get('[data-cy=price]'); // ⚠️ searches the whole page
cy.contains('button', 'Place order').click();
In an interview: cy.get always searches from the document root, even if it stands in a chain. Find searches only inside the previous element. And contains finds an element by text, and you can pass it a selector to narrow the search, for example a button with a particular text.
And in Playwright:page.locator() searches across the page, locator.locator() inside another locator, and text with getByText(). An important difference is strict mode: if a locator for an action finds several elements, Playwright fails with an error at once.
Which selectors does Cypress recommend and why?
The most stable ones are separate attributes for tests: data-cy, data-test, data-testid. They don't change when styles or markup are refactored.
Classes and ids for styling are brittle, and positional selectors like div:nth-child(3) are the worst. cy.contains is appropriate when the text is part of the requirement. For Playwright-style semantic locators there is the Testing Library plugin with cy.findByRole.
cy.get('[data-cy=submit]'); // ✅
cy.contains('button', 'Sign in'); // ✅ if the text is a requirement
cy.get('.btn.btn-primary.mt-2'); // ⚠️ depends on styles
cy.get('form > div:nth-child(3) > button'); // ❌ breaks with the markup
In an interview: The best are separate attributes for tests, for example data-cy: they aren't touched when styles change. Contains by text is for when the text itself is the requirement. I don't use classes for styling or positional selectors, because they break with any change of markup. If you need semantic locators, there is the Testing Library plugin with findByRole.
And in Playwright:Semantic locators are built in: getByRole, getByLabel, getByPlaceholder, getByTestId. The attribute for getByTestId is configured in the config through testIdAttribute, for example to data-cy.
How do you narrow the search: within, eq, first, filter?
.within() limits all nested commands to one block, which is convenient for a table row or a product card. .eq(n), .first(), .last() take an element by index. .filter() keeps the elements that match a selector or a text through :contains().
Use indexes only when the order is part of the logic. Otherwise bind to the content.
cy.contains('[data-cy=row]', 'Order #1042').within(() => {
cy.get('[data-cy=status]').should('have.text', 'Shipped');
cy.get('[data-cy=cancel]').click();
});
cy.get('[data-cy=product]').filter(':contains("Thermos")').first().click();
In an interview: Within limits the search to one block: I find the right table row by text, and all the commands inside search only in it. Eq, first and last pick by index, but I use them only when the order really matters. Otherwise I narrow by content with contains or filter.
And in Playwright:Instead of within there is locator composition, and instead of filter(':contains') there is filter({ hasText }).
const row = page.getByTestId('row').filter({ hasText: 'Order #1042' });
await expect(row.getByTestId('status')).toHaveText('Shipped');
await row.getByTestId('cancel').click();
Why is { force: true } dangerous?
The option force: true turns off the actionability checks: Cypress no longer checks whether the element is visible, covered by a modal or disabled. The event simply fires at the element.
If force "fixed" a test, the test now passes despite a real problem: a user couldn't click, and the test is green. Justified use is rare, for example a hidden native input under a custom checkbox, and there must be a comment next to it saying why.
// ⚠️ only with an explanation
// the native input is hidden, the custom checkbox covers it
cy.get('input[type=checkbox]').check({ force: true });
In an interview: Force turns off the checks: whether the element is visible, not covered, enabled. If a test started passing only with force, most likely it is now hiding a real bug: a user couldn't click. I use it only in rare cases, for example for a hidden native checkbox, and always with a comment.
And in Playwright:Playwright has the same { force: true }, and the risks are the same: it turns off the actionability checks.
How do you work with iframes, new tabs, and file downloads and uploads?
- Iframe. There is no dedicated command. You take
contentDocument.body and wrap it with cy.wrap, or use the cypress-iframe plugin. An iframe from another domain is not accessible because of the same-origin policy.
- New tabs. Cypress works in only one. You remove
target="_blank" and open the link in the same tab, or simply check the href attribute.
- Upload. The built-in command
.selectFile(), with drag-and-drop support.
- Download. You check the file in the
cypress/downloads folder with cy.readFile.
cy.get('iframe#payment')
.its('0.contentDocument.body').should('not.be.empty')
.then(cy.wrap)
.find('[data-cy=card]').type('4242424242424242');
cy.get('[data-cy=docs-link]').invoke('removeAttr', 'target').click();
cy.get('input[type=file]').selectFile('cypress/fixtures/avatar.png');
cy.readFile('cypress/downloads/report.csv').should('contain', 'Total');
In an interview: For an iframe I take the contentDocument body and wrap it in cy.wrap, or I use the cypress-iframe plugin, but an iframe from another domain is not accessible. Cypress doesn't support new tabs, so I remove target blank or check the href. For uploading files there is the built-in selectFile, and a downloaded file I verify with readFile in the downloads folder.
And in Playwright:Here Playwright is noticeably stronger. An iframe is page.frameLocator(), including from another domain. New tabs are context.waitForEvent('page'). Upload is setInputFiles(), download is page.waitForEvent('download').
await page.frameLocator('iframe#payment').getByTestId('card').fill('4242...');
const [tab] = await Promise.all([
page.context().waitForEvent('page'),
page.getByTestId('docs-link').click(),
]);
Selectors and data-cy
The best Cypress practice is separate test attributes data-cy, data-test or data-testid. They don't change together with styles or text and clearly show developers that the element is used in tests. The selector looks like this: cy.get('[data-cy=submit]').
You should avoid CSS classes, especially generated ones, tags and positional selectors like div > span:nth-child(3). They break with any change of markup. An ID is fine if it is stable and not generated dynamically.
cy.contains works well when the text is part of what is being tested, but it is sensitive to changes in copy and localisation. For accessibility and stability, Testing Library for Cypress is often added, with search by role and name.