Cypress 12.8.1 not working with Stripe Elements iframe

The short answer is, the Stripe iframes take time to load and display the fields you need to access, so you need to add a retry.

Usually you use .should() assertions to retry until something you want is present in the DOM.

Unfortunately, with an <iframe> inside the page, you can’t use .should() because it doesn’t retry all the steps in the chain back to contentDocument.

So you need roll-your-own retry with a recursive function.

Here’s a working example using a sample Stripe page:

cy.intercept({ resourceType: /xhr|fetch/ }, { log: false })  // suppress fetch logs 
cy.viewport(1500, 1000)
cy.visit('https://stripe-payments-demo.appspot.com');  

function getCardField(selector, attempts = 0) {
  Cypress.log({displayName: 'getCardField', message: `${selector}: ${attempts}`})
  
  if (attempts > 50) throw new Error('too many attempts')

  return cy.get('iframe', {timeout:10_000, log:false})
    .eq(1, {log:false})
    .its('0.contentDocument', {log:false}) 
    .find('body', {log:false})
    .then(body => {
      const cardField = body.find(selector)
      if (!cardField.length) {
        return cy.wait(300, {log:false})
          .then(() => {
            getCardField(selector, ++attempts)
          })
      } else {
        return cy.wrap(cardField)
      }
    })
}

getCardField('div.CardField')
  .type('4242424242424242')

getCardField('div.CardField')
  .find('input').eq(0)
  .should('have.value', '4242 4242 4242 4242')   // ✅ passes

A more general recursive function

function getStripeField({iframeSelector, fieldSelector}, attempts = 0) {
  Cypress.log({displayName: 'getCardField', message: `${fieldSelector}: ${attempts}`})

  if (attempts > 50) throw new Error('too many attempts')

  return cy.get(iframeSelector, {timeout:10_000, log:false})
    .eq(0, {log:false})
    .its('0.contentDocument', {log:false}) 
    .find('body', {log:false})
    .then(body => {
      const stripeField = body.find(fieldSelector)
      if (!stripeField.length) {
        return cy.wait(300, {log:false})
          .then(() => {
            getStripeField({iframeSelector, fieldSelector}, ++attempts)
          })
      } else {
        return cy.wrap(stripeField)
      }
    })
}
cy.visit('https://hivepass.app/temp-stripe-example.html')

getStripeField({
  iframeSelector: 'iframe[title="Secure card number input frame"]', 
  fieldSelector: 'div.CardNumberField-input-wrapper'
})
.type('4242424242424242')

getStripeField({
  iframeSelector: 'iframe[title="Secure card number input frame"]', 
  fieldSelector: 'div.CardNumberField-input-wrapper input'
})
.should('have.value', '4242 4242 4242 4242')

getStripeField({
  iframeSelector: '[title="Secure expiration date input frame"]', 
  fieldSelector: '[name="exp-date"]'
})
.type('0323')

getStripeField({
  iframeSelector: '[title="Secure expiration date input frame"]', 
  fieldSelector: '[name="exp-date"]'
})
.should('have.value', '03 / 23')

Note, it seems to be important to re-query the stripe field after updating, when confirming it’s new value.

Leave a Comment