Skip to content

Commit cd088cf

Browse files
authored
AuthFlowTester App (#2821)
* Create AuthFlowTester app with login UI test. * Add all AuthFlowTester UI elements and make functions for rest requests. Update CI to run UI tests. * Add all UI elements. * Add JwtTokenView. * Move common composables to BaseComponents class and refine UI. * Fix test issue. * Add tests. * Fix new AuthFlowTester UI test. * Read test OAuthConfig from file.
1 parent e207720 commit cd088cf

39 files changed

+2902
-6
lines changed

.github/workflows/nightly.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,17 @@ jobs:
3737
matrix:
3838
lib: [SalesforceReact]
3939
uses: ./.github/workflows/reusable-workflow.yaml
40+
with:
41+
lib: ${{ matrix.lib }}
42+
secrets: inherit
43+
android-nightly-UI-Tests:
44+
if: success() || failure()
45+
needs: [android-nightly-React]
46+
strategy:
47+
fail-fast: false
48+
matrix:
49+
lib: [App]
50+
uses: ./.github/workflows/reusable-workflow.yaml
4051
with:
4152
lib: ${{ matrix.lib }}
4253
secrets: inherit
File renamed without changes.
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
on:
2+
workflow_call:
3+
inputs:
4+
is_pr:
5+
type: boolean
6+
default: false
7+
8+
jobs:
9+
test-android:
10+
runs-on: ubuntu-latest
11+
env:
12+
BUNDLE_GEMFILE: ${{ github.workspace }}/.github/DangerFiles/Gemfile
13+
CI_USER_USERNAME: ${{ secrets.CI_USER_USERNAME }}
14+
CI_USER_PASSWORD: ${{ secrets.CI_USER_PASSWORD }}
15+
TEST_CONFIG: ${{ secrets.UI_TEST_CONFIG }}
16+
steps:
17+
- uses: actions/checkout@v4
18+
if: ${{ inputs.is_pr }}
19+
with:
20+
# We need a sufficient depth or Danger will occasionally run into issues checking which files were modified.
21+
fetch-depth: 100
22+
ref: ${{ github.event.pull_request.head.sha }}
23+
- uses: actions/checkout@v4
24+
if: ${{ ! inputs.is_pr }}
25+
with:
26+
ref: ${{ github.head_ref }}
27+
- name: Install Dependencies
28+
env:
29+
TEST_CREDENTIALS: ${{ secrets.TEST_CREDENTIALS }}
30+
run: |
31+
./install.sh
32+
echo $TEST_CREDENTIALS > ./shared/test/test_credentials.json
33+
echo $TEST_CONFIG > ./native/NativeSampleApps/AuthFlowTester/src/main/assets/test_config.json
34+
- uses: actions/setup-java@v4
35+
with:
36+
distribution: 'zulu'
37+
java-version: '21'
38+
- name: Setup Android SDK
39+
uses: android-actions/setup-android@v3
40+
- uses: gradle/actions/setup-gradle@v4
41+
with:
42+
# This is the actual Gradle version (not AGP), which can be found in the gradle-wrapper.properties file.
43+
gradle-version: "8.14.3"
44+
add-job-summary: on-failure
45+
add-job-summary-as-pr-comment: on-failure
46+
- name: Build for Testing
47+
if: success() || failure()
48+
run: |
49+
./gradlew native:NativeSampleApps:AuthFlowTester:assembleDebug
50+
- name: Build Tests for PR
51+
if: ${{ inputs.is_pr }}
52+
run: |
53+
./gradlew native:NativeSampleApps:AuthFlowTester:assembleAndroidTest --tests "com.salesforce.samples.authflowtester.loginTest"
54+
- name: Build Tests for Nightly
55+
if: ${{ !inputs.is_pr }}
56+
run: |
57+
./gradlew native:NativeSampleApps:AuthFlowTester:assembleAndroidTest
58+
- uses: 'google-github-actions/auth@v2'
59+
if: success() || failure()
60+
with:
61+
credentials_json: '${{ secrets.GCLOUD_SERVICE_KEY }}'
62+
- uses: 'google-github-actions/setup-gcloud@v2'
63+
if: success() || failure()
64+
- name: Run Tests
65+
continue-on-error: true
66+
if: success() || failure()
67+
env:
68+
# Most used according to https://gs.statcounter.com/android-version-market-share/mobile-tablet/worldwide
69+
PR_API_VERSION: "35"
70+
FULL_API_RANGE: "28 29 30 31 32 33 34 35 36"
71+
IS_PR: ${{ inputs.is_pr }}
72+
run: |
73+
LEVELS_TO_TEST=$FULL_API_RANGE
74+
RETRIES=0
75+
76+
if $IS_PR ; then
77+
LEVELS_TO_TEST=$PR_API_VERSION
78+
RETRIES=1
79+
fi
80+
81+
mkdir firebase_results
82+
for LEVEL in $LEVELS_TO_TEST
83+
do
84+
GCLOUD_RESULTS_DIR=authflowtester-api-${LEVEL}-build-${{github.run_number}}
85+
86+
eval gcloud beta firebase test android run \
87+
--project mobile-apps-firebase-test \
88+
--type instrumentation \
89+
--app "native/NativeSampleApps/AuthFlowTester/build/outputs/apk/debug/AuthFlowTester-debug.apk" \
90+
--test="native/NativeSampleApps/AuthFlowTester/build/outputs/apk/androidTest/debug/AuthFlowTester-debug-androidTest.apk" \
91+
--device model=MediumPhone.arm,version=${LEVEL},locale=en,orientation=portrait \
92+
--environment-variables username=${CI_USER_USERNAME},password=${CI_USER_PASSWORD} \
93+
--directories-to-pull=/sdcard \
94+
--results-dir=${GCLOUD_RESULTS_DIR} \
95+
--results-history-name=AuthFlowTester \
96+
--timeout=30m --no-performance-metrics \
97+
--num-flaky-test-attempts=${RETRIES} || true
98+
done
99+
- name: Copy Test Results
100+
continue-on-error: true
101+
if: success() || failure()
102+
env:
103+
# Most used according to https://gs.statcounter.com/android-version-market-share/mobile-tablet/worldwide
104+
PR_API_VERSION: "35"
105+
FULL_API_RANGE: "28 29 30 31 32 33 34 35 36"
106+
IS_PR: ${{ inputs.is_pr }}
107+
run: |
108+
LEVELS_TO_TEST=$FULL_API_RANGE
109+
110+
if $IS_PR ; then
111+
LEVELS_TO_TEST=$PR_API_VERSION
112+
fi
113+
114+
for LEVEL in $LEVELS_TO_TEST
115+
do
116+
GCLOUD_RESULTS_DIR=authflowtester-api-${LEVEL}-build-${{github.run_number}}
117+
BUCKET_PATH="gs://test-lab-w87i9sz6q175u-kwp8ium6js0zw/${GCLOUD_RESULTS_DIR}"
118+
119+
gsutil ls ${BUCKET_PATH} > /dev/null 2>&1
120+
if [ $? == 0 ] ; then
121+
# Copy XML file for test reporting
122+
if gsutil ls "${BUCKET_PATH}/*test_results_merged.xml" > /dev/null 2>&1; then
123+
# Sharded runs produce test_results_merged.xml at top level
124+
gsutil cp "${BUCKET_PATH}/*test_results_merged.xml" firebase_results/api_${LEVEL}_test_result.xml
125+
else
126+
gsutil cp "${BUCKET_PATH}/*/test_result_1.xml" firebase_results/api_${LEVEL}_test_result.xml
127+
fi
128+
fi
129+
done
130+
- name: Test Report
131+
uses: mikepenz/action-junit-report@v5
132+
if: success() || failure()
133+
with:
134+
check_name: ${{ inputs.lib }} Test Results
135+
job_name: ${{ inputs.lib }} Test Results
136+
require_tests: true
137+
check_retries: true
138+
flaky_summary: true
139+
fail_on_failure: true
140+
group_reports: false
141+
include_passed: true
142+
include_empty_in_summary: false
143+
simplified_summary: true
144+
report_paths: 'firebase_results/**.xml'

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ yarn.lock
2121
index.android.bundle*
2222
shared/test/test_credentials.json
2323
.vscode/
24+
test_config.json

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
[submodule "external/shared"]
22
path = external/shared
33
url = https://github.com/forcedotcom/SalesforceMobileSDK-Shared.git
4+
[submodule "external/SalesforceMobileSDK-UITests"]
5+
path = external/SalesforceMobileSDK-UITests
6+
url = https://github.com/forcedotcom/SalesforceMobileSDK-UITests

libs/SalesforceSDK/res/values/sf__strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109

110110
<!-- Developer Support -->
111111
<string name="sf__dev_support_title">Mobile SDK Developer Support</string>
112+
<string name="sf__dev_support_title_menu_item">Developer Support</string>
112113
<string name="sf__notifications_local_show_dev_support_content">Show Salesforce Mobile SDK developer support</string>
113114
<string name="sf__notifications_local_show_dev_support_text">Tap to display Salesforce Mobile SDK developer support in the active app.</string>
114115
<string name="sf__notifications_local_show_dev_support_title">Salesforce Mobile Developer Support</string>

libs/SalesforceSDK/src/com/salesforce/androidsdk/app/SalesforceSDKManager.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,9 @@ open class SalesforceSDKManager protected constructor(
234234
/**
235235
* Asynchronously retrieves the app config for the specified login host. If not set or null is
236236
* returned the values found in the BootConfig file will be used for all servers.
237+
*
238+
* The [com.salesforce.androidsdk.util.urlHostOrNull] String extension function may be
239+
* helpful for simple comparisons.
237240
*/
238241
var appConfigForLoginHost: suspend (server: String) -> OAuthConfig? = {
239242
OAuthConfig(getBootConfig(appContext))

libs/SalesforceSDK/src/com/salesforce/androidsdk/auth/ScopeParser.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ class ScopeParser {
4848
return ScopeParser(scopeString)
4949
}
5050

51+
/**
52+
* String extension to convert to [ScopeParser].
53+
*/
54+
fun String?.toScopeParser(): ScopeParser = ScopeParser(scopeString = this)
55+
5156
/**
5257
* Computes the scope parameter from an array of scopes.
5358
*
@@ -74,6 +79,18 @@ class ScopeParser {
7479
scopesSet.add(REFRESH_TOKEN)
7580
return scopesSet.joinToString(" ")
7681
}
82+
83+
/**
84+
* Computes the scope parameter from an array of scopes.
85+
*
86+
* Behavior:
87+
* - If {@code scopes} is null or empty, returns an empty string. This indicates that all
88+
* scopes assigned to the connected app / external client app will be requested by default
89+
* (no explicit scope parameter is sent).
90+
* - If {@code scopes} is non-empty, ensures {@code refresh_token} is present in the set and
91+
* returns a space-delimited string of unique, sorted scopes.
92+
*/
93+
fun Array<String>?.toScopeParameter(): String = computeScopeParameter(this)
7794
}
7895

7996
private val _scopes: MutableSet<String>

libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/components/LoginServerListItem.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ import com.salesforce.androidsdk.R.string.sf__server_url_delete
9393
import com.salesforce.androidsdk.config.LoginServerManager.LoginServer
9494
import com.salesforce.androidsdk.ui.theme.sfDarkColors
9595
import com.salesforce.androidsdk.ui.theme.sfLightColors
96+
import com.salesforce.androidsdk.util.test.ExcludeFromJacocoGeneratedReport
9697
import kotlinx.coroutines.delay
9798
import kotlinx.coroutines.launch
9899

@@ -244,6 +245,7 @@ fun LoginServerListItem(
244245
}
245246
}
246247

248+
@ExcludeFromJacocoGeneratedReport
247249
@Preview("Default Server", showBackground = true)
248250
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, backgroundColor = 0xFF181818)
249251
@Composable
@@ -258,6 +260,7 @@ private fun DefaultServerPreview() {
258260
}
259261
}
260262

263+
@ExcludeFromJacocoGeneratedReport
261264
@Preview("Very Long Default Server", showBackground = true)
262265
@Composable
263266
private fun LongDefaultServerPreview() {
@@ -275,6 +278,7 @@ private fun LongDefaultServerPreview() {
275278
}
276279
}
277280

281+
@ExcludeFromJacocoGeneratedReport
278282
@Preview("Custom Server", showBackground = true)
279283
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, backgroundColor = 0xFF181818)
280284
@Composable
@@ -289,6 +293,7 @@ private fun CustomServerPreview() {
289293
}
290294
}
291295

296+
@ExcludeFromJacocoGeneratedReport
292297
@Preview("Deleting", showBackground = true)
293298
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true, backgroundColor = 0xFF181818)
294299
@Composable
@@ -304,6 +309,7 @@ private fun DeletingLoginServer() {
304309
}
305310
}
306311

312+
@ExcludeFromJacocoGeneratedReport
307313
@Preview("Very Long Custom Server", showBackground = true)
308314
@Composable
309315
private fun LongServerPreview() {
@@ -321,6 +327,7 @@ private fun LongServerPreview() {
321327
}
322328
}
323329

330+
@ExcludeFromJacocoGeneratedReport
324331
@Preview("Very Long Custom Server Deleting", showBackground = true)
325332
@Composable
326333
private fun LongServerDeletingPreview() {

0 commit comments

Comments
 (0)