Bug 26846: Fix regressions - Preselect expected and late serials
[koha.git] / t / lib / Selenium.pm
blob261502aae13095e20423554e129ab0a6af939cd6
1 package t::lib::Selenium;
3 # This file is part of Koha.
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.
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.
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>.
19 use Modern::Perl;
20 use Carp qw( croak );
21 use JSON qw( from_json );
22 use File::Slurp qw( write_file );
24 use C4::Context;
26 use base qw(Class::Accessor);
27 __PACKAGE__->mk_accessors(qw(login password base_url opac_base_url selenium_addr selenium_port driver));
29 sub capture {
30 my ( $class, $driver ) = @_;
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";
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";
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},
59 bless $self, $class;
60 $self->add_error_handler;
61 return $self;
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";
74 print STDERR "\n";
75 $self->capture( $driver );
76 croak $selenium_error;
81 sub remove_error_handler {
82 my ( $self ) = @_;
83 $self->{driver}->error_handler( sub {} );
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,
97 sub auth {
98 my ( $self, $login, $password ) = @_;
100 $login ||= $self->login;
101 $password ||= $self->password;
102 my $mainpage = $self->base_url . 'mainpage.pl';
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();
110 sub opac_auth {
111 my ( $self, $login, $password ) = @_;
113 $login ||= $self->login;
114 $password ||= $self->password;
115 my $mainpage = $self->opac_base_url . 'opac-main.pl';
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;
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;
136 sub submit_form {
137 my ( $self ) = @_;
139 my $default_submit_selector = '//fieldset[@class="action"]/input[@type="submit"]';
140 $self->click_when_visible( $default_submit_selector );
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}.'"]';
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.'") ]';
161 } else {
162 die "Only ends-with is supported so far ($k)";
165 } else {
166 $xpath_selector .= '//a[contains(@href, "'.$params->{href}.'")]';
169 if ( exists $params->{id} ) {
170 $xpath_selector .= '//*[@id="'.$params->{id}.'"]';
172 $self->click_when_visible( $xpath_selector );
175 sub wait_for_element_visible {
176 my ( $self, $xpath_selector ) = @_;
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;
186 $self->add_error_handler;
187 return $elt;
190 sub show_all_entries {
191 my ( $self, $xpath_selector ) = @_;
193 $self->driver->find_element( $xpath_selector
194 . '//div[@class="dataTables_length"]/label/select/option[@value="-1"]'
195 )->click;
196 my ($all_displayed, $i);
197 my $max_retries = $self->max_retries;
198 while ( not $all_displayed ) {
199 my $dt_infos = $self->driver->get_text(
200 $xpath_selector . '//div[@class="dataTables_info"]' );
202 if ( $dt_infos =~ m|Showing 1 to (\d+) of (\d+) entries| ) {
203 $all_displayed = 1 if $1 == $2;
206 $self->driver->pause(1000) unless $all_displayed;
208 die "Cannot show all entries from table $xpath_selector"
209 if $max_retries <= ++$i
213 sub click_when_visible {
214 my ( $self, $xpath_selector ) = @_;
216 my $elt = $self->wait_for_element_visible( $xpath_selector );
218 my $clicked;
219 $self->remove_error_handler;
220 while ( not $clicked ) {
221 eval { $self->driver->find_element($xpath_selector)->click };
222 $clicked = !$@;
223 $self->driver->pause(1000) unless $clicked;
225 $self->add_error_handler;
226 $elt->click unless $clicked; # finally Raise the error
229 sub max_retries { 10 }
231 =head1 NAME
233 t::lib::Selenium - Selenium helper module
235 =head1 SYNOPSIS
237 my $s = t::lib::Selenium->new;
238 my $driver = $s->driver;
239 my $base_url = $s->base_url;
240 $s->auth;
241 $driver->get($s->base_url . 'mainpage.pl');
242 $s->fill_form({ input_id => 'value' });
244 =head1 DESCRIPTION
246 The goal of this module is to group the different actions we need
247 when we use automation test using Selenium
249 =head1 METHODS
251 =head2 new
253 my $s = t::lib::Selenium->new;
255 Constructor - Returns the object Selenium
256 You can pass login, password, base_url, selenium_addr, selenium_port
257 If not passed, the environment variables will be used
258 KOHA_USER, KOHA_PASS, KOHA_INTRANET_URL, SELENIUM_ADDR SELENIUM_PORT
259 Or koha, koha, syspref staffClientBaseURL, localhost, 4444
261 =head2 auth
263 $s->auth;
265 Will login into Koha.
267 =head2 fill_form
269 $driver->get($url)
270 $s->fill_form({
271 input_id => 'value',
272 element_id => 'other_value',
275 Will fill the different elements of a form.
276 The keys must be element ids (input and select are supported so far)
277 The values must a string.
279 =head2 submit_form
281 $s->submit_form;
283 It will submit the form using the submit button present in in the fieldset with a clas="action".
284 It should be the default way. If it does not work you should certainly fix the Koha interface.
286 =head2 click
288 $s->click
290 This is a bit dirty for now but will evolve depending on the needs
291 3 parameters possible but only the following 2 forms are used:
292 $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
293 $s->click({ id => 'element_id });
295 =head2 click_when_visible
297 $c->click_when_visible
299 Should always be called to avoid the "An element could not be located on the page" error
301 =head2 capture
302 $c->capture
304 Capture a screenshot and upload it using the excellent lut.im service provided by framasoft
305 The url of the image will be printed on STDERR (it should be better to return it instead)
307 =head2 add_error_handler
308 $c->add_error_handler
310 Add our specific error handler to the driver.
311 It will displayed a trace as well as capture a screenshot of the current screen.
312 So only case you should need it is after you called remove_error_handler
314 =head2 remove_error_handler
315 $c->remove_error_handler
317 Do *not* call this method if you are not aware of what it will do!
318 It will remove any kinds of error raised by the driver.
319 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.
320 You certainly should call it for only one statement then must call add_error_handler right after.
322 =head1 AUTHORS
324 Jonathan Druart <jonathan.druart@bugs.koha-community.org>
326 Alex Buckley <alexbuckley@catalyst.net.nz>
328 Koha Development Team
330 =head1 COPYRIGHT
332 Copyright 2017 - Koha Development Team
334 =head1 LICENSE
336 This file is part of Koha.
338 Koha is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by
339 the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
341 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.
343 You should have received a copy of the GNU General Public License along with Koha; if not, see <http://www.gnu.org/licenses>.
345 =cut