diff --git a/.github/workflows/browserstack-e2e-android.yml b/.github/workflows/browserstack-e2e-android.yml index 8fd117948..8e2723daa 100644 --- a/.github/workflows/browserstack-e2e-android.yml +++ b/.github/workflows/browserstack-e2e-android.yml @@ -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: @@ -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 diff --git a/.github/workflows/browserstack-parse-results-ios.yml b/.github/workflows/browserstack-parse-results-ios.yml index cb72e235d..42abfa6cc 100644 --- a/.github/workflows/browserstack-parse-results-ios.yml +++ b/.github/workflows/browserstack-parse-results-ios.yml @@ -29,7 +29,7 @@ jobs: steps: - name: Wait for BrowserStack XCUITest build to finish - timeout-minutes: 40 + timeout-minutes: 60 shell: bash run: | set -euo pipefail @@ -37,7 +37,7 @@ jobs: 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}")" diff --git a/.github/workflows/browserstack-prep-ios-artifacts.yml b/.github/workflows/browserstack-prep-ios-artifacts.yml index 2e8f9ab2b..03b68ae4d 100644 --- a/.github/workflows/browserstack-prep-ios-artifacts.yml +++ b/.github/workflows/browserstack-prep-ios-artifacts.yml @@ -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 @@ -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 diff --git a/.github/workflows/build-and-test-ios.yml b/.github/workflows/build-and-test-ios.yml index 1ce0b8090..f619dd488 100644 --- a/.github/workflows/build-and-test-ios.yml +++ b/.github/workflows/build-and-test-ios.yml @@ -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 # 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. @@ -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). # 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" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 27c9ccc94..775bfbc88 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 3a603a053..dbd543687 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -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 diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz index 9bf917b23..110677dce 100644 Binary files a/.yarn/install-state.gz and b/.yarn/install-state.gz differ diff --git a/PingSampleApp/README.md b/PingSampleApp/README.md index 06eee08d4..df665db28 100644 --- a/PingSampleApp/README.md +++ b/PingSampleApp/README.md @@ -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 diff --git a/PingTestRunner/android/app/build.gradle b/PingTestRunner/android/app/build.gradle index a1940bb37..1bd593e64 100644 --- a/PingTestRunner/android/app/build.gradle +++ b/PingTestRunner/android/app/build.gradle @@ -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 { diff --git a/PingTestRunner/android/settings.gradle b/PingTestRunner/android/settings.gradle index 7c07059f0..666523fb6 100644 --- a/PingTestRunner/android/settings.gradle +++ b/PingTestRunner/android/settings.gradle @@ -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") diff --git a/PingTestRunner/package.json b/PingTestRunner/package.json index 764cfa70c..add5ffe5c 100644 --- a/PingTestRunner/package.json +++ b/PingTestRunner/package.json @@ -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", @@ -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", diff --git a/yarn.lock b/yarn.lock index ab7891456..5970dafd4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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" @@ -6490,7 +6490,7 @@ __metadata: optional: true bin: detox: local-cli/cli.js - checksum: 10c0/5f59b76c1b9427132b5b3068cdab5950893b1d98d44f7418d0f2563853d618b62b8db53be762bedf73793f15eb5b51fce75cab62e5aa34bb65773494c18683d3 + checksum: 10c0/e49a2c37146b3e6cbf2221fea32b29ace3dda75d6bcaae6a531b3827a1b8a4f99556dd7b858881d4eace49df0db3249c36aa8d786b076cbb05e323eb25e2f39f languageName: node linkType: hard @@ -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"