Posts Tagged ‘parallel testing’

Hey, Selenium. Hurry The Hell Up.

February 11th, 2011 by The Sauce Labs Team

I love automated browser tests, but I *really* love fast builds. The faster the better. So when a build that I’m waiting for starts taking over 5-10 minutes, all I can think to myself is… “Hurry the hell up!” And we all go through it, even here at Sauce. So here are a few things we’ve found to really help speed up Selenium testing:

1.) Paralellism

The more tests you can run in parallel, the faster your build can finish. So when writing your tests, always ask yourself, “Can I run this test by itself and have it pass?” If you think to yourself, “No… another test has to run *before* for this one to pass,” then stop. Put the shovel down, and start climbing your way out of that hole. Tests that rely on previous tests cause random, weird bugs to pop up, are hard to run when you want to reproduce those bugs, and are just slow. So keep your tests independent. It’s how they were meant to be anyway.

When none of your tests are inter-dependent, that means they can all be run in parallel, and in theory, your entire build should only take as long as your single longest test (in practice, it’s a bit longer, but not too much). Which brings us to the next point…

2.) Short Tests

Write your tests to only test a single tiny bit of functionality. Keep the number of steps in your tests under 20. All the parallelism in the world won’t save you from long-running tests, because a single test can’t be parallelized. The best written tests we’ve seen generally run in under 10 seconds. Yeah, I know. That’s short. If you’re wondering how that’s possible, check out step #4 (but be patient, we still have to get through step #3. Don’t rush me.)

A pleasant side-effect of this is that when a build fails, you’ll be able to narrow it down to a very specific area point and fix it all the faster.

3.) Sauce Connect 2, BOOST MODE!

Take a breath. A deep one. Then repeat “Sauce Connect 2, BOOST MODE!” to yourself, slowly, with some real emphasis on the “BOOST MODE”. Nice huh? Even if that’s all it were, it would be worth it, but there’s more. A whole lot more.

Sauce Connect 1 was a python-based tool that could easily and securely connect a staging server behind a firewall to the Sauce OnDemand browser cloud. Raved about by developers, sysadmins, and network admins alike, it was a cornerstone in enabling (very) security-minded orgs to bask in the glory of the Sauce Cloud. Even so, we knew it could go further, so why not push it?

Sauce Connect 2 is java-based (we lovingly call it “Enterprise Edition” internally), and in addition to some handy things like not having to specify each domain by hand anymore, it comes with BOOST MODE. Not ‘boost mode’, BOOST MODE. It looks faster that way.

When launching Sauce Connect 2 with the -b flag, it activates some magic under the hood and causes reduced traffic to your server, and speeds up resource-intensive tests considerably. And though it works for everyone, for our friends in distant lands in particular, you’re really going to love it.

Best part is, it’s just a -b flag. Couldn’t be simpler.

(Note: For the time being, this only works on Firefox and IE, though we have support coming for Safari and Chrome. Also, this isn’t “released” yet, so it’s my gift to you, our loyal reader. Let’s just keep it a secret between us, shall we? Check it out here)

4.) Pre-populate the cookies

This is a really nice trick.

Every browser you get from Sauce Labs is in a clean state. This is great, and you really want this. Even if Fred says you don’t want it, you want it. Tell Fred, “Fred, last night I spent an hour debugging tests, and found out nothing was wrong. You know what the problem was? Someone had installed the fuzzy_warm_critters_asciis browser extension, and it replaced every instance of my javascript with an ASCII cat. I want clean browsers every time.” Do it. Fred will understand.

ASCII cats are so cute!

Who needs javascript when you can have warm fuzzy asciicatz?

But this poses a bit of a problem with shorter builds, because now each browser session has to go through a login process, and possibly a few more steps depending on your application logic, just to get to where you want to test. Previously, I simply recommended abstracting the login process into a separate method so that the tests looked cleaner, but when you’re waiting for the build to finish, that’s not good enough. You want them clean *and* fast. One way to get this is to set the cookie via Selenium after the browser starts up.

Log in to your user via server-side logic (e.g. generate a row in your sessions table, or however you have it setup locally), generate a cookie for that login session, and push it into the browser via Selenium. Bam. You’re done logging in, and your test can continue on as though it had already spent the 5 seconds logging in and the 10 seconds adding items to the shopping cart, all so you can just test the “confirm purchase” button works on your site as expected in IE7.

