Skip to content

E2E Tests

E2E Tests #8

Workflow file for this run

name: E2E Tests
on:
pull_request:
branches:
- main
paths:
- 'frontend/**'
- 'deploy/helm/**'
- 'tests/**'
- '.github/workflows/e2e-tests.yaml'
push:
branches:
- main
workflow_dispatch:
# MaaS configuration - can be overridden with repository secrets for different environments
env:
MAAS_ENDPOINT: ${{ secrets.MAAS_ENDPOINT }}
MAAS_MODEL_ID: ${{ secrets.MAAS_MODEL_ID }}
# MAAS_API_KEY is passed as a secret in the helm install step
jobs:
unit-tests:
name: Unit Tests
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install unit test dependencies
run: |
pip install -r tests/unit/requirements.txt
- name: Run unit tests
run: |
pytest tests/unit/ -v --tb=short --cov=frontend --cov-report=term-missing
- name: Upload unit test results
if: always()
uses: actions/upload-artifact@v4
with:
name: unit-test-results
path: |
.coverage
htmlcov/
integration-tests:
name: Integration Tests (Streamlit App)
runs-on: ubuntu-latest
timeout-minutes: 15
needs: unit-tests
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install integration test dependencies
run: |
pip install -r tests/integration/requirements.txt
- name: Run integration tests
run: |
pytest tests/integration/test_*.py -v --tb=short
- name: Upload integration test results
if: always()
uses: actions/upload-artifact@v4
with:
name: integration-test-results
path: pytest-results/
llamastack-integration-tests:
name: LlamaStack Integration Tests
runs-on: ubuntu-latest
timeout-minutes: 60
needs: unit-tests
# Skip this job for fork PRs since secrets are not available
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install test dependencies
run: |
pip install -r tests/integration/llamastack/requirements.txt
- name: Create Kind cluster config file
run: |
cat <<EOF > kind-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 30080
hostPort: 8501
protocol: TCP
- containerPort: 30081
hostPort: 8321
protocol: TCP
EOF
- name: Create Kind cluster
uses: helm/kind-action@v1
with:
cluster_name: rag-e2e
config: kind-config.yaml
- name: Install Required CRDs
run: |
echo "Installing CRDs required by helm chart subcomponents..."
# OpenShift Route CRD
kubectl apply -f - <<EOF
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: routes.route.openshift.io
spec:
group: route.openshift.io
names:
kind: Route
listKind: RouteList
plural: routes
singular: route
scope: Namespaced
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
x-kubernetes-preserve-unknown-fields: true
EOF
# KServe InferenceService CRD
kubectl apply -f - <<EOF
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: inferenceservices.serving.kserve.io
spec:
group: serving.kserve.io
names:
kind: InferenceService
listKind: InferenceServiceList
plural: inferenceservices
singular: inferenceservice
scope: Namespaced
versions:
- name: v1beta1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
x-kubernetes-preserve-unknown-fields: true
EOF
# KServe ServingRuntime CRD
kubectl apply -f - <<EOF
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: servingruntimes.serving.kserve.io
spec:
group: serving.kserve.io
names:
kind: ServingRuntime
listKind: ServingRuntimeList
plural: servingruntimes
singular: servingruntime
scope: Namespaced
versions:
- name: v1alpha1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
x-kubernetes-preserve-unknown-fields: true
EOF
# OpenDataHub DataSciencePipelinesApplication CRD
kubectl apply -f - <<EOF
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: datasciencepipelinesapplications.datasciencepipelinesapplications.opendatahub.io
spec:
group: datasciencepipelinesapplications.opendatahub.io
names:
kind: DataSciencePipelinesApplication
listKind: DataSciencePipelinesApplicationList
plural: datasciencepipelinesapplications
singular: datasciencepipelinesapplication
scope: Namespaced
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
x-kubernetes-preserve-unknown-fields: true
EOF
# Kubeflow Notebook CRD
kubectl apply -f - <<EOF
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: notebooks.kubeflow.org
spec:
group: kubeflow.org
names:
kind: Notebook
listKind: NotebookList
plural: notebooks
singular: notebook
scope: Namespaced
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
x-kubernetes-preserve-unknown-fields: true
EOF
echo "Waiting for all CRDs to be established..."
kubectl wait --for condition=established --timeout=60s crd/routes.route.openshift.io
kubectl wait --for condition=established --timeout=60s crd/inferenceservices.serving.kserve.io
kubectl wait --for condition=established --timeout=60s crd/servingruntimes.serving.kserve.io
kubectl wait --for condition=established --timeout=60s crd/datasciencepipelinesapplications.datasciencepipelinesapplications.opendatahub.io
kubectl wait --for condition=established --timeout=60s crd/notebooks.kubeflow.org
echo "✅ All required CRDs installed successfully"
- name: Verify cluster
run: |
kubectl cluster-info
kubectl get nodes
kubectl get pods -A
kubectl get crds | grep route || echo "Route CRD check"
- name: Add Helm repository
run: |
helm repo add rag-charts https://rh-ai-quickstart.github.io/ai-architecture-charts
helm repo update
- name: Build Helm dependencies
run: |
cd deploy/helm/rag
helm dependency build
- name: Display MaaS configuration
run: |
echo "========================================="
echo "MaaS Configuration (Public)"
echo "========================================="
echo "Model ID: ${MAAS_MODEL_ID}"
echo "Endpoint: ${MAAS_ENDPOINT}"
echo "========================================="
- name: Validate MaaS API key
env:
MAAS_API_KEY: ${{ secrets.MAAS_API_KEY }}
run: |
# Check if MAAS_API_KEY secret is set
if [ -z "${MAAS_API_KEY}" ]; then
echo ""
echo "❌ ERROR: MAAS_API_KEY secret is not configured!"
echo ""
# Check if this is a fork PR
if [ "${{ github.event_name }}" = "pull_request" ] && [ "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]; then
echo "⚠️ This appears to be a pull request from a fork."
echo "GitHub Actions does not provide access to secrets for security reasons"
echo "when running workflows triggered by pull requests from forks."
fi
echo ""
echo "To fix this, add the MAAS_API_KEY secret to your repository:"
echo "1. Go to: Settings > Secrets and variables > Actions"
echo "2. Click 'New repository secret'"
echo "3. Name: MAAS_API_KEY"
echo "4. Value: Your Red Hat MaaS API key"
echo ""
echo "Note: For fork PRs, these tests will be skipped automatically."
echo "For more information, see:"
echo "https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions"
echo ""
exit 1
fi
echo "✅ MaaS API key is configured"
- name: Install RAG application with MaaS
env:
MAAS_API_KEY: ${{ secrets.MAAS_API_KEY }}
run: |
# Create namespace
kubectl create namespace rag-e2e || true
echo "========================================="
echo "Installing RAG application with MaaS"
echo "========================================="
echo "Using model: ${MAAS_MODEL_ID}"
echo "Helm chart: deploy/helm/rag"
echo "Namespace: rag-e2e"
echo "Values: tests/e2e/values-e2e.yaml"
echo "========================================="
# Install the chart with e2e values
# Override MaaS configuration from environment variables
# Note: All MaaS settings (endpoint, model ID, API key) passed via --set
# Some values may appear as *** in logs due to GitHub Actions secret masking
helm install rag deploy/helm/rag \
--namespace rag-e2e \
--values tests/e2e/values-e2e.yaml \
--set global.models.${MAAS_MODEL_ID}.url="${MAAS_ENDPOINT}" \
--set global.models.${MAAS_MODEL_ID}.id="${MAAS_MODEL_ID}" \
--set global.models.${MAAS_MODEL_ID}.enabled=true \
--set global.models.${MAAS_MODEL_ID}.apiToken="${MAAS_API_KEY}" \
--set-json llama-stack.initContainers='[]' \
--skip-crds \
--timeout 20m \
--debug
- name: Wait for core services to be ready
run: |
echo "========================================="
echo "Listing all resources in namespace..."
echo "========================================="
kubectl get all -n rag-e2e
echo ""
echo "========================================="
echo "Checking deployments..."
echo "========================================="
kubectl get deployments -n rag-e2e -o wide
echo ""
echo "========================================="
echo "Checking pods..."
echo "========================================="
kubectl get pods -n rag-e2e -o wide
echo ""
echo "========================================="
echo "Waiting for Llama Stack deployment (10min timeout)..."
echo "========================================="
kubectl wait --for=condition=available --timeout=600s \
deployment/llamastack -n rag-e2e || {
echo "❌ Llama Stack deployment failed to become available"
echo "Pod status:"
kubectl get pods -l app.kubernetes.io/name=llamastack -n rag-e2e
echo "Pod logs:"
kubectl logs -l app.kubernetes.io/name=llamastack -n rag-e2e --tail=100
exit 1
}
echo ""
echo "========================================="
echo "Waiting for RAG UI deployment (5min timeout)..."
echo "========================================="
kubectl wait --for=condition=available --timeout=300s \
deployment/rag -n rag-e2e || {
echo "❌ RAG UI deployment failed to become available"
echo "Pod status:"
kubectl get pods -l app.kubernetes.io/name=rag -n rag-e2e
echo "Pod logs:"
kubectl logs -l app.kubernetes.io/name=rag -n rag-e2e --tail=100
exit 1
}
echo ""
echo "========================================="
echo "Waiting for pods to be ready..."
echo "========================================="
kubectl wait --for=condition=ready --timeout=600s \
pod -l app.kubernetes.io/name=llamastack -n rag-e2e
kubectl wait --for=condition=ready --timeout=300s \
pod -l app.kubernetes.io/name=rag -n rag-e2e
echo ""
echo "========================================="
echo "✅ ALL CORE SERVICES ARE READY!"
echo "========================================="
kubectl get pods -n rag-e2e -o wide
- name: Expose services via NodePort
run: |
# Expose RAG UI
kubectl patch service rag -n rag-e2e -p '{"spec":{"type":"NodePort","ports":[{"port":8501,"nodePort":30080}]}}'
# Expose Llama Stack
kubectl patch service llamastack -n rag-e2e -p '{"spec":{"type":"NodePort","ports":[{"port":8321,"nodePort":30081}]}}'
# Verify services
kubectl get services -n rag-e2e
- name: Port forward services
run: |
# Start port forwarding in background
kubectl port-forward -n rag-e2e svc/rag 8501:8501 &
kubectl port-forward -n rag-e2e svc/llamastack 8321:8321 &
# Wait for port forwarding to establish
sleep 10
- name: Run LlamaStack integration tests with MaaS inference
env:
LLAMA_STACK_ENDPOINT: http://localhost:8321
RAG_UI_ENDPOINT: http://localhost:8501
INFERENCE_MODEL: ${{ env.MAAS_MODEL_ID }}
SKIP_MODEL_TESTS: "false" # Enable inference tests with MaaS
run: |
echo "Starting LlamaStack integration tests with MaaS-enabled inference..."
echo "Model: ${INFERENCE_MODEL}"
echo "MaaS Endpoint: ${MAAS_ENDPOINT}"
pytest tests/integration/llamastack/ -v --tb=short
- name: Debug - Get pod logs on failure
if: failure()
run: |
echo "=== Deployment status ==="
kubectl get deployments -n rag-e2e
echo "=== Pod status ==="
kubectl get pods -n rag-e2e -o wide
echo "=== Service status ==="
kubectl get services -n rag-e2e
echo "=== Events ==="
kubectl get events -n rag-e2e --sort-by='.lastTimestamp'
echo "=== RAG UI logs ==="
kubectl logs -l app.kubernetes.io/name=rag -n rag-e2e --tail=200 || echo "No RAG UI logs available"
echo "=== Llama Stack logs (CRITICAL - Check model registration) ==="
kubectl logs -l app.kubernetes.io/name=llamastack -n rag-e2e --tail=300 || echo "No Llama Stack logs available"
echo "=== Llama Stack pod details ==="
kubectl describe pod -l app.kubernetes.io/name=llamastack -n rag-e2e || echo "No pod details"
echo "=== PGVector logs ==="
kubectl logs -l app.kubernetes.io/name=pgvector -n rag-e2e --tail=100 || echo "No PGVector logs available"
echo "=== MinIO logs ==="
kubectl logs -l app.kubernetes.io/name=minio -n rag-e2e --tail=100 || echo "No MinIO logs available"
echo "=== Ingestion Pipeline logs (if enabled) ==="
kubectl logs -l app.kubernetes.io/name=ingestion-pipeline -n rag-e2e --tail=100 || echo "No ingestion pipeline logs"
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: llamastack-integration-test-results
path: pytest-results/
- name: Cleanup
if: always()
run: |
# Kill port-forward processes
pkill -f "kubectl port-forward" || true
# Optional: Uncomment to delete cluster
# kind delete cluster --name rag-e2e
fork-pr-notice:
name: Fork PR Notice
runs-on: ubuntu-latest
# Only run this job for fork PRs
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository
steps:
- name: Notice about skipped tests
run: |
echo "## ⚠️ Fork Pull Request Detected"
echo ""
echo "This pull request is from a fork. For security reasons, GitHub Actions does not"
echo "provide access to repository secrets (like \`MAAS_API_KEY\`) for workflows triggered"
echo "by pull requests from forks."
echo ""
echo "**The following tests have been skipped:**"
echo "- LlamaStack Integration Tests (requires MAAS_API_KEY)"
echo "- UI E2E Tests (requires MAAS_API_KEY)"
echo ""
echo "**Tests that still run:**"
echo "- ✅ Unit Tests"
echo "- ✅ Integration Tests (Streamlit App)"
echo ""
echo "The MaaS-dependent tests will run automatically when this PR is merged to \`main\`."
echo ""
echo "For more information, see:"
echo "https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions"
ui-e2e-tests:
name: UI E2E Tests (Playwright)
runs-on: ubuntu-latest
timeout-minutes: 60
needs: [unit-tests, integration-tests]
# Skip this job for fork PRs since secrets are not available
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install UI test dependencies
run: |
pip install -r tests/e2e_ui/requirements.txt
- name: Install Playwright browsers
run: |
playwright install chromium
- name: Create Kind cluster config file
run: |
cat <<EOF > kind-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 30080
hostPort: 8501
protocol: TCP
- containerPort: 30081
hostPort: 8321
protocol: TCP
EOF
- name: Create Kind cluster
uses: helm/kind-action@v1
with:
cluster_name: rag-e2e-ui
config: kind-config.yaml
- name: Install Required CRDs
run: |
echo "Installing CRDs required by helm chart subcomponents..."
# OpenShift Route CRD
kubectl apply -f - <<EOF
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: routes.route.openshift.io
spec:
group: route.openshift.io
names:
kind: Route
listKind: RouteList
plural: routes
singular: route
scope: Namespaced
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
x-kubernetes-preserve-unknown-fields: true
EOF
kubectl wait --for condition=established --timeout=60s crd/routes.route.openshift.io
echo "✅ All required CRDs installed successfully"
- name: Add Helm repository
run: |
helm repo add rag-charts https://rh-ai-quickstart.github.io/ai-architecture-charts
helm repo update
- name: Build Helm dependencies
run: |
cd deploy/helm/rag
helm dependency build
- name: Install RAG application
env:
MAAS_API_KEY: ${{ secrets.MAAS_API_KEY }}
run: |
kubectl create namespace rag-e2e-ui || true
helm install rag deploy/helm/rag \
--namespace rag-e2e-ui \
--values tests/e2e/values-e2e.yaml \
--set global.models.${MAAS_MODEL_ID}.url="${MAAS_ENDPOINT}" \
--set global.models.${MAAS_MODEL_ID}.id="${MAAS_MODEL_ID}" \
--set global.models.${MAAS_MODEL_ID}.enabled=true \
--set global.models.${MAAS_MODEL_ID}.apiToken="${MAAS_API_KEY}" \
--set-json llama-stack.initContainers='[]' \
--skip-crds \
--timeout 20m \
--debug
- name: Wait for services to be ready
run: |
kubectl wait --for=condition=available --timeout=600s \
deployment/llamastack -n rag-e2e-ui
kubectl wait --for=condition=available --timeout=300s \
deployment/rag -n rag-e2e-ui
echo "Waiting for pods to be ready..."
kubectl wait --for=condition=ready --timeout=600s \
pod -l app.kubernetes.io/name=llamastack -n rag-e2e-ui
kubectl wait --for=condition=ready --timeout=300s \
pod -l app.kubernetes.io/name=rag -n rag-e2e-ui
echo "✅ All pods are ready"
kubectl get pods -n rag-e2e-ui
- name: Expose services via NodePort
run: |
kubectl patch service rag -n rag-e2e-ui -p '{"spec":{"type":"NodePort","ports":[{"port":8501,"nodePort":30080}]}}'
kubectl patch service llamastack -n rag-e2e-ui -p '{"spec":{"type":"NodePort","ports":[{"port":8321,"nodePort":30081}]}}'
# Verify services
kubectl get services -n rag-e2e-ui
- name: Run UI E2E tests with Playwright
env:
RAG_UI_ENDPOINT: http://localhost:8501
LLAMA_STACK_ENDPOINT: http://localhost:8321
MAAS_ENDPOINT: ${{ env.MAAS_ENDPOINT }}
MAAS_MODEL_ID: ${{ env.MAAS_MODEL_ID }}
SKIP_MODEL_TESTS: "false" # Enable MaaS inference tests in UI
run: |
echo "Starting port forwarding and running tests..."
# Start port forwarding in background and keep them running
echo "Starting port forwarding for RAG UI..."
kubectl port-forward -n rag-e2e-ui svc/rag 8501:8501 > /tmp/rag-portforward.log 2>&1 &
RAG_PF_PID=$!
echo "RAG port-forward PID: $RAG_PF_PID"
echo "Starting port forwarding for LlamaStack..."
kubectl port-forward -n rag-e2e-ui svc/llamastack 8321:8321 > /tmp/llamastack-portforward.log 2>&1 &
LLAMASTACK_PF_PID=$!
echo "LlamaStack port-forward PID: $LLAMASTACK_PF_PID"
# Function to check if port forwarding is working
check_port_forwarding() {
(timeout 2 bash -c 'cat < /dev/null > /dev/tcp/localhost/8501' 2>/dev/null) && \
(timeout 2 bash -c 'cat < /dev/null > /dev/tcp/localhost/8321' 2>/dev/null)
}
# Function to verify processes are alive
check_processes() {
kill -0 $RAG_PF_PID 2>/dev/null && kill -0 $LLAMASTACK_PF_PID 2>/dev/null
}
# Wait for port forwarding to establish
echo "Waiting for port forwarding to be ready..."
for i in {1..30}; do
if check_port_forwarding && check_processes; then
echo "✅ Port forwarding is working! (attempt $i)"
break
fi
if [ $i -eq 30 ]; then
echo "❌ Port forwarding failed to establish after 30 attempts"
echo "RAG port-forward log:"
cat /tmp/rag-portforward.log || true
echo "LlamaStack port-forward log:"
cat /tmp/llamastack-portforward.log || true
echo "Checking port-forward processes:"
ps aux | grep "kubectl port-forward" || true
echo "Checking if ports are listening:"
ss -tlnp | grep -E ':(8501|8321)' || netstat -tlnp 2>/dev/null | grep -E ':(8501|8321)' || true
exit 1
fi
echo "Waiting for port forwarding... (attempt $i/30)"
sleep 2
done
# Set up cleanup trap to kill port forwarding on exit
trap "echo 'Cleaning up port forwarding...'; kill $RAG_PF_PID $LLAMASTACK_PF_PID 2>/dev/null || true" EXIT
echo "Running UI E2E tests with MaaS integration..."
echo "MaaS Endpoint: ${MAAS_ENDPOINT}"
echo "MaaS Model ID: ${MAAS_MODEL_ID}"
# Run tests
pytest tests/e2e_ui/ -v --tb=short --browser chromium || TEST_EXIT_CODE=$?
# Verify port forwarding was still working after tests
if ! check_port_forwarding; then
echo "⚠️ Warning: Port forwarding stopped working during tests"
echo "RAG port-forward log:"
cat /tmp/rag-portforward.log || true
echo "LlamaStack port-forward log:"
cat /tmp/llamastack-portforward.log || true
fi
# Exit with test result
exit ${TEST_EXIT_CODE:-0}
- name: Upload Playwright test results
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-test-results
path: |
test-results/
playwright-report/
- name: Upload Playwright screenshots
if: failure()
uses: actions/upload-artifact@v4
with:
name: playwright-screenshots
path: test-results/
- name: Debug - Get pod logs on failure
if: failure()
run: |
echo "=== RAG UI logs ==="
kubectl logs -l app.kubernetes.io/name=rag -n rag-e2e-ui --tail=200 || echo "No RAG UI logs"
echo "=== Llama Stack logs ==="
kubectl logs -l app.kubernetes.io/name=llamastack -n rag-e2e-ui --tail=300 || echo "No Llama Stack logs"
- name: Cleanup UI tests
if: always()
run: |
pkill -f "kubectl port-forward" || true
# kind delete cluster --name rag-e2e-ui