construct the Test::WWW::Selenium object ourselves to prevent Socialtext::WikiFixture...
[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;
105 }
a5dd2803 106}
107
69f9b61a 108sub stop_selenium_server {
a5dd2803 109 my($self) = @_;
b05fc143 110 if (my $proc = delete $self->{selenium_rc}{selenium_server_proc}) {
111 my $url = sprintf
112 "http://%s:%s/selenium-server/driver/?cmd=shutDownSeleniumServer",
113 $self->{selenium_rc}{host}, $self->{selenium_rc}{port};
114 eval { get($url); }; # will fail if it never started
115 delete $self->{src};
116 $proc->kill("KILL");
117 }
118 if (delete $self->{selenium_rc}{xvnc_started}) {
119 my $host = $self->{selenium_rc}{host};
120 my @do_ssh = $host eq 'localhost' ? () : ('ssh', $host);
121 Child->new(sub {
122 exec(@do_ssh, 'vncserver', '-kill',
e5f319fc 123 ":$self->{selenium_rc}{xvnc_display}");
b05fc143 124 })->start->wait;
125 }
69f9b61a 126}
127
128sub start_app_server {
129 my($self) = @_;
e5f319fc 130 my $child = Child->new(sub { exec($self->{app_server_cmd}) } );
131 $self->{app_server_proc} = $child->start;
69f9b61a 132}
133
134sub stop_app_server {
135 my($self) = @_;
e5f319fc 136 $self->{app_server_proc}->kill("KILL");
a5dd2803 137}
138
676409e6 139sub test_directory {
3749d2e5 140 my ($self, $dir) = @_;
0f44efef 141 if(!exists $self->{app_server_proc}) {
142 $self->start_app_server;
143 }
8ecad210 144 if($self->{selenium_rc}{start} && !$self->{selenium_rc}{selenium_server_proc}) {
145 $self->start_selenium_server;
146 }
3749d2e5 147 my @tests = File::Find::Rule->file()->name('*.html')->in($dir);
a5dd2803 148 for my $test (@tests) {
a5dd2803 149 $self->run_tests_for($test);
a5dd2803 150 }
676409e6 151}
152
153sub run_tests_for {
154 my ($self, $html_file) = @_;
155 my $rows = $self->get_rows_for($html_file);
17ccdf07 156 $self->{src}->run_test_table($rows);
676409e6 157}
158
159my $te = HTML::TableExtract->new;
160sub get_rows_for {
161 my ($self, $html_file) = @_;
162 my $html = io($html_file)->all;
163 $te->parse($html);
164 my $table = ($te->tables)[0];
165 my @rows = map {
799fcc50 166 [ map { (!defined $_ or $_ eq "\240") ? () : $_ } @$_ ]
167 } grep { defined $_->[1] } $table->rows;
676409e6 168 return \@rows;
169}
170
e5f319fc 171sub done {
7522d6aa 172 my($self) = @_;
fbd64e21 173 if(exists $self->{selenium_rc}{xvnc_server_proc} and
174 exists $self->{selenium_rc}{selenium_server_proc}) {
e5f319fc 175 $self->stop_selenium_server;
7522d6aa 176 }
8ecad210 177 $self->stop_app_server;
7522d6aa 178}
179
676409e6 1801;
afe57b05 181
182__END__
183
184=head1 NAME
185
186Test::Harness::Selenium - Test your app with Selenium
187
188=head1 SYNOPSIS
189
190 # t/selenium.t
191 my $ths = Test::Harness::Selenium->new(
192 selenium_rc => {
193 host => 'selenium_xvnc_server',
194 port => 12345,
195 start => 1, # start xvnc and selenium RC via ssh(1)
196 },
197 browser => '*firefox',
198 # app_base is relative from the machine running selenium_rc
199 app_base => 'http://10.0.0.5:3000/',
200 app_start_cmd => 'script/myapp_server.pl -p 3000',
201 );
202 # HTML tables as emitted by the Selenium IDE
203 $ths->test_directory('t/selenium_corpus');
204
205
206 # or, if you've got a selenium server already running (say, on a designer's
207 # Win32 box)
208 my $ths = Test::Harness::Selenium->new(
209 selenium_rc=> {
210 host => 'designers_machine',
211 port => 54321,
212 start => 0, # they've already got the RC running
213 },
214 browser => '*iexplore', # can't live with it, can't live without it
215 app_base => 'http://10.0.0.5:3000/',
216 app_start_cmd => 'script/myapp_server.pl -p 3000',
217 );
218 # otherwise the same
219 $ths->test_directory('t/selenium_corpus');
220
221=head1 DESCRIPTION
222
223C<Test::Harness::Selenium> provides an abstracted way of doing Web app testing
224using the Selenium framework. It will connect to a running Selenium RC server,
225or start one using ssh(1) to connect to a machine and then launching the RC
226server and an xvnc(1) instance in which to run the given browser. After the
227connection is established, Test::Harness::Selenium will read the specified HTML
228files from disk and massage them into a format that
229L<Socialtext::WikiFixture::Selenese> can parse and send to the Selenium RC
230server. The RC server will then script the browser to do the actions described
231in the HTML files. These actions return results, which Test::Harness::Selenium
232then interprets as passing or failing as appropriate.
233
234=head1 METHODS
235
236=head2 new
237
238=over 4
239
240=item arguments: %attrs
241
242=item Return value: new Test::Harness::Selenium object
243
244=back
245
246Constructor. Accepts a list of key/value pairs according to the following:
247
248=over 4
249
250=item selenium_rc
251
252Hashref. Accepts the keys host, port, start. host and port describe the server
253on which to start/find the Selenium RC server and possibly the xvnc server.
254start is a Boolean indicating whether to start the Selenium RC server and xvnc
255server.
256
257=item browser
258
259Scalar. The browser to use for testing the app. Must be in a form that Selenium
260RC understands (e.g. '*firefox'); see the Selenium docs for more info.
261
262=item app_base
263
264Scalar. The URL, relative to the machine running Selenium RC, for the base of
265the app. All requests made to the app are relative to this URL.
266
267=item app_start_cmd
268
269Scalar. This command will be run by start_app_server to run the app server to
270test.
271
272=back
273
274=head2 test_directory
275
276=item arguments: $dir
277
278=item Return value: None
279
280Object method. test_directory will use L<File::Find::Rule> to find all C<< .html >>
281files in the given directory, and then formats massages them into data
282structures that L<Socialtext::WikiFixture::Selenese> can send to the Selenium RC
283server. test_directory will then output appropriate TAP according to whether the
284tests and checks passed or failed, respectively.
285
286=head2 run_tests_for
287
288=item arguments: $html_file
289
290=item Return value: None
291
292run_tests_for is called by test_directory for each C<< .html >> file it finds in
293the given directory.
294
295=head2 start_app_server, start_app_server
296
297=item Arguments: None
298
299=item Return value: None
300
301Start and stop the app server using the command given to the constructor.
302
303=head2 start_selenium_server, start_selenium_server
304
305=item Arguments: None
306
307=item Return value: None
308
309Start and stop the selenium / xvnc servers using the params given to the
310constructor.
311
312=head1 ENVIRONMENT
313
314=head2 SELENIUM_RC_HOST, SELENIUM_RC_PORT, SELENIUM_RC_START
315
316These values override the matching values in the selenium_rc hashref passed to
317new.
318
319=head1 AUTHOR
320
321Chris Nehren <c.nehren/ths@shadowcat.co.uk>, Matt S. Trout <mst@shadowcat.co.uk>
322
323=head1 CONTRIBUTORS
324
325No one, yet. Patches most welcome!
326
327=head1 COPYRIGHT
328
329Copyright (c) 2011 the Test::Harness::Selenium "AUTHOR" and
330"CONTRIBUTORS" as listed above.
331
332=head1 LICENSE
333
334This library is free software and may be distributed under the same terms as
335perl itself.