Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot target elements inside of an iframe #136

Open
brian-mann opened this Issue May 11, 2016 · 175 comments

Comments

@brian-mann
Copy link
Member

brian-mann commented May 11, 2016

Currently the driver does not support selecting or accessing elements from within an iframe. This isn't a technical limitation - its an oversight in the driver.

Users of Cypress need to access elements in an iframe and additionally access an API to "switch into" and switch back out of different iframes. Currently the driver thinks the element has been detached from the DOM (because its parent document is not the expected one).

Lastly Cypress must inject itself into iframes so things like XHR's work just like the main frame. This will ideally use something like Mutation Observers to be notified when new iframes are being pushed into the DOM.

  • Add API to navigate between frames
  • Update the Driver to take into account element document references to known frames
@Courey

This comment has been minimized.

Copy link

Courey commented May 31, 2016

commenting that I care because the docs said I should and we need this functionality for full test coverage.

@acusti

This comment has been minimized.

Copy link

acusti commented Jul 21, 2016

We also would need this functionality to be able to really use Cypress. Our app relies on rendering into an iframe for a significant part of it‘s functionality (it’s an editor, and the iframe is used to sandbox the thing being edited), and being able to target elements in that iframe is pretty essential to our testing.

@brian-mann

This comment has been minimized.

Copy link
Member Author

brian-mann commented Jul 21, 2016

Is the iframe same domain or cross domain?

We can pretty easily add same domain iframe support. Cross domain will take a lot more work. The difference will likely be days of work vs 1-2 weeks.

@acusti

This comment has been minimized.

Copy link

acusti commented Jul 22, 2016

Same domain. The iframe is really just a sandboxed canvas that we render into, rather than a 3rd party resource that we are loading into the app. (And thanks for the quick reply!)

@maximilianschmid

This comment has been minimized.

Copy link

maximilianschmid commented Sep 21, 2016

same domain iframe would allow us to test emails via e.g. mailinator.com

@brian-mann

This comment has been minimized.

Copy link
Member Author

brian-mann commented Sep 21, 2016

Cypress actually injects to forcibly enable it to access same domain <iframes> and even sub-domain <iframes> but there is an artificial limitation in the driver code where it get's confused when elements are returned and they're not bound to the top frame of your application.

This is coming up on our radar and it will introduce a few more API's to enable switching in and out of <iframes>.

As a workaround today you can actually still target these elements and potentially perform actions on them by you cannot return them or use them in cypress commands.

So this actually works:

cy.get("iframe").then(function($iframe){
  // query into the iframe
  var b = $iframe.contents().find("body")

  // you can work with this element here but it cannot
  // be returned

  var evt = new Event(...)
  b.dispatchEvent(evt)

  return null
})
@harz87

This comment has been minimized.

Copy link

harz87 commented Oct 14, 2016

I would need that feature too. To really test all use cases of our credit card from which is implemented by using braintree hosted fields api. They inject iframes into div and do the complete validation. However to test that our visuals and the submission to the server works I would need to be able to access those iframes.

@brian-mann

This comment has been minimized.

Copy link
Member Author

brian-mann commented Oct 14, 2016

@harz87 the braintree iframes you're referring to are cross origin frames right?

If that's the case you'll need to disable web security before being able to access them. After you do that you can at least manually interact with them and force their values to be set - although you won't be able to use Cypress API's until we add support for switching to and from iframes.

@rbone

This comment has been minimized.

Copy link

rbone commented Feb 6, 2017

This is something that'd be pretty important for my company. We've currently got a working ruby+selenium testing setup, however not all of our company is able to take advantage of those tools as we have some PHP and Go codebases, each of which is rapidly accumulating more and more JS heavy interfaces we'd like to be able to test. We're looking at cypress as a possible candidate to standardise on, but iframe support is currently a blocker.

The specific test case we're evaluating is our payments flow, which loads a modal containing an iframe on the same domain with all of the controls for inputting payment details and submitting requests to pay to the server.

