Skip to content
Merged
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
76 changes: 38 additions & 38 deletions .github/workflows/nightly.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,42 +7,42 @@ on:
workflow_dispatch:

jobs:
ios-nightly:
strategy:
fail-fast: false
matrix:
lib: [SalesforceSDKCommon, SalesforceAnalytics, SalesforceSDKCore, SmartStore, MobileSync]
ios: [^26, ^18, ^17]
include:
- ios: ^26
xcode: ^26
- ios: ^18
xcode: ^16
- ios: ^17
xcode: ^16
uses: ./.github/workflows/reusable-test-workflow.yaml
with:
lib: ${{ matrix.lib }}
ios: ${{ matrix.ios }}
xcode: ${{ matrix.xcode }}
secrets: inherit
ios-nightly:
strategy:
fail-fast: false
matrix:
lib: [SalesforceSDKCommon, SalesforceAnalytics, SalesforceSDKCore, SmartStore, MobileSync]
ios: [^26, ^18, ^17]
include:
- ios: ^26
xcode: ^26
- ios: ^18
xcode: ^16
- ios: ^17
xcode: ^16
uses: ./.github/workflows/reusable-test-workflow.yaml
with:
lib: ${{ matrix.lib }}
ios: ${{ matrix.ios }}
xcode: ${{ matrix.xcode }}
secrets: inherit

native-samples-nightly:
strategy:
fail-fast: false
matrix:
app: [RestAPIExplorer, MobileSyncExplorer, AuthFlowTester]
ios: [^26, ^18, ^17]
include:
- ios: ^26
xcode: ^26
- ios: ^18
xcode: ^16
- ios: ^17
xcode: ^16
uses: ./.github/workflows/reusable-build-workflow.yaml
with:
app: ${{ matrix.app }}
ios: ${{ matrix.ios }}
xcode: ${{ matrix.xcode }}
secrets: inherit
native-samples-nightly:
strategy:
fail-fast: false
matrix:
app: [RestAPIExplorer, MobileSyncExplorer, AuthFlowTester]
ios: [^26, ^18, ^17]
include:
- ios: ^26
xcode: ^26
- ios: ^18
xcode: ^16
- ios: ^17
xcode: ^16
uses: ./.github/workflows/reusable-build-workflow.yaml
with:
app: ${{ matrix.app }}
ios: ${{ matrix.ios }}
xcode: ${{ matrix.xcode }}
secrets: inherit
2 changes: 2 additions & 0 deletions .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,6 @@ jobs:
with:
is_pr: true
pr_test: "AuthFlowTesterUITests/LegacyLoginTests/testCAOpaque_DefaultScopes_WebServerFlow"
short_timeout: "2"
long_timeout: "7"
secrets: inherit
74 changes: 65 additions & 9 deletions .github/workflows/reusable-ui-test-workflow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,39 @@ on:
default: ""
required: false
type: string
test_suite:
description: "Test suite to run (e.g. 'AuthFlowTesterUITests/BeaconLoginTests'). Empty = run all tests. Used with matrix for parallel nightly runs."
default: ""
required: false
type: string
destination:
description: "xcodebuild -destination (e.g. 'platform=iOS Simulator,name=iPhone 17,OS=26.0'). Empty = use default for iOS 26."
default: ""
required: false
type: string
short_timeout:
description: "UI test short timeout in seconds (env UI_TEST_SHORT_TIMEOUT). Empty = use code default (1)."
default: ""
required: false
type: string
long_timeout:
description: "UI test long timeout in seconds (env UI_TEST_LONG_TIMEOUT). Empty = use code default (3)."
default: ""
required: false
type: string

jobs:
test-ui:
runs-on: ${{ inputs.macos }}
steps:
- name: Set result suffix for matrix runs
id: result_suffix
run: |
if [ -n "${{ inputs.test_suite }}" ]; then
echo "suffix=$(echo '${{ inputs.test_suite }}' | tr '/' '-')" >> "$GITHUB_OUTPUT"
else
echo "suffix=all" >> "$GITHUB_OUTPUT"
fi
- uses: actions/checkout@v4
if: ${{ inputs.is_pr }}
with:
Expand Down Expand Up @@ -62,18 +85,25 @@ jobs:
if [ -n "${{ inputs.destination }}" ]; then
echo "destination=${{ inputs.destination }}" >> "$GITHUB_OUTPUT"
else
# Extract major iOS version from input (e.g. ^26 -> 26, 26.0 -> 26)
IOS_VERSION="${{ inputs.ios }}"
IOS_MAJOR=$(echo "$IOS_VERSION" | sed 's/^\^//' | cut -d. -f1)
SIM_UDID=$(xcrun simctl list devices available -j | python3 -c '
import sys, json
major = sys.argv[1]
d = json.load(sys.stdin)
for devices in d.get("devices", {}).values():
runtime_prefix = "iOS-" + major + "-"
for runtime_key, devices in d.get("devices", {}).items():
if runtime_prefix not in runtime_key:
continue
for dev in devices:
if dev.get("isAvailable", True) and "iPhone" in dev.get("name", ""):
print(dev["udid"])
sys.exit(0)
sys.exit(1)
')
' "$IOS_MAJOR")
if [ -z "$SIM_UDID" ]; then
echo "::error::No available iPhone simulator found"
echo "::error::No available iPhone simulator found for iOS $IOS_MAJOR"
exit 1
fi
echo "destination=platform=iOS Simulator,id=$SIM_UDID" >> "$GITHUB_OUTPUT"
Expand All @@ -85,7 +115,10 @@ jobs:
ONLY_TESTING_ARGS=()
if [ -n "${{ inputs.pr_test }}" ]; then
ONLY_TESTING_ARGS+=(-only-testing "${{ inputs.pr_test }}")
elif [ -n "${{ inputs.test_suite }}" ]; then
ONLY_TESTING_ARGS+=(-only-testing "${{ inputs.test_suite }}")
fi
set -o pipefail
xcodebuild test \
-workspace SalesforceMobileSDK.xcworkspace \
-scheme AuthFlowTester \
Expand All @@ -97,13 +130,36 @@ jobs:
| xcbeautify
env:
CODE_COVERAGE: YES
- name: Publish test results
uses: kishikawakatsumi/xcresulttool@v1
# Xcode 15.3+ only forwards env vars prefixed with TEST_RUNNER_ to the test runner (prefix is stripped).
TEST_RUNNER_UI_TEST_SHORT_TIMEOUT: ${{ inputs.short_timeout }}
TEST_RUNNER_UI_TEST_LONG_TIMEOUT: ${{ inputs.long_timeout }}
- name: Verify xcresult bundle exists
if: success() || failure()
run: |
if [ ! -d "test.xcresult" ]; then
echo "::error::test.xcresult bundle was not created"
exit 1
fi
echo "xcresult bundle exists and contains:"
ls -lh test.xcresult/
- name: Parse test results
if: success() || failure()
run: |
brew install xcresultparser
xcresultparser -o junit test.xcresult > test-results-authflowtester-ui-ios${{ inputs.ios }}-${{ steps.result_suffix.outputs.suffix }}.xml
- name: Test Report
uses: mikepenz/action-junit-report@v5
if: success() || failure()
with:
path: test.xcresult
show-passed-tests: true
title: AuthFlowTester UI Test Results
check_name: AuthFlowTester UI Test Results ${{ steps.result_suffix.outputs.suffix }}
job_name: AuthFlowTester UI Test Results ${{ steps.result_suffix.outputs.suffix }}
require_tests: true
include_empty_in_summary: false
simplified_summary: true
detailed_summary: true
comment: true
job_summary: true
report_paths: 'test-results-authflowtester-ui-ios${{ inputs.ios }}-${{ steps.result_suffix.outputs.suffix }}.xml'
- uses: codecov/codecov-action@v4
if: success() || failure()
with:
Expand All @@ -114,6 +170,6 @@ jobs:
if: success() || failure()
uses: actions/upload-artifact@v4
with:
name: xcresult-authflowtester-ui-ios${{ inputs.ios }}
name: xcresult-authflowtester-ui-ios${{ inputs.ios }}-${{ steps.result_suffix.outputs.suffix }}
path: test.xcresult/
retention-days: 30
4 changes: 3 additions & 1 deletion .github/workflows/ui-test-nightly.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: UI Nightly Tests

on:
schedule:
- cron: "0 5 * * 3,5" # cron is UTC, this translates to 10 PM PST Tues and Thur.
- cron: "0 5 * * 2,4" # cron is UTC, this translates to 10 PM PST Mon and Wed.
workflow_dispatch:

jobs:
Expand All @@ -11,4 +11,6 @@ jobs:
with:
ios: "^26"
xcode: "^26"
short_timeout: "2"
long_timeout: "7"
secrets: inherit
Original file line number Diff line number Diff line change
Expand Up @@ -213,14 +213,13 @@ struct JwtDetailsData {
/// and extract data (user credentials, OAuth configuration, JWT details) from the UI.
class AuthFlowTesterMainPageObject {
let app: XCUIApplication
let timeout: double_t = 3


init(testApp: XCUIApplication) {
app = testApp
}

func isShowing() -> Bool {
return navigationTitle().waitForExistence(timeout: 1)
return navigationTitle().waitForExistence(timeout: UITestTimeouts.long)
}

func performLogout() {
Expand All @@ -231,7 +230,7 @@ class AuthFlowTesterMainPageObject {
func makeRestRequest() -> Bool {
tap(makeRestRequestButton())
let alert = app.alerts["Request Successful"]
if (alert.waitForExistence(timeout: timeout)) {
if (alert.waitForExistence(timeout: UITestTimeouts.long)) {
alert.buttons["OK"].tap()
return true
}
Expand All @@ -241,7 +240,7 @@ class AuthFlowTesterMainPageObject {
func revokeAccessToken() -> Bool {
tap(revokeButton())
let alert = app.alerts["Access Token Revoked"]
if (alert.waitForExistence(timeout: timeout)) {
if (alert.waitForExistence(timeout: UITestTimeouts.long)) {
alert.buttons["OK"].tap()
return true
}
Expand Down Expand Up @@ -282,7 +281,7 @@ class AuthFlowTesterMainPageObject {
tapIfPresent(allowButton())

let alert = app.alerts["Migration Error"]
if (alert.waitForExistence(timeout: timeout)) {
if (alert.waitForExistence(timeout: UITestTimeouts.long)) {
alert.buttons["OK"].tap()
return false
}
Expand All @@ -309,7 +308,7 @@ class AuthFlowTesterMainPageObject {

// Wait for alert to appear
let alert = importConfigAlert()
_ = alert.waitForExistence(timeout: timeout)
_ = alert.waitForExistence(timeout: UITestTimeouts.long)

// Type into the alert's text field
let textField = importConfigTextField()
Expand Down Expand Up @@ -438,25 +437,25 @@ class AuthFlowTesterMainPageObject {
// MARK: - Actions

private func tap(_ element: XCUIElement) {
_ = element.waitForExistence(timeout: timeout)
_ = element.waitForExistence(timeout: UITestTimeouts.long)
element.tap()
}

private func tapIfPresent(_ element: XCUIElement) {
if (element.waitForExistence(timeout: timeout)) {
if (element.waitForExistence(timeout: UITestTimeouts.long)) {
element.tap()
}
}

private func setTextField(_ textField: XCUIElement, value: String) {
_ = textField.waitForExistence(timeout: timeout)
_ = textField.waitForExistence(timeout: UITestTimeouts.long)
textField.tap()

// Clear any existing text
if let currentValue = textField.value as? String, !currentValue.isEmpty {
textField.tap()
let selectAll = app.menuItems["Select All"]
if selectAll.waitForExistence(timeout: 1) {
if selectAll.waitForExistence(timeout: UITestTimeouts.short) {
selectAll.tap()
textField.typeText(XCUIKeyboardKey.delete.rawValue)
}
Expand Down Expand Up @@ -535,7 +534,7 @@ class AuthFlowTesterMainPageObject {

func getJwtDetails() -> JwtDetailsData? {
// Check if JWT export button exists (indicates JWT token is available)
guard exportJwtTokenButton().waitForExistence(timeout: 1) else {
guard exportJwtTokenButton().waitForExistence(timeout: UITestTimeouts.short) else {
return nil
}

Expand Down Expand Up @@ -571,7 +570,7 @@ class AuthFlowTesterMainPageObject {

// Wait for and get the alert
let alert = app.alerts[alertTitle]
guard alert.waitForExistence(timeout: timeout) else {
guard alert.waitForExistence(timeout: UITestTimeouts.long) else {
return [:]
}

Expand All @@ -594,7 +593,7 @@ class AuthFlowTesterMainPageObject {

private func hasStaticText(_ text: String) -> Bool {
let staticText = app.staticTexts.containing(NSPredicate(format: "label CONTAINS '\(text)'"))
return staticText.firstMatch.waitForExistence(timeout: timeout)
return staticText.firstMatch.waitForExistence(timeout: UITestTimeouts.long)
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import SalesforceSDKCore
/// Use after navigating to Login Options from the login screen (e.g. via Settings → Login Options).
class LoginOptionsPageObject {
let app: XCUIApplication
let timeout: double_t = 2

init(testApp: XCUIApplication) {
app = testApp
Expand Down Expand Up @@ -101,7 +100,7 @@ class LoginOptionsPageObject {

// Wait for alert and enter JSON (text field is automatically focused)
let alert = app.alerts["Import Configuration"]
_ = alert.waitForExistence(timeout: timeout)
_ = alert.waitForExistence(timeout: UITestTimeouts.long)

let textField = alert.textFields.firstMatch
textField.typeText(jsonString)
Expand All @@ -116,7 +115,7 @@ class LoginOptionsPageObject {

// Wait for alert and enter JSON (text field is automatically focused)
let alert = app.alerts["Import Discovery Result"]
_ = alert.waitForExistence(timeout: timeout)
_ = alert.waitForExistence(timeout: UITestTimeouts.long)

let textField = alert.textFields.firstMatch
textField.typeText(jsonString)
Expand Down Expand Up @@ -153,12 +152,12 @@ class LoginOptionsPageObject {
// MARK: - Actions

private func tap(_ element: XCUIElement) {
_ = element.waitForExistence(timeout: timeout)
_ = element.waitForExistence(timeout: UITestTimeouts.long)
element.tap()
}

private func setSwitchField(_ switchField: XCUIElement, value: Bool) {
_ = switchField.waitForExistence(timeout: timeout)
_ = switchField.waitForExistence(timeout: UITestTimeouts.long)

// Switch values are "0" (off) or "1" (on) in XCTest
let currentValue = (switchField.value as? String) == "1"
Expand Down
Loading
Loading