From a5dea7f06bdf954a7853ad4d6c388f1af7bc718d Mon Sep 17 00:00:00 2001 From: Wolfgang Mathurin Date: Tue, 10 Mar 2026 15:29:32 -0700 Subject: [PATCH 01/10] Splitting test suites LegacyLoginTests into LegacyLoginTests and LegacyLoginTestsNotHybrid --- .../Tests/ECALoginTests.swift | 2 +- .../Tests/LegacyLoginTests.swift | 58 ++++++------------- .../Tests/LegacyLoginTestsNotHybrid.swift | 47 +++++++++++++++ .../AuthFlowTesterUITests/overview.md | 28 +++++---- 4 files changed, 83 insertions(+), 52 deletions(-) create mode 100644 native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/LegacyLoginTestsNotHybrid.swift diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/ECALoginTests.swift b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/ECALoginTests.swift index 3ad8529ab1..4ae83a57f1 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/ECALoginTests.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/ECALoginTests.swift @@ -59,7 +59,7 @@ class ECALoginTests: BaseAuthFlowTester { } /// Login with ECA JWT using subset of scopes and web server flow. - func testECAJwt_SubsetScopes_NotHybrid() throws { + func testECAJwt_SubsetScopes() throws { launchLoginAndValidate(staticAppConfigName: .ecaJwt, staticScopeSelection: .subset) } diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/LegacyLoginTests.swift b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/LegacyLoginTests.swift index 6d189d7af2..40c7fd8ad8 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/LegacyLoginTests.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/LegacyLoginTests.swift @@ -30,79 +30,55 @@ import XCTest /// Tests for legacy login flows including: /// - Connected App (CA) configurations (traditional OAuth connected apps) /// - User agent flow tests -/// - Non-hybrid flow tests /// - Default, subset, and all scope variations +/// - Hybrid flow (default behavior) +/// +/// For non-hybrid flow tests, see LegacyLoginTestsNotHybrid which extends this class. /// /// NB: Tests use the first user from ui_test_config.json /// class LegacyLoginTests: BaseAuthFlowTester { + // MARK: - Test Configuration + + /// Returns whether to use hybrid flow for tests. + /// Subclasses can override this to test non-hybrid flows. + func useHybridFlow() -> Bool { + return true + } + // MARK: - CA Web Server Flow Tests /// Login with CA opaque using default scopes and web server flow. func testCAOpaque_DefaultScopes_WebServerFlow() throws { - launchLoginAndValidate(staticAppConfigName: .caOpaque) + launchLoginAndValidate(staticAppConfigName: .caOpaque, useHybridFlow: useHybridFlow()) } /// Login with CA opaque using subset of scopes and web server flow. func testCAOpaque_SubsetScopes_WebServerFlow() throws { - launchLoginAndValidate(staticAppConfigName: .caOpaque, staticScopeSelection: .subset, useHybridFlow: false) + launchLoginAndValidate(staticAppConfigName: .caOpaque, staticScopeSelection: .subset, useHybridFlow: useHybridFlow()) } /// Login with CA opaque using all scopes and web server flow. func testCAOpaque_AllScopes_WebServerFlow() throws { - launchLoginAndValidate(staticAppConfigName: .caOpaque, staticScopeSelection: .all) - } - - // MARK: - CA Non-hybrid Web Server Flow Tests - - /// Login with CA opaque using default scopes and (non-hybrid) web server flow. - func testCAOpaque_DefaultScopes_WebServerFlow_NotHybrid() throws { - launchLoginAndValidate(staticAppConfigName: .caOpaque, useHybridFlow: false) - } - - /// Login with CA opaque using subset of scopes and (non-hybrid) web server flow. - func testCAOpaque_SubsetScopes_WebServerFlow_NotHybrid() throws { - launchLoginAndValidate(staticAppConfigName: .caOpaque, staticScopeSelection: .subset, useHybridFlow: false) - } - - /// Login with CA opaque using all scopes and (non-hybrid) web server flow. - func testCAOpaque_AllScopes_WebServerFlow_NotHybrid() throws { - launchLoginAndValidate(staticAppConfigName: .caOpaque, staticScopeSelection: .all, useHybridFlow: false) + launchLoginAndValidate(staticAppConfigName: .caOpaque, staticScopeSelection: .all, useHybridFlow: useHybridFlow()) } // MARK: - CA User Agent Flow Tests /// Login with CA opaque using default scopes and user agent flow. func testCAOpaque_DefaultScopes_UserAgentFlow() throws { - launchLoginAndValidate(staticAppConfigName: .caOpaque, useWebServerFlow: false) + launchLoginAndValidate(staticAppConfigName: .caOpaque, useWebServerFlow: false, useHybridFlow: useHybridFlow()) } /// Login with CA opaque using subset of scopes and user agent flow. func testCAOpaque_SubsetScopes_UserAgentFlow() throws { - launchLoginAndValidate(staticAppConfigName: .caOpaque, staticScopeSelection: .subset, useWebServerFlow: false) + launchLoginAndValidate(staticAppConfigName: .caOpaque, staticScopeSelection: .subset, useWebServerFlow: false, useHybridFlow: useHybridFlow()) } /// Login with CA opaque using all scopes and user agent flow. func testCAOpaque_AllScopes_UserAgentFlow() throws { - launchLoginAndValidate(staticAppConfigName: .caOpaque, staticScopeSelection: .all, useWebServerFlow: false) - } - - // MARK: - CA Non-hybrid User Agent Flow Tests - - /// Login with CA opaque using default scopes and (non-hybrid) user agent flow. - func testCAOpaque_DefaultScopes_UserAgentFlow_NotHybrid() throws { - launchLoginAndValidate(staticAppConfigName: .caOpaque, useWebServerFlow: false, useHybridFlow: false) - } - - /// Login with CA opaque using subset of scopes and (non-hybrid) user agent flow. - func testCAOpaque_SubsetScopes_UserAgentFlow_NotHybrid() throws { - launchLoginAndValidate(staticAppConfigName: .caOpaque, staticScopeSelection: .subset, useWebServerFlow: false, useHybridFlow: false) - } - - /// Login with CA opaque using all scopes and (non-hybrid) user agent flow. - func testCAOpaque_AllScopes_UserAgentFlow_NotHybrid() throws { - launchLoginAndValidate(staticAppConfigName: .caOpaque, staticScopeSelection: .all, useWebServerFlow: false, useHybridFlow: false) + launchLoginAndValidate(staticAppConfigName: .caOpaque, staticScopeSelection: .all, useWebServerFlow: false, useHybridFlow: useHybridFlow()) } } diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/LegacyLoginTestsNotHybrid.swift b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/LegacyLoginTestsNotHybrid.swift new file mode 100644 index 0000000000..271b24042a --- /dev/null +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/LegacyLoginTestsNotHybrid.swift @@ -0,0 +1,47 @@ +/* + LegacyLoginTestsNotHybrid.swift + AuthFlowTesterUITests + + Copyright (c) 2026-present, salesforce.com, inc. All rights reserved. + + Redistribution and use of this software in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list of conditions + and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of + conditions and the following disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to + endorse or promote products derived from this software without specific prior written + permission of salesforce.com, inc. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import XCTest + +/// Tests for legacy login flows using non-hybrid authentication. +/// Extends LegacyLoginTests and runs the same test cases with useHybridFlow set to false. +/// +/// Non-hybrid flow means the app does not receive front-door session cookies (SIDs) +/// for Lightning, Visualforce, and Content domains during authentication. +/// +/// NB: Tests use the first user from ui_test_config.json +/// +class LegacyLoginTestsNotHybrid: LegacyLoginTests { + + // MARK: - Test Configuration Override + + /// Returns false to test non-hybrid flows. + /// Overrides the parent class implementation. + override func useHybridFlow() -> Bool { + return false + } +} diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/overview.md b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/overview.md index 913e4248a4..9be6be27a4 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/overview.md +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/overview.md @@ -6,7 +6,8 @@ This document provides an overview of all UI tests in the AuthFlowTester test su | Class | Description | |-------|-------------| -| `LegacyLoginTests` | Tests for legacy login flows (CA, user agent flow, non-hybrid flow) with default, subset, and all scopes | +| `LegacyLoginTests` | Tests for legacy login flows (CA, user agent flow, hybrid flow) with default, subset, and all scopes | +| `LegacyLoginTestsNotHybrid` | Tests for legacy login flows (CA, user agent flow, non-hybrid flow) - extends LegacyLoginTests | | `ECALoginTests` | Tests for External Client App (ECA) login flows | | `BeaconLoginTests` | Tests for Beacon app login flows (using regular_auth login host) | | `AdvancedAuthBeaconLoginTests` | Tests for Beacon app login flows (using advanced_auth login host) | @@ -20,24 +21,31 @@ This document provides an overview of all UI tests in the AuthFlowTester test su ## Login Tests -### LegacyLoginTests (12 tests) +### LegacyLoginTests (6 tests) -Tests for Connected App (CA) configurations with default, subset, and all scopes, including user agent flow and non-hybrid flow options. +Tests for Connected App (CA) configurations with default, subset, and all scopes using hybrid authentication flow. Tests both web server and user agent OAuth flows. | Test Name | App Config | Scopes | Flow | Hybrid | |-----------|------------|--------|------|--------| | `testCAOpaque_DefaultScopes_WebServerFlow` | CA Opaque | Default | Web Server | Yes | -| `testCAOpaque_SubsetScopes_WebServerFlow` | CA Opaque | Subset | Web Server | No | +| `testCAOpaque_SubsetScopes_WebServerFlow` | CA Opaque | Subset | Web Server | Yes | | `testCAOpaque_AllScopes_WebServerFlow` | CA Opaque | All | Web Server | Yes | -| `testCAOpaque_DefaultScopes_WebServerFlow_NotHybrid` | CA Opaque | Default | Web Server | No | -| `testCAOpaque_SubsetScopes_WebServerFlow_NotHybrid` | CA Opaque | Subset | Web Server | No | -| `testCAOpaque_AllScopes_WebServerFlow_NotHybrid` | CA Opaque | All | Web Server | No | | `testCAOpaque_DefaultScopes_UserAgentFlow` | CA Opaque | Default | User Agent | Yes | | `testCAOpaque_SubsetScopes_UserAgentFlow` | CA Opaque | Subset | User Agent | Yes | | `testCAOpaque_AllScopes_UserAgentFlow` | CA Opaque | All | User Agent | Yes | -| `testCAOpaque_DefaultScopes_UserAgentFlow_NotHybrid` | CA Opaque | Default | User Agent | No | -| `testCAOpaque_SubsetScopes_UserAgentFlow_NotHybrid` | CA Opaque | Subset | User Agent | No | -| `testCAOpaque_AllScopes_UserAgentFlow_NotHybrid` | CA Opaque | All | User Agent | No | + +### LegacyLoginTestsNotHybrid (6 tests) + +Tests for Connected App (CA) configurations with non-hybrid authentication flow. Extends `LegacyLoginTests` and runs the same tests with `useHybridFlow` set to false. Non-hybrid flow means the app does not receive front-door session cookies (SIDs) during authentication. + +| Test Name | App Config | Scopes | Flow | Hybrid | +|-----------|------------|--------|------|--------| +| `testCAOpaque_DefaultScopes_WebServerFlow` | CA Opaque | Default | Web Server | No | +| `testCAOpaque_SubsetScopes_WebServerFlow` | CA Opaque | Subset | Web Server | No | +| `testCAOpaque_AllScopes_WebServerFlow` | CA Opaque | All | Web Server | No | +| `testCAOpaque_DefaultScopes_UserAgentFlow` | CA Opaque | Default | User Agent | No | +| `testCAOpaque_SubsetScopes_UserAgentFlow` | CA Opaque | Subset | User Agent | No | +| `testCAOpaque_AllScopes_UserAgentFlow` | CA Opaque | All | User Agent | No | ### ECALoginTests (6 tests) From c12489e196ec034d82562d14398951f7594d007f Mon Sep 17 00:00:00 2001 From: Wolfgang Mathurin Date: Tue, 10 Mar 2026 17:27:29 -0700 Subject: [PATCH 02/10] New test: migrate CA (user agent) to Beacon (web server) --- .../SessionDetailViewController.swift | 5 ++- .../AuthFlowTesterMainPageObject.swift | 43 +++++++++++++++---- .../Tests/RefreshTokenMigrationTests.swift | 16 +++++++ .../Util/BaseAuthFlowTester.swift | 28 ++++++------ .../AuthFlowTesterUITests/overview.md | 16 ++++++- 5 files changed, 85 insertions(+), 23 deletions(-) diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTester/ViewControllers/SessionDetailViewController.swift b/native/SampleApps/AuthFlowTester/AuthFlowTester/ViewControllers/SessionDetailViewController.swift index 178adf6852..1e1e810c15 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTester/ViewControllers/SessionDetailViewController.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTester/ViewControllers/SessionDetailViewController.swift @@ -117,7 +117,10 @@ struct SessionDetailView: View { } .sheet(isPresented: $showMigrateRefreshToken) { NavigationView { - VStack { + VStack(spacing: 20) { + AuthFlowTypesView() + .padding(.top) + BootConfigEditor( title: "New App Configuration", buttonLabel: "Migrate refresh token", diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/AuthFlowTesterMainPageObject.swift b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/AuthFlowTesterMainPageObject.swift index fcdcbdcea8..30dde2a48a 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/AuthFlowTesterMainPageObject.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/AuthFlowTesterMainPageObject.swift @@ -266,20 +266,24 @@ class AuthFlowTesterMainPageObject { tap(swithToUserButton()) } - func changeAppConfig(appConfig: AppConfig, scopesToRequest: String = "") -> Bool { + func changeAppConfig(appConfig: AppConfig, scopesToRequest: String = "", useWebServerFlow: Bool, useHybridFlow: Bool) -> Bool { // Tap Change Key button to open the sheet tap(bottomBarChangeKeyButton()) - + + // Set the auth flow switches + setSwitchField(useWebServerFlowSwitch(), value: useWebServerFlow) + setSwitchField(useHybridSwitch(), value: useHybridFlow) + // Build JSON config and import it let configJSON = buildConfigJSON(consumerKey: appConfig.consumerKey, redirectUri: appConfig.redirectUri, scopes: scopesToRequest) importConfig(configJSON) - + // Tap the migrate button tap(migrateRefreshTokenButton()) - + // Tap the allow button if it appears tapIfPresent(allowButton()) - + let alert = app.alerts["Migration Error"] if (alert.waitForExistence(timeout: UITestTimeouts.long)) { alert.buttons["OK"].tap() @@ -433,7 +437,17 @@ class AuthFlowTesterMainPageObject { private func swithToUserButton() -> XCUIElement { return app.buttons["Switch to User"] } - + + // Auth flow switches + + private func useWebServerFlowSwitch() -> XCUIElement { + return app.switches["Use Web Server Flow"] + } + + private func useHybridSwitch() -> XCUIElement { + return app.switches["Use Hybrid Flow"] + } + // MARK: - Actions private func tap(_ element: XCUIElement) { @@ -450,7 +464,7 @@ class AuthFlowTesterMainPageObject { private func setTextField(_ textField: XCUIElement, value: String) { _ = textField.waitForExistence(timeout: UITestTimeouts.long) textField.tap() - + // Clear any existing text if let currentValue = textField.value as? String, !currentValue.isEmpty { textField.tap() @@ -460,10 +474,21 @@ class AuthFlowTesterMainPageObject { textField.typeText(XCUIKeyboardKey.delete.rawValue) } } - + textField.typeText(value) } - + + private func setSwitchField(_ switchField: XCUIElement, value: Bool) { + _ = switchField.waitForExistence(timeout: UITestTimeouts.long) + + // Switch values are "0" (off) or "1" (on) in XCTest + let currentValue = (switchField.value as? String) == "1" + + if currentValue != value { + tap(switchField) + } + } + // MARK: - Data Extraction Methods func getUserCredentials() -> UserCredentialsData { diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/RefreshTokenMigrationTests.swift b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/RefreshTokenMigrationTests.swift index e644e5e425..58b0e28407 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/RefreshTokenMigrationTests.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/RefreshTokenMigrationTests.swift @@ -104,6 +104,22 @@ class RefreshTokenMigrationTests: BaseAuthFlowTester { ) } + // Migrate from CA (user agent) to Beacon (web server) + func testMigrateCAUserAgentToBeaconWebServer() throws { + launchAndLogin( + loginHost: .regularAuth, + user:.second, + staticAppConfigName: .caOpaque, + useWebServerFlow: false + ) + migrateAndValidate( + loginHost: .regularAuth, + staticAppConfigName: .caOpaque, + migrationAppConfigName: .beaconOpaque, + migrationUseWebServerFlow: true + ) + } + // Migrate from Beacon to CA func testMigrateBeaconToCA() throws { launchAndLogin( diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Util/BaseAuthFlowTester.swift b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Util/BaseAuthFlowTester.swift index 39b49294f9..998bca47b7 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Util/BaseAuthFlowTester.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Util/BaseAuthFlowTester.swift @@ -422,28 +422,30 @@ class BaseAuthFlowTester: XCTestCase { /// - staticScopeSelection: The scope selection for static configuration. Defaults to `.empty`. /// - migrationAppConfigName: The app configuration to migrate to. /// - migrationScopeSelection: The scope selection for the migration target. Defaults to `.empty`. - /// - useWebServerFlow: Whether to use web server OAuth flow. Defaults to `true`. - /// - useHybridFlow: Whether to use hybrid authentication flow. Defaults to `true`. + /// - migrationUseWebServerFlow: Whether to use web server OAuth flow for migration. Defaults to `true`. + /// - migrationUseHybridFlow: Whether to use hybrid authentication flow for migration. Defaults to `true`. func migrateAndValidate( loginHost: KnownLoginHostConfig, staticAppConfigName: KnownAppConfig, staticScopeSelection: ScopeSelection = .empty, migrationAppConfigName: KnownAppConfig, migrationScopeSelection: ScopeSelection = .empty, - useWebServerFlow: Bool = true, - useHybridFlow: Bool = true + migrationUseWebServerFlow: Bool = true, + migrationUseHybridFlow: Bool = true, ) { // Get original credentials before migration let originalUserCredentials = mainPage.getUserCredentials() // Get current user let user = getKnownUserConfig(loginHost: loginHost, byUsername: originalUserCredentials.username) - - + + // Migrate refresh token migrateRefreshToken( appConfigName: migrationAppConfigName, - scopeSelection: migrationScopeSelection + scopeSelection: migrationScopeSelection, + useWebServerFlow: migrationUseWebServerFlow, + useHybridFlow: migrationUseHybridFlow ) // Validate after migration @@ -454,8 +456,8 @@ class BaseAuthFlowTester: XCTestCase { staticScopeSelection: staticScopeSelection, userAppConfigName: migrationAppConfigName, userScopeSelection: migrationScopeSelection, - useWebServerFlow: useWebServerFlow, - useHybridFlow: useHybridFlow + useWebServerFlow: migrationUseWebServerFlow, + useHybridFlow: migrationUseHybridFlow ) // Making sure the refresh token changed @@ -599,12 +601,14 @@ class BaseAuthFlowTester: XCTestCase { private func migrateRefreshToken( appConfigName: KnownAppConfig, - scopeSelection: ScopeSelection + scopeSelection: ScopeSelection, + useWebServerFlow: Bool, + useHybridFlow: Bool ) { let appConfig = getAppConfig(named: appConfigName) let scopesToRequest = testConfig.getScopesToRequest(for: appConfig, scopeSelection) - - XCTAssert(mainPage.changeAppConfig(appConfig: appConfig, scopesToRequest: scopesToRequest), "Failed to migrate refresh token") + + XCTAssert(mainPage.changeAppConfig(appConfig: appConfig, scopesToRequest: scopesToRequest, useWebServerFlow: useWebServerFlow, useHybridFlow: useHybridFlow), "Failed to migrate refresh token") } /// Asserts that the main page is loaded and showing. diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/overview.md b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/overview.md index 9be6be27a4..7fc2531abc 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/overview.md +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/overview.md @@ -118,7 +118,7 @@ Tests for verifying that user sessions persist across app restarts. Includes CA, ### RefreshTokenMigrationTests (9 tests) -Tests for migrating refresh tokens between different app configurations without re-authentication. +Tests for migrating refresh tokens between different app configurations without re-authentication. Tests can optionally specify the OAuth flow type (web server vs user agent) and hybrid flow setting to use during migration. | Test Name | Original App | Migration App | Scope Change | Multi-User | |-----------|--------------|---------------|--------------|------------| @@ -207,6 +207,20 @@ Tests for login scenarios with two users using various configurations, including | **Opaque** | Opaque access tokens | | **JWT** | JSON Web Token based access tokens | +### OAuth Flow Types + +| Flow Type | Description | +|-----------|-------------| +| **Web Server Flow** | OAuth 2.0 web server flow (authorization code flow) - default | +| **User Agent Flow** | OAuth 2.0 user agent flow (implicit flow) | + +### Hybrid Flow + +| Setting | Description | +|---------|-------------| +| **Hybrid** | Authentication includes front-door session cookies (SIDs) for Lightning, Visualforce, and Content domains | +| **Non-Hybrid** | Authentication without front-door session cookies | + ## Login Hosts The test suite supports testing against different Salesforce org configurations with different authentication mechanisms. The login host configuration is specified in `ui_test_config.json` under the `loginHosts` array. From bbfdb40006ab0ed1a0924fd37c6f170cd870bce1 Mon Sep 17 00:00:00 2001 From: Wolfgang Mathurin Date: Tue, 10 Mar 2026 19:59:44 -0700 Subject: [PATCH 03/10] Adding new tests: - login with invalid client id in dynamic configuration - login with invalid scope in dynamic configuration --- .../AuthFlowTester.xcodeproj/project.pbxproj | 1 - .../PageObjects/LoginPageObject.swift | 16 ++++++ .../Tests/ECALoginTests.swift | 13 +++++ .../Util/BaseAuthFlowTester.swift | 33 +++++++++--- .../Util/UITestConfigUtils.swift | 51 +++++++++++++++---- .../AuthFlowTesterUITests/overview.md | 26 ++++++---- 6 files changed, 112 insertions(+), 28 deletions(-) diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTester.xcodeproj/project.pbxproj b/native/SampleApps/AuthFlowTester/AuthFlowTester.xcodeproj/project.pbxproj index 912cf14a06..024d21476d 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTester.xcodeproj/project.pbxproj +++ b/native/SampleApps/AuthFlowTester/AuthFlowTester.xcodeproj/project.pbxproj @@ -87,7 +87,6 @@ isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( overview.md, - ui_test_config.json, ); target = 4F8E4AF02ED13CE800DA7B7A /* AuthFlowTesterUITests */; }; diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/LoginPageObject.swift b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/LoginPageObject.swift index 1d219a95d2..760027d8d9 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/LoginPageObject.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/LoginPageObject.swift @@ -50,6 +50,14 @@ class LoginPageObject { return advancedAuthCloseButton().waitForExistence(timeout: UITestTimeouts.short) } + func isShowingInvalidClientIdError() -> Bool { + return invalidClientIdText().waitForExistence(timeout: UITestTimeouts.long) + } + + func isShowingUnexpectedOauthError() -> Bool { + return unexpectedOauthErrorText().waitForExistence(timeout: UITestTimeouts.long) + } + func closeAdvancedAuth() -> Void { tap(advancedAuthCloseButton()) tap(hostRow(host: "Production")) @@ -174,6 +182,14 @@ class LoginPageObject { private func toolbarDoneButton() -> XCUIElement { return app.toolbars["Toolbar"].buttons["Done"] } + + private func invalidClientIdText() -> XCUIElement { + return app.staticTexts["error=invalid_client_id&error_description=client%20identifier%20invalid"] + } + + private func unexpectedOauthErrorText() -> XCUIElement { + return app.staticTexts["OAUTH_APPROVAL_ERROR_GENERIC : An unexpected error has occurred during authentication. Please try again."] + } private func usernameFieldLabel() -> XCUIElement { return app.staticTexts["Username"] diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/ECALoginTests.swift b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/ECALoginTests.swift index 4ae83a57f1..cd2b0b2520 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/ECALoginTests.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/ECALoginTests.swift @@ -67,4 +67,17 @@ class ECALoginTests: BaseAuthFlowTester { func testECAJwt_AllScopes() throws { launchLoginAndValidate(staticAppConfigName: .ecaJwt, staticScopeSelection: .all) } + + // MARK: - Negative testing + + /// Login with invalid client id in dynamic configuration + func testDynamicConfigurationWithInvalidClientId() throws { + launchAndLogin(loginHost: .regularAuth, user: .first, staticAppConfigName: .ecaOpaque, dynamicAppConfigName: .invalid) + } + + /// Login with invalid scope in dynamic configuration + func testDynamicConfigurationWithInvalidScope() throws { + launchAndLogin(loginHost: .regularAuth, user: .first, staticAppConfigName: .ecaOpaque, dynamicAppConfigName: .ecaJwt, dynamicScopeSelection: .invalid) + } + } diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Util/BaseAuthFlowTester.swift b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Util/BaseAuthFlowTester.swift index 998bca47b7..058ffd96d5 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Util/BaseAuthFlowTester.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Util/BaseAuthFlowTester.swift @@ -34,6 +34,7 @@ class BaseAuthFlowTester: XCTestCase { // App Pages private var loginPage: LoginPageObject! private var mainPage: AuthFlowTesterMainPageObject! + private var logoutAtTearDown: Bool = true // Test configuration private let testConfig = UITestConfigUtils.shared @@ -44,7 +45,9 @@ class BaseAuthFlowTester: XCTestCase { } override func tearDown() { - logout() + if (logoutAtTearDown) { + logout() + } super.tearDown() } @@ -125,15 +128,31 @@ class BaseAuthFlowTester: XCTestCase { let loginHostToUse = useWelcomeDiscovery ? "welcome.salesforce.com/discovery" : hostConfig.urlNoProtocol loginPage.configureLoginHost(host: loginHostToUse) + // Invalid app config + if (dynamicAppConfigName == .invalid || (dynamicAppConfigName == nil && staticAppConfigName == .invalid)) { + XCTAssertTrue(loginPage.isShowingInvalidClientIdError(), "Login page should show invalid client id error") + logoutAtTearDown = false + return + } + + // Welcome login if (useWelcomeDiscovery) { XCTAssertTrue(loginPage.hasFilledUsernameField(username: userConfig.username), "Login page should have pre-filled username") loginPage.performWelcomeLogin(password: userConfig.password) - } else { - if (loginHost == .regularAuth) { - loginPage.performLogin(username: userConfig.username, password: userConfig.password) - } else { - loginPage.performAdvancedLogin(username: userConfig.username, password: userConfig.password) - } + } + // Regular auth + else if (loginHost == .regularAuth) { + loginPage.performLogin(username: userConfig.username, password: userConfig.password) + } + // Advanced auth + else if (loginHost == .advancedAuth) { + loginPage.performAdvancedLogin(username: userConfig.username, password: userConfig.password) + } + + // Invalid scope + if (dynamicScopeSelection == .invalid || (dynamicAppConfig == nil && staticScopeSelection == .invalid)) { + XCTAssertTrue(loginPage.isShowingUnexpectedOauthError(), "Screen should show OAuth Error") + logoutAtTearDown = false } } diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Util/UITestConfigUtils.swift b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Util/UITestConfigUtils.swift index cba9d74bf9..9cfc1f72a5 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Util/UITestConfigUtils.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Util/UITestConfigUtils.swift @@ -66,9 +66,10 @@ enum TestConfigError: Error, CustomStringConvertible { // MARK: - ScopeSelection enum ScopeSelection { - case empty // will not send scopes param - should be granted all the scopes defined on the server - case all // will send all the scopes defined in ui_test_config.json - case subset // will send a subset of the scopes defined in ui_test_config.json + case empty // Will not send scopes param - should be granted all the scopes defined on the server + case all // Will send all the scopes defined in ui_test_config.json + case subset // Will send a subset of the scopes defined in ui_test_config.json (all except sfap_api) + case invalid // Will send "invalid_scope" for negative testing scenarios } // MARK: - Configured Users @@ -97,6 +98,7 @@ enum KnownAppConfig: String { case beaconJwt = "beacon_jwt" case caOpaque = "ca_opaque" case caJwt = "ca_jwt" + case invalid = "invalid" // Returns hard-coded invalid app config for negative testing } // MARK: - Configuration Models @@ -293,8 +295,24 @@ class UITestConfigUtils { } } - /// Returns an app by its name or throws an error if not found or not configured + /// Returns an app by its name or throws an error if not found or not configured. + /// + /// - Parameter name: The known app configuration name to retrieve. + /// - Returns: The app configuration for the specified name. + /// - Throws: `TestConfigError.appNotFound` if the app doesn't exist in ui_test_config.json, + /// or `TestConfigError.appNotConfigured` if the app has an empty consumer key. + /// - Note: For `.invalid`, returns a hard-coded invalid configuration for negative testing scenarios. func getApp(named name: KnownAppConfig) throws -> AppConfig { + // Return hard-coded invalid app config for negative testing scenarios + if name == .invalid { + return AppConfig( + name: "invalid", + consumerKey: "invalid_consumer_key", + redirectUri: "invalid://callback", + scopes: "invalid_scope" + ) + } + guard let app = config?.apps.first(where: { $0.name == name.rawValue }) else { throw TestConfigError.appNotFound(name.rawValue) } @@ -304,21 +322,36 @@ class UITestConfigUtils { return app } - /// Returns scopes to request + /// Returns scopes to request based on the scope selection strategy. + /// + /// - Parameters: + /// - appConfig: The app configuration containing available scopes. + /// - scopesParam: The scope selection strategy to apply. + /// - Returns: A space-separated string of scopes to request, or empty string for `.empty`, + /// or "invalid_scope" for `.invalid` testing scenarios. func getScopesToRequest(for appConfig: AppConfig, _ scopesParam: ScopeSelection) -> String { switch(scopesParam) { case .empty: return "" - case .subset: return removeScope(scopes: appConfig.scopes, scopeToRemove: "sfap_api") // that assumes the selected ca/eca/beacon has the sfap_api scope + case .subset: return removeScope(scopes: appConfig.scopes, scopeToRemove: "sfap_api") // Assumes the app has sfap_api scope case .all: return appConfig.scopes + case .invalid: return "invalid_scope" // For negative testing } } - /// Returns expected scopes granted + /// Returns expected scopes that should be granted based on the scope selection. + /// + /// - Parameters: + /// - appConfig: The app configuration containing available scopes. + /// - scopeSelection: The scope selection strategy that was used during login. + /// - Returns: A space-separated string of scopes expected to be granted. + /// Returns empty string for `.invalid` as invalid scopes should not be granted. + /// - Note: For `.empty`, assumes scopes in ui_test_config.json match server configuration. func getExpectedScopesGranted(for appConfig:AppConfig, _ scopeSelection: ScopeSelection) -> String { switch(scopeSelection) { - case .empty: return appConfig.scopes // that assumes the scopes in ui_test_config.json match the server config - case .subset: return removeScope(scopes: appConfig.scopes, scopeToRemove: "sfap_api") // that assumes the selected ca/eca/beacon has the sfap_api scope + case .empty: return appConfig.scopes // Assumes scopes in config match server + case .subset: return removeScope(scopes: appConfig.scopes, scopeToRemove: "sfap_api") // Assumes app has sfap_api scope case .all: return appConfig.scopes + case .invalid: return "" // Invalid scopes should not be granted } } } diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/overview.md b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/overview.md index 7fc2531abc..df2f3ac833 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/overview.md +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/overview.md @@ -8,7 +8,7 @@ This document provides an overview of all UI tests in the AuthFlowTester test su |-------|-------------| | `LegacyLoginTests` | Tests for legacy login flows (CA, user agent flow, hybrid flow) with default, subset, and all scopes | | `LegacyLoginTestsNotHybrid` | Tests for legacy login flows (CA, user agent flow, non-hybrid flow) - extends LegacyLoginTests | -| `ECALoginTests` | Tests for External Client App (ECA) login flows | +| `ECALoginTests` | Tests for External Client App (ECA) login flows including negative testing with invalid configurations | | `BeaconLoginTests` | Tests for Beacon app login flows (using regular_auth login host) | | `AdvancedAuthBeaconLoginTests` | Tests for Beacon app login flows (using advanced_auth login host) | | `WelcomeLoginTests` | Tests for welcome (domain discovery) login flows using simulated domain discovery | @@ -47,18 +47,20 @@ Tests for Connected App (CA) configurations with non-hybrid authentication flow. | `testCAOpaque_SubsetScopes_UserAgentFlow` | CA Opaque | Subset | User Agent | No | | `testCAOpaque_AllScopes_UserAgentFlow` | CA Opaque | All | User Agent | No | -### ECALoginTests (6 tests) +### ECALoginTests (8 tests) -Tests for External Client App (ECA) configurations using web server flow with hybrid auth. +Tests for External Client App (ECA) configurations using web server flow with hybrid auth. Includes negative testing scenarios for invalid client ID and invalid scopes. -| Test Name | App Config | Scopes | -|-----------|------------|--------| -| `testECAOpaque_DefaultScopes` | ECA Opaque | Default | -| `testECAOpaque_SubsetScopes` | ECA Opaque | Subset | -| `testECAOpaque_AllScopes` | ECA Opaque | All | -| `testECAJwt_DefaultScopes` | ECA JWT | Default | -| `testECAJwt_SubsetScopes_NotHybrid` | ECA JWT | Subset | -| `testECAJwt_AllScopes` | ECA JWT | All | +| Test Name | App Config | Scopes | Description | +|-----------|------------|--------|-------------| +| `testECAOpaque_DefaultScopes` | ECA Opaque | Default | Standard login with default scopes | +| `testECAOpaque_SubsetScopes` | ECA Opaque | Subset | Standard login with subset scopes | +| `testECAOpaque_AllScopes` | ECA Opaque | All | Standard login with all scopes | +| `testECAJwt_DefaultScopes` | ECA JWT | Default | Standard login with default scopes | +| `testECAJwt_SubsetScopes` | ECA JWT | Subset | Standard login with subset scopes | +| `testECAJwt_AllScopes` | ECA JWT | All | Standard login with all scopes | +| `testDynamicConfigurationWithInvalidClientId` | Invalid (dynamic) | Default | Negative test: invalid client ID in dynamic config | +| `testDynamicConfigurationWithInvalidScope` | ECA JWT (dynamic) | Invalid | Negative test: invalid scope in dynamic config | ### BeaconLoginTests (6 tests) @@ -173,6 +175,7 @@ Tests for login scenarios with two users using various configurations, including | **Default** | No scopes requested (all scopes defined in server config should be granted) | | **Subset** | Explicitly requests all scopes except for sfap_api | | **All** | Explicitly requests all scopes | +| **Invalid** | Requests "invalid_scope" for negative testing scenarios | ## App Configuration Types @@ -199,6 +202,7 @@ Tests for login scenarios with two users using various configurations, including | `beaconJwt` | Beacon | JWT | `api content id lightning refresh_token sfap_api web` | | `caOpaque` | CA | Opaque | `api content id lightning refresh_token sfap_api visualforce web` | | `caJwt` | CA | JWT | `api content id lightning refresh_token sfap_api visualforce web` | +| `invalid` | Invalid | N/A | `invalid_scope` (for negative testing) | ### Token Formats From 49d7b9a869d71b67e9c2c4ea164a59783e43aad1 Mon Sep 17 00:00:00 2001 From: Wolfgang Mathurin Date: Wed, 11 Mar 2026 11:13:06 -0700 Subject: [PATCH 04/10] Discovery simulation is only shown in login options when running automated tests --- .../LoginOptionsViewController.swift | 23 +++++++++++-------- .../Util/BaseAuthFlowTester.swift | 5 ++-- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/DevConfig/LoginOptionsViewController.swift b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/DevConfig/LoginOptionsViewController.swift index 4be45e0181..c2b19a589b 100644 --- a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/DevConfig/LoginOptionsViewController.swift +++ b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/DevConfig/LoginOptionsViewController.swift @@ -69,6 +69,8 @@ public struct LoginOptionsView: View { } public var body: some View { + let isUITesting = ProcessInfo.processInfo.environment["IS_UI_TESTING"] == "1" + VStack(spacing: 0) { // Custom title bar with close button – trigger handler then dismiss so presenter can run its callback TitleBarView(title: SFSDKResourceUtils.localizedString("LOGIN_OPTIONS"), onDismiss: closeSheet) @@ -110,15 +112,18 @@ public struct LoginOptionsView: View { initiallyExpanded: false ) - Divider() - - // Simulate domain discovery – sets simulatedDomainDiscoveryResult so the next discovery navigation uses this result - DiscoveryResultEditor( - loginHost: $discoveryLoginHost, - userName: $discoveryUserName, - onUseForSimulation: handleSimulatedDomainDiscovery, - initiallyExpanded: false - ) + // Only show DiscoveryResultEditor during UI tests + if isUITesting { + Divider() + + // Simulate domain discovery – sets simulatedDomainDiscoveryResult so the next discovery navigation uses this result + DiscoveryResultEditor( + loginHost: $discoveryLoginHost, + userName: $discoveryUserName, + onUseForSimulation: handleSimulatedDomainDiscovery, + initiallyExpanded: false + ) + } } .padding(.bottom, 40) } diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Util/BaseAuthFlowTester.swift b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Util/BaseAuthFlowTester.swift index 058ffd96d5..49f3a34333 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Util/BaseAuthFlowTester.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Util/BaseAuthFlowTester.swift @@ -59,8 +59,9 @@ class BaseAuthFlowTester: XCTestCase { func launch() { app = XCUIApplication() - // Note: Environment variables are configured in AuthFlowTester.xctestplan - // (AutomaticTextCompletionEnabled=0) + // Set environment variable to indicate we're running UI tests + // This is used to show/hide certain UI elements like DiscoveryResultEditor + app.launchEnvironment["IS_UI_TESTING"] = "1" loginPage = LoginPageObject(testApp: app) mainPage = AuthFlowTesterMainPageObject(testApp: app) From 7dd45ab6618014582567fe2b030d75d145cd1b22 Mon Sep 17 00:00:00 2001 From: Wolfgang Mathurin Date: Wed, 11 Mar 2026 14:58:26 -0700 Subject: [PATCH 05/10] Adding multi users tests where one user logs out (refresh token is therefore revoked) --- .../Tests/MultiUserLoginTests.swift | 98 ++++++++++++++++++- 1 file changed, 94 insertions(+), 4 deletions(-) diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/MultiUserLoginTests.swift b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/MultiUserLoginTests.swift index bfba5dc7cf..7b1d0d873b 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/MultiUserLoginTests.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/MultiUserLoginTests.swift @@ -312,9 +312,9 @@ class MultiUserLoginTests: BaseAuthFlowTester { // MARK: - Token Revocation Tests - /// Revoke user's access token (who uses dynamic config) and verify other user is unaffected. + /// Revoke access for user with dynamic config and verify other user is unaffected. /// Tests token isolation when one user uses dynamic consumer key selection. - func testRevokeUserWithDynamicConfig_OtherUserUnaffected() throws { + func testRevokeAccessForUserWithDynamicConfig_OtherUserUnaffected() throws { // Login User A with static config launchAndLogin( loginHost: .regularAuth, @@ -379,9 +379,9 @@ class MultiUserLoginTests: BaseAuthFlowTester { logout() } - /// Revoke CA user's access token and verify ECA user is unaffected. + /// Revoke access for CA user and verify ECA user is unaffected. /// Tests token isolation between users with different app types (CA vs ECA). - func testDifferentAppTypes_RevokeCaUser_EcaUserUnaffected() throws { + func testDifferentAppTypes_RevokeAccessForCaUser_EcaUserUnaffected() throws { // Login User A with CA Opaque launchAndLogin( loginHost: .regularAuth, @@ -447,4 +447,94 @@ class MultiUserLoginTests: BaseAuthFlowTester { // Logout second user logout() } + + // MARK: - User Logout Tests + + /// Logout user with dynamic config and verify other user is unaffected. + /// Tests that logging out one user automatically switches to the other user. + func testLogoutUserWithDynamicConfig_OtherUserUnaffected() throws { + // Login User A with static config + launchAndLogin( + loginHost: .regularAuth, + user: .fourth, + staticAppConfigName: .ecaOpaque + ) + + // Get User A credentials + let userACredentials = getUserCredentials() + + // Login User B with dynamic config (overrides consumer key at runtime) + loginOtherUserAndValidate( + loginHost: .regularAuth, + user: .fifth, + staticAppConfigName: .ecaOpaque, + dynamicAppConfigName: .ecaJwt + ) + + // Logout User B (should automatically switch to User A) + logout() + + // Verify we're automatically on User A after User B logout + let currentUserCredentials = getUserCredentials() + XCTAssertEqual( + currentUserCredentials.username, + userACredentials.username, + "Should automatically be on User A after User B logout" + ) + + // Verify User A's credentials are intact + XCTAssertEqual( + currentUserCredentials.accessToken, + userACredentials.accessToken, + "User A's access token should be unchanged after User B logout" + ) + + // Make API call for User A (should succeed) + XCTAssertTrue(makeRestRequest(), "User A's API call should succeed") + } + + /// Logout CA user and verify ECA user is unaffected. + /// Tests that logging out one user automatically switches to the other user with different app types. + func testDifferentAppTypes_LogoutCaUser_EcaUserUnaffected() throws { + // Login User A with CA Opaque + launchAndLogin( + loginHost: .regularAuth, + user: .fourth, + staticAppConfigName: .caOpaque + ) + + // Login User B with ECA Opaque + loginOtherUserAndValidate( + loginHost: .regularAuth, + user: .fifth, + staticAppConfigName: .ecaOpaque + ) + + // Get User B credentials + let userBCredentials = getUserCredentials() + + // Switch to User A + switchToUser(loginHost: .regularAuth, user: .fourth) + + // Logout User A (should automatically switch to User B) + logout() + + // Verify we're automatically on User B after User A logout + let currentUserCredentials = getUserCredentials() + XCTAssertEqual( + currentUserCredentials.username, + userBCredentials.username, + "Should automatically be on User B after User A logout" + ) + + // Verify User B's credentials are intact + XCTAssertEqual( + currentUserCredentials.accessToken, + userBCredentials.accessToken, + "User B's access token should be unchanged after User A logout" + ) + + // Make API call for User B (should succeed) + XCTAssertTrue(makeRestRequest(), "User B's API call should succeed") + } } From 98abee14ff4448d01c99cff67cc74765abf6f350 Mon Sep 17 00:00:00 2001 From: Wolfgang Mathurin Date: Wed, 11 Mar 2026 14:59:48 -0700 Subject: [PATCH 06/10] Adding migration test with auth flow type change (user agent to web server) Using accessibility identifier to auth flow type toggles --- .../Login/DevConfig/AuthFlowTypesView.swift | 4 +- .../AUTOMATED_TEST_SCENARIOS.md | 1099 +++++++++++++++++ .../AuthFlowTesterMainPageObject.swift | 6 +- .../PageObjects/LoginOptionsPageObject.swift | 6 +- .../Tests/RefreshTokenMigrationTests.swift | 35 +- .../AuthFlowTesterUITests/overview.md | 36 +- 6 files changed, 1155 insertions(+), 31 deletions(-) create mode 100644 native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/AUTOMATED_TEST_SCENARIOS.md diff --git a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/DevConfig/AuthFlowTypesView.swift b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/DevConfig/AuthFlowTypesView.swift index 50d17ee022..081b296448 100644 --- a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/DevConfig/AuthFlowTypesView.swift +++ b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/DevConfig/AuthFlowTypesView.swift @@ -47,15 +47,17 @@ public struct AuthFlowTypesView: View { Text(SFSDKResourceUtils.localizedString("LOGIN_OPTIONS_USE_WEB_SERVER_FLOW")) .font(.body) } + .accessibilityIdentifier("useWebServerFlowToggle") .onChange(of: useWebServerFlow) { _, newValue in SalesforceManager.shared.useWebServerAuthentication = newValue } .padding(.horizontal) - + Toggle(isOn: $useHybridFlow) { Text(SFSDKResourceUtils.localizedString("LOGIN_OPTIONS_USE_HYBRID_FLOW")) .font(.body) } + .accessibilityIdentifier("useHybridFlowToggle") .onChange(of: useHybridFlow) { _, newValue in SalesforceManager.shared.useHybridAuthentication = newValue } diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/AUTOMATED_TEST_SCENARIOS.md b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/AUTOMATED_TEST_SCENARIOS.md new file mode 100644 index 0000000000..c5d9847318 --- /dev/null +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/AUTOMATED_TEST_SCENARIOS.md @@ -0,0 +1,1099 @@ +# AuthFlowTester Automated Test Scenarios + +## Overview + +This document describes the automated UI test scenarios for AuthFlowTester, organized according to the Mobile SDK 13.2 high-level test plans. The automated tests validate three key features introduced in Mobile SDK 13.2: + +1. **Dynamic Scope Retrieval** - Applications can omit scopes in bootconfig and receive all configured scopes from the server +2. **Runtime Consumer Key Selection** - Applications can override the consumer key at runtime via dynamic configuration +3. **Refresh Token Migration** - Applications can exchange refresh tokens between different consumer keys and scopes + +## Related Resources + +- [13.2 Test Org Setup for Authentication Testing](https://drive.google.com/file/d/1SiXgxD46_LBhUATvt8kGYmmcoQd7reIgU0NICGbK-Ks/view) - User accounts and server-side app configuration +- [13.2 Dynamic Scope Retrieval High Level Test Plan](https://drive.google.com/file/d/14Ioi4qwRj3Gnsc67Fm59HoiaD2vnUH0IKAJZ5p9GX60/view) +- [13.2 Runtime Consumer Key Selection High Level Test Plan](https://drive.google.com/file/d/1-sFppGcou7XPhCpXXzOHix3Tk188uyddnnYp3R6_Vrk/view) +- [13.2 Refresh Token Migration High Level Test Plan](https://drive.google.com/file/d/19-o-3oQbDS_Dsv3hSEd8H-Me0lmGlmw2zZaIpiOC-pU/view) + +## General Test Execution Instructions + +### Prerequisites + +1. **Test Accounts and Server Setup**: User accounts and server-side apps (ECA, CA, Beacon) configured per the [13.2 Test Org Setup for Authentication Testing](https://drive.google.com/file/d/1SiXgxD46_LBhUATvt8kGYmmcoQd7reIgU0NICGbK-Ks/view) document +2. **Test Configuration**: User credentials and app configurations defined in `ui_test_config.json` + +Note: The automated tests configure the consumer key and login server dynamically - no bootconfig changes are required. + +### How to Execute Test Scenarios Manually + +All test scenarios follow this general pattern: + +1. **Launch AuthFlowTester** +2. **Configure Login Options** (via Login Settings menu on login screen) + - Click the settings/gear icon on the login screen + - Configure Static Configuration (equivalent to bootconfig - persists across app restarts) + - OR Configure Dynamic Configuration (runtime override - does not persist) + - Select auth flow type (Web Server Flow vs User Agent Flow, Hybrid vs Non-Hybrid) + - Save/apply settings +3. **Perform Login** + - Enter credentials + - Complete authentication +4. **Validate Credentials** + - Expand "User Credentials" section - verify username, client ID, redirect URI, granted scopes, token format + - Expand "OAuth Configuration" section - verify static configuration persists + - If JWT token: expand "JWT Access Token Details" - verify expiration, scopes, client ID +5. **Test Token Operations** + - Click "Revoke Access Token" - verify token is revoked + - Click "Make Rest API request" - verify access token is refreshed and API succeeds +6. **Multi-User Operations** (if applicable) + - Click "Users" icon (bottom bar) - add or switch users +7. **Migration Operations** (if applicable) + - Click "Key" icon (bottom bar) - configure new app configuration + - Configure auth flow type (Web Server Flow vs User Agent Flow, Hybrid vs Non-Hybrid) + - Verify refresh token changes in "User Credentials" section + +**Note**: The automated tests access the login settings menu directly through the UI test framework. Manual testers can also access login configuration through the Dev Support Menu (^+⌘+Z or shake device) if needed. + +### Negative Testing Support + +The test suite includes support for negative testing scenarios through invalid configurations: + +- **Invalid App Configuration** (`.invalid`): Uses hard-coded invalid values (consumer key: "invalid_consumer_key", redirect URI: "invalid://callback") to test error handling when authentication fails +- **Invalid Scopes** (`.invalid`): Requests "invalid_scope" to test error handling when server rejects invalid scope requests + +These can be used in automated tests by specifying: +```swift +// Test with invalid app config +launchLoginAndValidate(staticAppConfigName: .invalid) + +// Test with invalid scopes +launchLoginAndValidate(staticAppConfigName: .caOpaque, staticScopeSelection: .invalid) +``` + +--- + +## 1. Dynamic Scope Retrieval High Level Test Plan + +### Overview +Tests for the feature that allows applications to omit scopes from bootconfig and dynamically receive all scopes configured on the server. Scopes granted are saved with the user account and can be queried by the application. + +### Test Groups + +#### Group 1: Basic Login / Scope Behavior + +##### Test 1.1: Login with Default Scopes (No Explicit Scopes) +**Automated Test**: `ECALoginTests.testECAOpaque_DefaultScopes`, `ECALoginTests.testECAJwt_DefaultScopes` +**Maps To**: High-Level Test Case 1.1 +**Description**: Login using ECA with no explicitly requested scopes + +**Manual Execution Steps**: +1. Launch AuthFlowTester +2. Click the settings/gear icon on the login screen to access login options +3. Expand "Static Configuration" +4. Configure: + - Consumer Key: ECA-AllScopes consumer key + - Scopes: (leave empty to request all scopes) + - Auth Flow: Web Server Flow (Hybrid) +5. Click "Use static config" +6. Enter credentials and login +8. Expand "User Credentials" section +9. **Verify**: Granted scopes = id, api, refresh_token (all scopes defined on ECA-AllScopes) +10. Click "Make Rest API request" +11. **Verify**: API call succeeds +12. Click "Revoke Access Token" +13. Click "Make Rest API request" again +14. **Verify**: Access token is refreshed automatically, API call succeeds + +##### Test 1.2: Login Explicitly Requesting Scopes +**Automated Test**: `ECALoginTests.testECAOpaque_AllScopes`, `ECALoginTests.testECAJwt_AllScopes` +**Maps To**: High-Level Test Case 1.2 +**Description**: Login requesting specific scopes (id, api, refresh_token) + +**Manual Execution Steps**: +1. Launch AuthFlowTester +2. Click the settings/gear icon on the login screen to access login options +3. Expand "Static Configuration" +4. Configure: + - Consumer Key: ECA-AllScopes consumer key + - Scopes: `id api refresh_token` +5. Click "Use static config" +6. Enter credentials and login +8. Expand "User Credentials" section +9. **Verify**: Granted scopes = id, api, refresh_token (exactly as requested) +10. Click "Make Rest API request" +11. **Verify**: API call succeeds + +##### Test 1.3: Login with Subset of Scopes +**Automated Test**: `ECALoginTests.testECAOpaque_SubsetScopes`, `ECALoginTests.testECAJwt_SubsetScopes` +**Maps To**: High-Level Test Case 1.4 +**Description**: Login requesting only id and refresh_token (omitting api scope) + +**Manual Execution Steps**: +1. Launch AuthFlowTester +2. Click the settings/gear icon on the login screen to access login options +3. Expand "Static Configuration" +4. Configure: + - Consumer Key: ECA-AllScopes consumer key + - Scopes: `id refresh_token` +5. Click "Use static config" +6. Enter credentials and login +8. Expand "User Credentials" section +9. **Verify**: Granted scopes = id, refresh_token (no api scope) +10. Click "Make Rest API request" +11. **Verify**: API call fails (missing api scope) + +#### Group 2: API Call & Token Behavior + +##### Test 2.1: Call API After Successful Login +**Automated Test**: All login tests include API validation +**Maps To**: High-Level Test Case 2.1 +**Description**: Verify API calls work after login + +**Manual Execution Steps**: +1. Complete any successful login test (e.g., Test 1.1) +2. Click "Make Rest API request" +3. **Verify**: API returns success response +4. **Verify**: Request/response details shown + +##### Test 2.2: Revoke Access Token and Refresh +**Automated Test**: Built into all validation flows via `assertRevokeAndRefreshWorks` +**Maps To**: High-Level Test Case 2.2 +**Description**: Verify automatic token refresh after revocation + +**Manual Execution Steps**: +1. Complete any successful login test +2. Note current access token value in "User Credentials" +3. Click "Revoke Access Token" +4. **Verify**: Access token is cleared/invalidated +5. Click "Make Rest API request" +6. **Verify**: New access token appears in "User Credentials" +7. **Verify**: Access token value changed +8. **Verify**: API call succeeds + +#### Group 3: Multi-User Scenarios + +##### Test 3.1: Multiple Users with Different Apps +**Automated Test**: `MultiUserLoginTests.testBothStatic_DifferentApps` +**Maps To**: High-Level Test Case 3.1 +**Description**: Login User A with one ECA, then User B with different ECA + +**Manual Execution Steps**: +1. Launch AuthFlowTester +2. Login User A with ECA-AllScopes (default scopes) +3. Verify User A credentials show correct scopes +4. Click "Users" icon (bottom bar) +5. Click "Add User" +6. Login User B with ECA-NewScopeAdded (default scopes) +7. **Verify**: User B credentials show ECA-NewScopeAdded scopes (including sfap_api) +8. Switch back to User A +9. **Verify**: User A credentials unchanged (no sfap_api scope) +10. Switch back to User B +11. **Verify**: User B credentials unchanged (still has sfap_api scope) + +##### Test 3.2: Multiple Users with Same App, Different Scopes +**Automated Test**: `MultiUserLoginTests.testBothStatic_SameApp_DifferentScopes` +**Maps To**: Related to High-Level Test Case 3.1 +**Description**: Two users with same ECA but different scope selections + +**Manual Execution Steps**: +1. Launch AuthFlowTester +2. Login User A with ECA-AllScopes, subset scopes (id, refresh_token) +3. Verify User A has only id, refresh_token scopes +4. Click "Users" icon, add User B +5. Login User B with ECA-AllScopes, default scopes (all) +6. **Verify**: User B has id, api, refresh_token scopes +7. Switch between users +8. **Verify**: Each user retains their respective scopes + +##### Test 3.3: Revoke One User's Token +**Automated Test**: `MultiUserLoginTests.testRevokeAccessForUserWithDynamicConfig_OtherUserUnaffected` +**Maps To**: High-Level Test Case 3.2 +**Description**: Revoke user's access token (who uses dynamic config), verify other user unaffected + +**Manual Execution Steps**: +1. Login User A and User B (following Test 3.1) +2. Switch to User B +3. Click "Revoke Access Token" +4. Switch to User A +5. Click "Make Rest API request" +6. **Verify**: User A's API call succeeds without needing refresh +7. Switch back to User B +8. Click "Make Rest API request" +9. **Verify**: User B's access token is refreshed, API succeeds + +#### Group 4: Refresh Token Migration (Scope Changes) + +##### Test 4.1: Migrate with More Scopes on Server +**Automated Test**: `RefreshTokenMigrationTests.testMigrateCA_AddMoreScopes`, `RefreshTokenMigrationTests.testMigrateECA_AddMoreScopes` +**Maps To**: High-Level Test Case 4.1 +**Description**: Login with subset scopes, migrate requesting all scopes + +**Manual Execution Steps**: +1. Launch AuthFlowTester +2. Login with ECA-AllScopes, subset scopes (id, refresh_token) +3. Note refresh token value in "User Credentials" +4. Click "Key" icon (bottom bar) +5. Configure migration auth flow type (optional): + - Use Web Server Flow: On/Off + - Use Hybrid Flow: On/Off +6. Configure migration: + - Consumer Key: ECA-AllScopes (same) + - Scopes: `id api refresh_token` (all scopes) +7. Click "Migrate" +8. **Verify**: Refresh token value changed +9. **Verify**: Granted scopes now include api scope +10. Click "Make Rest API request" +11. **Verify**: API call now succeeds + +##### Test 4.2: Migrate with Fewer Scopes +**Automated Test**: Part of migration test validation +**Maps To**: High-Level Test Case 4.2, 4.4 +**Description**: Login with all scopes, migrate requesting subset + +**Manual Execution Steps**: +1. Launch AuthFlowTester +2. Login with ECA-AllScopes, all scopes (id, api, refresh_token) +3. Verify API call works +4. Click "Key" icon +5. Configure migration auth flow type (optional): + - Use Web Server Flow: On/Off + - Use Hybrid Flow: On/Off +6. Configure migration: + - Consumer Key: ECA-AllScopes (same) + - Scopes: `id refresh_token` (subset) +7. Click "Migrate" +8. **Verify**: Refresh token changed +9. **Verify**: Granted scopes = id, refresh_token (api removed) +10. Click "Make Rest API request" +11. **Verify**: API call fails (missing api scope) + +##### Test 4.3: Migrate from Explicit to Implicit Scopes +**Automated Test**: Implicitly tested in migration tests +**Maps To**: High-Level Test Case 4.3 +**Description**: Login with subset, migrate with no explicit scopes + +**Manual Execution Steps**: +1. Launch AuthFlowTester +2. Login with ECA-AllScopes, subset scopes (id, refresh_token) +3. Verify only id, refresh_token scopes present +4. Click "Key" icon +5. Configure migration auth flow type (optional): + - Use Web Server Flow: On/Off + - Use Hybrid Flow: On/Off +6. Configure migration: + - Consumer Key: ECA-AllScopes (same) + - Scopes: (leave empty for all server-defined scopes) +7. Click "Migrate" +8. **Verify**: Refresh token changed +9. **Verify**: Granted scopes = id, api, refresh_token (all ECA scopes) +10. Click "Make Rest API request" +11. **Verify**: API call succeeds + +#### Group 5: Negative Testing Scenarios + +##### Test 5.1: Dynamic Configuration with Invalid Client ID +**Automated Test**: `ECALoginTests.testDynamicConfigurationWithInvalidClientId` +**Maps To**: Negative Testing +**Description**: Attempt login with invalid client ID in dynamic configuration to verify error handling + +**Manual Execution Steps**: +1. Launch AuthFlowTester +2. Click the settings/gear icon on the login screen to access login options +3. Expand "Static Configuration" - configure ECA Opaque (valid config) +4. Expand "Dynamic Configuration" - configure with invalid client ID: + - Consumer Key: `invalid_consumer_key` + - Redirect URI: `invalid://callback` + - Scopes: (leave empty) +5. Click "Use dynamic config" +6. Enter credentials and attempt login +7. **Verify**: Login fails with appropriate error +8. **Verify**: Error indicates invalid client credentials + +##### Test 5.2: Dynamic Configuration with Invalid Scope +**Automated Test**: `ECALoginTests.testDynamicConfigurationWithInvalidScope` +**Maps To**: Negative Testing +**Description**: Attempt login with invalid scope in dynamic configuration to verify scope validation + +**Manual Execution Steps**: +1. Launch AuthFlowTester +2. Click the settings/gear icon on the login screen to access login options +3. Expand "Static Configuration" - configure ECA Opaque (valid config) +4. Expand "Dynamic Configuration" - configure ECA JWT with invalid scope: + - Consumer Key: ECA-JWT consumer key + - Scopes: `invalid_scope` +5. Click "Use dynamic config" +6. Enter credentials and attempt login +7. **Verify**: Login fails or completes with no scopes granted +8. **Verify**: Invalid scope is not in granted scopes + +--- + +## 2. Runtime Consumer Key Selection High Level Test Plan + +### Overview +Tests for the feature that allows applications to override the consumer key at runtime via dynamic configuration (block/lambda), enabling different consumer keys per login host without recompiling the app. + +### Test Groups + +#### Group 1: Base Functional Scenarios + +##### Test BF-01: Login with Dynamic Config +**Automated Test**: `LoginWithRestartTests.testECAJwt_DefaultScopes_DynamicConfiguration_WithRestart` +**Maps To**: High-Level Test Case BF-01 +**Description**: Use dynamic configuration to specify consumer key at runtime + +**Manual Execution Steps**: +1. Launch AuthFlowTester +2. Click the settings/gear icon on the login screen to access login options +3. Expand "Static Configuration" - configure ECA-Opaque (will be in bootconfig) +4. Expand "Dynamic Configuration" - configure ECA-JWT (different consumer key) +5. Click "Use dynamic config" +6. Enter credentials and login +8. Expand "User Credentials" section +9. **Verify**: Client ID = ECA-JWT consumer key (not ECA-Opaque) +10. **Verify**: Token format = jwt +11. Expand "OAuth Configuration" section +12. **Verify**: Static config shows ECA-Opaque settings (not used for this user) +13. Click "Make Rest API request" +14. **Verify**: API call succeeds + +##### Test BF-02: Access Token Revocation and Refresh +**Automated Test**: Built into all dynamic config tests +**Maps To**: High-Level Test Case BF-02 +**Description**: Verify token refresh works with dynamic configuration + +**Manual Execution Steps**: +1. Complete Test BF-01 +2. Note access token value +3. Click "Revoke Access Token" +4. **Verify**: Access token cleared +5. Click "Make Rest API request" +6. **Verify**: Access token refreshed (new value) +7. **Verify**: API call succeeds + +#### Group 2: Multi-User Scenarios + +##### Test MU-01: Two Users with Different Configs +**Automated Test**: `MultiUserLoginTests.testFirstStatic_SecondDynamic_DifferentApps` +**Maps To**: High-Level Test Case MU-01 +**Description**: User A uses static config, User B uses dynamic config + +**Manual Execution Steps**: +1. Launch AuthFlowTester +2. Login User A with static config (ECA-Opaque) +3. Verify User A has ECA-Opaque consumer key +4. Click "Users" icon, add User B +5. Configure dynamic config for ECA-JWT +6. Login User B with dynamic config +7. **Verify**: User B has ECA-JWT consumer key +8. Switch to User A +9. **Verify**: User A still has ECA-Opaque settings +10. Switch to User B +11. **Verify**: User B still has ECA-JWT settings +12. **Verify**: Configs are isolated per user + +##### Test MU-02: Revoke One User's Access Token +**Automated Test**: `MultiUserLoginTests.testRevokeAccessForUserWithDynamicConfig_OtherUserUnaffected` +**Maps To**: High-Level Test Case MU-02 +**Description**: Revoke user's token (who uses dynamic config), other user unaffected + +**Manual Execution Steps**: +1. Setup two users (following Test MU-01) +2. Switch to User A +3. Click "Revoke Access Token" +4. Switch to User B +5. Click "Make Rest API request" +6. **Verify**: User B's API succeeds without refresh +7. Switch to User A +8. Click "Make Rest API request" +9. **Verify**: User A's token refreshed automatically + +##### Test MU-02a: Logout One User +**Automated Test**: `MultiUserLoginTests.testLogoutUserWithDynamicConfig_OtherUserUnaffected` +**Maps To**: High-Level Test Case MU-02 (variation with logout) +**Description**: Logout user with dynamic config, verify other user unaffected and automatic switch occurs + +**Manual Execution Steps**: +1. Login User A with ECA Opaque (static) +2. Add User B with ECA JWT (dynamic) +3. Currently on User B - Click "Logout" icon (bottom bar) and confirm +4. **Verify**: App automatically switches to User A +5. **Verify**: User A's credentials unchanged +6. Click "Make Rest API request" +7. **Verify**: User A's API succeeds +8. **Verify**: Cannot switch back to User B (logged out) + +##### Test MU-03: Both Users Use Dynamic Config +**Automated Test**: `MultiUserLoginTests.testBothDynamic_DifferentApps` +**Maps To**: High-Level Test Case MU-01, MU-04 +**Description**: Two users both using dynamic configuration with different apps + +**Manual Execution Steps**: +1. Launch AuthFlowTester +2. Login User A with dynamic config (ECA-Opaque) +3. Add User B, login with dynamic config (ECA-JWT) +4. **Verify**: Each user has correct consumer key and token format +5. Switch between users multiple times +6. **Verify**: Dynamic configs persist per user +7. Make API calls for each user +8. **Verify**: API calls work correctly for both users + +##### Test MU-04: Mixed Static and Dynamic Scopes +**Automated Test**: `MultiUserLoginTests.testFirstDynamic_SecondStatic_DifferentApps` +**Maps To**: Related to High-Level Test Case MU-04 +**Description**: First user dynamic config, second user static config + +**Manual Execution Steps**: +1. Launch AuthFlowTester +2. Login User A with dynamic config (ECA-JWT) +3. Add User B with static config (ECA-Opaque) +4. **Verify**: User A has ECA-JWT (from dynamic) +5. **Verify**: User B has ECA-Opaque (from static) +6. Switch between users +7. **Verify**: Each retains correct configuration +8. Test API calls for both +9. **Verify**: Both work independently + +#### Group 3: App Restart Scenarios + +##### Test RS-01: Restart After Login +**Automated Test**: `DynamicConfigLoginTests` tests with `restartAndValidate` +**Maps To**: High-Level Test Case RS-01 +**Description**: Verify user session and dynamic config persist after restart + +**Manual Execution Steps**: +1. Login with dynamic config (ECA-JWT) +2. Verify credentials correct +3. Terminate app (swipe up from app switcher) +4. Launch app again +5. **Verify**: User still logged in +6. **Verify**: User credentials show ECA-JWT consumer key +7. Click "Make Rest API request" +8. **Verify**: API call succeeds + +##### Test RS-02: Restart, Revoke, and Refresh +**Automated Test**: Covered by restart validation in dynamic config tests +**Maps To**: High-Level Test Case RS-02 +**Description**: After restart, verify token refresh still works + +**Manual Execution Steps**: +1. Complete Test RS-01 +2. Click "Revoke Access Token" +3. Click "Make Rest API request" +4. **Verify**: Access token refreshed +5. **Verify**: API call succeeds + +##### Test RS-03: Multi-User Restart +**Automated Test**: `LoginWithRestartTests.testMultiUserRestart` +**Maps To**: High-Level Test Case RS-03 +**Description**: Restart with multiple users, verify all persist + +**Manual Execution Steps**: +1. Login User A with dynamic config (ECA-Opaque) +2. Add User B with static config (ECA-JWT) +3. Terminate app +4. Launch app +5. **Verify**: User A appears in user switcher +6. **Verify**: User B appears in user switcher +7. Switch to each user +8. **Verify**: Each has correct consumer key +9. Make API calls +10. **Verify**: Both work correctly + +#### Group 4: Scope & Consumer Key Behavior + +##### Test SC-01: Request Subset of Scopes +**Automated Test**: `LoginWithRestartTests.testECAJwt_SubsetScopes_DynamicConfiguration_WithRestart` +**Maps To**: High-Level Test Case SC-01 +**Description**: Use dynamic config to request only specific scopes + +**Manual Execution Steps**: +1. Launch AuthFlowTester +2. Configure dynamic config: + - Consumer Key: ECA JWT + - Scopes: `id api` (subset only) +3. Login with dynamic config +4. **Verify**: Granted scopes = id, api only +5. **Verify**: No other scopes present + +##### Test SC-02: No Scopes Specified +**Automated Test**: `LoginWithRestartTests.testECAJwt_DefaultScopes_DynamicConfiguration_WithRestart` +**Maps To**: High-Level Test Case SC-02 +**Description**: Dynamic config with no scopes → get all ECA scopes + +**Manual Execution Steps**: +1. Launch AuthFlowTester +2. Configure dynamic config: + - Consumer Key: ECA JWT + - Scopes: (leave empty) +3. Login with dynamic config +4. **Verify**: Granted scopes include all available ECA scopes + +--- + +## 3. Refresh Token Migration High Level Test Plan + +### Overview +Tests for the feature that allows applications to exchange a refresh token for a new one with different consumer key and/or scopes, facilitating migration to Beacon apps, JWT tokens, and new scopes. The migration UI includes auth flow type configuration (Web Server Flow vs User Agent Flow, Hybrid vs Non-Hybrid) that can be set before performing the migration. + +### Test Groups + +#### Group 1: Baseline Login & Token Establishment + +##### Test 1.1: Login with CA +**Automated Test**: `LegacyLoginTests.testCAOpaque_DefaultScopes_WebServerFlow` +**Maps To**: High-Level Test Scenario 1.1 +**Description**: Establish baseline with Connected App + +**Manual Execution Steps**: +1. Launch AuthFlowTester +2. Login with CA Opaque (static config) +3. **Verify**: Login succeeds +4. **Verify**: Granted scopes = id, api, refresh_token +5. Click "Make Rest API request" +6. **Verify**: API works +7. Click "Revoke Access Token", then make API call +8. **Verify**: Token refresh works + +##### Test 1.2: Login with ECA +**Automated Test**: `ECALoginTests.testECAOpaque_DefaultScopes` +**Maps To**: High-Level Test Scenario 1.2 +**Description**: Establish baseline with External Client App + +**Manual Execution Steps**: +1. Launch AuthFlowTester +2. Login with ECA Opaque (static config) +3. **Verify**: Login succeeds +4. **Verify**: Granted scopes = id, api, refresh_token +5. Test API and refresh (same as Test 1.1) + +##### Test 1.3: Login with Beacon +**Automated Test**: `BeaconLoginTests.testBeaconOpaque_DefaultScopes` +**Maps To**: High-Level Test Scenario 1.3 +**Description**: Establish baseline with Beacon app + +**Manual Execution Steps**: +1. Launch AuthFlowTester +2. Configure login host for advanced authentication (if needed) +3. Login with Beacon Opaque (static config) +4. **Verify**: Login succeeds +5. **Verify**: Beacon child key appears in credentials +6. **Verify**: Granted scopes = id, api, refresh_token +7. Test API and refresh + +#### Group 2: Basic Migration Flows (Single User) + +##### Test 2.1: Migrate CA to ECA +**Automated Test**: `RefreshTokenMigrationTests.testMigrateCAToECA` +**Maps To**: High-Level Test Scenario 2.1 +**Description**: Migrate from Connected App to External Client App + +**Manual Execution Steps**: +1. Launch AuthFlowTester +2. Login with CA Opaque +3. Note refresh token value +4. Click "Key" icon (bottom bar) +5. Configure migration auth flow type (optional): + - Use Web Server Flow: On/Off + - Use Hybrid Flow: On/Off +6. Configure migration: + - Consumer Key: ECA Opaque + - Scopes: (empty for default) +7. Click "Migrate" +8. **Verify**: Refresh token changed (new value) +9. **Verify**: Client ID = ECA Opaque consumer key +10. **Verify**: Granted scopes = id, api, refresh_token +11. Click "Make Rest API request" +12. **Verify**: API succeeds with new token + +##### Test 2.2: Migrate CA to Beacon +**Automated Test**: `RefreshTokenMigrationTests.testMigrateCAToBeacon` +**Maps To**: High-Level Test Scenario 2.2 +**Description**: Migrate from Connected App to Beacon app + +**Manual Execution Steps**: +1. Launch AuthFlowTester +2. Login with CA Opaque +3. Note refresh token value +4. Click "Key" icon +5. Configure migration auth flow type (optional): + - Use Web Server Flow: On/Off + - Use Hybrid Flow: On/Off +6. Configure migration: + - Consumer Key: Beacon Opaque + - Scopes: (empty for default) +7. Click "Migrate" +8. **Verify**: Refresh token changed +9. **Verify**: Beacon child key appears in credentials +10. **Verify**: Client ID = Beacon Opaque consumer key +11. Click "Make Rest API request" +12. **Verify**: API succeeds + +##### Test 2.3: Migrate ECA with Scope Addition +**Automated Test**: `RefreshTokenMigrationTests.testMigrateECA_AddMoreScopes` +**Maps To**: High-Level Test Scenario 2.3 +**Description**: Migrate within same ECA, adding more scopes + +**Manual Execution Steps**: +1. Launch AuthFlowTester +2. Login with ECA JWT, subset scopes (id, refresh_token) +3. Verify API call fails (no api scope) +4. Click "Key" icon +5. Configure migration auth flow type (optional): + - Use Web Server Flow: On/Off + - Use Hybrid Flow: On/Off +6. Configure migration: + - Consumer Key: ECA JWT (same app) + - Scopes: (empty for all server scopes) +7. Click "Migrate" +8. **Verify**: Refresh token changed +9. **Verify**: Granted scopes now include all available scopes +10. Click "Make Rest API request" +11. **Verify**: API succeeds + +##### Test 2.4: Migrate Beacon with Scope Addition +**Automated Test**: `RefreshTokenMigrationTests.testMigrateBeacon_AddMoreScopes` +**Maps To**: High-Level Test Scenario 2.4 +**Description**: Migrate within Beacon, adding scopes + +**Manual Execution Steps**: +1. Launch AuthFlowTester +2. Login with Beacon JWT, subset scopes +3. Click "Key" icon +4. Configure migration: + - Consumer Key: Beacon JWT (same app) + - Scopes: (empty for all) +5. Click "Migrate" +6. **Verify**: Refresh token changed +7. **Verify**: Beacon child key retained +8. **Verify**: Scopes extended +9. Test API + +##### Test 2.4a: Migrate CA User Agent to ECA Web Server +**Automated Test**: `RefreshTokenMigrationTests.testMigrateCAUserAgentToECAWebServer` +**Maps To**: High-Level Test Scenario 2.1 (with auth flow type change) +**Description**: Migrate from CA with user agent flow to ECA with web server flow + +**Manual Execution Steps**: +1. Launch AuthFlowTester +2. Login with CA Opaque using user agent flow (disable "Use Web Server Flow" in login settings) +3. Note refresh token value +4. Click "Key" icon +5. Configure migration auth flow type: + - Use Web Server Flow: On + - Use Hybrid Flow: On (default) +6. Configure migration: + - Consumer Key: ECA Opaque + - Scopes: (empty for default) +7. Click "Migrate" +8. **Verify**: Refresh token changed +9. **Verify**: Client ID = ECA Opaque consumer key +10. Click "Make Rest API request" +11. **Verify**: API succeeds + +##### Test 2.4b: Migrate CA User Agent to Beacon Web Server +**Automated Test**: `RefreshTokenMigrationTests.testMigrateCAUserAgentToBeaconWebServer` +**Maps To**: High-Level Test Scenario 2.2 (with auth flow type change) +**Description**: Migrate from CA with user agent flow to Beacon with web server flow + +**Manual Execution Steps**: +1. Launch AuthFlowTester +2. Login with CA Opaque using user agent flow (disable "Use Web Server Flow" in login settings) +3. Note refresh token value +4. Click "Key" icon +5. Configure migration auth flow type: + - Use Web Server Flow: On + - Use Hybrid Flow: On (default) +6. Configure migration: + - Consumer Key: Beacon Opaque + - Scopes: (empty for default) +7. Click "Migrate" +8. **Verify**: Refresh token changed +9. **Verify**: Beacon child key appears in credentials +10. **Verify**: Client ID = Beacon Opaque consumer key +11. Click "Make Rest API request" +12. **Verify**: API succeeds + +##### Test 2.5: Migrate ECA to CA (Rollback) +**Automated Test**: `RefreshTokenMigrationTests.testMigrateCAToECA` (includes rollback) +**Maps To**: High-Level Test Scenario 2.5 +**Description**: Migrate from ECA back to CA + +**Manual Execution Steps**: +1. Login with ECA Opaque +2. Migrate to CA Opaque +3. **Verify**: Refresh token changed +4. **Verify**: Client ID = CA Opaque consumer key +5. **Verify**: No beacon child key +6. Test API succeeds + +##### Test 2.6: Migrate Beacon to CA (Rollback) +**Automated Test**: `RefreshTokenMigrationTests.testMigrateBeaconToCA` +**Maps To**: High-Level Test Scenario 2.6 +**Description**: Migrate from Beacon back to CA + +**Manual Execution Steps**: +1. Login with Beacon Opaque +2. Verify beacon child key present +3. Migrate to CA Opaque +4. **Verify**: Refresh token changed +5. **Verify**: Beacon child key disappears +6. **Verify**: Client ID = CA Opaque +7. Test API + +##### Test 2.7: Migrate Between Token Formats (Opaque to JWT) +**Automated Test**: `RefreshTokenMigrationTests.testMigrateBeaconOpaqueToJWTAndBack` +**Maps To**: High-Level Test Scenario 2.7 +**Description**: Migrate from opaque token to JWT token format + +**Manual Execution Steps**: +1. Login with Beacon-Opaque +2. **Verify**: Token format = opaque (empty in UI) +3. Migrate to Beacon-JWT +4. **Verify**: Refresh token changed +5. **Verify**: Token format = jwt +6. Expand "JWT Access Token Details" +7. **Verify**: JWT details shown (expiration, scopes, etc.) +8. Test API succeeds + +#### Group 3: Multi-User Scenarios + +##### Test 3.1: Two Users, Both CA +**Automated Test**: `MultiUserLoginTests.testBothStatic_SameApp_SameScopes` +**Maps To**: High-Level Test Scenario 3.1 +**Description**: Two users with same app type + +**Manual Execution Steps**: +1. Login User A with CA Opaque +2. Add User B, login with CA Opaque +3. **Verify**: Both have independent tokens +4. Switch between users +5. **Verify**: Each retains own credentials + +##### Test 3.2: Two Users, Different Apps +**Automated Test**: `MultiUserLoginTests.testBothStatic_DifferentApps` +**Maps To**: High-Level Test Scenario 3.2 +**Description**: User A with CA, User B with ECA + +**Manual Execution Steps**: +1. Login User A with CA Opaque +2. Add User B with ECA Opaque +3. **Verify**: User A has CA consumer key +4. **Verify**: User B has ECA consumer key +5. Switch between users +6. **Verify**: Each retains own app configuration + +##### Test 3.3: Migrate One User Only +**Automated Test**: `RefreshTokenMigrationTests.testMigrateOneUserOnly` +**Maps To**: High-Level Test Scenario 3.3 +**Description**: Migrate User A, leave User B unchanged + +**Manual Execution Steps**: +1. Login User A with CA Opaque +2. Add User B with CA Opaque +3. Switch to User A +4. Migrate User A to ECA Opaque +5. **Verify**: User A now has ECA consumer key +6. Switch to User B +7. **Verify**: User B unchanged (still CA Opaque) +8. Test APIs for both users + +##### Test 3.4: Revoke Access for One User During Multi-User +**Automated Test**: `MultiUserLoginTests.testDifferentAppTypes_RevokeAccessForCaUser_EcaUserUnaffected` +**Maps To**: High-Level Test Scenario 3.4 +**Description**: Revoke CA user's access token, verify ECA user unaffected (different app types) + +**Manual Execution Steps**: +1. Setup two users (any configuration) +2. Switch to User A +3. Click "Revoke Access Token" +4. Switch to User B +5. Click "Make Rest API request" +6. **Verify**: User B's API succeeds without refresh +7. Switch to User A +8. Click "Make Rest API request" +9. **Verify**: User A's token refreshed + +##### Test 3.4a: Logout One User During Multi-User +**Automated Test**: `MultiUserLoginTests.testDifferentAppTypes_LogoutCaUser_EcaUserUnaffected` +**Maps To**: High-Level Test Scenario 3.4 (variation with logout) +**Description**: Logout CA user, verify ECA user unaffected and automatic switch occurs + +**Manual Execution Steps**: +1. Login User A with CA Opaque +2. Add User B with ECA Opaque +3. Switch to User A +4. Click "Logout" icon (bottom bar) and confirm logout +5. **Verify**: App automatically switches to User B +6. **Verify**: User B's credentials unchanged +7. Click "Make Rest API request" +8. **Verify**: User B's API succeeds +9. **Verify**: Cannot switch back to User A (logged out) + +##### Test 3.5: Beacon + Non-Beacon Multi-User +**Automated Test**: `MultiUserLoginTests.testBeaconAndNonBeacon_MultiUser` +**Maps To**: High-Level Test Scenario 3.5 +**Description**: User A with Beacon, User B with CA + +**Manual Execution Steps**: +1. Login User A with Beacon Opaque +2. **Verify**: User A has beacon child key +3. Add User B with CA Opaque +4. **Verify**: User B has no beacon child key +5. Switch between users +6. **Verify**: User A always shows child key +7. **Verify**: User B never shows child key + +#### Group 4: Persistence & App-Restart + +##### Test 4.1: Restart After CA Login +**Automated Test**: `LoginWithRestartTests.testCAOpaque_DefaultScopes_WithRestart` +**Maps To**: High-Level Test Scenario 4.1 +**Description**: Verify CA session persists after restart + +**Manual Execution Steps**: +1. Login with CA Opaque +2. Verify credentials +3. Terminate app +4. Launch app +5. **Verify**: User still logged in +6. **Verify**: Credentials unchanged +7. Test API succeeds + +##### Test 4.2: Restart After ECA Login +**Automated Test**: `LoginWithRestartTests.testECAOpaque_DefaultScopes_WithRestart` +**Maps To**: High-Level Test Scenario 4.2 +**Description**: Verify ECA session persists + +**Manual Execution Steps**: +1. Login with ECA Opaque +2. Terminate and restart app +3. **Verify**: Session persists +4. Test API + +##### Test 4.3: Restart After Beacon Login +**Automated Test**: `LoginWithRestartTests.testBeaconOpaque_DefaultScopes_WithRestart` +**Maps To**: High-Level Test Scenario 4.3 +**Description**: Verify Beacon session and child key persist + +**Manual Execution Steps**: +1. Login with Beacon Opaque +2. **Verify**: Beacon child key present +3. Terminate and restart app +4. **Verify**: Session persists +5. **Verify**: Beacon child key still present +6. Test API + +##### Test 4.4: Restart After Migration (CA to ECA) +**Automated Test**: `RefreshTokenMigrationWithRestartTests.testMigrateCAToECA_WithRestart` +**Maps To**: High-Level Test Scenario 4.4 +**Description**: Verify migrated tokens persist + +**Manual Execution Steps**: +1. Login with CA Opaque +2. Migrate to ECA Opaque +3. Verify migration successful +4. Terminate and restart app +5. **Verify**: Migrated configuration persists +6. **Verify**: Client ID = ECA Opaque +7. Test API + +##### Test 4.5: Restart After Migration (CA to Beacon) +**Automated Test**: `RefreshTokenMigrationWithRestartTests.testMigrateCAToBeacon_WithRestart` +**Maps To**: High-Level Test Scenario 4.5 +**Description**: Verify beacon migration persists + +**Manual Execution Steps**: +1. Login with CA Opaque +2. Migrate to Beacon Opaque +3. Terminate and restart app +4. **Verify**: Beacon child key persists +5. Test API + +##### Test 4.6: Restart After Scope Migration +**Automated Test**: `RefreshTokenMigrationWithRestartTests.testMigrateScopeAddition_WithRestart` +**Maps To**: High-Level Test Scenario 4.6 +**Description**: Verify scope changes persist + +**Manual Execution Steps**: +1. Login with ECA JWT, subset scopes +2. Migrate to ECA JWT with all scopes +3. Verify extended scopes present +4. Terminate and restart app +5. **Verify**: Extended scopes persist +6. Test API + +##### Test 4.7: Restart After Beacon Scope Migration +**Automated Test**: `RefreshTokenMigrationWithRestartTests.testMigrateBeaconScopeAddition_WithRestart` +**Maps To**: High-Level Test Scenario 4.7 +**Description**: Verify beacon scope migration persists + +**Manual Execution Steps**: +1. Login with Beacon JWT, subset scopes +2. Migrate to Beacon JWT with all scopes +3. Terminate and restart app +4. **Verify**: Beacon child key persists +5. **Verify**: Extended scopes persist +6. Test API + +##### Test 4.8: Restart With Multiple Users +**Automated Test**: `RefreshTokenMigrationWithRestartTests.testMigrateMultipleUsers_WithRestart` +**Maps To**: High-Level Test Scenario 4.8 +**Description**: Verify multi-user persistence after restart + +**Manual Execution Steps**: +1. Login User A with CA Opaque +2. Migrate User A to ECA Opaque +3. Add User B with Beacon Opaque +4. Terminate and restart app +5. **Verify**: Both users in switcher +6. Switch to User A +7. **Verify**: User A has ECA Opaque config +8. Switch to User B +9. **Verify**: User B has Beacon Opaque with child key +10. Test APIs for both users + +--- + +## Additional Test Coverage + +### Legacy Login Tests + +**Test Class**: `LegacyLoginTests` +**Description**: Tests for traditional Connected App (CA) authentication flows with default, subset, and all scopes using hybrid authentication. Tests both web server and user agent OAuth flows. + +#### CA Web Server Flow Tests +- `testCAOpaque_DefaultScopes_WebServerFlow` - Default scopes, web server flow, hybrid +- `testCAOpaque_SubsetScopes_WebServerFlow` - Subset scopes, web server flow, hybrid +- `testCAOpaque_AllScopes_WebServerFlow` - All scopes, web server flow, hybrid + +#### CA User Agent Flow Tests +- `testCAOpaque_DefaultScopes_UserAgentFlow` - Default scopes, user agent flow, hybrid +- `testCAOpaque_SubsetScopes_UserAgentFlow` - Subset scopes, user agent flow, hybrid +- `testCAOpaque_AllScopes_UserAgentFlow` - All scopes, user agent flow, hybrid + +### Legacy Login Tests (Non-Hybrid) + +**Test Class**: `LegacyLoginTestsNotHybrid` (extends `LegacyLoginTests`) +**Description**: Tests for traditional Connected App (CA) authentication flows using non-hybrid authentication. Extends `LegacyLoginTests` and overrides `useHybridFlow()` to return false. Non-hybrid flow means the app does not receive front-door session cookies (SIDs) for Lightning, Visualforce, and Content domains during authentication. + +#### CA Web Server Flow Tests (Non-Hybrid) +- `testCAOpaque_DefaultScopes_WebServerFlow` - Default scopes, web server flow, non-hybrid +- `testCAOpaque_SubsetScopes_WebServerFlow` - Subset scopes, web server flow, non-hybrid +- `testCAOpaque_AllScopes_WebServerFlow` - All scopes, web server flow, non-hybrid + +#### CA User Agent Flow Tests (Non-Hybrid) +- `testCAOpaque_DefaultScopes_UserAgentFlow` - Default scopes, user agent flow, non-hybrid +- `testCAOpaque_SubsetScopes_UserAgentFlow` - Subset scopes, user agent flow, non-hybrid +- `testCAOpaque_AllScopes_UserAgentFlow` - All scopes, user agent flow, non-hybrid + +### Beacon Tests with Advanced Authentication + +#### Advanced Auth Beacon Tests +**Test Class**: `AdvancedAuthBeaconLoginTests` (extends `BeaconLoginTests`) +**Description**: Same beacon tests but using advanced authentication login host + +Tests all beacon scenarios with `.advancedAuth` login host instead of `.regularAuth`: +- Opaque tokens (default, subset, all scopes) +- JWT tokens (default, subset, all scopes) + +### Welcome Discovery Tests + +#### Welcome Discovery Flow Tests +**Test Class**: `WelcomeLoginTests` +**Description**: Tests for welcome.salesforce.com/discovery domain-based login + +- `testWelcomeDiscovery_RegularAuthLoginHost` - Discovery with regular auth host, static config +- `testWelcomeDiscovery_AdvancedAuthLoginHost` - Discovery with advanced auth host, static config +- `testWelcomeDiscovery_RegularAuthLoginHost_DynamicConfig` - Discovery with dynamic config selection +- `testWelcomeDiscovery_AdvancedAuthLoginHost_DynamicConfig` - Discovery with dynamic and advanced auth + +**Manual Execution**: +1. Configure login host: `welcome.salesforce.com/discovery` +2. Configure discovery settings (username pre-fill) +3. Login will redirect to user's org after domain discovery + +--- + +## Test Configuration + +### User Configuration +Tests use different users from `ui_test_config.json` to avoid login conflicts when running test suites in parallel (max 5 concurrent logins per user): +- `.first` - Used by LegacyLoginTests, ECALoginTests, BeaconLoginTests, WelcomeLoginTests +- `.second` - Used by RefreshTokenMigrationTests, AdvancedAuthBeaconLoginTests, and LoginWithRestartTests (dynamic config tests) +- `.third` - Used by RefreshTokenMigrationWithRestartTests and LoginWithRestartTests (persistence tests) +- `.fourth` and `.fifth` - Used for multi-user tests (including token revocation) in MultiUserLoginTests and related scenarios + +### App Configurations +Tests reference these app configurations: +- `caOpaque` - Connected App with opaque tokens +- `caJwt` - Connected App with JWT tokens +- `ecaOpaque` - External Client App with opaque tokens +- `ecaJwt` - External Client App with JWT tokens +- `beaconOpaque` - Beacon app with opaque tokens +- `beaconJwt` - Beacon app with JWT tokens +- `invalid` - Invalid app configuration (consumer key: "invalid_consumer_key", redirect URI: "invalid://callback", scopes: "invalid_scope") for negative testing + +### Scope Selections +- `.empty` - No scopes specified (default to all server scopes) +- `.subset` - Subset of scopes (all scopes except `sfap_api`) +- `.all` - All available scopes explicitly requested +- `.invalid` - Invalid scope ("invalid_scope") for negative testing scenarios + +### Login Hosts +- `.regularAuth` - Standard authentication endpoint +- `.advancedAuth` - Advanced authentication endpoint (for beacon tests) + +--- + +## Validation Steps Performed in All Tests + +Each automated test performs these validations: + +1. **Main Page Loads** + - Verify AuthFlowTester main screen appears + +2. **User Credentials Validation** + - Username matches expected user + - Client ID (consumer key) matches expected app + - Redirect URI matches expected app + - Granted scopes match expected scopes + - Token format (JWT vs opaque) correct + +3. **OAuth Configuration Validation** + - Static consumer key matches bootconfig + - Static callback URL matches bootconfig + - Static scopes match bootconfig (persists across restarts) + +4. **JWT Details Validation** (if applicable) + - JWT access token details visible + - Scopes in JWT match expected + - Client ID in JWT matches + - Expiration time present + +5. **SID Validation** + - Session ID format validated based on hybrid vs non-hybrid flow + +6. **URL Validation** + - URLs validated based on web server vs user agent flow + +7. **Token Refresh Cycle** + - Revoke access token + - Make REST API request + - Verify access token changed (refreshed) + - Verify API call succeeds + +8. **Beacon Child Key Validation** (if applicable) + - Beacon child consumer key present when using beacon app + - Absent when not using beacon app + +--- + +## Notes + +- Tests are designed to be independent and can run in any order +- Each test performs cleanup in `tearDown()` by logging out +- All tests include automatic token refresh validation +- Multi-user tests verify isolation between user sessions +- Migration tests verify refresh token changes after migration +- Restart tests verify persistence of credentials across app restarts diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/AuthFlowTesterMainPageObject.swift b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/AuthFlowTesterMainPageObject.swift index 30dde2a48a..985872b21b 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/AuthFlowTesterMainPageObject.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/AuthFlowTesterMainPageObject.swift @@ -441,11 +441,11 @@ class AuthFlowTesterMainPageObject { // Auth flow switches private func useWebServerFlowSwitch() -> XCUIElement { - return app.switches["Use Web Server Flow"] + return app.switches["useWebServerFlowToggle"] } private func useHybridSwitch() -> XCUIElement { - return app.switches["Use Hybrid Flow"] + return app.switches["useHybridFlowToggle"] } // MARK: - Actions @@ -485,7 +485,7 @@ class AuthFlowTesterMainPageObject { let currentValue = (switchField.value as? String) == "1" if currentValue != value { - tap(switchField) + switchField.tap() } } diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/LoginOptionsPageObject.swift b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/LoginOptionsPageObject.swift index 735b1b29ee..3917680346 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/LoginOptionsPageObject.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/LoginOptionsPageObject.swift @@ -127,11 +127,11 @@ class LoginOptionsPageObject { // MARK: - UI Element Accessors (LoginOptionsView) private func useWebServerFlowSwitch() -> XCUIElement { - return app.switches["Use Web Server Flow"] + return app.switches["useWebServerFlowToggle"] } private func useHybridSwitch() -> XCUIElement { - return app.switches["Use Hybrid Flow"] + return app.switches["useHybridFlowToggle"] } /// Returns the import button for either the static or dynamic configuration section. @@ -163,7 +163,7 @@ class LoginOptionsPageObject { let currentValue = (switchField.value as? String) == "1" if currentValue != value { - tap(switchField) + switchField.tap() } } } diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/RefreshTokenMigrationTests.swift b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/RefreshTokenMigrationTests.swift index 58b0e28407..f8e0b1b2e6 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/RefreshTokenMigrationTests.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/RefreshTokenMigrationTests.swift @@ -104,8 +104,25 @@ class RefreshTokenMigrationTests: BaseAuthFlowTester { ) } - // Migrate from CA (user agent) to Beacon (web server) - func testMigrateCAUserAgentToBeaconWebServer() throws { + // Migrate from Beacon to CA + func testMigrateBeaconToCA() throws { + launchAndLogin( + loginHost: .regularAuth, + user:.second, + staticAppConfigName: .beaconOpaque + ) + migrateAndValidate( + loginHost: .regularAuth, + staticAppConfigName: .beaconOpaque, + migrationAppConfigName: .caOpaque + ) + } + + // MARK: - Migration with auth flow type change (user agent to web server flow) + + + // Migrate from CA (user agent) to ECA (web server) + func testMigrateCAUserAgentToECAWebServer() throws { launchAndLogin( loginHost: .regularAuth, user:.second, @@ -115,22 +132,24 @@ class RefreshTokenMigrationTests: BaseAuthFlowTester { migrateAndValidate( loginHost: .regularAuth, staticAppConfigName: .caOpaque, - migrationAppConfigName: .beaconOpaque, + migrationAppConfigName: .ecaOpaque, migrationUseWebServerFlow: true ) } - // Migrate from Beacon to CA - func testMigrateBeaconToCA() throws { + // Migrate from CA (user agent) to Beacon (web server) + func testMigrateCAUserAgentToBeaconWebServer() throws { launchAndLogin( loginHost: .regularAuth, user:.second, - staticAppConfigName: .beaconOpaque + staticAppConfigName: .caOpaque, + useWebServerFlow: false ) migrateAndValidate( loginHost: .regularAuth, - staticAppConfigName: .beaconOpaque, - migrationAppConfigName: .caOpaque + staticAppConfigName: .caOpaque, + migrationAppConfigName: .beaconOpaque, + migrationUseWebServerFlow: true ) } diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/overview.md b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/overview.md index df2f3ac833..da1878ed86 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/overview.md +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/overview.md @@ -118,7 +118,7 @@ Tests for verifying that user sessions persist across app restarts. Includes CA, ## Migration Tests -### RefreshTokenMigrationTests (9 tests) +### RefreshTokenMigrationTests (11 tests) Tests for migrating refresh tokens between different app configurations without re-authentication. Tests can optionally specify the OAuth flow type (web server vs user agent) and hybrid flow setting to use during migration. @@ -129,6 +129,8 @@ Tests for migrating refresh tokens between different app configurations without | `testMigrateBeacon_AddMoreScopes` | Beacon JWT (subset) | Beacon JWT (all) | Yes | No | | `testMigrateCAToBeacon` | CA Opaque | Beacon Opaque | No | No | | `testMigrateBeaconToCA` | Beacon Opaque | CA Opaque | No | No | +| `testMigrateCAUserAgentToECAWebServer` | CA Opaque (user agent) | ECA Opaque (web server) | No | No | +| `testMigrateCAUserAgentToBeaconWebServer` | CA Opaque (user agent) | Beacon Opaque (web server) | No | No | | `testMigrateCAToECA` | CA Opaque → ECA Opaque → CA Opaque | No | No | | `testMigrateCAToBeaconAndBack` | CA Opaque → Beacon Opaque → CA Opaque | No | No | | `testMigrateBeaconOpaqueToJWTAndBack` | Beacon Opaque → Beacon JWT → Beacon Opaque | No | No | @@ -150,21 +152,23 @@ Tests for verifying that migrated refresh tokens persist across app restarts. Co ## Multi-User Tests -### MultiUserLoginTests (9 tests) - -Tests for login scenarios with two users using various configurations, including token revocation scenarios. - -| Test Name | User 1 Config | User 2 Config | Same App | Same Scopes | Beacon | Token Revocation | -|-----------|---------------|---------------|----------|-------------|--------|------------------| -| `testBothStatic_SameApp_SameScopes` | Static (Opaque) | Static (Opaque) | Yes | Yes | No | No | -| `testBothStatic_DifferentApps` | Static (Opaque) | Static (JWT) | No | Yes | No | No | -| `testBothStatic_SameApp_DifferentScopes` | Static (Opaque, subset) | Static (Opaque, default) | Yes | No | No | No | -| `testFirstStatic_SecondDynamic_DifferentApps` | Static (Opaque) | Dynamic (JWT) | No | Yes | No | No | -| `testFirstDynamic_SecondStatic_DifferentApps` | Dynamic (JWT) | Static (Opaque) | No | Yes | No | No | -| `testBothDynamic_DifferentApps` | Dynamic (Opaque) | Dynamic (JWT) | No | Yes | No | No | -| `testBeaconAndNonBeacon_MultiUser` | Beacon (Opaque) | CA (Opaque) | No | Yes | Yes | No | -| `testRevokeUserWithDynamicConfig_OtherUserUnaffected` | ECA (Opaque) static | ECA (JWT) dynamic | No | Yes | No | Yes (User B) | -| `testDifferentAppTypes_RevokeCaUser_EcaUserUnaffected` | CA (Opaque) | ECA (Opaque) | No | Yes | No | Yes (User A) | +### MultiUserLoginTests (11 tests) + +Tests for login scenarios with two users using various configurations, including token revocation and user logout scenarios. + +| Test Name | User 1 Config | User 2 Config | Same App | Same Scopes | Beacon | Action | +|-----------|---------------|---------------|----------|-------------|--------|--------| +| `testBothStatic_SameApp_SameScopes` | Static (Opaque) | Static (Opaque) | Yes | Yes | No | None | +| `testBothStatic_DifferentApps` | Static (Opaque) | Static (JWT) | No | Yes | No | None | +| `testBothStatic_SameApp_DifferentScopes` | Static (Opaque, subset) | Static (Opaque, default) | Yes | No | No | None | +| `testFirstStatic_SecondDynamic_DifferentApps` | Static (Opaque) | Dynamic (JWT) | No | Yes | No | None | +| `testFirstDynamic_SecondStatic_DifferentApps` | Dynamic (JWT) | Static (Opaque) | No | Yes | No | None | +| `testBothDynamic_DifferentApps` | Dynamic (Opaque) | Dynamic (JWT) | No | Yes | No | None | +| `testBeaconAndNonBeacon_MultiUser` | Beacon (Opaque) | CA (Opaque) | No | Yes | Yes | None | +| `testRevokeAccessForUserWithDynamicConfig_OtherUserUnaffected` | ECA (Opaque) static | ECA (JWT) dynamic | No | Yes | No | Revoke User B | +| `testDifferentAppTypes_RevokeAccessForCaUser_EcaUserUnaffected` | CA (Opaque) | ECA (Opaque) | No | Yes | No | Revoke User A | +| `testLogoutUserWithDynamicConfig_OtherUserUnaffected` | ECA (Opaque) static | ECA (JWT) dynamic | No | Yes | No | Logout User B | +| `testDifferentAppTypes_LogoutCaUser_EcaUserUnaffected` | CA (Opaque) | ECA (Opaque) | No | Yes | No | Logout User A | --- From 75e9071011de32962d4ebe4f28417497f9857352 Mon Sep 17 00:00:00 2001 From: Wolfgang Mathurin Date: Wed, 11 Mar 2026 15:01:27 -0700 Subject: [PATCH 07/10] Not checking long doc --- .../AUTOMATED_TEST_SCENARIOS.md | 1099 ----------------- 1 file changed, 1099 deletions(-) delete mode 100644 native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/AUTOMATED_TEST_SCENARIOS.md diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/AUTOMATED_TEST_SCENARIOS.md b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/AUTOMATED_TEST_SCENARIOS.md deleted file mode 100644 index c5d9847318..0000000000 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/AUTOMATED_TEST_SCENARIOS.md +++ /dev/null @@ -1,1099 +0,0 @@ -# AuthFlowTester Automated Test Scenarios - -## Overview - -This document describes the automated UI test scenarios for AuthFlowTester, organized according to the Mobile SDK 13.2 high-level test plans. The automated tests validate three key features introduced in Mobile SDK 13.2: - -1. **Dynamic Scope Retrieval** - Applications can omit scopes in bootconfig and receive all configured scopes from the server -2. **Runtime Consumer Key Selection** - Applications can override the consumer key at runtime via dynamic configuration -3. **Refresh Token Migration** - Applications can exchange refresh tokens between different consumer keys and scopes - -## Related Resources - -- [13.2 Test Org Setup for Authentication Testing](https://drive.google.com/file/d/1SiXgxD46_LBhUATvt8kGYmmcoQd7reIgU0NICGbK-Ks/view) - User accounts and server-side app configuration -- [13.2 Dynamic Scope Retrieval High Level Test Plan](https://drive.google.com/file/d/14Ioi4qwRj3Gnsc67Fm59HoiaD2vnUH0IKAJZ5p9GX60/view) -- [13.2 Runtime Consumer Key Selection High Level Test Plan](https://drive.google.com/file/d/1-sFppGcou7XPhCpXXzOHix3Tk188uyddnnYp3R6_Vrk/view) -- [13.2 Refresh Token Migration High Level Test Plan](https://drive.google.com/file/d/19-o-3oQbDS_Dsv3hSEd8H-Me0lmGlmw2zZaIpiOC-pU/view) - -## General Test Execution Instructions - -### Prerequisites - -1. **Test Accounts and Server Setup**: User accounts and server-side apps (ECA, CA, Beacon) configured per the [13.2 Test Org Setup for Authentication Testing](https://drive.google.com/file/d/1SiXgxD46_LBhUATvt8kGYmmcoQd7reIgU0NICGbK-Ks/view) document -2. **Test Configuration**: User credentials and app configurations defined in `ui_test_config.json` - -Note: The automated tests configure the consumer key and login server dynamically - no bootconfig changes are required. - -### How to Execute Test Scenarios Manually - -All test scenarios follow this general pattern: - -1. **Launch AuthFlowTester** -2. **Configure Login Options** (via Login Settings menu on login screen) - - Click the settings/gear icon on the login screen - - Configure Static Configuration (equivalent to bootconfig - persists across app restarts) - - OR Configure Dynamic Configuration (runtime override - does not persist) - - Select auth flow type (Web Server Flow vs User Agent Flow, Hybrid vs Non-Hybrid) - - Save/apply settings -3. **Perform Login** - - Enter credentials - - Complete authentication -4. **Validate Credentials** - - Expand "User Credentials" section - verify username, client ID, redirect URI, granted scopes, token format - - Expand "OAuth Configuration" section - verify static configuration persists - - If JWT token: expand "JWT Access Token Details" - verify expiration, scopes, client ID -5. **Test Token Operations** - - Click "Revoke Access Token" - verify token is revoked - - Click "Make Rest API request" - verify access token is refreshed and API succeeds -6. **Multi-User Operations** (if applicable) - - Click "Users" icon (bottom bar) - add or switch users -7. **Migration Operations** (if applicable) - - Click "Key" icon (bottom bar) - configure new app configuration - - Configure auth flow type (Web Server Flow vs User Agent Flow, Hybrid vs Non-Hybrid) - - Verify refresh token changes in "User Credentials" section - -**Note**: The automated tests access the login settings menu directly through the UI test framework. Manual testers can also access login configuration through the Dev Support Menu (^+⌘+Z or shake device) if needed. - -### Negative Testing Support - -The test suite includes support for negative testing scenarios through invalid configurations: - -- **Invalid App Configuration** (`.invalid`): Uses hard-coded invalid values (consumer key: "invalid_consumer_key", redirect URI: "invalid://callback") to test error handling when authentication fails -- **Invalid Scopes** (`.invalid`): Requests "invalid_scope" to test error handling when server rejects invalid scope requests - -These can be used in automated tests by specifying: -```swift -// Test with invalid app config -launchLoginAndValidate(staticAppConfigName: .invalid) - -// Test with invalid scopes -launchLoginAndValidate(staticAppConfigName: .caOpaque, staticScopeSelection: .invalid) -``` - ---- - -## 1. Dynamic Scope Retrieval High Level Test Plan - -### Overview -Tests for the feature that allows applications to omit scopes from bootconfig and dynamically receive all scopes configured on the server. Scopes granted are saved with the user account and can be queried by the application. - -### Test Groups - -#### Group 1: Basic Login / Scope Behavior - -##### Test 1.1: Login with Default Scopes (No Explicit Scopes) -**Automated Test**: `ECALoginTests.testECAOpaque_DefaultScopes`, `ECALoginTests.testECAJwt_DefaultScopes` -**Maps To**: High-Level Test Case 1.1 -**Description**: Login using ECA with no explicitly requested scopes - -**Manual Execution Steps**: -1. Launch AuthFlowTester -2. Click the settings/gear icon on the login screen to access login options -3. Expand "Static Configuration" -4. Configure: - - Consumer Key: ECA-AllScopes consumer key - - Scopes: (leave empty to request all scopes) - - Auth Flow: Web Server Flow (Hybrid) -5. Click "Use static config" -6. Enter credentials and login -8. Expand "User Credentials" section -9. **Verify**: Granted scopes = id, api, refresh_token (all scopes defined on ECA-AllScopes) -10. Click "Make Rest API request" -11. **Verify**: API call succeeds -12. Click "Revoke Access Token" -13. Click "Make Rest API request" again -14. **Verify**: Access token is refreshed automatically, API call succeeds - -##### Test 1.2: Login Explicitly Requesting Scopes -**Automated Test**: `ECALoginTests.testECAOpaque_AllScopes`, `ECALoginTests.testECAJwt_AllScopes` -**Maps To**: High-Level Test Case 1.2 -**Description**: Login requesting specific scopes (id, api, refresh_token) - -**Manual Execution Steps**: -1. Launch AuthFlowTester -2. Click the settings/gear icon on the login screen to access login options -3. Expand "Static Configuration" -4. Configure: - - Consumer Key: ECA-AllScopes consumer key - - Scopes: `id api refresh_token` -5. Click "Use static config" -6. Enter credentials and login -8. Expand "User Credentials" section -9. **Verify**: Granted scopes = id, api, refresh_token (exactly as requested) -10. Click "Make Rest API request" -11. **Verify**: API call succeeds - -##### Test 1.3: Login with Subset of Scopes -**Automated Test**: `ECALoginTests.testECAOpaque_SubsetScopes`, `ECALoginTests.testECAJwt_SubsetScopes` -**Maps To**: High-Level Test Case 1.4 -**Description**: Login requesting only id and refresh_token (omitting api scope) - -**Manual Execution Steps**: -1. Launch AuthFlowTester -2. Click the settings/gear icon on the login screen to access login options -3. Expand "Static Configuration" -4. Configure: - - Consumer Key: ECA-AllScopes consumer key - - Scopes: `id refresh_token` -5. Click "Use static config" -6. Enter credentials and login -8. Expand "User Credentials" section -9. **Verify**: Granted scopes = id, refresh_token (no api scope) -10. Click "Make Rest API request" -11. **Verify**: API call fails (missing api scope) - -#### Group 2: API Call & Token Behavior - -##### Test 2.1: Call API After Successful Login -**Automated Test**: All login tests include API validation -**Maps To**: High-Level Test Case 2.1 -**Description**: Verify API calls work after login - -**Manual Execution Steps**: -1. Complete any successful login test (e.g., Test 1.1) -2. Click "Make Rest API request" -3. **Verify**: API returns success response -4. **Verify**: Request/response details shown - -##### Test 2.2: Revoke Access Token and Refresh -**Automated Test**: Built into all validation flows via `assertRevokeAndRefreshWorks` -**Maps To**: High-Level Test Case 2.2 -**Description**: Verify automatic token refresh after revocation - -**Manual Execution Steps**: -1. Complete any successful login test -2. Note current access token value in "User Credentials" -3. Click "Revoke Access Token" -4. **Verify**: Access token is cleared/invalidated -5. Click "Make Rest API request" -6. **Verify**: New access token appears in "User Credentials" -7. **Verify**: Access token value changed -8. **Verify**: API call succeeds - -#### Group 3: Multi-User Scenarios - -##### Test 3.1: Multiple Users with Different Apps -**Automated Test**: `MultiUserLoginTests.testBothStatic_DifferentApps` -**Maps To**: High-Level Test Case 3.1 -**Description**: Login User A with one ECA, then User B with different ECA - -**Manual Execution Steps**: -1. Launch AuthFlowTester -2. Login User A with ECA-AllScopes (default scopes) -3. Verify User A credentials show correct scopes -4. Click "Users" icon (bottom bar) -5. Click "Add User" -6. Login User B with ECA-NewScopeAdded (default scopes) -7. **Verify**: User B credentials show ECA-NewScopeAdded scopes (including sfap_api) -8. Switch back to User A -9. **Verify**: User A credentials unchanged (no sfap_api scope) -10. Switch back to User B -11. **Verify**: User B credentials unchanged (still has sfap_api scope) - -##### Test 3.2: Multiple Users with Same App, Different Scopes -**Automated Test**: `MultiUserLoginTests.testBothStatic_SameApp_DifferentScopes` -**Maps To**: Related to High-Level Test Case 3.1 -**Description**: Two users with same ECA but different scope selections - -**Manual Execution Steps**: -1. Launch AuthFlowTester -2. Login User A with ECA-AllScopes, subset scopes (id, refresh_token) -3. Verify User A has only id, refresh_token scopes -4. Click "Users" icon, add User B -5. Login User B with ECA-AllScopes, default scopes (all) -6. **Verify**: User B has id, api, refresh_token scopes -7. Switch between users -8. **Verify**: Each user retains their respective scopes - -##### Test 3.3: Revoke One User's Token -**Automated Test**: `MultiUserLoginTests.testRevokeAccessForUserWithDynamicConfig_OtherUserUnaffected` -**Maps To**: High-Level Test Case 3.2 -**Description**: Revoke user's access token (who uses dynamic config), verify other user unaffected - -**Manual Execution Steps**: -1. Login User A and User B (following Test 3.1) -2. Switch to User B -3. Click "Revoke Access Token" -4. Switch to User A -5. Click "Make Rest API request" -6. **Verify**: User A's API call succeeds without needing refresh -7. Switch back to User B -8. Click "Make Rest API request" -9. **Verify**: User B's access token is refreshed, API succeeds - -#### Group 4: Refresh Token Migration (Scope Changes) - -##### Test 4.1: Migrate with More Scopes on Server -**Automated Test**: `RefreshTokenMigrationTests.testMigrateCA_AddMoreScopes`, `RefreshTokenMigrationTests.testMigrateECA_AddMoreScopes` -**Maps To**: High-Level Test Case 4.1 -**Description**: Login with subset scopes, migrate requesting all scopes - -**Manual Execution Steps**: -1. Launch AuthFlowTester -2. Login with ECA-AllScopes, subset scopes (id, refresh_token) -3. Note refresh token value in "User Credentials" -4. Click "Key" icon (bottom bar) -5. Configure migration auth flow type (optional): - - Use Web Server Flow: On/Off - - Use Hybrid Flow: On/Off -6. Configure migration: - - Consumer Key: ECA-AllScopes (same) - - Scopes: `id api refresh_token` (all scopes) -7. Click "Migrate" -8. **Verify**: Refresh token value changed -9. **Verify**: Granted scopes now include api scope -10. Click "Make Rest API request" -11. **Verify**: API call now succeeds - -##### Test 4.2: Migrate with Fewer Scopes -**Automated Test**: Part of migration test validation -**Maps To**: High-Level Test Case 4.2, 4.4 -**Description**: Login with all scopes, migrate requesting subset - -**Manual Execution Steps**: -1. Launch AuthFlowTester -2. Login with ECA-AllScopes, all scopes (id, api, refresh_token) -3. Verify API call works -4. Click "Key" icon -5. Configure migration auth flow type (optional): - - Use Web Server Flow: On/Off - - Use Hybrid Flow: On/Off -6. Configure migration: - - Consumer Key: ECA-AllScopes (same) - - Scopes: `id refresh_token` (subset) -7. Click "Migrate" -8. **Verify**: Refresh token changed -9. **Verify**: Granted scopes = id, refresh_token (api removed) -10. Click "Make Rest API request" -11. **Verify**: API call fails (missing api scope) - -##### Test 4.3: Migrate from Explicit to Implicit Scopes -**Automated Test**: Implicitly tested in migration tests -**Maps To**: High-Level Test Case 4.3 -**Description**: Login with subset, migrate with no explicit scopes - -**Manual Execution Steps**: -1. Launch AuthFlowTester -2. Login with ECA-AllScopes, subset scopes (id, refresh_token) -3. Verify only id, refresh_token scopes present -4. Click "Key" icon -5. Configure migration auth flow type (optional): - - Use Web Server Flow: On/Off - - Use Hybrid Flow: On/Off -6. Configure migration: - - Consumer Key: ECA-AllScopes (same) - - Scopes: (leave empty for all server-defined scopes) -7. Click "Migrate" -8. **Verify**: Refresh token changed -9. **Verify**: Granted scopes = id, api, refresh_token (all ECA scopes) -10. Click "Make Rest API request" -11. **Verify**: API call succeeds - -#### Group 5: Negative Testing Scenarios - -##### Test 5.1: Dynamic Configuration with Invalid Client ID -**Automated Test**: `ECALoginTests.testDynamicConfigurationWithInvalidClientId` -**Maps To**: Negative Testing -**Description**: Attempt login with invalid client ID in dynamic configuration to verify error handling - -**Manual Execution Steps**: -1. Launch AuthFlowTester -2. Click the settings/gear icon on the login screen to access login options -3. Expand "Static Configuration" - configure ECA Opaque (valid config) -4. Expand "Dynamic Configuration" - configure with invalid client ID: - - Consumer Key: `invalid_consumer_key` - - Redirect URI: `invalid://callback` - - Scopes: (leave empty) -5. Click "Use dynamic config" -6. Enter credentials and attempt login -7. **Verify**: Login fails with appropriate error -8. **Verify**: Error indicates invalid client credentials - -##### Test 5.2: Dynamic Configuration with Invalid Scope -**Automated Test**: `ECALoginTests.testDynamicConfigurationWithInvalidScope` -**Maps To**: Negative Testing -**Description**: Attempt login with invalid scope in dynamic configuration to verify scope validation - -**Manual Execution Steps**: -1. Launch AuthFlowTester -2. Click the settings/gear icon on the login screen to access login options -3. Expand "Static Configuration" - configure ECA Opaque (valid config) -4. Expand "Dynamic Configuration" - configure ECA JWT with invalid scope: - - Consumer Key: ECA-JWT consumer key - - Scopes: `invalid_scope` -5. Click "Use dynamic config" -6. Enter credentials and attempt login -7. **Verify**: Login fails or completes with no scopes granted -8. **Verify**: Invalid scope is not in granted scopes - ---- - -## 2. Runtime Consumer Key Selection High Level Test Plan - -### Overview -Tests for the feature that allows applications to override the consumer key at runtime via dynamic configuration (block/lambda), enabling different consumer keys per login host without recompiling the app. - -### Test Groups - -#### Group 1: Base Functional Scenarios - -##### Test BF-01: Login with Dynamic Config -**Automated Test**: `LoginWithRestartTests.testECAJwt_DefaultScopes_DynamicConfiguration_WithRestart` -**Maps To**: High-Level Test Case BF-01 -**Description**: Use dynamic configuration to specify consumer key at runtime - -**Manual Execution Steps**: -1. Launch AuthFlowTester -2. Click the settings/gear icon on the login screen to access login options -3. Expand "Static Configuration" - configure ECA-Opaque (will be in bootconfig) -4. Expand "Dynamic Configuration" - configure ECA-JWT (different consumer key) -5. Click "Use dynamic config" -6. Enter credentials and login -8. Expand "User Credentials" section -9. **Verify**: Client ID = ECA-JWT consumer key (not ECA-Opaque) -10. **Verify**: Token format = jwt -11. Expand "OAuth Configuration" section -12. **Verify**: Static config shows ECA-Opaque settings (not used for this user) -13. Click "Make Rest API request" -14. **Verify**: API call succeeds - -##### Test BF-02: Access Token Revocation and Refresh -**Automated Test**: Built into all dynamic config tests -**Maps To**: High-Level Test Case BF-02 -**Description**: Verify token refresh works with dynamic configuration - -**Manual Execution Steps**: -1. Complete Test BF-01 -2. Note access token value -3. Click "Revoke Access Token" -4. **Verify**: Access token cleared -5. Click "Make Rest API request" -6. **Verify**: Access token refreshed (new value) -7. **Verify**: API call succeeds - -#### Group 2: Multi-User Scenarios - -##### Test MU-01: Two Users with Different Configs -**Automated Test**: `MultiUserLoginTests.testFirstStatic_SecondDynamic_DifferentApps` -**Maps To**: High-Level Test Case MU-01 -**Description**: User A uses static config, User B uses dynamic config - -**Manual Execution Steps**: -1. Launch AuthFlowTester -2. Login User A with static config (ECA-Opaque) -3. Verify User A has ECA-Opaque consumer key -4. Click "Users" icon, add User B -5. Configure dynamic config for ECA-JWT -6. Login User B with dynamic config -7. **Verify**: User B has ECA-JWT consumer key -8. Switch to User A -9. **Verify**: User A still has ECA-Opaque settings -10. Switch to User B -11. **Verify**: User B still has ECA-JWT settings -12. **Verify**: Configs are isolated per user - -##### Test MU-02: Revoke One User's Access Token -**Automated Test**: `MultiUserLoginTests.testRevokeAccessForUserWithDynamicConfig_OtherUserUnaffected` -**Maps To**: High-Level Test Case MU-02 -**Description**: Revoke user's token (who uses dynamic config), other user unaffected - -**Manual Execution Steps**: -1. Setup two users (following Test MU-01) -2. Switch to User A -3. Click "Revoke Access Token" -4. Switch to User B -5. Click "Make Rest API request" -6. **Verify**: User B's API succeeds without refresh -7. Switch to User A -8. Click "Make Rest API request" -9. **Verify**: User A's token refreshed automatically - -##### Test MU-02a: Logout One User -**Automated Test**: `MultiUserLoginTests.testLogoutUserWithDynamicConfig_OtherUserUnaffected` -**Maps To**: High-Level Test Case MU-02 (variation with logout) -**Description**: Logout user with dynamic config, verify other user unaffected and automatic switch occurs - -**Manual Execution Steps**: -1. Login User A with ECA Opaque (static) -2. Add User B with ECA JWT (dynamic) -3. Currently on User B - Click "Logout" icon (bottom bar) and confirm -4. **Verify**: App automatically switches to User A -5. **Verify**: User A's credentials unchanged -6. Click "Make Rest API request" -7. **Verify**: User A's API succeeds -8. **Verify**: Cannot switch back to User B (logged out) - -##### Test MU-03: Both Users Use Dynamic Config -**Automated Test**: `MultiUserLoginTests.testBothDynamic_DifferentApps` -**Maps To**: High-Level Test Case MU-01, MU-04 -**Description**: Two users both using dynamic configuration with different apps - -**Manual Execution Steps**: -1. Launch AuthFlowTester -2. Login User A with dynamic config (ECA-Opaque) -3. Add User B, login with dynamic config (ECA-JWT) -4. **Verify**: Each user has correct consumer key and token format -5. Switch between users multiple times -6. **Verify**: Dynamic configs persist per user -7. Make API calls for each user -8. **Verify**: API calls work correctly for both users - -##### Test MU-04: Mixed Static and Dynamic Scopes -**Automated Test**: `MultiUserLoginTests.testFirstDynamic_SecondStatic_DifferentApps` -**Maps To**: Related to High-Level Test Case MU-04 -**Description**: First user dynamic config, second user static config - -**Manual Execution Steps**: -1. Launch AuthFlowTester -2. Login User A with dynamic config (ECA-JWT) -3. Add User B with static config (ECA-Opaque) -4. **Verify**: User A has ECA-JWT (from dynamic) -5. **Verify**: User B has ECA-Opaque (from static) -6. Switch between users -7. **Verify**: Each retains correct configuration -8. Test API calls for both -9. **Verify**: Both work independently - -#### Group 3: App Restart Scenarios - -##### Test RS-01: Restart After Login -**Automated Test**: `DynamicConfigLoginTests` tests with `restartAndValidate` -**Maps To**: High-Level Test Case RS-01 -**Description**: Verify user session and dynamic config persist after restart - -**Manual Execution Steps**: -1. Login with dynamic config (ECA-JWT) -2. Verify credentials correct -3. Terminate app (swipe up from app switcher) -4. Launch app again -5. **Verify**: User still logged in -6. **Verify**: User credentials show ECA-JWT consumer key -7. Click "Make Rest API request" -8. **Verify**: API call succeeds - -##### Test RS-02: Restart, Revoke, and Refresh -**Automated Test**: Covered by restart validation in dynamic config tests -**Maps To**: High-Level Test Case RS-02 -**Description**: After restart, verify token refresh still works - -**Manual Execution Steps**: -1. Complete Test RS-01 -2. Click "Revoke Access Token" -3. Click "Make Rest API request" -4. **Verify**: Access token refreshed -5. **Verify**: API call succeeds - -##### Test RS-03: Multi-User Restart -**Automated Test**: `LoginWithRestartTests.testMultiUserRestart` -**Maps To**: High-Level Test Case RS-03 -**Description**: Restart with multiple users, verify all persist - -**Manual Execution Steps**: -1. Login User A with dynamic config (ECA-Opaque) -2. Add User B with static config (ECA-JWT) -3. Terminate app -4. Launch app -5. **Verify**: User A appears in user switcher -6. **Verify**: User B appears in user switcher -7. Switch to each user -8. **Verify**: Each has correct consumer key -9. Make API calls -10. **Verify**: Both work correctly - -#### Group 4: Scope & Consumer Key Behavior - -##### Test SC-01: Request Subset of Scopes -**Automated Test**: `LoginWithRestartTests.testECAJwt_SubsetScopes_DynamicConfiguration_WithRestart` -**Maps To**: High-Level Test Case SC-01 -**Description**: Use dynamic config to request only specific scopes - -**Manual Execution Steps**: -1. Launch AuthFlowTester -2. Configure dynamic config: - - Consumer Key: ECA JWT - - Scopes: `id api` (subset only) -3. Login with dynamic config -4. **Verify**: Granted scopes = id, api only -5. **Verify**: No other scopes present - -##### Test SC-02: No Scopes Specified -**Automated Test**: `LoginWithRestartTests.testECAJwt_DefaultScopes_DynamicConfiguration_WithRestart` -**Maps To**: High-Level Test Case SC-02 -**Description**: Dynamic config with no scopes → get all ECA scopes - -**Manual Execution Steps**: -1. Launch AuthFlowTester -2. Configure dynamic config: - - Consumer Key: ECA JWT - - Scopes: (leave empty) -3. Login with dynamic config -4. **Verify**: Granted scopes include all available ECA scopes - ---- - -## 3. Refresh Token Migration High Level Test Plan - -### Overview -Tests for the feature that allows applications to exchange a refresh token for a new one with different consumer key and/or scopes, facilitating migration to Beacon apps, JWT tokens, and new scopes. The migration UI includes auth flow type configuration (Web Server Flow vs User Agent Flow, Hybrid vs Non-Hybrid) that can be set before performing the migration. - -### Test Groups - -#### Group 1: Baseline Login & Token Establishment - -##### Test 1.1: Login with CA -**Automated Test**: `LegacyLoginTests.testCAOpaque_DefaultScopes_WebServerFlow` -**Maps To**: High-Level Test Scenario 1.1 -**Description**: Establish baseline with Connected App - -**Manual Execution Steps**: -1. Launch AuthFlowTester -2. Login with CA Opaque (static config) -3. **Verify**: Login succeeds -4. **Verify**: Granted scopes = id, api, refresh_token -5. Click "Make Rest API request" -6. **Verify**: API works -7. Click "Revoke Access Token", then make API call -8. **Verify**: Token refresh works - -##### Test 1.2: Login with ECA -**Automated Test**: `ECALoginTests.testECAOpaque_DefaultScopes` -**Maps To**: High-Level Test Scenario 1.2 -**Description**: Establish baseline with External Client App - -**Manual Execution Steps**: -1. Launch AuthFlowTester -2. Login with ECA Opaque (static config) -3. **Verify**: Login succeeds -4. **Verify**: Granted scopes = id, api, refresh_token -5. Test API and refresh (same as Test 1.1) - -##### Test 1.3: Login with Beacon -**Automated Test**: `BeaconLoginTests.testBeaconOpaque_DefaultScopes` -**Maps To**: High-Level Test Scenario 1.3 -**Description**: Establish baseline with Beacon app - -**Manual Execution Steps**: -1. Launch AuthFlowTester -2. Configure login host for advanced authentication (if needed) -3. Login with Beacon Opaque (static config) -4. **Verify**: Login succeeds -5. **Verify**: Beacon child key appears in credentials -6. **Verify**: Granted scopes = id, api, refresh_token -7. Test API and refresh - -#### Group 2: Basic Migration Flows (Single User) - -##### Test 2.1: Migrate CA to ECA -**Automated Test**: `RefreshTokenMigrationTests.testMigrateCAToECA` -**Maps To**: High-Level Test Scenario 2.1 -**Description**: Migrate from Connected App to External Client App - -**Manual Execution Steps**: -1. Launch AuthFlowTester -2. Login with CA Opaque -3. Note refresh token value -4. Click "Key" icon (bottom bar) -5. Configure migration auth flow type (optional): - - Use Web Server Flow: On/Off - - Use Hybrid Flow: On/Off -6. Configure migration: - - Consumer Key: ECA Opaque - - Scopes: (empty for default) -7. Click "Migrate" -8. **Verify**: Refresh token changed (new value) -9. **Verify**: Client ID = ECA Opaque consumer key -10. **Verify**: Granted scopes = id, api, refresh_token -11. Click "Make Rest API request" -12. **Verify**: API succeeds with new token - -##### Test 2.2: Migrate CA to Beacon -**Automated Test**: `RefreshTokenMigrationTests.testMigrateCAToBeacon` -**Maps To**: High-Level Test Scenario 2.2 -**Description**: Migrate from Connected App to Beacon app - -**Manual Execution Steps**: -1. Launch AuthFlowTester -2. Login with CA Opaque -3. Note refresh token value -4. Click "Key" icon -5. Configure migration auth flow type (optional): - - Use Web Server Flow: On/Off - - Use Hybrid Flow: On/Off -6. Configure migration: - - Consumer Key: Beacon Opaque - - Scopes: (empty for default) -7. Click "Migrate" -8. **Verify**: Refresh token changed -9. **Verify**: Beacon child key appears in credentials -10. **Verify**: Client ID = Beacon Opaque consumer key -11. Click "Make Rest API request" -12. **Verify**: API succeeds - -##### Test 2.3: Migrate ECA with Scope Addition -**Automated Test**: `RefreshTokenMigrationTests.testMigrateECA_AddMoreScopes` -**Maps To**: High-Level Test Scenario 2.3 -**Description**: Migrate within same ECA, adding more scopes - -**Manual Execution Steps**: -1. Launch AuthFlowTester -2. Login with ECA JWT, subset scopes (id, refresh_token) -3. Verify API call fails (no api scope) -4. Click "Key" icon -5. Configure migration auth flow type (optional): - - Use Web Server Flow: On/Off - - Use Hybrid Flow: On/Off -6. Configure migration: - - Consumer Key: ECA JWT (same app) - - Scopes: (empty for all server scopes) -7. Click "Migrate" -8. **Verify**: Refresh token changed -9. **Verify**: Granted scopes now include all available scopes -10. Click "Make Rest API request" -11. **Verify**: API succeeds - -##### Test 2.4: Migrate Beacon with Scope Addition -**Automated Test**: `RefreshTokenMigrationTests.testMigrateBeacon_AddMoreScopes` -**Maps To**: High-Level Test Scenario 2.4 -**Description**: Migrate within Beacon, adding scopes - -**Manual Execution Steps**: -1. Launch AuthFlowTester -2. Login with Beacon JWT, subset scopes -3. Click "Key" icon -4. Configure migration: - - Consumer Key: Beacon JWT (same app) - - Scopes: (empty for all) -5. Click "Migrate" -6. **Verify**: Refresh token changed -7. **Verify**: Beacon child key retained -8. **Verify**: Scopes extended -9. Test API - -##### Test 2.4a: Migrate CA User Agent to ECA Web Server -**Automated Test**: `RefreshTokenMigrationTests.testMigrateCAUserAgentToECAWebServer` -**Maps To**: High-Level Test Scenario 2.1 (with auth flow type change) -**Description**: Migrate from CA with user agent flow to ECA with web server flow - -**Manual Execution Steps**: -1. Launch AuthFlowTester -2. Login with CA Opaque using user agent flow (disable "Use Web Server Flow" in login settings) -3. Note refresh token value -4. Click "Key" icon -5. Configure migration auth flow type: - - Use Web Server Flow: On - - Use Hybrid Flow: On (default) -6. Configure migration: - - Consumer Key: ECA Opaque - - Scopes: (empty for default) -7. Click "Migrate" -8. **Verify**: Refresh token changed -9. **Verify**: Client ID = ECA Opaque consumer key -10. Click "Make Rest API request" -11. **Verify**: API succeeds - -##### Test 2.4b: Migrate CA User Agent to Beacon Web Server -**Automated Test**: `RefreshTokenMigrationTests.testMigrateCAUserAgentToBeaconWebServer` -**Maps To**: High-Level Test Scenario 2.2 (with auth flow type change) -**Description**: Migrate from CA with user agent flow to Beacon with web server flow - -**Manual Execution Steps**: -1. Launch AuthFlowTester -2. Login with CA Opaque using user agent flow (disable "Use Web Server Flow" in login settings) -3. Note refresh token value -4. Click "Key" icon -5. Configure migration auth flow type: - - Use Web Server Flow: On - - Use Hybrid Flow: On (default) -6. Configure migration: - - Consumer Key: Beacon Opaque - - Scopes: (empty for default) -7. Click "Migrate" -8. **Verify**: Refresh token changed -9. **Verify**: Beacon child key appears in credentials -10. **Verify**: Client ID = Beacon Opaque consumer key -11. Click "Make Rest API request" -12. **Verify**: API succeeds - -##### Test 2.5: Migrate ECA to CA (Rollback) -**Automated Test**: `RefreshTokenMigrationTests.testMigrateCAToECA` (includes rollback) -**Maps To**: High-Level Test Scenario 2.5 -**Description**: Migrate from ECA back to CA - -**Manual Execution Steps**: -1. Login with ECA Opaque -2. Migrate to CA Opaque -3. **Verify**: Refresh token changed -4. **Verify**: Client ID = CA Opaque consumer key -5. **Verify**: No beacon child key -6. Test API succeeds - -##### Test 2.6: Migrate Beacon to CA (Rollback) -**Automated Test**: `RefreshTokenMigrationTests.testMigrateBeaconToCA` -**Maps To**: High-Level Test Scenario 2.6 -**Description**: Migrate from Beacon back to CA - -**Manual Execution Steps**: -1. Login with Beacon Opaque -2. Verify beacon child key present -3. Migrate to CA Opaque -4. **Verify**: Refresh token changed -5. **Verify**: Beacon child key disappears -6. **Verify**: Client ID = CA Opaque -7. Test API - -##### Test 2.7: Migrate Between Token Formats (Opaque to JWT) -**Automated Test**: `RefreshTokenMigrationTests.testMigrateBeaconOpaqueToJWTAndBack` -**Maps To**: High-Level Test Scenario 2.7 -**Description**: Migrate from opaque token to JWT token format - -**Manual Execution Steps**: -1. Login with Beacon-Opaque -2. **Verify**: Token format = opaque (empty in UI) -3. Migrate to Beacon-JWT -4. **Verify**: Refresh token changed -5. **Verify**: Token format = jwt -6. Expand "JWT Access Token Details" -7. **Verify**: JWT details shown (expiration, scopes, etc.) -8. Test API succeeds - -#### Group 3: Multi-User Scenarios - -##### Test 3.1: Two Users, Both CA -**Automated Test**: `MultiUserLoginTests.testBothStatic_SameApp_SameScopes` -**Maps To**: High-Level Test Scenario 3.1 -**Description**: Two users with same app type - -**Manual Execution Steps**: -1. Login User A with CA Opaque -2. Add User B, login with CA Opaque -3. **Verify**: Both have independent tokens -4. Switch between users -5. **Verify**: Each retains own credentials - -##### Test 3.2: Two Users, Different Apps -**Automated Test**: `MultiUserLoginTests.testBothStatic_DifferentApps` -**Maps To**: High-Level Test Scenario 3.2 -**Description**: User A with CA, User B with ECA - -**Manual Execution Steps**: -1. Login User A with CA Opaque -2. Add User B with ECA Opaque -3. **Verify**: User A has CA consumer key -4. **Verify**: User B has ECA consumer key -5. Switch between users -6. **Verify**: Each retains own app configuration - -##### Test 3.3: Migrate One User Only -**Automated Test**: `RefreshTokenMigrationTests.testMigrateOneUserOnly` -**Maps To**: High-Level Test Scenario 3.3 -**Description**: Migrate User A, leave User B unchanged - -**Manual Execution Steps**: -1. Login User A with CA Opaque -2. Add User B with CA Opaque -3. Switch to User A -4. Migrate User A to ECA Opaque -5. **Verify**: User A now has ECA consumer key -6. Switch to User B -7. **Verify**: User B unchanged (still CA Opaque) -8. Test APIs for both users - -##### Test 3.4: Revoke Access for One User During Multi-User -**Automated Test**: `MultiUserLoginTests.testDifferentAppTypes_RevokeAccessForCaUser_EcaUserUnaffected` -**Maps To**: High-Level Test Scenario 3.4 -**Description**: Revoke CA user's access token, verify ECA user unaffected (different app types) - -**Manual Execution Steps**: -1. Setup two users (any configuration) -2. Switch to User A -3. Click "Revoke Access Token" -4. Switch to User B -5. Click "Make Rest API request" -6. **Verify**: User B's API succeeds without refresh -7. Switch to User A -8. Click "Make Rest API request" -9. **Verify**: User A's token refreshed - -##### Test 3.4a: Logout One User During Multi-User -**Automated Test**: `MultiUserLoginTests.testDifferentAppTypes_LogoutCaUser_EcaUserUnaffected` -**Maps To**: High-Level Test Scenario 3.4 (variation with logout) -**Description**: Logout CA user, verify ECA user unaffected and automatic switch occurs - -**Manual Execution Steps**: -1. Login User A with CA Opaque -2. Add User B with ECA Opaque -3. Switch to User A -4. Click "Logout" icon (bottom bar) and confirm logout -5. **Verify**: App automatically switches to User B -6. **Verify**: User B's credentials unchanged -7. Click "Make Rest API request" -8. **Verify**: User B's API succeeds -9. **Verify**: Cannot switch back to User A (logged out) - -##### Test 3.5: Beacon + Non-Beacon Multi-User -**Automated Test**: `MultiUserLoginTests.testBeaconAndNonBeacon_MultiUser` -**Maps To**: High-Level Test Scenario 3.5 -**Description**: User A with Beacon, User B with CA - -**Manual Execution Steps**: -1. Login User A with Beacon Opaque -2. **Verify**: User A has beacon child key -3. Add User B with CA Opaque -4. **Verify**: User B has no beacon child key -5. Switch between users -6. **Verify**: User A always shows child key -7. **Verify**: User B never shows child key - -#### Group 4: Persistence & App-Restart - -##### Test 4.1: Restart After CA Login -**Automated Test**: `LoginWithRestartTests.testCAOpaque_DefaultScopes_WithRestart` -**Maps To**: High-Level Test Scenario 4.1 -**Description**: Verify CA session persists after restart - -**Manual Execution Steps**: -1. Login with CA Opaque -2. Verify credentials -3. Terminate app -4. Launch app -5. **Verify**: User still logged in -6. **Verify**: Credentials unchanged -7. Test API succeeds - -##### Test 4.2: Restart After ECA Login -**Automated Test**: `LoginWithRestartTests.testECAOpaque_DefaultScopes_WithRestart` -**Maps To**: High-Level Test Scenario 4.2 -**Description**: Verify ECA session persists - -**Manual Execution Steps**: -1. Login with ECA Opaque -2. Terminate and restart app -3. **Verify**: Session persists -4. Test API - -##### Test 4.3: Restart After Beacon Login -**Automated Test**: `LoginWithRestartTests.testBeaconOpaque_DefaultScopes_WithRestart` -**Maps To**: High-Level Test Scenario 4.3 -**Description**: Verify Beacon session and child key persist - -**Manual Execution Steps**: -1. Login with Beacon Opaque -2. **Verify**: Beacon child key present -3. Terminate and restart app -4. **Verify**: Session persists -5. **Verify**: Beacon child key still present -6. Test API - -##### Test 4.4: Restart After Migration (CA to ECA) -**Automated Test**: `RefreshTokenMigrationWithRestartTests.testMigrateCAToECA_WithRestart` -**Maps To**: High-Level Test Scenario 4.4 -**Description**: Verify migrated tokens persist - -**Manual Execution Steps**: -1. Login with CA Opaque -2. Migrate to ECA Opaque -3. Verify migration successful -4. Terminate and restart app -5. **Verify**: Migrated configuration persists -6. **Verify**: Client ID = ECA Opaque -7. Test API - -##### Test 4.5: Restart After Migration (CA to Beacon) -**Automated Test**: `RefreshTokenMigrationWithRestartTests.testMigrateCAToBeacon_WithRestart` -**Maps To**: High-Level Test Scenario 4.5 -**Description**: Verify beacon migration persists - -**Manual Execution Steps**: -1. Login with CA Opaque -2. Migrate to Beacon Opaque -3. Terminate and restart app -4. **Verify**: Beacon child key persists -5. Test API - -##### Test 4.6: Restart After Scope Migration -**Automated Test**: `RefreshTokenMigrationWithRestartTests.testMigrateScopeAddition_WithRestart` -**Maps To**: High-Level Test Scenario 4.6 -**Description**: Verify scope changes persist - -**Manual Execution Steps**: -1. Login with ECA JWT, subset scopes -2. Migrate to ECA JWT with all scopes -3. Verify extended scopes present -4. Terminate and restart app -5. **Verify**: Extended scopes persist -6. Test API - -##### Test 4.7: Restart After Beacon Scope Migration -**Automated Test**: `RefreshTokenMigrationWithRestartTests.testMigrateBeaconScopeAddition_WithRestart` -**Maps To**: High-Level Test Scenario 4.7 -**Description**: Verify beacon scope migration persists - -**Manual Execution Steps**: -1. Login with Beacon JWT, subset scopes -2. Migrate to Beacon JWT with all scopes -3. Terminate and restart app -4. **Verify**: Beacon child key persists -5. **Verify**: Extended scopes persist -6. Test API - -##### Test 4.8: Restart With Multiple Users -**Automated Test**: `RefreshTokenMigrationWithRestartTests.testMigrateMultipleUsers_WithRestart` -**Maps To**: High-Level Test Scenario 4.8 -**Description**: Verify multi-user persistence after restart - -**Manual Execution Steps**: -1. Login User A with CA Opaque -2. Migrate User A to ECA Opaque -3. Add User B with Beacon Opaque -4. Terminate and restart app -5. **Verify**: Both users in switcher -6. Switch to User A -7. **Verify**: User A has ECA Opaque config -8. Switch to User B -9. **Verify**: User B has Beacon Opaque with child key -10. Test APIs for both users - ---- - -## Additional Test Coverage - -### Legacy Login Tests - -**Test Class**: `LegacyLoginTests` -**Description**: Tests for traditional Connected App (CA) authentication flows with default, subset, and all scopes using hybrid authentication. Tests both web server and user agent OAuth flows. - -#### CA Web Server Flow Tests -- `testCAOpaque_DefaultScopes_WebServerFlow` - Default scopes, web server flow, hybrid -- `testCAOpaque_SubsetScopes_WebServerFlow` - Subset scopes, web server flow, hybrid -- `testCAOpaque_AllScopes_WebServerFlow` - All scopes, web server flow, hybrid - -#### CA User Agent Flow Tests -- `testCAOpaque_DefaultScopes_UserAgentFlow` - Default scopes, user agent flow, hybrid -- `testCAOpaque_SubsetScopes_UserAgentFlow` - Subset scopes, user agent flow, hybrid -- `testCAOpaque_AllScopes_UserAgentFlow` - All scopes, user agent flow, hybrid - -### Legacy Login Tests (Non-Hybrid) - -**Test Class**: `LegacyLoginTestsNotHybrid` (extends `LegacyLoginTests`) -**Description**: Tests for traditional Connected App (CA) authentication flows using non-hybrid authentication. Extends `LegacyLoginTests` and overrides `useHybridFlow()` to return false. Non-hybrid flow means the app does not receive front-door session cookies (SIDs) for Lightning, Visualforce, and Content domains during authentication. - -#### CA Web Server Flow Tests (Non-Hybrid) -- `testCAOpaque_DefaultScopes_WebServerFlow` - Default scopes, web server flow, non-hybrid -- `testCAOpaque_SubsetScopes_WebServerFlow` - Subset scopes, web server flow, non-hybrid -- `testCAOpaque_AllScopes_WebServerFlow` - All scopes, web server flow, non-hybrid - -#### CA User Agent Flow Tests (Non-Hybrid) -- `testCAOpaque_DefaultScopes_UserAgentFlow` - Default scopes, user agent flow, non-hybrid -- `testCAOpaque_SubsetScopes_UserAgentFlow` - Subset scopes, user agent flow, non-hybrid -- `testCAOpaque_AllScopes_UserAgentFlow` - All scopes, user agent flow, non-hybrid - -### Beacon Tests with Advanced Authentication - -#### Advanced Auth Beacon Tests -**Test Class**: `AdvancedAuthBeaconLoginTests` (extends `BeaconLoginTests`) -**Description**: Same beacon tests but using advanced authentication login host - -Tests all beacon scenarios with `.advancedAuth` login host instead of `.regularAuth`: -- Opaque tokens (default, subset, all scopes) -- JWT tokens (default, subset, all scopes) - -### Welcome Discovery Tests - -#### Welcome Discovery Flow Tests -**Test Class**: `WelcomeLoginTests` -**Description**: Tests for welcome.salesforce.com/discovery domain-based login - -- `testWelcomeDiscovery_RegularAuthLoginHost` - Discovery with regular auth host, static config -- `testWelcomeDiscovery_AdvancedAuthLoginHost` - Discovery with advanced auth host, static config -- `testWelcomeDiscovery_RegularAuthLoginHost_DynamicConfig` - Discovery with dynamic config selection -- `testWelcomeDiscovery_AdvancedAuthLoginHost_DynamicConfig` - Discovery with dynamic and advanced auth - -**Manual Execution**: -1. Configure login host: `welcome.salesforce.com/discovery` -2. Configure discovery settings (username pre-fill) -3. Login will redirect to user's org after domain discovery - ---- - -## Test Configuration - -### User Configuration -Tests use different users from `ui_test_config.json` to avoid login conflicts when running test suites in parallel (max 5 concurrent logins per user): -- `.first` - Used by LegacyLoginTests, ECALoginTests, BeaconLoginTests, WelcomeLoginTests -- `.second` - Used by RefreshTokenMigrationTests, AdvancedAuthBeaconLoginTests, and LoginWithRestartTests (dynamic config tests) -- `.third` - Used by RefreshTokenMigrationWithRestartTests and LoginWithRestartTests (persistence tests) -- `.fourth` and `.fifth` - Used for multi-user tests (including token revocation) in MultiUserLoginTests and related scenarios - -### App Configurations -Tests reference these app configurations: -- `caOpaque` - Connected App with opaque tokens -- `caJwt` - Connected App with JWT tokens -- `ecaOpaque` - External Client App with opaque tokens -- `ecaJwt` - External Client App with JWT tokens -- `beaconOpaque` - Beacon app with opaque tokens -- `beaconJwt` - Beacon app with JWT tokens -- `invalid` - Invalid app configuration (consumer key: "invalid_consumer_key", redirect URI: "invalid://callback", scopes: "invalid_scope") for negative testing - -### Scope Selections -- `.empty` - No scopes specified (default to all server scopes) -- `.subset` - Subset of scopes (all scopes except `sfap_api`) -- `.all` - All available scopes explicitly requested -- `.invalid` - Invalid scope ("invalid_scope") for negative testing scenarios - -### Login Hosts -- `.regularAuth` - Standard authentication endpoint -- `.advancedAuth` - Advanced authentication endpoint (for beacon tests) - ---- - -## Validation Steps Performed in All Tests - -Each automated test performs these validations: - -1. **Main Page Loads** - - Verify AuthFlowTester main screen appears - -2. **User Credentials Validation** - - Username matches expected user - - Client ID (consumer key) matches expected app - - Redirect URI matches expected app - - Granted scopes match expected scopes - - Token format (JWT vs opaque) correct - -3. **OAuth Configuration Validation** - - Static consumer key matches bootconfig - - Static callback URL matches bootconfig - - Static scopes match bootconfig (persists across restarts) - -4. **JWT Details Validation** (if applicable) - - JWT access token details visible - - Scopes in JWT match expected - - Client ID in JWT matches - - Expiration time present - -5. **SID Validation** - - Session ID format validated based on hybrid vs non-hybrid flow - -6. **URL Validation** - - URLs validated based on web server vs user agent flow - -7. **Token Refresh Cycle** - - Revoke access token - - Make REST API request - - Verify access token changed (refreshed) - - Verify API call succeeds - -8. **Beacon Child Key Validation** (if applicable) - - Beacon child consumer key present when using beacon app - - Absent when not using beacon app - ---- - -## Notes - -- Tests are designed to be independent and can run in any order -- Each test performs cleanup in `tearDown()` by logging out -- All tests include automatic token refresh validation -- Multi-user tests verify isolation between user sessions -- Migration tests verify refresh token changes after migration -- Restart tests verify persistence of credentials across app restarts From d9b86243c20afa9efd4dd1ef51d8669d4d065600 Mon Sep 17 00:00:00 2001 From: Wolfgang Mathurin Date: Wed, 11 Mar 2026 16:47:02 -0700 Subject: [PATCH 08/10] New PageObject for AuthFlowTypes to avoid code duplication Import button to set auth flow types by providing a JSON string (toggling was not working all the time) --- .../Login/DevConfig/AuthFlowTypesView.swift | 57 ++++++++++- .../AuthFlowTesterMainPageObject.swift | 37 ++------ .../PageObjects/AuthFlowTypesPageObject.swift | 95 +++++++++++++++++++ .../PageObjects/LoginOptionsPageObject.swift | 28 ++---- 4 files changed, 164 insertions(+), 53 deletions(-) create mode 100644 native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/AuthFlowTypesPageObject.swift diff --git a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/DevConfig/AuthFlowTypesView.swift b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/DevConfig/AuthFlowTypesView.swift index 081b296448..78791b16e7 100644 --- a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/DevConfig/AuthFlowTypesView.swift +++ b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/DevConfig/AuthFlowTypesView.swift @@ -27,20 +27,40 @@ import SwiftUI +// MARK: - JSON Import Labels +public struct AuthFlowTypesJSONKeys { + public static let useWebServerFlow = "useWebServerFlow" + public static let useHybridFlow = "useHybridFlow" +} + public struct AuthFlowTypesView: View { @State private var useWebServerFlow: Bool @State private var useHybridFlow: Bool - + @State private var showImportAlert: Bool = false + @State private var importJSONText: String = "" + public init() { _useWebServerFlow = State(initialValue: SalesforceManager.shared.useWebServerAuthentication) _useHybridFlow = State(initialValue: SalesforceManager.shared.useHybridAuthentication) } - + public var body: some View { VStack(alignment: .leading, spacing: 12) { - Text(SFSDKResourceUtils.localizedString("LOGIN_OPTIONS_AUTH_FLOW_TYPES_TITLE")) - .font(.headline) - .padding(.horizontal) + HStack { + Text(SFSDKResourceUtils.localizedString("LOGIN_OPTIONS_AUTH_FLOW_TYPES_TITLE")) + .font(.headline) + Spacer() + Button(action: { + importJSONText = "" + showImportAlert = true + }) { + Image(systemName: "square.and.arrow.down") + .font(.subheadline) + .foregroundColor(.blue) + } + .accessibilityIdentifier("importAuthFlowTypesButton") + } + .padding(.horizontal) VStack(alignment: .leading, spacing: 8) { Toggle(isOn: $useWebServerFlow) { @@ -65,6 +85,33 @@ public struct AuthFlowTypesView: View { } } .padding(.vertical) + .alert("Import Auth Flow Types", isPresented: $showImportAlert) { + TextField("Paste JSON here", text: $importJSONText) + Button("Import") { + importAuthFlowTypesFromJSON() + } + Button("Cancel", role: .cancel) { } + } message: { + Text("Paste JSON with useWebServerFlow and useHybridFlow") + } + } + + // MARK: - Helper Methods + + private func importAuthFlowTypesFromJSON() { + guard let jsonData = importJSONText.data(using: .utf8), + let json = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] else { + return + } + + if let webServerFlow = json[AuthFlowTypesJSONKeys.useWebServerFlow] as? Bool { + useWebServerFlow = webServerFlow + SalesforceManager.shared.useWebServerAuthentication = webServerFlow + } + if let hybridFlow = json[AuthFlowTypesJSONKeys.useHybridFlow] as? Bool { + useHybridFlow = hybridFlow + SalesforceManager.shared.useHybridAuthentication = hybridFlow + } } } diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/AuthFlowTesterMainPageObject.swift b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/AuthFlowTesterMainPageObject.swift index 985872b21b..f2de559588 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/AuthFlowTesterMainPageObject.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/AuthFlowTesterMainPageObject.swift @@ -213,9 +213,11 @@ struct JwtDetailsData { /// and extract data (user credentials, OAuth configuration, JWT details) from the UI. class AuthFlowTesterMainPageObject { let app: XCUIApplication + let authFlowTypesPageObject: AuthFlowTypesPageObject init(testApp: XCUIApplication) { app = testApp + authFlowTypesPageObject = AuthFlowTypesPageObject(testApp: testApp) } func isShowing() -> Bool { @@ -270,9 +272,11 @@ class AuthFlowTesterMainPageObject { // Tap Change Key button to open the sheet tap(bottomBarChangeKeyButton()) - // Set the auth flow switches - setSwitchField(useWebServerFlowSwitch(), value: useWebServerFlow) - setSwitchField(useHybridSwitch(), value: useHybridFlow) + // Set auth flow types using the dedicated page object + authFlowTypesPageObject.setAuthFlowTypes( + useWebServerFlow: useWebServerFlow, + useHybridFlow: useHybridFlow + ) // Build JSON config and import it let configJSON = buildConfigJSON(consumerKey: appConfig.consumerKey, redirectUri: appConfig.redirectUri, scopes: scopesToRequest) @@ -309,15 +313,15 @@ class AuthFlowTesterMainPageObject { private func importConfig(_ jsonString: String) { tap(importConfigButton()) - + // Wait for alert to appear let alert = importConfigAlert() _ = alert.waitForExistence(timeout: UITestTimeouts.long) - + // Type into the alert's text field let textField = importConfigTextField() textField.typeText(jsonString) - + tap(importAlertButton()) } @@ -438,16 +442,6 @@ class AuthFlowTesterMainPageObject { return app.buttons["Switch to User"] } - // Auth flow switches - - private func useWebServerFlowSwitch() -> XCUIElement { - return app.switches["useWebServerFlowToggle"] - } - - private func useHybridSwitch() -> XCUIElement { - return app.switches["useHybridFlowToggle"] - } - // MARK: - Actions private func tap(_ element: XCUIElement) { @@ -478,17 +472,6 @@ class AuthFlowTesterMainPageObject { textField.typeText(value) } - private func setSwitchField(_ switchField: XCUIElement, value: Bool) { - _ = switchField.waitForExistence(timeout: UITestTimeouts.long) - - // Switch values are "0" (off) or "1" (on) in XCTest - let currentValue = (switchField.value as? String) == "1" - - if currentValue != value { - switchField.tap() - } - } - // MARK: - Data Extraction Methods func getUserCredentials() -> UserCredentialsData { diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/AuthFlowTypesPageObject.swift b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/AuthFlowTypesPageObject.swift new file mode 100644 index 0000000000..54e3593aa9 --- /dev/null +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/AuthFlowTypesPageObject.swift @@ -0,0 +1,95 @@ +/* + AuthFlowTypesPageObject.swift + AuthFlowTesterUITests + + Copyright (c) 2026-present, salesforce.com, inc. All rights reserved. + + Redistribution and use of this software in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list of conditions + and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of + conditions and the following disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to + endorse or promote products derived from this software without specific prior written + permission of salesforce.com, inc. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import Foundation +import XCTest +import SalesforceSDKCore + +/// Page object for interacting with the AuthFlowTypesView (auth flow switches) during UI tests. +/// Can be used from both LoginOptionsView and the refresh token migration sheet. +class AuthFlowTypesPageObject { + let app: XCUIApplication + + init(testApp: XCUIApplication) { + app = testApp + } + + /// Sets the auth flow types (useWebServerFlow and useHybridFlow) using JSON import. + func setAuthFlowTypes(useWebServerFlow: Bool, useHybridFlow: Bool) { + // Wait for the import button to be ready + _ = importAuthFlowTypesButton().waitForExistence(timeout: UITestTimeouts.long) + + // Build and import JSON + let authFlowTypesJSON = buildAuthFlowTypesJSON( + useWebServerFlow: useWebServerFlow, + useHybridFlow: useHybridFlow + ) + importAuthFlowTypes(authFlowTypesJSON) + } + + // MARK: - Private Helpers + + private func buildAuthFlowTypesJSON(useWebServerFlow: Bool, useHybridFlow: Bool) -> String { + let config: [String: Bool] = [ + AuthFlowTypesJSONKeys.useWebServerFlow: useWebServerFlow, + AuthFlowTypesJSONKeys.useHybridFlow: useHybridFlow + ] + guard let jsonData = try? JSONSerialization.data(withJSONObject: config, options: []), + let jsonString = String(data: jsonData, encoding: .utf8) else { + return "{}" + } + return jsonString + } + + private func importAuthFlowTypes(_ jsonString: String) { + tap(importAuthFlowTypesButton()) + + // Wait for alert to appear + let alert = app.alerts["Import Auth Flow Types"] + _ = alert.waitForExistence(timeout: UITestTimeouts.long) + + // Type into the alert's text field + let textField = alert.textFields.firstMatch + textField.typeText(jsonString) + + // Tap Import button + alert.buttons["Import"].tap() + } + + // MARK: - UI Element Accessors + + private func importAuthFlowTypesButton() -> XCUIElement { + return app.buttons["importAuthFlowTypesButton"] + } + + // MARK: - Actions + + private func tap(_ element: XCUIElement) { + _ = element.waitForExistence(timeout: UITestTimeouts.long) + element.tap() + } +} diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/LoginOptionsPageObject.swift b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/LoginOptionsPageObject.swift index 3917680346..7753b711ba 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/LoginOptionsPageObject.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/LoginOptionsPageObject.swift @@ -33,9 +33,11 @@ import SalesforceSDKCore /// Use after navigating to Login Options from the login screen (e.g. via Settings → Login Options). class LoginOptionsPageObject { let app: XCUIApplication + let authFlowTypesPageObject: AuthFlowTypesPageObject init(testApp: XCUIApplication) { app = testApp + authFlowTypesPageObject = AuthFlowTypesPageObject(testApp: testApp) } /// Configures login options: flow switches, static/dynamic app config, and discovery result. @@ -50,8 +52,11 @@ class LoginOptionsPageObject { discoveryLoginHost: String, discoveryUsername: String, ) -> Void { - setSwitchField(useWebServerFlowSwitch(), value: useWebServerFlow) - setSwitchField(useHybridSwitch(), value: useHybridFlow) + // Set auth flow types using the dedicated page object + authFlowTypesPageObject.setAuthFlowTypes( + useWebServerFlow: useWebServerFlow, + useHybridFlow: useHybridFlow + ) if let staticAppConfig = staticAppConfig { let configJSON = buildConfigJSON(consumerKey: staticAppConfig.consumerKey, redirectUri: staticAppConfig.redirectUri, scopes: staticScopes) @@ -126,14 +131,6 @@ class LoginOptionsPageObject { // MARK: - UI Element Accessors (LoginOptionsView) - private func useWebServerFlowSwitch() -> XCUIElement { - return app.switches["useWebServerFlowToggle"] - } - - private func useHybridSwitch() -> XCUIElement { - return app.switches["useHybridFlowToggle"] - } - /// Returns the import button for either the static or dynamic configuration section. private func importConfigButton(useStaticConfiguration: Bool = true) -> XCUIElement { let buttons = app.buttons.matching(identifier: "importConfigButton") @@ -155,15 +152,4 @@ class LoginOptionsPageObject { _ = element.waitForExistence(timeout: UITestTimeouts.long) element.tap() } - - private func setSwitchField(_ switchField: XCUIElement, value: Bool) { - _ = switchField.waitForExistence(timeout: UITestTimeouts.long) - - // Switch values are "0" (off) or "1" (on) in XCTest - let currentValue = (switchField.value as? String) == "1" - - if currentValue != value { - switchField.tap() - } - } } From e682cffb6d0de1d39106ec1a74063a39d9b3a6bb Mon Sep 17 00:00:00 2001 From: Wolfgang Mathurin Date: Wed, 11 Mar 2026 20:11:26 -0700 Subject: [PATCH 09/10] New unit tests for AuthFlowViewTypesView --- .../Login/DevConfig/AuthFlowTypesView.swift | 6 +- .../AuthFlowTypesViewTests.swift | 116 ++++++++++++++++++ 2 files changed, 119 insertions(+), 3 deletions(-) diff --git a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/DevConfig/AuthFlowTypesView.swift b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/DevConfig/AuthFlowTypesView.swift index 78791b16e7..d56c5d9251 100644 --- a/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/DevConfig/AuthFlowTypesView.swift +++ b/libs/SalesforceSDKCore/SalesforceSDKCore/Classes/Login/DevConfig/AuthFlowTypesView.swift @@ -88,7 +88,7 @@ public struct AuthFlowTypesView: View { .alert("Import Auth Flow Types", isPresented: $showImportAlert) { TextField("Paste JSON here", text: $importJSONText) Button("Import") { - importAuthFlowTypesFromJSON() + applyAuthFlowTypesFromJSON(importJSONText) } Button("Cancel", role: .cancel) { } } message: { @@ -98,8 +98,8 @@ public struct AuthFlowTypesView: View { // MARK: - Helper Methods - private func importAuthFlowTypesFromJSON() { - guard let jsonData = importJSONText.data(using: .utf8), + internal func applyAuthFlowTypesFromJSON(_ jsonString: String) { + guard let jsonData = jsonString.data(using: .utf8), let json = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] else { return } diff --git a/libs/SalesforceSDKCore/SalesforceSDKCoreTests/AuthFlowTypesViewTests.swift b/libs/SalesforceSDKCore/SalesforceSDKCoreTests/AuthFlowTypesViewTests.swift index 0accfc5232..bed9fb0992 100644 --- a/libs/SalesforceSDKCore/SalesforceSDKCoreTests/AuthFlowTypesViewTests.swift +++ b/libs/SalesforceSDKCore/SalesforceSDKCoreTests/AuthFlowTypesViewTests.swift @@ -85,5 +85,121 @@ class AuthFlowTypesViewTests: XCTestCase { wait(for: [expectation], timeout: 2.0) } + + func testImportAuthFlowTypesFromJSON() { + // Set initial state + SalesforceManager.shared.useWebServerAuthentication = true + SalesforceManager.shared.useHybridAuthentication = true + + // Create the view + var view = AuthFlowTypesView() + + // Verify initial state + XCTAssertTrue(SalesforceManager.shared.useWebServerAuthentication, + "Web server authentication should initially be true") + XCTAssertTrue(SalesforceManager.shared.useHybridAuthentication, + "Hybrid authentication should initially be true") + + // Create JSON string + let json: [String: Any] = [ + AuthFlowTypesJSONKeys.useWebServerFlow: false, + AuthFlowTypesJSONKeys.useHybridFlow: false + ] + + guard let jsonData = try? JSONSerialization.data(withJSONObject: json, options: []), + let jsonString = String(data: jsonData, encoding: .utf8) else { + XCTFail("Failed to create JSON string") + return + } + + // Call the internal method to apply the JSON + view.applyAuthFlowTypesFromJSON(jsonString) + + // Verify the values were updated + XCTAssertFalse(SalesforceManager.shared.useWebServerAuthentication, + "Web server authentication should be false after import") + XCTAssertFalse(SalesforceManager.shared.useHybridAuthentication, + "Hybrid authentication should be false after import") + } + + func testImportAuthFlowTypesFromJSONPartialUpdate() { + // Set initial state + SalesforceManager.shared.useWebServerAuthentication = true + SalesforceManager.shared.useHybridAuthentication = true + + // Create the view + let view = AuthFlowTypesView() + + // Test importing only one value + let json: [String: Any] = [ + AuthFlowTypesJSONKeys.useWebServerFlow: false + // Note: useHybridFlow is not included + ] + + guard let jsonData = try? JSONSerialization.data(withJSONObject: json, options: []), + let jsonString = String(data: jsonData, encoding: .utf8) else { + XCTFail("Failed to create JSON string") + return + } + + // Call the internal method to apply the JSON + view.applyAuthFlowTypesFromJSON(jsonString) + + // Verify only the specified value was updated + XCTAssertFalse(SalesforceManager.shared.useWebServerAuthentication, + "Web server authentication should be false after import") + XCTAssertTrue(SalesforceManager.shared.useHybridAuthentication, + "Hybrid authentication should remain true (not in JSON)") + } + + func testAuthFlowTypesJSONKeys() { + // Test that the JSON keys are correctly defined + XCTAssertEqual(AuthFlowTypesJSONKeys.useWebServerFlow, "useWebServerFlow", + "useWebServerFlow key should match expected value") + XCTAssertEqual(AuthFlowTypesJSONKeys.useHybridFlow, "useHybridFlow", + "useHybridFlow key should match expected value") + } + + func testImportAuthFlowTypesFromInvalidJSON() { + // Set initial state + SalesforceManager.shared.useWebServerAuthentication = true + SalesforceManager.shared.useHybridAuthentication = true + + // Create the view + let view = AuthFlowTypesView() + + // Test with invalid JSON + let invalidJSON = "{ this is not valid JSON }" + + // Call the internal method with invalid JSON + view.applyAuthFlowTypesFromJSON(invalidJSON) + + // Verify nothing changed (method should handle invalid JSON gracefully) + XCTAssertTrue(SalesforceManager.shared.useWebServerAuthentication, + "Web server authentication should remain true after invalid JSON") + XCTAssertTrue(SalesforceManager.shared.useHybridAuthentication, + "Hybrid authentication should remain true after invalid JSON") + } + + func testImportAuthFlowTypesFromEmptyJSON() { + // Set initial state + SalesforceManager.shared.useWebServerAuthentication = true + SalesforceManager.shared.useHybridAuthentication = false + + // Create the view + let view = AuthFlowTypesView() + + // Test with empty JSON object + let emptyJSON = "{}" + + // Call the internal method with empty JSON + view.applyAuthFlowTypesFromJSON(emptyJSON) + + // Verify nothing changed + XCTAssertTrue(SalesforceManager.shared.useWebServerAuthentication, + "Web server authentication should remain unchanged after empty JSON") + XCTAssertFalse(SalesforceManager.shared.useHybridAuthentication, + "Hybrid authentication should remain unchanged after empty JSON") + } } From 27e49e5306f6b5b244566153fa6967272ec60360 Mon Sep 17 00:00:00 2001 From: Wolfgang Mathurin Date: Thu, 12 Mar 2026 10:39:50 -0700 Subject: [PATCH 10/10] Configure UI test reports to exclude retries and group by suite - Add check_retries: false to prevent counting retried tests separately - Add group_suite: true to group tests by test suite in report - Ensures test counts show actual test count (71) not inflated retry counts --- .github/workflows/reusable-ui-test-workflow.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/reusable-ui-test-workflow.yaml b/.github/workflows/reusable-ui-test-workflow.yaml index f29ff868b2..ee242a80c0 100644 --- a/.github/workflows/reusable-ui-test-workflow.yaml +++ b/.github/workflows/reusable-ui-test-workflow.yaml @@ -160,6 +160,8 @@ jobs: comment: true job_summary: true report_paths: 'test-results-authflowtester-ui-ios${{ inputs.ios }}-${{ steps.result_suffix.outputs.suffix }}.xml' + check_retries: false + group_suite: true - uses: codecov/codecov-action@v4 if: success() || failure() with: