Skip to main content

qamule-pytest

qamule-pytest provides reusable Android device and artifact fixtures for pytest-based automation.

Devices are connected through uiautomator2 and reused for the whole test session. Artifact directories are created only when a test or plugin requests them.

Install

pip install qamule-pytest

Use Devices

Without configuration, request the default d fixture:

def test_smoke(d):
    assert d.serial is not None

Register named devices by repeating --device NAME:SERIAL:

pytest --device phone:emulator-5554 --device tablet:emulator-5556

Set one uiautomator2 server port for all configured devices when needed:

pytest --device phone:emulator-5554 --device-port 26831
def test_on_phone(phone):
    assert phone.serial == "emulator-5554"

Device names must be non-keyword Python identifiers and must not use a fixture name reserved by qamule-pytest. When --device-port is omitted, the server port is not passed to uiautomator2 and its default is used.

qamule-pytest currently supports single-process pytest execution. It rejects active pytest-xdist runs with a usage error; run without -n or --numprocesses.

Use device_manager.get(name) for lazy lookup. The devices fixture connects and returns all configured devices.

Artifact Directories

Use the fixtures exposed by the plugin:

def test_capture(testcase_device_artifacts_dir):
    screenshot = testcase_device_artifacts_dir("phone") / "screen.jpg"
    screenshot.write_bytes(b"example")

Artifacts use the following layout:

pytest-artifacts/<session-id>/<sequence>-<testcase>/<device-name>/

When a test call fails, each device already connected during the pytest session is captured automatically:

<device-name>/failure.jpg
<device-name>/hierarchy.xml

Failure capture does not connect unused devices. Screenshot and hierarchy capture are independent best-effort operations, so capture errors never replace the original test failure. Expected xfail results are not captured.

Available fixtures:

  • session: metadata and paths for the current pytest run.
  • artifacts_dir: artifact root for the current run.
  • testcase: metadata and paths for the current test execution.
  • testcase_artifacts_dir: artifact directory for the current test.
  • testcase_device_artifacts_dir(name): artifact directory for one device.

Directories are created lazily. Requesting session alone does not write an artifact directory.

Plugins that cannot request fixtures from a hook can use get_session(config), get_testcase(item), or get_testcase_device_dir(item, name) from qamule_pytest.features.artifacts.plugin.

Session Reports

Every pytest run prints its QAMule session ID and writes live report data under:

pytest-sessions/<session-id>/events.jsonl
pytest-sessions/<session-id>/state.json

events.jsonl is an append-only stream containing session, collection, test lifecycle, phase result, duration, and failure traceback events. state.json is an atomically updated snapshot containing the pytest process ID, session state, timestamps, exit status, and result totals.

Inspect a session from the directory where pytest started:

uv run qamule report status <session-id>
uv run qamule report show <session-id> 'tests/test_login.py::test_login'

Serve the realtime report locally:

uv run qamule report serve <session-id>
uv run qamule report serve ./pytest-sessions/<session-id>
uv run qamule report serve <session-id> --host 127.0.0.1 --port 26826 -q

The report receives incremental updates over Server-Sent Events. Only the selected case's evidence is rendered, report JSON contains image metadata only, and the browser requests each image only when it is near the visible area. report serve accepts either a session ID or the absolute or relative path to its pytest-sessions/<session-id> directory. Use --root PATH with a session ID when another report command runs outside the directory where pytest started. This package intentionally does not export static report bundles.

Interactive Pauses

Use the checkpoint fixture to stop a test until an external decision arrives:

def test_login(checkpoint):
    decision = checkpoint(
        "confirm the login screen",
        images=["screens/login.jpg"],
    )
    assert decision.result is True

Pause automatically after failed test calls with:

uv run pytest --pause-on-failure

Every checkpoint and failure pause times out after 600 seconds by default. Set a different positive duration for the whole pytest run with --pause-timeout SECONDS. A timed-out checkpoint returns a decision with result=None, has_result=False, and timed_out=True. A timed-out failure pause releases pytest so the original test failure can finish.

To exercise checkpoint-dependent tests without external interaction, mock every checkpoint result for the pytest run:

uv run pytest --checkpoint-mock-result true
uv run pytest --checkpoint-mock-result false

The checkpoint still publishes its paused event and artifacts, then immediately resumes with the selected boolean result. While this option is enabled, checkpoint watch instructions are not printed and the resume message is mock via --checkpoint-mock-result.

While pytest is paused, use its printed session ID from another terminal:

uv run qamule watch <session-id>
uv run qamule resume <session-id> <pause-id> \
    --result true \
    --message "login screen approved"

Checkpoint resumes require --result true, --result false, or --result none. Failure pauses do not require a result. Attach images by repeating --image PATH. Common image formats such as JPG, PNG, GIF, and WebP are supported when Python can identify their MIME type. JPG is recommended and is the format used for automatic failure screenshots.

Pause state is stored under pytest-sessions/<session-id>/pauses/<pause-id>/. Image bytes are deduplicated under pytest-sessions/<session-id>/blobs/<sha256>; status and resume files contain only metadata and hashes. watch also returns the final session state if pytest finishes or reports process_exited if the pytest process disappears. Resume and timeout compete for one atomically published resolution.json, so a resume accepted at the deadline cannot later be overwritten by a timeout.

License

qamule-pytest is released under the MIT License.

Development

From the workspace root:

uv sync --package qamule-pytest
uv run pytest packages/qamule-pytest/tests -q

Metadata

Release files for qamule-pytest 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for qamule-pytest 0.1.0
File Size Uploaded Provenance
qamule_pytest-0.1.0.tar.gz 70.0 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for qamule-pytest 0.1.0
File Interpreter ABI Platform
qamule_pytest-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 134.1 kB

Release files / qamule_pytest-0.1.0.tar.gz

Download URL qamule_pytest-0.1.0.tar.gz
Size 70.0 kB
Tags Source
SHA-256 checksum
How to use checksums
016fb195c8e967f7ab7d1edecd94a7dd657530a67fc56a9041809b5fdcf003db
BLAKE2b-256 checksum
How to use checksums
b353854b9cf6eab6a27cfa6471baec686b8f30f243ab8015404bbfa76ffb4a08
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / qamule_pytest-0.1.0-py3-none-any.whl

Download URL qamule_pytest-0.1.0-py3-none-any.whl
Size 64.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8032c974eea3c7205b638b24b0b6833130002d482ca21d8233a65b874a808cbe
BLAKE2b-256 checksum
How to use checksums
37453c745d8971d204c6b935b5123c2540fc81b9221c67e07e6a1df05de405bb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for qamule-pytest 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page