mirror of
https://github.com/selfxyz/self.git
synced 2026-01-09 06:38:09 -05:00
Feat: Lightweight e2e tests for iOS and Android (#840)
* Add Maestro e2e testing * Run Maestro flows in parallel * Fix mobile e2e workflow * Fix e2e script flow path * prettier * fix * prettier * standardize yml files and new formatting commands * fix ndk * fix exclusions * use double quotes for yml files * feedback * fixes * fixes * fix * fix ios job * unneeded * fix workflows * fix launch workflow * fix * fix pipeline * workflow fixes * install app to emulators * better logging * save current version of test script * android works. ios wip. update locks * fix pipelines * cr feedback * fix android e2e test * Split mobile e2e workflow by platform (#842) * Replace react-native-quick-crypto with @noble/hashes (#841) * Add tests for ethers polyfills * Add crypto utils * Inline crypto polyfills into ethers util * sort and update gemfile lock * update lock * chore: incrementing ios build number for version 2.6.3 [github action] * android works. ios wip. update locks * Specify Maestro platform * Fix Android build step in e2e workflow * fix android * update ios * add concurrency * update Podfile.lock * fix android * prettier * fix * fix android pipeline * try job again * fix ios * fix android * fix ios * fix command * use android runner now that path is fixed * fix android e2e test * fix adb * add caching * fix build * speed up build * fix * test emulator options * updates * fix pipeline * fix * fix script and move on * add comment --------- Co-authored-by: Self GitHub Actions <action@github.com> * feedback * fixes * ignore for now * ignore * fix tests * fix ios simulator booting * fix ios test * shutdown after run * fix ios test * better timing * increase ios timeout * fix both flows * fix pipeline * combine command * fix ios * break up build steps for better caching * remove cache * fix ios and android test pipelines * update logic --------- Co-authored-by: Self GitHub Actions <action@github.com>
This commit is contained in:
@@ -69,10 +69,6 @@ app/android/build/
|
||||
app/ios/build/
|
||||
app/ios/Pods/
|
||||
app/ios/DerivedData/
|
||||
app/android/.gradle/
|
||||
app/android/gradle/
|
||||
app/android/gradlew
|
||||
app/android/gradlew.bat
|
||||
|
||||
# Circuit build outputs
|
||||
circuits/build/
|
||||
|
||||
2
.github/actions/get-version/action.yml
vendored
2
.github/actions/get-version/action.yml
vendored
@@ -14,4 +14,4 @@ runs:
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION=$(node -p "require('${{ inputs.app_path }}/package.json').version")
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
|
||||
9
.github/actions/mobile-setup/action.yml
vendored
9
.github/actions/mobile-setup/action.yml
vendored
@@ -34,7 +34,6 @@ runs:
|
||||
sudo locale-gen en_US.UTF-8
|
||||
sudo update-locale LANG=en_US.UTF-8
|
||||
|
||||
|
||||
- name: Setup Ruby environment
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
@@ -57,11 +56,11 @@ runs:
|
||||
shell: bash
|
||||
run: |
|
||||
cd ${{ inputs.app_path }}
|
||||
|
||||
|
||||
# Configure Yarn
|
||||
corepack enable
|
||||
yarn set version 4.6.0
|
||||
|
||||
|
||||
echo "📦 Installing JavaScript dependencies with strict lock file..."
|
||||
if ! yarn install --immutable; then
|
||||
echo ""
|
||||
@@ -77,14 +76,14 @@ runs:
|
||||
echo "This ensures everyone has the exact same dependency versions."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# Run mobile-specific installation
|
||||
if [[ "${{ runner.os }}" == "macOS" ]]; then
|
||||
yarn install-app:mobile-deploy:ios
|
||||
else
|
||||
yarn install-app:mobile-deploy
|
||||
fi
|
||||
|
||||
|
||||
# Install Ruby gems with bundler (respecting cache)
|
||||
echo "📦 Installing Ruby gems with strict lock file..."
|
||||
if ! bundle install --jobs 4 --retry 3; then
|
||||
|
||||
2
.github/actions/yarn-install/action.yml
vendored
2
.github/actions/yarn-install/action.yml
vendored
@@ -22,7 +22,7 @@ runs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'yarn'
|
||||
cache: "yarn"
|
||||
cache-dependency-path: yarn.lock
|
||||
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: OpenPassport CI/CD
|
||||
name: Circuits Build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -16,7 +16,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ['self-hosted', 'selfxyz-org', 'ubuntu-22-04', '128ram']
|
||||
runs-on: ["self-hosted", "selfxyz-org", "ubuntu-22-04", "128ram"]
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
10
.github/workflows/circuits.yml
vendored
10
.github/workflows/circuits.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Self Circuits CI/CD
|
||||
name: Circuits CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -6,16 +6,16 @@ on:
|
||||
- main
|
||||
- openpassportv2
|
||||
paths:
|
||||
- 'circuits/**'
|
||||
- 'common/**'
|
||||
- "circuits/**"
|
||||
- "common/**"
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
- main
|
||||
- openpassportv2
|
||||
paths:
|
||||
- 'circuits/**'
|
||||
- 'common/**'
|
||||
- "circuits/**"
|
||||
- "common/**"
|
||||
jobs:
|
||||
run_circuit_tests:
|
||||
if: github.event.pull_request.draft == false
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: General Self CI
|
||||
name: Common CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
10
.github/workflows/contracts.yml
vendored
10
.github/workflows/contracts.yml
vendored
@@ -1,19 +1,19 @@
|
||||
name: Self Contracts CI/CD
|
||||
name: Contracts CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- main
|
||||
paths:
|
||||
- 'contracts/**'
|
||||
- 'common/**'
|
||||
- "contracts/**"
|
||||
- "common/**"
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
- main
|
||||
paths:
|
||||
- 'contracts/**'
|
||||
- 'common/**'
|
||||
- "contracts/**"
|
||||
- "common/**"
|
||||
jobs:
|
||||
test_contracts:
|
||||
if: github.event.pull_request.draft == false
|
||||
|
||||
2
.github/workflows/mobile-bundle-analysis.yml
vendored
2
.github/workflows/mobile-bundle-analysis.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: mobile-bundle-analysis
|
||||
name: Mobile Bundle Analysis
|
||||
|
||||
env:
|
||||
NODE_VERSION: 18
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: App CI
|
||||
name: Mobile CI
|
||||
env:
|
||||
# Build environment versions
|
||||
NODE_VERSION: 18
|
||||
20
.github/workflows/mobile-deploy-auto.yml
vendored
20
.github/workflows/mobile-deploy-auto.yml
vendored
@@ -5,9 +5,9 @@ on:
|
||||
types: [closed]
|
||||
branches: [main, dev]
|
||||
paths:
|
||||
- 'app/**'
|
||||
- '!app/**/*.md'
|
||||
- '!app/docs/**'
|
||||
- "app/**"
|
||||
- "!app/**/*.md"
|
||||
- "!app/docs/**"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -22,13 +22,13 @@ jobs:
|
||||
deployment_track: ${{ steps.check.outputs.deployment_track }}
|
||||
version_bump: ${{ steps.check.outputs.version_bump }}
|
||||
platforms: ${{ steps.check.outputs.platforms }}
|
||||
|
||||
|
||||
steps:
|
||||
- name: Check deployment conditions
|
||||
id: check
|
||||
run: |
|
||||
echo "🔍 Checking deployment conditions..."
|
||||
|
||||
|
||||
# Skip if PR has skip-deploy in title or body
|
||||
if [[ "${{ github.event.pull_request.title }}" =~ \[skip-deploy\] ]] ||
|
||||
[[ "${{ github.event.pull_request.body }}" =~ \[skip-deploy\] ]]; then
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
echo "⏭️ Skipping deployment due to [skip-deploy] flag"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
# Determine deployment track based on target branch
|
||||
if [[ "${{ github.base_ref }}" == "main" ]]; then
|
||||
echo "deployment_track=production" >> $GITHUB_OUTPUT
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
echo "deployment_track=internal" >> $GITHUB_OUTPUT
|
||||
echo "🧪 Deployment track: internal testing"
|
||||
fi
|
||||
|
||||
|
||||
# Determine version bump from PR labels
|
||||
labels="${{ join(github.event.pull_request.labels.*.name, ',') }}"
|
||||
if [[ "$labels" =~ version:major ]]; then
|
||||
@@ -61,11 +61,11 @@ jobs:
|
||||
echo "version_bump=build" >> $GITHUB_OUTPUT
|
||||
echo "📈 Version bump: build only"
|
||||
fi
|
||||
|
||||
|
||||
# Always deploy both platforms for now (can be enhanced later)
|
||||
echo 'platforms=["ios", "android"]' >> $GITHUB_OUTPUT
|
||||
echo "should_deploy=true" >> $GITHUB_OUTPUT
|
||||
|
||||
|
||||
- name: Log deployment info
|
||||
if: steps.check.outputs.should_deploy == 'true'
|
||||
run: |
|
||||
@@ -106,4 +106,4 @@ jobs:
|
||||
- name: Notify skip
|
||||
run: |
|
||||
echo "📱 Mobile deployment was skipped for this PR"
|
||||
echo "To deploy manually, use the 'Run workflow' button on the Mobile App Deployments workflow"
|
||||
echo "To deploy manually, use the 'Run workflow' button on the Mobile App Deployments workflow"
|
||||
|
||||
12
.github/workflows/mobile-deploy.yml
vendored
12
.github/workflows/mobile-deploy.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Mobile App Deployments
|
||||
name: Mobile Deploy
|
||||
|
||||
env:
|
||||
# Build environment versions
|
||||
@@ -9,11 +9,11 @@ env:
|
||||
ANDROID_NDK_VERSION: 27.0.11718014
|
||||
|
||||
# Cache versioning - increment these to bust caches when needed
|
||||
GH_CACHE_VERSION: v1 # Global cache version
|
||||
GH_YARN_CACHE_VERSION: v1 # Yarn-specific cache version
|
||||
GH_GEMS_CACHE_VERSION: v1 # Ruby gems cache version
|
||||
GH_PODS_CACHE_VERSION: v1 # CocoaPods cache version
|
||||
GH_GRADLE_CACHE_VERSION: v1 # Gradle cache version
|
||||
GH_CACHE_VERSION: v1 # Global cache version
|
||||
GH_YARN_CACHE_VERSION: v1 # Yarn-specific cache version
|
||||
GH_GEMS_CACHE_VERSION: v1 # Ruby gems cache version
|
||||
GH_PODS_CACHE_VERSION: v1 # CocoaPods cache version
|
||||
GH_GRADLE_CACHE_VERSION: v1 # Gradle cache version
|
||||
|
||||
# Path configuration
|
||||
WORKSPACE: ${{ github.workspace }}
|
||||
|
||||
275
.github/workflows/mobile-e2e.yml
vendored
Normal file
275
.github/workflows/mobile-e2e.yml
vendored
Normal file
@@ -0,0 +1,275 @@
|
||||
name: Mobile E2E
|
||||
|
||||
env:
|
||||
# Build environment versions
|
||||
NODE_VERSION: 18
|
||||
JAVA_VERSION: 17
|
||||
ANDROID_API_LEVEL: 33
|
||||
ANDROID_NDK_VERSION: 27.0.11718014
|
||||
# Performance optimizations
|
||||
GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.workers.max=4 -Dorg.gradle.parallel=true -Dorg.gradle.configureondemand=true -Dorg.gradle.caching=true
|
||||
CI: true
|
||||
# Disable Maestro analytics in CI
|
||||
MAESTRO_CLI_NO_ANALYTICS: true
|
||||
MAESTRO_VERSION: 1.41.0
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, release/**]
|
||||
paths:
|
||||
- "app/**"
|
||||
- ".github/workflows/mobile-e2e.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "app/**"
|
||||
- ".github/workflows/mobile-e2e.yml"
|
||||
|
||||
jobs:
|
||||
e2e-android:
|
||||
# TODO: The Android E2E test job is temporarily disabled due to a recurring
|
||||
# Maestro driver timeout issue in the CI environment. The emulator becomes
|
||||
# unresponsive, preventing Maestro from connecting. This needs further
|
||||
# investigation, but has been disabled to unblock the pipeline.
|
||||
# To test locally, run `./scripts/test-e2e-local.sh android --workflow-match`
|
||||
if: false
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-android-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
timeout-minutes: 45
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
- run: corepack enable
|
||||
- run: corepack prepare yarn@4.6.0 --activate
|
||||
- name: Cache Yarn dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .yarn/cache
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- run: yarn install --immutable --silent
|
||||
- name: Cache Maestro
|
||||
id: cache-maestro
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.maestro
|
||||
key: ${{ runner.os }}-maestro-${{ env.MAESTRO_VERSION }}
|
||||
- name: Install Maestro
|
||||
if: steps.cache-maestro.outputs.cache-hit != 'true'
|
||||
run: curl -Ls "https://get.maestro.mobile.dev" | bash
|
||||
- name: Add Maestro to path
|
||||
run: echo "$HOME/.maestro/bin" >> "$GITHUB_PATH"
|
||||
- name: Setup Java environment
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: ${{ env.JAVA_VERSION }}
|
||||
- name: Cache Gradle packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-
|
||||
- name: Setup Android SDK
|
||||
uses: android-actions/setup-android@v3
|
||||
with:
|
||||
accept-android-sdk-licenses: true
|
||||
|
||||
- name: Cache NDK
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.ANDROID_HOME }}/ndk/${{ env.ANDROID_NDK_VERSION }}
|
||||
key: ${{ runner.os }}-ndk-${{ env.ANDROID_NDK_VERSION }}
|
||||
- name: Install NDK
|
||||
run: sdkmanager "ndk;${{ env.ANDROID_NDK_VERSION }}"
|
||||
- name: Build dependencies (outside emulator)
|
||||
run: |
|
||||
echo "Building dependencies..."
|
||||
yarn workspace @selfxyz/mobile-app run build:deps --silent || { echo "❌ Dependency build failed"; exit 1; }
|
||||
echo "✅ Dependencies built successfully"
|
||||
- name: Cache Android build
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
app/android/app/build
|
||||
app/android/.gradle
|
||||
key: ${{ runner.os }}-android-build-${{ hashFiles('app/android/**/*.gradle*', 'app/android/gradle-wrapper.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-android-build-
|
||||
- name: Build Android APK
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
with:
|
||||
api-level: ${{ env.ANDROID_API_LEVEL }}
|
||||
arch: x86_64
|
||||
target: google_apis
|
||||
force-avd-creation: false
|
||||
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -camera-front none -memory 8192 -cores 4 -accel on
|
||||
disable-animations: true
|
||||
script: |
|
||||
echo "Building Android APK..."
|
||||
chmod +x app/android/gradlew
|
||||
(cd app/android && ./gradlew assembleRelease --quiet --parallel --build-cache --no-configuration-cache) || { echo "❌ Android build failed"; exit 1; }
|
||||
echo "✅ Android build succeeded"
|
||||
- name: Install and Test on Android
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
with:
|
||||
api-level: ${{ env.ANDROID_API_LEVEL }}
|
||||
arch: x86_64
|
||||
target: google_apis
|
||||
force-avd-creation: false
|
||||
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -camera-front none -memory 8192 -cores 4 -accel on
|
||||
disable-animations: true
|
||||
script: |
|
||||
echo "Installing app on emulator..."
|
||||
APK_PATH="app/android/app/build/outputs/apk/release/app-release.apk"
|
||||
[ -f "$APK_PATH" ] || { echo "❌ APK not found at $APK_PATH"; exit 1; }
|
||||
adb install -r "$APK_PATH" || { echo "❌ App installation failed"; exit 1; }
|
||||
echo "✅ App installed successfully"
|
||||
|
||||
echo "⏰ Giving the emulator a moment to settle..."
|
||||
sleep 5
|
||||
|
||||
echo "🎭 Running Maestro tests..."
|
||||
export MAESTRO_DRIVER_STARTUP_TIMEOUT=180000
|
||||
maestro test tests/e2e/launch.android.flow.yaml --format junit --output app/maestro-results.xml
|
||||
env:
|
||||
E2E_BUILD: "true"
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: maestro-results-android
|
||||
path: app/maestro-results.xml
|
||||
if-no-files-found: warn
|
||||
|
||||
e2e-ios:
|
||||
timeout-minutes: 45
|
||||
runs-on: macos-latest
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-ios-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
- run: corepack enable
|
||||
- run: corepack prepare yarn@4.6.0 --activate
|
||||
- name: Cache Yarn dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .yarn/cache
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- run: yarn install --immutable --silent
|
||||
- name: Cache Maestro
|
||||
id: cache-maestro
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.maestro
|
||||
key: ${{ runner.os }}-maestro-${{ env.MAESTRO_VERSION }}
|
||||
- name: Install Maestro
|
||||
if: steps.cache-maestro.outputs.cache-hit != 'true'
|
||||
run: curl -Ls "https://get.maestro.mobile.dev" | bash
|
||||
- name: Add Maestro to path
|
||||
run: echo "$HOME/.maestro/bin" >> "$GITHUB_PATH"
|
||||
- name: Cache Pods
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
app/ios/Pods
|
||||
~/Library/Caches/CocoaPods
|
||||
key: ${{ runner.os }}-pods-${{ hashFiles('app/ios/Podfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pods-
|
||||
- name: Cache Xcode build
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: app/ios/build
|
||||
key: ${{ runner.os }}-xcode-${{ hashFiles('app/ios/Podfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-xcode-
|
||||
- name: Build dependencies (outside main flow)
|
||||
run: |
|
||||
echo "Building dependencies..."
|
||||
yarn workspace @selfxyz/mobile-app run build:deps --silent || { echo "❌ Dependency build failed"; exit 1; }
|
||||
echo "✅ Dependencies built successfully"
|
||||
- name: Install iOS dependencies
|
||||
run: |
|
||||
echo "Installing iOS dependencies..."
|
||||
(cd app/ios && pod install --silent) || { echo "❌ Pod install failed"; exit 1; }
|
||||
echo "✅ Pods installed successfully"
|
||||
- name: Setup iOS Simulator
|
||||
run: |
|
||||
echo "Setting up iOS Simulator..."
|
||||
echo "Available simulators:"
|
||||
xcrun simctl list devices | grep "iPhone 15"
|
||||
xcrun simctl boot "iPhone 15" || true
|
||||
xcrun simctl bootstatus "iPhone 15" -b
|
||||
echo "Simulator status:"
|
||||
xcrun simctl list devices | grep "iPhone 15"
|
||||
- name: Build iOS App
|
||||
run: |
|
||||
echo "Building iOS app..."
|
||||
xcodebuild -workspace app/ios/OpenPassport.xcworkspace -scheme OpenPassport -configuration Release -sdk iphonesimulator -derivedDataPath app/ios/build -jobs "$(sysctl -n hw.ncpu)" -parallelizeTargets -quiet || { echo "❌ iOS build failed"; exit 1; }
|
||||
echo "✅ iOS build succeeded"
|
||||
- name: Install and Test on iOS
|
||||
run: |
|
||||
echo "Installing app on simulator..."
|
||||
APP_PATH=$(find app/ios/build/Build/Products/Release-iphonesimulator -name "*.app" | head -1)
|
||||
[ -z "$APP_PATH" ] && { echo "❌ Could not find built iOS app"; exit 1; }
|
||||
echo "Found app at: $APP_PATH"
|
||||
|
||||
echo "🔍 Determining app bundle ID from built app..."
|
||||
IOS_BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print CFBundleIdentifier" "$APP_PATH/Info.plist")
|
||||
[ -z "$IOS_BUNDLE_ID" ] && { echo "❌ Could not determine bundle ID from $APP_PATH/Info.plist"; exit 1; }
|
||||
echo "✅ App Bundle ID: $IOS_BUNDLE_ID"
|
||||
|
||||
echo "Removing any existing app installation..."
|
||||
xcrun simctl uninstall "iPhone 15" "$IOS_BUNDLE_ID" 2>/dev/null || true
|
||||
|
||||
echo "Installing app..."
|
||||
xcrun simctl install "iPhone 15" "$APP_PATH"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "❌ iOS app installation failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Verifying app installation..."
|
||||
if xcrun simctl listapps "iPhone 15" | grep -q "$IOS_BUNDLE_ID"; then
|
||||
echo "✅ App successfully installed"
|
||||
else
|
||||
echo "❌ App installation verification failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "🚀 Testing app launch capability..."
|
||||
xcrun simctl launch "iPhone 15" "$IOS_BUNDLE_ID" || {
|
||||
echo "⚠️ Direct app launch test failed - this might be expected."
|
||||
}
|
||||
|
||||
echo "⏰ Checking simulator readiness..."
|
||||
sleep 10
|
||||
xcrun simctl listapps "iPhone 15" > /dev/null || sleep 5
|
||||
|
||||
echo "Running Maestro tests..."
|
||||
echo "Starting test execution..."
|
||||
maestro test app/tests/e2e/launch.ios.flow.yaml --format junit --output app/maestro-results.xml || {
|
||||
echo "Maestro test failed, but continuing to upload results..."
|
||||
exit 1
|
||||
}
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: maestro-results-ios
|
||||
path: app/maestro-results.xml
|
||||
if-no-files-found: warn
|
||||
20
.github/workflows/npm-publish.yml
vendored
20
.github/workflows/npm-publish.yml
vendored
@@ -1,13 +1,13 @@
|
||||
name: Publish to npm
|
||||
name: NPM Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- 'sdk/core/package.json'
|
||||
- 'sdk/qrcode/package.json'
|
||||
- 'common/package.json'
|
||||
- "sdk/core/package.json"
|
||||
- "sdk/qrcode/package.json"
|
||||
- "common/package.json"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -52,8 +52,8 @@ jobs:
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
node-version: "18"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
- name: Install Dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
@@ -82,8 +82,8 @@ jobs:
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
node-version: "18"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
- name: Install Dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
@@ -111,8 +111,8 @@ jobs:
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
node-version: "18"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Dependencies
|
||||
uses: ./.github/actions/yarn-install
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# AGENTS Instructions
|
||||
|
||||
This repository is a Yarn v4 monorepo with several workspaces:
|
||||
|
||||
- `app` – mobile app (@selfxyz/mobile-app)
|
||||
- `circuits` – zk-SNARK circuits (@selfxyz/circuits)
|
||||
- `common` – shared utilities (@selfxyz/common)
|
||||
@@ -11,9 +12,11 @@ This repository is a Yarn v4 monorepo with several workspaces:
|
||||
## Workflow
|
||||
|
||||
### Setup
|
||||
|
||||
- Run `yarn install` once before running any other commands. This installs root dependencies and sets up husky hooks.
|
||||
|
||||
### Commit Checks
|
||||
|
||||
Before committing, run the following commands:
|
||||
|
||||
```bash
|
||||
@@ -31,19 +34,23 @@ yarn types
|
||||
```
|
||||
|
||||
### Tests
|
||||
|
||||
- Run unit tests where available:
|
||||
- `yarn workspace @selfxyz/common test`
|
||||
- `yarn workspace @selfxyz/circuits test` # may fail if OpenSSL algorithms are missing
|
||||
- `yarn workspace @selfxyz/circuits test` # may fail if OpenSSL algorithms are missing
|
||||
- `yarn workspace @selfxyz/mobile-app test`
|
||||
- Tests for `@selfxyz/contracts` are currently disabled in CI and may be skipped.
|
||||
|
||||
### Formatting
|
||||
|
||||
- Use Prettier configuration from `.prettierrc` files.
|
||||
- Follow `.editorconfig` for line endings and indentation.
|
||||
|
||||
### Commit Guidelines
|
||||
|
||||
- Write short, imperative commit messages (e.g. `Fix address validation`).
|
||||
- The pull request body should summarize the changes and mention test results.
|
||||
|
||||
## Scope
|
||||
|
||||
These instructions apply to the entire repository unless overridden by a nested `AGENTS.md`.
|
||||
|
||||
@@ -7,6 +7,7 @@ By scanning the NFC chip in their ID document, users can prove their validity wh
|
||||
Under the hood, Self uses zk-SNARKs to make sure personal data is redacted, but the document is verified.
|
||||
|
||||
Use cases unlocked include:
|
||||
|
||||
- **Airdrop protection**: Protect a token distribution from bots
|
||||
- **Social media**: Add humanity checks to user's profiles
|
||||
- **Quadratic funding**: Prevent farmers from skewing rewards
|
||||
@@ -27,6 +28,7 @@ Checkout our [coverage map here](http://map.self.xyz/).
|
||||
#### What can I request/prove with Self?
|
||||
|
||||
When a country issues a passport or a compliant ID document, they sign datagroups that include at least:
|
||||
|
||||
- First and last name
|
||||
- Nationality
|
||||
- Date of birth
|
||||
@@ -59,7 +61,7 @@ The International Civil Aviation Organization (ICAO) is a specialized agency of
|
||||
- Allow DeFi protocols to check if the nationality of a user is included in a set of forbidden states.
|
||||
- Gate an adult content website to a specific age.
|
||||
- Create a petition system or a survey portal.
|
||||
- Passport Wallet: use [active authentication](https://en.wikipedia.org/wiki/Biometric_passport#:~:text=Active%20Authentication%20(AA),Using%20AA%20is%20optional.) to build a wallet, a multisig or a recovery module using passport signatures
|
||||
- Passport Wallet: use [active authentication](<https://en.wikipedia.org/wiki/Biometric_passport#:~:text=Active%20Authentication%20(AA),Using%20AA%20is%20optional.>) to build a wallet, a multisig or a recovery module using passport signatures
|
||||
|
||||
We provide bounties for new and interesting applications using Self.
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ codecov:
|
||||
coverage:
|
||||
precision: 2
|
||||
round: down
|
||||
range: '80...100'
|
||||
range: "80...100"
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
@@ -18,36 +18,36 @@ coverage:
|
||||
flags:
|
||||
screens:
|
||||
paths:
|
||||
- 'src/screens/'
|
||||
- "src/screens/"
|
||||
stores:
|
||||
paths:
|
||||
- 'src/stores/'
|
||||
- "src/stores/"
|
||||
providers:
|
||||
paths:
|
||||
- 'src/providers/'
|
||||
- "src/providers/"
|
||||
components:
|
||||
paths:
|
||||
- 'src/components/'
|
||||
- "src/components/"
|
||||
utils:
|
||||
paths:
|
||||
- 'src/utils/'
|
||||
- "src/utils/"
|
||||
hooks:
|
||||
paths:
|
||||
- 'src/hooks/'
|
||||
- "src/hooks/"
|
||||
navigation:
|
||||
paths:
|
||||
- 'src/navigation/'
|
||||
- "src/navigation/"
|
||||
layouts:
|
||||
paths:
|
||||
- 'src/layouts/'
|
||||
- "src/layouts/"
|
||||
|
||||
ignore:
|
||||
- 'src/mocks/**'
|
||||
- 'src/types/**'
|
||||
- 'src/**/*.d.ts'
|
||||
- 'src/**/index.{ts,tsx}'
|
||||
- 'src/**/*.test.{ts,tsx}'
|
||||
- 'src/**/*.spec.{ts,tsx}'
|
||||
- 'src/**/__tests__/**'
|
||||
- 'src/**/__mocks__/**'
|
||||
- 'tests/**'
|
||||
- "src/mocks/**"
|
||||
- "src/types/**"
|
||||
- "src/**/*.d.ts"
|
||||
- "src/**/index.{ts,tsx}"
|
||||
- "src/**/*.test.{ts,tsx}"
|
||||
- "src/**/*.spec.{ts,tsx}"
|
||||
- "src/**/__tests__/**"
|
||||
- "src/**/__mocks__/**"
|
||||
- "tests/**"
|
||||
|
||||
12
app/.github/workflows/test-coverage.yml
vendored
12
app/.github/workflows/test-coverage.yml
vendored
@@ -4,13 +4,13 @@ on:
|
||||
push:
|
||||
branches: [main, dev]
|
||||
paths:
|
||||
- 'app/**'
|
||||
- '.github/workflows/test-coverage.yml'
|
||||
- "app/**"
|
||||
- ".github/workflows/test-coverage.yml"
|
||||
pull_request:
|
||||
branches: [main, dev]
|
||||
paths:
|
||||
- 'app/**'
|
||||
- '.github/workflows/test-coverage.yml'
|
||||
- "app/**"
|
||||
- ".github/workflows/test-coverage.yml"
|
||||
|
||||
jobs:
|
||||
test:
|
||||
@@ -22,8 +22,8 @@ jobs:
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: 'yarn'
|
||||
node-version: "22"
|
||||
cache: "yarn"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
|
||||
3
app/.gitignore
vendored
3
app/.gitignore
vendored
@@ -92,3 +92,6 @@ yarn-error.log
|
||||
|
||||
# web app
|
||||
.tamagui/*
|
||||
|
||||
# Maestro
|
||||
maestro-results.xml
|
||||
|
||||
@@ -7,5 +7,13 @@
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"endOfLine": "auto"
|
||||
"endOfLine": "auto",
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.yml", "*.yaml"],
|
||||
"options": {
|
||||
"singleQuote": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
enableScripts: true
|
||||
checksumBehavior: 'update'
|
||||
checksumBehavior: "update"
|
||||
|
||||
@@ -25,7 +25,7 @@ GEM
|
||||
artifactory (3.0.17)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.4.0)
|
||||
aws-partitions (1.1141.0)
|
||||
aws-partitions (1.1142.0)
|
||||
aws-sdk-core (3.229.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
@@ -37,7 +37,7 @@ GEM
|
||||
aws-sdk-kms (1.110.0)
|
||||
aws-sdk-core (~> 3, >= 3.228.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.196.0)
|
||||
aws-sdk-s3 (1.196.1)
|
||||
aws-sdk-core (~> 3, >= 3.228.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
|
||||
@@ -341,6 +341,25 @@ bundle exec fastlane ios internal_test test_mode:true
|
||||
bundle exec fastlane android internal_test test_mode:true
|
||||
```
|
||||
|
||||
### Maestro end-to-end tests
|
||||
|
||||
Install the Maestro CLI locally using curl or Homebrew:
|
||||
|
||||
```bash
|
||||
curl -Ls https://get.maestro.mobile.dev | bash
|
||||
# or
|
||||
brew install maestro
|
||||
```
|
||||
|
||||
Then build the app and run the flow:
|
||||
|
||||
```bash
|
||||
yarn test:e2e:android # Android
|
||||
yarn test:e2e:ios # iOS
|
||||
```
|
||||
|
||||
The flow definition for Android is in [`tests/e2e/launch.android.flow.yaml`](tests/e2e/launch.android.flow.yaml) and for iOS is in [`tests/e2e/launch.ios.flow.yaml`](tests/e2e/launch.ios.flow.yaml).
|
||||
|
||||
## FAQ
|
||||
|
||||
If you get something like this:
|
||||
|
||||
@@ -130,7 +130,11 @@ android {
|
||||
arguments += "-DANDROID_STL=c++_shared"
|
||||
}
|
||||
ndk {
|
||||
abiFilters += "arm64-v8a"
|
||||
if (System.env.E2E_BUILD == "true") {
|
||||
abiFilters "x86_64"
|
||||
} else {
|
||||
abiFilters "arm64-v8a", "x86_64"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,6 +151,12 @@ android {
|
||||
storePassword MYAPP_UPLOAD_STORE_PASSWORD
|
||||
keyAlias MYAPP_UPLOAD_KEY_ALIAS
|
||||
keyPassword MYAPP_UPLOAD_KEY_PASSWORD
|
||||
} else {
|
||||
// Fallback to debug signing for CI/local testing
|
||||
storeFile file('debug.keystore')
|
||||
storePassword 'android'
|
||||
keyAlias 'androiddebugkey'
|
||||
keyPassword 'android'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,12 @@
|
||||
|
||||
import Foundation
|
||||
import React
|
||||
#if !E2E_TESTING
|
||||
import NFCPassportReader
|
||||
#endif
|
||||
import Security
|
||||
|
||||
#if !E2E_TESTING
|
||||
@available(iOS 13, macOS 10.15, *)
|
||||
extension CertificateType {
|
||||
func stringValue() -> String {
|
||||
@@ -23,6 +26,7 @@ extension CertificateType {
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Helper function to map the keys of a dictionary
|
||||
extension Dictionary {
|
||||
@@ -31,6 +35,7 @@ extension Dictionary {
|
||||
}
|
||||
}
|
||||
|
||||
#if !E2E_TESTING
|
||||
@available(iOS 15, *)
|
||||
@objc(PassportReader)
|
||||
class PassportReader: NSObject {
|
||||
@@ -416,3 +421,38 @@ func serializePublicKey(_ publicKey: SecKey) -> String? {
|
||||
return true
|
||||
}
|
||||
}
|
||||
#else
|
||||
// E2E Testing stub implementation
|
||||
@available(iOS 15, *)
|
||||
@objc(PassportReader)
|
||||
class PassportReader: NSObject {
|
||||
override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
@objc(configure:enableDebugLogs:)
|
||||
func configure(token: String, enableDebugLogs: Bool) {
|
||||
// No-op for E2E testing
|
||||
}
|
||||
|
||||
@objc(scanPassport:dateOfBirth:dateOfExpiry:canNumber:useCan:skipPACE:skipCA:extendedMode:usePacePolling:resolve:reject:)
|
||||
func scanPassport(
|
||||
_ passportNumber: String,
|
||||
dateOfBirth: String,
|
||||
dateOfExpiry: String,
|
||||
canNumber: String,
|
||||
useCan: NSNumber,
|
||||
skipPACE: NSNumber,
|
||||
skipCA: NSNumber,
|
||||
extendedMode: NSNumber,
|
||||
usePacePolling: NSNumber,
|
||||
resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
||||
reject("E2E_TESTING", "NFC scanning not available in E2E testing mode", nil)
|
||||
}
|
||||
|
||||
@objc
|
||||
static func requiresMainQueueSetup() -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -27,7 +27,10 @@ target "Self" do
|
||||
config = use_native_modules!
|
||||
|
||||
use_frameworks!
|
||||
pod "NFCPassportReader", git: "https://github.com/seshanthS/NFCPassportReader", commit: "74098a5e29c23b3f5a58dc14a336556fa89c0ad6"
|
||||
# Skip NFCPassportReader for e2e testing to avoid build issues
|
||||
unless ENV["E2E_TESTING"] == "1"
|
||||
pod "NFCPassportReader", git: "https://github.com/seshanthS/NFCPassportReader", commit: "74098a5e29c23b3f5a58dc14a336556fa89c0ad6"
|
||||
end
|
||||
|
||||
pod "QKMRZScanner"
|
||||
pod "lottie-ios"
|
||||
@@ -63,6 +66,7 @@ target "Self" do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
target.build_configurations.each do |config|
|
||||
config.build_settings["IPHONEOS_DEPLOYMENT_TARGET"] = "15.0"
|
||||
config.build_settings["GCC_PREPROCESSOR_DEFINITIONS"] ||= ["$(inherited)", "_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION"]
|
||||
@@ -79,15 +83,18 @@ target "Self" do
|
||||
system(command)
|
||||
end
|
||||
|
||||
framework_paths = [
|
||||
"Pods/OpenSSL-Universal/Frameworks/OpenSSL.xcframework/ios-arm64/OpenSSL.framework/OpenSSL",
|
||||
"Pods/OpenSSL-Universal/Frameworks/OpenSSL.xcframework/ios-arm64_x86_64-maccatalyst/OpenSSL.framework/OpenSSL",
|
||||
"Pods/OpenSSL-Universal/Frameworks/OpenSSL.xcframework/ios-arm64_x86_64-simulator/OpenSSL.framework/OpenSSL",
|
||||
"Pods/OpenSSL-Universal/Frameworks/OpenSSL.xcframework/macos-arm64_x86_64/OpenSSL.framework/OpenSSL",
|
||||
]
|
||||
# Only strip OpenSSL bitcode if NFCPassportReader is included (not in e2e testing)
|
||||
unless ENV["E2E_TESTING"] == "1"
|
||||
framework_paths = [
|
||||
"Pods/OpenSSL-Universal/Frameworks/OpenSSL.xcframework/ios-arm64/OpenSSL.framework/OpenSSL",
|
||||
"Pods/OpenSSL-Universal/Frameworks/OpenSSL.xcframework/ios-arm64_x86_64-maccatalyst/OpenSSL.framework/OpenSSL",
|
||||
"Pods/OpenSSL-Universal/Frameworks/OpenSSL.xcframework/ios-arm64_x86_64-simulator/OpenSSL.framework/OpenSSL",
|
||||
"Pods/OpenSSL-Universal/Frameworks/OpenSSL.xcframework/macos-arm64_x86_64/OpenSSL.framework/OpenSSL",
|
||||
]
|
||||
|
||||
framework_paths.each do |framework_relative_path|
|
||||
strip_bitcode_from_framework(bitcode_strip_path, framework_relative_path)
|
||||
framework_paths.each do |framework_relative_path|
|
||||
strip_bitcode_from_framework(bitcode_strip_path, framework_relative_path)
|
||||
end
|
||||
end
|
||||
|
||||
# https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202
|
||||
@@ -136,5 +143,25 @@ target "Self" do
|
||||
config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64"
|
||||
end
|
||||
end
|
||||
|
||||
# Add E2E_TESTING compilation condition for main app target when environment variable is set
|
||||
if ENV["E2E_TESTING"] == "1"
|
||||
# Find Self.xcodeproj and add E2E_TESTING compilation condition
|
||||
self_project_path = File.join(installer.sandbox.project_path, "../Self.xcodeproj")
|
||||
if File.exist?(self_project_path)
|
||||
project = Xcodeproj::Project.open(self_project_path)
|
||||
project.targets.each do |target|
|
||||
if target.name == "Self"
|
||||
target.build_configurations.each do |config|
|
||||
existing_conditions = config.build_settings["SWIFT_ACTIVE_COMPILATION_CONDITIONS"] || ""
|
||||
unless existing_conditions.to_s.include?("E2E_TESTING")
|
||||
config.build_settings["SWIFT_ACTIVE_COMPILATION_CONDITIONS"] = (existing_conditions.to_s + " E2E_TESTING").strip
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
project.save
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2284,6 +2284,6 @@ SPEC CHECKSUMS:
|
||||
SwiftyTesseract: 1f3d96668ae92dc2208d9842c8a59bea9fad2cbb
|
||||
Yoga: b05994d1933f507b0a28ceaa4fdb968dc18da178
|
||||
|
||||
PODFILE CHECKSUM: 558a8b95f1ca0bd657ecdbe22eb0b6972605ad2b
|
||||
PODFILE CHECKSUM: 8ed8bfe711f629f0a3f4d4fdacf04034737d609b
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@@ -55,6 +55,8 @@
|
||||
"test:build": "yarn build:deps && yarn web:build && yarn types && yarn analyze:bundle:ios",
|
||||
"test:coverage": "jest --coverage --passWithNoTests",
|
||||
"test:coverage:ci": "jest --coverage --passWithNoTests --ci --coverageReporters=lcov --coverageReporters=text --coverageReporters=json",
|
||||
"test:e2e:android": "cd android && ./gradlew assembleDebug && cd .. && maestro test tests/e2e/launch.android.flow.yaml",
|
||||
"test:e2e:ios": "xcodebuild -workspace ios/OpenPassport.xcworkspace -scheme OpenPassport -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build && maestro test tests/e2e/launch.ios.flow.yaml",
|
||||
"test:fastlane": "bundle exec ruby -Itest fastlane/test/helpers_test.rb",
|
||||
"test:tree-shaking": "node ./scripts/test-tree-shaking.cjs",
|
||||
"test:web-build": "jest tests/web-build-render.test.ts --testTimeout=180000",
|
||||
|
||||
659
app/scripts/test-e2e-local.sh
Executable file
659
app/scripts/test-e2e-local.sh
Executable file
@@ -0,0 +1,659 @@
|
||||
#!/bin/bash
|
||||
# Unified Local E2E Testing Script
|
||||
# Run this from the app directory
|
||||
|
||||
set -e
|
||||
|
||||
PLATFORM=${1:-}
|
||||
EMULATOR_PID=""
|
||||
|
||||
# Colors for better output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
print_usage() {
|
||||
echo "🎭 Local E2E Testing"
|
||||
echo "Usage: $0 [ios|android] [--workflow-match]"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 ios - Run iOS e2e tests locally"
|
||||
echo " $0 android - Run Android e2e tests locally"
|
||||
echo " $0 android --workflow-match - Run Android tests matching GitHub Actions workflow"
|
||||
echo ""
|
||||
echo "Prerequisites:"
|
||||
echo " iOS: Xcode, iOS Simulator, CocoaPods"
|
||||
echo " Android: Android SDK, running emulator"
|
||||
echo ""
|
||||
echo "Workflow Match Mode:"
|
||||
echo " --workflow-match - Use Release builds and exact workflow steps"
|
||||
echo " (No Metro dependency, matches CI environment)"
|
||||
}
|
||||
|
||||
log_info() {
|
||||
echo -e "${BLUE}$1${NC}"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}✅ $1${NC}"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}⚠️ $1${NC}"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}❌ $1${NC}"
|
||||
}
|
||||
|
||||
# Check if we're in the right directory (app directory)
|
||||
check_directory() {
|
||||
if [ ! -f "package.json" ]; then
|
||||
log_error "Please run this from the app directory (where package.json exists)"
|
||||
echo "Current directory: $(pwd)"
|
||||
echo "Expected: /path/to/your/project/app"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if Maestro is installed and install if needed
|
||||
setup_maestro() {
|
||||
if ! command -v maestro &> /dev/null; then
|
||||
if [ -f "$HOME/.maestro/bin/maestro" ]; then
|
||||
log_info "📦 Maestro found in ~/.maestro/bin, adding to PATH..."
|
||||
export PATH="$HOME/.maestro/bin:$PATH"
|
||||
else
|
||||
log_info "📦 Installing Maestro..."
|
||||
curl -Ls "https://get.maestro.mobile.dev" | bash
|
||||
export PATH="$HOME/.maestro/bin:$PATH"
|
||||
log_success "Maestro installed successfully"
|
||||
fi
|
||||
else
|
||||
log_success "Maestro already available in PATH"
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if Metro is running (required for debug builds)
|
||||
check_metro_running() {
|
||||
# Skip Metro check if in workflow match mode (Release builds don't need Metro)
|
||||
if [ "$WORKFLOW_MATCH" = "true" ]; then
|
||||
log_info "🔍 Skipping Metro check (Release builds don't need Metro)"
|
||||
return
|
||||
fi
|
||||
|
||||
log_info "🔍 Checking if Metro server is running..."
|
||||
|
||||
# Check if Metro is running on port 8081
|
||||
if ! curl -f -s http://localhost:8081/status > /dev/null 2>&1; then
|
||||
log_error "Metro server is not running!"
|
||||
echo ""
|
||||
echo "React Native debug builds require Metro to serve the JavaScript bundle."
|
||||
echo "Please start Metro in another terminal before running e2e tests:"
|
||||
echo ""
|
||||
echo " ${BLUE}cd $(pwd)${NC}"
|
||||
echo " ${BLUE}yarn start${NC}"
|
||||
echo ""
|
||||
echo "Wait for Metro to show 'Metro waiting on exp://localhost:8081' then re-run this script."
|
||||
echo ""
|
||||
echo "Or use --workflow-match to use Release builds (no Metro needed):"
|
||||
echo " ${BLUE}$0 $PLATFORM --workflow-match${NC}"
|
||||
exit 1
|
||||
else
|
||||
log_success "Metro server is running on http://localhost:8081"
|
||||
fi
|
||||
}
|
||||
|
||||
# Build dependencies (shared by both platforms)
|
||||
build_dependencies() {
|
||||
log_info "🔨 Building dependencies..."
|
||||
if ! yarn build:deps; then
|
||||
log_error "Dependency build failed"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Run Maestro tests (shared by both platforms)
|
||||
run_maestro_tests() {
|
||||
log_info "🎭 Running Maestro tests..."
|
||||
echo "Starting test execution..."
|
||||
|
||||
# Use platform-specific flow files
|
||||
if [ "$PLATFORM" = "ios" ]; then
|
||||
FLOW_FILE="tests/e2e/launch.ios.flow.yaml"
|
||||
else
|
||||
FLOW_FILE="tests/e2e/launch.android.flow.yaml"
|
||||
fi
|
||||
|
||||
# Set a longer timeout for the driver to start, especially for the first run
|
||||
export MAESTRO_DRIVER_STARTUP_TIMEOUT=180000 # 180 seconds (3 minutes) in ms
|
||||
|
||||
# Attempt to run Maestro, capturing output to check for a specific error
|
||||
MAESTRO_OUTPUT_FILE=$(mktemp)
|
||||
if maestro test "$FLOW_FILE" --format junit --output maestro-results.xml > "$MAESTRO_OUTPUT_FILE" 2>&1; then
|
||||
log_success "🎉 Maestro tests passed on the first attempt!"
|
||||
cat "$MAESTRO_OUTPUT_FILE"
|
||||
rm "$MAESTRO_OUTPUT_FILE"
|
||||
return 0
|
||||
else
|
||||
# First attempt failed, check for known timeout errors
|
||||
cat "$MAESTRO_OUTPUT_FILE"
|
||||
if grep -q "MaestroDriverStartupException\|IOSDriverTimeoutException" "$MAESTRO_OUTPUT_FILE"; then
|
||||
log_warning "Maestro driver failed to start. Retrying in 30 seconds..."
|
||||
sleep 30
|
||||
|
||||
# Second attempt
|
||||
log_info "🎭 Retrying Maestro tests..."
|
||||
if maestro test "$FLOW_FILE" --format junit --output maestro-results.xml; then
|
||||
log_success "🎉 Maestro tests passed on the second attempt!"
|
||||
rm "$MAESTRO_OUTPUT_FILE"
|
||||
return 0
|
||||
else
|
||||
log_error "Maestro tests failed on the second attempt."
|
||||
rm "$MAESTRO_OUTPUT_FILE"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
# Failed for a different reason, so don't retry
|
||||
log_error "Maestro tests failed for a reason other than driver timeout."
|
||||
rm "$MAESTRO_OUTPUT_FILE"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
shutdown_all_simulators() {
|
||||
log_info "🔌 Shutting down all running iOS simulators..."
|
||||
xcrun simctl shutdown all
|
||||
log_success "All simulators shut down"
|
||||
}
|
||||
|
||||
# iOS-specific functions
|
||||
setup_ios_environment() {
|
||||
# Check if Xcode is available
|
||||
if ! command -v xcrun &> /dev/null; then
|
||||
log_error "Xcode not found. Please install Xcode and iOS Simulator"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "🍎 Setting up iOS environment..."
|
||||
cd ios
|
||||
echo "Installing CocoaPods dependencies with e2e configuration..."
|
||||
# Set environment variable for e2e testing to enable OpenSSL fixes
|
||||
export E2E_TESTING=1
|
||||
pod install
|
||||
cd ..
|
||||
}
|
||||
|
||||
setup_ios_simulator() {
|
||||
log_info "📱 Setting up iOS Simulator..."
|
||||
|
||||
# Get available iOS simulators
|
||||
echo "Available simulators:"
|
||||
xcrun simctl list devices
|
||||
|
||||
# Find the first available iPhone simulator (prefer booted ones, then shutdown ones)
|
||||
AVAILABLE_SIMULATOR=$(xcrun simctl list devices | grep "iPhone" | grep "(Booted)" | head -1 | sed -E 's/.*\(([A-F0-9-]+)\).*/\1/')
|
||||
|
||||
if [ -z "$AVAILABLE_SIMULATOR" ]; then
|
||||
# Try to find any available iPhone simulator that's shutdown
|
||||
AVAILABLE_SIMULATOR=$(xcrun simctl list devices | grep "iPhone" | grep "(Shutdown)" | head -1 | sed -E 's/.*\(([A-F0-9-]+)\).*/\1/')
|
||||
fi
|
||||
|
||||
if [ -z "$AVAILABLE_SIMULATOR" ]; then
|
||||
# Try to find any available simulator
|
||||
AVAILABLE_SIMULATOR=$(xcrun simctl list devices | grep "(Booted)" | head -1 | sed -E 's/.*\(([A-F0-9-]+)\).*/\1/')
|
||||
fi
|
||||
|
||||
if [ -z "$AVAILABLE_SIMULATOR" ]; then
|
||||
# Last resort - any shutdown simulator
|
||||
AVAILABLE_SIMULATOR=$(xcrun simctl list devices | grep "(Shutdown)" | head -1 | sed -E 's/.*\(([A-F0-9-]+)\).*/\1/')
|
||||
fi
|
||||
|
||||
if [ -z "$AVAILABLE_SIMULATOR" ]; then
|
||||
log_error "No available simulators found. Please create a simulator in Xcode."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get the simulator name for display
|
||||
SIMULATOR_NAME=$(xcrun simctl list devices | grep "$AVAILABLE_SIMULATOR" | sed -E 's/^[[:space:]]*([^(]+).*/\1/' | xargs)
|
||||
|
||||
log_info "Using simulator: $SIMULATOR_NAME ($AVAILABLE_SIMULATOR)"
|
||||
|
||||
# Boot the simulator and ensure the Simulator app is open
|
||||
log_info "Booting $SIMULATOR_NAME simulator..."
|
||||
# This can fail if the device is already booted. The `|| true` handles this gracefully.
|
||||
# Our shutdown command should prevent this, but we keep it for robustness.
|
||||
xcrun simctl boot "$AVAILABLE_SIMULATOR" || true
|
||||
|
||||
log_info "Opening Simulator app to ensure it is visible..."
|
||||
open -a Simulator
|
||||
|
||||
log_info "Waiting for simulator to fully boot..."
|
||||
xcrun simctl bootstatus "$AVAILABLE_SIMULATOR" -b
|
||||
|
||||
# Store the simulator ID for later use
|
||||
export IOS_SIMULATOR_ID="$AVAILABLE_SIMULATOR"
|
||||
export IOS_SIMULATOR_NAME="$SIMULATOR_NAME"
|
||||
|
||||
echo "Simulator status:"
|
||||
xcrun simctl list devices | grep "$AVAILABLE_SIMULATOR"
|
||||
}
|
||||
|
||||
build_ios_app() {
|
||||
log_info "🔨 Building iOS app..."
|
||||
# Set environment variable for e2e testing to enable OpenSSL fixes
|
||||
export E2E_TESTING=1
|
||||
|
||||
# Set build configuration based on workflow match
|
||||
if [ "$WORKFLOW_MATCH" = "true" ]; then
|
||||
log_info "Using Release configuration for workflow match"
|
||||
BUILD_CONFIG="Release"
|
||||
else
|
||||
log_info "Using Debug configuration for local development"
|
||||
BUILD_CONFIG="Debug"
|
||||
fi
|
||||
|
||||
if ! xcodebuild -workspace ios/OpenPassport.xcworkspace -scheme OpenPassport -configuration "$BUILD_CONFIG" -sdk iphonesimulator -derivedDataPath ios/build -jobs "$(sysctl -n hw.ncpu)" -parallelizeTargets SWIFT_ACTIVE_COMPILATION_CONDITIONS="E2E_TESTING"; then
|
||||
log_error "iOS build failed"
|
||||
exit 1
|
||||
fi
|
||||
log_success "iOS build succeeded"
|
||||
}
|
||||
|
||||
install_ios_app() {
|
||||
log_info "📦 Installing app on simulator..."
|
||||
APP_PATH=$(find "ios/build/Build/Products/$BUILD_CONFIG-iphonesimulator" -name "*.app" | head -1)
|
||||
if [ -z "$APP_PATH" ]; then
|
||||
log_error "Could not find built iOS app in ios/build/Build/Products/$BUILD_CONFIG-iphonesimulator"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found app at: $APP_PATH"
|
||||
|
||||
# Dynamically determine the bundle ID from the built app
|
||||
log_info "🔍 Determining app bundle ID from built app..."
|
||||
IOS_BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print CFBundleIdentifier" "$APP_PATH/Info.plist")
|
||||
if [ -z "$IOS_BUNDLE_ID" ]; then
|
||||
log_error "Could not determine bundle ID from $APP_PATH/Info.plist"
|
||||
exit 1
|
||||
fi
|
||||
export IOS_BUNDLE_ID
|
||||
log_success "App Bundle ID: $IOS_BUNDLE_ID"
|
||||
|
||||
# Use the dynamic simulator ID
|
||||
SIMULATOR_ID="${IOS_SIMULATOR_ID:-iPhone 15}"
|
||||
log_info "Installing on simulator: $SIMULATOR_ID"
|
||||
|
||||
# Uninstall any existing version first
|
||||
echo "Removing any existing app installation..."
|
||||
xcrun simctl uninstall "$SIMULATOR_ID" "$IOS_BUNDLE_ID" 2>/dev/null || true
|
||||
|
||||
# Install the app
|
||||
echo "Installing app..."
|
||||
if ! xcrun simctl install "$SIMULATOR_ID" "$APP_PATH"; then
|
||||
log_error "iOS app installation failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify the app is installed
|
||||
echo "Verifying app installation..."
|
||||
echo "All installed apps on simulator:"
|
||||
xcrun simctl listapps "$SIMULATOR_ID"
|
||||
echo "Checking for exact bundle ID: $IOS_BUNDLE_ID"
|
||||
if xcrun simctl listapps "$SIMULATOR_ID" | grep -q "$IOS_BUNDLE_ID"; then
|
||||
log_success "App successfully installed"
|
||||
else
|
||||
log_error "App installation verification failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test if the app can be launched directly
|
||||
log_info "🚀 Testing app launch capability..."
|
||||
if ! xcrun simctl launch "$SIMULATOR_ID" "$IOS_BUNDLE_ID"; then
|
||||
log_warning "Direct app launch test failed - this might be expected if the app has launch conditions, but it could also indicate a problem."
|
||||
fi
|
||||
}
|
||||
|
||||
# Android-specific functions
|
||||
setup_android_environment() {
|
||||
# Check if Android SDK is configured
|
||||
if [ -z "$ANDROID_HOME" ]; then
|
||||
log_error "ANDROID_HOME environment variable is not set."
|
||||
echo "Please set ANDROID_HOME to your Android SDK directory."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Define and export full paths to tools for robustness
|
||||
export ADB_CMD="$ANDROID_HOME/platform-tools/adb"
|
||||
export EMULATOR_CMD="$ANDROID_HOME/emulator/emulator"
|
||||
|
||||
if [ ! -f "$ADB_CMD" ]; then
|
||||
log_error "adb not found at $ADB_CMD"
|
||||
echo "Please ensure your ANDROID_HOME is set correctly."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if emulator is running
|
||||
log_info "📱 Checking for Android emulator..."
|
||||
|
||||
# Set shorter wait time for emulator shutdown to reduce logging
|
||||
export ANDROID_EMULATOR_WAIT_TIME_BEFORE_KILL=5
|
||||
|
||||
RUNNING_EMULATOR=$($ADB_CMD devices | grep emulator | head -1 | cut -f1)
|
||||
|
||||
if [ -z "$RUNNING_EMULATOR" ]; then
|
||||
log_info "No Android emulator running. Attempting to start one..."
|
||||
|
||||
# Check if emulator command is available
|
||||
if [ ! -f "$EMULATOR_CMD" ]; then
|
||||
log_error "emulator command not found at $EMULATOR_CMD"
|
||||
echo "Please ensure your ANDROID_HOME is set correctly."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get available AVDs
|
||||
log_info "Finding available Android Virtual Devices..."
|
||||
AVAILABLE_AVDS=$($EMULATOR_CMD -list-avds)
|
||||
|
||||
if [ -z "$AVAILABLE_AVDS" ]; then
|
||||
log_error "No Android Virtual Devices (AVDs) found."
|
||||
echo "Please create an AVD in Android Studio:"
|
||||
echo " 1. Open Android Studio"
|
||||
echo " 2. Go to Tools > Device Manager"
|
||||
echo " 3. Create Virtual Device"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Use the first available AVD
|
||||
FIRST_AVD=$(echo "$AVAILABLE_AVDS" | head -1)
|
||||
log_info "Using emulator: $FIRST_AVD"
|
||||
|
||||
# Start the emulator in background
|
||||
log_info "Starting emulator (this may take a minute)..."
|
||||
"$EMULATOR_CMD" -avd "$FIRST_AVD" -no-snapshot-load >/dev/null 2>&1 &
|
||||
EMULATOR_PID=$!
|
||||
|
||||
# Wait for emulator to start
|
||||
log_info "Waiting for emulator to boot..."
|
||||
for i in {1..60}; do
|
||||
if "$ADB_CMD" devices | grep -q emulator; then
|
||||
RUNNING_EMULATOR=$("$ADB_CMD" devices | grep emulator | head -1 | cut -f1)
|
||||
log_success "Emulator started: $RUNNING_EMULATOR"
|
||||
break
|
||||
fi
|
||||
echo -n "."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [ -z "$RUNNING_EMULATOR" ]; then
|
||||
log_error "Emulator failed to start within 2 minutes"
|
||||
echo "You can try starting it manually:"
|
||||
echo " $EMULATOR_CMD -avd $FIRST_AVD"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Wait for emulator to be fully booted
|
||||
log_info "Waiting for emulator to be fully booted..."
|
||||
for i in {1..30}; do
|
||||
if "$ADB_CMD" -s "$RUNNING_EMULATOR" shell getprop sys.boot_completed 2>/dev/null | grep -q "1"; then
|
||||
log_success "Emulator fully booted and ready"
|
||||
break
|
||||
fi
|
||||
echo -n "."
|
||||
sleep 2
|
||||
done
|
||||
else
|
||||
log_success "Android emulator already running: $RUNNING_EMULATOR"
|
||||
|
||||
# Ensure the running emulator is fully booted
|
||||
log_info "Checking if emulator is fully booted..."
|
||||
if ! "$ADB_CMD" -s "$RUNNING_EMULATOR" shell getprop sys.boot_completed 2>/dev/null | grep -q "1"; then
|
||||
log_warning "Emulator is running but not fully booted, waiting..."
|
||||
for i in {1..15}; do
|
||||
if "$ADB_CMD" -s "$RUNNING_EMULATOR" shell getprop sys.boot_completed 2>/dev/null | grep -q "1"; then
|
||||
log_success "Emulator is now fully booted"
|
||||
break
|
||||
fi
|
||||
echo -n "."
|
||||
sleep 2
|
||||
done
|
||||
else
|
||||
log_success "Emulator is fully booted and ready"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Store the emulator device ID for later use
|
||||
export ANDROID_EMULATOR_ID="$RUNNING_EMULATOR"
|
||||
|
||||
log_success "Android emulator ready:"
|
||||
"$ADB_CMD" devices
|
||||
}
|
||||
|
||||
build_android_app() {
|
||||
log_info "🔨 Building Android APK..."
|
||||
# Note: Using Release builds to avoid Metro dependency in CI
|
||||
# Debug builds require Metro server, Release builds have JS bundled
|
||||
# Run the build inside the android directory so gradlew is available
|
||||
echo "Current working directory: $(pwd)"
|
||||
echo "Checking if gradlew exists:"
|
||||
ls -la android/gradlew || echo "gradlew not found in android/"
|
||||
|
||||
cd android
|
||||
if ! ./gradlew assembleRelease --quiet; then
|
||||
log_error "Android build failed"
|
||||
exit 1
|
||||
fi
|
||||
log_success "Android build succeeded"
|
||||
cd ..
|
||||
}
|
||||
|
||||
install_android_app() {
|
||||
log_info "📦 Installing app on emulator..."
|
||||
# Check if APK was built successfully (matching workflow)
|
||||
APK_PATH="android/app/build/outputs/apk/release/app-release.apk"
|
||||
log_info "Looking for APK at: $APK_PATH"
|
||||
if [ ! -f "$APK_PATH" ]; then
|
||||
log_error "APK not found at expected location"
|
||||
echo "Available files in build directory:"
|
||||
find android/app/build -name "*.apk" 2>/dev/null || echo "No APK files found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found APK at: $APK_PATH"
|
||||
|
||||
# Use the dynamic emulator ID
|
||||
EMULATOR_ID="${ANDROID_EMULATOR_ID:-emulator-5554}"
|
||||
log_info "Installing on emulator: $EMULATOR_ID"
|
||||
|
||||
# Dynamically find the latest 'aapt' tool path and determine package name
|
||||
# First try 'aapt' (classic tool) for package name extraction, then fall back to 'aapt2' with supported commands
|
||||
AAPT_PATH=$(find "$ANDROID_HOME/build-tools" -type f -name "aapt" | sort -r | head -n 1)
|
||||
if [ -n "$AAPT_PATH" ]; then
|
||||
log_info "Using aapt to get package name from $APK_PATH..."
|
||||
ACTUAL_PACKAGE=$("$AAPT_PATH" dump badging "$APK_PATH" 2>/dev/null | grep "package:" | sed -E "s/.*name='([^']+)'.*/\1/" | head -1)
|
||||
else
|
||||
log_warning "aapt not found, trying aapt2..."
|
||||
AAPT2_PATH=$(find "$ANDROID_HOME/build-tools" -type f -name "aapt2" | sort -r | head -n 1)
|
||||
if [ -n "$AAPT2_PATH" ]; then
|
||||
log_info "Found aapt2 at: $AAPT2_PATH"
|
||||
# aapt2 doesn't support 'dump packagename', so we'll use 'dump xmltree' to parse the manifest
|
||||
ACTUAL_PACKAGE=$("$AAPT2_PATH" dump xmltree "$APK_PATH" AndroidManifest.xml 2>/dev/null | grep -A1 "package=" | grep "A:" | sed -E "s/.*A: package=\"([^\"]+)\".*/\1/" | head -1)
|
||||
else
|
||||
log_error "Neither aapt nor aapt2 found in $ANDROID_HOME/build-tools"
|
||||
echo "Please ensure your Android build-tools are installed correctly."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$ACTUAL_PACKAGE" ]; then
|
||||
log_success "Determined APK package name: $ACTUAL_PACKAGE"
|
||||
else
|
||||
log_warning "Could not determine package name from APK, assuming default: com.proofofpassportapp"
|
||||
ACTUAL_PACKAGE="com.proofofpassportapp"
|
||||
fi
|
||||
|
||||
# Install the app, replacing any existing installation.
|
||||
# The -r flag allows adb to replace an existing app, which is safer than
|
||||
# trying to uninstall first, especially in a CI environment where the
|
||||
# emulator state might be inconsistent.
|
||||
echo "Installing app..."
|
||||
if ! "$ADB_CMD" -s "$EMULATOR_ID" install -r "$APK_PATH"; then
|
||||
log_error "Android app installation failed"
|
||||
exit 1
|
||||
fi
|
||||
log_success "App successfully installed"
|
||||
|
||||
# Verify installation
|
||||
log_info "🔍 Verifying app installation..."
|
||||
|
||||
# Give a moment for installation to settle
|
||||
sleep 2
|
||||
|
||||
# Check if the package is installed using the detected package name
|
||||
echo "Checking installed packages for: $ACTUAL_PACKAGE"
|
||||
PACKAGE_CHECK=$("$ADB_CMD" -s "$EMULATOR_ID" shell pm list packages | grep "$ACTUAL_PACKAGE" || echo "")
|
||||
if [ -n "$PACKAGE_CHECK" ]; then
|
||||
log_success "App package verified on device: $PACKAGE_CHECK"
|
||||
else
|
||||
log_warning "Package '$ACTUAL_PACKAGE' not found, doing broader search..."
|
||||
|
||||
# Try searching for parts of the package name
|
||||
PARTIAL_CHECKS=(
|
||||
"proofofpassport"
|
||||
"warroom"
|
||||
"passport"
|
||||
)
|
||||
|
||||
FOUND_PACKAGE=""
|
||||
for PARTIAL in "${PARTIAL_CHECKS[@]}"; do
|
||||
PARTIAL_RESULT=$("$ADB_CMD" -s "$EMULATOR_ID" shell pm list packages | grep "$PARTIAL" || echo "")
|
||||
if [ -n "$PARTIAL_RESULT" ]; then
|
||||
echo "Found packages containing '$PARTIAL': $PARTIAL_RESULT"
|
||||
FOUND_PACKAGE="true"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$FOUND_PACKAGE" ]; then
|
||||
log_error "No related packages found on device"
|
||||
echo "Attempting to continue anyway - Maestro might still work..."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Test if the app can be launched directly
|
||||
log_info "🚀 Testing app launch capability..."
|
||||
"$ADB_CMD" -s "$EMULATOR_ID" shell am start -n "$ACTUAL_PACKAGE/.MainActivity" || {
|
||||
log_warning "Direct app launch test failed - this might be expected if the main activity name is different"
|
||||
}
|
||||
}
|
||||
|
||||
# Cleanup function for Android emulator
|
||||
cleanup_android_emulator() {
|
||||
if [ -n "$EMULATOR_PID" ] && kill -0 "$EMULATOR_PID" 2>/dev/null; then
|
||||
log_info "Cleaning up Android emulator (PID: $EMULATOR_PID)..."
|
||||
# Kill the emulator process silently
|
||||
kill "$EMULATOR_PID" >/dev/null 2>&1
|
||||
# Wait a moment for graceful shutdown
|
||||
sleep 2
|
||||
# Force kill if still running
|
||||
if kill -0 "$EMULATOR_PID" 2>/dev/null; then
|
||||
kill -9 "$EMULATOR_PID" >/dev/null 2>&1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Also silence any remaining emulator processes that might be hanging
|
||||
if [ -n "$FIRST_AVD" ]; then
|
||||
pkill -f "emulator.*$FIRST_AVD" >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
|
||||
# Main platform runners
|
||||
run_ios_tests() {
|
||||
echo "🍎 Starting local iOS e2e testing..."
|
||||
|
||||
shutdown_all_simulators
|
||||
check_metro_running
|
||||
setup_ios_environment
|
||||
setup_ios_simulator
|
||||
build_ios_app
|
||||
install_ios_app
|
||||
|
||||
log_info "⏰ Giving the simulator a moment to settle before starting tests..."
|
||||
sleep 15
|
||||
|
||||
run_maestro_tests
|
||||
MAESTRO_STATUS=$?
|
||||
|
||||
log_success "Local iOS e2e testing completed!"
|
||||
|
||||
shutdown_all_simulators
|
||||
|
||||
exit $MAESTRO_STATUS
|
||||
}
|
||||
|
||||
run_android_tests() {
|
||||
echo "🤖 Starting local Android e2e testing..."
|
||||
|
||||
# Set up trap to cleanup emulator on script exit
|
||||
trap cleanup_android_emulator EXIT
|
||||
|
||||
# Only check Metro if not in workflow match mode
|
||||
if [ "$WORKFLOW_MATCH" != "true" ]; then
|
||||
check_metro_running
|
||||
fi
|
||||
|
||||
setup_android_environment
|
||||
build_android_app
|
||||
install_android_app
|
||||
|
||||
log_info "⏰ Giving the emulator a moment to settle before starting tests..."
|
||||
sleep 45
|
||||
|
||||
run_maestro_tests
|
||||
MAESTRO_STATUS=$?
|
||||
|
||||
log_success "Local Android e2e testing completed!"
|
||||
exit $MAESTRO_STATUS
|
||||
}
|
||||
|
||||
# Main execution
|
||||
main() {
|
||||
check_directory
|
||||
|
||||
if [ -z "$PLATFORM" ]; then
|
||||
print_usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for workflow match mode
|
||||
WORKFLOW_MATCH="false"
|
||||
for arg in "$@"; do
|
||||
if [ "$arg" = "--workflow-match" ]; then
|
||||
WORKFLOW_MATCH="true"
|
||||
log_info "🔧 Running in workflow match mode (Release builds, no Metro)"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
setup_maestro
|
||||
build_dependencies
|
||||
|
||||
case "$PLATFORM" in
|
||||
ios)
|
||||
run_ios_tests
|
||||
;;
|
||||
android)
|
||||
run_android_tests
|
||||
;;
|
||||
*)
|
||||
log_error "Invalid platform: $PLATFORM"
|
||||
echo "Valid options: ios, android"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main "$@"
|
||||
@@ -1,11 +1,12 @@
|
||||
// SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11
|
||||
|
||||
// https://docs.ethers.org/v6/cookbook/react-native/
|
||||
// eslint-disable-next-line simple-import-sort/imports
|
||||
import { ethers } from 'ethers';
|
||||
import { hmac } from '@noble/hashes/hmac';
|
||||
import { pbkdf2 as noblePbkdf2 } from '@noble/hashes/pbkdf2';
|
||||
import { sha256 as nobleSha256 } from '@noble/hashes/sha256';
|
||||
import { sha512 as nobleSha512 } from '@noble/hashes/sha512';
|
||||
import { ethers } from 'ethers';
|
||||
|
||||
function randomBytes(length: number): Uint8Array {
|
||||
if (typeof globalThis.crypto?.getRandomValues !== 'function') {
|
||||
|
||||
6
app/tests/e2e/launch.android.flow.yaml
Normal file
6
app/tests/e2e/launch.android.flow.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
appId: com.proofofpassportapp
|
||||
---
|
||||
- launchApp
|
||||
- extendedWaitUntil:
|
||||
visible: "I have a Passport or Biometric ID"
|
||||
timeout: 30000
|
||||
6
app/tests/e2e/launch.ios.flow.yaml
Normal file
6
app/tests/e2e/launch.ios.flow.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
appId: com.warroom.proofofpassport
|
||||
---
|
||||
- launchApp
|
||||
- extendedWaitUntil:
|
||||
visible: "I have a Passport or Biometric ID"
|
||||
timeout: 30000
|
||||
@@ -1,10 +1,11 @@
|
||||
// SPDX-License-Identifier: BUSL-1.1; Copyright (c) 2025 Social Connect Labs, Inc.; Licensed under BUSL-1.1 (see LICENSE); Apache-2.0 from 2029-06-11
|
||||
|
||||
// Register crypto polyfills
|
||||
import '../../src/utils/ethers';
|
||||
|
||||
// eslint-disable-next-line simple-import-sort/imports
|
||||
import { ethers } from 'ethers';
|
||||
|
||||
import '../../src/utils/ethers';
|
||||
|
||||
describe('ethers crypto polyfills', () => {
|
||||
it('randomBytes returns requested length and unique values', () => {
|
||||
const a = ethers.randomBytes(16);
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
],
|
||||
"scripts": {
|
||||
"build": "yarn workspaces foreach --topological-dev --parallel --exclude @selfxyz/contracts -i --all run build",
|
||||
"format": "yarn workspaces foreach --parallel -i --all --exclude self-workspace-root run format",
|
||||
"format": "yarn format:root && yarn format:github && yarn workspaces foreach --parallel -i --all --exclude self-workspace-root run format",
|
||||
"format:github": "prettier --parser yaml --write .github/**/*.yml --single-quote false",
|
||||
"format:root": "prettier --parser markdown --write *.md && prettier --parser yaml --write .*.{yml,yaml} --single-quote false",
|
||||
"gitleaks": "gitleaks protect --staged --redact --config=.gitleaks.toml",
|
||||
"postinstall": "patch-package",
|
||||
"lint": "yarn workspaces foreach --parallel -i --all --exclude self-workspace-root run lint",
|
||||
|
||||
Reference in New Issue
Block a user