Integrating QTP with Non-Mercury Products: Some Code
Continuing the discussion about QTP automation, I’ll give a little bit of the code I use to manage the application. Short version: I treat it like any other object. The calling script just requests a QTP (COM/Win32::OLE) object, then tells it which test to run, passing in some options along the way. It’s pretty Perl-advanced, and brings in a lot of other technologies, so pay close attention. Or, just copy it into a file and call it like a black-box.
This first snippet is in a file called QTP.pm. It is just a package that gets blessed as a ‘QTP’ object. The cgi script is the second snippet, which invokes the class and runs operations on it.
Update: At Bob’s suggestion, I’ve cleaned up a few things and added comments. I’ve got more to do, and it’s still in Perl, but hopefully this will make it slightly more than “Read Never”.
Note the places commented “TODO”. This indicates you need to replace the value in <> with values appropriate for your environment.
package QTP;# Standard Perl/CPAN modules
use Win32::OLE;
use Win32::OLE::Variant qw(
EFAULT nothing);
use FileHandle;
use File::Spec;
use File::Copy;
use Data::Dumper;
use XML::XSLT;
# Set OLE warn level to “warn”, not “fail” on minor errors
$Win32::OLE::Warn = 3;
# constructor
sub new
{
my ($proto, $args) = @_;
my $class = ref($proto) || $proto;
# $args is a hashref containing settings for the constructor
my $self = bless {
# visible defaults to false–QTP runs faster when it’s off, but
# is useful when debugging
visible => $args->{visible} || 0,
# the path to the Object Repository (optional)
ORPath => $args->{ORPath},
# path to the test
testpath => $args->{testpath},
}, $class;
# Get an instance of QTP through Win32::OLE
eval
{
$self->{qtp} = Win32::OLE->new( ‘Quicktest.Application’, ‘Quit’ );
};
die $@ if ($@);
return $self;
}
# gather information about the test sites. This should be abstracted to an
# external module, as it is not QTP-specific
sub get_site_info
{
my $self = shift;
my $site_name = shift;
# Create a user agent object
use LWP::UserAgent;
$ua = LWP::UserAgent->new;
# Create a request
my $req = HTTP::Request->new(GET => ‘http://<hostname>/’.
$site_name . ‘.txt’); # TODO
$req->content_type(‘text/plain’);
# Pass request to the user agent and get a response back
my $res = $ua->request($req);
# Check the outcome of the response
if ($res->is_success) {
my @results = split /\n/, $res->content;
for my $line (@results)
{
# The information comes back in tab separated key-value pairs
my ($key, $value) = split /\t/, $line;
$ENV{$key} = $value;
}
}
else {
# if there was an error
print $res->status_line, “\n“;
}
}
# last_status is stored after the most recent test run
# would like to get this from the QTP application itself,
# but normally it’s already been destroyed by this time
sub get_last_status
{
my $self = shift;
return $self->{last_status};
}
# This method performs the bulk of the work
# Because of the limitations of Win32::OLE, the application
# object gets launched and destroyed in the course of this method,
# so any information I want needs to be gathered while it’s still hanging
# around. The other methods about getting status and test results just
# query members of the class that are captured in this method.
sub run_test
{
my $self = shift;
# This hash holds the arguments
my %args = @_;
#Test Definitions here – these tests become hard-coded, so we don’t
# recommend using them. This is deprecated, and I’ll post the update
# the moment I refactor it out.
my $AllPredefinedTests =
{
Login =>
{
Path => ‘C:\<path>’, # TODO
RO => 1,
Arg => 0,
},
};
my $test = $args{Test};
my $testDetails;
# Check to see whether we have a valid path or not (whether in the
# predefined tests or in a path sent in through the method invocation)
if (exists $AllPredefinedTests->{$test})
{
$testDetails = $AllPredefinedTests->{$test};
}
else
{
if (-e $args{Test})
{
$testDetails =
{
Path => $args{Test},
RO => 1,
Arg => 0,
}
}
}
# Tell the QTP object to launch the application according to the scrubbed
# data from above
$self->{qtp}->Open(
$testDetails->{‘Path’},
$testDetails->{‘RO’},
$testDetails->{‘Arg’} );
$self->{qtp}->Launch;
# Make the application visible or invisible
$self->{qtp}->{Visible} = $self->{visible};
# RunResultsOptions is a separate COM object.
# No later reference to it is needed, so we instantiate it here and let it die.
# This object allows us to set the Test Results path to whatever we need it to be
my $qtResultsOpt = Win32::OLE->new( ‘QuickTest.RunResultsOptions’, ‘Quit’ );
$qtResultsOpt->{ResultsLocation} = ‘<results path>’; # TODO
# Execute the test
$self->{qtp}->Test->Run($qtResultsOpt);
# capture the test run results for later use
$self->{last_status} = $self->{qtp}->Test->LastRunResults->{Status};
return $self;
}
# Since we put the results in a place we specified, it won’t get rolled up into
# the ‘Res1′, ‘Res2′, etc., folders. Now we know where to get them and can
# transform them however we want
sub get_results
{
my $self = shift;
# $args is a hashref
my $args = shift;
# by the end of this code block, we’ll have the name of a stylesheet.
# If it’s invalid, the XSLT processor will pick it up. We could go crazy
# trying to determine whether or not it’s invalid, so we’ll punt
# Default to a known good stylesheet (PShort.xsl, distributed with QTP)
my $stylesheet = $args->{stylesheet} ||
‘http://<xsl doc server>/PShort.xsl’; # TODO
# put together the results path, with the default pointing to the same
# directory as above
my $results_path = $args->{results_path} || File::Spec->catfile
(
‘<results path>’, # TODO
‘Report’,
‘Results.xml’
);
# This invoked the LibXML2 app ‘xsltproc’. In other places in our library,
# we’ve replaced this with XML::LibXSLT so we don’t have to launch
# an external process. I’ll refactor this to use that one day.
my @results = `xsltproc $stylesheet $results_path`;
my $results = join ”, @status;
# if xsltproc hit an error message
($?>> 8 ) and die “xsltproc returned the following status code: $results”;
return $results;
}
# A little Perl 6 before Perl 6 is available
sub slurp
{
my $filename = shift;
# concatenate the file into one large string
open(FILE, “<$filename”);
my $return;
while (<FILE>) { $return .= $_ }
return $return;
}
# don’t forget this line:
1;
Any idea how I would set an user-defined environemnt variable after the script has been loaded into QTP?