### Set up xvfb and window manager for CI (Travis-CI example) Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/troubleshooting.md For CI environments like Travis-CI, xvfb and a window manager (herbstluftwm) are necessary for UI events to work correctly. This configuration ensures xvfb is installed, started, and the DISPLAY variable is set. ```yaml sudo: required before_install: - sudo apt-get update - sudo apt-get install -y xvfb herbstluftwm install: - "export DISPLAY=:99.0" - "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1920x1200x24 -ac +extension GLX +render -noreset" - sleep 3 before_script: - "herbstluftwm &" - sleep 1 ``` -------------------------------- ### Install and Start xvfb on Linux for Qt GUI Tests Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/troubleshooting.md Add this step to your CI pipeline before running your pytest-qt-based Qt6 tests with pytest. This installs necessary Xvfb and Qt-related libraries and starts the Xvfb server. ```yaml script: | sudo apt update sudo apt-get install -y xvfb libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xinput0 libxcb-xfixes0 libxcb-shape0 libglib2.0-0 libgl1-mesa-dev sudo apt-get install -y '^libxcb.*-dev' libx11-xcb-dev libglu1-mesa-dev libxrender-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev sudo apt-get install -y x11-utils /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1920x1200x24 -ac +extension GLX displayName: 'Install and start xvfb and other dependencies on Linux for Qt GUI tests' condition: and(succeededOrFailed(), eq(variables['Agent.OS'], 'Linux')) ``` -------------------------------- ### Install and Configure pre-commit Hooks Source: https://github.com/pytest-dev/pytest-qt/blob/master/README.rst Install pre-commit for local development to ensure code quality and consistency. Run pre-commit install to set up the necessary hooks for pre-commit checks. ```bash pip install pre-commit pre-commit install ``` -------------------------------- ### GitLab CI/CD configuration for Qt6 with pytest-qt Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/troubleshooting.md This GitLab CI/CD configuration sets up a Python 3.11 environment for testing Qt6 applications. It installs required Qt libraries, starts xvfb, and installs pytest-qt. Ensure your runner is compatible and the image includes necessary base packages. ```yaml variables: DISPLAY: ':99.0' test: stage: test image: python:3.11 script: - apt update - apt install -y libgl1 libegl1 libdbus-1-3 libxcb-cursor0 libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 x11-utils xvfb - /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1920x1200x24 -ac +extension GLX - python -m pip install pyqt6 pytest-qt - python -m pytest test.py ``` -------------------------------- ### GitHub Actions CI configuration for pytest-qt Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/troubleshooting.md This GitHub Actions workflow configures a Linux environment for running pytest-qt. It installs necessary XCB libraries, sets the DISPLAY variable, and starts xvfb. Ensure you are using an appropriate checkout and Python setup action. ```yaml name: my qt ci in github actions on: [push, pull_request] jobs: Linux: runs-on: ${{ matrix.os }} strategy: matrix: os : [ubuntu-latest] python: ["3.10"] env: DISPLAY: ':99.0' steps: - name: get repo uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} - name: setup ${{ matrix.os }} run: | sudo apt install libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 x11-utils /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1920x1200x24 -ac +extension GLX ``` -------------------------------- ### Install pytest-qt with Development Extras Source: https://github.com/pytest-dev/pytest-qt/blob/master/README.rst Install pytest-qt in editable mode with development extras to set up your environment for contributing. This includes necessary tools for development and testing. ```bash pip install --editable .[dev] ``` -------------------------------- ### Basic Test Setup with qtbot and tmp_path Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/tutorial.md Initializes a test function using the `qtbot` fixture for widget control and `tmp_path` for temporary file creation. The `qtbot` fixture automatically creates a `QApplication` instance. ```python def test_basic_search(qtbot, tmp_path): """ test to ensure basic find files functionality is working. """ tmp_path.joinpath("video1.avi").touch() tmp_path.joinpath("video1.srt").touch() tmp_path.joinpath("video2.avi").touch() tmp_path.joinpath("video2.srt").touch() ``` -------------------------------- ### Custom Application Fixture Source: https://context7.com/pytest-dev/pytest-qt/llms.txt Example of using a custom QApplication fixture. Ensure your custom application class is correctly set up. ```python from unittest import mock def test_custom_app(qapp): # qapp is now an instance of MyApp qapp.finish_startup() assert qapp.startup_complete is True ``` -------------------------------- ### Basic Test Example Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/wait_until.md This example demonstrates a test that might fail due to the asynchronous nature of GUI events. ```python def test_validate(qtbot): window = MyWindow() window.edit.setText("not a number") # after focusing, should update status label window.edit.setFocus() assert window.status.text() == "Please input a number" ``` -------------------------------- ### Install pytest-qt and Override Binding Source: https://context7.com/pytest-dev/pytest-qt/llms.txt Install pytest-qt using pip or conda. The Qt binding can be overridden at runtime using the PYTEST_QT_API environment variable. ```bash pip install pytest-qt # Or via conda: conda install -c conda-forge pytest-qt # Override binding at runtime without editing pytest.ini: PYTEST_QT_API=pyside6 pytest tests/ ``` -------------------------------- ### TimeoutError Example Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/wait_until.md This example illustrates the output when qtbot.waitUntil times out. It shows the last assertion error and the TimeoutError message. ```text _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ def check_label(): > assert window.status.text() == "Please input a number" E AssertionError: assert 'OK' == 'Please input a number' E - OK E + Please input a number _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ > qtbot.waitUntil(check_label) E pytestqt.exceptions.TimeoutError: waitUntil timed out in 1000 milliseconds ``` -------------------------------- ### Capture Qt Warnings in a Failing Test Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/logging.md This example demonstrates how a Qt warning message is captured and displayed when a test fails. Ensure you have `pytest-qt` installed and configured. ```python from pytestqt.qt_compat import qt_api def do_something(): qt_api.qWarning("this is a WARNING message") def test_foo(): do_something() assert 0 ``` -------------------------------- ### Configure pytest-qt Binding and Logging Source: https://context7.com/pytest-dev/pytest-qt/llms.txt Configure the Qt API binding, logging levels, and ignore patterns in pytest.ini. This setup ensures consistent behavior and error reporting across tests. ```ini # pytest.ini [pytest] qt_api = pyqt6 # pyside6 | pyqt6 | pyqt5 qt_default_raising = true # raise TimeoutError on signal/callback timeouts qt_log_level_fail = WARNING # fail tests that emit WARNING or above Qt messages qt_log_format = {rec.when} {rec.type_name}: {rec.message} qt_log_ignore = WM_DESTROY.*sent QFont::.*cannot be made qt_no_exception_capture = 0 # set to 1 to disable virtual-method exception hook qt_qapp_name = my-app-tests # custom QApplication.applicationName() ``` -------------------------------- ### Example of a failing test due to a critical Qt log message Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/logging.md This output shows a test failing because a `QtCriticalMsg` was captured, as configured by `qt_log_level_fail`. ```bash >pytest test.py --color=no -q F ================================== FAILURES =================================== __________________________________ test_foo ___________________________________ test.py:5: Failure: Qt messages with level CRITICAL or above emitted ---------------------------- Captured Qt messages ----------------------------- QtCriticalMsg: WM_PAINT failed ``` -------------------------------- ### Test that passes with ignored Qt log messages Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/logging.md This example demonstrates how `qt_log_ignore` prevents specific messages from causing test failures. The test passes because the critical messages match the ignore patterns. ```bash pytest test.py --color=no -q . 1 passed in 0.01 seconds ``` -------------------------------- ### Widget Instantiation and Registration with qtbot Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/tutorial.md Creates an instance of the `Window` widget, displays it, and registers it with the `qtbot` fixture. Registering the widget with `qtbot` ensures it is properly managed during the test. ```python window = Window() window.show() qtbot.addWidget(window) ``` -------------------------------- ### Simulate keyboard and mouse input with qtbot Source: https://context7.com/pytest-dev/pytest-qt/llms.txt Provides low-level QTest-based methods for simulating raw keyboard and mouse events directly on widgets. Use for typing strings, pressing keys with modifiers, and clicking/double-clicking with mouse buttons. ```python def test_keyboard_and_mouse_input(qtbot): widget = qt_api.QtWidgets.QLineEdit() qtbot.addWidget(widget) widget.show() # Type a full string into the line edit qtbot.keyClicks(widget, "Hello, world!") assert widget.text() == "Hello, world!" # Press a single key with a modifier qtbot.keyClick( widget, qt_api.QtCore.Qt.Key.Key_A, modifier=qt_api.QtCore.Qt.KeyboardModifier.ControlModifier, ) # Ctrl+A – selects all text # Type a named key qtbot.keyClick(widget, qt_api.QtCore.Qt.Key.Key_Delete) assert widget.text() == "" # Mouse click at the center of the widget qtbot.mouseClick( widget, qt_api.QtCore.Qt.MouseButton.LeftButton, pos=qt_api.QtCore.QPoint(widget.width() // 2, widget.height() // 2), ) # Double-click qtbot.mouseDClick(widget, qt_api.QtCore.Qt.MouseButton.LeftButton) ``` -------------------------------- ### Using the Custom Dialog Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/note_dialogs.md Demonstrates the client-side usage of the custom dialog's 'ask' method. This shows how to invoke the dialog and handle its returned values. ```python name, age = AskNameAndAgeDialog.ask("Enter name and age because of bananas:", parent) if name is not None: # use name and age for bananas ... ``` -------------------------------- ### Override a Virtual Method in PyQt Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/virtual_methods.md This is a basic example of overriding the `mouseReleaseEvent` virtual method in a QWidget subclass. Ensure you import `QWidget` from your Qt binding. ```python from PySide6.QtWidgets import QWidget class MyWidget(QWidget): # mouseReleaseEvent def mouseReleaseEvent(self, ev): print(f"mouse released at: {ev.pos()}") ``` -------------------------------- ### Test QStandardItemModel with qtmodeltester Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/modeltester.md Instantiate your model, populate it with items, and then call qtmodeltester.check to verify its integrity. Failures will pinpoint the specific issue. ```python def test_standard_item_model(qtmodeltester): model = QStandardItemModel() items = [QStandardItem(str(i)) for i in range(4)] model.setItem(0, 0, items[0]) model.setItem(0, 1, items[1]) model.setItem(1, 0, items[2]) model.setItem(1, 1, items[3]) qtmodeltester.check(model) ``` -------------------------------- ### Basic Widget Test with qtbot Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/intro.md Use the `qtbot` fixture to interact with Qt widgets in your tests. It handles `qApp` creation and widget registration. ```python def test_hello(qtbot): widget = HelloWidget() qtbot.addWidget(widget) # Click the greet button and make sure the appropriate label is updated. widget.button_greet.click() assert widget.greet_label.text() == "Hello!" ``` -------------------------------- ### Define Custom QApplication Class Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/qapplication.md Define a custom QApplication class to add specific attributes or methods for testing. Ensure it inherits from the appropriate Qt API class. ```python from pytestqt.qt_compat import qt_api class CustomQApplication(qt_api.QtWidgets.QApplication): def __init__(self, *argv): super().__init__(*argv) self.custom_attr = "xxx" def custom_function(self): pass ``` -------------------------------- ### Run Tests with tox Source: https://github.com/pytest-dev/pytest-qt/blob/master/README.rst Execute tests using tox with specified environments. This command runs tests for different Python versions and Qt API combinations. ```bash tox -e py-pyside6,py-pyqt5 ``` -------------------------------- ### Test PyQt/PySide Widgets with qtbot Fixture Source: https://github.com/pytest-dev/pytest-qt/blob/master/README.rst Use the qtbot fixture to simulate user interactions with Qt widgets and assert expected behavior. It handles qApp creation and provides methods for mouse clicks, key presses, and more. ```python def test_hello(qtbot): widget = HelloWidget() qtbot.addWidget(widget) # click in the Greet button and make sure it updates the appropriate label qtbot.mouseClick(widget.button_greet, qt_api.QtCore.Qt.MouseButton.LeftButton) assert widget.greet_label.text() == "Hello!" ``` -------------------------------- ### Take widget screenshot with qtbot.screenshot Source: https://context7.com/pytest-dev/pytest-qt/llms.txt Captures the widget's current visual state as a PNG file. Supports specifying a region to crop. Returns a pathlib.Path to the saved image. ```python def test_widget_renders_correctly(qtbot): widget = qt_api.QtWidgets.QLabel("Screenshot test") widget.resize(200, 100) qtbot.addWidget(widget) with qtbot.waitExposed(widget): widget.show() path = qtbot.screenshot(widget, suffix="initial") assert path.exists() assert path.suffix == ".png" # e.g. /tmp/pytest-xxx/test_widget_renders_correctly0/screenshot_QLabel_initial.png # Screenshot a specific region (QRect) region = qt_api.QtCore.QRect(0, 0, 100, 50) cropped = qtbot.screenshot(widget, suffix="cropped", region=region) assert cropped.exists() ``` -------------------------------- ### Take Screenshot of Widget with qtbot.screenshot() Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/debugging.md Use `qtbot.screenshot()` to capture an image of a widget. The screenshot is saved to a temporary directory, and its path is returned. This is helpful for visually debugging widget appearance. ```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 ``` -------------------------------- ### Mock QApplication.exit() Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/qapplication.md Alternatively, use the mock package to patch QApplication.exit() and assert that it was called. This approach verifies the call without altering the application's behavior. ```python def test_exit_button(qtbot): with mock.patch.object(QApplication, "exit"): button = get_app_exit_button() button.click() assert QApplication.exit.call_count == 1 ``` -------------------------------- ### Mocking QMessageBox.question with monkeypatch Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/note_dialogs.md Use monkeypatch to mock QMessageBox.question for testing dialog interactions. This approach replaces the actual dialog with a predefined response. ```python def test_Qt(qtbot, monkeypatch): simple = Simple() qtbot.addWidget(simple) monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes) simple.query() assert simple.answer ``` -------------------------------- ### Use qtbot.waitSignal to test signals Source: https://github.com/pytest-dev/pytest-qt/blob/master/CHANGELOG.rst Use `qtbot.waitSignal` to block until signals are emitted or a timeout occurs. Connect additional signals to the blocker and check `signal_triggered` after the block. ```python def test_long_computation(qtbot): app = Application() # Watch for the app.worker.finished signal, then start the worker. with qtbot.waitSignal(app.worker.finished, timeout=10000) as blocker: blocker.connect(app.worker.failed) # Can add other signals to blocker app.worker.start() # Test will wait here until either signal is emitted, or 10 seconds has elapsed assert blocker.signal_triggered # Assuming the work took less than 10 seconds assert_application_results(app) ``` -------------------------------- ### Validate QAbstractItemModel implementations with qtmodeltester Source: https://context7.com/pytest-dev/pytest-qt/llms.txt Runs Qt's model-testing suite against QAbstractItemModel subclasses to check consistency invariants. Can force the use of the pure-Python implementation. ```python def test_custom_tree_model(qtmodeltester): from PyQt6.QtGui import QStandardItemModel, QStandardItem model = QStandardItemModel() # Build a 2×2 grid with a nested child for row in range(2): parent_item = QStandardItem(f"Row {row}") for col in range(2): parent_item.appendRow(QStandardItem(f"Child {row},{col}")) model.appendRow(parent_item) # Runs the full Qt model-consistency test suite (uses C++ tester if available) qtmodeltester.check(model) # Force the pure-Python implementation instead model2 = QStandardItemModel() model2.appendRow(QStandardItem("only item")) qtmodeltester.check(model2, force_py=True) ``` -------------------------------- ### Interacting with QComboBox Widgets Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/tutorial.md Demonstrates how to interact with `QComboBox` widgets by clearing existing items and setting a new current text. Prefer using widget's own methods like `setCurrentText` for reliable interaction. ```python window.fileComboBox.clear() window.fileComboBox.setCurrentText("*.avi") window.directoryComboBox.clear() window.directoryComboBox.setCurrentText(str(tmp_path)) ``` -------------------------------- ### Wait for Widget to be Exposed Source: https://context7.com/pytest-dev/pytest-qt/llms.txt Use qtbot.waitExposed as a context manager to block until a widget is visible on screen. This is crucial for ensuring proper rendering, especially on X11 systems. It raises a TimeoutError if the widget does not become exposed within the specified timeout. ```python def test_splash_screen(qtbot): splash = qt_api.QtWidgets.QSplashScreen() qtbot.addWidget(splash) # Block until the splash is actually visible on screen (important on X11) with qtbot.waitExposed(splash, timeout=3000): splash.show() assert splash.isVisible() ``` -------------------------------- ### Configure tox to pass DISPLAY and XAUTHORITY Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/troubleshooting.md When tox runs pytest-qt, it might fail if the DISPLAY and XAUTHORITY environment variables are not passed to the test environment. Add these to your tox.ini to resolve this. This solution requires a GUI to be present. ```ini [testenv] passenv = DISPLAY XAUTHORITY ``` -------------------------------- ### Force Python implementation of QAbstractItemModelTester Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/modeltester.md To use the Python implementation of the tester instead of the C++ one (available from PyQt5 5.11), pass force_py=True to the check method. ```python qtmodeltester.check(model, force_py=True) ``` -------------------------------- ### Wait for Multiple Signals with qtbot.waitSignals Source: https://context7.com/pytest-dev/pytest-qt/llms.txt Use `qtbot.waitSignals` to block until a list of signals are emitted. Supports 'none' for any order and 'strict' for a specific sequence. Ensure signals are correctly passed as a list. ```python def test_all_workers_complete(qtbot): workers = [Worker(i) for i in range(3)] # Wait for all finished signals; order doesn't matter (default) with qtbot.waitSignals( [w.finished for w in workers], timeout=15_000, order="none", ): for w in workers: w.start() # Strict order: signals must fire exactly in the listed sequence def check_50(pct): return pct == 50 def check_100(pct): return pct == 100 with qtbot.waitSignals( [workers[0].progress, workers[0].progress, workers[0].finished], check_params_cbs=[check_50, check_100, None], order="strict", timeout=10_000, ) as blocker: workers[0].run() # blocker.all_signals_and_args contains SignalAndArgs objects in received order print(blocker.all_signals_and_args) ``` -------------------------------- ### Set Environment Variables for CI Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/troubleshooting.md Set these environment variables for the job that runs tests. DISPLAY is needed for pytest-qt not to crash. PYTHONFAULTHANDLER is enabled in case UI tests crash without meaningful error messages. ```yaml variables: DISPLAY: ':99.0' PYTHONFAULTHANDLER: 'enabled' ``` -------------------------------- ### qtbot.waitExposed Source: https://context7.com/pytest-dev/pytest-qt/llms.txt A context manager that blocks until the given widget is exposed (rendered on screen). It raises `qtbot.TimeoutError` if the widget does not become exposed within the specified timeout. ```APIDOC ## qtbot.waitExposed ### Description Context manager that blocks until the given widget is exposed (rendered on screen) or raises `qtbot.TimeoutError` after the timeout. ### Parameters - **widget** (QWidget) - The widget to wait for. - **timeout** (int, optional) - The maximum time in milliseconds to wait for the widget to be exposed. Defaults to a configured value. ### Request Example ```python def test_splash_screen(qtbot): splash = qt_api.QtWidgets.QSplashScreen() qtbot.addWidget(splash) # Block until the splash is actually visible on screen (important on X11) with qtbot.waitExposed(splash, timeout=3000): splash.show() assert splash.isVisible() ``` ``` -------------------------------- ### Using waitUntil with Boolean Callback Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/wait_until.md This snippet demonstrates using qtbot.waitUntil with a lambda function that returns a boolean. This approach is often terser but provides a less specific failure message on timeout. ```python def test_validate(qtbot): window = MyWindow() window.edit.setText("not a number") # after focusing, should update status label window.edit.setFocus() qtbot.waitUntil(lambda: window.edit.hasFocus()) assert window.status.text() == "Please input a number" ``` -------------------------------- ### Wait for Window to be Active Source: https://context7.com/pytest-dev/pytest-qt/llms.txt Use qtbot.waitActive as a context manager to block until a widget becomes the active window. This is useful for testing asynchronous window managers and ensuring focus is correctly set. A timeout can be specified to prevent indefinite blocking. ```python def test_main_window_focus(qtbot): window = qt_api.QtWidgets.QMainWindow() qtbot.addWidget(window) with qtbot.waitActive(window, timeout=5000): window.show() window.raise_() window.activateWindow() assert window.isActiveWindow() ``` -------------------------------- ### Using the qtlog Fixture to Check Log Records Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/logging.md The `qtlog` fixture allows you to inspect captured Qt log messages within your tests. `qtlog.records` provides a list of `Record` instances for assertion. ```python def do_something(): qWarning('this is a WARNING message') def test_foo(qtlog): do_something() emitted = [(m.type, m.message.strip()) for m in qtlog.records] assert emitted == [(QtWarningMsg, 'this is a WARNING message')] ``` -------------------------------- ### Override QApplication class with qapp_cls fixture Source: https://context7.com/pytest-dev/pytest-qt/llms.txt Supply a custom QApplication subclass for tests that exercise application-level behavior by overriding the `qapp_cls` fixture in `conftest.py`. ```python # conftest.py import pytest from pytestqt.qt_compat import qt_api class MyApp(qt_api.QtWidgets.QApplication): def __init__(self, *args): super().__init__(*args) self.startup_complete = False def finish_startup(self): self.startup_complete = True @pytest.fixture(scope="session") def qapp_cls(): return MyApp ``` -------------------------------- ### Configure Qt API in pytest.ini Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/intro.md Specify which Qt binding to use by setting the `qt_api` variable in your `pytest.ini` file. This configuration is overridden by the `PYTEST_QT_API` environment variable. ```ini [pytest] qt_api=pyqt5 ``` -------------------------------- ### Wait for Signal Emission Source: https://context7.com/pytest-dev/pytest-qt/llms.txt Use qtbot.waitSignal as a context manager to block test execution until a specific Qt signal is emitted or a timeout occurs. It can optionally validate signal parameters using a callback function and allows connecting to other signals (like failure) to unblock execution. ```python def test_worker_finishes(qtbot): app = MyApplication() # Basic usage: wait up to 10 s for the finished signal with qtbot.waitSignal(app.worker.finished, timeout=10_000) as blocker: blocker.connect(app.worker.failed) # also unblock on failure signal app.worker.start() # Execution reaches here only after finished or failed is emitted assert app.worker.result == "ok" # Validate signal parameters with check_params_cb def is_complete(progress: int) -> bool: return progress == 100 with qtbot.waitSignal( app.worker.progress, timeout=5000, check_params_cb=is_complete ) as blocker: app.worker.run_step() assert blocker.args == [100] # arguments emitted with the signal assert blocker.signal_triggered # False if timeout was reached (raising=False) ``` -------------------------------- ### Override qapp_cls Fixture for Custom QApplication Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/qapplication.md Configure your conftest.py to use a custom QApplication class by overriding the qapp_cls fixture. This ensures the 'qapp' fixture uses your custom class. ```python @pytest.fixture(scope="session") def qapp_cls(): return CustomQApplication ``` -------------------------------- ### Simulating Button Click Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/tutorial.md Simulates a user clicking a button on the widget. This is a direct interaction with the widget's `click` method. ```python window.findButton.click() ``` -------------------------------- ### Register Widget with qtbot Source: https://context7.com/pytest-dev/pytest-qt/llms.txt Register a widget with qtbot to ensure it is automatically closed and cleaned up at the end of the test. An optional function can be provided to execute just before the widget is closed. ```python from pytestqt.qt_compat import qt_api def test_widget_lifecycle(qtbot): widget = qt_api.QtWidgets.QLabel("Hello pytest-qt") # Register widget – it will be closed automatically after the test qtbot.addWidget(widget) # Optional: run a function just before the widget is closed log = [] widget2 = qt_api.QtWidgets.QPushButton("Click me") qtbot.addWidget(widget2, before_close_func=lambda w: log.append(f"closing: {w}")) widget.show() assert widget.text() == "Hello pytest-qt" # widget and widget2 are closed by qtbot after the test regardless of pass/fail ``` -------------------------------- ### Wait for Multiple Signals Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/signals.md Use `qtbot.waitSignals` with a list of signals to block until all specified signals are emitted. It also supports the `raising` parameter to control timeout error behavior. ```python def test_workers(qtbot): workers = spawn_workers() with qtbot.waitSignals([w.finished for w in workers]): for w in workers: w.start() # this will be reached after all workers emit their "finished" # signal or a qtbot.TimeoutError will be raised assert_application_results(app) ``` -------------------------------- ### qtbot.waitSignals Source: https://context7.com/pytest-dev/pytest-qt/llms.txt Blocks until multiple signals are emitted, with options for ordering constraints. Useful for synchronizing test execution with asynchronous signal emissions. ```APIDOC ## `qtbot.waitSignals` — Block until multiple signals all fire Waits for every signal in the provided list to be emitted before continuing, with optional ordering constraints. ### Parameters - `signals` (list): A list of signals to wait for. - `timeout` (int): Maximum time in milliseconds to wait for signals. - `order` (str): Specifies the order in which signals must be emitted. Can be 'none' (any order) or 'strict' (exact order). - `check_params_cbs` (list, optional): A list of callback functions to check signal parameters. ### Returns A blocker object that contains `all_signals_and_args` after the signals have been received. ``` -------------------------------- ### Wait for a Single Signal with Timeout Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/signals.md Use `qtbot.waitSignal` to block a test until a signal is emitted or a timeout is reached. Connect additional signals to the blocker to trigger on any of them. ```python def test_long_computation(qtbot): app = Application() # Watch for the app.worker.finished signal, then start the worker. with qtbot.waitSignal(app.worker.finished, timeout=10000) as blocker: blocker.connect(app.worker.failed) # Can add other signals to blocker app.worker.start() # Test will block at this point until either the "finished" or the # "failed" signal is emitted. If 10 seconds passed without a signal, # qtbot.TimeoutError will be raised. assert_application_results(app) ``` -------------------------------- ### qtbot.waitActive Source: https://context7.com/pytest-dev/pytest-qt/llms.txt A context manager that blocks until the widget becomes the active (focused) window. This is particularly useful for testing asynchronous window managers. It raises `qtbot.TimeoutError` if the widget does not become active within the specified timeout. ```APIDOC ## qtbot.waitActive ### Description Context manager that blocks until the widget becomes the active (focused) window, useful for asynchronous window managers. ### Parameters - **widget** (QWidget) - The widget to wait for to become active. - **timeout** (int, optional) - The maximum time in milliseconds to wait for the widget to become active. Defaults to a configured value. ### Request Example ```python def test_main_window_focus(qtbot): window = qt_api.QtWidgets.QMainWindow() qtbot.addWidget(window) with qtbot.waitActive(window, timeout=5000): window.show() window.raise_() window.activateWindow() assert window.isActiveWindow() ``` ``` -------------------------------- ### Wait for Signals with Parameter Checks Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/signals.md Use `check_params_cbs` to provide callables that verify signal parameters. Each callable must return True if parameters match, False otherwise. `None` can be used to skip parameter checking for a specific signal. ```python def test_status_100(status): """Return true if status has reached 100%.""" return status == 100 def test_status_50(status): """Return true if status has reached 50%.""" return status == 50 def test_status_complete(qtbot): app = Application() signals = [app.worker.status, app.worker.status, app.worker.finished] callbacks = [test_status_50, test_status_100, None] with qtbot.waitSignals( signals, raising=True, check_params_cbs=callbacks ) as blocker: app.worker.start() ``` -------------------------------- ### Configure Qt API in pytest.ini Source: https://github.com/pytest-dev/pytest-qt/blob/master/README.rst Specify the Qt API (PySide6, PyQt6, or PyQt5) to be used by pytest-qt by setting the qt_api configuration variable in your pytest.ini file. ```ini [pytest] qt_api=pyqt6 ``` -------------------------------- ### Customizing Qt Log Output Format Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/logging.md Control the format of captured Qt log messages using the `--qt-log-format` command-line option. You can use standard string formatting with record attributes. ```bash $ pytest test.py --qt-log-format="{rec.when} {rec.type_name}: {rec.message}" ``` -------------------------------- ### Custom Dialog Class with Convenience Method Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/note_dialogs.md Define a custom dialog class with a class method 'ask' to simplify its usage and facilitate testing. This method handles dialog instantiation, execution, and return value extraction. ```python class AskNameAndAgeDialog(QDialog): @classmethod def ask(cls, text, parent): dialog = cls(parent) dialog.text.setText(text) if dialog.exec_() == QDialog.Accepted: return dialog.getName(), dialog.getAge() else: return None, None ``` -------------------------------- ### Retrieve All Signal Arguments with check_params_cb Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/signals.md When using `check_params_cb`, `all_args` stores a list of all received signal parameters (as tuples) in order, even if they didn't match the callback's criteria. ```python # Example demonstrating all_args usage would go here, but is not present in the source. ``` -------------------------------- ### Wait for Callback with qtbot.waitCallback Source: https://context7.com/pytest-dev/pytest-qt/llms.txt Use `qtbot.waitCallback` as a context manager to block until a callback-style asynchronous method is invoked. It returns a callable object that can be passed to Qt methods. The `cb.args` and `cb.kwargs` attributes store the arguments received by the callback. ```python def test_javascript_result(qtbot): from PyQt6.QtWebEngineWidgets import QWebEngineView view = QWebEngineView() qtbot.addWidget(view) view.setHtml("") # Wait for the JS callback to be invoked with qtbot.waitCallback(timeout=5000) as cb: view.page().runJavaScript("2 + 2", cb) # After the with-block, cb has been called assert cb.args == [4] assert cb.kwargs == {} cb.assert_called_with(4) # convenience assertion ``` -------------------------------- ### Mocking Custom Dialog 'ask' Method for Form Testing Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/note_dialogs.md Mock the custom dialog's 'ask' class method using monkeypatch to test forms that rely on it. This allows simulating user input without displaying the actual dialog. ```python def test_form_registration(qtbot, monkeypatch): user = User.empty_user() form = RegistrationForm(user) monkeypatch.setattr( AskNameAndAgeDialog, "ask", classmethod(lambda *args: ("John", 30)) ) # Clicking on the button will call AskNameAndAgeDialog.ask in its slot. form.enter_info_button.click() assert user.name == "John" assert user.age == 30 ``` -------------------------------- ### Setting Default Qt Log Format in pytest.ini Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/logging.md Configure a default Qt log format for your project by adding the `qt_log_format` option to your `pytest.ini` file. ```ini [pytest] qt_log_format = {rec.when} {rec.type_name}: {rec.message} ``` -------------------------------- ### Raise Exception in Virtual Method Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/virtual_methods.md Demonstrates raising a `RuntimeError` within a virtual method. In PyQt5/PyQt6, this would typically cause an abort. Other wrappers print a stack trace. pytest-qt captures this to fail the test. ```python from PySide6.QtWidgets import QWidget from PySide6.QtCore import QTest, QtCore class MyWidget(QWidget): def mouseReleaseEvent(self, ev): raise RuntimeError("unexpected error") w = MyWidget() QTest.mouseClick(w, QtCore.Qt.LeftButton) ``` -------------------------------- ### Stop Current Test with qtbot.stop() Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/debugging.md Call `qtbot.stop()` to interrupt the current test. This is useful for inspecting the GUI state before the test continues or fails. Ensure Xvfb or offscreen platform plugins are disabled if you need to see the windows. -------------------------------- ### Python test with ignored and non-ignored critical Qt log messages Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/logging.md This test emits two critical messages. One matches an ignore pattern, while the other does not, demonstrating how `qt_log_ignore` selectively prevents failures. ```python def do_something(): qCritical("WM_PAINT not handled") qCritical("QObject: widget destroyed in another thread") def test_foo(qtlog): do_something() ``` -------------------------------- ### qtbot.assertNotEmitted Source: https://context7.com/pytest-dev/pytest-qt/llms.txt A context manager that asserts a signal is never emitted within the specified block. It can also perform an asynchronous wait for delayed signal emissions. ```APIDOC ## `qtbot.assertNotEmitted` — Assert a signal is never emitted Context manager that fails the test if the given signal fires within the block (or within an optional async wait period). ### Parameters - `signal` (Signal): The signal to monitor for emissions. - `wait` (int, optional): Time in milliseconds to wait for delayed signal emissions. ### Usage Use as a context manager (`with qtbot.assertNotEmitted(...)`) to wrap code that should not trigger the specified signal. ``` -------------------------------- ### Capture Exceptions with qtbot.captureExceptions Source: https://context7.com/pytest-dev/pytest-qt/llms.txt Employ `qtbot.captureExceptions` as a context manager to intercept and collect exceptions raised within Qt virtual methods (like slots or event handlers) during the block. Captured exceptions are stored in a list for later inspection, preventing interpreter crashes. ```python def test_exception_in_event_handler(qtbot): class BrokenWidget(qt_api.QtWidgets.QWidget): def mousePressEvent(self, event): raise ValueError("something went wrong") broken = BrokenWidget() qtbot.addWidget(broken) with qtbot.captureExceptions() as exceptions: qtbot.mouseClick(broken, qt_api.QtCore.Qt.MouseButton.LeftButton) assert len(exceptions) == 1 exc_type, exc_value, _ = exceptions[0] assert exc_type is ValueError assert str(exc_value) == "something went wrong" ``` -------------------------------- ### Catching Exceptions in Qt Virtual Methods Source: https://context7.com/pytest-dev/pytest-qt/llms.txt Demonstrates how pytest-qt's exception hook catches exceptions raised in Qt virtual methods (like paintEvent), converting them into test failures instead of crashes. Use pytest.raises to assert the expected exception. ```python import pytest from pytestqt.qt_compat import qt_api class WidgetWithBuggyHandler(qt_api.QtWidgets.QWidget): def paintEvent(self, event): raise RuntimeError("paint failed") def test_virtual_method_exception_is_caught(qtbot): """Without pytest-qt's hook this would call abort() and crash Python.""" w = WidgetWithBuggyHandler() qtbot.addWidget(w) # The exception in paintEvent will be captured and re-raised by pytest-qt, # causing the test to fail with a clear traceback rather than a crash. with pytest.raises(RuntimeError, match="paint failed"): with qtbot.waitExposed(w): w.show() ``` -------------------------------- ### qtbot.captureExceptions Source: https://context7.com/pytest-dev/pytest-qt/llms.txt A context manager that collects exceptions raised inside Qt virtual methods (slots, event handlers) during the block. Captured exceptions are made available for inspection, preventing them from crashing the interpreter. ```APIDOC ## `qtbot.captureExceptions` — Capture exceptions from Qt virtual methods Context manager that collects exceptions raised inside Qt virtual methods (slots, event handlers) during the block, making them available for inspection instead of crashing the interpreter. ### Returns A list of captured exceptions. Each item in the list is a tuple containing the exception type, exception value, and traceback. ### Usage Wrap code that might raise exceptions in Qt slots or event handlers within a `with qtbot.captureExceptions() as exceptions:` block. ``` -------------------------------- ### Accessing callback arguments with qtbot.waitCallback Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/wait_callback.md After a callback managed by qtbot.waitCallback has been invoked, its arguments and keyword arguments are available via the .args and .kwargs attributes, respectively. You can also use .assert_called_with() for direct verification. ```python assert cb.args == [2] assert cb.kwargs == {} ``` ```python cb.assert_called_with(2) ``` -------------------------------- ### Preventing QApplication.exit() from Terminating Test Suite Source: https://context7.com/pytest-dev/pytest-qt/llms.txt Monkeypatch QApplication.exit to prevent it from terminating the test process. This is useful when testing widgets that might call exit. ```python def test_exit_button_does_not_terminate_suite(qtbot, monkeypatch): """Monkeypatch QApplication.exit so the test process doesn't end.""" exit_calls = [] monkeypatch.setattr( qt_api.QtWidgets.QApplication, "exit", lambda *a: exit_calls.append(a) ) button = get_exit_button() qtbot.addWidget(button) button.click() assert exit_calls == [()] ``` -------------------------------- ### Override Qt log failure settings using pytest marks Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/logging.md Use `pytest.mark.qt_log_level_fail` and `pytest.mark.qt_log_ignore` to configure log failure behavior on a per-test basis, overriding `pytest.ini` settings. ```python import pytest from pytestqt.qt_compat import qCritical def do_something(): qCritical("WM_PAINT not handled") qCritical("QObject: widget destroyed in another thread") @pytest.mark.qt_log_level_fail("CRITICAL") @pytest.mark.qt_log_ignore("WM_DESTROY.*sent", "WM_PAINT failed") def test_foo(qtlog): do_something() ``` -------------------------------- ### Using qtbot.waitCallback to test JavaScript execution Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/wait_callback.md Use qtbot.waitCallback as a context manager to wait for a callback to be invoked after an asynchronous operation, such as running JavaScript in a QWebEnginePage. The callback is passed directly to the operation. ```python def test_js(qtbot): page = QWebEnginePage() with qtbot.waitCallback() as cb: page.runJavaScript("1 + 1", cb) cb.assert_called_with(2) # result of the last js statement ``` -------------------------------- ### Assert Signal Not Emitted Source: https://github.com/pytest-dev/pytest-qt/blob/master/docs/signals.md Use `qtbot.assertNotEmitted` as a context manager to ensure a specific signal is not emitted within a block of code. Optionally, specify a `wait` duration in milliseconds to account for asynchronous signals. ```python def test_no_error(qtbot): ... with qtbot.assertNotEmitted(app.worker.error): app.worker.start() ``` ```python def test_no_error(qtbot): ... with qtbot.assertNotEmitted(page.loadFinished, wait=100): page.runJavaScript("document.getElementById('not-a-link').click()") ``` -------------------------------- ### qtbot.waitCallback Source: https://context7.com/pytest-dev/pytest-qt/llms.txt Blocks until a callback-style asynchronous method completes. It returns a callable object that can be passed as a callback to Qt methods and then blocks until it is invoked. ```APIDOC ## `qtbot.waitCallback` — Block until a callback-style async method completes Returns a callable object that can be passed as a callback to Qt methods (e.g., `QWebEnginePage.runJavaScript`), then blocks until it is called. ### Parameters - `timeout` (int, optional): Maximum time in milliseconds to wait for the callback. ### Returns A callable object (`cb`) that, after being invoked, will have `args` and `kwargs` attributes containing the arguments it was called with. It also provides an `assert_called_with` method. ``` -------------------------------- ### qtbot.addWidget Source: https://context7.com/pytest-dev/pytest-qt/llms.txt Registers a widget with the test bot for automatic cleanup at the end of the test. This prevents widget leaks between tests. An optional `before_close_func` can be provided to execute a function just before the widget is closed. ```APIDOC ## qtbot.addWidget ### Description Registers a widget with the test bot so it is automatically closed and cleaned up at the end of the test, preventing widget leaks between tests. ### Parameters - **widget** (QWidget) - The widget to register. - **before_close_func** (callable, optional) - A function to call just before the widget is closed. ### Request Example ```python from pytestqt.qt_compat import qt_api def test_widget_lifecycle(qtbot): widget = qt_api.QtWidgets.QLabel("Hello pytest-qt") # Register widget – it will be closed automatically after the test qtbot.addWidget(widget) # Optional: run a function just before the widget is closed log = [] widget2 = qt_api.QtWidgets.QPushButton("Click me") qtbot.addWidget(widget2, before_close_func=lambda w: log.append(f"closing: {w}")) widget.show() assert widget.text() == "Hello pytest-qt" # widget and widget2 are closed by qtbot after the test regardless of pass/fail ``` ```