Search code examples
pythonqmlsquish

Get QML element in Squish by id or objectName using findObject() or waitForObject() without object map


How to get QML element by id or objectName using either findObject() or waitForObject() without object map? Is it even possible?

Consider:

Item {
    id: page
    objectName: "pageObject"

    Text {
        id: foobar
        objectName: "lorem"
        text: "ipsum"
    }
}

I would like to access foobar's text in test script like:

obj = findObject("foobar")
if obj.text == "ipsum":
    test.passes("all good")
else:
    test.fail("uh oh")

I have also tried:

obj = findObject("lorem")
obj = findObject("{name='lorem'}")
obj = findObject("{name='lorem' type='Text'}")
obj = findObject("{objectName='lorem'}")
obj = findObject("{objectName='lorem' type='Text'}")

Solution

  • Ultimately I solved the issue by only having the root level object in object map. All other objects are iterated in code using functions below.

    import sys
    import time
    
    # Definitions
    ROOT_SYMBOLIC_OBJECT = ":_KMainView"
    
    # Object finding functions
    def findSymbolic(symbolicName):
        # Note: give Squish symbolic name like ":Foobar"
        try:
            obj = findObject(symbolicName)
        except LookupError:
            sys.exc_clear()
            test.log("FAIL: Root object not found: " + symbolicName)
            sys.exit()
    
        return obj
    
    def findRoot():
        return findSymbolic(ROOT_SYMBOLIC_OBJECT)
    
    def findFlat(fromElement, findId, occurence, stopOnFail, verbose):
        start = time.time()
        result = None
        found = 0
    
        if fromElement and hasattr(fromElement, 'children'):
            children = object.children(fromElement)
            for child in children:
                if hasattr(child, 'id') and child.id == findId:
                    found += 1
                    if found == occurence:
                        result = child
                        break
    
        if result == None and stopOnFail:
            test.log("FAIL: findFlat: " + findId + " not found")
            sys.exit()
    
        if verbose:
            printElapsed("findFlat", findId, start)
    
        return result
    
    def findRecursive(fromElement, findId, occurence):
        return findRecursiveWithOptions(fromElement, findId, occurence, True, False, True)
    
    def findRecursiveWithOptions(fromElement, findId, occurence, stopOnFail, verbose, skipUnnamed):
        start = time.time()
        found = 0
        depth = -1
    
        obj, found, depth = objIter(fromElement, findId, occurence, verbose, skipUnnamed, found, depth)
    
        if found == occurence:
            printElapsed("findRecursive ok", findId, start)
            return obj
    
        printElapsed("findRecursive not found", findId, start)
        if stopOnFail:
            test.log("FAIL: findRecursive:" + findId + " not found.")
            sys.exit()
    
        return None 
    
    def objIter(fromElement, findId, occurence, verbose, skipUnnamed, found, depth):
        depth += 1
    
        if verbose:
            printObjIter(fromElement, depth)
    
        children = object.children(fromElement)
        for child in children:
            if hasattr(child, 'id'):
                if child.id == findId:
                    found += 1
                    if found == occurence:
                        return child, found, depth
            elif skipUnnamed:
                continue
    
            obj, found, depth = objIter(child, findId, occurence, verbose, skipUnnamed, found, depth)
            depth = depth - 1
            if found == occurence: 
                return obj, found, depth
    
        return None, found, depth
    
    def findRecursiveList(fromElement, findId):
        return findRecursiveListWithOptions(fromElement, findId, True, False, True)
    
    def findRecursiveListWithOptions(fromElement, findId, stopOnFail, verbose, skipUnnamed):
        start = time.time()
        objList = []
        depth = -1
    
        objList, depth = objListIter(fromElement, findId, verbose, skipUnnamed, objList, depth)
    
        printElapsed("findRecursiveList", findId, start)
        return objList 
    
    def objListIter(fromElement, findId, verbose, skipUnnamed, objList, depth):
        depth += 1
    
        if verbose:
            printObjIter(fromElement, depth)
    
        children = object.children(fromElement)
        for child in children:
            if hasattr(child, 'id'):
                if child.id == findId:
                    objList.append(child)
            elif skipUnnamed:
                continue
    
            objList, depth = objListIter(child, findId, verbose, skipUnnamed, objList, depth)
            depth = depth - 1
    
        return objList, depth
    
    # Utilities
    def printElapsed(functionName, objectId, start):
        elapsed = time.time() - start
        test.log(functionName + " - " + objectId + " in " + str(elapsed) + "s.")
    
    def printObjIter(element, depth):
        dashes = "-" * depth
    
        if hasattr(element, 'id'):
            test.log(dashes + " " + str(element.id))
        else:
            test.log(dashes + " [unnamed]")
    

    Example test script for the question's QML:

    startApplication("exampleapp") # Launch application binary 'exampleapp'
    snooze(10) # Let it start
    
    root = findRoot()
    page = findFlat(root, "page", 1, True, False)
    foobar = findFlat(page, "foobar", 1, True, False)
    
    # Also just: foobar = findRecursive(root, "foobar", 1)
    
    if foobar.text == "ipsum":
        test.passes("text is ok")
    else:
        test.fail("Incorrect foobar text: " + foobar.text)