yellowlab-akephalos 0.2.6

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.
Files changed (38) hide show
  1. data/MIT_LICENSE +20 -0
  2. data/README.md +109 -0
  3. data/bin/akephalos +88 -0
  4. data/lib/akephalos.rb +19 -0
  5. data/lib/akephalos/capybara.rb +343 -0
  6. data/lib/akephalos/client.rb +181 -0
  7. data/lib/akephalos/client/cookies.rb +73 -0
  8. data/lib/akephalos/client/filter.rb +120 -0
  9. data/lib/akephalos/configuration.rb +49 -0
  10. data/lib/akephalos/console.rb +32 -0
  11. data/lib/akephalos/cucumber.rb +6 -0
  12. data/lib/akephalos/htmlunit.rb +36 -0
  13. data/lib/akephalos/htmlunit/ext/confirm_handler.rb +18 -0
  14. data/lib/akephalos/htmlunit/ext/http_method.rb +30 -0
  15. data/lib/akephalos/node.rb +188 -0
  16. data/lib/akephalos/page.rb +113 -0
  17. data/lib/akephalos/remote_client.rb +92 -0
  18. data/lib/akephalos/server.rb +79 -0
  19. data/lib/akephalos/version.rb +3 -0
  20. data/src/htmlunit/apache-mime4j-0.6.jar +0 -0
  21. data/src/htmlunit/commons-codec-1.4.jar +0 -0
  22. data/src/htmlunit/commons-collections-3.2.1.jar +0 -0
  23. data/src/htmlunit/commons-io-1.4.jar +0 -0
  24. data/src/htmlunit/commons-lang-2.4.jar +0 -0
  25. data/src/htmlunit/commons-logging-1.1.1.jar +0 -0
  26. data/src/htmlunit/cssparser-0.9.5.jar +0 -0
  27. data/src/htmlunit/htmlunit-2.8.jar +0 -0
  28. data/src/htmlunit/htmlunit-core-js-2.8.jar +0 -0
  29. data/src/htmlunit/httpclient-4.0.1.jar +0 -0
  30. data/src/htmlunit/httpcore-4.0.1.jar +0 -0
  31. data/src/htmlunit/httpmime-4.0.1.jar +0 -0
  32. data/src/htmlunit/nekohtml-1.9.14.jar +0 -0
  33. data/src/htmlunit/sac-1.3.jar +0 -0
  34. data/src/htmlunit/serializer-2.7.1.jar +0 -0
  35. data/src/htmlunit/xalan-2.7.1.jar +0 -0
  36. data/src/htmlunit/xercesImpl-2.9.1.jar +0 -0
  37. data/src/htmlunit/xml-apis-1.3.04.jar +0 -0
  38. metadata +167 -0
@@ -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,109 @@
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.net),
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
+ ## 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
+
71
+ ### Setting the HtmlUnit log level
72
+
73
+ By default it uses the 'fatal' level. You can change that like this:
74
+
75
+ Capybara.register_driver :akephalos do |app|
76
+ # available options
77
+ # "trace", "debug", "info", "warn", "error", or "fatal"
78
+ Capybara::Driver::Akephalos.new(app, :htmlunit_log_level => 'fatal')
79
+ end
80
+
81
+ ### Running Akephalos with Spork
82
+
83
+ Spork.prefork do
84
+ ...
85
+ Akephalos::RemoteClient.manager
86
+ end
87
+
88
+ Spork.each_run do
89
+ Thread.current['DRb'] = { 'server' => DRb::DRbServer.new }
90
+ end
91
+
92
+ More info at : [sporking-with-akephalos](http://spacevatican.org/2011/7/3/sporking-with-akephalos)
93
+
94
+ ## More
95
+
96
+ * [bin/akephalos](http://bernerdschaefer.github.com/akephalos/akephalos-bin.html)
97
+ allows you to start an interactive shell or DRb server, as well as perform
98
+ other maintenance features.
99
+
100
+ * [Filters](http://bernerdschaefer.github.com/akephalos/filters.html) allows
101
+ you to declare mock responses for external resources and services requested
102
+ by the browser.
103
+
104
+ ## Resources
105
+
106
+ * [API Documentation](http://bernerdschaefer.github.com/akephalos/api)
107
+ * [Source code](http://github.com/bernerdschaefer/akephalos) and
108
+ [issues](http://github.com/bernerdschaefer/akephalos/issues) are hosted on
109
+ github.
@@ -0,0 +1,88 @@
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] <port>"
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.9-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.9-SNAPSHOT-with-dependencies.zip htmlunit-2.9-SNAPSHOT/lib htmlunit-2.9-SNAPSHOT/lib/* &> /dev/null]
39
+ puts "done"
40
+
41
+ File.unlink "htmlunit-2.9-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 port = 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!(port)
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
+ "-Xmx512M",
71
+ "-cp", [JRubyJars.core_jar_path, JRubyJars.stdlib_jar_path].join(File::PATH_SEPARATOR),
72
+ "org.jruby.Main"
73
+ ]
74
+ ruby_args = [
75
+ "-Ku",
76
+ "-I", ["vendor", lib, src].join(File::PATH_SEPARATOR),
77
+ "-r", "akephalos/server",
78
+ "-e", "Akephalos::Server.start!(#{port.inspect})"
79
+ ]
80
+
81
+ # Bundler sets ENV["RUBYOPT"] to automatically load bundler/setup.rb, but
82
+ # since the akephalos server doesn't have any gem dependencies and is
83
+ # always executed with the same context, we clear RUBYOPT before running
84
+ # exec.
85
+ ENV["RUBYOPT"] = ""
86
+ exec("java", *(java_args + ruby_args))
87
+ end
88
+ end
@@ -0,0 +1,19 @@
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 'pathname'
8
+
9
+ module Akephalos
10
+ BIN_DIR = Pathname(__FILE__).expand_path.dirname.parent + 'bin'
11
+ end
12
+
13
+ require 'akephalos/client'
14
+ require 'capybara'
15
+ require 'akephalos/capybara'
16
+
17
+ Capybara.register_driver :akephalos do |app|
18
+ Capybara::Driver::Akephalos.new(app)
19
+ end
@@ -0,0 +1,343 @@
1
+ # Driver class exposed to Capybara. It implements Capybara's full driver API,
2
+ # and is the entry point for interaction between the test suites and HtmlUnit.
3
+ #
4
+ # This class and +Capybara::Driver::Akephalos::Node+ are written to run on both
5
+ # MRI and JRuby, and is agnostic whether the Akephalos::Client instance is used
6
+ # directly or over DRb.
7
+ class Capybara::Driver::Akephalos < Capybara::Driver::Base
8
+
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
17
+
18
+ # @api capybara
19
+ # @param [String] name attribute name
20
+ # @return [String] the attribute value
21
+ def [](name)
22
+ name = name.to_s
23
+ case name
24
+ when 'checked'
25
+ native.checked?
26
+ else
27
+ native[name.to_s]
28
+ end
29
+ end
30
+
31
+ # @api capybara
32
+ # @return [String, Array<String>] the form element's value
33
+ def value
34
+ native.value
35
+ end
36
+
37
+ # Set the form element's value.
38
+ #
39
+ # @api capybara
40
+ # @param [String] value the form element's new value
41
+ def set(value)
42
+ if tag_name == 'textarea'
43
+ native.value = value.to_s
44
+ elsif tag_name == 'input' and type == 'radio'
45
+ click
46
+ elsif tag_name == 'input' and type == 'checkbox'
47
+ if value != self['checked']
48
+ click
49
+ end
50
+ elsif tag_name == 'input'
51
+ native.value = value.to_s
52
+ end
53
+ end
54
+
55
+ # @api capybara
56
+ def select_option
57
+ native.click
58
+ end
59
+
60
+ # Unselect an option from a select box.
61
+ #
62
+ # @api capybara
63
+ def unselect_option
64
+ unless select_node.multiple_select?
65
+ raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box."
66
+ end
67
+
68
+ native.unselect
69
+ end
70
+
71
+ # Click the element.
72
+ def click
73
+ native.click
74
+ end
75
+
76
+ # Drag the element on top of the target element.
77
+ #
78
+ # @api capybara
79
+ # @param [Node] element the target element
80
+ def drag_to(element)
81
+ trigger('mousedown')
82
+ element.trigger('mousemove')
83
+ element.trigger('mouseup')
84
+ end
85
+
86
+ # @api capybara
87
+ # @return [String] the element's tag name
88
+ def tag_name
89
+ native.tag_name
90
+ end
91
+
92
+ # @api capybara
93
+ # @return [true, false] the element's visiblity
94
+ def visible?
95
+ native.visible?
96
+ end
97
+
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.
117
+ #
118
+ # @api capybara
119
+ # @param [String] event the event to trigger
120
+ def trigger(event)
121
+ native.fire_event(event.to_s)
122
+ end
123
+
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?
138
+ end
139
+
140
+ private
141
+
142
+ # Return all child nodes which match the selector criteria.
143
+ #
144
+ # @api capybara
145
+ # @return [Array<Node>] the matched nodes
146
+ def all_unfiltered(selector)
147
+ nodes = []
148
+ native.find(selector).each { |node| nodes << self.class.new(driver, node) }
149
+ nodes
150
+ end
151
+
152
+ # @return [String] the node's type attribute
153
+ def type
154
+ native[:type]
155
+ end
156
+
157
+ # @return [Node] the select node, if this is an option node
158
+ def select_node
159
+ find('./ancestor::select').first
160
+ end
161
+ end
162
+
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 = {})
192
+ @app = app
193
+ @options = options
194
+ @rack_server = Capybara::Server.new(@app)
195
+ @rack_server.boot if Capybara.run_server
196
+ end
197
+
198
+ # Visit the given path in the browser.
199
+ #
200
+ # @param [String] path relative path to visit
201
+ def visit(path)
202
+ browser.visit(url(path))
203
+ end
204
+
205
+ # @return [String] the page's original source
206
+ def source
207
+ page.source
208
+ end
209
+
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)
216
+ def body
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
224
+ end
225
+
226
+ # @return [Hash{String => String}] the page's response headers
227
+ def response_headers
228
+ page.response_headers
229
+ end
230
+
231
+ # @return [Integer] the response's status code
232
+ def status_code
233
+ page.status_code
234
+ end
235
+
236
+ # Execute the given block within the context of a specified frame.
237
+ #
238
+ # @param [String] frame_id the frame's id
239
+ # @raise [Capybara::ElementNotFound] if the frame is not found
240
+ def within_frame(frame_id, &block)
241
+ unless page.within_frame(frame_id, &block)
242
+ raise Capybara::ElementNotFound, "Unable to find frame with id '#{frame_id}'"
243
+ end
244
+ end
245
+
246
+ # Clear all cookie session data.
247
+ # @deprecated This method is deprecated in Capybara's master branch. Use
248
+ # {#reset!} instead.
249
+ def cleanup!
250
+ reset!
251
+ end
252
+
253
+ # Clear all cookie session data.
254
+ def reset!
255
+ cookies.clear
256
+ end
257
+
258
+ # Confirm or cancel the dialog, returning the text of the dialog
259
+ def confirm_dialog(confirm = true, &block)
260
+ browser.confirm_dialog(confirm, &block)
261
+ end
262
+
263
+ # @return [String] the page's current URL
264
+ def current_url
265
+ page.current_url
266
+ end
267
+
268
+ # Search for nodes which match the given XPath selector.
269
+ #
270
+ # @param [String] selector XPath query
271
+ # @return [Array<Node>] the matched nodes
272
+ def find(selector)
273
+ nodes = []
274
+ page.find(selector).each { |node| nodes << Node.new(self, node) }
275
+ nodes
276
+ end
277
+
278
+ # Execute JavaScript against the current page, discarding any return value.
279
+ #
280
+ # @param [String] script the JavaScript to be executed
281
+ # @return [nil]
282
+ def execute_script(script)
283
+ page.execute_script script
284
+ end
285
+
286
+ # Execute JavaScript against the current page and return the results.
287
+ #
288
+ # @param [String] script the JavaScript to be executed
289
+ # @return the result of the JavaScript
290
+ def evaluate_script(script)
291
+ page.evaluate_script script
292
+ end
293
+
294
+ # @return the current page
295
+ def page
296
+ browser.page
297
+ end
298
+
299
+ # @return the browser
300
+ def browser
301
+ @browser ||= Akephalos::Client.new(@options)
302
+ end
303
+
304
+ # @return the session cookies
305
+ def cookies
306
+ browser.cookies
307
+ end
308
+
309
+ # @return [String] the current user agent string
310
+ def user_agent
311
+ browser.user_agent
312
+ end
313
+
314
+ # Set the User-Agent header for this session. If :default is given, the
315
+ # User-Agent header will be reset to the default browser's user agent.
316
+ #
317
+ # @param [:default] user_agent the default user agent
318
+ # @param [String] user_agent the user agent string to use
319
+ def user_agent=(user_agent)
320
+ browser.user_agent = user_agent
321
+ end
322
+
323
+ # Disable waiting in Capybara, since waiting is handled directly by
324
+ # Akephalos.
325
+ #
326
+ # @return [false]
327
+ def wait
328
+ false
329
+ end
330
+
331
+ private
332
+
333
+ # @param [String] path
334
+ # @return [String] the absolute URL for the given path
335
+ def url(path)
336
+ rack_server.url(path)
337
+ end
338
+
339
+ end
340
+
341
+ Capybara.register_driver :akephalos do |app|
342
+ Capybara::Driver::Akephalos.new(app)
343
+ end