Fix documentation errors. RT#64979
[catagits/Test-WWW-Selenium-Catalyst.git] / lib / Test / WWW / Selenium / Catalyst.pm
1 package Test::WWW::Selenium::Catalyst;
2
3 use warnings;
4 use strict;
5 use Carp;
6 use Alien::SeleniumRC;
7 use Test::WWW::Selenium;
8 use Test::More;
9 use Catalyst::Utils;
10 use Catalyst::EngineLoader;
11
12 BEGIN { $ENV{CATALYST_ENGINE} ||= 'HTTP'; }
13
14 local $SIG{CHLD} = 'IGNORE';
15
16 our $DEBUG = $ENV{CATALYST_DEBUG};
17 our $app; # app name (MyApp)
18 our $sel_pid; # pid of selenium server
19 our $app_pid; # pid of myapp server
20 our $www_selenium;
21
22 =head1 NAME
23
24 Test::WWW::Selenium::Catalyst - Test your Catalyst application with Selenium
25
26 =cut
27
28 our $VERSION = '0.06';
29
30 =head1 DEVELOPERISH RELEASE
31
32 This is still a test release.  It's working for me in production, but
33 it depends on a Java application (SeleniumRC), which can be
34 unreliable.  On my Debian system, I had to put C<firefox-bin> in my
35 path, and add C</usr/lib/firefox> to C<LD_LIBRARY_PATH>.  Every distro
36 and OS is different, so I'd like some feedback on how this works on
37 your system.  I would like to find a clean solution that lets this
38 module "Just Work" for everyone, but I have a feeling that it's going
39 to look more like C<if(gentoo){ ... } elsif (debian) { ... }> and so
40 on.  I can live with that, but I need your help to get to that stage!
41
42 Please report any problems to RT, the Catalyst mailing list, or the
43 #catalyst IRC channel on L<irc.perl.org>.  Thanks!
44
45 =head1 SYNOPSIS
46
47     use Test::WWW::Selenium::Catalyst 'MyApp', 'command line to selenium';
48     use Test::More tests => 2;
49
50     my $sel = Test::WWW::Selenium::Catalyst->start; 
51     $sel->open_ok('/');
52     $sel->is_text_present_ok('Welcome to MyApp');
53
54 This module starts the SeleniumRC server and your Catalyst app so that
55 you can test it with SeleniumRC.  Once you've called
56 C<< Test::WWW::Selenium::Catalyst->start >>, everything is just like
57 L<Test::WWW::Selenium|Test::WWW:Selenium>.
58
59 =head1 METHODS
60
61 =head2 start(\%args)
62
63 Starts the Selenium and Catalyst servers, and returns a pre-initialized,
64 ready-to-use Test::WWW::Selenium object.
65
66 Arguments:
67
68 =over
69
70 =item app_uri
71
72 URI at which the application can be reached. If this is specified then no
73 application server will be started.
74
75 =item port
76
77 B<Default>: 3000
78
79 Port on which to run the catalyst application server. The C<MYAPP_PORT>
80 environment variable is also respected.
81
82
83 =item selenium_class
84
85 B<Default>: Test::WWW::Selenium
86
87 Classname of Selenium object to create. Use this if you want to subclass
88 selenium to add custom logic.
89
90 =item selenium_host
91
92 =item selenium_port
93
94 Location of externally running selenium server if you do not wish this module
95 to control one. See also for details.
96
97 =back
98
99 All other options passed verbatim to the selenium constructor.
100
101 B<NOTE>: By default a selenium server is started when you C<use> this module,
102 and it's killed when your test exits. If wish to manage a selenium server
103 yourself, (for instance you wish to start up a server once and run a number of
104 tests against it) pass C<-no_selenium_server> to import:
105
106  use Test::WWW::Selenium::Catalyst 'MyApp',
107    -no_selenium_server => 1
108
109 Along a similar vein you can also pass command line arguments to the selenium
110 server via C<-selenium_args>:
111
112  use Test::WWW::Selenium::Catalyst 'MyApp',
113    -selenium_args => "-singleWindow -port 4445"
114
115 =head2 sel_pid
116
117 Returns the process ID of the Selenium Server.
118
119 =head2 app_pid
120
121 Returns the process ID of the Catalyst server.
122
123 =cut
124
125
126 sub _start_server {
127     my ($class, $args) = @_;
128     # fork off a selenium server
129     my $pid;
130     if(0 == ($pid = fork())){
131         local $SIG{TERM} = sub {
132             diag("Selenium server $$ going down (TERM)") if $DEBUG;
133             exit 0;
134         };
135         
136         chdir '/';
137         
138         if(!$DEBUG){
139             close *STDERR;
140             close *STDOUT;
141             #close *STDIN;
142         }
143         
144         diag("Selenium running in $$") if $DEBUG;
145         $class->_start_selenium($args);
146         diag("Selenium server $$ going down") if $DEBUG;
147         exit 1;
148     }
149     $sel_pid = $pid;
150 }
151
152 # Moved out to be subclassable seperately to the fork logic
153 sub _start_selenium {
154     my ($class, $arg) = @_;
155     $arg = '' unless defined $arg;
156     Alien::SeleniumRC::start($arg)
157       or croak "Can't start Selenium server";
158 }
159
160 sub sel_pid {
161     return $sel_pid;
162 }
163
164 sub app_pid {
165     return $app_pid;
166 }
167
168 sub import {
169     my ($class, $appname, %args) = @_;
170
171     croak q{Specify your app's name} if !$appname;
172     $app = $appname;
173     
174     my $d = $ENV{Catalyst::Utils::class2env($appname). "_DEBUG"}; # MYAPP_DEBUG 
175     if(defined $d){
176         $DEBUG = $d;
177     }
178
179     $args{-selenium_args} ||= '-singleWindow';
180
181     if ($ENV{SELENIUM_SERVER}) {
182         $args{-no_selenium_server} = 1;
183     }
184     elsif ($ENV{SELENIUM_PORT}) {
185         $args{-selenium_args} .= " -port " . $ENV{SELENIUM_PORT};
186     }
187    
188     unless ($args{-no_selenium_server}) {
189       $class->_start_server($args{-selenium_args}) or croak "Couldn't start selenium server";
190     }
191     return 1;
192 }
193
194 sub start {
195     my $class = shift;
196     my $args  = shift || {};
197
198     my $port = delete $args->{port};
199     $port ||= $ENV{Catalyst::Utils::class2env($app). "_PORT"} # MYAPP_PORT
200           ||  3000;
201  
202     my $uri;
203
204     # Check for CATALYST_SERVER env var like TWMC does.
205     if ( $ENV{CATALYST_SERVER} ) {
206       $uri = $ENV{CATALYST_SERVER};
207     } elsif ( $args->{app_uri} ) {
208       $uri = delete $args->{app_uri}
209     } else {
210       # start a Catalyst MyApp server
211       eval("use $app");
212       croak "Couldn't load $app: $@" if $@;
213       
214       my $pid;
215       if(0 == ($pid = fork())){
216           local $SIG{TERM} = sub {
217               diag("Catalyst server $$ going down (TERM)") if $DEBUG;
218               exit 0;
219           };
220           diag("Catalyst server running in pid $$ with port $port") if $DEBUG;
221           my $loader = Catalyst::EngineLoader->new(application_name => $app);
222           my $server = $loader->auto(port => $port, host => 'localhost',
223               server_ready => sub {
224                   diag("Server started on port $port") if $DEBUG;
225               },
226           );
227           $app->run($port, 'localhost', $server);
228
229           diag("Process $$ (catalyst server) exiting.") if $DEBUG;
230           exit 1;
231       }
232       $uri = 'http://localhost:' . $port;
233       $app_pid = $pid;
234     }
235     
236     my $tries = 5;
237     my $error;
238     my $sel_class = delete $args->{selenium_class} || 'Test::WWW::Selenium';
239     my $sel;
240
241     if ($ENV{SELENIUM_SERVER}) {
242         my $uri = $ENV{SELENIUM_SERVER};
243         $uri =~ s!^(?:http://)?!http://!;
244         $uri = new URI($uri);
245         $args->{selenium_host} = $uri->host;
246         $args->{selenium_port} = $uri->port;
247     }
248     elsif ($ENV{SELENIUM_PORT}) {
249         $args->{selenium_port} = $ENV{SELENIUM_PORT};
250     }
251
252     my $sel_host = delete $args->{selenium_host} || 'localhost';
253     my $sel_port = delete $args->{selenium_port} || 4444;
254     while(!$sel && $tries--){ 
255         sleep 1;
256         diag("Waiting for selenium server to start")
257           if $DEBUG;
258         
259         eval {
260             $sel = $sel_class->new(
261                 host => $sel_host,
262                 port => $sel_port,
263                 browser => '*firefox',
264                 browser_url => $uri,
265                 auto_stop => 0,
266                 %$args
267             );
268         };
269         $error = $@;
270     }
271     croak "Can't start selenium: $error" if $error;
272     
273     return $www_selenium = $sel;
274 }
275
276 END {
277     if($sel_pid){
278         if($www_selenium){
279             diag("Shutting down Selenium Server $sel_pid") if $DEBUG;
280             $www_selenium->stop();
281             # This can fail if a page hasn't been requested yet.
282             eval { $www_selenium->do_command('shutDownSeleniumServer') };
283             undef $www_selenium;
284         }
285         diag("Killing Selenium Server $sel_pid") if $DEBUG;
286         kill 15, $sel_pid or diag "Killing Selenium: $!";
287         undef $sel_pid;
288
289     } elsif ($www_selenium) {
290         diag("Using external Selenium server. Don't shut it down.") if $DEBUG;
291         undef $www_selenium;
292     }
293
294     if($app_pid){
295         diag("Killing catalyst server $app_pid") if $DEBUG;
296         kill 15, $app_pid or diag "Killing MyApp: $!";
297         undef $app_pid;
298     }
299     diag("Waiting for children to die") if $DEBUG;
300     waitpid $sel_pid, 0 if $sel_pid;
301     waitpid $app_pid, 0 if $app_pid;
302 }
303
304
305 =head1 ENVIRONMENT
306
307 Debugging messages are shown if C<CATALYST_DEBUG> or C<MYAPP_DEBUG>
308 are set.  C<MYAPP> is the name of your application, uppercased.  (This
309 is the same syntax as Catalyst itself.)
310
311 C<CATALYST_SERVER> can be set to test against an externally running server,
312 in a similar manner to how L<Test::WWW::Mechanize::Catalyst> behaves.
313
314 The port that the application sever runs on can be affected by C<MYAPP_PORT>
315 in addition to being specifiable in the arguments passed to start.
316
317 =head1 DIAGNOSTICS
318
319 =head2 Specify your app's name
320
321 You need to pass your Catalyst app's name as the argument to the use
322 statement:
323
324     use Test::WWW::Selenium::Catalyst 'MyApp'
325
326 C<MyApp> is the name of your Catalyst app.
327
328 =head1 SEE ALSO
329
330 =over 4 
331
332 =item * 
333
334 Selenium website: L<http://seleniumhq.org/>
335
336 =item * 
337
338 Description of what you can do with the C<$sel> object: L<Test::WWW::Selenium>
339 and L<WWW::Selenium>
340
341 =item * 
342
343 If you don't need a real web browser: L<Test::WWW::Mechanize::Catalyst>
344
345 =back
346
347 =head1 AUTHOR
348
349 Ash Berlin C<< <ash@cpan.org> >>
350
351 Jonathan Rockway, C<< <jrockway at cpan.org> >>
352
353 =head1 BUGS
354
355 Please report any bugs or feature requests to
356 C<bug-test-www-selenium-catalyst at rt.cpan.org>, or through the web interface at
357 L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Test-WWW-Selenium-Catalyst>.
358 I will be notified, and then you'll automatically be notified of progress on
359 your bug as I make changes.
360
361 =head1 PATCHES
362
363 Send me unified diffs against the git HEAD at:
364
365     git://github.com/jrockway/test-www-selenium-catalyst.git
366
367 You can view the repository online at 
368
369     http://github.com/jrockway/test-www-selenium-catalyst/tree/master
370
371 Thanks in advance for your contributions!
372
373 =head1 ACKNOWLEDGEMENTS
374
375 Thanks for mst for getting on my (jrockway's) case to actually write this thing
376 :)
377
378 =head1 COPYRIGHT & LICENSE
379
380 Copyright 2009 Ash Berlin, all rights reserved.
381
382 Copyright 2006 Jonathan Rockway, all rights reserved.
383
384 This program is free software; you can redistribute it and/or modify it
385 under the same terms as Perl itself.
386
387 =cut
388
389 1; # End of Test::WWW::Selenium::Catalyst