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.


Sunday, February 27, 2011

waitForCondition revisited

In March 2010, I wrote a piece on using waitForCondition with Selenium 1.0, Selenium RC [Java] waitForCondition example.

At the bottom of the article is a line of code that will let you wait for an ajax call to complete. This line of code was written when I was working on a project using scriptaculous.js.

Recently I joined a company using jQuery.js. The code for waiting is the same but the variable you wait for has changed. If you use the code from March 2010, it would fail because the variable does not exist.

Instead you want to use the last line below:

WebDriver browser = new FirefoxDriver();
    Selenium selenium = new WebDriverBackedSelenium(browser, baseURL);
    browser.get(baseURL);

    selenium.waitForCondition("selenium.browserbot.getCurrentWindow().jQuery.isReady == true", "30000");

You might wonder how I figured this out. The easiest way is to ask the developer. If that is not an option, Firebug on Firefox or the Inspect feature of Chrome will help you out. On Chrome I would right click on the web page and select Inspect Element. The Developer Tools will appear. Select the Scripts section. On the right will be Scope Variables with a pause button at the top of the frame. Clicking the pause button will display the Scope Variables. You then want to look at the variables and see if something jumps out at you.

The other approach is look at the select menu at the top of the left frame. This will list the different javascript files. When I looked at this list I saw the jquery.js script. I could then look up the APIs for jquery.js and figure things out.

Friday, February 25, 2011

The Platform class in Selenium 2

There is a class in Selenium 2 called Platform. It is very helpful in those situations where you need to do something different for different platforms. Here is some sample code:

 @Test
 public void seleniumPlatformExamples() {
  System.err.println("Supported platforms:");
  for(Platform p : Platform.values()) {
   System.err.println(p.toString());
  }
  System.err.print("The current platform is " + Platform.getCurrent());
  System.err.printf(" v%d.%d\n", Platform.getCurrent().getMajorVersion(), Platform.getCurrent().getMinorVersion());
 }

The Platform.getCurrent() will return the current instance of Platform. You can then access all the other features using that instance.

There are other functions like checking to see if current version is equal to some enum value.

Thursday, February 24, 2011

Using the features of Selenium 1.0 with Selenium 2.0 code

I recently posted about doing a screen capture on exception. This got me thinking about how I can maximize the browser window before I do the screen capture. In Selenium 1.x I would use:

Selenium sel = new DefaultSelenium("localhost", 4444, "*firefox", "http://www.google.com");
    sel.windowMaximize();

But how do I do this using WebDriver? The solution is to use a WebDriver instance to create a WebDriverBackedSelenium instance. Here is the code:

WebDriver driver = new FirefoxDriver();
    Selenium sel = new WebDriverBackedSelenium(driver, "http://www.google.com");
    sel.windowMaximize();

and it is that simple. If I want to use Selenium 2.0 features, I access things via the driver variable. If I want to use Selenium 1.0 features, I access things via the sel variable.

Quite simple.

Generating a screen capture on exception thrown with Selenium 2

Recently, someone asked how to have Selenium 2 (WebDriver) create a screen capture if an exception is thrown. Here is how you do it...

Let's say you have the code:

WebDriver driver;

    @Before
    public void setUp() {
        driver = new FirefoxDriver();
    }

and you want to change it so the test cases will generate a screen capture file when an exception is thrown. Here is the first change:

WebDriver driver;

    @Before
    public void setUp() {
        WebDriverEventListener eventListener = new MyEventListener();
        driver = new EventFiringWebDriver(new FirefoxDriver()).register(eventListener);
    }

The second change is to create the MyEventListener class. The MyEventListener class will be:

public class MyEventListener implements WebDriverEventListener {
    // All the methods of the WebDriverEventListener need to
    // be implemented here. You can leave most of them blank.
    // For example...
    public void afterChangeValueOf(WebElement arg0, WebDriver arg1) {
        // does nothing
    }

    // ...

    public void onException(Throwable arg0, WebDriver arg1) {
        String filename = generateRandomFilename(arg0);
        createScreenCaptureJPEG(filename);
    }
}

