blow up after failing to start the selenium server - no point continuing
[scpubgit/Test-Harness-Selenium.git] / lib / Test / Harness / Selenium.pm
CommitLineData
676409e6 1package Test::Harness::Selenium;
2use strictures 1;
3
a5dd2803 4use File::Find::Rule;
3749d2e5 5use Socialtext::WikiFixture::Selenese;
676409e6 6use HTML::TableExtract;
7use IO::All;
a5dd2803 8use Alien::SeleniumRC;
9use LWP::Simple;
69f9b61a 10use Child;
676409e6 11
525368ed 12our $VERSION = '0.01';
13
799fcc50 14use Test::More;
676409e6 15BEGIN {
16 package Test::Builder;
17
18 use Class::Method::Modifiers;
19 use ExtUtils::MakeMaker qw(prompt);
20
21 if (!$ENV{AUTOMATED_TESTING}) {
22 around ok => sub {
23 my ($orig, $self) = (shift, shift);
24 my $res = $self->$orig(@_);
25 unless ($res) {
26 if ('y' eq prompt "Well that didn't work, did it. Bail out?", 'y') {
27 exit 255;
28 }
29 }
30 return $res;
31 };
32 }
33}
34
3749d2e5 35sub new {
69f9b61a 36 my $class = shift;
37 my %args = @_;
8b643e0b 38 my $selrc = ($args{selenium_rc} ||={});
39 $selrc->{$_} = $ENV{"SELENIUM_RC_${\uc $_}"}
40 for grep exists $ENV{"SELENIUM_RC_${\uc $_}"},
41 qw(host port start start_xvnc xvnc_display);
42 $selrc->{xvnc_display} ||= '0';
43 $selrc->{host} ||= 'localhost';
44 $selrc->{port} ||= 4444;
45 $args{browser} ||= '*firefox';
69f9b61a 46 my $self = \%args;
3749d2e5 47 bless $self, $class;
48}
49
69f9b61a 50sub start_selenium_server {
a5dd2803 51 my($self) = @_;
b05fc143 52 my $selrc = $self->{selenium_rc};
53 if($selrc->{start}) {
54 my ($host, $display, $port) = @{$selrc}{qw(host xvnc_display port)};
55 my @do_ssh = $host eq 'localhost' ? () : ('ssh', $host);
56 if ($selrc->{start_xvnc}) {
57 $selrc->{xvnc_server_proc} = Child->new(
58 sub {
59 exec(
60 @do_ssh,
61 'vncserver', ":${display}",
62 );
63 }
64 )->start;
65 $selrc->{xvnc_started} = 1;
66 sleep 3;
67 }
68 $selrc->{selenium_server_proc} = Child->new(
69 sub {
70 exec(
71 @do_ssh,
72 'env', "DISPLAY=:${display}", 'selenium-rc', '-port', $port
73 )
a5dd2803 74 }
b05fc143 75 )->start;
76 sleep 1;
a5dd2803 77 }
69f9b61a 78 my $tries = 0;
79 while($tries < 5) {
80 eval {
1a6bcc32 81 # if we don't create the ::Selenium object ourselves, then
82 # wikifixture shuts the session down after the first test table
83 # is run, at which point KABOOM when you try and run a second one.
69f9b61a 84 $self->{src} = Socialtext::WikiFixture::Selenese->new(
1a6bcc32 85 selenium => Test::WWW::Selenium->new(
86 host => $self->{selenium_rc}{host},
87 port => $self->{selenium_rc}{port},
88 browser => $self->{browser},
89 browser_url => $self->{app_base},
90 )
69f9b61a 91 );
92 };
93 $tries++;
94 if(!defined $self->{src}) {
95 sleep 10;
96 }
97 else {
98 last;
99 }
a5dd2803 100 }
e5f319fc 101 if($tries == 5) {
102 diag "timed out waiting for selenium server to start at
103 http://$self->{selenium_rc}{host}:$self->{selenium_rc}{port}" if $tries == 5;
104 $self->done;
91053783 105 die;
e5f319fc 106 }
a5dd2803 107}
108
69f9b61a 109sub stop_selenium_server {
a5dd2803 110 my($self) = @_;
b05fc143 111 if (my $proc = delete $self->{selenium_rc}{selenium_server_proc}) {
112 my $url = sprintf
113 "http://%s:%s/selenium-server/driver/?cmd=shutDownSeleniumServer",
114 $self->{selenium_rc}{host}, $self->{selenium_rc}{port};
115 eval { get($url); }; # will fail if it never started
116 delete $self->{src};
117 $proc->kill("KILL");
118 }
119 if (delete $self->{selenium_rc}{xvnc_started}) {
120 my $host = $self->{selenium_rc}{host};
121 my @do_ssh = $host eq 'localhost' ? () : ('ssh', $host);
122 Child->new(sub {
123 exec(@do_ssh, 'vncserver', '-kill',
e5f319fc 124 ":$self->{selenium_rc}{xvnc_display}");
b05fc143 125 })->start->wait;
126 }
69f9b61a 127}
128
129sub start_app_server {
130 my($self) = @_;
e5f319fc 131 my $child = Child->new(sub { exec($self->{app_server_cmd}) } );
132 $self->{app_server_proc} = $child->start;
69f9b61a 133}
134
135sub stop_app_server {
136 my($self) = @_;
e5f319fc 137 $self->{app_server_proc}->kill("KILL");
a5dd2803 138}
139
676409e6 140sub test_directory {
3749d2e5 141 my ($self, $dir) = @_;
0f44efef 142 if(!exists $self->{app_server_proc}) {
143 $self->start_app_server;
144 }
8ecad210 145 if($self->{selenium_rc}{start} && !$self->{selenium_rc}{selenium_server_proc}) {
146 $self->start_selenium_server;
147 }
3749d2e5 148 my @tests = File::Find::Rule->file()->name('*.html')->in($dir);
a5dd2803 149 for my $test (@tests) {
a5dd2803 150 $self->run_tests_for($test);
a5dd2803 151 }
676409e6 152}
153
154sub run_tests_for {
155 my ($self, $html_file) = @_;
156 my $rows = $self->get_rows_for($html_file);
17ccdf07 157 $self->{src}->run_test_table($rows);
676409e6 158}
159
160my $te = HTML::TableExtract->new;
161sub get_rows_for {
162 my ($self, $html_file) = @_;
163 my $html = io($html_file)->all;
164 $te->parse($html);
165 my $table = ($te->tables)[0];
166 my @rows = map {
799fcc50 167 [ map { (!defined $_ or $_ eq "\240") ? () : $_ } @$_ ]
168 } grep { defined $_->[1] } $table->rows;
676409e6 169 return \@rows;
170}
171
e5f319fc 172sub done {
7522d6aa 173 my($self) = @_;
fbd64e21 174 if(exists $self->{selenium_rc}{xvnc_server_proc} and
175 exists $self->{selenium_rc}{selenium_server_proc}) {
e5f319fc 176 $self->stop_selenium_server;
7522d6aa 177 }
8ecad210 178 $self->stop_app_server;
7522d6aa 179}
180
676409e6 1811;
afe57b05 182
183__END__
184
185=head1 NAME
186
187Test::Harness::Selenium - Test your app with Selenium
188
189=head1 SYNOPSIS
190
191 # t/selenium.t
192 my $ths = Test::Harness::Selenium->new(
193 selenium_rc => {
194 host => 'selenium_xvnc_server',
195 port => 12345,
196 start => 1, # start xvnc and selenium RC via ssh(1)
197 },
198 browser => '*firefox',
199 # app_base is relative from the machine running selenium_rc
200 app_base => 'http://10.0.0.5:3000/',
201 app_start_cmd => 'script/myapp_server.pl -p 3000',
202 );
203 # HTML tables as emitted by the Selenium IDE
204 $ths->test_directory('t/selenium_corpus');
205
206
207 # or, if you've got a selenium server already running (say, on a designer's
208 # Win32 box)
209 my $ths = Test::Harness::Selenium->new(
210 selenium_rc=> {
211 host => 'designers_machine',
212 port => 54321,
213 start => 0, # they've already got the RC running
214 },
215 browser => '*iexplore', # can't live with it, can't live without it
216 app_base => 'http://10.0.0.5:3000/',
217 app_start_cmd => 'script/myapp_server.pl -p 3000',
218 );
219 # otherwise the same
220 $ths->test_directory('t/selenium_corpus');
221
222=head1 DESCRIPTION
223
224C<Test::Harness::Selenium> provides an abstracted way of doing Web app testing
225using the Selenium framework. It will connect to a running Selenium RC server,
226or start one using ssh(1) to connect to a machine and then launching the RC
227server and an xvnc(1) instance in which to run the given browser. After the
228connection is established, Test::Harness::Selenium will read the specified HTML
229files from disk and massage them into a format that
230L<Socialtext::WikiFixture::Selenese> can parse and send to the Selenium RC
231server. The RC server will then script the browser to do the actions described
232in the HTML files. These actions return results, which Test::Harness::Selenium
233then interprets as passing or failing as appropriate.
234
235=head1 METHODS
236
237=head2 new
238
239=over 4
240
241=item arguments: %attrs
242
243=item Return value: new Test::Harness::Selenium object
244
245=back
246
247Constructor. Accepts a list of key/value pairs according to the following:
248
249=over 4
250
251=item selenium_rc
252
253Hashref. Accepts the keys host, port, start. host and port describe the server
254on which to start/find the Selenium RC server and possibly the xvnc server.
255start is a Boolean indicating whether to start the Selenium RC server and xvnc
256server.
257
258=item browser
259
260Scalar. The browser to use for testing the app. Must be in a form that Selenium
261RC understands (e.g. '*firefox'); see the Selenium docs for more info.
262
263=item app_base
264
265Scalar. The URL, relative to the machine running Selenium RC, for the base of
266the app. All requests made to the app are relative to this URL.
267
268=item app_start_cmd
269
270Scalar. This command will be run by start_app_server to run the app server to
271test.
272
273=back
274
275=head2 test_directory
276
277=item arguments: $dir
278
279=item Return value: None
280
281Object method. test_directory will use L<File::Find::Rule> to find all C<< .html >>
282files in the given directory, and then formats massages them into data
283structures that L<Socialtext::WikiFixture::Selenese> can send to the Selenium RC
284server. test_directory will then output appropriate TAP according to whether the
285tests and checks passed or failed, respectively.
286
287=head2 run_tests_for
288
289=item arguments: $html_file
290
291=item Return value: None
292
293run_tests_for is called by test_directory for each C<< .html >> file it finds in
294the given directory.
295
296=head2 start_app_server, start_app_server
297
298=item Arguments: None
299
300=item Return value: None
301
302Start and stop the app server using the command given to the constructor.
303
304=head2 start_selenium_server, start_selenium_server
305
306=item Arguments: None
307
308=item Return value: None
309
310Start and stop the selenium / xvnc servers using the params given to the
311constructor.
312
313=head1 ENVIRONMENT
314
315=head2 SELENIUM_RC_HOST, SELENIUM_RC_PORT, SELENIUM_RC_START
316
317These values override the matching values in the selenium_rc hashref passed to
318new.
319
320=head1 AUTHOR
321
322Chris Nehren <c.nehren/ths@shadowcat.co.uk>, Matt S. Trout <mst@shadowcat.co.uk>
323
324=head1 CONTRIBUTORS
325
326No one, yet. Patches most welcome!
327
328=head1 COPYRIGHT
329
330Copyright (c) 2011 the Test::Harness::Selenium "AUTHOR" and
331"CONTRIBUTORS" as listed above.
332
333=head1 LICENSE
334
335This library is free software and may be distributed under the same terms as
336perl itself.