Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
7b99a2a
feat: add shared jest tests for demo apps
narefyev91 May 6, 2026
7111313
feat: use correct version for yarn
narefyev91 May 6, 2026
d12501f
feat: add change set
alpharius-ck May 7, 2026
ca838d6
Potential fix for pull request finding
alpharius-ck May 8, 2026
643b12c
feat: add required globals
alpharius-ck May 8, 2026
0bd212f
feat: fixes for local build
alpharius-ck May 12, 2026
fde9f2b
Merge branch 'main' into fixes-for-local-build
alpharius-ck May 13, 2026
3f0dd65
feat: add ios e2e tests
alpharius-ck May 22, 2026
5eae0f4
chore: merge main to current branch
alpharius-ck May 22, 2026
4c74e91
feat: add ios e2e tests
alpharius-ck May 26, 2026
e0c6bca
feat: update CI failed
alpharius-ck May 26, 2026
e126da0
feat: add document
alpharius-ck May 26, 2026
b0e8189
Merge branch 'main' into e2e-tests-for-rn-ios
alpharius-ck May 26, 2026
5e70e03
feat: add concurent ci runs
alpharius-ck May 26, 2026
370fee2
chore: add dispatch
alpharius-ck May 26, 2026
7f4890a
chore: retrigger CI
alpharius-ck May 27, 2026
fce7ce1
feat: fix actions for detox
alpharius-ck May 27, 2026
1b8180a
feat: fix actions for RNApp
alpharius-ck May 27, 2026
ac32c18
feat: add correct sim for ci
alpharius-ck May 28, 2026
ba5a4c7
feat: update ci
alpharius-ck May 28, 2026
6079aa0
feat: update ci
alpharius-ck May 28, 2026
26ff08e
feat: update RNApp on CI
alpharius-ck May 29, 2026
9d94277
feat: update Expo55 on CI
alpharius-ck May 29, 2026
a53ac67
feat: add local bash commands
alpharius-ck Jun 1, 2026
ef6d578
feat: add fix for Expo55
alpharius-ck Jun 1, 2026
9ed1725
feat: add fix for Expo55 ci
alpharius-ck Jun 1, 2026
71c8e55
feat: add fix for Expo54 ci
alpharius-ck Jun 2, 2026
07e3525
Merge branch 'main' into e2e-tests-for-rn-ios
alpharius-ck Jun 2, 2026
4eeb404
feat: add fix for Vanilla android
alpharius-ck Jun 3, 2026
db3eb33
feat: add change set
alpharius-ck Jun 3, 2026
f41059b
feat: add changes based on first review
alpharius-ck Jun 8, 2026
994dae2
feat: add more changes
alpharius-ck Jun 8, 2026
699a96d
feat: add E2E tests for AppleApp
alpharius-ck Jun 8, 2026
9b98257
feat: add missing local run
alpharius-ck Jun 8, 2026
0097306
feat: fix e2e test
alpharius-ck Jun 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions .github/actions/e2e-ios/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
name: iOS E2E (Detox)
description: Build and run Detox iOS end-to-end tests for a brownfield example app

inputs:
app-path:
description: 'Path to the app workspace (e.g. apps/RNApp)'
required: true

run-expo-prebuild:
description: 'Run brownfield codegen and Expo iOS prebuild before pod install'
required: false
default: 'false'

run-brownfield-codegen:
description: 'Run brownfield codegen before building (required for RNApp)'
required: false
default: 'false'

artifact-name:
description: 'Name prefix for Detox artifacts uploaded on failure'
required: false
default: 'detox'

runs:
using: composite
steps:
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6

- name: Setup
uses: ./.github/actions/setup

- name: Prepare iOS environment
uses: ./.github/actions/prepare-ios

- name: Install applesimutils
run: |
brew tap wix/brew
brew install applesimutils
shell: bash

