Repository navigation
test: add Detox e2e suite with a dedicated E2ETestApp #3881
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
6d3c1cf
feat(SampleApp): add Detox e2e suite with mock server, robots and pagβ¦
testableapple bcd7e37
ci: trust the applesimutils formula before installing it
testableapple e121dd1
ci(e2e): run Detox on a dedicated E2ETestApp and cache builds
testableapple fface0a
ci(e2e): set composer text directly on Android and give the emulator β¦
testableapple 5df91f8
ci(e2e): run the Android emulator with the native SDK's setup
testableapple 508150d
ci(e2e): use the default emulator RAM and log OOM kills on failure
testableapple f5554cc
Disable iOS
testableapple cd6ab59
ci(e2e): run Android on API 36 and upload emulator crash data
testableapple 239e8ec
Test iOS
testableapple ad53596
Merge branch 'develop' into alexeyalterpesotskiy/rn-449-e2e-infra
testableapple d97eedf
Test iOS
testableapple cff6aad
ci(e2e): drop iOS launch permissions and pre-boot the simulator
testableapple 40d9faa
ci(e2e): keep the Detox config out of the app cache keys
testableapple be2d3da
Uncomment stuff
testableapple 62684e8
ci(e2e): stop running on develop pushes
testableapple 109051d
fix(fastlane): restore the SDK size gate and fix the README's API level
testableapple 0ce18c0
ci(e2e): dismiss Android ANR dialogs before the tests
testableapple 159b7a1
fix(MessageStatus): put the status testIDs on the icons
testableapple e69f5e6
Merge remote-tracking branch 'origin/develop' into alexeyalterpesotskβ¦
testableapple File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,339 @@ | ||
| name: E2E Tests | ||
|
|
||
| on: | ||
| pull_request: | ||
|
|
||
| workflow_dispatch: | ||
| inputs: | ||
| ios: | ||
| description: "Run iOS e2e tests" | ||
| type: boolean | ||
| default: false | ||
| android: | ||
| description: "Run Android e2e tests" | ||
| type: boolean | ||
| default: true | ||
| mock_server_branch: | ||
| description: "stream-chat-test-mock-server branch" | ||
| type: string | ||
| default: "main" | ||
|
|
||
| permissions: | ||
| contents: read | ||
| pull-requests: read | ||
| checks: read | ||
|
|
||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| env: | ||
| MOCK_SERVER_BRANCH: ${{ inputs.mock_server_branch || 'main' }} | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| GITHUB_PR_NUM: ${{ github.event.pull_request.number }} | ||
| GITHUB_EVENT_ACTION: ${{ github.event.action }} | ||
| GITHUB_EVENT_BEFORE: ${{ github.event.before }} | ||
| GITHUB_EVENT_AFTER: ${{ github.event.after }} | ||
| # node_modules of the e2e app and the workspaces it depends on (nmHoistingLimits: workspaces | ||
| # keeps them apart); `yarn workspaces focus` installs nothing else. | ||
| NODE_MODULES_PATHS: | | ||
| node_modules | ||
| configs/*/node_modules | ||
| package/node_modules | ||
| package/native-package/node_modules | ||
| examples/E2ETestApp/node_modules | ||
| .yarn/install-state.gz | ||
|
|
||
| jobs: | ||
| check: | ||
| name: Check E2E Required | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 10 | ||
| outputs: | ||
| required: ${{ steps.check.outputs.required }} | ||
| steps: | ||
| - uses: actions/checkout@v6 | ||
| - uses: ./.github/actions/ruby-cache | ||
| - name: Check if e2e tests are required | ||
| id: check | ||
| working-directory: examples/SampleApp | ||
| run: bundle exec fastlane e2e_check_required | ||
|
|
||
| ios: | ||
| needs: check | ||
| if: needs.check.outputs.required == 'true' && github.event_name == 'workflow_dispatch' && inputs.ios | ||
| runs-on: macos-15 | ||
| timeout-minutes: 60 | ||
| env: | ||
| DETOX_IOS_DEVICE: "iPhone 17 Pro" | ||
| IOS_VERSION: "26.2" | ||
| XCODE_VERSION: "26.2" | ||
| APP_PATH: examples/E2ETestApp/ios/build/detox/Build/Products/Release-iphonesimulator/E2ETestApp.app | ||
| MOCK_SERVER_BUNDLE_PATH: ${{ github.workspace }}/../mock-server-gems | ||
| steps: | ||
| - uses: actions/checkout@v6 | ||
| - uses: actions/setup-node@v6 | ||
| with: | ||
| node-version: 24.x | ||
| cache: "yarn" | ||
| cache-dependency-path: "yarn.lock" | ||
| - uses: maxim-lobanov/setup-xcode@v1 | ||
| with: | ||
| xcode-version: ${{ env.XCODE_VERSION }} | ||
| # A fresh simulator takes minutes to boot on CI; boot it now so that overlaps with setup. | ||
| - name: Boot simulator | ||
| run: | | ||
| runtime="com.apple.CoreSimulator.SimRuntime.iOS-${IOS_VERSION//./-}" | ||
| udid=$(xcrun simctl list devices available -j | | ||
| jq -r --arg rt "$runtime" --arg name "$DETOX_IOS_DEVICE" '.devices[$rt][]? | select(.name == $name) | .udid' | head -1) | ||
| if [ -z "$udid" ]; then | ||
| echo "::warning::No $DETOX_IOS_DEVICE on iOS $IOS_VERSION; Detox will pick and boot a simulator itself" | ||
| exit 0 | ||
| fi | ||
| echo "DETOX_IOS_UDID=$udid" >> "$GITHUB_ENV" | ||
| nohup xcrun simctl boot "$udid" > /dev/null 2>&1 & | ||
| - uses: ./.github/actions/ruby-cache | ||
|
|
||
| # --- JS dependencies --- | ||
| - name: Restore node_modules | ||
| id: node-modules | ||
| uses: actions/cache/restore@v5 | ||
| with: | ||
| path: ${{ env.NODE_MODULES_PATHS }} | ||
| key: ${{ runner.os }}-e2e-node-modules-${{ hashFiles('yarn.lock') }} | ||
| # Only the e2e app's dependency tree, not every example app. | ||
| - name: Install dependencies | ||
| run: yarn workspaces focus e2etestapp | ||
| - name: Save node_modules | ||
| if: steps.node-modules.outputs.cache-hit != 'true' | ||
| uses: actions/cache/save@v5 | ||
| with: | ||
| path: ${{ env.NODE_MODULES_PATHS }} | ||
| key: ${{ steps.node-modules.outputs.cache-primary-key }} | ||
|
|
||
| # --- App binary: rebuilt only when the app or SDK sources change, not for test-only changes --- | ||
| - name: Restore app | ||
| id: app | ||
| uses: actions/cache/restore@v5 | ||
| with: | ||
| path: ${{ env.APP_PATH }} | ||
| key: e2e-ios-app-xcode${{ env.XCODE_VERSION }}-${{ hashFiles('yarn.lock', 'package/package.json', 'package/src/**', 'package/shared-native/**', 'package/native-package/package.json', 'package/native-package/src/**', 'package/native-package/ios/**', 'package/native-package/*.podspec', 'examples/SampleApp/Gemfile.lock', 'examples/E2ETestApp/*.js', '!examples/E2ETestApp/.detoxrc.js', 'examples/E2ETestApp/*.json', 'examples/E2ETestApp/*.tsx', 'examples/E2ETestApp/src/**', 'examples/E2ETestApp/ios/**', '!examples/E2ETestApp/ios/build/**', '!examples/E2ETestApp/ios/Pods/**') }} | ||
| - name: Set up ccache | ||
| if: steps.app.outputs.cache-hit != 'true' | ||
| uses: hendrikmuhs/ccache-action@v1.2 | ||
| with: | ||
| key: e2e-ios-xcode${{ env.XCODE_VERSION }} | ||
| max-size: 1500M | ||
| - name: Point the RN ccache wrappers at ccache | ||
| if: steps.app.outputs.cache-hit != 'true' | ||
| # RN's ccache-clang.sh runs `$CCACHE_BINARY clang`, but Xcode doesn't export that build | ||
| # setting to the compiler, so without this every compile silently skips ccache. | ||
| run: echo "CCACHE_BINARY=$(which ccache)" >> "$GITHUB_ENV" | ||
| - name: Cache pods | ||
| if: steps.app.outputs.cache-hit != 'true' | ||
| uses: actions/cache@v5 | ||
| with: | ||
| path: examples/E2ETestApp/ios/Pods | ||
| key: ${{ runner.os }}-e2e-pods-${{ hashFiles('examples/E2ETestApp/ios/Podfile.lock') }} | ||
| restore-keys: | | ||
| ${{ runner.os }}-e2e-pods- | ||
| - name: Install pods | ||
| if: steps.app.outputs.cache-hit != 'true' | ||
| working-directory: examples/SampleApp | ||
| run: USE_CCACHE=1 bundle exec fastlane e2e_pod_install | ||
| - name: Build app | ||
| if: steps.app.outputs.cache-hit != 'true' | ||
| working-directory: examples/E2ETestApp | ||
| run: yarn e2e:build -c ios.sim.release | ||
| - name: Save app | ||
| if: steps.app.outputs.cache-hit != 'true' | ||
| uses: actions/cache/save@v5 | ||
| with: | ||
| path: ${{ env.APP_PATH }} | ||
| key: ${{ steps.app.outputs.cache-primary-key }} | ||
|
|
||
| # --- Test tooling --- | ||
| - name: Install applesimutils | ||
| # Homebrew refuses formulae from non-official taps until they are trusted. | ||
| run: brew tap wix/brew && brew trust --formula wix/brew/applesimutils && brew install wix/brew/applesimutils | ||
| - name: Restore Detox framework | ||
| id: detox-framework | ||
| uses: actions/cache/restore@v5 | ||
| with: | ||
| path: ~/Library/Detox | ||
| key: ${{ runner.os }}-detox-framework-xcode${{ env.XCODE_VERSION }}-${{ hashFiles('yarn.lock') }} | ||
| # Yarn's `enableScripts: false` skips Detox's postinstall, which would build this. | ||
| - name: Build Detox framework | ||
| if: steps.detox-framework.outputs.cache-hit != 'true' | ||
| working-directory: examples/E2ETestApp | ||
| run: yarn detox build-framework-cache | ||
| - name: Save Detox framework | ||
| if: steps.detox-framework.outputs.cache-hit != 'true' | ||
| uses: actions/cache/save@v5 | ||
| with: | ||
| path: ~/Library/Detox | ||
| key: ${{ steps.detox-framework.outputs.cache-primary-key }} | ||
| - name: Resolve mock server revision | ||
| id: mock-server | ||
| run: echo "sha=$(git ls-remote https://github.com/GetStream/stream-chat-test-mock-server "refs/heads/$MOCK_SERVER_BRANCH" | cut -f1)" >> "$GITHUB_OUTPUT" | ||
| - name: Cache mock server gems | ||
| uses: actions/cache@v5 | ||
| with: | ||
| path: ${{ env.MOCK_SERVER_BUNDLE_PATH }} | ||
| key: ${{ runner.os }}-mock-server-gems-${{ steps.mock-server.outputs.sha }} | ||
| restore-keys: | | ||
| ${{ runner.os }}-mock-server-gems- | ||
|
|
||
| - name: Wait for the simulator | ||
| if: env.DETOX_IOS_UDID != '' | ||
| timeout-minutes: 15 | ||
| # `-b` boots it if the background boot never started; it errors (405) when the device is | ||
| # already booted, and then the plain call returns at once. | ||
| run: xcrun simctl bootstatus "$DETOX_IOS_UDID" -b || xcrun simctl bootstatus "$DETOX_IOS_UDID" | ||
| - name: Run e2e tests | ||
| working-directory: examples/SampleApp | ||
| run: bundle exec fastlane test_e2e platform:ios skip_build:true mock_server_branch:"$MOCK_SERVER_BRANCH" | ||
| - uses: actions/upload-artifact@v6 | ||
| if: failure() | ||
| with: | ||
| name: e2e-ios-logs | ||
| path: | | ||
| examples/E2ETestApp/stream-chat-test-mock-server/logs | ||
| examples/E2ETestApp/artifacts | ||
|
|
||
| android: | ||
| needs: check | ||
| if: needs.check.outputs.required == 'true' && (github.event_name != 'workflow_dispatch' || inputs.android) | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 60 | ||
| env: | ||
| # Same as the native Android SDK e2e jobs, which run stably on these runners | ||
| ANDROID_API_LEVEL: "36" | ||
| ANDROID_EMULATOR_OPTIONS: -no-snapshot-save -no-window -no-audio -no-boot-anim -gpu swiftshader_indirect -camera-back none -camera-front none | ||
| DETOX_ADB_NAME: "emulator-5554" | ||
| APK_PATHS: | | ||
| examples/E2ETestApp/android/app/build/outputs/apk/release/app-release.apk | ||
| examples/E2ETestApp/android/app/build/outputs/apk/androidTest/release/app-release-androidTest.apk | ||
| MOCK_SERVER_BUNDLE_PATH: ${{ github.workspace }}/../mock-server-gems | ||
| # The emulator is x86_64: skip compiling native code for the other ABIs. | ||
| ORG_GRADLE_PROJECT_reactNativeArchitectures: x86_64 | ||
| steps: | ||
| - uses: actions/checkout@v6 | ||
| - uses: actions/setup-node@v6 | ||
| with: | ||
| node-version: 24.x | ||
| cache: "yarn" | ||
| cache-dependency-path: "yarn.lock" | ||
| - uses: actions/setup-java@v5 | ||
| with: | ||
| distribution: "zulu" | ||
| java-version: "21" | ||
| # Caches Gradle's dependencies, wrapper and build cache. It only writes on the default branch by | ||
| # 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 | ||
| - uses: ./.github/actions/ruby-cache | ||
|
|
||
| # --- JS dependencies --- | ||
| - name: Restore node_modules | ||
| id: node-modules | ||
| uses: actions/cache/restore@v5 | ||
| with: | ||
| path: ${{ env.NODE_MODULES_PATHS }} | ||
| key: ${{ runner.os }}-e2e-node-modules-${{ hashFiles('yarn.lock') }} | ||
| # Only the e2e app's dependency tree, not every example app. | ||
| - name: Install dependencies | ||
| run: yarn workspaces focus e2etestapp | ||
| - name: Save node_modules | ||
| if: steps.node-modules.outputs.cache-hit != 'true' | ||
| uses: actions/cache/save@v5 | ||
| with: | ||
| path: ${{ env.NODE_MODULES_PATHS }} | ||
| key: ${{ steps.node-modules.outputs.cache-primary-key }} | ||
|
|
||
| # --- App binaries: rebuilt only when the app or SDK sources change, not for test-only changes --- | ||
| - name: Restore APKs | ||
| id: apk | ||
| uses: actions/cache/restore@v5 | ||
| with: | ||
| path: ${{ env.APK_PATHS }} | ||
| key: e2e-android-apk-${{ hashFiles('yarn.lock', 'package/package.json', 'package/src/**', 'package/shared-native/**', 'package/native-package/package.json', 'package/native-package/src/**', 'package/native-package/android/**', '!package/native-package/android/build/**', 'examples/E2ETestApp/*.js', '!examples/E2ETestApp/.detoxrc.js', 'examples/E2ETestApp/*.json', 'examples/E2ETestApp/*.tsx', 'examples/E2ETestApp/src/**', 'examples/E2ETestApp/android/**', '!examples/E2ETestApp/android/**/build/**', '!examples/E2ETestApp/android/.gradle/**') }} | ||
| # Built before the emulator boots so the emulator doesn't idle through the Gradle build. | ||
| - name: Build app | ||
| if: steps.apk.outputs.cache-hit != 'true' | ||
| working-directory: examples/E2ETestApp | ||
| run: yarn e2e:build -c android.att.release | ||
| - name: Save APKs | ||
| if: steps.apk.outputs.cache-hit != 'true' | ||
| uses: actions/cache/save@v5 | ||
| with: | ||
| path: ${{ env.APK_PATHS }} | ||
| key: ${{ steps.apk.outputs.cache-primary-key }} | ||
| # Frees the daemons' memory for the emulator, which otherwise can get killed mid-run. | ||
| - name: Stop Gradle daemons | ||
| if: steps.apk.outputs.cache-hit != 'true' | ||
| working-directory: examples/E2ETestApp/android | ||
| run: ./gradlew --stop | ||
|
|
||
| # --- Emulator: same setup as the native Android SDK's e2e jobs (AOSP image, no Play services) --- | ||
| - name: Enable KVM | ||
| run: | | ||
| echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules | ||
| sudo udevadm control --reload-rules | ||
| sudo udevadm trigger --name-match=kvm | ||
|
|
||
| # --- Test tooling --- | ||
| - name: Resolve mock server revision | ||
| id: mock-server | ||
| run: echo "sha=$(git ls-remote https://github.com/GetStream/stream-chat-test-mock-server "refs/heads/$MOCK_SERVER_BRANCH" | cut -f1)" >> "$GITHUB_OUTPUT" | ||
| - name: Cache mock server gems | ||
| uses: actions/cache@v5 | ||
| with: | ||
| path: ${{ env.MOCK_SERVER_BUNDLE_PATH }} | ||
| key: ${{ runner.os }}-mock-server-gems-${{ steps.mock-server.outputs.sha }} | ||
| restore-keys: | | ||
| ${{ runner.os }}-mock-server-gems- | ||
|
|
||
| - name: Run e2e tests (Android emulator) | ||
| uses: reactivecircus/android-emulator-runner@v2 | ||
| with: | ||
| api-level: ${{ env.ANDROID_API_LEVEL }} | ||
| arch: x86_64 | ||
| profile: pixel | ||
| emulator-options: ${{ env.ANDROID_EMULATOR_OPTIONS }} | ||
| disable-animations: true | ||
| working-directory: examples/SampleApp | ||
| script: bundle exec fastlane test_e2e platform:android device:att skip_build:true mock_server_branch:"$MOCK_SERVER_BRANCH" | ||
| # The emulator has vanished mid-run without any output; this shows whether the kernel killed it. | ||
| - name: Diagnose emulator | ||
| if: failure() | ||
| run: | | ||
| sudo dmesg -T | grep -i -E 'out of memory|oom-kill|killed process' || echo "No OOM kills in dmesg" | ||
| free -m | ||
| df -h | ||
| ls -la /tmp/android-runner 2>/dev/null || true | ||
| - uses: actions/upload-artifact@v6 | ||
| if: failure() | ||
| with: | ||
| name: e2e-android-logs | ||
| path: | | ||
| examples/E2ETestApp/stream-chat-test-mock-server/logs | ||
| examples/E2ETestApp/artifacts | ||
| /tmp/android-runner | ||
|
|
||
| result: | ||
| name: Test E2E Result | ||
| runs-on: ubuntu-latest | ||
| if: always() | ||
| needs: | ||
| - check | ||
| - android | ||
| steps: | ||
| - name: Check E2E results | ||
| if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') | ||
| run: | | ||
| echo "$NEEDS" | ||
| exit 1 | ||
| env: | ||
| NEEDS: ${{ toJson(needs) }} | ||
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
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
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
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.