Reputation: 61
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
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
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
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