Bläddra i källkod

Resolve #78: Test::WebDriver has been merged into this distro as Test::Selenium::Remote::Driver

Mark Stosberg 12 år sedan
förälder
incheckning
273cfe594d
6 ändrade filer med 789 tillägg och 3 borttagningar
  1. 43 2
      Changes
  2. 4 0
      dist.ini
  3. 521 0
      lib/Test/Selenium/Remote/Driver.pm
  4. 200 0
      lib/Test/Selenium/Remote/WebElement.pm
  5. 2 1
      t/00-load.t
  6. 19 0
      t/Test-Selenium-Remote-Driver-google.t

+ 43 - 2
Changes

@@ -1,4 +1,5 @@
 Revision history for Selenium-Remote-Driver
+
 0.18   7-26-2013
         [THINGS THAT MIGHT BREAK YOUR CODE]
         - We now 'croak' instead of returning error strings in a number of
@@ -13,6 +14,13 @@ Revision history for Selenium-Remote-Driver
           https://github.com/aivaturi/Selenium-Remote-Driver/pull/77
 
         [NEW FEATURES]
+        - Merged Test::WebDriver into this distribution as Test::Selenium::Remote::Driver.
+          Several updates to it are included since the last release. (Mark Stosberg)   
+
+        - Added and started using Test::Selenium::Remote::WebElement. This a
+          sub-class of Selenium::Remote::WebElement. This allows testing
+          methods on the "WebElement" object as well as main '$driver' object.
+
         - Added a pause() function, that sleeps for the given number of milliseconds, just like
           Test::WWW::Selenium.  https://github.com/aivaturi/Selenium-Remote-Driver/issues/60
 
@@ -24,7 +32,7 @@ Revision history for Selenium-Remote-Driver
         - new() now accepts a 'webelement_class' option to allow you to specify an alternate WebElement class.
           This could be used to add testing methods or functionality for WebElements.  
 
-        - New shortcut methods absorbed from Test::WebDriver:
+        - Shortcut methods absorbed from Test::WebDriver into Selenium::Remote::Driver:
 
           get_text() Returns the text for a particular element.
           get_body() Returns the text for the whole body.
@@ -33,6 +41,39 @@ Revision history for Selenium-Remote-Driver
         - New short method to save a screenshot to a file: $driver->capture_screenshot($filename).
           The name was chosen for compatibility with a WWW::Selenium method of the same name.  
 
+        - Removed 'get_location()' as an alias in Test::Selenium::Remote::Driver. 
+          Please use the more specific 'get_current_url()' instead.
+          It's a more specific name that matches the Selenium API method name.
+
+        - Adding text checking methods to Test::Selenium::Remote::Driver
+
+            # Run regex's against the entire page source
+            content_like()
+            content_unlike()
+
+            # Run regex's against just the "text" of the page
+            text_like()
+            text_unlike()
+
+            # Check page source for strings
+            content_contains()
+            content_lacks()
+
+            # Check text of page for strings
+            text_contains()
+            text_lacks()
+
+          The names are intentionally the same as their Test::WWW::Mechanize
+          counterparts.  However, these methods may take an arrayref of regular
+          expressions for improved performance when you want to run multiple
+          tests against the same page.
+
+          Normally Selenium::Remote::Driver would make an HTTP request to the
+          driven browser for each content check. By using an arrayref for
+          multiple checks of the same page, you can avoid the extra HTTP
+          requests and speed up your tests. (Mark Stosberg)
+
+
         [DOCUMENTATION]
         - Updated ChangeLog to reflect that debug_on() and debug_off() methods were added in 0.16.
 
@@ -46,7 +87,7 @@ Revision history for Selenium-Remote-Driver
            explains it in much more detail than I can.
            Fortunately, it boils down to this: http://perldoc.perl.org/5.10.0/functions/use.html#use-VERSION
 
-
+        - Added Test::LongString as a dependency to support the new content checking methods.
 
 0.17   7-13-2013
         - Second build with DistZilla, now with distzilla branch merged into master.

+ 4 - 0
dist.ini

@@ -1,6 +1,8 @@
 name = Selenium-Remote-Driver
 version = 0.18
 author = Aditya Ivaturi <ivaturi@gmail.com>
+author = Luke Closs <cpan@5thplane.com>
+author = Mark Stosberg <mark@stosberg.com>
 license = Apache_2_0
 
 copyright_holder = Aditya Ivaturi
@@ -28,6 +30,7 @@ copyright_holder = Aditya Ivaturi
 [Prereqs / BuildRequires]
 LWP::Protocol::PSGI = 0
 Test::More = 0
+Test::Builder = 0
 
 [Prereqs / RuntimeRequires]
 perl = 5.010
@@ -39,6 +42,7 @@ JSON = 0
 LWP::UserAgent = 0
 Net::Ping = 0
 MIME::Base64 = 0
+IO::Socket = 0
 
 [Prereqs / TestRequires]
 Carp = 1.25

+ 521 - 0
lib/Test/Selenium/Remote/Driver.pm

@@ -0,0 +1,521 @@
+use strict;
+use warnings;
+package Test::Selenium::Remote::Driver;
+use parent  'Selenium::Remote::Driver';
+# ABSTRACT: Useful testing subclass for Selenium::Remote::Driver
+
+use Test::Selenium::Remote::WebElement;
+use Test::More;
+use Test::Builder;
+use Test::LongString;
+use IO::Socket;
+
+our $AUTOLOAD;
+
+my $Test = Test::Builder->new;
+$Test->exported_to(__PACKAGE__);
+
+my %comparator = (
+    is       => 'is_eq',
+    isnt     => 'isnt_eq',
+    like     => 'like',
+    unlike   => 'unlike',
+);
+my $comparator_keys = join '|', keys %comparator;
+
+# These commands don't require a locator
+my %no_locator = map { $_ => 1 }
+                qw( alert_text current_window_handle current_url
+                    title page_source body location path);
+
+sub no_locator {
+    my $self   = shift;
+    my $method = shift;
+    return $no_locator{$method};
+}
+
+sub AUTOLOAD {
+    my $name = $AUTOLOAD;
+    $name =~ s/.*:://;
+    return if $name eq 'DESTROY';
+    my $self = $_[0];
+
+    my $sub;
+    if ($name =~ /(\w+)_($comparator_keys)$/i) {
+        my $getter = "get_$1";
+        my $comparator = $comparator{lc $2};
+
+        # make a subroutine that will call Test::Builder's test methods
+        # with driver data from the getter
+        if ($self->no_locator($1)) {
+            $sub = sub {
+                my( $self, $str, $name ) = @_;
+                diag "Test::Selenium::Remote::Driver running no_locator $getter (@_[1..$#_])"
+                    if $self->{verbose};
+                $name = "$getter, '$str'"
+                    if $self->{default_names} and !defined $name;
+                no strict 'refs';
+                my $rc = $Test->$comparator( $self->$getter, $str, $name );
+                if (!$rc && $self->error_callback) {
+                    &{$self->error_callback}($name);
+                }
+                return $rc;
+            };
+        }
+        else {
+            $sub = sub {
+                my( $self, $locator, $str, $name ) = @_;
+                diag "Test::Selenium::Remote::Driver running with locator $getter (@_[1..$#_])"
+                    if $self->{verbose};
+                $name = "$getter, $locator, '$str'"
+                    if $self->{default_names} and !defined $name;
+                no strict 'refs';
+                no strict 'refs';
+                my $rc = $Test->$comparator( $self->$getter($locator), $str, $name );
+                if (!$rc && $self->error_callback) {
+                    &{$self->error_callback}($name);
+                }
+                return $rc;
+            };
+        }
+    }
+    elsif ($name =~ /(\w+?)_?ok$/i) {
+        my $cmd = $1;
+
+        # make a subroutine for ok() around the selenium command
+        $sub = sub {
+            my( $self, $arg1, $arg2, $name ) = @_;
+            if ($self->{default_names} and !defined $name) {
+                $name = $cmd;
+                $name .= ", $arg1" if defined $arg1;
+                $name .= ", $arg2" if defined $arg2;
+            }
+            diag "Test::Selenium::Remote::Driver running _ok $cmd (@_[1..$#_])"
+                    if $self->{verbose};
+
+            local $Test::Builder::Level = $Test::Builder::Level + 1;
+            my $rc = '';
+            eval { $rc = $self->$cmd( $arg1, $arg2 ) };
+            die $@ if $@ and $@ =~ /Can't locate object method/;
+            diag($@) if $@;
+            $rc = ok( $rc, $name );
+            if (!$rc && $self->error_callback) {
+                &{$self->error_callback}($name);
+            }
+            return $rc;
+        };
+    }
+
+    # jump directly to the new subroutine, avoiding an extra frame stack
+    if ($sub) {
+        no strict 'refs';
+        *{$AUTOLOAD} = $sub;
+        goto &$AUTOLOAD;
+    }
+    else {
+        # try to pass through to Selenium::Remote::Driver
+        my $sel = 'Selenium::Remote::Driver';
+        my $sub = "${sel}::${name}";
+        goto &$sub if exists &$sub;
+        my ($package, $filename, $line) = caller;
+        die qq(Can't locate object method "$name" via package ")
+            . __PACKAGE__
+            . qq(" (also tried "$sel") at $filename line $line\n);
+    }
+}
+
+sub error_callback {
+    my ($self, $cb) = @_;
+    if (defined($cb)) {
+        $self->{error_callback} = $cb;
+    }
+    return $self->{error_callback};
+}
+
+=head2 new ( %opts )
+
+This will create a new Test::Selenium::Remote::Driver object, which subclasses 
+L<Selenium::Remote::Driver>.  This subclass provides useful testing
+functions.  It is modeled on L<Test::WWW::Selenium>.
+
+Environment vars can be used to specify options to pass to
+L<Selenium::Remote::Driver>. ENV vars are prefixed with C<TWD_>.
+
+Set the Selenium server address with C<$TWD_HOST> and C<$TWD_PORT>.
+
+Pick which browser is used using the  C<$TWD_BROWSER>, C<$TWD_VERSION>,
+C<$TWD_PLATFORM>, C<$TWD_JAVASCRIPT>, C<$TWD_EXTRA_CAPABILITIES>.
+
+See L<Selenium::Driver::Remote> for the meanings of these options.
+
+=cut
+
+sub new {
+    my ($class, %p) = @_;
+
+    for my $opt (qw/remote_server_addr port browser_name version platform
+                    javascript auto_close extra_capabilities/) {
+        $p{$opt} ||= $ENV{ 'TWD_' . uc($opt) };
+    }
+    $p{browser_name}       ||= $ENV{TWD_BROWSER}; # ykwim
+    $p{remote_server_addr} ||= $ENV{TWD_HOST};    # ykwim
+    $p{webelement_class}   ||= 'Test::Selenium::Remote::WebElement';
+
+    my $self = $class->SUPER::new(%p);
+    $self->{verbose} = $p{verbose};
+    return $self;
+}
+
+=head2 server_is_running( $host, $port )
+
+Returns true if a Selenium server is running.  The host and port 
+parameters are optional, and default to C<localhost:4444>.
+
+Environment vars C<TWD_HOST> and C<TWD_PORT> can also be used to
+determine the server to check.
+
+=cut
+
+sub server_is_running {
+    my $class_or_self = shift;
+    my $host = $ENV{TWD_HOST} || shift || 'localhost';
+    my $port = $ENV{TWD_PORT} || shift || 4444;
+
+    return ($host, $port) if IO::Socket::INET->new(
+        PeerAddr => $host,
+        PeerPort => $port,
+    );
+    return;
+
+}
+
+=head2 $twd->content_like( $regex [, $desc ] )
+
+   $twd->content_like( $regex [, $desc ] )
+   $twd->content_like( [$regex_1, $regex_2] [, $desc ] )
+
+Tells if the content of the page matches I<$regex>. If an arrayref of regex's
+are provided, one 'test' is run for each regex against the content of the
+current page.
+
+A default description of 'Content is like "$regex"' will be provided if there
+is no description.
+
+=cut
+
+sub content_like {
+    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 like "$regex"} if (not defined $desc);
+        return like_string($content , $regex, $desc );
+    }
+    elsif (ref $regex eq 'ARRAY') {
+        for my $re (@$regex) {
+            my $desc = qq{Content is like "$re"} if (not defined $desc);
+            like_string($content , $re, $desc );
+        }
+    }
+}
+
+=head2 $twd->content_unlike( $regex [, $desc ] )
+
+   $twd->content_unlike( $regex [, $desc ] )
+   $twd->content_unlike( [$regex_1, $regex_2] [, $desc ] )
+
+Tells if the content of the page does NOT match I<$regex>. If an arrayref of regex's
+are provided, one 'test' is run for each regex against the content of the
+current page.
+
+A default description of 'Content is unlike "$regex"' will be provided if there
+is no description.
+
+=cut
+
+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);
+        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);
+            unlike_string($content , $re, $desc );
+        }
+    }
+}
+
+
+=head2 $twd->text_like( $regex [, $desc ] )
+
+   $twd->text_like( $regex [, $desc ] )
+   $twd->text_like( [$regex_1, $regex_2] [, $desc ] )
+
+Tells if the text of the page (as returned by C<< get_body() >>)  matches
+I<$regex>. If an arrayref of regex's are provided, one 'test' is run for each
+regex against the content of the current page.
+
+A default description of 'Content is like "$regex"' will be provided if there
+is no description.
+
+To also match the HTML see, C<< content_unlike() >>.
+
+=cut
+
+sub text_like {
+    my $self = shift;
+    my $regex = shift;
+    my $desc = shift;
+
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+    my $text = $self->get_body();
+
+    if (not ref $regex eq 'ARRAY') {
+        my $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);
+            like_string($text , $re, $desc );
+        }
+    }
+}
+
+=head2 $twd->text_unlike( $regex [, $desc ] )
+
+   $twd->text_unlike( $regex [, $desc ] )
+   $twd->text_unlike( [$regex_1, $regex_2] [, $desc ] )
+
+Tells if the text of the page (as returned by C<< get_body() >>)
+ does NOT match I<$regex>. If an arrayref of regex's
+are provided, one 'test' is run for each regex against the content of the
+current page.
+
+A default description of 'Text is unlike "$regex"' will be provided if there
+is no description.
+
+To also match the HTML see, C<< content_unlike() >>.
+
+=cut
+
+sub text_unlike {
+    my $self = shift;
+    my $regex = shift;
+    my $desc = shift;
+
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+    my $text = $self->get_body();
+
+    if (not ref $regex eq 'ARRAY') {
+        my $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);
+            unlike_string($text , $re, $desc );
+        }
+    }
+}
+
+#####
+
+=head2 $twd->content_contains( $str [, $desc ] )
+
+   $twd->content_contains( $str [, $desc ] )
+   $twd->content_contains( [$str_1, $str_2] [, $desc ] )
+
+Tells if the content of the page contains I<$str>. If an arrayref of strngs's
+are provided, one 'test' is run for each string against the content of the
+current page.
+
+A default description of 'Content contains "$str"' will be provided if there
+is no description.
+
+=cut
+
+sub content_contains {
+    my $self = shift;
+    my $str = shift;
+    my $desc = shift;
+
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+    my $content = $self->get_page_source();
+
+    if (not ref $str eq 'ARRAY') {
+        my $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);
+            contains_string($content , $s, $desc );
+        }
+    }
+}
+
+=head2 $twd->content_lacks( $str [, $desc ] )
+
+   $twd->content_lacks( $str [, $desc ] )
+   $twd->content_lacks( [$str_1, $str_2] [, $desc ] )
+
+Tells if the content of the page does NOT contain I<$str>. If an arrayref of strings
+are provided, one 'test' is run for each string against the content of the
+current page.
+
+A default description of 'Content lacks "$str"' will be provided if there
+is no description.
+
+=cut
+
+sub content_lacks {
+    my $self = shift;
+    my $str = shift;
+    my $desc = shift;
+
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+    my $content = $self->get_page_source();
+
+    if (not ref $str eq 'ARRAY') {
+        my $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);
+            lacks_string($content , $s, $desc );
+        }
+    }
+}
+
+
+=head2 $twd->text_contains( $str [, $desc ] )
+
+   $twd->text_contains( $str [, $desc ] )
+   $twd->text_contains( [$str_1, $str_2] [, $desc ] )
+
+Tells if the text of the page (as returned by C<< get_body() >>) contains
+I<$str>. If an arrayref of strings are provided, one 'test' is run for each
+regex against the content of the current page.
+
+A default description of 'Text contains "$str"' will be provided if there
+is no description.
+
+To also match the HTML see, C<< content_uncontains() >>.
+
+=cut
+
+sub text_contains {
+    my $self = shift;
+    my $str = shift;
+    my $desc = shift;
+
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+    my $text = $self->get_body();
+
+    if (not ref $str eq 'ARRAY') {
+        my $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);
+            contains_string($text , $s, $desc );
+        }
+    }
+}
+
+=head2 $twd->text_lacks( $str [, $desc ] )
+
+   $twd->text_lacks( $str [, $desc ] )
+   $twd->text_lacks( [$str_1, $str_2] [, $desc ] )
+
+Tells if the text of the page (as returned by C<< get_body() >>)
+ does NOT contain I<$str>. If an arrayref of strings
+are provided, one 'test' is run for each regex against the content of the
+current page.
+
+A default description of 'Text is lacks "$str"' will be provided if there
+is no description.
+
+To also match the HTML see, C<< content_lacks() >>.
+
+=cut
+
+sub text_lacks {
+    my $self = shift;
+    my $str = shift;
+    my $desc = shift;
+
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+    my $text = $self->get_body();
+
+    if (not ref $str eq 'ARRAY') {
+        my $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);
+            lacks_string($text , $s, $desc );
+        }
+    }
+}
+
+
+
+1;
+
+__END__
+
+=head1 NOTES
+
+This module was forked from Test::WebDriver 0.01.
+
+For Best Practice - I recommend subclassing Test::Selenium::Remote::Driver for your application,
+and then refactoring common or app specific methods into MyApp::WebDriver so that
+your test files do not have much duplication.  As your app changes, you can update
+MyApp::WebDriver rather than all the individual test files.
+
+=head1 AUTHORS
+
+=over 4
+
+=item *
+
+Created by: Luke Closs <lukec@cpan.org>, but inspired by
+ L<Test::WWW::Selenium> and its authors.
+
+=back
+
+=head1 CONTRIBUTORS
+
+This work was sponsored by Prime Radiant, Inc. Mark Stosberg <mark@stosberg.com> also contributed.
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (c) 2012 Prime Radiant, Inc.
+
+This program is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself.

+ 200 - 0
lib/Test/Selenium/Remote/WebElement.pm

@@ -0,0 +1,200 @@
+package Test::Selenium::Remote::WebElement;
+use parent 'Selenium::Remote::WebElement';
+
+use Test::More;
+use Test::Builder;
+ 
+our $AUTOLOAD;
+
+our $Test = Test::Builder->new;
+$Test->exported_to(__PACKAGE__);
+
+our %comparator = (
+    is     => 'is_eq',
+    isnt   => 'isnt_eq',
+    like   => 'like',
+    unlike => 'unlike',
+);
+
+# These commands don't require a locator
+# grep item lib/WWW/Selenium.pm | grep sel | grep \(\) | grep get
+our %no_locator = map { $_ => 1 }
+                qw( send_keys speed alert confirmation prompt location title
+                    body_text all_buttons all_links all_fields
+                    mouse_speed all_window_ids all_window_names
+                    all_window_titles html_source cookie absolute_location );
+
+sub no_locator {
+    my $self   = shift;
+    my $method = shift;
+    return $no_locator{$method};
+}
+ 
+our %one_arg = map { $_ => 1 } qw(send_keys);
+
+sub one_arg {
+    my $self   = shift;
+    my $method = shift;
+    return $one_arg{$method};
+}
+
+our %no_arg = map { $_ => 1 } qw(click clear);
+
+sub no_arg {
+    my $self   = shift;
+    my $method = shift;
+    return $no_arg{$method};
+}
+
+our %no_return = map { $_ => 1 } qw(send_keys click clear);
+
+sub no_return {
+    my $self   = shift;
+    my $method = shift;
+    return $no_return{$method};
+}
+
+sub AUTOLOAD {
+    my $name = $AUTOLOAD;
+    $name =~ s/.*:://;
+    return if $name eq 'DESTROY';
+    my $self = $_[0];
+ 
+    my $sub;
+    if ($name =~ /(\w+)_(is|isnt|like|unlike)$/i) {
+        my $getter = $1;
+        my $comparator = $comparator{lc $2};
+ 
+        # make a subroutine that will call Test::Builder's test methods
+        # with selenium data from the getter
+        if ($self->no_locator($1)) {
+            $sub = sub {
+                my( $self, $str, $name ) = @_;
+                diag "Test::Selenium::Remote::Driver running $getter (@_[1..$#_])"
+                    if $self->{verbose};
+                $name = "$getter, '$str'"
+                    if $self->{default_names} and !defined $name;
+                no strict 'refs';
+                my $rc = $Test->$comparator( $self->$getter, $str, $name );
+                if (!$rc && $self->error_callback) {
+                    &{$self->error_callback}( $name, $self );
+                }
+                return $rc;
+            };
+        }
+        else {
+            $sub = sub {
+                my( $self, $locator, $str, $name ) = @_;
+                diag "Test::Selenium::Remote::Driver running $getter (@_[1..$#_])"
+                    if $self->{verbose};
+                $name = "$getter, $locator, '$str'"
+                    if $self->{default_names} and !defined $name;
+                no strict 'refs';
+                my $rc = $Test->$comparator( $self->$getter($locator), $str, $name );
+                if (!$rc && $self->error_callback) {
+                    &{$self->error_callback}( $name, $self );
+                }
+        return $rc;
+            };
+        }
+    }
+    elsif ($name =~ /(\w+?)_?ok$/i) {
+        my $cmd = $1;
+ 
+        # make a subroutine for ok() around the selenium command
+        $sub = sub {
+            my( $self, $arg1, $arg2, $name );
+            $self = $_[0];
+            if ($self->no_arg($cmd)) {
+                $name = $_[1];
+            }
+            elsif ($self->one_arg($cmd)) {
+                $arg1 = $_[1];
+                $name = $_[2];
+            }
+            else {
+                $arg1 = $_[1];
+                $arg2 = $_[2];
+                $name = $_[3];
+            }
+
+            if ($self->{default_names} and !defined $name) {
+                $name = $cmd;
+                $name .= ", $arg1" if defined $arg1;
+                $name .= ", $arg2" if defined $arg2;
+            }
+            diag "Test::Selenium::Remote::Driver running $cmd (@_[1..$#_])"
+                    if $self->{verbose};
+ 
+            local $Test::Builder::Level = $Test::Builder::Level + 1;
+            my $rc = '';
+            eval { 
+                if ($self->no_arg($cmd)) {
+                    $rc = $self->$cmd();
+                }
+                elsif ($self->one_arg($cmd)) {
+                    $rc = $self->$cmd( $arg1 );
+                }
+                else {
+                    $rc = $self->$cmd( $arg1, $arg2 );
+                }
+            };
+            die $@ if $@ and $@ =~ /Can't locate object method/;
+            diag($@) if $@;
+            if ($self->no_return($cmd)) {
+                $rc = ok( 1, "$name... no return value" );
+            }
+            else {
+                $rc = ok( $rc, $name );
+            }
+            if (!$rc && $self->error_callback) {
+                &{$self->error_callback}( $name, $self );
+            }
+            return $rc;
+        };
+    }
+ 
+    # jump directly to the new subroutine, avoiding an extra frame stack
+    if ($sub) {
+        no strict 'refs';
+        *{$AUTOLOAD} = $sub;
+        goto &$AUTOLOAD;
+    }
+    else {
+        # try to pass through to Selenium::Remote::Driver
+        my $sel = 'Selenium::Remote::Driver';
+        my $sub = "${sel}::${name}";
+        goto &$sub if exists &$sub;
+        my ($package, $filename, $line) = caller;
+        die qq(Can't locate object method "$name" via package ")
+            . __PACKAGE__
+            . qq(" (also tried "$sel") at $filename line $line\n);
+    }
+}
+
+sub error_callback {
+    my ($self, $cb) = @_;
+    if (defined($cb)) {
+        $self->{error_callback} = $cb;
+    }
+    return $self->{error_callback};
+}
+
+sub value_is_ok {
+    my $self = shift;
+    my $txt_compare = shift;
+    my $desc = shift;
+
+    return(is($self->get_value(), $txt_compare, $desc));
+}
+
+sub type_ok {
+    my $e = shift;
+    my $text = shift;
+    my $desc = shift;
+
+    $e->send_keys_ok($text, $desc);
+    $e->value_is_ok($text, $desc);
+}
+
+1;

+ 2 - 1
t/00-load.t

@@ -1,8 +1,9 @@
 use strict;
 
-use Test::More tests => 1;
+use Test::More tests => 2;
 
 BEGIN {
     use_ok( 'Selenium::Remote::Driver' ) || print "Bail out!";
+    use_ok( 'Test::Selenium::Remote::Driver' ) || print "Bail out!";
 }
 

+ 19 - 0
t/Test-Selenium-Remote-Driver-google.t

@@ -0,0 +1,19 @@
+#!/usr/bin/env perl
+use Test::More;
+use Test::Selenium::Remote::Driver;
+
+my ($host, $port) = Test::Selenium::Remote::Driver->server_is_running;
+unless ($host and $port) {
+    plan skip_all => "No Webdriver server found!";
+    exit 0;
+}
+
+# Try to find 
+my $t = Test::Selenium::Remote::Driver->new(
+    remote_server_addr => $host, port => $port,
+);
+$t->get_ok('http://www.google.com');
+$t->title_like(qr/Google/);
+$t->body_like(qr/Google/);
+
+done_testing();