Testing SproutCore with Cucumber + Selenium

This isn’t really a complete tutorial; just a few tricks to make the process a lot easier.

First, add the following code to features/support/env.rb, replacing existing bits as necessary:

Webrat.configure do |config|
  config.mode = :selenium
  config.application_environment = Rails.env

  # Same as script/server, so SproutCore can proxy to the same port in both
  # development and test modes.
  config.application_port = 3000 

  # This is used for both the initial browser startup, and the maximum
  # timeout for commands like 'open'.  It needs to be very high, because we
  # may need to wait for a SproutCore recompile at some point.
  config.selenium_browser_startup_timeout = 40
end

require 'features/environments/selenium_locator_fix'

World(Webrat::Selenium::Methods)
World(Webrat::Selenium::Matchers)

def expect_element xpath
  selenium.wait_for_element("xpath=#{xpath}", :timeout_in_seconds => 5)
end

def press_key(str)
  chr = str[0]
  chr.integer?.should == true # Breaks for Ruby 1.9?
  key_arg = sprintf("\\%02d", chr)

  # This works in all current browsers except Safari < 4.
  # http://stackoverflow.com/questions/497094/how-do-i-find-out-which-javascript-element-has-focus
  selenium.key_press("dom=document.activeElement", key_arg)
end

Here’s the locators fix:

# Make field_labeled work correctly with Selenium.  This is based on the
# discussion at
# https://webrat.lighthouseapp.com/projects/10503/tickets/255-field_labeled-locator-missing-in-selenium-mode
# but it has been changed in a number of ways to actually make it work again.

module Webrat::Locators
  def field_labeled(label, *field_types)
    Webrat::Locators::FieldLabeledLocator.new(self, Webrat::XML.document(response_body), label, *field_types).locate!
  end
end

class Webrat::SeleniumSession
  def dom
    @dom ||= Webrat::XML.document(response_body)
  end

  def current_dom
    dom
  end

  def elements
    @elements ||= {}
  end
end

Now, we need to add layerId properties to some of our globally-known views, so we have a chance of finding them:

// Needed so Cucumber can find this element.  Note that this ID
// actually applies to a wrapper element containing the actual input
// element, so it's still pretty tricky to get at.
layerId: 'searchBox'

Here are some sample step definitions:

When /^I run a search for "([^\"]*)"$/ do |query|
  # SproutCore assigns random element IDs, and places those IDs on wrapper
  # elements instead of the actual input elements.  So we override the
  # layerId property on our testable views, and write some messy XPath to
  # extract the actual element we want.
  locator = "xpath=id('searchBox')//input"
  selenium.wait_for_element(locator, 5)
  selenium.type(locator, "#{query}")
  press_key("\r")
end

When /^I press tab$/ do
  press_key("\t")
end

When /^I press return$/ do
  press_key("\r")
end