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
9 changes: 6 additions & 3 deletions .github/workflows/browserstack-e2e-android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@
# This software may be modified and distributed under the terms
# of the MIT license. See the LICENSE file for details.
#
# TODO: This workflow is not enforced right now and is currently failing.
# We have an open ticket with BrowserStack waiting for them to release a new version
# of the cloud driver based on Detox version 20.43+.
name: BrowserStack E2E — Android

on:
Expand Down Expand Up @@ -146,12 +143,18 @@ jobs:
# across all included modules so the directories are present.
run: |
cd android && ./gradlew \
:ping-identity_rn-binding:generateCodegenArtifactsFromSchema \
:ping-identity_rn-browser:generateCodegenArtifactsFromSchema \
:ping-identity_rn-device-client:generateCodegenArtifactsFromSchema \
:ping-identity_rn-device-id:generateCodegenArtifactsFromSchema \
:ping-identity_rn-device-profile:generateCodegenArtifactsFromSchema \
:ping-identity_rn-external-idp:generateCodegenArtifactsFromSchema \
:ping-identity_rn-fido:generateCodegenArtifactsFromSchema \
:ping-identity_rn-journey:generateCodegenArtifactsFromSchema \
:ping-identity_rn-logger:generateCodegenArtifactsFromSchema \
:ping-identity_rn-oath:generateCodegenArtifactsFromSchema \
:ping-identity_rn-oidc:generateCodegenArtifactsFromSchema \
:ping-identity_rn-push:generateCodegenArtifactsFromSchema \
:ping-identity_rn-storage:generateCodegenArtifactsFromSchema \
:react-native-async-storage_async-storage:generateCodegenArtifactsFromSchema

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/browserstack-parse-results-ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ jobs:

steps:
- name: Wait for BrowserStack XCUITest build to finish
timeout-minutes: 40
timeout-minutes: 60
shell: bash
run: |
set -euo pipefail

build_id="${{ inputs.browserstack-build-id }}"
echo "Polling BrowserStack build: $build_id"

for attempt in $(seq 1 180); do
for attempt in $(seq 1 240); do
response="$(curl --silent --fail-with-body \
-u "${{ secrets.BROWSERSTACK_USERNAME }}:${{ secrets.BROWSERSTACK_ACCESS_KEY }}" \
-X GET "https://api-cloud.browserstack.com/app-automate/xcuitest/v2/builds/${build_id}")"
Expand Down
80 changes: 57 additions & 23 deletions .github/workflows/browserstack-prep-ios-artifacts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
timeout-minutes: 60

env:
XCODE_VERSION: ${{ vars.XCODE_VERSION || '26.4.1' }}
XCODE_VERSION: ${{ vars.XCODE_VERSION || '26.2' }}
RUBY_VERSION: '2.6.10'
BUNDLE_GEMFILE: ${{ github.workspace }}/PingTestRunner/Gemfile
PING_TEST_RUNNER_DIR: PingTestRunner
Expand All @@ -52,33 +52,67 @@ jobs:
- name: Setup project dependencies
uses: ./.github/actions/setup-proj

# Resolve Xcode: the pinned XCODE_VERSION may exist on the runner but lack the iOS simulator runtime
# component (SDK ≠ runtime — the SDK ships inside Xcode but the runtime is provisioned separately).
# This step prefers the pinned version but falls back to the newest installed Xcode whose major.minor
# iOS version has a matching pre-provisioned simulator runtime, so the archive destination is always valid.
# Xcode version must be pinned to match the iOS SDK used when building XCUITest artifacts for
# BrowserStack real-device testing. Building with a newer Xcode (e.g. 26.x iOS SDK) produces
# binaries that reference XCTest symbols not present on older iOS real devices (e.g.
# _OBJC_CLASS_$_XCTCommandLineToolHelper), causing a dyld crash before any test runs.
# The pinned version must target an SDK compatible with the oldest iOS version used in BrowserStack
# test runs. These builds target real devices (-sdk iphoneos / generic/platform=iOS) — simulator
# runtimes are not relevant here and must not be used as a selection gate.
#
# Root cause of prior failures:
# 1. The original step used `xcrun simctl list runtimes` to gate Xcode selection. Because simulator
# runtimes are provisioned independently of Xcode on GitHub-hosted runners, the pinned Xcode was
# rejected whenever its matching simulator runtime was absent — even though this workflow never
# touches a simulator. This caused fallback to a newer Xcode (e.g. 26.4) whose XCTest SDK
# produced device binaries incompatible with iOS 17/18 BrowserStack devices.
# 2. Switching to a directory existence check was not sufficient: Xcode can be installed on the
# runner without its device platform component (Platforms/iPhoneOS.platform). When the platform
# is missing, ibtool fails with "iOS X.Y Platform Not Installed" during storyboard compilation
# even though xcodebuild is present and reports a valid version. The fix therefore checks for
# the actual iPhoneOS SDK directory inside the Xcode bundle, which is what ibtool and actool
# require at build time.
- name: Resolve Xcode version
run: |
# Check whether the iphoneos SDK platform component is installed inside a given Xcode app.
has_iphoneos_sdk() {
[ -d "$1/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs" ] \
&& ls "$1/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs"/iPhoneOS*.sdk 2>/dev/null | grep -q .
}

PINNED="/Applications/Xcode_${XCODE_VERSION}.app"
SELECTED=""
AVAILABLE_RUNTIMES=$(xcrun simctl list runtimes 2>/dev/null \
| grep -i "iOS" | grep -v "unavailable" \
| grep -oE "[0-9]+\.[0-9]+" | sort -u)
for app in "$PINNED" $(ls -d /Applications/Xcode*.app 2>/dev/null | sort -rV); do
[ -d "$app" ] || continue
xcode_ios=$(DEVELOPER_DIR="$app/Contents/Developer" \
xcodebuild -showsdks 2>/dev/null \
| grep -i "iphonesimulator" | grep -oE "[0-9]+\.[0-9]+" | sort -rV | head -1)
[ -n "$xcode_ios" ] || continue
if echo "$AVAILABLE_RUNTIMES" | grep -qx "$xcode_ios"; then
SELECTED="$app"
echo "XCODE_RESOLVED_PATH=$app" >> "$GITHUB_ENV"
echo "Resolved Xcode: $app (iOS SDK $xcode_ios, runtime available)"
break
if [ -d "$PINNED" ] && has_iphoneos_sdk "$PINNED"; then
echo "XCODE_RESOLVED_PATH=$PINNED" >> "$GITHUB_ENV"
echo "Resolved Xcode: $PINNED (pinned version ${XCODE_VERSION}, iphoneos SDK present)"
else
if [ -d "$PINNED" ]; then
echo "::warning::Pinned Xcode ${XCODE_VERSION} found but iphoneos SDK platform not installed"
else
echo "::warning::Pinned Xcode ${XCODE_VERSION} not found at $PINNED"
fi
echo "Available Xcode installs (with iphoneos SDK):"
AVAILABLE=$(ls -d /Applications/Xcode*.app 2>/dev/null | sort -V)
for app in $AVAILABLE; do
has_iphoneos_sdk "$app" && echo " $app" || echo " $app (no iphoneos SDK)"
done
# Pick the next version after the pinned one that has the iphoneos SDK installed
NEXT=""
for app in $(echo "$AVAILABLE" | sort -V); do
ver=$(echo "$app" | grep -oE "Xcode_[0-9]+(\.[0-9]+)*" | sed 's/Xcode_//')
[ -z "$ver" ] && continue
if awk -v v="$ver" -v pin="${XCODE_VERSION}" 'BEGIN{exit !(v > pin)}' && has_iphoneos_sdk "$app"; then
NEXT="$app"
break
fi
done
if [ -n "$NEXT" ]; then
echo "XCODE_RESOLVED_PATH=$NEXT" >> "$GITHUB_ENV"
echo "Falling back to next available version with iphoneos SDK: $NEXT"
else
echo "Skipping $app: iOS $xcode_ios SDK present but runtime not provisioned (available: $(echo $AVAILABLE_RUNTIMES | tr '\n' ' '))"
echo "::error::Pinned Xcode ${XCODE_VERSION} unavailable and no newer version with iphoneos SDK found"
exit 1
fi
done
[ -n "$SELECTED" ] || { echo "No Xcode with a matching provisioned iOS simulator runtime found"; exit 1; }
fi

