Pat NakajimaPat Nakajima
Run JavaScript in Selenium tests. Easily.
edit Posted by Pat Nakajima on Thursday March 05, 2009 at 04:46PM

Here's the gist of this post: gist.github.com/58876

Ever since I've started using Webrat, a lot of the pain of Selenium has gone away for me. There's still a little bit of pain though. Part of it is caused by the fact that it's harder than it should be to just execute arbitrary bits of JavaScript in in your current window under test. Well no more. Here's a helper:

module SeleniumHelpers
  # Execute JavaScript in the context of your Selenium window
  def run_javascript(javascript)
    driver.get_eval <<-JS
      (function() {
        with(this) {
          #{javascript}
        }
      }).call(selenium.browserbot.getCurrentWindow());
    JS
  end

  private

  # If running in regular Selenium context, get_eval is defined on self.
  def driver
    respond_to?(:selenium) ? send(:selenium) : self
  end
end

To use it with Cucumber, do like so:

World do |world|
  world.extend(SeleniumHelpers)
  world
end

To use it with POS, do like so:

class JavaScriptHelperTest < SeleniumTestCase
  include SeleniumHelpers

  # your tests go here...
end

Now what?

Now to run JavaScript in your Selenium window, just call run_javascript. Note that it's always going to return a String, so you may have to massage the output a tad:

checked_boxes_count = run_javascript <<-JS
  jQuery('input[type=checkbox]:checked').size();
JS

checked_boxes_count         # => "3"
checked_boxes_count.to_i   # => 3

Cooler stuff

While Webrat's DSL for traversing web apps is awesome, I've always found the alternatives (Polonium for example) to not jive well with how I think. They're way better than talking directly to Selenium, you're still locked in to a certain style. The run_javascript helper makes it easier to write your own helpers that fit your own style.

module ElementHelpers
  class Element
    def initialize(context, selector)
      @context, @selector = context, selector
    end

    def hide!
      call(:hide)
    end

    def show!
      call(:show)
    end

    def visible?
      call(:is, ':visible') == 'true'
    end

    private

    def call(fn, *args)
      @context.run_javascript <<-JS
        return jQuery(#{@selector.inspect})[#{fn.to_s.inspect}](#{args.map(&:inspect).join(', ')});
      JS
    end
  end

  def locate(selector)
    Element.new(self, selector)
  end
end

Now you can write your tests like so:

class JavaScriptHelperTest < ActiveSupport::TestCase
  include SeleniumHelpers
  include ElementHelpers

  def setup
    @element = locate('#all')
  end

  def test_visible_by_default
    assert @element.visible?
  end

  def test_hide_element
    @element.hide!
    assert ! @element.visible?
  end

  def test_show_element
    @element.hide! # setup
    @element.show!
    assert @element.visible?
  end
end

Credit should go to Brian Takita, since he did most of the hard work and I just wrote a method. Let me know if you have any issues or ideas with the helper, and may all your tests be green.