Skip to main content

pytest-qamule

A pytest plugin for QAMule automation testing, integrating device control, live pause debugging/checkpoint, and AI/human-friendly test reports.

What it does

  • Device fixtures: expose uiautomator2 devices as pytest fixtures such as d, phone, or tablet.
  • Live pause: pause a failing test, inspect the app state, then resume or abort from the CLI.
  • Checkpoints: add explicit human/AI review points inside tests with the checkpoint fixture.
  • Live reports: write an event-backed report while pytest runs, then inspect it from a browser or JSON CLI.

Install

uv add pytest-qamule

The package exposes a pytest plugin through the qamule pytest11 entry point and a qamule CLI script.

Device fixtures

pytest-qamule exposes uiautomator2 devices as pytest fixtures. With no options, a default d fixture connects through u2.connect():

pytest
def test_home_screen(d):
    d.app_start("com.example.app")

Register named devices with repeated --device NAME:SERIAL options:

pytest --device phone:emulator-5554 --device tablet:0123456789
def test_home_screen(phone):
    phone.app_start("com.example.app")

Pause Sessions

QAMule creates a session directory directly under the current working directory. Pause protocol files are written under <session-id>/pauses/<pause-id>/. Pytest prints the session id at startup:

QAMule Session ID: s-20260624-153000-a1b2c3
QAMule Session Dir: /path/to/project/s-20260624-153000-a1b2c3

The session is marked finished during pytest unconfigure. Generated s-*/ session directories are ignored by git.

Failure Pause

Enable live pause on test failures with --qamule-pause-on-failure, or use the shorter --pause-on-failure alias for local debugging.

pytest --qamule-pause-on-failure -s
pytest --pause-on-failure -s

Failure pause triggers only when the pytest call phase fails and the report is not an expected xfail. It pauses after the failing call report is produced, then resumes teardown and the rest of the run after a resume command.

Attach preset images before the failure with the auto-loaded qamule_pause fixture:

def test_failure(qamule_pause):
    qamule_pause.add_image("before-failure.png")
    qamule_pause.add_images(["detail-1.png", "detail-2.png"])
    assert False, "boom"

After resume or abort, failure pause data is attached to the pytest call report for future reporting integrations:

def pytest_runtest_logreport(report):
    if report.when == "call" and hasattr(report, "qamule_pause"):
        message = report.qamule_pause["message"]
        images = report.qamule_pause["images"]

The same payload is also added to report.user_properties with the key qamule_pause.

Checkpoints

The checkpoint fixture is auto-loaded by the pytest plugin and can be used directly in tests. It pauses immediately when called, without requiring --pause-on-failure.

def test_checkpoint(checkpoint):
    response = checkpoint("verify app state", images=["checkpoint.png"])
    assert response.result is True
    assert response.message == "checkpoint accepted"
    assert response.images[0].base64

checkpoint(...) returns a CheckpointResult with:

response.result
response.message
response.images

Resume a checkpoint with a result:

qamule pause resume p-000001 --session-id <session-id> --result true --message "checkpoint accepted"

For non-interactive runs, mock checkpoint resume results at pytest startup:

pytest --qamule-checkpoint-mock-result true -s
pytest --qamule-checkpoint-mock-result false -s
pytest --qamule-checkpoint-mock-result none -s
pytest --qamule-checkpoint-mock-result true --qamule-checkpoint-mock-message "approved by reviewer" -s

Mocked checkpoints still write pause and result protocol files, then immediately resume. By default, the checkpoint message is the selected mock value: true, false, or none. Use --qamule-checkpoint-mock-message to attach a more descriptive review message.

--result must be true, false, or none. Checkpoint pauses do not support abort. Checkpoint timeouts automatically resume with response.result is None and response.message == "pause timed out"; they do not automatically fail the test.

CLI

qamule pause commands emit JSON and require --session-id:

qamule pause status --session-id <session-id>
qamule pause ls --session-id <session-id>
qamule pause show <pause-id> --session-id <session-id>
qamule pause resume <pause-id> --session-id <session-id> --message "checked" --image screenshot.png
qamule pause abort <pause-id> --session-id <session-id> --message "stop run" --image screenshot.png
qamule pause watch --session-id <session-id> --timeout 30 --interval 0.5

Command behavior:

  • status: prints whether the session has active pauses; exits 0 when active pauses exist and 2 otherwise.
  • ls: lists all pauses for the session.
  • show: prints one pause by id; exits 2 if the pause is missing.
  • resume: writes a resume command for an active pause.
  • abort: writes an abort command for an active failure pause; checkpoint abort exits 5 and does not write a command.
  • watch: waits for an active pause, a finished session, or timeout; timeout exits 2.

resume continues a pause. abort stops a failure pause by raising KeyboardInterrupt in pytest.

Images

The --image option accepts a file path and can be repeated. File contents are encoded as base64, and the file name is stored as the image alias. Duplicate image content is removed while preserving first-seen order. The same alias can appear more than once when the underlying base64 content is different.

qamule commands keep terminal JSON concise: attached images are reported by alias or count, and base64 fields are omitted from command output. Full base64 image data is written only to the pause protocol files for the report UI and protocol readers.

