pythonpyqtvisibleqttest

QtTest under PyQt5 fails when widgets-under-test have to be visible to work


I've started to create UI tests for my PyQt5 widgets using QtTest but have run into the following difficulties:

Is there a way to make widgets visible during test? Is this good practice (e.g. w.r.t. CI test on GitHub) and is QtTest the way to go?

I have tried to use pytest with pytest-qt without success as I couldn't find a proper introduction or tutorial and I do know "Test PyQt GUIs with QTest and unittest".

Below you find a MWE consisting of a widget mwe_qt_widget.MyWidget with a combobox, a pushbutton and a label that gets updated by the other two subwidgets:

from PyQt5.QtWidgets import QComboBox, QWidget, QPushButton, QLabel, QHBoxLayout, QApplication

class MyWidget(QWidget):
    def __init__(self, parent=None):
        super(MyWidget, self).__init__(parent)
        self.n = 0  # click counter
        self.lbl = QLabel("default", self)
        self.but = QPushButton("+ 1", self)
        self.cmb = QComboBox(self)
        self.cmb.addItems(["A", "B", "C"])

        lay_h_main = QHBoxLayout(self)
        lay_h_main.addWidget(self.cmb)
        lay_h_main.addWidget(self.but)
        lay_h_main.addWidget(self.lbl)
        self.setLayout(lay_h_main)

        self.cmb.currentIndexChanged.connect(
            lambda: self.lbl.setText(self.cmb.currentText()+f" {self.n}"))
        self.but.clicked.connect(self.update_label)
    # --------------------------------------------------------------------------
    def update_label(self):
        """count clicks and update label with current combobox text"""
        if self.isVisible():
            self.n += 1
            self.lbl.setText(self.cmb.currentText()+f" {self.n}")
# ==============================================================================
if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    mainw = MyWidget(None)
    app.setActiveWindow(mainw)
    mainw.show()
    sys.exit(app.exec_())

This widget is tested with the following test setup. test_visibility() and test_button() fail because both require that the widget-under-test is visible:

import sys, unittest
import mwe_qt_widget
from PyQt5 import QtTest, QtCore
from PyQt5.QtWidgets import QApplication

class WidgetTest(unittest.TestCase):
    def init(self):
        """Instantiate widget-under-test and assert default settings"""
        self.form = mwe_qt_widget.MyWidget()
        self.assertEqual(self.form.cmb.currentText(), "A")
        self.assertEqual(self.form.lbl.text(), "default")
    # --------------------------------------------------------------------------
    def test_button(self):
        """Test whether button click updates label"""
        self.init()
        QtTest.QTest.mouseClick(self.form.but, QtCore.Qt.LeftButton)
        self.assertEqual(self.form.cmb.currentText(), "A")
        self.assertEqual(self.form.lbl.text(), "A 1")
    # --------------------------------------------------------------------------
    def test_combobox(self):
        """Test whether combobox updates label"""
        self.init()
        QtTest.QTest.keyClick(self.form.cmb, QtCore.Qt.Key_PageDown)
        QtTest.QTest.qWait(100)
        self.assertEqual(self.form.cmb.currentText(), "B")
        self.assertEqual(self.form.lbl.text(), "B 0")
    # --------------------------------------------------------------------------
    def test_visibility(self):
        """Test visibility of widget and subwidgets"""
        self.init()
        self.assertEqual(self.form.isVisible(), True)
        self.assertEqual(self.form.cmb.isVisible(), True)
# ==============================================================================
if __name__ == '__main__':
    app = QApplication(sys.argv)  # Must construct a QApplication before a QWidget
    unittest.main()
    mainw = WidgetTest()
    app.setActiveWindow(mainw)
    mainw.show()

Solution

  • The problem is simple: QWidgets are hidden by default so isVisible() will return false, the solution is to invoke the show() method in init() to make it visible:

    class WidgetTest(unittest.TestCase):
        def init(self):
            """Instantiate widget-under-test and assert default settings"""
            self.form = mwe_qt_widget.MyWidget()
            self.form.show()
            self.assertEqual(self.form.cmb.currentText(), "A")
            self.assertEqual(self.form.lbl.text(), "default")