diff --git a/.circleci/config.yml b/.circleci/config.yml index b716020d93..1c2edb80a7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,8 +1,70 @@ version: 2.1 orbs: - gh: circleci/github-cli@2.0 - node: circleci/node@5.1.1 + gh: circleci/github-cli@2.3.0 + node: circleci/node@5.2.0 + ruby: zebbra/ruby@0.0.2 + nix: eld/nix@1.1.1 + android: circleci/android@2.4.0 + rn: react-native-community/react-native@7.1.1 + docker: circleci/docker@2.4.0 + +commands: + install_java: + steps: + - run: + name: Install Java + command: | + sudo apt-get update && sudo apt-get install -y openjdk-8-jdk + sudo update-alternatives --set java /usr/lib/jvm/java-8-openjdk-amd64/bin/java + sudo update-alternatives --set javac /usr/lib/jvm/java-8-openjdk-amd64/bin/javac + java -version + install_yarn_deps: + steps: + - node/install: + install-yarn: true + node-version: '18.18.2' + - run: yarn global add node-gyp + - rn/yarn_install + prepare_ruby: + steps: + - add_ssh_keys: + fingerprints: + - "19:7e:f3:6c:be:a7:17:01:7d:09:ca:39:c3:98:86:90" + - run: brew update + - ruby/install-bundler + - restore_cache: + key: 1-gem-{{ checksum "ios/Gemfile.lock" }} + - run: cd ios && bundle config set deployment 'true' + - run: cd ios && bundle config set --local path 'vendor/bundle' + - run: cd ios && bundle check || bundle install + - run: gem install cocoapods + - save_cache: + key: 1-gem-{{ checksum "ios/Gemfile.lock" }} + paths: + - ios/vendor + metro_start: + steps: + - run: + command: yarn start + background: true + nix-with-flake: + steps: + - nix/install + - run: + name: Set up Nix environment for Flakes + command: | + mkdir -p ~/.config/nix + echo 'experimental-features = nix-command flakes' > ~/.config/nix/nix.conf + local_backend: + steps: + - run: + command: | + nix develop -c make tilt-up + background: true + wait_for_backend_ready: + steps: + - run: nix develop -c tilt wait --timeout 1h --for=condition=Ready uiresources init-setup parameters: version: @@ -28,9 +90,9 @@ parameters: default: "" jobs: - test_android_in_pr: + e2e_build_android: docker: - - image: cimg/android:2022.12 + - image: cimg/android:2022.12 resource_class: xlarge environment: TERM: dumb @@ -39,130 +101,68 @@ jobs: working_directory: ~/galoy-mobile shell: /bin/bash --login -o pipefail steps: - # if workflow was triggered by API then don't run the test jobs - - run: | - if [ << pipeline.trigger_source >> = "api" ]; then - circleci-agent step halt - fi - checkout: path: ~/galoy-mobile - - - node/install: - install-yarn: true - node-version: '18.18.2' - - - run: sudo apt-get install gcc g++ make - - run: gpg --keyserver keyserver.ubuntu.com --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB - - - restore_cache: - key: 1-gem-{{ checksum "android/Gemfile.lock" }} - - run: cd android && bundle config set deployment 'true' - - run: cd android && bundle check || bundle install - - save_cache: - key: 1-gem-{{ checksum "android/Gemfile.lock" }} + - install_java + - install_yarn_deps + - run: yarn e2e:build android.emu.debug + - persist_to_workspace: + root: ~/galoy-mobile paths: - - android/vendor - - - restore_cache: - key: yarn-{{ checksum "yarn.lock" }} - - run: yarn install - - save_cache: - key: yarn-{{ checksum "yarn.lock" }} - paths: - - node_modules - - run: echo $RELEASE_KEYSTORE | base64 -d > android/app/release.keystore - - run: yarn android:prepareAssets - - run: - name: Test Browserstack - command: | - set -o pipefail - cd android && bundle exec fastlane browserstack | tee browserstack_output.log - error_code=$? - SESSION_ID=$(cat browserstack_output.log | grep sessionId | head -n1 | sed -n "s/^.*'\(.*\)'.*$/\1/ p") - echo "Session ID" - echo $SESSION_ID - echo "Browserstack URL" - echo "https://app-automate.browserstack.com/dashboard/v2/builds/$BROWSERSTACK_ANDROID_BUILD/sessions/$SESSION_ID" - VIDEO_URL=$(curl -s -u "$BROWSERSTACK_USER:$BROWSERSTACK_ACCESS_KEY" -X GET "https://api-cloud.browserstack.com/app-automate/sessions/$SESSION_ID.json" | jq -r '.automation_session.video_url') - echo "Video URL" - echo $VIDEO_URL - exit $error_code + - android/app/build + e2e_run_android: + environment: + TEST_ENV: Local + executor: + name: android/android-machine + resource-class: xlarge + tag: 2023.11.1 + working_directory: ~/galoy-mobile + steps: + - checkout: + path: ~/galoy-mobile + - attach_workspace: + at: ~/galoy-mobile + - install_yarn_deps + - nix-with-flake + - local_backend + - android/create-avd: + avd-name: Pixel_API_34_AOSP + install: true + system-image: system-images;android-34;default;x86_64 + - android/start-emulator: + avd-name: Pixel_API_34_AOSP + no-window: true + post-emulator-launch-assemble-command: "" + - metro_start + - wait_for_backend_ready + - run: yarn e2e:test android.emu.debug -d -R 5 --take-screenshots all --record-videos all --record-logs all --headless + - store_artifacts: + path: artifacts - test_ios_in_pr: + e2e_ios: + environment: + GALOY_STAGING_GLOBAL_OTP: 403370 macos: xcode: 14.2.0 resource_class: macos.x86.medium.gen2 working_directory: ~/galoy-mobile - environment: - FL_OUTPUT_DIR: output shell: /bin/bash --login -o pipefail steps: - # if workflow was triggered by API then don't run the test jobs - - run: | - if [ << pipeline.trigger_source >> = "api" ]; then - circleci-agent step halt - fi - checkout: path: ~/galoy-mobile - - - run: - name: Check Ruby version - command: | - rbenv versions - echo "Ruby version in .ruby-version:" - cat .ruby-version - echo "Ruby version in Gemfile:" - grep -E "^ruby" Gemfile - - - run: - name: Install Bundler 2.2.30 - command: | - gem install bundler:2.2.30 - echo "export BUNDLE_PATH=$(bundle show --path)" >> $BASH_ENV - source $BASH_ENV - - - add_ssh_keys: - fingerprints: - - "19:7e:f3:6c:be:a7:17:01:7d:09:ca:39:c3:98:86:90" - - restore_cache: - key: 1-gem-{{ checksum "ios/Gemfile.lock" }} - - run: cd ios && bundle config set deployment 'true' - - run: cd ios && bundle config set --local path 'vendor/bundle' - - run: cd ios && bundle check || bundle install - - run: gem install cocoapods - - save_cache: - key: 1-gem-{{ checksum "ios/Gemfile.lock" }} - paths: - - ios/vendor - - restore_cache: - key: 1-yarn-{{ checksum "yarn.lock" }}-pod1-{{ checksum "ios/Podfile.lock" }} - - run: yarn install - - save_cache: - key: 1-yarn-{{ checksum "yarn.lock" }}-pod1-{{ checksum "ios/Podfile.lock" }} - paths: - - node_modules - - ios/Pods - - run: - name: Import Apple Certificates - command: | - tmpfile=$(mktemp /tmp/wwdr-cert.cer) - curl -f -o $tmpfile https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer && security import $tmpfile ~/Library/Keychains/login.keychain-db - - run: - name: Browserstack Testing - command: | - set -o pipefail - cd ios && bundle exec fastlane browserstack | tee browserstack_output.log - error_code=$? - SESSION_ID=$(cat browserstack_output.log | grep sessionId | head -n1 | sed -n "s/^.*'\(.*\)'.*$/\1/ p") - echo "Session ID" - echo $SESSION_ID - echo "Browserstack URL" - echo "https://app-automate.browserstack.com/dashboard/v2/builds/$BROWSERSTACK_IOS_BUILD/sessions/$SESSION_ID" - VIDEO_URL=$(curl -s -u "$BROWSERSTACK_USER:$BROWSERSTACK_ACCESS_KEY" -X GET "https://api-cloud.browserstack.com/app-automate/sessions/$SESSION_ID.json" | jq -r '.automation_session.video_url') - echo "Video URL" - echo $VIDEO_URL - exit $error_code - no_output_timeout: 15m + - run: | + brew tap wix/brew + brew install applesimutils + - prepare_ruby + - install_yarn_deps + - metro_start + - run: yarn e2e:build ios.sim.debug + - rn/ios_simulator_start: + device: iPhone SE (3rd generation) + - run: yarn e2e:test ios.sim.debug -d -R 5 --take-screenshots all --record-videos all --record-logs all + - store_artifacts: + path: artifacts build_android: docker: @@ -242,12 +242,7 @@ jobs: - checkout: path: ~/galoy-mobile - run: git checkout << pipeline.parameters.git_ref >> - - run: - name: Install Bundler 2.2.30 - command: | - gem install bundler:2.2.30 - echo "export BUNDLE_PATH=$(bundle show --path)" >> $BASH_ENV - source $BASH_ENV + - ruby/install-bundler - add_ssh_keys: fingerprints: - "19:7e:f3:6c:be:a7:17:01:7d:09:ca:39:c3:98:86:90" @@ -354,5 +349,8 @@ workflows: not: equal: [main, << pipeline.git.branch >>] jobs: - - test_android_in_pr - - test_ios_in_pr + - e2e_ios + - e2e_build_android + - e2e_run_android: + requires: + - e2e_build_android diff --git a/.detoxrc.js b/.detoxrc.js new file mode 100644 index 0000000000..be55bbffa4 --- /dev/null +++ b/.detoxrc.js @@ -0,0 +1,53 @@ +/** @type {Detox.DetoxConfig} */ +module.exports = { + testRunner: { + args: { + $0: "jest", + config: "e2e/detox/jest.config.js", + }, + jest: { + setupTimeout: 120000, + }, + }, + apps: { + "ios.debug": { + type: "ios.app", + binaryPath: "ios/build/Build/Products/Debug-iphonesimulator/Blink.app", + build: + "xcodebuild -workspace ios/GaloyApp.xcworkspace -scheme GaloyApp -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build", + }, + "android.debug": { + type: "android.apk", + binaryPath: "android/app/build/outputs/apk/debug/app-universal-debug.apk", + testBinaryPath: + "android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk", + build: + "cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug", + reversePorts: [8081], + }, + }, + devices: { + simulator: { + type: "ios.simulator", + device: { + type: "iPhone SE (3rd generation)", + }, + }, + emulator: { + type: "android.emulator", + device: { + avdName: "Pixel_API_34_AOSP", + }, + }, + }, + configurations: { + "ios.sim.debug": { + device: "simulator", + app: "ios.debug", + }, + "android.emu.debug": { + device: "emulator", + app: "android.debug", + }, + }, +} diff --git a/.envrc b/.envrc new file mode 100644 index 0000000000..c7734529fc --- /dev/null +++ b/.envrc @@ -0,0 +1,7 @@ +if [ -f ".env.local" ]; then + dotenv .env.local +fi + +use flake . + +export TEST_ENV="Local" diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000000..b50993e703 --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,14 @@ +name: temp_test_deleteme +on: + pull_request: + branches: [main] +jobs: + testing123: + runs-on: macos-11 + steps: + - run: brew install --cask docker + - run: xattr -d -r com.apple.quarantine /Applications/Docker.app + - run: open -g /Applications/Docker.app --args --unattended --install-privileged-components --accept-license + - run: while ! docker system info &>/dev/null; do sleep 1; done + timeout-minutes: 5 + - run: docker system info diff --git a/.gitignore b/.gitignore index 19bd1ac29b..d0bb71eb19 100644 --- a/.gitignore +++ b/.gitignore @@ -74,9 +74,8 @@ ios/GoogleService-Info.plist yalc.lock *.log - -.env -.envrc +.env.local +.env.tmp.ci .dependencies ios/assets ios/GaloyApp/assets @@ -86,4 +85,8 @@ android/app/src/main/assets .metro-health-check* # testing -/coverage \ No newline at end of file +/coverage + +artifacts + +.direnv diff --git a/Makefile b/Makefile index c0636342db..a4a3d85d61 100644 --- a/Makefile +++ b/Makefile @@ -18,3 +18,9 @@ reset-ios: start: yarn start + +tilt-up: + cd dev && GALOY_QUICKSTART_PATH="dev/vendor/galoy-quickstart" tilt up + +tilt-down: + cd dev && GALOY_QUICKSTART_PATH="dev/vendor/galoy-quickstart" tilt down diff --git a/android/app/build.gradle b/android/app/build.gradle index 7b9446751e..6fae56e739 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -90,6 +90,8 @@ android { targetSdkVersion rootProject.ext.targetSdkVersion versionCode 676 versionName "2.2.220" + testBuildType System.getProperty('testBuildType', 'debug') + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } // https://github.com/react-native-community/discussions-and-proposals/issues/602 @@ -120,6 +122,7 @@ android { signingConfig signingConfigs.release minifyEnabled enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" + proguardFile "${rootProject.projectDir}/../node_modules/detox/android/detox/proguard-rules-app.pro" } } @@ -157,6 +160,9 @@ dependencies { } else { implementation jscFlavor } + + androidTestImplementation('com.wix:detox:+') + implementation 'androidx.appcompat:appcompat:1.1.0' } apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) diff --git a/android/app/src/androidTest/java/com/galoyapp/DetoxTest.java b/android/app/src/androidTest/java/com/galoyapp/DetoxTest.java new file mode 100644 index 0000000000..63163c8ac4 --- /dev/null +++ b/android/app/src/androidTest/java/com/galoyapp/DetoxTest.java @@ -0,0 +1,29 @@ +package com.galoyapp; // (1) + +import com.wix.detox.Detox; +import com.wix.detox.config.DetoxConfig; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.LargeTest; +import androidx.test.rule.ActivityTestRule; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class DetoxTest { + @Rule // (2) + public ActivityTestRule mActivityRule = new ActivityTestRule<>(MainActivity.class, false, false); + + @Test + public void runDetoxTests() { + DetoxConfig detoxConfig = new DetoxConfig(); + detoxConfig.idlePolicyConfig.masterTimeoutSec = 90; + detoxConfig.idlePolicyConfig.idleResourceTimeoutSec = 60; + detoxConfig.rnContextLoadTimeoutSec = (BuildConfig.DEBUG ? 180 : 60); + + Detox.runTests(mActivityRule, detoxConfig); + } +} diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index b21d1bf2c8..6ea899939c 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -32,6 +32,7 @@ android:roundIcon="@mipmap/ic_launcher" android:allowBackup="false" android:usesCleartextTraffic="true" + android:networkSecurityConfig="@xml/network_security_config" android:theme="@style/BootTheme"> + + + 10.0.2.2 + localhost + + diff --git a/android/build.gradle b/android/build.gradle index 362f661769..8804d2d9f6 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -26,5 +26,16 @@ buildscript { classpath 'com.google.gms:google-services:4.3.15' // firebase classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2' + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") + } +} + +allprojects { + repositories { + google() + mavenCentral() + maven { + url "$rootDir/../node_modules/detox/Detox-android" + } } } diff --git a/app/navigation/root-navigator.tsx b/app/navigation/root-navigator.tsx index 58e21a7216..49ec6b48fc 100644 --- a/app/navigation/root-navigator.tsx +++ b/app/navigation/root-navigator.tsx @@ -102,6 +102,7 @@ export const RootStack = () => { headerTitleStyle: styles.title, headerBackTitleStyle: styles.title, headerTintColor: colors.black, + headerBackTestID: LL.common.back(), }} initialRouteName={isAuthed ? "authenticationCheck" : "getStarted"} > @@ -428,6 +429,7 @@ export const ContactNavigator = () => { screenOptions={{ gestureEnabled: true, headerBackTitle: LL.common.back(), + headerBackTestID: LL.common.back(), headerStyle: styles.headerStyle, headerTitleStyle: styles.title, headerBackTitleStyle: styles.title, @@ -484,6 +486,7 @@ export const PhoneLoginNavigator = () => { screenOptions={{ gestureEnabled: true, headerBackTitle: LL.common.back(), + headerBackTestID: LL.common.back(), headerStyle: styles.headerStyle, headerTitleStyle: styles.title, headerBackTitleStyle: styles.title, diff --git a/app/screens/developer-screen/developer-screen.tsx b/app/screens/developer-screen/developer-screen.tsx index d299d40f95..c67c66196f 100644 --- a/app/screens/developer-screen/developer-screen.tsx +++ b/app/screens/developer-screen/developer-screen.tsx @@ -202,7 +202,7 @@ export const DeveloperScreen: React.FC = () => { return ( - +