Search code examples
pythonpyside2qttestqgraphicswidgetqtest

How to generate mouse click on QGraphicsWidget in QTest?


I want to test the functionality of pressing a button from my panel. The problem is that the button is not QPushButtons, but rather QGraphicWidget elements.

How do I generate this button mouse-click to test its behavior?

Buttons are not simple QPushButtons, because they have special behaviors on mouseover, are animated, etc. Those buttons are added in the scene, then the scene is added to the view, and the view as a widget is added to the layout. I can see that there is a possibility to click on the view using:

QTest.mouseClick(self.panel_with_buttons.view.viewport(), Qt.LeftButton)

but that doesn't click on the button. I've also tried specifying the position of the button:

rect = self.panel_with_buttons.button2.boundingRect()
QTest.mouseClick(self.panel_with_buttons.view.viewport(), Qt.LeftButton, pos = rect.center())

but this is for some reason unsupported argument

Code for Button class and PanelWithButtons class (panel_with_buttons.py):

from PySide2 import QtWidgets, QtCore, QtGui


class Button(QtWidgets.QGraphicsWidget):
    pressed = QtCore.Signal()

    def __init__(self, image_path: str, parent=None):
        super().__init__(parent)
        self._pixmap = QtGui.QPixmap(image_path)

    def paint(self, painter, option, widget=None):
        painter.setPen(QtCore.Qt.NoPen)
        painter.drawPixmap(0, 0, self._pixmap)

    def mousePressEvent(self, event: QtWidgets.QGraphicsSceneMouseEvent):
        self.pressed.emit()


class PanelWithButtons(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.button = QtWidgets.QPushButton('button1')
        self.button.clicked.connect(self.button_clicked)

        pix = 'home.png'
        self.button2 = Button(pix)
        self.button2.pressed.connect(self.button2_pressed)

        layout = QtWidgets.QVBoxLayout()
        layout.addWidget(self.button)
        self.scene = QtWidgets.QGraphicsScene()
        self.scene.addItem(self.button2)
        self.view = QtWidgets.QGraphicsView(self.scene)
        layout.addWidget(self.view)

        self.setLayout(layout)

    @QtCore.Slot()
    def button_clicked(self):
        print('button1 clicked')

    @QtCore.Slot()
    def button2_pressed(self):
        print('button2 pressed')

test.py:

from unittest import TestCase
from PySide2 import QtWidgets
from PySide2.QtTest import QTest
from PySide2.QtCore import Qt, QPoint
from test.panel_with_buttons import PanelWithButtons


class TestPanel(TestCase):
    def setUp(self) -> None:
        app = QtWidgets.QApplication()
        self.panel_with_buttons = PanelWithButtons()

    def test_go_cargo(self):
        QTest.mouseClick(self.panel_with_buttons.button, Qt.LeftButton)
        rect = self.panel_with_buttons.button2.boundingRect()
        QTest.mouseClick(self.panel_with_buttons.view.viewport(), Qt.LeftButton)

As the output, I'm receiving mouse click to the first button (I can see 'button1 clicked'), but I don't know how to generate click to the second button.


Solution

  • The panel_with_buttons.py file has the following errors:

    • You must create an absolute path of the resources, in this case the path of the image. When you run a .py file, if a relative path is passed this will be where the script was launched that could bring problems, assuming the image is next to panel_with_buttons.py.

    • You must set the size of the Button to be the size of the image using the resize() method.

    Considering that the file should be the following:

    import os
    from PySide2 import QtWidgets, QtCore, QtGui
    
    current_dir = os.path.dirname(os.path.realpath(__file__))
    
    
    class Button(QtWidgets.QGraphicsWidget):
        pressed = QtCore.Signal()
    
        def __init__(self, image_path: str, parent=None):
            super().__init__(parent)
            self._pixmap = QtGui.QPixmap(image_path)
            self.resize(self._pixmap.size())
    
        def paint(self, painter, option, widget=None):
            painter.setPen(QtCore.Qt.NoPen)
            painter.drawPixmap(0, 0, self._pixmap)
    
        def mousePressEvent(self, event: QtWidgets.QGraphicsSceneMouseEvent):
            self.pressed.emit()
            super().mousePressEvent(event)
    
    
    class PanelWithButtons(QtWidgets.QWidget):
        def __init__(self, parent=None):
            super().__init__(parent)
    
            self.button = QtWidgets.QPushButton("button1")
            self.button.clicked.connect(self.button_clicked)
    
            print(os.getcwd())
            pix = os.path.join(current_dir, "home.png")
            self.button2 = Button(pix)
            self.button2.pressed.connect(self.button2_pressed)
    
            layout = QtWidgets.QVBoxLayout()
            layout.addWidget(self.button)
            self.scene = QtWidgets.QGraphicsScene()
            self.scene.addItem(self.button2)
            self.view = QtWidgets.QGraphicsView(self.scene)
            layout.addWidget(self.view)
    
            self.setLayout(layout)
    
        @QtCore.Slot()
        def button_clicked(self):
            print("button1 clicked")
    
        @QtCore.Slot()
        def button2_pressed(self):
            print("button2 pressed")
    
    
    if __name__ == "__main__":
        import sys
    
        app = QtWidgets.QApplication(sys.argv)
        w = PanelWithButtons()
        w.show()
        sys.exit(app.exec_())
    

    On the other hand you must obtain the position of a point that belongs to Button relative to the viewport using the methods mapToScene of Button and mapFromScene of QGraphicsView, in this case the point will be the center of the Button.

    from unittest import TestCase
    from PySide2 import QtCore, QtCore, QtWidgets, QtTest
    
    from test.panel_with_buttons import PanelWithButtons
    
    
    class TestPanel(TestCase):
        def setUp(self) -> None:
            app = QtWidgets.QApplication()
            self.panel_with_buttons = PanelWithButtons()
            self.panel_with_buttons.resize(640, 480)
    
        def test_go_cargo(self):
            QtTest.QTest.mouseClick(
                self.panel_with_buttons.button, QtCore.Qt.LeftButton
            )
            it = self.panel_with_buttons.button2
            sp = it.mapToScene(it.rect().center())
            p = self.panel_with_buttons.view.mapFromScene(sp)
            QtTest.QTest.mouseClick(
                self.panel_with_buttons.view.viewport(), QtCore.Qt.LeftButton, pos=p
            )