I have been reading about stale elements and am still a bit confused. For instance, the following won't work, correct?
public void clickFoo(WebElement ele) {
try {
ele.click();
} catch (StaleElementReferenceException ex) {
ele.click();
}
}
because if ele is stale, it will remain stale. The best thing is to redo the driver.findElement(By), but as you can see in this example, there is no xpath. You can attempt to ele.getAttribute("id") and use that, but if the element has no id, this also will not work. All methods calling this would have to put the try/catch around it, which may not be feasible.
Is there some other way the element could be refound? Also, assuming there is an id, would the id remain the same after the element goes stale? What in the WebElement object ele is different once it goes stale?
(Java Eclipse)
I would recommend you NOT create a method like the above. There's no need to add another function layer on top of .click(). Just call .click() on the element itself.
driver.findElement(By.id("test-id")).click();
or
WebElement e = driver.findElement(By.id("test-id"));
e.click();
One way that I use regularly to avoid stale elements is find the element only when you need it and generally I do this by in a page object method. Here's a quick example.
The page object for a home page.
public class HomePage
{
private WebDriver driver;
public WebElement staleElement;
private By waitForLocator = By.id("sampleId");
// please put the variable declarations in alphabetical order
private By sampleElementLocator = By.id("sampleId");
public HomePage(WebDriver driver)
{
this.driver = driver;
// wait for page to finish loading
new WebDriverWait(driver, 10).until(ExpectedConditions.presenceOfElementLocated(waitForLocator));
// see if we're on the right page
if (!driver.getCurrentUrl().contains("samplePage.jsp"))
{
throw new IllegalStateException("This is not the XXXX Sample page. Current URL: " + driver.getCurrentUrl());
}
}
public void clickSampleElement()
{
// sample method code goes here
driver.findElement(sampleElementLocator).click();
}
}
To use it
WebDriver driver = new FirefoxDriver();
driver.manage().window().maximize();
driver.get("http://www.example.com");
HomePage homePage = new HomePage(driver);
homePage.clickSampleElement();
// do stuff that changes the page and makes the element stale
homePage.clickSampleElement();
Now I no longer have to rely on an old reference. I just call the method again and it does all the work for me.
There are many references on the page object model. Here's one from the Selenium wiki. http://www.seleniumhq.org/docs/06_test_design_considerations.jsp#page-object-design-pattern
If you want to read more info on what a stale element is, the docs have a good explanation. http://docs.seleniumhq.org/exceptions/stale_element_reference.jsp
Related
For the Page Object example class below, I have an accountsLink private member which maps to a non-dynamic element on the Login page when it loads. It is initialized using the FindBy annotation when the initElements method is called from the constructor.
public class Login {
private WebDriver driver;
#FindBy(id = "account")
private WebElement accountsLink;
//constructor, elements are initialized by the PageFactory
public MainPage(WebDriver driver) {
this.driver = driver;
PageFactory.initElements(driver, this);
}
//clicking accounts opens a dynamic ajax menu which has a Sign In Button
public SignInPage clickAccountsLink() {
accountsLink.click();
WebElement signInButton = driver.findElement(By.id("signin"));
signInButton.click();
return new SignInPage(driver);
}
}
Now for the problem. I have another element (signInButton) which is dynamically loaded only when you click the accountsLink element. This action doesn't take you to another page but only brings up an ajax menu where the sign in button will appear.
My question is, since the signInButton element only appears when the accountsLink element is clicked, can it be declared as a member of the Login class with a FindBy annotation or do I have to stick with my current solution of using a driver.findElement(By.id("signin")) inside the clickAccountsLink method?
I hope my question makes sense.
When PageFactory.initElements is called it parses the current DOM. If the WebElement doesn't exist in that time it can't be given as a value to a variable, exactly as you can't locate non-existing WebElement using driver.findElement.
Your solution is the way to go, although I would use explicit wait and Expected Conditions when loading the signInButton.
You can declare, I don't think it's gonna give you any error. Page Factory creates a dummy element when it initialized the class. It creates the actual element only when you intersect with the element for the first time.
For example in following class NoExistingElement element doesn't exist and it won't give me any error, If I will call the enterText method of class. The test case will pass without any error.
However, If i will try to call any function on NoExistingElement element then only it will fail with Webdriver exception, ElementNotFoundException
public class GoogleSearch {
#FindBy(name="q")
static WebElement searchBox;
#FindBy(name = "qqqqq")
WebElement NoExistingElement;
public GoogleSearch(WebDriver driver){
PageFactory.initElements(driver, this);
driver.get("https://www.google.com");
}
public void searchOnGoogle(String text){
searchBox.sendKeys(text);
}
}
The problem I have is that my Page Object class finds all of the objects on the screen and then sends it to a more generic method to do the actual data entry. During this data entry process, some objects become stale and I get the "StaleElementException".
My plan is to catch that exception and attempt to re-find the element again.
Is there a way of extract the selection strategy from the runtime WebElement object other than doing a "object.toString()" and then parsing it?
I use page objects by defining the locators at the top of the class, then the constructor that verifies that we're on the right page (etc), and then methods for each action available on the page. Here's a simple example for the Google search page.
GoogleSearchPage.java
public class GoogleSearchPage
{
private WebDriver driver;
private By waitForLocator = By.id("lst-ib"); // optional
private By searchBoxLocator = By.id("lst-ib");
private By searchButtonLocator = By.cssSelector("button[name='btnG']");
private By feelingLuckyButtonLocator = By.id("gbqfbb");
public GoogleSearchPage(WebDriver webDriver)
{
driver = webDriver;
// wait for page to finish loading
new WebDriverWait(driver, 10).until(ExpectedConditions.presenceOfElementLocated(waitForLocator));
// see if we're on the right page
if (!driver.getCurrentUrl().contains("https://www.google.com"))
{
throw new IllegalStateException("This is not the Google search page. Current URL: " + driver.getCurrentUrl());
}
}
public void doSearch(String searchString)
{
driver.findElement(searchBoxLocator).sendKeys(searchString);
driver.findElement(searchButtonLocator).click();
}
}
GoogleSearchTest.java
public class GoogleSearchTest
{
public static void main(String[] args)
{
WebDriver driver = new FirefoxDriver();
driver.get("http://www.google.com");
GoogleSearchPage googleSearchPage = new GoogleSearchPage(driver);
googleSearchPage.doSearch("selenium");
System.out.println(driver.getCurrentUrl().contains("#q=selenium"));
}
}
This is obviously a super simple example but it shows one good way to create page objects that should significantly reduce the frequency of StaleElementExceptions and, in some cases, speed up your script execution because you only scrape what you need and move on.
Reduce the urge to provide a getX() and clickX() method for each and every element on the page. Instead favor task based methods. Ask yourself what tasks a user is going to want to accomplish on the page and provide methods to do those tasks. It will keep your page object API much cleaner and more clear in what it provides to the consumer (you and other script writers).
I am learning Selenium Webdriver using Java.
As a learning example, I tried to open MakeMyTrip, access International Flights page and click on One Way radio button in Google Chrome.
I tried different ways to locate this radio button but it's still not working.
Please find below my code sample.
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
public class TryRadioClass {
/**
* #param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
System.setProperty("webdriver.chrome.driver", "Chrome exe path");
WebDriver driver=new ChromeDriver();
driver.get("http://www.makemytrip.com/international-flights");
driver.manage().window().maximize();
driver.manage().timeouts().implicitlyWait(45, TimeUnit.SECONDS);
boolean displayFlag = driver.findElement(By.linkText("ONE WAY")).isDisplayed();
System.out.println("Display Flag :- "+displayFlag);
boolean enableFlag = driver.findElement(By.linkText("ONE WAY")).isEnabled();
System.out.println("Enable Flag :- "+enableFlag);
if(displayFlag==true && enableFlag==true)
{
WebElement element=driver.findElement(By.linkText("ONE WAY"));
element.click();
System.out.println("Tried to click One Way");
}
}
}
Can anyone please help me to resolve this issue?
Use below code :-
if(displayFlag==true && enableFlag==true)
{
try{
Thread.sleep(5000);
}
catch(Exception ex)
{
System.out.println(ex.getMessage());
}
WebElement element=driver.findElement(By.xpath("//span[#class='radio_state']"));
JavascriptExecutor executor = (JavascriptExecutor) driver;
executor.executeScript("arguments[0].click();", element);
System.out.println("Tried to click One Way");
}
enjoy .. get back to me if still getting any issue :)
Clicking on link sometimes might skip checking the radio button. Try clicking on the radio button (or input html tag) directly rather than clicking on the anchor tag. Here's an example -
WebElement ele=driver.findElement(By.xpath("//input[#value='one_way']"));
ele.click();
Hope this helps.
Try to never use Thread.sleep(time)
Never use general Exception catching.
Instead of it try this, what is more safe:
public Boolean isDisplayed() {
try {
wait.withTimeout(20).until(new ExpectedCondition<Boolean>() {
#Nullable #Override public Boolean apply(WebDriver input) {
return videoComponent.isDisplayed();
}
});
} catch (TimeoutException e) {
return false;
}
return true;
}
This code will check markup within 20 seconds until it return true.If not, after 20 sec it will return false.
Always specify Exception type, which you want to catch. In other cases it is useless.
Try below xpath.
//*[#id="one_way_button1"]/span/input
It should work.
driver.findElement(By.xpath(//*[#id=\"one_way_button1\"]/span/input)).click();
As per my check, the specific radio button has following structure:
<a id="one_way_button1" href="javascript:void(0);" onclick="change_trip_type('one_way_button', 'trip_type', 'o');" class="one_way_button trip_type row first seg_text" tabindex="1">
<span class="radio_state">
<input type="radio" name="way_fields" value="one_way">
</span> ONE WAY
</a>
So what you are gonna to click is not the tag a which contains 'ONE WAY' but the span inside. You may have a try to locate the span by using xpath
"//a[text()='ONE WAY']/span[#class='radio_state']"
We have found in the past that with a radio and a click that the above happens (Sometimes it clicks it sometimes it does not) and as we try and steer clear of wait for tasks/Thread.Sleep (Due to then timing issues it then can cause on different environments). This alone can become a giant headache quick in using thread.sleeps :p
We have personally found that sometimes the best solution is to send a click then a (Sendkeys.Enter or a Sendkeys.Space) or just send the (Sendkeys.Enter or a Sendkeys.Space) only and don't use the click with a radio.
Especially on pages that use Telerik controls. This then tends to work on multiple machines/environments without the need to add a Thread.Sleep and make every test which uses that step take way longer than it needs to (Trust me when you have 1000's of tests that 5s thread.sleep soon adds up if a lot of tests use the same method)
Just throwing in another possible solution into the mix...
This is the more proper way to do this and I just tested it and it works fine. The best practice is to wait for the element to be clickable. You do that using WebDriverWait with ExpectedConditions. What this will do is wait up to 20s for the element to appear, when it does execution continues. This is a big advantage over Thread.sleep().
driver.get("http://www.makemytrip.com/international-flights");
driver.manage().window().maximize();
WebDriverWait wait = new WebDriverWait(driver, 20);
WebElement oneWayRadioButton = wait.until(ExpectedConditions.elementToBeClickable(By.id("one_way_button1")));
oneWayRadioButton.click();
System.out.println("Clicked One Way");
I tried many different ways to force selenium to wait for the radio button to be visible but it kept timing out. I eventually had to settle for clicking the label for:
WebDriverWait wait = new WebDriverWait(driver, 20);
WebElement element = driver.findElement(selector);
wait.until(ExpectedConditions.elementToBeClickable(element));
element.click();
Where selected is defined by:
By selector = By.cssSelector(cssSelector);
With HTML looking like this:
...
<div class="multi-choice">
<input id="wibble" type="radio" name="wobble" value="TEXT">
<label for="wibble">Wobble</label>
</div>
...
And an example string cssSelector of:
String cssSelector = "label[for='wibble']"
I use selenium IDE to initially record the tests and save them as Java WebDriver tests.
When I go into an input field, delete all the text and enter a new value, it records that as 2 commands:
driver.findElement(By.id("username")).clear();
driver.findElement(By.id("username")).sendKeys("johnnyleitrim");
One problem with this for me is that the clear() event fires a Javascript change event for the "username" field. This does not happen when I use the browser itself - it waits until the field loses focus before firing the change javascript event, and that's what I want to emulate in Selenium.
The reason I need this is that I do validation on the change() event, and when change is called with an empty value, it displays an alert telling the user the information is invalid - and this alert stops Selenium
So how do I clear the field without using WebElement.clear()?
You can avoid using the clear() method and use the Actions class to clear and set text in one go, therefore firing the onchange() event only once the text is set.
Call the below method like:
ClearAndSetText(By.id("username"),"johnnyleitrim");
The method clicks the element, selects the existing text using shift+home keys,clears using backspace, and then types in the new text - just like how a user would do.
public void ClearAndSetText(By by, string text)
{
WebElement element = driver.findElement(by);
Actions navigator = new Actions(driver);
navigator.click(element)
.sendKeys(Keys.END)
.keyDown(Keys.SHIFT)
.sendKeys(Keys.HOME)
.keyUp(Keys.SHIFT)
.sendKeys(Keys.BACK_SPACE)
.sendKeys(text)
.perform();
}
You can try it using JavaScriptExecutor (although I haven't tested it).
JavaScriptExecutor js = (JavaScriptExecutor) driver;
js.executeScript("document.querySelector(\"input[id='username']\").value = ''");
Seems like it's a known Selenium bug. There were a few options as workarounds mentioned on the bug page, but they all meant having to "heavily" modify the code returned from Selenium IDE. Instead, I decided to create a Proxy which would do the work for me without too much modification to the IDE generated code:
protected WebElement findElement(By criteria) {
try {
WebElementHandler webElementHander = new WebElementHandler(seleniumWebDriver.findElement(criteria));
return (WebElement) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{WebElement.class}, webElementHander);
} catch (NoSuchElementException e) {
logger.error("Could not find " + criteria + " on page " + seleniumWebDriver.getCurrentUrl());
throw e;
}
}
private class WebElementHandler implements InvocationHandler {
private WebElement proxiedElement;
private WebElementHandler(WebElement proxiedElement) {
this.proxiedElement = proxiedElement;
}
#Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("clear")) {
Keys[] keys = new Keys[proxiedElement.getAttribute("value").length()];
for (int i = 0; i < keys.length; i++)
keys[i] = Keys.BACK_SPACE;
proxiedElement.sendKeys(Keys.chord(keys));
return null;
}
return method.invoke(proxiedElement, args);
}
}
It's convenient to wait for an WebElement to be present with WebDriverWait and ExpectedConditions.
The problem is, what if WebElement.findElment was the only possible way to locate the element , 'cause it has no id, no name, no unique class?
WebDriverWait's constructor accepts only WebDriver as arguments, not WebElement.
I've set the implicitlyWait time, so it seems not a good idea to use try{} catch(NoSuchElementException e){}, 'cause I don't want to wait that long time for this element.
Here's the scenario:
There's one web page with a form containing many input tags. Each input tag has a format requirement.
A dynamic div tag would be present after this input tag when the format requirement is not satisfied.
As there're so many input tags, I create a general method like:
public WebElement txtBox(String name) {
return driver.findElement(By.name(name));
}
instead of creating a data member for each input tag.
Then I create a method isValid to check whether user inputs in some input are valid. All I should do in isValid is to check whether a div tag is present after inputboxToCheck, with code like this:
public boolean isValid(WebElement inputboxToCheck) {
WebElementWait wait = new WebElementWait(inputboxToCheck, 1);
try {
wait.until(ExpectedConditions.presenceOfElementLocated(By.xpath("./following-sibling::div")));
return false;
} catch (TimeOutException e) {
return true;
}
}
WebElementWait is an imaginary (not exist) class which works the same way as WebDriverWait.
The WebElementWait class as metioned above:
package org.openqa.selenium.support.ui;
import java.util.concurrent.TimeUnit;
import org.openqa.selenium.NotFoundException;
import org.openqa.selenium.WebElement;
public class WebElementWait extends FluentWait<WebElement> {
public final static long DEFAULT_SLEEP_TIMEOUT = 500;
public WebElementWait(WebElement element, long timeOutInSeconds) {
this(element, new SystemClock(), Sleeper.SYSTEM_SLEEPER, timeOutInSeconds, DEFAULT_SLEEP_TIMEOUT);
}
public WebElementWait(WebElement element, long timeOutInSeconds, long sleepInMillis) {
this(element, new SystemClock(), Sleeper.SYSTEM_SLEEPER, timeOutInSeconds, sleepInMillis);
}
protected WebElementWait(WebElement element, Clock clock, Sleeper sleeper, long timeOutInSeconds,
long sleepTimeOut) {
super(element, clock, sleeper);
withTimeout(timeOutInSeconds, TimeUnit.SECONDS);
pollingEvery(sleepTimeOut, TimeUnit.MILLISECONDS);
ignoring(NotFoundException.class);
}
}
It's the same as WebDriverWait, except that the WebDriver argument is replaced with WebElement.
Then, the isValid method:
//import com.google.common.base.Function;
//import org.openqa.selenium.TimeoutException;
public boolean isValid(WebElement e) {
try {
WebElementWait wait = new WebElementWait(e, 1);
//#SuppressWarnings("unused")
//WebElement icon =
wait.until(new Function<WebElement, WebElement>() {
public WebElement apply(WebElement d) {
return d.findElement(By
.xpath("./following-sibling::div[class='invalid-icon']"));
}
});
return false;
} catch (TimeoutException exception) {
return true;
}
}
I don't know if this help you, but it permits to wait element how much time do you want.
public WebElement findDynamicElement(By by, int timeOut) {
WebDriverWait wait = new WebDriverWait(driver, timeOut);
WebElement element = wait.until(ExpectedConditions.visibilityOfElementLocated(by));
return element;
}
findDynamicElement(By.xpath("//body") , 30);
A more universal variant of user2432405's solution would be using SearchContext type rather then WebElement:
public class SearchContextWait extends FluentWait<SearchContext> {
...
This allows to do waits on both WebDriver and WebElement similarly as the SearchContext interface is the ancestor of both WebDriver and WebElement. The isValid method needs adjustment too:
...
WebElement icon = wait
.until(new Function<SearchContext, WebElement>() {
public WebElement apply(SearchContext d) {
...
Unfortunately, you lose all conveniences of ExpectedConditions.xxxx() methods as they use the WebDriver interface internally.
I found this blog: Checking for an element – exists?, visible?, present? - https://jkotests.wordpress.com/2012/11/02/checking-for-an-element-exists-visible-present/
And it brought up the differences between exists, visible, and present.
exists? – Returns whether this element actually exists.
present? – Returns true if the element exists and is visible on the page
visible? – If any parent element isn’t visible then we cannot write to the element. The only reliable way to determine this is to iterate
up the DOM element tree checking every element to make sure it’s
visible.
Exists will tell you if what you are searching for is anywhere in the DOM; however, WebDriver does not seem to have a built in way to check if an element exists similar to plain driver.findElement(By.name(name)).
And, as explained in the blog, Exists is not the same as Present. So I can't use ExpectedConditions.presenceOfAllElementLocatedBy(By.cssSelector(cssSelector)
My solution: (looking for feedback here :)
public WebElement waitForElementExists(String selector, String timeout) {
Wait<WebDriver> wait = new WebDriverWait(driver, timeout);
WebElement element = wait.until(new Function<WebDriver, WebElement>() {
public WebElement apply(WebDriver driver) {
return driver.findElement(By.cssSelector(selector));
}
});
return element;
}