Here's a screenshot to give you a better idea:

screen shot 2017-02-06 at 2 41 03 pm

All of the content you can see is actually embedded in an iframe. Our tests go through the various payment methods we offer, filling in details like credit card information, billing address, etc. before clicking pay and then asserting that an appropriate message is displayed such as "Payment successful" or an appropriate error message. Unfortunately we can't just test the content within the iframe directly as the payment modal is designed to be embedded into a page, and it communicates necessary information between the page containing the iframe and the page within the iframe.

We can use the workaround posted above, however as we're aiming to replace an already functional test system it makes it harder to justify using the workaround when it greatly reduces the benefits of using cypress. Let me know if there's anymore information you need.

@oliver3

This comment has been minimized.

Copy link

oliver3 commented Feb 14, 2017

We would like to have this feature in cypress, because we are using CKEditor for wysiwyg input in our application, and it uses an iframe.

@afohlmeister

This comment has been minimized.

Copy link

afohlmeister commented Feb 17, 2017

+1 We are also integrating external (cross-domain) payment methods and would like to test.

@mvandebunt

This comment has been minimized.

Copy link

mvandebunt commented Apr 6, 2017

+1 We are also integrating external (cross-domain) payment methods

@Alex0007

This comment has been minimized.

Copy link

Alex0007 commented Jun 16, 2017

People writing about payment methods, so do i. I'm trying to pass Stripe checkout iframe

cypress.json
{
    "chromeWebSecurity": false
}
.get('iframe.stripe_checkout_app').should('exist')
.then(function ($iframe) {
    const iframe = $iframe.contents()
    const cardNumInput = iframe.find('input:eq(0)')
    const cardValidInput = iframe.find('input:eq(1)')
    const cvcInput = iframe.find('input:eq(2)')

    cardNumInput.val('4242424242424242')
    cardValidInput.val('1222')
    cvcInput.val('123')

    setTimeout(() => {
        iframe.find('button').click()
    }, 1000)

    return null
})

image

But after click:
image

Manually filled form looks formatted
image

Anyone knows how to resolve this?

@brian-mann

This comment has been minimized.

Copy link
Member Author

brian-mann commented Jun 16, 2017

EDITED: to show using proper Cypress commands.

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

This comment has been minimized.

Copy link

caerie4 commented Jun 22, 2017

We use Iframe to insert user made forms into a webpage. We have to look at whats entered in the fields. The forms don't even show up in the cypress browser. They are just replaced with "<iframe> placeholder for". It hows up fine in a plain chrome browser though.

@caerie4

This comment has been minimized.

Copy link

caerie4 commented Jun 22, 2017

Are you still planning to add support for this later?

@brian-mann

This comment has been minimized.

Copy link
Member Author

brian-mann commented Jun 22, 2017

We don't show content in iframes when reverting to a snapshot (nor will we ever do that).

However we will eventually support targeting DOM elements inside of iframes.

@AshMcConnell

This comment has been minimized.

Copy link

AshMcConnell commented Aug 11, 2017

This would be great for us too, we are using CKeditor quite a bit (in an iFrame)

@bd82

This comment has been minimized.

Copy link

bd82 commented Aug 27, 2017

I'm also very interested in the ability to target elements in an iframe (same domain).
The group I'm part of are building a Web IDE and during testing the whole application "instance" runs in an iframe for isolation purposes.

This means that the root frame is only responsible for starting the inner frame application
and then issuing commands.

This works great for our unit and integration testing using karma.
I would very like to explore cypress as an alternative for our flaky E2E selenium tests.
But without iframe targeting 95% of our use case becomes irrelevant.

@brian-mann

This comment has been minimized.

Copy link
Member Author

brian-mann commented Sep 8, 2017

We are much closer to having this work. Here in 0.20.0 you are able to wrap <iframe> elements and at least use Cypress commands on them.

