Skip to content

Run Frontend Tests #1071

Run Frontend Tests

Run Frontend Tests #1071

Workflow file for this run

name: Run Frontend Tests
on:
workflow_call:
secrets:
OPENAI_API_KEY:
required: true
STORE_API_KEY:
required: true
ANTHROPIC_API_KEY:
required: true
TAVILY_API_KEY:
required: true
inputs:
suites:
description: "Test suites to run (JSON array)"
required: false
type: string
default: '[]'
release:
description: "Whether this is a release build"
required: false
type: boolean
default: false
tests_folder:
description: "(Optional) Tests to run"
required: false
type: string
default: "tests"
ref:
description: "(Optional) ref to checkout"
required: false
type: string
workflow_dispatch:
inputs:
suites:
description: "Test suites to run (JSON array)"
required: false
type: string
default: '[]'
release:
description: "Whether this is a release build"
required: false
type: boolean
default: false
tests_folder:
description: "(Optional) Tests to run"
required: false
type: string
default: "tests"
env:
NODE_VERSION: "21"
PYTHON_VERSION: "3.12"
# Define the directory where Playwright browsers will be installed.
# Adjust if your project uses a different path.
PLAYWRIGHT_BROWSERS_PATH: "ms-playwright"
jobs:
determine-test-suite:
name: Determine Test Suites and Shard Distribution
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.setup-matrix.outputs.matrix }}
test_grep: ${{ steps.set-matrix.outputs.test_grep }}
suites: ${{ steps.set-matrix.outputs.suites }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref || github.ref }}
fetch-depth: 0
- name: Paths Filter
id: filter
uses: dorny/paths-filter@v3
with:
filters: .github/changes-filter.yaml
- name: Determine Test Suites from Changes
id: set-matrix
run: |
# Start with input suites if provided, otherwise empty array
echo "Changes filter output: ${{ steps.filter.outputs }}"
SUITES='${{ inputs.suites }}'
echo "Initial suites: $SUITES"
TEST_GREP=""
RELEASE="${{ inputs.release || 'false' }}"
echo "Release build: $RELEASE"
# Only set to release if it's explicitly a release build
if [[ "$RELEASE" == "true" ]]; then
SUITES='["release"]'
echo "Release build detected - setting suites to: $SUITES"
# grep pattern for release is the @release tag - run all tests
TEST_GREP="--grep=\"@release\""
else
# If input suites were not provided, determine based on changes
if [[ "$SUITES" == "[]" ]]; then
echo "No input suites provided - determining from changes"
TAGS=()
# Add suites and tags based on changed files
if [[ "${{ steps.filter.outputs.components }}" == "true" ]]; then
SUITES=$(echo $SUITES | jq -c '. += ["components"]')
TAGS+=("@components")
echo "Added components suite"
fi
if [[ "${{ steps.filter.outputs.starter-projects }}" == "true" ]]; then
SUITES=$(echo $SUITES | jq -c '. += ["starter-projects"]')
TAGS+=("@starter-projects")
echo "Added starter-projects suite"
fi
if [[ "${{ steps.filter.outputs.workspace }}" == "true" ]]; then
SUITES=$(echo $SUITES | jq -c '. += ["workspace"]')
TAGS+=("@workspace")
echo "Added workspace suite"
fi
if [[ "${{ steps.filter.outputs.api }}" == "true" ]]; then
SUITES=$(echo $SUITES | jq -c '. += ["api"]')
TAGS+=("@api")
echo "Added api suite"
fi
if [[ "${{ steps.filter.outputs.database }}" == "true" ]]; then
SUITES=$(echo $SUITES | jq -c '. += ["database"]')
TAGS+=("@database")
echo "Added database suite"
fi
# Create grep pattern if we have tags
if [ ${#TAGS[@]} -gt 0 ]; then
# Join tags with | for OR logic
REGEX_PATTERN=$(IFS='|'; echo "${TAGS[*]}")
TEST_GREP="--grep=\"${REGEX_PATTERN}\""
fi
else
# Process input suites to tags
TAGS=()
if echo "$SUITES" | jq -e 'contains(["components"])' > /dev/null; then
TAGS+=("@components")
fi
if echo "$SUITES" | jq -e 'contains(["starter-projects"])' > /dev/null; then
TAGS+=("@starter-projects")
fi
if echo "$SUITES" | jq -e 'contains(["workspace"])' > /dev/null; then
TAGS+=("@workspace")
fi
if echo "$SUITES" | jq -e 'contains(["api"])' > /dev/null; then
TAGS+=("@api")
fi
if echo "$SUITES" | jq -e 'contains(["database"])' > /dev/null; then
TAGS+=("@database")
fi
if [ ${#TAGS[@]} -gt 0 ]; then
# Join tags with | for OR logic
REGEX_PATTERN=$(IFS='|'; echo "${TAGS[*]}")
TEST_GREP="--grep \"${REGEX_PATTERN}\""
fi
fi
fi
# Ensure compact JSON output
SUITES=$(echo "$SUITES" | jq -c '.')
echo "Final test suites to run: $SUITES"
echo "Test grep pattern: $TEST_GREP"
# Ensure proper JSON formatting for matrix output
echo "matrix=$(echo $SUITES | jq -c .)" >> $GITHUB_OUTPUT
echo "test_grep=$TEST_GREP" >> $GITHUB_OUTPUT
- name: Setup Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v4
id: setup-node
with:
node-version: ${{ env.NODE_VERSION }}
cache: "npm"
cache-dependency-path: ./src/frontend/package-lock.json
- name: Install Frontend Dependencies
run: npm ci
working-directory: ./src/frontend
- name: Calculate Test Shards Distribution
id: setup-matrix
run: |
cd src/frontend
# Get the test count using playwright's built-in grep
if [ -n "${{ steps.set-matrix.outputs.test_grep }}" ]; then
TEST_COUNT=$(npx playwright test ${{ inputs.tests_folder }} ${{ steps.set-matrix.outputs.test_grep }} --list | wc -l)
else
TEST_COUNT=$(npx playwright test ${{ inputs.tests_folder }} --list | wc -l)
fi
echo "Total tests to run: $TEST_COUNT"
# Calculate optimal shard count - 1 shard per 5 tests, min 1, max 10
SHARD_COUNT=$(( (TEST_COUNT + 4) / 5 ))
if [ $SHARD_COUNT -lt 1 ]; then
SHARD_COUNT=1
elif [ $SHARD_COUNT -gt 10 ]; then
SHARD_COUNT=10
fi
# Create the matrix combinations string
MATRIX_COMBINATIONS=""
for i in $(seq 1 $SHARD_COUNT); do
if [ $i -gt 1 ]; then
MATRIX_COMBINATIONS="$MATRIX_COMBINATIONS,"
fi
MATRIX_COMBINATIONS="$MATRIX_COMBINATIONS{\"shardIndex\": $i, \"shardTotal\": $SHARD_COUNT}"
done
echo "matrix={\"include\":[$MATRIX_COMBINATIONS]}" >> "$GITHUB_OUTPUT"
setup-and-test:
name: Playwright Tests - Shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
runs-on: ubuntu-latest
if: ${{ needs.determine-test-suite.outputs.test_grep != '' }}
needs: determine-test-suite
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.determine-test-suite.outputs.matrix) }}
env:
OPENAI_API_KEY: ${{ inputs.openai_api_key || secrets.OPENAI_API_KEY }}
STORE_API_KEY: ${{ inputs.store_api_key || secrets.STORE_API_KEY }}
SEARCH_API_KEY: "${{ secrets.SEARCH_API_KEY }}"
ASTRA_DB_APPLICATION_TOKEN: "${{ secrets.ASTRA_DB_APPLICATION_TOKEN }}"
ASTRA_DB_API_ENDPOINT: "${{ secrets.ASTRA_DB_API_ENDPOINT }}"
ANTHROPIC_API_KEY: "${{ secrets.ANTHROPIC_API_KEY }}"
TAVILY_API_KEY: "${{ secrets.TAVILY_API_KEY }}"
UV_CACHE_DIR: /tmp/.uv-cache
outputs:
failed: ${{ steps.check-failure.outputs.failed }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref || github.ref }}
- name: Setup Node.js Environment
uses: actions/setup-node@v4
id: setup-node
with:
node-version: ${{ env.NODE_VERSION }}
cache: "npm"
cache-dependency-path: ./src/frontend/package-lock.json
- name: Install Frontend Dependencies
run: npm ci
working-directory: ./src/frontend
- name: Install Playwright Browser Dependencies
id: install-playwright
uses: ./.github/actions/install-playwright
with:
working-directory: ./src/frontend
browsers: chromium
- name: Setup Python Environment with UV
uses: ./.github/actions/setup-uv
- name: Install Python Dependencies
run: uv sync --dev
- name: Configure Environment Variables
run: |
touch .env
echo "${{ secrets.ENV_VARS }}" > .env
- name: Execute Playwright Tests
uses: nick-fields/retry@v3
with:
timeout_minutes: 12
max_attempts: 2
command: |
cd src/frontend
echo 'Running tests with pattern: ${{ needs.determine-test-suite.outputs.test_grep }}'
npx playwright test ${{ inputs.tests_folder }} ${{ needs.determine-test-suite.outputs.test_grep }} --shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --list
# echo command before running
echo "npx playwright test ${{ inputs.tests_folder }} ${{ needs.determine-test-suite.outputs.test_grep }} --trace on --shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --workers 2"
npx playwright test ${{ inputs.tests_folder }} ${{ needs.determine-test-suite.outputs.test_grep }} --trace on --shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --workers 2
- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v4
with:
name: blob-report-${{ matrix.shardIndex }}
path: src/frontend/blob-report
retention-days: 1
- name: Cleanup UV Cache
run: uv cache prune --ci
merge-reports:
# We need to repeat the condition at every step
# https://github.com/actions/runner/issues/662
needs: setup-and-test
runs-on: ubuntu-latest
if: always()
env:
EXIT_CODE: ${{!contains(needs.setup-and-test.result, 'failure') && !contains(needs.setup-and-test.result, 'cancelled') && '0' || '1'}}
steps:
- name: "Should Merge Reports"
# If the CI was successful, we don't need to merge the reports
# so we can skip all the steps below
id: should_merge_reports
run: |
if [ "$EXIT_CODE" == "0" ]; then
echo "should_merge_reports=false" >> $GITHUB_OUTPUT
else
echo "should_merge_reports=true" >> $GITHUB_OUTPUT
fi
- name: Checkout code
if: ${{ steps.should_merge_reports.outputs.should_merge_reports == 'true' }}
uses: actions/checkout@v4
- name: Setup Node.js
if: ${{ steps.should_merge_reports.outputs.should_merge_reports == 'true' }}
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Download blob reports from GitHub Actions Artifacts
if: ${{ steps.should_merge_reports.outputs.should_merge_reports == 'true' }}
uses: actions/download-artifact@v4
with:
path: all-blob-reports
pattern: blob-report-*
merge-multiple: true
- name: Merge into HTML Report
if: ${{ steps.should_merge_reports.outputs.should_merge_reports == 'true' }}
run: |
npx playwright merge-reports --reporter html ./all-blob-reports
- name: Upload HTML report
if: ${{ steps.should_merge_reports.outputs.should_merge_reports == 'true' }}
uses: actions/upload-artifact@v4
with:
name: html-report--attempt-${{ github.run_attempt }}
path: playwright-report
retention-days: 14