diff --git a/.gitignore b/.gitignore index 6b2d8b4092..5d4e948584 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,5 @@ android/.settings # Local environment (direnv) .envrc +# e2e +*Example/artifacts diff --git a/Example/.detoxrc.js b/Example/.detoxrc.js index 4269d09526..41ed27fecc 100644 --- a/Example/.detoxrc.js +++ b/Example/.detoxrc.js @@ -1,2 +1,2 @@ -const utils = require('../scripts/detox-utils.cjs'); +const utils = require('../scripts/e2e/detox-utils.cjs'); module.exports = utils.commonDetoxConfigFactory('ScreensExample'); diff --git a/Example/package.json b/Example/package.json index c53f43b3da..1a40d54538 100644 --- a/Example/package.json +++ b/Example/package.json @@ -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": { diff --git a/FabricExample/.detoxrc.js b/FabricExample/.detoxrc.js index 477f13da53..0b18322286 100644 --- a/FabricExample/.detoxrc.js +++ b/FabricExample/.detoxrc.js @@ -1,3 +1,3 @@ -const utils = require('../scripts/detox-utils.cjs'); +const utils = require('../scripts/e2e/detox-utils.cjs'); module.exports = utils.commonDetoxConfigFactory('FabricExample'); diff --git a/FabricExample/package.json b/FabricExample/package.json index 22c0ed09bb..91199b09f3 100644 --- a/FabricExample/package.json +++ b/FabricExample/package.json @@ -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": { diff --git a/scripts/detox-utils.cjs b/scripts/e2e/detox-utils.cjs similarity index 84% rename from scripts/detox-utils.cjs rename to scripts/e2e/detox-utils.cjs index 8f66557200..9d88dc50ef 100644 --- a/scripts/detox-utils.cjs +++ b/scripts/e2e/detox-utils.cjs @@ -1,25 +1,27 @@ const ChildProcess = require('node:child_process'); +const { iosDevice } = require('./ios-devices'); -const CI_AVD_NAME = 'e2e_emulator'; +// Should be kept in sync with the constant defined in e2e workflow file +const DEFAULT_CI_AVD_NAME = 'e2e_emulator'; const isRunningCI = process.env.CI != null; -// Assumes that local developement is done on arm64-v8a. +// Assumes that local development is done on arm64-v8a. const apkBulidArchitecture = isRunningCI ? 'x86_64' : 'arm64-v8a'; // test-butler requires AOSP emulator image, which is not available to download for arm64-v8a in Android Studio SDK Manager, therefore // 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 + // "RNS_E2E_AVD_NAME" can be set for local development + const avdName = process.env.RNS_E2E_AVD_NAME ?? null; + if (avdName !== null) { + return avdName } // Fallback: try to use Android SDK try { - let stdout = ChildProcess.execSync("emulator -list-avds") + let stdout = ChildProcess.execSync("emulator -list-avds"); // Possibly convert Buffer to string if (typeof stdout !== 'string') { @@ -32,22 +34,22 @@ function detectLocalAndroidEmulator() { throw new Error('No installed AVDs detected on the device'); } - // Just select first one in the list. + // 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}` + 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); } } function detectAndroidEmulatorName() { - return isRunningCI ? CI_AVD_NAME : detectLocalAndroidEmulator(); + // "RNS_E2E_AVD_NAME" can be set for local development + return isRunningCI ? DEFAULT_CI_AVD_NAME : detectLocalAndroidEmulator(); } /** - * @type {Detox.DetoxConfig} * @param {string} applicationName name (FabricExample / ScreensExample) * @returns {Detox.DetoxConfig} */ @@ -94,14 +96,12 @@ function commonDetoxConfigFactory(applicationName) { devices: { simulator: { type: 'ios.simulator', - device: { - type: 'iPhone 16 Pro', - }, + device: iosDevice, }, attached: { type: 'android.attached', device: { - adbName: CI_AVD_NAME, + adbName: process.env.RNS_ADB_NAME, }, utilBinaryPaths: testButlerApkPath, }, @@ -147,10 +147,10 @@ function commonDetoxConfigFactory(applicationName) { app: 'android.release', }, }, - } + }; } module.exports = { commonDetoxConfigFactory, isRunningCI, -} +}; diff --git a/scripts/e2e/ios-devices.js b/scripts/e2e/ios-devices.js new file mode 100644 index 0000000000..bbb969f81c --- /dev/null +++ b/scripts/e2e/ios-devices.js @@ -0,0 +1,52 @@ +const DEFAULT_APPLE_DEVICE = 'iPhone 17'; +const DEFAULT_IOS_VERSION = 'iOS 26.2'; + +/** + * @return {string} + */ +function getAppleDevice() { + const envVariableKey = 'RNS_E2E_APPLE_SIM_NAME'; + const passedDevice = process.env[envVariableKey]; + if (passedDevice) { + if (/^(iPhone|iPad)\s.+/.test(passedDevice)) { + return passedDevice; + } else { + throw new Error(`Environment variable ${envVariableKey} should be "iPhone xyz" or "iPad xyz".`); + } + } + return process.env.RNS_E2E_APPLE_SIM_NAME || DEFAULT_APPLE_DEVICE; +} +/** + * @return {`iOS ${string}`} requested version of ios, or default if not specified + */ +function getIOSVersion() { + const envVariableKey = 'RNS_E2E_IOS_VERSION'; + const passedVersion = process.env[envVariableKey]; + if (passedVersion) { + if (/^(iOS)\s.+/.test(passedVersion)) { + return /** @type {`iOS ${string}`} */ (passedVersion); + } else { + throw new Error(`Environment variable ${envVariableKey} should be "iOS xyz".`); + } + } + return DEFAULT_IOS_VERSION; +} + +/** + * @typedef {Object} AppleDevice - represents Detox's config for an Apple device + * @property {string} type - a string which represents a model of an iPhone + * @property {`iOS ${string}`} os - operation system version + */ + +/** + * @satisfies {AppleDevice} + * @readonly + * */ +const iosDevice = { + type: getAppleDevice(), + os: getIOSVersion(), +}; + +module.exports = { + iosDevice, +};