diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 5f308e7..864e05d 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -6,126 +6,182 @@ jobs: ios_e2e: runs-on: macos-latest env: - EXPO_NO_WAIT: true CI: true + MAESTRO_CLI_NO_ANALYTICS: true + timeout-minutes: 30 steps: - name: Checkout repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 20 + cache: "npm" + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: zulu + java-version: 21 - name: Install dependencies run: npm ci - - name: Install Expo CLI - run: npm install -g expo-cli + - name: Install Maestro + run: | + curl -Ls "https://get.maestro.mobile.dev" | bash + echo "$HOME/.maestro/bin" >> $GITHUB_PATH - name: Install applesimutils run: | brew tap wix/brew brew install applesimutils + - name: Install CocoaPods dependencies + run: | + cd ios + pod install + cd .. + + - name: Boot iOS Simulator + run: | + xcrun simctl shutdown all + xcrun simctl boot "iPhone 16" + xcrun simctl list devices | grep Booted + sleep 10 + + - name: Build and install iOS app + run: | + xcodebuild -scheme MyTestLibraryExample \ + -workspace ios/MyTestLibraryExample.xcworkspace \ + -configuration Debug \ + -sdk iphonesimulator \ + -derivedDataPath build \ + -arch arm64 + + xcrun simctl install booted build/Build/Products/Debug-iphonesimulator/MyTestLibraryExample.app + + - name: Run iOS Maestro tests + run: | + # Create reports directory + mkdir -p e2e/reports + + # Start Metro bundler for tests + npm start & + METRO_PID=$! + sleep 10 + + # Run the app + xcrun simctl launch booted mytestlibrary.example || true + sleep 10 + + # Run the tests + npm run e2e:test + + # Clean up + kill $METRO_PID 2>/dev/null || true + pkill -f "Metro" || true + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: e2e-test-results-ios + path: e2e/reports/ + + - name: Upload screenshots + uses: actions/upload-artifact@v4 + if: failure() + with: + name: e2e-screenshots-ios + path: e2e/reports/screenshots/ + + android_e2e: + runs-on: ubuntu-latest + env: + CI: true + MAESTRO_CLI_NO_ANALYTICS: true + timeout-minutes: 30 + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: "npm" + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: zulu + java-version: 21 + + - name: Install dependencies + run: npm ci + + - name: Install Maestro + run: | + curl -Ls "https://get.maestro.mobile.dev" | bash + echo "$HOME/.maestro/bin" >> $GITHUB_PATH + + - name: Android SDK + uses: android-actions/setup-android@v3 + + - name: Setup NDK + uses: nttld/setup-ndk@v1 + with: + ndk-version: r26d + - name: Prebuild - run: npm run prebuild - - # Build iOS and Android apps for E2E - - name: Build iOS for Detox - run: npm run e2e:build:ios - - # Start Metro bundler in background - - name: Start Metro bundler - run: npx expo start --dev-client --port 8081 & - - # Give Metro a few seconds to boot before launching Detox - - name: Wait for Metro - run: sleep 20 - - - name: Run iOS Detox tests - run: npm run e2e:test:ios - - # TODO: Fix android e2e tests (No space left on device) - # android_e2e: - # runs-on: ubuntu-latest - # env: - # EXPO_NO_WAIT: true - # CI: true - - # steps: - # - name: Checkout repo - # uses: actions/checkout@v3 - - # - name: Set up Node.js - # uses: actions/setup-node@v3 - # with: - # node-version: 20 - - # - name: Install dependencies - # run: npm ci - - # - name: Install Expo CLI - # run: npm install -g expo-cli - - # - name: Android SDK - # uses: android-actions/setup-android@v3 - - # - name: Setup NDK - # uses: nttld/setup-ndk@v1 - # with: - # ndk-version: r26d - - # - name: Prebuild - # run: npm run prebuild - - # - name: Start Metro bundler - # run: npx expo start --dev-client --port 8081 & - - # - name: Wait for Metro - # run: | - # for i in {1..30}; do - # if nc -z localhost 8081; then - # echo "Metro is up!" - # break - # fi - # echo "Waiting for Metro..." - # sleep 2 - # done - - # - name: Enable KVM (faster emulator) - # 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: Clean workspace - # run: | - # echo "Cleaning unused caches..." - # sudo rm -rf $HOME/.gradle/caches/ - # sudo rm -rf $HOME/.android/build-cache/ - # sudo rm -rf android/app/build/ - # sudo rm -rf /tmp/* - # sudo rm -rf /usr/local/lib/android/sdk/system-images - - # - name: Build Android for Detox - # uses: reactivecircus/android-emulator-runner@v2 - # with: - # api-level: 29 - # target: google_apis - # arch: x86_64 - # disable-animations: true - # disk-size: 2048M - # emulator-options: "-no-snapshot -no-boot-anim -no-window -gpu swiftshader_indirect -memory 4096 -cores 4" - # script: npm run e2e:build:android - - # - name: Run Android Detox tests - # uses: reactivecircus/android-emulator-runner@v2 - # with: - # api-level: 29 - # target: google_apis - # arch: x86_64 - # disable-animations: true - # disk-size: 2048M - # emulator-options: "-no-snapshot -no-boot-anim -no-window -gpu swiftshader_indirect -memory 4096 -cores 4" - # script: npm run e2e:build:android \ No newline at end of file + run: npm run assets + + - name: Enable KVM (faster emulator) + 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: Free up disk space + run: | + sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/share/boost + sudo rm -rf $ANDROID_HOME/ndk || true + docker system prune -af || true + + - name: Build Debug APK + run: | + npm run build:android + + - name: Test Android with Maestro + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 29 + target: default + arch: x86_64 + disable-animations: true + emulator-options: "-no-snapshot -no-boot-anim -no-window -gpu swiftshader_indirect -memory 2048 -cores 2" + script: | + # Start Metro bundler for tests + npm start & METRO_PID=$! + + # Build and install the app + cd android && ./gradlew assembleDebug && cd .. + + # Install the app + adb install -r android/app/build/outputs/apk/debug/app-debug.apk + + # Run the tests + npm run e2e:test + + # Clean up + kill $METRO_PID 2>/dev/null || true && pkill -f "Metro" || true + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: e2e-test-results-android + path: e2e/reports/ + diff --git a/.gitignore b/.gitignore index 1a49203..dbbcad7 100644 --- a/.gitignore +++ b/.gitignore @@ -19,10 +19,13 @@ web-build/ expo-env.d.ts # @end expo-cli +build/ ios/build ios/.xcode.env.local ios/Pods MoproReactNativeBindings/android/build/ MoproReactNativeBindings/android/.gradle/ MoproReactNativeBindings/android/.cxx/ -MoproReactNativeBindings/lib \ No newline at end of file +MoproReactNativeBindings/lib + +e2e/reports/ \ No newline at end of file diff --git a/MoproReactNativeBindings/android/src/main/jniLibs/x86_64/libmopro_example_app.a b/MoproReactNativeBindings/android/src/main/jniLibs/x86_64/libmopro_example_app.a new file mode 100644 index 0000000..593f063 Binary files /dev/null and b/MoproReactNativeBindings/android/src/main/jniLibs/x86_64/libmopro_example_app.a differ diff --git a/e2e/README.md b/e2e/README.md new file mode 100644 index 0000000..fd12d9c --- /dev/null +++ b/e2e/README.md @@ -0,0 +1,127 @@ +# E2E Tests for React Native Proof App + +This directory contains end-to-end (E2E) tests for the React Native cryptographic proof generation app using Maestro. + +## Test Structure + +- `tests/` - Contains individual test files + - `circom-proof-test.yaml` - Tests for Circom proof generation and verification +- `maestro.yaml` - Global Maestro configuration +- `reports/` - Test execution reports (generated automatically) + +## Prerequisites + +1. **Maestro CLI** - Install using: + + ```bash + curl -Ls "https://get.maestro.mobile.dev" | bash + export PATH="$PATH":"$HOME/.maestro/bin" + ``` + +2. **iOS Simulator** (for iOS tests): + - Xcode installed + - iOS Simulator running + - App built and installed + +3. **Android Emulator** (for Android tests): + - Android Studio installed + - Android Emulator running + - App built and installed + +## Running Tests + +### Run All Tests + +```bash +# From project root +npm run e2e:test +``` + +### Run Individual Tests + +```bash +# Run specific test +maestro test e2e/tests/circom-proof-test.yaml +``` + +### Run Tests with Different Configurations + +```bash +# Run with custom device +maestro test e2e/tests/ --device "iPhone 14 Pro" + +# Run with verbose output +maestro test e2e/tests/ --verbose + +# Run and generate reports +maestro test e2e/tests/ --format junit --output e2e/reports/ +``` + +## Test Coverage + +The E2E tests cover: + +1. **App Launch** - Verifies app starts correctly +2. **Tab Navigation** - Tests switching between Circom, Halo2, and Noir tabs +3. **Input Validation** - Tests text input fields for each proof type +4. **Proof Generation** - Tests proof generation functionality +5. **Proof Verification** - Tests proof verification functionality +6. **UI Elements** - Verifies all UI components are visible and interactive +7. **Complete Workflow** - Tests end-to-end user journey + +## Test Data + +Tests use various input values to verify proof generation: + +- Circom: a=3, b=4 and a=5, b=6 +- Halo2: out=55 and out=89 +- Noir: a=7, b=8 and a=2, b=3 + +## Troubleshooting + +### Common Issues + +1. **App not found**: Ensure the app is built and installed on the device/simulator +2. **Element not found**: Check that testID attributes are correctly set in the app +3. **Timeout errors**: Increase timeout values in maestro.yaml +4. **Device not found**: Ensure simulator/emulator is running and accessible + +### Debug Mode + +Run tests in debug mode for detailed logging: + +```bash +maestro test e2e/tests/ --debug +``` + +### Screenshots + +Screenshots are automatically taken on test failures and saved to `e2e/reports/screenshots/` + +## CI/CD Integration + +The tests are designed to run in CI/CD pipelines. Use the following commands: + +```bash +# Install Maestro in CI +curl -Ls "https://get.maestro.mobile.dev" | bash +export PATH="$PATH":"$HOME/.maestro/bin" + +# Run tests +maestro test e2e/tests/ --format junit --output e2e/reports/ +``` + +## Adding New Tests + +1. Create a new `.yaml` file in the `tests/` directory +2. Follow the Maestro YAML syntax +3. Use existing testID attributes from the app +4. Add the test to the test suite by running it individually first +5. Update this README if needed + +## Test Maintenance + +- Update tests when UI changes +- Add new test cases for new features +- Remove obsolete tests +- Keep test data realistic and varied diff --git a/e2e/maestro.yaml b/e2e/maestro.yaml new file mode 100644 index 0000000..93a6146 --- /dev/null +++ b/e2e/maestro.yaml @@ -0,0 +1,55 @@ +# Maestro E2E Test Configuration +# This file contains global configuration for Maestro tests + +# Test suite configuration +testSuite: + name: "React Native Proof App E2E Tests" + description: "End-to-end tests for the cryptographic proof generation React Native app" + +# Global test settings +global: + # Timeout for individual actions (in milliseconds) + actionTimeout: 10000 + + # Timeout for assertions (in milliseconds) + assertionTimeout: 5000 + + # Screenshot settings + takeScreenshotOnFailure: true + takeScreenshotOnSuccess: false + + # Logging level + logLevel: "INFO" + +# Device configuration +device: + # iOS Simulator settings + ios: + deviceName: "iPhone 17" + osVersion: "26.0" + + # Android Emulator settings + android: + deviceName: "Pixel_8_API_35" + osVersion: "14" + +# Test execution settings +execution: + # Parallel execution settings + parallel: false + maxConcurrentTests: 1 + + # Retry settings + retryOnFailure: 2 + retryDelay: 5000 + +# Reporting settings +reporting: + # Generate HTML reports + generateHtmlReport: true + + # Generate JUnit XML reports for CI/CD + generateJunitReport: true + + # Report output directory + outputDir: "./e2e/reports" diff --git a/e2e/tests/circom-proof-test.yaml b/e2e/tests/circom-proof-test.yaml new file mode 100644 index 0000000..b95b8b5 --- /dev/null +++ b/e2e/tests/circom-proof-test.yaml @@ -0,0 +1,25 @@ +appId: mytestlibrary.example +--- +- launchApp +- extendedWaitUntil: + visible: "Circom Proof" + timeout: 60000 +- assertVisible: "Circom Proof" +- assertVisible: "Halo2 Proof" +- assertVisible: "Noir Proof" + +# Test Circom Proof functionality +- tapOn: + id: "circom-input-a" +- eraseText +- inputText: 5 +- tapOn: + id: "circom-input-b" +- eraseText +- inputText: 6 +- tapOn: + id: "circom-gen-proof-button" +- tapOn: + id: "circom-verify-proof-button" +- assertVisible: "true" +- assertVisible: '["30","5"]' diff --git a/package.json b/package.json index 8f1a71f..dc88ff3 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,9 @@ "android": "npm run assets && react-native run-android", "start": "react-native start", "build:android": "react-native build-android --extra-params \"--no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a\"", - "build:ios": "react-native build-ios --mode Debug" + "build:ios": "react-native build-ios --mode Debug", + "e2e:test": "maestro test e2e/tests/ --debug-output=e2e/reports", + "e2e:test:circom": "maestro test e2e/tests/circom-proof-test.yaml --debug-output=e2e/reports" }, "dependencies": { "@react-native/new-app-screen": "0.81.4",