- name: Select Xcode version
run: sudo xcode-select -s "$XCODE_RESOLVED_PATH" && /usr/bin/xcodebuild -version
Expand Down
66 changes: 11 additions & 55 deletions .github/workflows/build-and-test-ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,40 +83,9 @@ jobs:
working-directory: ${{ env.IOS_DIRECTORY }}
run: bundle exec pod install --repo-update

# Resolve Xcode: the pinned XCODE_VERSION may exist on the runner but lack the iOS simulator runtime
# component (SDK ≠ runtime — the SDK ships inside Xcode but the runtime is provisioned separately).
# This step prefers the pinned version but falls back to the newest installed Xcode whose major.minor
# iOS version has a matching pre-provisioned simulator runtime, so the destination is always bootable.
- name: Resolve Xcode version
run: |
PINNED="/Applications/Xcode_${{ env.XCODE_VERSION }}.app"
SELECTED=""
# Get all available iOS runtime versions (system-wide, not Xcode-scoped).
AVAILABLE_RUNTIMES=$(xcrun simctl list runtimes 2>/dev/null \
| grep -i "iOS" | grep -v "unavailable" \
| grep -oE "[0-9]+\.[0-9]+" | sort -u)
for app in "$PINNED" $(ls -d /Applications/Xcode*.app 2>/dev/null | sort -rV); do
[ -d "$app" ] || continue
# Derive the iOS SDK version this Xcode ships with (e.g. "26.1" from "iphonesimulator26.1").
xcode_ios=$(DEVELOPER_DIR="$app/Contents/Developer" \
xcodebuild -showsdks 2>/dev/null \
| grep -i "iphonesimulator" | grep -oE "[0-9]+\.[0-9]+" | sort -rV | head -1)
[ -n "$xcode_ios" ] || continue
# Check if a runtime matching this Xcode's major.minor iOS version is provisioned.
if echo "$AVAILABLE_RUNTIMES" | grep -qx "$xcode_ios"; then
SELECTED="$app"
echo "XCODE_RESOLVED_PATH=$app" >> "$GITHUB_ENV"
echo "Resolved Xcode: $app (iOS SDK $xcode_ios, runtime available)"
break
else
echo "Skipping $app: iOS $xcode_ios SDK present but runtime not provisioned (available: $(echo $AVAILABLE_RUNTIMES | tr '\n' ' '))"
fi
done
[ -n "$SELECTED" ] || { echo "No Xcode with a matching provisioned iOS simulator runtime found"; exit 1; }

# Select Xcode version
- name: Select Xcode version
run: sudo xcode-select -s "${{ env.XCODE_RESOLVED_PATH }}" && /usr/bin/xcodebuild -version
run: sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app && /usr/bin/xcodebuild -version
Comment thread
coderabbitai[bot] marked this conversation as resolved.

# Clang module caches are sensitive to SDK header mtimes on the runner image.
# This must run AFTER Xcode is selected so the active SDK is finalised.
Expand All @@ -136,32 +105,19 @@ jobs:
chmod +x product/xcresultparser
sudo mv product/xcresultparser /usr/local/bin/

# Resolve the best available iPhone simulator: prefer iPhone 17, fall back to any iPhone.
# Uses the device UDID as the destination to avoid OS version string mismatches between
# simctl runtime keys (e.g. "iOS-26-4") and xcodebuild's reported OS (e.g. "26.4.1").
# Resolve a simulator for Xcode 26.4.1 using xcrun simctl.
# xcodebuild -showdestinations only returns placeholders on a fresh CI runner.
# Pick the first iPhone from the iOS 26.4 runtime (matches the active Xcode SDK).
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Any reason we are pinning to 26.4 and not have it more relaxed?

# Sets DESTINATION and SIMULATOR_UDID in GITHUB_ENV for all subsequent steps.
- name: Resolve simulator destination
run: |
DEVICES_JSON=$(xcrun simctl list devices available --json)
RESULT=$(echo "$DEVICES_JSON" | jq -r '
[.devices | to_entries[]
| select(.key | test("iOS"))
| . as $runtime
| .value[]
| select(.name | test("^iPhone"))
| {
os: ($runtime.key | gsub("com.apple.CoreSimulator.SimRuntime.iOS-"; "") | gsub("-"; ".")),
name: .name,
udid: .udid
}]
| (map(select(.name == "iPhone 17")) | sort_by(.os) | last)
// (sort_by(.os) | last)
| "\(.name)|\(.os)|\(.udid)"
')
SIMULATOR_NAME=$(echo "$RESULT" | cut -d'|' -f1)
OS_VERSION=$(echo "$RESULT" | cut -d'|' -f2)
SIMULATOR_UDID=$(echo "$RESULT" | cut -d'|' -f3)
echo "Resolved: $SIMULATOR_NAME ($OS_VERSION) [$SIMULATOR_UDID]"
echo "--- xcrun simctl list devices available ---"
xcrun simctl list devices available
echo "-------------------------------------------"
SIMULATOR_UDID=$(xcrun simctl list devices "iOS 26.4" available | grep "iPhone" | grep -oE '[A-F0-9-]{36}' | head -1)
[ -n "$SIMULATOR_UDID" ] || { echo "No eligible iPhone simulator found for iOS 26.4"; exit 1; }
SIMULATOR_NAME=$(xcrun simctl list devices "iOS 26.4" available | grep "$SIMULATOR_UDID" | sed 's/ *(.*//' | xargs)
echo "Resolved: ${SIMULATOR_NAME} (iOS 26.4) [${SIMULATOR_UDID}]"
echo "DESTINATION=platform=iOS Simulator,id=${SIMULATOR_UDID}" >> "$GITHUB_ENV"
echo "SIMULATOR_UDID=${SIMULATOR_UDID}" >> "$GITHUB_ENV"

Expand Down
3 changes: 0 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,7 @@ jobs:
secrets: inherit

# Run Android E2E tests on BrowserStack real devices after Android unit tests pass.
# Blocked: BrowserStack cloud driver does not yet support Detox 20.43+.
# Remove the `if false` guard once the upstream fix is released.
browserstack-android:
if: false
needs: android-unit-tests
permissions:
contents: read
Expand Down
6 changes: 0 additions & 6 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,7 @@ jobs:
path: PingTestRunner/artifacts/

# ── Android E2E (emulator on ubuntu-latest) ───────────────────────────────
# NOTE: This job will currently fail. The BrowserStack Detox fork we depend on
# for cloud device testing is pinned to a Detox version that is incompatible
# with React Native 0.80.
# TODO: Re-enable and validate once BrowserStack releases an updated version
# of their Detox fork with RN 0.80 support.
e2e-android:
if: false
name: E2E — Android Emulator
runs-on: ubuntu-latest
timeout-minutes: 60
Expand Down
Binary file modified .yarn/install-state.gz
Binary file not shown.
7 changes: 4 additions & 3 deletions PingSampleApp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,14 @@ See `.env.example` for the full list of supported keys including external IdP

## Step 2: Install dependencies

This project is part of a Yarn workspace. Run `yarn install` from the **repo
root** (not from `PingSampleApp/`) — this installs both workspace packages and
the sample app's dependencies in one step:
This project is part of a Yarn workspace. Run the following from the **repo
root** (not from `PingSampleApp/`) — this installs all workspace dependencies
and builds the SDK packages the sample app links against:

```sh
# from repo root
yarn install
yarn packages:build
```

### iOS — install CocoaPods
Expand Down
6 changes: 6 additions & 0 deletions PingTestRunner/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ android {
proguardFile "${rootProject.projectDir}/../node_modules/detox/android/detox/proguard-rules-app.pro"
}
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
excludes += "/META-INF/versions/9/OSGI-INF/MANIFEST.MF"
}
}
}

dependencies {
Expand Down
4 changes: 4 additions & 0 deletions PingTestRunner/android/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,14 @@ include(":ping-identity_rn-browser")
project(":ping-identity_rn-browser").projectDir = file("../../packages/browser/android")
include(":ping-identity_rn-core")
project(":ping-identity_rn-core").projectDir = file("../../packages/core/android")
include(":ping-identity_rn-device-client")
project(":ping-identity_rn-device-client").projectDir = file("../../packages/device-client/android")
include(":ping-identity_rn-device-id")
project(":ping-identity_rn-device-id").projectDir = file("../../packages/device-id/android")
include(":ping-identity_rn-device-profile")
project(":ping-identity_rn-device-profile").projectDir = file("../../packages/device-profile/android")
include(":ping-identity_rn-external-idp")
project(":ping-identity_rn-external-idp").projectDir = file("../../packages/external-idp/android")
include(":ping-identity_rn-fido")
project(":ping-identity_rn-fido").projectDir = file("../../packages/fido/android")
include(":ping-identity_rn-journey")
Expand Down
4 changes: 2 additions & 2 deletions PingTestRunner/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"test:bs:android": "NODE_NO_WARNINGS=1 detox test --configuration android.bs --loglevel trace",
"test:e2e:ios": "sh -c 'export DETOX_SERVER_PORT=${DETOX_SERVER_PORT:-8099}; export NODE_NO_WARNINGS=1; yarn e2e:android:free-detox-port && detox test --configuration ios.sim \"$@\"' --",
"build:e2e:android": "detox build --configuration android.emu",
"build:bs:android": "cd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release",
"build:bs:android": "cd android && ./gradlew :app:assembleRelease :app:assembleAndroidTest -DtestBuildType=release",
"build:e2e:ios": "detox build --configuration ios.sim",
"build:bs:ios:archive": "sh ./scripts/build-bs-ios-archive.sh",
"build:bs:ios:ipa": "sh ./scripts/build-bs-ios-ipa.sh",
Expand Down Expand Up @@ -63,7 +63,7 @@
"@testing-library/react-native": "^13.3.3",
"@types/jest": "^29.5.14",
"@types/react": "^19.1.0",
"detox": "npm:@browserstack/detox@20.38.0-cloud.1",
"detox": "npm:@browserstack/detox@20.38.0-cloud.3",
"dotenv": "^16.4.7",
"eslint": "^8.19.0",
"jest": "^29.7.0",
Expand Down
10 changes: 5 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6442,9 +6442,9 @@ __metadata:
languageName: node
linkType: hard

"detox@npm:@browserstack/detox@20.38.0-cloud.1":
version: 20.38.0-cloud.1
resolution: "@browserstack/detox@npm:20.38.0-cloud.1"
"detox@npm:@browserstack/detox@20.38.0-cloud.3":
version: 20.38.0-cloud.3
resolution: "@browserstack/detox@npm:20.38.0-cloud.3"
dependencies:
"@wix-pilot/core": "npm:^3.2.2"
"@wix-pilot/detox": "npm:^1.0.11"
Expand Down Expand Up @@ -6490,7 +6490,7 @@ __metadata:
optional: true
bin:
detox: local-cli/cli.js
checksum: 10c0/5f59b76c1b9427132b5b3068cdab5950893b1d98d44f7418d0f2563853d618b62b8db53be762bedf73793f15eb5b51fce75cab62e5aa34bb65773494c18683d3
checksum: 10c0/e49a2c37146b3e6cbf2221fea32b29ace3dda75d6bcaae6a531b3827a1b8a4f99556dd7b858881d4eace49df0db3249c36aa8d786b076cbb05e323eb25e2f39f
languageName: node
linkType: hard

Expand Down Expand Up @@ -12554,7 +12554,7 @@ __metadata:
"@testing-library/react-native": "npm:^13.3.3"
"@types/jest": "npm:^29.5.14"
"@types/react": "npm:^19.1.0"
detox: "npm:@browserstack/detox@20.38.0-cloud.1"
detox: "npm:@browserstack/detox@20.38.0-cloud.3"
dotenv: "npm:^16.4.7"
eslint: "npm:^8.19.0"
jest: "npm:^29.7.0"
Expand Down
Loading