Semiramis C
Semiramis C

Reputation: 61

Cypress - How to switch between elements in iframe

I'm trying to interact with some elements inside an iframe with cypress. If I use the approach in https://bparkerproductions.com/how-to-interact-with-iframes-using-cypress-io/ for only one element per test, everything is fine.

# commands.js
Cypress.Commands.add(
    'iframe',
    { prevSubject: 'element' },
    ($iframe) => {
        return new Cypress.Promise(resolve => {
            $iframe.on('load', () => {
                resolve($iframe.contents().find('body'))
            })
        })
    })


# landing_page.spec.js    
cy.get('iframe').iframe().find('#happybutton').should('be.visible')

However, I want to look for multiple elements, click on them, and check if they are rendered correctly, but if I assign the iframe contents to a variable and reuse it to locate another element (for example, a button), cypress tries to locate the second element (for example, a menu) from the first element (the button, which is doomed to fail, because the button does not contain the menu).

# landing_page.spec.js  
let iframeContent = cy.get('iframe').iframe()
iframeContent.find('#happybutton').should('be.visible')
iframeContent.find('#myMenu').should('be.visible')

I tried using different variables, or calling directly cy.get('iframe').iframe(), every time I wanted to interact with different elements, but cypress gets trapped in an infinite loop and the test never ends (but no errors or warnings are produced).

Does anybody knows a way to avoid this infinite loop? As I want to reproduce a sequence of steps to build a test case, it is not possible to isolate each interaction in a different test.

Or does anybody knows of a framework that is more suitable for working with iframes?

Upvotes: 3

Views: 3576

Answers (3)

wbartussek
wbartussek

Reputation: 2008

The above answers pointed me to the right direction. By omittimg the 'then' phrase and the first cy.get('@iframeContent'), Semiramis' solution can be simplified a bit and made easier to understand like this:

cy.get('iframe').iframe().as('iframeContent')
cy.get('@iframeContent').find('#happybutton').click()
cy.get('@iframeContent').find('#myMenu')
cy.get('@iframeContent').find('#anotherElement').should('be.visible')

For Cypress newbees (like me): Cypress Variables and Aliases

Upvotes: 0

Semiramis C
Semiramis C

Reputation: 61

Thanks to Marion's answer I found a way to refactor my code, so now it works! Note: the iframe() function was left untouched

# commands.js
Cypress.Commands.add(
    'iframe',
    { prevSubject: 'element' },
    ($iframe) => {
        return new Cypress.Promise(resolve => {
            $iframe.on('load', () => {
                resolve($iframe.contents().find('body'))
            })
        })
    })


# landing_page.spec.js  
cy.get('iframe').iframe().as('iframeContent')
cy.get('@iframeContent').then((iframeContent) => {
    cy.get(iframeContent).find('#happybutton').click()
    cy.get(iframeContent).find('#myMenu')
    cy.get(iframeContent).find('#anotherElement').should('be.visible')
})

Upvotes: 1

user12697177
user12697177

Reputation:

The problem is $iframe.on('load', only fires once, so you can't call cy.get('iframe').iframe() twice which is effectively what both .find() commands are doing.

let iframeContent = cy.get('iframe').iframe() doesn't store the iframe body, it stores a "chainer" which is treated like a function or getter.

The "infinite loop" is Cypress waiting for the promise resolve() call the second time, which never happens.

So you can nest the commands like this

cy.get('iframe').iframe().then(body => {

  cy.wrap(body).find('#happybutton').should('be.visible')
  cy.wrap(body).find('#myMenu').should('be.visible')

});

or you can enhance the command by adding a tag when the load event fires

Cypress.Commands.add('iframe', { prevSubject: 'element' }, ($iframe) => {
  return $iframe._hasloaded 
    ? $iframe.contents().find('body')
    : new Cypress.Promise(resolve => {
        $iframe.on('load', () => {
          $iframe._hasloaded = true;
          resolve($iframe.contents().find('body'))
        })
      })
  })

Upvotes: 3

Related Questions