This isn’t to say that you shouldn’t test out your login form or your “add fuzzy critter to cart” logic via browser tests. Because you should. But you shouldn’t do it for every test. Test it once, test it good, and leave it at that.

This works on anything that relies on state to be built up. Logins, Shopping Carts, Playlists, etc. You get a clean browser for every tests, and your tests are speedy. And you know what? Even Fred’ll love it. And that – in and of itself – is worth it.

Got any tips on how you speed up your Selenium builds? Take us to school and share ‘em. We’re all ears!

Oh, and for Fred’s sake…

|\      _,,,---,,_
 /,`.-'`'    -.  ;-;;,_
 |,4-  ) )-,_..;\ (  `'-'
 '---''(_/--'  `-'\_)
Share

JUnit 4 and Selenium – Part Three: Parallelism and OnDemand

October 25th, 2010 by Adam Goucher

This is the third and final part of how to create Selenium scripts to run in parallel. Today we get into the meat of things with parallel execution, both locally and in OnDemand.

JUnit 4 doesn’t ship with a true parallel solution, but Harald Wellman wrote a blog post on running parameterized JUnit tests in parallel, which does just about everything we want. But instead of having a fixed thread pool side, I modified it to use a dynamically sized one. With this new class in your project, we only need to change the @RunWith line to use our new parallel, parameterized runner.

@RunWith(Parallelized.class)

Now the execution profile is as follows: For each item in the @Parameters Collection, launch a new thread to execute the current test method in parallel. Have two browser strings? Then two threads run in parallel. Have eight browser strings? Well, that is eight threads running in parallel. This means that however long it takes run a single browser through all your scripts, that’s how long it will take to run against any number of browsers.

Win!

Well, sure, but only if you have all those browsers on your machine or have an Se-Grid installation at your disposal. Most people do not have either luxury, so that is where Sauce OnDemand comes into the mix. Hosted in the cloud, you don’t need to have the browsers on your machine or even behind the firewall. With some minor modification to our example class, we can have a script that runs locally or in the OnDemand cloud.

package com.saucelabs.cc;

import java.util.Collection;
import java.util.List;
import java.util.LinkedList;
import java.util.Arrays;

import java.io.InputStream;
import java.util.Properties;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

import com.thoughtworks.selenium.DefaultSelenium;
import com.thoughtworks.selenium.Selenium;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.FieldNamingPolicy;

import com.saucelabs.junit.Parallelized;
import com.saucelabs.ondemand.ConnectionParameters;

@RunWith(Parallelized.class)
public class TestParallelOnDemand {
    private Selenium selenium;
    private String browser;
    private String browserVersion;
    private String os;
    public static Properties browserProps = new Properties();
    private Properties parallelProps = new Properties();
    private String json;

    public TestParallelOnDemand(String os, String browser, String version) throws Exception {
        super();
        this.browser = browser;
        this.browserVersion = version;
        this.os = os;

        InputStream is = this.getClass().getResourceAsStream("/parallel.properties");
        parallelProps.load(is);

        if (parallelProps.getProperty("ondemand").equals("true")) {
          Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_DASHES).create();
          ConnectionParameters od = new ConnectionParameters();
          od.setBrowser(this.browser);
          od.browserVersion = this.browserVersion;
          od.jobName = this.getClass().getName();
          od.os = this.os;
          this.json = gson.toJson(od);
        }
    }

    @Parameters
    public static LinkedList browsersStrings() throws Exception {
      LinkedList browsers = new LinkedList();

      InputStream is = TestParallelOnDemand.class.getResourceAsStream("/browser.properties");
      browserProps.load(is);

      String[] rawBrowserStrings = browserProps.getProperty("browsers").split(",");
      for (String rawBrowserString : rawBrowserStrings) {
        if (rawBrowserString.indexOf(";") != -1) {
          String[] browserParts = rawBrowserString.split(";");
          browsers.add(new String[] { browserParts[0], browserParts[1], browserParts[2] });
        } else {
          browsers.add(new String[] { rawBrowserString, "", "" });
        }
      }
      return browsers;
    }

    @Before
    public void setUp() throws Exception  {
        if (parallelProps.getProperty("ondemand").equals("true")) {
          selenium = new DefaultSelenium("saucelabs.com", 4444, this.json, "http://saucelabs.com");
        } else {
          selenium = new DefaultSelenium("localhost", 4444, this.browser, "http://localhost:3000");
        }
        selenium.start();
        selenium.setTimeout("90000");
        selenium.windowMaximize();
    }

    @Test
    public void testSauce() throws Exception {
        this.selenium.open("/");
        assertEquals("Cross browser testing with Selenium - Sauce Labs", this.selenium.getTitle());
    }

    @After
    public void tearDown() throws Exception {
        selenium.stop();
    }
}

