Skip to content
Open
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
7042674
Update sample app to receive input args on Android (required for test…
tustanivsky Nov 11, 2025
6e34b25
Add Android integration tests to CI
tustanivsky Nov 11, 2025
588aada
Comment crash capturing test
tustanivsky Nov 11, 2025
c82a55e
Fix test script
tustanivsky Nov 12, 2025
6b14d0a
Fix minor issues
tustanivsky Nov 12, 2025
a7bb5e3
Rename vars
tustanivsky Nov 12, 2025
7b3ab68
Clean up
tustanivsky Nov 12, 2025
a47a7ea
Better comments
tustanivsky Nov 12, 2025
35c488b
Refactor
tustanivsky Nov 12, 2025
297cf36
Fix workflow
tustanivsky Nov 12, 2025
d62218d
Fix
tustanivsky Nov 12, 2025
35862ce
Simplify mechanism to pass input args to Android app
tustanivsky Nov 13, 2025
3bc3f33
Clean up comments
tustanivsky Nov 13, 2025
281af4a
Merge branch 'main' into chore/android-tests
tustanivsky Nov 20, 2025
c9079bc
Test SauceLabs
tustanivsky Nov 20, 2025
957c826
Add separate SauceLabs test script
tustanivsky Nov 21, 2025
11d997a
Fix log retrieval
tustanivsky Nov 21, 2025
f87da41
Fix
tustanivsky Nov 21, 2025
4ea1b22
Rename script for running Android integration tests locally
tustanivsky Nov 21, 2025
d09d95e
Update readme
tustanivsky Nov 21, 2025
89c6014
Revert CI tweaks for faster iterations
tustanivsky Nov 21, 2025
b99f9a6
Clean up
tustanivsky Nov 21, 2025
4c7547b
Fix
tustanivsky Nov 21, 2025
f0bb43d
Fix typo
tustanivsky Nov 24, 2025
5fea785
Limit android e2e tests to 2 parallel jobs
tustanivsky Nov 24, 2025
9eeb769
Remove Pester install step as it is redundant
tustanivsky Nov 24, 2025
aeb2f78
Rename test scripts
tustanivsky Nov 24, 2025
c6777cc
Clean up
tustanivsky Nov 24, 2025
d03ecbb
Rework SauceLabs API requests
tustanivsky Nov 24, 2025
30d3be2
Simplify logs retrieval
tustanivsky Nov 24, 2025
532fa78
Remove unnecessary quotes
tustanivsky Nov 24, 2025
3e09c20
Configure new SauceLabs credentials
tustanivsky Nov 25, 2025
1c27be6
Change test device
tustanivsky Nov 25, 2025
3d8a9c7
Change SauceLabs test job name title
tustanivsky Nov 25, 2025
7dbcb78
Fix android test results upload check
tustanivsky Nov 25, 2025
e4a7195
Make SauceLabs device name configurable
tustanivsky Nov 25, 2025
e43d13f
Fix sample app
tustanivsky Nov 25, 2025
c218ae8
Try using app-runner Android providers for running tests
tustanivsky Nov 25, 2025
25fe615
Fix param name
tustanivsky Nov 25, 2025
69f3e65
Pass device name to SauceLabs provider
tustanivsky Nov 25, 2025
0b5fa5f
Connect to SauceLabs device set in env var
tustanivsky Nov 26, 2025
3b20dc8
Bump app-runner
tustanivsky Nov 26, 2025
c22b2c6
Merge android test scripts
tustanivsky Nov 26, 2025
49d01ee
Remove platform-specific test scripts
tustanivsky Nov 26, 2025
e4d0fb4
Add SauceLabs session name override
tustanivsky Nov 26, 2025
95d2fe6
Bump app-runner
tustanivsky Nov 26, 2025
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
13 changes: 13 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -198,3 +198,16 @@ jobs:
uses: ./.github/workflows/integration-test-windows.yml
with:
unreal-version: ${{ matrix.unreal }}

integration-test-android:
needs: [test-android]
name: Android UE ${{ matrix.unreal }}
secrets: inherit
strategy:
fail-fast: false
max-parallel: 2
matrix:
unreal: ['5.4', '5.5', '5.6', '5.7']
uses: ./.github/workflows/integration-test-android.yml
with:
unreal-version: ${{ matrix.unreal }}
52 changes: 52 additions & 0 deletions .github/workflows/integration-test-android.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
on:
workflow_call:
inputs:
unreal-version:
required: true
type: string

jobs:
integration-test:
name: Integration Test
runs-on: ubuntu-latest

env:
SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
GITHUB_TOKEN: ${{ github.token }}
SAUCE_REGION: us-west-1
SAUCE_DEVICE_NAME: Samsung_Galaxy_S23_15_real_sjc1
SAUCE_SESSION_NAME: UE ${{ inputs.unreal-version }} Android Integration Test

steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Download sample build
uses: actions/download-artifact@v4
with:
name: UE ${{ inputs.unreal-version }} sample build (Android)
path: sample-build

- name: Run integration tests
id: run-integration-tests
shell: pwsh
working-directory: integration-test
env:
SENTRY_UNREAL_TEST_DSN: ${{ secrets.SENTRY_UNREAL_TEST_DSN }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_API_TOKEN }}
SENTRY_UNREAL_TEST_APP_PATH: ${{ github.workspace }}/sample-build/SentryPlayground-arm64.apk
run: |
cmake -B build -S .
$Container = New-PesterContainer -Path 'Integration.Android.Tests.ps1' -Data @{ Platform = 'SauceLabs' }
Invoke-Pester -Container $Container -CI

