Bug 24347: Add a 'search to order' option similar to 'search to hold'
[koha.git] / t / lib / Selenium.pm
blob29f7ef4700b848dcf6dec8ae22eef093cdab121b
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 );
23 use C4::Context;
25 use base qw(Class::Accessor);
26 __PACKAGE__->mk_accessors(qw(login password base_url opac_base_url selenium_addr selenium_port driver));
28 sub capture {
29 my ( $class, $driver ) = @_;
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";
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},
53 bless $self, $class;
54 $self->add_error_handler;
55 return $self;
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";
68 print STDERR "\n";
69 $self->capture( $driver );
70 croak $selenium_error;
75 sub remove_error_handler {
76 my ( $self ) = @_;
77 $self->{driver}->error_handler( sub {} );
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,
91 sub auth {
92 my ( $self, $login, $password ) = @_;
94 $login ||= $self->login;
95 $password ||= $self->password;
96 my $mainpage = $self->base_url . 'mainpage.pl';
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();
104 sub opac_auth {
105 my ( $self, $login, $password ) = @_;
107 $login ||= $self->login;
108 $password ||= $self->password;
109 my $mainpage = $self->opac_base_url . 'opac-main.pl';
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;
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;
130 sub submit_form {
131 my ( $self ) = @_;
133 my $default_submit_selector = '//fieldset[@class="action"]/input[@type="submit"]';
134 $self->click_when_visible( $default_submit_selector );
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}.'"]';
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.'") ]';
155 } else {
156 die "Only ends-with is supported so far ($k)";
159 } else {
160 $xpath_selector .= '//a[contains(@href, "'.$params->{href}.'")]';
163 if ( exists $params->{id} ) {
164 $xpath_selector .= '//*[@id="'.$params->{id}.'"]';
166 $self->click_when_visible( $xpath_selector );
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;
178 $elt->click;
181 =head1 NAME
183 t::lib::Selenium - Selenium helper module
185 =head1 SYNOPSIS
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' });
194 =head1 DESCRIPTION
196 The goal of this module is to group the different actions we need
197 when we use automation test using Selenium
199 =head1 METHODS
201 =head2 new
203 my $s = t::lib::Selenium->new;
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
211 =head2 auth
213 $s->auth;
215 Will login into Koha.
217 =head2 fill_form
219 $driver->get($url)
220 $s->fill_form({
221 input_id => 'value',
222 element_id => 'other_value',
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.
229 =head2 submit_form
231 $s->submit_form;
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.
236 =head2 click
238 $s->click
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 });
245 =head2 click_when_visible
247 $c->click_when_visible
249 Should always be called to avoid the "An element could not be located on the page" error
251 =head2 capture
252 $c->capture
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)
257 =head2 add_error_handler
258 $c->add_error_handler
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
264 =head2 remove_error_handler
265 $c->remove_error_handler
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.
272 =head1 AUTHORS
274 Jonathan Druart <jonathan.druart@bugs.koha-community.org>
276 Alex Buckley <alexbuckley@catalyst.net.nz>
278 Koha Development Team
280 =head1 COPYRIGHT
282 Copyright 2017 - Koha Development Team
284 =head1 LICENSE
286 This file is part of Koha.
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.
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.
293 You should have received a copy of the GNU General Public License along with Koha; if not, see <http://www.gnu.org/licenses>.
295 =cut