Skip to content

Commit 2f5268c

Browse files
authored
Merge pull request #30 from getsentry/feat/android-providers
feat: add Android support (adb, SauceLabs)
2 parents f51f852 + 1ec6480 commit 2f5268c

29 files changed

+2663
-20
lines changed

.github/workflows/app-runner.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
name: AppRunner
2+
permissions: read-all
23

34
on:
45
push:
@@ -13,8 +14,15 @@ on:
1314
jobs:
1415
test:
1516
uses: ./.github/workflows/test-powershell-module.yml
17+
secrets: inherit
1618
with:
1719
module-name: SentryAppRunner
1820
module-path: app-runner
1921
test-path: Tests
22+
exclude-path: Adb.Tests.ps1
2023
settings-path: PSScriptAnalyzerSettings.psd1
24+
25+
test-adb:
26+
uses: ./.github/workflows/test-adb-provider.yml
27+
with:
28+
module-path: app-runner
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
name: Test ADB Provider
2+
permissions:
3+
contents: read
4+
5+
on:
6+
workflow_call:
7+
inputs:
8+
module-path:
9+
description: 'Path to the module directory'
10+
required: true
11+
type: string
12+
13+
jobs:
14+
test:
15+
runs-on: ubuntu-latest
16+
defaults:
17+
run:
18+
working-directory: ${{ inputs.module-path }}
19+
shell: pwsh
20+
21+
steps:
22+
- name: Checkout repository
23+
uses: actions/checkout@v4
24+
25+
- name: Enable KVM group permissions
26+
run: |
27+
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
28+
sudo udevadm control --reload-rules
29+
sudo udevadm trigger --name-match=kvm
30+
31+
- name: Setup Android directories
32+
run: |
33+
mkdir -p $HOME/.android/avd
34+
touch $HOME/.android/repositories.cfg
35+
36+
- name: Install Android SDK Build Tools
37+
shell: bash
38+
run: |
39+
${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager --install "build-tools;35.0.0"
40+
echo "${ANDROID_HOME}/build-tools/35.0.0" >> $GITHUB_PATH
41+
42+
- name: Run ADB Provider Integration Tests
43+
uses: reactivecircus/android-emulator-runner@d94c3fbe4fe6a29e4a5ba47c12fb47677c73656b
44+
timeout-minutes: 45
45+
with:
46+
api-level: 35
47+
target: 'google_apis'
48+
arch: x86_64
49+
force-avd-creation: true
50+
disable-animations: true
51+
disable-spellchecker: true
52+
emulator-options: >
53+
-no-window
54+
-no-snapshot-save
55+
-gpu swiftshader_indirect
56+
-noaudio
57+
-no-boot-anim
58+
-camera-back none
59+
-camera-front none
60+
script: |
61+
adb wait-for-device
62+
echo "Android emulator is ready"
63+
adb devices
64+
cd ${{ inputs.module-path }} && pwsh -Command "Invoke-Pester Tests/Adb.Tests.ps1 -CI"

.github/workflows/test-powershell-module.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ on:
2323
required: false
2424
type: string
2525
default: ''
26+
exclude-path:
27+
description: 'Comma-separated list of test file paths to exclude (relative to test-path)'
28+
required: false
29+
type: string
30+
default: ''
2631
settings-path:
2732
description: 'Path to PSScriptAnalyzer settings file (relative to repo root)'
2833
required: false
@@ -74,6 +79,10 @@ jobs:
7479
run:
7580
working-directory: ${{ inputs.module-path }}
7681
shell: pwsh
82+
env:
83+
SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}
84+
SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
85+
SAUCE_REGION: us-west-1
7786

