akephalos 0.1.0-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 +55 -0
- data/bin/akephalos +87 -0
- data/lib/akephalos.rb +18 -0
- data/lib/akephalos/capybara.rb +161 -0
- data/lib/akephalos/client.rb +61 -0
- data/lib/akephalos/client/filter.rb +82 -0
- data/lib/akephalos/client/listener.rb +18 -0
- data/lib/akephalos/configuration.rb +22 -0
- data/lib/akephalos/console.rb +27 -0
- data/lib/akephalos/cucumber.rb +6 -0
- data/lib/akephalos/htmlunit.rb +27 -0
- data/lib/akephalos/htmlunit/ext/http_method.rb +18 -0
- data/lib/akephalos/node.rb +73 -0
- data/lib/akephalos/page.rb +34 -0
- data/lib/akephalos/remote_client.rb +57 -0
- data/lib/akephalos/server.rb +25 -0
- data/lib/akephalos/version.rb +3 -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-httpclient-3.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.7.jar +0 -0
- data/src/htmlunit/htmlunit-core-js-2.7.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 +134 -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,55 @@
|
|
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.com),
|
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
|
+
* [Source code](http://github.com/bernerdschaefer/akephalos) and
|
54
|
+
[issues](http://github.com/bernerdschaefer/akephalos/issues) are hosted on
|
55
|
+
github.
|
data/bin/akephalos
ADDED
@@ -0,0 +1,87 @@
|
|
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] <socket_file>"
|
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.8-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.8-SNAPSHOT-with-dependencies.zip htmlunit-2.8-SNAPSHOT/lib htmlunit-2.8-SNAPSHOT/lib/* &> /dev/null]
|
39
|
+
puts "done"
|
40
|
+
|
41
|
+
File.unlink "htmlunit-2.8-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 socket_file = 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!(socket_file)
|
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
|
+
"-I", "vendor:#{lib}:#{src}",
|
76
|
+
"-r", "akephalos/server",
|
77
|
+
"-e", "Akephalos::Server.start!(#{socket_file.inspect})"
|
78
|
+
]
|
79
|
+
|
80
|
+
# Bundler sets ENV["RUBYOPT"] to automatically load bundler/setup.rb, but
|
81
|
+
# since the akephalos server doesn't have any gem dependencies and is
|
82
|
+
# always executed with the same context, we clear RUBYOPT before running
|
83
|
+
# exec.
|
84
|
+
ENV["RUBYOPT"] = ""
|
85
|
+
exec("java", *(java_args + ruby_args))
|
86
|
+
end
|
87
|
+
end
|
data/lib/akephalos.rb
ADDED
@@ -0,0 +1,18 @@
|
|
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 'capybara'
|
8
|
+
|
9
|
+
module Akephalos
|
10
|
+
BIN_DIR = Pathname(__FILE__).expand_path.dirname.parent + 'bin'
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'akephalos/client'
|
14
|
+
require 'akephalos/capybara'
|
15
|
+
|
16
|
+
if Object.const_defined? :Cucumber
|
17
|
+
require 'akephalos/cucumber'
|
18
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
class Capybara::Driver::Akephalos < Capybara::Driver::Base
|
2
|
+
|
3
|
+
class Node < Capybara::Node
|
4
|
+
|
5
|
+
def [](name)
|
6
|
+
name = name.to_s
|
7
|
+
case name
|
8
|
+
when 'checked'
|
9
|
+
node.checked?
|
10
|
+
else
|
11
|
+
node[name.to_s]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def text
|
16
|
+
node.text
|
17
|
+
end
|
18
|
+
|
19
|
+
def value
|
20
|
+
if tag_name == "select" && self[:multiple]
|
21
|
+
node.selected_options.map { |option| option.text }
|
22
|
+
elsif tag_name == "select"
|
23
|
+
selected_option = node.selected_options.first
|
24
|
+
selected_option ? selected_option.text : nil
|
25
|
+
else
|
26
|
+
self[:value]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def set(value)
|
31
|
+
if tag_name == 'textarea'
|
32
|
+
node.value = value.to_s
|
33
|
+
elsif tag_name == 'input' and type == 'radio'
|
34
|
+
click
|
35
|
+
elsif tag_name == 'input' and type == 'checkbox'
|
36
|
+
if value != self['checked']
|
37
|
+
click
|
38
|
+
end
|
39
|
+
elsif tag_name == 'input'
|
40
|
+
node.value = value.to_s
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def select(option)
|
45
|
+
result = node.select_option(option)
|
46
|
+
|
47
|
+
if result == nil
|
48
|
+
options = node.options.map(&:text).join(", ")
|
49
|
+
raise Capybara::OptionNotFound, "No such option '#{option}' in this select box. Available options: #{options}"
|
50
|
+
else
|
51
|
+
result
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def unselect(option)
|
56
|
+
unless self[:multiple]
|
57
|
+
raise Capybara::UnselectNotAllowed, "Cannot unselect option '#{option}' from single select box."
|
58
|
+
end
|
59
|
+
|
60
|
+
result = node.unselect_option(option)
|
61
|
+
|
62
|
+
if result == nil
|
63
|
+
options = node.options.map(&:text).join(", ")
|
64
|
+
raise Capybara::OptionNotFound, "No such option '#{option}' in this select box. Available options: #{options}"
|
65
|
+
else
|
66
|
+
result
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def trigger(event)
|
71
|
+
node.fire_event(event.to_s)
|
72
|
+
end
|
73
|
+
|
74
|
+
def tag_name
|
75
|
+
node.tag_name
|
76
|
+
end
|
77
|
+
|
78
|
+
def visible?
|
79
|
+
node.visible?
|
80
|
+
end
|
81
|
+
|
82
|
+
def drag_to(element)
|
83
|
+
trigger('mousedown')
|
84
|
+
element.trigger('mousemove')
|
85
|
+
element.trigger('mouseup')
|
86
|
+
end
|
87
|
+
|
88
|
+
def click
|
89
|
+
node.click
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def all_unfiltered(selector)
|
95
|
+
nodes = []
|
96
|
+
node.find(selector).each { |node| nodes << Node.new(driver, node) }
|
97
|
+
nodes
|
98
|
+
end
|
99
|
+
|
100
|
+
def type
|
101
|
+
node[:type]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
attr_reader :app, :rack_server
|
106
|
+
|
107
|
+
def self.driver
|
108
|
+
@driver ||= Akephalos::Client.new
|
109
|
+
end
|
110
|
+
|
111
|
+
def initialize(app)
|
112
|
+
@app = app
|
113
|
+
@rack_server = Capybara::Server.new(@app)
|
114
|
+
@rack_server.boot if Capybara.run_server
|
115
|
+
end
|
116
|
+
|
117
|
+
def visit(path)
|
118
|
+
browser.visit(url(path))
|
119
|
+
end
|
120
|
+
|
121
|
+
def source
|
122
|
+
page.source
|
123
|
+
end
|
124
|
+
|
125
|
+
def body
|
126
|
+
page.modified_source
|
127
|
+
end
|
128
|
+
|
129
|
+
def current_url
|
130
|
+
page.current_url
|
131
|
+
end
|
132
|
+
|
133
|
+
def find(selector)
|
134
|
+
nodes = []
|
135
|
+
page.find(selector).each { |node| nodes << Node.new(self, node) }
|
136
|
+
nodes
|
137
|
+
end
|
138
|
+
|
139
|
+
def evaluate_script(script)
|
140
|
+
page.execute_script script
|
141
|
+
end
|
142
|
+
|
143
|
+
def page
|
144
|
+
browser.page
|
145
|
+
end
|
146
|
+
|
147
|
+
def browser
|
148
|
+
self.class.driver
|
149
|
+
end
|
150
|
+
|
151
|
+
def wait
|
152
|
+
false
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
def url(path)
|
158
|
+
rack_server.url(path)
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'akephalos/configuration'
|
2
|
+
|
3
|
+
if RUBY_PLATFORM != "java"
|
4
|
+
require 'akephalos/remote_client'
|
5
|
+
Akephalos::Client = Akephalos::RemoteClient
|
6
|
+
else
|
7
|
+
require 'akephalos/htmlunit'
|
8
|
+
require 'akephalos/htmlunit/ext/http_method'
|
9
|
+
|
10
|
+
require 'akephalos/page'
|
11
|
+
require 'akephalos/node'
|
12
|
+
|
13
|
+
require 'akephalos/client/filter'
|
14
|
+
require 'akephalos/client/listener'
|
15
|
+
|
16
|
+
module Akephalos
|
17
|
+
class Client
|
18
|
+
java_import 'com.gargoylesoftware.htmlunit.NicelyResynchronizingAjaxController'
|
19
|
+
java_import 'com.gargoylesoftware.htmlunit.SilentCssErrorHandler'
|
20
|
+
|
21
|
+
attr_reader :page
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@_client = java.util.concurrent.FutureTask.new do
|
25
|
+
client = WebClient.new
|
26
|
+
|
27
|
+
Filter.new(client)
|
28
|
+
client.addWebWindowListener(Listener.new(self))
|
29
|
+
client.setAjaxController(NicelyResynchronizingAjaxController.new)
|
30
|
+
client.setCssErrorHandler(SilentCssErrorHandler.new)
|
31
|
+
|
32
|
+
client
|
33
|
+
end
|
34
|
+
Thread.new { @_client.run }
|
35
|
+
end
|
36
|
+
|
37
|
+
def configuration=(config)
|
38
|
+
Akephalos.configuration = config
|
39
|
+
end
|
40
|
+
|
41
|
+
def visit(url)
|
42
|
+
client.getPage(url)
|
43
|
+
page
|
44
|
+
end
|
45
|
+
|
46
|
+
def page=(_page)
|
47
|
+
if @page != _page
|
48
|
+
@page = Page.new(_page)
|
49
|
+
end
|
50
|
+
@page
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
def client
|
55
|
+
@client ||= @_client.get.tap do |client|
|
56
|
+
client.getCurrentWindow.getHistory.ignoreNewPages_.set(true)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Akephalos
|
2
|
+
class Client
|
3
|
+
class Filter < WebConnectionWrapper
|
4
|
+
java_import 'com.gargoylesoftware.htmlunit.util.NameValuePair'
|
5
|
+
java_import 'com.gargoylesoftware.htmlunit.WebResponseData'
|
6
|
+
java_import 'com.gargoylesoftware.htmlunit.WebResponseImpl'
|
7
|
+
|
8
|
+
def filter(request)
|
9
|
+
if filter = Akephalos.filters.find { |filter| request.http_method === filter[:method] && request.url.to_s =~ filter[:filter] }
|
10
|
+
start_time = Time.now
|
11
|
+
headers = filter[:headers].map { |name, value| NameValuePair.new(name.to_s, value.to_s) }
|
12
|
+
response = WebResponseData.new(filter[:body].to_s.to_java_bytes, filter[:status], HTTP_STATUS_CODES.fetch(filter[:status], "Unknown"), headers)
|
13
|
+
WebResponseImpl.new(response, request, Time.now - start_time)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def getResponse(request)
|
18
|
+
filter(request) || super
|
19
|
+
end
|
20
|
+
|
21
|
+
HTTP_STATUS_CODES = {
|
22
|
+
100 => "Continue",
|
23
|
+
101 => "Switching Protocols",
|
24
|
+
102 => "Processing",
|
25
|
+
200 => "OK",
|
26
|
+
201 => "Created",
|
27
|
+
202 => "Accepted",
|
28
|
+
203 => "Non-Authoritative Information",
|
29
|
+
204 => "No Content",
|
30
|
+
205 => "Reset Content",
|
31
|
+
206 => "Partial Content",
|
32
|
+
207 => "Multi-Status",
|
33
|
+
300 => "Multiple Choices",
|
34
|
+
301 => "Moved Permanently",
|
35
|
+
302 => "Found",
|
36
|
+
303 => "See Other",
|
37
|
+
304 => "Not Modified",
|
38
|
+
305 => "Use Proxy",
|
39
|
+
306 => "Switch Proxy",
|
40
|
+
307 => "Temporary Redirect",
|
41
|
+
400 => "Bad Request",
|
42
|
+
401 => "Unauthorized",
|
43
|
+
402 => "Payment Required",
|
44
|
+
403 => "Forbidden",
|
45
|
+
404 => "Not Found",
|
46
|
+
405 => "Method Not Allowed",
|
47
|
+
406 => "Not Acceptable",
|
48
|
+
407 => "Proxy Authentication Required",
|
49
|
+
408 => "Request Timeout",
|
50
|
+
409 => "Conflict",
|
51
|
+
410 => "Gone",
|
52
|
+
411 => "Length Required",
|
53
|
+
412 => "Precondition Failed",
|
54
|
+
413 => "Request Entity Too Large",
|
55
|
+
414 => "Request-URI Too Long",
|
56
|
+
415 => "Unsupported Media Type",
|
57
|
+
416 => "Requested Range Not Satisfiable",
|
58
|
+
417 => "Expectation Failed",
|
59
|
+
418 => "I'm a teapot",
|
60
|
+
421 => "There are too many connections from your internet address",
|
61
|
+
422 => "Unprocessable Entity",
|
62
|
+
423 => "Locked",
|
63
|
+
424 => "Failed Dependency",
|
64
|
+
425 => "Unordered Collection",
|
65
|
+
426 => "Upgrade Required",
|
66
|
+
449 => "Retry With",
|
67
|
+
450 => "Blocked by Windows Parental Controls",
|
68
|
+
500 => "Internal Server Error",
|
69
|
+
501 => "Not Implemented",
|
70
|
+
502 => "Bad Gateway",
|
71
|
+
503 => "Service Unavailable",
|
72
|
+
504 => "Gateway Timeout",
|
73
|
+
505 => "HTTP Version Not Supported",
|
74
|
+
506 => "Variant Also Negotiates",
|
75
|
+
507 => "Insufficient Storage",
|
76
|
+
509 => "Bandwidth Limit Exceeded",
|
77
|
+
510 => "Not Extended",
|
78
|
+
530 => "User access denied"
|
79
|
+
}.freeze
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Akephalos
|
2
|
+
class Client
|
3
|
+
class Listener
|
4
|
+
include com.gargoylesoftware.htmlunit.WebWindowListener
|
5
|
+
|
6
|
+
def initialize(client)
|
7
|
+
@client = client
|
8
|
+
end
|
9
|
+
|
10
|
+
def webWindowClosed(event)
|
11
|
+
end
|
12
|
+
|
13
|
+
def webWindowContentChanged(event)
|
14
|
+
@client.page = event.getNewPage
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Akephalos
|
2
|
+
def self.configuration
|
3
|
+
@configuration ||= {}
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.configuration=(config)
|
7
|
+
@configuration = config
|
8
|
+
end
|
9
|
+
|
10
|
+
module Filters
|
11
|
+
def filters
|
12
|
+
configuration[:filters] ||= []
|
13
|
+
end
|
14
|
+
|
15
|
+
def filter(method, regex, options = {})
|
16
|
+
regex = Regexp.new(Regexp.escape(regex)) if regex.is_a?(String)
|
17
|
+
filters << {:method => method, :filter => regex, :status => 200, :body => "", :headers => {}}.merge!(options)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
extend Filters
|
22
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
def session
|
2
|
+
Capybara.app_host = "http://localhost:3000"
|
3
|
+
@session ||= Capybara::Session.new(:Akephalos)
|
4
|
+
end
|
5
|
+
alias page session
|
6
|
+
|
7
|
+
module Akephalos
|
8
|
+
class Console
|
9
|
+
|
10
|
+
def self.start
|
11
|
+
require 'irb'
|
12
|
+
|
13
|
+
begin
|
14
|
+
require 'irb/completion'
|
15
|
+
rescue Exception
|
16
|
+
# No readline available, proceed anyway.
|
17
|
+
end
|
18
|
+
|
19
|
+
if ::File.exists? ".irbrc"
|
20
|
+
ENV['IRBRC'] = ".irbrc"
|
21
|
+
end
|
22
|
+
|
23
|
+
IRB.start
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "pathname"
|
2
|
+
require "java"
|
3
|
+
|
4
|
+
dependency_directory = $:.detect { |path| Dir[File.join(path, 'htmlunit/htmlunit-*.jar')].any? }
|
5
|
+
|
6
|
+
raise "Could not find htmlunit/htmlunit-VERSION.jar in load path:\n [ #{$:.join(",\n ")}\n ]" unless dependency_directory
|
7
|
+
|
8
|
+
Dir[File.join(dependency_directory, "htmlunit/*.jar")].each do |jar|
|
9
|
+
require jar
|
10
|
+
end
|
11
|
+
|
12
|
+
java.lang.System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog")
|
13
|
+
java.lang.System.setProperty("org.apache.commons.logging.simplelog.defaultlog", "fatal")
|
14
|
+
java.lang.System.setProperty("org.apache.commons.logging.simplelog.showdatetime", "true")
|
15
|
+
|
16
|
+
java_import "com.gargoylesoftware.htmlunit.WebClient"
|
17
|
+
java_import "com.gargoylesoftware.htmlunit.util.WebConnectionWrapper"
|
18
|
+
java_import 'com.gargoylesoftware.htmlunit.HttpMethod'
|
19
|
+
|
20
|
+
|
21
|
+
# Disable history tracking
|
22
|
+
com.gargoylesoftware.htmlunit.History.field_reader :ignoreNewPages_
|
23
|
+
|
24
|
+
# Run in Firefox compatibility mode
|
25
|
+
com.gargoylesoftware.htmlunit.BrowserVersion.setDefault(
|
26
|
+
com.gargoylesoftware.htmlunit.BrowserVersion::FIREFOX_3
|
27
|
+
)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class HttpMethod
|
2
|
+
def ===(other)
|
3
|
+
case other
|
4
|
+
when HttpMethod
|
5
|
+
super
|
6
|
+
when :any
|
7
|
+
true
|
8
|
+
when :get
|
9
|
+
self == self.class::GET
|
10
|
+
when :post
|
11
|
+
self == self.class::POST
|
12
|
+
when :put
|
13
|
+
self == self.class::PUT
|
14
|
+
when :delete
|
15
|
+
self == self.class::DELETE
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Akephalos
|
2
|
+
class Node
|
3
|
+
def initialize(node)
|
4
|
+
@nodes = []
|
5
|
+
@_node = node
|
6
|
+
end
|
7
|
+
|
8
|
+
def checked?
|
9
|
+
@_node.isChecked
|
10
|
+
end
|
11
|
+
|
12
|
+
def text
|
13
|
+
@_node.asText
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](name)
|
17
|
+
@_node.hasAttribute(name.to_s) ? @_node.getAttribute(name.to_s) : nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def value=(value)
|
21
|
+
case tag_name
|
22
|
+
when "textarea"
|
23
|
+
@_node.setText(value)
|
24
|
+
when "input"
|
25
|
+
@_node.setValueAttribute(value)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def select_option(option)
|
30
|
+
opt = @_node.getOptions.detect { |o| o.asText == option }
|
31
|
+
|
32
|
+
opt && opt.setSelected(true)
|
33
|
+
end
|
34
|
+
|
35
|
+
def unselect_option(option)
|
36
|
+
opt = @_node.getOptions.detect { |o| o.asText == option }
|
37
|
+
|
38
|
+
opt && opt.setSelected(false)
|
39
|
+
end
|
40
|
+
|
41
|
+
def options
|
42
|
+
@_node.getOptions.map { |node| Node.new(node) }
|
43
|
+
end
|
44
|
+
|
45
|
+
def selected_options
|
46
|
+
@_node.getSelectedOptions.map { |node| Node.new(node) }
|
47
|
+
end
|
48
|
+
|
49
|
+
def fire_event(name)
|
50
|
+
@_node.fireEvent(name)
|
51
|
+
end
|
52
|
+
|
53
|
+
def tag_name
|
54
|
+
@_node.getNodeName
|
55
|
+
end
|
56
|
+
|
57
|
+
def visible?
|
58
|
+
@_node.isDisplayed
|
59
|
+
end
|
60
|
+
|
61
|
+
def click
|
62
|
+
@_node.click
|
63
|
+
@_node.getPage.getEnclosingWindow.getJobManager.waitForJobs(1000)
|
64
|
+
@_node.getPage.getEnclosingWindow.getJobManager.waitForJobsStartingBefore(1000)
|
65
|
+
end
|
66
|
+
|
67
|
+
def find(selector)
|
68
|
+
nodes = @_node.getByXPath(selector).map { |node| Node.new(node) }
|
69
|
+
@nodes << nodes
|
70
|
+
nodes
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Akephalos
|
2
|
+
class Page
|
3
|
+
def initialize(page)
|
4
|
+
@nodes = []
|
5
|
+
@_page = page
|
6
|
+
end
|
7
|
+
|
8
|
+
def find(selector)
|
9
|
+
nodes = @_page.getByXPath(selector).map { |node| Node.new(node) }
|
10
|
+
@nodes << nodes
|
11
|
+
nodes
|
12
|
+
end
|
13
|
+
|
14
|
+
def modified_source
|
15
|
+
@_page.asXml
|
16
|
+
end
|
17
|
+
|
18
|
+
def source
|
19
|
+
@_page.getWebResponse.getContentAsString
|
20
|
+
end
|
21
|
+
|
22
|
+
def current_url
|
23
|
+
@_page.getWebResponse.getRequestSettings.getUrl.toString
|
24
|
+
end
|
25
|
+
|
26
|
+
def execute_script(script)
|
27
|
+
@_page.executeJavaScript(script).getJavaScriptResult
|
28
|
+
end
|
29
|
+
|
30
|
+
def ==(other)
|
31
|
+
@_page == other
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'drb/drb'
|
2
|
+
|
3
|
+
# We need to define our own NativeException class for the cases when a native
|
4
|
+
# exception is raised by the JRuby DRb server.
|
5
|
+
class NativeException < StandardError; end # :nodoc:
|
6
|
+
|
7
|
+
module Akephalos
|
8
|
+
##
|
9
|
+
# The +RemoteClient+ class provides an interface to an +Akephalos::Client+
|
10
|
+
# isntance on a remote DRb server.
|
11
|
+
#
|
12
|
+
# == Usage
|
13
|
+
# client = Akephalos::RemoteClient.new
|
14
|
+
# client.visit "http://www.oinopa.com"
|
15
|
+
# client.page.source # => "<!DOCTYPE html PUBLIC..."
|
16
|
+
class RemoteClient
|
17
|
+
@socket_file = "/tmp/akephalos.#{Process.pid}.sock"
|
18
|
+
|
19
|
+
##
|
20
|
+
# Starts a remote akephalos server and returns the remote Akephalos::Client
|
21
|
+
# instance.
|
22
|
+
def self.new
|
23
|
+
start!
|
24
|
+
DRb.start_service
|
25
|
+
client = DRbObject.new_with_uri("drbunix://#{@socket_file}")
|
26
|
+
# We want to share our local configuration with the remote server
|
27
|
+
# process, so we share an undumped version of our configuration. This
|
28
|
+
# lets us continue to make changes locally and have them reflected in the
|
29
|
+
# remote process.
|
30
|
+
client.configuration = Akephalos.configuration.extend(DRbUndumped)
|
31
|
+
client
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Start a remote server process, returning when it is available for use.
|
36
|
+
def self.start!
|
37
|
+
remote_client = fork do
|
38
|
+
exec("#{Akephalos::BIN_DIR + 'akephalos'} #{@socket_file}")
|
39
|
+
end
|
40
|
+
|
41
|
+
# Set up a monitor thread to detect if the forked server exits
|
42
|
+
# prematurely.
|
43
|
+
server_monitor = Thread.new { Thread.current[:exited] = Process.wait }
|
44
|
+
|
45
|
+
# Wait for the server to be accessible on the socket we specified.
|
46
|
+
until File.exists?(@socket_file)
|
47
|
+
exit!(1) if server_monitor[:exited]
|
48
|
+
sleep 1
|
49
|
+
end
|
50
|
+
server_monitor.kill
|
51
|
+
|
52
|
+
# Ensure that the remote server shuts down gracefully when we are
|
53
|
+
# finished.
|
54
|
+
at_exit { Process.kill(:INT, remote_client); File.unlink(@socket_file) }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# This file runs a JRuby DRb server, and is run by `akephalos --server`.
|
2
|
+
require "pathname"
|
3
|
+
require "drb/drb"
|
4
|
+
require "akephalos/client"
|
5
|
+
|
6
|
+
# In ruby-1.8.7 and later, the message for a NameError exception is lazily
|
7
|
+
# evaluated. There are, however, different implementations of this between ruby
|
8
|
+
# and jrby, so we realize these messages when sending over DRb.
|
9
|
+
class NameError::Message
|
10
|
+
def _dump
|
11
|
+
to_s
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
[Akephalos::Page, Akephalos::Node].each { |klass| klass.send(:include, DRbUndumped) }
|
16
|
+
|
17
|
+
module Akephalos
|
18
|
+
class Server
|
19
|
+
def self.start!(socket_file)
|
20
|
+
client = Client.new
|
21
|
+
DRb.start_service("drbunix://#{socket_file}", client)
|
22
|
+
DRb.thread.join
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
metadata
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: akephalos
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: java
|
11
|
+
authors:
|
12
|
+
- Bernerd Schaefer
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-07-26 00:00:00 -05:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: capybara
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
- 3
|
30
|
+
- 8
|
31
|
+
version: 0.3.8
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: sinatra
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 0
|
43
|
+
version: "0"
|
44
|
+
type: :development
|
45
|
+
version_requirements: *id002
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
prerelease: false
|
49
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
segments:
|
54
|
+
- 1
|
55
|
+
- 30
|
56
|
+
version: "1.30"
|
57
|
+
type: :development
|
58
|
+
version_requirements: *id003
|
59
|
+
description: Headless Browser for Integration Testing with Capybara
|
60
|
+
email: bj.schaefer@gmail.com
|
61
|
+
executables:
|
62
|
+
- akephalos
|
63
|
+
extensions: []
|
64
|
+
|
65
|
+
extra_rdoc_files: []
|
66
|
+
|
67
|
+
files:
|
68
|
+
- lib/akephalos/capybara.rb
|
69
|
+
- lib/akephalos/client/filter.rb
|
70
|
+
- lib/akephalos/client/listener.rb
|
71
|
+
- lib/akephalos/client.rb
|
72
|
+
- lib/akephalos/configuration.rb
|
73
|
+
- lib/akephalos/console.rb
|
74
|
+
- lib/akephalos/cucumber.rb
|
75
|
+
- lib/akephalos/htmlunit/ext/http_method.rb
|
76
|
+
- lib/akephalos/htmlunit.rb
|
77
|
+
- lib/akephalos/node.rb
|
78
|
+
- lib/akephalos/page.rb
|
79
|
+
- lib/akephalos/remote_client.rb
|
80
|
+
- lib/akephalos/server.rb
|
81
|
+
- lib/akephalos/version.rb
|
82
|
+
- lib/akephalos.rb
|
83
|
+
- src/htmlunit/commons-codec-1.4.jar
|
84
|
+
- src/htmlunit/commons-collections-3.2.1.jar
|
85
|
+
- src/htmlunit/commons-httpclient-3.1.jar
|
86
|
+
- src/htmlunit/commons-io-1.4.jar
|
87
|
+
- src/htmlunit/commons-lang-2.4.jar
|
88
|
+
- src/htmlunit/commons-logging-1.1.1.jar
|
89
|
+
- src/htmlunit/cssparser-0.9.5.jar
|
90
|
+
- src/htmlunit/htmlunit-2.7.jar
|
91
|
+
- src/htmlunit/htmlunit-core-js-2.7.jar
|
92
|
+
- src/htmlunit/nekohtml-1.9.14.jar
|
93
|
+
- src/htmlunit/sac-1.3.jar
|
94
|
+
- src/htmlunit/serializer-2.7.1.jar
|
95
|
+
- src/htmlunit/xalan-2.7.1.jar
|
96
|
+
- src/htmlunit/xercesImpl-2.9.1.jar
|
97
|
+
- src/htmlunit/xml-apis-1.3.04.jar
|
98
|
+
- README.md
|
99
|
+
- MIT_LICENSE
|
100
|
+
has_rdoc: true
|
101
|
+
homepage: http://bernerdschaefer.github.com/akephalos
|
102
|
+
licenses: []
|
103
|
+
|
104
|
+
post_install_message:
|
105
|
+
rdoc_options: []
|
106
|
+
|
107
|
+
require_paths:
|
108
|
+
- - lib
|
109
|
+
- src
|
110
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
111
|
+
requirements:
|
112
|
+
- - ">="
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
segments:
|
115
|
+
- 0
|
116
|
+
version: "0"
|
117
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
segments:
|
122
|
+
- 1
|
123
|
+
- 3
|
124
|
+
- 6
|
125
|
+
version: 1.3.6
|
126
|
+
requirements: []
|
127
|
+
|
128
|
+
rubyforge_project: akephalos
|
129
|
+
rubygems_version: 1.3.6
|
130
|
+
signing_key:
|
131
|
+
specification_version: 3
|
132
|
+
summary: Headless Browser for Integration Testing with Capybara
|
133
|
+
test_files: []
|
134
|
+
|