1 package Test::Harness::Selenium;
5 use Socialtext::WikiFixture::Selenese;
6 use HTML::TableExtract;
12 our $VERSION = '0.01';
16 package Test::Builder;
18 use Class::Method::Modifiers;
19 use ExtUtils::MakeMaker qw(prompt);
21 if (!$ENV{AUTOMATED_TESTING}) {
23 my ($orig, $self) = (shift, shift);
24 my $res = $self->$orig(@_);
26 if ('y' eq prompt "Well that didn't work, did it. Bail out?", 'y') {
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';
50 sub start_selenium_server {
52 my $selrc = $self->{selenium_rc};
54 my ($host, $display, $port) = @{$selrc}{qw(host xvnc_display port)};
55 my @do_ssh = $host eq 'localhost' ? () : ('ssh', $host);
56 if ($selrc->{start_xvnc}) {
57 $selrc->{xvnc_server_proc} = Child->new(
61 'vncserver', ":${display}",
65 $selrc->{xvnc_started} = 1;
68 $selrc->{selenium_server_proc} = Child->new(
72 'env', "DISPLAY=:${display}", 'selenium-rc', '-port', $port
81 # if we don't create the ::Selenium object ourselves, then
82 # wikifixture shuts the session down after the first test table
83 # is run, at which point KABOOM when you try and run a second one.
84 $self->{src} = Socialtext::WikiFixture::Selenese->new(
85 selenium => Test::WWW::Selenium->new(
86 host => $self->{selenium_rc}{host},
87 port => $self->{selenium_rc}{port},
88 browser => $self->{browser},
89 browser_url => $self->{app_base},
94 if(!defined $self->{src}) {
102 diag "timed out waiting for selenium server to start at
103 http://$self->{selenium_rc}{host}:$self->{selenium_rc}{port}" if $tries == 5;
108 sub stop_selenium_server {
110 if (my $proc = delete $self->{selenium_rc}{selenium_server_proc}) {
112 "http://%s:%s/selenium-server/driver/?cmd=shutDownSeleniumServer",
113 $self->{selenium_rc}{host}, $self->{selenium_rc}{port};
114 eval { get($url); }; # will fail if it never started
118 if (delete $self->{selenium_rc}{xvnc_started}) {
119 my $host = $self->{selenium_rc}{host};
120 my @do_ssh = $host eq 'localhost' ? () : ('ssh', $host);
122 exec(@do_ssh, 'vncserver', '-kill',
123 ":$self->{selenium_rc}{xvnc_display}");
128 sub start_app_server {
130 my $child = Child->new(sub { exec($self->{app_server_cmd}) } );
131 $self->{app_server_proc} = $child->start;
134 sub stop_app_server {
136 $self->{app_server_proc}->kill("KILL");
140 my ($self, $dir) = @_;
141 if(!exists $self->{app_server_proc}) {
142 $self->start_app_server;
144 if($self->{selenium_rc}{start} && !$self->{selenium_rc}{selenium_server_proc}) {
145 $self->start_selenium_server;
147 my @tests = File::Find::Rule->file()->name('*.html')->in($dir);
148 for my $test (@tests) {
149 $self->run_tests_for($test);
154 my ($self, $html_file) = @_;
155 my $rows = $self->get_rows_for($html_file);
156 $self->{src}->run_test_table($rows);
159 my $te = HTML::TableExtract->new;
161 my ($self, $html_file) = @_;
162 my $html = io($html_file)->all;
164 my $table = ($te->tables)[0];
166 [ map { (!defined $_ or $_ eq "\240") ? () : $_ } @$_ ]
167 } grep { defined $_->[1] } $table->rows;
173 if(exists $self->{selenium_rc}{xvnc_server_proc} and
174 exists $self->{selenium_rc}{selenium_server_proc}) {
175 $self->stop_selenium_server;
177 $self->stop_app_server;
186 Test::Harness::Selenium - Test your app with Selenium
191 my $ths = Test::Harness::Selenium->new(
193 host => 'selenium_xvnc_server',
195 start => 1, # start xvnc and selenium RC via ssh(1)
197 browser => '*firefox',
198 # app_base is relative from the machine running selenium_rc
199 app_base => 'http://10.0.0.5:3000/',
200 app_start_cmd => 'script/myapp_server.pl -p 3000',
202 # HTML tables as emitted by the Selenium IDE
203 $ths->test_directory('t/selenium_corpus');
206 # or, if you've got a selenium server already running (say, on a designer's
208 my $ths = Test::Harness::Selenium->new(
210 host => 'designers_machine',
212 start => 0, # they've already got the RC running
214 browser => '*iexplore', # can't live with it, can't live without it
215 app_base => 'http://10.0.0.5:3000/',
216 app_start_cmd => 'script/myapp_server.pl -p 3000',
219 $ths->test_directory('t/selenium_corpus');
223 C<Test::Harness::Selenium> provides an abstracted way of doing Web app testing
224 using the Selenium framework. It will connect to a running Selenium RC server,
225 or start one using ssh(1) to connect to a machine and then launching the RC
226 server and an xvnc(1) instance in which to run the given browser. After the
227 connection is established, Test::Harness::Selenium will read the specified HTML
228 files from disk and massage them into a format that
229 L<Socialtext::WikiFixture::Selenese> can parse and send to the Selenium RC
230 server. The RC server will then script the browser to do the actions described
231 in the HTML files. These actions return results, which Test::Harness::Selenium
232 then interprets as passing or failing as appropriate.
240 =item arguments: %attrs
242 =item Return value: new Test::Harness::Selenium object
246 Constructor. Accepts a list of key/value pairs according to the following:
252 Hashref. Accepts the keys host, port, start. host and port describe the server
253 on which to start/find the Selenium RC server and possibly the xvnc server.
254 start is a Boolean indicating whether to start the Selenium RC server and xvnc
259 Scalar. The browser to use for testing the app. Must be in a form that Selenium
260 RC understands (e.g. '*firefox'); see the Selenium docs for more info.
264 Scalar. The URL, relative to the machine running Selenium RC, for the base of
265 the app. All requests made to the app are relative to this URL.
269 Scalar. This command will be run by start_app_server to run the app server to
274 =head2 test_directory
276 =item arguments: $dir
278 =item Return value: None
280 Object method. test_directory will use L<File::Find::Rule> to find all C<< .html >>
281 files in the given directory, and then formats massages them into data
282 structures that L<Socialtext::WikiFixture::Selenese> can send to the Selenium RC
283 server. test_directory will then output appropriate TAP according to whether the
284 tests and checks passed or failed, respectively.
288 =item arguments: $html_file
290 =item Return value: None
292 run_tests_for is called by test_directory for each C<< .html >> file it finds in
295 =head2 start_app_server, start_app_server
297 =item Arguments: None
299 =item Return value: None
301 Start and stop the app server using the command given to the constructor.
303 =head2 start_selenium_server, start_selenium_server
305 =item Arguments: None
307 =item Return value: None
309 Start and stop the selenium / xvnc servers using the params given to the
314 =head2 SELENIUM_RC_HOST, SELENIUM_RC_PORT, SELENIUM_RC_START
316 These values override the matching values in the selenium_rc hashref passed to
321 Chris Nehren <c.nehren/ths@shadowcat.co.uk>, Matt S. Trout <mst@shadowcat.co.uk>
325 No one, yet. Patches most welcome!
329 Copyright (c) 2011 the Test::Harness::Selenium "AUTHOR" and
330 "CONTRIBUTORS" as listed above.
334 This library is free software and may be distributed under the same terms as