35d2c04fc399850dfe25118f5b8b1d0e79ff00f9
[srvgit] / t / lib / Selenium.pm
1 package t::lib::Selenium;
2
3 # This file is part of Koha.
4 #
5 # Koha is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # Koha is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
19 use Modern::Perl;
20 use Carp qw( croak );
21 use JSON qw( from_json );
22 use File::Slurp qw( write_file );
23
24 use C4::Context;
25
26 use base qw(Class::Accessor);
27 __PACKAGE__->mk_accessors(qw(login password base_url opac_base_url selenium_addr selenium_port driver));
28
29 sub capture {
30     my ( $class, $driver ) = @_;
31
32     $driver->capture_screenshot('selenium_failure.png');
33
34 }
35
36 sub new {
37     my ( $class, $params ) = @_;
38     my $self   = {};
39     my $config = $class->config;
40     $self->{login}    = $params->{login}    || $config->{login};
41     $self->{password} = $params->{password} || $config->{password};
42     $self->{base_url} = $params->{base_url} || $config->{base_url};
43     $self->{opac_base_url} = $params->{opac_base_url} || $config->{opac_base_url};
44     $self->{selenium_addr} = $params->{selenium_addr} || $config->{selenium_addr};
45     $self->{selenium_port} = $params->{selenium_port} || $config->{selenium_port};
46     $self->{driver} = Selenium::Remote::Driver->new(
47         port               => $self->{selenium_port},
48         remote_server_addr => $self->{selenium_addr},
49     );
50     bless $self, $class;
51     $self->add_error_handler;
52     $self->driver->set_implicit_wait_timeout(5000);
53     return $self;
54 }
55
56 sub add_error_handler {
57     my ( $self ) = @_;
58     $self->{driver}->error_handler(
59         sub {
60             my ( $driver, $selenium_error ) = @_;
61             print STDERR "\nSTRACE:";
62             my $i = 1;
63             while ( (my @call_details = (caller($i++))) ){
64                 print STDERR "\t" . $call_details[1]. ":" . $call_details[2] . " in " . $call_details[3]."\n";
65             }
66             print STDERR "\n";
67             $self->capture( $driver );
68             $driver->quit();
69             croak $selenium_error;
70         }
71     );
72 }
73
74 sub remove_error_handler {
75     my ( $self ) = @_;
76     $self->{driver}->error_handler( sub {} );
77 }
78
79 sub config {
80     return {
81         login    => $ENV{KOHA_USER} || 'koha',
82         password => $ENV{KOHA_PASS} || 'koha',
83         base_url => ( $ENV{KOHA_INTRANET_URL} || C4::Context->preference("staffClientBaseURL") ) . "/cgi-bin/koha/",
84         opac_base_url => ( $ENV{KOHA_OPAC_URL} || C4::Context->preference("OPACBaseURL") ) . "/cgi-bin/koha/",
85         selenium_addr => $ENV{SELENIUM_ADDR} || 'localhost',
86         selenium_port => $ENV{SELENIUM_PORT} || 4444,
87     };
88 }
89
90 sub auth {
91     my ( $self, $login, $password ) = @_;
92
93     $login ||= $self->login;
94     $password ||= $self->password;
95     my $mainpage = $self->base_url . 'mainpage.pl';
96
97     $self->driver->get($mainpage);
98     $self->fill_form( { userid => $login, password => $password } );
99     my $login_button = $self->driver->find_element('//input[@id="submit-button"]');
100     $login_button->click();
101 }
102
103 sub opac_auth {
104     my ( $self, $login, $password ) = @_;
105
106     $login ||= $self->login;
107     $password ||= $self->password;
108     my $mainpage = $self->opac_base_url . 'opac-main.pl';
109
110     $self->driver->get($mainpage . q|?logout.x=1|); # Logout before, to make sure we will see the login form
111     $self->driver->get($mainpage);
112     $self->fill_form( { userid => $login, password => $password } );
113     $self->submit_form;
114 }
115
116 sub fill_form {
117     my ( $self, $values ) = @_;
118     while ( my ( $id, $value ) = each %$values ) {
119         my $element = $self->driver->find_element('//*[@id="'.$id.'"]');
120         my $tag = $element->get_tag_name();
121         if ( $tag eq 'input' ) {
122             $self->driver->find_element('//input[@id="'.$id.'"]')->send_keys($value);
123         } elsif ( $tag eq 'select' ) {
124             $self->driver->find_element('//select[@id="'.$id.'"]/option[@value="'.$value.'"]')->click;
125         }
126     }
127 }
128
129 sub submit_form {
130     my ( $self ) = @_;
131
132     my $default_submit_selector = '//fieldset[@class="action"]/input[@type="submit"]';
133     $self->driver->find_element($default_submit_selector)->click
134 }
135
136 sub click {
137     my ( $self, $params ) = @_;
138     my $xpath_selector;
139     if ( exists $params->{main} ) {
140         $xpath_selector = '//div[@id="'.$params->{main}.'"]';
141     } elsif ( exists $params->{main_class} ) {
142         $xpath_selector = '//div[@class="'.$params->{main_class}.'"]';
143     }
144     if ( exists $params->{href} ) {
145         if ( ref( $params->{href} ) ) {
146             for my $k ( keys %{ $params->{href} } ) {
147                 if ( $k eq 'ends-with' ) {
148                     # ends-with version for xpath version 1
149                     my $ends_with = $params->{href}{"ends-with"};
150                     $xpath_selector .= '//a[substring(@href, string-length(@href) - string-length("'.$ends_with.'") + 1 ) = "'.$ends_with.'"]';
151                     # ends-with version for xpath version 2
152                     #$xpath_selector .= '//a[ends-with(@href, "'.$ends_with.'") ]';
153
154             } else {
155                     die "Only ends-with is supported so far ($k)";
156                 }
157             }
158         } else {
159             $xpath_selector .= '//a[contains(@href, "'.$params->{href}.'")]';
160         }
161     }
162     if ( exists $params->{id} ) {
163         $xpath_selector .= '//*[@id="'.$params->{id}.'"]';
164     }
165     $self->driver->find_element($xpath_selector)->click
166 }
167
168 sub wait_for_element_visible {
169     my ( $self, $xpath_selector ) = @_;
170
171     my ($visible, $elt);
172     $self->remove_error_handler;
173     my $max_retries = $self->max_retries;
174     my $i;
175     while ( not $visible ) {
176         $elt = eval {$self->driver->find_element($xpath_selector) };
177         $visible = $elt && $elt->is_displayed;
178         $self->driver->pause(1000) unless $visible;
179
180         die "Cannot wait more for element '$xpath_selector' to be visible"
181             if $max_retries <= ++$i
182     }
183     $self->add_error_handler;
184     return $elt;
185 }
186
187 sub show_all_entries {
188     my ( $self, $xpath_selector ) = @_;
189
190     $self->driver->find_element( $xpath_selector
191           . '//div[@class="dataTables_length"]/label/select/option[@value="-1"]'
192     )->click;
193     my ($all_displayed, $i);
194     my $max_retries = $self->max_retries;
195     while ( not $all_displayed ) {
196         my $dt_infos = $self->driver->get_text(
197             $xpath_selector . '//div[@class="dataTables_info"]' );
198
199         if ( $dt_infos =~ m|Showing 1 to (\d+) of (\d+) entries| ) {
200             $all_displayed = 1 if $1 == $2;
201         }
202
203         $self->driver->pause(1000) unless $all_displayed;
204
205         die "Cannot show all entries from table $xpath_selector"
206             if $max_retries <= ++$i
207     }
208 }
209
210 sub click_when_visible {
211     my ( $self, $xpath_selector ) = @_;
212
213     my $elt = $self->wait_for_element_visible( $xpath_selector );
214
215     my $clicked;
216     $self->remove_error_handler;
217     while ( not $clicked ) {
218         eval { $self->driver->find_element($xpath_selector)->click };
219         $clicked = !$@;
220         $self->driver->pause(1000) unless $clicked;
221     }
222     $self->add_error_handler;
223     $elt->click unless $clicked; # finally Raise the error
224 }
225
226 sub max_retries { 10 }
227
228 =head1 NAME
229
230 t::lib::Selenium - Selenium helper module
231
232 =head1 SYNOPSIS
233
234     my $s = t::lib::Selenium->new;
235     my $driver = $s->driver;
236     my $base_url = $s->base_url;
237     $s->auth;
238     $driver->get($s->base_url . 'mainpage.pl');
239     $s->fill_form({ input_id => 'value' });
240
241 =head1 DESCRIPTION
242
243 The goal of this module is to group the different actions we need
244 when we use automation test using Selenium
245
246 =head1 METHODS
247
248 =head2 new
249
250     my $s = t::lib::Selenium->new;
251
252     Constructor - Returns the object Selenium
253     You can pass login, password, base_url, selenium_addr, selenium_port
254     If not passed, the environment variables will be used
255     KOHA_USER, KOHA_PASS, KOHA_INTRANET_URL, SELENIUM_ADDR SELENIUM_PORT
256     Or koha, koha, syspref staffClientBaseURL, localhost, 4444
257
258 =head2 auth
259
260     $s->auth;
261
262     Will login into Koha.
263
264 =head2 fill_form
265
266     $driver->get($url)
267     $s->fill_form({
268         input_id => 'value',
269         element_id => 'other_value',
270     });
271
272     Will fill the different elements of a form.
273     The keys must be element ids (input and select are supported so far)
274     The values must a string.
275
276 =head2 submit_form
277
278     $s->submit_form;
279
280     It will submit the form using the submit button present in in the fieldset with a clas="action".
281     It should be the default way. If it does not work you should certainly fix the Koha interface.
282
283 =head2 click
284
285     $s->click
286
287     This is a bit dirty for now but will evolve depending on the needs
288     3 parameters possible but only the following 2 forms are used:
289     $s->click({ href => '/module/script.pl?foo=bar', main => 'doc3' }); # Sometimes we have doc or doc3. To make sure we are not going to hit a link in the header
290     $s->click({ id => 'element_id });
291
292 =head2 click_when_visible
293
294     $c->click_when_visible
295
296     Should always be called to avoid the "An element could not be located on the page" error
297
298 =head2 capture
299     $c->capture
300
301 Capture a screenshot and upload it using the excellent lut.im service provided by framasoft
302 The url of the image will be printed on STDERR (it should be better to return it instead)
303
304 =head2 add_error_handler
305     $c->add_error_handler
306
307 Add our specific error handler to the driver.
308 It will displayed a trace as well as capture a screenshot of the current screen.
309 So only case you should need it is after you called remove_error_handler
310
311 =head2 remove_error_handler
312     $c->remove_error_handler
313
314 Do *not* call this method if you are not aware of what it will do!
315 It will remove any kinds of error raised by the driver.
316 It can be useful in some cases, for instance if you want to make sure something will not happen and that could make the driver exploses otherwise.
317 You certainly should call it for only one statement then must call add_error_handler right after.
318
319 =head1 AUTHORS
320
321 Jonathan Druart <jonathan.druart@bugs.koha-community.org>
322
323 Alex Buckley <alexbuckley@catalyst.net.nz>
324
325 Koha Development Team
326
327 =head1 COPYRIGHT
328
329 Copyright 2017 - Koha Development Team
330
331 =head1 LICENSE
332
333 This file is part of Koha.
334
335 Koha is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by
336 the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
337
338 Koha is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
339
340 You should have received a copy of the GNU General Public License along with Koha; if not, see <http://www.gnu.org/licenses>.
341
342 =cut
343
344 1;