Metadata-Version: 2.4
Name: mgf-uitest-desktop
Version: 0.1.2
Summary: Shared observable-behavior UI testing for the Magogi PySide6/Qt desktop apps — drives each app's real widget tree in an offscreen QApplication and asserts the pillars an in-process handler call can't see: layout integrity (nothing squeezed below its own minimum, no elided text, no child escaping its parent), accessibility (accessible-name coverage + focus-chain reachability), runtime health (no QtWarning/QtCritical, no unraisable exception), plus real input events, modal-dialog auto-answer and a screenshot gallery, across a viewport x theme matrix. The desktop half of the mgf-uitest family (web sibling: mgf-uitest-web). Sibling under the mgf.* namespace.
Project-URL: Homepage, https://codeberg.org/magogi-admin/mgf-uitest-desktop
Project-URL: Issues, https://codeberg.org/magogi-admin/mgf-uitest-desktop/issues
Project-URL: Feedback, https://codeberg.org/magogi-admin/mgf-uitest-desktop/src/branch/main/FEEDBACK.md
Author: Bassam Alsanie, mgf-uitest-desktop contributors
License: Apache-2.0
License-File: LICENSE
License-File: NOTICE
Keywords: accessibility,desktop,magogi,pyside6,pytest-qt,qt,ui-testing
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Environment :: X11 Applications :: Qt
Classifier: Framework :: Pytest
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Testing
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: pyside6<7,>=6.7
Requires-Dist: pytest-qt<5,>=4.4
Requires-Dist: pytest<10,>=8.0
Provides-Extra: dev
Requires-Dist: coverage[toml]>=7.10; extra == 'dev'
Requires-Dist: import-linter<3.0,>=2.0; extra == 'dev'
Requires-Dist: mgf-standard<0.2,>=0.1.57; extra == 'dev'
Requires-Dist: mgf-test-supervisor<0.2,>=0.1.20; extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest-timeout<3,>=2.3; extra == 'dev'
Requires-Dist: ruff<0.16,>=0.4; extra == 'dev'
Provides-Extra: standards
Requires-Dist: mgf-common[standards]<1.0,>=0.50; extra == 'standards'
Description-Content-Type: text/markdown

# mgf-uitest-desktop

Observable-behavior UI testing for the Magogi PySide6/Qt desktop apps — the
missing tier.

Every Magogi desktop app is a PySide6 application whose GUI suite, today, calls
its handlers directly:

```python
window._retry_connection()          # the current shape
assert window._status_label.text() == "Retrying…"
```

That proves the handler works. It proves nothing about the **button**. Disconnect
`retry_button.clicked` and the suite stays green — which is not a hypothetical:
hrb-app shipped a visibly-broken button past a green suite on 2026-07-16, and the
audit that followed found **zero** real input events in the whole suite, four
`objectName`s, and no `setAccessibleName` anywhere.

This package closes that gap. It drives each app's **real widget tree** in an
offscreen `QApplication`, with **real posted input events**, and asserts the
pillars a handler call cannot see.

```python
def test_greeting(console):
    console.click("#greet-button")                    # a real mouse event
    assert "Hello, world" in console.text("#greeting")
    console.assert_ok()                               # layout + a11y + health
    console.snapshot("greeting")
```

## §1. The pillars

1. **Layout integrity** — reads Qt's real geometry to catch what an operator
   notices: text elided because `QFontMetrics` says it never fitted, a child whose
   rect escapes its parent, a window squeezed below its own `minimumSizeHint()`.
   Deterministic; **no image baseline** to bless.
2. **Functional** — drive the real golden paths through real input events, and
   assert the observable outcome.
3. **Accessibility** — every interactive widget has an accessible name and is
   reachable by Tab. Honest scope: this is **not** axe — see §4.
4. **Runtime health** — zero Qt warnings/criticals, zero unraisable exceptions.
   Qt fails quietly; this is what makes it audible.
5. **Responsive** — the above across a viewport × theme (× DPI scale) matrix.
6. **Visual gallery** — PNGs per matrix cell, for human eyeballing.

