forgeqe-cli 2.2.1__py3-none-any.whl → 2.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
forgeqe_cli/__init__.py CHANGED
@@ -4,4 +4,4 @@ The agent itself is the npm package ``@capagents/forgeqe`` (OpenTUI + Bun).
4
4
  This distribution only puts a ``forgeqe`` command on PATH.
5
5
  """
6
6
 
7
- __version__ = "2.2.1"
7
+ __version__ = "2.2.2"
@@ -0,0 +1,132 @@
1
+ """ForgeQE plugin for pytest: an HTML report after every run, from the terminal, an IDE's test runner, or CI.
2
+
3
+ pytest loads it by itself from the forgeqe-cli wheel (the pytest11 entry point). It is on in a folder with
4
+ forgeqe.yaml or .forgeqe/, or with ``pytest --forgeqe-report`` or FORGEQE_REPORT=1; FORGEQE_REPORT=0 turns it off.
5
+ At the end of the run it writes the results, with each failure's message and traceback, to
6
+ .pytest_cache/forgeqe/results.json and runs ``forgeqe report`` on it.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import os
13
+ import re
14
+ import subprocess
15
+ import sys
16
+ import tempfile
17
+ import time
18
+ from datetime import datetime, timezone
19
+ from pathlib import Path
20
+
21
+ ANSI = re.compile(r"\x1b\[[0-9;]*[A-Za-z]")
22
+
23
+
24
+ def _clean(text: str) -> str:
25
+ return ANSI.sub("", text or "")
26
+
27
+
28
+ def pytest_addoption(parser):
29
+ parser.getgroup("forgeqe").addoption(
30
+ "--forgeqe-report", action="store_true", default=False, help="write a ForgeQE HTML report after the run"
31
+ )
32
+
33
+
34
+ def _active(config) -> bool:
35
+ flag = os.environ.get("FORGEQE_REPORT")
36
+ if flag == "0":
37
+ return False
38
+ if flag == "1" or config.getoption("forgeqe_report", False):
39
+ return True
40
+ root = Path(str(config.rootpath))
41
+ return (root / "forgeqe.yaml").exists() or (root / ".forgeqe").is_dir()
42
+
43
+
44
+ def pytest_configure(config):
45
+ # An xdist worker sends its results to the controller, which writes the one report.
46
+ if hasattr(config, "workerinput") or not _active(config):
47
+ return
48
+ config.pluginmanager.register(_Collector(config), "forgeqe-collector")
49
+
50
+
51
+ def _message(report) -> str:
52
+ crash = getattr(getattr(report, "longrepr", None), "reprcrash", None)
53
+ text = crash.message if crash is not None else str(getattr(report, "longrepr", "") or "")
54
+ first = next((line.strip() for line in _clean(text).splitlines() if line.strip()), "")
55
+ return first[:300]
56
+
57
+
58
+ class _Collector:
59
+ def __init__(self, config):
60
+ self.config = config
61
+ self.tests: dict[str, dict] = {}
62
+ self.started = datetime.now(timezone.utc)
63
+ self.t0 = time.monotonic()
64
+ self.line = ""
65
+
66
+ def pytest_runtest_logreport(self, report):
67
+ nodeid = report.nodeid
68
+ test = self.tests.setdefault(
69
+ nodeid, {"name": " › ".join(nodeid.split("::")), "status": "passed", "seconds": 0.0, "file": report.location[0]}
70
+ )
71
+ if report.outcome == "rerun":
72
+ # pytest-rerunfailures: a failure that is run again; passing later makes the test flaky, not failed.
73
+ test["rerun"] = True
74
+ test.setdefault("message", _message(report))
75
+ test.setdefault("details", _clean(report.longreprtext))
76
+ return
77
+ test["seconds"] = round(test["seconds"] + (report.duration or 0), 3)
78
+ if report.failed:
79
+ if test["status"] != "failed":
80
+ test["message"] = _message(report)
81
+ test["details"] = _clean(report.longreprtext)
82
+ test["status"] = "failed"
83
+ elif report.skipped and test["status"] == "passed" and report.when in ("setup", "call"):
84
+ test["status"] = "skipped"
85
+
86
+ def pytest_sessionfinish(self, session):
87
+ if not self.tests:
88
+ return
89
+ tests = []
90
+ for test in self.tests.values():
91
+ rerun = test.pop("rerun", False)
92
+ if test["status"] == "passed" and rerun:
93
+ test["flaky"] = True
94
+ elif test["status"] != "failed" and not test.get("flaky"):
95
+ test.pop("message", None)
96
+ test.pop("details", None)
97
+ tests.append({k: v for k, v in test.items() if v not in ("", None)})
98
+ doc = {
99
+ "format": "forgeqe-results",
100
+ "version": 1,
101
+ "framework": "pytest",
102
+ "startedAt": self.started.isoformat(),
103
+ "ms": round((time.monotonic() - self.t0) * 1000),
104
+ "command": " ".join(["pytest", *self.config.invocation_params.args])[:500],
105
+ "tests": tests,
106
+ }
107
+ cache = getattr(self.config, "cache", None)
108
+ folder = Path(cache.mkdir("forgeqe")) if cache is not None else Path(tempfile.mkdtemp(prefix="forgeqe-"))
109
+ path = folder / "results.json"
110
+ path.write_text(json.dumps(doc, indent=2) + "\n", encoding="utf-8")
111
+ self.line = _report(path, Path(str(self.config.rootpath)))
112
+
113
+ def pytest_terminal_summary(self, terminalreporter):
114
+ if self.line:
115
+ terminalreporter.write_line(self.line)
116
+
117
+
118
+ def _report(path: Path, root: Path) -> str:
119
+ """Run forgeqe report on the results; the line to show."""
120
+ # This wheel's own launcher, by the Python running pytest: no PATH lookup and no .cmd shim on Windows.
121
+ bin_ = os.environ.get("FORGEQE_BIN")
122
+ cmd = [bin_, "report", str(path)] if bin_ else [sys.executable, "-m", "forgeqe_cli", "report", str(path)]
123
+ try:
124
+ run = subprocess.run(cmd, cwd=str(root), capture_output=True, text=True, timeout=300)
125
+ except (OSError, subprocess.TimeoutExpired) as exc:
126
+ return f"ForgeQE: no report ({exc.__class__.__name__}). Build it with: forgeqe report {path}"
127
+ # forgeqe report exits 1 when a test failed: that is a report, not an error.
128
+ line = next((l for l in _clean(run.stdout).splitlines() if l.startswith("Report")), "") if run.returncode <= 1 else ""
129
+ if line:
130
+ return "ForgeQE report: " + line[len("Report"):].strip()
131
+ why = (_clean(run.stderr).strip().splitlines() or ["it did not finish"])[-1]
132
+ return f"ForgeQE: no report ({why}). Build it with: forgeqe report {path}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: forgeqe-cli
3
- Version: 2.2.1
3
+ Version: 2.2.2
4
4
  Summary: pip launcher for the ForgeQE browser agent (npm package: @capagents/forgeqe)
