akephalos2 2.0.0

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 (38) hide show
  1. data/MIT_LICENSE +20 -0
  2. data/README.md +287 -0
  3. data/bin/akephalos +102 -0
  4. data/lib/akephalos/capybara.rb +343 -0
  5. data/lib/akephalos/client/cookies.rb +73 -0
  6. data/lib/akephalos/client/filter.rb +120 -0
  7. data/lib/akephalos/client.rb +192 -0
  8. data/lib/akephalos/configuration.rb +49 -0
  9. data/lib/akephalos/console.rb +32 -0
  10. data/lib/akephalos/cucumber.rb +6 -0
  11. data/lib/akephalos/htmlunit/ext/confirm_handler.rb +18 -0
  12. data/lib/akephalos/htmlunit/ext/http_method.rb +30 -0
  13. data/lib/akephalos/htmlunit.rb +36 -0
  14. data/lib/akephalos/node.rb +192 -0
  15. data/lib/akephalos/page.rb +113 -0
  16. data/lib/akephalos/remote_client.rb +93 -0
  17. data/lib/akephalos/server.rb +79 -0
  18. data/lib/akephalos/version.rb +3 -0
  19. data/lib/akephalos.rb +19 -0
  20. data/vendor/html-unit/apache-mime4j-0.6.jar +0 -0
  21. data/vendor/html-unit/commons-codec-1.4.jar +0 -0
  22. data/vendor/html-unit/commons-collections-3.2.1.jar +0 -0
  23. data/vendor/html-unit/commons-io-2.0.1.jar +0 -0
  24. data/vendor/html-unit/commons-lang-2.6.jar +0 -0
  25. data/vendor/html-unit/commons-logging-1.1.1.jar +0 -0
  26. data/vendor/html-unit/cssparser-0.9.5.jar +0 -0
  27. data/vendor/html-unit/htmlunit-2.9.jar +0 -0
  28. data/vendor/html-unit/htmlunit-core-js-2.9.jar +0 -0
  29. data/vendor/html-unit/httpclient-4.1.2.jar +0 -0
  30. data/vendor/html-unit/httpcore-4.1.2.jar +0 -0
  31. data/vendor/html-unit/httpmime-4.1.2.jar +0 -0
  32. data/vendor/html-unit/nekohtml-1.9.15.jar +0 -0
  33. data/vendor/html-unit/sac-1.3.jar +0 -0
  34. data/vendor/html-unit/serializer-2.7.1.jar +0 -0
  35. data/vendor/html-unit/xalan-2.7.1.jar +0 -0
  36. data/vendor/html-unit/xercesImpl-2.9.1.jar +0 -0
  37. data/vendor/html-unit/xml-apis-1.3.04.jar +0 -0
  38. metadata +127 -0