- name: Upload integration test output
if: ${{ always() && steps.run-integration-tests.outcome == 'failure' }}
uses: actions/upload-artifact@v4
with:
name: UE ${{ inputs.unreal-version }} integration test output (Android)
path: |
integration-test/output/
retention-days: 14
8 changes: 1 addition & 7 deletions .github/workflows/integration-test-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,15 @@ jobs:
chmod +x ${{ github.workspace }}/sample-build/SentryPlayground.sh
chmod +x ${{ github.workspace }}/sample-build/SentryPlayground/Plugins/sentry/Binaries/Linux/crashpad_handler

- name: Install Pester
shell: pwsh
run: |
Install-Module -Name Pester -Force -SkipPublisherCheck

- name: Run integration tests
id: run-integration-tests
shell: pwsh
working-directory: integration-test
env:
SENTRY_UNREAL_TEST_DSN: ${{ secrets.SENTRY_UNREAL_TEST_DSN }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_API_TOKEN }}
SENTRY_UNREAL_TEST_APP_PATH: ${{ github.workspace }}/sample-build/SentryPlayground.sh
run: |
cd integration-test
mkdir build
cmake -B build -S .
Invoke-Pester Integration.Tests.ps1 -CI

Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/integration-test-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,12 @@ jobs:

- name: Run integration tests
id: run-integration-tests
working-directory: integration-test
env:
SENTRY_UNREAL_TEST_DSN: ${{ secrets.SENTRY_UNREAL_TEST_DSN }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_API_TOKEN }}
SENTRY_UNREAL_TEST_APP_PATH: ${{ github.workspace }}/sample-build/SentryPlayground.exe
run: |
cd integration-test
mkdir build
cmake -B build -S .
Invoke-Pester Integration.Tests.ps1 -CI

Expand Down
2 changes: 1 addition & 1 deletion integration-test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ include(FetchContent)
FetchContent_Declare(
app-runner
GIT_REPOSITORY https://github.com/getsentry/app-runner.git
GIT_TAG 503795f0ef0f8340fcc0f0bc5fb5437df8cff9ef
GIT_TAG bd38cdc7be2e001e21394abe1406050f99f690ce
)

FetchContent_MakeAvailable(app-runner)
Expand Down
280 changes: 280 additions & 0 deletions integration-test/Integration.Android.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
# Integration tests for Sentry Unreal SDK on Android
# Supports both ADB (local devices/emulators) and SauceLabs (cloud devices)
#
# Usage:
# # Default (uses ADB)
# Invoke-Pester Integration.Android.Tests.ps1
#
# # Explicit platform selection with Pester containers
# $Container = New-PesterContainer -Path 'Integration.Android.Tests.ps1' -Data @{ Platform = 'Adb' }
# Invoke-Pester -Container $Container
#
# $Container = New-PesterContainer -Path 'Integration.Android.Tests.ps1' -Data @{ Platform = 'SauceLabs' }
# Invoke-Pester -Container $Container
#
# Requires:
# - Pre-built APK
# - Environment variables: SENTRY_UNREAL_TEST_DSN, SENTRY_AUTH_TOKEN, SENTRY_UNREAL_TEST_APP_PATH
#
# For ADB:
# - Android emulator or device connected via ADB
#
# For SauceLabs:
# - SAUCE_USERNAME, SAUCE_ACCESS_KEY, SAUCE_REGION, SAUCE_DEVICE_NAME

param(
[Parameter(Mandatory = $false)]
[ValidateSet('Adb', 'SauceLabs')]
[string]$Platform = 'Adb'
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

BeforeAll {
# Map friendly platform name to app-runner platform name
$appRunnerPlatform = if ($Platform -eq 'Adb') { 'AndroidAdb' } else { 'AndroidSauceLabs' }

Write-Host "Running Android tests with platform: $Platform (app-runner: $appRunnerPlatform)" -ForegroundColor Cyan

# Check if configuration file exists
$configFile = "$PSScriptRoot/TestConfig.local.ps1"
if (-not (Test-Path $configFile)) {
throw "Configuration file '$configFile' not found. Run 'cmake -B build -S .' first"
}

# Load configuration (provides $global:AppRunnerPath)
. $configFile

# Import app-runner modules (SentryApiClient, test utilities)
. "$global:AppRunnerPath/import-modules.ps1"

# Validate environment variables (test-specific only, not provider-specific)
$script:DSN = $env:SENTRY_UNREAL_TEST_DSN
$script:AuthToken = $env:SENTRY_AUTH_TOKEN
$script:ApkPath = $env:SENTRY_UNREAL_TEST_APP_PATH

if (-not $script:DSN) {
throw "Environment variable SENTRY_UNREAL_TEST_DSN must be set"
}

if (-not $script:AuthToken) {
throw "Environment variable SENTRY_AUTH_TOKEN must be set"
}

if (-not $script:ApkPath) {
throw "Environment variable SENTRY_UNREAL_TEST_APP_PATH must be set"
}

# Validate app path
if (-not (Test-Path $script:ApkPath)) {
throw "Application not found at: $script:ApkPath"
}

# Connect to Sentry API
Write-Host "Connecting to Sentry API..." -ForegroundColor Yellow
Connect-SentryApi -DSN $script:DSN -ApiToken $script:AuthToken

# Create output directory
$script:OutputDir = "$PSScriptRoot/output"
if (-not (Test-Path $script:OutputDir)) {
New-Item -ItemType Directory -Path $script:OutputDir | Out-Null
}

$script:PackageName = "io.sentry.unreal.sample"
$script:ActivityName = "$script:PackageName/com.epicgames.unreal.GameActivity"

# Connect to Android device (provider validates its own env vars)
Write-Host "Connecting to Android via $Platform..." -ForegroundColor Yellow
Connect-Device -Platform $appRunnerPlatform

# Install APK
Write-Host "Installing APK via $Platform..." -ForegroundColor Yellow
Install-DeviceApp -Path $script:ApkPath

# ==========================================
# RUN 1: Crash test - creates minidump
# ==========================================
# The crash is captured but NOT uploaded yet (Android behavior).
# TODO: Re-enable once Android SDK tag persistence is fixed (`test.crash_id` tag set before crash is not synced to the captured crash on Android)

# Write-Host "Running crash-capture test (will crash)..." -ForegroundColor Yellow
# $cmdlineCrashArgs = "-e cmdline -crash-capture"
# $global:AndroidCrashResult = Invoke-DeviceApp -ExecutablePath $script:ActivityName -Arguments $cmdlineCrashArgs

# Write-Host "Crash test exit code: $($global:AndroidCrashResult.ExitCode)" -ForegroundColor Cyan

# ==========================================
# RUN 2: Message test - uploads crash from Run 1 + captures message
# ==========================================
# Currently we need to run again so that Sentry sends the crash event captured during the previous app session.

Write-Host "Running message-capture test on $Platform..." -ForegroundColor Yellow
$cmdlineMessageArgs = "-e cmdline -message-capture"
$global:AndroidMessageResult = Invoke-DeviceApp -ExecutablePath $script:ActivityName -Arguments $cmdlineMessageArgs

Write-Host "Message test exit code: $($global:AndroidMessageResult.ExitCode)" -ForegroundColor Cyan
}

Describe "Sentry Unreal Android Integration Tests ($Platform)" {

# ==========================================
# NOTE: Crash Capture Tests are DISABLED due to tag sync issue
# Uncomment when Android SDK tag persistence is fixed
# ==========================================
# Context "Crash Capture Tests" {
# BeforeAll {
# # Crash event is sent during the MESSAGE run (Run 2)
# # But the crash_id comes from the CRASH run (Run 1)
# $CrashResult = $global:AndroidCrashResult
# $CrashEvent = $null
#
# # Parse crash event ID from crash run output
# $eventIds = Get-EventIds -AppOutput $CrashResult.Output -ExpectedCount 1
#
# if ($eventIds -and $eventIds.Count -gt 0) {
# Write-Host "Crash ID captured: $($eventIds[0])" -ForegroundColor Cyan
# $crashId = $eventIds[0]
#
# # Fetch crash event using the tag (event was sent during message run)
# try {
# $CrashEvent = Get-SentryTestEvent -TagName 'test.crash_id' -TagValue "$crashId"
# Write-Host "Crash event fetched from Sentry successfully" -ForegroundColor Green
# } catch {
# Write-Host "Failed to fetch crash event from Sentry: $_" -ForegroundColor Red
# }
# } else {
# Write-Host "Warning: No crash event ID found in output" -ForegroundColor Yellow
# }
# }
#
# It "Should output event ID before crash" {
# $eventIds = Get-EventIds -AppOutput $CrashResult.Output -ExpectedCount 1
# $eventIds | Should -Not -BeNullOrEmpty
# $eventIds.Count | Should -Be 1
# }
#
# It "Should capture crash event in Sentry (uploaded during next run)" {
# $CrashEvent | Should -Not -BeNullOrEmpty
# }
#
# It "Should have correct event type and platform" {
# $CrashEvent.type | Should -Be 'error'
# $CrashEvent.platform | Should -Be 'native'
# }
#
# It "Should have exception information" {
# $CrashEvent.exception | Should -Not -BeNullOrEmpty
# $CrashEvent.exception.values | Should -Not -BeNullOrEmpty
# }
#
# It "Should have stack trace" {
# $exception = $CrashEvent.exception.values[0]
# $exception.stacktrace | Should -Not -BeNullOrEmpty
# $exception.stacktrace.frames | Should -Not -BeNullOrEmpty
# }
#
# It "Should have user context" {
# $CrashEvent.user | Should -Not -BeNullOrEmpty
# $CrashEvent.user.username | Should -Be 'TestUser'
# $CrashEvent.user.email | Should -Be '[email protected]'
# $CrashEvent.user.id | Should -Be '12345'
# }
#
# It "Should have test.crash_id tag for correlation" {
# $tags = $CrashEvent.tags
# $crashIdTag = $tags | Where-Object { $_.key -eq 'test.crash_id' }
# $crashIdTag | Should -Not -BeNullOrEmpty
# $crashIdTag.value | Should -Not -BeNullOrEmpty
# }
#
# It "Should have integration test tag" {
# $tags = $CrashEvent.tags
# ($tags | Where-Object { $_.key -eq 'test.suite' }).value | Should -Be 'integration'
# }
#
# It "Should have breadcrumbs from before crash" {
# $CrashEvent.breadcrumbs | Should -Not -BeNullOrEmpty
# $CrashEvent.breadcrumbs.values | Should -Not -BeNullOrEmpty
# }
# }

Context "Message Capture Tests" {
BeforeAll {
$MessageResult = $global:AndroidMessageResult
$MessageEvent = $null

# Parse event ID from output
$eventIds = Get-EventIds -AppOutput $MessageResult.Output -ExpectedCount 1

if ($eventIds -and $eventIds.Count -gt 0) {
Write-Host "Message event ID captured: $($eventIds[0])" -ForegroundColor Cyan

# Fetch event from Sentry (with polling)
try {
$MessageEvent = Get-SentryTestEvent -EventId $eventIds[0]
Write-Host "Message event fetched from Sentry successfully" -ForegroundColor Green
}
catch {
Write-Host "Failed to fetch message event from Sentry: $_" -ForegroundColor Red
}
}
else {
Write-Host "Warning: No message event ID found in output" -ForegroundColor Yellow
}
}

It "Should output event ID" {
$eventIds = Get-EventIds -AppOutput $MessageResult.Output -ExpectedCount 1
$eventIds | Should -Not -BeNullOrEmpty
$eventIds.Count | Should -Be 1
}

It "Should output TEST_RESULT with success" {
$testResultLine = $MessageResult.Output | Where-Object { $_ -match 'TEST_RESULT:' }
$testResultLine | Should -Not -BeNullOrEmpty
$testResultLine | Should -Match '"success"\s*:\s*true'
}

It "Should capture message event in Sentry" {
$MessageEvent | Should -Not -BeNullOrEmpty
}

It "Should have correct platform" {
# Android events are captured from Java layer, so platform is 'java' not 'native'
$MessageEvent.platform | Should -Be 'java'
}

It "Should have message content" {
$MessageEvent.message | Should -Not -BeNullOrEmpty
$MessageEvent.message.formatted | Should -Match 'Integration test message'
}

It "Should have user context" {
$MessageEvent.user | Should -Not -BeNullOrEmpty
$MessageEvent.user.username | Should -Be 'TestUser'
}

It "Should have integration test tag" {
$tags = $MessageEvent.tags
($tags | Where-Object { $_.key -eq 'test.suite' }).value | Should -Be 'integration'
}

It "Should have breadcrumbs" {
$MessageEvent.breadcrumbs | Should -Not -BeNullOrEmpty
$MessageEvent.breadcrumbs.values | Should -Not -BeNullOrEmpty
}
}
}

AfterAll {
# Disconnect from Android device
Write-Host "Disconnecting from $Platform..." -ForegroundColor Yellow
Disconnect-Device

# Disconnect from Sentry API
Write-Host "Disconnecting from Sentry API..." -ForegroundColor Yellow
Disconnect-SentryApi

Write-Host "Integration tests complete" -ForegroundColor Green
}
Loading