Dynamic objects in squish (python)

b140e522-60b2-46da-8dd6-b73b41fe4481

Squish is a great automation tool to write test cases for Qt applications. By default it offers to find objects by symbolic name, which basically is the name of the object generated depending on it’s properties.

The problem is that with more complicated Qt applications there might be more complicated objects – dynamic objects. In simple terms –  you record a set of actions and squish generates a code for you, where each object is named using symbolic names. When you try to run just recorded test case it fails, because squish couldn’t find the object with the same properties. The actual object in software has changed it’s properties, but object under symbolic name that’s used in a test case has the old properties.

Another reason why you would want to use the following code is that a lot of times in development process object’s properties are changed. Once the new build comes out test automation developer has to update test scripts. It may not be that complicated if there’s only couple of them, but it becomes very annoying if you have more than 50 to edit.

Froglogic offers a couple of solutions, but in my case they didn’t work. So I had to come up with my solution. Using below code it’s possible to return a list of all visible object names.

def getAllChildren(obj_name, count):
    sym_list = [];
    p = findObject(obj_name);
    p_children = object.children(p);
    for child in xrange(0, len(p_children)):
        sym_obj_name = str(objectMap.symbolicName(p_children[child]))
        sym_list.append(sym_obj_name)
        print sym_obj_name
    layer = getChildrens(0, sym_list);
    sym_list.extend(layer);
    nlayer_len = [];
    for k in range(0,count):
        sym_list_length = len(sym_list)
        if k == 0:
            layer_length = len(layer)
            start_index = sym_list_length - layer_length + 1
        else:
            layer_length = nlayer_len[k-1]
            start_index = sym_list_length - layer_length + 1
        next_layer = getChildrens(start_index, sym_list);
        sym_list.extend(next_layer);
        if len(next_layer) != 0:
            nlayer_len.append(len(next_layer))
        else:
            print 'Can\'t find any children, finished!'
            return sym_list
    return sym_list
     

The above code includes getChildrens function which was designed to simplify repetitive processes.

def getChildrens(start_index, sym_list):
    sym_list2 = [];
    end_index = len(sym_list)
    for name in xrange(start_index, end_index):
        obj = findObject(sym_list[name]);
        obj_children = object.children(obj)
        if len(obj_children) != 0:
            for child in xrange(0, len(obj_children)):
                sym_obj_name = str(objectMap.symbolicName(obj_children[child]))
                sym_list2.append(sym_obj_name)
    return sym_list2
     

getChildrens function basically takes objects from list and returns their children. For more detailed explanation of the functions see below.


Explanation:

def getAllChildren(obj_name, count):
    sym_list = [];
     

obj_name – Symbolic or real name of the parent object which contains children objects. count – the number of layers to go through (for loop range). With sym_list = [] an empty list is created. All children objects names will be appended to this list.

    p = findObject(obj_name);
    p_children = object.children(p);
     

Here we find the parent object by symbolic name and return a tuple list of child objects.

    for child in xrange(0, len(p_children)):
        sym_obj_name = str(objectMap.symbolicName(p_children[child]))
        sym_list.append(sym_obj_name)
        print sym_obj_name
     

Now we’re looping through list of parent’s children. Because object.children(p) returns list of objects we have to use xrange function. len(p_children) returns the length of p_children list. Looping in range from 0 to length of p_children list.

objectMap.symbolicName(p_children[child]) function takes child object from p_children list and returns symbolic name. If the name is not mapped in objects.map it returns the real name of the object. This is assigned to sym_obj_name variable where with str(…) we’re converting symbolic name to string. After conversion we’re appending this string to previously created sym_list.

    layer = getChildrens(0, sym_list);
    sym_list.extend(layer);
     

getChildrens function goes through each object in sym_list and returns a list of their child objects. This list is assigned to layer and later on the sym_list is extended. New child objects are added to main list – sym_list.

    nlayer_len = [];
    for k in range(0,count):
        sym_list_length = len(sym_list)
        if k == 0:
            layer_length = len(layer)
            start_index = sym_list_length - layer_length + 1
        else:
            layer_length = nlayer_len[k-1]
            start_index = sym_list_length - layer_length + 1
        next_layer = getChildrens(start_index, sym_list);
        sym_list.extend(next_layer);
        if len(next_layer) != 0:
            nlayer_len.append(len(next_layer))
        else:
            print 'Can\'t find any children, finished!'
            return sym_list
    return sym_list
     

This is where the actual repetitive process or so called looping starts. Firstly, an empty list for next layer‘s length is created. This is necessary in order to effectively loop only through objects that were recently added to sym_list.

sym_list_length variable is necessary in order to detect sym_list length within each time when we start the loop. With if statement we’re telling that if this is the first loop then we’re going to assign first layer‘s length to for loop’s layer_length variable. Any other time we’re going to take a value from next layer’s list – nlayer_len with index k-1.

With start_index we’re determining from which sym_list index we’re going to start the looping. If you skip this step you’ll end up with object name repetition as well as a lot slower script’s execution.

next_layer = getChildrens(start_index, sym_list) Uses calculated start_index in order to go through objects that were not used before. List of new child objects are assigned to next_layer variable.

With sym_list.extend(next_layer) we’re extending sym_list and adding new child objects to the list.

Finally, with another if statement we’re checking if there are any more children objects. If there’s not we’re returning sym_list with all visible child objects.


Two examples of how I use getAllChildren function.

def findObjectById(parent_obj, id, count = 20):
    object_list = getAllChildren(parent_obj, count);
    for obj in xrange(0, len(object_list)):
        object_string = object_list[obj]
        if id in object_string:
            print "Found object: %s" %object_string
            object = findObject(object_string)
            status = True
            return object
            break
        else:
            status = False
    if status == False:
        test.fail('Couldn\'t find object by id: %s' %id)
     

findObjectById finds all visible object names using getAllChildren(parent_obj, count) and then goes through the list of these names looking for id in each string.

In this case id can be any string it doesn’t necessary have to be the actual id of the object. The function checks if there’s a matching string (id) in the object name and if there is, it uses findObject(object_string) to return object.

def mouseClickObj(object, timeout = 20000, sleep_amount = 0.2, x = 0, y = 0):
    try:
        snooze(sleep_amount);
        if x > 0:
            mouseClick(waitForObject(object, timeout), x, y, Qt.LeftButton);
        else:
            mouseClick(waitForObject(object, timeout));
        snooze(sleep_amount);
    except LookupError:
        test.fail('%s was not found or it\'s properties were changed' %object)

def mouseClickObjById(container, id, count = 20, sleep = 0.2):
    object = findObjectById(container, id, count);
    mouseClickObj(object, sleep_amount = sleep);
     

By default squish has built in mouseClick() function, but later on in test automation development process I decided to come up with nice way to snooze between each click.

mouseClickObjById uses previously explained findObjectById function to find visible elements in real time. Basically, you can look for part of the object name and it will find it.

For example, If I have an object ‘{container=’:Application_QQuickWindowQmlImpl’ text=’This is the object text’ type=’Text’ unnamed=’1′ visible=’true’}’ in application under test, I could set the id in mouseClickObjById to be ‘This is the object text’ and it would find it and click on it.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s