Skip to content

Commit 6c1cbf3

Browse files
authored
Token Migration UI Tests (#2844)
* Add token migration feature and AuthFlowTester UI to exercise it. * Improve Token Migration loading indicator. Add app icon for AuthFlowTester. * Cleanup Token Migration error logging. * Update existing test classes. * Add tests for UserAccountManager.migrateRefreshToken, TokenMigrationActivity and MigrationCallbackRegistry. * Add TokenMigrationActivity tests. * Additional tests. * Add tests for AuthenticationUtilities handleScreenLockPolicy, handleBiometricAuthPolicy and handleDuplicateUserAccount. Split out TokenMigrationView and add tests. * Remove unnecessary code and improve tests. * Compose UI Test POC. * Improve migration test flow with UITestConfig. Increase speed of webview page objects by removing unnecessary calls. Fix tests on small and low API devices. Switch to sed to make install and set version scripts portable. * Add the remaining migration tests. Split handling of Authorization Page into login and migration and increased timeouts for consistency. * Add Test Orchestrator for UI Tests. Run two UI tests on PR. * Fix rare timing/synchronization test issue. * Update UI Test CI config. * Simplify rest call to avoid rare Compose UI Test issue.
1 parent 9e8338d commit 6c1cbf3

29 files changed

Lines changed: 1404 additions & 205 deletions

.github/workflows/reusable-ui-workflow.yaml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ jobs:
2323
ref: ${{ github.head_ref }}
2424
- name: Install Dependencies
2525
env:
26-
TEST_CREDENTIALS: ${{ secrets.TEST_CREDENTIALS }}
26+
MSDK_ANDROID_REMOTE_ACCESS_CALLBACK_URL: ${{ secrets.MSDK_ANDROID_REMOTE_ACCESS_CALLBACK_URL }}
27+
MSDK_ANDROID_REMOTE_ACCESS_CONSUMER_KEY: ${{ secrets.MSDK_ANDROID_REMOTE_ACCESS_CONSUMER_KEY }}
2728
UI_TEST_CONFIG: ${{ secrets.UI_TEST_CONFIG }}
2829
run: |
2930
./install.sh
30-
echo $TEST_CREDENTIALS > ./shared/test/test_credentials.json
3131
echo $UI_TEST_CONFIG > ./shared/test/ui_test_config.json
3232
- uses: actions/setup-java@v4
3333
with:
@@ -62,8 +62,6 @@ jobs:
6262
PR_API_VERSION: "35"
6363
FULL_API_RANGE: "28 29 30 31 32 33 34 35 36"
6464
IS_PR: ${{ inputs.is_pr }}
65-
CI_USER_USERNAME: ${{ secrets.CI_USER_USERNAME }}
66-
CI_USER_PASSWORD: ${{ secrets.CI_USER_PASSWORD }}
6765
run: |
6866
LEVELS_TO_TEST=$FULL_API_RANGE
6967
RETRIES=0
@@ -72,8 +70,9 @@ jobs:
7270
if $IS_PR ; then
7371
LEVELS_TO_TEST=$PR_API_VERSION
7472
RETRIES=1
75-
# Run only a single test named "testLogin"
76-
TEST_TARGETS="--test-targets \"class com.salesforce.samples.authflowtester.LoginTest#testLogin\""
73+
# Run only a handful of smoke tests.
74+
TEST_TARGETS="--test-targets \"class com.salesforce.samples.authflowtester.LoginTest#testBasicLogin\""
75+
TEST_TARGETS+=",\"class com.salesforce.samples.authflowtester.TokenMigrationTest#testMigrate_ECA_AddMoreScopes\""
7776
fi
7877
7978
mkdir firebase_results
@@ -84,14 +83,15 @@ jobs:
8483
eval gcloud firebase test android run \
8584
--project mobile-apps-firebase-test \
8685
--type instrumentation \
86+
--use-orchestrator \
87+
--environment-variables clearPackageData=true \
8788
--app "native/NativeSampleApps/AuthFlowTester/build/outputs/apk/debug/AuthFlowTester-debug.apk" \
8889
--test "native/NativeSampleApps/AuthFlowTester/build/outputs/apk/androidTest/debug/AuthFlowTester-debug-androidTest.apk" \
8990
--device model=MediumPhone.arm,version=${LEVEL},locale=en,orientation=portrait \
90-
--environment-variables username=${CI_USER_USERNAME},password=${CI_USER_PASSWORD} \
9191
--directories-to-pull=/sdcard \
9292
--results-dir=${GCLOUD_RESULTS_DIR} \
9393
--results-history-name=AuthFlowTester \
94-
--timeout=5m --no-performance-metrics \
94+
--timeout=10m --no-performance-metrics \
9595
$TEST_TARGETS \
9696
--num-flaky-test-attempts=${RETRIES} || true
9797
done

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,6 @@ shared/test/ui_test_config.json
2525
!libs/test/**/bootconfig.xml
2626
!libs/SalesforceSDK/**/bootconfig.xml
2727
native/NativeSampleApps/AuthFlowTester/src/main/assets/
28-
.vscode/
28+
.vscode/
29+
Test*.html
30+
keep.xml

.gitmodules

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
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
Lines changed: 0 additions & 1 deletion
This file was deleted.

install.sh

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,11 @@ apply_bootconfig_paths() {
3838
if [ -f "$bootconfig" ]; then
3939
# Substitute env vars if set
4040
if [ -n "${MSDK_ANDROID_REMOTE_ACCESS_CONSUMER_KEY:-}" ]; then
41-
gsed -i "s|__CONSUMER_KEY__|${MSDK_ANDROID_REMOTE_ACCESS_CONSUMER_KEY}|g" "$bootconfig"
41+
# sed -i.bak works identically on both BSD sed (macOS) and GNU sed (Linux)
42+
sed -i.bak "s|__CONSUMER_KEY__|${MSDK_ANDROID_REMOTE_ACCESS_CONSUMER_KEY}|g" "$bootconfig" && rm -f "$bootconfig.bak"
4243
fi
4344
if [ -n "${MSDK_ANDROID_REMOTE_ACCESS_CALLBACK_URL:-}" ]; then
44-
gsed -i "s|__REDIRECT_URI__|${MSDK_ANDROID_REMOTE_ACCESS_CALLBACK_URL}|g" "$bootconfig"
45+
sed -i.bak "s|__REDIRECT_URI__|${MSDK_ANDROID_REMOTE_ACCESS_CALLBACK_URL}|g" "$bootconfig" && rm -f "$bootconfig.bak"
4546
fi
4647
fi
4748
done

libs/SalesforceSDK/res/values/sf__strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
<string name="sf__server_remove_content_description">Remove Server</string>
4747
<string name="sf__server_delete_content_description">Confirm Delete</string>
4848
<string name="sf__server_picker_click_label">Select login server</string>
49+
<string name="sf__custom_url_button_content_description">Add New Connection</string>
4950

5051
<!-- Manage space activity -->
5152
<string name="sf__manage_space_title">Clear User Data?</string>
@@ -59,6 +60,7 @@
5960
<string name="sf__account_picker_content_description">User Account Picker</string>
6061
<string name="sf__account_selector_click_label">Select user account</string>
6162
<string name="sf__profile_photo_content_description">Profile Photo</string>
63+
<string name="sf__add_new_account_content_description">Add New Account</string>
6264

6365
<!-- SP status updates -->
6466
<string name="sf__failed_to_send_request_to_idp">Failed to send request to IDP app</string>
@@ -120,4 +122,5 @@
120122
<string name="sf__login_options_consumer_key_field_content_description">Consumer Key Field</string>
121123
<string name="sf__login_options_redirect_uri_field_content_description">Redirect URI Field</string>
122124
<string name="sf__login_options_scopes_field_content_description">Scopes Field</string>
125+
<string name="sf__login_options_save_button_content_description">Save Button</string>
123126
</resources>

libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/LoginOptionsActivity.kt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,13 @@ import androidx.compose.ui.Modifier
6868
import androidx.compose.ui.graphics.Color
6969
import androidx.compose.ui.platform.LocalContext
7070
import androidx.compose.ui.res.stringResource
71+
import androidx.compose.ui.semantics.Role
72+
import androidx.compose.ui.semantics.clearAndSetSemantics
7173
import androidx.compose.ui.semantics.contentDescription
74+
import androidx.compose.ui.semantics.role
7275
import androidx.compose.ui.semantics.semantics
76+
import androidx.compose.ui.semantics.toggleableState
77+
import androidx.compose.ui.state.ToggleableState
7378
import androidx.compose.ui.text.font.FontWeight
7479
import androidx.compose.ui.tooling.preview.Preview
7580
import androidx.compose.ui.unit.dp
@@ -158,8 +163,10 @@ fun OptionToggle(
158163
Switch(
159164
checked = checked,
160165
onCheckedChange = { optionData.value = it },
161-
modifier = Modifier.semantics {
166+
modifier = Modifier.clearAndSetSemantics {
162167
this.contentDescription = contentDescription
168+
this.toggleableState = ToggleableState(checked)
169+
this.role = Role.Switch
163170
}
164171
)
165172
}
@@ -173,6 +180,7 @@ fun BootConfigView(config: OAuthConfig? = null) {
173180
val consumerKeyFieldDesc = stringResource(R.string.sf__login_options_consumer_key_field_content_description)
174181
val redirectFieldDesc = stringResource(R.string.sf__login_options_redirect_uri_field_content_description)
175182
val scopesFieldDesc = stringResource(R.string.sf__login_options_scopes_field_content_description)
183+
val saveContentDesc = stringResource(R.string.sf__login_options_save_button_content_description)
176184
val validInput = overrideConsumerKey.isNotBlank() && overrideRedirectUri.isNotBlank()
177185
val activity = LocalActivity.current
178186

@@ -244,7 +252,9 @@ fun BootConfigView(config: OAuthConfig? = null) {
244252
)
245253

246254
Button(
247-
modifier = Modifier.padding(PADDING_SIZE.dp).fillMaxWidth(),
255+
modifier = Modifier.padding(PADDING_SIZE.dp)
256+
.fillMaxWidth()
257+
.semantics { contentDescription = saveContentDesc },
248258
shape = RoundedCornerShape(CORNER_RADIUS.dp),
249259
contentPadding = PaddingValues(PADDING_SIZE.dp),
250260
colors = ButtonColors(
@@ -358,8 +368,10 @@ fun LoginOptionsScreen(
358368
SalesforceSDKManager.getInstance().debugOverrideAppConfig = null
359369
}
360370
},
361-
modifier = Modifier.semantics {
371+
modifier = Modifier.clearAndSetSemantics {
362372
contentDescription = dynamicConfigToggleDesc
373+
toggleableState = ToggleableState(useDynamicConfig)
374+
role = Role.Switch
363375
}
364376
)
365377
}

libs/SalesforceSDK/src/com/salesforce/androidsdk/ui/TokenMigrationActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ internal const val ERROR_BUILD_REST_CLIENT = "Unable to build RestClient."
8282
@VisibleForTesting
8383
internal const val ERROR_SINGLE_ACCESS_FAILED = "Request for single access bridge url failed"
8484
@VisibleForTesting
85-
internal const val ERROR_TOKEN_INVALID_DESC = "User's existing token may be invalid."
85+
internal const val ERROR_TOKEN_INVALID_DESC = "Current refresh token may be invalid or missing web scope."
8686

8787
internal class TokenMigrationActivity : ComponentActivity() {
8888

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ internal fun MenuItem(
394394
)
395395
},
396396
onClick = onClick,
397+
modifier = Modifier.semantics { contentDescription = text }
397398
)
398399
}
399400

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,10 @@ import androidx.lifecycle.viewmodel.compose.viewModel
113113
import com.salesforce.androidsdk.R.string.sf__account_picker_content_description
114114
import com.salesforce.androidsdk.R.string.sf__account_selector_text
115115
import com.salesforce.androidsdk.R.string.sf__add_new_account
116+
import com.salesforce.androidsdk.R.string.sf__add_new_account_content_description
116117
import com.salesforce.androidsdk.R.string.sf__back_button_content_description
117118
import com.salesforce.androidsdk.R.string.sf__custom_url_button
119+
import com.salesforce.androidsdk.R.string.sf__custom_url_button_content_description
118120
import com.salesforce.androidsdk.R.string.sf__pick_server
119121
import com.salesforce.androidsdk.R.string.sf__server_close_button_content_description
120122
import com.salesforce.androidsdk.R.string.sf__server_picker_content_description
@@ -256,6 +258,11 @@ internal fun PickerBottomSheet(
256258
PickerStyle.LoginServerPicker -> stringResource(sf__server_picker_content_description)
257259
PickerStyle.UserAccountPicker -> stringResource(sf__account_picker_content_description)
258260
}
261+
val addButtonContentDescription = when (pickerStyle) {
262+
PickerStyle.LoginServerPicker -> stringResource(sf__custom_url_button_content_description)
263+
PickerStyle.UserAccountPicker -> stringResource(sf__add_new_account_content_description)
264+
}
265+
259266
val themeRippleConfiguration = RippleConfiguration(color = colorScheme.onSecondary)
260267
val coroutineScope = rememberCoroutineScope()
261268

@@ -455,7 +462,8 @@ internal fun PickerBottomSheet(
455462
},
456463
modifier = Modifier
457464
.padding(PADDING_SIZE.dp)
458-
.fillMaxWidth(),
465+
.fillMaxWidth()
466+
.semantics { contentDescription = addButtonContentDescription },
459467
shape = RoundedCornerShape(CORNER_RADIUS.dp),
460468
contentPadding = PaddingValues(PADDING_SIZE.dp),
461469
border = BorderStroke(STROKE_WIDTH.dp, colorScheme.outline),

0 commit comments

Comments
 (0)