Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
a30b6cb
fix: change runs-on to ubuntu for E2E Android
kacperzolkiewski Oct 8, 2025
7f01207
fix: change order of E2E Android workflow commands
kacperzolkiewski Oct 8, 2025
3571f43
fix: reconnecting e2e tests
kacperzolkiewski Oct 8, 2025
2d7e97e
fix: increase tests timeout
kacperzolkiewski Oct 8, 2025
d3d1ac6
fix: move build into scrip block
kacperzolkiewski Oct 9, 2025
f8b2917
fix: android build
kacperzolkiewski Oct 9, 2025
213ba34
fix: android build path fix
kacperzolkiewski Oct 9, 2025
00c15f9
fix: remove unnecessary commands
kacperzolkiewski Oct 9, 2025
0e57fd9
fix: use proper emulator
kacperzolkiewski Oct 9, 2025
ad34028
fix: api level
kacperzolkiewski Oct 9, 2025
888ffdf
fix: use pixel 6
kacperzolkiewski Oct 9, 2025
00a0a86
fix: use pixel 8 pro for e2e
kacperzolkiewski Oct 24, 2025
7c3eaa7
fix: use pixel 7 for e2e
kacperzolkiewski Oct 24, 2025
1d6826b
fix: change pixel density for e2e on android
kacperzolkiewski Oct 27, 2025
f5b1530
fix: use 22 node version
kacperzolkiewski Oct 27, 2025
23c4c54
fix: use env for project name
kacperzolkiewski Oct 27, 2025
f5f5d8c
fix: use paperexample name
kacperzolkiewski Oct 27, 2025
ea83ad7
fix: kill metro
kacperzolkiewski Oct 27, 2025
3281a6d
fix: remove killing metro
kacperzolkiewski Oct 27, 2025
41c73fc
fix: kill metro by PID
kacperzolkiewski Oct 27, 2025
a2b37e6
fix: starting metro
kacperzolkiewski Oct 27, 2025
966953e
fix: remove comits
kacperzolkiewski Oct 27, 2025
439b4a6
fix: kill background process
kacperzolkiewski Oct 27, 2025
7356dbc
fix: kill processes
kacperzolkiewski Oct 27, 2025
b640943
fix: remove comments
kacperzolkiewski Oct 27, 2025
9aeaabd
fix: add 20s sleep before killing metro
kacperzolkiewski Oct 27, 2025
6a6a40e
fix: bash kill process
kacperzolkiewski Oct 27, 2025
4477655
fix: save metro pid
kacperzolkiewski Oct 27, 2025
0ac645f
fix: remove disdown
kacperzolkiewski Oct 27, 2025
801363e
fix: add nohup
kacperzolkiewski Oct 27, 2025
9845835
fix: run full script in one bash
kacperzolkiewski Oct 27, 2025
ec25230
fix: save metro pid to file
kacperzolkiewski Oct 27, 2025
c82ed62
fix: run script inside bash
kacperzolkiewski Oct 27, 2025
9d3eddc
fix: use bash inside script
kacperzolkiewski Oct 27, 2025
e9e0be7
fix: use sh -c
kacperzolkiewski Oct 27, 2025
7ad0fc4
fix: use pkill
kacperzolkiewski Oct 27, 2025
a80a11b
fix: use ps with grep
kacperzolkiewski Oct 27, 2025
628ce30
fix: pront start process
kacperzolkiewski Oct 27, 2025
07b87a0
fix: kill metro with ps and grep
kacperzolkiewski Oct 27, 2025
3a72c6f
fix: terminating process
kacperzolkiewski Oct 27, 2025
00fe1b6
fix: add logs
kacperzolkiewski Oct 27, 2025
49832b5
fix: remove process by port
kacperzolkiewski Oct 27, 2025
4cc3655
fix: uncomment script
kacperzolkiewski Oct 27, 2025
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
135 changes: 67 additions & 68 deletions .github/workflows/e2e-android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ on:
workflow_dispatch:
jobs:
test:
runs-on: macos-12
runs-on: ubuntu-latest
timeout-minutes: 60
env:
WORKING_DIRECTORY: paper-example
Expand All @@ -25,87 +25,89 @@ jobs:
group: android-e2e-example-${{ github.ref }}
cancel-in-progress: true
steps:
- name: checkout
uses: actions/checkout@v3
with:
submodules: recursive
- uses: actions/setup-node@v3
- name: Checkout
uses: actions/checkout@v4

- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
node-version: 18
cache: 'yarn'
tool-cache: true
android: false

- name: Set up JDK 17
uses: actions/setup-java@v2
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'zulu'
cache: 'gradle'
- name: Install NDK
uses: nttld/setup-ndk@v1
id: setup-ndk
with:
ndk-version: r26d
local-cache: true
- name: Set ANDROID_NDK
run: echo "ANDROID_NDK=$ANDROID_HOME/ndk-bundle" >> $GITHUB_ENV
- name: Cache SDK image
id: cache-sdk-img
uses: actions/cache@v3
with:
path: $ANDROID_HOME/system-images/
key: ${{ runner.os }}-build-system-images-${{ env.SYSTEM_IMAGES }}
- name: SKDs - download required images
if: ${{ steps.cache-sdd-img.outputs.cache-hit != 'true' }}
run: $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "system-images;android-34;google_apis;x86_64"
- name: Cache AVD
id: cache-avd
uses: actions/cache@v3

- name: Setup Node.js
uses: actions/setup-node@v6
with:
path: ~/.android/avd/${{ env.AVD_NAME }}.avd
key: ${{ runner.os }}-avd-images-${{ env.SYSTEM_IMAGES }}-${{ env.AVD_NAME }}
- name: Emulator - Create
if: ${{ steps.cache-avd.outputs.cache-hit != 'true' }}
run: $ANDROID_HOME/cmdline-tools/latest/bin/avdmanager create avd -n ${{ env.AVD_NAME }} --device 28 --package "${{ env.SYSTEM_IMAGES }}" --sdcard 512M
- name: Emulator - Set screen settings
if: ${{ steps.cache-avd.outputs.cache-hit != 'true' }}
node-version: 22
cache: 'yarn'

- name: Install AVD dependencies
# libxkbfile1 is removed by "Free Disk Space (Ubuntu)" step first. Here we install it again
# as it seems to be needed by the emulator.
run: |
echo "AVD config path: $HOME/.android/avd/${{ env.AVD_NAME }}.avd/config.ini"
sed -i '' 's/.*hw\.lcd\.density.*/hw\.lcd\.density = 480/g' $HOME/.android/avd/${{ env.AVD_NAME }}.avd/config.ini
sed -i '' 's/.*hw\.lcd\.width.*/hw\.lcd\.width = 1344/g' $HOME/.android/avd/${{ env.AVD_NAME }}.avd/config.ini
sed -i '' 's/.*hw\.lcd\.height.*/hw\.lcd\.height = 2992/g' $HOME/.android/avd/${{ env.AVD_NAME }}.avd/config.ini
- name: Emulator - Boot
run: $ANDROID_HOME/emulator/emulator -memory 4096 -avd ${{ env.AVD_NAME }} -no-window -gpu swiftshader_indirect -no-snapshot -noaudio -no-boot-anim &
sudo apt update
sudo apt-get install -y libpulse0 libgl1 libxkbfile1

- name: ADB Wait For Device
run: adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;'
timeout-minutes: 10
- 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

- name: Reverse TCP
working-directory: apps/${{ env.WORKING_DIRECTORY }}
run: adb devices | grep '\t' | awk '{print $1}' | sed 's/\\s//g' | xargs -I {} adb -s {} reverse tcp:8081 tcp:8081
- name: AVD cache
uses: actions/cache@v4
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
key: avd-${{ env.API_LEVEL }}

- name: Install root node dependencies
run: yarn
- name: Run emulator, Metro, and E2E
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ env.API_LEVEL }}
target: default
profile: pixel_7
ram-size: '4096M'
disk-size: '5G'
disable-animations: false
force-avd-creation: false
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
avd-name: e2e_emulator
arch: x86_64
script: |
# Install root node dependencies
yarn install
# Install example app node dependencies
yarn --cwd apps/${{ env.WORKING_DIRECTORY }} install

