Qt Forum

    • Login
    • Search
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search
    • Unsolved

    Update: Forum Guidelines & Code of Conduct

    Solved C++ integration tests with embedded QtQuick components

    QML and Qt Quick
    2
    9
    313
    Loading More Posts
    • 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
      patrickkidd last edited by patrickkidd

      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://vedanamedia.com/

      1 Reply Last reply Reply Quote 0
      • SGaist
        SGaist Lifetime Qt Champion last edited by

        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 Reply Quote 0
        • P
          patrickkidd @SGaist last edited by

          @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://vedanamedia.com/

          1 Reply Last reply Reply Quote 0
          • SGaist
            SGaist Lifetime Qt Champion last edited by

            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 Reply Quote 0
            • P
              patrickkidd @SGaist last edited by

              @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://vedanamedia.com/

              1 Reply Last reply Reply Quote 0
              • SGaist
                SGaist Lifetime Qt Champion last edited by

                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 Reply Quote 1
                • P
                  patrickkidd @SGaist last edited by

                  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://vedanamedia.com/

                  1 Reply Last reply Reply Quote 1
                  • SGaist
                    SGaist Lifetime Qt Champion last edited by

                    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 Reply Quote 1
                    • P
                      patrickkidd @SGaist last edited by

                      @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://vedanamedia.com/

                      1 Reply Last reply Reply Quote 0
                      • First post
                        Last post