7887
steps:
7988
- name: Checkout repository
@@ -94,6 +103,16 @@ jobs:
94103
$config.Filter.ExcludeTag = $excludeTags.Split(',').Trim()
95104
}
96105
106+
$excludePath = "${{ inputs.exclude-path }}"
107+
if ($excludePath) {
108+
$testPath = "${{ inputs.test-path }}"
109+
$config.Run.ExcludePath = $excludePath.Split(',').Trim() | ForEach-Object {
110+
$relativePath = Join-Path $testPath $_
111+
# ExcludePath requires absolute paths
112+
Join-Path (Get-Location) $relativePath
113+
}
114+
}
115+
97116
$testResults = Invoke-Pester -Configuration $config
98117
99118
Write-Host "Test Summary:" -ForegroundColor Cyan

README.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,9 @@ Currently supported:
2121
- Windows
2222
- macOS
2323
- Linux
24-
25-
Future support planned:
26-
27-
- Mobile platforms (iOS, Android)
24+
- **Mobile Platforms:**
25+
- Android (via ADB or SauceLabs Real Device Cloud)
26+
- iOS (via SauceLabs - coming soon)
2827

2928
## Requirements
3029

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# Android Helper Functions
2+
# Shared utilities for Android device providers (ADB and SauceLabs)
3+
4+
<#
5+
.SYNOPSIS
6+
Converts an Android activity path into package name and activity name components.
7+
8+
.DESCRIPTION
9+
Converts the ExecutablePath format used by Android apps: "package.name/activity.name"
10+
Returns a hashtable with PackageName and ActivityName properties.
11+
12+
.PARAMETER ExecutablePath
13+
The full activity path in format "package.name/activity.name"
14+
15+
.EXAMPLE
16+
ConvertFrom-AndroidActivityPath "io.sentry.unreal.sample/com.epicgames.unreal.GameActivity"
17+
Returns: @{ PackageName = "io.sentry.unreal.sample"; ActivityName = "com.epicgames.unreal.GameActivity" }
18+
19+
.EXAMPLE
20+
ConvertFrom-AndroidActivityPath "com.example.app/.MainActivity"
21+
Returns: @{ PackageName = "com.example.app"; ActivityName = ".MainActivity" }
22+
#>
23+
function ConvertFrom-AndroidActivityPath {
24+
[CmdletBinding()]
25+
param(
26+
[Parameter(Mandatory = $true)]
27+
[string]$ExecutablePath
28+
)
29+
30+
if ($ExecutablePath -notmatch '^([^/]+)/(.+)$') {
31+
throw "ExecutablePath must be in format 'package.name/activity.name'. Got: $ExecutablePath"
32+
}
33+
34+
return @{
35+
PackageName = $matches[1]
36+
ActivityName = $matches[2]
37+
}
38+
}
39+
40+
<#
41+
.SYNOPSIS
42+
Validates that Android Intent extras are in the correct format.
43+
44+
.DESCRIPTION
45+
Android Intent extras should be passed in the format understood by `am start`.
46+
This function validates and optionally formats the arguments string.
47+
48+
Common Intent extra formats:
49+
-e key value String extra
50+
-es key value String extra (explicit)
51+
-ez key true|false Boolean extra
52+
-ei key value Integer extra
53+
-el key value Long extra
54+
55+
.PARAMETER Arguments
56+
The arguments string to validate/format
57+
58+
.EXAMPLE
59+
Test-IntentExtrasFormat "-e cmdline -crash-capture"
60+
Returns: $true
61+
62+
.EXAMPLE
63+
Test-IntentExtrasFormat "-e test true -ez debug false"
64+
Returns: $true
65+
#>
66+
function Test-IntentExtrasFormat {
67+
[CmdletBinding()]
68+
param(
69+
[Parameter(Mandatory = $false)]
70+
[string]$Arguments
71+
)
72+
73+
if ([string]::IsNullOrWhiteSpace($Arguments)) {
74+
return $true
75+
}
76+
77+
# Intent extras must start with flags: -e, -es, -ez, -ei, -el, -ef, -eu, etc.
78+
# Followed by at least one whitespace and additional content
79+
if ($Arguments -notmatch '^--?[a-z]{1,2}\s+') {
80+
throw "Invalid Intent extras format: '$Arguments'. Must start with flags like -e, -es, -ez, -ei, -el, etc. followed by key-value pairs."
81+
}
82+
83+
return $true
84+
}
85+
86+
<#
87+
.SYNOPSIS
88+
Extracts the package name from an APK file using aapt.
89+
90+
.DESCRIPTION
91+
Extracts the real package name from an APK file using aapt (Android Asset Packaging Tool).
92+
Requires aapt or aapt2 to be available in PATH.
93+
94+
.PARAMETER ApkPath
95+
Path to the APK file
96+
97+
.EXAMPLE
98+
Get-ApkPackageName "MyApp.apk"
99+
Returns: "com.example.myapp" (actual package name from AndroidManifest.xml)
100+
101+
.EXAMPLE
102+
Get-ApkPackageName "SentryPlayground.apk"
103+
Returns: "io.sentry.sample"
104+
105+
.NOTES
106+
Requires aapt or aapt2 to be in PATH or Android SDK to be installed.
107+
Throws an error if aapt is not available or if the package name cannot be extracted.
108+
#>
109+
function Get-ApkPackageName {
110+
[CmdletBinding()]
111+
param(
112+
[Parameter(Mandatory = $true)]
113+
[string]$ApkPath
114+
)
115+
116+
if (-not (Test-Path $ApkPath)) {
117+
throw "APK file not found: $ApkPath"
118+
}
119+
120+
if ($ApkPath -notlike '*.apk') {
121+
throw "File must be an .apk file. Got: $ApkPath"
122+
}
123+
124+
# Find aapt or aapt2
125+
$aaptCmd = Get-Command aapt -ErrorAction SilentlyContinue
126+
if (-not $aaptCmd) {
127+
$aaptCmd = Get-Command aapt2 -ErrorAction SilentlyContinue
128+
}
129+
130+
if (-not $aaptCmd) {
131+
throw "aapt or aapt2 not found in PATH. Please install Android SDK Build Tools and ensure aapt is available in PATH."
132+
}
133+
134+
Write-Debug "Using $($aaptCmd.Name) to extract package name from APK"
135+
136+
try {
137+
$PSNativeCommandUseErrorActionPreference = $false
138+
$output = & $aaptCmd.Name dump badging $ApkPath 2>&1
139+
140+
# Parse output for package name: package: name='com.example.app'
141+
foreach ($line in $output) {
142+
if ($line -match "package:\s+name='([^']+)'") {
143+
$packageName = $matches[1]
144+
Write-Debug "Extracted package name: $packageName"
145+
return $packageName
146+
}
147+
}
148+
149+
throw "Failed to extract package name from APK using aapt. APK may be corrupted or invalid."
150+
}
151+
finally {
152+
$PSNativeCommandUseErrorActionPreference = $true
153+
}
154+
}
155+
156+
<#
157+
.SYNOPSIS
158+
Parses logcat output into structured format.
159+
160+
.DESCRIPTION
161+
Converts raw logcat output (array of strings) into a consistent format
162+
that can be used by test utilities like Get-EventIds.
163+
164+
.PARAMETER LogcatOutput
165+
Array of logcat log lines (raw output from adb or SauceLabs)
166+
167+
.EXAMPLE
168+
$logs = adb -s emulator-5554 logcat -d
169+
Format-LogcatOutput $logs
170+
#>
171+
function Format-LogcatOutput {
172+
[CmdletBinding()]
173+
param(
174+
[Parameter(Mandatory = $false)]
175+
[object[]]$LogcatOutput
176+
)
177+
178+
if ($null -eq $LogcatOutput -or $LogcatOutput.Count -eq 0) {
179+
return @()
180+
}
181+
182+
# Ensure output is an array of strings
183+
return @($LogcatOutput | ForEach-Object {
184+
if ($null -ne $_) {
185+
$_.ToString()
186+
}
187+
} | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
188+
}

0 commit comments

Comments
 (0)