Skip to content
Open
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
815c739
Creating base e2e test for Real Device validations
ifernandezdiaz Jun 9, 2025
ed5b752
Creating base e2e test for Real Device validations
ifernandezdiaz Jun 9, 2025
5b012e1
Implementing page object pattern
ifernandezdiaz Jun 10, 2025
a6ef2ea
Using Compose Testing + Espresso
ifernandezdiaz Jun 26, 2025
047b762
Merge branch 'main' into QA-1126b/adding-native-sanity-test
ifernandezdiaz Jun 26, 2025
2af019e
Updating code to read credentials from an external json file
ifernandezdiaz Jun 26, 2025
6181378
Adding missing steps to test-device workflow
ifernandezdiaz Jun 27, 2025
772245c
Merge branch 'main' into QA-1126b/adding-native-sanity-test
ifernandezdiaz Jun 27, 2025
a2ed706
Adding missing steps to retrieve secrets from Azure
ifernandezdiaz Jun 27, 2025
2856545
Enabling release test build type
ifernandezdiaz Jun 30, 2025
58c98fa
Merge branch 'main' into QA-1126b/adding-native-sanity-test
ifernandezdiaz Jun 30, 2025
a44a8dc
Pulling SM creds from Azure
ifernandezdiaz Jul 1, 2025
b0fb071
Updating Azure creds to pull SM secrets
ifernandezdiaz Jul 1, 2025
26cb4ce
Merge branch 'main' into QA-1126b/adding-native-sanity-test
ifernandezdiaz Jul 10, 2025
a371efb
Adding testData file
ifernandezdiaz Jul 10, 2025
b8172f9
Installing saucectl via npm
ifernandezdiaz Jul 10, 2025
4b2f0da
Pulling SauceLabs creds from GH secrets
ifernandezdiaz Jul 11, 2025
6b2ac29
Revert "Pulling SauceLabs creds from GH secrets"
ifernandezdiaz Jul 11, 2025
22644ab
Fixing SauceLabs creds retrieval
ifernandezdiaz Jul 11, 2025
5fc3c02
Fixing testApp filename
ifernandezdiaz Jul 11, 2025
e9afd92
Signing testApp
ifernandezdiaz Jul 11, 2025
a6bd9b6
Signing testApp
ifernandezdiaz Jul 11, 2025
c6f8fe2
Signing testApp
ifernandezdiaz Jul 11, 2025
9a724d8
Fixing importing issues during test execution
ifernandezdiaz Jul 11, 2025
a5721f3
Updating the way we read json data
ifernandezdiaz Jul 11, 2025
e20c814
Merge branch 'main' into QA-1126b/adding-native-sanity-test
ifernandezdiaz Jul 14, 2025
7bedb1f
Adding steps to enable screen recording
ifernandezdiaz Jul 14, 2025
85f0bd9
Removing steps to test faster on Device Farm
ifernandezdiaz Jul 14, 2025
d3d02f4
Increasing timeout
ifernandezdiaz Jul 14, 2025
cab4461
Decreasing timeout
ifernandezdiaz Jul 14, 2025
9474133
Restoring build steps
ifernandezdiaz Jul 14, 2025
47a9117
Merge branch 'main' into QA-1126b/adding-native-sanity-test
ifernandezdiaz Jul 14, 2025
7dc5b99
Fix formatting
ifernandezdiaz Jul 14, 2025
909b0d0
Integrating e2e test run unto build workflow
ifernandezdiaz Jul 14, 2025
bbd7e7c
Adding suggestions
ifernandezdiaz Jul 15, 2025
6be753b
Updating branch
ifernandezdiaz Jul 15, 2025
62b9088
Fixing lint issues
ifernandezdiaz Jul 15, 2025
7a98555
fixing static analisys issues
ifernandezdiaz Jul 18, 2025
c4ebde7
Merge branch 'main' into QA-1126b/adding-native-sanity-test
ifernandezdiaz Jul 18, 2025
be325eb
Merge branch 'main' into QA-1126b/adding-native-sanity-test
ifernandezdiaz Jul 21, 2025
b6e90e4
Merge branch 'main' into QA-1126b/adding-native-sanity-test
ifernandezdiaz Jul 22, 2025
b1a74c6
Adding suggestions
ifernandezdiaz Jul 22, 2025
a6efb31
Adding package structure to androidTest folders
ifernandezdiaz Jul 23, 2025
b74d3b1
Merge branch 'main' into QA-1126b/adding-native-sanity-test
ifernandezdiaz Jul 23, 2025
ab481d2
Merge branch 'main' into QA-1126b/adding-native-sanity-test
ifernandezdiaz Jul 28, 2025
f803752
Refactor - move test apk assembly to build job
vvolkgang Jul 31, 2025
bb8dda4
Adding missing permissions to test-device workflow call
ifernandezdiaz Aug 4, 2025
12edccc
Moving test-app creation to fastlane
ifernandezdiaz Aug 8, 2025
15b5b86
Updating branch
ifernandezdiaz Aug 8, 2025
da9b60f
Fixing build issues
ifernandezdiaz Aug 8, 2025
9bf3d1e
Merge branch 'main' into QA-1126b/adding-native-sanity-test
ifernandezdiaz Aug 11, 2025
0aafc52
Updating branch
ifernandezdiaz Sep 30, 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
150 changes: 148 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ jobs:
strategy:
fail-fast: false
matrix:
variant: ["prod", "dev"]
artifact: ["apk", "aab"]
variant: [ "prod", "dev" ]
artifact: [ "apk", "aab" ]
steps:
- name: Check out repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
Expand Down Expand Up @@ -602,3 +602,149 @@ jobs:
bundle exec fastlane distributeReleaseFDroidToFirebase \
actionUrl:${{ env.GITHUB_ACTION_RUN_URL }} \
service_credentials_file:${{ env.APP_FDROID_FIREBASE_CREDS_PATH }}

