Real-world TestComplete Plug-ins Part II
TestComplete support for Raize CodeSite
of this series, we built a simple TestComplete 4 plug-in that provided a set of string utilities for
use in test projects. The goal of the project was a general overview of the TestComplete plug-in SDK
and to have a real-world example of a plug-in implementing the most basic of the SDK capabilities.
Plus, the end result was fun, any chance you get to convert integers to Roman numerals in code you should take.
Anyway, in this, the second installment, we're taking a somewhat deeper look. This is still a fairly basic
plug-in, providing routines that you can call from your test projects. However, it also offers another feature,
which is the focus of this article: it is an event listener.
First a quick word about CodeSite:
CodeSite, by Raize Software, is a fine debugging tool offering a way to send messages to a special
viewer window (see figure 1) from an application under test.
Figure 1 - the CodeSite Viewer
This is special in that the CodeSite tools are compiled straight into your application, and send messages via
object methods in the actual application's source code. This allows you to send messages and other values to the
viewer, create virtual call stacks (by sending EnterMethod and ExitMethod messages) or even send complex information
like your system's memory status, XML file content, etc.
By compiling the CodeSite objects into a TestComplete plug-in, we make all these CodeSite messaging methods
available from our test scripts. This can be quite handy in the right situations; I've used it most often in
situations where I have to debug complex or troublesome test scripts.
Now, back to our plug-in: as I mentioned, this CodeSite plug-in is an event listener, so there are really
two uses or benefits to this plug-in.
- It provides access to many of the CodeSite object's methods for use with your test scripts. This is
great for debugging your scripts, etc.
- By listening to the TestComplete Log message events, the plug-in is able to pass any TestComplete log
messages on to the CodeSite viewer in real-time. Why is this valuable?
Because currently, TestComplete does not persist its Log to XML until the end of the test run. If you want
to see something in the Log during the test run, you're out of luck. But by listening to, and handling the
ItcLogMessageEvent events, we can see each message of the test log as it is created. Cool, huh? You could
actually use this technique in your own event-listening plug-in to handle the messages using something other than CodeSite.
Note that there are a couple of other elements of the SDK which get used in this plug-in, most notably,
Settings (or engine options) however, the primary focus of this article is on event listening. We'll touch
on Settings in more detail in a later article.
Also note, as I mentioned in Part I, this is not a tutorial into the basics and inner workings of the
TestComplete plug-in SDK. AutomatedQA has provided some materials on that in the TestComplete SDK, which
is available for download.
These articles are not authoritative in terms of best practices, nor is AutomatedQA responsible
for their content.
My hope is that you can get something out of these articles, perhaps creating your own plug-ins for
TestComplete, or at the very least, simply installing and using the compiled plug-ins.
The TestComplete SDK samples are available for either Delphi or MS Visual C++, but not C# at this time. Thus for now, I'm creating my plug-ins in Delphi.
Let's start with the Registrator class, since that's where TestComplete itself starts with our plug-ins.
In the tsTC4_CSRegistrator.pas source code file, you'll find a descendant of TaqBaseExtensionRegistrator.
The base registrator class is part of the TestComplete SDK and is found in the PluginBaseExtRegistrator unit.
The registrator tells TestComplete about our plug-in using data from the instance of TRegPluginInfo found
in the tsTC4_CSConst.pas unit.
Rule-of-thumb in case you use an existing plug-in as a starting place for your own plug-ins: replace
the GUIDs you find here. You probably already knew that
There's not much to note in the registrator class that's not already covered in the SDK documentation,
but I will just call your attention to the Get_TargetProductKey method of TaqBaseExtensionRegistrator -
this is where you'd be able to specify specifically what edition(s) of TestComplete (or TestExecute) your
plug-in targets. The sample projects (and this plug-in) all specify cAllProduct as the target, meaning all
The Type Library
Next let's take a look at the type lib for this particular project. The type lib is where we define the
Interface(s) that describe what your plug-in makes available for scripting in TestComplete. Since TestComplete's
code completion system uses this interface when displaying available code symbols, you should be aware that what
you define here is what you can use in script code. I'll include the definition of the ItsCodeSite interface here
both in the Delphi declaration version and the IDL version. If you don't care about these, just skip over the
following. However, before you do, pay attention to the values of the IDL text, there you'll see values specified
for the helpstrings. Note that the helpstring is where TestComplete gets the descriptive text it displays when you
are browsing in the Code Completion window.
If you skipped over the code, welcome back! The ItsCodeSite interface leads quite nicely to the next two classes: the plug-in (TtsCodeSitePlugin) and the run-time object (TtsCSRuntimeObj). Briefly, the run-time object is what TestComplete uses to implement the scripting interfaces at test run-time ("test-time"?). As a result, the run-time object needs to implement the interface we described above.
Your head may be swimming at the moment, so here's a quick refresher for those scoring along at home:
Registrator: Tells TestComplete about the plug-in
Interface: Describes the scripting interface of your plug-in (used by both the plug-in object and the run-time object)
Plug-in object: gives TestComplete an access point for managing the design-time features of a plug-in (code completion, options, images, etc.)
Run-time object: what actually executes when TestComplete tries to execute calls to your plug-in.
Ok, back to the action: so as I said, the run-time object implements the interface we described so that TestComplete has something to actually execute when script methods are called. The plug-in object, however, is how we have script methods to call in the first place. The plug-in object for a scriptable plug-in should implement ItcCodeCompletionProvider, an interface provided by the TestComplete SDK. Here's the Delphi declaration of that interface:
If you look close, you'll see that this is basically the information you see about an object in the Code Completion window:
Figure 2 - the Code Completion Window
Notice, the interface asks for a name (GetName) - our plug-in returns "CodeSite".
The interface wants a bitmap (GetBitmap) - I provided a nifty hybrid icon pirating the CodeSite icon and overlaying
it with "TS" for Thoughtsmithy (Hi, Ray. Nice icon).
GetDescription: "Provides scripting access to the Raize CodeSite object"
What about the TypeInformation methods? This is where you point TestComplete to the TypeLib for your plug-in and tell
it what interface to use for the methods and properties to list (AddCheckPoint, AddSeparator, etc. - see ItsCodeSite above).
Easy, huh? Well, maybe not the first time, but I assure you, if I got it, you can.
The Run-time Object
Back to the run-time object. This CodeSite plug-in is a breeze to implement in the run-time realm; it's basically a
pass-though to the CodeSite object. If you look at the source code for the run-time object (TtsCSRuntimeObj) you'll see calls like:
However, you'll also find a few methods where I did a little more than pass-through. Things like the SendPoint and SendRect
methods, but also the EnterMethod and ExitMethod calls. CodeSite uses these to manage a virtual call stack. That wasn't too
complicated as long as I was just passing-though calls to CodeSite, but once I added the second functionality of this plug-in
(event-listening to mirror the TestComplete log in CodeSite) things got much more complicated. The run-time object manages calls
to CodeSite from your scripts, but the plug-in object is what implements the event listening. I'll spare you the details and will
just say that to keep Enter/Exit pairs in synch while also tracking CodeSite category information (colors and strings) I
implemented a few stacks and that's what you see in the TtsCSRuntimeObj.ExitMethod implementation.
And now what you've been awaiting: event-listening! As I've mentioned, besides pass-through methods, this plug-in takes
advantage of the TestComplete SDK provision for event listening to follow the TestComplete Log on a message-by-message basis,
passing each one to CodeSite and formatting the CodeSite messages to match the TestComplete messages where possible.
Take a look at the TestComplete log and the CodeSite mirror side-by-side:
Figures 3 and 4 - TestComplete log (on the left) and CodeSite log
For the most part, they match. There are a couple of issues resulting from changes in TestComplete 4 which I have not
yet addressed, but I'll get most of those worked out, but worth noting is this: one of the new features of the TestComplete 4
Log system is the ability to post a message to a folder in the log by the folder's ID. Using this method allows you to post
messages to folders that have already been "left behind". In other words, the TC4 log does not have to be sequential.
However, since this plug-in is listening and logging in real-time, we do have to be sequential. Thus, in the images above,
you may notice that the yellow-background messages are in the "Second folder" folder in TestComplete's log (on the left),
however, those messages are orphaned in the CodeSite version of the log (on the right) when "Second folder" is closed and
then those messages are posted later. There's not much we can do about that issue (except applaud the coolness of the new Log system).
If we look at the declaration of the plug-in object, notice the interfaces that the object implements:
IaqBaseEvent, IaqEventsListenerProvider and ItcLogMessageEvent. The first two are used for event listening in
general while ItcLogMessagesEvent is for the Log messages specifically. Here are the interface declarations:
IaqBaseEvent - the ancestor of IaqEventsListenerProvider: It basically has one property (EventInfo)
and its get access method, Get_EventInfo. Not much here, in the plug-in implementation, we simply
return the ClassName of the plug-in object. Done.
IaqEventListenerProvider: Not much here either - we implement GetCountListenedEvent to tell ItcLogMessageEvent
TestComplete how many Event interfaces we are implementing (i.e. how many things are we listening to?)
In GetListenedEventInfo, we tell TestComplete what we're listening to (ItcLogMessageEvent). Done.
: This should be fairly familiar to you if you've used TestComplete for any time at all.
Messages, errors, warnings, etc. Any time a message is sent to the Log, these events fire. If we're listening
for them, we can take action when the event fires. In the case of the CodeSite plug-in, when one of these
events fire, I pass on the message to CodeSite. For your own plug-ins, you could handle the messages, alter
them, pass them on somewhere else, etc. Very powerful.
You can use the same technique with other descendants of IaqBaseEvent to achieve some powerful customization.
Obviously there's a good bit more that we could cover here, but that should be enough to get started
with the event listening techniques in your own plug-ins.
You can download the install for this CodeSite plug-in, along with all the source code, a help file and a sample
TestComplete project here.