TestComplete 3 FAQ. Writing Scripts - Part 1
This page contains answers to frequently asked questions about TestComplete 3. For answers to questions about TestComplete ver. 4 - 6, see TestComplete 6 FAQ.
Q.: Can I connect to any ODBC (or OLE DB) database to check results? Can I work with database fields from scripts?
A.: Yes, you can. There are several ways to do this:
The ADO plug-in provides work with databases through ADO interfaces. It provides access to native ADO objects and to analogues of the VCL ADO support components. The second plug-in, BDE, supports work with databases via the Borland Database Engine objects.
[VBScript]
Set Con = CreateObject("ADODB.Connection")
Con.ConnectionString = _
"Provider=Microsoft.Jet.OLEDB.3.51;"
+ _
"Data Source=D:\Program
Files\Microsoft Visual Studio\VB98\biblio.mdb"
Con.Open
. . .
In general, you can work with any database by setting up a corresponding DSN and using the ADO plug-in. For example, in the Control Panel | Administrator Tools | Data Source (ODBC) dialog create a new DSN based on the "Microsoft Access Driver (*.mdb)" driver and link this DSN to the Biblio.mdb database that comes with Microsoft Visual Basic. Name the DSN as "MyDSBiblio" and run the following script to see how to work with the database via the ADO plug-in:
[VBScript]
' The TestDB routine posts to the test log
all authors
' whose birth year is specified in the database
Sub TestDB
' Creates a new connection
Set ConnDB = ADO.CreateConnection
ConnDB.ConnectionString = "DSN=MyDSBiblio"
ConnDB.Open
' Opens a recordset
Set Tbl = ConnDB.Execute("SELECT
* FROM Authors " + _
"WHERE [Year
Born] IS NOT NULL")
' Scans all records returned
by the query
Tbl.MoveFirst
While Not Tbl.EOF
' Gets the
field values
s = Tbl.Fields("Author").Value
' Converts Year
Born (date value) to string value
s2 = CStr(Tbl.Fields("Year
Born").Value)
' Posts message
to the log
Log.Message s + " (" + s2 +
")"
Tbl.MoveNext
WEnd
' Closes the recordset and connection
Tbl.Close
ConnDB.Close
End Sub
See also the topic "Retrieving Data from Database Fields" in TestComplete on-line help. It holds plenty of references to examples that illustrate working with the databases from scripts.
Q.: If the window (or process) does not exist, I get an error. How can I check if the window (process) exists?
A.: Use the Exists method. Each process or window object in TestComplete has the Exists method. It returns True, if the tested object actually exists in the system. Else - False. For instance,
[VBScript]
' Gets
the process object
Set p = Sys.WaitProcess("MyApplication",
5000)
If p.Exists Then
' continue script execution
Else
Log.Error "The process does not exist."
End If
If you want to avoid error messages which are posted to the log by the Process, Window or Child functions when the required process or window does not exist, use the WaitWindow, WaitProcess or WaitChild routines. They delay the script execution for the specified period of time or until the specified object appears. They don't post any messages to the test log as Process or Window do. The general rule is that the Process, Window and Child methods should be used when you are sure that the required object already exists. WaitProcess, WaitWindow and WaitChild should be used when you are not certain if the object exists.
One note: Suppose, you have a window object with the name Window('WndClass', 'WndCaption', -1). You can wait for it using the following code:
p.WaitWindow('WndClass*', 'WndCaption*', -1, 5000)
If you map this object to a new name, say MyWindow, you can still use this code to wait for the object. However, name mapping means you want to avoid problems that may occur due to changes in object attributes in different builds of the tested application. So, you may wait for the object using the following code:
p.WaitChild('MyWindow', 5000);
Q.: How do I delay the script execution until the desired window appears on screen?
A.: .: It's quite easy --
[DelphiScript]
repeat
w := p.WaitWindow('WindowClassName', 'WindowCaption', 1, 500);
until w.VisibleOnScreen;
[VBScript]
b = False
Do Until b
w = p.WaitWindow("WindowClassName", "WindowCaption", 1, 500)
b = w.VisibleOnScreen
Loop
[JScript]
b = false
while(! b){
w = p.WaitWindow("WindowClassName", "WindowCaption", 1, 500)
b = w.VisibleOnScreen
}
[C++Script, C#Script]
b = false;
while(! b){
w = p.WaitWindow("WindowClassName", "WindowCaption", 1, 500);
b = w.VisibleOnScreen;
}
Q.: Does TestComplete handle exceptions in the application under test?
A.: TestComplete can trace exceptions only if the application under test is an Open Application. The fact is that TestComplete handles exceptions only when the script calls methods or properties of an application object. This is not possible with ordinary "closed" applications. Suppose that an exception occurs after pressing a button on a form. TestComplete will handle exception if the script simulates the button pressing with one of the following instructions:
w.Button1.Native.Click
orw.Window("TButton", "Button1").Native.Click.
TestComplete will not handle the exception, if you use the following code to simulate the button pressing --
w.Window('TButton','Button1').Click
// Exception is not handled!!!
Note that Internet Explorer 6.0 from .NET Framework includes some issues, so exceptions that occur in VBScript, JScript or C++Script routines are not passed to TestComplete. The script simply stops without any error message. In this case, you can use the following code to determine the description of the error.
Here is a sample script that handles exceptions --
[DelphiScript]
procedure Test;
var
p, w: OleVariant;
begin
p := Sys.Process('Project1');
w := p.Form1;
w.Activate;
try
w.Button1.Native.Click; // We call "native" object method
except
// Posts the exception message to the test log
Log.Message(ExceptionMessage);
end;
end;
[VBScript]
Sub Test
Set p = Sys.Process("Project1")
Set w = p.Form1
On Error Resume Next
w.Button1.Native.Click ' We call "native" object method
If Err.Number > 0 Then
' Posts the exception message to the log and
' clears the Err object
Log.Message(Err.Description)
Err.Clear
End If
End Sub
[JScript]
function Test()
{
var p, w
p = Sys.Process("Project1")
w = p.Form1
w.Activate()
try
{
w.Button1.Native.Click // We call "native" object method
}
catch(exception)
{
// Posts the exception message to the test log
Log.Message(exception.description)
}
}
[C++Script, C#Script]
function Test()
{
var p, w;
p = Sys["Process"]("Project1");
w = p["Form1"];
w["Activate"]();
try
{
w["Button1"]["Native"]["Click"](); // We call "native" object method
}
catch(exception)
{
// Posts the exception message to the test log
Log["Message"](exception["description"]);
}
}
Q.: I'd like to access my application objects by name in my scripts. Is it possible?
A.: Yes, sure. The application under test must be compiled as an Open Application. This makes the application objects, methods and properties available to TestComplete. In scripts you can refer to the application objects, methods, properties or fields by their names. For instance:
[DelphiScript]
var
p, w;
begin
p := Sys.Process('MyVCLApp');
// Reference to an object
w := p.MainForm;
// Reference to a property
w.Button1.Caption := 'New button caption';
end;
This feature works for Delphi, C++Builder and Visual Basic Open Applications and does not work for Visual C++ (Visual C++ objects have no Name property).
Note that method or property name may coincide with the method, property or action name used in TestComplete for this object. For example, to work with buttons, TestComplete uses the Win32Button object. It has the Click action that simulates a mouse click on the button. The application object Button may also have a Click method, e.g. VCL's TButton objects have this method. In such case you should use the keyword native to specify that you want to call a method of the application object.
w.Button1.native.Click // <-- call to an application method
w.Button1.Click // <-- call to TestComplete action
If your application is not an Open one or the application has no names (as in case with the Visual C++ applications), you can assign names to the desired object using the Name Mapping panel. For instance, you can map the name Window('TMainFormClass','MainFormTitle', -1) to MyMainFrm and then work with the window using the mapped name, that is, instead of
p.Window('TMainFormClass','MainFormTitle', -1).Click(10, 20)
you can write
p.MyMainFrm.Click(10, 20)
For more information on mapping the object names, see "Name Mapping" in TestComplete on-line help.
Q.: Can I access TestComplete options from scripts?
A.: Use the Options object for this purpose. For instance, the following code changes the On Unexpected Window | Stop Execution option (the option from the Run Options group of the Options | Engine Options dialog) --
Options.Run.StopOnUnexpWindow = False
Q.: How to find the focused control?
A.: You can use the following function to do this:
[DelphiScript] function GetFocusedWindow(var FocusedWnd: OleVariant): OleVariant; var w, i: OleVariant; begin w := Sys.ActiveWindow; for i := 0 to w.ChildCount - 1 do if w.Child(i).Focused then begin FocusedWnd := w.Child(i); Result := True; Exit; end; Result := False; end;
[VBScript]
Function GetFocusedWindow(FocusedWnd)
Set w = Sys.ActiveWindow
For i = 0 To w.ChildCount - 1
If w.Child(i).Focused Then
Set FocusedWnd = w.Child(i)
GetFocusedWindow = True
Exit
End If
Next
GetFocusedWindow = False
End Function
[JScript]
function GetFocusedWindow(FocusedWnd)
{
var w, i;
w = Sys.ActiveWindow();
for (i = 0; i < w.ChildCount; i++)
{
if (w.Child(i).Focused)
{
FocusedWnd = w.Child(i);
return True;
};
};
return False;
}
[C++Script, C#Script]
function GetFocusedWindow(FocusedWnd)
{
var w, i;
w = Sys["ActiveWindow"]();
for (i = 0; i < w["ChildCount"]; i++)
{
if (w["Child"](i)["Focused"])
{
FocusedWnd = w["Child"](i);
return True;
};
};
return False;
}
Here is an example of how to use the above function:
[DelphiScript]
procedure Test;
var p, w, FocusedWindow: OleVariant;
begin
p := Sys.Process('notepad');
w := p.Window('Notepad', '*');
w.Activate;
if GetFocusedWindow(FocusedWindow) then
begin
ShowMessage(FocusedWindow.Name);
ShowMessage(FocusedWindow.wText);
end;
end;
[VBScript]
Sub Test;
Set p = Sys.Process("notepad")
Set w = p.Window("Notepad", "*")
Call w.Activate
If GetFocusedWindow(FocusedWindow) Then
Call ShowMessage(FocusedWindow.Name)
Call ShowMessage(FocusedWindow.wText)
End IF
End
[JScript]
function Test
{
var p, w, FocusedWindow;
p = Sys.Process("notepad");
w = p.Window("Notepad", "*");
w.Activate();
if (GetFocusedWindow(FocusedWindow))
{
ShowMessage(FocusedWindow.Name);
ShowMessage(FocusedWindow.wText);
};
}
[C++Script, C#Script]
function Test
{
var p, w, FocusedWindow;
p = Sys["Process"]("notepad");
w = p["Window"]("Notepad", "*");
w["Activate"]();
if (GetFocusedWindow(FocusedWindow))
{
ShowMessage(FocusedWindow["Name"]);
ShowMessage(FocusedWindow["wText"]);
};
}
Q.: Is there a function that returns a window object from the window that is currently in focus?
A.: Yes. Use Sys.ActiveWindow.
Q.: How to call procedures or variables declared in another unit? I need to call a procedure declared in UnitA from UnitB and vice versa.
A.: By default, a script can call routines declared within the same unit. To call scripts in another unit, the current one must be "linked" to it -
In DelphiScript projects place cursor at the beginning of the unit and add the following string:
uses Unit_Name;
If you need to link to several units, separate their names with commas, that is
uses Unit1_Name, Unit2_Name;
In VBScript projects place cursor at the beginning of the unit and add the following string:
'USEUNIT Unit_Name
Be sure you specify the apostrophe ( ' ) before USEUNIT.
To link to several units, use the following syntax:
'USEUNIT Unit1_Name
'USEUNIT Unit2_Name
In JScript, C++Script or C#Script projects place cursor at the beginning of the unit and add the following string:
//USEUNIT Unit_Name;
Be sure to specify the double slash ( // ) before USEUNIT.
You can link to several units in the following manner:
//USEUNIT Unit1_Name
//USEUNIT Unit2_Name
Note that units can refer to each other. In other words, circular references are allowed. For instance, if you need to call scripts declared in UnitA from UnitB, and to call scripts declared in UnitB from UnitA, you must add references to both units --
[VBScript]
UnitA
'USEUNIT UnitB
...
UnitB
'USEUNIT UnitA
...
[DelphiScript]
UnitA
uses UnitB;
...
UnitB
uses UnitA;
...
Circular references are not allowed in JScript, C++Script and C#Script. In these languages, circular links may cause errors in jscript.dll (C++Script and C#Script are based on JScript and use this dll as well), which causes errors in TestComplete.
Q.: I can use the same name for two procedures that are in different units, because I cannot use a prefix that would specify the unit. Is this correct?
A.: In DelphiScript you can specify the unit name before the routine name, i.e. you can use the syntax unit_name.script_name to call a script routine. VBScript and JScript do not support such a feature. For instance, in VBScript you can use Private to make a procedure invisible outside its own unit, but you have no way of selecting among several external procedures of the same name. VB would do it, but not VBScript.
Q.: Is there a way to call routines defined in another project?
A.: Yes. It's possible. If you have several test projects, you may want to use the same routines from one project in other ones. The uses (or USEUNIT) clause added to a unit, lets you refer to routines defined in other units of the same project. If you need to call routines defined in a unit from another project, you should add a unit that holds these routines to your project. To do so, select Add Unit on the Units toolbar. This will bring up the standard Open File dialog where you can specify the unit to be added.
![]() |
By default, both Unregister ProjectEvents and Insert ProjectEvents in new project options (Options | Engine Options | Project events) are enabled in TestComplete. This means that whenever you create a new project, TestComplete starts it with a new unit and this unit (Unit1.sxx in the project’s folder) has a form associated with it (Unit1.fxx). The form has the ProjectEvents control placed to it and the corresponding type library file (<ProjectName>.tlb) in the project’s folder. The type library makes it possible to call actual event handlers from the EventControl.ocx file, which is located in the folder TestComplete is installed to. This .tlb file is referenced by the form file. As a result, each time TestComplete loads the project, it registers this type library. Since the Unregister ProjectEvents option is on, each time TestComplete closes the project, it unregisters the project’s type library. So, if you try to use a unit in some project (project A) while this unit belongs to another project (project B) and has an associated form with the ProjectEvents control on it, TestComplete will try to process the type library to which this form refers. However, since the type library is not registered in the system (it was unregistered when closing project B), this attempt will fail and you will get the error message “Error reading ProjectEvents1.CGUID: class not registered.” This will not make the entire project A unusable. Only the events that are handled in the unreachable type library will not be handled in the way programmed in project B. If an event is handled in both projects, in project A it will be handled according to the logic implemented in this project. TestComplete’s project structure allows only one ProjectEvents control per project (that is why the .tlb file gets the name of the project, rather than of the unit/form the file is referenced by). However, sometimes it might be useful to have more than one ProjectEvents control in the same project. To do this, you can use a simple workaround: before importing a reusable unit, disable the Unregister ProjectEvents option. As a result, after TestComplete closes a project that contains the ProjectEvents control, the type library remains registered in the system and TestComplete can use it from other test projects. The entire technique is as follows:
If the reusable unit handles the same event for both project A and project B (e.g. OnUnexpectedWindow) and this event occurs during project A’s run, TestComplete will call the corresponding handler from project A and, if the window remains unhandled, TestComplete will call the handler from project B. |
If the unit includes the routines you don't want to "export", you can create a new unit by selecting New Unit on the Units toolbar, copy the needed routines to this unit and then add the new unit to the desired project. You may even create a unit "library", that is, units that hold the desired routines, and then add these units to any project you want.
Q.: I need to compare an object in the beginning, and then compare it again at the end. How would I go about doing that?
A.: First, you should save the properties of the desired object using the Objects.Save method. Then, you can change the object's properties and call Objects.Compare to compare them with values you have saved. Below there is a sample routine. Note that you must compile your application as an Open one in order to have access to the object's properties:
[VBScript]
Sub SaveAndChange
TestedApps.RunAll 'You may also use Sys.LaunchApplications
Set p = Sys.WaitProcess("Project1", 10000, True)
If Not p.Exists Then
Log.Warning "Project not found."
Exit Sub
End If
' Gets the button to be saved
' In our case the button name is Command1
Set SaveBtn = p.Form1.Command1
' Saves the buttons properties
Objects.Save SaveBtn, "OldBtn"
' Changes the object properties
SaveBtn.Caption = "New Caption"
' Compares the button properties with
' the saved ones
If Objects.Compare(SaveBtn, "OldBtn") Then
Log.Warning "Caption was not changed."
Else
Log.Message "Caption was changed"
End If
End Sub
Note that Objects.Compare compares all stored object properties. If for any reason you wish to compare only part of them, you can use the Partial Compare plug-in written by Eric Holton. The plug-in is available for free download at http://www.automatedqa.com/downloads/plugins.asp.
Q.: Is it possible to capture an image or a screen while executing the script for later comparison?
A.: Yes. Scripts can save images using the Regions.SaveToFile and Regions.SaveToClipboard methods. The saved image will be automatically added to the Regions page of the Stores panel. To compare images, use the Regions.Compare method:
[VBScript]
Set w = p.Window("MyWindowClass", "MyWindowCaption", 1)
w.Activate
Regions.SaveToFile w, "MyImage1"
. . .
If Not Regions.Compare(w, "MyImage1") Then
Log.Warning "The window " + w.FullName + " was changed!"
End If
See also the "How To Compare and Find Images" and "How To Save and Compare Images and Properties" help topics.
Q.: I save the object using Objects.Save and some properties, e.g. Items (TStrings type), are not saved. Is there any way to save these properties?
A.: The Objects object can save only properties of simple types (integer, string, and so on) TestComplete engine works with TStrings via IDispatch, and thus, properties of the TStrings objects are not saved. So, for instance, if you need to save tree view items, Objects will not save you from writing the save and retrieve code yourself. For instance, the code below illustrates how you can save TreeView items. This example assumes that the application displays a form with a VCL TListView control on it, and that this list view is passed to SaveItems as its first parameter. SaveItems first retrieves all treeview items and saves them as text under the filename specified as the last parameter. Next, SaveItems compares this file with another one (hard-coded) and reports directly to the Log whether the treeview contents have changed. Note that the application under test must be compiled as an Open Application. To start testing, run the MainTest procedure in TestComplete.
[DelphiScript]
procedure SaveItems(LV : OleVariant; FileName : string);
var
i, j : Integer;
s : string;
F : TextFile;
Node : OleVariant;
begin
AssignFile(F,FileName);
Rewrite(F);
try
for i := 0 to LV.Items.Count - 1 do
begin
Node := LV.Items.Item[i];
s := Node.Caption;
Log.Message(s);
Writeln(F,s);
{ If you use PRegister to prepare the Open Application,
use the following line }
for j := 0 to Node.SubItems.Count-1 do
{ If you work through Debug Info Agent™,
the TStrings.Count property is invisible
to TestComplete (see Debug Info Restrictions).
You have to call the read method of
the Count property directly.}
for j := 0 to Node.SubItems.GetCount-1 do
begin
{ If you use PRegister to prepare the Open Application,
use the following line }
s := Node.SubItems.Strings.Strings[j];
{ If you work through Debug Info Agent™,
the TStrings.Strings property is invisible to TestComplete.
You have to call its read method directly.}
s := Node.SubItems.Strings.Get(j);
Writeln(F,s);
end;
Writeln(F,'');
end;
finally
CloseFile(F);
end;
// Compares two files
if not Files.Compare(FileName, 'ListViewContents.txt') then
Log.Error('ListView has changed', '', pmHighest)
else
Log.Message('OK', '', pmLower);
end;
procedure MainTest;
var
p, w: OleVariant;
begin
TestedApps.RunAll;
p := Sys.Process('Project1');
w := p.Form1;
w.Activate;
SaveItems(w.ListView1,'c:\Work\Test1.txt');
end;
Q.: How do I simulate a keypress from script code?
A.: There are two possible ways:
- The first, the simplest, is to use the Sys.Keys method. It
allows you to simulate the pressing of any key including functional
keys (Tab, Enter, Del, Backspace,
arrow keys, and so on). For example,
[VBScript]
Sys.Keys "Hello, wro[BS][BS]orld[Enter]"[DelphiScript]
Sys.Keys('Hello, wro[BS][BS]orld[Enter]');[JScript]
Sys.Keys("Hello, wro[BS][BS]orld[Enter]");[C++Script, C#Script]
In most cases Sys.Keys will fit all your needs. However, this method has some limitations. For instance, it cannot simulate holding the Shift key for a long period of time, e.g. if you need to hold Shift pressed while you are pressing several keys. The possible solution here is to use the second method.
Sys["Keys"]("Hello, wro[BS][BS]orld[Enter]"); - The second method is more complex than the first one. It uses
Windows API functions for the keypress simulation. Below there
are DelphiScript and VBScript examples. To use them, you must
install Win32API plug-in in TestComplete.
[DelphiScript] function IsExtendedKey(VK: Byte): Boolean; begin case VK of VK_SNAPSHOT, VK_DIVIDE, VK_NUMLOCK, VK_INSERT, VK_DELETE, VK_HOME, VK_END, VK_PRIOR, VK_NEXT, VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT, VK_LWIN, VK_RWIN, VK_APPS: Result:= True; else Result:= False; end; end; procedure PressKey(VK: OleVariant); var Flags: OleVariant; begin if IsExtendedKey(VK) then Flags := KEYEVENTF_EXTENDEDKEY else Flags := 0; keybd_event(VK, MapVirtualKey(VK, 0), Flags, 0); end; procedure UpKey(VK: OleVariant); var Flags: OleVariant; begin if IsExtendedKey(VK) then Flags := KEYEVENTF_EXTENDEDKEY else Flags := 0; keybd_event(VK, MapVirtualKey(VK, 0), Flags or KEYEVENTF_KEYUP, 0); end; procedure Test1; begin PressKey(VK_SHIFT); . . . // Simulate user actions here . . . // UpKey simulates the releasing of a key // Each call to PressKey must have a corresponding call to UpKey UpKey(VK_SHIFT); end;[VBScript] Function IsExtendedKey(VK) Select Case VK Case VK_SNAPSHOT, VK_DIVIDE, VK_NUMLOCK, _ VK_INSERT, VK_DELETE, VK_HOME, VK_END, _ VK_PRIOR, VK_NEXT, VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT, _ VK_LWIN, VK_RWIN, VK_APPS IsExtendedKey = True Case Else IsExtendedKey = False End Select End Function Sub PressKey(VK) Dim Flags If IsExtendedKey(VK) Then Flags = KEYEVENTF_EXTENDEDKEY Else Flags = 0 End If Call keybd_event(VK, MapVirtualKey(VK, 0), Flags, 0) End Sub Sub UpKey(VK) Dim Flags If IsExtendedKey(VK) Then Flags = KEYEVENTF_EXTENDEDKEY Else Flags = 0 End If Call keybd_event(VK, MapVirtualKey(VK, 0), Flags or KEYEVENTF_KEYUP, 0) End Sub Sub Test1 Call PressKey(VK_SHIFT) . . . ' Simulate user actions here . . . ' UpKey simulates the releasing of a key ' Each call to PressKey must have a corresponding call to UpKey Call UpKey(VK_SHIFT) End Sub
Q.: How to create a COM object via script?
A.: Call the Sys.GetOleObject method and pass the program ID or class ID of the desired OLE object to it. For example,
[DelphiScript]
w := Sys.GetOleObject('Word.Application'); // Creates MS Word
w.Visible := True;
w.Documents.Add; // Creates a new document
. . .
[VBScript]
Set w = Sys.GetOleObject("Word.Application") ' Creates MS Word
w.Visible = True
w.Documents.Add ' Creates a new document
. . .
[JScript]
w = Sys.GetOleObject("Word.Application"); // Creates MS Word
w.Visible = true;
w.Documents.Add(); // Creates a new document
. . .
[C++Script, C#Script]
w = Sys["GetOleObject"]("Word.Application"); // Creates MS Word
w["Visible"] = true;
w["Documents"]["Add"](); // Creates a new document
. . .