test_on_device:
name: Running sanity check on a real device
needs: publish_playstore
runs-on: ubuntu-24.04
if: github.ref == 'refs/heads/main'
env:
_JAVA_VERSION: 17
_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

steps:
- name: Check out repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1

- name: Cache Gradle files
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-v2-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', '**/libs.versions.toml') }}
restore-keys: |
${{ runner.os }}-gradle-v2-

- name: Cache build output
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: ${{ github.workspace }}/build-cache
key: ${{ runner.os }}-build-cache-${{ github.sha }}
restore-keys: |
${{ runner.os }}-build-

- name: Configure JDK
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with:
distribution: "temurin"
java-version: ${{ env._JAVA_VERSION }}

- name: Configure Ruby
uses: ruby/setup-ruby@ca041f971d66735f3e5ff1e21cc13e2d51e7e535 # v1.233.0
with:
bundler-cache: true

- name: Install Fastlane
run: |
gem install bundler:2.2.27
bundle config path vendor/bundle
bundle install --jobs 4 --retry 3

- name: Install saucectl
run: |
npm i -g saucectl

- name: Log in to Azure
uses: Azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}

- name: Retrieve secrets
env:
ACCOUNT_NAME: bitwardenci
CONTAINER_NAME: mobile
run: |
mkdir -p ${{ github.workspace }}/secrets
mkdir -p ${{ github.workspace }}/app/src/standardRelease

az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
--name app_play-keystore.jks --file ${{ github.workspace }}/keystores/app_play-keystore.jks --output none
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
--name google-services.json --file ${{ github.workspace }}/app/src/standardRelease/google-services.json --output none

- name: Log in to Azure (Bitwarden)
uses: bitwarden/gh-actions/azure-login@main
with:
subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
tenant_id: ${{ secrets.AZURE_TENANT_ID }}
client_id: ${{ secrets.AZURE_CLIENT_ID }}