# Set up ADB reverse for Metro
$ANDROID_HOME/platform-tools/adb reverse tcp:8081 tcp:8081

- name: Install example app node dependencies
run: yarn
working-directory: apps/${{ env.WORKING_DIRECTORY }}
# Start Metro in the background
E2E=true yarn --cwd apps/${{ env.WORKING_DIRECTORY }} start &> output.log &

- name: Build Android app
working-directory: apps/${{ env.WORKING_DIRECTORY }}/android
run: ./gradlew assembleDebug
# Build the Android app
cd apps/${{ env.WORKING_DIRECTORY }}/android && ./gradlew assembleDebug

- name: Start Metro server
working-directory: apps/${{ env.WORKING_DIRECTORY }}
run: E2E=true yarn start &> output.log &
# Install the app APK
$ANDROID_HOME/platform-tools/adb install -r apps/${{ env.WORKING_DIRECTORY }}/android/app/build/outputs/apk/debug/app-debug.apk

- name: Install APK
run: adb install -r apps/${{ env.WORKING_DIRECTORY }}/android/app/build/outputs/apk/debug/app-debug.apk
# Launch the app using bash
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'

- name: Launch APK
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'
# Run E2E tests
yarn e2e

- name: Run e2e Tests
run: E2E=true yarn e2e
# Kill Metro
lsof -ti:8081 | xargs -r kill

- name: Upload test report
uses: actions/upload-artifact@v4
Expand All @@ -114,6 +116,3 @@ jobs:
path: |
report.html
jest-html-reporters-attach/

- name: Kill emulator (so it can be cached safely)
run: adb devices | grep emulator | cut -f1 | while read line; do adb -s $line emu kill; done
2 changes: 1 addition & 1 deletion __tests__/e2e/GeneralSvgRenderingTest.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ testCases.forEach((testCase) => {

const referenceFilePath = path.resolve(
'e2e',
'references',
global.os === 'android' ? 'references/android' : 'references/ios',
testCase.replace('.svg', '.png')
);
const renderedFilePath = path.resolve(
Expand Down
109 changes: 60 additions & 49 deletions apps/common/example/e2e/TestingView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,59 +16,70 @@ export const TestingView = () => {
const [message, setMessage] = useState('⏳ Connecting to Jest server...');

const connect = useCallback(() => {
const client = new WebSocket(wsUri);
setWsClient(client);
setMessage('⏳ Connecting to Jest server...');
client.onopen = () => {
client.send(
JSON.stringify({
os: Platform.OS,
version: Platform.Version,
arch: isFabric() ? 'fabric' : 'paper',
connectionTime: new Date(),
}),
);
setMessage('✅ Connected to Jest server. Waiting for render requests.');
};
client.onerror = (err: any) => {
if (!err.message) {
return;
}
console.error(
`Error while connecting to E2E WebSocket server at ${wsUri}: ${err.message}. Will retry in 3 seconds.`,
);
setMessage(
`🚨 Failed to connect to Jest server at ${wsUri}: ${err.message}! Will retry in 3 seconds.`,
);
setTimeout(() => {
connect();
}, 3000);
};
client.onmessage = ({data: rawMessage}) => {
const message = JSON.parse(rawMessage);
if (message.type == 'renderRequest') {
setMessage(`✅ Rendering tests, please don't close this tab.`);
const {width, height} = message;
setResolution({width, height});
setRenderedContent(
createElementFromObject(
message.data.type || 'SvgFromXml',
message.data.props,
),
const startTime = Date.now();
const MAX_TIMEOUT = 10000;
let client = null;

const attemptConnect = () => {
client = new WebSocket(wsUri);
setWsClient(client);

client.onopen = () => {
client.send(
JSON.stringify({
os: Platform.OS,
version: Platform.Version,
arch: isFabric() ? 'fabric' : 'paper',
connectionTime: new Date(),
}),
);
setReadyToSnapshot(true);
}
};
client.onclose = event => {
if (event.code == 1006 && event.reason) {
// this is an error, let error handler take care of it
return;
}
setMessage(
`✅ Connection to Jest server has been closed. You can close this tab safely. (${event.code})`,
);

setMessage('✅ Connected to Jest server. Waiting for render requests.');
};

client.onerror = (err: any) => {
const elapsed = Date.now() - startTime;
if (elapsed >= MAX_TIMEOUT) {
setMessage(`❌ Failed to connect within ${MAX_TIMEOUT} milliseconds`);
return;
}

console.error(
`Error connecting to E2E WebSocket at ${wsUri}: ${
err.message ?? ''
}. Retrying...`,
);
setMessage(`🚨 Failed to connect: ${err.message ?? ''}. Retrying...`);
setTimeout(attemptConnect, 500);
};

client.onmessage = ({data: rawMessage}) => {
const message = JSON.parse(rawMessage);
if (message.type === 'renderRequest') {
setMessage(`✅ Rendering tests, please don't close this tab.`);
const {width, height} = message;
setResolution({width, height});
setRenderedContent(
createElementFromObject(
message.data.type || 'SvgFromXml',
message.data.props,
),
);
setReadyToSnapshot(true);
}
};

client.onclose = event => {
if (event.code === 1006 && event.reason) return;
setMessage(
`✅ Connection closed. You can close this tab safely. (${event.code})`,
);
};
};

attemptConnect();

return () => {
setWsClient(null);
client.close();
Expand Down
4 changes: 2 additions & 2 deletions apps/paper-example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2336,7 +2336,7 @@ PODS:
- ReactCommon/turbomodule/core
- SocketRocket
- Yoga
- RNSVG (15.12.1):
- RNSVG (15.13.0):
- React-Core
- SocketRocket (0.7.1)
- Yoga (0.0.0)
Expand Down Expand Up @@ -2667,7 +2667,7 @@ SPEC CHECKSUMS:
RNGestureHandler: 971c0e79a6a10390f90cd21901f8f890312ab28c
RNReanimated: 0fa596406af0734a0a55059ce8f3c52b51021a4f
RNScreens: 945c9fc4872ccc9a235e55cd643957b688ecf657
RNSVG: e47c4f9226c48027a1140407e839b00cccb06bfe
RNSVG: 8fd187b73b631c89a2d775c0702ec91c5e8f5e52
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
Yoga: 0c4b7d2aacc910a1f702694fa86be830386f4ceb

Expand Down
2 changes: 1 addition & 1 deletion e2e/env.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const width = 200;
export const height = 200;
export const maxPixelDiff = width * height * 0.005;
export const targetPixelRatio = 3.0;
export const targetPixelRatio = global.os === 'android' ? 2.625 : 3.0;
22 changes: 17 additions & 5 deletions e2e/generateReferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,26 @@ const path = require('path');
const puppeteer = require('puppeteer');
const fs = require('fs');

const main = async () => {
const generateReferences = async (
platform: 'android' | 'ios',
scaleFactor: number
) => {
const browser = await puppeteer.launch();
const page = await browser.newPage();

await page.setViewport({
height: 200,
width: 200,
// This is hardcoded value which makes it possible to use only devices with pixel ratio = 3. You can change it
height: 200,
// This is value which makes it possible to use only devices with particular pixel ratio. You can change it
// and regenerate reference images if you want to use device with different pixel ratio
// see: https://reactnative.dev/docs/pixelratio
deviceScaleFactor: 3,
deviceScaleFactor: scaleFactor,
});

const casesPath = path.resolve('e2e', 'cases');
const referencesPath = path.resolve('e2e', 'references');
const referencesPath = path.resolve('e2e', 'references', platform);
const cases = fs.readdirSync(casesPath);

for (const testCase of cases) {
const svgPath = path.resolve(casesPath, testCase);
await page.goto(`file://${svgPath}`);
Expand All @@ -32,4 +38,10 @@ const main = async () => {
await browser.close();
};

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

main();
Binary file added e2e/references/android/1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added e2e/references/android/2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added e2e/references/android/3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
File renamed without changes
File renamed without changes
Loading