From b4ae6be580114dbd9258b7a10dafc1a9a8060520 Mon Sep 17 00:00:00 2001 From: Hugo Dias Date: Fri, 21 May 2021 14:25:30 +0100 Subject: [PATCH] feat: react-native android support (#777) --- md/github-actions.md | 3 +- md/react-native.md | 94 +++++++++++++++++++++++++++++++++++ package.json | 1 + src/cmds/test.js | 2 +- src/test/index.js | 14 +++++- src/test/react-native.js | 101 ++++++++++++++++++++++++++++++++++++++ src/types.d.ts | 4 +- test/utils/echo-server.js | 2 +- 8 files changed, 215 insertions(+), 6 deletions(-) create mode 100644 md/react-native.md create mode 100644 src/test/react-native.js diff --git a/md/github-actions.md b/md/github-actions.md index 5ee87523a..b69c6afe7 100644 --- a/md/github-actions.md +++ b/md/github-actions.md @@ -48,7 +48,8 @@ jobs: - uses: actions/checkout@v2 - uses: microsoft/playwright-github-action@v1 - run: npm install - - run: npx aegir test -t browser -t webworker --bail --cov + - run: npx aegir test -t browser --bail --cov + - run: npx aegir test -t webworker --bail - uses: codecov/codecov-action@v1 test-firefox: needs: check diff --git a/md/react-native.md b/md/react-native.md new file mode 100644 index 000000000..ccaf63e4a --- /dev/null +++ b/md/react-native.md @@ -0,0 +1,94 @@ +# React Native Setup + +## Boring and heavy way for all OSes + +Go to https://reactnative.dev/docs/environment-setup click in "React Native CLI Quickstart" and follow instructions. + +If you don't want to fill your system with the full android studio or XCode follow the instructions below but be aware that there are dragons ahead! + +## Android + +```bash +brew install --cask adoptopenjdk/openjdk/adoptopenjdk8 +export ANDROID_SDK_ROOT="~/android-sdk" +touch ~/.android/repositories.cfg +``` + +Download `cmdline-tool` from [https://developer.android.com/studio#cmdline-tools](https://developer.android.com/studio#cmdline-tools) and extract to `~/android-sdk/cmdline-tools/latest` + +```bash + +~/android-sdk/cmdline-tools/latest/bin/sdkmanager --update +~/android-sdk/cmdline-tools/latest/bin/sdkmanager "platform-tools" "platforms;android-29" "build-tools;29.0.2" "system-images;android-29;default;x86_64" + +// in your .zshrc or similar add sdk to PATH +PATH=$PATH:$ANDROID_SDK_ROOT/emulator +PATH=$PATH:$ANDROID_SDK_ROOT/tools +PATH=$PATH:$ANDROID_SDK_ROOT/tools/bin +PATH=$PATH:$ANDROID_SDK_ROOT/platform-tools +export PATH +``` +Now all the tools are in the `$PATH` , no need for absolute paths anymore. + +### Some examples + +You normally dont need to run any of these + +```bash +# install new platforms, build tools and system images +sdkmanager --update +sdkmanager "platforms;android-29" "build-tools;29.0.2" "system-images;android-29;default;x86_64" + +# create an avd +avdmanager create avd -n aegir-android -d pixel --package "system-images;android-29;default;x86_64" + +# delete avd +avdmanager delete avd -n aegir-android + +# manually run the emulator +emulator @aegir-android + +# list avds +emulator -list-avds + +# redirect port trafic +adb -s emulator-5554 reverse tcp:3000 tcp:3000 +adb reverse --list +adb reverse --remove-all +``` +> If the internal aegir AVD changes SDK versions you might need to run the `sdkmanager` above to update and install the new SDK versions in your system. + + +### emulator acceleration (optional) + +[https://developer.android.com/studio/run/emulator-acceleration#vm-mac](https://developer.android.com/studio/run/emulator-acceleration#vm-mac) + +### Aegir config +Android needs special attention for networks settings ([docs](https://developer.android.com/studio/run/emulator-networking)). The most common change is to use the special address `10.0.2.2` to redirect trafic to your local loopback interface `127.0.0.1`. + +```js +module.exports = { + test: { + async before (options) { + let echoServer = new EchoServer() + await echoServer.start() + const { address, port } = echoServer.server.address() + let hostname = address // address will normally be 127.0.0.1 + + if(options.runner === 'react-native-android') { + hostname = '10.0.2.2' + } + + return { + echoServer, + env: { ECHO_SERVER : format({ protocol: 'http:', hostname, port })} + } + }, + async after (options, before) { + await before.echoServer.stop() + } + } +} + + +``` \ No newline at end of file diff --git a/package.json b/package.json index 53f241828..9fdd7870b 100644 --- a/package.json +++ b/package.json @@ -107,6 +107,7 @@ "polka": "^0.5.2", "premove": "^3.0.1", "proper-lockfile": "^4.1.2", + "react-native-test-runner": "^5.0.0", "read-pkg-up": "^7.0.1", "semver": "^7.3.5", "simple-git": "^2.37.0", diff --git a/src/cmds/test.js b/src/cmds/test.js index 533b94fa0..e3d530c89 100644 --- a/src/cmds/test.js +++ b/src/cmds/test.js @@ -43,7 +43,7 @@ module.exports = { alias: 't', describe: 'In which target environment to execute the tests', type: 'array', - choices: ['node', 'browser', 'webworker', 'electron-main', 'electron-renderer'], + choices: ['node', 'browser', 'webworker', 'electron-main', 'electron-renderer', 'react-native-android', 'react-native-ios'], default: userConfig.test.target }, watch: { diff --git a/src/test/index.js b/src/test/index.js index a6d7b3966..cf6228ff3 100644 --- a/src/test/index.js +++ b/src/test/index.js @@ -4,6 +4,7 @@ const pmap = require('p-map') const node = require('./node') const browser = require('./browser') const electron = require('./electron') +const rn = require('./react-native') /** * @typedef {import("execa").Options} ExecaOptions @@ -71,8 +72,19 @@ const TASKS = [ * @param {TestOptions & GlobalOptions} ctx */ enabled: (ctx) => ctx.target.includes('electron-renderer') + }, + { + title: 'Test React Native Android', + /** + * @param {TestOptions & GlobalOptions} opts + * @param {ExecaOptions} execaOptions + */ + task: (opts, execaOptions) => rn({ ...opts, runner: 'react-native-android' }, execaOptions), + /** + * @param {TestOptions & GlobalOptions} ctx + */ + enabled: (ctx) => ctx.target.includes('react-native-android') } - ] module.exports = { diff --git a/src/test/react-native.js b/src/test/react-native.js new file mode 100644 index 000000000..a15410c1e --- /dev/null +++ b/src/test/react-native.js @@ -0,0 +1,101 @@ +'use strict' +const path = require('path') +const execa = require('execa') +const merge = require('merge-options') + +/** + * @typedef {import("execa").Options} ExecaOptions + * @typedef {import('../types').TestOptions} TestOptions + * @typedef {import('../types').GlobalOptions} GlobalOptions + */ + +/** + * + * @param {TestOptions & GlobalOptions} argv + * @param {ExecaOptions} execaOptions + */ +module.exports = async (argv, execaOptions) => { + const AVDName = 'aegir-android-29' + const extra = argv['--'] ? argv['--'] : [] + const emulator = process.env.CI ? [] : ['--emulator', AVDName] + const forwardOptions = /** @type {string[]} */([ + ...extra, + argv.timeout && `--timeout=${argv.timeout}`, + argv.grep && `--grep=${argv.grep}`, + argv.bail && '--bail' + ].filter(Boolean)) + const files = argv.files.length > 0 + ? argv.files + : [ + '**/*.spec.{js,ts}', + 'test/browser.{js,ts}' + ] + + // before hook + const before = await argv.fileConfig.test.before(argv) + const beforeEnv = before && before.env ? before.env : {} + + await checkAndroidEnv() + + if (!await checkAvd(AVDName)) { + await execa('avdmanager', [ + 'create', + 'avd', + '-n', AVDName, + '-d', 'pixel', + '--package', 'system-images;android-29;default;x86_64' + ]) + } + + // run pw-test + await execa('rn-test', + [ + ...files, + '--platform', argv.runner === 'react-native-android' ? 'android' : 'ios', + ...emulator, + '--reset-cache', + ...forwardOptions + ], + merge( + { + env: { + AEGIR_RUNNER: argv.runner, + NODE_ENV: process.env.NODE_ENV || 'test', + ...beforeEnv + }, + preferLocal: true, + localDir: path.join(__dirname, '../..'), + stdio: 'inherit' + }, + execaOptions + ) + ) + + // after hook + await argv.fileConfig.test.after(argv, before) +} + +/** + * Check for avd + * + * @param {string} name + */ +async function checkAvd (name) { + const avd = await execa('emulator', ['-list-avds']) + + return avd.stdout.split('\n').includes(name) +} + +async function checkAndroidEnv () { + if (!process.env.ANDROID_SDK_ROOT) { + throw new Error('ANDROID_SDK_ROOT is not set') + } + + try { + await execa('emulator', ['-help']) + // await execa('sdkmanager') + await execa('avdmanager', ['list']) + } catch (err) { + throw new Error(`"Command ${err.path}" is not available, you need to properly setup your android environment.`) + } +} diff --git a/src/types.d.ts b/src/types.d.ts index e4ecc1005..14c0c4e55 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -171,7 +171,7 @@ interface TestOptions { /** * In which target environment to execute the tests */ - target: Array<'node' | 'browser' | 'webworker' | 'electron-main' | 'electron-renderer'> + target: Array<'node' | 'browser' | 'webworker' | 'electron-main' | 'electron-renderer' | 'react-native-android' | 'react-native-ios'> /** * Watch files for changes and rerun tests */ @@ -203,7 +203,7 @@ interface TestOptions { /** * Runner enviroment */ - runner: 'node' | 'browser' | 'webworker' | 'electron-main' | 'electron-renderer' + runner: 'node' | 'browser' | 'webworker' | 'electron-main' | 'electron-renderer' | 'react-native-android' | 'react-native-ios' /** * Browser options */ diff --git a/test/utils/echo-server.js b/test/utils/echo-server.js index c9cd3dab6..97f9ad180 100644 --- a/test/utils/echo-server.js +++ b/test/utils/echo-server.js @@ -15,7 +15,7 @@ describe('echo server spec', () => { before(async () => { await echo.start() - const { port, address } = /** @type {import('node:net').AddressInfo} */(echo.server.address()) + const { port, address } = /** @type {import('net').AddressInfo} */(echo.server.address()) url = format({ protocol: 'http:', hostname: address, port }) })