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,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 [WebResponse] 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::WebResponse.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 [WebResponse]
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
@@ -0,0 +1,49 @@
1
+ module Akephalos
2
+
3
+ @configuration = {}
4
+
5
+ class << self
6
+ # @return [Hash] the configuration
7
+ attr_accessor :configuration
8
+ end
9
+
10
+ module Filters
11
+ # @return [Array] all defined filters
12
+ def filters
13
+ configuration[:filters] ||= []
14
+ end
15
+
16
+ # Defines a new filter to be tested by Akephalos::Filter when executing
17
+ # page requests. An HTTP method and a regex or string to match against the
18
+ # URL are required for defining a filter.
19
+ #
20
+ # You can additionally pass the following options to define how the
21
+ # filtered request should respond:
22
+ #
23
+ # :status (defaults to 200)
24
+ # :body (defaults to "")
25
+ # :headers (defaults to {})
26
+ #
27
+ # If we define a filter with no additional options, then, we will get an
28
+ # empty HTML response:
29
+ #
30
+ # Akephalos.filter :post, "http://example.com"
31
+ # Akephalos.filter :any, %r{http://.*\.com}
32
+ #
33
+ # If you instead, say, wanted to simulate a failure in an external system,
34
+ # you could do this:
35
+ #
36
+ # Akephalos.filter :post, "http://example.com",
37
+ # :status => 500, :body => "Something went wrong"
38
+ #
39
+ # @param [Symbol] method the HTTP method to match
40
+ # @param [RegExp, String] regex URL matcher
41
+ # @param [Hash] options response values
42
+ def filter(method, regex, options = {})
43
+ regex = Regexp.new(Regexp.escape(regex)) if regex.is_a?(String)
44
+ filters << {:method => method, :filter => regex, :status => 200, :body => "", :headers => {}}.merge!(options)
45
+ end
46
+ end
47
+
48
+ extend Filters
49
+ end
@@ -0,0 +1,32 @@
1
+ # Begin a new Capybara session, by default connecting to localhost on port
2
+ # 3000.
3
+ def session
4
+ Capybara.app_host ||= "http://localhost:3000"
5
+ @session ||= Capybara::Session.new(:akephalos)
6
+ end
7
+ alias page session
8
+
9
+ module Akephalos
10
+ # Simple class for starting an IRB session.
11
+ class Console
12
+
13
+ # Start an IRB session. Tries to load irb/completion, and also loads a
14
+ # .irbrc file if it exists.
15
+ def self.start
16
+ require 'irb'
17
+
18
+ begin
19
+ require 'irb/completion'
20
+ rescue Exception
21
+ # No readline available, proceed anyway.
22
+ end
23
+
24
+ if ::File.exists? ".irbrc"
25
+ ENV['IRBRC'] = ".irbrc"
26
+ end
27
+
28
+ IRB.start
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,6 @@
1
+ require 'capybara/cucumber'
2
+
3
+ Before('@akephalos') do
4
+ Capybara.current_driver = :akephalos
5
+ end
6
+
@@ -0,0 +1,31 @@
1
+ require "pathname"
2
+ require "java"
3
+
4
+ Dir["#{Dir.pwd}/.akephalos/#{ENV['htmlunit_version']}/*.jar"].each {|file| require file }
5
+
6
+ java.lang.System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog")
7
+ java.lang.System.setProperty("org.apache.commons.logging.simplelog.defaultlog", "fatal")
8
+ java.lang.System.setProperty("org.apache.commons.logging.simplelog.showdatetime", "true")
9
+
10
+ # Container module for com.gargoylesoftware.htmlunit namespace.
11
+ module HtmlUnit
12
+ java_import "com.gargoylesoftware.htmlunit.BrowserVersion"
13
+ java_import "com.gargoylesoftware.htmlunit.History"
14
+ java_import "com.gargoylesoftware.htmlunit.HttpMethod"
15
+ java_import 'com.gargoylesoftware.htmlunit.ConfirmHandler'
16
+ java_import "com.gargoylesoftware.htmlunit.NicelyResynchronizingAjaxController"
17
+ java_import "com.gargoylesoftware.htmlunit.SilentCssErrorHandler"
18
+ java_import "com.gargoylesoftware.htmlunit.WebClient"
19
+ java_import "com.gargoylesoftware.htmlunit.WebResponseData"
20
+ java_import "com.gargoylesoftware.htmlunit.WebResponse"
21
+ java_import "com.gargoylesoftware.htmlunit.WaitingRefreshHandler"
22
+
23
+ # Container module for com.gargoylesoftware.htmlunit.util namespace.
24
+ module Util
25
+ java_import "com.gargoylesoftware.htmlunit.util.NameValuePair"
26
+ java_import "com.gargoylesoftware.htmlunit.util.WebConnectionWrapper"
27
+ end
28
+
29
+ # Disable history tracking
30
+ History.field_reader :ignoreNewPages_
31
+ end
@@ -0,0 +1,18 @@
1
+ # Reopen com.gargoylesoftware.htmlunit.ConfirmHandler to provide an interface to
2
+ # confirm a dialog and capture its message
3
+ module HtmlUnit
4
+ module ConfirmHandler
5
+
6
+ # Boolean - true for ok, false for cancel
7
+ attr_accessor :handleConfirmValue
8
+
9
+ # last confirmation's message
10
+ attr_reader :text
11
+
12
+ # handleConfirm will be called by htmlunit on a confirm, so store the message.
13
+ def handleConfirm(page, message)
14
+ @text = message
15
+ return handleConfirmValue.nil? ? true : handleConfirmValue
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,30 @@
1
+ module HtmlUnit
2
+ # Reopen HtmlUnit's HttpMethod class to add convenience methods.
3
+ class HttpMethod
4
+
5
+ # Loosely compare HttpMethod with another object, accepting either an
6
+ # HttpMethod instance or a symbol describing the method. Note that :any is a
7
+ # special symbol which will always return true.
8
+ #
9
+ # @param [HttpMethod] other an HtmlUnit HttpMethod object
10
+ # @param [Symbol] other a symbolized representation of an http method
11
+ # @return [true/false]
12
+ def ===(other)
13
+ case other
14
+ when HttpMethod
15
+ super
16
+ when :any
17
+ true
18
+ when :get
19
+ self == self.class::GET
20
+ when :post
21
+ self == self.class::POST
22
+ when :put
23
+ self == self.class::PUT
24
+ when :delete
25
+ self == self.class::DELETE
26
+ end
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,38 @@
1
+ module HtmlUnit
2
+ def self.download_htmlunit(version)
3
+ if not version_exist?(version)
4
+ puts "Installing HTMLUnit #{version} at .akephalos/#{version}/"
5
+ Dir.mkdir(".akephalos") unless File.exists?(".akephalos")
6
+ Dir.mkdir(".akephalos/#{version}") unless File.exists?(".akephalos/#{version}")
7
+ download(version)
8
+ unzip(version)
9
+ remove_cache(version)
10
+ else
11
+ puts "Using HTMLUnit #{version}"
12
+ end
13
+ end
14
+
15
+ def self.version_exist?(version)
16
+ File.exist?(".akephalos/#{version}/htmlunit-#{version}.jar")
17
+ end
18
+
19
+ def self.unzip(version)
20
+ `tar xzf htmlunit-#{version}.zip`
21
+ `mv -f htmlunit-2.10-SNAPSHOT htmlunit-2.10 > /dev/null 2>&1`
22
+ `cp -r htmlunit-#{version}/lib/ .akephalos/#{version}/`
23
+ end
24
+
25
+ def self.download(version)
26
+ if version == "2.10"
27
+ %x[curl -L -o htmlunit-2.10.zip http://build.canoo.com/htmlunit/artifacts/htmlunit-2.10-SNAPSHOT-with-dependencies.zip]
28
+ elsif version == '2.9'
29
+ %x[curl -L -o htmlunit-2.9.zip http://sourceforge.net/projects/htmlunit/files/htmlunit/2.9/htmlunit-2.9-bin.zip]
30
+ else
31
+ %x[curl -L -O http://sourceforge.net/projects/htmlunit/files/htmlunit/#{version}/htmlunit-#{version}.zip]
32
+ end
33
+ end
34
+
35
+ def self.remove_cache(version)
36
+ `rm -rf htmlunit-#{version} htmlunit-#{version}.zip`
37
+ end
38
+ end
@@ -0,0 +1,187 @@
1
+ module Akephalos
2
+
3
+ # Akephalos::Node wraps HtmlUnit's DOMNode class, providing a simple API for
4
+ # interacting with an element on the page.
5
+ class Node
6
+ # @param [HtmlUnit::DOMNode] node
7
+ def initialize(node)
8
+ @nodes = []
9
+ @_node = node
10
+ end
11
+
12
+ # @return [true, false] whether the element is checked
13
+ def checked?
14
+ if @_node.respond_to?(:isChecked)
15
+ @_node.isChecked
16
+ else
17
+ !! self[:checked]
18
+ end
19
+ end
20
+
21
+ # @return [String] inner text of the node
22
+ # Returns a textual representation of this element that represents what would
23
+ # be visible to the user if this page was shown in a web browser.
24
+ # For example, a single-selection select element would return the currently
25
+ # selected value as text.
26
+ # Note: This will cleanup/reduce whitespace
27
+ def text
28
+ @_node.asText
29
+ end
30
+
31
+ # Returns the raw text content of this node and its descendants...
32
+ def text_content
33
+ @_node.getTextContent
34
+ end
35
+
36
+ # Returns a string representation of the XML document from this element and
37
+ # all it's children (recursively). The charset used is the current page encoding.
38
+ def xml
39
+ @_node.asXml
40
+ end
41
+
42
+ # Return the value of the node's attribute.
43
+ #
44
+ # @param [String] name attribute on node
45
+ # @return [String] the value of the named attribute
46
+ # @return [nil] when the node does not have the named attribute
47
+ def [](name)
48
+ @_node.hasAttribute(name.to_s) ? @_node.getAttribute(name.to_s) : nil
49
+ end
50
+
51
+ # Return the value of a form element. If the element is a select box and
52
+ # has "multiple" declared as an attribute, then all selected options will
53
+ # be returned as an array.
54
+ #
55
+ # @return [String, Array<String>] the node's value
56
+ def value
57
+ case tag_name
58
+ when "select"
59
+ if self[:multiple]
60
+ selected_options.map { |option| option.value }
61
+ else
62
+ selected_option = @_node.selected_options.first
63
+ selected_option ? Node.new(selected_option).value : nil
64
+ end
65
+ when "option"
66
+ self[:value] || text
67
+ when "textarea"
68
+ @_node.getText
69
+ else
70
+ self[:value]
71
+ end
72
+ end
73
+
74
+ # Set the value of the form input.
75
+ #
76
+ # @param [String] value
77
+ def value=(value)
78
+ case tag_name
79
+ when "textarea"
80
+ @_node.setText("")
81
+ type(value)
82
+ when "input"
83
+ if file_input?
84
+ @_node.setValueAttribute(value)
85
+ else
86
+ @_node.setValueAttribute("")
87
+ type(value)
88
+ end
89
+ end
90
+ end
91
+
92
+ # Types each character into a text or input field.
93
+ #
94
+ # @param [String] value the string to type
95
+ def type(value)
96
+ value.each_char do |c|
97
+ @_node.type(c)
98
+ end
99
+ end
100
+
101
+ # @return [true, false] whether the node allows multiple-option selection (if the node is a select).
102
+ def multiple_select?
103
+ !self[:multiple].nil?
104
+ end
105
+
106
+ # @return [true, false] whether the node is a file input
107
+ def file_input?
108
+ tag_name == "input" && @_node.getAttribute("type") == "file"
109
+ end
110
+
111
+
112
+ # Unselect an option.
113
+ #
114
+ # @return [true, false] whether the unselection was successful
115
+ def unselect
116
+ @_node.setSelected(false)
117
+ end
118
+
119
+ # Return the option elements for a select box.
120
+ #
121
+ # @return [Array<Node>] the options
122
+ def options
123
+ @_node.getOptions.map { |node| Node.new(node) }
124
+ end
125
+
126
+ # Return the selected option elements for a select box.
127
+ #
128
+ # @return [Array<Node>] the selected options
129
+ def selected_options
130
+ @_node.getSelectedOptions.map { |node| Node.new(node) }
131
+ end
132
+
133
+ # Fire a JavaScript event on the current node. Note that you should not
134
+ # prefix event names with "on", so:
135
+ #
136
+ # link.fire_event('mousedown')
137
+ #
138
+ # @param [String] JavaScript event name
139
+ def fire_event(name)
140
+ @_node.fireEvent(name)
141
+ end
142
+
143
+ # @return [String] the node's tag name
144
+ def tag_name
145
+ @_node.getNodeName
146
+ end
147
+
148
+ # @return [true, false] whether the node is visible to the user accounting
149
+ # for CSS.
150
+ def visible?
151
+ @_node.isDisplayed
152
+ end
153
+
154
+ # @return [true, false] whether the node is selected to the user accounting
155
+ # for CSS.
156
+ def selected?
157
+ if @_node.respond_to?(:isSelected)
158
+ @_node.isSelected
159
+ else
160
+ !! self[:selected]
161
+ end
162
+ end
163
+
164
+ # Click the node and then wait for any triggered JavaScript callbacks to
165
+ # fire.
166
+ def click
167
+ @_node.click
168
+ @_node.getPage.getWebClient.waitForBackgroundJavaScriptStartingBefore(3000)
169
+ end
170
+
171
+ # Search for child nodes which match the given XPath selector.
172
+ #
173
+ # @param [String] selector an XPath selector
174
+ # @return [Array<Node>] the matched nodes
175
+ def find(selector)
176
+ nodes = @_node.getByXPath(selector).map { |node| Node.new(node) }
177
+ @nodes << nodes
178
+ nodes
179
+ end
180
+
181
+ # @return [String] the XPath expression for this node
182
+ def xpath
183
+ @_node.getCanonicalXPath
184
+ end
185
+ end
186
+
187
+ end