@@ -623,15 +623,139 @@ jobs:
623623 adb -s "${SERIAL}" pull "/sdcard/googletest/test_outputfiles/allure-results" "${OUT_DIR}/${SERIAL}" >/dev/null 2>&1 || true
624624 done
625625
626+ - name : Merge Allure results (add device label)
627+ if : always()
628+ env :
629+ OUT_DIR : ${{ runner.temp }}/allure-results
630+ MERGED_DIR : ${{ runner.temp }}/allure-results-merged
631+ run : |
632+ set -euo pipefail
633+ python3 - <<'PY'
634+ import json
635+ import os
636+ import shutil
637+ import subprocess
638+ from pathlib import Path
639+
640+ out_dir = Path(os.environ["OUT_DIR"])
641+ merged_dir = Path(os.environ["MERGED_DIR"])
642+ merged_dir.mkdir(parents=True, exist_ok=True)
643+
644+ def get_prop(serial: str, prop: str) -> str:
645+ try:
646+ result = subprocess.run(
647+ ["adb", "-s", serial, "shell", "getprop", prop],
648+ check=False,
649+ capture_output=True,
650+ text=True,
651+ timeout=5,
652+ )
653+ return result.stdout.strip()
654+ except Exception:
655+ return ""
656+
657+ device_dirs = [p for p in out_dir.iterdir() if p.is_dir()]
658+ device_info = {}
659+ for device_dir in device_dirs:
660+ serial = device_dir.name
661+ model = get_prop(serial, "ro.product.model") or "unknown"
662+ sdk = get_prop(serial, "ro.build.version.release") or get_prop(serial, "ro.build.version.sdk") or "unknown"
663+ device_info[serial] = {"model": model, "sdk": sdk}
664+
665+ def device_label(serial: str) -> str:
666+ meta = device_info.get(serial, {})
667+ model = meta.get("model") or "unknown"
668+ sdk = meta.get("sdk") or "unknown"
669+ return f"{model} - {sdk} ({serial})"
670+
671+ def add_label(data: dict, name: str, value: str) -> dict:
672+ labels = [l for l in data.get("labels", []) if l.get("name") != name]
673+ labels.append({"name": name, "value": value})
674+ data["labels"] = labels
675+ return data
676+
677+ for device_dir in device_dirs:
678+ serial = device_dir.name
679+ src_dir = device_dir / "allure-results"
680+ if not src_dir.is_dir():
681+ src_dir = device_dir
682+ if not src_dir.is_dir():
683+ continue
684+
685+ label = device_label(serial)
686+ for item in src_dir.iterdir():
687+ if item.is_dir():
688+ continue
689+ if item.name in ("executor.json", "environment.properties"):
690+ continue
691+ if item.name.endswith("-result.json"):
692+ try:
693+ data = json.loads(item.read_text(encoding="utf-8"))
694+ except Exception:
695+ continue
696+ data = add_label(data, "device", label)
697+ (merged_dir / item.name).write_text(
698+ json.dumps(data, ensure_ascii=True),
699+ encoding="utf-8",
700+ )
701+ else:
702+ shutil.copy2(item, merged_dir / item.name)
703+
704+ if device_info:
705+ devices = ", ".join(device_label(serial) for serial in sorted(device_info.keys()))
706+ (merged_dir / "environment.properties").write_text(
707+ f"devices={devices}\n", encoding="utf-8"
708+ )
709+
710+ run_id = os.environ.get("GITHUB_RUN_ID", "")
711+ repo = os.environ.get("GITHUB_REPOSITORY", "")
712+ server = os.environ.get("GITHUB_SERVER_URL", "https://github.com")
713+ run_url = f"{server}/{repo}/actions/runs/{run_id}" if repo and run_id else ""
714+ executor = {
715+ "name": "GitHub Actions",
716+ "type": "github",
717+ "url": run_url,
718+ "buildName": os.environ.get("GITHUB_RUN_NUMBER", ""),
719+ "buildUrl": run_url,
720+ "reportName": "Android UI Tests",
721+ }
722+ (merged_dir / "executor.json").write_text(
723+ json.dumps(executor, ensure_ascii=True),
724+ encoding="utf-8",
725+ )
726+ PY
727+
728+ - name : Generate Allure HTML report
729+ if : always()
730+ env :
731+ MERGED_DIR : ${{ runner.temp }}/allure-results-merged
732+ REPORT_DIR : ${{ runner.temp }}/allure-report
733+ run : |
734+ set -euo pipefail
735+ if [[ ! -d "${MERGED_DIR}" ]]; then
736+ echo "No merged Allure results found"
737+ exit 0
738+ fi
739+
740+ ALLURE_VERSION="2.29.0"
741+ ALLURE_TGZ="${RUNNER_TEMP}/allure-${ALLURE_VERSION}.tgz"
742+ curl -fsSL -o "${ALLURE_TGZ}" \
743+ "https://github.com/allure-framework/allure2/releases/download/${ALLURE_VERSION}/allure-${ALLURE_VERSION}.tgz"
744+ tar -xzf "${ALLURE_TGZ}" -C "${RUNNER_TEMP}"
745+ "${RUNNER_TEMP}/allure-${ALLURE_VERSION}/bin/allure" \
746+ generate "${MERGED_DIR}" -o "${REPORT_DIR}" --clean
747+
626748 - name : Upload test artifacts
627749 if : always()
628750 uses : actions/upload-artifact@v4
629751 with :
630752 name : android-ui-test-results
631753 if-no-files-found : warn
754+ retention-days : 90
632755 path : |
633756 tests/testsCore/build/reports/androidTests/connected
634- ${{ runner.temp }}/allure-results
757+ ${{ runner.temp }}/allure-results-merged
758+ ${{ runner.temp }}/allure-report
635759
636760 - name : Cleanup (remove secrets + build outputs)
637761 if : always()
@@ -640,6 +764,8 @@ jobs:
640764 rm -f "${RUNNER_TEMP}/secrets.json" || true
641765 rm -f "${RUNNER_TEMP}/Wire.apk" "${RUNNER_TEMP}/Wire.old.apk" || true
642766 rm -rf "${RUNNER_TEMP}/allure-results" || true
767+ rm -rf "${RUNNER_TEMP}/allure-results-merged" || true
768+ rm -rf "${RUNNER_TEMP}/allure-report" || true
643769
644770 # Remove build outputs (these can contain BuildConfig secrets) but keep Gradle caches
645771 git clean -ffdx -e .gradle -e .kotlin
0 commit comments