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;


local $SIG{CHLD} = 'IGNORE';

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


our $VERSION = '0.07';


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<>.  Thanks!


    use Test::WWW::Selenium::Catalyst 'MyApp', 'command line to selenium';
    use Test::More tests => 2;

    my $sel = Test::WWW::Selenium::Catalyst->start; 
    $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

=head1 METHODS

=head2 start(\%args)

Starts the Selenium and Catalyst servers, and returns a pre-initialized,
ready-to-use Test::WWW::Selenium object.



=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.


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.


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 '/';
            close *STDERR;
            close *STDOUT;
            #close *STDIN;
        diag("Selenium running in $$") if $DEBUG;
        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;
      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';

        $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;

        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,
        $error = $@;
    croak "Can't start selenium: $error" if $error;
    return $www_selenium = $sel;

            diag("Shutting down Selenium Server $sel_pid") if $DEBUG;
            # 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;

        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;


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.


=head2 Specify your app's name

You need to pass your Catalyst app's name as the argument to the use

    use Test::WWW::Selenium::Catalyst 'MyApp'

C<MyApp> is the name of your Catalyst app.

=head1 SEE ALSO

=over 4 

=item * 

Selenium website: L<>

=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>


=head1 AUTHOR

Ash Berlin C<< <> >>

Jonathan Rockway, C<< <jrockway at> >>

=head1 BUGS

Please report any bugs or feature requests to
C<bug-test-www-selenium-catalyst at>, or through the web interface at
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:


You can view the repository online at

Thanks in advance for your contributions!


Thanks for mst for getting on my (jrockway's) case to actually write this thing


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.


1; # End of Test::WWW::Selenium::Catalyst