- name: Get E2E test secrets from Azure
uses: bitwarden/gh-actions/get-keyvault-secrets@main
with:
keyvault: gh-android
secrets: "BWS-ACCESS-TOKEN, SAUCE-LABS-USERNAME, SAUCE-LABS-ACCESS-KEY"
id: get-e2e-secrets

- name: Log out from Azure
uses: bitwarden/gh-actions/azure-logout@main

- name: Retrieve test data
uses: bitwarden/sm-action@14f92f1d294ae3c2b6a3845d389cd2c318b0dfd8 # v2.2.0
with:
access_token: ${{ steps.get-e2e-secrets.outputs.BWS-ACCESS-TOKEN }}
secrets: |
63e93f73-5118-4a62-9db8-b3160176aa8a > TEST_ACCOUNT_CREDS

- name: Configure .json test data file
run: printf %s '${{ env.TEST_ACCOUNT_CREDS }}' > app/src/androidTest/assets/TestData.json

- name: Build test APK (espresso)
run: |
./gradlew :app:assembleStandardReleaseAndroidTest

- name: Download release APK artifact
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: com.x8bit.bitwarden.apk
path: app/build/outputs/apk/standard/release/

- name: Signing test APK
run: |
$ANDROID_SDK_ROOT/build-tools/34.0.0/apksigner sign \
--ks keystores/app_play-keystore.jks \
--ks-key-alias bitwarden \
--ks-pass pass:${{ secrets.PLAY_KEYSTORE_PASSWORD }} \
--key-pass pass:${{ secrets.PLAY_KEYSTORE_PASSWORD }} \
app/build/outputs/apk/androidTest/standard/release/com.x8bit.bitwarden-standard-release-androidTest.apk

- name: Upload APK to SauceLabs storage
run: |
saucectl storage upload app/build/outputs/apk/standard/release/com.x8bit.bitwarden.apk
env:
SAUCE_USERNAME: ${{ steps.get-e2e-secrets.outputs.SAUCE-LABS-USERNAME }}
SAUCE_ACCESS_KEY: ${{ steps.get-e2e-secrets.outputs.SAUCE-LABS-ACCESS-KEY }}

- name: Upload test APK to SauceLabs storage
run: |
saucectl storage upload app/build/outputs/apk/androidTest/standard/release/com.x8bit.bitwarden-standard-release-androidTest.apk
env:
SAUCE_USERNAME: ${{ steps.get-e2e-secrets.outputs.SAUCE-LABS-USERNAME }}
SAUCE_ACCESS_KEY: ${{ steps.get-e2e-secrets.outputs.SAUCE-LABS-ACCESS-KEY }}

- name: Run tests on SauceLabs
run: saucectl run --config .sauce/config.yml
env:
SAUCE_USERNAME: ${{ steps.get-e2e-secrets.outputs.SAUCE-LABS-USERNAME }}
SAUCE_ACCESS_KEY: ${{ steps.get-e2e-secrets.outputs.SAUCE-LABS-ACCESS-KEY }}

- name: Upload SauceLabs test report
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: saucectl-report
path: saucectl-report.xml
151 changes: 147 additions & 4 deletions .github/workflows/test-device.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,155 @@ on:

permissions:
contents: read
actions: read
checks: write
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checks: write is only needed to create or update status checks. Neither are happening directly in the job, that I can see. I don't think this permission is needed.

id-token: write

jobs:
test:
name: Test Device
test-device:
name: Check main build against real devices
runs-on: ubuntu-24.04
env:
_JAVA_VERSION: 17
_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

steps:
- name: Placeholder step
run: echo "Placeholder workflow step"
- name: Check out repo
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1

- name: Cache Gradle files
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-v2-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', '**/libs.versions.toml') }}
restore-keys: |
${{ runner.os }}-gradle-v2-

- name: Cache build output
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: ${{ github.workspace }}/build-cache
key: ${{ runner.os }}-build-cache-${{ github.sha }}
restore-keys: |
${{ runner.os }}-build-

- name: Configure JDK
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with:
distribution: "temurin"
java-version: ${{ env.JAVA_VERSION }}

- name: Configure Ruby
uses: ruby/setup-ruby@ca041f971d66735f3e5ff1e21cc13e2d51e7e535 # v1.233.0
with:
bundler-cache: true

- name: Install Fastlane
run: |
gem install bundler:2.2.27
bundle config path vendor/bundle
bundle install --jobs 4 --retry 3

- name: Install saucectl
run: |
npm i -g saucectl

- name: Log in to Azure
uses: Azure/login@e15b166166a8746d1a47596803bd8c1b595455cf # v1.6.0
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}

- name: Retrieve secrets
env:
ACCOUNT_NAME: bitwardenci
CONTAINER_NAME: mobile
run: |
mkdir -p ${{ github.workspace }}/secrets
mkdir -p ${{ github.workspace }}/app/src/standardRelease

az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
--name app_play-keystore.jks --file ${{ github.workspace }}/keystores/app_play-keystore.jks --output none
az storage blob download --account-name $ACCOUNT_NAME --container-name $CONTAINER_NAME \
--name google-services.json --file ${{ github.workspace }}/app/src/standardRelease/google-services.json --output none

- name: Log in to Azure
uses: bitwarden/gh-actions/azure-login@main
with:
subscription_id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
tenant_id: ${{ secrets.AZURE_TENANT_ID }}
client_id: ${{ secrets.AZURE_CLIENT_ID }}

- name: Get E2E secrets from Azure
uses: bitwarden/gh-actions/get-keyvault-secrets@main
with:
keyvault: gh-android
secrets: "BWS-ACCESS-TOKEN, SAUCE-LABS-USERNAME, SAUCE-LABS-ACCESS-KEY"
id: get-e2e-secrets

- name: Log out from Azure
uses: bitwarden/gh-actions/azure-logout@main

- name: Retrieve test data
uses: bitwarden/sm-action@14f92f1d294ae3c2b6a3845d389cd2c318b0dfd8 # v2.2.0
with:
access_token: ${{ steps.get-e2e-secrets.outputs.BWS-ACCESS-TOKEN }}
secrets: |
63e93f73-5118-4a62-9db8-b3160176aa8a > TEST_ACCOUNT_CREDS

- name: Configure .json test data file
run: printf %s '${{ env.TEST_ACCOUNT_CREDS }}' > app/src/androidTest/assets/TestData.json

- name: Build release APK
env:
PLAY_KEYSTORE_PASSWORD: ${{ secrets.PLAY_KEYSTORE_PASSWORD }}
run: |
bundle exec fastlane assemblePlayStoreReleaseApk \
storeFile:app_play-keystore.jks \
storePassword:'${{ env.PLAY_KEYSTORE_PASSWORD }}' \
keyAlias:bitwarden \
keyPassword:'${{ env.PLAY_KEYSTORE_PASSWORD }}'

- name: Build test APK (espresso)
run: |
./gradlew :app:assembleStandardReleaseAndroidTest

- name: Signing test APK
run: |
$ANDROID_SDK_ROOT/build-tools/34.0.0/apksigner sign \
--ks keystores/app_play-keystore.jks \
--ks-key-alias bitwarden \
--ks-pass pass:${{ secrets.PLAY_KEYSTORE_PASSWORD }} \
--key-pass pass:${{ secrets.PLAY_KEYSTORE_PASSWORD }} \
app/build/outputs/apk/androidTest/standard/release/com.x8bit.bitwarden-standard-release-androidTest.apk

- name: Upload app APK to SauceLabs storage
run: |
saucectl storage upload app/build/outputs/apk/standard/release/com.x8bit.bitwarden.apk
env:
SAUCE_USERNAME: ${{ steps.get-e2e-secrets.outputs.SAUCE-LABS-USERNAME }}
SAUCE_ACCESS_KEY: ${{ steps.get-e2e-secrets.outputs.SAUCE-LABS-ACCESS-KEY }}

