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;
109 sub stop_selenium_server {
111 if (my $proc = delete $self->{selenium_rc}{selenium_server_proc}) {
113 "http://%s:%s/selenium-server/driver/?cmd=shutDownSeleniumServer",
114 $self->{selenium_rc}{host}, $self->{selenium_rc}{port};
115 eval { get($url); }; # will fail if it never started
119 if (delete $self->{selenium_rc}{xvnc_started}) {
120 my $host = $self->{selenium_rc}{host};
121 my @do_ssh = $host eq 'localhost' ? () : ('ssh', $host);
123 exec(@do_ssh, 'vncserver', '-kill',
124 ":$self->{selenium_rc}{xvnc_display}");
129 sub start_app_server {
131 return unless $self->{app_server_cmd};
132 my $child = Child->new(sub { exec($self->{app_server_cmd}) } );
133 $self->{app_server_proc} = $child->start;
136 sub stop_app_server {
138 if (my $proc = $self->{app_server_proc}) {
143 sub start_everything {
145 if(!exists $self->{app_server_proc}) {
146 $self->start_app_server;
148 if($self->{selenium_rc}{start} && !$self->{selenium_rc}{selenium_server_proc}) {
149 $self->start_selenium_server;
154 my ($self, @proto) = @_;
157 @files = grep /\.html$/, @proto;
158 @dirs = grep !/\.htmL$/, @proto;
160 $0 =~ /^(t\/.*).t$/ or die "Can't guess directory from $0";
163 $self->start_everything;
164 $self->test_file($_) for @files;
165 $self->test_directory($_) for
166 sort map File::Find::Rule->directory->in($_), @dirs;
170 my ($self, $dir) = @_;
171 $self->start_everything;
173 sort File::Find::Rule->file->name('*.html')->maxdepth(1)->in($dir);
175 for my $test (@tests) {
176 $self->test_file($test);
181 my ($self, $html_file) = @_;
182 my $rows = $self->get_rows_for($html_file);
183 $self->{src}->run_test_table($rows);
186 my $te = HTML::TableExtract->new;
188 my ($self, $html_file) = @_;
189 my $html = io($html_file)->all;
191 my $table = ($te->tables)[0];
193 [ map { (!defined $_ or $_ eq "\240") ? () : $_ } @$_ ]
194 } grep { defined $_->[1] } $table->rows;
200 $self->stop_selenium_server;
201 $self->stop_app_server;
204 sub DESTROY { shift->done }
212 Test::Harness::Selenium - Test your app with Selenium
217 my $ths = Test::Harness::Selenium->new(
219 host => 'selenium_xvnc_server',
221 start => 1, # start xvnc and selenium RC via ssh(1)
223 browser => '*firefox',
224 # app_base is relative from the machine running selenium_rc
225 app_base => 'http://10.0.0.5:3000/',
226 app_start_cmd => 'script/myapp_server.pl -p 3000',
228 # HTML tables as emitted by the Selenium IDE
229 $ths->test_directory('t/selenium_corpus');
232 # or, if you've got a selenium server already running (say, on a designer's
234 my $ths = Test::Harness::Selenium->new(
236 host => 'designers_machine',
238 start => 0, # they've already got the RC running
240 browser => '*iexplore', # can't live with it, can't live without it
241 app_base => 'http://10.0.0.5:3000/',
242 app_start_cmd => 'script/myapp_server.pl -p 3000',
245 $ths->test_directory('t/selenium_corpus');
249 C<Test::Harness::Selenium> provides an abstracted way of doing Web app testing
250 using the Selenium framework. It will connect to a running Selenium RC server,
251 or start one using ssh(1) to connect to a machine and then launching the RC
252 server and an xvnc(1) instance in which to run the given browser. After the
253 connection is established, Test::Harness::Selenium will read the specified HTML
254 files from disk and massage them into a format that
255 L<Socialtext::WikiFixture::Selenese> can parse and send to the Selenium RC
256 server. The RC server will then script the browser to do the actions described
257 in the HTML files. These actions return results, which Test::Harness::Selenium
258 then interprets as passing or failing as appropriate.
266 =item arguments: %attrs
268 =item Return value: new Test::Harness::Selenium object
272 Constructor. Accepts a list of key/value pairs according to the following:
278 Hashref. Accepts the keys host, port, start. host and port describe the server
279 on which to start/find the Selenium RC server and possibly the xvnc server.
280 start is a Boolean indicating whether to start the Selenium RC server and xvnc
285 Scalar. The browser to use for testing the app. Must be in a form that Selenium
286 RC understands (e.g. '*firefox'); see the Selenium docs for more info.
290 Scalar. The URL, relative to the machine running Selenium RC, for the base of
291 the app. All requests made to the app are relative to this URL.
295 Scalar. This command will be run by start_app_server to run the app server to
300 =head2 test_directory
302 =item arguments: $dir
304 =item Return value: None
306 Object method. test_directory will use L<File::Find::Rule> to find all C<< .html >>
307 files in the given directory, and then formats massages them into data
308 structures that L<Socialtext::WikiFixture::Selenese> can send to the Selenium RC
309 server. test_directory will then output appropriate TAP according to whether the
310 tests and checks passed or failed, respectively.
314 =item arguments: $html_file
316 =item Return value: None
318 run_tests_for is called by test_directory for each C<< .html >> file it finds in
321 =head2 start_app_server, start_app_server
323 =item Arguments: None
325 =item Return value: None
327 Start and stop the app server using the command given to the constructor.
329 =head2 start_selenium_server, start_selenium_server
331 =item Arguments: None
333 =item Return value: None
335 Start and stop the selenium / xvnc servers using the params given to the
340 =head2 SELENIUM_RC_HOST, SELENIUM_RC_PORT, SELENIUM_RC_START
342 These values override the matching values in the selenium_rc hashref passed to
347 Chris Nehren <c.nehren/ths@shadowcat.co.uk>, Matt S. Trout <mst@shadowcat.co.uk>
351 No one, yet. Patches most welcome!
355 Copyright (c) 2011 the Test::Harness::Selenium "AUTHOR" and
356 "CONTRIBUTORS" as listed above.
360 This library is free software and may be distributed under the same terms as