Updated script to list all cucumber step definitions

In a previous post I put up a small script that would dump out all step definitions available in a cucumber project. Here’s an updated version… it hasn’t changed much apart from the output is now a html table that for each step definition contains the regular expression, any regex modifiers, any parameters to the step definition and a link to the file that contains the definition.

Anyway, here’s the script:

step_definition_dir = "./features/steps"

f = File.new("output.htm", "w")

f << "<table><th>Regex</th><th>Modifiers</th><th>Step Definition Args</th><th>Source file</th>"

Dir.glob(File.join(step_definition_dir,'**/*.rb')).each do |step_file|
  File.new(step_file).read.each_line do |line|
    next unless line =~ /^\s*(?:Given|When|Then)\s+\//
    matches = /(?:Given|When|Then)\s*\/(.*)\/([imxo]*)\s*do\s*(?:$|\|(.*)\|)/.match(line).captures
    matches << step_file
    f << "<tr>"
    f << "<td>#{matches[0]}</td>"
    f << "<td>#{matches[1]}</td>"
    f << "<td>#{matches[2]}</td>"
    f << "<td><a href=\"#{matches[3]}\">#{matches[3]}</a></td>"
    f << "</tr>"
  end
end

f << "</table>"

—— UPDATE ——

Turns out that in the latest version of cucumber (0.6.1) there’s a new formatter called stepdefs which prints out the step regexs and the step definition file that they live in. It doesn’t report regex modifiers or the step arguments – apart from that it’s great. Here’s how to use it:

cucumber -d -f stepdefs

…though it only seems to work for steps called in an ordinary scenario, not a scenario outline.

Ruby regex code examples summary

Coming from java, ruby’s regex implementation seems a bit obtuse. Here’s a bunch of code examples which I’ve put together as a mini-reference for my own use. If I’ve missed anything major, leave a comment. And sorry about the code bleeding off to the right, I need a more code-friendly wordpress theme.

Each line contains an example of some ruby regex related call. The comment that follows it shows what the output of the line would be and gives a short explanation.


#Creating Regular Expressions
#============================
my_laborious_regex = Regexp.new('[a-z]{3}')
puts my_laborious_regex #=> "(?-mix:[a-z]{3})" because that's the regex and the default regex options
puts my_laborious_regex.source #=> "[a-z]{3}" because .source returns the regex pattern we specified

my_prettiest_regex = /[a-z]{3}/
puts my_prettiest_regex #=> "(?-mix:[a-z]{3})" because that's the regex and the default regex options
puts my_prettiest_regex.source #=> "[a-z]{3}" because .source returns the regex pattern we specified
puts my_laborious_regex == my_prettiest_regex #=> "true" because though they were created differently, they are the same pattern (and options)

#Playing with matching strings
#=============================
puts /^(\d{3})(\d{3})/.match("123456789") #=> "123456" because the regex will match the first 6 chars
puts /[a-z][0-9].*[a-z]/.match("123a8---a123") #=> "a8---a" because the regex ignores the first and last "123"

#Playing with capture groups
#===========================
puts /^123(\d{3})(\d{3})$/.match("123456789")[0] #=> "123456789", because [0] returns the match
puts /^123(\d{3})(\d{3})$/.match("123456789")[1] #=> "456", because [1] returns the first capture group
puts /^123(\d{3})(\d{3})$/.match("123456789")[2] #=> "789", because [2] returns the second capture group
puts /^123(\d{3})(\d{3})$/.match("123456789")[3] #=> "nil", because there are only 2 capture groups
puts /^123(\d{3})(\d{3})$/.match("123456789").to_a.inspect #=> ["123456789", "456", "789"], the match and the capture group results

#Playing with pre/post match
#===========================
the_match = /[a-z]+/.match("321abcdefg987")
puts the_match #=> "abcdefg" because the regex captures a string of lowercase alpha chars
puts the_match.pre_match #=> "321" because it's what comes before the string that was captured
puts the_match.post_match #=> "987" because it's what comes after the string that was captured

