Real-world TestComplete Plug-ins Part III - Extended Colors Plug-in
In the first two installments of this series, we built a simple TestComplete 4 plug-in that offered a set of run-time string utilities, and a plug-in to offer run-time support for Raize CodeSite.
The goals of these first two sessions were first, to introduce the TestComplete plug-in SDK, and then, to examine the event listening features of the SDK.
This, the third installment, we're introducing a very powerful part of the SDK - visual, design-time panels; the ability to add a UI component into the TestComplete workspace.
In this article,
- We'll look at creating panels for your plug-ins
- I'll explain the interfaces used in creating settings accessed from the TestComplete options panel.
- I'll explain also about the mechanism I use for supporting settings.
- Though I use the TestComplete Syntax system in this plug-in, I'll hold off on going into detail - that's the subject of my next article.
Before we continue, let me point out again that these articles are not presenting a "best practices" approach to creating custom TestComplete plug-ins. Rather they serve to introduce, and explain (to some extent) some of the classes in the TestComplete SDK (downloadable by you, here
). No doubt there are other ways to implement these plug-ins, but I'm following the model set forth in the samples from the SDK download and applying them to full-blown, functional plug-ins to see how these things can be implemented in the real world.
Lastly, as I've also pointed out previously, the TestComplete SDK is not currently available for developing plug-ins in C#, rather it is possible to create plug-ins either in C++ or in Delphi. Since I've never been a fan of needless obfuscation, I'm creating these plug-ins in Delphi.
Extended Colors Plug-in
What are we trying to accomplish with this plug-in? Well, I was always a bit frustrated that there wasn't more robust support in TestComplete for custom, or named colors. I didn't want just one red or green - I wanted a dozen, for different reasons.
Also, at the very first TestComplete training summit, in Vegas, Robert Martin, Greg Stevenson and I (and the rest of the group) discussed what we would want in a TestComplete colors plug-in. Here it is, guys!
Here are the specs:
- To be able to maintain a customizable list of custom colors, accessible by color name, to be used in TestComplete scripts.
- To have convenient access to the color list at TestComplete design-time.
- To be able to insert color references (either by value or by color name) into a TestComplete script.
- Apart from the normal design-time access to the color list, to also offer a means of adding new named colors to the list.
The Extended Colors Plug-in meets these specs. We'll look over the source code, taking particular notice of how we can implement panel-based parts of our plug-ins, but first, let's look at the end result.
The Colors plug-in provides a small panel in the TestComplete Select Panels
list. If you select it (to make it visible) and then dock it somewhere, you'll see something like this (figure 1).
- notice the Colors plug-in docked between the Code Explorer and the Project Explorer.
From this panel, we can display the available color list, with color swatches, from the drop-down list at the top of the panel. If you click the down arrow, you'll see something like figure 2. Figure 2
- selecting a color from the color list.
Once you've selected a color, you can insert the selection into your test script, either by name (using the Colors.ColorByName() method, which we'll examine in a moment), or by integer value. Alternatively, you could copy those values to the Windows clipboard to paste wherever you'd like. See figure 3. Figure 3
- inserting the selected color.
Clicking Insert will insert the selected color reference at the current cursor position in the active code editor window. You can experiment with the other options, but they're fairly straightforward. Notice that there's a refresh button to the right of the color drop-down. You can refresh the plug-in's color list from the color list file (an INI file we'll discuss later).
Speaking of ColorByName(), see figure 4.
- the rather uncomplicated code completion list for the Colors object.
If you take out Cream, MoneyGreen and SkyBlue (which are just direct references to the Borland colors of the same names), you only have Count (a count of items in the color list), Names (an indexed list of the names in the color list) and ColorByName() (a routine for returning a color value, from the color list, by name). Simple, huh?
Back in the plug-in window, if you click the little color palette button between the drop-down and the refresh button, you'll see figure 5. Figure 5
- the Add Color to Color List dialog.
The Add Color window allows you to add colors (and give them names) to the color list. You can type in an HTML color value, pick a color from the Color Picker, or define the color in RGB values using the sliders. Perhaps if I get ambitious, I'll add set of HSL (hue, saturation, and luminescence) selectors for us geeky techno-art types.
One last thing, there's a TestComplete setting (option) for this plug-in. Figure 6.
- the TestComplete options pages. Notice an option for setting the location of the Colors.ini file.
That's all there is to it. Colors are a fairly simple affair, managing them should be too. Now let's dig in and see how all this works.
I've covered the basics of TaqBaseExtensionRegistrator and its descendents in the previous installments of this series, but I'll just remind you that the TtsColorsPluginRegistrator from this project uses tsColorsPluginInfo, an implementation of TRegPluginInfo, found in the consts file, tsTC4_ColorConst.pas.
The constants file contains much of the same elements we've seen in the previous installments, such as a Registrator key GUID, a plug-in name, copyright info, etc. But in this instance, there exist some other constants. Some are easily explained, like the color names, but you'll also see several constants involving "Settings". Yes, those are values involved with providing the option we saw in figure 6, above. They're rather straightforward, but we'll get to them later.
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 ItsColors 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; you'll see there 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've been reading along in parts I and II of this series, you probably know what's coming next - the plug-in object and the runtime object; the two primary implementations of the plug-in at design-time and at run-time, respectively. The run-time object we'll get to shortly. As for the plug-in object, we're seeing a few new things here.
Let's look at the declaration.
The TaqBaseWindowPlugin methods we've covered before, as well as the ItcRuntimeObjectFactory and ItcCodeCompletionProvider methods. That leaves us with the Settings methods (IaqSettingsUpdateListener_DVS and IaqSettingsProvider_DVS) - I really didn't want to talk about these until I talked about settings, but I've been putting that off long enough, so let's cover it now.
In this implementation, there are two parts (two interfaces) to managing our plug-in settings.
- Telling TestComplete about our settings
- Listening for when TestComplete says our settings have changed.
It should be fairly easy to tell which interface is for which item:
IaqSettingsProvider_DVS provides TestComplete with information about the settings we need.
IaqSettingsUpdateListener_DVS listens for when our settings have been updated.
By the way, I'm assured that the "DVS" doesn't mean anything, that it just serves as a distinguishing suffix. I'm not so sure - there must be a good story behind that
Anyway, let's look at the two interfaces.
When our plug-in implements IaqSettingsProvider_DVS, InitializeSettings is where we tell TestComplete about the settings we need to maintain. Presumably, if we had some cleaning up to handle, we could use FinalizeSettings for that purpose, but I have yet to implement that method.
Here's the Colors plug-ins implementation of InitializeSettings.
Dissecting this, we can see that first I'm loading an image, it's the Colors plug-in icon, and we'll use this in a minute.
Next, I'm getting a reference to the TestComplete Settings Manager (IaqSettingsManagerEx_DVS). Then, accessing the settings manager as an IaqSettingsGroupOwner, we create a settings group.
Here's the declaration of CreateSettingcsGroup:
Look at figure 6 again to see the settings group we're creating. Here it is:
Almost all the parameters of CreateSettingsGroup we can see here.
- AKey - a GUID - it uniquely identifies the settings group. Mine is declared in the consts file as cSettingsGroupKey.
- APath - similar to a menu path, this is the path down through the Options tree to our desired settings group. In this case, it's "Thoughtsmithy|Colors".
- ACaption - the caption for the settings group. It's in the grey bar at the top of the dialog: "Colors plug-in Settings"
- AHelpContext - I haven't experimented with this, but I'd be interested to know whether this works for 3rd-party plug-ins. I doubt it, but will welcome correction.
- AImageHandle - a bitmap handle to the image you want displayed in the dialog's caption area. In this case, it's the little color palette icon.
Now that we've created a settings group, we can add a setting. We're adding a setting that holds the path to the INI file that contains our colors list. In the code listing above, the Colors plug-in's InitializeSettings, we create a setting by calling CreateSetting, then we populate it, by calling SetValue.
Here's the declaration of CreateSetting:
- ASettingType - You'll find the definition for AQ_SETTING_TYPE in TestComplete_TLB, but here it is:
No doubt you can discern that these are the available settings types. They should be fairly clear, but let me take this moment to discuss how settings are setup on the Options pages. One of my favorite parts of the SDK is how, when I add settings to a plug-in, part of TestComplete's role is to nicely display the settings in the Options dialog. You don't need to worry about any of that, you just create the settings, give TestComplete the information about the setting, and TestComplete takes care of the rest.
You can see, in figure 7 that TestComplete has nicely laid out my settings, taking into account the caption length.
- ASettingKey - Here's the ubiquitous GUID to identify the setting. You'll no doubt remember the GUID I have in my constants file, tsTC4_ColorsConst.pas. Here's where it comes into play.
- ASettingGroupKey - the GUID for the settings group we added earlier. If you added more than one settings group per plug-in, you could specify to which group each setting belongs.
- ACaption - the name of the setting; in our humble example, it's "Colors ini file"
- ADescription - this is the string that appears down in the Description box at the bottom of the dialog.
- ACategory - I'm not just entirely sure how this is different from the GUID for settings group key (other than the fact that it's not a GUID), but the method asks for it, so I don't argue.
- AReadOnly - is the setting writable?
- AStored - the value persisted between sessions?
- ASetting - this is the created IaqBaseSetting_DVS.
So much for IaqSettingsProvider_DVS; now let's take a look at IaqSettingsProviderListener_DVS. Implementing this interface is very straightforward: basically, it listens for when a setting changes, and executes UpdateSetting when the change occurs.
If we look at the Color plug-ins implementation of UpdateSetting, youll see that were just evaluating which setting changed (theres only one for this plug-in) and taking appropriate action.
I've put off discussing something, in part because I'm frustrated that I haven't come up with a better solution. It's the issue of sharing values between the realms of the plug-in object and the run-time object. I've discussed this with the AQA support guys, and what I'm doing is what was suggested by them.
The issue is that the run-time object can't ask the plug-in for values. In the TestComplete 3 SDK, this was a non-issue, because there was not this clear delineation between the plug-in and run-time domains. But in TC4, the plug-in is the listener, but the run-time object needs the values when playing back the script. Presumably the solution would be to make the run-time object also implement IaqSettingsProviderListener_DVS, and I've tried that but ran into some issues. I'll be trying that approach again soon, but since the model I'm using for this article has served me well in some other, non-settings-related ways, I'll probably keep using it even if I get the other approach working.
So here's the solution to the dilemma: Figure 8
- sharing values.
On the left is our plug-in. On the right, the run-time object. These two need to share info, but don't know about each other. The suggested solution was to create a repository in a common location. Thus, I've created TtsColorsUtils (and other _Utils classes). These are static classes that store values privately, offering Get_ and Set_ methods as static ("class methods" in Delphi) methods.
Thus, when TsColorsPlugin receives a settings update notification, it sets the common value via TtsColorsUtils.SetColorsIniPath(). When TtsColorsRuntimeObj needs the value, it calls TtsColorsUtils.GetColorsIniPath(). What can I say? It works.
The Visual Panels
If you look at our plug-in object's methods, you'll see the following that are inherited from TaqBaseWindowPlug-in.
The two that we're looking at are CreateControl and GetPanelImageName, the latter, first.
The implementation of GetPanelImageName could not be simpler.
We're just returning the name of the image in our resource file that is to be used for our plug-in panel's icon. I have a resource file, tsColorsIcons.res that contains the image named "TSCOLORSICON". Figure 9
- the resource file.
You can see that the image appears on the plug-in's panel in TestComplete.
The other method, CreateControl, is equally simple. We just create the plug-in's panel by calling its CreateEx method. Note that there's nothing terribly fancy about the form, TtsFrm_Colors. It descends from TForm, and I've added an additional constructor, CreateEx(). This is another one of those things that's handled automagically by TestComplete. If you've set everything up correctly, your plug-in's panel will appear in the Select Panel dialog under the View menu in TestComplete, figure 10. Figure 10
- The Colors panel (and icon) in the Select panel dialog.
As far as the other form, the Add Colors dialog is concerned, it is created and handled entirely by the plug-in panel. TestComplete doesn't know anything about it. That's a whole lot of TestComplete plug-in UI goodness for very little coding effort.
As for the remainder of the working code for this plug-in, I leave it to the student to examine. The INI file is where we store the color values, in a big name=value list. The plug-in reads and sets the values in that list, and uses the values for displaying the colors by name.
As you can see, creating custom plug-ins for TestComplete that implement a visual interface is quite simple. The larger issue in this article was actually dealing with settings. I hope that this article, along with the sample projects in the TestComplete 4 SDK download will get you up and running with creating your own plug-ins. Even if it doesn't, I hope you'll find the accompanying Colors plug-in to be of value.
The plug-in install, source code, sample test project and documentation can be downloaded here
Next up, Part IV - the TestComplete SDK Syntax System.