init III
This commit is contained in:
574
Perl OTRS/Kernel/System/UnitTest/Driver.pm
Normal file
574
Perl OTRS/Kernel/System/UnitTest/Driver.pm
Normal file
@@ -0,0 +1,574 @@
|
||||
# --
|
||||
# Copyright (C) 2001-2019 OTRS AG, https://otrs.com/
|
||||
# --
|
||||
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
|
||||
# the enclosed file COPYING for license information (GPL). If you
|
||||
# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
|
||||
# --
|
||||
|
||||
package Kernel::System::UnitTest::Driver;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Storable();
|
||||
use Term::ANSIColor();
|
||||
|
||||
# UnitTest helper must be loaded to override the builtin time functions!
|
||||
use Kernel::System::UnitTest::Helper;
|
||||
|
||||
use Kernel::System::VariableCheck qw(DataIsDifferent);
|
||||
|
||||
our @ObjectDependencies = (
|
||||
'Kernel::Config',
|
||||
'Kernel::System::Log',
|
||||
'Kernel::System::Main',
|
||||
);
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Kernel::System::UnitTest::Driver - unit test file execution wrapper
|
||||
|
||||
=head1 PUBLIC INTERFACE
|
||||
|
||||
=head2 new()
|
||||
|
||||
create unit test driver object. Do not use it directly, instead use:
|
||||
|
||||
my $Driver = $Kernel::OM->Create(
|
||||
'Kernel::System::UnitTest::Driver',
|
||||
ObjectParams => {
|
||||
Verbose => $Self->{Verbose},
|
||||
ANSI => $Self->{ANSI},
|
||||
},
|
||||
);
|
||||
|
||||
=cut
|
||||
|
||||
sub new {
|
||||
my ( $Type, %Param ) = @_;
|
||||
|
||||
# allocate new hash for object
|
||||
my $Self = {};
|
||||
bless( $Self, $Type );
|
||||
|
||||
$Self->{ANSI} = $Param{ANSI};
|
||||
$Self->{Verbose} = $Param{Verbose};
|
||||
|
||||
# We use an output buffering mechanism if Verbose is not set. Only failed tests will be output in this case.
|
||||
|
||||
# Make sure stuff is always flushed to keep it in the right order.
|
||||
*STDOUT->autoflush(1);
|
||||
*STDERR->autoflush(1);
|
||||
$Self->{OriginalSTDOUT} = *STDOUT;
|
||||
$Self->{OriginalSTDOUT}->autoflush(1);
|
||||
$Self->{OutputBuffer} = '';
|
||||
|
||||
# Report results via file.
|
||||
$Self->{ResultDataFile} = $Kernel::OM->Get('Kernel::Config')->Get('Home') . '/var/tmp/UnitTest.dump';
|
||||
unlink $Self->{ResultDataFile}; # purge if exists
|
||||
|
||||
return $Self;
|
||||
}
|
||||
|
||||
=head2 Run()
|
||||
|
||||
executes a single unit test file and provides it with an empty environment (fresh C<ObjectManager> instance).
|
||||
|
||||
This method assumes that it runs in a dedicated child process just for this one unit test.
|
||||
This process forking is done in L<Kernel::System::UnitTest>, which creates one child process per test file.
|
||||
|
||||
All results will be collected and written to a C<var/tmp/UnitTest.dump> file that the main process will
|
||||
load to collect all results.
|
||||
|
||||
=cut
|
||||
|
||||
sub Run {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
my $File = $Param{File};
|
||||
|
||||
my $UnitTestFile = $Kernel::OM->Get('Kernel::System::Main')->FileRead(
|
||||
Location => $File,
|
||||
);
|
||||
|
||||
if ( !$UnitTestFile ) {
|
||||
$Self->True( 0, "ERROR: $!: $File" );
|
||||
print STDERR "ERROR: $!: $File\n";
|
||||
$Self->_SaveResults();
|
||||
return;
|
||||
}
|
||||
|
||||
print "+-------------------------------------------------------------------+\n";
|
||||
print "$File:\n";
|
||||
print "+-------------------------------------------------------------------+\n";
|
||||
|
||||
# Use non-overridden time() function.
|
||||
my $FileStartTime = CORE::time; ## no critic
|
||||
|
||||
# Create a new scope to be sure to destroy local object of the test files.
|
||||
{
|
||||
# Make sure every UT uses its own clean environment.
|
||||
## nofilter(TidyAll::Plugin::OTRS::Perl::ObjectManagerCreation)
|
||||
local $Kernel::OM = Kernel::System::ObjectManager->new(
|
||||
'Kernel::System::Log' => {
|
||||
LogPrefix => 'OTRS-otrs.UnitTest',
|
||||
},
|
||||
);
|
||||
|
||||
# Provide $Self as 'Kernel::System::UnitTest' for convenience.
|
||||
$Kernel::OM->ObjectInstanceRegister(
|
||||
Package => 'Kernel::System::UnitTest::Driver',
|
||||
Object => $Self,
|
||||
Dependencies => [],
|
||||
);
|
||||
|
||||
$Self->{OutputBuffer} = '';
|
||||
local *STDOUT = *STDOUT;
|
||||
local *STDERR = *STDERR;
|
||||
if ( !$Self->{Verbose} ) {
|
||||
undef *STDOUT;
|
||||
undef *STDERR;
|
||||
open STDOUT, '>:utf8', \$Self->{OutputBuffer}; ## no critic
|
||||
open STDERR, '>:utf8', \$Self->{OutputBuffer}; ## no critic
|
||||
}
|
||||
|
||||
# HERE the actual tests are run.
|
||||
my $TestSuccess = eval ${$UnitTestFile}; ## no critic
|
||||
|
||||
if ( !$TestSuccess ) {
|
||||
if ($@) {
|
||||
$Self->True( 0, "ERROR: Error in $File: $@" );
|
||||
}
|
||||
else {
|
||||
$Self->True( 0, "ERROR: $File did not return a true value." );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Use non-overridden time() function.
|
||||
$Self->{ResultData}->{Duration} = CORE::time - $FileStartTime; ## no critic
|
||||
|
||||
if ( $Self->{SeleniumData} ) {
|
||||
$Self->{ResultData}->{SeleniumData} = $Self->{SeleniumData};
|
||||
}
|
||||
|
||||
print { $Self->{OriginalSTDOUT} } "\n";
|
||||
|
||||
return $Self->_SaveResults();
|
||||
}
|
||||
|
||||
=head2 True()
|
||||
|
||||
test for a scalar value that evaluates to true.
|
||||
|
||||
Send a scalar value to this function along with the test's name:
|
||||
|
||||
$UnitTestObject->True(1, 'Test Name');
|
||||
|
||||
$UnitTestObject->True($ParamA, 'Test Name');
|
||||
|
||||
Internally, the function receives this value and evaluates it to see
|
||||
if it's true, returning 1 in this case or undef, otherwise.
|
||||
|
||||
my $TrueResult = $UnitTestObject->True(
|
||||
$TestValue,
|
||||
'Test Name',
|
||||
);
|
||||
|
||||
=cut
|
||||
|
||||
sub True {
|
||||
my ( $Self, $True, $Name ) = @_;
|
||||
|
||||
if ( !$Name ) {
|
||||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||||
Priority => 'error',
|
||||
Message => 'Need Name! E. g. True(\$A, \'Test Name\')!'
|
||||
);
|
||||
$Self->_Print( 0, 'ERROR: Need Name! E. g. True(\$A, \'Test Name\')' );
|
||||
return;
|
||||
}
|
||||
|
||||
if ($True) {
|
||||
$Self->_Print( 1, $Name );
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
$Self->_Print( 0, $Name );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
=head2 False()
|
||||
|
||||
test for a scalar value that evaluates to false.
|
||||
|
||||
It has the same interface as L</True()>, but tests
|
||||
for a false value instead.
|
||||
|
||||
=cut
|
||||
|
||||
sub False {
|
||||
my ( $Self, $False, $Name ) = @_;
|
||||
|
||||
if ( !$Name ) {
|
||||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||||
Priority => 'error',
|
||||
Message => 'Need Name! E. g. False(\$A, \'Test Name\')!'
|
||||
);
|
||||
$Self->_Print( 0, 'ERROR: Need Name! E. g. False(\$A, \'Test Name\')' );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !$False ) {
|
||||
$Self->_Print( 1, $Name );
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
$Self->_Print( 0, $Name );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
=head2 Is()
|
||||
|
||||
compares two scalar values for equality.
|
||||
|
||||
To this function you must send a pair of scalar values to compare them,
|
||||
and the name that the test will take, this is done as shown in the examples
|
||||
below.
|
||||
|
||||
$UnitTestObject->Is($A, $B, 'Test Name');
|
||||
|
||||
Returns 1 if the values were equal, or undef otherwise.
|
||||
|
||||
my $IsResult = $UnitTestObject->Is(
|
||||
$ValueFromFunction, # test data
|
||||
1, # expected value
|
||||
'Test Name',
|
||||
);
|
||||
|
||||
=cut
|
||||
|
||||
sub Is {
|
||||
my ( $Self, $Test, $ShouldBe, $Name ) = @_;
|
||||
|
||||
if ( !$Name ) {
|
||||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||||
Priority => 'error',
|
||||
Message => 'Need Name! E. g. Is(\$A, \$B, \'Test Name\')!'
|
||||
);
|
||||
$Self->_Print( 0, 'ERROR: Need Name! E. g. Is(\$A, \$B, \'Test Name\')' );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !defined $Test && !defined $ShouldBe ) {
|
||||
$Self->_Print( 1, "$Name (is 'undef')" );
|
||||
return 1;
|
||||
}
|
||||
elsif ( !defined $Test && defined $ShouldBe ) {
|
||||
$Self->_Print( 0, "$Name (is 'undef' should be '$ShouldBe')" );
|
||||
return;
|
||||
}
|
||||
elsif ( defined $Test && !defined $ShouldBe ) {
|
||||
$Self->_Print( 0, "$Name (is '$Test' should be 'undef')" );
|
||||
return;
|
||||
}
|
||||
elsif ( $Test eq $ShouldBe ) {
|
||||
$Self->_Print( 1, "$Name (is '$ShouldBe')" );
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
$Self->_Print( 0, "$Name (is '$Test' should be '$ShouldBe')" );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
=head2 IsNot()
|
||||
|
||||
compares two scalar values for inequality.
|
||||
|
||||
It has the same interface as L</Is()>, but tests
|
||||
for inequality instead.
|
||||
|
||||
=cut
|
||||
|
||||
sub IsNot {
|
||||
my ( $Self, $Test, $ShouldBe, $Name ) = @_;
|
||||
|
||||
if ( !$Name ) {
|
||||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||||
Priority => 'error',
|
||||
Message => 'Need Name! E. g. IsNot(\$A, \$B, \'Test Name\')!'
|
||||
);
|
||||
$Self->_Print( 0, 'ERROR: Need Name! E. g. IsNot(\$A, \$B, \'Test Name\')' );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !defined $Test && !defined $ShouldBe ) {
|
||||
$Self->_Print( 0, "$Name (is 'undef')" );
|
||||
return;
|
||||
}
|
||||
elsif ( !defined $Test && defined $ShouldBe ) {
|
||||
$Self->_Print( 1, "$Name (is 'undef')" );
|
||||
return 1;
|
||||
}
|
||||
elsif ( defined $Test && !defined $ShouldBe ) {
|
||||
$Self->_Print( 1, "$Name (is '$Test')" );
|
||||
return 1;
|
||||
}
|
||||
if ( $Test ne $ShouldBe ) {
|
||||
$Self->_Print( 1, "$Name (is '$Test')" );
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
$Self->_Print( 0, "$Name (is '$Test' should not be '$ShouldBe')" );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
=head2 IsDeeply()
|
||||
|
||||
compares complex data structures for equality.
|
||||
|
||||
To this function you must send the references to two data structures to be compared,
|
||||
and the name that the test will take, this is done as shown in the examples
|
||||
below.
|
||||
|
||||
$UnitTestObject-> IsDeeply($ParamA, $ParamB, 'Test Name');
|
||||
|
||||
Where $ParamA and $ParamB must be references to a structure (scalar, list or hash).
|
||||
|
||||
Returns 1 if the data structures are the same, or undef otherwise.
|
||||
|
||||
my $IsDeeplyResult = $UnitTestObject->IsDeeply(
|
||||
\%ResultHash, # test data
|
||||
\%ExpectedHash, # expected value
|
||||
'Dummy Test Name',
|
||||
);
|
||||
|
||||
=cut
|
||||
|
||||
sub IsDeeply {
|
||||
my ( $Self, $Test, $ShouldBe, $Name ) = @_;
|
||||
|
||||
if ( !$Name ) {
|
||||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||||
Priority => 'error',
|
||||
Message => 'Need Name! E. g. Is(\$A, \$B, \'Test Name\')!'
|
||||
);
|
||||
$Self->_Print( 0, 'ERROR: Need Name! E. g. Is(\$A, \$B, \'Test Name\')' );
|
||||
return;
|
||||
}
|
||||
|
||||
my $Diff = DataIsDifferent(
|
||||
Data1 => $Test,
|
||||
Data2 => $ShouldBe,
|
||||
);
|
||||
|
||||
if ( !defined $Test && !defined $ShouldBe ) {
|
||||
$Self->_Print( 1, "$Name (is 'undef')" );
|
||||
return 1;
|
||||
}
|
||||
elsif ( !defined $Test && defined $ShouldBe ) {
|
||||
$Self->_Print( 0, "$Name (is 'undef' should be defined)" );
|
||||
return;
|
||||
}
|
||||
elsif ( defined $Test && !defined $ShouldBe ) {
|
||||
$Self->_Print( 0, "$Name (is defined should be 'undef')" );
|
||||
return;
|
||||
}
|
||||
elsif ( !$Diff ) {
|
||||
$Self->_Print( 1, "$Name matches expected value" );
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
my $ShouldBeDump = $Kernel::OM->Get('Kernel::System::Main')->Dump($ShouldBe);
|
||||
my $TestDump = $Kernel::OM->Get('Kernel::System::Main')->Dump($Test);
|
||||
$Self->_Print( 0, "$Name (is '$TestDump' should be '$ShouldBeDump')" );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
=head2 IsNotDeeply()
|
||||
|
||||
compares two data structures for inequality.
|
||||
|
||||
It has the same interface as L</IsDeeply()>, but tests
|
||||
for inequality instead.
|
||||
|
||||
=cut
|
||||
|
||||
sub IsNotDeeply {
|
||||
my ( $Self, $Test, $ShouldBe, $Name ) = @_;
|
||||
|
||||
if ( !$Name ) {
|
||||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||||
Priority => 'error',
|
||||
Message => 'Need Name! E. g. IsNot(\$A, \$B, \'Test Name\')!'
|
||||
);
|
||||
$Self->_Print( 0, 'ERROR: Need Name! E. g. IsNot(\$A, \$B, \'Test Name\')' );
|
||||
return;
|
||||
}
|
||||
|
||||
my $Diff = DataIsDifferent(
|
||||
Data1 => $Test,
|
||||
Data2 => $ShouldBe,
|
||||
);
|
||||
|
||||
if ( !defined $Test && !defined $ShouldBe ) {
|
||||
$Self->_Print( 0, "$Name (is 'undef')" );
|
||||
return;
|
||||
}
|
||||
elsif ( !defined $Test && defined $ShouldBe ) {
|
||||
$Self->_Print( 1, "$Name (is 'undef')" );
|
||||
return 1;
|
||||
}
|
||||
elsif ( defined $Test && !defined $ShouldBe ) {
|
||||
$Self->_Print( 1, "$Name (differs from expected value)" );
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($Diff) {
|
||||
$Self->_Print( 1, "$Name (The structures are not equal.)" );
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
|
||||
# $Self->_Print( 0, "$Name (matches the expected value)" );
|
||||
my $TestDump = $Kernel::OM->Get('Kernel::System::Main')->Dump($Test);
|
||||
$Self->_Print( 0, "$Name (The structures are equal: '$TestDump')" );
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
=head2 AttachSeleniumScreenshot()
|
||||
|
||||
attach a screenshot taken during Selenium error handling. These will be sent to the server
|
||||
together with the test results.
|
||||
|
||||
$Driver->AttachSeleniumScreenshot(
|
||||
Filename => $Filename,
|
||||
Content => $Data # raw image data
|
||||
);
|
||||
|
||||
=cut
|
||||
|
||||
sub AttachSeleniumScreenshot {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
push @{ $Self->{ResultData}->{Results}->{ $Self->{TestCount} }->{Screenshots} },
|
||||
{
|
||||
Filename => $Param{Filename},
|
||||
Content => $Param{Content},
|
||||
};
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
=begin Internal:
|
||||
|
||||
=cut
|
||||
|
||||
sub _SaveResults {
|
||||
my ($Self) = @_;
|
||||
|
||||
if ( !$Self->{ResultData} ) {
|
||||
$Self->True( 0, 'No result data found.' );
|
||||
}
|
||||
|
||||
my $Success = Storable::nstore( $Self->{ResultData}, $Self->{ResultDataFile} );
|
||||
if ( !$Success ) {
|
||||
print STDERR $Self->_Color( 'red', "Could not store result data in $Self->{ResultDataFile}\n" );
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub _Print {
|
||||
my ( $Self, $ResultOk, $Message ) = @_;
|
||||
|
||||
$Message ||= '->>No Name!<<-';
|
||||
|
||||
my $ShortMessage = $Message;
|
||||
if ( length $ShortMessage > 1000 && !$Self->{Verbose} ) {
|
||||
$ShortMessage = substr( $ShortMessage, 0, 1000 ) . "...";
|
||||
}
|
||||
|
||||
if ( $Self->{Verbose} || !$ResultOk ) {
|
||||
print { $Self->{OriginalSTDOUT} } $Self->{OutputBuffer};
|
||||
}
|
||||
$Self->{OutputBuffer} = '';
|
||||
|
||||
$Self->{TestCount}++;
|
||||
if ($ResultOk) {
|
||||
if ( $Self->{Verbose} ) {
|
||||
print { $Self->{OriginalSTDOUT} } " "
|
||||
. $Self->_Color( 'green', "ok" )
|
||||
. " $Self->{TestCount} - $ShortMessage\n";
|
||||
}
|
||||
else {
|
||||
print { $Self->{OriginalSTDOUT} } $Self->_Color( 'green', "." );
|
||||
}
|
||||
|
||||
$Self->{ResultData}->{TestOk}++;
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
if ( !$Self->{Verbose} ) {
|
||||
print { $Self->{OriginalSTDOUT} } "\n";
|
||||
}
|
||||
print { $Self->{OriginalSTDOUT} } " "
|
||||
. $Self->_Color( 'red', "not ok" )
|
||||
. " $Self->{TestCount} - $ShortMessage\n";
|
||||
$Self->{ResultData}->{TestNotOk}++;
|
||||
$Self->{ResultData}->{Results}->{ $Self->{TestCount} }->{Status} = 'not ok';
|
||||
$Self->{ResultData}->{Results}->{ $Self->{TestCount} }->{Message} = $Message;
|
||||
|
||||
my $TestFailureDetails = $Message;
|
||||
$TestFailureDetails =~ s{\(.+\)$}{};
|
||||
if ( length $TestFailureDetails > 200 ) {
|
||||
$TestFailureDetails = substr( $TestFailureDetails, 0, 200 ) . "...";
|
||||
}
|
||||
|
||||
# Store information about failed tests, but only if we are running in a toplevel unit test object
|
||||
# that is actually processing files, and not in an embedded object that just runs individual tests.
|
||||
push @{ $Self->{ResultData}->{NotOkInfo} }, sprintf "%s - %s", $Self->{TestCount},
|
||||
$TestFailureDetails;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
=head2 _Color()
|
||||
|
||||
this will color the given text (see Term::ANSIColor::color()) if
|
||||
ANSI output is available and active, otherwise the text stays unchanged.
|
||||
|
||||
my $PossiblyColoredText = $CommandObject->_Color('green', $Text);
|
||||
|
||||
=cut
|
||||
|
||||
sub _Color {
|
||||
my ( $Self, $Color, $Text ) = @_;
|
||||
|
||||
return $Text if !$Self->{ANSI};
|
||||
return Term::ANSIColor::color($Color) . $Text . Term::ANSIColor::color('reset');
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=end Internal:
|
||||
|
||||
=head1 TERMS AND CONDITIONS
|
||||
|
||||
This software is part of the OTRS project (L<https://otrs.org/>).
|
||||
|
||||
This software comes with ABSOLUTELY NO WARRANTY. For details, see
|
||||
the enclosed file COPYING for license information (GPL). If you
|
||||
did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>.
|
||||
|
||||
=cut
|
||||
1142
Perl OTRS/Kernel/System/UnitTest/Helper.pm
Normal file
1142
Perl OTRS/Kernel/System/UnitTest/Helper.pm
Normal file
File diff suppressed because it is too large
Load Diff
824
Perl OTRS/Kernel/System/UnitTest/Selenium.pm
Normal file
824
Perl OTRS/Kernel/System/UnitTest/Selenium.pm
Normal file
@@ -0,0 +1,824 @@
|
||||
# --
|
||||
# Copyright (C) 2001-2019 OTRS AG, https://otrs.com/
|
||||
# --
|
||||
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
|
||||
# the enclosed file COPYING for license information (GPL). If you
|
||||
# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
|
||||
# --
|
||||
|
||||
package Kernel::System::UnitTest::Selenium;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use MIME::Base64();
|
||||
use File::Path();
|
||||
use File::Temp();
|
||||
use Time::HiRes();
|
||||
|
||||
use Kernel::Config;
|
||||
use Kernel::System::User;
|
||||
use Kernel::System::UnitTest::Helper;
|
||||
use Kernel::System::VariableCheck qw(IsArrayRefWithData);
|
||||
|
||||
our @ObjectDependencies = (
|
||||
'Kernel::Config',
|
||||
'Kernel::System::AuthSession',
|
||||
'Kernel::System::Log',
|
||||
'Kernel::System::Main',
|
||||
'Kernel::System::DateTime',
|
||||
'Kernel::System::UnitTest::Driver',
|
||||
'Kernel::System::UnitTest::Helper',
|
||||
);
|
||||
|
||||
# If a test throws an exception, we'll record it here in a package variable so that we can
|
||||
# take screenshots of *all* Selenium instances that are currently running on shutdown.
|
||||
our $TestException;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Kernel::System::UnitTest::Selenium - run front end tests
|
||||
|
||||
This class inherits from Selenium::Remote::Driver. You can use
|
||||
its full API (see
|
||||
L<http://search.cpan.org/~aivaturi/Selenium-Remote-Driver-0.15/lib/Selenium/Remote/Driver.pm>).
|
||||
|
||||
Every successful Selenium command will be logged as a successful unit test.
|
||||
In case of an error, an exception will be thrown that you can catch in your
|
||||
unit test file and handle with C<HandleError()> in this class. It will output
|
||||
a failing test result and generate a screen shot for analysis.
|
||||
|
||||
=head2 new()
|
||||
|
||||
create a selenium object to run front end tests.
|
||||
|
||||
To do this, you need a running C<selenium> or C<phantomjs> server.
|
||||
|
||||
Specify the connection details in C<Config.pm>, like this:
|
||||
|
||||
# For testing with Firefox until v. 47 (testing with recent FF and marionette is currently not supported):
|
||||
$Self->{'SeleniumTestsConfig'} = {
|
||||
remote_server_addr => 'localhost',
|
||||
port => '4444',
|
||||
platform => 'ANY',
|
||||
browser_name => 'firefox',
|
||||
extra_capabilities => {
|
||||
marionette => \0, # Required to run FF 47 or older on Selenium 3+.
|
||||
},
|
||||
};
|
||||
|
||||
# For testing with Chrome/Chromium (requires installed geckodriver):
|
||||
$Self->{'SeleniumTestsConfig'} = {
|
||||
remote_server_addr => 'localhost',
|
||||
port => '4444',
|
||||
platform => 'ANY',
|
||||
browser_name => 'chrome',
|
||||
extra_capabilities => {
|
||||
chromeOptions => {
|
||||
# disable-infobars makes sure window size calculations are ok
|
||||
args => [ "disable-infobars" ],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Then you can use the full API of L<Selenium::Remote::Driver> on this object.
|
||||
|
||||
=cut
|
||||
|
||||
sub new {
|
||||
my ( $Class, %Param ) = @_;
|
||||
|
||||
$Param{UnitTestDriverObject} ||= $Kernel::OM->Get('Kernel::System::UnitTest::Driver');
|
||||
|
||||
$Param{UnitTestDriverObject}->True( 1, "Starting up Selenium scenario..." );
|
||||
|
||||
my %SeleniumTestsConfig = %{ $Kernel::OM->Get('Kernel::Config')->Get('SeleniumTestsConfig') // {} };
|
||||
|
||||
if ( !%SeleniumTestsConfig ) {
|
||||
my $Self = bless {}, $Class;
|
||||
$Self->{UnitTestDriverObject} = $Param{UnitTestDriverObject};
|
||||
return $Self;
|
||||
}
|
||||
|
||||
for my $Needed (qw(remote_server_addr port browser_name platform)) {
|
||||
if ( !$SeleniumTestsConfig{$Needed} ) {
|
||||
die "SeleniumTestsConfig must provide $Needed!";
|
||||
}
|
||||
}
|
||||
|
||||
$Kernel::OM->Get('Kernel::System::Main')->RequireBaseClass('Selenium::Remote::Driver')
|
||||
|| die "Could not load Selenium::Remote::Driver";
|
||||
|
||||
$Kernel::OM->Get('Kernel::System::Main')->Require('Kernel::System::UnitTest::Selenium::WebElement')
|
||||
|| die "Could not load Kernel::System::UnitTest::Selenium::WebElement";
|
||||
|
||||
my $Self;
|
||||
|
||||
# TEMPORARY WORKAROUND FOR GECKODRIVER BUG https://github.com/mozilla/geckodriver/issues/1470:
|
||||
# If marionette handshake fails, wait and try again. Can be removed after the bug is fixed
|
||||
# in a new geckodriver version.
|
||||
eval {
|
||||
$Self = $Class->SUPER::new(
|
||||
webelement_class => 'Kernel::System::UnitTest::Selenium::WebElement',
|
||||
%SeleniumTestsConfig
|
||||
);
|
||||
};
|
||||
if ($@) {
|
||||
my $Exception = $@;
|
||||
|
||||
# Only handle this specific geckodriver exception.
|
||||
die $Exception if $Exception !~ m{Socket timeout reading Marionette handshake data};
|
||||
|
||||
# Sleep and try again, bail out if it fails a second time.
|
||||
# A long sleep of 10 seconds is acceptable here, as it occurs only very rarely.
|
||||
sleep 10;
|
||||
|
||||
$Self = $Class->SUPER::new(
|
||||
webelement_class => 'Kernel::System::UnitTest::Selenium::WebElement',
|
||||
%SeleniumTestsConfig
|
||||
);
|
||||
}
|
||||
|
||||
$Self->{UnitTestDriverObject} = $Param{UnitTestDriverObject};
|
||||
$Self->{SeleniumTestsActive} = 1;
|
||||
|
||||
$Self->{UnitTestDriverObject}->{SeleniumData} = { %{ $Self->get_capabilities() }, %{ $Self->status() } };
|
||||
|
||||
#$Self->debug_on();
|
||||
|
||||
# set screen size from config or use defauls
|
||||
my $Height = $SeleniumTestsConfig{window_height} || 1200;
|
||||
my $Width = $SeleniumTestsConfig{window_width} || 1400;
|
||||
|
||||
$Self->set_window_size( $Height, $Width );
|
||||
|
||||
$Self->{BaseURL} = $Kernel::OM->Get('Kernel::Config')->Get('HttpType') . '://';
|
||||
$Self->{BaseURL} .= Kernel::System::UnitTest::Helper->GetTestHTTPHostname();
|
||||
|
||||
# Remember the start system time for the selenium test run.
|
||||
$Self->{TestStartSystemTime} = time; ## no critic
|
||||
|
||||
return $Self;
|
||||
}
|
||||
|
||||
=head2 RunTest()
|
||||
|
||||
runs a selenium test if Selenium testing is configured.
|
||||
|
||||
$SeleniumObject->RunTest( sub { ... } );
|
||||
|
||||
=cut
|
||||
|
||||
sub RunTest {
|
||||
my ( $Self, $Test ) = @_;
|
||||
|
||||
if ( !$Self->{SeleniumTestsActive} ) {
|
||||
$Self->{UnitTestDriverObject}->True( 1, 'Selenium testing is not active, skipping tests.' );
|
||||
return 1;
|
||||
}
|
||||
|
||||
eval {
|
||||
$Test->();
|
||||
};
|
||||
|
||||
$TestException = $@ if $@;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
=begin Internal:
|
||||
|
||||
=head2 _execute_command()
|
||||
|
||||
Override internal command of base class.
|
||||
|
||||
We use it to output successful command runs to the UnitTest object.
|
||||
Errors will cause an exeption and be caught elsewhere.
|
||||
|
||||
=end Internal:
|
||||
|
||||
=cut
|
||||
|
||||
sub _execute_command { ## no critic
|
||||
my ( $Self, $Res, $Params ) = @_;
|
||||
|
||||
my $Result = $Self->SUPER::_execute_command( $Res, $Params );
|
||||
|
||||
my $TestName = 'Selenium command success: ';
|
||||
$TestName .= $Kernel::OM->Get('Kernel::System::Main')->Dump(
|
||||
{
|
||||
%{ $Res || {} }, ## no critic
|
||||
%{ $Params || {} }, ## no critic
|
||||
}
|
||||
);
|
||||
|
||||
if ( $Self->{SuppressCommandRecording} ) {
|
||||
print $TestName;
|
||||
}
|
||||
else {
|
||||
$Self->{UnitTestDriverObject}->True( 1, $TestName );
|
||||
}
|
||||
|
||||
return $Result;
|
||||
}
|
||||
|
||||
=head2 get()
|
||||
|
||||
Override get method of base class to prepend the correct base URL.
|
||||
|
||||
$SeleniumObject->get(
|
||||
$URL,
|
||||
);
|
||||
|
||||
=cut
|
||||
|
||||
sub get { ## no critic
|
||||
my ( $Self, $URL ) = @_;
|
||||
|
||||
if ( $URL !~ m{http[s]?://}smx ) {
|
||||
$URL = "$Self->{BaseURL}/$URL";
|
||||
}
|
||||
|
||||
$Self->SUPER::get($URL);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
=head2 get_alert_text()
|
||||
|
||||
Override get_alert_text() method of base class to return alert text as string.
|
||||
|
||||
my $AlertText = $SeleniumObject->get_alert_text();
|
||||
|
||||
returns
|
||||
|
||||
my $AlertText = 'Some alert text!'
|
||||
|
||||
=cut
|
||||
|
||||
sub get_alert_text { ## no critic
|
||||
my ($Self) = @_;
|
||||
|
||||
my $AlertText = $Self->SUPER::get_alert_text();
|
||||
|
||||
die "Alert dialog is not present" if ref $AlertText eq 'HASH'; # Chrome returns HASH when there is no alert text.
|
||||
|
||||
return $AlertText;
|
||||
}
|
||||
|
||||
=head2 VerifiedGet()
|
||||
|
||||
perform a get() call, but wait for the page to be fully loaded (works only within OTRS).
|
||||
Will die() if the verification fails.
|
||||
|
||||
$SeleniumObject->VerifiedGet(
|
||||
$URL,
|
||||
);
|
||||
|
||||
=cut
|
||||
|
||||
sub VerifiedGet {
|
||||
my ( $Self, $URL ) = @_;
|
||||
|
||||
$Self->get($URL);
|
||||
|
||||
$Self->WaitFor(
|
||||
JavaScript =>
|
||||
'return typeof(Core) == "object" && typeof(Core.App) == "object" && Core.App.PageLoadComplete'
|
||||
) || die "OTRS API verification failed after page load.";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
=head2 VerifiedRefresh()
|
||||
|
||||
perform a refresh() call, but wait for the page to be fully loaded (works only within OTRS).
|
||||
Will die() if the verification fails.
|
||||
|
||||
$SeleniumObject->VerifiedRefresh();
|
||||
|
||||
=cut
|
||||
|
||||
sub VerifiedRefresh {
|
||||
my ( $Self, $URL ) = @_;
|
||||
|
||||
$Self->refresh();
|
||||
|
||||
$Self->WaitFor(
|
||||
JavaScript =>
|
||||
'return typeof(Core) == "object" && typeof(Core.App) == "object" && Core.App.PageLoadComplete'
|
||||
) || die "OTRS API verification failed after page load.";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
=head2 Login()
|
||||
|
||||
login to agent or customer interface
|
||||
|
||||
$SeleniumObject->Login(
|
||||
Type => 'Agent', # Agent|Customer
|
||||
User => 'someuser',
|
||||
Password => 'somepassword',
|
||||
);
|
||||
|
||||
=cut
|
||||
|
||||
sub Login {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
# check needed stuff
|
||||
for (qw(Type User Password)) {
|
||||
if ( !$Param{$_} ) {
|
||||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||||
Priority => 'error',
|
||||
Message => "Need $_!",
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$Self->{UnitTestDriverObject}->True( 1, 'Initiating login...' );
|
||||
|
||||
# we will try several times to log in
|
||||
my $MaxTries = 5;
|
||||
|
||||
TRY:
|
||||
for my $Try ( 1 .. $MaxTries ) {
|
||||
|
||||
eval {
|
||||
my $ScriptAlias = $Kernel::OM->Get('Kernel::Config')->Get('ScriptAlias');
|
||||
|
||||
if ( $Param{Type} eq 'Agent' ) {
|
||||
$ScriptAlias .= 'index.pl';
|
||||
}
|
||||
else {
|
||||
$ScriptAlias .= 'customer.pl';
|
||||
}
|
||||
|
||||
$Self->get("${ScriptAlias}");
|
||||
|
||||
$Self->delete_all_cookies();
|
||||
$Self->VerifiedGet("${ScriptAlias}?Action=Login;User=$Param{User};Password=$Param{Password}");
|
||||
|
||||
# login successful?
|
||||
$Self->find_element( 'a#LogoutButton', 'css' ); # dies if not found
|
||||
|
||||
$Self->{UnitTestDriverObject}->True( 1, 'Login sequence ended...' );
|
||||
};
|
||||
|
||||
# an error happend
|
||||
if ($@) {
|
||||
|
||||
$Self->{UnitTestDriverObject}->True( 1, "Login attempt $Try of $MaxTries not successful." );
|
||||
|
||||
# try again
|
||||
next TRY if $Try < $MaxTries;
|
||||
|
||||
die "Login failed!";
|
||||
}
|
||||
|
||||
# login was sucessful
|
||||
else {
|
||||
last TRY;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
=head2 WaitFor()
|
||||
|
||||
wait with increasing sleep intervals until the given condition is true or the wait time is over.
|
||||
Exactly one condition (JavaScript or WindowCount) must be specified.
|
||||
|
||||
my $Success = $SeleniumObject->WaitFor(
|
||||
AlertPresent => 1, # Wait until an alert, confirm or prompt dialog is present
|
||||
Callback => sub { ... } # Wait until function returns true
|
||||
ElementExists => 'xpath-selector' # Wait until an element is present
|
||||
ElementExists => ['css-selector', 'css'],
|
||||
ElementMissing => 'xpath-selector', # Wait until an element is not present
|
||||
ElementMissing => ['css-selector', 'css'],
|
||||
JavaScript => 'return $(".someclass").length', # Javascript code that checks condition
|
||||
WindowCount => 2, # Wait until this many windows are open
|
||||
Time => 20, # optional, wait time in seconds (default 20)
|
||||
);
|
||||
|
||||
=cut
|
||||
|
||||
sub WaitFor {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
if (
|
||||
!$Param{JavaScript}
|
||||
&& !$Param{WindowCount}
|
||||
&& !$Param{AlertPresent}
|
||||
&& !$Param{Callback}
|
||||
&& !$Param{ElementExists}
|
||||
&& !$Param{ElementMissing}
|
||||
)
|
||||
{
|
||||
die "Need JavaScript, WindowCount, ElementExists, ElementMissing or AlertPresent.";
|
||||
}
|
||||
|
||||
local $Self->{SuppressCommandRecording} = 1;
|
||||
|
||||
$Param{Time} //= 20;
|
||||
my $WaitedSeconds = 0;
|
||||
my $Interval = 0.1;
|
||||
my $WaitSeconds = 0.5;
|
||||
|
||||
while ( $WaitedSeconds <= $Param{Time} ) {
|
||||
if ( $Param{JavaScript} ) {
|
||||
return 1 if $Self->execute_script( $Param{JavaScript} );
|
||||
}
|
||||
elsif ( $Param{WindowCount} ) {
|
||||
return 1 if scalar( @{ $Self->get_window_handles() } ) == $Param{WindowCount};
|
||||
}
|
||||
elsif ( $Param{AlertPresent} ) {
|
||||
|
||||
# Eval is needed because the method would throw if no alert is present (yet).
|
||||
return 1 if eval { $Self->get_alert_text() };
|
||||
}
|
||||
elsif ( $Param{Callback} ) {
|
||||
return 1 if $Param{Callback}->();
|
||||
}
|
||||
elsif ( $Param{ElementExists} ) {
|
||||
my @Arguments
|
||||
= ref( $Param{ElementExists} ) eq 'ARRAY' ? @{ $Param{ElementExists} } : $Param{ElementExists};
|
||||
if ( eval { $Self->find_element(@Arguments) } ) {
|
||||
Time::HiRes::sleep($WaitSeconds);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
elsif ( $Param{ElementMissing} ) {
|
||||
my @Arguments
|
||||
= ref( $Param{ElementMissing} ) eq 'ARRAY' ? @{ $Param{ElementMissing} } : $Param{ElementMissing};
|
||||
if ( !eval { $Self->find_element(@Arguments) } ) {
|
||||
Time::HiRes::sleep($WaitSeconds);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
Time::HiRes::sleep($Interval);
|
||||
$WaitedSeconds += $Interval;
|
||||
$Interval += 0.1;
|
||||
}
|
||||
|
||||
my $Argument = '';
|
||||
for my $Key (qw(JavaScript WindowCount AlertPresent)) {
|
||||
$Argument = "$Key => $Param{$Key}" if $Param{$Key};
|
||||
}
|
||||
$Argument = "Callback" if $Param{Callback};
|
||||
|
||||
die "WaitFor($Argument) failed.";
|
||||
}
|
||||
|
||||
=head2 SwitchToFrame()
|
||||
|
||||
Change focus to another frame on the page. If C<WaitForLoad> is passed, it will wait until the frame has loaded the
|
||||
page completely.
|
||||
|
||||
my $Success = $SeleniumObject->SwitchToFrame(
|
||||
FrameSelector => '.Iframe', # (required) CSS selector of the frame element
|
||||
WaitForLoad => 1, # (optional) Wait until the frame has loaded, if necessary
|
||||
Time => 20, # (optional) Wait time in seconds (default 20)
|
||||
);
|
||||
|
||||
=cut
|
||||
|
||||
sub SwitchToFrame {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
if ( !$Param{FrameSelector} ) {
|
||||
die 'Need FrameSelector.';
|
||||
}
|
||||
|
||||
if ( $Param{WaitForLoad} ) {
|
||||
$Self->WaitFor(
|
||||
JavaScript => "return typeof(\$('$Param{FrameSelector}').get(0).contentWindow.Core) == 'object'
|
||||
&& typeof(\$('$Param{FrameSelector}').get(0).contentWindow.Core.App) == 'object'
|
||||
&& \$('$Param{FrameSelector}').get(0).contentWindow.Core.App.PageLoadComplete;",
|
||||
Time => $Param{Time},
|
||||
);
|
||||
}
|
||||
|
||||
$Self->switch_to_frame( $Self->find_element( $Param{FrameSelector}, 'css' ) );
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
=head2 DragAndDrop()
|
||||
|
||||
Drag and drop an element.
|
||||
|
||||
$SeleniumObject->DragAndDrop(
|
||||
Element => '.Element', # (required) css selector of element which should be dragged
|
||||
Target => '.Target', # (required) css selector of element on which the dragged element should be dropped
|
||||
TargetOffset => { # (optional) Offset for target. If not specified, the mouse will move to the middle of the element.
|
||||
X => 150,
|
||||
Y => 100,
|
||||
}
|
||||
);
|
||||
|
||||
=cut
|
||||
|
||||
sub DragAndDrop {
|
||||
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
# Value is optional parameter
|
||||
for my $Needed (qw(Element Target)) {
|
||||
if ( !$Param{$Needed} ) {
|
||||
die "Need $Needed";
|
||||
}
|
||||
}
|
||||
|
||||
my %TargetOffset;
|
||||
if ( $Param{TargetOffset} ) {
|
||||
%TargetOffset = (
|
||||
xoffset => $Param{TargetOffset}->{X} || 0,
|
||||
yoffset => $Param{TargetOffset}->{Y} || 0,
|
||||
);
|
||||
}
|
||||
|
||||
# Make sure Element is visible
|
||||
$Self->WaitFor(
|
||||
JavaScript => 'return typeof($) === "function" && $(\'' . $Param{Element} . ':visible\').length;',
|
||||
);
|
||||
my $Element = $Self->find_element( $Param{Element}, 'css' );
|
||||
|
||||
# Move mouse to from element, drag and drop
|
||||
$Self->mouse_move_to_location( element => $Element );
|
||||
|
||||
# Holds the mouse button on the element
|
||||
$Self->button_down();
|
||||
|
||||
# Make sure Target is visible
|
||||
$Self->WaitFor(
|
||||
JavaScript => 'return typeof($) === "function" && $(\'' . $Param{Target} . ':visible\').length;',
|
||||
);
|
||||
my $Target = $Self->find_element( $Param{Target}, 'css' );
|
||||
|
||||
# Move mouse to the destination
|
||||
$Self->mouse_move_to_location(
|
||||
element => $Target,
|
||||
%TargetOffset,
|
||||
);
|
||||
|
||||
# Release
|
||||
$Self->button_up();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
=head2 HandleError()
|
||||
|
||||
use this method to handle any Selenium exceptions.
|
||||
|
||||
$SeleniumObject->HandleError($@);
|
||||
|
||||
It will create a failing test result and store a screen shot of the page
|
||||
for analysis (in folder /var/otrs-unittest if it exists, in $Home/var/httpd/htdocs otherwise).
|
||||
|
||||
=cut
|
||||
|
||||
sub HandleError {
|
||||
my ( $Self, $Error ) = @_;
|
||||
|
||||
$Self->{UnitTestDriverObject}->False( 1, "Exception in Selenium': $Error" );
|
||||
|
||||
#eval {
|
||||
my $Data = $Self->screenshot();
|
||||
return if !$Data;
|
||||
$Data = MIME::Base64::decode_base64($Data);
|
||||
|
||||
#
|
||||
# Store screenshots in a local folder from where they can be opened directly in the browser.
|
||||
#
|
||||
my $LocalScreenshotDir = $Kernel::OM->Get('Kernel::Config')->Get('Home') . '/var/httpd/htdocs/SeleniumScreenshots';
|
||||
mkdir $LocalScreenshotDir || return $Self->False( 1, "Could not create $LocalScreenshotDir." );
|
||||
|
||||
my $DateTimeObj = $Kernel::OM->Create('Kernel::System::DateTime');
|
||||
my $Filename = $DateTimeObj->ToString();
|
||||
$Filename .= '-' . ( int rand 100_000_000 ) . '.png';
|
||||
$Filename =~ s{[ :]}{-}smxg;
|
||||
|
||||
my $HttpType = $Kernel::OM->Get('Kernel::Config')->Get('HttpType');
|
||||
my $Hostname = $Kernel::OM->Get('Kernel::System::UnitTest::Helper')->GetTestHTTPHostname();
|
||||
my $URL = "$HttpType://$Hostname/"
|
||||
. $Kernel::OM->Get('Kernel::Config')->Get('Frontend::WebPath')
|
||||
. "SeleniumScreenshots/$Filename";
|
||||
|
||||
$Kernel::OM->Get('Kernel::System::Main')->FileWrite(
|
||||
Directory => $LocalScreenshotDir,
|
||||
Filename => $Filename,
|
||||
Content => \$Data,
|
||||
) || return $Self->False( 1, "Could not write file $LocalScreenshotDir/$Filename" );
|
||||
|
||||
#
|
||||
# If a shared screenshot folder is present, then we also store the screenshot there for external use.
|
||||
#
|
||||
if ( -d '/var/otrs-unittest/' && -w '/var/otrs-unittest/' ) {
|
||||
|
||||
my $SharedScreenshotDir = '/var/otrs-unittest/SeleniumScreenshots';
|
||||
mkdir $SharedScreenshotDir || return $Self->False( 1, "Could not create $SharedScreenshotDir." );
|
||||
|
||||
$Kernel::OM->Get('Kernel::System::Main')->FileWrite(
|
||||
Directory => $SharedScreenshotDir,
|
||||
Filename => $Filename,
|
||||
Content => \$Data,
|
||||
)
|
||||
|| return $Self->{UnitTestDriverObject}->False( 1, "Could not write file $SharedScreenshotDir/$Filename" );
|
||||
}
|
||||
|
||||
$Self->{UnitTestDriverObject}->False( 1, "Saved screenshot in $URL" );
|
||||
$Self->{UnitTestDriverObject}->AttachSeleniumScreenshot(
|
||||
Filename => $Filename,
|
||||
Content => $Data
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
=head2 DEMOLISH()
|
||||
|
||||
override DEMOLISH from L<Selenium::Remote::Driver> (required because this class is managed by L<Moo>).
|
||||
Performs proper error handling (calls C<HandleError()> if needed). Adds a unit test result to indicate the shutdown,
|
||||
and performs some clean-ups.
|
||||
|
||||
=cut
|
||||
|
||||
sub DEMOLISH {
|
||||
my $Self = shift;
|
||||
|
||||
if ($TestException) {
|
||||
$Self->HandleError($TestException);
|
||||
}
|
||||
|
||||
# Could be missing on early die.
|
||||
if ( $Self->{UnitTestDriverObject} ) {
|
||||
$Self->{UnitTestDriverObject}->True( 1, "Shutting down Selenium scenario." );
|
||||
}
|
||||
|
||||
if ( $Self->{SeleniumTestsActive} ) {
|
||||
$Self->SUPER::DEMOLISH(@_);
|
||||
|
||||
# Cleanup possibly leftover zombie firefox profiles.
|
||||
my @LeftoverFirefoxProfiles = $Kernel::OM->Get('Kernel::System::Main')->DirectoryRead(
|
||||
Directory => '/tmp/',
|
||||
Filter => 'anonymous*webdriver-profile',
|
||||
);
|
||||
|
||||
for my $LeftoverFirefoxProfile (@LeftoverFirefoxProfiles) {
|
||||
if ( -d $LeftoverFirefoxProfile ) {
|
||||
File::Path::remove_tree($LeftoverFirefoxProfile);
|
||||
}
|
||||
}
|
||||
|
||||
# Cleanup all sessions, which was created after the selenium test start time.
|
||||
my $AuthSessionObject = $Kernel::OM->Get('Kernel::System::AuthSession');
|
||||
|
||||
my @Sessions = $AuthSessionObject->GetAllSessionIDs();
|
||||
|
||||
SESSION:
|
||||
for my $SessionID (@Sessions) {
|
||||
|
||||
my %SessionData = $AuthSessionObject->GetSessionIDData( SessionID => $SessionID );
|
||||
|
||||
next SESSION if !%SessionData;
|
||||
next SESSION
|
||||
if $SessionData{UserSessionStart} && $SessionData{UserSessionStart} < $Self->{TestStartSystemTime};
|
||||
|
||||
$AuthSessionObject->RemoveSessionID( SessionID => $SessionID );
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
=head1 DEPRECATED FUNCTIONS
|
||||
|
||||
=head2 WaitForjQueryEventBound()
|
||||
|
||||
waits until event handler is bound to the selected C<jQuery> element. Deprecated - it will be removed in the future releases.
|
||||
|
||||
$SeleniumObject->WaitForjQueryEventBound(
|
||||
CSSSelector => 'li > a#Test', # (required) css selector
|
||||
Event => 'click', # (optional) Specify event name. Default 'click'.
|
||||
);
|
||||
|
||||
=cut
|
||||
|
||||
sub WaitForjQueryEventBound {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
# Check needed stuff.
|
||||
if ( !$Param{CSSSelector} ) {
|
||||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||||
Priority => 'error',
|
||||
Message => "Need CSSSelector!",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
my $Event = $Param{Event} || 'click';
|
||||
|
||||
# Wait for element availability.
|
||||
$Self->WaitFor(
|
||||
JavaScript => 'return typeof($) === "function" && $("' . $Param{CSSSelector} . '").length;'
|
||||
);
|
||||
|
||||
# Wait for jQuery initialization.
|
||||
$Self->WaitFor(
|
||||
JavaScript =>
|
||||
'return Object.keys($("' . $Param{CSSSelector} . '")[0]).length > 0'
|
||||
);
|
||||
|
||||
# Get jQuery object keys.
|
||||
my $Keys = $Self->execute_script(
|
||||
'return Object.keys($("' . $Param{CSSSelector} . '")[0]);'
|
||||
);
|
||||
|
||||
if ( !IsArrayRefWithData($Keys) ) {
|
||||
die "Couldn't determine jQuery object id";
|
||||
}
|
||||
|
||||
my $JQueryObjectID;
|
||||
|
||||
KEY:
|
||||
for my $Key ( @{$Keys} ) {
|
||||
if ( $Key =~ m{^jQuery\d+$} ) {
|
||||
$JQueryObjectID = $Key;
|
||||
last KEY;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !$JQueryObjectID ) {
|
||||
die "Couldn't determine jQuery object id.";
|
||||
}
|
||||
|
||||
# Wait until click event is bound to the element.
|
||||
$Self->WaitFor(
|
||||
JavaScript =>
|
||||
'return $("' . $Param{CSSSelector} . '")[0].' . $JQueryObjectID . '.events
|
||||
&& $("' . $Param{CSSSelector} . '")[0].' . $JQueryObjectID . '.events.' . $Event . '
|
||||
&& $("' . $Param{CSSSelector} . '")[0].' . $JQueryObjectID . '.events.' . $Event . '.length > 0;',
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
=head2 InputFieldValueSet()
|
||||
|
||||
sets modernized input field value.
|
||||
|
||||
$SeleniumObject->InputFieldValueSet(
|
||||
Element => 'css-selector', # (required) css selector
|
||||
Value => 3, # (optional) Value
|
||||
);
|
||||
|
||||
=cut
|
||||
|
||||
sub InputFieldValueSet {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
# Check needed stuff.
|
||||
if ( !$Param{Element} ) {
|
||||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||||
Priority => 'error',
|
||||
Message => "Need Element!",
|
||||
);
|
||||
die 'Missing Element.';
|
||||
}
|
||||
my $Value = $Param{Value} // '';
|
||||
|
||||
if ( $Value !~ m{^\[} && $Value !~ m{^".*"$} ) {
|
||||
|
||||
# Quote text of Value is not array and if not already quoted.
|
||||
$Value = "\"$Value\"";
|
||||
}
|
||||
|
||||
# Set selected value.
|
||||
$Self->execute_script(
|
||||
"\$('$Param{Element}').val($Value).trigger('redraw.InputField').trigger('change');"
|
||||
);
|
||||
|
||||
# Wait until selection tree is closed.
|
||||
$Self->WaitFor(
|
||||
ElementMissing => [ '.InputField_ListContainer', 'css' ],
|
||||
);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 TERMS AND CONDITIONS
|
||||
|
||||
This software is part of the OTRS project (L<https://otrs.org/>).
|
||||
|
||||
This software comes with ABSOLUTELY NO WARRANTY. For details, see
|
||||
the enclosed file COPYING for license information (GPL). If you
|
||||
did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>.
|
||||
|
||||
=cut
|
||||
78
Perl OTRS/Kernel/System/UnitTest/Selenium/WebElement.pm
Normal file
78
Perl OTRS/Kernel/System/UnitTest/Selenium/WebElement.pm
Normal file
@@ -0,0 +1,78 @@
|
||||
# --
|
||||
# Copyright (C) 2001-2019 OTRS AG, https://otrs.com/
|
||||
# --
|
||||
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
|
||||
# the enclosed file COPYING for license information (GPL). If you
|
||||
# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
|
||||
# --
|
||||
|
||||
package Kernel::System::UnitTest::Selenium::WebElement;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use parent qw(Selenium::Remote::WebElement);
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Kernel::System::UnitTest::Selenium::WebElement - Utility functions for Selenium WebElements
|
||||
|
||||
|
||||
=head2 VerifiedSubmit()
|
||||
|
||||
Submit a form element, and wait for the page to be fully loaded (works only in OTRS)
|
||||
|
||||
$SeleniumObject->VerifiedSubmit();
|
||||
|
||||
=cut
|
||||
|
||||
sub VerifiedSubmit {
|
||||
my ( $Self, $Params ) = @_;
|
||||
|
||||
$Self->submit();
|
||||
|
||||
$Self->driver()->WaitFor(
|
||||
JavaScript =>
|
||||
'return typeof(Core) == "object" && typeof(Core.App) == "object" && Core.App.PageLoadComplete'
|
||||
) || die "OTRS API verification failed after element submit.";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
=head2 VerifiedClick()
|
||||
|
||||
click an element that causes a page get/reload/submit and wait for the page to be fully loaded
|
||||
(works only in OTRS).
|
||||
|
||||
$SeleniumObject->VerifiedClick(
|
||||
$Button # optional, see Selenium docs
|
||||
);
|
||||
|
||||
=cut
|
||||
|
||||
sub VerifiedClick { ## no critic
|
||||
my $Self = shift;
|
||||
|
||||
$Self->driver()->execute_script('window.Core.App.PageLoadComplete = false;');
|
||||
|
||||
$Self->SUPER::click(@_);
|
||||
|
||||
$Self->driver()->WaitFor(
|
||||
JavaScript =>
|
||||
'return typeof(Core) == "object" && typeof(Core.App) == "object" && Core.App.PageLoadComplete'
|
||||
) || die "OTRS API verification failed after element click.";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 TERMS AND CONDITIONS
|
||||
|
||||
This software is part of the OTRS project (L<https://otrs.org/>).
|
||||
|
||||
This software comes with ABSOLUTELY NO WARRANTY. For details, see
|
||||
the enclosed file COPYING for license information (GPL). If you
|
||||
did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>.
|
||||
|
||||
=cut
|
||||
Reference in New Issue
Block a user