Selenium interaction with DOM seems extremely slow while doing couple of things in every page instantiation. Throughout the site we have visible spinner that indicates any outstanding API calls resolved or not. In summary I have three methods that make sure the stability of page before performing any action.
Check for the DOM ready state
Check for any outstanding JQuery calls
Check for loading spinners
All of these three are done as a part of the page object instantiation with following methods.
public static void waitForLoadingAllSpinnersAnywhere(final WebDriver driver){
final WebDriverWait wait = new WebDriverWait(driver, timeout);
wait.until(waitForDomReadyState());
wait.until(waitForjQueryToBeInactive());
List<WebElement> elements = wait.until(ExpectedConditions.presenceOfAllElementsLocatedBy(spinnersLoacator));
for(WebElement element: elements){
wait.until(invisibilityOfElementLocated(element));
}
}
private static ExpectedCondition<Boolean> waitForDomReadyState(){
return new ExpectedCondition<Boolean>() {
#Override
public Boolean apply(WebDriver d){
return ( ((JavascriptExecutor) d).executeScript("return document.readyState;").equals("complete"));
}
};
}
private static ExpectedCondition<Boolean> waitForjQueryToBeInactive(){
return new ExpectedCondition<Boolean>() {
#Override
public Boolean apply(WebDriver d){
return (Boolean) ( ((JavascriptExecutor) d).executeScript("return jQuery.active == 0;"));
}
};
}
public static ExpectedCondition<Boolean> invisibilityOfElementLocated(final WebElement element){
return new ExpectedCondition<Boolean>() {
#Override
public Boolean apply(WebDriver driver){
try{
return !element.isDisplayed();
} catch (NoSuchElementException | StaleElementReferenceException e){
// Returns true because the element is not present in DOM.
// The
// try block checks if the element is present but is
// invisible or stale
return true;
}
}
};
}
Taking an example of a page(say patient page) which has good number of API calls and fetches a lot of data. For a initial class instantiation it takes about 17s(log below). My Selenium knowledge says, the subsequent page instantiation should not take same or more time to check DOM ready state, or JQuery call or spinner waits since there is nothing changing at all. However, every time new page instantiate I see it takes same amount of time taken to check all these three. What's happening there? Does Selenium actually tries to interact with Server every time I do these or just interaction with the client is slow for some reason? If so, what could be the possible answer?
Console log
==== [[Finished waiting for 8 spinner elements found on widget [Patient] after [17] s]]
==== [[Start waiting for 8 spinner elements found on widget [Patient] ]]
==== [[Finished waiting for 8 spinner elements found on widget [Patient] after [17] s]]
==== Browser on [[[Patient]]]
==== [[Start waiting for 8 spinner elements found on widget [Patient] ]]
==== [[Finished waiting for 8 spinner elements found on widget [Patient] after [17] s]]
Environment:
Selenium 2.48
Firefox 38
I also tried with Selenium 2.52 and firefox 44 with same result
Selenium handles all the waiting on the client side with a request sent to the server for every evaluation until the condition is met.
It can quickly degenerate in case of high latency, especially if there are a lot of calls. Moreover some evaluations require a script injection which doesn't help either.
So the best way to improve the performance in your case would be to use a single asynchronous JavaScript call:
public static void waitForLoadingAllSpinnersAnywhere(final WebDriver driver) {
const String JS_WAIT_SPINNERS =
"var callback = arguments[0]; " +
"(function fn(){ " +
" if (document.readyState == 'complete' && jQuery.active == 0) { " +
" var elts = $('.spinners'); " +
" if (elts.length == 8 && !elts.is(':visible')) " +
" return callback(); " +
" } " +
" setTimeout(fn, 60); " +
"})();";
((JavascriptExecutor)driver).executeAsyncScript(JS_WAIT_SPINNERS);
}
To initialize the timeout:
driver.manage().timeouts().setScriptTimeout(30, TimeUnit.SECONDS);
Your test seems to be all non-native calls and so Firefox should work for you but I am surprised that the Firefox native call to driver.navigate() even worked for you to get to the inital page if you were using 44 and 48. It is well known that 31.6.0 was the last supported native Firefox version. So, I would say you should use Chrome until you figure this out.
But, to answer your thing about slowness. The way you wrote your code, you are highly dependent on jQuery and I would imagine your are having an issue with your calls to jQuery code being delayed, which propagates out to your Selenium test, and further impacted by the fact that your looping through multiple spinners. One thing I have noticed before is that if a page is busy running ajax calls, then your Selenium calls with JavascriptExecutor might have to wait in line for those to give up bits of processor time.
What would I do differently?
Well, I would write my spinner waits to operate on the DOM instead of calling JavascriptExecutors to jQuery. Maybe in your case, this is not an option, but I think a well thought out plan can improve the efficiency of your page ready workflow.
Related
I have a registration form that register many users ,the problem in the first loop when I click on create it go too fast and didn't register the first one and resister the second ...,
so I use Thread.sleep(500);
I want to avoid using sleep
is there a way to do it
here is my code
#Given("user on registration page and create users")
public void user_on_registration_page_and_create_users() throws InterruptedException {
System.out.println(userLoginPageDataList);
for(UserLoginPageData userLoginPageData:userLoginPageDataList){
userRegistrationPage.init();
logger.info("*************************************** init the driver && go to registration page http://localhost:4200/register");
logger.info("*************************************** reading line "+userLoginPageData.getRowIndex() +" from Excel file");
userRegistrationPage.enterUserLogin(userLoginPageData.getUsername());
userRegistrationPage.enterUserPassword(userLoginPageData.getPassword());
userRegistrationPage.enterUserRole(userLoginPageData.getUserRole());
userRegistrationPage.clickOnCreate();
// Thread.sleep(500);
logger.info(userLoginPageData.getUsername()+" is registred");
}
}
You can use explicit(smart) wait.
WebDriverWait w = new WebDriverWait(driver, 5); //will wait 5 seconds most , but if element is visuble in the third second it will wait 3 sec.
w.until(ExpectedConditions.visibilityOfElementLocated(By.id("submit_btn")));
read more on When to use explicit wait vs implicit wait in Selenium Webdriver?
One of the possible solutions (when you work with PageFactory) is to implement your own Locator that can be extended from AjaxElementLocator.
Say you have a form and the form has some noticeable property saying that it is ready to accept the input (this might be some button state or displaying some label, etc).
So you can initialize your page object in the way its fields will be "available" if that condition is met.
This can be achieved using your custom Locator/LocatorFactory in your PageFactory.init().
For example here is the form of two fields. The condition saying it is ready for interaction is then create button is enabled:
class MyForm {
#FindBy(id = "user")
WebElement user;
#FindBy(id = "create")
WebElement create;
public MyForm(SearchContext searchContext){
PageFactory.initElements(field -> new AjaxElementLocator(searchContext, field, 10){
#Override
protected boolean isElementUsable(WebElement element) {
return create.isEnabled();
}
}, this);
}
}
Unless create button is enabled any attempt to invoke fields methods would be failing and the script will fail in 10 seconds of retries.
More details about how you use the conditions with page objects you can find in this post.
I have a table with several rows. Some of these rows may have a specific element and others may not. For sure some will and some won't.
I find the row and have it into a WebElement. Now to see whether an element is there I do the following (assume xp = ".//someelement)
List<WebElement> eles = row.findElements(By.xpath(xp));
if (eles.size() == 0) {
// element is not there
} else {
// element is there
}
This is fine when the element is present. When it is not, it takes like 30 seconds or a minute to figure out that it is not there. If called often this can really slow down the test. I can do
try {
WebElement ele = row.findElement(by.xpath(xp));
} catch (Exception ex) {
// element is not there
}
using a more detailed Exception. This works fine too but same problem. It waits a minute or half a minute.
Is there a way to check more quickly whether an element is present or not? If it were relative to driver (driver.findElementBy()) instead of an element (row.findElementBy()) I think I might know how to do it.
This is Java.
In your first example where you have a List of Elements you are not trying to locate one element; but several (let's say a collection of rows instead of one row). The second element ele is finding (or trying to find) a specific item (let's say 1 row). Hence, ideally you should say in your comments that some elementS were not there for eles . Nevertheless, the time issue is probably down to an implicit or explicit wait. Read here for more.
I prefer the first way where you check for a collection of elements (so you can aim it at a xpath and find all the tags included (or none at all). Ideally though you should go for an explicit wait.
Here is the method for waiting, it will return true/or false based on if the element was present during the polling time (10sec for example); worth noting that if the element is found as present earlier than the 10sec limit the loop will break and will return true. Of course you can play with timeOut to achieve the desired result; don't go to fast though (like 2 sec) otherwise you are risking your test occasionally failing because the tables were not loaded yet:
public boolean waitForElement(String elementXpath, int timeOut) {
try{
WebDriverWait wait = new WebDriverWait(driver, timeOut);
boolean elementPresent=wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath(elementXpath)).isDisplayed());
System.out.printf("%nElement is present [T/F]..? ")+elementPresent;
}
catch(TimeoutException e1){e1.printStackTrace();elementPresent=false;}
return elementPresent;
}
I'm guessing that you are already using an explicit wait of 30sec for all of your findElement attempts hence the discrepancy.
Hope the above helps,
Best of luck!
Another option is to use WebDriverWait (explicit waits) rather than implicit ones.
This basically makes it so your tests will only wait a long time when you tell them too, otherwise they'll instantly fail if they don't find the elements you're looking for.
Adapted from slide52 of tourdedave's slideshare
// Use this class whenever you have to access the driver
// And you should only have to setDriver in a BeforeMethod when setting up.
// This method shows how to do it with a single browser
// This could be converted quite easily to threadlocals for parallel test runs
public class DriverManager {
private final WebDriver driver;
public static WebDriver getDriver() {
return driver;
}
public static setDriver(WebDriver driver) {
DriverManager.driver = driver;
}
public class WaitUntil {
public static Boolean displayed(By locator, Integer... timeout) {
try {
waitFor(ExpectedConditions.visibilityOfElementLocated(locator),
(timeout.length = 0 : null ? timeout[0];
} catch (TimeoutException exception) {
return false;
}
return true;
}
// add additional methods you want to wait for
// Look at the source of the ExpectedConditions class to see how to create
// your own
// e.g. presence of element
// number of results from locator changes
// element done moving
// url changes, etc.
private static void waitFor(ExpectedCondition<WebElement> condition, Integer timeout) {
timeout = timeout != null ? timeout[0] : 5; //default timeout if no value given
WebDriverWait wait = new WebDriverWait(driver, timeout);
wait.until(condition);
}
}
Then in any class you can
By submitButtonBy = By.cssSelector(".submit);
use WaitUntil.displayed(submitButtonBy);
And it will wait for 5 seconds. If you want to wait for 10:
use WaitUntil.displayed(submitButtonBy, 10);
The nice thing about making a class with a bunch of methods like this is it's easy to add additional exceptions so you can choose to return false if there's a stale element or something else, rather than have to deal with a try catch in page classes or test classes.
In my learning curve I have been looking at the right way to wait for an element to be loaded and you get lots of pages on google.
Got down to 2 but in my view Method2(ExpectedConditions.ElementIsVisible) is more elegant and does what method1 is trying to achieve do you agree or is there a better way?
Method 1
public static IWebElement FindElement(this IWebDriver driver, By by, int timeoutInSeconds)
{
if (timeoutInSeconds > 0)
{
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(timeoutInSeconds));
return wait.Until(drv => drv.FindElement(by));
}
return driver.FindElement(by);
}
Method 2
public static IWebElement FindElement(this IWebDriver driver, By by, int timeoutInSeconds)
{
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(timeoutInSeconds));
var element = wait.Until(ExpectedConditions.ElementIsVisible(by));
return element;
}
Any suggestions or improvements also what happens if "NoFoundElement exception is thrown is it already handled or should I ignore it?
I would say yes, "Method 2" is the preferred way of doing it. Simply because if something is already implemented in WebDriver, there's no need for you to reimplement it in your testing framework.
As to your question regarding the NotFoundException: If the condition that you're waiting for isn't met after the specified timeout, the WebDriverWait will raise a WebDriverTimeoutException. Depending on the condition that you wanted to wait for, the WebDriverTimeoutException will have an inner exception with more details. If for example you use ExpectedConditions.ElementIsVisible(By.Id("myid")) and the element couldn't be located at all, the inner exception will be a NoSuchElementException. If the element could be located but isn't visible after the given timeout you'll just get the WebDriverTimeoutException.
Depending on what you want to do once you're sure that the element is "there", you can also use different ExpectedConditions. If the element you're waiting for is a button and you want to click on it, you can use ExpectedConditions.ElementToBeClickable as this will not only wait for the element to get loaded into the DOM and visible, but also wait for the element to get enabled.
I know that this question was asked many times before, but I still couldn't find a solution that works for me. When I run my tests with Selenium WebDriver most of the times they fail with "NoSuchElementException". I tried using Explicit and Implicit Waits but nothing seems to work. So, is there any other way besides using Waits in which I can make my tests more reliable?
I'm using selenium-java-2.31.0 with FirefoxDriver. Below are some samples of code I tried to make my tests more reliable:
public void waitAndClickElement(WebDriver driver, final By selector) {
Wait<WebDriver> wait = new FluentWait<WebDriver>(driver)
.withTimeout(50, TimeUnit.SECONDS)
.pollingEvery(5, TimeUnit.SECONDS)
.ignoring(NoSuchElementException.class);
WebElement elementToClick = wait
.until(new Function<WebDriver, WebElement>() {
public WebElement apply(WebDriver driver) {
return driver.findElement(selector);
}
});
waitForElementVisible(driver, selector);
elementToClick.click();
}
..and this:
public WebElement waitForElementPresent(WebDriver driver, final By selector){
Wait<WebDriver> wait = new FluentWait<WebDriver>(driver)
.withTimeout(70, TimeUnit.SECONDS)
.pollingEvery(5, TimeUnit.SECONDS)
.ignoring(NoSuchElementException.class);
WebElement elementToClick = wait
.until(new Function<WebDriver, WebElement>() {
public WebElement apply(WebDriver driver) {
return driver.findElement(selector);
}
});
return elementToClick;
}
...and this:
WebDriverWait wait = new WebDriverWait(driver, 50);
WebElement user_name = wait.until(visibilityOfElementLocated(By.xpath("//*#id='userName']")));
...and this:
driver.manage().timeouts().implicitlyWait(50, TimeUnit.SECONDS);
...and finally one of the tests that I try to make more reliable:
#Test
public void test1{
waitAndClickElement(driver, By.xpath("//*[#id='linkLogIn']"));
waitForElementPresent(driver, By.xpath("//*[#id='userName']")).sendKeys("name");
waitForElementPresent(driver, By.xpath("//*[#id='inputEmail']")).sendKeys("email#gmail.com");
waitForElementPresent(driver,By.xpath("//*[#id='resetPassword']")).click();
assertTrue(isElementPresent(By.xpath("//*[#id='moduleMain']")));
}
Thank you!
Try below custom method. It works fine for me,
public boolean waitForElementToBePresent(By by, int waitInMilliSeconds) throws Exception
{
WebDriver driver = getDriver();
int wait = waitInMilliSeconds;
int iterations = (wait/250);
long startmilliSec = System.currentTimeMillis();
for (int i = 0; i < iterations; i++)
{
if((System.currentTimeMillis()-startmilliSec)>wait)
return false;
List<WebElement> elements = driver.findElements(by);
if (elements != null && elements.size() > 0)
return true;
Thread.sleep(250);
}
return false;
}
Use it like,
waitForElementToBePresent(By.id("linkLogIn", 5000);
driver.findElement(By.id("linkLogIn")).click();
WebDriver is perfectly stable if you handle exceptions properly. The problem is that the methods of ExpectedConditions class don't handle the exceptions for you although most people will reply to your question as if it does.
You can try my method if you want. This method returns in between 0 to 90 seconds, depending on the scenario. You may prefer to alter this method a little, but it should work. The important concepts here are:
1. Use the new FluentWait class with the .ignoring method (or .ignoreAll() ).
2. Use findElement() BUT make sure you catch (and nicely handle) the possible
exceptions (that you are ignoring in the wait).
3. Use a loop to retry after exceptions but govern that by either time or
# of tries.
And the code:
public WebElement getElementByLocator( final By locator ) {
LOGGER.info( "Get element by locator: " + locator.toString() );
final long startTime = System.currentTimeMillis();
Wait<WebDriver> wait = new FluentWait<WebDriver>( driver )
.withTimeout(30, TimeUnit.SECONDS)
.pollingEvery(5, TimeUnit.SECONDS)
.ignoring( NoSuchElementException.class )
.ignoring( StaleElementReferenceException.class ) ;
int tries = 0;
boolean found = false;
WebElement we = null;
while ( (System.currentTimeMillis() - startTime) < 91000 ) {
LOGGER.info( "Searching for element. Try number " + (tries++) );
try {
we = wait.until( ExpectedConditions.visibilityOfElementLocated( locator ) );
found = true;
break;
} catch ( StaleElementReferenceException e ) {
LOGGER.info( "Stale element: \n" + e.getMessage() + "\n");
} catch ( NoSuchElementException nse ) {
LOGGER.info( "No such element: \n" + nse.getMessage() + "\n");
}
}
long endTime = System.currentTimeMillis();
long totalTime = endTime - startTime;
if ( found ) {
LOGGER.info("Found element after waiting for " + totalTime + " mill." );
} else {
LOGGER.info( "Failed to find element after " + totalTime + " mill." );
}
return we;
}
When you run findElement, you will get an error when it's not found. This occurs for one of three reasons:
Your selector is wrong
If the selector is wrong, the best thing to do debug until you get to that spot and pause test execution. Then use the console to figure out the correct selector to find the element.
The element isn't there
You may notice in your first action that the element you're looking for isn't actually there. In that case, find out why you're in that wrong state and fix it. If you're expecting the element to not be there, Here is a great C# example on how to extend your IWebElement object to allow for a .Exists() method.
The element is late
Determining if the element is just late is easy. Run the test normally once and then run in debug mode stepping over each step manually. If your normal test run fails while your manual steps work, you know you found your issue. Typically the issue is due to AJAX loads not occurring on page load. In these instances, a good webdev will typically add some kind of spinner image that you can easily search for. I created a helper method called WaitForPageLoad() that first waits till page load, then verifies that the spinner isn't present, then waits again for page load to complete. You want 2 page load waits because a modal will spin then load while a new page load will load then spin. Finally, the page is complete, your element will be present.
I have faced with the same type of problem, while using WebDriver with C#.
I can propose 2 different ways on how you can avoid(not completely, but minimize) NoSuchElementException in your tests:
First of all you should figure out how your application works - does it use a lot of Ajax and other asynch. requests/responses. Then you can use explicit wait for every element, which can not be located at once.
You can write your own implementation of WebElement class based on Selenium WebDriver WebElement class.
Main idea - everytime you will use your webelement it will relocated - so you will not be worry about NoSuchElement or StaleElementException.
Did you try to catch element by element without all theses wait and wait.until?
simply like : WebElement username = driver.findelement(By.id("userName"));
Can you drop your html by the way ?
EDIT:
What i can suggest is :
protected void sleep(int i) {
driver.manage().timeouts().implicitlyWait(i, TimeUnit.SECONDS);
}
#test
void test(){
driver.findElement(By.id("linkLogIn")).click(); sleep(6);
driver.findElement(By.id("userName")).sendKeys("user"); sleep(1);
driver.findElement(By.id("inputEmail")).sendKeys("mail#gmail.com"); sleep(1);
driver.findElement(By.id("resetPassword")).click(); sleep(10);
Assert.assertTrue(isElementPresent(By.id("moduleMain")));
}
Well your code tells me that you are only waiting until the element is present.
waitForElementPresent(driver, By.xpath("//*[#id='userName']")).sendKeys("name");
waitForElementPresent(driver, By.xpath("//*[#id='inputEmail']")).sendKeys("email#gmail.com");
It tells me nothing that you clicked the field, then using sendkeys to input the text.
How about adding click
waitForElementPresent(driver, By.xpath("//*[#id='userName']"));
driver.findElement(by.id ="userName").click();
driver.findElement(by.id ="userName").sendKeys("name");
The problem is the mouse focus on webdriver, it need to be focused in appropriate field AFAIK
I'm using Selenium Webdriver and have run into the following issue with my app under test.
The app has multiple pages each with an appropriate ".page-title" element which contains the name of the page (e.g. "Other Documents"). As the tests navigate around the app they assert that the browser is on the expected page using these elements before doing other stuff.
The issue is that if you click a button in the app which performs an action, then check that you're on the right page (e.g. check page-title element displays correct text), Webdriver doesn't wait for the action to be performed (e.g. new page load), it returns straight away and the test fails.
If you add a short thread sleep (500ms) between performing the action and checking you're on the right page, then you get StaleElementReferenceException (some of the time) and if you add a large thread sleep the test passes (but not quite all the time).
My aim is to reduce the flakiness of the tests, does anyone have a suggestion as to how I can do this without Thread.sleep?
instead of inserting thread.sleep method explicity
do try the WebDriver in built Implicitwait method..(C# code snippet)
Driver.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(Max_Time_Limit));
This makes WebDriver to wait till the element is visible/available. In case if it finds elements before the Max_Time_Limit, it snaps out of sleep mode and resumes the execution.
So no hassle of waiting till the Hard bound Max_Time_Limit.This way it helps speeding up your execution Time as well.
I hope this helps...All the best :-)
Try using this wait: using this you can wait for max time 15 secs/wait for the expected condition to be true i.e. wait for some element to be present.
You can give the xpath of some element on the next page, when that element is visible then the next step will be executed.
WebDriverWait wait = new WebDriverWait(driver, 15);
wait.until(ExpectedConditions.elementToBeClickable(By.xpath("\xpath of some element on the next page")));
//Assert page title
driver.getTitle();
use fluentWait mechanism . Considered to be a robust approach. As documentation on fluent wait gives:
An implementation of the Wait interface that may have its timeout and polling interval configured on the fly.
Each FluentWait instance defines the maximum amount of time to wait for a condition, as well as the frequency with which to check the condition. Furthermore, the user may configure the wait to ignore specific types of exceptions whilst waiting, such as NoSuchElementExceptions when searching for an element on the page.
details you can get here
here is the code of method I use:
public WebElement fluentWait(final By locator){
Wait<WebDriver> wait = new FluentWait<WebDriver>(driver)
.withTimeout(30, TimeUnit.SECONDS)
.pollingEvery(5, TimeUnit.SECONDS)
.ignoring(NoSuchElementException.class);
WebElement foo = wait.until(
new Function<WebDriver, WebElement>() {
public WebElement apply(WebDriver driver) {
return driver.findElement(locator);
}
}
);
return foo; } ;
String xPathElement="...blablabla...";
fluentWait(By.xpath(xPathElement)).click();
//fluentWait(By.xpath(xPathElement)).getText();
Hope it works for you.
You can use Selenium's ExpectedCondition.
The code below (written in JAVA) waits for a maximum of inTimeout seconds for the element you want to appear. If the element appears sooner, it ends the wait.
public static void wait(WebDriver b,long inTimeout) {
final SlnDriver browser=b;
final long NO_LOADING_TIMEOUT = inTimeout;
class HasCondition implements ExpectedCondition<Boolean> {
#Override
public Boolean apply(WebDriver d) {
Boolean expected=false;
WebElement e = browser.findElement(By.xpath("blabla"));
if (e.getText().contains("TextYouWant")) {
expected= true;
break;
}
}
return expected;
}
}
}
for (;;) {
try {
new WebDriverWait(browser, NO_LOADING_TIMEOUT).until(new HasCondition());
} catch (TimeoutException e) {
return;
}
}
}