Google Analytics

Search

To search for specific articles you can use advanced Google features. Go to www.google.com and enter "site:darrellgrainger.blogspot.com" before your search terms, e.g.

site:darrellgrainger.blogspot.com CSS selectors

will search for "CSS selectors" but only on my site.


Showing posts with label Selenium. Show all posts
Showing posts with label Selenium. Show all posts

Wednesday, August 24, 2016

The difference between NotFoundException, NoSuchElementException and StateElementReferenceException

Been a while since I posted something here...

Recently, someone on the WebDriver Google Group asked what the difference between NotFoundException, NoSuchElementException and StateElementReferenceException are. Here is the answer I posted:

The NotFoundException is a super class which includes the subclass NoSuchElementException. The known direct subclasses of NotFoundException are: NoAlertPresentException, NoSuchContextException, NoSuchElementException, NoSuchFrameException and NoSuchWindowException. So if I want one catch statement to catch all five exception and they will all be handled the same way then I can just handle NotFoundException. But if I want to handle any of these five exceptions differently, I can catch the more specific subclass.

The NoSuchElementException is thrown when the element you are attempting to find is not in the DOM. This can happen for three reasons. 

The first is because the element does not exist and never will. To fix this, change your findElement to be correct.

The second is that you need to do something on the page to make the element appear. For example, the user selects Country and javascript populates a City field. If you attempt to look for a city before you select a country, the city you are looking for does not exist and you get a NoSuchElementException. To fix this you have to make sure the steps in your test are correct.

The third is that the element is generated by javascript but WebDriver attempts to find the element before the javascript has created it. The fix for this is to use WebDriverWait to wait for the element to appear (visibility and/or clickable).

StaleElementReferenceException is when you find an element, the DOM gets modified then you reference the WebElement. For example,

WebElement we = driver.findElement(By.cssSelector("#valid"));
// you do something which alters the page or a javascript event alters the page
we.click();

A classic example if this might be:

List<WebElement> listOfAnchors = driver.findElements(By.tag("a"));
for(WebElement anchor : listOfAnchors) {
anchor.click();
System.out.println(driver.getTitle());
driver.navigate.back();
}

This code will get all the anchor elements into a list. Lets say there are 5 anchors on the page. The list now has 5 WebElement references. We get the first reference and click it. This take us to a new page. This is a new DOM. We print the title of the new page. Then we use back() to go back to the original page. The DOM looks just like the same DOM but it is a different DOM. So now all the references in the list a stale. On the second iteration, it gets the second reference and clicks it. This will throw a StaleElementReferenceException.

More difficult to debug is:

WebElement we = driver.findElement(...);
// javascript event gets fired by the website
we.click();



Sometimes this will throw a StaleElementReferenceException but sometimes the timing will be different and the click will work. I've seen many people have this intermittent problem. They add more code which doesn't fix the problem. The extra code just changes the timing and hides the problem. The problem comes back a few days later. So they add more random code. It looks like they fixed the problem but they just changed the timing. So if you get a StaleElementReferenceException and it is not clear why, it is probably this problem and you need to figure out how to make the findElement and click atomic.

Friday, January 8, 2016

Difference between using current node (.) and text() function in XPath for Selenium locators


Recently someone asked about using the partial link text to find an anchor with an IMG tag in the middle of the text. The HTML snippet was:

<a href="something.html">
&nbsp;
<img src="filename.gif">
&nbsp;
partial link text
</a>
The initial attempt for a locator was:
"//a[contains(text(),'partial link text')]"

Normally I would expect this to work. However, the text() function does not seem to find it. Peter Jeffery Gale (thanks Peter) noticed that the following locator did work:
"//a[contains(.,'partial link text')]"
The . notation is the current node in the DOM. This is going to be an object of type Node. I'm guessing that the Node is getting cast to a string. Something similar to:
"//a[contains(string(.),'partial link text')]"
The end result seems to be that getting the entire Node, convert it to a string and scanning the string for a substring always works. Using the XPath function text() to get the text for an element only gets the text up to the first inner element. If the text you are looking for is after the inner element you must use the current node to search for the string and not the XPath text() function.


Thursday, September 25, 2014

Silencing ChromeDriver with WebDriver

While setting up a test environment today we decided to have the tests running on the same machine as the build radiator.

A build radiator takes up the entire display. It shows a green bar for each job on the build server. If someone checks in a change and it breaks a test, the bar turns red and everyone stops to fix the build.

The consequence of this is that the build radiator has to be visible to everyone in the room. Having a browser open on the display is not an option.

So we need to run our WebDriver tests without showing the browser or any other output. Our build server is running Linux. So we have WebDriver tests. We can run them from the command line using something like:
java org.testng.TestNG testng.xml
where testng.xml is a TestNG test suite example. When we run it as this we see the browser open and the tests executing. The tests were written using ChromeDriver. When we run this on the build radiator however, we don't want the browser opening. The solution is actually quite easy for Linux. We use an application called Xvfb:
xvfb-run --server-args="-screen 0 1600x1200x24" java org.testng.TestNG testng.xml
The command xvfb-run will run the application using the X Virtual FrameBuffer. The --server-args lets us pass arguments to the server. The "-screen 0" tells xvfb to use screen 0. The "1600x1200x24" tells xvfb to make the virtual display 1600 by 1200 with 24 bit depth. If your application has to work on 1024 by 768 and 16 bit colour then you can use "1024x768x16".

When you execute this you will not see the browser open. It almost seems like nothing is happening. The only thing you will see is the output from TestNG (a dot for a pass, an I for an ignore and an F for a failure) and the output from chromedriver. What if you want to look at the logs and see just the output from TestNG; not interlaced with output from chromedriver?

