celerity_thingista 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. data/.document +5 -0
  2. data/.gitignore +9 -0
  3. data/.gitmodules +3 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE +278 -0
  7. data/README.rdoc +84 -0
  8. data/Rakefile +15 -0
  9. data/benchmark/bm_2000_spans.rb +48 -0
  10. data/benchmark/bm_digg.rb +26 -0
  11. data/benchmark/bm_google_images.rb +36 -0
  12. data/benchmark/bm_input_locator.rb +69 -0
  13. data/benchmark/bm_text_input.rb +19 -0
  14. data/benchmark/loader.rb +14 -0
  15. data/celerity.gemspec +26 -0
  16. data/lib/celerity.rb +75 -0
  17. data/lib/celerity/browser.rb +924 -0
  18. data/lib/celerity/clickable_element.rb +73 -0
  19. data/lib/celerity/collections.rb +164 -0
  20. data/lib/celerity/container.rb +802 -0
  21. data/lib/celerity/default_viewer.rb +14 -0
  22. data/lib/celerity/disabled_element.rb +40 -0
  23. data/lib/celerity/element.rb +314 -0
  24. data/lib/celerity/element_collection.rb +115 -0
  25. data/lib/celerity/element_locator.rb +164 -0
  26. data/lib/celerity/elements/button.rb +54 -0
  27. data/lib/celerity/elements/file_field.rb +29 -0
  28. data/lib/celerity/elements/form.rb +22 -0
  29. data/lib/celerity/elements/frame.rb +86 -0
  30. data/lib/celerity/elements/image.rb +89 -0
  31. data/lib/celerity/elements/label.rb +16 -0
  32. data/lib/celerity/elements/link.rb +43 -0
  33. data/lib/celerity/elements/meta.rb +14 -0
  34. data/lib/celerity/elements/non_control_elements.rb +124 -0
  35. data/lib/celerity/elements/option.rb +38 -0
  36. data/lib/celerity/elements/radio_check.rb +114 -0
  37. data/lib/celerity/elements/select_list.rb +146 -0
  38. data/lib/celerity/elements/table.rb +154 -0
  39. data/lib/celerity/elements/table_cell.rb +36 -0
  40. data/lib/celerity/elements/table_elements.rb +42 -0
  41. data/lib/celerity/elements/table_row.rb +54 -0
  42. data/lib/celerity/elements/text_field.rb +168 -0
  43. data/lib/celerity/exception.rb +83 -0
  44. data/lib/celerity/htmlunit.rb +64 -0
  45. data/lib/celerity/htmlunit/commons-codec-1.7.jar +0 -0
  46. data/lib/celerity/htmlunit/commons-collections-3.2.1.jar +0 -0
  47. data/lib/celerity/htmlunit/commons-io-2.4.jar +0 -0
  48. data/lib/celerity/htmlunit/commons-lang3-3.1.jar +0 -0
  49. data/lib/celerity/htmlunit/commons-logging-1.1.1.jar +0 -0
  50. data/lib/celerity/htmlunit/cssparser-0.9.9.jar +0 -0
  51. data/lib/celerity/htmlunit/htmlunit-2.12.jar +0 -0
  52. data/lib/celerity/htmlunit/htmlunit-core-js-2.12.jar +0 -0
  53. data/lib/celerity/htmlunit/httpclient-4.2.3.jar +0 -0
  54. data/lib/celerity/htmlunit/httpcore-4.2.2.jar +0 -0
  55. data/lib/celerity/htmlunit/httpmime-4.2.3.jar +0 -0
  56. data/lib/celerity/htmlunit/jetty-http-8.1.9.v20130131.jar +0 -0
  57. data/lib/celerity/htmlunit/jetty-io-8.1.9.v20130131.jar +0 -0
  58. data/lib/celerity/htmlunit/jetty-util-8.1.9.v20130131.jar +0 -0
  59. data/lib/celerity/htmlunit/jetty-websocket-8.1.9.v20130131.jar +0 -0
  60. data/lib/celerity/htmlunit/nekohtml-1.9.18.jar +0 -0
  61. data/lib/celerity/htmlunit/sac-1.3.jar +0 -0
  62. data/lib/celerity/htmlunit/serializer-2.7.1.jar +0 -0
  63. data/lib/celerity/htmlunit/xalan-2.7.1.jar +0 -0
  64. data/lib/celerity/htmlunit/xercesImpl-2.10.0.jar +0 -0
  65. data/lib/celerity/htmlunit/xml-apis-1.4.01.jar +0 -0
  66. data/lib/celerity/identifier.rb +28 -0
  67. data/lib/celerity/ignoring_web_connection.rb +15 -0
  68. data/lib/celerity/input_element.rb +25 -0
  69. data/lib/celerity/javascript_debugger.rb +32 -0
  70. data/lib/celerity/listener.rb +143 -0
  71. data/lib/celerity/resources/no_viewer.png +0 -0
  72. data/lib/celerity/short_inspect.rb +29 -0
  73. data/lib/celerity/util.rb +129 -0
  74. data/lib/celerity/version.rb +3 -0
  75. data/lib/celerity/viewer_connection.rb +89 -0
  76. data/lib/celerity/watir_compatibility.rb +70 -0
  77. data/lib/celerity/xpath_support.rb +50 -0
  78. data/spec/browser_authentication_spec.rb +16 -0
  79. data/spec/browser_spec.rb +439 -0
  80. data/spec/button_spec.rb +24 -0
  81. data/spec/clickable_element_spec.rb +39 -0
  82. data/spec/default_viewer_spec.rb +23 -0
  83. data/spec/element_spec.rb +77 -0
  84. data/spec/filefield_spec.rb +18 -0
  85. data/spec/htmlunit_spec.rb +63 -0
  86. data/spec/implementation.rb +7 -0
  87. data/spec/index_offset_spec.rb +24 -0
  88. data/spec/link_spec.rb +16 -0
  89. data/spec/listener_spec.rb +142 -0
  90. data/spec/spec_helper.rb +6 -0
  91. data/spec/table_spec.rb +41 -0
  92. data/spec/watir_compatibility_spec.rb +32 -0
  93. data/tasks/benchmark.rake +4 -0
  94. data/tasks/check.rake +24 -0
  95. data/tasks/clean.rake +3 -0
  96. data/tasks/fix.rake +25 -0
  97. data/tasks/jar.rake +55 -0
  98. data/tasks/rdoc.rake +4 -0
  99. data/tasks/snapshot.rake +25 -0
  100. data/tasks/spec.rake +27 -0
  101. data/tasks/website.rake +10 -0
  102. data/tasks/yard.rake +16 -0
  103. data/website/benchmarks.html +237 -0
  104. data/website/css/color.css +153 -0
  105. data/website/css/hacks.css +3 -0
  106. data/website/css/layout.css +179 -0
  107. data/website/css/screen.css +5 -0
  108. data/website/css/textmate.css +226 -0
  109. data/website/css/typography.css +72 -0
  110. data/website/gfx/body_bg.gif +0 -0
  111. data/website/gfx/button_bg.jpg +0 -0
  112. data/website/gfx/header_bg.jpg +0 -0
  113. data/website/gfx/header_left.jpg +0 -0
  114. data/website/gfx/header_right.jpg +0 -0
  115. data/website/gfx/nav_bg.jpg +0 -0
  116. data/website/index.html +125 -0
  117. data/website/yard/index.html +1 -0
  118. metadata +246 -0
