Cap every scenario at 5 minutes (sample, not play-to-end) #5
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: video-playback | |
| # Runs NRSampleApp playback automation across iOS + tvOS simulators. | |
| # | |
| # Layered config — change a setting WITHOUT a PR by editing GitHub Actions | |
| # variables (Settings → Secrets and variables → Actions → Variables tab). | |
| # Priority high → low: | |
| # 1. workflow_dispatch inputs (nr_overrides, extra_streams) | |
| # 2. GitHub repo vars.NEW_RELIC_* | |
| # 3. playback-config.json per_leg block (matched by leg tag) | |
| # 4. playback-config.json global block | |
| # 5. NewRelicSetup.swift defaults | |
| # | |
| # This workflow lives only on `internal/video-rig` and is not merged to master. | |
| on: | |
| schedule: | |
| # GitHub Actions cron is UTC-only. IST (India Standard Time) is UTC+5:30. | |
| # Times below are written as UTC, with IST equivalents in comments. | |
| - cron: '30 3 * * *' # 03:30 UTC = 09:00 IST · daily comprehensive | |
| - cron: '30 */6 * * *' # 00:30/06:30/12:30/18:30 UTC = 06:00/12:00/18:00/00:00 IST · smoke every 6h | |
| workflow_dispatch: | |
| inputs: | |
| scenarios: | |
| description: 'Scenario subset (comma-separated). Empty = all.' | |
| required: false | |
| default: '' | |
| # ------ Per-leg toggles ------ | |
| run_iOS_18_iPhone_16_Pro: | |
| description: 'Run iOS 18 / iPhone 16 Pro' | |
| type: boolean | |
| default: true | |
| run_iOS_17_iPhone_15: | |
| description: 'Run iOS 17.5 / iPhone 15' | |
| type: boolean | |
| default: true | |
| run_iOS_16_iPhone_14: | |
| description: 'Run iOS 16.4 / iPhone 14 (downloads runtime, +10 min)' | |
| type: boolean | |
| default: false | |
| run_tvOS_18_Apple_TV_4K: | |
| description: 'Run tvOS 18 / Apple TV 4K' | |
| type: boolean | |
| default: true | |
| # ------ Per-run NRVA overrides ------ | |
| nr_overrides: | |
| description: 'NRVA per-run JSON override, e.g. {"harvest_cycle_secs":60,"debug_logging":false}' | |
| required: false | |
| default: '{}' | |
| # ------ Per-run extra streams (ContentItem JSON array) ------ | |
| extra_streams: | |
| description: 'Extra ContentItems (JSON array). Empty = none.' | |
| required: false | |
| default: '' | |
| config_file: | |
| description: 'Path to playback-config.json (relative to NRSampleApp dir)' | |
| required: false | |
| default: 'playback-config.json' | |
| push: | |
| branches: [internal/video-rig] | |
| paths: | |
| - 'Examples/iOS/NRSampleApp/**' | |
| - '.github/workflows/playback.yml' | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: video-playback-${{ github.ref }} | |
| cancel-in-progress: true | |
| # Defaults for cron + push (no inputs available there). | |
| env: | |
| CRON_DEFAULT_iOS_18_iPhone_16_Pro: 'true' | |
| CRON_DEFAULT_iOS_17_iPhone_15: 'true' | |
| CRON_DEFAULT_iOS_16_iPhone_14: 'false' | |
| CRON_DEFAULT_tvOS_18_Apple_TV_4K: 'true' | |
| jobs: | |
| playback: | |
| name: ${{ matrix.tag }} | |
| runs-on: macos-14 | |
| timeout-minutes: 120 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - tag: iOS-18-iPhone-16-Pro | |
| platform: iOS | |
| scheme: NRSampleApp_iOS | |
| device: iPhone 16 Pro | |
| os_version: "" | |
| download_os: "" | |
| input_key: run_iOS_18_iPhone_16_Pro | |
| - tag: iOS-17-iPhone-15 | |
| platform: iOS | |
| scheme: NRSampleApp_iOS | |
| device: iPhone 15 | |
| os_version: "" # use whatever iPhone 15 has pre-installed | |
| download_os: "" | |
| input_key: run_iOS_17_iPhone_15 | |
| - tag: iOS-16-iPhone-14 | |
| platform: iOS | |
| scheme: NRSampleApp_iOS | |
| device: iPhone 14 | |
| os_version: "16.4" | |
| download_os: "16.4" | |
| input_key: run_iOS_16_iPhone_14 | |
| - tag: tvOS-18-Apple-TV-4K | |
| platform: tvOS | |
| scheme: NRSampleApp_tvOS | |
| device: Apple TV 4K (3rd generation) | |
| os_version: "" | |
| download_os: "" | |
| input_key: run_tvOS_18_Apple_TV_4K | |
| steps: | |
| - name: Resolve whether this leg should run | |
| id: gate | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| INPUT_iOS_18_iPhone_16_Pro: ${{ inputs.run_iOS_18_iPhone_16_Pro }} | |
| INPUT_iOS_17_iPhone_15: ${{ inputs.run_iOS_17_iPhone_15 }} | |
| INPUT_iOS_16_iPhone_14: ${{ inputs.run_iOS_16_iPhone_14 }} | |
| INPUT_tvOS_18_Apple_TV_4K: ${{ inputs.run_tvOS_18_Apple_TV_4K }} | |
| KEY: ${{ matrix.input_key }} | |
| run: | | |
| set -euo pipefail | |
| if [ "$EVENT_NAME" = "workflow_dispatch" ]; then | |
| VAR_NAME="INPUT_${KEY#run_}" | |
| else | |
| VAR_NAME="CRON_DEFAULT_${KEY#run_}" | |
| fi | |
| VAL=$(eval echo "\${$VAR_NAME:-true}") | |
| case "$VAL" in | |
| true|1|yes) SHOULD_RUN=true ;; | |
| *) SHOULD_RUN=false ;; | |
| esac | |
| echo "should_run=$SHOULD_RUN" >> "$GITHUB_OUTPUT" | |
| echo "Leg ${{ matrix.tag }} → $SHOULD_RUN (event=$EVENT_NAME)" | |
| - uses: actions/checkout@v4 | |
| if: steps.gate.outputs.should_run == 'true' | |
| - name: Select Xcode 16+ (macos-14 defaults to 15.4 which can't open | |
| format-77 project files generated by current XcodeGen) | |
| if: steps.gate.outputs.should_run == 'true' | |
| uses: maxim-lobanov/setup-xcode@v1 | |
| with: | |
| xcode-version: 'latest-stable' | |
| - name: Show Xcode + simulator versions | |
| if: steps.gate.outputs.should_run == 'true' | |
| run: | | |
| xcodebuild -version | |
| xcrun simctl --version | |
| - name: Install XcodeGen + jq | |
| if: steps.gate.outputs.should_run == 'true' | |
| run: brew install xcodegen jq | |
| - name: Show pre-installed simulator runtimes | |
| if: steps.gate.outputs.should_run == 'true' | |
| run: xcrun simctl list runtimes | |
| - name: Download iOS runtime ${{ matrix.download_os }} | |
| if: steps.gate.outputs.should_run == 'true' && matrix.download_os != '' | |
| run: | | |
| set -euxo pipefail | |
| sudo xcodebuild -downloadPlatform iOS -buildVersion ${{ matrix.download_os }} \ | |
| || xcodebuild -downloadPlatform iOS -buildVersion ${{ matrix.download_os }} | |
| xcrun simctl list runtimes | grep -i "iOS ${{ matrix.download_os }}" \ | |
| || { echo "ERROR: runtime did not install"; exit 1; } | |
| - name: Create simulator for ${{ matrix.tag }} | |
| if: steps.gate.outputs.should_run == 'true' && matrix.os_version != '' | |
| run: | | |
| set -euxo pipefail | |
| RUNTIME_ID="iOS-${{ matrix.os_version }}" | |
| RUNTIME_ID="${RUNTIME_ID//./-}" | |
| xcrun simctl create "auto-${{ matrix.tag }}" "${{ matrix.device }}" "$RUNTIME_ID" \ | |
| || echo "(device may already exist — continuing)" | |
| - name: Run playback automation | |
| if: steps.gate.outputs.should_run == 'true' | |
| working-directory: Examples/iOS/NRSampleApp | |
| env: | |
| SCENARIOS: ${{ inputs.scenarios }} | |
| PLATFORM: ${{ matrix.platform }} | |
| SCHEME: ${{ matrix.scheme }} | |
| DEVICE_NAME: >- | |
| ${{ matrix.os_version != '' | |
| && format('auto-{0}', matrix.tag) | |
| || matrix.device }} | |
| OS_VERSION: ${{ matrix.os_version }} | |
| LEG_TAG: ${{ matrix.tag }} | |
| CONFIG_FILE: ${{ inputs.config_file || 'playback-config.json' }} | |
| # ------ Sensitive (always from secrets) ------ | |
| NEW_RELIC_APP_TOKEN: ${{ secrets.NEW_RELIC_APP_TOKEN }} | |
| # ------ Non-sensitive NRVA settings (from repo vars; edit in GitHub UI, no PR) ------ | |
| NEW_RELIC_COLLECTOR_ADDRESS: ${{ vars.NEW_RELIC_COLLECTOR_ADDRESS }} | |
| NEW_RELIC_HARVEST_CYCLE_SECS: ${{ vars.NEW_RELIC_HARVEST_CYCLE_SECS }} | |
| NEW_RELIC_LIVE_HARVEST_CYCLE_SECS: ${{ vars.NEW_RELIC_LIVE_HARVEST_CYCLE_SECS }} | |
| NEW_RELIC_REGULAR_BATCH_SIZE_BYTES: ${{ vars.NEW_RELIC_REGULAR_BATCH_SIZE_BYTES }} | |
| NEW_RELIC_LIVE_BATCH_SIZE_BYTES: ${{ vars.NEW_RELIC_LIVE_BATCH_SIZE_BYTES }} | |
| NEW_RELIC_MAX_DEAD_LETTER_SIZE: ${{ vars.NEW_RELIC_MAX_DEAD_LETTER_SIZE }} | |
| NEW_RELIC_MAX_OFFLINE_STORAGE_MB: ${{ vars.NEW_RELIC_MAX_OFFLINE_STORAGE_MB }} | |
| NEW_RELIC_QOE_ENABLED: ${{ vars.NEW_RELIC_QOE_ENABLED }} | |
| NEW_RELIC_QOE_INTERVAL_MULTIPLIER: ${{ vars.NEW_RELIC_QOE_INTERVAL_MULTIPLIER }} | |
| NEW_RELIC_DEBUG_LOGGING: ${{ vars.NEW_RELIC_DEBUG_LOGGING }} | |
| NEW_RELIC_MEMORY_OPTIMIZATION: ${{ vars.NEW_RELIC_MEMORY_OPTIMIZATION }} | |
| # ------ Per-run overrides (workflow_dispatch only) ------ | |
| NR_OVERRIDES_JSON: ${{ inputs.nr_overrides }} | |
| # ------ Extra streams: input wins, fallback to repo var ------ | |
| PLAYBACK_EXTRA_STREAMS: >- | |
| ${{ inputs.extra_streams != '' | |
| && inputs.extra_streams | |
| || vars.PLAYBACK_EXTRA_STREAMS }} | |
| run: ./scripts/run-playback.sh | |
| - name: Upload artifacts | |
| if: always() && steps.gate.outputs.should_run == 'true' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: playback-${{ matrix.tag }}-${{ github.run_id }} | |
| path: Examples/iOS/NRSampleApp/build/playback-artifacts/ | |
| retention-days: 14 | |
| report: | |
| name: Status report | |
| needs: playback | |
| if: always() | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Download all leg artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts | |
| pattern: 'playback-*' | |
| - name: Aggregate green/red across legs | |
| id: aggregate | |
| run: | | |
| set -uo pipefail | |
| cd artifacts || { echo "no artifacts to aggregate"; exit 0; } | |
| { | |
| echo "# 🎬 Video Playback — $(date -u +%Y-%m-%dT%H:%M:%SZ)" | |
| echo "" | |
| echo "Branch: \`${{ github.ref_name }}\` · Trigger: \`${{ github.event_name }}\` · Run: [#${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})" | |
| echo "" | |
| echo "| Leg | Status | OK | Fail | Cap-hit | Scenarios |" | |
| echo "|---|---|---:|---:|---:|---|" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| TOTAL=0 | |
| PASSED=0 | |
| FAILED=0 | |
| for dir in playback-*; do | |
| [ -d "$dir" ] || continue | |
| TAG=$(echo "$dir" | sed -E 's/^playback-(.*)-[0-9]+$/\1/') | |
| SUMMARY="$dir/SUMMARY.txt" | |
| TOTAL=$((TOTAL + 1)) | |
| if [ ! -f "$SUMMARY" ]; then | |
| echo "| \`$TAG\` | ⚠️ no summary | — | — | — | (artifact missing) |" >> "$GITHUB_STEP_SUMMARY" | |
| FAILED=$((FAILED + 1)) | |
| continue | |
| fi | |
| OK=$(grep -c " ok " "$SUMMARY" 2>/dev/null | head -1) | |
| FL=$(grep -c " fail " "$SUMMARY" 2>/dev/null | head -1) | |
| CH=$(grep -c " cap-hit " "$SUMMARY" 2>/dev/null | head -1) | |
| : "${OK:=0}"; : "${FL:=0}"; : "${CH:=0}" | |
| # Build a compact scenario list (id:result, id:result, ...) | |
| SCEN=$(awk 'NR>10 && $1 != "----" && $1 != "OVERALL:" && NF>=6 { printf "%s:%s, ", $1, $6 }' "$SUMMARY" | sed 's/, $//') | |
| if [ "$FL" -gt 0 ]; then | |
| STATUS="❌ red" | |
| FAILED=$((FAILED + 1)) | |
| elif [ "$CH" -gt 0 ]; then | |
| STATUS="⚠️ amber" | |
| PASSED=$((PASSED + 1)) | |
| else | |
| STATUS="✅ green" | |
| PASSED=$((PASSED + 1)) | |
| fi | |
| echo "| \`$TAG\` | $STATUS | $OK | $FL | $CH | $SCEN |" >> "$GITHUB_STEP_SUMMARY" | |
| done | |
| { | |
| echo "" | |
| if [ "$FAILED" -eq 0 ] && [ "$TOTAL" -gt 0 ]; then | |
| echo "## ✅ All $TOTAL legs passed" | |
| elif [ "$TOTAL" -eq 0 ]; then | |
| echo "## ⚠️ No leg artifacts found (all legs may have been skipped)" | |
| else | |
| echo "## ❌ $FAILED / $TOTAL legs failed" | |
| fi | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| echo "passed=$PASSED" >> "$GITHUB_OUTPUT" | |
| echo "failed=$FAILED" >> "$GITHUB_OUTPUT" | |
| echo "total=$TOTAL" >> "$GITHUB_OUTPUT" | |
| - name: Fail workflow if any leg failed | |
| if: steps.aggregate.outputs.failed != '0' && steps.aggregate.outputs.failed != '' | |
| run: | | |
| echo "::error::${{ steps.aggregate.outputs.failed }}/${{ steps.aggregate.outputs.total }} legs failed." | |
| exit 1 |