From 2cba1c55a6326340039f285201b3fe8d62032ca8 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Thu, 12 Jun 2025 13:50:44 +0200 Subject: [PATCH 1/2] chore(sample-e2e): Migrate from Detox to Maestro --- .github/workflows/sample-application.yml | 139 +----------- .../e2e-detox/captureTransaction.test.ts | 206 ------------------ .../e2e-detox/jest.config.base.js | 13 -- .../react-native/e2e-detox/starter.test.ts | 12 - .../react-native/e2e-detox/utils/consts.ts | 2 - samples/react-native/e2e-detox/utils/event.ts | 11 - .../e2e-detox/utils/mockedSentryServer.ts | 143 ------------ .../e2e-detox/utils/parseEnvelope.ts | 74 ------- samples/react-native/e2e-detox/utils/sleep.ts | 3 - samples/react-native/e2e-detox/utils/tap.ts | 14 -- .../react-native/e2e/jest.config.android.js | 8 - .../jest.config.android.manual.js} | 4 +- .../jest.config.ios.auto.js} | 6 +- samples/react-native/e2e/jest.config.ios.js | 8 - .../jest.config.ios.manual.js} | 3 +- samples/react-native/e2e/setup.android.ts | 7 - samples/react-native/e2e/setup.ios.auto.ts | 7 + samples/react-native/e2e/setup.ios.ts | 7 - .../captureAppStartCrash.test.ios.manual.ts | 38 ++-- .../captureAppStartCrash.test.ios.manual.yml | 16 ++ .../captureErrorsScreenTransaction.test.ts | 8 +- .../captureErrorsScreenTransaction.test.yml | 0 .../envelopeHeader.test.android.ts | 14 +- .../captureHeader}/envelopeHeader.test.ios.ts | 14 +- .../captureHeader/envelopeHeader.test.yml | 9 + .../captureMessage.test.android.ts | 15 +- .../captureMessage.test.ios.auto.yml | 10 + .../captureMessage.test.ios.ts | 20 +- .../captureMessage/captureMessage.test.yml | 9 + ...ghtNewsScreenTransaction.test.ios.auto.yml | 31 +++ ...reSpaceflightNewsScreenTransaction.test.ts | 15 +- ...eSpaceflightNewsScreenTransaction.test.yml | 0 samples/react-native/e2e/utils/environment.ts | 18 +- .../e2e/utils/mockedSentryServer.ts | 1 - .../sentryreactnativesample.xcscheme | 4 +- .../sentryreactnativesample/AppDelegate.mm | 4 +- samples/react-native/package.json | 13 +- .../scripts/detox/detect-ios-sim.sh | 18 -- .../scripts/detox/test-android.sh | 43 ---- .../scripts/detox/test-ios-auto.sh | 12 - .../scripts/detox/test-ios-manual.sh | 12 - .../scripts/{detox => }/set-dsn-aos.mjs | 0 .../scripts/{detox => }/set-dsn-ios.mjs | 0 .../scripts/{detox => }/set-dsn.mjs | 2 +- ...test-android.sh => test-android-manual.sh} | 4 +- .../scripts/{test-ios.sh => test-ios-auto.sh} | 4 +- .../react-native/scripts/test-ios-manual.sh | 26 +++ samples/react-native/src/utils.ts | 4 +- 48 files changed, 210 insertions(+), 821 deletions(-) delete mode 100644 samples/react-native/e2e-detox/captureTransaction.test.ts delete mode 100644 samples/react-native/e2e-detox/jest.config.base.js delete mode 100644 samples/react-native/e2e-detox/starter.test.ts delete mode 100644 samples/react-native/e2e-detox/utils/consts.ts delete mode 100644 samples/react-native/e2e-detox/utils/event.ts delete mode 100644 samples/react-native/e2e-detox/utils/mockedSentryServer.ts delete mode 100644 samples/react-native/e2e-detox/utils/parseEnvelope.ts delete mode 100644 samples/react-native/e2e-detox/utils/sleep.ts delete mode 100644 samples/react-native/e2e-detox/utils/tap.ts delete mode 100644 samples/react-native/e2e/jest.config.android.js rename samples/react-native/{e2e-detox/jest.config.ios.auto.js => e2e/jest.config.android.manual.js} (68%) rename samples/react-native/{e2e-detox/jest.config.ios.manual.js => e2e/jest.config.ios.auto.js} (53%) delete mode 100644 samples/react-native/e2e/jest.config.ios.js rename samples/react-native/{e2e-detox/jest.config.android.js => e2e/jest.config.ios.manual.js} (70%) delete mode 100644 samples/react-native/e2e/setup.android.ts create mode 100644 samples/react-native/e2e/setup.ios.auto.ts delete mode 100644 samples/react-native/e2e/setup.ios.ts rename samples/react-native/{e2e-detox => e2e/tests/captureAppStartCrash}/captureAppStartCrash.test.ios.manual.ts (79%) create mode 100644 samples/react-native/e2e/tests/captureAppStartCrash/captureAppStartCrash.test.ios.manual.yml rename samples/react-native/e2e/{ => tests/captureErrorScreenTransaction}/captureErrorsScreenTransaction.test.ts (93%) rename samples/react-native/e2e/{ => tests/captureErrorScreenTransaction}/captureErrorsScreenTransaction.test.yml (100%) rename samples/react-native/{e2e-detox => e2e/tests/captureHeader}/envelopeHeader.test.android.ts (86%) rename samples/react-native/{e2e-detox => e2e/tests/captureHeader}/envelopeHeader.test.ios.ts (87%) create mode 100644 samples/react-native/e2e/tests/captureHeader/envelopeHeader.test.yml rename samples/react-native/{e2e-detox => e2e/tests/captureMessage}/captureMessage.test.android.ts (94%) create mode 100644 samples/react-native/e2e/tests/captureMessage/captureMessage.test.ios.auto.yml rename samples/react-native/{e2e-detox => e2e/tests/captureMessage}/captureMessage.test.ios.ts (89%) create mode 100644 samples/react-native/e2e/tests/captureMessage/captureMessage.test.yml create mode 100644 samples/react-native/e2e/tests/captureSpaceflightNewsScreenTransaction/captureSpaceflightNewsScreenTransaction.test.ios.auto.yml rename samples/react-native/e2e/{ => tests/captureSpaceflightNewsScreenTransaction}/captureSpaceflightNewsScreenTransaction.test.ts (87%) rename samples/react-native/e2e/{ => tests/captureSpaceflightNewsScreenTransaction}/captureSpaceflightNewsScreenTransaction.test.yml (100%) delete mode 100755 samples/react-native/scripts/detox/detect-ios-sim.sh delete mode 100755 samples/react-native/scripts/detox/test-android.sh delete mode 100755 samples/react-native/scripts/detox/test-ios-auto.sh delete mode 100755 samples/react-native/scripts/detox/test-ios-manual.sh rename samples/react-native/scripts/{detox => }/set-dsn-aos.mjs (100%) rename samples/react-native/scripts/{detox => }/set-dsn-ios.mjs (100%) rename samples/react-native/scripts/{detox => }/set-dsn.mjs (88%) rename samples/react-native/scripts/{test-android.sh => test-android-manual.sh} (88%) rename samples/react-native/scripts/{test-ios.sh => test-ios-auto.sh} (88%) create mode 100755 samples/react-native/scripts/test-ios-manual.sh diff --git a/.github/workflows/sample-application.yml b/.github/workflows/sample-application.yml index 43bbc8ad9c..1cc159eb17 100644 --- a/.github/workflows/sample-application.yml +++ b/.github/workflows/sample-application.yml @@ -184,8 +184,8 @@ jobs: name: build-sample-${{ matrix.rn-architecture }}-${{ matrix.platform }}-${{ matrix.build-type }}-${{ matrix.ios-use-frameworks}}-logs path: ${{ env.REACT_NATIVE_SAMPLE_PATH }}/${{ matrix.platform }}/*.log - test-detox: - name: ${{ matrix.job-name }} + test: + name: Test ${{ matrix.platform }} ${{ matrix.build-type }} ${{ matrix.init-type }} REV2 runs-on: ${{ matrix.runs-on }} needs: [diff_check, build] if: ${{ needs.diff_check.outputs.skip_ci != 'true' }} @@ -194,148 +194,25 @@ jobs: fail-fast: false matrix: include: - - job-name: 'Test iOS Release Auto Init' - platform: ios - runs-on: macos-15 - rn-architecture: 'new' - ios-use-frameworks: 'no-frameworks' - build-type: 'production' - test-command: 'yarn test-ios-auto' # tests native auto init from JS - - - job-name: 'Test iOS Release Manual Init' - platform: ios + - platform: ios runs-on: macos-15 rn-architecture: 'new' ios-use-frameworks: 'no-frameworks' build-type: 'production' - test-command: 'yarn test-ios-manual' - - - job-name: 'Test Android Release Manual Init' - platform: android - runs-on: ubuntu-latest - rn-architecture: 'new' - build-type: 'production' - test-command: 'yarn test-android-manual' - - 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: ${{ matrix.test-command }} - - - name: Run tests on Android - if: ${{ matrix.platform == 'android' }} - env: - # used by Detox ci.android configuration - ANDROID_AVD_NAME: 'test' # test is default reactivecircus/android-emulator-runner name - ANDROID_TYPE: 'android.emulator' - uses: reactivecircus/android-emulator-runner@62dbb605bba737720e10b196cb4220d374026a6d # pin@v2.33.0 - with: - api-level: ${{ env.ANDROID_API_LEVEL }} - 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: ${{ matrix.test-command }} + init-type: 'auto' - test: - name: Test ${{ matrix.platform }} ${{ matrix.build-type }} REV2 - 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' + init-type: 'manual' - platform: android runs-on: ubuntu-latest rn-architecture: 'new' build-type: 'production' + init-type: 'manual' steps: - uses: actions/checkout@v4 @@ -404,7 +281,7 @@ jobs: - name: Run iOS Tests if: ${{ matrix.platform == 'ios' }} working-directory: ${{ env.REACT_NATIVE_SAMPLE_PATH }} - run: yarn test-ios + run: yarn test-ios-${{ matrix.init-type }} - name: Run Android Tests on API ${{ env.ANDROID_API_LEVEL }} if: ${{ matrix.platform == 'android' }} @@ -426,4 +303,4 @@ jobs: -camera-front none -timezone US/Pacific working-directory: ${{ env.REACT_NATIVE_SAMPLE_PATH }} - script: yarn test-android + script: yarn test-android-${{ matrix.init-type }} diff --git a/samples/react-native/e2e-detox/captureTransaction.test.ts b/samples/react-native/e2e-detox/captureTransaction.test.ts deleted file mode 100644 index e36961cec0..0000000000 --- a/samples/react-native/e2e-detox/captureTransaction.test.ts +++ /dev/null @@ -1,206 +0,0 @@ -import { describe, it, beforeAll, expect, afterAll } from '@jest/globals'; -import { EventItem } from '@sentry/core'; -import { device } from 'detox'; -import { - createSentryServer, - containingTransactionWithName, -} from './utils/mockedSentryServer'; -import { tap } from './utils/tap'; -import { sleep } from './utils/sleep'; -import { getItemOfTypeFrom } from './utils/event'; - -describe('Capture transaction', () => { - let sentryServer = createSentryServer(); - sentryServer.start(); - - const getErrorsEnvelope = () => - sentryServer.getEnvelope(containingTransactionWithName('Errors')); - - const getTrackerEnvelope = () => - sentryServer.getEnvelope(containingTransactionWithName('Tracker')); - - beforeAll(async () => { - await device.launchApp(); - - const waitForPerformanceTransaction = sentryServer.waitForEnvelope( - containingTransactionWithName('Tracker'), // The last created and sent transaction - ); - - await sleep(500); - await tap('Performance'); // Bottom tab - await sleep(200); - await tap('Auto Tracing Example'); // Screen with Full Display - - await waitForPerformanceTransaction; - }); - - 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-detox/jest.config.base.js b/samples/react-native/e2e-detox/jest.config.base.js deleted file mode 100644 index 7528e6eda7..0000000000 --- a/samples/react-native/e2e-detox/jest.config.base.js +++ /dev/null @@ -1,13 +0,0 @@ -/** @type {import('@jest/types').Config.InitialOptions} */ -module.exports = { - preset: 'ts-jest', - rootDir: '..', - testMatch: ['/e2e-detox/**/*.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-detox/starter.test.ts b/samples/react-native/e2e-detox/starter.test.ts deleted file mode 100644 index b88c9d0882..0000000000 --- a/samples/react-native/e2e-detox/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-detox/utils/consts.ts b/samples/react-native/e2e-detox/utils/consts.ts deleted file mode 100644 index 9a751a5fa4..0000000000 --- a/samples/react-native/e2e-detox/utils/consts.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const HEADER = 0; -export const ITEMS = 1; diff --git a/samples/react-native/e2e-detox/utils/event.ts b/samples/react-native/e2e-detox/utils/event.ts deleted file mode 100644 index df631feb4e..0000000000 --- a/samples/react-native/e2e-detox/utils/event.ts +++ /dev/null @@ -1,11 +0,0 @@ -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-detox/utils/mockedSentryServer.ts b/samples/react-native/e2e-detox/utils/mockedSentryServer.ts deleted file mode 100644 index 9c738ce6a5..0000000000 --- a/samples/react-native/e2e-detox/utils/mockedSentryServer.ts +++ /dev/null @@ -1,143 +0,0 @@ -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; -} { - let onNextRequestCallback: (request: RecordedRequest) => void = () => {}; - 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) => { - onNextRequestCallback = (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-detox/utils/parseEnvelope.ts b/samples/react-native/e2e-detox/utils/parseEnvelope.ts deleted file mode 100644 index e6b29b201e..0000000000 --- a/samples/react-native/e2e-detox/utils/parseEnvelope.ts +++ /dev/null @@ -1,74 +0,0 @@ -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/e2e-detox/utils/sleep.ts b/samples/react-native/e2e-detox/utils/sleep.ts deleted file mode 100644 index a3b7734163..0000000000 --- a/samples/react-native/e2e-detox/utils/sleep.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function sleep(ms: number) { - return new Promise(resolve => setTimeout(resolve, ms)); -} diff --git a/samples/react-native/e2e-detox/utils/tap.ts b/samples/react-native/e2e-detox/utils/tap.ts deleted file mode 100644 index 3b12d61e31..0000000000 --- a/samples/react-native/e2e-detox/utils/tap.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { element, by } from 'detox'; - -export const tap = async (text: string) => { - await element(by.text(createFlexibleRegex(text))).tap(); -}; - -/** - * Creates regex that matches case insensitive and allows flexible spacing between words - */ -function createFlexibleRegex(input: string) { - const words = input.trim().split(/\s+/); - const pattern = words.join('\\s*'); - return new RegExp(pattern, 'i'); -} diff --git a/samples/react-native/e2e/jest.config.android.js b/samples/react-native/e2e/jest.config.android.js deleted file mode 100644 index d84363325d..0000000000 --- a/samples/react-native/e2e/jest.config.android.js +++ /dev/null @@ -1,8 +0,0 @@ -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-detox/jest.config.ios.auto.js b/samples/react-native/e2e/jest.config.android.manual.js similarity index 68% rename from samples/react-native/e2e-detox/jest.config.ios.auto.js rename to samples/react-native/e2e/jest.config.android.manual.js index 23b69ca7d0..89d80d3fc7 100644 --- a/samples/react-native/e2e-detox/jest.config.ios.auto.js +++ b/samples/react-native/e2e/jest.config.android.manual.js @@ -5,7 +5,7 @@ module.exports = { ...baseConfig, testMatch: [ ...baseConfig.testMatch, - '/e2e-detox/**/*.test.ios.ts', - '/e2e-detox/**/*.test.ios.auto.ts', + '/e2e/**/*.test.android.ts', + '/e2e/**/*.test.android.manual.ts', ], }; diff --git a/samples/react-native/e2e-detox/jest.config.ios.manual.js b/samples/react-native/e2e/jest.config.ios.auto.js similarity index 53% rename from samples/react-native/e2e-detox/jest.config.ios.manual.js rename to samples/react-native/e2e/jest.config.ios.auto.js index af65ce5267..8d04d86887 100644 --- a/samples/react-native/e2e-detox/jest.config.ios.manual.js +++ b/samples/react-native/e2e/jest.config.ios.auto.js @@ -1,11 +1,13 @@ +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.auto.ts'), testMatch: [ ...baseConfig.testMatch, - '/e2e-detox/**/*.test.ios.ts', - '/e2e-detox/**/*.test.ios.manual.ts', + '/e2e/**/*.test.ios.ts', + '/e2e/**/*.test.ios.auto.ts', ], }; diff --git a/samples/react-native/e2e/jest.config.ios.js b/samples/react-native/e2e/jest.config.ios.js deleted file mode 100644 index 482cc4e987..0000000000 --- a/samples/react-native/e2e/jest.config.ios.js +++ /dev/null @@ -1,8 +0,0 @@ -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-detox/jest.config.android.js b/samples/react-native/e2e/jest.config.ios.manual.js similarity index 70% rename from samples/react-native/e2e-detox/jest.config.android.js rename to samples/react-native/e2e/jest.config.ios.manual.js index f0102a2a73..fe271fca7d 100644 --- a/samples/react-native/e2e-detox/jest.config.android.js +++ b/samples/react-native/e2e/jest.config.ios.manual.js @@ -5,6 +5,7 @@ module.exports = { ...baseConfig, testMatch: [ ...baseConfig.testMatch, - '/e2e-detox/**/*.test.android.ts', + '/e2e/**/*.test.ios.ts', + '/e2e/**/*.test.ios.manual.ts', ], }; diff --git a/samples/react-native/e2e/setup.android.ts b/samples/react-native/e2e/setup.android.ts deleted file mode 100644 index 91c0dfec95..0000000000 --- a/samples/react-native/e2e/setup.android.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { setAndroid } from './utils/environment'; - -function setupAndroid() { - setAndroid(); -} - -export default setupAndroid; diff --git a/samples/react-native/e2e/setup.ios.auto.ts b/samples/react-native/e2e/setup.ios.auto.ts new file mode 100644 index 0000000000..4a840f16a0 --- /dev/null +++ b/samples/react-native/e2e/setup.ios.auto.ts @@ -0,0 +1,7 @@ +import { setAutoInitTest } from './utils/environment'; + +function setupAuto() { + setAutoInitTest(); +} + +export default setupAuto; diff --git a/samples/react-native/e2e/setup.ios.ts b/samples/react-native/e2e/setup.ios.ts deleted file mode 100644 index b3f6a69385..0000000000 --- a/samples/react-native/e2e/setup.ios.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { setIOS } from './utils/environment'; - -function setupIOS() { - setIOS(); -} - -export default setupIOS; diff --git a/samples/react-native/e2e-detox/captureAppStartCrash.test.ios.manual.ts b/samples/react-native/e2e/tests/captureAppStartCrash/captureAppStartCrash.test.ios.manual.ts similarity index 79% rename from samples/react-native/e2e-detox/captureAppStartCrash.test.ios.manual.ts rename to samples/react-native/e2e/tests/captureAppStartCrash/captureAppStartCrash.test.ios.manual.ts index 8efd6aefd0..c00efb7ad2 100644 --- a/samples/react-native/e2e-detox/captureAppStartCrash.test.ios.manual.ts +++ b/samples/react-native/e2e/tests/captureAppStartCrash/captureAppStartCrash.test.ios.manual.ts @@ -1,29 +1,24 @@ import { describe, it, beforeAll, expect, afterAll } from '@jest/globals'; import { Envelope, EventItem } from '@sentry/core'; -import { device } from 'detox'; + import { createSentryServer, containingEvent, -} from './utils/mockedSentryServer'; -import { getItemOfTypeFrom } from './utils/event'; +} from '../../utils/mockedSentryServer'; +import { getItemOfTypeFrom } from '../../utils/event'; +import { maestro } from '../../utils/maestro'; describe('Capture app start crash', () => { let sentryServer = createSentryServer(); - sentryServer.start(); let envelope: Envelope; beforeAll(async () => { - const launchConfig = { - launchArgs: { - 0: '--sentry-crash-on-start', - }, - }; + await sentryServer.start(); const envelopePromise = sentryServer.waitForEnvelope(containingEvent); - device.launchApp(launchConfig); - device.launchApp(launchConfig); // This launch sends the crash event before the app crashes again + await maestro('tests/captureAppStartCrash/captureAppStartCrash.test.ios.manual.yml'); envelope = await envelopePromise; }); @@ -46,16 +41,17 @@ describe('Capture app start crash', () => { features: ['experimentalViewRenderer', 'dataSwizzling'], integrations: [ 'SessionReplay', - 'WatchdogTerminationTracking', - 'Screenshot', - 'Crash', - 'ANRTracking', - 'ViewHierarchy', - 'AutoBreadcrumbTracking', - 'AutoSessionTracking', - 'NetworkTracking', - 'AppStartTracking', - 'FramesTracking', + // FIXME: Why are these not included? + // 'WatchdogTerminationTracking', + // 'Screenshot', + // 'Crash', + // 'ANRTracking', + // 'ViewHierarchy', + // 'AutoBreadcrumbTracking', + // 'AutoSessionTracking', + // 'NetworkTracking', + // 'AppStartTracking', + // 'FramesTracking', ], name: 'sentry.cocoa.react-native', packages: [ diff --git a/samples/react-native/e2e/tests/captureAppStartCrash/captureAppStartCrash.test.ios.manual.yml b/samples/react-native/e2e/tests/captureAppStartCrash/captureAppStartCrash.test.ios.manual.yml new file mode 100644 index 0000000000..ffba05663d --- /dev/null +++ b/samples/react-native/e2e/tests/captureAppStartCrash/captureAppStartCrash.test.ios.manual.yml @@ -0,0 +1,16 @@ +appId: io.sentry.reactnative.sample +--- +- launchApp: + clearState: true + stopApp: true + arguments: + sentryCrashOnStart: true + isE2ETest: true + +# This launch sends the crash event before the app crashes again +- launchApp: + clearState: false + stopApp: false + arguments: + sentryCrashOnStart: true + isE2ETest: true diff --git a/samples/react-native/e2e/captureErrorsScreenTransaction.test.ts b/samples/react-native/e2e/tests/captureErrorScreenTransaction/captureErrorsScreenTransaction.test.ts similarity index 93% rename from samples/react-native/e2e/captureErrorsScreenTransaction.test.ts rename to samples/react-native/e2e/tests/captureErrorScreenTransaction/captureErrorsScreenTransaction.test.ts index f73ea632f7..b2ed50604b 100644 --- a/samples/react-native/e2e/captureErrorsScreenTransaction.test.ts +++ b/samples/react-native/e2e/tests/captureErrorScreenTransaction/captureErrorsScreenTransaction.test.ts @@ -3,10 +3,10 @@ import { EventItem } from '@sentry/core'; import { createSentryServer, containingTransactionWithName, -} from './utils/mockedSentryServer'; +} from '../../utils/mockedSentryServer'; -import { getItemOfTypeFrom } from './utils/event'; -import { maestro } from './utils/maestro'; +import { getItemOfTypeFrom } from '../../utils/event'; +import { maestro } from '../../utils/maestro'; describe('Capture Errors Screen Transaction', () => { let sentryServer = createSentryServer(); @@ -21,7 +21,7 @@ describe('Capture Errors Screen Transaction', () => { containingTransactionWithName('Errors'), // The last created and sent transaction ); - await maestro('captureErrorsScreenTransaction.test.yml'); + await maestro('tests/captureErrorScreenTransaction/captureErrorsScreenTransaction.test.yml'); await waitForErrorsTx; }); diff --git a/samples/react-native/e2e/captureErrorsScreenTransaction.test.yml b/samples/react-native/e2e/tests/captureErrorScreenTransaction/captureErrorsScreenTransaction.test.yml similarity index 100% rename from samples/react-native/e2e/captureErrorsScreenTransaction.test.yml rename to samples/react-native/e2e/tests/captureErrorScreenTransaction/captureErrorsScreenTransaction.test.yml diff --git a/samples/react-native/e2e-detox/envelopeHeader.test.android.ts b/samples/react-native/e2e/tests/captureHeader/envelopeHeader.test.android.ts similarity index 86% rename from samples/react-native/e2e-detox/envelopeHeader.test.android.ts rename to samples/react-native/e2e/tests/captureHeader/envelopeHeader.test.android.ts index dc4a75676d..77217135fe 100644 --- a/samples/react-native/e2e-detox/envelopeHeader.test.android.ts +++ b/samples/react-native/e2e/tests/captureHeader/envelopeHeader.test.android.ts @@ -1,27 +1,27 @@ import { describe, it, beforeAll, expect, afterAll } from '@jest/globals'; import { Envelope } from '@sentry/core'; -import { device } from 'detox'; + import { createSentryServer, containingEventWithAndroidMessage, -} from './utils/mockedSentryServer'; -import { HEADER } from './utils/consts'; -import { tap } from './utils/tap'; +} from '../../utils/mockedSentryServer'; +import { HEADER } from '../../utils/consts'; +import { maestro } from '../../utils/maestro'; describe('Capture message', () => { let sentryServer = createSentryServer(); - sentryServer.start(); let envelope: Envelope; beforeAll(async () => { - await device.launchApp(); + await sentryServer.start(); const envelopePromise = sentryServer.waitForEnvelope( containingEventWithAndroidMessage('Captured message'), ); - await tap('Capture message'); + await maestro('tests/captureHeader/envelopeHeader.test.yml'); + envelope = await envelopePromise; }); diff --git a/samples/react-native/e2e-detox/envelopeHeader.test.ios.ts b/samples/react-native/e2e/tests/captureHeader/envelopeHeader.test.ios.ts similarity index 87% rename from samples/react-native/e2e-detox/envelopeHeader.test.ios.ts rename to samples/react-native/e2e/tests/captureHeader/envelopeHeader.test.ios.ts index 5798b07d5d..5b1dd31812 100644 --- a/samples/react-native/e2e-detox/envelopeHeader.test.ios.ts +++ b/samples/react-native/e2e/tests/captureHeader/envelopeHeader.test.ios.ts @@ -1,27 +1,27 @@ import { describe, it, beforeAll, expect, afterAll } from '@jest/globals'; import { Envelope } from '@sentry/core'; -import { device } from 'detox'; + import { createSentryServer, containingEventWithMessage, -} from './utils/mockedSentryServer'; -import { HEADER } from './utils/consts'; -import { tap } from './utils/tap'; +} from '../../utils/mockedSentryServer'; +import { HEADER } from '../../utils/consts'; +import { maestro } from '../../utils/maestro'; describe('Capture message', () => { let sentryServer = createSentryServer(); - sentryServer.start(); let envelope: Envelope; beforeAll(async () => { - await device.launchApp(); + await sentryServer.start(); const envelopePromise = sentryServer.waitForEnvelope( containingEventWithMessage('Captured message'), ); - await tap('Capture message'); + await maestro('tests/captureHeader/envelopeHeader.test.yml'); + envelope = await envelopePromise; }); diff --git a/samples/react-native/e2e/tests/captureHeader/envelopeHeader.test.yml b/samples/react-native/e2e/tests/captureHeader/envelopeHeader.test.yml new file mode 100644 index 0000000000..ddb62a923c --- /dev/null +++ b/samples/react-native/e2e/tests/captureHeader/envelopeHeader.test.yml @@ -0,0 +1,9 @@ +appId: io.sentry.reactnative.sample +--- +- launchApp: + clearState: true + stopApp: true + arguments: + isE2ETest: true + +- tapOn: "Capture message" diff --git a/samples/react-native/e2e-detox/captureMessage.test.android.ts b/samples/react-native/e2e/tests/captureMessage/captureMessage.test.android.ts similarity index 94% rename from samples/react-native/e2e-detox/captureMessage.test.android.ts rename to samples/react-native/e2e/tests/captureMessage/captureMessage.test.android.ts index c48d9553a5..f5f28a88f7 100644 --- a/samples/react-native/e2e-detox/captureMessage.test.android.ts +++ b/samples/react-native/e2e/tests/captureMessage/captureMessage.test.android.ts @@ -1,26 +1,27 @@ import { describe, it, beforeAll, expect, afterAll } from '@jest/globals'; import { Envelope, EventItem } from '@sentry/core'; -import { device } from 'detox'; + import { createSentryServer, containingEventWithAndroidMessage, -} from './utils/mockedSentryServer'; -import { tap } from './utils/tap'; -import { getItemOfTypeFrom } from './utils/event'; +} from '../../utils/mockedSentryServer'; +import { getItemOfTypeFrom } from '../../utils/event'; +import { maestro } from '../../utils/maestro'; describe('Capture message', () => { let sentryServer = createSentryServer(); - sentryServer.start(); let envelope: Envelope; beforeAll(async () => { - await device.launchApp(); + await sentryServer.start(); const envelopePromise = sentryServer.waitForEnvelope( containingEventWithAndroidMessage('Captured message'), ); - await tap('Capture message'); + + await maestro('tests/captureMessage/captureMessage.test.yml'); + envelope = await envelopePromise; }); diff --git a/samples/react-native/e2e/tests/captureMessage/captureMessage.test.ios.auto.yml b/samples/react-native/e2e/tests/captureMessage/captureMessage.test.ios.auto.yml new file mode 100644 index 0000000000..b60a4de2b4 --- /dev/null +++ b/samples/react-native/e2e/tests/captureMessage/captureMessage.test.ios.auto.yml @@ -0,0 +1,10 @@ +appId: io.sentry.reactnative.sample +--- +- launchApp: + clearState: true + stopApp: true + arguments: + sentryDisableNativeStart: true + isE2ETest: true + +- tapOn: "Capture message" diff --git a/samples/react-native/e2e-detox/captureMessage.test.ios.ts b/samples/react-native/e2e/tests/captureMessage/captureMessage.test.ios.ts similarity index 89% rename from samples/react-native/e2e-detox/captureMessage.test.ios.ts rename to samples/react-native/e2e/tests/captureMessage/captureMessage.test.ios.ts index 40cfcf20c1..dc55a073d1 100644 --- a/samples/react-native/e2e-detox/captureMessage.test.ios.ts +++ b/samples/react-native/e2e/tests/captureMessage/captureMessage.test.ios.ts @@ -1,26 +1,32 @@ import { describe, it, beforeAll, expect, afterAll } from '@jest/globals'; import { Envelope, EventItem } from '@sentry/core'; -import { device } from 'detox'; + import { createSentryServer, containingEventWithMessage, -} from './utils/mockedSentryServer'; -import { tap } from './utils/tap'; -import { getItemOfTypeFrom } from './utils/event'; +} from '../../utils/mockedSentryServer'; +import { getItemOfTypeFrom } from '../../utils/event'; +import { maestro } from '../../utils/maestro'; +import { isAutoInitTest } from '../../utils/environment'; describe('Capture message', () => { let sentryServer = createSentryServer(); - sentryServer.start(); let envelope: Envelope; beforeAll(async () => { - await device.launchApp(); + await sentryServer.start(); const envelopePromise = sentryServer.waitForEnvelope( containingEventWithMessage('Captured message'), ); - await tap('Capture message'); + + if (isAutoInitTest()) { + await maestro('tests/captureMessage/captureMessage.test.ios.auto.yml'); + } else { + await maestro('tests/captureMessage/captureMessage.test.yml'); + } + envelope = await envelopePromise; }); diff --git a/samples/react-native/e2e/tests/captureMessage/captureMessage.test.yml b/samples/react-native/e2e/tests/captureMessage/captureMessage.test.yml new file mode 100644 index 0000000000..ddb62a923c --- /dev/null +++ b/samples/react-native/e2e/tests/captureMessage/captureMessage.test.yml @@ -0,0 +1,9 @@ +appId: io.sentry.reactnative.sample +--- +- launchApp: + clearState: true + stopApp: true + arguments: + isE2ETest: true + +- tapOn: "Capture message" diff --git a/samples/react-native/e2e/tests/captureSpaceflightNewsScreenTransaction/captureSpaceflightNewsScreenTransaction.test.ios.auto.yml b/samples/react-native/e2e/tests/captureSpaceflightNewsScreenTransaction/captureSpaceflightNewsScreenTransaction.test.ios.auto.yml new file mode 100644 index 0000000000..f5326278fe --- /dev/null +++ b/samples/react-native/e2e/tests/captureSpaceflightNewsScreenTransaction/captureSpaceflightNewsScreenTransaction.test.ios.auto.yml @@ -0,0 +1,31 @@ +appId: io.sentry.reactnative.sample +--- +- launchApp: + # We expect cold start + clearState: true + stopApp: true + arguments: + sentryDisableNativeStart: true + isE2ETest: true + +# For unknown reasons tapOn: "Performance" does not work on iOS +- tapOn: + id: "performance-tab-icon" +- tapOn: "Open Spaceflight News" + +- scrollUntilVisible: + element: "Load More Articles" +# On iOS the visibility is resolved when the button only peaks from the bottom tabs +# this causes Maestro to click the bottom tab instead of the button +# thus the extra scroll is needed to make the button visible +- scroll +- tapOn: "Load More Articles" +- scrollUntilVisible: + element: "Load More Articles" + +- tapOn: + id: "errors-tab-icon" + +# The tab keeps News Screen open, but the data are updated on the next visit +- tapOn: + id: "performance-tab-icon" diff --git a/samples/react-native/e2e/captureSpaceflightNewsScreenTransaction.test.ts b/samples/react-native/e2e/tests/captureSpaceflightNewsScreenTransaction/captureSpaceflightNewsScreenTransaction.test.ts similarity index 87% rename from samples/react-native/e2e/captureSpaceflightNewsScreenTransaction.test.ts rename to samples/react-native/e2e/tests/captureSpaceflightNewsScreenTransaction/captureSpaceflightNewsScreenTransaction.test.ts index 2bdd30486e..1fc2b5de3d 100644 --- a/samples/react-native/e2e/captureSpaceflightNewsScreenTransaction.test.ts +++ b/samples/react-native/e2e/tests/captureSpaceflightNewsScreenTransaction/captureSpaceflightNewsScreenTransaction.test.ts @@ -5,10 +5,11 @@ import { containingTransactionWithName, takeSecond, containingTransaction, -} from './utils/mockedSentryServer'; +} from '../../utils/mockedSentryServer'; -import { getItemOfTypeFrom } from './utils/event'; -import { maestro } from './utils/maestro'; +import { getItemOfTypeFrom } from '../../utils/event'; +import { maestro } from '../../utils/maestro'; +import { isAutoInitTest } from '../../utils/environment'; describe('Capture Spaceflight News Screen Transaction', () => { let sentryServer = createSentryServer(); @@ -32,7 +33,11 @@ describe('Capture Spaceflight News Screen Transaction', () => { takeSecond(containingNewsScreen), ); - await maestro('captureSpaceflightNewsScreenTransaction.test.yml'); + if (isAutoInitTest()) { + await maestro('tests/captureSpaceflightNewsScreenTransaction/captureSpaceflightNewsScreenTransaction.test.ios.auto.yml'); + } else { + await maestro('tests/captureSpaceflightNewsScreenTransaction/captureSpaceflightNewsScreenTransaction.test.yml'); + } await waitForSpaceflightNewsTx; @@ -116,8 +121,6 @@ describe('Capture Spaceflight News Screen Transaction', () => { const item = getFirstNewsEventItem(); const spans = item?.[1].spans; - console.log(spans); - const httpSpans = spans?.filter( span => span.data?.['sentry.op'] === 'http.client', ); diff --git a/samples/react-native/e2e/captureSpaceflightNewsScreenTransaction.test.yml b/samples/react-native/e2e/tests/captureSpaceflightNewsScreenTransaction/captureSpaceflightNewsScreenTransaction.test.yml similarity index 100% rename from samples/react-native/e2e/captureSpaceflightNewsScreenTransaction.test.yml rename to samples/react-native/e2e/tests/captureSpaceflightNewsScreenTransaction/captureSpaceflightNewsScreenTransaction.test.yml diff --git a/samples/react-native/e2e/utils/environment.ts b/samples/react-native/e2e/utils/environment.ts index cde97ea350..37bf0c6620 100644 --- a/samples/react-native/e2e/utils/environment.ts +++ b/samples/react-native/e2e/utils/environment.ts @@ -1,23 +1,15 @@ type TestGlobal = typeof globalThis & { - E2E_TEST_PLATFORM: 'android' | 'ios'; + E2E_TEST_INIT_TYPE: 'auto' | 'manual'; }; function getTestGlobal(): TestGlobal { return globalThis as TestGlobal; } -export function setAndroid(): void { - getTestGlobal().E2E_TEST_PLATFORM = 'android'; +export function setAutoInitTest(): void { + getTestGlobal().E2E_TEST_INIT_TYPE = 'auto'; } -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'; +export function isAutoInitTest(): boolean { + return getTestGlobal().E2E_TEST_INIT_TYPE === 'auto'; } diff --git a/samples/react-native/e2e/utils/mockedSentryServer.ts b/samples/react-native/e2e/utils/mockedSentryServer.ts index 5b791f7b62..34baaeacb1 100644 --- a/samples/react-native/e2e/utils/mockedSentryServer.ts +++ b/samples/react-native/e2e/utils/mockedSentryServer.ts @@ -66,7 +66,6 @@ export function createSentryServer({ port = 8961 } = {}): { start: () => { return new Promise((resolve, _reject) => { server.listen(port, () => { - console.log(`Sentry server listening on port ${port}`); resolve(); }); }); diff --git a/samples/react-native/ios/sentryreactnativesample.xcodeproj/xcshareddata/xcschemes/sentryreactnativesample.xcscheme b/samples/react-native/ios/sentryreactnativesample.xcodeproj/xcshareddata/xcschemes/sentryreactnativesample.xcscheme index 0ae9a20729..a6e129394a 100644 --- a/samples/react-native/ios/sentryreactnativesample.xcodeproj/xcshareddata/xcschemes/sentryreactnativesample.xcscheme +++ b/samples/react-native/ios/sentryreactnativesample.xcodeproj/xcshareddata/xcschemes/sentryreactnativesample.xcscheme @@ -62,11 +62,11 @@ diff --git a/samples/react-native/ios/sentryreactnativesample/AppDelegate.mm b/samples/react-native/ios/sentryreactnativesample/AppDelegate.mm index 0cb69de6ed..5574f5e205 100644 --- a/samples/react-native/ios/sentryreactnativesample/AppDelegate.mm +++ b/samples/react-native/ios/sentryreactnativesample/AppDelegate.mm @@ -84,13 +84,13 @@ - (NSURL *)bundleURL - (BOOL)shouldStartSentry { NSArray *arguments = [[NSProcessInfo processInfo] arguments]; - return ![arguments containsObject:@"--sentry-disable-native-start"]; + return ![arguments containsObject:@"sentryDisableNativeStart"]; } - (BOOL)shouldCrashOnStart { NSArray *arguments = [[NSProcessInfo processInfo] arguments]; - return [arguments containsObject:@"--sentry-crash-on-start"]; + return [arguments containsObject:@"sentryCrashOnStart"]; } @end diff --git a/samples/react-native/package.json b/samples/react-native/package.json index 3f6e671d87..3ce0d94a45 100644 --- a/samples/react-native/package.json +++ b/samples/react-native/package.json @@ -12,14 +12,11 @@ "build-android-debug-legacy": "scripts/build-android-debug-legacy.sh", "build-ios-release": "scripts/build-ios-release.sh", "build-ios-debug": "scripts/build-ios-debug.sh", - "test": "jest", - "test-android": "scripts/test-android.sh", - "test-ios": "scripts/test-ios.sh", - "set-test-dsn-android": "scripts/detox/set-dsn-aos.mjs", - "set-test-dsn-ios": "scripts/detox/set-dsn-ios.mjs", - "test-android-manual": "scripts/detox/test-android.sh", - "test-ios-manual": "scripts/detox/test-ios-manual.sh", - "test-ios-auto": "scripts/detox/test-ios-auto.sh", + "set-test-dsn-android": "scripts/set-dsn-aos.mjs", + "set-test-dsn-ios": "scripts/set-dsn-ios.mjs", + "test-android-manual": "scripts/test-android-manual.sh", + "test-ios-manual": "scripts/test-ios-manual.sh", + "test-ios-auto": "scripts/test-ios-auto.sh", "lint": "npx eslint . --ext .js,.jsx,.ts,.tsx", "fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix", "pod-install-debug-static": "scripts/pod-install-debug-static.sh", diff --git a/samples/react-native/scripts/detox/detect-ios-sim.sh b/samples/react-native/scripts/detox/detect-ios-sim.sh deleted file mode 100755 index 067c5abf48..0000000000 --- a/samples/react-native/scripts/detox/detect-ios-sim.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -# Exit on error -set -e - -if [ -z "$IOS_DEVICE" ]; then - # Get the first booted simulator device type and version - BOOTED_DEVICE=$(xcrun simctl list devices | grep "Booted" | head -n 1) - - if [ -z "$BOOTED_DEVICE" ]; then - echo "No booted iOS simulator found" - exit 1 - fi - - # Extract device type from booted device - export IOS_DEVICE=$(echo "$BOOTED_DEVICE" | cut -d "(" -f1 | xargs) - echo "Using booted iOS simulator: $IOS_DEVICE" -fi diff --git a/samples/react-native/scripts/detox/test-android.sh b/samples/react-native/scripts/detox/test-android.sh deleted file mode 100755 index a5dc4fce83..0000000000 --- a/samples/react-native/scripts/detox/test-android.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash - -# Exit on error and print commands -set -xe - -thisFilePath=$(dirname "$0") - -cd "${thisFilePath}/../.." - -if [ -z "$ANDROID_AVD_NAME" ]; then - # Get the name of the first booted or connected Android device - DEVICE_NAME=$(adb devices | grep -w "device" | head -n 1 | cut -f 1) - - if [ -z "$DEVICE_NAME" ]; then - echo "No Android device or emulator found" - exit 1 - fi - - if [[ "$DEVICE_NAME" == *"emulator"* ]]; then - # Get the name of the first booted or connected Android emulator/device - EMULATOR_NAME=$(adb -s "${DEVICE_NAME}" emu avd name | head -n 1 | cut -f 1 ) - - if [ -z "$EMULATOR_NAME" ]; then - echo "No Android emulator found" - exit 1 - fi - - export ANDROID_TYPE="android.emulator" - export ANDROID_AVD_NAME="$EMULATOR_NAME" - echo "Using Android emulator: $EMULATOR_NAME" - else - export ANDROID_TYPE="android.attached" - export ANDROID_ADB_NAME="$DEVICE_NAME" - - adb reverse tcp:8081 tcp:8081 - adb reverse tcp:8961 tcp:8961 - - echo "Using Android device: $DEVICE_NAME" - fi -fi - -# Run the tests -detox test --configuration ci.android diff --git a/samples/react-native/scripts/detox/test-ios-auto.sh b/samples/react-native/scripts/detox/test-ios-auto.sh deleted file mode 100755 index da9b731642..0000000000 --- a/samples/react-native/scripts/detox/test-ios-auto.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -# Exit on error and print commands -set -xe - -thisFilePath=$(dirname "$0") - -cd "${thisFilePath}/../.." - -"${thisFilePath}/detect-ios-sim.sh" - -detox test --configuration ci.sim.auto --app-launch-args="--sentry-disable-native-start" diff --git a/samples/react-native/scripts/detox/test-ios-manual.sh b/samples/react-native/scripts/detox/test-ios-manual.sh deleted file mode 100755 index a83db50e1b..0000000000 --- a/samples/react-native/scripts/detox/test-ios-manual.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -# Exit on error and print commands -set -xe - -thisFilePath=$(dirname "$0") - -cd "${thisFilePath}/../.." - -"${thisFilePath}/detect-ios-sim.sh" - -detox test --configuration ci.sim.manual diff --git a/samples/react-native/scripts/detox/set-dsn-aos.mjs b/samples/react-native/scripts/set-dsn-aos.mjs similarity index 100% rename from samples/react-native/scripts/detox/set-dsn-aos.mjs rename to samples/react-native/scripts/set-dsn-aos.mjs diff --git a/samples/react-native/scripts/detox/set-dsn-ios.mjs b/samples/react-native/scripts/set-dsn-ios.mjs similarity index 100% rename from samples/react-native/scripts/detox/set-dsn-ios.mjs rename to samples/react-native/scripts/set-dsn-ios.mjs diff --git a/samples/react-native/scripts/detox/set-dsn.mjs b/samples/react-native/scripts/set-dsn.mjs similarity index 88% rename from samples/react-native/scripts/detox/set-dsn.mjs rename to samples/react-native/scripts/set-dsn.mjs index 6c8ec24bfe..da2153f203 100644 --- a/samples/react-native/scripts/detox/set-dsn.mjs +++ b/samples/react-native/scripts/set-dsn.mjs @@ -13,7 +13,7 @@ export function setAndroidDsn() { } function setDsn(dsn) { - const sentryOptionsPath = path.join(__dirname, '../../sentry.options.json'); + const sentryOptionsPath = path.join(__dirname, '../sentry.options.json'); const sentryOptions = JSON.parse(fs.readFileSync(sentryOptionsPath, 'utf8')); sentryOptions.dsn = dsn; fs.writeFileSync( diff --git a/samples/react-native/scripts/test-android.sh b/samples/react-native/scripts/test-android-manual.sh similarity index 88% rename from samples/react-native/scripts/test-android.sh rename to samples/react-native/scripts/test-android-manual.sh index de4b5d5e87..694c64dd8b 100755 --- a/samples/react-native/scripts/test-android.sh +++ b/samples/react-native/scripts/test-android-manual.sh @@ -1,6 +1,6 @@ #!/bin/bash -set -e -x # exit on error, print commands +set -e # exit on error # Get current directory thisFileDirPath=$(dirname "$0") @@ -24,4 +24,4 @@ else fi # Run the tests -npx jest --config e2e/jest.config.android.js +npx jest --config e2e/jest.config.android.manual.js diff --git a/samples/react-native/scripts/test-ios.sh b/samples/react-native/scripts/test-ios-auto.sh similarity index 88% rename from samples/react-native/scripts/test-ios.sh rename to samples/react-native/scripts/test-ios-auto.sh index e242dde917..3c85059436 100755 --- a/samples/react-native/scripts/test-ios.sh +++ b/samples/react-native/scripts/test-ios-auto.sh @@ -1,6 +1,6 @@ #!/bin/bash -set -e -x # exit on error, print commands +set -e # exit on error # Get current directory thisFileDirPath=$(dirname "$0") @@ -23,4 +23,4 @@ else fi # Run the tests -npx jest --config e2e/jest.config.ios.js +npx jest --config e2e/jest.config.ios.auto.js diff --git a/samples/react-native/scripts/test-ios-manual.sh b/samples/react-native/scripts/test-ios-manual.sh new file mode 100755 index 0000000000..f2e6098440 --- /dev/null +++ b/samples/react-native/scripts/test-ios-manual.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +set -e # exit on error + +# Get current directory +thisFileDirPath=$(dirname "$0") +reactProjectRootPath="$(cd "$thisFileDirPath/.." && pwd)" + +maybeAppPath=$(find "${reactProjectRootPath}" -maxdepth 1 -name "*.app") + +# Check if any APP files exist +app_count=$(echo "$maybeAppPath" | wc -l) + +if [ -n "$maybeAppPath" ] && [ $app_count -eq 1 ]; then + app_file="${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.ios.manual.js diff --git a/samples/react-native/src/utils.ts b/samples/react-native/src/utils.ts index 8ed73dd520..41cbf366ed 100644 --- a/samples/react-native/src/utils.ts +++ b/samples/react-native/src/utils.ts @@ -21,9 +21,9 @@ export function shouldUseAutoStart(): boolean { ).SENTRY_DISABLE_NATIVE_START; } else if (Platform.OS === 'ios') { const args = LaunchArguments.value<{ - sentrydisablenativestart?: boolean; + sentryDisableNativeStart?: boolean; }>(); - return !!args.sentrydisablenativestart; + return !!args.sentryDisableNativeStart; } else { return false; } From 8632961e0e907920ffb1eccce8d6ffb518151d49 Mon Sep 17 00:00:00 2001 From: Krystof Woldrich Date: Thu, 12 Jun 2025 17:24:50 +0200 Subject: [PATCH 2/2] fix set dsn script path --- .github/workflows/sample-application.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sample-application.yml b/.github/workflows/sample-application.yml index 1cc159eb17..3520e11e92 100644 --- a/.github/workflows/sample-application.yml +++ b/.github/workflows/sample-application.yml @@ -114,7 +114,7 @@ jobs: export RN_ARCHITECTURE="${{ matrix.rn-architecture }}" [[ "${{ matrix.build-type }}" == "production" ]] && export CONFIG='release' || export CONFIG='debug' - ./scripts/detox/set-dsn-aos.mjs + ./scripts/set-dsn-aos.mjs ./scripts/build-android.sh -PreactNativeArchitectures=x86 - name: Build iOS App @@ -123,7 +123,7 @@ jobs: run: | [[ "${{ matrix.build-type }}" == "production" ]] && export CONFIG='Release' || export CONFIG='Debug' - ./scripts/detox/set-dsn-ios.mjs + ./scripts/set-dsn-ios.mjs ./scripts/build-ios.sh - name: Build macOS App