akephalos-nerian 0.2.4-java
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.
- data/MIT_LICENSE +20 -0
- data/README.md +56 -0
- data/bin/akephalos +88 -0
- data/lib/akephalos.rb +19 -0
- data/lib/akephalos/capybara.rb +316 -0
- data/lib/akephalos/client.rb +112 -0
- data/lib/akephalos/client/cookies.rb +73 -0
- data/lib/akephalos/client/filter.rb +120 -0
- data/lib/akephalos/configuration.rb +49 -0
- data/lib/akephalos/console.rb +32 -0
- data/lib/akephalos/cucumber.rb +6 -0
- data/lib/akephalos/htmlunit.rb +38 -0
- data/lib/akephalos/htmlunit/ext/http_method.rb +30 -0
- data/lib/akephalos/node.rb +172 -0
- data/lib/akephalos/page.rb +113 -0
- data/lib/akephalos/remote_client.rb +84 -0
- data/lib/akephalos/server.rb +56 -0
- data/lib/akephalos/version.rb +3 -0
- data/src/htmlunit/apache-mime4j-0.6.jar +0 -0
- data/src/htmlunit/commons-codec-1.4.jar +0 -0
- data/src/htmlunit/commons-collections-3.2.1.jar +0 -0
- data/src/htmlunit/commons-io-1.4.jar +0 -0
- data/src/htmlunit/commons-lang-2.4.jar +0 -0
- data/src/htmlunit/commons-logging-1.1.1.jar +0 -0
- data/src/htmlunit/cssparser-0.9.5.jar +0 -0
- data/src/htmlunit/htmlunit-2.8.jar +0 -0
- data/src/htmlunit/htmlunit-core-js-2.8.jar +0 -0
- data/src/htmlunit/httpclient-4.0.1.jar +0 -0
- data/src/htmlunit/httpcore-4.0.1.jar +0 -0
- data/src/htmlunit/httpmime-4.0.1.jar +0 -0
- data/src/htmlunit/nekohtml-1.9.14.jar +0 -0
- data/src/htmlunit/sac-1.3.jar +0 -0
- data/src/htmlunit/serializer-2.7.1.jar +0 -0
- data/src/htmlunit/xalan-2.7.1.jar +0 -0
- data/src/htmlunit/xercesImpl-2.9.1.jar +0 -0
- data/src/htmlunit/xml-apis-1.3.04.jar +0 -0
- metadata +150 -0
@@ -0,0 +1,172 @@
|
|
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
|
+
def text
|
23
|
+
@_node.asText
|
24
|
+
end
|
25
|
+
|
26
|
+
# Return the value of the node's attribute.
|
27
|
+
#
|
28
|
+
# @param [String] name attribute on node
|
29
|
+
# @return [String] the value of the named attribute
|
30
|
+
# @return [nil] when the node does not have the named attribute
|
31
|
+
def [](name)
|
32
|
+
@_node.hasAttribute(name.to_s) ? @_node.getAttribute(name.to_s) : nil
|
33
|
+
end
|
34
|
+
|
35
|
+
# Return the value of a form element. If the element is a select box and
|
36
|
+
# has "multiple" declared as an attribute, then all selected options will
|
37
|
+
# be returned as an array.
|
38
|
+
#
|
39
|
+
# @return [String, Array<String>] the node's value
|
40
|
+
def value
|
41
|
+
case tag_name
|
42
|
+
when "select"
|
43
|
+
if self[:multiple]
|
44
|
+
selected_options.map { |option| option.value }
|
45
|
+
else
|
46
|
+
selected_option = @_node.selected_options.first
|
47
|
+
selected_option ? Node.new(selected_option).value : nil
|
48
|
+
end
|
49
|
+
when "option"
|
50
|
+
self[:value] || text
|
51
|
+
when "textarea"
|
52
|
+
@_node.getText
|
53
|
+
else
|
54
|
+
self[:value]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Set the value of the form input.
|
59
|
+
#
|
60
|
+
# @param [String] value
|
61
|
+
def value=(value)
|
62
|
+
case tag_name
|
63
|
+
when "textarea"
|
64
|
+
@_node.setText("")
|
65
|
+
type(value)
|
66
|
+
when "input"
|
67
|
+
if file_input?
|
68
|
+
@_node.setValueAttribute(value)
|
69
|
+
else
|
70
|
+
@_node.setValueAttribute("")
|
71
|
+
type(value)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Types each character into a text or input field.
|
77
|
+
#
|
78
|
+
# @param [String] value the string to type
|
79
|
+
def type(value)
|
80
|
+
value.each_char do |c|
|
81
|
+
@_node.type(c)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# @return [true, false] whether the node allows multiple-option selection (if the node is a select).
|
86
|
+
def multiple_select?
|
87
|
+
!self[:multiple].nil?
|
88
|
+
end
|
89
|
+
|
90
|
+
# @return [true, false] whether the node is a file input
|
91
|
+
def file_input?
|
92
|
+
tag_name == "input" && @_node.getAttribute("type") == "file"
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
# Unselect an option.
|
97
|
+
#
|
98
|
+
# @return [true, false] whether the unselection was successful
|
99
|
+
def unselect
|
100
|
+
@_node.setSelected(false)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Return the option elements for a select box.
|
104
|
+
#
|
105
|
+
# @return [Array<Node>] the options
|
106
|
+
def options
|
107
|
+
@_node.getOptions.map { |node| Node.new(node) }
|
108
|
+
end
|
109
|
+
|
110
|
+
# Return the selected option elements for a select box.
|
111
|
+
#
|
112
|
+
# @return [Array<Node>] the selected options
|
113
|
+
def selected_options
|
114
|
+
@_node.getSelectedOptions.map { |node| Node.new(node) }
|
115
|
+
end
|
116
|
+
|
117
|
+
# Fire a JavaScript event on the current node. Note that you should not
|
118
|
+
# prefix event names with "on", so:
|
119
|
+
#
|
120
|
+
# link.fire_event('mousedown')
|
121
|
+
#
|
122
|
+
# @param [String] JavaScript event name
|
123
|
+
def fire_event(name)
|
124
|
+
@_node.fireEvent(name)
|
125
|
+
end
|
126
|
+
|
127
|
+
# @return [String] the node's tag name
|
128
|
+
def tag_name
|
129
|
+
@_node.getNodeName
|
130
|
+
end
|
131
|
+
|
132
|
+
# @return [true, false] whether the node is visible to the user accounting
|
133
|
+
# for CSS.
|
134
|
+
def visible?
|
135
|
+
@_node.isDisplayed
|
136
|
+
end
|
137
|
+
|
138
|
+
# @return [true, false] whether the node is selected to the user accounting
|
139
|
+
# for CSS.
|
140
|
+
def selected?
|
141
|
+
if @_node.respond_to?(:isSelected)
|
142
|
+
@_node.isSelected
|
143
|
+
else
|
144
|
+
!! self[:selected]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Click the node and then wait for any triggered JavaScript callbacks to
|
149
|
+
# fire.
|
150
|
+
def click
|
151
|
+
@_node.click
|
152
|
+
@_node.getPage.getEnclosingWindow.getJobManager.waitForJobs(1000)
|
153
|
+
@_node.getPage.getEnclosingWindow.getJobManager.waitForJobsStartingBefore(1000)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Search for child nodes which match the given XPath selector.
|
157
|
+
#
|
158
|
+
# @param [String] selector an XPath selector
|
159
|
+
# @return [Array<Node>] the matched nodes
|
160
|
+
def find(selector)
|
161
|
+
nodes = @_node.getByXPath(selector).map { |node| Node.new(node) }
|
162
|
+
@nodes << nodes
|
163
|
+
nodes
|
164
|
+
end
|
165
|
+
|
166
|
+
# @return [String] the XPath expression for this node
|
167
|
+
def xpath
|
168
|
+
@_node.getCanonicalXPath
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Akephalos
|
2
|
+
|
3
|
+
# Akephalos::Page wraps HtmlUnit's HtmlPage class, exposing an API for
|
4
|
+
# interacting with a page in the browser.
|
5
|
+
class Page
|
6
|
+
# @param [HtmlUnit::HtmlPage] page
|
7
|
+
def initialize(page)
|
8
|
+
@nodes = []
|
9
|
+
@_page = page
|
10
|
+
end
|
11
|
+
|
12
|
+
# Search for nodes which match the given XPath selector.
|
13
|
+
#
|
14
|
+
# @param [String] selector an XPath selector
|
15
|
+
# @return [Array<Node>] the matched nodes
|
16
|
+
def find(selector)
|
17
|
+
nodes = current_frame.getByXPath(selector).map { |node| Node.new(node) }
|
18
|
+
@nodes << nodes
|
19
|
+
nodes
|
20
|
+
end
|
21
|
+
|
22
|
+
# Return the page's source, including any JavaScript-triggered DOM changes.
|
23
|
+
#
|
24
|
+
# @return [String] the page's modified source
|
25
|
+
def modified_source
|
26
|
+
current_frame.asXml
|
27
|
+
end
|
28
|
+
|
29
|
+
# Return the page's source as returned by the web server.
|
30
|
+
#
|
31
|
+
# @return [String] the page's original source
|
32
|
+
def source
|
33
|
+
current_frame.getWebResponse.getContentAsString
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [Hash{String => String}] the page's response headers
|
37
|
+
def response_headers
|
38
|
+
headers = current_frame.getWebResponse.getResponseHeaders.map do |header|
|
39
|
+
[header.getName, header.getValue]
|
40
|
+
end
|
41
|
+
Hash[*headers.flatten]
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [Integer] the response's status code
|
45
|
+
def status_code
|
46
|
+
current_frame.getWebResponse.getStatusCode
|
47
|
+
end
|
48
|
+
|
49
|
+
# Execute the given block in the context of the frame specified.
|
50
|
+
#
|
51
|
+
# @param [String] frame_id the frame's id
|
52
|
+
# @return [true] if the frame is found
|
53
|
+
# @return [nil] if the frame is not found
|
54
|
+
def within_frame(frame_id)
|
55
|
+
return unless @current_frame = find_frame(frame_id)
|
56
|
+
yield
|
57
|
+
true
|
58
|
+
ensure
|
59
|
+
@current_frame = nil
|
60
|
+
end
|
61
|
+
|
62
|
+
# @return [String] the current page's URL.
|
63
|
+
def current_url
|
64
|
+
current_frame.getWebResponse.getRequestSettings.getUrl.toString
|
65
|
+
end
|
66
|
+
|
67
|
+
# Execute JavaScript against the current page, discarding any return value.
|
68
|
+
#
|
69
|
+
# @param [String] script the JavaScript to be executed
|
70
|
+
# @return [nil]
|
71
|
+
def execute_script(script)
|
72
|
+
current_frame.executeJavaScript(script)
|
73
|
+
nil
|
74
|
+
end
|
75
|
+
|
76
|
+
# Execute JavaScript against the current page and return the results.
|
77
|
+
#
|
78
|
+
# @param [String] script the JavaScript to be executed
|
79
|
+
# @return the result of the JavaScript
|
80
|
+
def evaluate_script(script)
|
81
|
+
current_frame.executeJavaScript(script).getJavaScriptResult
|
82
|
+
end
|
83
|
+
|
84
|
+
# Compare this page with an HtmlUnit page.
|
85
|
+
#
|
86
|
+
# @param [HtmlUnit::HtmlPage] other an HtmlUnit page
|
87
|
+
# @return [true, false]
|
88
|
+
def ==(other)
|
89
|
+
@_page == other
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
# Return the current frame. Usually just @_page, except when inside of the
|
95
|
+
# within_frame block.
|
96
|
+
#
|
97
|
+
# @return [HtmlUnit::HtmlPage] the current frame
|
98
|
+
def current_frame
|
99
|
+
@current_frame || @_page
|
100
|
+
end
|
101
|
+
|
102
|
+
# @param [String] id the frame's id
|
103
|
+
# @return [HtmlUnit::HtmlPage] the specified frame
|
104
|
+
# @return [nil] if no frame is found
|
105
|
+
def find_frame(id)
|
106
|
+
frame = @_page.getFrames.find do |frame|
|
107
|
+
frame.getFrameElement.getAttribute("id") == id
|
108
|
+
end
|
109
|
+
frame.getEnclosedPage if frame
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'drb/drb'
|
3
|
+
|
4
|
+
# We need to define our own NativeException class for the cases when a native
|
5
|
+
# exception is raised by the JRuby DRb server.
|
6
|
+
class NativeException < StandardError; end
|
7
|
+
|
8
|
+
module Akephalos
|
9
|
+
|
10
|
+
# The +RemoteClient+ class provides an interface to an +Akephalos::Client+
|
11
|
+
# isntance on a remote DRb server.
|
12
|
+
#
|
13
|
+
# == Usage
|
14
|
+
# client = Akephalos::RemoteClient.new
|
15
|
+
# client.visit "http://www.oinopa.com"
|
16
|
+
# client.page.source # => "<!DOCTYPE html PUBLIC..."
|
17
|
+
class RemoteClient
|
18
|
+
# Start a remote akephalos server and return the remote Akephalos::Client
|
19
|
+
# instance.
|
20
|
+
#
|
21
|
+
# @return [DRbObject] the remote client instance
|
22
|
+
def self.new
|
23
|
+
server_port = start!
|
24
|
+
|
25
|
+
DRb.start_service("druby://127.0.0.1:#{find_available_port}")
|
26
|
+
client = DRbObject.new_with_uri("druby://127.0.0.1:#{server_port}")
|
27
|
+
|
28
|
+
# We want to share our local configuration with the remote server
|
29
|
+
# process, so we share an undumped version of our configuration. This
|
30
|
+
# lets us continue to make changes locally and have them reflected in the
|
31
|
+
# remote process.
|
32
|
+
client.configuration = Akephalos.configuration.extend(DRbUndumped)
|
33
|
+
|
34
|
+
client
|
35
|
+
end
|
36
|
+
|
37
|
+
# Start a remote server process and return when it is available for use.
|
38
|
+
def self.start!
|
39
|
+
port = find_available_port
|
40
|
+
|
41
|
+
remote_client = IO.popen("#{Akephalos::BIN_DIR + 'akephalos'} #{port}")
|
42
|
+
|
43
|
+
# Set up a monitor thread to detect if the forked server exits
|
44
|
+
# prematurely.
|
45
|
+
server_monitor = Thread.new { Thread.current[:exited] = Process.wait(remote_client.pid) }
|
46
|
+
|
47
|
+
# Wait for the server to be accessible on the socket we specified.
|
48
|
+
until responsive?(port)
|
49
|
+
exit!(1) if server_monitor[:exited]
|
50
|
+
sleep 0.5
|
51
|
+
end
|
52
|
+
server_monitor.kill
|
53
|
+
|
54
|
+
# Ensure that the remote server shuts down gracefully when we are
|
55
|
+
# finished.
|
56
|
+
at_exit { Process.kill(:INT, remote_client.pid) }
|
57
|
+
|
58
|
+
port
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
# @api private
|
64
|
+
# @param [Integer] port the port to check for responsiveness
|
65
|
+
# @return [true, false] whether the port is responsive
|
66
|
+
def self.responsive?(port)
|
67
|
+
socket = TCPSocket.open('127.0.0.1', port)
|
68
|
+
true
|
69
|
+
rescue Errno::ECONNREFUSED
|
70
|
+
false
|
71
|
+
ensure
|
72
|
+
socket.close if socket
|
73
|
+
end
|
74
|
+
|
75
|
+
# @api private
|
76
|
+
# @return [Integer] the next available port
|
77
|
+
def self.find_available_port
|
78
|
+
server = TCPServer.new('127.0.0.1', 0)
|
79
|
+
server.addr[1]
|
80
|
+
ensure
|
81
|
+
server.close if server
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require "pathname"
|
2
|
+
require "drb/drb"
|
3
|
+
require "akephalos/client"
|
4
|
+
|
5
|
+
# In ruby-1.8.7 and later, the message for a NameError exception is lazily
|
6
|
+
# evaluated. There are, however, different implementations of this between ruby
|
7
|
+
# and jruby, so we realize these messages when sending over DRb.
|
8
|
+
class NameError::Message
|
9
|
+
# @note This method is called by DRb before sending the error to the remote
|
10
|
+
# connection.
|
11
|
+
# @return [String] the inner message.
|
12
|
+
def _dump
|
13
|
+
to_s
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
[
|
18
|
+
Akephalos::Page,
|
19
|
+
Akephalos::Node,
|
20
|
+
Akephalos::Client::Cookies,
|
21
|
+
Akephalos::Client::Cookies::Cookie
|
22
|
+
].each { |klass| klass.send(:include, DRbUndumped) }
|
23
|
+
|
24
|
+
module Akephalos
|
25
|
+
|
26
|
+
# Akephalos::Server is used by `akephalos --server` to start a DRb server
|
27
|
+
# serving an instance of Akephalos::Client.
|
28
|
+
class Server
|
29
|
+
# Start DRb service for an Akephalos::Client.
|
30
|
+
#
|
31
|
+
# @param [String] port attach server to
|
32
|
+
def self.start!(port)
|
33
|
+
abort_on_parent_exit!
|
34
|
+
client = Client.new
|
35
|
+
DRb.start_service("druby://127.0.0.1:#{port}", client)
|
36
|
+
DRb.thread.join
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
# Exit if STDIN is no longer readable, which corresponds to the process
|
42
|
+
# which started the server exiting prematurely.
|
43
|
+
#
|
44
|
+
# @api private
|
45
|
+
def self.abort_on_parent_exit!
|
46
|
+
Thread.new do
|
47
|
+
begin
|
48
|
+
STDIN.read
|
49
|
+
rescue IOError
|
50
|
+
exit
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|