akephalos2-stable 2.1.1.1

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.
@@ -0,0 +1,16 @@
1
+ # **Akephalos** is a cross-platform Ruby interface for *HtmlUnit*, a headless
2
+ # browser for the Java platform.
3
+ #
4
+ # The only requirement is that a Java runtime is available.
5
+ #
6
+ require 'java' if RUBY_PLATFORM == 'java'
7
+ require 'pathname'
8
+
9
+ module Akephalos
10
+ BIN_DIR = Pathname(__FILE__).expand_path.dirname.parent + 'bin'
11
+ ENV['htmlunit_version'] ||= "2.9"
12
+ end
13
+
14
+ require 'akephalos/client'
15
+ require 'capybara'
16
+ require 'akephalos/capybara'
@@ -0,0 +1,348 @@
1
+ # Driver class exposed to Capybara. It implements Capybara's full driver API,
2
+ # and is the entry point for interaction between the test suites and HtmlUnit.
3
+ #
4
+ # This class and +Capybara::Driver::Akephalos::Node+ are written to run on both
5
+ # MRI and JRuby, and is agnostic whether the Akephalos::Client instance is used
6
+ # directly or over DRb.
7
+ class Capybara::Driver::Akephalos < Capybara::Driver::Base
8
+
9
+ # Akephalos-specific implementation for Capybara's Driver::Node class.
10
+ class Node < Capybara::Driver::Node
11
+
12
+ # @api capybara
13
+ # @return [String] the inner text of the node
14
+ def text
15
+ native.text
16
+ end
17
+
18
+ # @api capybara
19
+ # @param [String] name attribute name
20
+ # @return [String] the attribute value
21
+ def [](name)
22
+ name = name.to_s
23
+ case name
24
+ when 'checked'
25
+ native.checked?
26
+ else
27
+ native[name.to_s]
28
+ end
29
+ end
30
+
31
+ # @api capybara
32
+ # @return [String, Array<String>] the form element's value
33
+ def value
34
+ native.value
35
+ end
36
+
37
+ # Set the form element's value.
38
+ #
39
+ # @api capybara
40
+ # @param [String] value the form element's new value
41
+ def set(value)
42
+ if tag_name == 'textarea'
43
+ native.value = value.to_s
44
+ elsif tag_name == 'input' and type == 'radio'
45
+ click
46
+ elsif tag_name == 'input' and type == 'checkbox'
47
+ if value != self['checked']
48
+ click
49
+ end
50
+ elsif tag_name == 'input'
51
+ native.value = value.to_s
52
+ end
53
+ end
54
+
55
+ # @api capybara
56
+ def select_option
57
+ #if it is already selected: do nothing
58
+ #if it isn't selected: click on it
59
+ native.click unless selected?
60
+ end
61
+
62
+ # Unselect an option from a select box.
63
+ #
64
+ # @api capybara
65
+ def unselect_option
66
+ unless select_node.multiple_select?
67
+ raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box."
68
+ end
69
+
70
+ native.unselect
71
+ end
72
+
73
+ # Click the element.
74
+ def click
75
+ native.click
76
+ end
77
+
78
+ # Drag the element on top of the target element.
79
+ #
80
+ # @api capybara
81
+ # @param [Node] element the target element
82
+ def drag_to(element)
83
+ trigger('mousedown')
84
+ element.trigger('mousemove')
85
+ element.trigger('mouseup')
86
+ end
87
+
88
+ # @api capybara
89
+ # @return [String] the element's tag name
90
+ def tag_name
91
+ native.tag_name
92
+ end
93
+
94
+ # @api capybara
95
+ # @return [true, false] the element's visiblity
96
+ def visible?
97
+ native.visible?
98
+ end
99
+
100
+ # @api capybara
101
+ # @return [true, false] the element's visiblity
102
+ def checked?
103
+ native.checked?
104
+ end
105
+
106
+ # @api capybara
107
+ # @return [true, false] the element's visiblity
108
+ def selected?
109
+ native.selected?
110
+ end
111
+
112
+ # @api capybara
113
+ # @return [String] the XPath to locate the node
114
+ def path
115
+ native.xpath
116
+ end
117
+
118
+ # Trigger an event on the element.
119
+ #
120
+ # @api capybara
121
+ # @param [String] event the event to trigger
122
+ def trigger(event)
123
+ native.fire_event(event.to_s)
124
+ end
125
+
126
+ # @api capybara
127
+ # @param [String] selector XPath query
128
+ # @return [Array<Node>] the matched nodes
129
+ def find(selector)
130
+ nodes = []
131
+ native.find(selector).each { |node| nodes << self.class.new(self, node) }
132
+ nodes
133
+ end
134
+
135
+ protected
136
+
137
+ # @return [true, false] whether the node allows multiple-option selection (if the node is a select).
138
+ def multiple_select?
139
+ tag_name == "select" && native.multiple_select?
140
+ end
141
+
142
+ private
143
+
144
+ # Return all child nodes which match the selector criteria.
145
+ #
146
+ # @api capybara
147
+ # @return [Array<Node>] the matched nodes
148
+ def all_unfiltered(selector)
149
+ nodes = []
150
+ native.find(selector).each { |node| nodes << self.class.new(driver, node) }
151
+ nodes
152
+ end
153
+
154
+ # @return [String] the node's type attribute
155
+ def type
156
+ native[:type]
157
+ end
158
+
159
+ # @return [Node] the select node, if this is an option node
160
+ def select_node
161
+ find('./ancestor::select').first
162
+ end
163
+ end
164
+
165
+ attr_reader :app, :rack_server, :options
166
+
167
+ # Creates a new instance of the Akephalos Driver for Capybara. The driver is
168
+ # registered with Capybara by a name, so that it can be chosen when
169
+ # Capybara's javascript_driver is changed. By default, Akephalos is
170
+ # registered like this:
171
+ #
172
+ # Capybara.register_driver :akephalos do |app|
173
+ # Capybara::Akephalos::Driver.new(
174
+ # app,
175
+ # :browser => :firefox_3_6,
176
+ # :validate_scripts => true
177
+ # )
178
+ # end
179
+ #
180
+ # @param app the Rack application to run
181
+ # @param [Hash] options the Akephalos configuration options
182
+ # @option options [Symbol] :browser (:firefox_3_6) the browser
183
+ # compatibility mode to run in. Available options:
184
+ # :firefox_3_6
185
+ # :firefox_3
186
+ # :ie6
187
+ # :ie7
188
+ # :ie8
189
+ #
190
+ # @option options [true, false] :validate_scripts (true) whether to raise
191
+ # exceptions on script errors
192
+ #
193
+ def initialize(app, options = {})
194
+ @app = app
195
+ @options = options
196
+ @rack_server = Capybara::Server.new(@app)
197
+ @rack_server.boot if Capybara.run_server
198
+ end
199
+
200
+ # Visit the given path in the browser.
201
+ #
202
+ # @param [String] path relative path to visit
203
+ def visit(path)
204
+ browser.visit(url(path))
205
+ end
206
+
207
+ # @return [String] the page's original source
208
+ def source
209
+ page.source
210
+ end
211
+
212
+ # @return [String] the page's modified source
213
+ # page.modified_source will return a string with
214
+ # html entities converted into the unicode equivalent
215
+ # but the string will be marked as ASCII-8BIT
216
+ # which causes conversion issues so we force the encoding
217
+ # to UTF-8 (ruby 1.9 only)
218
+ def body
219
+ body_source = page.modified_source
220
+
221
+ if body_source.respond_to?(:force_encoding)
222
+ body_source.force_encoding("UTF-8")
223
+ else
224
+ body_source
225
+ end
226
+ end
227
+
228
+ # @return [Hash{String => String}] the page's response headers
229
+ def response_headers
230
+ page.response_headers
231
+ end
232
+
233
+ # @return [Integer] the response's status code
234
+ def status_code
235
+ page.status_code
236
+ end
237
+
238
+ # Execute the given block within the context of a specified frame.
239
+ #
240
+ # @param [String] frame_id the frame's id or index
241
+ # @raise [Capybara::ElementNotFound] if the frame is not found
242
+ def within_frame(frame_id_or_index, &block)
243
+ result = page.within_frame(frame_id_or_index, &block)
244
+ unless page.within_frame(frame_id_or_index, &block)
245
+ raise Capybara::ElementNotFound, "Unable to find frame with id '#{frame_id_or_index}'"
246
+ end
247
+ end
248
+
249
+ # Clear all cookie session data.
250
+ # @deprecated This method is deprecated in Capybara's master branch. Use
251
+ # {#reset!} instead.
252
+ def cleanup!
253
+ reset!
254
+ end
255
+
256
+ # Reset session
257
+ def reset!
258
+ cookies.clear
259
+ browser.close_all_windows()
260
+ browser.visit('about:blank')
261
+ end
262
+
263
+ # Confirm or cancel the dialog, returning the text of the dialog
264
+ def confirm_dialog(confirm = true, &block)
265
+ browser.confirm_dialog(confirm, &block)
266
+ end
267
+
268
+ # @return [String] the page's current URL
269
+ def current_url
270
+ page.current_url
271
+ end
272
+
273
+ # Search for nodes which match the given XPath selector.
274
+ #
275
+ # @param [String] selector XPath query
276
+ # @return [Array<Node>] the matched nodes
277
+ def find(selector)
278
+ nodes = []
279
+ page.find(selector).each { |node| nodes << Node.new(self, node) }
280
+ nodes
281
+ end
282
+
283
+ # Execute JavaScript against the current page, discarding any return value.
284
+ #
285
+ # @param [String] script the JavaScript to be executed
286
+ # @return [nil]
287
+ def execute_script(script)
288
+ page.execute_script script
289
+ end
290
+
291
+ # Execute JavaScript against the current page and return the results.
292
+ #
293
+ # @param [String] script the JavaScript to be executed
294
+ # @return the result of the JavaScript
295
+ def evaluate_script(script)
296
+ page.evaluate_script script
297
+ end
298
+
299
+ # @return the current page
300
+ def page
301
+ browser.page
302
+ end
303
+
304
+ # @return the browser
305
+ def browser
306
+ @browser ||= Akephalos::Client.new(@options)
307
+ end
308
+
309
+ # @return the session cookies
310
+ def cookies
311
+ browser.cookies
312
+ end
313
+
314
+ # @return [String] the current user agent string
315
+ def user_agent
316
+ browser.user_agent
317
+ end
318
+
319
+ # Set the User-Agent header for this session. If :default is given, the
320
+ # User-Agent header will be reset to the default browser's user agent.
321
+ #
322
+ # @param [:default] user_agent the default user agent
323
+ # @param [String] user_agent the user agent string to use
324
+ def user_agent=(user_agent)
325
+ browser.user_agent = user_agent
326
+ end
327
+
328
+ # Disable waiting in Capybara, since waiting is handled directly by
329
+ # Akephalos.
330
+ #
331
+ # @return [false]
332
+ def wait
333
+ false
334
+ end
335
+
336
+ private
337
+
338
+ # @param [String] path
339
+ # @return [String] the absolute URL for the given path
340
+ def url(path)
341
+ rack_server.url(path)
342
+ end
343
+
344
+ end
345
+
346
+ Capybara.register_driver :akephalos do |app|
347
+ Capybara::Driver::Akephalos.new(app)
348
+ end
@@ -0,0 +1,192 @@
1
+ require 'akephalos/configuration'
2
+
3
+ if RUBY_PLATFORM != "java"
4
+ require 'akephalos/remote_client'
5
+ Akephalos::Client = Akephalos::RemoteClient
6
+ else
7
+ require 'akephalos/htmlunit'
8
+ require 'akephalos/htmlunit/ext/http_method'
9
+ require 'akephalos/htmlunit/ext/confirm_handler'
10
+
11
+ require 'akephalos/page'
12
+ require 'akephalos/node'
13
+
14
+ require 'akephalos/client/cookies'
15
+ require 'akephalos/client/filter'
16
+
17
+ module Akephalos
18
+
19
+ # Akephalos::Client wraps HtmlUnit's WebClient class. It is the main entry
20
+ # point for all interaction with the browser, exposing its current page and
21
+ # allowing navigation.
22
+ class Client
23
+
24
+ # @return [Akephalos::Page] the current page
25
+ attr_reader :page
26
+
27
+ # @return [HtmlUnit::BrowserVersion] the configured browser version
28
+ attr_reader :browser_version
29
+
30
+ # @return [true/false] whether to raise errors on javascript failures
31
+ attr_reader :validate_scripts
32
+
33
+ # @return [true/false] whether to ignore insecure ssl certificates
34
+ attr_reader :use_insecure_ssl
35
+
36
+ # @return ["trace" / "debug" / "info" / "warn" / "error" or "fatal"] which points the htmlunit log level
37
+ attr_reader :htmlunit_log_level
38
+
39
+ # The default configuration options for a new Client.
40
+ DEFAULT_OPTIONS = {
41
+ :browser => :firefox_3_6,
42
+ :validate_scripts => true,
43
+ :use_insecure_ssl => false,
44
+ :htmlunit_log_level => 'fatal'
45
+ }
46
+
47
+ # Map of browser version symbols to their HtmlUnit::BrowserVersion
48
+ # instances.
49
+ BROWSER_VERSIONS = {
50
+ :ie6 => HtmlUnit::BrowserVersion::INTERNET_EXPLORER_6,
51
+ :ie7 => HtmlUnit::BrowserVersion::INTERNET_EXPLORER_7,
52
+ :ie8 => HtmlUnit::BrowserVersion::INTERNET_EXPLORER_8,
53
+ :firefox_3_6 => HtmlUnit::BrowserVersion::FIREFOX_3_6
54
+ }
55
+
56
+ # @param [Hash] options the configuration options for this client
57
+ #
58
+ # @option options [Symbol] :browser (:firefox_3_6) the browser version (
59
+ # see BROWSER_VERSIONS)
60
+ #
61
+ # @option options [true, false] :validate_scripts (true) whether to raise
62
+ # errors on javascript errors
63
+ def initialize(options = {})
64
+ process_options!(options)
65
+
66
+ @_client = java.util.concurrent.FutureTask.new do
67
+
68
+ if @http_proxy.nil? or @http_proxy_port.nil?
69
+ client = HtmlUnit::WebClient.new(browser_version)
70
+ else
71
+ client = HtmlUnit::WebClient.new(browser_version, @http_proxy, @http_proxy_port)
72
+ end
73
+
74
+ client.setThrowExceptionOnFailingStatusCode(false)
75
+ client.setAjaxController(HtmlUnit::NicelyResynchronizingAjaxController.new)
76
+ client.setCssErrorHandler(HtmlUnit::SilentCssErrorHandler.new)
77
+ client.setThrowExceptionOnScriptError(validate_scripts)
78
+ client.setUseInsecureSSL(use_insecure_ssl)
79
+ client.setRefreshHandler(HtmlUnit::WaitingRefreshHandler.new)
80
+
81
+ Filter.new(client)
82
+ client
83
+ end
84
+ Thread.new { @_client.run }
85
+ end
86
+
87
+ # Visit the requested URL and return the page.
88
+ #
89
+ # @param [String] url the URL to load
90
+ # @return [Page] the loaded page
91
+ def visit(url)
92
+ client.getPage(url)
93
+ page
94
+ end
95
+
96
+ # @return [Cookies] the cookies for this session
97
+ def cookies
98
+ @cookies ||= Cookies.new(client.getCookieManager)
99
+ end
100
+
101
+ # @return [String] the current user agent string
102
+ def user_agent
103
+ @user_agent || client.getBrowserVersion.getUserAgent
104
+ end
105
+
106
+ # Set the User-Agent header for this session. If :default is given, the
107
+ # User-Agent header will be reset to the default browser's user agent.
108
+ #
109
+ # @param [:default] user_agent the default user agent
110
+ # @param [String] user_agent the user agent string to use
111
+ def user_agent=(user_agent)
112
+ if user_agent == :default
113
+ @user_agent = nil
114
+ client.removeRequestHeader("User-Agent")
115
+ else
116
+ @user_agent = user_agent
117
+ client.addRequestHeader("User-Agent", user_agent)
118
+ end
119
+ end
120
+
121
+ # @return [Page] the current page
122
+ def page
123
+ self.page = client.getCurrentWindow.getTopWindow.getEnclosedPage
124
+ @page
125
+ end
126
+
127
+ # Update the current page.
128
+ #
129
+ # @param [HtmlUnit::HtmlPage] _page the new page
130
+ # @return [Page] the new page
131
+ def page=(_page)
132
+ if @page != _page
133
+ @page = Page.new(_page)
134
+ end
135
+ @page
136
+ end
137
+
138
+ # @return [true, false] whether javascript errors will raise exceptions
139
+ def validate_scripts?
140
+ !!validate_scripts
141
+ end
142
+
143
+ # @return [true, false] whether to ignore insecure ssl certificates
144
+ def use_insecure_ssl?
145
+ !!use_insecure_ssl
146
+ end
147
+
148
+ # Merges the DEFAULT_OPTIONS with those provided to initialize the Client
149
+ # state, namely, its browser version, whether it should
150
+ # validate scripts, and htmlunit log level.
151
+ #
152
+ # @param [Hash] options the options to process
153
+ def process_options!(options)
154
+ options = DEFAULT_OPTIONS.merge(options)
155
+
156
+ @browser_version = BROWSER_VERSIONS.fetch(options.delete(:browser))
157
+ @validate_scripts = options.delete(:validate_scripts)
158
+ @use_insecure_ssl = options.delete(:use_insecure_ssl)
159
+ @htmlunit_log_level = options.delete(:htmlunit_log_level)
160
+ @http_proxy = options.delete(:http_proxy)
161
+ @http_proxy_port = options.delete(:http_proxy_port)
162
+
163
+ java.lang.System.setProperty("org.apache.commons.logging.simplelog.defaultlog", @htmlunit_log_level)
164
+ end
165
+
166
+ # Confirm or cancel the dialog, returning the text of the dialog
167
+ def confirm_dialog(confirm = true, &block)
168
+ handler = HtmlUnit::ConfirmHandler.new
169
+ handler.handleConfirmValue = confirm
170
+ client.setConfirmHandler(handler)
171
+ yield if block_given?
172
+ return handler.text
173
+ end
174
+
175
+ def close_all_windows()
176
+ @client.closeAllWindows()
177
+ end
178
+
179
+ private
180
+
181
+ # Call the future set up in #initialize and return the WebCLient
182
+ # instance.
183
+ #
184
+ # @return [HtmlUnit::WebClient] the WebClient instance
185
+ def client
186
+ @client ||= @_client.get.tap do |client|
187
+ client.getCurrentWindow.getHistory.ignoreNewPages_.set(true)
188
+ end
189
+ end
190
+ end
191
+ end
192
+ end