- name: Brownfield codegen
if: inputs.run-brownfield-codegen == 'true'
run: yarn codegen
working-directory: ${{ inputs.app-path }}
shell: bash

- name: Expo iOS prebuild
if: inputs.run-expo-prebuild == 'true'
run: |
node ../../packages/cli/dist/main.js codegen
yarn expo prebuild --platform ios --no-install
working-directory: ${{ inputs.app-path }}
shell: bash

- name: Restore Pods cache
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5
with:
path: ${{ inputs.app-path }}/ios/Pods
key: ${{ runner.os }}-e2e-ios-pods-${{ inputs.app-path }}-${{ hashFiles(format('{0}/ios/Podfile.lock', inputs.app-path)) }}
restore-keys: |
${{ runner.os }}-e2e-ios-pods-${{ inputs.app-path }}-

- name: Install CocoaPods
run: pod install
working-directory: ${{ inputs.app-path }}/ios
shell: bash

- name: Restore Detox build cache
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5
with:
path: ${{ inputs.app-path }}/ios/build
key: ${{ runner.os }}-e2e-ios-build-${{ inputs.app-path }}-${{ hashFiles(format('{0}/ios/Podfile.lock', inputs.app-path), format('{0}/ios/*.xcodeproj/project.pbxproj', inputs.app-path)) }}
restore-keys: |
${{ runner.os }}-e2e-ios-build-${{ inputs.app-path }}-

- name: Detox build (iOS Simulator)
run: yarn e2e:build:ios
working-directory: ${{ inputs.app-path }}
shell: bash

- name: Detox test (iOS Simulator)
run: yarn e2e:test:ios
working-directory: ${{ inputs.app-path }}
shell: bash

- name: Upload Detox artifacts on failure
if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: ${{ inputs.artifact-name }}-ios
path: ${{ inputs.app-path }}/artifacts
if-no-files-found: ignore
52 changes: 52 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,13 @@
- 'packages/**'
rnapp:
- 'apps/RNApp/**'
- 'apps/brownfield-example-shared-tests/**'
expo54:
- 'apps/ExpoApp54/**'
- 'apps/brownfield-example-shared-tests/**'
expo55:
- 'apps/ExpoApp55/**'
- 'apps/brownfield-example-shared-tests/**'
androidapp:
- 'apps/AndroidApp/**'
appleapp:
Expand Down Expand Up @@ -201,3 +204,52 @@
with:
variant: expo${{ matrix.version }}
rn-project-path: apps/ExpoApp${{ matrix.version }}

e2e-ios-rnapp:
name: E2E iOS (RNApp)
runs-on: macos-26
timeout-minutes: 90
needs: [filter, build-lint]
if: |
always() &&
(
needs.filter.outputs.rnapp == 'true' ||
needs.filter.outputs.packages == 'true' ||
needs.filter.outputs.ci == 'true'
) &&
(needs.build-lint.result == 'success' || needs.build-lint.result == 'skipped')
steps:
- name: Run Detox E2E (RNApp)
uses: ./.github/actions/e2e-ios
with:
app-path: apps/RNApp
artifact-name: detox-rnapp
run-brownfield-codegen: 'true'

e2e-ios-expo:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
name: E2E iOS (Expo ${{ matrix.version }})
runs-on: macos-26
timeout-minutes: 90
needs: [filter, build-lint]
if: |
always() &&
(
needs.filter.outputs.expo54 == 'true' ||
needs.filter.outputs.expo55 == 'true' ||
needs.filter.outputs.packages == 'true' ||
needs.filter.outputs.ci == 'true'
) &&
(needs.build-lint.result == 'success' || needs.build-lint.result == 'skipped')
strategy:
fail-fast: false
matrix:
include:
- version: '54'
- version: '55'
steps:
- name: Run Detox E2E (Expo ${{ matrix.version }})
uses: ./.github/actions/e2e-ios
with:
app-path: apps/ExpoApp${{ matrix.version }}
artifact-name: detox-expo${{ matrix.version }}
run-expo-prebuild: 'true'

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
43 changes: 43 additions & 0 deletions apps/ExpoApp54/.detoxrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const {
getIosSimulatorDeviceType,
} = require('../brownfield-example-shared-tests/detox-ios-simulator-device.cjs');

