#!/usr/bin/env python3 """Publish a completed full run with explicitly matched conditional-case receipts.""" import argparse import hashlib import importlib.util import json from pathlib import Path ROOT = Path(__file__).resolve().parents[2] BASE = ROOT / "tests/golden/WBV2-P1424-001" spec = importlib.util.spec_from_file_location("full_smoke", ROOT / "tools/web/run-wbv2-full-smoke.py") runner = importlib.util.module_from_spec(spec) spec.loader.exec_module(runner) write_json, artifact, now = runner.write_json, runner.artifact, runner.now def load(path): return json.loads(path.read_text()) def digest(path): return hashlib.sha256(path.read_bytes()).hexdigest() def main(): parser = argparse.ArgumentParser() parser.add_argument("run", type=Path) parser.add_argument("--supplement", type=Path, action="append", default=[]) parser.add_argument("--repair", type=Path, action="append", default=[], help="Complete spec receipt after a test-only correction; runtime input drift is rejected") args = parser.parse_args() run = args.run.resolve() original = load(run / "summary.json") inputs = load(run / "inputs.json") blockers = [] if original.get("status") in ("RUNNING", "INTERRUPTED") or len(original.get("groups", [])) != 3: raise RuntimeError("The main and both dedicated configurations must finish before publication") drift = [ref["path"] for ref in inputs if not (ROOT / ref["path"]).is_file() or digest(ROOT / ref["path"]) != ref["sha256"]] supplement_rows = {} supplement_receipts = [] repair_rows = {} repair_receipts = [] repair_paths = set() for kind, folder in [("supplement", p) for p in args.supplement] + [("repair", p) for p in args.repair]: folder = folder.resolve() receipt = load(folder / "result.json") report = load(folder / "playwright.json") if receipt.get("status") != "PASS" or receipt.get("exitCode") != 0 or report.get("errors"): raise RuntimeError(f"Supplement did not pass: {folder}") if receipt["source"]["sha256"] != digest(ROOT / receipt["source"]["path"]): raise RuntimeError(f"Supplement source changed: {folder}") for ref in receipt["artifacts"]: if digest(ROOT / ref["path"]) != ref["sha256"]: raise RuntimeError(f"Supplement artifact changed: {ref['path']}") for case in runner.rows(report.get("suites", [])): if case["status"] != "expected": raise RuntimeError(f"Supplement contains an unpassed case: {case['title']}") key = (case["spec"], case["title"]) target = supplement_rows if kind == "supplement" else repair_rows if key in target: raise RuntimeError(f"Duplicate supplement: {key}") target[key] = {**case, "receipt": artifact(folder / "result.json")} if kind == "supplement": supplement_receipts.append(artifact(folder / "result.json")) else: source = receipt["source"]["path"] if not source.startswith("web/tests/e2e/") or not source.endswith(".spec.ts") or source not in drift: raise RuntimeError("Only changed E2E spec files can use a test-only repair receipt") repair_paths.add(source) repair_receipts.append({"source": receipt["source"], "receipt": artifact(folder / "result.json"), "originalSource": next(ref for ref in inputs if ref["path"] == source)}) runtime_drift = [name for name in drift if name not in repair_paths] if runtime_drift or not original.get("inputsUnchanged"): blockers.append({"kind": "INPUT_DRIFT", "paths": runtime_drift, "nextAction": "Run a new complete batch on the changed runtime inputs"}) repaired_specs = {Path(name).name for name in repair_paths} for name in repaired_specs: previous = [case for group in original["groups"] for case in group.get("tests", []) if case["spec"] == name] replacement = [case for (filename, _), case in repair_rows.items() if filename == name] if not previous or len(replacement) < len(previous): raise RuntimeError(f"Repair must execute the complete spec: {name}") unchanged_titles = {case["title"] for case in previous if case["status"] == "expected"} if not unchanged_titles.issubset({case["title"] for case in replacement}): raise RuntimeError(f"Repair removed a previously passing test: {name}") cases = [] conditional = [] for group in original["groups"]: ref = group.get("report") if ref and digest(ROOT / ref["path"]) != ref["sha256"]: raise RuntimeError(f"Group report changed: {group['group']}") if group.get("exitCode") not in (0, 1) or group.get("errors") or group.get("status") not in ("PASS", "INCOMPLETE", "FAILED"): blockers.append({"kind": "GROUP_FAILURE", "group": group["group"], "status": group["status"], "nextAction": "Inspect the group's JSON errors and failure attachments"}) if group.get("exitCode") == 1: failed_in_group = [case for case in group.get("tests", []) if case["status"] not in ("expected", "skipped")] if not failed_in_group or any(case["spec"] not in repaired_specs for case in failed_in_group): blockers.append({"kind": "UNRESOLVED_GROUP_EXIT", "group": group["group"], "nextAction": "Resolve every failing spec or the non-test runner error"}) for case in group.get("tests", []): if case["spec"] in repaired_specs: continue key = (case["spec"], case["title"]) if case["status"] == "skipped" and key in supplement_rows: replacement = supplement_rows.pop(key) conditional.append({"spec": case["spec"], "title": case["title"], "originalStatus": "skipped", "supplement": replacement["receipt"]}) cases.append(replacement) else: cases.append(case) cases.extend(repair_rows.values()) if supplement_rows: raise RuntimeError("Supplement cases must match a collected conditional skip exactly") failed = [case for case in cases if case["status"] != "expected"] blockers.extend({"kind": "TEST_NOT_PASSED", **case, "nextAction": "Repair the concrete failing workflow using its test and local Blender source"} for case in failed) expected = set(original["expectedSpecs"]) observed = {case["spec"] for case in cases} if expected != observed: blockers.append({"kind": "SPEC_COLLECTION_GAP", "missing": sorted(expected - observed), "unexpected": sorted(observed - expected), "nextAction": "Supply the missing configuration or fixture and execute it"}) catalog = load(ROOT / "web/app/src/capabilities/blender-feature-route-catalog.json") soak_path = run / "main/soak.json" soak = load(soak_path) if soak_path.is_file() else {} if soak.get("profile") != "FORMAL" or soak.get("status") != "READY" or soak.get("actualDurationMs", 0) < 1_800_000: blockers.append({"kind": "FORMAL_SOAK_INCOMPLETE", "nextAction": "Complete the formal 30-minute editing and recovery test"}) report = { "schemaVersion": 1, "task": "WBV2-P1424-001", "updatedAt": now(), "scope": "Complete Chromium E2E batch plus both dedicated configurations and explicitly configured conditional cases", "status": "PASS" if not blockers else "FAILED", "fullDesktopParity": "NOT_ESTABLISHED", "totalSpecs": len(expected), "passedSpecs": sum(all(c["status"] == "expected" for c in cases if c["spec"] == name) for name in expected & observed), "totalTests": len(cases), "passedTests": sum(c["status"] == "expected" for c in cases), "failedTests": len(failed), "unresolvedSkips": sum(c["status"] == "skipped" for c in failed), "batch": artifact(run / "summary.json"), "inputs": artifact(run / "inputs.json"), "conditionalCases": conditional, "supplements": supplement_receipts, "testOnlyRepairs": repair_receipts, "formalSoak": {"receipt": artifact(soak_path) if soak else None, **{key: soak.get(key) for key in ("actualDurationMs", "cycles", "autosaves", "reopens", "finalSnapshotCount", "pageErrors", "observed", "limits")}}, "historical": {"totalSpecs": 243, "passed": 228, "failed": 13, "skippedConfigurations": 2}, "blockers": blockers, "sourceInventory": {"unitCount": catalog["unitCount"], "verifiedUnits": catalog["behavioralEvidence"]["verifiedUnits"], "unverifiedUnits": catalog["behavioralEvidence"]["unverifiedUnits"], "sourceTreeSha256": catalog["sourceTreeSha256"], "boundUnitDelta": 0, "freshness": "Existing catalog; smoke results do not increase source-unit coverage"}, "limitations": ["Conditional cases retain their original skipped result and an explicit configured passing receipt.", "Test-only repairs replace complete spec results; original failures and both test-source hashes remain recorded.", "Offline coverage excludes server-native services and file://.", "Passing this E2E suite is not proof of complete Blender desktop parity."], } report_path = BASE / "full-smoke-closure-report.json" write_json(report_path, report) latest_path = BASE / "full-smoke-latest.json" write_json(latest_path, {"status": report["status"], "report": artifact(report_path), "batch": report["batch"], "rawBatchStatus": original["status"]}) product_path = BASE / "product-implementation-report.json" product = load(product_path) product.setdefault("historicalFullE2ESmoke", product.get("fullE2ESmoke")) product["fullE2ESmoke"] = {"status": report["status"], "countUnit": "spec_files", "total": report["totalSpecs"], "passed": report["passedSpecs"], "failed": report["totalSpecs"] - report["passedSpecs"], "testsTotal": report["totalTests"], "testsPassed": report["passedTests"], "unresolvedSkips": report["unresolvedSkips"], "conditionalCases": len(conditional), "report": artifact(report_path)} write_json(product_path, product) status_path = ROOT / "docs/status/WBV2-P1424-001.md" lines = [line for line in status_path.read_text().splitlines() if not line.startswith("- full Chromium batch:")] lines.append(f"- full Chromium batch: {report['status']}; {report['passedSpecs']}/{report['totalSpecs']} spec files, " f"{report['passedTests']}/{report['totalTests']} tests; {len(conditional)} conditional cases explicitly supplemented; " "`tests/golden/WBV2-P1424-001/full-smoke-closure-report.json`") status_path.write_text("\n".join(lines) + "\n") manifest_path = BASE / "manifest.json" manifest = load(manifest_path) for path in (report_path, latest_path, product_path, status_path, Path(__file__).resolve(), ROOT / "tools/web/run-wbv2-full-smoke.py", ROOT / "tools/web/run-wbv2-smoke-closure.py", BASE / "full-smoke-task-report.md"): ref = artifact(path) manifest["artifacts"][ref["path"]] = {"sha256": ref["sha256"], "bytes": path.stat().st_size} write_json(manifest_path, manifest) print(json.dumps({k: report[k] for k in ("status", "totalSpecs", "passedSpecs", "totalTests", "passedTests", "blockers")}), flush=True) return 0 if not blockers else 1 if __name__ == "__main__": raise SystemExit(main())