├── .gitignore ├── .rspec ├── .rvmrc.example ├── .travis.yml ├── Gemfile ├── MIT_LICENSE ├── README.md ├── Rakefile ├── akephalos.gemspec ├── bin └── akephalos ├── lib ├── akephalos.rb └── akephalos │ ├── capybara.rb │ ├── client.rb │ ├── client │ ├── cookies.rb │ └── filter.rb │ ├── configuration.rb │ ├── console.rb │ ├── cucumber.rb │ ├── exception_handling_delegators.rb │ ├── htmlunit.rb │ ├── htmlunit │ └── ext │ │ ├── confirm_handler.rb │ │ └── http_method.rb │ ├── htmlunit_downloader.rb │ ├── node.rb │ ├── page.rb │ ├── remote_client.rb │ ├── server.rb │ └── version.rb ├── spec ├── integration │ ├── browser_version_spec.rb │ ├── capybara │ │ ├── driver_spec.rb │ │ └── session_spec.rb │ ├── confirm_dialog_spec.rb │ ├── cookies_spec.rb │ ├── domnode_spec.rb │ ├── filter_spec.rb │ ├── iframe_selection_spec.rb │ ├── script_errors_spec.rb │ ├── slow_page_loads_spec.rb │ ├── subdomains_spec.rb │ └── user_agent_spec.rb ├── spec_helper.rb ├── support │ └── application.rb └── unit │ ├── client_spec.rb │ └── htmlunit_downloader_spec.rb └── tasks └── spec.rake /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle/ 2 | .rvmrc 3 | .yardoc 4 | burn 5 | Gemfile.lock 6 | docs/build 7 | pkg 8 | tags 9 | .idea/ 10 | *.gem 11 | .akephalos 12 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --colour 2 | --format nested 3 | -------------------------------------------------------------------------------- /.rvmrc.example: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This is an RVM Project .rvmrc file, used to automatically load the ruby 4 | # development environment upon cd'ing into the directory 5 | 6 | # First we specify our desired [@], the @gemset name is optional. 7 | environment_id="ruby-1.9.3-p0@akephalos2" 8 | 9 | # 10 | # Uncomment following line if you want options to be set only for given project. 11 | # 12 | # PROJECT_JRUBY_OPTS=( --1.9 ) 13 | 14 | # 15 | # First we attempt to load the desired environment directly from the environment 16 | # file. This is very fast and efficient compared to running through the entire 17 | # CLI and selector. If you want feedback on which environment was used then 18 | # insert the word 'use' after --create as this triggers verbose mode. 19 | # 20 | if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \ 21 | && -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]] 22 | then 23 | \. "${rvm_path:-$HOME/.rvm}/environments/$environment_id" 24 | 25 | if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]] 26 | then 27 | . "${rvm_path:-$HOME/.rvm}/hooks/after_use" 28 | fi 29 | else 30 | # If the environment file has not yet been created, use the RVM CLI to select. 31 | if ! rvm --create "$environment_id" 32 | then 33 | echo "Failed to create RVM environment '${environment_id}'." 34 | return 1 35 | fi 36 | fi 37 | 38 | # 39 | # If you use an RVM gemset file to install a list of gems (*.gems), you can have 40 | # it be automatically loaded. Uncomment the following and adjust the filename if 41 | # necessary. 42 | # 43 | # filename=".gems" 44 | # if [[ -s "$filename" ]] 45 | # then 46 | # rvm gemset import "$filename" | grep -v already | grep -v listed | grep -v complete | sed '/^$/d' 47 | # fi 48 | 49 | # If you use bundler, this might be useful to you: 50 | # if command -v bundle && [[ -s Gemfile ]] 51 | # then 52 | # bundle install 53 | # fi 54 | 55 | if [[ $- == *i* ]] # check for interactive shells 56 | then 57 | echo "Using: $(tput setaf 2)$GEM_HOME$(tput sgr0)" # show the user the ruby and gemset they are using in green 58 | else 59 | echo "Using: $GEM_HOME" # don't use colors in interactive shells 60 | fi 61 | 62 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | notifications: 2 | email: false 3 | rvm: 4 | - 1.9.3 5 | - jruby 6 | env: 7 | - JRUBY_OPTS="--1.9" 8 | before_script: 9 | - git clone git://github.com/Nerian/html-unit-vendor.git .akephalos/2.9 10 | script: "bundle exec rspec spec -f p" 11 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | gemspec 3 | -------------------------------------------------------------------------------- /MIT_LICENSE: -------------------------------------------------------------------------------- 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. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## -- Looking for maintainer -- 2 | 3 | # Important Notice 4 | 5 | This repo has rewritten its history and as such is not compatible with the main Akephalos repo. 6 | 7 | You can get the unaltered – before history rewrite – pristine copy at: [https://github.com/Nerian/akephalos](https://github.com/Nerian/akephalos) 8 | 9 | Further development will be done here: 10 | [https://github.com/Nerian/akephalos2](https://github.com/Nerian/akephalos2) 11 | 12 | Its history was rewritten in order to remove .jar vendor files that were making its size huge. 13 | 14 | 15 | # Akephalos 16 | 17 | Akephalos is a full-stack headless browser for integration testing with 18 | [Capybara](https://github.com/jnicklas/capybara). It is built on top of [HtmlUnit](http://htmlunit.sourceforge.net), 19 | a GUI-less browser for the Java platform, but can be run on both JRuby and 20 | MRI with no need for JRuby to be installed on the system. 21 | 22 | The name Akephalos /ā-sĕf'ə-ləs/ comes from the Greek ἀκέφαλος akephalos, which literally means "headless". 23 | 24 | 25 | ## Installation 26 | 27 | ``` ruby 28 | gem install akephalos2 29 | ``` 30 | 31 | Or 32 | 33 | ``` ruby 34 | gem 'akephalos2', :require => 'akephalos' 35 | ``` 36 | 37 | Or (for the current master branch) 38 | 39 | ``` ruby 40 | gem 'akephalos2', :git => 'git://github.com/Nerian/akephalos2.git' 41 | ``` 42 | 43 | Akephalos creates a `.akephalos` folder where it stores HTMLUnit binaries. You should set Git to ignore that folder. 44 | 45 | ``` 46 | git ignore .akephalos 47 | ``` 48 | 49 | ### Windows 50 | 51 | You will need to manually download HTMLUnit, extract it and save it to `.akephalos/:version`. 52 | 53 | http://sourceforge.net/projects/htmlunit/files/htmlunit/ 54 | 55 | For example: 56 | 57 | * Download [htmlunit-2.9-bin.zip](http://sourceforge.net/projects/htmlunit/files/latest/download?source=files) 58 | * Extract it 59 | * You will get a folder named `htmlunit-2.9`. Rename it to just `2.9` 60 | * Create the folder `.akephalos` in your project folder 61 | * Move the `2.9` folder inside `.akephalos` 62 | 63 | You are done, run your tests. 64 | 65 | # Questions, bugs, etc: 66 | 67 | We use GitHub issues: 68 | 69 | [https://github.com/Nerian/akephalos2/issues](https://github.com/Nerian/akephalos2/issues) 70 | 71 | 72 | # Development 73 | 74 | 75 | 76 | 77 | 78 | ``` bash 79 | git clone https://github.com/Nerian/akephalos2 80 | ``` 81 | 82 | Also, we have a .rvmrc file already cooked: 83 | 84 | ``` bash 85 | cp .rvmrc.example .rvmrc 86 | ``` 87 | 88 | 89 | ## Setup 90 | 91 | Configuring akephalos is as simple as requiring it and setting Capybara's 92 | javascript driver: 93 | 94 | ``` ruby 95 | require 'akephalos' 96 | Capybara.javascript_driver = :akephalos 97 | ``` 98 | 99 | 100 | ## Basic Usage 101 | 102 | Akephalos provides a driver for Capybara, so using Akephalos is no 103 | different than using Selenium or Rack::Test. For a full usage guide, check 104 | out Capybara's DSL [documentation](http://github.com/jnicklas/capybara). It 105 | makes no assumptions about the testing framework being used, and works with 106 | RSpec, Cucumber, and Test::Unit. 107 | 108 | Here's some sample RSpec code: 109 | 110 | ``` ruby 111 | # encoding: utf-8 112 | 113 | describe "Home Page" do 114 | before { visit "/" } 115 | 116 | context "searching" do 117 | 118 | before do 119 | fill_in "Search", :with => "akephalos" 120 | click_button "Go" 121 | end 122 | 123 | it "returns results" { page.should have_css("#results") } 124 | 125 | it "includes the search term" { page.should have_content("akephalos") } 126 | end 127 | 128 | end 129 | ``` 130 | 131 | 132 | ### Encoding 133 | 134 | Akephalos uses UTF-8 encoding by default. You may need to add `# encoding: utf-8` at the first line of your test. This behavior is the default using JRuby in 1.9 mode, but you can use JRuby in 1.8 mode by setting the environment variable `ENV['JRUBY_1_8']=true`. Note that Akephalos works with MRI. I refer here to the JRuby that is used internally by Akephalos. 135 | 136 | 137 | ### Frames 138 | 139 | Capybara allows you to perform your action on a context, for example inside a div or a frame. With Akephalos you can select the frame either by id or by index. 140 | 141 | ``` html 142 | 143 |

Test

144 | 145 |

Test2

146 | 147 |

Test3