The MyEventListener class will have 15 methods, including the two examples I have given here. The main method that you must implement if you want screen captures whenever an exception is thrown would be the onException method.

The biggest trick for this method is generating a unique filename for each exception. First thought is that the filename could be in the format "YYYY-MM-DD-HH-MM-SS.jpg". Unless you get two exception in one minute this will work okay. Unfortunately, it will be hard to figure out what the exception was unless you kept some sort of log in the code execution. You'll also have to waste time figuring out which exception goes with which date/time.

Personally, I'd use the format "YYYY-MM-DD-HH-MM-SS-message-from-the-throwable-argument.jpg". Selenium tends to throw multiple line exception messages. So you could take the first line of the message, change characters which are illegal for your file system and change them to underscores. You could also have something to set the location of the screen capture files and prepend that to the filename.

Here is the code I came up with in 2 minutes:

private String generateRandomFilename(Throwable arg0) {
        Calendar c = Calendar.getInstance();
        String filename = arg0.getMessage();
        int i = filename.indexOf('\n');
        filename = filename.substring(0, i).replaceAll("\\s", "_").replaceAll(":", "") + ".jpg";
        filename = "" + c.get(Calendar.YEAR) + 
            "-" + c.get(Calendar.MONTH) + 
            "-" + c.get(Calendar.DAY_OF_MONTH) +
            "-" + c.get(Calendar.HOUR_OF_DAY) +
            "-" + c.get(Calendar.MINUTE) +
            "-" + c.get(Calendar.SECOND) +
            "-" + filename;
        return filename;
    }

The final part is the code to actually generate the file. This is standard Robot stuff. Here is the code I whipped together a few projects back:

private void createScreenCaptureJPEG(String filename) {
  try {
   BufferedImage img = getScreenAsBufferedImage();
   File output = new File(filename);
   ImageIO.write(img, "jpg", output);
  } catch (IOException e) {
   e.printStackTrace();
  }
 }
 
 private BufferedImage getScreenAsBufferedImage() {
  BufferedImage img = null;
  try {
   Robot r;
   r = new Robot();
   Toolkit t = Toolkit.getDefaultToolkit();
   Rectangle rect = new Rectangle(t.getScreenSize());
   img = r.createScreenCapture(rect);
  } catch (AWTException e) {
   e.printStackTrace();
  }
  return img;
 }

And that is it. Whenever an exception is thrown a file will be generate.

Monday, February 21, 2011

Using Selenium 2.0 with WebDriver and Safari

I've been looking at Selenium 2.0 and writing test cases using WebDriver. Looking at the APIs I see there is a class for Android, iPhone, InternetExplorer, Firefox and Chrome which extends the RemoteWebDriver.

So how do I use Safari? The code to set the search string for Google, using WebDriver, would be:

WebDriver browser = new FirefoxDriver();

browser.get("http://www.google.com");
WebElement input = browser.findElement(By.name("q"));
input.sendKeys("Selenium");

If I wanted to do the same thing using Safari web browser, I could use:

Selenium browser = new DefaultSelenium("localhost", 4444, "*safari", "http://www.google.com");

browser.type(name("q"), "Selenium");

The problem with this is I need to do things differently for Safari. I have to use Selenium 1.0 commands to test Safari and Selenium 2.0 for everything else. So how can I use a browser which was supported in Selenium 1.0 with all the Selenium 2.0 APIs?

The solution is:

Selenium sel = new DefaultSelenium("localhost", 4444, "*safari", baseURL);
CommandExecutor executor = new SeleneseCommandExecutor(sel);
DesiredCapabilities dc = new DesiredCapabilities();
WebDriver browser = new RemoteWebDriver(executor, dc);

browser.get("http://www.google.com");
WebElement input = browser.findElement(By.name("q"));
input.sendKeys("Selenium");

If you look at this block of code, the last 3 lines are the same as the last three lines of the first block of code. Essentially, I add the first three lines then change the WebDriver declaration to a new RemoteWebDriver.

The one thing I found however, on my Mac OS X, if I started the SeleniumServer with setSingleWindow(false) it would fail to work. I run my SeleniumServer inside my Java code using:

private static SeleniumServer ss;
private static RemoteControlConfiguration rcc;

@BeforeClass
public static void setUpBeforeClass() throws Exception {
rcc = new RemoteControlConfiguration();
rcc.setInteractive(true);
rcc.setSingleWindow(true);
rcc.setTimeoutInSeconds(10);
ss = new SeleniumServer(rcc);
ss.start();
}

If you are running the SeleniumServer from the command line, you'll have to look at the command line options to ensure you are running in Single Window mode.

Saturday, February 12, 2011

Selenium 2

I've been to the dark side (HP Quick Test Professional) for the past year but I'm back to working with open source tools like Selenium.

When I last looked at Selenium it was just in the process of releasing version 2.0.

One of my complaint of 1.0 was how inconsistent the Java APIs were. Simple things like iterating through all the OPTIONS on a SELECT list wasn't really there. You could use Selenium to make the application do anything a user would do but for test automation you need to have access to information that a manual test would usually validate with their eyes.

I recently started looking at version 2.0. There seems to be a great deal of improvement.


  1. It will still run the Selenium 1.0 code with no change at all.
  2. You can mix and match 1.0 and 2.0 code which enables you to slowly refactor your old 1.0 automation to 2.0 over time.
  3. No more effort trying to create a jUnit 3 case to extend all your test cases from.
  4. A much more simple design with far less 'extras' to help you, i.e. a K.I.S.S. approach to the APIs.

In the old Selenium 1.0 we would create an instance of Selenium and it would be the gateway to the browser. With Selenium 2.0 we create an instance of WebDriver. WebDriver is a base class of classes that represent the different browsers. If you wanted to create a fast, non-GUI browser you can use the HTMLUnit browser. For example, using jUnit 4 annotations, your @Before method would be:

WebBrowser browser;

@Before
public void setUp() {
    browser = new HtmlUnitDriver();
}

Now all your test cases (@Test) can you the browser instance to access the APIs. If you then want to change the test to run on a real web browser you can use:

    browser = new FirefoxDriver();
or
    browser = new InternetExplorerDriver();
or
    browser = new IPhoneDriver();
or
    browser = new AndroidDriver();

As new browsers are added to the WebDriver family this list will grow.

One you have a browser instance there is a limited number of options:

browser.findElement(by);
browser.findElements(by);
browser.get(url);
browser.getTitle();
browser.navigate();
browser.switchTo();

The biggest methods are the findElement and findElements. The first will find a single element. The input is the By class. It can identify elements by name, id, xpath, etc. If the identifier matches more than one element, it will return the first match. Realistically, I can see this changing with different browsers or at least changing over time as systems get upgraded. You really want to make sure the identifier matches a single, unique element.

The second method, findElements, works like the first method but it returns a list. Most elements are WebElements (some exceptions). So the first method returns a single WebElement and the second method returns a List<WebElement>. The best thing is you can find ANY html element. 

Once you have a WebElement, you have the following list of methods:

element.clear();
element.click();
element.findElement(by);
element.findElements(by);
element.getAttribute(attributeName);
element.getTagName();
element.getText();
element.getValue();
element.isEnabled();
element.isSelected();
element.sendKeys(keysToSend);
element.setSelected();
element.submit();
element.toggle();

The findElement and findElements will search from the given element and down in the DOM. For example, if you use browser.findElement to find a SELECT tag then you can use the result of that to find all OPTION tags. It would then be searching under the SELECT tag and therefore find all the OPTIONs for that tag.

That is essentially it. K.I.S.S. Rather than using arrays it is using Lists and Iterators. If the elements are uniquely identified and don't move around much it could be easy to maintain elements. In many cases the developer will find he needs to change a DIV to a SPAN or an A to a IMG with an onclick attribute. Rather than searching for a DIV you want to encourage the use of id attributes (which must be unique according to HTML standards) you can then find everything By id. A change in the tag or location in the DOM would require no maintenance of the automation scripts.

More to follow...