Skip to content

Commit

Permalink
Fix: Use fastlane 'adb' action instead of 'sh' command (#11)
Browse files Browse the repository at this point in the history
* fix: use fastlane adb action instead of shell command

* fix missed adb commands

* move avd logic in helper

* use adb_helper directly

* add emulator helper class

* account for different versions of the cmdline tools

* allow different naming of apk file
  • Loading branch information
nemanjar7 authored Jan 1, 2025
1 parent 601fb81 commit 3fe4904
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 26 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require 'fastlane/action'
require 'fastlane_core/configuration/config_item'
require 'fastlane/plugin/android_emulator'
require 'fastlane/helper/adb_helper'
require_relative '../helper/maestro_orchestration_helper'

module Fastlane
Expand All @@ -18,15 +18,18 @@ def self.run(params)
end

UI.message("Emualtor_device: #{params[:emulator_device]}")
adb = "#{params[:sdk_dir]}/platform-tools/adb"
UI.message("SDK DIR: #{params[:sdk_dir]}")
adb = Helper::AdbHelper.new
UI.message("ADB: #{adb.adb_path}")

setup_emulator(params)
sleep(5)
demo_mode(params)
install_android_app(params)

UI.message("Running Maestro tests on Android...")
devices = `#{adb} devices`.split("\n").drop(1)
devices = adb.trigger(command: "devices").split("\n").drop(1)
serial = nil
if devices.empty?
UI.message("No running emulators found.")
else
Expand All @@ -42,18 +45,19 @@ def self.run(params)
end

UI.message("Exit demo mode and kill Android emulator...")
system("#{adb} shell am broadcast -a com.android.systemui.demo -e command exit")
sleep(3)
system("#{adb} emu kill")
adb.trigger(command: "shell am broadcast -a com.android.systemui.demo -e command exit")
sleep(5)
adb.trigger(command: "emu kill", serial: serial)
UI.success("Android emulator killed. Process finished.")
end

def self.setup_emulator(params)
sdk_dir = params[:sdk_dir]
adb = "#{sdk_dir}/android-commandlinetools/platform-tools/adb"
emulator = Helper::EmulatorHelper.new
adb = Helper::AdbHelper.new
avdmanager = Helper::AvdHelper.new

UI.message("Stop all running emulators...")
devices = `#{adb} devices`.split("\n").drop(1)
devices = adb.trigger(command: "devices").split("\n").drop(1)
UI.message("Devices: #{devices}")

if devices.empty?
Expand All @@ -63,51 +67,53 @@ def self.setup_emulator(params)
devices.each do |device|
serial = device.split("\t").first # Extract the serial number
if serial.include?("emulator") # Check if it's an emulator
system("#{adb} -s #{serial} emu kill") # Stop the emulator
adb.trigger(command: "emu kill", serial: serial)
system("Stopped emulator: #{serial}")
end
end
end

UI.message("Setting up new Android emulator...")
system("#{sdk_dir}/android-commandlinetools/cmdline-tools/latest/bin/avdmanager create avd -n '#{params[:emulator_name]}' -f -k '#{params[:emulator_package]}' -d '#{params[:emulator_device]}'")
avdmanager.create_avd(name: params[:emulator_name], package: params[:emulator_package], device: params[:emulator_device])
sleep(5)

UI.message("Starting Android emulator...")
system("#{sdk_dir}/android-commandlinetools/emulator/emulator -avd #{params[:emulator_name]} -port #{params[:emulator_port]} > /dev/null 2>&1 &")
sh("#{adb} -e wait-for-device")
emulator.start_emulator(name: params[:emulator_name], port: params[:emulator_port])
adb.trigger(command: "wait-for-device")

sleep(5) while sh("#{adb} -e shell getprop sys.boot_completed").strip != "1"
sleep(5) while adb.trigger(command: "shell getprop sys.boot_completed").strip != "1"

UI.success("Android emulator started.")
end

def self.demo_mode(params)
adb = Helper::AdbHelper.new

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")
adb.trigger(command: "shell settings put global sysui_demo_allowed 1")
adb.trigger(command: "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")
adb.trigger(command: "shell am broadcast -a com.android.systemui.demo -e command enter")
adb.trigger(command: "shell am broadcast -a com.android.systemui.demo -e command clock -e hhmm 1200")
adb.trigger(command: "shell am broadcast -a com.android.systemui.demo -e command battery -e level 100")
adb.trigger(command: "shell am broadcast -a com.android.systemui.demo -e command network -e wifi show -e level 4")
adb.trigger(command: "shell am broadcast -a com.android.systemui.demo -e command network -e mobile show -e datatype none -e level 4")
end

def self.install_android_app(params)
UI.message("Installing Android app...")

sdk_dir = params[:sdk_dir]
adb = "#{sdk_dir}/platform-tools/adb"
apk_path = Dir["app/build/outputs/apk/release/app-release.apk"].first
adb = Helper::AdbHelper.new
apk_path = Dir["app/build/outputs/apk/release/*.apk"].first
UI.success("APK path: #{apk_path}")

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}'")
adb.trigger(command: "install -r '#{apk_path}'")
UI.success("APK installed on Android emulator.")
end

