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