/**
* Requires a native tree from `expo prebuild` / `expo run:ios` (ios/ + Pods).
* @type {Detox.DetoxConfig}
*/
module.exports = {
testRunner: {
$0: 'jest',
args: {
config: 'e2e/jest.config.cjs',
_: ['e2e'],
},
jest: {
setupTimeout: 300000,
},
},
apps: {
'ios.debug': {
type: 'ios.app',
binaryPath:
'ios/build/Build/Products/Debug-iphonesimulator/ExpoApp54.app',
build:
'xcodebuild -workspace ios/ExpoApp54.xcworkspace -scheme ExpoApp54 -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build',
},
},
devices: {
'ios.sim': {
type: 'ios.simulator',
device: {
type: getIosSimulatorDeviceType(),
},
},
},
configurations: {
'ios.sim.debug': {
device: 'ios.sim',
app: 'ios.debug',
},
},
};
3 changes: 3 additions & 0 deletions apps/ExpoApp54/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ yarn-error.*

app-example

# Detox
artifacts/

# generated native folders
/ios
/android
58 changes: 36 additions & 22 deletions apps/ExpoApp54/RNApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { SafeAreaView } from 'react-native-safe-area-context';
import { Button, StyleSheet, Text, View } from 'react-native';
import BrownfieldNavigation from '@callstack/brownfield-navigation';

import PostMessageTab from './app/(tabs)/postMessage';
import Counter from './components/counter';

