akephalos-nerian 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- 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 +164 -0
data/MIT_LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Bernerd Schaefer
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# Akephalos
|
2
|
+
Akephalos is a full-stack headless browser for integration testing with
|
3
|
+
Capybara. It is built on top of [HtmlUnit](http://htmlunit.sourceforge.net),
|
4
|
+
a GUI-less browser for the Java platform, but can be run on both JRuby and
|
5
|
+
MRI with no need for JRuby to be installed on the system.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
gem install akephalos
|
10
|
+
|
11
|
+
## Setup
|
12
|
+
|
13
|
+
Configuring akephalos is as simple as requiring it and setting Capybara's
|
14
|
+
javascript driver:
|
15
|
+
|
16
|
+
require 'akephalos'
|
17
|
+
Capybara.javascript_driver = :akephalos
|
18
|
+
|
19
|
+
## Basic Usage
|
20
|
+
|
21
|
+
Akephalos provides a driver for Capybara, so using Akephalos is no
|
22
|
+
different than using Selenium or Rack::Test. For a full usage guide, check
|
23
|
+
out Capybara's DSL [documentation](http://github.com/jnicklas/capybara). It
|
24
|
+
makes no assumptions about the testing framework being used, and works with
|
25
|
+
RSpec, Cucumber, and Test::Unit.
|
26
|
+
|
27
|
+
Here's some sample RSpec code:
|
28
|
+
|
29
|
+
describe "Home Page" do
|
30
|
+
before { visit "/" }
|
31
|
+
context "searching" do
|
32
|
+
before do
|
33
|
+
fill_in "Search", :with => "akephalos"
|
34
|
+
click_button "Go"
|
35
|
+
end
|
36
|
+
it "returns results" { page.should have_css("#results") }
|
37
|
+
it "includes the search term" { page.should have_content("akephalos") }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
## More
|
42
|
+
|
43
|
+
* [bin/akephalos](http://bernerdschaefer.github.com/akephalos/akephalos-bin.html)
|
44
|
+
allows you to start an interactive shell or DRb server, as well as perform
|
45
|
+
other maintenance features.
|
46
|
+
|
47
|
+
* [Filters](http://bernerdschaefer.github.com/akephalos/filters.html) allows
|
48
|
+
you to declare mock responses for external resources and services requested
|
49
|
+
by the browser.
|
50
|
+
|
51
|
+
## Resources
|
52
|
+
|
53
|
+
* [API Documentation](http://bernerdschaefer.github.com/akephalos/api)
|
54
|
+
* [Source code](http://github.com/bernerdschaefer/akephalos) and
|
55
|
+
[issues](http://github.com/bernerdschaefer/akephalos/issues) are hosted on
|
56
|
+
github.
|
data/bin/akephalos
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# vim:set filetype=ruby:
|
3
|
+
|
4
|
+
require "pathname"
|
5
|
+
require "optparse"
|
6
|
+
|
7
|
+
options = { :interactive => false }
|
8
|
+
|
9
|
+
parser = OptionParser.new do |opts|
|
10
|
+
opts.banner = "Usage: akephalos [--interactive, --use-htmlunit-snapshot] | [--server] <port>"
|
11
|
+
opts.on("-s", "--server", "Run in server mode (default)")
|
12
|
+
opts.on("-i", "--interactive", "Run in interactive mode") { options[:interactive] = true }
|
13
|
+
opts.on("--use-htmlunit-snapshot", "Use the snapshot of htmlunit") { options[:use_htmlunit_snapshot] = true }
|
14
|
+
|
15
|
+
opts.on_tail("-h", "--help", "Show this message") { puts opts; exit }
|
16
|
+
end
|
17
|
+
parser.parse!
|
18
|
+
|
19
|
+
root = Pathname(__FILE__).expand_path.dirname.parent
|
20
|
+
lib = root + 'lib'
|
21
|
+
src = root + 'src'
|
22
|
+
|
23
|
+
case
|
24
|
+
when options[:use_htmlunit_snapshot]
|
25
|
+
require "fileutils"
|
26
|
+
|
27
|
+
FileUtils.mkdir_p("vendor/htmlunit")
|
28
|
+
Dir["vendor/htmlunit/*.jar"].each { |jar| File.unlink(jar) }
|
29
|
+
|
30
|
+
Dir.chdir("vendor") do
|
31
|
+
$stdout.print "Downloading latest snapshot... "
|
32
|
+
$stdout.flush
|
33
|
+
%x[curl -O http://build.canoo.com/htmlunit/artifacts/htmlunit-2.9-SNAPSHOT-with-dependencies.zip &> /dev/null]
|
34
|
+
puts "done"
|
35
|
+
|
36
|
+
$stdout.print "Extracting dependencies... "
|
37
|
+
$stdout.flush
|
38
|
+
%x[unzip -j -d htmlunit htmlunit-2.9-SNAPSHOT-with-dependencies.zip htmlunit-2.9-SNAPSHOT/lib htmlunit-2.9-SNAPSHOT/lib/* &> /dev/null]
|
39
|
+
puts "done"
|
40
|
+
|
41
|
+
File.unlink "htmlunit-2.9-SNAPSHOT-with-dependencies.zip"
|
42
|
+
end
|
43
|
+
|
44
|
+
$stdout.puts "="*40
|
45
|
+
$stdout.puts "The latest HtmlUnit snapshot has been extracted to vendor/htmlunit!"
|
46
|
+
when options[:interactive]
|
47
|
+
$:.unshift('vendor', lib, src)
|
48
|
+
require 'rubygems'
|
49
|
+
require 'akephalos'
|
50
|
+
require 'akephalos/console'
|
51
|
+
Akephalos::Console.start
|
52
|
+
else
|
53
|
+
unless port = ARGV[0]
|
54
|
+
puts parser.help
|
55
|
+
exit
|
56
|
+
end
|
57
|
+
|
58
|
+
if RUBY_PLATFORM == "java"
|
59
|
+
$:.unshift("vendor", lib, src)
|
60
|
+
require 'akephalos/server'
|
61
|
+
Akephalos::Server.start!(port)
|
62
|
+
else
|
63
|
+
require 'rubygems'
|
64
|
+
require 'jruby-jars'
|
65
|
+
|
66
|
+
jruby = JRubyJars.core_jar_path
|
67
|
+
jruby_stdlib = JRubyJars.stdlib_jar_path
|
68
|
+
|
69
|
+
java_args = [
|
70
|
+
"-Xmx128M",
|
71
|
+
"-cp", [JRubyJars.core_jar_path, JRubyJars.stdlib_jar_path].join(":"),
|
72
|
+
"org.jruby.Main"
|
73
|
+
]
|
74
|
+
ruby_args = [
|
75
|
+
"-Ku",
|
76
|
+
"-I", "vendor:#{lib}:#{src}",
|
77
|
+
"-r", "akephalos/server",
|
78
|
+
"-e", "Akephalos::Server.start!(#{port.inspect})"
|
79
|
+
]
|
80
|
+
|
81
|
+
# Bundler sets ENV["RUBYOPT"] to automatically load bundler/setup.rb, but
|
82
|
+
# since the akephalos server doesn't have any gem dependencies and is
|
83
|
+
# always executed with the same context, we clear RUBYOPT before running
|
84
|
+
# exec.
|
85
|
+
ENV["RUBYOPT"] = ""
|
86
|
+
exec("java", *(java_args + ruby_args))
|
87
|
+
end
|
88
|
+
end
|
data/lib/akephalos.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# **Akephalos** is a cross-platform Ruby interface for *HtmlUnit*, a headless
|
2
|
+
# browser for the Java platform.
|
3
|
+
#
|
4
|
+
# The only requirement is that a Java runtime is available.
|
5
|
+
#
|
6
|
+
require 'java' if RUBY_PLATFORM == 'java'
|
7
|
+
require 'pathname'
|
8
|
+
|
9
|
+
module Akephalos
|
10
|
+
BIN_DIR = Pathname(__FILE__).expand_path.dirname.parent + 'bin'
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'akephalos/client'
|
14
|
+
require 'capybara'
|
15
|
+
require 'akephalos/capybara'
|
16
|
+
|
17
|
+
Capybara.register_driver :akephalos do |app|
|
18
|
+
Capybara::Driver::Akephalos.new(app)
|
19
|
+
end
|
@@ -0,0 +1,316 @@
|
|
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
|
164
|
+
|
165
|
+
# @return [Client] an instance of Akephalos::Client
|
166
|
+
def self.driver
|
167
|
+
@driver ||= Akephalos::Client.new
|
168
|
+
end
|
169
|
+
|
170
|
+
def initialize(app)
|
171
|
+
@app = app
|
172
|
+
@rack_server = Capybara::Server.new(@app)
|
173
|
+
@rack_server.boot if Capybara.run_server
|
174
|
+
end
|
175
|
+
|
176
|
+
# Visit the given path in the browser.
|
177
|
+
#
|
178
|
+
# @param [String] path relative path to visit
|
179
|
+
def visit(path)
|
180
|
+
browser.visit(url(path))
|
181
|
+
end
|
182
|
+
|
183
|
+
# @return [String] the page's original source
|
184
|
+
def source
|
185
|
+
page.source
|
186
|
+
end
|
187
|
+
|
188
|
+
# @return [String] the page's modified source
|
189
|
+
# page.modified_source will return a string with
|
190
|
+
# html entities converted into the unicode equivalent
|
191
|
+
# but the string will be marked as ASCII-8BIT
|
192
|
+
# which causes conversion issues so we force the encoding
|
193
|
+
# to UTF-8 (ruby 1.9 only)
|
194
|
+
def body
|
195
|
+
body_source = page.modified_source
|
196
|
+
|
197
|
+
if body_source.respond_to?(:force_encoding)
|
198
|
+
body_source.force_encoding("UTF-8")
|
199
|
+
else
|
200
|
+
body_source
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# @return [Hash{String => String}] the page's response headers
|
205
|
+
def response_headers
|
206
|
+
page.response_headers
|
207
|
+
end
|
208
|
+
|
209
|
+
# @return [Integer] the response's status code
|
210
|
+
def status_code
|
211
|
+
page.status_code
|
212
|
+
end
|
213
|
+
|
214
|
+
# Execute the given block within the context of a specified frame.
|
215
|
+
#
|
216
|
+
# @param [String] frame_id the frame's id
|
217
|
+
# @raise [Capybara::ElementNotFound] if the frame is not found
|
218
|
+
def within_frame(frame_id, &block)
|
219
|
+
unless page.within_frame(frame_id, &block)
|
220
|
+
raise Capybara::ElementNotFound, "Unable to find frame with id '#{frame_id}'"
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# Clear all cookie session data.
|
225
|
+
# @deprecated This method is deprecated in Capybara's master branch. Use
|
226
|
+
# {#reset!} instead.
|
227
|
+
def cleanup!
|
228
|
+
reset!
|
229
|
+
end
|
230
|
+
|
231
|
+
# Clear all cookie session data.
|
232
|
+
def reset!
|
233
|
+
cookies.clear
|
234
|
+
end
|
235
|
+
|
236
|
+
# @return [String] the page's current URL
|
237
|
+
def current_url
|
238
|
+
page.current_url
|
239
|
+
end
|
240
|
+
|
241
|
+
# Search for nodes which match the given XPath selector.
|
242
|
+
#
|
243
|
+
# @param [String] selector XPath query
|
244
|
+
# @return [Array<Node>] the matched nodes
|
245
|
+
def find(selector)
|
246
|
+
nodes = []
|
247
|
+
page.find(selector).each { |node| nodes << Node.new(self, node) }
|
248
|
+
nodes
|
249
|
+
end
|
250
|
+
|
251
|
+
# Execute JavaScript against the current page, discarding any return value.
|
252
|
+
#
|
253
|
+
# @param [String] script the JavaScript to be executed
|
254
|
+
# @return [nil]
|
255
|
+
def execute_script(script)
|
256
|
+
page.execute_script script
|
257
|
+
end
|
258
|
+
|
259
|
+
# Execute JavaScript against the current page and return the results.
|
260
|
+
#
|
261
|
+
# @param [String] script the JavaScript to be executed
|
262
|
+
# @return the result of the JavaScript
|
263
|
+
def evaluate_script(script)
|
264
|
+
page.evaluate_script script
|
265
|
+
end
|
266
|
+
|
267
|
+
# @return the current page
|
268
|
+
def page
|
269
|
+
browser.page
|
270
|
+
end
|
271
|
+
|
272
|
+
# @return the browser
|
273
|
+
def browser
|
274
|
+
self.class.driver
|
275
|
+
end
|
276
|
+
|
277
|
+
# @return the session cookies
|
278
|
+
def cookies
|
279
|
+
browser.cookies
|
280
|
+
end
|
281
|
+
|
282
|
+
# @return [String] the current user agent string
|
283
|
+
def user_agent
|
284
|
+
browser.user_agent
|
285
|
+
end
|
286
|
+
|
287
|
+
# Set the User-Agent header for this session. If :default is given, the
|
288
|
+
# User-Agent header will be reset to the default browser's user agent.
|
289
|
+
#
|
290
|
+
# @param [:default] user_agent the default user agent
|
291
|
+
# @param [String] user_agent the user agent string to use
|
292
|
+
def user_agent=(user_agent)
|
293
|
+
browser.user_agent = user_agent
|
294
|
+
end
|
295
|
+
|
296
|
+
# Disable waiting in Capybara, since waiting is handled directly by
|
297
|
+
# Akephalos.
|
298
|
+
#
|
299
|
+
# @return [false]
|
300
|
+
def wait
|
301
|
+
false
|
302
|
+
end
|
303
|
+
|
304
|
+
private
|
305
|
+
|
306
|
+
# @param [String] path
|
307
|
+
# @return [String] the absolute URL for the given path
|
308
|
+
def url(path)
|
309
|
+
rack_server.url(path)
|
310
|
+
end
|
311
|
+
|
312
|
+
end
|
313
|
+
|
314
|
+
Capybara.register_driver :akephalos do |app|
|
315
|
+
Capybara::Driver::Akephalos.new(app)
|
316
|
+
end
|