From cc9543fa32e75fd3c2076716ddcc271a3f99e9ad Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 18 Feb 2025 14:43:48 +0100 Subject: [PATCH 01/16] chore(samples): Add package scripts for native builds, dsn and testing --- .github/workflows/sample-application.yml | 67 +++------- .../react-native-macos/scripts/pod-install.sh | 18 +++ samples/react-native/.detoxrc.js | 124 ++++-------------- samples/react-native/.gitignore | 2 + samples/react-native/package.json | 24 +++- .../scripts/build-android-debug-legacy.sh | 11 ++ .../scripts/build-android-debug.sh | 11 ++ .../scripts/build-android-release-legacy.sh | 11 ++ .../scripts/build-android-release.sh | 11 ++ samples/react-native/scripts/build-android.sh | 29 ++++ .../react-native/scripts/build-ios-debug.sh | 10 ++ .../react-native/scripts/build-ios-release.sh | 12 ++ samples/react-native/scripts/build-ios.sh | 30 +++++ .../pod-install-debug-dynamic-legacy.sh | 12 ++ .../scripts/pod-install-debug-dynamic.sh | 12 ++ .../pod-install-debug-static-legacy.sh | 12 ++ .../scripts/pod-install-debug-static.sh | 12 ++ .../pod-install-release-dynamic-legacy.sh | 12 ++ .../scripts/pod-install-release-dynamic.sh | 12 ++ .../pod-install-release-static-legacy.sh | 12 ++ .../scripts/pod-install-release-static.sh | 12 ++ samples/react-native/scripts/pod-install.sh | 18 +++ samples/react-native/scripts/set-aos-dsn.mjs | 5 - samples/react-native/scripts/set-dsn-aos.mjs | 5 + samples/react-native/scripts/set-dsn-ios.mjs | 5 + .../scripts/{dsn.mjs => set-dsn.mjs} | 0 samples/react-native/scripts/set-ios-dsn.mjs | 5 - samples/react-native/scripts/test-android.sh | 43 ++++++ samples/react-native/scripts/test-ios.sh | 24 ++++ samples/react-native/sentry.options.json | 8 +- 30 files changed, 400 insertions(+), 169 deletions(-) create mode 100755 samples/react-native-macos/scripts/pod-install.sh create mode 100755 samples/react-native/scripts/build-android-debug-legacy.sh create mode 100755 samples/react-native/scripts/build-android-debug.sh create mode 100755 samples/react-native/scripts/build-android-release-legacy.sh create mode 100755 samples/react-native/scripts/build-android-release.sh create mode 100755 samples/react-native/scripts/build-android.sh create mode 100755 samples/react-native/scripts/build-ios-debug.sh create mode 100755 samples/react-native/scripts/build-ios-release.sh create mode 100755 samples/react-native/scripts/build-ios.sh create mode 100755 samples/react-native/scripts/pod-install-debug-dynamic-legacy.sh create mode 100755 samples/react-native/scripts/pod-install-debug-dynamic.sh create mode 100755 samples/react-native/scripts/pod-install-debug-static-legacy.sh create mode 100755 samples/react-native/scripts/pod-install-debug-static.sh create mode 100755 samples/react-native/scripts/pod-install-release-dynamic-legacy.sh create mode 100755 samples/react-native/scripts/pod-install-release-dynamic.sh create mode 100755 samples/react-native/scripts/pod-install-release-static-legacy.sh create mode 100755 samples/react-native/scripts/pod-install-release-static.sh create mode 100755 samples/react-native/scripts/pod-install.sh delete mode 100755 samples/react-native/scripts/set-aos-dsn.mjs create mode 100755 samples/react-native/scripts/set-dsn-aos.mjs create mode 100755 samples/react-native/scripts/set-dsn-ios.mjs rename samples/react-native/scripts/{dsn.mjs => set-dsn.mjs} (100%) delete mode 100755 samples/react-native/scripts/set-ios-dsn.mjs create mode 100755 samples/react-native/scripts/test-android.sh create mode 100755 samples/react-native/scripts/test-ios.sh diff --git a/.github/workflows/sample-application.yml b/.github/workflows/sample-application.yml index 53202a0f31..0ee636f8a6 100644 --- a/.github/workflows/sample-application.yml +++ b/.github/workflows/sample-application.yml @@ -99,66 +99,39 @@ jobs: if: ${{ matrix.platform == 'ios' || matrix.platform == 'macos' }} working-directory: samples run: | - [[ "${{ matrix.platform }}" == "ios" ]] && cd react-native/ios - [[ "${{ matrix.platform }}" == "macos" ]] && cd react-native-macos/macos + [[ "${{ matrix.platform }}" == "ios" ]] && cd react-native + [[ "${{ matrix.platform }}" == "macos" ]] && cd react-native-macos - [[ "${{ matrix.build-type }}" == "production" ]] && ENABLE_PROD=1 || ENABLE_PROD=0 - [[ "${{ matrix.rn-architecture }}" == "new" ]] && ENABLE_NEW_ARCH=1 || ENABLE_NEW_ARCH=0 + [[ "${{ matrix.build-type }}" == "production" ]] && export ENABLE_PROD=1 || export ENABLE_PROD=0 + [[ "${{ matrix.rn-architecture }}" == "new" ]] && export ENABLE_NEW_ARCH=1 || export ENABLE_NEW_ARCH=0 [[ "${{ matrix.ios-use-frameworks }}" == "dynamic-frameworks" ]] && export USE_FRAMEWORKS=dynamic - echo "ENABLE_PROD=$ENABLE_PROD" - echo "ENABLE_NEW_ARCH=$ENABLE_NEW_ARCH" - PRODUCTION=$ENABLE_PROD RCT_NEW_ARCH_ENABLED=$ENABLE_NEW_ARCH bundle exec pod install - cat Podfile.lock | grep $RN_SENTRY_POD_NAME + + ./scripts/pod-install.sh - name: Build Android App if: ${{ matrix.platform == 'android' }} - working-directory: ${{ env.REACT_NATIVE_SAMPLE_PATH }}/android + working-directory: ${{ env.REACT_NATIVE_SAMPLE_PATH }} run: | - ../scripts/set-aos-dsn.mjs - - if [[ ${{ matrix.rn-architecture }} == 'new' ]]; then - perl -i -pe's/newArchEnabled=false/newArchEnabled=true/g' gradle.properties - echo 'New Architecture enabled' - elif [[ ${{ matrix.rn-architecture }} == 'legacy' ]]; then - perl -i -pe's/newArchEnabled=true/newArchEnabled=false/g' gradle.properties - echo 'Legacy Architecture enabled' - else - echo 'No changes for architecture: ${{ matrix.rn-architecture }}' - fi - [[ "${{ matrix.build-type }}" == "production" ]] && CONFIG='Release' || CONFIG='Debug' - echo "Building $CONFIG" - [[ "${{ matrix.build-type }}" == "production" ]] && TEST_TYPE='release' || TEST_TYPE='debug' - echo "Building $TEST_TYPE" + export RN_ARCHITECTURE="${{ matrix.rn-architecture }}" + [[ "${{ matrix.build-type }}" == "production" ]] && export CONFIG='release' || export CONFIG='debug' - ./gradlew ":app:assemble$CONFIG" app:assembleAndroidTest -DtestBuildType=$TEST_TYPE -PreactNativeArchitectures=x86 + ../scripts/set-dsn-aos.mjs + ../scripts/build-android.sh -PreactNativeArchitectures=x86 - name: Build iOS App if: ${{ matrix.platform == 'ios' }} working-directory: ${{ env.REACT_NATIVE_SAMPLE_PATH }}/ios run: | - ../scripts/set-ios-dsn.mjs + [[ "${{ matrix.build-type }}" == "production" ]] && export CONFIG='Release' || export CONFIG='Debug' - [[ "${{ matrix.build-type }}" == "production" ]] && CONFIG='Release' || CONFIG='Debug' - echo "Building $CONFIG" - mkdir -p "DerivedData" - derivedData="$(cd "DerivedData" ; pwd -P)" - set -o pipefail && xcodebuild \ - -workspace sentryreactnativesample.xcworkspace \ - -configuration "$CONFIG" \ - -scheme sentryreactnativesample \ - -sdk 'iphonesimulator' \ - -destination 'generic/platform=iOS Simulator' \ - ONLY_ACTIVE_ARCH=yes \ - -derivedDataPath "$derivedData" \ - build \ - | tee xcodebuild.log \ - | xcbeautify --quieter --is-ci --disable-colored-output + ../scripts/set-dsn-ios.mjs + ../scripts/build-ios.sh - name: Build macOS App if: ${{ matrix.platform == 'macos' }} working-directory: samples/react-native-macos/macos run: | - [[ "${{ matrix.build-type }}" == "production" ]] && CONFIG='Release' || CONFIG='Debug' + [[ "${{ matrix.build-type }}" == "production" ]] && export CONFIG='Release' || export CONFIG='Debug' echo "Building $CONFIG" mkdir -p "DerivedData" derivedData="$(cd "DerivedData" ; pwd -P)" @@ -176,16 +149,13 @@ jobs: - name: Archive iOS App if: ${{ matrix.platform == 'ios' && matrix.rn-architecture == 'new' && matrix.build-type == 'production' && matrix.ios-use-frameworks == 'no-frameworks' }} run: | - cd ${{ env.REACT_NATIVE_SAMPLE_PATH }}/ios/DerivedData/Build/Products/Release-iphonesimulator zip -r \ ${{ github.workspace }}/${{ env.IOS_APP_ARCHIVE_PATH }} \ - sentryreactnativesample.app + ${{ env.REACT_NATIVE_SAMPLE_PATH }}/sentryreactnativesample.app - name: Archive Android App if: ${{ matrix.platform == 'android' && matrix.rn-architecture == 'new' && matrix.build-type == 'production' }} run: | - mv ${{ env.REACT_NATIVE_SAMPLE_PATH }}/android/app/build/outputs/apk/release/app-release.apk app.apk - mv ${{ env.REACT_NATIVE_SAMPLE_PATH }}/android/app/build/outputs/apk/androidTest/release/app-release-androidTest.apk app-androidTest.apk zip -j \ ${{ env.ANDROID_APP_ARCHIVE_PATH }} \ app.apk \ @@ -306,13 +276,14 @@ jobs: - name: Run Detox iOS Tests if: ${{ matrix.platform == 'ios' }} working-directory: ${{ env.REACT_NATIVE_SAMPLE_PATH }} - run: detox test --configuration ci.sim + run: yarn test-ios - name: Run tests on Android if: ${{ matrix.platform == 'android' }} env: # used by Detox ci.android configuration ANDROID_AVD_NAME: 'test' # test is default reactivecircus/android-emulator-runner name + ANDROID_TYPE: 'android.emulator' uses: reactivecircus/android-emulator-runner@62dbb605bba737720e10b196cb4220d374026a6d # pin@v2.33.0 with: api-level: ${{ env.ANDROID_API_LEVEL }} @@ -331,4 +302,4 @@ jobs: -camera-front none -timezone US/Pacific working-directory: ${{ env.REACT_NATIVE_SAMPLE_PATH }} - script: detox test --configuration ci.android + script: yarn test-android diff --git a/samples/react-native-macos/scripts/pod-install.sh b/samples/react-native-macos/scripts/pod-install.sh new file mode 100755 index 0000000000..5d1ada6789 --- /dev/null +++ b/samples/react-native-macos/scripts/pod-install.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# Exit on error +set -e + +thisFilePath=$(dirname "$0") + +echo "USE_FRAMEWORKS=$USE_FRAMEWORKS" +echo "ENABLE_PROD=$ENABLE_PROD" +echo "ENABLE_NEW_ARCH=$ENABLE_NEW_ARCH" + +cd "${thisFilePath}/.." +bundle install + +cd ios +PRODUCTION=$ENABLE_PROD RCT_NEW_ARCH_ENABLED=$ENABLE_NEW_ARCH bundle exec pod update + +cat Podfile.lock | grep $RN_SENTRY_POD_NAME diff --git a/samples/react-native/.detoxrc.js b/samples/react-native/.detoxrc.js index e6ba0d66cd..11c535ba65 100644 --- a/samples/react-native/.detoxrc.js +++ b/samples/react-native/.detoxrc.js @@ -1,56 +1,9 @@ const process = require('process'); -const testRunnerIos = { - args: { - $0: 'jest', - config: 'e2e/jest.config.ios.js', - }, - jest: { - setupTimeout: 120000, - }, -}; - -const testRunnerAos = { - args: { - $0: 'jest', - config: 'e2e/jest.config.android.js', - }, - jest: { - setupTimeout: 120000, - }, -}; - /** @type {Detox.DetoxConfig} */ module.exports = { testRunner: {}, apps: { - 'ios.debug': { - type: 'ios.app', - binaryPath: - 'ios/build/Build/Products/Debug-iphonesimulator/sentryreactnativesample.app', - build: - 'xcodebuild -workspace ios/sentryreactnativesample.xcworkspace -scheme sentryreactnativesample -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build', - }, - 'ios.release': { - type: 'ios.app', - binaryPath: - 'ios/build/Build/Products/Release-iphonesimulator/sentryreactnativesample.app', - build: - 'xcodebuild -workspace ios/sentryreactnativesample.xcworkspace -scheme sentryreactnativesample -configuration Release -sdk iphonesimulator -derivedDataPath ios/build', - }, - 'android.debug': { - type: 'android.apk', - binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk', - build: - 'cd android && ./gradlew app:assembleDebug app:assembleAndroidTest -DtestBuildType=debug', - reversePorts: [8081], - }, - 'android.release': { - type: 'android.apk', - binaryPath: 'android/app/build/outputs/apk/release/app-release.apk', - build: - 'cd android && ./gradlew app:assembleRelease app:assembleAndroidTest -DtestBuildType=release', - }, 'ci.android': { type: 'android.apk', binaryPath: 'app.apk', @@ -62,78 +15,47 @@ module.exports = { }, }, devices: { - simulator: { - type: 'ios.simulator', - device: { - type: 'iPhone 16', - }, - }, - attached: { - type: 'android.attached', - device: { - adbName: '.*', - }, - }, - emulator: { - type: 'android.emulator', - device: { - avdName: 'Pixel_9_API_35', - }, - }, 'ci.emulator': { - type: 'android.emulator', + type: process.env.ANDROID_TYPE?.trim(), device: { - avdName: process.env.ANDROID_AVD_NAME, + avdName: process.env.ANDROID_AVD_NAME?.trim(), + adbName: process.env.ANDROID_ADB_NAME?.trim(), }, }, 'ci.simulator': { type: 'ios.simulator', device: { - type: process.env.IOS_DEVICE, - os: process.env.IOS_VERSION, + type: process.env.IOS_DEVICE?.trim(), + os: process.env.IOS_VERSION?.trim(), }, }, }, configurations: { - 'ios.sim.debug': { - device: 'simulator', - app: 'ios.debug', - testRunner: testRunnerIos, - }, - 'ios.sim.release': { - device: 'simulator', - app: 'ios.release', - testRunner: testRunnerIos, - }, - 'android.att.debug': { - device: 'attached', - app: 'android.debug', - testRunner: testRunnerAos, - }, - 'android.att.release': { - device: 'attached', - app: 'android.release', - testRunner: testRunnerAos, - }, - 'android.emu.debug': { - device: 'emulator', - app: 'android.debug', - testRunner: testRunnerAos, - }, - 'android.emu.release': { - device: 'emulator', - app: 'android.release', - testRunner: testRunnerAos, - }, 'ci.android': { device: 'ci.emulator', app: 'ci.android', - testRunner: testRunnerAos, + testRunner: { + args: { + $0: 'jest', + config: 'e2e/jest.config.android.js', + }, + jest: { + setupTimeout: 120000, + }, + }, }, 'ci.sim': { device: 'ci.simulator', app: 'ci.ios', - testRunner: testRunnerIos, + testRunner: { + args: { + $0: 'jest', + config: 'e2e/jest.config.ios.js', + }, + jest: { + setupTimeout: 120000, + }, + }, }, }, }; diff --git a/samples/react-native/.gitignore b/samples/react-native/.gitignore index 40824c4232..9eb5c6ab12 100644 --- a/samples/react-native/.gitignore +++ b/samples/react-native/.gitignore @@ -63,3 +63,5 @@ yarn-error.log .metro-health-check* *.xcarchive +*.apk +**/*.app diff --git a/samples/react-native/package.json b/samples/react-native/package.json index c2697d9959..dfb6457bed 100644 --- a/samples/react-native/package.json +++ b/samples/react-native/package.json @@ -4,16 +4,28 @@ "private": true, "scripts": { "postinstall": "patch-package", - "android": "react-native run-android", - "ios": "react-native run-ios", "start": "react-native start", + "build-android-release": "scripts/build-android-release.sh", + "build-android-release-legacy": "scripts/build-android-release-legacy.sh", + "build-android-debug": "scripts/build-android-debug.sh", + "build-android-debug-legacy": "scripts/build-android-debug-legacy.sh", + "build-ios-release": "scripts/build-ios-release.sh", + "build-ios-debug": "scripts/build-ios-debug.sh", "test": "jest", + "set-test-dsn-android": "scripts/set-dsn-aos.mjs", + "set-test-dsn-ios": "scripts/set-dsn-ios.mjs", + "test-android": "scripts/test-android.sh", + "test-ios": "scripts/test-ios.sh", "lint": "npx eslint . --ext .js,.jsx,.ts,.tsx", "fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix", - "pod-install": "cd ios; RCT_NEW_ARCH_ENABLED=1 bundle exec pod install; cd ..", - "pod-install-production": "cd ios; PRODUCTION=1 RCT_NEW_ARCH_ENABLED=1 bundle exec pod install; cd ..", - "pod-install-legacy": "cd ios; bundle exec pod install; cd ..", - "pod-install-legacy-production": "cd ios; PRODUCTION=1 bundle exec pod install; cd ..", + "pod-install-debug-static": "scripts/pod-install-debug-static.sh", + "pod-install-debug-static-legacy": "scripts/pod-install-debug-static-legacy.sh", + "pod-install-debug-dynamic": "scripts/pod-install-debug-dynamic.sh", + "pod-install-debug-dynamic-legacy": "scripts/pod-install-debug-dynamic-legacy.sh", + "pod-install-release-static": "scripts/pod-install-release-static.sh", + "pod-install-release-static-legacy": "scripts/pod-install-release-static-legacy.sh", + "pod-install-release-dynamic": "scripts/pod-install-release-dynamic.sh", + "pod-install-release-dynamic-legacy": "scripts/pod-install-release-dynamic-legacy.sh", "clean-ios": "cd ios; rm -rf Podfile.lock Pods build; cd ..", "clean-watchman": "watchman watch-del-all", "set-build-number": "npx react-native-version --skip-tag --never-amend --set-build", diff --git a/samples/react-native/scripts/build-android-debug-legacy.sh b/samples/react-native/scripts/build-android-debug-legacy.sh new file mode 100755 index 0000000000..ac0952892d --- /dev/null +++ b/samples/react-native/scripts/build-android-debug-legacy.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# Exit on error +set -e + +thisFilePath=$(dirname "$0") + +export RN_ARCHITECTURE="legacy" +export CONFIG="debug" + +"${thisFilePath}/build-android.sh" diff --git a/samples/react-native/scripts/build-android-debug.sh b/samples/react-native/scripts/build-android-debug.sh new file mode 100755 index 0000000000..89f9ae626c --- /dev/null +++ b/samples/react-native/scripts/build-android-debug.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# Exit on error +set -e + +thisFilePath=$(dirname "$0") + +export RN_ARCHITECTURE="new" +export CONFIG="debug" + +"${thisFilePath}/build-android.sh" diff --git a/samples/react-native/scripts/build-android-release-legacy.sh b/samples/react-native/scripts/build-android-release-legacy.sh new file mode 100755 index 0000000000..cf853c15cc --- /dev/null +++ b/samples/react-native/scripts/build-android-release-legacy.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# Exit on error +set -e + +thisFilePath=$(dirname "$0") + +export RN_ARCHITECTURE="legacy" +export CONFIG="release" + +"${thisFilePath}/build-android.sh" diff --git a/samples/react-native/scripts/build-android-release.sh b/samples/react-native/scripts/build-android-release.sh new file mode 100755 index 0000000000..3403a3c1bb --- /dev/null +++ b/samples/react-native/scripts/build-android-release.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# Exit on error +set -e + +thisFilePath=$(dirname "$0") + +export RN_ARCHITECTURE="new" +export CONFIG="release" + +"${thisFilePath}/build-android.sh" diff --git a/samples/react-native/scripts/build-android.sh b/samples/react-native/scripts/build-android.sh new file mode 100755 index 0000000000..866b5cc130 --- /dev/null +++ b/samples/react-native/scripts/build-android.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# Exit on error and print commands +set -xe + +thisFilePath=$(dirname "$0") + +cd "${thisFilePath}/../android" + +rm -rf ../app.apk ../app-androidTest.apk + +if [[ "${RN_ARCHITECTURE}" == 'new' ]]; then + perl -i -pe's/newArchEnabled=false/newArchEnabled=true/g' gradle.properties + echo 'New Architecture enabled' +elif [[ "${RN_ARCHITECTURE}" == 'legacy' ]]; then + perl -i -pe's/newArchEnabled=true/newArchEnabled=false/g' gradle.properties + echo 'Legacy Architecture enabled' +else + echo "No changes for architecture: ${RN_ARCHITECTURE}" +fi + +echo "Building $CONFIG" + +assembleConfig=$(python -c "print(\"${CONFIG}\".capitalize())") + +./gradlew ":app:assemble${assembleConfig}" app:assembleAndroidTest -DtestBuildType=$CONFIG "$@" + +cp "app/build/outputs/apk/${CONFIG}/app-${CONFIG}.apk" ../app.apk +cp "app/build/outputs/apk/androidTest/${CONFIG}/app-${CONFIG}-androidTest.apk" ../app-androidTest.apk diff --git a/samples/react-native/scripts/build-ios-debug.sh b/samples/react-native/scripts/build-ios-debug.sh new file mode 100755 index 0000000000..088ed50f28 --- /dev/null +++ b/samples/react-native/scripts/build-ios-debug.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# Exit on error and print commands +set -xe + +thisFilePath=$(dirname "$0") + +export CONFIG='Debug' + +"${thisFilePath}/build-ios.sh" diff --git a/samples/react-native/scripts/build-ios-release.sh b/samples/react-native/scripts/build-ios-release.sh new file mode 100755 index 0000000000..b7215ce142 --- /dev/null +++ b/samples/react-native/scripts/build-ios-release.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Exit on error and print commands +set -xe + +thisFilePath=$(dirname "$0") + +cd "${thisFilePath}/../ios" + +export CONFIG='Release' + +"${thisFilePath}/build-ios.sh" diff --git a/samples/react-native/scripts/build-ios.sh b/samples/react-native/scripts/build-ios.sh new file mode 100755 index 0000000000..7897aba332 --- /dev/null +++ b/samples/react-native/scripts/build-ios.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# Exit on error and print commands +set -xe + +thisFilePath=$(dirname "$0") + +cd "${thisFilePath}/../ios" + +rm -rf ../sentryreactnativesample.app + +echo "Building $CONFIG" + +rm -rf xcodebuild.log + +mkdir -p "DerivedData" +derivedData="$(cd "DerivedData" ; pwd -P)" +set -o pipefail && xcodebuild \ + -workspace sentryreactnativesample.xcworkspace \ + -configuration "$CONFIG" \ + -scheme sentryreactnativesample \ + -sdk 'iphonesimulator' \ + -destination 'generic/platform=iOS Simulator' \ + ONLY_ACTIVE_ARCH=yes \ + -derivedDataPath "$derivedData" \ + build \ + | tee xcodebuild.log \ + | if [ "$CI" = "true" ]; then xcbeautify --quieter --is-ci --disable-colored-output; else xcbeautify; fi + +cp -r "DerivedData/Build/Products/${CONFIG}-iphonesimulator/sentryreactnativesample.app" ../sentryreactnativesample.app diff --git a/samples/react-native/scripts/pod-install-debug-dynamic-legacy.sh b/samples/react-native/scripts/pod-install-debug-dynamic-legacy.sh new file mode 100755 index 0000000000..cea9690bb0 --- /dev/null +++ b/samples/react-native/scripts/pod-install-debug-dynamic-legacy.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Exit on error +set -e + +export ENABLE_PROD=0 +export ENABLE_NEW_ARCH=0 +export USE_FRAMEWORKS=dynamic + +thisFilePath=$(dirname "$0") + +"${thisFilePath}/pod-install.sh" diff --git a/samples/react-native/scripts/pod-install-debug-dynamic.sh b/samples/react-native/scripts/pod-install-debug-dynamic.sh new file mode 100755 index 0000000000..ed3acafb8b --- /dev/null +++ b/samples/react-native/scripts/pod-install-debug-dynamic.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Exit on error +set -e + +export ENABLE_PROD=0 +export ENABLE_NEW_ARCH=1 +export USE_FRAMEWORKS=dynamic + +thisFilePath=$(dirname "$0") + +"${thisFilePath}/pod-install.sh" diff --git a/samples/react-native/scripts/pod-install-debug-static-legacy.sh b/samples/react-native/scripts/pod-install-debug-static-legacy.sh new file mode 100755 index 0000000000..52b80ba450 --- /dev/null +++ b/samples/react-native/scripts/pod-install-debug-static-legacy.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Exit on error +set -e + +export ENABLE_PROD=0 +export ENABLE_NEW_ARCH=0 +export USE_FRAMEWORKS=static + +thisFilePath=$(dirname "$0") + +"${thisFilePath}/pod-install.sh" diff --git a/samples/react-native/scripts/pod-install-debug-static.sh b/samples/react-native/scripts/pod-install-debug-static.sh new file mode 100755 index 0000000000..86049e4425 --- /dev/null +++ b/samples/react-native/scripts/pod-install-debug-static.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Exit on error +set -e + +export ENABLE_PROD=0 +export ENABLE_NEW_ARCH=1 +export USE_FRAMEWORKS=static + +thisFilePath=$(dirname "$0") + +"${thisFilePath}/pod-install.sh" diff --git a/samples/react-native/scripts/pod-install-release-dynamic-legacy.sh b/samples/react-native/scripts/pod-install-release-dynamic-legacy.sh new file mode 100755 index 0000000000..d6f7449abc --- /dev/null +++ b/samples/react-native/scripts/pod-install-release-dynamic-legacy.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Exit on error +set -e + +export ENABLE_PROD=1 +export ENABLE_NEW_ARCH=0 +export USE_FRAMEWORKS=dynamic + +thisFilePath=$(dirname "$0") + +"${thisFilePath}/pod-install.sh" diff --git a/samples/react-native/scripts/pod-install-release-dynamic.sh b/samples/react-native/scripts/pod-install-release-dynamic.sh new file mode 100755 index 0000000000..9207b45dfe --- /dev/null +++ b/samples/react-native/scripts/pod-install-release-dynamic.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Exit on error +set -e + +export ENABLE_PROD=1 +export ENABLE_NEW_ARCH=1 +export USE_FRAMEWORKS=dynamic + +thisFilePath=$(dirname "$0") + +"${thisFilePath}/pod-install.sh" diff --git a/samples/react-native/scripts/pod-install-release-static-legacy.sh b/samples/react-native/scripts/pod-install-release-static-legacy.sh new file mode 100755 index 0000000000..9742caa73a --- /dev/null +++ b/samples/react-native/scripts/pod-install-release-static-legacy.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Exit on error +set -e + +export ENABLE_PROD=1 +export ENABLE_NEW_ARCH=0 +export USE_FRAMEWORKS=static + +thisFilePath=$(dirname "$0") + +"${thisFilePath}/pod-install.sh" diff --git a/samples/react-native/scripts/pod-install-release-static.sh b/samples/react-native/scripts/pod-install-release-static.sh new file mode 100755 index 0000000000..8de5b13a61 --- /dev/null +++ b/samples/react-native/scripts/pod-install-release-static.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Exit on error +set -e + +export ENABLE_PROD=1 +export ENABLE_NEW_ARCH=1 +export USE_FRAMEWORKS=static + +thisFilePath=$(dirname "$0") + +"${thisFilePath}/pod-install.sh" diff --git a/samples/react-native/scripts/pod-install.sh b/samples/react-native/scripts/pod-install.sh new file mode 100755 index 0000000000..5d1ada6789 --- /dev/null +++ b/samples/react-native/scripts/pod-install.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# Exit on error +set -e + +thisFilePath=$(dirname "$0") + +echo "USE_FRAMEWORKS=$USE_FRAMEWORKS" +echo "ENABLE_PROD=$ENABLE_PROD" +echo "ENABLE_NEW_ARCH=$ENABLE_NEW_ARCH" + +cd "${thisFilePath}/.." +bundle install + +cd ios +PRODUCTION=$ENABLE_PROD RCT_NEW_ARCH_ENABLED=$ENABLE_NEW_ARCH bundle exec pod update + +cat Podfile.lock | grep $RN_SENTRY_POD_NAME diff --git a/samples/react-native/scripts/set-aos-dsn.mjs b/samples/react-native/scripts/set-aos-dsn.mjs deleted file mode 100755 index 521cb44a11..0000000000 --- a/samples/react-native/scripts/set-aos-dsn.mjs +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env node - -import { setAndroidDsn } from './dsn.mjs'; - -setAndroidDsn(); diff --git a/samples/react-native/scripts/set-dsn-aos.mjs b/samples/react-native/scripts/set-dsn-aos.mjs new file mode 100755 index 0000000000..01bed2c984 --- /dev/null +++ b/samples/react-native/scripts/set-dsn-aos.mjs @@ -0,0 +1,5 @@ +#!/usr/bin/env node + +import { setAndroidDsn } from './set-dsn.mjs'; + +setAndroidDsn(); diff --git a/samples/react-native/scripts/set-dsn-ios.mjs b/samples/react-native/scripts/set-dsn-ios.mjs new file mode 100755 index 0000000000..7757c1e7b7 --- /dev/null +++ b/samples/react-native/scripts/set-dsn-ios.mjs @@ -0,0 +1,5 @@ +#!/usr/bin/env node + +import { setIosDsn } from './set-dsn.mjs'; + +setIosDsn(); diff --git a/samples/react-native/scripts/dsn.mjs b/samples/react-native/scripts/set-dsn.mjs similarity index 100% rename from samples/react-native/scripts/dsn.mjs rename to samples/react-native/scripts/set-dsn.mjs diff --git a/samples/react-native/scripts/set-ios-dsn.mjs b/samples/react-native/scripts/set-ios-dsn.mjs deleted file mode 100755 index ea1ca7e61a..0000000000 --- a/samples/react-native/scripts/set-ios-dsn.mjs +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env node - -import { setIosDsn } from './dsn.mjs'; - -setIosDsn(); diff --git a/samples/react-native/scripts/test-android.sh b/samples/react-native/scripts/test-android.sh new file mode 100755 index 0000000000..bbef32a81f --- /dev/null +++ b/samples/react-native/scripts/test-android.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# Exit on error and print commands +set -xe + +thisFilePath=$(dirname "$0") + +cd "${thisFilePath}/.." + +if [ -z "$ANDROID_AVD_NAME" ]; then + # Get the name of the first booted or connected Android device + DEVICE_NAME=$(adb devices | grep -w "device" | head -n 1 | cut -f 1) + + if [ -z "$DEVICE_NAME" ]; then + echo "No Android device or emulator found" + exit 1 + fi + + if [[ "$DEVICE_NAME" == *"emulator"* ]]; then + # Get the name of the first booted or connected Android emulator/device + EMULATOR_NAME=$(adb -s "${DEVICE_NAME}" emu avd name | head -n 1 | cut -f 1 ) + + if [ -z "$EMULATOR_NAME" ]; then + echo "No Android emulator found" + exit 1 + fi + + export ANDROID_TYPE="android.emulator" + export ANDROID_AVD_NAME="$EMULATOR_NAME" + echo "Using Android emulator: $EMULATOR_NAME" + else + export ANDROID_TYPE="android.attached" + export ANDROID_ADB_NAME="$DEVICE_NAME" + + adb reverse tcp:8081 tcp:8081 + adb reverse tcp:8961 tcp:8961 + + echo "Using Android device: $DEVICE_NAME" + fi +fi + +# Run the tests +detox test --configuration ci.android diff --git a/samples/react-native/scripts/test-ios.sh b/samples/react-native/scripts/test-ios.sh new file mode 100755 index 0000000000..9ca3e95c4c --- /dev/null +++ b/samples/react-native/scripts/test-ios.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# Exit on error and print commands +set -xe + +thisFilePath=$(dirname "$0") + +cd "${thisFilePath}/.." + +if [ -z $IOS_DEVICE ]; then + # Get the first booted simulator device type and version + BOOTED_DEVICE=$(xcrun simctl list devices | grep "Booted" | head -n 1) + + if [ -z "$BOOTED_DEVICE" ]; then + echo "No booted iOS simulator found" + exit 1 + fi + + # Extract device type from booted device + export IOS_DEVICE=$(echo "$BOOTED_DEVICE" | cut -d "(" -f1 | xargs) + echo "Using booted iOS simulator: $IOS_DEVICE" +fi + +detox test --configuration ci.sim diff --git a/samples/react-native/sentry.options.json b/samples/react-native/sentry.options.json index 53ae525bc0..58425c35d5 100644 --- a/samples/react-native/sentry.options.json +++ b/samples/react-native/sentry.options.json @@ -6,13 +6,13 @@ "enableAutoSessionTracking": true, "sessionTrackingIntervalMillis": 30000, "enableTracing": true, - "tracesSampleRate": 1.0, + "tracesSampleRate": 1, "attachStacktrace": true, "attachScreenshot": true, "attachViewHierarchy": true, "enableCaptureFailedRequests": true, - "profilesSampleRate": 1.0, - "replaysSessionSampleRate": 1.0, - "replaysOnErrorSampleRate": 1.0, + "profilesSampleRate": 1, + "replaysSessionSampleRate": 1, + "replaysOnErrorSampleRate": 1, "spotlight": true } From f2a14c95011342fdaa042ad6c1a1f30b97c2de29 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Wed, 19 Feb 2025 09:20:17 +0100 Subject: [PATCH 02/16] fix ci --- .github/workflows/sample-application.yml | 10 +++++----- samples/react-native-macos/scripts/pod-install.sh | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/sample-application.yml b/.github/workflows/sample-application.yml index 0ee636f8a6..6b7d5e285a 100644 --- a/.github/workflows/sample-application.yml +++ b/.github/workflows/sample-application.yml @@ -115,17 +115,17 @@ jobs: export RN_ARCHITECTURE="${{ matrix.rn-architecture }}" [[ "${{ matrix.build-type }}" == "production" ]] && export CONFIG='release' || export CONFIG='debug' - ../scripts/set-dsn-aos.mjs - ../scripts/build-android.sh -PreactNativeArchitectures=x86 + ./scripts/set-dsn-aos.mjs + ./scripts/build-android.sh -PreactNativeArchitectures=x86 - name: Build iOS App if: ${{ matrix.platform == 'ios' }} - working-directory: ${{ env.REACT_NATIVE_SAMPLE_PATH }}/ios + working-directory: ${{ env.REACT_NATIVE_SAMPLE_PATH }} run: | [[ "${{ matrix.build-type }}" == "production" ]] && export CONFIG='Release' || export CONFIG='Debug' - ../scripts/set-dsn-ios.mjs - ../scripts/build-ios.sh + ./scripts/set-dsn-ios.mjs + ./scripts/build-ios.sh - name: Build macOS App if: ${{ matrix.platform == 'macos' }} diff --git a/samples/react-native-macos/scripts/pod-install.sh b/samples/react-native-macos/scripts/pod-install.sh index 5d1ada6789..a923f8c32a 100755 --- a/samples/react-native-macos/scripts/pod-install.sh +++ b/samples/react-native-macos/scripts/pod-install.sh @@ -12,7 +12,7 @@ echo "ENABLE_NEW_ARCH=$ENABLE_NEW_ARCH" cd "${thisFilePath}/.." bundle install -cd ios +cd macos PRODUCTION=$ENABLE_PROD RCT_NEW_ARCH_ENABLED=$ENABLE_NEW_ARCH bundle exec pod update cat Podfile.lock | grep $RN_SENTRY_POD_NAME From d9fec19d3322fcfe2cb323b1247b1a2ac9c18ccb Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Wed, 19 Feb 2025 09:40:40 +0100 Subject: [PATCH 03/16] fix android archive path --- .github/workflows/sample-application.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sample-application.yml b/.github/workflows/sample-application.yml index 6b7d5e285a..e03be966fc 100644 --- a/.github/workflows/sample-application.yml +++ b/.github/workflows/sample-application.yml @@ -158,8 +158,8 @@ jobs: run: | zip -j \ ${{ env.ANDROID_APP_ARCHIVE_PATH }} \ - app.apk \ - app-androidTest.apk + ${{ env.REACT_NATIVE_SAMPLE_PATH }}/app.apk \ + ${{ env.REACT_NATIVE_SAMPLE_PATH }}/app-androidTest.apk - name: Upload iOS APP if: ${{ matrix.platform == 'ios' && matrix.rn-architecture == 'new' && matrix.build-type == 'production' && matrix.ios-use-frameworks == 'no-frameworks' }} From 6b8f31435662862abba18e4ea1945e8372713b94 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Wed, 19 Feb 2025 10:52:33 +0100 Subject: [PATCH 04/16] fix ios build release --- samples/react-native/scripts/build-ios-release.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/samples/react-native/scripts/build-ios-release.sh b/samples/react-native/scripts/build-ios-release.sh index b7215ce142..4a21d04c17 100755 --- a/samples/react-native/scripts/build-ios-release.sh +++ b/samples/react-native/scripts/build-ios-release.sh @@ -5,8 +5,6 @@ set -xe thisFilePath=$(dirname "$0") -cd "${thisFilePath}/../ios" - export CONFIG='Release' "${thisFilePath}/build-ios.sh" From 25ab23bac28a11274a64a9309d64c6f8f9467ea3 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Wed, 19 Feb 2025 11:09:44 +0100 Subject: [PATCH 05/16] fix(sample-e2e): Fix type errors missing sentry/core and afterAll --- samples/react-native/e2e/captureMessage.test.android.ts | 2 +- samples/react-native/e2e/captureMessage.test.ios.ts | 2 +- samples/react-native/e2e/envelopeHeader.test.android.ts | 2 +- samples/react-native/e2e/envelopeHeader.test.ios.ts | 2 +- samples/react-native/package.json | 1 + yarn.lock | 1 + 6 files changed, 6 insertions(+), 4 deletions(-) diff --git a/samples/react-native/e2e/captureMessage.test.android.ts b/samples/react-native/e2e/captureMessage.test.android.ts index a56d3704bc..ad6d298a02 100644 --- a/samples/react-native/e2e/captureMessage.test.android.ts +++ b/samples/react-native/e2e/captureMessage.test.android.ts @@ -1,4 +1,4 @@ -import { describe, it, beforeAll, expect } from '@jest/globals'; +import { describe, it, beforeAll, expect, afterAll } from '@jest/globals'; import { Envelope } from '@sentry/core'; import { device } from 'detox'; import { diff --git a/samples/react-native/e2e/captureMessage.test.ios.ts b/samples/react-native/e2e/captureMessage.test.ios.ts index 25c953b707..a379718a68 100644 --- a/samples/react-native/e2e/captureMessage.test.ios.ts +++ b/samples/react-native/e2e/captureMessage.test.ios.ts @@ -1,4 +1,4 @@ -import { describe, it, beforeAll, expect } from '@jest/globals'; +import { describe, it, beforeAll, expect, afterAll } from '@jest/globals'; import { Envelope } from '@sentry/core'; import { device } from 'detox'; import { diff --git a/samples/react-native/e2e/envelopeHeader.test.android.ts b/samples/react-native/e2e/envelopeHeader.test.android.ts index 26700f9245..4be175d6c9 100644 --- a/samples/react-native/e2e/envelopeHeader.test.android.ts +++ b/samples/react-native/e2e/envelopeHeader.test.android.ts @@ -1,4 +1,4 @@ -import { describe, it, beforeAll, expect } from '@jest/globals'; +import { describe, it, beforeAll, expect, afterAll } from '@jest/globals'; import { Envelope } from '@sentry/core'; import { device } from 'detox'; import { diff --git a/samples/react-native/e2e/envelopeHeader.test.ios.ts b/samples/react-native/e2e/envelopeHeader.test.ios.ts index 4b72f76e18..04ce534226 100644 --- a/samples/react-native/e2e/envelopeHeader.test.ios.ts +++ b/samples/react-native/e2e/envelopeHeader.test.ios.ts @@ -1,4 +1,4 @@ -import { describe, it, beforeAll, expect } from '@jest/globals'; +import { describe, it, beforeAll, expect, afterAll } from '@jest/globals'; import { Envelope } from '@sentry/core'; import { device } from 'detox'; import { diff --git a/samples/react-native/package.json b/samples/react-native/package.json index 82e50a6755..b7b8980b88 100644 --- a/samples/react-native/package.json +++ b/samples/react-native/package.json @@ -53,6 +53,7 @@ "@react-native/metro-config": "0.77.0", "@react-native/typescript-config": "0.77.0", "@sentry/babel-plugin-component-annotate": "^3.1.2", + "@sentry/core": "8.54.0", "@types/react": "^18.2.65", "@types/react-native-vector-icons": "^6.4.18", "@types/react-test-renderer": "^18.0.0", diff --git a/yarn.lock b/yarn.lock index 17b6a36ac1..8dee18d805 100644 --- a/yarn.lock +++ b/yarn.lock @@ -25067,6 +25067,7 @@ __metadata: "@react-navigation/native-stack": ^7.2.0 "@react-navigation/stack": ^7.1.1 "@sentry/babel-plugin-component-annotate": ^3.1.2 + "@sentry/core": 8.54.0 "@sentry/react-native": 6.7.0 "@types/react": ^18.2.65 "@types/react-native-vector-icons": ^6.4.18 From 8b2227a326dfd969d857edf526b2a0f9d9800fdb Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Thu, 20 Feb 2025 10:45:24 +0100 Subject: [PATCH 06/16] fix IOS_DEVICE empty check --- samples/react-native/scripts/test-ios.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/react-native/scripts/test-ios.sh b/samples/react-native/scripts/test-ios.sh index 9ca3e95c4c..ee9b1bb83f 100755 --- a/samples/react-native/scripts/test-ios.sh +++ b/samples/react-native/scripts/test-ios.sh @@ -7,7 +7,7 @@ thisFilePath=$(dirname "$0") cd "${thisFilePath}/.." -if [ -z $IOS_DEVICE ]; then +if [ -z "$IOS_DEVICE" ]; then # Get the first booted simulator device type and version BOOTED_DEVICE=$(xcrun simctl list devices | grep "Booted" | head -n 1) From 20e7f55e3584fe5cd11639d9307a3c68f9a0155c Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Thu, 20 Feb 2025 16:31:28 +0100 Subject: [PATCH 07/16] fix ios archive --- .github/workflows/sample-application.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/sample-application.yml b/.github/workflows/sample-application.yml index e03be966fc..603cafa619 100644 --- a/.github/workflows/sample-application.yml +++ b/.github/workflows/sample-application.yml @@ -148,10 +148,11 @@ jobs: - name: Archive iOS App if: ${{ matrix.platform == 'ios' && matrix.rn-architecture == 'new' && matrix.build-type == 'production' && matrix.ios-use-frameworks == 'no-frameworks' }} + working-directory: ${{ env.REACT_NATIVE_SAMPLE_PATH }} run: | zip -r \ ${{ github.workspace }}/${{ env.IOS_APP_ARCHIVE_PATH }} \ - ${{ env.REACT_NATIVE_SAMPLE_PATH }}/sentryreactnativesample.app + sentryreactnativesample.app - name: Archive Android App if: ${{ matrix.platform == 'android' && matrix.rn-architecture == 'new' && matrix.build-type == 'production' }} From 56fe4b8dc56e67f183f1586633a75a45afb290ad Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Thu, 20 Feb 2025 18:24:58 +0100 Subject: [PATCH 08/16] test(e2e): Verify captured Errors Screen transaction --- .../e2e/captureMessage.test.android.ts | 8 +-- .../e2e/captureMessage.test.ios.ts | 8 +-- .../e2e/captureTransaction.test.ts | 71 +++++++++++++++++++ samples/react-native/e2e/utils/event.ts | 11 +++ .../e2e/utils/mockedSentryServer.ts | 46 ++++++++++-- samples/react-native/e2e/utils/sleep.ts | 3 + samples/react-native/sentry.options.json | 2 +- 7 files changed, 134 insertions(+), 15 deletions(-) create mode 100644 samples/react-native/e2e/captureTransaction.test.ts create mode 100644 samples/react-native/e2e/utils/event.ts create mode 100644 samples/react-native/e2e/utils/sleep.ts diff --git a/samples/react-native/e2e/captureMessage.test.android.ts b/samples/react-native/e2e/captureMessage.test.android.ts index ad6d298a02..1cb2ac7c5e 100644 --- a/samples/react-native/e2e/captureMessage.test.android.ts +++ b/samples/react-native/e2e/captureMessage.test.android.ts @@ -1,12 +1,12 @@ import { describe, it, beforeAll, expect, afterAll } from '@jest/globals'; -import { Envelope } from '@sentry/core'; +import { Envelope, EventItem } from '@sentry/core'; import { device } from 'detox'; import { createSentryServer, containingEvent, } from './utils/mockedSentryServer'; -import { HEADER, ITEMS } from './utils/consts'; import { tap } from './utils/tap'; +import { getItemOfTypeFrom } from './utils/event'; describe('Capture message', () => { let sentryServer = createSentryServer(); @@ -27,9 +27,7 @@ describe('Capture message', () => { }); it('envelope contains message event', async () => { - const item = (envelope[ITEMS] as [{ type?: string }, unknown][]).find( - i => i[HEADER].type === 'event', - ); + const item = getItemOfTypeFrom(envelope, 'event'); expect(item).toEqual([ { diff --git a/samples/react-native/e2e/captureMessage.test.ios.ts b/samples/react-native/e2e/captureMessage.test.ios.ts index a379718a68..090bfa9ad7 100644 --- a/samples/react-native/e2e/captureMessage.test.ios.ts +++ b/samples/react-native/e2e/captureMessage.test.ios.ts @@ -1,12 +1,12 @@ import { describe, it, beforeAll, expect, afterAll } from '@jest/globals'; -import { Envelope } from '@sentry/core'; +import { Envelope, EventItem } from '@sentry/core'; import { device } from 'detox'; import { createSentryServer, containingEvent, } from './utils/mockedSentryServer'; -import { HEADER, ITEMS } from './utils/consts'; import { tap } from './utils/tap'; +import { getItemOfTypeFrom } from './utils/event'; describe('Capture message', () => { let sentryServer = createSentryServer(); @@ -27,9 +27,7 @@ describe('Capture message', () => { }); it('envelope contains message event', async () => { - const item = (envelope[ITEMS] as [{ type?: string }, unknown][]).find( - i => i[HEADER].type === 'event', - ); + const item = getItemOfTypeFrom(envelope, 'event'); expect(item).toEqual([ { diff --git a/samples/react-native/e2e/captureTransaction.test.ts b/samples/react-native/e2e/captureTransaction.test.ts new file mode 100644 index 0000000000..b94a8cf81a --- /dev/null +++ b/samples/react-native/e2e/captureTransaction.test.ts @@ -0,0 +1,71 @@ +import { describe, it, beforeAll, expect, afterAll } from '@jest/globals'; +import { Envelope, EventItem } from '@sentry/core'; +import { device } from 'detox'; +import { + createSentryServer, + containingTransactionWithName, +} from './utils/mockedSentryServer'; +import { tap } from './utils/tap'; +import { sleep } from './utils/sleep'; +import { getItemOfTypeFrom } from './utils/event'; + +describe('Capture transaction', () => { + let sentryServer = createSentryServer(); + sentryServer.start(); + + let envelope: Envelope; + + beforeAll(async () => { + await device.launchApp(); + + const waitForPerformanceTransaction = sentryServer.waitForEnvelope( + containingTransactionWithName('Performance'), + ); + + await sleep(1_000); + await tap('Performance'); // Bottom tab + await sleep(1_000); + + await waitForPerformanceTransaction; + + envelope = sentryServer.getEnvelope( + containingTransactionWithName('Errors'), // Sample App Initial Screen + ); + }); + + afterAll(async () => { + await sentryServer.close(); + }); + + it('envelope contains transaction context', async () => { + const item = getItemOfTypeFrom(envelope, 'transaction'); + + expect(item).toEqual([ + expect.objectContaining({ + length: expect.any(Number), + type: 'transaction', + }), + expect.objectContaining({ + platform: 'javascript', + contexts: expect.objectContaining({ + trace: { + data: { + 'route.has_been_seen': false, + 'route.key': expect.stringMatching(/^ErrorsScreen/), + 'route.name': 'ErrorsScreen', + 'sentry.idle_span_finish_reason': 'idleTimeout', + 'sentry.op': 'ui.load', + 'sentry.origin': 'auto.app.start', + 'sentry.sample_rate': 1, + 'sentry.source': 'component', + }, + op: 'ui.load', + origin: 'auto.app.start', + span_id: expect.any(String), + trace_id: expect.any(String), + }, + }), + }), + ]); + }); +}); diff --git a/samples/react-native/e2e/utils/event.ts b/samples/react-native/e2e/utils/event.ts new file mode 100644 index 0000000000..df631feb4e --- /dev/null +++ b/samples/react-native/e2e/utils/event.ts @@ -0,0 +1,11 @@ +import { Envelope, EnvelopeItem } from '@sentry/core'; +import { HEADER, ITEMS } from './consts'; + +export function getItemOfTypeFrom( + envelope: Envelope, + type: string, +): T | undefined { + return (envelope[ITEMS] as [{ type?: string }, unknown][]).find( + i => i[HEADER].type === type, + ) as T | undefined; +} diff --git a/samples/react-native/e2e/utils/mockedSentryServer.ts b/samples/react-native/e2e/utils/mockedSentryServer.ts index 40667b0f9d..7f19a0d24b 100644 --- a/samples/react-native/e2e/utils/mockedSentryServer.ts +++ b/samples/react-native/e2e/utils/mockedSentryServer.ts @@ -1,7 +1,8 @@ import { IncomingMessage, ServerResponse, createServer } from 'node:http'; import { createGunzip } from 'node:zlib'; -import { Envelope } from '@sentry/core'; +import { Envelope, EnvelopeItem } from '@sentry/core'; import { parseEnvelope } from './parseEnvelope'; +import { Event } from '@sentry/core'; type RecordedRequest = { path: string | undefined; @@ -16,6 +17,7 @@ export function createSentryServer({ port = 8961 } = {}): { ) => Promise; close: () => Promise; start: () => void; + getEnvelope: (predicate: (envelope: Envelope) => boolean) => Envelope; } { let onNextRequestCallback: (request: RecordedRequest) => void = () => {}; const requests: RecordedRequest[] = []; @@ -74,11 +76,47 @@ export function createSentryServer({ port = 8961 } = {}): { server.close(() => resolve()); }); }, + getEnvelope: (predicate: (envelope: Envelope) => boolean) => { + const envelope = requests.find( + request => request.envelope && predicate(request.envelope), + )?.envelope; + + if (!envelope) { + throw new Error('Envelope not found'); + } + + return envelope; + }, }; } export function containingEvent(envelope: Envelope) { - return envelope[1].some( - item => (item[0] as { type?: string }).type === 'event', - ); + return envelope[1].some(item => itemHeaderIsType(item[0], 'event')); +} + +export function containingTransactionWithName(name: string) { + return (envelope: Envelope) => + envelope[1].some( + item => + itemHeaderIsType(item[0], 'transaction') && + itemBodyIsEvent(item[1]) && + item[1].transaction && + item[1].transaction.includes(name), + ); +} + +export function itemBodyIsEvent(itemBody: EnvelopeItem[1]): itemBody is Event { + return typeof itemBody === 'object' && 'event_id' in itemBody; +} + +export function itemHeaderIsType(itemHeader: EnvelopeItem[0], type: string) { + if (typeof itemHeader !== 'object' || !('type' in itemHeader)) { + return false; + } + + if (itemHeader.type !== type) { + return false; + } + + return true; } diff --git a/samples/react-native/e2e/utils/sleep.ts b/samples/react-native/e2e/utils/sleep.ts new file mode 100644 index 0000000000..a3b7734163 --- /dev/null +++ b/samples/react-native/e2e/utils/sleep.ts @@ -0,0 +1,3 @@ +export function sleep(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); +} diff --git a/samples/react-native/sentry.options.json b/samples/react-native/sentry.options.json index 58425c35d5..8fd5fe07a4 100644 --- a/samples/react-native/sentry.options.json +++ b/samples/react-native/sentry.options.json @@ -1,5 +1,5 @@ { - "dsn": "https://1df17bd4e543fdb31351dee1768bb679@o447951.ingest.sentry.io/5428561", + "dsn": "http://key@localhost:8961/123456", "debug": true, "environment": "dev", "enableUserInteractionTracing": true, From 4cfc9377222a3de5abbf285a6e30362cf24062bd Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Fri, 21 Feb 2025 12:06:35 +0100 Subject: [PATCH 09/16] add more tests --- .../e2e/captureTransaction.test.ts | 164 +++++++++++++++++- 1 file changed, 155 insertions(+), 9 deletions(-) diff --git a/samples/react-native/e2e/captureTransaction.test.ts b/samples/react-native/e2e/captureTransaction.test.ts index b94a8cf81a..a0c922f064 100644 --- a/samples/react-native/e2e/captureTransaction.test.ts +++ b/samples/react-native/e2e/captureTransaction.test.ts @@ -13,24 +13,25 @@ describe('Capture transaction', () => { let sentryServer = createSentryServer(); sentryServer.start(); - let envelope: Envelope; + const getErrorsEnvelope = () => + sentryServer.getEnvelope(containingTransactionWithName('Errors')); + + const getTrackerEnvelope = () => + sentryServer.getEnvelope(containingTransactionWithName('Tracker')); beforeAll(async () => { await device.launchApp(); const waitForPerformanceTransaction = sentryServer.waitForEnvelope( - containingTransactionWithName('Performance'), + containingTransactionWithName('Tracker'), // The last created and sent transaction ); - await sleep(1_000); + await sleep(500); await tap('Performance'); // Bottom tab - await sleep(1_000); + await sleep(200); + await tap('Auto Tracing Example'); // Screen with Full Display await waitForPerformanceTransaction; - - envelope = sentryServer.getEnvelope( - containingTransactionWithName('Errors'), // Sample App Initial Screen - ); }); afterAll(async () => { @@ -38,7 +39,10 @@ describe('Capture transaction', () => { }); it('envelope contains transaction context', async () => { - const item = getItemOfTypeFrom(envelope, 'transaction'); + const item = getItemOfTypeFrom( + getErrorsEnvelope(), + 'transaction', + ); expect(item).toEqual([ expect.objectContaining({ @@ -68,4 +72,146 @@ describe('Capture transaction', () => { }), ]); }); + + it('contains app start measurements', async () => { + const item = getItemOfTypeFrom( + getErrorsEnvelope(), + 'transaction', + ); + + expect(item?.[1]).toEqual( + expect.objectContaining({ + measurements: expect.objectContaining({ + app_start_warm: { + unit: 'millisecond', + value: expect.any(Number), + }, + time_to_initial_display: { + unit: 'millisecond', + value: expect.any(Number), + }, + }), + }), + ); + }); + + it('contains time to initial display measurements', async () => { + const item = getItemOfTypeFrom( + await getErrorsEnvelope(), + 'transaction', + ); + + expect(item?.[1]).toEqual( + expect.objectContaining({ + measurements: expect.objectContaining({ + time_to_initial_display: { + unit: 'millisecond', + value: expect.any(Number), + }, + }), + }), + ); + }); + + it('contains JS stall measurements', async () => { + const item = getItemOfTypeFrom( + await getErrorsEnvelope(), + 'transaction', + ); + + expect(item?.[1]).toEqual( + expect.objectContaining({ + measurements: expect.objectContaining({ + stall_count: { + unit: 'none', + value: expect.any(Number), + }, + stall_longest_time: { + unit: 'millisecond', + value: expect.any(Number), + }, + stall_total_time: { + unit: 'millisecond', + value: expect.any(Number), + }, + }), + }), + ); + }); + + it('contains native frames measurements', async () => { + const item = getItemOfTypeFrom( + getErrorsEnvelope(), + 'transaction', + ); + + expect(item?.[1]).toEqual( + expect.objectContaining({ + measurements: expect.objectContaining({ + frames_frozen: { + unit: 'none', + value: 0, // Should we force 0 in e2e tests? + }, + frames_slow: { + unit: 'none', + value: expect.any(Number), + }, + frames_total: { + unit: 'none', + value: expect.any(Number), + }, + }), + }), + ); + }); + + it('contains time to display measurements', async () => { + const item = getItemOfTypeFrom( + getTrackerEnvelope(), + 'transaction', + ); + + expect(item?.[1]).toEqual( + expect.objectContaining({ + measurements: expect.objectContaining({ + time_to_initial_display: { + unit: 'millisecond', + value: expect.any(Number), + }, + time_to_full_display: { + unit: 'millisecond', + value: expect.any(Number), + }, + }), + }), + ); + }); + + it('contains at least one xhr breadcrumb of request to the tracker endpoint', async () => { + const item = getItemOfTypeFrom( + getTrackerEnvelope(), + 'transaction', + ); + + expect(item?.[1]).toEqual( + expect.objectContaining({ + breadcrumbs: expect.arrayContaining([ + expect.objectContaining({ + category: 'xhr', + data: { + end_timestamp: expect.any(Number), + method: 'GET', + response_body_size: expect.any(Number), + start_timestamp: expect.any(Number), + status_code: expect.any(Number), + url: expect.stringContaining('api.covid19api.com/summary'), + }, + level: 'info', + timestamp: expect.any(Number), + type: 'http', + }), + ]), + }), + ); + }); }); From 86a675e6b8154093a41db09529c86d1ad52aabb6 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Mon, 24 Feb 2025 14:56:37 +0100 Subject: [PATCH 10/16] revert dsn change --- samples/react-native/sentry.options.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/react-native/sentry.options.json b/samples/react-native/sentry.options.json index 8fd5fe07a4..58425c35d5 100644 --- a/samples/react-native/sentry.options.json +++ b/samples/react-native/sentry.options.json @@ -1,5 +1,5 @@ { - "dsn": "http://key@localhost:8961/123456", + "dsn": "https://1df17bd4e543fdb31351dee1768bb679@o447951.ingest.sentry.io/5428561", "debug": true, "environment": "dev", "enableUserInteractionTracing": true, From 71fada97408d159943e16ef61b486075321d7dbd Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Mon, 24 Feb 2025 14:57:29 +0100 Subject: [PATCH 11/16] fix lint --- samples/react-native/e2e/captureTransaction.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/react-native/e2e/captureTransaction.test.ts b/samples/react-native/e2e/captureTransaction.test.ts index a0c922f064..4fbbd3fce7 100644 --- a/samples/react-native/e2e/captureTransaction.test.ts +++ b/samples/react-native/e2e/captureTransaction.test.ts @@ -1,5 +1,5 @@ import { describe, it, beforeAll, expect, afterAll } from '@jest/globals'; -import { Envelope, EventItem } from '@sentry/core'; +import { EventItem } from '@sentry/core'; import { device } from 'detox'; import { createSentryServer, From 0d6f5436ea853e744cf3f7cea5d04eb7255ae5e6 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Mon, 24 Feb 2025 15:59:42 +0100 Subject: [PATCH 12/16] remove jest extended from sample e2e tests --- samples/react-native/package.json | 1 + yarn.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/samples/react-native/package.json b/samples/react-native/package.json index fb1ddbcd67..82d81e9632 100644 --- a/samples/react-native/package.json +++ b/samples/react-native/package.json @@ -78,6 +78,7 @@ "eslint": "^8.19.0", "eslint-plugin-ft-flow": "^3.0.11", "jest": "^29.6.3", + "jest-extended": "^4.0.2", "patch-package": "^8.0.0", "prettier": "2.8.8", "react-test-renderer": "18.3.1", diff --git a/yarn.lock b/yarn.lock index d0220b5a0b..b3b76d4117 100644 --- a/yarn.lock +++ b/yarn.lock @@ -25113,6 +25113,7 @@ __metadata: eslint: ^8.19.0 eslint-plugin-ft-flow: ^3.0.11 jest: ^29.6.3 + jest-extended: ^4.0.2 patch-package: ^8.0.0 prettier: 2.8.8 react: 18.3.1 From c5c93d204667b305d39c4047b6b629b2d1945fec Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Mon, 24 Feb 2025 16:01:40 +0100 Subject: [PATCH 13/16] Relax test to expect any type of app start, remove native frames as missing is also valid --- .../e2e/captureTransaction.test.ts | 47 +++++++------------ 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/samples/react-native/e2e/captureTransaction.test.ts b/samples/react-native/e2e/captureTransaction.test.ts index 4fbbd3fce7..74775fdddc 100644 --- a/samples/react-native/e2e/captureTransaction.test.ts +++ b/samples/react-native/e2e/captureTransaction.test.ts @@ -79,17 +79,30 @@ describe('Capture transaction', () => { 'transaction', ); + expect( + item?.[1].measurements?.app_start_warm || + item?.[1].measurements?.app_start_cold, + ).toBeDefined(); expect(item?.[1]).toEqual( expect.objectContaining({ measurements: expect.objectContaining({ - app_start_warm: { - unit: 'millisecond', - value: expect.any(Number), - }, time_to_initial_display: { unit: 'millisecond', value: expect.any(Number), }, + // Expect warm or cold app start measurements + ...(item?.[1].measurements?.app_start_warm && { + app_start_warm: { + unit: 'millisecond', + value: expect.any(Number), + }, + }), + ...(item?.[1].measurements?.app_start_cold && { + app_start_cold: { + unit: 'millisecond', + value: expect.any(Number), + }, + }), }), }), ); @@ -139,32 +152,6 @@ describe('Capture transaction', () => { ); }); - it('contains native frames measurements', async () => { - const item = getItemOfTypeFrom( - getErrorsEnvelope(), - 'transaction', - ); - - expect(item?.[1]).toEqual( - expect.objectContaining({ - measurements: expect.objectContaining({ - frames_frozen: { - unit: 'none', - value: 0, // Should we force 0 in e2e tests? - }, - frames_slow: { - unit: 'none', - value: expect.any(Number), - }, - frames_total: { - unit: 'none', - value: expect.any(Number), - }, - }), - }), - ); - }); - it('contains time to display measurements', async () => { const item = getItemOfTypeFrom( getTrackerEnvelope(), From def6e30df9b803e973404a5efd401612eae85f90 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Mon, 24 Feb 2025 16:45:55 +0100 Subject: [PATCH 14/16] add app, device and react native context smoke tests --- .../e2e/captureMessage.test.android.ts | 99 +++++++++++++++++++ .../e2e/captureMessage.test.ios.ts | 82 +++++++++++++++ 2 files changed, 181 insertions(+) diff --git a/samples/react-native/e2e/captureMessage.test.android.ts b/samples/react-native/e2e/captureMessage.test.android.ts index 1cb2ac7c5e..d08473ac2b 100644 --- a/samples/react-native/e2e/captureMessage.test.android.ts +++ b/samples/react-native/e2e/captureMessage.test.android.ts @@ -44,4 +44,103 @@ describe('Capture message', () => { }), ]); }); + + it('contains device context', async () => { + const item = getItemOfTypeFrom(envelope, 'event'); + + expect(item?.[1]).toEqual( + expect.objectContaining({ + contexts: expect.objectContaining({ + device: expect.objectContaining({ + battery_level: expect.any(Number), + battery_temperature: expect.any(Number), + boot_time: expect.any(String), + brand: expect.any(String), + charging: expect.any(Boolean), + connection_type: expect.any(String), + family: expect.any(String), + free_memory: expect.any(Number), + free_storage: expect.any(Number), + id: expect.any(String), + language: expect.any(String), + locale: expect.any(String), + low_memory: expect.any(Boolean), + manufacturer: expect.any(String), + memory_size: expect.any(Number), + model: expect.any(String), + model_id: expect.any(String), + online: expect.any(Boolean), + orientation: expect.any(String), + processor_count: expect.any(Number), + processor_frequency: expect.any(Number), + screen_density: expect.any(Number), + screen_dpi: expect.any(Number), + screen_height_pixels: expect.any(Number), + screen_width_pixels: expect.any(Number), + simulator: expect.any(Boolean), + storage_size: expect.any(Number), + timezone: expect.any(String), + }), + }), + }), + ); + }); + + it('contains app context', async () => { + const item = getItemOfTypeFrom(envelope, 'event'); + + expect(item?.[1]).toEqual( + expect.objectContaining({ + contexts: expect.objectContaining({ + app: expect.objectContaining({ + app_build: expect.any(String), + app_identifier: expect.any(String), + app_name: expect.any(String), + app_start_time: expect.any(String), + app_version: expect.any(String), + in_foreground: expect.any(Boolean), + view_names: ['ErrorsScreen'], + }), + }), + }), + ); + }); + + it('contains os context', async () => { + const item = getItemOfTypeFrom(envelope, 'event'); + + expect(item?.[1]).toEqual( + expect.objectContaining({ + contexts: expect.objectContaining({ + os: { + build: expect.any(String), + kernel_version: expect.any(String), + name: 'Android', + rooted: expect.any(Boolean), + version: expect.any(String), + }, + }), + }), + ); + }); + + it('contains react native context', async () => { + const item = getItemOfTypeFrom(envelope, 'event'); + + expect(item?.[1]).toEqual( + expect.objectContaining({ + contexts: expect.objectContaining({ + react_native_context: { + expo: false, + fabric: expect.any(Boolean), + hermes_debug_info: expect.any(Boolean), + hermes_version: expect.any(String), + js_engine: 'hermes', + react_native_version: expect.any(String), + turbo_module: expect.any(Boolean), + }, + }), + }), + ); + }); }); diff --git a/samples/react-native/e2e/captureMessage.test.ios.ts b/samples/react-native/e2e/captureMessage.test.ios.ts index 090bfa9ad7..9e3a804881 100644 --- a/samples/react-native/e2e/captureMessage.test.ios.ts +++ b/samples/react-native/e2e/captureMessage.test.ios.ts @@ -41,4 +41,86 @@ describe('Capture message', () => { }), ]); }); + + it('contains device context', async () => { + const item = getItemOfTypeFrom(envelope, 'event'); + + expect(item?.[1]).toEqual( + expect.objectContaining({ + contexts: expect.objectContaining({ + device: expect.objectContaining({ + arch: expect.any(String), + family: expect.any(String), + free_memory: expect.any(Number), + locale: expect.any(String), + memory_size: expect.any(Number), + model: expect.any(String), + model_id: expect.any(String), + processor_count: expect.any(Number), + simulator: expect.any(Boolean), + thermal_state: expect.any(String), + usable_memory: expect.any(Number), + }), + }), + }), + ); + }); + + it('contains app context', async () => { + const item = getItemOfTypeFrom(envelope, 'event'); + + expect(item?.[1]).toEqual( + expect.objectContaining({ + contexts: expect.objectContaining({ + app: expect.objectContaining({ + app_build: expect.any(String), + app_identifier: expect.any(String), + app_name: expect.any(String), + app_start_time: expect.any(String), + app_version: expect.any(String), + in_foreground: expect.any(Boolean), + // view_names: ['ErrorsScreen-jn5qquvH9Nz'], // TODO: fix this generated hash should not be part of the name + }), + }), + }), + ); + }); + + it('contains os context', async () => { + const item = getItemOfTypeFrom(envelope, 'event'); + + expect(item?.[1]).toEqual( + expect.objectContaining({ + contexts: expect.objectContaining({ + os: { + build: expect.any(String), + kernel_version: expect.any(String), + name: 'iOS', + rooted: expect.any(Boolean), + version: expect.any(String), + }, + }), + }), + ); + }); + + it('contains react native context', async () => { + const item = getItemOfTypeFrom(envelope, 'event'); + + expect(item?.[1]).toEqual( + expect.objectContaining({ + contexts: expect.objectContaining({ + react_native_context: { + expo: false, + fabric: expect.any(Boolean), + hermes_debug_info: expect.any(Boolean), + hermes_version: expect.any(String), + js_engine: 'hermes', + react_native_version: expect.any(String), + turbo_module: expect.any(Boolean), + }, + }), + }), + ); + }); }); From 3aae5bf57bfff42249c33b1f9927576246af3b82 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Mon, 24 Feb 2025 16:46:08 +0100 Subject: [PATCH 15/16] include transaction name in the tests --- samples/react-native/e2e/captureTransaction.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/samples/react-native/e2e/captureTransaction.test.ts b/samples/react-native/e2e/captureTransaction.test.ts index 74775fdddc..95d109c4e4 100644 --- a/samples/react-native/e2e/captureTransaction.test.ts +++ b/samples/react-native/e2e/captureTransaction.test.ts @@ -51,6 +51,7 @@ describe('Capture transaction', () => { }), expect.objectContaining({ platform: 'javascript', + transaction: 'ErrorsScreen', contexts: expect.objectContaining({ trace: { data: { From 97e1b30f2123ecb9638c18a650c79994649f4a15 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 25 Feb 2025 11:01:58 +0100 Subject: [PATCH 16/16] remove jest extended --- samples/react-native/package.json | 1 - yarn.lock | 1 - 2 files changed, 2 deletions(-) diff --git a/samples/react-native/package.json b/samples/react-native/package.json index 82d81e9632..fb1ddbcd67 100644 --- a/samples/react-native/package.json +++ b/samples/react-native/package.json @@ -78,7 +78,6 @@ "eslint": "^8.19.0", "eslint-plugin-ft-flow": "^3.0.11", "jest": "^29.6.3", - "jest-extended": "^4.0.2", "patch-package": "^8.0.0", "prettier": "2.8.8", "react-test-renderer": "18.3.1", diff --git a/yarn.lock b/yarn.lock index b3b76d4117..d0220b5a0b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -25113,7 +25113,6 @@ __metadata: eslint: ^8.19.0 eslint-plugin-ft-flow: ^3.0.11 jest: ^29.6.3 - jest-extended: ^4.0.2 patch-package: ^8.0.0 prettier: 2.8.8 react: 18.3.1