diff --git a/.github/scripts/o2-run-report.sh b/.github/scripts/o2-run-report.sh index 464a65cdf1..6e0473b654 100755 --- a/.github/scripts/o2-run-report.sh +++ b/.github/scripts/o2-run-report.sh @@ -34,23 +34,34 @@ gh_get(){ curl -s -H "Authorization: token ${GH_TOKEN}" -H "Accept: application/ # wall-clock of one attempt = max(completed) - min(started) across its jobs (clamped non-negative # to guard against clock-skew where a completed_at precedes a started_at). -attempt_wall(){ jq '[.jobs[]|select(.completed_at!=null and .started_at!=null)] as $j - | (if ($j|length)>0 then (($j|map(.completed_at|fromdateiso8601)|max)-($j|map(.started_at|fromdateiso8601)|min)) else 0 end) +# $1 = floor epoch (the attempt's run_started_at). On a RE-RUN, GitHub's per-attempt jobs API +# carries over jobs that did NOT re-run with their ORIGINAL (earlier-attempt) timestamps, so a +# naive min(started) spans the idle gap between a failed run and a much-later manual re-run — +# inflating the duration by hours. Flooring the start at run_started_at counts only this +# attempt's actual work (no effect on normal runs, where min(started) >= run_started_at). +attempt_wall(){ jq --argjson floor "${1:-0}" '[.jobs[]|select(.completed_at!=null and .started_at!=null)] as $j + | (if ($j|length)>0 then (($j|map(.completed_at|fromdateiso8601)|max)-([($j|map(.started_at|fromdateiso8601)|min), $floor]|max)) else 0 end) | if . < 0 then 0 else . end'; } +# Epoch seconds of a run/attempt object's run_started_at (0 if absent). +run_started_epoch(){ jq 'if .run_started_at then (.run_started_at|fromdateiso8601) else 0 end'; } RUN_JSON=$(gh_get "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" 2>/dev/null) [ -n "$RUN_JSON" ] || { echo "::warning::could not fetch run object — skipping"; exit 0; } ATT="${GITHUB_RUN_ATTEMPT:-1}" +# This attempt's start — the floor for its wall-clock (see attempt_wall). +RUN_STARTED=$(printf '%s' "$RUN_JSON" | run_started_epoch 2>/dev/null || echo 0) +[ -n "$RUN_STARTED" ] || RUN_STARTED=0 THIS_JOBS=$(gh_get "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/attempts/${ATT}/jobs?per_page=100" 2>/dev/null) -FINAL_DUR=$(printf '%s' "$THIS_JOBS" | attempt_wall 2>/dev/null || echo 0) +FINAL_DUR=$(printf '%s' "$THIS_JOBS" | attempt_wall "$RUN_STARTED" 2>/dev/null || echo 0) # total across attempts: add each prior attempt's wall-clock. TOTAL_DUR="$FINAL_DUR" if [ "$ATT" -gt 1 ] 2>/dev/null; then SUM="$FINAL_DUR" for n in $(seq 1 $((ATT-1))); do - W=$(gh_get "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/attempts/${n}/jobs?per_page=100" 2>/dev/null | attempt_wall 2>/dev/null || echo 0) + NFLOOR=$(gh_get "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/attempts/${n}" 2>/dev/null | run_started_epoch 2>/dev/null || echo 0) + W=$(gh_get "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/attempts/${n}/jobs?per_page=100" 2>/dev/null | attempt_wall "${NFLOOR:-0}" 2>/dev/null || echo 0) SUM=$(awk -v a="$SUM" -v b="$W" 'BEGIN{print a+b}') done TOTAL_DUR="$SUM" diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 63a1dcb9bd..323243f229 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -972,16 +972,36 @@ jobs: fi fi - # Unset CI to use non-CI reporter config (html + json instead of blob) - unset CI - npx playwright merge-reports --config playwright.config.js ./all-blob-reports + # Force the html + json reporters explicitly with their output paths instead of + # relying on `unset CI` to flip playwright.config.js's `process.env.CI ? blob : html/json` + # ternary. The runner exports CI=true and `unset CI` does not reliably reach the + # merge subprocess, so the config would intermittently re-select the `blob` reporter — + # which writes a merged blob-report/ and NOTHING to playwright-results/, making the + # downstream `ls playwright-results/html-report/` fail with a cryptic exit 2. + mkdir -p playwright-results + # These env vars override playwright.config.js's reporter settings: + # PLAYWRIGHT_HTML_OUTPUT_DIR - html reporter output directory + # PLAYWRIGHT_HTML_OPEN=never - never auto-open the report (required for CI) + # PLAYWRIGHT_JSON_OUTPUT_NAME - json reporter output file path + PLAYWRIGHT_HTML_OUTPUT_DIR=playwright-results/html-report \ + PLAYWRIGHT_HTML_OPEN=never \ + PLAYWRIGHT_JSON_OUTPUT_NAME=playwright-results/report.json \ + npx playwright merge-reports --reporter html,json ./all-blob-reports echo "Contents of playwright-results:" - ls -la playwright-results/ + ls -la playwright-results/ || true + if [ ! -f playwright-results/html-report/index.html ] || [ ! -f playwright-results/report.json ]; then + echo "::error::merge-reports did not produce the expected html-report/index.html and report.json" + echo "::error::all-blob-reports contents:" + ls -la all-blob-reports/ || true + exit 1 + fi + # Diagnostics only — never fail the step on these (the guard above already + # confirmed the outputs exist). echo "Contents of html-report:" - ls -la playwright-results/html-report/ + ls -la playwright-results/html-report/ || true echo "Checking report.json:" - ls -lh playwright-results/report.json + ls -lh playwright-results/report.json || true - name: Extract report stats for OpenObserve id: report_stats diff --git a/.github/workflows/playwright_regression.yml b/.github/workflows/playwright_regression.yml index 61f2f40528..728170bbe4 100644 --- a/.github/workflows/playwright_regression.yml +++ b/.github/workflows/playwright_regression.yml @@ -612,15 +612,36 @@ jobs: fi fi - unset CI - npx playwright merge-reports --config playwright.config.js ./all-blob-reports + # Force the html + json reporters explicitly with their output paths instead of + # relying on `unset CI` to flip playwright.config.js's `process.env.CI ? blob : html/json` + # ternary. The runner exports CI=true and `unset CI` does not reliably reach the + # merge subprocess, so the config would intermittently re-select the `blob` reporter — + # which writes a merged blob-report/ and NOTHING to playwright-results/, making the + # downstream `ls playwright-results/html-report/` fail with a cryptic exit 2. + mkdir -p playwright-results + # These env vars override playwright.config.js's reporter settings: + # PLAYWRIGHT_HTML_OUTPUT_DIR - html reporter output directory + # PLAYWRIGHT_HTML_OPEN=never - never auto-open the report (required for CI) + # PLAYWRIGHT_JSON_OUTPUT_NAME - json reporter output file path + PLAYWRIGHT_HTML_OUTPUT_DIR=playwright-results/html-report \ + PLAYWRIGHT_HTML_OPEN=never \ + PLAYWRIGHT_JSON_OUTPUT_NAME=playwright-results/report.json \ + npx playwright merge-reports --reporter html,json ./all-blob-reports echo "Contents of playwright-results:" - ls -la playwright-results/ + ls -la playwright-results/ || true + if [ ! -f playwright-results/html-report/index.html ] || [ ! -f playwright-results/report.json ]; then + echo "::error::merge-reports did not produce the expected html-report/index.html and report.json" + echo "::error::all-blob-reports contents:" + ls -la all-blob-reports/ || true + exit 1 + fi + # Diagnostics only — never fail the step on these (the guard above already + # confirmed the outputs exist). echo "Contents of html-report:" - ls -la playwright-results/html-report/ + ls -la playwright-results/html-report/ || true echo "Checking report.json:" - ls -lh playwright-results/report.json + ls -lh playwright-results/report.json || true - name: Extract report stats for OpenObserve id: report_stats @@ -748,19 +769,33 @@ jobs: "https://api.github.com/repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/attempts/${GITHUB_RUN_ATTEMPT}/jobs?per_page=100" \ > jobs.json || echo '{"jobs":[]}' > jobs.json + # This attempt's start time — the floor for awall (below). On a RE-RUN, GitHub keeps + # the jobs that did NOT re-run at their ORIGINAL (earlier-attempt) timestamps, so a + # naive min(started) spans the idle gap to a much-later manual re-run and inflates the + # duration by hours. Flooring the start at run_started_at counts only this attempt's work. + RUN_STARTED=$(curl -s -H "Authorization: token $GH_TOKEN" -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \ + | jq 'if .run_started_at then (.run_started_at|fromdateiso8601) else 0 end' 2>/dev/null || echo 0) + [ -n "$RUN_STARTED" ] || RUN_STARTED=0 + STATS="${MERGE_STATS:-}"; [ -z "$STATS" ] && STATS=null # Retry-aware: this attempt's wall-clock + total summed across all attempts (so the # latest-attempt doc carries the cumulative compute; dashboards keep latest per run_id). ATT="${GITHUB_RUN_ATTEMPT:-1}" - awall(){ jq '[.jobs[]|select(.completed_at!=null and .started_at!=null)] as $j | (if ($j|length)>0 then (($j|map(.completed_at|fromdateiso8601)|max)-($j|map(.started_at|fromdateiso8601)|min)) else 0 end) | if . < 0 then 0 else . end'; } - FINAL_DUR=$(awall < jobs.json 2>/dev/null || echo 0) + # $1 = floor epoch (run_started_at); start = max(min job started, floor) so re-run + # carry-over jobs with old timestamps don't inflate the wall-clock. + awall(){ jq --argjson floor "${1:-0}" '[.jobs[]|select(.completed_at!=null and .started_at!=null)] as $j | (if ($j|length)>0 then (($j|map(.completed_at|fromdateiso8601)|max)-([($j|map(.started_at|fromdateiso8601)|min), $floor]|max)) else 0 end) | if . < 0 then 0 else . end'; } + FINAL_DUR=$(awall "$RUN_STARTED" < jobs.json 2>/dev/null || echo 0) TOTAL_DUR="$FINAL_DUR" if [ "$ATT" -gt 1 ] 2>/dev/null; then SUM="$FINAL_DUR" for n in $(seq 1 $((ATT-1))); do + NFLOOR=$(curl -s -H "Authorization: token $GH_TOKEN" -H "Accept: application/vnd.github.v3+json" \ + "https://api.github.com/repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/attempts/${n}" \ + | jq 'if .run_started_at then (.run_started_at|fromdateiso8601) else 0 end' 2>/dev/null || echo 0) W=$(curl -s -H "Authorization: token $GH_TOKEN" -H "Accept: application/vnd.github.v3+json" \ - "https://api.github.com/repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/attempts/${n}/jobs?per_page=100" | awall 2>/dev/null || echo 0) + "https://api.github.com/repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/attempts/${n}/jobs?per_page=100" | awall "${NFLOOR:-0}" 2>/dev/null || echo 0) SUM=$(awk -v a="$SUM" -v b="$W" 'BEGIN{print a+b}') done TOTAL_DUR="$SUM"