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 $self->{src} = Socialtext::WikiFixture::Selenese->new(
82 host => $self->{selenium_rc}{host},
83 port => $self->{selenium_rc}{port},
84 browser => $self->{browser},
85 browser_url => $self->{app_base},
89 if(!defined $self->{src}) {
97 diag "timed out waiting for selenium server to start at
98 http://$self->{selenium_rc}{host}:$self->{selenium_rc}{port}" if $tries == 5;
103 sub stop_selenium_server {
105 if (my $proc = delete $self->{selenium_rc}{selenium_server_proc}) {
107 "http://%s:%s/selenium-server/driver/?cmd=shutDownSeleniumServer",
108 $self->{selenium_rc}{host}, $self->{selenium_rc}{port};
109 eval { get($url); }; # will fail if it never started
113 if (delete $self->{selenium_rc}{xvnc_started}) {
114 my $host = $self->{selenium_rc}{host};
115 my @do_ssh = $host eq 'localhost' ? () : ('ssh', $host);
117 exec(@do_ssh, 'vncserver', '-kill',
118 ":$self->{selenium_rc}{xvnc_display}");
123 sub start_app_server {
125 my $child = Child->new(sub { exec($self->{app_server_cmd}) } );
126 $self->{app_server_proc} = $child->start;
129 sub stop_app_server {
131 $self->{app_server_proc}->kill("KILL");
135 my ($self, $dir) = @_;
136 if(!exists $self->{app_server_proc}) {
137 $self->start_app_server;
139 if($self->{selenium_rc}{start} && !$self->{selenium_rc}{selenium_server_proc}) {
140 $self->start_selenium_server;
142 my @tests = File::Find::Rule->file()->name('*.html')->in($dir);
143 for my $test (@tests) {
144 $self->run_tests_for($test);
149 my ($self, $html_file) = @_;
150 my $rows = $self->get_rows_for($html_file);
151 $self->{src}->run_test_table($rows);
154 my $te = HTML::TableExtract->new;
156 my ($self, $html_file) = @_;
157 my $html = io($html_file)->all;
159 my $table = ($te->tables)[0];
161 [ map { (!defined $_ or $_ eq "\240") ? () : $_ } @$_ ]
162 } grep { defined $_->[1] } $table->rows;
168 if(exists $self->{selenium_rc}{xvnc_server_proc} and
169 exists $self->{selenium_rc}{selenium_server_proc}) {
170 $self->stop_selenium_server;
172 $self->stop_app_server;
181 Test::Harness::Selenium - Test your app with Selenium
186 my $ths = Test::Harness::Selenium->new(
188 host => 'selenium_xvnc_server',
190 start => 1, # start xvnc and selenium RC via ssh(1)
192 browser => '*firefox',
193 # app_base is relative from the machine running selenium_rc
194 app_base => 'http://10.0.0.5:3000/',
195 app_start_cmd => 'script/myapp_server.pl -p 3000',
197 # HTML tables as emitted by the Selenium IDE
198 $ths->test_directory('t/selenium_corpus');
201 # or, if you've got a selenium server already running (say, on a designer's
203 my $ths = Test::Harness::Selenium->new(
205 host => 'designers_machine',
207 start => 0, # they've already got the RC running
209 browser => '*iexplore', # can't live with it, can't live without it
210 app_base => 'http://10.0.0.5:3000/',
211 app_start_cmd => 'script/myapp_server.pl -p 3000',
214 $ths->test_directory('t/selenium_corpus');
218 C<Test::Harness::Selenium> provides an abstracted way of doing Web app testing
219 using the Selenium framework. It will connect to a running Selenium RC server,
220 or start one using ssh(1) to connect to a machine and then launching the RC
221 server and an xvnc(1) instance in which to run the given browser. After the
222 connection is established, Test::Harness::Selenium will read the specified HTML
223 files from disk and massage them into a format that
224 L<Socialtext::WikiFixture::Selenese> can parse and send to the Selenium RC
225 server. The RC server will then script the browser to do the actions described
226 in the HTML files. These actions return results, which Test::Harness::Selenium
227 then interprets as passing or failing as appropriate.
235 =item arguments: %attrs
237 =item Return value: new Test::Harness::Selenium object
241 Constructor. Accepts a list of key/value pairs according to the following:
247 Hashref. Accepts the keys host, port, start. host and port describe the server
248 on which to start/find the Selenium RC server and possibly the xvnc server.
249 start is a Boolean indicating whether to start the Selenium RC server and xvnc
254 Scalar. The browser to use for testing the app. Must be in a form that Selenium
255 RC understands (e.g. '*firefox'); see the Selenium docs for more info.
259 Scalar. The URL, relative to the machine running Selenium RC, for the base of
260 the app. All requests made to the app are relative to this URL.
264 Scalar. This command will be run by start_app_server to run the app server to
269 =head2 test_directory
271 =item arguments: $dir
273 =item Return value: None
275 Object method. test_directory will use L<File::Find::Rule> to find all C<< .html >>
276 files in the given directory, and then formats massages them into data
277 structures that L<Socialtext::WikiFixture::Selenese> can send to the Selenium RC
278 server. test_directory will then output appropriate TAP according to whether the
279 tests and checks passed or failed, respectively.
283 =item arguments: $html_file
285 =item Return value: None
287 run_tests_for is called by test_directory for each C<< .html >> file it finds in
290 =head2 start_app_server, start_app_server
292 =item Arguments: None
294 =item Return value: None
296 Start and stop the app server using the command given to the constructor.
298 =head2 start_selenium_server, start_selenium_server
300 =item Arguments: None
302 =item Return value: None
304 Start and stop the selenium / xvnc servers using the params given to the
309 =head2 SELENIUM_RC_HOST, SELENIUM_RC_PORT, SELENIUM_RC_START
311 These values override the matching values in the selenium_rc hashref passed to
316 Chris Nehren <c.nehren/ths@shadowcat.co.uk>, Matt S. Trout <mst@shadowcat.co.uk>
320 No one, yet. Patches most welcome!
324 Copyright (c) 2011 the Test::Harness::Selenium "AUTHOR" and
325 "CONTRIBUTORS" as listed above.
329 This library is free software and may be distributed under the same terms as