Skip to content

Commit 872ed4a

Browse files
committed
feat: add CI/CD maestro tests
[test] upload screenshots as artifacts
1 parent 45739aa commit 872ed4a

File tree

8 files changed

+365
-87
lines changed

8 files changed

+365
-87
lines changed

.github/workflows/ci.yml

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
name: React Native CI
2+
3+
on:
4+
pull_request:
5+
branches: main
6+
push:
7+
branches: main
8+
schedule:
9+
- cron: '0 0 * * *' # Runs at 00:00 UTC every day
10+
11+
jobs:
12+
ios-build:
13+
name: iOS Build
14+
runs-on: macos-latest
15+
defaults:
16+
run:
17+
working-directory: example
18+
19+
steps:
20+
- name: Checkout repository
21+
uses: actions/checkout@v3
22+
23+
- name: Setup Node.js
24+
uses: actions/setup-node@v3
25+
with:
26+
node-version: '22'
27+
cache: 'npm'
28+
cache-dependency-path: example/package-lock.json
29+
30+
- name: Cache CocoaPods
31+
uses: actions/cache@v3
32+
with:
33+
path: |
34+
example/ios/Pods
35+
key: ${{ runner.os }}-pods-${{ hashFiles('example/ios/Podfile.lock') }}
36+
restore-keys: |
37+
${{ runner.os }}-pods-
38+
39+
- name: Install dependencies
40+
run: |
41+
npm install --frozen-lockfile
42+
cd ios && pod install
43+
44+
- name: Install Maestro CLI
45+
run: |
46+
curl -Ls "https://get.maestro.mobile.dev" | bash
47+
brew tap facebook/fb
48+
brew install facebook/fb/idb-companion
49+
50+
- name: Add Maestro to path
51+
run: echo "${HOME}/.maestro/bin" >> $GITHUB_PATH
52+
53+
- name: Start packager
54+
run: npm start &
55+
56+
- name: Build iOS
57+
run: |
58+
npm run ios
59+
60+
- name: Setup iOS simulator
61+
run: |
62+
UDID=$(xcrun simctl list devices | grep "iPhone" | grep "Booted" | head -1 | grep -E -o -i "([0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})")
63+
if [ -z "$UDID" ]; then
64+
UDID=$(xcrun simctl list devices available | grep "iPhone" | head -1 | grep -E -o -i "([0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})")
65+
xcrun simctl boot "${UDID}"
66+
fi
67+
open -a Simulator
68+
xcrun simctl launch "${UDID}" com.jscexample
69+
70+
- name: Run iOS tests
71+
run: |
72+
export MAESTRO_DRIVER_STARTUP_TIMEOUT=1500000
73+
export MAESTRO_WAIT_TIMEOUT=10000
74+
npm run test:e2e
75+
76+
77+
android-build:
78+
name: Android Build
79+
runs-on: ubuntu-latest
80+
defaults:
81+
run:
82+
working-directory: example
83+
84+
steps:
85+
- name: Checkout repository
86+
uses: actions/checkout@v3
87+
88+
- name: Setup Node.js
89+
uses: actions/setup-node@v3
90+
with:
91+
node-version: '18'
92+
cache: 'npm'
93+
cache-dependency-path: example/package-lock.json
94+
95+
- name: Setup Java
96+
uses: actions/setup-java@v3
97+
with:
98+
distribution: 'zulu'
99+
java-version: '17'
100+
101+
- name: Install dependencies
102+
run: npm install --frozen-lockfile
103+
104+
- name: Start packager
105+
run: npm start &
106+
107+
- name: Install Maestro CLI
108+
run: |
109+
curl -Ls "https://get.maestro.mobile.dev" | bash
110+
111+
- name: Add Maestro to path
112+
run: echo "${HOME}/.maestro/bin" >> $GITHUB_PATH
113+
114+
- name: Create AVD and generate snapshot for caching
115+
uses: reactivecircus/android-emulator-runner@v2
116+
with:
117+
target: aosp_atd
118+
api-level: 30
119+
arch: x86
120+
ram-size: 4096M
121+
channel: canary
122+
profile: pixel
123+
avd-name: Pixel_3a_API_30_AOSP
124+
force-avd-creation: false
125+
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
126+
emulator-boot-timeout: 12000
127+
disable-animations: false
128+
working-directory: example
129+
script: |
130+
npm run android
131+
npm run test:e2e

example/App.tsx

Lines changed: 142 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
1-
/**
2-
* Sample React Native App
3-
* https://github.com/facebook/react-native
4-
*
5-
* @format
6-
*/
7-
8-
import React from 'react';
9-
import type {PropsWithChildren} from 'react';
1+
import React, { useState } from 'react';
102
import {
3+
Button,
114
SafeAreaView,
125
ScrollView,
136
StatusBar,
@@ -16,52 +9,127 @@ import {
169
useColorScheme,
1710
View,
1811
} from 'react-native';
19-
2012
import {
2113
Colors,
22-
DebugInstructions,
2314
Header,
24-
LearnMoreLinks,
25-
ReloadInstructions,
2615
} from 'react-native/Libraries/NewAppScreen';
2716

28-
type SectionProps = PropsWithChildren<{
29-
title: string;
30-
}>;
31-
32-
function Section({children, title}: SectionProps): React.JSX.Element {
33-
const isDarkMode = useColorScheme() === 'dark';
34-
return (
35-
<View style={styles.sectionContainer}>
36-
<Text
37-
style={[
38-
styles.sectionTitle,
39-
{
40-
color: isDarkMode ? Colors.white : Colors.black,
41-
},
42-
]}>
43-
{title}
44-
</Text>
45-
<Text
46-
style={[
47-
styles.sectionDescription,
48-
{
49-
color: isDarkMode ? Colors.light : Colors.dark,
50-
},
51-
]}>
52-
{children}
53-
</Text>
54-
</View>
55-
);
56-
}
57-
5817
function App(): React.JSX.Element {
5918
const isDarkMode = useColorScheme() === 'dark';
19+
const [testResult, setTestResult] = useState('No test run yet');
20+
const [testName, setTestName] = useState('');
6021

6122
const backgroundStyle = {
6223
backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
6324
};
6425

26+
const testConsoleLog = () => {
27+
console.log('Hello from JSC');
28+
setTestName('Console Test Result');
29+
setTestResult('Hello from JSC');
30+
};
31+
32+
const testBasicOperations = () => {
33+
const mathResult = 2 + 2;
34+
const stringResult = 'Hello ' + 'World';
35+
const arrayResult = [1, 2, 3].map(x => x * 2);
36+
37+
const result = `Math: ${mathResult}\nString: ${stringResult}\nArray: ${arrayResult}`;
38+
console.log(result);
39+
setTestName('Basic Operations Result');
40+
setTestResult(result);
41+
};
42+
43+
const testComplexOperations = () => {
44+
const obj = { a: 1, b: 2 };
45+
const square = (x: number) => x * x;
46+
const squareResult = square(4);
47+
48+
let result = `Object: ${JSON.stringify(obj)}\nSquare(4): ${squareResult}`;
49+
50+
try {
51+
// eslint-disable-next-line no-eval
52+
const dynamicFn = eval('(x) => x * 3');
53+
const dynamicResult = dynamicFn(4);
54+
result += `\nDynamic function(4): ${dynamicResult}`;
55+
} catch (error) {
56+
result += `\nDynamic function error: ${error}`;
57+
}
58+
59+
console.log(result);
60+
setTestName('Complex Operations Result');
61+
setTestResult(result);
62+
};
63+
64+
const testGlobalAccess = () => {
65+
const result = `SetTimeout exists: ${typeof global.setTimeout === 'function'}`;
66+
console.log(result);
67+
setTestName('Global Access Result');
68+
setTestResult(result);
69+
};
70+
71+
const testErrorHandling = () => {
72+
let results: string[] = [];
73+
74+
try {
75+
throw new Error('Custom error');
76+
} catch (error) {
77+
if (error instanceof Error) {
78+
results.push(`Regular error: ${error.message}`);
79+
}
80+
}
81+
82+
try {
83+
const undefined1 = undefined;
84+
// @ts-ignore
85+
undefined1.someMethod();
86+
} catch (error) {
87+
if (error instanceof Error) {
88+
results.push(`Type error: ${error.message}`);
89+
}
90+
}
91+
92+
try {
93+
// eslint-disable-next-line no-eval
94+
eval('syntax error{');
95+
} catch (error) {
96+
if (error instanceof Error) {
97+
results.push(`Eval error: ${error.message}`);
98+
}
99+
}
100+
101+
const result = results.join('\n');
102+
console.log(result);
103+
setTestName('Error Handling Result');
104+
setTestResult(result);
105+
};
106+
107+
const testAsync = async () => {
108+
try {
109+
const result = await new Promise((resolve) => {
110+
setTimeout(() => resolve('Regular async completed'), 1000);
111+
});
112+
console.log('Regular async result:', result);
113+
setTestName('Async Test Result');
114+
setTestResult(String(result));
115+
} catch (error) {
116+
setTestName('Async Error');
117+
setTestResult(String(error));
118+
}
119+
};
120+
121+
const testMemoryAndPerformance = () => {
122+
const arr = new Array(1000000);
123+
for (let i = 0; i < arr.length; i++) {
124+
arr[i] = i;
125+
}
126+
const result = `Array length: ${arr.length}`;
127+
128+
console.log(result);
129+
setTestName('Memory & Performance Result');
130+
setTestResult(result);
131+
};
132+
65133
return (
66134
<SafeAreaView style={backgroundStyle}>
67135
<StatusBar
@@ -73,45 +141,47 @@ function App(): React.JSX.Element {
73141
style={backgroundStyle}>
74142
<Header />
75143
<View
76-
style={{
77-
backgroundColor: isDarkMode ? Colors.black : Colors.white,
78-
}}>
79-
<Section title="Step One">
80-
Edit <Text style={styles.highlight}>App.tsx</Text> to change this
81-
screen and then come back to see your edits.
82-
</Section>
83-
<Section title="See Your Changes">
84-
<ReloadInstructions />
85-
</Section>
86-
<Section title="Debug">
87-
<DebugInstructions />
88-
</Section>
89-
<Section title="Learn More">
90-
Read the docs to discover what to do next:
91-
</Section>
92-
<LearnMoreLinks />
144+
style={[
145+
styles.container,
146+
{backgroundColor: isDarkMode ? Colors.black : Colors.white},
147+
]}>
148+
<Button title="Console Log Test" onPress={testConsoleLog} />
149+
<Button title="Basic Operations" onPress={testBasicOperations} />
150+
<Button title="Complex Operations" onPress={testComplexOperations} />
151+
<Button title="Global Access Test" onPress={testGlobalAccess} />
152+
<Button title="Error Handling Test" onPress={testErrorHandling} />
153+
<Button title="Async Test" onPress={testAsync} />
154+
<Button title="Memory & Performance" onPress={testMemoryAndPerformance} />
155+
<View style={styles.resultContainer}>
156+
<Text style={styles.resultTitle} testID="resultTitle">
157+
{testName || 'Test Results'}
158+
</Text>
159+
<Text style={styles.resultContent} testID="resultContent">
160+
{testResult}
161+
</Text>
162+
</View>
93163
</View>
94164
</ScrollView>
95165
</SafeAreaView>
96166
);
97167
}
98168

99169
const styles = StyleSheet.create({
100-
sectionContainer: {
101-
marginTop: 32,
102-
paddingHorizontal: 24,
170+
container: {
171+
padding: 12,
103172
},
104-
sectionTitle: {
105-
fontSize: 24,
106-
fontWeight: '600',
173+
resultContainer: {
174+
marginTop: 20,
175+
padding: 10,
176+
backgroundColor: '#f0f0f0',
107177
},
108-
sectionDescription: {
109-
marginTop: 8,
110-
fontSize: 18,
111-
fontWeight: '400',
178+
resultTitle: {
179+
fontSize: 16,
180+
fontWeight: 'bold',
181+
marginBottom: 8,
112182
},
113-
highlight: {
114-
fontWeight: '700',
183+
resultContent: {
184+
fontSize: 14,
115185
},
116186
});
117187

0 commit comments

Comments
 (0)