Skip to content

👷🛠️ PR Builder #8235

👷🛠️ PR Builder

👷🛠️ PR Builder #8235

Workflow file for this run

# This workflow will build the project on pull requests with tests
# Uses:
# OS: ubuntu-latest
# Go: go 1.x
name: 👷🛠️ PR Builder
on:
pull_request:
merge_group:
workflow_dispatch:
# Avoid running multiple PR builders for the same PR on subsequent pushes.
concurrency:
group: pr-builder-${{ github.ref }}
cancel-in-progress: true
env:
GOFLAGS: "-mod=readonly"
jobs:
dependency-guard:
name: 🛡️ Dependency Guard
if: ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' }}
uses: ./.github/workflows/dependency-guard.yml
with:
detection-mode: pr
security-audit:
name: 🔒 Security Audit
if: ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' }}
runs-on: ubuntu-latest
steps:
- name: 📥 Checkout Code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: ⚙️ Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 'lts/*'
- name: 📦 Install pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
with:
version: 'latest'
run_install: false
- name: 🔍 Run pnpm audit (Frontend)
working-directory: frontend
run: |
echo "🔍 Running pnpm audit for frontend dependencies..."
IGNORE_ARGS=()
while IFS= read -r id; do
IGNORE_ARGS+=(--ignore "$id")
done < <(jq -r '.frontend[].id' "$GITHUB_WORKSPACE/.audit-ignore.json")
pnpm audit --audit-level=high "${IGNORE_ARGS[@]}"
- name: 🔍 Run npm audit (E2E Tests)
working-directory: tests/e2e
run: |
echo "🔍 Running npm audit for E2E test dependencies..."
IGNORED_GHSAS=$(jq '[(.common + .e2e)[].id]' "$GITHUB_WORKSPACE/.audit-ignore.json")
AUDIT_RESULT=$(npm audit --json --audit-level=high 2>&1) || true
REMAINING=$(echo "$AUDIT_RESULT" | jq --argjson ignored "$IGNORED_GHSAS" '
[.vulnerabilities[].via[] | select(type == "object")
| select(.severity == "high" or .severity == "critical")
| select(.url | test($ignored | join("|")) | not)
] | length
')
if [ "$REMAINING" -gt 0 ]; then
echo "❌ Found $REMAINING unignored high/critical advisories:"
echo "$AUDIT_RESULT" | jq --argjson ignored "$IGNORED_GHSAS" '
[.vulnerabilities[].via[] | select(type == "object")
| select(.severity == "high" or .severity == "critical")
| select(.url | test($ignored | join("|")) | not)
] | unique_by(.url) | .[] | "\(.severity): \(.name) - \(.url)"
'
exit 1
fi
echo "✅ All high/critical advisories are in the ignored list."
- name: 🔍 Run npm audit (Sample Apps)
run: |
echo "🔍 Running npm audit for sample app dependencies..."
IGNORED_GHSAS=$(jq '[(.common + ."sample-apps")[].id]' "$GITHUB_WORKSPACE/.audit-ignore.json")
for app in samples/apps/*/; do
if [ -f "${app}package-lock.json" ]; then
echo "Auditing $app..."
cd "$app"
AUDIT_RESULT=$(npm audit --json --audit-level=high 2>&1) || true
REMAINING=$(echo "$AUDIT_RESULT" | jq --argjson ignored "$IGNORED_GHSAS" '
[.vulnerabilities[].via[] | select(type == "object")
| select(.severity == "high" or .severity == "critical")
| select(.url | test($ignored | join("|")) | not)
] | length
')
if [ "$REMAINING" -gt 0 ]; then
echo "❌ Found $REMAINING unignored high/critical advisories in $app:"
echo "$AUDIT_RESULT" | jq --argjson ignored "$IGNORED_GHSAS" '
[.vulnerabilities[].via[] | select(type == "object")
| select(.severity == "high" or .severity == "critical")
| select(.url | test($ignored | join("|")) | not)
] | unique_by(.url) | .[] | "\(.severity): \(.name) - \(.url)"
'
exit 1
fi
echo "✅ $app - All high/critical advisories are in the ignored list."
cd - > /dev/null
fi
done
dependency-review:
name: 🔎 Dependency Review
if: ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' }}
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- name: 📥 Checkout Code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: 🔎 Dependency Review
uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4
with:
fail-on-severity: high
deny-licenses: GPL-3.0-only,GPL-3.0-or-later,AGPL-3.0-only,AGPL-3.0-or-later
comment-summary-in-pr: always
base-ref: ${{ github.event.merge_group.base_sha || github.event.pull_request.base.sha }}
head-ref: ${{ github.event.merge_group.head_sha || github.event.pull_request.head.sha }}
verify-mocks:
name: 🔍 Verify Mock Files
if: ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' }}
runs-on: ubuntu-latest
steps:
- name: 📥 Checkout Code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: ⚙️ Set up Go Environment
uses: ./.github/actions/setup-go
- name: 🔧 Install Mockery
run: make install-mockery
- name: 🏭 Generate Mocks
run: make mockery
- name: 🔎 Check for Outdated Mocks
run: |
if [ -n "$(git status --porcelain --untracked-files=all -- backend/tests/mocks backend/internal)" ]; then
echo "❌ Mock files are out of sync with interface definitions!"
echo ""
echo "The following mock files need to be regenerated:"
git status --porcelain --untracked-files=all -- backend/tests/mocks backend/internal | awk '{print $2}'
echo ""
echo "To fix this:"
echo " 1. Run: make mockery"
echo " 2. Commit the updated mock files"
echo " 3. Push your changes"
echo ""
echo "This ensures mock implementations stay synchronized with their interfaces."
exit 1
fi
echo "✅ All mock files are up to date"
lint:
name: 🧹 Lint Code
needs: dependency-guard
if: ${{ always() && (needs.dependency-guard.result == 'success' || needs.dependency-guard.result == 'skipped') && (github.event_name == 'pull_request' || github.event_name == 'merge_group') }}
runs-on: ubuntu-latest
steps:
- name: 📥 Checkout Code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
# We need to fetch all branches and commits so that Nx affected has a base to compare against.
fetch-depth: 0
- name: ⚙️ Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 'lts/*'
- name: 🔄 Fetch main branch for Nx affected base
run: git fetch origin main:main --update-head-ok || true
- name: 🐳 Set SHAs for Nx
id: set-shas
uses: nrwl/nx-set-shas@3e9ad7370203c1e93d109be57f3b72eb0eb511b1 # v4.4.0
with:
main-branch-name: "main"
- name: 📦 Install pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
with:
version: 'latest'
run_install: false
cache_dependency_path: frontend/pnpm-lock.yaml
- name: ⚙️ Set up Go Environment
uses: ./.github/actions/setup-go
- name: 📦 Prepare golangci-lint Locally
run: make golangci-lint
- name: 🔍 Run Backend Linter
run: make lint_backend
- name: 🧩 Install Dependencies & Build Frontend
run: |
cd frontend
pnpm install --frozen-lockfile
pnpm build
- name: 🔍 Run Frontend Linter
run: |
cd frontend
pnpm nx affected --target=lint --parallel=3 --base=${{ steps.set-shas.outputs.base }} --head=${{ steps.set-shas.outputs.head }}
- name: 🎨 Check Frontend Formatting
run: |
cd frontend
pnpm format:check
build:
name: 🛠️ Build Product
needs: dependency-guard
if: ${{ always() && (needs.dependency-guard.result == 'success' || needs.dependency-guard.result == 'skipped') && (github.event.label.name == 'trigger-pr-builder' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request' || github.event_name == 'merge_group') }}
runs-on: ubuntu-latest
steps:
- name: 📥 Checkout Code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: ⚙️ Set up Go Environment
uses: ./.github/actions/setup-go
- name: ⚙️ Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 'lts/*'
- name: 📦 Install pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
with:
version: 'latest'
run_install: false
cache_dependency_path: frontend/pnpm-lock.yaml
- name: 🗄️ Cache Go Modules
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
id: cache-go-modules
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-modules-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-modules-
- name: 📦 Install Backend Dependencies
run: |
cd backend
go mod download
cd ../tests/integration
go mod download
- name: 📦 Install Frontend Dependencies
run: |
cd frontend
pnpm install --frozen-lockfile
- name: 🧹 Clean Previous Builds
run: |
set -e
make clean
- name: 🔨 Build Frontend
run: |
cd frontend
pnpm build
- name: 🔨 Build Product with Coverage
run: |
set -e
export LOG_LEVEL=debug
make build_with_coverage_only OS=$(go env GOOS) ARCH=$(go env GOARCH)
# Find the built distribution
DIST_PATH=$(find target/dist -name "thunder-*.zip" | head -1)
echo "Built distribution: $DIST_PATH"
- name: 📦 Upload Built Distribution
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: thunder-distribution
path: target/dist/*.zip
if-no-files-found: error
- name: 📊 Upload Unit Test Coverage Report to Codecov
uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: backend/coverage_unit.out
disable_search: true
flags: backend-unit
name: Backend Unit Tests
fail_ci_if_error: false
- name: 📦 Archive Unit Coverage Report
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: unit-coverage-report
path: backend/coverage_unit.out
if-no-files-found: error
test-frontend:
name: 🧪 Frontend Tests (shard ${{ matrix.shard }}/4)
needs: dependency-guard
if: ${{ always() && (needs.dependency-guard.result == 'success' || needs.dependency-guard.result == 'skipped') && (github.event.label.name == 'trigger-pr-builder' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request' || github.event_name == 'merge_group') }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
steps:
- name: 📥 Checkout Code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: ⚙️ Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 'lts/*'
- name: 📦 Install pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
with:
version: 'latest'
run_install: false
cache_dependency_path: frontend/pnpm-lock.yaml
- name: 📦 Install Frontend Dependencies
run: |
cd frontend
pnpm install --frozen-lockfile
- name: 🔨 Build Frontend Packages
run: |
cd frontend
pnpm build:packages
- name: 🧪 Run Console Tests with Coverage
run: |
cd frontend/apps/thunder-console
pnpm vitest run --coverage --shard=${{ matrix.shard }}/4
- name: 🧪 Run Gate Tests with Coverage
if: matrix.shard == 1
run: |
cd frontend/apps/thunder-gate
pnpm test:coverage
# Strip branch data (BRDA/BRF/BRH) from frontend LCOV reports before uploading.
# React Compiler (babel-plugin-react-compiler) generates phantom branch points in compiled
# output that inflate the partial-line count in Codecov's coverage calculation.
# This is a known upstream issue: https://github.com/facebook/react/issues/32950
# Line and function coverage remain unaffected and accurately reflect test quality.
- name: 🧹 Strip branch data from frontend LCOV reports
run: |
for lcov_file in frontend/apps/thunder-console/coverage/lcov.info frontend/apps/thunder-gate/coverage/lcov.info; do
if [ -f "$lcov_file" ]; then
sed -i '/^BRDA:/d;/^BRF:/d;/^BRH:/d' "$lcov_file"
fi
done
- name: 📦 Upload console coverage artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: console-coverage-shard-${{ matrix.shard }}
path: frontend/apps/thunder-console/coverage/lcov.info
if-no-files-found: error
- name: 📦 Upload gate coverage artifact
if: matrix.shard == 1
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: gate-coverage
path: frontend/apps/thunder-gate/coverage/lcov.info
if-no-files-found: error
upload-frontend-coverage:
name: 📊 Upload Frontend Coverage
needs: test-frontend
if: always() && needs.test-frontend.result == 'success'
runs-on: ubuntu-latest
steps:
- name: 📥 Checkout Code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: 📥 Download all coverage artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
pattern: console-coverage-shard-*
path: coverage/console
- name: 📥 Download gate coverage artifact
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: gate-coverage
path: coverage/gate
- name: 🔀 Merge console coverage from shards
run: |
cat coverage/console/console-coverage-shard-*/lcov.info > coverage/console-merged-lcov.info
- name: 📊 Upload `@thunder/console` Unit Test Coverage Report to Codecov
uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage/console-merged-lcov.info
disable_search: true
flags: frontend-apps-console-unit
name: Frontend Console App Unit Tests
fail_ci_if_error: false
- name: 📊 Upload `@thunder/gate` Unit Test Coverage Report to Codecov
uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage/gate/lcov.info
disable_search: true
flags: frontend-apps-gate-unit
name: Frontend Gate App Unit Tests
fail_ci_if_error: false
build_samples:
name: 🛠️ Build Sample Apps
needs: dependency-guard
if: ${{ always() && (needs.dependency-guard.result == 'success' || needs.dependency-guard.result == 'skipped') && (github.event.label.name == 'trigger-pr-builder' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request' || github.event_name == 'merge_group') }}
runs-on: ubuntu-latest
steps:
- name: 📥 Checkout Code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: ⚙️ Set up Go Environment
uses: ./.github/actions/setup-go
- name: ⚙️ Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 'lts/*'
- name: 📦 Install pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
with:
version: 'latest'
run_install: false
cache_dependency_path: frontend/pnpm-lock.yaml
- name: 🧩 Build and Package Sample Apps
run: |
make build_samples
make package_samples OS=$(go env GOOS) ARCH=$(go env GOARCH)
- name: 📦 Upload Built Sample App
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: sample-app-react-sdk
path: target/dist/sample-app-react-sdk-*.zip
test-integration:
name: 🧪 Integration Tests (${{ matrix.database }})
needs: build
if: ${{ always() && needs.build.result == 'success' }}
runs-on: ubuntu-latest
strategy:
matrix:
database: [sqlite, postgres]
fail-fast: false
services:
postgres:
image: postgres:latest
env:
POSTGRES_USER: asgthunder
POSTGRES_PASSWORD: asgthunder
POSTGRES_DB: thunderdb
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: 📥 Checkout Code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: ⚙️ Set up Go Environment
uses: ./.github/actions/setup-go
- name: 📥 Download Unit Coverage Report
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: unit-coverage-report
path: backend/
- name: 🗄️ Cache Go Modules
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
id: cache-go-modules
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-modules-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-modules-
- name: 📦 Install Dependencies
run: |
cd backend
go mod download
cd ../tests/integration
go mod download
- name: 📝 Configure Test Database
run: |
chmod +x tests/integration/resources/scripts/setup-test-config.sh
./tests/integration/resources/scripts/setup-test-config.sh
env:
DB_TYPE: ${{ matrix.database }}
- name: 🧪 Run Integration Tests (${{ matrix.database }})
uses: ./.github/actions/run-integration-tests
with:
database-type: ${{ matrix.database }}
coverage-enabled: true
- name: 📊 Upload Integration Test Coverage Report to Codecov
uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: target/coverage_integration.out
disable_search: true
flags: backend-integration-${{ matrix.database }}
name: Backend Integration Tests (${{ matrix.database }})
fail_ci_if_error: false
# - name: 🧩 Generate Combined Coverage Report
# run: ./build.sh merge_coverage
# - name: 📊 Upload Combined Coverage Report to Codecov
# uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4
# with:
# token: ${{ secrets.CODECOV_TOKEN }}
# files: target/coverage_combined.out
# disable_search: true
# flags: backend-combined-${{ matrix.database }}
# name: Backend-combined Coverage (${{ matrix.database }})
# fail_ci_if_error: false
test-e2e:
name: 🎭 Playwright E2E Tests
needs: [build, build_samples]
if: ${{ always() && needs.build.result == 'success' && needs.build_samples.result == 'success' }}
runs-on: ubuntu-latest
steps:
- name: 📥 Checkout Code
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: ⚙️ Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 'lts/*'
- name: 📥 Download Built Distribution
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: thunder-distribution
path: target/dist/
- name: 📥 Download Built Sample App
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: sample-app-react-sdk
path: target/dist/
- name: 📂 Extract Server to E2E
run: |
mkdir -p tests/e2e/thunder-server
unzip target/dist/thunder-*.zip -d tests/e2e/thunder-server
cd tests/e2e/thunder-server
# Find the extracted folder (name includes version and arch) and move its contents to the root of tests/e2e/thunder-server
EXTRACTED_DIR=$(find . -maxdepth 1 -type d -name "thunder-*" | head -n 1)
if [ -n "$EXTRACTED_DIR" ]; then
mv "$EXTRACTED_DIR"/* .
rmdir "$EXTRACTED_DIR"
fi
chmod +x setup.sh start.sh
- name: 🚀 Start Thunder Server
run: |
cd tests/e2e/thunder-server
./setup.sh
THUNDER_SKIP_SECURITY=true ./start.sh &
THUNDER_PID=$!
echo "THUNDER_PID=$THUNDER_PID" >> $GITHUB_ENV
# Wait for server to be ready
echo "Waiting for server to be ready on https://localhost:8090..."
for i in {1..60}; do
if curl -s -k https://localhost:8090/health/liveness > /dev/null; then
echo "Server is UP!"
break
fi
echo "Still waiting ($i/60)..."
sleep 2
done
# Verify server is actually running
if ! curl -s -k https://localhost:8090/health/liveness > /dev/null; then
echo "Server failed to start within 2 minutes"
exit 1
fi
echo "Server started successfully!"
- name: 🔍 Extract Sample App ID
id: extract-app-id
run: |
echo "Fetching applications from Thunder..."
# Get applications list
APPS_RESPONSE=$(curl -s -k https://localhost:8090/applications)
# Extract Sample App ID using jq
SAMPLE_APP_ID=$(echo "$APPS_RESPONSE" | jq -r '.applications[] | select(.name == "Sample App") | .id')
if [ -z "$SAMPLE_APP_ID" ] || [ "$SAMPLE_APP_ID" == "null" ]; then
echo "⚠️ Warning: Sample App not found in applications list"
echo "Available applications:"
echo "$APPS_RESPONSE" | jq -r '.applications[] | " - \(.name) (\(.id))"'
echo "sample_app_id=" >> $GITHUB_OUTPUT
else
echo "✓ Sample App ID extracted: $SAMPLE_APP_ID"
echo "sample_app_id=$SAMPLE_APP_ID" >> $GITHUB_OUTPUT
fi
- name: 🔄 Restart Thunder Server with Security Enabled
run: |
cd tests/e2e/thunder-server
echo "Stopping Thunder server..."
# Kill the start.sh wrapper process
kill $THUNDER_PID 2>/dev/null || true
# Also kill any process still listening on port 8090
# (start.sh backgrounds the thunder binary, so killing start.sh
# may leave the thunder process running as an orphan)
sleep 2
if lsof -ti tcp:8090 >/dev/null 2>&1; then
echo "Port 8090 still in use, killing remaining processes..."
kill -9 $(lsof -ti tcp:8090) 2>/dev/null || true
fi
# Wait for port to be fully released
sleep 3
echo "Starting Thunder server with security enabled..."
./start.sh &
THUNDER_PID=$!
echo "THUNDER_PID=$THUNDER_PID" >> $GITHUB_ENV
# Wait for server to be ready
echo "Waiting for server to be ready on https://localhost:8090..."
for i in {1..60}; do
if curl -s -k https://localhost:8090/health/liveness > /dev/null; then
echo "Server is UP with security enabled!"
break
fi
echo "Still waiting ($i/60)..."
sleep 2
done
# Verify server is actually running
if ! curl -s -k https://localhost:8090/health/liveness > /dev/null; then
echo "Server failed to restart within 2 minutes"
exit 1
fi
echo "Server restarted successfully with security enabled!"
- name: 📱 Extract and Start Sample App
run: |
echo "Extracting Sample App..."
mkdir -p tests/e2e/sample-app
# Find the sample app zip file (platform-specific)
SAMPLE_APP_ZIP=$(find target/dist -name "sample-app-react-sdk-*.zip" | head -n 1)
if [ -z "$SAMPLE_APP_ZIP" ]; then
echo "⚠️ Warning: Sample app zip not found in target/dist"
echo "Available files:"
ls -la target/dist/
exit 1
fi
echo "Found sample app: $SAMPLE_APP_ZIP"
unzip -q "$SAMPLE_APP_ZIP" -d tests/e2e/sample-app
cd tests/e2e/sample-app
# Find the extracted folder and move its contents
EXTRACTED_DIR=$(find . -maxdepth 1 -type d -name "sample-app-*" | head -n 1)
if [ -n "$EXTRACTED_DIR" ]; then
mv "$EXTRACTED_DIR"/* .
rmdir "$EXTRACTED_DIR"
fi
# Make start script executable
chmod +x start.sh
echo "Starting Sample App..."
./start.sh &
# Wait for sample app to be ready
echo "Waiting for sample app to be ready on https://localhost:3000..."
for i in {1..60}; do
if curl -s -k https://localhost:3000 > /dev/null 2>&1; then
echo "Sample App is UP!"
break
fi
echo "Still waiting ($i/60)..."
sleep 2
done
# Verify sample app is running
if ! curl -s -k https://localhost:3000 > /dev/null 2>&1; then
echo "Sample app failed to start within 2 minutes"
exit 1
fi
echo "Sample App started successfully!"
- name: Install E2E dependencies
run: npm ci
working-directory: tests/e2e
- name: Install Playwright Browsers
run: npx playwright install --with-deps
working-directory: tests/e2e
- name: Run Playwright tests
run: npx playwright test
working-directory: tests/e2e
env:
# Configuration Priority: Secret > Variable > Hardcoded Default
BASE_URL: ${{ secrets.PLAYWRIGHT_BASE_URL || vars.PLAYWRIGHT_BASE_URL || 'https://localhost:8090' }}
ADMIN_USERNAME: ${{ secrets.PLAYWRIGHT_ADMIN_USERNAME || 'admin' }}
ADMIN_PASSWORD: ${{ secrets.PLAYWRIGHT_ADMIN_PASSWORD || 'admin' }}
TEST_USER_USERNAME: ${{ secrets.PLAYWRIGHT_TEST_USER_USERNAME || 'testuser' }}
TEST_USER_PASSWORD: ${{ secrets.PLAYWRIGHT_TEST_USER_PASSWORD || 'admin' }}
PLAYWRIGHT_WORKERS: ${{ vars.PLAYWRIGHT_WORKERS || 1 }}
DEBUG_AUTH: ${{ vars.PLAYWRIGHT_DEBUG_AUTH || 'false' }}
# MFA Test Configuration
SAMPLE_APP_ID: ${{ steps.extract-app-id.outputs.sample_app_id }}
SAMPLE_APP_URL: 'https://localhost:3000'
THUNDER_URL: 'https://localhost:8090'
AUTO_SETUP_MFA: ${{ vars.AUTO_SETUP_MFA || 'true' }}
MOCK_SMS_SERVER_PORT: ${{ vars.MOCK_SMS_SERVER_PORT || 8098 }}
SAMPLE_APP_USERNAME: ${{ secrets.SAMPLE_APP_USERNAME || 'e2e-test-user' }}
SAMPLE_APP_PASSWORD: ${{ secrets.SAMPLE_APP_PASSWORD || 'e2e-test-password' }}
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: tests/e2e/playwright-report/
retention-days: 30
detect-powershell-changes:
name: 🔍 Detect PowerShell Changes
if: github.event_name == 'pull_request' || github.event_name == 'merge_group'
runs-on: ubuntu-latest
outputs:
should-run: ${{ steps.filter.outputs.powershell }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3
id: filter
with:
base: ${{ github.event_name == 'merge_group' && github.event.merge_group.base_sha || github.event.pull_request.base.sha }}
ref: ${{ github.event_name == 'merge_group' && github.sha || github.event.pull_request.head.sha }}
filters: |
powershell:
- '**.ps1'
- '.github/workflows/windows-powershell-validation.yml'
- name: ✅ PowerShell files detected
if: steps.filter.outputs.powershell == 'true'
run: echo "PowerShell files changed - validation will run"
- name: ⏭️ No PowerShell changes
if: steps.filter.outputs.powershell != 'true'
run: echo "No PowerShell changes - skipping validation"
validate-windows:
name: 🪟 Validate Windows PowerShell
needs: detect-powershell-changes
if: needs.detect-powershell-changes.outputs.should-run == 'true'
uses: ./.github/workflows/windows-powershell-validation.yml