NatRitmeyer.com

An SDET's thoughts on test automation, TDD, BDD, ruby, CI, devops and software development

Review of Application Testing With Capybara

Matt Robbins of opensourcetester.co.uk fame has written a book called “Application Testing with Capybara”, published by Packt.

There are few books I feel I can recommend to those starting off in test automation but this book is one of them.

Most book begin with the tedious ‘how to install [the software]’ but by the end of Chapter 1 Matt has taken you on the journey from installing the relevant software to having a fully working test. If you’re the type who wants to get down to business quickly (hello!) then this book is for you.

As well as giving clear examples of how to deal with each of the types of elements you’re likely to encounter when automating a web UI with Capybara, Matt also explains the often-confusing rules around finders, scoping and multiple matchers. If you want to better understand how Capybara deals with finding elements then read this chapter!

A final thing: most tutorials you come across on the web only deal with the simplest use cases. Instead of avoiding the more difficult scenarios, Matt deals with them head on in the ‘Ninja Topics’ chapter. If you’re struggling with using Capybara with tools other than cucumber, this is the chapter for you. If you want to get under capybara’s hood, there’s lots of good info. Want to use Capybara with a driver other than Selenium? Everything you need is here :)

So, if you want to learn Capybara, buy yourself a copy of “Application Testing with Capybara”!

SitePrism 2.5

SitePrism 2.5 is made up mainly of contributions from various people. From the HISTORY file:

  • added ability to select iframe by index – thanks to Mike Kelly
  • site_prism now does lazy loading – thanks to MrSutter
  • added config block and improved capybara integration – thanks to tmertens (and to LukasMac for testing it)
  • changed #set_url to convert its input to a string – thanks to Jared Fraser

Due to various lame excuses I’ve taken ages to get this version out so please forgive me.

To get an idea of the main functional changes, see the following sections of the ReadMe:

1
2
3
SitePrism.configure do |config|
  config.use_implicit_waits = true
end
1
2
3
4
5
6
7
8
9
10
class SearchResults < SitePrism::Page
  element :view_more, "li", text: "View More"
end

@results_page.<element_or_section_name> :text => "Welcome!"
@results_page.has_<element_or_section_name>? :count => 25
@results_page.has_no_<element_or_section_name>? :text => "Logout"
@results_page.wait_for_<element_or_section_name> :count => 25
@results_page.wait_until_<element_or_section_name>_visible :text => "Some ajaxy text appears!"
@results_page.wait_until_<element_or_section_name>_invisible :text => "Some ajaxy text disappears!"

Both changes supplied by tmertens, both changes being the last 2 gripes I hear from people about SitePrism :)

Thanks to LukasMac for his extensive testing of this version.

Finally, add your project or company to the Who is using SitePrism page. It would make my day!

Hope that helps!

XPath Queries in Chrome

Running xpath queries shouldn’t be hard. It used to be that you’d have to install plugins into whatever browser you were using. They were often clumsy and always buggy. And, even though querying using CSS selectors has grown more popular in the automated-acceptance-web-test world, there are still sometimes that xpath is the only option.

It turns out that Chrome has built in support for xpath queries in its dev tools. Simply run the following in the console tab:

$x("your_xpath_here")

For example, run the following command in the console when the http://www.google.co.uk page is loaded:

$x("//input[@name='q']")

Here’s a screenshot of $x("your_xpath") in action:

Hope that helps!

A Pattern for Generating Dynamic Test Data

When writing acceptance tests that use test data (and that’s most of them), I like to deal with abstractions of that data rather than the data itself. The reasons for this are:

  • Using abstractions leads to more expressive tests: @expired_account instead of 77481 better relays the intent of the test to the reader
  • Using abstractions frees me from worrying about the details of the test data – no hard coded IDs!
  • Lack of hard coded data means that when something needs to change I only need to change it in one place, leading to more robust and maintainable code
  • Test code is code – it should adhere to all the usual best practices for coding; abstraction is one of them

To illustrate this let’s compare the following two chunks of code:

1
2
3
@bob = Human.new
AccountService.create_account_for @bob
AccountService.should have_account_for @bob

…and…

1
2
3
4
5
6
7
8
9
10
@name = "Bob"
@age = 50
@country = "Botswana"

AccountService.create_account_for @name, @age, @country

xml = AccountService.account_details_for_user_with_details @name, @age, @country
xml.at_xpath("//account/name").text.should == @name
xml.at_xpath("//account/name").text.should == @age
xml.at_xpath("//account/name").text.should == @country

Which one more quickly and clearly transmits the intent of the test to the reader? I’d argue that the first does. The reader is not distracted with unnecessary details; instead they know they are creating a new generic Human object, creating an account with it and then verifying that the account has been created. The second one achieves the same thing but uses a lot more code – figuring out the intent of the test takes more time and effort; the result is less maintainable too.