- name: Upload test APK to SauceLabs storage
run: |
saucectl storage upload app/build/outputs/apk/androidTest/standard/release/com.x8bit.bitwarden-standard-release-androidTest.apk
env:
SAUCE_USERNAME: ${{ steps.get-e2e-secrets.outputs.SAUCE-LABS-USERNAME }}
SAUCE_ACCESS_KEY: ${{ steps.get-e2e-secrets.outputs.SAUCE-LABS-ACCESS-KEY }}

- name: Run tests on SauceLabs
run: saucectl run --config .sauce/config.yml
env:
SAUCE_USERNAME: ${{ steps.get-e2e-secrets.outputs.SAUCE-LABS-USERNAME }}
SAUCE_ACCESS_KEY: ${{ steps.get-e2e-secrets.outputs.SAUCE-LABS-ACCESS-KEY }}

- name: Upload SauceLabs test report
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: saucectl-report
path: saucectl-report.xml
32 changes: 32 additions & 0 deletions .sauce/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
apiVersion: v1alpha
kind: espresso
defaults:
timeout: 10m
sauce:
region: us-west-1
# Controls how many suites are executed at the same time (sauce test env only).
concurrency: 1
retries: 1
visibility: team
metadata:
tags:
- Android
- sanity-e2e
build: Sanity check on Real devices
reporters:
junit:
enabled: true
filename: saucectl-report.xml
espresso:
app: storage:filename=com.x8bit.bitwarden.apk
testApp: storage:filename=com.x8bit.bitwarden-standard-release-androidTest.apk
suites:
- name: "Android - Sanity"
devices:
- name: "Google.*"
platformVersion: "^1[3456].*"
options:
deviceType: PHONE
testOptions:
package: e2e.tests
resigningEnabled: false
12 changes: 11 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ android {
namespace = "com.x8bit.bitwarden"
compileSdk = libs.versions.compileSdk.get().toInt()

testBuildType = "release"

room {
schemaDirectory("$projectDir/schemas")
}
Expand Down Expand Up @@ -248,6 +250,10 @@ dependencies {
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.uiautomator)
implementation(libs.androidx.espresso.core)
implementation(libs.androidx.junit.ktx)
implementation(libs.androidx.ui.test.junit4.android)
Comment on lines +259 to +262
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove these as implementation dependencies?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason, the build fails if I remove those dependencies ๐Ÿค”

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think he want's them to be an androidTestImplementation to avoid having these test dependencies in the non-test builds

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but those deps are already present as androidTestImplementation in lines 302-306.
Apart from that, we need those deps in non-test builds since we aim to test the release build on real devices

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๐Ÿค” We do not want to ship with unnecessary dependencies like this. Is there a specific error you are seeing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find a way to build the required testApp without those deps ๐Ÿซค

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we need a new variant for this.

ksp(libs.androidx.room.compiler)
implementation(libs.androidx.room.ktx)
implementation(libs.androidx.room.runtime)
Expand Down Expand Up @@ -284,7 +290,6 @@ dependencies {
testImplementation(testFixtures(project(":network")))
testImplementation(testFixtures(project(":ui")))

testImplementation(libs.androidx.compose.ui.test)
testImplementation(libs.google.hilt.android.testing)
testImplementation(platform(libs.junit.bom))
testRuntimeOnly(libs.junit.platform.launcher)
Expand All @@ -294,6 +299,11 @@ dependencies {
testImplementation(libs.mockk.mockk)
testImplementation(libs.robolectric.robolectric)
testImplementation(libs.square.turbine)
androidTestImplementation(libs.androidx.uiautomator)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(libs.androidx.junit.ktx)
androidTestImplementation(libs.androidx.ui.test.junit4.android)
androidTestImplementation(libs.androidx.compose.ui.test)
}

tasks {
Expand Down
Loading