Implementing Callbacks in Script Extensions

Applies to TestComplete 14.70, last modified on April 22, 2021

Callbacks are calls to user-defined script routines from script extensions. The custom script routines or callback routines are implemented by the user in their TestComplete project and are passed as arguments to script extensions. This way, callbacks provide a way for script extensions to execute user-defined code, and allow users to create custom routines to be run by script extensions.

Callbacks are useful if a script extension is supposed to repeatedly perform an arbitrary task. For example, enumerate items in a set and process them one-by-one. In this case, the script extension could implement the generic item iterator functionality and, instead of performing a pre-defined operation on each item, apply the callback routine to it.

The rest of this topic explains how to implement callbacks in script extensions.

Callbacks work only at test run time, so they can only be used in custom scripting objects and keyword test operations. Custom actions cannot use callbacks.
Implementing Callbacks
Calling callback routines from script extensions

To call a routine defined in a user’s TestComplete project from a script extension, use the Runner.CallMethod method:

JScript

Runner.CallMethod("UnitName.RoutineName", Param1, Param2 .. ParamN);

VBScript

Runner.CallMethod "UnitName.RoutineName", Param1, Param2 .. ParamN
' -- or --
Call Runner.CallMethod("UnitName.RoutineName", Param1, Param2 .. ParamN)

The callback routine must be specified in the UnitName.RoutineName format, where UnitName is the name of the user’s script unit where the routine is defined, and RoutineName is the routine name. Both parts are required. If the routine uses parameters, they must be listed after the routine name. Usually, callback routines have one or more parameters that allow you to pass data, such as information about the currently processed item, from the script extension into the callback routine. For example, if a script extensions provide the file enumerator functionality, the callback routine can use one parameter holding the current file name.

If the callback routine is a function, that is, if it returns a value, the Runner.CallMethod method returns this value as well. To obtain this value, you can call the Runner.CallMethod method as a function:

JScript

var res = Runner.CallMethod("UnitName.RoutineName", Param1, Param2 .. ParamN);

VBScript

Dim res

res = Runner.CallMethod("UnitName.RoutineName", Param1, Param2 .. ParamN)

The script extension can use this returned value as your needs dictate. For example, when implementing an iterator, you may use a non-zero return value from the callback function as a condition to stop the iterator.

Implementing callback routines in TestComplete projects

Callback routines are usually script routines and can be created in the same way as any other routines. However, this routine must be implemented to receive the same number of arguments as the script extension demands. For example, if the script extension implements the file enumerator functionality, the callback routine can use one parameter that specifies the path to the file to be processed. For example:

JavaScript, JScript

function ProcessItem(Item)
{
  Log.Message(Item);
}

Python

def ProcessItem(Item):
  Log.Message(Item)

VBScript

Sub ProcessItem(Item)
  Log.Message(Item)
End Sub

DelphiScript

procedure ProcessItem(Item);
begin
  Log.Message(Item);
end;

C++Script, C#Script

function ProcessItem(Item)
{
  Log["Message"](Item);
}

Also, the callback routine can return a value, if it is required by the script extension.

Passing callback routines to script extensions

To specify a custom routine to be executed by a script extension, the user must pass the routine name and the name of the script unit containing that routine to the script extension. There are several ways this information can be passed to a custom object. For example, an object method can take the routine name as an argument, or an object can be implemented to have a special property for storing the routine name:

CustomObject.DoSmth("Unit1.MyRoutine");

-- or --

CustomObject.RoutineName = "Unit1.MyRoutine";

CustomObject.DoSmth();

Also, the unit name and the routine name may be passed as a single argument in the UnitName.RoutineName format, or as separate arguments, whatever you find more convenient. In any case, the script extension should then convert this information to the UnitName.RoutineName format in order to be used in the Runner.CallMethod method.

Example

Let’s illustrate the callback implementation and usage with an example. Suppose you are creating a script extension that would allow the users to apply custom processing to all files in a folder. For example, print a file list, set or clear a specific attribute on each file, modify each file in a specific way, and so on. All of these tasks are similar and can be implemented by iterating through the files in a folder and working with each file. This is what the extension will do: enumerate files in the specified folder (and, optionally, its subfolders) and call a user-defined routine on each file. Implementing the file processing routine will be left to the users.

In our example, the script extension will provide a custom Folder object containing the Process method. The method will take three arguments:

  • The path to the folder to be processed.
  • The name of the script routine to execute on each file, in the UnitName.RoutineName format. This routine must use one parameter that holds the file path.
  • A boolean value that specifies whether the folder should be processed recursively (True) or not (False).

Below you will find the source code of a custom runtime object and the description.xml file:

JScript

var RoutineName;
var Recursive;

// This function is called by the Process method of the Folder object
function Folder_Process(AFolderPath, ARoutineName, ARecursive)
{
  // Initialize the iterator settings
  RoutineName = ARoutineName;
  Recursive = ARecursive;

  // Process the files
  DoProcessFiles(aqFileSystem.GetFolderInfo(AFolderPath));
}


