Iframes, switchTo() and default content with Selenium

Published by

on

So, now that you are an expert in writing CSS selectors to identify your WebElements (possible because of my older webinar on this topic), you want to write some new tests. You are inspecting the page you will test, identifying what WebElements you will need, and start writing the selectors. Once you have them, and the test contains all the necessary interactions with those WebElements, you run the test, confident it will pass. But instead, surprise. You get a NoSuchElementException. You double, triple, quadruple check the page, and by the looks of it, the selector is correctly written. And that is true. However, when you inspect the page further, you notice that your element is actually contained within an <iframe> tag (<iframe>…</iframe>).

In this case, the selector you wrote can be left as is. In the test however you will need to perform two additional steps: switch to the iframe that contains your WebElement before interacting with it, and switching back to the parent frame (also called default content) once you want to interact with items outside of the iframe. For both these steps you will use the ‘switchTo()’ method from the Selenium library.

Here is a very simple drawing representation of a document with iframes:

1. Switching to an iframe

In order to switch to an iframe, first you need to uniquely identify it. For that, in Selenium, you have three options: identify by id (or name), by index, or by a custom CSS selector. Let’s take a look at them one by one.

Iframe by id/name

As mentioned, for this approach, when the iframe tag has an ‘id’ attribute or a ‘name’ attribute, you can use them to uniquely identify the iframe for switching to it. The usage of this approach is as follows, where the id or the name that represent your <iframe> will be expressed as String parameters:

driver.switchTo().frame(String id);
driver.switchTo().frame(String name);

Below are a few HTML examples of what the <iframe> tag might look like in your application. Of course, the values for the ‘id’ and ‘name’ are made up for the purpose of demonstration.

As an example the HTML code for an iframe that can be identified by an id looks as follows:

<iframe id="frameWithId" ...></iframe>

In this case, the value of the id which needs to be passed to the ‘switchTo()’ method is the value of the ‘id’ attribute from this HTML element. And that value is ‘frameWithId’. In the test, switching to this iframe will be done as follows:

driver.switchTo().frame("frameWithId");

On the other hand, an example of the HTML code for an iframe that can be identified by a name looks as follows:

<iframe name="frameWithName" ...></iframe>

In this case, the value needed to be specified for identifying this iframe will be the value of the name attribute, namely ‘frameWithName’. In the test, switching to this iframe will be done as follows:

driver.switchTo().frame("frameWithId");

Iframe by index

If your iframe has no id or name, or if they have a different value each time the page is rendered, there are still other ways to identify it in order to access it from your tests. One of them is to identify the frame by its index. 

For this approach, you need to first inspect the page and see how many <iframe> tags you have. Imagine you would store all the <iframe> tags in a Java List. The index refers to the index in the List of the <iframe> tag you are interested in, considering that indexes for Java Lists start with 0. So, let’s say you have 5 <iframe> tags on your page and you are interested in the third one. That means the index you will use for identifying your frame in the tests will be 2. Note that this approach only works if the <iframe> you are interested in has the same index each time the page opens.

In order to switch to a frame identified by index in Selenium, you will use the following syntax, where the index is represented as an int parameter:

driver.switchTo().frame(int index);

For this approach, the <iframe> tag can have whatever attributes it is allowed to have, since those do not matter in the process of identifying it.

Iframe as WebElement

This approach is used to identify the <iframe> based on a selector, just as you would identify a regular WebElement. For example, you could use the value of the ‘src’ attribute, or the value of the ‘class’ attribute if it exists and uniquely identifies the frame.

The syntax for switching to an <iframe> identified as a WebElement is as follows, where the parameter of type WebElement represents the selector of the frame:

driver.switchTo().frame(WebElement element);

Let’s say that the HTML code for the <iframe> you are interested in is the following one:

<iframe src="forFrameAsWebElement.html" ...></iframe>

In this case, the selector used for identifying the WebElement will be based on the ‘src’ attribute. In this case let’s say you are interested in the ‘src’ attribute to contain the String ‘FrameAsWebElement’. In my PageObject approach, in the class where i store the WebElements, i will create an entry for the this frame:

@FindBy(css = "[src*='FrameAsWebElement']") public WebElement frameAsWebElement;

Switching to this frame from a test would look like this, considering that ‘page’ is the PageObject class:

driver.switchTo().frame(page.frameAsWebElement);

2. Switch to defaultContent