This requires a few changes to the creation of the WebDriver object. Normally, you might have something like:
System.setProperty("webdriver.chrome.driver", "./chromedriver");
WebDriver driver = new WebDriver();
but this outputs chromedriver log information to the screen. You could use:
System.setProperty("webdriver.chrome.driver", "./chromedriver");
System.setProperty("webdriver.chrome.args", "--disable-logging");
WebDriver driver = new WebDriver();
This will stop most the output but you will see the header for when chromedriver starts up:
Starting ChromeDriver (v2.9.248307) on port 9515
So how do you get rid of this? I was digging through the code for chromedriver (remember it is open source) and I found some code where it was checking for the property webdriver.chrome.silentOutput. If this was set to true then it would run with the silent flag set to true. So I tried:
System.setProperty("webdriver.chrome.driver", "./chromedriver");
System.setProperty("webdriver.chrome.args", "--disable-logging");
System.setProperty("webdriver.chrome.silentOutput", "true");
WebDriver driver = new WebDriver();
Sure enough that did it. Complete silence from chromedriver.

Friday, July 11, 2014

Interactive Ruby Shell

My current project uses Ruby and has a web testing component to it. The obvious choice for testing a web application with Ruby would be Selenium-WebDriver.

If you are familiar with Ruby you should be familiar with the Interactive Ruby Shell or irb.

If I enter irb at a command prompt I am placed at the Interactive Ruby Shell:

1.9.3-p547 :001 > 
Once you are at the Interactive Ruby Shell you can try things to see how they work. In a compiled language like Java you would have to compile the code into class files then execute them. With Ruby you can actually type the lines out and see what happens immediately. For example, to do the basic Selenium example I can enter:
require 'selenium-webdriver'
driver = Selenium::WebDriver.for :chrome
At this point a chrome browser should open. If it does not possible problems might be if chromedriver isn't in your PATH. Before you open the command prompt make sure that chromedriver is in your PATH. If it is in the PATH and you run irb then the Ruby code above should open a Chrome browser. It also assumes you have Chrome installed.

Once the browser opens you can do:
driver.methods - Object.methods
All objects in Ruby have a methods method. All objects also inherit Object. So the line above says to give me all the methods for driver and subtract all the Object methods from the list. So what will remain are the Selenium WebDriver methods:

 => [:save_screenshot, :screenshot_as, :action, :mouse, :keyboard, :navigate, :switch_to, :manage, :get, :current_url, :title, :page_source, :quit, :close, :window_handles, :window_handle, :execute_script, :execute_async_script, :first, :all, :script, :[], :browser, :capabilities, :ref, :find_element, :find_elements]
From this list I can see all the things I can do with driver, an instance of Selenium WebDriver. So now that I have an instance of WebDriver and I have the browser open I can enter:
driver.get 'http://www.google.com'
text_field = driver.find_element :id => 'gbqfq'
text_field.send_keys 'Selenium'
puts driver.title
driver.quit
As I type these lines I will see the browser switch to http://www.google.com, sending the text 'Selenium' to the browser and the browser closing (driver.quit).

Wednesday, April 30, 2014

WebDriverWait versus FluentWait


The WebDriver code waits for the page to load but today we have dynamic websites using things like angularjs and Ajax. WebDriver does not wait for the javascript to execute. So you might have something like:
<a href="http://{{env}}.company.com/foo/bar.html">{{env}}</a>
When the page loads, javascript runs and converts {{env}} to some defined value. So on the test environment {{env}} might convert to test, on stage it converts to stage and on production it converts to www. However, WebDriver will not wait for the javascript to make the substitution.  The end result is clicking the element will cause WebDriver to go to "{{env}}.company.com" when we really wanted it to wait and go to "www.company.com".

So how do we make WebDriver wait for the variable to be updated by javascript?

The answer is WebDriverWait or FluentWait. Below are examples of how you can use WebDriverWait and FluentWait to wait for the javascript to finish:
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.support.ui.*;
import java.util.Arrays;
import static java.util.concurrent.TimeUnit.*;
import static org.junit.Assert.assertFalse;

public class TestingFluentWaitAndWebDriverWait {
  WebDriver driver;
  WebElement button;

  @Before
  public void setUp() {
    System.setProperty("webdriver.chrome.driver", "/Users/ThoughtWorks/IdeaProjects/SeleniumTesting/chromedriver2");
    DesiredCapabilities dc = DesiredCapabilities.chrome();
    dc.setCapability("driver.switches", Arrays.asList("--start-maximized"));
    driver = new ChromeDriver(dc);
    driver.manage().window().maximize();
    driver.get("http://www.google.com");
    driver.findElement(By.cssSelector("#gbqfq")).sendKeys("\"Darrell Grainger\"");
    button = driver.findElement(By.cssSelector("#gbqfb"));
  }

  @Test
  public void testWebDriverWait() {
    new WebDriverWait(driver, 3).until(ExpectedConditions.visibilityOf(button)).click();
    assertMyBlogLinkExists();
  }

  @Test
  public void testFluentWait() {
    new FluentWait<WebElement>(button).withTimeout(3, SECONDS)
        .pollingEvery(100, MILLISECONDS)
        .until(new Function<WebElement, Boolean>() {
          public Boolean apply(WebElement w) {
            return w.isDisplayed();
          }
        });
    button.click();
    assertMyBlogLinkExists();
  }

  @Test
  public void testFluentWaitPredicate() {
    new FluentWait<WebElement>(button).withTimeout(3, SECONDS)
        .pollingEvery(100, MILLISECONDS)
        .until(new Predicate<WebElement>() {
          public boolean apply(WebElement w) {
            return w.isDisplayed();
          }
        });
    button.click();
    assertMyBlogLinkExists();
  }

  private void assertMyBlogLinkExists() {
    final String linkText = "QA & Testing";
    try {
    new FluentWait<WebDriver>(driver).withTimeout(3, SECONDS)
        .pollingEvery(100, MILLISECONDS)
        .ignoring(NoSuchElementException.class)
        .until(new Function<WebDriver, Boolean>() {
          public Boolean apply(WebDriver d) {
            WebElement link = d.findElement(By.linkText(linkText));
            return link.isDisplayed();
          }
        });
    } catch(TimeoutException te) {
      assertFalse(String.format("Timeout waiting for link: '%s'", linkText), true);
    }
  }

  @After
  public void tearDown() {
    if(driver != null)
    driver.quit();
  }
}
All three tests will result in the same outcome.

