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

Iframe support #136

Open
brian-mann opened this issue May 11, 2016 · 392 comments
Open

Iframe support #136

brian-mann opened this issue May 11, 2016 · 392 comments

Comments

@brian-mann
Copy link
Member

@brian-mann brian-mann commented May 11, 2016

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.

@Courey
Copy link

@Courey 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
Copy link

@acusti 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
Copy link
Member Author

@brian-mann 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
Copy link

@acusti 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
Copy link

@maximilianschmid maximilianschmid commented Sep 21, 2016

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

@brian-mann
Copy link
Member Author

@brian-mann 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
Copy link

@harz87 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
Copy link
Member Author

@brian-mann 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
Copy link

@rbone 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
Copy link

@oliver3 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
Copy link

@afohlmeister afohlmeister commented Feb 17, 2017

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

@mvandebunt
Copy link

@mvandebunt mvandebunt commented Apr 6, 2017

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

@Alex0007
Copy link

@Alex0007 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
Copy link
Member Author

@brian-mann 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
Copy link

@caerie4 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
Copy link

@caerie4 caerie4 commented Jun 22, 2017

Are you still planning to add support for this later?

@brian-mann
Copy link
Member Author

@brian-mann 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
Copy link

@AshMcConnell AshMcConnell commented Aug 11, 2017

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

@bd82
Copy link

@bd82 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
Copy link
Member Author

@brian-mann 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
Copy link
Contributor

@paulfalgout 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
Copy link
Member Author

@brian-mann 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
Copy link
Member Author

@brian-mann 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
Copy link
Contributor

@paulfalgout paulfalgout commented Nov 2, 2017

ok that was the direction I was headed 👍

@ioan-ghisoi
Copy link

@ioan-ghisoi 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
Copy link
Contributor

@paulfalgout 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!');
@attheshow
Copy link

@attheshow attheshow commented Feb 5, 2021

Oh my goodness that looks so much easier! Thank you all for working towards this!

@cdtinney
Copy link

@cdtinney cdtinney commented Feb 5, 2021

@jennifer-shehane Looks great. This should work with Square's SqPaymentForm too.

@Coding-Means-Always-Learning
Copy link

@Coding-Means-Always-Learning Coding-Means-Always-Learning commented Feb 5, 2021

Getting a snapshot of frames in time travel is a big progress and will help us a lot also the proposed sample code looks simple and easy to use .
I am assuming we can use the same logic wherever we have multiple frames i.e. frames within frames in the same page..

@JoeEarly
Copy link

@JoeEarly JoeEarly commented Feb 8, 2021

Excited!

@Coding-Means-Always-Learning
Copy link

@Coding-Means-Always-Learning Coding-Means-Always-Learning commented Feb 10, 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.

Hello @jennifer-shehane
Do you know when likely this will be rolled out