5
5
  Author: CLI-Agents
6
6
  License: MIT
@@ -0,0 +1,10 @@
1
+ forgeqe_cli/__init__.py,sha256=P8v4jSY_ROSVD98F0zQcv49wFe88He6hTop3NqlqJrg,200
2
+ forgeqe_cli/__main__.py,sha256=frkRQzq1Cd7HI3aiw0W0eJ-w1r8VkXOJrv0wIR-on9Y,46
3
+ forgeqe_cli/launcher.py,sha256=GKmbo1W4xpNz2I3smM0CXGbnAYRdwhrX8Pe48Gr4kgs,6413
4
+ forgeqe_cli/pytest_plugin.py,sha256=f_hH2JXv059oJwHBBdR-TZAhuFYqM2LMr6vAAcRpC5o,5570
5
+ forgeqe_cli-2.2.2.dist-info/licenses/LICENSE,sha256=Z_3_KODt79ZZl4nLelIVBSsVsL6AhEypl7HouMkKjYA,3079
6
+ forgeqe_cli-2.2.2.dist-info/METADATA,sha256=LB2sIwF4wFAVpWvZgpbo42iftHYyk4fXcQDbnDBkQ84,50190
7
+ forgeqe_cli-2.2.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
8
+ forgeqe_cli-2.2.2.dist-info/entry_points.txt,sha256=KphefM5TY2MOIbABNg2UJJQsEZUZgrNM4ZD3lrIphVg,139
9
+ forgeqe_cli-2.2.2.dist-info/top_level.txt,sha256=YrCSJdE_QY2GlHw_tgRYsQpZ1cSiIejC9swAUcRLpD8,12
10
+ forgeqe_cli-2.2.2.dist-info/RECORD,,
@@ -1,3 +1,6 @@
1
1
  [console_scripts]
2
2
  forgeqe = forgeqe_cli.launcher:main
3
3
  webpilot = forgeqe_cli.launcher:main
4
+
5
+ [pytest11]
6
+ forgeqe = forgeqe_cli.pytest_plugin
@@ -1,9 +0,0 @@
1
- forgeqe_cli/__init__.py,sha256=RuUCEANfbsqtwHS91AF7Tut7cJrNq7Ui3h50bGu6aXk,200
2
- forgeqe_cli/__main__.py,sha256=frkRQzq1Cd7HI3aiw0W0eJ-w1r8VkXOJrv0wIR-on9Y,46
3
- forgeqe_cli/launcher.py,sha256=GKmbo1W4xpNz2I3smM0CXGbnAYRdwhrX8Pe48Gr4kgs,6413
4
- forgeqe_cli-2.2.1.dist-info/licenses/LICENSE,sha256=Z_3_KODt79ZZl4nLelIVBSsVsL6AhEypl7HouMkKjYA,3079
5
- forgeqe_cli-2.2.1.dist-info/METADATA,sha256=RmpSV-v_5NpO2Gnc-0fi5ur1IUjwAc6vfkWwhZvEmqo,50190
6
- forgeqe_cli-2.2.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
7
- forgeqe_cli-2.2.1.dist-info/entry_points.txt,sha256=1IHZSsCnhKZfoCBsdScd7pRKwRzQ5bruc1Nk_FA5SI4,91
8
- forgeqe_cli-2.2.1.dist-info/top_level.txt,sha256=YrCSJdE_QY2GlHw_tgRYsQpZ1cSiIejC9swAUcRLpD8,12
9
- forgeqe_cli-2.2.1.dist-info/RECORD,,