0

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.

Dave Schweisguth
  • 36,475
  • 10
  • 98
  • 121
D.Spetz
  • 231
  • 1
  • 10
  • You had me at "refactor" and "massive". I have an answer in mind, but please write out the line after `elsif buttonName == 'LOGIN_BUTTON'` so I can be sure I'm addressing all of the necessary variations. – Dave Schweisguth Jun 03 '14 at 21:10
  • @DaveSchweisguth Added a few more lines to the example code to show the problem. Hope that gives a better feel of the problem – D.Spetz Jun 04 '14 at 12:59
  • I was looking for another one of those eval lines. – Dave Schweisguth Jun 04 '14 at 13:02
  • Ahhh, gotcha. Added a few more for example. Only real difference between them is the hash values used to find the right html element – D.Spetz Jun 04 '14 at 13:08
  • Is the $DataHash value always "link(:id => 'something')"? – Dave Schweisguth Jun 04 '14 at 16:39
  • No, it depends on which page element is being worked with. Links, buttons, divs, text fields etc. all have different keywords. And sometimes we select them by name instead of id. So we have stuff like div(:name => example) – D.Spetz Jun 04 '14 at 17:17

2 Answers2

1

As you mentioned you can reuse steps see Reuse Cucumber steps. But I personally found it pretty complicated when I tried to do it. So, from my side I suggest you to implement Page Object pattern. The idea is that you describe your pages or even some modules like separate entities which provides you with ability to interact with them. For understanding concept see here. And here you can find some example. Assuming this your step definition would like

Given(/I press the (.*)$/) do |buttonName|
  @my_home_page.click_search_button
    ...
  end
end

Where click_search_button method encapsulates your 'ladder' logic to press login button if search button is not present yet.

Hopefully it makes sense for you.

Community
  • 1
  • 1
olyv
  • 3,699
  • 5
  • 37
  • 67
0

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.

Dave Schweisguth
  • 36,475
  • 10
  • 98
  • 121