Using 'else' in Python

Python allows you to use the 'else' keyword in places where most other programming languages don't. I assume that you're familiar with the basic if/else where the block after the 'else' is executed when the condition in the if-statement isn't true.

In Python you can also add 'else' to a for loop, a while loop as well as a try/except section. I was aware of the ability to use 'else' in combination with a for loop but I only learned of the try/except/else combination a few days ago so perhaps it's worth spreading the word about this.

Using 'else' in combination with 'if'

For completeness it's important to discuss the basics of the if/else first as the other uses of 'else' in Python differ slightly differently from this.

if condition:
    # executed when condition == True
    ...
else:
    # executed when condition == False
    ...

Despite the familiarity of this construct, it's worth noting that the reason why the else block is executed is explicitly mentioned in the code (i.e., the condition in the if-statement).

For the other uses of 'else' you'll see that this isn't the case. You'll need to have read a Python guide (or this article) to know when those 'else' blocks are executed.

Using 'else' in combination with 'for'

This is where things become interesting as the else statement can lead to cleaner code. It frequently happens that we wish to iterate over a collection of objects and when a certain condition is met we break from the loop. For example:

for car in cars:
    if needs_repair(car):
        send_for_repair(car)
        break

In this example we iterate over a collection of cars and we stop when we've found a car that needs a repair. No further cars will be examined when the first broken car has been found.

Now, what if we wish to take a certain action when we haven't found any car for repair? We could introduce a variable car_found_for_repair for this purpose:

car_found_for_repair = False
for car in cars:
    if needs_repair(car):
        send_for_repair(car)
        car_found_for_repair = True
        break
if not car_found_for_repair:
    # do something
    ...

Python allows an elegant solution by adding an else statement to the for loop. The block of code after the else is executed when we have not performed a break in the loop. The code now looks as follows and it behaves the same:

for car in cars:
    if needs_repair(car):
        send_for_repair(car)
        break
else:
    # do something
    ...

In other words, when the for loop completes successfully (i.e., without being exited by a break statement) the else section is executed. But when we break from the loop at some point then that section won't be executed.

Using 'else' in combination with 'while'

This is feature in the Python language that I only discovered when reading the grammar specification of the language. This construct may not be that popular or, at least, I have not seen it in the Python code that I have read over the years.

Similar to the for loop you can add an else statement to a while loop and it'll be executed when a break has not been performed in the loop. I'll leave the example out as it would be similar to the above one.

Using 'else' in combination with 'try' and 'except'

The basics of a try/except section in Python consist of:

try:
    # run some code
except ValueError:
    # catch exception when it is raised

This means the code within the try is executed and when a ValueError is raised execution continues in the except block. Some of you will know that a finally block can be added to execute code after a try/except regardless of whether an exception was raised:

try:
    # run some code
except ValueError:
    # run this when ValueError is raised
finally:
    # run this after the code and any exception handling code

Similar to the for loop example above, what if we wish to execute some code when no exception has been raised? We could introduce another boolean variable for that purpose but Python allows you to add an else block:

try:
    # run some code
except ValueError:
    # run this when ValueError is raised
else:
    # run this only when no exception is raised
finally:
    # run this after the code and any exception handling code

The else section is only executed when no exception at all is raised.

In this example we are only catching ValueError exceptions. When, for example, an IndexError is raised then Python will run the try block up until the exception, the finally block and the ValueError won't be caught (yet).

Python 3: Using "yield from" in Generators - Part 2

In the previous part of this tutorial, I have discussed the basics of generators, some differences between functions and generators. I have also hinted at some benefits of the new yield from syntax. I recommend reading that part of the tutorial first before continuing unless you're well familiar with generators in Python.

All examples in this part of the tutorial will be in Python 3.3 unless stated otherwise.

Basic binary tree

In this second part I'll build upon a basic binary tree example that demonstrates the uses of the yield from syntax. For the sake of this example, I'll let each node in the tree have a list of children rather than, as is often done, let each node point to its parent node.

Here is the implementation of this data structure without the new syntax:

class Node:

    def __init__(self, value):
        self.left = []
        self.value = value
        self.right = []

    def iterate(self):
        for node in self.left:
            yield node.value
        yield self.value
        for node in self.right:
            yield node.value

def main():
    root = Node(0)
    root.left = [Node(i) for i in [1, 2, 3]]
    root.right = [Node(i) for i in [4, 5, 6]]
    for value in root.iterate():
        print(value)

if __name__ == "__main__":
    main()

As expected, this prints the values 1, 2, 3 (of the left children), the value 0 (of the root node) and the values 4, 5, and 6 (of the right children).

However, this example only iterates over the root node and its children; it won't recursively iterate over the children of the child nodes (if there happened to be any). Let's modify the iterate() method to make that happen:

def iterate(self):
    for node in self.left:
        for value in node.iterate():
            yield value
    yield self.value
    for node in self.right:
        for value in node.iterate():
            yield value

This version also calls iterate() on each of the child nodes so this yields each of the nodes in the tree. This code is rather cumbersome: for each of the left and right children we yield all the values by using explicit iteration. This is where yield from simplifies the code:

def iterate(self):
    for node in self.left:
        yield from node.iterate()
    yield self.value
    for node in self.right:
        yield from node.iterate()

Other aspects of generators

Now, the story would be over pretty quickly if that was all there was to it. To appreciate yield from I'll need to explain an alternative way of using a generator.

A generator can be controlled using methods such as send() and next(). These and related methods allow you to start, stop and continue a generator rather than having Python handle most of the generator's execution.

For example, instead of the above basic loop:

for value in root.iterate():
    print(value)

we can also write:

it = root.iterate()
while True:
    try:
        print(it.send(None))
    except StopIteration:
        break

The send() method allows you to "send" a value into the generator, which means the yield expression receives that value. That value can be used by assigning it to a variable (i.e., v = yield self.value).

In this case we repeatedly send the value None into the generator and our generator doesn't utilize the value that it sent into it. Effectively this leads to the same result as the previous loop that prints the generator's yielded values.

Benefits of yield from

The primary benefits of yield from come when you've written a generator that uses these techniques and when it needs to be refactored. This means you'll have to subdivide the generator into multiple subgenerators and send / receive values to and from those subgenerators. Rather than rewriting the generator to send values to the subgenerator, you can simply use yield from and the semantics will remain the same.