So what has changed?

The @Parameters are loaded differently again. When the script was only ever going to run locally, the browser string provided enough information. But since it’s also going to be run in OnDemand, there needs to be some extra information for the OS and browser version. Using the same pattern as part two, the browser string information has been moved from the test code and into an external properties file.

# order is important! - OS/browser/version
browsers=Windows 2003;*firefox;3.6.,Windows 2003;*googlechrome;3.,

OnDemand integrates with Selenium scripts by sending a JSON string with the configuration information to their server, which is why there is a new decision in the @Before method. An important design pattern when creating scripts that run locally or in some other environment is to add a switch in the script to determine where to go. You don’t want to have to modify code itself in order to run between different environments.

The JSON itself is an interesting challenge. Just as changing the source code didn’t make sense when parameterizing the individual browser strings, hard coding the JSON doesn’t make much sense either. To build the JSON in code, the Gson library was used. Here is the ConnectionParameters class that Gson is building the connection information from.

package com.saucelabs.ondemand;

import java.io.InputStream;
import java.util.Properties;

public class ConnectionParameters {
  // hyphenated strings like access-key and browser-version need to be camel-case
  public String username;
  public String accessKey;
  public String os;
  private String browser;
  public String browserVersion;
  public String jobName;

  // gson does not convert transient fields
  private transient String propertiesFile = "/ondemand.properties";
  private transient Properties ondemandProperties = new Properties();

  public ConnectionParameters() throws Exception {
    InputStream is = this.getClass().getResourceAsStream(propertiesFile);
    ondemandProperties.load(is);
    this.username = ondemandProperties.getProperty("username");
    this.accessKey = ondemandProperties.getProperty("access-key");
  }

  // ondemand browser strings cant start with the *
  public void setBrowser(String browser) {
    if (browser.startsWith("*")) {
      this.browser = browser.substring(1);
    }
  }

  public String getBrowser() {
    return this.browser;
  }
}

It should also be no surprise by now that the username and access key is loaded from an external file, as that can change and we wouldn’t want to recompile for something like that.

The only other thing of interest is the getter/setter for the browser string. Recall that the string can be used by either the local Se-RC server or OnDemand. Currently, OnDemand does not like having the * at the beginning, so we take care of that behind the scenes from the script.

With this bit of infrastructure in place, we can run our scripts against any OS/Browser/Version combination that OnDemand currently supports or you have handy on your own hardware, and it will automatically scale execution to run each method in parallel.

The example used here is a little extreme as the test itself is really only two lines, but in a real implementation scenario I would:

  • Create a custom formatter for Selenium IDE (or Sauce IDE) so when you export the recorded script, it has everything your particular framework requires
  • Create a class hierarchy for the scripts to move all the non @Test methods out of the class to make it a bit tidier

And so concludes the series on how to run Selenium scripts in parallel using JUnit 4. To see the full project, including the Maven pom, check out Parallel Test Examples in the Github repo.

Share

Parallel JUnit 4 and Selenium – Part Two: External Properties

October 22nd, 2010 by Adam Goucher

In the first part of this series, we saw how to make use of the Parameterized runner that comes with JUnit 4 to execute our tests across multiple browsers. But that came with two penalties. The increasing test duration will be addressed in part three, but today we’ll address how to modify the browsers used without having to recompile our test.

As someone who sees a lot of different teams’ Selenium code, I’m appalled at how often a simple change, such as adjusting which browser(s) to run against, requires a recompile. To me, that is a nasty code smell. Things that do not materially affect the purpose of the script and are often changing should be externalized out of the code. In our case, we are going to use a Properties file inside the @Parameters method. Nothing else in the script has changed.

@Parameters
public static LinkedList browsersStrings() throws Exception {
  LinkedList browsers = new LinkedList();

  InputStream is = TestProperties.class.getResourceAsStream("/environment.properties");
  environmentProps.load(is);

  String[] rawBrowserStrings = environmentProps.getProperty("browsers").split(",");
  for (String rawBrowserString : rawBrowserStrings) {
    browsers.add(new String[] { rawBrowserString });
  }
  return browsers;
}

