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.