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