From ab5cdfe6ef6259879f8804292e1c0d8e8690bdf1 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Mon, 3 Mar 2025 15:24:22 +0100 Subject: [PATCH 01/16] internal(sample-rn): Add Detox for integration/e2e tests of the rn sample (#4535) --- .github/workflows/sample-application.yml | 172 +++++- samples/react-native/.detoxrc.js | 119 ++++ samples/react-native/android/app/build.gradle | 9 + .../android/app/proguard-rules.pro | 4 + .../sentry/reactnative/sample/DetoxTest.java | 28 + .../android/app/src/main/AndroidManifest.xml | 3 +- .../main/res/xml/network_security_config.xml | 7 + samples/react-native/android/build.gradle | 8 + samples/react-native/e2e/jest.config.js | 13 + samples/react-native/e2e/starter.test.ts | 12 + samples/react-native/jest.config.js | 7 + samples/react-native/package.json | 4 + samples/react-native/src/App.tsx | 7 + yarn.lock | 538 +++++++++++++++++- 14 files changed, 911 insertions(+), 20 deletions(-) create mode 100644 samples/react-native/.detoxrc.js create mode 100644 samples/react-native/android/app/src/androidTest/java/io/sentry/reactnative/sample/DetoxTest.java create mode 100644 samples/react-native/android/app/src/main/res/xml/network_security_config.xml create mode 100644 samples/react-native/e2e/jest.config.js create mode 100644 samples/react-native/e2e/starter.test.ts create mode 100644 samples/react-native/jest.config.js diff --git a/.github/workflows/sample-application.yml b/.github/workflows/sample-application.yml index 3ec82a6e31..e4e9a8f8b3 100644 --- a/.github/workflows/sample-application.yml +++ b/.github/workflows/sample-application.yml @@ -14,6 +14,12 @@ concurrency: env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} RN_SENTRY_POD_NAME: RNSentry + IOS_APP_ARCHIVE_PATH: sentry-react-native-sample.app.zip + ANDROID_APP_ARCHIVE_PATH: sentry-react-native-sample.apk.zip + REACT_NATIVE_SAMPLE_PATH: samples/react-native + IOS_DEVICE: 'iPhone 16' + IOS_VERSION: '18.1' + ANDROID_API_LEVEL: '30' jobs: diff_check: @@ -66,7 +72,7 @@ jobs: - uses: ruby/setup-ruby@v1 if: ${{ matrix.platform == 'ios' || matrix.platform == 'macos' }} with: - working-directory: ${{ matrix.platform == 'ios' && ' samples/react-native' || ' samples/react-native-macos' }} + working-directory: ${{ matrix.platform == 'ios' && env.REACT_NATIVE_SAMPLE_PATH || ' samples/react-native-macos' }} ruby-version: '3.3.0' # based on what is used in the sample bundler-cache: true # runs 'bundle install' and caches installed gems automatically cache-version: 1 # cache the installed gems @@ -106,7 +112,7 @@ jobs: - name: Build Android App if: ${{ matrix.platform == 'android' }} - working-directory: samples/react-native/android + working-directory: ${{ env.REACT_NATIVE_SAMPLE_PATH }}/android run: | if [[ ${{ matrix.rn-architecture }} == 'new' ]]; then perl -i -pe's/newArchEnabled=false/newArchEnabled=true/g' gradle.properties @@ -119,11 +125,14 @@ jobs: fi [[ "${{ matrix.build-type }}" == "production" ]] && CONFIG='Release' || CONFIG='Debug' echo "Building $CONFIG" - ./gradlew ":app:assemble$CONFIG" -PreactNativeArchitectures=x86 + [[ "${{ matrix.build-type }}" == "production" ]] && TEST_TYPE='release' || TEST_TYPE='debug' + echo "Building $TEST_TYPE" + + ./gradlew ":app:assemble$CONFIG" app:assembleAndroidTest -DtestBuildType=$TEST_TYPE -PreactNativeArchitectures=x86 - name: Build iOS App if: ${{ matrix.platform == 'ios' }} - working-directory: samples/react-native/ios + working-directory: ${{ env.REACT_NATIVE_SAMPLE_PATH }}/ios run: | [[ "${{ matrix.build-type }}" == "production" ]] && CONFIG='Release' || CONFIG='Debug' echo "Building $CONFIG" @@ -160,9 +169,162 @@ jobs: | tee xcodebuild.log \ | xcbeautify --quieter --is-ci --disable-colored-output + - 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 + + - 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 \ + 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' }} + uses: actions/upload-artifact@v4 + with: + name: sample-rn-${{ matrix.rn-architecture }}-${{ matrix.build-type }}-${{ matrix.ios-use-frameworks}}-${{ matrix.platform }} + path: ${{ env.IOS_APP_ARCHIVE_PATH }} + retention-days: 1 + + - name: Upload Android APK + if: ${{ matrix.platform == 'android' && matrix.rn-architecture == 'new' && matrix.build-type == 'production' }} + uses: actions/upload-artifact@v4 + with: + name: sample-rn-${{ matrix.rn-architecture }}-${{ matrix.build-type }}-${{ matrix.platform }} + path: ${{ env.ANDROID_APP_ARCHIVE_PATH }} + retention-days: 1 + - name: Upload logs if: ${{ always() }} uses: actions/upload-artifact@v4 with: name: build-sample-${{ matrix.rn-architecture }}-${{ matrix.platform }}-${{ matrix.build-type }}-${{ matrix.ios-use-frameworks}}-logs - path: samples/react-native/${{ matrix.platform }}/*.log + path: ${{ env.REACT_NATIVE_SAMPLE_PATH }}/${{ matrix.platform }}/*.log + + test: + name: Test ${{ matrix.platform }} ${{ matrix.build-type }} + runs-on: ${{ matrix.runs-on }} + needs: [diff_check, build] + if: ${{ needs.diff_check.outputs.skip_ci != 'true' }} + strategy: + # we want that the matrix keeps running, default is to cancel them if it fails. + fail-fast: false + matrix: + include: + - platform: ios + runs-on: macos-15 + rn-architecture: 'new' + ios-use-frameworks: 'no-frameworks' + build-type: 'production' + + - platform: android + runs-on: ubuntu-latest + rn-architecture: 'new' + build-type: 'production' + + steps: + - uses: actions/checkout@v4 + + - name: Download iOS App Archive + if: ${{ matrix.platform == 'ios' }} + uses: actions/download-artifact@v4 + with: + name: sample-rn-${{ matrix.rn-architecture }}-${{ matrix.build-type }}-${{ matrix.ios-use-frameworks}}-${{ matrix.platform }} + path: ${{ env.REACT_NATIVE_SAMPLE_PATH }} + + - name: Download Android APK + if: ${{ matrix.platform == 'android' }} + uses: actions/download-artifact@v4 + with: + name: sample-rn-${{ matrix.rn-architecture }}-${{ matrix.build-type }}-${{ matrix.platform }} + path: ${{ env.REACT_NATIVE_SAMPLE_PATH }} + + - name: Unzip iOS App Archive + if: ${{ matrix.platform == 'ios' }} + working-directory: ${{ env.REACT_NATIVE_SAMPLE_PATH }} + run: unzip ${{ env.IOS_APP_ARCHIVE_PATH }} + + - name: Unzip Android APK + if: ${{ matrix.platform == 'android' }} + working-directory: ${{ env.REACT_NATIVE_SAMPLE_PATH }} + run: unzip ${{ env.ANDROID_APP_ARCHIVE_PATH }} + + - name: Enable Corepack + run: | + npm install -g corepack@0.29.4 + corepack enable + - uses: actions/setup-node@v4 + with: + node-version: 18 + cache: 'yarn' + cache-dependency-path: yarn.lock + + - name: Install JS Dependencies + run: yarn install + + - name: Install Detox + run: npm install -g detox-cli@20.0.0 + + - name: Install Apple Simulator Utilities + if: ${{ matrix.platform == 'ios' }} + run: | + brew tap wix/brew + brew install applesimutils + + - name: Setup KVM + if: ${{ matrix.platform == 'android' }} + shell: bash + run: | + # check if virtualization is supported... + sudo apt install -y --no-install-recommends cpu-checker coreutils && echo "CPUs=$(nproc --all)" && kvm-ok + # allow access to KVM to run the emulator + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \ + | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - uses: futureware-tech/simulator-action@dab10d813144ef59b48d401cd95da151222ef8cd # pin@v4 + if: ${{ matrix.platform == 'ios' }} + with: + # the same envs are used by Detox ci.sim configuration + model: ${{ env.IOS_DEVICE }} + os_version: ${{ env.IOS_VERSION }} + + - name: Run Detox iOS Tests + if: ${{ matrix.platform == 'ios' }} + working-directory: ${{ env.REACT_NATIVE_SAMPLE_PATH }} + run: detox test --configuration ci.sim + + - 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 + uses: reactivecircus/android-emulator-runner@62dbb605bba737720e10b196cb4220d374026a6d # pin@v2.33.0 + with: + api-level: ${{ env.ANDROID_API_LEVEL }} + force-avd-creation: false + disable-animations: true + disable-spellchecker: true + target: 'aosp_atd' + channel: canary # Necessary for ATDs + emulator-options: > + -no-window + -no-snapshot-save + -gpu swiftshader_indirect + -noaudio + -no-boot-anim + -camera-back none + -camera-front none + -timezone US/Pacific + working-directory: ${{ env.REACT_NATIVE_SAMPLE_PATH }} + script: detox test --configuration ci.android diff --git a/samples/react-native/.detoxrc.js b/samples/react-native/.detoxrc.js new file mode 100644 index 0000000000..fd29191f91 --- /dev/null +++ b/samples/react-native/.detoxrc.js @@ -0,0 +1,119 @@ +const process = require('process'); + +/** @type {Detox.DetoxConfig} */ +module.exports = { + testRunner: { + args: { + $0: 'jest', + config: 'e2e/jest.config.js', + }, + jest: { + setupTimeout: 120000, + }, + }, + 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', + testBinaryPath: 'app-androidTest.apk', + }, + 'ci.ios': { + type: 'ios.app', + binaryPath: 'sentryreactnativesample.app', + }, + }, + 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', + device: { + avdName: process.env.ANDROID_AVD_NAME, + }, + }, + 'ci.simulator': { + type: 'ios.simulator', + device: { + type: process.env.IOS_DEVICE, + os: process.env.IOS_VERSION, + }, + }, + }, + configurations: { + 'ios.sim.debug': { + device: 'simulator', + app: 'ios.debug', + }, + 'ios.sim.release': { + device: 'simulator', + app: 'ios.release', + }, + 'android.att.debug': { + device: 'attached', + app: 'android.debug', + }, + 'android.att.release': { + device: 'attached', + app: 'android.release', + }, + 'android.emu.debug': { + device: 'emulator', + app: 'android.debug', + }, + 'android.emu.release': { + device: 'emulator', + app: 'android.release', + }, + 'ci.android': { + device: 'ci.emulator', + app: 'ci.android', + }, + 'ci.sim': { + device: 'ci.simulator', + app: 'ci.ios', + }, + }, +}; diff --git a/samples/react-native/android/app/build.gradle b/samples/react-native/android/app/build.gradle index 81dacdc70e..7ce1c1ece0 100644 --- a/samples/react-native/android/app/build.gradle +++ b/samples/react-native/android/app/build.gradle @@ -138,6 +138,11 @@ android { targetSdkVersion rootProject.ext.targetSdkVersion versionCode 41 versionName "6.9.0" + buildConfigField "boolean", "SENTRY_DISABLE_NATIVE_START", System.getenv('SENTRY_DISABLE_NATIVE_START') ?: String.valueOf(sentryDisableNativeStart) + + // Detox + testBuildType System.getProperty('testBuildType', 'debug') + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } signingConfigs { @@ -192,11 +197,15 @@ android { signingConfig signingConfigs.debug minifyEnabled enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" + proguardFile "${rootProject.projectDir}/../node_modules/detox/android/detox/proguard-rules-app.pro" } } } dependencies { + androidTestImplementation('com.wix:detox:+') + implementation 'androidx.appcompat:appcompat:1.7.0' + // The version of react-native is set by the React Native Gradle Plugin implementation("com.facebook.react:react-android") diff --git a/samples/react-native/android/app/proguard-rules.pro b/samples/react-native/android/app/proguard-rules.pro index 11b025724a..f4ada6b5a1 100644 --- a/samples/react-native/android/app/proguard-rules.pro +++ b/samples/react-native/android/app/proguard-rules.pro @@ -8,3 +8,7 @@ # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: + +# Detox Release tests were failing on missing kotlin.Result +# It should be covered by node_modules/detox/android/detox/proguard-rules-app.pro but it seems missing +-keep class kotlin.** { *; } diff --git a/samples/react-native/android/app/src/androidTest/java/io/sentry/reactnative/sample/DetoxTest.java b/samples/react-native/android/app/src/androidTest/java/io/sentry/reactnative/sample/DetoxTest.java new file mode 100644 index 0000000000..28b9b28d1c --- /dev/null +++ b/samples/react-native/android/app/src/androidTest/java/io/sentry/reactnative/sample/DetoxTest.java @@ -0,0 +1,28 @@ +package io.sentry.reactnative.sample; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.LargeTest; +import androidx.test.rule.ActivityTestRule; +import com.wix.detox.Detox; +import com.wix.detox.config.DetoxConfig; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class DetoxTest { + @Rule + public ActivityTestRule mActivityRule = + new ActivityTestRule<>(MainActivity.class, false, false); + + @Test + public void runDetoxTests() { + DetoxConfig detoxConfig = new DetoxConfig(); + detoxConfig.idlePolicyConfig.masterTimeoutSec = 90; + detoxConfig.idlePolicyConfig.idleResourceTimeoutSec = 60; + detoxConfig.rnContextLoadTimeoutSec = (BuildConfig.DEBUG ? 180 : 60); + + Detox.runTests(mActivityRule, detoxConfig); + } +} diff --git a/samples/react-native/android/app/src/main/AndroidManifest.xml b/samples/react-native/android/app/src/main/AndroidManifest.xml index e1892528b8..095bdca459 100644 --- a/samples/react-native/android/app/src/main/AndroidManifest.xml +++ b/samples/react-native/android/app/src/main/AndroidManifest.xml @@ -9,7 +9,8 @@ android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="false" android:theme="@style/AppTheme" - android:supportsRtl="true"> + android:supportsRtl="true" + android:networkSecurityConfig="@xml/network_security_config"> + + + 10.0.2.2 + localhost + + diff --git a/samples/react-native/android/build.gradle b/samples/react-native/android/build.gradle index 7b72203ef2..99224fc962 100644 --- a/samples/react-native/android/build.gradle +++ b/samples/react-native/android/build.gradle @@ -20,4 +20,12 @@ buildscript { } } +allprojects { + repositories { + maven { + url("$rootDir/../node_modules/detox/Detox-android") + } + } +} + apply plugin: "com.facebook.react.rootproject" diff --git a/samples/react-native/e2e/jest.config.js b/samples/react-native/e2e/jest.config.js new file mode 100644 index 0000000000..b52d19a014 --- /dev/null +++ b/samples/react-native/e2e/jest.config.js @@ -0,0 +1,13 @@ +/** @type {import('@jest/types').Config.InitialOptions} */ +module.exports = { + preset: 'ts-jest', + rootDir: '..', + testMatch: ['/e2e/**/*.test.ts'], + testTimeout: 120000, + maxWorkers: 1, + globalSetup: 'detox/runners/jest/globalSetup', + globalTeardown: 'detox/runners/jest/globalTeardown', + reporters: ['detox/runners/jest/reporter'], + testEnvironment: 'detox/runners/jest/testEnvironment', + verbose: true, +}; diff --git a/samples/react-native/e2e/starter.test.ts b/samples/react-native/e2e/starter.test.ts new file mode 100644 index 0000000000..b88c9d0882 --- /dev/null +++ b/samples/react-native/e2e/starter.test.ts @@ -0,0 +1,12 @@ +import { describe, it, beforeAll } from '@jest/globals'; +import { device, expect } from 'detox'; + +describe('Shows HomeScreen', () => { + beforeAll(async () => { + await device.launchApp(); + }); + + it('Shows Bottom Tab Bar', async () => { + await expect(element(by.text('Performance'))).toBeVisible(); + }); +}); diff --git a/samples/react-native/jest.config.js b/samples/react-native/jest.config.js new file mode 100644 index 0000000000..27803eeafc --- /dev/null +++ b/samples/react-native/jest.config.js @@ -0,0 +1,7 @@ +/** @type {import('@jest/types').Config.InitialOptions} */ +module.exports = { + testMatch: [ + '/__tests__/**/*-test.ts', + '/__tests__/**/*-test.tsx', + ], +}; diff --git a/samples/react-native/package.json b/samples/react-native/package.json index e8dfbd52e8..39bc4e286c 100644 --- a/samples/react-native/package.json +++ b/samples/react-native/package.json @@ -52,6 +52,8 @@ "@react-native/metro-config": "0.77.1", "@react-native/typescript-config": "0.77.1", "@sentry/babel-plugin-component-annotate": "3.2.1", + "@types/jest": "^29.5.14", + "@types/node": "^22.13.1", "@types/react": "^18.2.65", "@types/react-native-vector-icons": "^6.4.18", "@types/react-test-renderer": "^18.0.0", @@ -59,6 +61,7 @@ "@typescript-eslint/parser": "^7.18.0", "babel-jest": "^29.6.3", "babel-plugin-module-resolver": "^5.0.0", + "detox": "^20.33.0", "eslint": "^8.19.0", "eslint-plugin-ft-flow": "^3.0.11", "jest": "^29.6.3", @@ -66,6 +69,7 @@ "prettier": "2.8.8", "react-test-renderer": "18.3.1", "sentry-react-native-samples-utils": "workspace:^", + "ts-jest": "^29.2.5", "typescript": "5.0.4" }, "engines": { diff --git a/samples/react-native/src/App.tsx b/samples/react-native/src/App.tsx index f0e612d862..987c6ad242 100644 --- a/samples/react-native/src/App.tsx +++ b/samples/react-native/src/App.tsx @@ -39,6 +39,9 @@ import WebviewScreen from './Screens/WebviewScreen'; import { isTurboModuleEnabled } from '@sentry/react-native/dist/js/utils/environment'; import * as ImagePicker from 'react-native-image-picker'; +/* false by default to avoid issues in e2e tests waiting for the animation end */ +const RUNNING_INDICATOR = false; + if (typeof setImmediate === 'undefined') { require('setimmediate'); } @@ -322,6 +325,10 @@ function RunningIndicator() { return null; } + if (!RUNNING_INDICATOR) { + return null; + } + return ; } diff --git a/yarn.lock b/yarn.lock index e0bd3f78f3..17bcaf04a8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4694,6 +4694,13 @@ __metadata: languageName: node linkType: hard +"@flatten-js/interval-tree@npm:^1.1.2": + version: 1.1.3 + resolution: "@flatten-js/interval-tree@npm:1.1.3" + checksum: 8ff9dc4062b20bd1bcff735b6734d93489409af59f87db799abe534d745dd8cd9293a15e720a999058bc97c66b88f1cdb14f6142d122723ffe52032c5ca2efde + languageName: node + linkType: hard + "@hapi/hoek@npm:^9.0.0, @hapi/hoek@npm:^9.3.0": version: 9.3.0 resolution: "@hapi/hoek@npm:9.3.0" @@ -8692,7 +8699,7 @@ __metadata: languageName: node linkType: hard -"@types/jest@npm:^29.5.13": +"@types/jest@npm:^29.5.13, @types/jest@npm:^29.5.14": version: 29.5.14 resolution: "@types/jest@npm:29.5.14" dependencies: @@ -8841,6 +8848,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^22.13.1": + version: 22.13.1 + resolution: "@types/node@npm:22.13.1" + dependencies: + undici-types: ~6.20.0 + checksum: a0759e4bedc3fe892c3ddef5fa9cb5251f9c5b24defc1a389438ea3b5b727c481c1a9bc94bae4ecc7426c89ad293cd66633d163da1ab14d74d358cbec9e1ce31 + languageName: node + linkType: hard + "@types/normalize-package-data@npm:^2.4.0": version: 2.4.4 resolution: "@types/normalize-package-data@npm:2.4.4" @@ -9954,7 +9970,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^8.0.0, ajv@npm:^8.0.1, ajv@npm:^8.9.0": +"ajv@npm:^8.0.0, ajv@npm:^8.0.1, ajv@npm:^8.6.3, ajv@npm:^8.9.0": version: 8.17.1 resolution: "ajv@npm:8.17.1" dependencies: @@ -11236,7 +11252,7 @@ __metadata: languageName: node linkType: hard -"bluebird@npm:3.7.2, bluebird@npm:^3.1.1, bluebird@npm:^3.4.7, bluebird@npm:^3.5.1, bluebird@npm:^3.5.5, bluebird@npm:^3.7.2": +"bluebird@npm:3.7.2, bluebird@npm:^3.1.1, bluebird@npm:^3.4.7, bluebird@npm:^3.5.1, bluebird@npm:^3.5.4, bluebird@npm:^3.5.5, bluebird@npm:^3.7.2": version: 3.7.2 resolution: "bluebird@npm:3.7.2" checksum: 869417503c722e7dc54ca46715f70e15f4d9c602a423a02c825570862d12935be59ed9c7ba34a9b31f186c017c23cac6b54e35446f8353059c101da73eac22ef @@ -11343,6 +11359,13 @@ __metadata: languageName: node linkType: hard +"browser-process-hrtime@npm:^1.0.0": + version: 1.0.0 + resolution: "browser-process-hrtime@npm:1.0.0" + checksum: e30f868cdb770b1201afb714ad1575dd86366b6e861900884665fb627109b3cc757c40067d3bfee1ff2a29c835257ea30725a8018a9afd02ac1c24b408b1e45f + languageName: node + linkType: hard + "browserslist@npm:^4.23.1, browserslist@npm:^4.23.3": version: 4.23.3 resolution: "browserslist@npm:4.23.3" @@ -11468,6 +11491,87 @@ __metadata: languageName: node linkType: hard +"bunyamin@npm:^1.5.2": + version: 1.6.3 + resolution: "bunyamin@npm:1.6.3" + dependencies: + "@flatten-js/interval-tree": ^1.1.2 + multi-sort-stream: ^1.0.4 + stream-json: ^1.7.5 + trace-event-lib: ^1.3.1 + peerDependencies: + "@types/bunyan": ^1.8.8 + bunyan: ^1.8.15 || ^2.0.0 + peerDependenciesMeta: + "@types/bunyan": + optional: true + bunyan: + optional: true + checksum: 3422db179c2f1d9581740b18de79c925e2ab25ee49ea5e66a5b66db16372d6f641927de55010c997050049d9e9569f4b720d409ffa0a573ded86aef5d49768eb + languageName: node + linkType: hard + +"bunyan-debug-stream@npm:^3.1.0": + version: 3.1.1 + resolution: "bunyan-debug-stream@npm:3.1.1" + dependencies: + chalk: ^4.1.2 + peerDependencies: + bunyan: "*" + peerDependenciesMeta: + bunyan: + optional: true + checksum: e0dd2c42de27857bd7c70b600ac30ecf7ef5efe7837c6ea2d87b98e48c7cd16a4fcce1d08439d9fc5dbff2d672b191357ea579750c9cd6379703109f5077bca4 + languageName: node + linkType: hard + +"bunyan@npm:^1.8.12": + version: 1.8.15 + resolution: "bunyan@npm:1.8.15" + dependencies: + dtrace-provider: ~0.8 + moment: ^2.19.3 + mv: ~2 + safe-json-stringify: ~1 + dependenciesMeta: + dtrace-provider: + optional: true + moment: + optional: true + mv: + optional: true + safe-json-stringify: + optional: true + bin: + bunyan: bin/bunyan + checksum: a479e0787c3a0b6565b54bd15f0b6c729d624c5aba53523e140e49e279b7a78508df93000e758bf6d02361117d6b4e6e5fc1d5ece05366fb6c4ba41bf1ac7d52 + languageName: node + linkType: hard + +"bunyan@npm:^2.0.5": + version: 2.0.5 + resolution: "bunyan@npm:2.0.5" + dependencies: + dtrace-provider: ~0.8 + exeunt: 1.1.0 + moment: ^2.19.3 + mv: ~2 + safe-json-stringify: ~1 + dependenciesMeta: + dtrace-provider: + optional: true + moment: + optional: true + mv: + optional: true + safe-json-stringify: + optional: true + bin: + bunyan: bin/bunyan + checksum: a932e883387e5bef23eee0f1f9af94e8b885da32492eaf7164dc58e3b42e5a65845068beb7ac8fbcff31511a55728c1a826bf48ba3e4edd7e220ebf0fe2ab989 + languageName: node + linkType: hard + "byte-size@npm:8.1.1": version: 8.1.1 resolution: "byte-size@npm:8.1.1" @@ -11531,6 +11635,13 @@ __metadata: languageName: node linkType: hard +"caf@npm:^15.0.1": + version: 15.0.1 + resolution: "caf@npm:15.0.1" + checksum: 832cc5d3a6053efb458ed1c1f5e5d3ebbc7710f2275f033c6362dcfd1565f15e29dbee15fa0f3301ecb5c4dbdc753c070b5a4a6d3dc8e246cb784cb26c601e8b + languageName: node + linkType: hard + "call-bind@npm:^1.0.2, call-bind@npm:^1.0.5, call-bind@npm:^1.0.6, call-bind@npm:^1.0.7": version: 1.0.7 resolution: "call-bind@npm:1.0.7" @@ -11705,6 +11816,17 @@ __metadata: languageName: node linkType: hard +"child-process-promise@npm:^2.2.0": + version: 2.2.1 + resolution: "child-process-promise@npm:2.2.1" + dependencies: + cross-spawn: ^4.0.2 + node-version: ^1.0.0 + promise-polyfill: ^6.0.1 + checksum: fb72dda7ee78099f106d57bf3d7cc3225c16c9ddfe8e364e3535a52396482ee81aecd3eff0da7131ca17b7ba9fcbb8af827da63a03f0c3262c76268696898642 + languageName: node + linkType: hard + "chokidar@npm:^3.4.0": version: 3.6.0 resolution: "chokidar@npm:3.6.0" @@ -12570,6 +12692,16 @@ __metadata: languageName: node linkType: hard +"cross-spawn@npm:^4.0.2": + version: 4.0.2 + resolution: "cross-spawn@npm:4.0.2" + dependencies: + lru-cache: ^4.0.1 + which: ^1.2.9 + checksum: 8ce57b3e11c5c798542a21ddfdc1edef33ab6fe001958b31f3340a6ff684e3334a8baad2751efa78b6200aad442cf12b939396d758b0dd5c42c9b782c28fe06e + languageName: node + linkType: hard + "cross-spawn@npm:^6.0.0": version: 6.0.6 resolution: "cross-spawn@npm:6.0.6" @@ -12873,6 +13005,13 @@ __metadata: languageName: node linkType: hard +"decamelize@npm:^4.0.0": + version: 4.0.0 + resolution: "decamelize@npm:4.0.0" + checksum: b7d09b82652c39eead4d6678bb578e3bebd848add894b76d0f6b395bc45b2d692fb88d977e7cfb93c4ed6c119b05a1347cef261174916c2e75c0a8ca57da1809 + languageName: node + linkType: hard + "decamelize@npm:^6.0.0": version: 6.0.0 resolution: "decamelize@npm:6.0.0" @@ -13336,6 +13475,64 @@ __metadata: languageName: node linkType: hard +"detox-copilot@npm:^0.0.27": + version: 0.0.27 + resolution: "detox-copilot@npm:0.0.27" + checksum: 4f01ed1f21fe3128ee50037b63085fe95ccdc9e723c6b034d53720fa325123e39d4f83d18b1ab88a11a679258b0ff734e74f0738118e260f10945fadbe205443 + languageName: node + linkType: hard + +"detox@npm:^20.33.0": + version: 20.33.0 + resolution: "detox@npm:20.33.0" + dependencies: + ajv: ^8.6.3 + bunyan: ^1.8.12 + bunyan-debug-stream: ^3.1.0 + caf: ^15.0.1 + chalk: ^4.0.0 + child-process-promise: ^2.2.0 + detox-copilot: ^0.0.27 + execa: ^5.1.1 + find-up: ^5.0.0 + fs-extra: ^11.0.0 + funpermaproxy: ^1.1.0 + glob: ^8.0.3 + ini: ^1.3.4 + jest-environment-emit: ^1.0.8 + json-cycle: ^1.3.0 + lodash: ^4.17.11 + multi-sort-stream: ^1.0.3 + multipipe: ^4.0.0 + node-ipc: 9.2.1 + proper-lockfile: ^3.0.2 + resolve-from: ^5.0.0 + sanitize-filename: ^1.6.1 + semver: ^7.0.0 + serialize-error: ^8.0.1 + shell-quote: ^1.7.2 + signal-exit: ^3.0.3 + stream-json: ^1.7.4 + strip-ansi: ^6.0.1 + telnet-client: 1.2.8 + tempfile: ^2.0.0 + trace-event-lib: ^1.3.1 + which: ^1.3.1 + ws: ^7.0.0 + yargs: ^17.0.0 + yargs-parser: ^21.0.0 + yargs-unparser: ^2.0.0 + peerDependencies: + jest: 29.x.x || 28.x.x || ^27.2.5 + peerDependenciesMeta: + jest: + optional: true + bin: + detox: local-cli/cli.js + checksum: 14a9a230f02c6e7e535e96223a9aacbef05c06c20887eac3d1f1df1aca612a1c529f94e265064df39d1c98ee700fbc2a26ebdd18affea5e60f17a527ae42d6e0 + languageName: node + linkType: hard + "devtools-protocol@npm:0.0.1232444": version: 0.0.1232444 resolution: "devtools-protocol@npm:0.0.1232444" @@ -13490,6 +13687,25 @@ __metadata: languageName: node linkType: hard +"dtrace-provider@npm:~0.8": + version: 0.8.8 + resolution: "dtrace-provider@npm:0.8.8" + dependencies: + nan: ^2.14.0 + node-gyp: latest + checksum: f2dc89df6a9c443dc9bae3b53496e0685b5da89142951d451c1ce062c75d96698ffc0b3d90f621a59a6a18578be552378ad4e08210759038910ff2080be556b9 + languageName: node + linkType: hard + +"duplexer2@npm:^0.1.2": + version: 0.1.4 + resolution: "duplexer2@npm:0.1.4" + dependencies: + readable-stream: ^2.0.2 + checksum: 744961f03c7f54313f90555ac20284a3fb7bf22fdff6538f041a86c22499560eb6eac9d30ab5768054137cb40e6b18b40f621094e0261d7d8c35a37b7a5ad241 + languageName: node + linkType: hard + "duplexer@npm:^0.1.1, duplexer@npm:~0.1.1": version: 0.1.2 resolution: "duplexer@npm:0.1.2" @@ -13504,6 +13720,13 @@ __metadata: languageName: node linkType: hard +"easy-stack@npm:^1.0.1": + version: 1.0.1 + resolution: "easy-stack@npm:1.0.1" + checksum: 161a99e497b3857b0be4ec9e1ebbe90b241ea9d84702f9881b8e5b3f6822065b8c4e33436996935103e191bffba3607de70712a792f4d406a050def48c6bc381 + languageName: node + linkType: hard + "edge-paths@npm:^3.0.5": version: 3.0.5 resolution: "edge-paths@npm:3.0.5" @@ -14511,6 +14734,13 @@ __metadata: languageName: node linkType: hard +"event-pubsub@npm:4.3.0": + version: 4.3.0 + resolution: "event-pubsub@npm:4.3.0" + checksum: 6940f57790c01a967b7c637f1c9fd000ee968a1d5894186ffb3356ffbe174c70e22e62adbbcfcee3f305482d99b6abe7613c1c27c909b07adc9127dc16c8cf73 + languageName: node + linkType: hard + "event-target-shim@npm:^5.0.0, event-target-shim@npm:^5.0.1": version: 5.0.1 resolution: "event-target-shim@npm:5.0.1" @@ -14588,6 +14818,13 @@ __metadata: languageName: node linkType: hard +"exeunt@npm:1.1.0": + version: 1.1.0 + resolution: "exeunt@npm:1.1.0" + checksum: c0054fa49d7b3abbc2acecd4c6e34c6ce3a0370f9c31d18cdf64dad6be9a6d3fb84d93be892b7d1906f3f23051b3855bde7b255129fc49605a04392f69e98ea2 + languageName: node + linkType: hard + "exit@npm:^0.1.2": version: 0.1.2 resolution: "exit@npm:0.1.2" @@ -15483,6 +15720,17 @@ __metadata: languageName: node linkType: hard +"fs-extra@npm:^11.0.0": + version: 11.3.0 + resolution: "fs-extra@npm:11.3.0" + dependencies: + graceful-fs: ^4.2.0 + jsonfile: ^6.0.1 + universalify: ^2.0.0 + checksum: f983c706e0c22b0c0747a8e9c76aed6f391ba2d76734cf2757cd84da13417b402ed68fe25bace65228856c61d36d3b41da198f1ffbf33d0b34283a2f7a62c6e9 + languageName: node + linkType: hard + "fs-extra@npm:^11.1.0, fs-extra@npm:^11.2.0": version: 11.2.0 resolution: "fs-extra@npm:11.2.0" @@ -15610,6 +15858,13 @@ __metadata: languageName: node linkType: hard +"funpermaproxy@npm:^1.1.0": + version: 1.1.0 + resolution: "funpermaproxy@npm:1.1.0" + checksum: 74cf0aafeadbd79053324f1fb981c1a4358618722ad01c65bd1466b42498fd07acb7749ab9224b25fc8e81c2e1283b92ceee61dded265bd7527b225351db998b + languageName: node + linkType: hard + "gauge@npm:^5.0.0": version: 5.0.2 resolution: "gauge@npm:5.0.2" @@ -15974,7 +16229,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^8.1.0": +"glob@npm:^8.0.3, glob@npm:^8.1.0": version: 8.1.0 resolution: "glob@npm:8.1.0" dependencies: @@ -17093,6 +17348,13 @@ __metadata: languageName: node linkType: hard +"is-plain-obj@npm:^2.1.0": + version: 2.1.0 + resolution: "is-plain-obj@npm:2.1.0" + checksum: cec9100678b0a9fe0248a81743041ed990c2d4c99f893d935545cfbc42876cbe86d207f3b895700c690ad2fa520e568c44afc1605044b535a7820c1d40e38daa + languageName: node + linkType: hard + "is-plain-obj@npm:^4.1.0": version: 4.1.0 resolution: "is-plain-obj@npm:4.1.0" @@ -17583,6 +17845,39 @@ __metadata: languageName: node linkType: hard +"jest-environment-emit@npm:^1.0.8": + version: 1.0.8 + resolution: "jest-environment-emit@npm:1.0.8" + dependencies: + bunyamin: ^1.5.2 + bunyan: ^2.0.5 + bunyan-debug-stream: ^3.1.0 + funpermaproxy: ^1.1.0 + lodash.merge: ^4.6.2 + node-ipc: 9.2.1 + strip-ansi: ^6.0.0 + tslib: ^2.5.3 + peerDependencies: + "@jest/environment": ">=27.2.5" + "@jest/types": ">=27.2.5" + jest: ">=27.2.5" + jest-environment-jsdom: ">=27.2.5" + jest-environment-node: ">=27.2.5" + peerDependenciesMeta: + "@jest/environment": + optional: true + "@jest/types": + optional: true + jest: + optional: true + jest-environment-jsdom: + optional: true + jest-environment-node: + optional: true + checksum: 0c7bafbd3a6e5952f6abb45958f0d2997371d29b29f3876afda48d1d734ccd703577aaac0d5afec2e19dc33a9db0e9458721fe73dbe797f0ced21481d908acfd + languageName: node + linkType: hard + "jest-environment-jsdom@npm:^29.2.1, jest-environment-jsdom@npm:^29.6.2": version: 29.7.0 resolution: "jest-environment-jsdom@npm:29.7.0" @@ -18085,6 +18380,22 @@ __metadata: languageName: node linkType: hard +"js-message@npm:1.0.7": + version: 1.0.7 + resolution: "js-message@npm:1.0.7" + checksum: 18dcc4d80356e8b5be978ca7838d96d4e350a1cb8adc5741c229dec6df09f53bfed7c75c1f360171d2d791a14e2f077d6c2b1013ba899ded7a27d7dfcd4f3784 + languageName: node + linkType: hard + +"js-queue@npm:2.0.2": + version: 2.0.2 + resolution: "js-queue@npm:2.0.2" + dependencies: + easy-stack: ^1.0.1 + checksum: 5049c3f648315ed13e46755704ff5453df70f7e8e1812acf1f98d6700efbec32421f76294a0e63fd2a9f8aabaf124233bbb308f9a2caec9d9f3d833ab5a73079 + languageName: node + linkType: hard + "js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" @@ -18310,6 +18621,13 @@ __metadata: languageName: node linkType: hard +"json-cycle@npm:^1.3.0": + version: 1.5.0 + resolution: "json-cycle@npm:1.5.0" + checksum: 0a44cd349676c6726093c64283fb75402f9104b32325b06c9270af6d639e7caac419f5301a39298aef2ac1659b273b167e02bd622e628c3392cf86f0e77a9f78 + languageName: node + linkType: hard + "json-parse-better-errors@npm:^1.0.1": version: 1.0.2 resolution: "json-parse-better-errors@npm:1.0.2" @@ -19142,6 +19460,16 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^4.0.1": + version: 4.1.5 + resolution: "lru-cache@npm:4.1.5" + dependencies: + pseudomap: ^1.0.2 + yallist: ^2.1.2 + checksum: 4bb4b58a36cd7dc4dcec74cbe6a8f766a38b7426f1ff59d4cf7d82a2aa9b9565cd1cb98f6ff60ce5cd174524868d7bc9b7b1c294371851356066ca9ac4cf135a + languageName: node + linkType: hard + "lru-cache@npm:^5.1.1": version: 5.1.1 resolution: "lru-cache@npm:5.1.1" @@ -20595,7 +20923,7 @@ __metadata: languageName: node linkType: hard -"moment@npm:2.30.1, moment@npm:^2.24.0, moment@npm:^2.29.4": +"moment@npm:2.30.1, moment@npm:^2.19.3, moment@npm:^2.24.0, moment@npm:^2.29.4": version: 2.30.1 resolution: "moment@npm:2.30.1" checksum: 859236bab1e88c3e5802afcf797fc801acdbd0ee509d34ea3df6eea21eb6bcc2abd4ae4e4e64aa7c986aa6cba563c6e62806218e6412a765010712e5fa121ba6 @@ -20650,6 +20978,13 @@ __metadata: languageName: node linkType: hard +"multi-sort-stream@npm:^1.0.3, multi-sort-stream@npm:^1.0.4": + version: 1.0.4 + resolution: "multi-sort-stream@npm:1.0.4" + checksum: b234754e0e7489623f5184ba0e887ffd8014fe829c846fd8a95569339b6e19a616ae1d44f3d064279adfbf92fa5c4d016a89fc5026e16dbd680ebd67067b19a0 + languageName: node + linkType: hard + "multimatch@npm:5.0.0": version: 5.0.0 resolution: "multimatch@npm:5.0.0" @@ -20663,6 +20998,16 @@ __metadata: languageName: node linkType: hard +"multipipe@npm:^4.0.0": + version: 4.0.0 + resolution: "multipipe@npm:4.0.0" + dependencies: + duplexer2: ^0.1.2 + object-assign: ^4.1.0 + checksum: 5a494ec2ce5bfdb389882ca595e3c4a33cae6c90dad879db2e3aa9a94484d8b164b0fb7b58ccf7593ae7e8c6213fd3f53a736b2c98e4f14c5ed1d38debc33f98 + languageName: node + linkType: hard + "mute-stream@npm:0.0.7": version: 0.0.7 resolution: "mute-stream@npm:0.0.7" @@ -20684,7 +21029,7 @@ __metadata: languageName: node linkType: hard -"mv@npm:2.1.1": +"mv@npm:2.1.1, mv@npm:~2": version: 2.1.1 resolution: "mv@npm:2.1.1" dependencies: @@ -20706,6 +21051,15 @@ __metadata: languageName: node linkType: hard +"nan@npm:^2.14.0": + version: 2.22.0 + resolution: "nan@npm:2.22.0" + dependencies: + node-gyp: latest + checksum: 222e3a090e326c72f6782d948f44ee9b81cfb2161d5fe53216f04426a273fd094deee9dcc6813096dd2397689a2b10c1a92d3885d2e73fd2488a51547beb2929 + languageName: node + linkType: hard + "nanoid@npm:3.3.7, nanoid@npm:^3.1.23, nanoid@npm:^3.3.7": version: 3.3.7 resolution: "nanoid@npm:3.3.7" @@ -20892,6 +21246,17 @@ __metadata: languageName: node linkType: hard +"node-ipc@npm:9.2.1": + version: 9.2.1 + resolution: "node-ipc@npm:9.2.1" + dependencies: + event-pubsub: 4.3.0 + js-message: 1.0.7 + js-queue: 2.0.2 + checksum: a38aa4c8ca4317b293e0ce21f0a3a4941fc51c054800b35e263fcfe3e0feeb60e7d2c497f015054b28783316c6e7d9cc3837af9d9958bcbd8c577d0cdf6964b7 + languageName: node + linkType: hard + "node-java-connector@npm:1.1.1": version: 1.1.1 resolution: "node-java-connector@npm:1.1.1" @@ -20962,6 +21327,13 @@ __metadata: languageName: node linkType: hard +"node-version@npm:^1.0.0": + version: 1.2.0 + resolution: "node-version@npm:1.2.0" + checksum: 74e92d2a7f0fe0fce3aafd6dcc30b3b440999df68b3d92fcefcad2a52b37bc29c6b542f33760229390bfdc1a4d993fb65b9c199b1f0d568969d07fc1c04bc1e7 + languageName: node + linkType: hard + "nopt@npm:^7.0.0, nopt@npm:^7.2.0, nopt@npm:^7.2.1": version: 7.2.1 resolution: "nopt@npm:7.2.1" @@ -22421,6 +22793,13 @@ __metadata: languageName: node linkType: hard +"promise-polyfill@npm:^6.0.1": + version: 6.1.0 + resolution: "promise-polyfill@npm:6.1.0" + checksum: 6f1899cca37e48f67a424842282acd525d8d99d3536f2d97e37a117cfc4a0006683330ceaf5a15fbc09b4450f319a680292f9970a5f8e9cf90acbce0bdb0f751 + languageName: node + linkType: hard + "promise-retry@npm:^2.0.1": version: 2.0.1 resolution: "promise-retry@npm:2.0.1" @@ -22479,6 +22858,17 @@ __metadata: languageName: node linkType: hard +"proper-lockfile@npm:^3.0.2": + version: 3.2.0 + resolution: "proper-lockfile@npm:3.2.0" + dependencies: + graceful-fs: ^4.1.11 + retry: ^0.12.0 + signal-exit: ^3.0.2 + checksum: 1be1bb702b9d47bdf18d75f22578f51370781feba7d2617f70ff8c66a86bcfa6e55b4f69c57fc326380110f2d1ffdb6e54a4900814bf156c04ee4eb2d3c065aa + languageName: node + linkType: hard + "proto-list@npm:~1.2.1": version: 1.2.4 resolution: "proto-list@npm:1.2.4" @@ -22526,6 +22916,13 @@ __metadata: languageName: node linkType: hard +"pseudomap@npm:^1.0.2": + version: 1.0.2 + resolution: "pseudomap@npm:1.0.2" + checksum: 856c0aae0ff2ad60881168334448e898ad7a0e45fe7386d114b150084254c01e200c957cf378378025df4e052c7890c5bd933939b0e0d2ecfcc1dc2f0b2991f5 + languageName: node + linkType: hard + "psl@npm:^1.1.33": version: 1.9.0 resolution: "psl@npm:1.9.0" @@ -23593,7 +23990,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^2.0.1, readable-stream@npm:^2.0.5, readable-stream@npm:~2.3.6": +"readable-stream@npm:^2.0.1, readable-stream@npm:^2.0.2, readable-stream@npm:^2.0.5, readable-stream@npm:~2.3.6": version: 2.3.8 resolution: "readable-stream@npm:2.3.8" dependencies: @@ -24285,6 +24682,13 @@ __metadata: languageName: node linkType: hard +"safe-json-stringify@npm:~1": + version: 1.2.0 + resolution: "safe-json-stringify@npm:1.2.0" + checksum: 5bb32db6d6a3ceb3752df51f4043a412419cd3d4fcd5680a865dfa34cd7e575ba659c077d13f52981ced084061df9c75c7fb12e391584d4264e6914c1cd3d216 + languageName: node + linkType: hard + "safe-regex-test@npm:^1.0.3": version: 1.0.3 resolution: "safe-regex-test@npm:1.0.3" @@ -24310,7 +24714,7 @@ __metadata: languageName: node linkType: hard -"sanitize-filename@npm:1.6.3": +"sanitize-filename@npm:1.6.3, sanitize-filename@npm:^1.6.1": version: 1.6.3 resolution: "sanitize-filename@npm:1.6.3" dependencies: @@ -24644,6 +25048,8 @@ __metadata: "@react-navigation/stack": ^7.1.1 "@sentry/babel-plugin-component-annotate": 3.2.1 "@sentry/react-native": 6.9.0 + "@types/jest": ^29.5.14 + "@types/node": ^22.13.1 "@types/react": ^18.2.65 "@types/react-native-vector-icons": ^6.4.18 "@types/react-test-renderer": ^18.0.0 @@ -24652,6 +25058,7 @@ __metadata: babel-jest: ^29.6.3 babel-plugin-module-resolver: ^5.0.0 delay: ^6.0.0 + detox: ^20.33.0 eslint: ^8.19.0 eslint-plugin-ft-flow: ^3.0.11 jest: ^29.6.3 @@ -24671,6 +25078,7 @@ __metadata: react-test-renderer: 18.3.1 redux: ^4.2.1 sentry-react-native-samples-utils: "workspace:^" + ts-jest: ^29.2.5 typescript: 5.0.4 languageName: unknown linkType: soft @@ -24724,6 +25132,15 @@ __metadata: languageName: node linkType: hard +"serialize-error@npm:^8.0.1": + version: 8.1.0 + resolution: "serialize-error@npm:8.1.0" + dependencies: + type-fest: ^0.20.2 + checksum: 2eef236d50edd2d7926e602c14fb500dc3a125ee52e9f08f67033181b8e0be5d1122498bdf7c23c80683cddcad083a27974e9e7111ce23165f4d3bcdd6d65102 + languageName: node + linkType: hard + "serve-favicon@npm:2.5.0": version: 2.5.0 resolution: "serve-favicon@npm:2.5.0" @@ -24946,6 +25363,13 @@ __metadata: languageName: node linkType: hard +"shell-quote@npm:^1.7.2": + version: 1.8.2 + resolution: "shell-quote@npm:1.8.2" + checksum: 1e97b62ced1c4c5135015978ebf273bed1f425a68cf84163e83fbb0f34b3ff9471e656720dab2b7cbb4ae0f58998e686d17d166c28dfb3662acd009e8bd7faed + languageName: node + linkType: hard + "shelljs@npm:^0.8.3": version: 0.8.5 resolution: "shelljs@npm:0.8.5" @@ -25374,6 +25798,13 @@ __metadata: languageName: node linkType: hard +"stream-chain@npm:^2.2.5": + version: 2.2.5 + resolution: "stream-chain@npm:2.2.5" + checksum: c83cbf504bd11e2bcbe761a92801295b3decac7ffa4092ceffca2eb1b5d0763bcc511fa22cd8044e8a18c21ca66794fd10c8d9cd1292a3e6c0d83a4194c6b8ed + languageName: node + linkType: hard + "stream-combiner@npm:^0.2.2": version: 0.2.2 resolution: "stream-combiner@npm:0.2.2" @@ -25384,6 +25815,15 @@ __metadata: languageName: node linkType: hard +"stream-json@npm:^1.7.4, stream-json@npm:^1.7.5": + version: 1.9.1 + resolution: "stream-json@npm:1.9.1" + dependencies: + stream-chain: ^2.2.5 + checksum: 2ebf0648f9ed82ee79727a9a47805231a70d5032e0c21cee3e05cd3c449d3ce49c72b371555447eeef55904bae22ac64be8ae6086fc6cce0b83b3aa617736b64 + languageName: node + linkType: hard + "stream-slice@npm:^0.1.2": version: 0.1.2 resolution: "stream-slice@npm:0.1.2" @@ -25961,7 +26401,16 @@ __metadata: languageName: node linkType: hard -"temp-dir@npm:1.0.0": +"telnet-client@npm:1.2.8": + version: 1.2.8 + resolution: "telnet-client@npm:1.2.8" + dependencies: + bluebird: ^3.5.4 + checksum: d2430c5449a46f6f4f9a7c2c648164f014c308aa0d3207a4d6b5b7f0e443322d07b180ecac63ad43eadb6557c8ef5ae7dce1ea6276464c8c82c8c6a9c9c01bf2 + languageName: node + linkType: hard + +"temp-dir@npm:1.0.0, temp-dir@npm:^1.0.0": version: 1.0.0 resolution: "temp-dir@npm:1.0.0" checksum: cb2b58ddfb12efa83e939091386ad73b425c9a8487ea0095fe4653192a40d49184a771a1beba99045fbd011e389fd563122d79f54f82be86a55620667e08a6b2 @@ -25994,6 +26443,16 @@ __metadata: languageName: node linkType: hard +"tempfile@npm:^2.0.0": + version: 2.0.0 + resolution: "tempfile@npm:2.0.0" + dependencies: + temp-dir: ^1.0.0 + uuid: ^3.0.1 + checksum: 8a92a0f57e0ae457dfbc156b14c427b42048a86ca6bade311835cc2aeda61b25b82d688f71f2d663dde6f172f479ed07293b53f7981e41cb6f9120a3eb4fe797 + languageName: node + linkType: hard + "tempy@npm:^0.7.1": version: 0.7.1 resolution: "tempy@npm:0.7.1" @@ -26195,6 +26654,15 @@ __metadata: languageName: node linkType: hard +"trace-event-lib@npm:^1.3.1": + version: 1.4.1 + resolution: "trace-event-lib@npm:1.4.1" + dependencies: + browser-process-hrtime: ^1.0.0 + checksum: f10dbfeccee9ec80a8cf69ecadd49fa609fc2593fb50a83cc4b664524c0531f91009134bf54302f9c4911afed119b0eebb8d2724723fc44516e24a40aaae9219 + languageName: node + linkType: hard + "treeverse@npm:^3.0.0": version: 3.0.0 resolution: "treeverse@npm:3.0.0" @@ -26248,7 +26716,7 @@ __metadata: languageName: node linkType: hard -"ts-jest@npm:^29.1.1": +"ts-jest@npm:^29.1.1, ts-jest@npm:^29.2.5": version: 29.2.5 resolution: "ts-jest@npm:29.2.5" dependencies: @@ -26376,6 +26844,13 @@ __metadata: languageName: node linkType: hard +"tslib@npm:^2.5.3": + version: 2.8.1 + resolution: "tslib@npm:2.8.1" + checksum: e4aba30e632b8c8902b47587fd13345e2827fa639e7c3121074d5ee0880723282411a8838f830b55100cbe4517672f84a2472667d355b81e8af165a55dc6203a + languageName: node + linkType: hard + "tsutils@npm:^3.21.0": version: 3.21.0 resolution: "tsutils@npm:3.21.0" @@ -26759,6 +27234,13 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~6.20.0": + version: 6.20.0 + resolution: "undici-types@npm:6.20.0" + checksum: b7bc50f012dc6afbcce56c9fd62d7e86b20a62ff21f12b7b5cbf1973b9578d90f22a9c7fe50e638e96905d33893bf2f9f16d98929c4673c2480de05c6c96ea8b + languageName: node + linkType: hard + "undici@npm:^6.11.1, undici@npm:^6.18.2": version: 6.21.1 resolution: "undici@npm:6.21.1" @@ -27018,6 +27500,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^3.0.1": + version: 3.4.0 + resolution: "uuid@npm:3.4.0" + bin: + uuid: ./bin/uuid + checksum: 58de2feed61c59060b40f8203c0e4ed7fd6f99d42534a499f1741218a1dd0c129f4aa1de797bcf822c8ea5da7e4137aa3673431a96dae729047f7aca7b27866f + languageName: node + linkType: hard + "uuid@npm:^7.0.3": version: 7.0.3 resolution: "uuid@npm:7.0.3" @@ -27390,7 +27881,7 @@ __metadata: languageName: node linkType: hard -"which@npm:^1.2.9": +"which@npm:^1.2.9, which@npm:^1.3.1": version: 1.3.1 resolution: "which@npm:1.3.1" dependencies: @@ -27608,7 +28099,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:^7, ws@npm:^7.5.1, ws@npm:^7.5.10": +"ws@npm:^7, ws@npm:^7.0.0, ws@npm:^7.5.1, ws@npm:^7.5.10": version: 7.5.10 resolution: "ws@npm:7.5.10" peerDependencies: @@ -27739,6 +28230,13 @@ __metadata: languageName: node linkType: hard +"yallist@npm:^2.1.2": + version: 2.1.2 + resolution: "yallist@npm:2.1.2" + checksum: 9ba99409209f485b6fcb970330908a6d41fa1c933f75e08250316cce19383179a6b70a7e0721b89672ebb6199cc377bf3e432f55100da6a7d6e11902b0a642cb + languageName: node + linkType: hard + "yallist@npm:^3.0.2": version: 3.1.1 resolution: "yallist@npm:3.1.1" @@ -27769,7 +28267,7 @@ __metadata: languageName: node linkType: hard -"yargs-parser@npm:21.1.1, yargs-parser@npm:^21.0.1, yargs-parser@npm:^21.1.1": +"yargs-parser@npm:21.1.1, yargs-parser@npm:^21.0.0, yargs-parser@npm:^21.0.1, yargs-parser@npm:^21.1.1": version: 21.1.1 resolution: "yargs-parser@npm:21.1.1" checksum: ed2d96a616a9e3e1cc7d204c62ecc61f7aaab633dcbfab2c6df50f7f87b393993fe6640d017759fe112d0cb1e0119f2b4150a87305cc873fd90831c6a58ccf1c @@ -27793,7 +28291,19 @@ __metadata: languageName: node linkType: hard -"yargs@npm:17.7.2, yargs@npm:^17.3.1, yargs@npm:^17.6.2, yargs@npm:^17.7.2": +"yargs-unparser@npm:^2.0.0": + version: 2.0.0 + resolution: "yargs-unparser@npm:2.0.0" + dependencies: + camelcase: ^6.0.0 + decamelize: ^4.0.0 + flat: ^5.0.2 + is-plain-obj: ^2.1.0 + checksum: 68f9a542c6927c3768c2f16c28f71b19008710abd6b8f8efbac6dcce26bbb68ab6503bed1d5994bdbc2df9a5c87c161110c1dfe04c6a3fe5c6ad1b0e15d9a8a3 + languageName: node + linkType: hard + +"yargs@npm:17.7.2, yargs@npm:^17.0.0, yargs@npm:^17.3.1, yargs@npm:^17.6.2, yargs@npm:^17.7.2": version: 17.7.2 resolution: "yargs@npm:17.7.2" dependencies: From 05dac0e30a4874bae4ff87aad6ea4f49c62b0b56 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Mon, 3 Mar 2025 16:15:35 +0100 Subject: [PATCH 02/16] removed unused build config property --- samples/react-native/android/app/build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/samples/react-native/android/app/build.gradle b/samples/react-native/android/app/build.gradle index 7ce1c1ece0..46efce4e3d 100644 --- a/samples/react-native/android/app/build.gradle +++ b/samples/react-native/android/app/build.gradle @@ -138,7 +138,6 @@ android { targetSdkVersion rootProject.ext.targetSdkVersion versionCode 41 versionName "6.9.0" - buildConfigField "boolean", "SENTRY_DISABLE_NATIVE_START", System.getenv('SENTRY_DISABLE_NATIVE_START') ?: String.valueOf(sentryDisableNativeStart) // Detox testBuildType System.getProperty('testBuildType', 'debug') From ec20ae48d82bb5ca1f9d26ed7889c2086351bd25 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 4 Mar 2025 14:49:07 +0100 Subject: [PATCH 03/16] fix(appStart): Attach App Start spans to the first created not the first processed root span --- CHANGELOG.md | 1 + .../src/js/tracing/integrations/appStart.ts | 46 +++++++++++++++++-- .../core/test/profiling/integration.test.ts | 3 +- .../tracing/integrations/appStart.test.ts | 10 +++- .../tracing/reactnavigation.ttid.test.tsx | 1 + 5 files changed, 54 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d94d004b1a..777e0618d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ ### Fixes - Considers the `SENTRY_DISABLE_AUTO_UPLOAD` and `SENTRY_DISABLE_NATIVE_DEBUG_UPLOAD` environment variables in the configuration of the Sentry Android Gradle Plugin for Expo plugin ([#4583](https://github.com/getsentry/sentry-react-native/pull/4583)) +- Attach App Start spans to the first created not the first processed root span ([#4618](https://github.com/getsentry/sentry-react-native/pull/4618)) ### Dependencies diff --git a/packages/core/src/js/tracing/integrations/appStart.ts b/packages/core/src/js/tracing/integrations/appStart.ts index 572039f30e..696527f99c 100644 --- a/packages/core/src/js/tracing/integrations/appStart.ts +++ b/packages/core/src/js/tracing/integrations/appStart.ts @@ -1,5 +1,5 @@ /* eslint-disable complexity, max-lines */ -import type { Client, Event, Integration, SpanJSON, TransactionEvent } from '@sentry/core'; +import type { Client, Event, Integration, Span, SpanJSON, TransactionEvent } from '@sentry/core'; import { getCapturedScopesOnSpan, getClient, @@ -17,7 +17,7 @@ import { } from '../../measurements'; import type { NativeAppStartResponse } from '../../NativeRNSentry'; import type { ReactNativeClientOptions } from '../../options'; -import { convertSpanToTransaction, setEndTimeValue } from '../../utils/span'; +import { convertSpanToTransaction, isRootSpan, setEndTimeValue } from '../../utils/span'; import { NATIVE } from '../../wrapper'; import { APP_START_COLD as APP_START_COLD_OP, @@ -137,16 +137,18 @@ export const appStartIntegration = ({ let _client: Client | undefined = undefined; let isEnabled = true; let appStartDataFlushed = false; + let firstStartedActiveRootSpanId: string | undefined = undefined; const setup = (client: Client): void => { _client = client; - const clientOptions = client.getOptions() as ReactNativeClientOptions; + const { enableAppStartTracking } = client.getOptions() as ReactNativeClientOptions; - const { enableAppStartTracking } = clientOptions; if (!enableAppStartTracking) { isEnabled = false; logger.warn('[AppStart] App start tracking is disabled.'); } + + client.on('spanStart', recordFirstStartedActiveRootSpanId); }; const afterAllSetup = (_client: Client): void => { @@ -168,6 +170,27 @@ export const appStartIntegration = ({ return event; }; + const recordFirstStartedActiveRootSpanId = (rootSpan: Span): void => { + if (firstStartedActiveRootSpanId) { + return; + } + + if (!isRootSpan(rootSpan)) { + return; + } + + setFirstStartedActiveRootSpanId(rootSpan.spanContext().spanId); + }; + + /** + * For testing purposes only. + * @private + */ + const setFirstStartedActiveRootSpanId = (spanId: string | undefined): void => { + firstStartedActiveRootSpanId = spanId; + logger.debug('[AppStart] First started active root span id recorded.', firstStartedActiveRootSpanId); + }; + async function captureStandaloneAppStart(): Promise { if (!standalone) { logger.debug( @@ -213,11 +236,23 @@ export const appStartIntegration = ({ return; } + if (!firstStartedActiveRootSpanId) { + logger.warn('[AppStart] No first started active root span id recorded. Can not attach app start.'); + return; + } + if (!event.contexts || !event.contexts.trace) { logger.warn('[AppStart] Transaction event is missing trace context. Can not attach app start.'); return; } + if (firstStartedActiveRootSpanId !== event.contexts.trace.span_id) { + logger.warn( + '[AppStart] First started active root span id does not match the transaction event span id. Can not attached app start.', + ); + return; + } + const appStart = await NATIVE.fetchNativeAppStart(); if (!appStart) { logger.warn('[AppStart] Failed to retrieve the app start metrics from the native layer.'); @@ -333,7 +368,8 @@ export const appStartIntegration = ({ afterAllSetup, processEvent, captureStandaloneAppStart, - }; + setFirstStartedActiveRootSpanId, + } as AppStartIntegration; }; function setSpanDurationAsMeasurementOnTransactionEvent(event: TransactionEvent, label: string, span: SpanJSON): void { diff --git a/packages/core/test/profiling/integration.test.ts b/packages/core/test/profiling/integration.test.ts index 83da5cc53d..7462d94c6e 100644 --- a/packages/core/test/profiling/integration.test.ts +++ b/packages/core/test/profiling/integration.test.ts @@ -262,8 +262,9 @@ describe('profiling integration', () => { const transaction1 = Sentry.startSpanManual({ name: 'test-name-1' }, span => span); const transaction2 = Sentry.startSpanManual({ name: 'test-name-2' }, span => span); transaction1.end(); - transaction2.end(); + jest.runOnlyPendingTimers(); + transaction2.end(); jest.runAllTimers(); expectEnvelopeToContainProfile( diff --git a/packages/core/test/tracing/integrations/appStart.test.ts b/packages/core/test/tracing/integrations/appStart.test.ts index 17709730dd..a5f86374fe 100644 --- a/packages/core/test/tracing/integrations/appStart.test.ts +++ b/packages/core/test/tracing/integrations/appStart.test.ts @@ -34,6 +34,10 @@ import { NATIVE } from '../../../src/js/wrapper'; import { getDefaultTestClientOptions, TestClient } from '../../mocks/client'; import { mockFunction } from '../../testutils'; +type AppStartIntegrationTest = ReturnType & { + setFirstStartedActiveRootSpanId: (spanId: string | undefined) => void; +}; + let dateNowSpy: jest.SpyInstance; jest.mock('../../../src/js/wrapper', () => { @@ -692,7 +696,10 @@ describe('App Start Integration', () => { const integration = appStartIntegration(); const client = new TestClient(getDefaultTestClientOptions()); - const actualEvent = await integration.processEvent(getMinimalTransactionEvent(), {}, client); + const firstEvent = getMinimalTransactionEvent(); + (integration as AppStartIntegrationTest).setFirstStartedActiveRootSpanId(firstEvent.contexts?.trace?.span_id); + + const actualEvent = await integration.processEvent(firstEvent, {}, client); expect(actualEvent).toEqual( expectEventWithAttachedColdAppStart({ timeOriginMilliseconds, appStartTimeMilliseconds }), ); @@ -725,6 +732,7 @@ describe('App Start Integration', () => { function processEvent(event: Event): PromiseLike | Event | null { const integration = appStartIntegration(); + (integration as AppStartIntegrationTest).setFirstStartedActiveRootSpanId(event.contexts?.trace?.span_id); return integration.processEvent(event, {}, new TestClient(getDefaultTestClientOptions())); } diff --git a/packages/core/test/tracing/reactnavigation.ttid.test.tsx b/packages/core/test/tracing/reactnavigation.ttid.test.tsx index c34c1271cb..387d6b9799 100644 --- a/packages/core/test/tracing/reactnavigation.ttid.test.tsx +++ b/packages/core/test/tracing/reactnavigation.ttid.test.tsx @@ -367,6 +367,7 @@ describe('React Navigation - TTID', () => { }); test('idle transaction should cancel the ttid span if new frame not received', () => { + jest.runOnlyPendingTimers(); // Flush app start transaction mockedNavigation.navigateToNewScreen(); jest.runOnlyPendingTimers(); // Flush ttid transaction From c24a5f3009afbd2c85f7f66a1f33c6dbe747a2cf Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 4 Mar 2025 14:49:07 +0100 Subject: [PATCH 04/16] fix(appStart): Attach App Start spans to the first created not the first processed root span --- CHANGELOG.md | 1 + .../src/js/tracing/integrations/appStart.ts | 46 +++++++++++++++++-- .../core/test/profiling/integration.test.ts | 3 +- .../tracing/integrations/appStart.test.ts | 10 +++- .../tracing/reactnavigation.ttid.test.tsx | 1 + 5 files changed, 54 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d94d004b1a..777e0618d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ ### Fixes - Considers the `SENTRY_DISABLE_AUTO_UPLOAD` and `SENTRY_DISABLE_NATIVE_DEBUG_UPLOAD` environment variables in the configuration of the Sentry Android Gradle Plugin for Expo plugin ([#4583](https://github.com/getsentry/sentry-react-native/pull/4583)) +- Attach App Start spans to the first created not the first processed root span ([#4618](https://github.com/getsentry/sentry-react-native/pull/4618)) ### Dependencies diff --git a/packages/core/src/js/tracing/integrations/appStart.ts b/packages/core/src/js/tracing/integrations/appStart.ts index 572039f30e..696527f99c 100644 --- a/packages/core/src/js/tracing/integrations/appStart.ts +++ b/packages/core/src/js/tracing/integrations/appStart.ts @@ -1,5 +1,5 @@ /* eslint-disable complexity, max-lines */ -import type { Client, Event, Integration, SpanJSON, TransactionEvent } from '@sentry/core'; +import type { Client, Event, Integration, Span, SpanJSON, TransactionEvent } from '@sentry/core'; import { getCapturedScopesOnSpan, getClient, @@ -17,7 +17,7 @@ import { } from '../../measurements'; import type { NativeAppStartResponse } from '../../NativeRNSentry'; import type { ReactNativeClientOptions } from '../../options'; -import { convertSpanToTransaction, setEndTimeValue } from '../../utils/span'; +import { convertSpanToTransaction, isRootSpan, setEndTimeValue } from '../../utils/span'; import { NATIVE } from '../../wrapper'; import { APP_START_COLD as APP_START_COLD_OP, @@ -137,16 +137,18 @@ export const appStartIntegration = ({ let _client: Client | undefined = undefined; let isEnabled = true; let appStartDataFlushed = false; + let firstStartedActiveRootSpanId: string | undefined = undefined; const setup = (client: Client): void => { _client = client; - const clientOptions = client.getOptions() as ReactNativeClientOptions; + const { enableAppStartTracking } = client.getOptions() as ReactNativeClientOptions; - const { enableAppStartTracking } = clientOptions; if (!enableAppStartTracking) { isEnabled = false; logger.warn('[AppStart] App start tracking is disabled.'); } + + client.on('spanStart', recordFirstStartedActiveRootSpanId); }; const afterAllSetup = (_client: Client): void => { @@ -168,6 +170,27 @@ export const appStartIntegration = ({ return event; }; + const recordFirstStartedActiveRootSpanId = (rootSpan: Span): void => { + if (firstStartedActiveRootSpanId) { + return; + } + + if (!isRootSpan(rootSpan)) { + return; + } + + setFirstStartedActiveRootSpanId(rootSpan.spanContext().spanId); + }; + + /** + * For testing purposes only. + * @private + */ + const setFirstStartedActiveRootSpanId = (spanId: string | undefined): void => { + firstStartedActiveRootSpanId = spanId; + logger.debug('[AppStart] First started active root span id recorded.', firstStartedActiveRootSpanId); + }; + async function captureStandaloneAppStart(): Promise { if (!standalone) { logger.debug( @@ -213,11 +236,23 @@ export const appStartIntegration = ({ return; } + if (!firstStartedActiveRootSpanId) { + logger.warn('[AppStart] No first started active root span id recorded. Can not attach app start.'); + return; + } + if (!event.contexts || !event.contexts.trace) { logger.warn('[AppStart] Transaction event is missing trace context. Can not attach app start.'); return; } + if (firstStartedActiveRootSpanId !== event.contexts.trace.span_id) { + logger.warn( + '[AppStart] First started active root span id does not match the transaction event span id. Can not attached app start.', + ); + return; + } + const appStart = await NATIVE.fetchNativeAppStart(); if (!appStart) { logger.warn('[AppStart] Failed to retrieve the app start metrics from the native layer.'); @@ -333,7 +368,8 @@ export const appStartIntegration = ({ afterAllSetup, processEvent, captureStandaloneAppStart, - }; + setFirstStartedActiveRootSpanId, + } as AppStartIntegration; }; function setSpanDurationAsMeasurementOnTransactionEvent(event: TransactionEvent, label: string, span: SpanJSON): void { diff --git a/packages/core/test/profiling/integration.test.ts b/packages/core/test/profiling/integration.test.ts index 83da5cc53d..7462d94c6e 100644 --- a/packages/core/test/profiling/integration.test.ts +++ b/packages/core/test/profiling/integration.test.ts @@ -262,8 +262,9 @@ describe('profiling integration', () => { const transaction1 = Sentry.startSpanManual({ name: 'test-name-1' }, span => span); const transaction2 = Sentry.startSpanManual({ name: 'test-name-2' }, span => span); transaction1.end(); - transaction2.end(); + jest.runOnlyPendingTimers(); + transaction2.end(); jest.runAllTimers(); expectEnvelopeToContainProfile( diff --git a/packages/core/test/tracing/integrations/appStart.test.ts b/packages/core/test/tracing/integrations/appStart.test.ts index 17709730dd..a5f86374fe 100644 --- a/packages/core/test/tracing/integrations/appStart.test.ts +++ b/packages/core/test/tracing/integrations/appStart.test.ts @@ -34,6 +34,10 @@ import { NATIVE } from '../../../src/js/wrapper'; import { getDefaultTestClientOptions, TestClient } from '../../mocks/client'; import { mockFunction } from '../../testutils'; +type AppStartIntegrationTest = ReturnType & { + setFirstStartedActiveRootSpanId: (spanId: string | undefined) => void; +}; + let dateNowSpy: jest.SpyInstance; jest.mock('../../../src/js/wrapper', () => { @@ -692,7 +696,10 @@ describe('App Start Integration', () => { const integration = appStartIntegration(); const client = new TestClient(getDefaultTestClientOptions()); - const actualEvent = await integration.processEvent(getMinimalTransactionEvent(), {}, client); + const firstEvent = getMinimalTransactionEvent(); + (integration as AppStartIntegrationTest).setFirstStartedActiveRootSpanId(firstEvent.contexts?.trace?.span_id); + + const actualEvent = await integration.processEvent(firstEvent, {}, client); expect(actualEvent).toEqual( expectEventWithAttachedColdAppStart({ timeOriginMilliseconds, appStartTimeMilliseconds }), ); @@ -725,6 +732,7 @@ describe('App Start Integration', () => { function processEvent(event: Event): PromiseLike | Event | null { const integration = appStartIntegration(); + (integration as AppStartIntegrationTest).setFirstStartedActiveRootSpanId(event.contexts?.trace?.span_id); return integration.processEvent(event, {}, new TestClient(getDefaultTestClientOptions())); } diff --git a/packages/core/test/tracing/reactnavigation.ttid.test.tsx b/packages/core/test/tracing/reactnavigation.ttid.test.tsx index c34c1271cb..387d6b9799 100644 --- a/packages/core/test/tracing/reactnavigation.ttid.test.tsx +++ b/packages/core/test/tracing/reactnavigation.ttid.test.tsx @@ -367,6 +367,7 @@ describe('React Navigation - TTID', () => { }); test('idle transaction should cancel the ttid span if new frame not received', () => { + jest.runOnlyPendingTimers(); // Flush app start transaction mockedNavigation.navigateToNewScreen(); jest.runOnlyPendingTimers(); // Flush ttid transaction From daab7487f5346c866ad61ff40d7bae13bc772810 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 4 Mar 2025 15:13:32 +0100 Subject: [PATCH 05/16] add jest driven maestro tests --- samples/react-native/.detoxrc.js | 119 ---- samples/react-native/android/app/build.gradle | 4 - .../android/app/proguard-rules.pro | 4 - .../sentry/reactnative/sample/DetoxTest.java | 28 - samples/react-native/android/build.gradle | 8 - .../e2e/captureTransaction.test.ts | 203 +++++++ .../e2e/captureTransaction.test.yml | 13 + samples/react-native/e2e/jest.config.js | 4 - samples/react-native/e2e/starter.test.ts | 12 - samples/react-native/e2e/utils/consts.ts | 2 + samples/react-native/e2e/utils/event.ts | 11 + samples/react-native/e2e/utils/maestro.ts | 25 + .../e2e/utils/mockedSentryServer.ts | 148 +++++ .../react-native/e2e/utils/parseEnvelope.ts | 74 +++ samples/react-native/package.json | 5 +- samples/react-native/src/App.tsx | 8 +- samples/react-native/src/utils.ts | 23 + yarn.lock | 527 +----------------- 18 files changed, 532 insertions(+), 686 deletions(-) delete mode 100644 samples/react-native/.detoxrc.js delete mode 100644 samples/react-native/android/app/src/androidTest/java/io/sentry/reactnative/sample/DetoxTest.java create mode 100644 samples/react-native/e2e/captureTransaction.test.ts create mode 100644 samples/react-native/e2e/captureTransaction.test.yml delete mode 100644 samples/react-native/e2e/starter.test.ts create mode 100644 samples/react-native/e2e/utils/consts.ts create mode 100644 samples/react-native/e2e/utils/event.ts create mode 100644 samples/react-native/e2e/utils/maestro.ts create mode 100644 samples/react-native/e2e/utils/mockedSentryServer.ts create mode 100644 samples/react-native/e2e/utils/parseEnvelope.ts diff --git a/samples/react-native/.detoxrc.js b/samples/react-native/.detoxrc.js deleted file mode 100644 index fd29191f91..0000000000 --- a/samples/react-native/.detoxrc.js +++ /dev/null @@ -1,119 +0,0 @@ -const process = require('process'); - -/** @type {Detox.DetoxConfig} */ -module.exports = { - testRunner: { - args: { - $0: 'jest', - config: 'e2e/jest.config.js', - }, - jest: { - setupTimeout: 120000, - }, - }, - 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', - testBinaryPath: 'app-androidTest.apk', - }, - 'ci.ios': { - type: 'ios.app', - binaryPath: 'sentryreactnativesample.app', - }, - }, - 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', - device: { - avdName: process.env.ANDROID_AVD_NAME, - }, - }, - 'ci.simulator': { - type: 'ios.simulator', - device: { - type: process.env.IOS_DEVICE, - os: process.env.IOS_VERSION, - }, - }, - }, - configurations: { - 'ios.sim.debug': { - device: 'simulator', - app: 'ios.debug', - }, - 'ios.sim.release': { - device: 'simulator', - app: 'ios.release', - }, - 'android.att.debug': { - device: 'attached', - app: 'android.debug', - }, - 'android.att.release': { - device: 'attached', - app: 'android.release', - }, - 'android.emu.debug': { - device: 'emulator', - app: 'android.debug', - }, - 'android.emu.release': { - device: 'emulator', - app: 'android.release', - }, - 'ci.android': { - device: 'ci.emulator', - app: 'ci.android', - }, - 'ci.sim': { - device: 'ci.simulator', - app: 'ci.ios', - }, - }, -}; diff --git a/samples/react-native/android/app/build.gradle b/samples/react-native/android/app/build.gradle index 46efce4e3d..752989e846 100644 --- a/samples/react-native/android/app/build.gradle +++ b/samples/react-native/android/app/build.gradle @@ -138,10 +138,6 @@ android { targetSdkVersion rootProject.ext.targetSdkVersion versionCode 41 versionName "6.9.0" - - // Detox - testBuildType System.getProperty('testBuildType', 'debug') - testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } signingConfigs { diff --git a/samples/react-native/android/app/proguard-rules.pro b/samples/react-native/android/app/proguard-rules.pro index f4ada6b5a1..11b025724a 100644 --- a/samples/react-native/android/app/proguard-rules.pro +++ b/samples/react-native/android/app/proguard-rules.pro @@ -8,7 +8,3 @@ # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: - -# Detox Release tests were failing on missing kotlin.Result -# It should be covered by node_modules/detox/android/detox/proguard-rules-app.pro but it seems missing --keep class kotlin.** { *; } diff --git a/samples/react-native/android/app/src/androidTest/java/io/sentry/reactnative/sample/DetoxTest.java b/samples/react-native/android/app/src/androidTest/java/io/sentry/reactnative/sample/DetoxTest.java deleted file mode 100644 index 28b9b28d1c..0000000000 --- a/samples/react-native/android/app/src/androidTest/java/io/sentry/reactnative/sample/DetoxTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.sentry.reactnative.sample; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.LargeTest; -import androidx.test.rule.ActivityTestRule; -import com.wix.detox.Detox; -import com.wix.detox.config.DetoxConfig; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -@LargeTest -public class DetoxTest { - @Rule - public ActivityTestRule mActivityRule = - new ActivityTestRule<>(MainActivity.class, false, false); - - @Test - public void runDetoxTests() { - DetoxConfig detoxConfig = new DetoxConfig(); - detoxConfig.idlePolicyConfig.masterTimeoutSec = 90; - detoxConfig.idlePolicyConfig.idleResourceTimeoutSec = 60; - detoxConfig.rnContextLoadTimeoutSec = (BuildConfig.DEBUG ? 180 : 60); - - Detox.runTests(mActivityRule, detoxConfig); - } -} diff --git a/samples/react-native/android/build.gradle b/samples/react-native/android/build.gradle index 99224fc962..7b72203ef2 100644 --- a/samples/react-native/android/build.gradle +++ b/samples/react-native/android/build.gradle @@ -20,12 +20,4 @@ buildscript { } } -allprojects { - repositories { - maven { - url("$rootDir/../node_modules/detox/Detox-android") - } - } -} - apply plugin: "com.facebook.react.rootproject" diff --git a/samples/react-native/e2e/captureTransaction.test.ts b/samples/react-native/e2e/captureTransaction.test.ts new file mode 100644 index 0000000000..64f808b549 --- /dev/null +++ b/samples/react-native/e2e/captureTransaction.test.ts @@ -0,0 +1,203 @@ +import { describe, it, beforeAll, expect, afterAll } from '@jest/globals'; +import { EventItem } from '@sentry/core'; +import { + createSentryServer, + containingTransactionWithName, +} from './utils/mockedSentryServer'; + +import { getItemOfTypeFrom } from './utils/event'; +import { maestro } from './utils/maestro'; + +describe('Capture transaction', () => { + let sentryServer = createSentryServer(); + sentryServer.start(); + + const getErrorsEnvelope = () => + sentryServer.getEnvelope(containingTransactionWithName('Errors')); + + const getTrackerEnvelope = () => + sentryServer.getEnvelope(containingTransactionWithName('Tracker')); + + beforeAll(async () => { + const waitForTrackerTx = sentryServer.waitForEnvelope( + containingTransactionWithName('Tracker'), // The last created and sent transaction + ); + const waitForErrorsTx = sentryServer.waitForEnvelope( + containingTransactionWithName('Errors'), // The last created and sent transaction + ); + + await maestro('captureTransaction.test.yml'); + + await Promise.all([waitForTrackerTx, waitForErrorsTx]); + }); + + afterAll(async () => { + await sentryServer.close(); + }); + + it('envelope contains transaction context', async () => { + const item = getItemOfTypeFrom( + getErrorsEnvelope(), + 'transaction', + ); + + expect(item).toEqual([ + expect.objectContaining({ + length: expect.any(Number), + type: 'transaction', + }), + expect.objectContaining({ + platform: 'javascript', + transaction: 'ErrorsScreen', + 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', + 'thread.name': 'javascript', + }, + op: 'ui.load', + origin: 'auto.app.start', + span_id: expect.any(String), + trace_id: expect.any(String), + }, + }), + }), + ]); + }); + + it('contains app start measurements', async () => { + const item = getItemOfTypeFrom( + getErrorsEnvelope(), + 'transaction', + ); + + expect( + item?.[1].measurements?.app_start_warm || + item?.[1].measurements?.app_start_cold, + ).toBeDefined(); + expect(item?.[1]).toEqual( + expect.objectContaining({ + measurements: expect.objectContaining({ + 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), + }, + }), + }), + }), + ); + }); + + 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 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', + }), + ]), + }), + ); + }); +}); diff --git a/samples/react-native/e2e/captureTransaction.test.yml b/samples/react-native/e2e/captureTransaction.test.yml new file mode 100644 index 0000000000..741b26f80d --- /dev/null +++ b/samples/react-native/e2e/captureTransaction.test.yml @@ -0,0 +1,13 @@ +appId: io.sentry.reactnative.sample +--- +- launchApp + # We expect cold start + clearState: true + stopApp: true + launchArgs: + isE2ETest: true + +# For unknown reasons tapOn: "Performance" does not work on iOS +- tapOn: + id: "performance-tab-icon" +- tapOn: "Auto Tracing Example" diff --git a/samples/react-native/e2e/jest.config.js b/samples/react-native/e2e/jest.config.js index b52d19a014..49b0b27c51 100644 --- a/samples/react-native/e2e/jest.config.js +++ b/samples/react-native/e2e/jest.config.js @@ -5,9 +5,5 @@ module.exports = { testMatch: ['/e2e/**/*.test.ts'], testTimeout: 120000, maxWorkers: 1, - globalSetup: 'detox/runners/jest/globalSetup', - globalTeardown: 'detox/runners/jest/globalTeardown', - reporters: ['detox/runners/jest/reporter'], - testEnvironment: 'detox/runners/jest/testEnvironment', verbose: true, }; diff --git a/samples/react-native/e2e/starter.test.ts b/samples/react-native/e2e/starter.test.ts deleted file mode 100644 index b88c9d0882..0000000000 --- a/samples/react-native/e2e/starter.test.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { describe, it, beforeAll } from '@jest/globals'; -import { device, expect } from 'detox'; - -describe('Shows HomeScreen', () => { - beforeAll(async () => { - await device.launchApp(); - }); - - it('Shows Bottom Tab Bar', async () => { - await expect(element(by.text('Performance'))).toBeVisible(); - }); -}); diff --git a/samples/react-native/e2e/utils/consts.ts b/samples/react-native/e2e/utils/consts.ts new file mode 100644 index 0000000000..9a751a5fa4 --- /dev/null +++ b/samples/react-native/e2e/utils/consts.ts @@ -0,0 +1,2 @@ +export const HEADER = 0; +export const ITEMS = 1; 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/maestro.ts b/samples/react-native/e2e/utils/maestro.ts new file mode 100644 index 0000000000..af54e9bdb1 --- /dev/null +++ b/samples/react-native/e2e/utils/maestro.ts @@ -0,0 +1,25 @@ +import { spawn } from 'node:child_process'; +import path from 'node:path'; + +/** + * Run a Maestro test and return a promise that resolves when the test is finished. + * + * @param test - The path to the Maestro test file relative to the `e2e` directory. + * @returns A promise that resolves when the test is finished. + */ +export const maestro = async (test: string) => { + return new Promise((resolve, reject) => { + const process = spawn('maestro', ['test', test, '--format', 'junit'], { + cwd: path.join(__dirname, '..'), + stdio: 'inherit', + }); + + process.on('close', code => { + if (code !== 0) { + reject(`Maestro test failed with code ${code}. See logs above.`); + } else { + resolve(undefined); + } + }); + }); +}; diff --git a/samples/react-native/e2e/utils/mockedSentryServer.ts b/samples/react-native/e2e/utils/mockedSentryServer.ts new file mode 100644 index 0000000000..43bbf80991 --- /dev/null +++ b/samples/react-native/e2e/utils/mockedSentryServer.ts @@ -0,0 +1,148 @@ +import { IncomingMessage, ServerResponse, createServer } from 'node:http'; +import { createGunzip } from 'node:zlib'; +import { Envelope, EnvelopeItem } from '@sentry/core'; +import { parseEnvelope } from './parseEnvelope'; +import { Event } from '@sentry/core'; + +type RecordedRequest = { + path: string | undefined; + headers: Record; + body: Buffer; + envelope: Envelope; +}; + +export function createSentryServer({ port = 8961 } = {}): { + waitForEnvelope: ( + predicate: (envelope: Envelope) => boolean, + ) => Promise; + close: () => Promise; + start: () => void; + getEnvelope: (predicate: (envelope: Envelope) => boolean) => Envelope; +} { + const nextRequestCallbacks: (typeof onNextRequestCallback)[] = []; + let onNextRequestCallback: (request: RecordedRequest) => void = ( + request: RecordedRequest, + ) => { + nextRequestCallbacks.forEach(callback => callback(request)); + }; + const requests: RecordedRequest[] = []; + + const server = createServer((req: IncomingMessage, res: ServerResponse) => { + let body: Buffer = Buffer.from([]); + + const gunzip = createGunzip(); + req.pipe(gunzip); + + gunzip.on('data', (chunk: Buffer) => { + body = Buffer.concat([body, chunk]); + }); + + gunzip.on('end', () => { + const request = { + path: req.url, + headers: req.headers, + body: body, + envelope: parseEnvelope(body), + }; + requests.push(request); + + body = Buffer.from([]); + + res.writeHead(200); + res.end('OK'); + + onNextRequestCallback(request); + }); + }); + + return { + start: () => { + server.listen(port); + }, + waitForEnvelope: async ( + predicate: (envelope: Envelope) => boolean, + ): Promise => { + return new Promise((resolve, reject) => { + nextRequestCallbacks.push((request: RecordedRequest) => { + try { + if (predicate(request.envelope)) { + resolve(request.envelope); + return; + } + } catch (e) { + reject(e); + return; + } + }); + }); + }, + close: async () => { + await new Promise(resolve => { + 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 => itemHeaderIsType(item[0], 'event')); +} + +export function containingEventWithAndroidMessage(message: string) { + return (envelope: Envelope) => + envelope[1].some( + item => + itemHeaderIsType(item[0], 'event') && + itemBodyIsEvent(item[1]) && + item[1].message && + (item[1].message as unknown as { message: string }).message === message, + ); +} + +export function containingEventWithMessage(message: string) { + return (envelope: Envelope) => + envelope[1].some( + item => + itemHeaderIsType(item[0], 'event') && + itemBodyIsEvent(item[1]) && + item[1].message === message, + ); +} + +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/parseEnvelope.ts b/samples/react-native/e2e/utils/parseEnvelope.ts new file mode 100644 index 0000000000..e6b29b201e --- /dev/null +++ b/samples/react-native/e2e/utils/parseEnvelope.ts @@ -0,0 +1,74 @@ +import { + Envelope, + BaseEnvelopeHeaders, + BaseEnvelopeItemHeaders, +} from '@sentry/core'; + +/** + * Parses an envelope + */ +export function parseEnvelope(env: string | Uint8Array): Envelope { + let buffer = typeof env === 'string' ? encodeUTF8(env) : env; + + function readBinary(length?: number): Uint8Array { + if (!length) { + throw new Error('Binary Envelope Items must have a length to be read'); + } + const bin = buffer.subarray(0, length); + // Replace the buffer with the remaining data excluding trailing newline + buffer = buffer.subarray(length + 1); + return bin; + } + + function readJson(): T { + let i = buffer.indexOf(0xa); + // If we couldn't find a newline, we must have found the end of the buffer + if (i < 0) { + i = buffer.length; + } + + return JSON.parse(decodeUTF8(readBinary(i))) as T; + } + + const envelopeHeader = readJson(); + + const items: [any, any][] = []; + + while (buffer.length) { + const itemHeader = readJson(); + const isBinaryAttachment = + itemHeader.type === 'attachment' && + itemHeader.content_type !== 'application/json'; + // TODO: Parse when needed for the tests + const isReplayVideo = (itemHeader.type as string) === 'replay_video'; + + try { + let item: any = {}; + if (isReplayVideo || isBinaryAttachment) { + item = readBinary(itemHeader.length); + } else { + item = readJson(); + } + items.push([itemHeader, item]); + } catch (e) { + console.error(e, 'itemHeader', itemHeader, 'buffer', buffer.toString()); + throw e; + } + } + + return [envelopeHeader, items]; +} + +/** + * Encode a string to UTF8 array. + */ +function encodeUTF8(input: string): Uint8Array { + return new TextEncoder().encode(input); +} + +/** + * Decode a UTF8 array to string. + */ +function decodeUTF8(input: Uint8Array): string { + return new TextDecoder().decode(input); +} diff --git a/samples/react-native/package.json b/samples/react-native/package.json index 39bc4e286c..8bfa916021 100644 --- a/samples/react-native/package.json +++ b/samples/react-native/package.json @@ -8,6 +8,8 @@ "ios": "react-native run-ios", "start": "react-native start", "test": "jest", + "test-android": "jest --config e2e/jest.config.js", + "test-ios": "jest --config e2e/jest.config.js", "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 ..", @@ -25,12 +27,14 @@ "@react-navigation/native": "^7.0.14", "@react-navigation/native-stack": "^7.2.0", "@react-navigation/stack": "^7.1.1", + "@sentry/core": "8.54.0", "@sentry/react-native": "6.9.0", "delay": "^6.0.0", "react": "18.3.1", "react-native": "0.77.1", "react-native-gesture-handler": "^2.22.1", "react-native-image-picker": "^8.0.0", + "react-native-launch-arguments": "^4.1.0", "react-native-reanimated": "3.16.7", "react-native-safe-area-context": "5.2.0", "react-native-screens": "4.6.0", @@ -61,7 +65,6 @@ "@typescript-eslint/parser": "^7.18.0", "babel-jest": "^29.6.3", "babel-plugin-module-resolver": "^5.0.0", - "detox": "^20.33.0", "eslint": "^8.19.0", "eslint-plugin-ft-flow": "^3.0.11", "jest": "^29.6.3", diff --git a/samples/react-native/src/App.tsx b/samples/react-native/src/App.tsx index 987c6ad242..831f131dad 100644 --- a/samples/react-native/src/App.tsx +++ b/samples/react-native/src/App.tsx @@ -18,7 +18,6 @@ import Animated, { import * as Sentry from '@sentry/react-native'; import { FeedbackWidget } from '@sentry/react-native'; -import { SENTRY_INTERNAL_DSN } from './dsn'; import ErrorsScreen from './Screens/ErrorsScreen'; import PerformanceScreen from './Screens/PerformanceScreen'; import TrackerScreen from './Screens/TrackerScreen'; @@ -32,7 +31,7 @@ import GesturesTracingScreen from './Screens/GesturesTracingScreen'; import { LogBox, Platform, StyleSheet, View } from 'react-native'; import Ionicons from 'react-native-vector-icons/Ionicons'; import PlaygroundScreen from './Screens/PlaygroundScreen'; -import { logWithoutTracing } from './utils'; +import { getDsn, logWithoutTracing } from './utils'; import { ErrorEvent } from '@sentry/core'; import HeavyNavigationScreen from './Screens/HeavyNavigationScreen'; import WebviewScreen from './Screens/WebviewScreen'; @@ -57,7 +56,7 @@ const reactNavigationIntegration = Sentry.reactNavigationIntegration({ Sentry.init({ // Replace the example DSN below with your own DSN: - dsn: SENTRY_INTERNAL_DSN, + dsn: getDsn(), debug: true, environment: 'dev', beforeSend: (event: ErrorEvent) => { @@ -251,6 +250,7 @@ function BottomTabsNavigator() { name={focused ? 'bug' : 'bug-outline'} size={size} color={color} + testID="errors-tab-icon" /> ), }} @@ -266,6 +266,7 @@ function BottomTabsNavigator() { name={focused ? 'speedometer' : 'speedometer-outline'} size={size} color={color} + testID="performance-tab-icon" /> ), }} @@ -283,6 +284,7 @@ function BottomTabsNavigator() { } size={size} color={color} + testID="playground-tab-icon" /> ), }} diff --git a/samples/react-native/src/utils.ts b/samples/react-native/src/utils.ts index 8681333e30..1404c2ce36 100644 --- a/samples/react-native/src/utils.ts +++ b/samples/react-native/src/utils.ts @@ -1,3 +1,8 @@ +import { Platform } from 'react-native'; +import { LaunchArguments } from 'react-native-launch-arguments'; + +import { SENTRY_INTERNAL_DSN } from './dsn'; + export function logWithoutTracing(...args: unknown[]) { if ('__sentry_original__' in console.log) { console.log.__sentry_original__(...args); @@ -5,3 +10,21 @@ export function logWithoutTracing(...args: unknown[]) { console.log(...args); } } + +export const isE2ETest = () => { + try { + return !!LaunchArguments.value().isE2ETest; + } catch (e) { + return false; + } +}; + +export const getDsn = () => { + if (isE2ETest() && Platform.OS === 'android') { + return 'http://key@10.0.2.2:8961/123456'; + } + if (isE2ETest() && Platform.OS === 'ios') { + return 'http://key@localhost:8961/123456'; + } + return SENTRY_INTERNAL_DSN; +}; diff --git a/yarn.lock b/yarn.lock index 17bcaf04a8..fbdd294045 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4694,13 +4694,6 @@ __metadata: languageName: node linkType: hard -"@flatten-js/interval-tree@npm:^1.1.2": - version: 1.1.3 - resolution: "@flatten-js/interval-tree@npm:1.1.3" - checksum: 8ff9dc4062b20bd1bcff735b6734d93489409af59f87db799abe534d745dd8cd9293a15e720a999058bc97c66b88f1cdb14f6142d122723ffe52032c5ca2efde - languageName: node - linkType: hard - "@hapi/hoek@npm:^9.0.0, @hapi/hoek@npm:^9.3.0": version: 9.3.0 resolution: "@hapi/hoek@npm:9.3.0" @@ -9970,7 +9963,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^8.0.0, ajv@npm:^8.0.1, ajv@npm:^8.6.3, ajv@npm:^8.9.0": +"ajv@npm:^8.0.0, ajv@npm:^8.0.1, ajv@npm:^8.9.0": version: 8.17.1 resolution: "ajv@npm:8.17.1" dependencies: @@ -11252,7 +11245,7 @@ __metadata: languageName: node linkType: hard -"bluebird@npm:3.7.2, bluebird@npm:^3.1.1, bluebird@npm:^3.4.7, bluebird@npm:^3.5.1, bluebird@npm:^3.5.4, bluebird@npm:^3.5.5, bluebird@npm:^3.7.2": +"bluebird@npm:3.7.2, bluebird@npm:^3.1.1, bluebird@npm:^3.4.7, bluebird@npm:^3.5.1, bluebird@npm:^3.5.5, bluebird@npm:^3.7.2": version: 3.7.2 resolution: "bluebird@npm:3.7.2" checksum: 869417503c722e7dc54ca46715f70e15f4d9c602a423a02c825570862d12935be59ed9c7ba34a9b31f186c017c23cac6b54e35446f8353059c101da73eac22ef @@ -11359,13 +11352,6 @@ __metadata: languageName: node linkType: hard -"browser-process-hrtime@npm:^1.0.0": - version: 1.0.0 - resolution: "browser-process-hrtime@npm:1.0.0" - checksum: e30f868cdb770b1201afb714ad1575dd86366b6e861900884665fb627109b3cc757c40067d3bfee1ff2a29c835257ea30725a8018a9afd02ac1c24b408b1e45f - languageName: node - linkType: hard - "browserslist@npm:^4.23.1, browserslist@npm:^4.23.3": version: 4.23.3 resolution: "browserslist@npm:4.23.3" @@ -11491,87 +11477,6 @@ __metadata: languageName: node linkType: hard -"bunyamin@npm:^1.5.2": - version: 1.6.3 - resolution: "bunyamin@npm:1.6.3" - dependencies: - "@flatten-js/interval-tree": ^1.1.2 - multi-sort-stream: ^1.0.4 - stream-json: ^1.7.5 - trace-event-lib: ^1.3.1 - peerDependencies: - "@types/bunyan": ^1.8.8 - bunyan: ^1.8.15 || ^2.0.0 - peerDependenciesMeta: - "@types/bunyan": - optional: true - bunyan: - optional: true - checksum: 3422db179c2f1d9581740b18de79c925e2ab25ee49ea5e66a5b66db16372d6f641927de55010c997050049d9e9569f4b720d409ffa0a573ded86aef5d49768eb - languageName: node - linkType: hard - -"bunyan-debug-stream@npm:^3.1.0": - version: 3.1.1 - resolution: "bunyan-debug-stream@npm:3.1.1" - dependencies: - chalk: ^4.1.2 - peerDependencies: - bunyan: "*" - peerDependenciesMeta: - bunyan: - optional: true - checksum: e0dd2c42de27857bd7c70b600ac30ecf7ef5efe7837c6ea2d87b98e48c7cd16a4fcce1d08439d9fc5dbff2d672b191357ea579750c9cd6379703109f5077bca4 - languageName: node - linkType: hard - -"bunyan@npm:^1.8.12": - version: 1.8.15 - resolution: "bunyan@npm:1.8.15" - dependencies: - dtrace-provider: ~0.8 - moment: ^2.19.3 - mv: ~2 - safe-json-stringify: ~1 - dependenciesMeta: - dtrace-provider: - optional: true - moment: - optional: true - mv: - optional: true - safe-json-stringify: - optional: true - bin: - bunyan: bin/bunyan - checksum: a479e0787c3a0b6565b54bd15f0b6c729d624c5aba53523e140e49e279b7a78508df93000e758bf6d02361117d6b4e6e5fc1d5ece05366fb6c4ba41bf1ac7d52 - languageName: node - linkType: hard - -"bunyan@npm:^2.0.5": - version: 2.0.5 - resolution: "bunyan@npm:2.0.5" - dependencies: - dtrace-provider: ~0.8 - exeunt: 1.1.0 - moment: ^2.19.3 - mv: ~2 - safe-json-stringify: ~1 - dependenciesMeta: - dtrace-provider: - optional: true - moment: - optional: true - mv: - optional: true - safe-json-stringify: - optional: true - bin: - bunyan: bin/bunyan - checksum: a932e883387e5bef23eee0f1f9af94e8b885da32492eaf7164dc58e3b42e5a65845068beb7ac8fbcff31511a55728c1a826bf48ba3e4edd7e220ebf0fe2ab989 - languageName: node - linkType: hard - "byte-size@npm:8.1.1": version: 8.1.1 resolution: "byte-size@npm:8.1.1" @@ -11635,13 +11540,6 @@ __metadata: languageName: node linkType: hard -"caf@npm:^15.0.1": - version: 15.0.1 - resolution: "caf@npm:15.0.1" - checksum: 832cc5d3a6053efb458ed1c1f5e5d3ebbc7710f2275f033c6362dcfd1565f15e29dbee15fa0f3301ecb5c4dbdc753c070b5a4a6d3dc8e246cb784cb26c601e8b - languageName: node - linkType: hard - "call-bind@npm:^1.0.2, call-bind@npm:^1.0.5, call-bind@npm:^1.0.6, call-bind@npm:^1.0.7": version: 1.0.7 resolution: "call-bind@npm:1.0.7" @@ -11816,17 +11714,6 @@ __metadata: languageName: node linkType: hard -"child-process-promise@npm:^2.2.0": - version: 2.2.1 - resolution: "child-process-promise@npm:2.2.1" - dependencies: - cross-spawn: ^4.0.2 - node-version: ^1.0.0 - promise-polyfill: ^6.0.1 - checksum: fb72dda7ee78099f106d57bf3d7cc3225c16c9ddfe8e364e3535a52396482ee81aecd3eff0da7131ca17b7ba9fcbb8af827da63a03f0c3262c76268696898642 - languageName: node - linkType: hard - "chokidar@npm:^3.4.0": version: 3.6.0 resolution: "chokidar@npm:3.6.0" @@ -12692,16 +12579,6 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:^4.0.2": - version: 4.0.2 - resolution: "cross-spawn@npm:4.0.2" - dependencies: - lru-cache: ^4.0.1 - which: ^1.2.9 - checksum: 8ce57b3e11c5c798542a21ddfdc1edef33ab6fe001958b31f3340a6ff684e3334a8baad2751efa78b6200aad442cf12b939396d758b0dd5c42c9b782c28fe06e - languageName: node - linkType: hard - "cross-spawn@npm:^6.0.0": version: 6.0.6 resolution: "cross-spawn@npm:6.0.6" @@ -13005,13 +12882,6 @@ __metadata: languageName: node linkType: hard -"decamelize@npm:^4.0.0": - version: 4.0.0 - resolution: "decamelize@npm:4.0.0" - checksum: b7d09b82652c39eead4d6678bb578e3bebd848add894b76d0f6b395bc45b2d692fb88d977e7cfb93c4ed6c119b05a1347cef261174916c2e75c0a8ca57da1809 - languageName: node - linkType: hard - "decamelize@npm:^6.0.0": version: 6.0.0 resolution: "decamelize@npm:6.0.0" @@ -13475,64 +13345,6 @@ __metadata: languageName: node linkType: hard -"detox-copilot@npm:^0.0.27": - version: 0.0.27 - resolution: "detox-copilot@npm:0.0.27" - checksum: 4f01ed1f21fe3128ee50037b63085fe95ccdc9e723c6b034d53720fa325123e39d4f83d18b1ab88a11a679258b0ff734e74f0738118e260f10945fadbe205443 - languageName: node - linkType: hard - -"detox@npm:^20.33.0": - version: 20.33.0 - resolution: "detox@npm:20.33.0" - dependencies: - ajv: ^8.6.3 - bunyan: ^1.8.12 - bunyan-debug-stream: ^3.1.0 - caf: ^15.0.1 - chalk: ^4.0.0 - child-process-promise: ^2.2.0 - detox-copilot: ^0.0.27 - execa: ^5.1.1 - find-up: ^5.0.0 - fs-extra: ^11.0.0 - funpermaproxy: ^1.1.0 - glob: ^8.0.3 - ini: ^1.3.4 - jest-environment-emit: ^1.0.8 - json-cycle: ^1.3.0 - lodash: ^4.17.11 - multi-sort-stream: ^1.0.3 - multipipe: ^4.0.0 - node-ipc: 9.2.1 - proper-lockfile: ^3.0.2 - resolve-from: ^5.0.0 - sanitize-filename: ^1.6.1 - semver: ^7.0.0 - serialize-error: ^8.0.1 - shell-quote: ^1.7.2 - signal-exit: ^3.0.3 - stream-json: ^1.7.4 - strip-ansi: ^6.0.1 - telnet-client: 1.2.8 - tempfile: ^2.0.0 - trace-event-lib: ^1.3.1 - which: ^1.3.1 - ws: ^7.0.0 - yargs: ^17.0.0 - yargs-parser: ^21.0.0 - yargs-unparser: ^2.0.0 - peerDependencies: - jest: 29.x.x || 28.x.x || ^27.2.5 - peerDependenciesMeta: - jest: - optional: true - bin: - detox: local-cli/cli.js - checksum: 14a9a230f02c6e7e535e96223a9aacbef05c06c20887eac3d1f1df1aca612a1c529f94e265064df39d1c98ee700fbc2a26ebdd18affea5e60f17a527ae42d6e0 - languageName: node - linkType: hard - "devtools-protocol@npm:0.0.1232444": version: 0.0.1232444 resolution: "devtools-protocol@npm:0.0.1232444" @@ -13687,25 +13499,6 @@ __metadata: languageName: node linkType: hard -"dtrace-provider@npm:~0.8": - version: 0.8.8 - resolution: "dtrace-provider@npm:0.8.8" - dependencies: - nan: ^2.14.0 - node-gyp: latest - checksum: f2dc89df6a9c443dc9bae3b53496e0685b5da89142951d451c1ce062c75d96698ffc0b3d90f621a59a6a18578be552378ad4e08210759038910ff2080be556b9 - languageName: node - linkType: hard - -"duplexer2@npm:^0.1.2": - version: 0.1.4 - resolution: "duplexer2@npm:0.1.4" - dependencies: - readable-stream: ^2.0.2 - checksum: 744961f03c7f54313f90555ac20284a3fb7bf22fdff6538f041a86c22499560eb6eac9d30ab5768054137cb40e6b18b40f621094e0261d7d8c35a37b7a5ad241 - languageName: node - linkType: hard - "duplexer@npm:^0.1.1, duplexer@npm:~0.1.1": version: 0.1.2 resolution: "duplexer@npm:0.1.2" @@ -13720,13 +13513,6 @@ __metadata: languageName: node linkType: hard -"easy-stack@npm:^1.0.1": - version: 1.0.1 - resolution: "easy-stack@npm:1.0.1" - checksum: 161a99e497b3857b0be4ec9e1ebbe90b241ea9d84702f9881b8e5b3f6822065b8c4e33436996935103e191bffba3607de70712a792f4d406a050def48c6bc381 - languageName: node - linkType: hard - "edge-paths@npm:^3.0.5": version: 3.0.5 resolution: "edge-paths@npm:3.0.5" @@ -14734,13 +14520,6 @@ __metadata: languageName: node linkType: hard -"event-pubsub@npm:4.3.0": - version: 4.3.0 - resolution: "event-pubsub@npm:4.3.0" - checksum: 6940f57790c01a967b7c637f1c9fd000ee968a1d5894186ffb3356ffbe174c70e22e62adbbcfcee3f305482d99b6abe7613c1c27c909b07adc9127dc16c8cf73 - languageName: node - linkType: hard - "event-target-shim@npm:^5.0.0, event-target-shim@npm:^5.0.1": version: 5.0.1 resolution: "event-target-shim@npm:5.0.1" @@ -14818,13 +14597,6 @@ __metadata: languageName: node linkType: hard -"exeunt@npm:1.1.0": - version: 1.1.0 - resolution: "exeunt@npm:1.1.0" - checksum: c0054fa49d7b3abbc2acecd4c6e34c6ce3a0370f9c31d18cdf64dad6be9a6d3fb84d93be892b7d1906f3f23051b3855bde7b255129fc49605a04392f69e98ea2 - languageName: node - linkType: hard - "exit@npm:^0.1.2": version: 0.1.2 resolution: "exit@npm:0.1.2" @@ -15720,17 +15492,6 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:^11.0.0": - version: 11.3.0 - resolution: "fs-extra@npm:11.3.0" - dependencies: - graceful-fs: ^4.2.0 - jsonfile: ^6.0.1 - universalify: ^2.0.0 - checksum: f983c706e0c22b0c0747a8e9c76aed6f391ba2d76734cf2757cd84da13417b402ed68fe25bace65228856c61d36d3b41da198f1ffbf33d0b34283a2f7a62c6e9 - languageName: node - linkType: hard - "fs-extra@npm:^11.1.0, fs-extra@npm:^11.2.0": version: 11.2.0 resolution: "fs-extra@npm:11.2.0" @@ -15858,13 +15619,6 @@ __metadata: languageName: node linkType: hard -"funpermaproxy@npm:^1.1.0": - version: 1.1.0 - resolution: "funpermaproxy@npm:1.1.0" - checksum: 74cf0aafeadbd79053324f1fb981c1a4358618722ad01c65bd1466b42498fd07acb7749ab9224b25fc8e81c2e1283b92ceee61dded265bd7527b225351db998b - languageName: node - linkType: hard - "gauge@npm:^5.0.0": version: 5.0.2 resolution: "gauge@npm:5.0.2" @@ -16229,7 +15983,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^8.0.3, glob@npm:^8.1.0": +"glob@npm:^8.1.0": version: 8.1.0 resolution: "glob@npm:8.1.0" dependencies: @@ -17348,13 +17102,6 @@ __metadata: languageName: node linkType: hard -"is-plain-obj@npm:^2.1.0": - version: 2.1.0 - resolution: "is-plain-obj@npm:2.1.0" - checksum: cec9100678b0a9fe0248a81743041ed990c2d4c99f893d935545cfbc42876cbe86d207f3b895700c690ad2fa520e568c44afc1605044b535a7820c1d40e38daa - languageName: node - linkType: hard - "is-plain-obj@npm:^4.1.0": version: 4.1.0 resolution: "is-plain-obj@npm:4.1.0" @@ -17845,39 +17592,6 @@ __metadata: languageName: node linkType: hard -"jest-environment-emit@npm:^1.0.8": - version: 1.0.8 - resolution: "jest-environment-emit@npm:1.0.8" - dependencies: - bunyamin: ^1.5.2 - bunyan: ^2.0.5 - bunyan-debug-stream: ^3.1.0 - funpermaproxy: ^1.1.0 - lodash.merge: ^4.6.2 - node-ipc: 9.2.1 - strip-ansi: ^6.0.0 - tslib: ^2.5.3 - peerDependencies: - "@jest/environment": ">=27.2.5" - "@jest/types": ">=27.2.5" - jest: ">=27.2.5" - jest-environment-jsdom: ">=27.2.5" - jest-environment-node: ">=27.2.5" - peerDependenciesMeta: - "@jest/environment": - optional: true - "@jest/types": - optional: true - jest: - optional: true - jest-environment-jsdom: - optional: true - jest-environment-node: - optional: true - checksum: 0c7bafbd3a6e5952f6abb45958f0d2997371d29b29f3876afda48d1d734ccd703577aaac0d5afec2e19dc33a9db0e9458721fe73dbe797f0ced21481d908acfd - languageName: node - linkType: hard - "jest-environment-jsdom@npm:^29.2.1, jest-environment-jsdom@npm:^29.6.2": version: 29.7.0 resolution: "jest-environment-jsdom@npm:29.7.0" @@ -18380,22 +18094,6 @@ __metadata: languageName: node linkType: hard -"js-message@npm:1.0.7": - version: 1.0.7 - resolution: "js-message@npm:1.0.7" - checksum: 18dcc4d80356e8b5be978ca7838d96d4e350a1cb8adc5741c229dec6df09f53bfed7c75c1f360171d2d791a14e2f077d6c2b1013ba899ded7a27d7dfcd4f3784 - languageName: node - linkType: hard - -"js-queue@npm:2.0.2": - version: 2.0.2 - resolution: "js-queue@npm:2.0.2" - dependencies: - easy-stack: ^1.0.1 - checksum: 5049c3f648315ed13e46755704ff5453df70f7e8e1812acf1f98d6700efbec32421f76294a0e63fd2a9f8aabaf124233bbb308f9a2caec9d9f3d833ab5a73079 - languageName: node - linkType: hard - "js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" @@ -18621,13 +18319,6 @@ __metadata: languageName: node linkType: hard -"json-cycle@npm:^1.3.0": - version: 1.5.0 - resolution: "json-cycle@npm:1.5.0" - checksum: 0a44cd349676c6726093c64283fb75402f9104b32325b06c9270af6d639e7caac419f5301a39298aef2ac1659b273b167e02bd622e628c3392cf86f0e77a9f78 - languageName: node - linkType: hard - "json-parse-better-errors@npm:^1.0.1": version: 1.0.2 resolution: "json-parse-better-errors@npm:1.0.2" @@ -19460,16 +19151,6 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^4.0.1": - version: 4.1.5 - resolution: "lru-cache@npm:4.1.5" - dependencies: - pseudomap: ^1.0.2 - yallist: ^2.1.2 - checksum: 4bb4b58a36cd7dc4dcec74cbe6a8f766a38b7426f1ff59d4cf7d82a2aa9b9565cd1cb98f6ff60ce5cd174524868d7bc9b7b1c294371851356066ca9ac4cf135a - languageName: node - linkType: hard - "lru-cache@npm:^5.1.1": version: 5.1.1 resolution: "lru-cache@npm:5.1.1" @@ -20923,7 +20604,7 @@ __metadata: languageName: node linkType: hard -"moment@npm:2.30.1, moment@npm:^2.19.3, moment@npm:^2.24.0, moment@npm:^2.29.4": +"moment@npm:2.30.1, moment@npm:^2.24.0, moment@npm:^2.29.4": version: 2.30.1 resolution: "moment@npm:2.30.1" checksum: 859236bab1e88c3e5802afcf797fc801acdbd0ee509d34ea3df6eea21eb6bcc2abd4ae4e4e64aa7c986aa6cba563c6e62806218e6412a765010712e5fa121ba6 @@ -20978,13 +20659,6 @@ __metadata: languageName: node linkType: hard -"multi-sort-stream@npm:^1.0.3, multi-sort-stream@npm:^1.0.4": - version: 1.0.4 - resolution: "multi-sort-stream@npm:1.0.4" - checksum: b234754e0e7489623f5184ba0e887ffd8014fe829c846fd8a95569339b6e19a616ae1d44f3d064279adfbf92fa5c4d016a89fc5026e16dbd680ebd67067b19a0 - languageName: node - linkType: hard - "multimatch@npm:5.0.0": version: 5.0.0 resolution: "multimatch@npm:5.0.0" @@ -20998,16 +20672,6 @@ __metadata: languageName: node linkType: hard -"multipipe@npm:^4.0.0": - version: 4.0.0 - resolution: "multipipe@npm:4.0.0" - dependencies: - duplexer2: ^0.1.2 - object-assign: ^4.1.0 - checksum: 5a494ec2ce5bfdb389882ca595e3c4a33cae6c90dad879db2e3aa9a94484d8b164b0fb7b58ccf7593ae7e8c6213fd3f53a736b2c98e4f14c5ed1d38debc33f98 - languageName: node - linkType: hard - "mute-stream@npm:0.0.7": version: 0.0.7 resolution: "mute-stream@npm:0.0.7" @@ -21029,7 +20693,7 @@ __metadata: languageName: node linkType: hard -"mv@npm:2.1.1, mv@npm:~2": +"mv@npm:2.1.1": version: 2.1.1 resolution: "mv@npm:2.1.1" dependencies: @@ -21051,15 +20715,6 @@ __metadata: languageName: node linkType: hard -"nan@npm:^2.14.0": - version: 2.22.0 - resolution: "nan@npm:2.22.0" - dependencies: - node-gyp: latest - checksum: 222e3a090e326c72f6782d948f44ee9b81cfb2161d5fe53216f04426a273fd094deee9dcc6813096dd2397689a2b10c1a92d3885d2e73fd2488a51547beb2929 - languageName: node - linkType: hard - "nanoid@npm:3.3.7, nanoid@npm:^3.1.23, nanoid@npm:^3.3.7": version: 3.3.7 resolution: "nanoid@npm:3.3.7" @@ -21246,17 +20901,6 @@ __metadata: languageName: node linkType: hard -"node-ipc@npm:9.2.1": - version: 9.2.1 - resolution: "node-ipc@npm:9.2.1" - dependencies: - event-pubsub: 4.3.0 - js-message: 1.0.7 - js-queue: 2.0.2 - checksum: a38aa4c8ca4317b293e0ce21f0a3a4941fc51c054800b35e263fcfe3e0feeb60e7d2c497f015054b28783316c6e7d9cc3837af9d9958bcbd8c577d0cdf6964b7 - languageName: node - linkType: hard - "node-java-connector@npm:1.1.1": version: 1.1.1 resolution: "node-java-connector@npm:1.1.1" @@ -21327,13 +20971,6 @@ __metadata: languageName: node linkType: hard -"node-version@npm:^1.0.0": - version: 1.2.0 - resolution: "node-version@npm:1.2.0" - checksum: 74e92d2a7f0fe0fce3aafd6dcc30b3b440999df68b3d92fcefcad2a52b37bc29c6b542f33760229390bfdc1a4d993fb65b9c199b1f0d568969d07fc1c04bc1e7 - languageName: node - linkType: hard - "nopt@npm:^7.0.0, nopt@npm:^7.2.0, nopt@npm:^7.2.1": version: 7.2.1 resolution: "nopt@npm:7.2.1" @@ -22793,13 +22430,6 @@ __metadata: languageName: node linkType: hard -"promise-polyfill@npm:^6.0.1": - version: 6.1.0 - resolution: "promise-polyfill@npm:6.1.0" - checksum: 6f1899cca37e48f67a424842282acd525d8d99d3536f2d97e37a117cfc4a0006683330ceaf5a15fbc09b4450f319a680292f9970a5f8e9cf90acbce0bdb0f751 - languageName: node - linkType: hard - "promise-retry@npm:^2.0.1": version: 2.0.1 resolution: "promise-retry@npm:2.0.1" @@ -22858,17 +22488,6 @@ __metadata: languageName: node linkType: hard -"proper-lockfile@npm:^3.0.2": - version: 3.2.0 - resolution: "proper-lockfile@npm:3.2.0" - dependencies: - graceful-fs: ^4.1.11 - retry: ^0.12.0 - signal-exit: ^3.0.2 - checksum: 1be1bb702b9d47bdf18d75f22578f51370781feba7d2617f70ff8c66a86bcfa6e55b4f69c57fc326380110f2d1ffdb6e54a4900814bf156c04ee4eb2d3c065aa - languageName: node - linkType: hard - "proto-list@npm:~1.2.1": version: 1.2.4 resolution: "proto-list@npm:1.2.4" @@ -22916,13 +22535,6 @@ __metadata: languageName: node linkType: hard -"pseudomap@npm:^1.0.2": - version: 1.0.2 - resolution: "pseudomap@npm:1.0.2" - checksum: 856c0aae0ff2ad60881168334448e898ad7a0e45fe7386d114b150084254c01e200c957cf378378025df4e052c7890c5bd933939b0e0d2ecfcc1dc2f0b2991f5 - languageName: node - linkType: hard - "psl@npm:^1.1.33": version: 1.9.0 resolution: "psl@npm:1.9.0" @@ -23312,6 +22924,16 @@ __metadata: languageName: node linkType: hard +"react-native-launch-arguments@npm:^4.1.0": + version: 4.1.0 + resolution: "react-native-launch-arguments@npm:4.1.0" + peerDependencies: + react: ">=16.8.1" + react-native: ">=0.60.0-rc.0 <1.0.x" + checksum: 719b6cbfb0bb77152b94f0f96c30a90847b9ade4908936435d2c2024e81d2538968a9225457d443f84513e72487565c7b31472307db37292640ffb2a77315e3e + languageName: node + linkType: hard + "react-native-macos@npm:0.73.34": version: 0.73.34 resolution: "react-native-macos@npm:0.73.34" @@ -23990,7 +23612,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^2.0.1, readable-stream@npm:^2.0.2, readable-stream@npm:^2.0.5, readable-stream@npm:~2.3.6": +"readable-stream@npm:^2.0.1, readable-stream@npm:^2.0.5, readable-stream@npm:~2.3.6": version: 2.3.8 resolution: "readable-stream@npm:2.3.8" dependencies: @@ -24682,13 +24304,6 @@ __metadata: languageName: node linkType: hard -"safe-json-stringify@npm:~1": - version: 1.2.0 - resolution: "safe-json-stringify@npm:1.2.0" - checksum: 5bb32db6d6a3ceb3752df51f4043a412419cd3d4fcd5680a865dfa34cd7e575ba659c077d13f52981ced084061df9c75c7fb12e391584d4264e6914c1cd3d216 - languageName: node - linkType: hard - "safe-regex-test@npm:^1.0.3": version: 1.0.3 resolution: "safe-regex-test@npm:1.0.3" @@ -24714,7 +24329,7 @@ __metadata: languageName: node linkType: hard -"sanitize-filename@npm:1.6.3, sanitize-filename@npm:^1.6.1": +"sanitize-filename@npm:1.6.3": version: 1.6.3 resolution: "sanitize-filename@npm:1.6.3" dependencies: @@ -25047,6 +24662,7 @@ __metadata: "@react-navigation/native-stack": ^7.2.0 "@react-navigation/stack": ^7.1.1 "@sentry/babel-plugin-component-annotate": 3.2.1 + "@sentry/core": 8.54.0 "@sentry/react-native": 6.9.0 "@types/jest": ^29.5.14 "@types/node": ^22.13.1 @@ -25058,7 +24674,6 @@ __metadata: babel-jest: ^29.6.3 babel-plugin-module-resolver: ^5.0.0 delay: ^6.0.0 - detox: ^20.33.0 eslint: ^8.19.0 eslint-plugin-ft-flow: ^3.0.11 jest: ^29.6.3 @@ -25068,6 +24683,7 @@ __metadata: react-native: 0.77.1 react-native-gesture-handler: ^2.22.1 react-native-image-picker: ^8.0.0 + react-native-launch-arguments: ^4.1.0 react-native-reanimated: 3.16.7 react-native-safe-area-context: 5.2.0 react-native-screens: 4.6.0 @@ -25132,15 +24748,6 @@ __metadata: languageName: node linkType: hard -"serialize-error@npm:^8.0.1": - version: 8.1.0 - resolution: "serialize-error@npm:8.1.0" - dependencies: - type-fest: ^0.20.2 - checksum: 2eef236d50edd2d7926e602c14fb500dc3a125ee52e9f08f67033181b8e0be5d1122498bdf7c23c80683cddcad083a27974e9e7111ce23165f4d3bcdd6d65102 - languageName: node - linkType: hard - "serve-favicon@npm:2.5.0": version: 2.5.0 resolution: "serve-favicon@npm:2.5.0" @@ -25363,13 +24970,6 @@ __metadata: languageName: node linkType: hard -"shell-quote@npm:^1.7.2": - version: 1.8.2 - resolution: "shell-quote@npm:1.8.2" - checksum: 1e97b62ced1c4c5135015978ebf273bed1f425a68cf84163e83fbb0f34b3ff9471e656720dab2b7cbb4ae0f58998e686d17d166c28dfb3662acd009e8bd7faed - languageName: node - linkType: hard - "shelljs@npm:^0.8.3": version: 0.8.5 resolution: "shelljs@npm:0.8.5" @@ -25798,13 +25398,6 @@ __metadata: languageName: node linkType: hard -"stream-chain@npm:^2.2.5": - version: 2.2.5 - resolution: "stream-chain@npm:2.2.5" - checksum: c83cbf504bd11e2bcbe761a92801295b3decac7ffa4092ceffca2eb1b5d0763bcc511fa22cd8044e8a18c21ca66794fd10c8d9cd1292a3e6c0d83a4194c6b8ed - languageName: node - linkType: hard - "stream-combiner@npm:^0.2.2": version: 0.2.2 resolution: "stream-combiner@npm:0.2.2" @@ -25815,15 +25408,6 @@ __metadata: languageName: node linkType: hard -"stream-json@npm:^1.7.4, stream-json@npm:^1.7.5": - version: 1.9.1 - resolution: "stream-json@npm:1.9.1" - dependencies: - stream-chain: ^2.2.5 - checksum: 2ebf0648f9ed82ee79727a9a47805231a70d5032e0c21cee3e05cd3c449d3ce49c72b371555447eeef55904bae22ac64be8ae6086fc6cce0b83b3aa617736b64 - languageName: node - linkType: hard - "stream-slice@npm:^0.1.2": version: 0.1.2 resolution: "stream-slice@npm:0.1.2" @@ -26401,16 +25985,7 @@ __metadata: languageName: node linkType: hard -"telnet-client@npm:1.2.8": - version: 1.2.8 - resolution: "telnet-client@npm:1.2.8" - dependencies: - bluebird: ^3.5.4 - checksum: d2430c5449a46f6f4f9a7c2c648164f014c308aa0d3207a4d6b5b7f0e443322d07b180ecac63ad43eadb6557c8ef5ae7dce1ea6276464c8c82c8c6a9c9c01bf2 - languageName: node - linkType: hard - -"temp-dir@npm:1.0.0, temp-dir@npm:^1.0.0": +"temp-dir@npm:1.0.0": version: 1.0.0 resolution: "temp-dir@npm:1.0.0" checksum: cb2b58ddfb12efa83e939091386ad73b425c9a8487ea0095fe4653192a40d49184a771a1beba99045fbd011e389fd563122d79f54f82be86a55620667e08a6b2 @@ -26443,16 +26018,6 @@ __metadata: languageName: node linkType: hard -"tempfile@npm:^2.0.0": - version: 2.0.0 - resolution: "tempfile@npm:2.0.0" - dependencies: - temp-dir: ^1.0.0 - uuid: ^3.0.1 - checksum: 8a92a0f57e0ae457dfbc156b14c427b42048a86ca6bade311835cc2aeda61b25b82d688f71f2d663dde6f172f479ed07293b53f7981e41cb6f9120a3eb4fe797 - languageName: node - linkType: hard - "tempy@npm:^0.7.1": version: 0.7.1 resolution: "tempy@npm:0.7.1" @@ -26654,15 +26219,6 @@ __metadata: languageName: node linkType: hard -"trace-event-lib@npm:^1.3.1": - version: 1.4.1 - resolution: "trace-event-lib@npm:1.4.1" - dependencies: - browser-process-hrtime: ^1.0.0 - checksum: f10dbfeccee9ec80a8cf69ecadd49fa609fc2593fb50a83cc4b664524c0531f91009134bf54302f9c4911afed119b0eebb8d2724723fc44516e24a40aaae9219 - languageName: node - linkType: hard - "treeverse@npm:^3.0.0": version: 3.0.0 resolution: "treeverse@npm:3.0.0" @@ -26844,13 +26400,6 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.5.3": - version: 2.8.1 - resolution: "tslib@npm:2.8.1" - checksum: e4aba30e632b8c8902b47587fd13345e2827fa639e7c3121074d5ee0880723282411a8838f830b55100cbe4517672f84a2472667d355b81e8af165a55dc6203a - languageName: node - linkType: hard - "tsutils@npm:^3.21.0": version: 3.21.0 resolution: "tsutils@npm:3.21.0" @@ -27500,15 +27049,6 @@ __metadata: languageName: node linkType: hard -"uuid@npm:^3.0.1": - version: 3.4.0 - resolution: "uuid@npm:3.4.0" - bin: - uuid: ./bin/uuid - checksum: 58de2feed61c59060b40f8203c0e4ed7fd6f99d42534a499f1741218a1dd0c129f4aa1de797bcf822c8ea5da7e4137aa3673431a96dae729047f7aca7b27866f - languageName: node - linkType: hard - "uuid@npm:^7.0.3": version: 7.0.3 resolution: "uuid@npm:7.0.3" @@ -27881,7 +27421,7 @@ __metadata: languageName: node linkType: hard -"which@npm:^1.2.9, which@npm:^1.3.1": +"which@npm:^1.2.9": version: 1.3.1 resolution: "which@npm:1.3.1" dependencies: @@ -28099,7 +27639,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:^7, ws@npm:^7.0.0, ws@npm:^7.5.1, ws@npm:^7.5.10": +"ws@npm:^7, ws@npm:^7.5.1, ws@npm:^7.5.10": version: 7.5.10 resolution: "ws@npm:7.5.10" peerDependencies: @@ -28230,13 +27770,6 @@ __metadata: languageName: node linkType: hard -"yallist@npm:^2.1.2": - version: 2.1.2 - resolution: "yallist@npm:2.1.2" - checksum: 9ba99409209f485b6fcb970330908a6d41fa1c933f75e08250316cce19383179a6b70a7e0721b89672ebb6199cc377bf3e432f55100da6a7d6e11902b0a642cb - languageName: node - linkType: hard - "yallist@npm:^3.0.2": version: 3.1.1 resolution: "yallist@npm:3.1.1" @@ -28267,7 +27800,7 @@ __metadata: languageName: node linkType: hard -"yargs-parser@npm:21.1.1, yargs-parser@npm:^21.0.0, yargs-parser@npm:^21.0.1, yargs-parser@npm:^21.1.1": +"yargs-parser@npm:21.1.1, yargs-parser@npm:^21.0.1, yargs-parser@npm:^21.1.1": version: 21.1.1 resolution: "yargs-parser@npm:21.1.1" checksum: ed2d96a616a9e3e1cc7d204c62ecc61f7aaab633dcbfab2c6df50f7f87b393993fe6640d017759fe112d0cb1e0119f2b4150a87305cc873fd90831c6a58ccf1c @@ -28291,19 +27824,7 @@ __metadata: languageName: node linkType: hard -"yargs-unparser@npm:^2.0.0": - version: 2.0.0 - resolution: "yargs-unparser@npm:2.0.0" - dependencies: - camelcase: ^6.0.0 - decamelize: ^4.0.0 - flat: ^5.0.2 - is-plain-obj: ^2.1.0 - checksum: 68f9a542c6927c3768c2f16c28f71b19008710abd6b8f8efbac6dcce26bbb68ab6503bed1d5994bdbc2df9a5c87c161110c1dfe04c6a3fe5c6ad1b0e15d9a8a3 - languageName: node - linkType: hard - -"yargs@npm:17.7.2, yargs@npm:^17.0.0, yargs@npm:^17.3.1, yargs@npm:^17.6.2, yargs@npm:^17.7.2": +"yargs@npm:17.7.2, yargs@npm:^17.3.1, yargs@npm:^17.6.2, yargs@npm:^17.7.2": version: 17.7.2 resolution: "yargs@npm:17.7.2" dependencies: From 158bcac67b9066c4ee3540ef427eff70eb7b8069 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Tue, 4 Mar 2025 15:30:35 +0100 Subject: [PATCH 06/16] Add test scripts --- .github/workflows/sample-application.yml | 8 +++--- samples/react-native/e2e/.gitignore | 1 + .../e2e/captureTransaction.test.yml | 4 +-- samples/react-native/package.json | 4 +-- samples/react-native/scripts/test-android.sh | 25 +++++++++++++++++++ samples/react-native/scripts/test-ios.sh | 24 ++++++++++++++++++ 6 files changed, 57 insertions(+), 9 deletions(-) create mode 100644 samples/react-native/e2e/.gitignore 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 e4e9a8f8b3..cffe088793 100644 --- a/.github/workflows/sample-application.yml +++ b/.github/workflows/sample-application.yml @@ -181,11 +181,9 @@ jobs: 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 \ - app-androidTest.apk + app.apk - name: Upload iOS APP if: ${{ matrix.platform == 'ios' && matrix.rn-architecture == 'new' && matrix.build-type == 'production' && matrix.ios-use-frameworks == 'no-frameworks' }} @@ -302,7 +300,7 @@ 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' }} @@ -327,4 +325,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/e2e/.gitignore b/samples/react-native/e2e/.gitignore new file mode 100644 index 0000000000..6722cd96e7 --- /dev/null +++ b/samples/react-native/e2e/.gitignore @@ -0,0 +1 @@ +*.xml diff --git a/samples/react-native/e2e/captureTransaction.test.yml b/samples/react-native/e2e/captureTransaction.test.yml index 741b26f80d..9f59b1155a 100644 --- a/samples/react-native/e2e/captureTransaction.test.yml +++ b/samples/react-native/e2e/captureTransaction.test.yml @@ -1,10 +1,10 @@ appId: io.sentry.reactnative.sample --- -- launchApp +- launchApp: # We expect cold start clearState: true stopApp: true - launchArgs: + arguments: isE2ETest: true # For unknown reasons tapOn: "Performance" does not work on iOS diff --git a/samples/react-native/package.json b/samples/react-native/package.json index 8bfa916021..623cea1b79 100644 --- a/samples/react-native/package.json +++ b/samples/react-native/package.json @@ -8,8 +8,8 @@ "ios": "react-native run-ios", "start": "react-native start", "test": "jest", - "test-android": "jest --config e2e/jest.config.js", - "test-ios": "jest --config e2e/jest.config.js", + "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 ..", diff --git a/samples/react-native/scripts/test-android.sh b/samples/react-native/scripts/test-android.sh new file mode 100755 index 0000000000..b2a34abd82 --- /dev/null +++ b/samples/react-native/scripts/test-android.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Get current directory +thisFileDirPath=$(dirname "$0") +reactProjectRootPath="$(cd "$thisFileDirPath/.." && pwd)" + +maybeApkPath="${reactProjectRootPath}/*.apk" + +# Check if any APK files exist +apk_count=$(ls -1 "${maybeApkPath}" 2>/dev/null | wc -l) + +if [ $apk_count -eq 1 ]; then + # Force install single APK using adb + apk_file=$(ls "${maybeApkPath}") + echo "Installing $apk_file..." + adb install -r "$apk_file" +elif [ $apk_count -gt 1 ]; then + echo "Error: Multiple APK files found. Expected only one APK file." + exit 1 +else + echo "No APK files found, continuing without install" +fi + +# Run the tests +npx jest --config e2e/jest.config.js diff --git a/samples/react-native/scripts/test-ios.sh b/samples/react-native/scripts/test-ios.sh new file mode 100755 index 0000000000..047bd8c7de --- /dev/null +++ b/samples/react-native/scripts/test-ios.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# Get current directory +thisFileDirPath=$(dirname "$0") +reactProjectRootPath="$(cd "$thisFileDirPath/.." && pwd)" + +maybeAppPath="${reactProjectRootPath}/*.app" + +# Check if any APP files exist +app_count=$(ls -1 "${maybeAppPath}" 2>/dev/null | wc -l) + +if [ $app_count -eq 1 ]; then + app_file=$(ls "${maybeAppPath}") + echo "Installing $app_file..." + xcrun simctl install booted "$app_file" +elif [ $app_count -gt 1 ]; then + echo "Error: Multiple APP files found. Expected only one APP file." + exit 1 +else + echo "No APP files found, continuing without install" +fi + +# Run the tests +npx jest --config e2e/jest.config.js From d9f58041e60f206a0acf1d08007579e38f53c9b6 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Wed, 5 Mar 2025 19:44:01 +0100 Subject: [PATCH 07/16] fix android build --- .github/workflows/sample-application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sample-application.yml b/.github/workflows/sample-application.yml index cffe088793..32cf758a43 100644 --- a/.github/workflows/sample-application.yml +++ b/.github/workflows/sample-application.yml @@ -128,7 +128,7 @@ jobs: [[ "${{ matrix.build-type }}" == "production" ]] && TEST_TYPE='release' || TEST_TYPE='debug' echo "Building $TEST_TYPE" - ./gradlew ":app:assemble$CONFIG" app:assembleAndroidTest -DtestBuildType=$TEST_TYPE -PreactNativeArchitectures=x86 + ./gradlew ":app:assemble$CONFIG" -PreactNativeArchitectures=x86 - name: Build iOS App if: ${{ matrix.platform == 'ios' }} From 261884391f798564abe28620617a3cea240192c2 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Wed, 5 Mar 2025 21:42:56 +0100 Subject: [PATCH 08/16] Add isAndroid and isIOS conditions --- .../e2e/captureTransaction.test.ts | 44 +++++++++++-------- .../react-native/e2e/jest.config.android.js | 8 ++++ .../{jest.config.js => jest.config.base.js} | 0 samples/react-native/e2e/jest.config.ios.js | 8 ++++ samples/react-native/e2e/setup.android.ts | 7 +++ samples/react-native/e2e/setup.ios.ts | 7 +++ samples/react-native/e2e/utils/environment.ts | 23 ++++++++++ samples/react-native/scripts/test-android.sh | 2 +- samples/react-native/scripts/test-ios.sh | 2 +- 9 files changed, 80 insertions(+), 21 deletions(-) create mode 100644 samples/react-native/e2e/jest.config.android.js rename samples/react-native/e2e/{jest.config.js => jest.config.base.js} (100%) create mode 100644 samples/react-native/e2e/jest.config.ios.js create mode 100644 samples/react-native/e2e/setup.android.ts create mode 100644 samples/react-native/e2e/setup.ios.ts create mode 100644 samples/react-native/e2e/utils/environment.ts diff --git a/samples/react-native/e2e/captureTransaction.test.ts b/samples/react-native/e2e/captureTransaction.test.ts index 64f808b549..d0be790d37 100644 --- a/samples/react-native/e2e/captureTransaction.test.ts +++ b/samples/react-native/e2e/captureTransaction.test.ts @@ -7,6 +7,7 @@ import { import { getItemOfTypeFrom } from './utils/event'; import { maestro } from './utils/maestro'; +import { isAndroid, isIOS } from './utils/environment'; describe('Capture transaction', () => { let sentryServer = createSentryServer(); @@ -72,39 +73,44 @@ describe('Capture transaction', () => { ]); }); - it('contains app start measurements', async () => { + it('contains cold app start measurements', async () => { const item = getItemOfTypeFrom( getErrorsEnvelope(), 'transaction', ); - expect( - item?.[1].measurements?.app_start_warm || - item?.[1].measurements?.app_start_cold, - ).toBeDefined(); - expect(item?.[1]).toEqual( - expect.objectContaining({ - measurements: expect.objectContaining({ - 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: { + if (isIOS()) { + expect(item?.[1]).toEqual( + expect.objectContaining({ + measurements: expect.objectContaining({ + time_to_initial_display: { unit: 'millisecond', value: expect.any(Number), }, - }), - ...(item?.[1].measurements?.app_start_cold && { app_start_cold: { unit: 'millisecond', value: expect.any(Number), }, }), }), - }), - ); + ); + } else if (isAndroid()) { + // TMP: Until the cold app start is fixed on Android + expect(item?.[1]).toEqual( + expect.objectContaining({ + measurements: expect.objectContaining({ + time_to_initial_display: { + unit: 'millisecond', + value: expect.any(Number), + }, + app_start_warm: { + unit: 'millisecond', + value: expect.any(Number), + }, + }), + }), + ); + } }); it('contains time to initial display measurements', async () => { diff --git a/samples/react-native/e2e/jest.config.android.js b/samples/react-native/e2e/jest.config.android.js new file mode 100644 index 0000000000..d84363325d --- /dev/null +++ b/samples/react-native/e2e/jest.config.android.js @@ -0,0 +1,8 @@ +const path = require('path'); +const baseConfig = require('./jest.config.base'); + +/** @type {import('@jest/types').Config.InitialOptions} */ +module.exports = { + ...baseConfig, + globalSetup: path.resolve(__dirname, 'setup.android.ts'), +}; diff --git a/samples/react-native/e2e/jest.config.js b/samples/react-native/e2e/jest.config.base.js similarity index 100% rename from samples/react-native/e2e/jest.config.js rename to samples/react-native/e2e/jest.config.base.js diff --git a/samples/react-native/e2e/jest.config.ios.js b/samples/react-native/e2e/jest.config.ios.js new file mode 100644 index 0000000000..482cc4e987 --- /dev/null +++ b/samples/react-native/e2e/jest.config.ios.js @@ -0,0 +1,8 @@ +const path = require('path'); +const baseConfig = require('./jest.config.base'); + +/** @type {import('@jest/types').Config.InitialOptions} */ +module.exports = { + ...baseConfig, + globalSetup: path.resolve(__dirname, 'setup.ios.ts'), +}; diff --git a/samples/react-native/e2e/setup.android.ts b/samples/react-native/e2e/setup.android.ts new file mode 100644 index 0000000000..91c0dfec95 --- /dev/null +++ b/samples/react-native/e2e/setup.android.ts @@ -0,0 +1,7 @@ +import { setAndroid } from './utils/environment'; + +function setupAndroid() { + setAndroid(); +} + +export default setupAndroid; diff --git a/samples/react-native/e2e/setup.ios.ts b/samples/react-native/e2e/setup.ios.ts new file mode 100644 index 0000000000..b3f6a69385 --- /dev/null +++ b/samples/react-native/e2e/setup.ios.ts @@ -0,0 +1,7 @@ +import { setIOS } from './utils/environment'; + +function setupIOS() { + setIOS(); +} + +export default setupIOS; diff --git a/samples/react-native/e2e/utils/environment.ts b/samples/react-native/e2e/utils/environment.ts new file mode 100644 index 0000000000..cde97ea350 --- /dev/null +++ b/samples/react-native/e2e/utils/environment.ts @@ -0,0 +1,23 @@ +type TestGlobal = typeof globalThis & { + E2E_TEST_PLATFORM: 'android' | 'ios'; +}; + +function getTestGlobal(): TestGlobal { + return globalThis as TestGlobal; +} + +export function setAndroid(): void { + getTestGlobal().E2E_TEST_PLATFORM = 'android'; +} + +export function setIOS(): void { + getTestGlobal().E2E_TEST_PLATFORM = 'ios'; +} + +export function isAndroid(): boolean { + return getTestGlobal().E2E_TEST_PLATFORM === 'android'; +} + +export function isIOS(): boolean { + return getTestGlobal().E2E_TEST_PLATFORM === 'ios'; +} diff --git a/samples/react-native/scripts/test-android.sh b/samples/react-native/scripts/test-android.sh index b2a34abd82..851d7080b6 100755 --- a/samples/react-native/scripts/test-android.sh +++ b/samples/react-native/scripts/test-android.sh @@ -22,4 +22,4 @@ else fi # Run the tests -npx jest --config e2e/jest.config.js +npx jest --config e2e/jest.config.android.js diff --git a/samples/react-native/scripts/test-ios.sh b/samples/react-native/scripts/test-ios.sh index 047bd8c7de..2d13953f1c 100755 --- a/samples/react-native/scripts/test-ios.sh +++ b/samples/react-native/scripts/test-ios.sh @@ -21,4 +21,4 @@ else fi # Run the tests -npx jest --config e2e/jest.config.js +npx jest --config e2e/jest.config.ios.js From 65b3fa46e8dd3aacaea74764dddcde62c8150d68 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Wed, 5 Mar 2025 21:43:51 +0100 Subject: [PATCH 09/16] install maestro for e2e tests --- .github/workflows/sample-application.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/sample-application.yml b/.github/workflows/sample-application.yml index 32cf758a43..a8314a49d1 100644 --- a/.github/workflows/sample-application.yml +++ b/.github/workflows/sample-application.yml @@ -13,6 +13,7 @@ concurrency: env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + MAESTRO_VERSION: '1.39.0' RN_SENTRY_POD_NAME: RNSentry IOS_APP_ARCHIVE_PATH: sentry-react-native-sample.app.zip ANDROID_APP_ARCHIVE_PATH: sentry-react-native-sample.apk.zip @@ -232,6 +233,11 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Install Maestro + uses: dniHze/maestro-test-action@bda8a93211c86d0a05b7a4597c5ad134566fbde4 # pin@v1.0.0 + with: + version: ${{env.MAESTRO_VERSION}} + - name: Download iOS App Archive if: ${{ matrix.platform == 'ios' }} uses: actions/download-artifact@v4 From f98ef4a87ec60bbf40e1841c2b209c338edf99af Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Wed, 5 Mar 2025 22:00:55 +0100 Subject: [PATCH 10/16] fix app/apk package lookup --- samples/react-native/scripts/test-android.sh | 8 +++++--- samples/react-native/scripts/test-ios.sh | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/samples/react-native/scripts/test-android.sh b/samples/react-native/scripts/test-android.sh index 851d7080b6..9adf2ee9d9 100755 --- a/samples/react-native/scripts/test-android.sh +++ b/samples/react-native/scripts/test-android.sh @@ -1,17 +1,19 @@ #!/bin/bash +set -e -x # exit on error, print commands + # Get current directory thisFileDirPath=$(dirname "$0") reactProjectRootPath="$(cd "$thisFileDirPath/.." && pwd)" -maybeApkPath="${reactProjectRootPath}/*.apk" +maybeApkPath=$(find "${reactProjectRootPath}" -maxdepth 1 -name "*.apk") # Check if any APK files exist -apk_count=$(ls -1 "${maybeApkPath}" 2>/dev/null | wc -l) +apk_count=$(echo "$maybeApkPath" | wc -l) if [ $apk_count -eq 1 ]; then # Force install single APK using adb - apk_file=$(ls "${maybeApkPath}") + apk_file="${maybeApkPath}" echo "Installing $apk_file..." adb install -r "$apk_file" elif [ $apk_count -gt 1 ]; then diff --git a/samples/react-native/scripts/test-ios.sh b/samples/react-native/scripts/test-ios.sh index 2d13953f1c..4c08c3bad3 100755 --- a/samples/react-native/scripts/test-ios.sh +++ b/samples/react-native/scripts/test-ios.sh @@ -1,16 +1,18 @@ #!/bin/bash +set -e -x # exit on error, print commands + # Get current directory thisFileDirPath=$(dirname "$0") reactProjectRootPath="$(cd "$thisFileDirPath/.." && pwd)" -maybeAppPath="${reactProjectRootPath}/*.app" +maybeAppPath=$(find "${reactProjectRootPath}" -maxdepth 1 -name "*.app") # Check if any APP files exist -app_count=$(ls -1 "${maybeAppPath}" 2>/dev/null | wc -l) +app_count=$(echo "$maybeAppPath" | wc -l) if [ $app_count -eq 1 ]; then - app_file=$(ls "${maybeAppPath}") + app_file="${maybeAppPath}" echo "Installing $app_file..." xcrun simctl install booted "$app_file" elif [ $app_count -gt 1 ]; then From 920dbc32207da7445244b543c9d630070700f760 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Thu, 6 Mar 2025 10:46:13 +0100 Subject: [PATCH 11/16] Clean up test jobs naming --- .github/workflows/sample-application.yml | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/.github/workflows/sample-application.yml b/.github/workflows/sample-application.yml index a8314a49d1..b964854106 100644 --- a/.github/workflows/sample-application.yml +++ b/.github/workflows/sample-application.yml @@ -275,15 +275,6 @@ jobs: - name: Install JS Dependencies run: yarn install - - name: Install Detox - run: npm install -g detox-cli@20.0.0 - - - name: Install Apple Simulator Utilities - if: ${{ matrix.platform == 'ios' }} - run: | - brew tap wix/brew - brew install applesimutils - - name: Setup KVM if: ${{ matrix.platform == 'android' }} shell: bash @@ -296,23 +287,20 @@ jobs: sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm - - uses: futureware-tech/simulator-action@dab10d813144ef59b48d401cd95da151222ef8cd # pin@v4 + - name: Boot ${{ env.IOS_DEVICE }} with iOS ${{ env.IOS_VERSION }} + uses: futureware-tech/simulator-action@dab10d813144ef59b48d401cd95da151222ef8cd # pin@v4 if: ${{ matrix.platform == 'ios' }} with: - # the same envs are used by Detox ci.sim configuration model: ${{ env.IOS_DEVICE }} os_version: ${{ env.IOS_VERSION }} - - name: Run Detox iOS Tests + - name: Run iOS Tests if: ${{ matrix.platform == 'ios' }} working-directory: ${{ env.REACT_NATIVE_SAMPLE_PATH }} run: yarn test-ios - - name: Run tests on Android + - name: Run Android Tests on API ${{ env.ANDROID_API_LEVEL }} if: ${{ matrix.platform == 'android' }} - env: - # used by Detox ci.android configuration - ANDROID_AVD_NAME: 'test' # test is default reactivecircus/android-emulator-runner name uses: reactivecircus/android-emulator-runner@62dbb605bba737720e10b196cb4220d374026a6d # pin@v2.33.0 with: api-level: ${{ env.ANDROID_API_LEVEL }} From feceea11103f5476165deefdb943beb5181a2ba4 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Thu, 6 Mar 2025 18:02:37 +0100 Subject: [PATCH 12/16] fix test script for missing app packages --- samples/react-native/scripts/test-android.sh | 2 +- samples/react-native/scripts/test-ios.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/react-native/scripts/test-android.sh b/samples/react-native/scripts/test-android.sh index 9adf2ee9d9..de4b5d5e87 100755 --- a/samples/react-native/scripts/test-android.sh +++ b/samples/react-native/scripts/test-android.sh @@ -11,7 +11,7 @@ maybeApkPath=$(find "${reactProjectRootPath}" -maxdepth 1 -name "*.apk") # Check if any APK files exist apk_count=$(echo "$maybeApkPath" | wc -l) -if [ $apk_count -eq 1 ]; then +if [ -n "$maybeApkPath" ] && [ $apk_count -eq 1 ]; then # Force install single APK using adb apk_file="${maybeApkPath}" echo "Installing $apk_file..." diff --git a/samples/react-native/scripts/test-ios.sh b/samples/react-native/scripts/test-ios.sh index 4c08c3bad3..e242dde917 100755 --- a/samples/react-native/scripts/test-ios.sh +++ b/samples/react-native/scripts/test-ios.sh @@ -11,7 +11,7 @@ maybeAppPath=$(find "${reactProjectRootPath}" -maxdepth 1 -name "*.app") # Check if any APP files exist app_count=$(echo "$maybeAppPath" | wc -l) -if [ $app_count -eq 1 ]; then +if [ -n "$maybeAppPath" ] && [ $app_count -eq 1 ]; then app_file="${maybeAppPath}" echo "Installing $app_file..." xcrun simctl install booted "$app_file" From d2b4d2aadc560bb1c6123a81a5d0728c9bd63f70 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Thu, 6 Mar 2025 18:03:04 +0100 Subject: [PATCH 13/16] until android fix --- 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 d0be790d37..c4b9b2a645 100644 --- a/samples/react-native/e2e/captureTransaction.test.ts +++ b/samples/react-native/e2e/captureTransaction.test.ts @@ -73,7 +73,7 @@ describe('Capture transaction', () => { ]); }); - it('contains cold app start measurements', async () => { + it('contains app start measurements', async () => { const item = getItemOfTypeFrom( getErrorsEnvelope(), 'transaction', From 22d07979d271f083ffcdce7aaf64705d398936b5 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Thu, 6 Mar 2025 18:08:41 +0100 Subject: [PATCH 14/16] remove unused android dependencies --- samples/react-native/android/app/build.gradle | 4 ---- 1 file changed, 4 deletions(-) diff --git a/samples/react-native/android/app/build.gradle b/samples/react-native/android/app/build.gradle index 752989e846..81dacdc70e 100644 --- a/samples/react-native/android/app/build.gradle +++ b/samples/react-native/android/app/build.gradle @@ -192,15 +192,11 @@ android { signingConfig signingConfigs.debug minifyEnabled enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" - proguardFile "${rootProject.projectDir}/../node_modules/detox/android/detox/proguard-rules-app.pro" } } } dependencies { - androidTestImplementation('com.wix:detox:+') - implementation 'androidx.appcompat:appcompat:1.7.0' - // The version of react-native is set by the React Native Gradle Plugin implementation("com.facebook.react:react-android") From 7472c357177955a733a9c92ca5fe60bbda935df1 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Thu, 6 Mar 2025 18:14:12 +0100 Subject: [PATCH 15/16] remove unused network security config --- .../react-native/android/app/src/main/AndroidManifest.xml | 3 +-- .../app/src/main/res/xml/network_security_config.xml | 7 ------- 2 files changed, 1 insertion(+), 9 deletions(-) delete mode 100644 samples/react-native/android/app/src/main/res/xml/network_security_config.xml diff --git a/samples/react-native/android/app/src/main/AndroidManifest.xml b/samples/react-native/android/app/src/main/AndroidManifest.xml index 095bdca459..e1892528b8 100644 --- a/samples/react-native/android/app/src/main/AndroidManifest.xml +++ b/samples/react-native/android/app/src/main/AndroidManifest.xml @@ -9,8 +9,7 @@ android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="false" android:theme="@style/AppTheme" - android:supportsRtl="true" - android:networkSecurityConfig="@xml/network_security_config"> + android:supportsRtl="true"> - - - 10.0.2.2 - localhost - - From 64962120be54801f2399753bcbdae89b309c24c6 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Fri, 7 Mar 2025 10:20:03 +0100 Subject: [PATCH 16/16] Revert "remove unused network security config" This reverts commit 7472c357177955a733a9c92ca5fe60bbda935df1. --- .../react-native/android/app/src/main/AndroidManifest.xml | 3 ++- .../app/src/main/res/xml/network_security_config.xml | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 samples/react-native/android/app/src/main/res/xml/network_security_config.xml diff --git a/samples/react-native/android/app/src/main/AndroidManifest.xml b/samples/react-native/android/app/src/main/AndroidManifest.xml index e1892528b8..095bdca459 100644 --- a/samples/react-native/android/app/src/main/AndroidManifest.xml +++ b/samples/react-native/android/app/src/main/AndroidManifest.xml @@ -9,7 +9,8 @@ android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="false" android:theme="@style/AppTheme" - android:supportsRtl="true"> + android:supportsRtl="true" + android:networkSecurityConfig="@xml/network_security_config"> + + + 10.0.2.2 + localhost + +