Tuesday, April 29, 2014

Creating good locators

I am constantly seeing people look for tools which will create locators. I think a lot of us starting by using Firebug to examine the DOM and select locators. With Chrome I can now right-click on an element and select Inspect Element.

Now of this really helps figure out what is a good locator. So people are still looking for a tool which will tell them the best locator for an element.

If I go to http://www.google.com and inspect the Google Search button I will see:
<button class="gbqfba" aria-label="Google Search" id="gbqfba" name="btnK">
<span id="gbqfsa">Google Search</span>
</button>
We might think that the Selenium locator By.cssSelector("#gbqfba") might be a good locator. One definition of a good locator is that it needs to be consistent and unique. For years the Google Search button has had the id attribute of gbqfba. So it is consistent. It is an id and according to the HTML standard there can be only one element for each id value. So it will be unique.

But is it a good locator? I will argue that it is not a good locator. If I had the following Selenium code:
driver.findElement(By.cssSelector("#gbqfq")).sendKeys("Darrell Grainger");
driver.findElement(By.cssSelector("#gbqfba")).click();
assertThat(driver.findElement(By.xpath("//a[contains(text(),'Testing')]")).getText(), startsWith("QA"));
it will run consistently over years. But what happens if something changes and I need to refactor this code? The first line is sending my name to something with id='gbqfq', it is clicking something with id='gbqfba'. What if we changed the HTML to have:
<button class="gbqfba" aria-label="Google Search" id="gbqfba" data-qa='search button'>
<span id="gbqfsa" data-qa='text for search button'>Google Search</span>
</button>
then the Selenium code could read:
driver.findElement(By.cssSelector("[data-qa='search input box']")).sendKeys("Darrell Grainger");
driver.findElement(By.cssSelector("[data-qa='search button']")).click();
assertThat(driver.findElement(By.cssSelector("[data-qa='first search result']")).getText(), startsWith("QA"));
Now if I'm looking at the code and have no knowledge of the website being automated, it should be easy for me to figure out that the first element is the search input box, the second element is the search button and the last element is the first search result.

Essentially, by adding the data-qa attribute the elements we are trying to locate:
  • easier to locate the element
  • easier to read the code when running/refactoring it
  • make the code less brittle
A possible complaint would be that adding these elements could interfere with other elements on the page. The whole 'data-' syntax is a standard for HTML5. If there is a change someone is going to use data-qa then you can use data-foo where foo is the name of your application or your company name or your name.

Another possibility is that it is not unique. It only has to be unique to the given page. If the value is incredibly descriptive then it should be unique. You make the value of data-qa reflect the information necessary for someone testing the application manually then it should be unique. If it is not unique then how would a manual tester find THE element if its description was not unique?

I also considered adding more data to the page would increase what the CSS and XPath engines had to index more information. So I tried loading up page with these attributes added and could not measure any noticeable impact on the performance.

The only other concern I can image is if I add too much data or too verbose descriptions on the data-qa values then I could increase the amount of data transmitted. More bytes transmitted might impact performance of the web page. Wasn't able to see any significant impact in this area but your mileage might vary. So this is something to watch out for.

Thursday, November 28, 2013

Opening two windows for one WebDriver

I'm currently testing a project with two applications. The first is an administrator tool for updating/modifying the customer site and the second is the actual customer site.

There are three ways to test a project like this. The first is creating one instance of WebDriver and access each application one at a time, e.g.

WebDriver driver = new ChromeDriver();
driver.get(adminToolURL);
// do some admin stuff
driver.get(customerSiteURL);
// do some site stuff

The second would be to create two instances of WebDriver. One to access the admin tool and the other to access the customer site, e.g.

WebDriver adminDriver = new ChromeDriver();
adminDriver.get(adminToolURL);
WebDriver driver = new InternetExplorerDriver();
driver.get(customerSiteURL);
// do some admin stuff with adminDriver
// do some site stuff with driver

The nice thing about this scenario is that you might have different browser requirements. The admin tool only has to work with one browser and the customer site has to work with a variety of browsers. So I can hard code the adminDriver to one browser and configure the test framework to change the browser. Run 1, InternetExplorerDriver; run 2, ChromeDriver; run 3, SafariDriver; etc.

The third way is to create one instance of WebDriver and open two windows. If you use the driver.get() method, it will open the site in the current window. To open a second window you'll need to use something in Selenium to force a second window. The trick is to use JavascriptExecutor to run javascript which opens a second window, e.g.

WebDriver driver = new ChromeDriver();
driver.get(adminToolURL);
Set<String> windows = driver.getWindowHandles();
String adminToolHandle = driver.getWindowHandle();
((JavascriptExecutor)driver).executeScript("window.open();");
Set<String> customerWindow = driver.getWindowHandles();
customerWindow.removeAll(windows);
String customerSiteHandle = ((String)customerWindow.toArray()[0]);
driver.switchTo().window(customerSiteHandle);
driver.get(customerSiteURL);
driver.switchTo().window(adminToolHandle);

The first two lines are straight forward and open a window with the admin tool. The next few lines does the following: get a list of the currently open windows, save the window handle for the admin tool window, open a new window using executeScript, get a second list of the currently open window (this will be the same as the first list PLUS the new window). Next remove all the original windows handles from the second list. This should leave the second list (customerWindow) with only one window handle, e.g. the new window. The last three lines show you how to switch between the customer site window and the admin tool window.


Thursday, October 31, 2013

WebDriverWait

In an older article I wrote about a method I created called waitForElement. After looking through some of the WebDriver code I found WebDriverWait. You should read the original article before you read this one.

The WebDriverWait class extends FluentWait<WebDriver> which is a special version of FluentWait which has WebDriver instances.

In the article for waitForElement I was talking about how Selenium cannot detect that Javascript has finished altering the DOM. So if you try to interact with an element with Selenium and Javascript is still altering it, you will have undefined results. The waitForElement method would wait for the element to be full rendered, i.e. for the Javascript to be finished. The WebDriverWait serves the same purpose.

