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