5

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'}")
Jarno Argillander
  • 5,885
  • 2
  • 31
  • 33

3 Answers3

1

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)
Jarno Argillander
  • 5,885
  • 2
  • 31
  • 33
1

You can replace the symbolic name ":_KMainView" stored in ROOT_SYMBOLIC_OBJECT with its real name just as well, because symbolic name are just place holders for real names:

Article - How Squish looks up Real Names from Symbolic Names

frog.ca
  • 684
  • 4
  • 8
  • Interesting, unfortunately I don't work in the project anymore so cannot verify this. – Jarno Argillander Mar 09 '16 at 10:02
  • 1
    @JarnoArgillander, you mentioned not to work on the project anymore, yet you provided another answer where you are using the symbolic name ":_KMainView" and where you have that symbolic name in the object map. This entry in the object map is not required. Simply replace ":_KMainView" with the real name associated with this symbolic name in the object map. You are using object.children() to iterate over all children in the QQuickView, but this can be quite slow compared to using a real name that expresses the same, for example findObject("{container={type='QQuickView'} id='xyz'}"). – frog.ca Aug 21 '17 at 19:30
0

The syntax should be: findObject("{container=':_QQuickView' id='foobar' type='Text' visible='true'}")

You may find the information in [Navigate] -> [Open Symbolic Name], and then choose the element and right-click on mouse to "Copy Real Name"

diro
  • 241
  • 2
  • 5
  • This doesn't work based on testing. Also need to support invisible items and items of any type. Basically this is what I would want: findObject("{container=':_QQuickView' id='foobar'}"). – Jarno Argillander Sep 02 '14 at 11:04