module Calabash::Cucumber::UIA
Low-level module for interacting directly with UIA
See also {developer.apple.com/library/ios/documentation/ToolsLanguages/Reference/UIAElementClassReference/UIAElement/UIAElement.html} Typically used to interact with System or remote views.
Public Class Methods
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 532 def self.redefine_instance_methods_if_necessary(xcode, automator=nil) return if Calabash::Cucumber::Environment.xtc? if xcode.version_gte_8? reason = "UIAutomation is not available in Xcode >= 8.0." return self.redefine_instance_methods_to_raise(reason) end if automator && automator.name == :device_agent reason = "UIAutomation is not available when testing with DeviceAgent." return self.redefine_instance_methods_to_raise(reason) end end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 547 def self.redefine_instance_methods_to_raise(reason) methods = Calabash::Cucumber::UIA.instance_methods methods.each do |method_name| Calabash::Cucumber::UIA.send(:remove_method, method_name) Calabash::Cucumber::UIA.send(:define_method, method_name) do |*args| case method_name when :uia raise RuntimeError, %Q[ #{reason} #{method_name} has been removed from the Calabash API. It is not possible to make raw UIAutomation JavaScript calls. If you are trying to make query, use the DeviceAgent query API. device_agent.query({type: "TextField", index:1}) device_agent.query({marked: "Cancel"}) If you are trying to perform a gesture or enter text, in most cases the normal Core method will work. If a normal Core method does work, try the DeviceAgent Gesture API. device_agent.touch({type: "TextField", index:1}) device_agent.touch({marked: "Button"}) If you cannot find an equivalent DeviceAgent workaround, please create an issue and include: 1. At a high level, what you are trying to do. 2. The JavaScript you are trying to invoke. Links: * http://calabashapi.xamarin.com/ios/Calabash/Cucumber/DeviceAgent.html * https://github.com/calabash/calabash-ios/issues ] when :uia_call, :uia_call_windows, :uia_call_method, :uia_names raise RuntimeError, %Q[ #{reason} #{method_name} has been removed from the Calabash API. There is no suggested workaround for this method. Please review the DeviceAgent API for a replacement. If you can find no replacement, please create an issue and include: If you are trying to make query, use the DeviceAgent query API. device_agent.query({type: "TextField", index:1}) device_agent.query({marked: "Cancel"}) If you are trying to perform a gesture or enter text, in most cases the normal Core method will work. If a normal Core method does work, try the DeviceAgent Gesture API. device_agent.touch({type: "TextField", index:1}) device_agent.touch({marked: "Button"}) If you cannot find an equivalent DeviceAgent workaround, please create an issue and include: 1. At a high level, what you are trying to do. 2. The method you are invoking with the arguments. Links: * http://calabashapi.xamarin.com/ios/Calabash/Cucumber/DeviceAgent.html * https://github.com/calabash/calabash-ios/issues ] when :uia_element_exists?, :uia_element_does_not_exist? raise RuntimeError, %Q[ #{reason} #{method_name} has been removed from the Calabash API. Use the DeviceAgent wait API. device_agent.wait_for_view({marked: "Cancel"}) device_agent.wait_for_no_view({marked: "Cancel"}) ] when :uia_query, :uia_query_el raise RuntimeError, %Q[ #{reason} #{method_name} has been removed from the Calabash API. Use the DeviceAgent query API. device_agent.query({marked: "Cancel"}) device_agent.query({type: "TextField", index:1}) ] when :uia_query_windows raise RuntimeError, %Q[ #{reason} #{method_name} has been removed from the Calabash API. Try to use the DeviceAgent query API. device_agent.query({marked: "Cancel"}) device_agent.query({type: "TextField", index:1}) If the DeviceAgent query API does not find the correct views, please create an issue and include: 1. At a high level, what you are trying to do. 2. The method you are invoking with the arguments. ] when :uia_screenshot raise RuntimeError, %Q[ #{reason} #{method_name} has been removed from the Calabash API. There is no replacement for this method. Please create an issue and include: 1. At a high level, what you are trying to do. 2. A screenshot of the view you are trying capture. ] when :uia_orientation, :uia_rotate_home_button_to, :uia_rotate raise RuntimeError, %Q[ #{reason} #{method_name} has been removed from the Calabash API. You should not be calling this method. Always call the orientation methods defined in the Core API. ] when :uia_type_string, :uia_type_string_raw, :uia_enter, :uia_set_responder_value raise RuntimeError, %Q[ #{reason} #{method_name} has been removed from the Calabash API. In general, you should use the the text input methods defined in Core. In some cases you will need to use the DeviceAgent query and keyboard API. device_agent.touch({type: "TextField", index: 1}) wait_for_keyboard keyboard_enter_text("Hello") It is important to note that the DeviceAgent implementations of: * keyboard_enter_text * keyboard_enter_char * enter_text_in * enter_text * fast_enter_text have exactly the same performance. You should prefer `enter_text` or `enter_text_in` because it matches the Calabash 2.0 API. ] when :uia_set_location raise RuntimeError, %Q[ #{reason} #{method_name} has been removed from the Calabash API. This method has been broken for various iOS versions and device combinations for years. At the moment, we do not have replacement for location spoofing with DeviceAgent. ] when :uia_send_app_to_background raise RuntimeError, %Q[ #{reason} #{method_name} has been removed from the Calabash API. You should not use this method. If the Core send_app_to_background is not working under UIAutomation, please create a GitHub issue. https://github.com/calabash/calabash-ios/issues ] when :uia_keyboard_visible?, :uia_wait_for_keyboard raise RuntimeError, %Q[ #{reason} #{method_name} has been removed from the Calabash API. We have not found a case (yet) where the the Core keyboard_visible? and wait_for_keyboard methods do not work when using DeviceAgent. If you find a case where the Core methods do not work, please create a GitHub issue. The current DeviceAgent keyboard API is scheduled for removal. It is crucial that you report workflows that require the DeviceAgent keyboard API. device_agent.keyboard_visible? wait_for { device_agent.keyboard_visible? } https://github.com/calabash/calabash-ios/issues ] when :uia_handle_command, :uia_serialize_command, :uia_serialize_arguments, :uia_serialize_argument, :escape_uia_string, :send_uia_command raise RuntimeError, %Q[ #{reason} #{method_name} has been removed from the Calabash API. There is no replacement. ] when :uia_tap, :uia_tap_mark, :uia_tap_offset, :uia_double_tap, :uia_double_tap_mark, :uia_double_tap_offset, :uia_two_finger_tap, :uia_two_finger_tap_offset, :uia_touch_hold, :uia_touch_hold_offset, :uia_pan, :uia_pan_offset, :uia_swipe, :uia_swipe_offset, :uia_flick_offset, :uia_drag_inside, :uia_drag_inside_mark, :uia_pinch, :uia_pinch_offset, :uia_scroll_to raise RuntimeError, %Q[ #{reason} #{method_name} has been removed from the Calabash API. DeviceAgent is our replacement for UIAutomation. In most cases, you will not need to use a special DeviceAgent gesture method like you did with UIAutomation. If a Core gesture method does not work, there is a DeviceAgent gesture API. device_agent.touch({type: "Button", marked: "Back"}) For UIA pan gestures (flick, swipe, pan) use pan_coordinates. from_point = device_agent.query_for_coordinate({marked: "From"}) to_point = device_agent.query_for_coordinate({marked: "To"}) pan_coordinates(from_point, to_point) ] else raise ArgumentError, "This #{method_name} has not been handled" end end end true end
Public Instance Methods
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 485 def escape_uia_string(string) escape_string string end
DEPRECATED: Use uia("...javascript..", options)
instead. deprecated because the method signature is poor @!visibility private
# File lib/calabash-cucumber/uia.rb, line 492 def send_uia_command(opts ={}) # TODO formally deprecate send_uia_command with _deprecated function #cmd = opts[:command] #new_opts = cmd.select{|x| x != :command} #RunLoop.deprecated("0.9.163", "Use 'uia(#{cmd}, #{new_opts})' instead") uia(opts[:command], opts) end
Executes raw JavaScript in the UIAutomation environment (using ‘eval`). @param {String} command the JavaScript snippet to execute @return {Object} the result returned by the UIA
process
# File lib/calabash-cucumber/uia.rb, line 14 def uia(command, options={}) raise ArgumentError, "Please supply :command" unless command # UIA only makes sense if there is a run loop launcher = Calabash::Cucumber::Launcher.launcher_if_used run_loop = launcher && launcher.attached_to_automator? && launcher.run_loop # Automatically attach in the calabash console if !run_loop && defined?(IRB) RunLoop.log_debug("Attaching to current instruments process...") launcher = Calabash::Cucumber::Launcher.new Calabash::Cucumber::Launcher.attach run_loop = launcher.run_loop RunLoop.log_debug("Attached!") end strategy = run_loop[:uia_strategy] case strategy when :preferences, :shared_element path = strategy == :preferences ? 'uia' : 'uia-shared' res = http({:method => :post, :path => path}, {:command => command}.merge(options)) begin res = JSON.parse(res) rescue TypeError, JSON::ParserError => _ raise "Could not parse response '#{res}'; the app has probably crashed" end if res['outcome'] != 'SUCCESS' raise "uia action failed because: #{res['reason']}\n#{res['details']}" end res['results'].first when :host res = RunLoop.send_command(run_loop, command) status = res['status'] case status when 'success' res when 'error' value = res['value'] if value msg = "uia action failed because: #{res['value']}" else msg = 'uia action failed for an unknown reason' end raise msg else candidates = ['success', 'error'] raise RuntimeError, "expected '#{status}' to be one of #{candidates}" end else candidates = [:preferences, :shared_element, :host] raise ArgumentError, "expected '#{run_loop[:uia_strategy]}' to be one of #{candidates}" end end
Advanced method used to invoke UIAutomation JavaScript methods on objects found via Calabash
queries @see uia_query
@example Calling UIAButton.isVisible() - {developer.apple.com/library/ios/documentation/ToolsLanguages/Reference/UIAElementClassReference/UIAElement/UIAElement.html}
uia_call [:button, {marked:'New Post'}], :isVisible
@example Advanced example that chains calls and uses arguments
uia_call [:view, marked:'New Post'], {withName:"New Post"}, :toString, {charAt:0}
@param {Array} args_arr array describing the query, e.g., ‘[:button, {marked:’foo’}]‘ @param {Array} opts optional arguments specifying a chained sequence of method calls (see example)
# File lib/calabash-cucumber/uia.rb, line 133 def uia_call(args_arr, *opts) uia_call_method(:queryEl, [args_arr], *opts) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 415 def uia_call_method(cmd, args_arr, *opts) if opts.empty? return uia_handle_command(cmd, *args_arr) end js_cmd = uia_serialize_command(cmd, *args_arr) js_args = [] opts.each do |invocation| js_args << case invocation when Symbol "#{invocation}()" when Hash m = invocation.keys.first args = invocation[m] if args.is_a?(Array) serialized_args = (args.map &:to_json).join(',') else serialized_args = args.to_json end "#{m}(#{serialized_args})" else raise "Invalid invocation spec #{invocation}" end end command = "#{js_cmd}.#{js_args.join('.')}" if RunLoop::Environment.debug? puts 'Sending UIA command' puts command end uia_result(uia(command)) end
Similar to ‘uia_call` but searches all windows @see uia_call
# File lib/calabash-cucumber/uia.rb, line 139 def uia_call_windows(args_arr, *opts) uia_call_method(:queryElWindows, [args_arr], *opts) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 168 def uia_double_tap(*queryparts) uia_handle_command(:doubleTap, queryparts) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 173 def uia_double_tap_mark(mark) uia_double_tap(:view, {:marked => mark}) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 178 def uia_double_tap_offset(offset) uia_handle_command(:doubleTapOffset, offset) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 228 def uia_drag_inside(dir, queryparts, options={}) uia_call_method(:swipe, [dir, queryparts, options]) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 233 def uia_drag_inside_mark(dir, mark, options={}) uia_call_method(:swipeMark, [dir, "\"#{mark}\"", options]) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 258 def uia_element_does_not_exist?(*queryparts) uia_handle_command(:elementDoesNotExist, queryparts) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 253 def uia_element_exists?(*queryparts) uia_handle_command(:elementExists, queryparts) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 387 def uia_enter uia_handle_command(:enter) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 193 def uia_flick_offset(from, to) uia_handle_command(:flickOffset, from, to) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 452 def uia_handle_command(cmd, *query_args) command = uia_serialize_command(cmd, *query_args) if RunLoop::Environment.debug? puts 'Sending UIA command' puts command end s = uia(command) uia_result(s) end
Used for detecting keyboards that are not normally visible to calabash; e.g. the keyboard on the ‘MFMailComposeViewController`
@note
IMPORTANT this should only be used when the app does not respond to `keyboard_visible?` and UIAutomation is being used.
@see keyboard_visible?
@raise [RuntimeError] If the app was not launched with instruments
# File lib/calabash-cucumber/uia.rb, line 320 def uia_keyboard_visible? res = uia_query_windows(:keyboard) res != ":nil" end
Invoke a Calabash
query inside the UIAutomation Calabash
engine - includes all UIAWindows. Note that this traverses the UIA
(accessibility) hierarchy. @example uia equivalent of ‘identifier “button”`
uia_names :button # returns [ "Browse", "UINavigationBarBackIndicatorDefault.png", "16h", "reader postaction comment blue", "16h", "52", "17h", "10", "Reader", "Notifications", "Me", "New Post" ]
@param {Array} queryparts array of segments in the query, e.g., ‘:button, {marked:’Hello’}‘ @return {Array<String>} “names” (accessibilityIdentifier) of UIAElements matching the query.
# File lib/calabash-cucumber/uia.rb, line 119 def uia_names(*queryparts) #TODO escape '\n etc in query uia_handle_command(:names, queryparts) end
Gets the current orientation of the device @return {String} the current orientation of the device
one of `{'portrait', 'portrait-upside-down', 'landscape-left', 'landscape-right', 'faceup', 'facedown' }`
# File lib/calabash-cucumber/uia.rb, line 278 def uia_orientation o = uia_handle_command(:orientation).to_s o[1..o.length] end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 208 def uia_pan(from_q, to_q) uia_handle_command(:pan, from_q, to_q) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 213 def uia_pan_offset(from, to, options) uia_handle_command(:panOffset, from, to, options) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 238 def uia_pinch(*queryparts) uia_handle_command(:pinch, queryparts) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 243 def uia_pinch_offset(in_or_out, offset, duration) uia_handle_command(:pinchOffset, in_or_out, offset, duration) end
Invoke a Calabash
query inside the UIAutomation Calabash
engine Note that this traverses the UIA
(accessibility) hierarchy. @example uia query equivalent of “button marked:‘Hello’”
uia_query :button, marked:'Hello'
@param {Array} queryparts array of segments in the query, e.g., ‘:button, {marked:’Hello’}‘ @return {Array<Hash>} UIAElements matching the query in serialized form.
# File lib/calabash-cucumber/uia.rb, line 76 def uia_query(*queryparts) #TODO escape '\n etc in query uia_handle_command(:query, queryparts) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 82 def uia_query_el(*queryparts) #TODO escape '\n etc in query uia_handle_command(:queryEl, queryparts) end
Invoke a Calabash
query inside the UIAutomation Calabash
engine - includes all UIAWindows. Note that this traverses the UIA
(accessibility) hierarchy. @example uia query equivalent of “button marked:‘Hello’”
uia_query_windows :button
@param {Array} queryparts array of segments in the query, e.g., ‘:button, {marked:’Hello’}‘ @return {Array<Hash>} UIAElements matching the query in serialized form.
# File lib/calabash-cucumber/uia.rb, line 93 def uia_query_windows(*queryparts) #TODO escape '\n etc in query uia_handle_command(:queryWindows, queryparts) end
Simulates Rotation of the device @param [String|Symbol] dir The position of the home button after the rotation.
Can be one of `{'clockwise' | 'counter-clockwise'| :left | :right}`.
# File lib/calabash-cucumber/uia.rb, line 271 def uia_rotate(dir) uia_handle_command(:rotate, dir) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 263 def uia_screenshot(name) uia_handle_command(:screenshot, name) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 248 def uia_scroll_to(*queryparts) uia_handle_command(:scrollTo, queryparts) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 407 def uia_send_app_to_background(secs) #uia_handle_command(:deactivate, secs) #Temporary workaround: https://github.com/calabash/calabash-ios/issues/556 js_deactivate = %Q[var x = target.deactivateAppForDuration(#{secs}); var MAX_RETRY=5, retry_count = 0; while (!x && retry_count < MAX_RETRY) { x = target.deactivateAppForDuration(#{secs}); retry_count += 1}; x] uia(js_deactivate) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 476 def uia_serialize_argument(part) if part.is_a?(String) "'#{escape_uia_string(part)}'" else "'#{escape_uia_string(part.to_edn)}'" end end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 469 def uia_serialize_arguments(args) args.map do |part| uia_serialize_argument(part) end end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 463 def uia_serialize_command(cmd, *query_args) args = uia_serialize_arguments(query_args) %Q[uia.#{cmd}(#{args.join(', ')})] end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 392 def uia_set_location(options) validate_hash_is_location!(options) if options[:place] place = options[:place] search_results = Geocoder.search(place) raise "Got no results for #{place}" if search_results.empty? loc = search_results.first loc_data = {'latitude' => loc.latitude, 'longitude' => loc.longitude} elsif options.is_a?(Hash) loc_data = options end uia_handle_command(:setLocation, loc_data) end
Advanced method used for fast keyboard entry by calling the setValue method on the input with current keyboard focus. This is an alternative to calling ‘keyboard_enter_text`
@param {String} value the value to set
# File lib/calabash-cucumber/uia.rb, line 148 def uia_set_responder_value(value) uia_call_method(:elementWithKeyboardFocus, [], setValue: value) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 218 def uia_swipe(*queryparts) uia_handle_command(:swipe, queryparts) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 223 def uia_swipe_offset(offset, options) uia_handle_command(:swipeOffset, offset, options) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 153 def uia_tap(*queryparts) uia_handle_command(:tap, queryparts) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 158 def uia_tap_mark(mark) uia_handle_command(:tapMark, mark) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 163 def uia_tap_offset(offset) uia_handle_command(:tapOffset, offset) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 198 def uia_touch_hold(duration, *queryparts) uia_handle_command(:touchHold, duration, queryparts) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 203 def uia_touch_hold_offset(duration, offset) uia_handle_command(:touchHoldOffset, duration, offset) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 183 def uia_two_finger_tap(*queryparts) uia_handle_command(:twoFingerTap, queryparts) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 188 def uia_two_finger_tap_offset(offset) uia_handle_command(:twoFingerTapOffset, offset) end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 352 def uia_type_string(string, opt_text_before='', escape=true) result = uia_handle_command(:typeString, string, opt_text_before) # When 'status' == 'success', we get back result['value']. Sometimes, # the 'value' key is not present in the result - in which case we assume # success without error. return if result.nil? # Typing on UIWebViews returns result['value'] => ':nil'. There might # be other cases where result is _not_ a Hash. return unless result.is_a? Hash # If there is no 'status' key, then we assume success. Syntax errors # should be caught upstream. # https://github.com/calabash/calabash-ios/issues/374 return unless result.has_key? 'status' status = result['status'] # If status is not 'error' we punt. Should never happen. return if status != 'error' if result.has_key? 'value' raise "Could not type '#{string}' - UIAutomation returned an error: '#{result['value']}'" else raise "Could not type '#{string}' - UIAutomation returned '#{result}'" end end
@!visibility private
# File lib/calabash-cucumber/uia.rb, line 382 def uia_type_string_raw(str) uia("uia.keyboard().typeString('#{str}')") end
Waits for a keyboard that is not normally visible to calabash; e.g. the keyboard on ‘MFMailComposeViewController`.
@note
IMPORTANT this should only be used when the app does not respond to `keyboard_visible?` and UIAutomation is being used.
@see keyboard_visible?
@raise [RuntimeError] if the app was not launched with instruments
# File lib/calabash-cucumber/uia.rb, line 335 def uia_wait_for_keyboard(options={}) default_opts = { :timeout => 10, :retry_frequency => 0.1, :post_timeout => 0.5, :timeout_message => "Keyboard did not appear" } options = default_opts.merge(options) wait_for(options) do uia_keyboard_visible? end true end
Private Instance Methods
# File lib/calabash-cucumber/uia.rb, line 514 def uia_result(s) if RunLoop::Environment.debug? puts 'Result' p s end if s['status'] == 'success' s['value'] else s end end
# File lib/calabash-cucumber/uia.rb, line 504 def validate_hash_is_location!(options) return if options[:latitude] and options[:longitude] if (options[:latitude] and not options[:longitude]) || (options[:longitude] and not options[:latitude]) raise 'Both latitude and longitude must be specified if either is.' elsif not options[:place] raise 'Either :place or :latitude and :longitude must be specified.' end end