lib/calabash-android/operations.rb in calabash-android-0.5.1 vs lib/calabash-android/operations.rb in calabash-android-0.5.2.pre1

- old
+ new

@@ -4,990 +4,1184 @@ require 'open-uri' require 'rubygems' require 'json' require 'socket' require 'timeout' +require 'calabash-android/gestures' require 'calabash-android/helpers' +require 'calabash-android/environment_helpers' require 'calabash-android/text_helpers' require 'calabash-android/touch_helpers' require 'calabash-android/wait_helpers' require 'calabash-android/version' require 'calabash-android/env' require 'retriable' require 'cucumber' +require 'date' +require 'time' module Calabash module Android -module Operations - include Calabash::Android::TextHelpers - include Calabash::Android::TouchHelpers - include Calabash::Android::WaitHelpers + module Operations + include Calabash::Android::EnvironmentHelpers + include Calabash::Android::TextHelpers + include Calabash::Android::TouchHelpers + include Calabash::Android::WaitHelpers - def current_activity - `#{default_device.adb_command} shell dumpsys window windows`.each_line.grep(/mFocusedApp.+[\.\/]([^.\s\/\}]+)/){$1}.first - end + def current_activity + `#{default_device.adb_command} shell dumpsys window windows`.each_line.grep(/mFocusedApp.+[\.\/]([^.\s\/\}]+)/){$1}.first + end - def log(message) - $stdout.puts "#{Time.now.strftime("%Y-%m-%d %H:%M:%S")} - #{message}" if (ARGV.include? "-v" or ARGV.include? "--verbose") - end + def log(message) + $stdout.puts "#{Time.now.strftime("%Y-%m-%d %H:%M:%S")} - #{message}" if (ARGV.include? "-v" or ARGV.include? "--verbose") + end - def macro(txt) - if self.respond_to?(:step) - step(txt) - else - Then(txt) + def macro(txt) + if self.respond_to?(:step) + step(txt) + else + Then(txt) + end end - end - def default_device - unless @default_device - @default_device = Device.new(self, ENV["ADB_DEVICE_ARG"], ENV["TEST_SERVER_PORT"], ENV["APP_PATH"], ENV["TEST_APP_PATH"]) + def default_device + unless @default_device + @default_device = Device.new(self, ENV["ADB_DEVICE_ARG"], ENV["TEST_SERVER_PORT"], ENV["APP_PATH"], ENV["TEST_APP_PATH"]) + end + @default_device end - @default_device - end - def set_default_device(device) - @default_device = device - end + def set_default_device(device) + @default_device = device + end - def performAction(action, *arguments) - puts "Warning: The method performAction is deprecated. Please use perform_action instead." + def performAction(action, *arguments) + puts "Warning: The method performAction is deprecated. Please use perform_action instead." - perform_action(action, *arguments) - end + perform_action(action, *arguments) + end - def perform_action(action, *arguments) - @removed_actions = File.readlines(File.join(File.dirname(__FILE__), 'removed_actions.txt')) unless @removed_actions - @removed_actions.map! &:chomp + def perform_action(action, *arguments) + if removed_actions.include?(action) + puts "\e[31mError: The action '#{action}' was removed in calabash-android 0.5\e[0m" + puts 'Solutions that do not require the removed action can be found on:' + puts "\e[36mhttps://github.com/calabash/calabash-android/blob/master/migrating_to_calabash_0.5.md\##{action}\e[0m" + elsif deprecated_actions.has_key?(action) + puts "\e[31mWarning: The action '#{action}' is deprecated\e[0m" + puts "\e[32mUse '#{deprecated_actions[action]}' instead\e[0m" + end - if @removed_actions.include?(action) - puts "\e[31mError: The action '#{action}' was removed in calabash-android 0.5\e[0m" - puts 'Solutions that do not require the removed action can be found on:' - puts "\e[36mhttps://github.com/calabash/calabash-android/blob/master/migrating_to_calabash_0.5.md\##{action}\e[0m" + default_device.perform_action(action, *arguments) end - - default_device.perform_action(action, *arguments) - end - def reinstall_apps - default_device.reinstall_apps - end + def removed_actions + @removed_actions ||= File.readlines(File.join(File.dirname(__FILE__), 'removed_actions.txt')).map(&:chomp) + end - def reinstall_test_server - default_device.reinstall_test_server - end + def deprecated_actions + @deprecated_actions ||= Hash[ + *File.readlines(File.join(File.dirname(__FILE__), 'deprecated_actions.map')).map{|e| e.chomp.split(',', 2)}.flatten + ] + end - def install_app(app_path) - default_device.install_app(app_path) - end + def reinstall_apps + default_device.reinstall_apps + end - def update_app(app_path) - default_device.update_app(app_path) - end + def reinstall_test_server + default_device.reinstall_test_server + end - def uninstall_apps - default_device.uninstall_app(package_name(default_device.test_server_path)) - default_device.uninstall_app(package_name(default_device.app_path)) - end + def install_app(app_path) + default_device.install_app(app_path) + end - def wake_up - default_device.wake_up() - end + def update_app(app_path) + default_device.update_app(app_path) + end - def clear_app_data - default_device.clear_app_data - end + def uninstall_apps + default_device.uninstall_app(package_name(default_device.test_server_path)) + default_device.uninstall_app(package_name(default_device.app_path)) + end - def pull(remote, local) - default_device.pull(remote, local) - end + def wake_up + default_device.wake_up() + end - def push(local, remote) - default_device.push(local, remote) - end + def clear_app_data + default_device.clear_app_data + end - def start_test_server_in_background(options={}) - default_device.start_test_server_in_background(options) - end + def pull(remote, local) + default_device.pull(remote, local) + end - def shutdown_test_server - default_device.shutdown_test_server - end + def push(local, remote) + default_device.push(local, remote) + end - def screenshot_embed(options={:prefix => nil, :name => nil, :label => nil}) - path = default_device.screenshot(options) - embed(path, "image/png", options[:label] || File.basename(path)) - end + def start_test_server_in_background(options={}) + default_device.start_test_server_in_background(options) + end - def screenshot(options={:prefix => nil, :name => nil}) - default_device.screenshot(options) - end + def shutdown_test_server + default_device.shutdown_test_server + end - def fail(msg="Error. Check log for details.", options={:prefix => nil, :name => nil, :label => nil}) - screenshot_and_raise(msg, options) - end + def screenshot_embed(options={:prefix => nil, :name => nil, :label => nil}) + path = default_device.screenshot(options) + embed(path, "image/png", options[:label] || File.basename(path)) + end - def set_gps_coordinates_from_location(location) - default_device.set_gps_coordinates_from_location(location) - end + def screenshot(options={:prefix => nil, :name => nil}) + default_device.screenshot(options) + end - def set_gps_coordinates(latitude, longitude) - default_device.set_gps_coordinates(latitude, longitude) - end + def client_version + default_device.client_version + end - def get_preferences(name) - default_device.get_preferences(name) - end + def server_version + default_device.server_version + end - def set_preferences(name, hash) - default_device.set_preferences(name, hash) - end + def fail(msg="Error. Check log for details.", options={:prefix => nil, :name => nil, :label => nil}) + screenshot_and_raise(msg, options) + end - def clear_preferences(name) - default_device.clear_preferences(name) - end + def set_gps_coordinates_from_location(location) + default_device.set_gps_coordinates_from_location(location) + end - def query(uiquery, *args) - converted_args = [] - args.each do |arg| - if arg.is_a?(Hash) and arg.count == 1 - if arg.values.is_a?(Array) && arg.values.count == 1 - values = arg.values.flatten + def set_gps_coordinates(latitude, longitude) + default_device.set_gps_coordinates(latitude, longitude) + end + + def get_preferences(name) + default_device.get_preferences(name) + end + + def set_preferences(name, hash) + default_device.set_preferences(name, hash) + end + + def clear_preferences(name) + default_device.clear_preferences(name) + end + + def query(uiquery, *args) + converted_args = [] + args.each do |arg| + if arg.is_a?(Hash) and arg.count == 1 + if arg.values.is_a?(Array) && arg.values.count == 1 + values = arg.values.flatten + else + values = [arg.values] + end + + converted_args << {:method_name => arg.keys.first, :arguments => values} else - values = [arg.values] + converted_args << arg end - - converted_args << {:method_name => arg.keys.first, :arguments => values} - else - converted_args << arg end + map(uiquery,:query,*converted_args) end - map(uiquery,:query,*converted_args) - end - def flash(query_string) - map(query_string, :flash) - end + def flash(query_string) + map(query_string, :flash) + end - def each_item(opts={:query => "android.widget.ListView", :post_scroll => 0.2}, &block) - uiquery = opts[:query] || "android.widget.ListView" - skip_if = opts[:skip_if] || lambda { |i| false } - stop_when = opts[:stop_when] || lambda { |i| false } - check_element_exists(uiquery) - num_items = query(opts[:query], :adapter, :count).first - num_items.times do |item| - next if skip_if.call(item) - break if stop_when.call(item) + def each_item(opts={:query => "android.widget.ListView", :post_scroll => 0.2}, &block) + uiquery = opts[:query] || "android.widget.ListView" + skip_if = opts[:skip_if] || lambda { |i| false } + stop_when = opts[:stop_when] || lambda { |i| false } + check_element_exists(uiquery) + num_items = query(opts[:query], :adapter, :count).first + num_items.times do |item| + next if skip_if.call(item) + break if stop_when.call(item) - scroll_to_row(opts[:query], item) - sleep(opts[:post_scroll]) if opts[:post_scroll] and opts[:post_scroll] > 0 - yield(item) + scroll_to_row(opts[:query], item) + sleep(opts[:post_scroll]) if opts[:post_scroll] and opts[:post_scroll] > 0 + yield(item) + end end - end - def ni - raise "Not yet implemented." - end + def set_date(query_string, year_or_datestring, month=nil, day=nil) + wait_for_element_exists(query_string) - ### + if month.nil? && day.nil? && year_or_datestring.is_a?(String) + date = Date.parse(year_or_datestring) + set_date(query_string, date.year, date.month, date.day) + else + year = year_or_datestring + query(query_string, updateDate: [year, month-1, day]) + end + end - ### simple page object helper + def set_time(query_string, hour_or_timestring, minute=nil) + wait_for_element_exists(query_string) - def page(clz, *args) - clz.new(self, *args) - end + if minute.nil? && hour_or_timestring.is_a?(String) + time = Time.parse(hour_or_timestring) + set_time(query_string, time.hour, time.min) + else + hour = hour_or_timestring + query(query_string, setCurrentHour: hour) + query(query_string, setCurrentMinute: minute) + end + end - ### + def classes(query_string, *args) + query(query_string, :class, *args) + end - ### app life cycle - def connect_to_test_server - puts "Explicit calls to connect_to_test_server should be removed." - puts "Please take a look in your hooks file for calls to this methods." - puts "(Hooks are stored in features/support)" - end + def ni + raise "Not yet implemented." + end - def disconnect_from_test_server - puts "Explicit calls to disconnect_from_test_server should be removed." - puts "Please take a look in your hooks file for calls to this methods." - puts "(Hooks are stored in features/support)" - end + ### - class Device - attr_reader :app_path, :test_server_path, :serial, :server_port, :test_server_port + ### simple page object helper - def initialize(cucumber_world, serial, server_port, app_path, test_server_path, test_server_port = 7102) + def page(clz, *args) + clz.new(self, *args) + end - @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}" - log forward_cmd - log `#{forward_cmd}` + ### app life cycle + def connect_to_test_server + puts "Explicit calls to connect_to_test_server should be removed." + puts "Please take a look in your hooks file for calls to this methods." + puts "(Hooks are stored in features/support)" end - def reinstall_apps() - uninstall_app(package_name(@app_path)) - install_app(@app_path) - reinstall_test_server() + def disconnect_from_test_server + puts "Explicit calls to disconnect_from_test_server should be removed." + puts "Please take a look in your hooks file for calls to this methods." + puts "(Hooks are stored in features/support)" end - def reinstall_test_server() - uninstall_app(package_name(@test_server_path)) - install_app(@test_server_path) - end + class Device + attr_reader :app_path, :test_server_path, :serial, :server_port, :test_server_port - def install_app(app_path) - cmd = "#{adb_command} install \"#{app_path}\"" - log "Installing: #{app_path}" - result = `#{cmd}` - log result - pn = package_name(app_path) - succeeded = `#{adb_command} shell pm list packages`.include?("package:#{pn}") + def initialize(cucumber_world, serial, server_port, app_path, test_server_path, test_server_port = 7102) - unless succeeded - ::Cucumber.wants_to_quit = true - raise "#{pn} did not get installed. Aborting!" + @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}" + log forward_cmd + log `#{forward_cmd}` end - end - def update_app(app_path) - cmd = "#{adb_command} install -r \"#{app_path}\"" - log "Updating: #{app_path}" - result = `#{cmd}` - log "result: #{result}" - succeeded = result.include?("Success") + 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 - unless succeeded - ::Cucumber.wants_to_quit = true - raise "#{pn} did not get updated. Aborting!" + def reinstall_test_server + uninstall_app(package_name(@test_server_path)) + install_app(@test_server_path) end - end - def uninstall_app(package_name) - log "Uninstalling: #{package_name}" - log `#{adb_command} uninstall #{package_name}` - end + def install_app(app_path) + cmd = "#{adb_command} install \"#{app_path}\"" + log "Installing: #{app_path}" + result = `#{cmd}` + log result + pn = package_name(app_path) + succeeded = `#{adb_command} shell pm list packages`.lines.map{|line| line.chomp.sub("package:", "")}.include?(pn) - def app_running? - begin - http("/ping") == "pong" - rescue - false + unless succeeded + ::Cucumber.wants_to_quit = true + raise "#{pn} did not get installed. Reason: '#{result.lines.last.chomp}'. Aborting!" + end end - end - 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 + def update_app(app_path) + cmd = "#{adb_command} install -r \"#{app_path}\"" + log "Updating: #{app_path}" + result = `#{cmd}` + log "result: #{result}" + succeeded = result.include?("Success") - def perform_action(action, *arguments) - log "Action: #{action} - Params: #{arguments.join(', ')}" + unless succeeded + ::Cucumber.wants_to_quit = true + raise "#{pn} did not get updated. Aborting!" + end + end - params = {"command" => action, "arguments" => arguments} + def uninstall_app(package_name) + log "Uninstalling: #{package_name}" + log `#{adb_command} uninstall #{package_name}` - Timeout.timeout(300) do + succeeded = !(`#{adb_command} shell pm list packages`.lines.map{|line| line.chomp.sub("package:", "")}.include?(package_name)) + + unless succeeded + ::Cucumber.wants_to_quit = true + raise "#{package_name} was not uninstalled. Aborting!" + end + end + + def app_running? begin - result = http("/", params, {:read_timeout => 350}) - rescue Exception => e - log "Error communicating with test server: #{e}" - raise e + http("/ping") == "pong" + rescue + false end - 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 + + 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 + + def perform_action(action, *arguments) + log "Action: #{action} - Params: #{arguments.join(', ')}" + + params = {"command" => action, "arguments" => arguments} + + Timeout.timeout(300) do + begin + result = http("/", params, {:read_timeout => 350}) + rescue Exception => e + log "Error communicating with test server: #{e}" + raise e + end + 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 - result + rescue Timeout::Error + raise Exception, "Step timed out" end - rescue Timeout::Error - raise Exception, "Step timed out" - end - def http(path, data = {}, options = {}) - begin + 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"}) + 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 - 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 + rescue HTTPClient::TimeoutError, + HTTPClient::KeepAliveDisconnected, + Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ECONNABORTED, + Errno::ETIMEDOUT => e + 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 - end - def set_http(http) - @http = http - end + def set_http(http) + @http = http + end - 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}" + 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 - url.path = path - url - end - def make_http_request(options) - begin - unless @http - @http = init_request(options) + 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) + else + @http.get(options[:uri], options) + end + raise Errno::ECONNREFUSED if response.status_code == 502 + response.body + rescue Exception => e + if @http + @http.reset_all + @http=nil + end + raise e end - header = options[:header] || {} - header["Content-Type"] = "application/json;charset=utf-8" - options[:header] = header + end + def init_request(options) + http = HTTPClient.new + configure_http(http, options) + end - response = if options[:method] == :post - @http.post(options[:uri], options) + 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 - @http.get(options[:uri], options) + if ENV['DEBUG_HTTP'] and (ENV['DEBUG_HTTP'] != '0') + http.debug_dev = $stdout + else + http.debug_dev= nil + end end - raise Errno::ECONNREFUSED if response.status_code == 502 - response.body - rescue Exception => e - if @http - @http.reset_all - @http=nil - end - raise e + http end - end - def init_request(options) - http = HTTPClient.new - configure_http(http, options) - end + def screenshot(options={:prefix => nil, :name => nil}) + prefix = options[:prefix] || ENV['SCREENSHOT_PATH'] || "" + name = options[:name] - 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 + if name.nil? + name = "screenshot" else - http.debug_dev= nil + if File.extname(name).downcase == ".png" + name = name.split(".png")[0] + end end - end - http - end - def screenshot(options={:prefix => nil, :name => nil}) - prefix = options[:prefix] || ENV['SCREENSHOT_PATH'] || "" - name = options[:name] + @@screenshot_count ||= 0 + path = "#{prefix}#{name}_#{@@screenshot_count}.png" - if name.nil? - name = "screenshot" - else - if File.extname(name).downcase == ".png" - name = name.split(".png")[0] + 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}\"" + log screenshot_cmd + raise "Could not take screenshot" unless system(screenshot_cmd) end + + @@screenshot_count += 1 + path end - @@screenshot_count ||= 0 - path = "#{prefix}#{name}_#{@@screenshot_count}.png" + def client_version + Calabash::Android::VERSION + end - if ENV["SCREENSHOT_VIA_USB"] == "false" + def server_version begin - res = http("/screenshot") - rescue EOFError - raise "Could not take screenshot. App is most likely not running anymore." + response = perform_action('version') + raise 'Invalid response' unless response['success'] + rescue => e + log("Could not contact server") + log(e && e.backtrace && e.backtrace.join("\n")) + raise "The server did not respond. Make sure the server is running." 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}\"" - log screenshot_cmd - raise "Could not take screenshot" unless system(screenshot_cmd) + + response['message'] end - @@screenshot_count += 1 - path - end + def adb_command + "#{Env.adb_path} -s #{serial}" + end - def adb_command - "#{Env.adb_path} -s #{serial}" - end + def default_serial + devices = connected_devices + 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 - def default_serial - devices = connected_devices - 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 + 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) - 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 - port = 34777 - port += 1 while ports.has_value?(port) - ports[serial] = port + f.rewind + f.write(YAML::dump(state)) + f.truncate(f.pos) - f.rewind - f.write(YAML::dump(state)) - f.truncate(f.pos) + log "Persistently allocated port #{port} to #{serial}" + return port + end + end - log "Persistently allocated port #{port} to #{serial}" - return port + def server_port_configuration + File.expand_path(ENV['CALABASH_SERVER_PORTS'] || "~/.calabash.yaml") end - end - def server_port_configuration - File.expand_path(ENV['CALABASH_SERVER_PORTS'] || "~/.calabash.yaml") - end + def connected_devices + lines = `#{Env.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 - def connected_devices - lines = `#{Env.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 + 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" + log "Waking up device using:" + log wake_up_cmd + raise "Could not wake up the device" unless system(wake_up_cmd) - 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" - log "Waking up device using:" - log wake_up_cmd - raise "Could not wake up the device" unless system(wake_up_cmd) + retriable :tries => 10, :interval => 1 do + raise "Could not remove the keyguard" if keyguard_enabled? + end + end - retriable :tries => 10, :interval => 1 do - raise "Could not remove the keyguard" if keyguard_enabled? + def clear_app_data + cmd = "#{adb_command} shell am instrument #{package_name(@test_server_path)}/sh.calaba.instrumentationbackend.ClearAppData" + raise "Could not clear data" unless system(cmd) end - end - def clear_app_data - cmd = "#{adb_command} shell am instrument #{package_name(@test_server_path)}/sh.calaba.instrumentationbackend.ClearAppData" - raise "Could not clear data" unless system(cmd) - end + def pull(remote, local) + cmd = "#{adb_command} pull #{remote} #{local}" + raise "Could not pull #{remote} to #{local}" unless system(cmd) + end - def pull(remote, local) - cmd = "#{adb_command} pull #{remote} #{local}" - raise "Could not pull #{remote} to #{local}" unless system(cmd) - end + def push(local, remote) + cmd = "#{adb_command} push #{local} #{remote}" + raise "Could not push #{local} to #{remote}" unless system(cmd) + end - def push(local, remote) - cmd = "#{adb_command} push #{local} #{remote}" - raise "Could not push #{local} to #{remote}" unless system(cmd) - end + def start_test_server_in_background(options={}) + raise "Will not start test server because of previous failures." if ::Cucumber.wants_to_quit - def start_test_server_in_background(options={}) - raise "Will not start test server because of previous failures." if ::Cucumber.wants_to_quit + if keyguard_enabled? + wake_up + end - if keyguard_enabled? - wake_up - end + env_options = options - env_options = {:target_package => package_name(@app_path), - :main_activity => main_activity(@app_path), - :test_server_port => @test_server_port, - :class => "sh.calaba.instrumentationbackend.InstrumentationBackend"} + env_options[:target_package] ||= package_name(@app_path) + env_options[:main_activity] ||= main_activity(@app_path) + env_options[:test_server_port] ||= @test_server_port + env_options[:class] ||= "sh.calaba.instrumentationbackend.InstrumentationBackend" - env_options = env_options.merge(options) + cmd_arr = [adb_command, "shell am instrument"] - 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 - 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" - cmd_arr << "#{package_name(@test_server_path)}/sh.calaba.instrumentationbackend.CalabashInstrumentationTestRunner" + cmd = cmd_arr.join(" ") - cmd = cmd_arr.join(" ") + log "Starting test server using:" + log cmd + raise "Could not execute command to start test server" unless system("#{cmd} 2>&1") - log "Starting test server using:" - log cmd - raise "Could not execute command to start test server" unless system("#{cmd} 2>&1") + retriable :tries => 10, :interval => 1 do + raise "App did not start" unless app_running? + end - retriable :tries => 10, :interval => 1 do - raise "App did not start" unless app_running? - end - - begin - retriable :tries => 10, :interval => 3 do + begin + retriable :tries => 10, :interval => 3 do log "Checking if instrumentation backend is ready" log "Is app running? #{app_running?}" ready = http("/ready", {}, {:read_timeout => 1}) if ready != "true" log "Instrumentation backend not yet ready" raise "Not ready" else log "Instrumentation backend is ready!" end + end + rescue Exception => 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 - rescue Exception => 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 + log "Checking client-server version match..." - log "Checking client-server version match..." - response = perform_action('version') - unless response['success'] - 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") - log(msg_s) - raise msg_s - end - unless response['message'] == Calabash::Android::VERSION + 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") + log(msg_s) + raise msg_s + end - msg = ["Calabash Client and Test-server version mismatch."] - msg << "Client version #{Calabash::Android::VERSION}" - msg << "Test-server version #{response['message']}" - msg << "Expected Test-server version #{Calabash::Android::VERSION}" - msg << "\n\nSolution:\n\n" - msg << "Run 'reinstall_test_server' to make sure you have the correct version" - msg_s = msg.join("\n") - log(msg_s) - raise msg_s - end - log("Client and server versions match. Proceeding...") + client_version = client_version() + unless server_version == client_version + msg = ["Calabash Client and Test-server version mismatch."] + msg << "Client version #{client_version}" + msg << "Test-server version #{server_version}" + msg << "Expected Test-server version #{client_version}" + msg << "\n\nSolution:\n\n" + msg << "Run 'reinstall_test_server' to make sure you have the correct version" + msg_s = msg.join("\n") + log(msg_s) + raise msg_s + end - end + log("Client and server versions match (client: #{client_version}, server: #{server_version}). Proceeding...") + end - def shutdown_test_server - begin - http("/kill") - Timeout::timeout(3) do - sleep 0.3 while app_running? + def shutdown_test_server + begin + http("/kill") + Timeout::timeout(3) do + sleep 0.3 while app_running? + end + rescue HTTPClient::KeepAliveDisconnected + log ("Server not responding. Moving on.") + rescue Timeout::Error + log ("Could not kill app. Waited to 3 seconds.") + rescue EOFError + log ("Could not kill app. App is most likely not running anymore.") end - rescue HTTPClient::KeepAliveDisconnected - log ("Server not responding. Moving on.") - rescue Timeout::Error - log ("Could not kill app. Waited to 3 seconds.") - rescue EOFError - log ("Could not kill app. App is most likely not running anymore.") end - end - ##location - def set_gps_coordinates_from_location(location) - require 'geocoder' - results = Geocoder.search(location) - raise Exception, "Got no results for #{location}" if results.empty? + ##location + def set_gps_coordinates_from_location(location) + require 'geocoder' + results = Geocoder.search(location) + raise Exception, "Got no results for #{location}" if results.empty? - best_result = results.first - set_gps_coordinates(best_result.latitude, best_result.longitude) - end + best_result = results.first + set_gps_coordinates(best_result.latitude, best_result.longitude) + end - def set_gps_coordinates(latitude, longitude) - perform_action('set_gps_coordinates', latitude, longitude) - end + def set_gps_coordinates(latitude, longitude) + perform_action('set_gps_coordinates', latitude, longitude) + end - def get_preferences(name) + def get_preferences(name) - log "Get preferences: #{name}, app running? #{app_running?}" - preferences = {} + log "Get preferences: #{name}, app running? #{app_running?}" + preferences = {} - if app_running? - json = perform_action('get_preferences', name); - else + 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" + 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) + raise "Could not get preferences" unless system(cmd) - logcat_cmd = get_logcat_cmd(logcat_id) - logcat_output = `#{logcat_cmd}` + logcat_cmd = get_logcat_cmd(logcat_id) + logcat_output = `#{logcat_cmd}` - json = get_json_from_logcat(logcat_output) + json = get_json_from_logcat(logcat_output) - raise "Could not get preferences" unless json != nil and json["success"] - end + 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 + # 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"] + json_item = JSON.parse(item) + preferences[json_item["key"]] = json_item["value"] + end end + + preferences end - preferences - end + def set_preferences(name, hash) - def set_preferences(name, hash) + log "Set preferences: #{name}, #{hash}, app running? #{app_running?}" - log "Set preferences: #{name}, #{hash}, app running? #{app_running?}" + if app_running? + perform_action('set_preferences', name, hash); + else - if app_running? - perform_action('set_preferences', name, hash); - else + params = hash.map {|k,v| "-e \"#{k}\" \"#{v}\""}.join(" ") - params = hash.map {|k,v| "-e \"#{k}\" \"#{v}\""}.join(" ") + logcat_id = get_logcat_id() + cmd = "#{adb_command} shell am instrument -e logcat #{logcat_id} -e name \"#{name}\" #{params} #{package_name(@test_server_path)}/sh.calaba.instrumentationbackend.SetPreferences" - logcat_id = get_logcat_id() - cmd = "#{adb_command} shell am instrument -e logcat #{logcat_id} -e name \"#{name}\" #{params} #{package_name(@test_server_path)}/sh.calaba.instrumentationbackend.SetPreferences" + raise "Could not set preferences" unless system(cmd) - raise "Could not set preferences" unless system(cmd) + logcat_cmd = get_logcat_cmd(logcat_id) + logcat_output = `#{logcat_cmd}` - logcat_cmd = get_logcat_cmd(logcat_id) - logcat_output = `#{logcat_cmd}` + json = get_json_from_logcat(logcat_output) - json = get_json_from_logcat(logcat_output) + raise "Could not set preferences" unless json != nil and json["success"] + end + end - raise "Could not set preferences" unless json != nil and json["success"] + def clear_preferences(name) + + 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 - end - def clear_preferences(name) + def get_json_from_logcat(logcat_output) - log "Clear preferences: #{name}, app running? #{app_running?}" + 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 - if app_running? - perform_action('clear_preferences', name); - else + return nil + end - 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) + 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 - logcat_cmd = get_logcat_cmd(logcat_id) - logcat_output = `#{logcat_cmd}` + random = (0..10000).to_a.sample + "#{Time.now.strftime("%s")}_#{random}" + end - json = get_json_from_logcat(logcat_output) + def get_logcat_cmd(tag) + # returns raw logcat output for our tag + # filtering out everthing else - raise "Could not clear preferences" unless json != nil and json["success"] + "#{adb_command} logcat -d -v raw #{tag}:* *:S" end end - def get_json_from_logcat(logcat_output) + def label(uiquery) + ni + end - 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 + def screenshot_and_raise(msg, options = nil) + if options + screenshot_embed options + else + screenshot_embed end + raise(msg) + end - return nil + def hide_soft_keyboard + perform_action('hide_soft_keyboard') end - 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 + def execute_uiquery(uiquery) + if uiquery.instance_of? String + elements = query(uiquery) - random = (0..10000).to_a.sample - "#{Time.now.strftime("%s")}_#{random}" + return elements.first unless elements.empty? + else + elements = uiquery + + return elements.first if elements.instance_of?(Array) + return elements if elements.instance_of?(Hash) + end + + nil end - def get_logcat_cmd(tag) - # returns raw logcat output for our tag - # filtering out everthing else + def step_deprecated + puts 'Warning: This predefined step is deprecated.' + end - "#{adb_command} logcat -d -v raw #{tag}:* *:S" + def http(path, data = {}, options = {}) + default_device.http(path, data, options) end - end - def label(uiquery) - ni - end + def html(q) + query(q).map {|e| e['html']} + end - def screenshot_and_raise(msg, options = nil) - if options - screenshot_embed options - else - screenshot_embed + def set_text(uiquery, txt) + puts "set_text is deprecated. Use enter_text instead" + enter_text(uiquery, txt) end - raise(msg) - end - def hide_soft_keyboard - perform_action('hide_soft_keyboard') - end + def press_user_action_button(action_name=nil) + if action_name.nil? + perform_action("press_user_action_button") + else + perform_action("press_user_action_button", action_name) + end + end - def execute_uiquery(uiquery) - if uiquery.instance_of? String - elements = query(uiquery) + def press_button(key) + perform_action('press_key', key) + end - return elements.first unless elements.empty? - else - elements = uiquery + def press_back_button + press_button('KEYCODE_BACK') + end - return elements.first if elements.instance_of?(Array) - return elements if elements.instance_of?(Hash) + def press_menu_button + press_button('KEYCODE_MENU') end - nil - end + def press_down_button + press_button('KEYCODE_DPAD_DOWN') + end - def step_deprecated - puts 'Warning: This predefined step is deprecated.' - end + def press_up_button + press_button('KEYCODE_DPAD_UP') + end - def http(path, data = {}, options = {}) - default_device.http(path, data, options) - end + def press_left_button + press_button('KEYCODE_DPAD_LEFT') + end - def html(q) - query(q).map {|e| e['html']} - end + def press_right_button + press_button('KEYCODE_DPAD_RIGHT') + end - def set_text(uiquery, txt) - puts "set_text is deprecated. Use enter_text instead" - enter_text(uiquery, txt) - end + def press_enter_button + press_button('KEYCODE_ENTER') + end - def press_back_button - perform_action('go_back') - end + def select_options_menu_item(identifier, options={}) + press_menu_button + tap_when_element_exists("DropDownListView * marked:'#{identifier}'", options) + end - def press_menu_button - perform_action('press_menu') - end + def select_context_menu_item(view_uiquery, menu_item_query_string) + long_press(view_uiquery) - def select_options_menu_item(identifier, options={}) - press_menu_button - tap_when_element_exists("DropDownListView * marked:'#{identifier}'", options) - end + container_class = 'com.android.internal.view.menu.ListMenuItemView' + wait_for_element_exists(container_class) - def select_context_menu_item(view_uiquery, menu_item_query_string) - long_press(view_uiquery) + combined_query_string = "#{container_class} descendant #{menu_item_query_string}" + touch(combined_query_string) + end - container_class = 'com.android.internal.view.menu.ListMenuItemView' - wait_for_element_exists(container_class) + def select_item_from_spinner(item_query_string, options={}) + spinner_query_string = options[:spinner] || "android.widget.AbsSpinner" + direction = options[:direction] || :down + count = query(spinner_query_string, :getCount).first + scroll_view_query_string = options[:scroll_view] || "android.widget.AbsListView index:0" - combined_query_string = "#{container_class} descendant #{menu_item_query_string}" - touch(combined_query_string) - end + unless direction == :up || direction == :down + raise "Invalid direction '#{direction}'. Only upwards and downwards scrolling is supported" + end - def swipe(dir,options={}) - ni - end + touch(spinner_query_string) - def cell_swipe(options={}) - ni - end + change_direction = false - def done - ni - end + wait_for({retry_frequency: 0}.merge(options)) do + if query(item_query_string).empty? + scroll(scroll_view_query_string, direction) - def scroll_up - scroll("android.widget.ScrollView", :up) - end + if change_direction + direction = direction == :up ? :down : :up + change_direction = false + else + # Because getLastVisiblePosition returns the last element even though it is not visible, + # we have to scroll one more time to make sure we do not change direction before the last + # element is fully visible + if direction == :down + change_direction = true if query(scroll_view_query_string, :getLastVisiblePosition).first+1 == count + elsif direction == :up + change_direction = true if query(scroll_view_query_string, :getFirstVisiblePosition).first == 0 + end + end - def scroll_down - scroll("android.widget.ScrollView", :down) - end + false + else + true + end + end - def scroll(query_string, direction) - if direction != :up && direction != :down - raise 'Only upwards and downwards scrolling is supported for now' + touch(item_query_string) end - scroll_x = 0 - scroll_y = 0 + def swipe(query_string, options={}) + raise 'Swipe not implemented. Use flick or pan instead.' + end - action = lambda do - element = query(query_string).first - raise "No elements found. Query: #{query_string}" if element.nil? + def cell_swipe(options={}) + ni + end - width = element['rect']['width'] - height = element['rect']['height'] + def done + ni + end - if direction == :up - scroll_y = -height/2 - else - scroll_y = height/2 + def find_scrollable_view(options={}) + timeout = options[:timeout] || 30 + + begin + Timeout.timeout(timeout, WaitError) do + scroll_view_query_string = "android.widget.ScrollView index:0" + list_view_query_string = "android.widget.AbsListView index:0" + web_view_query_string = "android.webkit.WebView index:0" + + loop do + if element_exists(scroll_view_query_string) + return scroll_view_query_string + elsif element_exists(list_view_query_string) + return list_view_query_string + elsif element_exists(web_view_query_string) + return web_view_query_string + end + end + end + rescue WaitError + raise WaitError.new('Could not find any scrollable views') end + end - query(query_string, {scrollBy: [scroll_x.to_i, scroll_y.to_i]}) + def scroll_up(options={}) + scroll(find_scrollable_view(options), :up) end - when_element_exists(query_string, action: action) - end + def scroll_down(options={}) + scroll(find_scrollable_view(options), :down) + end - def scroll_to(query_string, options={}) - options[:action] ||= lambda {} + def scroll(query_string, direction) + if direction != :up && direction != :down + raise 'Only upwards and downwards scrolling is supported for now' + end - all_query_string = query_string + action = lambda do + elements = query(query_string) + raise "No elements found. Query: #{query_string}" if elements.empty? - unless all_query_string.chomp.downcase.start_with?('all') - all_query_string = "all #{all_query_string}" - end + if elements.length > 1 + query_string = "#{query_string} index:0" + end - wait_for_element_exists(all_query_string) + element = elements.first - visibility_query_string = all_query_string[4..-1] + response = query(query_string, :getFirstVisiblePosition).first - unless query(visibility_query_string).empty? - when_element_exists(visibility_query_string, options) - return - end + if response.is_a?(Hash) && response.has_key?("error") # View is not of type android.widget.AbsListView + scroll_x = 0 + scroll_y = 0 + width = element['rect']['width'] + height = element['rect']['height'] - element = query(all_query_string).first - raise "No elements found. Query: #{all_query_string}" if element.nil? - element_center_y = element['rect']['center_y'] + if direction == :up + scroll_y = -height/2 + else + scroll_y = height/2 + end - if element.has_key?('html') - scroll_view_query_string = element['webView'] - else - scroll_view_query_string = "#{all_query_string} parent android.widget.ScrollView index:0" + query(query_string, {scrollBy: [scroll_x.to_i, scroll_y.to_i]}) + else # View is of type android.widget.AbsListView + first_position = response.to_i + last_position = query(query_string, :getLastVisiblePosition).first.to_i + + selection_index = if direction == :up + [first_position + [first_position - last_position + 1, -1].min, 0].max + elsif direction == :down + first_position + [last_position - first_position, 1].max + end + + query(query_string, setSelection: selection_index) + end + end + + when_element_exists(query_string, action: action) end - scroll_element = query(scroll_view_query_string).first + def scroll_to(query_string, options={}) + options[:action] ||= lambda {} - raise "Could not find parent scroll view. Query: #{scroll_view_query_string}" if element.nil? + all_query_string = query_string - scroll_element_y = scroll_element['rect']['y'] - scroll_element_height = scroll_element['rect']['height'] + unless all_query_string.chomp.downcase.start_with?('all') + all_query_string = "all #{all_query_string}" + end - if element_center_y > scroll_element_y + scroll_element_height - scroll_by_y = element_center_y - (scroll_element_y + scroll_element_height) + 2 - else - scroll_by_y = element_center_y - scroll_element_y - 2 - end + wait_for_element_exists(all_query_string) - result = query(scroll_view_query_string, {scrollBy: [0, scroll_by_y.to_i]}).first - raise 'Could not scroll parent view' if result != '<VOID>' + element = query(all_query_string).first + raise "No elements found. Query: #{all_query_string}" if element.nil? + element_y = element['rect']['y'] + element_height = element['rect']['height'] + element_bottom = element_y + element_height - visibility_query_string = all_query_string[4..-1] - when_element_exists(visibility_query_string, options) - end + scroll_view_query_string = options[:container] || if element.has_key?('html') + "android.webkit.WebView id:'#{element['webView']}'" + else + "#{all_query_string} parent android.widget.ScrollView index:0" + end - def scroll_to_row(uiquery,number) - query(uiquery, {:smoothScrollToPosition => number}) - puts "TODO:detect end of scroll - use sleep for now" - end + scroll_element = query(scroll_view_query_string).first - def pinch(in_out,options={}) - ni - end + raise "Could not find parent scroll view. Query: '#{escape_quotes(scroll_view_query_string)}'" if scroll_element.nil? - def rotate(dir) - ni - end + scroll_element_y = scroll_element['rect']['y'] + scroll_element_height = scroll_element['rect']['height'] - def app_to_background(secs) - ni - end + if element_bottom > scroll_element_y + scroll_element_height + scroll_by_y = element_bottom - (scroll_element_y + scroll_element_height) + elsif element_y < scroll_element_y + scroll_by_y = element_y - scroll_element_y + else + scroll_by_y = 0 + end - def element_does_not_exist(uiquery) - query(uiquery).empty? - end + if scroll_by_y != 0 + result = query(scroll_view_query_string, {scrollBy: [0, scroll_by_y]}).first + raise 'Could not scroll parent view' if result != '<VOID>' + end - def element_exists(uiquery) - not element_does_not_exist(uiquery) - end + visibility_query_string = all_query_string[4..-1] + when_element_exists(visibility_query_string, options) + end - def view_with_mark_exists(expected_mark) - element_exists( "android.view.View marked:'#{expected_mark}'" ) - end + def scroll_to_row(uiquery,number) + query(uiquery, {:smoothScrollToPosition => number}) + puts "TODO:detect end of scroll - use sleep for now" + end - def check_element_exists( query ) - if not element_exists( query ) - screenshot_and_raise "No element found for query: #{query}" + def rotate(dir) + ni end - end - def check_element_does_not_exist( query ) - if element_exists( query ) - screenshot_and_raise "Expected no elements to match query: #{query}" + def app_to_background(secs) + ni end - end - def check_view_with_mark_exists(expected_mark) - check_element_exists( "view marked:'#{expected_mark}'" ) - end + def element_does_not_exist(uiquery) + query(uiquery).empty? + end - # a better name would be element_exists_and_is_not_hidden - def element_is_not_hidden(uiquery) - ni - end + def element_exists(uiquery) + not element_does_not_exist(uiquery) + end + def view_with_mark_exists(expected_mark) + element_exists( "android.view.View marked:'#{expected_mark}'" ) + end - def load_playback_data(recording,options={}) - ni - end + def check_element_exists( query ) + if not element_exists( query ) + screenshot_and_raise "No element found for query: #{query}" + end + end - def playback(recording, options={}) - ni - end + def check_element_does_not_exist( query ) + if element_exists( query ) + screenshot_and_raise "Expected no elements to match query: #{query}" + end + end - def interpolate(recording, options={}) - ni - end + def check_view_with_mark_exists(expected_mark) + check_element_exists( "view marked:'#{expected_mark}'" ) + end - def record_begin - ni - end + # a better name would be element_exists_and_is_not_hidden + def element_is_not_hidden(uiquery) + ni + end - def record_end(file_name) - ni - end - def backdoor(sel, arg) - result = perform_action("backdoor", sel, arg) - if !result["success"] - screenshot_and_raise(result["message"]) + def load_playback_data(recording,options={}) + ni end - # for android results are returned in bonusInformation - result["bonusInformation"].first - end + def playback(recording, options={}) + ni + end - def map(query, method_name, *method_args) - operation_map = { - :method_name => method_name, - :arguments => method_args - } - res = http("/map", - {:query => query, :operation => operation_map}) - res = JSON.parse(res) - if res['outcome'] != 'SUCCESS' - screenshot_and_raise "map #{query}, #{method_name} failed because: #{res['reason']}\n#{res['details']}" + def interpolate(recording, options={}) + ni end - res['results'] - end + def record_begin + ni + end - def url_for( method ) - default_device.url_for(method) - end + def record_end(file_name) + ni + end - def make_http_request(options) - default_device.make_http_request(options) + def backdoor(method_name, arguments = [], options={}) + arguments = [arguments] unless arguments.is_a?(Array) + + result = JSON.parse(http('/backdoor', {method_name: method_name, arguments: arguments})) + + if result['outcome'] != 'SUCCESS' + raise result.to_s + end + + result['result'] + end + + def map(query, method_name, *method_args) + operation_map = { + :method_name => method_name, + :arguments => method_args + } + res = http("/map", + {:query => query, :operation => operation_map}) + res = JSON.parse(res) + if res['outcome'] != 'SUCCESS' + screenshot_and_raise "map #{query}, #{method_name} failed because: #{res['reason']}\n#{res['details']}" + end + + res['results'] + end + + def url_for( method ) + default_device.url_for(method) + end + + def make_http_request(options) + default_device.make_http_request(options) + end end -end -end end +end end \ No newline at end of file