Skip to content

Instantly share code, notes, and snippets.

@mbrochh
Last active February 8, 2024 13:02
Show Gist options
  • Save mbrochh/460f6d4fce959791c8f947cb30bed6a7 to your computer and use it in GitHub Desktop.
Save mbrochh/460f6d4fce959791c8f947cb30bed6a7 to your computer and use it in GitHub Desktop.
Controlling a Stripe payent popup with Cypress.io
// for this to work you need to set `"chromeWebSecurity": false` in cypress.json
describe('Make Stripe Payment', function() {
before(function() {
cy.visit('http://localhost:3000/en/stripe/checkout/')
Cypress.Cookies.preserveOnce('sessionid')
})
it('should enter credit card details and finalise payment', function() {
cy.get('[data-test="button-FormStripeCart-PayWithCreditCard"]').click()
// there are better ways to get the iFrame in a promise, I think, and there is
// a new API coming up soon to deal with iFrames, but I was lazy and just put a
// wait here - 6secs is usually enough to let the Stripe popup load fully
cy.wait(6000)
cy.get('iframe').then($iframe => {
const doc = $iframe.contents()
let input = doc.find('input')[0]
// super weird stuff here, if you just input '4242424242424242', the value
// that you end up seing in the input element is jumbled up a little,
// probably because of the way how Stripe inserts spaces while you are
// typing. By luck I found out that this issue can get worked around if
// you just chain-call type()
cy
.wrap(input)
.type('4242')
.type('4242')
.type('4242')
.type('4242')
input = doc.find('input')[1]
cy
.wrap(input)
.clear()
.type('12')
.type('20')
input = doc.find('input')[2]
cy
.wrap(input)
.type('123')
.type('{enter}')
})
cy.url({ timeout: 20000 }).should('contain', '/en/profile/my-orders/')
Cypress.Cookies.preserveOnce('sessionid')
})
})
@pouchyRT10
Copy link

This was helpful! Thanks.

A few updates for future search results (and probably myself):

• Stripe must have updated their markup, so I had to use CSS attribute selectors to get it: iframe[name^="__privateStripeFrame"]
• With Cypress 3.1.5 I did not seem to need to use Cypress.Cookies.preserveOnce('sessionid') as an example above shows.
• With Chrome 72 I did not seem to need --disable-site-isolation-trials
• But I did need the "chromeWebSecurity": false in cypress.json
• I did need to split the main 424242424242424242 bit into four chained instances of .type('4242').

Here is a full test that seems to work pretty well:

it(`works when you submit name, email and card number`, () => {
    cy.get('iframe[name^="__privateStripeFrame"]').then($iframe => {
      const $body = $iframe.contents().find('body')
      cy.wrap($body)
        .find('input[name="cardnumber"]')
        .type('4242')
        .type('4242')
        .type('4242')
        .type('4242')

      cy.wrap($body)
        .find('input:eq(2)')
        .type('1222')
      cy.wrap($body)
        .find('input:eq(3)')
        .type('223')
      cy.wrap($body)
        .find('input:eq(4)')
        .type('424242')
    })

    cy.get('.dollar-amount').type('1')

    cy.get('form')
      .submit()
      .should('contain', 'payment submitted')

    cy.get('.container').should('contain', '1000 credits')
  })

this one worked for me. thank you everyone for sharing your work

@jeremygottfried
Copy link

Hi all,
This solution has been inconsistent for me.
Sometimes cypress times out looking for body inside the iframe.
In debugger, body is sometimes returned as null.
Is there a solution for this that works consistently?

@dobromir-hristov
Copy link

Yeah I get that from time to time too, but I just re-run, its not that often :D You can await an extra few seconds probably.

@mjhea0
Copy link

mjhea0 commented Nov 14, 2019

Which version of Cypress are you using @jeremygottfried?

@jeremygottfried
Copy link

cypress: "^3.6.1"

@jeremygottfried
Copy link

Thanks @dobromir-hristov
It does work more consistently when I add a longer timeout before cy.get('iframe[name^="__privateStripeFrame5"]')
I wish there was a way to wait until the iframe body loads instead of an arbitrary timeout.

@mjhea0
Copy link

mjhea0 commented Nov 14, 2019

I've had success with using should instead of wait like in this example: https://medium.com/bratislava-angular/testing-an-app-inside-an-iframe-using-cypress-434a4d8b8bbe

@jeremygottfried
Copy link

Thank you @mjhea0 I will check that out

@a-toms
Copy link

a-toms commented Jan 14, 2020

cy.get('iframe[name^=__privateStripeFrame]')
      .then(($firstIFrame) => {
        cy.wrap($firstIFrame.contents().find('iframe#challengeFrame'))
          .then(($secondIFrame) => {
            // authorise
            const target = confirm ? '#test-source-authorize-3ds' : '#test-source-fail-3ds'
            cy.wrap($secondIFrame.contents().find(target)).click()
          })
      })

Thank you very much @dobromir-hristov! This solved the stripe 3D Secure issue for me.

@a-toms
Copy link

a-toms commented Jan 14, 2020

