Skip to content

Commit

Permalink
Actions for iOS and Android (#2)
Browse files Browse the repository at this point in the history
* draft of the plugin

* new line in bot

* drop ruby version

* add correct version of ruby

* use ruby 2.7 for setup

* separate android and ios actions

* use already defined action

* remove commented code

* remove build_install and emu_launch methods

* fix issues

* add maestro prefix and env_name for flow_file

* fix bugs and add emu kill command

* leave only needed parameters

* wip

* override demo mode

* rename demo_mode method

* add tests for parameter passing

* parameter tests for android

* move dev deps to gemfile

* leave only necessary params

* add default opt for params
  • Loading branch information
nemanjar7 authored Dec 16, 2024
1 parent f220e83 commit d666dfa
Show file tree
Hide file tree
Showing 9 changed files with 371 additions and 71 deletions.
8 changes: 8 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
time: "08:00"
timezone: "America/New_York"
8 changes: 4 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/cache@v1
- uses: actions/checkout@v4
- uses: actions/cache@v2
with:
path: vendor/bundle
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile') }}
Expand All @@ -17,13 +17,13 @@ jobs:
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.5
ruby-version: 2.7
- name: Install dependencies
run: bundle check || bundle install --jobs=4 --retry=3 --path vendor/bundle
- name: Run tests
run: bundle exec rake
- name: Upload artifact
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: test-results
path: test-results
16 changes: 3 additions & 13 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,27 +1,17 @@
source('https://rubygems.org')

# Provides a consistent environment for Ruby projects by tracking and installing exact gem versions.
gem 'bundler'
# Automation tool for mobile developers.
gemspec

# Add development dependencies here
gem 'fastlane', '>= 2.225.0'
# Provides an interactive debugging environment for Ruby.
gem 'pry'
# A simple task automation tool.
gem 'rake'
# Behavior-driven testing tool for Ruby.
gem 'rspec'
# Formatter for RSpec to generate JUnit compatible reports.
gem 'rspec_junit_formatter'
# A Ruby static code analyzer and formatter.
gem 'rubocop', '1.50.2'
# A collection of RuboCop cops for performance optimizations.
gem 'rubocop-performance'
# A RuboCop extension focused on enforcing tools.
gem 'rubocop-require_tools'
# SimpleCov is a code coverage analysis tool for Ruby.
gem 'simplecov'

gemspec

plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)
2 changes: 1 addition & 1 deletion fastlane-plugin-maestro_orchestration.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ Gem::Specification.new do |spec|

# Don't add a dependency to fastlane or fastlane_re
# since this would cause a circular dependency

# spec.add_dependency 'your-dependency', '~> 1.0.0'
spec.add_dependency('fastlane-plugin-android_emulator', '~> 1.2', '>= 1.2.1')
end
1 change: 0 additions & 1 deletion fastlane/Pluginfile

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
require 'fastlane/action'
require 'fastlane_core/configuration/config_item'
require 'fastlane/plugin/android_emulator'
require_relative '../helper/maestro_orchestration_helper'

module Fastlane
module Actions
class MaestroOrchestrationAndroidAction < Action
def self.run(params)
required_params = [:emulator_package, :emulator_device, :maestro_flow_file]
missing_params = required_params.select { |param| params[param].nil? }

if missing_params.any?
missing_params.each do |param|
UI.error("Missing parameter: #{param}")
end
raise "Missing required parameters: #{missing_params.join(', ')}"
end

Fastlane::Actions::AndroidEmulatorAction.run(
name: params[:emulator_name],
sdk_dir: params[:sdk_dir],
package: params[:emulator_package],
device: params[:emulator_device],
port: params[:emulator_port],
demo_mode: false,
cold_boot: true,
additional_options: []
)
sleep(5)
demo_mode(params)
build_and_install_android_app(params)

UI.message("Running Maestro tests on Android...")
sh("maestro test #{params[:maestro_flow_file]}")
UI.success("Finished Maestro tests on Android.")

UI.message("Exit demo mode and kill Android emulator...")
adb = "#{params[:sdk_dir]}/platform-tools/adb"
system("#{adb} shell am broadcast -a com.android.systemui.demo -e command exit")
sleep(3)
system("#{adb} emu kill")
UI.success("Android emulator killed. Process finished.")
end

def self.demo_mode(params)
UI.message("Checking and allowing demo mode on Android emulator...")
sh("#{params[:sdk_dir]}/platform-tools/adb shell settings put global sysui_demo_allowed 1")
sh("#{params[:sdk_dir]}/platform-tools/adb shell settings get global sysui_demo_allowed")

UI.message("Setting demo mode commands...")
sh("#{params[:sdk_dir]}/platform-tools/adb shell am broadcast -a com.android.systemui.demo -e command enter")
sh("#{params[:sdk_dir]}/platform-tools/adb shell am broadcast -a com.android.systemui.demo -e command clock -e hhmm 1200")
sh("#{params[:sdk_dir]}/platform-tools/adb shell am broadcast -a com.android.systemui.demo -e command battery -e level 100")
sh("#{params[:sdk_dir]}/platform-tools/adb shell am broadcast -a com.android.systemui.demo -e command network -e wifi show -e level 4")
sh("#{params[:sdk_dir]}/platform-tools/adb shell am broadcast -a com.android.systemui.demo -e command network -e mobile show -e datatype none -e level 4")
end

def self.build_and_install_android_app(params)
UI.message("Building Android app...")
other_action.gradle(task: "assembleDebug")

apk_path = Dir["app/build/outputs/apk/debug/app-debug.apk"].first

if apk_path.nil?
UI.user_error!("Error: APK file not found in build outputs.")
end

