TestComplete 6 FAQ - Writing Script Code
This page contains answers to frequently asked questions about TestComplete ver. 4 - 6. For answers to questions on TestComplete 3, see TestComplete 3 FAQ.
Q.: How can I 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 routines in another unit, the current one must be “linked” to it:
In VBScript projects place the 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 DelphiScript projects place the cursor at the beginning of the unit and add the following string:
uses Unit_Name;
If you need to link several units, separate their names with commas, that is
uses Unit1_Name, Unit2_Name;
In JScript, C++Script or C#Script projects place the 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 several units in the following manner:
//USEUNIT Unit1_Name
//USEUNIT Unit2_Name
Note: The USEUNIT statement does not allow tab or space characters at the beginning of the line and between the USEUNIT and the comment operator (' in VBScript and // in JScript).
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 the jscript.dll (C++Script and C#Script are based on JScript and use this dll as well), which causes errors in TestComplete.
Another solution is to use the Runner.CallMethod function. It calls any routine specified by its full name (that is, unit_name.routine_name). If you call a script routine using this function, there is no need to include the unit specified by unit_name in the uses (USEUNIT) clause of the current unit.
[VBScript]
Call Runner.CallMethod("UnitA.MyScript")
[JScript]
Runner.CallMethod("UnitA.MyScript")
[DelphiScript]
Runner.CallMethod('UnitA.MyScript)
[C++Script, C#Script]
Runner["CallMethod"]("UnitA.MyScript")
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.: You can use the same name for the routines that are in different units. To distinguish routines use the unit name as a prefix, that is you can use the syntax unit_name.script_name to call a script routine. In earlier versions of TestComplete this approach only worked for DelphiScript, but now works for all scripting languages.
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 this, right-click the Script project item in the Project Explorer panel and choose Add | Existing Item from the context menu.
This will display the standard Open File dialog where you can
specify the unit to be added.
If the unit includes routines that you do not 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 can 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 cannot find functions to deal with time. Where should I search for them?
A.: VBScript and JScript have special objects and functions
that return time, e.g. Time or Now in
VBScript or the Date object in JScript. Since C++Script and C#Script are based on JScript,
you can use JScript functions in these languages as well.
If you use DelphiScript, you can use the following routines from the Utilities plug-in (it is installed in the <TestComplete>\Extensions folder) --
DateTimeToStr
StrToDateTime
Now
Date
Time
See the “Utilities Object” help topic for more information.
For example:
Log.Message(Now)
Log.Message(DateTimeToStr(Now))
Log.Message(DateToStr(Now))
Log.Message(TimeToStr(Now))
You can also use functions from this plug-in in any other scripting language, not only in DelphiScript.
Another scripting object - StopWatch, which is available in TestComplete 5 and later, lets you measure time intervals during the script execution:
HISUtils.StopWatch.Start();
Test(); // run the test
HISUtils.StopWatch.Stop();
Log.Message("Test execution time: " + HISUtils.StopWatch.ToString());
For more information, see “StopWatch Object” in TestComplete on-line help.
You can find many examples on working with date and time values in the “Working With Dates” and “Working With Time” sections in TestComplete’s help file.
Q.: How can I pass function parameters by reference in JScript (C++Script, C#Script)?
A: JScript does not support passing function parameters by reference. Since C++Script and CScript are based on JScript, they do not support reference parameters as well. However, there are several approaches that you can use to work around this restriction:
1. Use global variables. Global variables can be accessed and modified throughout the entire script unit:
[JScript]
var MyVar;
function Test()
{
MyVar = 10;
ChangeGlobalVariable();
Log.Message(MyVar); // 12
}
function ChangeGlobalVariable()
{
MyVar += 2;
}
[C++Script, C#Script]
var MyVar;
function Test()
{
MyVar = 10;
ChangeGlobalVariable();
Log["Message"](MyVar); // 12
}
function ChangeGlobalVariable()
{
MyVar += 2;
}
2. Use project, project suite or network suite variables. These variables are accessible and can be modified in all scripts in the current project, project suite or network suite, respectively (for more information on these variables, see the “Local Variables” topic in TestComplete help):
[JScript]
function Test()
{
Project.Variables.MyVar = 10;
ChangeProjectVariable();
Log.Message(Project.Variables.MyVar); // 12
}
function ChangeProjectVariable()
{
Project.Variables.MyVar += 2;
}
[C++Script, C#Script]
function Test()
{
Project["Variables"]["MyVar"] = 10;
ChangeProjectVariable();
Log["Message"](Project["Variables"]["MyVar"]); // 12
}
function ChangeProjectVariable()
{
Project["Variables"]["MyVar"] += 2;
}
3. Return the desired value from the function using the return operator and assign it to the variable:
[JScript]
function Test()
{
var MyVar = 10;
MyVar = GetNewValue(MyVar);
Log.Message(MyVar); // 12
}
function GetNewValue(AVar)
{
return AVar + 2;
}
[C++Script, C#Script]
function Test()
{
var MyVar = 10;
MyVar = GetNewValue(MyVar);
Log["Message"](MyVar); // 12
}
function GetNewValue(AVar)
{
return AVar + 2;
}
4. The JScript Array and Object objects are always passed by reference, so changes made to array elements and object properties remain after the execution returns to the caller function:
[JScript]
function Test()
{
var MyArray = new Array(1);
MyArray[0] = 10;
ChangeArray(MyArray);
Log.Message (MyArray[0]); // 12
var MyObj = new MyObject (20);
ChangeObject (MyObj);
Log.Message (MyObj.MyValue); // 22
}
function ChangeArray(Arr)
{
Arr[0] += 2;
}
// Creates a new MyObject object
function MyObject (AVal)
{
this.MyValue = AVal;
}
function ChangeObject (AObj)
{
AObj.MyValue += 2;
}
[C++Script, C#Script]
function Test()
{
var MyArray = new Array(1);
MyArray[0] = 10;
ChangeArray(MyArray);
Log["Message"](MyArray[0]); // 12
var MyObj = new MyObject (20);
ChangeObject (MyObj);
Log["Message"](MyObj["MyValue"]); // 22
}
function ChangeArray(Arr)
{
Arr[0] += 2;
}
// Creates a new MyObject object
function MyObject (AVal)
{
this["MyValue"] = AVal;
}
function ChangeObject (AObj)
{
AObj["MyValue"] += 2;
}
Q.: How can I close a modal dialog which was invoked by a method called from script?
A.: Generally a script instruction is not executed until the previous instruction is finished. This behavior causes unexpected pauses in test execution since routines that invoke modal dialogs return the resulting value only when the corresponding dialog is closed.
To solve this issue the Runner.CallObjectMethodAsync method was introduced in TestComplete ver. 4.2. It calls the specified object method asynchronously. That is, it does not wait for the method to complete, but proceeds to the next code instruction. Thus the further instructions can simulate the actions needed to close the dialog (typically press OK or Close button). To find out whether the previously called method had finished, the CallObjectMethodAsync returns an especial CallObjectMethodAsyncResult object. See the "Testing Modal Windows" help topic for details.
Below is an example that programmatically calls a dialog of an Open application and afterwards closes it via user interface actions.
[VBScript]
Sub Test
Dim p, w, ResObject
...
' Calls the method that displays a modal dialog
Set ResObject = Runner.CallObjectMethodAsync(p.VCLObject("MainForm").VCLObject("ADialog"), "Execute")
' Simulates user actions over the dialog and closes it
Set w = p.WaitWindow("*", " ADialogCaption ", -1, 1000)
If w.Exists Then
w.Window("Button", "*Ok*").ClickButton()
Else
Log.Message("The window was not found.")
End If
' Waits for method completion
ResObject.WaitForCompletion(5000)
' Processes the result
If ResObject.Completed Then
Log.Message("Success, Return Value: "+CStr(ResObject.ReturnValue))
Else
Log.Message("Failure, Return Value: "+CStr(ResObject.ReturnValue))
End If
...
End Sub
[JScript]
function Test()
{
var p, w, ResObject;
...
// Calls the method that displays a modal dialog
ResObject = Runner.CallObjectMethodAsync(p.VCLObject("MainForm").VCLObject("ADialog "), "Execute");
// Simulates user actions over the dialog and closes it
w = p.WaitWindow("*", " ADialogCaption", -1, 1000);
if (w.Exists)
w.Window("Button", "*Ok*").ClickButton();
else Log.Message("The window was not found.")
// Waits for method completion
ResObject.WaitForCompletion(5000);
// Processes the result
if(ResObject.Completed)
Log.Message("Success, Return Value: " + ResObject.ReturnValue);
else
Log.Message("Failure, Return Value: " + ResObject.ReturnValue);
...
}
[DelphiScript]
procedure Test();
var p, w, ResObject: OleVariant;
begin
...
// Calls the method that displays a modal dialog
ResObject := Runner.CallObjectMethodAsync(p.VCLObject('MainForm').VCLObject('ADialog'), 'Execute');
// Simulates user actions over the dialog and closes it
w := p.WaitWindow('*', 'ADialogCaption', -1, 1000);
if w.Exists then
w.Window("Button", '*Ok*').ClickButton()
else Log.Message('The window was not found.');
// Waits for method completion
ResObject.WaitForCompletion(5000);
// Processes the result
if ResObject.Completed then
Log.Message('Success, Return Value: ' + VarToStr(ResObject.ReturnValue))
else
Log.Message('Failure, Return Value: ' + VarToStr(ResObject.ReturnValue));
...
end;
[C#Script, C++Script]
function Test()
{
var p, w, ResObject;
...
// Calls the method that displays a modal dialog
ResObject = Runner["CallObjectMethodAsync"](p["VCLObject"]("MainForm")["VCLObject"]("ADialog"), "Execute");
// Simulates user actions over the dialog and closes it
w = p["WaitWindow"]("*", "ADialogCaption", -1, 1000);
if (w["Exists"])
w["Window"]("Button", "*Ok*")["ClickButton"]();
else Log["Message"]("The window was not found.")
// Waits for method completion
ResObject["WaitForCompletion"](5000);
// Processes the result
if(ResObject["Completed"])
Log["Message"]("Success, Return Value: " + ResObject["ReturnValue"]);
else
Log["Message"]("Failure, Return Value: " + ResObject["ReturnValue"]);
...
}
However, it is not recommended to use the Runner.CallObjectMethodAsync method to perform asynchronous calls to methods provided by TestComplete. Some of the built-in methods may operate normally, while the others may cause problems when they are called asynchronously. Therefore certain TestComplete methods cannot be launched via CallObjectMethodAsync. An example of such a method is the BuiltIn.SendMAPIMail routine. It often invokes a security warning dialog displayed by Outlook / Outlook Express which cannot be closed by TestComplete.
The only solution is to close the dialog from another process, namely, via the Windows Script Host. This is a language-independent utility that introduces wide capabilities for automation. The helper script we are interested in should focus the dialog window and perform the actions needed to close the dialog.
Here is the code of a script that closes the Outlook Express security dialog. The code should be placed in a text file named CloseSecurityDialog.vbs
' VBScript
WindowCaption = "Outlook Express"
Set WshShell = WScript.CreateObject("WScript.Shell")
' Focus the dialog window
a = WshShell.AppActivate(WindowCaption)
While Not a
WScript.Sleep(100)
a = WshShell.AppActivate(WindowCaption)
WEnd
WScript.Sleep(600)
' Simulate the ALT+S keystroke that is an accelerator For the "Send" button
WshShell.SendKeys("%S")
Once the helper Windows Script is created, you can call it from TestComplete right before the instruction that invokes the modal dialog. The following code snippet demonstrates how to do this:
[VBScript]
Dim WshShell
...
' Obtain the OLE object of Windows Script Host
Set WshShell = Sys.OleObject("WScript.Shell")
' Launch the script at specified path
WshShell.Run("C:\Work\CloseSecurityDialog.vbs")
' Call the routine invoking a modal dialog
If SendMAPIMail("John Smith", "jsmith@domain.com", "MessageSubject", "MessageBody") Then
Log.Message("Mail was sent")
Else
Log.Warning("Mail was not sent")
End If
...
End Sub
[JScript]
function ModalMessager()
{
var WshShell;
...
// Obtain the OLE object of Windows Script Host
WshShell = Sys.OleObject("WScript.Shell");
// Launch the script at specified path
WshShell.Run("C:\\Work\\CloseSecurityDialog.vbs");
// Call the routine invoking a modal dialog
if (SendMAPIMail("John Smith", "jsmith@domain.com", "MessageSubject", "MessageBody"))
Log.Message("Mail was sent");
else
Log.Warning("Mail was not sent");
...
}
[DelphiScript]
function ModalMessager();
var WshShell : OleVariant;
begin
...
// Obtain the OLE object of Windows Script Host
WshShell := Sys.OleObject('WScript.Shell');
// Launch the script at specified path
WshShell.Run('C:\Work\CloseSecurityDialog.vbs');
// Call the routine invoking a modal dialog
if (SendMAPIMail('John Smith', 'jsmith@domain.com', 'MessageSubject', 'MessageBody')) then
Log.Message('Mail was sent.')
else
Log.Warning('Mail was not sent.');
...
end;
[C#Script, C++Script]
function ModalMessager()
{
var WshShell;
...
// Obtain the OLE object of Windows Script Host
WshShell = Sys["OleObject"]("WScript.Shell");
// Launch the script at specified path
WshShell["Run"]("C:\\Work\\CloseSecurityDialog.vbs");
// Call the routine invoking a modal dialog
if (SendMAPIMail("John Smith", "jsmith@domain.com", "MessageSubject", "MessageBody"))
Log["Message"]("Mail was sent");
else
Log["Warning"]("Mail was not sent");
...
}
