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