From c2893147b4c85779362843c12057e224f8445e4e Mon Sep 17 00:00:00 2001
From: Yousif <74918474+yousif-bugsnag@users.noreply.github.com>
Date: Mon, 23 Sep 2024 13:32:46 +0100
Subject: [PATCH] test(react-navigation): move react navigation tests to
dynamic fixture setup (#2201)
* test(react-native): move react navigation scenarios to dynamic test fixture
* ci(react-native): run react navigation scenarios as part of main test run
* skip react navigation tests on new arch
* enable react navigation tests in full pipeline
* move legacy android builder step to full pipeline
* tidy up fixture generation script
---
.../basic/react-native-android-pipeline.yml | 2 -
.../basic/react-native-ios-pipeline.yml | 2 -
.buildkite/full/pipeline.full.yml | 18 ++
.../react-native-android-pipeline.full.yml | 2 -
.../full/react-native-ios-pipeline.full.yml | 2 -
.buildkite/pipeline.yml | 18 --
scripts/generate-react-native-fixture.js | 204 ++++++++++++------
.../features/fixtures/app/dynamic/App.js | 18 +-
.../fixtures/scenario-launcher/package.json | 3 +
.../src/lib/ScenarioLauncher.js | 14 +-
...ctNavigationBreadcrumbsDisabledScenario.js | 73 +++++++
...actNavigationBreadcrumbsEnabledScenario.js | 72 +++++++
.../scenario-launcher/src/scenarios/index.js | 4 +
test/react-native/features/navigation.feature | 2 +-
14 files changed, 326 insertions(+), 108 deletions(-)
create mode 100644 test/react-native/features/fixtures/scenario-launcher/src/scenarios/ReactNavigationBreadcrumbsDisabledScenario.js
create mode 100644 test/react-native/features/fixtures/scenario-launcher/src/scenarios/ReactNavigationBreadcrumbsEnabledScenario.js
diff --git a/.buildkite/basic/react-native-android-pipeline.yml b/.buildkite/basic/react-native-android-pipeline.yml
index 788d1fe74..714915454 100644
--- a/.buildkite/basic/react-native-android-pipeline.yml
+++ b/.buildkite/basic/react-native-android-pipeline.yml
@@ -77,7 +77,6 @@ steps:
manual:
permit_on_passed: true
env:
- SKIP_NAVIGATION_SCENARIOS: "true"
RN_VERSION: "{{matrix}}"
RCT_NEW_ARCH_ENABLED: "0"
concurrency: 25
@@ -111,7 +110,6 @@ steps:
manual:
permit_on_passed: true
env:
- SKIP_NAVIGATION_SCENARIOS: "true"
RCT_NEW_ARCH_ENABLED: "1"
RN_VERSION: "{{matrix}}"
concurrency: 25
diff --git a/.buildkite/basic/react-native-ios-pipeline.yml b/.buildkite/basic/react-native-ios-pipeline.yml
index f168adf87..00f0f6704 100644
--- a/.buildkite/basic/react-native-ios-pipeline.yml
+++ b/.buildkite/basic/react-native-ios-pipeline.yml
@@ -79,7 +79,6 @@ steps:
manual:
permit_on_passed: true
env:
- SKIP_NAVIGATION_SCENARIOS: "true"
RN_VERSION: "{{matrix}}"
RCT_NEW_ARCH_ENABLED: "0"
concurrency: 25
@@ -110,7 +109,6 @@ steps:
- --aws-public-ip
env:
RCT_NEW_ARCH_ENABLED: "1"
- SKIP_NAVIGATION_SCENARIOS: "true"
RN_VERSION: "{{matrix}}"
retry:
manual:
diff --git a/.buildkite/full/pipeline.full.yml b/.buildkite/full/pipeline.full.yml
index 45ee8cd03..01d682239 100644
--- a/.buildkite/full/pipeline.full.yml
+++ b/.buildkite/full/pipeline.full.yml
@@ -1,5 +1,23 @@
steps:
+ #
+ # Java 11 Android builder base - used by React Native and React Native CLI (< 0.73)
+ #
+ - label: ":docker: Build Java 11 Android Builder base image"
+ key: "android-builder-base-java-11"
+ timeout_in_minutes: 30
+ plugins:
+ - docker-compose#v4.12.0:
+ build: android-builder-base-java-11
+ image-repository: 855461928731.dkr.ecr.us-west-1.amazonaws.com/js
+ cache-from: android-builder-base-java-11:855461928731.dkr.ecr.us-west-1.amazonaws.com/js:android-builder-base-java-11
+ - docker-compose#v4.12.0:
+ push: android-builder-base-java-11:855461928731.dkr.ecr.us-west-1.amazonaws.com/js:android-builder-base-java-11
+ retry:
+ automatic:
+ - exit_status: "*"
+ limit: 1
+
#
# Upload full React Native pipelines
#
diff --git a/.buildkite/full/react-native-android-pipeline.full.yml b/.buildkite/full/react-native-android-pipeline.full.yml
index 87482fc3f..1a1285b94 100644
--- a/.buildkite/full/react-native-android-pipeline.full.yml
+++ b/.buildkite/full/react-native-android-pipeline.full.yml
@@ -327,7 +327,6 @@ steps:
manual:
permit_on_passed: true
env:
- SKIP_NAVIGATION_SCENARIOS: "true"
RN_VERSION: "{{matrix}}"
RCT_NEW_ARCH_ENABLED: "0"
concurrency: 25
@@ -362,7 +361,6 @@ steps:
manual:
permit_on_passed: true
env:
- SKIP_NAVIGATION_SCENARIOS: "true"
RCT_NEW_ARCH_ENABLED: "1"
RN_VERSION: "{{matrix}}"
concurrency: 25
diff --git a/.buildkite/full/react-native-ios-pipeline.full.yml b/.buildkite/full/react-native-ios-pipeline.full.yml
index 91fb44b84..86a2846de 100644
--- a/.buildkite/full/react-native-ios-pipeline.full.yml
+++ b/.buildkite/full/react-native-ios-pipeline.full.yml
@@ -297,7 +297,6 @@ steps:
manual:
permit_on_passed: true
env:
- SKIP_NAVIGATION_SCENARIOS: "true"
RN_VERSION: "{{matrix}}"
RCT_NEW_ARCH_ENABLED: "0"
concurrency: 25
@@ -329,7 +328,6 @@ steps:
- --aws-public-ip
env:
RCT_NEW_ARCH_ENABLED: "1"
- SKIP_NAVIGATION_SCENARIOS: "true"
RN_VERSION: "{{matrix}}"
retry:
manual:
diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml
index 59f2633b6..401022a08 100644
--- a/.buildkite/pipeline.yml
+++ b/.buildkite/pipeline.yml
@@ -9,24 +9,6 @@ steps:
queue: macos-14
command: scripts/license_finder.sh
- #
- # Java 11 Android builder base - used by React Native and React Native CLI (< 0.73)
- #
- - label: ":docker: Build Java 11 Android Builder base image"
- key: "android-builder-base-java-11"
- timeout_in_minutes: 30
- plugins:
- - docker-compose#v4.12.0:
- build: android-builder-base-java-11
- image-repository: 855461928731.dkr.ecr.us-west-1.amazonaws.com/js
- cache-from: android-builder-base-java-11:855461928731.dkr.ecr.us-west-1.amazonaws.com/js:android-builder-base-java-11
- - docker-compose#v4.12.0:
- push: android-builder-base-java-11:855461928731.dkr.ecr.us-west-1.amazonaws.com/js:android-builder-base-java-11
- retry:
- automatic:
- - exit_status: "*"
- limit: 1
-
#
# Publish/package notifier
#
diff --git a/scripts/generate-react-native-fixture.js b/scripts/generate-react-native-fixture.js
index e5217fae8..4df76bd5a 100644
--- a/scripts/generate-react-native-fixture.js
+++ b/scripts/generate-react-native-fixture.js
@@ -15,22 +15,33 @@ if (!process.env.REGISTRY_URL) {
const notifierVersion = process.env.NOTIFIER_VERSION || common.determineVersion()
-const rnVersion = process.env.RN_VERSION
+const reactNativeVersion = process.env.RN_VERSION
const ROOT_DIR = resolve(__dirname, '../')
+const isNewArchEnabled = process.env.RCT_NEW_ARCH_ENABLED === 'true' || process.env.RCT_NEW_ARCH_ENABLED === '1'
+
let fixturePath = 'test/react-native/features/fixtures/generated/'
-if (process.env.RCT_NEW_ARCH_ENABLED === '1') {
+if (isNewArchEnabled) {
fixturePath += 'new-arch/'
} else {
fixturePath += 'old-arch/'
}
-const fixtureDir = resolve(ROOT_DIR, fixturePath, rnVersion)
+const fixtureDir = resolve(ROOT_DIR, fixturePath, reactNativeVersion)
const replacementFilesDir = resolve(ROOT_DIR, 'test/react-native/features/fixtures/app/dynamic/')
const DEPENDENCIES = [
- 'react-native-file-access@3.0.4'
+ 'react-native-file-access@3.0.4',
+ `@bugsnag/react-native@${notifierVersion}`
+]
+
+const REACT_NAVIGATION_DEPENDENCIES = [
+ `@bugsnag/plugin-react-navigation@${notifierVersion}`,
+ '@react-navigation/native',
+ '@react-navigation/native-stack',
+ 'react-native-screens',
+ 'react-native-safe-area-context'
]
if (!process.env.SKIP_GENERATE_FIXTURE) {
@@ -40,76 +51,16 @@ if (!process.env.SKIP_GENERATE_FIXTURE) {
}
// create the test fixture
- const RNInitArgs = [`react-native@${process.env.RN_VERSION}`, 'init', 'reactnative', '--directory', fixtureDir, '--version', rnVersion, '--npm', '--skip-install']
+ const RNInitArgs = [`react-native@${process.env.RN_VERSION}`, 'init', 'reactnative', '--directory', fixtureDir, '--version', reactNativeVersion, '--npm', '--skip-install']
execFileSync('npx', RNInitArgs, { stdio: 'inherit' })
- // replace the App.js/App.tsx file with our own App.js file
- fs.readdirSync(resolve(fixtureDir))
- .filter((file) => /App\.[tj]sx?$/.test(file))
- .map((file) => fs.unlinkSync(resolve(fixtureDir, file)))
+ replaceGeneratedFixtureFiles()
- fs.copyFileSync(
- resolve(replacementFilesDir, 'App.js'),
- resolve(fixtureDir, 'App.js')
- )
-
- // replace the AndroidManifest.xml file with our own
- fs.copyFileSync(
- resolve(replacementFilesDir, 'android/AndroidManifest.xml'),
- resolve(fixtureDir, 'android/app/src/main/AndroidManifest.xml')
- )
-
- // replace the Info.plist file with our own
- fs.copyFileSync(
- resolve(replacementFilesDir, 'ios/Info.plist'),
- resolve(fixtureDir, 'ios/reactnative/Info.plist')
- )
-
- // copy the exportOptions.plist file
- fs.copyFileSync(
- resolve(replacementFilesDir, 'ios/exportOptions.plist'),
- resolve(fixtureDir, 'exportOptions.plist')
- )
-
- // update pbxproj
- let pbxProjContents = fs.readFileSync(`${fixtureDir}/ios/reactnative.xcodeproj/project.pbxproj`, 'utf8')
- pbxProjContents = pbxProjContents.replaceAll('org.reactjs.native.example', 'com.bugsnag.fixtures')
-
- fs.writeFileSync(`${fixtureDir}/ios/reactnative.xcodeproj/project.pbxproj`, pbxProjContents)
-
- // update Podfile
- let podfileContents = fs.readFileSync(`${fixtureDir}/ios/Podfile`, 'utf8')
-
- // use static frameworks (this fixes an issue with react-native-file-access on 0.75)
- if (parseFloat(rnVersion) >= 0.75) {
- podfileContents = podfileContents.replace(/target 'reactnative' do/, 'use_frameworks! :linkage => :static\ntarget \'reactnative\' do')
- }
-
- // disable Flipper
- if (podfileContents.includes('use_flipper!')) {
- podfileContents = podfileContents.replace(/use_flipper!/, '# use_flipper!')
- } else if (podfileContents.includes(':flipper_configuration')) {
- podfileContents = podfileContents.replace(/:flipper_configuration/, '# :flipper_configuration')
- }
-
- fs.writeFileSync(`${fixtureDir}/ios/Podfile`, podfileContents)
-
- const fixtureDependencyArgs = DEPENDENCIES.join(' ')
-
- // install test fixture dependencies and local packages
- execSync(`npm install --save ${fixtureDependencyArgs}`, { cwd: fixtureDir, stdio: 'inherit' })
-
- // install @bugsnag/react-native from the registry
- execSync(`npm install --save @bugsnag/react-native@${notifierVersion} --registry ${process.env.REGISTRY_URL}`, { cwd: fixtureDir, stdio: 'inherit' })
-
- // install the scenario launcher package
- const scenarioLauncherPackage = `${ROOT_DIR}/test/react-native/features/fixtures/scenario-launcher`
- execSync(`npm pack ${scenarioLauncherPackage} --pack-destination ${fixtureDir}`, { cwd: ROOT_DIR, stdio: 'inherit' })
- execSync('npm install --save bugsnag-react-native-scenarios-1.0.0.tgz', { cwd: fixtureDir, stdio: 'inherit' })
+ installFixtureDependencies()
}
if (process.env.BUILD_ANDROID === 'true' || process.env.BUILD_ANDROID === '1') {
- if (process.env.RCT_NEW_ARCH_ENABLED === 'true' || process.env.RCT_NEW_ARCH_ENABLED === '1') {
+ if (isNewArchEnabled) {
// If we're building with the new architecture, replace the gradle.properties file
fs.copyFileSync(
resolve(replacementFilesDir, 'android/newarch.gradle.properties'),
@@ -163,3 +114,120 @@ if (process.env.BUILD_IOS === 'true' || process.env.BUILD_IOS === '1') {
execFileSync('xcrun', exportArgs, { cwd: fixtureDir, stdio: 'inherit' })
}
+
+function installFixtureDependencies () {
+ if (!isNewArchEnabled) {
+ DEPENDENCIES.push(...REACT_NAVIGATION_DEPENDENCIES)
+ }
+
+ const fixtureDependencyArgs = DEPENDENCIES.join(' ')
+
+ // install test fixture dependencies
+ execSync(`npm install --save ${fixtureDependencyArgs} --registry ${process.env.REGISTRY_URL}`, { cwd: fixtureDir, stdio: 'inherit' })
+
+ // install the scenario launcher package
+ const scenarioLauncherPackage = `${ROOT_DIR}/test/react-native/features/fixtures/scenario-launcher`
+ execSync(`npm pack ${scenarioLauncherPackage} --pack-destination ${fixtureDir}`, { cwd: ROOT_DIR, stdio: 'inherit' })
+ execSync('npm install --save bugsnag-react-native-scenarios-1.0.0.tgz', { cwd: fixtureDir, stdio: 'inherit' })
+}
+
+/** Replace native files generated by react-native cli with pre-configured files */
+function replaceGeneratedFixtureFiles () {
+ // replace the App.js/App.tsx file with our own App.js file
+ fs.readdirSync(resolve(fixtureDir))
+ .filter((file) => /App\.[tj]sx?$/.test(file))
+ .map((file) => fs.unlinkSync(resolve(fixtureDir, file)))
+
+ fs.copyFileSync(
+ resolve(replacementFilesDir, 'App.js'),
+ resolve(fixtureDir, 'App.js')
+ )
+
+ // replace the AndroidManifest.xml file with our own
+ fs.copyFileSync(
+ resolve(replacementFilesDir, 'android/AndroidManifest.xml'),
+ resolve(fixtureDir, 'android/app/src/main/AndroidManifest.xml')
+ )
+
+ // replace the Info.plist file with our own
+ fs.copyFileSync(
+ resolve(replacementFilesDir, 'ios/Info.plist'),
+ resolve(fixtureDir, 'ios/reactnative/Info.plist')
+ )
+
+ // copy the exportOptions.plist file
+ fs.copyFileSync(
+ resolve(replacementFilesDir, 'ios/exportOptions.plist'),
+ resolve(fixtureDir, 'exportOptions.plist')
+ )
+
+ // update pbxproj
+ let pbxProjContents = fs.readFileSync(`${fixtureDir}/ios/reactnative.xcodeproj/project.pbxproj`, 'utf8')
+ pbxProjContents = pbxProjContents.replaceAll('org.reactjs.native.example', 'com.bugsnag.fixtures')
+
+ fs.writeFileSync(`${fixtureDir}/ios/reactnative.xcodeproj/project.pbxproj`, pbxProjContents)
+
+ // update Podfile
+ let podfileContents = fs.readFileSync(`${fixtureDir}/ios/Podfile`, 'utf8')
+
+ // use static frameworks (this fixes an issue with react-native-file-access on 0.75)
+ if (parseFloat(reactNativeVersion) >= 0.75) {
+ podfileContents = podfileContents.replace(/target 'reactnative' do/, 'use_frameworks! :linkage => :static\ntarget \'reactnative\' do')
+ }
+
+ // disable Flipper
+ if (podfileContents.includes('use_flipper!')) {
+ podfileContents = podfileContents.replace(/use_flipper!/, '# use_flipper!')
+ } else if (podfileContents.includes(':flipper_configuration')) {
+ podfileContents = podfileContents.replace(/:flipper_configuration/, '# :flipper_configuration')
+ }
+
+ fs.writeFileSync(`${fixtureDir}/ios/Podfile`, podfileContents)
+
+ // react navigation setup
+ if (!isNewArchEnabled) {
+ configureReactNavigationAndroid()
+ }
+}
+
+function configureReactNavigationAndroid () {
+ const fileExtension = parseFloat(reactNativeVersion) < 0.73 ? 'java' : 'kt'
+ let mainActivityPattern, mainActivityReplacement
+ if (fileExtension === 'java') {
+ mainActivityPattern = 'public class MainActivity extends ReactActivity {'
+ mainActivityReplacement = `
+import android.os.Bundle;
+
+public class MainActivity extends ReactActivity {
+
+ /**
+ * Required for react-navigation/native implementation
+ * https://reactnavigation.org/docs/getting-started/#installing-dependencies-into-a-bare-react-native-project
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(null);
+ }
+`
+ } else if (fileExtension === 'kt') {
+ mainActivityPattern = 'class MainActivity : ReactActivity() {'
+ mainActivityReplacement = `
+import android.os.Bundle
+
+class MainActivity : ReactActivity() {
+
+ /**
+ * Required for react-navigation/native implementation
+ * https://reactnavigation.org/docs/getting-started/#installing-dependencies-into-a-bare-react-native-project
+ */
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(null)
+ }
+`
+ }
+
+ const mainActivityPath = `${fixtureDir}/android/app/src/main/java/com/reactnative/MainActivity.${fileExtension}`
+ let mainActivityContents = fs.readFileSync(mainActivityPath, 'utf8')
+ mainActivityContents = mainActivityContents.replace(mainActivityPattern, mainActivityReplacement)
+ fs.writeFileSync(mainActivityPath, mainActivityContents)
+}
diff --git a/test/react-native/features/fixtures/app/dynamic/App.js b/test/react-native/features/fixtures/app/dynamic/App.js
index 38bfb256d..8fa4ee360 100644
--- a/test/react-native/features/fixtures/app/dynamic/App.js
+++ b/test/react-native/features/fixtures/app/dynamic/App.js
@@ -1,18 +1,22 @@
-import React, { useEffect } from 'react'
+import React, { useEffect, useState } from 'react'
import { SafeAreaView, StyleSheet, View, Text } from 'react-native'
import { launchScenario } from '@bugsnag/react-native-scenarios'
const App = () => {
+ const [scenario, setScenario] = useState(null)
+
useEffect(() => {
- launchScenario()
+ launchScenario(setScenario)
}, [])
return (
-
-
- React Native Test App
-
-
+ scenario !== null ? scenario.view() : (
+
+
+ React Native Test App
+
+
+ )
)
}
diff --git a/test/react-native/features/fixtures/scenario-launcher/package.json b/test/react-native/features/fixtures/scenario-launcher/package.json
index 824eb96d2..bc497a36d 100644
--- a/test/react-native/features/fixtures/scenario-launcher/package.json
+++ b/test/react-native/features/fixtures/scenario-launcher/package.json
@@ -20,6 +20,9 @@
"react-native-navigation": "*"
},
"peerDependencies": {
+ "@bugsnag/plugin-react-navigation": "*",
+ "@react-navigation/native": "*",
+ "@react-navigation/native-stack": "*",
"react": "*",
"react-native": "*",
"react-native-file-access": "*"
diff --git a/test/react-native/features/fixtures/scenario-launcher/src/lib/ScenarioLauncher.js b/test/react-native/features/fixtures/scenario-launcher/src/lib/ScenarioLauncher.js
index 4ef7fbafd..e389e43f3 100644
--- a/test/react-native/features/fixtures/scenario-launcher/src/lib/ScenarioLauncher.js
+++ b/test/react-native/features/fixtures/scenario-launcher/src/lib/ScenarioLauncher.js
@@ -3,7 +3,7 @@ import { getCurrentCommand } from './CommandRunner'
import { NativeInterface } from './native'
import Bugsnag from '@bugsnag/react-native'
-async function runScenario (scenarioName, apiKey, notifyEndpoint, sessionEndpoint, scenarioData) {
+async function runScenario (scenarioName, apiKey, notifyEndpoint, sessionEndpoint, scenarioData, setScenario) {
console.error(`[Bugsnag ScenarioLauncher] running scenario: ${scenarioName}`)
const nativeConfig = {
@@ -20,8 +20,6 @@ async function runScenario (scenarioName, apiKey, notifyEndpoint, sessionEndpoin
// create the scenario and allow it to modify the configuration
const scenario = new Scenarios[scenarioName](nativeConfig, jsConfig, scenarioData)
- console.error(`[Bugsnag ScenarioLauncher] with config: ${JSON.stringify(nativeConfig)} (native) and ${JSON.stringify(jsConfig)} (js)`)
-
// clear persistent data
console.error('[Bugsnag ScenarioLauncher] clearing persistent data')
NativeInterface.clearPersistentData()
@@ -36,7 +34,10 @@ async function runScenario (scenarioName, apiKey, notifyEndpoint, sessionEndpoin
// run the scenario
console.error('launching scenario')
- setTimeout(() => scenario.run(), 1)
+ setTimeout(() => {
+ scenario.run()
+ if (typeof scenario.view === 'function') setScenario(scenario)
+ }, 1)
}
async function startBugsnag (scenarioName, apiKey, notifyEndpoint, sessionEndpoint, scenarioData) {
@@ -67,7 +68,7 @@ async function startBugsnag (scenarioName, apiKey, notifyEndpoint, sessionEndpoi
Bugsnag.start(jsConfig)
}
-export async function launchScenario () {
+export async function launchScenario (setScenario) {
const command = await getCurrentCommand()
switch (command.action) {
@@ -78,7 +79,8 @@ export async function launchScenario () {
command.api_key,
command.notify,
command.sessions,
- command.scenario_data
+ command.scenario_data,
+ setScenario
)
case 'start-bugsnag':
diff --git a/test/react-native/features/fixtures/scenario-launcher/src/scenarios/ReactNavigationBreadcrumbsDisabledScenario.js b/test/react-native/features/fixtures/scenario-launcher/src/scenarios/ReactNavigationBreadcrumbsDisabledScenario.js
new file mode 100644
index 000000000..8c6c37362
--- /dev/null
+++ b/test/react-native/features/fixtures/scenario-launcher/src/scenarios/ReactNavigationBreadcrumbsDisabledScenario.js
@@ -0,0 +1,73 @@
+import Scenario from './Scenario'
+import Bugsnag from '@bugsnag/react-native'
+import BugsnagPluginReactNavigation from '@bugsnag/plugin-react-navigation'
+import * as React from 'react'
+import { View, Text } from 'react-native'
+import { NavigationContainer } from '@react-navigation/native'
+import { createNativeStackNavigator } from '@react-navigation/native-stack'
+
+const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
+
+export class ReactNavigationBreadcrumbsDisabledScenario extends Scenario {
+ constructor (configuration, jsConfig) {
+ super()
+ configuration.enabledBreadcrumbTypes = ['process', 'request', 'log']
+ jsConfig.plugins = [new BugsnagPluginReactNavigation()]
+ }
+
+ view () {
+ const BugsnagNavigationContainer = Bugsnag.getPlugin('reactNavigation').createNavigationContainer(NavigationContainer)
+ const Stack = createNativeStackNavigator()
+ return (
+
+
+
+
+
+
+ )
+ }
+
+ run () {
+ }
+}
+
+function HomeScreen ({ navigation }) {
+ React.useEffect(() => {
+ (async () => {
+ await delay(100)
+ Bugsnag.notify(new Error('HomeNavigationError'))
+ await delay(250)
+ navigation.navigate('Details')
+ })()
+ }, [])
+
+ return (
+
+ Home Screen
+
+ )
+}
+
+function DetailsScreen ({ navigation }) {
+ React.useEffect(() => {
+ (async () => {
+ await delay(100)
+ Bugsnag.notify(new Error('DetailsNavigationError'))
+ await delay(250)
+ throw new Error('DetailsNavigationUnhandledError')
+ })()
+ }, [])
+
+ return (
+
+ Details Screen
+
+ )
+}
diff --git a/test/react-native/features/fixtures/scenario-launcher/src/scenarios/ReactNavigationBreadcrumbsEnabledScenario.js b/test/react-native/features/fixtures/scenario-launcher/src/scenarios/ReactNavigationBreadcrumbsEnabledScenario.js
new file mode 100644
index 000000000..6d3fdedd1
--- /dev/null
+++ b/test/react-native/features/fixtures/scenario-launcher/src/scenarios/ReactNavigationBreadcrumbsEnabledScenario.js
@@ -0,0 +1,72 @@
+import Scenario from './Scenario'
+import Bugsnag from '@bugsnag/react-native'
+import BugsnagPluginReactNavigation from '@bugsnag/plugin-react-navigation'
+import * as React from 'react'
+import { View, Text } from 'react-native'
+import { NavigationContainer } from '@react-navigation/native'
+import { createNativeStackNavigator } from '@react-navigation/native-stack'
+
+const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
+
+export class ReactNavigationBreadcrumbsEnabledScenario extends Scenario {
+ constructor (_configuration, jsConfig) {
+ super()
+ jsConfig.plugins = [new BugsnagPluginReactNavigation()]
+ }
+
+ view () {
+ const BugsnagNavigationContainer = Bugsnag.getPlugin('reactNavigation').createNavigationContainer(NavigationContainer)
+ const Stack = createNativeStackNavigator()
+ return (
+
+
+
+
+
+
+ )
+ }
+
+ run () {
+ }
+}
+
+function HomeScreen ({ navigation }) {
+ React.useEffect(() => {
+ (async () => {
+ await delay(100)
+ Bugsnag.notify(new Error('HomeNavigationError'))
+ await delay(250)
+ navigation.navigate('Details')
+ })()
+ }, [])
+
+ return (
+
+ Home Screen
+
+ )
+}
+
+function DetailsScreen ({ navigation }) {
+ React.useEffect(() => {
+ (async () => {
+ await delay(100)
+ Bugsnag.notify(new Error('DetailsNavigationError'))
+ await delay(250)
+ throw new Error('DetailsNavigationUnhandledError')
+ })()
+ }, [])
+
+ return (
+
+ Details Screen
+
+ )
+}
diff --git a/test/react-native/features/fixtures/scenario-launcher/src/scenarios/index.js b/test/react-native/features/fixtures/scenario-launcher/src/scenarios/index.js
index 9fbb92d42..ea540f5be 100644
--- a/test/react-native/features/fixtures/scenario-launcher/src/scenarios/index.js
+++ b/test/react-native/features/fixtures/scenario-launcher/src/scenarios/index.js
@@ -71,3 +71,7 @@ export { UnhandledOverrideJsErrorScenario } from './UnhandledOverrideJsErrorScen
export { FeatureFlagsScenario } from './FeatureFlagsScenario'
export { FeatureFlagsNativeCrashScenario } from './FeatureFlagsNativeCrashScenario'
export { NativeFeatureFlagsScenario } from './NativeFeatureFlagsScenario'
+
+// navigation.feature
+export { ReactNavigationBreadcrumbsEnabledScenario } from './ReactNavigationBreadcrumbsEnabledScenario'
+export { ReactNavigationBreadcrumbsDisabledScenario } from './ReactNavigationBreadcrumbsDisabledScenario'
diff --git a/test/react-native/features/navigation.feature b/test/react-native/features/navigation.feature
index 5ddcae29b..335427dfd 100644
--- a/test/react-native/features/navigation.feature
+++ b/test/react-native/features/navigation.feature
@@ -1,4 +1,4 @@
-@navigation
+@navigation @skip_new_arch
Feature: Navigation plugin features
Scenario: Navigating screens causes breadcrumbs and context to be updated