Files
hermes-agent/scripts/ci/e2e_screenshot_status.py
ethernet 2ab7d9bfe3 chore(ci): remove publish-e2e-evidence pipeline
gh v2.99 gained a native --attach flag for issues, PRs and comments, so
the custom trusted-publisher chain (gh-image extension + GH_IMAGE_SESSION_TOKEN
workflow_run job + attachment-upload script) is superseded.

Remove:
- .github/workflows/publish-e2e-evidence.yml (workflow_run publisher)
- scripts/ci/publish_e2e_evidence.py + its tests
- e2e-evidence-* artifact upload + evidence staging in e2e-desktop.yml /
  e2e_screenshot_status.py, incl. the 'inline evidence is publishing...'
  marker placeholder in the CI review comment status

Keep: the review-comment screenshot/diff counts and artifact links produced
by e2e_screenshot_status.py.
2026-09-24 23:53:17 -04:00

111 lines
4.0 KiB
Python

#!/usr/bin/env python3
"""Select Desktop E2E visual evidence and build its CI review status."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
SOURCE = "playwright e2e"
def _files(root: Path, pattern: str) -> list[Path]:
return sorted(path for path in root.rglob(pattern) if path.is_file()) if root.exists() else []
def _is_explicit_screenshot(path: Path) -> bool:
"""Exclude Playwright's automatic and visual-comparator PNG outputs."""
return not (
path.name.startswith(("test-finished-", "test-failed-"))
or path.name.endswith(("-actual.png", "-expected.png", "-diff.png"))
)
def build_manifest(results_dir: Path) -> dict:
"""Record stable screenshot names from one E2E run for main/PR comparison."""
screenshots = [path for path in _files(results_dir, "*.png") if _is_explicit_screenshot(path)]
return {"version": 1, "screenshot_names": sorted({path.name for path in screenshots})}
def _base_screenshot_names(path: Path | None) -> set[str] | None:
"""Return ``None`` when main evidence is unavailable (never guess newness)."""
if path is None or not path.is_file():
return None
try:
data = json.loads(path.read_text(encoding="utf-8-sig"))
except json.JSONDecodeError:
return None
names = data.get("screenshot_names", []) if isinstance(data, dict) else []
if not isinstance(data, dict) or not isinstance(names, list):
return None
return {name for name in names if isinstance(name, str)}
def select_evidence(results_dir: Path, base_manifest: Path | None = None) -> dict:
"""Select only screenshots new to main, plus every generated visual diff."""
base_names = _base_screenshot_names(base_manifest)
screenshots = [] if base_names is None else [
path for path in _files(results_dir, "*.png")
if _is_explicit_screenshot(path) and path.name not in base_names
]
diffs: list[dict[str, Path]] = []
for diff in _files(results_dir, "*-diff.png"):
stem = diff.with_name(diff.name.removesuffix("-diff.png"))
entry = {"diff": diff}
for kind in ("actual", "expected"):
candidate = stem.with_name(f"{stem.name}-{kind}.png")
if candidate.is_file():
entry[kind] = candidate
diffs.append(entry)
return {"screenshots": screenshots, "diffs": diffs}
def build_status(selection: dict, artifact_url: str = "") -> list[dict]:
"""Return the review status for the unified CI review comment."""
screenshots = selection["screenshots"]
diffs = selection["diffs"]
if not screenshots and not diffs:
return []
summary_parts = []
if screenshots:
summary_parts.append(
f"{len(screenshots)} new screenshot{'s' if len(screenshots) != 1 else ''} vs main"
)
if diffs:
summary_parts.append(f"{len(diffs)} visual diff{'s' if len(diffs) != 1 else ''}")
result: dict[str, str] = {
"kind": "info",
"title": "Desktop E2E visual evidence",
"summary": "; ".join(summary_parts) + ".",
}
if artifact_url:
result["link"] = artifact_url
result["link_label"] = "View test artifacts"
return [{"source": SOURCE, "results": [result]}]
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--results-dir", type=Path, required=True)
parser.add_argument("--base-manifest", type=Path)
parser.add_argument("--manifest-output", type=Path, required=True)
parser.add_argument("--artifact-url", default="")
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
args.manifest_output.write_text(
json.dumps(build_manifest(args.results_dir), sort_keys=True) + "\n", encoding="utf-8"
)
selection = select_evidence(args.results_dir, args.base_manifest)
args.output.write_text(
json.dumps(build_status(selection, args.artifact_url)) + "\n", encoding="utf-8"
)
return 0
if __name__ == "__main__":
raise SystemExit(main())