kill dead code, update docs
[scpubgit/Test-Harness-Selenium.git] / lib / Test / Harness / Selenium.pm
1 package Test::Harness::Selenium;
2 use strictures 1;
3
4 use File::Find::Rule;
5 use Socialtext::WikiFixture::Selenese;
6 use HTML::TableExtract;
7 use IO::All;
8 use Alien::SeleniumRC;
9 use LWP::Simple;
10 use Child;
11
12 our $VERSION = '0.01';
13
14 use Test::More;
15 BEGIN {
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
35 sub new {
36   my $class = shift;
37   my %args = @_;
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';
46   my $self = \%args;
47   bless $self, $class;
48 }
49
50 sub start_selenium_server {
51   my($self) = @_;
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         )
74       }
75     )->start;
76     sleep 1;
77   }
78   my $tries = 0;
79   while($tries < 5) {
80     eval {
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.
84       $self->{src} = Socialtext::WikiFixture::Selenese->new(
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         )
91       );
92     };
93     $tries++;
94     if(!defined $self->{src}) {
95       sleep 10;
96     }
97     else {
98       last;
99     }
100   }
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;
105     die;
106   }
107 }
108
109 sub stop_selenium_server {
110   my($self) = @_;
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',
124         ":$self->{selenium_rc}{xvnc_display}");
125     })->start->wait;
126   }
127 }
128
129 sub start_app_server {
130   my($self) = @_;
131   return unless $self->{app_server_cmd};
132   my $child = Child->new(sub { exec($self->{app_server_cmd}) } );
133   $self->{app_server_proc} = $child->start;
134 }
135
136 sub stop_app_server {
137   my($self) = @_;
138   if (my $proc = $self->{app_server_proc}) {
139     $proc->kill("KILL");
140   }
141 }
142
143 sub test_directory {
144   my ($self, $dir) = @_;
145   $self->start_everything;
146   my @tests =
147     sort File::Find::Rule->file->name('*.html')->maxdepth(1)->in($dir);
148
149   for my $test (@tests) {
150     $self->test_file($test);
151   }
152 }
153
154 sub test_file {
155   my ($self, $html_file) = @_;
156   my $rows = $self->get_rows_for($html_file);
157   $self->{src}->run_test_table($rows);
158 }
159
160 # might as well keep this object around.
161 my $te = HTML::TableExtract->new;
162 sub get_rows_for {
163   my ($self, $html_file) = @_;
164   my $html = io($html_file)->all;
165   $te->parse($html);
166   my $table = ($te->tables)[0];
167   my @rows = map {
168     [ map { (!defined $_ or $_ eq "\240") ? () : $_ } @$_ ]
169   } grep { defined $_->[1] } $table->rows;
170   return \@rows;
171 }
172
173 sub done {
174   my($self) = @_;
175   $self->stop_selenium_server;
176   $self->stop_app_server;
177 }
178
179 sub DESTROY { shift->done }
180
181 1;
182
183 __END__
184
185 =head1 NAME
186
187 Test::Harness::Selenium - Test your app with Selenium
188
189 =head1 SYNOPSIS
190
191   # t/catapp.t
192   my $browser = shift;
193   my $s = Test::Harness::Selenium->new(
194       selenium_rc => {
195         host => '10.0.0.8',
196         port => $< + 6900,
197         start => 1,
198         xvnc_display => 1,
199       },
200       app_base => 'http://10.0.0.5:3000',
201       app_server_cmd => 'examples/THSelenium-Test/script/thselenium_test_server.pl',
202       browser => '*firefox',
203   );
204   # HTML tables as emitted by the Selenium IDE
205   eval { $s->test_directory('t/corpus/') };
206   $s->done;
207   done_testing;
208
209
210   # or, if you've got a selenium server already running (say, on a designer's
211   # Win32 box)
212   my $ths = Test::Harness::Selenium->new(
213     selenium_rc=> {
214       host => 'designers_machine',
215       port => 54321,
216       start => 0, # they've already got the RC running
217     },
218     browser => '*iexplore', # can't live with it, can't live without it
219     app_base => 'http://10.0.0.5:3000/',
220     app_server_cmd => 'script/myapp_server.pl -p 3000',
221   );
222   # otherwise the same
223   $ths->test_directory('t/selenium_corpus');
224
225 =head1 DESCRIPTION
226
227 C<Test::Harness::Selenium> provides an abstracted way of doing Web app testing
228 using the Selenium framework. It will connect to a running Selenium RC server,
229 or start one using ssh(1) to connect to a machine and then launching the RC
230 server and an xvnc(1) instance in which to run the given browser. After the
231 connection is established, Test::Harness::Selenium will read the specified HTML
232 files from disk and massage them into a format that
233 L<Socialtext::WikiFixture::Selenese> can parse and send to the Selenium RC
234 server. The RC server will then script the browser to do the actions described
235 in the HTML files. These actions return results, which Test::Harness::Selenium
236 then interprets as passing or failing as appropriate.
237
238 =head1 METHODS
239
240 =head2 new
241
242 =over 4
243
244 =item arguments: %attrs
245
246 =item Return value: new Test::Harness::Selenium object
247
248 =back
249
250 Constructor. Accepts a list of key/value pairs according to the following:
251
252 =over 4
253
254 =item selenium_rc
255
256 Hashref. Accepts the keys host, port, start, start_xvnc, xvnc_display. host and
257 port describe the server on which to start/find the Selenium RC server and
258 possibly the xvnc server. start is a Boolean indicating whether to start the
259 Selenium RC server, whereas start_xvnc dictates the same for the xvnc server.
260 xvnc_display is the X11 display to point browsers at when launching them.
261
262 =item browser
263
264 Scalar. The browser to use for testing the app. Must be in a form that Selenium
265 RC understands (e.g. '*firefox'); see the Selenium docs for more info.
266
267 =item app_base
268
269 Scalar. The URL, relative to the machine running Selenium RC, for the base of
270 the app. All requests made to the app are relative to this URL.
271
272 =item app_server_cmd
273
274 Scalar. This command will be run by start_app_server to run the app server to
275 test.
276
277 =back
278
279 =head2 test_directory
280
281 =item arguments: $dir
282
283 =item Return value: None
284
285 Object method. test_directory will use L<File::Find::Rule> to find all C<< .html >>
286 files in the given directory, and then massages them into data structures that
287 L<Socialtext::WikiFixture::Selenese> can send to the Selenium RC server.
288 test_directory will then output appropriate TAP according to whether the tests
289 and checks passed or failed, respectively.
290
291 =head2 test_file
292
293 =item arguments: $file
294
295 =item Return value: None
296
297 Object method. Runs the tests given in the specified file.
298
299 =head2 start_app_server, stop_app_server
300
301 =item Arguments: None
302
303 =item Return value: None
304
305 Object method. Start and stop the app server using the command given to the
306 constructor.  References the app_server_cmd key passed to the constructor.
307
308 =head2 start_selenium_server, start_selenium_server
309
310 =item Arguments: None
311
312 =item Return value: None
313
314 Start and stop the selenium / xvnc servers using the params given to the
315 constructor.
316
317 =head1 ENVIRONMENT
318
319 =head2 SELENIUM_RC_HOST, SELENIUM_RC_PORT, SELENIUM_RC_START,
320 SELENIUM_RC_START_XVNC, SELENIUM_RC_XVNC_DISPLAY
321
322 These values override the matching values in the selenium_rc hashref passed to
323 new.
324
325 =head1 AUTHOR
326
327 Chris Nehren <c.nehren/ths@shadowcat.co.uk>, Matt S. Trout <mst@shadowcat.co.uk>
328
329 =head1 CONTRIBUTORS
330
331 No one, yet. Patches most welcome! We most especially welcome doc patches to
332 make our code easier to use. We can write the best code in the world, but it
333 doesn't do anyone any good if it's impossible to use because of bad docs.
334
335 =head1 COPYRIGHT
336
337 Copyright (c) 2011 the Test::Harness::Selenium "AUTHOR" and
338 "CONTRIBUTORS" as listed above.
339
340 =head1 LICENSE
341
342 This library is free software and may be distributed under the same terms as
343 perl itself.