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.


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.