A pytest plugin for testing QField qml plugins
Project description
pytest-qfield
A pytest plugin for testing QField QML plugins. This
plugin uses pytest-qgis and QGIS behind the hood since QField does not
have a python api.
Features
Fixtures
The following fixtures are provided by pytest-qfield:
qfield_bot: AQFieldBotinstance to interact with the QField QML environment.qfield_iface: A stub implementation of the QField application interface (ifacein QML).qfield_new_project: Initializes a new QField project.main_window_qml_path: Path to the QML file used for the QField main window. Can be overridden to use a custom QML main window.
Stubs and Overriding Fixtures
pytest-qfield provides several stub fixtures that are automatically injected into the QML engine's context. You can override these fixtures in your conftest.py to provide custom behavior or extended versions of the stub classes.
Available Stubs
The following stub fixtures correspond to objects available in the QField QML context:
| Fixture Name | QML Context Property | Description |
|---|---|---|
qfield_iface |
iface |
QField application interface. |
qgs_project_stub |
qgisProject |
QgsProject instance. |
qfield_platform_utilities_stub |
platformUtilities |
Platform-specific utilities. |
qfield_theme_stub |
Theme |
Theme colors, fonts, and layout constants. Required for QField versions >v4.0.6 where Theme is a C++ singleton. |
qfield_string_utils_stub |
StringUtils |
String utility functions. |
qfield_layer_utils_stub |
LayerUtils |
Layer utility functions. |
qfield_feature_utils_stub |
FeatureUtils |
Feature utility functions. |
qfield_geometry_utils_stub |
GeometryUtils |
Geometry utility functions. |
qfield_qml_extra_context_properties |
(various) | Dictionary of extra context properties to inject. |
Named-Item Stubs
Some QField QML code locates objects through iface.findItemByObjectName("...") rather than as context properties. pytest-qfield auto-registers default stubs on the iface for the following object names:
| Fixture Name | Registered objectName |
Description |
|---|---|---|
qfield_positioning_stub |
positionSource |
QField Positioning item — active flag and projectedPosition (x, y). |
qfield_geometry_highlighter_stub |
geometryHighlighter |
GeometryHighlighter exposing geometryWrapper, duration, visible, and update(). |
Override the relevant fixture to inject custom values (e.g. a fixed position):
# conftest.py
import pytest
from pytest_qfield.stub_interface.qfield_stubs import QFieldPositioningStub
@pytest.fixture
def qfield_positioning_stub() -> QFieldPositioningStub:
return QFieldPositioningStub(x=389870.0, y=6678167.0, active=True)
See test/test_named_item_overrides.py for a complete working example. To assert behaviour when a named item is missing, clear the registration on the iface stub or override the fixture to return a sentinel.
Map Canvas Stub
iface.mapCanvas() returns a QFieldMapCanvasStub exposing the QField MapCanvas signals plugins listen to:
| Member | Description |
|---|---|
clicked(QPointF, int) signal |
Emitted by QField when the user taps the map. |
confirmedClicked(QPointF, int) signal |
Emitted on a long-press / confirmed tap. |
mapSettings.screenToCoordinate(QPointF) -> QPointF |
Delegates to the wired QgsMapCanvas.mapSettings().mapToPixel() for real screen→CRS projection (identity fallback when no canvas is wired). |
The stub is auto-attached to the iface as qml_map_canvas and is also exposed via the qfield_map_canvas_stub fixture for overrides. By default the fixture wires pytest-qgis's qgis_canvas so projection works for real — set an extent and size on qgis_canvas before emitting clicks:
from PyQt6.QtCore import QPointF
from qgis.core import QgsRectangle
def test_pick_end_point(qfield_bot, qgis_canvas):
qgis_canvas.show()
qgis_canvas.resize(200, 200)
qgis_canvas.setExtent(QgsRectangle(0, 0, 1000, 1000))
qgis_canvas.refresh()
# Pixel (0, 0) is the top-left of the canvas → CRS (0, 1000).
qfield_bot.iface.qml_map_canvas.clicked.emit(QPointF(0.0, 0.0), 0)
# ...assert your plugin reacted to the picked coordinate
If you'd rather skip the canvas setup and pass project-CRS coordinates directly, override the fixture to drop canvas wiring:
@pytest.fixture
def qfield_map_canvas_stub() -> QFieldMapCanvasStub:
return QFieldMapCanvasStub() # no canvas → identity screenToCoordinate
For richer modelling (custom signals, an extent property, etc.), subclass QFieldMapCanvasStub / QFieldMapSettingsStub and return your subclass from the fixture.
How to Override
To override a stub, subclass it and redefine the fixture in your conftest.py or test module.
For example, to make StringUtils.createUuid() return a deterministic value:
# conftest.py
import pytest
from PyQt6.QtCore import pyqtSlot
from pytest_qfield.stub_interface.qfield_stubs import QFieldStringUtilsStub
class DeterministicStringUtils(QFieldStringUtilsStub):
@pyqtSlot(result=str)
def createUuid(self) -> str:
return "{00000000-0000-0000-0000-000000000000}"
@pytest.fixture
def qfield_string_utils_stub() -> QFieldStringUtilsStub:
return DeterministicStringUtils()
Any QML code calling StringUtils.createUuid() will now receive the fixed value.
See test/test_fixture_override.py for a complete working example.
You can also use qfield_qml_extra_context_properties to inject additional objects into the QML context:
@pytest.fixture
def qfield_qml_extra_context_properties():
return {
"myCustomObject": MyCustomObject()
}
Other overridable fixtures include:
main_window_qml_path: Override to use a different QML file as the main window shell.register_qfield_resources: Override to register your own compiled Qt resources (.qrc).register_qfield_types: Override to register additional QML types.register_qgis_types: Override to register additional QGIS-related QML types.
QFieldBot
The qfield_bot fixture provides several methods to help testing:
load_plugin(qml_file): Loads a QField plugin QML file.open_project(qfield_project_file): Opens a QField project file.show_window(): Shows the QField main window.get_item(object_name): Finds a QML item by itsobjectName.click_item(item): Simulates a mouse click on a QML item.load_js_function(js_file, function_name, params): Loads a JavaScript function from a file for direct testing.
Examples
- Basic plugin loading/clicking tests:
test/test_plugin.py - Overriding stub fixtures:
test/test_fixture_override.py - Overriding auto-registered named-item stubs:
test/test_named_item_overrides.py - Javascript function tests:
test/test_javascript_functions.py - Stub interface integration:
test/test_stub_interface.py - Visual/manual checks:
test/visual/test_plugin_visually.py
Installation
You must have QGIS >= 4.0 installed to use this plugin.
Install with pip or uv to a python environment that is aware of system QGIS libraries.
You can create one with qgis-venv-creator.
pip install pytest-qfield
# uv add --dev pytest-qfield
Configure QField imports path
The plugin needs access to QField source code for its QML imports (typically QField/src/qml/imports).
First clone QField source code somewhere and checkout the target QField tag and then
set it either with an environment variable or a pytest.ini value.
QFIELD_IMPORTS_DIR=/absolute/path/to/QField/src/qml/imports pytest
# pyproject.toml
[tool.pytest.ini_options]
qfield_imports_dir = /absolute/path/to/QField/src/qml/imports
Development environment
This project uses uv to manage python packages. Make sure to have it installed first.
- Clone QField source code somewhere and checkout the tag for requested QField version.
- Copy .env.example to .env and fill the missing values
- Create a venv that is aware of system QGIS libraries:
uv venv --system-site-packages. Make sure to use same Python executable as QGIS.- On Windows, maybe use a tool like qgis-venv-creator.
# Activate the virtual environment
$ source .venv/bin/activate
# Install dependencies
$ uv sync
# Install pre-commit hooks
$ prek install
# Run tests
$ uv run pytest
Updating dependencies
uv lock --upgrade
Release process
Releases are made automatically upon push to main branch using Python Semantic Release.
Contributing
Contributions are very welcome.
Inspirations
License
Distributed under the terms of the GNU GPL v2.0 license, "pytest-qfield" is free and open source software.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pytest_qfield-0.5.0.tar.gz.
File metadata
- Download URL: pytest_qfield-0.5.0.tar.gz
- Upload date:
- Size: 25.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5781587993d0356af9a58952cfd9a21551a2aa4ff90749b87f33e7c3fa511bbd
|
|
| MD5 |
b2886f8f77d9a080a7e79f1f01689ea1
|
|
| BLAKE2b-256 |
4a1127fed229c54254eb5ec05aa4f3afc22b81fa0f95b1ea97b2977dfe8cd8aa
|
File details
Details for the file pytest_qfield-0.5.0-py3-none-any.whl.
File metadata
- Download URL: pytest_qfield-0.5.0-py3-none-any.whl
- Upload date:
- Size: 30.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5351c80bdb058457ce295a405005559ecedc5879bb3d2b5d727902545992619b
|
|
| MD5 |
94a9991164826ac88c84ae1e36ad6a4e
|
|
| BLAKE2b-256 |
0e27aa8d647ec82b3e9003809d01d242398608e8e08db09b562310580d608364
|