Skip to content

Iframe support #136

Open
Open

Description

@brian-mann

Updated Feb 9, 2021 - see note below or #136 (comment)

Currently the Test Runner does not support selecting or accessing elements from within an iframe.

What users want

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 Test Runner thinks the element has been detached from the DOM (because its parent document is not the expected one).

What we need to do

  • Add new cy commands to switch into iframes and then also switch back to the "main" frame.

  • 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

Things to consider

  • How will we handle snapshotting? Currently we don't take snapshots for anything inside of an <iframe>.
  • How will we handle cross origin frames? It's possible to enable these with { chromeWebSecurity: false } (Chromium-based browsers only).
  • How will we show context switching in the Command Log? It should probably look / be colored differently than the 'normal' main commands

Examples of how we could do this

// switch to an iframe subject
cy
  .get('#iframe-foo').switchToIframe() 
  .get('#button').click() // executed in <iframe id='iframe-foo' />

// or pass in $iframe object in hand
cy
  .get('#iframe-foo').then(($iframe) => {
    cy.switchToIframe($iframe)
    cy.get('#button').click()
  })

// now switch back to the main frame
cy
  .switchToMain()
  .get(':checkbox').check() // issued on the main frame

Workaround

It's possible to run cy.* commands on iframe elements like below:

cy.get('iframe')
  .then(($iframe) => {
    const $body = $iframe.contents().find('body')

    cy.wrap($body)
      .find('input')
      .type('fake@email.com')
})

⚠️ Updates

Updates as of Feb 9, 2021

Pasting some snippets from our technical brief on iframe support that we are currently planning. As always, things can change as we move forward with implementation, but this is what we are currently planning.

If there's any feedback/criticism on these specific proposed APIs, we'd be happy to hear it.

.switchToFrame([...args], callback)

Switches into an iframe and evals callback in the iframe. Doesn’t matter whether the iframe is same-origin or cross-origin.

Stripe payment example

// ❗️ This is planned work and does not currently work
cy.visit('someshop.com')
// ... add stuff to cart
// ... get to payment page
cy.get('iframe').switchToFrame(() => {
  cy.get('#name').type('name')
  cy.get('#number').type('1234-5678...')
  cy.contains('Pay').click()
})
// go on with cypress commands in the main frame
cy.contains('Thanks for your order')

Same-origin iframe

Example where a site uses a same-domain iframe as a date-picker widget

// ❗️ This is planned work and does not currently work
cy.visit('https://date-picker.com')
cy.get('iframe').switchToFrame(() => {
  cy.get('.next-month').click()
  cy.contains('24').click()
})
// switch out of iframe context because callback is finished
cy.get('.date').should('have.text', '2/24/2021')

We also intend to support snapshots of iframes.