import { checkAndFetchUpdate } from './utils/expo-rn-updates';
Expand All @@ -13,29 +14,35 @@ type RNAppProps = {
export default function RNApp({ nativeOsVersionLabel }: RNAppProps) {
return (
<SafeAreaView style={styles.container}>
<Text style={styles.title}>Expo React Native Brownfield</Text>
<View style={styles.header}>
<Text style={styles.title}>Expo React Native Brownfield</Text>

{nativeOsVersionLabel ? (
<Text
style={styles.nativeOsVersionLabel}
accessibilityLabel="Native OS version"
>
{nativeOsVersionLabel}
</Text>
) : null}
{nativeOsVersionLabel ? (
<Text
style={styles.nativeOsVersionLabel}
accessibilityLabel="Native OS version"
>
{nativeOsVersionLabel}
</Text>
) : null}

<View style={styles.content}>
<Counter />
<View style={styles.content}>
<Counter />

<Button
title="Navigate to Settings"
onPress={() => BrownfieldNavigation.navigateToSettings()}
/>
<Button
title="Navigate to Referrals"
onPress={() => BrownfieldNavigation.navigateToReferrals('123')}
/>
<Button title="Fetch Update" onPress={checkAndFetchUpdate} />
<Button
title="Navigate to Settings"
onPress={() => BrownfieldNavigation.navigateToSettings()}
/>
<Button
title="Navigate to Referrals"
onPress={() => BrownfieldNavigation.navigateToReferrals('123')}
/>
<Button title="Fetch Update" onPress={checkAndFetchUpdate} />
</View>
</View>

<View style={styles.postMessageSection}>
<PostMessageTab />
</View>
</SafeAreaView>
);
Expand All @@ -45,7 +52,9 @@ const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#eeeeee',
paddingTop: 20,
},
header: {
flexShrink: 0,
},
title: {
fontSize: 20,
Expand All @@ -59,10 +68,15 @@ const styles = StyleSheet.create({
marginTop: 4,
},
content: {
flex: 1,
minHeight: 220,
justifyContent: 'center',
alignItems: 'center',
},
postMessageSection: {
flex: 1,
minHeight: 200,
marginTop: 8,
},
text: {
fontSize: 18,
textAlign: 'center',
Expand Down
2 changes: 2 additions & 0 deletions apps/ExpoApp54/app/(tabs)/postMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { StyleSheet, FlatList, TouchableOpacity } from 'react-native';

import { useCallback, useEffect, useRef, useState } from 'react';
import { brownfieldE2eTestIds } from '@callstack/brownfield-example-shared-tests/e2eTestIds';
import ReactNativeBrownfield from '@callstack/react-native-brownfield';
import type { MessageEvent } from '@callstack/react-native-brownfield';

Expand Down Expand Up @@ -51,6 +52,7 @@ export default function PostMessageTab() {
return (
<ThemedView style={styles.messageSection}>
<TouchableOpacity
testID={brownfieldE2eTestIds.sendMessageToNative}
style={styles.sendButton}
onPress={sendMessage}
activeOpacity={0.8}
Expand Down
10 changes: 9 additions & 1 deletion apps/ExpoApp54/components/postMessage/MessageBubble.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Animated, StyleSheet } from 'react-native';
import { Message } from './Message';
import { useEffect, useRef } from 'react';
import { brownfieldE2eTestIds } from '@callstack/brownfield-example-shared-tests/e2eTestIds';
import { ThemedText } from '@/components/themed-text';

export function MessageBubble({ item }: { item: Message }) {
Expand Down Expand Up @@ -40,7 +41,14 @@ export function MessageBubble({ item }: { item: Message }) {
<ThemedText style={styles.bubbleLabel}>
{isFromNative ? 'From Native' : 'From RN'}
</ThemedText>
<ThemedText style={styles.bubbleText}>{item.text}</ThemedText>
<ThemedText
style={styles.bubbleText}
testID={
isFromNative ? undefined : brownfieldE2eTestIds.rnPostMessageText
}
>
{item.text}
</ThemedText>
</Animated.View>
);
}
Expand Down
23 changes: 23 additions & 0 deletions apps/ExpoApp54/e2e/jest.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const path = require('node:path');

const appRoot = path.join(__dirname, '..');
const sharedTestsRoot = path.join(
__dirname,
'../../brownfield-example-shared-tests'
);

module.exports = {
maxWorkers: 1,
rootDir: appRoot,
roots: [appRoot, sharedTestsRoot],
modulePaths: [path.join(appRoot, 'node_modules')],
testTimeout: 120000,
verbose: true,
reporters: ['detox/runners/jest/reporter'],
globalSetup: 'detox/runners/jest/globalSetup',
globalTeardown: 'detox/runners/jest/globalTeardown',
testEnvironment: 'detox/runners/jest/testEnvironment',
testMatch: [
path.join(sharedTestsRoot, 'e2e/expoPostMessageBrownfield.e2e.js'),
],
};
20 changes: 13 additions & 7 deletions apps/ExpoApp54/entry.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { ExpoRoot } from 'expo-router';
import type { ComponentProps } from 'react';
import { AppRegistry } from 'react-native';
import { SafeAreaProvider } from 'react-native-safe-area-context';

import RNApp from './RNApp';

function App() {
const ctx = require.context('./app');
return <ExpoRoot context={ctx} />;
type RNAppRootProps = ComponentProps<typeof RNApp>;

function RNAppRoot(props: RNAppRootProps) {
return (
<SafeAreaProvider>
<RNApp {...props} />
</SafeAreaProvider>
);
}

AppRegistry.registerComponent('RNApp', () => RNApp);
// Keep compatibility with Expo's default app key.
AppRegistry.registerComponent('main', () => App);
AppRegistry.registerComponent('RNApp', () => RNAppRoot);
AppRegistry.registerComponent('main', () => RNAppRoot);
Comment thread
alpharius-ck marked this conversation as resolved.
Outdated
Loading