UI.message("Found APK file at: #{apk_path}")
sh("adb install -r '#{apk_path}'")
UI.success("APK installed on Android emulator.")
end

def self.description
"Boots an Android emulator, builds the app, installs it, and runs Maestro tests"
end

def self.available_options
[
FastlaneCore::ConfigItem.new(
key: :sdk_dir,
env_name: "MAESTRO_ANDROID_SDK_DIR",
description: "Path to the Android SDK DIR",
default_value: "~/Library/Android/sdk",
optional: true,
verify_block: proc do |value|
UI.user_error!("No ANDROID_SDK_DIR given, pass using `sdk_dir: 'sdk_dir'`") unless value && !value.empty?
end
),
FastlaneCore::ConfigItem.new(
key: :emulator_name,
env_name: "MAESTRO_AVD_NAME",
description: "Name of the AVD",
default_value: "Maestro_Android_Emulator",
optional: true
),
FastlaneCore::ConfigItem.new(
key: :emulator_package,
env_name: "MAESTRO_AVD_PACKAGE",
description: "The selected system image of the emulator",
default_value: "system-images;android-35;google_apis_playstore;arm64-v8a",
optional: true
),
FastlaneCore::ConfigItem.new(
key: :emulator_device,
env_name: "MAESTRO_AVD_DEVICE",
description: "Device",
default_value: "pixel_8_pro",
optional: true
),
FastlaneCore::ConfigItem.new(
key: :location,
env_name: "MAESTRO_AVD_LOCATION",
description: "Set location of the emulator '<longitude> <latitude>'",
default_value: "28.0362979, -82.4930012",
optional: true
),
FastlaneCore::ConfigItem.new(
key: :emulator_port,
env_name: "MAESTRO_AVD_PORT",
description: "Port of the emulator",
default_value: "5554",
optional: true
),
FastlaneCore::ConfigItem.new(
key: :maestro_flow_file,
env_name: "MAESTRO_ANDROID_FLOW_FILE",
description: "The path to the Maestro flow YAML file",
optional: false,
type: String
)
]
end

def self.is_supported?(platform)
platform == :android
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
require 'fastlane/action'
require 'fastlane_core/configuration/config_item'
require_relative '../helper/maestro_orchestration_helper'

module Fastlane
module Actions
class MaestroOrchestrationIosAction < Action
def self.run(params)
required_params = [:simulator_device, :scheme, :workspace, :maestro_flow_file]
missing_params = required_params.select { |param| params[param].nil? }

if missing_params.any?
missing_params.each do |param|
UI.error("Missing parameter: #{param}")
end
raise "Missing required parameters: #{missing_params.join(', ')}"
end

device_name = params[:simulator_device]
boot_ios_simulator(device_name)
build_and_install_ios_app(params)

UI.message("Running Maestro tests on iOS...")
`maestro test #{params[:maestro_flow_file]}`
UI.success("Finished Maestro tests on iOS.")
end

def self.boot_ios_simulator(device_name)
simulators_list = `xcrun simctl list devices`.strip

unless simulators_list.include?(device_name)
UI.error("Simulator '#{device_name}' not found.")
return
end

device_status = simulators_list.match(/#{Regexp.quote(device_name)}.*\((.*?)\)/)
if device_status && device_status[1].casecmp('Booted').zero?
UI.success("#{device_name} is already booted.")
else
UI.message("#{device_name} is not booted. Booting now...")
system("xcrun simctl boot '#{device_name}'")
UI.message("Waiting for the simulator to boot...")
sleep(5)
UI.success("Simulator '#{device_name}' is booted.")
end
end

def self.build_and_install_ios_app(params)
UI.message("Building iOS app with scheme: #{params[:scheme]}")
other_action.gym(
workspace: params[:workspace],
scheme: params[:scheme],
destination: "platform=iOS Simulator,name=#{params[:simulator_device]}",
configuration: "Debug",
clean: true,
sdk: "iphonesimulator",
build_path: "./build",
skip_archive: true,
skip_package_ipa: true,
include_symbols: false,
include_bitcode: false,
xcargs: "-UseModernBuildSystem=YES"
)

derived_data_path = File.expand_path("~/Library/Developer/Xcode/DerivedData")
app_path = Dir["#{derived_data_path}/**/#{params[:scheme]}.app"].first

if app_path.nil?
UI.user_error!("Error: .app file not found in DerivedData.")
end

UI.message("Found .app file at: #{app_path}")
sh("xcrun simctl install booted '#{app_path}'")
UI.success("App installed on iOS simulator.")
end

def self.description
"Boots an iOS simulator, builds the app, installs it, and runs Maestro tests"
end

def self.available_options
[
FastlaneCore::ConfigItem.new(
key: :simulator_device,
env_name: "MAESTRO_IOS_DEVICE",
description: "The iOS simulator device to boot",
optional: false,
type: String
),
FastlaneCore::ConfigItem.new(
key: :scheme,
env_name: "MAESTRO_IOS_SCHEME",
description: "The iOS app scheme to build",
optional: false,
type: String
),
FastlaneCore::ConfigItem.new(
key: :workspace,
env_name: "MAESTRO_IOS_WORKSPACE",
description: "The Xcode workspace",
optional: false,
type: String
),
FastlaneCore::ConfigItem.new(
key: :maestro_flow_file,
env_name: "MAESTRO_IOS_FLOW_FILE",
description: "The path to the Maestro flows YAML file",
optional: false,
type: String
)
]
end

def self.is_supported?(platform)
platform == :ios
end
end
end
end
Loading

0 comments on commit d666dfa

Please sign in to comment.