cypress-io / cypress

Fast, easy and reliable testing for anything that runs in a browser.
https://cypress.io
MIT License
47.39k stars 3.2k forks source link

Iframe support #136

Open brian-mann opened 8 years ago

brian-mann commented 8 years ago

Updated Feb 9, 2021 - see note below or https://github.com/cypress-io/cypress/issues/136#issuecomment-773765339

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

Things to consider

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 commented 8 years ago

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

acusti commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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

brian-mann commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

@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 commented 7 years ago

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 commented 7 years ago

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 commented 7 years ago

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

mvandebunt commented 7 years ago

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

Alex0007 commented 7 years ago

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

cypress.json ```json { "chromeWebSecurity": false } ``` ```js .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](https://user-images.githubusercontent.com/1143226/27237881-2d901ba2-52e4-11e7-8554-9268a483e87b.png) But after click: ![image](https://user-images.githubusercontent.com/1143226/27237901-3ec261b4-52e4-11e7-8bf6-0a75c0bdb000.png) Manually filled form looks formatted ![image](https://user-images.githubusercontent.com/1143226/27238263-a3edf1ce-52e5-11e7-9dc1-627729f07cd1.png)

Anyone knows how to resolve this?

brian-mann commented 7 years ago

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 commented 7 years ago

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 "