diff --git a/.circleci/config.yml b/.circleci/config.yml index 00466cf8..11f422f8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -417,6 +417,219 @@ jobs: name: release command: bundle exec fastlane github_release + run-maestro-e2e-tests-ios: + macos: + xcode: 26.3.0 + resource_class: m4pro.medium + steps: + - checkout + - install-bundle-dependencies + - run: + name: Install Rosetta 2 + command: softwareupdate --install-rosetta --agree-to-license || true + - run: + name: Install Java for Maestro + command: | + brew install openjdk@17 + sudo ln -sfn "$(brew --prefix openjdk@17)/libexec/openjdk.jdk" /Library/Java/JavaVirtualMachines/openjdk-17.jdk + echo 'export JAVA_HOME=$(/usr/libexec/java_home -v 17)' >> "$BASH_ENV" + - revenuecat/install-maestro + - run: + name: Install Unity Hub and Editor + command: | + brew install --cask unity-hub + /Applications/Unity\ Hub.app/Contents/MacOS/Unity\ Hub -- --headless install \ + --version 6000.2.6f2 \ + --changeset 4a4dcaec6541 \ + -a arm64 \ + --module ios + echo "Verifying iOS module installation..." + if [ ! -d "/Applications/Unity/Hub/Editor/6000.2.6f2/PlaybackEngines/iOSSupport" ]; then + echo "iOS module not found, installing separately..." + /Applications/Unity\ Hub.app/Contents/MacOS/Unity\ Hub -- --headless install-modules \ + --version 6000.2.6f2 \ + -m ios + fi + ls -la /Applications/Unity/Hub/Editor/6000.2.6f2/PlaybackEngines/ || echo "No PlaybackEngines dir" + no_output_timeout: 30m + - run: + name: Clear quarantine attributes from Unity + command: | + sudo xattr -cr /Applications/Unity/Hub/Editor/6000.2.6f2/Unity.app || true + sudo xattr -cr "/Applications/Unity/Hub/Editor/6000.2.6f2/Unity.app/Contents/Frameworks/UnityLicensingClient.app" || true + sudo spctl --master-disable 2>/dev/null || true + - run: + name: Activate Unity License + command: | + rm -f "/Library/Application Support/Unity/Unity_lic.ulf" 2>/dev/null || true + rm -rf ~/Library/Unity/licenses 2>/dev/null || true + rm -rf "/tmp/Unity.Licensing.Client" 2>/dev/null || true + mkdir -p "/Library/Application Support/Unity" 2>/dev/null || true + + activated=false + for i in 1 2 3 4 5; do + pkill -9 -f "Unity.Licensing.Client" 2>/dev/null || true + pkill -9 -f "Unity " 2>/dev/null || true + sleep 5 + + echo "=== License activation attempt $i ===" + /Applications/Unity/Hub/Editor/6000.2.6f2/Unity.app/Contents/MacOS/Unity \ + -batchmode -nographics \ + -serial "$UNITY_SERIAL" \ + -username "$UNITY_EMAIL" \ + -password "$UNITY_PASSWORD" \ + -logFile - \ + -quit & + UNITY_PID=$! + ( sleep 120 && kill -9 $UNITY_PID 2>/dev/null ) & + WATCHDOG_PID=$! + if wait $UNITY_PID 2>/dev/null; then + kill $WATCHDOG_PID 2>/dev/null || true + activated=true + break + fi + kill $WATCHDOG_PID 2>/dev/null || true + + echo "Attempt $i failed, cleaning up and retrying in 60s..." + pkill -9 -f "Unity.Licensing.Client" 2>/dev/null || true + pkill -9 -f "Unity " 2>/dev/null || true + rm -f "/Library/Application Support/Unity/Unity_lic.ulf" 2>/dev/null || true + sleep 60 + done + if [ "$activated" = false ]; then + echo "All license activation attempts failed" + ls -la "/Library/Application Support/Unity/" 2>/dev/null || true + exit 1 + fi + echo "License activated successfully" + ls -la "/Library/Application Support/Unity/" 2>/dev/null || true + no_output_timeout: 15m + - run: + name: Boot iOS Simulator + command: | + xcrun simctl boot "iPhone 17" || true + xcrun simctl bootstatus "iPhone 17" -b + - run: + name: Run Maestro E2E Tests (iOS) + command: bundle exec fastlane run_maestro_e2e_tests_ios + no_output_timeout: 30m + - store_test_results: + path: fastlane/test_output + - store_artifacts: + path: fastlane/test_output + - run: + name: Return Unity License + command: | + /Applications/Unity/Hub/Editor/6000.2.6f2/Unity.app/Contents/MacOS/Unity \ + -batchmode -quit -nographics \ + -returnlicense \ + -logFile - || true + when: always + + run-maestro-e2e-tests-android: + macos: + xcode: 26.3.0 + resource_class: m4pro.medium + steps: + - checkout + - install-bundle-dependencies + - run: + name: Install Rosetta 2 + command: softwareupdate --install-rosetta --agree-to-license || true + - run: + name: Install Java for Maestro + command: | + brew install openjdk@17 + sudo ln -sfn "$(brew --prefix openjdk@17)/libexec/openjdk.jdk" /Library/Java/JavaVirtualMachines/openjdk-17.jdk + echo 'export JAVA_HOME=$(/usr/libexec/java_home -v 17)' >> "$BASH_ENV" + - revenuecat/install-maestro + - run: + name: Install Android SDK + command: | + brew install --cask android-commandlinetools + yes | sdkmanager --licenses || true + sdkmanager "platform-tools" "emulator" "system-images;android-34;google_apis;arm64-v8a" + - run: + name: Install Unity Hub and Editor + command: | + brew install --cask unity-hub + /Applications/Unity\ Hub.app/Contents/MacOS/Unity\ Hub -- --headless install \ + --version 6000.2.6f2 \ + --changeset 4a4dcaec6541 \ + -a arm64 \ + --module android + echo "Verifying Android module installation..." + ls -la /Applications/Unity/Hub/Editor/6000.2.6f2/PlaybackEngines/ || echo "No PlaybackEngines dir" + no_output_timeout: 60m + - run: + name: Clear quarantine attributes from Unity + command: | + sudo xattr -cr /Applications/Unity/Hub/Editor/6000.2.6f2/Unity.app || true + sudo xattr -cr "/Applications/Unity/Hub/Editor/6000.2.6f2/Unity.app/Contents/Frameworks/UnityLicensingClient.app" || true + sudo spctl --master-disable 2>/dev/null || true + - run: + name: Activate Unity License + command: | + rm -f "/Library/Application Support/Unity/Unity_lic.ulf" 2>/dev/null || true + rm -rf ~/Library/Unity/licenses 2>/dev/null || true + rm -rf "/tmp/Unity.Licensing.Client" 2>/dev/null || true + mkdir -p "/Library/Application Support/Unity" 2>/dev/null || true + + activated=false + for i in 1 2 3 4 5; do + pkill -9 -f "Unity.Licensing.Client" 2>/dev/null || true + pkill -9 -f "Unity " 2>/dev/null || true + sleep 5 + + echo "=== License activation attempt $i ===" + /Applications/Unity/Hub/Editor/6000.2.6f2/Unity.app/Contents/MacOS/Unity \ + -batchmode -nographics \ + -serial "$UNITY_SERIAL" \ + -username "$UNITY_EMAIL" \ + -password "$UNITY_PASSWORD" \ + -logFile - \ + -quit & + UNITY_PID=$! + ( sleep 120 && kill -9 $UNITY_PID 2>/dev/null ) & + WATCHDOG_PID=$! + if wait $UNITY_PID 2>/dev/null; then + kill $WATCHDOG_PID 2>/dev/null || true + activated=true + break + fi + kill $WATCHDOG_PID 2>/dev/null || true + + echo "Attempt $i failed, cleaning up and retrying in 60s..." + pkill -9 -f "Unity.Licensing.Client" 2>/dev/null || true + pkill -9 -f "Unity " 2>/dev/null || true + rm -f "/Library/Application Support/Unity/Unity_lic.ulf" 2>/dev/null || true + sleep 60 + done + if [ "$activated" = false ]; then + echo "All license activation attempts failed" + ls -la "/Library/Application Support/Unity/" 2>/dev/null || true + exit 1 + fi + echo "License activated successfully" + ls -la "/Library/Application Support/Unity/" 2>/dev/null || true + no_output_timeout: 15m + - run: + name: Run Maestro E2E Tests (Android) + command: bundle exec fastlane run_maestro_e2e_tests_android + no_output_timeout: 30m + - store_test_results: + path: fastlane/test_output + - store_artifacts: + path: fastlane/test_output + - run: + name: Return Unity License + command: | + /Applications/Unity/Hub/Editor/6000.2.6f2/Unity.app/Contents/MacOS/Unity \ + -batchmode -quit -nographics \ + -returnlicense \ + -logFile - || true + when: always + update-hybrid-common-versions: description: "Creates a PR updating purchases-hybrid-common to latest release" docker: @@ -468,6 +681,16 @@ workflows: - archive-ios: requires: - build-integration-tests-ios + - run-maestro-e2e-tests-ios: + context: + - maestro-e2e-tests + - e2e-tests + - unity + - run-maestro-e2e-tests-android: + context: + - maestro-e2e-tests + - e2e-tests + - unity - hold: type: approval requires: @@ -516,6 +739,28 @@ workflows: - revenuecat/automatic-bump: ruby_version: "3.2.0" + maestro-e2e-tests-ios: + when: + or: + - equal: ["maestro_e2e_tests", << pipeline.schedule.name >>] + jobs: + - run-maestro-e2e-tests-ios: + context: + - maestro-e2e-tests + - e2e-tests + - unity + + maestro-e2e-tests-android: + when: + or: + - equal: ["maestro_e2e_tests", << pipeline.schedule.name >>] + jobs: + - run-maestro-e2e-tests-android: + context: + - maestro-e2e-tests + - e2e-tests + - unity + update-hybrid-common-versions: when: equal: [ upgrade-hybrid-common, << pipeline.parameters.action >> ] diff --git a/fastlane/Fastfile b/fastlane/Fastfile index a5b1bfe6..38945bad 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -130,6 +130,92 @@ lane :update_hybrid_common do |options| ) end +desc "Run maestro E2E tests on iOS" +lane :run_maestro_e2e_tests_ios do + UI.user_error!("RC_E2E_TEST_API_KEY_PRODUCTION_TEST_STORE is not set") if ENV["RC_E2E_TEST_API_KEY_PRODUCTION_TEST_STORE"].to_s.empty? + project_path = "../e2e-tests/MaestroTestApp" + xcode_project_path = "#{project_path}/build/ios" + derived_data_path = "#{project_path}/build/xcode" + + sh("sed -i '' 's/MAESTRO_TESTS_REVENUECAT_API_KEY/'\"$RC_E2E_TEST_API_KEY_PRODUCTION_TEST_STORE\"'/g' #{project_path}/Assets/Scripts/MaestroTestApp.cs") + + unity_path = ENV['UNITY_PATH'] || '/Applications/Unity/Hub/Editor/6000.2.6f2/Unity.app/Contents/MacOS/Unity' + + sh( + "#{unity_path}", + "-batchmode", + "-quit", + "-projectPath", project_path, + "-executeMethod", "SceneSetup.SetupScene", + "-nographics", + "-logFile", "-" + ) + + sh( + "#{unity_path}", + "-batchmode", + "-quit", + "-projectPath", project_path, + "-buildTarget", "iOS", + "-executeMethod", "BuildScript.BuildIOS", + "-nographics", + "-logFile", "-" + ) + + sh( + "xcodebuild", + "-project", "#{xcode_project_path}/Unity-iPhone.xcodeproj", + "-scheme", "Unity-iPhone", + "-configuration", "Debug", + "-sdk", "iphonesimulator", + "-destination", "platform=iOS Simulator,name=iPhone 17", + "-derivedDataPath", derived_data_path + ) + + app_path = "#{derived_data_path}/Build/Products/Debug-iphonesimulator/MaestroTestApp.app" + sh("xcrun simctl install booted '#{app_path}'") + + sh("mkdir -p test_output") + sh("maestro test --format junit --output test_output/report.xml --test-output-dir test_output ../e2e-tests/maestro/") +end + +desc "Run maestro E2E tests on Android" +lane :run_maestro_e2e_tests_android do + UI.user_error!("RC_E2E_TEST_API_KEY_PRODUCTION_TEST_STORE is not set") if ENV["RC_E2E_TEST_API_KEY_PRODUCTION_TEST_STORE"].to_s.empty? + project_path = "../e2e-tests/MaestroTestApp" + apk_path = "#{project_path}/build/android/MaestroTestApp.apk" + + sh("sed -i 's/MAESTRO_TESTS_REVENUECAT_API_KEY/'\"$RC_E2E_TEST_API_KEY_PRODUCTION_TEST_STORE\"'/g' #{project_path}/Assets/Scripts/MaestroTestApp.cs") + + unity_path = ENV['UNITY_PATH'] || '/Applications/Unity/Hub/Editor/6000.2.6f2/Unity.app/Contents/MacOS/Unity' + + sh( + "#{unity_path}", + "-batchmode", + "-quit", + "-projectPath", project_path, + "-executeMethod", "SceneSetup.SetupScene", + "-nographics", + "-logFile", "-" + ) + + sh( + "#{unity_path}", + "-batchmode", + "-quit", + "-projectPath", project_path, + "-buildTarget", "Android", + "-executeMethod", "BuildScript.BuildAndroid", + "-nographics", + "-logFile", "-" + ) + + sh("adb install '#{apk_path}'") + + sh("mkdir -p test_output") + sh("maestro test --format junit --output test_output/report.xml --test-output-dir test_output ../e2e-tests/maestro/") +end + desc "Tag current branch with current version number" lane :tag_current_branch do |options| version_number = current_version_number