Forum Discussion

matthew_morgan's avatar
matthew_morgan
Occasional Contributor
11 years ago

Unnamed WPF Objects

I have an application written using WPF that has a truely astonishing number of controls without native names (several hundred).  I read through the article pertaining to this subject (http://support.smartbear.com/viewarticle/55176/) which indicated that an alternative name could be used that netclass, caption and index values to create a pseudo name.



I was wondering how problematic that option is.  If a new control is added near the beginning, does it throw off all of the subsequent controls from the name mapping?  Would you recommend having the development group produce names for all (or at least most) of these controls?



Just looking for direction before I go too far down the rabbit hole.
  • Hey so I just posted something similar on other threat.

    I'll repeat, but I think it's useful for you:



    I'm currently testing a WPF based application.



    I use a combination of both methods: whenever I can use 'NameMapping' I do, whenever I can't I use a function I made which receives the parent object (usually mapped name), property/ies and value/s and it handles zero, one and many objects found.



    So I use as much as I can the NameMappings. And whenever there's no useful property/value pairs to identify an object I grab the application code, name the objects myself and next build I'm ready to go.



    Cheers,

    Leandro
  • leandropoblet's avatar
    leandropoblet
    Frequent Contributor
    Hey so I just posted something similar on other threat.

    I'll repeat, but I think it's useful for you:



    I'm currently testing a WPF based application.



    I use a combination of both methods: whenever I can use 'NameMapping' I do, whenever I can't I use a function I made which receives the parent object (usually mapped name), property/ies and value/s and it handles zero, one and many objects found.



    So I use as much as I can the NameMappings. And whenever there's no useful property/value pairs to identify an object I grab the application code, name the objects myself and next build I'm ready to go.



    Cheers,

    Leandro
  • matthew_morgan's avatar
    matthew_morgan
    Occasional Contributor
    @Ryan, I use the .Find method on the name field specifically to avoid the issue you are referring to.  In this case, there aren't any other fields for me to use in the find.  The only difference between the various fields is the  database data element they link to; which doesn't seem to be a property I can search on (granted I haven't seen the XAML and it may be that it is a public property, and I just couldn't find it).



    @Leandro, That is the answer I feared, though it isn't unexpected. As I said above, almost none of these fields contain useful property/value pairs.  So it looks like I'm stuck (although I can at least use the generated name until they get around to giving proper names to everything).
  • Ryan_Moran's avatar
    Ryan_Moran
    Valued Contributor
    Well everyone does things differently, but if you would like to take Leandro's approach and try to backwards engineer some other naming convention by all means try it out and let us all know how it works for you in about a year from now. If you spend less time maintaining your scripts and more time developing new ones then i'll consider it a great success! I can only speak from personal experience that find and findall methods to dynamically recognize objects has been very successful for us.
  • matthew_morgan's avatar
    matthew_morgan
    Occasional Contributor
    You may be misinturpretting my last post (and I definitely see why).  I wanted to clarify: I am using the find methods. I keep an xml file (not related to TestComplete's mapping system) that contains a list of all of my applications/screens/controls within every application I test.  For example: humans call it the "Last Name" field on the "User Information" screen in the "Orders" application and developers* call it the "txtLName" textbox on the "tpUser" tabPage in the "tbUserData" tabControl on the "pnlData" panel (etc. etc. ad nausium until you get to Sys.Process("Orders"))).  My mapping is at the process or application level (e.g. Orders = Sys.Orders), then at the screen level (e.g. User Information = tpUser), then at the control level (e.g. Last Name = txtLName).  My find method then looks for 'tpUser' in 'Sys.Orders' then looks for 'txtLName' within the resulting UI object.  It works well when there are actually names provided (which isn't the case for this one application that I am concerned about).  Granted, it also means every interactive element in my application needs that mapping, but that doesn't require a developer to maintain.



    * I have been informed that most developers are human
  • Ryan_Moran's avatar
    Ryan_Moran
    Valued Contributor
    Interesting...

    I do work with a similar wpf application but I haven't experienced the issue you describe here. What do the full names of the controls look like? Can you paste a few examples?



  • matthew_morgan's avatar
    matthew_morgan
    Occasional Contributor
    On one screen I have the following controls:

     - WPFObject("TextBox", "", 1)

     - WPFObject("TextBox", "", 2)

     - WPFObject("DropDownListBox", "", 3)

     - WPFObject("DropDownListBox", "", 3) (there are two of these, within seperate sub-screens, which becomes organizationally difficult though not impossible to handle using the approach I outlined above)

     - WPFObject("DateTimeControl", "", 2)



    As far as I can tell there is no difference within the properties that I can access between these fields except their position on the screen.  I (as a human) know what they are only because the label next to them tells me.  I could possibly set up a system where I check the label next to control I want (e.g. WPFObject("DateTimeControl", "", 1)) which would tell the automation as well, but since the indexes get off when a different control is used (e.g. WPFObject("Label", "DOB", 7) is the label next to the DateTimeControl #1) I would need to step through all of the labels looking for the one with a similar 'Top' and similar 'Left + Width' to the control I am trying to manipulate...ugh.  On the screen I pulled up (one of many) there are 124 of these unnamed controls.
  • Ryan_Moran's avatar
    Ryan_Moran
    Valued Contributor
    I wrote this custom function for you based on the information you provided. See if it helps you at all:






    function main(){


    wshell = new ActiveXObject('WScript.Shell');


    /*


     how you can call the search function when your control has a name


    */


    var myFoundControl = findWPFControl("DBUtilities","DBUtilities","Button*Archive Jobs","2");


    /*


     returns the wpf control with really long name:


     Sys.Process("DBUtilities").WPFObject("HwndSource: MainWindow", "DBUtilities 10.00.1622  |  PROTO")


     .WPFObject("MainWindow", "DBUtilities 10.00.1622  |  PROTO", 1).WPFObject("Grid", "", 1)


     .WPFObject("busyIndicator").WPFObject("Grid", "", 1).WPFObject("borderTasks").WPFObject("Grid", "", 1)


     .WPFObject("StackPanel", "", 1).WPFObject("GroupBox", "Archival Options", 2)


     .WPFObject("StackPanel", "", 1).WPFObject("Button", "Archive Jobs", 2)


    */


    if (myFoundControl){


     wshell.popup(myFoundControl.FullName);


     }


     


    /*


     how you can call the search function when your control DOES NOT have a name


     note the difference in the controlType parameter and how I pass the parent object name to narrow my search


     additional "keywords" can be specified or the findall parameters within the findWPFControl function may be modified similarly


    */


    var myFoundControl = findWPFControl("DBUtilities","DBUtilities","GroupBox*, 2*Button","2");


    /*


     returns the wpf control with really long name:


     Sys.Process("DBUtilities").WPFObject("HwndSource: MainWindow", "DBUtilities 10.00.1622  |  PROTO")


     .WPFObject("MainWindow", "DBUtilities 10.00.1622  |  PROTO", 1).WPFObject("Grid", "", 1)


     .WPFObject("busyIndicator").WPFObject("Grid", "", 1).WPFObject("borderTasks").WPFObject("Grid", "", 1)


     .WPFObject("StackPanel", "", 1).WPFObject("GroupBox", "Archival Options", 2)


     .WPFObject("StackPanel", "", 1).WPFObject("Button", "", 2)


    */


    if (myFoundControl){


     wshell.popup(myFoundControl.FullName);


     }


    }


     


     


    function findWPFControl(processName,formName,controlType,controlIndex,timeOut){


    timeOut = !timeOut ? 0:timeOut;


    var myControl = undefined;


    var myObjectArray = [];


    /*


     returns forms WPF control specified by type and index


     check if process exists - return undefined if it does not


    */


    if (!Sys.WaitProcess(processName,0).Exists){


     return undefined;


     }


    /*


     check if form exists - return undefined if it does not


    */


    if (!Sys.Process(processName).WaitWPFObject("*","*" + formName + "*",0).Exists){


     return undefined;


     }


    myObjectArray = VBArray(Sys.Process(processName).WPFObject("*","*" + formName + "*").FindAll(["FullName"],["*" + controlType + "*, " + controlIndex],500,true)).toArray();


    for (var object = 0;object < myObjectArray.length;object++){


     /*


      you can iterate the object array and check additional properties here


     */


     if (myObjectArray[object].Exists){


      return myObjectArray[object];


      }


     }


    /*


     if timeOut is reached (roughly 30 seconds + search time


     then return undefined


    */


    if (timeOut > 30){


     return undefined;


     }


    timeOut++;


    /*


     if we didn't find the object (possibly because the form is loading)


     then make a call back to the findWPFControl function


    */


    BuiltIn.Delay(1000,"Searching for control...")


    return findWPFControl(processName,formName,controlType,controlIndex,timeOut);


    }