Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. C++ integration tests with embedded QtQuick components

C++ integration tests with embedded QtQuick components

Scheduled Pinned Locked Moved Solved QML and Qt Quick
9 Posts 2 Posters 763 Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • P Offline
    P Offline
    patrickkidd
    wrote on last edited by patrickkidd
    #1

    I have a QWidgets app with a few embedded QtQuick forms, and want to write integration tests that include the QtQuick components. Has anyone done this?

    I am using PyQt and pytest, but I am proficient in C++ and the QtTest framework.

    Thanks!

    https://alaskafamilysystems.com/

    1 Reply Last reply
    0
    • SGaistS Offline
      SGaistS Offline
      SGaist
      Lifetime Qt Champion
      wrote on last edited by
      #2

      Hi,

      Are you already using pytest-qt ?

      Interested in AI ? www.idiap.ch
      Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

      P 1 Reply Last reply
      0
      • SGaistS SGaist

        Hi,

        Are you already using pytest-qt ?

        P Offline
        P Offline
        patrickkidd
        wrote on last edited by
        #3

        @SGaist yep. And it’s helpful for QtWidget tests. But that doesn’t have any way to simulate GUI events in QML so far as I know

        https://alaskafamilysystems.com/

        1 Reply Last reply
        0
        • SGaistS Offline
          SGaistS Offline
          SGaist
          Lifetime Qt Champion
          wrote on last edited by
          #4

          I haven't used it yet with a QtQuick application. I think you should contact the plugin authors for more information about that use case.

          Interested in AI ? www.idiap.ch
          Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

          P 1 Reply Last reply
          0
          • SGaistS SGaist

            I haven't used it yet with a QtQuick application. I think you should contact the plugin authors for more information about that use case.

            P Offline
            P Offline
            patrickkidd
            wrote on last edited by
            #5

            @SGaist Well, I think an even better question might be how to do this between C++ and Qml. The pytest-qt plugin is simple enough, but would rely on the basic functionality that may or may not exist at the C++ level. So I think that is really what my question is aimed at here.

            https://alaskafamilysystems.com/

            1 Reply Last reply
            0
            • SGaistS Offline
              SGaistS Offline
              SGaist
              Lifetime Qt Champion
              wrote on last edited by
              #6

              Good question and I don't currently know.

              It's likely going to involve the TestCase QML type somehow.

              Interested in AI ? www.idiap.ch
              Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

              P 1 Reply Last reply
              1
              • SGaistS SGaist

                Good question and I don't currently know.

                It's likely going to involve the TestCase QML type somehow.

                P Offline
                P Offline
                patrickkidd
                wrote on last edited by
                #7

                I sorted out a way to do this. I write a proper C++ (or python in my case) test and then pass input events to Qml. It's quite straightforward to roll your own keyboard/mouse simulation and property verification tools.

                The steps are:

                • Obtain the QQuickItem from C++ using the objectName property with qquickWidget->rootObject()->findChild<QQuickItem>(objectName)
                • Simulate the input event on the enclosing QQuickWidget using QTest.keyClicks, QTest::mouseClick(), etc.
                  • For keyboard events you have to manually focus the QQuickItem using either QQuickItem::forceActiveFocus() or with a mouse click.
                  • For mouse events you have to do the math to map item coordinates to scene (i.e. QQuickWidget) coordinates. Not hard.

                Controls in qml need to have the objectName property set to a unique string for that component for the C++ test code to find them. This is redundant if you are also effectively setting the same token on the id property, but I don't know how to find a child QQuickItem by id and actually think it isn't possible:

                TextEdit {
                    id: descriptionEdit
                    objectName: "descriptionEdit"
                }
                

                The following is my helper methods I added to all of my QWidget classes that contain a QQuickWidget as the self.qml property, You'll notice one custom input method for selecting a particular tab in a TabBar. You could write custom methods for many other Item subclasses, such as ListView & TableView (I will do so soon).

                    ## Tests                                                                                                                                                                                                     
                
                    def findItem(self, objectName):
                        parts = objectName.split('.')
                        item = self.qml.rootObject()
                        for partName in parts:
                            item = item.findChild(QQuickItem, partName)
                        if not item:
                            raise RuntimeError('Could not find item:', objectName)
                        return item
                
                    def focusItem(self, objectName):
                        item = self.findItem(objectName)
                        item.forceActiveFocus()
                        if not item.hasActiveFocus():
                            raise RuntimeError('Could not set active focus on:', objectName)
                        return item
                
                    def keyClick(self, objectName, key):
                        self.focusItem(objectName)
                        util.qtbot.keyClick(self.qml, key)
                
                    def keyClicks(self, objectName, s):
                        self.focusItem(objectName)
                        util.qtbot.keyClicks(self.qml, s)
                        self.qml.rootObject().forceActiveFocus()
                        if not self.qml.rootObject().hasActiveFocus():
                            raise RuntimeError('Could not set active focus on root object.')
                
                    def mouseClick(self, objectName, buttons):
                        item = self.findItem(objectName)
                        rect = item.mapRectToScene(QRectF(0, 0, item.width(), item.height())).toRect()
                        util.qtbot.mouseClick(self.qml, buttons, Qt.NoModifier, rect.center())
                
                    def clickTabWidgetPage(self, objectName, iTab):
                        item = self.findItem(objectName)
                        rect = item.mapRectToScene(QRectF(0, 0, item.width(), item.height())).toRect()
                        count = item.property('count')
                        tabWidth = rect.width() / count
                        tabStartX = tabWidth * iTab
                        tabEndX = tabWidth * iTab + tabWidth
                        tabCenterX = tabStartX + ((tabEndX - tabStartX) / 2)
                        tabCenterY = rect.height() / 2
                        tabCenter = QPoint(tabCenterX, tabCenterY)
                        util.qtbot.mouseClick(self.qml, Qt.LeftButton, Qt.NoModifier, tabCenter)
                        currentIndex = item.property('currentIndex')
                        if not currentIndex == iTab:
                            raise RuntimeError('Unable to click tab bar button index %i for TabBar %s. `currentIndex` is still %i' % (iTab, objectName, currentIndex))
                
                    def itemProp(self, objectName, attr):
                        item = self.findItem(objectName)
                        return item.property(attr)
                

                You will also notice that I wrote in a dot-reference in findItem that walks down child items when a . is in the objectName, such as dateButtons.dateTextInput. Here is some pytest code to do some user input:

                    if personName:
                        ep.keyClicks('nameBox', personName)
                        assert ep.itemProp('nameBox', 'currentText') == personName
                    ep.keyClicks('dateButtons.dateTextInput', util.dateString(props['date']))
                    # assertFalse(ui.dateEdit.lineEdit().hasFocus())                                                                                                                                                             
                    if props['unsure'] != ep.itemProp('dateButtons', 'unsure'):
                	ep.keyClick('dateButtons.unsureBox', Qt.Key_Space)
                    ep.keyClicks('descriptionEdit', props['description'])
                    ep.keyClicks('locationEdit', props['location'])
                    if props['nodal'] != ep.itemProp('nodalBox', 'checked'):
                	ep.keyClick('nodalBox', Qt.Key_Space)
                    ep.clickTabWidgetPage('tabBar', 1)
                    ep.findItem('notesEdit').selectAll()
                    ep.keyClicks('notesEdit', props['notes'])
                    ep.mouseClick('doneButton', Qt.LeftButton)
                

                And here is the respective pytest code to verify that the corresponding Qml Items' properties reflect the change:

                    assert event.description() == props['description']
                    assert event.date() == props['date']
                    assert event.unsure() == (props['unsure'] == Qt.Checked)
                    assert event.location() == props['location']
                    assert event.nodal() == (props['nodal'] == Qt.Checked)
                    assert event.notes() == props['notes']
                

                That should allow you to write C++ unit/integration tests that allow for covering qml components contained with the QtWidgets app. Sweet!

                https://alaskafamilysystems.com/

                1 Reply Last reply
                1
                • SGaistS Offline
                  SGaistS Offline
                  SGaist
                  Lifetime Qt Champion
                  wrote on last edited by
                  #8

                  Thanks for the detailed feedback !

                  Interested in AI ? www.idiap.ch
                  Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

                  P 1 Reply Last reply
                  1
                  • SGaistS SGaist

                    Thanks for the detailed feedback !

                    P Offline
                    P Offline
                    patrickkidd
                    wrote on last edited by
                    #9

                    @SGaist Yeah, this seems like an important cookbook recipe in the making. I wish I had the time to tidy it up for something like that.

                    https://alaskafamilysystems.com/

                    1 Reply Last reply
                    0

                    • Login

                    • Login or register to search.
                    • First post
                      Last post
                    0
                    • Categories
                    • Recent
                    • Tags
                    • Popular
                    • Users
                    • Groups
                    • Search
                    • Get Qt Extensions
                    • Unsolved