Bugzilla – Attachment 41534 Details for
Bug 14536
PageObject-pattern base implementation
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 14536 - PageObject-pattern base implementation
Bug-14536---PageObject-pattern-base-implementation.patch (text/plain), 132.30 KB, created by
Olli-Antti Kivilahti
on 2015-08-17 12:00:47 UTC
(
hide
)
Description:
Bug 14536 - PageObject-pattern base implementation
Filename:
MIME Type:
Creator:
Olli-Antti Kivilahti
Created:
2015-08-17 12:00:47 UTC
Size:
132.30 KB
patch
obsolete
>From 07b75d88a1c625cce8db384f967ce5717394f5d4 Mon Sep 17 00:00:00 2001 >From: Olli-Antti Kivilahti <olli-antti.kivilahti@jns.fi> >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) Add more functionalty for handling messaging preferences > >- Fixes memberentry form submit to return Circulation PageObject. >- Prevents missing element errors at different steps of memberentry page. Patron >modification has multiple different steps (full edit, information edit, sms number edit, >library use edit, alternate address edit, alternative contact edit). Some of the input fields >required for validation are not always present in each step of these modifications. >- Adds functionality to check checkboxes and validate if they are checked or not >- Adds navigation to moremember edit links. >- Adds navigation to opac-messaging >--- > misc/devel/interactiveWebDriverShell.pl | 196 +++++++++++++++++++++ > t/db_dependent/Koha/Auth.t | 72 ++++++++ > t/lib/Page.pm | 294 ++++++++++++++++++++++++++++++++ > 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 | 208 ++++++++++++++++++++++ > t/lib/Page/Mainpage.pm | 64 +++++++ > t/lib/Page/Members/ApiKeys.pm | 200 ++++++++++++++++++++++ > t/lib/Page/Members/LeftNavigation.pm | 116 +++++++++++++ > t/lib/Page/Members/MemberFlags.pm | 143 ++++++++++++++++ > t/lib/Page/Members/Memberentry.pm | 261 ++++++++++++++++++++++++++++ > t/lib/Page/Members/Moremember.pm | 207 ++++++++++++++++++++++ > t/lib/Page/Members/Toolbar.pm | 144 ++++++++++++++++ > t/lib/Page/Opac.pm | 223 ++++++++++++++++++++++++ > t/lib/Page/Opac/LeftNavigation.pm | 149 ++++++++++++++++ > 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/OpacSearch.pm | 142 +++++++++++++++ > t/lib/Page/Opac/OpacSearchHistory.pm | 121 +++++++++++++ > t/lib/Page/Opac/OpacUser.pm | 64 +++++++ > t/lib/Page/PageUtils.pm | 69 ++++++++ > 25 files changed, 3699 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/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/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/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/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..72d4df9 >--- /dev/null >+++ b/misc/devel/interactiveWebDriverShell.pl >@@ -0,0 +1,196 @@ >+#!/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 <http://www.gnu.org/licenses>. >+ >+=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 = <<HELP; >+ >+interactiveWebDriverShell.pl >+ >+ Prepares a perl debugger session with the requested PageObject loaded. >+ Then you can easily guide the UserAgent through the web session. >+ >+ You should install Term::ReadLine::Gnu for a more pleasant debugging experience. >+ >+ -h --help This help! >+ >+ -p --page Which PageObject matching the given page you want to preload? >+ >+ -P --params List of parameters the PageObject must have. >+ >+ -L --login List of userid and password to automatically login to Koha. Eg: >+ ./interactiveWebDriverShell.pl -L admin 1234 -p members/moremember.pl -P 12 >+ >+ -l --list Lists available PageObjects and their matching --page -parameter >+ values. >+ >+EXAMPLE INVOCATIONS: >+ >+./interactiveWebDriverShell.pl -p mainpage.pl -L admin 1234 >+./interactiveWebDriverShell.pl -p members/moremember.pl -P 1 -L admin 1234 >+ >+USAGE: >+ >+Start the session from your shell >+ ..\$ misc/devel/interactiveWebDriverShell.pl -p mainpage.pl >+or >+Start the session from your shell with parameters >+ ..\$ misc/devel/interactiveWebDriverShell.pl -p members/moremember.pl -P 12 >+ >+Continue to the breakpoint set in this script >+ DB<1> 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"], >+ }, >+ "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..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 <http://www.gnu.org/licenses>. >+ >+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..595eab0 >--- /dev/null >+++ b/t/lib/Page.pm >@@ -0,0 +1,294 @@ >+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 <http://www.gnu.org/licenses>. >+ >+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 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 <http://www.gnu.org/licenses>. >+ >+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 <http://www.gnu.org/licenses>. >+ >+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 <http://www.gnu.org/licenses>. >+ >+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 <http://www.gnu.org/licenses>. >+ >+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..79636f3 >--- /dev/null >+++ b/t/lib/Page/Intra.pm >@@ -0,0 +1,208 @@ >+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 <http://www.gnu.org/licenses>. >+ >+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 <span> 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 <http://www.gnu.org/licenses>. >+ >+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 <http://www.gnu.org/licenses>. >+ >+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<scalar(@$apiKeyRows) ; $i++) { >+ #Iterate every apiKey in the apiKeys table and prefetch the interesting data as text and available action elements. >+ my $row = $apiKeyRows->[$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/LeftNavigation.pm b/t/lib/Page/Members/LeftNavigation.pm >new file mode 100644 >index 0000000..d9f2405 >--- /dev/null >+++ b/t/lib/Page/Members/LeftNavigation.pm >@@ -0,0 +1,116 @@ >+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 <http://www.gnu.org/licenses>. >+ >+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); >+} >+ >+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 <http://www.gnu.org/licenses>. >+ >+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..dfb6161 >--- /dev/null >+++ b/t/lib/Page/Members/Memberentry.pm >@@ -0,0 +1,261 @@ >+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 <http://www.gnu.org/licenses>. >+ >+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(); >+ >+ my $submitButton = $d->find_element('form#entryform input[type="submit"]'); >+ $submitButton->submit(); >+ $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; >\ No newline at end of file >diff --git a/t/lib/Page/Members/Moremember.pm b/t/lib/Page/Members/Moremember.pm >new file mode 100644 >index 0000000..0a6cd6d >--- /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 <http://www.gnu.org/licenses>. >+ >+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/Toolbar.pm b/t/lib/Page/Members/Toolbar.pm >new file mode 100644 >index 0000000..132a588 >--- /dev/null >+++ b/t/lib/Page/Members/Toolbar.pm >@@ -0,0 +1,144 @@ >+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 <http://www.gnu.org/licenses>. >+ >+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 $moreButton = $d->find_element("#searchtohold + div > button", 'css'); >+ >+ 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->{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(); >+ $elements->{edit}->click(); >+ 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! >\ 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 <http://www.gnu.org/licenses>. >+ >+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/LeftNavigation.pm b/t/lib/Page/Opac/LeftNavigation.pm >new file mode 100644 >index 0000000..ae10de5 >--- /dev/null >+++ b/t/lib/Page/Opac/LeftNavigation.pm >@@ -0,0 +1,149 @@ >+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 <http://www.gnu.org/licenses>. >+ >+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); >+} >+ >+1; #Make the compiler happy! >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 <http://www.gnu.org/licenses>. >+ >+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<scalar(@$apiKeyRows) ; $i++) { >+ #Iterate every apiKey in the apiKeys table and prefetch the interesting data as text and available action elements. >+ my $row = $apiKeyRows->[$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 <http://www.gnu.org/licenses>. >+ >+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 <http://www.gnu.org/licenses>. >+ >+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 <http://www.gnu.org/licenses>. >+ >+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/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 <http://www.gnu.org/licenses>. >+ >+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 <http://www.gnu.org/licenses>. >+ >+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<scalar(@$histories) ; $i++) { >+ $histories->[$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 <http://www.gnu.org/licenses>. >+ >+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 <http://www.gnu.org/licenses>. >+ >+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
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 14536
:
41023
|
41052
|
41057
|
41121
|
41156
|
41254
|
41460
|
41524
|
41525
|
41534
|
41678
|
42060
|
42111
|
42118
|
42136
|
42160
|
42433
|
42435
|
42716
|
44410
|
47254
|
63245