Forum Discussion

torus's avatar
torus
Contributor
10 days ago

Perform Action @ position relative to object

Is there a way to perform an action at a position which is relative to the object AND not within the bounds object (perform an action on a secondary object which is located x/y distance away from the initial object)? It looks as if the ClientX and ClientY give error messages if the x-y position is outside of the selected object's bounds. 

Example: I have mapped the 'Enter Loan Amount' in the name mapping. It is identified by it's text. 
I want to locate the 'enter loan amount' text box (the one with 10000 in it) with a testComplete feature which would be equivalent to "Perform an Action at a location that is 50 pixels to the right of the 'Enter Loan Amount' label object". 
Why? Because the order of these rows moves around a lot (as developers design) and I always want to be able to just find the textbox by locating it relative to it's corresponding text located to it's left.

 

  • JDR2500's avatar
    JDR2500
    Frequent Contributor

    You can certainly do what you're describing using a script.  For instance, you can get the top left screen coordinate of the label and the width of the label.  Adding those values together will give you the right side of the label in screen coordinates.  Add 50 to that and you should have the screen coordinate value you want.  Then you can click there (e.g. Sys.Desktop.Click <Add your coords here>).

    However, I can't endorse using that approach.  You should be able to find that edit box reliably by creating a name mapping using the right object mapping properties.

    What properties do you see for it in the Object Spy?  There has to be something there that will work.  Caption?  ObjectIdentifier?  Name?  ID?

    • torus's avatar
      torus
      Contributor

      Thanks JDR2500 for the help. I was hoping there was some functionality in the keyword testing that could do this. The person who was asking prefers not to script if they can do without and it seemed like a common enough functionality need (for filling out forms/etc) that I thought the functionality would have made its way into the keyword arena. 

      In the nameMapping, it is easy enough to use the parent's properties as an identifier (see image below) ... but wasn't sure how to reference an object by one of it's sibling's properties. I'll play around with the name mapping a bit more. 

      If anyone knows if there is a way to do what I am trying to do via keyword functionality or name mapping properties, please let me know. Thanks in advance. 

       

      • JDR2500's avatar
        JDR2500
        Frequent Contributor

        WPF, ugh.  So many levels of hierarchy.  Nevertheless, I think you should be able to accomplish what you want using well-constructed name mappings of all the levels.  For each item in the level you'll need unique identifiers for the ID properties.  That may require more than one.  For example you might be able to use WPFControlName in combination with Uid. WPFControlOrdinalNo is another option.  That one is an index.  Consequently, there is risk it could change if your developers insert another control that increments the index number. 
        Here is an example of a hierarchy and ID properties I've used:

        There are some other tricks that can help with WPF.  I didn't know about all of them when I got started and wasn't able to take advantage of the Simplified WPF object tree.  That hides some of the WPF levels to make the name mappings more user friendly.
        Project Properties - WPF Options | TestComplete Documentation

        It's worth reading as much as you can about using WPF with TestComplete.  

        With the right name mapping setup you should have no problem reliably targeting any WPF control in your application.  It's a pain to create and you should not rely on the mappings automatically created during keyword testing.  Go back to each one in the hierarchy.  Use the Object Spy to identify good static unique properties and add them to the mapping.  You can also use the Extended Find feature.

        Once you get the hang of name mappings everything else will fall into place.  Unfortunately, it seems your first experience will be with WPF controls.  They are trickier in my experience due to the ridiculous depth of the hierarchy. 

  • rraghvani's avatar
    rraghvani
    Champion Level 3

    Imagine the Object Browser shows the following hierarchy structure, based on the form

    If the the text and the textbox is within the same container, then it's possible to get the parents' child. For example, I want to get the label object, which doesn't have a unique id. Therefore, I'm going to use the textbox (since it has a unique id), to get the parent. I'll use the parent to get the child object, label.

     var firstname = obj.Frame("iframeResult").Form(0).Panel(0).Textbox("fname");
    
     // Textbox("fname") parent is obj.Frame("iframeResult").Form(0).Panel(0)
     parent = obj.Frame("iframeResult").Form(0).Panel(0).Textbox("fname").Parent();    
    
     var label = parent.Label(0); // Same as obj.Frame("iframeResult").Form(0).Panel(0).Label(0)

    You might be able to do something similar.

    In this example, I have grouped the text and textbox into a grid and have provided an AutomationId called "LoanAmount". You can easily get the required object, if your developers implement the controls for automation.

     

  • scot1967's avatar
    scot1967
    Regular Contributor

    "Why? Because the order of these rows moves around a lot (as developers design) and I always want to be able to just find the textbox by locating it relative to it's corresponding text located to it's left."

    If dev does not name the objects effectively to make the application 'automation friendly / testable' it makes this task much harder. 

    If you can find and map or script an instance of the label object  or textbox object, the task will be pretty easy.  At this level you are independent of the screen position and just locating the object.   

    If you still want to perform actions relative to the position of the label, you just need to do a little math based on the position of the found label object. 

    The object properties should hold the position of the found object...

    Also as described by others you can use the parent or child property of the found object.  If both exist in a single stack panel you should be able to move up a level or two and then search down from that parent to a target child which should be unique within that part of the app tree.  Again with this approach the onscreen position of the object is irrelevant as you are working with an instance of the object using it's properties. 

    Otherwise... would OCR work for a desktop app in this situation?

    https://support.smartbear.com/testcomplete/docs/testing-with/object-identification/ocr/index.html#about

    Snip from the doc article

    If there are several text fragments that match the text, specify how the operation will choose the needed text fragment:

    • Nearest to center
    • Left most
    • Right most
    • Top most
    • Bottom most
    • Largest
    • Smallest
    • None - The operation will simulate a user action on the first fragment it finds when searching from top to bottom and from left to right, and will then post a warning to the test log.
    3. Specify Method

    On the Specify Method page, select the user action you want to simulate:

    • You can simulate common actions TestComplete provides for all on-screen objects. You can find their description in the TestComplete Help.
    • To simulate a user action next to the selected text fragment, select one of the following methods:
  • torus's avatar
    torus
    Contributor

    I thought 'On-Screen Action' would be the way to go. But like said, seems like this only works on the selected object. Not on an object relative to the selected object. 

  • torus's avatar
    torus
    Contributor

    Thank you everyone for all the help. After reading what everyone had to say, I could see there was no way to do this via keyword features so I scripted it out. Thanks again for all the help figuring this out. It would be nice to be able to just find an object relative to another object via a keyword feature; oh well. I tried the OCR Actions and it too seems to only work within the object that you extracted the text from (yes, poor naming or no naming makes things A Lot harder).

     

    function testing(rowLabel)
    {
      // --- 1. Declaration section
      //var rowLabel = "Name"  
      var table = Aliases.Viewport.ViewportWindow1.ViewportWindow2.SchematicWindow.zshowDataGrid;
      
      // --- 2. Search the table for the row contianing the text, 'rowLabel' in the Name column
      var propertyNames = new Array("ClrClassName", "Text");
      var propertyValues = new Array("TextBlock", rowLabel);  
      var labelTextBlock = table.FindAllChildren(propertyNames, propertyValues, 6);  
      
      // --- 3. Get the parent row for this item
      var rowContainingLabel = labelTextBlock[0].Parent.Parent.Parent.Parent;
      
      // --- 4. Get Cell in column 2 found in this row
      var propertyNames = new Array("ClrClassName", "WPFControlOrdinalNo");
      var propertyValues = new Array("DataGridCell", "2");    
      var cellInColumnTwo = rowContainingLabel.FindAllChildren(propertyNames, propertyValues, 3);
      
      // --- 5. Get the textblock in this cell  
      var propertyNames = new Array("ClrClassName", "WPFControlName");
      var propertyValues = new Array("TextBlock", "_textBlockValue");    
      var textBlockCorrespondingToLabel = cellInColumnTwo[0].FindAllChildren(propertyNames, propertyValues, 7);
      
      // --- 6. Finally get the text (or set the text if needed) for the field
      var value = textBlockCorrespondingToLabel[0].WPFControlText;
      
      // --- 7. Return retrieved value if needed
      return value;
    }

     

    • rraghvani's avatar
      rraghvani
      Champion Level 3

      If your developers are still making changes to this control, then you should ideally push for the changes to be made, to either group the controls or provide an automation id. It will take less than a minute for the developers to implement this!