kill dead code, update docs
[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) = @_;
5a456070 131 return unless $self->{app_server_cmd};
e5f319fc 132 my $child = Child->new(sub { exec($self->{app_server_cmd}) } );
133 $self->{app_server_proc} = $child->start;
69f9b61a 134}
135
136sub stop_app_server {
137 my($self) = @_;
5a456070 138 if (my $proc = $self->{app_server_proc}) {
139 $proc->kill("KILL");
140 }
a5dd2803 141}
142
ccb22fec 143sub 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
a5dd2803 149 for my $test (@tests) {
ccb22fec 150 $self->test_file($test);
a5dd2803 151 }
676409e6 152}
153
ccb22fec 154sub test_file {
676409e6 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
84b9e9a5 160# might as well keep this object around.
676409e6 161my $te = HTML::TableExtract->new;
162sub 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 {
799fcc50 168 [ map { (!defined $_ or $_ eq "\240") ? () : $_ } @$_ ]
169 } grep { defined $_->[1] } $table->rows;
676409e6 170 return \@rows;
171}
172
e5f319fc 173sub done {
7522d6aa 174 my($self) = @_;
5a456070 175 $self->stop_selenium_server;
8ecad210 176 $self->stop_app_server;
7522d6aa 177}
178
5a456070 179sub DESTROY { shift->done }
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
84b9e9a5 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',
afe57b05 203 );
204 # HTML tables as emitted by the Selenium IDE
84b9e9a5 205 eval { $s->test_directory('t/corpus/') };
206 $s->done;
207 done_testing;
afe57b05 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/',
84b9e9a5 220 app_server_cmd => 'script/myapp_server.pl -p 3000',
afe57b05 221 );
222 # otherwise the same
223 $ths->test_directory('t/selenium_corpus');
224
225=head1 DESCRIPTION
226
227C<Test::Harness::Selenium> provides an abstracted way of doing Web app testing
228using the Selenium framework. It will connect to a running Selenium RC server,
229or start one using ssh(1) to connect to a machine and then launching the RC
230server and an xvnc(1) instance in which to run the given browser. After the
231connection is established, Test::Harness::Selenium will read the specified HTML
232files from disk and massage them into a format that
233L<Socialtext::WikiFixture::Selenese> can parse and send to the Selenium RC
234server. The RC server will then script the browser to do the actions described
235in the HTML files. These actions return results, which Test::Harness::Selenium
236then 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
250Constructor. Accepts a list of key/value pairs according to the following:
251
252=over 4
253
254=item selenium_rc
255
84b9e9a5 256Hashref. Accepts the keys host, port, start, start_xvnc, xvnc_display. host and
257port describe the server on which to start/find the Selenium RC server and
258possibly the xvnc server. start is a Boolean indicating whether to start the
259Selenium RC server, whereas start_xvnc dictates the same for the xvnc server.
260xvnc_display is the X11 display to point browsers at when launching them.
afe57b05 261
262=item browser
263
264Scalar. The browser to use for testing the app. Must be in a form that Selenium
265RC understands (e.g. '*firefox'); see the Selenium docs for more info.
266
267=item app_base
268
269Scalar. The URL, relative to the machine running Selenium RC, for the base of
270the app. All requests made to the app are relative to this URL.
271
84b9e9a5 272=item app_server_cmd
afe57b05 273
274Scalar. This command will be run by start_app_server to run the app server to
275test.
276
277=back
278
279=head2 test_directory
280
281=item arguments: $dir
282
283=item Return value: None
284
285Object method. test_directory will use L<File::Find::Rule> to find all C<< .html >>
84b9e9a5 286files in the given directory, and then massages them into data structures that
287L<Socialtext::WikiFixture::Selenese> can send to the Selenium RC server.
288test_directory will then output appropriate TAP according to whether the tests
289and checks passed or failed, respectively.
afe57b05 290
84b9e9a5 291=head2 test_file
afe57b05 292
84b9e9a5 293=item arguments: $file
afe57b05 294
295=item Return value: None
296
84b9e9a5 297Object method. Runs the tests given in the specified file.
afe57b05 298
84b9e9a5 299=head2 start_app_server, stop_app_server
afe57b05 300
301=item Arguments: None
302
303=item Return value: None
304
84b9e9a5 305Object method. Start and stop the app server using the command given to the
306constructor. References the app_server_cmd key passed to the constructor.
afe57b05 307
308=head2 start_selenium_server, start_selenium_server
309
310=item Arguments: None
311
312=item Return value: None
313
314Start and stop the selenium / xvnc servers using the params given to the
315constructor.
316
317=head1 ENVIRONMENT
318
84b9e9a5 319=head2 SELENIUM_RC_HOST, SELENIUM_RC_PORT, SELENIUM_RC_START,
320SELENIUM_RC_START_XVNC, SELENIUM_RC_XVNC_DISPLAY
afe57b05 321
322These values override the matching values in the selenium_rc hashref passed to
323new.
324
325=head1 AUTHOR
326
327Chris Nehren <c.nehren/ths@shadowcat.co.uk>, Matt S. Trout <mst@shadowcat.co.uk>
328
329=head1 CONTRIBUTORS
330
84b9e9a5 331No one, yet. Patches most welcome! We most especially welcome doc patches to
332make our code easier to use. We can write the best code in the world, but it
333doesn't do anyone any good if it's impossible to use because of bad docs.
afe57b05 334
335=head1 COPYRIGHT
336
337Copyright (c) 2011 the Test::Harness::Selenium "AUTHOR" and
338"CONTRIBUTORS" as listed above.
339
340=head1 LICENSE
341
342This library is free software and may be distributed under the same terms as
343perl itself.