diff --git a/.github/workflows/e2e-android-detox.yml b/.github/workflows/e2e-android-detox.yml index 4544f364698..9f0607d418f 100644 --- a/.github/workflows/e2e-android-detox.yml +++ b/.github/workflows/e2e-android-detox.yml @@ -1,12 +1,42 @@ name: Detox E2E Android Tests PR on: + push: + branches: + - test_android_e2e pull_request: branches: - main + - test_android_e2e types: - labeled +env: + AWS_REGION: "us-east-1" + ADMIN_EMAIL: ${{ secrets.MM_MOBILE_E2E_ADMIN_EMAIL }} + ADMIN_USERNAME: ${{ secrets.MM_MOBILE_E2E_ADMIN_USERNAME }} + ADMIN_PASSWORD: ${{ secrets.MM_MOBILE_E2E_ADMIN_PASSWORD }} + BRANCH: ${{ github.event_name == 'pull_request' && github.head_ref || github.ref_name }} + COMMIT_HASH: ${{ github.sha }} + DEVICE_NAME: ${{ inputs.ios_device_name }} + DEVICE_OS_VERSION: ${{ inputs.ios_device_os_name }} + DETOX_AWS_S3_BUCKET: "mattermost-detox-report" + HEADLESS: "false" + TYPE: ${{ inputs.run-type }} + PULL_REQUEST: "https://github.com/mattermost/mattermost-mobile/pull/${{ github.event.number }}" + SITE_1_URL: ${{ inputs.MM_TEST_SERVER_URL || 'https://mobile-e2e-site-1.test.mattermost.cloud' }} + SITE_2_URL: "https://mobile-e2e-site-2.test.mattermost.cloud" + SITE_3_URL: "https://mobile-e2e-site-3.test.mattermost.cloud" + ZEPHYR_ENABLE: ${{ inputs.record_tests_in_zephyr }} + JIRA_PROJECT_KEY: "MM" + ZEPHYR_API_KEY: ${{ secrets.MM_MOBILE_E2E_ZEPHYR_API_KEY }} + ZEPHYR_FOLDER_ID: "3233873" + TEST_CYCLE_LINK_PREFIX: ${{ secrets.MM_MOBILE_E2E_TEST_CYCLE_LINK_PREFIX }} + WEBHOOK_URL: ${{ secrets.MM_MOBILE_E2E_WEBHOOK_URL }} + FAILURE_MESSAGE: "Something has failed" + RUNNING_E2E: "true" + IOS: "false" + concurrency: group: "${{ github.workflow }}-${{ github.event.pull_request.number }}-${{ github.event.label.name }}" cancel-in-progress: true @@ -48,17 +78,6 @@ jobs: key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} restore-keys: ${{ runner.os }}-gradle- - - name: Validate Gradle wrapper - uses: gradle/actions/wrapper-validation@v3 - - - name: Inject Detox settings - run: cd detox && npm run e2e:android-inject-settings - - - name: Update minSdkVersion for react-native-image-picker - run: | - sed -i 's/minSdkVersion 21/minSdkVersion 23/' ./node_modules/react-native-image-picker/android/build.gradle - cat ./node_modules/react-native-image-picker/android/build.gradle | grep minSdkVersion - - name: Detox build run: | cd detox @@ -69,5 +88,121 @@ jobs: - name: ci/upload-android-pr-build uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: - name: android-build-apk-${{ github.run_id }} - path: "android/app/build/outputs/apk/**/app-*.apk" + name: android-build-files-${{ github.run_id }} + path: "android/app/build/**/*" + + detox-android-e2e: + runs-on: ubuntu-latest-8-cores + # needs: build-android-apk + env: + ANDROID_HOME: /usr/local/lib/android/sdk + ANDROID_SDK_ROOT: /usr/local/lib/android/sdk + + steps: + - name: ci/checkout-repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Install Dependencies + run: | + sudo apt-get update + sudo apt-get install -y libpulse0 + sudo apt-get install -y scrot ffmpeg xvfb + + - 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: ci/prepare-android-build + uses: ./.github/actions/prepare-android-build + env: + STORE_FILE: "${{ secrets.MM_MOBILE_STORE_FILE }}" + STORE_ALIAS: "${{ secrets.MM_MOBILE_STORE_ALIAS }}" + STORE_PASSWORD: "${{ secrets.MM_MOBILE_STORE_PASSWORD }}" + MATTERMOST_BUILD_GH_TOKEN: "${{ secrets.MATTERMOST_BUILD_GH_TOKEN }}" + + - name: Install Dependencies + run: | + cd detox + npm install + + - name: Create destination path + run: mkdir -p android/app/build + + - name: Download artifact + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + curl -L -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + https://api.github.com/repos/mattermost/mattermost-mobile/actions/artifacts/2523423750/zip \ + --output android/app/build/artifact.zip + + - name: Unzip artifact + run: | + unzip android/app/build/artifact.zip -d android/app/build + rm -rf android/app/build/artifact.zip + + - name: Set up Android SDK + run: | + export ANDROID_HOME=/usr/local/lib/android/sdk + export PATH=$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/emulator:$ANDROID_HOME/tools/bin:$ANDROID_HOME/platform-tools:$PATH + echo "ANDROID_HOME=$ANDROID_HOME" >> $GITHUB_ENV + echo "PATH=$PATH" >> $GITHUB_ENV + + - name: Start Xvfb + run: | + Xvfb :99 -screen 0 1920x1080x24 & + export DISPLAY=:99 + echo "DISPLAY=:99" >> $GITHUB_ENV + + - name: Accept Android licenses + run: | + yes | sdkmanager --licenses || true + + - name: Install Android system image + run: | + sdkmanager "system-images;android-33;default;x86_64" + sdkmanager "platform-tools" "emulator" + + - name: Create and start Android emulator + run: | + cd detox + chmod +x ./create_android_emulator.sh + ./create_android_emulator.sh + + - name: Generate Report Path + id: s3 + run: | + path="${{ github.run_id }}/${{ github.sha }}" + echo "path=$(echo "${path}" | sed 's/\./-/g')" >> ${GITHUB_OUTPUT} + + - name: Save report Detox Dependencies + id: report-link + run: | + cd detox + npm run e2e:save-report + env: + DETOX_AWS_ACCESS_KEY_ID: ${{ secrets.MM_MOBILE_DETOX_AWS_ACCESS_KEY_ID }} + DETOX_AWS_SECRET_ACCESS_KEY: ${{ secrets.MM_MOBILE_DETOX_AWS_SECRET_ACCESS_KEY }} + IOS: false + BUILD_ID: ${{ github.run_id }} + REPORT_PATH: ${{ steps.s3.outputs.path }} + ## These are needed for the MM Webhook report + COMMIT_HASH: ${{ github.sha }} + BRANCH: ${{ github.ref }} + + - name: Stop Android emulator + if: always() # Ensure the emulator is stopped even if the tests fail + run: | + adb emu kill + + - name: Upload android Test Report + if: always() + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: android-results-${{ github.run_id }} + path: detox/artifacts/ diff --git a/detox/.detoxrc.json b/detox/.detoxrc.json index a38b3f4faa4..8fd9e431766 100644 --- a/detox/.detoxrc.json +++ b/detox/.detoxrc.json @@ -37,7 +37,7 @@ "android.emulator": { "type": "android.emulator", "device": { - "avdName": "detox_pixel_4_xl_api_31" + "avdName": "detox_pixel_4_xl_api_33" } } }, diff --git a/detox/README.md b/detox/README.md index a52a925cb37..ec0edeee406 100644 --- a/detox/README.md +++ b/detox/README.md @@ -1,36 +1,49 @@ # How to Run Detox Tests -## Android +This guide will help you set up and run Detox tests for your project. -### Install Dependencies +## Install Dependencies -From the root directory, run the following command to install the necessary dependencies: +First, navigate to the root directory of your project and install the necessary dependencies by running: ```sh npm install ``` -### Inject Detox Settings +navigate to the `detox` folder and run `npm install` + +## Android + +### Build Detox Android App -To inject the Detox settings into your project, navigate to the `detox` directory and run the following command: +To build the Detox Android app, navigate to the `detox` folder and run: ```sh -npm run inject-detox-settings +npm run e2e:android-build ``` -### Update `minSdkVersion` for `react-native-image-picker` +### Run Detox Android Tests -On macOS machines, update the `minSdkVersion` of `react-native-image-picker` to 23 by running the following command from the root directory. -This is required for Detox to build the test apk targeting android API 31 or higher. +To execute the Detox tests on Android, navigate to the `detox` folder and run: ```sh -sed -i '' 's/minSdkVersion 21/minSdkVersion 23/' ./node_modules/react-native-image-picker/android/build.gradle +npm run e2e:android-test ``` -### Build detox android app +## iOS + +### Build iOS Simulator -From the `detox` folder run: +To build the iOS simulator for Detox, navigate to the `detox` folder and run: +```sh +npm run e2e:ios-build ``` -npm run e2e:android-build + +### Run iOS Tests + +To execute the Detox tests on iOS, navigate to the `detox` folder and run: + +```sh +npm run e2e:ios-test ``` \ No newline at end of file diff --git a/detox/create_android_emulator.sh b/detox/create_android_emulator.sh index 18ef420e420..2ed0b566045 100755 --- a/detox/create_android_emulator.sh +++ b/detox/create_android_emulator.sh @@ -1,39 +1,129 @@ #!/bin/bash - # Reference: Download Android (AOSP) Emulators - https://github.com/wix/Detox/blob/master/docs/guide/android-dev-env.md#android-aosp-emulators -# sdkmanager "system-images;android-31;default;arm64-v8a" -# sdkmanager --licenses set -ex set -o pipefail -SDK_VERSION=31 -NAME="detox_pixel_4_xl_api_${SDK_VERSION}" +SDK_VERSION=33 +AVD_NAME="detox_pixel_4_xl_api_${SDK_VERSION}" + +setup_avd_home() { + if [[ "$CI" == "true" ]]; then + export ANDROID_AVD_HOME=$(pwd)/.android/avd + mkdir -p "$ANDROID_AVD_HOME" + fi +} -if emulator -list-avds | grep -q $NAME; then - echo "'${NAME}' Android virtual device already exists." -else - CPU_ARCH_FAMILY='' - CPU_ARCH='' +get_cpu_architecture() { if [[ $(uname -p) == 'arm' ]]; then - CPU_ARCH_FAMILY=arm64-v8a - CPU_ARCH=arm64 + echo "arm64-v8a arm64" + else + echo "x86_64 x86_64" + fi +} + +create_avd() { + local cpu_arch_family cpu_arch + read cpu_arch_family cpu_arch < <(get_cpu_architecture) + + avdmanager create avd -n "$AVD_NAME" -k "system-images;android-${SDK_VERSION};default;${cpu_arch_family}" -p "$AVD_NAME" -d 'pixel' + + cp -r android_emulator/ "$AVD_NAME/" + sed -i -e "s|AvdId = change_avd_id|AvdId = ${AVD_NAME}|g" "$AVD_NAME/config.ini" + sed -i -e "s|avd.ini.displayname = change_avd_displayname|avd.ini.displayname = Detox Pixel 4 XL API ${SDK_VERSION}|g" "$AVD_NAME/config.ini" + sed -i -e "s|abi.type = change_type|abi.type = ${cpu_arch_family}|g" "$AVD_NAME/config.ini" + sed -i -e "s|hw.cpu.arch = change_cpu_arch|hw.cpu.arch = ${cpu_arch}|g" "$AVD_NAME/config.ini" + sed -i -e "s|image.sysdir.1 = change_to_image_sysdir/|image.sysdir.1 = system-images/android-${SDK_VERSION}/default/${cpu_arch_family}/|g" "$AVD_NAME/config.ini" + sed -i -e "s|skin.path = change_to_absolute_path/pixel_4_xl_skin|skin.path = $(pwd)/${AVD_NAME}/pixel_4_xl_skin|g" "$AVD_NAME/config.ini" + + echo "hw.cpu.ncore=5" >> "$AVD_NAME/config.ini" + echo "Android virtual device successfully created: ${AVD_NAME}" +} + +start_adb_server() { + echo "Restarting ADB server..." + adb kill-server + adb start-server +} + +start_emulator() { + echo "Starting the emulator..." + local emulator_opts="-avd $AVD_NAME -no-snapshot -no-boot-anim -no-audio -no-window" + + if [[ "$CI" == "true" || "$(uname -s)" == "Linux" ]]; then + emulator $emulator_opts -gpu host -accel on -qemu -m 4096 & else - CPU_ARCH_FAMILY=x86_64 - CPU_ARCH=x86_64 + emulator $emulator_opts -gpu guest -verbose -qemu -vnc :0 fi +} + +wait_for_emulator() { + if [[ "$CI" != "true" ]]; then return; fi - # Create virtual device in a relative "detox_pixel_4_xl_api_${SDK_VERSION}" folder - avdmanager create avd -n $NAME -k "system-images;android-${SDK_VERSION};default;${CPU_ARCH_FAMILY}" -p $NAME -d 'pixel' + echo "Waiting for emulator to boot..." + adb wait-for-device + until [[ "$(adb shell getprop sys.boot_completed | tr -d '\r')" == "1" ]]; do + echo "Waiting for emulator to fully boot..." + sleep 10 + done + echo "Emulator is fully booted." +} + +install_app() { + echo "Installing the app..." + adb install -r ../android/app/build/outputs/apk/debug/app-debug.apk + adb shell pm list packages | grep "com.mattermost.rnbeta" && echo "App is installed." || echo "App is not installed." +} + +start_server() { + echo "Starting the server..." + cd .. + RUNNING_E2E=true npm run start & + local timeout=120 interval=5 elapsed=0 + + until nc -z localhost 8081; do + if [[ $elapsed -ge $timeout ]]; then + echo "Server did not start within 3 minutes." + exit 1 + fi + echo "Waiting for server to be ready..." + sleep $interval + elapsed=$((elapsed + interval)) + done + echo "Server is ready." +} + +setup_adb_reverse() { + echo "Setting up ADB reverse port forwarding..." + adb reverse tcp:8081 tcp:8081 +} + +run_detox_tests() { + echo "Running Detox tests..." + cd detox + npm run e2e:android-test -- about.e2e.ts account_menu.e2e.ts +} + +main() { + setup_avd_home + + if ! emulator -list-avds | grep -q "$AVD_NAME"; then + create_avd + else + echo "'${AVD_NAME}' Android virtual device already exists." + fi + + start_adb_server + start_emulator + wait_for_emulator + + if [[ "$CI" == "true" ]]; then + install_app + start_server + setup_adb_reverse + fi - # Copy predefined config and skin - cp -r android_emulator/ $NAME/ - sed -i -e "s|AvdId = change_avd_id|AvdId = ${NAME}|g" $NAME/config.ini - sed -i -e "s|avd.ini.displayname = change_avd_displayname|avd.ini.displayname = Detox Pixel 4 XL API ${SDK_VERSION}|g" $NAME/config.ini - sed -i -e "s|abi.type = change_type|abi.type = ${CPU_ARCH_FAMILY}|g" $NAME/config.ini - sed -i -e "s|hw.cpu.arch = change_cpu_arch|hw.cpu.arch = ${CPU_ARCH}|g" $NAME/config.ini - sed -i -e "s|image.sysdir.1 = change_to_image_sysdir/|image.sysdir.1 = system-images/android-${SDK_VERSION}/default/${CPU_ARCH_FAMILY}/|g" $NAME/config.ini - sed -i -e "s|skin.path = change_to_absolute_path/pixel_4_xl_skin|skin.path = $(pwd)/${NAME}/pixel_4_xl_skin|g" $NAME/config.ini + run_detox_tests +} - echo "Android virtual device successfully created: ${NAME}" -fi +main diff --git a/detox/inject-detox-settings.js b/detox/inject-detox-settings.js deleted file mode 100644 index 8ba2db574dc..00000000000 --- a/detox/inject-detox-settings.js +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. -/* eslint-disable no-console */ -const fs = require('fs'); -const path = require('path'); - -// Paths to files -const androidManifestPath = path.resolve( - __dirname, - '../android/app/src/debug/AndroidManifest.xml', -); -const settingsGradlePath = path.resolve(__dirname, '../android/settings.gradle'); - -// Detox code to add to settings.gradle -const detoxSettings = ` -include ':detox' -project(':detox').projectDir = new File(rootProject.projectDir, '../detox/node_modules/detox/android') -`; - -// Updated AndroidManifest.xml content -const updatedManifest = ` - - - - - - - -`; - -// Update AndroidManifest.xml -function updateAndroidManifest() { - try { - fs.writeFileSync(androidManifestPath, updatedManifest, 'utf-8'); - console.log('AndroidManifest.xml updated successfully.'); - } catch (err) { - console.error(`Failed to update AndroidManifest.xml: ${err.message}`); - } -} - -// Update settings.gradle -function updateSettingsGradle() { - try { - const content = fs.readFileSync(settingsGradlePath, 'utf-8'); - if (content.includes("include ':detox'")) { - console.log('Detox settings already present in settings.gradle.'); - return; - } - fs.writeFileSync(settingsGradlePath, content + detoxSettings, 'utf-8'); - console.log('settings.gradle updated successfully.'); - } catch (err) { - console.error(`Failed to update settings.gradle: ${err.message}`); - } -} - -// Run updates -updateAndroidManifest(); -updateSettingsGradle(); diff --git a/detox/package.json b/detox/package.json index 662c8fd7ab4..b3fbbf7ca58 100644 --- a/detox/package.json +++ b/detox/package.json @@ -42,7 +42,6 @@ "xml2js": "0.6.2" }, "scripts": { - "e2e:android-inject-settings": "node inject-detox-settings.js", "e2e:android-create-emulator": "./create_android_emulator.sh", "e2e:android-build": "detox build -c android.emu.debug", "e2e:android-test": "detox test -c android.emu.debug", diff --git a/detox/save_report.js b/detox/save_report.js index 0b59d8829be..867458e88e3 100644 --- a/detox/save_report.js +++ b/detox/save_report.js @@ -99,12 +99,16 @@ const saveReport = async () => { // Merge all XML reports into one single XML report const platform = process.env.IOS === 'true' ? 'ios' : 'android'; const combinedFilePath = `${ARTIFACTS_DIR}/${platform}-combined.xml`; + + console.log('Combined file path:', combinedFilePath); + console.log('Merfe file arg:', [`${ARTIFACTS_DIR}/${platform}-results*/${platform}-junit*.xml`]); + await mergeFiles(path.join(__dirname, combinedFilePath), [`${ARTIFACTS_DIR}/${platform}-results*/${platform}-junit*.xml`]); console.log(`Merged, check ${combinedFilePath}`); // Read XML from a file const xml = fse.readFileSync(combinedFilePath); - const {testsuites} = convertXmlToJson(xml); + const {testsuites} = convertXmlToJson(xml, platform); // Generate short summary, write to file and then send report via webhook const allTests = getAllTests(testsuites); diff --git a/detox/utils/report.js b/detox/utils/report.js index c0bc53fb944..c9d3d5e734c 100644 --- a/detox/utils/report.js +++ b/detox/utils/report.js @@ -11,8 +11,7 @@ const {ARTIFACTS_DIR} = require('./constants'); const MAX_FAILED_TITLES = 5; -function convertXmlToJson(xml) { - const platform = process.env.IOS === 'true' ? 'ios' : 'android'; +function convertXmlToJson(xml, platform) { const jsonFile = `${ARTIFACTS_DIR}/${platform}-junit.json`; // Convert XML to JSON