Handling Iframes in Cypress: Native Methods vs. cypress-iframe Plugin
In this article, we explore how to effectively handle iframes in Cypress, a powerful tool for end-to-end testing. Iframes present unique challenges in web testing due to their separate DOM context, but with the right strategies, you can efficiently interact with them. We’ll discuss best practices for iframe handling, and provide sample iframe application and respective scripts.
Iframe Overview
An inline frame (iFrame) is an element that loads another HTML element inside of a web page. They are commonly used to embed specific content like external ads, videos, tags, or other interactive elements into the page.
Interaction with iframes can be challenging and may cause failures because iframes have a separate DOM context, traditional methods of interaction can fail, necessitating specialized approaches using cypres.
Here’s a breakdown of the two primary approaches:
1. cypress-iframe Plugin
The cypress-iframe plugin provides simplified commands to manage iframe interactions:
Advantages:
Ease of Use: Commands like frameLoaded() and iframe() simplify iframe handling, making tests more readable and quicker to write.
Efficiency: Reduces boilerplate code, allowing testers to focus on business logic rather than technical intricacies.
Disadvantages:
Dependency Management: Adds a third-party dependency that requires monitoring for updates and compatibility.
Limited Control: Abstracts details, which can complicate debugging and handling of edge cases.
2. Native Cypress Methods
Gleb Bahmutov, a prominent Cypress expert and a well-known figure in the Cypress testing community, strongly advocates for using native Cypress commands due to their seamless integration and the performance benefits they offer.
Advantages:
No External Dependencies: Avoids relying on third-party plugins, reducing potential maintenance issues.
Greater Control: Provides complete control over iframe interactions, using commands like cy.get().its(‘0.contentDocument.body’) to access iframe content.
Custom Commands: Encourages creating reusable custom commands to handle iframes consistently across tests.
Example Custom Command:
Cypress.Commands.add('getIframeBody', (iframeSelector) => {
return cy.get(iframeSelector)
.its('0.contentDocument.body')
.should('not.be.empty')
.then(cy.wrap());
});
Recommendation
Use cypress-iframe if you prioritize simplicity and rapid test development, especially in projects with frequent iframe use and simpler testing needs.
Adopt Native Methods if you require more control and stability, avoiding external dependencies, particularly in complex or long-term projects.
Best practices for testing iframes using native methods:
Ensure you are using Cypress version 10 or later. Currently, we are using version 10.11.0
Cypress does not automatically have access to the DOM inside iframes.
Test Application:
-> Approach- cy.iframe(): Not able to access the ‘.button’ class and leading to timeout error.
describe('Localhost Application Test Suite', () => {
beforeEach(() => {
cy.visit('http://localhost:3000');
});
it('click a button inside the first iframe1 and verify text', () => {
cy.iframe('[data-cy="the-frame-1"]').then($iframeBody => {
cy.log('Checking iframe body:', $iframeBody.html());
cy.wrap($iframeBody).find('.button').then($button => {
cy.log('Button found:', $button);
});
});
});
it('click a button inside the first iframe2 and verify text', () => {
cy.iframe('[data-cy="the-frame-2"]').then($iframeBody => {
cy.log('Checking iframe body:', $iframeBody.html());
cy.wrap($iframeBody).find('.button').then($button => {
cy.log('Button found:', $button);
});
});
});
});
Test Report:
-> Approach 1: Using .get(0) to Access the document
This approach directly accesses the iframe’s document object, allowing you to manipulate it using Cypress methods:
describe('Iframe Test', () => {
beforeEach(() => {
cy.visit('http://localhost:3000');
});
it('should interact with elements inside an iframe', () => {
// First, get the iframe element
cy.get('[data-cy="the-frame-1"]').then($iframe => {
// Then access the document of the iframe
const doc = $iframe.contents().get(0);
// Wrap the document to use Cypress commands on it
cy.wrap(doc).then((doc) => {
// Find elements inside the document
const button = Cypress.$(doc).find('button');
cy.wrap(button).click();
});
});
});
})
.get(0): This method retrieves the document element from the jQuery object representing the iframe. It’s the equivalent of accessing iframe.contentDocument in plain JavaScript
-> Approach 2: Using .find(‘body’) to Access the Iframe Content
This method involves accessing the body of the iframe document and then using Cypress commands within that context:
describe('Iframe Test', () => {
beforeEach(() => {
cy.visit('http://localhost:3000');
});
it('should click the button inside Iframe 1', () => {
cy.get('[data-cy="the-frame-1"]').then(($iframe) => {
const iframeBody = $iframe.contents().find('body');
cy.wrap(iframeBody).within(() => {
cy.get('button').click();
});
});
});
it('should click the button inside Iframe 2', () => {
cy.get('[data-cy="the-frame-2"]').then(($iframe) => {
const iframeBody = $iframe.contents().find('body');
cy.wrap(iframeBody).within(() => {
cy.get('button').click();
});
});
});
});
})
.find(‘body’): This method finds the body element within the iframe’s document. It’s a more specific way of scoping the query to the body, which is useful if you want to ensure that you’re interacting within the visible area of the iframe.
->We can create a custom function to access iframes elements:
The clickButtonInIframe custom command assumes it is defined in cypress/support/commands.js
// Custom command to access iframe body and perform an action
Cypress.Commands.add('clickButtonInIframe', (iframeSelector, buttonSelector) => {
cy.get(iframeSelector)
.its('0.contentDocument.body')
.should('not.be.empty')
.then(cy.wrap)
.find(buttonSelector)
.should('be.visible')
.click();
return
});
->Test Script:
describe('Iframe Test', () => {
beforeEach(() => {
cy.visit('http://localhost:3000');
});
it('should click the button inside Iframe 1', () => {
cy.get('[data-cy="the-frame-1"]').then(($iframe) => {
const iframeBody = $iframe.contents().find('body');
cy.wrap(iframeBody).within(() => {
cy.get('button').click();
});
});
});
it('should click the button inside Iframe 2', () => {
cy.get('[data-cy="the-frame-2"]').then(($iframe) => {
const iframeBody = $iframe.contents().find('body');
cy.wrap(iframeBody).within(() => {
cy.get('button').click();
});
});
});
it('should interact with elements inside an iframe using direct document access', () => {
cy.get('[data-cy="the-frame-1"]').then($iframe => {
// Then access the document of the iframe
const doc = $iframe.contents().get(0);
// Wrap the document to use Cypress commands on it
cy.wrap(doc).then((doc) => {
// Find elements inside the document
const button = Cypress.$(doc).find('button');
cy.wrap(button).click();
});
});
});
it('using cypress command for clicking a button inside the iframe', () => {
cy.clickButtonInIframe('[data-cy="the-frame-1"]', 'button');
});
});
Test Reports:
->Approach3: Negative test/ not recommended
Attempt to directly access the button inside iframes 1,2 without proper access.
->Test Script:
describe('Negative Iframe Test', () => {
beforeEach(() => {
cy.visit('http://localhost:3000');
});
it('should fail to click the button inside Iframe 1 without accessing its body', () => {
// Attempt to directly click the button inside Iframe 1 without proper access
cy.get('[data-cy="the-frame-1"]').then(($iframe) => {
// Attempt to find the button in the iframe's body directly
expect(() => {
cy.wrap($iframe).find('button').click();
}).to.throw();
});
});
it('should fail to click the button inside Iframe 2 without accessing its body', () => {
// Attempt to directly click the button inside Iframe 2 without proper access
cy.get('[data-cy="the-frame-2"]').then(($iframe) => {
// Attempt to find the button in the iframe's body directly
expect(() => {
cy.wrap($iframe).find('button').click();
}).to.throw();
});
});
});
Wrapping Up
Mastering the handling of iFrames in Cypress is essential for any developer or tester working with complex web applications. By following the best practices and sample scripts outlined, you can ensure reliable and efficient test interactions with iframe content, enhancing the quality of your testing efforts. We welcome your feedback on these suggested approaches.
Source: This blog was originally published at https://testgrid.io/blog/handling-iframes-in-cypress/
Subscribe to my newsletter
Read articles from Jace Reed directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Jace Reed
Jace Reed
Senior Software Tester with 7+ years' experience. Expert in automation, API, and Agile. Boosted test coverage by 30%. Focused on delivering top-tier software.