class Calabash::Android::Operations::Device
Attributes
app_path[R]
serial[R]
server_port[R]
test_server_path[R]
test_server_port[R]
Public Class Methods
new(cucumber_world, serial, server_port, app_path, test_server_path, test_server_port = 7102)
click to toggle source
# File lib/calabash-android/operations.rb, line 319 def initialize(cucumber_world, serial, server_port, app_path, test_server_path, test_server_port = 7102) @cucumber_world = cucumber_world @serial = serial || default_serial @server_port = server_port || default_server_port @app_path = app_path @test_server_path = test_server_path @test_server_port = test_server_port forward_cmd = "#{adb_command} forward tcp:#{@server_port} tcp:#{@test_server_port}" calabash_log forward_cmd calabash_log `#{forward_cmd}` end
Public Instance Methods
_sdk_version()
click to toggle source
# File lib/calabash-android/operations.rb, line 333 def _sdk_version `#{adb_command} shell getprop ro.build.version.sdk`.to_i end
adb_command()
click to toggle source
# File lib/calabash-android/operations.rb, line 631 def adb_command "\"#{Calabash::Android::Dependencies.adb_path}\" -s #{serial}" end
app_running?()
click to toggle source
# File lib/calabash-android/operations.rb, line 436 def app_running? begin http("/ping") == "pong" rescue false end end
application_installed?(package_name)
click to toggle source
# File lib/calabash-android/operations.rb, line 432 def application_installed?(package_name) (`#{adb_command} shell pm list packages`.lines.map{|line| line.chomp.sub("package:", "")}.include?(package_name)) end
clear_app_data()
click to toggle source
# File lib/calabash-android/operations.rb, line 688 def clear_app_data unless application_installed?(package_name(@app_path)) raise "Cannot clear data, application #{package_name(@app_path)} is not installed" end unless application_installed?(package_name(@test_server_path)) raise "Cannot clear data, test-server #{package_name(@test_server_path)} is not installed" end cmd = "#{adb_command} shell am instrument #{package_name(@test_server_path)}/sh.calaba.instrumentationbackend.ClearAppData2" raise "Could not clear data" unless system(cmd) # Wait for the cleanup activity to finish. This is a hard sleep for now sleep 2 true end
clear_preferences(name)
click to toggle source
# File lib/calabash-android/operations.rb, line 935 def clear_preferences(name) calabash_log "Clear preferences: #{name}, app running? #{app_running?}" if app_running? perform_action('clear_preferences', name); else logcat_id = get_logcat_id() cmd = "#{adb_command} shell am instrument -e logcat #{logcat_id} -e name \"#{name}\" #{package_name(@test_server_path)}/sh.calaba.instrumentationbackend.ClearPreferences" raise "Could not clear preferences" unless system(cmd) logcat_cmd = get_logcat_cmd(logcat_id) logcat_output = `#{logcat_cmd}` json = get_json_from_logcat(logcat_output) raise "Could not clear preferences" unless json != nil and json["success"] end end
client_version()
click to toggle source
# File lib/calabash-android/operations.rb, line 614 def client_version Calabash::Android::VERSION end
configure_http(http, options)
click to toggle source
# File lib/calabash-android/operations.rb, line 563 def configure_http(http, options) return unless http http.connect_timeout = options[:open_timeout] || 15 http.send_timeout = options[:send_timeout] || 15 http.receive_timeout = options[:read_timeout] || 15 if options.has_key?(:debug) && options[:debug] http.debug_dev= $stdout else if ENV['DEBUG_HTTP'] and (ENV['DEBUG_HTTP'] != '0') http.debug_dev = $stdout else http.debug_dev= nil end end http end
connected_devices()
click to toggle source
# File lib/calabash-android/operations.rb, line 668 def connected_devices # Run empty ADB command to remove eventual first-run messages `"#{Calabash::Android::Dependencies.adb_path}" devices` lines = `"#{Calabash::Android::Dependencies.adb_path}" devices`.split("\n") start_index = lines.index{ |x| x =~ /List of devices attached/ } + 1 lines[start_index..-1].collect { |l| l.split("\t").first } end
default_serial()
click to toggle source
# File lib/calabash-android/operations.rb, line 635 def default_serial devices = connected_devices calabash_log "connected_devices: #{devices}" raise "No connected devices" if devices.empty? raise "More than one device connected. Specify device serial using ADB_DEVICE_ARG" if devices.length > 1 devices.first end
default_server_port()
click to toggle source
# File lib/calabash-android/operations.rb, line 643 def default_server_port require 'yaml' File.open(File.expand_path(server_port_configuration), File::RDWR|File::CREAT) do |f| f.flock(File::LOCK_EX) state = YAML::load(f) || {} ports = state['server_ports'] ||= {} return ports[serial] if ports.has_key?(serial) port = 34777 port += 1 while ports.has_value?(port) ports[serial] = port f.rewind f.write(YAML::dump(state)) f.truncate(f.pos) calabash_log "Persistently allocated port #{port} to #{serial}" return port end end
ensure_apps_installed()
click to toggle source
# File lib/calabash-android/operations.rb, line 351 def ensure_apps_installed apps = [@app_path, @test_server_path] apps.each do |app| package = package_name(app) md5 = Digest::MD5.file(File.expand_path(app)) if !application_installed?(package) || (!@@installed_apps.keys.include?(package) || @@installed_apps[package] != md5) calabash_log "MD5 checksum for app '#{app}' (#{package}): #{md5}" uninstall_app(package) install_app(app) @@installed_apps[package] = md5 end end end
get_json_from_logcat(logcat_output)
click to toggle source
# File lib/calabash-android/operations.rb, line 956 def get_json_from_logcat(logcat_output) logcat_output.split(/\r?\n/).each do |line| begin json = JSON.parse(line) return json rescue # nothing to do here, just discarding logcat rubbish end end return nil end
get_logcat_cmd(tag)
click to toggle source
# File lib/calabash-android/operations.rb, line 979 def get_logcat_cmd(tag) # returns raw logcat output for our tag # filtering out everthing else "#{adb_command} logcat -d -v raw #{tag}:* *:S" end
get_logcat_id()
click to toggle source
# File lib/calabash-android/operations.rb, line 970 def get_logcat_id() # we need a unique logcat tag so we can later # query the logcat output and filter out everything # but what we are interested in random = (0..10000).to_a.sample "#{Time.now.strftime("%s")}_#{random}" end
get_preferences(name)
click to toggle source
# File lib/calabash-android/operations.rb, line 876 def get_preferences(name) calabash_log "Get preferences: #{name}, app running? #{app_running?}" preferences = {} if app_running? json = perform_action('get_preferences', name); else logcat_id = get_logcat_id() cmd = "#{adb_command} shell am instrument -e logcat #{logcat_id} -e name \"#{name}\" #{package_name(@test_server_path)}/sh.calaba.instrumentationbackend.GetPreferences" raise "Could not get preferences" unless system(cmd) logcat_cmd = get_logcat_cmd(logcat_id) logcat_output = `#{logcat_cmd}` json = get_json_from_logcat(logcat_output) raise "Could not get preferences" unless json != nil and json["success"] end # at this point we have valid json, coming from an action # or instrumentation, but we don't care, just parse if json["bonusInformation"].length > 0 json["bonusInformation"].each do |item| json_item = JSON.parse(item) preferences[json_item["key"]] = json_item["value"] end end preferences end
http(path, data = {}, options = {})
click to toggle source
# File lib/calabash-android/operations.rb, line 474 def http(path, data = {}, options = {}) begin configure_http(@http, options) make_http_request( :method => :post, :body => data.to_json, :uri => url_for(path), :header => {"Content-Type" => "application/json;charset=utf-8"}) rescue HTTPClient::TimeoutError, HTTPClient::KeepAliveDisconnected, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ECONNABORTED, Errno::ETIMEDOUT => e calabash_log "It looks like your app is no longer running. \nIt could be because of a crash or because your test script shut it down." raise e end end
http_put(path, data = {}, options = {})
click to toggle source
# File lib/calabash-android/operations.rb, line 493 def http_put(path, data = {}, options = {}) begin configure_http(@http, options) make_http_request( :method => :put, :body => data, :uri => url_for(path), :header => {"Content-Type" => "application/octet-stream"}) rescue HTTPClient::TimeoutError, HTTPClient::KeepAliveDisconnected, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ECONNABORTED, Errno::ETIMEDOUT => e calabash_log "It looks like your app is no longer running. \nIt could be because of a crash or because your test script shut it down." raise e end end
init_request(options)
click to toggle source
# File lib/calabash-android/operations.rb, line 558 def init_request(options) http = HTTPClient.new configure_http(http, options) end
install_app(app_path)
click to toggle source
# File lib/calabash-android/operations.rb, line 367 def install_app(app_path) if _sdk_version >= 23 cmd = "#{adb_command} install -g -t \"#{app_path}\"" else cmd = "#{adb_command} install -t \"#{app_path}\"" end calabash_log "Installing: #{app_path}" result = `#{cmd}` calabash_log result pn = package_name(app_path) succeeded = `#{adb_command} shell pm list packages`.lines.map{|line| line.chomp.sub("package:", "")}.include?(pn) unless succeeded ::Cucumber.wants_to_quit = true raise "#{pn} did not get installed. Reason: '#{result.lines.last.chomp}'. Aborting!" end # Enable GPS location mocking on Android Marshmallow+ if _sdk_version >= 23 cmd = "#{adb_command} shell appops set #{package_name(app_path)} 58 allow" calabash_log("Enabling GPS mocking using '#{cmd}'") `#{cmd}` end true end
keyguard_enabled?()
click to toggle source
# File lib/calabash-android/operations.rb, line 444 def keyguard_enabled? dumpsys = `#{adb_command} shell dumpsys window windows` #If a line containing mCurrentFocus and Keyguard exists the keyguard is enabled dumpsys.lines.any? { |l| l.include?("mCurrentFocus") and l.include?("Keyguard")} end
make_http_request(options)
click to toggle source
# File lib/calabash-android/operations.rb, line 530 def make_http_request(options) begin unless @http @http = init_request(options) end header = options[:header] || {} header["Content-Type"] = "application/json;charset=utf-8" options[:header] = header response = if options[:method] == :post @http.post(options[:uri], options) elsif options[:method] == :put @http.put(options[:uri], options) else @http.get(options[:uri], options) end raise Errno::ECONNREFUSED if response.status_code == 502 response.body rescue => e if @http @http.reset_all @http=nil end raise e end end
perform_action(action, *arguments)
click to toggle source
# File lib/calabash-android/operations.rb, line 450 def perform_action(action, *arguments) calabash_log "Action: #{action} - Params: #{arguments.join(', ')}" params = {"command" => action, "arguments" => arguments} Timeout.timeout(300) do begin result = http("/", params, {:read_timeout => 350}) rescue => e calabash_log "Error communicating with test server: #{e}" raise e end calabash_log "Result:'" + result.strip + "'" raise "Empty result from TestServer" if result.chomp.empty? result = JSON.parse(result) if not result["success"] then raise "Action '#{action}' unsuccessful: #{result["message"]}" end result end rescue Timeout::Error raise "Step timed out" end
pull(remote, local)
click to toggle source
# File lib/calabash-android/operations.rb, line 706 def pull(remote, local) cmd = "#{adb_command} pull #{remote} #{local}" raise "Could not pull #{remote} to #{local}" unless system(cmd) end
push(local, remote)
click to toggle source
# File lib/calabash-android/operations.rb, line 711 def push(local, remote) cmd = "#{adb_command} push #{local} #{remote}" raise "Could not push #{local} to #{remote}" unless system(cmd) end
reinstall_apps()
click to toggle source
# File lib/calabash-android/operations.rb, line 337 def reinstall_apps uninstall_app(package_name(@app_path)) uninstall_app(package_name(@test_server_path)) install_app(@app_path) install_app(@test_server_path) end
reinstall_test_server()
click to toggle source
# File lib/calabash-android/operations.rb, line 344 def reinstall_test_server uninstall_app(package_name(@test_server_path)) install_app(@test_server_path) end
screenshot(options={:prefix => nil, :name => nil})
click to toggle source
# File lib/calabash-android/operations.rb, line 580 def screenshot(options={:prefix => nil, :name => nil}) prefix = options[:prefix] || ENV['SCREENSHOT_PATH'] || "" name = options[:name] if name.nil? name = "screenshot" else if File.extname(name).downcase == ".png" name = name.split(".png")[0] end end @@screenshot_count ||= 0 path = "#{prefix}#{name}_#{@@screenshot_count}.png" if ENV["SCREENSHOT_VIA_USB"] == "false" begin res = http("/screenshot") rescue EOFError raise "Could not take screenshot. App is most likely not running anymore." end File.open(path, 'wb') do |f| f.write res end else screenshot_cmd = "java -jar \"#{File.join(File.dirname(__FILE__), 'lib', 'screenshotTaker.jar')}\" #{serial} \"#{path}\"" calabash_log screenshot_cmd raise "Could not take screenshot" unless system(screenshot_cmd) end @@screenshot_count += 1 path end
server_port_configuration()
click to toggle source
# File lib/calabash-android/operations.rb, line 664 def server_port_configuration File.expand_path(ENV['CALABASH_SERVER_PORTS'] || "~/.calabash.yaml") end
server_version()
click to toggle source
# File lib/calabash-android/operations.rb, line 618 def server_version begin response = perform_action('version') raise 'Invalid response' unless response['success'] rescue => e calabash_log("Could not contact server") calabash_log(e && e.backtrace && e.backtrace.join("\n")) raise "The server did not respond. Make sure the server is running." end response['message'] end
set_gps_coordinates(latitude, longitude)
click to toggle source
# File lib/calabash-android/operations.rb, line 872 def set_gps_coordinates(latitude, longitude) perform_action('set_gps_coordinates', latitude, longitude) end
set_gps_coordinates_from_location(location)
click to toggle source
location
# File lib/calabash-android/operations.rb, line 863 def set_gps_coordinates_from_location(location) require 'geocoder' results = Geocoder.search(location) raise "Got no results for #{location}" if results.empty? best_result = results.first set_gps_coordinates(best_result.latitude, best_result.longitude) end
set_http(http)
click to toggle source
# File lib/calabash-android/operations.rb, line 512 def set_http(http) @http = http end
set_preferences(name, hash)
click to toggle source
# File lib/calabash-android/operations.rb, line 910 def set_preferences(name, hash) calabash_log "Set preferences: #{name}, #{hash}, app running? #{app_running?}" if app_running? perform_action('set_preferences', name, hash); else params = hash.map {|k,v| "-e \"#{k}\" \"#{v}\""}.join(" ") logcat_id = get_logcat_id() am_cmd = Shellwords.escape("am instrument -e logcat #{logcat_id} -e name \"#{name}\" #{params} #{package_name(@test_server_path)}/sh.calaba.instrumentationbackend.SetPreferences") cmd = "#{adb_command} shell #{am_cmd}" raise "Could not set preferences" unless system(cmd) logcat_cmd = get_logcat_cmd(logcat_id) logcat_output = `#{logcat_cmd}` json = get_json_from_logcat(logcat_output) raise "Could not set preferences" unless json != nil and json["success"] end end
shutdown_test_server()
click to toggle source
# File lib/calabash-android/operations.rb, line 843 def shutdown_test_server begin unless @adb_shell_pid.nil? Process.kill("HUP",@adb_shell_pid) @adb_shell_pid = nil end http("/kill") Timeout::timeout(3) do sleep 0.3 while app_running? end rescue HTTPClient::KeepAliveDisconnected calabash_log ("Server not responding. Moving on.") rescue Timeout::Error calabash_log ("Could not kill app. Waited to 3 seconds.") rescue EOFError calabash_log ("Could not kill app. App is most likely not running anymore.") end end
start_application(intent)
click to toggle source
# File lib/calabash-android/operations.rb, line 829 def start_application(intent) begin result = JSON.parse(http("/start-application", {intent: intent}, {read_timeout: 60})) rescue HTTPClient::ReceiveTimeoutError => e raise "Failed to start application. Starting took more than 60 seconds: #{e.class} - #{e.message}" end if result['outcome'] != 'SUCCESS' raise result['detail'] end result['result'] end
start_test_server_in_background(options={}, &block)
click to toggle source
# File lib/calabash-android/operations.rb, line 716 def start_test_server_in_background(options={}, &block) raise "Will not start test server because of previous failures." if ::Cucumber.wants_to_quit if keyguard_enabled? wake_up end env_options = options.clone env_options.delete(:intent) env_options[:main_activity] ||= ENV['MAIN_ACTIVITY'] || 'null' env_options[:test_server_port] ||= @test_server_port env_options[:class] ||= "sh.calaba.instrumentationbackend.InstrumentationBackend" cmd_arr = [adb_command, "shell am instrument"] env_options.each_pair do |key, val| cmd_arr << "-e" cmd_arr << key.to_s cmd_arr << val.to_s end cmd_arr << "#{package_name(@test_server_path)}/sh.calaba.instrumentationbackend.CalabashInstrumentationTestRunner" if options[:with_uiautomator] cmd_arr.insert(2, "-w") shutdown_test_server @adb_shell_pid = Process.spawn(cmd_arr.join(" "), :in => '/dev/null') rescue "Could not execute command to start test server with uiautomator" else cmd = cmd_arr.join(" ") calabash_log "Starting test server using:" calabash_log cmd raise "Could not execute command to start test server" unless system("#{cmd} 2>&1") end Calabash::Android::Retry.retry :tries => 600, :interval => 0.1 do raise "App did not start see adb logcat for details" unless app_running? end begin Calabash::Android::Retry.retry :tries => 300, :interval => 0.1 do calabash_log "Checking if instrumentation backend is ready" calabash_log "Is app running? #{app_running?}" ready = http("/ready", {}, {:read_timeout => 1}) if ready != "true" calabash_log "Instrumentation backend not yet ready" raise "Not ready" else calabash_log "Instrumentation backend is ready!" end end rescue => e msg = "Unable to make connection to Calabash Test Server at http://127.0.0.1:#{@server_port}/\n" msg << "Please check the logcat output for more info about what happened\n" raise msg end begin server_version = server_version() rescue msg = ["Unable to obtain Test Server version. "] msg << "Please run 'reinstall_test_server' to make sure you have the correct version" msg_s = msg.join("\n") calabash_log(msg_s) raise msg_s end client_version = client_version() if Calabash::Android::Environment.skip_version_check? calabash_log(%Q[ Client version #{client_version} Test-server version #{server_version} ]) $stdout.flush else calabash_log "Checking client-server version match..." if server_version != client_version calabash_log(%Q[ Calabash Client and Test-server version mismatch. Client version #{client_version} Test-server version #{server_version} Expected Test-server version #{client_version} Solution: Run 'reinstall_test_server' to make sure you have the correct version ]) else calabash_log("Client and server versions match (client: #{client_version}, server: #{server_version}). Proceeding...") end end block.call if block start_application(options[:intent]) # What was Calabash tracking? Read this post for information # No private data (like ip addresses) were collected # https://github.com/calabash/calabash-android/issues/655 # # Removing usage tracking to avoid problems with EU General Data # Protection Regulation which takes effect in 2018. # Calabash::Android::UsageTracker.new.post_usage_async end
uninstall_app(package_name)
click to toggle source
# File lib/calabash-android/operations.rb, line 414 def uninstall_app(package_name) exists = application_installed?(package_name) if exists calabash_log "Uninstalling: #{package_name}" calabash_log `#{adb_command} uninstall #{package_name}` succeeded = !application_installed?(package_name) unless succeeded ::Cucumber.wants_to_quit = true raise "#{package_name} was not uninstalled. Aborting!" end else calabash_log "Package not installed: #{package_name}. Skipping uninstall." end end
update_app(app_path)
click to toggle source
# File lib/calabash-android/operations.rb, line 395 def update_app(app_path) if _sdk_version >= 23 cmd = "#{adb_command} install -r -g \"#{app_path}\"" else cmd = "#{adb_command} install -r \"#{app_path}\"" end calabash_log "Updating: #{app_path}" result = `#{cmd}` calabash_log "result: #{result}" succeeded = result.include?("Success") unless succeeded ::Cucumber.wants_to_quit = true pn = package_name(app_path) raise "#{pn} did not get updated. Aborting!" end end
url_for(method)
click to toggle source
# File lib/calabash-android/operations.rb, line 516 def url_for(method) url = URI.parse(ENV['DEVICE_ENDPOINT']|| "http://127.0.0.1:#{@server_port}") path = url.path if path.end_with? "/" path = "#{path}#{method}" else path = "#{path}/#{method}" end url.path = path url end
wake_up()
click to toggle source
# File lib/calabash-android/operations.rb, line 677 def wake_up wake_up_cmd = "#{adb_command} shell am start -a android.intent.action.MAIN -n #{package_name(@test_server_path)}/sh.calaba.instrumentationbackend.WakeUp" calabash_log "Waking up device using:" calabash_log wake_up_cmd raise "Could not wake up the device" unless system(wake_up_cmd) Calabash::Android::Retry.retry :tries => 10, :interval => 1 do raise "Could not remove the keyguard" if keyguard_enabled? end end