akephalos-nerian 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/MIT_LICENSE +20 -0
  2. data/README.md +56 -0
  3. data/bin/akephalos +88 -0
  4. data/lib/akephalos.rb +19 -0
  5. data/lib/akephalos/capybara.rb +316 -0
  6. data/lib/akephalos/client.rb +112 -0
  7. data/lib/akephalos/client/cookies.rb +73 -0
  8. data/lib/akephalos/client/filter.rb +120 -0
  9. data/lib/akephalos/configuration.rb +49 -0
  10. data/lib/akephalos/console.rb +32 -0
  11. data/lib/akephalos/cucumber.rb +6 -0
  12. data/lib/akephalos/htmlunit.rb +38 -0
  13. data/lib/akephalos/htmlunit/ext/http_method.rb +30 -0
  14. data/lib/akephalos/node.rb +172 -0
  15. data/lib/akephalos/page.rb +113 -0
  16. data/lib/akephalos/remote_client.rb +84 -0
  17. data/lib/akephalos/server.rb +56 -0
  18. data/lib/akephalos/version.rb +3 -0
  19. data/src/htmlunit/apache-mime4j-0.6.jar +0 -0
  20. data/src/htmlunit/commons-codec-1.4.jar +0 -0
  21. data/src/htmlunit/commons-collections-3.2.1.jar +0 -0
  22. data/src/htmlunit/commons-io-1.4.jar +0 -0
  23. data/src/htmlunit/commons-lang-2.4.jar +0 -0
  24. data/src/htmlunit/commons-logging-1.1.1.jar +0 -0
  25. data/src/htmlunit/cssparser-0.9.5.jar +0 -0
  26. data/src/htmlunit/htmlunit-2.8.jar +0 -0
  27. data/src/htmlunit/htmlunit-core-js-2.8.jar +0 -0
  28. data/src/htmlunit/httpclient-4.0.1.jar +0 -0
  29. data/src/htmlunit/httpcore-4.0.1.jar +0 -0
  30. data/src/htmlunit/httpmime-4.0.1.jar +0 -0
  31. data/src/htmlunit/nekohtml-1.9.14.jar +0 -0
  32. data/src/htmlunit/sac-1.3.jar +0 -0
  33. data/src/htmlunit/serializer-2.7.1.jar +0 -0
  34. data/src/htmlunit/xalan-2.7.1.jar +0 -0
  35. data/src/htmlunit/xercesImpl-2.9.1.jar +0 -0
  36. data/src/htmlunit/xml-apis-1.3.04.jar +0 -0
  37. metadata +164 -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
@@ -0,0 +1,3 @@
1
+ module Akephalos #:nodoc
2
+ VERSION = "0.2.4"
3
+ end