yellowlab-akephalos 0.2.6

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