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