Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,5 @@ android/.settings
# Local environment (direnv)
.envrc

# e2e
*Example/artifacts
2 changes: 1 addition & 1 deletion Example/.detoxrc.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
const utils = require('../scripts/detox-utils.cjs');
const utils = require('../scripts/e2e/detox-utils.cjs');
module.exports = utils.commonDetoxConfigFactory('ScreensExample');
8 changes: 4 additions & 4 deletions Example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
"format": "prettier --write --list-different './src/**/*.{js,ts,tsx}'",
"lint": "eslint --ext '.js,.ts,.tsx' --fix src && yarn check-types && yarn format",
"check-types": "tsc --noEmit",
"build-e2e-ios": "detox build --configuration ios.release",
"build-e2e-android": "detox build --configuration android.release",
"test-e2e-ios": "detox test --configuration ios.release --take-screenshots failing",
"test-e2e-android": "detox test --configuration android.release --take-screenshots failing",
"build-e2e-ios": "detox build --configuration ios.sim.release",
"build-e2e-android": "detox build --configuration android.emu.release",
"test-e2e-ios": "detox test --configuration ios.sim.release --take-screenshots failing",
"test-e2e-android": "detox test --configuration android.emu.release --take-screenshots failing",
"postinstall": "patch-package"
},
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion FabricExample/.detoxrc.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
const utils = require('../scripts/detox-utils.cjs');
const utils = require('../scripts/e2e/detox-utils.cjs');
module.exports = utils.commonDetoxConfigFactory('FabricExample');

