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