| Line 0
          
      
      
        Link Here | 
            
              |  |  | 1 | package t::lib::Page; | 
            
              | 2 |  | 
            
              | 3 | # Copyright 2015 Open Source Freedom Fighters | 
            
              | 4 | # | 
            
              | 5 | # This file is part of Koha. | 
            
              | 6 | # | 
            
              | 7 | # Koha is free software; you can redistribute it and/or modify it | 
            
              | 8 | # under the terms of the GNU General Public License as published by | 
            
              | 9 | # the Free Software Foundation; either version 3 of the License, or | 
            
              | 10 | # (at your option) any later version. | 
            
              | 11 | # | 
            
              | 12 | # Koha is distributed in the hope that it will be useful, but | 
            
              | 13 | # WITHOUT ANY WARRANTY; without even the implied warranty of | 
            
              | 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 
            
              | 15 | # GNU General Public License for more details. | 
            
              | 16 | # | 
            
              | 17 | # You should have received a copy of the GNU General Public License | 
            
              | 18 | # along with Koha; if not, see <http://www.gnu.org/licenses>. | 
            
              | 19 |  | 
            
              | 20 | use Modern::Perl; | 
            
              | 21 | use Test::More; | 
            
              | 22 |  | 
            
              | 23 | use C4::Context; | 
            
              | 24 |  | 
            
              | 25 | use t::lib::WebDriverFactory; | 
            
              | 26 |  | 
            
              | 27 | use Koha::Exception::BadParameter; | 
            
              | 28 | use Koha::Exception::SystemCall; | 
            
              | 29 |  | 
            
              | 30 | =head NAME t::lib::Page | 
            
              | 31 |  | 
            
              | 32 | =head SYNOPSIS | 
            
              | 33 |  | 
            
              | 34 | PageObject-pattern parent class. Extend this to implement specific pages shown to our users. | 
            
              | 35 |  | 
            
              | 36 | PageObjects are used to make robust and reusable integration test components to test | 
            
              | 37 | various front-end features. PageObjects load a Selenium::Remote::Driver implementation, | 
            
              | 38 | phantomjs by default and use this to do scripted user actions in the browser, | 
            
              | 39 | eg. clicking HTML-elements, accepting popup dialogs, entering text to input fields. | 
            
              | 40 |  | 
            
              | 41 | PageObjects encapsulate those very low-level operations into clear and easily usable | 
            
              | 42 | actions or services, like doPasswordLogin(). | 
            
              | 43 | PageObjects also seamlessly deal with navigation from one page to another, eg. | 
            
              | 44 |     my $mainpage = t::lib::Page::Mainpage->new(); | 
            
              | 45 |     $mainpage->doPasswordLogin('admin', '1234')->gotoPatrons()-> | 
            
              | 46 |                searchPatrons({keywordSearch => "Jane Doe"}); | 
            
              | 47 |  | 
            
              | 48 | =head Class variables | 
            
              | 49 |  | 
            
              | 50 | Selenium::Remote::Driver driver, contains the driver implementation used to run these tests | 
            
              | 51 | t::Page::Common::Header  header, the header page component (not implemented) | 
            
              | 52 | t::Page::Common::Footer  footer, the footer page component (not implemented) | 
            
              | 53 | Scalar                   userInteractionDelay, How many milliseconds to wait for javascript | 
            
              | 54 |                                                to stop processing by default after user actions? | 
            
              | 55 |  | 
            
              | 56 | =head DEBUGGING | 
            
              | 57 |  | 
            
              | 58 | Set Environment value | 
            
              | 59 |     $ENV{KOHA_PAGEOBJECT_DEBUG} = 1; | 
            
              | 60 | Before creating the first PageObject to enable debugging. | 
            
              | 61 | Debugging output is written to /tmp/PageObjectDebug/ by default, but you can change it | 
            
              | 62 | using the same environment variable | 
            
              | 63 |     $ENV{KOHA_PAGEOBJECT_DEBUG} = "/tmp/generalDebugging/"; | 
            
              | 64 |  | 
            
              | 65 | =cut | 
            
              | 66 |  | 
            
              | 67 | sub new { | 
            
              | 68 |     my ($class, $params) = @_; | 
            
              | 69 |     $params = _mergeDefaultConfig($params); | 
            
              | 70 |  | 
            
              | 71 |     my $self = {}; | 
            
              | 72 |     bless($self, $class); | 
            
              | 73 |     unless ($params->{driver}) { | 
            
              | 74 |         my ($driver) = t::lib::WebDriverFactory::getUserAgentDrivers({phantomjs => $params}); | 
            
              | 75 |         $self->{driver} = $driver; | 
            
              | 76 |     } | 
            
              | 77 |     $self->{type}     = $params->{type}; #This parameter is mandatory. _mergeDefaultConfig() dies without it. | 
            
              | 78 |     $self->{resource} = $params->{resource} || '/'; | 
            
              | 79 |     $self->{resource} .= "?".join('&', @{$params->{getParams}}) if $params->{getParams}; | 
            
              | 80 |     $self->{header}   = $params->{header}   || undef; | 
            
              | 81 |     $self->{footer}   = $params->{footer}   || undef; | 
            
              | 82 |  | 
            
              | 83 |     $self->{userInteractionDelay} = $params->{userInteractionDelay} || 500; | 
            
              | 84 |  | 
            
              | 85 |     $self->{driver}->set_window_size(1280, 960); | 
            
              | 86 |     $self->{driver}->get( $self->{resource} ); | 
            
              | 87 |  | 
            
              | 88 |     $self->debugSetEnvironment(); #If debugging is enabled | 
            
              | 89 |  | 
            
              | 90 |     return $self; | 
            
              | 91 | } | 
            
              | 92 |  | 
            
              | 93 | =head rebrandFromPageObject | 
            
              | 94 | When we are getting redirected from one page to another we rebrand the existing PageObject | 
            
              | 95 | as another PageObject to have the new page's services available. | 
            
              | 96 | =cut | 
            
              | 97 |  | 
            
              | 98 | sub rebrandFromPageObject { | 
            
              | 99 |     my ($class, $self) = @_; | 
            
              | 100 |     bless($self, $class); | 
            
              | 101 |     return $self; | 
            
              | 102 | } | 
            
              | 103 |  | 
            
              | 104 | =head _mergeDefaultConfig | 
            
              | 105 |  | 
            
              | 106 | @THROWS Koha::Exception::BadParameter | 
            
              | 107 | =cut | 
            
              | 108 |  | 
            
              | 109 | sub _mergeDefaultConfig { | 
            
              | 110 |     my ($params) = @_; | 
            
              | 111 |     unless (ref($params) eq 'HASH' && $params->{type}) { | 
            
              | 112 |         Koha::Exception::BadParameter->throw(error => "t::lib::Page:> When instantiating Page-objects, you must define the 'type'-parameter."); | 
            
              | 113 |     } | 
            
              | 114 |  | 
            
              | 115 |     my $testServerConfigs = C4::Context->config('testservers'); | 
            
              | 116 |     my $conf = $testServerConfigs->{ $params->{type} }; | 
            
              | 117 |     Koha::Exception::BadParameter->throw(error => "t::lib::Page:> Unknown 'type'-parameter '".$params->{type}."'. Values 'opac', 'staff' and 'rest' are supported.") | 
            
              | 118 |                 unless $conf; | 
            
              | 119 |     #Merge given $params-config on top of the $KOHA_CONF's testservers-directives | 
            
              | 120 |     @$conf{keys %$params} = values %$params; | 
            
              | 121 |     return $conf; | 
            
              | 122 | } | 
            
              | 123 |  | 
            
              | 124 | =head quit | 
            
              | 125 | Wrapper for Selenium::Remote::Driver->quit(), | 
            
              | 126 | Delete the session & close open browsers. | 
            
              | 127 |  | 
            
              | 128 | When ending this browser session, it is polite to quit, or there is a risk of leaving | 
            
              | 129 | floating test browsers floating around. | 
            
              | 130 | =cut | 
            
              | 131 |  | 
            
              | 132 | sub quit { | 
            
              | 133 |     my ($self) = @_; | 
            
              | 134 |     $self->getDriver()->quit(); | 
            
              | 135 | } | 
            
              | 136 |  | 
            
              | 137 | =head pause | 
            
              | 138 | Wrapper for Selenium::Remote::Driver->pause(), | 
            
              | 139 | =cut | 
            
              | 140 |  | 
            
              | 141 | sub pause { | 
            
              | 142 |     my ($self, $pauseMillis) = @_; | 
            
              | 143 |     $self->getDriver()->pause($pauseMillis); | 
            
              | 144 |     return $self; | 
            
              | 145 | } | 
            
              | 146 |  | 
            
              | 147 | =head isPasswordLoginAvailable | 
            
              | 148 |  | 
            
              | 149 |     $page->isPasswordLoginAvailable(); | 
            
              | 150 |  | 
            
              | 151 | @RETURN t::lib::Page-object | 
            
              | 152 | @CROAK if password login is unavailable. | 
            
              | 153 | =cut | 
            
              | 154 |  | 
            
              | 155 | sub isPasswordLoginAvailable { | 
            
              | 156 |     my $self = shift; | 
            
              | 157 |     my $d = $self->getDriver(); | 
            
              | 158 |     $self->debugTakeSessionSnapshot(); | 
            
              | 159 |  | 
            
              | 160 |     _getPasswordLoginElements($d); | 
            
              | 161 |     ok(($d->get_title() =~ /Log in to Koha/), "PasswordLogin available"); | 
            
              | 162 |     return $self; | 
            
              | 163 | } | 
            
              | 164 |  | 
            
              | 165 | sub doPasswordLogin { | 
            
              | 166 |     my ($self, $username, $password) = @_; | 
            
              | 167 |     my $d = $self->getDriver(); | 
            
              | 168 |     $self->debugTakeSessionSnapshot(); | 
            
              | 169 |  | 
            
              | 170 |     my ($submitButton, $useridInput, $passwordInput) = _getPasswordLoginElements($d); | 
            
              | 171 |     $useridInput->send_keys($username); | 
            
              | 172 |     $passwordInput->send_keys($password); | 
            
              | 173 |     $submitButton->click(); | 
            
              | 174 |     $self->debugTakeSessionSnapshot(); | 
            
              | 175 |  | 
            
              | 176 |     my $cookies = $d->get_all_cookies(); | 
            
              | 177 |     my @cgisessid = grep {$_->{name} eq 'CGISESSID'} @$cookies; | 
            
              | 178 |  | 
            
              | 179 |     ok(($d->get_title() !~ /Log in to Koha/ && #No longer in the login page | 
            
              | 180 |         $d->get_title() !~ /Access denied/ && | 
            
              | 181 |         $cgisessid[0]) #Cookie CGISESSID defined! | 
            
              | 182 |        , "PasswordLogin succeeded"); | 
            
              | 183 |  | 
            
              | 184 |     return $self; #After a succesfull password login, we are directed to the same page we tried to access. | 
            
              | 185 | } | 
            
              | 186 |  | 
            
              | 187 | sub _getPasswordLoginElements { | 
            
              | 188 |     my $d = shift; | 
            
              | 189 |     my $submitButton  = $d->find_element('#submit'); | 
            
              | 190 |     my $useridInput   = $d->find_element('#userid'); | 
            
              | 191 |     my $passwordInput = $d->find_element('#password'); | 
            
              | 192 |     return ($submitButton, $useridInput, $passwordInput); | 
            
              | 193 | } | 
            
              | 194 |  | 
            
              | 195 | sub doPasswordLogout { | 
            
              | 196 |     my ($self, $username, $password) = @_; | 
            
              | 197 |     my $d = $self->getDriver(); | 
            
              | 198 |     $self->debugTakeSessionSnapshot(); | 
            
              | 199 |  | 
            
              | 200 |     #Click the dropdown menu to make the logout-link visible | 
            
              | 201 |     my $logged_in_identifierA = $d->find_element('#drop3'); #What a nice and descriptive HTML element name! | 
            
              | 202 |     $logged_in_identifierA->click(); | 
            
              | 203 |  | 
            
              | 204 |     #Logout | 
            
              | 205 |     my $logoutA = $d->find_element('#logout'); | 
            
              | 206 |     $logoutA->click(); | 
            
              | 207 |  | 
            
              | 208 |     ok(($d->get_title() =~ /Log in to Koha/), "PasswordLogout succeeded"); | 
            
              | 209 |     return $self; #After a succesfull password logout, we are still in the same page we did before logout. | 
            
              | 210 | } | 
            
              | 211 |  | 
            
              | 212 | ################################################ | 
            
              | 213 |   ##  INTRODUCING OBJECT ACCESSORS  ## | 
            
              | 214 | ################################################ | 
            
              | 215 | sub setDriver { | 
            
              | 216 |     my ($self, $driver) = @_; | 
            
              | 217 |     $self->{driver} = $driver; | 
            
              | 218 | } | 
            
              | 219 | sub getDriver { | 
            
              | 220 |     my ($self) = @_; | 
            
              | 221 |     return $self->{driver}; | 
            
              | 222 | } | 
            
              | 223 |  | 
            
              | 224 | ################################################ | 
            
              | 225 |   ##  INTRODUCING TESTING HELPERS  ## | 
            
              | 226 | ################################################ | 
            
              | 227 | sub debugSetEnvironment { | 
            
              | 228 |     my ($self) = @_; | 
            
              | 229 |     if ($ENV{KOHA_PAGEOBJECT_DEBUG}) { | 
            
              | 230 |         $self->{debugSessionId} = sprintf("%03i",rand(999)); | 
            
              | 231 |         $self->{debugSessionTmpDirectory} = "/tmp/PageObjectDebug/"; | 
            
              | 232 |         $self->{debugSessionTmpDirectory} = $ENV{KOHA_PAGEOBJECT_DEBUG} if (not(ref($ENV{KOHA_PAGEOBJECT_DEBUG})) && length($ENV{KOHA_PAGEOBJECT_DEBUG}) > 1); | 
            
              | 233 |         my $error = system(("mkdir", "-p", $self->{debugSessionTmpDirectory})); | 
            
              | 234 |         Koha::Exception::SystemCall->throw(error => "Trying to create a temporary directory for PageObject debugging session '".$self->{debugSessionId}."' failed:\n  $?") | 
            
              | 235 |                 if $error; | 
            
              | 236 |         $self->{debugInternalCounter} = 1; | 
            
              | 237 |  | 
            
              | 238 |         print "\n\n--Starting PageObject debugging session '".$self->{debugSessionId}."'\n\n"; | 
            
              | 239 |     } | 
            
              | 240 | } | 
            
              | 241 |  | 
            
              | 242 | sub debugTakeSessionSnapshot { | 
            
              | 243 |     my ($self) = @_; | 
            
              | 244 |     if ($ENV{KOHA_PAGEOBJECT_DEBUG}) { | 
            
              | 245 |         my ($actionIdentifier, $actionFile) = $self->_debugGetSessionIdentifier(2); | 
            
              | 246 |  | 
            
              | 247 |         $self->_debugWriteHTML($actionIdentifier, $actionFile); | 
            
              | 248 |         $self->_debugWriteScreenshot($actionIdentifier, $actionFile); | 
            
              | 249 |         $self->{debugInternalCounter}++; | 
            
              | 250 |     } | 
            
              | 251 | } | 
            
              | 252 |  | 
            
              | 253 | sub _debugGetSessionIdentifier { | 
            
              | 254 |     my ($self, $callerDepth) = @_; | 
            
              | 255 |     $callerDepth = $callerDepth || 2; | 
            
              | 256 |     ##Create a unique and descriptive identifier for this program state. | 
            
              | 257 |     my ($package, $filename, $line, $subroutine) = caller($callerDepth); #Get where we are called from | 
            
              | 258 |     $subroutine = $2 if ($subroutine =~ /(::|->)([^:->]+)$/); #Get the last part of the package, the subroutine name. | 
            
              | 259 |     my $actionIdentifier = "[session '".$self->{debugSessionId}."', counter '".sprintf("%03i",$self->{debugInternalCounter})."', caller '$subroutine']"; | 
            
              | 260 |     my $actionFile = $self->{debugSessionId}.'_'.sprintf("%03i",$self->{debugInternalCounter}).'_'.$subroutine; | 
            
              | 261 |     return ($actionIdentifier, $actionFile); | 
            
              | 262 | } | 
            
              | 263 |  | 
            
              | 264 | sub _debugWriteHTML { | 
            
              | 265 |     require Data::Dumper; | 
            
              | 266 |     my ($self, $actionIdentifier, $actionFile) = @_; | 
            
              | 267 |     my $d = $self->getDriver(); | 
            
              | 268 |  | 
            
              | 269 |     ##Write the current Response data | 
            
              | 270 |     open(my $fh, ">:encoding(UTF-8)", $self->{debugSessionTmpDirectory}.$actionFile.'.html') | 
            
              | 271 |                 or die "Trying to open a filehandle for PageObject debugging output $actionIdentifier:\n  $@"; | 
            
              | 272 |     print $fh $d->get_title()."\n"; | 
            
              | 273 |     print $fh "ALL COOKIES DUMP:\n".Data::Dumper::Dumper($d->get_all_cookies()); | 
            
              | 274 |     print $fh $d->get_page_source()."\n"; | 
            
              | 275 |     close $fh; | 
            
              | 276 | } | 
            
              | 277 |  | 
            
              | 278 | sub _debugWriteScreenshot { | 
            
              | 279 |     my ($self, $actionIdentifier, $actionFile) = @_; | 
            
              | 280 |     my $d = $self->getDriver(); | 
            
              | 281 |  | 
            
              | 282 |     ##Write a screenshot of the view to file. | 
            
              | 283 |     my $ok = $d->capture_screenshot($self->{debugSessionTmpDirectory}.$actionFile.'.png'); | 
            
              | 284 |     Koha::Exception::SystemCall->throw(error => "Cannot capture a screenshot for PageObject $actionIdentifier") | 
            
              | 285 |                 unless $ok; | 
            
              | 286 | } | 
            
              | 287 | 1; #Make the compiler happy! |