@@ -0,0 +1,343 @@
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
+ native.click
58
+ end
59
+
60
+ # Unselect an option from a select box.
61
+ #
62
+ # @api capybara
63
+ def unselect_option
64
+ unless select_node.multiple_select?
65
+ raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box."
66
+ end
67
+
68
+ native.unselect
69
+ end
70
+
71
+ # Click the element.
72
+ def click
73
+ native.click
74
+ end
75
+
76
+ # Drag the element on top of the target element.
77
+ #
78
+ # @api capybara
79
+ # @param [Node] element the target element
80
+ def drag_to(element)
81
+ trigger('mousedown')
82
+ element.trigger('mousemove')
83
+ element.trigger('mouseup')
84
+ end
85
+
86
+ # @api capybara
87
+ # @return [String] the element's tag name
88
+ def tag_name
89
+ native.tag_name
90
+ end
91
+
92
+ # @api capybara
93
+ # @return [true, false] the element's visiblity
94
+ def visible?
95
+ native.visible?
96
+ end
97
+
98
+ # @api capybara
99
+ # @return [true, false] the element's visiblity
100
+ def checked?
101
+ native.checked?
102
+ end
103
+
104
+ # @api capybara
105
+ # @return [true, false] the element's visiblity
106
+ def selected?
107
+ native.selected?
108
+ end
109
+
110
+ # @api capybara
111
+ # @return [String] the XPath to locate the node
112
+ def path
113
+ native.xpath
114
+ end
115
+
116
+ # Trigger an event on the element.
117
+ #
118
+ # @api capybara
119
+ # @param [String] event the event to trigger
120
+ def trigger(event)
121
+ native.fire_event(event.to_s)
122
+ end
123
+
124
+ # @api capybara
125
+ # @param [String] selector XPath query
126
+ # @return [Array<Node>] the matched nodes
127
+ def find(selector)
128
+ nodes = []
129
+ native.find(selector).each { |node| nodes << self.class.new(self, node) }
130
+ nodes
131
+ end
132
+
133
+ protected
134
+
135
+ # @return [true, false] whether the node allows multiple-option selection (if the node is a select).
136
+ def multiple_select?
137
+ tag_name == "select" && native.multiple_select?
138
+ end
139
+
140
+ private
141
+
142
+ # Return all child nodes which match the selector criteria.
143
+ #
144
+ # @api capybara
145
+ # @return [Array<Node>] the matched nodes
146
+ def all_unfiltered(selector)
147
+ nodes = []
148
+ native.find(selector).each { |node| nodes << self.class.new(driver, node) }
149
+ nodes
150
+ end
151
+
152
+ # @return [String] the node's type attribute
153
+ def type
154
+ native[:type]
155
+ end
156
+
157
+ # @return [Node] the select node, if this is an option node
158
+ def select_node
159
+ find('./ancestor::select').first
160
+ end
161
+ end
162
+
163
+ attr_reader :app, :rack_server, :options
164
+
165
+ # Creates a new instance of the Akephalos Driver for Capybara. The driver is
166
+ # registered with Capybara by a name, so that it can be chosen when
167
+ # Capybara's javascript_driver is changed. By default, Akephalos is
168
+ # registered like this:
169
+ #
170
+ # Capybara.register_driver :akephalos do |app|
171
+ # Capybara::Akephalos::Driver.new(
172
+ # app,
173
+ # :browser => :firefox_3_6,
174
+ # :validate_scripts => true
175
+ # )
176
+ # end
177
+ #
178
+ # @param app the Rack application to run
179
+ # @param [Hash] options the Akephalos configuration options
180
+ # @option options [Symbol] :browser (:firefox_3_6) the browser
181
+ # compatibility mode to run in. Available options:
182
+ # :firefox_3_6
183
+ # :firefox_3
184
+ # :ie6
185
+ # :ie7
186
+ # :ie8
187
+ #
188
+ # @option options [true, false] :validate_scripts (true) whether to raise
189
+ # exceptions on script errors
190
+ #
191
+ def initialize(app, options = {})
192
+ @app = app
193
+ @options = options
194
+ @rack_server = Capybara::Server.new(@app)
195
+ @rack_server.boot if Capybara.run_server
196
+ end
197
+
198
+ # Visit the given path in the browser.
199
+ #
200
+ # @param [String] path relative path to visit
201
+ def visit(path)
202
+ browser.visit(url(path))
203
+ end
204
+
205
+ # @return [String] the page's original source
206
+ def source
207
+ page.source
208
+ end
209
+
210
+ # @return [String] the page's modified source
211
+ # page.modified_source will return a string with
212
+ # html entities converted into the unicode equivalent
213
+ # but the string will be marked as ASCII-8BIT
214
+ # which causes conversion issues so we force the encoding
215
+ # to UTF-8 (ruby 1.9 only)
216
+ def body
217
+ body_source = page.modified_source
218
+
219
+ if body_source.respond_to?(:force_encoding)
220
+ body_source.force_encoding("UTF-8")
221
+ else
222
+ body_source
223
+ end
224
+ end
225
+
226
+ # @return [Hash{String => String}] the page's response headers
227
+ def response_headers
228
+ page.response_headers
229
+ end
230
+
231
+ # @return [Integer] the response's status code
232
+ def status_code
233
+ page.status_code
234
+ end
235
+
236
+ # Execute the given block within the context of a specified frame.
237
+ #
238
+ # @param [String] frame_id the frame's id
239
+ # @raise [Capybara::ElementNotFound] if the frame is not found
240
+ def within_frame(frame_id, &block)
241
+ unless page.within_frame(frame_id, &block)
242
+ raise Capybara::ElementNotFound, "Unable to find frame with id '#{frame_id}'"
243
+ end
244
+ end
245
+
246
+ # Clear all cookie session data.
247
+ # @deprecated This method is deprecated in Capybara's master branch. Use
248
+ # {#reset!} instead.
249
+ def cleanup!
250
+ reset!
251
+ end
252
+
253
+ # Clear all cookie session data.
254
+ def reset!
255
+ cookies.clear
256
+ end
257
+
258
+ # Confirm or cancel the dialog, returning the text of the dialog
259
+ def confirm_dialog(confirm = true, &block)
260
+ browser.confirm_dialog(confirm, &block)
261
+ end
262
+
263
+ # @return [String] the page's current URL
264
+ def current_url
265
+ page.current_url
266
+ end
267
+
268
+ # Search for nodes which match the given XPath selector.
269
+ #
270
+ # @param [String] selector XPath query
271
+ # @return [Array<Node>] the matched nodes
272
+ def find(selector)
273
+ nodes = []
274
+ page.find(selector).each { |node| nodes << Node.new(self, node) }
275
+ nodes
276
+ end
277
+
278
+ # Execute JavaScript against the current page, discarding any return value.
279
+ #
280
+ # @param [String] script the JavaScript to be executed
281
+ # @return [nil]
282
+ def execute_script(script)
283
+ page.execute_script script
284
+ end
285
+
286
+ # Execute JavaScript against the current page and return the results.
287
+ #
288
+ # @param [String] script the JavaScript to be executed
289
+ # @return the result of the JavaScript
290
+ def evaluate_script(script)
291
+ page.evaluate_script script
292
+ end
293
+
294
+ # @return the current page
295
+ def page
296
+ browser.page
297
+ end
298
+
299
+ # @return the browser
300
+ def browser
301
+ @browser ||= Akephalos::Client.new(@options)
302
+ end
303
+
304
+ # @return the session cookies
305
+ def cookies
306
+ browser.cookies
307
+ end
308
+
309
+ # @return [String] the current user agent string
310
+ def user_agent
311
+ browser.user_agent
312
+ end
313
+
314
+ # Set the User-Agent header for this session. If :default is given, the
315
+ # User-Agent header will be reset to the default browser's user agent.
316
+ #
317
+ # @param [:default] user_agent the default user agent
318
+ # @param [String] user_agent the user agent string to use
319
+ def user_agent=(user_agent)
320
+ browser.user_agent = user_agent
321
+ end
322
+
323
+ # Disable waiting in Capybara, since waiting is handled directly by
324
+ # Akephalos.
325
+ #
326
+ # @return [false]
327
+ def wait
328
+ false
329
+ end
330
+
331
+ private
332
+
333
+ # @param [String] path
334
+ # @return [String] the absolute URL for the given path
335
+ def url(path)
336
+ rack_server.url(path)
337
+ end
338
+
339
+ end
340
+
341
+ Capybara.register_driver :akephalos do |app|
342
+ Capybara::Driver::Akephalos.new(app)
343
+ end
@@ -0,0 +1,73 @@
1
+ module Akephalos
2
+ class Client
3
+ # Interface for working with HtmlUnit's CookieManager, providing a basic
4
+ # API for manipulating the cookies in a session.
5
+ class Cookies
6
+ include Enumerable
7
+
8
+ # @param [HtmlUnit::CookieManager] cookie manager
9
+ def initialize(cookie_manager)
10
+ @cookie_manager = cookie_manager
11
+ end
12
+
13
+ # @param [name] the cookie name
14
+ # @return [Cookie] the cookie with the given name
15
+ # @return [nil] when no cookie is found
16
+ def [](name)
17
+ cookie = @cookie_manager.getCookie(name)
18
+ Cookie.new(cookie) if cookie
19
+ end
20
+
21
+ # Clears all cookies for this session.
22
+ def clear
23
+ @cookie_manager.clearCookies
24
+ end
25
+
26
+ # Iterator for all cookies in the current session.
27
+ def each
28
+ @cookie_manager.getCookies.each do |cookie|
29
+ yield Cookie.new(cookie)
30
+ end
31
+ end
32
+
33
+ # Remove the cookie from the session.
34
+ #
35
+ # @param [Cookie] the cookie to remove
36
+ def delete(cookie)
37
+ @cookie_manager.removeCookie(cookie.native)
38
+ end
39
+
40
+ # @return [true, false] whether there are any cookies
41
+ def empty?
42
+ !any?
43
+ end
44
+
45
+ class Cookie
46
+
47
+ attr_reader :domain, :expires, :name, :path, :value
48
+
49
+ # @param [HtmlUnit::Cookie] the cookie
50
+ def initialize(cookie)
51
+ @_cookie = cookie
52
+ @domain = cookie.getDomain
53
+ @expires = cookie.getExpires
54
+ @name = cookie.getName
55
+ @path = cookie.getPath
56
+ @value = cookie.getValue
57
+ @secure = cookie.isSecure
58
+ end
59
+
60
+ def secure?
61
+ !!@secure
62
+ end
63
+
64
+ # @return [HtmlUnit::Cookie] the native cookie object
65
+ # @api private
66
+ def native
67
+ @_cookie
68
+ end
69
+ end
70
+
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,120 @@
1
+ module Akephalos
2
+ class Client
3
+
4
+ # Akephalos::Client::Filter extends HtmlUnit's WebConnectionWrapper to
5
+ # enable filtering outgoing requests generated interally by HtmlUnit.
6
+ #
7
+ # When a request comes through, it will be tested against the filters
8
+ # defined in Akephalos.filters and return a mock response if a match is
9
+ # found. If no filters are defined, or no filters match the request, then
10
+ # the response will bubble up to HtmlUnit for the normal request/response
11
+ # cycle.
12
+ class Filter < HtmlUnit::Util::WebConnectionWrapper
13
+ # Filters an outgoing request, and if a match is found, returns the mock
14
+ # response.
15
+ #
16
+ # @param [WebRequest] request the pending HTTP request
17
+ # @return [WebResponseImpl] when the request matches a defined filter
18
+ # @return [nil] when no filters match the request
19
+ def filter(request)
20
+ if filter = find_filter(request)
21
+ start_time = Time.now
22
+ headers = filter[:headers].map do |name, value|
23
+ HtmlUnit::Util::NameValuePair.new(name.to_s, value.to_s)
24
+ end
25
+ response = HtmlUnit::WebResponseData.new(
26
+ filter[:body].to_s.to_java_bytes,
27
+ filter[:status],
28
+ HTTP_STATUS_CODES.fetch(filter[:status], "Unknown"),
29
+ headers
30
+ )
31
+ HtmlUnit::WebResponseImpl.new(response, request, Time.now - start_time)
32
+ end
33
+ end
34
+
35
+ # Searches for a filter which matches the request's HTTP method and url.
36
+ #
37
+ # @param [WebRequest] request the pending HTTP request
38
+ # @return [Hash] when a filter matches the request
39
+ # @return [nil] when no filters match the request
40
+ def find_filter(request)
41
+ Akephalos.filters.find do |filter|
42
+ request.http_method === filter[:method] && request.url.to_s =~ filter[:filter]
43
+ end
44
+ end
45
+
46
+ # This method is called by WebClient when a page is requested, and will
47
+ # return a mock response if the request matches a defined filter or else
48
+ # return the actual response.
49
+ #
50
+ # @api htmlunit
51
+ # @param [WebRequest] request the pending HTTP request
52
+ # @return [WebResponseImpl]
53
+ def getResponse(request)
54
+ filter(request) || super
55
+ end
56
+
57
+ # Map of status codes to their English descriptions.
58
+ HTTP_STATUS_CODES = {
59
+ 100 => "Continue",
60
+ 101 => "Switching Protocols",
61
+ 102 => "Processing",
62
+ 200 => "OK",
63
+ 201 => "Created",
64
+ 202 => "Accepted",
65
+ 203 => "Non-Authoritative Information",
66
+ 204 => "No Content",
67
+ 205 => "Reset Content",
68
+ 206 => "Partial Content",
69
+ 207 => "Multi-Status",
70
+ 300 => "Multiple Choices",
71
+ 301 => "Moved Permanently",
72
+ 302 => "Found",
73
+ 303 => "See Other",
74
+ 304 => "Not Modified",
75
+ 305 => "Use Proxy",
76
+ 306 => "Switch Proxy",
77
+ 307 => "Temporary Redirect",
78
+ 400 => "Bad Request",
79
+ 401 => "Unauthorized",
80
+ 402 => "Payment Required",
81
+ 403 => "Forbidden",
82
+ 404 => "Not Found",
83
+ 405 => "Method Not Allowed",
84
+ 406 => "Not Acceptable",
85
+ 407 => "Proxy Authentication Required",
86
+ 408 => "Request Timeout",
87
+ 409 => "Conflict",
88
+ 410 => "Gone",
89
+ 411 => "Length Required",
90
+ 412 => "Precondition Failed",
91
+ 413 => "Request Entity Too Large",
92
+ 414 => "Request-URI Too Long",
93
+ 415 => "Unsupported Media Type",
94
+ 416 => "Requested Range Not Satisfiable",
95
+ 417 => "Expectation Failed",
96
+ 418 => "I'm a teapot",
97
+ 421 => "There are too many connections from your internet address",
98
+ 422 => "Unprocessable Entity",
99
+ 423 => "Locked",
100
+ 424 => "Failed Dependency",
101
+ 425 => "Unordered Collection",
102
+ 426 => "Upgrade Required",
103
+ 449 => "Retry With",
104
+ 450 => "Blocked by Windows Parental Controls",
105
+ 500 => "Internal Server Error",
106
+ 501 => "Not Implemented",
107
+ 502 => "Bad Gateway",
108
+ 503 => "Service Unavailable",
109
+ 504 => "Gateway Timeout",
110
+ 505 => "HTTP Version Not Supported",
111
+ 506 => "Variant Also Negotiates",
112
+ 507 => "Insufficient Storage",
113
+ 509 => "Bandwidth Limit Exceeded",
114
+ 510 => "Not Extended",
115
+ 530 => "User access denied"
116
+ }.freeze
117
+ end
118
+
119
+ end
120
+ end