## TL;DR

Cypress commands do not pierce iframes by default. Get the iframe's body document first, then query inside it with `cy.wrap()`, and treat the iframe content as a separate scoped root for every command.

## Error

```text
CypressError: Timed out retrying after 4000ms: Expected to find element: `#pay-button`, but never found it.
(The button is inside a Stripe-style iframe.)
```

## Steps

1. Confirm the element is inside an iframe using the runner's DOM snapshot. Expected: you see an `[iframe]` ancestor.
2. Add the `cypress-iframe` plugin or write the helper: `cy.get('iframe').its('0.contentDocument.body').should('not.be.empty').then(cy.wrap)`. Expected: you hold the iframe body as a Cypress subject.
3. Query inside it: `cy.get('iframe').its('0.contentDocument.body').then(cy.wrap).find('#pay-button').click()`. Expected: the click targets the inner element.
4. For repeated use, make it a custom command `cy.iframe()` so tests read cleanly. Expected: one helper used across specs.
5. If the iframe is cross-origin, you also need `chromeWebSecurity: false` in config. Expected: Cypress can access the foreign document.

## When to use

- Payment forms, embedded editors, or widgets rendered in iframes.
- The selector works in devtools console on the inner document but not via `cy.get()`.

## When not to use

- Shadow DOM (different piercing mechanism).
- Same-page elements that are merely hidden (visibility skill).

## Tool compatibility

- Cypress 10 through 14; `cypress-iframe` plugin or a hand-rolled helper.

## Variant phrasings

### Cypress cannot find element inside iframe

The classic symptom; the fix is scoping commands to the iframe body.

### contentDocument is null in Cypress iframe helper

The iframe had not loaded yet; add a `.should()` retry on the body before wrapping.

## Why it happens

Each iframe has its own document. `cy.get()` queries the top document only, so inner elements are invisible to it.

## Edge cases

- `chromeWebSecurity: false` weakens a security boundary; scope it to the specs that need it.
- Iframes that lazy-load need a wait on their `load` event, not a fixed sleep.
- Nested iframes require piercing one level at a time.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_3X85-Dh79tCOerdYQvty_Q
