package Test::WWW::Selenium::Catalyst;
use warnings;
use strict;
use Carp;
use Alien::SeleniumRC;
use Test::WWW::Selenium;
use Test::More;
use Catalyst::Utils;
use Catalyst::EngineLoader;
BEGIN { $ENV{CATALYST_ENGINE} ||= 'HTTP'; }
local $SIG{CHLD} = 'IGNORE';
our $DEBUG = $ENV{CATALYST_DEBUG};
our $app; # app name (MyApp)
our $sel_pid; # pid of selenium server
our $app_pid; # pid of myapp server
our $www_selenium;
=head1 NAME
Test::WWW::Selenium::Catalyst - Test your Catalyst application with Selenium
=cut
our $VERSION = '0.07';
=head1 DEVELOPERISH RELEASE
This is still a test release. It's working for me in production, but
it depends on a Java application (SeleniumRC), which can be
unreliable. On my Debian system, I had to put C<firefox-bin> in my
path, and add C</usr/lib/firefox> to C<LD_LIBRARY_PATH>. Every distro
and OS is different, so I'd like some feedback on how this works on
your system. I would like to find a clean solution that lets this
module "Just Work" for everyone, but I have a feeling that it's going
to look more like C<if(gentoo){ ... } elsif (debian) { ... }> and so
on. I can live with that, but I need your help to get to that stage!
Please report any problems to RT, the Catalyst mailing list, or the
#catalyst IRC channel on L<irc.perl.org>. Thanks!
=head1 SYNOPSIS
use Test::WWW::Selenium::Catalyst 'MyApp', 'command line to selenium';
use Test::More tests => 2;
my $sel = Test::WWW::Selenium::Catalyst->start;
$sel->open_ok('/');
$sel->is_text_present_ok('Welcome to MyApp');
This module starts the SeleniumRC server and your Catalyst app so that
you can test it with SeleniumRC. Once you've called
C<< Test::WWW::Selenium::Catalyst->start >>, everything is just like
L<Test::WWW::Selenium|Test::WWW:Selenium>.
=head1 METHODS
=head2 start(\%args)
Starts the Selenium and Catalyst servers, and returns a pre-initialized,
ready-to-use Test::WWW::Selenium object.
Arguments:
=over
=item app_uri
URI at which the application can be reached. If this is specified then no
application server will be started.
=item port
B<Default>: 3000
Port on which to run the catalyst application server. The C<MYAPP_PORT>
environment variable is also respected.
=item selenium_class
B<Default>: Test::WWW::Selenium
Classname of Selenium object to create. Use this if you want to subclass
selenium to add custom logic.
=item selenium_host
=item selenium_port
Location of externally running selenium server if you do not wish this module
to control one. See also for details.
=back
All other options passed verbatim to the selenium constructor.
B<NOTE>: By default a selenium server is started when you C<use> this module,
and it's killed when your test exits. If wish to manage a selenium server
yourself, (for instance you wish to start up a server once and run a number of
tests against it) pass C<-no_selenium_server> to import:
use Test::WWW::Selenium::Catalyst 'MyApp',
-no_selenium_server => 1
Along a similar vein you can also pass command line arguments to the selenium
server via C<-selenium_args>:
use Test::WWW::Selenium::Catalyst 'MyApp',
-selenium_args => "-singleWindow -port 4445"
=head2 sel_pid
Returns the process ID of the Selenium Server.
=head2 app_pid
Returns the process ID of the Catalyst server.
=cut
sub _start_server {
my ($class, $args) = @_;
# fork off a selenium server
my $pid;
if(0 == ($pid = fork())){
local $SIG{TERM} = sub {
diag("Selenium server $$ going down (TERM)") if $DEBUG;
exit 0;
};
chdir '/';
if(!$DEBUG){
close *STDERR;
close *STDOUT;
#close *STDIN;
}
diag("Selenium running in $$") if $DEBUG;
$class->_start_selenium($args);
diag("Selenium server $$ going down") if $DEBUG;
exit 1;
}
$sel_pid = $pid;
}
# Moved out to be subclassable seperately to the fork logic
sub _start_selenium {
my ($class, $arg) = @_;
$arg = '' unless defined $arg;
Alien::SeleniumRC::start($arg)
or croak "Can't start Selenium server";
}
sub sel_pid {
return $sel_pid;
}
sub app_pid {
return $app_pid;
}
sub import {
my ($class, $appname, %args) = @_;
croak q{Specify your app's name} if !$appname;
$app = $appname;
my $d = $ENV{Catalyst::Utils::class2env($appname). "_DEBUG"}; # MYAPP_DEBUG
if(defined $d){
$DEBUG = $d;
}
$args{-selenium_args} ||= '-singleWindow';
if ($ENV{SELENIUM_SERVER}) {
$args{-no_selenium_server} = 1;
}
elsif ($ENV{SELENIUM_PORT}) {
$args{-selenium_args} .= " -port " . $ENV{SELENIUM_PORT};
}
unless ($args{-no_selenium_server}) {
$class->_start_server($args{-selenium_args}) or croak "Couldn't start selenium server";
}
return 1;
}
sub start {
my $class = shift;
my $args = shift || {};
my $port = delete $args->{port};
$port ||= $ENV{Catalyst::Utils::class2env($app). "_PORT"} # MYAPP_PORT
|| 3000;
my $uri;
# Check for CATALYST_SERVER env var like TWMC does.
if ( $ENV{CATALYST_SERVER} ) {
$uri = $ENV{CATALYST_SERVER};
} elsif ( $args->{app_uri} ) {
$uri = delete $args->{app_uri}
} else {
# start a Catalyst MyApp server
eval("use $app");
croak "Couldn't load $app: $@" if $@;
my $pid;
if(0 == ($pid = fork())){
local $SIG{TERM} = sub {
diag("Catalyst server $$ going down (TERM)") if $DEBUG;
exit 0;
};
diag("Catalyst server running in pid $$ with port $port") if $DEBUG;
my $loader = Catalyst::EngineLoader->new(application_name => $app);
my $server = $loader->auto(port => $port, host => 'localhost',
server_ready => sub {
diag("Server started on port $port") if $DEBUG;
},
);
$app->run($port, 'localhost', $server);
diag("Process $$ (catalyst server) exiting.") if $DEBUG;
exit 1;
}
$uri = 'http://localhost:' . $port;
$app_pid = $pid;
}
my $tries = 5;
my $error;
my $sel_class = delete $args->{selenium_class} || 'Test::WWW::Selenium';
my $sel;
if ($ENV{SELENIUM_SERVER}) {
my $uri = $ENV{SELENIUM_SERVER};
$uri =~ s!^(?:http://)?!http://!;
$uri = new URI($uri);
$args->{selenium_host} = $uri->host;
$args->{selenium_port} = $uri->port;
}
elsif ($ENV{SELENIUM_PORT}) {
$args->{selenium_port} = $ENV{SELENIUM_PORT};
}
my $sel_host = delete $args->{selenium_host} || 'localhost';
my $sel_port = delete $args->{selenium_port} || 4444;
while(!$sel && $tries--){
sleep 1;
diag("Waiting for selenium server to start")
if $DEBUG;
eval {
$sel = $sel_class->new(
host => $sel_host,
port => $sel_port,
browser => '*firefox',
browser_url => $uri,
auto_stop => 0,
%$args
);
};
$error = $@;
}
croak "Can't start selenium: $error" if $error;
return $www_selenium = $sel;
}
END {
if($sel_pid){
if($www_selenium){
diag("Shutting down Selenium Server $sel_pid") if $DEBUG;
$www_selenium->stop();
# This can fail if a page hasn't been requested yet.
eval { $www_selenium->do_command('shutDownSeleniumServer') };
undef $www_selenium;
}
diag("Killing Selenium Server $sel_pid") if $DEBUG;
kill 15, $sel_pid or diag "Killing Selenium: $!";
undef $sel_pid;
} elsif ($www_selenium) {
diag("Using external Selenium server. Don't shut it down.") if $DEBUG;
undef $www_selenium;
}
if($app_pid){
diag("Killing catalyst server $app_pid") if $DEBUG;
kill 15, $app_pid or diag "Killing MyApp: $!";
undef $app_pid;
}
diag("Waiting for children to die") if $DEBUG;
waitpid $sel_pid, 0 if $sel_pid;
waitpid $app_pid, 0 if $app_pid;
}
=head1 ENVIRONMENT
Debugging messages are shown if C<CATALYST_DEBUG> or C<MYAPP_DEBUG>
are set. C<MYAPP> is the name of your application, uppercased. (This
is the same syntax as Catalyst itself.)
C<CATALYST_SERVER> can be set to test against an externally running server,
in a similar manner to how L<Test::WWW::Mechanize::Catalyst> behaves.
The port that the application sever runs on can be affected by C<MYAPP_PORT>
in addition to being specifiable in the arguments passed to start.
=head1 DIAGNOSTICS
=head2 Specify your app's name
You need to pass your Catalyst app's name as the argument to the use
statement:
use Test::WWW::Selenium::Catalyst 'MyApp'
C<MyApp> is the name of your Catalyst app.
=head1 SEE ALSO
=over 4
=item *
Selenium website: L<http://seleniumhq.org/>
=item *
Description of what you can do with the C<$sel> object: L<Test::WWW::Selenium>
and L<WWW::Selenium>
=item *
If you don't need a real web browser: L<Test::WWW::Mechanize::Catalyst>
=back
=head1 AUTHOR
Ash Berlin C<< <ash@cpan.org> >>
Jonathan Rockway, C<< <jrockway at cpan.org> >>
=head1 BUGS
Please report any bugs or feature requests to
C<bug-test-www-selenium-catalyst at rt.cpan.org>, or through the web interface at
L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Test-WWW-Selenium-Catalyst>.
I will be notified, and then you'll automatically be notified of progress on
your bug as I make changes.
=head1 PATCHES
Send me unified diffs against the git HEAD at:
git://github.com/jrockway/test-www-selenium-catalyst.git
You can view the repository online at
http://github.com/jrockway/test-www-selenium-catalyst/tree/master
Thanks in advance for your contributions!
=head1 ACKNOWLEDGEMENTS
Thanks for mst for getting on my (jrockway's) case to actually write this thing
:)
=head1 COPYRIGHT & LICENSE
Copyright 2009 Ash Berlin, all rights reserved.
Copyright 2006 Jonathan Rockway, all rights reserved.
This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.
=cut
1; # End of Test::WWW::Selenium::Catalyst