Images can come from code-side presets and CLI commands. They are merged in this order:

  1. Code-side preset images from qamule_pause or checkpoint(..., images=[...])
  2. CLI images from qamule pause resume ... --image ... or qamule pause abort ... --image ...

Each image has this shape in protocol files, checkpoint responses, and failure report attachments:

{
    "alias": "image",
    "base64": "...",
}

Example failure workflow:

pytest test_a.py --pause-on-failure -s
qamule pause watch --session-id <session-id>
qamule pause show p-000001 --session-id <session-id>
qamule pause resume p-000001 --session-id <session-id> --message "checked failure state" --image screenshot.png

Example checkpoint workflow:

pytest test_a.py -s
qamule pause watch --session-id <session-id>
qamule pause resume p-000001 --session-id <session-id> --result true --message "checkpoint accepted" --image final.png

Live Reports

QAMule writes the live report store automatically whenever pytest runs:

pytest
pytest --pause-on-failure -s

Report files are written directly in the QAMule session directory while pytest is still running:

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

events.jsonl is the append-only event stream, and state.json records whether the run is running, finished, aborted, interrupted, or lost. QAMule does not store a duplicated report snapshot; report views are built dynamically from <session-id>/events.jsonl plus pause protocol files under <session-id>/pauses/. Evidence images are stored once in pause protocol JSON as alias and base64, and report views reuse that data instead of creating image asset files or copying base64 into report events. If the pytest process disappears while state.json still says running, report readers display the session as lost instead of treating the report as invalid.

Serve the human-friendly live report UI with:

qamule report serve --session-id <session-id>
qamule report serve --session-id <session-id> --host 127.0.0.1 --port 8765
qamule report serve --session-id <session-id> -q

The report server opens the browser UI by default and exposes JSON endpoints backed by the same dynamically built report view. Pass -q or --quite to print the URL without opening a browser. The browser uses server-sent events to render new results as they arrive. The UI logo is served from packaged report assets, so it does not require a separate logo.jpg in the current working directory.

All qamule report commands read events.jsonl and state.json from <session-id>/ under the current working directory. To inspect a copied report, run the command from the directory that contains the session directory.

AI agents and scripts can query focused slices of the report without reading the full file:

qamule report status --session-id <session-id>
qamule report list --session-id <session-id>
qamule report list --session-id <session-id> --outcome failed
qamule report list --session-id <session-id> --with evidence
qamule report show tests/test_a.py::test_example --session-id <session-id>
qamule report failures --session-id <session-id>
qamule report checkpoints --session-id <session-id>
qamule report rebuild --session-id <session-id>

qamule report list can filter by --outcome passed|failed|skipped|running|unknown and by --with checkpoint|failure-evidence|evidence.

The default report view focuses on test outcomes, checkpoint decisions, failure evidence, messages, results, and images. Detailed event history remains available through <session-id>/events.jsonl, while pause details and images remain in <session-id>/pauses/.

When a pause has both code-side preset images and CLI-attached images, the report keeps the legacy merged images list for compatibility and also exposes preset_images and attached_images so the browser can display them in separate groups.

To avoid duplicate image storage, pause.json owns code-side preset image base64 and command.json owns CLI-attached image base64. result.json records only the pause decision fields and does not store image base64.

Development

Pause and report CLI commands share the same public JSON output helper. When changing CLI output, keep terminal JSON free of base64 fields and cover the shared helper plus the affected feature tests.

uv sync
uv run pytest
uv run pytest tests/unit/test_output.py
uv run pytest tests/unit/features/device tests/integration/features/device
uv run pytest tests/unit/features/pause tests/integration/features/pause
uv run pytest tests/unit/features/report tests/integration/features/report

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pytest_qamule-0.2.0.tar.gz (527.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pytest_qamule-0.2.0-py3-none-any.whl (517.3 kB view details)

Uploaded Python 3

File details

Details for the file pytest_qamule-0.2.0.tar.gz.

File metadata

  • Download URL: pytest_qamule-0.2.0.tar.gz
  • Upload date:
  • Size: 527.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.25 {"installer":{"name":"uv","version":"0.11.25","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

Hashes for pytest_qamule-0.2.0.tar.gz
Algorithm Hash digest
SHA256 df575caa9364f27ff931b599e4e10d10b65f3e4d357193f305d7db63c7d84e85
MD5 4a47f03c01ea61de8f1a1d945e55d758
BLAKE2b-256 bf1195a5ea3e3b907826a2df25e0c4c4f33a87a19711fdf1b7738d5485dd908c

See more details on using hashes here.

File details

Details for the file pytest_qamule-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: pytest_qamule-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 517.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.25 {"installer":{"name":"uv","version":"0.11.25","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

Hashes for pytest_qamule-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3c8d6fe979af9f1fab0a5498566e2f5a02441dd9f2f30747944a746b681769b2
MD5 2e57d283ebce4041b766688d62246fcf
BLAKE2b-256 28c1b04d5e1d7872273cea980e005343ab5cb0ecebedbf064cf56e555e25085d

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 files

0.1.0

2 files

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