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