module Fastlane module Actions class AppiumAction < Action INVOKE_TIMEOUT = 30 APPIUM_PATH_HOMEBREW = '/usr/local/bin/appium' APPIUM_APP_PATH = '/Applications/Appium.app' APPIUM_APP_BUNDLE_PATH = 'Contents/Resources/node_modules/.bin/appium' def self.run(params) Actions.verify_gem!('rspec') Actions.verify_gem!('appium_lib') require 'rspec' require 'appium_lib' unless Helper.test? FastlaneCore::PrintTable.print_values( config: params, title: "Summary for Appium Action" ) if params[:invoke_appium_server] appium_pid = invoke_appium_server(params) wait_for_appium_server(params) end configure_rspec(params) rspec_args = [] rspec_args << params[:spec_path] status = RSpec::Core::Runner.run(rspec_args).to_i if status != 0 UI.user_error!("Failed to run Appium spec. status code: #{status}") end ensure Actions.sh "kill #{appium_pid}" if appium_pid end def self.invoke_appium_server(params) appium = detect_appium(params) fork do Process.exec("#{appium} -a #{params[:host]} -p #{params[:port]}") end end def self.detect_appium(params) appium_path = params[:appium_path] || `which appium`.to_s.strip if appium_path.empty? if File.exist?(APPIUM_PATH_HOMEBREW) appium_path = APPIUM_PATH_HOMEBREW elsif File.exist?(APPIUM_APP_PATH) appium_path = APPIUM_APP_PATH end end unless File.exist?(appium_path) UI.user_error!("You have to install Appium using `npm install -g appium`") end if appium_path.end_with?('.app') appium_path = "#{appium_path}/#{APPIUM_APP_BUNDLE_PATH}" end UI.message("Appium executable detected: #{appium_path}") appium_path end def self.wait_for_appium_server(params) loop.with_index do |_, count| break if `lsof -i:#{params[:port]}`.to_s.length != 0 if count * 5 > INVOKE_TIMEOUT UI.user_error!("Invoke Appium server timed out") end sleep 5 end end def self.configure_rspec(params) RSpec.configure do |c| c.before(:each) do caps = params[:caps] || {} caps[:platformName] ||= params[:platform] caps[:autoAcceptAlerts] ||= true caps[:app] = params[:app_path] appium_lib = params[:appium_lib] || {} @driver = Appium::Driver.new( caps: caps, server_url: params[:host], port: params[:port], appium_lib: appium_lib ).start_driver Appium.promote_appium_methods(RSpec::Core::ExampleGroup) end c.after(:each) do @driver.quit unless @driver.nil? end end end def self.description 'Run UI test by Appium with RSpec' end def self.available_options [ FastlaneCore::ConfigItem.new( key: :platform, env_name: 'FL_APPIUM_PLATFORM', description: 'Appium platform name', is_string: true ), FastlaneCore::ConfigItem.new( key: :spec_path, env_name: 'FL_APPIUM_SPEC_PATH', description: 'Path to Appium spec directory', is_string: true ), FastlaneCore::ConfigItem.new( key: :app_path, env_name: 'FL_APPIUM_APP_FILE_PATH', description: 'Path to Appium target app file', is_string: true ), FastlaneCore::ConfigItem.new( key: :invoke_appium_server, env_name: 'FL_APPIUM_INVOKE_APPIUM_SERVER', description: 'Use local Appium server with invoke automatically', is_string: false, default_value: true, optional: true ), FastlaneCore::ConfigItem.new( key: :host, env_name: 'FL_APPIUM_HOST', description: 'Hostname of Appium server', is_string: true, default_value: '0.0.0.0', optional: true ), FastlaneCore::ConfigItem.new( key: :port, env_name: 'FL_APPIUM_PORT', description: 'HTTP port of Appium server', is_string: false, default_value: 4723, optional: true ), FastlaneCore::ConfigItem.new( key: :appium_path, env_name: 'FL_APPIUM_EXECUTABLE_PATH', description: 'Path to Appium executable', is_string: true, optional: true ), FastlaneCore::ConfigItem.new( key: :caps, env_name: 'FL_APPIUM_CAPS', description: 'Hash of caps for Appium::Driver', is_string: false, type: Hash, optional: true ), FastlaneCore::ConfigItem.new( key: :appium_lib, env_name: 'FL_APPIUM_LIB', description: 'Hash of appium_lib for Appium::Driver', is_string: false, type: Hash, optional: true ) ] end def self.author 'yonekawa' end def self.is_supported?(platform) [:ios, :android].include?(platform) end def self.category :testing end def self.example_code [ 'appium( app_path: "appium/apps/TargetApp.app", spec_path: "appium/spec", platform: "iOS", caps: { versionNumber: "9.1", deviceName: "iPhone 6" }, appium_lib: { wait: 10 } )' ] end end end end