has anyone tried to test https://stripe.com/docs/payments/3d-secure#example-of-a-3d-secure-2-flow 3D secure payment test? when after adding card details new popup message comes for confirmation which is iframe inside an iframe?

See my comment above.

@henri-padam
Copy link

Thanks all :)

@Fifciu
Copy link

Fifciu commented Mar 6, 2020

For me to test it without 3ds, it was enough:

cy.get('iframe[name="__privateStripeFrame5"]').then(function($iframe) {
      const $body = $iframe.contents().find('body')
      cy
        .wrap($body)
        .find('input[autocomplete="cc-number"]')
        .type('4242424242424242', { force: true })
      cy
        .wrap($body)
        .find('input[autocomplete="cc-exp"]')
        .type('03/30', { force: true })
      cy
        .wrap($body)
        .find('input[autocomplete="cc-csc"]')
        .type('737', { force: true })
      cy
        .wrap($body)
        .find('input[autocomplete="postal-code"]')
        .type('12345', { force: true })
  })

However, for 3ds I was struggling for a while. I hadd to add this to support/commands.js:

Cypress.Commands.add('iframe', { prevSubject: 'element' }, $iframe => {
  return new Cypress.Promise(resolve => {
      $iframe.ready(function() {
        resolve($iframe.contents().find('body'));
      });
  });
});

Obviously, in plugins I also used:

module.exports = (on, config) => {
  // `on` is used to hook into various events Cypress emits
  // `config` is the resolved Cypress config
  on("before:browser:launch", (browser = {}, launchOptions) => {
    if (browser.name === "chrome") {
      launchOptions.args.push("--disable-site-isolation-trials");
    }
  })
}

And finally in the test (for 3ds part):

cy.get('iframe[name^="__privateStripeFrame"]:eq(0)', { timeout: 20000 })
      .iframe()
      .find('iframe#challengeFrame')
      .iframe()
      .find('#test-source-authorize-3ds')
      .click()

@estefafdez
Copy link

I wasn't able to pass it with any of those methods. Any help? thanks!

@gianmarxWebmapp
Copy link

i have this problem with payment cypress : https://stackoverflow.com/questions/64391115/cypress-automatic-tests-for-credit-card-payment can you help me please. I wasn't able to pass it with any of those methods. Any help? thanks!

@poojasinghdev
Copy link

There is another complication, we have our own receipt and invoice generated after the transaction is made, so if we stub the response, we cannot check our invoice and We do not visit, when you click on the button it goes to a redirect (it's not the old stripe popup) but the new stripe checkout is not API, but it's a redirect github.com/stripe-samples/checkout-one-time-payments

@dbalatero
Copy link

dbalatero commented Dec 26, 2020

I've created a more stable way to test Stripe Elements and released it as an npm package.

The API is fairly simple to use and abstracts away waiting for any <iframe>s. It also does not add time to your tests with cy.wait.

    cy.get('#card-element').within(() => {
      cy.fillElementsInput('cardNumber', '4242424242424242');
      cy.fillElementsInput('cardExpiry', '1025'); // MMYY
      cy.fillElementsInput('cardCvc', '123');
      cy.fillElementsInput('postalCode', '90210');
    });

@estefafdez
Copy link

Nice @dbalatero, thanks for sharing!

@nsmarino
Copy link

Thanks @dbalatero, exactly what i needed

@d1manson
Copy link

d1manson commented Apr 13, 2021

If anyone is interested in testing stripe 3dsecure flows server side, I wrote this gist a few months back. I thought it was worth sharing here since everyone here is in the market for help testing stripe!

@mike-mccormick
Copy link

I found another method for handling Stripe's SCA/3DS popup. Inspired both by the comments here and recently implementing puppeteer to handle an azure login (big props to this blog post ) I realised if you have the URL source of the frame, you can just browse to it.

I've created a gist here.

@mdmathewdc
Copy link

My Stripe form after clicking the 'Submit' button results in Cypress showing 'Whoops, There are no Tests to Run'.

Does anyone has any idea why?

@mannycolon
Copy link

Thanks @dbalatero, very helpful. It worked on my end.

@kaushal0212
Copy link

cy.get("#braint-hosted-number")
.its("0.contentDocument.body")
.then(cy.wrap)
.invoke("val", "4005519200000004")
.click()
.type("4005519200000004");

@dimasambo
Copy link

For the 3D secure I cleaned it up a bit and made it into a utility, so I can both confirm and deny it. Hope it helps someone Note the __privateStripeFrame does not have the number, it could fail on you randomly otherwise :)

function confirm3DSecureDialog (confirm = true) {
    cy.wait(5000)
    cy.get('iframe[name^=__privateStripeFrame]')
      .then(($firstIFrame) => {
        cy.wrap($firstIFrame.contents().find('iframe#challengeFrame'))
          .then(($secondIFrame) => {
            // authorise
            const target = confirm ? '#test-source-authorize-3ds' : '#test-source-fail-3ds'
            cy.wrap($secondIFrame.contents().find(target)).click()
          })
      })
  }

About testing 3DS, has anybody had an issue that after confirming 3ds modal your next code not running?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment