From 46d1867bb49d9bb2b938bec373ee7fa6da781071 Mon Sep 17 00:00:00 2001 From: Olli-Antti Kivilahti Date: Fri, 10 Jul 2015 11:06:53 +0000 Subject: [PATCH] Bug 14536 - PageObject-pattern base implementation This patch introduces: -PageObject base classes for OPAC and Intra, and example tests. More example tests in Buugg 14540, where setting test context is easier. -a interactive debug script to drive PageObjects more easily. ------------------ PageObjects are used to make robust and reusable integration test components to test various front-end features. PageObjects load a Selenium::Remote::Driver implementation, phantomjs by default and use this to do scripted user actions in the browser, eg. clicking HTML-elements, accepting popup dialogs, entering text to input fields. PageObjects encapsulate those very low-level operations into clear and easily usable actions or services, like doPasswordLogin(). PageObjects also seamlessly deal with navigation from one page to another, eg. my $mainpage = t::lib::Page::Mainpage->new(); $mainpage->doPasswordLogin('admin', '1234')->gotoPatrons()-> searchPatrons({keywordSearch => "Jane Doe"}); PageObjects make doing integration tests so great! You can google for the specifics of PageObject patterns and there are a ton of great tutorials explaining their function. Now PageObjects also have a debugging system where you can get the server response in a title+cookies+html-dump and a screenshot of the browser session. see t::lib::Page for more info! --------------------- Interactive debugger at misc/devel/interactiveWebDriverShell.pl loads the desired PageObject with the needed parameters to the debugger context and makes dynamic UserAgent scripting easy and fun! --------------------- Bug 14536: (follow-up) Adds polling to Page.pm and enhances navigation. This follow-up adds a subroutine poll to superclass Page.pm. Polling will execute an anonymous subroutine for as long as defined or until the subroutine is successful. --------------------- --- misc/devel/interactiveWebDriverShell.pl | 202 +++++++++++++++++++ t/db_dependent/Koha/Auth.t | 72 +++++++ t/lib/Page.pm | 344 ++++++++++++++++++++++++++++++++ t/lib/Page/Catalogue/Detail.pm | 150 ++++++++++++++ t/lib/Page/Catalogue/Search.pm | 107 ++++++++++ t/lib/Page/Catalogue/Toolbar.pm | 136 +++++++++++++ t/lib/Page/Circulation/Circulation.pm | 76 +++++++ t/lib/Page/Intra.pm | 228 +++++++++++++++++++++ t/lib/Page/Mainpage.pm | 64 ++++++ t/lib/Page/Members/ApiKeys.pm | 200 +++++++++++++++++++ t/lib/Page/Members/Boraccount.pm | 164 +++++++++++++++ t/lib/Page/Members/LeftNavigation.pm | 138 +++++++++++++ t/lib/Page/Members/MemberFlags.pm | 143 +++++++++++++ t/lib/Page/Members/Memberentry.pm | 265 ++++++++++++++++++++++++ t/lib/Page/Members/Moremember.pm | 207 +++++++++++++++++++ t/lib/Page/Members/Notices.pm | 195 ++++++++++++++++++ t/lib/Page/Members/Pay.pm | 106 ++++++++++ t/lib/Page/Members/Paycollect.pm | 276 +++++++++++++++++++++++++ t/lib/Page/Members/Toolbar.pm | 154 ++++++++++++++ t/lib/Page/Opac.pm | 238 ++++++++++++++++++++++ t/lib/Page/Opac/LeftNavigation.pm | 167 ++++++++++++++++ t/lib/Page/Opac/OpacAccount.pm | 142 +++++++++++++ t/lib/Page/Opac/OpacApiKeys.pm | 170 ++++++++++++++++ t/lib/Page/Opac/OpacMain.pm | 125 ++++++++++++ t/lib/Page/Opac/OpacMemberentry.pm | 161 +++++++++++++++ t/lib/Page/Opac/OpacMessaging.pm | 101 ++++++++++ t/lib/Page/Opac/OpacPaycollect.pm | 94 +++++++++ t/lib/Page/Opac/OpacSearch.pm | 142 +++++++++++++ t/lib/Page/Opac/OpacSearchHistory.pm | 121 +++++++++++ t/lib/Page/Opac/OpacUser.pm | 64 ++++++ t/lib/Page/PageUtils.pm | 69 +++++++ 31 files changed, 4821 insertions(+) create mode 100755 misc/devel/interactiveWebDriverShell.pl create mode 100644 t/db_dependent/Koha/Auth.t create mode 100644 t/lib/Page.pm create mode 100644 t/lib/Page/Catalogue/Detail.pm create mode 100644 t/lib/Page/Catalogue/Search.pm create mode 100644 t/lib/Page/Catalogue/Toolbar.pm create mode 100644 t/lib/Page/Circulation/Circulation.pm create mode 100644 t/lib/Page/Intra.pm create mode 100644 t/lib/Page/Mainpage.pm create mode 100644 t/lib/Page/Members/ApiKeys.pm create mode 100644 t/lib/Page/Members/Boraccount.pm create mode 100644 t/lib/Page/Members/LeftNavigation.pm create mode 100644 t/lib/Page/Members/MemberFlags.pm create mode 100644 t/lib/Page/Members/Memberentry.pm create mode 100644 t/lib/Page/Members/Moremember.pm create mode 100644 t/lib/Page/Members/Notices.pm create mode 100644 t/lib/Page/Members/Pay.pm create mode 100644 t/lib/Page/Members/Paycollect.pm create mode 100644 t/lib/Page/Members/Toolbar.pm create mode 100644 t/lib/Page/Opac.pm create mode 100644 t/lib/Page/Opac/LeftNavigation.pm create mode 100644 t/lib/Page/Opac/OpacAccount.pm create mode 100644 t/lib/Page/Opac/OpacApiKeys.pm create mode 100644 t/lib/Page/Opac/OpacMain.pm create mode 100644 t/lib/Page/Opac/OpacMemberentry.pm create mode 100644 t/lib/Page/Opac/OpacMessaging.pm create mode 100644 t/lib/Page/Opac/OpacPaycollect.pm create mode 100644 t/lib/Page/Opac/OpacSearch.pm create mode 100644 t/lib/Page/Opac/OpacSearchHistory.pm create mode 100644 t/lib/Page/Opac/OpacUser.pm create mode 100644 t/lib/Page/PageUtils.pm diff --git a/misc/devel/interactiveWebDriverShell.pl b/misc/devel/interactiveWebDriverShell.pl new file mode 100755 index 0000000..f3e6acd --- /dev/null +++ b/misc/devel/interactiveWebDriverShell.pl @@ -0,0 +1,202 @@ +#!/usr/bin/perl -d + +# Copyright 2015 KohaSuomi +# +# 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 . + +=head1 NAME + +interactiveWebDriverShell.pl + +=head1 SYNOPSIS + + misc/devel/interactiveWebDriverShell.pl -p mainpage.pl + +Prepares a perl debugger session with the requested PageObject loaded. +Then you can easily guide the UserAgent through the web session. + +=cut + +use Modern::Perl; + +use Getopt::Long qw(:config no_ignore_case); +use Data::Dumper; + +my ($help, $page, $list, @params, @login); + +GetOptions( + "h|help" => \$help, + "p|page=s" => \$page, + "P|params=s{,}" => \@params, + "L|login=s{,}" => \@login, + "l|list" => \$list, +); + +my $help_msg = < c + +The PageObject is bound to variable \$po, +and the Selenium::Remote::Driver-implementation to \$d. +Then all you need to do is start navigating the web! + DB<2> \$po->isPasswordLoginAvailable()->doPasswordLogin('admin','1234'); + + DB<3> \$ele = \$d->find_element('input[value="Save"]'); + +Note! Do not use "my \$ele = 123;" in the debugger session, because that doesn't +work as excepted, simply use "\$ele = 123;". + +HELP + +if ($help) { + print $help_msg; + exit; +} +unless ($page || $list) { + print $help_msg; + exit; +} + +my $supportedPageObjects = { +################################################################################ + ########## STAFF CONFIGURATIONS ########## +################################################################################ + 'mainpage.pl' => + { package => "t::lib::Page::Mainpage", + urlEndpoint => "mainpage.pl", + status => "OK", + params => "none", + }, + "members/moremember.pl" => + { package => "t::lib::Page::Members::Moremember", + urlEndpoint => "members/moremember.pl", + status => "not implemented", + params => ["borrowernumber"], + }, + "members/statistics.pl" => + { package => "t::lib::Page::Members::Statistics", + urlEndpoint => "members/statistics.pl", + status => "OK", + params => ["borrowernumber"], + }, + "members/member-flags.pl" => + { package => "t::lib::Page::Members::MemberFlags", + urlEndpoint => "members/member-flags.pl", + status => "not implemented", + params => ["borrowernumber"], + }, + "catalogue/detail.pl" => + { package => "t::lib::Page::Catalogue::Detail", + urlEndpoint => "catalogue/detail.pl", + status => "OK", + params => ["biblionumber"], + }, +################################################################################ + ########## OPAC CONFIGURATIONS ########## +################################################################################ + "opac/opac-main.pl" => + { package => "t::lib::Page::Opac::OpacMain", + urlEndpoint => "opac/opac-main.pl", + status => "OK", + }, +}; +################################################################################ + ########## END OF PAGE CONFIGURATIONS ########## +################################################################################ + +listSupportedPageObjects ($supportedPageObjects) if $list; +my ($po, $d) = deployPageObject($supportedPageObjects, $page, \@params, \@login) if $page; + + + +print "--Debugging--\n"; +$DB::single = 1; #Breakpoint here +$DB::single = 1; + + + +sub listSupportedPageObjects { + my ($supportedPageObjects) = @_; + print Data::Dumper::Dumper($supportedPageObjects); + exit; +} +sub deployPageObject { + my ($supportedPageObjects, $page, $params, $login) = @_; + + ##Find correct PageObject deployment rules + my $pageObjectMapping = $supportedPageObjects->{$page}; + die "No PageObject mapped to --page '$page'. See --list to list available PageObjects.\n" unless $pageObjectMapping; + + ##Dynamically load package + my $package = $pageObjectMapping->{package}; + eval "require $package"; + + ##Fill required parameters + my $poParams = {}; + if (ref($pageObjectMapping->{params}) eq 'ARRAY') { + foreach my $paramName (@{$pageObjectMapping->{params}}) { + $poParams->{$paramName} = shift(@$params); + die "Insufficient parameters given, parameter '$paramName' unsatisfied.\n" unless $poParams->{$paramName}; + } + } + + ##Check if the status is OK + die "PageObject status for '$page' is not 'OK'. Current status '".$pageObjectMapping->{status}."'.\nPlease implement the missing PageObject.\n" unless $pageObjectMapping->{status} eq 'OK'; + + ##Create PageObject + my $po = $package->new($poParams); + + ##Password login if desired + eval { + $po->isPasswordLoginAvailable->doPasswordLogin($login->[0], $login->[1]) if scalar(@$login); + }; if ($@) { + print "Password login unavailable.\n"; + } + + return ($po, $po->getDriver()); +} diff --git a/t/db_dependent/Koha/Auth.t b/t/db_dependent/Koha/Auth.t new file mode 100644 index 0000000..1017667 --- /dev/null +++ b/t/db_dependent/Koha/Auth.t @@ -0,0 +1,72 @@ +#!/usr/bin/env perl + +# Copyright 2015 Open Source Freedom Fighters +# +# 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 . + +use Modern::Perl; + +use Test::More; +use Try::Tiny; #Even Selenium::Remote::Driver uses Try::Tiny :) + +use t::lib::Page::Mainpage; + +use t::lib::TestObjects::PatronFactory; + +##Setting up the test context +my $testContext = {}; + +my $password = '1234'; +my $borrowerFactory = t::lib::TestObjects::PatronFactory->new(); +my $borrowers = $borrowerFactory->createTestGroup([ + {firstname => 'Olli-Antti', + surname => 'Kivi', + cardnumber => '1A01', + branchcode => 'CPL', + flags => '1', #superlibrarian, not exactly a very good way of doing permission testing? + userid => 'mini_admin', + password => $password, + }, + ], undef, $testContext); + +##Test context set, starting testing: +eval { #run in a eval-block so we don't die without tearing down the test context + + testPasswordLogin(); + +}; +if ($@) { #Catch all leaking errors and gracefully terminate. + warn $@; + tearDown(); + exit 1; +} + +##All tests done, tear down test context +tearDown(); +done_testing; + +sub tearDown { + t::lib::TestObjects::ObjectFactory->tearDownTestContext($testContext); +} + +###################################################### + ### STARTING TEST IMPLEMENTATIONS ### +###################################################### + +sub testPasswordLogin { + my $mainpage = t::lib::Page::Mainpage->new(); + $mainpage->isPasswordLoginAvailable()->doPasswordLogin($borrowers->{'1A01'}->userid(), $password)->quit(); +} \ No newline at end of file diff --git a/t/lib/Page.pm b/t/lib/Page.pm new file mode 100644 index 0000000..e162ba7 --- /dev/null +++ b/t/lib/Page.pm @@ -0,0 +1,344 @@ +package t::lib::Page; + +# Copyright 2015 Open Source Freedom Fighters +# +# 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 . + +use Modern::Perl; +use Test::More; + +use C4::Context; + +use t::lib::WebDriverFactory; + +use Koha::Exception::BadParameter; +use Koha::Exception::SystemCall; + +=head NAME t::lib::Page + +=head SYNOPSIS + +PageObject-pattern parent class. Extend this to implement specific pages shown to our users. + +PageObjects are used to make robust and reusable integration test components to test +various front-end features. PageObjects load a Selenium::Remote::Driver implementation, +phantomjs by default and use this to do scripted user actions in the browser, +eg. clicking HTML-elements, accepting popup dialogs, entering text to input fields. + +PageObjects encapsulate those very low-level operations into clear and easily usable +actions or services, like doPasswordLogin(). +PageObjects also seamlessly deal with navigation from one page to another, eg. + my $mainpage = t::lib::Page::Mainpage->new(); + $mainpage->doPasswordLogin('admin', '1234')->gotoPatrons()-> + searchPatrons({keywordSearch => "Jane Doe"}); + +=head Class variables + +Selenium::Remote::Driver driver, contains the driver implementation used to run these tests +t::Page::Common::Header header, the header page component (not implemented) +t::Page::Common::Footer footer, the footer page component (not implemented) +Scalar userInteractionDelay, How many milliseconds to wait for javascript + to stop processing by default after user actions? + +=head DEBUGGING + +Set Environment value + $ENV{KOHA_PAGEOBJECT_DEBUG} = 1; +Before creating the first PageObject to enable debugging. +Debugging output is written to /tmp/PageObjectDebug/ by default, but you can change it +using the same environment variable + $ENV{KOHA_PAGEOBJECT_DEBUG} = "/tmp/generalDebugging/"; + +=cut + +sub new { + my ($class, $params) = @_; + $params = _mergeDefaultConfig($params); + + my $self = {}; + bless($self, $class); + unless ($params->{driver}) { + my ($driver) = t::lib::WebDriverFactory::getUserAgentDrivers({phantomjs => $params}); + $self->{driver} = $driver; + } + $self->{type} = $params->{type}; #This parameter is mandatory. _mergeDefaultConfig() dies without it. + $self->{resource} = $params->{resource} || '/'; + $self->{resource} .= "?".join('&', @{$params->{getParams}}) if $params->{getParams}; + $self->{header} = $params->{header} || undef; + $self->{footer} = $params->{footer} || undef; + + $self->{userInteractionDelay} = $params->{userInteractionDelay} || 500; + + $self->{driver}->set_window_size(1280, 960); + $self->{driver}->get( $self->{resource} ); + + $self->debugSetEnvironment(); #If debugging is enabled + + return $self; +} + +=head rebrandFromPageObject +When we are getting redirected from one page to another we rebrand the existing PageObject +as another PageObject to have the new page's services available. + +@RETURNS The desired new PageObject Page +=cut + +sub rebrandFromPageObject { + my ($class, $self) = @_; + bless($self, $class); + my $d = $self->getDriver(); + $d->pause(250); #Wait for javascript to load. + $self->debugTakeSessionSnapshot(); + ok(1, "Navigated to $class"); + return $self; +} + +=head _mergeDefaultConfig + +@THROWS Koha::Exception::BadParameter +=cut + +sub _mergeDefaultConfig { + my ($params) = @_; + unless (ref($params) eq 'HASH' && $params->{type}) { + Koha::Exception::BadParameter->throw(error => "t::lib::Page:> When instantiating Page-objects, you must define the 'type'-parameter."); + } + + my $testServerConfigs = C4::Context->config('testservers'); + my $conf = $testServerConfigs->{ $params->{type} }; + Koha::Exception::BadParameter->throw(error => "t::lib::Page:> Unknown 'type'-parameter '".$params->{type}."'. Values 'opac', 'staff' and 'rest' are supported.") + unless $conf; + #Merge given $params-config on top of the $KOHA_CONF's testservers-directives + @$conf{keys %$params} = values %$params; + return $conf; +} + +=head quit +Wrapper for Selenium::Remote::Driver->quit(), +Delete the session & close open browsers. + +When ending this browser session, it is polite to quit, or there is a risk of leaving +floating test browsers floating around. +=cut + +sub quit { + my ($self) = @_; + $self->getDriver()->quit(); +} + +=head pause +Wrapper for Selenium::Remote::Driver->pause(), +=cut + +sub pause { + my ($self, $pauseMillis) = @_; + $self->getDriver()->pause($pauseMillis); + return $self; +} + +=head +Wrapper for Selenium::Remote::Driver->refresh() +=cut + +sub refresh { + my ($self) = @_; + $self->getDriver()->refresh(); + $self->debugTakeSessionSnapshot(); + return $self; +} + +=head poll +Polls anonymous subroutine $func at given rate $pauseMillis for given times $polls or +until $func succeeds without exceptions. + +In case of an exception, optional anonymous subroutine $success is called to confirm +whether or not the action was successful. If this subroutine is not defined or it returns +false, polling continues. + +Default pause for polling is 50ms and the polling runs by default for 20 times. + +@PARAM1 $func Anonymous subroutine to be polled +@PARAM2 $success OPTIONAL Success function to check if action was successful +@PARAM3 $polls OPTIONAL Defines the times polling will be ran +@PARAM4 $pauseMillis OPTIONAL Defines the wait between two polls + +@RETURNS 1 if polling was success, otherwise die +=cut + +sub poll { + my ($self, $func, $success, $polls, $pauseMillis) = @_; + + # initialize default values if not given + $polls = 20 unless defined $polls; + $pauseMillis = 50 unless defined $pauseMillis; + + for (my $i = 0; $i < $polls; $i++){ + eval { + &$func(); + }; + if ($@) { + return 1 if defined $success and &$success(); + $self->getDriver()->pause($pauseMillis); + next; + } + return 1 unless $@; # if no errors, return true + } + die $@; +} + +=head mockConfirmPopup + +Workaround to a missing feature in PhantomJS v1.9 +Confirm popup's cannot be negotiated with. This preparatory method makes confirm dialogs +always return 'true' or 'false' without showing the actual dialog. + +@PARAM1 Boolean, the confirm popup dialog's return value. + +=cut + +sub mockConfirmPopup { + my ($self, $retVal) = @_; + my $d = $self->getDriver(); + + my $script = q{ + var retVal = (arguments[0] == 1) ? true : false; + var callback = arguments[arguments.length-1]; + window.confirm = function(){return retVal;}; + callback(); + }; + $d->execute_async_script($script, ($retVal ? 1 : 0)); +} + +=head mockPromptPopup + +Workaround to a missing feature in PhantomJS v1.9 +Prompt popup's cannot be negotiated with. This preparatory method makes prompt dialogs +always return 'true' or 'false' without showing the actual dialog. + +@PARAM1 Boolean, the prompt popup dialog's return value. + +=cut + +sub mockPromptPopup { + my ($self, $retVal) = @_; + my $d = $self->getDriver(); + + my $script = q{ + var callback = arguments[arguments.length-1]; + window.prompt = function(){return arguments[0];}; + callback(); + }; + $d->execute_async_script($script, ($retVal ? 1 : 0)); +} + +=head mockAlertPopup + +Workaround to a missing feature in PhantomJS v1.9 +Alert popup's cannot be negotiated with and they freeze PageObject testing. +This preparatory method makes alert dialogs always return NULL without showing +the actual dialog. + +=cut + +sub mockAlertPopup { + my ($self, $retVal) = @_; + my $d = $self->getDriver(); + + my $script = q{ + var callback = arguments[arguments.length-1]; + window.alert = function(){return;}; + callback(); + }; + $d->execute_async_script($script); +} + +################################################ + ## INTRODUCING OBJECT ACCESSORS ## +################################################ +sub setDriver { + my ($self, $driver) = @_; + $self->{driver} = $driver; +} +sub getDriver { + my ($self) = @_; + return $self->{driver}; +} + +################################################ + ## INTRODUCING TESTING HELPERS ## +################################################ +sub debugSetEnvironment { + my ($self) = @_; + if ($ENV{KOHA_PAGEOBJECT_DEBUG}) { + $self->{debugSessionId} = sprintf("%03i",rand(999)); + $self->{debugSessionTmpDirectory} = "/tmp/PageObjectDebug/"; + $self->{debugSessionTmpDirectory} = $ENV{KOHA_PAGEOBJECT_DEBUG} if (not(ref($ENV{KOHA_PAGEOBJECT_DEBUG})) && length($ENV{KOHA_PAGEOBJECT_DEBUG}) > 1); + my $error = system(("mkdir", "-p", $self->{debugSessionTmpDirectory})); + Koha::Exception::SystemCall->throw(error => "Trying to create a temporary directory for PageObject debugging session '".$self->{debugSessionId}."' failed:\n $?") + if $error; + $self->{debugInternalCounter} = 1; + + print "\n\n--Starting PageObject debugging session '".$self->{debugSessionId}."'\n\n"; + } +} + +sub debugTakeSessionSnapshot { + my ($self) = @_; + if ($ENV{KOHA_PAGEOBJECT_DEBUG}) { + my ($actionIdentifier, $actionFile) = $self->_debugGetSessionIdentifier(2); + + $self->_debugWriteHTML($actionIdentifier, $actionFile); + $self->_debugWriteScreenshot($actionIdentifier, $actionFile); + $self->{debugInternalCounter}++; + } +} + +sub _debugGetSessionIdentifier { + my ($self, $callerDepth) = @_; + $callerDepth = $callerDepth || 2; + ##Create a unique and descriptive identifier for this program state. + my ($package, $filename, $line, $subroutine) = caller($callerDepth); #Get where we are called from + $subroutine = $2 if ($subroutine =~ /(::|->)([^:->]+)$/); #Get the last part of the package, the subroutine name. + my $actionIdentifier = "[session '".$self->{debugSessionId}."', counter '".sprintf("%03i",$self->{debugInternalCounter})."', caller '$subroutine']"; + my $actionFile = $self->{debugSessionId}.'_'.sprintf("%03i",$self->{debugInternalCounter}).'_'.$subroutine; + return ($actionIdentifier, $actionFile); +} + +sub _debugWriteHTML { + require Data::Dumper; + my ($self, $actionIdentifier, $actionFile) = @_; + my $d = $self->getDriver(); + + ##Write the current Response data + open(my $fh, ">:encoding(UTF-8)", $self->{debugSessionTmpDirectory}.$actionFile.'.html') + or die "Trying to open a filehandle for PageObject debugging output $actionIdentifier:\n $@"; + print $fh $d->get_title()."\n"; + print $fh "ALL COOKIES DUMP:\n".Data::Dumper::Dumper($d->get_all_cookies()); + print $fh $d->get_page_source()."\n"; + close $fh; +} + +sub _debugWriteScreenshot { + my ($self, $actionIdentifier, $actionFile) = @_; + my $d = $self->getDriver(); + + ##Write a screenshot of the view to file. + my $ok = $d->capture_screenshot($self->{debugSessionTmpDirectory}.$actionFile.'.png'); + Koha::Exception::SystemCall->throw(error => "Cannot capture a screenshot for PageObject $actionIdentifier") + unless $ok; +} +1; #Make the compiler happy! \ No newline at end of file diff --git a/t/lib/Page/Catalogue/Detail.pm b/t/lib/Page/Catalogue/Detail.pm new file mode 100644 index 0000000..4372069 --- /dev/null +++ b/t/lib/Page/Catalogue/Detail.pm @@ -0,0 +1,150 @@ +package t::lib::Page::Catalogue::Detail; + +# Copyright 2015 Open Source Freedom Fighters +# +# 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 . + +use Modern::Perl; +use Scalar::Util qw(blessed); +use Test::More; + +use t::lib::Page::Catalogue::Search; + +use base qw(t::lib::Page::Intra t::lib::Page::Catalogue::Toolbar); + +use Koha::Exception::BadParameter; + +=head NAME t::lib::Page::Catalogue::Detail + +=head SYNOPSIS + +detail.pl PageObject providing page functionality as a service! + +=cut + +=head new + + my $detail = t::lib::Page::Catalogue::Detail->new({biblionumber => "1"})->doPasswordLogin('admin', '2134'); + +Instantiates a WebDriver and loads the catalogue/detail.pl to show the given Biblio +@PARAM1 HASHRef of optional and MANDATORY parameters +MANDATORY extra parameters: + biblionumber => loads the page to display the Biblio matching the given parameter + +@RETURNS t::lib::Page::Catalogue::Detail, ready for user actions! +=cut + +sub new { + my ($class, $params) = @_; + unless (ref($params) eq 'HASH' || (blessed($params) && $params->isa('t::lib::Page') )) { + $params = {}; + } + $params->{resource} = '/cgi-bin/koha/catalogue/detail.pl'; + $params->{type} = 'staff'; + + $params->{getParams} = []; + #Handle MANDATORY parameters + if ($params->{biblionumber}) { + push @{$params->{getParams}}, "biblionumber=".$params->{biblionumber}; + } + else { + Koha::Exception::BadParameter->throw(error => __PACKAGE__."->new():> Parameter 'borrowernumber' is missing."); + } + + my $self = $class->SUPER::new($params); + + return $self; +} + +################################################################################ +=head UI Mapping helper subroutines +See. Selenium documentation best practices for UI element mapping to common language descriptions. +=cut +################################################################################ + +=head _getBiblioMarkers + +@RETURNS HASHRef of Biblio data elements of the displayed Biblio details. + 'title' is guaranteed, others are optional +=cut + +sub _getBiblioMarkers { + my ($self) = @_; + my $d = $self->getDriver(); + + my $e = {}; + $e->{title} = $d->find_element("#catalogue_detail_biblio .title", 'css')->get_text(); #title is mandatory and should always exist to simplify testing. + eval { + $e->{author} = $d->find_element("#catalogue_detail_biblio .author a", 'css')->get_text(); + }; + eval { + $e->{isbn} = $d->find_element("#catalogue_detail_biblio span[property='isbn']", 'css')->get_text(); + }; + return $e; +} + + + +################################################################################ +=head PageObject Services + +=cut +################################################################################ + +=head isBiblioMatch + + $detail->isBiblioMatch($record); + +Checks that the loaded Biblio matches the give MARC::Record. +=cut + +sub isBiblioMatch { + my ($self, $record) = @_; + $self->debugTakeSessionSnapshot(); + + my $e = $self->_getBiblioMarkers(); + my $testFail; + if (not($record->title() eq $e->{title})) { + $testFail = 1; + } + if ($record->author() && not($record->author() eq $e->{author})) { + $testFail = 1; + } + + ok(not($testFail), "Biblio '".$record->title()."' matches loaded Biblio"); + return $self; +} + +=head deleteBiblio +Deletes the displayed Biblio + +@RETURNS t::lib::PageObject::Catalogue::Search as the execution moves to that PageObject. +=cut + +sub deleteBiblio { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $e = $self->_getEditDropdownElements(); + $self->mockConfirmPopup('true'); + $e->{deleteRecord}->click(); + + return t::lib::Page::Catalogue::Search->rebrandFromPageObject($self); +} + + +1; #Make the compiler happy! diff --git a/t/lib/Page/Catalogue/Search.pm b/t/lib/Page/Catalogue/Search.pm new file mode 100644 index 0000000..0f25274 --- /dev/null +++ b/t/lib/Page/Catalogue/Search.pm @@ -0,0 +1,107 @@ +package t::lib::Page::Catalogue::Search; + +# Copyright 2015 Open Source Freedom Fighters +# +# 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 . + +use Modern::Perl; +use Scalar::Util qw(blessed); +use Time::HiRes; +use Test::More; + +use base qw(t::lib::Page::Intra t::lib::Page::Catalogue::Toolbar); + +use Koha::Exception::UnknownObject; + +=head NAME t::lib::Page::Catalogue::Search + +=head SYNOPSIS + +search.pl PageObject providing page functionality as a service! + +=cut + +=head new + + my $search = t::lib::Page::Catalogue::Search->new()->doPasswordLogin('admin', '2134'); + +Instantiates a WebDriver and loads the catalogue/search.pl to show the given Biblio +@PARAM1 HASHRef of optional and MANDATORY parameters + +@RETURNS t::lib::Page::Catalogue::Search, ready for user actions! +=cut + +sub new { + my ($class, $params) = @_; + unless (ref($params) eq 'HASH' || (blessed($params) && $params->isa('t::lib::Page') )) { + $params = {}; + } + $params->{resource} = '/cgi-bin/koha/catalogue/search.pl'; + $params->{type} = 'staff'; + + $params->{getParams} = []; + + my $self = $class->SUPER::new($params); + + return $self; +} + +=head rebrandFromPageObject +@EXTENDS t::lib::Page->rebrandFromPageObject() + +Checks that the rebranding succeeds by making sure that the page we are rebranding to +is the page we have. +=cut + +sub rebrandFromPageObject { + my ($class, $self) = @_; + my $d = $self->getDriver(); + + my $waitCycles = 0; + while ($waitCycles <= 10 && + $d->get_current_url() !~ m!catalogue/search.pl! ) { + Time::HiRes::usleep(250000); + $waitCycles++; + } + + if ($waitCycles > 10) { + my ($package, $filename, $line, $subroutine) = caller(1); + Koha::Exception::UnknownObject->throw(error => __PACKAGE__."->rebrandFromPageObject():> Timeout looking for proper Page markers. This is not a 'catalogue/search.pl'-page!\n Called from $package->$subroutine"); + } + return $class->SUPER::rebrandFromPageObject($self); +} + +################################################################################ +=head UI Mapping helper subroutines +See. Selenium documentation best practices for UI element mapping to common language descriptions. +=cut +################################################################################ + + + + + +################################################################################ +=head PageObject Services + +=cut +################################################################################ + + + + + +1; #Make the compiler happy! diff --git a/t/lib/Page/Catalogue/Toolbar.pm b/t/lib/Page/Catalogue/Toolbar.pm new file mode 100644 index 0000000..565869e --- /dev/null +++ b/t/lib/Page/Catalogue/Toolbar.pm @@ -0,0 +1,136 @@ +package t::lib::Page::Catalogue::Toolbar; + +# Copyright 2015 KohaSuomi! +# +# 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 . + +use Modern::Perl; +use Test::More; + +use Koha::Exception::BadParameter; + +=head NAME t::lib::Page::Catalogue::Toolbar + +=head SYNOPSIS + +PageObject Accessory representing a shared Template between PageObjects. +This encapsulates specific page-related functionality. + +In this case this encapsulates the members-module toolbar's services and provides +a reusable class from all member-module PageObjects. + +=cut + +################################################################################ +=head UI Mapping helper subroutines +See. Selenium documentation best practices for UI element mapping to common language descriptions. +=cut +################################################################################ + +=head _getToolbarActionElements +@RETURNS HASHRef of Selenium::Driver::Webelements matching all the clickable elements + in the actions toolbar over the Biblio information. +=cut + +sub _getToolbarActionElements { + my ($self) = @_; + my $d = $self->getDriver(); + + my $e = {}; + eval { + $e->{new} = $d->find_element("#newDropdownContainer button", 'css'); + }; + eval { + $e->{edit} = $d->find_element("#editDropdownContainer button", 'css'); + }; + eval { + $e->{save} = $d->find_element("#saveDropdownContainer button", 'css'); + }; + eval { + $e->{addTo} = $d->find_element("#addtoDropdownContainer button", 'css'); + }; + eval { + $e->{print} = $d->find_element("#printbiblio", 'css'); + }; + eval { + $e->{placeHold} = $d->find_element("#placeholdDropdownContainer button", 'css'); + }; + return $e; +} + +=head _getEditDropdownElements +Clicks the dropdown open if it isnt yet. +@RETURNS HASHRef of all the dropdown elements under the Edit button in the toolbar + over Biblio information. +=cut + +sub _getEditDropdownElements { + my ($self) = @_; + my $d = $self->getDriver(); + + my $toolbarElements = $self->_getToolbarActionElements(); + my $editButton = $toolbarElements->{edit}; + my $dropdownElement; + eval { + $dropdownElement = $d->find_child_element($editButton, "#editDropdownContainer ul a:nth-of-type(1)", 'css'); + }; + unless ($dropdownElement && $dropdownElement->is_visible()) { + $editButton->click(); + $self->debugTakeSessionSnapshot(); + } + + my $e = {}; + eval { + $e->{editRecord} = $d->find_element("a[id|='editbiblio']", 'css'); + }; + eval { + $e->{editItems} = $d->find_element("a[id|='edititems']", 'css'); + }; + eval { + $e->{editItemsInBatch} = $d->find_element("a[id|='batchedit']", 'css'); + }; + eval { + $e->{deleteItemsInBatch} = $d->find_element("a[id|='batchdelete']", 'css'); + }; + eval { + $e->{attachItem} = $d->find_element("a[href*='cataloguing/moveitem.pl']", 'css'); + }; + eval { + $e->{editAsNew} = $d->find_element("a[id|='duplicatebiblio']", 'css'); + }; + eval { + $e->{replaceRecord} = $d->find_element("a[id|='z3950copy']", 'css'); + }; + eval { + $e->{deleteRecord} = $d->find_element("a[id|='deletebiblio']", 'css'); + }; + eval { + $e->{deleteAllItems} = $d->find_element("a[id|='deleteallitems']", 'css'); + }; + return $e; +} + + +################################################################################ +=head PageObject Services + +=cut +################################################################################ + + + + +1; #Make the compiler happy! \ No newline at end of file diff --git a/t/lib/Page/Circulation/Circulation.pm b/t/lib/Page/Circulation/Circulation.pm new file mode 100644 index 0000000..a651a66 --- /dev/null +++ b/t/lib/Page/Circulation/Circulation.pm @@ -0,0 +1,76 @@ +package t::lib::Page::Circulation::Circulation; + +# Copyright 2015 Open Source Freedom Fighters +# +# 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 . + +use Modern::Perl; +use Scalar::Util qw(blessed); +use Test::More; + +use base qw(t::lib::Page::Intra t::lib::Page::Members::Toolbar t::lib::Page::Members::LeftNavigation); + +use Koha::Exception::BadParameter; + +=head NAME t::lib::Page::Circulation::Circulation + +=head SYNOPSIS + +circulation.pl PageObject providing page functionality as a service! + +=cut + +=head new + + my $circulation = t::lib::Page::Circulation::Circulation->new({borrowernumber => "1"}); + +Instantiates a WebDriver and loads the circ/circulation.pl. +@PARAM1 HASHRef of optional and MANDATORY parameters +MANDATORY extra parameters: + borrowernumber => loads the page to display Borrower matching the given borrowernumber + +@RETURNS t::lib::Page::Circulation::Circulation, ready for user actions! +=cut + +sub new { + my ($class, $params) = @_; + unless (ref($params) eq 'HASH' || (blessed($params) && $params->isa('t::lib::Page') )) { + $params = {}; + } + $params->{resource} = '/cgi-bin/koha/circ/circulation.pl'; + $params->{type} = 'staff'; + + $params->{getParams} = []; + #Handle MANDATORY parameters + if ($params->{borrowernumber}) { + push @{$params->{getParams}}, "borrowernumber=".$params->{borrowernumber}; + } + else { + Koha::Exception::BadParameter->throw(error => __PACKAGE__."->new():> Parameter 'borrowernumber' is missing."); + } + + my $self = $class->SUPER::new($params); + + return $self; +} + +################################################################################ +=head UI Mapping helper subroutines +See. Selenium documentation best practices for UI element mapping to common language descriptions. +=cut +################################################################################ + +1; \ No newline at end of file diff --git a/t/lib/Page/Intra.pm b/t/lib/Page/Intra.pm new file mode 100644 index 0000000..5f472d3 --- /dev/null +++ b/t/lib/Page/Intra.pm @@ -0,0 +1,228 @@ +package t::lib::Page::Intra; + +# Copyright 2015 Open Source Freedom Fighters +# +# 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 . + +use Modern::Perl; +use Test::More; + +use C4::Context; + +use t::lib::WebDriverFactory; + +use Koha::Exception::BadParameter; +use Koha::Exception::SystemCall; + +use base qw(t::lib::Page); + +=head NAME t::lib::Page::Intra + +=head SYNOPSIS + +PageObject-pattern parent class for Intranet-pages (staff client). Extend this to implement specific pages shown to our users. + +=cut + +################################################################################ +=head UI Mapping helper subroutines +See. Selenium documentation best practices for UI element mapping to common language descriptions. +=cut +################################################################################ + +=head _getBreadcrumbLinks + +@RETURNS List of all breadcrumb links +=cut + +sub _getBreadcrumbLinks { + my ($self) = @_; + my $d = $self->getDriver(); + + my $breadcrumbLinks = $d->find_elements("div#breadcrumbs a"); + return ($breadcrumbLinks); +} + +=head _getHeaderElements + +@RETURNS HASHRef of all the Intranet header clickables. +=cut + +sub _getHeaderElements { + my ($self) = @_; + my $d = $self->getDriver(); + + my ($patronsA, $searchA, $cartA, $moreA, $drop3A, $helpA); + #Always visible elements + $patronsA = $d->find_element("#header a[href*='members-home.pl']"); + $searchA = $d->find_element ("#header a[href*='search.pl']"); + $cartA = $d->find_element ("#header a#cartmenulink"); + $moreA = $d->find_element ("#header a[href='#']"); + $drop3A = $d->find_element ("#header a#drop3"); + $helpA = $d->find_element ("#header a#helper"); + + my $e = {}; + $e->{patrons} = $patronsA if $patronsA; + $e->{search} = $searchA if $searchA; + $e->{cart} = $cartA if $cartA; + $e->{more} = $moreA if $moreA; + $e->{drop3} = $drop3A if $drop3A; + $e->{help} = $helpA if $helpA; + return $e; +} + +=head _getPasswordLoginElements + +@RETURNS List of Selenium::Remote::Webelement-objects, + ($submitButton, $useridInput, $passwordInput) +=cut + +sub _getPasswordLoginElements { + my ($self) = @_; + my $d = $self->getDriver(); + + my $submitButton = $d->find_element('#submit'); + my $useridInput = $d->find_element('#userid'); + my $passwordInput = $d->find_element('#password'); + return ($submitButton, $useridInput, $passwordInput); +} + +=head _getLoggedInBranchNameElement +@RETURNS Selenium::Remote::WebElement matching the containing the currently logged in users branchname +=cut + +sub _getLoggedInBranchNameElement { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $header = $self->_getHeaderElements(); + my $loggedInBranchNameSpan = $d->find_child_element($header->{drop3}, "#logged-in-branch-name", 'css'); + return $loggedInBranchNameSpan; +} + +=head _getLoggedInBranchCode +@RETURNS String, the logged in branch code +=cut + +sub _getLoggedInBranchCode { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + #Because the branchcode element is hidden, we need to inject some javascript to get its value since Selenium (t$ + my $script = q{ + var elem = document.getElementById('logged-in-branch-code').innerHTML; + var callback = arguments[arguments.length-1]; + callback(elem); + }; + my $loggedInBranchCode = $d->execute_async_script($script); + return $loggedInBranchCode; +} + +################################################################################ +=head PageObject Services + +=cut +################################################################################ + +=head isPasswordLoginAvailable + + $page->isPasswordLoginAvailable(); + +@RETURN t::lib::Page-object +@CROAK if password login is unavailable. +=cut + +sub isPasswordLoginAvailable { + my $self = shift; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + $self->_getPasswordLoginElements(); + ok(($d->get_title() =~ /Log in to Koha/), "Intra PasswordLogin available"); + return $self; +} + +sub doPasswordLogin { + my ($self, $username, $password) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my ($submitButton, $useridInput, $passwordInput) = $self->_getPasswordLoginElements(); + $useridInput->send_keys($username); + $passwordInput->send_keys($password); + $submitButton->click(); + $self->debugTakeSessionSnapshot(); + + my $cookies = $d->get_all_cookies(); + my @cgisessid = grep {$_->{name} eq 'CGISESSID'} @$cookies; + + ok(($d->get_title() !~ /Log in to Koha/ && #No longer in the login page + $d->get_title() !~ /Access denied/ && + $cgisessid[0]) #Cookie CGISESSID defined! + , "Intra PasswordLogin succeeded"); + + return $self; #After a succesfull password login, we are directed to the same page we tried to access. +} + +sub failPasswordLogin { + my ($self, $username, $password) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my ($submitButton, $useridInput, $passwordInput) = $self->_getPasswordLoginElements(); + $useridInput->send_keys($username); + $passwordInput->send_keys($password); + $submitButton->click(); + $self->debugTakeSessionSnapshot(); + + my $cookies = $d->get_all_cookies(); + my @cgisessid = grep {$_->{name} eq 'CGISESSID'} @$cookies; + + ok($d->get_title() =~ /Log in to Koha/ #Still in the login page + , "Intra PasswordLogin failed"); + + return $self; #After a successful password login, we are directed to the same page we tried to access. +} + +sub doPasswordLogout { + my ($self, $username, $password) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + #Click the dropdown menu to make the logout-link visible + my $logged_in_identifierA = $d->find_element('#drop3'); #What a nice and descriptive HTML element name! + $logged_in_identifierA->click(); + + #Logout + my $logoutA = $d->find_element('#logout'); + $logoutA->click(); + $self->debugTakeSessionSnapshot(); + + ok(($d->get_title() =~ /Log in to Koha/), "Intra PasswordLogout succeeded"); + return $self; #After a succesfull password logout, we are still in the same page we did before logout. +} + +sub isLoggedInBranchCode { + my ($self, $expectedBranchCode) = @_; + + my $loggedInBranchCode = $self->_getLoggedInBranchCode(); + is($expectedBranchCode, $loggedInBranchCode, "#logged-in-branch-code '".$loggedInBranchCode."' matches '$expectedBranchCode'"); + return $self; +} + +1; #Make the compiler happy! diff --git a/t/lib/Page/Mainpage.pm b/t/lib/Page/Mainpage.pm new file mode 100644 index 0000000..99efafb --- /dev/null +++ b/t/lib/Page/Mainpage.pm @@ -0,0 +1,64 @@ +package t::lib::Page::Mainpage; + +# Copyright 2015 Open Source Freedom Fighters +# +# 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 . + +use Modern::Perl; + +use base qw(t::lib::Page::Intra); + +=head NAME t::lib::Page::Mainpage + +=head SYNOPSIS + +Mainpage PageObject providing page functionality as a service! + +=cut + +sub new { + my ($class, $params) = @_; + unless (ref($params) eq 'HASH') { + $params = {}; + } + $params->{resource} = '/cgi-bin/koha/mainpage.pl'; + $params->{type} = 'staff'; + my $self = $class->SUPER::new($params); + + return $self; +} + +################################################################################ +=head UI Mapping helper subroutines +See. Selenium documentation best practices for UI element mapping to common language descriptions. +=cut +################################################################################ + + + + + +################################################################################ +=head PageObject Services + +=cut +################################################################################ + + + + + +1; #Make the compiler happy! \ No newline at end of file diff --git a/t/lib/Page/Members/ApiKeys.pm b/t/lib/Page/Members/ApiKeys.pm new file mode 100644 index 0000000..070cc08 --- /dev/null +++ b/t/lib/Page/Members/ApiKeys.pm @@ -0,0 +1,200 @@ +package t::lib::Page::Members::ApiKeys; + +# Copyright 2015 KohaSuomi! +# +# 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 . + +use Modern::Perl; +use Scalar::Util qw(blessed); +use Test::More; + +use base qw(t::lib::Page::Intra t::lib::Page::Members::Toolbar); + +use Koha::Exception::BadParameter; +use Koha::Exception::UnknownObject; + +=head NAME t::lib::Page::Members::ApiKeys + +=head SYNOPSIS + +apikeys.pl PageObject providing page functionality as a service! + +=cut + +=head new + + my $apikeys = t::lib::Page::Members::ApiKeys->new({borrowernumber => "1"}); + +Instantiates a WebDriver and loads the members/apikeys.pl. +@PARAM1 HASHRef of optional and MANDATORY parameters +MANDATORY extra parameters: + borrowernumber => loads the page to display Borrower matching the given borrowernumber + +@RETURNS t::lib::Page::Members::ApiKeys, ready for user actions! +=cut + +sub new { + my ($class, $params) = @_; + unless (ref($params) eq 'HASH' || (blessed($params) && $params->isa('t::lib::Page') )) { + $params = {}; + } + $params->{resource} = '/cgi-bin/koha/members/apikeys.pl'; + $params->{type} = 'staff'; + + $params->{getParams} = []; + #Handle MANDATORY parameters + if ($params->{borrowernumber}) { + push @{$params->{getParams}}, "borrowernumber=".$params->{borrowernumber}; + } + else { + Koha::Exception::BadParameter->throw(error => __PACKAGE__."->new():> Parameter 'borrowernumber' is missing."); + } + + my $self = $class->SUPER::new($params); + + return $self; +} + +################################################################################ +=head UI Mapping helper subroutines +See. Selenium documentation best practices for UI element mapping to common language descriptions. +=cut +################################################################################ + +=head _getActionsAndTableElements +@RETURNS List of + HASHRef of Selenium::Driver::Webelement-objects matching the generic + actions on this page, eg. 'generateNewKey'. + HASHRef of Selenium::Driver::Webelement-objects keyed with the apiKey hash/text. + These are all the apiKey table rows present, and have the + elements prefetched for easy access. + +=cut + +sub _getActionsAndTableElements { + my ($self) = @_; + my $d = $self->getDriver(); + + my $generateNewKeySubmit = $d->find_element("#generatenewkey", 'css'); + + my $a = {}; #Collect action elements here + $a->{generateNewKey} = $generateNewKeySubmit; #Bind the global action here for easy reference. + + my $apiKeyRows; + eval { #We might not have ApiKeys yet. + $apiKeyRows = $d->find_elements("#apikeystable tr", 'css'); + shift @$apiKeyRows; #Remove the table header row + }; + my %apiKeys; + for(my $i=0 ; $i[$i]; + $row->{'nth-of-type'} = $i+1; #starts from 1 + $row->{key} = $d->find_child_element($row, "td.apikeykey", 'css')->get_text(); + $row->{active} = $d->find_child_element($row, "td.apikeyactive", 'css')->get_text(); + $row->{lastTransaction} = $d->find_child_element($row, "td.apikeylastransaction", 'css')->get_text(); + $row->{delete} = $d->find_child_element($row, "input.apikeydelete", 'css'); + eval { + $row->{revoke} = $d->find_child_element($row, "input.apikeyrevoke", 'css'); + }; + eval { + $row->{activate} = $d->find_child_element($row, "input.apikeyactivate", 'css'); + }; + $apiKeys{$row->{key}} = $row; + } + + return ($a, \%apiKeys); +} + + + +################################################################################ +=head PageObject Services + +=cut +################################################################################ + +sub generateNewApiKey { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my ($actionElements, $apiKeyRows) = $self->_getActionsAndTableElements(); + my $apiKeyRowsCountPre = (ref $apiKeyRows eq 'HASH') ? scalar(keys(%$apiKeyRows)) : 0; + $actionElements->{generateNewKey}->click(); + $self->debugTakeSessionSnapshot(); + + ($actionElements, $apiKeyRows) = $self->_getActionsAndTableElements(); + my $apiKeyRowsCountPost = (ref $apiKeyRows eq 'HASH') ? scalar(keys(%$apiKeyRows)) : 0; + is($apiKeyRowsCountPre+1, $apiKeyRowsCountPost, "ApiKey generated"); + return $self; +} + +sub revokeApiKey { + my ($self, $apiKey) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my ($actionElements, $apiKeyRows) = $self->_getActionsAndTableElements(); + my $apiKeyRow = $apiKeyRows->{$apiKey}; + Koha::Exception::UnknownObject->throw(error => __PACKAGE__."revokeApiKey():> No matching apiKey '$apiKey' found.") unless $apiKeyRow; + $apiKeyRow->{revoke}->click(); + $self->debugTakeSessionSnapshot(); + + ($actionElements, $apiKeyRows) = $self->_getActionsAndTableElements(); + $apiKeyRow = $apiKeyRows->{$apiKey}; + Koha::Exception::UnknownObject->throw(error => __PACKAGE__."revokeApiKey():> No matching apiKey '$apiKey' found after revoking it.") unless $apiKeyRow; + is($apiKeyRow->{active}, 'No', "ApiKey revoked"); + return $self; +} + +sub activateApiKey { + my ($self, $apiKey) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my ($actionElements, $apiKeyRows) = $self->_getActionsAndTableElements(); + my $apiKeyRow = $apiKeyRows->{$apiKey}; + Koha::Exception::UnknownObject->throw(error => __PACKAGE__."revokeApiKey():> No matching apiKey '$apiKey' found.") unless $apiKeyRow; + $apiKeyRow->{activate}->click(); + $self->debugTakeSessionSnapshot(); + + ($actionElements, $apiKeyRows) = $self->_getActionsAndTableElements(); + $apiKeyRow = $apiKeyRows->{$apiKey}; + Koha::Exception::UnknownObject->throw(error => __PACKAGE__."revokeApiKey():> No matching apiKey '$apiKey' found after activating it.") unless $apiKeyRow; + is($apiKeyRow->{active}, 'Yes', "ApiKey activated"); + return $self; +} + +sub deleteApiKey { + my ($self, $apiKey) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my ($actionElements, $apiKeyRows) = $self->_getActionsAndTableElements(); + my $apiKeyRowsCountPre = (ref $apiKeyRows eq 'HASH') ? scalar(keys(%$apiKeyRows)) : 0; + my $apiKeyRow = $apiKeyRows->{$apiKey}; + Koha::Exception::UnknownObject->throw(error => __PACKAGE__."revokeApiKey():> No matching apiKey '$apiKey' found.") unless $apiKeyRow; + $apiKeyRow->{delete}->click(); + $self->debugTakeSessionSnapshot(); + + ($actionElements, $apiKeyRows) = $self->_getActionsAndTableElements(); + my $apiKeyRowsCountPost = (ref $apiKeyRows eq 'HASH') ? scalar(keys(%$apiKeyRows)) : 0; + is($apiKeyRowsCountPre-1, $apiKeyRowsCountPost, "ApiKey deleted"); + return $self; +} + +1; #Make the compiler happy! diff --git a/t/lib/Page/Members/Boraccount.pm b/t/lib/Page/Members/Boraccount.pm new file mode 100644 index 0000000..f777436 --- /dev/null +++ b/t/lib/Page/Members/Boraccount.pm @@ -0,0 +1,164 @@ +package t::lib::Page::Members::Boraccount; + +# Copyright 2015 Open Source Freedom Fighters +# +# 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 . + +use Modern::Perl; +use Scalar::Util qw(blessed); +use Test::More; + +use base qw(t::lib::Page::Intra t::lib::Page::Members::Toolbar t::lib::Page::Members::LeftNavigation); + +use t::lib::Page::Members::ApiKeys; + +use Koha::Exception::BadParameter; + +=head NAME t::lib::Page::Members::Boraccount + +=head SYNOPSIS + +boraccount.pl PageObject providing page functionality as a service! + +=cut + +=head new + + my $boraccount = t::lib::Page::Members::Boraccount->new({borrowernumber => "1"}); + +Instantiates a WebDriver and loads the members/boraccount.pl. +@PARAM1 HASHRef of optional and MANDATORY parameters +MANDATORY extra parameters: + borrowernumber => loads the page to display Borrower matching the given borrowernumber + +@RETURNS t::lib::Page::Members::Boraccount, ready for user actions! +=cut + +sub new { + my ($class, $params) = @_; + unless (ref($params) eq 'HASH' || (blessed($params) && $params->isa('t::lib::Page') )) { + $params = {}; + } + $params->{resource} = '/cgi-bin/koha/members/boraccount.pl'; + $params->{type} = 'staff'; + + $params->{getParams} = []; + #Handle MANDATORY parameters + if ($params->{borrowernumber}) { + push @{$params->{getParams}}, "borrowernumber=".$params->{borrowernumber}; + } + else { + Koha::Exception::BadParameter->throw(error => __PACKAGE__."->new():> Parameter 'borrowernumber' is missing."); + } + + my $self = $class->SUPER::new($params); + + return $self; +} + +################################################################################ +=head UI Mapping helper subroutines +See. Selenium documentation best practices for UI element mapping to common language descriptions. +=cut +################################################################################ + +sub navigateToPayFinesTab { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $tab = $d->find_element("div.statictabs ul li a[href*='pay.pl?borrowernumber=']", 'css'); + $tab->click(); + ok($d->get_title() =~ m/Pay Fines for/, "Intra Navigate to Pay fines tab"); + + $self->debugTakeSessionSnapshot(); + + return t::lib::Page::Members::Pay->rebrandFromPageObject($self); +} + +sub findFine { + my ($self, $note) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $payment_note = $d->find_element("//td[text() = \"".$note."\"]", "xpath"); + + ok(1, "Intra Found payment with note ".$note); + + return $self; +} + +sub isFinePaid { + my ($self, $note, $noteColumnIndex, $amountOutstandingColumnIndex) = @_; + + return isFineAmountOutstanding($self, $note, "0.00", $noteColumnIndex, $amountOutstandingColumnIndex); +} + +sub isFineAmount { + my ($self, $note, $amount, $noteColumnIndex, $amountColumnIndex) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + $noteColumnIndex = 3 if not defined $noteColumnIndex; + $amountColumnIndex = 4 if not defined $amountColumnIndex; + + # If POS integration is enabled, add 1 to index (because of column "transaction id") + if (C4::Context->preference("POSIntegration") ne "OFF") { + $noteColumnIndex++; + $amountColumnIndex++; + } + + my $fine = $d->find_element("//tr[(td[".$noteColumnIndex."][text()=\"".$note."\"]) and (td[".$amountColumnIndex."][text()=\"".$amount."\"])]", "xpath"); + + ok(1, "Intra Found payment with note ".$note." and amount ".$amount); + + return $self; +} + +sub isFineAmountOutstanding { + my ($self, $note, $amountOutstanding, $noteColumnIndex, $amountOutstandingColumnIndex) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + $noteColumnIndex = 3 if not defined $noteColumnIndex; + $amountOutstandingColumnIndex = 5 if not defined $amountOutstandingColumnIndex; + + # If POS integration is enabled, add 1 to index (because of column "transaction id") + if (C4::Context->preference("POSIntegration") ne "OFF") { + $noteColumnIndex++; + $amountOutstandingColumnIndex++; + } + + my $fine = $d->find_element("//tr[(td[".$noteColumnIndex."][text()=\"".$note."\"]) and (td[".$amountOutstandingColumnIndex."][text()=\"".$amountOutstanding."\"])]", "xpath"); + + ok(1, "Intra Found payment with note ".$note." and amountoutstanding ".$amountOutstanding); + + return $self; +} + +sub isTransactionComplete { + my ($self, $transactionnumber) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $transaction_columns = $d->find_element("//td[contains(\@class, 'transactionnumber') and text() = '".$transactionnumber."']", "xpath"); + + ok(1, "Intra Found transaction, number ".$transactionnumber); + + return $self; +} + +1; #Make the compiler happy! diff --git a/t/lib/Page/Members/LeftNavigation.pm b/t/lib/Page/Members/LeftNavigation.pm new file mode 100644 index 0000000..30473c5 --- /dev/null +++ b/t/lib/Page/Members/LeftNavigation.pm @@ -0,0 +1,138 @@ +package t::lib::Page::Members::LeftNavigation; + +# Copyright 2015 Open Source Freedom Fighters +# +# 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 . + +use Modern::Perl; +use Scalar::Util qw(blessed); +use Test::More; + +=head NAME t::lib::Page::Members::LeftNavigation + +=head SYNOPSIS + +Provides the services of the members/circulation left navigation column/frame for the implementing PageObject + +=cut + +################################################################################ +=head UI Mapping helper subroutines +See. Selenium documentation best practices for UI element mapping to common language descriptions. +=cut +################################################################################ + +=head _getLeftNavigationActionElements +@RETURNS HASHRef of Selenium::Driver::Webelements matching all the clickable elements + in the left navigation frame/column at members and circulation pages. +=cut + +sub _getLeftNavigationActionElements { + my ($self) = @_; + my $d = $self->getDriver(); + + my $e = {}; + eval { + $e->{checkOut} = $d->find_element("div#menu a[href*='circ/circulation.pl']", 'css'); + }; + eval { + $e->{details} = $d->find_element("div#menu a[href*='members/moremember.pl']", 'css'); + }; + eval { + $e->{fines} = $d->find_element("div#menu a[href*='members/pay.pl']", 'css'); + }; + eval { + $e->{routingLists} = $d->find_element("div#menu a[href*='members/routing-lists.pl']", 'css'); + }; + eval { + $e->{circulationHistory} = $d->find_element("div#menu a[href*='members/readingrec.pl']", 'css'); + }; + eval { + $e->{modificationLog} = $d->find_element("div#menu a[href*='tools/viewlog.pl']", 'css'); + }; + eval { + $e->{notices} = $d->find_element("div#menu a[href*='members/notices.pl']", 'css'); + }; + eval { + $e->{statistics} = $d->find_element("div#menu a[href*='members/statistics.pl']", 'css'); + }; + eval { + $e->{purchaseSuggestions} = $d->find_element("div#menu a[href*='members/purchase-suggestions.pl']", 'css'); + }; + return $e; +} + + + +################################################################################ +=head PageObject Services + +=cut +################################################################################ + +sub navigateCheckout { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $elements = $self->_getLeftNavigationActionElements(); + $elements->{checkOut}->click(); + $self->debugTakeSessionSnapshot(); + + ok($d->get_title() =~ m/Checking out to/i, + "Intra Navigate to Check out"); + + return t::lib::Page::Circulation::Circulation->rebrandFromPageObject($self); +} + +sub navigateToDetails { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $elements = $self->_getLeftNavigationActionElements(); + $elements->{details}->click(); + $self->debugTakeSessionSnapshot(); + + ok($d->get_title() =~ m/Patron details for/i, + "Intra Navigate to Details"); + + return t::lib::Page::Members::Moremember->rebrandFromPageObject($self); +} + +sub navigateToNotices { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $elements = $self->_getLeftNavigationActionElements(); + my $func = sub { + $elements->{notices}->click(); + }; + my $success = sub { + return $self->getDriver()->get_title() =~ m/Sent notices/; + }; + + $self->poll($func, $success, 20, 50); + $self->debugTakeSessionSnapshot(); + + ok($d->get_title() =~ m/Sent notices/i, + "Intra Navigate to Notices"); + + return t::lib::Page::Members::Notices->rebrandFromPageObject($self); +} + +1; #Make the compiler happy! diff --git a/t/lib/Page/Members/MemberFlags.pm b/t/lib/Page/Members/MemberFlags.pm new file mode 100644 index 0000000..3fc93a5 --- /dev/null +++ b/t/lib/Page/Members/MemberFlags.pm @@ -0,0 +1,143 @@ +package t::lib::Page::Members::MemberFlags; + +# Copyright 2015 Open Source Freedom Fighters +# +# 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 . + +use Modern::Perl; +use Test::More; + +use t::lib::Page::Members::Moremember; + +use base qw(t::lib::Page::Intra t::lib::Page::Members::Toolbar); + +=head NAME t::lib::Page::Members::MemberFlags + +=head SYNOPSIS + +member-flags.pl PageObject providing page functionality as a service! + +=cut + +=head new + + my $memberflags = t::lib::Page::Members::MemberFlags->new({borrowernumber => "1"}); + +Instantiates a WebDriver and loads the members/member-flags.pl. +@PARAM1 HASHRef of optional and MANDATORY parameters +MANDATORY extra parameters: + borrowernumber => loads the page to display Borrower matching the given borrowernumber + +@RETURNS t::lib::Page::Members::MemberFlags, ready for user actions! +=cut + +sub new { + my ($class, $params) = @_; + unless (ref($params) eq 'HASH') { + $params = {}; + } + $params->{resource} = '/cgi-bin/koha/members/member-flags.pl'; + $params->{type} = 'staff'; + + $params->{getParams} = []; + #Handle MANDATORY parameters + if ($params->{borrowernumber}) { + push @{$params->{getParams}}, "member=".$params->{borrowernumber}; + } + else { + Koha::Exception::BadParameter->throw(error => __PACKAGE__."->new():> Parameter 'borrowernumber' is missing."); + } + + my $self = $class->SUPER::new($params); + + return $self; +} + +################################################################################ +=head UI Mapping helper subroutines +See. Selenium documentation best practices for UI element mapping to common language descriptions. +=cut +################################################################################ + +sub _getPermissionTreeControlElements { + my ($self) = @_; + my $d = $self->getDriver(); + + my $saveButton = $d->find_element('input[value="Save"]'); + my $cancelButton = $d->find_element('a.cancel'); + return ($saveButton, $cancelButton); +} + +=head _getPermissionTreePermissionElements + +@PARAM1 Scalar, Koha::Auth::PermissionModule's module +@PARAM2 Scalar, Koha::Auth::Permission's code +=cut + +sub _getPermissionTreePermissionElements { + my ($self, $module, $code) = @_; + my $d = $self->getDriver(); + + my $moduleTreeExpansionButton = $d->find_element("div.$module-hitarea"); + my $moduleCheckbox = $d->find_element("input#flag-$module"); + my $permissionCheckbox = $d->find_element('input#'.$module.'_'.$code); + return ($moduleTreeExpansionButton, $moduleCheckbox, $permissionCheckbox); +} + + + +################################################################################ +=head PageObject Services + +=cut +################################################################################ + +sub togglePermission { + my ($self, $permissionModule, $permissionCode) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my ($moduleTreeExpansionButton, $moduleCheckbox, $permissionCheckbox) = $self->_getPermissionTreePermissionElements($permissionModule, $permissionCode); + if ($moduleTreeExpansionButton->get_attribute("class") =~ /expandable-hitarea/) { #Permission checkboxes are hidden and need to be shown. + $moduleTreeExpansionButton->click(); + $d->pause( $self->{userInteractionDelay} ); + } + + + #$moduleCheckbox->click(); #Clicking this will toggle all module permissions. + my $checked = $permissionCheckbox->get_attribute("checked") || ''; #Returns undef if not checked + $permissionCheckbox->click(); + ok($checked ne ($permissionCheckbox->get_attribute("checked") || ''), + "Module '$permissionModule', permission '$permissionCode', checkbox toggled"); + $self->debugTakeSessionSnapshot(); + + return $self; +} + +sub submitPermissionTree { + my $self = shift; + my $d = $self->getDriver(); + + my ($submitButton, $cancelButton) = $self->_getPermissionTreeControlElements(); + $submitButton->click(); + $self->debugTakeSessionSnapshot(); + + ok(($d->get_title() =~ /Patron details for/), "Permissions set"); + + return t::lib::Page::Members::Moremember->rebrandFromPageObject($self); +} + +1; #Make the compiler happy! diff --git a/t/lib/Page/Members/Memberentry.pm b/t/lib/Page/Members/Memberentry.pm new file mode 100644 index 0000000..1e3b222 --- /dev/null +++ b/t/lib/Page/Members/Memberentry.pm @@ -0,0 +1,265 @@ +package t::lib::Page::Members::Memberentry; + +# Copyright 2015 Open Source Freedom Fighters +# +# 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 . + +use Modern::Perl; +use Scalar::Util qw(blessed); +use Test::More; + +use base qw(t::lib::Page::Intra t::lib::Page::Members::Toolbar t::lib::Page::Members::LeftNavigation); + +use Koha::Exception::BadParameter; + +use t::lib::Page::Circulation::Circulation; + + +sub new { + my ($class, $params) = @_; + unless (ref($params) eq 'HASH' || (blessed($params) && $params->isa('t::lib::Page') )) { + $params = {}; + } + $params->{resource} = '/cgi-bin/koha/members/memberentry.pl'; + $params->{type} = 'staff'; + + $params->{getParams} = []; + #Handle MANDATORY parameters + if ($params->{borrowernumber}) { + push @{$params->{getParams}}, "borrowernumber=".$params->{borrowernumber}; + } + else { + Koha::Exception::BadParameter->throw(error => __PACKAGE__."->new():> Parameter 'borrowernumber' is missing."); + } + push @{$params->{getParams}}, "destination=".$params->{destination} if $params->{destination}; + push @{$params->{getParams}}, "op=".$params->{op} if $params->{op}; + push @{$params->{getParams}}, "categorycode=".$params->{categorycode} if $params->{categorycode}; + my $self = $class->SUPER::new($params); + + return $self; +} + +################################################################################ +=head UI Mapping helper subroutines +See. Selenium documentation best practices for UI element mapping to common language descriptions. +=cut +################################################################################ + + +sub _getInputFieldsForValidation { + my ($self) = @_; + my $d = $self->getDriver(); + + my ($emailInput, $emailProInput, $email_BInput, $phoneInput, $phoneProInput, $phone_BInput, $SMSnumber); + eval { + $emailInput = $d->find_element('#email'); + }; + eval { + $emailProInput = $d->find_element('#emailpro'); + }; + eval { + $email_BInput = $d->find_element('#B_email'); + }; + eval { + $phoneInput = $d->find_element('#phone'); + }; + eval { + $phoneProInput = $d->find_element('#phonepro'); + }; + eval { + $phone_BInput = $d->find_element('#B_phone'); + }; + eval { + $SMSnumber = $d->find_element('#SMSnumber'); + }; + + return ($emailInput, $emailProInput, $email_BInput, $phoneInput, $phoneProInput, $phone_BInput, $SMSnumber); +} + +sub _getMessagingPreferenceCheckboxes { + my ($self) = @_; + my $d = $self->getDriver(); + + my @email_prefs = $d->find_elements('input[type="checkbox"][id^="email"]'); + my @phone_prefs = $d->find_elements('input[type="checkbox"][id^="phone"]'); + my @sms_prefs = $d->find_elements('input[type="checkbox"][id^="sms"]'); + + return { email => \@email_prefs, phone => \@phone_prefs, sms => \@sms_prefs }; +} + + +################################################################################ +=head PageObject Services + +=cut +################################################################################ + +sub checkMessagingPreferencesSet { + my ($self, $valid, @prefs) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + foreach my $type (@prefs){ + my @this_pref = $d->find_elements('input[type="checkbox"][id^="'.$type.'"]'); + + my $ok = 0; + + foreach my $checkbox (@this_pref){ + ok(0, "Intra Memberentry $type messaging checkbox ".$checkbox->get_attribute('id')." not checked") if !$checkbox->is_selected() and $valid; + ok(0, "Intra Memberentry $type messaging checkbox ".$checkbox->get_attribute('id')." checked (not supposed to be)") if $checkbox->is_selected() and !$valid; + $ok = 1; + } + ok($ok, "Intra Memberentry $type messaging checkboxes ok (all " . (($valid) ? 'checked':'unchecked') . ")"); + } + + return $self; +} + +sub clearMessagingContactFields { + my ($self, @fields) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my ($emailInput, $emailProInput, $email_BInput, $phoneInput, $phoneProInput, $phone_BInput, $SMSnumber) = $self->_getInputFieldsForValidation(); + + if (@fields) { + for my $field (@fields){ + $emailInput->clear() if $field eq "email"; + $emailProInput->clear() if $field eq "emailpro"; + $email_BInput->clear() if $field eq "B_email"; + $phoneInput->clear() if $field eq "phone"; + $phoneProInput->clear() if $field eq "phonepro"; + $phone_BInput->clear() if $field eq "B_phone"; + $SMSnumber->clear() if $field eq "SMSnumber"; + } + ok(1, "Intra Memberentry contact fields (@fields) cleared"); + } else { + $emailInput->clear(); + $emailProInput->clear(); + $email_BInput->clear(); + $phoneInput->clear(); + $phoneProInput->clear(); + $phone_BInput->clear(); + $SMSnumber->clear(); + ok(1, "Intra Memberentry contact fields (email, emailpro, email_B, phone, phonepro, phone_B, SMSnumber) cleared"); + } + + return $self; +} + +sub checkPreferences { + my ($self, $valid, $type) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $checkboxes = $self->_getMessagingPreferenceCheckboxes(); + + ok (0, "Intra $type checkboxes not defined") if not defined $checkboxes->{$type}; + return 0 if not defined $checkboxes->{$type}; + + foreach my $checkbox (@{ $checkboxes->{$type} }){ + ok(0, "Intra Memberentry $type messaging checkbox ".$checkbox->get_attribute('id')." not available") if !$checkbox->is_enabled() and $valid; + ok(0, "Intra Memberentry $type messaging checkbox ".$checkbox->get_attribute('id')." available (not supposed to be)") if $checkbox->is_enabled() and !$valid; + + $checkbox->click() if not $checkbox->is_selected(); + } + ok (1, "Intra Memberentry $type messaging checkboxes checked") if $valid; + + return $self; +} +sub setEmail { + my ($self, $input) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my ($emailInput, $emailProInput, $email_BInput, $phoneInput, $phoneProInput, $phone_BInput, $SMSnumber) = $self->_getInputFieldsForValidation(); + + $emailInput->clear(); + $emailProInput->clear(); + $email_BInput->clear(); + $emailInput->send_keys($input); + $emailProInput->send_keys($input); + $email_BInput->send_keys($input); + ok(1, "Intra Memberentry Wrote \"$input\" to email fields."); + + return $self; +} + +sub setPhone { + my ($self, $input) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my ($emailInput, $emailProInput, $email_BInput, $phoneInput, $phoneProInput, $phone_BInput, $SMSnumber) = $self->_getInputFieldsForValidation(); + + $phoneInput->clear(); + $phoneProInput->clear(); + $phone_BInput->clear(); + $phoneInput->send_keys($input); + $phoneProInput->send_keys($input); + $phone_BInput->send_keys($input); + ok(1, "Intra Memberentry Wrote \"$input\" to phone fields."); + + return $self; +} + +sub setSMSNumber { + my ($self, $input) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my ($emailInput, $emailProInput, $email_BInput, $phoneInput, $phoneProInput, $phone_BInput, $SMSnumber) = $self->_getInputFieldsForValidation(); + + $SMSnumber->clear(); + $SMSnumber->send_keys($input); + ok(1, "Intra Memberentry Wrote \"$input\" to SMSnumber."); + + return $self; +} + +sub submitForm { + my ($self, $valid) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + eval { + my $holdsIdentifier = $d->find_element('#othernames'); + $holdsIdentifier->click(); + }; + my $submitButton = $d->find_element('#saverecord'); + $submitButton->click(); + $self->debugTakeSessionSnapshot(); + + if ($valid) { + my $submitted = $d->find_element("#editpatron", 'css'); + ok(1, "Intra Memberentry Submit changes success"); + return t::lib::Page::Circulation::Circulation->rebrandFromPageObject($self); + } else { + my @notsubmitted = $d->find_elements('form#entryform label[class="error"]', 'css'); + my $error_ids = ""; + + foreach my $el_id (@notsubmitted){ + my $attr_id = $el_id->get_attribute("for"); + $error_ids .= "'".$attr_id . "' "; + } + + ok(1, "Intra Memberentry Submit changes ". $error_ids .", validation error (as expected)."); + $d->refresh(); + return $self; + } +} + +1; diff --git a/t/lib/Page/Members/Moremember.pm b/t/lib/Page/Members/Moremember.pm new file mode 100644 index 0000000..c55e5d7 --- /dev/null +++ b/t/lib/Page/Members/Moremember.pm @@ -0,0 +1,207 @@ +package t::lib::Page::Members::Moremember; + +# Copyright 2015 Open Source Freedom Fighters +# +# 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 . + +use Modern::Perl; +use Scalar::Util qw(blessed); +use Test::More; + +use base qw(t::lib::Page::Intra t::lib::Page::Members::Toolbar t::lib::Page::Members::LeftNavigation); + +use t::lib::Page::Members::ApiKeys; + +use Koha::Exception::BadParameter; + +=head NAME t::lib::Page::Members::Moremember + +=head SYNOPSIS + +moremember.pl PageObject providing page functionality as a service! + +=cut + +=head new + + my $moremember = t::lib::Page::Members::Moremember->new({borrowernumber => "1"}); + +Instantiates a WebDriver and loads the members/moremember.pl. +@PARAM1 HASHRef of optional and MANDATORY parameters +MANDATORY extra parameters: + borrowernumber => loads the page to display Borrower matching the given borrowernumber + +@RETURNS t::lib::Page::Members::Moremember, ready for user actions! +=cut + +sub new { + my ($class, $params) = @_; + unless (ref($params) eq 'HASH' || (blessed($params) && $params->isa('t::lib::Page') )) { + $params = {}; + } + $params->{resource} = '/cgi-bin/koha/members/moremember.pl'; + $params->{type} = 'staff'; + + $params->{getParams} = []; + #Handle MANDATORY parameters + if ($params->{borrowernumber}) { + push @{$params->{getParams}}, "borrowernumber=".$params->{borrowernumber}; + } + else { + Koha::Exception::BadParameter->throw(error => __PACKAGE__."->new():> Parameter 'borrowernumber' is missing."); + } + + my $self = $class->SUPER::new($params); + + return $self; +} + +################################################################################ +=head UI Mapping helper subroutines +See. Selenium documentation best practices for UI element mapping to common language descriptions. +=cut +################################################################################ + +sub _getEditLinks { + my ($self) = @_; + my $d = $self->getDriver(); + + my $patron_info_edit = $d->find_element("div#patron-information div.action a[href*='memberentry.pl?op=modify'][href*='step=1']", 'css'); + my $sms_number_edit = $d->find_element("div.action a[href*='memberentry.pl?op=modify'][href*='step=5']", 'css'); + my $library_use_edit = $d->find_element("div.action a[href*='memberentry.pl?op=modify'][href*='step=3']", 'css'); + my $alternate_addr_edit = $d->find_element("div.action a[href*='memberentry.pl?op=modify'][href*='step=6']", 'css'); + my $alternative_contact_edit = $d->find_element("div.action a[href*='memberentry.pl?op=modify'][href*='step=2']", 'css'); + + my $e = {}; + $e->{patron_information} = $patron_info_edit if $patron_info_edit; + $e->{smsnumber} = $sms_number_edit if $sms_number_edit; + $e->{library_use} = $library_use_edit if $library_use_edit; + $e->{alternate_address} = $alternate_addr_edit if $alternate_addr_edit; + $e->{alternative_contact} = $alternative_contact_edit if $alternative_contact_edit; + return $e; +} + +sub _getMessagingPreferenceCheckboxes { + my ($self) = @_; + my $d = $self->getDriver(); + + my @email_prefs = $d->find_elements('input[type="checkbox"][id^="email"]'); + my @phone_prefs = $d->find_elements('input[type="checkbox"][id^="phone"]'); + my @sms_prefs = $d->find_elements('input[type="checkbox"][id^="sms"]'); + + return { email => \@email_prefs, phone => \@phone_prefs, sms => \@sms_prefs }; +} + +################################################################################ +=head PageObject Services + +=cut +################################################################################ + +sub checkMessagingPreferencesSet { + my ($self, $valid, @prefs) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + foreach my $type (@prefs){ + my @this_pref = $d->find_elements('input[type="checkbox"][id^="'.$type.'"]'); + + my $ok = 0; + + foreach my $checkbox (@this_pref){ + ok(0, "Intra Moremember $type messaging checkbox ".$checkbox->get_attribute('id')." not checked") if !$checkbox->is_selected() and $valid; + ok(0, "Intra Moremember $type messaging checkbox ".$checkbox->get_attribute('id')." checked (not supposed to be)") if $checkbox->is_selected() and !$valid; + $ok = 1; + } + ok($ok, "Intra Moremember $type messaging checkboxes ok (all " . (($valid) ? 'checked':'unchecked') . ")"); + } + + return $self; +} + +sub navigateToPatronInformationEdit { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $elements = $self->_getEditLinks(); + $elements->{patron_information}->click(); + ok($d->get_title() =~ m/Modify(.*)patron/, "Intra Navigate to Modify patron information"); + + $self->debugTakeSessionSnapshot(); + + return t::lib::Page::Members::Memberentry->rebrandFromPageObject($self); +} + +sub navigateToSMSnumberEdit { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $elements = $self->_getEditLinks(); + $elements->{smsnumber}->click(); + ok($d->get_title() =~ m/Modify(.*)patron/, "Intra Navigate to Modify patron SMS number"); + + $self->debugTakeSessionSnapshot(); + + return t::lib::Page::Members::Memberentry->rebrandFromPageObject($self); +} + +sub navigateToLibraryUseEdit { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $elements = $self->_getEditLinks(); + $elements->{library_use}->click(); + ok($d->get_title() =~ m/Modify(.*)patron/, "Intra Navigate to Modify patron Library use"); + + $self->debugTakeSessionSnapshot(); + + return t::lib::Page::Members::Memberentry->rebrandFromPageObject($self); +} + +sub navigateToAlternateAddressEdit { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $elements = $self->_getEditLinks(); + $elements->{alternate_address}->click(); + ok($d->get_title() =~ m/Modify(.*)patron/, "Intra Navigate to Modify patron Alternate address"); + + $self->debugTakeSessionSnapshot(); + + return t::lib::Page::Members::Memberentry->rebrandFromPageObject($self); +} + +sub navigateToAlternativeContactEdit { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $elements = $self->_getEditLinks(); + $elements->{alternative_contact}->click(); + ok($d->get_title() =~ m/Modify(.*)patron/, "Intra Navigate to Modify patron Alternative contact"); + + $self->debugTakeSessionSnapshot(); + + return t::lib::Page::Members::Memberentry->rebrandFromPageObject($self); +} + + + +1; #Make the compiler happy! diff --git a/t/lib/Page/Members/Notices.pm b/t/lib/Page/Members/Notices.pm new file mode 100644 index 0000000..2e6fd4f --- /dev/null +++ b/t/lib/Page/Members/Notices.pm @@ -0,0 +1,195 @@ +package t::lib::Page::Members::Notices; + +# Copyright 2015 Open Source Freedom Fighters +# +# 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 . + +use Modern::Perl; +use Scalar::Util qw(blessed); +use Test::More; + +use base qw(t::lib::Page::Intra t::lib::Page::Members::Toolbar t::lib::Page::Members::LeftNavigation); + +use Koha::Exception::BadParameter; + +use t::lib::Page::Circulation::Circulation; + + +sub new { + my ($class, $params) = @_; + unless (ref($params) eq 'HASH' || (blessed($params) && $params->isa('t::lib::Page') )) { + $params = {}; + } + $params->{resource} = '/cgi-bin/koha/members/notices.pl'; + $params->{type} = 'staff'; + + $params->{getParams} = []; + #Handle MANDATORY parameters + if ($params->{borrowernumber}) { + push @{$params->{getParams}}, "borrowernumber=".$params->{borrowernumber}; + } + else { + Koha::Exception::BadParameter->throw(error => __PACKAGE__."->new():> Parameter 'borrowernumber' is missing."); + } + my $self = $class->SUPER::new($params); + + return $self; +} + +################################################################################ +=head UI Mapping helper subroutines +See. Selenium documentation best practices for UI element mapping to common language descriptions. +=cut +################################################################################ + +sub _getNoticesTable { + my ($self) = @_; + my $d = $self->getDriver(); + + my $e = {}; + + eval { + $e->{noticestable_headers} = $d->find_elements("#noticestable th", 'css'); + }; + eval { + $e->{noticestable_cells} = $d->find_elements("#noticestable td", 'css'); + }; + eval { + $e->{noticestable_rows} = $d->find_elements("#noticestable tr", 'css'); + }; + + return $e; +} + +################################################################################ +=head PageObject Services + +=cut +################################################################################ + + +# column and row first index is 1 +sub getTextInColumnAtRow { + my ($self, $searchText, $params) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + if (not exists $params->{column} or not exists $params->{row} or + not $params->{column} or not $params->{row}) { + warn "t::lib::Page::Members::Notices->getColumnAtRow must be called with column and row numbers, e.g. {column => 1, row=1}"; + } + + my $col = $params->{column}; + my $row = $params->{row}; + + my $cell = $d->find_element("#noticestable tr:nth-child($row) td:nth-child($col)"); + + ok(($cell->get_text() =~ /^(.*)$searchText(.*)$/), "Intra Notices Text \"$searchText\" found at column ". + $params->{column}." row ".$params->{row}." matches \"".$cell->get_text()."\"."); + + return $self; +} + +sub hasDeliveryNoteColumnInNoticesTable { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $elements = $self->_getNoticesTable(); + + my $headers = $elements->{noticestable_headers}; + + my $hasDeliveryNote = 0; + + foreach my $header(@$headers){ + $hasDeliveryNote = 1 if ($header->get_text() =~ /^Delivery note$/); + } + + ok($hasDeliveryNote, "Intra Notices Table has all headings"); + + return $self; +} + +sub hasTextInTableCell { + my ($self, $txt) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $elements = $self->_getNoticesTable(); + + my $cells = $elements->{noticestable_cells}; + + my $hasText = 0; + + foreach my $cell(@$cells){ + $hasText = 1 if ($cell->get_text() =~ /^(.*)$txt(.*)$/); + } + + ok($hasText, "Intra Notices Found text '$txt' from table"); + + return $self; +} + +sub verifyNoMessages { + my ($self, $note) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $dialog = $d->find_element("div.yui-b div.dialog", 'css')->get_text(); + + ok($dialog =~ /^There is no record of any(.*)$/, "Intra Notices No messages sent"); + + return $self; +} + +sub openNotice { + my ($self, $titleText) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my @titles = $d->find_elements(".notice-title", 'css'); + + my $opened = 0; + foreach my $title (@titles){ + if ($title->get_text() eq $titleText) { + $title->click(); + $opened++; + } + } + + ok(($opened > 0), "Opened ". $opened ." notices '".$titleText."' successfully."); + + return $self; +} + +sub resendMessage { + my ($self, $titleText, $shouldSucceed) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my @resendLinks = $d->find_elements('//tr/td[a//text()[contains(.,\''.$titleText.'\')]]/following-sibling::td[2]/div/a[text()=\'Resend\']', 'xpath'); + + my $resent = 0; + foreach my $link (@resendLinks){ + $link->click(); + $resent++; + } + is(($resent > 0) ? 1:0, $shouldSucceed, "Resent ". $resent ." notices '".$titleText."' successfully."); + + return $self; +} + +1; diff --git a/t/lib/Page/Members/Pay.pm b/t/lib/Page/Members/Pay.pm new file mode 100644 index 0000000..581d882 --- /dev/null +++ b/t/lib/Page/Members/Pay.pm @@ -0,0 +1,106 @@ +package t::lib::Page::Members::Pay; + +# Copyright 2015 Open Source Freedom Fighters +# +# 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 . + +use Modern::Perl; +use Scalar::Util qw(blessed); +use Test::More; + +use base qw(t::lib::Page::Intra t::lib::Page::Members::Toolbar t::lib::Page::Members::LeftNavigation); + +use t::lib::Page::Members::ApiKeys; + +use Koha::Exception::BadParameter; + +=head NAME t::lib::Page::Members::Pay + +=head SYNOPSIS + +pay.pl PageObject providing page functionality as a service! + +=cut + +=head new + + my $pay = t::lib::Page::Members::Pay->new({borrowernumber => "1"}); + +Instantiates a WebDriver and loads the members/pay.pl. +@PARAM1 HASHRef of optional and MANDATORY parameters +MANDATORY extra parameters: + borrowernumber => loads the page to display Borrower matching the given borrowernumber + +@RETURNS t::lib::Page::Members::Pay, ready for user actions! +=cut + +sub new { + my ($class, $params) = @_; + unless (ref($params) eq 'HASH' || (blessed($params) && $params->isa('t::lib::Page') )) { + $params = {}; + } + $params->{resource} = '/cgi-bin/koha/members/pay.pl'; + $params->{type} = 'staff'; + + $params->{getParams} = []; + #Handle MANDATORY parameters + if ($params->{borrowernumber}) { + push @{$params->{getParams}}, "borrowernumber=".$params->{borrowernumber}; + } + else { + Koha::Exception::BadParameter->throw(error => __PACKAGE__."->new():> Parameter 'borrowernumber' is missing."); + } + + my $self = $class->SUPER::new($params); + + return $self; +} + +################################################################################ +=head UI Mapping helper subroutines +See. Selenium documentation best practices for UI element mapping to common language descriptions. +=cut +################################################################################ + +sub PayAmount{ + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $tab = $d->find_element("input[id='paycollect'][type='submit']", 'css'); + $tab->click(); + ok($d->get_title() =~ m/Collect fine payment for/, "Intra Navigate to Paycollect (all fines)"); + + $self->debugTakeSessionSnapshot(); + + return t::lib::Page::Members::Paycollect->rebrandFromPageObject($self); +} + +sub PaySelected { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $tab = $d->find_element("input[id='payselected'][type='submit']", 'css'); + $tab->click(); + ok($d->get_title() =~ m/Collect fine payment for/, "Intra Navigate to Paycollect (selected fines)"); + + $self->debugTakeSessionSnapshot(); + + return t::lib::Page::Members::Paycollect->rebrandFromPageObject($self); +} + +1; #Make the compiler happy! diff --git a/t/lib/Page/Members/Paycollect.pm b/t/lib/Page/Members/Paycollect.pm new file mode 100644 index 0000000..3ea1287 --- /dev/null +++ b/t/lib/Page/Members/Paycollect.pm @@ -0,0 +1,276 @@ +package t::lib::Page::Members::Paycollect; + +# Copyright 2015 Open Source Freedom Fighters +# +# 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 . + +use Modern::Perl; +use Scalar::Util qw(blessed); +use Test::More; + +use base qw(t::lib::Page::Intra t::lib::Page::Members::Toolbar t::lib::Page::Members::LeftNavigation); + +use t::lib::Page::Members::ApiKeys; + +use Selenium::Remote::WDKeys; + +use Koha::Exception::BadParameter; + +=head NAME t::lib::Page::Members::Paycollect + +=head SYNOPSIS + +paycollect.pl PageObject providing page functionality as a service! + +=cut + +=head new + + my $paycollect = t::lib::Page::Members::Paycollect->new({borrowernumber => "1", selected => "1,2,3,4,5"}); + +Instantiates a WebDriver and loads the members/paycollect.pl. +@PARAM1 HASHRef of optional and MANDATORY parameters +MANDATORY extra parameters: + borrowernumber => loads the page to display Borrower matching the given borrowernumber + +@RETURNS t::lib::Page::Members::Paycollect, ready for user actions! +=cut + +sub new { + my ($class, $params) = @_; + unless (ref($params) eq 'HASH' || (blessed($params) && $params->isa('t::lib::Page') )) { + $params = {}; + } + $params->{resource} = '/cgi-bin/koha/members/paycollect.pl'; + $params->{type} = 'staff'; + + $params->{getParams} = []; + #Handle MANDATORY parameters + if ($params->{borrowernumber}) { + push @{$params->{getParams}}, "borrowernumber=".$params->{borrowernumber}; + } + else { + Koha::Exception::BadParameter->throw(error => __PACKAGE__."->new():> Parameter 'borrowernumber' is missing."); + } + + if ($params->{selected}) { + push @{$params->{getParams}}, "selected=".$params->{selected}; + } + + my $self = $class->SUPER::new($params); + + return $self; +} + +################################################################################ +=head UI Mapping helper subroutines +See. Selenium documentation best practices for UI element mapping to common language descriptions. +=cut +################################################################################ + +sub addNewCashRegister { + my ($self, $cashregisternumber) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $input = $d->find_element("input[id='office_new']", 'css'); + $input->clear(); + $input->send_keys($cashregisternumber); + my $button = $d->find_element("button[id='new_office']", 'css'); + $button->click(); + + my $ok = 1 if $d->find_element("button[id='office-".$cashregisternumber."']",'css'); + ok($ok, "Intra Added a new cash register '".$cashregisternumber."'"); + + $self->debugTakeSessionSnapshot(); + + return $self; +} + +sub addNoteToSelected { + my ($self, $note) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $input = $d->find_element("textarea[id='selected_accts_notes']", 'css'); + $input->clear(); + $input->send_keys($note); + + $self->debugTakeSessionSnapshot(); + + return $self; +} + +sub confirmPayment { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $confirm = $d->find_element("input[name='submitbutton'][type='submit']",'css'); + + $confirm->click(); + + ok(1, "Intra Confirmed payment"); + $self->debugTakeSessionSnapshot(); + + return t::lib::Page::Members::Paycollect->rebrandFromPageObject($self); +} + +sub openAddNewCashRegister { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $link = $d->find_element("span[id='add_new_office'] a", 'css'); + $link->click(); + ok($d->find_element("input[id='office_new']",'css'), "Intra Opened input for adding a new cash register"); + + $self->debugTakeSessionSnapshot(); + + return $self; +} + +sub paymentLoadingScreen { + my ($self, $cashregisternumber) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $ok = 1 if $d->find_element("button[id='recheck']",'css'); + + ok($ok, "Intra Payment loading screen open"); + + return $self; +} + +sub selectCashRegister { + my ($self, $cashregisternumber) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $cashregister = $d->find_element("button[id='office-".$cashregisternumber."']",'css'); + + $cashregister->click(); + + my $ok = 1 if $d->find_element("button[id='office-".$cashregisternumber."'][class='office-button selected']",'css'); + ok($ok, "Intra Selected cash register '".$cashregisternumber."'"); + $self->debugTakeSessionSnapshot(); + + return $self; +} + +sub sendPaymentToPOS { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $confirm = $d->find_element("input[name='submitbutton'][type='submit']",'css'); + + # $confirm->click() is broken. It doesn't move on until AJAX at next page is completed. Need to use + # alternative method. Click submit with JavaScript and poll until loading screen is open. + my $script = q{ + $("input[name='submitbutton'][type='submit']").click(); + }; + $d->execute_script($script); + + my $func = undef; # we only need to poll for success + my $success = sub { + eval { + my $el = $d->find_element("button[id='recheck']",'css'); + }; + if ($@) { + return 0; + } + return 1; + }; + + $self->poll($func, $success, 50, 100); # poll for max 5 seconds + + ok(1, "Intra Sent payment to cash register"); + $self->debugTakeSessionSnapshot(); + + return t::lib::Page::Members::Paycollect->rebrandFromPageObject($self); +} + +sub setAmount { + my ($self, $amount) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $input = $d->find_element("input[name='paid']", 'css'); + # Clear and send_keys did not work. Set values by JS + my $script = qq(('#paid').val('$amount');); + $d->execute_script('$'.$script); + + is($input->get_value(), $amount, "Intra Set payment amount to ".$amount); + $self->debugTakeSessionSnapshot(); + + return $self; +} + +sub waitUntilPaymentIsAcceptedAtPOS { + my ($self) = @_; + + return waitUntilPaymentIsCompletedAtPOS($self, "paid"); +} +sub waitUntilPaymentIsCancelledAtPOS { + my ($self) = @_; + + return waitUntilPaymentIsCompletedAtPOS($self, "cancelled"); +} +sub waitUntilPaymentIsCompletedAtPOS { + my ($self, $status) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $recheck = $d->find_element("button[id='recheck']",'css'); + + my $func = undef; # we only need to poll for success + my $success = sub { + eval { + my $el = $d->find_element("span#status span.".$status."[style*='inline-block']",'css'); + }; + if ($@) { + return 0; + } + return 1; + }; + + $self->poll($func, $success, 50, 100); # poll for max 5 seconds + + ok(1, "Payment is completed"); + $self->debugTakeSessionSnapshot(); + + # Poll until "recheck" button is not found. This means we have been + # redirected to Boraccount + $func = undef; # we only need to poll for success + $success = sub { + eval { + my $el = $d->find_element("button[id='recheck']",'css'); + }; + if ($@) { + return 1; + } + return 0; + }; + + $self->poll($func, $success, 50, 100); # poll for max 5 seconds + + $self->debugTakeSessionSnapshot(); + + return t::lib::Page::Members::Boraccount->rebrandFromPageObject($self); +} + +1; #Make the compiler happy! diff --git a/t/lib/Page/Members/Toolbar.pm b/t/lib/Page/Members/Toolbar.pm new file mode 100644 index 0000000..0bb00d2 --- /dev/null +++ b/t/lib/Page/Members/Toolbar.pm @@ -0,0 +1,154 @@ +package t::lib::Page::Members::Toolbar; + +# Copyright 2015 KohaSuomi! +# +# 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 . + +use Modern::Perl; +use Test::More; + +use Koha::Exception::BadParameter; + +=head NAME t::lib::Page::Members::Toolbar + +=head SYNOPSIS + +PageObject Accessory representing a shared Template between PageObjects. +This encapsulates specific page-related functionality. + +In this case this encapsulates the members-module toolbar's services and provides +a reusable class from all member-module PageObjects. + +=cut + +################################################################################ +=head UI Mapping helper subroutines +See. Selenium documentation best practices for UI element mapping to common language descriptions. +=cut +################################################################################ + +=head _getToolbarActionElements +Shares the same toolbar with moremember.pl +@RETURNS HASHRef of Selenium::Driver::Webelements matching all the clickable elements + in the actions toolbar over the Borrower information. +=cut + +sub _getToolbarActionElements { + my ($self) = @_; + my $d = $self->getDriver(); + + my $editA = $d->find_element("#editpatron", 'css'); + my $changePasswordA = $d->find_element("#changepassword", 'css'); + my $duplicateA = $d->find_element("#duplicate", 'css'); + my $printButton = $d->find_element("#duplicate + div > button", 'css'); + my $searchToHoldA = $d->find_element("#searchtohold", 'css'); + my $addNewMessage = $d->find_element("#addnewmessageLabel", 'css'); + my $moreButton = $d->find_element("//button[contains(.,'More')]", 'xpath'); + + my $e = {}; + $e->{edit} = $editA if $editA; + $e->{changePassword} = $changePasswordA if $changePasswordA; + $e->{duplicate} = $duplicateA if $duplicateA; + $e->{print} = $printButton if $printButton; + $e->{searchToHold} = $searchToHoldA if $searchToHoldA; + $e->{addNewMessage} = $addNewMessage if $addNewMessage; + $e->{more} = $moreButton if $moreButton; + return $e; +} + +=head _getMoreDropdownElements +Clicks the dropdown open if it isnt yet. +@RETURNS HASHRef of all the dropdown elements under the More button in the toolbar + over Borrower information. +=cut + +sub _getMoreDropdownElements { + my ($self) = @_; + my $d = $self->getDriver(); + + my $toolbarElements = $self->_getToolbarActionElements(); + my $moreButton = $toolbarElements->{more}; + my $deleteA; + eval { + $deleteA = $d->find_child_element($moreButton, "#deletepatron", 'css'); + }; + unless ($deleteA && $deleteA->is_visible()) { + $moreButton->click(); + $self->debugTakeSessionSnapshot(); + } + + my $renewPatronA = $d->find_element("#renewpatron", 'css'); + my $setPermissionsA = $d->find_element("#patronflags", 'css'); + my $manageApiKeysA = $d->find_element("#apikeys", 'css'); + $deleteA = $d->find_element("#deletepatron", 'css'); + $self->debugTakeSessionSnapshot(); + my $updateChildToAdultPatronA = $d->find_element("#updatechild", 'css'); + my $exportCheckinBarcodesA = $d->find_element("#exportcheckins", 'css'); + + my $e = {}; + $e->{renewPatron} = $renewPatronA if $renewPatronA; + $e->{setPermissions} = $setPermissionsA if $setPermissionsA; + $e->{manageApiKeys} = $manageApiKeysA if $manageApiKeysA; + $e->{delete} = $deleteA if $deleteA; + $e->{updateChildToAdultPatron} = $updateChildToAdultPatronA if $updateChildToAdultPatronA; + $e->{exportCheckinBarcodes} = $exportCheckinBarcodesA if $exportCheckinBarcodesA; + return $e; +} + + +################################################################################ +=head PageObject Services + +=cut +################################################################################ + +sub navigateManageApiKeys { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $elements = $self->_getMoreDropdownElements(); + $elements->{manageApiKeys}->click(); + ok($d->get_title() =~ m/API Keys/, "Intra Navigate to Manage API Keys"); + + $self->debugTakeSessionSnapshot(); + + return t::lib::Page::Members::ApiKeys->rebrandFromPageObject($self); +} +sub navigateEditPatron { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $elements = $self->_getToolbarActionElements(); + + my $func = sub { + $elements->{edit}->click(); + }; + my $success = sub { + return $self->getDriver()->get_title() =~ m/Modify(.*)patron/; + }; + + $self->poll($func, $success, 20, 50); + + ok($d->get_title() =~ m/Modify(.*)patron/, "Intra Navigate to Modify patron"); + $self->debugTakeSessionSnapshot(); + + return t::lib::Page::Members::Memberentry->rebrandFromPageObject($self); +} + + +1; #Make the compiler happy! diff --git a/t/lib/Page/Opac.pm b/t/lib/Page/Opac.pm new file mode 100644 index 0000000..2635cb8 --- /dev/null +++ b/t/lib/Page/Opac.pm @@ -0,0 +1,238 @@ +package t::lib::Page::Opac; + +# Copyright 2015 Open Source Freedom Fighters +# +# 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 . + +use Modern::Perl; +use Test::More; + +use C4::Context; + +use t::lib::WebDriverFactory; + +use Koha::Exception::BadParameter; +use Koha::Exception::SystemCall; + +use base qw(t::lib::Page); + +=head NAME t::lib::Page::Opac + +=head SYNOPSIS + +PageObject-pattern parent class for OPAC-pages. Extend this to implement specific pages shown to our users. + +=cut + +################################################################################ +=head UI Mapping helper subroutines +See. Selenium documentation best practices for UI element mapping to common language descriptions. +=cut +################################################################################ + +=head _getHeaderRegionActionElements + +Returns each element providing some kind of an action from the topmost header bar in OPAC. +All elements are not always present on each page, so test if the return set contains your +desired element. +@PARAM1 Selenium::Remote::Driver +@RETURNS HASHRef of the found elements: + { cart => $cartA, + lists => $listsA, + loggedinusername => $loggedinusernameA, + searchHistory => $searchHistoryA, + deleteSearchHistory => $deleteSearchHistoryA, + logout => $logoutA, + login => $loginA, + } +=cut + +sub _getHeaderRegionActionElements { + my ($self) = @_; + my $d = $self->getDriver(); + + my ($cartA, $listsA, $loggedinusernameA, $searchHistoryA, $deleteSearchHistoryA, $logoutA, $loginA); + #Always visible elements + $cartA = $d->find_element("#header-region a#cartmenulink"); + $listsA = $d->find_element("#header-region a#listsmenu"); + #Occasionally visible elements + eval { + $loggedinusernameA = $d->find_element("#header-region a[href*='opac-user.pl']"); + }; + eval { + $searchHistoryA = $d->find_element("#header-region a[href*='opac-search-history.pl']"); + }; + eval { + $deleteSearchHistoryA = $d->find_element("#header-region a[href*='opac-search-history.pl'] + a"); + }; + eval { + $logoutA = $d->find_element("#header-region #logout"); + }; + eval { + $loginA = $d->find_element("#header-region #members a.loginModal-trigger"); + }; + + my $e = {}; + $e->{cart} = $cartA if $cartA; + $e->{lists} = $listsA if $listsA; + $e->{loggedinusername} = $loggedinusernameA if $loggedinusernameA; + $e->{searchHistory} = $searchHistoryA if $searchHistoryA; + $e->{deleteSearchHistory} = $deleteSearchHistoryA if $deleteSearchHistoryA; + $e->{logout} = $logoutA if $logoutA; + $e->{login} = $loginA if $loginA; + return ($e); +} + +sub _getMoresearchesElements { + my ($self) = @_; + my $d = $self->getDriver(); + + my $advancedSearchA = $d->find_element("#moresearches a[href*='opac-search.pl']"); + my $authoritySearchA = $d->find_element("#moresearches a[href*='opac-authorities-home.pl']"); + my $tagCloudA = $d->find_element("#moresearches a[href*='opac-tags.pl']"); + return ($advancedSearchA, $authoritySearchA, $tagCloudA); +} + +sub _getBreadcrumbLinks { + my ($self) = @_; + my $d = $self->getDriver(); + + my $breadcrumbLinks = $d->find_elements("ul.breadcrumb a"); + return ($breadcrumbLinks); +} + + + +################################################################################ +=head PageObject Services + +=cut +################################################################################ + +=head isPasswordLoginAvailable + + $page->isPasswordLoginAvailable(); + +@RETURN t::lib::Page-object +@CROAK if password login is unavailable. +=cut + +sub isPasswordLoginAvailable { + my $self = shift; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + $self->_getPasswordLoginElements(); + ok(1, "PasswordLogin available"); + return $self; +} + +sub doPasswordLogin { + my ($self, $username, $password) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my ($submitButton, $useridInput, $passwordInput) = $self->_getPasswordLoginElements(); + $useridInput->send_keys($username); + $passwordInput->send_keys($password); + $submitButton->click(); + $self->debugTakeSessionSnapshot(); + + my $cookies = $d->get_all_cookies(); + my @cgisessid = grep {$_->{name} eq 'CGISESSID'} @$cookies; + + my $loggedinusernameSpan = $d->find_element('span.loggedinusername'); + ok(($cgisessid[0]), "PasswordLogin succeeded"); #We have the element && Cookie CGISESSID defined! + + return $self; #After a succesfull password login, we are directed to the same page we tried to access. +} + +sub failPasswordLogin { + my ($self, $username, $password) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my ($submitButton, $useridInput, $passwordInput) = $self->_getPasswordLoginElements(); + $useridInput->send_keys($username); + $passwordInput->send_keys($password); + $submitButton->click(); + $self->debugTakeSessionSnapshot(); + + ok($d->get_title() =~ /Log in to your account/ #Still in the login page + , "Opac PasswordLogin failed"); + + return $self; #After a successful password login, we are directed to the same page we tried to access. +} + +sub doPasswordLogout { + my ($self, $username, $password) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + #Logout + my $headerElements = $self->_getHeaderRegionActionElements(); + my $logoutA = $headerElements->{logout}; + $logoutA->click(); + $self->debugTakeSessionSnapshot(); + + $headerElements = $self->_getHeaderRegionActionElements(); #Take the changed header elements + ok(($headerElements->{login}->get_text() =~ /Log in/ || + $d->get_title() =~ /Log in to your account/), "Opac Header PasswordLogout succeeded"); + + return t::lib::Page::Opac::OpacMain->rebrandFromPageObject($self); +} + +sub navigateSearchHistory { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $headerElements = $self->_getHeaderRegionActionElements(); + my $searchHistoryA = $headerElements->{searchHistory}; + $searchHistoryA->click(); + $self->debugTakeSessionSnapshot(); + + ok(($d->get_title() =~ /Your search history/), "Opac Navigation to search history."); + return t::lib::Page::Opac::OpacSearchHistory->rebrandFromPageObject($self); +} + +sub navigateAdvancedSearch { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my ($advancedSearchA, $authoritySearchA, $tagCloudA) = $self->_getMoresearchesElements(); + $advancedSearchA->click(); + + $self->debugTakeSessionSnapshot(); + ok(($d->get_title() =~ /Advanced search/), "Opac Navigating to advanced search."); + return t::lib::Page::Opac::OpacSearch->rebrandFromPageObject($self); +} + +sub navigateHome { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $breadcrumbLinks = $self->_getBreadcrumbLinks(); + $breadcrumbLinks->[0]->click(); + + $self->debugTakeSessionSnapshot(); + ok(($d->get_current_url() =~ /opac-main\.pl/), "Opac Navigating to OPAC home."); + return t::lib::Page::Opac::OpacMain->rebrandFromPageObject($self); +} + +1; #Make the compiler happy! \ No newline at end of file diff --git a/t/lib/Page/Opac/LeftNavigation.pm b/t/lib/Page/Opac/LeftNavigation.pm new file mode 100644 index 0000000..1fbbb26 --- /dev/null +++ b/t/lib/Page/Opac/LeftNavigation.pm @@ -0,0 +1,167 @@ +package t::lib::Page::Opac::LeftNavigation; + +# Copyright 2015 Open Source Freedom Fighters +# +# 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 . + +use Modern::Perl; +use Scalar::Util qw(blessed); +use Test::More; + +use t::lib::Page::Opac::OpacApiKeys; +use t::lib::Page::Opac::OpacMessaging; + +=head NAME t::lib::Page::Opac::LeftNavigation + +=head SYNOPSIS + +Provides the services of the Opac left navigation column/frame for the implementing PageObject + +=cut + +################################################################################ +=head UI Mapping helper subroutines +See. Selenium documentation best practices for UI element mapping to common language descriptions. +=cut +################################################################################ + +=head _getLeftNavigationActionElements +@RETURNS HASHRef of Selenium::Driver::Webelements matching all the clickable elements + in the left navigation frame/column at all Opac pages requiring login. +=cut + +sub _getLeftNavigationActionElements { + my ($self) = @_; + my $d = $self->getDriver(); + + my $e = {}; + eval { + $e->{yourSummary} = $d->find_element("a[href*='opac-user.pl']", 'css'); + }; + eval { + $e->{yourFines} = $d->find_element("a[href*='opac-account.pl']", 'css'); + }; + eval { + $e->{yourPersonalDetails} = $d->find_element("a[href*='opac-memberentry.pl']", 'css'); + }; + eval { + $e->{yourTags} = $d->find_element("a[href*='opac-tags.pl']", 'css'); + }; + eval { + $e->{changeYourPassword} = $d->find_element("a[href*='opac-passwd.pl']", 'css'); + }; + eval { + $e->{yourSearchHistory} = $d->find_element("a[href*='opac-search-history.pl']", 'css'); + }; + eval { + $e->{yourReadingHistory} = $d->find_element("a[href*='opac-readingrecord.pl']", 'css'); + }; + eval { + $e->{yourPurchaseSuggestions} = $d->find_element("a[href*='opac-suggestions.pl']", 'css'); + }; + eval { + $e->{yourMessaging} = $d->find_element("a[href*='opac-messaging.pl']", 'css'); + }; + eval { + $e->{yourLists} = $d->find_element("a[href*='opac-shelves.pl']", 'css'); + }; + eval { + $e->{yourAPIKeys} = $d->find_element("a[href*='opac-apikeys.pl']", 'css'); + }; + return $e; +} + + + +################################################################################ +=head PageObject Services + +=cut +################################################################################ + +sub navigateYourAPIKeys { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $elements = $self->_getLeftNavigationActionElements(); + $elements->{yourAPIKeys}->click(); + $self->debugTakeSessionSnapshot(); + + my $breadcrumbs = $self->_getBreadcrumbLinks(); + + ok(ref($breadcrumbs) eq 'ARRAY' && + $breadcrumbs->[scalar(@$breadcrumbs)-1]->get_text() =~ m/API keys/i, + "Opac Navigate to Your API Keys"); + + return t::lib::Page::Opac::OpacApiKeys->rebrandFromPageObject($self); +} + +sub navigateYourMessaging { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $elements = $self->_getLeftNavigationActionElements(); + $elements->{yourMessaging}->click(); + $self->debugTakeSessionSnapshot(); + + my $breadcrumbs = $self->_getBreadcrumbLinks(); + + ok(ref($breadcrumbs) eq 'ARRAY' && + $breadcrumbs->[scalar(@$breadcrumbs)-1]->get_text() =~ m/Your messaging/i, + "Opac Navigate to Your messaging"); + + return t::lib::Page::Opac::OpacMessaging->rebrandFromPageObject($self); +} + +sub navigateYourPersonalDetails { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $elements = $self->_getLeftNavigationActionElements(); + $elements->{yourPersonalDetails}->click(); + $self->debugTakeSessionSnapshot(); + + my $breadcrumbs = $self->_getBreadcrumbLinks(); + + ok(ref($breadcrumbs) eq 'ARRAY' && + $breadcrumbs->[scalar(@$breadcrumbs)-1]->get_text() =~ m/Your personal details/i, + "Opac Navigate to Your personal details"); + + return t::lib::Page::Opac::OpacMemberentry->rebrandFromPageObject($self); +} + +sub navigateYourFines { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $elements = $self->_getLeftNavigationActionElements(); + $elements->{yourFines}->click(); + $self->debugTakeSessionSnapshot(); + + my $breadcrumbs = $self->_getBreadcrumbLinks(); + + ok(ref($breadcrumbs) eq 'ARRAY' && + $breadcrumbs->[scalar(@$breadcrumbs)-1]->get_text() =~ m/Your fines and charges/i, + "Opac Navigate to Your fines"); + + return t::lib::Page::Opac::OpacAccount->rebrandFromPageObject($self); +} + +1; #Make the compiler happy! diff --git a/t/lib/Page/Opac/OpacAccount.pm b/t/lib/Page/Opac/OpacAccount.pm new file mode 100644 index 0000000..f3f3511 --- /dev/null +++ b/t/lib/Page/Opac/OpacAccount.pm @@ -0,0 +1,142 @@ +package t::lib::Page::Opac::OpacAccount; + +# Copyright 2016 KohaSuomi +# +# 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 Lice strongnse +# along with Koha; if not, see . + +use Modern::Perl; +use Scalar::Util qw(blessed); +use Test::More; + +use t::lib::Page::Opac::OpacAccount; +use t::lib::Page::Opac::OpacPaycollect; + +use base qw(t::lib::Page::Opac t::lib::Page::Opac::LeftNavigation); + +use Koha::Exception::BadParameter; + +=head NAME t::lib::Page::Opac::OpacAccount + +=head SYNOPSIS + +PageObject providing page functionality as a service! + +=cut + +=head new + + my $opacaccount = t::lib::Page::Opac::OpacAccount->new(); + +Instantiates a WebDriver and loads the opac/opac-account.pl. +@PARAM1 HASHRef of optional and MANDATORY parameters +MANDATORY extra parameters: + none atm. + +@RETURNS t::lib::Page::Opac::OpacAccount, ready for user actions! +=cut + +sub new { + my ($class, $params) = @_; + unless (ref($params) eq 'HASH' || (blessed($params) && $params->isa('t::lib::Page') )) { + $params = {}; + } + $params->{resource} = '/cgi-bin/koha/opac-account.pl'; + $params->{type} = 'opac'; + + my $self = $class->SUPER::new($params); + + return $self; +} + +################################################################################ +=head UI Mapping helper subroutines +See. Selenium documentation best practices for UI element mapping to common language descriptions. +=cut +################################################################################ + +sub findFine { + my ($self, $note) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $payment_note = $d->find_element("//td[contains(.,\"".$note."\")]", "xpath"); + + ok(1, "Opac Found payment with description ".$note); + + return $self; +} + +sub isFinePaid { + my ($self, $note, $noteColumnIndex, $amountOutstandingColumnIndex) = @_; + + return isFineAmountOutstanding($self, $note, "0.00", $noteColumnIndex, $amountOutstandingColumnIndex); +} + +sub isFineAmount { + my ($self, $note, $amount, $noteColumnIndex, $amountColumnIndex) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + $noteColumnIndex = 2 if not defined $noteColumnIndex; + $amountColumnIndex = 3 if not defined $amountColumnIndex; + + my $fine = $d->find_element("//tr[(td[".$noteColumnIndex."][contains(.,\"".$note."\")]) and (td[".$amountColumnIndex."][text()=\"".$amount."\"])]", "xpath"); + + ok(1, "Opac Found payment with note ".$note." and amount ".$amount); + + return $self; +} + +sub isFineAmountOutstanding { + my ($self, $note, $amountOutstanding, $noteColumnIndex, $amountOutstandingColumnIndex) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + $noteColumnIndex = 2 if not defined $noteColumnIndex; + $amountOutstandingColumnIndex = 4 if not defined $amountOutstandingColumnIndex; + + my $fine = $d->find_element("//tr[(td[".$noteColumnIndex."][contains(.,\"".$note."\")]) and (td[".$amountOutstandingColumnIndex."][text()=\"".$amountOutstanding."\"])]", "xpath"); + + ok(1, "Opac Found payment with note ".$note." and amountoutstanding ".$amountOutstanding); + + return $self; +} + +sub isEverythingPaid { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $transaction_columns = $d->find_element("//td[contains(\@class, 'sum') and text() = '0.00']", "xpath"); + + ok(1, "Opac Found total due 0.00, no outstanding fines"); + + return $self; +} + +sub PayFines { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $payButton = $d->find_element("button.pay-fines", "css"); + + $payButton->click(); + + return t::lib::Page::Opac::OpacPaycollect->rebrandFromPageObject($self);; +} + +1; diff --git a/t/lib/Page/Opac/OpacApiKeys.pm b/t/lib/Page/Opac/OpacApiKeys.pm new file mode 100644 index 0000000..72df3ef --- /dev/null +++ b/t/lib/Page/Opac/OpacApiKeys.pm @@ -0,0 +1,170 @@ +package t::lib::Page::Opac::OpacApiKeys; + +# Copyright 2015 KohaSuomi! +# +# 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 . + +use Modern::Perl; +use Scalar::Util qw(blessed); +use Test::More; + +use base qw(t::lib::Page::Opac t::lib::Page::Opac::LeftNavigation); + +use Koha::Exception::BadParameter; +use Koha::Exception::UnknownObject; + +=head NAME t::lib::Page::Members::ApiKeys + +=head SYNOPSIS + +apikeys.pl PageObject providing page functionality as a service! + +=cut + +sub new { + Koha::Exception::FeatureUnavailable->throw(error => __PACKAGE__."->new():> You must login first to navigate to this page!"); +} + +################################################################################ +=head UI Mapping helper subroutines +See. Selenium documentation best practices for UI element mapping to common language descriptions. +=cut +################################################################################ + +=head _getActionsAndTableElements +@RETURNS List of + HASHRef of Selenium::Driver::Webelement-objects matching the generic + actions on this page, eg. 'generateNewKey'. + HASHRef of Selenium::Driver::Webelement-objects keyed with the apiKey hash/text. + These are all the apiKey table rows present, and have the + elements prefetched for easy access. + +=cut + +sub _getActionsAndTableElements { + my ($self) = @_; + my $d = $self->getDriver(); + + my $generateNewKeySubmit = $d->find_element("#generatenewkey", 'css'); + + my $a = {}; #Collect action elements here + $a->{generateNewKey} = $generateNewKeySubmit; #Bind the global action here for easy reference. + + my $apiKeyRows; + eval { #We might not have ApiKeys yet. + $apiKeyRows = $d->find_elements("#apikeystable tr", 'css'); + shift @$apiKeyRows; #Remove the table header row + }; + my %apiKeys; + for(my $i=0 ; $i[$i]; + $row->{'nth-of-type'} = $i+1; #starts from 1 + $row->{key} = $d->find_child_element($row, "td.apikeykey", 'css')->get_text(); + $row->{active} = $d->find_child_element($row, "td.apikeyactive", 'css')->get_text(); + $row->{lastTransaction} = $d->find_child_element($row, "td.apikeylastransaction", 'css')->get_text(); + $row->{delete} = $d->find_child_element($row, "input.apikeydelete", 'css'); + eval { + $row->{revoke} = $d->find_child_element($row, "input.apikeyrevoke", 'css'); + }; + eval { + $row->{activate} = $d->find_child_element($row, "input.apikeyactivate", 'css'); + }; + $apiKeys{$row->{key}} = $row; + } + + return ($a, \%apiKeys); +} + + + +################################################################################ +=head PageObject Services + +=cut +################################################################################ + +sub generateNewApiKey { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my ($actionElements, $apiKeyRows) = $self->_getActionsAndTableElements(); + my $apiKeyRowsCountPre = (ref $apiKeyRows eq 'HASH') ? scalar(keys(%$apiKeyRows)) : 0; + $actionElements->{generateNewKey}->click(); + $self->debugTakeSessionSnapshot(); + + ($actionElements, $apiKeyRows) = $self->_getActionsAndTableElements(); + my $apiKeyRowsCountPost = (ref $apiKeyRows eq 'HASH') ? scalar(keys(%$apiKeyRows)) : 0; + is($apiKeyRowsCountPre+1, $apiKeyRowsCountPost, "ApiKey generated"); + return $self; +} + +sub revokeApiKey { + my ($self, $apiKey) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my ($actionElements, $apiKeyRows) = $self->_getActionsAndTableElements(); + my $apiKeyRow = $apiKeyRows->{$apiKey}; + Koha::Exception::UnknownObject->throw(error => __PACKAGE__."revokeApiKey():> No matching apiKey '$apiKey' found.") unless $apiKeyRow; + $apiKeyRow->{revoke}->click(); + $self->debugTakeSessionSnapshot(); + + ($actionElements, $apiKeyRows) = $self->_getActionsAndTableElements(); + $apiKeyRow = $apiKeyRows->{$apiKey}; + Koha::Exception::UnknownObject->throw(error => __PACKAGE__."revokeApiKey():> No matching apiKey '$apiKey' found after revoking it.") unless $apiKeyRow; + is($apiKeyRow->{active}, 'No', "ApiKey revoked"); + return $self; +} + +sub activateApiKey { + my ($self, $apiKey) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my ($actionElements, $apiKeyRows) = $self->_getActionsAndTableElements(); + my $apiKeyRow = $apiKeyRows->{$apiKey}; + Koha::Exception::UnknownObject->throw(error => __PACKAGE__."revokeApiKey():> No matching apiKey '$apiKey' found.") unless $apiKeyRow; + $apiKeyRow->{activate}->click(); + $self->debugTakeSessionSnapshot(); + + ($actionElements, $apiKeyRows) = $self->_getActionsAndTableElements(); + $apiKeyRow = $apiKeyRows->{$apiKey}; + Koha::Exception::UnknownObject->throw(error => __PACKAGE__."revokeApiKey():> No matching apiKey '$apiKey' found after activating it.") unless $apiKeyRow; + is($apiKeyRow->{active}, 'Yes', "ApiKey activated"); + return $self; +} + +sub deleteApiKey { + my ($self, $apiKey) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my ($actionElements, $apiKeyRows) = $self->_getActionsAndTableElements(); + my $apiKeyRowsCountPre = (ref $apiKeyRows eq 'HASH') ? scalar(keys(%$apiKeyRows)) : 0; + my $apiKeyRow = $apiKeyRows->{$apiKey}; + Koha::Exception::UnknownObject->throw(error => __PACKAGE__."revokeApiKey():> No matching apiKey '$apiKey' found.") unless $apiKeyRow; + $apiKeyRow->{delete}->click(); + $self->debugTakeSessionSnapshot(); + + ($actionElements, $apiKeyRows) = $self->_getActionsAndTableElements(); + my $apiKeyRowsCountPost = (ref $apiKeyRows eq 'HASH') ? scalar(keys(%$apiKeyRows)) : 0; + is($apiKeyRowsCountPre-1, $apiKeyRowsCountPost, "ApiKey deleted"); + return $self; +} + +1; #Make the compiler happy! diff --git a/t/lib/Page/Opac/OpacMain.pm b/t/lib/Page/Opac/OpacMain.pm new file mode 100644 index 0000000..6d1d7dd --- /dev/null +++ b/t/lib/Page/Opac/OpacMain.pm @@ -0,0 +1,125 @@ +package t::lib::Page::Opac::OpacMain; + +# Copyright 2015 Open Source Freedom Fighters +# +# 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 . + +use Modern::Perl; +use Scalar::Util qw(blessed); +use Test::More; + +use t::lib::Page::Opac::OpacUser; + +use base qw(t::lib::Page::Opac); + +use Koha::Exception::BadParameter; + +=head NAME t::lib::Page::Opac::OpacMain + +=head SYNOPSIS + +PageObject providing page functionality as a service! + +=cut + +=head new + + my $opacmain = t::lib::Page::Opac::OpacMain->new(); + +Instantiates a WebDriver and loads the opac/opac-main.pl. +@PARAM1 HASHRef of optional and MANDATORY parameters +MANDATORY extra parameters: + none atm. + +@RETURNS t::lib::Page::Opac::OpacMain, ready for user actions! +=cut + +sub new { + my ($class, $params) = @_; + unless (ref($params) eq 'HASH' || (blessed($params) && $params->isa('t::lib::Page') )) { + $params = {}; + } + $params->{resource} = '/cgi-bin/koha/opac-main.pl'; + $params->{type} = 'opac'; + + my $self = $class->SUPER::new($params); + + return $self; +} + +################################################################################ +=head UI Mapping helper subroutines +See. Selenium documentation best practices for UI element mapping to common language descriptions. +=cut +################################################################################ + +sub _getPasswordLoginElements { + my ($self) = @_; + my $d = $self->getDriver(); + + my $submitButton = $d->find_element('form#auth input[type="submit"]'); + my $useridInput = $d->find_element('#userid'); + my $passwordInput = $d->find_element('#password'); + return ($submitButton, $useridInput, $passwordInput); +} + + + +################################################################################ +=head PageObject Services + +=cut +################################################################################ + +=head isPasswordLoginAvailable + + $page->isPasswordLoginAvailable(); + +@RETURN t::lib::Page-object +@CROAK if password login is unavailable. +=cut + +sub isPasswordLoginAvailable { + my $self = shift; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + $self->_getPasswordLoginElements(); + ok(1, "OpacMain PasswordLogin available"); + return $self; +} + +sub doPasswordLogin { + my ($self, $username, $password) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my ($submitButton, $useridInput, $passwordInput) = $self->_getPasswordLoginElements(); + $useridInput->send_keys($username); + $passwordInput->send_keys($password); + $submitButton->click(); + $self->debugTakeSessionSnapshot(); + + my $cookies = $d->get_all_cookies(); + my @cgisessid = grep {$_->{name} eq 'CGISESSID'} @$cookies; + + my $loggedinusernameSpan = $d->find_element('span.loggedinusername'); + ok(($cgisessid[0]), "OpacMain PasswordLogin succeeded"); #We have the element && Cookie CGISESSID defined! + + return t::lib::Page::Opac::OpacUser->rebrandFromPageObject($self); +} + +1; #Make the compiler happy! \ No newline at end of file diff --git a/t/lib/Page/Opac/OpacMemberentry.pm b/t/lib/Page/Opac/OpacMemberentry.pm new file mode 100644 index 0000000..9c4e450 --- /dev/null +++ b/t/lib/Page/Opac/OpacMemberentry.pm @@ -0,0 +1,161 @@ +package t::lib::Page::Opac::OpacMemberentry; + +# Copyright 2015 Open Source Freedom Fighters +# +# 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 Lice strongnse +# along with Koha; if not, see . + +use Modern::Perl; +use Scalar::Util qw(blessed); +use Test::More; + +use t::lib::Page::Opac::OpacUser; + +use base qw(t::lib::Page::Opac t::lib::Page::Opac::LeftNavigation); + +use Koha::Exception::BadParameter; + +=head NAME t::lib::Page::Opac::OpacMemberentry + +=head SYNOPSIS + +PageObject providing page functionality as a service! + +=cut + +=head new + + my $opacmemberentry = t::lib::Page::Opac::OpacMemberentry->new(); + +Instantiates a WebDriver and loads the opac/opac-memberentry.pl. +@PARAM1 HASHRef of optional and MANDATORY parameters +MANDATORY extra parameters: + none atm. + +@RETURNS t::lib::Page::Opac::OpacMemberentry, ready for user actions! +=cut + +sub new { + my ($class, $params) = @_; + unless (ref($params) eq 'HASH' || (blessed($params) && $params->isa('t::lib::Page') )) { + $params = {}; + } + $params->{resource} = '/cgi-bin/koha/opac-memberentry.pl'; + $params->{type} = 'opac'; + + my $self = $class->SUPER::new($params); + + return $self; +} + +################################################################################ +=head UI Mapping helper subroutines +See. Selenium documentation best practices for UI element mapping to common language descriptions. +=cut +################################################################################ + +sub _getInputFieldsForValidation { + my ($self) = @_; + my $d = $self->getDriver(); + + my $emailInput = $d->find_element('#borrower_email'); + my $emailProInput = $d->find_element('#borrower_emailpro'); + my $email_BInput = $d->find_element('#borrower_B_email'); + + my $phoneInput = $d->find_element('#borrower_phone'); + my $phoneProInput = $d->find_element('#borrower_phonepro'); + my $phone_BInput = $d->find_element('#borrower_B_phone'); + + return ($emailInput, $emailProInput, $email_BInput, $phoneInput, $phoneProInput, $phone_BInput); +} + + +################################################################################ +=head PageObject Services + +=cut +################################################################################ + +=head isFieldsAvailable + + $page->isFieldsAvailable(); + +@RETURN t::lib::Page-object +@CROAK if unable to find required fields. +=cut + +sub setEmail { + my ($self, $input) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my ($emailInput, $emailProInput, $email_BInput, $phoneInput, $phoneProInput, $phone_BInput) = $self->_getInputFieldsForValidation(); + + $emailInput->send_keys($input); + $emailProInput->send_keys($input); + $email_BInput->send_keys($input); + ok(1, "OpacMemberentry Wrote \"$input\" to email fields."); + + return $self; +} + +sub setPhone { + my ($self, $input) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my ($emailInput, $emailProInput, $email_BInput, $phoneInput, $phoneProInput, $phone_BInput) = $self->_getInputFieldsForValidation(); + + $phoneInput->send_keys($input); + $phoneProInput->send_keys($input); + $phone_BInput->send_keys($input); + ok(1, "OpacMemberentry Wrote \"$input\" to phone fields."); + + return $self; +} + +sub submitForm { + my ($self, $valid) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $submitButton = $d->find_element('form#memberentry-form input[type="submit"]'); + $submitButton->click(); + + $self->debugTakeSessionSnapshot(); + + if ($valid) { + my $submitted = $d->find_element('#update-submitted'); + ok(1, "OpacMemberentry Submit changes success"); + } else { + my @notsubmitted = $d->find_elements('form#memberentry-form label[id^="borrower_"][id$="-error"]', 'css'); + my $error_ids = ""; + + foreach my $el_id (@notsubmitted){ + my $attr_id = $el_id->get_attribute("id"); + $attr_id =~ s/borrower_//g; + $attr_id =~ s/-error//g; + $error_ids .= "'".$attr_id . "' "; + } + + ok(1, "OpacMemberentry Submit changes ". $error_ids .", validation error (as expected)."); + } + + + + return t::lib::Page::Opac::OpacUser->rebrandFromPageObject($self); +} + +1; \ No newline at end of file diff --git a/t/lib/Page/Opac/OpacMessaging.pm b/t/lib/Page/Opac/OpacMessaging.pm new file mode 100644 index 0000000..acb0aee --- /dev/null +++ b/t/lib/Page/Opac/OpacMessaging.pm @@ -0,0 +1,101 @@ +package t::lib::Page::Opac::OpacMessaging; + +# Copyright 2015 Open Source Freedom Fighters +# +# 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 Lice strongnse +# along with Koha; if not, see . + +use Modern::Perl; +use Scalar::Util qw(blessed); +use Test::More; + +use t::lib::Page::Opac::OpacUser; + +use base qw(t::lib::Page::Opac t::lib::Page::Opac::LeftNavigation); + +use Koha::Exception::BadParameter; + +=head NAME t::lib::Page::Opac::OpacMessaging + +=head SYNOPSIS + +PageObject providing page functionality as a service! + +=cut + +=head new + + my $opacmemberentry = t::lib::Page::Opac::OpacMessaging->new(); + +Instantiates a WebDriver and loads the opac/opac-messaging.pl. +@PARAM1 HASHRef of optional and MANDATORY parameters +MANDATORY extra parameters: + none atm. + +@RETURNS t::lib::Page::Opac::OpacMessaging, ready for user actions! +=cut + +sub new { + my ($class, $params) = @_; + unless (ref($params) eq 'HASH' || (blessed($params) && $params->isa('t::lib::Page') )) { + $params = {}; + } + $params->{resource} = '/cgi-bin/koha/opac-messaging.pl'; + $params->{type} = 'opac'; + + my $self = $class->SUPER::new($params); + + return $self; +} + +sub _getMessagingPreferenceCheckboxes { + my ($self) = @_; + my $d = $self->getDriver(); + + my @email_prefs = $d->find_elements('input[type="checkbox"][id^="email"]'); + my @phone_prefs = $d->find_elements('input[type="checkbox"][id^="phone"]'); + my @sms_prefs = $d->find_elements('input[type="checkbox"][id^="sms"]'); + + return { email => \@email_prefs, phone => \@phone_prefs, sms => \@sms_prefs }; +} + +################################################################################ +=head UI Mapping helper subroutines +See. Selenium documentation best practices for UI element mapping to common language descriptions. +=cut +################################################################################ + +sub checkMessagingPreferencesSet { + my ($self, $valid, @prefs) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + foreach my $type (@prefs){ + my @this_pref = $d->find_elements('input[type="checkbox"][id^="'.$type.'"]'); + + my $ok = 0; + + foreach my $checkbox (@this_pref){ + ok(0, "Opac Messaging $type checkbox ".$checkbox->get_attribute('id')." not checked") if !$checkbox->is_selected() and $valid; + ok(0, "Opac Messaging $type checkbox ".$checkbox->get_attribute('id')." checked (not supposed to be)") if $checkbox->is_selected() and !$valid; + $ok = 1; + } + ok($ok, "Opac Messaging $type checkboxes ok (all " . (($valid) ? 'checked':'unchecked') . ")"); + } + + return $self; +} + +1; diff --git a/t/lib/Page/Opac/OpacPaycollect.pm b/t/lib/Page/Opac/OpacPaycollect.pm new file mode 100644 index 0000000..e980ed6 --- /dev/null +++ b/t/lib/Page/Opac/OpacPaycollect.pm @@ -0,0 +1,94 @@ +package t::lib::Page::Opac::OpacPaycollect; + +# Copyright 2016 KohaSuomi +# +# 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 Lice strongnse +# along with Koha; if not, see . + +use Modern::Perl; +use Scalar::Util qw(blessed); +use Test::More; + +use t::lib::Page::Opac::OpacPaycollect; + +use base qw(t::lib::Page::Opac t::lib::Page::Opac::LeftNavigation); + +use Koha::Exception::BadParameter; + +=head NAME t::lib::Page::Opac::OpacPaycollect + +=head SYNOPSIS + +PageObject providing page functionality as a service! + +=cut + +=head new + + my $opacpaycollect = t::lib::Page::Opac::OpacPaycollect->new(); + +Instantiates a WebDriver and loads the opac/opac-paycollect.pl. +@PARAM1 HASHRef of optional and MANDATORY parameters +MANDATORY extra parameters: + none atm. + +@RETURNS t::lib::Page::Opac::OpacPaycollect, ready for user actions! +=cut + +sub new { + my ($class, $params) = @_; + unless (ref($params) eq 'HASH' || (blessed($params) && $params->isa('t::lib::Page') )) { + $params = {}; + } + $params->{resource} = '/cgi-bin/koha/opac-paycollect.pl'; + $params->{type} = 'opac'; + + my $self = $class->SUPER::new($params); + + return $self; +} + +################################################################################ +=head UI Mapping helper subroutines +See. Selenium documentation best practices for UI element mapping to common language descriptions. +=cut +################################################################################ + +sub isPreparing { + my ($self, $status) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $h3 = $d->find_element("//h3[contains(.,\"Preparing to pay fines\")]",'xpath'); + is($h3->get_text(), "Preparing to pay fines", "Opac preparing to redirect user to online shop (but skipping online shop in these tests)"); + my $continueLink = $d->find_element("//p[contains(.,\"automatically redirected to\")]/a[text()=\"online payment\"]", "xpath"); + + $continueLink->click(); + ok(1, "Opac Clicked to continue with the payment"); + + return $self; +} + +sub isPaymentPaid { + my ($self, $status) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $h3 = $d->find_element("//h3[contains(.,\"You have no outstanding\")]",'xpath'); + ok(1, "Opac Skipped online store. No outstanding fines"); + + return $self; +} +1; diff --git a/t/lib/Page/Opac/OpacSearch.pm b/t/lib/Page/Opac/OpacSearch.pm new file mode 100644 index 0000000..d1a8140 --- /dev/null +++ b/t/lib/Page/Opac/OpacSearch.pm @@ -0,0 +1,142 @@ +package t::lib::Page::Opac::OpacSearch; + +# Copyright 2015 Open Source Freedom Fighters +# +# 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 . + +use Modern::Perl; +use Scalar::Util qw(blessed); +use Test::More; + +use t::lib::Page::PageUtils; +use t::lib::Page::Opac::OpacMain; +use t::lib::Page::Opac::OpacSearchHistory; + +use base qw(t::lib::Page::Opac); + +use Koha::Exception::BadParameter; + +=head NAME t::lib::Page::Opac::OpacSearch + +=head SYNOPSIS + +PageObject providing page functionality as a service! + +=cut + +=head new + + my $opacsearch = t::lib::Page::Opac::OpacSearch->new(); + +Instantiates a WebDriver and loads the opac/opac-search.pl. +@PARAM1 HASHRef of optional and MANDATORY parameters +MANDATORY extra parameters: + none atm. + +@RETURNS t::lib::Page::Opac::OpacSearch, ready for user actions! +=cut + +sub new { + my ($class, $params) = @_; + unless (ref($params) eq 'HASH' || (blessed($params) && $params->isa('t::lib::Page') )) { + $params = {}; + } + $params->{resource} = '/cgi-bin/koha/opac-search.pl'; + $params->{type} = 'opac'; + + my $self = $class->SUPER::new($params); + + return $self; +} + + +################################################################################ +=head UI Mapping helper subroutines +See. Selenium documentation best practices for UI element mapping to common language descriptions. +=cut +################################################################################ + +sub _findSearchFieldElements { + my ($self, $searchField) = @_; + my $d = $self->getDriver(); + $searchField = '0' unless $searchField; + + my $indexSelect = $d->find_element("#search-field_$searchField"); + my $termInput = $d->find_element("#search-field_$searchField + input[name='q']"); + my $searchSubmit = $d->find_element("input[type='submit'].btn-success"); #Returns the first instance. + return ($indexSelect, $termInput, $searchSubmit); +} + + + +################################################################################ +=head PageObject Services + +=cut +################################################################################ + +=head doSetSearchFieldTerm + +Sets the search index and term for one of the (by default) three search fields. +@PARAM1, Integer, which search field to put the parameters into? + Starts from 0 == the topmost search field. +@PARAM2, String, the index to use. Undef if you want to use whatever there is. + Use the english index full name, eg. "Keyword", "Title", "Author". +@PARAM3, String, the search term. This replaces any existing search terms in the search field. +=cut + +sub doSetSearchFieldTerm { + my ($self, $searchField, $selectableIndex, $term) = @_; + $searchField = '0' unless $searchField; #Trouble with Perl interpreting 0 + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my ($indexSelect, $termInput, $searchSubmit) = $self->_findSearchFieldElements($searchField); + + if ($selectableIndex) { + t::lib::Page::PageUtils::displaySelectsOptions($d, $indexSelect); + my $optionElement = t::lib::Page::PageUtils::getSelectElementsOptionByName($d, $indexSelect, $selectableIndex); + $optionElement->click(); + } + + if ($term) { + $termInput->clear(); + $termInput->send_keys($term); + } + else { + Koha::Exception::BadParameter->throw("doSetSearchFieldTerm():> Parameter \$main is mandatory but is missing? Parameters as follow\n: @_"); + } + + $selectableIndex = '' unless $selectableIndex; + ok(1, "SearchField parameters '$selectableIndex' and '$term' set."); + $self->debugTakeSessionSnapshot(); + return $self; +} + +sub doSearchSubmit { + my ($self) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my ($indexSelect, $termInput, $searchSubmit) = $self->_findSearchFieldElements(0); #We just want the submit button + $searchSubmit->click(); + $self->debugTakeSessionSnapshot(); + + ok(($d->get_title() =~ /Results of search/), "SearchField search."); + return $self; +} + +1; #Make the compiler happy! diff --git a/t/lib/Page/Opac/OpacSearchHistory.pm b/t/lib/Page/Opac/OpacSearchHistory.pm new file mode 100644 index 0000000..136d889 --- /dev/null +++ b/t/lib/Page/Opac/OpacSearchHistory.pm @@ -0,0 +1,121 @@ +package t::lib::Page::Opac::OpacSearchHistory; + +# Copyright 2015 Open Source Freedom Fighters +# +# 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 . + +use Modern::Perl; +use Test::More; + +use base qw(t::lib::Page::Opac t::lib::Page::Opac::LeftNavigation); + +use Koha::Exception::FeatureUnavailable; + +=head NAME t::lib::Page::Opac::OpacSearchHistory + +=head SYNOPSIS + +PageObject providing page functionality as a service! + +=cut + +=head new + +YOU CANNOT GET HERE WITHOUT LOGGING IN FIRST! + +=cut + +sub new { + Koha::Exception::FeatureUnavailable->throw(error => __PACKAGE__."->new():> You must login first to navigate to this page!"); +} + +################################################################################ +=head UI Mapping helper subroutines +See. Selenium documentation best practices for UI element mapping to common language descriptions. +=cut +################################################################################ + +sub _getAllSearchHistories { + my ($self) = @_; + my $d = $self->getDriver(); + + $self->pause(500); #Wait for datatables to load the page. + my $histories = $d->find_elements("table.historyt tr"); + #First index has the table header, so skip that. + shift @$histories; + for (my $i=0 ; $i[$i] = $self->_castSearchHistoryRowToHash($histories->[$i]); + } + return $histories; +} + + + +################################################################################ +=head PageObject Services + +=cut +################################################################################ + +=head testDoSearchHistoriesExist + + $opacsearchhistory->testDoSearchHistoriesExist([ 'maximus', + 'julius', + 'titus', + ]); +@PARAM1 ARRAYRef of search strings shown in the opac-search-history.pl -page. + These search strings need only be contained in the displayed values. +=cut + +sub testDoSearchHistoriesExist { + my ($self, $searchStrings) = @_; + my $d = $self->getDriver(); + $self->debugTakeSessionSnapshot(); + + my $histories = $self->_getAllSearchHistories(); + foreach my $s (@$searchStrings) { + + my $matchFound; + foreach my $h (@$histories) { + if ($h->{searchStringA}->get_text() =~ /$s/) { + $matchFound = $h->{searchStringA}->get_text(); + last(); + } + } + ok($matchFound =~ /$s/, "SearchHistory $s exists."); + } + return $self; +} + +sub _castSearchHistoryRowToHash { + my ($self, $historyRow) = @_; + my $d = $self->getDriver(); + + my $checkbox = $d->find_child_element($historyRow, "input[type='checkbox']","css"); + my $date = $d->find_child_element($historyRow, "span[title]","css"); + $date = $date->get_text(); + my $searchStringA = $d->find_child_element($historyRow, "a + a","css"); + my $resultsCount = $d->find_child_element($historyRow, "td + td + td + td","css"); + + my $sh = { checkbox => $checkbox, + date => $date, + searchStringA => $searchStringA, + resultsCount => $resultsCount, + }; + return $sh; +} + +1; #Make the compiler happy! \ No newline at end of file diff --git a/t/lib/Page/Opac/OpacUser.pm b/t/lib/Page/Opac/OpacUser.pm new file mode 100644 index 0000000..38fda04 --- /dev/null +++ b/t/lib/Page/Opac/OpacUser.pm @@ -0,0 +1,64 @@ +package t::lib::Page::Opac::OpacUser; + +# Copyright 2015 Open Source Freedom Fighters +# +# 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 . + +use Modern::Perl; + +use base qw(t::lib::Page::Opac t::lib::Page::Opac::LeftNavigation); + +use Koha::Exception::FeatureUnavailable; + +=head NAME t::lib::Page::Opac::OpacUser + +=head SYNOPSIS + +PageObject providing page functionality as a service! + +=cut + +=head new + +YOU CANNOT GET HERE WITHOUT LOGGING IN FIRST! +Navigate here from opac-main.pl for example. +=cut + +sub new { + Koha::Exception::FeatureUnavailable->throw(error => __PACKAGE__."->new():> You must login first to navigate to this page!"); +} + +################################################################################ +=head UI Mapping helper subroutines +See. Selenium documentation best practices for UI element mapping to common language descriptions. +=cut +################################################################################ + + + + + +################################################################################ +=head PageObject Services + +=cut +################################################################################ + + + + + +1; #Make the compiler happy! diff --git a/t/lib/Page/PageUtils.pm b/t/lib/Page/PageUtils.pm new file mode 100644 index 0000000..77ddae0 --- /dev/null +++ b/t/lib/Page/PageUtils.pm @@ -0,0 +1,69 @@ +package t::lib::Page::PageUtils; + +# Copyright 2015 Open Source Freedom Fighters +# +# 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 . + +use Modern::Perl; + +use Koha::Exception::UnknownObject; + +=head NAME t::lib::Page::PageUtils + +=head SYNOPSIS + +Contains all kinds of helper functions used all over the PageObject testing framework. + +=cut + +sub getSelectElementsOptionByName { + my ($d, $selectElement, $optionName) = @_; + + my $options = $d->find_child_elements($selectElement, "option", 'css'); + my $correctOption; + foreach my $option (@$options) { + if ($option->get_text() eq $optionName) { + $correctOption = $option; + last(); + } + } + + return $correctOption if $correctOption; + + ##Throw Exception because we didn't find the option element. + my @availableOptions; + foreach my $option (@$options) { + push @availableOptions, $option->get_tag_name() .', value: '. $option->get_value() .', text: '. $option->get_text(); + } + Koha::Exception::UnknownObject->throw(error => + "getSelectElementsOptionByName():> Couldn't find the given option-element using '$optionName'. Available options:\n". + join("\n", @availableOptions)); +} + +sub displaySelectsOptions { + my ($d, $selectElement) = @_; + + my $options = $d->find_child_elements($selectElement, "option", 'css'); + if (scalar(@$options)) { + $selectElement->click() if $options->[0]->is_hidden(); + } + else { + Koha::Exception::UnknownObject->throw(error => + "_displaySelectsOptions():> element: ".$selectElement->get_tag_name()-', class: '.$selectElement->get_attribute("class").", doesn't have any option-elements?"); + } +} + +1; \ No newline at end of file -- 2.7.4