From bea7ebd85034925138fe5197d581caedd2ff4899 Mon Sep 17 00:00:00 2001 From: Alex Buckley Date: Thu, 28 Sep 2017 00:04:29 +0000 Subject: [PATCH] Bug 19243 - Followup moving common Selenium functions into a seperate module In this patch a module t/lib/Selenium.pm has been created and it contains the auth, patron_auth, patron_opac_auth, cleaup, time_difffunctions which are common to other Selenium text scripts. Also a function to wait for an element to be found before it is interacted with was created and it is called pause_driver At present this pause_driver function throws an error in the test output saying that it cannot find an element and then it fails. Test plan (this test plan includes how to install Selenium as well as how to run this test, for the benefit of people who have not got Selenium installed on their machines): 1. wget https://selenium-release.storage.googleapis.com/2.53/selenium-server-standalone-2.53.0.jar 2. vim /etc/apt/sources.list.d/firefox.list 3. Paste into the file: deb http://packages.linuxmint.com debian import 4. sudo apt-get update 5. sudo apt-get install firefox 6. sudo apt-get install xvfb 7. Set the SELENIUM_PATH vartiable: SELENIUM_PATH=/home/vagrant/kohaclone/selenium-server-standalone-2.53.0.jar 8. Xvfb :1 -screen 0 1024x768x24 2>&1 >/dev/null & 9. DISPLAY=:1 java -jar $SELENIUM_PATH Note: This will start up the selenium server. Everytime you want to shutdown this terminal window and want to restart Selenium just run the step 9 command to restart the Selenium server 10. Open a new terminal window and write in: sudo cpanm -i Selenium::Remote::Driver 11. Now everything is installed and you can set up for running the selenium tests 12. Drop and recreate the Koha database and restart memcached 13. Go through the web installer installing all sample data and onboarding tool 14. Create a superlibrarian user with the username koha and password koha 15. If your usual port configuration for the intranet and OPAC is 8081 and 8080 respectively then set the staffClientBaseURL and OPACBaseURL system preferences to localhost:8080 and localhost:80 respectively 16. sudo koha-shell 17. perl t/db_dependent/selenium/administration_tasks.t 18. The test should pass. Note: The time_diff comments in the test output showing what the test is doing Note: If you have issues with installing Selenium and Selenium::Remote::Driver please write a comment on the bug report and I will be more than happy to create a screencapture video showing all the steps Note: Warns in front of the $driver->get_title() function have been implemented because during troubleshooting they were found to be an effective solution for stopping the test from failing due to not being able to find an element on the page. If anyone can suggest an alternative to using these warns please do Sponsored-By: Catalyst IT --- t/db_dependent/selenium/administration_tasks.t | 103 ++++---------- t/lib/Selenium.pm | 185 +++++++++++++++++++++++++ 2 files changed, 215 insertions(+), 73 deletions(-) create mode 100644 t/lib/Selenium.pm diff --git a/t/db_dependent/selenium/administration_tasks.t b/t/db_dependent/selenium/administration_tasks.t index 08c5a38..160395b 100644 --- a/t/db_dependent/selenium/administration_tasks.t +++ b/t/db_dependent/selenium/administration_tasks.t @@ -30,6 +30,7 @@ use C4::Biblio qw( AddBiblio ); # We shouldn't use it use Test::More tests => 1; use MARC::Record; use MARC::Field; +use t::lib::Selenium; my $dbh = C4::Context->dbh; my $login = 'koha'; @@ -37,6 +38,7 @@ my $password = 'koha'; my $base_url= 'http://'.C4::Context->preference("staffClientBaseURL")."/cgi-bin/koha/"; my $opac_url= C4::Context->preference("OPACBaseURL"); +my $number_of_biblios_to_insert = 0; our $sample_data = { category => { categorycode => 'test_cat', @@ -71,28 +73,31 @@ SKIP: { $prev_time = $start; $driver->get($base_url."mainpage.pl"); like( $driver->get_title(), qr(Log in to Koha), ); - auth( $driver, $login, $password ); - time_diff("main"); + t::lib::Selenium::auth( $driver, $login, $password ); + t::lib::Selenium::time_diff($prev_time, "main"); # Navigate to the Administration area and create an item type - $driver->find_element_by_xpath('/html/body/div[4]/div/div[1]/div/div[1]/div[2]/div/ul/li[5]/a')->click; + $driver->find_element('//a[@href="/cgi-bin/koha/admin/admin-home.pl"]')->click; +# t::lib::Selenium::pause_driver($driver, '/html/body/div[4]/div/div/div[2]/div[1]/dl[1]/dt[2]/a'); +# $driver->pause(20000); $driver->pause(20000); - $driver->find_element_by_xpath('/html/body/div[4]/div/div/div[2]/div[1]/dl[1]/dt[2]/a')->click; +# $driver->find_element_by_xpath('/html/body/div[4]/div/div/div[2]/div[1]/dl[1]/dt[2]/a')->click; +# $$driver->find_element_by_xpath('/html/body/div[4]/div/div/div[2]/div[1]/dl[1]/dt[2]/a')->click; $driver->pause(20000); $driver->find_element('//a[@id="newitemtype"]')->click; $driver->pause(20000); - $driver->find_element_by_xpath('/html/body/div[4]/div/div[1]/div/form/fieldset[1]/ol[1]/li[1]/input')->send_keys("DVD"); - $driver->find_element_by_xpath('/html/body/div[4]/div/div[1]/div/form/fieldset[1]/ol[1]/li[2]/input')->send_keys("Digital Optical Disc"); + $driver->find_element('//input[@id="itemtype"]')->send_keys("DVD"); + $driver->find_element('//input[@id="description"]')->send_keys("Digital Optical Disc"); $driver->find_element_by_xpath('/html/body/div[4]/div/div[1]/div/form/fieldset[2]/input')->click; if ( $driver->find_element('//a[@href="/cgi-bin/koha/admin/itemtypes.pl?op=add_form&itemtype=DVD"]')) { - time_diff("Item type successfully added"); + t::lib::Selenium::time_diff($prev_time, "Item type successfully added"); } else { - time_diff("Item type not added successfully"); + t::lib::Selenium::time_diff($prev_time, "Item type not added successfully"); } # Add circulation/fine rules $driver->pause(20000); - $driver->find_element_by_xpath('/html/body/div[3]/a[2]')->click; + $driver->find_element('//input[@href="/cgi-bin/koha/admin/admin-home.pl"]')->click; $driver->pause(20000); $driver->find_element_by_xpath('/html/body/div[4]/div/div/div[2]/div[1]/dl[2]/dt[2]/a')->click; $driver->pause(20000); @@ -103,7 +108,7 @@ SKIP: { $driver->find_element_by_xpath('/html/body/div[4]/div/div[1]/div/div[2]/form[3]/table/tbody/tr[2]/td[22]/input')->send_keys("4"); $driver->find_element_by_xpath('/html/body/div[4]/div/div[1]/div/div[2]/form[3]/table/tbody/tr[2]/td[23]/input')->send_keys("4"); $driver->find_element_by_xpath('/html/body/div[4]/div/div[1]/div/div[2]/form[3]/table/tbody/tr[2]/td[28]/button[1]')->click; - time_diff("Circulation rule added"); + t::lib::Selenium::time_diff($prev_time, "Circulation rule added"); # Modify frameworks $driver->pause(20000); @@ -111,7 +116,6 @@ SKIP: { $driver->pause(20000); $driver->find_element_by_xpath('/html/body/div[4]/div/div/div[2]/div[2]/dl[1]/dt[1]/a')->click; $driver->pause(20000); - warn $driver->get_title(); $driver->find_element_by_xpath('/html/body/div[4]/div/div[1]/div/div[2]/table/tbody/tr[5]/td[3]/div/a')->click; $driver->find_element_by_xpath('/html/body/div[4]/div/div[1]/div/div[2]/table/tbody/tr[5]/td[3]/div/ul/li[1]/a')->click; $driver->pause(20000); @@ -128,7 +132,7 @@ SKIP: { $driver->find_element_by_xpath('/html/body/div[4]/div/div[1]/div/form/fieldset[1]/ol/li[3]/input')->send_keys("Local Dewey"); $driver->find_element_by_xpath('/html/body/div[4]/div/div[1]/div/form/fieldset[2]/input')->click; $driver->pause(20000); - time_diff("Added subfield to framework"); + t::lib::Selenium::time_diff($prev_time, "Added subfield to framework"); #Add library $driver->get_title(); @@ -147,9 +151,9 @@ SKIP: { $driver->find_element_by_xpath('/html/body/div[4]/div/div[1]/div/div[3]/div[1]/div[2]/label/select')->click; $driver->find_element_by_xpath('/html/body/div[4]/div/div[1]/div/div[3]/div[1]/div[2]/label/select/option[3]')->click; if ($driver->find_element('//td[text()="Test library"]')) { - time_diff("Library successfully added"); + t::lib::Selenium::time_diff($prev_time, "Library successfully added"); } else { - time_diff("Library not successfully added"); + t::lib::Selenium::time_diff($prev_time, "Library not successfully added"); } @@ -168,9 +172,9 @@ SKIP: { $driver->find_element_by_xpath('/html/body/div[4]/div/div[1]/div/form/fieldset[1]/ol/li[4]/input')->send_keys("Hardcover"); $driver->find_element_by_xpath('/html/body/div[4]/div/div[1]/div/form/fieldset[2]/input[2]')->click; if ($driver->find_element('//td[text()="HBK"]')) { - time_diff("Authorised value successfully added"); + t::lib::Selenium::time_diff($prev_time, "Authorised value successfully added"); } else { - time_diff("Authorised value not successfully added"); + t::lib::Seleniumtime_diff($prev_time, "Authorised value not successfully added"); } #Patron category @@ -188,9 +192,9 @@ SKIP: { $driver->find_element_by_xpath('/html/body/div[4]/div/div[1]/div/form/fieldset[2]/input')->click; $driver->pause(20000); if ($driver->find_element('//td[text()="AD"]')) { - time_diff("Patron category successfully added"); + t::lib::Selenium::time_diff($prev_time, "Patron category successfully added"); } else { - time_diff("Patron category not successfully added"); + t::lib::Selenium::time_diff(i$prev_time, "Patron category not successfully added"); } #Add patron attribute type @@ -206,11 +210,10 @@ SKIP: { $driver->find_element_by_xpath('/html/body/div[4]/div/div[1]/div/form/fieldset[1]/ol/li[11]/select/option[2]')->click; $driver->find_element_by_xpath('/html/body/div[4]/div/div[1]/div/form/fieldset[2]/input')->click; $driver->pause(20000); - warn $driver->get_title(); if ($driver->find_element('//td[text()="DL"]')) { - time_diff("Patron attribute type successfully added"); + t::lib::Selenium::time_diff($prev_time, "Patron attribute type successfully added"); } else { - time_diff("Patron attribute type not successfully added"); + t::lib::Selenium::time_diff($prev_time, "Patron attribute type not successfully added"); } #Add Z39.50 target server @@ -229,9 +232,9 @@ SKIP: { $driver->find_element_by_xpath('/html/body/div[4]/div/div[1]/div/form/fieldset[2]/input')->click; $driver->pause(20000); if ($driver->find_element('//td[text()="consull.ull.ac.uk:210"]')) { - time_diff("Z39.50 target successfully added"); + t::lib::Selenium::time_diff($prev_time, "Z39.50 target successfully added"); } else { - time_diff("Z39.50 target not successfully added"); + t::lib::Selenium::time_diff($prev_time, "Z39.50 target not successfully added"); } #Add item circulation alerts @@ -240,7 +243,7 @@ SKIP: { $driver->find_element_by_xpath('/html/body/div[4]/div/div/div[2]/div[1]/dl[2]/dt[6]/a')->click; $driver->pause(20000); $driver->find_element_by_xpath('/html/body/div[4]/div/div[1]/div/div[2]/div[1]/table/tbody/tr[1]/td[1]')->click; - time_diff("Added item circulation alert"); + t::lib::Selenium::time_diff($prev_time, "Added item circulation alert"); #Add city $driver->find_element_by_xpath('/html/body/div[3]/a[2]')->click; @@ -255,9 +258,9 @@ SKIP: { $driver->find_element_by_xpath('/html/body/div[4]/div/div[1]/div/form/fieldset[2]/input')->click; $driver->pause(20000); if ($driver->find_element('//td[text()="Wellington"]')) { - time_diff("City successfully added"); + t::lib::Selenium::time_diff($prev_time, "City successfully added"); } else { - time_diff("City not successfully added"); + t::lib::Selenium::time_diff($prev_time, "City not successfully added"); } close $fh; @@ -265,52 +268,6 @@ SKIP: { }; END { - cleanup() if $cleanup_needed; + t::lib::Selenium::cleanup($sample_data,$number_of_biblios_to_insert,$borrowernumber) if $cleanup_needed; }; -sub auth { - my ( $driver, $login, $password) = @_; - fill_form( $driver, { userid => 'koha', password => 'koha' } ); - my $login_button = $driver->find_element('//input[@id="submit"]'); - $login_button->submit(); -} - -sub patron_auth { - my ( $driver,$patronusername, $patronpassword) = @_; - fill_form( $driver, { userid => $patronusername, password => $patronpassword } ); - my $login_button = $driver->find_element('//input[@id="submit"]'); - $login_button->submit(); -} - -sub patron_opac_auth { - my ( $driver,$patronusername, $patronpassword) = @_; - fill_form( $driver, { userid => $patronusername, password => $patronpassword } ); - my $login_button = $driver->find_element('//input[@value="Log in"]'); - $login_button->submit(); -} - -sub fill_form { - my ( $driver, $values ) = @_; - while ( my ( $id, $value ) = each %$values ) { - my $element = $driver->find_element('//*[@id="'.$id.'"]'); - my $tag = $element->get_tag_name(); - if ( $tag eq 'input' ) { - $driver->find_element('//input[@id="'.$id.'"]')->send_keys($value); - } elsif ( $tag eq 'select' ) { - $driver->find_element('//select[@id="'.$id.'"]/option[@value="'.$value.'"]')->click; - } - } -} - -sub cleanup { - my $dbh = C4::Context->dbh; - $dbh->do(q|DELETE FROM categories WHERE categorycode = ?|, {}, $sample_data->{category}{categorycode}); - $dbh->do(q|DELETE FROM borrowers WHERE userid = ?|, {}, $sample_data->{patron}{userid}); -} - -sub time_diff { - my $lib = shift; - my $now = gettimeofday; - warn "CP $lib = " . sprintf("%.2f", $now - $prev_time ) . "\n"; - $prev_time = $now; -} diff --git a/t/lib/Selenium.pm b/t/lib/Selenium.pm new file mode 100644 index 0000000..c993fd5 --- /dev/null +++ b/t/lib/Selenium.pm @@ -0,0 +1,185 @@ +package t::lib::Selenium; + +use Modern::Perl; + +use Koha::Database; + +use Carp; +use Module::Load; +use String::Random; +use Time::HiRes qw(gettimeofday); +use Selenium::Waiter qw/wait_until/; +use Try::Tiny; + +my $dbh = C4::Context->dbh; +my $login = 'koha'; +my $password = 'koha'; +my $base_url= 'http://'.C4::Context->preference("staffClientBaseURL")."/cgi-bin/koha/"; + +sub auth { + my ( $driver, $login, $password) = @_; + fill_form( $driver, { userid => 'koha', password => 'koha' } ); + my $login_button = $driver->find_element('//input[@id="submit"]'); + $login_button->submit(); +} + +sub patron_auth { + my ( $driver,$patronusername, $patronpassword) = @_; + fill_form( $driver, { userid => $patronusername, password => $patronpassword } ); + my $login_button = $driver->find_element('//input[@id="submit"]'); + $login_button->submit(); +} + +sub patron_opac_auth { + my ( $driver,$patronusername, $patronpassword) = @_; + fill_form( $driver, { userid => $patronusername, password => $patronpassword } ); + my $login_button = $driver->find_element('//input[@value="Log in"]'); + $login_button->submit(); +} + +sub fill_form { + my ( $driver, $values ) = @_; + while ( my ( $id, $value ) = each %$values ) { + my $element = $driver->find_element('//*[@id="'.$id.'"]'); + my $tag = $element->get_tag_name(); + if ( $tag eq 'input' ) { + $driver->find_element('//input[@id="'.$id.'"]')->send_keys($value); + } elsif ( $tag eq 'select' ) { + $driver->find_element('//select[@id="'.$id.'"]/option[@value="'.$value.'"]')->click; + } + } +} + +sub cleanup { + my ($sample_data, $number_of_biblios_to_insert,$borrowernumber) = @_; + my $dbh = C4::Context->dbh; + $dbh->do(q|DELETE FROM categories WHERE categorycode = ?|, {}, $sample_data->{category}{categorycode}); + $dbh->do(q|DELETE FROM borrowers WHERE userid = ?|, {}, $sample_data->{patron}{userid}); + for my $i ( 1 .. $number_of_biblios_to_insert ) { + $dbh->do(qq|DELETE FROM biblio WHERE title = "test biblio $i"|); + }; + + $dbh->do(q|DELETE FROM issues where borrowernumber=?|, {}, $borrowernumber); + $dbh->do(q|DELETE FROM old_issues where borrowernumber=?|, {},$borrowernumber); + for my $i ( 1 .. $number_of_biblios_to_insert ) { + $dbh->do(qq|DELETE items, biblio FROM biblio INNER JOIN items ON biblio.biblionumber = items.biblionumber WHERE biblio.title ="test biblio$i"|); + }; +} + +sub time_diff { + my ($prev_time, $lib) = (@_); + my $now = gettimeofday; + warn "CP $lib = " . sprintf("%.2f", $now - $prev_time ) . "\n"; + $prev_time = $now; +} + +sub pause_driver { + my ($driver, $element) = @_; + $driver->set_implicit_wait_timeout(30000); + my $elem_exists = wait_until {$driver->find_element_by_xpath('$element')}; + + if ($elem_exists) { + $driver->find_element_by_xpath('$element')->click; + } +} + + +=head1 NAME + +t::lib::Selenium.pm - Koha module for common Selenium functions + +=head1 SYNOPSIS + + +=head1 DESCRIPTION + +This Perl module + + +This module automatically creates database records for you. +If needed, records for foreign keys are created too. +Values will be randomly generated if not passed to TestBuilder. +Note that you should wrap these actions in a transaction yourself. + +=head1 METHODS + +=head2 new + + my $builder = t::lib::TestBuilder->new; + + Constructor - Returns the object TestBuilder + +=head2 schema + + my $schema = $builder->schema; + + Getter - Returns the schema of DBIx::Class + +=head2 delete + + $builder->delete({ + source => $source, + records => $patron, # OR: records => [ $patron, ... ], + }); + + Delete individual records, created by builder. + Returns the number of delete attempts, or undef. + +=head2 build + + $builder->build({ source => $source_name, value => $value }); + + Create a test record in the table, represented by $source_name. + The name is required and must conform to the DBIx::Class schema. + Values may be specified by the optional $value hashref. Will be + randomized otherwise. + If needed, TestBuilder creates linked records for foreign keys. + Returns the values of the new record as a hashref, or undef if + the record could not be created. + + Note that build also supports recursive hash references inside the + value hash for foreign key columns, like: + value => { + column1 => 'some_value', + fk_col2 => { + columnA => 'another_value', + } + } + The hash for fk_col2 here means: create a linked record with build + where columnA has this value. In case of a composite FK the hashes + are merged. + + Realize that passing primary key values to build may result in undef + if a record with that primary key already exists. + +=head2 build_object + +Given a plural Koha::Object-derived class, it creates a random element, and +returns the corresponding Koha::Object. + + my $patron = $builder->build_object({ class => 'Koha::Patrons' [, value => { ... }] }); + +=head1 AUTHOR + +Alex Buckley + +Catalyst Koha Development Team + +=head1 COPYRIGHT + +Copyright 2017 - Catalyst IT + +=head1 LICENSE + +This file is part of Koha. + +Koha is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or (at your option) any later version. + +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. + +You should have received a copy of the GNU General Public License along with Koha; if not, see . + +=cut + +1; -- 2.1.4