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}) {
144 my ($self, $dir) = @_;
145 $self->start_everything;
147 sort File::Find::Rule->file->name('*.html')->maxdepth(1)->in($dir);
149 for my $test (@tests) {
150 $self->test_file($test);
155 my ($self, $html_file) = @_;
156 my $rows = $self->get_rows_for($html_file);
157 $self->{src}->run_test_table($rows);
160 # might as well keep this object around.
161 my $te = HTML::TableExtract->new;
163 my ($self, $html_file) = @_;
164 my $html = io($html_file)->all;
166 my $table = ($te->tables)[0];
168 [ map { (!defined $_ or $_ eq "\240") ? () : $_ } @$_ ]
169 } grep { defined $_->[1] } $table->rows;
175 $self->stop_selenium_server;
176 $self->stop_app_server;
179 sub DESTROY { shift->done }
187 Test::Harness::Selenium - Test your app with Selenium
193 my $s = Test::Harness::Selenium->new(
200 app_base => 'http://10.0.0.5:3000',
201 app_server_cmd => 'examples/THSelenium-Test/script/thselenium_test_server.pl',
202 browser => '*firefox',
204 # HTML tables as emitted by the Selenium IDE
205 eval { $s->test_directory('t/corpus/') };
210 # or, if you've got a selenium server already running (say, on a designer's
212 my $ths = Test::Harness::Selenium->new(
214 host => 'designers_machine',
216 start => 0, # they've already got the RC running
218 browser => '*iexplore', # can't live with it, can't live without it
219 app_base => 'http://10.0.0.5:3000/',
220 app_server_cmd => 'script/myapp_server.pl -p 3000',
223 $ths->test_directory('t/selenium_corpus');
227 C<Test::Harness::Selenium> provides an abstracted way of doing Web app testing
228 using the Selenium framework. It will connect to a running Selenium RC server,
229 or start one using ssh(1) to connect to a machine and then launching the RC
230 server and an xvnc(1) instance in which to run the given browser. After the
231 connection is established, Test::Harness::Selenium will read the specified HTML
232 files from disk and massage them into a format that
233 L<Socialtext::WikiFixture::Selenese> can parse and send to the Selenium RC
234 server. The RC server will then script the browser to do the actions described
235 in the HTML files. These actions return results, which Test::Harness::Selenium
236 then interprets as passing or failing as appropriate.
244 =item arguments: %attrs
246 =item Return value: new Test::Harness::Selenium object
250 Constructor. Accepts a list of key/value pairs according to the following:
256 Hashref. Accepts the keys host, port, start, start_xvnc, xvnc_display. host and
257 port describe the server on which to start/find the Selenium RC server and
258 possibly the xvnc server. start is a Boolean indicating whether to start the
259 Selenium RC server, whereas start_xvnc dictates the same for the xvnc server.
260 xvnc_display is the X11 display to point browsers at when launching them.
264 Scalar. The browser to use for testing the app. Must be in a form that Selenium
265 RC understands (e.g. '*firefox'); see the Selenium docs for more info.
269 Scalar. The URL, relative to the machine running Selenium RC, for the base of
270 the app. All requests made to the app are relative to this URL.
274 Scalar. This command will be run by start_app_server to run the app server to
279 =head2 test_directory
281 =item arguments: $dir
283 =item Return value: None
285 Object method. test_directory will use L<File::Find::Rule> to find all C<< .html >>
286 files in the given directory, and then massages them into data structures that
287 L<Socialtext::WikiFixture::Selenese> can send to the Selenium RC server.
288 test_directory will then output appropriate TAP according to whether the tests
289 and checks passed or failed, respectively.
293 =item arguments: $file
295 =item Return value: None
297 Object method. Runs the tests given in the specified file.
299 =head2 start_app_server, stop_app_server
301 =item Arguments: None
303 =item Return value: None
305 Object method. Start and stop the app server using the command given to the
306 constructor. References the app_server_cmd key passed to the constructor.
308 =head2 start_selenium_server, start_selenium_server
310 =item Arguments: None
312 =item Return value: None
314 Start and stop the selenium / xvnc servers using the params given to the
319 =head2 SELENIUM_RC_HOST, SELENIUM_RC_PORT, SELENIUM_RC_START,
320 SELENIUM_RC_START_XVNC, SELENIUM_RC_XVNC_DISPLAY
322 These values override the matching values in the selenium_rc hashref passed to
327 Chris Nehren <c.nehren/ths@shadowcat.co.uk>, Matt S. Trout <mst@shadowcat.co.uk>
331 No one, yet. Patches most welcome! We most especially welcome doc patches to
332 make our code easier to use. We can write the best code in the world, but it
333 doesn't do anyone any good if it's impossible to use because of bad docs.
337 Copyright (c) 2011 the Test::Harness::Selenium "AUTHOR" and
338 "CONTRIBUTORS" as listed above.
342 This library is free software and may be distributed under the same terms as