There are some caveats and some situations which aren't handled but that's beyond the scope of this tutorial (I'll refer you to the official proposal for details).

Another example generator

Let's create a small generator that demonstrates the above:

def node_iterate(self):
    for node in self.left:
        input_value = yield node.value
        # ...
    input_value = yield self.value
    # ...
    for node in self.right:
        input_value = yield node.value
        # ...

This generator only iterates over the node and its immediate children. Any value sent into the generator is stored in the variable input_value which is then available for computations (the # ... sections).

For example, the code that uses this generator may perform various computations and it passes intermediate values back into the generator so that they can be used there. The following shows how the yielded values are summed and the subtotals are passed back into the generator:

total = 0
it = root.node_iterate()
it.send(None)
while True:
    try:
        value = it.send(total)
        total += value
    except StopIteration:
        break

Refactoring iteration over children

It seems repetitive to have the same code for both the left and right children so we can refactor that into:

def child_iterate(self, nodes):
    for node in nodes:
        input_value = yield node.value
        # ...

def node_iterate(self):
    yield from self.child_iterate(self.left)
    input_value = yield self.value
    # ...
    yield from self.child_iterate(self.right)

Note that it is not recommended practice to call a class method (self.child_iterate) by passing an instance variable (i.e., the self.left and self.right arguments) but that is a topic for a different post. The important part is that we can only refactor in this way as a result of the new yield from semantics.

When we send values into node_iterate() they are automatically passed into the subgenerator child_iterate() where input_value receives a value.

Prior to Python 3.3, if we had written:

for value in self.child_iterate(self.left):
    yield value

then this would yield values from child_iterate() but input_value would remain None. To make it work as expected, we would need to explicitly send() the values from node_iterate() into child_iterate().

More refactoring

Lastly, we can perform one more refactoring step leaving us with only one section where the input_value is received and used:

def process(self):
    input_value = yield self.value
    # ...

def child_iterate(self, nodes):
    for node in nodes:
        yield from node.process()

def node_iterate(self):
    yield from self.child_iterate(self.left)
    self.process()
    yield from self.child_iterate(self.right)

As you become more familiar with using send() and related functions, you'll notice that yield from makes life easier. I suggest reading the exact semantics in the specification to know when and how you'll be able to use it.

Python 3: Using "yield from" in Generators - Part 1

A new feature in Python 3.3 is the ability to use yield from when defining a generator. My current experience with Python 3 has been with Python 3.1 so I've upgraded my installation in Ubuntu to Python 3.3 to explore "yield from". In this tutorial, in two parts, I'm going to outline some examples and potential use cases.

If you're already familiar with generators then you can skip the first section of this article and continue with the specifics of "yield from" below it.

Brief introduction to generators

In Python a generator can be used to let a function return a list of values without having to store them all at once in memory. This also allows you to utilize the values immediately without having to wait until all values have been computed.

Let's look at the following Python 2 function:

def not_a_generator():
    result = []
    for i in xrange(2000):
        result.append(perform_expensive_computation(i))
    return result

When we call not_a_generator() we have to wait until perform_expensive_computation has been performed on all 2000 integers.

This is inconvenient because we may not actually end up using all the computed results. For example, we may wish to use the above function as follows:

for element in not_a_generator():
    if not certain_condition(element):
        break
    # ...

Depending on the behaviour of certain_condition, it could be that we only use the first 700 values returned from not_a_generator() and we would have wasted time on computing the remaining values.

We can turn not_a_generator() into a generator by using the yield keyword:

def my_generator():
    for i in xrange(2000):
        yield perform_expensive_computation(i)

Difference between a function and a generator

Calling this generator is no different from our previous function:

for element in my_generator():
    if not certain_condition(element):
        break
    # ...

The difference is that whenever the generator "yields" a value the execution of the generator is paused and the code continues where the generator was called. That means the value of the variable element is known and the execution of the code can continue.

When the if-statement involving not certain_condition(element) is true then we no longer need the generator to compute the remaining values. On the other hand, if the for loop finishes for the current value of element then the generator is executed again until it yields another value.

The above will continue until all values of the generator have been yielded or until we no longer need the generator.

Note that we can easily get the behaviour of not_a_generator() back by storing the results of my_generator in a list: list(my_generator()). This forces the generator to be executed fully and to yield all its values.

Also note that a generator can't contain return value statements: using one or more yield statements turns a function into a generator and you can only use yield to return values to where the generator is called.

Why yield from?

When a new feature is introduced in a programming language we should ask ourselves if and why this was necessary. The short explanation is that it enables you to easily refactor a generator by splitting it up into multiple generators.

For basic purposes we can use plain generators to compute values and to pass those values around. The benefits of yield from should become clear when we know what it does and in which situations it can be used.

Consider a generator that looks like this:

def generator():
    for i in range(10):
        yield i
    for j in range(10, 20):
        yield j

As expected this generator yields the numbers 0 to 19. Let's say we wish to split this generator into two generators so we reuse them elsewhere.

We could rewrite the above into:

def generator2():
    for i in range(10):
        yield i

def generator3():
    for j in range(10, 20):
        yield j

def generator():
    for i in generator2():
        yield i
    for j in generator3():
        yield j

This version of generator() also yields the numbers 0 to 19. However, it feels unnecessary to specify that we wish to iterate over both generator2 and generator3 and yield their values. This is where yield from comes in. Using this new keyword we can rewrite generator into:

def generator():
    yield from generator2()
    yield from generator3()

This gives the same result and it is much cleaner to write and maintain. It is also quite similar to the way functions are refactored and split up into multiple functions. For example, a large function can be split into several smaller functions, f1(), f2() and f3(), and the original function simply calls f1(), f2() and f3() in sequence.

Useful situations for 'yield from'

Those of you familiar with the itertools module may note that the above example is rather simple and does not truly justify introducing a new keyword in the language.

Using the chain function from the itertools module we also could have written:

from itertools import chain

def generator():
    for v in chain(generator2(), generator3()):
        yield v

It can be argued that the yield from syntax and semantics are slightly cleaner than importing an additional function from a module but, leaving that aside, we have not yet seen an example where yield from has enabled us to do something new. As we will see later in this tutorial, the main benefit of yield from is to allow easy refactoring of generators.

It should be noted that it is not necessary for new programming language syntax to also introduce new semantics (i.e, to express something that was not possible before).

Many languages introduce syntax, often called syntactic sugar, to make it easier to write something that would otherwise be cumbersome to write. For example, Haskell allows you to easily write a string as "example" which is shorthand syntax for ['e', 'x', 'a', 'm', 'p', 'l', 'e'] (a list of characters). Cleaner and more maintainable code can suffice to introduce new syntax into a programming language.

Binary tree example

The proposal that introduces the yield from syntax provides a few examples to demonstrate the new behaviour. One of them is a basic binary tree and we can traverse the nodes of the tree using normal for loops as well as the new yield from syntax.

In the next part of this tutorial we will build upon this example to explain the benefits of yield from.

2012: Year in Review

As we move from one year to the next it's a good time to reflect and look back on what the past year has brought us. Some of the main events in 2012 for me include a mayor road trip in the United States along various places, changing jobs and moving abroad. I'm now living in London and I've moved from working at a well-established media company (Lukkien) to a growing technology startup (Rangespan).

Travel

I've travelled to various countries and visited places that I haven't been to before. I've worked in three different cities (Ede, Amsterdam, London) and I've spend some time exploring Tucson, Phoenix, the Grand Canyon, the Hoover Dam, Las Vegas, Los Angeles, Barcelona, Madrid and London (twice).

Furthermore, I've seen precious gems and minerals at the Tucson Gem & Mineral Show, the largest event of its kind in the world. Other travel highlights include immersing myself in the Spanish cities Barcelona and Madrid and being in London during the 2012 Olympic Games.

Career

Professionally I've learned a lot in both jobs about software development and software testing as well as building a stable software platform and keeping the systems running. I've shared my knowledge by answering many questions on Stack Overflow and I aim to write more about technical subjects here on this blog in 2013.

Some new technologies that I've used in 2012 include Selenium (see my previously posted tutorial), Celery (in a task-based online control panel for a render farm), MongoDB and Amazon Web Services.

Expat in London

Moving abroad can take quite some time and effort (see my post on finding a room in London) and I still haven't fully settled in or taken care of all the necessary paperwork. Nonetheless I think it has been worth the effort thus far and it's going to pay off in the near future. Based on my previous experience with living in Melbourne, Australia, and my time so far in London, the year 2013 has at least all the opportunities of becoming a good year for me.

On Finding a Room in London

Here are some tips for finding a room in London. This is based my personal experience to find a room through SpareRoom.co.uk and your mileage may vary. Some things to keep in mind is that I moved in September, a busy time of the year, and that I didn't have connections or friends to speed up the process.

  • Get lucky or lower your expectations: The room that you want is certainly out there but whether you'll get it is a different story. I've found and replied to rooms that looked perfect in terms of commute time, location, flatmates, the whole shebang. But alas, I didn't get those room for a variety of reasons, most likely because someone else was chosen.

  • Call instead of email: This is the only way to get noticed. My first approach was to send loads of emails to all rooms that looked interesting but that didn't work. I've learned that rooms in London are in high demand and tenants are frequently found for a room in a matter of hours or days. That means people take a room without viewing it (do they?) or their approach is different from mine. I can see why that is sometimes a decent approach (e.g., far away from the UK and unable to view it) but I'm currently in London so I'd rather view the room and meet the flatmates first.

  • Spend some time on the message you send: If you do send an email make sure you spend some additional time to craft the message. I'm not necessarily talking about grammar and punctuation (but do check that too). I'm more talking about the fact that you now only have one shot to convince the current flatmates to get back to you.

Even if you call they may ask you to send a message which allows them to sift through the interested people and find the ones that they would possibly want as flatmate. I would do that too if I were looking for a flatmate but it stacks the odds against potential tenants as they know more about me than I do about them. Most ads generally don't have much info about the current flatmates so you're running blind in that respect.

When you've found a room that belongs to an agency then it's easier as you're not directly talking to the flatmates. The agency wants to make money so they're more likely to arrange a viewing.

What can also happen is that current flatmates have run out of time in their personal agenda to make viewings possible. You may have called quickly once the ad appeared (or so you thought) but they're way too busy already with scheduled viewings. Better luck next time.

More search tips when looking for a room

Usually when looking at an ad you're looking for the basic criteria (location, price) but you're also looking for potential red flags that (should) make you reconsider.

You're looking for something (e.g., a good landlord) but you only have limited information. A great landlord may write a room ad in ALL CAPS and crappy English but it's more likely that he/she would spend some more time on crafting a decent ad.

Some other tips:

  • Don't necessarily restrict your search by checking all the boxes. I initially had "sharing with professionals" ticked but that also excludes flats where that information of other flatmates has not been filled in or where the flat is half students, half professionals.

  • Try not to confuse the price per week with the price per month. You can't simply multiply the price of a week by four and expect that to be the price per month. To obtain the price per month, you need to compute: (price per week * 52) / 12. As there are 12 months in a year, you can't multiply the price per week by four because that would lead to 4 * 12 = 48 weeks and not 52 as usual (in most years).

Why London is considered expensive

On a fun note, you sometimes encounter rooms that are outright bizarre. Just to give you an idea of how expensive London is, I've included the following photo. For 80 pounds (inclusive) a week you can call yourself the proud tenant of the following room:

That's right: you'll be sharing a television on a fridge with three others, all in one room. When the number of flatmates significantly starts to exceed the number of rooms, you know it's time to mark the room as unsuitable and search for another one.

For this room, the price a month of 320 pounds comes to about 400 euros at the time of writing which goes to show that London is a whole different world compared to student rooms in, e.g., Amsterdam and Utrecht, or even the accommodation that I stayed in in Melbourne, Australia.

Conclusion

In the end everything worked out just fine. This blog post has been in the pipeline for about three months now but I didn't get around to publish it until now. When I moved to London I spent about seven weeks in hostels before finding the place where I'm living now.

Arriving in London in September when many international students are also looking for a place to stay isn't exactly ideal but it also means there are a lot more properties on the market for renting.

Movie Review: Total Recall (2012)

The good thing about movie reviews is that you don't need to be an experienced film critic to write them. So without further ado, here is my first ever movie review of Total Recall (2012) that was recently released.

The main question worth answering is: "Did I enjoy myself?" Yes, I did. But looking at some other reviews online, it does help that I could not compare this remake to the original movie.

I actually happened to be at the UK premiere in Leicester Square, London on August 16. It was interesting to see the stage being built and to see the crowds swell as the actors started arriving. To be honest I had to look up who the actors were as most names did not really ring a bell. This fact should not worry you: I often know singers or actors but not by name or work of art that they have produced.

Nonetheless it was nice to see an actual movie premiere: I don't remember having been to one before. Or is it just that I can't recall one - get it?

Futuristic technology

What I liked about the movie was the futuristic environment in which it was set and the concept of a divided planet with two habitable regions. These happen to be in Britain (United British Federation) and Australia (The Colony) and they are connected by The Fall, a huge transport system that allows you to travel through the center of the Earth.

From a technological perspective this does raise some interesting questions. If we were to actually build this device, how would we do it? Not just the financial costs and construction efforts but it seems safety is a major problem? I mean, an elevator automatically locks up if the ropes fail and an underground tunnel has a safety pedestrian tunnel next to it in case of fire or emergency.

But if you get stuck near the center of the Earth you're not going to take the fire escape stairs back to the surface. Given the number of passenger decks it would also require quite a few emergency elevators to bring people back. I happily "suspended my disbelief" in this regard as it was otherwise very well portrayed.

Another thing: at what point should the gravity switch occur before it becomes problematic for the passengers? In case you have not (yet) seen the movie: this is where the passenger seats are turned upside down to make sure you arrive in proper position at your destination. It makes sense to do it at the center of the Earth because that is where you switch from traveling towards the core to traveling away from the core.

But happens if you do it at three-quarters of your journey (i.e., you have traveled beyond the Earth's core and you are about to arrive at your destination)? Would you experience hanging upside down with half the normal gravity? What if we gradually turn the seats starting when we are at the Earth's core and finishing when we are at three-quarters of our journey?

I have to say The Fall was the most interesting aspect of our supposed future environment together with Rekall's technology to implant memories (Inception anyone?). The rest was rather common with flying cars, many levels of construction and visual displays for communication purposes.

Hollywood as usual

The movie is certainly not bad but it does follow the well-known Hollywood approach of "lots of action, little story". For example, the movie leaves the viewer rather clueless about the protagonist's sidekick whose role is never really explained (other than fighting for the same cause).

The movie entertained me until the end which is a good thing (not like the Batteship movie earlier this year where I had to remind myself not to fall asleep). However, this careful phrasing also tells you that the movie did not blow me away.

On Travel Between London and The Netherlands

As I have recently found a job in London, the question of how to travel cheaply and efficiently between London and my parents' house in the Netherlands becomes a lot more interesting. So far I have always taken a flight to London and in this article I'm going to outline why I'm taking the train next time.

The conventional wisdom seems to be that flights operated by cheap airlines are the way to go. This may be true if you live near an airport (e.g., Schiphol Airport, Eindhoven Airport, Rotterdam The Hague Airport) but if you don't then it turns out the train is worth considering.

Let's consider some examples:

  • a flight by KLM, our national airline, costs a few hundred pounds but you fly into London Heathrow Airport. This makes for a convenient onward journey as this airport is connected to the Tube. Unfortunately, the price alone makes me look for alternatives.
  • a flight by, e.g., RyanAir or EasyJet is cheap but these airlines fly into airports further away from the city (e.g., London Gatwick, London Stansted and London Southend). This means you need to pay for a bus or train to get to central London. These flights can be cheap but you don't get to choose the exact time and date: you simply need to fly whenever it happens to be the cheapest. Furthermore, you need to deal with additional costs if you wish to fly with checked baggage rather than just hand luggage.

You may be able to find a flight of around 50 euros (hand luggage only) and the costs to/from the airport can be up to 20 euros on each side of the border. Depending on the airport I would also need to take a train and bus to get there (Eindhoven Airport) rather than just a train (Amsterdam Airport).

Let's say the price sums up to about 100 euros for door-to-door travel. This looks reasonable but can we travel to London for a similar or better price and without the restrictions on luggage and times?

It turns out that we can.

We first take the train to Roosendaal and then to Brussels, Belgium. From there we take the Eurostar train to London St. Pancras station. The price for a single journey ranges between about 80 euros to 150 euros depending on the travel time. The cheapest fare I've seen is actually 50 euros (about 40 pounds at the time of writing) but that does include a delay of eight hours at night in Brussels.

This price is almost door-to-door as it will take a few more stops with the Tube to get to my destination in London. The price may not differ that much but, taking everything together, travel by train appears to make for a much more convenient door-to-door journey (several trains and the Tube versus several trains, bus, plane, bus/train, Tube). It looks like I'll be giving the train a try on my next journey to London.

On a final note, it really depends on where you live and where you need to go. If you happen to live near an airport then I can imagine that a cheap flight is the best choice.

Updated Website

I've updated this website today with a greatly simplified structure: all writings are now organized by date on the homepage, with no further distinction between blogs or tutorials.

I may add more sections in the future when there is more content but for now this is the simplest system that works. The website is still completely file-based and I can easily add new content.

Running Tests in Python with Selenium 2 and WebDriver

Why use Selenium?

If your website or web application is growing beyond a certain size, it is no longer feasible to manually test everything. There are simply too many features and edge case scenarios to keep clicking through manually. This is where automated testing software comes into view.

Selenium is a set of tools to automatically test your web project in multiple browsers. You can let it click on butttons, enter text in forms and browse your website to see if everything is still behaving as it should.

The approach

In this tutorial we are going to use Selenium WebDriver and Python to write our tests.

The basic idea is:

  • Launch Selenium
  • Write some unit tests in Python
  • Execute the tests in one or browsers

We'll run the tests on Firefox but the approach below can be scaled to multiple browsers, running both locally and in virtual machines.

Before we continue, we'll need to discuss some Selenium terminology:

  • webdriver: a webdriver instance of Selenium that runs the tests for a particular browser configuration. For example, you can run tests for Firefox or for a particular version of Internet Explorer.
  • hub: the hub is the Selenium server which handles communication to all the webdrivers. Each of the webdrivers registers itself with the hub so the hub knows which browser configurations are available for testing.

Install Selenium

First of all, download the stand-alone Selenium server. You'll need to put this .jar file on all the computers or virtual machines that you're going to use for automated testing.

The stand-alone server can run as a hub or as a remote depending on the parameters that you pass upon launching it. Obviously, you'll only need one hub and at least one webdriver to run your tests.

For the Python test scripts, install the Selenium binding from PyPI. To check that you've installed it correctly, run the python command and type:

import selenium

If you get no errors then you have installed it correctly.

Running Selenium

Starting a hub is pretty straightforward:

java -jar selenium-server-standalone-2.18.0.jar -host <IP address> -port 4444 -role hub

Starting a webdriver:

java -jar selenium-server-standalone-2.18.0.jar -host <IP address> -port 5555 -role webdriver -hub http://<IP address of hub>:4444/grid/register -browser browserName=firefox,platform=MAC

In the above commands the IP address is where the hub or webdriver runs.

You don't necessarily need to pass all the parameters (for example, Selenium uses port 4444 by default for the hub) but I think it's better to be explicit.

The -role parameter specifies whether a hub or a webdriver is being launched.

The -hub parameter is needed for webdrivers and it specifies the registration URL to the hub.

The -browser parameter is used to specify the browser configuration. These details will be useful later on to ask the hub if we can run the tests on a particular browser. This means we don't need to know where the webdriver instances are running, we can simply ask the hub to run the tests on, e.g., Firefox and it will handle the rest for us.

You can visit the following URL to view the status of the hub and the registered webdrivers:

http://<IP address of hub>:4444/grid/console

Writing Python tests

For the Python tests we use the unittest module from the Python Standard Library.

from selenium import webdriver
import unittest
import sys

class ExampleTestCase(unittest.TestCase):

    def setUp(self):
        self.driver = webdriver.Remote(desired_capabilities={
            "browserName": "firefox",
            "platform": "MAC",
        })

    def test_example(self):
        self.driver.get("http://www.google.com")
        self.assertEqual(self.driver.title, "Google")

    def tearDown(self):
        self.driver.quit()

if __name__ == "__main__":
    unittest.main()

Running Python tests

You can now simply run the tests with the following command:

python filename.py

where filename.py is the the file with the above Python code. It may take a while for Selenium to open the browser window and execute your commands but after a while it should print the test results in the console. Selenium loads the Google homepage and checks whether the title is "Google".

Multiple browsers and multiple platforms

The above approach is suitable for testing your website in a particular browser but the real benefit comes from testing it in multiple browsers on multiple operating systems.

The browser configuration is specified in the setUp method of the test case. You could create some functionality (for example, by turning the above script into a script with some command-line parameters) to run the test case multiple times with different browser configurations.

You could specify the capabilities as part of the test case itself:

class ExampleTestCase(unittest.TestCase):
    capabilities = None

which means you can instantiate the driver as follows:

self.driver = webdriver.Remote(desired_capabilities=self.capabilities)

Lastly, you need to pass the command-line parameters to the test case before running it:

if __name__ == "__main__":
    ExampleTestCase.capabilities = {
        "browserName": sys.argv[1],
        "platform": sys.argv[2],
    }
    unittest.main()

You can now launch multiple Selenium drivers and run the same test code for multiple browsers and platforms:

python filename.py firefox MAC
python filename.py firefox LINUX
Contents © 2014 Simeon Visser