From 177ccd77d110210575de24727f1cfe46b3acebbb 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! --- misc/devel/interactiveWebDriverShell.pl | 190 +++++++++++++++++++++++++++ t/db_dependent/Koha/Auth.t | 72 +++++++++++ t/lib/Page.pm | 222 +++++++++++++++++++++++++++++++ t/lib/Page/Intra.pm | 195 ++++++++++++++++++++++++++++ t/lib/Page/Mainpage.pm | 64 +++++++++ t/lib/Page/Members/MemberFlags.pm | 143 ++++++++++++++++++++ t/lib/Page/Members/Moremember.pm | 89 +++++++++++++ t/lib/Page/Opac.pm | 223 ++++++++++++++++++++++++++++++++ t/lib/Page/Opac/OpacMain.pm | 125 ++++++++++++++++++ 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 ++++++++++ 13 files changed, 1719 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/Intra.pm create mode 100644 t/lib/Page/Mainpage.pm create mode 100644 t/lib/Page/Members/MemberFlags.pm create mode 100644 t/lib/Page/Members/Moremember.pm create mode 100644 t/lib/Page/Opac.pm create mode 100644 t/lib/Page/Opac/OpacMain.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..fdd5eb5 --- /dev/null +++ b/misc/devel/interactiveWebDriverShell.pl @@ -0,0 +1,190 @@ +#!/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/member-flags.pl" => + { package => "t::lib::Page::Members::MemberFlags", + urlEndpoint => "members/member-flags.pl", + status => "not implemented", + params => ["borrowernumber"], + }, +################################################################################ + ########## 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..441070d --- /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::BorrowerFactory; + +##Setting up the test context +my $testContext = {}; + +my $password = '1234'; +my $borrowerFactory = t::lib::TestObjects::BorrowerFactory->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..f46711b --- /dev/null +++ b/t/lib/Page.pm @@ -0,0 +1,222 @@ +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. +=cut + +sub rebrandFromPageObject { + my ($class, $self) = @_; + bless($self, $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; +} + +################################################ + ## 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/Intra.pm b/t/lib/Page/Intra.pm new file mode 100644 index 0000000..464b45a --- /dev/null +++ b/t/lib/Page/Intra.pm @@ -0,0 +1,195 @@ +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 _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 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/MemberFlags.pm b/t/lib/Page/Members/MemberFlags.pm new file mode 100644 index 0000000..60448bd --- /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); + +=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/Moremember.pm b/t/lib/Page/Members/Moremember.pm new file mode 100644 index 0000000..82afa62 --- /dev/null +++ b/t/lib/Page/Members/Moremember.pm @@ -0,0 +1,89 @@ +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 base qw(t::lib::Page::Intra); + +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 +################################################################################ + + + + + +################################################################################ +=head PageObject Services + +=cut +################################################################################ + + + + + +1; #Make the compiler happy! \ No newline at end of file diff --git a/t/lib/Page/Opac.pm b/t/lib/Page/Opac.pm new file mode 100644 index 0000000..b7d805d --- /dev/null +++ b/t/lib/Page/Opac.pm @@ -0,0 +1,223 @@ +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 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 + my $txt = $headerElements->{login}->get_text(); + 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); + ok((), "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/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/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..70e6383 --- /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); + +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..975750a --- /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); + +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! \ No newline at end of file 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 -- 1.9.1