148 | 149 | 150 | ``` 151 | 152 | You can operate within the context of iframe `test2` with any of these calls: 153 | 154 | ``` ruby 155 | # By ID 156 | within_frame("test2") do 157 | .... 158 | end 159 | 160 | # By index 161 | within_frame(1) do 162 | .... 163 | end 164 | ``` 165 | 166 | ## Configuration 167 | 168 | There are now a few configuration options available through Capybara's new 169 | `register_driver` API. 170 | 171 | 172 | ### Configure the max memory that Java Virtual Machine can use 173 | 174 | The max memory that the JVM is going to use can be set using an environment variable in your spec_helper or .bashrc file. 175 | 176 | ``` ruby 177 | ENV['akephalos_jvm_max_memory'] 178 | ``` 179 | 180 | 181 | The default value is 128 MB. 182 | 183 | If you use Akephalos's bin the parameter `-m [memory]` sets the max memory for the JVM. 184 | 185 | ``` bash 186 | $ akephalos -m 670 187 | ``` 188 | 189 | 190 | ### Configure the version of HTMLUnit 191 | 192 | The Htmlunit version is configured with a environmental variable named `htmlunit_version`. The possible versions are listed at [here](http://sourceforge.net/projects/htmlunit/files/htmlunit/) 193 | 194 | ``` 195 | ENV["htmlunit_version"] = "2.10" # Development Snapshots 196 | ENV["htmlunit_version"] = "2.9" 197 | ENV["htmlunit_version"] = "2.8" 198 | ENV["htmlunit_version"] = "2.7" 199 | ``` 200 | 201 | It defaults to HtmlUnit 2.9. You can manually download or copy your own version to .akephalos/:version and use it with `ENV["htmlunit_version"] = "version"` 202 | 203 | ### Using a different browser 204 | 205 | HtmlUnit supports a few browser implementations, and you can choose which 206 | browser you would like to use through Akephalos. By default, Akephalos uses 207 | Firefox 3.6. 208 | 209 | ``` ruby 210 | Capybara.register_driver :akephalos do |app| 211 | # available options: 212 | # :ie6, :ie7, :ie8, :firefox_3_6 213 | Capybara::Driver::Akephalos.new(app, :browser => :ie8) 214 | end 215 | ``` 216 | 217 | 218 | ### Using a Proxy Server 219 | 220 | ``` ruby 221 | Capybara.register_driver :akephalos do |app| 222 | Capybara::Driver::Akephalos.new(app, :http_proxy => 'myproxy.com', :http_proxy_port => 8080) 223 | end 224 | ``` 225 | 226 | 227 | ### Ignoring javascript errors 228 | 229 | By default HtmlUnit (and Akephalos) will raise an exception when an error 230 | is encountered in javascript files. This is generally desirable, except 231 | that certain libraries aren't supported by HtmlUnit. If possible, it's 232 | best to keep the default behaviour, and use Filters (see below) to mock 233 | offending libraries. If needed, however, you can configure Akephalos to 234 | ignore javascript errors. 235 | 236 | ``` ruby 237 | Capybara.register_driver :akephalos do |app| 238 | Capybara::Driver::Akephalos.new(app, :validate_scripts => false) 239 | end 240 | ``` 241 | 242 | 243 | ### Setting the HtmlUnit log level 244 | 245 | By default it uses the 'fatal' level. You can change that like this: 246 | 247 | ``` ruby 248 | Capybara.register_driver :akephalos do |app| 249 | # available options 250 | # "trace", "debug", "info", "warn", "error", or "fatal" 251 | Capybara::Driver::Akephalos.new(app, :htmlunit_log_level => 'fatal') 252 | end 253 | ``` 254 | 255 | 256 | ### Running Akephalos with Spork 257 | 258 | ``` ruby 259 | Spork.prefork do 260 | ... 261 | Akephalos::RemoteClient.manager 262 | end 263 | 264 | Spork.each_run do 265 | ... 266 | Thread.current['DRb'] = { 'server' => DRb::DRbServer.new } 267 | end 268 | ``` 269 | 270 | 271 | More info at : [sporking-with-akephalos](http://spacevatican.org/2011/7/3/sporking-with-akephalos) 272 | 273 | ### Filters 274 | 275 | Akephalos allows you to filter requests originating from the browser and return mock responses. This will let you easily filter requests for external resources when running your tests, such as Facebook's API and Google Analytics. 276 | 277 | Configuring filters in Akephalos should be familiar to anyone who has used FakeWeb or a similar library. The simplest filter requires only an HTTP method (:get, :post, :put, :delete, :any) and a string or regex to match against. 278 | 279 | ``` ruby 280 | Akephalos.filter(:get, "http://www.google.com") 281 | Akephalos.filter(:any, %r{^http://(api\.)?twitter\.com/.*$}) 282 | ``` 283 | 284 | 285 | By default, all filtered requests will return an empty body with a 200 status code. You can change this by passing additional options to your filter call. 286 | 287 | ``` ruby 288 | Akephalos.filter(:get, "http://google.com/missing", 289 | :status => 404, :body => "...

Not Found

...") 290 | 291 | Akephalos.filter(:post, "http://my-api.com/resource.xml", 292 | :status => 201, :headers => { 293 | "Content-Type" => "application/xml", 294 | "Location" => "http://my-api.com/resources/1.xml" }, 295 | :body => {:id => 100}.to_xml) 296 | ``` 297 | 298 | 299 | And that's really all there is to it! It should be fairly trivial to set up filters for the external resources you need to fake. For reference, however, here's what we ended up using for our external sources. 300 | 301 | #### Example: Google Maps 302 | 303 | Google Analytics code is passively applied based on HTML comments, so simply returning an empty response body is enough to disable it without errors. 304 | 305 | ``` ruby 306 | Akephalos.filter(:get, "http://www.google-analytics.com/ga.js", 307 | :headers => {"Content-Type" => "application/javascript"}) 308 | ``` 309 | 310 | 311 | Google Maps requires the most extensive amount of API definitions of the three, but these few lines cover everything we've encountered so far. 312 | 313 | ``` ruby 314 | Akephalos.filter(:get, "http://maps.google.com/maps/api/js?sensor=false", 315 | :headers => {"Content-Type" => "application/javascript"}, 316 | :body => "window.google = { 317 | maps: { 318 | LatLng: function(){}, 319 | Map: function(){}, 320 | Marker: function(){}, 321 | MapTypeId: {ROADMAP:1} 322 | } 323 | };") 324 | ``` 325 | 326 | 327 | #### Example: Facebook Connect 328 | 329 | Facebook Connect 330 | 331 | When you enable Facebook Connect on your page, the FeatureLoader is requested, and then additional resources are loaded when you call FB_RequireFeatures. We can therefore return an empty function from our filter to disable all Facebook Connect code. 332 | 333 | ``` ruby 334 | Akephalos.filter(:get, 335 | "http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php", 336 | :headers => {"Content-Type" => "application/javascript"}, 337 | :body => "window.FB_RequireFeatures = function() {};") 338 | ``` 339 | 340 | 341 | ### Akephalos' Interactive mode 342 | 343 | #### bin/akephalos 344 | 345 | The bundled akephalos binary provides a command line interface to a few useful features. 346 | 347 | 348 | #### akephalos --interactive 349 | 350 | Running Akephalos in interactive mode gives you an IRB context for interacting with your site just as you would in your tests: 351 | 352 | ``` ruby 353 | $ akephalos --interactive 354 | -> Capybara.app_host # => "http://localhost:3000" 355 | -> page.visit "/" 356 | -> page.fill_in "Search", :with => "akephalos" 357 | -> page.click_button "Go" 358 | -> page.has_css?("#search_results") # => true 359 | ``` 360 | 361 | 362 | #### akephalos --use-htmlunit-snapshot 363 | 364 | This will instruct Akephalos to use the latest development snapshot of HtmlUnit as found on it's Cruise Control server. HtmlUnit and its dependencies will be unpacked into vendor/htmlunit in the current working directory. 365 | 366 | This is what the output looks like: 367 | 368 | ``` ruby 369 | $ akephalos --use-htmlunit-snapshot 370 | 371 | Downloading latest snapshot... done 372 | Extracting dependencies... done 373 | ======================================== 374 | The latest HtmlUnit snapshot has been extracted to vendor/htmlunit! 375 | Once HtmlUnit has been extracted, Akephalos will automatically detect 376 | the vendored version and use it instead of the bundled version. 377 | ``` 378 | 379 | 380 | #### akephalos --server 381 | 382 | Akephalos uses this command internally to start a JRuby DRb server using the provided socket file. 383 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | require 'rake/clean' 3 | 4 | JAVA = RUBY_PLATFORM == "java" 5 | 6 | $LOAD_PATH.unshift File.expand_path("../lib", __FILE__) 7 | require "akephalos/version" 8 | 9 | CLEAN.include "*.gem" 10 | 11 | task :build do 12 | system "gem build akephalos.gemspec" 13 | end 14 | 15 | task "build:java" do 16 | system "export PLATFORM=java && gem build akephalos.gemspec" 17 | end 18 | 19 | task "build:all" => ['build', 'build:java'] 20 | 21 | task :install => (JAVA ? 'build:java' : 'build') do 22 | gemfile = "akephalos-#{Akephalos::VERSION}#{"-java" if JAVA}.gem" 23 | system "gem install #{gemfile}" 24 | end 25 | 26 | task :release => 'build:all' do 27 | puts "Tagging #{Akephalos::VERSION}..." 28 | system "git tag -a #{Akephalos::VERSION} -m 'Tagging #{Akephalos::VERSION}'" 29 | puts "Pushing to Github..." 30 | system "git push --tags" 31 | puts "Pushing to Gemcutter..." 32 | ["", "-java"].each do |platform| 33 | system "gem push akephalos2-#{Akephalos::VERSION}#{platform}.gem" 34 | end 35 | end 36 | 37 | load 'tasks/spec.rake' 38 | 39 | task :default => :spec 40 | -------------------------------------------------------------------------------- /akephalos.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib/', __FILE__) 3 | $LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib) 4 | 5 | require "akephalos/version" 6 | 7 | Gem::Specification.new do |s| 8 | s.name = "akephalos2" 9 | s.version = Akephalos::VERSION 10 | s.platform = ENV["PLATFORM"] || "ruby" 11 | s.authors = ["Bernerd Schaefer", "Gonzalo Rodríguez-Baltanás Díaz"] 12 | s.email = ["bj.schaefer@gmail.com", "siotopo@gmail.com"] 13 | s.homepage = "https://github.com/Nerian/akephalos2" 14 | s.summary = "Headless Browser for Integration Testing with Capybara" 15 | s.description = s.summary 16 | 17 | s.add_runtime_dependency "capybara" 18 | s.add_runtime_dependency "rake" 19 | 20 | if RUBY_PLATFORM != "java" && ENV["PLATFORM"] != "java" 21 | s.add_runtime_dependency "jruby-jars" 22 | end 23 | 24 | if RUBY_PLATFORM =~ /mingw32/ 25 | s.add_runtime_dependency "win32-process" 26 | end 27 | 28 | s.add_development_dependency "sinatra" 29 | s.add_development_dependency "rspec" 30 | 31 | s.files = Dir.glob("lib/**/*.rb") + %w(README.md MIT_LICENSE) 32 | s.require_paths = %w(lib vendor) 33 | s.executables = %w(akephalos) 34 | end 35 | -------------------------------------------------------------------------------- /bin/akephalos: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # vim:set filetype=ruby: 3 | 4 | require "pathname" 5 | require "optparse" 6 | require 'rubygems' 7 | require "akephalos/htmlunit_downloader" 8 | 9 | options = { :interactive => false, :default_jvm_max_memory => '128'} 10 | 11 | parser = OptionParser.new do |opts| 12 | opts.banner = "Usage: akephalos [--interactive, --use-htmlunit-snapshot, --memory [number]] | [--server] " 13 | opts.on("-s", "--server", "Run in server mode (default)") 14 | opts.on("-i", "--interactive", "Run in interactive mode") { options[:interactive] = true } 15 | opts.on("--use-htmlunit-snapshot", "Use the snapshot of htmlunit") { options[:use_htmlunit_snapshot] = true } 16 | opts.on("-m", "--memory [number]", "Max memory for the Java Virtual Machine, defaults to #{options[:default_jvm_max_memory]} 17 | or env variable $akephalos_jvm_max_memory") do |memory| 18 | options[:akephalos_jvm_max_memory] = memory.to_s 19 | end 20 | 21 | if options[:akephalos_jvm_max_memory].nil? 22 | if ENV['akephalos_jvm_max_memory'].nil? 23 | options[:akephalos_jvm_max_memory] = options[:default_jvm_max_memory] 24 | else 25 | options[:akephalos_jvm_max_memory] = ENV['akephalos_jvm_max_memory'] 26 | end 27 | end 28 | puts "Using #{options[:akephalos_jvm_max_memory]} MB for the JVM" 29 | 30 | HtmlUnit.download_htmlunit(ENV["htmlunit_version"]) 31 | 32 | opts.on_tail("-h", "--help", "Show this message") { puts opts; exit } 33 | end 34 | parser.parse! 35 | 36 | root = Pathname(__FILE__).expand_path.dirname.parent 37 | lib = root + 'lib' 38 | src = root + 'vendor' 39 | 40 | case 41 | when options[:use_htmlunit_snapshot] 42 | require "fileutils" 43 | 44 | FileUtils.mkdir_p("vendor/html-unit") 45 | Dir["vendor/html-unit/*.jar"].each { |jar| File.unlink(jar) } 46 | 47 | Dir.chdir("vendor") do 48 | $stdout.print "Downloading latest snapshot... " 49 | $stdout.flush 50 | %x[curl -O http://build.canoo.com/htmlunit/artifacts/htmlunit-2.10-SNAPSHOT-with-dependencies.zip] 51 | puts "done" 52 | 53 | $stdout.print "Extracting dependencies... " 54 | $stdout.flush 55 | %x[unzip -j -d html-unit htmlunit-2.10-SNAPSHOT-with-dependencies.zip htmlunit-2.10-SNAPSHOT/lib htmlunit-2.10-SNAPSHOT/lib/* &> /dev/null] 56 | puts "done" 57 | 58 | File.unlink "htmlunit-2.10-SNAPSHOT-with-dependencies.zip" 59 | end 60 | 61 | $stdout.puts "="*40 62 | $stdout.puts "The latest HtmlUnit snapshot has been extracted to vendor/html-unit!" 63 | when options[:interactive] 64 | $LOAD_PATH.unshift('vendor', lib, src) 65 | require 'akephalos' 66 | require 'akephalos/console' 67 | Akephalos::Console.start 68 | else 69 | unless port = ARGV[0] 70 | puts parser.help 71 | exit 72 | end 73 | 74 | if RUBY_PLATFORM == "java" 75 | $LOAD_PATH.unshift("vendor", lib, src) 76 | require 'akephalos/server' 77 | Akephalos::Server.start!(port) 78 | else 79 | require 'jruby-jars' 80 | 81 | jruby = JRubyJars.core_jar_path 82 | jruby_stdlib = JRubyJars.stdlib_jar_path 83 | 84 | java_args = [ 85 | "-Xmx#{options[:akephalos_jvm_max_memory]}M", 86 | "-Dfile.encoding=UTF8", 87 | "-cp", [JRubyJars.core_jar_path, JRubyJars.stdlib_jar_path].join(File::PATH_SEPARATOR), 88 | "org.jruby.Main", 89 | "-X+O" 90 | ] 91 | 92 | ruby_args = [ 93 | "-Ku", 94 | "-I", ["#{Dir.pwd}/.akephalos/#{ENV['htmlunit_version']}", lib, src].join(File::PATH_SEPARATOR), 95 | "-r", "akephalos/server", 96 | "-e", "Akephalos::Server.start!(#{port.inspect})" 97 | ] 98 | 99 | # Bundler sets ENV["RUBYOPT"] to automatically load bundler/setup.rb, but 100 | # since the akephalos server doesn't have any gem dependencies and is 101 | # always executed with the same context, we clear RUBYOPT before running 102 | # exec. 103 | if ENV['JRUBY_1_8'] 104 | ENV["RUBYOPT"] = '' 105 | else 106 | ENV["RUBYOPT"] = "--1.9" 107 | end 108 | exec("java", *(java_args + ruby_args)) 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /lib/akephalos.rb: -------------------------------------------------------------------------------- 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 | 8 | module Akephalos 9 | BIN_DIR = Pathname(__FILE__).expand_path.dirname.parent + 'bin' 10 | ENV['htmlunit_version'] ||= "2.9" 11 | end 12 | 13 | require 'akephalos/htmlunit_downloader' 14 | require 'akephalos/client' 15 | require 'capybara' 16 | require 'akephalos/capybara' 17 | -------------------------------------------------------------------------------- /lib/akephalos/capybara.rb: -------------------------------------------------------------------------------- 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] 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 | #if it is already selected: do nothing 58 | #if it isn't selected: click on it 59 | native.click unless selected? 60 | end 61 | 62 | # Unselect an option from a select box. 63 | # 64 | # @api capybara 65 | def unselect_option 66 | unless select_node.multiple_select? 67 | raise Capybara::UnselectNotAllowed, "Cannot unselect option from single select box." 68 | end 69 | 70 | native.unselect 71 | end 72 | 73 | # Click the element. 74 | def click 75 | native.click 76 | end 77 | 78 | # Drag the element on top of the target element. 79 | # 80 | # @api capybara 81 | # @param [Node] element the target element 82 | def drag_to(element) 83 | trigger('mousedown') 84 | element.trigger('mousemove') 85 | element.trigger('mouseup') 86 | end 87 | 88 | # @api capybara 89 | # @return [String] the element's tag name 90 | def tag_name 91 | native.tag_name 92 | end 93 | 94 | # @api capybara 95 | # @return [true, false] the element's visiblity 96 | def visible? 97 | native.visible? 98 | end 99 | 100 | # @api capybara 101 | # @return [true, false] the element's visiblity 102 | def checked? 103 | native.checked? 104 | end 105 | 106 | # @api capybara 107 | # @return [true, false] the element's visiblity 108 | def selected? 109 | native.selected? 110 | end 111 | 112 | # @api capybara 113 | # @return [String] the XPath to locate the node 114 | def path 115 | native.xpath 116 | end 117 | 118 | # Trigger an event on the element. 119 | # 120 | # @api capybara 121 | # @param [String] event the event to trigger 122 | def trigger(event) 123 | native.fire_event(event.to_s) 124 | end 125 | 126 | # @api capybara 127 | # @param [String] selector XPath query 128 | # @return [Array] the matched nodes 129 | def find(selector) 130 | nodes = [] 131 | native.find(selector).each { |node| nodes << self.class.new(self, node) } 132 | nodes 133 | end 134 | 135 | protected 136 | 137 | # @return [true, false] whether the node allows multiple-option selection (if the node is a select). 138 | def multiple_select? 139 | tag_name == "select" && native.multiple_select? 140 | end 141 | 142 | private 143 | 144 | # Return all child nodes which match the selector criteria. 145 | # 146 | # @api capybara 147 | # @return [Array] the matched nodes 148 | def all_unfiltered(selector) 149 | nodes = [] 150 | native.find(selector).each { |node| nodes << self.class.new(driver, node) } 151 | nodes 152 | end 153 | 154 | # @return [String] the node's type attribute 155 | def type 156 | native[:type] 157 | end 158 | 159 | # @return [Node] the select node, if this is an option node 160 | def select_node 161 | find('./ancestor::select').first 162 | end 163 | end 164 | 165 | attr_reader :app, :rack_server, :options 166 | 167 | # Creates a new instance of the Akephalos Driver for Capybara. The driver is 168 | # registered with Capybara by a name, so that it can be chosen when 169 | # Capybara's javascript_driver is changed. By default, Akephalos is 170 | # registered like this: 171 | # 172 | # Capybara.register_driver :akephalos do |app| 173 | # Capybara::Akephalos::Driver.new( 174 | # app, 175 | # :browser => :firefox_3_6, 176 | # :validate_scripts => true 177 | # ) 178 | # end 179 | # 180 | # @param app the Rack application to run 181 | # @param [Hash] options the Akephalos configuration options 182 | # @option options [Symbol] :browser (:firefox_3_6) the browser 183 | # compatibility mode to run in. Available options: 184 | # :firefox_3_6 185 | # :firefox_3 186 | # :ie6 187 | # :ie7 188 | # :ie8 189 | # 190 | # @option options [true, false] :validate_scripts (true) whether to raise 191 | # exceptions on script errors 192 | # 193 | def initialize(app, options = {}) 194 | @app = app 195 | @options = options 196 | @rack_server = Capybara::Server.new(@app) 197 | @rack_server.boot if Capybara.run_server 198 | end 199 | 200 | # Visit the given path in the browser. 201 | # 202 | # @param [String] path relative path to visit 203 | def visit(path) 204 | browser.visit(url(path)) 205 | end 206 | 207 | # @return [String] the page's original source 208 | def source 209 | page.source 210 | end 211 | 212 | # @return [String] the page's modified source 213 | # page.modified_source will return a string with 214 | # html entities converted into the unicode equivalent 215 | # but the string will be marked as ASCII-8BIT 216 | # which causes conversion issues so we force the encoding 217 | # to UTF-8 (ruby 1.9 only) 218 | def body 219 | body_source = page.modified_source 220 | 221 | if body_source.respond_to?(:force_encoding) 222 | body_source.force_encoding("UTF-8") 223 | else 224 | body_source 225 | end 226 | end 227 | 228 | # @return [Hash{String => String}] the page's response headers 229 | def response_headers 230 | page.response_headers 231 | end 232 | 233 | # @return [Integer] the response's status code 234 | def status_code 235 | page.status_code 236 | end 237 | 238 | # Execute the given block within the context of a specified frame. 239 | # 240 | # @param [String] frame_id the frame's id or index 241 | # @raise [Capybara::ElementNotFound] if the frame is not found 242 | def within_frame(frame_id_or_index, &block) 243 | result = page.within_frame(frame_id_or_index, &block) 244 | unless page.within_frame(frame_id_or_index, &block) 245 | raise Capybara::ElementNotFound, "Unable to find frame with id '#{frame_id_or_index}'" 246 | end 247 | end 248 | 249 | # Clear all cookie session data. 250 | # @deprecated This method is deprecated in Capybara's master branch. Use 251 | # {#reset!} instead. 252 | def cleanup! 253 | reset! 254 | end 255 | 256 | # Reset session 257 | def reset! 258 | cookies.clear 259 | browser.close_all_windows() 260 | browser.visit('about:blank') 261 | end 262 | 263 | # Confirm or cancel the dialog, returning the text of the dialog 264 | def confirm_dialog(confirm = true, &block) 265 | browser.confirm_dialog(confirm, &block) 266 | end 267 | 268 | # @return [String] the page's current URL 269 | def current_url 270 | page.current_url 271 | end 272 | 273 | # Search for nodes which match the given XPath selector. 274 | # 275 | # @param [String] selector XPath query 276 | # @return [Array] the matched nodes 277 | def find(selector) 278 | nodes = [] 279 | page.find(selector).each { |node| nodes << Node.new(self, node) } 280 | nodes 281 | end 282 | 283 | # Execute JavaScript against the current page, discarding any return value. 284 | # 285 | # @param [String] script the JavaScript to be executed 286 | # @return [nil] 287 | def execute_script(script) 288 | page.execute_script script 289 | end 290 | 291 | # Execute JavaScript against the current page and return the results. 292 | # 293 | # @param [String] script the JavaScript to be executed 294 | # @return the result of the JavaScript 295 | def evaluate_script(script) 296 | page.evaluate_script script 297 | end 298 | 299 | # @return the current page 300 | def page 301 | browser.page 302 | end 303 | 304 | # @return the browser 305 | def browser 306 | @browser ||= Akephalos::Client.new(@options) 307 | end 308 | 309 | # @return the session cookies 310 | def cookies 311 | browser.cookies 312 | end 313 | 314 | # @return [String] the current user agent string 315 | def user_agent 316 | browser.user_agent 317 | end 318 | 319 | # Set the User-Agent header for this session. If :default is given, the 320 | # User-Agent header will be reset to the default browser's user agent. 321 | # 322 | # @param [:default] user_agent the default user agent 323 | # @param [String] user_agent the user agent string to use 324 | def user_agent=(user_agent) 325 | browser.user_agent = user_agent 326 | end 327 | 328 | # Disable waiting in Capybara, since waiting is handled directly by 329 | # Akephalos. 330 | # 331 | # @return [false] 332 | def wait 333 | false 334 | end 335 | 336 | private 337 | 338 | # @param [String] path 339 | # @return [String] the absolute URL for the given path 340 | def url(path) 341 | if Capybara.run_server 342 | rack_server.url(path) 343 | else 344 | Capybara.app_host + path 345 | end 346 | end 347 | 348 | end 349 | 350 | Capybara.register_driver :akephalos do |app| 351 | Capybara::Driver::Akephalos.new(app) 352 | end 353 | -------------------------------------------------------------------------------- /lib/akephalos/client.rb: -------------------------------------------------------------------------------- 1 | require 'akephalos/configuration' 2 | 3 | if RUBY_PLATFORM != "java" 4 | require 'akephalos/remote_client' 5 | Akephalos::Client = Akephalos::RemoteClient 6 | else 7 | require 'akephalos/htmlunit' 8 | require 'akephalos/htmlunit/ext/http_method' 9 | require 'akephalos/htmlunit/ext/confirm_handler' 10 | 11 | require 'akephalos/exception_handling_delegators' 12 | require 'akephalos/page' 13 | require 'akephalos/node' 14 | 15 | require 'akephalos/client/cookies' 16 | require 'akephalos/client/filter' 17 | 18 | module Akephalos 19 | 20 | # Akephalos::Client wraps HtmlUnit's WebClient class. It is the main entry 21 | # point for all interaction with the browser, exposing its current page and 22 | # allowing navigation. 23 | class Client 24 | 25 | class << self 26 | 27 | alias_method :new_orig, :new 28 | 29 | def new(*args) 30 | ExceptionConvertingDelegator.new(new_orig(*args), "NativeException", RuntimeError) 31 | end 32 | 33 | end 34 | 35 | # @return [Akephalos::Page] the current page 36 | attr_reader :page 37 | 38 | # @return [HtmlUnit::BrowserVersion] the configured browser version 39 | attr_reader :browser_version 40 | 41 | # @return [true/false] whether to raise errors on javascript failures 42 | attr_reader :validate_scripts 43 | 44 | # @return [true/false] whether to ignore insecure ssl certificates 45 | attr_reader :use_insecure_ssl 46 | 47 | # @return ["trace" / "debug" / "info" / "warn" / "error" or "fatal"] which points the htmlunit log level 48 | attr_reader :htmlunit_log_level 49 | 50 | # The default configuration options for a new Client. 51 | DEFAULT_OPTIONS = { 52 | :browser => :firefox_3_6, 53 | :validate_scripts => true, 54 | :use_insecure_ssl => false, 55 | :htmlunit_log_level => 'fatal' 56 | } 57 | 58 | # Map of browser version symbols to their HtmlUnit::BrowserVersion 59 | # instances. 60 | BROWSER_VERSIONS = { 61 | :ie6 => HtmlUnit::BrowserVersion::INTERNET_EXPLORER_6, 62 | :ie7 => HtmlUnit::BrowserVersion::INTERNET_EXPLORER_7, 63 | :ie8 => HtmlUnit::BrowserVersion::INTERNET_EXPLORER_8, 64 | :firefox_3_6 => HtmlUnit::BrowserVersion::FIREFOX_3_6 65 | } 66 | 67 | # @param [Hash] options the configuration options for this client 68 | # 69 | # @option options [Symbol] :browser (:firefox_3_6) the browser version ( 70 | # see BROWSER_VERSIONS) 71 | # 72 | # @option options [true, false] :validate_scripts (true) whether to raise 73 | # errors on javascript errors 74 | def initialize(options = {}) 75 | process_options!(options) 76 | 77 | @_client = java.util.concurrent.FutureTask.new do 78 | 79 | if @http_proxy.nil? or @http_proxy_port.nil? 80 | client = HtmlUnit::WebClient.new(browser_version) 81 | else 82 | client = HtmlUnit::WebClient.new(browser_version, @http_proxy, @http_proxy_port) 83 | end 84 | 85 | client.setThrowExceptionOnFailingStatusCode(false) 86 | client.setAjaxController(HtmlUnit::NicelyResynchronizingAjaxController.new) 87 | client.setCssErrorHandler(HtmlUnit::SilentCssErrorHandler.new) 88 | client.setThrowExceptionOnScriptError(validate_scripts) 89 | client.setUseInsecureSSL(use_insecure_ssl) 90 | client.setRefreshHandler(HtmlUnit::WaitingRefreshHandler.new) 91 | 92 | Filter.new(client) 93 | client 94 | end 95 | Thread.new { @_client.run } 96 | end 97 | 98 | # Visit the requested URL and return the page. 99 | # 100 | # @param [String] url the URL to load 101 | # @return [Page] the loaded page 102 | def visit(url) 103 | client.getPage(url) 104 | page 105 | end 106 | 107 | # @return [Cookies] the cookies for this session 108 | def cookies 109 | @cookies ||= Cookies.new(client.getCookieManager) 110 | end 111 | 112 | # @return [String] the current user agent string 113 | def user_agent 114 | @user_agent || client.getBrowserVersion.getUserAgent 115 | end 116 | 117 | # Set the User-Agent header for this session. If :default is given, the 118 | # User-Agent header will be reset to the default browser's user agent. 119 | # 120 | # @param [:default] user_agent the default user agent 121 | # @param [String] user_agent the user agent string to use 122 | def user_agent=(user_agent) 123 | if user_agent == :default 124 | @user_agent = nil 125 | client.removeRequestHeader("User-Agent") 126 | else 127 | @user_agent = user_agent 128 | client.addRequestHeader("User-Agent", user_agent) 129 | end 130 | end 131 | 132 | # @return [Page] the current page 133 | def page 134 | self.page = client.getCurrentWindow.getTopWindow.getEnclosedPage 135 | @page 136 | end 137 | 138 | # Update the current page. 139 | # 140 | # @param [HtmlUnit::HtmlPage] _page the new page 141 | # @return [Page] the new page 142 | def page=(_page) 143 | if @page != _page 144 | @page = Page.new(_page) 145 | end 146 | @page 147 | end 148 | 149 | # @return [true, false] whether javascript errors will raise exceptions 150 | def validate_scripts? 151 | !!validate_scripts 152 | end 153 | 154 | # @return [true, false] whether to ignore insecure ssl certificates 155 | def use_insecure_ssl? 156 | !!use_insecure_ssl 157 | end 158 | 159 | # Merges the DEFAULT_OPTIONS with those provided to initialize the Client 160 | # state, namely, its browser version, whether it should 161 | # validate scripts, and htmlunit log level. 162 | # 163 | # @param [Hash] options the options to process 164 | def process_options!(options) 165 | options = DEFAULT_OPTIONS.merge(options) 166 | 167 | @browser_version = BROWSER_VERSIONS.fetch(options.delete(:browser)) 168 | @validate_scripts = options.delete(:validate_scripts) 169 | @use_insecure_ssl = options.delete(:use_insecure_ssl) 170 | @htmlunit_log_level = options.delete(:htmlunit_log_level) 171 | @http_proxy = options.delete(:http_proxy) 172 | @http_proxy_port = options.delete(:http_proxy_port) 173 | 174 | java.lang.System.setProperty("org.apache.commons.logging.simplelog.defaultlog", @htmlunit_log_level) 175 | end 176 | 177 | # Confirm or cancel the dialog, returning the text of the dialog 178 | def confirm_dialog(confirm = true, &block) 179 | handler = HtmlUnit::ConfirmHandler.new 180 | handler.handleConfirmValue = confirm 181 | client.setConfirmHandler(handler) 182 | yield if block_given? 183 | return handler.text 184 | end 185 | 186 | def close_all_windows() 187 | @client.closeAllWindows() 188 | end 189 | 190 | private 191 | 192 | # Call the future set up in #initialize and return the WebCLient 193 | # instance. 194 | # 195 | # @return [HtmlUnit::WebClient] the WebClient instance 196 | def client 197 | @client ||= @_client.get.tap do |client| 198 | client.getCurrentWindow.getHistory.ignoreNewPages_.set(true) 199 | end 200 | end 201 | end 202 | end 203 | end 204 | -------------------------------------------------------------------------------- /lib/akephalos/client/cookies.rb: -------------------------------------------------------------------------------- 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 74 | -------------------------------------------------------------------------------- /lib/akephalos/client/filter.rb: -------------------------------------------------------------------------------- 1 | module Akephalos 2 | class Client 3 | 4 | # Akephalos::Client::Filter extends HtmlUnit's WebConnectionWrapper to 5 | # enable filtering outgoing requests generated interally by HtmlUnit. 6 | # 7 | # When a request comes through, it will be tested against the filters 8 | # defined in Akephalos.filters and return a mock response if a match is 9 | # found. If no filters are defined, or no filters match the request, then 10 | # the response will bubble up to HtmlUnit for the normal request/response 11 | # cycle. 12 | class Filter < HtmlUnit::Util::WebConnectionWrapper 13 | # Filters an outgoing request, and if a match is found, returns the mock 14 | # response. 15 | # 16 | # @param [WebRequest] request the pending HTTP request 17 | # @return [WebResponse] when the request matches a defined filter 18 | # @return [nil] when no filters match the request 19 | def filter(request) 20 | if filter = find_filter(request) 21 | start_time = Time.now 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( 26 | filter[:body].to_s.to_java_bytes, 27 | filter[:status], 28 | HTTP_STATUS_CODES.fetch(filter[:status], "Unknown"), 29 | headers 30 | ) 31 | HtmlUnit::WebResponse.new(response, request, Time.now - start_time) 32 | end 33 | end 34 | 35 | # Searches for a filter which matches the request's HTTP method and url. 36 | # 37 | # @param [WebRequest] request the pending HTTP request 38 | # @return [Hash] when a filter matches the request 39 | # @return [nil] when no filters match the request 40 | def find_filter(request) 41 | Akephalos.filters.find do |filter| 42 | request.http_method === filter[:method] && request.url.to_s =~ filter[:filter] 43 | end 44 | end 45 | 46 | # This method is called by WebClient when a page is requested, and will 47 | # return a mock response if the request matches a defined filter or else 48 | # return the actual response. 49 | # 50 | # @api htmlunit 51 | # @param [WebRequest] request the pending HTTP request 52 | # @return [WebResponse] 53 | def getResponse(request) 54 | filter(request) || super 55 | end 56 | 57 | # Map of status codes to their English descriptions. 58 | HTTP_STATUS_CODES = { 59 | 100 => "Continue", 60 | 101 => "Switching Protocols", 61 | 102 => "Processing", 62 | 200 => "OK", 63 | 201 => "Created", 64 | 202 => "Accepted", 65 | 203 => "Non-Authoritative Information", 66 | 204 => "No Content", 67 | 205 => "Reset Content", 68 | 206 => "Partial Content", 69 | 207 => "Multi-Status", 70 | 300 => "Multiple Choices", 71 | 301 => "Moved Permanently", 72 | 302 => "Found", 73 | 303 => "See Other", 74 | 304 => "Not Modified", 75 | 305 => "Use Proxy", 76 | 306 => "Switch Proxy", 77 | 307 => "Temporary Redirect", 78 | 400 => "Bad Request", 79 | 401 => "Unauthorized", 80 | 402 => "Payment Required", 81 | 403 => "Forbidden", 82 | 404 => "Not Found", 83 | 405 => "Method Not Allowed", 84 | 406 => "Not Acceptable", 85 | 407 => "Proxy Authentication Required", 86 | 408 => "Request Timeout", 87 | 409 => "Conflict", 88 | 410 => "Gone", 89 | 411 => "Length Required", 90 | 412 => "Precondition Failed", 91 | 413 => "Request Entity Too Large", 92 | 414 => "Request-URI Too Long", 93 | 415 => "Unsupported Media Type", 94 | 416 => "Requested Range Not Satisfiable", 95 | 417 => "Expectation Failed", 96 | 418 => "I'm a teapot", 97 | 421 => "There are too many connections from your internet address", 98 | 422 => "Unprocessable Entity", 99 | 423 => "Locked", 100 | 424 => "Failed Dependency", 101 | 425 => "Unordered Collection", 102 | 426 => "Upgrade Required", 103 | 449 => "Retry With", 104 | 450 => "Blocked by Windows Parental Controls", 105 | 500 => "Internal Server Error", 106 | 501 => "Not Implemented", 107 | 502 => "Bad Gateway", 108 | 503 => "Service Unavailable", 109 | 504 => "Gateway Timeout", 110 | 505 => "HTTP Version Not Supported", 111 | 506 => "Variant Also Negotiates", 112 | 507 => "Insufficient Storage", 113 | 509 => "Bandwidth Limit Exceeded", 114 | 510 => "Not Extended", 115 | 530 => "User access denied" 116 | }.freeze 117 | end 118 | 119 | end 120 | end 121 | -------------------------------------------------------------------------------- /lib/akephalos/configuration.rb: -------------------------------------------------------------------------------- 1 | module Akephalos 2 | 3 | @configuration = {} 4 | 5 | class << self 6 | # @return [Hash] the configuration 7 | attr_accessor :configuration 8 | end 9 | 10 | module Filters 11 | # @return [Array] all defined filters 12 | def filters 13 | configuration[:filters] ||= [] 14 | end 15 | 16 | # Defines a new filter to be tested by Akephalos::Filter when executing 17 | # page requests. An HTTP method and a regex or string to match against the 18 | # URL are required for defining a filter. 19 | # 20 | # You can additionally pass the following options to define how the 21 | # filtered request should respond: 22 | # 23 | # :status (defaults to 200) 24 | # :body (defaults to "") 25 | # :headers (defaults to {}) 26 | # 27 | # If we define a filter with no additional options, then, we will get an 28 | # empty HTML response: 29 | # 30 | # Akephalos.filter :post, "http://example.com" 31 | # Akephalos.filter :any, %r{http://.*\.com} 32 | # 33 | # If you instead, say, wanted to simulate a failure in an external system, 34 | # you could do this: 35 | # 36 | # Akephalos.filter :post, "http://example.com", 37 | # :status => 500, :body => "Something went wrong" 38 | # 39 | # @param [Symbol] method the HTTP method to match 40 | # @param [RegExp, String] regex URL matcher 41 | # @param [Hash] options response values 42 | def filter(method, regex, options = {}) 43 | regex = Regexp.new(Regexp.escape(regex)) if regex.is_a?(String) 44 | filters << {:method => method, :filter => regex, :status => 200, :body => "", :headers => {}}.merge!(options) 45 | end 46 | end 47 | 48 | extend Filters 49 | end 50 | -------------------------------------------------------------------------------- /lib/akephalos/console.rb: -------------------------------------------------------------------------------- 1 | # Begin a new Capybara session, by default connecting to localhost on port 2 | # 3000. 3 | def session 4 | Capybara.app_host ||= "http://localhost:3000" 5 | @session ||= Capybara::Session.new(:akephalos) 6 | end 7 | alias page session 8 | 9 | module Akephalos 10 | # Simple class for starting an IRB session. 11 | class Console 12 | 13 | # Start an IRB session. Tries to load irb/completion, and also loads a 14 | # .irbrc file if it exists. 15 | def self.start 16 | require 'irb' 17 | 18 | begin 19 | require 'irb/completion' 20 | rescue Exception 21 | # No readline available, proceed anyway. 22 | end 23 | 24 | if ::File.exists? ".irbrc" 25 | ENV['IRBRC'] = ".irbrc" 26 | end 27 | 28 | IRB.start 29 | end 30 | 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/akephalos/cucumber.rb: -------------------------------------------------------------------------------- 1 | require 'capybara/cucumber' 2 | 3 | Before('@akephalos') do 4 | Capybara.current_driver = :akephalos 5 | end 6 | 7 | -------------------------------------------------------------------------------- /lib/akephalos/exception_handling_delegators.rb: -------------------------------------------------------------------------------- 1 | require 'delegate' 2 | 3 | ## 4 | # This class can be used to wrap an object so that any exceptions raised by the object's methods are recued and passed to a specified exception_handler block 5 | # 6 | class ExceptionCatchingDelegator < SimpleDelegator 7 | 8 | ## 9 | # * *Args* : 10 | # - ++ -> delgate - object to be wrapped 11 | # - ++ -> exception_handler - block to handle rescued exeptions (will be called with yield(exception)) 12 | # 13 | def initialize(delegate, exception_handler) 14 | super(delegate) 15 | @exception_handler = exception_handler 16 | end 17 | 18 | ## 19 | # Override of method_missing to rescue exceptions and pass them to the exception_handler 20 | # 21 | def method_missing(m, *args, &block) 22 | begin 23 | return super(m, *args, &block) 24 | rescue Exception => exception 25 | @exception_handler.yield(exception) 26 | end 27 | end 28 | 29 | end 30 | 31 | ## 32 | # This class can be used to wrap an object so that exceptions matching a given type are rescued and then raised as another type. 33 | # 34 | # This kind of exception converting is most of use when dealing with exceptions passed across DRb where a remote 35 | # exception class may not exist on the client-side. 36 | # 37 | class ExceptionConvertingDelegator < ExceptionCatchingDelegator 38 | 39 | ## 40 | # * *Args* : 41 | # - ++ -> delgate - object to be wrapped 42 | # - ++ -> exception_type_to_catch - an exception class or name of an exception class that will be used to match (in a regular-expression sense) the name of exceptions thrown by the delegate's methods 43 | # - ++ -> exception_type_to_throw - the exception class that will be used to create and raise a new exception 44 | # 45 | def initialize(delegate, exception_type_to_catch = Exception, exception_type_to_throw = RuntimeError) 46 | 47 | handler = lambda do |e| 48 | 49 | raise e unless e.class.name =~ Regexp.new(exception_type_to_catch.to_s) 50 | 51 | # Create and raise a RuntimeError 52 | message = e.class.name 53 | unless e.message.nil? || e.message.size == 0 54 | message << " " 55 | message << e.message 56 | end 57 | new_exception = exception_type_to_throw.new(message) 58 | new_exception.set_backtrace(e.backtrace) 59 | raise new_exception 60 | end 61 | 62 | super(delegate, handler) 63 | 64 | end 65 | 66 | end 67 | -------------------------------------------------------------------------------- /lib/akephalos/htmlunit.rb: -------------------------------------------------------------------------------- 1 | require "java" 2 | 3 | Dir["#{Dir.pwd}/.akephalos/#{ENV['htmlunit_version']}/*.jar"].each {|file| require file } 4 | 5 | java.lang.System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog") 6 | java.lang.System.setProperty("org.apache.commons.logging.simplelog.defaultlog", "fatal") 7 | java.lang.System.setProperty("org.apache.commons.logging.simplelog.showdatetime", "true") 8 | 9 | # Container module for com.gargoylesoftware.htmlunit namespace. 10 | module HtmlUnit 11 | java_import "com.gargoylesoftware.htmlunit.BrowserVersion" 12 | java_import "com.gargoylesoftware.htmlunit.History" 13 | java_import "com.gargoylesoftware.htmlunit.HttpMethod" 14 | java_import 'com.gargoylesoftware.htmlunit.ConfirmHandler' 15 | java_import "com.gargoylesoftware.htmlunit.NicelyResynchronizingAjaxController" 16 | java_import "com.gargoylesoftware.htmlunit.SilentCssErrorHandler" 17 | java_import "com.gargoylesoftware.htmlunit.WebClient" 18 | java_import "com.gargoylesoftware.htmlunit.WebResponseData" 19 | java_import "com.gargoylesoftware.htmlunit.WebResponse" 20 | java_import "com.gargoylesoftware.htmlunit.WaitingRefreshHandler" 21 | 22 | # Container module for com.gargoylesoftware.htmlunit.util namespace. 23 | module Util 24 | java_import "com.gargoylesoftware.htmlunit.util.NameValuePair" 25 | java_import "com.gargoylesoftware.htmlunit.util.WebConnectionWrapper" 26 | end 27 | 28 | # Disable history tracking 29 | History.field_reader :ignoreNewPages_ 30 | end 31 | -------------------------------------------------------------------------------- /lib/akephalos/htmlunit/ext/confirm_handler.rb: -------------------------------------------------------------------------------- 1 | # Reopen com.gargoylesoftware.htmlunit.ConfirmHandler to provide an interface to 2 | # confirm a dialog and capture its message 3 | module HtmlUnit 4 | module ConfirmHandler 5 | 6 | # Boolean - true for ok, false for cancel 7 | attr_accessor :handleConfirmValue 8 | 9 | # last confirmation's message 10 | attr_reader :text 11 | 12 | # handleConfirm will be called by htmlunit on a confirm, so store the message. 13 | def handleConfirm(page, message) 14 | @text = message 15 | return handleConfirmValue.nil? ? true : handleConfirmValue 16 | end 17 | end 18 | end -------------------------------------------------------------------------------- /lib/akephalos/htmlunit/ext/http_method.rb: -------------------------------------------------------------------------------- 1 | module HtmlUnit 2 | # Reopen HtmlUnit's HttpMethod class to add convenience methods. 3 | class HttpMethod 4 | 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 27 | end 28 | 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/akephalos/htmlunit_downloader.rb: -------------------------------------------------------------------------------- 1 | module HtmlUnit 2 | def self.download_htmlunit(version) 3 | version ||= "2.9" 4 | if not version_exist?(version) 5 | puts "Installing HTMLUnit #{version} at .akephalos/#{version}/" 6 | Dir.mkdir(".akephalos") unless File.exists?(".akephalos") 7 | Dir.mkdir(".akephalos/#{version}") unless File.exists?(".akephalos/#{version}") 8 | download(version) 9 | unzip(version) 10 | remove_cache(version) 11 | else 12 | puts "Using HTMLUnit #{version}" 13 | end 14 | end 15 | 16 | def self.version_exist?(version) 17 | File.exist?(".akephalos/#{version}/htmlunit-#{version}.jar") 18 | end 19 | 20 | def self.unzip(version) 21 | `unzip -o -j -d .akephalos/#{version} htmlunit-#{version}.zip` 22 | end 23 | 24 | def self.download(version) 25 | if version == "2.10" 26 | %x[curl -L -o htmlunit-2.10.zip http://build.canoo.com/htmlunit/artifacts/htmlunit-2.10-SNAPSHOT-with-dependencies.zip] 27 | elsif version == '2.9' 28 | %x[curl -L -o htmlunit-2.9.zip http://sourceforge.net/projects/htmlunit/files/htmlunit/2.9/htmlunit-2.9-bin.zip] 29 | else 30 | %x[curl -L -o htmlunit-#{version}.zip http://sourceforge.net/projects/htmlunit/files/htmlunit/#{version}/htmlunit-#{version}-bin.zip] 31 | end 32 | end 33 | 34 | def self.remove_cache(version) 35 | `rm -rf htmlunit-#{version} htmlunit-#{version}.zip` 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/akephalos/node.rb: -------------------------------------------------------------------------------- 1 | module Akephalos 2 | 3 | # Akephalos::Node wraps HtmlUnit's DOMNode class, providing a simple API for 4 | # interacting with an element on the page. 5 | class Node 6 | 7 | class << self 8 | 9 | alias_method :new_orig, :new 10 | 11 | def new(*args) 12 | ExceptionConvertingDelegator.new(new_orig(*args), "NativeException", RuntimeError) 13 | end 14 | 15 | end 16 | 17 | # @param [HtmlUnit::DOMNode] node 18 | def initialize(node) 19 | @nodes = [] 20 | @_node = node 21 | end 22 | 23 | # @return [true, false] whether the element is checked 24 | def checked? 25 | if @_node.respond_to?(:isChecked) 26 | @_node.isChecked 27 | else 28 | !! self[:checked] 29 | end 30 | end 31 | 32 | # @return [String] inner text of the node 33 | # Returns a textual representation of this element that represents what would 34 | # be visible to the user if this page was shown in a web browser. 35 | # For example, a single-selection select element would return the currently 36 | # selected value as text. 37 | # Note: This will cleanup/reduce whitespace 38 | def text 39 | @_node.asText 40 | end 41 | 42 | # Returns the raw text content of this node and its descendants... 43 | def text_content 44 | @_node.getTextContent 45 | end 46 | 47 | # Returns a string representation of the XML document from this element and 48 | # all it's children (recursively). The charset used is the current page encoding. 49 | def xml 50 | @_node.asXml 51 | end 52 | 53 | # Return the value of the node's attribute. 54 | # 55 | # @param [String] name attribute on node 56 | # @return [String] the value of the named attribute 57 | # @return [nil] when the node does not have the named attribute 58 | def [](name) 59 | @_node.hasAttribute(name.to_s) ? @_node.getAttribute(name.to_s) : nil 60 | end 61 | 62 | # Return the value of a form element. If the element is a select box and 63 | # has "multiple" declared as an attribute, then all selected options will 64 | # be returned as an array. 65 | # 66 | # @return [String, Array] the node's value 67 | def value 68 | case tag_name 69 | when "select" 70 | if self[:multiple] 71 | selected_options.map { |option| option.value } 72 | else 73 | selected_option = @_node.selected_options.first 74 | selected_option ? Node.new(selected_option).value : nil 75 | end 76 | when "option" 77 | self[:value] || text 78 | when "textarea" 79 | @_node.getText 80 | else 81 | self[:value] 82 | end 83 | end 84 | 85 | # Set the value of the form input. 86 | # 87 | # @param [String] value 88 | def value=(value) 89 | case tag_name 90 | when "textarea" 91 | @_node.setText("") 92 | type(value) 93 | when "input" 94 | if file_input? 95 | @_node.setValueAttribute(value) 96 | else 97 | @_node.setValueAttribute("") 98 | type(value) 99 | end 100 | end 101 | end 102 | 103 | # Types each character into a text or input field. 104 | # 105 | # @param [String] value the string to type 106 | def type(value) 107 | value.each_char do |c| 108 | @_node.type(c) 109 | end 110 | end 111 | 112 | # @return [true, false] whether the node allows multiple-option selection (if the node is a select). 113 | def multiple_select? 114 | !self[:multiple].nil? 115 | end 116 | 117 | # @return [true, false] whether the node is a file input 118 | def file_input? 119 | tag_name == "input" && @_node.getAttribute("type") == "file" 120 | end 121 | 122 | 123 | # Unselect an option. 124 | # 125 | # @return [true, false] whether the unselection was successful 126 | def unselect 127 | @_node.setSelected(false) 128 | end 129 | 130 | # Return the option elements for a select box. 131 | # 132 | # @return [Array] the options 133 | def options 134 | @_node.getOptions.map { |node| Node.new(node) } 135 | end 136 | 137 | # Return the selected option elements for a select box. 138 | # 139 | # @return [Array] the selected options 140 | def selected_options 141 | @_node.getSelectedOptions.map { |node| Node.new(node) } 142 | end 143 | 144 | # Fire a JavaScript event on the current node. Note that you should not 145 | # prefix event names with "on", so: 146 | # 147 | # link.fire_event('mousedown') 148 | # 149 | # @param [String] JavaScript event name 150 | def fire_event(name) 151 | @_node.fireEvent(name) 152 | end 153 | 154 | # @return [String] the node's tag name 155 | def tag_name 156 | @_node.getNodeName 157 | end 158 | 159 | # @return [true, false] whether the node is visible to the user accounting 160 | # for CSS. 161 | def visible? 162 | @_node.isDisplayed 163 | end 164 | 165 | # @return [true, false] whether the node is selected to the user accounting 166 | # for CSS. 167 | def selected? 168 | if @_node.respond_to?(:isSelected) 169 | @_node.isSelected 170 | else 171 | !! self[:selected] 172 | end 173 | end 174 | 175 | # Click the node and then wait for any triggered JavaScript callbacks to 176 | # fire. 177 | def click 178 | @_node.click 179 | @_node.getPage.getWebClient.waitForBackgroundJavaScriptStartingBefore(3000) 180 | end 181 | 182 | # Search for child nodes which match the given XPath selector. 183 | # 184 | # @param [String] selector an XPath selector 185 | # @return [Array] the matched nodes 186 | def find(selector) 187 | nodes = @_node.getByXPath(selector).map { |node| Node.new(node) } 188 | @nodes << nodes 189 | nodes 190 | end 191 | 192 | # @return [String] the XPath expression for this node 193 | def xpath 194 | @_node.getCanonicalXPath 195 | end 196 | end 197 | 198 | end 199 | -------------------------------------------------------------------------------- /lib/akephalos/page.rb: -------------------------------------------------------------------------------- 1 | module Akephalos 2 | 3 | # Akephalos::Page wraps HtmlUnit's HtmlPage class, exposing an API for 4 | # interacting with a page in the browser. 5 | class Page 6 | 7 | class << self 8 | 9 | alias_method :new_orig, :new 10 | 11 | def new(*args) 12 | ExceptionConvertingDelegator.new(new_orig(*args), "NativeException", RuntimeError) 13 | end 14 | 15 | end 16 | 17 | # @param [HtmlUnit::HtmlPage] page 18 | def initialize(page) 19 | @nodes = [] 20 | @_page = page 21 | end 22 | 23 | # Search for nodes which match the given XPath selector. 24 | # 25 | # @param [String] selector an XPath selector 26 | # @return [Array] the matched nodes 27 | def find(selector) 28 | nodes = current_frame.getByXPath(selector).map { |node| Node.new(node) } 29 | @nodes << nodes 30 | nodes 31 | end 32 | 33 | # Return the page's source, including any JavaScript-triggered DOM changes. 34 | # 35 | # @return [String] the page's modified source 36 | def modified_source 37 | current_frame.asXml 38 | end 39 | 40 | # Return the page's source as returned by the web server. 41 | # 42 | # @return [String] the page's original source 43 | def source 44 | current_frame.getWebResponse.getContentAsString 45 | end 46 | 47 | # @return [Hash{String => String}] the page's response headers 48 | def response_headers 49 | headers = current_frame.getWebResponse.getResponseHeaders.map do |header| 50 | [header.getName, header.getValue] 51 | end 52 | Hash[*headers.flatten] 53 | end 54 | 55 | # @return [Integer] the response's status code 56 | def status_code 57 | current_frame.getWebResponse.getStatusCode 58 | end 59 | 60 | # Execute the given block in the context of the frame specified. 61 | # 62 | # @param [String] frame_id the frame's id 63 | # @return [true] if the frame is found 64 | # @return [nil] if the frame is not found 65 | def within_frame(frame_id) 66 | return unless @current_frame = find_frame(frame_id) 67 | yield 68 | true 69 | ensure 70 | @current_frame = nil 71 | end 72 | 73 | # @return [String] the current page's URL. 74 | def current_url 75 | current_frame.getWebResponse.getWebRequest.getUrl.toString 76 | end 77 | 78 | # Execute JavaScript against the current page, discarding any return value. 79 | # 80 | # @param [String] script the JavaScript to be executed 81 | # @return [nil] 82 | def execute_script(script) 83 | current_frame.executeJavaScript(script) 84 | nil 85 | end 86 | 87 | # Execute JavaScript against the current page and return the results. 88 | # 89 | # @param [String] script the JavaScript to be executed 90 | # @return the result of the JavaScript 91 | def evaluate_script(script) 92 | current_frame.executeJavaScript(script).getJavaScriptResult 93 | end 94 | 95 | # Compare this page with an HtmlUnit page. 96 | # 97 | # @param [HtmlUnit::HtmlPage] other an HtmlUnit page 98 | # @return [true, false] 99 | def ==(other) 100 | @_page == other 101 | end 102 | 103 | private 104 | 105 | # Return the current frame. Usually just @_page, except when inside of the 106 | # within_frame block. 107 | # 108 | # @return [HtmlUnit::HtmlPage] the current frame 109 | def current_frame 110 | @current_frame || @_page 111 | end 112 | 113 | # @param [String/Integer] id the frame's id or index 114 | # @return [HtmlUnit::HtmlPage] the specified frame 115 | # @return [nil] if no frame is found 116 | def find_frame(id_or_index) 117 | if id_or_index.is_a? Fixnum 118 | frame = @_page.getFrames.get(id_or_index) rescue nil 119 | else 120 | frame = @_page.getFrames.find do |frame| 121 | frame.getFrameElement.getAttribute("id") == id_or_index 122 | end 123 | end 124 | frame.getEnclosedPage if frame 125 | end 126 | 127 | end 128 | 129 | end 130 | -------------------------------------------------------------------------------- /lib/akephalos/remote_client.rb: -------------------------------------------------------------------------------- 1 | require 'socket' 2 | require 'drb/drb' 3 | 4 | module Akephalos 5 | 6 | # The +RemoteClient+ class provides an interface to an +Akephalos::Client+ 7 | # isntance on a remote DRb server. 8 | # 9 | # == Usage 10 | # client = Akephalos::RemoteClient.new 11 | # client.visit "http://www.oinopa.com" 12 | # client.page.source # => " :ie6) 20 | end 21 | end 22 | 23 | it "renders IE6 content" do 24 | session.visit "/ie_test" 25 | session.should have_content("InternetExplorer6") 26 | end 27 | 28 | it "does not render other content" do 29 | session.visit "/ie_test" 30 | session.should_not have_content("InternetExplorer7") 31 | end 32 | end 33 | 34 | context "when in IE7 mode" do 35 | before do 36 | Capybara.register_driver :akephalos do |app| 37 | Capybara::Driver::Akephalos.new(app, :browser => :ie7) 38 | end 39 | end 40 | 41 | it "renders IE7 content" do 42 | session.visit "/ie_test" 43 | session.should have_content("InternetExplorer7") 44 | end 45 | 46 | it "does not render other content" do 47 | session.visit "/ie_test" 48 | session.should_not have_content("InternetExplorer6") 49 | end 50 | end 51 | 52 | context "when in Firefox mode" do 53 | before do 54 | Capybara.register_driver :akephalos do |app| 55 | Capybara::Driver::Akephalos.new(app, :browser => :firefox_3_6) 56 | end 57 | end 58 | 59 | it "does not render IE content" do 60 | session.visit "/ie_test" 61 | session.should_not have_content("InternetExplorer") 62 | end 63 | end 64 | end 65 | 66 | end 67 | -------------------------------------------------------------------------------- /spec/integration/capybara/driver_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Capybara::Driver::Akephalos do 4 | 5 | before do 6 | @driver = Capybara::Driver::Akephalos.new(TestApp) 7 | end 8 | 9 | it_should_behave_like "driver" 10 | it_should_behave_like "driver with javascript support" 11 | it_should_behave_like "driver with header support" 12 | it_should_behave_like "driver with status code support" 13 | it_should_behave_like "driver with frame support" 14 | it_should_behave_like "driver with cookies support" 15 | 16 | end 17 | -------------------------------------------------------------------------------- /spec/integration/capybara/session_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Capybara::Session do 4 | context 'with akephalos driver' do 5 | 6 | before do 7 | @session = Capybara::Session.new(:akephalos, TestApp) 8 | end 9 | 10 | describe '#driver' do 11 | it "should be a headless driver" do 12 | @session.driver.should be_an_instance_of(Capybara::Driver::Akephalos) 13 | end 14 | end 15 | 16 | describe '#mode' do 17 | it "should remember the mode" do 18 | @session.mode.should == :akephalos 19 | end 20 | end 21 | 22 | it_should_behave_like "session" 23 | it_should_behave_like "session with javascript support" 24 | 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/integration/confirm_dialog_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Capybara::Session do 4 | context 'with akephalos driver' do 5 | 6 | before do 7 | @session = Capybara::Session.new(:akephalos, Application) 8 | end 9 | 10 | it "should ok confirm" do 11 | message = @session.driver.confirm_dialog do 12 | @session.visit('/confirm_test') 13 | end 14 | 15 | node = @session.find(:css, "p#test") 16 | node.text.should == "Confirmed" 17 | 18 | # If desired, assert the message was what we expected 19 | message.should == 'Are you sure?' 20 | end 21 | 22 | it "should cancel confirm" do 23 | @session.driver.confirm_dialog(false) do 24 | @session.visit('/confirm_test') 25 | end 26 | 27 | node = @session.find(:css, "p#test") 28 | node.text.should == "Cancelled" 29 | end 30 | end 31 | end 32 | 33 | -------------------------------------------------------------------------------- /spec/integration/cookies_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Capybara::Driver::Akephalos do 4 | context 'with akephalos driver' do 5 | 6 | let(:driver) { Capybara::Driver::Akephalos.new(Application) } 7 | after { driver.reset! } 8 | 9 | describe "#cookies" do 10 | context "when no cookies are set" do 11 | it "returns an empty array" do 12 | driver.cookies.should be_empty 13 | end 14 | end 15 | 16 | context "when cookies are set" do 17 | before { driver.visit("/set_cookie") } 18 | 19 | it "returns the cookies" do 20 | driver.cookies.should_not be_empty 21 | end 22 | 23 | describe "#cookies.each" do 24 | it "yields cookie objects" do 25 | yielded = false 26 | driver.cookies.each { yielded = true } 27 | yielded.should be_true 28 | end 29 | end 30 | 31 | describe "#cleanup!" do 32 | it "clears the cookies" do 33 | driver.cleanup! 34 | driver.cookies.should be_empty 35 | end 36 | end 37 | 38 | describe "#cookies.delete" do 39 | it "removes the cookie from the sesison" do 40 | driver.cookies.delete(driver.cookies["capybara"]) 41 | driver.cookies.should be_empty 42 | end 43 | end 44 | 45 | describe "#cookies[]" do 46 | context "when the cookie exists" do 47 | it "returns the named cookie" do 48 | driver.cookies["capybara"].should_not be_nil 49 | end 50 | 51 | context "the cookie" do 52 | let(:cookie) { driver.cookies["capybara"] } 53 | it "includes the name" do 54 | cookie.name.should == "capybara" 55 | end 56 | 57 | it "includes the domain" do 58 | ["localhost", "127.0.0.1", "0.0.0.0"].include?(cookie.domain).should be_true 59 | end 60 | 61 | it "includes the path" do 62 | cookie.path.should == "/" 63 | end 64 | 65 | it "includes the value" do 66 | cookie.value.should == "test_cookie" 67 | end 68 | 69 | it "includes the expiration" do 70 | cookie.expires.should be_nil 71 | end 72 | 73 | it "includes security" do 74 | cookie.should_not be_secure 75 | end 76 | end 77 | 78 | end 79 | context "when the cookie does not exist" do 80 | it "returns nil" do 81 | driver.cookies["nonexistant"].should be_nil 82 | end 83 | end 84 | end 85 | 86 | end 87 | end 88 | 89 | end 90 | end 91 | 92 | -------------------------------------------------------------------------------- /spec/integration/domnode_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require 'spec_helper' 3 | 4 | describe Capybara::Session do 5 | context 'with akephalos driver' do 6 | 7 | before do 8 | @session = Capybara::Session.new(:akephalos, Application) 9 | end 10 | 11 | it "Element#text will return browser formatted text" do 12 | @session.visit('/domnode_test') 13 | 14 | @session.find(:css, "p#original_html").text.should == "original html" 15 | @session.find(:css, "p#horizontal_tab").text.should == "original html" 16 | @session.find(:css, "p#line_feed").text.should == "original html" 17 | @session.find(:css, "p#carriage_return").text.should == "original html" 18 | @session.find(:css, "p#non_breaking_space").text.should == " original html " 19 | @session.find(:css, "p#utf").text.should == "uʍop ǝpısdn ɯ,ı 'ǝɯ ʇɐ ʞooן |" 20 | 21 | end 22 | 23 | it "Element#native#text_content will return raw text including whitespace" do 24 | @session.visit('/domnode_test') 25 | 26 | @session.find(:css, "p#original_html").native.text_content.should == "original html" 27 | @session.find(:css, "p#horizontal_tab").native.text_content.should == "\toriginal\t html\t" 28 | @session.find(:css, "p#line_feed").native.text_content.should == "\noriginal\n html\n" 29 | @session.find(:css, "p#carriage_return").native.text_content.should == "\roriginal\r html\r" 30 | @session.find(:css, "p#non_breaking_space").native.text_content.should == "\302\240original\302\240 html\302\240" 31 | @session.find(:css, "p#utf").native.text_content.should == " uʍop ǝpısdn ɯ,ı 'ǝɯ ʇɐ ʞooן |" 32 | end 33 | 34 | it "Element#native#xml will return the current DOM from the node as xml" do 35 | @session.visit('/xml_vs_source_test') 36 | @session.find(:css, "p#javascript_modified").native.xml.should == "

\n javascript modified after\n

\n" 37 | end 38 | 39 | it "Session#source will return the original html with the DOM pre-javascript" do 40 | @session.visit('/xml_vs_source_test') 41 | @session.source.should == " \n

javascript modified

\n \n \n" 42 | end 43 | end 44 | end -------------------------------------------------------------------------------- /spec/integration/filter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Filters" do 4 | before do 5 | @session = Capybara::Session.new(:akephalos, TestApp) 6 | end 7 | 8 | context "with no filter" do 9 | it "returns the page's source" do 10 | @session.visit "/" 11 | @session.source.should == "Hello world! Relative" 12 | end 13 | end 14 | 15 | context "with a filter" do 16 | after { Akephalos.filters.clear } 17 | 18 | it "returns the filter's source" do 19 | Akephalos.filter :get, %r{.*}, :body => "Howdy!" 20 | @session.visit "/" 21 | @session.source.should == "Howdy!" 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/integration/iframe_selection_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Capybara::Session do 4 | context 'with akephalos driver' do 5 | 6 | before do 7 | @session = Capybara::Session.new(:akephalos, Application) 8 | end 9 | 10 | context "iframe selection" do 11 | it "should select iframe by index" do 12 | @session.visit('/iframe_selection_test') 13 | @session.within_frame(0) do 14 | @session.should have_content("Frame 1") 15 | @session.should_not have_content("Frame 2") 16 | end 17 | @session.within_frame(1) do 18 | @session.should have_content("Frame 2") 19 | @session.should_not have_content("Frame 1") 20 | end 21 | end 22 | 23 | it "should return the usual exception when selecting a crazy index" do 24 | @session.visit('/iframe_selection_test') 25 | expect { @session.within_frame(9999) {} }.should raise_error(Capybara::ElementNotFound) 26 | end 27 | 28 | it "should select iframe by id" do 29 | @session.visit('/iframe_selection_test') 30 | @session.within_frame('third') do 31 | @session.should have_content("Frame 3") 32 | @session.should_not have_content("Frame 2") 33 | @session.should_not have_content("Frame 1") 34 | end 35 | end 36 | end 37 | 38 | end 39 | end 40 | 41 | -------------------------------------------------------------------------------- /spec/integration/script_errors_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Capybara::Driver::Akephalos do 4 | 5 | describe "visiting a page with a javascript error" do 6 | context "with script validation enabled" do 7 | 8 | let(:driver) do 9 | Capybara::Driver::Akephalos.new(Application) 10 | end 11 | 12 | it "raises an exception" do 13 | running do 14 | driver.visit "/page_with_javascript_error" 15 | end.should raise_error 16 | end 17 | 18 | end 19 | 20 | context "with script validation disabled" do 21 | 22 | let(:driver) do 23 | Capybara::Driver::Akephalos.new( 24 | Application, 25 | :validate_scripts => false 26 | ) 27 | end 28 | 29 | it "ignores the error" do 30 | running do 31 | driver.visit "/page_with_javascript_error" 32 | end.should_not raise_error 33 | end 34 | 35 | end 36 | end 37 | 38 | end 39 | -------------------------------------------------------------------------------- /spec/integration/slow_page_loads_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Capybara::Session do 4 | context 'with akephalos driver' do 5 | 6 | before do 7 | @session = Capybara::Session.new(:akephalos, Application) 8 | end 9 | 10 | context "slow page load" do 11 | it "should wait for the page to finish loading" do 12 | @session.visit('/slow_page') 13 | @session.current_url.should include('/slow_page') 14 | end 15 | end 16 | 17 | context "slow ajax load" do 18 | it "should wait for ajax to load" do 19 | @session.visit('/slow_ajax_load') 20 | @session.click_link('Click me') 21 | @session.should have_xpath("//p[contains(.,'Loaded!')]") 22 | end 23 | end 24 | 25 | end 26 | end 27 | 28 | -------------------------------------------------------------------------------- /spec/integration/subdomains_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Capybara::Driver::Akephalos do 4 | 5 | let(:driver) { Capybara::Driver::Akephalos.new(Application) } 6 | after do 7 | Capybara.app_host = nil 8 | driver.reset! 9 | end 10 | 11 | context "when changing Capybara.app_host" do 12 | it "defaults app_host settings" do 13 | url = driver.rack_server.url("/app_domain_detection") 14 | driver.visit "/app_domain_detection" 15 | driver.body.should include(url) 16 | driver.current_url.should == url 17 | end 18 | 19 | it "respects the app_host setting" do 20 | url = "http://smackaho.st:#{driver.rack_server.port}/app_domain_detection" 21 | Capybara.app_host = "http://smackaho.st:#{driver.rack_server.port}" 22 | driver.visit "/app_domain_detection" 23 | driver.body.should include(url) 24 | driver.current_url.should == url 25 | end 26 | 27 | it "allows changing app_host multiple times" do 28 | url = "http://sub1.smackaho.st:#{driver.rack_server.port}/app_domain_detection" 29 | Capybara.app_host = "http://sub1.smackaho.st:#{driver.rack_server.port}" 30 | driver.visit "/app_domain_detection" 31 | driver.body.should include(url) 32 | driver.current_url.should == url 33 | 34 | url = "http://sub2.smackaho.st:#{driver.rack_server.port}/app_domain_detection" 35 | Capybara.app_host = "http://sub2.smackaho.st:#{driver.rack_server.port}" 36 | driver.visit "/app_domain_detection" 37 | driver.body.should include(url) 38 | driver.current_url.should == url 39 | end 40 | end 41 | 42 | end 43 | -------------------------------------------------------------------------------- /spec/integration/user_agent_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Capybara::Driver::Akephalos do 4 | 5 | let(:driver) { Capybara::Driver::Akephalos.new(Application) } 6 | after do 7 | driver.user_agent = :default 8 | driver.reset! 9 | end 10 | 11 | describe "#user_agent=" do 12 | context "when given :default" do 13 | it "resets the user agent" do 14 | driver.user_agent = "something else" 15 | driver.user_agent = :default 16 | driver.user_agent.should =~ /Firefox/ 17 | end 18 | end 19 | 20 | context "when given a string" do 21 | it "sets the user agent" do 22 | new_user_agent = "iPhone user agent" 23 | driver.user_agent = new_user_agent 24 | driver.user_agent.should == new_user_agent 25 | end 26 | end 27 | end 28 | 29 | describe "#user_agent" do 30 | context "with the default set" do 31 | it "returns the default user agent string" do 32 | driver.user_agent.should =~ /Firefox/ 33 | end 34 | end 35 | end 36 | 37 | context "when requesting a page" do 38 | context "with no user agent set" do 39 | it "sends the default header" do 40 | driver.visit "/user_agent_detection" 41 | driver.body.should include("Firefox") 42 | end 43 | end 44 | 45 | context "with a user agent set" do 46 | it "sends the given user agent header" do 47 | driver.user_agent = "iPhone user agent" 48 | driver.visit "/user_agent_detection" 49 | driver.body.should include("iPhone user agent") 50 | end 51 | 52 | context "when resetting back to default" do 53 | it "sends the default user agent header" do 54 | driver.user_agent = :default 55 | driver.visit "/user_agent_detection" 56 | driver.body.should include("Firefox") 57 | end 58 | end 59 | end 60 | 61 | end 62 | 63 | end 64 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "yaml" 2 | YAML::ENGINE.yamler= 'syck' if defined?(YAML::ENGINE) 3 | 4 | root = File.expand_path('../../', __FILE__) 5 | lib_paths = [root] + %w(vendor lib vendor).collect { |dir| File.join(root, dir) } 6 | (lib_paths).each do |dir| 7 | $LOAD_PATH.unshift dir unless $LOAD_PATH.include?(dir) 8 | end 9 | 10 | require 'akephalos' 11 | 12 | spec_dir = nil 13 | $LOAD_PATH.detect do |dir| 14 | if File.exists? File.join(dir, "capybara.rb") 15 | spec_dir = File.expand_path(File.join(dir,"..","spec")) 16 | $LOAD_PATH.unshift( spec_dir ) 17 | end 18 | end 19 | 20 | RSpec.configure do |config| 21 | running_with_jruby = RUBY_PLATFORM =~ /java/ 22 | 23 | config.treat_symbols_as_metadata_keys_with_true_values = true 24 | 25 | warn "[AKEPHALOS] ** Skipping JRuby-only specs" unless running_with_jruby 26 | 27 | config.before(:each, :full_description => /wait for block to return true/) do 28 | pending "This spec failure is a red herring; akephalos waits for " \ 29 | "javascript events implicitly, including setTimeout." 30 | end 31 | 32 | config.before(:each, :full_description => /drag and drop/) do 33 | pending "drag and drop is not supported yet" 34 | end 35 | 36 | config.filter_run_excluding(:platform => lambda { |value| 37 | return true if value == :jruby && !running_with_jruby 38 | }) 39 | end 40 | 41 | require File.join(spec_dir,"spec_helper") 42 | require "support/application" -------------------------------------------------------------------------------- /spec/support/application.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | class Application < TestApp 3 | get '/slow_page' do 4 | sleep 1 5 | "

Loaded!

" 6 | end 7 | 8 | get '/slow_ajax_load' do 9 | <<-HTML 10 | 11 | 12 | with_js 13 | 14 | 22 | 23 | 24 | Click me 25 | 26 | HTML 27 | end 28 | 29 | get '/user_agent_detection' do 30 | request.user_agent 31 | end 32 | 33 | get '/app_domain_detection' do 34 | "http://#{request.host_with_port}/app_domain_detection" 35 | end 36 | 37 | get '/page_with_javascript_error' do 38 | <<-HTML 39 | 40 | 43 | 44 | 45 | 46 | HTML 47 | end 48 | 49 | get '/ie_test' do 50 | <<-HTML 51 | 52 | 55 | 58 | 61 | 62 | HTML 63 | end 64 | 65 | # horizontal tab 66 | # line feed 67 | # carriage return 68 | #     69 | get "/domnode_test" do 70 | <<-HTML 71 | 72 |

original html

73 |

original html

74 |

original html

75 |

original html

76 |

 original  html 

77 |

uʍop ǝpısdn ɯ,ı 'ǝɯ ʇɐ ʞooן |

78 | 79 | HTML 80 | end 81 | 82 | get "/xml_vs_source_test" do 83 | <<-HTML 84 | 85 |

javascript modified

86 | 89 | 90 | HTML 91 | end 92 | 93 | get "/confirm_test" do 94 | <<-HTML 95 | 96 |

Test

97 | 104 | 105 | HTML 106 | end 107 | 108 | 109 | get '/one_text' do "Frame 1" end 110 | get '/two_text' do "Frame 2" end 111 | get '/three_text' do "Frame 3" end 112 | 113 | get '/iframe_selection_test' do 114 | <<-HTML 115 | 116 |

Test

117 | 118 |

Test2

119 | 120 |

Test3

121 | 122 | 123 | HTML 124 | end 125 | 126 | end 127 | 128 | if $0 == __FILE__ 129 | Rack::Handler::Mongrel.run Application, :Port => 8070 130 | end 131 | 132 | -------------------------------------------------------------------------------- /spec/unit/client_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Akephalos::Client, :platform => :jruby do 4 | 5 | context "browser version" do 6 | 7 | it "defaults to Firefox 3.6" do 8 | client = Akephalos::Client.new 9 | client.browser_version.should == 10 | HtmlUnit::BrowserVersion::FIREFOX_3_6 11 | end 12 | 13 | it "can be configured in the initializer" do 14 | client = Akephalos::Client.new(:browser => :ie6) 15 | client.browser_version.should == 16 | HtmlUnit::BrowserVersion::INTERNET_EXPLORER_6 17 | end 18 | 19 | it "configures HtmlUnit" do 20 | client = Akephalos::Client.new(:browser => :ie7) 21 | 22 | client.send(:client).getBrowserVersion.should == 23 | HtmlUnit::BrowserVersion::INTERNET_EXPLORER_7 24 | end 25 | 26 | end 27 | 28 | context "HTMLUnit log level" do 29 | 30 | it "defaults to fatal" do 31 | client = Akephalos::Client.new 32 | client.htmlunit_log_level.should == 'fatal' 33 | end 34 | 35 | context "#can be configured to" do 36 | 37 | ["trace", "debug", "info", "warn", "error", "fatal"].each do |log_level| 38 | it "#{log_level}" do 39 | client = Akephalos::Client.new(:htmlunit_log_level => log_level) 40 | client.htmlunit_log_level.should == log_level 41 | end 42 | end 43 | 44 | end 45 | end 46 | 47 | context "script validation" do 48 | 49 | it "defaults to raising errors on script execution" do 50 | Akephalos::Client.new.validate_scripts?.should be_true 51 | end 52 | 53 | it "can be configured not to raise errors on script execution" do 54 | Akephalos::Client.new( 55 | :validate_scripts => false 56 | ).validate_scripts?.should be_false 57 | end 58 | 59 | it "configures HtmlUnit" do 60 | client = Akephalos::Client.new(:validate_scripts => false) 61 | 62 | client.send(:client).isThrowExceptionOnScriptError.should be_false 63 | end 64 | 65 | end 66 | 67 | context "using proxy server" do 68 | it "configures proxy server" do 69 | http_proxy = 'myproxy.com' 70 | http_proxy_port = 8080 71 | 72 | client = Akephalos::Client.new(:http_proxy => http_proxy, :http_proxy_port => http_proxy_port) 73 | 74 | proxy_config = client.send(:client).getProxyConfig 75 | proxy_config.getProxyHost.should == http_proxy 76 | proxy_config.getProxyPort.should == http_proxy_port 77 | end 78 | end 79 | 80 | context "using insecure ssl verification" do 81 | 82 | it "defaults to not ignoring insecure ssl certificates" do 83 | Akephalos::Client.new.use_insecure_ssl?.should be_false 84 | end 85 | 86 | it "can be configured to ignore insecure ssl certificates" do 87 | Akephalos::Client.new( 88 | :use_insecure_ssl => true 89 | ).use_insecure_ssl?.should be_true 90 | end 91 | 92 | it "configures HtmlUnit" do 93 | pending "how do we check this?" 94 | end 95 | 96 | end 97 | 98 | end 99 | -------------------------------------------------------------------------------- /spec/unit/htmlunit_downloader_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | unless ENV['TRAVIS'] 4 | describe "Download HTMLUnit" do 5 | 6 | before(:each) do 7 | `rm -rf .akephalos` 8 | HtmlUnit.stub(:download).with("2.9").and_return(true) 9 | HtmlUnit.stub(:remove_cache).with("2.9").and_return(true) 10 | HtmlUnit.stub(:unzip).with("2.9").and_return(true) 11 | end 12 | 13 | it "should creates .akephalos folder" do 14 | HtmlUnit.download_htmlunit("2.9") 15 | File.exists?(".akephalos").should == true 16 | end 17 | 18 | it "should creates folder for that version" do 19 | HtmlUnit.download_htmlunit("2.9") 20 | File.exists?(".akephalos/2.9").should == true 21 | end 22 | 23 | it "should download that version" do 24 | HtmlUnit.should_receive(:download).with("2.9").and_return(true) 25 | HtmlUnit.download_htmlunit("2.9") 26 | end 27 | 28 | it "should unzip it" do 29 | HtmlUnit.should_receive(:unzip).with("2.9").and_return(true) 30 | HtmlUnit.download_htmlunit("2.9") 31 | end 32 | 33 | it "should remove the cache file" do 34 | HtmlUnit.should_receive(:remove_cache).with("2.9").and_return(true) 35 | HtmlUnit.download_htmlunit("2.9") 36 | end 37 | 38 | it "should just use it if already present" do 39 | HtmlUnit.stub(:version_exist?).and_return(true) 40 | HtmlUnit.should_not_receive(:remove_cache) 41 | HtmlUnit.download_htmlunit("2.9") 42 | end 43 | end 44 | 45 | describe "Integration test" do 46 | 47 | it "should set up htmlunit 2.10" do 48 | `rm -rf .akephalos` 49 | HtmlUnit.download_htmlunit("2.10") 50 | File.exist?(".akephalos/2.10/htmlunit-2.10-SNAPSHOT.jar").should == true 51 | end 52 | 53 | it "should set up htmlunit 2.9" do 54 | `rm -rf .akephalos` 55 | HtmlUnit.download_htmlunit("2.9") 56 | File.exist?(".akephalos/2.9/htmlunit-2.9.jar").should == true 57 | end 58 | 59 | it "should set up htmlunit 2.8" do 60 | `rm -rf .akephalos` 61 | HtmlUnit.download_htmlunit("2.8") 62 | File.exist?(".akephalos/2.8/htmlunit-2.8.jar").should == true 63 | end 64 | end 65 | end -------------------------------------------------------------------------------- /tasks/spec.rake: -------------------------------------------------------------------------------- 1 | require 'rspec/core/rake_task' 2 | 3 | RSpec::Core::RakeTask.new(:spec) do |spec| 4 | spec.pattern = 'spec/**/*_spec.rb' 5 | end 6 | 7 | RSpec::Core::RakeTask.new(:spec) do |spec| 8 | spec.pattern = 'spec/**/*_spec.rb' 9 | spec.rcov = true 10 | end 11 | --------------------------------------------------------------------------------