function DoProcessFiles(AFolder)
{
  // Apply the user's routine to each file in the AFolder
  var files = AFolder.Files;
  while (files.HasNext())
    Runner.CallMethod(RoutineName, files.Next().Path);

  // Process files in the AFolder's subfolders
  if (Recursive)
  {
    var subfolders = AFolder.SubFolders;
    while (subfolders.HasNext())
      DoProcessFiles(subfolders.Next());
  }
}

VBScript

Dim RoutineName
Dim Recursive

' This procedure is called by the Process method of the Folder object
Sub Folder_Process(AFolderPath, ARoutineName, ARecursive)
  ' Initialize the iterator settings
  RoutineName = ARoutineName
  Recursive = ARecursive

  ' Process the files
  DoProcessFiles(aqFileSystem.GetFolderInfo(AFolderPath))
End Sub


Sub DoProcessFiles(AFolder)
  Dim files, subfolders

  ' Apply the user's routine to each file in the AFolder
  Set files = AFolder.Files
  While files.HasNext
    Call Runner.CallMethod(RoutineName, files.Next.Path)
  Wend

  ' Process files in the AFolder's subfolders
  If Recursive Then
    Set subfolders = AFolder.SubFolders
    While subfolders.HasNext
      DoProcessFiles(subfolders.Next)
    Wend
  End If
End Sub

description.xml

<ScriptExtensionGroup Name="Runtime Objects">
    <ScriptExtension Name="Folder Object" Author="SmartBear Software" Version="1.0">
        <Script Name="">  <!-- Specify the script file name here-->
            <RuntimeObject Name="Folder">
                <Method Name="Process" Routine="Folder_Process">Applies the specified routine to each file in a folder.</Method>
                <Description>Provides a unified way of processing files in a folder.</Description>
            </RuntimeObject>
        </Script>
    </ScriptExtension>
</ScriptExtensionGroup>

You can copy this code, paste it to any text editor (such as Notepad) and save to the appropriate files. To add the extension to TestComplete, you can either copy the folder containing the extension’s files to the <TestComplete>\Bin\Extensions\ScriptExtensions folder or to any other folder specified in the Script Extensions Folders, or package the extension and install this package in TestComplete. See Deploying Script Extensions and Installing and Uninstalling Script Extensions.

Now let’s see how users can use this extension to process a folder’s contents using their custom routines. The following is a sample TestComplete script that uses the Folder.Process method provided by the extension to process files in the C:\Test folder. The script processes the files using two different routines:

  • LogFilePath -- logs the path to the specified file.
  • MakeReadOnly -- sets the read-only attribute on a file.

Both routines use a single parameter that specifies the fully-qualified path to the file to operate on. The parameter value is initialized by the script extension whenever it calls the appropriate callback routine.

JavaScript, JScript

// Unit1

function Main()
{
  // Log the list of files in the C:\Test folder and its subfolders
  Folder.Process("C:\\Test", "Unit1.LogFilePath", true);


  // Make files in the C:\Test folder read-only
  Folder.Process("C:\\Test", "Unit1.MakeReadOnly", false);
}

function LogFilePath(FileName)
{
  Log.Message(FileName);
}

function MakeReadOnly(FileName)
{
  aqFile.SetFileAttributes(FileName, 1);
}

Python

# Unit1

def Main():
  # Log the list of files in the C:\Test folder and its subfolders
  Folder.Process("C:\\Test", "Unit1.LogFilePath", True)


  # Make files in the C:\Test folder read-only
  Folder.Process("C:\\Test", "Unit1.MakeReadOnly", False)

def LogFilePath(FileName):
  Log.Message(FileName)

def MakeReadOnly(FileName):
  aqFile.SetFileAttributes(FileName, 1)

VBScript

' Unit1

Sub Main
  ' Log the list of files in the C:\Test folder and its subfolders
  Call Folder.Process("C:\Test", "Unit1.LogFilePath", True)

  ' Make files in the C:\Test folder read-only
  Call Folder.Process("C:\Test", "Unit1.MakeReadOnly", False)
End Sub

Sub LogFilePath(FileName)
  Log.Message(FileName)
End Sub

Sub MakeReadOnly(FileName)
  Call aqFile.SetFileAttributes(FileName, 1)
End Sub

DelphiScript

// Unit1

procedure Main;
begin
  // Log the list of files in the C:\Test folder and its subfolders  Folder.Process('C:\Test', 'Unit1.LogFilePath', true);


  // Make files in the C:\Test folder read-only
  Folder.Process('C:\Test', 'Unit1.MakeReadOnly', false);
end;

procedure LogFilePath (FileName);
begin
  Log.Message(FileName);
end;

procedure MakeReadOnly(FileName);
begin
  aqFile.SetFileAttributes(FileName, 1);
end;

C++Script, C#Script

// Unit1

function Main()
{
  // Log the list of files in the C:\Test folder and its subfolders
  Folder["Process"]("C:\\Test", "Unit1.LogFilePath", true);


  // Make files in the C:\Test folder read-only
  Folder["Process"]("C:\\Test", "Unit1.MakeReadOnly", false);
}

function LogFilePath(FileName)
{
  Log["Message"](FileName);
}

function MakeReadOnly(FileName)
{
  aqFile["SetFileAttributes"](FileName, 1);
}

See Also

Creating Script Extensions
CallMethod Method

Highlight search results