Nothing special here, just standard loading of a properties file that has a browsers key, which is a comma separated list of allowable Selenium browser strings. Now the same compiled code will run different browsers without actually change the Java code.

The result is much nicer, but still has to address the problem of a massive increase in execution. That will be the subject of part three of this series.

To see the full source code the snippet was pulled from, see TestProperties.java

Share

Parallel JUnit 4 and Selenium – Part One: Parameters

October 20th, 2010 by Adam Goucher

Though not the first testing framework created, JUnit has garnered plenty of mainstream attention and created a wave of followers.

One of these is TestNG, which was created to address, among other things, the difficulty of passing parameters into scripts, as well as run scripts in parallel. For these reasons, the Selenium community has largely chosen TestNG as the framework of choice. But JUnit 4 can also address these topics using the @RunWith annotation and a custom runner.

This is part one in a series of posts illustrating how to create parameterized, parallel scripts that can be run either on your local infrastructure or on Sauce OnDemand.

Before we start into code, here’s a bit of context. I’ve been writing test frameworks and harnesses for a decade now and know the frustration of finding half answers online that have been stripped down for easy blog post write-ups. The intent of my post is to not inflict you with that experience. Instead, I’ll discuss parallel execution in the context of a full project – complete with Maven pom.xml and all headers in their proper places. There will be places in this post where they are omitted, so you are recommended to clone the Sauce Labs Parallel Test Examples github repo.

We begin our journey with @RunWith(Parameterized.class). This annotation will iterate over each test method with a set of parameters of your choosing. For Selenium scripts, the part that begs for parallelization is the browser, so that is what we will parameterize first.

Have a look at TestParameters.java, as there are a couple of interesting things beyond the standard @Before, @Test and @After.

package com.saucelabs.cc;

import java.util.Collection;
import java.util.List;
import java.util.Arrays;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

import com.thoughtworks.selenium.DefaultSelenium;
import com.thoughtworks.selenium.Selenium;

@RunWith(Parameterized.class)
public class TestParameters {
    private Selenium selenium;
    private String browser;

    public TestParameters(String browser){
        super();
        this.browser = browser;
    }

    @Parameters
    public static Collection browsersStrings(){
      return Arrays.asList(new Object[][] { {"*firefox"}, {"*googlechrome"} });
    }

    @Before
    public void setUp() throws Exception {
        selenium = new DefaultSelenium("localhost", 4444, this.browser, "http://localhost:3000");
        selenium.start();
        selenium.setTimeout("90000");
        selenium.windowMaximize();
    }

    @Test
    public void testSauce() throws Exception {
        this.selenium.open("/");
        assertEquals("Cross browser testing with Selenium - Sauce Labs", this.selenium.getTitle());
    }

    @After
    public void tearDown() throws Exception {
        selenium.stop();
    }
}

The first thing that sticks out is the @RunWith class annotation. While there are a number of different runners floating around, we are using the Parameterized one that comes with JUnit itself. With this in place, JUnit will look for a method annotated with @Parameters to use as arguments to the constructor. In this case, we currently only have one parameter, which we set as this.browser. Of course, this is then used when launching the browser in @Before.

And how does this address parallelization? Well, the @parameters method must return a Collection that is then iterated over, so you end up executing testSauce twice – the first time with “*firefox” and then with “*googlechrome.”

This is cool from a technical perspective, but has two pretty big flaws. First, the Java code needs to be edited to change browsers, and second, by executing serially, the time it takes to run the scripts increases 100% for every browser added. We’ll address both of those starting with external loading of our @Parameters in the next post of the series.

Share

Video Demo: RSpec + Selenium + Sauce OnDemand

October 6th, 2010 by Ashley Wilson

Thanks to all who tuned in to our live web demo last week, Using RSpec + Selenium to Test Your Ruby Builds Faster. Sauce Labs Developer Sean Grove walked attendees through the steps of writing an RSpec test and using it with Selenium, and then showed how to run that test across multiple browsers in the cloud using Sauce OnDemand.