Expand All @@ -121,7 +127,7 @@ def self.available_options
key: :sdk_dir,
env_name: "MAESTRO_ANDROID_SDK_DIR",
description: "Path to the Android SDK DIR",
default_value: "~/Library/Android/sdk",
default_value: ENV["ANDROID_HOME"] || ENV["ANDROID_SDK_ROOT"] || "~/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?
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
require 'fastlane_core/ui/ui'
require 'fastlane/action'

module Fastlane
UI = FastlaneCore::UI unless Fastlane.const_defined?(:UI)
Helper = FastlaneCore::Helper unless Fastlane.const_defined?(:Helper)

module Helper
class MaestroOrchestrationHelper
Expand All @@ -12,5 +14,91 @@ def self.show_message
UI.message("Hello from the maestro_orchestration plugin helper!")
end
end

class AvdHelper
# Path to the avd binary
attr_accessor :avdmanager_path
# Available AVDs
attr_accessor :avds

def initialize(avdmanager_path: nil)
android_home = ENV.fetch('ANDROID_HOME', nil) || ENV.fetch('ANDROID_SDK_ROOT', nil)
if (avdmanager_path.nil? || avdmanager_path == "avdmanager") && android_home
# First search for cmdline-tools dir
cmdline_tools_path = File.join(android_home, "cmdline-tools")

# Find the first available 'bin' folder within cmdline-tools
available_path = Dir.glob(File.join(cmdline_tools_path, "*", "bin")).first
raise "No valid bin path found in #{cmdline_tools_path}" unless available_path

avdmanager_path = File.join(available_path, "avdmanager")
end

self.avdmanager_path = Helper.get_executable_path(File.expand_path(avdmanager_path))
end

def trigger(command: nil)
raise "avdmanager_path is not set" unless avdmanager_path

# Build and execute the command
command = [avdmanager_path.shellescape, command].compact.join(" ").strip
Action.sh(command)
end

# Create a new AVD
def create_avd(name:, package:, device: "pixel_7_pro")
raise "AVD name is required" if name.nil? || name.empty?
raise "System image package is required" if package.nil? || package.empty?

UI.message("This is the package parameter passed: #{package}")

command = [
"create avd",
"-n #{name.shellescape}",
"-f",
"-k \"#{package}\"",
"-d #{device.shellescape}"
].join(" ")

trigger(command: command)
end
end

class EmulatorHelper
attr_accessor :emulator_path

def initialize(emulator_path: nil)
android_home = ENV.fetch('ANDROID_HOME', nil) || ENV.fetch('ANDROID_SDK_ROOT', nil)
if (emulator_path.nil? || emulator_path == "avdmanager") && android_home
emulator_path = File.join(android_home, "emulator", "emulator")
end
UI.message("This is the emulator path: #{emulator_path}")

self.emulator_path = Helper.get_executable_path(File.expand_path(emulator_path))
end

def trigger(command: nil)
raise "emulator_path is not set" unless emulator_path

# Build and execute the command
command = [emulator_path.shellescape, command].compact.join(" ").strip
Action.sh(command)
end

# Start an emulator instance
def start_emulator(name:, port:)
raise "Emulator name is required" if name.nil? || name.empty?
raise "Port is required" if port.nil? || port.to_s.empty?

command = [
"-avd #{name.shellescape}",
"-port #{port.shellescape}",
"> /dev/null 2>&1 &"
].join(" ")

UI.message("Starting emulator #{name} on port #{port}...")
trigger(command: command)
end
end
end
end

0 comments on commit 3fe4904

Please sign in to comment.