Q23 of 48 · Cypress

How do you handle iframes in Cypress?

CypressMidcypressiframescross-originmid

Short answer

Short answer: Get the iframe element, drill into its document via `.its('0.contentDocument.body').then(cy.wrap)`, and continue chaining inside it. `cypress-iframe` plugin wraps this. Same-origin iframes work well; cross-origin needs `cy.origin` and is more constrained.

Detail

Cypress doesn't have a first-class iframe API like Selenium's switchTo().frame() or Playwright's frameLocator. The pattern uses jQuery to dig into the iframe's body:

cy.get('iframe[name=stripe-card]')
  .its('0.contentDocument.body')
  .should('not.be.empty')
  .then(cy.wrap)
  .find('input[name=cardnumber]')
  .type('4242 4242 4242 4242');

The should('not.be.empty') is a guard — iframes load asynchronously, and querying before the frame body exists yields an empty subject. The .then(cy.wrap) rewraps the body so subsequent commands chain naturally.

The cypress-iframe plugin wraps this pattern as cy.iframe(selector).find(...), which is more readable for repeated use.

Cross-origin iframes (a Stripe checkout, an embedded YouTube) are restricted because the parent and the iframe are different origins. cy.origin('https://js.stripe.com', () => { ... }) lets you switch context, but with caveats — limited subset of commands, no shared variables. Plan tests around stubbing the third-party at the network layer (cy.intercept) when feasible.

For payment-style iframes specifically, Stripe and similar services provide test card numbers and a sandbox; using them is more reliable than fighting iframe boundaries.

// EXAMPLE

iframe.cy.ts

// Same-origin iframe — manual pattern
it('types in an iframe input', () => {
  cy.visit('/embedded-form');
  cy.get('iframe[name=embed]')
    .its('0.contentDocument.body')
    .should('not.be.empty')
    .then(cy.wrap)
    .find('[data-test=name]')
    .type('Alice');
});

// Cross-origin (Stripe)
it('handles a cross-origin iframe with cy.origin', () => {
  cy.visit('/checkout');
  cy.get('iframe[title="Secure card payment input frame"]').then(($iframe) => {
    cy.origin('https://js.stripe.com', { args: { iframe: $iframe[0] } }, () => {
      // Limited commands available here
    });
  });
});

// WHAT INTERVIEWERS LOOK FOR

The `.its('0.contentDocument.body')` + `cy.wrap` pattern, the `should('not.be.empty')` guard, and `cy.origin` for cross-origin.

// COMMON PITFALL

Forgetting the `should('not.be.empty')` guard and getting an intermittent failure when the frame hasn't loaded yet.