Pat Nakajima's blog



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.

Pat NakajimaPat Nakajima
Better View Testing with Elementor
edit Posted by Pat Nakajima on Tuesday November 25, 2008 at 02:18AM

We've got a few mantras at Pivotal. One of them has to do with testing all the time. It's a Good Thing, for sure. Until recently though, I had always inserted a tacit "except for views" to the end of it. The reason for my reservations wasn't the fact that view tests can be brittle. Any test can be brittle. I didn't like testing views because it seemed like the test I was writing never really described the code I was writing. Let's look at a typical view test to see what I mean:

describe "/posts/index.html.erb" do
  def render_view
    render "/posts/index.html.erb"
    response.body
  end

  before(:each) do
    assigns[:posts] = [
      stub_model(Post, :name => "First!", :body => "first body."),
      stub_model(Post, :name => "Second!", :body => "second body.")
    ]
  end

  describe "assertions using have_tag" do
    it "renders posts" do
      render_view
      response.should have_tag(".post", 2)
    end

    it "renders post headers" do
      render_view
      response.should have_tag(".post .post-name", "First!", 1)
      response.should have_tag(".post .post-name", "Second!", 1)
    end

    it "renders post bodies" do
      render_view
      response.should have_tag(".post .post-body", "first body.", 1)
      response.should have_tag(".post .post-body", "second body.", 1)
    end
  end
end

This snippet uses the have_tag helper. It's somewhat slow, and to my eyes, expresses intent about as well as an apple floating in a top-hat filled with perfume. The "tag" is a selector? The content filter is just the second argument? And the last argument is the amount of "tag" the response should have? You can test like this, but why would you?

I've also seen a more manual pattern, using a library like Hpricot or Nokogiri to parse the response body, then asserting on the results of that:

describe "assertions using Nokogiri" do
  def doc
    @doc ||= Nokogiri(render_view)
  end

  it "renders posts" do
    doc.search('.post').should have(2).nodes
  end

  it "renders post headers" do
    headers = doc.search('.post .post-name')
    headers.should have(2).elements
    headers.detect { |element| element.text == "First!" }.should_not be_nil
    headers.detect { |element| element.text == "Second!" }.should_not be_nil
  end

  it "renders post bodies" do
    bodies = doc.search('.post .post-name')
    bodies.should have(2).elements
    bodies.detect { |element| element.text == "first body." }.should_not be_nil
    bodies.detect { |element| element.text == "second body." }.should_not be_nil
  end
end

It's faster, since it's not using have_tag, but still not very expressive. CSS selectors are still littered across the it statements, but at least it's only once per test. Still, using detect to find content is no good. And I don't think CSS selectors have any business in it statements at all. That seems like asserting on the name of a method being called, not its behavior.

The solution!

Given my problems with the above approaches, I created a gem that allows the following assertion syntax:

it "renders posts" do
  result.should have(2).posts
end

it "renders post headers" do
  result.should have(2).post_headers
  result.should have(1).post_header.with_text("First!")
  result.should have(1).post_header.with_text("Second!")
end

it "renders post bodies" do
  result.should have(2).post_bodies
  result.should have(1).post_body.with_text("first body.")
  result.should have(1).post_body.with_text("second body.")
end

What's a result? And how does it know how many posts, post_headers, and post_bodies it has? The result is defined in a before block like so:

require 'elementor'
require 'elementor/spec'

include Elementor

attr_reader :result

before(:each) do
  @result = elements(:from => :render_view) do |tag|
    tag.posts         ".post"
    tag.post_headers  ".post .post-name"
    tag.post_bodies   ".post .post-body"
  end
end

The elements method allows you to name your CSS selectors using the tag block argument. The tag object uses method_missing to register your names. The :from option specifies a method to be called that will return some raw markup.

Naming selectors alone was a huge win for me, but there are a few other cool bits about the @result object. First, you get to use the with_text helper for filtering content. You'll also get a with_attrs helper for filtering based on a hash of attribute values.

The project is called Elementor, and you can install it like so:

[sudo] gem install elementor

The code is on the GitHub here: github.com/nakajima/elementor (and you can see the CI build here). Take a look at the specs for all of the examples of what you can do. Hopefully, you'll find it as useful as I have. If not, please share your reasons in the comments!