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