bahr.dev software development on AWS

Testing Stripe Elements with Cypress

In this article we will look into how we can test a website that uses iframes like Stripe Elements.

We recently started using Cypress as our E2E testing framework and love it for its simplicity and ease of use. If you haven’t used it before give it a try!

While Cypress is elegant to work with on your own code, it might get tricky when you use third party components in your webapp. In our case the users can pay with credit card. To collect the payment details we use Stripe Elements which loads an iframe into our website. Cypress however doesn’t support iframes yet. Luckily mightyiam found a workaround to access iframes and their contents with Cypress.

Setup

We’re assuming that you set up a Cypress project and ran the example test suite. Your project structure should look like the picture below.

Project structure

We will start with extending the commands.js file.

Cypress.Commands.add(
    'iframeLoaded',
    {prevSubject: 'element'},
    ($iframe) => {
        const contentWindow = $iframe.prop('contentWindow');
        return new Promise(resolve => {
            if (
                contentWindow &&
                contentWindow.document.readyState === 'complete'
            ) {
                resolve(contentWindow)
            } else {
                $iframe.on('load', () => {
                    resolve(contentWindow)
                })
            }
        })
    });


Cypress.Commands.add(
    'getInDocument',
    {prevSubject: 'document'},
    (document, selector) => Cypress.$(selector, document)
);

Cypress.Commands.add(
    'getWithinIframe',
    (targetElement) => cy.get('iframe').iframeLoaded().its('document').getInDocument(targetElement)
);

Append this code to your commands.js file. Now you can use cy.getWithinIframe('a selector') to target any element within the iframe.

cy.get('iframe')
  .iframeLoaded()
  .its('document')
  .getInDocument('button')
  .trigger('click')

If you need greater flexibility you can use iframeLoaded and getInDocument as shown above. You can chain many Cypress commands after getInDocument.

Typing into Stripe Elements

Now we’re able to fill out credit card details. In your browser’s dev tools you can see the structure of the iframe being loaded by Stripe Elements.

Iframe structure of Stripe Elements

As you can see in the highlighted area, there’s an input that we can select with [name="cardnumber"]. With this information we can tell Cypress to fill out the test credit card number 4242 4242 4242 4242 as well as the other required information. The other input parts are exp-date, cvc and postal.

/// <reference types="Cypress" />

context('Actions', () => {

    it('should fill out creditcard', () => {
        cy.getWithinIframe('[name="cardnumber"]').type('4242424242424242');
        cy.getWithinIframe('[name="exp-date"]').type('1232');
        cy.getWithinIframe('[name="cvc"]').type('987');
        cy.getWithinIframe('[name="postal"]').type('12345');

        // adjust this to use your own pay now button
        cy.get('[data-cy="pay-now"]').click();
    });
});

Integrate the code into your test suite and run it. You should see Cypress filling out the credit details.

Cypress fills out credit card details

That’s it! You can now test your complete order process without any hacks.

Troubleshooting

If you’re running Cypress with Chrome you might have to disable web security by adding "chromeWebSecurity": false to the cypress.json file.

Open issues

We haven’t tested how this behaves with multiple iframes.

Did you enjoy this article? Let me know on Twitter or via email to michael@bahr.dev and share it with your friends!