akephalos 0.1.0-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+