Skip to content

test: add Detox e2e suite with a dedicated E2ETestApp - #3881

Open
testableapple wants to merge 17 commits into
developfrom
alexeyalterpesotskiy/rn-449-e2e-infra
Open

testableapple wants to merge 17 commits into
developfrom
alexeyalterpesotskiy/rn-449-e2e-infra

Conversation

@testableapple

@testableapple testableapple commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

🎯 Goal

E2E test automation for the SDK, on par with the native iOS/Android and Flutter suites, running on PRs.

Linear: RN-450, RN-451, RN-452, RN-453, RN-454, RN-455, RN-457 (parent RN-449)

🛠 Implementation details

Dedicated app: examples/E2ETestApp. A new bare RN 0.88 workspace with only the SDK and its required peer dependencies (no Firebase, maps, video or navigation library), like the native SDKs' StreamChatUITestsApp. It has three screens: a user selector, the channel list, and a channel stacked over the list. It reads a MOCK_SERVER_URL launch argument (react-native-launch-arguments), never persists a login and runs without offline storage, so every test starts clean. A clean Release build takes ~2 min on an M-series Mac; the SampleApp took ~15 min on Android. The SampleApp app is unchanged; only its Fastfile gains the e2e lanes.

Framework (RN-450): Detox 20 + Jest 30 in examples/E2ETestApp/e2e/, with ios.sim.*, android.emu.* and android.att.* configs (debug and release).

Mock server (RN-451): the shared stream-chat-test-mock-server, one server per test through its driver (port 4569).

Page objects (RN-452) and robots (RN-453, RN-454, RN-455): structure and naming follow the native suites:

  • BackendRobot / ParticipantRobot: 1:1 ports of the Android robots
  • UserRobot + UserRobot*Asserts: assert extensions, like Android's
  • LoginPage / ChannelListPage / MessageListPage
  • setUpStreamTestCase(), step(), and uiautomator-style element helpers

Robots chain fluently like the native ones: await userRobot.login().openChannel().assertMessage('Hi').

Smoke suite: 6 tests (MessageList ×3, ChannelList ×3), named after their Android counterparts and keeping the native expectations.

SDK (additive testIDs only): channel-preview-title, channel-preview-message, channel-preview-sender, and message-status-{read,delivered,sending,sent}, with a unit test.

CI (RN-457): .github/workflows/e2e-test.yml.

  • Android runs on PRs: ubuntu-latest, KVM, API 36 AOSP emulator, pixel profile (the native Android SDK's setup).
  • iOS runs only on demand (workflow_dispatch with ios: true), not on PRs, to save CI time and macOS runners: macos-15, Xcode 26.2, iPhone 17 Pro.
  • Gated like the iOS SDK: a cheap Check E2E Required job runs is_check_required over the e2e sources (Fastfile impact_analysis), and test_e2e checks it too.
  • Test E2E Result is the check to require in branch protection. It covers Android (it doesn't wait for iOS) and passes when the Android job passes or is skipped as not required.
  • Caching:
    • the built .app / .apk, keyed on the app and SDK sources but not on e2e/, so a test-only change skips the native build
    • node_modules of yarn workspaces focus e2etestapp
    • CocoaPods, ccache (with CCACHE_BINARY exported, without which RN's ccache wrappers silently skip ccache), the Detox framework, Gradle (cache-read-only: false, since setup-gradle otherwise only writes on the default branch), and the mock server's gems
    • each PR fills its own caches (the workflow doesn't run on develop): its first run builds cold, and later pushes reuse them
  • On failure, uploads the mock server logs, Detox's logs and screenshots, and the emulator's crash data.

Fastlane (in examples/SampleApp/fastlane, next to the repo's other lanes): start_mock_server, stop_mock_server, e2e_pod_install, test_e2e, e2e_check_required.

Notable findings, documented in examples/E2ETestApp/e2e/README.md:

  • Fabric mounts some testID'd views (message-list-item-*, message-text-container) empty, with their content as siblings, so page objects scope to message-content-wrapper or the list instead.
  • iOS runs without Detox's idle synchronization, which can wedge on RN's UIManager shadow queue. Robots wait explicitly, and taps retry while the target isn't hittable yet.
  • launchApp({ permissions }) makes Detox restart SpringBoard once per permission on every launch. On CI that cost 1.5–3 min per test, so the suite doesn't pass any.
  • Android sets the composer text directly (as the native suite does). Typing through Gboard left the text in a composing state that the composer didn't send.

How to run and extend the suite: examples/E2ETestApp/e2e/README.md.

🎨 UI Changes

None (testIDs only).

🧪 Testing

Not covered yet: Allure TestOps reporting, full suite ports, and iOS on PRs (manual runs only, to save CI time and macOS runners).

☑️ Checklist

  • I have signed the Stream CLA (required)
  • PR targets the develop branch
  • Documentation is updated
  • New code is tested in main example apps, including all possible scenarios
    • SampleApp iOS and Android
    • Expo iOS and Android

@testableapple
testableapple marked this pull request as ready for review October 20, 2026 18:09
@testableapple testableapple changed the title feat(SampleApp): add Detox e2e suite with mock server, robots and page objects test: add Detox e2e suite with a dedicated E2ETestApp Oct 20, 2026
@Stream-SDK-Bot

Copy link
Copy Markdown
Contributor

SDK Size

title develop branch diff status
js_bundle_size 2036 KB 2036 KB +252 B 🟢
ai_components_size 130 KB 130 KB 0 B 🟢

@isekovanic
isekovanic self-requested a review October 26, 2026 10:00
accessibilityElementsHidden={!!accessibilityLabel}
importantForAccessibility={accessibilityLabel ? 'no-hide-descendants' : undefined}
style={[styles.container, container]}
testID={testID}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Woulc it be possible to move this further down please ? On the icon(s) for example or something like that.

The reason is because RN has a feature called view flattening, which essentially means that not all views in React's virtual DOM tree are going to be translated directly to native views under the hood. This is mostly done for performance reasons, in order for us to avoid doing unnecessary layout/rendering work.

Passing a testID is one surefire way to make a view non-collapsible.

The icons under the hood are SVGs anyway, so they aren't collapsible by default (and should work).

checks: ['Test E2E Result']
},
size: {
sources: ['package/', 'package.json']

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This drops examples/SampleApp/fastlane/ and examples/SampleApp/Gemfile.lock from the size check sources that #3858 added, along with the comment explaining them right ?

They were added there so that changes to how size is measured (the size lanes, the stream_actions plugin version, the root minify-* scripts) rerun the Metrics job too, not only SDK changes. Looks like the gate got lost in a develop merge and was restored from the older list. Could we use the full list here?

So something like:

size: {
  sources: ['package/', 'package.json', 'examples/SampleApp/fastlane/', 'examples/SampleApp/Gemfile.lock']
}

(but if I'm wrong about this please ignore)

# default, and this workflow doesn't run there, so let PR runs write: their reruns reuse it.
- uses: gradle/actions/setup-gradle@v4
with:
cache-read-only: false

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we run it on pushes to develop or add a small cache warming job there maybe ? Then PR runs can restore from develop and stay readonly, which is what setup-gradle for example does by default. That also gets rid of the cold first run on every PR.

Asking this because I think there's a cache limit to actions (I think it's 10GB or something ?) and this looks like it'll add quite a few more things to the cache, hence causing it to begin evicting stuff and make branch builds longer intrinsically. Or shall we maybe make it runnable as a workflow_dispatch action on branches and do an automatic run on pushing to develop specifically ?

Another thing is that it'll make branch checks slower by default (depending on how long it lasts)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants