Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-pytest-qt for 
openSUSE:Factory checked in at 2022-07-05 12:09:49
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pytest-qt (Old)
 and      /work/SRC/openSUSE:Factory/.python-pytest-qt.new.1548 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-pytest-qt"

Tue Jul  5 12:09:49 2022 rev:9 rq:986733 version:4.1.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pytest-qt/python-pytest-qt.changes        
2021-07-13 22:37:58.933864215 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-pytest-qt.new.1548/python-pytest-qt.changes  
    2022-07-05 12:10:24.388636554 +0200
@@ -1,0 +2,25 @@
+Fri Jul  1 19:06:12 UTC 2022 - Ben Greiner <c...@bnavigator.de>
+
+- Update to version 4.1.0
+  * pytest-qt now requires Python 3.7+.
+  * Improved PEP-8 aliases definition so they have a smaller call
+    stack depth by one and better parameter suggestions in IDEs.
+    (#383). Thanks @luziferius for the PR.
+  * Updated model tester handling around hasChildren based on Qt's
+    updates.
+  * New qapp_cls fixture returning the QApplication class to use,
+    thus making it easier to use a custom subclass without having
+    to override the whole qapp fixture. Thanks @The-Compiler for
+    the PR.
+  * Updated model tester to track/verify in-flight changes based on
+    Qt's updates. Thanks @The-Compiler for the PR.
+  * New qtbot.screenshot() method which can be used to take a
+    screenshot of the given widget. Thanks @The-Compiler for the
+    PR.
+
+-------------------------------------------------------------------
+Thu Apr 21 13:13:18 UTC 2022 - Ben Greiner <c...@bnavigator.de>
+
+- Enable pyside6 test flavor
+
+-------------------------------------------------------------------

Old:
----
  pytest-qt-4.0.2.tar.gz

New:
----
  pytest-qt-4.1.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-pytest-qt.spec ++++++
--- /var/tmp/diff_new_pack.BKnTmg/_old  2022-07-05 12:10:24.788637129 +0200
+++ /var/tmp/diff_new_pack.BKnTmg/_new  2022-07-05 12:10:24.796637140 +0200
@@ -1,7 +1,7 @@
 #
 # spec file
 #
-# Copyright (c) 2021 SUSE LLC
+# Copyright (c) 2022 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -65,13 +65,14 @@
 %endif
 
 Name:           python-pytest-qt%{psuffix}
-Version:        4.0.2
+Version:        4.1.0
 Release:        0
 Summary:        Pytest support for PyQt and PySide applications
 License:        MIT
 Group:          Development/Languages/Python
 URL:            https://github.com/pytest-dev/pytest-qt
 Source:         
https://files.pythonhosted.org/packages/source/p/pytest-qt/pytest-qt-%{version}.tar.gz
+BuildRequires:  %{python_module base >= 3.7}
 BuildRequires:  %{python_module setuptools_scm}
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  dos2unix
@@ -91,9 +92,8 @@
 Requires:       (python-qt5 or python-PyQt6)
 %endif
 %if %{with test}
-# https://github.com/pytest-dev/pytest-qt/issues/376
-BuildRequires:  %{python_module pytest >= 4.5}
 BuildRequires:  %{python_module pytest-qt = %{version}}
+BuildRequires:  %{python_module pytest}
 %endif
 %python_subpackages
 

++++++ _multibuild ++++++
--- /var/tmp/diff_new_pack.BKnTmg/_old  2022-07-05 12:10:24.836637198 +0200
+++ /var/tmp/diff_new_pack.BKnTmg/_new  2022-07-05 12:10:24.840637204 +0200
@@ -2,6 +2,6 @@
   <flavor>test-pyqt5</flavor>
   <flavor>test-pyqt6</flavor>
   <flavor>test-pyside2</flavor>
-  <!-- flavor>test-pyside6</flavor -->
+  <flavor>test-pyside6</flavor>
 </multibuild>
 

++++++ pytest-qt-4.0.2.tar.gz -> pytest-qt-4.1.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pytest-qt-4.0.2/.github/workflows/main.yml 
new/pytest-qt-4.1.0/.github/workflows/main.yml
--- old/pytest-qt-4.0.2/.github/workflows/main.yml      2021-06-14 
00:54:48.000000000 +0200
+++ new/pytest-qt-4.1.0/.github/workflows/main.yml      2022-06-23 
16:39:11.000000000 +0200
@@ -10,23 +10,39 @@
       fail-fast: false
 
       matrix:
-        python-version: [3.6, 3.7, 3.8, 3.9]
+        python-version: ["3.7", "3.8", "3.9", "3.10", "3.11-dev"]
         qt-lib: [pyqt5, pyqt6, pyside2, pyside6]
         os: [ubuntu-20.04, windows-latest, macos-latest]
         include:
-          - python-version: "3.6"
-            tox-env: "py36"
           - python-version: "3.7"
             tox-env: "py37"
           - python-version: "3.8"
             tox-env: "py38"
           - python-version: "3.9"
             tox-env: "py39"
+          - python-version: "3.10"
+            tox-env: "py310"
+          - python-version: "3.11-dev"
+            tox-env: "py311"
+        # https://bugreports.qt.io/browse/PYSIDE-1797
+        exclude:
+          - qt-lib: pyside6
+            os: macos-latest
+            python-version: "3.7"
+          - qt-lib: pyside6
+            os: ubuntu-20.04
+            python-version: "3.7"
+          # Not installable so far
+          - qt-lib: pyside6
+            python-version: "3.11-dev"
+          - qt-lib: pyside2
+            os: windows-latest
+            python-version: "3.11-dev"
 
     steps:
-    - uses: actions/checkout@v1
+    - uses: actions/checkout@v3
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v1
+      uses: actions/setup-python@v3
       with:
         python-version: ${{ matrix.python-version }}
 
@@ -48,9 +64,9 @@
     runs-on: ubuntu-latest
 
     steps:
-    - uses: actions/checkout@v1
+    - uses: actions/checkout@v3
     - name: Set up Python
-      uses: actions/setup-python@v1
+      uses: actions/setup-python@v3
       with:
         python-version: "3.7"
     - name: Install tox
@@ -73,9 +89,9 @@
     needs: [build, checks]
 
     steps:
-    - uses: actions/checkout@v1
+    - uses: actions/checkout@v3
     - name: Set up Python
-      uses: actions/setup-python@v1
+      uses: actions/setup-python@v3
       with:
         python-version: "3.7"
     - name: Build package
@@ -85,7 +101,7 @@
         python setup.py sdist bdist_wheel
     - name: Publish package to PyPI
       if: github.event_name == 'push' && startsWith(github.event.ref, 
'refs/tags')
-      uses: pypa/gh-action-pypi-publish@master
+      uses: pypa/gh-action-pypi-publish@v1.5.0
       with:
         user: __token__
         password: ${{ secrets.pypi_token }}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pytest-qt-4.0.2/.pre-commit-config.yaml 
new/pytest-qt-4.1.0/.pre-commit-config.yaml
--- old/pytest-qt-4.0.2/.pre-commit-config.yaml 2021-06-14 00:54:48.000000000 
+0200
+++ new/pytest-qt-4.1.0/.pre-commit-config.yaml 2022-06-23 16:39:11.000000000 
+0200
@@ -1,33 +1,33 @@
 repos:
 -   repo: https://github.com/psf/black
-    rev: 21.5b2
+    rev: 22.3.0
     hooks:
     -   id: black
         args: [--safe, --quiet]
         language_version: python3
 -   repo: https://github.com/asottile/blacken-docs
-    rev: v1.10.0
+    rev: v1.12.1
     hooks:
     -   id: blacken-docs
         additional_dependencies: [black==20.8b1]
         language_version: python3
 -   repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.0.1
+    rev: v4.2.0
     hooks:
     -   id: trailing-whitespace
     -   id: end-of-file-fixer
     -   id: check-yaml
     -   id: debug-statements
 -   repo: https://github.com/asottile/pyupgrade
-    rev: v2.19.1
+    rev: v2.32.1
     hooks:
     - id: pyupgrade
 -   repo: https://github.com/PyCQA/flake8
-    rev: 3.9.2
+    rev: 4.0.1
     hooks:
     - id: flake8
 -   repo: https://github.com/pre-commit/pygrep-hooks
-    rev: v1.8.0
+    rev: v1.9.0
     hooks:
     -   id: rst-backticks
 -   repo: local
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pytest-qt-4.0.2/CHANGELOG.rst 
new/pytest-qt-4.1.0/CHANGELOG.rst
--- old/pytest-qt-4.0.2/CHANGELOG.rst   2021-06-14 00:54:48.000000000 +0200
+++ new/pytest-qt-4.1.0/CHANGELOG.rst   2022-06-23 16:39:11.000000000 +0200
@@ -1,3 +1,23 @@
+UNRELEASED
+----------
+
+4.1.0 (2022-06-23)
+------------------
+
+- ``pytest-qt`` now requires Python 3.7+.
+- Improved PEP-8 aliases definition so they have a smaller call stack depth by 
one and better parameter suggestions in IDEs. (`#383`_). Thanks `@luziferius`_ 
for the PR.
+- Updated model tester handling around ``hasChildren`` based on Qt's updates.
+- New ``qapp_cls`` fixture returning the ``QApplication`` class to use, thus
+  making it easier to use a custom subclass without having to override the
+  whole ``qapp`` fixture. Thanks `@The-Compiler`_ for the PR.
+- Updated model tester to track/verify in-flight changes based on Qt's updates.
+  Thanks `@The-Compiler`_ for the PR.
+- New ``qtbot.screenshot()`` method which can be used to take a screenshot of
+  the given widget. Thanks `@The-Compiler`_ for the PR.
+
+.. _#383: https://github.com/pytest-dev/pytest-qt/pull/383
+.. _@luziferius: https://github.com/luziferius
+
 4.0.2 (2021-06-14)
 ------------------
 
@@ -78,7 +98,7 @@
   pass ``timeout=1000`` to those functions (`#306`_). Thanks `@The-Compiler`_
   for the PR.
 - ``waitUntil`` now raises a ``TimeoutError`` when a timeout occurs to make the
-  cause of the timeout more explict (`#222`_). Thanks `@karlch`_ for the PR.
+  cause of the timeout more explicit (`#222`_). Thanks `@karlch`_ for the PR.
 - The ``QtTest::keySequence`` method is now exposed (if available, with Qt >=
   5.10) (`#289`_). Thanks `@The-Compiler`_ for the PR.
 - ``addWidget`` now enforces that its argument is a ``QWidget`` in order to
@@ -534,7 +554,7 @@
   when tests fail, similar to `pytest-catchlog`_. Also, tests
   can be configured to automatically fail if an unexpected message is 
generated.
 
-- New method ``waitSignals``: will block untill **all** signals given are
+- New method ``waitSignals``: will block until **all** signals given are
   triggered (thanks `@The-Compiler`_ for idea and complete PR).
 
 - New parameter ``raising`` to ``waitSignals`` and ``waitSignals``: when 
``True``
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pytest-qt-4.0.2/PKG-INFO new/pytest-qt-4.1.0/PKG-INFO
--- old/pytest-qt-4.0.2/PKG-INFO        2021-06-14 00:54:52.000000000 +0200
+++ new/pytest-qt-4.1.0/PKG-INFO        2022-06-23 16:39:27.000000000 +0200
@@ -1,28 +1,27 @@
 Metadata-Version: 2.1
 Name: pytest-qt
-Version: 4.0.2
+Version: 4.1.0
 Summary: pytest support for PyQt and PySide applications
 Home-page: http://github.com/pytest-dev/pytest-qt
 Author: Bruno Oliveira
 Author-email: nicodde...@gmail.com
 License: MIT
 Keywords: pytest qt test unittest
-Platform: UNKNOWN
 Classifier: Development Status :: 5 - Production/Stable
 Classifier: Framework :: Pytest
 Classifier: Intended Audience :: Developers
 Classifier: License :: OSI Approved :: MIT License
 Classifier: Operating System :: OS Independent
 Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
 Classifier: Programming Language :: Python :: 3.8
 Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
 Classifier: Topic :: Desktop Environment :: Window Managers
 Classifier: Topic :: Software Development :: Quality Assurance
 Classifier: Topic :: Software Development :: Testing
 Classifier: Topic :: Software Development :: User Interfaces
-Requires-Python: >=3.6
+Requires-Python: >=3.7
 Provides-Extra: doc
 Provides-Extra: dev
 License-File: LICENSE
@@ -32,7 +31,7 @@
 =========
 
 pytest-qt is a `pytest`_ plugin that allows programmers to write tests
-for `PyQt5`_, `PyQt6`_, `PySide2`_ and `PyQt6`_ applications.
+for `PyQt5`_, `PyQt6`_, `PySide2`_ and `PySide6`_ applications.
 
 The main usage is to use the ``qtbot`` fixture, responsible for handling 
``qApp``
 creation as needed and provides methods to simulate user interaction,
@@ -103,7 +102,7 @@
 Requirements
 ============
 
-Since version 4.0.0, ``pytest-qt`` requires Python 3.6+.
+Since version 4.1.0, ``pytest-qt`` requires Python 3.7+.
 
 Works with either PySide6_, PySide2_, PyQt6_ or PyQt5_, picking whichever
 is available on the system, giving preference to the first one installed in
@@ -115,7 +114,7 @@
 - ``PyQt5``
 
 To force a particular API, set the configuration variable ``qt_api`` in your 
``pytest.ini`` file to
-``pyqt6``, ``pyside2``, ``pyqt6`` or ```pyqt5``:
+``pyqt5``, ``pyside2``, or ``pyqt6``:
 
 .. code-block:: ini
 
@@ -207,8 +206,9 @@
 
 **Powered by**
 
-.. |pycharm| image:: https://www.jetbrains.com/pycharm/docs/logo_pycharm.png
+.. |pycharm| image:: 
https://resources.jetbrains.com/storage/products/company/brand/logos/PyCharm.png
   :target: https://www.jetbrains.com/pycharm
+  :width: 400
 
 .. |pydev| image:: http://www.pydev.org/images/pydev_banner3.png
   :target: https://www.pydev.org
@@ -218,5 +218,3 @@
 |pydev|
 
 .. _tox: https://tox.readthedocs.io
-
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pytest-qt-4.0.2/README.rst 
new/pytest-qt-4.1.0/README.rst
--- old/pytest-qt-4.0.2/README.rst      2021-06-14 00:54:48.000000000 +0200
+++ new/pytest-qt-4.1.0/README.rst      2022-06-23 16:39:11.000000000 +0200
@@ -3,7 +3,7 @@
 =========
 
 pytest-qt is a `pytest`_ plugin that allows programmers to write tests
-for `PyQt5`_, `PyQt6`_, `PySide2`_ and `PyQt6`_ applications.
+for `PyQt5`_, `PyQt6`_, `PySide2`_ and `PySide6`_ applications.
 
 The main usage is to use the ``qtbot`` fixture, responsible for handling 
``qApp``
 creation as needed and provides methods to simulate user interaction,
@@ -74,7 +74,7 @@
 Requirements
 ============
 
-Since version 4.0.0, ``pytest-qt`` requires Python 3.6+.
+Since version 4.1.0, ``pytest-qt`` requires Python 3.7+.
 
 Works with either PySide6_, PySide2_, PyQt6_ or PyQt5_, picking whichever
 is available on the system, giving preference to the first one installed in
@@ -86,7 +86,7 @@
 - ``PyQt5``
 
 To force a particular API, set the configuration variable ``qt_api`` in your 
``pytest.ini`` file to
-``pyqt6``, ``pyside2``, ``pyqt6`` or ```pyqt5``:
+``pyqt5``, ``pyside2``, or ``pyqt6``:
 
 .. code-block:: ini
 
@@ -178,8 +178,9 @@
 
 **Powered by**
 
-.. |pycharm| image:: https://www.jetbrains.com/pycharm/docs/logo_pycharm.png
+.. |pycharm| image:: 
https://resources.jetbrains.com/storage/products/company/brand/logos/PyCharm.png
   :target: https://www.jetbrains.com/pycharm
+  :width: 400
 
 .. |pydev| image:: http://www.pydev.org/images/pydev_banner3.png
   :target: https://www.pydev.org
Binary files old/pytest-qt-4.0.2/docs/_static/button.png and 
new/pytest-qt-4.1.0/docs/_static/button.png differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pytest-qt-4.0.2/docs/conf.py 
new/pytest-qt-4.1.0/docs/conf.py
--- old/pytest-qt-4.0.2/docs/conf.py    2021-06-14 00:54:48.000000000 +0200
+++ new/pytest-qt-4.1.0/docs/conf.py    2022-06-23 16:39:11.000000000 +0200
@@ -41,8 +41,8 @@
 master_doc = "index"
 
 # General information about the project.
-project = u"pytest-qt"
-copyright = u"2013, Bruno Oliveira"
+project = "pytest-qt"
+copyright = "2013, Bruno Oliveira"
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
@@ -180,7 +180,7 @@
 # Grouping the document tree into LaTeX files. List of tuples
 # (source start file, target name, title, author, documentclass 
[howto/manual]).
 latex_documents = [
-    ("index", "pytest-qt.tex", u"pytest-qt Documentation", u"Bruno Oliveira", 
"manual")
+    ("index", "pytest-qt.tex", "pytest-qt Documentation", "Bruno Oliveira", 
"manual")
 ]
 
 # The name of an image file (relative to this directory) to place at the top of
@@ -208,7 +208,7 @@
 
 # One entry per manual page. List of tuples
 # (source start file, name, description, authors, manual section).
-man_pages = [("index", "pytest-qt", u"pytest-qt Documentation", [u"Bruno 
Oliveira"], 1)]
+man_pages = [("index", "pytest-qt", "pytest-qt Documentation", ["Bruno 
Oliveira"], 1)]
 
 # If true, show URL addresses after external links.
 # man_show_urls = False
@@ -223,8 +223,8 @@
     (
         "index",
         "pytest-qt",
-        u"pytest-qt Documentation",
-        u"Bruno Oliveira",
+        "pytest-qt Documentation",
+        "Bruno Oliveira",
         "pytest-qt",
         "One line description of project.",
         "Miscellaneous",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pytest-qt-4.0.2/docs/debugging.rst 
new/pytest-qt-4.1.0/docs/debugging.rst
--- old/pytest-qt-4.0.2/docs/debugging.rst      1970-01-01 01:00:00.000000000 
+0100
+++ new/pytest-qt-4.1.0/docs/debugging.rst      2022-06-23 16:39:11.000000000 
+0200
@@ -0,0 +1,54 @@
+Debugging failing tests
+=======================
+
+When a GUI-related test fails, it can sometimes be hard to find out where the 
culprit lies. To aid
+with debugging such tests, the ``qtbot`` fixture allows to stop the current
+test and to take screenshots of widgets.
+
+Stopping the current test
+-------------------------
+
+By calling :meth:`pytestqt.qtbot.QtBot.stop`, the current test gets
+interrupted. After closing all visible windows, ``qtbot`` attempts to restore
+the previous state and the test continues to run.
+
+.. note::
+
+   If you use Xvfb or the ``offscreen`` platform plugin (e.g. via
+   ``QT_QPA_PLATFORM=offscreen``), remember to disable those to see the 
windows. With the
+   `pytest-xvfb <https://github.com/The-Compiler/pytest-xvfb/>`_ plugin, this
+   is possible by passing ``--no-xvfb`` to pytest.
+
+Taking screenshots
+------------------
+
+.. versionadded:: 4.1
+
+Via :meth:`pytestqt.qtbot.QtBot.screenshot`, a screenshot of a given widget
+can be taken. That screenshot is then saved into a temporary directory provided
+by pytest. For example, this test:
+
+.. code:: python
+
+    from pytestqt.qt_compat import qt_api
+
+
+    def test_screenshot(qtbot):
+        button = qt_api.QtWidgets.QPushButton()
+        button.setText("Hello World!")
+        qtbot.add_widget(button)
+        path = qtbot.screenshot(button)
+        assert False, path  # show the path and fail the test
+
+would result in the following file at a location like
+``/tmp/pytest-of-USER/pytest-N/test_screenshot0/screenshot_QPushButton.png``:
+
+.. image:: _static/button.png
+
+The filename is generated based on the following parts:
+
+* ``screenshot``
+* The class of the widget (e.g. ``QWidget`` or ``QPushButton``)
+* The widget's ``objectName()``, if set
+* The given ``suffix``, if passed
+* A counter to make the filename unique, if another screenshot already exists
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pytest-qt-4.0.2/docs/index.rst 
new/pytest-qt-4.1.0/docs/index.rst
--- old/pytest-qt-4.0.2/docs/index.rst  2021-06-14 00:54:48.000000000 +0200
+++ new/pytest-qt-4.1.0/docs/index.rst  2022-06-23 16:39:11.000000000 +0200
@@ -20,6 +20,7 @@
     modeltester
     qapplication
     note_dialogs
+    debugging
     troubleshooting
     reference
     changelog
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pytest-qt-4.0.2/docs/intro.rst 
new/pytest-qt-4.1.0/docs/intro.rst
--- old/pytest-qt-4.0.2/docs/intro.rst  2021-06-14 00:54:48.000000000 +0200
+++ new/pytest-qt-4.1.0/docs/intro.rst  2022-06-23 16:39:11.000000000 +0200
@@ -74,7 +74,7 @@
 Requirements
 ============
 
-Since version 4.0.0, ``pytest-qt`` requires Python 3.6+.
+``pytest-qt`` requires Python 3.7+.
 
 Works with either PySide6_, PySide2_, PyQt6_ or PyQt5_, picking whichever
 is available on the system, giving preference to the first one installed in
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pytest-qt-4.0.2/docs/qapplication.rst 
new/pytest-qt-4.1.0/docs/qapplication.rst
--- old/pytest-qt-4.0.2/docs/qapplication.rst   2021-06-14 00:54:48.000000000 
+0200
+++ new/pytest-qt-4.1.0/docs/qapplication.rst   2022-06-23 16:39:11.000000000 
+0200
@@ -64,13 +64,16 @@
 
 If your tests require access to app-level functions, like
 ``CustomQApplication.custom_function()``, you can override the built-in
-``qapp`` fixture in your ``conftest.py`` to use your own app:
+``qapp_cls`` fixture in your ``conftest.py`` to return your custom class:
 
 .. code-block:: python
 
     @pytest.fixture(scope="session")
-    def qapp():
-        yield CustomQApplication([])
+    def qapp_cls():
+        return CustomQApplication
+
+The ``qapp`` fixture will then use the returned class instead of the default
+``QApplication`` from ``QtWidgets``.
 
 Setting a QApplication name
 ---------------------------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pytest-qt-4.0.2/docs/troubleshooting.rst 
new/pytest-qt-4.1.0/docs/troubleshooting.rst
--- old/pytest-qt-4.0.2/docs/troubleshooting.rst        2021-06-14 
00:54:48.000000000 +0200
+++ new/pytest-qt-4.1.0/docs/troubleshooting.rst        2022-06-23 
16:39:11.000000000 +0200
@@ -103,3 +103,19 @@
 ~~~~~~~~~~~
 
 Instead of running Xvfb manually it is possible to use ``pytest-xvfb`` plugin.
+
+Using with other Qt-related packages
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Using Python's Qt modules (``PySide`` or ``PyQt5``) with other packages which
+use Qt (e.g. ``cv2``) can result in conflicts. This is because the latter 
builds
+their own Qt and modify Qt-related environment variables. This may not raise 
errors
+in your local app, but running the tests on CI servers can fail.
+
+In this case, try use the package without Qt dependency. For example, if your
+code does not rely on ``cv2``'s Qt feature you can use
+``opencv-python-headless`` instead of full ``opencv-python``.
+
+More details can be found in `issue #396`_.
+
+.. _issue #396: https://github.com/pytest-dev/pytest-qt/issues/396
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pytest-qt-4.0.2/docs/wait_until.rst 
new/pytest-qt-4.1.0/docs/wait_until.rst
--- old/pytest-qt-4.0.2/docs/wait_until.rst     2021-06-14 00:54:48.000000000 
+0200
+++ new/pytest-qt-4.1.0/docs/wait_until.rst     2022-06-23 16:39:11.000000000 
+0200
@@ -59,7 +59,7 @@
     E         + Please input a number
     _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
_ _ _
     >       qtbot.waitUntil(check_label)
-    E       pytestqt.exceptions.TimeoutError: waitUntil timed out in 1000 
miliseconds
+    E       pytestqt.exceptions.TimeoutError: waitUntil timed out in 1000 
milliseconds
 
 
 A second way to use ``qtbot.waitUntil`` is to pass a callback which returns 
``True`` when the
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pytest-qt-4.0.2/setup.py new/pytest-qt-4.1.0/setup.py
--- old/pytest-qt-4.0.2/setup.py        2021-06-14 00:54:48.000000000 +0200
+++ new/pytest-qt-4.1.0/setup.py        2022-06-23 16:39:11.000000000 +0200
@@ -23,7 +23,7 @@
     url="http://github.com/pytest-dev/pytest-qt";,
     use_scm_version={"write_to": "src/pytestqt/_version.py"},
     setup_requires=["setuptools_scm"],
-    python_requires=">=3.6",
+    python_requires=">=3.7",
     classifiers=[
         "Development Status :: 5 - Production/Stable",
         "Framework :: Pytest",
@@ -31,10 +31,10 @@
         "License :: OSI Approved :: MIT License",
         "Operating System :: OS Independent",
         "Programming Language :: Python :: 3",
-        "Programming Language :: Python :: 3.6",
         "Programming Language :: Python :: 3.7",
         "Programming Language :: Python :: 3.8",
         "Programming Language :: Python :: 3.9",
+        "Programming Language :: Python :: 3.10",
         "Topic :: Desktop Environment :: Window Managers",
         "Topic :: Software Development :: Quality Assurance",
         "Topic :: Software Development :: Testing",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pytest-qt-4.0.2/src/pytest_qt.egg-info/PKG-INFO 
new/pytest-qt-4.1.0/src/pytest_qt.egg-info/PKG-INFO
--- old/pytest-qt-4.0.2/src/pytest_qt.egg-info/PKG-INFO 2021-06-14 
00:54:52.000000000 +0200
+++ new/pytest-qt-4.1.0/src/pytest_qt.egg-info/PKG-INFO 2022-06-23 
16:39:26.000000000 +0200
@@ -1,28 +1,27 @@
 Metadata-Version: 2.1
 Name: pytest-qt
-Version: 4.0.2
+Version: 4.1.0
 Summary: pytest support for PyQt and PySide applications
 Home-page: http://github.com/pytest-dev/pytest-qt
 Author: Bruno Oliveira
 Author-email: nicodde...@gmail.com
 License: MIT
 Keywords: pytest qt test unittest
-Platform: UNKNOWN
 Classifier: Development Status :: 5 - Production/Stable
 Classifier: Framework :: Pytest
 Classifier: Intended Audience :: Developers
 Classifier: License :: OSI Approved :: MIT License
 Classifier: Operating System :: OS Independent
 Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
 Classifier: Programming Language :: Python :: 3.8
 Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
 Classifier: Topic :: Desktop Environment :: Window Managers
 Classifier: Topic :: Software Development :: Quality Assurance
 Classifier: Topic :: Software Development :: Testing
 Classifier: Topic :: Software Development :: User Interfaces
-Requires-Python: >=3.6
+Requires-Python: >=3.7
 Provides-Extra: doc
 Provides-Extra: dev
 License-File: LICENSE
@@ -32,7 +31,7 @@
 =========
 
 pytest-qt is a `pytest`_ plugin that allows programmers to write tests
-for `PyQt5`_, `PyQt6`_, `PySide2`_ and `PyQt6`_ applications.
+for `PyQt5`_, `PyQt6`_, `PySide2`_ and `PySide6`_ applications.
 
 The main usage is to use the ``qtbot`` fixture, responsible for handling 
``qApp``
 creation as needed and provides methods to simulate user interaction,
@@ -103,7 +102,7 @@
 Requirements
 ============
 
-Since version 4.0.0, ``pytest-qt`` requires Python 3.6+.
+Since version 4.1.0, ``pytest-qt`` requires Python 3.7+.
 
 Works with either PySide6_, PySide2_, PyQt6_ or PyQt5_, picking whichever
 is available on the system, giving preference to the first one installed in
@@ -115,7 +114,7 @@
 - ``PyQt5``
 
 To force a particular API, set the configuration variable ``qt_api`` in your 
``pytest.ini`` file to
-``pyqt6``, ``pyside2``, ``pyqt6`` or ```pyqt5``:
+``pyqt5``, ``pyside2``, or ``pyqt6``:
 
 .. code-block:: ini
 
@@ -207,8 +206,9 @@
 
 **Powered by**
 
-.. |pycharm| image:: https://www.jetbrains.com/pycharm/docs/logo_pycharm.png
+.. |pycharm| image:: 
https://resources.jetbrains.com/storage/products/company/brand/logos/PyCharm.png
   :target: https://www.jetbrains.com/pycharm
+  :width: 400
 
 .. |pydev| image:: http://www.pydev.org/images/pydev_banner3.png
   :target: https://www.pydev.org
@@ -218,5 +218,3 @@
 |pydev|
 
 .. _tox: https://tox.readthedocs.io
-
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pytest-qt-4.0.2/src/pytest_qt.egg-info/SOURCES.txt 
new/pytest-qt-4.1.0/src/pytest_qt.egg-info/SOURCES.txt
--- old/pytest-qt-4.0.2/src/pytest_qt.egg-info/SOURCES.txt      2021-06-14 
00:54:52.000000000 +0200
+++ new/pytest-qt-4.1.0/src/pytest_qt.egg-info/SOURCES.txt      2022-06-23 
16:39:27.000000000 +0200
@@ -17,6 +17,7 @@
 docs/Makefile
 docs/changelog.rst
 docs/conf.py
+docs/debugging.rst
 docs/index.rst
 docs/intro.rst
 docs/logging.rst
@@ -31,6 +32,7 @@
 docs/virtual_methods.rst
 docs/wait_callback.rst
 docs/wait_until.rst
+docs/_static/button.png
 docs/_static/find_files_dialog.png
 src/pytest_qt.egg-info/PKG-INFO
 src/pytest_qt.egg-info/SOURCES.txt
@@ -53,6 +55,8 @@
 tests/test_exceptions.py
 tests/test_logging.py
 tests/test_modeltest.py
+tests/test_qtbot_pep8_aliases.py
 tests/test_qtest_proxies.py
+tests/test_screenshot.py
 tests/test_wait_signal.py
 tests/test_wait_until.py
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pytest-qt-4.0.2/src/pytest_qt.egg-info/entry_points.txt 
new/pytest-qt-4.1.0/src/pytest_qt.egg-info/entry_points.txt
--- old/pytest-qt-4.0.2/src/pytest_qt.egg-info/entry_points.txt 2021-06-14 
00:54:52.000000000 +0200
+++ new/pytest-qt-4.1.0/src/pytest_qt.egg-info/entry_points.txt 2022-06-23 
16:39:26.000000000 +0200
@@ -1,3 +1,2 @@
 [pytest11]
 pytest-qt = pytestqt.plugin
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pytest-qt-4.0.2/src/pytestqt/_version.py 
new/pytest-qt-4.1.0/src/pytestqt/_version.py
--- old/pytest-qt-4.0.2/src/pytestqt/_version.py        2021-06-14 
00:54:52.000000000 +0200
+++ new/pytest-qt-4.1.0/src/pytestqt/_version.py        2022-06-23 
16:39:25.000000000 +0200
@@ -1,5 +1,5 @@
 # coding: utf-8
 # file generated by setuptools_scm
 # don't change, don't track in version control
-version = '4.0.2'
-version_tuple = (4, 0, 2)
+__version__ = version = '4.1.0'
+__version_tuple__ = version_tuple = (4, 1, 0)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pytest-qt-4.0.2/src/pytestqt/exceptions.py 
new/pytest-qt-4.1.0/src/pytestqt/exceptions.py
--- old/pytest-qt-4.0.2/src/pytestqt/exceptions.py      2021-06-14 
00:54:48.000000000 +0200
+++ new/pytest-qt-4.1.0/src/pytestqt/exceptions.py      2022-06-23 
16:39:11.000000000 +0200
@@ -101,3 +101,14 @@
     """
 
     pass
+
+
+class ScreenshotError(Exception):
+    """
+    .. versionadded:: 4.1
+
+    Exception thrown by :method:`pytestqt.qtbot.QtBot.screenshot` if taking the
+    screenshot failed.
+    """
+
+    pass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pytest-qt-4.0.2/src/pytestqt/logging.py 
new/pytest-qt-4.1.0/src/pytestqt/logging.py
--- old/pytest-qt-4.0.2/src/pytestqt/logging.py 2021-06-14 00:54:48.000000000 
+0200
+++ new/pytest-qt-4.1.0/src/pytestqt/logging.py 2022-06-23 16:39:11.000000000 
+0200
@@ -10,7 +10,7 @@
 
 class QtLoggingPlugin:
     """
-    Pluging responsible for installing a QtMessageHandler before each
+    Plugin responsible for installing a QtMessageHandler before each
     test and augment reporting if the test failed with the messages captured.
     """
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pytest-qt-4.0.2/src/pytestqt/modeltest.py 
new/pytest-qt-4.1.0/src/pytestqt/modeltest.py
--- old/pytest-qt-4.0.2/src/pytestqt/modeltest.py       2021-06-14 
00:54:48.000000000 +0200
+++ new/pytest-qt-4.1.0/src/pytestqt/modeltest.py       2022-06-23 
16:39:11.000000000 +0200
@@ -1,6 +1,6 @@
 # This file is based on the original C++ qabstractitemmodeltester.cpp from:
 # 
http://code.qt.io/cgit/qt/qtbase.git/tree/src/testlib/qabstractitemmodeltester.cpp
-# Commit 4af292fe5158c2d19e8ab1351c71c3940c7f1032
+# Commit b6759ff81c1b6ecb7e18144db0b7c9c5884d7f24
 #
 # Licensed under the following terms:
 #
@@ -41,6 +41,7 @@
 #
 # $QT_END_LICENSE$
 
+import enum
 import collections
 
 from pytestqt.qt_compat import qt_api
@@ -52,6 +53,18 @@
 HAS_QT_TESTER = hasattr(qt_api.QtTest, "QAbstractItemModelTester")
 
 
+class _ChangeInFlight(enum.Enum):
+
+    COLUMNS_INSERTED = enum.auto()
+    COLUMNS_MOVED = enum.auto()
+    COLUMNS_REMOVED = enum.auto()
+    LAYOUT_CHANGED = enum.auto()
+    MODEL_RESET = enum.auto()
+    ROWS_INSERTED = enum.auto()
+    ROWS_MOVED = enum.auto()
+    ROWS_REMOVED = enum.auto()
+
+
 class ModelTester:
 
     """A tester for Qt's QAbstractItemModels."""
@@ -63,6 +76,7 @@
         self._remove = None
         self._changing = []
         self._qt_tester = None
+        self._change_in_flight = None
 
     def _debug(self, text):
         print("modeltest: " + text)
@@ -131,10 +145,32 @@
         # Special checks for changes
         
self._model.layoutAboutToBeChanged.connect(self._on_layout_about_to_be_changed)
         self._model.layoutChanged.connect(self._on_layout_changed)
+
+        # column operations
+        self._model.columnsAboutToBeInserted.connect(
+            self._on_columns_about_to_be_inserted
+        )
+        
self._model.columnsAboutToBeMoved.connect(self._on_columns_about_to_be_moved)
+        self._model.columnsAboutToBeRemoved.connect(
+            self._on_columns_about_to_be_removed
+        )
+        self._model.columnsInserted.connect(self._on_columns_inserted)
+        self._model.columnsMoved.connect(self._on_columns_moved)
+        self._model.columnsRemoved.connect(self._on_columns_removed)
+
+        # row operations
         
self._model.rowsAboutToBeInserted.connect(self._on_rows_about_to_be_inserted)
+        self._model.rowsAboutToBeMoved.connect(self._on_rows_about_to_be_moved)
         
self._model.rowsAboutToBeRemoved.connect(self._on_rows_about_to_be_removed)
         self._model.rowsInserted.connect(self._on_rows_inserted)
+        self._model.rowsMoved.connect(self._on_rows_moved)
         self._model.rowsRemoved.connect(self._on_rows_removed)
+
+        # reset
+        
self._model.modelAboutToBeReset.connect(self._on_model_about_to_be_reset)
+        self._model.modelReset.connect(self._on_model_reset)
+
+        # data
         self._model.dataChanged.connect(self._on_data_changed)
         self._model.headerDataChanged.connect(self._on_header_data_changed)
 
@@ -293,7 +329,7 @@
         # QModelIndex when asked for the parent of an invalid index.
         assert not self._parent(qt_api.QtCore.QModelIndex()).isValid()
 
-        if not self._has_children():
+        if self._model.rowCount() == 0 or self._column_count() == 0:
             return
 
         # Column 0                | Column 1      |
@@ -304,11 +340,12 @@
         # Common error test #1, make sure that a top level index has a parent
         # that is a invalid QModelIndex.
         top_index = self._model.index(0, 0, qt_api.QtCore.QModelIndex())
+        assert top_index.isValid()
         assert not self._parent(top_index).isValid()
 
         # Common error test #2, make sure that a second level index has a
         # parent that is the first level index.
-        if self._has_children(top_index):
+        if self._model.rowCount(top_index) > 0 and 
self._column_count(top_index) > 0:
             child_index = self._model.index(0, 0, top_index)
             assert self._parent(child_index) == top_index
 
@@ -317,7 +354,10 @@
         # Usually the second column shouldn't have children.
         if self._model.hasIndex(0, 1):
             top_index_1 = self._model.index(0, 1, qt_api.QtCore.QModelIndex())
-            if self._has_children(top_index) and 
self._has_children(top_index_1):
+            if (
+                self._model.rowCount(top_index) > 0
+                and self._model.rowCount(top_index_1) > 0
+            ):
                 child_index = self._model.index(0, 0, top_index)
                 assert child_index.isValid()
                 child_index_1 = self._model.index(0, 0, top_index_1)
@@ -447,7 +487,7 @@
 
     def _test_data(self):
         """Test model's implementation of data()"""
-        if not self._has_children():
+        if self._model.rowCount() == 0 or self._column_count() == 0:
             return
 
         # A valid index should have a valid QVariant data
@@ -511,11 +551,104 @@
             qt_api.QtCore.Qt.CheckState.Checked,
         ]
 
+    def _on_columns_about_to_be_inserted(self, parent, start, end):
+        assert self._change_in_flight is None
+        self._change_in_flight = _ChangeInFlight.COLUMNS_INSERTED
+        last_index = self._model.index(start - 1, 0, parent)
+        self._debug(
+            "columns about to be inserted: start {}, end {}, parent {}, "
+            "current count of parent {}, last before insertion {} {}".format(
+                start,
+                end,
+                self._modelindex_debug(parent),
+                self._model.rowCount(parent),
+                self._modelindex_debug(last_index),
+                self._model.data(last_index),
+            )
+        )
+
+    def _on_columns_inserted(self, parent, start, end):
+        assert self._change_in_flight == _ChangeInFlight.COLUMNS_INSERTED
+        self._change_in_flight = None
+        self._debug(
+            "columns inserted: start {}, end {}, parent {}, "
+            "current count of parent {}, ".format(
+                start,
+                end,
+                self._modelindex_debug(parent),
+                self._model.rowCount(parent),
+            )
+        )
+
+    def _on_columns_about_to_be_moved(
+        self, source_parent, source_start, source_end, dest_parent, dest_column
+    ):
+        assert self._change_in_flight is None
+        self._change_in_flight = _ChangeInFlight.COLUMNS_MOVED
+        self._debug(
+            "columns about to be moved: source start {}, source end {}, "
+            "source parent {}, destination parent {}, "
+            "destination column {}".format(
+                source_start,
+                source_end,
+                self._modelindex_debug(source_parent),
+                self._modelindex_debug(dest_parent),
+                dest_column,
+            )
+        )
+
+    def _on_columns_moved(
+        self, source_parent, source_start, source_end, dest_parent, dest_column
+    ):
+        assert self._change_in_flight == _ChangeInFlight.COLUMNS_MOVED
+        self._change_in_flight = None
+        self._debug(
+            "columns moved: source start {}, source end {}, "
+            "source parent {}, destination parent {}, "
+            "destination column {}".format(
+                source_start,
+                source_end,
+                self._modelindex_debug(source_parent),
+                self._modelindex_debug(dest_parent),
+                dest_column,
+            )
+        )
+
+    def _on_columns_about_to_be_removed(self, parent, start, end):
+        assert self._change_in_flight is None
+        self._change_in_flight = _ChangeInFlight.COLUMNS_REMOVED
+        last_index = self._model.index(start - 1, 0, parent)
+        self._debug(
+            "columns about to be removed: start {}, end {}, "
+            "parent {}, parent rowcount {}, last before removal {}".format(
+                start,
+                end,
+                self._modelindex_debug(parent),
+                self._model.rowCount(parent),
+                self._modelindex_debug(last_index),
+            )
+        )
+
+    def _on_columns_removed(self, parent, start, end):
+        assert self._change_in_flight == _ChangeInFlight.COLUMNS_REMOVED
+        self._change_in_flight = None
+        self._debug(
+            "columns removed: start {}, end {}, parent {}, parent rowcount 
{}".format(
+                start,
+                end,
+                self._modelindex_debug(parent),
+                self._model.rowCount(parent),
+            )
+        )
+
     def _on_rows_about_to_be_inserted(self, parent, start, end):
         """Store what is about to be inserted.
 
         This gets stored to make sure it actually happens in rowsInserted.
         """
+        assert self._change_in_flight is None
+        self._change_in_flight = _ChangeInFlight.ROWS_INSERTED
+
         last_index = self._model.index(start - 1, 0, parent)
         next_index = self._model.index(start, 0, parent)
         parent_rowcount = self._model.rowCount(parent)
@@ -541,6 +674,9 @@
 
     def _on_rows_inserted(self, parent, start, end):
         """Confirm that what was said was going to happen actually did."""
+        assert self._change_in_flight == _ChangeInFlight.ROWS_INSERTED
+        self._change_in_flight = None
+
         c = self._insert.pop()
         last_data = (
             self._model.data(self._model.index(start - 1, 0, parent))
@@ -591,25 +727,82 @@
         if next_data is not None:
             assert c.next == next_data
 
+    def _on_rows_about_to_be_moved(
+        self, source_parent, source_start, source_end, dest_parent, dest_row
+    ):
+        assert self._change_in_flight is None
+        self._change_in_flight = _ChangeInFlight.ROWS_MOVED
+        self._debug(
+            "rows about to be moved: source start {}, source end {}, "
+            "source parent {}, destination parent {}, "
+            "destination row {}".format(
+                source_start,
+                source_end,
+                self._modelindex_debug(source_parent),
+                self._modelindex_debug(dest_parent),
+                dest_row,
+            )
+        )
+
+    def _on_rows_moved(
+        self, source_parent, source_start, source_end, dest_parent, dest_row
+    ):
+        assert self._change_in_flight == _ChangeInFlight.ROWS_MOVED
+        self._change_in_flight = None
+        self._debug(
+            "rows moved: source start {}, source end {}, "
+            "source parent {}, destination parent {}, "
+            "destination row {}".format(
+                source_start,
+                source_end,
+                self._modelindex_debug(source_parent),
+                self._modelindex_debug(dest_parent),
+                dest_row,
+            )
+        )
+
     def _on_layout_about_to_be_changed(self):
+        assert self._change_in_flight is None
+        self._change_in_flight = _ChangeInFlight.LAYOUT_CHANGED
+
         for i in range(max(self._model.rowCount(), 100)):
             idx = qt_api.QtCore.QPersistentModelIndex(self._model.index(i, 0))
             self._changing.append(idx)
 
     def _on_layout_changed(self):
+        assert self._change_in_flight == _ChangeInFlight.LAYOUT_CHANGED
+        self._change_in_flight = None
+
         for p in self._changing:
             assert p == self._model.index(p.row(), p.column(), p.parent())
         self._changing = []
 
+    def _on_model_about_to_be_reset(self):
+        assert self._change_in_flight is None
+        self._change_in_flight = _ChangeInFlight.MODEL_RESET
+
+    def _on_model_reset(self):
+        assert self._change_in_flight == _ChangeInFlight.MODEL_RESET
+        self._change_in_flight = None
+
     def _on_rows_about_to_be_removed(self, parent, start, end):
         """Store what is about to be removed to make sure it actually happens.
 
         This gets stored to make sure it actually happens in rowsRemoved.
         """
+        assert self._change_in_flight is None
+        self._change_in_flight = _ChangeInFlight.ROWS_REMOVED
+
         parent_rowcount = self._model.rowCount(parent)
-        last_index = self._model.index(start - 1, 0, parent) if start > 0 else 
None
+        last_index = (
+            self._model.index(start - 1, 0, parent)
+            if start > 0 and self._column_count(parent) > 0
+            else None
+        )
         next_index = (
-            self._model.index(end + 1, 0, parent) if end < parent_rowcount - 1 
else None
+            self._model.index(end + 1, 0, parent)
+            if end < parent_rowcount - 1 and self._column_count(parent) > 0
+            else None
         )
 
         self._debug(
@@ -638,6 +831,9 @@
 
     def _on_rows_removed(self, parent, start, end):
         """Confirm that what was said was going to happen actually did."""
+        assert self._change_in_flight == _ChangeInFlight.ROWS_REMOVED
+        self._change_in_flight = None
+
         c = self._remove.pop()
         last_data = (
             self._model.data(self._model.index(start - 1, 0, c.parent))
@@ -713,7 +909,7 @@
     def _column_count(self, parent=qt_api.QtCore.QModelIndex()):
         """
         Workaround for the fact that ``columnCount`` is a private method in
-        QAbstractListModel/QAbstractTableModel subclasses.
+        QAbstractListModel subclasses.
         """
         if isinstance(self._model, qt_api.QtCore.QAbstractListModel):
             return 1 if parent == qt_api.QtCore.QModelIndex() else 0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pytest-qt-4.0.2/src/pytestqt/plugin.py 
new/pytest-qt-4.1.0/src/pytestqt/plugin.py
--- old/pytest-qt-4.0.2/src/pytestqt/plugin.py  2021-06-14 00:54:48.000000000 
+0200
+++ new/pytest-qt-4.1.0/src/pytestqt/plugin.py  2022-06-23 16:39:11.000000000 
+0200
@@ -1,3 +1,5 @@
+import warnings
+
 import pytest
 
 from pytestqt.exceptions import (
@@ -27,7 +29,30 @@
 
 
 @pytest.fixture(scope="session")
-def qapp(qapp_args, pytestconfig):
+def qapp_cls():
+    """
+    Fixture that provides the QApplication subclass to use.
+
+    You can override this fixture to use a custom QApplication subclass from
+    your application for tests:
+
+    .. code-block:: python
+
+       @pytest.fixture(scope="session")
+       def qapp_cls():
+           return myapp.Application
+
+    Or use a ``QCoreApplication`` if you want to test a non-gui Qt application:
+
+       @pytest.fixture(scope="session")
+       def qapp_cls():
+           return qt_api.QtCore.QCoreApplication
+    """
+    return qt_api.QtWidgets.QApplication
+
+
+@pytest.fixture(scope="session")
+def qapp(qapp_args, qapp_cls, pytestconfig):
     """
     Fixture that instantiates the QApplication instance that will be used by
     the tests.
@@ -38,12 +63,17 @@
     app = qt_api.QtWidgets.QApplication.instance()
     if app is None:
         global _qapp_instance
-        _qapp_instance = qt_api.QtWidgets.QApplication(qapp_args)
+        _qapp_instance = qapp_cls(qapp_args)
         name = pytestconfig.getini("qt_qapp_name")
         _qapp_instance.setApplicationName(name)
         return _qapp_instance
     else:
-        return app  # pragma: no cover
+        if not isinstance(app, qapp_cls):
+            warnings.warn(
+                f"Existing QApplication {app} is not an instance of qapp_cls: "
+                f"{qapp_cls}"
+            )
+        return app
 
 
 # holds a global QApplication instance created in the qapp fixture; keeping
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pytest-qt-4.0.2/src/pytestqt/qtbot.py 
new/pytest-qt-4.1.0/src/pytestqt/qtbot.py
--- old/pytest-qt-4.0.2/src/pytestqt/qtbot.py   2021-06-14 00:54:48.000000000 
+0200
+++ new/pytest-qt-4.1.0/src/pytestqt/qtbot.py   2022-06-23 16:39:11.000000000 
+0200
@@ -2,7 +2,7 @@
 import weakref
 import warnings
 
-from pytestqt.exceptions import TimeoutError
+from pytestqt.exceptions import TimeoutError, ScreenshotError
 from pytestqt.qt_compat import qt_api
 from pytestqt.wait_signal import (
     SignalBlocker,
@@ -39,6 +39,7 @@
     .. automethod:: waitExposed
     .. automethod:: waitForWindowShown
     .. automethod:: stop
+    .. automethod:: screenshot
     .. automethod:: wait
 
     **Signals and Events**
@@ -88,7 +89,7 @@
 
     .. staticmethod:: keyToAscii (key)
 
-        Auxiliary method that converts the given constant ot its equivalent 
ascii.
+        Auxiliary method that converts the given constant to its equivalent 
ascii.
 
         :param Qt.Key_* key: one of the constants for keys in the Qt namespace.
 
@@ -138,6 +139,17 @@
 
     def __init__(self, request):
         self._request = request
+        # pep8 aliases. Set here to automatically use implementations defined 
in sub-classes for alias creation
+        self.add_widget = self.addWidget
+        self.capture_exceptions = self.captureExceptions
+        self.wait_active = self.waitActive
+        self.wait_exposed = self.waitExposed
+        self.wait_for_window_shown = self.waitForWindowShown
+        self.wait_signal = self.waitSignal
+        self.wait_signals = self.waitSignals
+        self.assert_not_emitted = self.assertNotEmitted
+        self.wait_until = self.waitUntil
+        self.wait_callback = self.waitCallback
 
     def _should_raise(self, raising_arg):
         ini_val = self._request.config.getini("qt_default_raising")
@@ -593,6 +605,56 @@
 
     capture_exceptions = captureExceptions
 
+    def screenshot(self, widget, suffix="", region=None):
+        """
+        .. versionadded:: 4.1
+
+        Take a screenshot of the given widget and save it.
+
+        The file is saved in a test-specific directory using pytest's 
``tmp_path``
+        fixture. The filename is ensured to be unique using a counter, and 
contains the
+        ``objectName()`` of the widget if set, as well as its class name. A 
custom
+        ``suffix`` can be given to add to the generated name.
+
+        :param QWidget widget:
+            The widget to take a screenshot of.
+        :param str suffix:
+            An optional suffix to add to the filename.
+        :param QRect region:
+            The region of the widget to screeshot. By default, the entire 
widget is
+            contained.
+        :returns:
+            A ``pathlib.Path`` object with the taken screenshot.
+        :raises ScreenshotError: if taking the screenshot or saving the file 
failed.
+        """
+        pixmap = widget.grab() if region is None else widget.grab(region)
+        if pixmap.isNull():
+            raise ScreenshotError("Got null pixmap from Qt")
+
+        tmp_path = self._request.getfixturevalue("tmp_path")
+
+        parts = ["screenshot", widget.__class__.__name__]
+        name = widget.objectName()
+        if name:
+            parts.append(name)
+        if suffix:
+            parts.append(suffix)
+
+        for i in range(1, 500):
+            counter = [] if i == 1 else [str(i)]
+
+            path = tmp_path / ("_".join(parts + counter) + ".png")
+            if path.exists():
+                continue
+
+            ok = pixmap.save(str(path))
+            if not ok:
+                raise ScreenshotError(f"Saving to {path} failed")
+
+            return path
+
+        raise ScreenshotError(f"Failed to find unique filename, last try: 
{path}")
+
     @staticmethod
     def keyClick(*args, **kwargs):
         qt_api.QtTest.QTest.keyClick(*args, **kwargs)
@@ -645,35 +707,6 @@
     def mouseRelease(*args, **kwargs):
         qt_api.QtTest.QTest.mouseRelease(*args, **kwargs)
 
-    # pep-8 aliases
-
-    def add_widget(self, *args, **kwargs):
-        return self.addWidget(*args, **kwargs)
-
-    def wait_active(self, *args, **kwargs):
-        return self.waitActive(*args, **kwargs)
-
-    def wait_exposed(self, *args, **kwargs):
-        return self.waitExposed(*args, **kwargs)
-
-    def wait_for_window_shown(self, *args, **kwargs):
-        return self.waitForWindowShown(*args, **kwargs)
-
-    def wait_signal(self, *args, **kwargs):
-        return self.waitSignal(*args, **kwargs)
-
-    def wait_signals(self, *args, **kwargs):
-        return self.waitSignals(*args, **kwargs)
-
-    def assert_not_emitted(self, *args, **kwargs):
-        return self.assertNotEmitted(*args, **kwargs)
-
-    def wait_until(self, *args, **kwargs):
-        return self.waitUntil(*args, **kwargs)
-
-    def wait_callback(self, *args, **kwargs):
-        return self.waitCallback(*args, **kwargs)
-
 
 # provide easy access to exceptions to qtbot fixtures
 QtBot.SignalEmittedError = SignalEmittedError
@@ -721,7 +754,7 @@
 
     def __init__(self, method_name, adjective_name, widget, timeout):
         """
-        :param str method_name: name ot the ``QtTest`` method to call to check 
if widget is active/exposed.
+        :param str method_name: name to the ``QtTest`` method to call to check 
if widget is active/exposed.
         :param str adjective_name: "activated" or "exposed".
         :param widget:
         :param timeout:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pytest-qt-4.0.2/src/pytestqt/wait_signal.py 
new/pytest-qt-4.1.0/src/pytestqt/wait_signal.py
--- old/pytest-qt-4.0.2/src/pytestqt/wait_signal.py     2021-06-14 
00:54:48.000000000 +0200
+++ new/pytest-qt-4.1.0/src/pytestqt/wait_signal.py     2022-06-23 
16:39:11.000000000 +0200
@@ -626,7 +626,7 @@
     :ivar int timeout: maximum time to wait for the callback to be called.
 
     :ivar bool raising:
-        If :class:`TimeoutError` should be raised if a timeout occured.
+        If :class:`TimeoutError` should be raised if a timeout occurred.
 
         .. note:: contrary to the parameter of same name in
             :meth:`pytestqt.qtbot.QtBot.waitCallback`, this parameter does not
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pytest-qt-4.0.2/tests/test_basics.py 
new/pytest-qt-4.1.0/tests/test_basics.py
--- old/pytest-qt-4.0.2/tests/test_basics.py    2021-06-14 00:54:48.000000000 
+0200
+++ new/pytest-qt-4.1.0/tests/test_basics.py    2022-06-23 16:39:11.000000000 
+0200
@@ -42,6 +42,90 @@
     res.stdout.fnmatch_lines("*1 passed*")
 
 
+def test_qapp_cls(testdir):
+    testdir.makepyfile(
+        app="""
+        from pytestqt.qt_compat import qt_api
+
+        # Gets run before the plugin via conftest.py
+        qt_api.set_qt_api(None)
+
+        class CustomQApp(qt_api.QtWidgets.QApplication):
+            pass
+        """
+    )
+    testdir.makeconftest(
+        """
+        import pytest
+        from app import CustomQApp
+
+        @pytest.fixture(scope="session")
+        def qapp_cls():
+            return CustomQApp
+        """
+    )
+    testdir.makepyfile(
+        """
+        from app import CustomQApp
+
+        def test_cls(qapp):
+            assert isinstance(qapp, CustomQApp)
+    """
+    )
+    res = testdir.runpytest_subprocess()
+    res.stdout.fnmatch_lines("*1 passed*")
+
+
+def test_qapp_reuse_existing(testdir):
+    testdir.makepyfile(
+        """
+        from pytestqt.qt_compat import qt_api
+
+        app_instance = qt_api.QtWidgets.QApplication([])
+
+        def test_instances(qapp):
+            assert qapp is app_instance
+            assert qapp is qt_api.QtWidgets.QApplication.instance()
+        """
+    )
+    res = testdir.runpytest_subprocess()
+    res.stdout.fnmatch_lines("*1 passed*")
+
+
+def test_qapp_reuse_wrong_type(testdir):
+    testdir.makeconftest(
+        """
+        import pytest
+        from pytestqt.qt_compat import qt_api
+
+        # Gets run before the plugin
+        qt_api.set_qt_api(None)
+
+        class CustomQApp(qt_api.QtWidgets.QApplication):
+            pass
+
+        @pytest.fixture(scope="session")
+        def qapp_cls():
+            return CustomQApp
+        """
+    )
+    testdir.makepyfile(
+        """
+        from pytestqt.qt_compat import qt_api
+
+        app_instance = qt_api.QtWidgets.QApplication([])
+
+        def test_wrong_type(qapp):
+            pass
+        """
+    )
+    res = testdir.runpytest_subprocess()
+    res.stdout.fnmatch_lines(
+        "*Existing QApplication <*.QtWidgets.QApplication* at 0x*> is not an "
+        "instance of qapp_cls: <class 'conftest.CustomQApp'>"
+    )
+
+
 def test_key_events(qtbot, event_recorder):
     """
     Basic key events test.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pytest-qt-4.0.2/tests/test_modeltest.py 
new/pytest-qt-4.1.0/tests/test_modeltest.py
--- old/pytest-qt-4.0.2/tests/test_modeltest.py 2021-06-14 00:54:48.000000000 
+0200
+++ new/pytest-qt-4.1.0/tests/test_modeltest.py 2022-06-23 16:39:11.000000000 
+0200
@@ -1,3 +1,5 @@
+import sys
+
 import pytest
 
 from pytestqt.qt_compat import qt_api
@@ -54,6 +56,23 @@
     qtmodeltester.check(proxy, force_py=True)
 
 
+def test_standard_item_model_zero_columns(qtmodeltester):
+    model = qt_api.QtGui.QStandardItemModel()
+    qtmodeltester.check(model, force_py=True)
+
+    # QTBUG-92220
+    model.insertRows(0, 5)
+    model.removeRows(0, 5)
+
+    # QTBUG-92886
+    model.insertRows(0, 5)
+    model.removeRows(1, 2)
+
+    parent_index = model.index(0, 0)
+    model.insertRows(0, 5, parent_index)
+    model.insertRows(1, 2, parent_index)
+
+
 @pytest.mark.parametrize(
     "broken_role",
     [
@@ -94,12 +113,22 @@
     check_model(BrokenTypeModel(), should_pass=False)
 
 
+xfail_py311_pyside2 = pytest.mark.xfail(
+    sys.version_info[:2] == (3, 11) and qt_api.pytest_qt_api == "pyside2",
+    reason="Fails to OR mask flags",
+)
+
+
 @pytest.mark.parametrize(
     "role_value, should_pass",
     [
-        (qt_api.QtCore.Qt.AlignmentFlag.AlignLeft, True),
-        (qt_api.QtCore.Qt.AlignmentFlag.AlignRight, True),
-        (0xFFFFFF, False),
+        pytest.param(
+            qt_api.QtCore.Qt.AlignmentFlag.AlignLeft, True, 
marks=xfail_py311_pyside2
+        ),
+        pytest.param(
+            qt_api.QtCore.Qt.AlignmentFlag.AlignRight, True, 
marks=xfail_py311_pyside2
+        ),
+        pytest.param(0xFFFFFF, False, marks=xfail_py311_pyside2),
         ("foo", False),
         (object(), False),
     ],
@@ -242,7 +271,7 @@
 
 
 def test_overridden_methods(qtmodeltester):
-    """Make sure overriden methods of a model are actually run.
+    """Make sure overridden methods of a model are actually run.
 
     With a previous implementation of the modeltester using sip.cast, the 
custom
     implementations did never actually run.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pytest-qt-4.0.2/tests/test_qtbot_pep8_aliases.py 
new/pytest-qt-4.1.0/tests/test_qtbot_pep8_aliases.py
--- old/pytest-qt-4.0.2/tests/test_qtbot_pep8_aliases.py        1970-01-01 
01:00:00.000000000 +0100
+++ new/pytest-qt-4.1.0/tests/test_qtbot_pep8_aliases.py        2022-06-23 
16:39:11.000000000 +0200
@@ -0,0 +1,92 @@
+import inspect
+from unittest.mock import MagicMock
+
+import pytest
+
+from pytestqt.qtbot import QtBot
+
+
+def _format_pep_8(camel_case_name: str) -> str:
+    """
+    Helper that creates a pep8_compliant_method_name
+    from a given camelCaseMethodName.
+    """
+    return camel_case_name[0].lower() + "".join(
+        f"_{letter.lower()}" if letter.isupper() else letter.lower()
+        for letter in camel_case_name[1:]
+    )
+
+
+@pytest.mark.parametrize(
+    "expected, camel_case_input",
+    [
+        ("add_widget", "addWidget"),
+        ("wait_active", "waitActive"),
+        ("wait_exposed", "waitExposed"),
+        ("wait_for_window_shown", "waitForWindowShown"),
+        ("wait_signal", "waitSignal"),
+        ("wait_signals", "waitSignals"),
+        ("assert_not_emitted", "assertNotEmitted"),
+        ("wait_until", "waitUntil"),
+        ("wait_callback", "waitCallback"),
+    ],
+)
+def test_format_pep8(expected: str, camel_case_input: str):
+    assert _format_pep_8(camel_case_input) == expected
+
+
+def test_pep8_aliases(qtbot):
+    """
+    Test that defined PEP8 aliases actually refer to the correct 
implementation.
+    Only check methods that have such an alias defined.
+    """
+    for name, func in inspect.getmembers(qtbot, inspect.ismethod):
+        if name != name.lower():
+            pep8_name = _format_pep_8(name)
+            if hasattr(qtbot, pep8_name):
+                # Found a PEP8 alias.
+                assert (
+                    getattr(qtbot, name).__func__ is getattr(qtbot, 
pep8_name).__func__
+                )
+
+
+def 
generate_test_cases_for_test_subclass_of_qtbot_has_overwritten_pep8_aliases():
+    """
+    For each PEP8 alias found in QtBot, yields a test case consisting of
+    a QtBot subclass that has the alias pair???s camelCase implementation
+    overwritten with a MagicMock.
+
+    Yields tuples (subclass, camelCaseMethodName, pep8_method_name_alias)
+    """
+    for name, func in inspect.getmembers(QtBot, inspect.isfunction):
+        if name != name.lower():
+            subclass_logic_mock = MagicMock()
+            pep8_name = _format_pep_8(name)
+            if hasattr(QtBot, pep8_name):
+                # Found a PEP8 alias.
+                methods = QtBot.__dict__.copy()
+                # Only overwrite the camelCase method
+                methods[name] = subclass_logic_mock
+                sub_class = type("QtBotSubclass", (QtBot,), methods)
+                yield sub_class, name, pep8_name
+
+
+@pytest.mark.parametrize(
+    "qtbot_subclass, method_name, pep8_name",
+    
generate_test_cases_for_test_subclass_of_qtbot_has_overwritten_pep8_aliases(),
+)
+def test_subclass_of_qtbot_has_overwritten_pep8_aliases(
+    qtbot_subclass, method_name: str, pep8_name: str
+):
+    """
+    Test that subclassing QtBot does not create surprises,
+    by checking that the PEP8 aliases follow overwritten
+    camelCase methods.
+    """
+    instance: QtBot = qtbot_subclass(MagicMock())
+    assert isinstance(getattr(instance, method_name), MagicMock)
+    assert isinstance(getattr(instance, pep8_name), MagicMock)
+    # Now call the pep8_name_method and check that the subclass???s
+    # camelCaseMethod implementation was actually called.
+    getattr(instance, pep8_name)()
+    getattr(instance, method_name).assert_called()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pytest-qt-4.0.2/tests/test_screenshot.py 
new/pytest-qt-4.1.0/tests/test_screenshot.py
--- old/pytest-qt-4.0.2/tests/test_screenshot.py        1970-01-01 
01:00:00.000000000 +0100
+++ new/pytest-qt-4.1.0/tests/test_screenshot.py        2022-06-23 
16:39:11.000000000 +0200
@@ -0,0 +1,92 @@
+import pathlib
+
+import pytest
+
+from pytestqt.qt_compat import qt_api
+from pytestqt.exceptions import ScreenshotError
+
+
+@pytest.fixture
+def widget(qtbot):
+    w = qt_api.QtWidgets.QWidget()
+    qtbot.addWidget(w)
+    w.setAttribute(qt_api.QtCore.Qt.WidgetAttribute.WA_StyledBackground)
+    w.setStyleSheet("background-color: magenta")
+    return w
+
+
+def test_basic(qtbot, widget):
+    path = qtbot.screenshot(widget)
+    assert path.exists()
+
+    pixmap = qt_api.QtGui.QPixmap()
+    assert pixmap.load(str(path))
+
+    image = pixmap.toImage()
+    color = image.pixelColor(image.rect().center())
+    assert (color.red(), color.green(), color.blue()) == (255, 0, 255)
+
+
+def test_region(qtbot, widget):
+    region = qt_api.QtCore.QRect(0, 0, 25, 25)
+    path = qtbot.screenshot(widget, region=region)
+
+    pixmap = qt_api.QtGui.QPixmap()
+    assert pixmap.load(str(path))
+    assert pixmap.rect() == region
+
+
+def test_filename_class(qtbot, widget):
+    path = qtbot.screenshot(widget)
+    assert path.name == "screenshot_QWidget.png"
+
+
+def test_filename_objectname(qtbot, widget):
+    widget.setObjectName("shotgun")
+    path = qtbot.screenshot(widget)
+    assert path.name == "screenshot_QWidget_shotgun.png"
+
+
+def test_filename_suffix(qtbot, widget):
+    path = qtbot.screenshot(widget, suffix="before")
+    assert path.name == "screenshot_QWidget_before.png"
+
+
+def test_filename_both(qtbot, widget):
+    widget.setObjectName("shotgun")
+    path = qtbot.screenshot(widget, suffix="before")
+    assert path.name == "screenshot_QWidget_shotgun_before.png"
+
+
+def test_filename_endless(qtbot, widget, monkeypatch):
+    monkeypatch.setattr(pathlib.Path, "exists", lambda _self: True)
+    with pytest.raises(ScreenshotError, match="Failed to find unique 
filename"):
+        qtbot.screenshot(widget, suffix="before")
+
+
+def test_filename_invalid(qtbot, widget):
+    with pytest.raises(ScreenshotError, match="Saving to .* failed"):
+        qtbot.screenshot(widget, suffix=r"invalid/path\everywhere")
+
+
+def test_folder(qtbot, tmp_path, widget):
+    path = qtbot.screenshot(widget)
+    assert path.parent == tmp_path
+
+
+@pytest.mark.parametrize(
+    "existing, expected",
+    [
+        (["QLineEdit"], "QWidget"),
+        (["QWidget"], "QWidget_2"),
+        (["QWidget_2"], "QWidget"),
+        (["QWidget", "QWidget_2"], "QWidget_3"),
+    ],
+)
+def test_filename_dedup(qtbot, widget, tmp_path, existing, expected):
+    for name in existing:
+        path = tmp_path / f"screenshot_{name}.png"
+        path.touch()
+
+    path = qtbot.screenshot(widget)
+    assert path.name == f"screenshot_{expected}.png"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pytest-qt-4.0.2/tox.ini new/pytest-qt-4.1.0/tox.ini
--- old/pytest-qt-4.0.2/tox.ini 2021-06-14 00:54:48.000000000 +0200
+++ new/pytest-qt-4.1.0/tox.ini 2022-06-23 16:39:11.000000000 +0200
@@ -1,5 +1,5 @@
 [tox]
-envlist = py{36,37,38}-pyqt5, py{36,37,38}-pyside2, py{37,38}-pyside6, 
py{36,37,38}-pyqt6, linting
+envlist = py{37,38,39,310}-{pyqt5,pyside2,pyside6,pyqt6}, linting
 
 [testenv]
 deps=

Reply via email to