diff --git a/.gitignore b/.gitignore index 61c7d289bb..f8e697a3f9 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ shared/test/test_credentials.json /libs/MobileSync/build/ /libs/SalesforceSDKCommon/build/ /native/SampleApps/RestAPIExplorer/build/ +/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/ui_test_config.json node_modules/ .idea/ package-lock.json diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTester.xcodeproj/project.pbxproj b/native/SampleApps/AuthFlowTester/AuthFlowTester.xcodeproj/project.pbxproj index 64e09cae45..02f7192349 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTester.xcodeproj/project.pbxproj +++ b/native/SampleApps/AuthFlowTester/AuthFlowTester.xcodeproj/project.pbxproj @@ -83,7 +83,7 @@ 4F5946272ED670C7003C5BDE /* Exceptions for "AuthFlowTesterUITests" folder in "AuthFlowTesterUITests" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( - test_config.json.sample, + ui_test_config.json.sample, ); target = 4F8E4AF02ED13CE800DA7B7A /* AuthFlowTesterUITests */; }; diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/AuthFlowTesterMainPageObject.swift b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/AuthFlowTesterMainPageObject.swift index 09db0f839e..5b1756b630 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/AuthFlowTesterMainPageObject.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/AuthFlowTesterMainPageObject.swift @@ -27,6 +27,7 @@ import Foundation import XCTest +import SalesforceSDKCore // MARK: - Label Constants (mirroring the app's Labels structs for JSON parsing) @@ -270,10 +271,9 @@ class AuthFlowTesterMainPageObject { // Tap Change Key button to open the sheet tap(bottomBarChangeKeyButton()) - // Fill in the text fields - setTextField(consumerKeyTextField(), value: appConfig.consumerKey) - setTextField(callbackUrlTextField(), value: appConfig.redirectUri) - setTextField(scopesTextField(), value: scopesToRequest) + // 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()) @@ -289,6 +289,35 @@ class AuthFlowTesterMainPageObject { return true } + // MARK: - Config Import Helpers + + private func buildConfigJSON(consumerKey: String, redirectUri: String, scopes: String) -> String { + let config: [String: String] = [ + BootConfigJSONKeys.consumerKey: consumerKey, + BootConfigJSONKeys.redirectUri: redirectUri, + BootConfigJSONKeys.scopes: scopes + ] + guard let jsonData = try? JSONSerialization.data(withJSONObject: config, options: []), + let jsonString = String(data: jsonData, encoding: .utf8) else { + return "{}" + } + return jsonString + } + + private func importConfig(_ jsonString: String) { + tap(importConfigButton()) + + // Wait for alert to appear + let alert = importConfigAlert() + _ = alert.waitForExistence(timeout: timeout) + + // Type into the alert's text field + let textField = importConfigTextField() + textField.typeText(jsonString) + + tap(importAlertButton()) + } + // MARK: - UI Element Accessors private func navigationTitle() -> XCUIElement { @@ -373,6 +402,25 @@ class AuthFlowTesterMainPageObject { return buttons.matching(predicate).firstMatch } + // Config import + + private func importConfigButton() -> XCUIElement { + return app.buttons.matching(identifier: "importConfigButton").firstMatch + } + + private func importConfigAlert() -> XCUIElement { + return app.alerts["Import Configuration"] + } + + private func importConfigTextField() -> XCUIElement { + // Access text field through the alert - SwiftUI alert TextFields are accessed this way + return importConfigAlert().textFields.firstMatch + } + + private func importAlertButton() -> XCUIElement { + return importConfigAlert().buttons["Import"] + } + // User switching private func newUserButton() -> XCUIElement { diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/LoginPageObject.swift b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/LoginPageObject.swift index baae10ff57..5f419e5c4f 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/LoginPageObject.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/PageObjects/LoginPageObject.swift @@ -43,6 +43,13 @@ class LoginPageObject { return loginNavigationBar().waitForExistence(timeout: timeout) } + func switchToLSCIfShowingAdvancedAuthentication() -> Void { + if (isShowingAdvancedAuth()) { + tap(advancedAuthCloseButton()) + tap(hostRow(host: "Production")) + } + } + func configureLoginHost(host: String) -> Void { tap(settingsButton()) tap(changeServerButton()) @@ -60,7 +67,9 @@ class LoginPageObject { func performLogin(username: String, password: String) { setTextField(usernameField(), value: username) + tap(passwordFieldLabel()) // click on label to hide keyboard setTextField(passwordField(), value: password) + tap(usernameFieldLabel()) // click on label to hide keyboard tap(loginButton()) tapIfPresent(allowButton()) } @@ -163,11 +172,19 @@ class LoginPageObject { private func hostRow(host: String) -> XCUIElement { return app.staticTexts[host].firstMatch } + + private func usernameFieldLabel() -> XCUIElement { + return app.staticTexts["Username"] + } private func usernameField() -> XCUIElement { return app.descendants(matching: .textField).element } + private func passwordFieldLabel() -> XCUIElement { + return app.staticTexts["Password"] + } + private func passwordField() -> XCUIElement { return app.descendants(matching: .secureTextField).element } @@ -239,6 +256,10 @@ class LoginPageObject { return importConfigAlert().buttons["Import"] } + private func advancedAuthCloseButton() -> XCUIElement { + return app.otherElements["TopBrowserBar"].buttons["Close"] + } + // MARK: - Actions private func tap(_ element: XCUIElement) { @@ -293,5 +314,9 @@ class LoginPageObject { return row.waitForExistence(timeout: timeout) } + private func isShowingAdvancedAuth() -> Bool { + return advancedAuthCloseButton().waitForExistence(timeout: timeout) + } + } diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/AdvancedAuthBeaconLoginTests.swift b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/AdvancedAuthBeaconLoginTests.swift new file mode 100644 index 0000000000..e63e483f23 --- /dev/null +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/AdvancedAuthBeaconLoginTests.swift @@ -0,0 +1,47 @@ +/* + AdvancedAuthBeaconLoginTests.swift + AuthFlowTesterUITests + + Copyright (c) 2025-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 login flows using Beacon app configurations with advanced authentication. +/// This class runs the same tests as BeaconLoginTests but uses the advanced_auth login host. +/// +/// NB: Tests use the first user from ui_test_config.json (advanced_auth host) +/// +class AdvancedAuthBeaconLoginTests: BeaconLoginTests { + + // MARK: - Login Host Configuration + + /// Override to use advanced authentication login host. + override func loginHostConfig() -> KnownLoginHostConfig { + return .advancedAuth + } + + override func postLogoutCleanup() { + switchToLSCIfShowingAdvancedAuthentication() + } +} diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/BaseAuthFlowTesterTest.swift b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/BaseAuthFlowTesterTest.swift index 2e7fa83df7..0ff7810b7e 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/BaseAuthFlowTesterTest.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/BaseAuthFlowTesterTest.swift @@ -36,25 +36,23 @@ class BaseAuthFlowTesterTest: XCTestCase { private var mainPage: AuthFlowTesterMainPageObject! // Test configuration - private let testConfig = TestConfigUtils.shared - private let host: String = TestConfigUtils.shared.loginHostNoProtocol ?? "" - private var loginHostConfiguredAlready = false + private let testConfig = UITestConfigUtils.shared override func setUp() { super.setUp() continueAfterFailure = false - - guard host != "" else { - XCTFail("No login host configured") - fatalError("No login host configured") - } } override func tearDown() { logout() + postLogoutCleanup() super.tearDown() } + func postLogoutCleanup() { + // Some tests might need to do more e.g. switch back to login host that uses regular auth so that login settings is shown + } + // MARK: - Public API for Subclasses /// Launches the application and ensures it starts in a logged-out state. @@ -78,6 +76,7 @@ class BaseAuthFlowTesterTest: XCTestCase { /// Must be called after `launch()`. /// /// - Parameters: + /// - loginHost: The login host configuration to use. /// - user: The user to log in with. /// - staticAppConfigName: The static app configuration name. /// - staticScopeSelection: The scope selection for static configuration. Defaults to `.empty`. @@ -86,6 +85,7 @@ class BaseAuthFlowTesterTest: XCTestCase { /// - useWebServerFlow: Whether to use web server OAuth flow. Defaults to `true`. /// - useHybridFlow: Whether to use hybrid authentication flow. Defaults to `true`. func login( + loginHost: KnownLoginHostConfig, user: KnownUserConfig, staticAppConfigName: KnownAppConfig, staticScopeSelection: ScopeSelection = .empty, @@ -94,13 +94,7 @@ class BaseAuthFlowTesterTest: XCTestCase { useWebServerFlow: Bool = true, useHybridFlow: Bool = true, ) { - // To speed up things a bit - only configuring login host once (it never changes) - if (!loginHostConfiguredAlready) { - loginPage.configureLoginHost(host: host) - loginHostConfiguredAlready = true - } - - let userConfig = getUser(user) + let userConfig = getUser(loginHost: loginHost, user: user) let staticAppConfig = getAppConfig(named: staticAppConfigName) let dynamicAppConfig = dynamicAppConfigName == nil ? nil : getAppConfig(named: dynamicAppConfigName!) let staticScopes = testConfig.getScopesToRequest(for: staticAppConfig, staticScopeSelection) @@ -115,6 +109,12 @@ class BaseAuthFlowTesterTest: XCTestCase { useHybridFlow: useHybridFlow, ) + // Configuring login host last + // When the configured login host requires advanced authentication + // the login settings button is no longer available on the screen + let hostConfig = try! testConfig.getLoginHost(loginHost) + loginPage.configureLoginHost(host: hostConfig.urlNoProtocol) + loginPage.performLogin(username: userConfig.username, password: userConfig.password) } @@ -133,6 +133,7 @@ class BaseAuthFlowTesterTest: XCTestCase { /// Use this method when multiple users are logged in and you want to switch between them. /// /// - Parameters: + /// - loginHost: The login host configuration to use. /// - user: The user to switch to. /// - staticAppConfigName: The static app configuration name. /// - staticScopeSelection: The scope selection for static configuration. Defaults to `.empty`. @@ -141,6 +142,7 @@ class BaseAuthFlowTesterTest: XCTestCase { /// - useWebServerFlow: Whether web server OAuth flow was used. Defaults to `true`. /// - useHybridFlow: Whether hybrid authentication flow was used. Defaults to `true`. func switchToUserAndValidate( + loginHost: KnownLoginHostConfig, user: KnownUserConfig, staticAppConfigName: KnownAppConfig, staticScopeSelection: ScopeSelection = .empty, @@ -150,10 +152,11 @@ class BaseAuthFlowTesterTest: XCTestCase { useHybridFlow: Bool = true ) { // Switch user - mainPage.switchToUser(username: getUser(user).username) + mainPage.switchToUser(username: getUser(loginHost: loginHost, user: user).username) // Validate validate( + loginHost: loginHost, user: user, staticAppConfigName: staticAppConfigName, staticScopeSelection: staticScopeSelection, @@ -170,6 +173,7 @@ class BaseAuthFlowTesterTest: XCTestCase { /// Use this for the initial login flow in tests. /// /// - Parameters: + /// - loginHost: The login host configuration to use. /// - user: The user to log in with. /// - staticAppConfigName: The static app configuration name. /// - staticScopeSelection: The scope selection for static configuration. Defaults to `.empty`. @@ -178,6 +182,7 @@ class BaseAuthFlowTesterTest: XCTestCase { /// - useWebServerFlow: Whether to use web server OAuth flow. Defaults to `true`. /// - useHybridFlow: Whether to use hybrid authentication flow. Defaults to `true`. func launchAndLogin( + loginHost: KnownLoginHostConfig, user: KnownUserConfig, staticAppConfigName: KnownAppConfig, staticScopeSelection: ScopeSelection = .empty, @@ -191,6 +196,7 @@ class BaseAuthFlowTesterTest: XCTestCase { // Login login( + loginHost: loginHost, user: user, staticAppConfigName: staticAppConfigName, staticScopeSelection: staticScopeSelection, @@ -207,6 +213,7 @@ class BaseAuthFlowTesterTest: XCTestCase { /// Use this for the initial login flow in tests. /// /// - Parameters: + /// - loginHost: The login host configuration to use. Defaults to `.regularAuth`. /// - user: The user to log in with. Defaults to `.first`. /// - staticAppConfigName: The static app configuration name. /// - staticScopeSelection: The scope selection for static configuration. Defaults to `.empty`. @@ -215,6 +222,7 @@ class BaseAuthFlowTesterTest: XCTestCase { /// - useWebServerFlow: Whether to use web server OAuth flow. Defaults to `true`. /// - useHybridFlow: Whether to use hybrid authentication flow. Defaults to `true`. func launchLoginAndValidate( + loginHost: KnownLoginHostConfig = .regularAuth, user: KnownUserConfig = .first, staticAppConfigName: KnownAppConfig, staticScopeSelection: ScopeSelection = .empty, @@ -232,6 +240,7 @@ class BaseAuthFlowTesterTest: XCTestCase { // Login login( + loginHost: loginHost, user: user, staticAppConfigName: staticAppConfigName, staticScopeSelection: staticScopeSelection, @@ -243,6 +252,7 @@ class BaseAuthFlowTesterTest: XCTestCase { // Validate validate( + loginHost: loginHost, user: user, staticAppConfigName: staticAppConfigName, staticScopeSelection: staticScopeSelection, @@ -259,6 +269,7 @@ class BaseAuthFlowTesterTest: XCTestCase { /// Taps the "Add User" button before performing login. /// /// - Parameters: + /// - loginHost: The login host configuration to use. /// - user: The user to log in with. /// - staticAppConfigName: The static app configuration name. /// - staticScopeSelection: The scope selection for static configuration. Defaults to `.empty`. @@ -267,6 +278,7 @@ class BaseAuthFlowTesterTest: XCTestCase { /// - useWebServerFlow: Whether to use web server OAuth flow. Defaults to `true`. /// - useHybridFlow: Whether to use hybrid authentication flow. Defaults to `true`. func loginOtherUserAndValidate( + loginHost: KnownLoginHostConfig, user: KnownUserConfig, staticAppConfigName: KnownAppConfig, staticScopeSelection: ScopeSelection = .empty, @@ -284,6 +296,7 @@ class BaseAuthFlowTesterTest: XCTestCase { // Login login( + loginHost: loginHost, user: user, staticAppConfigName: staticAppConfigName, staticScopeSelection: staticScopeSelection, @@ -295,6 +308,7 @@ class BaseAuthFlowTesterTest: XCTestCase { // Validate validate( + loginHost: loginHost, user: user, staticAppConfigName: staticAppConfigName, staticScopeSelection: staticScopeSelection, @@ -311,12 +325,14 @@ class BaseAuthFlowTesterTest: XCTestCase { /// with the expected credentials. Use this to test session persistence. /// /// - Parameters: + /// - loginHost: The login host configuration to use. Defaults to `.regularAuth`. /// - user: The user that should still be logged in after restart. Defaults to `.first`. /// - userAppConfigName: The app configuration the user was logged in with. /// - userScopeSelection: The scope selection the user was logged in with. Defaults to `.empty`. /// - useWebServerFlow: Whether web server OAuth flow was used. Defaults to `true`. /// - useHybridFlow: Whether hybrid authentication flow was used. Defaults to `true`. func restartAndValidate( + loginHost: KnownLoginHostConfig = .regularAuth, user: KnownUserConfig = .first, userAppConfigName: KnownAppConfig, userScopeSelection: ScopeSelection = .empty, @@ -330,6 +346,7 @@ class BaseAuthFlowTesterTest: XCTestCase { // Validate user // Not checking static app config since it will depend on the bootconfig of the target app validateUser( + loginHost: loginHost, user: user, userAppConfigName: userAppConfigName, userScopeSelection: userScopeSelection, @@ -344,6 +361,7 @@ class BaseAuthFlowTesterTest: XCTestCase { /// then validates that the credentials are updated correctly and the refresh token has changed. /// /// - Parameters: + /// - loginHost: The login host configuration to use. /// - staticAppConfigName: The static app configuration name. /// - staticScopeSelection: The scope selection for static configuration. Defaults to `.empty`. /// - migrationAppConfigName: The app configuration to migrate to. @@ -351,6 +369,7 @@ class BaseAuthFlowTesterTest: XCTestCase { /// - useWebServerFlow: Whether to use web server OAuth flow. Defaults to `true`. /// - useHybridFlow: Whether to use hybrid authentication flow. Defaults to `true`. func migrateAndValidate( + loginHost: KnownLoginHostConfig, staticAppConfigName: KnownAppConfig, staticScopeSelection: ScopeSelection = .empty, migrationAppConfigName: KnownAppConfig, @@ -362,7 +381,7 @@ class BaseAuthFlowTesterTest: XCTestCase { let originalUserCredentials = mainPage.getUserCredentials() // Get current user - let user = getKnownUserConfig(byUsername: originalUserCredentials.username) + let user = getKnownUserConfig(loginHost: loginHost, byUsername: originalUserCredentials.username) // Migrate refresh token @@ -373,6 +392,7 @@ class BaseAuthFlowTesterTest: XCTestCase { // Validate after migration let migratedUserCredentials = validate( + loginHost: loginHost, user: user, staticAppConfigName: staticAppConfigName, staticScopeSelection: staticScopeSelection, @@ -390,10 +410,15 @@ class BaseAuthFlowTesterTest: XCTestCase { ) } + func switchToLSCIfShowingAdvancedAuthentication() { + loginPage.switchToLSCIfShowingAdvancedAuthentication() + } + // MARK: - Private Helpers @discardableResult private func validateUser( + loginHost: KnownLoginHostConfig, user: KnownUserConfig, userAppConfigName: KnownAppConfig, userScopeSelection: ScopeSelection, @@ -401,7 +426,7 @@ class BaseAuthFlowTesterTest: XCTestCase { useHybridFlow: Bool, ) -> UserCredentialsData { - let userConfig = getUser(user) + let userConfig = getUser(loginHost: loginHost, user: user) let userAppConfig = getAppConfig(named: userAppConfigName) let expectedGrantedScopes = testConfig.getExpectedScopesGranted(for: userAppConfig, userScopeSelection) let issuesJwt = userAppConfig.issuesJwt @@ -439,6 +464,7 @@ class BaseAuthFlowTesterTest: XCTestCase { @discardableResult private func validate( + loginHost: KnownLoginHostConfig, user: KnownUserConfig, staticAppConfigName: KnownAppConfig, staticScopeSelection: ScopeSelection, @@ -454,6 +480,7 @@ class BaseAuthFlowTesterTest: XCTestCase { assertMainPageLoaded() let userCredentials = validateUser( + loginHost: loginHost, user: user, userAppConfigName: userAppConfigName, userScopeSelection: userScopeSelection, @@ -569,21 +596,21 @@ class BaseAuthFlowTesterTest: XCTestCase { } } - private func getUser(_ user: KnownUserConfig) -> UserConfig { + private func getUser(loginHost: KnownLoginHostConfig, user: KnownUserConfig) -> UserConfig { do { - return try testConfig.getUser(user) + return try testConfig.getUser(loginHost, user) } catch { - XCTFail("Failed to get user \(user): \(error)") - fatalError("Failed to get user \(user): \(error)") + XCTFail("Failed to get user \(user) from login host \(loginHost): \(error)") + fatalError("Failed to get user \(user) from login host \(loginHost): \(error)") } } - private func getKnownUserConfig(byUsername username: String) -> KnownUserConfig { + private func getKnownUserConfig(loginHost: KnownLoginHostConfig, byUsername username: String) -> KnownUserConfig { do { - return try testConfig.getKnownUserConfig(byUsername: username) + return try testConfig.getKnownUserConfig(loginHost, byUsername: username) } catch { - XCTFail("Failed to get user \(username): \(error)") - fatalError("Failed to get user \(username): \(error)") + XCTFail("Failed to get user \(username) from login host \(loginHost): \(error)") + fatalError("Failed to get user \(username) from login host \(loginHost): \(error)") } } diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/BeaconLoginTests.swift b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/BeaconLoginTests.swift index 19e0742635..c98d2023f9 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/BeaconLoginTests.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/BeaconLoginTests.swift @@ -30,42 +30,50 @@ import XCTest /// Tests for login flows using Beacon app configurations. /// Beacon apps are lightweight authentication apps for specific use cases. /// -/// NB: Tests use the first user from test_config.json +/// NB: Tests use the first user from ui_test_config.json /// class BeaconLoginTests: BaseAuthFlowTesterTest { + // MARK: - Login Host Configuration + + /// Returns the login host configuration to use for tests. + /// Subclasses can override this to use a different login host. + func loginHostConfig() -> KnownLoginHostConfig { + return .regularAuth + } + // MARK: - Beacon Opaque Tests /// Login with Beacon advanced opaque using default scopes and web server flow. func testBeaconAdvancedOpaque_DefaultScopes() throws { - launchLoginAndValidate(staticAppConfigName: .beaconAdvancedOpaque) + launchLoginAndValidate(loginHost: loginHostConfig(), staticAppConfigName: .beaconAdvancedOpaque) } /// Login with Beacon advanced opaque using subset of scopes and web server flow. func testBeaconAdvancedOpaque_SubsetScopes() throws { - launchLoginAndValidate(staticAppConfigName: .beaconAdvancedOpaque, staticScopeSelection: .subset) + launchLoginAndValidate(loginHost: loginHostConfig(), staticAppConfigName: .beaconAdvancedOpaque, staticScopeSelection: .subset) } /// Login with Beacon advanced opaque using all scopes and web server flow. func testBeaconAdvancedOpaque_AllScopes() throws { - launchLoginAndValidate(staticAppConfigName: .beaconAdvancedOpaque, staticScopeSelection: .all) + launchLoginAndValidate(loginHost: loginHostConfig(), staticAppConfigName: .beaconAdvancedOpaque, staticScopeSelection: .all) } // MARK: - Beacon JWT Tests /// Login with Beacon advanced JWT using default scopes and web server flow. func testBeaconAdvancedJwt_DefaultScopes() throws { - launchLoginAndValidate(staticAppConfigName: .beaconAdvancedJwt) + launchLoginAndValidate(loginHost: loginHostConfig(), staticAppConfigName: .beaconAdvancedJwt) } /// Login with Beacon advanced JWT using subset of scopes and web server flow. func testBeaconAdvancedJwt_SubsetScopes() throws { - launchLoginAndValidate(staticAppConfigName: .beaconAdvancedJwt, staticScopeSelection: .subset) + launchLoginAndValidate(loginHost: loginHostConfig(), staticAppConfigName: .beaconAdvancedJwt, staticScopeSelection: .subset) } /// Login with Beacon advanced JWT using all scopes and web server flow. func testBeaconAdvancedJwt_AllScopes() throws { - launchLoginAndValidate(staticAppConfigName: .beaconAdvancedJwt, staticScopeSelection: .all) + launchLoginAndValidate(loginHost: loginHostConfig(), staticAppConfigName: .beaconAdvancedJwt, staticScopeSelection: .all) } // MARK: - Using dynamic config @@ -74,10 +82,12 @@ class BeaconLoginTests: BaseAuthFlowTesterTest { /// Restart the application and validate it still works afterwards func testBeaconAdvancedJwt_DefaultScopes_DynamicConfiguration_WithRestart() throws { launchLoginAndValidate( + loginHost: loginHostConfig(), staticAppConfigName: .beaconAdvancedOpaque, dynamicAppConfigName: .beaconAdvancedJwt ) restartAndValidate( + loginHost: loginHostConfig(), userAppConfigName: .beaconAdvancedJwt ) } @@ -86,11 +96,13 @@ class BeaconLoginTests: BaseAuthFlowTesterTest { /// Restart the application and validate it still works afterwards func testBeaconAdvancedJwt_SubsetScopes_DynamicConfiguration_WithRestart() throws { launchLoginAndValidate( + loginHost: loginHostConfig(), staticAppConfigName: .beaconAdvancedOpaque, dynamicAppConfigName: .beaconAdvancedJwt, dynamicScopeSelection: .subset ) restartAndValidate( + loginHost: loginHostConfig(), userAppConfigName: .beaconAdvancedJwt, userScopeSelection: .subset ) diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/ECALoginTests.swift b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/ECALoginTests.swift index 11376936bc..03140bd9ea 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/ECALoginTests.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/ECALoginTests.swift @@ -30,7 +30,7 @@ import XCTest /// Tests for login flows using External Client App (ECA) configurations. /// ECA apps are first-party Salesforce apps that use enhanced authentication flows. /// -/// NB: Tests use the first user from test_config.json +/// NB: Tests use the first user from ui_test_config.json /// class ECALoginTests: BaseAuthFlowTesterTest { diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/LegacyLoginTests.swift b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/LegacyLoginTests.swift index bd0011206b..5745248968 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/LegacyLoginTests.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/LegacyLoginTests.swift @@ -32,7 +32,7 @@ import XCTest /// - User agent flow tests /// - Non-hybrid flow tests /// -/// NB: Tests use the first user from test_config.json +/// NB: Tests use the first user from ui_test_config.json /// class LegacyLoginTests: BaseAuthFlowTesterTest { diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/MigrationTests.swift b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/MigrationTests.swift index 578ac05df4..103dd5dd99 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/MigrationTests.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/MigrationTests.swift @@ -31,7 +31,7 @@ import XCTest /// These tests verify that users can seamlessly transition between app types /// (CA, ECA, Beacon) and token formats (opaque, JWT) without re-authentication. /// -/// NB: Tests use the second user from test_config.json +/// NB: Tests use the second user from ui_test_config.json /// class MigrationTests: BaseAuthFlowTesterTest { @@ -40,11 +40,13 @@ class MigrationTests: BaseAuthFlowTesterTest { /// Migrate within same CA (scope upgrade). func testMigrateCA_AddMoreScopes() throws { launchAndLogin( + loginHost: .regularAuth, user:.second, staticAppConfigName: .caAdvancedJwt, staticScopeSelection: .subset ) migrateAndValidate( + loginHost: .regularAuth, staticAppConfigName: .caAdvancedJwt, staticScopeSelection: .subset, migrationAppConfigName: .caAdvancedJwt, @@ -55,11 +57,13 @@ class MigrationTests: BaseAuthFlowTesterTest { /// Migrate within same ECA (scope upgrade). func testMigrateECA_AddMoreScopes() throws { launchAndLogin( + loginHost: .regularAuth, user:.second, staticAppConfigName: .ecaAdvancedJwt, staticScopeSelection: .subset ) migrateAndValidate( + loginHost: .regularAuth, staticAppConfigName: .ecaAdvancedJwt, staticScopeSelection: .subset, migrationAppConfigName: .ecaAdvancedJwt, @@ -70,11 +74,13 @@ class MigrationTests: BaseAuthFlowTesterTest { /// Migrate within same Beacon (scope upgrade). func testMigrateBeacon_AddMoreScopes() throws { launchAndLogin( + loginHost: .regularAuth, user:.second, staticAppConfigName: .beaconAdvancedJwt, staticScopeSelection: .subset ) migrateAndValidate( + loginHost: .regularAuth, staticAppConfigName: .beaconAdvancedJwt, staticScopeSelection: .subset, migrationAppConfigName: .beaconAdvancedJwt, @@ -87,10 +93,12 @@ class MigrationTests: BaseAuthFlowTesterTest { // Migrate from CA to Beacon func testMigrateCAToBeacon() throws { launchAndLogin( + loginHost: .regularAuth, user:.second, staticAppConfigName: .caAdvancedOpaque ) migrateAndValidate( + loginHost: .regularAuth, staticAppConfigName: .caAdvancedOpaque, migrationAppConfigName: .beaconAdvancedOpaque ) @@ -99,10 +107,12 @@ class MigrationTests: BaseAuthFlowTesterTest { // Migrate from Beacon to CA func testMigrateBeaconToCA() throws { launchAndLogin( + loginHost: .regularAuth, user:.second, staticAppConfigName: .beaconAdvancedOpaque ) migrateAndValidate( + loginHost: .regularAuth, staticAppConfigName: .beaconAdvancedOpaque, migrationAppConfigName: .caAdvancedOpaque ) @@ -113,14 +123,17 @@ class MigrationTests: BaseAuthFlowTesterTest { /// Migrate from CA to ECA and back to CA func testMigrateCAToECA() throws { launchAndLogin( + loginHost: .regularAuth, user:.second, staticAppConfigName: .caAdvancedOpaque ) migrateAndValidate( + loginHost: .regularAuth, staticAppConfigName: .caAdvancedOpaque, migrationAppConfigName: .ecaAdvancedOpaque ) migrateAndValidate( + loginHost: .regularAuth, staticAppConfigName: .caAdvancedOpaque, // should not have changed migrationAppConfigName: .caAdvancedOpaque ) @@ -129,14 +142,17 @@ class MigrationTests: BaseAuthFlowTesterTest { // Migrate from CA to Beacon and back to CA func testMigrateCAToBeaconAndBack() throws { launchAndLogin( + loginHost: .regularAuth, user:.second, staticAppConfigName: .caAdvancedOpaque ) migrateAndValidate( + loginHost: .regularAuth, staticAppConfigName: .caAdvancedOpaque, migrationAppConfigName: .beaconAdvancedOpaque ) migrateAndValidate( + loginHost: .regularAuth, staticAppConfigName: .caAdvancedOpaque, // should not have changed migrationAppConfigName: .caAdvancedOpaque ) @@ -145,14 +161,17 @@ class MigrationTests: BaseAuthFlowTesterTest { /// Migrate from Beacon opaque to Beacon JWT and back to Beacon opaque func testMigrateBeaconOpaqueToJWTAndBack() throws { launchAndLogin( + loginHost: .regularAuth, user:.second, staticAppConfigName: .beaconAdvancedOpaque ) migrateAndValidate( + loginHost: .regularAuth, staticAppConfigName: .beaconAdvancedOpaque, migrationAppConfigName: .beaconAdvancedJwt ) migrateAndValidate( + loginHost: .regularAuth, staticAppConfigName: .beaconAdvancedOpaque, // should not have changed migrationAppConfigName: .beaconAdvancedOpaque ) diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/MultiUserLoginTests.swift b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/MultiUserLoginTests.swift index 137bfc8f18..f46e604cf5 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/MultiUserLoginTests.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/MultiUserLoginTests.swift @@ -33,7 +33,7 @@ import XCTest /// - Same or different app types (opaque vs JWT) /// - Same or different scopes /// -/// NB: Tests use the fourth and fifth user from test_config.json +/// NB: Tests use the fourth and fifth user from ui_test_config.json /// class MultiUserLoginTests: BaseAuthFlowTesterTest { @@ -43,24 +43,28 @@ class MultiUserLoginTests: BaseAuthFlowTesterTest { func testBothStatic_SameApp_SameScopes() throws { // Initial user launchAndLogin( + loginHost: .regularAuth, user: .fourth, staticAppConfigName: .ecaAdvancedOpaque ) // Other user loginOtherUserAndValidate( + loginHost: .regularAuth, user: .fifth, staticAppConfigName: .ecaAdvancedOpaque ) // Switch back to initial user switchToUserAndValidate( + loginHost: .regularAuth, user: .fourth, staticAppConfigName: .ecaAdvancedOpaque, userAppConfigName: .ecaAdvancedOpaque) // Switch back to other user switchToUserAndValidate( + loginHost: .regularAuth, user: .fifth, staticAppConfigName: .ecaAdvancedOpaque, userAppConfigName: .ecaAdvancedOpaque) @@ -73,24 +77,28 @@ class MultiUserLoginTests: BaseAuthFlowTesterTest { func testBothStatic_DifferentApps() throws { // Initial user launchAndLogin( + loginHost: .regularAuth, user: .fourth, staticAppConfigName: .ecaAdvancedOpaque ) // Other user loginOtherUserAndValidate( + loginHost: .regularAuth, user: .fifth, staticAppConfigName: .ecaAdvancedJwt ) // Switch back to initial user switchToUserAndValidate( + loginHost: .regularAuth, user: .fourth, staticAppConfigName: .ecaAdvancedJwt, // static config overwritten userAppConfigName: .ecaAdvancedOpaque) // Switch back to other user switchToUserAndValidate( + loginHost: .regularAuth, user: .fifth, staticAppConfigName: .ecaAdvancedJwt, userAppConfigName: .ecaAdvancedJwt) @@ -103,6 +111,7 @@ class MultiUserLoginTests: BaseAuthFlowTesterTest { func testBothStatic_SameApp_DifferentScopes() throws { // Initial user launchAndLogin( + loginHost: .regularAuth, user: .fourth, staticAppConfigName: .ecaAdvancedOpaque, staticScopeSelection: .subset @@ -110,12 +119,14 @@ class MultiUserLoginTests: BaseAuthFlowTesterTest { // Other user loginOtherUserAndValidate( + loginHost: .regularAuth, user: .fifth, staticAppConfigName: .ecaAdvancedOpaque ) // Switch back to initial user switchToUserAndValidate( + loginHost: .regularAuth, user: .fourth, staticAppConfigName: .ecaAdvancedOpaque, staticScopeSelection: .empty, @@ -125,6 +136,7 @@ class MultiUserLoginTests: BaseAuthFlowTesterTest { // Switch back to other user switchToUserAndValidate( + loginHost: .regularAuth, user: .fifth, staticAppConfigName: .ecaAdvancedOpaque, staticScopeSelection: .empty, @@ -142,12 +154,14 @@ class MultiUserLoginTests: BaseAuthFlowTesterTest { func testFirstStatic_SecondDynamic_DifferentApps() throws { // Initial user launchAndLogin( + loginHost: .regularAuth, user: .fourth, staticAppConfigName: .ecaAdvancedOpaque ) // Other user loginOtherUserAndValidate( + loginHost: .regularAuth, user: .fifth, staticAppConfigName: .ecaAdvancedOpaque, dynamicAppConfigName: .ecaAdvancedJwt @@ -155,6 +169,7 @@ class MultiUserLoginTests: BaseAuthFlowTesterTest { // Switch back to initial user switchToUserAndValidate( + loginHost: .regularAuth, user: .fourth, staticAppConfigName: .ecaAdvancedOpaque, userAppConfigName: .ecaAdvancedOpaque @@ -162,6 +177,7 @@ class MultiUserLoginTests: BaseAuthFlowTesterTest { // Switch back to other user switchToUserAndValidate( + loginHost: .regularAuth, user: .fifth, staticAppConfigName: .ecaAdvancedOpaque, userAppConfigName: .ecaAdvancedJwt, @@ -175,6 +191,7 @@ class MultiUserLoginTests: BaseAuthFlowTesterTest { func testFirstDynamic_SecondStatic_DifferentApps() throws { // Initial user launchAndLogin( + loginHost: .regularAuth, user: .fourth, staticAppConfigName: .ecaBasicOpaque, dynamicAppConfigName: .ecaAdvancedJwt @@ -182,12 +199,14 @@ class MultiUserLoginTests: BaseAuthFlowTesterTest { // Other user loginOtherUserAndValidate( + loginHost: .regularAuth, user: .fifth, staticAppConfigName: .ecaAdvancedOpaque ) // Switch back to initial user switchToUserAndValidate( + loginHost: .regularAuth, user: .fourth, staticAppConfigName: .ecaAdvancedOpaque, userAppConfigName: .ecaAdvancedJwt @@ -195,6 +214,7 @@ class MultiUserLoginTests: BaseAuthFlowTesterTest { // Switch back to other user switchToUserAndValidate( + loginHost: .regularAuth, user: .fifth, staticAppConfigName: .ecaAdvancedOpaque, userAppConfigName: .ecaAdvancedOpaque, @@ -210,6 +230,7 @@ class MultiUserLoginTests: BaseAuthFlowTesterTest { func testBothDynamic_DifferentApps() throws { // Initial user launchAndLogin( + loginHost: .regularAuth, user: .fourth, staticAppConfigName: .ecaBasicOpaque, dynamicAppConfigName: .ecaAdvancedOpaque @@ -217,6 +238,7 @@ class MultiUserLoginTests: BaseAuthFlowTesterTest { // Other user loginOtherUserAndValidate( + loginHost: .regularAuth, user: .fifth, staticAppConfigName: .ecaBasicOpaque, dynamicAppConfigName: .ecaAdvancedJwt @@ -224,6 +246,7 @@ class MultiUserLoginTests: BaseAuthFlowTesterTest { // Switch back to initial user switchToUserAndValidate( + loginHost: .regularAuth, user: .fourth, staticAppConfigName: .ecaBasicOpaque, userAppConfigName: .ecaAdvancedOpaque @@ -231,6 +254,7 @@ class MultiUserLoginTests: BaseAuthFlowTesterTest { // Switch back to other user switchToUserAndValidate( + loginHost: .regularAuth, user: .fifth, staticAppConfigName: .ecaBasicOpaque, userAppConfigName: .ecaAdvancedJwt, diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/TestConfigUtils.swift b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/UITestConfigUtils.swift similarity index 69% rename from native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/TestConfigUtils.swift rename to native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/UITestConfigUtils.swift index 1d0329987d..3fe233f9da 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/TestConfigUtils.swift +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/Tests/UITestConfigUtils.swift @@ -1,5 +1,5 @@ /* - TestConfigUtils.swift + UITestConfigUtils.swift AuthFlowTesterUITests Copyright (c) 2025-present, salesforce.com, inc. All rights reserved. @@ -38,34 +38,37 @@ enum TestConfigError: Error, CustomStringConvertible { case userNotFound(String) case appNotFound(String) case appNotConfigured(String) + case loginHostNotFound(String) var description: String { switch self { case .noPrimaryUser: - return "No primary user found in test_config.json" + return "No primary user found in ui_test_config.json" case .noSecondaryUser: - return "No secondary user found in test_config.json" + return "No secondary user found in ui_test_config.json" case .noThirdUser: - return "No third user found in test_config.json" + return "No third user found in ui_test_config.json" case .noFourthUser: - return "No fourth user found in test_config.json" + return "No fourth user found in ui_test_config.json" case .noFifthUser: - return "No fifth user found in test_config.json" + return "No fifth user found in ui_test_config.json" case .userNotFound(let username): - return "User '\(username)' not found in test_config.json" + return "User '\(username)' not found in ui_test_config.json" case .appNotFound(let appName): - return "App '\(appName)' not found in test_config.json" + return "App '\(appName)' not found in ui_test_config.json" case .appNotConfigured(let appName): - return "App '\(appName)' has empty consumerKey in test_config.json" + return "App '\(appName)' has empty consumerKey in ui_test_config.json" + case .loginHostNotFound(let hostName): + return "Login host '\(hostName)' not found in ui_test_config.json" } } } -// MAKR: - ScopeSelection +// 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 test_config.json - case subset // will send a subset of the scopes defined in test_config.json + 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 } // MARK: - Configured Users @@ -78,6 +81,13 @@ enum KnownUserConfig { case fifth } +// MARK: - Login Host Names + +enum KnownLoginHostConfig: String { + case regularAuth = "regular_auth" + case advancedAuth = "advanced_auth" +} + // MARK: - App Names enum KnownAppConfig: String { @@ -97,6 +107,20 @@ enum KnownAppConfig: String { // MARK: - Configuration Models +/// Represents a login host configuration for testing +struct LoginHostConfig: Codable { + let name: String + let url: String + let users: [UserConfig] + + /// Returns URL without protocol (https:// or http://) + var urlNoProtocol: String { + return url + .replacingOccurrences(of: "https://", with: "") + .replacingOccurrences(of: "http://", with: "") + } +} + /// Represents an app configuration for testing struct AppConfig: Codable { let name: String @@ -123,18 +147,17 @@ struct UserConfig: Codable { /// Represents the complete test configuration struct TestConfig: Codable { - let loginHost: String + let loginHosts: [LoginHostConfig] let apps: [AppConfig] - let users: [UserConfig] } // MARK: - Configuration Utility -/// Utility class to parse and access test configuration from test_config.json in the test bundle -class TestConfigUtils { +/// Utility class to parse and access test configuration from ui_test_config.json in the test bundle +class UITestConfigUtils { /// Shared singleton instance - static let shared = TestConfigUtils() + static let shared = UITestConfigUtils() /// Parsed test configuration (nil if not provided or parsing failed) private(set) var config: TestConfig? @@ -148,23 +171,23 @@ class TestConfigUtils { // MARK: - Configuration Parsing - /// Parses the test configuration from test_config.json file in the test bundle + /// Parses the test configuration from ui_test_config.json file in the test bundle private func parseConfiguration() { // Get the bundle for this class - let bundle = Bundle(for: TestConfigUtils.self) + let bundle = Bundle(for: UITestConfigUtils.self) - // Look for test_config.json file - guard let configPath = bundle.path(forResource: "test_config", ofType: "json") else { - print("⚠️ test_config.json file not found in test bundle") + // Look for ui_test_config.json file + guard let configPath = bundle.path(forResource: "ui_test_config", ofType: "json") else { + print("⚠️ ui_test_config.json file not found in test bundle") return } // Read the file contents guard let jsonData = try? Data(contentsOf: URL(fileURLWithPath: configPath)) else { - let error = NSError(domain: "TestConfigUtils", code: 1, - userInfo: [NSLocalizedDescriptionKey: "Failed to read test_config.json file"]) + let error = NSError(domain: "UITestConfigUtils", code: 1, + userInfo: [NSLocalizedDescriptionKey: "Failed to read ui_test_config.json file"]) parseError = error - print("❌ Failed to read test_config.json file") + print("❌ Failed to read ui_test_config.json file") return } @@ -172,10 +195,9 @@ class TestConfigUtils { do { let decoder = JSONDecoder() config = try decoder.decode(TestConfig.self, from: jsonData) - print("✅ Test configuration loaded successfully from test_config.json") - print(" Login Host: \(config?.loginHost ?? "none")") + print("✅ Test configuration loaded successfully from ui_test_config.json") + print(" Login Hosts: \(config?.loginHosts.count ?? 0)") print(" Apps: \(config?.apps.count ?? 0)") - print(" Users: \(config?.users.count ?? 0)") } catch { parseError = error print("❌ Failed to parse test configuration: \(error.localizedDescription)") @@ -189,15 +211,9 @@ class TestConfigUtils { return config != nil } - /// Returns the login host from configuration - var loginHost: String? { - return config?.loginHost - } - - var loginHostNoProtocol: String? { - return loginHost? - .replacingOccurrences(of: "https://", with: "") - .replacingOccurrences(of: "http://", with: "") + /// Returns all login hosts from configuration + var loginHosts: [LoginHostConfig] { + return config?.loginHosts ?? [] } /// Returns all apps from configuration @@ -205,11 +221,6 @@ class TestConfigUtils { return config?.apps ?? [] } - /// Returns all users from configuration - var users: [UserConfig] { - return config?.users ?? [] - } - // MARK: - Scope Utilities /// Removes a scope from a space-separated scope string. @@ -231,41 +242,51 @@ class TestConfigUtils { // MARK: - Throwing Accessors - /// Returns a user by their position (first or second) or throws an error if not found - func getUser(_ user: KnownUserConfig) throws -> UserConfig { + /// Returns a login host configuration by its name or throws an error if not found + func getLoginHost(_ loginHost: KnownLoginHostConfig) throws -> LoginHostConfig { + guard let hostConfig = config?.loginHosts.first(where: { $0.name == loginHost.rawValue }) else { + throw TestConfigError.loginHostNotFound(loginHost.rawValue) + } + return hostConfig + } + + /// Returns a user by their position (first, second, etc.) for a specific login host or throws an error if not found + func getUser(_ loginHost: KnownLoginHostConfig, _ user: KnownUserConfig) throws -> UserConfig { + let hostConfig = try getLoginHost(loginHost) + switch user { case .first: - guard let firstUser = config?.users.first else { + guard let firstUser = hostConfig.users.first else { throw TestConfigError.noPrimaryUser } return firstUser case .second: - guard let users = config?.users, users.count >= 2 else { + guard hostConfig.users.count >= 2 else { throw TestConfigError.noSecondaryUser } - return users[1] + return hostConfig.users[1] case .third: - guard let users = config?.users, users.count >= 3 else { + guard hostConfig.users.count >= 3 else { throw TestConfigError.noThirdUser } - return users[2] + return hostConfig.users[2] case .fourth: - guard let users = config?.users, users.count >= 4 else { + guard hostConfig.users.count >= 4 else { throw TestConfigError.noFourthUser } - return users[3] + return hostConfig.users[3] case .fifth: - guard let users = config?.users, users.count >= 5 else { + guard hostConfig.users.count >= 5 else { throw TestConfigError.noFifthUser } - return users[4] + return hostConfig.users[4] } } - /// Returns a known user config by their username or throws an error if not found - func getKnownUserConfig(byUsername username: String) throws -> KnownUserConfig { - guard let users = config?.users, - let index = users.firstIndex(where: { $0.username == username }) else { + /// Returns a known user config by their username for a specific login host or throws an error if not found + func getKnownUserConfig(_ loginHost: KnownLoginHostConfig, byUsername username: String) throws -> KnownUserConfig { + let hostConfig = try getLoginHost(loginHost) + guard let index = hostConfig.users.firstIndex(where: { $0.username == username }) else { throw TestConfigError.userNotFound(username) } switch index { @@ -301,10 +322,9 @@ class TestConfigUtils { /// Returns expected scopes granted func getExpectedScopesGranted(for appConfig:AppConfig, _ scopeSelection: ScopeSelection) -> String { switch(scopeSelection) { - case .empty: return appConfig.scopes // that assumes the scopes in test_config.json match the server config + 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 .all: return appConfig.scopes } } } - diff --git a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/test_config.json.sample b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/ui_test_config.json.sample similarity index 60% rename from native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/test_config.json.sample rename to native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/ui_test_config.json.sample index 109a70d952..f04ec91927 100644 --- a/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/test_config.json.sample +++ b/native/SampleApps/AuthFlowTester/AuthFlowTesterUITests/ui_test_config.json.sample @@ -1,5 +1,58 @@ { - "loginHost": "https://testorg.my.salesforce.com", + "loginHosts": [ + { + "name": "regular_auth", + "url": "https://your-test-org.my.salesforce.com", + "users": [ + { + "username": "testios1@testorg.com", + "password": "yourPasswordHere" + }, + { + "username": "testios2@testorg.com", + "password": "yourPasswordHere" + }, + { + "username": "testios3@testorg.com", + "password": "yourPasswordHere" + }, + { + "username": "testios4@testorg.com", + "password": "yourPasswordHere" + }, + { + "username": "testios5@testorg.com", + "password": "yourPasswordHere" + } + ] + }, + { + "name": "advanced_auth", + "url": "https://your-advanced-test-org.my.salesforce.com", + "users": [ + { + "username": "testios1@advancedtestorg.com", + "password": "yourPasswordHere" + }, + { + "username": "testios2@advancedtestorg.com", + "password": "yourPasswordHere" + }, + { + "username": "testios3@advancedtestorg.com", + "password": "yourPasswordHere" + }, + { + "username": "testios4@advancedtestorg.com", + "password": "yourPasswordHere" + }, + { + "username": "testios5@advancedtestorg.com", + "password": "yourPasswordHere" + } + ] + } + ], "apps": [ { "name": "ca_basic_opaque", @@ -53,7 +106,7 @@ "name": "beacon_basic_opaque", "consumerKey": "your_consumer_key_here", "redirectUri": "beaconbasicopaque://success/done", - "scopes": "api id refresh_token" + "scopes": "api profile refresh_token" }, { "name": "beacon_basic_jwt", @@ -65,23 +118,13 @@ "name": "beacon_advanced_opaque", "consumerKey": "your_consumer_key_here", "redirectUri": "beaconadvancedopaque://success/done", - "scopes": "api content id lightning refresh_token sfap_api visualforce web" + "scopes": "api content id lightning refresh_token sfap_api web" }, { "name": "beacon_advanced_jwt", "consumerKey": "your_consumer_key_here", "redirectUri": "beaconadvancedjwt://success/done", - "scopes": "api content id lightning refresh_token sfap_api visualforce web" - } - ], - "users": [ - { - "username": "testios1@testorg.com", - "password": "yourPasswordHere" - }, - { - "username": "testios2@testorg.com", - "password": "yourPasswordHere" + "scopes": "api content id lightning refresh_token sfap_api web" } ] }