android-ci-tests
Setup GitHub Actions workflow for running Android tests in CI
When & Why to Use This Skill
This Claude skill automates the creation of a robust GitHub Actions workflow specifically tailored for Android development. It streamlines the Continuous Integration process by configuring unit tests, lint checks, and complex instrumented tests, incorporating industry best practices like Gradle/AVD caching and KVM acceleration to ensure fast, reliable, and scalable build pipelines.
Use Cases
- Automated Pull Request Validation: Ensure code quality by automatically running unit tests and linting checks whenever a developer submits a pull request to the main or develop branches.
- Accelerated Build Pipelines: Reduce CI wait times significantly by implementing advanced caching strategies for Gradle dependencies and Android Virtual Device (AVD) snapshots.
- Headless UI Testing: Execute instrumented tests in a virtualized environment with KVM acceleration, providing rapid feedback on UI stability without requiring manual emulator setup.
- Standardized DevOps Workflows: Quickly bootstrap a production-ready CI/CD configuration for new Android projects, ensuring consistent testing standards across the entire engineering team.
| name | android-ci-tests |
|---|---|
| description | Setup GitHub Actions workflow for running Android tests in CI |
| category | android |
| version | 1.0.0 |
| - project_path | Path to Android project |
| verify | "yamllint .github/workflows/test.yml" |
Android CI Tests Setup
Sets up GitHub Actions workflow for running Android unit and instrumented tests in CI.
Prerequisites
- Android project with test dependencies
- GitHub repository
Inputs
| Input | Required | Default | Description |
|---|---|---|---|
| project_path | Yes | . | Android project root |
Process
Step 1: Create CI Test Workflow
Create .github/workflows/test.yml:
name: Android CI Tests
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up JDK 17
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.0
with:
java-version: '17'
distribution: 'temurin'
- name: Setup Gradle cache
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
.gradle/configuration-cache
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
gradle-${{ runner.os }}-
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Run unit tests
run: ./gradlew test --stacktrace
- name: Upload test results
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.0
with:
name: unit-test-results
path: |
app/build/test-results/
app/build/reports/tests/
retention-days: 7
- name: Upload coverage reports (if JaCoCo configured)
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.0
with:
name: coverage-reports
path: app/build/reports/jacoco/
retention-days: 7
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up JDK 17
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.0
with:
java-version: '17'
distribution: 'temurin'
- name: Setup Gradle cache
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
.gradle/configuration-cache
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
gradle-${{ runner.os }}-
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Run lint
run: ./gradlew lintDebug --stacktrace
- name: Upload lint results
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.0
with:
name: lint-results
path: app/build/reports/lint-results-debug.html
retention-days: 7
instrumented-tests:
runs-on: ubuntu-latest
# Optional: Only run instrumented tests if unit tests pass
needs: unit-tests
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Set up JDK 17
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.0
with:
java-version: '17'
distribution: 'temurin'
- name: Setup Gradle cache
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
.gradle/configuration-cache
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
gradle-${{ runner.os }}-
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Enable KVM group permissions
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: AVD cache
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
key: avd-${{ runner.os }}-api-30
- name: Create AVD and generate snapshot for caching
if: steps.avd-cache.outputs.cache-hit != 'true'
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 30
target: google_apis
arch: x86_64
profile: pixel_6
force-avd-creation: false
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
script: echo "Generated AVD snapshot for caching."
- name: Run instrumented tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 30
target: google_apis
arch: x86_64
profile: pixel_6
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
script: ./gradlew connectedDebugAndroidTest --stacktrace
- name: Upload instrumented test results
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.0
with:
name: instrumented-test-results
path: |
app/build/reports/androidTests/
app/build/outputs/androidTest-results/
retention-days: 7
Features:
- ✅ Runs on push and pull requests
- ✅ Separate jobs for unit tests, lint, and instrumented tests
- ✅ Gradle caching for faster builds
- ✅ AVD caching for faster emulator startup
- ✅ KVM acceleration for faster emulator performance
- ✅ All actions pinned to commit SHAs
- ✅ Test results uploaded as artifacts
- ✅ Instrumented tests only run if unit tests pass
Verification
MANDATORY: Verify workflow is valid:
# Validate YAML syntax
yamllint .github/workflows/test.yml
# Verify workflow exists
test -f .github/workflows/test.yml && echo "✓ Test workflow created"
# Push to trigger workflow
git add .github/workflows/test.yml
git commit -m "Add CI test workflow"
git push
Monitor: Go to repository → Actions tab to see tests running
Outputs
| Output | Location | Description |
|---|---|---|
| Test workflow | .github/workflows/test.yml | CI test automation |
Optional Enhancements
Add Code Coverage
If using JaCoCo, add to app/build.gradle.kts:
android {
buildTypes {
debug {
enableAndroidTestCoverage = true
enableUnitTestCoverage = true
}
}
}
Add Test Report Comments
Add a step to comment test results on PRs:
- name: Comment test results on PR
if: github.event_name == 'pull_request'
uses: EnricoMi/publish-unit-test-result-action@v2
with:
files: app/build/test-results/**/*.xml
Troubleshooting
"Emulator failed to start"
Cause: KVM not available or permissions issue Fix: GitHub-hosted runners should have KVM enabled. Check runner logs.
"Out of memory during tests"
Cause: Gradle daemon using too much memory
Fix: Add to gradle.properties: org.gradle.jvmargs=-Xmx2048m
"Tests timeout"
Cause: Instrumented tests taking too long Fix: Increase timeout or split tests across multiple jobs
Completion Criteria
-
.github/workflows/test.ymlexists and is valid - Workflow runs on push and pull requests
- Unit tests execute successfully
- Lint checks execute successfully
- Instrumented tests execute successfully (if configured)
- Test results uploaded as artifacts