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