diff --git a/lib/webrat/ruby_html_unit.rb b/lib/webrat/ruby_html_unit.rb
new file mode 100644
index 0000000..511c4e5
--- /dev/null
+++ b/lib/webrat/ruby_html_unit.rb
@@ -0,0 +1,41 @@
+$:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
+
+module RubyHtmlUnit
+ Jars = Dir[File.dirname(__FILE__) + '/ruby_html_unit/htmlunit/*.jar']
+end
+
+if RUBY_PLATFORM =~ /java/
+ require 'java'
+ RubyHtmlUnit::Jars.each { |jar| require(jar) }
+
+ module HtmlUnit
+ include_package 'com.gargoylesoftware.htmlunit'
+ end
+ JavaString = java.lang.String
+else
+ raise "RubyHtmlUnit only works on JRuby at the moment."
+end
+
+
+Dir[File.join(File.dirname(__FILE__), "ruby_html_unit", "*.rb")].each do |file|
+ require File.expand_path(file)
+end
+
+
+# Deal with the test server
+if `uname`.chomp == "Darwin"
+ TEST_SERVER_PORT = '3001'
+
+ if `ps ax`.match(/^\s*(\d*).*-e test -p #{TEST_SERVER_PORT}/)
+ puts "A test server was found running on port #{TEST_SERVER_PORT} (PID #{$1})"
+ else
+ puts "A test server was not found running (looking for -e test -p #{TEST_SERVER_PORT})"
+ puts "Please start a new test server using the command below:"
+ puts
+
+ command_string = "ruby #{RAILS_ROOT}/script/server -e test -p #{TEST_SERVER_PORT}"
+
+ puts command_string
+ exit
+ end
+end
\ No newline at end of file
diff --git a/lib/webrat/ruby_html_unit/alert_handler.rb b/lib/webrat/ruby_html_unit/alert_handler.rb
new file mode 100644
index 0000000..016e1c1
--- /dev/null
+++ b/lib/webrat/ruby_html_unit/alert_handler.rb
@@ -0,0 +1,13 @@
+module RubyHtmlUnit
+
+ class AlertHandler
+ def handleAlert(html_unit_page, alert_message)
+ alert_messages << alert_message
+ end
+
+ def alert_messages
+ @alert_messages ||= []
+ end
+ end
+
+end
\ No newline at end of file
diff --git a/lib/webrat/ruby_html_unit/element.rb b/lib/webrat/ruby_html_unit/element.rb
new file mode 100644
index 0000000..29988b5
--- /dev/null
+++ b/lib/webrat/ruby_html_unit/element.rb
@@ -0,0 +1,119 @@
+module RubyHtmlUnit
+
+ class Element
+ def initialize(element, page)
+ @html_unit_element = element
+ @page = page
+ end
+
+ def elem_type
+ @html_unit_element.node_name
+ end
+
+ def id_attribute
+ @html_unit_element.id_attribute
+ end
+
+ def matches_text?(text)
+ @html_unit_element.as_text =~ text_as_regexp(text)
+ end
+
+ def matches_attribute?(attribute, text)
+ @html_unit_element.get_attribute(attribute) =~ text_as_regexp(text)
+ end
+
+ def ancestors
+ current_element = self
+ [].tap do |ancs|
+ while current_element.parent && current_element.elem_type != 'html'
+ ancs << current_element
+ current_element = current_element.parent
+ end
+ end
+ end
+
+ def label_matches?(label_text)
+ ancestors.each do |anc|
+ return true if anc.elem_type == 'label' && anc.matches_text?(label_text)
+ end
+
+ if id_attribute.blank?
+ return nil
+ else
+ label_tag = @page.elements_by_xpath("//label[@for='#{id_attribute}']").first
+ return label_tag if label_tag && label_tag.matches_text?(label_text)
+ end
+ end
+
+ def click
+ @html_unit_element.click
+ end
+
+ def fill_in_with(value)
+ clear
+ type_string(value)
+ end
+
+ def clear
+ case @html_unit_element.getTagName
+ when 'textarea' then @html_unit_element.setText('')
+ when 'input' then @html_unit_element.setValueAttribute('')
+ end
+ end
+
+ def select(option_text)
+ @html_unit_element.getOptions.select { |e| e.asText =~ text_as_regexp(option_text) }.each do |option|
+ option.click
+ #@container.update_page(option.click)
+ end
+ end
+
+ def set(value = true)
+ @html_unit_element.setChecked(value)
+ #value ? @html_unit_element.click : @html_unit_element.setChecked(value)
+ end
+
+ def includes_option?(option_text)
+ !!@html_unit_element.getByXPath("//option").detect {|opt| opt.as_text =~ text_as_regexp(option_text) }
+ end
+
+ def to_s
+ @html_unit_element.as_text
+ end
+
+ def hidden?
+ !!ancestors.detect { |elem| elem.style =~ /display\s*:[\s'"]*none/ }
+ end
+
+ def visible?
+ !hidden?
+ end
+
+ def parent
+ @html_unit_element.respond_to?(:parent_node) ? Element.new(@html_unit_element.parent_node, @page) : nil
+ end
+
+ def class_name
+ @html_unit_element.class_attribute
+ end
+
+ def method_missing(name, *args)
+ return @html_unit_element.send("#{name}_attribute") if @html_unit_element.respond_to?("#{name}_attribute")
+ super
+ end
+
+ protected
+ def type_string(value)
+ last_page = nil
+ JavaString.new(value.to_java_bytes, @html_unit_element.getPage.getPageEncoding).toCharArray.each do |char|
+ last_page = @html_unit_element.type(char)
+ end
+ last_page
+ end
+
+ def text_as_regexp(text)
+ Regexp.new(Regexp.escape(text), true)
+ end
+ end
+
+end
\ No newline at end of file
diff --git a/lib/webrat/ruby_html_unit/htmlunit/commons-codec-1.3.jar b/lib/webrat/ruby_html_unit/htmlunit/commons-codec-1.3.jar
new file mode 100644
index 0000000..957b675
Binary files /dev/null and b/lib/webrat/ruby_html_unit/htmlunit/commons-codec-1.3.jar differ
diff --git a/lib/webrat/ruby_html_unit/htmlunit/commons-collections-3.2.jar b/lib/webrat/ruby_html_unit/htmlunit/commons-collections-3.2.jar
new file mode 100644
index 0000000..75580be
Binary files /dev/null and b/lib/webrat/ruby_html_unit/htmlunit/commons-collections-3.2.jar differ
diff --git a/lib/webrat/ruby_html_unit/htmlunit/commons-httpclient-3.1.jar b/lib/webrat/ruby_html_unit/htmlunit/commons-httpclient-3.1.jar
new file mode 100644
index 0000000..7c59774
Binary files /dev/null and b/lib/webrat/ruby_html_unit/htmlunit/commons-httpclient-3.1.jar differ
diff --git a/lib/webrat/ruby_html_unit/htmlunit/commons-io-1.4.jar b/lib/webrat/ruby_html_unit/htmlunit/commons-io-1.4.jar
new file mode 100644
index 0000000..133dc6c
Binary files /dev/null and b/lib/webrat/ruby_html_unit/htmlunit/commons-io-1.4.jar differ
diff --git a/lib/webrat/ruby_html_unit/htmlunit/commons-lang-2.4.jar b/lib/webrat/ruby_html_unit/htmlunit/commons-lang-2.4.jar
new file mode 100644
index 0000000..532939e
Binary files /dev/null and b/lib/webrat/ruby_html_unit/htmlunit/commons-lang-2.4.jar differ
diff --git a/lib/webrat/ruby_html_unit/htmlunit/commons-logging-1.1.1.jar b/lib/webrat/ruby_html_unit/htmlunit/commons-logging-1.1.1.jar
new file mode 100644
index 0000000..1deef14
Binary files /dev/null and b/lib/webrat/ruby_html_unit/htmlunit/commons-logging-1.1.1.jar differ
diff --git a/lib/webrat/ruby_html_unit/htmlunit/cssparser-0.9.5.jar b/lib/webrat/ruby_html_unit/htmlunit/cssparser-0.9.5.jar
new file mode 100644
index 0000000..9fc2767
Binary files /dev/null and b/lib/webrat/ruby_html_unit/htmlunit/cssparser-0.9.5.jar differ
diff --git a/lib/webrat/ruby_html_unit/htmlunit/htmlunit-2.2.jar b/lib/webrat/ruby_html_unit/htmlunit/htmlunit-2.2.jar
new file mode 100644
index 0000000..8cc878a
Binary files /dev/null and b/lib/webrat/ruby_html_unit/htmlunit/htmlunit-2.2.jar differ
diff --git a/lib/webrat/ruby_html_unit/htmlunit/htmlunit-core-js-2.2.jar b/lib/webrat/ruby_html_unit/htmlunit/htmlunit-core-js-2.2.jar
new file mode 100644
index 0000000..be23a89
Binary files /dev/null and b/lib/webrat/ruby_html_unit/htmlunit/htmlunit-core-js-2.2.jar differ
diff --git a/lib/webrat/ruby_html_unit/htmlunit/nekohtml-1.9.8.jar b/lib/webrat/ruby_html_unit/htmlunit/nekohtml-1.9.8.jar
new file mode 100644
index 0000000..1848c0a
Binary files /dev/null and b/lib/webrat/ruby_html_unit/htmlunit/nekohtml-1.9.8.jar differ
diff --git a/lib/webrat/ruby_html_unit/htmlunit/sac-1.3.jar b/lib/webrat/ruby_html_unit/htmlunit/sac-1.3.jar
new file mode 100644
index 0000000..39b92b1
Binary files /dev/null and b/lib/webrat/ruby_html_unit/htmlunit/sac-1.3.jar differ
diff --git a/lib/webrat/ruby_html_unit/htmlunit/xalan-2.7.0.jar b/lib/webrat/ruby_html_unit/htmlunit/xalan-2.7.0.jar
new file mode 100644
index 0000000..007be39
Binary files /dev/null and b/lib/webrat/ruby_html_unit/htmlunit/xalan-2.7.0.jar differ
diff --git a/lib/webrat/ruby_html_unit/htmlunit/xercesImpl-2.8.1.jar b/lib/webrat/ruby_html_unit/htmlunit/xercesImpl-2.8.1.jar
new file mode 100644
index 0000000..3b351f6
Binary files /dev/null and b/lib/webrat/ruby_html_unit/htmlunit/xercesImpl-2.8.1.jar differ
diff --git a/lib/webrat/ruby_html_unit/htmlunit/xml-apis-1.0.b2.jar b/lib/webrat/ruby_html_unit/htmlunit/xml-apis-1.0.b2.jar
new file mode 100644
index 0000000..ad33a5a
Binary files /dev/null and b/lib/webrat/ruby_html_unit/htmlunit/xml-apis-1.0.b2.jar differ
diff --git a/lib/webrat/ruby_html_unit/page.rb b/lib/webrat/ruby_html_unit/page.rb
new file mode 100644
index 0000000..34b2e10
--- /dev/null
+++ b/lib/webrat/ruby_html_unit/page.rb
@@ -0,0 +1,114 @@
+module RubyHtmlUnit
+
+ class Page
+ SAVED_PAGE_DIR = File.expand_path('.')
+
+ def initialize(html_unit_page)
+ @html_unit_page = html_unit_page
+ end
+
+ def body
+ @html_unit_page.getWebResponse.getContentAsString || ''
+ end
+ alias_method :html_document, :body
+
+ def find_link(text)
+ matching_links = elements_by_tag_name('a').select{|e| e.matches_text?(text)}
+
+ if matching_links.any?
+ matching_links.sort_by { |l| l.to_s.length }.first
+ else
+ flunk("Could not find link with text #{text.inspect}")
+ end
+ end
+
+ def find_button(text = nil)
+ buttons = []
+ field_type_xpaths = %w( //button //input[@type='submit'] //input[@type='reset'] //input[@type='image'] //input[@type='button'] )
+ field_type_xpaths.each do |xpath_expr|
+ buttons += elements_by_xpath(xpath_expr)
+ end
+
+ return buttons.first if text.nil?
+
+ matching_buttons = buttons.select{|b| b.matches_attribute?('value', text) }
+
+ if matching_buttons.any?
+ matching_buttons.sort_by { |b| b.to_s.length }.first
+ else
+ flunk("Could not find button with text #{text.inspect}")
+ end
+ end
+
+ def find_field(id_or_name_or_label, field_type)
+ field_type_xpaths = case field_type
+ when :text then %w( //input[@type='text'] //input[@type='hidden'] //textarea )
+ when :select then %w( //select )
+ when :radio then %w( //input[@type='radio'] )
+ when :checkbox then %w( //input[@type='checkbox'] )
+ end
+
+ field_type_xpaths.each do |xpath_expr|
+ elements_by_xpath(xpath_expr).each do |possible_field|
+ return possible_field if possible_field.id_attribute == id_or_name_or_label ||
+ possible_field.name == id_or_name_or_label ||
+ possible_field.label_matches?(id_or_name_or_label)
+ end
+ end
+
+ flunk("Could not find #{field_type_xpaths.inspect}: #{id_or_name_or_label.inspect}")
+ end
+
+ def find_select_list_with_option(option_text, id_or_name_or_label = nil)
+ if id_or_name_or_label
+ select_tag = find_field(id_or_name_or_label, :select)
+ return select_tag if select_tag && select_tag.includes_option?(option_text)
+ else
+ elements_by_xpath("//select").each do |sel|
+ if sel.includes_option?(option_text)
+ return sel
+ end
+ end
+ end
+ nil
+ end
+
+ def elements_by_tag_name(tag_name)
+ node_list = @html_unit_page.getElementsByTagName(tag_name)
+ list_len = node_list.length
+
+ [].tap do |array|
+ 0.upto(list_len-1) { |i| array << Element.new(node_list.item(i), self)}
+ end
+ end
+
+ def elements_by_xpath(xpath_expression)
+ @html_unit_page.getByXPath(xpath_expression).to_a.map{ |e| Element.new(e, self) }
+ end
+
+ def save_and_open
+ return unless File.exist?(SAVED_PAGE_DIR)
+
+ filename = "#{SAVED_PAGE_DIR}/webrat-#{Time.now.to_i}.html"
+
+ File.open(filename, "w") do |f|
+ f.write rewrite_css_and_image_references(body)
+ end
+
+ open_in_browser(filename)
+ end
+
+ def rewrite_css_and_image_references(response_html) # :nodoc
+ response_html.gsub(%r<"/(stylesheets|images)>, Session.rewrite_url('"/\1'))
+ end
+
+ def open_in_browser(path) # :nodoc
+ `open #{path}`
+ end
+
+ def flunk(message)
+ raise message
+ end
+ end
+
+end
\ No newline at end of file
diff --git a/lib/webrat/ruby_html_unit/response.rb b/lib/webrat/ruby_html_unit/response.rb
new file mode 100644
index 0000000..201db0e
--- /dev/null
+++ b/lib/webrat/ruby_html_unit/response.rb
@@ -0,0 +1,24 @@
+module RubyHtmlUnit
+
+ class Response
+ def initialize(html_unit_response)
+ @html_unit_response = html_unit_response
+ end
+
+ def success?
+ @html_unit_response.status_code / 100 == 2
+ end
+
+ def content_type
+ @html_unit_response.content_type
+ end
+
+ def body
+ @html_unit_response.getContentAsString || ''
+ end
+ alias_method :text, :body
+
+
+ end
+
+end
\ No newline at end of file
diff --git a/lib/webrat/ruby_html_unit/rspec_story.rb b/lib/webrat/ruby_html_unit/rspec_story.rb
new file mode 100644
index 0000000..2c9847a
--- /dev/null
+++ b/lib/webrat/ruby_html_unit/rspec_story.rb
@@ -0,0 +1,26 @@
+class Spec::Story::Runner::ScenarioRunner
+ def initialize
+ @listeners = []
+ end
+end
+
+module RubyHtmlUnit
+
+ class RspecStory
+ include ::Spec::Matchers
+ include ::Spec::Rails::Matchers
+
+ def current_session
+ @current_session ||= Session.new
+ end
+
+ def method_missing(name, *args)
+ if current_session.respond_to?(name)
+ current_session.send(name, *args)
+ else
+ super
+ end
+ end
+ end
+
+end
\ No newline at end of file
diff --git a/lib/webrat/ruby_html_unit/session.rb b/lib/webrat/ruby_html_unit/session.rb
new file mode 100644
index 0000000..221de84
--- /dev/null
+++ b/lib/webrat/ruby_html_unit/session.rb
@@ -0,0 +1,127 @@
+module RubyHtmlUnit
+
+ class Session
+ attr_accessor :response, :current_page, :alert_handler
+
+ include ActionController::UrlWriter
+ include ActionController::Assertions
+ include Test::Unit::Assertions
+
+
+ class << self
+ def rewrite_url(url)
+ if url =~ %r{^(/.*)} || url =~ %r{^https?://www.example.com(/.*)}
+ append_to_root_url($1)
+ else
+ url
+ end
+ end
+
+ def append_to_root_url(path)
+ path = "/#{path}" unless path =~ /^\//
+ "http://localhost:#{TEST_SERVER_PORT}#{path}"
+ end
+ end
+
+ def initialize()
+ #java.lang.System.getProperties.put("org.apache.commons.logging.simplelog.defaultlog", opts[:log_level] ? opts[:log_level].to_s : "warn")
+ java.lang.System.getProperties.put("org.apache.commons.logging.simplelog.defaultlog", "warn")
+
+ browser = ::HtmlUnit::BrowserVersion::FIREFOX_2
+ @webclient = ::HtmlUnit::WebClient.new(browser)
+ @webclient.setThrowExceptionOnScriptError(false) #unless opts[:javascript_exceptions]
+ @webclient.setThrowExceptionOnFailingStatusCode(false) #unless opts[:status_code_exceptions]
+ @webclient.setCssEnabled(false) #unless opts[:css]
+ @webclient.setUseInsecureSSL(true) #if opts[:secure_ssl]
+
+ @alert_handler = AlertHandler.new
+
+ @webclient.setAlertHandler(@alert_handler)
+
+ @response = nil
+ @current_page = nil
+ end
+
+ def visits(url)
+ update_page(@webclient.getPage(Session.rewrite_url(url)))
+ end
+
+ def clicks_link(text)
+ link = @current_page.find_link(text)
+ update_page(link.click)
+ end
+
+ def clicks_button(text = nil)
+ button = @current_page.find_button(text)
+ update_page(button.click)
+ end
+
+ def fills_in(id_or_name_or_label, options = {})
+ field = @current_page.find_field(id_or_name_or_label, :text)
+ update_page(field.fill_in_with(options[:with]))
+ end
+
+ def selects(option_text, options = {})
+ id_or_name_or_label = options[:from]
+
+ select_tag = @current_page.find_select_list_with_option(option_text, id_or_name_or_label)
+
+ flunk("Could not find option #{option_text.inspect}") if select_tag.nil?
+ select_tag.select(option_text)
+ end
+
+ def chooses(label)
+ field = @current_page.find_field(label, :radio)
+ update_page(field.set)
+ end
+
+ def checks(id_or_name_or_label)
+ field = @current_page.find_field(id_or_name_or_label, :checkbox)
+ update_page(field.set(true))
+ end
+
+ def unchecks(id_or_name_or_label)
+ field = @current_page.find_field(id_or_name_or_label, :checkbox)
+ update_page(field.set(false))
+ end
+
+ def get_element(dom_id)
+ @current_page.elements_by_xpath("//*[@id='#{dom_id}']").first
+ end
+ alias_method :get_element_by_id, :get_element
+
+ def wait_for_result(wait_type)
+ # No support for different types of waiting right now
+ sleep(0.5)
+ # if wait_type == :ajax
+ # wait_for_ajax
+ # elsif wait_type == :effects
+ # wait_for_effects
+ # else
+ # wait_for_page_to_load
+ # end
+ end
+
+ def update_page(html_unit_page)
+ @current_page = Page.new(html_unit_page)
+ @response = Response.new(html_unit_page.getWebResponse)
+ end
+
+ def save_and_open_page
+ @current_page.save_and_open
+ end
+
+ def respond_to?(method)
+ super || @current_page.respond_to?(method)
+ end
+
+ def method_missing(name, *args)
+ if @current_page.respond_to?(name)
+ @current_page.send(name, *args)
+ else
+ super
+ end
+ end
+ end
+
+end
\ No newline at end of file