Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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')
})
})
@wichopy

This comment has been minimized.

Copy link

@wichopy wichopy commented Nov 13, 2018

Was really hoping this would work. When I try getting the iframe, i get undefined :(

@mjhea0

This comment has been minimized.

Copy link

@mjhea0 mjhea0 commented Nov 25, 2018

Try:

cy.get('iframe.stripe_checkout_app').then(function($iframe) {
    const $body = $iframe.contents().find('body')
    cy
      .wrap($body)
      .find('input:eq(0)')
      .type('4242424242424242')
    cy
      .wrap($body)
      .find('input:eq(1)')
      .type('1222')
    cy
      .wrap($body)
      .find('input:eq(2)')
      .type('123')
})

You will need to disable chromeWebSecurity:

// cypress.json

{
  "chromeWebSecurity": false
}

--disable-site-isolation-trials:

Check: https://docs.cypress.io/api/plugins/browser-launch-api.html# AND #1951

// /plugins/index.js

module.exports = (on, config) => {
  on("before:browser:launch", (browser = {}, args) => {
    if (browser.name === "chrome") {
      args.push("--disable-site-isolation-trials");
      return args;
    }
  });
};
@christopherfrance

This comment has been minimized.

Copy link

@christopherfrance christopherfrance commented Feb 22, 2019

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')
  })
@kianaditya

This comment has been minimized.

Copy link

@kianaditya kianaditya commented Mar 6, 2019

Thank you everyone for sharing! This was very helpful.

@sheelah

This comment has been minimized.

Copy link

@sheelah sheelah commented Mar 26, 2019

+1 to that! Thanks all 🙌

@richlewis14

This comment has been minimized.

Copy link

@richlewis14 richlewis14 commented Apr 30, 2019

Very useful, thank you for sharing. Was wondering though has anyone tried writing some tests that interact with the 3D secure pages? It seems as if the payment gateway is now housed in a modal ?

@techieshark

This comment has been minimized.

Copy link

@techieshark techieshark commented May 3, 2019

Very helpful, thanks. If anyone is using Elements instead of Checkout, something like this might be helpful. The code below is designed to test a form with separate credit card and expiry elements.

// Enter test card data into Stripe Elements:
    cy.get('.__PrivateStripeElement > iframe').then(($elements) => {
      // $elements result set is the iframes for credit, expiry.
      const stripeElementsInputSelector = '.InputElement';

      // type into the first element:
      const creditInput = $elements.eq(0).contents().find(stripeElementsInputSelector);
      cy.wrap(creditInput).type('4242424242424242');

      // type into the expiry element:
      const expirationInput = $elements.eq(1).contents().find(stripeElementsInputSelector);
      // Expire far in the future: Dec 2059 seems to work.
      cy.wrap(expirationInput).type('12/59');
    });
@heitorlessa

This comment has been minimized.

Copy link

@heitorlessa heitorlessa commented Jun 26, 2019

THANK YOU @techieshark - That was super helpful and clean. I'm new to Cypress and encountered this issue today, and just extended to add CVC element.

@d1manson

This comment has been minimized.

Copy link

@d1manson d1manson commented Jul 9, 2019

Thanks for all the help. Not sure if everyone else found the same, but it seemed to me that you do need the cy.wait, or you can use a cy.should like this...

 cy.get("iframe.stripe_checkout_app").should(
      $iframe =>
        expect($iframe.contents().find("input[placeholder='Card number'")).to
          .exist
    );

And then follow it up with the main code (esentially the same as @mjhea0 's above)....

    cy.get("iframe.stripe_checkout_app").then($iframe => {
      const $body = $iframe.contents().find("body");
      cy.wrap($body)
        .find("input[placeholder='Card number']")
        .type("4242424242424242");
      cy.wrap($body)
        .find("input[placeholder='MM / YY']")
        .type("1222");
      cy.wrap($body)
        .find("input[placeholder='CVC']")
        .type("123");
    });

I found I needed chromeWebSecurity:false in cypress.json, but not the change in plugins/index.js.
I'm on Cypress 3.3.2.

@kkumaresan2489

This comment has been minimized.

Copy link

@kkumaresan2489 kkumaresan2489 commented Jul 12, 2019

Thank you everyone 🙏

@eithed

This comment has been minimized.

Copy link

@eithed eithed commented Aug 7, 2019

If you're using separate element for each of the input fields (card number, cvc, expiry), then each one of those will be loaded in separate iframe. What worked for me is this:

// wait for the frames to load
cy.wait(1000)

/** PAYMENT **/
cy.get('.__PrivateStripeElement iframe').then(($iframe) => {
  cy.wrap($iframe.contents()[0].body)
    .find('input[name="cardnumber"]')
    .type('4242 4242 4242 4242')
  cy.wrap($iframe.contents()[1].body)
    .find('input[name=exp-date]')
    .type('1230')
  cy.wrap($iframe.contents()[2].body)
    .find('input[name=cvc]')
    .type('123')
})
@rickardomc

This comment has been minimized.

Copy link

@rickardomc rickardomc commented Aug 18, 2019

How can I create a local page with stripe form?

@nikhilesh009

This comment has been minimized.

Copy link

@nikhilesh009 nikhilesh009 commented Sep 12, 2019

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?

@tim-maguire

This comment has been minimized.

Copy link

@tim-maguire tim-maguire commented Sep 13, 2019

@nikhilesh009 we're doing something like:

  cy.get('.stripe-form__card-element iframe').then(($element) => {
    const $body = $element.contents();
    cy.wrap($body.find('input[name=cardnumber]')).type('4000002760003184');
    cy.wrap($body.find('input[name=exp-date]')).type('0423');
    cy.wrap($body.find('input[name=cvc]')).type('123');
  });
  cy.get('.stripe-form__card-element iframe').then(($element) => {
    const $body = $element.contents();
    cy.wrap($body.find('input[name=postal]')).type('12345');
  });

  cy.get('#stripeSubmitButton').click();

  // Have to give the inner iframe (challenge) enough time to load.
  cy.wait(6000);
  cy.get('iframe[name="__privateStripeFrame8"]').then(($iframe) => {
    const $body = $iframe.contents();
    cy.wrap($body.find('iframe#challengeFrame')).then(($el) => {
      const $challenge = $el.contents();
     // authorise
     cy.wrap($challenge.find('#test-source-authorize-3ds')).click();
    });
  });
@dobromir-hristov

This comment has been minimized.

Copy link

@dobromir-hristov dobromir-hristov commented Oct 9, 2019

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()
          })
      })
  }
@pouchyRT10

This comment has been minimized.

Copy link

@pouchyRT10 pouchyRT10 commented Oct 14, 2019

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

This comment has been minimized.

Copy link

@jeremygottfried jeremygottfried commented Nov 14, 2019

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

This comment has been minimized.

Copy link

@dobromir-hristov dobromir-hristov commented Nov 14, 2019

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

This comment has been minimized.

Copy link

@mjhea0 mjhea0 commented Nov 14, 2019

Which version of Cypress are you using @jeremygottfried?

@jeremygottfried

This comment has been minimized.

Copy link

@jeremygottfried jeremygottfried commented Nov 14, 2019

cypress: "^3.6.1"

@jeremygottfried

This comment has been minimized.

Copy link

@jeremygottfried jeremygottfried commented Nov 14, 2019

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

This comment has been minimized.

Copy link

@mjhea0 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

This comment has been minimized.

Copy link

@jeremygottfried jeremygottfried commented Nov 15, 2019

Thank you @mjhea0 I will check that out

@a-toms

This comment has been minimized.

Copy link

@a-toms 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

This comment has been minimized.

Copy link

@a-toms 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

This comment has been minimized.

Copy link

@henri-padam henri-padam commented Mar 2, 2020

Thanks all :)

@Fifciu

This comment has been minimized.

Copy link

@Fifciu 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

This comment has been minimized.

Copy link

@estefafdez estefafdez commented Jun 16, 2020

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

@gianmarxWebmapp

This comment has been minimized.

Copy link

@gianmarxWebmapp gianmarxWebmapp commented Oct 16, 2020

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

This comment has been minimized.

Copy link

@poojasinghdev poojasinghdev commented Nov 2, 2020

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

This comment has been minimized.

Copy link

@dbalatero 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

This comment has been minimized.

Copy link

@estefafdez estefafdez commented Dec 28, 2020

Nice @dbalatero, thanks for sharing!

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