@@ -0,0 +1,26 @@
1
+ require File.dirname(__FILE__) + "/loader"
2
+
3
+ TESTS = 5
4
+ res = Benchmark.bmbm do |results|
5
+ results.report("Diggs on front page") do
6
+ TESTS.times do
7
+ # Create browser object
8
+ browser = create_browser
9
+
10
+ # Go to digg.com
11
+ browser.goto('http://digg.com/')
12
+
13
+ # Gather statistics
14
+ total_diggs = 0
15
+ digg_number_elements = browser.links.select { |link| link.id =~ /diggs/ }
16
+ digg_numbers = digg_number_elements.collect { |digg_number_element| digg_number_element.text }
17
+ digg_numbers.each { |digg_number| total_diggs += digg_number.to_i }
18
+ #puts "Found #{digg_numbers.size} stories, with a total of #{total_diggs} diggs."
19
+ end
20
+ end
21
+ end
22
+
23
+ puts
24
+ total = res.inject(0.0) { |mem, bm| mem + bm.real }
25
+ puts "total : " + total.to_s
26
+ puts "average: " + (total/res.size.to_f).to_s
@@ -0,0 +1,36 @@
1
+ require File.dirname(__FILE__) + "/loader"
2
+
3
+ TESTS = 5
4
+ res = Benchmark.bmbm do |results|
5
+ results.report("Google image search results") do
6
+ TESTS.times do
7
+ # Create browser object
8
+ browser = create_browser
9
+
10
+ # Goto images.google.com
11
+ browser.goto('http://images.google.com/ncr')
12
+
13
+ # Search for Watir
14
+ browser.text_field(:name, 'q').set('Watir')
15
+ browser.button(:value, 'Search Images').click
16
+
17
+ src_pool = []
18
+ pages = 1
19
+ # Gather statistics and click Next if there are more results
20
+ while browser.link(:text, 'Next').exists?
21
+ pages += 1
22
+ browser.link(:text, 'Next').click unless src_pool.empty?
23
+ table_cells = browser.cells.select { |cell| cell.id =~ /tDataImage\d+/ }
24
+ table_cells.each do |cell|
25
+ src_pool << cell.images.first.src if cell.images.first.exists?
26
+ end
27
+ end
28
+ #puts "Looked at #{pages} pages of image search results. Got #{src_pool.size} images."
29
+ end
30
+ end
31
+ end
32
+
33
+ puts
34
+ total = res.inject(0.0) { |mem, bm| mem + bm.real }
35
+ puts "total : " + total.to_s
36
+ puts "average: " + (total/res.size.to_f).to_s
@@ -0,0 +1,69 @@
1
+ require File.dirname(__FILE__) + "/loader"
2
+
3
+ browser = create_browser
4
+ browser.goto(HTML_DIR + "/forms_with_input_elements.html")
5
+
6
+ TESTS = 1000
7
+ res = Benchmark.bmbm do |results|
8
+ results.report("text input by id (String)") do
9
+ TESTS.times { browser.text_field(:id, "new_user_first_name").exists? }
10
+ end
11
+ results.report("text input by id (Regexp)") do
12
+ TESTS.times { browser.text_field(:id, /first_name/).exists? }
13
+ end
14
+ results.report("text input by name (String)") do
15
+ TESTS.times { browser.text_field(:name, "new_user_email").exists? }
16
+ end
17
+ results.report("text input by name (Regexp)") do
18
+ TESTS.times { browser.text_field(:name, /user_email/).exists? }
19
+ end
20
+
21
+ results.report("select list by id (String)") do
22
+ TESTS.times { browser.select_list(:id, 'new_user_country').exists? }
23
+ end
24
+ results.report("select list by id (Regexp)") do
25
+ TESTS.times { browser.select_list(:id, /user_country/).exists? }
26
+ end
27
+ results.report("select list by name (String)") do
28
+ TESTS.times { browser.select_list(:name, 'new_user_country').exists? }
29
+ end
30
+ results.report("select list by name (Regexp)") do
31
+ TESTS.times { browser.select_list(:name, /user_country/).exists? }
32
+ end
33
+
34
+ results.report("checkbox by id (String)") do
35
+ TESTS.times { browser.checkbox(:id, 'new_user_interests_books').exists? }
36
+ end
37
+ results.report("checkbox by id (Regexp)") do
38
+ TESTS.times { browser.checkbox(:id, /interests_books/).exists? }
39
+ end
40
+
41
+ results.report("checkbox by name (String)") do
42
+ TESTS.times { browser.checkbox(:name, 'new_user_interests').exists? }
43
+ end
44
+ results.report("checkbox by name (Regexp)") do
45
+ TESTS.times { browser.checkbox(:name, /user_interests/).exists? }
46
+ end
47
+
48
+ results.report("checkbox by id (String) and value (String)") do
49
+ TESTS.times { browser.checkbox(:id, 'new_user_interests_books', 'cars').exists? }
50
+ end
51
+ results.report("checkbox by id (Regexp) and value (Regexp)") do
52
+ TESTS.times { browser.checkbox(:id, /interests_books/, /car/).exists? }
53
+ end
54
+
55
+ results.report("checkbox by name (String) and value (String)") do
56
+ TESTS.times { browser.checkbox(:name, 'new_user_interests', 'dancing').exists? }
57
+ end
58
+ results.report("checkbox by name (Regexp) and value (Regexp)") do
59
+ TESTS.times { browser.checkbox(:name, /user_interests/, /danc/).exists? }
60
+ end
61
+
62
+
63
+
64
+ end
65
+
66
+ puts
67
+ total = res.inject(0.0) { |mem, bm| mem + bm.real }
68
+ puts "total : " + total.to_s
69
+ puts "average: " + (total/res.size.to_f).to_s
@@ -0,0 +1,19 @@
1
+ require File.dirname(__FILE__) + "/loader"
2
+
3
+ browser = create_browser
4
+ browser.goto(HTML_DIR + "/forms_with_input_elements.html")
5
+
6
+ TESTS = 10000
7
+ res = Benchmark.bmbm do |results|
8
+ results.report("TextField#set") do
9
+ TESTS.times { browser.text_field(:id, "new_user_first_name").set("1234567890") }
10
+ end
11
+ results.report("TextField#value=") do
12
+ TESTS.times { browser.text_field(:id, "new_user_first_name").value = "1234567890" }
13
+ end
14
+ end
15
+
16
+ puts
17
+ total = res.inject(0.0) { |mem, bm| mem + bm.real }
18
+ puts "total : " + total.to_s
19
+ puts "average: " + (total/res.size.to_f).to_s
@@ -0,0 +1,14 @@
1
+ require 'benchmark'
2
+ require File.dirname(__FILE__) + "/../spec/spec_helper"
3
+
4
+
5
+ def create_browser
6
+ if RUBY_PLATFORM =~ /java/
7
+ browser = Celerity::Browser.new(:log_level => :off)
8
+ else
9
+ require 'watir'
10
+ browser = Watir::IE.new
11
+ end
12
+
13
+ browser
14
+ end
data/celerity.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require 'celerity/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = %q{celerity_thingista}
7
+ s.version = Celerity::VERSION
8
+ s.authors = ["Jari Bakken", "T. Alexander Lystad", "Knut Johannes Dahle"]
9
+ s.description = "Celerity is a JRuby wrapper around HtmlUnit – a headless Java browser with JavaScript support. It provides a simple API for programmatic navigation through web applications. Celerity provides a superset of Watir's API."
10
+ s.summary = %q{Celerity is a JRuby library for easy and fast functional test automation for web applications.}
11
+ s.email = %q{jari.bakken@gmail.com}
12
+ s.homepage = %q{http://github.com/jarib/celerity}
13
+ s.require_paths = ["lib"]
14
+ s.rubyforge_project = %q{celerity}
15
+
16
+ s.add_development_dependency "rake", "~> 0.9.2"
17
+ s.add_development_dependency "rspec", "~> 2.0.0"
18
+ s.add_development_dependency "yard", ">= 0"
19
+ s.add_development_dependency "sinatra", "~> 1.0"
20
+ s.add_development_dependency "mongrel", ">= 0"
21
+
22
+ s.files = `git ls-files`.split("\n")
23
+ s.test_files = []
24
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
+ end
26
+
data/lib/celerity.rb ADDED
@@ -0,0 +1,75 @@
1
+ raise "Celerity only works on JRuby at the moment." unless RUBY_PLATFORM =~ /java/
2
+
3
+ require "java"
4
+ require "logger"
5
+ require "uri"
6
+ require "pp"
7
+ require "timeout"
8
+ require "time"
9
+ require "socket"
10
+ require "fileutils"
11
+ require "thread"
12
+
13
+ module Celerity
14
+ Log = Logger.new($DEBUG ? $stderr : nil)
15
+ Log.level = Logger::DEBUG
16
+
17
+ @index_offset = 1
18
+ class << self
19
+
20
+ #
21
+ # This index_offset attribute controls the indexing used when locating
22
+ # elements by :index or fetching from Celerity::ElementCollections.
23
+ #
24
+ # By default it is set to 1 for Watir compatibility, but users who use
25
+ # Celerity exlusively may want it set to 0 to make Celerity more consistent with Ruby.
26
+ #
27
+ attr_accessor :index_offset
28
+ end
29
+
30
+ DIR = File.expand_path("../celerity", __FILE__)
31
+ end
32
+
33
+ require "celerity/version"
34
+ require "celerity/htmlunit"
35
+ require "celerity/exception"
36
+ require "celerity/clickable_element"
37
+ require "celerity/disabled_element"
38
+ require "celerity/element_collection"
39
+ require "celerity/collections"
40
+ require "celerity/element_locator"
41
+ require "celerity/identifier"
42
+ require "celerity/short_inspect"
43
+ require "celerity/container"
44
+ require "celerity/xpath_support"
45
+ require "celerity/element"
46
+ require "celerity/input_element"
47
+ require "celerity/elements/non_control_elements"
48
+ require "celerity/elements/button"
49
+ require "celerity/elements/file_field"
50
+ require "celerity/elements/form"
51
+ require "celerity/elements/frame"
52
+ require "celerity/elements/image"
53
+ require "celerity/elements/label"
54
+ require "celerity/elements/link"
55
+ require "celerity/elements/meta"
56
+ require "celerity/elements/option"
57
+ require "celerity/elements/radio_check"
58
+ require "celerity/elements/select_list"
59
+ require "celerity/elements/table"
60
+ require "celerity/elements/table_elements"
61
+ require "celerity/elements/table_cell"
62
+ require "celerity/elements/table_row"
63
+ require "celerity/elements/text_field"
64
+ require "celerity/util"
65
+ require "celerity/default_viewer"
66
+ require "celerity/listener"
67
+ require "celerity/ignoring_web_connection"
68
+ require "celerity/javascript_debugger"
69
+ require "celerity/viewer_connection"
70
+ require "celerity/browser"
71
+ require "celerity/watir_compatibility"
72
+
73
+ # undefine deprecated methods to use them for Element attributes
74
+ Object.send :undef_method, :id if Object.method_defined? "id"
75
+ Object.send :undef_method, :type if Object.method_defined? "type"
@@ -0,0 +1,924 @@
1
+ module Celerity
2
+ class Browser
3
+ include Container
4
+ include XpathSupport
5
+
6
+ attr_accessor :page, :object, :charset
7
+ attr_reader :webclient, :viewer, :options
8
+
9
+ #
10
+ # Initialize a browser and go to the given URL
11
+ #
12
+ # @param [String] uri The URL to go to.
13
+ # @return [Celerity::Browser] instance.
14
+ #
15
+
16
+ def self.start(uri)
17
+ browser = new
18
+ browser.goto(uri)
19
+ browser
20
+ end
21
+
22
+ #
23
+ # Not implemented. Use ClickableElement#click_and_attach instead.
24
+ #
25
+
26
+ def self.attach(*args)
27
+ raise NotImplementedError, "use ClickableElement#click_and_attach instead"
28
+ end
29
+
30
+ #
31
+ # Creates a browser object.
32
+ #
33
+ # @see Celerity::Container for an introduction to the main API.
34
+ #
35
+ # @option opts :browser [:internet_explorer, :firefox, :firefox3] (:firefox3) Set the BrowserVersion used by HtmlUnit. Defaults to Firefox 3.
36
+ # @option opts :charset [String] ("UTF-8") Specify the charset that webclient will use for requests.
37
+ # @option opts :css [Boolean] (true) Enable/disable CSS. Enabled by default.
38
+ # @option opts :ignore_pattern [Regexp] See Browser#ignore_pattern=
39
+ # @option opts :javascript_enabled [Boolean] (true) Enable/disable JavaScript evaluation. Enabled by default.
40
+ # @option opts :javascript_exceptions [Boolean] (false) Raise exceptions on script errors. Disabled by default.
41
+ # @option opts :log_level [Symbol] (:warning) @see log_level=
42
+ # @option opts :proxy [String] (nil) Proxy server to use, in address:port format.
43
+ # @option opts :refresh_handler [:immediate, :waiting, :threaded] (:immediate) Set HtmlUnit's refresh handler.
44
+ # @option opts :render [:html, :xml] (:html) What DOM representation to send to connected viewers.
45
+ # @option opts :resynchronize [Boolean] (false) Use HtmlUnit::NicelyResynchronizingAjaxController to resynchronize Ajax calls.
46
+ # @option opts :secure_ssl [Boolean] (true) Enable/disable secure SSL. Enabled by default.
47
+ # @option opts :status_code_exceptions [Boolean] (false) Raise exceptions on failing status codes (404 etc.). Disabled by default.
48
+ # @option opts :user_agent [String] Override the User-Agent set by the :browser option
49
+ # @option opts :default_wait [Integer] The default number of seconds to wait when Browser#wait is called.
50
+ # @option opts :viewer [String, false] ("127.0.0.1:6429") Connect to a CelerityViewer if available.
51
+ #
52
+ # @return [Celerity::Browser] An instance of the browser.
53
+ #
54
+ # @api public
55
+ #
56
+
57
+ def initialize(opts = {})
58
+ unless opts.is_a?(Hash)
59
+ raise TypeError, "wrong argument type #{opts.class}, expected Hash"
60
+ end
61
+
62
+ unless (render_types = [:html, :xml, nil, 'html', 'xml']).include?(opts[:render])
63
+ raise ArgumentError, "expected one of #{render_types.inspect} for key :render"
64
+ end
65
+
66
+ @options = opts.dup # keep the unmodified version around as well
67
+ opts = opts.dup # we'll delete from opts, so dup to avoid side effects
68
+
69
+ @render_type = opts.delete(:render) || :html
70
+ @charset = opts.delete(:charset) || "UTF-8"
71
+ @page = nil
72
+ @error_checkers = []
73
+ @browser = self # for Container#browser
74
+
75
+ setup_webclient opts
76
+ setup_viewer opts.delete(:viewer)
77
+
78
+ self.log_level = opts.delete(:log_level) || :off
79
+
80
+ raise ArgumentError, "unknown option #{opts.inspect}" unless opts.empty?
81
+ end
82
+
83
+ def inspect
84
+ short_inspect :exclude => %w[@webclient @browser @object @options @listener @event_listener]
85
+ end
86
+
87
+ #
88
+ # Goto the given URL
89
+ #
90
+ # @param [String] uri The url.
91
+ # @param [Hash] (optional) a Hash of HTTP headers to use for the request.
92
+ #
93
+ # @return [String] The url.
94
+ #
95
+
96
+ def goto(uri, headers = nil)
97
+ uri = "http://#{uri}" unless uri =~ %r{://}
98
+
99
+ request = HtmlUnit::WebRequest.new(::Java::JavaNet::URL.new(uri))
100
+
101
+ request.setAdditionalHeaders(headers) if headers
102
+ request.setCharset(@charset)
103
+
104
+ rescue_status_code_exception do
105
+ self.page = @webclient.getPage(request)
106
+ end
107
+
108
+ url()
109
+ end
110
+
111
+ #
112
+ # Set the credentials used for basic HTTP authentication. (Celerity only)
113
+ #
114
+ # Example:
115
+ # browser.credentials = "username:password"
116
+ #
117
+ # @param [String] A string with username / password, separated by a colon
118
+ #
119
+
120
+ def credentials=(string)
121
+ user, pass = string.split(":")
122
+ dcp = HtmlUnit::DefaultCredentialsProvider.new
123
+ dcp.addCredentials(user, pass)
124
+ @webclient.setCredentialsProvider(dcp)
125
+ end
126
+
127
+ #
128
+ # Unsets the current page / closes all windows
129
+ #
130
+
131
+ def close
132
+ @page = nil
133
+ @object = nil
134
+ @webclient.closeAllWindows
135
+ @viewer.close
136
+ end
137
+
138
+ #
139
+ # @return [String] the URL of the current page
140
+ #
141
+
142
+ def url
143
+ assert_exists
144
+ @page.getWebResponse.getWebRequest.getUrl.toString
145
+ end
146
+
147
+ #
148
+ # @return [String] the title of the current page
149
+ #
150
+
151
+ def title
152
+ @page ? @page.getTitleText : ''
153
+ end
154
+
155
+ #
156
+ # @return [String] the value of window.status
157
+ #
158
+
159
+ def status
160
+ execute_script "window.status" # avoid the listener overhead
161
+ end
162
+
163
+ #
164
+ # @return [String] the HTML content of the current page
165
+ #
166
+
167
+ def html
168
+ return '' unless @page
169
+
170
+ @page.getWebResponse.getContentAsString(@charset)
171
+ end
172
+
173
+ #
174
+ # @return [String] the XML representation of the DOM
175
+ #
176
+
177
+ def xml
178
+ return '' unless @page
179
+ return @page.asXml if @page.respond_to?(:asXml)
180
+ return text # fallback to text (for exampel for "plain/text" pages)
181
+ end
182
+
183
+ #
184
+ # @return [String] a text representation of the current page
185
+ #
186
+
187
+ def text
188
+ return '' unless @page
189
+
190
+ if @page.respond_to?(:getContent)
191
+ @page.getContent.strip
192
+ elsif @page.respond_to?(:getDocumentElement) && doc = @page.getDocumentElement
193
+ doc.asText.strip
194
+ else
195
+ ''
196
+ end
197
+ end
198
+
199
+ #
200
+ # @return [Hash] response headers as a hash
201
+ #
202
+
203
+ def response_headers
204
+ return {} unless @page
205
+
206
+ Hash[*@page.getWebResponse.getResponseHeaders.map { |obj| [obj.name, obj.value] }.flatten]
207
+ end
208
+
209
+ #
210
+ # @return [Fixnum] status code of the last request
211
+ #
212
+
213
+ def status_code
214
+ @page.getWebResponse.getStatusCode
215
+ end
216
+
217
+ #
218
+ # @return [String] content-type as in 'text/html'
219
+ #
220
+
221
+ def content_type
222
+ return '' unless @page
223
+
224
+ @page.getWebResponse.getContentType
225
+ end
226
+
227
+ #
228
+ # @return [IO, nil] page contents as an IO, returns nil if no page is loaded.
229
+ #
230
+
231
+ def io
232
+ return nil unless @page
233
+
234
+ @page.getWebResponse.getContentAsStream.to_io
235
+ end
236
+
237
+ #
238
+ # Check if the current page contains the given text.
239
+ #
240
+ # @param [String, Regexp] expected_text The text to look for.
241
+ # @return [Numeric, nil] The index of the matched text, or nil if it isn't found.
242
+ # @raise [TypeError]
243
+ #
244
+
245
+ def contains_text(expected_text)
246
+ return nil unless exist?
247
+ super
248
+ end
249
+
250
+ #
251
+ # @return [HtmlUnit::HtmlHtml] the underlying HtmlUnit document.
252
+ #
253
+
254
+ def document
255
+ @object
256
+ end
257
+
258
+ #
259
+ # Goto back one history item
260
+ # @return [String] The url of the resulting page.
261
+ #
262
+
263
+ def back
264
+ @webclient.getCurrentWindow.getHistory.back
265
+ refresh_page_from_window
266
+
267
+ url
268
+ end
269
+
270
+ #
271
+ # Go forward one history item
272
+ # @return [String] The url of the resulting page.
273
+ #
274
+
275
+ def forward
276
+ @webclient.getCurrentWindow.getHistory.forward
277
+ refresh_page_from_window
278
+
279
+ url
280
+ end
281
+
282
+ #
283
+ # Wait for javascript jobs to finish
284
+ #
285
+ def wait(sec = @default_wait)
286
+ assert_exists
287
+ @webclient.waitForBackgroundJavaScript(sec * 1000);
288
+ end
289
+
290
+ #
291
+ # Refresh the current page
292
+ #
293
+
294
+ def refresh
295
+ assert_exists
296
+ @page.refresh
297
+ end
298
+
299
+ #
300
+ # Clears all cookies. (Celerity only)
301
+ #
302
+
303
+ def clear_cookies
304
+ @webclient.getCookieManager.clearCookies
305
+ end
306
+
307
+ #
308
+ # Clears the cache of "compiled JavaScript files and parsed CSS snippets"
309
+ #
310
+
311
+ def clear_cache
312
+ @webclient.cache.clear
313
+ end
314
+
315
+ #
316
+ # Set the maximum number of files to cache.
317
+ #
318
+
319
+ def cache_limit=(size)
320
+ @webclient.cache.setMaxSize(size)
321
+ end
322
+
323
+ def cache_limit
324
+ @webclient.cache.getMaxSize
325
+ end
326
+
327
+ #
328
+ # Get the cookies for this session. (Celerity only)
329
+ #
330
+ # @return [Hash<domain, Hash<name, value>>]
331
+ #
332
+
333
+ def cookies
334
+ result = Hash.new { |hash, key| hash[key] = {} }
335
+
336
+ cookies = @webclient.getCookieManager.getCookies
337
+ cookies.each do |cookie|
338
+ result[cookie.getDomain][cookie.getName] = cookie.getValue
339
+ end
340
+
341
+ result
342
+ end
343
+
344
+ #
345
+ # Add a cookie with the given parameters (Celerity only)
346
+ #
347
+ # @param [String] domain
348
+ # @param [String] name
349
+ # @param [String] value
350
+ #
351
+ # @option opts :path [String] ("/") A path
352
+ # @option opts :expires [Time] (1 day from now) An expiration date
353
+ # @option opts :secure [Boolean] (false)
354
+ #
355
+
356
+ def add_cookie(domain, name, value, opts = {})
357
+ path = opts.delete(:path) || "/"
358
+ max_age = opts.delete(:expires) || (Time.now + 60*60*24) # not sure if this is correct
359
+ secure = opts.delete(:secure) || false
360
+
361
+ raise(ArgumentError, "unknown option: #{opts.inspect}") unless opts.empty?
362
+
363
+ cookie = HtmlUnit::Util::Cookie.new(domain, name, value, path, max_age, secure)
364
+ @webclient.getCookieManager.addCookie cookie
365
+
366
+ cookie
367
+ end
368
+
369
+ #
370
+ # Remove the cookie with the given domain and name (Celerity only)
371
+ #
372
+ # @param [String] domain
373
+ # @param [String] name
374
+ #
375
+ # @raise [CookieNotFoundError] if the cookie doesn't exist
376
+ #
377
+
378
+ def remove_cookie(domain, name)
379
+ cm = @webclient.getCookieManager
380
+ cookie = cm.getCookies.find { |c| c.getDomain == domain && c.getName == name }
381
+
382
+ if cookie.nil?
383
+ raise CookieNotFoundError, "no cookie with domain #{domain.inspect} and name #{name.inspect}"
384
+ end
385
+
386
+ cm.removeCookie(cookie)
387
+ end
388
+
389
+ #
390
+ # Execute the given JavaScript on the current page.
391
+ # @return [Object] The resulting Object
392
+ #
393
+
394
+ def execute_script(source)
395
+ assert_exists
396
+ @page.executeJavaScript(source.to_s).getJavaScriptResult
397
+ end
398
+
399
+ # experimental - should be removed?
400
+ def send_keys(keys)
401
+ keys = keys.gsub(/\s*/, '').scan(/((?:\{[A-Z]+?\})|.)/u).flatten
402
+ keys.each do |key|
403
+ element = @page.getFocusedElement
404
+ case key
405
+ when "{TAB}"
406
+ @page.tabToNextElement
407
+ when /\w/
408
+ element.type(key)
409
+ else
410
+ raise NotImplementedError
411
+ end
412
+ end
413
+ end
414
+
415
+ #
416
+ # Wait until the given block evaluates to true (Celerity only)
417
+ #
418
+ # @param [Fixnum] timeout Number of seconds to wait before timing out (default: 30).
419
+ # @yieldparam [Celerity::Browser] browser The browser instance.
420
+ # @see Celerity::Browser#resynchronized
421
+ #
422
+
423
+ def wait_until(timeout = 30, &block)
424
+ returned = nil
425
+
426
+ Timeout.timeout(timeout) do
427
+ until returned = yield(self)
428
+ refresh_page_from_window
429
+ sleep 0.1
430
+ end
431
+ end
432
+
433
+ returned
434
+ end
435
+
436
+ #
437
+ # Wait while the given block evaluates to true (Celerity only)
438
+ #
439
+ # @param [Fixnum] timeout Number of seconds to wait before timing out (default: 30).
440
+ # @yieldparam [Celerity::Browser] browser The browser instance.
441
+ # @see Celerity::Browser#resynchronized
442
+ #
443
+
444
+ def wait_while(timeout = 30, &block)
445
+ returned = nil
446
+
447
+ Timeout.timeout(timeout) do
448
+ while returned = yield(self)
449
+ refresh_page_from_window
450
+ sleep 0.1
451
+ end
452
+ end
453
+
454
+ returned
455
+ end
456
+
457
+ #
458
+ # Allows you to temporarily switch to HtmlUnit's NicelyResynchronizingAjaxController
459
+ # to resynchronize ajax calls.
460
+ #
461
+ # @browser.resynchronized do |b|
462
+ # b.link(:id, 'trigger_ajax_call').click
463
+ # end
464
+ #
465
+ # @yieldparam [Celerity::Browser] browser The current browser object.
466
+ # @see Celerity::Browser#new for how to configure the browser to always use this.
467
+ #
468
+
469
+ def resynchronized(&block)
470
+ old_controller = @webclient.ajaxController
471
+ @webclient.setAjaxController(::HtmlUnit::NicelyResynchronizingAjaxController.new)
472
+ yield self
473
+ @webclient.setAjaxController(old_controller)
474
+ end
475
+
476
+ #
477
+ # Allows you to temporarliy switch to HtmlUnit's default AjaxController, so
478
+ # ajax calls are performed asynchronously. This is useful if you have created
479
+ # the Browser with :resynchronize => true, but want to switch it off temporarily.
480
+ #
481
+ # @yieldparam [Celerity::Browser] browser The current browser object.
482
+ # @see Celerity::Browser#new
483
+ #
484
+
485
+ def asynchronized(&block)
486
+ old_controller = @webclient.ajaxController
487
+ @webclient.setAjaxController(::HtmlUnit::AjaxController.new)
488
+ yield self
489
+ @webclient.setAjaxController(old_controller)
490
+ end
491
+
492
+ #
493
+ # Start or stop HtmlUnit's DebuggingWebConnection. (Celerity only)
494
+ # The output will go to /tmp/«name»
495
+ #
496
+ # @param [String] name directory name
497
+ # @param [block] blk block to execute
498
+ #
499
+
500
+ def debug_web_connection(name, &blk)
501
+ old_wc = @webclient.getWebConnection
502
+
503
+ @webclient.setWebConnection HtmlUnit::Util::DebuggingWebConnection.new(old_wc, name)
504
+ res = yield
505
+ @webclient.setWebConnection old_wc
506
+
507
+ res
508
+ end
509
+
510
+ def trace_javascript(debugger_klass = Celerity::JavascriptDebugger, &blk)
511
+ context_factory = @webclient.getJavaScriptEngine.getContextFactory
512
+ context_factory.setDebugger debugger_klass.new
513
+ yield
514
+ context_factory.setDebugger nil
515
+ end
516
+
517
+ #
518
+ # Add a listener block for one of the available types. (Celerity only)
519
+ # Types map to HtmlUnit interfaces like this:
520
+ #
521
+ # :status => StatusHandler
522
+ # :alert => AlertHandler ( window.alert() )
523
+ # :web_window_event => WebWindowListener
524
+ # :html_parser => HTMLParserListener
525
+ # :incorrectness => IncorrectnessListener
526
+ # :confirm => ConfirmHandler ( window.confirm() )
527
+ # :prompt => PromptHandler ( window.prompt() )
528
+ #
529
+ # Examples:
530
+ #
531
+ # browser.add_listener(:status) { |page, message| ... }
532
+ # browser.add_listener(:alert) { |page, message| ... }
533
+ # browser.add_listener(:web_window_event) { |web_window_event| ... }
534
+ # browser.add_listener(:html_parser) { |message, url, line, column, key| ... }
535
+ # browser.add_listener(:incorrectness) { |message, origin| ... }
536
+ # browser.add_listener(:confirm) { |page, message| ...; true }
537
+ # browser.add_listener(:prompt) { |page, message| ... }
538
+ #
539
+ #
540
+ # @param [Symbol] type One of the above symbols.
541
+ # @param [Proc] block A block to be executed for events of this type.
542
+ #
543
+
544
+ def add_listener(type, &block)
545
+ listener.add_listener(type, &block)
546
+ end
547
+
548
+ def remove_listener(type, block)
549
+ listener.remove_listener(type, block)
550
+ end
551
+
552
+ #
553
+ # Specify a boolean value to click either 'OK' or 'Cancel' in any confirm
554
+ # dialogs that might show up during the duration of the given block.
555
+ #
556
+ # (Celerity only)
557
+ #
558
+ # @param [Boolean] bool true to click 'OK', false to click 'cancel'
559
+ # @param [Proc] block A block that will trigger the confirm() call(s).
560
+ #
561
+
562
+ def confirm(bool, &block)
563
+ blk = lambda { bool }
564
+
565
+ listener.add_listener(:confirm, &blk)
566
+ yield
567
+ listener.remove_listener(:confirm, blk)
568
+ end
569
+
570
+ #
571
+ # Add a 'checker' proc that will be run on every page load
572
+ #
573
+ # @param [Proc] checker The proc to be run (can also be given as a block)
574
+ # @yieldparam [Celerity::Browser] browser The current browser object.
575
+ # @raise [ArgumentError] if no Proc or block was given.
576
+ #
577
+
578
+ def add_checker(checker = nil, &block)
579
+ if block_given?
580
+ @error_checkers << block
581
+ elsif Proc === checker
582
+ @error_checkers << checker
583
+ else
584
+ raise ArgumentError, "argument must be a Proc or block"
585
+ end
586
+ end
587
+
588
+ #
589
+ # Remove the given checker from the list of checkers
590
+ # @param [Proc] checker The Proc to disable.
591
+ #
592
+
593
+ def disable_checker(checker)
594
+ @error_checkers.delete(checker)
595
+ end
596
+
597
+ #
598
+ # :finest, :finer, :fine, :config, :info, :warning, :severe, or :off, :all
599
+ #
600
+ # @return [Symbol] the current log level
601
+ #
602
+
603
+ def log_level
604
+ Celerity::Util.logger_for('com.gargoylesoftware.htmlunit').level.to_s.downcase.to_sym
605
+ end
606
+
607
+ #
608
+ # Set Java log level (default is :warning, can be any of :all, :finest, :finer, :fine, :config, :info, :warning, :severe, :off)
609
+ #
610
+ # @param [Symbol] level The new log level.
611
+ #
612
+
613
+ def log_level=(level)
614
+ log_level = java.util.logging.Level.const_get(level.to_s.upcase)
615
+
616
+ [ 'com.gargoylesoftware.htmlunit',
617
+ 'com.gargoylesoftware.htmlunit.html',
618
+ 'com.gargoylesoftware.htmlunit.javascript',
619
+ 'org.apache.commons.httpclient'
620
+ ].each { |package| Celerity::Util.logger_for(package).level = log_level }
621
+
622
+ level
623
+ end
624
+
625
+ #
626
+ # If a request is made to an URL that matches the pattern set here, Celerity
627
+ # will ignore the request and return an empty page with content type "text/html" instead.
628
+ #
629
+ # This is useful to block unwanted requests (like ads/banners).
630
+ #
631
+
632
+ def ignore_pattern=(regexp)
633
+ unless regexp.kind_of?(Regexp)
634
+ raise TypeError, "expected Regexp, got #{regexp.inspect}:#{regexp.class}"
635
+ end
636
+
637
+ Celerity::IgnoringWebConnection.new(@webclient, regexp)
638
+ end
639
+
640
+ #
641
+ # Checks if we have a page currently loaded.
642
+ # @return [true, false]
643
+ #
644
+
645
+ def exist?
646
+ !!@page
647
+ end
648
+ alias_method :exists?, :exist?
649
+
650
+ #
651
+ # Turn on/off javascript exceptions
652
+ #
653
+ # @param [Bool]
654
+ #
655
+
656
+ def javascript_exceptions=(bool)
657
+ @webclient.throwExceptionOnScriptError = bool
658
+ end
659
+
660
+ def javascript_exceptions
661
+ @webclient.throwExceptionOnScriptError
662
+ end
663
+
664
+ #
665
+ # Turn on/off status code exceptions
666
+ #
667
+ # @param [Bool]
668
+ #
669
+
670
+ def status_code_exceptions=(bool)
671
+ @webclient.throwExceptionOnFailingStatusCode = bool
672
+ end
673
+
674
+ def status_code_exceptions
675
+ @webclient.throwExceptionOnFailingStatusCode
676
+ end
677
+
678
+ #
679
+ # Turn on/off CSS loading
680
+ #
681
+ # @param [Bool]
682
+ #
683
+
684
+ def css=(bool)
685
+ @webclient.cssEnabled = bool
686
+ end
687
+
688
+ def css
689
+ @webclient.cssEnabled
690
+ end
691
+
692
+ def refresh_handler=(symbol)
693
+ handler = case symbol
694
+ when :waiting
695
+ HtmlUnit::WaitingRefreshHandler.new
696
+ when :threaded
697
+ HtmlUnit::ThreadedRefreshHandler.new
698
+ when :immediate
699
+ HtmlUnit::ImmediateRefreshHandler.new
700
+ else
701
+ raise ArgumentError, "expected :waiting, :threaded or :immediate"
702
+ end
703
+
704
+ @webclient.setRefreshHandler handler
705
+ end
706
+
707
+ #
708
+ # Turn on/off secure SSL
709
+ #
710
+ # @param [Bool]
711
+ #
712
+
713
+ def secure_ssl=(bool)
714
+ @webclient.useInsecureSSL = !bool
715
+ end
716
+
717
+ #
718
+ # Turn on/off JavaScript execution
719
+ #
720
+ # @param [Bool]
721
+ #
722
+
723
+ def javascript_enabled=(bool)
724
+ @webclient.setJavaScriptEnabled(bool)
725
+ end
726
+
727
+ def javascript_enabled
728
+ @webclient.isJavaScriptEnabled
729
+ end
730
+
731
+ #
732
+ # Open the JavaScript debugger GUI
733
+ #
734
+
735
+ def visual_debugger
736
+ HtmlUnit::Util::WebClientUtils.attachVisualDebugger @webclient
737
+ end
738
+
739
+ #
740
+ # Sets the current page object for the browser
741
+ #
742
+ # @param [HtmlUnit::HtmlPage] value The page to set.
743
+ # @api private
744
+ #
745
+
746
+ def page=(value)
747
+ return if @page == value
748
+ @page = value
749
+
750
+ if @page.respond_to?("getDocumentElement")
751
+ @object = @page.getDocumentElement || @object
752
+ elsif @page.is_a? HtmlUnit::UnexpectedPage
753
+ raise UnexpectedPageException, @page.getWebResponse.getContentType
754
+ end
755
+
756
+ render unless @viewer == DefaultViewer
757
+ run_error_checks
758
+
759
+ value
760
+ end
761
+
762
+ #
763
+ # Check that we have a @page object.
764
+ #
765
+ # @raise [UnknownObjectException] if no page is loaded.
766
+ # @api private
767
+ #
768
+
769
+ def assert_exists
770
+ raise UnknownObjectException, "no page loaded" unless exist?
771
+ end
772
+
773
+ #
774
+ # Returns the element that currently has the focus (Celerity only)
775
+ #
776
+
777
+ def focused_element
778
+ element_from_dom_node(page.getFocusedElement())
779
+ end
780
+
781
+ #
782
+ # Enable Celerity's internal WebWindowEventListener
783
+ #
784
+ # @api private
785
+ #
786
+
787
+ def enable_event_listener
788
+ @event_listener ||= lambda do |event|
789
+ self.page = @page ? @page.getEnclosingWindow.getEnclosedPage : event.getNewPage
790
+ end
791
+
792
+ listener.add_listener(:web_window_event, &@event_listener)
793
+ end
794
+
795
+ #
796
+ # Disable Celerity's internal WebWindowEventListener
797
+ #
798
+ # @api private
799
+ #
800
+
801
+ def disable_event_listener
802
+ listener.remove_listener(:web_window_event, @event_listener)
803
+
804
+ if block_given?
805
+ result = yield
806
+ enable_event_listener
807
+
808
+ result
809
+ end
810
+ end
811
+
812
+ private
813
+
814
+ #
815
+ # Runs the all the checker procs added by +add_checker+
816
+ #
817
+ # @see add_checker
818
+ # @api private
819
+ #
820
+
821
+ def run_error_checks
822
+ @error_checkers.each { |e| e[self] }
823
+ end
824
+
825
+ #
826
+ # Configure the webclient according to the options given to #new.
827
+ # @see initialize
828
+ #
829
+
830
+ def setup_webclient(opts)
831
+ browser = (opts.delete(:browser) || :firefox3).to_sym
832
+
833
+ browser_version = case browser
834
+ when :firefox, :ff, :firefox3, :ff3 # default :firefox
835
+ ::HtmlUnit::BrowserVersion::FIREFOX_3
836
+ when :firefox_3_6, :ff36
837
+ ::HtmlUnit::BrowserVersion::FIREFOX_3_6
838
+ when :internet_explorer_6, :ie6
839
+ ::HtmlUnit::BrowserVersion::INTERNET_EXPLORER_6
840
+ when :internet_explorer, :ie, :internet_explorer7, :internet_explorer_7, :ie7 # default :ie
841
+ ::HtmlUnit::BrowserVersion::INTERNET_EXPLORER_7
842
+ when :internet_explorer_8, :ie8
843
+ ::HtmlUnit::BrowserVersion::INTERNET_EXPLORER_8
844
+ else
845
+ raise ArgumentError, "unknown browser: #{browser.inspect}"
846
+ end
847
+
848
+ if ua = opts.delete(:user_agent)
849
+ browser_version.setUserAgent(ua)
850
+ end
851
+
852
+ @webclient = if proxy = opts.delete(:proxy)
853
+ phost, pport = proxy.split(":")
854
+ ::HtmlUnit::WebClient.new(browser_version, phost, pport.to_i)
855
+ else
856
+ ::HtmlUnit::WebClient.new(browser_version)
857
+ end
858
+
859
+ self.javascript_exceptions = false unless opts.delete(:javascript_exceptions)
860
+ self.status_code_exceptions = false unless opts.delete(:status_code_exceptions)
861
+ self.css = !!opts.delete(:css) if opts.has_key?(:css)
862
+ self.javascript_enabled = opts.delete(:javascript_enabled) != false
863
+ self.secure_ssl = opts.delete(:secure_ssl) != false
864
+ self.ignore_pattern = opts.delete(:ignore_pattern) if opts[:ignore_pattern]
865
+ self.refresh_handler = opts.delete(:refresh_handler) if opts[:refresh_handler]
866
+ self.cache_limit = opts.delete(:cache_limit) if opts[:cache_limit]
867
+
868
+ @default_wait = Integer(opts.delete(:default_wait) || 10)
869
+
870
+ if opts.delete(:resynchronize)
871
+ controller = ::HtmlUnit::NicelyResynchronizingAjaxController.new
872
+ @webclient.setAjaxController controller
873
+ end
874
+
875
+ enable_event_listener
876
+ end
877
+
878
+ def setup_viewer(option)
879
+ @viewer = DefaultViewer
880
+ return if option == false
881
+
882
+ host_string = option.kind_of?(String) ? option : "127.0.0.1:6429"
883
+ host, port = host_string.split(":")
884
+
885
+ if viewer = ViewerConnection.create(host, port.to_i)
886
+ @viewer = viewer
887
+ end
888
+ rescue Errno::ECONNREFUSED, SocketError => e
889
+ nil
890
+ end
891
+
892
+ #
893
+ # This *should* be unneccessary, but sometimes the page we get from the
894
+ # window is different (ie. a different object) from our current @page
895
+ # (Used by #wait_while and #wait_until)
896
+ #
897
+
898
+ def refresh_page_from_window
899
+ new_page = @page.getEnclosingWindow.getEnclosedPage
900
+
901
+ if new_page && (new_page != @page)
902
+ self.page = new_page
903
+ else
904
+ Log.debug "unneccessary refresh"
905
+ end
906
+ end
907
+
908
+ #
909
+ # Render the current page on the connected viewer.
910
+ # @api private
911
+ #
912
+
913
+ def render
914
+ @viewer.render_html(self.send(@render_type), url)
915
+ rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EPIPE
916
+ @viewer = DefaultViewer
917
+ end
918
+
919
+ def listener
920
+ @listener ||= Celerity::Listener.new(@webclient)
921
+ end
922
+
923
+ end # Browser
924
+ end # Celerity