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