Dynamic Automation

Test automation is a great thing to have on a testing team. Letting a computer do the heavy repetitive lifting will save you a ton of time.

After awhile of writing it though, you may notice that any test can be broken down into atomic parts.

For today’s post I’ll be referring just to UI testing, using Ruby’s Watir framework.

Think of how you interact with a website–are you really doing anything more than clicking things, entering text or selecting from dropdowns?

If you were to write a definition to describe clicking a submit button on a login page, I bet the absolute simplest way you could think of would look something like this:

def click_submit
   $browser.element(:xpath => '//button[@id="submit"]').when_present.click
end

So if your code is full of simple one-liner methods like that, it will become a pattern once you do it enough.

Today I’m going to show you how to write Ruby code that will generate methods like this for you, at runtime, with minimal human input.

Ruby’s define_method… method

Ruby has a quite handy method that can define methods for you at runtime.

You can specify what you want the method to do, what you want it to be called, and whether it takes any arguments.

Really cool whiz bang technomage stuff for when you know you’ll have a certain pattern for creating methods.

Application

This can be carried over to defining methods for interacting with elements. Let’s first set some ground rules:

  • The basic types are element, text_field and select_list
  • The corresponding actions are click, enter and select, respectively
  • Clicking an element (of any kind) does not take any arguments
  • Entering data (such as in a text field) takes one argument
  • Selecting an option (such as from a dropdown) takes one argument
  • Each “thingie” on the screen should have an accurate name, and an xpath to locate it

The synopsis for this would be:

def [action]_[name](arg if action == enter or select)
   $browser.[type](:xpath => '[xpath]').when_present.[action](arg if action == enter or select)
end

So now let’s create some code to generate these for us. First let’s define a hash at the top that contains the names vs. xpath locators of each element we’re concerned about for, say, a login page:

xpaths = {
   'username' => '//input[@id="username"]',
   'password' => '//input[@id="password"]',
   'submit' => '//img[@id="submit"]',
}

Next we make a list of actions:

actions = [ 'click', 'select', 'enter' ]

Finally we loop through the names/xpaths and actions, in a nested loop, and create the methods:

xpaths.each do |name, xpath|
   actions.each do |action|
   # 'click' takes 0 arguments, and refers to an element
   if action == "click" then
      define_method("#{action}_#{name}") {
         puts "$b.element(:xpath => '#{xpath}').when_present.click"
      }
   elsif action == "select" then
      define_method("#{action}_#{name}") do |argument|
         puts "$b.select_list(:xpath => '#{xpath}).when_present.select('#{argument}')"
      end
   elsif action == "enter" then
      define_method("#{action}_#{name}") do |argument|
         puts "$b.text_field(:xpath => '#{xpath}).when_present.set('#{argument}')"
      end
  end
end

The puts line in each defined method will print out the line of code that will be executed. This is just so the code can be tested, so you can see what’s going on.

Now let’s put some code in to test this:

enter_username("joe")
enter_password("schmoe")
click_submit

Note that none of these methods were actually created by hand in that code above–you won’t find these methods explicitly defined. But if we run this, we get the following:

$b.text_field(:xpath => '//input[@id="username"]).when_present.set('joe')
$b.text_field(:xpath => '//input[@id="password"]).when_present.set('schmoe')
$b.element(:xpath => '//img[@id="submit"]').when_present.click

If you’re familiar with Watir syntax, you will notice that this looks like the actual code you’d use to drive a test.

Note: the $b variable is the browser variable, set up globally, and set up at the beginning of a test. Replace with whatever variable you would use to drive your browser session. 

When you’re happy with the outputs of the puts (putses?), replace those three with an eval instead. This will cause that code to actually get executed when you run a for-real UI test.

Hide the Ugly

Next step is to take this code and put it somewhere where most people won’t have to think about it. Encapsulate it in a class, ideally named for the page it refers to, and call it login_page.rb:

class LoginPage
   # put all that stuff from above, into this class.
end

Then in your main code, instantiate that page, and use the defined methods from inside the class. The methods will generated for you as soon as you make a new instance of the class.

Then your main code will look like this:

require "./login_page.rb"
login_page = LoginPage.new
login_page.enter_username("joe")
login_page.enter_password("schmoe")
login_page.click_submit

Surprise! Self Documenting Code!

An immediate benefit of this approach is, your code becomes really readable. It’s incredibly easy to understand what the code’s doing, when you see enter_username, enter_password, click_submit.

caving
Oh, -there’s- the base class…

Also, if you’re new to the team, it’s a lot more likely that you’ll be able to sit down and be productive quicker, if you know that pages and elements are named appropriately in the code. You won’t have to go spelunking around trying to find oddly-named classes or methods. It should be nice and streamlined.

Refactoring

With this approach, your code, overall, will be granular enough that the only things you may have to change are the names or xpaths. They’re all up at the top of your code so they’re easy to find.

Maintenance is also much simpler. You can probably refactor this a step further by having the generation code squirreled off another level lower, and have your individual page classes call that function. The page class would then only contain a list of names and xpaths.

Where Else Can You Do This? 

Seems like scripting languages such as Ruby let you get away with defining things dynamically, much easier than a formally compiled language. I’ve heard (but not confirmed) that this can be done in Java (and therefore Selenium) using reflection. But it’s not something I’ve tried out. From what I understand it’s a real pain in the tube, but if you know a way, please share! This is a great way to simplify your code.

Have fun, and keep experimenting!

– Fritz

Advertisements

2 thoughts on “Dynamic Automation

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s