Once you are done interacting with the iframe, you should switch back to the default content. That is the top level of the document, the one you normally interact with (unless you need to work inside an iframe). You need to switch to the default content every time you are done working in an iframe and want to continue interacting with the page that embeds the iframe, or when you need to switch to a different iframe (which is not embedded in the first iframe). In this latter case, the usage is: switch to frame 1, interact with it, switch back to default content, switch to frame 2, interact with it, switch back to default content. 

By default when you start a test by opening a new page, the default content is the one you will interact with. In this case you don’t need to switch to the default content and you only need to switch to the default content if you previously switched to an iframe.

Note that when you are working inside an iframe you do not have access to the page elements that are in the default content, just as when you are on the default content level you do not have access to the iframe elements.

Switching to the default content is done as follows:

driver.switchTo().defaultContent();

3. Examples

Note: Due to a formatting issue i encountered with the blog editor, i inserted a space character between the opening tag and the label ‘iframe’ in the examples below. In your code, that space will not be there. It’s just to help me show the code from these examples.

Example 1

Given the following HTML code:

< iframe id="frameWithId" src="forFrameWithId.html" style="width:100%;height:100px;" 
          frameborder="no"></iframe>

And the content of the ‘forFrameWithId.html’ page which is embedded in the initial page as an iframe:

<!DOCTYPE html>
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<body>
<div class="w3-display-middle w3-container w3-border-top w3-border-bottom 
              w3-border-teal">
    <button id="btnForFrameWithId"
            class="w3-btn w3-border w3-teal w3-margin w3-padding-large w3-left  
                    w3-opacity">Inside frame with id</button>
</div>
</body>

Create a test that will check that the text on the button identified by the id ‘btnForFrameWithId’ is ‘Inside frame with id’.

Solution

In the PageObject class, in this example ‘BasicPage’, a WebElement variable ‘buttonForFrameWithId’ will be created for identifying the button whose id is ‘btnForFrameWithId’:

@FindBy(css = "#btnForFrameWithId") public WebElement buttonForFrameWithId;

In the test class, in the field declaration section, the WebDriver instance and the PageObject class instance will be declared. These are ‘driver’ and ‘page’ namely.

private BasicPage page;
private WebDriver driver;

In the @BeforeClass method, the driver will be initialized and the page will also be initialized through PageFactory:

driver = browserGetter.getChromeDriver();
page = PageFactory.initElements(driver, BasicPage.class);

The page that will be tested will also be open, but that part is omitted. 

In the test method, first a switch to the iframe will be done. The iframe containing the desired button has an id of value ‘frameWithId’. Then, the button’s text will be read with the ‘getText()’ method from Selenium, and it will be compared, through an assertion of type ‘assertEquals()’ to the expected text. The last line of code for this example will make the switch back to the default content. Here is the relevant code:

driver.switchTo().frame("frameWithId");
assertEquals("Inside frame with id", page.butonForFrameWithId.getText());
driver.switchTo().defaultContent();

Example 2

Given the following HTML code:

<!DOCTYPE html>
<body>
< iframe id="frameWithId" src="forFrameWithId.html" style="width:100%;height:100px;" 
               frameborder="no"></iframe>
< iframe src="forFrameWithIndex.html" style="width:100%;height:100px;" 
               frameborder="no"></iframe>
< iframe src="forFrameAsWebElement.html" style="width:100%;height:100px;"
        frameborder="no"></iframe>
</body>

And the content of the frame whose value for the ‘src’ attribute is  ‘forFrameWithIndex.html’ and is embedded in the initial page as an iframe:

<!DOCTYPE html>
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<body>
<div class="w3-display-middle w3-container w3-border-top w3-border-bottom 
                        w3-border-cyan">
    <button id="btnForFrameWithIndex"
            class="w3-btn w3-border w3-cyan w3-margin w3-padding-large w3-left  
                     w3-opacity">Inside frame with index</button>
</div></body>

Create a test that will check that the text on the button identified by the id ‘btnForFrameWithIndex’ is ‘Inside frame with index’.

Solution

In the PageObject class, in this example ‘BasicPage’, a WebElement variable ‘buttonForFrameWithIndex’ will be created for identifying the button whose id is ‘btnForFrameWithIndex’:

@FindBy(css = "#btnForFrameWithIndex") public WebElement buttonForFrameWithIndex;

In the test class, in the field declaration section, the WebDriver instance and the PageObject class instance will be declared. These are ‘driver’ and ‘page’ namely.

private BasicPage page;
private WebDriver driver;

In the @BeforeClass method, the driver will be initialized and the page will also be initialized through PageFactory:

driver = browserGetter.getChromeDriver();
page = PageFactory.initElements(driver, BasicPage.class);

The page that will be tested will also be open, but that part is omitted. 

In the test method, first a switch to the iframe will be done. The iframe containing the desired button does not have a name or an id. Therefore, we can identify it based on its’ index. Since this is the second iframe on the page, and since we are talking in Java, it will have an index of 1. 

Then, the button’s text will be read with the ‘getText()’ method from Selenium, and it will be compared, through an assertion of type ‘assertEquals()’ to the expected text. The last line of code for this example will make the switch back to the default content. Here is the relevant code:

driver.switchTo().frame(1);
assertEquals("Inside frame with index", page.butonForFrameWithIndex.getText());
driver.switchTo().defaultContent();

Example 3

Given the following HTML code:

<!DOCTYPE html>
<body>
< iframe id="frameWithId" src="forFrameWithId.html" style="width:100%;height:100px;"
           frameborder="no"></iframe>
< iframe src="forFrameWithIndex.html" style="width:100%;height:100px;" 
           frameborder="no"></iframe>
< iframe src="forFrameAsWebElement.html" style="width:100%;height:100px;"
        frameborder="no"></iframe>
</body>

And the content of the frame whose value for the ‘src’ attribute is  ‘forFrameAsWebElement.html’ and is embedded in the initial page as an iframe:

<!DOCTYPE html>
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<body>
<div class="w3-display-middle w3-container w3-border-top w3-border-bottom 
            w3-border-deep-purple">
    <button id="btnForFrameAsWebElement"
            class="w3-btn w3-border w3-deep-purple w3-margin w3-padding-large w3-left
              w3-opacity">Inside frame as WebElement</button>
</div>
</body>

Create a test that will check that the text on the button identified by the id ‘btnForFrameAsWebElement’ is ‘Inside frame as WebElement’.

Solution

In the PageObject class, in this example ‘BasicPage’, a WebElement variable ‘buttonForFrameWithName’ will be created for identifying the button whose id is ‘btnForFrameAsWebElement’:

@FindBy(css = "#btnForFrameAsWebElement") public WebElement buttonForFrameWithName;

In this particular case, because the iframe we need does not have a name or id, and we don’t want to identify it by index, we can write a CSS selector for identifying it based on its ‘src’ attribute. In the ‘BasicPage’ class, we will create another WebElement variable dedicated to the <iframe> tag. Its’ selector refers to the ‘src’ attribute containing the String ‘FrameAsWebElement’.

@FindBy(css = "[src*='FrameAsWebElement']") public WebElement frameAsWebElement;

In the test class, in the field declaration section, the WebDriver instance and the PageObject class instance will be declared. These are ‘driver’ and ‘page’ namely.

private BasicPage page;
private WebDriver driver;

In the @BeforeClass method, the driver will be initialized and the page will also be initialized through PageFactory:

driver = browserGetter.getChromeDriver();
page = PageFactory.initElements(driver, BasicPage.class);

The page that will be tested will also be open, but that part is omitted. 

In the test method, first a switch to the iframe will be done. Then, the button’s text will be read with the ‘getText()’ method from Selenium, and it will be compared, through an assertion of type ‘assertEquals()’ to the expected text. The last line of code for this example will make the switch back to the default content. Here is the relevant code:

driver.switchTo().frame(page.frameAsWebElement);
assertEquals("Inside frame as WebElement", page.buttonForFrameWithName.getText());
driver.switchTo().defaultContent();

GitHub location of example code

PageObject class, section ‘iframes page’:

https://github.com/iamalittletester/selenium-tutorial/blob/master/src/main/java/tutorialsolution/pages/BasicPage.java

Test class:

https://github.com/iamalittletester/selenium-tutorial/blob/master/src/test/java/tutorialsolution/iframessolution/IframesTest.java

Test methods: frameById, frameByIndex, frameAsWebElement.
HTML code:

https://github.com/iamalittletester/selenium-tutorial/blob/master/src/main/resources/withIframes.html

https://github.com/iamalittletester/selenium-tutorial/blob/master/src/main/resources/forFrameWithId.html

https://github.com/iamalittletester/selenium-tutorial/blob/master/src/main/resources/forFrameWithIndex.html

https://github.com/iamalittletester/selenium-tutorial/blob/master/src/main/resources/forFrameAsWebElement.html 

Further reading

https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-iframe-element

One response to “Iframes, switchTo() and default content with Selenium”

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Blog at WordPress.com.