We'll still need to build in API's that enable you to switch the document context to the iframe, so you can use the querying methods of Cypress the same as you do on the outer document. Also things like verifying actionability still need work - notice I need to pass { force: true } to get the click to work.

Nevertheless its a big step forward because you can at least now fill out forms and interact with iframe elements, which prior to 0.20.0 did not work at all.

screen shot 2017-09-08 at 9 14 41 am

screen shot 2017-09-08 at 9 14 28 am

@paulfalgout

This comment has been minimized.

Copy link
Contributor

paulfalgout commented Nov 2, 2017

So this does work, but I find I have to add a wait(5000) prior to the then otherwise getting the iframe contents will be the about:blank while the frame is loading. since the iframe request is not XHR I suspect there's no way to route it with a wait alias? any other suggestions than an arbitrary wait time?

@brian-mann

This comment has been minimized.

Copy link
Member Author

brian-mann commented Nov 2, 2017

No, this is all part of the complexity of support iframes. Essentially all of the same safeguards we've added to the main frame have to be replicated on iframes.

We do a ton of work to ensure document and window are current - we listen for load events and unload events to pause and resume command execution - all of that has to be implemented for frames.

The additional complexity is that we can't add those listeners until you've told us you want to "switch" into those frames.

@brian-mann

This comment has been minimized.

Copy link
Member Author

brian-mann commented Nov 2, 2017

You'd likely need to write a custom command that takes this into account. It checks document and polls it until its ready

@paulfalgout

This comment has been minimized.

Copy link
Contributor

paulfalgout commented Nov 2, 2017

ok that was the direction I was headed 👍

@ioan-ghisoi

This comment has been minimized.

Copy link

ioan-ghisoi commented Nov 3, 2017

This is an option typing in an input field situated in the iframe:

cy.get(iframe_selector).then($iframe => {

    const iframe = $iframe.contents();

    const myInput = iframe.find("your input selector like #myElement");
    cy.wrap(myInput).type("example");

    //you don't need to trigger events like keyup or change

});
@paulfalgout

This comment has been minimized.

Copy link
Contributor

paulfalgout commented Nov 7, 2017

This is what I ended up doing. Seems to work rather well. You can alias the iframe() method, but if anything in the iframe loads another URL you have to do the original get().iframe() again.

Cypress.Commands.add('iframe', { prevSubject: 'element' }, $iframe => {
    return new Cypress.Promise(resolve => {
        $iframe.on('load', () => {
            resolve($iframe.contents().find('body'));
        });
    });
});
// for <iframe id="foo" src="bar.html"></iframe>
cy.get('#foo').iframe().find('.bar').should('contain', 'Success!');
@gbminnock

This comment has been minimized.

Copy link

gbminnock commented Feb 11, 2019

I care about this

@malarathinasamy

This comment has been minimized.

Copy link

malarathinasamy commented Feb 12, 2019

much needed feature for us to go ahead with cypress

@lucksp

This comment has been minimized.

Copy link

lucksp commented Feb 12, 2019

The way our company's product works is with iFrame's, on our client site. We need this feature. THANKS!

@mrchovo

This comment has been minimized.

Copy link

mrchovo commented Feb 13, 2019

Do anybody have any clue why @richban solution did not work? I have got a similar error while trying to workaround Braintree iframes. And as I check out this thread, most of solutions here are tailored to Stripe. Did somebody figure out solution also for Braintree? All answers appreciated! 😅

Also looking for official iframe support from Cypress.

@joshuakeel

This comment has been minimized.

Copy link

joshuakeel commented Feb 13, 2019

We really need this feature at my company as well. We embed many of our applications in iframes, and some of our off-the-shelf applications (which we'd like to smoke test) use iframes as well.

@Shackless

This comment has been minimized.

Copy link

Shackless commented Feb 14, 2019

This would open Cypress for React/Storybook testing and would be much appreciated.

@robertcristensen

This comment has been minimized.

Copy link

robertcristensen commented Feb 14, 2019

Hope it is going to be fixed. Thanks!

@amlivingamliving

This comment has been minimized.

Copy link

amlivingamliving commented Feb 14, 2019

We need iframe support as well - thanks!

@albert-olive

This comment has been minimized.

Copy link

albert-olive commented Feb 20, 2019

Has someone tried with <CardElement /> ?

Now I don't find the input:

CypressError: Timed out retrying: cy.click() failed because this element is not visible:

<span class="CardField-number-fakeNumber-last4 InputElement"></span>

This element '<span.CardField-number-fakeNumber-last4.InputElement>' is not visible because it has an effective width and height of: '0 x 0' pixels.

Fix this problem, or use {force: true} to disable error checking.
@10kresources

This comment has been minimized.

Copy link

10kresources commented Feb 21, 2019

Thanks @paulfalgout , your solution works just fine for me.

But the problem I am facing now is how do I switch back to parent.

My situation is like

We have a legacy angular app
We are converting to react 1 page at a time.
So the header and some other stuff remains common and we are migrating the page by loading react thingy in an iframe
Now after I interact with react page in iframe, I have to interact with header.

How can I do that?

@christopherfrance

This comment has been minimized.

Copy link

christopherfrance commented Feb 22, 2019

None of the solutions in this thread worked for me, but I found a good solution here: https://gist.github.com/mbrochh/460f6d4fce959791c8f947cb30bed6a7

The key for me was to set: "chromeWebSecurity": false in cypress.json.

Then I do:

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('form')
      .submit()
      .should('contain', 'payment submitted')

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

Note: I'm using react-stripe-elements for my Stripe implementation:

import { CardElement } from 'react-stripe-elements'
<CardElement />

@amrayoub

This comment has been minimized.

Copy link

amrayoub commented Feb 25, 2019

@christopherfrance can you please provide the html or web template for that test .
i'm trying to get cypress Iframe works in an angular app

@christopherfrance

This comment has been minimized.

Copy link

christopherfrance commented Feb 26, 2019

@amrayoub I updated my comment to indicate which implementation I'm using (react-stripe-elements). Hope that's helpful.

It's likely you'll need to update which selectors you use to target your form and inputs.

(Also my example had a custom input specific to my case, which I just removed.)

@pareshkrc

This comment has been minimized.

Copy link

pareshkrc commented Feb 27, 2019

+1
Very important feature any ui automation framework should have!

@lilaconlee

This comment has been minimized.

Copy link
Contributor

lilaconlee commented Mar 1, 2019

@mrchovo This worked for me on the latest major versions of Braintree Drop-in and Hosted Fields:

// Valid field keys: 'number', 'expirationDate', 'expirationMonth', 'expirationYear', 'cvv', 'postalCode'
it('fills out drop-in', () => {
  Cypress.Commands.add('btType', (field, text) => {
    cy.get(`#braintree-hosted-field-${field}`).then($iframe => {
      const body = $iframe.contents().find('body')
      cy.wrap(body)
        .find(`.${field}`)
        .type(text)
    })
  });

  cy.visit('braintree-drop-in.html')
  cy.wait(2000)
  cy.btType('number', 4111111111111111)
  cy.btType('expirationDate', '1/11')
})
@odemeniuk

This comment has been minimized.

Copy link

odemeniuk commented Mar 4, 2019

Solution to input text into richtext editor nested under ifame using cy.window

Cypress.Commands.add("iframe", (anytext) => { cy.window() .then(win => { var iframe = win.document.getElementById('iframe locator'); iframe.contentWindow.document.getElementsByTagName('body')[0].innerHTML = "anytext"; }) })

dbpiper added a commit to dbpiper/Euclid that referenced this issue Mar 7, 2019

feat: write integration test for header component
This is essentially a proof of concept of how to do integration tests
on components which are in Storybook. That is, just taking the e2e tests
for the Header component and running them on the Storybook version. This
*should* have been very simple, basically a copy-paste from the e2e
test, however it was not.

There is an issue with Cypress, namely that [it doesn't properly support
iframes.](cypress-io/cypress#136)
To work around this issue, I investigated many workarounds and came up
with a solution that I think solves the problem in a very elegant way:
load the iframe with Cypress directly. This way, we can just use Cypress
as normal and completely ignore the iframe issue, however this did
require a bit of work figuring out the best way to load the iframe and
selecting it.

There was a bit of a problem with getting the src url of the Storybook
preview iframe which was 'id=+' for some reason instead of the real
id of the div. This may be a bug caused by Storybook 5.0 which was just
released. Either way, I worked-around this issue by selecting the
id from the story div using a Cypress alias and then manually telling
Cypress the url for the iframe. This is not really great as it is
kind of a hacky solution, however it does solve the problem in a way
that is pretty unlikely to break in the future (since I cannot imagine
Storybook changing their id format, as it is pretty consistent with
the UI).

Signed-off-by: David Piper <david.piper@mailfence.com>

dbpiper added a commit to dbpiper/Euclid that referenced this issue Mar 7, 2019

feat: write integration test for header component
This is essentially a proof of concept of how to do integration tests
on components which are in Storybook. That is, just taking the e2e tests
for the Header component and running them on the Storybook version. This
*should* have been very simple, basically a copy-paste from the e2e
test, however it was not.

There is an issue with Cypress, namely that [it doesn't properly support
iframes.](cypress-io/cypress#136)
To work around this issue, I investigated many workarounds and came up
with a solution that I think solves the problem in a very elegant way:
load the iframe with Cypress directly. This way, we can just use Cypress
as normal and completely ignore the iframe issue, however this did
require a bit of work figuring out the best way to load the iframe and
selecting it.

There was a bit of a problem with getting the src url of the Storybook
preview iframe which was 'id=+' for some reason instead of the real
id of the div. This may be a bug caused by Storybook 5.0 which was just
released. Either way, I worked-around this issue by selecting the
id from the story div using a Cypress alias and then manually telling
Cypress the url for the iframe. This is not really great as it is
kind of a hacky solution, however it does solve the problem in a way
that is pretty unlikely to break in the future (since I cannot imagine
Storybook changing their id format, as it is pretty consistent with
the UI).

Signed-off-by: David Piper <david.piper@mailfence.com>

dbpiper added a commit to dbpiper/Euclid that referenced this issue Mar 8, 2019

feat: write integration test for header component
This is essentially a proof of concept of how to do integration tests
on components which are in Storybook. That is, just taking the e2e tests
for the Header component and running them on the Storybook version. This
*should* have been very simple, basically a copy-paste from the e2e
test, however it was not.

There is an issue with Cypress, namely that [it doesn't properly support
iframes.](cypress-io/cypress#136)
To work around this issue, I investigated many workarounds and came up
with a solution that I think solves the problem in a very elegant way:
load the iframe with Cypress directly. This way, we can just use Cypress
as normal and completely ignore the iframe issue, however this did
require a bit of work figuring out the best way to load the iframe and
selecting it.

There was a bit of a problem with getting the src url of the Storybook
preview iframe which was 'id=+' for some reason instead of the real
id of the div. This may be a bug caused by Storybook 5.0 which was just
released. Either way, I worked-around this issue by selecting the
id from the story div using a Cypress alias and then manually telling
Cypress the url for the iframe. This is not really great as it is
kind of a hacky solution, however it does solve the problem in a way
that is pretty unlikely to break in the future (since I cannot imagine
Storybook changing their id format, as it is pretty consistent with
the UI).

Signed-off-by: David Piper <david.piper@mailfence.com>

dbpiper added a commit to dbpiper/Euclid that referenced this issue Mar 8, 2019

feat: write integration test for header component
This is essentially a proof of concept of how to do integration tests
on components which are in Storybook. That is, just taking the e2e tests
for the Header component and running them on the Storybook version. This
*should* have been very simple, basically a copy-paste from the e2e
test, however it was not.

There is an issue with Cypress, namely that [it doesn't properly support
iframes.](cypress-io/cypress#136)
To work around this issue, I investigated many workarounds and came up
with a solution that I think solves the problem in a very elegant way:
load the iframe with Cypress directly. This way, we can just use Cypress
as normal and completely ignore the iframe issue, however this did
require a bit of work figuring out the best way to load the iframe and
selecting it.

There was a bit of a problem with getting the src url of the Storybook
preview iframe which was 'id=+' for some reason instead of the real
id of the div. This may be a bug caused by Storybook 5.0 which was just
released. Either way, I worked-around this issue by selecting the
id from the story div using a Cypress alias and then manually telling
Cypress the url for the iframe. This is not really great as it is
kind of a hacky solution, however it does solve the problem in a way
that is pretty unlikely to break in the future (since I cannot imagine
Storybook changing their id format, as it is pretty consistent with
the UI).

Signed-off-by: David Piper <david.piper@mailfence.com>
@attilavago

This comment has been minimized.

Copy link

attilavago commented Mar 9, 2019

I can also see the benefit of having this feature. Please prioritise. 👍

dbpiper added a commit to dbpiper/Euclid that referenced this issue Mar 11, 2019

feat: write integration test for header component
This is essentially a proof of concept of how to do integration tests
on components which are in Storybook. That is, just taking the e2e tests
for the Header component and running them on the Storybook version. This
*should* have been very simple, basically a copy-paste from the e2e
test, however it was not.

There is an issue with Cypress, namely that [it doesn't properly support
iframes.](cypress-io/cypress#136)
To work around this issue, I investigated many workarounds and came up
with a solution that I think solves the problem in a very elegant way:
load the iframe with Cypress directly. This way, we can just use Cypress
as normal and completely ignore the iframe issue, however this did
require a bit of work figuring out the best way to load the iframe and
selecting it.

There was a bit of a problem with getting the src url of the Storybook
preview iframe which was 'id=+' for some reason instead of the real
id of the div. This may be a bug caused by Storybook 5.0 which was just
released. Either way, I worked-around this issue by selecting the
id from the story div using a Cypress alias and then manually telling
Cypress the url for the iframe. This is not really great as it is
kind of a hacky solution, however it does solve the problem in a way
that is pretty unlikely to break in the future (since I cannot imagine
Storybook changing their id format, as it is pretty consistent with
the UI).

Signed-off-by: David Piper <david.piper@mailfence.com>
@trent-boyd

This comment has been minimized.

Copy link

trent-boyd commented Mar 18, 2019

Yeah, I've love to see this.

@AdamFarrar

This comment has been minimized.

Copy link

AdamFarrar commented Mar 18, 2019

Would be incredibly helpful! Thanks for considering!

@47blister47

This comment has been minimized.

Copy link

47blister47 commented Mar 19, 2019

We would like it too. Thanks in advance.

@mrchovo

This comment has been minimized.

Copy link

mrchovo commented Mar 19, 2019

@lilaconlee thanks a lot your code is working also for me 🙂 In conclusion my way was that I`ve tried handling iframe like this:

cy.wrap($body)
  .find('#credit-card-number', { timeout: 10000 })
  .type(correct_credit_card);

or even adding wait directly before cy.wrap. Now when I dig into your code the only change needed for my code to start working was add wait even before get iframe.

And when I`ve think about it I can see what happend wrong in my use-case and feel ashamed it was this trivial. 😅

@mxrguspxrt

This comment has been minimized.

Copy link

mxrguspxrt commented Mar 20, 2019

Could we please have a working example in:

https://docs.cypress.io/guides/references/known-issues.html#Difficult-use-cases

This issue has been open from 2016.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.