Просмотр исходного кода

Merge branch 'master' into action-chains-experiment

Emmanuel Peroumalnaik 10 лет назад
Родитель
Сommit
f362903503
59 измененных файлов с 2760 добавлено и 689 удалено
  1. 3 0
      .gitignore
  2. 34 0
      Changes
  3. 61 0
      INSTALL.md
  4. 49 67
      README.md
  5. 12 6
      cpanfile
  6. 5 7
      dist.ini
  7. 344 0
      lib/Selenium/CanStartBinary.pm
  8. 70 0
      lib/Selenium/CanStartBinary/FindBinary.pm
  9. 35 0
      lib/Selenium/CanStartBinary/ProbePort.pm
  10. 82 0
      lib/Selenium/Chrome.pm
  11. 84 0
      lib/Selenium/Firefox.pm
  12. 79 0
      lib/Selenium/Firefox/Binary.pm
  13. 286 0
      lib/Selenium/Firefox/Profile.pm
  14. BIN
      lib/Selenium/Firefox/amd64/libibushandler.so
  15. BIN
      lib/Selenium/Firefox/amd64/x_ignore_nofocus.so
  16. BIN
      lib/Selenium/Firefox/webdriver.xpi
  17. 68 0
      lib/Selenium/Firefox/webdriver_prefs.json
  18. BIN
      lib/Selenium/Firefox/x86/libibushandler.so
  19. BIN
      lib/Selenium/Firefox/x86/x_ignore_nofocus.so
  20. 23 0
      lib/Selenium/InternetExplorer.pm
  21. 95 0
      lib/Selenium/PhantomJS.pm
  22. 37 2
      lib/Selenium/Remote/Commands.pm
  23. 320 25
      lib/Selenium/Remote/Driver.pm
  24. 8 236
      lib/Selenium/Remote/Driver/Firefox/Profile.pm
  25. 36 0
      lib/Selenium/Remote/Finders.pm
  26. 8 6
      lib/Selenium/Remote/Mock/RemoteConnection.pm
  27. 111 0
      lib/Selenium/Waiter.pm
  28. 158 120
      lib/Test/Selenium/Remote/Driver.pm
  29. 44 1
      lib/Test/Selenium/Remote/Role/DoesTesting.pm
  30. 92 7
      t/01-driver.t
  31. 3 1
      t/02-webelement.t
  32. 6 8
      t/03-spec-coverage.t
  33. 89 0
      t/CanStartBinary.t
  34. 50 0
      t/Finders.t
  35. 16 15
      t/Firefox-Profile.t
  36. 3 2
      t/Remote-Connection.t
  37. 116 9
      t/Test-Selenium-Remote-Driver.t
  38. 48 0
      t/Waiter.t
  39. 39 0
      t/convenience.t
  40. 26 28
      t/error.t
  41. 25 0
      t/lib/TestHarness.pm
  42. 19 20
      t/mock-recordings/01-driver-mock-MSWin32.json
  43. 3 4
      t/mock-recordings/01-driver-mock-darwin.json
  44. 6 9
      t/mock-recordings/01-driver-mock-linux.json
  45. 2 3
      t/mock-recordings/02-webelement-mock-darwin.json
  46. 13 18
      t/mock-recordings/02-webelement-mock-linux.json
  47. 28 28
      t/mock-recordings/10-switch-to-window-mock-darwin.json
  48. 28 28
      t/mock-recordings/10-switch-to-window-mock-linux.json
  49. 20 0
      t/mock-recordings/convenience-mock-MSWin32.json
  50. 14 0
      t/mock-recordings/convenience-mock-darwin.json
  51. 14 0
      t/mock-recordings/convenience-mock-linux.json
  52. 8 0
      t/mock-recordings/finders-mock-MSWin32.json
  53. 2 0
      t/mock-recordings/finders-mock-darwin.json
  54. 2 0
      t/mock-recordings/finders-mock-linux.json
  55. 6 6
      t/mock-recordings/firefox-profile-mock-darwin.json
  56. 6 9
      t/mock-recordings/firefox-profile-mock-linux.json
  57. 12 12
      t/mock-recordings/test-selenium-remote-driver-google-mock-darwin.json
  58. 12 12
      t/mock-recordings/test-selenium-remote-driver-google-mock-linux.json
  59. 0 0
      t/uploadTest

+ 3 - 0
.gitignore

@@ -10,3 +10,6 @@ cover_db
 Selenium-Remote-Driver-*
 .prove
 _prove
+#vim swapfiles
+*.swo
+*.swp

+ 34 - 0
Changes

@@ -1,5 +1,39 @@
 Revision history for Selenium-Remote-Driver
 
+0.25  03-24-2015
+        (selenium version: 2.45.0)
+
+        [NEW FEATURES]
+        - #189 Start webdrivers directly, removing the need for the JRE. See
+           Selenium::Chrome, Selenium::Firefox, Selenium::PhantomJS.
+
+        [BUG FIXES]
+        - #194, #195: @teodesian, @jamadam: fix undocumented upload file behavior
+
+0.24    02-18-2015
+        [NEW FEATURES]
+        - #182 Non-crashing warn-instead-of-die parameter finders: find_element_by_class, etc
+        - #183 Convenience packages for starting up browsers: Selenium::Chrome, Selenium::Firefox, etc
+        - Provide wait_until utility functionality in Selenium::Waiter
+
+        [BUG FIXES]
+        - #184 Fix bug about reverting default finder in T:S:R:D
+
+0.23   01-25-2014
+        [NEW FEATURES]
+        - #178 Fix upload_file's return value & add test suite
+        - #179 Add optional middle finder-strategy argument to T:S:R:D's find_element-like functions
+        - #174 Add new endpoints: cache status, geolocation, log types, orientation, etc
+
+        [BUG FIXES]
+        - #175 Fix find_elements
+        - #180 Stop overwriting body_text_unlike test descriptions
+        - #141 Explicitly cast height and weight to integers for set_window_size
+
+0.2203 01-18-2014
+        [BUG FIXES]
+        - #175 Fix find_elements_ok
+
 0.2202 11-26-2014
         [BUG FIXES]
         - Generalize error message for invalid finder strategies

+ 61 - 0
INSTALL.md

@@ -0,0 +1,61 @@
+## Installation
+
+It's probably easiest to use the `cpanm` or `CPAN` commands:
+
+```bash
+$ cpanm Selenium::Remote::Driver
+```
+
+If you want to install from this repository, you have a few options;
+see the [installation docs][] for more details.
+
+[installation docs]: /install.md
+
+### With Dist::Zilla
+
+If you have Dist::Zilla, it's straightforward:
+
+```bash
+$ dzil listdeps --missing | cpanm
+$ dzil install
+```
+
+### Without Dist::Zilla
+
+We maintain two branches that have `Makefile.PL`:
+[`cpan`][cpan-branch] and [`build/master`][bm-branch]. The `cpan`
+branch is only updated every time we release to the CPAN, and it is
+not kept up to date with master. The `build/master` branch is an
+up-to-date copy of the latest changes in master, and will usually
+contain changes that have not made it to a CPAN release yet.
+
+To get either of these, you can use the following, (replacing
+"build/master" with "cpan" if desired):
+
+```bash
+$ cpanm -v git://github.com/gempesaw/Selenium-Remote-Driver.git@build/master
+```
+
+Or, without `cpanm` and/or without the `git://` protocol:
+
+```bash
+$ git clone https://github.com/gempesaw/Selenium-Remote-Driver --branch build/master --single-branch --depth 1
+$ cd Selenium-Remote-Driver
+$ perl Makefile.PL
+```
+
+Note that due to POD::Weaver, the line numbers between these generated
+branches and the master branch are unfortunately completely
+incompatible.
+
+[cpan-branch]: https://github.com/gempesaw/Selenium-Remote-Driver/tree/cpan
+[bm-branch]: https://github.com/gempesaw/Selenium-Remote-Driver/tree/build/master
+
+### Viewing dependencies
+
+You can also use `cpanm` to help you with dependencies after you've
+cloned the repository:
+
+```bash
+$ cpanm --showdeps .
+```

+ 49 - 67
README.md

@@ -20,72 +20,31 @@ already downloaded and started the
 
 ## Installation
 
-It's probably easiest to use cpanm:
+It's probably easiest to use the `cpanm` or `CPAN` commands:
 
 ```bash
 $ cpanm Selenium::Remote::Driver
 ```
 
-If you want to install from this repository, you have a few options:
+If you want to install from this repository, you have a few options;
+see the [installation docs][] for more details.
 
-### With Dist::Zilla
-
-If you have Dist::Zilla, it's straightforward:
-
-```bash
-$ dzil listdeps --missing | cpanm
-$ dzil install
-```
-
-### Without Dist::Zilla
-
-We maintain two branches that have `Makefile.PL`:
-[`cpan`][cpan-branch] and [`build/master`][bm-branch]. The `cpan`
-branch is only updated every time we release to the CPAN, and it is
-not kept up to date with master. The `build/master` branch is an
-up-to-date copy of the latest changes in master, and will usually
-contain changes that have not made it to a CPAN release yet.
-
-To get either of these, you can use the following, (replacing
-"build/master" with "cpan" if desired):
-
-```bash
-$ cpanm -v git://github.com/gempesaw/Selenium-Remote-Driver.git@build/master
-```
-
-Or, without `cpanm` and/or without the `git://` protocol:
-
-```bash
-$ git clone https://github.com/gempesaw/Selenium-Remote-Driver --branch build/master --single-branch --depth 1
-$ cd Selenium-Remote-Driver
-$ perl Makefile.PL
-```
-
-Note that due to POD::Weaver, the line numbers between these generated
-branches and the master branch are unfortunately completely
-incompatible.
-
-[cpan-branch]: https://github.com/gempesaw/Selenium-Remote-Driver/tree/cpan
-[bm-branch]: https://github.com/gempesaw/Selenium-Remote-Driver/tree/build/master
-
-### Viewing dependencies
-
-You can also use `cpanm` to help you with dependencies after you've
-cloned the repository:
-
-```bash
-$ cpanm --showdeps .
-```
+[installation docs]: /INSTALL.md
 
 ## Usage
 
-You'll need a Remote WebDriver Server running somewhere. You can
-download a [selenium-standalone-server.jar][standalone] and run one
-locally, or you can point your driver somewhere like [Saucelabs][s].
+You can either use this module with the standalone java server, or use
+it to directly start the webdriver binaries for you. Note that the
+latter option does _not_ require the JRE/JDK to be installed, nor does
+it require the selenium standalone server (despite the name of the
+main module!).
 
-[s]: http://saucelabs.com
+### with a standalone server
 
-### Locally
+Download the standalone server and have it running on port 4444; then
+the following should start up Firefox for you:
+
+#### Locally
 
 ```perl
 #! /usr/bin/perl
@@ -112,7 +71,7 @@ print $driver->get_title . "\n"; # CPAN Selenium Remote Driver - Google Search
 $driver->quit;
 ```
 
-### Saucelabs
+#### Saucelabs
 
 ```perl
 use Selenium::Remote::Driver;
@@ -141,20 +100,43 @@ useful [example snippets][ex].
 [ie]: https://github.com/gempesaw/Selenium-Remote-Driver/wiki/IE-browser-automation
 [chrome]: https://github.com/gempesaw/Selenium-Remote-Driver/wiki/Chrome-browser-automation
 [pjs]: https://github.com/gempesaw/Selenium-Remote-Driver/wiki/PhantomJS-Headless-Browser-Automation
-[ex]: https://github.com/gempesaw/Selenium-Remote-Driver/wiki/Example-Snippets
+[ex]:
+https://github.com/gempesaw/Selenium-Remote-Driver/wiki/Example-Snippets
+
+### no standalone server
+
+- _Firefox_: simply have the browser installed in the normal place
+for your OS.
+
+- _Chrome_: install the Chrome browser, [download Chromedriver][dcd]
+and get it in your `$PATH`:
+
+- _PhantomJS_: install the PhantomJS binary and get it in your `$PATH`
+
+As long as the proper binary is available in your path, you should be
+able to do the following:
 
-#### NB: Problems with Webdriver 2.42.x ?
+```perl
+my $firefox = Selenium::Firefox->new;
+$firefox->get('http://www.google.com');
+
+my $chrome = Selenium::Chrome->new;
+$chrome->get('http://www.google.com');
+
+my $ghost = Selenium::PhantomJS->new;
+$ghost->get('http://www.google.com');
+```
+
+Note that you can also pass a `binary` argument to any of the above
+classes to manually specify what binary to start:
+
+```perl
+my $chrome = Selenium::Chrome->new(binary => '~/Downloads/chromedriver');
+```
 
-It appears that the standalone webdriver API for no-content successful
-responses changed slightly in 2.42.x versions, breaking things like
-`get_ok` and `set_window_size`. Your options for fixes are:
+See the pod for the different modules for more details.
 
-* Upgrade your version of S::R::D via your preferred method! We've
-  released v0.2002 of S::R::D to CPAN, which contains the fixes to
-  address this.
-* Or, stick with v2.41.0 of the Selenium standalone server or lower
-  for your tests. v0.2001 of S::R::D still works with v2.41.0 of the
-  standalone server.
+[dcd]: https://sites.google.com/a/chromium.org/chromedriver/downloads
 
 ## Support and Documentation
 

+ 12 - 6
cpanfile

@@ -1,18 +1,24 @@
-requires "Archive::Extract" => "0";
 requires "Archive::Zip" => "0";
 requires "Carp" => "0";
 requires "Cwd" => "0";
 requires "Data::Dumper" => "0";
 requires "Exporter" => "0";
+requires "File::Basename" => "0";
 requires "File::Copy" => "0";
+requires "File::Spec" => "0";
+requires "File::Spec::Functions" => "0";
 requires "File::Temp" => "0";
+requires "File::Which" => "0";
 requires "HTTP::Headers" => "0";
 requires "HTTP::Request" => "0";
 requires "HTTP::Response" => "0";
 requires "IO::Compress::Zip" => "0";
 requires "IO::Socket" => "0";
+requires "IO::Socket::INET" => "0";
+requires "IO::Uncompress::Unzip" => "0";
 requires "JSON" => "0";
 requires "LWP::UserAgent" => "0";
+requires "List::MoreUtils" => "0";
 requires "MIME::Base64" => "0";
 requires "Moo" => "1.005";
 requires "Moo::Role" => "0";
@@ -22,6 +28,7 @@ requires "Sub::Install" => "0";
 requires "Test::Builder" => "0";
 requires "Test::LongString" => "0";
 requires "Try::Tiny" => "0";
+requires "XML::Simple" => "0";
 requires "base" => "0";
 requires "constant" => "0";
 requires "namespace::clean" => "0";
@@ -30,19 +37,18 @@ requires "strict" => "0";
 requires "warnings" => "0";
 
 on 'test' => sub {
-  requires "File::Basename" => "0";
   requires "File::stat" => "0";
   requires "FindBin" => "0";
-  requires "IO::Socket::INET" => "0";
-  requires "LWP::Simple" => "0";
-  requires "Test::Exception" => "0";
+  requires "Test::Fatal" => "0";
   requires "Test::LWP::UserAgent" => "0";
   requires "Test::More" => "0";
+  requires "Test::Warn" => "0";
+  requires "Time::Mock" => "0";
   requires "lib" => "0";
 };
 
 on 'configure' => sub {
-  requires "ExtUtils::MakeMaker" => "6.30";
+  requires "ExtUtils::MakeMaker" => "0";
 };
 
 on 'develop' => sub {

+ 5 - 7
dist.ini

@@ -1,5 +1,4 @@
 name = Selenium-Remote-Driver
-version = 0.2202
 author = Aditya Ivaturi <ivaturi@gmail.com>
 author = Daniel Gempesaw <gempesaw@gmail.com>
 author = Luke Closs <cpan@5thplane.com>
@@ -11,7 +10,6 @@ copyright_year = 2014
 [TravisYML]
 build_branch = /cpan|build/
 perl_version = 5.20 5.18 5.16 5.14 5.12 5.10
-dzil_branch =
 test_authordeps = 0
 test_deps = 0
 
@@ -20,6 +18,7 @@ allow_dirty = dist.ini
 allow_dirty = cpanfile
 allow_dirty = Changes
 [Git::Commit]
+[Git::NextVersion]
 [Git::Tag]
 ;[Git::Push]
 [Git::CommitBuild]
@@ -66,6 +65,7 @@ copy = cpanfile
 [PodCoverageTests]
 [PodSyntaxTests]
 
+[CheckChangesHasContent]
 [TestRelease]
 [ConfirmRelease]
 [UploadToCPAN]
@@ -74,11 +74,9 @@ copy = cpanfile
 perl = 5.010
 Moo = 1.005
 
-[MetaResources]
-bugtracker.web = https://github.com/gempesaw/Selenium-Remote-Driver/issues
-repository.type = git
-repository.url = git://github.com/gempesaw/Selenium-Remote-Driver.git
-repository.web = https://github.com/gempesaw/Selenium-Remote-Driver
+[GithubMeta]
+issues = 1
+user = gempesaw
 
 [Encoding]
 filename = t/www/icon.gif

+ 344 - 0
lib/Selenium/CanStartBinary.pm

@@ -0,0 +1,344 @@
+package Selenium::CanStartBinary;
+
+# ABSTRACT: Teach a WebDriver how to start its own binary aka no JRE!
+use File::Spec;
+use Selenium::CanStartBinary::ProbePort qw/find_open_port_above probe_port/;
+use Selenium::Firefox::Binary qw/setup_firefox_binary_env/;
+use Selenium::Waiter qw/wait_until/;
+use Moo::Role;
+
+=head1 SYNOPSIS
+
+    package My::Selenium::Chrome {
+        use Moo;
+        extends 'Selenium::Remote::Driver';
+
+        has 'binary' => ( is => 'ro', default => 'chromedriver' );
+        has 'binary_port' => ( is => 'ro', default => 9515 );
+        has '_binary_args' => ( is => 'ro', default => sub {
+            return ' --port=' . shift->port . ' --url-base=wd/hub ';
+        });
+        with 'Selenium::CanStartBinary';
+        1
+    };
+
+    my $chrome_via_binary = My::Selenium::Chrome->new;
+    my $chrome_with_path  = My::Selenium::Chrome->new(
+        binary => './chromedriver'
+    );
+
+=head1 DESCRIPTION
+
+This role takes care of the details for starting up a Webdriver
+instance. It does not do any downloading or installation of any sort -
+you're still responsible for obtaining and installing the necessary
+binaries into your C<$PATH> for this role to find. You may be
+interested in L<Selenium::Chrome>, L<Selenium::Firefox>, or
+L<Selenium::PhantomJS> if you're looking for classes that already
+consume this role.
+
+The role determines whether or not it should try to do its own magic
+based on whether the consuming class is instantiated with a
+C<remote_server_addr> and/or C<port>.
+
+    # We'll start up the Chrome binary for you
+    my $chrome_via_binary = Selenium::Chrome->new;
+
+    # Look for a selenium server running on 4444.
+    my $chrome_via_server = Selenium::Chrome->new( port => 4444 );
+
+If they're missing, we assume the user wants to use a webdriver
+directly and act accordingly. We handle finding the proper associated
+binary (or you can specify it with L</binary>), figuring out what
+arguments it wants, setting up any necessary environments, and
+starting up the binary.
+
+There's a number of TODOs left over - namely Windows support is
+severely lacking, and we're pretty naive when we attempt to locate the
+executables on our own.
+
+In the following documentation, C<required> refers to when you're
+consuming the role, not the C<required> when you're instantiating a
+class that has already consumed the role.
+
+=attr binary
+
+Required: Specify the path to the executable in question, or the name
+of the executable for us to find via L<File::Which/which>.
+
+=cut
+
+requires 'binary';
+
+=attr binary_port
+
+Required: Specify a default port that for the webdriver binary to try
+to bind to. If that port is unavailable, we'll probe above that port
+until we find a valid one.
+
+=cut
+
+requires 'binary_port';
+
+=attr _binary_args
+
+Required: Specify the arguments that the particular binary needs in
+order to start up correctly. In particular, you may need to tell the
+binary about the proper port when we start it up, or that it should
+use a particular prefix to match up with the behavior of the Remote
+Driver server.
+
+If your binary doesn't need any arguments, just have the default be an
+empty string.
+
+=cut
+
+requires '_binary_args';
+
+=attr port
+
+The role will attempt to determine the proper port for us. Consuming
+roles should set a default port in L</binary_port> at which we will
+begin searching for an open port.
+
+Note that if we cannot locate a suitable L</binary>, port will be set
+to 4444 so we can attempt to look for a Selenium server at
+C<127.0.0.1:4444>.
+
+=cut
+
+has '+port' => (
+    is => 'lazy',
+    builder => sub {
+        my ($self) = @_;
+
+        if ($self->binary) {
+            return find_open_port_above($self->binary_port);
+        }
+        else {
+            return 4444
+        }
+    }
+);
+
+=attr binary_mode
+
+Mostly intended for internal use, its builder coordinates all the side
+effects of interacting with the binary: locating the executable,
+finding an open port, setting up the environment, shelling out to
+start the binary, and ensuring that the webdriver is listening on the
+correct port.
+
+If all of the above steps pass, it will return truthy after
+instantiation. If any of them fail, it should return falsy and the
+class should attempt normal L<Selenium::Remote::Driver> behavior.
+
+=cut
+
+has 'binary_mode' => (
+    is => 'lazy',
+    init_arg => undef,
+    builder => 1,
+    predicate => 1
+);
+
+has 'try_binary' => (
+    is => 'lazy',
+    default => sub { 0 },
+    trigger => sub {
+        my ($self) = @_;
+        $self->binary_mode if $self->try_binary;
+    }
+);
+
+=attr window_title
+
+Intended for internal use: this will build us a unique title for the
+background binary process of the Webdriver. Then, when we're cleaning
+up, we know what the window title is that we're going to C<taskkill>.
+
+=cut
+
+has 'window_title' => (
+    is => 'lazy',
+    init_arg => undef,
+    builder => sub {
+        my ($self) = @_;
+        my (undef, undef, $file) = File::Spec->splitpath( $self->binary );
+        my $port = $self->port;
+
+        return $file . ':' . $port;
+    }
+);
+
+use constant IS_WIN => $^O eq 'MSWin32';
+
+sub BUILDARGS {
+    # There's a bit of finagling to do to since we can't ensure the
+    # attribute instantiation order. To decide whether we're going into
+    # binary mode, we need the remote_server_addr and port. But, they're
+    # both lazy and only instantiated immediately before S:R:D's
+    # remote_conn attribute. Once remote_conn is set, we can't change it,
+    # so we need the following order:
+    #
+    #     parent: remote_server_addr, port
+    #     role:   binary_mode (aka _build_binary_mode)
+    #     parent: remote_conn
+    #
+    # Since we can't force an order, we introduced try_binary which gets
+    # decided during BUILDARGS to tip us off as to whether we should try
+    # binary mode or not.
+    my ( $class, %args ) = @_;
+
+    if ( ! exists $args{remote_server_addr} && ! exists $args{port} ) {
+        $args{try_binary} = 1;
+
+        # Windows may throw a fit about invalid pointers if we try to
+        # connect to localhost instead of 127.1
+        $args{remote_server_addr} = '127.0.0.1';
+    }
+    else {
+        $args{try_binary} = 0;
+        $args{binary_mode} = 0;
+    }
+
+    return { %args };
+}
+
+sub _build_binary_mode {
+    my ($self) = @_;
+
+    # We don't know what to do without a binary driver to start up
+    return unless $self->binary;
+
+    # Either the user asked for 4444, or we couldn't find an open port
+    my $port = $self->port + 0;
+    return if $port == 4444;
+
+    if ($self->isa('Selenium::Firefox')) {
+        my @args = ($port);
+
+        if ($self->has_firefox_profile) {
+            push @args, $self->firefox_profile;
+        }
+
+        setup_firefox_binary_env(@args);
+    }
+
+    my $command = $self->_construct_command;
+    system($command);
+
+    my $success = wait_until { probe_port($port) } timeout => 10;
+    if ($success) {
+        return 1;
+    }
+    else {
+        die 'Unable to connect to the ' . $self->binary . ' binary on port ' . $port;
+    }
+}
+
+sub shutdown_binary {
+    my ($self) = @_;
+
+    if ( $self->auto_close && defined $self->session_id ) {
+        $self->quit();
+    }
+
+    if ($self->has_binary_mode && $self->binary_mode) {
+        # Tell the binary itself to shutdown
+        my $port = $self->port;
+        my $ua = $self->ua;
+        my $res = $ua->get('http://127.0.0.1:' . $port . '/wd/hub/shutdown');
+
+        # Close the orphaned command windows on windows
+        $self->shutdown_windows_binary;
+    }
+}
+
+sub shutdown_windows_binary {
+    my ($self) = @_;
+
+    if (IS_WIN) {
+        if ($self->isa('Selenium::Firefox')) {
+            # FIXME: Blech, handle a race condition that kills the
+            # driver before it's finished cleaning up its sessions. In
+            # particular, when the perl process ends, it wants to
+            # clean up the temp directory it created for the Firefox
+            # profile. But, if the Firefox process is still running,
+            # it will have a lock on the temp profile directory, and
+            # perl will get upset. This "solution" is _very_ bad.
+            sleep(2);
+            # Firefox doesn't have a Driver/Session architecture - the
+            # only thing running is Firefox itself, so there's no
+            # other task to kill.
+            return;
+        }
+        else {
+            my $kill = 'taskkill /FI "WINDOWTITLE eq ' . $self->window_title . '" > nul 2>&1';
+            system($kill);
+        }
+    }
+}
+
+# We want to do things before the DEMOLISH phase, as during DEMOLISH
+# we apparently have no guarantee that anything is still around
+before DEMOLISH => sub {
+    my ($self) = @_;
+    $self->shutdown_binary;
+};
+
+sub _construct_command {
+    my ($self) = @_;
+    my $executable = $self->binary;
+
+    # Executable path names may have spaces
+    $executable = '"' . $executable . '"';
+
+    # The different binaries take different arguments for proper setup
+    $executable .= $self->_binary_args;
+
+    # Handle Windows vs Unix discrepancies for invoking shell commands
+    my ($prefix, $suffix) = ($self->_cmd_prefix, $self->_cmd_suffix);
+    return join(' ', ($prefix, $executable, $suffix) );
+}
+
+sub _cmd_prefix {
+    my ($self) = @_;
+
+    if (IS_WIN) {
+        my $prefix = 'start "' . $self->window_title . '"';
+
+        # Let's minimize the command windows for the drivers that have
+        # separate binaries - but let's not minimize the Firefox
+        # window itself.
+        if (! $self->isa('Selenium::Firefox')) {
+            $prefix .= ' /MIN ';
+        }
+        return $prefix;
+    }
+    else {
+        return '';
+    }
+}
+
+sub _cmd_suffix {
+    # TODO: allow users to specify whether & where they want driver
+    # output to go
+
+    if (IS_WIN) {
+        return ' > /nul 2>&1 ';
+    }
+    else {
+        return ' > /dev/null 2>&1 &';
+    }
+}
+
+=head1 SEE ALSO
+
+Selenium::Chrome
+Selenium::Firefox
+Selenium::PhantomJS
+
+=cut
+
+1;

+ 70 - 0
lib/Selenium/CanStartBinary/FindBinary.pm

@@ -0,0 +1,70 @@
+package Selenium::CanStartBinary::FindBinary;
+
+# ABSTRACT: Coercions for finding webdriver binaries on your system
+use File::Which qw/which/;
+use Cwd qw/abs_path/;
+use File::Which qw/which/;
+use IO::Socket::INET;
+use Selenium::Firefox::Binary qw/firefox_path/;
+
+require Exporter;
+our @ISA = qw/Exporter/;
+our @EXPORT_OK = qw/coerce_simple_binary coerce_firefox_binary/;
+
+use constant IS_WIN => $^O eq 'MSWin32';
+
+sub coerce_simple_binary {
+    my ($executable) = @_;
+
+    my $manual_binary = _validate_manual_binary($executable);
+    if ($manual_binary) {
+        return $manual_binary;
+    }
+    else {
+        return _naive_find_binary($executable);
+    }
+}
+
+sub coerce_firefox_binary {
+    my ($executable) = @_;
+
+    my $manual_binary = _validate_manual_binary($executable);
+    if ($manual_binary) {
+        return $manual_binary;
+    }
+    else {
+        return firefox_path();
+    }
+}
+
+sub _validate_manual_binary {
+    my ($executable) = @_;
+
+    my $abs_executable = eval {
+        my $path = abs_path($executable);
+        die unless -e $path;
+        $path
+    };
+
+    if ( $abs_executable ) {
+        if ( -x $abs_executable || IS_WIN ) {
+            return $abs_executable;
+        }
+        else {
+            die 'The binary at ' . $executable . ' is not executable. Choose the correct file or chmod +x it as needed.';
+        }
+    }
+}
+
+sub _naive_find_binary {
+    my ($executable) = @_;
+
+    my $naive_binary = which($executable);
+    if (defined $naive_binary) {
+        return $naive_binary;
+    }
+    else {
+        warn qq(Unable to find the $naive_binary binary in your \$PATH. We'll try falling back to standard Remote Driver);
+        return;
+    }
+}

+ 35 - 0
lib/Selenium/CanStartBinary/ProbePort.pm

@@ -0,0 +1,35 @@
+package Selenium::CanStartBinary::ProbePort;
+
+# ABSTRACT: Utility functions for finding open ports to eventually bind to
+use IO::Socket::INET;
+use Selenium::Waiter qw/wait_until/;
+
+require Exporter;
+our @ISA = qw/Exporter/;
+our @EXPORT_OK = qw/find_open_port_above probe_port/;
+
+sub find_open_port_above {
+    my ($port) = @_;
+
+    my $free_port = wait_until {
+        if ( probe_port($port) ) {
+            $port++;
+            return 0;
+        }
+        else {
+            return $port;
+        }
+    };
+
+    return $free_port;
+}
+
+sub probe_port {
+    my ($port) = @_;
+
+    return IO::Socket::INET->new(
+        PeerAddr => '127.0.0.1',
+        PeerPort => $port,
+        Timeout => 3
+    );
+}

+ 82 - 0
lib/Selenium/Chrome.pm

@@ -0,0 +1,82 @@
+package Selenium::Chrome;
+
+# ABSTRACT: Use ChromeDriver without a Selenium server
+use Moo;
+use Selenium::CanStartBinary::FindBinary qw/coerce_simple_binary/;
+extends 'Selenium::Remote::Driver';
+
+=head1 SYNOPSIS
+
+    my $driver = Selenium::Chrome->new;
+
+=head1 DESCRIPTION
+
+This class allows you to use the ChromeDriver without needing the JRE
+or a selenium server running. When you refrain from passing the
+C<remote_server_addr> and C<port> arguments, we will search for the
+chromedriver executable binary in your $PATH. We'll try to start the
+binary connect to it, shutting it down at the end of the test.
+
+If the chromedriver binary is not found, we'll fall back to the
+default L<Selenium::Remote::Driver> behavior of assuming defaults of
+127.0.0.1:4444 after waiting a few seconds.
+
+If you specify a remote server address, or a port, we'll assume you
+know what you're doing and take no additional behavior.
+
+If you're curious whether your Selenium::Chrome instance is using a
+separate ChromeDriver binary, or through the selenium server, you can
+check the C<binary_mode> attr after instantiation.
+
+=cut
+
+has '+browser_name' => (
+    is => 'ro',
+    default => sub { 'chrome' }
+);
+
+=attr binary
+
+Optional: specify the path to your binary. If you don't specify
+anything, we'll try to find it on our own via L<File::Which/which>.
+
+=cut
+
+has 'binary' => (
+    is => 'lazy',
+    coerce => \&coerce_simple_binary,
+    default => sub { 'chromedriver' },
+    predicate => 1
+);
+
+=attr binary_port
+
+Optional: specify the port that we should bind to. If you don't
+specify anything, we'll default to the driver's default port. Since
+there's no a priori guarantee that this will be an open port, this is
+_not_ necessarily the port that we end up using - if the port here is
+already bound, we'll search above it until we find an open one.
+
+See L<Selenium::CanStartBinary/port> for more details, and
+L<Selenium::Remote::Driver/port> after instantiation to see what the
+actual port turned out to be.
+
+=cut
+
+has 'binary_port' => (
+    is => 'lazy',
+    default => sub { 9515 }
+);
+
+has '_binary_args' => (
+    is => 'lazy',
+    builder => sub {
+        my ($self) = @_;
+
+        return ' --port=' . $self->port . ' --url-base=wd/hub ';
+    }
+);
+
+with 'Selenium::CanStartBinary';
+
+1;

+ 84 - 0
lib/Selenium/Firefox.pm

@@ -0,0 +1,84 @@
+package Selenium::Firefox;
+
+# ABSTRACT: Use FirefoxDriver without a Selenium server
+use Moo;
+use Selenium::CanStartBinary::FindBinary qw/coerce_firefox_binary/;
+extends 'Selenium::Remote::Driver';
+
+=head1 SYNOPSIS
+
+    my $driver = Selenium::Firefox->new;
+
+=head1 DESCRIPTION
+
+This class allows you to use the FirefoxDriver without needing the JRE
+or a selenium server running. When you refrain from passing the
+C<remote_server_addr> and C<port> arguments, we will search for the
+Firefox executable in your $PATH. We'll try to start the binary
+connect to it, shutting it down at the end of the test.
+
+If the Firefox application is not found in the expected places, we'll
+fall back to the default L<Selenium::Remote::Driver> behavior of
+assuming defaults of 127.0.0.1:4444 after waiting a few seconds.
+
+If you specify a remote server address, or a port, we'll assume you
+know what you're doing and take no additional behavior.
+
+If you're curious whether your Selenium::Firefox instance is using a
+separate Firefox binary, or through the selenium server, you can check
+the C<binary_mode> attr after instantiation.
+
+=cut
+
+has '+browser_name' => (
+    is => 'ro',
+    default => sub { 'firefox' }
+);
+
+=attr binary
+
+Optional: specify the path to your binary. If you don't specify
+anything, we'll try to find it on our own in the default installation
+paths for Firefox. If your Firefox is elsewhere, we probably won't be
+able to find it, so you may be well served by specifying it yourself.
+
+=cut
+
+has 'binary' => (
+    is => 'lazy',
+    coerce => \&coerce_firefox_binary,
+    default => sub { 'firefox' },
+    predicate => 1
+);
+
+=attr binary_port
+
+Optional: specify the port that we should bind to. If you don't
+specify anything, we'll default to the driver's default port. Since
+there's no a priori guarantee that this will be an open port, this is
+_not_ necessarily the port that we end up using - if the port here is
+already bound, we'll search above it until we find an open one.
+
+See L<Selenium::CanStartBinary/port> for more details, and
+L<Selenium::Remote::Driver/port> after instantiation to see what the
+actual port turned out to be.
+
+=cut
+
+has 'binary_port' => (
+    is => 'lazy',
+    default => sub { 9090 }
+);
+
+has '_binary_args' => (
+    is => 'lazy',
+    builder => sub {
+        my ($self) = @_;
+
+        return ' -no-remote';
+    }
+);
+
+with 'Selenium::CanStartBinary';
+
+1;

+ 79 - 0
lib/Selenium/Firefox/Binary.pm

@@ -0,0 +1,79 @@
+package Selenium::Firefox::Binary;
+
+# ABSTRACT: Subroutines for locating and properly initializing the Firefox Binary
+use File::Which qw/which/;
+use Selenium::Firefox::Profile;
+
+require Exporter;
+our @ISA = qw/Exporter/;
+our @EXPORT_OK = qw/firefox_path setup_firefox_binary_env/;
+
+sub _firefox_windows_path {
+    # TODO: make this slightly less dumb
+    my @program_files = (
+        $ENV{PROGRAMFILES} // 'C:\Program Files',
+        $ENV{'PROGRAMFILES(X86)'} // 'C:\Program Files (x86)',
+    );
+
+    foreach (@program_files) {
+        my $binary_path = $_ . '\Mozilla Firefox\firefox.exe';
+        return $binary_path if -x $binary_path;
+    }
+
+    # Fall back to a completely naive strategy
+    warn q/We couldn't find a viable firefox.EXE; you may want to specify it via the binary attribute./;
+    return which('firefox');
+}
+
+sub _firefox_darwin_path {
+    my $default_firefox = '/Applications/Firefox.app/Contents/MacOS/firefox-bin';
+
+    if (-e $default_firefox && -x $default_firefox) {
+        return $default_firefox
+    }
+    else {
+        return which('firefox-bin');
+    }
+}
+
+sub _firefox_unix_path {
+    # TODO: maybe which('firefox3'), which('firefox2') ?
+    return which('firefox') || '/usr/bin/firefox';
+}
+
+sub firefox_path {
+    my $path;
+    if ($^O eq 'MSWin32') {
+        $path =_firefox_windows_path();
+    }
+    elsif ($^O eq 'darwin') {
+        $path = _firefox_darwin_path();
+    }
+    else {
+        $path = _firefox_unix_path;
+    }
+
+    if (not -x $path) {
+        die $path . ' is not an executable file.';
+    }
+
+    return $path;
+}
+
+# We want the profile to persist to the end of the session, not just
+# the end of this function.
+my $profile;
+sub setup_firefox_binary_env {
+    my ($port, $caller_profile) = @_;
+
+    $profile = $caller_profile || Selenium::Firefox::Profile->new;
+    $profile->add_webdriver($port);
+
+    $ENV{'XRE_PROFILE_PATH'} = $profile->_layout_on_disk;
+    $ENV{'MOZ_NO_REMOTE'} = '1';             # able to launch multiple instances
+    $ENV{'MOZ_CRASHREPORTER_DISABLE'} = '1'; # disable breakpad
+    $ENV{'NO_EM_RESTART'} = '1';             # prevent the binary from detaching from the console.log
+}
+
+
+1;

+ 286 - 0
lib/Selenium/Firefox/Profile.pm

@@ -0,0 +1,286 @@
+package Selenium::Firefox::Profile;
+
+# ABSTRACT: Use custom profiles with Selenium::Remote::Driver
+# TODO: convert this to Moo!
+
+use strict;
+use warnings;
+
+use Archive::Zip qw( :ERROR_CODES );
+use Carp qw(croak);
+use Cwd qw(abs_path);
+use File::Copy qw(copy);
+use File::Temp;
+use File::Basename qw(dirname);
+use IO::Uncompress::Unzip qw(unzip $UnzipError);
+use JSON qw/decode_json/;
+use MIME::Base64;
+use Scalar::Util qw(blessed looks_like_number);
+use XML::Simple;
+
+=head1 DESCRIPTION
+
+You can use this module to create a custom Firefox Profile for your
+Selenium tests. Currently, you can set browser preferences and add
+extensions to the profile before passing it in the constructor for a
+new Selenium::Remote::Driver.
+
+=head1 SYNPOSIS
+
+    use Selenium::Remote::Driver;
+    use Selenium::Firefox::Profile;
+
+    my $profile = Selenium::Firefox::Profile->new;
+    $profile->set_preference(
+        'browser.startup.homepage' => 'http://www.google.com',
+        'browser.cache.disk.capacity' => 358400
+    );
+
+    $profile->set_boolean_preference(
+        'browser.shell.checkDefaultBrowser' => 0
+    );
+
+    $profile->add_extension('t/www/redisplay.xpi');
+
+    my $driver = Selenium::Remote::Driver->new(
+        'firefox_profile' => $profile
+    );
+
+    $driver->get('http://www.google.com');
+    print $driver->get_title();
+
+=cut
+
+sub new {
+    my $class = shift;
+
+    # TODO: add handling for a pre-existing profile folder passed into
+    # the constructor
+
+    # TODO: accept user prefs, boolean prefs, and extensions in
+    # constructor
+    my $self = {
+        profile_dir => File::Temp->newdir(),
+        user_prefs => {},
+        extensions => []
+      };
+    bless $self, $class or die "Can't bless $class: $!";
+
+    return $self;
+}
+
+=method set_preference
+
+Set string and integer preferences on the profile object. You can set
+multiple preferences at once. If you need to set a boolean preference,
+either use JSON::true/JSON::false, or see C<set_boolean_preference()>.
+
+    $profile->set_preference("quoted.integer.pref" => '"20140314220517"');
+    # user_pref("quoted.integer.pref", "20140314220517");
+
+    $profile->set_preference("plain.integer.pref" => 9005);
+    # user_pref("plain.integer.pref", 9005);
+
+    $profile->set_preference("string.pref" => "sample string value");
+    # user_pref("string.pref", "sample string value");
+
+=cut
+
+sub set_preference {
+    my ($self, %prefs) = @_;
+
+    foreach (keys %prefs) {
+        my $value = $prefs{$_};
+        my $clean_value = '';
+
+        if ( JSON::is_bool($value) ) {
+            $self->set_boolean_preference($_, $value );
+            next;
+        }
+        elsif ($value =~ /^(['"]).*\1$/ or looks_like_number($value)) {
+            # plain integers: 0, 1, 32768, or integers wrapped in strings:
+            # "0", "1", "20140204". in either case, there's nothing for us
+            # to do.
+            $clean_value = $value;
+        }
+        else {
+            # otherwise it's hopefully a string that we'll need to
+            # quote on our own
+            $clean_value = '"' . $value . '"';
+        }
+
+        $self->{user_prefs}->{$_} = $clean_value;
+    }
+}
+
+=method set_boolean_preference
+
+Set preferences that require boolean values of 'true' or 'false'. You
+can set multiple preferences at once. For string or integer
+preferences, use C<set_preference()>.
+
+    $profile->set_boolean_preference("false.pref" => 0);
+    # user_pref("false.pref", false);
+
+    $profile->set_boolean_preference("true.pref" => 1);
+    # user_pref("true.pref", true);
+
+=cut
+
+sub set_boolean_preference {
+    my ($self, %prefs) = @_;
+
+    foreach (keys %prefs) {
+        my $value = $prefs{$_};
+
+        $self->{user_prefs}->{$_} = $value ? 'true' : 'false';
+    }
+}
+
+=method get_preference
+
+Retrieve the computed value of a preference. Strings will be double
+quoted and boolean values will be single quoted as "true" or "false"
+accordingly.
+
+    $profile->set_boolean_preference("true.pref" => 1);
+    print $profile->get_preference("true.pref") # true
+
+    $profile->set_preference("string.pref" => "an extra set of quotes");
+    print $profile->get_preference("string.pref") # "an extra set of quotes"
+
+=cut
+
+sub get_preference {
+    my ($self, $pref) = @_;
+
+    return $self->{user_prefs}->{$pref};
+}
+
+=method add_extension
+
+Add an existing C<.xpi> to the profile by providing its path. This
+only works with packaged C<.xpi> files, not plain/un-packed extension
+directories.
+
+    $profile->add_extension('t/www/redisplay.xpi');
+
+=cut
+
+sub add_extension {
+    my ($self, $xpi) = @_;
+
+    croak 'File not found: ' . $xpi unless -e $xpi;
+    my $xpi_abs_path = abs_path($xpi);
+    croak '$xpi_abs_path: extensions must be in .xpi format' unless $xpi_abs_path =~ /\.xpi$/;
+
+    push (@{$self->{extensions}}, $xpi_abs_path);
+}
+
+=method add_webdriver
+
+Primarily for internal use, we add the webdriver extension to the
+current Firefox profile.
+
+=cut
+
+sub add_webdriver {
+    my ($self, $port) = @_;
+
+    my $this_dir = dirname(abs_path(__FILE__));
+    my $webdriver_extension = $this_dir . '/webdriver.xpi';
+    my $default_prefs_filename = $this_dir . '/webdriver_prefs.json';
+
+    my $json;
+    {
+        local $/;
+        open (my $fh, '<', $default_prefs_filename);
+        $json = <$fh>;
+        close ($fh);
+    }
+    my $webdriver_prefs = decode_json($json);
+
+    # TODO: Let the user's mutable preferences persist instead of
+    # overwriting them here.
+    $self->set_preference(%{ $webdriver_prefs->{mutable} });
+    $self->set_preference(%{ $webdriver_prefs->{frozen} });
+
+    $self->add_extension($webdriver_extension);
+    $self->set_preference('webdriver_firefox_port', $port);
+}
+
+sub _encode {
+    my $self = shift;
+
+    # The remote webdriver accepts the Firefox profile as a base64
+    # encoded zip file
+    $self->_layout_on_disk();
+
+    my $zip = Archive::Zip->new();
+    my $dir_member = $zip->addTree( $self->{profile_dir} );
+
+    my $string = "";
+    open (my $fh, ">", \$string);
+    binmode($fh);
+    unless ( $zip->writeToFileHandle($fh) == AZ_OK ) {
+        die 'write error';
+    }
+
+    return encode_base64($string);
+}
+
+sub _layout_on_disk {
+    my $self = shift;
+
+    $self->_write_preferences();
+    $self->_install_extensions();
+
+    return $self->{profile_dir};
+}
+
+sub _write_preferences {
+    my $self = shift;
+
+    my $userjs = $self->{profile_dir} . "/user.js";
+    open (my $fh, ">>", $userjs)
+        or die "Cannot open $userjs for writing preferences: $!";
+
+    foreach (keys %{$self->{user_prefs}}) {
+        print $fh 'user_pref("' . $_ . '", ' . $self->get_preference($_) . ');' . "\n";
+    }
+    close ($fh);
+}
+
+sub _install_extensions {
+    my $self = shift;
+    my $extension_dir = $self->{profile_dir} . "/extensions/";
+    mkdir $extension_dir unless -d $extension_dir;
+
+    # TODO: handle extensions that need to be unpacked
+    foreach my $xpi (@{$self->{extensions}}) {
+        # For Firefox to recognize the extension, we have to put the
+        # .xpi in the /extensions/ folder and change the filename to
+        # its id, which is found in the install.rdf in the root of the
+        # zip.
+
+        my $fh;
+        unzip $xpi => \$fh, Name => "install.rdf"
+          or die "unzip failed: $UnzipError\n";
+
+        my $rdf = XMLin($fh);
+        my $name = $rdf->{Description}->{'em:id'};
+
+        my $xpi_dest = $extension_dir . $name . ".xpi";
+        copy($xpi, $xpi_dest)
+            or croak "Error copying $_ to $xpi_dest : $!";
+    }
+}
+
+1;
+
+__END__
+
+=head1 SEE ALSO
+
+http://kb.mozillazine.org/About:config_entries
+https://developer.mozilla.org/en-US/docs/Mozilla/Preferences/A_brief_guide_to_Mozilla_preferences

BIN
lib/Selenium/Firefox/amd64/libibushandler.so


BIN
lib/Selenium/Firefox/amd64/x_ignore_nofocus.so


BIN
lib/Selenium/Firefox/webdriver.xpi


+ 68 - 0
lib/Selenium/Firefox/webdriver_prefs.json

@@ -0,0 +1,68 @@
+{
+  "frozen": {
+    "app.update.auto": false,
+    "app.update.enabled": false,
+    "browser.download.manager.showWhenStarting": false,
+    "browser.EULA.override": true,
+    "browser.EULA.3.accepted": true,
+    "browser.link.open_external": 2,
+    "browser.link.open_newwindow": 2,
+    "browser.offline": false,
+    "browser.safebrowsing.enabled": false,
+    "browser.safebrowsing.malware.enabled": false,
+    "browser.search.update": false,
+    "browser.sessionstore.resume_from_crash": false,
+    "browser.shell.checkDefaultBrowser": false,
+    "browser.tabs.warnOnClose": false,
+    "browser.tabs.warnOnOpen": false,
+    "datareporting.healthreport.service.enabled": false,
+    "datareporting.healthreport.uploadEnabled": false,
+    "datareporting.healthreport.service.firstRun": false,
+    "datareporting.healthreport.logging.consoleEnabled": false,
+    "datareporting.policy.dataSubmissionEnabled": false,
+    "datareporting.policy.dataSubmissionPolicyAccepted": false,
+    "devtools.errorconsole.enabled": true,
+    "dom.disable_open_during_load": false,
+    "extensions.autoDisableScopes": 10,
+    "extensions.blocklist.enabled": false,
+    "extensions.logging.enabled": true,
+    "extensions.update.enabled": false,
+    "extensions.update.notifyUser": false,
+    "network.manage-offline-status": false,
+    "network.http.phishy-userpass-length": 255,
+    "offline-apps.allow_by_default": true,
+    "prompts.tab_modal.enabled": false,
+    "security.csp.enable": false,
+    "security.fileuri.origin_policy": 3,
+    "security.fileuri.strict_origin_policy": false,
+    "security.warn_entering_secure": false,
+    "security.warn_entering_secure.show_once": false,
+    "security.warn_entering_weak": false,
+    "security.warn_entering_weak.show_once": false,
+    "security.warn_leaving_secure": false,
+    "security.warn_leaving_secure.show_once": false,
+    "security.warn_submit_insecure": false,
+    "security.warn_viewing_mixed": false,
+    "security.warn_viewing_mixed.show_once": false,
+    "signon.rememberSignons": false,
+    "toolkit.networkmanager.disable": true,
+    "toolkit.telemetry.prompted": 2,
+    "toolkit.telemetry.enabled": false,
+    "toolkit.telemetry.rejected": true
+  },
+  "mutable": {
+    "browser.dom.window.dump.enabled": true,
+    "browser.newtab.url": "about:blank",
+    "browser.newtabpage.enabled": false,
+    "browser.startup.page": 0,
+    "browser.startup.homepage": "about:blank",
+    "dom.max_chrome_script_run_time": 30,
+    "dom.max_script_run_time": 30,
+    "dom.report_all_js_exceptions": true,
+    "javascript.options.showInConsole": true,
+    "network.http.max-connections-per-server": 10,
+    "startup.homepage_welcome_url": "about:blank",
+    "webdriver_accept_untrusted_certs": true,
+    "webdriver_assume_untrusted_issuer": true
+  }
+}

BIN
lib/Selenium/Firefox/x86/libibushandler.so


BIN
lib/Selenium/Firefox/x86/x_ignore_nofocus.so


+ 23 - 0
lib/Selenium/InternetExplorer.pm

@@ -0,0 +1,23 @@
+package Selenium::InternetExplorer;
+
+# ABSTRACT: A convenience package for creating a IE instance
+use Moo;
+extends 'Selenium::Remote::Driver';
+
+=head1 SYNOPSIS
+
+    my $driver = Selenium::InternetExplorer->new;
+
+=cut
+
+has '+browser_name' => (
+    is => 'ro',
+    default => sub { 'internet_explorer' }
+);
+
+has '+platform' => (
+    is => 'ro',
+    default => sub { 'WINDOWS' }
+);
+
+1;

+ 95 - 0
lib/Selenium/PhantomJS.pm

@@ -0,0 +1,95 @@
+package Selenium::PhantomJS;
+
+# ABSTRACT: Use GhostDriver without a Selenium server
+use Moo;
+use Selenium::CanStartBinary::FindBinary qw/coerce_simple_binary/;
+extends 'Selenium::Remote::Driver';
+
+=head1 SYNOPSIS
+
+    my $driver = Selenium::PhantomJS->new;
+
+=head1 DESCRIPTION
+
+This class allows you to use PhantomJS via Ghostdriver without needing
+the JRE or a selenium server running. When you refrain from passing
+the C<remote_server_addr> and C<port> arguments, we will search for
+the phantomjs executable binary in your $PATH. We'll try to start the
+binary connect to it, shutting it down at the end of the test.
+
+If the binary is not found, we'll fall back to the default
+L<Selenium::Remote::Driver> behavior of assuming defaults of
+127.0.0.1:4444 after waiting a few seconds.
+
+If you specify a remote server address, or a port, we'll assume you
+know what you're doing and take no additional behavior.
+
+If you're curious whether your Selenium::PhantomJS instance is using a
+separate PhantomJS binary, or through the selenium server, you can check
+the C<binary_mode> attr after instantiation.
+
+    my $driver = Selenium::PhantomJS->new;
+    print $driver->binary_mode;
+
+N.B. - if you're using Windows and you installed C<phantomjs> via
+C<npm install -g phantomjs>, there is a very high probability that we
+will _not_ close down your phantomjs binary correctly after your
+test. You will be able to tell if we leave around empty command
+windows that you didn't start yourself. The easiest way to fix this is
+to download PhantomJS manually from their
+L<website|http://phantomjs.org/download.html> and put it in your
+C<%PATH%>. If this is a blocking issue for you, let us know in
+L<Github|https://github.com/gempesaw/Selenium-Remote-Driver>; thanks!
+
+=cut
+
+has '+browser_name' => (
+    is => 'ro',
+    default => sub { 'phantomjs' }
+);
+
+=attr binary
+
+Optional: specify the path to your binary. If you don't specify
+anything, we'll try to find it on our own via L<File::Which/which>.
+
+=cut
+
+has 'binary' => (
+    is => 'lazy',
+    coerce => \&coerce_simple_binary,
+    default => sub { 'phantomjs' },
+    predicate => 1
+);
+
+=attr binary_port
+
+Optional: specify the port that we should bind to. If you don't
+specify anything, we'll default to the driver's default port. Since
+there's no a priori guarantee that this will be an open port, this is
+_not_ necessarily the port that we end up using - if the port here is
+already bound, we'll search above it until we find an open one.
+
+See L<Selenium::CanStartBinary/port> for more details, and
+L<Selenium::Remote::Driver/port> after instantiation to see what the
+actual port turned out to be.
+
+=cut
+
+has 'binary_port' => (
+    is => 'lazy',
+    default => sub { 8910 }
+);
+
+has '_binary_args' => (
+    is => 'lazy',
+    builder => sub {
+        my ($self) = @_;
+
+        return ' --webdriver=127.0.0.1:' . $self->port;
+    }
+);
+
+with 'Selenium::CanStartBinary';
+
+1;

+ 37 - 2
lib/Selenium/Remote/Commands.pm

@@ -347,7 +347,7 @@ has '_cmds' => (
             'uploadFile' => {
                 'method'             => 'POST',
                 'url'                => 'session/:sessionId/file',
-                'no_content_success' => 1
+                'no_content_success' => 0
             },
             'getLocalStorageItem' => {
                 'method'             => 'GET',
@@ -358,7 +358,42 @@ has '_cmds' => (
                 'method'             => 'DELETE',
                 'url'                => '/session/:sessionId/local_storage/key/:key',
                 'no_content_success' => 1
-            }
+            },
+            'cacheStatus' => {
+                'method'             => 'GET',
+                'url'                => 'session/:sessionId/application_cache/status',
+                'no_content_success' => 0
+            },
+            'setGeolocation' => {
+                'method'             => 'POST',
+                'url'                => 'session/:sessionId/location',
+                'no_content_success' => 1
+            },
+            'getGeolocation'   => {
+                'method'             => 'GET',
+                'url'                => 'session/:sessionId/location',
+                'no_content_success' => 0
+            },
+            'getLog' => {
+                'method'             => 'POST',
+                'url'                => 'session/:sessionId/log',
+                'no_content_success' => 0
+            },
+            'getLogTypes'   => {
+                'method'             => 'GET',
+                'url'                => 'session/:sessionId/log/types',
+                'no_content_success' => 0
+            },
+            'setOrientation' => {
+                'method'             => 'POST',
+                'url'                => 'session/:sessionId/orientation',
+                'no_content_success' => 1
+            },
+            'getOrientation'   => {
+                'method'             => 'GET',
+                'url'                => 'session/:sessionId/orientation',
+                'no_content_success' => 0
+            },
 
             # /session/:sessionId/local_storage
             # /session/:sessionId/local_storage/key/:key

+ 320 - 25
lib/Selenium/Remote/Driver.pm

@@ -20,6 +20,11 @@ use Scalar::Util;
 use Selenium::Remote::RemoteConnection;
 use Selenium::Remote::Commands;
 use Selenium::Remote::WebElement;
+use File::Spec::Functions ();
+use File::Basename ();
+use Sub::Install ();
+use Cwd ();
+use MIME::Base64 ();
 
 use constant FINDERS => {
     class             => 'class name',
@@ -34,6 +39,7 @@ use constant FINDERS => {
     xpath             => 'xpath',
 };
 
+
 =head1 SYNOPSIS
 
     use Selenium::Remote::Driver;
@@ -62,7 +68,16 @@ the Selenium Server (Selenium Server is a Java application).
 
 =cut
 
-=head1 USAGE (read this first)
+=head1 USAGE
+
+=head2 Without Standalone Server
+
+As of v0.25, it's possible to use this module without a standalone
+server - that is, you would not need the JRE or the JDK to run your
+Selenium tests. See L<Selenium::Chrome>, L<Selenium::PhantomJS>, and
+L<Selenium::Firefox> for details. If you'd like additional browsers
+besides these, give us a holler over in
+L<Github|https://github.com/gempesaw/Selenium-Remote-Driver/issues>.
 
 =head2 Remote Driver Response
 
@@ -101,10 +116,14 @@ For example, a testing-subclass may extend the web-element object with testing m
 
 =head1 TESTING
 
-If are writing automated tests using this module, make sure you also see
-L<Test::Selenium::Remote::Driver> which is also included in this distribution.
-It includes convenience testing methods for many of the selenum methods
-available here.
+If are writing automated tests using this module, you may be
+interested in L<Test::Selenium::Remote::Driver> which is also included
+in this distribution. It includes convenience testing methods for many
+of the selenum methods available here.
+
+Your other option is to use this module in conjunction with your
+choice of testing modules, like L<Test::Spec> or L<Test::More> as
+you please.
 
 =head1 FUNCTIONS
 
@@ -124,7 +143,7 @@ available here.
         'platform'             - <string>   - desired platform: {WINDOWS|XP|VISTA|MAC|LINUX|UNIX|ANY}
         'javascript'           - <boolean>  - whether javascript should be supported
         'accept_ssl_certs'     - <boolean>  - whether SSL certs should be accepted, default is true.
-        'firefox_profile'      - Profile    - Use S::R::D::Firefox::Profile to create a Firefox profile for the browser to use
+        'firefox_profile'      - Profile    - Use Selenium::Firefox::Profile to create a Firefox profile for the browser to use
         'proxy'                - HASH       - Proxy configuration with the following keys:
             'proxyType' - <string> - REQUIRED, Possible values are:
                 direct     - A direct connection - no proxy in use,
@@ -201,7 +220,6 @@ available here.
 =head2 new_from_caps
 
  Description:
-
     For experienced users who want complete control over the desired
     capabilities, use this alternative constructor along with the
     C<desired_capabilities> hash key in the init hash. Unlike "new",
@@ -256,6 +274,7 @@ has 'remote_server_addr' => (
     is      => 'rw',
     coerce  => sub { ( defined($_[0]) ? $_[0] : 'localhost' )},
     default => sub {'localhost'},
+    predicate => 1
 );
 
 has 'browser_name' => (
@@ -265,8 +284,7 @@ has 'browser_name' => (
 );
 
 has 'base_url' => (
-    is      => 'rw',
-    lazy    => 1,
+    is      => 'lazy',
     coerce  => sub {
         my $base_url = shift;
         $base_url =~ s|/$||;
@@ -285,6 +303,7 @@ has 'port' => (
     is      => 'rw',
     coerce  => sub { ( defined($_[0]) ? $_[0] : '4444' )},
     default => sub {'4444'},
+    predicate => 1
 );
 
 has 'version' => (
@@ -382,24 +401,22 @@ has 'firefox_profile' => (
     coerce    => sub {
         my $profile = shift;
         unless (Scalar::Util::blessed($profile)
-          && $profile->isa('Selenium::Remote::Driver::Firefox::Profile')) {
-            croak "firefox_profile should be a Selenium::Remote::Driver::Firefox::Profile\n";
+          && $profile->isa('Selenium::Firefox::Profile')) {
+            croak "firefox_profile should be a Selenium::Firefox::Profile\n";
         }
 
-        return $profile->_encode;
+        return $profile;
     },
     predicate => 'has_firefox_profile'
 );
 
 has 'desired_capabilities' => (
-    is        => 'rw',
-    lazy      => 1,
+    is        => 'lazy',
     predicate => 'has_desired_capabilities'
 );
 
 has 'inner_window_size' => (
-    is        => 'rw',
-    lazy      => 1,
+    is        => 'lazy',
     predicate => 1,
     coerce    => sub {
         my $size = shift;
@@ -417,6 +434,8 @@ has 'inner_window_size' => (
 
 );
 
+with 'Selenium::Remote::Finders';
+
 sub BUILD {
     my $self = shift;
 
@@ -435,6 +454,22 @@ sub BUILD {
         my $size = $self->inner_window_size;
         $self->set_inner_window_size(@$size);
     }
+
+    # Setup non-croaking, parameter versions of finders
+    foreach my $by (keys %{ $self->FINDERS }) {
+        my $finder_name = 'find_element_by_' . $by;
+        # In case we get instantiated multiple times, we don't want to
+        # install into the name space every time.
+        unless ($self->can($finder_name)) {
+            my $find_sub = $self->_build_find_by($by);
+
+            Sub::Install::install_sub({
+                code => $find_sub,
+                into => __PACKAGE__,
+                as   => $finder_name,
+            });
+        }
+    }
 }
 
 sub new_from_caps {
@@ -475,6 +510,8 @@ sub _execute_command {
                 }
                 elsif ( $resp->{cmd_return} ) {
                     if ( ref( $resp->{cmd_return} ) eq 'HASH' ) {
+                        $msg .= ": $res->{command}"
+                          if $res->{command};
                         $msg .= ": $resp->{cmd_return}->{error}->{msg}"
                           if $resp->{cmd_return}->{error}->{msg};
                         $msg .= ": $resp->{cmd_return}->{message}"
@@ -516,7 +553,7 @@ sub new_session {
 
     if ($args->{desiredCapabilities}->{browserName} =~ /firefox/i
           && $self->has_firefox_profile) {
-        $args->{desiredCapabilities}->{firefox_profile} = $self->firefox_profile;
+        $args->{desiredCapabilities}->{firefox_profile} = $self->firefox_profile->_encode;
     }
 
     $self->_request_new_session($args);
@@ -1391,7 +1428,6 @@ sub capture_screenshot {
     my ( $self, $filename ) = @_;
     croak '$filename is required' unless $filename;
 
-    require MIME::Base64;
     open( my $fh, '>', $filename );
     binmode $fh;
     print $fh MIME::Base64::decode_base64( $self->screenshot() );
@@ -1581,6 +1617,8 @@ sub set_window_size {
     if ( not defined $height and not defined $width ) {
         croak "height & width of browser are required";
     }
+    $height += 0;
+    $width += 0;
     my $res = { 'command' => 'setWindowSize', 'window_handle' => $window };
     my $params = { 'height' => $height, 'width' => $width };
     my $ret = $self->_execute_command( $res, $params );
@@ -1747,8 +1785,27 @@ sub get_page_source {
 =head2 find_element
 
  Description:
-    Search for an element on the page, starting from the document root. The
-    located element will be returned as a WebElement object.
+    Search for an element on the page, starting from the document
+    root. The located element will be returned as a WebElement
+    object. If the element cannot be found, we will CROAK, killing
+    your script. If you wish for a warning instead, use the
+    parameterized version of the finders:
+
+        find_element_by_class
+        find_element_by_class_name
+        find_element_by_css
+        find_element_by_id
+        find_element_by_link
+        find_element_by_link_text
+        find_element_by_name
+        find_element_by_partial_link_text
+        find_element_by_tag_name
+        find_element_by_xpath
+
+    These functions all take a single STRING argument: the locator
+    search target of the element you want. If the element is found, we
+    will receive a WebElement. Otherwise, we will return 0. Note that
+    invoking methods on 0 will of course kill your script.
 
  Input: 2 (1 optional)
     Required:
@@ -2000,6 +2057,46 @@ sub find_child_elements {
     }
 }
 
+=head2 find_element_by_class
+
+See L</find_element>.
+
+=head2 find_element_by_class_name
+
+See L</find_element>.
+
+=head2 find_element_by_css
+
+See L</find_element>.
+
+=head2 find_element_by_id
+
+See L</find_element>.
+
+=head2 find_element_by_link
+
+See L</find_element>.
+
+=head2 find_element_by_link_text
+
+See L</find_element>.
+
+=head2 find_element_by_name
+
+See L</find_element>.
+
+=head2 find_element_by_partial_link_text
+
+See L</find_element>.
+
+=head2 find_element_by_tag_name
+
+See L</find_element>.
+
+=head2 find_element_by_xpath
+
+See L</find_element>.
+
 =head2 get_active_element
 
  Description:
@@ -2027,6 +2124,167 @@ sub get_active_element {
     }
 }
 
+=head2 cache_status
+
+ Description:
+    Get the status of the html5 application cache.
+
+ Usage:
+    print $driver->cache_status;
+
+ Output:
+    <number> - Status code for application cache: {UNCACHED = 0, IDLE = 1, CHECKING = 2, DOWNLOADING = 3, UPDATE_READY = 4, OBSOLETE = 5}
+
+=cut
+
+sub cache_status {
+    my ($self) = @_;
+    my $res = { 'command' => 'cacheStatus' };
+    return $self->_execute_command($res);
+}
+
+=head2 set_geolocation
+
+ Description:
+    Set the current geographic location - note that your driver must
+    implement this endpoint, or else it will crash your session. At the
+    very least, it works in v2.12 of Chromedriver.
+
+ Input:
+    Required:
+        HASH: A hash with key C<location> whose value is a Location hashref. See
+        usage section for example.
+
+ Usage:
+    $driver->set_geolocation( location => {
+        latitude  => 40.714353,
+        longitude => -74.005973,
+        altitude  => 0.056747
+    });
+
+ Output:
+    BOOLEAN - success or failure
+
+=cut
+
+sub set_geolocation {
+    my ( $self, %params ) = @_;
+    my $res = { 'command' => 'setGeolocation' };
+    return $self->_execute_command( $res, \%params );
+}
+
+=head2 get_geolocation
+
+ Description:
+    Get the current geographic location. Note that your webdriver must
+    implement this endpoint - otherwise, it will crash your session. At
+    the time of release, we couldn't get this to work on the desktop
+    FirefoxDriver or desktop Chromedriver.
+
+ Usage:
+    print $driver->get_geolocation;
+
+ Output:
+    { latitude: number, longitude: number, altitude: number } - The current geo location.
+
+=cut
+
+sub get_geolocation {
+    my ($self) = @_;
+    my $res = { 'command' => 'getGeolocation' };
+    return $self->_execute_command($res);
+}
+
+=head2 get_log
+
+ Description:
+    Get the log for a given log type. Log buffer is reset after each request.
+
+ Input:
+    Required:
+        <STRING> - Type of log to retrieve:
+        {client|driver|browser|server}. There may be others available; see
+        get_log_types for a full list for your driver.
+
+ Usage:
+    $driver->get_log( $log_type );
+
+ Output:
+    <ARRAY|ARRAYREF> - An array of log entries since the most recent request.
+
+=cut
+
+sub get_log {
+    my ( $self, $type ) = @_;
+    my $res = { 'command' => 'getLog' };
+    return $self->_execute_command( $res, { type => $type });
+}
+
+=head2 get_log_types
+
+ Description:
+    Get available log types. By default, every driver should have client,
+    driver, browser, and server types, but there may be more available,
+    depending on your driver.
+
+ Usage:
+    my @types = $driver->get_log_types;
+    $driver->get_log($types[0]);
+
+ Output:
+    <ARRAYREF> - The list of log types.
+
+=cut
+
+sub get_log_types {
+    my ($self) = @_;
+    my $res = { 'command' => 'getLogTypes' };
+    return $self->_execute_command($res);
+}
+
+
+=head2 set_orientation
+
+ Description:
+    Set the browser orientation.
+
+ Input:
+    Required:
+        <STRING> - Orientation {LANDSCAPE|PORTRAIT}
+
+ Usage:
+    $driver->set_orientation( $orientation  );
+
+ Output:
+    BOOLEAN - success or failure
+
+=cut
+
+sub set_orientation {
+    my ( $self, $orientation ) = @_;
+    my $res = { 'command' => 'setOrientation' };
+    return $self->_execute_command( $res, { orientation => $orientation } );
+}
+
+=head2 get_orientation
+
+ Description:
+    Get the current browser orientation. Returns either LANDSCAPE|PORTRAIT.
+
+ Usage:
+    print $driver->get_orientation;
+
+ Output:
+    <STRING> - your orientation.
+
+=cut
+
+sub get_orientation {
+    my ($self) = @_;
+    my $res = { 'command' => 'getOrientation' };
+    return $self->_execute_command($res);
+}
+
 =head2 send_modifier
 
  Description:
@@ -2186,6 +2444,13 @@ sub button_up {
     machine. That file then can be used for testing file upload on web
     forms. Returns the remote-server's path to the file.
 
+    Passing raw data as an argument past the filename will upload
+    that rather than the file's contents.
+
+    When passing raw data, be advised that it expects a zipped
+    and then base64 encoded version of a single file.
+    Multiple files are not supported by the remote server.
+
  Usage:
     my $remote_fname = $driver->upload_file( $fname );
     my $element = $driver->find_element( '//input[@id="file"]' );
@@ -2197,18 +2462,48 @@ sub button_up {
 # org.openqa.selenium.remote.RemoteWebElement java class.
 
 sub upload_file {
-    my ( $self, $filename ) = @_;
+    my ( $self, $filename, $raw_content ) = @_;
+
+    my $params;
+    if (defined $raw_content) {
+        #If no processing is passed, send the argument raw
+        $params = {
+            file => $raw_content
+        };
+    }
+    else {
+        #Otherwise, zip/base64 it.
+        $params = $self->_prepare_file($filename);
+    }
+
+    my $res = { 'command' => 'uploadFile' };    # /session/:SessionId/file
+    my $ret = $self->_execute_command( $res, $params );
+
+    #WORKAROUND: Since this is undocumented selenium functionality,
+    #work around a bug.
+    my ($drive, $path, $file) = File::Spec::Functions::splitpath($ret);
+    if ($file ne $filename) {
+        $ret = File::Spec::Functions::catpath($drive,$path,$filename);
+    }
+
+    return $ret;
+}
+
+sub _prepare_file {
+    my ($self,$filename) = @_;
+
+    #Apparently zip chokes on non-canonical paths, creating double
+    #submissions sometimes
+    $filename = Cwd::abs_path($filename);
+
     if ( not -r $filename ) { die "upload_file: no such file: $filename"; }
     my $string = "";    # buffer
     zip $filename => \$string
       or die "zip failed: $ZipError\n";    # compress the file into string
-    my $res = { 'command' => 'uploadFile' };    # /session/:SessionId/file
-    require MIME::Base64;
 
-    my $params = {
+    return {
         file => MIME::Base64::encode_base64($string)          # base64-encoded string
     };
-    return $self->_execute_command( $res, $params );
 }
 
 =head2 get_text

+ 8 - 236
lib/Selenium/Remote/Driver/Firefox/Profile.pm

@@ -1,254 +1,26 @@
 package Selenium::Remote::Driver::Firefox::Profile;
 
 # ABSTRACT: Use custom profiles with Selenium::Remote::Driver
-
 use strict;
 use warnings;
+use Selenium::Firefox::Profile;
 
-use Archive::Zip qw( :ERROR_CODES );
-use Archive::Extract;
-use Carp qw(croak);
-use Cwd qw(abs_path);
-use File::Copy qw(copy);
-use File::Temp;
-use MIME::Base64;
-use Scalar::Util qw(looks_like_number);
-
-=head1 DESCRIPTION
-
-You can use this module to create a custom Firefox Profile for your
-Selenium tests. Currently, you can set browser preferences and add
-extensions to the profile before passing it in the constructor for a
-new Selenium::Remote::Driver.
-
-=head1 SYNPOSIS
-
-    use Selenium::Remote::Driver;
-    use Selenium::Remote::Driver::Firefox::Profile;
-
-    my $profile = Selenium::Remote::Driver::Firefox::Profile->new;
-    $profile->set_preference(
-        'browser.startup.homepage' => 'http://www.google.com',
-        'browser.cache.disk.capacity' => 358400
-    );
-
-    $profile->set_boolean_preference(
-        'browser.shell.checkDefaultBrowser' => 0
-    );
-
-    $profile->add_extension('t/www/redisplay.xpi');
-
-    my $driver = Selenium::Remote::Driver->new(
-        'firefox_profile' => $profile
-    );
-
-    $driver->get('http://www.google.com');
-    print $driver->get_title();
-
-=cut
-
-sub new {
-    my $class = shift;
-
-    # TODO: add handling for a pre-existing profile folder passed into
-    # the constructor
-
-    # TODO: accept user prefs, boolean prefs, and extensions in
-    # constructor
-    my $self = {
-        profile_dir => File::Temp->newdir(),
-        user_prefs => {},
-        extensions => []
-      };
-    bless $self, $class or die "Can't bless $class: $!";
-
-    return $self;
-}
-
-=method set_preference
-
-Set string and integer preferences on the profile object. You can set
-multiple preferences at once. If you need to set a boolean preference,
-see C<set_boolean_preference()>.
-
-    $profile->set_preference("quoted.integer.pref" => '"20140314220517"');
-    # user_pref("quoted.integer.pref", "20140314220517");
-
-    $profile->set_preference("plain.integer.pref" => 9005);
-    # user_pref("plain.integer.pref", 9005);
-
-    $profile->set_preference("string.pref" => "sample string value");
-    # user_pref("string.pref", "sample string value");
-
-=cut
-
-sub set_preference {
-    my ($self, %prefs) = @_;
-
-    foreach (keys %prefs) {
-        my $value = $prefs{$_};
-        my $clean_value = '';
-
-        if ($value =~ /^(['"]).*\1$/ or looks_like_number($value)) {
-            # plain integers: 0, 1, 32768, or integers wrapped in strings:
-            # "0", "1", "20140204". in either case, there's nothing for us
-            # to do.
-            $clean_value = $value;
-        }
-        else {
-            # otherwise it's hopefully a string that we'll need to
-            # quote on our own
-            $clean_value = '"' . $value . '"';
-        }
-
-        $self->{user_prefs}->{$_} = $clean_value;
-    }
-}
-
-=method set_boolean_preference
-
-Set preferences that require boolean values of 'true' or 'false'. You
-can set multiple preferences at once. For string or integer
-preferences, use C<set_preference()>.
-
-    $profile->set_boolean_preference("false.pref" => 0);
-    # user_pref("false.pref", false);
-
-    $profile->set_boolean_preference("true.pref" => 1);
-    # user_pref("true.pref", true);
-
-=cut
-
-sub set_boolean_preference {
-    my ($self, %prefs) = @_;
-
-    foreach (keys %prefs) {
-        my $value = $prefs{$_};
-
-        $self->{user_prefs}->{$_} = $value ? 'true' : 'false';
-    }
-}
-
-=method get_preference
-
-Retrieve the computed value of a preference. Strings will be double
-quoted and boolean values will be single quoted as "true" or "false"
-accordingly.
-
-    $profile->set_boolean_preference("true.pref" => 1);
-    print $profile->get_preference("true.pref") # true
-
-    $profile->set_preference("string.pref" => "an extra set of quotes");
-    print $profile->get_preference("string.pref") # "an extra set of quotes"
-
-=cut
-
-sub get_preference {
-    my ($self, $pref) = @_;
-
-    return $self->{user_prefs}->{$pref};
+BEGIN {
+    push our @ISA, 'Selenium::Firefox::Profile';
 }
 
-=method add_extension
-
-Add an existing C<.xpi> to the profile by providing its path. This
-only works with packaged C<.xpi> files, not plain/un-packed extension
-directories.
+=head1 DESCRIPTION
 
-    $profile->add_extension('t/www/redisplay.xpi');
+We've renamed this class to the slightly less wordy
+L<Selenium::Firefox::Profile>. This is only around as an alias to
+hopefully prevent old code from breaking.
 
 =cut
 
-sub add_extension {
-    my ($self, $xpi) = @_;
-
-    croak 'File not found: ' . $xpi unless -e $xpi;
-    my $xpi_abs_path = abs_path($xpi);
-    croak '$xpi_abs_path: extensions must be in .xpi format' unless $xpi_abs_path =~ /\.xpi$/;
-
-    push (@{$self->{extensions}}, $xpi_abs_path);
-}
-
-sub _encode {
-    my $self = shift;
-
-    # The remote webdriver accepts the Firefox profile as a base64
-    # encoded zip file
-    $self->_layout_on_disk();
-
-    my $zip = Archive::Zip->new();
-    my $dir_member = $zip->addTree( $self->{profile_dir} );
-
-    my $string = "";
-    open (my $fh, ">", \$string);
-    binmode($fh);
-    unless ( $zip->writeToFileHandle($fh) == AZ_OK ) {
-        die 'write error';
-    }
-
-    return encode_base64($string);
-}
-
-sub _layout_on_disk {
-    my $self = shift;
-
-    $self->_write_preferences();
-    $self->_install_extensions();
-
-    return $self->{profile_dir};
-}
-
-sub _write_preferences {
-    my $self = shift;
-
-    my $userjs = $self->{profile_dir} . "/user.js";
-    open (my $fh, ">>", $userjs)
-        or die "Cannot open $userjs for writing preferences: $!";
-
-    foreach (keys %{$self->{user_prefs}}) {
-        print $fh 'user_pref("' . $_ . '", ' . $self->get_preference($_) . ');' . "\n";
-    }
-    close ($fh);
-}
-
-sub _install_extensions {
-    my $self = shift;
-    my $extension_dir = $self->{profile_dir} . "/extensions/";
-    mkdir $extension_dir unless -d $extension_dir;
-
-    # TODO: handle extensions that need to be unpacked
-    foreach (@{$self->{extensions}}) {
-        # For Firefox to recognize the extension, we have to put the
-        # .xpi in the /extensions/ folder and change the filename to
-        # its id, which is found in the install.rdf in the root of the
-        # zip.
-        my $ae = Archive::Extract->new( archive => $_,
-                                        type => "zip");
-
-        my $tempDir = File::Temp->newdir();
-        $ae->extract( to => $tempDir );
-        my $install = $ae->extract_path();
-        $install .= '/install.rdf';
-
-        open (my $fh, "<", $install)
-            or croak "No install.rdf inside $_: $!";
-        my (@file) = <$fh>;
-        close ($fh);
-
-        my @name = grep { chomp; $_ =~ /<em:id>[^{]/ } @file;
-        $name[0] =~ s/.*<em:id>(.*)<\/em:id>.*/$1/;
-
-        my $xpi_dest = $extension_dir . $name[0] . ".xpi";
-        copy($_, $xpi_dest)
-            or croak "Error copying $_ to $xpi_dest : $!";
-    }
-}
-
 1;
 
-__END__
-
 =head1 SEE ALSO
 
+Selenium::Firefox::Profile
 http://kb.mozillazine.org/About:config_entries
 https://developer.mozilla.org/en-US/docs/Mozilla/Preferences/A_brief_guide_to_Mozilla_preferences

+ 36 - 0
lib/Selenium/Remote/Finders.pm

@@ -0,0 +1,36 @@
+package Selenium::Remote::Finders;
+
+# ABSTRACT: Handle construction of generic parameter finders
+use Try::Tiny;
+use Carp qw/carp/;
+use Moo::Role;
+use namespace::clean;
+
+=head1 DESCRIPTION
+
+This package just takes care of setting up parameter finders - that
+is, the C<find_element_by_.*> versions of the find element
+functions. You probably don't need to do anything with this package;
+instead, see L<Selenium::Remote::Driver/find_element> documentation
+for the specific finder functions.
+
+=cut
+
+sub _build_find_by {
+    my ($self, $by) = @_;
+
+    return sub {
+        my ($driver, $locator) = @_;
+        my $strategy = $by;
+
+        return try {
+            return $driver->find_element($locator, $strategy);
+        }
+        catch {
+            carp $_;
+            return 0;
+        };
+    }
+}
+
+1;

+ 8 - 6
lib/Selenium/Remote/Mock/RemoteConnection.pm

@@ -52,12 +52,16 @@ has 'session_id' => (
     default => sub { undef },
 );
 
+has 'remote_server_addr' => (
+    is => 'lazy',
+    default => sub { 'localhost' }
+);
+
 sub BUILD {
     my $self = shift;
     croak 'Cannot define replay and record attributes at the same time' if (($self->replay) && ($self->record));
     croak 'replay_file attribute needs to be defined' if (($self->replay) && !($self->replay_file));
     croak 'replay attribute needs to be defined' if (!($self->replay) && ($self->replay_file));
-    $self->remote_server_addr('localhost');
     $self->port('4444');
     if ($self->replay) {
         $self->load_session_store($self->replay_file);
@@ -113,6 +117,9 @@ sub request {
         $content = $json->allow_nonref->utf8->canonical(1)->encode($params);
     }
     my $url_params = $resource->{url_params};
+
+    print "REQ: $method, $url, $content\n" if $self->debug;
+
     if ( $self->record ) {
         my $response = $self->SUPER::request( $resource, $params, 1 );
         push @{$self->session_store->{"$method $url $content"}},$response->as_string;
@@ -140,10 +147,6 @@ sub request {
     my $ret = { cmd_status => 'OK', cmd_return => 1 };
     if ( defined( $spec->{$cmd} ) ) {
         my $return_sub = $spec->{$cmd};
-        if ($no_content_success) {
-            $ret->{cmd_return} = 1;
-        }
-        else {
             my $mock_return = $return_sub->( $url_params, $params );
             if ( ref($mock_return) eq 'HASH' ) {
                 $ret->{cmd_status} = $mock_return->{status};
@@ -153,7 +156,6 @@ sub request {
             else {
                 $ret = $mock_return;
             }
-        }
         $ret->{session_id} = $self->fake_session_id if ( ref($ret) eq 'HASH' );
     }
     else {

+ 111 - 0
lib/Selenium/Waiter.pm

@@ -0,0 +1,111 @@
+package Selenium::Waiter;
+
+# ABSTRACT: Provides a utility wait_until function
+use Try::Tiny;
+require Exporter;
+our @ISA = qw/Exporter/;
+our @EXPORT = qw/wait_until/;
+
+=head1 SYNOPSIS
+
+    use Selenium::Waiter qw/wait_until/;
+    my $d = Selenium::Remote::Driver->new;
+
+    my $div = wait_until { $d->find_element('div', css') };
+
+=func wait_until
+
+Exported by default, it takes a BLOCK (required) and optionally a
+hash of configuration params. It uses a prototype to take its
+arguments, so usage looks look like:
+
+    use Selenium::Waiter;
+    my $div = wait_until { $driver->find_element('div', css') };
+
+The above snippet will search for C<css=div> for thirty seconds; if it
+ever finds the element, it will immediately return. More generally,
+Once the BLOCK returns anything truthy, the C<wait_until> will stop
+evaluating and the return of the BLOCK will be returned to you. If the
+BLOCK never returns a truthy value, we'll wait until the elapsed time
+has increased past the timeout and then return an empty string C<''>.
+
+B<Achtung!> Please make sure that the BLOCK you pass in can be
+executed in a timely fashion. For Webdriver, that means that you
+should set the appropriate C<implicit_wait> timeout low (a second or
+less!)  so that we can rerun the assert sub repeatedly. We don't do
+anything fancy behind the scenes: we just execute the BLOCK you pass
+in and sleep between iterations. If your BLOCK actively blocks for
+thirty seconds, like a C<find_element> would do with an
+C<implicit_wait> of 30 seconds, we won't be able to help you at all -
+that blocking behavior is on the webdriver server side, and is out of
+our control. We'd run one iteration, get blocked for thirty seconds,
+and return control to you at that point.
+
+=head4 Dying
+
+PLEASE check the return value before proceeding, as we unwisely
+suppress any attempts your BLOCK may make to die or croak. The BLOCK
+you pass is called in a L<Try::Tiny/try>, and if any of the
+invocations of your function throw and the BLOCK never becomes true,
+we'll carp exactly once at the end immediately before returning
+false. We overwrite the death message from each iteration, so at the
+end, you'll only see the most recent death message.
+
+    # warns once after thirty seconds: "kept from dying";
+    wait_until { die 'kept from dying' };
+
+The output of C<die>s from each iteration can be exposed if you wish
+to see the massacre:
+
+    # carps: "kept from dying" once a second for thirty seconds
+    wait_until { die 'kept from dying' } debug => 1;
+
+=head4 Timeouts and Intervals
+
+You can also customize the timeout, and/or the retry interval between
+iterations.
+
+    # prints hi three four times at 0, 3, 6, and 9 seconds
+    wait_until { print 'hi'; '' } timeout => 10, interval => 3;
+
+=cut
+
+sub wait_until (&%) {
+    my $assert = shift;
+    my $args = {
+        timeout => 30,
+        interval => 1,
+        debug => 0,
+        @_
+    };
+
+    my $start = time;
+    my $timeout_not_elapsed = sub {
+        my $elapsed = time - $start;
+        return $elapsed < $args->{timeout};
+    };
+
+    my $exception = '';
+    while ($timeout_not_elapsed->()) {
+        my $try_ret = try {
+            my $assert_ret = $assert->();
+            return $assert_ret if $assert_ret;
+        }
+        catch {
+            $exception = $_;
+            warn $_ if $args->{debug};
+            return '';
+        }
+        finally {
+            sleep($args->{interval});
+        };
+
+        return $try_ret if $try_ret;
+    }
+
+    # No need to repeat ourselves if we're already debugging.
+    warn $exception if $exception && ! $args->{debug};
+    return '';
+}
+
+1;

+ 158 - 120
lib/Test/Selenium/Remote/Driver.pm

@@ -33,7 +33,7 @@ has func_list => (
             'send_modifier_ok', 'accept_alert_ok', 'dismiss_alert_ok',
             'get_ok', 'go_back_ok', 'go_forward_ok', 'add_cookie_ok',
             'get_page_source_ok', 'find_element_ok', 'find_elements_ok',
-            'find_child_element_ok', 'find_child_elements_ok',
+            'find_child_element_ok', 'find_child_elements_ok', 'find_no_element_ok',
             'compare_elements_ok', 'click_ok', 'double_click_ok',
             'body_like',
         ];
@@ -44,7 +44,12 @@ sub has_args {
     my $self          = shift;
     my $fun_name      = shift;
     my $hash_fun_args = {
-        'find_element'     => 1,
+        'find_element'     => 2,
+        'find_no_element' => 2,
+        'find_child_element'     => 3,
+        'find_child_elements'     => 3,
+        'find_element'     => 2,
+        'find_elements'     => 2,
         'compare_elements' => 2,
         'get' => 1,
     };
@@ -234,66 +239,173 @@ more documentation, see the related test methods in L<Selenium::Remote::Driver>
 
     click_ok
     double_click_ok
+=cut
+
 
-=head2 $twd->type_element_ok($search_target, $keys, [, $desc ]);
+# function composing a find_element with locator with a webelement test
 
-   $twd->type_element_ok( $search_target, $keys [, $desc ] );
+sub _find_element_with_action { 
+    my $self = shift; 
+    my $method = shift;
+    my ($locator,$locator_strategy,$params,$desc) = @_;
+    # case 4 args 
+    if ($desc) { 
+        $self->croak('Invalid locator strategy') unless ($self->FINDERS->{$locator_strategy});
+    }
+    else { 
+        if ($params) { 
+            # means that we called it the 'old way' (no locator strategy)
+            if (!defined($self->FINDERS->{$locator_strategy})) { 
+                $desc = $params; 
+                $params = $locator_strategy; 
+                $locator_strategy = $self->_get_finder_key($self->default_finder);
+            }
+        }
+        else { 
+            # means it was called with no locator strategy and no desc 
+            if ($locator_strategy) { 
+                if (!defined($self->FINDERS->{$locator_strategy})) { 
+                    $params = $locator_strategy; 
+                    $locator_strategy = $self->_get_finder_key($self->default_finder);
+                }
+            }
+            else { 
+                $self->croak('Not enough arguments');
+            }
+        }
+    }
+    unless ($desc) {
+        $desc = $method;
+        $desc .= "'" . join( " ", ($params // '') ) . "'";
+    }
+    return $self->find_element($locator,$locator_strategy)->$method( $params, $desc );
+}
+
+
+=head2 $twd->type_element_ok($search_target [,$locator], $keys, [, $desc ]);
+
+   $twd->type_element_ok( $search_target [,$locator], $keys [, $desc ] );
 
 Use L<Selenium::Remote::Driver/find_element> to resolve the C<$search_target>
-to a web element, and then type C<$keys> into it, providing an optional test
+to a web element and an optional locator, and then type C<$keys> into it, providing an optional test
 label.
 
-Currently, other finders besides the default are not supported for C<type_ok()>.
 
 =cut
 
 sub type_element_ok {
     my $self    = shift;
-    my $locator = shift;
-    my $keys    = shift;
-    my $desc    = shift;
-    return $self->find_element($locator)->send_keys_ok( $keys, $desc );
+    my $method = 'send_keys_ok'; 
+    return $self->_find_element_with_action($method,@_);
+}
+
+
+=head2 $twd->element_text_is($search_target[,$finder],$expected_text [,$desc]);
+
+    $twd->element_text_is($search_target[,$finder],$expected_text [,$desc]);
+
+=cut
+
+sub element_text_is {
+    my $self = shift; 
+    my $method = 'text_is';
+    return $self->_find_element_with_action($method,@_);
 }
 
+=head2 $twd->element_value_is($search_target[,$finder],$expected_value [,$desc]);
 
-=head2 $twd->find_element_ok($search_target [, $desc ]);
+    $twd->element_value_is($search_target[,$finder],$expected_value [,$desc]);
+
+=cut
 
-   $twd->find_element_ok( $search_target [, $desc ] );
+sub element_value_is {
+    my $self = shift; 
+    my $method = 'value_is';
+    return $self->_find_element_with_action($method,@_);
+}
+
+=head2 $twd->click_element_ok($search_target [,$desc]);
+
+    $twd->click_element_ok($search_target [,$desc]);
+
+Find an element and then click on it.
+
+=cut
+
+sub click_element_ok {
+    my $self = shift; 
+    my $method = 'click_ok';
+    return $self->_find_element_with_action($method,@_);
+}
+
+=head2 $twd->clear_element_ok($search_target [,$desc]);
+
+    $twd->clear_element_ok($search_target [,$desc]);
+
+Find an element and then clear on it.
+
+=cut
+
+sub clear_element_ok {
+    my $self = shift; 
+    my $method = 'clear_ok';
+    return $self->_find_element_with_action($method,@_);
+}
+
+=head2 $twd->is_element_displayed_ok($search_target [,$desc]);
+
+    $twd->is_element_displayed_ok($search_target [,$desc]);
+
+Find an element and check to confirm that it is displayed. (visible)
+
+=cut
+
+sub is_element_displayed_ok {
+    my $self = shift; 
+    my $method = 'is_displayed_ok';
+    return $self->_find_element_with_action($method,@_);
+}
+
+=head2 $twd->is_element_enabled_ok($search_target [,$desc]);
+
+    $twd->is_element_enabled_ok($search_target [,$desc]);
+
+Find an element and check to confirm that it is enabled.
+
+=cut
+
+sub is_element_enabled_ok {
+    my $self = shift; 
+    my $method = 'is_enabled_ok';
+    return $self->_find_element_with_action($method,@_);
+}
+
+
+=head2 $twd->find_element_ok($search_target [,$finder, $desc ]);
+
+   $twd->find_element_ok( $search_target [,$finder, $desc ] );
 
 Returns true if C<$search_target> is successfully found on the page. L<$search_target>
-is passed to L<Selenium::Remote::Driver/find_element> using the C<default_finder>. See
-there for more details on the format. Currently, other finders besides the default are not supported
-for C<find_element_ok()>.
+is passed to L<Selenium::Remote::Driver/find_element> using a finder or the C<default_finder>
+if none passed.
+See there for more details on the format for C<find_element_ok()>.
 
 =cut
 
 # Eventually, it would be nice to support other finds like Test::WWW::Selenium does, like this:
 # 'xpath=//foo', or 'css=.foo', etc.
 
-=head2 $twd->find_no_element_ok($search_target [, $desc ]);
+=head2 $twd->find_no_element_ok($search_target [,$finder, $desc ]);
 
-   $twd->find_no_element_ok( $search_target [, $desc ] );
+   $twd->find_no_element_ok( $search_target [,$finder, $desc ] );
 
 Returns true if C<$search_target> is I<not> found on the page. L<$search_target>
-is passed to L<Selenium::Remote::Driver/find_element> using the C<default_finder>. See
-there for more details on the format. Currently, other finders besides the default are not supported
+is passed to L<Selenium::Remote::Driver/find_element> using a finder or the
+C<default_finder> if none passed.See there for more details on the format. 
 for C<find_no_element_ok()>.
 
 =cut
 
-sub find_no_element_ok {
-    my $self          = shift;
-    my $search_target = shift;
-    my $desc          = shift;
-    my $rv = 0 ;
-    local $Test::Builder::Level = $Test::Builder::Level + 1;
-    try {
-        $self->find_element($search_target)
-    } catch {
-        $rv = 1 if ($_);
-    };
-    return $self->ok($rv == 1,$desc);
-}
 
 =head2 $twd->content_like( $regex [, $desc ] )
 
@@ -348,18 +460,17 @@ sub content_unlike {
     my $self  = shift;
     my $regex = shift;
     my $desc  = shift;
-
     local $Test::Builder::Level = $Test::Builder::Level + 1;
 
     my $content = $self->get_page_source();
 
     if ( not ref $regex eq 'ARRAY' ) {
-        my $desc = qq{Content is unlike "$regex"} if ( not defined $desc );
+        $desc = qq{Content is unlike "$regex"} if ( not defined $desc );
         return unlike_string( $content, $regex, $desc );
     }
     elsif ( ref $regex eq 'ARRAY' ) {
         for my $re (@$regex) {
-            my $desc = qq{Content is unlike "$re"} if ( not defined $desc );
+            $desc = qq{Content is unlike "$re"} if ( not defined $desc );
             unlike_string( $content, $re, $desc );
         }
     }
@@ -392,12 +503,12 @@ sub body_text_like {
     my $text = $self->get_body();
 
     if ( not ref $regex eq 'ARRAY' ) {
-        my $desc = qq{Text is like "$regex"} if ( not defined $desc );
+        $desc = qq{Text is like "$regex"} if ( not defined $desc );
         return like_string( $text, $regex, $desc );
     }
     elsif ( ref $regex eq 'ARRAY' ) {
         for my $re (@$regex) {
-            my $desc = qq{Text is like "$re"} if ( not defined $desc );
+            $desc = qq{Text is like "$re"} if ( not defined $desc );
             like_string( $text, $re, $desc );
         }
     }
@@ -430,12 +541,12 @@ sub body_text_unlike {
     my $text = $self->get_body();
 
     if ( not ref $regex eq 'ARRAY' ) {
-        my $desc = qq{Text is unlike "$regex"} if ( not defined $desc );
+        $desc = qq{Text is unlike "$regex"} if ( not defined $desc );
         return unlike_string( $text, $regex, $desc );
     }
     elsif ( ref $regex eq 'ARRAY' ) {
         for my $re (@$regex) {
-            my $desc = qq{Text is unlike "$re"} if ( not defined $desc );
+            $desc = qq{Text is unlike "$re"} if ( not defined $desc );
             unlike_string( $text, $re, $desc );
         }
     }
@@ -467,12 +578,12 @@ sub content_contains {
     my $content = $self->get_page_source();
 
     if ( not ref $str eq 'ARRAY' ) {
-        my $desc = qq{Content contains "$str"} if ( not defined $desc );
+        $desc = qq{Content contains "$str"} if ( not defined $desc );
         return contains_string( $content, $str, $desc );
     }
     elsif ( ref $str eq 'ARRAY' ) {
         for my $s (@$str) {
-            my $desc = qq{Content contains "$s"} if ( not defined $desc );
+            $desc = qq{Content contains "$s"} if ( not defined $desc );
             contains_string( $content, $s, $desc );
         }
     }
@@ -502,12 +613,12 @@ sub content_lacks {
     my $content = $self->get_page_source();
 
     if ( not ref $str eq 'ARRAY' ) {
-        my $desc = qq{Content lacks "$str"} if ( not defined $desc );
+        $desc = qq{Content lacks "$str"} if ( not defined $desc );
         return lacks_string( $content, $str, $desc );
     }
     elsif ( ref $str eq 'ARRAY' ) {
         for my $s (@$str) {
-            my $desc = qq{Content lacks "$s"} if ( not defined $desc );
+            $desc = qq{Content lacks "$s"} if ( not defined $desc );
             lacks_string( $content, $s, $desc );
         }
     }
@@ -540,12 +651,12 @@ sub body_text_contains {
     my $text = $self->get_body();
 
     if ( not ref $str eq 'ARRAY' ) {
-        my $desc = qq{Text contains "$str"} if ( not defined $desc );
+        $desc = qq{Text contains "$str"} if ( not defined $desc );
         return contains_string( $text, $str, $desc );
     }
     elsif ( ref $str eq 'ARRAY' ) {
         for my $s (@$str) {
-            my $desc = qq{Text contains "$s"} if ( not defined $desc );
+            $desc = qq{Text contains "$s"} if ( not defined $desc );
             contains_string( $text, $s, $desc );
         }
     }
@@ -578,90 +689,17 @@ sub body_text_lacks {
     my $text = $self->get_body();
 
     if ( not ref $str eq 'ARRAY' ) {
-        my $desc = qq{Text is lacks "$str"} if ( not defined $desc );
+        $desc = qq{Text is lacks "$str"} if ( not defined $desc );
         return lacks_string( $text, $str, $desc );
     }
     elsif ( ref $str eq 'ARRAY' ) {
         for my $s (@$str) {
-            my $desc = qq{Text is lacks "$s"} if ( not defined $desc );
+            $desc = qq{Text is lacks "$s"} if ( not defined $desc );
             lacks_string( $text, $s, $desc );
         }
     }
 }
 
-=head2 $twd->element_text_is($search_target,$expected_text [,$desc]);
-
-    $twd->element_text_is($search_target,$expected_text [,$desc]);
-
-=cut
-
-sub element_text_is {
-    my ( $self, $search_target, $expected, $desc ) = @_;
-    return $self->find_element($search_target)->text_is( $expected, $desc );
-}
-
-=head2 $twd->element_value_is($search_target,$expected_value [,$desc]);
-
-    $twd->element_value_is($search_target,$expected_value [,$desc]);
-
-=cut
-
-sub element_value_is {
-    my ( $self, $search_target, $expected, $desc ) = @_;
-    return $self->find_element($search_target)->value_is( $expected, $desc );
-}
-
-=head2 $twd->click_element_ok($search_target [,$desc]);
-
-    $twd->click_element_ok($search_target [,$desc]);
-
-Find an element and then click on it.
-
-=cut
-
-sub click_element_ok {
-    my ( $self, $search_target, $desc ) = @_;
-    return $self->find_element($search_target)->click_ok($desc);
-}
-
-=head2 $twd->clear_element_ok($search_target [,$desc]);
-
-    $twd->clear_element_ok($search_target [,$desc]);
-
-Find an element and then clear on it.
-
-=cut
-
-sub clear_element_ok {
-    my ( $self, $search_target, $desc ) = @_;
-    return $self->find_element($search_target)->clear_ok($desc);
-}
-
-=head2 $twd->is_element_displayed_ok($search_target [,$desc]);
-
-    $twd->is_element_displayed_ok($search_target [,$desc]);
-
-Find an element and check to confirm that it is displayed. (visible)
-
-=cut
-
-sub is_element_displayed_ok {
-    my ( $self, $search_target, $desc ) = @_;
-    return $self->find_element($search_target)->is_displayed_ok($desc);
-}
-
-=head2 $twd->is_element_enabled_ok($search_target [,$desc]);
-
-    $twd->is_element_enabled_ok($search_target [,$desc]);
-
-Find an element and check to confirm that it is enabled.
-
-=cut
-
-sub is_element_enabled_ok {
-    my ( $self, $search_target, $desc ) = @_;
-    return $self->find_element($search_target)->is_enabled_ok($desc);
-}
 
 1;
 

+ 44 - 1
lib/Test/Selenium/Remote/Role/DoesTesting.pm

@@ -5,6 +5,7 @@ package Test::Selenium::Remote::Role::DoesTesting;
 use Moo::Role;
 use Test::Builder;
 use Try::Tiny;
+use List::MoreUtils qw/any/;
 use namespace::clean;
 
 requires qw(func_list has_args);
@@ -16,6 +17,17 @@ has _builder => (
 );
 
 
+# get back the key value from an already coerced finder (default finder)
+
+sub _get_finder_key { 
+    my $self = shift; 
+    my $finder_value = shift; 
+    foreach my $k (keys %{$self->FINDERS}) { 
+        return $k if ($self->FINDERS->{$k} eq $finder_value);
+    }
+    return; 
+}
+
 # main method for non ok tests
 
 sub _check_method {
@@ -38,19 +50,50 @@ sub _check_method {
 }
 
 # main method for _ok tests
+# a bit hacked so that find_no_element_ok can also be processed
 
 sub _check_ok {
     my $self   = shift;
     my $method = shift;
+    my $real_method = '';
     my @args   = @_;
     my ($rv, $num_of_args, @r_args);
     try {
         $num_of_args = $self->has_args($method);
         @r_args = splice( @args, 0, $num_of_args );
+        if ($method =~ m/^find(_no|_child)?_element/) { 
+            # case find_element_ok was called with no arguments    
+            if (scalar(@r_args) - $num_of_args == 1) { 
+                push @r_args, $self->_get_finder_key($self->default_finder);
+            }
+            else { 
+                if (scalar(@r_args) == $num_of_args) {
+                    # case find_element was called with no finder but
+                    # a test description
+                    my $finder = $r_args[$num_of_args - 1]; 
+                    my @FINDERS = keys (%{$self->FINDERS});
+                    unless ( any { $finder eq $_ } @FINDERS) { 
+                        $r_args[$num_of_args - 1] = $self->_get_finder_key($self->default_finder);
+                        push @args, $finder; 
+                    }
+                }
+            }
+        }
+        # quick hack to fit 'find_no_element' into check_ok logic
+        if ($method eq 'find_no_element') { 
+            $real_method = $method;
+            $method = 'find_element'; 
+        }
         $rv = $self->$method(@r_args);
     }
     catch {
-        $self->croak($_);
+        if ($real_method) {
+            $method = $real_method;
+            $rv     = 1;
+        }
+        else {
+            $self->croak($_);
+        }
     };
 
     my $default_test_name = $method;

+ 92 - 7
t/01-driver.t

@@ -13,6 +13,7 @@ use Selenium::Remote::Mock::RemoteConnection;
 use FindBin;
 use lib $FindBin::Bin . '/lib';
 use TestHarness;
+use Test::Fatal;
 
 my $harness = TestHarness->new(
     this_file => $FindBin::Script
@@ -20,9 +21,16 @@ my $harness = TestHarness->new(
 my %selenium_args = %{ $harness->base_caps };
 
 my $driver = Selenium::Remote::Driver->new(%selenium_args);
-my $website = 'http://localhost:63636';
+my $domain = $harness->domain;
+my $website = $harness->website;
 my $ret;
 
+my $chrome;
+eval { $chrome = Selenium::Remote::Driver->new(
+    %selenium_args,
+    browser_name => 'chrome'
+); };
+
 DESIRED_CAPABILITIES: {
     # We're using a different test method for these because we needed
     # to inspect payload of the POST to /session, and the method of
@@ -192,6 +200,14 @@ WINDOW: {
     ok(!$@,"Reset implicit wait timeout");
     $ret = $driver->get("$website/frameset.html");
     $ret = $driver->switch_to_frame('second');
+
+  SKIP: {
+        skip 'Cannot rotate desktop browsers', 3;
+        ok($driver->get_orientation eq 'PORTRAIT', 'Can get default orientation');
+        $ret = $driver->set_orientation('LANDSCAPE');
+        ok($ret, 'Can change orientation to LANDSCAPE');
+        ok($driver->get_orientation eq 'LANDSCAPE', 'Can get changed orientation');
+    }
 }
 
 COOKIES: {
@@ -202,7 +218,7 @@ COOKIES: {
     pass('Deleting cookies...');
     $ret = $driver->get_all_cookies();
     is(@{$ret}, 0, 'Deleted all cookies.');
-    $ret = $driver->add_cookie('foo', 'bar', '/', 'localhost', 0);
+    $ret = $driver->add_cookie('foo', 'bar', '/', $domain, 0);
     pass('Adding cookie foo...');
     $ret = $driver->get_all_cookies();
     is(@{$ret}, 1, 'foo cookie added.');
@@ -424,13 +440,8 @@ USER_AGENT: {
 }
 
 STORAGE: {
-    my $chrome;
-    my %selenium_chrome_args = ( browser_name => 'chrome');
-    $selenium_chrome_args{remote_conn} = $selenium_args{remote_conn};
-
   SKIP: {
         eval {
-            $chrome = Selenium::Remote::Driver->new(%selenium_chrome_args);
             $chrome->get($website);
         };
 
@@ -450,6 +461,80 @@ STORAGE: {
     }
 }
 
+HTML5: {
+  SKIP: {
+        skip 'HTML5 Application Cache is not supported by firefox or chrome', 1 if 1;
+        $driver->get($website);
+        my $status = $driver->cache_status;
+        ok($status, 'we can get application cache status');
+    }
+
+  SKIP: {
+        skip 'Geolocation requires Chrome to test', 2 unless $chrome;
+
+        my $ret = $chrome->set_geolocation( location => {
+            latitude => 40.714353,
+            longitude => -74.005973,
+            altitude => 0.056747
+        });
+        ok($ret, 'can set geolocation');
+
+      TODO: {
+            local $TODO = 'GET geolocation has a cast Long to Double error in Chromedriver';
+            my $ret = {};
+            eval { $ret = $chrome->get_geolocation };
+            ok(exists $ret->{location}, 'get_geolocation returns a location dictionary.');
+        }
+    }
+}
+
+LOGS: {
+    $driver->get($website);
+
+    my $types = $driver->get_log_types;
+    ok(scalar @$types >= 4, 'Can get log types');
+    foreach (@$types) {
+        my $log = $driver->get_log($_);
+        ok(defined $log, 'Can get logs from the ' . $_);
+    }
+}
+
+UPLOAD: {
+    #Webdriver only returns the full filename if there isn't any path components in it, so test both cases
+    my $testFile = "UEsDBBQACAAIAFtuNEYAAAAAAAAAAAAAAAAKABUAdXBsb2FkVGVzdFVUCQADjbG+VJ6xvlRVeAQA\n6APoAytJLS4BAFBLBwgMfn/YBgAAAAQAAABQSwECFAMUAAgACABbbjRGDH5/2AYAAAAEAAAACgAN\nAAAAAAAAAAAApIEAAAAAdXBsb2FkVGVzdFVUBQABjbG+VFV4AABQSwUGAAAAAAEAAQBFAAAAUwAA\nAAAA\n";
+    my $otherTestFile = "UEsDBBQACAAIAFtuNEYAAAAAAAAAAAAAAAAMABUAdC91cGxvYWRUZXN0VVQJAAOesb5UnrG+VFV4\nBADoA+gDK0ktLgEAUEsHCAx+f9gGAAAABAAAAFBLAQIUAxQACAAIAFtuNEYMfn/YBgAAAAQAAAAM\nAA0AAAAAAAAAAACkgQAAAAB0L3VwbG9hZFRlc3RVVAUAAZ6xvlRVeAAAUEsFBgAAAAABAAEARwAA\nAFUAAAAAAA==\n";
+
+    like( $driver->upload_file('uploadTest',$testFile),qr/uploadTest$/,'upload_file returns FULL path to the file: cwd');
+    like( $driver->upload_file('t/uploadTest',$otherTestFile),qr/uploadTest$/,'upload_file returns FULL path to the file: subdir');
+
+    my $fake_driver;
+    if ($harness->record) {
+        $fake_driver = $driver;
+    } else {
+        #Going to have to use a custom UA to test this, since bogosity ensues
+        my $ftua = Test::LWP::UserAgent->new;
+        my $fake_header = bless( {'content-type' => 'application/json; charset=utf-8'}, 'HTTP::Headers' );
+        #Seems a bit heavy handed, but shouldn't be a problem.
+        $ftua->map_response(qr{.*}, HTTP::Response->new(200, 'OK', $fake_header, '{"sessionId":"89726c41-409f-421e-95a8-8b1fa482fa33","status":0,"state":"success","value":"/tmp/89726c41-409f-421e-95a8-8b1fa482fa33/upload1105744174029267337file/uploadTest","class":"org.openqa.selenium.remote.Response","hCode":516517658}'));
+        $fake_driver = Selenium::Remote::Driver->new(
+            ua => $ftua
+        );
+
+    }
+
+    #In the case this is not mocked, it tests a real issue (.. in paths), if not, it makes sure the zip/base64 bits at least don't make us explode.
+    like( $fake_driver->upload_file('t/uploadTest'),qr/uploadTest$/,'upload_file: zip/base64 branch' );
+    like( $fake_driver->upload_file('t/../t/uploadTest'),qr/uploadTest$/,'upload_file: zip/base64 branch with .. in path' );
+
+    #Negative tests to verify that our expected behavior codepath is travelled by tests
+    like( exception { $driver->upload_file('@@@SomeFileThatDoesNotExist@@@')},qr/no such file/i,"Passing missing file terminates program");
+    SKIP: {
+        skip 'purposefully excluding this test from the recording', 1
+          if $harness->record;
+        like( exception { $driver->upload_file(__FILE__) },qr/501/,"Passing this file rightly fails due to mock not being present");
+    }
+}
+
 QUIT: {
     $ret = $driver->quit();
     ok((not defined $driver->{'session_id'}), 'Killed the remote session');

+ 3 - 1
t/02-webelement.t

@@ -15,7 +15,9 @@ my $harness = TestHarness->new(
 my %selenium_args = %{ $harness->base_caps };
 
 my $driver = Selenium::Remote::Driver->new(%selenium_args);
-my $website = 'http://localhost:63636';
+my $domain = $harness->domain;
+my $website = $harness->website;
+
 $driver->get("$website/formPage.html");
 my $ret;
 my $elem;

+ 6 - 8
t/03-spec-coverage.t

@@ -1,19 +1,19 @@
 #!perl
+
 use strict;
 use warnings;
 
+use LWP::UserAgent;
+use Selenium::Remote::Commands;
 use Test::More;
 
 unless($ENV{RELEASE_TESTING}) {
   plan(skip_all=>"Author tests not required for installation.");
 }
 
-eval {use LWP::Simple;};
-plan skip_all => "need LWP::Simple" if $@;
-use Selenium::Remote::Commands;
-
 my $uri  = "http://selenium.googlecode.com/svn/wiki/JsonWireProtocol.wiki";
-my $data = get($uri);
+my $ua = LWP::UserAgent->new;
+my $data = $ua->get($uri)->content;
 plan skip_all => "need internet connection to run spec test" if !$data;
 
 my $todo_list = {
@@ -35,6 +35,7 @@ my $todo_list = {
    'POST session/:sessionId/keys'                          => 1,
    'GET session/:sessionId/location'                       => 1,
    'POST session/:sessionId/location'                      => 1,
+   'POST session/:sessionId/window/:windowHandle/maximize' => 1,
    'GET session/:sessionId/local_storage'                  => 1,
    'POST session/:sessionId/local_storage'                 => 1,
    'DELETE session/:sessionId/local_storage'               => 1,
@@ -47,9 +48,6 @@ my $todo_list = {
    'GET session/:sessionId/session_storage/key/:key'       => 1,
    'DELETE session/:sessionId/session_storage/key/:key'    => 1,
    'GET session/:sessionId/session_storage/size'           => 1,
-   'POST session/:sessionId/log'                           => 1,
-   'GET session/:sessionId/log/types'                      => 1,
-   'GET session/:sessionId/application_cache/status'       => 1,
 };
 my @lines = split(/\n/, $data);
 my @methods;

+ 89 - 0
t/CanStartBinary.t

@@ -0,0 +1,89 @@
+#! /usr/bin/perl
+
+use strict;
+use warnings;
+use File::Which qw/which/;
+use Selenium::Chrome;
+use Selenium::Firefox;
+use Selenium::Firefox::Binary;
+use Selenium::PhantomJS;
+use Test::Fatal;
+use Test::More;
+
+unless ( $ENV{RELEASE_TESTING} ) {
+    plan skip_all => "Author tests not required for installation.";
+}
+
+PHANTOMJS: {
+  SKIP: {
+        my $has_phantomjs = which('phantomjs');
+        skip 'Phantomjs binary not found in path', 3
+          unless $has_phantomjs;
+
+        skip 'PhantomJS binary not found in path', 3
+          unless is_proper_phantomjs_available();
+
+        my $phantom = Selenium::PhantomJS->new;
+        is( $phantom->browser_name, 'phantomjs', 'binary phantomjs is okay');
+        isnt( $phantom->port, 4444, 'phantomjs can start up its own binary');
+
+        ok( Selenium::CanStartBinary::probe_port( $phantom->port ), 'the phantomjs binary is listening on its port');
+    }
+}
+
+MANUAL: {
+    ok( exception { PhantomJS->new( binary => '/bad/executable') },
+        'we throw if the user specified binary is not executable');
+
+  SKIP: {
+        my $phantom_binary = which('phantomjs');
+        skip 'PhantomJS needed for manual binary path tests', 2
+          unless $phantom_binary;
+
+        my $manual_phantom = Selenium::PhantomJS->new(
+            binary => $phantom_binary
+        );
+        isnt( $manual_phantom->port, 4444, 'manual phantom can start up user specified binary');
+        ok( Selenium::CanStartBinary::probe_port( $manual_phantom->port ), 'the manual chrome binary is listening on its port');
+    }
+}
+
+CHROME: {
+  SKIP: {
+        my $has_chromedriver = which('chromedriver');
+        skip 'Chrome binary not found in path', 3
+          unless $has_chromedriver;
+
+        my $chrome = Selenium::Chrome->new;
+        ok( $chrome->browser_name eq 'chrome', 'convenience chrome is okay' );
+        isnt( $chrome->port, 4444, 'chrome can start up its own binary');
+
+        ok( Selenium::CanStartBinary::probe_port( $chrome->port ), 'the chrome binary is listening on its port');
+    }
+}
+
+FIREFOX: {
+  SKIP: {
+        skip 'Firefox will not start up on UNIX without a display', 3
+          if ($^O ne 'MSWin32' && ! $ENV{DISPLAY});
+        my $binary = Selenium::Firefox::Binary::firefox_path();
+        skip 'Firefox binary not found in path', 3
+          unless $binary;
+
+        ok(-x $binary, 'we can find some sort of firefox');
+
+        my $firefox = Selenium::Firefox->new;
+        isnt( $firefox->port, 4444, 'firefox can start up its own binary');
+        ok( Selenium::CanStartBinary::probe_port( $firefox->port ), 'the firefox binary is listening on its port');
+    }
+}
+
+sub is_proper_phantomjs_available {
+    my $ver = `phantomjs --version` // '';
+    chomp $ver;
+
+    $ver =~ s/^(\d\.\d).*/$1/;
+    return $ver >= 1.9;
+}
+
+done_testing;

+ 50 - 0
t/Finders.t

@@ -0,0 +1,50 @@
+#! /usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More;
+use Selenium::Remote::Driver;
+use FindBin;
+use lib $FindBin::Bin . '/lib';
+use TestHarness;
+
+my $harness = TestHarness->new(
+    this_file => $FindBin::Script
+);
+
+my %selenium_args = %{ $harness->base_caps };
+
+my $driver = Selenium::Remote::Driver->new(%selenium_args);
+$driver->get('http://danielgempesaw.com/Selenium-Remote-Driver/xhtmlTest.html');
+
+# This depends explicitly on the page we're visiting (xhtmlTest.html),
+my %finders = (
+    class => 'navigation',
+    class_name => 'navigation',
+    css => 'html',
+    id => 'linkId',
+    link => 'this goes to the same place',
+    link_text => 'this goes to the same place',
+    name => 'windowOne',
+    partial_link_text => 'this goes to the same',
+    tag_name => 'html',
+    xpath => '//html'
+);
+
+foreach my $by (keys %finders) {
+    my $locator = $finders{$by};
+    my $method = 'find_element_by_' . $by;
+
+    ok($driver->can($method), $method . ':  installed properly');
+    my $elem = $driver->$method($locator);
+    ok($elem, $method . ': finds an element properly');
+    ok($elem->isa('Selenium::Remote::WebElement'), $method . ': element is a WebElement');
+    {
+        # Briefly suppress warning output for prettier tests
+        my $warned = 0;
+        local $SIG{__WARN__} = sub { $warned++ };
+        ok(!$driver->$method('missing') , $method . ': does not croak on unavailable elements');
+        ok($warned, $method . ': unavailable elements throw a warning');
+    }
+}
+done_testing;

+ 16 - 15
t/Firefox-Profile.t

@@ -7,7 +7,7 @@ use Selenium::Remote::Driver;
 use Test::More;
 
 use MIME::Base64 qw/decode_base64/;
-use Archive::Extract;
+use IO::Uncompress::Unzip qw(unzip $UnzipError);
 use File::Temp;
 use JSON;
 use Selenium::Remote::Mock::RemoteConnection;
@@ -26,7 +26,8 @@ my $fixture_dir = $FindBin::Bin . '/www/';
 
 CUSTOM_EXTENSION_LOADED: {
     my $profile = Selenium::Remote::Driver::Firefox::Profile->new;
-    my $website = 'http://localhost:63636';
+    my $domain = $harness->domain;
+    my $website = $harness->website;
     my $mock_encoded_profile = $fixture_dir . 'encoded_profile.b64';
     my $encoded;
 
@@ -74,7 +75,7 @@ CUSTOM_EXTENSION_LOADED: {
     # elements)
     $driver->set_implicit_wait_timeout(30000);
     $driver->find_element("h1", "tag_name");
-    cmp_ok($driver->get_current_url, '=~', qr/localhost/i,
+    cmp_ok($driver->get_current_url, '=~', qr/$domain/i,
            "profile loaded and preference respected!");
 
     $driver->get($website . '/index.html');
@@ -125,6 +126,15 @@ PREFERENCES: {
             cmp_ok($profile->get_preference($_), "eq", $expected->{$_},
                    "$_ pref is formatted correctly");
         }
+
+        $profile->set_preference(
+            'boolean.true.2' => JSON::true,
+            'boolean.false.2' => JSON::false
+        );
+        is($profile->get_preference('boolean.true.2'), 'true',
+           'format true booleans via set_preference & JSON::true');
+        is($profile->get_preference('boolean.false.2'), 'false',
+           'format false booleans via set_preference & JSON::false');
     }
 
   PACK_AND_UNPACK: {
@@ -132,19 +142,10 @@ PREFERENCES: {
         my $fh = File::Temp->new();
         print $fh decode_base64($encoded);
         close $fh;
-        my $zip = Archive::Extract->new(
-            archive => $fh->filename,
-            type => "zip"
-        );
-        my $tempdir = File::Temp->newdir();
-        my $ok = $zip->extract( to => $tempdir );
-        my $outdir = $zip->extract_path;
 
-        my $filename = $tempdir . "/user.js";
-        open ($fh, "<", $filename);
-        my (@file) = <$fh>;
-        close ($fh);
-        my $userjs = join('', @file);
+        my $userjs;
+        unzip $fh->filename => \$userjs, Name => "user.js"
+          or die "unzip failed: $UnzipError\n";
 
         foreach (keys %$expected) {
             my $value = $expected->{$_};

+ 3 - 2
t/Remote-Connection.t

@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 use Test::More;
-use Test::Exception;
+use Test::Fatal;
 use Test::LWP::UserAgent;
 
 BEGIN: {
@@ -32,7 +32,8 @@ REDIRECT: {
         url => 'http://localhost/redirect'
     };
 
-    lives_ok(sub { $conn->request($redirect_endpoint) }, '303 redirects no longer kill us');
+    is( exception { $conn->request($redirect_endpoint) }, undef,
+        '303 redirects no longer kill us');
 }
 
 

+ 116 - 9
t/Test-Selenium-Remote-Driver.t

@@ -1,20 +1,91 @@
 #!/usr/bin/env perl
 use Test::More;
-use Test::Exception;
+use Test::Fatal;
 use Test::Selenium::Remote::Driver;
 use Selenium::Remote::WebElement;
 use Selenium::Remote::Mock::Commands;
 use Selenium::Remote::Mock::RemoteConnection;
 
+my $find_element = sub {
+    my ( undef, $searched_item ) = @_;
+    if ( $searched_item->{value} eq 'q' ) {
+        return { status => 'OK', return => { ELEMENT => '123456' } };
+    }
+    if (   $searched_item->{value} eq 'p'
+        && $searched_item->{using} eq 'class name' )
+    {
+        return { status => 'OK', return => { ELEMENT => '123457' } };
+    }
+    if ( $searched_item->{value} eq '//body' && $searched_item->{using} eq 'xpath') {
+        return { status => 'OK', return => { ELEMENT => '123458' } };
+    }
+    return { status => 'NOK', return => 0, error => 'element not found' };
+};
+my $find_child_element = sub {
+    my ( $session_object, $searched_item ) = @_;
+    my $elem_id = $session_object->{id};
+    if ( $elem_id == 1 && $searched_item->{value} eq 'p' ) {
+        if ( $searched_item->{using} eq 'class name' ) {
+            return { status => 'OK', return => { ELEMENT => '11223344' } };
+        }
+
+        if ( $searched_item->{using} eq 'xpath' ) {
+            return { status => 'OK',
+                return => [ { ELEMENT => '112' }, { ELEMENT => '113' } ] };
+        }
+    }
+
+    return {
+        status => 'NOK', return => 0,
+        error  => 'child element not found'
+    };
+};
+
+my $find_elements = sub {
+    my ( undef, $searched_expr ) = @_;
+    if (   $searched_expr->{value} eq 'abc'
+        && $searched_expr->{using} eq 'xpath' )
+    {
+        return { status => 'OK',
+            return => [ { ELEMENT => '123456' }, { ELEMENT => '12341234' } ] };
+    }
+};
+
+my $send_keys = sub {
+    my ( $session_object, $val ) = @_;
+    my $keys = shift @{ $val->{value} };
+    return { status => 'OK', return => 1 } if ( $keys =~ /abc|def/ );
+    return { status => 'NOK', return => 0, error => 'cannot send keys' };
+};
+
+my $get_text = sub {
+    my ($session_object) =@_;
+    return 'abc' if ($session_object->{id} eq '123456');
+    return 'def' if ($session_object->{id} eq '123457');
+    return 'this output matches' if ($session_object->{id} eq '123458');
+    return;
+};
+
+my $get_attr = sub {
+    my ($session_object) = @_;
+    return 'foo';
+};
+
 my $spec = {
-    findElement => sub {
-        my (undef,$searched_item) = @_;
-        return { status => 'OK', return => { ELEMENT => '123456' } }
-          if ( $searched_item->{value} eq 'q' );
-        return { status => 'NOK', return => 0, error => 'element not found' };
-    },
+    findElement => $find_element,
+    findChildElement => $find_child_element,
     getPageSource => sub { return 'this output matches regex'},
+    findElements => $find_elements,
+    findChildElements => $find_child_element,
+    getElementText => $get_text,
+    sendKeysToElement => $send_keys,
+    getElementAttribute => $get_attr,
+    clickElement => sub { return { status => 'OK', return => 1 }; },
+    clearElement =>  sub { return { status => 'OK', return => 1 }; },
+    isElementDisplayed =>  sub { return { status => 'OK', return => 1 }; },
+    isElementEnabled =>  sub { return { status => 'OK', return => 1 }; },
 };
+
 my $mock_commands = Selenium::Remote::Mock::Commands->new;
 
 my $successful_driver =
@@ -22,10 +93,46 @@ my $successful_driver =
     remote_conn => Selenium::Remote::Mock::RemoteConnection->new( spec => $spec, mock_cmds => $mock_commands ),
     commands => $mock_commands,
 );
+
+# find element ok tests
 $successful_driver->find_element_ok('q','find_element_ok works');
-dies_ok { $successful_driver->find_element_ok('notq') } 'find_element_ok dies if element not found';
-$successful_driver->find_no_element_ok('notq','find_no_element_ok works');
+$successful_driver->default_finder('class');
+$successful_driver->find_element_ok('p','find_element_ok with a locator works');
+$successful_driver->default_finder('xpath');
+ok( exception { $successful_driver->find_element_ok('notq') }, 'find_element_ok dies if element not found' );
+$successful_driver->find_elements_ok('abc','find_elements_ok works');
+
+# find child element ok tests
+$successful_driver->find_child_elements_ok({id => 1},'p','find_child_elements_ok works');
+$successful_driver->find_child_element_ok({id => 1},'p','class','find_child_element_ok with a locator works');
+ok( exception { $successful_driver->find_child_element_ok({id => 1200}) }, 'find_child_element_ok dies if the element is not found' );
+
+# find no element ok test
+
+$successful_driver->find_no_element_ok('notq','xpath','find_no_element_ok works');
+
+# body and content function family
 $successful_driver->content_like( qr/matches/, 'content_like works');
 $successful_driver->content_unlike( qr/nomatch/, 'content_unlike works');
+$successful_driver->content_contains( 'matches', 'content_contains works');
+$successful_driver->content_lacks( 'nomatch', 'content_lacks works');
+$successful_driver->body_text_contains( ['match','output'], 'body_text_contains works');
+$successful_driver->body_text_lacks( 'nomatch', 'body_text_lacks works');
+$successful_driver->body_text_like( qr/this/, 'body_text_like works');
+$successful_driver->body_text_unlike( qr/notthis/, 'body_text_unlike works');
+
+$successful_driver->type_element_ok('q','abc');
+$successful_driver->default_finder('class');
+$successful_driver->type_element_ok('p','def','type_element_ok works with a locator');
+
+$successful_driver->element_text_is('q','abc','element has a correct text');
+$successful_driver->element_text_is('p','class','def');
+
+$successful_driver->element_value_is('p','class','foo');
+$successful_driver->click_element_ok('p','class','click_element_ok works');
+$successful_driver->clear_element_ok('q','element is cleared ok');
+$successful_driver->is_element_enabled_ok('p','class','element is enabled');
+$successful_driver->is_element_displayed_ok('q','element is displayed');
+
 
 done_testing();

+ 48 - 0
t/Waiter.t

@@ -0,0 +1,48 @@
+#! /usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More;
+use Test::Warn;
+use Test::Fatal;
+use Time::Mock throttle => 100;
+use Selenium::Waiter;
+
+SIMPLE_WAIT: {
+    my $ret;
+    waits_ok( sub { $ret = wait_until { 1 } }, '<', 5, 'immediately true returns quickly' );
+    ok($ret == 1, 'return value for a true wait_until is passed up');
+    waits_ok( sub { $ret = wait_until { 0 } }, '>', 25, 'never true expires the timeout' );
+    ok($ret eq '', 'return value for a false wait is an empty string');
+}
+
+EVENTUALLY: {
+    my $ret = 0;
+    waits_ok( sub { wait_until { $ret++ > 2 } }, '>', 2, 'eventually true takes time');
+
+    $ret = 0;
+    my %opts = ( interval => 2, timeout => 5 );
+    waits_ok(
+        sub { wait_until { $ret++; 0 } %opts }, '>', 4,
+        'timeout is respected'
+    );
+    ok(1 <= $ret && $ret <= 3, 'interval option changes iteration speed');
+}
+
+EXCEPTIONS: {
+    my %opts = ( timeout => 2 );
+    warning_is { wait_until { die 'caught!' } %opts } 'caught!',
+      'exceptions usually only warn once';
+}
+
+sub waits_ok  {
+    my ($sub, $cmp, $expected_duration, $test_desc) = @_;
+
+    my $start = time;
+    $sub->();
+    my $elapsed = time - $start;
+
+    cmp_ok($elapsed, $cmp, $expected_duration, $test_desc);
+}
+
+done_testing;

+ 39 - 0
t/convenience.t

@@ -0,0 +1,39 @@
+#! /usr/bin/perl
+
+use strict;
+use warnings;
+use Selenium::Chrome;
+use Selenium::Firefox;
+use Selenium::InternetExplorer;
+use Selenium::PhantomJS;
+use Test::More;
+
+use FindBin;
+use lib $FindBin::Bin . '/lib';
+use TestHarness;
+
+my $harness = TestHarness->new(
+    this_file => $FindBin::Script
+);
+
+my %caps = %{ $harness->base_caps };
+$caps{remote_server_addr} = '127.0.0.1';
+delete $caps{browser_name};
+
+my $firefox = Selenium::Firefox->new( %caps );
+ok( $firefox->browser_name eq 'firefox', 'convenience firefox is okay' );
+$firefox->quit;
+
+my $chrome = Selenium::Chrome->new( %caps );
+ok( $chrome->browser_name eq 'chrome', 'convenience chrome is okay' );
+$chrome->quit;
+
+SKIP: {
+    skip 'Can only test IE on windows', 1 unless $^O eq 'MSWin32';
+
+    my $ie = Selenium::InternetExplorer->new( %caps );
+    ok( $ie->browser_name eq 'internet_explorer', 'convenience ie is okay' );
+    $ie->quit;
+}
+
+done_testing;

+ 26 - 28
t/error.t

@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 use Test::More;
-use Test::Exception;
+use Test::Fatal;
 use Test::LWP::UserAgent;
 use IO::Socket::INET;
 
@@ -25,26 +25,24 @@ UNAVAILABLE_BROWSER: {
         '{"status":13,"sessionId":null,"value":{"message":"The path to..."} }'
     ));
 
-    throws_ok(
-        sub {
-            Selenium::Remote::Driver->new_from_caps(
-                ua => $tua,
-                desired_capabilities => {
-                    browserName => 'chrome'
-                }
-            );
-        }, qr/Could not create new session.*path to/,
-        'Errors in browser configuration are passed to user'
-    );
+    like( exception {
+        Selenium::Remote::Driver->new_from_caps(
+            ua => $tua,
+            desired_capabilities => {
+                browserName => 'chrome'
+            }
+        );
+    }, qr/Could not create new session.*path to/,
+          'Errors in browser configuration are passed to user' );
 }
 
 LOCAL: {
-    throws_ok(
-        sub {
-            Selenium::Remote::Driver->new_from_caps( port => 80 );
-        }, qr/Selenium server did not return proper status/,
-        'Error message for not finding a selenium server is helpful'
-    );
+    like( exception {
+        Selenium::Remote::Driver->new_from_caps(
+            port => 80
+        );
+    }, qr/Selenium server did not return proper status/,
+          'Error message for not finding a selenium server is helpful' );
 }
 
 SAUCE: {
@@ -59,16 +57,16 @@ SAUCE: {
         skip 'Cannot reach saucelabs for Sauce error case ', 1
           unless $sock;
 
-        throws_ok(
-            sub {
-                Selenium::Remote::Driver->new_from_caps(
-                    remote_server_addr => $host,
-                    port => $port,
-                    desired_capabilities => {
-                        browserName => 'invalid'
-                    }
-                );
-            }, qr/Sauce Labs/, 'Saucelabs errors are passed to user');
+        like(exception {
+            Selenium::Remote::Driver->new_from_caps(
+                remote_server_addr => $host,
+                port => $port,
+                desired_capabilities => {
+                    browserName => 'invalid'
+                }
+            );
+        }, qr/Sauce Labs/,
+             'Saucelabs errors are passed to user');
 
     }
 }

+ 25 - 0
t/lib/TestHarness.pm

@@ -42,6 +42,16 @@ has calling_file => (
     required => 1
 );
 
+=attr record
+
+Optional. Determines whether or not this test run should record new
+mocks, or look up a previous recording to replay against them. If the
+parameter is not used during construction, the default behavior is to
+check for the environment variable WD_MOCKING_RECORD to be defined and
+equal to 1.
+
+=cut
+
 has record => (
     is => 'ro',
     init_args => undef,
@@ -129,6 +139,21 @@ has mock_file => (
     }
 );
 
+has website => (
+    is => 'ro',
+    default => sub {
+        my ($self) = @_;
+        my $port = 63636;
+
+        return 'http://' . $self->domain . ':' . $port;
+    }
+);
+
+has domain => (
+    is => 'ro',
+    default => sub { 'localhost' }
+);
+
 sub DEMOLISH {
     my ($self) = @_;
     if ($self->record) {

Разница между файлами не показана из-за своего большого размера
+ 19 - 20
t/mock-recordings/01-driver-mock-MSWin32.json


Разница между файлами не показана из-за своего большого размера
+ 3 - 4
t/mock-recordings/01-driver-mock-darwin.json


Разница между файлами не показана из-за своего большого размера
+ 6 - 9
t/mock-recordings/01-driver-mock-linux.json


Разница между файлами не показана из-за своего большого размера
+ 2 - 3
t/mock-recordings/02-webelement-mock-darwin.json


Разница между файлами не показана из-за своего большого размера
+ 13 - 18
t/mock-recordings/02-webelement-mock-linux.json


+ 28 - 28
t/mock-recordings/10-switch-to-window-mock-darwin.json

@@ -1,41 +1,41 @@
 {
-   "GET session/0c22e3f0-51f7-48f4-8b7c-7997729a50bd/window_handles {}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:07:59 GMT\nServer: Jetty/5.1.x (Mac OS X/10.9.5 x86_64 java/1.7.0_67\nContent-Length: 197\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:07:59 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"0c22e3f0-51f7-48f4-8b7c-7997729a50bd\",\"status\":0,\"state\":\"success\",\"value\":[\"{fadff9d4-cbdc-dc49-8341-520b5c91818a}\"],\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":1963633841}\n",
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:08:00 GMT\nServer: Jetty/5.1.x (Mac OS X/10.9.5 x86_64 java/1.7.0_67\nContent-Length: 238\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:08:00 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"0c22e3f0-51f7-48f4-8b7c-7997729a50bd\",\"status\":0,\"state\":\"success\",\"value\":[\"{fadff9d4-cbdc-dc49-8341-520b5c91818a}\",\"{ef20de3a-4853-f144-ac85-54d82419498c}\"],\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":1225013973}\n"
+   "GET session/46aa7fd1-95f1-46d7-8596-befe7ff6a01d/title {}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Sat, 24 Jan 2015 18:42:21 GMT\nServer: Jetty/5.1.x (Mac OS X/10.10.1 x86_64 java/1.7.0_67\nContent-Length: 200\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Sat, 24 Jan 2015 18:42:21 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"46aa7fd1-95f1-46d7-8596-befe7ff6a01d\",\"status\":0,\"state\":\"success\",\"value\":\"The Perl Programming Language - www.perl.org\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":535096297}\n",
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Sat, 24 Jan 2015 18:42:21 GMT\nServer: Jetty/5.1.x (Mac OS X/10.10.1 x86_64 java/1.7.0_67\nContent-Length: 200\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Sat, 24 Jan 2015 18:42:21 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"46aa7fd1-95f1-46d7-8596-befe7ff6a01d\",\"status\":0,\"state\":\"success\",\"value\":\"The Perl Programming Language - www.perl.org\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":632942286}\n",
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Sat, 24 Jan 2015 18:42:21 GMT\nServer: Jetty/5.1.x (Mac OS X/10.10.1 x86_64 java/1.7.0_67\nContent-Length: 210\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Sat, 24 Jan 2015 18:42:22 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"46aa7fd1-95f1-46d7-8596-befe7ff6a01d\",\"status\":0,\"state\":\"success\",\"value\":\"The Comprehensive Perl Archive Network - www.cpan.org\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":1673698413}\n",
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Sat, 24 Jan 2015 18:42:22 GMT\nServer: Jetty/5.1.x (Mac OS X/10.10.1 x86_64 java/1.7.0_67\nContent-Length: 201\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Sat, 24 Jan 2015 18:42:22 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"46aa7fd1-95f1-46d7-8596-befe7ff6a01d\",\"status\":0,\"state\":\"success\",\"value\":\"The Perl Programming Language - www.perl.org\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":1981091695}\n",
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Sat, 24 Jan 2015 18:42:22 GMT\nServer: Jetty/5.1.x (Mac OS X/10.10.1 x86_64 java/1.7.0_67\nContent-Length: 209\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Sat, 24 Jan 2015 18:42:22 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"46aa7fd1-95f1-46d7-8596-befe7ff6a01d\",\"status\":0,\"state\":\"success\",\"value\":\"The Comprehensive Perl Archive Network - www.cpan.org\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":567364661}\n",
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Sat, 24 Jan 2015 18:42:22 GMT\nServer: Jetty/5.1.x (Mac OS X/10.10.1 x86_64 java/1.7.0_67\nContent-Length: 201\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Sat, 24 Jan 2015 18:42:22 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"46aa7fd1-95f1-46d7-8596-befe7ff6a01d\",\"status\":0,\"state\":\"success\",\"value\":\"The Perl Programming Language - www.perl.org\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":1323608166}\n"
    ],
-   "POST session/0c22e3f0-51f7-48f4-8b7c-7997729a50bd/window {\"name\":\"cpanorg\"}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:08:00 GMT\nServer: Jetty/5.1.x (Mac OS X/10.9.5 x86_64 java/1.7.0_67\nContent-Length: 159\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:08:00 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"0c22e3f0-51f7-48f4-8b7c-7997729a50bd\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":1450435222}\n"
+   "POST session/46aa7fd1-95f1-46d7-8596-befe7ff6a01d/window {\"name\":\"{00a43d5a-ae7a-6e41-bab5-b543628d0812}\"}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Sat, 24 Jan 2015 18:42:21 GMT\nServer: Jetty/5.1.x (Mac OS X/10.10.1 x86_64 java/1.7.0_67\nContent-Length: 159\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Sat, 24 Jan 2015 18:42:21 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"46aa7fd1-95f1-46d7-8596-befe7ff6a01d\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":2091283074}\n"
    ],
-   "POST session/0c22e3f0-51f7-48f4-8b7c-7997729a50bd/execute {\"args\":[],\"script\":\"$(window.open('http://cpan.org/', 'cpanorg'))\"}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:07:59 GMT\nServer: Jetty/5.1.x (Mac OS X/10.9.5 x86_64 java/1.7.0_67\nContent-Length: 159\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:08:00 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"0c22e3f0-51f7-48f4-8b7c-7997729a50bd\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":1629117058}\n"
-   ],
-   "POST session/0c22e3f0-51f7-48f4-8b7c-7997729a50bd/window {\"name\":\"{fadff9d4-cbdc-dc49-8341-520b5c91818a}\"}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:08:00 GMT\nServer: Jetty/5.1.x (Mac OS X/10.9.5 x86_64 java/1.7.0_67\nContent-Length: 159\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:08:00 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"0c22e3f0-51f7-48f4-8b7c-7997729a50bd\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":1014129120}\n"
+   "POST session/46aa7fd1-95f1-46d7-8596-befe7ff6a01d/url {\"url\":\"http://perl.org/\"}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Sat, 24 Jan 2015 18:42:19 GMT\nServer: Jetty/5.1.x (Mac OS X/10.10.1 x86_64 java/1.7.0_67\nContent-Length: 158\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Sat, 24 Jan 2015 18:42:21 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"46aa7fd1-95f1-46d7-8596-befe7ff6a01d\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":995585163}\n"
    ],
    "POST session {\"desiredCapabilities\":{\"acceptSslCerts\":true,\"browserName\":\"firefox\",\"javascriptEnabled\":true,\"platform\":\"ANY\",\"version\":null}}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:07:54 GMT\nServer: Jetty/5.1.x (Mac OS X/10.9.5 x86_64 java/1.7.0_67\nContent-Length: 545\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:07:58 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"0c22e3f0-51f7-48f4-8b7c-7997729a50bd\",\"status\":0,\"state\":null,\"value\":{\"platform\":\"MAC\",\"javascriptEnabled\":true,\"acceptSslCerts\":true,\"browserName\":\"firefox\",\"rotatable\":false,\"locationContextEnabled\":true,\"webdriver.remote.sessionid\":\"0c22e3f0-51f7-48f4-8b7c-7997729a50bd\",\"version\":\"32.0.3\",\"databaseEnabled\":true,\"cssSelectorsEnabled\":true,\"handlesAlerts\":true,\"webStorageEnabled\":true,\"nativeEvents\":false,\"applicationCacheEnabled\":true,\"takesScreenshot\":true},\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":1387044607}\n"
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Sat, 24 Jan 2015 18:42:14 GMT\nServer: Jetty/5.1.x (Mac OS X/10.10.1 x86_64 java/1.7.0_67\nContent-Length: 545\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Sat, 24 Jan 2015 18:42:19 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"46aa7fd1-95f1-46d7-8596-befe7ff6a01d\",\"status\":0,\"state\":null,\"value\":{\"platform\":\"MAC\",\"javascriptEnabled\":true,\"acceptSslCerts\":true,\"browserName\":\"firefox\",\"rotatable\":false,\"locationContextEnabled\":true,\"webdriver.remote.sessionid\":\"46aa7fd1-95f1-46d7-8596-befe7ff6a01d\",\"version\":\"34.0.5\",\"databaseEnabled\":true,\"cssSelectorsEnabled\":true,\"handlesAlerts\":true,\"webStorageEnabled\":true,\"nativeEvents\":false,\"applicationCacheEnabled\":true,\"takesScreenshot\":true},\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":1345975507}\n"
+   ],
+   "POST session/46aa7fd1-95f1-46d7-8596-befe7ff6a01d/execute {\"args\":[],\"script\":\"$(window.open('http://cpan.org/', 'cpanorg'))\"}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Sat, 24 Jan 2015 18:42:21 GMT\nServer: Jetty/5.1.x (Mac OS X/10.10.1 x86_64 java/1.7.0_67\nContent-Length: 159\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Sat, 24 Jan 2015 18:42:21 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"46aa7fd1-95f1-46d7-8596-befe7ff6a01d\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":1714323681}\n"
    ],
-   "DELETE session/0c22e3f0-51f7-48f4-8b7c-7997729a50bd {}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:08:00 GMT\nServer: Jetty/5.1.x (Mac OS X/10.9.5 x86_64 java/1.7.0_67\nContent-Length: 159\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:08:00 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"0c22e3f0-51f7-48f4-8b7c-7997729a50bd\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":1935882440}\n"
+   "POST session/46aa7fd1-95f1-46d7-8596-befe7ff6a01d/execute {\"args\":[],\"script\":\"return window.name = 'perlorg';\"}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Sat, 24 Jan 2015 18:42:21 GMT\nServer: Jetty/5.1.x (Mac OS X/10.10.1 x86_64 java/1.7.0_67\nContent-Length: 163\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Sat, 24 Jan 2015 18:42:21 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"46aa7fd1-95f1-46d7-8596-befe7ff6a01d\",\"status\":0,\"state\":\"success\",\"value\":\"perlorg\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":452687837}\n"
    ],
-   "GET session/0c22e3f0-51f7-48f4-8b7c-7997729a50bd/title {}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:07:59 GMT\nServer: Jetty/5.1.x (Mac OS X/10.9.5 x86_64 java/1.7.0_67\nContent-Length: 201\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:07:59 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"0c22e3f0-51f7-48f4-8b7c-7997729a50bd\",\"status\":0,\"state\":\"success\",\"value\":\"The Perl Programming Language - www.perl.org\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":1434205134}\n",
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:08:00 GMT\nServer: Jetty/5.1.x (Mac OS X/10.9.5 x86_64 java/1.7.0_67\nContent-Length: 200\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:08:00 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"0c22e3f0-51f7-48f4-8b7c-7997729a50bd\",\"status\":0,\"state\":\"success\",\"value\":\"The Perl Programming Language - www.perl.org\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":935491704}\n",
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:08:00 GMT\nServer: Jetty/5.1.x (Mac OS X/10.9.5 x86_64 java/1.7.0_67\nContent-Length: 210\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:08:00 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"0c22e3f0-51f7-48f4-8b7c-7997729a50bd\",\"status\":0,\"state\":\"success\",\"value\":\"The Comprehensive Perl Archive Network - www.cpan.org\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":1263174034}\n",
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:08:00 GMT\nServer: Jetty/5.1.x (Mac OS X/10.9.5 x86_64 java/1.7.0_67\nContent-Length: 200\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:08:00 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"0c22e3f0-51f7-48f4-8b7c-7997729a50bd\",\"status\":0,\"state\":\"success\",\"value\":\"The Perl Programming Language - www.perl.org\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":722361133}\n",
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:08:00 GMT\nServer: Jetty/5.1.x (Mac OS X/10.9.5 x86_64 java/1.7.0_67\nContent-Length: 210\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:08:00 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"0c22e3f0-51f7-48f4-8b7c-7997729a50bd\",\"status\":0,\"state\":\"success\",\"value\":\"The Comprehensive Perl Archive Network - www.cpan.org\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":2033285398}\n",
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:08:00 GMT\nServer: Jetty/5.1.x (Mac OS X/10.9.5 x86_64 java/1.7.0_67\nContent-Length: 199\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:08:00 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"0c22e3f0-51f7-48f4-8b7c-7997729a50bd\",\"status\":0,\"state\":\"success\",\"value\":\"The Perl Programming Language - www.perl.org\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":65977332}\n"
+   "DELETE session/46aa7fd1-95f1-46d7-8596-befe7ff6a01d {}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Sat, 24 Jan 2015 18:42:22 GMT\nServer: Jetty/5.1.x (Mac OS X/10.10.1 x86_64 java/1.7.0_67\nContent-Length: 159\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Sat, 24 Jan 2015 18:42:22 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"46aa7fd1-95f1-46d7-8596-befe7ff6a01d\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":1669332173}\n"
    ],
-   "POST session/0c22e3f0-51f7-48f4-8b7c-7997729a50bd/window {\"name\":\"perlorg\"}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:08:00 GMT\nServer: Jetty/5.1.x (Mac OS X/10.9.5 x86_64 java/1.7.0_67\nContent-Length: 159\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:08:00 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"0c22e3f0-51f7-48f4-8b7c-7997729a50bd\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":1968635741}\n"
+   "POST session/46aa7fd1-95f1-46d7-8596-befe7ff6a01d/window {\"name\":\"cpanorg\"}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Sat, 24 Jan 2015 18:42:22 GMT\nServer: Jetty/5.1.x (Mac OS X/10.10.1 x86_64 java/1.7.0_67\nContent-Length: 159\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Sat, 24 Jan 2015 18:42:22 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"46aa7fd1-95f1-46d7-8596-befe7ff6a01d\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":1436872962}\n"
    ],
-   "POST session/0c22e3f0-51f7-48f4-8b7c-7997729a50bd/url {\"url\":\"http://perl.org/\"}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:07:58 GMT\nServer: Jetty/5.1.x (Mac OS X/10.9.5 x86_64 java/1.7.0_67\nContent-Length: 159\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:07:59 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"0c22e3f0-51f7-48f4-8b7c-7997729a50bd\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":1107966583}\n"
+   "GET session/46aa7fd1-95f1-46d7-8596-befe7ff6a01d/window_handles {}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Sat, 24 Jan 2015 18:42:21 GMT\nServer: Jetty/5.1.x (Mac OS X/10.10.1 x86_64 java/1.7.0_67\nContent-Length: 196\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Sat, 24 Jan 2015 18:42:21 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"46aa7fd1-95f1-46d7-8596-befe7ff6a01d\",\"status\":0,\"state\":\"success\",\"value\":[\"{7d7301fb-0ba6-e649-aac2-5fdd32b1fc06}\"],\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":753721152}\n",
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Sat, 24 Jan 2015 18:42:21 GMT\nServer: Jetty/5.1.x (Mac OS X/10.10.1 x86_64 java/1.7.0_67\nContent-Length: 237\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Sat, 24 Jan 2015 18:42:21 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"46aa7fd1-95f1-46d7-8596-befe7ff6a01d\",\"status\":0,\"state\":\"success\",\"value\":[\"{7d7301fb-0ba6-e649-aac2-5fdd32b1fc06}\",\"{00a43d5a-ae7a-6e41-bab5-b543628d0812}\"],\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":693034498}\n"
    ],
-   "POST session/0c22e3f0-51f7-48f4-8b7c-7997729a50bd/execute {\"args\":[],\"script\":\"return window.name = 'perlorg';\"}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:07:59 GMT\nServer: Jetty/5.1.x (Mac OS X/10.9.5 x86_64 java/1.7.0_67\nContent-Length: 163\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:07:59 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"0c22e3f0-51f7-48f4-8b7c-7997729a50bd\",\"status\":0,\"state\":\"success\",\"value\":\"perlorg\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":361227765}\n"
+   "POST session/46aa7fd1-95f1-46d7-8596-befe7ff6a01d/window {\"name\":\"perlorg\"}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Sat, 24 Jan 2015 18:42:22 GMT\nServer: Jetty/5.1.x (Mac OS X/10.10.1 x86_64 java/1.7.0_67\nContent-Length: 159\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Sat, 24 Jan 2015 18:42:22 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"46aa7fd1-95f1-46d7-8596-befe7ff6a01d\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":1242399465}\n"
    ],
-   "POST session/0c22e3f0-51f7-48f4-8b7c-7997729a50bd/window {\"name\":\"{ef20de3a-4853-f144-ac85-54d82419498c}\"}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:08:00 GMT\nServer: Jetty/5.1.x (Mac OS X/10.9.5 x86_64 java/1.7.0_67\nContent-Length: 158\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:08:00 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"0c22e3f0-51f7-48f4-8b7c-7997729a50bd\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":972578961}\n"
+   "POST session/46aa7fd1-95f1-46d7-8596-befe7ff6a01d/window {\"name\":\"{7d7301fb-0ba6-e649-aac2-5fdd32b1fc06}\"}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Sat, 24 Jan 2015 18:42:22 GMT\nServer: Jetty/5.1.x (Mac OS X/10.10.1 x86_64 java/1.7.0_67\nContent-Length: 158\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Sat, 24 Jan 2015 18:42:22 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"46aa7fd1-95f1-46d7-8596-befe7ff6a01d\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":568541048}\n"
    ]
 }

+ 28 - 28
t/mock-recordings/10-switch-to-window-mock-linux.json

@@ -1,41 +1,41 @@
 {
-   "POST session/20dc20a9-b96c-437b-a61e-41748e18f185/window {\"name\":\"{2740fe00-52ec-4af2-bcb7-d35c76573605}\"}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:44:20 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 157\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:44:20 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"status\":0,\"sessionId\":\"20dc20a9-b96c-437b-a61e-41748e18f185\",\"value\":null,\"state\":\"success\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":24331845}\n"
+   "POST session/7636e703-b0ff-4853-9e8f-04cdcd197392/execute {\"args\":[],\"script\":\"return window.name = 'perlorg';\"}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Tue, 20 Jan 2015 21:32:47 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 161\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Tue, 20 Jan 2015 21:32:47 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"7636e703-b0ff-4853-9e8f-04cdcd197392\",\"status\":0,\"state\":\"success\",\"value\":\"perlorg\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":2291165}\n"
    ],
-   "POST session/20dc20a9-b96c-437b-a61e-41748e18f185/window {\"name\":\"{1075249a-12bb-4a12-bc59-1743aca55275}\"}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:44:19 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 156\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:44:19 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"status\":0,\"sessionId\":\"20dc20a9-b96c-437b-a61e-41748e18f185\",\"value\":null,\"state\":\"success\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":4399653}\n"
+   "POST session/7636e703-b0ff-4853-9e8f-04cdcd197392/window {\"name\":\"perlorg\"}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Tue, 20 Jan 2015 21:32:48 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 156\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Tue, 20 Jan 2015 21:32:48 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"7636e703-b0ff-4853-9e8f-04cdcd197392\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":8938795}\n"
    ],
-   "GET session/20dc20a9-b96c-437b-a61e-41748e18f185/window_handles {}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:44:19 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 195\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:44:19 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"status\":0,\"sessionId\":\"20dc20a9-b96c-437b-a61e-41748e18f185\",\"value\":[\"{2740fe00-52ec-4af2-bcb7-d35c76573605}\"],\"state\":\"success\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":27331543}\n",
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:44:19 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 235\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:44:19 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"status\":0,\"sessionId\":\"20dc20a9-b96c-437b-a61e-41748e18f185\",\"value\":[\"{2740fe00-52ec-4af2-bcb7-d35c76573605}\",\"{1075249a-12bb-4a12-bc59-1743aca55275}\"],\"state\":\"success\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":2369167}\n"
-   ],
-   "POST session/20dc20a9-b96c-437b-a61e-41748e18f185/window {\"name\":\"cpanorg\"}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:44:20 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 157\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:44:20 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"status\":0,\"sessionId\":\"20dc20a9-b96c-437b-a61e-41748e18f185\",\"value\":null,\"state\":\"success\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":25525711}\n"
+   "POST session/7636e703-b0ff-4853-9e8f-04cdcd197392/execute {\"args\":[],\"script\":\"$(window.open('http://cpan.org/', 'cpanorg'))\"}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Tue, 20 Jan 2015 21:32:47 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 157\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Tue, 20 Jan 2015 21:32:47 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"7636e703-b0ff-4853-9e8f-04cdcd197392\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":26466764}\n"
    ],
    "POST session {\"desiredCapabilities\":{\"acceptSslCerts\":true,\"browserName\":\"firefox\",\"javascriptEnabled\":true,\"platform\":\"ANY\",\"version\":null}}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:44:06 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 577\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:44:16 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"status\":0,\"sessionId\":\"20dc20a9-b96c-437b-a61e-41748e18f185\",\"value\":{\"platform\":\"LINUX\",\"javascriptEnabled\":true,\"acceptSslCerts\":true,\"browserName\":\"firefox\",\"rotatable\":false,\"locationContextEnabled\":true,\"webdriver.remote.sessionid\":\"20dc20a9-b96c-437b-a61e-41748e18f185\",\"version\":\"27.0.1\",\"databaseEnabled\":true,\"cssSelectorsEnabled\":true,\"handlesAlerts\":true,\"browserConnectionEnabled\":true,\"webStorageEnabled\":true,\"nativeEvents\":false,\"applicationCacheEnabled\":true,\"takesScreenshot\":true},\"state\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":17834946}\n"
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Tue, 20 Jan 2015 21:32:43 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 577\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Tue, 20 Jan 2015 21:32:45 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"7636e703-b0ff-4853-9e8f-04cdcd197392\",\"status\":0,\"state\":null,\"value\":{\"platform\":\"LINUX\",\"javascriptEnabled\":true,\"acceptSslCerts\":true,\"browserName\":\"firefox\",\"rotatable\":false,\"locationContextEnabled\":true,\"webdriver.remote.sessionid\":\"7636e703-b0ff-4853-9e8f-04cdcd197392\",\"version\":\"27.0.1\",\"databaseEnabled\":true,\"cssSelectorsEnabled\":true,\"handlesAlerts\":true,\"browserConnectionEnabled\":true,\"webStorageEnabled\":true,\"nativeEvents\":false,\"applicationCacheEnabled\":true,\"takesScreenshot\":true},\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":19471836}\n"
+   ],
+   "GET session/7636e703-b0ff-4853-9e8f-04cdcd197392/window_handles {}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Tue, 20 Jan 2015 21:32:47 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 193\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Tue, 20 Jan 2015 21:32:47 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"7636e703-b0ff-4853-9e8f-04cdcd197392\",\"status\":0,\"state\":\"success\",\"value\":[\"{6a65d322-dbf8-454a-9456-48e9825cdb7d}\"],\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":355015}\n",
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Tue, 20 Jan 2015 21:32:47 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 236\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Tue, 20 Jan 2015 21:32:47 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"7636e703-b0ff-4853-9e8f-04cdcd197392\",\"status\":0,\"state\":\"success\",\"value\":[\"{6a65d322-dbf8-454a-9456-48e9825cdb7d}\",\"{df147381-f1a4-4322-87e1-48063cb40e37}\"],\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":21793902}\n"
    ],
-   "POST session/20dc20a9-b96c-437b-a61e-41748e18f185/execute {\"args\":[],\"script\":\"$(window.open('http://cpan.org/', 'cpanorg'))\"}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:44:19 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 156\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:44:19 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"status\":0,\"sessionId\":\"20dc20a9-b96c-437b-a61e-41748e18f185\",\"value\":null,\"state\":\"success\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":2944777}\n"
+   "POST session/7636e703-b0ff-4853-9e8f-04cdcd197392/window {\"name\":\"{df147381-f1a4-4322-87e1-48063cb40e37}\"}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Tue, 20 Jan 2015 21:32:47 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 156\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Tue, 20 Jan 2015 21:32:47 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"7636e703-b0ff-4853-9e8f-04cdcd197392\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":1989637}\n"
    ],
-   "POST session/20dc20a9-b96c-437b-a61e-41748e18f185/window {\"name\":\"perlorg\"}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:44:20 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 156\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:44:20 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"status\":0,\"sessionId\":\"20dc20a9-b96c-437b-a61e-41748e18f185\",\"value\":null,\"state\":\"success\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":7348436}\n"
+   "POST session/7636e703-b0ff-4853-9e8f-04cdcd197392/window {\"name\":\"cpanorg\"}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Tue, 20 Jan 2015 21:32:47 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 156\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Tue, 20 Jan 2015 21:32:47 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"7636e703-b0ff-4853-9e8f-04cdcd197392\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":2083787}\n"
    ],
-   "DELETE session/20dc20a9-b96c-437b-a61e-41748e18f185 {}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:44:20 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 156\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:44:20 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"status\":0,\"sessionId\":\"20dc20a9-b96c-437b-a61e-41748e18f185\",\"value\":null,\"state\":\"success\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":3350041}\n"
+   "DELETE session/7636e703-b0ff-4853-9e8f-04cdcd197392 {}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Tue, 20 Jan 2015 21:32:48 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 157\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Tue, 20 Jan 2015 21:32:48 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"7636e703-b0ff-4853-9e8f-04cdcd197392\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":24960974}\n"
    ],
-   "GET session/20dc20a9-b96c-437b-a61e-41748e18f185/title {}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:44:19 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 198\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:44:19 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"status\":0,\"sessionId\":\"20dc20a9-b96c-437b-a61e-41748e18f185\",\"value\":\"The Perl Programming Language - www.perl.org\",\"state\":\"success\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":8590277}\n",
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:44:19 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 199\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:44:19 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"status\":0,\"sessionId\":\"20dc20a9-b96c-437b-a61e-41748e18f185\",\"value\":\"The Perl Programming Language - www.perl.org\",\"state\":\"success\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":25589988}\n",
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:44:19 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 208\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:44:20 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"status\":0,\"sessionId\":\"20dc20a9-b96c-437b-a61e-41748e18f185\",\"value\":\"The Comprehensive Perl Archive Network - www.cpan.org\",\"state\":\"success\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":30157936}\n",
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:44:20 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 198\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:44:20 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"status\":0,\"sessionId\":\"20dc20a9-b96c-437b-a61e-41748e18f185\",\"value\":\"The Perl Programming Language - www.perl.org\",\"state\":\"success\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":7798062}\n",
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:44:20 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 208\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:44:20 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"status\":0,\"sessionId\":\"20dc20a9-b96c-437b-a61e-41748e18f185\",\"value\":\"The Comprehensive Perl Archive Network - www.cpan.org\",\"state\":\"success\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":28986901}\n",
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:44:20 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 199\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:44:20 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"status\":0,\"sessionId\":\"20dc20a9-b96c-437b-a61e-41748e18f185\",\"value\":\"The Perl Programming Language - www.perl.org\",\"state\":\"success\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":13394224}\n"
+   "POST session/7636e703-b0ff-4853-9e8f-04cdcd197392/window {\"name\":\"{6a65d322-dbf8-454a-9456-48e9825cdb7d}\"}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Tue, 20 Jan 2015 21:32:47 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 156\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Tue, 20 Jan 2015 21:32:47 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"7636e703-b0ff-4853-9e8f-04cdcd197392\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":2488371}\n"
    ],
-   "POST session/20dc20a9-b96c-437b-a61e-41748e18f185/execute {\"args\":[],\"script\":\"return window.name = 'perlorg';\"}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:44:19 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 162\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:44:19 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"status\":0,\"sessionId\":\"20dc20a9-b96c-437b-a61e-41748e18f185\",\"value\":\"perlorg\",\"state\":\"success\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":21429697}\n"
+   "GET session/7636e703-b0ff-4853-9e8f-04cdcd197392/title {}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Tue, 20 Jan 2015 21:32:47 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 199\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Tue, 20 Jan 2015 21:32:47 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"7636e703-b0ff-4853-9e8f-04cdcd197392\",\"status\":0,\"state\":\"success\",\"value\":\"The Perl Programming Language - www.perl.org\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":29125428}\n",
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Tue, 20 Jan 2015 21:32:47 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 198\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Tue, 20 Jan 2015 21:32:47 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"7636e703-b0ff-4853-9e8f-04cdcd197392\",\"status\":0,\"state\":\"success\",\"value\":\"The Perl Programming Language - www.perl.org\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":6953343}\n",
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Tue, 20 Jan 2015 21:32:47 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 208\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Tue, 20 Jan 2015 21:32:47 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"7636e703-b0ff-4853-9e8f-04cdcd197392\",\"status\":0,\"state\":\"success\",\"value\":\"The Comprehensive Perl Archive Network - www.cpan.org\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":31987789}\n",
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Tue, 20 Jan 2015 21:32:47 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 199\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Tue, 20 Jan 2015 21:32:47 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"7636e703-b0ff-4853-9e8f-04cdcd197392\",\"status\":0,\"state\":\"success\",\"value\":\"The Perl Programming Language - www.perl.org\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":29357411}\n",
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Tue, 20 Jan 2015 21:32:47 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 208\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Tue, 20 Jan 2015 21:32:48 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"7636e703-b0ff-4853-9e8f-04cdcd197392\",\"status\":0,\"state\":\"success\",\"value\":\"The Comprehensive Perl Archive Network - www.cpan.org\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":19292607}\n",
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Tue, 20 Jan 2015 21:32:48 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 198\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Tue, 20 Jan 2015 21:32:48 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"7636e703-b0ff-4853-9e8f-04cdcd197392\",\"status\":0,\"state\":\"success\",\"value\":\"The Perl Programming Language - www.perl.org\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":5237134}\n"
    ],
-   "POST session/20dc20a9-b96c-437b-a61e-41748e18f185/url {\"url\":\"http://perl.org/\"}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:44:16 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 156\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:44:19 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"status\":0,\"sessionId\":\"20dc20a9-b96c-437b-a61e-41748e18f185\",\"value\":null,\"state\":\"success\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":4075103}\n"
+   "POST session/7636e703-b0ff-4853-9e8f-04cdcd197392/url {\"url\":\"http://perl.org/\"}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Tue, 20 Jan 2015 21:32:45 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 157\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Tue, 20 Jan 2015 21:32:47 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"7636e703-b0ff-4853-9e8f-04cdcd197392\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":28723770}\n"
    ]
 }

+ 20 - 0
t/mock-recordings/convenience-mock-MSWin32.json

@@ -0,0 +1,20 @@
+{
+   "DELETE session/281c1464-e8df-46d3-8ff2-7f0c80ff0541 {}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 18 Feb 2015 13:52:39 GMT\nServer: Jetty/5.1.x (Windows 7/6.1 amd64 java/1.8.0_31\nContent-Length: 158\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 18 Feb 2015 13:52:39 GMT\nClient-Peer: ::1:4444\nClient-Response-Num: 1\n\n{\"state\":\"success\",\"sessionId\":\"281c1464-e8df-46d3-8ff2-7f0c80ff0541\",\"hCode\":881303524,\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"status\":0}\n"
+   ],
+   "POST session {\"desiredCapabilities\":{\"acceptSslCerts\":true,\"browserName\":\"internet_explorer\",\"javascriptEnabled\":true,\"platform\":\"WINDOWS\",\"version\":\"\"}}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 18 Feb 2015 13:52:42 GMT\nServer: Jetty/5.1.x (Windows 7/6.1 amd64 java/1.8.0_31\nContent-Length: 818\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 18 Feb 2015 13:52:44 GMT\nClient-Peer: ::1:4444\nClient-Response-Num: 1\n\n{\"state\":null,\"sessionId\":\"45d7a89f-b0db-488b-a2d0-09bfa606a79d\",\"hCode\":1696048499,\"value\":{\"browserAttachTimeout\":0,\"enablePersistentHover\":true,\"ie.forceCreateProcessApi\":false,\"ie.usePerProcessProxy\":false,\"ignoreZoomSetting\":false,\"handlesAlerts\":true,\"version\":\"11\",\"platform\":\"WINDOWS\",\"nativeEvents\":true,\"ie.ensureCleanSession\":false,\"elementScrollBehavior\":0,\"ie.browserCommandLineSwitches\":\"\",\"webdriver.remote.sessionid\":\"45d7a89f-b0db-488b-a2d0-09bfa606a79d\",\"requireWindowFocus\":false,\"browserName\":\"internet explorer\",\"initialBrowserUrl\":\"http://localhost:42913/\",\"takesScreenshot\":true,\"javascriptEnabled\":true,\"ignoreProtectedModeSettings\":false,\"enableElementCacheCleanup\":true,\"cssSelectorsEnabled\":true,\"unexpectedAlertBehaviour\":\"dismiss\"},\"class\":\"org.openqa.selenium.remote.Response\",\"status\":0}\n"
+   ],
+   "POST session {\"desiredCapabilities\":{\"acceptSslCerts\":true,\"browserName\":\"chrome\",\"javascriptEnabled\":true,\"platform\":\"ANY\",\"version\":\"\"}}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 18 Feb 2015 13:52:39 GMT\nServer: Jetty/5.1.x (Windows 7/6.1 amd64 java/1.8.0_31\nContent-Length: 726\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 18 Feb 2015 13:52:41 GMT\nClient-Peer: ::1:4444\nClient-Response-Num: 1\n\n{\"state\":null,\"sessionId\":\"3bf9fe23-aa6c-4a4f-968b-b04f56b59464\",\"hCode\":204950963,\"value\":{\"applicationCacheEnabled\":false,\"rotatable\":false,\"mobileEmulationEnabled\":false,\"chrome\":{\"userDataDir\":\"C:\\\\Users\\\\Daniel\\\\AppData\\\\Local\\\\Temp\\\\scoped_dir3696_7705\"},\"takesHeapSnapshot\":true,\"databaseEnabled\":false,\"handlesAlerts\":true,\"version\":\"40.0.2214.94\",\"platform\":\"XP\",\"browserConnectionEnabled\":false,\"nativeEvents\":true,\"acceptSslCerts\":true,\"webdriver.remote.sessionid\":\"3bf9fe23-aa6c-4a4f-968b-b04f56b59464\",\"locationContextEnabled\":true,\"webStorageEnabled\":true,\"browserName\":\"chrome\",\"takesScreenshot\":true,\"javascriptEnabled\":true,\"cssSelectorsEnabled\":true},\"class\":\"org.openqa.selenium.remote.Response\",\"status\":0}\n"
+   ],
+   "DELETE session/3bf9fe23-aa6c-4a4f-968b-b04f56b59464 {}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 18 Feb 2015 13:52:41 GMT\nServer: Jetty/5.1.x (Windows 7/6.1 amd64 java/1.8.0_31\nContent-Length: 157\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 18 Feb 2015 13:52:42 GMT\nClient-Peer: ::1:4444\nClient-Response-Num: 1\n\n{\"state\":\"success\",\"sessionId\":\"3bf9fe23-aa6c-4a4f-968b-b04f56b59464\",\"hCode\":80186222,\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"status\":0}\n"
+   ],
+   "DELETE session/45d7a89f-b0db-488b-a2d0-09bfa606a79d {}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 18 Feb 2015 13:52:44 GMT\nServer: Jetty/5.1.x (Windows 7/6.1 amd64 java/1.8.0_31\nContent-Length: 159\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 18 Feb 2015 13:52:45 GMT\nClient-Peer: ::1:4444\nClient-Response-Num: 1\n\n{\"state\":\"success\",\"sessionId\":\"45d7a89f-b0db-488b-a2d0-09bfa606a79d\",\"hCode\":1563676228,\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"status\":0}\n"
+   ],
+   "POST session {\"desiredCapabilities\":{\"acceptSslCerts\":true,\"browserName\":\"firefox\",\"javascriptEnabled\":true,\"platform\":\"ANY\",\"version\":\"\"}}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 18 Feb 2015 13:52:37 GMT\nServer: Jetty/5.1.x (Windows 7/6.1 amd64 java/1.8.0_31\nContent-Length: 580\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 18 Feb 2015 13:52:39 GMT\nClient-Peer: ::1:4444\nClient-Response-Num: 1\n\n{\"state\":null,\"sessionId\":\"281c1464-e8df-46d3-8ff2-7f0c80ff0541\",\"hCode\":482940564,\"value\":{\"applicationCacheEnabled\":true,\"rotatable\":false,\"handlesAlerts\":true,\"databaseEnabled\":true,\"version\":\"27.0.1\",\"platform\":\"WINDOWS\",\"browserConnectionEnabled\":true,\"nativeEvents\":false,\"acceptSslCerts\":true,\"webdriver.remote.sessionid\":\"281c1464-e8df-46d3-8ff2-7f0c80ff0541\",\"webStorageEnabled\":true,\"locationContextEnabled\":true,\"browserName\":\"firefox\",\"takesScreenshot\":true,\"javascriptEnabled\":true,\"cssSelectorsEnabled\":true},\"class\":\"org.openqa.selenium.remote.Response\",\"status\":0}\n"
+   ]
+}

+ 14 - 0
t/mock-recordings/convenience-mock-darwin.json

@@ -0,0 +1,14 @@
+{
+   "DELETE session/ffa689f5-d71b-42fc-9d54-46ba96f46c07 {}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Mon, 26 Jan 2015 00:39:22 GMT\nServer: Jetty/5.1.x (Mac OS X/10.10.1 x86_64 java/1.7.0_67\nContent-Length: 158\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Mon, 26 Jan 2015 00:39:22 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"ffa689f5-d71b-42fc-9d54-46ba96f46c07\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":307272646}\n"
+   ],
+   "POST session {\"desiredCapabilities\":{\"acceptSslCerts\":true,\"browserName\":\"chrome\",\"javascriptEnabled\":true,\"platform\":\"ANY\",\"version\":\"\"}}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Mon, 26 Jan 2015 00:39:22 GMT\nServer: Jetty/5.1.x (Mac OS X/10.10.1 x86_64 java/1.7.0_67\nContent-Length: 744\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Mon, 26 Jan 2015 00:39:23 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"0bb63c2d-038d-4b2a-84dd-ed913c0f9e7c\",\"status\":0,\"state\":null,\"value\":{\"platform\":\"MAC\",\"acceptSslCerts\":true,\"javascriptEnabled\":true,\"browserName\":\"chrome\",\"chrome\":{\"userDataDir\":\"/var/folders/19/fy4zlkw13c160qyl6h5px_dh55vrly/T/.org.chromium.Chromium.7X4hmM\"},\"rotatable\":false,\"locationContextEnabled\":true,\"mobileEmulationEnabled\":false,\"webdriver.remote.sessionid\":\"0bb63c2d-038d-4b2a-84dd-ed913c0f9e7c\",\"version\":\"40.0.2214.91\",\"takesHeapSnapshot\":true,\"cssSelectorsEnabled\":true,\"databaseEnabled\":false,\"handlesAlerts\":true,\"browserConnectionEnabled\":false,\"nativeEvents\":true,\"webStorageEnabled\":true,\"applicationCacheEnabled\":false,\"takesScreenshot\":true},\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":85267270}\n"
+   ],
+   "POST session {\"desiredCapabilities\":{\"acceptSslCerts\":true,\"browserName\":\"firefox\",\"javascriptEnabled\":true,\"platform\":\"ANY\",\"version\":\"\"}}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Mon, 26 Jan 2015 00:39:19 GMT\nServer: Jetty/5.1.x (Mac OS X/10.10.1 x86_64 java/1.7.0_67\nContent-Length: 545\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Mon, 26 Jan 2015 00:39:22 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"ffa689f5-d71b-42fc-9d54-46ba96f46c07\",\"status\":0,\"state\":null,\"value\":{\"platform\":\"MAC\",\"javascriptEnabled\":true,\"acceptSslCerts\":true,\"browserName\":\"firefox\",\"rotatable\":false,\"locationContextEnabled\":true,\"webdriver.remote.sessionid\":\"ffa689f5-d71b-42fc-9d54-46ba96f46c07\",\"version\":\"34.0.5\",\"databaseEnabled\":true,\"cssSelectorsEnabled\":true,\"handlesAlerts\":true,\"webStorageEnabled\":true,\"nativeEvents\":false,\"applicationCacheEnabled\":true,\"takesScreenshot\":true},\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":1080275328}\n"
+   ],
+   "DELETE session/0bb63c2d-038d-4b2a-84dd-ed913c0f9e7c {}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Mon, 26 Jan 2015 00:39:23 GMT\nServer: Jetty/5.1.x (Mac OS X/10.10.1 x86_64 java/1.7.0_67\nContent-Length: 159\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Mon, 26 Jan 2015 00:39:23 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"0bb63c2d-038d-4b2a-84dd-ed913c0f9e7c\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":2132036725}\n"
+   ]
+}

+ 14 - 0
t/mock-recordings/convenience-mock-linux.json

@@ -0,0 +1,14 @@
+{
+   "DELETE session/101f1644-09ec-4501-8858-5451574cd8e3 {}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Sun, 01 Feb 2015 04:25:59 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 156\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Sun, 01 Feb 2015 04:25:59 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"101f1644-09ec-4501-8858-5451574cd8e3\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":2313854}\n"
+   ],
+   "POST session {\"desiredCapabilities\":{\"acceptSslCerts\":true,\"browserName\":\"chrome\",\"javascriptEnabled\":true,\"platform\":\"ANY\",\"version\":\"\"}}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Sun, 01 Feb 2015 04:25:59 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 698\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Sun, 01 Feb 2015 04:26:00 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"be2c5bf8-64ab-4470-8971-6317a9822c60\",\"status\":0,\"state\":null,\"value\":{\"platform\":\"LINUX\",\"acceptSslCerts\":true,\"javascriptEnabled\":true,\"browserName\":\"chrome\",\"chrome\":{\"userDataDir\":\"/tmp/.com.google.Chrome.pJgqtA\"},\"rotatable\":false,\"locationContextEnabled\":true,\"mobileEmulationEnabled\":false,\"webdriver.remote.sessionid\":\"be2c5bf8-64ab-4470-8971-6317a9822c60\",\"version\":\"40.0.2214.94\",\"takesHeapSnapshot\":true,\"cssSelectorsEnabled\":true,\"databaseEnabled\":false,\"handlesAlerts\":true,\"browserConnectionEnabled\":false,\"nativeEvents\":true,\"webStorageEnabled\":true,\"applicationCacheEnabled\":false,\"takesScreenshot\":true},\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":21990978}\n"
+   ],
+   "POST session {\"desiredCapabilities\":{\"acceptSslCerts\":true,\"browserName\":\"firefox\",\"javascriptEnabled\":true,\"platform\":\"ANY\",\"version\":\"\"}}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Sun, 01 Feb 2015 04:25:57 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 577\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Sun, 01 Feb 2015 04:25:59 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"101f1644-09ec-4501-8858-5451574cd8e3\",\"status\":0,\"state\":null,\"value\":{\"platform\":\"LINUX\",\"javascriptEnabled\":true,\"acceptSslCerts\":true,\"browserName\":\"firefox\",\"rotatable\":false,\"locationContextEnabled\":true,\"webdriver.remote.sessionid\":\"101f1644-09ec-4501-8858-5451574cd8e3\",\"version\":\"27.0.1\",\"databaseEnabled\":true,\"cssSelectorsEnabled\":true,\"handlesAlerts\":true,\"browserConnectionEnabled\":true,\"webStorageEnabled\":true,\"nativeEvents\":false,\"applicationCacheEnabled\":true,\"takesScreenshot\":true},\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":22283134}\n"
+   ],
+   "DELETE session/be2c5bf8-64ab-4470-8971-6317a9822c60 {}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Sun, 01 Feb 2015 04:26:00 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 157\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Sun, 01 Feb 2015 04:26:01 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"be2c5bf8-64ab-4470-8971-6317a9822c60\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":16791543}\n"
+   ]
+}

Разница между файлами не показана из-за своего большого размера
+ 8 - 0
t/mock-recordings/finders-mock-MSWin32.json


Разница между файлами не показана из-за своего большого размера
+ 2 - 0
t/mock-recordings/finders-mock-darwin.json


Разница между файлами не показана из-за своего большого размера
+ 2 - 0
t/mock-recordings/finders-mock-linux.json


Разница между файлами не показана из-за своего большого размера
+ 6 - 6
t/mock-recordings/firefox-profile-mock-darwin.json


Разница между файлами не показана из-за своего большого размера
+ 6 - 9
t/mock-recordings/firefox-profile-mock-linux.json


+ 12 - 12
t/mock-recordings/test-selenium-remote-driver-google-mock-darwin.json

@@ -1,20 +1,20 @@
 {
-   "GET session/bd8a6e01-32b2-4c40-b96b-6af25a394754/title {}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:08:08 GMT\nServer: Jetty/5.1.x (Mac OS X/10.9.5 x86_64 java/1.7.0_67\nContent-Length: 162\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:08:08 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"bd8a6e01-32b2-4c40-b96b-6af25a394754\",\"status\":0,\"state\":\"success\",\"value\":\"Google\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":845292969}\n"
+   "DELETE session/e54f6f37-692a-4b18-b2c0-acee4b00db98 {}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Sat, 24 Jan 2015 18:42:26 GMT\nServer: Jetty/5.1.x (Mac OS X/10.10.1 x86_64 java/1.7.0_67\nContent-Length: 159\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Sat, 24 Jan 2015 18:42:26 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"e54f6f37-692a-4b18-b2c0-acee4b00db98\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":1883757406}\n"
    ],
-   "POST session/bd8a6e01-32b2-4c40-b96b-6af25a394754/url {\"url\":\"http://www.google.com\"}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:08:07 GMT\nServer: Jetty/5.1.x (Mac OS X/10.9.5 x86_64 java/1.7.0_67\nContent-Length: 158\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:08:08 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"bd8a6e01-32b2-4c40-b96b-6af25a394754\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":234481742}\n"
-   ],
-   "DELETE session/bd8a6e01-32b2-4c40-b96b-6af25a394754 {}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:08:08 GMT\nServer: Jetty/5.1.x (Mac OS X/10.9.5 x86_64 java/1.7.0_67\nContent-Length: 159\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:08:08 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"bd8a6e01-32b2-4c40-b96b-6af25a394754\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":1184195640}\n"
+   "POST session/e54f6f37-692a-4b18-b2c0-acee4b00db98/url {\"url\":\"http://www.google.com\"}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Sat, 24 Jan 2015 18:42:25 GMT\nServer: Jetty/5.1.x (Mac OS X/10.10.1 x86_64 java/1.7.0_67\nContent-Length: 158\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Sat, 24 Jan 2015 18:42:26 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"e54f6f37-692a-4b18-b2c0-acee4b00db98\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":829766234}\n"
    ],
    "POST session {\"desiredCapabilities\":{\"acceptSslCerts\":true,\"browserName\":\"firefox\",\"javascriptEnabled\":false,\"platform\":\"ANY\",\"version\":null}}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:07:54 GMT\nServer: Jetty/5.1.x (Mac OS X/10.9.5 x86_64 java/1.7.0_67\nContent-Length: 544\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:08:07 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"bd8a6e01-32b2-4c40-b96b-6af25a394754\",\"status\":0,\"state\":null,\"value\":{\"platform\":\"MAC\",\"javascriptEnabled\":true,\"acceptSslCerts\":true,\"browserName\":\"firefox\",\"rotatable\":false,\"locationContextEnabled\":true,\"webdriver.remote.sessionid\":\"bd8a6e01-32b2-4c40-b96b-6af25a394754\",\"version\":\"32.0.3\",\"databaseEnabled\":true,\"cssSelectorsEnabled\":true,\"handlesAlerts\":true,\"webStorageEnabled\":true,\"nativeEvents\":false,\"applicationCacheEnabled\":true,\"takesScreenshot\":true},\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":656518097}\n"
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Sat, 24 Jan 2015 18:42:14 GMT\nServer: Jetty/5.1.x (Mac OS X/10.10.1 x86_64 java/1.7.0_67\nContent-Length: 544\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Sat, 24 Jan 2015 18:42:25 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"e54f6f37-692a-4b18-b2c0-acee4b00db98\",\"status\":0,\"state\":null,\"value\":{\"platform\":\"MAC\",\"javascriptEnabled\":true,\"acceptSslCerts\":true,\"browserName\":\"firefox\",\"rotatable\":false,\"locationContextEnabled\":true,\"webdriver.remote.sessionid\":\"e54f6f37-692a-4b18-b2c0-acee4b00db98\",\"version\":\"34.0.5\",\"databaseEnabled\":true,\"cssSelectorsEnabled\":true,\"handlesAlerts\":true,\"webStorageEnabled\":true,\"nativeEvents\":false,\"applicationCacheEnabled\":true,\"takesScreenshot\":true},\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":653167242}\n"
+   ],
+   "POST session/e54f6f37-692a-4b18-b2c0-acee4b00db98/element {\"using\":\"xpath\",\"value\":\"//body\"}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Sat, 24 Jan 2015 18:42:26 GMT\nServer: Jetty/5.1.x (Mac OS X/10.10.1 x86_64 java/1.7.0_67\nContent-Length: 169\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Sat, 24 Jan 2015 18:42:26 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"e54f6f37-692a-4b18-b2c0-acee4b00db98\",\"status\":0,\"state\":\"success\",\"value\":{\"ELEMENT\":\"0\"},\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":943097115}\n"
    ],
-   "POST session/bd8a6e01-32b2-4c40-b96b-6af25a394754/element {\"using\":\"xpath\",\"value\":\"//body\"}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:08:08 GMT\nServer: Jetty/5.1.x (Mac OS X/10.9.5 x86_64 java/1.7.0_67\nContent-Length: 170\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:08:08 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"bd8a6e01-32b2-4c40-b96b-6af25a394754\",\"status\":0,\"state\":\"success\",\"value\":{\"ELEMENT\":\"0\"},\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":1020548632}\n"
+   "GET session/e54f6f37-692a-4b18-b2c0-acee4b00db98/title {}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Sat, 24 Jan 2015 18:42:26 GMT\nServer: Jetty/5.1.x (Mac OS X/10.10.1 x86_64 java/1.7.0_67\nContent-Length: 162\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Sat, 24 Jan 2015 18:42:26 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"e54f6f37-692a-4b18-b2c0-acee4b00db98\",\"status\":0,\"state\":\"success\",\"value\":\"Google\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":206684361}\n"
    ],
-   "GET session/bd8a6e01-32b2-4c40-b96b-6af25a394754/element/0/text {}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:08:08 GMT\nServer: Jetty/5.1.x (Mac OS X/10.9.5 x86_64 java/1.7.0_67\nContent-Length: 356\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:08:08 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"bd8a6e01-32b2-4c40-b96b-6af25a394754\",\"status\":0,\"state\":\"success\",\"value\":\"+You\\nGmail\\nImages\\nSign in\\nGoogle SearchI\\u0027m Feeling Lucky\\nGet real-time results and the latest news from the 2014 Midterm Elections\\nPrivacy \\u0026 Terms Settings\\nAdvertising Business About\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":1601623645}\n"
+   "GET session/e54f6f37-692a-4b18-b2c0-acee4b00db98/element/0/text {}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Sat, 24 Jan 2015 18:42:26 GMT\nServer: Jetty/5.1.x (Mac OS X/10.10.1 x86_64 java/1.7.0_67\nContent-Length: 274\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Sat, 24 Jan 2015 18:42:26 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"e54f6f37-692a-4b18-b2c0-acee4b00db98\",\"status\":0,\"state\":\"success\",\"value\":\"+You\\nGmail\\nImages\\nSign in\\nGoogle SearchI\\u0027m Feeling Lucky\\nPrivacy Terms Settings\\nAdvertising Business About\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":1994517208}\n"
    ]
 }

+ 12 - 12
t/mock-recordings/test-selenium-remote-driver-google-mock-linux.json

@@ -1,20 +1,20 @@
 {
-   "GET session/74b05200-870b-421c-8ecc-48d7fca92bcb/element/0/text {}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:44:16 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 344\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:44:17 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"status\":0,\"sessionId\":\"74b05200-870b-421c-8ecc-48d7fca92bcb\",\"value\":\"+You\\nGmail\\nImages\\nSign in\\nGoogle SearchI'm Feeling Lucky\\nGet real-time results and the latest news from the 2014 Midterm Elections\\nPrivacy & Terms Settings\\nAdvertising Business About\",\"state\":\"success\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":10983873}\n"
+   "GET session/3f90768c-7a6b-426a-9453-94612bc1b5fe/title {}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Tue, 20 Jan 2015 21:32:54 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 161\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Tue, 20 Jan 2015 21:32:54 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"3f90768c-7a6b-426a-9453-94612bc1b5fe\",\"status\":0,\"state\":\"success\",\"value\":\"Google\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":28986901}\n"
    ],
-   "POST session/74b05200-870b-421c-8ecc-48d7fca92bcb/url {\"url\":\"http://www.google.com\"}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:44:13 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 157\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:44:16 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"status\":0,\"sessionId\":\"74b05200-870b-421c-8ecc-48d7fca92bcb\",\"value\":null,\"state\":\"success\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":28907098}\n"
+   "POST session/3f90768c-7a6b-426a-9453-94612bc1b5fe/element {\"using\":\"xpath\",\"value\":\"//body\"}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Tue, 20 Jan 2015 21:32:54 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 167\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Tue, 20 Jan 2015 21:32:54 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"3f90768c-7a6b-426a-9453-94612bc1b5fe\",\"status\":0,\"state\":\"success\",\"value\":{\"ELEMENT\":\"0\"},\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":7348436}\n"
    ],
-   "GET session/74b05200-870b-421c-8ecc-48d7fca92bcb/title {}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:44:16 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 161\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:44:16 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"status\":0,\"sessionId\":\"74b05200-870b-421c-8ecc-48d7fca92bcb\",\"value\":\"Google\",\"state\":\"success\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":26993204}\n"
+   "POST session {\"desiredCapabilities\":{\"acceptSslCerts\":true,\"browserName\":\"firefox\",\"javascriptEnabled\":false,\"platform\":\"ANY\",\"version\":null}}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Tue, 20 Jan 2015 21:32:43 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 577\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Tue, 20 Jan 2015 21:32:53 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"3f90768c-7a6b-426a-9453-94612bc1b5fe\",\"status\":0,\"state\":null,\"value\":{\"platform\":\"LINUX\",\"javascriptEnabled\":true,\"acceptSslCerts\":true,\"browserName\":\"firefox\",\"rotatable\":false,\"locationContextEnabled\":true,\"webdriver.remote.sessionid\":\"3f90768c-7a6b-426a-9453-94612bc1b5fe\",\"version\":\"27.0.1\",\"databaseEnabled\":true,\"cssSelectorsEnabled\":true,\"handlesAlerts\":true,\"browserConnectionEnabled\":true,\"webStorageEnabled\":true,\"nativeEvents\":false,\"applicationCacheEnabled\":true,\"takesScreenshot\":true},\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":19003540}\n"
    ],
-   "DELETE session/74b05200-870b-421c-8ecc-48d7fca92bcb {}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:44:17 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 157\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:44:17 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"status\":0,\"sessionId\":\"74b05200-870b-421c-8ecc-48d7fca92bcb\",\"value\":null,\"state\":\"success\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":10558334}\n"
+   "GET session/3f90768c-7a6b-426a-9453-94612bc1b5fe/element/0/text {}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Tue, 20 Jan 2015 21:32:54 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 363\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Tue, 20 Jan 2015 21:32:55 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"3f90768c-7a6b-426a-9453-94612bc1b5fe\",\"status\":0,\"state\":\"success\",\"value\":\"+You\\nGmail\\nImages\\nSign in\\nGoogle SearchI\\u0027m Feeling Lucky\\nLive! Watch President Obama\\u0027s State of the Union address. Tonight on YouTube at 9p ET\\nPrivacy Terms Settings\\nAdvertising Business About\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":5194611}\n"
    ],
-   "POST session {\"desiredCapabilities\":{\"acceptSslCerts\":true,\"browserName\":\"firefox\",\"javascriptEnabled\":false,\"platform\":\"ANY\",\"version\":null}}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:44:06 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 577\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:44:13 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"status\":0,\"sessionId\":\"74b05200-870b-421c-8ecc-48d7fca92bcb\",\"value\":{\"platform\":\"LINUX\",\"javascriptEnabled\":true,\"acceptSslCerts\":true,\"browserName\":\"firefox\",\"rotatable\":false,\"locationContextEnabled\":true,\"webdriver.remote.sessionid\":\"74b05200-870b-421c-8ecc-48d7fca92bcb\",\"version\":\"27.0.1\",\"databaseEnabled\":true,\"cssSelectorsEnabled\":true,\"handlesAlerts\":true,\"browserConnectionEnabled\":true,\"webStorageEnabled\":true,\"nativeEvents\":false,\"applicationCacheEnabled\":true,\"takesScreenshot\":true},\"state\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":10999907}\n"
+   "DELETE session/3f90768c-7a6b-426a-9453-94612bc1b5fe {}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Tue, 20 Jan 2015 21:32:55 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 157\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Tue, 20 Jan 2015 21:32:55 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"3f90768c-7a6b-426a-9453-94612bc1b5fe\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":13293943}\n"
    ],
-   "POST session/74b05200-870b-421c-8ecc-48d7fca92bcb/element {\"using\":\"xpath\",\"value\":\"//body\"}" : [
-      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Wed, 05 Nov 2014 04:44:16 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 167\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Wed, 05 Nov 2014 04:44:16 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"status\":0,\"sessionId\":\"74b05200-870b-421c-8ecc-48d7fca92bcb\",\"value\":{\"ELEMENT\":\"0\"},\"state\":\"success\",\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":4324892}\n"
+   "POST session/3f90768c-7a6b-426a-9453-94612bc1b5fe/url {\"url\":\"http://www.google.com\"}" : [
+      "HTTP/1.1 200 OK\nCache-Control: no-cache\nCache-Control: no-cache\nConnection: close\nDate: Tue, 20 Jan 2015 21:32:53 GMT\nServer: Jetty/5.1.x (Linux/3.2.0-23-generic-pae i386 java/1.7.0_51\nContent-Length: 157\nContent-Type: application/json; charset=utf-8\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nExpires: Thu, 01 Jan 1970 00:00:00 GMT\nClient-Date: Tue, 20 Jan 2015 21:32:54 GMT\nClient-Peer: 127.0.0.1:4444\nClient-Response-Num: 1\n\n{\"sessionId\":\"3f90768c-7a6b-426a-9453-94612bc1b5fe\",\"status\":0,\"state\":\"success\",\"value\":null,\"class\":\"org.openqa.selenium.remote.Response\",\"hCode\":25525711}\n"
    ]
 }

+ 0 - 0
t/uploadTest


Некоторые файлы не были показаны из-за большого количества измененных файлов