Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 148 additions & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,153 @@ jobs:
- name: Run Integration Tests
run: echo "npm run test:integration"

test-e2e:
runs-on: ubuntu-latest
timeout-minutes: 45

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup .NET Core
uses: actions/setup-dotnet@v5
with:
dotnet-version: 10.0.*
dotnet-quality: ga

- name: Install Aspire CLI
run: |
export PATH="$HOME/.dotnet/tools:$PATH"
if dotnet tool list --global | grep -Eiq '^aspire\.cli[[:space:]]'; then
dotnet tool update --global Aspire.Cli --version 13.3.4
else
dotnet tool install --global Aspire.Cli --version 13.3.4
fi
echo "$HOME/.dotnet/tools" >> "$GITHUB_PATH"
aspire --version

- name: Setup Node.js environment
uses: actions/setup-node@v6
with:
node-version: 24

- uses: actions/cache@v5
with:
path: ~/.nuget/packages
key: nuget-${{ runner.os }}-${{ hashFiles('**/packages.lock.json') }}
restore-keys: |
nuget-${{ runner.os }}-

- name: Nuget Restore
run: dotnet restore ./Exceptionless.slnx

- name: Build
run: dotnet build ./Exceptionless.slnx --no-restore --configuration Release

- name: Install Client Npm Packages
working-directory: src/Exceptionless.Web/ClientApp
run: npm ci

- name: Install Legacy Client Npm Packages
working-directory: src/Exceptionless.Web/ClientApp.angular
run: npm ci

- name: Install Playwright Chromium
working-directory: src/Exceptionless.Web/ClientApp
run: npx playwright install chromium --with-deps

- name: Start AppHost
run: |
aspire run --non-interactive --nologo -- --ci-e2e > aspire-run.log 2>&1 &
echo "$!" > aspire-run.pid
sleep 5
if ! kill -0 "$(cat aspire-run.pid)" 2>/dev/null; then
cat aspire-run.log
exit 1
fi

- name: Wait for Aspire Resources
run: |
for attempt in {1..60}; do
if curl -fksS https://web-ex.dev.localhost:7131/api/v2/about > /dev/null &&
curl -fksS https://web-ex.dev.localhost:7131/next/login > /dev/null; then
break
fi

if ! kill -0 "$(cat aspire-run.pid)" 2>/dev/null; then
cat aspire-run.log
exit 1
fi

if [ "$attempt" -eq 60 ]; then
cat aspire-run.log
exit 1
fi

sleep 5
done

- name: Verify E2E Endpoints
run: |
curl -fksS https://web-ex.dev.localhost:7131/api/v2/about > /dev/null
curl -fksS https://web-ex.dev.localhost:7131/next/login > /dev/null

- name: Run Playwright E2E Tests
working-directory: src/Exceptionless.Web/ClientApp
env:
E2E_URL: https://web-ex.dev.localhost:7131
E2E_RUN_ID: ci-${{ github.run_id }}-${{ github.run_attempt }}
run: npm run test:e2e:ci

- name: Stop AppHost
if: ${{ always() }}
run: |
aspire stop --all --non-interactive || true
if [ -f aspire-run.pid ]; then
kill "$(cat aspire-run.pid)" 2>/dev/null || true
fi

- name: Collect Aspire Logs
if: ${{ !cancelled() }}
run: |
mkdir -p aspire-logs
if [ -f aspire-run.log ]; then
cp aspire-run.log aspire-logs/
fi
if [ -f aspire-run.pid ]; then
cp aspire-run.pid aspire-logs/
fi
if [ -d "$HOME/.aspire/cli/logs" ]; then
cp -r "$HOME/.aspire/cli/logs/." aspire-logs/
fi
if [ -d "$HOME/.aspire/logs" ]; then
cp -r "$HOME/.aspire/logs/." aspire-logs/
fi

- name: Upload Playwright Report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v5
with:
name: playwright-report
path: src/Exceptionless.Web/ClientApp/playwright-report/
retention-days: 14

- name: Upload Playwright Test Results
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v5
with:
name: playwright-test-results
path: src/Exceptionless.Web/ClientApp/test-results/
retention-days: 14

- name: Upload Aspire Logs
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v5
with:
name: aspire-logs
path: aspire-logs/
retention-days: 14

docker-build:
runs-on: ubuntu-latest
needs: [version]
Expand Down Expand Up @@ -290,7 +437,7 @@ jobs:
docker-publish:
if: ${{ needs.version.outputs.should_publish == 'true' }}
runs-on: ubuntu-latest
needs: [version, docker-build, test-api, test-client]
needs: [version, docker-build, test-api, test-client, test-e2e]
timeout-minutes: 30
env:
VERSION: ${{ needs.version.outputs.version }}
Expand Down
68 changes: 68 additions & 0 deletions .github/workflows/production-e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Production E2E Tests

on:
workflow_dispatch:
schedule:
- cron: "0 8 * * 1"

permissions:
contents: read

concurrency:
group: production-e2e
cancel-in-progress: false

jobs:
e2e:
runs-on: ubuntu-latest
timeout-minutes: 30
defaults:
run:
working-directory: src/Exceptionless.Web/ClientApp

steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup Node.js environment
uses: actions/setup-node@v6
with:
node-version: 24

- name: Install Npm Packages
run: npm ci

- name: Install Playwright Chromium
run: npx playwright install chromium --with-deps

- name: Run Production E2E Tests
env:
E2E_EMAIL: ${{ secrets.E2E_EMAIL }}
E2E_PASSWORD: ${{ secrets.E2E_PASSWORD }}
E2E_URL: https://app.exceptionless.io
E2E_RUN_ID: production-${{ github.run_id }}-${{ github.run_attempt }}
Comment thread
ejsmith marked this conversation as resolved.
run: npm run test:e2e:prod
Comment thread
ejsmith marked this conversation as resolved.
Comment thread
ejsmith marked this conversation as resolved.

- name: Write Summary
if: ${{ always() }}
run: |
echo "### Production E2E Tests" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- Run ID: production-${{ github.run_id }}-${{ github.run_attempt }}" >> $GITHUB_STEP_SUMMARY
echo "- E2E URL: https://app.exceptionless.io" >> $GITHUB_STEP_SUMMARY

- name: Upload Playwright Report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v5
with:
name: production-playwright-report
path: src/Exceptionless.Web/ClientApp/playwright-report/
retention-days: 14

- name: Upload Playwright Test Results
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v5
with:
name: production-playwright-test-results
path: src/Exceptionless.Web/ClientApp/test-results/
retention-days: 14
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ artifacts
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
coverage/
src/Exceptionless.Web/ClientApp/playwright-report/
src/Exceptionless.Web/ClientApp/test-results/

# IDE / editor
Expand Down
22 changes: 17 additions & 5 deletions src/Exceptionless.AppHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
using Microsoft.Extensions.Hosting;

var builder = DistributedApplication.CreateBuilder(args);
var servicesOnly = args.Any(arg => StringComparer.OrdinalIgnoreCase.Equals(arg, "--services-only") || StringComparer.OrdinalIgnoreCase.Equals(arg, "services-only"));
var servicesOnly = HasArgument("--services-only");
var ciE2E = HasArgument("--ci-e2e");
var includeDevTools = !ciE2E;

var elastic = builder.AddElasticsearch("Elasticsearch", port: 9200)
.WithDataVolume(servicesOnly ? null : "exceptionless.data.v1");
Expand Down Expand Up @@ -49,20 +51,28 @@
{
elastic = elastic
.WithLifetime(ContainerLifetime.Persistent)
.WithContainerName("Exceptionless-Elasticsearch")
.WithKibana(b => b
.WithContainerName("Exceptionless-Elasticsearch");

if (includeDevTools)
{
elastic = elastic.WithKibana(b => b
.WithLifetime(ContainerLifetime.Persistent)
.WithContainerName("Exceptionless-Kibana")
.WithParentRelationship(elastic));
}

cache = cache
.WithLifetime(ContainerLifetime.Persistent)
.WithContainerName("Exceptionless-Redis")
.WithRedisInsight(b => b
.WithContainerName("Exceptionless-Redis");

if (includeDevTools)
{
cache = cache.WithRedisInsight(b => b
.WithLifetime(ContainerLifetime.Persistent)
.WithContainerName("Exceptionless-RedisInsight")
.WithUrlForEndpoint("http", u => u.DisplayText = "Redis")
.WithParentRelationship(cache), containerName: "Redis-insight");
}

mail = mail
.WithLifetime(ContainerLifetime.Persistent)
Expand Down Expand Up @@ -153,3 +163,5 @@
}

await builder.Build().RunAsync();

bool HasArgument(string name) => args.Any(arg => StringComparer.OrdinalIgnoreCase.Equals(arg, name) || StringComparer.OrdinalIgnoreCase.Equals(arg, name.TrimStart('-')));
2 changes: 2 additions & 0 deletions src/Exceptionless.Web/ClientApp/.prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ yarn.lock

# Generated files
src/lib/generated
playwright-report
test-results

# Third-party
.agents/
Expand Down
Loading
Loading