I have been trying to test a stripe checkout form using cypress.io
If anyone has managed to get this to work please let me know. I found a thread on the matter here http
The '.Input .InputElement'
selector from @user8888 did not work for me. Instead, I'm accessing each input
by it's name
attribute.
cy.get(".__PrivateStripeElement > iframe").then(($element) => {
const $body = $element.contents().find("body");
let stripe = cy.wrap($body);
stripe
.find('[name="cardnumber"]')
.click()
.type(MOCK_CC_NUMBER);
stripe = cy.wrap($body);
stripe
.find('[name="exp-date"]')
.click()
.type(MOCK_CC_EXP);
stripe = cy.wrap($body);
stripe
.find('[name="cvc"]')
.click()
.type(MOCK_CC_CVC);
stripe = cy.wrap($body);
stripe
.find('[name="postal"]')
.click()
.type(MOCK_CC_ZIP);
});
The iframe workflow is still pretty clunky (until this feature is implemented). For now, you can try forcing pretty much every DOM interaction:
cy.visit("https://jsfiddle.net/1w9jpnxo/1/");
cy.get("iframe").then( $iframe => {
const $doc = $iframe.contents();
cy.wrap( $doc.find("#input") ).type( "test", { force: true });
cy.wrap( $doc.find("#submit") ).click({ force: true });
});
The solution in this link is working for me. Basically, the steps are as below:
This doesn't directly answer your question, but after several days of trying to wrangle manipulating elements in the iframe using jQuery, re-implementing a bunch of stuff that Cypress already did, I smacked myself and started doing this:
Cypress.Commands.add('openiframe', () => {
return cy.get("iframe[src^='/']").then(iframe => {
cy.visit(Cypress.$(iframe).attr('src'), { timeout: Cypress.config("pageLoadTimeout") });
});
});
That allowed me to just cy.openiframe().then(() => {}); and proceed as if the site I was testing didn't put a bunch of functionality in an iframe in the first place.
The downside is that you've got to finish up what you're doing not in the iframe before doing anything in the iframe, so you can't go back and forth too easily.
It might not work for your use case, but if/when it does, it's the easiest workaround I've found.
I released a plugin yesterday that adds a simple Cypress API for filling in Stripe Elements:
cy.fillElementsInput('cardNumber', '4242424242424242');
This plugin avoids cy.wait()
calls, peeking into <iframe>
s manually, and other awkward selectors.
I just spent way too long trying to get this working, none of the answer I found would work completely. I added my solution to the cypress github issue for iframes (there is a bit more context there), also putting it here to hopefully save others some time.
I stole the onIframeReady() function from this stackoverflow answer.
Basically what it is doing is checking if the iframe has loaded, if the iframe has loaded it will do $iframe.contents().find("body");
to switch to the contents. If it has not loaded it will hook that same code into the load
event so it will run as soon as the iframe loads.
This is written as a custom command to allow use of cypress chaining after switching to the iframe, so put the following into your support/commands.js
file:
Cypress.Commands.add("iframe", { prevSubject: "element" }, $iframe => {
Cypress.log({
name: "iframe",
consoleProps() {
return {
iframe: $iframe,
};
},
});
return new Cypress.Promise(resolve => {
onIframeReady(
$iframe,
() => {
resolve($iframe.contents().find("body"));
},
() => {
$iframe.on("load", () => {
resolve($iframe.contents().find("body"));
});
}
);
});
});
function onIframeReady($iframe, successFn, errorFn) {
try {
const iCon = $iframe.first()[0].contentWindow,
bl = "about:blank",
compl = "complete";
const callCallback = () => {
try {
const $con = $iframe.contents();
if ($con.length === 0) {
// https://git.io/vV8yU
throw new Error("iframe inaccessible");
}
successFn($con);
} catch (e) {
// accessing contents failed
errorFn();
}
};
const observeOnload = () => {
$iframe.on("load.jqueryMark", () => {
try {
const src = $iframe.attr("src").trim(),
href = iCon.location.href;
if (href !== bl || src === bl || src === "") {
$iframe.off("load.jqueryMark");
callCallback();
}
} catch (e) {
errorFn();
}
});
};
if (iCon.document.readyState === compl) {
const src = $iframe.attr("src").trim(),
href = iCon.location.href;
if (href === bl && src !== bl && src !== "") {
observeOnload();
} else {
callCallback();
}
} else {
observeOnload();
}
} catch (e) {
// accessing contentWindow failed
errorFn();
}
}
Then you would call this like so from your tests:
cy.get('iframe.stripe_checkout_app')
.iframe()
.find('input:eq(0)')
.type("4000056655665556")
You can .alias() after calling .iframe()
to refer to it for the rest of your inputs or .get()
the iframe several times, I'll leave that up to you to figure out.