Skip to content

feat(W-23721537): speed up AuthFlowTester UI tests via launch-arg driven SDK reset - #4121

Merged
wmathurin merged 19 commits into
forcedotcom:devfrom
wmathurin:W-23721537-authflowtester-launch-arg-reset
Aug 6, 2026
Merged

feat(W-23721537): speed up AuthFlowTester UI tests via launch-arg driven SDK reset#4121
wmathurin merged 19 commits into
forcedotcom:devfrom
wmathurin:W-23721537-authflowtester-launch-arg-reset

Conversation

@wmathurin

@wmathurin wmathurin commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds SalesforceSDKManager.resetForUITesting() (#if DEBUG) — logs out all users (including async server refresh-token revocation), clears per-user in-memory feature flags, resets the selected login host to login.salesforce.com, removes persisted custom login servers, and restores all auth flags to their init defaults.
  • AppDelegate calls it when --resetSDKForUITesting is in launch arguments.
  • BaseAuthFlowTester.launch() passes --resetSDKForUITesting so every test gets a clean slate in-process at startup, replacing 10–20 s of UI-driven tearDown/launch cleanup per test.
  • tearDown is now a no-op (no UI logout, no DPoP toggle navigation).
  • restart() creates a fresh XCUIApplication without the reset arg so session-persistence tests continue to work.

SDK fix

SFUserAccountManager.m — preserve BW flag through refresh token migration

finalizeAuthCompletion: had an unconditional else that unregistered the BW (kSFAppFeatureSafariBrowserForLogin) per-user flag for any auth type other than SFOAuthTypeAdvancedBrowser. Refresh token migration completes with SFOAuthTypeRefreshTokenMigration — a silent token exchange that does not change how the user originally authenticated — so BW was being cleared even when the user had logged in via the external browser. Added an explicit no-op guard for SFOAuthTypeRefreshTokenMigration that leaves the existing per-user flag intact. Normal token refreshes and other non-browser auth types continue to clear BW as designed.

Test fixes (found while running the suite after the main change)

Several pre-existing assertion bugs surfaced because the launch-arg reset makes every test start from a known clean state — previously these were masked by state leaking between tests.

Fix Root cause
tokenFormat in credentials JSON was "Opaque" instead of "" UserCredentialsView.tokenFormat applied a display substitution ("""Opaque") that was flowing into the exported JSON. Split into tokenFormatRaw (export) and tokenFormat (display).
restartAndValidateUser wiped the session on restart app still carried --resetSDKForUITesting in launchArguments; fixed by creating a fresh XCUIApplication in restart().
2 s unnecessary wait per test when adding a login server hasHost() used a 10 s timeout to probe for a custom host that resetForUITesting always removes. Reduced to 2 s (host list is synchronously in-memory).
forceAdvancedAuthentication: nil was overriding SDK default to off configureLoginOptions was called with forceAdvancedAuthentication ?? false, actively writing false into the Login Options JSON and switching the app to WebView mode even when the caller meant "use the SDK default (browser on)". Fixed by making forceAdvancedAuthentication a non-nullable Bool = true across all helpers (matching Android's AuthFlowTest), removing the optional path entirely.
MU flag asserted absent while two users were still logged in switchToUserAndValidate calls in testFirstStatic/Dynamic_DifferentApps, testBothDynamic_DifferentApps, and testMigrateOneUserOnly (via migrateAndValidate) were missing isMultiUser: true. Added isMultiUser param to migrateAndValidate and fixed all call sites.
RT flag asserted absent after RTR cycle survived restart validateUser had no isRtr param, always calling validateUserAgent with isRtr: false. After launchLoginAndValidate runs an RTR cycle the RT flag persists through the non-resetting restart. Added isRtr to validateUser and validateUserAgent.
RT flag present at test start (e.g. testECAOpaqueRtr_Hybrid) SFSDKPerUserFeatureMarkersMap was never cleared by logoutAllUsers. RT flag from a previous test's RTR cycle survived into the next test's initial UA check. Added #if DEBUG resetPerUserFeaturesForUITesting to SFSDKAppFeatureMarkers and called it from resetForUITesting.
invalid_client_id error on standard server in ForceAdvancedAuth disabled test setForceAdvancedAuthentication was importing a My-Domain ECA consumer key (ecaOpaque) when testing against login.salesforce.com, triggering invalid_client_id. Made staticAppConfigName optional (defaults to nil); the app's default bootconfig.plist key is valid on the standard server.

Refactors

Refactor Detail
forceAdvancedAuthentication: Bool? = nilBool = true Matches Android. nil was ambiguous and masked the ?? false bug. Non-nullable default makes the intent explicit: browser on unless the test explicitly opts out with false.
restartAndValidateUser no longer has isRtr param Restart-and-validate is session-persistence only. RTR check after restart is a separate concern; tests call assertRevokeAndRefreshWorks(isRtr: true) explicitly, consistent with DPoP tests and the skipped testECAJwtRtr_Hybrid_WithRestart.
Extract resetAuthFlags helper in SalesforceSDKManager Both -init and resetForUITesting set the same auth flags. Extracted into a private instance method so the values are defined once and can't diverge.

New tests

  • LegacyLoginTests: added testCAOpaque_{Default,Subset,All}Scopes_WebServerFlow_InAppWebView — mirrors the existing browser-path web server flow tests with forceAdvancedAuthentication: false to cover the in-app WebView path under the same scope variations.

Files changed

File Change
SalesforceSDKCore/Classes/Common/SalesforceSDKManager.h Declare + (void)resetForUITesting under #if DEBUG
SalesforceSDKCore/Classes/Common/SalesforceSDKManager.m Implement resetForUITesting; extract resetAuthFlags; call resetPerUserFeaturesForUITesting; import SFSDKLoginHostStorage.h
SalesforceSDKCore/Classes/Common/SFSDKAppFeatureMarkers.h Declare + (void)resetPerUserFeaturesForUITesting under #if DEBUG
SalesforceSDKCore/Classes/Common/SFSDKAppFeatureMarkers.m Implement resetPerUserFeaturesForUITesting — clears SFSDKPerUserFeatureMarkersMap
SalesforceSDKCore/Classes/UserAccount/SFUserAccountManager.m Preserve BW per-user flag for SFOAuthTypeRefreshTokenMigration in finalizeAuthCompletion:
AuthFlowTester/Classes/AppDelegate.swift Call resetForUITesting() when launch arg present
AuthFlowTester/Views/UserCredentialsView.swift Split tokenFormat into raw (export) and display variants
AuthFlowTesterUITests/Util/BaseAuthFlowTester.swift Add launch arg; simplify tearDown; fix restart(); make forceAdvancedAuthentication non-nullable (Bool = true); remove isRtr from restartAndValidateUser; add isRtr/isMultiUser params where needed; make setForceAdvancedAuthentication staticAppConfigName optional; restore migrateAndValidate default to true
AuthFlowTesterUITests/PageObjects/LoginPageObject.swift Reduce hasHost timeout from 10 s to 2 s
AuthFlowTesterUITests/Tests/ForceAdvancedAuthTests.swift Drop redundant staticAppConfigName from disabled-auth test
AuthFlowTesterUITests/Tests/MultiUserLoginTests.swift Pass isMultiUser: true to switchToUserAndValidate while two users are logged in
AuthFlowTesterUITests/Tests/RefreshTokenMigrationTests.swift Pass isMultiUser: true to migrateAndValidate / switchToUserAndValidate; pass forceAdvancedAuthentication: false for user-agent-flow migration tests
AuthFlowTesterUITests/Tests/LegacyLoginTests.swift Add three _InAppWebView variants with forceAdvancedAuthentication: false

…ven SDK reset

Add SalesforceSDKManager.resetForUITesting() (#if DEBUG) which logs out all
users (including async server refresh-token revocation), resets the selected
login host to login.salesforce.com, removes persisted custom login servers,
and restores all auth flags to their post-init defaults.

AppDelegate calls it when --resetSDKForUITesting is in launch arguments.

BaseAuthFlowTester.launch() passes the flag so every test gets a clean slate
in-process at startup. tearDown is now a no-op (no UI logout, no DPoP toggle
navigation). restart() creates a fresh XCUIApplication without the reset arg
so session-persistence tests (LoginWithRestartTests, RefreshTokenMigration-
WithRestartTests) continue to work correctly.
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
1 Warning
⚠️ Static Analysis found an issue with one or more files you modified. Please fix the issue(s).

Clang Static Analysis Issues

File Type Category Description Line Col
SFUserAccountManager Nullability Memory error Null passed to a callee that requires a non-null 2nd parameter 1621 15
SFUserAccountManager Nullability Memory error Null passed to a callee that requires a non-null 2nd parameter 1636 15
SFUserAccountManager Nullability Memory error nil passed to a callee that requires a non-null 2nd parameter 2402 13
SalesforceSDKManager Nil value used as mutex for @synchronized() (no synchronization will occur) Logic error Nil value used as mutex for @synchronized() (no synchronization will occur) 159 5
SalesforceSDKManager Nil value used as mutex for @synchronized() (no synchronization will occur) Logic error Nil value used as mutex for @synchronized() (no synchronization will occur) 171 5

Generated by 🚫 Danger

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
TestsPassed ✅SkippedFailed
SalesforceSDKCore iOS ^18 Test Results904 ran904 ✅
TestResult
No test annotations available

@codecov

codecov Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 71.41%. Comparing base (1401c1b) to head (bd0347e).
⚠️ Report is 23 commits behind head on dev.

❌ Your patch status has failed because the patch coverage (34.78%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##              dev    #4121      +/-   ##
==========================================
+ Coverage   71.05%   71.41%   +0.35%     
==========================================
  Files         254      254              
  Lines       22480    22497      +17     
==========================================
+ Hits        15973    16066      +93     
+ Misses       6507     6431      -76     
Components Coverage Δ
Analytics 70.78% <ø> (ø)
Common 71.06% <ø> (+0.18%) ⬆️
Core 66.94% <100.00%> (+0.53%) ⬆️
SmartStore 73.44% <ø> (ø)
MobileSync 88.82% <ø> (ø)
Files with missing lines Coverage Δ
...rceSDKCore/Classes/Common/SFSDKAppFeatureMarkers.m 100.00% <100.00%> (ø)
...forceSDKCore/Classes/Common/SalesforceSDKManager.m 76.40% <100.00%> (+0.43%) ⬆️
...SDKCore/Classes/UserAccount/SFUserAccountManager.m 64.85% <100.00%> (+1.95%) ⬆️

... and 8 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…browser

login() computed advancedAuthEnabled = forceAdvancedAuthentication != false,
which evaluated nil as true (browser). Tests that omit the flag therefore
logged in via the browser, registering the BW marker — but restartAndValidateUser,
switchToUserAndValidate, and launchLoginAndValidate all used the same
!= false logic for expectAdvancedAuth. On restart, the BW flag is not
rehydrated from disk (per-user feature-flag persistence is a separate
unmerged story), so the assertion fires.

Fix: change all four sites from != false to == true so nil and false
both mean "use the in-app WebView". Also pass forceAdvancedAuthentication ?? false
to configureLoginOptions to explicitly clear any residual nAuthentication=true
from a prior test. Update two ForceAdvancedAuthTests cases that relied on
nil=ON to pass forceAdvancedAuthentication: true explicitly.

Port of fix from browser-login-telemetry-markers branch (commit 92a5823).
… restartAndValidateUser

switchToUserAndValidate calls in testBothDynamic, testFirstStatic/Dynamic, and
testMigrateOneUserOnly were missing isMultiUser: true — the MU flag is still
set while both users are logged in, so the assertion fires even though the
switch itself doesn't change the account count.

restartAndValidateUser gains an isRtr parameter (default false). When true,
it runs assertRevokeAndRefreshWorks with the correct expectAdvancedAuth derived
from its own params, replacing the standalone assertRevokeAndRefreshWorks call
that callers (RTRLoginTests) were making separately with defaulted params.

Port of fix from browser-login-telemetry-markers branch (commit 87abbf7).
…rateAndValidate

RT flag (testECAJwtRtr_NoHybrid_WithRestart):
validateUser had no isRtr param, so it always called validateUserAgent with
isRtr=false. After launchLoginAndValidate runs an RTR cycle the RT flag is
set and persists through the non-resetting restart — restartAndValidateUser's
validateUser call then asserts RT must be absent while it's present.
Fix: add isRtr to validateUser and forward it to validateUserAgent; thread
it from restartAndValidateUser.

MU flag (testMigrateOneUserOnly):
migrateAndValidate had no isMultiUser param. In testMigrateOneUserOnly User B
is still logged in when migrateAndValidate runs on User A, so MU is set in
the UA but validate() asserted it must not be there.
Fix: add isMultiUser to migrateAndValidate and forward it to validate();
pass isMultiUser: true at the call site in testMigrateOneUserOnly.
SFSDKPerUserFeatureMarkersMap is a static singleton that survives
logoutAllUsers. Add #if DEBUG resetPerUserFeaturesForUITesting and
call it from resetForUITesting so RT/DP flags from one test's RTR
cycle don't bleed into the next test's initial UA assertion.

Fixes testECAOpaqueRtr_Hybrid and any other non-RTR test that ran
after an RTR test with the same user key.
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
TestsPassed ☑️Skipped ⚠️Failed ❌️
AuthFlowTester UI Test Results all101 ran53 ✅4 ⚠️44 ❌
TestResult
AuthFlowTester UI Test Results all
AuthFlowTesterUITests.xctest
RefreshTokenMigrationTests.testMigrateCAToECA()❌ failure
RefreshTokenMigrationTests.testMigrateCAUserAgentToBeaconWebServer()❌ failure
RefreshTokenMigrationTests.testMigrateCAToBeaconAndBack()❌ failure
RefreshTokenMigrationTests.testMigrateBeaconOpaqueToJWTAndBack()❌ failure
RefreshTokenMigrationTests.testMigrateOneUserOnly()❌ failure
RefreshTokenMigrationTests.testMigrateCA_AddMoreScopes()❌ failure
RefreshTokenMigrationTests.testMigrateCAToBeacon()❌ failure
RefreshTokenMigrationTests.testMigrateBeaconToCA()❌ failure
RefreshTokenMigrationTests.testMigrateBeacon_AddMoreScopes()❌ failure
ForceAdvancedAuthTests.testForceAdvancedAuth_Disabled_BackAndGearStillPresent()❌ failure
ForceAdvancedAuthTests.testForceAdvancedAuth_MyDomainRegularHost_RemainsBrowser()❌ failure
ForceAdvancedAuthTests.testForceAdvancedAuth_Disabled_StandardServer_UsesInAppWebView()❌ failure
ForceAdvancedAuthTests.testForceAdvancedAuth_AddAdditionalUser_BackButtonAccessible()❌ failure
LegacyLoginTests.testCAOpaque_DefaultScopes_WebServerFlow_InAppWebView()❌ failure
LegacyLoginTests.testCAOpaque_AllScopes_WebServerFlow_InAppWebView()❌ failure
LegacyLoginTests.testCAOpaque_SubsetScopes_WebServerFlow()❌ failure
LegacyLoginTests.testCAOpaque_DefaultScopes_WebServerFlow()❌ failure
BeaconLoginTests.testBeaconOpaque_SubsetScopes()❌ failure
BeaconLoginTests.testBeaconJwt_SubsetScopes()❌ failure
ECALoginTests.testECAJwt_SubsetScopes()❌ failure
ECALoginTests.testECAOpaque_SubsetScopes()❌ failure
ECALoginTests.testDynamicConfigurationWithInvalidClientId()❌ failure
RefreshTokenMigrationWithRestartTests.testMigrateBeaconScopeAddition_WithRestart()❌ failure
RefreshTokenMigrationWithRestartTests.testMigrateScopeAddition_WithRestart()❌ failure
RefreshTokenMigrationWithRestartTests.testMigrateMultipleUsers_WithRestart()❌ failure
RefreshTokenMigrationWithRestartTests.testMigrateCAToBeacon_WithRestart()❌ failure
RefreshTokenMigrationWithRestartTests.testMigrateCAToECA_WithRestart()❌ failure
MultiUserLoginTests.testFirstStatic_SecondDynamic_DifferentApps()❌ failure
MultiUserLoginTests.testBothStatic_SameApp_DifferentScopes()❌ failure
MultiUserLoginTests.testBothStatic_SameApp_SameScopes()❌ failure
MultiUserLoginTests.testBothStatic_DifferentApps()❌ failure
DPoPLoginTests.test_givenTwoDPoPUsers_whenSwitchAndRefresh_thenTokensAndNoncesAreIsolated()❌ failure
DPoPLoginTests.test_givenDPoPUser_whenMigrateToDPoPRtr_thenRefreshTokenRotationEnabled()❌ failure
DPoPLoginTests.test_givenDPoPRtrNoHybrid_whenLogin_thenRefreshTokenRotatesAndDPoPBindingHolds()❌ failure
DPoPLoginTests.test_givenDPoPECA_whenAdminLogin_thenDPoPBindingWorksThroughSafariVC()❌ failure
DPoPLoginTests.test_givenDPoPNoHybrid_whenLogin_thenTokenTypeIsDPoPAndRefreshWorks()❌ failure
DPoPLoginTests.test_givenDPoPUserWithSubsetScopes_whenMigrateToAllScopes_thenDPoPBindingPreserved()❌ failure
DPoPLoginTests.test_givenDPoPHybrid_whenLogin_thenTokenTypeIsDPoPAndRefreshWorks()❌ failure
RTRLoginTests.testECAOpaqueRtr_Hybrid_WithRestart()❌ failure
LoginWithRestartTests.testWelcomeDiscovery_WithRestart()❌ failure
WelcomeLoginTests.testWelcomeDiscovery_AdvancedAuthLoginHost_DynamicConfig()❌ failure
WelcomeLoginTests.testWelcomeDiscovery_AdvancedAuthLoginHost()❌ failure
WelcomeLoginTests.testWelcomeDiscovery_RegularAuthLoginHost_DynamicConfig()❌ failure
WelcomeLoginTests.testWelcomeDiscovery_RegularAuthLoginHost()❌ failure

1. assertRevokeAndRefreshWorks default expectAdvancedAuth: true → false
   The old default assumed nil forceAdvancedAuthentication meant browser
   (BW) login. After the forceAdvancedAuthentication != false → == true
   fix, nil means in-app WebView (no BW). DPoP and RTR callers that omit
   expectAdvancedAuth were getting spurious BW assertions.

2. setForceAdvancedAuthentication staticAppConfigName now optional
   testForceAdvancedAuth_Disabled_StandardServer_UsesInAppWebView was
   importing ecaOpaque (a My-Domain ECA) as the consumer key while
   testing against login.salesforce.com, producing invalid_client_id and
   no login form. Default bootconfig.plist key is valid on the standard
   server; no config override needed for this test.
@wmathurin
wmathurin requested review from brandonpage and sfdctaka and removed request for brandonpage August 5, 2026 19:38
restartAndValidateUser's job is to restart and validate session
persistence. The revoke/refresh RTR check is a separate concern;
callers that need it pass assertRevokeAndRefreshWorks(isRtr: true)
explicitly, consistent with DPoP tests and the skipped
testECAJwtRtr_Hybrid_WithRestart.
…= true)

Matches Android's AuthFlowTest where forceAdvancedAuthentication is a
non-nullable Boolean defaulting to true (the SDK default). Removes the
nil path that was silently overriding sdk_forceAdvancedAuthentication
to false via configureLoginOptions (?? false). Now nil is impossible:
callers either rely on the true default (browser/BW) or pass false
explicitly to exercise the WebView path.

Also restores assertRevokeAndRefreshWorks default expectAdvancedAuth
back to true, consistent with the forceAdvancedAuthentication default.
…cope tests

Three new tests mirror testCAOpaque_{Default,Subset,All}Scopes_WebServerFlow
but with forceAdvancedAuthentication: false to exercise the web server
OAuth flow through the in-app WebView rather than the external browser.
@sfdctaka

sfdctaka commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

This file is not included in the PR.
image

Both -init and resetForUITesting set the same auth flags. Extract a
private resetAuthFlags instance method so the values are defined once
and can't diverge. simulatedDomainDiscoveryResult is reset only in
resetForUITesting (it's a test-only property absent from -init).
@wmathurin

Copy link
Copy Markdown
Contributor Author

This file is not included in the PR. image

Good catch. The assert was there before this PR. Fixed PR description.

@sfdctaka sfdctaka left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM!

…dValidate

Migration is a silent token exchange — the browser is never opened, so
BW is absent from the post-migration UA regardless of how the initial
login was done. Defaulting to false avoids spurious BW assertions in
all migration tests. Remove now-redundant explicit false from two
DPoPLoginTests callers.
SFOAuthTypeRefreshTokenMigration was hitting the generic else branch in
finalizeAuthCompletion that unregisters BW. Migration exchanges the
consumer key/token but does not change how the user originally
authenticated, so the existing per-user BW flag should be preserved.

Add an explicit guard for SFOAuthTypeRefreshTokenMigration that is a
no-op, leaving the flag intact. Normal token refreshes (SFOAuthTypeRefresh)
and other non-browser auth types continue to clear BW as designed.

Update migrateAndValidate default to forceAdvancedAuthentication: true
(BW is now carried through) and add explicit false for the two
user-agent-flow migration tests where BW was never registered.
Comment on lines +2185 to +2187
} else if (completedAuthType == SFOAuthTypeRefreshTokenMigration) {
// Migration exchanges the token but does not change how the user originally
// authenticated. Preserve the existing per-user BW flag rather than clearing it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Good catch.

…heck

The RT flag persists on disk after launchLoginAndValidate completes an
RTR cycle. restartAndValidateUser must tell validateUser to expect RT
in the post-restart UA; otherwise validateUserAgent asserts RT absent
and fails. isRtr is forwarded to validateUser only — the separate
assertRevokeAndRefreshWorks(isRtr: true) call is unchanged.
…, for WebView modality

testForceAdvancedAuth_Disabled_StandardServer_UsesInAppWebView was asserting
isShowingInAppLoginForm() (waits 30s for a text field inside the WKWebView) after
disabling advanced auth and restarting auth against login.salesforce.com. The
consumer key in bootconfig.plist is an org-specific test CA that triggers
invalid_client_id on the standard server, so the login page never renders its
username text field — the assertion timed out.

The test's intent is modality detection: confirm the SDK chose the in-app WebView
(SFLoginViewController) over the external browser (ASWebAuthenticationSession). For
that it is sufficient to observe the "Log In" navigation bar, which SFLoginViewController
presents immediately — before the WKWebView has finished loading any page.

Add isShowingLoginViewController() (checks the "Log In" nav bar) to LoginPageObject
and expose it in BaseAuthFlowTester. Switch the test to use it instead of
isShowingInAppLoginForm(), which remains available for callers that load a real page.
…ncedAuthUser_HasBWFlag_RegularAuthUser_DoesNot

launchLoginAndValidate defaults to forceAdvancedAuthentication: true, so User A
was logging in via the browser (BW registered). The test then asserted BW absent
for User A while two users are logged in, causing XCTAssertFalse to fail.

Pass forceAdvancedAuthentication: false for User A so the SDK uses the in-app
WebView — the .regularAuth host does not opt into native browser auth via its
auth config, so disabling the process-global flag is sufficient to use the WebView.
@wmathurin

Copy link
Copy Markdown
Contributor Author

A lot of tests fixed. Will further validate in the other PR.

@wmathurin
wmathurin merged commit eb62dd4 into forcedotcom:dev Aug 6, 2026
21 of 24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants