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.
- data/MIT_LICENSE +20 -0
- data/README.md +365 -0
- data/bin/akephalos +110 -0
- data/lib/akephalos.rb +16 -0
- data/lib/akephalos/capybara.rb +348 -0
- data/lib/akephalos/client.rb +192 -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 +31 -0
- data/lib/akephalos/htmlunit/ext/confirm_handler.rb +18 -0
- data/lib/akephalos/htmlunit/ext/http_method.rb +30 -0
- data/lib/akephalos/htmlunit_downloader.rb +38 -0
- data/lib/akephalos/node.rb +187 -0
- data/lib/akephalos/page.rb +118 -0
- data/lib/akephalos/remote_client.rb +93 -0
- data/lib/akephalos/server.rb +79 -0
- data/lib/akephalos/version.rb +3 -0
- metadata +158 -0
@@ -0,0 +1,118 @@
|
|
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.getWebRequest.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/Integer] id the frame's id or index
|
103
|
+
# @return [HtmlUnit::HtmlPage] the specified frame
|
104
|
+
# @return [nil] if no frame is found
|
105
|
+
def find_frame(id_or_index)
|
106
|
+
if id_or_index.is_a? Fixnum
|
107
|
+
frame = @_page.getFrames.get(id_or_index) rescue nil
|
108
|
+
else
|
109
|
+
frame = @_page.getFrames.find do |frame|
|
110
|
+
frame.getFrameElement.getAttribute("id") == id_or_index
|
111
|
+
end
|
112
|
+
end
|
113
|
+
frame.getEnclosedPage if frame
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
@@ -0,0 +1,93 @@
|
|
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
|
+
|
19
|
+
# @return [DRbObject] a new instance of Akephalos::Client from the DRb
|
20
|
+
# server
|
21
|
+
def self.new(options = {})
|
22
|
+
manager.new_client(options)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Starts a remove JRuby DRb server unless already running and returns an
|
26
|
+
# instance of Akephalos::ClientManager.
|
27
|
+
#
|
28
|
+
# @return [DRbObject] an instance of Akephalos::ClientManager
|
29
|
+
def self.manager
|
30
|
+
return @manager if defined?(@manager)
|
31
|
+
|
32
|
+
server_port = start!
|
33
|
+
|
34
|
+
DRb.start_service("druby://127.0.0.1:#{find_available_port}")
|
35
|
+
manager = DRbObject.new_with_uri("druby://127.0.0.1:#{server_port}")
|
36
|
+
|
37
|
+
# We want to share our local configuration with the remote server
|
38
|
+
# process, so we share an undumped version of our configuration. This
|
39
|
+
# lets us continue to make changes locally and have them reflected in the
|
40
|
+
# remote process.
|
41
|
+
manager.configuration = Akephalos.configuration.extend(DRbUndumped)
|
42
|
+
|
43
|
+
@manager = manager
|
44
|
+
end
|
45
|
+
|
46
|
+
# Start a remote server process and return when it is available for use.
|
47
|
+
def self.start!
|
48
|
+
port = find_available_port
|
49
|
+
|
50
|
+
remote_client = IO.popen("ruby #{Akephalos::BIN_DIR + 'akephalos'} #{port}")
|
51
|
+
|
52
|
+
# Set up a monitor thread to detect if the forked server exits
|
53
|
+
# prematurely.
|
54
|
+
server_monitor = Thread.new { Thread.current[:exited] = Process.wait(remote_client.pid) }
|
55
|
+
|
56
|
+
# Wait for the server to be accessible on the socket we specified.
|
57
|
+
until responsive?(port)
|
58
|
+
exit!(1) if server_monitor[:exited]
|
59
|
+
sleep 0.5
|
60
|
+
end
|
61
|
+
server_monitor.kill
|
62
|
+
|
63
|
+
# Ensure that the remote server shuts down gracefully when we are
|
64
|
+
# finished.
|
65
|
+
at_exit { Process.kill(:INT, remote_client.pid) }
|
66
|
+
|
67
|
+
port
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
# @api private
|
73
|
+
# @param [Integer] port the port to check for responsiveness
|
74
|
+
# @return [true, false] whether the port is responsive
|
75
|
+
def self.responsive?(port)
|
76
|
+
socket = TCPSocket.open('127.0.0.1', port)
|
77
|
+
true
|
78
|
+
rescue Errno::ECONNREFUSED
|
79
|
+
false
|
80
|
+
ensure
|
81
|
+
socket.close if socket
|
82
|
+
end
|
83
|
+
|
84
|
+
# @api private
|
85
|
+
# @return [Integer] the next available port
|
86
|
+
def self.find_available_port
|
87
|
+
server = TCPServer.new('127.0.0.1', 0)
|
88
|
+
server.addr[1]
|
89
|
+
ensure
|
90
|
+
server.close if server
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,79 @@
|
|
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
|
+
# The ClientManager is shared over DRb with the remote process, and
|
27
|
+
# facilitates communication between the processes.
|
28
|
+
#
|
29
|
+
# @api private
|
30
|
+
class ClientManager
|
31
|
+
include DRbUndumped
|
32
|
+
|
33
|
+
# @return [Akephalos::Client] a new client instance
|
34
|
+
def self.new_client(options = {})
|
35
|
+
# Store the client to ensure it isn't prematurely garbage collected.
|
36
|
+
@client = Client.new(options)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Set the global configuration settings for Akephalos.
|
40
|
+
#
|
41
|
+
# @param [Hash] config the configuration settings
|
42
|
+
# @return [Hash] the configuration
|
43
|
+
def self.configuration=(config)
|
44
|
+
Akephalos.configuration = config
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
# Akephalos::Server is used by `akephalos --server` to start a DRb server
|
50
|
+
# serving Akephalos::ClientManager.
|
51
|
+
class Server
|
52
|
+
|
53
|
+
# Start DRb service for Akephalos::ClientManager.
|
54
|
+
#
|
55
|
+
# @param [String] port attach server to
|
56
|
+
def self.start!(port)
|
57
|
+
abort_on_parent_exit!
|
58
|
+
DRb.start_service("druby://127.0.0.1:#{port}", ClientManager)
|
59
|
+
DRb.thread.join
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# Exit if STDIN is no longer readable, which corresponds to the process
|
65
|
+
# which started the server exiting prematurely.
|
66
|
+
#
|
67
|
+
# @api private
|
68
|
+
def self.abort_on_parent_exit!
|
69
|
+
Thread.new do
|
70
|
+
begin
|
71
|
+
STDIN.read
|
72
|
+
rescue IOError
|
73
|
+
exit
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
metadata
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: akephalos2-stable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 97
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 2
|
8
|
+
- 1
|
9
|
+
- 1
|
10
|
+
- 1
|
11
|
+
version: 2.1.1.1
|
12
|
+
platform: ruby
|
13
|
+
authors:
|
14
|
+
- Bernerd Schaefer
|
15
|
+
- "Gonzalo Rodr\xC3\xADguez-Baltan\xC3\xA1s D\xC3\xADaz"
|
16
|
+
autorequire:
|
17
|
+
bindir: bin
|
18
|
+
cert_chain: []
|
19
|
+
|
20
|
+
date: 2011-12-05 00:00:00 Z
|
21
|
+
dependencies:
|
22
|
+
- !ruby/object:Gem::Dependency
|
23
|
+
name: capybara
|
24
|
+
prerelease: false
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ">="
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
hash: 3
|
31
|
+
segments:
|
32
|
+
- 0
|
33
|
+
version: "0"
|
34
|
+
type: :runtime
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rake
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 3
|
45
|
+
segments:
|
46
|
+
- 0
|
47
|
+
version: "0"
|
48
|
+
type: :runtime
|
49
|
+
version_requirements: *id002
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: jruby-jars
|
52
|
+
prerelease: false
|
53
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
hash: 3
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
type: :runtime
|
63
|
+
version_requirements: *id003
|
64
|
+
- !ruby/object:Gem::Dependency
|
65
|
+
name: sinatra
|
66
|
+
prerelease: false
|
67
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
hash: 3
|
73
|
+
segments:
|
74
|
+
- 0
|
75
|
+
version: "0"
|
76
|
+
type: :development
|
77
|
+
version_requirements: *id004
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rspec
|
80
|
+
prerelease: false
|
81
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
hash: 3
|
87
|
+
segments:
|
88
|
+
- 0
|
89
|
+
version: "0"
|
90
|
+
type: :development
|
91
|
+
version_requirements: *id005
|
92
|
+
description: Headless Browser for Integration Testing with Capybara. Includes latest change from nerian which fixes html_unit downloader pulling HTMLUnit on demand instead of having it distributed in code.
|
93
|
+
email:
|
94
|
+
- bj.schaefer@gmail.com
|
95
|
+
- siotopo@gmail.com
|
96
|
+
executables:
|
97
|
+
- akephalos
|
98
|
+
extensions: []
|
99
|
+
|
100
|
+
extra_rdoc_files: []
|
101
|
+
|
102
|
+
files:
|
103
|
+
- lib/akephalos/capybara.rb
|
104
|
+
- lib/akephalos/client/cookies.rb
|
105
|
+
- lib/akephalos/client/filter.rb
|
106
|
+
- lib/akephalos/client.rb
|
107
|
+
- lib/akephalos/configuration.rb
|
108
|
+
- lib/akephalos/console.rb
|
109
|
+
- lib/akephalos/cucumber.rb
|
110
|
+
- lib/akephalos/htmlunit/ext/confirm_handler.rb
|
111
|
+
- lib/akephalos/htmlunit/ext/http_method.rb
|
112
|
+
- lib/akephalos/htmlunit.rb
|
113
|
+
- lib/akephalos/htmlunit_downloader.rb
|
114
|
+
- lib/akephalos/node.rb
|
115
|
+
- lib/akephalos/page.rb
|
116
|
+
- lib/akephalos/remote_client.rb
|
117
|
+
- lib/akephalos/server.rb
|
118
|
+
- lib/akephalos/version.rb
|
119
|
+
- lib/akephalos.rb
|
120
|
+
- README.md
|
121
|
+
- MIT_LICENSE
|
122
|
+
- bin/akephalos
|
123
|
+
homepage: https://github.com/Nerian/akephalos2
|
124
|
+
licenses: []
|
125
|
+
|
126
|
+
post_install_message:
|
127
|
+
rdoc_options: []
|
128
|
+
|
129
|
+
require_paths:
|
130
|
+
- lib
|
131
|
+
- vendor
|
132
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
133
|
+
none: false
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
hash: 3
|
138
|
+
segments:
|
139
|
+
- 0
|
140
|
+
version: "0"
|
141
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
142
|
+
none: false
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
hash: 3
|
147
|
+
segments:
|
148
|
+
- 0
|
149
|
+
version: "0"
|
150
|
+
requirements: []
|
151
|
+
|
152
|
+
rubyforge_project:
|
153
|
+
rubygems_version: 1.8.5
|
154
|
+
signing_key:
|
155
|
+
specification_version: 3
|
156
|
+
summary: Headless Browser for Integration Testing with Capybara. Includes latest change from nerian which fixes html_unit downloader pulling HTMLUnit on demand instead of having it distributed in code.
|
157
|
+
test_files: []
|
158
|
+
|