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