It doesn’t take much work to use test data abstractions like Human and what little work is required is paid back many, many, many times over. Eg: creating the above Human class is as simple as this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
require 'builder'

class Human
  attr_accessor :name
  attr_accessor :age
  attr_accessor :country

  def initialize
    @name = "Bob"
    @age = 50
    @country = "Botswana"
  end

  def to_xml
    b = Builder::XmlMarkup.new :indent => 2
    b.instruct! :xml, :version => "1.0", :encoding => "utf-8"
    b.Human do
      b.name @name
      b.age @age
      b.country @country
    end
    b.target!
  end
end

Let’s take a look at what’s going on.

The initialize method creates sensible default test data attributes when an instance of the Human class is created. The test data attributes are exposed using attr_accessors so the test data object can be changed in the test. The to_xml method creates an XML representation of the human. This could just as well be a to_json method that spits out a json representation of the human*.

* For those who take abstraction seriously this is a fine place to use the Template or Strategy patterns to decide between json and xml output at runtime.

Being able to create objects containing default test data that can be changed in the test will lead to more expressive test code (have I said that already?). Here’s what I mean:

1
2
@baby = Human.new
@baby.age = 1

The defaults, other than age are sensible, so we’ll leave them. The only one we need to change is age, so we override the default value with 1. Now that the @baby instance of Human has been created, when we see @baby in the test code it will read nicely:

1
AccountService.create_account_for(@baby).should == :too_young

But still, it would be nice to not have to change the age of @baby in the test – why can’t this happen automatically?

Well, by using the Factory pattern you can create specific instances of test data without cluttering up your test code. Factory classes are those that create instances of other classes, hiding any complicated setup. Eg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class HumanFactory
  def self.standard
    Human.new
  end

  def baby
    human = Human.new
    human.age = 1
    human
  end

  def self.too_old
    human = Human.new
    human.age = 500
    human.name = "Methuselah"
    human
  end
end

#and to use the factory...

@standard = HumanFactory.standard
@baby = HumanFactory.baby
@geriatric = HumanFactory.too_old

Thus far we are able to dynamically create objects that represent test data.

There is one more important thing in this pattern – the separation between the data and the representation of the data. When we call to_xml, we get back a string containing an XML representation of the test data object. What’s this for? Well, in your tests you can use the output of the method to pass to services, etc – that’s what the AccountService.create_account_for(@baby) example is doing – the to_xml method would be called inside the create_account_for method.

An essential attribute of the to_xml method is that however many times it is called, unless the data changes it should always return the same thing. For this reason the following would be bad:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
require 'builder'
require 'active_support/time'

class Human
  attr_accessor :birthday

  def initialize
    #@birthday not set to sensible default :(
  end

  def to_xml
    b = Builder::XmlMarkup.new :indent => 2
    b.instruct! :xml, :version => "1.0", :encoding => "utf-8"
    b.Human do
      b.birthday Time.now # <-- this is bad!
    end
    b.target!
  end
end

The problem with the above is that in the to_xml method there is a call to something that will change every time it is called; Time.now. To illustrate the point here’s what happens when you create an instance of the above class and call to_xml on it lots of times:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
irb(main):030:0> puts bob.to_xml
<?xml version="1.0" encoding="utf-8"?>
<Human>
  <birthday>2013-09-24 21:35:14 +0100</birthday>
</Human>
=> nil
irb(main):031:0> puts bob.to_xml
<?xml version="1.0" encoding="utf-8"?>
<Human>
  <birthday>2013-09-24 21:35:18 +0100</birthday>
</Human>
=> nil
irb(main):032:0> puts bob.to_xml
<?xml version="1.0" encoding="utf-8"?>
<Human>
  <birthday>2013-09-24 21:35:25 +0100</birthday>
</Human>
=> nil

As you can see, every time we call bob.to_xml his birthday changes. Not great. Instead, the to_xml code should be changed so that all it has to worry about is presenting the data. Here’s how to do it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
require 'builder'

class Human
  attr_accessor :birthday

  def initialize
    @birthday = Time.now
  end

  def to_xml
    b = Builder::XmlMarkup.new :indent => 2
    b.instruct! :xml, :version => "1.0", :encoding => "utf-8"
    b.Human do
      b.birthday @birthday.strftime "%Y-%m-%d"
    end
    b.target!
  end
end

This time, when we call to_xml a number of times, we’ll see that as well as the data being static, it is also now presented correctly:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
irb(main):054:0> bob = Human.new
=> #<Human:0x007fc1c1d44d80 @birthday=2013-09-24 21:38:19 +0100>
irb(main):055:0> puts bob.to_xml
<?xml version="1.0" encoding="utf-8"?>
<Human>
  <birthday>2013-09-24</birthday>
</Human>
=> nil
irb(main):056:0> puts bob.to_xml
<?xml version="1.0" encoding="utf-8"?>
<Human>
  <birthday>2013-09-24</birthday>
