pytest-dev / pytest-qt

pytest plugin for Qt (PyQt5/PyQt6 and PySide2/PySide6) application testing
https://pytest-qt.readthedocs.io
MIT License
410 stars 70 forks source link

qtbot.screenshot() changes test behavior #564

Closed philipp-radio closed 4 months ago

philipp-radio commented 4 months ago

I created a test using pytest-qt for a custom QMdiArea implementation. When doing this I noticed that taking a screenshot changes the program behavior. Code to replicate looks something like this:

def test_resize_mdi_area(qtbot, gui, widgets):
    qtbot.add_widget(gui._mdi_area)
    print(gui._mdi_area.width(), gui._mdi_area.height()) # prints 100, 30 should be 1359, 642
    gui.main_window.resize(gui.main_window.width() + 50, gui.main_window.height() + 50)
    print(gui._mdi_area.size().width(), gui._mdi_area.size().height()) # prints 100, 30 should be 1409, 692

I noticed that the values stay the same (and are wrong), even though I resized the main window. If I now take screenshots before & after resizing however, the values are correct:

def test_resize_mdi_area(qtbot, gui, widgets):
    qtbot.add_widget(gui._mdi_area)
    qtbot.screenshot(gui.main_window)
    print(gui._mdi_area.width(), gui._mdi_area.height()) # prints 1359, 642
    gui.main_window.resize(gui.main_window.width() + 50, gui.main_window.height() + 50)
    qtbot.screenshot(gui.main_window)
    print(gui._mdi_area.size().width(), gui._mdi_area.size().height()) # prints 1409, 692

I'm running the tests with tox and setting the QT_QPA_PLATFORM variable to offscreen and I guess the problem has something to do with that? I'm doing it this way because I got the tests working in a Gitlab CI pipeline with this configuration.

Just for reference, my implementation of QMdiArea looks like the following:

class MyMdiArea(QtWidgets.QMdiArea):
    """Customized implementation of :class:`QMdiArea`."""

    def __init__(self, parent: QtWidgets.QWidget | None = None) -> None:
        """Create a new instance of :class:`TdMdiArea`.

        Args:
            parent (QtWidgets.QWidget | None, optional): Parent widget.
                Defaults to None.
        """
        super().__init__(parent)
        self.setEnabled(True)
        self.setAutoFillBackground(False)
        self.setFrameShadow(QtWidgets.QFrame.Shadow.Raised)

    def cascadeSubWindows(self) -> None:  # noqa: N802
        """Cascade and resize all sub-windows."""
        super().cascadeSubWindows()
        for sub_window in self.subWindowList():
            sub_window.resize(500, 200)

    def horizontally_split_sub_windows(self) -> None:
        """Split sub windows horizontally.

        All sub-windows will have the full width of the MDI area, but only a part of
        the height.
        """
        sub_windows = self.subWindowList()
        if not sub_windows:
            return
        window_height = int(self.height() / len(sub_windows))
        for i, sub_window in enumerate(sub_windows):
            sub_window.move(0, i * window_height)
            sub_window.resize(self.width(), window_height)

I'm using PyQt 5.15.10 and pytest-qt 4.4.0.

The-Compiler commented 4 months ago

I doubt this is a pytest-qt issue. Can you call Qt's gui.main_window.grab() manually and see if that shows the same behavior?

philipp-radio commented 4 months ago

Yes you're right it's the same behavior. Can you make a guess why it's behaving this way?

philipp-radio commented 4 months ago

Closing this issue as it is not a pytest-qt problem. Thanks for your help :)

The-Compiler commented 4 months ago

The implementation of QWidget::grab() starts with:

QPixmap QWidget::grab(const QRect &rectangle)
{
    Q_D(QWidget);
    if (testAttribute(Qt::WA_PendingResizeEvent) || !testAttribute(Qt::WA_WState_Created))
        sendResizeEvents(this);

with that being defined as:

Indicates that a resize event is pending, e.g., when a hidden widget was resized. This flag is set or cleared by the Qt kernel.

So you might just need to actually show your widget?