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