mirror of
https://github.com/software-mansion/argent.git
synced 2026-09-14 19:27:14 +08:00
cd4190b80e
Now verified end-to-end on the real Mac: install/introspection/validation, the android tier (against a booted emulator), and the chromium tier all pass from a locally packed tgz; the rn tier auto-skips without `~/dev/bluesky` as designed. ## What this is A release-gating end-to-end harness under `scripts/e2e-full/` that starts from **only a `swmansion-argent-*.tgz` bundle** and exercises the whole product: the install flow, every CLI command, all 70 tools' argument validation, and a happy-path run of every tool against real devices. Intended to be run on the real Linux box and the real Mac before a release. Everything runs under a throwaway `HOME` + npm prefix, so it never touches the machine's real `~/.argent`, editor MCP configs, or global packages (safe on a shared box). It emits a JSONL log + a markdown coverage report and exits non-zero if any hard assertion fails. ## Phases | phase | needs | covers | |---|---|---| | `install` | npm + network | `npm i -g <tgz>`, bundled binaries, `init` (global + `--local`), `update`, `uninstall`, telemetry, MCP-config generation | | `introspection` | — | `--version/--help`, `tools describe` for **all 70** tools, feature flags, `server start/status/logs/stop`, `link/unlink` | | `validation` | — | per tool: missing-required / bad-enum / bad-type rejection (schema-driven, no hardware) | | `android` | Android emulator | happy-path of every touch/gesture/screenshot/app-lifecycle tool | | `chromium` | Electron (bundled optional dep) + a display | boots a generated Electron app; drives CDP tools (scroll/drag/tabs/cookies/storage) | | `rn` | `~/dev/bluesky` + Android device | debugger + react/native profiler + network chain against the real Bluesky app | Tiers auto-skip with a recorded reason when prerequisites are missing. iOS / tvOS / Vega are intentionally out of scope for now. ## What I actually verified (real runs, Linux) - **install** 18✓/0✗, **introspection** 85✓/0✗ (all 70 `tools describe`), **validation** 81✓/0✗, **chromium** 24✓/0✗ (real Electron app + CDP). - Holistic run: **190✓ / 0✗ / 34∼, exit 0.** Failure-gate confirmed (non-zero on fail), `bash -n` clean on all files, all 72 android/rn JSON arg literals linted. ## What I did NOT verify live (please scrutinize) - **`android` and `rn` tiers were never run against a live device.** The Android emulator would not stay up on my box (memory-constrained shared machine; QEMU threads hung seconds after boot across ~7 attempts). The code is complete and uses the same machinery the `chromium` tier exercised live, but it has **not** been proven end-to-end. This is the main reason for the "not certain how well it works" caveat. - The RN tier's exact debugger/profiler arg shapes are derived from `tools describe`, not confirmed against a running Metro. ## Run it ```bash bash scripts/e2e-full/run-e2e.sh # everything applicable to this OS bash scripts/e2e-full/run-e2e.sh --skip-install --phase introspection,validation # fast offline core bash scripts/e2e-full/run-e2e.sh --phase android --android-serial emulator-5554 # inject a device ```
111 lines
3.6 KiB
Python
111 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
||
"""Aggregate the E2E JSONL log into a markdown report.
|
||
|
||
Reads one JSON object per line: {phase, tool, case, status, detail}.
|
||
Emits: run header, per-phase pass/fail/skip summary, a per-tool coverage
|
||
matrix (was each tool validated? happy-path run?), and the full list of
|
||
failures with the exact case so a release engineer can reproduce.
|
||
"""
|
||
import json
|
||
import os
|
||
import sys
|
||
from collections import defaultdict, OrderedDict
|
||
|
||
DEVICE_PHASES = {"android", "chromium", "rn"}
|
||
|
||
|
||
def main() -> int:
|
||
path = sys.argv[1] if len(sys.argv) > 1 else os.environ.get("E2E_JSONL", "")
|
||
rows = []
|
||
with open(path) as fh:
|
||
for line in fh:
|
||
line = line.strip()
|
||
if line:
|
||
try:
|
||
rows.append(json.loads(line))
|
||
except json.JSONDecodeError:
|
||
pass
|
||
|
||
version = os.environ.get("TGZ_VERSION", "?")
|
||
osname = os.environ.get("E2E_OS", "?")
|
||
binname = os.environ.get("ARGENT_BIN", "?")
|
||
|
||
total = defaultdict(int)
|
||
per_phase = defaultdict(lambda: defaultdict(int))
|
||
tools = OrderedDict() # tool -> {"validation": status, "happy": status}
|
||
fails = []
|
||
|
||
for r in rows:
|
||
st = r.get("status", "?")
|
||
ph = r.get("phase", "?")
|
||
tool = r.get("tool", "?")
|
||
total[st] += 1
|
||
per_phase[ph][st] += 1
|
||
tools.setdefault(tool, {"validated": False, "happy": None})
|
||
if ph == "validation":
|
||
if st == "pass":
|
||
tools[tool]["validated"] = True
|
||
if ph in DEVICE_PHASES:
|
||
cur = tools[tool]["happy"]
|
||
# a single pass anywhere marks the tool happy-path covered
|
||
if st == "pass":
|
||
tools[tool]["happy"] = "pass"
|
||
elif st == "fail" and cur != "pass":
|
||
tools[tool]["happy"] = "fail"
|
||
elif st == "skip" and cur is None:
|
||
tools[tool]["happy"] = "skip"
|
||
if st == "fail":
|
||
fails.append(r)
|
||
|
||
out = []
|
||
w = out.append
|
||
w(f"# Argent full E2E report\n")
|
||
w(f"- **package:** `@swmansion/argent` v{version}")
|
||
w(f"- **os:** {osname}")
|
||
w(f"- **driver:** `{binname}`")
|
||
w(f"- **totals:** ✅ {total['pass']} pass · ❌ {total['fail']} fail · ∼ {total['skip']} skip"
|
||
f" ({sum(total.values())} cases)\n")
|
||
|
||
# Per-phase summary
|
||
w("## Per-phase\n")
|
||
w("| phase | pass | fail | skip |")
|
||
w("|---|---:|---:|---:|")
|
||
order = ["install", "introspection", "validation", "android", "chromium", "rn", "cleanup"]
|
||
seen = [p for p in order if p in per_phase] + [p for p in per_phase if p not in order]
|
||
for p in seen:
|
||
d = per_phase[p]
|
||
w(f"| {p} | {d['pass']} | {d['fail']} | {d['skip']} |")
|
||
w("")
|
||
|
||
# Coverage matrix
|
||
w("## Tool coverage\n")
|
||
w(f"{len(tools)} tools observed. `validated` = argument-schema rejection tests passed; "
|
||
"`happy-path` = a real device/app run (or why it was skipped).\n")
|
||
w("| tool | validated | happy-path |")
|
||
w("|---|:---:|:---:|")
|
||
|
||
def mark(v):
|
||
return {True: "✅", False: "—", None: "·", "pass": "✅", "fail": "❌", "skip": "∼"}.get(v, "?")
|
||
|
||
for tool in sorted(tools):
|
||
t = tools[tool]
|
||
w(f"| `{tool}` | {mark(t['validated'])} | {mark(t['happy'])} |")
|
||
w("")
|
||
|
||
# Failures
|
||
w("## Failures\n")
|
||
if not fails:
|
||
w("None. 🎉\n")
|
||
else:
|
||
for f in fails:
|
||
w(f"- **[{f.get('phase')}] `{f.get('tool')}` — {f.get('case')}**: "
|
||
f"{f.get('detail','').strip()}")
|
||
w("")
|
||
|
||
print("\n".join(out))
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|