Debugging your scripts with Pry

One cool thing that came out of SelConf 2013 (I wasn’t able to attend … just experienced it online) was Dave Haeffner‘s new Selenium tutorial Elemental Selenium. Its an email newsletter published once a week with a Selenium tip (in Ruby!) focusing on the basic foundations of how to solve certain problems. This is mainly aimed at beginning Selenium users, but anyone using Selenium and Ruby would find the tips useful. I have learned some new ideas and ways to address certain problems that I hadn’t thought of before from some of his tips. You can also browse the repository of archived tips on the website as well.

After Dave posted tip 11 about building a simple REPL to debug your tests, I started up a conversation with him about the benefits of using Pry over writing a simple debugger. The sum total of the conversation resulted in Dave asking me to write a guest tip on the subject.

LOL, I think it turned out pretty well. Go check it out and let me know your thoughts.

Highlighting the element before any clicks: A foray into the AbstractEventListener

Last week I wrote a post about how you can create a highlight method to help you visually find an element that you have selected. In the comments section, wiiniRyan suggested implementing the highlight method before all clicks occur using one of the Abstract Event Listeners hooks provided in the Selenium::WebDriver::Support module. Only thing was, he couldn’t figure out how to use the hooks according to his comments, and suggested that I might be able to provide some insight.

His comments intrigued me, so I did a little bit of research and hands on testing to figure out how these hooks work.

I looked at the documentation, but didn’t see anything real obvious about how to hook into them. My first attempts simply tried to subclass the AbstractEventListeners class, but that did no good, since the driver had no way to interface with the overridden hooks.

It wasn’t until I read a small crucial line after the #for method call on the main WebDriver documentation page that I figured out how these hooks worked:

One special argument is not passed on to the bridges, :listener. You can pass a listener for this option to get notified of WebDriver events. The passed object must respond to #call or implement the methods from AbstractEventListener

Essentially, you need to create a class with either every method hook defined, or use the generic #call method to delegate the hooks as they occur.

# Either
class AbstractDefineEveryMethodTest
  def before_click *args
    # .. do stuff
  end
  def before_#... rest of the hooks defined
end
# The downside to the above is that every single method
# hook needs to be defined, or else things will blow up
# when the hook tries to be called.
# Or, more efficiently
class AbstractHandleCallMethodTest
  def call *args
    # only need to deal with the hooks you are interested in
    case args.first
    when :before_click
      puts "Clicking #{args[1]}"
      # .. do stuff
    when :before_find
      puts "Finding #{args[1]} => #{args[2]}"
      # .. do other stuff
    end
  end
end

After creating this class, then we just create a new instance of the class and pass it into the initialization of our WebDriver browser:

ael = AbstractHandleCallMethodTest.new
driver = Selenium::WebDriver.for :firefox, :listener => ael

So, now when you find an element and click it, you will see the following output:

driver.find_element(:css => "h1").click # =>
Finding css selector => h1
Clicking #<Selenium::WebDriver::Element:0x000000022f6528>
=> "ok"

So, its a simple matter of adding the highlight method to the AbstractEventListener and using the #call method to call highlight when we want to.

class HighlightAbstractTest
  def highlight element, driver
    orig_style = element.attribute("style")
    driver.execute_script("arguments[0].setAttribute(arguments[1], arguments[2])", element, "style", "border: 2px solid yellow; color: yellow;")
    sleep 1
    driver.execute_script("arguments[0].setAttribute(arguments[1], arguments[2])", element, "style", orig_style)
  end
  def call *args
    case args.first
    when :before_click
      highlight args[1], args[2]
    end
  end
end
hat = HighlightAbstractTest.new
driver = Selenium::WebDriver.for :firefox, :listener => hat
driver.find_element(:css => "h1").click # => Be sure to watch in your browser window for the highlight to appear!
driver.find_elements(:css => "input[type='button']").each{|b| b.click } # => Be sure to watch in your browser window for the highlight to appear!

A couple words of caution:

First, if you are going to define each hook instead of using the recommenended #call method, be sure you define each and every hook inside your class. Otherwise, if that hook attempts to be executed, you will get a nasty error message about not being able to find the method.

Second, not all of the hooks provide the same parameters. In fact, the only hooks to include the element and driver, which are needed for the highlight method to work are the before_click, before_change_value_of (still trying to figure out when that one is triggered), after_click, and after_change_value_of. The other hooks have different parameters according to their respective function, which can be found by RTFM. Don’t get burned when you expect before_find to pass the whole element as a parameter. And I wouldn’t recommend trying to get the actual element inside this hook with the by and what parameters, lest you create an infinite loop.

AbstractEventListener seems like an interesting idea, but is a little bit complicated to set up. Also, I am not sure what other use cases might come from implementing these hooks. If you have some ideas, please share them in the comments!

Solving window.onbeforeunload nasty prompts

One of our tests that we run continuously is an iteration over all the pages in our control room app, checking to ensure the page loads without any error keywords. On a particular set of pages, there are Javascript window.onbeforeunload event listeners that generate the nasty prompts that want the user to verify they do indeed want to leave the page (presumably without saving their work).While looking into the application code, I notice that window.onbeforeunload gets set after every autoSave (application) call, which of course happens at random/timed intervals as well as when certain events are fired.

My first attempts at addressing this issue looked something like this:

@driver.execute_script("window.onbeforeunload = undefined;")
@driver.find_element(:css => "a.unload_prompt").click
## Wait for next page ... hope that a race condition doesn't occur

This worked most of the time. But one out of ten or fifteen times, the race condition would occur where the autoSave js call would occur just after I cleared the window.onbeforeunload and just before the element actually got clicked. This re-set the unload prompt, and caused the extremely frustrating

Selenium::WebDriver::Error::UnhandledAlertError: Modal dialog present

error to appear, failing my control room tests from that point on. ūüė¶

If you are one of the many users struggling with this, never fear. There is hope!

It all centers around the brilliance of what click returns. If we are just clicking on a regular link that either takes us to a page or executes some javascript, we get this response:

pry> @driver.find_element(:css => ".homepage a").click
=> "ok"

If we click a link that shows this window.onbeforeunload prompt:

pry> @driver.find_element(:css => "a.unload_prompt").click
=> "Are you sure you would like to leave this page?"

Aha! Webdriver is nice enough to detect that this unload is going to throw a nasty prompt and sends it back through the JSON Wire to be returned to the Ruby call to click. So how does this solve our issues? We can just get and accept the alert to continue on in the page!

pry> @driver.switch_to.alert.text
=> "Are you sure you would like to leave this page?"
pry> @driver.switch_to.alert.accept
=> ""

Thus, we can update our code to conditionally take care of the alert if the click returns anything other than an ok response:

click_resp = @driver.find_element(:css => "a.unload_prompt").click
@driver.switch_to.alert.accept unless click_resp.eql?("ok")
## Continue on to the next page, etc...

And that is how you solve this nasty problem!

Headless Gem causes Errno::ECONNREFUSED

Have you ever gotten this error while trying to run simultaneous headless Firefox instances of WebDriver?

Errno::ECONNREFUSED: Connection refused - connect(2)
/home/site/.rvm/rubies/ruby-1.9.3-p385/lib/ruby/1.9.1/net/http.rb:762:in `initialize'
/home/site/.rvm/rubies/ruby-1.9.3-p385/lib/ruby/1.9.1/net/http.rb:762:in `open'
/home/site/.rvm/rubies/ruby-1.9.3-p385/lib/ruby/1.9.1/net/http.rb:762:in `block in connect'
/home/site/.rvm/rubies/ruby-1.9.3-p385/lib/ruby/1.9.1/timeout.rb:54:in `timeout'
/home/site/.rvm/rubies/ruby-1.9.3-p385/lib/ruby/1.9.1/timeout.rb:99:in `timeout'
/home/site/.rvm/rubies/ruby-1.9.3-p385/lib/ruby/1.9.1/net/http.rb:762:in `connect'
/home/site/.rvm/rubies/ruby-1.9.3-p385/lib/ruby/1.9.1/net/http.rb:755:in `do_start'
/home/site/.rvm/rubies/ruby-1.9.3-p385/lib/ruby/1.9.1/net/http.rb:744:in `start'
/home/site/.rvm/rubies/ruby-1.9.3-p385/lib/ruby/1.9.1/net/http.rb:1284:in `request'

This one was plaguing me for a while. I could not understand why Firefox could not run separate instances of itself, when the SWD documentation indicated that each run used an entirely separate temporary Firefox profile, and so the instances would not conflict with each other.

But sure enough, if I started another job while one was already running, I would get the above error on the first job, while the second job hijacked the connection.

After really delving into the issue, I finally realized that the issue was not the Firefox profiles conflicting. Instead, the issues came from my usage of the headless gem to run the jobs headlessly using Xvfb. I was not defining a specific display to use when my scripts ran, and so the headless gem would default to using the :99 display. This was all fine and good when running the scripts individually. But if you run multiple scripts that try to use the same display, the first script gets disconnected from the headless session and the second script takes over.

The solution to this problem is to explicitly define a display number to use for each script that runs. This works especially well if you are running your scripts in a CI environment like Jenkins, where each build is given its own number that you can easily access as an environmental variable such as “BUILD_NUMBER”. Other ideas are to generate a random display number on the fly, or use another means to generate any integer number.

display = ENV['BUILD_NUMBER'] || "99"
@headless = Headless.new(:display => display)
@headless.start

This will generate a headless session on the display number that is defined by the BUILD_NUMBER environmental variable, and as long as the build numbers are completely different from each other, you will be good.