akephalos 0.2.4-java → 0.2.5-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/README.md CHANGED
@@ -38,6 +38,36 @@ Here's some sample RSpec code:
38
38
  end
39
39
  end
40
40
 
41
+ ## Configuration
42
+
43
+ There are now a few configuration options available through Capybara's new
44
+ `register_driver` API.
45
+
46
+ ### Using a different browser
47
+
48
+ HtmlUnit supports a few browser implementations, and you can choose which
49
+ browser you would like to use through Akephalos. By default, Akephalos uses
50
+ Firefox 3.6.
51
+
52
+ Capybara.register_driver :akephalos do |app|
53
+ # available options:
54
+ # :ie6, :ie7, :ie8, :firefox_3, :firefox_3_6
55
+ Capybara::Driver::Akephalos.new(app, :browser => :ie8)
56
+ end
57
+
58
+ ### Ignoring javascript errors
59
+
60
+ By default HtmlUnit (and Akephalos) will raise an exception when an error
61
+ is encountered in javascript files. This is generally desireable, except
62
+ that certain libraries aren't supported by HtmlUnit. If possible, it's
63
+ best to keep the default behavior, and use Filters (see below) to mock
64
+ offending libraries. If needed, however, you can configure Akephalos to
65
+ ignore javascript errors.
66
+
67
+ Capybara.register_driver :akephalos do |app|
68
+ Capybara::Driver::Akephalos.new(app, :validate_scripts => false)
69
+ end
70
+
41
71
  ## More
42
72
 
43
73
  * [bin/akephalos](http://bernerdschaefer.github.com/akephalos/akephalos-bin.html)
@@ -7,7 +7,7 @@ require "optparse"
7
7
  options = { :interactive => false }
8
8
 
9
9
  parser = OptionParser.new do |opts|
10
- opts.banner = "Usage: akephalos [--interactive, --use-htmlunit-snapshot] | [--server] <socket_file>"
10
+ opts.banner = "Usage: akephalos [--interactive, --use-htmlunit-snapshot] | [--server] <port>"
11
11
  opts.on("-s", "--server", "Run in server mode (default)")
12
12
  opts.on("-i", "--interactive", "Run in interactive mode") { options[:interactive] = true }
13
13
  opts.on("--use-htmlunit-snapshot", "Use the snapshot of htmlunit") { options[:use_htmlunit_snapshot] = true }
@@ -30,15 +30,15 @@ when options[:use_htmlunit_snapshot]
30
30
  Dir.chdir("vendor") do
31
31
  $stdout.print "Downloading latest snapshot... "
32
32
  $stdout.flush
33
- %x[curl -O http://build.canoo.com/htmlunit/artifacts/htmlunit-2.8-SNAPSHOT-with-dependencies.zip &> /dev/null]
33
+ %x[curl -O http://build.canoo.com/htmlunit/artifacts/htmlunit-2.9-SNAPSHOT-with-dependencies.zip &> /dev/null]
34
34
  puts "done"
35
35
 
36
36
  $stdout.print "Extracting dependencies... "
37
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]
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
39
  puts "done"
40
40
 
41
- File.unlink "htmlunit-2.8-SNAPSHOT-with-dependencies.zip"
41
+ File.unlink "htmlunit-2.9-SNAPSHOT-with-dependencies.zip"
42
42
  end
43
43
 
44
44
  $stdout.puts "="*40
@@ -50,7 +50,7 @@ when options[:interactive]
50
50
  require 'akephalos/console'
51
51
  Akephalos::Console.start
52
52
  else
53
- unless socket_file = ARGV[0]
53
+ unless port = ARGV[0]
54
54
  puts parser.help
55
55
  exit
56
56
  end
@@ -58,7 +58,7 @@ else
58
58
  if RUBY_PLATFORM == "java"
59
59
  $:.unshift("vendor", lib, src)
60
60
  require 'akephalos/server'
61
- Akephalos::Server.start!(socket_file)
61
+ Akephalos::Server.start!(port)
62
62
  else
63
63
  require 'rubygems'
64
64
  require 'jruby-jars'
@@ -71,10 +71,11 @@ else
71
71
  "-cp", [JRubyJars.core_jar_path, JRubyJars.stdlib_jar_path].join(":"),
72
72
  "org.jruby.Main"
73
73
  ]
74
- ruby_args = [
74
+ ruby_args = [
75
+ "-Ku",
75
76
  "-I", "vendor:#{lib}:#{src}",
76
77
  "-r", "akephalos/server",
77
- "-e", "Akephalos::Server.start!(#{socket_file.inspect})"
78
+ "-e", "Akephalos::Server.start!(#{port.inspect})"
78
79
  ]
79
80
 
80
81
  # Bundler sets ENV["RUBYOPT"] to automatically load bundler/setup.rb, but
@@ -5,15 +5,15 @@
5
5
  #
6
6
  require 'java' if RUBY_PLATFORM == 'java'
7
7
  require 'pathname'
8
- require 'capybara'
9
8
 
10
9
  module Akephalos
11
10
  BIN_DIR = Pathname(__FILE__).expand_path.dirname.parent + 'bin'
12
11
  end
13
12
 
14
13
  require 'akephalos/client'
14
+ require 'capybara'
15
15
  require 'akephalos/capybara'
16
16
 
17
- if Object.const_defined? :Cucumber
18
- require 'akephalos/cucumber'
17
+ Capybara.register_driver :akephalos do |app|
18
+ Capybara::Driver::Akephalos.new(app)
19
19
  end
@@ -6,8 +6,14 @@
6
6
  # directly or over DRb.
7
7
  class Capybara::Driver::Akephalos < Capybara::Driver::Base
8
8
 
9
- # Akephalos-specific implementation for Capybara's Node class.
10
- class Node < Capybara::Node
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
11
17
 
12
18
  # @api capybara
13
19
  # @param [String] name attribute name
@@ -16,22 +22,16 @@ class Capybara::Driver::Akephalos < Capybara::Driver::Base
16
22
  name = name.to_s
17
23
  case name
18
24
  when 'checked'
19
- node.checked?
25
+ native.checked?
20
26
  else
21
- node[name.to_s]
27
+ native[name.to_s]
22
28
  end
23
29
  end
24
30
 
25
31
  # @api capybara
26
- # @return [String] the inner text of the node
27
- def text
28
- node.text
29
- end
30
-
31
- # @api capybara
32
- # @return [String] the form element's value
32
+ # @return [String, Array<String>] the form element's value
33
33
  def value
34
- node.value
34
+ native.value
35
35
  end
36
36
 
37
37
  # Set the form element's value.
@@ -40,7 +40,7 @@ class Capybara::Driver::Akephalos < Capybara::Driver::Base
40
40
  # @param [String] value the form element's new value
41
41
  def set(value)
42
42
  if tag_name == 'textarea'
43
- node.value = value.to_s
43
+ native.value = value.to_s
44
44
  elsif tag_name == 'input' and type == 'radio'
45
45
  click
46
46
  elsif tag_name == 'input' and type == 'checkbox'
@@ -48,77 +48,93 @@ class Capybara::Driver::Akephalos < Capybara::Driver::Base
48
48
  click
49
49
  end
50
50
  elsif tag_name == 'input'
51
- node.value = value.to_s
51
+ native.value = value.to_s
52
52
  end
53
53
  end
54
54
 
55
- # Select an option from a select box.
56
- #
57
55
  # @api capybara
58
- # @param [String] option the option to select
59
- def select(option)
60
- result = node.select_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
56
+ def select_option
57
+ native.click
68
58
  end
69
59
 
70
60
  # Unselect an option from a select box.
71
61
  #
72
62
  # @api capybara
73
- # @param [String] option the option to unselect
74
- def unselect(option)
75
- unless self[:multiple]
76
- raise Capybara::UnselectNotAllowed, "Cannot unselect option '#{option}' from single select box."
63
+ def unselect_option
64
+ unless select_node.multiple_select?
65
+ raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box."
77
66
  end
78
67
 
79
- result = node.unselect_option(option)
68
+ native.unselect
69
+ end
80
70
 
81
- if result == nil
82
- options = node.options.map(&:text).join(", ")
83
- raise Capybara::OptionNotFound, "No such option '#{option}' in this select box. Available options: #{options}"
84
- else
85
- result
86
- end
71
+ # Click the element.
72
+ def click
73
+ native.click
87
74
  end
88
75
 
89
- # Trigger an event on the element.
76
+ # Drag the element on top of the target element.
90
77
  #
91
78
  # @api capybara
92
- # @param [String] event the event to trigger
93
- def trigger(event)
94
- node.fire_event(event.to_s)
79
+ # @param [Node] element the target element
80
+ def drag_to(element)
81
+ trigger('mousedown')
82
+ element.trigger('mousemove')
83
+ element.trigger('mouseup')
95
84
  end
96
85
 
97
86
  # @api capybara
98
87
  # @return [String] the element's tag name
99
88
  def tag_name
100
- node.tag_name
89
+ native.tag_name
101
90
  end
102
91
 
103
92
  # @api capybara
104
93
  # @return [true, false] the element's visiblity
105
94
  def visible?
106
- node.visible?
95
+ native.visible?
107
96
  end
108
97
 
109
- # Drag the element on top of the target element.
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.
110
117
  #
111
118
  # @api capybara
112
- # @param [Node] element the target element
113
- def drag_to(element)
114
- trigger('mousedown')
115
- element.trigger('mousemove')
116
- element.trigger('mouseup')
119
+ # @param [String] event the event to trigger
120
+ def trigger(event)
121
+ native.fire_event(event.to_s)
117
122
  end
118
123
 
119
- # Click the element.
120
- def click
121
- node.click
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?
122
138
  end
123
139
 
124
140
  private
@@ -129,25 +145,52 @@ class Capybara::Driver::Akephalos < Capybara::Driver::Base
129
145
  # @return [Array<Node>] the matched nodes
130
146
  def all_unfiltered(selector)
131
147
  nodes = []
132
- node.find(selector).each { |node| nodes << Node.new(driver, node) }
148
+ native.find(selector).each { |node| nodes << self.class.new(driver, node) }
133
149
  nodes
134
150
  end
135
151
 
136
152
  # @return [String] the node's type attribute
137
153
  def type
138
- node[:type]
154
+ native[:type]
139
155
  end
140
- end
141
156
 
142
- attr_reader :app, :rack_server
143
-
144
- # @return [Client] an instance of Akephalos::Client
145
- def self.driver
146
- @driver ||= Akephalos::Client.new
157
+ # @return [Node] the select node, if this is an option node
158
+ def select_node
159
+ find('./ancestor::select').first
160
+ end
147
161
  end
148
162
 
149
- def initialize(app)
163
+ attr_reader :app, :rack_server, :options
164
+
165
+ # Creates a new instance of the Akephalos Driver for Capybara. The driver is
166
+ # registered with Capybara by a name, so that it can be chosen when
167
+ # Capybara's javascript_driver is changed. By default, Akephalos is
168
+ # registered like this:
169
+ #
170
+ # Capybara.register_driver :akephalos do |app|
171
+ # Capybara::Akephalos::Driver.new(
172
+ # app,
173
+ # :browser => :firefox_3_6,
174
+ # :validate_scripts => true
175
+ # )
176
+ # end
177
+ #
178
+ # @param app the Rack application to run
179
+ # @param [Hash] options the Akephalos configuration options
180
+ # @option options [Symbol] :browser (:firefox_3_6) the browser
181
+ # compatibility mode to run in. Available options:
182
+ # :firefox_3_6
183
+ # :firefox_3
184
+ # :ie6
185
+ # :ie7
186
+ # :ie8
187
+ #
188
+ # @option options [true, false] :validate_scripts (true) whether to raise
189
+ # exceptions on script errors
190
+ #
191
+ def initialize(app, options = {})
150
192
  @app = app
193
+ @options = options
151
194
  @rack_server = Capybara::Server.new(@app)
152
195
  @rack_server.boot if Capybara.run_server
153
196
  end
@@ -165,8 +208,19 @@ class Capybara::Driver::Akephalos < Capybara::Driver::Base
165
208
  end
166
209
 
167
210
  # @return [String] the page's modified source
211
+ # page.modified_source will return a string with
212
+ # html entities converted into the unicode equivalent
213
+ # but the string will be marked as ASCII-8BIT
214
+ # which causes conversion issues so we force the encoding
215
+ # to UTF-8 (ruby 1.9 only)
168
216
  def body
169
- page.modified_source
217
+ body_source = page.modified_source
218
+
219
+ if body_source.respond_to?(:force_encoding)
220
+ body_source.force_encoding("UTF-8")
221
+ else
222
+ body_source
223
+ end
170
224
  end
171
225
 
172
226
  # @return [Hash{String => String}] the page's response headers
@@ -190,8 +244,15 @@ class Capybara::Driver::Akephalos < Capybara::Driver::Base
190
244
  end
191
245
 
192
246
  # Clear all cookie session data.
247
+ # @deprecated This method is deprecated in Capybara's master branch. Use
248
+ # {#reset!} instead.
193
249
  def cleanup!
194
- browser.clear_cookies
250
+ reset!
251
+ end
252
+
253
+ # Clear all cookie session data.
254
+ def reset!
255
+ cookies.clear
195
256
  end
196
257
 
197
258
  # @return [String] the page's current URL
@@ -232,7 +293,26 @@ class Capybara::Driver::Akephalos < Capybara::Driver::Base
232
293
 
233
294
  # @return the browser
234
295
  def browser
235
- self.class.driver
296
+ @browser ||= Akephalos::Client.new(@options)
297
+ end
298
+
299
+ # @return the session cookies
300
+ def cookies
301
+ browser.cookies
302
+ end
303
+
304
+ # @return [String] the current user agent string
305
+ def user_agent
306
+ browser.user_agent
307
+ end
308
+
309
+ # Set the User-Agent header for this session. If :default is given, the
310
+ # User-Agent header will be reset to the default browser's user agent.
311
+ #
312
+ # @param [:default] user_agent the default user agent
313
+ # @param [String] user_agent the user agent string to use
314
+ def user_agent=(user_agent)
315
+ browser.user_agent = user_agent
236
316
  end
237
317
 
238
318
  # Disable waiting in Capybara, since waiting is handled directly by
@@ -252,3 +332,7 @@ class Capybara::Driver::Akephalos < Capybara::Driver::Base
252
332
  end
253
333
 
254
334
  end
335
+
336
+ Capybara.register_driver :akephalos do |app|
337
+ Capybara::Driver::Akephalos.new(app)
338
+ end
@@ -10,6 +10,7 @@ else
10
10
  require 'akephalos/page'
11
11
  require 'akephalos/node'
12
12
 
13
+ require 'akephalos/client/cookies'
13
14
  require 'akephalos/client/filter'
14
15
 
15
16
  module Akephalos
@@ -18,34 +19,55 @@ else
18
19
  # point for all interaction with the browser, exposing its current page and
19
20
  # allowing navigation.
20
21
  class Client
21
- java_import 'com.gargoylesoftware.htmlunit.NicelyResynchronizingAjaxController'
22
- java_import 'com.gargoylesoftware.htmlunit.SilentCssErrorHandler'
23
22
 
23
+ # @return [Akephalos::Page] the current page
24
24
  attr_reader :page
25
25
 
26
- def initialize
26
+ # @return [HtmlUnit::BrowserVersion] the configured browser version
27
+ attr_reader :browser_version
28
+
29
+ # @return [true/false] whether to raise errors on javascript failures
30
+ attr_reader :validate_scripts
31
+
32
+ # The default configuration options for a new Client.
33
+ DEFAULT_OPTIONS = {
34
+ :browser => :firefox_3_6,
35
+ :validate_scripts => true
36
+ }
37
+
38
+ # Map of browser version symbols to their HtmlUnit::BrowserVersion
39
+ # instances.
40
+ BROWSER_VERSIONS = {
41
+ :ie6 => HtmlUnit::BrowserVersion::INTERNET_EXPLORER_6,
42
+ :ie7 => HtmlUnit::BrowserVersion::INTERNET_EXPLORER_7,
43
+ :ie8 => HtmlUnit::BrowserVersion::INTERNET_EXPLORER_8,
44
+ :firefox_3 => HtmlUnit::BrowserVersion::FIREFOX_3,
45
+ :firefox_3_6 => HtmlUnit::BrowserVersion::FIREFOX_3_6
46
+ }
47
+
48
+ # @param [Hash] options the configuration options for this client
49
+ #
50
+ # @option options [Symbol] :browser (:firefox_3_6) the browser version (
51
+ # see BROWSER_VERSIONS)
52
+ #
53
+ # @option options [true, false] :validate_scripts (true) whether to raise
54
+ # errors on javascript errors
55
+ def initialize(options = {})
56
+ process_options!(options)
57
+
27
58
  @_client = java.util.concurrent.FutureTask.new do
28
- client = WebClient.new
59
+ client = HtmlUnit::WebClient.new(browser_version)
29
60
 
30
61
  Filter.new(client)
31
- client.setAjaxController(NicelyResynchronizingAjaxController.new)
32
- client.setCssErrorHandler(SilentCssErrorHandler.new)
33
-
62
+ client.setThrowExceptionOnFailingStatusCode(false)
63
+ client.setAjaxController(HtmlUnit::NicelyResynchronizingAjaxController.new)
64
+ client.setCssErrorHandler(HtmlUnit::SilentCssErrorHandler.new)
65
+ client.setThrowExceptionOnScriptError(validate_scripts)
34
66
  client
35
67
  end
36
68
  Thread.new { @_client.run }
37
69
  end
38
70
 
39
- # Set the global configuration settings for Akephalos.
40
- #
41
- # @note This is only used when communicating over DRb, since just a
42
- # single client instance is exposed.
43
- # @param [Hash] config the configuration settings
44
- # @return [Hash] the configuration
45
- def configuration=(config)
46
- Akephalos.configuration = config
47
- end
48
-
49
71
  # Visit the requested URL and return the page.
50
72
  #
51
73
  # @param [String] url the URL to load
@@ -55,9 +77,29 @@ else
55
77
  page
56
78
  end
57
79
 
58
- # Clear all cookies for this browser session.
59
- def clear_cookies
60
- client.getCookieManager.clearCookies
80
+ # @return [Cookies] the cookies for this session
81
+ def cookies
82
+ @cookies ||= Cookies.new(client.getCookieManager)
83
+ end
84
+
85
+ # @return [String] the current user agent string
86
+ def user_agent
87
+ @user_agent || client.getBrowserVersion.getUserAgent
88
+ end
89
+
90
+ # Set the User-Agent header for this session. If :default is given, the
91
+ # User-Agent header will be reset to the default browser's user agent.
92
+ #
93
+ # @param [:default] user_agent the default user agent
94
+ # @param [String] user_agent the user agent string to use
95
+ def user_agent=(user_agent)
96
+ if user_agent == :default
97
+ @user_agent = nil
98
+ client.removeRequestHeader("User-Agent")
99
+ else
100
+ @user_agent = user_agent
101
+ client.addRequestHeader("User-Agent", user_agent)
102
+ end
61
103
  end
62
104
 
63
105
  # @return [Page] the current page
@@ -77,6 +119,23 @@ else
77
119
  @page
78
120
  end
79
121
 
122
+ # @return [true, false] whether javascript errors will raise exceptions
123
+ def validate_scripts?
124
+ !!validate_scripts
125
+ end
126
+
127
+ # Merges the DEFAULT_OPTIONS with those provided to initialize the Client
128
+ # state, namely, its browser version and whether it should
129
+ # validate scripts.
130
+ #
131
+ # @param [Hash] options the options to process
132
+ def process_options!(options)
133
+ options = DEFAULT_OPTIONS.merge(options)
134
+
135
+ @browser_version = BROWSER_VERSIONS.fetch(options.delete(:browser))
136
+ @validate_scripts = options.delete(:validate_scripts)
137
+ end
138
+
80
139
  private
81
140
 
82
141
  # Call the future set up in #initialize and return the WebCLient
@@ -0,0 +1,73 @@
1
+ module Akephalos
2
+ class Client
3
+ # Interface for working with HtmlUnit's CookieManager, providing a basic
4
+ # API for manipulating the cookies in a session.
5
+ class Cookies
6
+ include Enumerable
7
+
8
+ # @param [HtmlUnit::CookieManager] cookie manager
9
+ def initialize(cookie_manager)
10
+ @cookie_manager = cookie_manager
11
+ end
12
+
13
+ # @param [name] the cookie name
14
+ # @return [Cookie] the cookie with the given name
15
+ # @return [nil] when no cookie is found
16
+ def [](name)
17
+ cookie = @cookie_manager.getCookie(name)
18
+ Cookie.new(cookie) if cookie
19
+ end
20
+
21
+ # Clears all cookies for this session.
22
+ def clear
23
+ @cookie_manager.clearCookies
24
+ end
25
+
26
+ # Iterator for all cookies in the current session.
27
+ def each
28
+ @cookie_manager.getCookies.each do |cookie|
29
+ yield Cookie.new(cookie)
30
+ end
31
+ end
32
+
33
+ # Remove the cookie from the session.
34
+ #
35
+ # @param [Cookie] the cookie to remove
36
+ def delete(cookie)
37
+ @cookie_manager.removeCookie(cookie.native)
38
+ end
39
+
40
+ # @return [true, false] whether there are any cookies
41
+ def empty?
42
+ !any?
43
+ end
44
+
45
+ class Cookie
46
+
47
+ attr_reader :domain, :expires, :name, :path, :value
48
+
49
+ # @param [HtmlUnit::Cookie] the cookie
50
+ def initialize(cookie)
51
+ @_cookie = cookie
52
+ @domain = cookie.getDomain
53
+ @expires = cookie.getExpires
54
+ @name = cookie.getName
55
+ @path = cookie.getPath
56
+ @value = cookie.getValue
57
+ @secure = cookie.isSecure
58
+ end
59
+
60
+ def secure?
61
+ !!@secure
62
+ end
63
+
64
+ # @return [HtmlUnit::Cookie] the native cookie object
65
+ # @api private
66
+ def native
67
+ @_cookie
68
+ end
69
+ end
70
+
71
+ end
72
+ end
73
+ end
@@ -9,11 +9,7 @@ module Akephalos
9
9
  # found. If no filters are defined, or no filters match the request, then
10
10
  # the response will bubble up to HtmlUnit for the normal request/response
11
11
  # cycle.
12
- class Filter < WebConnectionWrapper
13
- java_import 'com.gargoylesoftware.htmlunit.util.NameValuePair'
14
- java_import 'com.gargoylesoftware.htmlunit.WebResponseData'
15
- java_import 'com.gargoylesoftware.htmlunit.WebResponseImpl'
16
-
12
+ class Filter < HtmlUnit::Util::WebConnectionWrapper
17
13
  # Filters an outgoing request, and if a match is found, returns the mock
18
14
  # response.
19
15
  #
@@ -23,14 +19,16 @@ module Akephalos
23
19
  def filter(request)
24
20
  if filter = find_filter(request)
25
21
  start_time = Time.now
26
- headers = filter[:headers].map { |name, value| NameValuePair.new(name.to_s, value.to_s) }
27
- response = WebResponseData.new(
22
+ headers = filter[:headers].map do |name, value|
23
+ HtmlUnit::Util::NameValuePair.new(name.to_s, value.to_s)
24
+ end
25
+ response = HtmlUnit::WebResponseData.new(
28
26
  filter[:body].to_s.to_java_bytes,
29
27
  filter[:status],
30
28
  HTTP_STATUS_CODES.fetch(filter[:status], "Unknown"),
31
29
  headers
32
30
  )
33
- WebResponseImpl.new(response, request, Time.now - start_time)
31
+ HtmlUnit::WebResponseImpl.new(response, request, Time.now - start_time)
34
32
  end
35
33
  end
36
34
 
@@ -1,7 +1,7 @@
1
1
  # Begin a new Capybara session, by default connecting to localhost on port
2
2
  # 3000.
3
3
  def session
4
- Capybara.app_host = "http://localhost:3000"
4
+ Capybara.app_host ||= "http://localhost:3000"
5
5
  @session ||= Capybara::Session.new(:Akephalos)
6
6
  end
7
7
  alias page session
@@ -13,15 +13,23 @@ java.lang.System.setProperty("org.apache.commons.logging.Log", "org.apache.commo
13
13
  java.lang.System.setProperty("org.apache.commons.logging.simplelog.defaultlog", "fatal")
14
14
  java.lang.System.setProperty("org.apache.commons.logging.simplelog.showdatetime", "true")
15
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
- )
16
+ # Container module for com.gargoylesoftware.htmlunit namespace.
17
+ module HtmlUnit
18
+ java_import "com.gargoylesoftware.htmlunit.BrowserVersion"
19
+ java_import "com.gargoylesoftware.htmlunit.History"
20
+ java_import "com.gargoylesoftware.htmlunit.HttpMethod"
21
+ java_import "com.gargoylesoftware.htmlunit.NicelyResynchronizingAjaxController"
22
+ java_import "com.gargoylesoftware.htmlunit.SilentCssErrorHandler"
23
+ java_import "com.gargoylesoftware.htmlunit.WebClient"
24
+ java_import "com.gargoylesoftware.htmlunit.WebResponseData"
25
+ java_import "com.gargoylesoftware.htmlunit.WebResponseImpl"
26
+
27
+ # Container module for com.gargoylesoftware.htmlunit.util namespace.
28
+ module Util
29
+ java_import "com.gargoylesoftware.htmlunit.util.NameValuePair"
30
+ java_import "com.gargoylesoftware.htmlunit.util.WebConnectionWrapper"
31
+ end
32
+
33
+ # Disable history tracking
34
+ History.field_reader :ignoreNewPages_
35
+ end
@@ -1,28 +1,30 @@
1
- # Reopen com.gargoylesoftware.htmlunit.HttpMethod to add convenience methods.
2
- class HttpMethod
1
+ module HtmlUnit
2
+ # Reopen HtmlUnit's HttpMethod class to add convenience methods.
3
+ class HttpMethod
3
4
 
4
- # Loosely compare HttpMethod with another object, accepting either an
5
- # HttpMethod instance or a symbol describing the method. Note that :any is a
6
- # special symbol which will always return true.
7
- #
8
- # @param [HttpMethod] other an HtmlUnit HttpMethod object
9
- # @param [Symbol] other a symbolized representation of an http method
10
- # @return [true/false]
11
- def ===(other)
12
- case other
13
- when HttpMethod
14
- super
15
- when :any
16
- true
17
- when :get
18
- self == self.class::GET
19
- when :post
20
- self == self.class::POST
21
- when :put
22
- self == self.class::PUT
23
- when :delete
24
- self == self.class::DELETE
5
+ # Loosely compare HttpMethod with another object, accepting either an
6
+ # HttpMethod instance or a symbol describing the method. Note that :any is a
7
+ # special symbol which will always return true.
8
+ #
9
+ # @param [HttpMethod] other an HtmlUnit HttpMethod object
10
+ # @param [Symbol] other a symbolized representation of an http method
11
+ # @return [true/false]
12
+ def ===(other)
13
+ case other
14
+ when HttpMethod
15
+ super
16
+ when :any
17
+ true
18
+ when :get
19
+ self == self.class::GET
20
+ when :post
21
+ self == self.class::POST
22
+ when :put
23
+ self == self.class::PUT
24
+ when :delete
25
+ self == self.class::DELETE
26
+ end
25
27
  end
26
- end
27
28
 
29
+ end
28
30
  end
@@ -11,7 +11,11 @@ module Akephalos
11
11
 
12
12
  # @return [true, false] whether the element is checked
13
13
  def checked?
14
- @_node.isChecked
14
+ if @_node.respond_to?(:isChecked)
15
+ @_node.isChecked
16
+ else
17
+ !! self[:checked]
18
+ end
15
19
  end
16
20
 
17
21
  # @return [String] inner text of the node
@@ -37,11 +41,13 @@ module Akephalos
37
41
  case tag_name
38
42
  when "select"
39
43
  if self[:multiple]
40
- @_node.selected_options.map { |option| option.text }
44
+ selected_options.map { |option| option.value }
41
45
  else
42
46
  selected_option = @_node.selected_options.first
43
- selected_option ? selected_option.text : nil
47
+ selected_option ? Node.new(selected_option).value : nil
44
48
  end
49
+ when "option"
50
+ self[:value] || text
45
51
  when "textarea"
46
52
  @_node.getText
47
53
  else
@@ -55,28 +61,43 @@ module Akephalos
55
61
  def value=(value)
56
62
  case tag_name
57
63
  when "textarea"
58
- @_node.setText(value)
64
+ @_node.setText("")
65
+ type(value)
59
66
  when "input"
60
- @_node.setValueAttribute(value)
67
+ if file_input?
68
+ @_node.setValueAttribute(value)
69
+ else
70
+ @_node.setValueAttribute("")
71
+ type(value)
72
+ end
61
73
  end
62
74
  end
63
75
 
64
- # Select an option from a select box by its value.
76
+ # Types each character into a text or input field.
65
77
  #
66
- # @return [true, false] whether the selection was successful
67
- def select_option(option)
68
- opt = @_node.getOptions.detect { |o| o.asText == option }
78
+ # @param [String] value the string to type
79
+ def type(value)
80
+ value.each_char do |c|
81
+ @_node.type(c)
82
+ end
83
+ end
84
+
85
+ # @return [true, false] whether the node allows multiple-option selection (if the node is a select).
86
+ def multiple_select?
87
+ !self[:multiple].nil?
88
+ end
69
89
 
70
- opt && opt.setSelected(true)
90
+ # @return [true, false] whether the node is a file input
91
+ def file_input?
92
+ tag_name == "input" && @_node.getAttribute("type") == "file"
71
93
  end
72
94
 
73
- # Unselect an option from a select box by its value.
95
+
96
+ # Unselect an option.
74
97
  #
75
98
  # @return [true, false] whether the unselection was successful
76
- def unselect_option(option)
77
- opt = @_node.getOptions.detect { |o| o.asText == option }
78
-
79
- opt && opt.setSelected(false)
99
+ def unselect
100
+ @_node.setSelected(false)
80
101
  end
81
102
 
82
103
  # Return the option elements for a select box.
@@ -114,6 +135,16 @@ module Akephalos
114
135
  @_node.isDisplayed
115
136
  end
116
137
 
138
+ # @return [true, false] whether the node is selected to the user accounting
139
+ # for CSS.
140
+ def selected?
141
+ if @_node.respond_to?(:isSelected)
142
+ @_node.isSelected
143
+ else
144
+ !! self[:selected]
145
+ end
146
+ end
147
+
117
148
  # Click the node and then wait for any triggered JavaScript callbacks to
118
149
  # fire.
119
150
  def click
@@ -131,6 +162,11 @@ module Akephalos
131
162
  @nodes << nodes
132
163
  nodes
133
164
  end
165
+
166
+ # @return [String] the XPath expression for this node
167
+ def xpath
168
+ @_node.getCanonicalXPath
169
+ end
134
170
  end
135
171
 
136
172
  end
@@ -1,3 +1,4 @@
1
+ require 'socket'
1
2
  require 'drb/drb'
2
3
 
3
4
  # We need to define our own NativeException class for the cases when a native
@@ -14,44 +15,78 @@ module Akephalos
14
15
  # client.visit "http://www.oinopa.com"
15
16
  # client.page.source # => "<!DOCTYPE html PUBLIC..."
16
17
  class RemoteClient
17
- @socket_file = "/tmp/akephalos.#{Process.pid}.sock"
18
+ # @return [DRbObject] a new instance of Akephalos::Client from the DRb
19
+ # server
20
+ def self.new(options = {})
21
+ manager.new_client(options)
22
+ end
18
23
 
19
- # Start a remote akephalos server and return the remote Akephalos::Client
20
- # instance.
24
+ # Starts a remove JRuby DRb server unless already running and returns an
25
+ # instance of Akephalos::ClientManager.
21
26
  #
22
- # @return [DRbObject] the remote client instance
23
- def self.new
24
- start!
27
+ # @return [DRbObject] an instance of Akephalos::ClientManager
28
+ def self.manager
29
+ return @manager if defined?(@manager)
30
+
31
+ server_port = start!
32
+
25
33
  DRb.start_service
26
- client = DRbObject.new_with_uri("drbunix://#{@socket_file}")
34
+ manager = DRbObject.new_with_uri("druby://127.0.0.1:#{server_port}")
35
+
27
36
  # We want to share our local configuration with the remote server
28
37
  # process, so we share an undumped version of our configuration. This
29
38
  # lets us continue to make changes locally and have them reflected in the
30
39
  # remote process.
31
- client.configuration = Akephalos.configuration.extend(DRbUndumped)
32
- client
40
+ manager.configuration = Akephalos.configuration.extend(DRbUndumped)
41
+
42
+ @manager = manager
33
43
  end
34
44
 
35
45
  # Start a remote server process and return when it is available for use.
36
46
  def self.start!
37
- remote_client = fork do
38
- exec("#{Akephalos::BIN_DIR + 'akephalos'} #{@socket_file}")
39
- end
47
+ port = find_available_port
48
+
49
+ remote_client = IO.popen("#{Akephalos::BIN_DIR + 'akephalos'} #{port}")
40
50
 
41
51
  # Set up a monitor thread to detect if the forked server exits
42
52
  # prematurely.
43
- server_monitor = Thread.new { Thread.current[:exited] = Process.wait }
53
+ server_monitor = Thread.new { Thread.current[:exited] = Process.wait(remote_client.pid) }
44
54
 
45
55
  # Wait for the server to be accessible on the socket we specified.
46
- until File.exists?(@socket_file)
56
+ until responsive?(port)
47
57
  exit!(1) if server_monitor[:exited]
48
- sleep 1
58
+ sleep 0.5
49
59
  end
50
60
  server_monitor.kill
51
61
 
52
62
  # Ensure that the remote server shuts down gracefully when we are
53
63
  # finished.
54
- at_exit { Process.kill(:INT, remote_client); File.unlink(@socket_file) }
64
+ at_exit { Process.kill(:INT, remote_client.pid) }
65
+
66
+ port
67
+ end
68
+
69
+ private
70
+
71
+ # @api private
72
+ # @param [Integer] port the port to check for responsiveness
73
+ # @return [true, false] whether the port is responsive
74
+ def self.responsive?(port)
75
+ socket = TCPSocket.open('127.0.0.1', port)
76
+ true
77
+ rescue Errno::ECONNREFUSED
78
+ false
79
+ ensure
80
+ socket.close if socket
81
+ end
82
+
83
+ # @api private
84
+ # @return [Integer] the next available port
85
+ def self.find_available_port
86
+ server = TCPServer.new('127.0.0.1', 0)
87
+ server.addr[1]
88
+ ensure
89
+ server.close if server
55
90
  end
56
91
  end
57
92
  end
@@ -14,21 +14,66 @@ class NameError::Message
14
14
  end
15
15
  end
16
16
 
17
- [Akephalos::Page, Akephalos::Node].each { |klass| klass.send(:include, DRbUndumped) }
17
+ [
18
+ Akephalos::Page,
19
+ Akephalos::Node,
20
+ Akephalos::Client::Cookies,
21
+ Akephalos::Client::Cookies::Cookie
22
+ ].each { |klass| klass.send(:include, DRbUndumped) }
18
23
 
19
24
  module Akephalos
20
25
 
26
+ # The ClientManager is shared over DRb with the remote process, and
27
+ # facilitates communication between the processes.
28
+ #
29
+ # @api private
30
+ class ClientManager
31
+ include DRbUndumped
32
+
33
+ # @return [Akephalos::Client] a new client instance
34
+ def self.new_client(options = {})
35
+ # Store the client to ensure it isn't prematurely garbage collected.
36
+ @client = Client.new(options)
37
+ end
38
+
39
+ # Set the global configuration settings for Akephalos.
40
+ #
41
+ # @param [Hash] config the configuration settings
42
+ # @return [Hash] the configuration
43
+ def self.configuration=(config)
44
+ Akephalos.configuration = config
45
+ end
46
+
47
+ end
48
+
21
49
  # Akephalos::Server is used by `akephalos --server` to start a DRb server
22
- # serving an instance of Akephalos::Client.
50
+ # serving Akephalos::ClientManager.
23
51
  class Server
24
- # Start DRb service for an Akephalos::Client.
52
+
53
+ # Start DRb service for Akephalos::ClientManager.
25
54
  #
26
- # @param [String] socket_file path to socket file to start
27
- def self.start!(socket_file)
28
- client = Client.new
29
- DRb.start_service("drbunix://#{socket_file}", client)
55
+ # @param [String] port attach server to
56
+ def self.start!(port)
57
+ abort_on_parent_exit!
58
+ DRb.start_service("druby://127.0.0.1:#{port}", ClientManager)
30
59
  DRb.thread.join
31
60
  end
61
+
62
+ private
63
+
64
+ # Exit if STDIN is no longer readable, which corresponds to the process
65
+ # which started the server exiting prematurely.
66
+ #
67
+ # @api private
68
+ def self.abort_on_parent_exit!
69
+ Thread.new do
70
+ begin
71
+ STDIN.read
72
+ rescue IOError
73
+ exit
74
+ end
75
+ end
76
+ end
32
77
  end
33
78
 
34
79
  end
@@ -1,3 +1,3 @@
1
1
  module Akephalos #:nodoc
2
- VERSION = "0.2.4"
2
+ VERSION = "0.2.5"
3
3
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 2
8
- - 4
9
- version: 0.2.4
8
+ - 5
9
+ version: 0.2.5
10
10
  platform: java
11
11
  authors:
12
12
  - Bernerd Schaefer
@@ -14,27 +14,29 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-09-14 00:00:00 -05:00
17
+ date: 2011-02-04 00:00:00 +01:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: capybara
22
22
  prerelease: false
23
23
  requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
24
25
  requirements:
25
26
  - - ~>
26
27
  - !ruby/object:Gem::Version
27
28
  segments:
28
29
  - 0
29
- - 3
30
- - 8
31
- version: 0.3.8
30
+ - 4
31
+ - 0
32
+ version: 0.4.0
32
33
  type: :runtime
33
34
  version_requirements: *id001
34
35
  - !ruby/object:Gem::Dependency
35
36
  name: sinatra
36
37
  prerelease: false
37
38
  requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
38
40
  requirements:
39
41
  - - ">="
40
42
  - !ruby/object:Gem::Version
@@ -47,14 +49,15 @@ dependencies:
47
49
  name: rspec
48
50
  prerelease: false
49
51
  requirement: &id003 !ruby/object:Gem::Requirement
52
+ none: false
50
53
  requirements:
51
- - - "="
54
+ - - ">="
52
55
  - !ruby/object:Gem::Version
53
56
  segments:
54
- - 1
57
+ - 2
55
58
  - 3
56
59
  - 0
57
- version: 1.3.0
60
+ version: 2.3.0
58
61
  type: :development
59
62
  version_requirements: *id003
60
63
  description: Headless Browser for Integration Testing with Capybara
@@ -67,6 +70,7 @@ extra_rdoc_files: []
67
70
 
68
71
  files:
69
72
  - lib/akephalos/capybara.rb
73
+ - lib/akephalos/client/cookies.rb
70
74
  - lib/akephalos/client/filter.rb
71
75
  - lib/akephalos/client.rb
72
76
  - lib/akephalos/configuration.rb
@@ -100,6 +104,7 @@ files:
100
104
  - src/htmlunit/xml-apis-1.3.04.jar
101
105
  - README.md
102
106
  - MIT_LICENSE
107
+ - bin/akephalos
103
108
  has_rdoc: true
104
109
  homepage: http://bernerdschaefer.github.com/akephalos
105
110
  licenses: []
@@ -111,6 +116,7 @@ require_paths:
111
116
  - lib
112
117
  - src
113
118
  required_ruby_version: !ruby/object:Gem::Requirement
119
+ none: false
114
120
  requirements:
115
121
  - - ">="
116
122
  - !ruby/object:Gem::Version
@@ -118,6 +124,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
118
124
  - 0
119
125
  version: "0"
120
126
  required_rubygems_version: !ruby/object:Gem::Requirement
127
+ none: false
121
128
  requirements:
122
129
  - - ">="
123
130
  - !ruby/object:Gem::Version
@@ -129,7 +136,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
129
136
  requirements: []
130
137
 
131
138
  rubyforge_project: akephalos
132
- rubygems_version: 1.3.6
139
+ rubygems_version: 1.3.7
133
140
  signing_key:
134
141
  specification_version: 3
135
142
  summary: Headless Browser for Integration Testing with Capybara