To define a WebDriverWait would be the following:
WebDriverWait wdw = new WebDriverWait(driver, timeoutInSeconds, pollingTimeInMs);
The first parameter is an already existing WebDriver element. The second parameter is how long we want to wait for the element to be present. If we set it to say 30 it will wait for 30 seconds before it throws an error. The third parameter is how often you want to check for the element. If you set it really low, e.g. 10, it will check every 10 milliseconds. However this could put a load on the test machine. If you know it typically takes 55 milliseconds then maybe waiting for 60 would be best. You would not want to wait for 1000 milliseconds as it would make your test too slow.

This just creates the WebDriverWait object. It doesn't actually do the waiting. Let's say you click the ACCEPT checkbox. This causes Javascript to make the Next button visible. If you just click the ACCEPT checkbox then click the Next button it might fail because the Next button isn't visible as quickly as you can click it. So you need to click the ACCEPT checkbox, wait for the Next button to be visible then click the Next button. Here is some example code:
long timeoutInSeconds = 30;
long pollingTimeInMs = 250;
WebDriverWait wdw = new WebDriverWait(driver, timeoutInSeconds, pollingTimeInMs);
wdw.until(visibilityOfElementLocated(By.id("next"))).click();
This will check every 250 milliseconds to see if the element located by id='next' is visible. If the element does not become visible in 30 seconds it will fail the step and throw an error.

The list of conditions you can wait for are:
presenceOfElementLocated(by);
visibilityOf(driver.findElement(by));
alertIsPresent();
elementSelectionStateToBe(by, true);
elementSelectionStateToBe(we, true);
elementToBeClickable(by);
elementToBeSelected(by);
frameToBeAvailableAndSwitchToIt(frameLocator);
invisibilityOfElementLocated(by);
invisibilityOfElementWithText(by,text);
presenceOfAllElementsLocatedBy(by);
textToBePresentInElement(by, text);
textToBePresentInElementValue(by, text);
titleContains(title);
titleIs(title);
visibilityOf(we);
visibilityOfElementLocated(by);
The WebDriverWait also lets you alter the polling time, timeout, message displayed when it times out and exceptions it should ignore while waiting:
wdw.pollingEvery(delayBetweenPolling, TimeUnit.MILLISECONDS)
.ignoring(NoSuchElementException.class)
.withTimeout(timeoutInSeconds, TimeUnit.SECONDS)
.withMessage(message)
.until(visibilityOfElementLocated(By.id("next"))).click();
Because you can set the unit of measure for polling time and timeout, you can refine these after you instantiate the WebDriverWait object.

.

Saturday, October 26, 2013

Windows Server and WebDriver

From time to time I see people trying to use Windows Server (2003 or 2008 R2) to run WebDriver scripts on Internet Explorer. They are posting to the WebDriver Google Group because it is not working and they are looking for answers on how to make it work.

The problem is they are asking the wrong question. They should not be asking, "How do I use WebDriver tests on Windows Server?" They should be asking, "Why don't my tests, which run on Windows XP or Windows 7, run on Windows Server 2008 R2?"

The answer is that Windows Server is not meant for people to use for surfing the web. They provide Internet Explorer if you need to access a local web application. You can explicitly add in trusted sites. But you cannot use it the way you would Internet Explorer on a Windows Workstation (like Windows XP, Windows 7 or Windows 8).

For more on this read the posting in Microsoft Developer Network: Enhanced Security Configuration for Windows Internet Explorer. The most important thing to note from this posting is the first two sentences:

As a best security practice, a server administrator should not browse Internet Web sites from the server. The administrator should only browse the Internet from a limited user account on a client work station to reduce the possibility of an attack on the server by a malicious Web site.

If you are really bent on using Windows Server there are ways to alter things but you will have to read the documentation on each version of Internet Explorer to understand how to do it.

The instructions for making Internet Explorer 7 on Windows Server 2003 work with Selenium will be different from getting Internet Explorer 8 on Windows Server 2008 R2 working with Selenium. Even getting Internet Explorer 9 on Windows Server 2008 R2 will be different as well. Microsoft is always changing what things are locked down and how you unlock them.

You should also realize that no respectable system administrator is going to use Windows Server to access a web application. They will use Virtual PC or a real Windows Workstation to access web applications. Therefore testing the application works okay with Internet Explorer on a Windows Server is not a configuration which should be supported. It does not test that the application runs on a real supported configuration, i.e. Internet Explorer on a Windows Workstation.

Monday, September 23, 2013

Dealing with HTTP Basic Authentication

If you go to a website and it has HTTP Basic Authentication turned on it will pop up a dialog asking you for username and password.

You can pass this information in the URL so the dialog does not pop up. This will allow you to go to these websites with automation tools like Selenium.

For example, if you go to httpwatch and click on the Display Image button it will pop open a dialog asking you for a username and password. For this site you can enter httpwatch for the username and any string for the password. So if I go to:

http://httpwatch:password@www.httpwatch.com/httpgallery/authentication/

then click the Display Image button, it will not pop open a dialog. This is because I have already been authenticated on the website.

HOWEVER, this will not work with current web browsers.

The reason for this is because web browser manufacturers recognized the fact that people can use this URL format to create fake websites. For example, I could have a website at 10.23.56.234, turn on basic authentication and make the username www.microsoft.com. I then send out an email with the URL:

http://www.microsoft.com:80@10.23.56.234/survey.html

This URL will go to my fake website with the username:password of www.microsoft.com:80. Some people might be tricked into thinking they are going to www.microsoft.com.

Fortunately, you can turn this feature back on.

For Internet Explorer you can make it accept the above URLs. It does open you up to someone spoofing you. So do this to a test machine, used only for automation, is okay. Using this on the computer you use to surf the web is a back idea.

If you go to http://support.microsoft.com/kb/834489/EN-US it will explain all the above. At the bottom it will talk about how to enable using username:password in the URL. In a nutshell, do the following:

  • Open regedit.exe
  • Find the key HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_HTTP_USERNAME_PASSWORD_DISABLE
  • Create the DWORD iexplore.exe=0
By setting iexplore.exe to 0 you are disabling the disabling of username:password, i.e. double negative.

