akephalos 0.2.4 → 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +30 -0
- data/bin/akephalos +9 -8
- data/lib/akephalos.rb +3 -3
- data/lib/akephalos/capybara.rb +148 -64
- data/lib/akephalos/client.rb +79 -20
- data/lib/akephalos/client/cookies.rb +73 -0
- data/lib/akephalos/client/filter.rb +6 -8
- data/lib/akephalos/console.rb +1 -1
- data/lib/akephalos/htmlunit.rb +20 -12
- data/lib/akephalos/htmlunit/ext/http_method.rb +26 -24
- data/lib/akephalos/node.rb +51 -15
- data/lib/akephalos/remote_client.rb +51 -16
- data/lib/akephalos/server.rb +52 -7
- data/lib/akephalos/version.rb +1 -1
- metadata +18 -10
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)
|
data/bin/akephalos
CHANGED
@@ -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] <
|
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.
|
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.
|
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.
|
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
|
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!(
|
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!(#{
|
78
|
+
"-e", "Akephalos::Server.start!(#{port.inspect})"
|
78
79
|
]
|
79
80
|
|
80
81
|
# Bundler sets ENV["RUBYOPT"] to automatically load bundler/setup.rb, but
|
data/lib/akephalos.rb
CHANGED
@@ -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
|
-
|
18
|
-
|
17
|
+
Capybara.register_driver :akephalos do |app|
|
18
|
+
Capybara::Driver::Akephalos.new(app)
|
19
19
|
end
|
data/lib/akephalos/capybara.rb
CHANGED
@@ -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
|
-
|
25
|
+
native.checked?
|
20
26
|
else
|
21
|
-
|
27
|
+
native[name.to_s]
|
22
28
|
end
|
23
29
|
end
|
24
30
|
|
25
31
|
# @api capybara
|
26
|
-
# @return [String] the
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
59
|
-
|
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
|
-
|
74
|
-
|
75
|
-
|
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
|
-
|
68
|
+
native.unselect
|
69
|
+
end
|
80
70
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
else
|
85
|
-
result
|
86
|
-
end
|
71
|
+
# Click the element.
|
72
|
+
def click
|
73
|
+
native.click
|
87
74
|
end
|
88
75
|
|
89
|
-
#
|
76
|
+
# Drag the element on top of the target element.
|
90
77
|
#
|
91
78
|
# @api capybara
|
92
|
-
# @param [
|
93
|
-
def
|
94
|
-
|
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
|
-
|
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
|
-
|
95
|
+
native.visible?
|
107
96
|
end
|
108
97
|
|
109
|
-
#
|
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 [
|
113
|
-
def
|
114
|
-
|
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
|
-
#
|
120
|
-
|
121
|
-
|
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
|
-
|
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
|
-
|
154
|
+
native[:type]
|
139
155
|
end
|
140
|
-
end
|
141
156
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/akephalos/client.rb
CHANGED
@@ -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
|
-
|
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.
|
32
|
-
client.
|
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
|
-
#
|
59
|
-
def
|
60
|
-
client.getCookieManager
|
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
|
27
|
-
|
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
|
|
data/lib/akephalos/console.rb
CHANGED
@@ -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
|
4
|
+
Capybara.app_host ||= "http://localhost:3000"
|
5
5
|
@session ||= Capybara::Session.new(:Akephalos)
|
6
6
|
end
|
7
7
|
alias page session
|
data/lib/akephalos/htmlunit.rb
CHANGED
@@ -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
|
-
|
17
|
-
|
18
|
-
java_import
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
com.gargoylesoftware.htmlunit.
|
23
|
-
|
24
|
-
|
25
|
-
com.gargoylesoftware.htmlunit.
|
26
|
-
|
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
|
-
|
2
|
-
class
|
1
|
+
module HtmlUnit
|
2
|
+
# Reopen HtmlUnit's HttpMethod class to add convenience methods.
|
3
|
+
class HttpMethod
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
data/lib/akephalos/node.rb
CHANGED
@@ -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
|
-
|
44
|
+
selected_options.map { |option| option.value }
|
41
45
|
else
|
42
46
|
selected_option = @_node.selected_options.first
|
43
|
-
selected_option ? selected_option.
|
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(
|
64
|
+
@_node.setText("")
|
65
|
+
type(value)
|
59
66
|
when "input"
|
60
|
-
|
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
|
-
#
|
76
|
+
# Types each character into a text or input field.
|
65
77
|
#
|
66
|
-
# @
|
67
|
-
def
|
68
|
-
|
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
|
-
|
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
|
-
|
95
|
+
|
96
|
+
# Unselect an option.
|
74
97
|
#
|
75
98
|
# @return [true, false] whether the unselection was successful
|
76
|
-
def
|
77
|
-
|
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
|
-
@
|
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
|
-
#
|
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]
|
23
|
-
def self.
|
24
|
-
|
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
|
-
|
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
|
-
|
32
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
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
|
56
|
+
until responsive?(port)
|
47
57
|
exit!(1) if server_monitor[:exited]
|
48
|
-
sleep
|
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
|
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
|
data/lib/akephalos/server.rb
CHANGED
@@ -14,21 +14,66 @@ class NameError::Message
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
-
[
|
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
|
50
|
+
# serving Akephalos::ClientManager.
|
23
51
|
class Server
|
24
|
-
|
52
|
+
|
53
|
+
# Start DRb service for Akephalos::ClientManager.
|
25
54
|
#
|
26
|
-
# @param [String]
|
27
|
-
def self.start!(
|
28
|
-
|
29
|
-
DRb.start_service("
|
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
|
data/lib/akephalos/version.rb
CHANGED
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 2
|
8
|
-
-
|
9
|
-
version: 0.2.
|
8
|
+
- 5
|
9
|
+
version: 0.2.5
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Bernerd Schaefer
|
@@ -14,27 +14,29 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date:
|
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
|
-
-
|
30
|
-
-
|
31
|
-
version: 0.
|
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: jruby-jars
|
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,6 +49,7 @@ dependencies:
|
|
47
49
|
name: sinatra
|
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
|
@@ -59,14 +62,15 @@ dependencies:
|
|
59
62
|
name: rspec
|
60
63
|
prerelease: false
|
61
64
|
requirement: &id004 !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
62
66
|
requirements:
|
63
|
-
- - "
|
67
|
+
- - ">="
|
64
68
|
- !ruby/object:Gem::Version
|
65
69
|
segments:
|
66
|
-
-
|
70
|
+
- 2
|
67
71
|
- 3
|
68
72
|
- 0
|
69
|
-
version:
|
73
|
+
version: 2.3.0
|
70
74
|
type: :development
|
71
75
|
version_requirements: *id004
|
72
76
|
description: Headless Browser for Integration Testing with Capybara
|
@@ -79,6 +83,7 @@ extra_rdoc_files: []
|
|
79
83
|
|
80
84
|
files:
|
81
85
|
- lib/akephalos/capybara.rb
|
86
|
+
- lib/akephalos/client/cookies.rb
|
82
87
|
- lib/akephalos/client/filter.rb
|
83
88
|
- lib/akephalos/client.rb
|
84
89
|
- lib/akephalos/configuration.rb
|
@@ -112,6 +117,7 @@ files:
|
|
112
117
|
- src/htmlunit/xml-apis-1.3.04.jar
|
113
118
|
- README.md
|
114
119
|
- MIT_LICENSE
|
120
|
+
- bin/akephalos
|
115
121
|
has_rdoc: true
|
116
122
|
homepage: http://bernerdschaefer.github.com/akephalos
|
117
123
|
licenses: []
|
@@ -123,6 +129,7 @@ require_paths:
|
|
123
129
|
- lib
|
124
130
|
- src
|
125
131
|
required_ruby_version: !ruby/object:Gem::Requirement
|
132
|
+
none: false
|
126
133
|
requirements:
|
127
134
|
- - ">="
|
128
135
|
- !ruby/object:Gem::Version
|
@@ -130,6 +137,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
130
137
|
- 0
|
131
138
|
version: "0"
|
132
139
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
140
|
+
none: false
|
133
141
|
requirements:
|
134
142
|
- - ">="
|
135
143
|
- !ruby/object:Gem::Version
|
@@ -141,7 +149,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
141
149
|
requirements: []
|
142
150
|
|
143
151
|
rubyforge_project: akephalos
|
144
|
-
rubygems_version: 1.3.
|
152
|
+
rubygems_version: 1.3.7
|
145
153
|
signing_key:
|
146
154
|
specification_version: 3
|
147
155
|
summary: Headless Browser for Integration Testing with Capybara
|