m00nreport-pytest
Official pytest plugin for M00N Report, an AI-native test management platform. Results, steps, attachments and retries land in M00N Report while the suite is still running, and each test can carry the manual test case it covers. A reporting problem is never a test failure: every hook is guarded, and the worst case is a single [m00nreport] warning on stderr.
Requirements
Python 3.9 or newer, pytest 7.0 or newer, and pluggy 1.2 or newer.
pip installs pluggy for you, so the floor is stated only because it bites when a lockfile gets it wrong: pytest 7 alone permits pluggy 0.12, and this plugin uses @pytest.hookimpl(wrapper=True), which is pluggy 1.2 API and raises at import below it. A pytest11 plugin that raises at import takes down every pytest invocation in that environment, including suites that have nothing to do with reporting.
The Playwright layer additionally needs pytest-playwright, and retries need pytest-rerunfailures. Both are optional.
Installation
pip install m00nreport-pytest
# with the Playwright layer
pip install "m00nreport-pytest[playwright]"
The package registers itself through the standard pytest11 entry point, so pytest loads it on every run once it is installed. There is no conftest.py wiring, no -p flag, no config block.
Quick Start
1. Get your API key
In M00N Report, open Project Settings -> API Keys and copy the key. It starts with m00n_ followed by 48 hex characters, and it identifies both the organization and the project.
Keep it out of the repository. In CI it belongs in a secret; locally, in an environment variable.
2. Point the plugin at your project
export M00N_SERVER_URL=https://m00nreport.com # or your own instance, root origin, no /api, no trailing slash
export M00N_API_KEY=m00n_...
PowerShell:
$env:M00N_SERVER_URL = "https://m00nreport.com"
$env:M00N_API_KEY = "m00n_..."
Everything except the key can live in the repository instead:
# pytest.ini
[pytest]
m00n_launch = Nightly regression
m00n_tags = nightly,regression
The same keys work in setup.cfg under [tool:pytest] and in pyproject.toml under [tool.pytest.ini_options].
3. Run your tests
pytest
[m00nreport] Run started: 9f3c1a7e-4d21-4b8a-9e77-2c5b1d0a6f84
--------------------------------- M00N Report ---------------------------------
Run ID: 9f3c1a7e-4d21-4b8a-9e77-2c5b1d0a6f84
Tests: 3/3 reported
Failed: 1
The run id is printed the moment the run starts, and again in the summary at the end. Both lines carry the same id, which is the run in the project's Launches list, filling in as tests finish. No [m00nreport] line at all means the plugin never activated - see Troubleshooting.
Configuration
Every option can be set three ways. Precedence, highest wins: CLI flag > pytest.ini key > environment variable > default.
| CLI flag | ini key | Env var | Default | Description |
|---|---|---|---|---|
--m00n-server-url |
m00n_server_url |
M00N_SERVER_URL |
required | Your M00N Report URL |
--m00n-api-key |
m00n_api_key |
M00N_API_KEY |
required | Project API key (m00n_...) |
--m00n-launch |
m00n_launch |
M00N_LAUNCH |
Run <date> |
Title for this run |
--m00n-tags |
m00n_tags |
M00N_TAGS |
[] |
Comma-separated tags |
--m00n-attributes |
m00n_attributes |
M00N_ATTRIBUTES |
{} |
JSON object of custom run attributes. Malformed JSON is ignored rather than crashing the run. |
M00N_ATTRIBUTE_<NAME> |
One attribute per variable, which is easier to set in a CI job than a JSON object quoted inside a YAML value. M00N_ATTRIBUTE_BRANCH=main becomes the attribute branch, and wins over the same key in M00N_ATTRIBUTES. |
|||
--m00n-debug |
m00n_debug |
M00N_DEBUG |
false |
Print a [m00nreport] line on stdout for every HTTP retry and any final exhaustion. Silent when off. |
--m00n-disable |
false |
Turn the plugin off for this invocation even when a URL and key are set. CLI only, by design. |
The plugin activates only once both the URL and the key resolve to a value.
A discovery pass creates no launch. --collect-only, --setup-only and --setup-plan run no tests, so the plugin stays out of the way entirely. Without that rule an IDE refreshing its test tree on every save fills the Launches list with empty runs.
Steps
from m00n_reporter import step
def test_checkout():
with step("Add item to cart"):
cart.add(sku="A-1")
with step("Pay"):
with step("Enter card"):
checkout.enter_card(EXPIRED_CARD)
checkout.submit()
Steps nest, and their timings are recorded. A step still open when the test fails is reported as skipped, so the step list shows how far the test got.
Attachments
from m00n_reporter import attach
def test_api_contract():
response = client.get("/api/cart")
attach(response.content, name="cart.json", content_type="application/json")
attach("artifacts/screenshot.png") # a path works too
Bytes need a name; for a path it defaults to the basename. Content type is guessed from the name when you do not pass one.
A file over 200MB is skipped with one warning (recount: grep -n 'MAX_ATTACHMENT_SIZE =' src/m00n_reporter/attachments.py). When a run reaches your plan's attachment quota the plugin warns run attachment limit reached (N/M MB used) and drops the rest of that run's uploads, including ones already queued. Neither ever fails the test.
Linking Tests to Cases
A m00n marker attaches the result to a manual test case, which is what turns a run into coverage rather than a list of green ticks.
import pytest
@pytest.mark.m00n(case_id=42, tags=["payments", "smoke"])
def test_checkout_rejects_expired_card():
...
case_id is the case's internal id, not the TC-42 number shown in the UI. Open the case in M00N Report and take the id from the address bar. Tags here are per test, on top of the run-level --m00n-tags.
Playwright Integration
With pytest-playwright installed, the plugin instruments the page fixture on its own. There is nothing to enable and no base class to inherit.
def test_login(page):
page.goto("https://example.com/login") # each of these becomes a step
page.fill("#email", "user@example.com")
page.click("button[type=submit]")
Direct page calls - goto, click, fill, press, wait_for_selector and the rest of the Page API - become steps carrying their selector and timing, and a screenshot is captured and attached when a test fails.
Locator calls do not become steps. page.get_by_role("button").click() goes through a Locator, which the plugin does not wrap; put those inside with step(...) if you want them in the report.
Video and trace files are uploaded too, on failing tests only, and only if you asked pytest-playwright to record them - both are off by default:
pytest --video=retain-on-failure --tracing=retain-on-failure
Retries
With pytest-rerunfailures, every attempt is reported, not only the final verdict, so a flaky test is visible as a flaky test rather than as a pass.
pytest --reruns 2
The summary counts attempts, so one test that passed on its third try reads as Tests: 3/3 reported.
Parallel Runs
Under pytest-xdist the whole run is a single launch, with no configuration: the controller starts it, every worker reports its own slice into it, and the controller closes it. Only the controller prints the run id and the summary, so -n 8 does not print eight of them.
pytest -n auto
CI Auto-Detection
Running in GitHub Actions, GitLab CI, Jenkins, Bitbucket Pipelines, Azure DevOps, CircleCI or Travis CI needs no configuration: branch, commit, pipeline, build number, build URL and who triggered it are detected and attached to the run. Anything you set through --m00n-attributes or M00N_ATTRIBUTE_<NAME> overrides a detected value.
# .github/workflows/tests.yml
name: Tests
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install -r requirements.txt
- run: pytest
env:
M00N_SERVER_URL: https://m00nreport.com
M00N_API_KEY: ${{ secrets.M00N_API_KEY }}
What Gets Sent
To your server URL over HTTPS: test node ids and file paths, step names and timings, outcomes, error messages and stack traces, the CI attributes above, and whatever you pass to attach().
A stack trace is pytest's own long representation of the failure - the same text pytest prints in its FAILURES section - so it carries the source lines around the failing assertion.
Not sent: environment variables beyond the CI attributes above. The API key is never printed, including under --m00n-debug.
Troubleshooting
Warnings go to stderr, prefixed [m00nreport]. The run id line, the summary and every --m00n-debug line go to stdout, so redirecting only stderr to a file captures the warnings and loses the run id. The suite stays green in every case below.
| Message or symptom | Cause |
|---|---|
serverUrl and apiKey are required. Reporter disabled. |
Exactly one of the two resolved, which usually means a typo in an environment variable name. |
Server unavailable (<url>). Reporter disabled - tests will continue without reporting. |
Nothing answered at that URL. Check for a typo, an /api suffix, or a host reachable only over VPN. |
Unauthorized. Reporter disabled. |
The key was rejected: unknown, revoked, or belonging to an inactive organization. The wording is the server's own, so another rejection reads differently, but Reporter disabled. always means a permanent error - attempted once, never retried. |
HTTP 503. Reporter disabled for this run. |
The server answered its health check but could not start the run, after three attempts. Most often a deploy in progress. |
Multiple service failures detected. Disabling reporter for remaining tests. |
Five consecutive service failures tripped the breaker. The run is reported up to that point and no further. |
No [m00nreport] line and no run in the app |
Neither the URL nor the key resolved, so the plugin stayed silently inactive. This is the expected state on a developer machine with nothing configured. In CI it is most often a forked pull request: GitHub does not expose secrets to them, so secrets.M00N_API_KEY is an empty string. |
| A run appears, but with no steps from Playwright | pytest-playwright is not installed, so the layer stayed off. Install the [playwright] extra. |
| Retries appear as a single result | pytest-rerunfailures is not installed, so pytest never reran the test. |
--m00n-debug prints every HTTP retry and any final exhaustion, which distinguishes "never tried" from "tried and could not reach the server".
Known Limitations
- Teardown-phase failures are not reported. If the test body passes but a fixture teardown raises afterwards, the test keeps the status it already reported at call time.
- Runs are not linked to releases. The plugin reports a launch; attaching launches to a release is done in the app or over MCP.
Support
License
MIT License. See LICENSE.
Metadata
Release files for m00nreport-pytest 1.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| m00nreport_pytest-1.1.0.tar.gz | 59.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| m00nreport_pytest-1.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 93.9 kB
Release files / m00nreport_pytest-1.1.0.tar.gz
| Download URL | m00nreport_pytest-1.1.0.tar.gz |
|---|---|
| Size | 59.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
92ccc23e26539d6df2f3bee333b9af0adc46ba583551a80d6cdf8b82958e74d2
|
|
BLAKE2b-256 checksum How to use checksums |
74790173899cfd2dff8c51df2d60ae61ffd74c895fa0c731e2e6494612e2400a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.10
|
Release files / m00nreport_pytest-1.1.0-py3-none-any.whl
| Download URL | m00nreport_pytest-1.1.0-py3-none-any.whl |
|---|---|
| Size | 34.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a02e6ac462f31a33079e0070aa14fceeafd365628f3b46ba79854c7e65747e51
|
|
BLAKE2b-256 checksum How to use checksums |
c8121b93baacc29b567fc071560785848666d82e9b5e8923301806fbf59c2d4e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.10
|