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>.
21 use JSON qw( from_json );
22 use File::Slurp qw( write_file );
26 use base qw(Class::Accessor);
27 __PACKAGE__->mk_accessors(qw(login password base_url opac_base_url selenium_addr selenium_port driver));
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};
41 print STDERR "\nSCREENSHOT: $lutim_server/" . $from_json->{msg}->{short} . "\n";
46 my ( $class, $params ) = @_;
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},
60 $self->add_error_handler;
61 $self->driver->set_implicit_wait_timeout(5000);
65 sub add_error_handler {
67 $self->{driver}->error_handler(
69 my ( $driver, $selenium_error ) = @_;
70 print STDERR "\nSTRACE:";
72 while ( (my @call_details = (caller($i++))) ){
73 print STDERR "\t" . $call_details[1]. ":" . $call_details[2] . " in " . $call_details[3]."\n";
76 $self->capture( $driver );
78 croak $selenium_error;
83 sub remove_error_handler {
85 $self->{driver}->error_handler( sub {} );
90 login => $ENV{KOHA_USER} || 'koha',
91 password => $ENV{KOHA_PASS} || 'koha',
92 base_url => ( $ENV{KOHA_INTRANET_URL} || C4::Context->preference("staffClientBaseURL") ) . "/cgi-bin/koha/",
93 opac_base_url => ( $ENV{KOHA_OPAC_URL} || C4::Context->preference("OPACBaseURL") ) . "/cgi-bin/koha/",
94 selenium_addr => $ENV{SELENIUM_ADDR} || 'localhost',
95 selenium_port => $ENV{SELENIUM_PORT} || 4444,
100 my ( $self, $login, $password ) = @_;
102 $login ||= $self->login;
103 $password ||= $self->password;
104 my $mainpage = $self->base_url . 'mainpage.pl';
106 $self->driver->get($mainpage);
107 $self->fill_form( { userid => $login, password => $password } );
108 my $login_button = $self->driver->find_element('//input[@id="submit-button"]');
109 $login_button->click();
113 my ( $self, $login, $password ) = @_;
115 $login ||= $self->login;
116 $password ||= $self->password;
117 my $mainpage = $self->opac_base_url . 'opac-main.pl';
119 $self->driver->get($mainpage . q|?logout.x=1|); # Logout before, to make sure we will see the login form
120 $self->driver->get($mainpage);
121 $self->fill_form( { userid => $login, password => $password } );
126 my ( $self, $values ) = @_;
127 while ( my ( $id, $value ) = each %$values ) {
128 my $element = $self->driver->find_element('//*[@id="'.$id.'"]');
129 my $tag = $element->get_tag_name();
130 if ( $tag eq 'input' ) {
131 $self->driver->find_element('//input[@id="'.$id.'"]')->send_keys($value);
132 } elsif ( $tag eq 'select' ) {
133 $self->driver->find_element('//select[@id="'.$id.'"]/option[@value="'.$value.'"]')->click;
141 my $default_submit_selector = '//fieldset[@class="action"]/input[@type="submit"]';
142 $self->driver->find_element($default_submit_selector)->click
146 my ( $self, $params ) = @_;
148 if ( exists $params->{main} ) {
149 $xpath_selector = '//div[@id="'.$params->{main}.'"]';
150 } elsif ( exists $params->{main_class} ) {
151 $xpath_selector = '//div[@class="'.$params->{main_class}.'"]';
153 if ( exists $params->{href} ) {
154 if ( ref( $params->{href} ) ) {
155 for my $k ( keys %{ $params->{href} } ) {
156 if ( $k eq 'ends-with' ) {
157 # ends-with version for xpath version 1
158 my $ends_with = $params->{href}{"ends-with"};
159 $xpath_selector .= '//a[substring(@href, string-length(@href) - string-length("'.$ends_with.'") + 1 ) = "'.$ends_with.'"]';
160 # ends-with version for xpath version 2
161 #$xpath_selector .= '//a[ends-with(@href, "'.$ends_with.'") ]';
164 die "Only ends-with is supported so far ($k)";
168 $xpath_selector .= '//a[contains(@href, "'.$params->{href}.'")]';
171 if ( exists $params->{id} ) {
172 $xpath_selector .= '//*[@id="'.$params->{id}.'"]';
174 $self->driver->find_element($xpath_selector)->click
177 sub wait_for_element_visible {
178 my ( $self, $xpath_selector ) = @_;
181 $self->remove_error_handler;
182 while ( not $visible ) {
183 $elt = eval {$self->driver->find_element($xpath_selector) };
184 $visible = $elt && $elt->is_displayed;
185 $self->driver->pause(1000) unless $visible;
187 $self->add_error_handler;
191 sub show_all_entries {
192 my ( $self, $xpath_selector ) = @_;
194 $self->driver->find_element( $xpath_selector
195 . '//div[@class="dataTables_length"]/label/select/option[@value="-1"]'
197 my ($all_displayed, $i);
198 my $max_retries = $self->max_retries;
199 while ( not $all_displayed ) {
200 my $dt_infos = $self->driver->get_text(
201 $xpath_selector . '//div[@class="dataTables_info"]' );
203 if ( $dt_infos =~ m|Showing 1 to (\d+) of (\d+) entries| ) {
204 $all_displayed = 1 if $1 == $2;
207 $self->driver->pause(1000) unless $all_displayed;
209 die "Cannot show all entries from table $xpath_selector"
210 if $max_retries <= ++$i
214 sub click_when_visible {
215 my ( $self, $xpath_selector ) = @_;
217 my $elt = $self->wait_for_element_visible( $xpath_selector );
220 $self->remove_error_handler;
221 while ( not $clicked ) {
222 eval { $self->driver->find_element($xpath_selector)->click };
224 $self->driver->pause(1000) unless $clicked;
226 $self->add_error_handler;
227 $elt->click unless $clicked; # finally Raise the error
230 sub max_retries { 10 }
234 t::lib::Selenium - Selenium helper module
238 my $s = t::lib::Selenium->new;
239 my $driver = $s->driver;
240 my $base_url = $s->base_url;
242 $driver->get($s->base_url . 'mainpage.pl');
243 $s->fill_form({ input_id => 'value' });
247 The goal of this module is to group the different actions we need
248 when we use automation test using Selenium
254 my $s = t::lib::Selenium->new;
256 Constructor - Returns the object Selenium
257 You can pass login, password, base_url, selenium_addr, selenium_port
258 If not passed, the environment variables will be used
259 KOHA_USER, KOHA_PASS, KOHA_INTRANET_URL, SELENIUM_ADDR SELENIUM_PORT
260 Or koha, koha, syspref staffClientBaseURL, localhost, 4444
266 Will login into Koha.
273 element_id => 'other_value',
276 Will fill the different elements of a form.
277 The keys must be element ids (input and select are supported so far)
278 The values must a string.
284 It will submit the form using the submit button present in in the fieldset with a clas="action".
285 It should be the default way. If it does not work you should certainly fix the Koha interface.
291 This is a bit dirty for now but will evolve depending on the needs
292 3 parameters possible but only the following 2 forms are used:
293 $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
294 $s->click({ id => 'element_id });
296 =head2 click_when_visible
298 $c->click_when_visible
300 Should always be called to avoid the "An element could not be located on the page" error
305 Capture a screenshot and upload it using the excellent lut.im service provided by framasoft
306 The url of the image will be printed on STDERR (it should be better to return it instead)
308 =head2 add_error_handler
309 $c->add_error_handler
311 Add our specific error handler to the driver.
312 It will displayed a trace as well as capture a screenshot of the current screen.
313 So only case you should need it is after you called remove_error_handler
315 =head2 remove_error_handler
316 $c->remove_error_handler
318 Do *not* call this method if you are not aware of what it will do!
319 It will remove any kinds of error raised by the driver.
320 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.
321 You certainly should call it for only one statement then must call add_error_handler right after.
325 Jonathan Druart <jonathan.druart@bugs.koha-community.org>
327 Alex Buckley <alexbuckley@catalyst.net.nz>
329 Koha Development Team
333 Copyright 2017 - Koha Development Team
337 This file is part of Koha.
339 Koha is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by
340 the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
342 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 You should have received a copy of the GNU General Public License along with Koha; if not, see <http://www.gnu.org/licenses>.