If you are running 32-bit Windows this works as stated. If you are running 64-bit Internet Explorer on 64-bit Windows this works as stated. However, if you are running 32-bit Internet Explorer on 64-bit Windows this does not work. By default, 64-bit Windows 7 will run 32-bit Internet Explorer.

When you look in the registry, all the settings for 32-bit programs on a 64-bit computer will be in the WOW6432Node. So rather than HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft you want to go to HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft.

For Chrome & Firefox, they should support basic authentication through the URL.

For Safari, go into the Preferences, go to the Security tab and disable Warn when visiting a fraudulent website.

In summary, if I have the Selenium Java code:


    driver.get("http://www.httpwatch.com/httpgallery/authentication/");
    driver.findElement(By.cssSelector("#displayImage")).click();
    WebElement img = driver.findElement(By.cssSelector("#downloadImg"));

It will never make it to the third line because the .click() will bring up an HTTP basic authentication dialog which will block Selenium. Instead, I would use:

    driver.get("http://httpwatch:password@www.httpwatch.com/httpgallery/authentication/");
    driver.findElement(By.cssSelector("#displayImage")).click();
    WebElement img = driver.findElement(By.cssSelector("#downloadImg"));

.

Thursday, August 29, 2013

Little discussed Java classes in WebDriver

Sleeper

Recently I have been exposed to classes in WebDriver that I did not know existed. I started with Selenium when it was beta and found myself inventing methods to do things for Selenium and later for WebDriver. So I never really looked at the extra capabilities for WebDriver. Even before Selenium I often needed to write code that slept for a few seconds. The proper way to use the Thread.sleep method would be:

    public void sleep(long ms) {
        try {
            Thread.sleep(ms);
        } catch(InterruptedException ie) {
            throw new RuntimeException(ie);
        }
    }
Today I decided to scan through the WebDriver APIs. While I was doing this I found the Sleeper class. It has two APIs which are nice to use. Rather than using the method above you can use:
    sleepTight(2000);        // sleep for 2000 milliseconds
    sleepTightInSeconds(2);  // sleep for 2 seconds
SystemClock

Another helpful class I found was the SystemClock class. It implements the Clock interface and has three functions:

    SystemClock sc = new SystemClock();

    long current = sc.now();
    long early = sc.laterBy(-60000);
    long later = sc.laterBy(60000)
    System.out.println(sc.isNowBefore(later));  // true
    System.out.println(sc.isNowBefore(early));  // false
    System.out.println(sc.isNowBefore(now));    // false
The value of 'current' will be the timestamp of now. It is similar to using System.currentTimeMillis() and the API docs actually refer to System.currentTimeMillis(). Obviously, the command immediately after sc.now() will be greater than 'current' just because the call to sc.now() will take some time. The class also has the laterBy method. It talks about passing it a positive long but you can actually pass a negative number. In this case I passed in -60000. The value is in milliseconds. So this would be -60 seconds from 'now'. The third line sets later to 60 seconds later. A few milliseconds will have passed so if the time was 1377825553203 when I called sc.now() then sc.isNowBefore() get called and set 'now' to a few milliseconds later. Maybe 'now' will be 1377825554713. So sc.isNowBefore(later) will be true because later will be around 1377825553203 + 60000 and therefore later than 'now'. Where as sc.isNowBefore(early) will be false because 'now' will be after 1377825553203 - 60000. The odd one is sc.isNowBefore(current). This is because 'current' was now when it was set but a few lines later and 'now' will be after 'current', i.e. now > current, and sc.isNowBefore(current) will be false.

ThreadGuard

This class is more for people writing multi-threaded tests. It is rare for me to write multi-threaded tests. Hopefully you will never have to go there but if you do it is important to know that the implementations of WebDriver (ChromeDriver, FirefoxDriver, InternetExplorerDriver, etc.) are not guaranteed to be thread safe. To make them thread safe you can use the ThreadGuard class. It is actually pretty simple to use. If you have:

    WebDriver driver = new FirefoxDriver();
You can make it thread safe by changing it to:
    WebDriver driver - ThreadGuard.protect(new FirefoxDriver());
It is that simple. I won't get into why you are using multi-threaded tests. If you don't know if you are using multi-threading then you are probably not using multi-threading.

UrlChecker

UrlChecker is also a helpful class. It checks to see if a URL is returning an HTTP response of 200 OK. You could use it to check for a URL existing before you navigate to it or do a if URL does not exist then go to a different URL. For example,

    try {
        UrlChecker uc = new UrlChecker();
        String testURL = "http://localhost:8080/testapp/index.html";
        uc.waitUntilAvailable(5, TimeUnit.SECONDS, new URL(testURL));
        driver.get(testURL)
    } catch(UrlChecker.TimeoutException te) {
        // if the testURL does not become available in 5 seconds
        // this code will be run
    } catch(MalformedURLException mue) {
        // needed by URL class
        // if you think the testURL might be malformed handle it here
    }

Urls

This class has two helpful static methods:

    String testURL = "http://localhost:8080/~darrell/tests/#/";
    System.out.println(Urls.toProtocolHostAndPort(testURL));
    System.out.println(Urls.urlEncode(testURL));
The first println will print "http://localhost:8080". As URLs go, the /~darrell/tests/#/ is the context root. If you try going to "http://localhost:8080" it must have a context root. It will just assume you meant "/" for the context root. The second println will print "http%3A%2F%2Flocalhost%3A8080%2F%7Edarrell%2Ftests%2F%23%2F". All the symbols are converted to their ASCII hexidecimal value prefixed with a percent symbol. So for example, %3A is a :, %2F is a /, %7E is a ~ and %23 is the #. There are other interesting classes in WebDriver but these are the less used/discussed classes. Other classes like WindowsUtils have enough happening that they really need their own article. I will say that the following are some interesting classes which require more investigation:
  • FluentWait
  • Platform
  • ProcessUtils
  • Select
  • Wait
  • WindowsUtils

Tuesday, June 25, 2013

Dealing with things that disappear from the DOM when you look at them

From time to time I encounter a javascript feature which does not let me examine it. For example, auto-complete features on input boxes.

We have all seen this feature if you go to http://www.google.ca. You start typing something and Google gives you suggestions on what you are going to type. For example, I enter "WebD" and Google suggests "WebDriver".

The problem automating some of these is finding the list of suggestions. For example, go to http://www.asp.net/ajaxlibrary/ajaxcontroltoolkitsamplesite/autocomplete/autocomplete.aspx with Chrome, Inspect the auto-complete input box (right click in the input box and select "Inspect") and type something into the input box.

You will see a list of suggestions appear as you type but looking at the DOM in the Inspect window there is no list of suggestions attached to the input element.

They have to exist somewhere in the DOM but you have to find them. So while the suggestion list is open I click on the Inspect window and press CTRL-F to find one of the suggestions.

The problem is the moment I click in the Inspect window, the list of suggestions disappears. So the CTRL-F in the Inspect window finds nothing.

If I type a word in the input box then press CTRL-F (don't change focus to the Inspect window) the auto-complete list disappears. Even if it didn't disappear, it would find the suggestions in the browser window, not in the Inspect window.

So here is how I find the suggestions in the DOM...
WebDriver driver = new ChromeDriver();
driver.get("http://www.asp.net/ajaxlibrary/ajaxcontroltoolkitsamplesite/autocomplete/autocomplete.aspx");
driver.findElements(By.cssSelector("input[name$='myTextBox']")).sendKeys("test");
String src = driver.getPageSource();
driver.quit();
then I set a breakpoint on the driver.quit(); and run in Debug mode. When it stops on the breakpoint I start using the Watch Window of my IDE.

If I enter "test" and one of the suggestions is "testASG" then I'm trying to find the list with "testASG" as one of the suggestions. So I will add:
src.indexOf("testASG");
to the Watch Window. Let's say it returns 34923. So now I know the text it at 34923 and that the element wrapping the suggestion is BEFORE 34923. So I can try:
src.substring(34500);
and examine the results. From this I might see "<li>testASG</li>" and some other stuff before the <li>. This tells me that the list of suggestion is a <ul> element (<ol> would be numbered). So I can start using:
src.indexOf("<ul");
I know it will start with <ul but there would be different, unknown attributes after the ul. When I did this it returned something like 5723. So I looked at:
src.substring(5720);
I see it is finding a "null". Not what I was looking for. So I might start looking for ul but using the index of "testASG". So I might try:
src.substring(33000).indexOf("<ul");
If that returns a number higher than 34923 (after the li) then I need a smaller number. If it gives me a number between 33000 and 34923 then I can look at the substring and see if that was the ul I was looking for.

By narrowing down (binary search) where the ul is for the list of suggestions I can finally see what the DOM looks like by examining the src variable. At some point I will figure out the locators for the ul, li, etc. I can even trying the following locator in the Watch Window:
driver.findElements(By.cssSelector("ul#AutoCompleteEx_completionListElem>li");
To get a list of all the suggestions.

Tuesday, May 7, 2013

Is an element on the visible screen

A number of times I have been on a website where clicking a link goes to a specific page with a specific element at the top of the page. For example, I am reading an article and it quotes a book. The quote is actually from page 17, paragraph 2, first sentence. So when I click the link to the article it should take me to page 17, paragraph 2, first sentence.

So how would you automate testing this?

The simple answer would be to find the WebElement which is expected to be at the top of the page. This is the easy part. Then you need to see if the WebElement is on the visible screen. If you click it, WebDriver will scroll it into view. This is not what we want.

So what do we do? The answer is find out the size of the visible window then see if the bottom-right corner of the WebElement is inside that dimension. So here is the code for that:

  private boolean isWebElementVisible(WebElement w) {
    Dimension weD = w.getSize();
    Point weP = w.getLocation();
    Dimension d = driver.manage().window().getSize();

    int x = d.getWidth();
    int y = d.getHeight();
    int x2 = weD.getWidth() + weP.getX();
    int y2 = weD.getHeight() + weP.getY();

    return x2 <= x && y2 <= y;
  }
This will tell me of the element is on the visible window. If the element is not on the visible window it will return false. If the element is on the visible window it will return true... well almost. Unfortunately, you can get the size of the browser window but you really want the size of the chrome, inside the window. So if the window dimension is 1024x768 it is really telling you how big the OUTSIDE of the window is and not the inside. If the element is located at 1024x768 it is really off the visible window. For example, on my Google Chrome the tabs/title is 22px high. So the visible screen would really be 768 - 22 or 746px.

So this is a close approximation but there is a small chance of the element being just off the bottom of the window and still return true. If you are running driver.manage().window().maximize() the odds that something is going to be off the bottom of the visible window is small enough that it is not worth worrying about. Add to that we would hopefully be manually testing the area occasionally during exploratory testing and the risk should be acceptable.

There is another problems with this. We aren't actually checking that the upper-left corner of the element is at the top of the screen. You might think, why not just check that the element we are looking for is at position (0, 0)? The reason for not checking this is because the element might not be exactly at (0, 0). If the screen is large enough for 20 elements, the page only has 4 elements and the element we are looking for is at the second position, it will be impossible for the element we are looking for to scroll. So it will be below (0, 0) but still on the visible window.

A good example if this code would be:

    driver.get("http://www.w3schools.com");
    WebElement we = driver.findElement(By.cssSelector("#gsc-i-id1"));
    assertTrue(isWebElementVisible(we));
    WebElement we2 = driver.findElement(By.cssSelector("#footer"));
    assertFalse(isWebElementVisible(we2));

This will go to www.w3schools.com, find the Google search input at the top of the page, confirm it is visible. Then it will find the footer at the bottom of the page and confirm it is not visible.

Wednesday, May 1, 2013

Why using JavascriptExecutor in WebDriver can be dangerous

I have occasionally seen people recommending running javascript using the JavascriptExecutor implementation. For example, I was working on a project where a test would pass on the developer's computer but would fail on the functional test machine. The line of code which failed was:
driver.findElement(By.cssSelector(".save")).click();
So the developer changed it to:
((JavascriptExecutor)driver).executeScript("$('.save').click();");

It seemed harmless and it worked.

The GUESS was that WebDriver found the button because it was always in the DOM with style="display: none;" but the click failed because it wasn't waiting for the button to become visible.

The idea was that the test was REALLY:

- open the dialog
- wait for the button to become visible
- click the button

If we used javascript we didn't have to wait for the button to be visible. So javascript seemed faster but was still testing the right thing.

No problem, right? Wrong. It turned out that the save button was on a javascript dialog which did not scroll. If you scrolled up and down the page the dialog remained still; the dialog ALWAYS stay exactly 120px from the top of display. When all the tests ran it created data which made the dialog 830px high. This placed the bottom of the dialog at 950px. The developers machine was 1600x1200 display. It made the dialog fully visible on the display. The functional test machine was set to 800x600. This forced the bottom of the dialog off the bottom of the display. Even when you made the browser full screen you could not see the save button.

So on the developers machine WebDriver would see and click the save button because it was visible. On the functional test machine (set to 800x600 because that was the minimum requirement) it was not visible and you could not scroll it into view because the dialog did not scroll.

When they switched to the javascript code it clicked the button regardless of whether the button was visible or not. If we shipped this application, any customer who was using a computer with 800x600 display (a significant number of people based on analytics) would be unable to click the save button even though the functional test passed.

The moral of the story is, if the javascript solution doesn't test EXACTLY the same requirement as the WebDriver solution, you could be making your tests report a false positive.

Saturday, April 13, 2013

Determining which version of Selenium goes with which browsers

Sometimes it is hard to know which version of Selenium you should be using. There is no hard and fast rule of which Selenium works with which browser but I have always found that you want a Selenium which was released after the browser but as close to the browser release date as possible.

It has been my experience that the latest version of Selenium and the latest version of Chrome go together. I have never found that the latest version of Selenium does not work with the latest version of Chrome.

On the other hand, I have seen Mozilla release a new version of Firefox and it takes a few weeks for a new version of Selenium to appear. During this period I find the latest version of Selenium does not work with the latest version of Firefox.

For example, Firefox 18.0 was released on 08-Jan-2013. At the time the latest version of Selenium was 2.28.0, released on 11-Dec-2012. If you updated to Firefox 18.0 the moment it was available, you might find that some of your Selenium tests started failing unexpectedly. Selenium 2.29.0 was released on 17-Jan-2013. I always disabled the ability for Firefox to automatically upgrade. When Selenium 2.29.0 was released, I upgraded my Selenium to 2.29.0 and my Firefox to 18.0.

You can see the release dates of Firefox (and other Mozilla browsers) on https://wiki.mozilla.org/Releases. What I do is create a list of Selenium release dates from the Selenium download area (https://code.google.com/p/selenium/downloads/list). I would then look at the Mozilla wiki and determine which Firefox had been released on an older date. From my example above:

11-Dec-2012    Selenium 2.28.0
30-Nov-2012    Firefox 17.0.1
or
17-Jan-2013    Selenium 2.29.0
08-Jan-2013    Firefox 18.0

Or in other words, if the project I am on has set a specific version of Firefox to support then I look for the Selenium which was released AFTER the Firefox release date. That is the combination I would test with.

For Internet Explorer I can find the release dates by searching for "Internet Explorer Release Dates". This currently takes me to IE Downloads. I also find the Wikipedia has an Internet Explorer page which gives release dates as well.

For the most part however, I let the version of Firefox drive which version of Selenium I need. Because I tend to have clients request IE7, IE8 and IE9 support, I find it best to go with the latest version of Selenium to support the latest version of IE.

Selenium does have limited support for Safari but this is typically not an issue. Much like IE, I let the version of Firefox drive which version of Selenium I need regardless of which version of Safari I need to support.

It should be noted that this is a general idea of how I determine which version of Selenium I want. If I use this rule and start finding that the version of Chrome matters, I would start trying to match the release date of Chrome to the release date of Selenium.


Tuesday, November 27, 2012

List of Selenium/WebDriver blogs.

I was recently pointed to a nice list of Selenium/WebDriver blogs. If you are looking for more information about Selenium/WebDriver you should check out http://it-kosmopolit.de/Selenium/blog/selenium-blogs/selenium_blogs.php.

I haven't checked out all the links but there appears to be a good start to the list of Selenium/WebDriver blogs available out there.

Tuesday, July 24, 2012

Generating a screen capture when using RemoteWebDriver

Recently I was asked if all the different implementations of WebDriver allow for screen captures. I had a look at the source code and see that almost all of them "implement TakesScreenshot". If the Java class for the driver implements TakeScreenshot then it allows for screen captures. This means a previous posting (http://darrellgrainger.blogspot.ca/2011/02/generating-screen-capture-on-exception.html) there isn't really any need for using Robot to generate the screen capture.

The nicest thing about using the built-in screen capture is that Robot will capture the visible screen. The built-in screen capture will capture the entire window in the browser, including the parts not visible, i.e. things you have to scroll to see.

Shortly after this someone asked of RemoteWebDriver supported screen capture. This was very important because Robot would not capture even the visible part of a REMOTE screen.

A quick check of RemoteWebDriver.java showed that it did NOT implement TakesScreenshot.

Recently however I found a solution. Assuming you have the code:
DesiredCapabilities dc = DesiredCapabilities.firefox();
URL url = new URL("http://localhost:4444/wd/hub");
WebDriver driver = new RemoteWebDriver(url,dc);
You can change the driver to:
WebDriver driver = new Augmenter().augment(new RemoteWebDriver(url,dc));
Now it has the capability to take a screen shot. To make the call, here are the three different outputs:
File f = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE);
String s = ((TakesScreenshot)driver).getScreenshotAs(OutputType.BASE64);
byte[] b = ((TakesScreenshot)driver).getScreenshotAs(OutputType.BYTES);


And that is all there is to taking a screenshot with RemoteWebDriver. I have tried this with Firefox and InternetExplorer. I assume it works with the other browsers but I leave that for you to explore.

Wednesday, July 4, 2012

Creating a screen capture on every action

Someone recently commented on an article I wrote about generating a screen capture when an exception is thrown (see Generating a screen capture on exception thrown with Selenium 2).

Performing an action when an exception is thrown is built into the Selenium framework. You just need to create the action to generate a screen capture and hook it into the framework.

The Selenium framework comes with a WebDriverEventListener interface. In my article above I created an implementation of the WebDriverEventListener interface. To do this properly, you need to implement the following methods:
beforeNavigateTo(String url, WebDriver driver);
afterNavigateTo(String url, WebDriver driver);
beforeNavigateBack(WebDriver driver);
afterNavigateBack(WebDriver driver);
beforeNavigateForward(WebDriver driver);
afterNavigateForward(WebDriver driver);
beforeFindBy(By by, WebElement element, WebDriver driver);
afterFindBy(By by, WebElement element, WebDriver driver);
beforeClickOn(WebElement element, WebDriver driver);
afterClickOn(WebElement element, WebDriver driver);
beforeChangeValueOf(WebElement element, WebDriver driver);
afterChangeValueOf(WebElement element, WebDriver driver);
beforeScript(String script, WebDriver driver);
afterScript(String script, WebDriver driver);
onException(Throwable throwable, WebDriver driver);
In my screen capture when an exception is thrown, I put the necessary code to generate a screen capture in the onException method. You can see from the list above that you can have Selenium perform an action when other events occur. For example, if you wanted to do a screen capture after each click() action, you could write:

public void afterClickOn(WebElement element, WebDriver driver) {
    String filename = generateRandomFilenameFromWebElementAndDriver(element, driver);
    createScreenCaptureJPEG(filename);
}

I'll leave it to the reader to figure out how to create the generateRandomFilenameFromWebElementAndDriver. You might do something like use getCurrentUrl(), convert things like colon, slash, etc. into valid filename characters then append the web element getTagName() to the end of the filename.

Or you could do something tricky like have the beforeClickOn generate the filename and the afterClickOn use that filename to generate the screen capture.

If the event you want to trigger a screen capture is not listed above, there is no easy answer for how you would do it.

Initially, you might considering changing the WebDriverEventListener interface. But this would require a change to how Selenium works. Every time there is a new release of Selenium, you would have to merge your changes back in.

I would recommend submitting the changes as a new feature to the Selenium project to see if they'll incorporate it or wrap the Selenium methods with your own methods. For example, you would call your sendKeys() which could do a driver.sendKeys then do a screen capture.

Wednesday, June 20, 2012

StaleElementException

Most automation tools depend on the concept of the page has finished loading. With AJAX and Web 2.0 this has become a grey area. META tags can refresh the page and Javascript can update the DOM at regular intervals.

For Selenium this means that StaleElementException can occur. StaleElementException occurs if I find an element, the DOM gets updated then I try to interact with the element.

Actions like:
driver.findElement(By.id("foo")).click();
are not atomic. Just because it was all entered on one line, the code generated is no different than:
By fooID = By.id("foo");
WebElement foo = driver.findElement(fooID);
foo.click();
If Javascript updates the page between the findElement call and the click call then I'll get a StaleElementException. It is not uncommon for this to occur on modern web pages. It will not happen consistently however. The timing has to be just right for this bug to occur.

Generally speaking, if you know the page has Javascript which automatically updates the DOM, you should assume a StaleElementException will occur. It might not occur when you are writing the test or running it on your local machine but it will happen. Often it will happen after you have 5000 test cases and haven't touched this code for over a year. Like most developers, if it worked yesterday and stopped working today you'll look at what you changed recently and never find this bug.

So how do I handle it? I use the following click method:
public boolean retryingFindClick(By by) {
        boolean result = false;
        int attempts = 0;
        while(attempts < 2) {
            try {
                driver.findElement(by).click();
                result = true;
                break;
            } catch(StaleElementException e) {
            }
            attempts++;
        }
        return result;
}
This will attempt to find and click the element. If the DOM changes between the find and click, it will try again. The idea is that if it failed and I try again immediately the second attempt will succeed. If the DOM changes are very rapid then this will not work. At that point you need to get development to slow down the DOM change so this works or you need to make a custom solution for that particular project.

The method takes as input a locator for the element you want to click. If it is successful it will return true. Otherwise it returns false. If it makes it past the click call, it will return true. All other failures will return false.

Personally, I would argue this should always work. If the developers are refreshing the page too quickly then it will be overloading the browser on the client machine.

Friday, April 20, 2012

How to find a popup window if it does not have a name


When using Selenium 2.0 (WebDriver) you will sometimes find clicking a link opens a popup window. Before you can interact with the elements on the new window you need to switch to the new window. If the new window has a name you can use the name to switch to the popup window:

    driver.switchTo().window(name);

However, if the window does not have a name you need to use the window handle. The problem is that every time you run the code the window handle will change. So you need to find the window handle for the popup window. To do this I use getWindowHandles() to find all the open windows, I click the link to open the new window. Next I use getWindowHandles() to get a second set of window handles. If I remove all the window handles of the first set from the second set I should end up with a set with only one element. That element will be the handle of the popup window.

Here is the code:
String getPopupWindowHandle(WebDriver driver, WebElement link) {

    // get all the window handles before the popup window appears
    Set<String> beforePopup = driver.getWindowHandles();

    // click the link which creates the popup window
    link.click();

    // get all the window handles after the popup window appears
    Set<String> afterPopup = driver.getWindowHandles();

    // remove all the handles from before the popup window appears
    afterPopup.removeAll(beforePopup);

    // there should be only one window handle left
    if(afterPopup.size() == 1) {
        return (String)afterPopup.toArray()[0];
    }
    return null;
}
To use this I simply call it with the WebElement which clicking opens the new window. You want to add some error handling to this but the basic idea of finding the popup window is here. To use the method I would use:
String currentWindowHandle = driver.getWindowHandle();
String popupWindowHandle = getPopupWindowHandle(driver, link);
driver.switchTo().window(popupWindowHandle);
// do stuff on the pop window
// close the popup window
driver.switchTo().window(currentWindowHandle)