---
name: mp-testing
description: >-
  Doctrine for testing a WeChat Mini Program through the dsh-mp-automator
  tools (mp_session, mp_doctor, mp_inspect, mp_query, mp_act, mp_screenshot,
  mp_console, mp_eval). Use when asked to test, verify, or debug a running
  mini program. The tools carry the capabilities and the gates; this skill
  carries the judgment.
---

# Mini Program testing doctrine

The `mp_*` tools enforce the mechanical rules for you (build-freshness gate,
selector re-resolution, byte-clamped output). What they cannot enforce is
judgment — that is this document.

## The rhythm: inspect → act → verify

Every test step is three beats, never one:

1. **Inspect** — `mp_query` / `mp_inspect{mode:"snapshot"}` to see what is
   actually on the page. Never act on an element you have not seen this
   session.
2. **Act** — `mp_act` with a selector. One action per call.
3. **Verify** — read the effect: `mp_inspect{mode:"data"}` for state,
   `mp_query` for the UI, `mp_console` for errors. An action without a
   verification read is not a test step; it is a hope.

## Selector discipline

`mp_act` re-resolves your selector at act time (that is what makes it safe),
so the selector IS your handle. Choose stable ones:

- Best: semantic classes (`.submit-btn`), ids, or text-bearing components.
- Acceptable: `tag` + `nth` when the page structure is simple and verified
  this session.
- Never: an nth against a selector you have not counted THIS page visit —
  list order changes between visits.
- The `uid` column in `mp_query` output is a reading label. Do not feed uids
  back into any tool; the runtime renumbers them invisibly.

## Assertion recipe (text-first)

Geometry is the primary assertion channel — it works on every model:

- Presence: `matched >= 1` for the selector.
- Visibility: the element's flags contain **`fully-visible`** — `partial` is
  NOT visible for assertion purposes (a half-clipped button fails users).
  Flags are relative to the CURRENT scroll window (the table discloses
  `scrollTop=` when scrolled): `offscreen` can mean "merely not scrolled
  to", so scroll first if the test targets below-fold content.
- Occlusion: no `contained-by (possible mask/overlay)` pair naming your
  element as the inner one. This is a candidate, not proof — confirm by
  acting: a truly covered element's tap will not produce the expected state
  change (verify beat).
- `read-failed` flags mean the runtime could not be read (common on
  Skyline) — that is an infrastructure fact, not an app fact. Narrow the
  selector; never convert a read failure into an app verdict.
- On vision-capable routes `mp_screenshot` also attaches the pixels
  (`<imageStatus>attached</imageStatus>`) — use the image to judge what
  geometry cannot (color, rendering artifacts, textual legibility), and keep
  geometry as the reproducible record.

## Evidence discipline

- Quote tool output verbatim in findings; never restate numbers from memory.
- A green run on a stale build proves nothing — that is why the freshness
  gate exists. When it refuses, rebuild first; do not switch the gate off to
  make a test pass.
- When it warns `build freshness UNKNOWN` (projects with no .ts/.js pairs),
  carry that warning into your report verbatim.
- `mp_console` errors after an action belong to that action until proven
  otherwise.
- Screenshots land in `captures/` inside the project — name them after the
  test step (`mp_screenshot{name:"after-login"}`), so a human can replay
  your run from the artifacts.

## Screenshot economy (vision routes)

On image-capable routes every ATTACHED screenshot keeps billing on every
later request — it stays in your context. The tool enforces a budget
(default 3 attaches per session) and skips re-attaching an unchanged screen;
your discipline on top of that:

- Screenshot at MILESTONES (after a flow completes, when a visual defect is
  suspected), never per-step — geometry answers most assertions for free.
- When the result says `image budget exhausted` or `NOT re-attached`, that
  is deliberate economy, not a failure: continue with the geometry table.
- Never ask the operator to raise `imageBudget` just to avoid reading the
  fact table.
- Native overlays (`wx.showLoading`, `showToast`, modal dialogs) render in
  a layer the DevTools screencap does NOT capture — the PNG is byte-identical
  with and without them (verified live). Never use a screenshot to assert a
  toast/loading appeared; a screenshot right after one will correctly dedupe
  as `NOT re-attached`.

## When something fails

Read the error's remedy line first — the tools map the CLI's failure codes
to next actions (`AUTOMATION_PORT_TIMEOUT` → enable the automation port;
`APP_NOT_RUNNING` → the BUILD is broken, check the DevTools console, do not
retry). `mp_session{action:"restart"}` is the reset lever. If `mp_doctor`
shows the project failing to resolve, you are in the wrong directory — the
session must run inside the mini program project.

## The boundary, honestly

Automator-layer instrumentation (`mockWxMethod`, `network*`, `media*`) is
not exposed by any tool and `mp_eval` cannot reach it. If a test genuinely
needs it, say so and hand the step to the human with the exact `vince-mp`
command — do not improvise around the boundary.

`mp_eval` is a different matter: it runs inside the page's appservice VM,
where `wx.*` and `getCurrentPages()` are in reach — so it CAN mutate storage
and page state. That power is deliberate (some tests need it) and gated
(build freshness; a deployment kill-switch). The discipline is yours:

- Use it to mutate ONLY when the test requires that mutation, and say so in
  your report — a state change you caused is not a finding about the app.
- Never use it to bypass a gate that just refused you.
- Prefer `mp_inspect`/`mp_query` for every read it could also do.
