celerity_thingista 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
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