Activity

  1. Courey commented on May 31, 2016

    @Courey

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

  2. acusti commented on Jul 21, 2016

    @acusti

    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.

  3. brian-mann commented on Jul 21, 2016

    @brian-mann
    MemberAuthor

    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.

  4. acusti commented on Jul 22, 2016

    @acusti

    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!)

  5. maximilianschmid commented on Sep 21, 2016

    @maximilianschmid

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

  6. brian-mann commented on Sep 21, 2016

    @brian-mann
    MemberAuthor

    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
    })
  7. harz87 commented on Oct 14, 2016

    @harz87

    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.

  8. brian-mann commented on Oct 14, 2016

    @brian-mann
    MemberAuthor

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

  9. rbone commented on Feb 6, 2017

    @rbone

    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.

  10. oliver3 commented on Feb 14, 2017

    @oliver3

    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.

  11. afohlmeister commented on Feb 17, 2017

    @afohlmeister

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

  12. mvandebunt commented on Apr 6, 2017

    @mvandebunt

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

  13. Alex0007 commented on Jun 16, 2017

    @Alex0007

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

    Details 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?

  14. 477 remaining items

  15. samtsai commented on Mar 25, 2024

    @samtsai
    Contributor

    What about the following approach?

    cy
      .get('#IFRAMELOCATOR')
      .its('0.contentDocument.body')
      .should('not.be.empty')
      // eslint-disable-next-line @typescript-eslint/unbound-method
      .then(cy.wrap)
      .find('#ELEMENTINIFRAME')
      .should("be.visible")

    It does exactly what it says. It checks that the content is not empty. But that does not help. If you have dynamic application, things are added to the DOM after the app is loaded, e.g. when backend requests are done.

    Long story short: It does not help for the problem I described. It just prevents the iFrame being completely empty.

    I think you could try combining the iframe helper and a plugin like: cypress-recurse to achieve what you're looking for.

    Also this is a great resource for finer details of above snippets: https://www.cypress.io/blog/2020/02/12/working-with-iframes-in-cypress

  16. jwedel commented on Mar 25, 2024

    @jwedel

    What about the following approach?

    cy
      .get('#IFRAMELOCATOR')
      .its('0.contentDocument.body')
      .should('not.be.empty')
      // eslint-disable-next-line @typescript-eslint/unbound-method
      .then(cy.wrap)
      .find('#ELEMENTINIFRAME')
      .should("be.visible")

    It does exactly what it says. It checks that the content is not empty. But that does not help. If you have dynamic application, things are added to the DOM after the app is loaded, e.g. when backend requests are done.
    Long story short: It does not help for the problem I described. It just prevents the iFrame being completely empty.

    I think you could try combining the iframe helper and a plugin like: cypress-recurse to achieve what you're looking for.

    Also this is a great resource for finer details of above snippets: https://www.cypress.io/blog/2020/02/12/working-with-iframes-in-cypress

    I read that article already a couple of times and it’s eventually also leading to the same suggestion as mentioned before.

    I didn’t know about the recurse plugin and it might be useful in some situations.

    But we have hundreds of Dom queries, adding recurse everywhere would lead hardly readable code.

    I think our RegEx test is the least bad of all the bad work-arounds.

  17. bahmutov commented on Mar 25, 2024

    @bahmutov
    Contributor

    @jwedel can you give me a small example of what is not loading or hard to check inside an iframe?

  18. jwedel commented on Mar 25, 2024

    @jwedel

    @jwedel can you give me a small example of what is not loading or hard to check inside an iframe?

    Generally everything that’s dynamically loading.

    We have a big Angular app running inside an iFrame. There is lots of elements being added and removed based on user interaction.

    Now let’s say you click on a button which loads something from the backend that gets rendered later. Then you’ll do the get->its->find chain, find will timeout after 4 seconds (in fact you can use whatever timeout you want) when the element was not there before. Also using intercept does not work reliably as it does not guarantee that the resulting elements are actually in the DOM already.

    Our solution looks inside the raw HTML for the element using a RegEx which is automatically retried by Cypress. Once the RegEx succeeds, we can use find as we know the element is there.

    But this is all very ugly and still fragile and you cannot use jQuery selectors.

  19. bahmutov commented on Mar 26, 2024

    @bahmutov
    Contributor

    @jwedel that is too fuzzy for me to help. I have created a simple iframe example showing flake due to loading and how I solve it, see https://glebbahmutov.com/blog/cypress-flaky-tests-exercises/ but if anyone has a realistic app they struggle with (and they can share publicly), I can take a look at it

  20. jwedel commented on Mar 26, 2024

    @jwedel

    @jwedel that is too fuzzy for me to help. I have created a simple iframe example showing flake due to loading and how I solve it, see https://glebbahmutov.com/blog/cypress-flaky-tests-exercises/ but if anyone has a realistic app they struggle with (and they can share publicly), I can take a look at it

    I watched your video. The last example is exactly the problem I was referring to.

    Where you use should.not.include we use a RegEx. We want Cypress to retry until a certain element is existing in the DOM.

    as we have a LOT of dynamic elements, we generalized it as a Cypress command that takes an optional RegEx where we could pass something like new RegEx(/data-test=“my-elem”/).

    this works but it’s annoying and clutters the code with unnecessary duplications as you want to add another find with the same element.

    so we start without that and when it breaks or gets flaky, we’ll add the RegEx.

    it works but it feels like using assembler when you actually want to use some proper high level language.

  21. bahmutov commented on Mar 26, 2024

    @bahmutov
    Contributor
  22. jwedel commented on Mar 28, 2024

    @jwedel

    I still don’t understand. Why not grab the document and wrap it and use normal cypress commands?

    We are doing that. As I said, we use almost the same code as you showed in the video.

    And as you already showed perfectly in the same video, any find after the wrap->then will not retry (actually Cypess does retry, but the underlying wrapped document snapshot does not change).

    So to retry for an element that takes time to appear in the DOM, we have to retry before the wrap happens.

  23. sanjeevyadav512 commented on Apr 21, 2025

    @sanjeevyadav512

    What if I need to interact with cross-origin iframe and I can not make chromeWebSecurity : false.

  24. bikmax commented on Apr 21, 2025

    @bikmax

    What if I need to interact with cross-origin iframe and I can not make chromeWebSecurity : false.

    bro just swtich to other framework that can do simple things simply. I abandoned Cypress just because of that. returned to Selenium + Python

  25. ali0311 commented on Apr 26, 2025

    @ali0311

    @sanjeevyadav512 From what I understand, Cypress struggles with handling cross-origin iframes.
    If your iframe usage is minimal, you can still work around it by executing vanilla JavaScript through the Cypress object and continue with your tests.
    You don't need to explicitly enter or exit the iframe — just run the vanilla JavaScrip script to perform the required actions.
    However, if you need to run extensive and complex tests within an iframe, Cypress may not be the best choice.

  26. mihhail-m commented on May 6, 2025

    @mihhail-m
  27. simrankaur83 commented on Feb 6, 2026

    @simrankaur83

    I'm trying to automate stripe in iframe on my app. I'm able to fill form but when I continue click button cypress browser crashes. Is there any solution??

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    EpicRequires breaking up into smaller issuesexisting workaroundpkg/driverThis is due to an issue in the packages/driver directorytopic: iframestype: featureNew feature that does not currently exist

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions