Adds CI coverage for the existing Detox iOS E2E suites in RNApp, ExpoApp54, and ExpoApp55.#352
Open
alpharius-ck wants to merge 15 commits into
Open
Adds CI coverage for the existing Detox iOS E2E suites in RNApp, ExpoApp54, and ExpoApp55.#352alpharius-ck wants to merge 15 commits into
alpharius-ck wants to merge 15 commits into
Conversation
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
# Conflicts: # turbo.json
Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds CI coverage for the existing Detox iOS E2E suites in RNApp, ExpoApp54, and ExpoApp55.
Introduces a reusable composite action (.github/actions/e2e-ios) that:
Test plan
If a job fails, download the detox-rnapp-ios / detox-expo54-ios / detox-expo55-ios artifact and inspect screenshots/logs
Changes for native files:
BrownfieldStore “not found” fix (RNApp iOS)
This document summarizes the investigation and fixes for the Brownie error when running RNApp on iOS:
AppleApp (brownfield host) worked with the same JS and store schema; only standalone RNApp failed until these changes were applied.
The problem
When running RNApp on iOS, React crashed on the first render of
Counterwith the error above.useStore('BrownfieldStore', …)in JavaScript could not see a store that native code was supposed to have registered.How Brownie works (short)
Brownie shares state between native and React Native through two layers:
BrownieStoreManager— holds stores and backsglobal.__brownieGetStore(what JS uses).Store/StoreManager— optional native-side wrapper;BrownfieldStore.register(...)creates aStoreand calls into the C++ bridge.JavaScript (
@callstack/brownie) reads stores via:That global is installed by
BrownieInstaller::install(), which must run when the Brownie turbo module is loaded under the New Architecture.Native registration must hit the same C++ manager instance that JavaScript uses.
Root causes
There were two separate issues, not one.
1. Duplicate Brownie in RNApp (main RNApp bug)
RNApp linked and embedded
BrownfieldLib.frameworkinto the app. That framework was built withinherit! :completein the Podfile, so it contained a full second copy of Brownie (including its ownBrownieStoreManagersingleton).At runtime:
AppDelegate→BrownfieldStore.register(...)__brownieGetStore→ C++ lookupRegistration and JS were talking to different singletons, so JS always saw “store not found” even though registration appeared to succeed on native.
AppleApp avoids this: it links one
Brownie.xcframeworkand oneBrownfieldLib.xcframeworkseparately, and registers viaimport Browniein the app — not by embedding a BrownfieldLib that re-exports and duplicates Brownie.2. JSI bindings not installed on iOS New Architecture (brownie package bug)
On iOS with
RCT_NEW_ARCH_ENABLED, React installs turbo-module JSI bindings only when the module is a C++TurboModuleWithJSIBindings.BrownieModuleonly implemented the ObjC protocolRCTTurboModuleWithJSIBindings. In bridgeless / New Architecture, that path is not used;dynamic_cast<TurboModuleWithJSIBindings*>on the C++ module fails, soBrownieInstaller::install()never ran andglobal.__brownieGetStorestayed undefined.Android already handled this in
BrownieModule.initialize()viainstallJSIBindingsIfNeeded().This could affect any iOS React Native 0.85+ app using Brownie; RNApp made it obvious because JS hits the store immediately on launch.
What we changed
RNApp — iOS app target
ios/RNApp.xcodeproj/project.pbxprojBrownfieldLibfrom the RNApp app target. TheBrownfieldLibtarget remains forbrownfield package:ios.ios/RNApp/AppDelegate.swiftimport Brownie; register store beforefactory.startReactNative(...)viaBrownieBootstrap.register(...).ios/BrownfieldLib/BrownfieldLib.swift@_exported import Brownie. Only re-exportsReactBrownfield.BrownfieldLibRNApp — Android
android/app/src/main/java/com/rnapp/MainApplication.ktregisterStoreIfNeeded(...)forBrownfieldStorebeforeloadReactNative(this).android/app/build.gradleimplementation(project(":BrownfieldLib"))for generated Kotlin types.@callstack/browniepackagepackages/brownie/ios/BrownieModule.mmBrownieTurboModuleextendingNativeBrownieModuleSpecJSI+TurboModuleWithJSIBindings;getTurboModulereturns it;installJSIBindingsWithRuntimecallsBrownieInstaller::install(runtime).__brownieGetStorenever set on New Architecturepackages/brownie/ios/BrownieModule.hRCTTurboModuleWithJSIBindingsimport from header.packages/brownie/ios/BrownieStore.swiftBrownieBootstrap— registers viaBrownieStoreBridgedirectly (C++ path JS uses).RNApp — tooling
package.jsonios/androidscripts runyarn codegenfirst.Codegen (operational)
From
apps/RNApp:This generates:
packages/brownie/ios/Generated/BrownfieldStore.swift(gitignored)android/BrownfieldLib/.../Generated/BrownfieldStore.ktWhy each fix works
Stop embedding
BrownfieldLibin RNAppRNApp as a standalone React Native app does not need the brownfield packaging framework at runtime. Embedding it pulled in a second Brownie. After removal, there is one
BrownieStoreManagerin the process; registration and JS use the same store map.BrownfieldLibis still built foryarn brownfield:package:ios; it is just not part of the dev app binary anymore.Register before React Native starts
Brownie expects stores to exist before the JS bundle runs components that call
useStore. BothAppDelegate(iOS) andMainApplication(Android) register initial state before starting RN.BrownieTurboModule(library fix)When JS first loads the Brownie turbo module, React calls
TurboModuleWithJSIBindings::installJSIBindingson the C++ module. That runsBrownieInstaller::install, which definesglobal.__brownieGetStore. Without this, JS fails even with a correctly registered native store.BrownieBootstrap(optional)BrownfieldStore.registercreates a SwiftStoreand also talks to the bridge. After the duplicate was removed,BrownfieldStore.registeris sufficient for RNApp again.BrownieBootstrapregisters straight throughBrownieStoreBridgeand documents “use this from AppDelegate before RN starts.” You can use either:No Brownie re-export in
BrownfieldLibPackaged
BrownfieldLib.xcframeworkshould not embed another full Brownie. Host apps (like AppleApp) link Brownie explicitly. That keeps brownfield packaging aligned with AppleApp’s working layout.Required vs optional going forward
Required for RNApp
BrownfieldLibin the RNApp app target.BrownieTurboModulein@callstack/browniefor iOS New Architecture.yarn codegenwhen store schemas change.Optional
BrownieBootstrapvsBrownfieldStore.registerinAppDelegate(equivalent after duplicate fix).yarn codegen &&prepended toios/androidscripts.Unchanged
BrownfieldStore.registerin appinit, separateBrownie+BrownfieldLibxcframeworks — no change needed.End-to-end flow after fixes (RNApp iOS)
sequenceDiagram participant AD as AppDelegate participant BB as Brownie C++ manager participant RN as React Native participant JS as JS useStore AD->>BB: BrownieBootstrap / register store AD->>RN: startReactNative RN->>JS: Load bundle JS->>RN: Require Brownie turbo module RN->>BB: BrownieTurboModule installs __brownieGetStore JS->>BB: __brownieGetStore("BrownfieldStore") BB-->>JS: Host object with stateTakeaway
The error looked like “forgot to register the store,” but RNApp actually had two separate issues:
BrownfieldLib) — why AppleApp worked and RNApp did not.BrownieTurboModule.Removing the duplicate was the decisive fix for RNApp; the turbo module fix is still necessary for correct Brownie behavior on modern React Native iOS.