8 changes: 4 additions & 4 deletions FabricExample/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
"lint": "eslint .",
"start": "npx react-native start",
"test": "jest",
"build-e2e-ios": "detox build --configuration ios.release",
"build-e2e-android": "detox build --configuration android.release",
"test-e2e-ios": "detox test --configuration ios.release --take-screenshots failing",
"test-e2e-android": "detox test --configuration android.release --take-screenshots failing",
"build-e2e-ios": "detox build --configuration ios.sim.release",
"build-e2e-android": "detox build --configuration android.emu.release",
"test-e2e-ios": "detox test --configuration ios.sim.release --take-screenshots failing",
"test-e2e-android": "detox test --configuration android.emu.release --take-screenshots failing",
"postinstall": "patch-package"
},
"dependencies": {
Expand Down
100 changes: 100 additions & 0 deletions scripts/e2e/android-devices.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
const ChildProcess = require('node:child_process');

// Should be kept in sync with the constant defined in e2e workflow file
const DEFAULT_CI_AVD_NAME = 'e2e_emulator';

function detectLocalAndroidEmulator() {
// "RNS_E2E_AVD_NAME" can be set for local developement
const avdName = process.env.RNS_AVD_NAME ?? null;
if (avdName !== null) {
return avdName
}

// Fallback: try to use Android SDK
try {
let stdout = ChildProcess.execSync("emulator -list-avds");

// Possibly convert Buffer to string
if (typeof stdout !== 'string') {
stdout = stdout.toString();
}

const avdList = stdout.trim().split('\n').map(name => name.trim());

if (avdList.length === 0) {
throw new Error('No installed AVDs detected on the device');
}

// Just select first one in the list.
// TODO: consider giving user a choice here.
return avdList[0];
} catch (error) {
const errorMessage = `Failed to find Android emulator. Set "RNS_E2E_AVD_NAME" env variable pointing to one. Cause: ${error}`;
console.error(errorMessage);
throw new Error(errorMessage);
}
}

/**
* @param {boolean} isRunningCI whether this script is run in CI environment
*/
function detectAndroidEmulatorName(isRunningCI) {
return isRunningCI ? DEFAULT_CI_AVD_NAME : detectLocalAndroidEmulator();
}

/**
* @returns {string | null} Device serial as requested by user, first serial from adb list or null
*/
function resolveAndroidDeviceSerial() {
const deviceSerial = process.env.RNS_DEVICE_SERIAL ?? null;

if (deviceSerial !== null) {
return deviceSerial;
}

// Fallback: try to use adb
try {
let stdout = ChildProcess.execSync("adb devices");

// Possibly convert Buffer to string
if (typeof stdout !== 'string') {
stdout = stdout.toString();
}

/** @type {string} */
const stringStdout = stdout;

// Example `adb devices` output:
//
// List of devices attached
// 6lh6huzhr48lu8t8 device
// emulator-5554 device

const deviceList = stringStdout
.trim()
.split('\n')
.map(line => line.trim())
.filter((line, index) => line !== '' && index !== 0) // empty lines & header
.map(line => line.split(' ', 1)[0]);


if (deviceList.length === 0) {
throw new Error("Seems that the attached device list is empty");
}

// Just select first one in the list.
// TODO: consider giving user a choice here.
return deviceList[0];
} catch (error) {
console.error(`Failed to find attached device. Try setting "RNS_DEVICE_SERIAL" env variable pointing to one. Cause: ${error}`);
}

return null;
}

module.exports = {
DEFAULT_CI_AVD_NAME,
detectLocalAndroidEmulator,
detectAndroidEmulatorName,
resolveAndroidDeviceSerial,
}
70 changes: 25 additions & 45 deletions scripts/detox-utils.cjs → scripts/e2e/detox-utils.cjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const ChildProcess = require('node:child_process');

const CI_AVD_NAME = 'e2e_emulator';
const AppleDeviceUtil = require('./ios-devices');
const AndroidDeviceUtil = require('./android-devices');

const isRunningCI = process.env.CI != null;

Expand All @@ -10,44 +9,24 @@ const apkBulidArchitecture = isRunningCI ? 'x86_64' : 'arm64-v8a';
// it is assumed here that arm64-v8a AOSP emulator is not available in local setup.
const testButlerApkPath = isRunningCI ? ['../Example/e2e/apps/test-butler-app-2.2.1.apk'] : undefined;

function detectLocalAndroidEmulator() {
// "DETOX_AVD_NAME" can be set for local developement
const detoxAvdName = process.env.DETOX_AVD_NAME ?? null;
if (detoxAvdName !== null) {
return detoxAvdName
}

// Fallback: try to use Android SDK
try {
let stdout = ChildProcess.execSync("emulator -list-avds")

// Possibly convert Buffer to string
if (typeof stdout !== 'string') {
stdout = stdout.toString();
}

const avdList = stdout.trim().split('\n').map(name => name.trim());

if (avdList.length === 0) {
throw new Error('No installed AVDs detected on the device');
}

// Just select first one in the list.
// TODO: consider giving user a choice here.
return avdList[0];
} catch (error) {
const errorMessage = `Failed to find Android emulator. Set "DETOX_AVD_NAME" env variable pointing to one. Cause: ${error}`
console.error(errorMessage);
throw new Error(errorMessage);
}
}

function detectAndroidEmulatorName() {
return isRunningCI ? CI_AVD_NAME : detectLocalAndroidEmulator();
}

/**
* @type {Detox.DetoxConfig}
* The output of this function can be controlled through couple of env vars.
*
* * `RNS_DEVICE_SERIAL` env var can be specified in case of running
* tests with an attached Android device. It can also be an emulator.
* The expected value here is the same as you would pass to `adb -s`.
* You can find device serial by running `adb devices` command.
*
* * `RNS_AVD_NAME` env var can be specified in case of running tests on Android emulator.
* The exepected value here is the same as displayed in Android Studio or listed by
* `emulator -list-avds`.
*
* * `RNS_APPLE_SIM_NAME` env var can be set in case of running tests on iOS simulator.
* The expected value here is exactly as one listed in XCode.
*
* * `RNS_IOS_VERSION` env var can be specified to request particular iOS version
* for the given simulator. Note that required SDK & simulators must be installed.
*
* @param {string} applicationName name (FabricExample / ScreensExample)
* @returns {Detox.DetoxConfig}
*/
Expand Down Expand Up @@ -95,20 +74,21 @@ function commonDetoxConfigFactory(applicationName) {
simulator: {
type: 'ios.simulator',
device: {
type: 'iPhone 16 Pro',
type: AppleDeviceUtil.resolveAppleSimulatorName(),
os: AppleDeviceUtil.getIOSVersion(),
},
},
attached: {
type: 'android.attached',
device: {
adbName: CI_AVD_NAME,
adbName: AndroidDeviceUtil.resolveAndroidDeviceSerial(),
},
utilBinaryPaths: testButlerApkPath,
},
emulator: {
type: 'android.emulator',
device: {
avdName: detectAndroidEmulatorName(),
avdName: AndroidDeviceUtil.detectAndroidEmulatorName(),
},
utilBinaryPaths: testButlerApkPath,
},
Expand Down Expand Up @@ -147,10 +127,10 @@ function commonDetoxConfigFactory(applicationName) {
app: 'android.release',
},
},
}
};
}

module.exports = {
commonDetoxConfigFactory,
isRunningCI,
}
};
28 changes: 28 additions & 0 deletions scripts/e2e/ios-devices.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const DEFAULT_APPLE_SIMULATOR_NAME = 'iPhone 17';
const DEFAULT_IOS_VERSION = 'iOS 26.2';

/**
* @return {string}
*/
function resolveAppleSimulatorName() {
return process.env.RNS_APPLE_SIM_NAME ?? DEFAULT_APPLE_SIMULATOR_NAME;
}
/**
* @return {`iOS ${string}`} requested version of ios, or default if not specified
*/
function getIOSVersion() {
const passedVersion = process.env.RNS_IOS_VERSION;
if (passedVersion) {
if (passedVersion.startsWith('iOS ')) {
return /** @type {`iOS ${string}`} */ (passedVersion);
}
return `iOS ${passedVersion}`;
}
return DEFAULT_IOS_VERSION;
}

module.exports = {
resolveAppleSimulatorName,
getIOSVersion,
};