I'm trying to create an XPath locator for an element that is inside an iFrame: I want this iframe to be the root of XPath because I have two similar elements in 2 different iframes - but seem unable to do so any ideas why it's not working?
edit- I'm not trying to interact with this element in any way(I know that I need to switch frames before interacting with elements inside it I'm just having an XPath locator problem) I want to create an XPath with iframe as the root element because of the situation mentioned above here's the code:
<div id="framesWrapper">
<div id="frame1Wrapper">
<iframe src="/sample" id="frame1" width="500px" height="350px"></iframe>
#document
<DOCTYPE hmtl>
<html>
<head>...</head>
<body style="background-color:#a9a9a9" data-new-gr-c-s-check-loaded="14.1065.0" data-gr-ext-installed=""><h1 id="sampleHeading">This is a sample page</h1>
<h1 id="sampleHeading">This is a sample page</h1>
</body>
</html>
</div>
<div id="frame2Wrapper" >
<iframe src="/sample" id="frame2" width="100px" height="100px"></iframe>
</div>
</div>
CodePudding user response:
Typically, you need to wait for the page to load before attempting to interact with elements on the page (seems like common sense). However, what you may not know is that
- If you don't use implicit or explicit wait, the web driver will attempt to interact with web elements immediately. On slow-loading pages, this will result in an error because the web element(s) in question will not be available.
- Using an implicit wait (using the
WebDriver
object) only waits until a predetermined timeout elapses. This tend to be a bit slow because it won't break out of the wait if the page loads fast. - Using an explicit wait (using
WebDriverWait
class) allows you to set a condition that will tell the web driver to break out of the wait if the condition is met (i.e. a web element is visible on the page).
If you knew these facts, forgive me for going into this explanation. I thought it was necessary for what it is to follow.
The general recipe for interacting with a web element located in an iframe is as follows:
1) Locate the iframe first
WebDriver driver... // your web driver instance
Duration timeout = Duration.ofSeconds(10); // adjust to your needs
Duration sleepBetweenPolls = Duration.ofMillis(10); // default "sleep" is 500 milliseconds
WebDriverWait wait = new WebDriverWait(driver, timeout, sleepBetweenPolls); // wait object can be reused
...
wait.until(ExpectedConditions.frameToBeAvailableAndSwitchToIt(By.xpath(...))); // passing the XPath of the iframe
I explicitly created all the objects you need so that you can see or understand better their function. For example, you don't need to explicitly create the Duration
objects. I just thought it was more helpful to show it this way. Also, since you don't need the iframe web element, there is no variable declaration preceding the wait.until(...)
. All you need is create a wait condition for the iframe to be available before you attempt step #2 below.
2) Locate the Web Element
WebElement button = wait.until(ExpectedConditions.elementToBeClickable(By.xpath(...))); // pass the xpath of the element
button.click(); // you might need something other than a button. There are other conditions to wait for (i.e. visibility of elements, availability of elements, etc.)
3) Return to the main (parent) frame when you are done
driver.switchTo().parentFrame();
That should be pretty much what you need. Obviously, there is more to this. TimeoutException
is the exception that is thrown if the timeout limit is reached. You may or may not want to wrap those lines of code in a try/catch. Typically what I do is I surround in a try/catch so that I can execute an alternate plan if my first attempt times out. For example, in some cases, I retry after an initial attempt. Other times, I attempt to locate another element. For instance, an element might be visible if no errors on the page loading occurs. So, my first attempt might be to locate that element and my second attempt might be to locate an expected error message. The point is that using a try/catch to handle timeouts might be beneficial. One thing I forgot to mention is that TimeoutException
is a type of RuntimeException
. As such, you are not forced to catch (handle) them. It is up to you if you will simply let it fail, or handle in a more graceful way.
Obviously in your case, you have two iframes you need to deal with. All you need to do is follow this recipe for both, explicitly passing the correct locator for each AND switching to the parent frame when you are done. This is very important. Otherwise, when you attempt to interact with other components in the parent frame, your web driver won't be able to because it will still thinks it is attempting to locate those components in the last frame you told it to look in (and that won't exist anymore).