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.
@@ -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.
@@ -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.
@@ -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
@@ -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,6 @@
1
+ require 'capybara/cucumber'
2
+
3
+ Before('@akephalos') do
4
+ Capybara.current_driver = :akephalos
5
+ end
6
+
@@ -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
@@ -0,0 +1,3 @@
1
+ module Akephalos #:nodoc
2
+ VERSION = "0.1.0"
3
+ end
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
+