My team is currently taking our old UI acceptance test scripts and automating them. To do this we are using Jruby, Cucumber and Watir-Webdriver. So far the automation process has been going pretty well. The only problem we have is that our step definitions are starting to get a bit out of hand.
For example, in most of our scenarios is a section like this:
Given I press the SEARCH_BUTTON
Then I should land on the SEARCH_PAGE
and the step definitions look like this:
Given(/I press the (.*)$/) do |buttonName|
if buttonName == 'SEARCH_BUTTON'
eval "$browser.#{$DataHash['home']['searchButton']}.when_present.click"
elsif buttonName == 'LOGIN_BUTTON'
eval "$b.#{$DataHash['loginPage']['loginButton']}.click"
elsif buttonName == 'HOME_BUTTON'
eval "$b.#{$DataHash['mainPage']['HomeButton']}.click"
elsif buttonName == 'ADD_PRODUCT_BUTTON'
#This if else ladder goes on like this for another 300+ lines
...
end
end
The $DataHash
variable refers to config.yml
, which uses a hash to store all of the different web elements we are using.
config.yml
home:
searchButton: "link(:id => 'searchBtn')"
searchTypeSelectBox: "select_list(:name => 'matchType')"
searchResetButton: "button(:id => 'resetSearch')"
#rest of the elements on the home page...
loginPage:
loginButton: "link(:id => 'login')"
#rest of the elements on the login page...
....
So $browser.$DataHash['home']['searchButton'].when_present.click
is equivalent to $browser.link(:id => 'searchBtn').when_present.click
We are using this basic step definition for every button that a user could click, and at this point this one step definition is something like 300+ lines of code. Most of which are single lines like above. Our other step definitions have the same sort of problem. Are there any good ways of refactoring our step definitions to make them less bloated, or at least easier to search through, without making the actual steps any less re-useable?
Initially we thought we could have the same step definition in multiple files based on which page was being tested. So in searchDefinitions.rb
there would be a step definition for Given(/I press the (.*)$/) do |buttonName|
which only had the different buttons found on the search page. Then in homeDefinitions.rb
would be the same step definition but only with code for the home page buttons. Basically breaking up the if-else ladder across multiple files. Of course, Cucumber doesn't allow the same step definition in multiple files so now we're at a bit of a loss.
Supposing that the minor differences in the eval
lines you show don't matter, extract the hash values that vary into a constant
BUTTON_KEYS = {
'search' => %w(home searchButton),
'login' => %w(loginPage loginButton)
# ...
}
and use it in your step definition:
Given(/I press the (.*) button$/) do |button_name|
keys = BUTTON_KEYS['button_name']
eval "$browser.#{$DataHash['#{keys[0]}']['#{keys[1]}']}.when_present.click"
end
Now you have half as many lines of code and less duplication.
I changed the step regexp to include "button", to remove that duplication from the button names, and the button names to be lowercase, as in normal English. Whether or not you're showing your feature files to non-programmers, Cucumber step names should read like natural language so that you can think about product requirements and not implementation details when you're reading them.
Alternative suggestion, valid if the two levels of keys in the the YAML are not really needed:
You could restructure the YAML like so
search button: "link(:id => 'searchBtn')"
search type select box: "select_list(:name => 'matchType')"
search reset button: "button(:id => 'resetSearch')"
# rest of the elements on the home page...
login button: "link(:id => 'login')"
# rest of the elements on the login page...
# ...
Then you wouldn't need the hash at all, and your step could just be
Given(/I press the (.*)$/) do |element_name|
eval "$browser.#{$DataHash['#{element_name}']}.when_present.click"
end
Or you could convert the YAML entirely into a hash (representing the method as a string and calling it with .send
), which would prevent you from making some syntax errors.