Skip to content

Commit a6593e6

Browse files
fix: change runs-on to ubuntu for E2E Android (#2793)
# Summary Change runs-on from `macos-12` which is deprecated to `ubuntu-latest` for Android E2E. ## Test Plan `E2E Android` action should complete
1 parent ff36ec4 commit a6593e6

12 files changed

Lines changed: 148 additions & 126 deletions

File tree

.github/workflows/e2e-android.yml

Lines changed: 67 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ on:
1414
workflow_dispatch:
1515
jobs:
1616
test:
17-
runs-on: macos-12
17+
runs-on: ubuntu-latest
1818
timeout-minutes: 60
1919
env:
2020
WORKING_DIRECTORY: paper-example
@@ -25,87 +25,89 @@ jobs:
2525
group: android-e2e-example-${{ github.ref }}
2626
cancel-in-progress: true
2727
steps:
28-
- name: checkout
29-
uses: actions/checkout@v3
30-
with:
31-
submodules: recursive
32-
- uses: actions/setup-node@v3
28+
- name: Checkout
29+
uses: actions/checkout@v4
30+
31+
- name: Free Disk Space (Ubuntu)
32+
uses: jlumbroso/free-disk-space@main
3333
with:
34-
node-version: 18
35-
cache: 'yarn'
34+
tool-cache: true
35+
android: false
36+
3637
- name: Set up JDK 17
37-
uses: actions/setup-java@v2
38+
uses: actions/setup-java@v4
3839
with:
3940
java-version: '17'
4041
distribution: 'zulu'
4142
cache: 'gradle'
42-
- name: Install NDK
43-
uses: nttld/setup-ndk@v1
44-
id: setup-ndk
45-
with:
46-
ndk-version: r26d
47-
local-cache: true
48-
- name: Set ANDROID_NDK
49-
run: echo "ANDROID_NDK=$ANDROID_HOME/ndk-bundle" >> $GITHUB_ENV
50-
- name: Cache SDK image
51-
id: cache-sdk-img
52-
uses: actions/cache@v3
53-
with:
54-
path: $ANDROID_HOME/system-images/
55-
key: ${{ runner.os }}-build-system-images-${{ env.SYSTEM_IMAGES }}
56-
- name: SKDs - download required images
57-
if: ${{ steps.cache-sdd-img.outputs.cache-hit != 'true' }}
58-
run: $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "system-images;android-34;google_apis;x86_64"
59-
- name: Cache AVD
60-
id: cache-avd
61-
uses: actions/cache@v3
43+
44+
- name: Setup Node.js
45+
uses: actions/setup-node@v6
6246
with:
63-
path: ~/.android/avd/${{ env.AVD_NAME }}.avd
64-
key: ${{ runner.os }}-avd-images-${{ env.SYSTEM_IMAGES }}-${{ env.AVD_NAME }}
65-
- name: Emulator - Create
66-
if: ${{ steps.cache-avd.outputs.cache-hit != 'true' }}
67-
run: $ANDROID_HOME/cmdline-tools/latest/bin/avdmanager create avd -n ${{ env.AVD_NAME }} --device 28 --package "${{ env.SYSTEM_IMAGES }}" --sdcard 512M
68-
- name: Emulator - Set screen settings
69-
if: ${{ steps.cache-avd.outputs.cache-hit != 'true' }}
47+
node-version: 22
48+
cache: 'yarn'
49+
50+
- name: Install AVD dependencies
51+
# libxkbfile1 is removed by "Free Disk Space (Ubuntu)" step first. Here we install it again
52+
# as it seems to be needed by the emulator.
7053
run: |
71-
echo "AVD config path: $HOME/.android/avd/${{ env.AVD_NAME }}.avd/config.ini"
72-
sed -i '' 's/.*hw\.lcd\.density.*/hw\.lcd\.density = 480/g' $HOME/.android/avd/${{ env.AVD_NAME }}.avd/config.ini
73-
sed -i '' 's/.*hw\.lcd\.width.*/hw\.lcd\.width = 1344/g' $HOME/.android/avd/${{ env.AVD_NAME }}.avd/config.ini
74-
sed -i '' 's/.*hw\.lcd\.height.*/hw\.lcd\.height = 2992/g' $HOME/.android/avd/${{ env.AVD_NAME }}.avd/config.ini
75-
- name: Emulator - Boot
76-
run: $ANDROID_HOME/emulator/emulator -memory 4096 -avd ${{ env.AVD_NAME }} -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim &
54+
sudo apt update
55+
sudo apt-get install -y libpulse0 libgl1 libxkbfile1
7756
78-
- name: ADB Wait For Device
79-
run: adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;'
80-
timeout-minutes: 10
57+
- name: Enable KVM
58+
run: |
59+
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
60+
sudo udevadm control --reload-rules
61+
sudo udevadm trigger --name-match=kvm
8162
82-
- name: Reverse TCP
83-
working-directory: apps/${{ env.WORKING_DIRECTORY }}
84-
run: adb devices | grep '\t' | awk '{print $1}' | sed 's/\\s//g' | xargs -I {} adb -s {} reverse tcp:8081 tcp:8081
63+
- name: AVD cache
64+
uses: actions/cache@v4
65+
id: avd-cache
66+
with:
67+
path: |
68+
~/.android/avd/*
69+
~/.android/adb*
70+
key: avd-${{ env.API_LEVEL }}
8571

86-
- name: Install root node dependencies
87-
run: yarn
72+
- name: Run emulator, Metro, and E2E
73+
uses: reactivecircus/android-emulator-runner@v2
74+
with:
75+
api-level: ${{ env.API_LEVEL }}
76+
target: default
77+
profile: pixel_7
78+
ram-size: '4096M'
79+
disk-size: '5G'
80+
disable-animations: false
81+
force-avd-creation: false
82+
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
83+
avd-name: e2e_emulator
84+
arch: x86_64
85+
script: |
86+
# Install root node dependencies
87+
yarn install
88+
# Install example app node dependencies
89+
yarn --cwd apps/${{ env.WORKING_DIRECTORY }} install
90+
91+
# Set up ADB reverse for Metro
92+
$ANDROID_HOME/platform-tools/adb reverse tcp:8081 tcp:8081
8893
89-
- name: Install example app node dependencies
90-
run: yarn
91-
working-directory: apps/${{ env.WORKING_DIRECTORY }}
94+
# Start Metro in the background
95+
E2E=true yarn --cwd apps/${{ env.WORKING_DIRECTORY }} start &> output.log &
9296
93-
- name: Build Android app
94-
working-directory: apps/${{ env.WORKING_DIRECTORY }}/android
95-
run: ./gradlew assembleDebug
97+
# Build the Android app
98+
cd apps/${{ env.WORKING_DIRECTORY }}/android && ./gradlew assembleDebug
9699
97-
- name: Start Metro server
98-
working-directory: apps/${{ env.WORKING_DIRECTORY }}
99-
run: E2E=true yarn start &> output.log &
100+
# Install the app APK
101+
$ANDROID_HOME/platform-tools/adb install -r apps/${{ env.WORKING_DIRECTORY }}/android/app/build/outputs/apk/debug/app-debug.apk
100102
101-
- name: Install APK
102-
run: adb install -r apps/${{ env.WORKING_DIRECTORY }}/android/app/build/outputs/apk/debug/app-debug.apk
103+
# Launch the app using bash
104+
bash -c 'until $ANDROID_HOME/platform-tools/adb shell monkey -p com.paperexample 1 | grep -q "Events injected: 1"; do sleep 1; echo "Retrying app launch..."; done'
103105
104-
- name: Launch APK
105-
run: 'while ! (adb shell monkey -p com.example 1 | grep -q "Events injected: 1"); do sleep 1; echo "Retrying due to errors in previous run..."; done'
106+
# Run E2E tests
107+
yarn e2e
106108
107-
- name: Run e2e Tests
108-
run: E2E=true yarn e2e
109+
# Kill Metro
110+
lsof -ti:8081 | xargs -r kill
109111
110112
- name: Upload test report
111113
uses: actions/upload-artifact@v4
@@ -114,6 +116,3 @@ jobs:
114116
path: |
115117
report.html
116118
jest-html-reporters-attach/
117-
118-
- name: Kill emulator (so it can be cached safely)
119-
run: adb devices | grep emulator | cut -f1 | while read line; do adb -s $line emu kill; done

__tests__/e2e/GeneralSvgRenderingTest.spec.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ testCases.forEach((testCase) => {
3434

3535
const referenceFilePath = path.resolve(
3636
'e2e',
37-
'references',
37+
global.os === 'android' ? 'references/android' : 'references/ios',
3838
testCase.replace('.svg', '.png')
3939
);
4040
const renderedFilePath = path.resolve(

apps/common/example/e2e/TestingView.tsx

Lines changed: 60 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -16,59 +16,70 @@ export const TestingView = () => {
1616
const [message, setMessage] = useState('⏳ Connecting to Jest server...');
1717

1818
const connect = useCallback(() => {
19-
const client = new WebSocket(wsUri);
20-
setWsClient(client);
2119
setMessage('⏳ Connecting to Jest server...');
22-
client.onopen = () => {
23-
client.send(
24-
JSON.stringify({
25-
os: Platform.OS,
26-
version: Platform.Version,
27-
arch: isFabric() ? 'fabric' : 'paper',
28-
connectionTime: new Date(),
29-
}),
30-
);
31-
setMessage('✅ Connected to Jest server. Waiting for render requests.');
32-
};
33-
client.onerror = (err: any) => {
34-
if (!err.message) {
35-
return;
36-
}
37-
console.error(
38-
`Error while connecting to E2E WebSocket server at ${wsUri}: ${err.message}. Will retry in 3 seconds.`,
39-
);
40-
setMessage(
41-
`🚨 Failed to connect to Jest server at ${wsUri}: ${err.message}! Will retry in 3 seconds.`,
42-
);
43-
setTimeout(() => {
44-
connect();
45-
}, 3000);
46-
};
47-
client.onmessage = ({data: rawMessage}) => {
48-
const message = JSON.parse(rawMessage);
49-
if (message.type == 'renderRequest') {
50-
setMessage(`✅ Rendering tests, please don't close this tab.`);
51-
const {width, height} = message;
52-
setResolution({width, height});
53-
setRenderedContent(
54-
createElementFromObject(
55-
message.data.type || 'SvgFromXml',
56-
message.data.props,
57-
),
20+
const startTime = Date.now();
21+
const MAX_TIMEOUT = 10000;
22+
let client = null;
23+
24+
const attemptConnect = () => {
25+
client = new WebSocket(wsUri);
26+
setWsClient(client);
27+
28+
client.onopen = () => {
29+
client.send(
30+
JSON.stringify({
31+
os: Platform.OS,
32+
version: Platform.Version,
33+
arch: isFabric() ? 'fabric' : 'paper',
34+
connectionTime: new Date(),
35+
}),
5836
);
59-
setReadyToSnapshot(true);
60-
}
61-
};
62-
client.onclose = event => {
63-
if (event.code == 1006 && event.reason) {
64-
// this is an error, let error handler take care of it
65-
return;
66-
}
67-
setMessage(
68-
`✅ Connection to Jest server has been closed. You can close this tab safely. (${event.code})`,
69-
);
37+
38+
setMessage('✅ Connected to Jest server. Waiting for render requests.');
39+
};
40+
41+
client.onerror = (err: any) => {
42+
const elapsed = Date.now() - startTime;
43+
if (elapsed >= MAX_TIMEOUT) {
44+
setMessage(`❌ Failed to connect within ${MAX_TIMEOUT} milliseconds`);
45+
return;
46+
}
47+
48+
console.error(
49+
`Error connecting to E2E WebSocket at ${wsUri}: ${
50+
err.message ?? ''
51+
}. Retrying...`,
52+
);
53+
setMessage(`🚨 Failed to connect: ${err.message ?? ''}. Retrying...`);
54+
setTimeout(attemptConnect, 500);
55+
};
56+
57+
client.onmessage = ({data: rawMessage}) => {
58+
const message = JSON.parse(rawMessage);
59+
if (message.type === 'renderRequest') {
60+
setMessage(`✅ Rendering tests, please don't close this tab.`);
61+
const {width, height} = message;
62+
setResolution({width, height});
63+
setRenderedContent(
64+
createElementFromObject(
65+
message.data.type || 'SvgFromXml',
66+
message.data.props,
67+
),
68+
);
69+
setReadyToSnapshot(true);
70+
}
71+
};
72+
73+
client.onclose = event => {
74+
if (event.code === 1006 && event.reason) return;
75+
setMessage(
76+
`✅ Connection closed. You can close this tab safely. (${event.code})`,
77+
);
78+
};
7079
};
7180

81+
attemptConnect();
82+
7283
return () => {
7384
setWsClient(null);
7485
client.close();

apps/paper-example/ios/Podfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2336,7 +2336,7 @@ PODS:
23362336
- ReactCommon/turbomodule/core
23372337
- SocketRocket
23382338
- Yoga
2339-
- RNSVG (15.12.1):
2339+
- RNSVG (15.13.0):
23402340
- React-Core
23412341
- SocketRocket (0.7.1)
23422342
- Yoga (0.0.0)
@@ -2667,7 +2667,7 @@ SPEC CHECKSUMS:
26672667
RNGestureHandler: 971c0e79a6a10390f90cd21901f8f890312ab28c
26682668
RNReanimated: 0fa596406af0734a0a55059ce8f3c52b51021a4f
26692669
RNScreens: 945c9fc4872ccc9a235e55cd643957b688ecf657
2670-
RNSVG: e47c4f9226c48027a1140407e839b00cccb06bfe
2670+
RNSVG: 8fd187b73b631c89a2d775c0702ec91c5e8f5e52
26712671
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
26722672
Yoga: 0c4b7d2aacc910a1f702694fa86be830386f4ceb
26732673

e2e/env.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
export const width = 200;
22
export const height = 200;
33
export const maxPixelDiff = width * height * 0.005;
4-
export const targetPixelRatio = 3.0;
4+
export const targetPixelRatio = global.os === 'android' ? 2.625 : 3.0;

e2e/generateReferences.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,26 @@ const path = require('path');
22
const puppeteer = require('puppeteer');
33
const fs = require('fs');
44

5-
const main = async () => {
5+
const generateReferences = async (
6+
platform: 'android' | 'ios',
7+
scaleFactor: number
8+
) => {
69
const browser = await puppeteer.launch();
710
const page = await browser.newPage();
11+
812
await page.setViewport({
9-
height: 200,
1013
width: 200,
11-
// This is hardcoded value which makes it possible to use only devices with pixel ratio = 3. You can change it
14+
height: 200,
15+
// This is value which makes it possible to use only devices with particular pixel ratio. You can change it
1216
// and regenerate reference images if you want to use device with different pixel ratio
1317
// see: https://reactnative.dev/docs/pixelratio
14-
deviceScaleFactor: 3,
18+
deviceScaleFactor: scaleFactor,
1519
});
20+
1621
const casesPath = path.resolve('e2e', 'cases');
17-
const referencesPath = path.resolve('e2e', 'references');
22+
const referencesPath = path.resolve('e2e', 'references', platform);
1823
const cases = fs.readdirSync(casesPath);
24+
1925
for (const testCase of cases) {
2026
const svgPath = path.resolve(casesPath, testCase);
2127
await page.goto(`file://${svgPath}`);
@@ -32,4 +38,10 @@ const main = async () => {
3238
await browser.close();
3339
};
3440

41+
const main = async () => {
42+
await generateReferences('ios', 3.0);
43+
// We use 2.625 for Android which corresponds to Pixel 7 device becuase the emulator with 3.0 is not available currently on CI
44+
await generateReferences('android', 2.625);
45+
};
46+
3547
main();

e2e/references/android/1.png

18.1 KB
Loading

e2e/references/android/2.png

15.8 KB
Loading

e2e/references/android/3.png

7.23 KB
Loading

0 commit comments

Comments
 (0)