For those of you who missed it, check out Sean’s sample code on Github (http://github.com/sgrove/rspec_sauce_webinar), and then follow along with the video below. We’re planning another Ruby-based webinar in the future, so make sure to subscribe to our blog’s RSS feed to stay up to date!

Share

Parallel Automated Browser Testing in PHP using Selenium and Sauce OnDemand

September 23rd, 2010 by The Sauce Labs Team

Guest post by Aytekin Tank, co-founder of Interlogy Internet Technologies

If you are in SaaS business, one of the best things you can do for yourself is to setup a continuous automated browser testing environment. It will save you a lot of headache, money, and customers down the road.

As a happy new Sauce OnDemand user, I’d like to share our story with automated browser tests and how we integrated a PHP based web app with Sauce OnDemand.

Our web application, JotForm, is a web based form builder. Using its drag and drop WYSIWYG form editor, you can create web forms quickly and easily. Since our core application is written in JavaScript, it is extremely important for us to do automated cross-browser tests. We accomplish this by using Selenium.

We use Hudson to automate our releases. We do continuous integration and deployment. We release our product ten times a day every day. When a developer makes a commit, Hudson executes hundreds of unit, integration, and cross browser tests. Our service has over 300,000 users and they can be very loud when we mess up. That’s why we care a lot about our tests. If we mess up once, we investigate how we missed it and make sure to add new tests so something like that never happens again.

Why Automated Selenium Tests?

If you are developing a web app, you must have already discovered that unit tests don’t work well. They can only really cover the server side and little bit on the JavaScript, but when it comes to testing your code on a multi-browser environment, they are pretty much useless. Browsers constantly change. Just this week, our developers spent a lot of time making our app work properly on IE 9 Beta.

Ideally, you would want to test your app on all browsers and all platforms. Selenium is the best tool for the job. You can easily record tests using Selenium IDE and integrate them with your continuous integration tools, such as Hudson or Cruise Control.

Why NOT Automated Selenium Tests In-house?

The biggest problem with running Selenium tests is creating and managing many testing environments. Usually if you are running Selenium tests constantly on a machine, it cannot be used by anyone. So in practice, they have to be on dedicated machines. And if your releases depend on the Selenium tests, you have to make sure they are always on and running smoothly. It is a lot of maintenance headache.

The other problem with Selenium is that it is slow. It takes Selenium a long time to launch up the browser to do the tests. Unless you have many dedicated machines, the tests have to run sequentially.

To speed up our Selenium tests, I looked for ways to run them in parallel. My first approach was to use Amazon EC2, since I am already pretty familiar with it. That turned out to not be a great solution. We still had to maintain many selenium environments. Keeping many EC2 instances always on can be quite expensive. Launching new instances on demand turned out to be too slow for our case.

Final Solution: Sauce OnDemand

Then, I found out about Sauce Labs’ Sauce OnDemand service. This is basically an on demand service that lets you run Selenium tests instantly on the cloud. It has many good code samples. So, setting it up was pretty straightforward. The great thing about Sauce OnDemand is that you can choose from many browser setups and run your tests on them instantly. No need to maintain dedicated machines.

Unfortunately, there was no documentation about running tests in parallel in PHP. So, we first looked into using PHPUnit’s new parallel processing features: @runTestsInSeparateProcesses and @runInSeparateProcess. They turned out to be too buggy to be usable at this time. So, we hacked together a simple script that runs the tests on the shell as background processes.

A Simple Example

I am a big Perl junkie, but since our existing unit and integration tests were written in PHPUnit, we wanted to keep our Selenium tests consistent and write them in PHP. Below, you can find a simple example showing how you can run automated cross-browser Sauce OnDemand tests on PHP. We hacked this together quickly so it is not documented or optimized. But it should give you a good jump-start:

1. BrowserTests.php: Includes actual Selenium tests and simplified for this example with a single test. On our production version, we have all the tests here, since we prefer to save time and run all tests at once on a specific browser. You can create these tests using Selenium IDE or Sauce IDE.

2. allBrowserTests.php: You can run this on the shell then add it to Hudson or any other Continuous Integration tool. Basically, this script runs Selenium tests on the background in parallel. It then checks their log files to see if they finished successfully or failed.

3. SeleniumTestHelper.php: This is the integration script with Sauce Labs. You should change the domain name, Sauce username and access key to your own.

Download all files files here. You must first have phpunit installed to use them.

Happy testing!

Aytekin Tank is a co-founder of Interlogy Internet Technologies. Their flagship product is JotForm online form builder.

Share

Cucumber Sauce: Cross-browser testing in parallel

September 19th, 2010 by The Sauce Labs Team

I’ve been meaning to release a “standard” cucumber-in-parallel cross-browser project for awhile now, but I just got around to it this weekend. It’s called Cucumber Sauce.

Highlights:

  • Run your cucumber tests across different browsers all at the same time
  • Browsers are configurable via a yaml file
  • Multiple browser config files are easy to setup, and one can be passed in when running the rake task. Have a browsers_core.yml file to run while developing, and a browsers_full.yml to run before pushing to production
  • Designed to run tests ultra-cleanly. Adds time overhead initially, but tests are all independent of one another, and can be made to run in parallel at the scenario level in the future. Follow this template or you’ll lose that ability to speed up your tests.

Check it out at the Cucumber Sauce github repo.

Share

Selenium Tips: How to Coordinate Multiple Browsers in Sauce OnDemand

September 3rd, 2010 by Santiago Suarez Ordoñez

Let’s imagine your team is making a chat application. Nothing too fancy, just something simple in which you need to make sure multiple users get together, talk, and read each other.

Doesn’t sound that bad, huh? But how about coordinating those browsers? You need to have different sessions running at the same time in coordination and interacting with each other through your web app.

Now, when we think about parallelization and multiple browsers, Sauce OnDemand is, of course, our first answer, but here, there can be a small limitation. Sauce OnDemand has a special timeout that waits for new commands for no more than 90 seconds*. If it doesn’t get any new command, it will kill the job and assume something went wrong.

This effects us, as we need to get multiple browsers running in parallel and in coordination. As we request them one after another, depending on the amount of browsers and the load our service is put under, the first ones might timeout while waiting for the others to come.

To avoid this, the best solution is to write a multi-threaded script, in which browsers will send heartbeat commands while they are waiting for the rest. Multi-threading can be done with every programming language, but I decided to write my example in Python because I think it’s understandable for almost anyone, even if you’ve never seen Python before. The complete example is in github as a gist, and here is a brief description of what each interesting part is doing:

def get_browser_and_wait(browser, browser_num):
    print "starting browser %s" % browser_num
    browser.start()
    browser.open("/") # if we get here, OnDemand already gave us the browser
    browsers_waiting.append(browser)
    print "browser %s ready" % browser_num
    while len(browsers_waiting) < len(browsers):
        print "browser %s sending heartbeat while waiting" % browser_num
        browser.open("/")
        time.sleep(3)

get_browser_and_wait is the function that will take care of the ‘start the browser’ request in OnDemand and wait till it comes back. Once the server is ready, it will keep sending open commands as heartbeats and waiting until the other requested browsers are ready and waiting too.

thread_list = []
for i, browser in enumerate(browsers):
    t = Thread(target=get_browser_and_wait, args=[browser, i + 1])
    thread_list.append(t)
    t.start()

for t in thread_list:
    t.join()

This is the magic multithreading part, in which we iterate over every Selenium instance and call get_browser_and_wait with it. Once we send each browser to a thread, the main thread will continue and get to the t.join() part. By this method, the main thread will wait for every browser thread to complete in order to proceed with the rest of the code.

Again, the full example is up in github: http://gist.github.com/511658. If you’re interested in learning what you can use this kind of test for, stay tuned!

* This helps avoid running (and charging) tests that get disconnected or crash.

Share

Running your Selenium tests in parallel: Perl

April 1st, 2010 by The Sauce Labs Team

The Test::WWW::Selenium does a good integration of Selenium with Perl testing framework. However by default, tests runs in sequence. One easy solution is to use the Thread::Pool::Simple module to run the test functions in parallel. Short testing showed that this parallel version ran for 19 seconds vs 39 seconds for the serial run.

#!/usr/bin/env perl

use Thread::Pool::Simple;
use Test::More "no_plan";
use Test::WWW::Selenium;

sub test_page() {
    my ($url, $input, $button) = @_;

    my $sel = Test::WWW::Selenium->new(
        host => "localhost",
        port => 4444,
        browser => "*firefox",
        browser_url => $url,
    );

    $sel->open($url);
    $sel->type_ok($input, "Sauce Labs");
    $sel->click_ok($button);
    $sel->wait_for_page_to_load_ok(5000);
    $sel->body_text_like(qr/Selenium/);
    $sel->close();
}

sub test_google() {
    &test_page("http://google.com", "q", "btnG");
}

sub test_yahoo() {
    &test_page("http://yahoo.com", "p", "search-submit");
}

sub test_bing() {
    &test_page("http://www.bing.com", "q", "go");
}

sub worker() {
    my $func = shift;
    # We can't pass CODE items to a pool, so we use eval
    eval("&$func();");
}

my @names = (
    "test_google",
    "test_yahoo",
    "test_bing",
);

my $pool = Thread::Pool::Simple->new(
    min => 3,
    max => 10,
    do => [\&worker],
);

foreach $name (@names) {
    print "Adding $name\n";
    $pool->add($name);
}
$pool->join();

Converting the code to work with Sauce OnDemand is a breeze as well, we will also use the job-name attribute to assign name for our jobs.

#!/usr/bin/env perl

use Thread::Pool::Simple;
use Test::More "no_plan";
use Test::WWW::Selenium;

# Replace username and access-key with your real
# Sauce OnDemand username and access-key
my $settings = qq(
{
    "username" : "YOUR-SAUCE-USER-NAME",
    "access-key" : "YOUR-SAUCE-ACCESS-KEY",
    "os" : "Linux",
    "browser" : "firefox",
    "browser-version" : ""
    "job-name" : "_JOB_NAME_"
}
);

sub test_page() {
    my ($name, $url, $input, $button) = @_;

    my $opts = $settings;
    $opts =~ s/_JOB_NAME_/$name/; # Add job name

    my $sel = Test::WWW::Selenium->new(
        host => "ondemand.saucelabs.com",
        port => 80,
        browser => $opts,
        browser_url => $url,
    );

    $sel->open($url);
    $sel->type_ok($input, "Sauce Labs");
    $sel->click_ok($button);
    $sel->wait_for_page_to_load_ok(5000);
    $sel->body_text_like(qr/Selenium/);
    $sel->close();
}

sub test_google() {
    &test_page("Google", "http://google.com", "q", "btnG");
}

sub test_yahoo() {
    &test_page("Yahoo", "http://yahoo.com", "p", "search-submit");
}

sub test_bing() {
    &test_page("Bing", "http://www.bing.com", "q", "go");
}

sub worker() {
    my $func = shift;
    # We can't pass CODE items to a pool, so we use eval
    eval("&$func();");
}

my @names = (
    "test_google",
    "test_yahoo",
    "test_bing",
);

my $pool = Thread::Pool::Simple->new(
    min => 3,
    max => 10,
    do => [\&worker],
);

foreach $name (@names) {
    print "Adding $name\n";
    $pool->add($name);
}
$pool->join();
Share

A bit of sugar and parallelism for Rails and RSpec

March 9th, 2010 by The Sauce Labs Team

Even though we focus very heavily on full-stack acceptance testing for the rails world, we know other forms of automated tests are critical as well. Our rails developers here make pretty heavy use of RSpec unit tests, and it’s nice to understand how to run those in parallel as well.

If you’re looking at how to setup a rails and selenium testing environment, check out our last post.

Parallelize the specs

We’ll use the excellent parallel_specs to beat a bit of parallelism into our specs. It prepares a separate database for each test environment, groups the specs to divide amongst processes, and then starts up a rails environment with a separate database for each group of processes.

I’ll paraphrase the installation instructions for convenience.

Install the required plug-in/gem:

sudo gem install parallel
script/plugin install git://github.com/grosser/parallel_specs.git

Here’s the semi-ingenious point – yaml can interpret ERB, so we can pass in an environment variable to the database.yml specifying at launch which database we want it to connect to.

Open config/database.yml and add the following:

test:
  adapter: sqlite3
  database: db/xxx_test<% ENV['TEST_ENV_NUMBER'] %>.sqlite3
  pool: 5
  timeout: 5000

(You can of course replace xxx_ with your project name)
So for example, to have our tests run against the xxx_test2 database, we would use:

export TEST_ENV_NUMBER=2; rake db:test:prepare

But it doesn’t make much sense to invoke it manually. That’s what plug-ins are for! Let’s go head and create/migrate a few test databases:

export TEST_ENV_NUMBER=0; rake db:test:create; rake db:test:migrate;
export TEST_ENV_NUMBER=1; rake db:test:create; rake db:test:migrate;
export TEST_ENV_NUMBER=2; rake db:test:create; rake db:test:migrate;

Great, now you’re able to run your non-Selenium tests in parallel!

But what about Selenium tests?

Stay tuned for our article on Sauce Labs’ SpecStorm plugin, that allows you to run your Selenium tests in true parallel fashion with Selenium Grid or our very own Sauce OnDemand service.

Share