#Playing with .split and .scan
#=============================
puts target_string = "abc123def456ghi789"
puts target_string.split(/[0-9]+/).inspect #=> "["abc", "def", "ghi"]", because .split hunts for the supplied pattern, strips matches out of the string and returns substrings that were between the matches in an array
puts target_string.scan(/[0-9]+/).inspect #=> "["123", "456", "789"]", because .scan hunts for strings that match the pattern and returns all the matches in an array

#Playing with string substitution
#================================
puts target_string = "hello hello hello"
puts target_string.sub(/hello/, "goodbye") #=> "goodbye hello hello", because .sub only replaces the first match
puts target_string.gsub(/hello/, "goodbye") #=> "goodbye goodbye goodbye", because .gsub replaces all matches
puts target_string.gsub(/o/, "a").gsub(/e/, "o") #=> "holla holla holla", .gsub returns a string so you can chain .gsub's together


You can run the above and the output you’ll get is:


(?-mix:[a-z]{3})
[a-z]{3}
(?-mix:[a-z]{3})
[a-z]{3}
true
123456
a8---a
123456789
456
789
nil
["123456789", "456", "789"]
abcdefg
321
987
abc123def456ghi789
["abc", "def", "ghi"]
["123", "456", "789"]
hello hello hello
goodbye hello hello
goodbye goodbye goodbye
holla holla holla

Finally, there is an awesome site that I link to already called http://www.rubular.com/. It’s a very useful tool for trying out regex patterns. Bookmark it.

How to know if you have a secure session in Watir

Sometimes you’re on a secure page, sometimes you’re not. How to know? There’s a very simple method you can write to find out, based on the browser’s current url. Here’s the method:


def is_session_secure?
  @browser.url =~ /^https/ ? true : false
end

It’s worth noting that the method highlights how much you can get done with very little code using ruby and watir. Here’s what it does: first, get the url out of the browser. Then, see if the url matches a simple regex. Since this operation is done in a ternary operation, a simple true/false will result. And, since ruby returns what the final line in a method resolves to, the true/false result of the match will be returned. Another nail in the coffin of big and bloated UI automation tools!

Here’s an example of the code in use…


require "rubygems"
require "firewatir"
require "test/unit"

include FireWatir

class GoogleSecurePageTests < Test::Unit::TestCase
  def setup
    @browser = Firefox.start("http://www.google.com")
  end

  def test_google_https
    assert(!is_session_secure?)
    @browser.link(:text, "Sign in").click
    assert(is_session_secure?)
  end

  def is_session_secure?
    @browser.url =~ /^https/ ? true : false
  end
end

How to add 2 ruby regex patterns together

Here’s a monkeypatch for ruby’s Regexp class that will allow you to concatenate 2 (or more) regex patterns together:


class Regexp
  def +(r)
    Regexp.new(source + r.source)
  end
end

It comes in very useful when doing something like the following…


protocol_rgx = /^http:\/\//
google_search_rgx = /.*\.google\.com$/
possible_google_domains = []
possible_google_domains << "http://www.google.com"
possible_google_domains << "http://images.google.com"
possible_google_domains << "http://finance.google.com"
possible_google_domains << "http://video.google.com"
possible_google_domains << "http://www.bing.com"

possible_google_domains.each do |domain|
  domain.scan(protocol_rgx + google_search_rgx).each do |match|
    puts match
  end
end

In the above example, I have 2 patterns; one for matching the protocol section of a url (protocol_rgx) and another to match the normal google search domain (google_search_rgx). When I add them together using the monkeypatch above and then scan a string with the resulting concatenated pattern, it will do exactly what you expect it to do: match the string against the full pattern. So the output of the above example is:

http://www.google.com

http://images.google.com

http://finance.google.com

http://video.google.com


The string we put in matches all the domains apart from the bing domain.

Enjoy adding regex patterns together!

Regular Expressions Pocket Reference

I’ve not had this book for long, but if you spend a lot of time with regular expressions (specifically with Rational Functional Tester’s object recognition properties) you’ll find it very useful. I know I have…

O’Reilly, prepare for some free advertising:

If you follow the link, you’ll find chapter excerpts which will give you an indication of what the book is like.