Plus the two things a Qt suite cannot do without: **bounded waits** instead of
wall-clock sleeps, and **modal-dialog auto-answer** — because
`QMessageBox.exec()` blocks inside C++, so a click on a button that asks "are you
sure?" does not fail a test today, it *hangs* it. That is why the fleet's
destructive verbs are untested, and `expect_dialog` is the way in.

## §2. How it drives an app

In-process, through the app's own window factory. The consumer provides exactly
**one** fixture — `app_launch`, returning an `AppLaunch` that says how to build
its real top-level window — and the plugin supplies the rest:

```
app_launch  →  ui_app (offscreen QApplication, session)  →  ui_window
               (built · sized · themed · exposed · ready) →  console
```

The harness never imports `mgf-hrb`, `mgf-common`, `mgf-brand-qt`, or any app's
domain stack — mechanically pinned by an import-linter contract. Its runtime
dependencies are `PySide6`, `pytest` and `pytest-qt`, and **nothing `mgf-*` at
all**.

## §3. Family

The desktop half of the `mgf-uitest` family. `mgf-uitest-web` is the web half, and
the shared vocabulary is deliberate: same fixture name (`console`), same
`*_launch` override contract, same seven pillar names, same `describe()`-as-
assertion-message discipline, same `{slug}__{cell}.png` gallery convention. The
two share **names, not code** — `PUBLIC_API.md` §5 maps every name web→desktop and
records where the two genuinely differ. Genuinely shared plumbing gets extracted
into an `mgf-uitest-core` **only if a third consumer ever needs it** — not on
speculation.

## §4. What this is not — read before trusting a green run

- **`assert_accessible` is not axe.** The web sibling runs axe-core: ~90 audited
  WCAG rules maintained by Deque. Qt has no axe, and this library vendors no rule
  corpus. It checks **two structural properties** — every interactive control has
  an accessible name, and every one is reachable by Tab — which is the floor below
  which assistive technology sees an unlabelled blank. Passing it is not a WCAG
  claim. Colour contrast is absent on purpose: it belongs with the design tokens
  that define the colours (`mgf-brand-qt`), not with the harness that drives
  widgets.
- **There is no pixel baseline.** Screenshots are a gallery for human eyes, the
  same stance as the web half. `grab_differs` is a same-process pixel *delta*
  probe ("did this repaint at all"), not a blessed-baseline diff.
- Full boundary: [`SCOPE.md`](SCOPE.md).

## §5. Setup

```bash
uv venv --python 3.13
uv pip install -e ".[dev]"
```

Qt needs no browser install, but it does need a platform plugin: the pytest plugin
sets `QT_QPA_PLATFORM=offscreen` for you (with `setdefault`, so
`QT_QPA_PLATFORM=xcb pytest -k clipping` still shows you the window when you are
debugging a finding).

## §6. Running the suite

```bash
pytest --cov --cov-report=term-missing
```

Run the gates **unpiped** and read the real exit code. This is not style: a Qt
suite can print `61 passed` and then die of SIGSEGV, and `pytest | tail` reports
the pipe's exit status, turning 139 into 0. That is how a real intermittent
segfault stayed invisible in this fleet for weeks
([`LESSON_LEARNED.md`](LESSON_LEARNED.md)).

Screenshots land in `tests/_artifacts/gallery/`
(`{name}__{viewport}__{theme}__{scale}x.png`). See `tests/integration/conftest.py`
for a worked `app_launch`.

**The app specs live in the apps, not here.** This repo's own suite proves the
library against a tiny in-repo sample window — including a test that seeds the
sample's button **unwired** and asserts the gate fires. A harness that claims to
catch dead buttons should have to prove it on one.

## §7. Standards conformance

`mgf-uitest-desktop` declares **L2 — Standard** (CFM-02): conform to every MUST
rule, evidenced by a per-rule audit —
[`docs/inprogress/MGF_STANDARDS_CONFORMANCE.md`](docs/inprogress/MGF_STANDARDS_CONFORMANCE.md).
As a 0.1.0 founding, that ledger is declared **provisional**: the mechanical gates
are green and the DOC-01 root set is complete, but the per-rule fresh-eyes audit
across the full corpus has not been run yet, and the ledger says so rather than
claiming a number it has not earned.

The public contract is [`PUBLIC_API.md`](PUBLIC_API.md) (every name, its stability
tier); the boundary is [`SCOPE.md`](SCOPE.md).
