Skip to content

Dashboard E2E Tests

Dashboard E2E Tests #9

Workflow file for this run

name: Dashboard E2E Tests
on:
workflow_run:
workflows: ["JS"]
types: [completed]
workflow_dispatch:
inputs:
image_tag:
description: "MLflow image tag to test (alphanumeric, dots, hyphens, underscores only)"
required: true
default: "odh-stable"
type: string
concurrency:
group: e2e-${{ github.event.workflow_run.head_repository.full_name || github.repository }}-${{ github.event.workflow_run.head_branch || github.ref }}
cancel-in-progress: true
defaults:
run:
shell: bash
jobs:
gate:
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success')
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
issues: read
pull-requests: read
outputs:
should-run: ${{ github.event_name == 'workflow_dispatch' || steps.check.outputs.should-run == 'true' }}
pr-number: ${{ steps.pr.outputs.pr_number }}
head-sha: ${{ steps.pr.outputs.head_sha }}
steps:
- name: Resolve PR number
id: pr
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
WF_PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number }}
EVENT_NAME: ${{ github.event_name }}
FALLBACK_SHA: ${{ github.sha }}
run: |
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
echo "pr_number=" >> "$GITHUB_OUTPUT"
echo "head_sha=$FALLBACK_SHA" >> "$GITHUB_OUTPUT"
exit 0
fi
# workflow_run.pull_requests is empty for fork PRs
PR_NUMBER="$WF_PR_NUMBER"
if [ -z "$PR_NUMBER" ] || [ "$PR_NUMBER" = "null" ]; then
echo "pull_requests array is empty (likely a fork PR), resolving via API..."
PR_NUMBER=$(gh api "repos/${REPO}/commits/${HEAD_SHA}/pulls" \
--jq '[.[] | select(.state == "open")][0].number' 2>/dev/null || echo "")
fi
if [ -z "$PR_NUMBER" ] || [ "$PR_NUMBER" = "null" ]; then
echo "::error::Could not resolve PR number for head SHA ${HEAD_SHA}"
exit 1
fi
echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
echo "head_sha=$HEAD_SHA" >> "$GITHUB_OUTPUT"
echo "Resolved PR #$PR_NUMBER for SHA $HEAD_SHA"
- name: Check for ok-to-test label
if: github.event_name != 'workflow_dispatch'
id: check
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ steps.pr.outputs.pr_number }}
run: |
set -euo pipefail
HAS_LABEL="$(gh api "repos/${REPO}/issues/${PR_NUMBER}/labels" \
--jq 'map(.name) | any(. == "ok-to-test")')"
if [[ "$HAS_LABEL" == "true" ]]; then
echo "Label 'ok-to-test' found, proceeding with e2e tests."
echo "should-run=true" >> "$GITHUB_OUTPUT"
else
echo "::notice::Skipping e2e: PR #${PR_NUMBER} is missing the 'ok-to-test' label. A maintainer must add it to run e2e tests."
fi
e2e:
needs: gate
if: needs.gate.outputs.should-run == 'true'
permissions:
contents: read
timeout-minutes: 45
runs-on: ubuntu-latest
defaults:
run:
working-directory: mlflow/server/js
env:
MLFLOW_E2E_BASE_URL: http://localhost:5000
MLFLOW_IMAGE: quay.io/opendatahub/mlflow:${{ github.event_name == 'workflow_dispatch' && inputs.image_tag || format('odh-pr-{0}', needs.gate.outputs.pr-number) }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ needs.gate.outputs.head-sha }}
persist-credentials: false
- name: Validate workflow_dispatch inputs
if: github.event_name == 'workflow_dispatch'
run: |
if [[ ! "$MLFLOW_IMAGE" =~ ^quay\.io/opendatahub/mlflow:[A-Za-z0-9._-]+$ ]]; then
echo "::error::Invalid image_tag input. Only [A-Za-z0-9._-] is allowed."
exit 1
fi
- name: Log image tag
run: echo "Using image $MLFLOW_IMAGE"
- uses: ./.github/actions/setup-node
- name: Install dependencies
run: yarn install --immutable
- name: Get Playwright version
id: pw-version
run: echo "version=$(npx playwright --version)" >> "$GITHUB_OUTPUT"
- name: Cache Playwright browsers
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ steps.pw-version.outputs.version }}-${{ hashFiles('mlflow/server/js/yarn.lock') }}
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Wait for Konflux build to complete
if: github.event_name != 'workflow_dispatch'
timeout-minutes: 30
working-directory: .
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMMIT_SHA: ${{ needs.gate.outputs.head-sha }}
REPO: ${{ github.repository }}
run: |
echo "Waiting for Konflux build to complete for commit: $COMMIT_SHA"
STATUS="pending"
CONCLUSION=""
for i in $(seq 1 40); do
if ! RESPONSE=$(timeout 15 gh api "repos/${REPO}/commits/${COMMIT_SHA}/check-runs" 2>/dev/null); then
echo "API call failed, retrying in 30s... (attempt $i/40)"
sleep 30
continue
fi
RESULT=$(echo "$RESPONSE" | jq -c '
[.check_runs[] | select(.name | startswith("mlflow-on-pull-request"))]
| sort_by(.started_at)
| last // {}
')
STATUS=$(echo "$RESULT" | jq -r '.status // "pending"')
CONCLUSION=$(echo "$RESULT" | jq -r '.conclusion // ""')
if [ "$STATUS" = "completed" ] && [ "$CONCLUSION" = "success" ]; then
echo "Konflux build succeeded after $((i * 30))s"
break
elif [ "$STATUS" = "completed" ] && [ "$CONCLUSION" != "success" ]; then
echo "Konflux build failed with conclusion: $CONCLUSION"
exit 1
fi
echo "Konflux build: status=$STATUS, retrying in 30s... (attempt $i/40)"
sleep 30
done
if [ "$STATUS" != "completed" ]; then
ELAPSED_MIN=$(( i * 30 / 60 ))
echo "Konflux build did not complete after ${ELAPSED_MIN} minutes"
exit 1
fi
- name: Pull Konflux PR image
working-directory: .
run: |
echo "Pulling image: $MLFLOW_IMAGE"
for attempt in $(seq 1 5); do
if docker pull "$MLFLOW_IMAGE"; then
echo "Image pulled successfully on attempt $attempt"
exit 0
fi
echo "Pull failed, retrying in 15s... (attempt $attempt/5)"
sleep 15
done
echo "::error::Failed to pull $MLFLOW_IMAGE after 5 attempts"
exit 1
- name: Start MLflow stack
working-directory: docker-compose
env:
MLFLOW_EXTRA_ARGS: "--enable-workspaces"
run: |
cp .env.dev.example .env
docker compose up -d --wait --wait-timeout 120
- name: Verify MLflow is serving
run: |
curl --retry 5 --retry-delay 5 --retry-all-errors --fail \
"$MLFLOW_E2E_BASE_URL/api/2.0/mlflow/experiments/search"
- name: Run e2e tests
run: yarn test:e2e
- name: Upload test report
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
if: always()
with:
name: e2e-test-report
path: mlflow/server/js/e2e/test-report/
if-no-files-found: ignore
retention-days: 14
- name: Upload test results
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
if: failure()
with:
name: e2e-test-results
path: mlflow/server/js/e2e/test-results/
if-no-files-found: ignore
retention-days: 14
- name: Dump docker compose logs
if: failure()
working-directory: docker-compose
run: docker compose logs --no-color --tail=500 2>&1 | tee docker-compose-logs.txt
- name: Upload docker compose logs
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
if: failure()
with:
name: docker-compose-logs
path: docker-compose/docker-compose-logs.txt
if-no-files-found: ignore
retention-days: 14
- name: Stop MLflow stack
if: always()
continue-on-error: true
working-directory: docker-compose
run: docker compose down -v --remove-orphans
report-status:
if: always() && github.event_name != 'workflow_dispatch' && needs.gate.result != 'skipped'
needs:
- gate
- e2e
permissions:
statuses: write
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Set status on PR
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
STATUS_SHA: ${{ needs.gate.outputs.head-sha }}
PR_NUMBER: ${{ needs.gate.outputs.pr-number }}
GATE_RESULT: ${{ needs.gate.result }}
GATE_SHOULD_RUN: ${{ needs.gate.outputs.should-run }}
E2E_RESULT: ${{ needs.e2e.result }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
set -euo pipefail
if [[ "$GATE_RESULT" != "success" ]]; then
STATE="failure"
DESC="E2E gate failed"
elif [[ "$GATE_SHOULD_RUN" != "true" ]]; then
STATE="pending"
DESC="Waiting for ok-to-test label on PR #${PR_NUMBER}"
elif [[ "$E2E_RESULT" == "success" ]]; then
STATE="success"
DESC="E2E tests passed"
elif [[ "$E2E_RESULT" == "skipped" ]]; then
STATE="pending"
DESC="E2E tests were skipped"
elif [[ "$E2E_RESULT" == "cancelled" ]]; then
STATE="pending"
DESC="E2E tests were cancelled"
else
STATE="failure"
DESC="E2E tests failed"
fi
gh api "repos/${REPO}/statuses/${STATUS_SHA}" \
-f state="$STATE" \
-f context="E2E Tests" \
-f description="$DESC" \
-f target_url="${RUN_URL}"