We have a new project starting in a 6 weeks, and thats built completely on frames and latency (that's what 3rd party devs are saying). We are doing a testing PoC at the moment and there are number of challenges even after using the alternate solution provided at the top of this ticket.

Application seems to have performance issue which will be fixed in near future however this is causing issue for our PoC when the frames do not load up at the 1st instance and I am unsure how to get them reload using the code.

At the moment following is how our code is looking after using the using the alternate frame selection within command script. You may see there are too many then and force clicks being used which we want to avoid

`cy.getIframeBody('parentframe').find('li[class="tab application-menu-trigger"] a').should('be.visible').click({force:true}).then(()=>{
cy.getIframeBody('child frame').find('#menu').should('be.visible').click({force:true})
})

    cy.getIframeBody('parent frame').find('#application-menu  header:nth-child(1) > a >  span').click().then(()=>{
        cy.getIframeBody('.m-app-frame').find('css selector').should('contain','Customer')    
    })


    cy.getIframeBody('parent frame').find('iframe[src*="CustomerServiceDesk"]').then((childFrame)=>{
        cy.getIframeBody(childFrame).find('div > input[id="COlistSearchQuery"]').should('have.attr','maxlength','200').type(''abc, {force: true})
        cy.getIframeBody(childFrame).find('#R1 > div.slick-cell.l0.r0.uppercase a').should('be.visible')
    })


    cy.getIframeBody('.m-app-frame').find('iframe[src*="CustomerServiceDesk"]').then((childFrame)=>{
        cy.getIframeBody(childFrame).find('#COlistSearchButton > span > span').click({force: true})
    })

    cy.getIframeBody('.m-app-frame').find('iframe[src*="CustomerServiceDesk"]').then((childFrame)=>{
        cy.getIframeBody(childFrame).find('#R1 > div.slick-cell.l0.r0.uppercase a').should('have.text',this.testData.customerNumber).click()
    })`

Is there better way to handle this until the frame support is rolled out.
Also i see the ticket has been raised 5 years ago and really appreciate all the wonderful features you have bought us so far however with this issue we are sitting on a fence and unable to decide which way to go . Hence keen to understand the planned roll out schedule for these frame support

And the following is how our 3rd party developers code visible to us

image

@debasisj
Copy link

@debasisj debasisj commented Feb 18, 2021

Eagerly waiting for this one to be available
By the way anyone can advise me if it can capture src attribute of the iFrame?

@vtrivedisdet
Copy link

@vtrivedisdet vtrivedisdet commented Feb 19, 2021

Iframe support is a must to be in parallel to the competitors, and to get an edge. There are applications who heavily use iframes and no native iframe support becomes a blocker to proceed further. The workarounds/hacks are not stable and reliable across the board. Custom logics can be written but then they are very flaky. Please include iframe support at the earliest.

@bahmutov
Copy link
Contributor

@bahmutov bahmutov commented Feb 19, 2021

I have tested an e-commerce site that uses Stripe Iframe. You can read the whole blog post at https://glebbahmutov.com/blog/tested-ecommerce/ but here is the little fragment I have used, straight from https://www.cypress.io/blog/2020/02/12/working-with-iframes-in-cypress/

// working with cross-origin Stripe iframe
// https://www.cypress.io/blog/2020/02/12/working-with-iframes-in-cypress/
const ccNumber = '4242424242424242'
const month = '12'
const year = '30'
const cvc = '123'
const zipCode = '90210'
getIframeBody('.stripe-card iframe')
  .find('input[name=cardnumber]').type(`${ccNumber}${month}${year}${cvc}${zipCode}`)
cy.contains('.pay-with-stripe', 'Pay with credit card').click()

/**
  * Little utility to find an iframe
  * @see https://www.cypress.io/blog/2020/02/12/working-with-iframes-in-cypress/
  */
const getIframeBody = (iframeSelector) => {
  // get the iframe > document > body
  // and retry until the body element is not empty
  return cy
  .get(iframeSelector)
  .its('0.contentDocument.body').should('not.be.empty')
  // wraps "body" DOM element to allow
  // chaining more Cypress commands, like ".find(...)"
  // https://on.cypress.io/wrap
  .then(cy.wrap)
}
@RoshenCandy
Copy link

@RoshenCandy RoshenCandy commented Feb 20, 2021

Help me, please!
The problem is that I have 5 iframes: the first two - I don't know, they are on all pages of the site and the last three - with the inputs. Each input has an individual iframe for him and most ways from Google can not help me to test payment on 'my' site Could you help me, please?

@ashokkumarg
Copy link

@ashokkumarg ashokkumarg commented Mar 10, 2021

Can the Cypress iframe have a support with XPATH? There are some scenarios where we need to handle with XPATH inside the iframes.

@AlucardFAS
Copy link

@AlucardFAS AlucardFAS commented Mar 16, 2021

Help me, please!
The problem is that I have 5 iframes: the first two - I don't know, they are on all pages of the site and the last three - with the inputs. Each input has an individual iframe for him and most ways from Google can not help me to test payment on 'my' site Could you help me, please?

@RoshenCandy Using the solution I wrote at #136 (comment) can you switch to any of the iframes?

@Coding-Means-Always-Learning

@jennifer-shehane - When I am looking in the roadmap page , I see this feature is now bumped down and Session API and Visit multiple superdomains are above this. Will that be the release order. Trying to understand when this iframe support feature is likely to be rolled out ?

@jennifer-shehane
Copy link
Member

@jennifer-shehane jennifer-shehane commented Apr 1, 2021

Generally the Roadmap is in order of how the work is progressing on our side. Although multidomain and iframe work are related work.

@Coding-Means-Always-Learning

Generally the Roadmap is in order of how the work is progressing on our side. Although multidomain and iframe work are related work.

Thank you @jennifer-shehane . Just keen to understand when likely the iframe support is going to be made available for us.

@Coding-Means-Always-Learning

Generally the Roadmap is in order of how the work is progressing on our side. Although multidomain and iframe work are related work.

Hello @jennifer-shehane hope you all are doing well and safe. Is there any tentative timeline or a plan for me to understand when this support may get released. We are struggling to make the decision of using this without knowing how soon or late will this be available.

Please advise

@cypress-is-awful

This comment has been minimized.

@cypress-is-awful

This comment has been minimized.

@maha238
Copy link

@maha238 maha238 commented Jun 2, 2021

Hi @jennifer-shehane hope you all are doing well and safe.. Is there a tentative timeline on when this feature will be released? It will be of great help if you could share the timeline.

Thanks,
Maha

@jennifer-shehane
Copy link
Member

@jennifer-shehane jennifer-shehane commented Jun 2, 2021

There's work that has begun on the feature-multidomain branch of this repo that you could track. Some work we're doing now: #16708 and #16450

@maha238
Copy link

@maha238 maha238 commented Jun 2, 2021

@jennifer-shehane, Appreciate your quick response here!

Thanks,
Maha

@Jimmy-Ru
Copy link

@Jimmy-Ru Jimmy-Ru commented Jun 8, 2021

@jennifer-shehane, @brian-mann, I have a web application which is using iframe, reading the elements inside the iframe is fine, however, if the iframe body has a Save & Continue button, user clicked the Save & Continue button, the app moved to the next screen, the contents of the same iframe reloaded and updated, how can read the elements of the new iframe body??

cy.get('iframe[title="Application Wizard"]').then($iframe =>{
  var $body =  $iframe.contents().find('body');
  cy.wrap($body).find('input[id="username"]').type("test username");             
  cy.wrap($body).find('input[id="lastname"]').type("test lastname");             
  cy.wrap($body).find('input').contains("Save & Continue").click();     
 // after clicking the Save & Continue button, the app moves to the next screen,  the contents of the same iframe reloaded and updated, how can read the new elements of the new iframe body??

});

cy.get('iframe') // again doesn't work, I'm getting an error saying TypeError, Cannot read property 'length' of undefined

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet