Browse Source

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

Mark Stosberg 12 years ago
parent
commit
273cfe594d

+ 43 - 2
Changes

@@ -1,4 +1,5 @@
 Revision history for Selenium-Remote-Driver
 Revision history for Selenium-Remote-Driver
+
 0.18   7-26-2013
 0.18   7-26-2013
         [THINGS THAT MIGHT BREAK YOUR CODE]
         [THINGS THAT MIGHT BREAK YOUR CODE]
         - We now 'croak' instead of returning error strings in a number of
         - 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
           https://github.com/aivaturi/Selenium-Remote-Driver/pull/77
 
 
         [NEW FEATURES]
         [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
         - 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
           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.
         - 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.  
           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_text() Returns the text for a particular element.
           get_body() Returns the text for the whole body.
           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).
         - 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.  
           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]
         [DOCUMENTATION]
         - Updated ChangeLog to reflect that debug_on() and debug_off() methods were added in 0.16.
         - 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.
            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
            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
 0.17   7-13-2013
         - Second build with DistZilla, now with distzilla branch merged into master.
         - Second build with DistZilla, now with distzilla branch merged into master.

+ 4 - 0
dist.ini

@@ -1,6 +1,8 @@
 name = Selenium-Remote-Driver
 name = Selenium-Remote-Driver
 version = 0.18
 version = 0.18
 author = Aditya Ivaturi <ivaturi@gmail.com>
 author = Aditya Ivaturi <ivaturi@gmail.com>
+author = Luke Closs <cpan@5thplane.com>
+author = Mark Stosberg <mark@stosberg.com>
 license = Apache_2_0
 license = Apache_2_0
 
 
 copyright_holder = Aditya Ivaturi
 copyright_holder = Aditya Ivaturi
@@ -28,6 +30,7 @@ copyright_holder = Aditya Ivaturi
 [Prereqs / BuildRequires]
 [Prereqs / BuildRequires]
 LWP::Protocol::PSGI = 0
 LWP::Protocol::PSGI = 0
 Test::More = 0
 Test::More = 0
+Test::Builder = 0
 
 
 [Prereqs / RuntimeRequires]
 [Prereqs / RuntimeRequires]
 perl = 5.010
 perl = 5.010
@@ -39,6 +42,7 @@ JSON = 0
 LWP::UserAgent = 0
 LWP::UserAgent = 0
 Net::Ping = 0
 Net::Ping = 0
 MIME::Base64 = 0
 MIME::Base64 = 0
+IO::Socket = 0
 
 
 [Prereqs / TestRequires]
 [Prereqs / TestRequires]
 Carp = 1.25
 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 strict;
 
 
-use Test::More tests => 1;
+use Test::More tests => 2;
 
 
 BEGIN {
 BEGIN {
     use_ok( 'Selenium::Remote::Driver' ) || print "Bail out!";
     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();