From 5aaf64af5bb671ed897db305ab28d35cb4a7cf1b Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Wed, 23 Apr 2025 16:36:44 +0100 Subject: [PATCH 01/16] ci(react-native): add expo pipeline and build dynamic fixtures --- .buildkite/expo-pipeline.block.yml | 10 + .buildkite/expo-pipeline.yml | 30 +++ .buildkite/scripts/packages.json | 5 + Gemfile | 1 + bin/generate-expo-fixture | 176 ++++++++++++++++++ .../features/fixtures/expo/fakekeys.jks | Bin 0 -> 2065 bytes .../features/fixtures/expo/npmconfig | 1 + 7 files changed, 223 insertions(+) create mode 100644 .buildkite/expo-pipeline.block.yml create mode 100644 .buildkite/expo-pipeline.yml create mode 100755 bin/generate-expo-fixture create mode 100644 test/react-native/features/fixtures/expo/fakekeys.jks create mode 100644 test/react-native/features/fixtures/expo/npmconfig diff --git a/.buildkite/expo-pipeline.block.yml b/.buildkite/expo-pipeline.block.yml new file mode 100644 index 000000000..5451658a5 --- /dev/null +++ b/.buildkite/expo-pipeline.block.yml @@ -0,0 +1,10 @@ +steps: + - block: "Trigger expo pipeline" + key: "trigger-expo-pipeline" + + - label: ":pipeline_upload: Expo pipeline" + depends_on: "trigger-expo-pipeline" + agents: + queue: macos + timeout_in_minutes: 2 + command: buildkite-agent pipeline upload .buildkite/expo-pipeline.yml \ No newline at end of file diff --git a/.buildkite/expo-pipeline.yml b/.buildkite/expo-pipeline.yml new file mode 100644 index 000000000..09cfd1515 --- /dev/null +++ b/.buildkite/expo-pipeline.yml @@ -0,0 +1,30 @@ +agents: + queue: "opensource" + +steps: + - label: ':android: Build expo APK' + key: "build-expo-apk" + timeout_in_minutes: 20 + agents: + queue: "macos-14" + env: + JAVA_VERSION: "17" + EXPO_VERSION: "52" + BUILD_ANDROID: 1 + artifact_paths: test/react-native/features/fixtures/generated/expo/52/test-fixture/output.apk + commands: + - bundle install + - ./bin/generate-expo-fixture + + - label: ':mac: Build expo IPA' + key: "build-expo-ipa" + timeout_in_minutes: 20 + agents: + queue: "macos-14" + env: + EXPO_VERSION: "52" + BUILD_IOS: 1 + artifact_paths: test/react-native/features/fixtures/generated/expo/52/test-fixture/output.ipa + commands: + - bundle install + - ./bin/generate-expo-fixture \ No newline at end of file diff --git a/.buildkite/scripts/packages.json b/.buildkite/scripts/packages.json index 2b381c9c4..0ac183e90 100644 --- a/.buildkite/scripts/packages.json +++ b/.buildkite/scripts/packages.json @@ -52,5 +52,10 @@ "pipeline": ".buildkite/react-native-navigation-pipeline.full.yml", "block": ".buildkite/react-native-navigation-pipeline.full.block.yml", "paths": [] + }, + { + "pipeline": ".buildkite/expo-pipeline.yml", + "block": ".buildkite/expo-pipeline.block.yml", + "paths": [] } ] \ No newline at end of file diff --git a/Gemfile b/Gemfile index 00a6b3f80..28bba2280 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,4 @@ source 'https://rubygems.org' gem 'cocoapods' +gem 'fastlane' diff --git a/bin/generate-expo-fixture b/bin/generate-expo-fixture new file mode 100755 index 000000000..f11552581 --- /dev/null +++ b/bin/generate-expo-fixture @@ -0,0 +1,176 @@ +#!/usr/bin/env node + +const { execFileSync, execSync } = require('child_process') +const { resolve } = require('path') +const fs = require('fs') + +if (!process.env.EXPO_VERSION) { + console.error('Please provide an Expo version') + process.exit(1) +} + +if (!process.env.EXPO_EAS_PROJECT_ID) { + console.error('EXPO_EAS_PROJECT_ID is not set') + process.exit(1) +} + +if (!process.env.EXPO_CREDENTIALS_DIR) { + console.error('EXPO_CREDENTIALS_DIR is not set') + process.exit(1) +} + +const expoVersion = process.env.EXPO_VERSION +const ROOT_DIR = resolve(__dirname, '../') + +const buildDir = resolve(ROOT_DIR, `test/react-native/features/fixtures/generated/expo/${expoVersion}`) +const easWorkingDir = `${buildDir}/build` +const fixtureDir = `${buildDir}/test-fixture` + +const PACKAGE_NAMES = [ + '@bugsnag/core-performance', + '@bugsnag/delivery-fetch-performance', + '@bugsnag/plugin-react-native-navigation-performance', + '@bugsnag/plugin-react-navigation-performance', + '@bugsnag/react-native-performance', + '@bugsnag/request-tracker-performance' +] + +const PACKAGE_DIRECTORIES = [ + `${ROOT_DIR}/packages/core`, + `${ROOT_DIR}/packages/delivery-fetch`, + `${ROOT_DIR}/packages/platforms/react-native`, + `${ROOT_DIR}/packages/plugin-react-native-navigation`, + `${ROOT_DIR}/packages/plugin-react-navigation`, + `${ROOT_DIR}/packages/request-tracker`, + `${ROOT_DIR}/test/react-native/features/fixtures/scenario-launcher` +] + +const DEPENDENCIES = [ + `@bugsnag/react-native`, + `@react-native-community/netinfo@11`, + `react-native-file-access@3` +] + +if (!process.env.SKIP_BUILD_PACKAGES) { + // run npm install in the root directory + execFileSync('npm', ['install'], { cwd: ROOT_DIR, stdio: 'inherit' }) + + // build the packages + const buildArgs = ['run', 'build', '--scope', PACKAGE_NAMES.join(' --scope ')] + execFileSync('npm', buildArgs, { cwd: ROOT_DIR, stdio: 'inherit', env: { ...process.env, ENABLE_TEST_CONFIGURATION: 1 } }) +} + +if (!process.env.SKIP_GENERATE_FIXTURE) { + // remove the fixture directory if it already exists + if (fs.existsSync(fixtureDir)) { + fs.rmSync(fixtureDir, { recursive: true, force: true }) + } + + if (fs.existsSync(easWorkingDir)) { + fs.rmSync(easWorkingDir, { recursive: true, force: true }) + } + + fs.mkdirSync(fixtureDir, { recursive: true }) + + // create the test fixture + const expoInitArgs = ['create-expo-app', 'test-fixture', '--no-install', '--template', `default@${expoVersion}`] + execFileSync('npx', expoInitArgs, { cwd: buildDir, stdio: 'inherit' }) + + // install the required packages + installFixtureDependencies() + + // modify the app.json file + const appConfig = require(`${fixtureDir}/app.json`) + appConfig.expo.ios = { + ...appConfig.expo.ios, + bundleIdentifier: 'com.bugsnag.expo.fixture', + buildNumber: '1', + infoPlist: { + NSAppTransportSecurity: { + NSAllowsArbitraryLoads: true + } + } + } + + // TODO: need to set usesCleartextTraffic to true for Android + // look into using expo-build-properties plugin https://docs.expo.dev/versions/latest/sdk/build-properties/#pluginconfigtypeandroid + appConfig.expo.android = { + ...appConfig.expo.android, + package: 'com.bugsnag.expo.fixture', + versionCode: 1, + permissions: ['INTERNET'], + config: { + googleMaps: { + apiKey: process.env.GOOGLE_MAPS_API_KEY + } + } + } + + fs.writeFileSync(`${fixtureDir}/app.json`, JSON.stringify(appConfig, null, 2)) + + // eas init + const easInitArgs = ['eas-cli@latest', 'init', '--id', `${process.env.EXPO_EAS_PROJECT_ID}`] + execFileSync('npx', easInitArgs, { cwd: fixtureDir, stdio: 'inherit' }) + + // eas build configure + const easBuildConfigArgs = ['eas-cli@latest', 'build:configure', '--platform', 'all'] + execFileSync('npx', easBuildConfigArgs, { cwd: fixtureDir, stdio: 'inherit', env: { ...process.env, EAS_NO_VCS: 1 } }) + + // modify the eas.json file + const easConfig = require(`${fixtureDir}/eas.json`) + + easConfig.cli.appVersionSource = 'local' + easConfig.build.production = { + distribution: 'internal', + credentialsSource: 'local', + android: { + buildType: 'apk' + }, + ios: { + enterpriseProvisioning: 'universal' + } + } + + fs.writeFileSync(`${fixtureDir}/eas.json`, JSON.stringify(easConfig, null, 2)) + + // copy keystore to the fixture directory + const keyStorePath = resolve(ROOT_DIR, `test/react-native/features/fixtures/expo/fakekeys.jks`) + fs.copyFileSync(keyStorePath, resolve(fixtureDir, 'fakekeys.jks')) + + // copy npmrc + const npmrcPath = resolve(ROOT_DIR, `test/react-native/features/fixtures/expo/npmconfig`) + fs.copyFileSync(npmrcPath, resolve(fixtureDir, '.npmrc')) + + // copy credentials to the fixture directory + const credentialsFiles = fs.readdirSync(process.env.EXPO_CREDENTIALS_DIR) + + for (const file of credentialsFiles) { + fs.copyFileSync(resolve(process.env.EXPO_CREDENTIALS_DIR, file), resolve(fixtureDir, file)) + } +} + +// build Android fixture +if (process.env.BUILD_ANDROID === 'true' || process.env.BUILD_ANDROID === '1') { + const easBuildArgs = ['eas-cli@latest', 'build', '--local', '--platform', 'android', '--profile', 'production', '--output', 'output.apk'] + execFileSync('npx', easBuildArgs, { cwd: `${fixtureDir}`, stdio: 'inherit', env: { ...process.env, EAS_LOCAL_BUILD_WORKINGDIR: easWorkingDir, EAS_LOCAL_BUILD_SKIP_CLEANUP: 1, EAS_NO_VCS: 1, EAS_PROJECT_ROOT: fixtureDir } }) +} + +// build iOS fixture +if (process.env.BUILD_IOS === 'true' || process.env.BUILD_IOS === '1') { + const easBuildArgs = ['eas-cli@latest', 'build', '--local', '--platform', 'ios', '--profile', 'production', '--output', 'output.ipa', '--non-interactive'] + execFileSync('npx', easBuildArgs, { cwd: `${fixtureDir}`, stdio: 'inherit', env: { ...process.env, EAS_LOCAL_BUILD_WORKINGDIR: easWorkingDir, EAS_LOCAL_BUILD_SKIP_CLEANUP: 1, EAS_NO_VCS: 1, EAS_PROJECT_ROOT: fixtureDir } }) +} + +/** Pack and install local packages from this repo */ +function installFixtureDependencies() { + // pack the required packages into the fixture directory + for (const package of PACKAGE_DIRECTORIES) { + const libraryPackArgs = ['pack', package, '--pack-destination', fixtureDir] + execFileSync('npm', libraryPackArgs, { cwd: ROOT_DIR, stdio: 'inherit' }) + } + + const fixtureDependencyArgs = DEPENDENCIES.join(' ') + + // install test fixture dependencies and local packages + execSync(`npm install --save ${fixtureDependencyArgs} *.tgz`, { cwd: fixtureDir, stdio: 'inherit' }) +} \ No newline at end of file diff --git a/test/react-native/features/fixtures/expo/fakekeys.jks b/test/react-native/features/fixtures/expo/fakekeys.jks new file mode 100644 index 0000000000000000000000000000000000000000..75604b1b5e05983d421419cb7b8a4d58de67ddca GIT binary patch literal 2065 zcmV+s2=4d(?f&fm0006200031000312ykI@b9ZlYWB>pGU|Ndp6#xJQ0Wg9D{V)y& z3M&Qy1OX}n5di@O00e>r=@4@P*)$;qgxj1S^RyramUJElrX&ZUIUhr;3WY3tK?3<=v&%x7$pWYs@3?`AELi0v$dj zFP<&mq9SFI?9<+f?AX?C)|OXJr&aLv79SgAwA%iAs z-av~+)`)5c7@%HD$&Hus?sT3%v?)Lvrm~l?`_q@2Vm@g}gq?Fse0`KxErZ{55vl(O z^rTNRZLYuSD;~i3gX#qy8QGu=_7c`;Q^4dqq!0|1;8|n@AazyZuQFN1YKFuAcAjR= z2eH63=5MVnKGXb5cWO;_S=dsV=o2Cfw!xkrDbi!^ za>c0rP-|oaC6kI9Z(U>=CWCaNDrA((+Obc7r1=OJ8g4i1`_4{q)@E~n{1Rg$l?df| z5H3I{lIce|IEWzB0rF|KQF`lAq~R|=s7B0p}#T>Cz-uCoT_TBS*^YDF*RN zO~a4`A(^!L(|6Q$41fi?(E5b@&m(3*#!8!=AYo}+6X+XLy zrw&R(I0imsj2ymAcVNG;#dJPJZ7PivD(#)1;2FDi%RT4NSb1Rp3b9pMkU*wHs1zH(Y zVd*|o{c{$js4-Vc9%IV^+}sK^=TY8b=j40iE7&{>=P9wIV$HSgpk*Up%oMI70S&!} z?1`o@W&}&o-^-9nZry~|q@2lzKPY~7{on;np`#S?{Uo44fBNGa{%25Wn=&{8FJuK7 zZ!&p2|MX=A!*e4CzF5(zE;#Yii2#E!D$NsAu8~tEw`@d#z8zuOuvF}?;RV7UzBEziY$e`E zlGR3Re+FO!F?{8pY<2LBkSg!6WtI(g3R3_800966SS~d%IRF3x$1s8d!!UvYtDpk{ z0RjR9B*e5lFbxI?Duzgg_YDC73k3i$5-|`k4h92N1PT)eLUm_zZeeFI9v2NUH!(6X zH8C_XIWRI>7Y#HrF)}bYF*GqbFfv*&5-|`k4h92N1PT)eLUm_zZeeFIf&n5h4F(A+ zhDe6@4FLfG1potr0S^E$f&mHwf&l>lwQ%YvekYxmdCcM#SRWbo9BmS3dB2N$Z$GcU z7n{oZ${?VfXz9FQ-+n{Jtp5S?*wwWG!9MZ%^Te1k_>7D2!@volX=FT-71ys|FqZ&J zq*lB%<7H}KVcr;PpI4vHu*6EG5=1&1qu3P}03=(f!}7wL0d?U)5EhLUCM)$n6$YeSoCIC8 z;8X;F-?>NP`LXjIVJZzr3*{BqA54Wx#E_E2OIR?hUuhhKn}6GJ`O>jRyP{$_duui9 z?UfMS;E#pcap64j^h0|8*^deh0s{d60iz)>A21yT163Uk1QrAo<}@&~nKVZ4XL#8# zW^b`e-?_dOFbxI?Duzgg_YDC73k3iJf&l>lI9pX_{IUA};%6)%o}ACYe9GxBRAr^7 z$4-py;JZ5C{DJz~kE-LK(&B-$b{s(V(s3MWn**hP+5=IaC{nWtURHYeS)GKN{CGGt zT3(55=?v zSw+p2hybQMRCB>=BoH9>iuw?~amFp`Chld literal 0 HcmV?d00001 diff --git a/test/react-native/features/fixtures/expo/npmconfig b/test/react-native/features/fixtures/expo/npmconfig new file mode 100644 index 000000000..214c29d13 --- /dev/null +++ b/test/react-native/features/fixtures/expo/npmconfig @@ -0,0 +1 @@ +registry=https://registry.npmjs.org/ From 322ee367c3572637579417b0550be6d915b31731 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Tue, 29 Apr 2025 11:20:45 +0100 Subject: [PATCH 02/16] test(react-native): launch scenarios in expo fixture --- bin/generate-expo-fixture | 4 +++ .../features/fixtures/expo/index.tsx | 32 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 test/react-native/features/fixtures/expo/index.tsx diff --git a/bin/generate-expo-fixture b/bin/generate-expo-fixture index f11552581..1c68cef1c 100755 --- a/bin/generate-expo-fixture +++ b/bin/generate-expo-fixture @@ -133,6 +133,10 @@ if (!process.env.SKIP_GENERATE_FIXTURE) { fs.writeFileSync(`${fixtureDir}/eas.json`, JSON.stringify(easConfig, null, 2)) + // copy the index.tsx file + const indexPath = resolve(ROOT_DIR, `test/react-native/features/fixtures/expo/index.tsx`) + fs.copyFileSync(indexPath, resolve(fixtureDir, 'app/(tabs)/index.tsx')) + // copy keystore to the fixture directory const keyStorePath = resolve(ROOT_DIR, `test/react-native/features/fixtures/expo/fakekeys.jks`) fs.copyFileSync(keyStorePath, resolve(fixtureDir, 'fakekeys.jks')) diff --git a/test/react-native/features/fixtures/expo/index.tsx b/test/react-native/features/fixtures/expo/index.tsx new file mode 100644 index 000000000..9af32aeba --- /dev/null +++ b/test/react-native/features/fixtures/expo/index.tsx @@ -0,0 +1,32 @@ +import { SafeAreaView, StyleSheet, Text } from 'react-native'; +import React, { useEffect, useState } from 'react' + +//@ts-expect-error no types +import { launchScenario, ScenarioContext } from '@bugsnag/react-native-performance-scenarios' + +export default function HomeScreen() { + + const [currentScenario, setCurrentScenario] = useState(null) + + useEffect(() => { + launchScenario(setCurrentScenario) + }, []) + + return ( + + + Expo Performance Test App + { currentScenario && } + + + ) +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + padding: 100, + } +}) From e03e971943fe7cd6988d81ea73a658f6727750ca Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Tue, 29 Apr 2025 11:57:36 +0100 Subject: [PATCH 03/16] ci(react-native): add expo tests to CI pipeline --- .buildkite/expo-pipeline.yml | 80 +++++++++++++++++++++++++++++++++++- docker-compose.yml | 1 + 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/.buildkite/expo-pipeline.yml b/.buildkite/expo-pipeline.yml index 09cfd1515..8cfee8210 100644 --- a/.buildkite/expo-pipeline.yml +++ b/.buildkite/expo-pipeline.yml @@ -2,6 +2,9 @@ agents: queue: "opensource" steps: + # + # Test fixtures + # - label: ':android: Build expo APK' key: "build-expo-apk" timeout_in_minutes: 20 @@ -27,4 +30,79 @@ steps: artifact_paths: test/react-native/features/fixtures/generated/expo/52/test-fixture/output.ipa commands: - bundle install - - ./bin/generate-expo-fixture \ No newline at end of file + - ./bin/generate-expo-fixture + + # + # End-to-end tests + # + - label: ":bitbar: :android: Expo {{matrix}} Android 15 end-to-end tests" + depends_on: "build-expo-apk" + timeout_in_minutes: 20 + plugins: + artifacts#v1.9.0: + download: "test/react-native/features/fixtures/generated/expo/{{matrix}}/test-fixture/output.apk" + upload: ./test/react-native/maze_output/**/* + docker-compose#v4.7.0: + pull: react-native-maze-runner + run: react-native-maze-runner + service-ports: true + command: + - --app=/app/features/fixtures/generated/expo/{{matrix}}/test-fixture/output.apk + - --farm=bb + - --device=ANDROID_15 + - --a11y-locator + - --fail-fast + - --appium-version=1.22 + - --no-tunnel + - --aws-public-ip + test-collector#v1.10.2: + files: "reports/TEST-*.xml" + format: "junit" + branch: "^main|next$$" + api-token-env-name: "REACT_NATIVE_PERFORMANCE_BUILDKITE_ANALYTICS_TOKEN" + env: + EXPO_VERSION: "{{matrix}}" + retry: + manual: + permit_on_passed: true + concurrency: 25 + concurrency_group: "bitbar" + concurrency_method: eager + matrix: + - "52" + + - label: ":bitbar: :mac: Expo {{matrix}} iOS 16 end-to-end tests" + depends_on: "build-expo-ipa" + timeout_in_minutes: 20 + plugins: + artifacts#v1.9.0: + download: "test/react-native/features/fixtures/generated/expo/{{matrix}}/test-fixture/output.ipa" + upload: ./test/react-native/maze_output/**/* + docker-compose#v4.12.0: + pull: react-native-maze-runner + run: react-native-maze-runner + service-ports: true + command: + - --app=/app/features/fixtures/generated/expo/{{matrix}}/test-fixture/output.ipa + - --farm=bb + - --device=IOS_16 + - --a11y-locator + - --fail-fast + - --appium-version=1.22 + - --no-tunnel + - --aws-public-ip + test-collector#v1.10.2: + files: "reports/TEST-*.xml" + format: "junit" + branch: "^main|next$$" + api-token-env-name: "REACT_NATIVE_PERFORMANCE_BUILDKITE_ANALYTICS_TOKEN" + env: + EXPO_VERSION: "{{matrix}}" + retry: + manual: + permit_on_passed: true + concurrency: 25 + concurrency_group: "bitbar" + concurrency_method: eager + matrix: + - "52" diff --git a/docker-compose.yml b/docker-compose.yml index ccb5c77a7..ae51b41c8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -80,6 +80,7 @@ services: BITBAR_USERNAME: BITBAR_ACCESS_KEY: RN_VERSION: + EXPO_VERSION: RCT_NEW_ARCH_ENABLED: REACT_NATIVE_NAVIGATION: NATIVE_INTEGRATION: From d861ec653b5da16f4ed9dd271fc95d19b812694f Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Tue, 29 Apr 2025 12:00:36 +0100 Subject: [PATCH 04/16] test(react-native): skip some tests on expo --- test/react-native/features/app-start-spans.feature | 4 ++-- test/react-native/features/error-correlation.feature | 1 + test/react-native/features/react-navigation.feature | 2 +- test/react-native/features/support/env.rb | 4 ++++ 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/test/react-native/features/app-start-spans.feature b/test/react-native/features/app-start-spans.feature index 611dda22d..95356f110 100644 --- a/test/react-native/features/app-start-spans.feature +++ b/test/react-native/features/app-start-spans.feature @@ -2,7 +2,7 @@ Feature: App Start spans # Skipped on 0.79/Android/Old Arch - see PLAT-14095 - @skip_android_old_arch_079 + @skip_android_old_arch_079 @skip_expo Scenario: App starts are automatically instrumented When I run 'AppStartScenario' And I relaunch the app after shutdown @@ -23,7 +23,7 @@ Feature: App Start spans And the trace payload field "resourceSpans.0.scopeSpans.0.spans.0" string attribute "bugsnag.app_start.type" equals "ReactNativeInit" # Skipped on 0.79/Android/Old Arch - see PLAT-14095 - @skip_android_old_arch_079 + @skip_android_old_arch_079 @skip_expo Scenario: A wrapper component provider can be provided as a config option When I run 'WrapperComponentProviderScenario' And I relaunch the app after shutdown diff --git a/test/react-native/features/error-correlation.feature b/test/react-native/features/error-correlation.feature index ea35329fa..c78b57e20 100644 --- a/test/react-native/features/error-correlation.feature +++ b/test/react-native/features/error-correlation.feature @@ -1,3 +1,4 @@ +@skip_expo Feature: Error correlation Scenario: Reported errors include the current trace and span id diff --git a/test/react-native/features/react-navigation.feature b/test/react-native/features/react-navigation.feature index 12361ccf7..ffa6afb77 100644 --- a/test/react-native/features/react-navigation.feature +++ b/test/react-native/features/react-navigation.feature @@ -1,7 +1,7 @@ @react_navigation Feature: Navigation spans with React Navigation - @skip_new_arch + @skip_new_arch @skip_expo Scenario: Navigation Spans are automatically instrumented When I run 'ReactNavigationScenario' And I wait to receive a sampling request diff --git a/test/react-native/features/support/env.rb b/test/react-native/features/support/env.rb index f8d838054..5e24d9c85 100644 --- a/test/react-native/features/support/env.rb +++ b/test/react-native/features/support/env.rb @@ -37,4 +37,8 @@ Before('@skip_android_old_arch_079') do |scenario| current_version = ENV['RN_VERSION'].nil? ? 0 : ENV['RN_VERSION'].to_f skip_this_scenario("Skipping scenario") if Maze::Helper.get_current_platform == 'android' && !ENV['RCT_NEW_ARCH_ENABLED'].eql?('1') && current_version == 0.79 +end + +Before('@skip_expo') do |scenario| + skip_this_scenario("Skipping scenario: Not supported in Expo") if ENV["EXPO_VERSION"] end \ No newline at end of file From 7a337f1edad135f9dd4d8b6a38b2d20240a22dc9 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Tue, 29 Apr 2025 12:03:26 +0100 Subject: [PATCH 05/16] test(react-native): remove react-native-dotenv from test fixture It doesn't work on Expo and isn't really used much anyway --- bin/generate-react-native-fixture | 8 +------- test/react-native/features/fixtures/app/env | 3 --- .../scenario-launcher/lib/ScenarioLauncher.js | 15 +-------------- 3 files changed, 2 insertions(+), 24 deletions(-) delete mode 100644 test/react-native/features/fixtures/app/env diff --git a/bin/generate-react-native-fixture b/bin/generate-react-native-fixture index 7cd97d748..5725d1cf0 100755 --- a/bin/generate-react-native-fixture +++ b/bin/generate-react-native-fixture @@ -67,7 +67,6 @@ const netinfoVersion = parseFloat(reactNativeVersion) <= 0.64 ? '10.0.0' : '11.3 const DEPENDENCIES = [ `@bugsnag/react-native@${process.env.NOTIFIER_VERSION}`, `@react-native-community/netinfo@${netinfoVersion}`, - `react-native-dotenv`, `react-native-file-access@${reactNativeFileAccessVersion}` ] @@ -306,14 +305,9 @@ function replaceGeneratedFixtureFiles() { resolve(fixtureDir, 'exportOptions.plist') ) - fs.copyFileSync( - resolve(ROOT_DIR, 'test/react-native/features/fixtures/app/env'), - resolve(fixtureDir, '.env') - ) - const babelConfig = require(resolve(fixtureDir, 'babel.config.js')) if (!babelConfig.plugins) babelConfig.plugins = [] - babelConfig.plugins.push('@babel/plugin-transform-export-namespace-from', 'module:react-native-dotenv') + babelConfig.plugins.push('@babel/plugin-transform-export-namespace-from') fs.writeFileSync(resolve(fixtureDir, 'babel.config.js'), `module.exports = ${util.inspect(babelConfig)}`) } diff --git a/test/react-native/features/fixtures/app/env b/test/react-native/features/fixtures/app/env deleted file mode 100644 index faae6a735..000000000 --- a/test/react-native/features/fixtures/app/env +++ /dev/null @@ -1,3 +0,0 @@ -REACT_APP_SCENARIO_NAME= -REACT_APP_API_KEY= -REACT_APP_ENDPOINT= \ No newline at end of file diff --git a/test/react-native/features/fixtures/scenario-launcher/lib/ScenarioLauncher.js b/test/react-native/features/fixtures/scenario-launcher/lib/ScenarioLauncher.js index 1472bc49d..1e0a7ab8f 100644 --- a/test/react-native/features/fixtures/scenario-launcher/lib/ScenarioLauncher.js +++ b/test/react-native/features/fixtures/scenario-launcher/lib/ScenarioLauncher.js @@ -5,7 +5,6 @@ import { NativeScenarioLauncher } from './native' import { wrapperComponentProvider } from '../scenarios/WrapperComponentProviderScenario' import React from 'react' import BugsnagPerformance from '@bugsnag/react-native-performance' -import { REACT_APP_API_KEY, REACT_APP_ENDPOINT, REACT_APP_SCENARIO_NAME } from '@env' async function loadReactNavigationScenario (scenario) { if (typeof scenario.registerScreens === 'function') { @@ -73,19 +72,7 @@ export async function launchScenario (setScenario, clearPersistedData = true) { await clearPersistedState() } - let command - - if (REACT_APP_SCENARIO_NAME && REACT_APP_API_KEY) { - command = { - action: 'run-scenario', - scenario_name: REACT_APP_SCENARIO_NAME, - api_key: REACT_APP_API_KEY, - endpoint: REACT_APP_ENDPOINT - } - } else { - command = await getCurrentCommand() - } - + const command = await getCurrentCommand() switch (command.action) { case 'run-scenario': return await runScenario( From 0dee82f810320bb7b62c0331712f231fd008d295 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Tue, 29 Apr 2025 12:04:50 +0100 Subject: [PATCH 06/16] test(react-native): fix missing import in scenario persistence --- .../features/fixtures/scenario-launcher/lib/Persistence.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/react-native/features/fixtures/scenario-launcher/lib/Persistence.js b/test/react-native/features/fixtures/scenario-launcher/lib/Persistence.js index 554f789e5..0826c4f45 100644 --- a/test/react-native/features/fixtures/scenario-launcher/lib/Persistence.js +++ b/test/react-native/features/fixtures/scenario-launcher/lib/Persistence.js @@ -1,3 +1,4 @@ +import { Platform } from 'react-native' import { Dirs, FileSystem } from 'react-native-file-access' const PERSISTED_STATE_VERSION = 1 From 865688cbbc0878fd3e7a2397a4034716a11d1887 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Tue, 29 Apr 2025 13:46:08 +0100 Subject: [PATCH 07/16] test(react-native): update resource attribute test assertion --- test/react-native/features/manual-spans.feature | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/react-native/features/manual-spans.feature b/test/react-native/features/manual-spans.feature index 49e447dff..494192008 100644 --- a/test/react-native/features/manual-spans.feature +++ b/test/react-native/features/manual-spans.feature @@ -43,7 +43,9 @@ Feature: Manual spans When I run 'ManualSpanScenario' And I wait to receive a sampling request And I wait for 1 span - Then the trace payload field "resourceSpans.0.resource" string attribute "service.name" equals "com.bugsnag.fixtures.reactnative.performance" + Then the trace payload field "resourceSpans.0.resource" string attribute "service.name" is one of: + | com.bugsnag.fixtures.reactnative.performance | + | com.bugsnag.expo.fixture | And the trace payload field "resourceSpans.0.resource" string attribute "host.arch" exists And the trace payload field "resourceSpans.0.resource" string attribute "device.model.identifier" exists And the trace payload field "resourceSpans.0.resource" string attribute "bugsnag.app.version_code" equals the platform-dependent string: From 1c28e43c2686964fb0057dbcdfc04ac7f7d44ad9 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Tue, 29 Apr 2025 16:27:12 +0100 Subject: [PATCH 08/16] test(react-native): set usesClearTestTraffic for expo fixture --- bin/generate-expo-fixture | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/bin/generate-expo-fixture b/bin/generate-expo-fixture index 1c68cef1c..706017bf7 100755 --- a/bin/generate-expo-fixture +++ b/bin/generate-expo-fixture @@ -48,12 +48,13 @@ const PACKAGE_DIRECTORIES = [ const DEPENDENCIES = [ `@bugsnag/react-native`, `@react-native-community/netinfo@11`, - `react-native-file-access@3` + `react-native-file-access@3`, + 'expo-build-properties' ] if (!process.env.SKIP_BUILD_PACKAGES) { - // run npm install in the root directory - execFileSync('npm', ['install'], { cwd: ROOT_DIR, stdio: 'inherit' }) + // run npm ci in the root directory + execFileSync('npm', ['ci', ['--no-audit']], { cwd: ROOT_DIR, stdio: 'inherit' }) // build the packages const buildArgs = ['run', 'build', '--scope', PACKAGE_NAMES.join(' --scope ')] @@ -92,8 +93,6 @@ if (!process.env.SKIP_GENERATE_FIXTURE) { } } - // TODO: need to set usesCleartextTraffic to true for Android - // look into using expo-build-properties plugin https://docs.expo.dev/versions/latest/sdk/build-properties/#pluginconfigtypeandroid appConfig.expo.android = { ...appConfig.expo.android, package: 'com.bugsnag.expo.fixture', @@ -106,6 +105,19 @@ if (!process.env.SKIP_GENERATE_FIXTURE) { } } + const plugins = appConfig.expo.plugins || [] + appConfig.expo.plugins = [ + ...plugins, + [ + 'expo-build-properties', + { + android: { + usesCleartextTraffic: true + } + } + ] + ] + fs.writeFileSync(`${fixtureDir}/app.json`, JSON.stringify(appConfig, null, 2)) // eas init @@ -176,5 +188,5 @@ function installFixtureDependencies() { const fixtureDependencyArgs = DEPENDENCIES.join(' ') // install test fixture dependencies and local packages - execSync(`npm install --save ${fixtureDependencyArgs} *.tgz`, { cwd: fixtureDir, stdio: 'inherit' }) + execSync(`npm install ${fixtureDependencyArgs} *.tgz --save --no-audit --legacy-peer-deps`, { cwd: fixtureDir, stdio: 'inherit' }) } \ No newline at end of file From 18a159731d8f52d96e7ff640fff70e954b367213 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Tue, 29 Apr 2025 17:26:34 +0100 Subject: [PATCH 09/16] ci(react-native): add full expo pipeline --- .buildkite/expo-pipeline.full.block.yml | 10 ++ .buildkite/expo-pipeline.full.yml | 116 +++++++++++++ .buildkite/expo-pipeline.yml | 210 ++++++++++++------------ .buildkite/scripts/packages.json | 5 + 4 files changed, 239 insertions(+), 102 deletions(-) create mode 100644 .buildkite/expo-pipeline.full.block.yml create mode 100644 .buildkite/expo-pipeline.full.yml diff --git a/.buildkite/expo-pipeline.full.block.yml b/.buildkite/expo-pipeline.full.block.yml new file mode 100644 index 000000000..209095469 --- /dev/null +++ b/.buildkite/expo-pipeline.full.block.yml @@ -0,0 +1,10 @@ +steps: + - block: "Trigger expo full pipeline" + key: "trigger-expo-full-pipeline" + + - label: ":pipeline_upload: Full Expo pipeline" + depends_on: "trigger-expo-full-pipeline" + agents: + queue: macos + timeout_in_minutes: 2 + command: buildkite-agent pipeline upload .buildkite/expo-pipeline.full.yml \ No newline at end of file diff --git a/.buildkite/expo-pipeline.full.yml b/.buildkite/expo-pipeline.full.yml new file mode 100644 index 000000000..337ab7ac8 --- /dev/null +++ b/.buildkite/expo-pipeline.full.yml @@ -0,0 +1,116 @@ +agents: + queue: "opensource" + +steps: + - group: "Expo Tests" + steps: + # + # Test fixtures + # + - label: ':android: Build Expo {{matrix}} APK' + key: "build-expo-apk-full" + timeout_in_minutes: 20 + agents: + queue: "macos-14" + env: + JAVA_VERSION: "17" + EXPO_VERSION: "{{matrix}}" + BUILD_ANDROID: 1 + artifact_paths: test/react-native/features/fixtures/generated/expo/**/test-fixture/output.apk + commands: + - bundle install + - ./bin/generate-expo-fixture + matrix: + - "51" + - "50" + + - label: ':mac: Build Expo {{matrix}} IPA' + key: "build-expo-ipa-full" + timeout_in_minutes: 20 + agents: + queue: "macos-14" + env: + EXPO_VERSION: "{{matrix}}" + BUILD_IOS: 1 + artifact_paths: test/react-native/features/fixtures/generated/expo/**/test-fixture/output.ipa + commands: + - bundle install + - ./bin/generate-expo-fixture + matrix: + - "51" + - "50" + + # + # End-to-end tests + # + - label: ":bitbar: :android: Expo {{matrix}} Android 15 end-to-end tests" + depends_on: "build-expo-apk-full" + timeout_in_minutes: 20 + plugins: + artifacts#v1.9.0: + download: "test/react-native/features/fixtures/generated/expo/{{matrix}}/test-fixture/output.apk" + upload: ./test/react-native/maze_output/**/* + docker-compose#v4.7.0: + pull: react-native-maze-runner + run: react-native-maze-runner + service-ports: true + command: + - --app=/app/features/fixtures/generated/expo/{{matrix}}/test-fixture/output.apk + - --farm=bb + - --device=ANDROID_15 + - --a11y-locator + - --appium-version=1.22 + - --no-tunnel + - --aws-public-ip + test-collector#v1.10.2: + files: "reports/TEST-*.xml" + format: "junit" + branch: "^main|next$$" + api-token-env-name: "REACT_NATIVE_PERFORMANCE_BUILDKITE_ANALYTICS_TOKEN" + env: + EXPO_VERSION: "{{matrix}}" + retry: + manual: + permit_on_passed: true + concurrency: 25 + concurrency_group: "bitbar" + concurrency_method: eager + matrix: + - "51" + - "50" + + - label: ":bitbar: :mac: Expo {{matrix}} iOS 16 end-to-end tests" + depends_on: "build-expo-ipa-full" + timeout_in_minutes: 20 + plugins: + artifacts#v1.9.0: + download: "test/react-native/features/fixtures/generated/expo/{{matrix}}/test-fixture/output.ipa" + upload: ./test/react-native/maze_output/**/* + docker-compose#v4.12.0: + pull: react-native-maze-runner + run: react-native-maze-runner + service-ports: true + command: + - --app=/app/features/fixtures/generated/expo/{{matrix}}/test-fixture/output.ipa + - --farm=bb + - --device=IOS_16 + - --a11y-locator + - --appium-version=1.22 + - --no-tunnel + - --aws-public-ip + test-collector#v1.10.2: + files: "reports/TEST-*.xml" + format: "junit" + branch: "^main|next$$" + api-token-env-name: "REACT_NATIVE_PERFORMANCE_BUILDKITE_ANALYTICS_TOKEN" + env: + EXPO_VERSION: "{{matrix}}" + retry: + manual: + permit_on_passed: true + concurrency: 25 + concurrency_group: "bitbar" + concurrency_method: eager + matrix: + - "51" + - "50" diff --git a/.buildkite/expo-pipeline.yml b/.buildkite/expo-pipeline.yml index 8cfee8210..cad102723 100644 --- a/.buildkite/expo-pipeline.yml +++ b/.buildkite/expo-pipeline.yml @@ -2,107 +2,113 @@ agents: queue: "opensource" steps: - # - # Test fixtures - # - - label: ':android: Build expo APK' - key: "build-expo-apk" - timeout_in_minutes: 20 - agents: - queue: "macos-14" - env: - JAVA_VERSION: "17" - EXPO_VERSION: "52" - BUILD_ANDROID: 1 - artifact_paths: test/react-native/features/fixtures/generated/expo/52/test-fixture/output.apk - commands: - - bundle install - - ./bin/generate-expo-fixture - - - label: ':mac: Build expo IPA' - key: "build-expo-ipa" - timeout_in_minutes: 20 - agents: - queue: "macos-14" - env: - EXPO_VERSION: "52" - BUILD_IOS: 1 - artifact_paths: test/react-native/features/fixtures/generated/expo/52/test-fixture/output.ipa - commands: - - bundle install - - ./bin/generate-expo-fixture + - group: "Expo Tests" + steps: + # + # Test fixtures + # + - label: ':android: Build Expo {{matrix}} APK' + key: "build-expo-apk" + timeout_in_minutes: 20 + agents: + queue: "macos-14" + env: + JAVA_VERSION: "17" + EXPO_VERSION: "{{matrix}}" + BUILD_ANDROID: 1 + artifact_paths: test/react-native/features/fixtures/generated/expo/**/test-fixture/output.apk + commands: + - bundle install + - ./bin/generate-expo-fixture + matrix: + - "52" - # - # End-to-end tests - # - - label: ":bitbar: :android: Expo {{matrix}} Android 15 end-to-end tests" - depends_on: "build-expo-apk" - timeout_in_minutes: 20 - plugins: - artifacts#v1.9.0: - download: "test/react-native/features/fixtures/generated/expo/{{matrix}}/test-fixture/output.apk" - upload: ./test/react-native/maze_output/**/* - docker-compose#v4.7.0: - pull: react-native-maze-runner - run: react-native-maze-runner - service-ports: true - command: - - --app=/app/features/fixtures/generated/expo/{{matrix}}/test-fixture/output.apk - - --farm=bb - - --device=ANDROID_15 - - --a11y-locator - - --fail-fast - - --appium-version=1.22 - - --no-tunnel - - --aws-public-ip - test-collector#v1.10.2: - files: "reports/TEST-*.xml" - format: "junit" - branch: "^main|next$$" - api-token-env-name: "REACT_NATIVE_PERFORMANCE_BUILDKITE_ANALYTICS_TOKEN" - env: - EXPO_VERSION: "{{matrix}}" - retry: - manual: - permit_on_passed: true - concurrency: 25 - concurrency_group: "bitbar" - concurrency_method: eager - matrix: - - "52" + - label: ':mac: Build Expo {{matrix}} IPA' + key: "build-expo-ipa" + timeout_in_minutes: 20 + agents: + queue: "macos-14" + env: + EXPO_VERSION: "{{matrix}}" + BUILD_IOS: 1 + artifact_paths: test/react-native/features/fixtures/generated/expo/**/test-fixture/output.ipa + commands: + - bundle install + - ./bin/generate-expo-fixture + matrix: + - "52" - - label: ":bitbar: :mac: Expo {{matrix}} iOS 16 end-to-end tests" - depends_on: "build-expo-ipa" - timeout_in_minutes: 20 - plugins: - artifacts#v1.9.0: - download: "test/react-native/features/fixtures/generated/expo/{{matrix}}/test-fixture/output.ipa" - upload: ./test/react-native/maze_output/**/* - docker-compose#v4.12.0: - pull: react-native-maze-runner - run: react-native-maze-runner - service-ports: true - command: - - --app=/app/features/fixtures/generated/expo/{{matrix}}/test-fixture/output.ipa - - --farm=bb - - --device=IOS_16 - - --a11y-locator - - --fail-fast - - --appium-version=1.22 - - --no-tunnel - - --aws-public-ip - test-collector#v1.10.2: - files: "reports/TEST-*.xml" - format: "junit" - branch: "^main|next$$" - api-token-env-name: "REACT_NATIVE_PERFORMANCE_BUILDKITE_ANALYTICS_TOKEN" - env: - EXPO_VERSION: "{{matrix}}" - retry: - manual: - permit_on_passed: true - concurrency: 25 - concurrency_group: "bitbar" - concurrency_method: eager - matrix: - - "52" + # + # End-to-end tests + # + - label: ":bitbar: :android: Expo {{matrix}} Android 15 end-to-end tests" + depends_on: "build-expo-apk" + timeout_in_minutes: 20 + plugins: + artifacts#v1.9.0: + download: "test/react-native/features/fixtures/generated/expo/{{matrix}}/test-fixture/output.apk" + upload: ./test/react-native/maze_output/**/* + docker-compose#v4.7.0: + pull: react-native-maze-runner + run: react-native-maze-runner + service-ports: true + command: + - --app=/app/features/fixtures/generated/expo/{{matrix}}/test-fixture/output.apk + - --farm=bb + - --device=ANDROID_15 + - --a11y-locator + - --fail-fast + - --appium-version=1.22 + - --no-tunnel + - --aws-public-ip + test-collector#v1.10.2: + files: "reports/TEST-*.xml" + format: "junit" + branch: "^main|next$$" + api-token-env-name: "REACT_NATIVE_PERFORMANCE_BUILDKITE_ANALYTICS_TOKEN" + env: + EXPO_VERSION: "{{matrix}}" + retry: + manual: + permit_on_passed: true + concurrency: 25 + concurrency_group: "bitbar" + concurrency_method: eager + matrix: + - "52" + + - label: ":bitbar: :mac: Expo {{matrix}} iOS 16 end-to-end tests" + depends_on: "build-expo-ipa" + timeout_in_minutes: 20 + plugins: + artifacts#v1.9.0: + download: "test/react-native/features/fixtures/generated/expo/{{matrix}}/test-fixture/output.ipa" + upload: ./test/react-native/maze_output/**/* + docker-compose#v4.12.0: + pull: react-native-maze-runner + run: react-native-maze-runner + service-ports: true + command: + - --app=/app/features/fixtures/generated/expo/{{matrix}}/test-fixture/output.ipa + - --farm=bb + - --device=IOS_16 + - --a11y-locator + - --fail-fast + - --appium-version=1.22 + - --no-tunnel + - --aws-public-ip + test-collector#v1.10.2: + files: "reports/TEST-*.xml" + format: "junit" + branch: "^main|next$$" + api-token-env-name: "REACT_NATIVE_PERFORMANCE_BUILDKITE_ANALYTICS_TOKEN" + env: + EXPO_VERSION: "{{matrix}}" + retry: + manual: + permit_on_passed: true + concurrency: 25 + concurrency_group: "bitbar" + concurrency_method: eager + matrix: + - "52" diff --git a/.buildkite/scripts/packages.json b/.buildkite/scripts/packages.json index 0ac183e90..5ef442461 100644 --- a/.buildkite/scripts/packages.json +++ b/.buildkite/scripts/packages.json @@ -57,5 +57,10 @@ "pipeline": ".buildkite/expo-pipeline.yml", "block": ".buildkite/expo-pipeline.block.yml", "paths": [] + }, + { + "pipeline": ".buildkite/expo-pipeline.full.yml", + "block": ".buildkite/expo-pipeline.full.block.yml", + "paths": [] } ] \ No newline at end of file From fe00396907a81accc0175b4a3feb0b207ecb02c8 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Wed, 30 Apr 2025 10:00:31 +0100 Subject: [PATCH 10/16] test(react-native): switch to tabs template --- bin/generate-expo-fixture | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/generate-expo-fixture b/bin/generate-expo-fixture index 706017bf7..8faa60c07 100755 --- a/bin/generate-expo-fixture +++ b/bin/generate-expo-fixture @@ -74,7 +74,7 @@ if (!process.env.SKIP_GENERATE_FIXTURE) { fs.mkdirSync(fixtureDir, { recursive: true }) // create the test fixture - const expoInitArgs = ['create-expo-app', 'test-fixture', '--no-install', '--template', `default@${expoVersion}`] + const expoInitArgs = ['create-expo-app', 'test-fixture', '--no-install', '--template', `tabs@${expoVersion}`] execFileSync('npx', expoInitArgs, { cwd: buildDir, stdio: 'inherit' }) // install the required packages From 36649956a0d2584f25dc41e610c30e8bd3f85a4f Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Wed, 30 Apr 2025 10:01:48 +0100 Subject: [PATCH 11/16] test(react-native): create npmrc file directly --- bin/generate-expo-fixture | 5 ++--- test/react-native/features/fixtures/expo/npmconfig | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) delete mode 100644 test/react-native/features/fixtures/expo/npmconfig diff --git a/bin/generate-expo-fixture b/bin/generate-expo-fixture index 8faa60c07..d84ba775a 100755 --- a/bin/generate-expo-fixture +++ b/bin/generate-expo-fixture @@ -153,9 +153,8 @@ if (!process.env.SKIP_GENERATE_FIXTURE) { const keyStorePath = resolve(ROOT_DIR, `test/react-native/features/fixtures/expo/fakekeys.jks`) fs.copyFileSync(keyStorePath, resolve(fixtureDir, 'fakekeys.jks')) - // copy npmrc - const npmrcPath = resolve(ROOT_DIR, `test/react-native/features/fixtures/expo/npmconfig`) - fs.copyFileSync(npmrcPath, resolve(fixtureDir, '.npmrc')) + // add .npmrc + fs.writeFileSync(resolve(fixtureDir, '.npmrc'), 'registry=https://registry.npmjs.org/\n') // copy credentials to the fixture directory const credentialsFiles = fs.readdirSync(process.env.EXPO_CREDENTIALS_DIR) diff --git a/test/react-native/features/fixtures/expo/npmconfig b/test/react-native/features/fixtures/expo/npmconfig deleted file mode 100644 index 214c29d13..000000000 --- a/test/react-native/features/fixtures/expo/npmconfig +++ /dev/null @@ -1 +0,0 @@ -registry=https://registry.npmjs.org/ From b2a115e6d61de9b61988907cff1351180479a7bc Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Wed, 30 Apr 2025 11:41:28 +0100 Subject: [PATCH 12/16] test(react-native): remove ios notification entitlement on sdk 50 --- bin/generate-expo-fixture | 29 +++++++++++-------- .../withRemoveiOSNotificationEntitlement.js | 10 +++++++ 2 files changed, 27 insertions(+), 12 deletions(-) create mode 100644 test/react-native/features/fixtures/expo/withRemoveiOSNotificationEntitlement.js diff --git a/bin/generate-expo-fixture b/bin/generate-expo-fixture index d84ba775a..92faa88b0 100755 --- a/bin/generate-expo-fixture +++ b/bin/generate-expo-fixture @@ -105,18 +105,23 @@ if (!process.env.SKIP_GENERATE_FIXTURE) { } } - const plugins = appConfig.expo.plugins || [] - appConfig.expo.plugins = [ - ...plugins, - [ - 'expo-build-properties', - { - android: { - usesCleartextTraffic: true - } + // set usesCleartextTraffic to true for Android + appConfig.expo.plugins.push([ + 'expo-build-properties', + { + android: { + usesCleartextTraffic: true } - ] - ] + } + ]) + + // for SDK 50 and below we need to add a config plugin to remove the aps-environment entitlement for iOS + // TODO: remove this when we drop support for SDK 50 + if (parseInt(expoVersion) < 51) { + const configPlugin = resolve(ROOT_DIR, `test/react-native/features/fixtures/expo/withRemoveiOSNotificationEntitlement.js`) + fs.copyFileSync(configPlugin, resolve(fixtureDir, 'withRemoveiOSNotificationEntitlement.js')) + appConfig.expo.plugins.push('./withRemoveiOSNotificationEntitlement') + } fs.writeFileSync(`${fixtureDir}/app.json`, JSON.stringify(appConfig, null, 2)) @@ -188,4 +193,4 @@ function installFixtureDependencies() { // install test fixture dependencies and local packages execSync(`npm install ${fixtureDependencyArgs} *.tgz --save --no-audit --legacy-peer-deps`, { cwd: fixtureDir, stdio: 'inherit' }) -} \ No newline at end of file +} diff --git a/test/react-native/features/fixtures/expo/withRemoveiOSNotificationEntitlement.js b/test/react-native/features/fixtures/expo/withRemoveiOSNotificationEntitlement.js new file mode 100644 index 000000000..746e81e8f --- /dev/null +++ b/test/react-native/features/fixtures/expo/withRemoveiOSNotificationEntitlement.js @@ -0,0 +1,10 @@ +const withEntitlementsPlist = require("@expo/config-plugins").withEntitlementsPlist; + +const withRemoveiOSNotificationEntitlement = (config) => { + return withEntitlementsPlist(config, mod => { + delete mod.modResults['aps-environment']; + return mod; + }) +} + +module.exports = withRemoveiOSNotificationEntitlement; From a78667049990d486751fc5cce5656d1e05df33f9 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Wed, 30 Apr 2025 12:13:06 +0100 Subject: [PATCH 13/16] ci: fix pipeline upload order --- .buildkite/scripts/pipeline-trigger.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/scripts/pipeline-trigger.js b/.buildkite/scripts/pipeline-trigger.js index 728aa7f14..28fd66db7 100644 --- a/.buildkite/scripts/pipeline-trigger.js +++ b/.buildkite/scripts/pipeline-trigger.js @@ -17,7 +17,7 @@ if (baseBranch) { execSync(`git --no-pager diff --name-only origin/${baseBranch}`, { stdio: 'inherit' }); } -packages.forEach(({ paths, block, pipeline }) => { +packages.reverse().forEach(({ paths, block, pipeline }) => { let upload = false; if (commitMessage.includes("[full ci]") || From c815325b271712950fb6a55853c3ae8e9403b089 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Wed, 30 Apr 2025 12:31:08 +0100 Subject: [PATCH 14/16] doc(react-native): update TESTING.md --- test/react-native/TESTING.md | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/test/react-native/TESTING.md b/test/react-native/TESTING.md index 6b7814e5a..ccf2d99bd 100644 --- a/test/react-native/TESTING.md +++ b/test/react-native/TESTING.md @@ -19,11 +19,11 @@ To generate an Android apk and/or iOS ipa, you will additionally need to set the Scenarios are written as React Native components and are packaged as a separate module under `test/react-native/features/fixtures/scenario-launcher` - these are packaged and installed into the test fixture when it's generated. -When running tests using maze runner, scenarios are launched via maze runner commands (see `env.rb`). However it's also possible to launch the test fixture locally (e.g. via `npx react-native run-android`) and run scenarios outside of maze runner by setting the appropriate environment variables in the test fixture's `.env` file +When running tests using maze runner, scenarios are launched via maze runner commands (see `env.rb`). However it's also possible to launch the test fixture locally (e.g. via `npx react-native run-android`) and run specific scenarios outside of maze runner by hardcoding a command in ScenarioLauncher.js ## Local testing with BrowserStack -__Note: only Bugsnag employees can run the end-to-end tests with BrowserStack.__ We have dedicated test infrastructure and private BrowserStack credentials which can't be shared outside of the organisation. +__Note: only SmartBear employees can run the end-to-end tests with BrowserStack.__ We have dedicated test infrastructure and private BrowserStack credentials which can't be shared outside of the organisation. The following environment variables need to be set - credentials can be found in our shared password manager: @@ -50,3 +50,20 @@ End-to-end tests in CI run on both Android and iOS, for both old and new archite - The latest 3 versions of React Native - Even versions of React Native going back to 0.72 - 0.64 + +## Expo testing + +To generate an Expo test fixture, from the root directory run the `./bin/generate-expo-fixture` script. You will need to make sure the following environment variables are set: + +__Note: only SmartBear employees can build the Expo test fixture.__ The build requires private credentials which can't be shared outside of the organisation. + +- `EXPO_VERSION` - Expo SDK version, e.g. 52 +- `EXPO_EAS_PROJECT_ID` - The EAS project ID for the test fixture project +- `EXPO_TOKEN` - Authentication token for EAS build - or, alternatively make sure you're logged in to the eas CLI (`eas login`) +- `EXPO_CREDENTIALS_DIR` - Path to a directory containing code signing credentials for the test fixture + +This will generate an Expo project in `test/react-native/features/fixtures/generated/expo//test-fixture`, and will also package the performance libraries and scenarios and install them into the test fixture project. + +To generate an Android apk and/or iOS ipa, you will additionally need to set the `BUILD_ANDROID` and `BUILD_IOS` environment variables respectively. + +Note that in order to run the test fixture locally, you will need to [create a development build](https://docs.expo.dev/develop/development-builds/create-a-build/). \ No newline at end of file From d60782dd26dca01cb9142924531b31483b59bede Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Mon, 12 May 2025 15:36:20 +0100 Subject: [PATCH 15/16] test(react-native): avoid storing scenario component in state --- test/react-native/features/fixtures/app/index.js | 5 +++-- .../features/fixtures/expo/index.tsx | 4 ++-- .../fixtures/react-native-navigation/index.js | 5 +++-- .../features/fixtures/scenario-launcher/index.js | 1 + .../scenario-launcher/lib/ScenarioComponent.jsx | 16 ++++++++++++++++ .../scenario-launcher/lib/ScenarioContext.js | 3 +-- .../scenario-launcher/lib/ScenarioLauncher.js | 11 ++--------- .../scenarios/TracePropagationScenario.js | 4 +++- 8 files changed, 31 insertions(+), 18 deletions(-) create mode 100644 test/react-native/features/fixtures/scenario-launcher/lib/ScenarioComponent.jsx diff --git a/test/react-native/features/fixtures/app/index.js b/test/react-native/features/fixtures/app/index.js index 470681fd7..b0bf1da34 100644 --- a/test/react-native/features/fixtures/app/index.js +++ b/test/react-native/features/fixtures/app/index.js @@ -1,7 +1,8 @@ import { AppRegistry, SafeAreaView, StyleSheet, Text } from 'react-native' import React, { useEffect, useState } from 'react' import { name as appName } from './app.json'; -import { launchScenario, launchFromStartupConfig, ScenarioContext } from '@bugsnag/react-native-performance-scenarios' +import { launchScenario, launchFromStartupConfig, ScenarioContext, ScenarioComponent } from '@bugsnag/react-native-performance-scenarios' +import BugsnagPerformance from '@bugsnag/react-native-performance'; const isStartupTest = launchFromStartupConfig() @@ -17,7 +18,7 @@ const App = () => { React Native Performance Test App - { currentScenario && } + ) diff --git a/test/react-native/features/fixtures/expo/index.tsx b/test/react-native/features/fixtures/expo/index.tsx index 9af32aeba..d1c204403 100644 --- a/test/react-native/features/fixtures/expo/index.tsx +++ b/test/react-native/features/fixtures/expo/index.tsx @@ -2,7 +2,7 @@ import { SafeAreaView, StyleSheet, Text } from 'react-native'; import React, { useEffect, useState } from 'react' //@ts-expect-error no types -import { launchScenario, ScenarioContext } from '@bugsnag/react-native-performance-scenarios' +import { launchScenario, ScenarioContext, ScenarioComponent } from '@bugsnag/react-native-performance-scenarios' export default function HomeScreen() { @@ -16,7 +16,7 @@ export default function HomeScreen() { Expo Performance Test App - { currentScenario && } + ) diff --git a/test/react-native/features/fixtures/react-native-navigation/index.js b/test/react-native/features/fixtures/react-native-navigation/index.js index a8cf7b466..7912c7c0a 100644 --- a/test/react-native/features/fixtures/react-native-navigation/index.js +++ b/test/react-native/features/fixtures/react-native-navigation/index.js @@ -1,4 +1,4 @@ -import { launchScenario, launchFromStartupConfig, ScenarioContext } from '@bugsnag/react-native-performance-scenarios' +import { launchScenario, launchFromStartupConfig, ScenarioContext, ScenarioComponent } from '@bugsnag/react-native-performance-scenarios' import { useEffect, useState } from 'react' import { SafeAreaView, StyleSheet, Text } from 'react-native' import { Navigation } from 'react-native-navigation' @@ -9,6 +9,7 @@ const isStartupTest = launchFromStartupConfig() const App = () => { const [currentScenario, setCurrentScenario] = useState(null) + useEffect(() => { if (!isStartupTest) launchScenario(setCurrentScenario) }, []) @@ -18,7 +19,7 @@ const App = () => { React Native Performance Test App react-native-navigation - { currentScenario && } + ) diff --git a/test/react-native/features/fixtures/scenario-launcher/index.js b/test/react-native/features/fixtures/scenario-launcher/index.js index 12e2fdbfc..bb1d3412e 100644 --- a/test/react-native/features/fixtures/scenario-launcher/index.js +++ b/test/react-native/features/fixtures/scenario-launcher/index.js @@ -1,3 +1,4 @@ export { launchScenario, launchFromStartupConfig } from './lib/ScenarioLauncher' export { NativeScenarioLauncher } from './lib/native' export { ScenarioContext } from './lib/ScenarioContext' +export { ScenarioComponent } from './lib/ScenarioComponent' \ No newline at end of file diff --git a/test/react-native/features/fixtures/scenario-launcher/lib/ScenarioComponent.jsx b/test/react-native/features/fixtures/scenario-launcher/lib/ScenarioComponent.jsx new file mode 100644 index 000000000..0762f541f --- /dev/null +++ b/test/react-native/features/fixtures/scenario-launcher/lib/ScenarioComponent.jsx @@ -0,0 +1,16 @@ +import React, { useContext } from 'react' +import { ScenarioContext } from './ScenarioContext' +import * as Scenarios from '../scenarios' +import BugsnagPerformance from '@bugsnag/react-native-performance' + +export const ScenarioComponent = () => { + const scenarioContext = useContext(ScenarioContext) + + if (scenarioContext) { + const scenario = Scenarios[scenarioContext.name] + const Scenario = scenario.withInstrumentedAppStarts ? BugsnagPerformance.withInstrumentedAppStarts(scenario.App) : scenario.App + return + } + + return null +} diff --git a/test/react-native/features/fixtures/scenario-launcher/lib/ScenarioContext.js b/test/react-native/features/fixtures/scenario-launcher/lib/ScenarioContext.js index 96080ec2d..0bec01adc 100644 --- a/test/react-native/features/fixtures/scenario-launcher/lib/ScenarioContext.js +++ b/test/react-native/features/fixtures/scenario-launcher/lib/ScenarioContext.js @@ -1,7 +1,6 @@ import React from 'react'; export const ScenarioContext = React.createContext({ - Component: null, + name: null, config: null, - reflectEndpoint: null, }) diff --git a/test/react-native/features/fixtures/scenario-launcher/lib/ScenarioLauncher.js b/test/react-native/features/fixtures/scenario-launcher/lib/ScenarioLauncher.js index 1e0a7ab8f..06adc99f0 100644 --- a/test/react-native/features/fixtures/scenario-launcher/lib/ScenarioLauncher.js +++ b/test/react-native/features/fixtures/scenario-launcher/lib/ScenarioLauncher.js @@ -53,16 +53,9 @@ async function runScenario (setScenario, scenarioName, apiKey, endpoint) { if (process.env.REACT_NATIVE_NAVIGATION) { loadReactNavigationScenario(scenario) } else { - - const reflectEndpoint = endpoint.replace('traces', 'reflect') - console.error(`[BugsnagPerformance] Reflect endpoint: ${reflectEndpoint}`) - - const ScenarioComponent = scenario.withInstrumentedAppStarts ? BugsnagPerformance.withInstrumentedAppStarts(scenario.App) : scenario.App - setScenario({ - Component: ScenarioComponent, - config: scenarioConfig, - reflectEndpoint, + name: scenarioName, + config: scenarioConfig }) } } diff --git a/test/react-native/features/fixtures/scenario-launcher/scenarios/TracePropagationScenario.js b/test/react-native/features/fixtures/scenario-launcher/scenarios/TracePropagationScenario.js index 8206845ec..b221d100f 100644 --- a/test/react-native/features/fixtures/scenario-launcher/scenarios/TracePropagationScenario.js +++ b/test/react-native/features/fixtures/scenario-launcher/scenarios/TracePropagationScenario.js @@ -66,10 +66,12 @@ const xhrWithHeaders = async (endpoint) => { } export const App = () => { - const { reflectEndpoint } = useContext(ScenarioContext) + const { config } = useContext(ScenarioContext) useEffect(() => { (async () => { + const reflectEndpoint = config.endpoint.replace('traces', 'reflect') + await delay(250) await fetchWithObjectLiteralHeadersInOptions(reflectEndpoint) From e5597f0f8e0bedf017039ed866e63f3fc2a4b150 Mon Sep 17 00:00:00 2001 From: Yousif Ahmed Date: Tue, 13 May 2025 09:38:22 +0100 Subject: [PATCH 16/16] test(react-native): fix scenario loading for RNN fixture --- .../fixtures/react-native-navigation/index.js | 35 +++++++++++++------ .../fixtures/scenario-launcher/index.js | 3 +- .../scenario-launcher/lib/ScenarioLauncher.js | 26 +------------- 3 files changed, 27 insertions(+), 37 deletions(-) diff --git a/test/react-native/features/fixtures/react-native-navigation/index.js b/test/react-native/features/fixtures/react-native-navigation/index.js index 7912c7c0a..b28010f79 100644 --- a/test/react-native/features/fixtures/react-native-navigation/index.js +++ b/test/react-native/features/fixtures/react-native-navigation/index.js @@ -1,4 +1,4 @@ -import { launchScenario, launchFromStartupConfig, ScenarioContext, ScenarioComponent } from '@bugsnag/react-native-performance-scenarios' +import { launchScenario, launchFromStartupConfig, Scenarios } from '@bugsnag/react-native-performance-scenarios' import { useEffect, useState } from 'react' import { SafeAreaView, StyleSheet, Text } from 'react-native' import { Navigation } from 'react-native-navigation' @@ -7,21 +7,34 @@ console.reportErrorsAsExceptions = false const isStartupTest = launchFromStartupConfig() -const App = () => { - const [currentScenario, setCurrentScenario] = useState(null) +const setScenario = (scenarioContext) => { + const scenario = Scenarios[scenarioContext.name] + if (typeof scenario.registerScreens === 'function') { + scenario.registerScreens() + return + } + + Navigation.registerComponent('Scenario', () => scenario.App) + Navigation.setRoot({ + root: { + component: { + name: 'Scenario' + } + } + }) +} + +const App = () => { useEffect(() => { - if (!isStartupTest) launchScenario(setCurrentScenario) + if (!isStartupTest) launchScenario(setScenario) }, []) return ( - - - React Native Performance Test App - react-native-navigation - - - + + React Native Performance Test App + react-native-navigation + ) } diff --git a/test/react-native/features/fixtures/scenario-launcher/index.js b/test/react-native/features/fixtures/scenario-launcher/index.js index bb1d3412e..966d43b85 100644 --- a/test/react-native/features/fixtures/scenario-launcher/index.js +++ b/test/react-native/features/fixtures/scenario-launcher/index.js @@ -1,4 +1,5 @@ export { launchScenario, launchFromStartupConfig } from './lib/ScenarioLauncher' export { NativeScenarioLauncher } from './lib/native' export { ScenarioContext } from './lib/ScenarioContext' -export { ScenarioComponent } from './lib/ScenarioComponent' \ No newline at end of file +export { ScenarioComponent } from './lib/ScenarioComponent' +export * as Scenarios from './scenarios' \ No newline at end of file diff --git a/test/react-native/features/fixtures/scenario-launcher/lib/ScenarioLauncher.js b/test/react-native/features/fixtures/scenario-launcher/lib/ScenarioLauncher.js index 06adc99f0..41e608f2d 100644 --- a/test/react-native/features/fixtures/scenario-launcher/lib/ScenarioLauncher.js +++ b/test/react-native/features/fixtures/scenario-launcher/lib/ScenarioLauncher.js @@ -6,23 +6,6 @@ import { wrapperComponentProvider } from '../scenarios/WrapperComponentProviderS import React from 'react' import BugsnagPerformance from '@bugsnag/react-native-performance' -async function loadReactNavigationScenario (scenario) { - if (typeof scenario.registerScreens === 'function') { - scenario.registerScreens() - } else { - import('react-native-navigation').then(({ Navigation }) => { - Navigation.registerComponent('Scenario', () => scenario.App) - Navigation.setRoot({ - root: { - component: { - name: 'Scenario' - } - } - }) - }) - } -} - async function runScenario (setScenario, scenarioName, apiKey, endpoint) { console.error(`[BugsnagPerformance] Launching scenario: ${scenarioName}`) const scenario = Scenarios[scenarioName] @@ -50,14 +33,7 @@ async function runScenario (setScenario, scenarioName, apiKey, endpoint) { BugsnagPerformance.start(scenarioConfig) } - if (process.env.REACT_NATIVE_NAVIGATION) { - loadReactNavigationScenario(scenario) - } else { - setScenario({ - name: scenarioName, - config: scenarioConfig - }) - } + setScenario({ name: scenarioName, config: scenarioConfig }) } export async function launchScenario (setScenario, clearPersistedData = true) {