Skip to content

Instantly share code, notes, and snippets.

@mike-mccormick
Last active July 13, 2021 07:44
Show Gist options
  • Save mike-mccormick/9390e9044612dbb4d3fd1868dd067048 to your computer and use it in GitHub Desktop.
Save mike-mccormick/9390e9044612dbb4d3fd1868dd067048 to your computer and use it in GitHub Desktop.
Handle Stripe 3DS/SCA in Cypress, using Puppetter
/*
Automating Stripe card testing with cypress is pretty easy (especially with 'cypress-plugin-stripe-elements')
but SCA/3DS testing really isn't.
When using a SCA/3DS test card, to simulate the approve/denial Stripe fires what appears to be a modal, however
it's actually an iFrame, within an iFrame within an iFrame that shows in a lightbox.
The innermost iFrame is very difficult to generate with because of how it's rendered (even when you've managed
to find and scope into each one), however the source URL can be browsed to.
This approach uses Cypress to burrow down, grab the URL, then have puppeteer browse to it, and approve or fail the transaction.
*/
//commands/stripe.js
Cypress.Commands.add("processStripeSCA", (action) => {
//Find the first frame - Named differently each load ( __privateStripeFrameXXXX )
cy.get("iframe[name*='__privateStripeFrame']")
.within(($element) => {
//Get the body from the first frame
const $body = $element.contents().find("body");
let topLevel = cy.wrap($body)
//Find the second frame
topLevel.find("iframe[name*='__stripeJSChallengeFrame']")
.within(($secondElement) => {
//Get the body from the second frame
const $secondBody = $secondElement.contents().find("body");
let secondLevel = cy.wrap($secondBody)
//Find the third frame - acsFrame
secondLevel.find("iframe[name*='acsFrame']")
//Scope into the actual modal
.within(($thirdElement) => {
//Grab the URL of the stripe popup, then have puppeteer browse to it!
cy.task('processSCA', {url: $thirdElement[0]["baseURI"], action: action});
})
})
})
})
// support/sca-helper.js
const puppeteer = require('puppeteer')
exports.processSCA = async function processSCA({url, action}) {
const creds = await puppeteer
.launch({headless: false})
.then(async browser => {
try{
let page = await browser.newPage();
debugger
//Navigate to Login Page
await page.goto(url);
//lazy wait to handle Stripe loading
await page.waitFor(5000)
let element;
if(action === 'pass')
{
element = '#test-source-authorize-3ds'
}
else
{
element = '#test-source-fail-3ds'
}
await page.click(element)
await page.waitForSelector('.FallbackMessageTitle')
await browser.close()
await browser.close()
console.log(localStorageData)
return true
} catch (error) {
console.log(error)
}
})
return true
}
@mike-mccormick
Copy link
Author

mike-mccormick commented Jul 12, 2021

Tip: If you do use this, you'll also need to burrow down into the iFrame overlay and click 'Close' after you've authenticated. For me, I use this:

    cy.get("iframe[name*='__privateStripeFrame']")
        .iframe('body .LightboxModalClose').click({force: true})

(.iframe comes from cypress-iframe)

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