TestComplete ( JScript ): Making windows definitions using wrapper classes
@ Fr, 23 January 2009, 19:14GUI level automated testing has a lot of surprizes related to interaction with window objects. The main problem is that each automation tool has its own set of the most effective solutions to the problem of window definitions maintainability. But these are the most effective solutions for each particular case ( not in general ). Nevertheless, there are some mechanisms which are common to a wide range of tools. Or at least there are analogs. For example there is such approach as windows definitions mapping which provides the ability to set some alias into correspondence to actual window definition. Such solution implementation in TestComplete has one key disadvantage: in case of windows hierarchy modifications for some particular object there is necessity to re-map all child objects. The alternative to mapping was introduced in TestComplete version 6. There was an Alias functionality providing the ability to construct mapped objects hierarchy. But this solution has another disadvantage: slow performance. So, we need some mechanisms allowing to minimize time costs for window definitions modification in case of UI changes. Let's examine this problem on some particular case.
We have some dialog window which can be accessed as:
Sys.Process( PRODUCT_NAME ).Window("TForm", "New Text Style", 1);
Here PRODUCT_NAME is some constant specifying process name. Let's think that the dialog has some text field Name which can be accessed by the following code:
Sys.Process( PRODUCT_NAME ).Window("TForm", "New Text Style", 1).Window( "TEdit" , "*" , 1 );
as well as there are 2 buttons: OK, Cancel which have difiniions correspondingly:
Sys.Process( PRODUCT_NAME ).Window("TForm", "New Text Style", 1).Window( "TButton", "*", 1 )
Sys.Process( PRODUCT_NAME ).Window("TForm", "New Text Style", 1).Window( "TButton", "*", 2 )
As it's seen from example, objects are defined in incompact way. Even more, the definitions like that are quite sensitive to UI changes because they use indices. Very often there's a situation when object names are different on different environments. The simpliest example: system message dialogs for different operating system locatizations have different button names defined on local language. In case of application under tests uses english only then there's no problem. But in case autotests are created for the some language-localized application, there's necessity to avoid windows definitions using object text. Actually this thing was done for the definitions above. Well, let's try to examine typical test scenario involving these objects. For example, we have to wait for the dialog for 5 seconds. If it appears, we enter the text (any combination of alpha-numeric characters) into text field and press OK button. If we use required object definitions as they are, the code implementing described scenario is:
if( Sys.Process( PRODUCT_NAME ).WaitWindow("TForm", "New Text Style", 1 , 5000 ).Exists ) {
Log.Error( "No Text Style dialog available" );
}
else {
Sys.Process( PRODUCT_NAME ).Window("TForm", "New Text Style", 1).Window( "TEdit" , "*" , 1 ).wText = "Some Text";
Sys.Process( PRODUCT_NAME ).Window("TForm", "New Text Style", 1).Window( "TButton", "*", 1 ).Click();
}
The most obvious thing here is that the code is quite huge. Code constructions are too long. Also we can imagine what may happen is some dialog window attributes are changed, for example, the header text. As the result, this dialog will require a little bit different definition causing necessity to modify dialog object definition as we as its child objects. Even more, each occurence of modified objects should be updated. In addition to this, there may be a lot of constructions affected by these UI changes and they may be spread through overall all scripts. In this case it takes too much time to apply changes everywhere. Sometimes it's not acceptable. So, we have to do something in order to minimize amount of required modifications. One of the solutions is to provide some interface for window objects access encapsulating explicit window objects definitions. It means that we may create some class containing methods designed to provide the access to all required elements. Well, let's create wrapper class and provide access ability to the dialog window. It can be implemented in this way:
function NewTextStyleDlg()
{
this.Get = function ()
{
return Sys.Process( PRODUCT_NAME ).Window("TForm", "New Text Style", 1);
}
}
As the result, we just have initially create the instance of the NewTextStyleDiagog class and then call Get method in order to get access to required dialog window. We may apply this feature to our sample code and correct it in the following way:
var dNewTextStyle = new NewTextStyleDlg();
if( Sys.Process( PRODUCT_NAME ).WaitWindow("TForm", "New Text Style", 1 , 5000 ).Exists ) {
Log.Error( "No Text Style dialog available" );
}
else {
dNewTextStyle.Get().Window( "TEdit" , "*" , 1 ).wText = "Some Text";
dNewTextStyle.Get().Window( "TButton", "*", 1 ).Click();
}
Great! We've optimized the code. Also this part of the code require less modifications in case of dialog window properties changes..
What's next? We have instruction checking object availability which still applies to explicitly defined window properties. So, we may update our wrapper class with the method which simply check whether dialog object is available. Our class may be updated in the following way:
function NewTextStyleDlg()
{
this.Get = function ()
{
return Sys.Process( PRODUCT_NAME ).Window("TForm", "New Text Style", 1);
}
this.Exists = function ( iWT )
{
if( iWT == undefined )
{
iWT = 5000;
}
Sys.Refresh();
return Sys.Process( PRODUCT_NAME ).WaitWindow("TForm", "New Text Style", 1,iWT).Exists;
}
}
As the result of changes made the test code can be updated in the following way:
var dNewTextStyle = new NewTextStyleDlg();
if( dNewTextStyle.Exists() ) {
Log.Error( "No Text Style dialog available" );
}
else {
dNewTextStyle.Get().Window( "TEdit" , "*" , 1 ).wText = "Some Text";
dNewTextStyle.Get().Window( "TButton", "*", 1 ).Click();
}
The only thing left is to wrap text fields and buttons into corresponding methods. There is a variaty of possible ways to do this but we stop on methods creation. We'll create methods returning references to each particular object. We need methods to access Name text field as well as to OK and Cancel buttons. It can be implemented like:
function NewTextStyleDlg()
{
this.Get = function ()
{
return Sys.Process( PRODUCT_NAME ).Window("TForm", "New Text Style", 1);
}
this.Exists = function ( iWT )
{
if( iWT == undefined )
{
iWT = 5000;
}
Sys.Refresh();
return Sys.Process( PRODUCT_NAME ).WaitWindow("TForm", "New Text Style", 1,iWT).Exists;
}
this.edtName = function () {
return this.Get().Window( "TEdit" , "*" , 1 );
}
this.btnOK = function () {
return this.Get().Window( "TButton", "*", 1 );
}
this.btnCancel = function () {
return this.Get().Window( "TButton", "*", 2 );
}
}
var dNewTextStyle = new NewTextStyleDlg();
if( dNewTextStyle.Exists() ) {
Log.Error( "No Text Style dialog available" );
}
else {
dNewTextStyle.edtName().wText = "Some Text";
dNewTextStyle.btnOK().Click();
}
Here it is. At this point our test code accesses to window objects only via wrapped interfaces instead of explicit difinitions. So, in case of any UI changes in this dialog ( taking into account that text field and buttons are still available but hierarchy is changed ) the only thing required to maintain test code is to modify wrapper class methods for corresponding objects. And these are local changes optimizing maintenance costs.
