diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 000000000000..82b2b510aee5 --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,4 @@ +# See https://github.com/rhysd/actionlint/blob/main/docs/config.md +self-hosted-runner: + labels: + - ubuntu-20.04-64core diff --git a/.github/actions/composite/configureAwsCredentials/action.yml b/.github/actions/composite/configureAwsCredentials/action.yml index aec147b8cc77..f092478962c8 100644 --- a/.github/actions/composite/configureAwsCredentials/action.yml +++ b/.github/actions/composite/configureAwsCredentials/action.yml @@ -8,6 +8,10 @@ inputs: AWS_SECRET_ACCESS_KEY: description: 'Secret Access Key to AWS' required: true + AWS_REGION: + description: 'Region for AWS' + required: true + default: 'us-east-1' runs: using: composite @@ -18,4 +22,4 @@ runs: with: aws-access-key-id: ${{ inputs.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ inputs.AWS_SECRET_ACCESS_KEY }} - aws-region: us-east-1 + aws-region: ${{ inputs.AWS_REGION }} diff --git a/.github/scripts/validateActionsAndWorkflows.sh b/.github/scripts/validateActionsAndWorkflows.sh index 9ac61c2e23a5..0c373a0a247a 100755 --- a/.github/scripts/validateActionsAndWorkflows.sh +++ b/.github/scripts/validateActionsAndWorkflows.sh @@ -41,6 +41,12 @@ done for ((i=0; i < ${#WORKFLOWS[@]}; i++)); do WORKFLOW=${WORKFLOWS[$i]} + + # Skip linting e2e workflow due to bug here: https://github.com/SchemaStore/schemastore/issues/2579 + if [[ "$WORKFLOW" == './workflows/preDeploy.yml' ]]; then + continue + fi + ajv -s ./tempSchemas/github-workflow.json -d "$WORKFLOW" --strict=false & ASYNC_PROCESSES[${#ACTIONS[@]} + $i]=$! done diff --git a/.github/workflows/e2ePerformanceRegressionTests.yml b/.github/workflows/e2ePerformanceRegressionTests.yml deleted file mode 100644 index ad6425d4ec90..000000000000 --- a/.github/workflows/e2ePerformanceRegressionTests.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: Run e2e performance regression tests - -on: - pull_request: - types: [labeled] - -jobs: - e2e-tests: - if: ${{ github.event.label.name == 'e2e' }} - name: Run e2e performance regression tests - # Although the tests will run on an android emulator, using macOS as its more performant - runs-on: macos-12 - steps: - # This action checks-out the repository, so the workflow can access it. - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 - with: - fetch-depth: 0 - - - uses: Expensify/App/.github/actions/composite/setupNode@main - - - uses: ruby/setup-ruby@eae47962baca661befdfd24e4d6c34ade04858f7 - with: - ruby-version: '2.7' - bundler-cache: true - - # Improve emulator startup time, see https://github.com/marketplace/actions/android-emulator-runner - - name: Gradle cache - uses: gradle/gradle-build-action@v2 - - - name: AVD cache - uses: actions/cache@v3 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-28 - - - name: Create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: 28 - ram-size: 3072M - heap-size: 512M - force-avd-creation: false - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: false - script: echo "Generated AVD snapshot for caching." - - # Note: if the android build fails the logs can be incomplete. It can help to run the build once manually to get a full log - - name: Preheat build system - env: - JAVA_HOME: ${{ env.JAVA_HOME_11_X64 }} - run: | - npm run android-build-e2e - - - name: Start emulator and run tests - id: tests - uses: reactivecircus/android-emulator-runner@v2 - env: - JAVA_HOME: ${{ env.JAVA_HOME_11_X64 }} - INTERACTION_TIMEOUT: 120000 # 2 minutes - # when logging progresses only refresh the _log_ every 30 seconds - LOGGER_PROGRESS_REFRESH_RATE: 30000 - # TODO: remove this once implementation done. - baseline: dev/ci-e2e-tests - with: - api-level: 28 - ram-size: 3072M - heap-size: 512M - force-avd-creation: false - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: true - script: npm run test:e2e - - - name: If tests failed, upload logs and video - if: ${{ failure() && steps.tests.conclusion == 'failure' }} - uses: actions/upload-artifact@v3 - with: - name: test-failure-logs - path: e2e/.results - retention-days: 5 diff --git a/.github/workflows/preDeploy.yml b/.github/workflows/preDeploy.yml index 8c15408eaea1..62117c5b676a 100644 --- a/.github/workflows/preDeploy.yml +++ b/.github/workflows/preDeploy.yml @@ -221,3 +221,104 @@ jobs: 3. Once your PR is deployed to _production_, we start a 7-day timer :alarm_clock:. After it has been on production for 7 days without causing any regressions, then we pay out the Upwork job. :moneybag: So it might take a while before you're paid for your work, but we typically post multiple new jobs every day, so there's plenty of opportunity. I hope you've had a positive experience contributing to this repo! :blush: + + e2e-tests: + name: "Run e2e performance regression tests" + runs-on: ubuntu-20.04-64core + steps: + - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + with: + fetch-depth: 0 + + - uses: Expensify/App/.github/actions/composite/setupNode@main + + - uses: ruby/setup-ruby@eae47962baca661befdfd24e4d6c34ade04858f7 + with: + ruby-version: '2.7' + bundler-cache: true + + # Cache gradle to improve Android build time + - name: Gradle cache + uses: gradle/gradle-build-action@3fbe033aaae657f011f88f29be9e65ed26bd29ef + + - name: Make zip directory for everything to send to AWS Device Farm + run: mkdir zip + + - name: Checkout "Compare" commit + run: git checkout ${{ github.event.before }} + + - name: Build "Compare" APK + run: npm run android-build-e2e + + - name: Copy "Compare" APK + run: cp android/app/build/outputs/apk/e2eRelease/app-e2eRelease.apk zip/app-e2eRelease-compare.apk + + - name: Checkout "Baseline" commit (last release) + run: git checkout "$(gh release list --limit 1 | awk '{ print $1 }')" + + - name: Build "Baseline" APK + run: npm run android-build-e2e + + - name: Copy "Baseline" APK + run: cp android/app/build/outputs/apk/e2eRelease/app-e2eRelease.apk zip/app-e2eRelease-baseline.apk + + - name: Checkout previous branch for source code to run on AWS Device farm + run: git checkout - + + - name: Copy e2e code into zip folder + run: cp -r tests/e2e zip + + - name: Zip everything in the zip directory up + run: zip -qr App.zip ./zip + + - name: Configure AWS Credentials + uses: Expensify/App/.github/actions/composite/configureAwsCredentials@main + with: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_REGION: us-west-2 + + - name: Schedule AWS Device Farm test run + uses: realm/aws-devicefarm/test-application@7b9a91236c456c97e28d384c9e476035d5ea686b + with: + name: App E2E Performance Regression Tests + project_arn: ${{ secrets.AWS_PROJECT_ARN }} + device_pool_arn: ${{ secrets.AWS_DEVICE_POOL_ARN }} + app_file: zip/app-e2eRelease-baseline.apk + app_type: ANDROID_APP + test_type: APPIUM_NODE + test_package_file: App.zip + test_package_type: APPIUM_NODE_TEST_PACKAGE + test_spec_file: tests/e2e/TestSpec.yml + test_spec_type: APPIUM_NODE_TEST_SPEC + remote_src: false + file_artifacts: Customer Artifacts.zip + cleanup: true + + - name: Unzip AWS Device Farm results + run: unzip Customer\ Artifacts.zip + + - name: Set output of AWS Device Farm into GitHub ENV + run: | + { echo 'OUTPUT<> "$GITHUB_ENV" + + - name: Get merged pull request + id: getMergedPullRequest + # TODO: Point back action actions-ecosystem after https://github.com/actions-ecosystem/action-get-merged-pull-request/pull/223 is merged + uses: roryabraham/action-get-merged-pull-request@7a7a194f6ff8f3eef58c822083695a97314ebec1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Leave output of AWS Device Farm as a PR comment + run: | + gh pr comment ${{ steps.getMergedPullRequest.outputs.number }} -F ./Host_Machine_Files/\$WORKING_DIRECTORY/output.md + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Check if test failed, if so leave a deploy blocker label + if: ${{ !contains(env.OUTPUT, 'There are no entries') }} + run: | + gh pr edit ${{ steps.getMergedPullRequest.outputs.number }} --add-label 'DeployBlockerCash' + gh pr comment ${{ steps.getMergedPullRequest.outputs.number }} -b "@Expensify/mobile-deployers 📣 Please look into this performance regression as it's a deploy blocker." + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index ba331269cf99..768ab38507e3 100644 --- a/.gitignore +++ b/.gitignore @@ -93,4 +93,4 @@ storybook-static .jest-cache # E2E test reports -e2e/.results/ +tests/e2e/.results/ diff --git a/README.md b/README.md index 61734c2417ed..dd3f0947a4b6 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ variables referenced here get updated since your local `.env` file is ignored. - `ONYX_METRICS` (optional) - Set this to `true` to capture even more performance metrics and see them in Flipper see [React-Native-Onyx#benchmarks](https://github.com/Expensify/react-native-onyx#benchmarks) for more information - `E2E_TESTING` (optional) - This needs to be set to `true` when running the e2e tests for performance regression testing. - This happens usually automatically, read [this](e2e/README.md) for more information + This happens usually automatically, read [this](tests/e2e/README.md) for more information ---- diff --git a/e2e/measure/math.js b/e2e/measure/math.js deleted file mode 100644 index b6acc763255e..000000000000 --- a/e2e/measure/math.js +++ /dev/null @@ -1,35 +0,0 @@ -const _ = require('underscore'); -const {DROP_WORST} = require('../config'); - -// Simple outlier removal, where we remove at the head and tail entries -const filterOutliers = (data) => { - // Copy the values, rather than operating on references to existing values - const values = [...data].sort(); - const removePerSide = Math.ceil(DROP_WORST / 2); - values.splice(0, removePerSide); - values.splice(values.length - removePerSide); - return values; -}; -const mean = arr => _.reduce(arr, (a, b) => a + b, 0) / arr.length; - -const std = (arr) => { - const avg = mean(arr); - return Math.sqrt(_.reduce(_.map(arr, i => (i - avg) ** 2), (a, b) => a + b) / arr.length); -}; - -const getStats = (entries) => { - const cleanedEntries = filterOutliers(entries); - const meanDuration = mean(cleanedEntries); - const stdevDuration = std(cleanedEntries); - - return { - mean: meanDuration, - stdev: stdevDuration, - runs: cleanedEntries.length, - entries: cleanedEntries, - }; -}; - -module.exports = { - getStats, -}; diff --git a/e2e/utils/androidReversePort.js b/e2e/utils/androidReversePort.js deleted file mode 100644 index b644ca1538dd..000000000000 --- a/e2e/utils/androidReversePort.js +++ /dev/null @@ -1,6 +0,0 @@ -const {SERVER_PORT} = require('../config'); -const execAsync = require('./execAsync'); - -module.exports = function () { - return execAsync(`adb reverse tcp:${SERVER_PORT} tcp:${SERVER_PORT}`); -}; diff --git a/e2e/utils/startRecordingVideo.js b/e2e/utils/startRecordingVideo.js deleted file mode 100644 index 005c9c123774..000000000000 --- a/e2e/utils/startRecordingVideo.js +++ /dev/null @@ -1,39 +0,0 @@ -const execAsync = require('./execAsync'); -const {OUTPUT_DIR} = require('../config'); -const Logger = require('../utils/logger'); - -module.exports = () => { - // The emulator on CI launches with no-window option. - // Taking screenshots results in blank shots. - // Recording a video however includes the graphic content. - const cmd = 'adb shell screenrecord /sdcard/video.mp4'; - let recordingFailed = false; - const recording = execAsync(cmd); - recording.catch((error) => { - // Don't abort on errors - Logger.warn('Error while recording video', error); - recordingFailed = true; - }); - - return (save = false) => { - if (recordingFailed) { return; } - - recording.abort(); - return new Promise((resolve) => { - if (!save) { - resolve(); - return; - } - setTimeout(() => { - if (save) { - const getVideo = () => execAsync('adb pull /sdcard/video.mp4'); - const moveVideo = () => execAsync(`mv video.mp4 ${OUTPUT_DIR}/video.mp4`); - const cleanupVideo = () => execAsync('adb shell rm /sdcard/video.mp4'); - getVideo().then(moveVideo).then(cleanupVideo).then(resolve); - } else { - resolve(); - } - }, 1000); // Give the device some time to finish writing the file - }); - }; -}; diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 959810eee87c..32e10fddc413 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -16,7 +16,7 @@ opt_out_usage platform :android do desc "Generate a new local APK for e2e testing" lane :build_e2e do - ENV["ENVFILE"]="e2e/.env.e2e" + ENV["ENVFILE"]="tests/e2e/.env.e2e" ENV["ENTRY_FILE"]="src/libs/E2E/reactNativeLaunchingTest.js" gradle( diff --git a/package.json b/package.json index 35e13db600f6..a0a563037e4c 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "analyze-packages": "ANALYZE_BUNDLE=true webpack --config config/webpack/webpack.common.js --env envFile=.env.production", "symbolicate:android": "npx metro-symbolicate android/app/build/generated/sourcemaps/react/release/index.android.bundle.map", "symbolicate:ios": "npx metro-symbolicate main.jsbundle.map", - "test:e2e": "node ./e2e/testRunner.js" + "test:e2e": "node tests/e2e/testRunner.js" }, "dependencies": { "@expensify/react-native-web": "0.18.9", diff --git a/src/libs/E2E/client.js b/src/libs/E2E/client.js index 1d61bfb348a8..82a1acdf3df2 100644 --- a/src/libs/E2E/client.js +++ b/src/libs/E2E/client.js @@ -1,5 +1,5 @@ -import Routes from '../../../e2e/server/routes'; -import Config from '../../../e2e/config'; +import Routes from '../../../tests/e2e/server/routes'; +import Config from '../../../tests/e2e/config'; const SERVER_ADDRESS = `http://localhost:${Config.SERVER_PORT}`; diff --git a/src/libs/E2E/reactNativeLaunchingTest.js b/src/libs/E2E/reactNativeLaunchingTest.js index 392ed7c207b1..f0fa7200a3c9 100644 --- a/src/libs/E2E/reactNativeLaunchingTest.js +++ b/src/libs/E2E/reactNativeLaunchingTest.js @@ -12,7 +12,7 @@ Performance.markStart('regularAppStart'); import '../../../index'; Performance.markEnd('regularAppStart'); -import E2EConfig from '../../../e2e/config'; +import E2EConfig from '../../../tests/e2e/config'; import E2EClient from './client'; console.debug('=========================='); diff --git a/e2e/.env.e2e b/tests/e2e/.env.e2e similarity index 100% rename from e2e/.env.e2e rename to tests/e2e/.env.e2e diff --git a/e2e/ADDING_TESTS.md b/tests/e2e/ADDING_TESTS.md similarity index 100% rename from e2e/ADDING_TESTS.md rename to tests/e2e/ADDING_TESTS.md diff --git a/e2e/README.md b/tests/e2e/README.md similarity index 98% rename from e2e/README.md rename to tests/e2e/README.md index 72cc4fead9a1..be45d677bae7 100644 --- a/e2e/README.md +++ b/tests/e2e/README.md @@ -32,7 +32,7 @@ It will run the tests of a test case multiple time to average out the results. ## Adding tests -To add a test checkout the [designed guide](./ADDING_TESTS.md). +To add a test checkout the [designed guide](tests/e2e/ADDING_TESTS.md). ## Structure diff --git a/tests/e2e/TestSpec.yml b/tests/e2e/TestSpec.yml new file mode 100644 index 000000000000..36a6e784727b --- /dev/null +++ b/tests/e2e/TestSpec.yml @@ -0,0 +1,26 @@ +version: 0.1 + +phases: + install: + commands: + # Install correct version of node + - export NVM_DIR=$HOME/.nvm + - . $NVM_DIR/nvm.sh + - nvm install 16.15.1 + - nvm use 16.15.1 + + # Reverse ports using AWS magic + - PORT=4723 + - IP_ADDRESS=$(ip -4 addr show eth0 | grep -Po "(?<=inet\s)\d+(\.\d+){3}") + - reverse_values="{\"ip_address\":\"$IP_ADDRESS\",\"local_port\":\"$PORT\",\"remote_port\":\"$PORT\"}" + - "curl -H \"Content-Type: application/json\" -X POST -d \"$reverse_values\" http://localhost:31007/reverse_forward_tcp" + - adb reverse tcp:$PORT tcp:$PORT + + test: + commands: + - cd zip + - npm install underscore + - node e2e/testRunner.js -- --skipInstallDeps --skipBuild + +artifacts: +- $WORKING_DIRECTORY diff --git a/e2e/compare/compare.js b/tests/e2e/compare/compare.js similarity index 100% rename from e2e/compare/compare.js rename to tests/e2e/compare/compare.js diff --git a/e2e/compare/math.js b/tests/e2e/compare/math.js similarity index 100% rename from e2e/compare/math.js rename to tests/e2e/compare/math.js diff --git a/e2e/compare/output/console.js b/tests/e2e/compare/output/console.js similarity index 100% rename from e2e/compare/output/console.js rename to tests/e2e/compare/output/console.js diff --git a/e2e/compare/output/format.js b/tests/e2e/compare/output/format.js similarity index 96% rename from e2e/compare/output/format.js rename to tests/e2e/compare/output/format.js index e4668b38fa52..38a48984a2e7 100644 --- a/e2e/compare/output/format.js +++ b/tests/e2e/compare/output/format.js @@ -17,7 +17,7 @@ const formatPercentChange = (value) => { return `${value >= 0 ? '+' : '-'}${formatPercent(absValue)}`; }; -const formatDuration = duration => `${duration.toFixed(1)} ms`; +const formatDuration = duration => `${duration.toFixed(3)} ms`; const formatDurationChange = (value) => { if (value > 0) { diff --git a/e2e/compare/output/markdown.js b/tests/e2e/compare/output/markdown.js similarity index 98% rename from e2e/compare/output/markdown.js rename to tests/e2e/compare/output/markdown.js index 5f685550112d..b0563ac2c966 100644 --- a/e2e/compare/output/markdown.js +++ b/tests/e2e/compare/output/markdown.js @@ -57,7 +57,7 @@ const buildSummaryTable = (entries, collapse = false) => { }; const buildMarkdown = (data) => { - let result = '# Performance Comparison Report'; + let result = '## Performance Comparison Report 📊'; if (data.errors && data.errors.length) { result += '\n\n### Errors\n'; diff --git a/e2e/compare/output/markdownTable.js b/tests/e2e/compare/output/markdownTable.js similarity index 100% rename from e2e/compare/output/markdownTable.js rename to tests/e2e/compare/output/markdownTable.js diff --git a/e2e/config.js b/tests/e2e/config.js similarity index 91% rename from e2e/config.js rename to tests/e2e/config.js index 6e8b8793dcd3..aad6b742b582 100644 --- a/e2e/config.js +++ b/tests/e2e/config.js @@ -1,4 +1,4 @@ -const OUTPUT_DIR = 'e2e/.results'; +const OUTPUT_DIR = process.env.WORKING_DIRECTORY || './results'; /** * @typedef TestConfig @@ -14,7 +14,7 @@ module.exports = { APP_PACKAGE: 'com.expensify.chat', // The port of the testing server that communicates with the app - SERVER_PORT: 3000, + SERVER_PORT: 4723, // The amount of times a test should be executed for average performance metrics RUNS: 30, @@ -36,7 +36,7 @@ module.exports = { LOG_FILE: `${OUTPUT_DIR}/debug.log`, // The time in milliseconds after which an operation fails due to timeout - INTERACTION_TIMEOUT: 30_000, + INTERACTION_TIMEOUT: 300000, TEST_NAMES, diff --git a/tests/e2e/measure/math.js b/tests/e2e/measure/math.js new file mode 100644 index 000000000000..058b67548d79 --- /dev/null +++ b/tests/e2e/measure/math.js @@ -0,0 +1,46 @@ +const _ = require('underscore'); + +const filterOutliersViaIQR = (data) => { + let q1; + let q3; + + const values = data.slice().sort((a, b) => a - b); + + if ((values.length / 4) % 1 === 0) { + q1 = (1 / 2) * (values[(values.length / 4)] + values[(values.length / 4) + 1]); + q3 = (1 / 2) * (values[(values.length * (3 / 4))] + values[(values.length * (3 / 4)) + 1]); + } else { + q1 = values[Math.floor((values.length / 4) + 1)]; + q3 = values[Math.ceil((values.length * (3 / 4)) + 1)]; + } + + const iqr = q3 - q1; + const maxValue = q3 + (iqr * 1.5); + const minValue = q1 - (iqr * 1.5); + + return _.filter(values, x => (x >= minValue) && (x <= maxValue)); +}; + +const mean = arr => _.reduce(arr, (a, b) => a + b, 0) / arr.length; + +const std = (arr) => { + const avg = mean(arr); + return Math.sqrt(_.reduce(_.map(arr, i => (i - avg) ** 2), (a, b) => a + b) / arr.length); +}; + +const getStats = (entries) => { + const cleanedEntries = filterOutliersViaIQR(entries); + const meanDuration = mean(cleanedEntries); + const stdevDuration = std(cleanedEntries); + + return { + mean: meanDuration, + stdev: stdevDuration, + runs: cleanedEntries.length, + entries: cleanedEntries, + }; +}; + +module.exports = { + getStats, +}; diff --git a/e2e/measure/writeTestStats.js b/tests/e2e/measure/writeTestStats.js similarity index 100% rename from e2e/measure/writeTestStats.js rename to tests/e2e/measure/writeTestStats.js diff --git a/e2e/server/index.js b/tests/e2e/server/index.js similarity index 100% rename from e2e/server/index.js rename to tests/e2e/server/index.js diff --git a/e2e/server/routes.js b/tests/e2e/server/routes.js similarity index 100% rename from e2e/server/routes.js rename to tests/e2e/server/routes.js diff --git a/e2e/testRunner.js b/tests/e2e/testRunner.js similarity index 87% rename from e2e/testRunner.js rename to tests/e2e/testRunner.js index 6884cda5831d..a5165db1ac67 100644 --- a/e2e/testRunner.js +++ b/tests/e2e/testRunner.js @@ -24,11 +24,9 @@ const killApp = require('./utils/killApp'); const launchApp = require('./utils/launchApp'); const createServerInstance = require('./server'); const installApp = require('./utils/installApp'); -const reversePort = require('./utils/androidReversePort'); const math = require('./measure/math'); const writeTestStats = require('./measure/writeTestStats'); const withFailTimeout = require('./utils/withFailTimeout'); -const startRecordingVideo = require('./utils/startRecordingVideo'); const args = process.argv.slice(2); @@ -51,27 +49,26 @@ const restartApp = async () => { }; const runTestsOnBranch = async (branch, baselineOrCompare) => { - // Switch branch and install dependencies - const progress = Logger.progressInfo(`Preparing ${baselineOrCompare} tests on branch '${branch}'`); - await execAsync(`git switch ${branch}`); + if (!args.includes('--skipInstallDeps') && !args.includes('--skipBuild')) { + // Switch branch and install dependencies + Logger.log(`Preparing ${baselineOrCompare} tests on branch '${branch}'`); + await execAsync(`git checkout ${branch}`); + } if (!args.includes('--skipInstallDeps')) { - progress.updateText(`Preparing ${baselineOrCompare} tests on branch '${branch}' - npm install`); + Logger.log(`Preparing ${baselineOrCompare} tests on branch '${branch}' - npm install`); await execAsync('npm i'); } // Build app if (!args.includes('--skipBuild')) { - progress.updateText(`Preparing ${baselineOrCompare} tests on branch '${branch}' - building app`); + Logger.log(`Preparing ${baselineOrCompare} tests on branch '${branch}' - building app`); await execAsync('npm run android-build-e2e'); } - progress.done(); - // Install app and reverse ports + // Install app let progressLog = Logger.progressInfo('Installing app'); - await installApp('android'); - Logger.log('Reversing port (for connecting to testing server) …'); - await reversePort(); + await installApp('android', baselineOrCompare); progressLog.done(); // Start the HTTP server @@ -123,8 +120,6 @@ const runTestsOnBranch = async (branch, baselineOrCompare) => { const progressText = `(${testIndex + 1}/${numOfTests}) Running test '${config.name}' (iteration ${i + 1}/${RUNS})`; testLog.updateText(progressText); - const stopVideoRecording = startRecordingVideo(); - await restartApp(); // Wait for a test to finish by waiting on its done call to the http server @@ -136,10 +131,8 @@ const runTestsOnBranch = async (branch, baselineOrCompare) => { resolve(); }); }), progressText); - await stopVideoRecording(false); } catch (e) { // When we fail due to a timeout it's interesting to take a screenshot of the emulator to see whats going on - await stopVideoRecording(true); testLog.done(); throw e; // Rethrow to abort execution } diff --git a/e2e/utils/execAsync.js b/tests/e2e/utils/execAsync.js similarity index 100% rename from e2e/utils/execAsync.js rename to tests/e2e/utils/execAsync.js diff --git a/e2e/utils/installApp.js b/tests/e2e/utils/installApp.js similarity index 58% rename from e2e/utils/installApp.js rename to tests/e2e/utils/installApp.js index 79d3bb1638e2..32c2f44c9533 100644 --- a/e2e/utils/installApp.js +++ b/tests/e2e/utils/installApp.js @@ -2,22 +2,27 @@ const {APP_PACKAGE} = require('../config'); const execAsync = require('./execAsync'); const Logger = require('./logger'); -const APP_PATH_FROM_ROOT = 'android/app/build/outputs/apk/e2eRelease/app-e2eRelease.apk'; +const BASELINE_APP_PATH_FROM_ROOT = './app-e2eRelease-baseline.apk'; +const COMPARE_APP_PATH_FROM_ROOT = './app-e2eRelease-compare.apk'; /** * Installs the app on the currently connected device for the given platform. * It removes the app first if it already exists, so it's a clean installation. - * @param {string} platform + * + * @param {String} platform + * @param {String} baselineOrCompare * @returns {Promise} */ -module.exports = function (platform = 'android') { +module.exports = function (platform = 'android', baselineOrCompare = 'baseline') { if (platform !== 'android') { throw new Error(`installApp() missing implementation for platform: ${platform}`); } + const apk = baselineOrCompare === 'baseline' ? BASELINE_APP_PATH_FROM_ROOT : COMPARE_APP_PATH_FROM_ROOT; + // Uninstall first, then install return execAsync(`adb uninstall ${APP_PACKAGE}`).catch((e) => { // Ignore errors Logger.warn('Failed to uninstall app:', e); - }).finally(() => execAsync(`adb install ${APP_PATH_FROM_ROOT}`)); + }).finally(() => execAsync(`adb install ${apk}`)); }; diff --git a/e2e/utils/killApp.js b/tests/e2e/utils/killApp.js similarity index 100% rename from e2e/utils/killApp.js rename to tests/e2e/utils/killApp.js diff --git a/e2e/utils/launchApp.js b/tests/e2e/utils/launchApp.js similarity index 100% rename from e2e/utils/launchApp.js rename to tests/e2e/utils/launchApp.js diff --git a/e2e/utils/logger.js b/tests/e2e/utils/logger.js similarity index 98% rename from e2e/utils/logger.js rename to tests/e2e/utils/logger.js index cbe76bbaf300..a12fecd1efad 100644 --- a/e2e/utils/logger.js +++ b/tests/e2e/utils/logger.js @@ -1,7 +1,7 @@ const fs = require('fs'); const {LOG_FILE} = require('../config'); -let isVerbose = false; +let isVerbose = true; const setLogLevelVerbose = (value) => { isVerbose = value; }; diff --git a/e2e/utils/withFailTimeout.js b/tests/e2e/utils/withFailTimeout.js similarity index 100% rename from e2e/utils/withFailTimeout.js rename to tests/e2e/utils/withFailTimeout.js