Pat Nakajima's blog
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.
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!