</Human>
=> nil
irb(main):057:0> puts bob.to_xml
<?xml version="1.0" encoding="utf-8"?>
<Human>
  <birthday>2013-09-24</birthday>
</Human>
=> nil

Summary:

  1. Create test data classes that represent types of test data you use in your acceptance tests (eg: class Human)
  2. Expose attributes of those test data classes using accessors (eg: attr_accessor :name)
  3. Set any attributes in the test data class to sensible defaults in the initialize method
  4. Create to_xml/to_json/to_csv/etc rendering methods that will render the test data object in the formats required by your system under test
  5. Ensure that rendering methods do not include any logic other than presentation logic
  6. Use your test data classes in your test code
  7. #win

Hope that helps!

How to Mount a SMB Share on Mac OS X

Mounting a remote SMB share on Mac OS X is done using the mount_smbfs tool. Here’s how to do it:

1
2
3
4
5
#Create the mount point:
mkdir share_name

#Mount the share:
mount_smbfs //username:password@server.name/share_name share_name/

When you’re done with the share, unmount it with the following command:

1
umount share_name/

Hope that helps!

A Script to Install Chromedriver on Linux

Installing chromedriver on linux can be annoying. Here’s a script that takes the pain out of installing the latest (at time of writing!) 64 bit version of the driver:

1
2
3
4
5
6
7
#!/bin/bash
wget https://chromedriver.googlecode.com/files/chromedriver_linux64_2.3.zip
unzip chromedriver_linux64_2.3.zip
sudo cp chromedriver /usr/bin/chromedriver
sudo chown root /usr/bin/chromedriver
sudo chmod +x /usr/bin/chromedriver
sudo chmod 755 /usr/bin/chromedriver

I’ve tested it on Fedora 19.

If you need a different version of the driver, just change the link on the first line of the script.

Hope that helps!

Libs Required for Ruby Dev on Fedora 19

In setting up a Fedora 19 VM for web testing with Chrome I found that I needed to install a few packages before things started to work. After some trial-and-error, I came up with the following command:

1
$ sudo yum install -y git vim gcc gcc-c++ ruby ruby-devel libxml2 libxml2-devel libxslt libxslt-devel

It should install everything you need to start coding in ruby (the above command assumes your code is stored in git and you’re using vim) – I had particular trouble trying to get nokogiri to work. But the above command solved it :)

Hope that helps!

How to Disable Fedora’s Screensaver From the Command Line

Today I found myself building a Fedora-based VM for running some tests that require chrome. I needed to be able to prevent the screensaver from starting, and after a bit of googling I figured out how to do it. Here’s the command you need to run:

1
$ gsettings set org.gnome.desktop.session idle-delay 0

I tested this on Fedora 19 but I guess it should work on any Gnome 3 distro…

Hope that helps!

How to Debug HTTParty Requests

I very frequently find myself debugging http calls. Curl makes it easy to do this through its -v switch that lets you see exactly what it’s doing. For example:

1
2
3
4
5
6
7
8
$ curl -v http://www.google.co.uk
* About to connect() to www.google.co.uk port 80 (#0)
*   Trying 173.194.41.95... connected
* Connected to www.google.co.uk (173.194.41.95) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8x zlib/1.2.5
> Host: www.google.co.uk
> Accept: */*

Hostname-to-IP resolution, any SSL handshaking as well as full header details are on display. If you’re doing a POST or PUT you get the body too. All very helpful.

But, once I’ve figured out what I need my code to do I need to translate my curl incantations into HTTParty – currently my favourite ruby http library. It is possible to get similar details out of httparty but it’s a little esoteric.

Here’s a fairly representative use of httparty:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
require 'httparty'

class Google
  include HTTParty

  base_uri "http://www.google.co.uk"

  def home_page
    self.class.get "/"
  end
end

goog = Google.new
puts goog.home_page

But if I run that, I don’t get any debug info (as expected):

1
2
3
$ ruby httparty_debug.rb
<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage"><head><meta ...
...

No headers, no hostname-to-ip resolution, no SSL details.

To get the debug info, add debug_output $stdout on a new line after include HTTParty, eg:

1
2
3
4
5
6
class Google
  include HTTParty
  debug_output $stdout # <= this is it!

  #...
end

The result of running that will be a console filled with debug info!

1
2
3
4
5
6
7
8
9
10
$ ruby httparty_debug.rb
opening connection to www.google.co.uk:80...
opened
<- "GET / HTTP/1.1\r\nConnection: close\r\nHost: www.google.co.uk\r\n\r\n"
-> "HTTP/1.1 200 OK\r\n"
-> "Date: Mon, 09 Sep 2013 18:59:12 GMT\r\n"
-> "Expires: -1\r\n"
...
...
...

Note that if you’d rather the debug info went to $stderr, change debug_output $stdout to debug_output $stderr.

Hope that helps!