Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/reusable-ui-test-workflow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,42 +27,91 @@

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) {
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
}
.padding(.horizontal)
}
}
.padding(.vertical)
.alert("Import Auth Flow Types", isPresented: $showImportAlert) {
TextField("Paste JSON here", text: $importJSONText)
Button("Import") {
applyAuthFlowTypesFromJSON(importJSONText)
}
Button("Cancel", role: .cancel) { }
} message: {
Text("Paste JSON with useWebServerFlow and useHybridFlow")
}
}

// MARK: - Helper Methods

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
}

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
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
overview.md,
ui_test_config.json,
);
target = 4F8E4AF02ED13CE800DA7B7A /* AuthFlowTesterUITests */;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,10 @@
}
.sheet(isPresented: $showMigrateRefreshToken) {
NavigationView {
VStack {
VStack(spacing: 20) {
AuthFlowTypesView()
.padding(.top)

BootConfigEditor(
title: "New App Configuration",
buttonLabel: "Migrate refresh token",
Expand Down Expand Up @@ -252,7 +255,7 @@

// Update the rootView with actual closures after init
self.rootView = SessionDetailView(
onChangeConsumerKey: { [weak self] in

Check warning on line 258 in native/SampleApps/AuthFlowTester/AuthFlowTester/ViewControllers/SessionDetailViewController.swift

View workflow job for this annotation

GitHub Actions / native-samples-pr (AuthFlowTester, ^18) / build-sample-app

variable 'self' was written to, but never read
// Alert is handled in SwiftUI
},
onSwitchUser: { [weak self] in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -266,20 +268,26 @@ 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 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)
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()
Expand All @@ -305,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())
}

Expand Down Expand Up @@ -433,7 +441,7 @@ class AuthFlowTesterMainPageObject {
private func swithToUserButton() -> XCUIElement {
return app.buttons["Switch to User"]
}

// MARK: - Actions

private func tap(_ element: XCUIElement) {
Expand All @@ -450,7 +458,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()
Expand All @@ -460,10 +468,10 @@ class AuthFlowTesterMainPageObject {
textField.typeText(XCUIKeyboardKey.delete.rawValue)
}
}

textField.typeText(value)
}

// MARK: - Data Extraction Methods

func getUserCredentials() -> UserCredentialsData {
Expand Down
Loading
Loading