From 5c67d522909e5fdd72c87fad24b25b22e721c2f4 Mon Sep 17 00:00:00 2001 From: Kazuaki MATSUO Date: Fri, 28 Jul 2017 00:55:47 +0900 Subject: [PATCH 1/5] refactor: search contexts for android and ios --- .../{mobile_methods.rb => search_context.rb} | 4 +- lib/appium_lib/android/mobile_methods.rb | 15 ------ lib/appium_lib/common/error.rb | 3 ++ lib/appium_lib/common/search_context.rb | 47 +++++++++++++++++-- lib/appium_lib/driver.rb | 46 +++++++++++++----- lib/appium_lib/ios/mobile_methods.rb | 39 --------------- 6 files changed, 83 insertions(+), 71 deletions(-) rename ios_tests/lib/ios/specs/ios/{mobile_methods.rb => search_context.rb} (87%) delete mode 100644 lib/appium_lib/android/mobile_methods.rb delete mode 100644 lib/appium_lib/ios/mobile_methods.rb diff --git a/ios_tests/lib/ios/specs/ios/mobile_methods.rb b/ios_tests/lib/ios/specs/ios/search_context.rb similarity index 87% rename from ios_tests/lib/ios/specs/ios/mobile_methods.rb rename to ios_tests/lib/ios/specs/ios/search_context.rb index 4a5de883..8548fa6c 100644 --- a/ios_tests/lib/ios/specs/ios/mobile_methods.rb +++ b/ios_tests/lib/ios/specs/ios/search_context.rb @@ -1,5 +1,5 @@ -# rake ios[ios/mobile_methods] -describe 'ios/mobile_methods' do +# rake ios[ios/search_context] +describe 'ios/search_context' do def before_first screen.must_equal catalog end diff --git a/lib/appium_lib/android/mobile_methods.rb b/lib/appium_lib/android/mobile_methods.rb deleted file mode 100644 index 7cb089ea..00000000 --- a/lib/appium_lib/android/mobile_methods.rb +++ /dev/null @@ -1,15 +0,0 @@ -module Appium - module Android - class << self - # @!method uiautomator_find - # find_element/s can be used with a [UISelector](http://developer.android.com/tools/help/uiautomator/UiSelector.html). - # - # ```ruby - # find_elements :uiautomator, 'new UiSelector().clickable(true)' - # ``` - def extended(_mod) - ::Appium::Driver::SearchContext::FINDERS[:uiautomator] = '-android uiautomator' - end - end # class << self - end # module Android -end # module Appium diff --git a/lib/appium_lib/common/error.rb b/lib/appium_lib/common/error.rb index 578615b6..f8efa5a0 100644 --- a/lib/appium_lib/common/error.rb +++ b/lib/appium_lib/common/error.rb @@ -1,5 +1,8 @@ module Appium module Error class NotSupportedAppiumServer < RuntimeError; end + + # Server side error + class ServerError; end end end diff --git a/lib/appium_lib/common/search_context.rb b/lib/appium_lib/common/search_context.rb index addc9b33..80b24fc2 100644 --- a/lib/appium_lib/common/search_context.rb +++ b/lib/appium_lib/common/search_context.rb @@ -1,10 +1,51 @@ module Appium class Driver module SearchContext - # rubocop:disable Style/MutableConstant + # @!method uiautomator_find + # For Android + # find_element/s can be used with a [UISelector](http://developer.android.com/tools/help/uiautomator/UiSelector.html). + # + # ```ruby + # find_elements :uiautomator, 'new UiSelector().clickable(true)' + # ``` + + # @!method uiautomation_find + # find_element/s can be used with a [UIAutomation command](https://developer.apple.com/library/ios/documentation/ToolsLanguages/Reference/UIAWindowClassReference/UIAWindow/UIAWindow.html#//apple_ref/doc/uid/TP40009930). + # + # ```ruby + # find_elements :uiautomation, 'elements() + # ``` + # + # @!method ios_predicate_string_find + # find_element/s can be used with a [Predicates](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Predicates/AdditionalChapters/Introduction.html) + # + # ```ruby + # find_elements :predicate, "isWDVisible == 1" + # find_elements :predicate, 'wdName == "Buttons"' + # find_elements :predicate, 'wdValue == "SearchBar" AND isWDDivisible == 1' + # ``` + # + # @!method ios_class_chain_find + # Only for XCUITest(WebDriverAgent) + # find_element/s can be used with a [class chain]( https://github.com/facebook/WebDriverAgent/wiki/Queries) + # + # ```ruby + # # select the third child button of the first child window element + # find_elements :class_chain, 'XCUIElementTypeWindow/XCUIElementTypeButton[3]' + # # select all the children windows + # find_elements :class_chain, 'XCUIElementTypeWindow' + # # select the second last child of the second child window + # find_elements :class_chain, 'XCUIElementTypeWindow[2]/XCUIElementTypeAny[-2]' + # ``` FINDERS = { - accessibility_id: 'accessibility id' - } + accessibility_id: 'accessibility id', + + uiautomator: '-android uiautomator', + + uiautomation: '-ios uiautomation', + predicate: '-ios predicate string', + class_chain: '-ios class chain' + }.freeze end end end diff --git a/lib/appium_lib/driver.rb b/lib/appium_lib/driver.rb index 696932f4..2e949041 100644 --- a/lib/appium_lib/driver.rb +++ b/lib/appium_lib/driver.rb @@ -23,7 +23,6 @@ require_relative 'ios/element/generic' require_relative 'ios/element/textfield' require_relative 'ios/element/text' -require_relative 'ios/mobile_methods' require_relative 'ios/xcuitest_gestures' # android @@ -35,7 +34,6 @@ require_relative 'android/element/generic' require_relative 'android/element/textfield' require_relative 'android/element/text' -require_relative 'android/mobile_methods' # device methods require_relative 'device/device' @@ -511,11 +509,11 @@ def check_server_version_xcuitest def appium_server_version driver.remote_status rescue Selenium::WebDriver::Error::WebDriverError => ex - raise unless ex.message.include?('content-type=""') + raise ::Appium::Error::ServerError unless ex.message.include?('content-type=""') # server (TestObject for instance) does not respond to status call {} rescue Selenium::WebDriver::Error::ServerError => e - raise unless e.message.include?('status code 500') + raise ::Appium::Error::ServerError unless e.message.include?('status code 500') # driver.remote_status returns 500 error for using selenium grid {} end @@ -611,11 +609,33 @@ def driver_quit end # Creates a new global driver and quits the old one if it exists. + # You can customise http_client as the following + # + # @example + # ```ruby + # require 'rubygems' + # require 'appium_lib' + # + # # platformName takes a string or a symbol. + # + # # Start iOS driver + # opts = { + # caps: { + # platformName: :ios, + # app: '/path/to/MyiOS.app' + # }, + # appium_lib: { + # wait_timeout: 30 + # } + # } + # custom_http_client = Custom::Http::Client.new(opts) + # Appium::Driver.new(opts).start_driver(custom_http_client) # # @return [Selenium::WebDriver] the new global driver - def start_driver + def start_driver(http_client = + Selenium::WebDriver::Remote::Http::Default.new(open_timeout: 999_999, read_timeout: 999_999)) # open_timeout and read_timeout are explicit wait. - @http_client ||= Selenium::WebDriver::Remote::Http::Default.new(open_timeout: 999_999, read_timeout: 999_999) + @http_client ||= http_client begin driver_quit @@ -630,12 +650,7 @@ def start_driver @driver.extend Selenium::WebDriver::DriverExtensions::HasLocation # export session - if @export_session - # rubocop:disable Style/RescueModifier - File.open('/tmp/appium_lib_session', 'w') do |f| - f.puts @driver.session_id - end rescue nil - end + write_session_id(@driver.session_id) if @export_session rescue Errno::ECONNREFUSED raise "ERROR: Unable to connect to Appium. Is the server running on #{server_url}?" end @@ -773,6 +788,13 @@ def x private + def write_session_id(session_id) + File.open('/tmp/appium_lib_session', 'w') { |f| f.puts session_id } + rescue IOError => e + ::Appium::Logger.warn e + nil + end + # If "automationName" is set only server side, this method set "automationName" attribute into @automation_name. # Since @automation_name is set only client side before start_driver is called. def set_automation_name_if_nil diff --git a/lib/appium_lib/ios/mobile_methods.rb b/lib/appium_lib/ios/mobile_methods.rb deleted file mode 100644 index d740f9f0..00000000 --- a/lib/appium_lib/ios/mobile_methods.rb +++ /dev/null @@ -1,39 +0,0 @@ -module Appium - module Ios - class << self - # @!method uiautomation_find - # find_element/s can be used with a [UIAutomation command](https://developer.apple.com/library/ios/documentation/ToolsLanguages/Reference/UIAWindowClassReference/UIAWindow/UIAWindow.html#//apple_ref/doc/uid/TP40009930). - # - # ```ruby - # find_elements :uiautomation, 'elements() - # ``` - # - # @!method ios_predicate_string_find - # find_element/s can be used with a [Predicates](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Predicates/AdditionalChapters/Introduction.html) - # - # ```ruby - # find_elements :predicate, "isWDVisible == 1" - # find_elements :predicate, 'wdName == "Buttons"' - # find_elements :predicate, 'wdValue == "SearchBar" AND isWDDivisible == 1' - # ``` - # - # @!method ios_class_chain_find - # Only for XCUITest(WebDriverAgent) - # find_element/s can be used with a [class chain]( https://github.com/facebook/WebDriverAgent/wiki/Queries) - # - # ```ruby - # # select the third child button of the first child window element - # find_elements :class_chain, 'XCUIElementTypeWindow/XCUIElementTypeButton[3]' - # # select all the children windows - # find_elements :class_chain, 'XCUIElementTypeWindow' - # # select the second last child of the second child window - # find_elements :class_chain, 'XCUIElementTypeWindow[2]/XCUIElementTypeAny[-2]' - # ``` - def extended(_mod) - ::Appium::Driver::SearchContext::FINDERS[:uiautomation] = '-ios uiautomation' - ::Appium::Driver::SearchContext::FINDERS[:predicate] = '-ios predicate string' - ::Appium::Driver::SearchContext::FINDERS[:class_chain] = '-ios class chain' - end - end # class << self - end # module Ios -end # module Appium From 2262c9d73cc732b588a386d09babf34acef7ce72 Mon Sep 17 00:00:00 2001 From: Kazuaki MATSUO Date: Fri, 28 Jul 2017 01:05:24 +0900 Subject: [PATCH 2/5] refactor: separate some methods --- lib/appium_lib/driver.rb | 48 +++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/lib/appium_lib/driver.rb b/lib/appium_lib/driver.rb index 2e949041..64687255 100644 --- a/lib/appium_lib/driver.rb +++ b/lib/appium_lib/driver.rb @@ -369,30 +369,11 @@ def initialize(opts = {}) raise 'opts must be a hash' unless opts.is_a? Hash opts = Appium.symbolize_keys opts - @caps = Capabilities.init_caps_for_appium(opts[:caps] || {}) appium_lib_opts = opts[:appium_lib] || {} - # appium_lib specific values - @custom_url = appium_lib_opts.fetch :server_url, false - @export_session = appium_lib_opts.fetch :export_session, false - @default_wait = appium_lib_opts.fetch :wait, 0 - @sauce_username = appium_lib_opts.fetch :sauce_username, ENV['SAUCE_USERNAME'] - @sauce_username = nil if !@sauce_username || (@sauce_username.is_a?(String) && @sauce_username.empty?) - @sauce_access_key = appium_lib_opts.fetch :sauce_access_key, ENV['SAUCE_ACCESS_KEY'] - @sauce_access_key = nil if !@sauce_access_key || (@sauce_access_key.is_a?(String) && @sauce_access_key.empty?) - @sauce_endpoint = appium_lib_opts.fetch :sauce_endpoint, ENV['SAUCE_ENDPOINT'] - @sauce_endpoint = 'ondemand.saucelabs.com:443/wd/hub' if - !@sauce_endpoint || (@sauce_endpoint.is_a?(String) && @sauce_endpoint.empty?) - @appium_port = appium_lib_opts.fetch :port, 4723 - # timeout and interval used in ::Appium::Comm.wait/wait_true - @appium_wait_timeout = appium_lib_opts.fetch :wait_timeout, 30 - @appium_wait_interval = appium_lib_opts.fetch :wait_interval, 0.5 - - # to pass it in Selenium.new. - # `listener = opts.delete(:listener)` is called in Selenium::Driver.new - @listener = appium_lib_opts.fetch :listener, nil + set_appium_lib_specific_values(appium_lib_opts) # Path to the .apk, .app or .app.zip. # The path can be local or remote for Sauce. @@ -440,6 +421,33 @@ def initialize(opts = {}) self # return newly created driver end + private + + def set_appium_lib_specific_values(appium_lib_opts) + @custom_url = appium_lib_opts.fetch :server_url, false + @export_session = appium_lib_opts.fetch :export_session, false + @default_wait = appium_lib_opts.fetch :wait, 0 + + @sauce_username = appium_lib_opts.fetch :sauce_username, ENV['SAUCE_USERNAME'] + @sauce_username = nil if !@sauce_username || (@sauce_username.is_a?(String) && @sauce_username.empty?) + @sauce_access_key = appium_lib_opts.fetch :sauce_access_key, ENV['SAUCE_ACCESS_KEY'] + @sauce_access_key = nil if !@sauce_access_key || (@sauce_access_key.is_a?(String) && @sauce_access_key.empty?) + @sauce_endpoint = appium_lib_opts.fetch :sauce_endpoint, ENV['SAUCE_ENDPOINT'] + @sauce_endpoint = 'ondemand.saucelabs.com:443/wd/hub' if + !@sauce_endpoint || (@sauce_endpoint.is_a?(String) && @sauce_endpoint.empty?) + + @appium_port = appium_lib_opts.fetch :port, 4723 + # timeout and interval used in ::Appium::Comm.wait/wait_true + @appium_wait_timeout = appium_lib_opts.fetch :wait_timeout, 30 + @appium_wait_interval = appium_lib_opts.fetch :wait_interval, 0.5 + + # to pass it in Selenium.new. + # `listener = opts.delete(:listener)` is called in Selenium::Driver.new + @listener = appium_lib_opts.fetch :listener, nil + end + + public + # Returns a hash of the driver attributes def driver_attributes { From 12b294714ef446cb02b936d1ca033c242c6a9ff9 Mon Sep 17 00:00:00 2001 From: Kazuaki MATSUO Date: Sat, 29 Jul 2017 09:26:56 +0900 Subject: [PATCH 3/5] refactor: revert and separate mobile_methods --- .../{search_context.rb => mobile_methods.rb} | 0 lib/appium_lib/android/mobile_methods.rb | 15 + lib/appium_lib/common/search_context.rb | 45 +-- lib/appium_lib/driver.rb | 7 +- lib/appium_lib/ios/mobile_methods.rb | 45 +++ lib/appium_lib/ios/xcuitest_gestures.rb | 362 +++++++++--------- 6 files changed, 250 insertions(+), 224 deletions(-) rename ios_tests/lib/ios/specs/ios/{search_context.rb => mobile_methods.rb} (100%) create mode 100644 lib/appium_lib/android/mobile_methods.rb create mode 100644 lib/appium_lib/ios/mobile_methods.rb diff --git a/ios_tests/lib/ios/specs/ios/search_context.rb b/ios_tests/lib/ios/specs/ios/mobile_methods.rb similarity index 100% rename from ios_tests/lib/ios/specs/ios/search_context.rb rename to ios_tests/lib/ios/specs/ios/mobile_methods.rb diff --git a/lib/appium_lib/android/mobile_methods.rb b/lib/appium_lib/android/mobile_methods.rb new file mode 100644 index 00000000..7cb089ea --- /dev/null +++ b/lib/appium_lib/android/mobile_methods.rb @@ -0,0 +1,15 @@ +module Appium + module Android + class << self + # @!method uiautomator_find + # find_element/s can be used with a [UISelector](http://developer.android.com/tools/help/uiautomator/UiSelector.html). + # + # ```ruby + # find_elements :uiautomator, 'new UiSelector().clickable(true)' + # ``` + def extended(_mod) + ::Appium::Driver::SearchContext::FINDERS[:uiautomator] = '-android uiautomator' + end + end # class << self + end # module Android +end # module Appium diff --git a/lib/appium_lib/common/search_context.rb b/lib/appium_lib/common/search_context.rb index 80b24fc2..636918ae 100644 --- a/lib/appium_lib/common/search_context.rb +++ b/lib/appium_lib/common/search_context.rb @@ -1,51 +1,10 @@ module Appium class Driver module SearchContext - # @!method uiautomator_find - # For Android - # find_element/s can be used with a [UISelector](http://developer.android.com/tools/help/uiautomator/UiSelector.html). - # - # ```ruby - # find_elements :uiautomator, 'new UiSelector().clickable(true)' - # ``` - - # @!method uiautomation_find - # find_element/s can be used with a [UIAutomation command](https://developer.apple.com/library/ios/documentation/ToolsLanguages/Reference/UIAWindowClassReference/UIAWindow/UIAWindow.html#//apple_ref/doc/uid/TP40009930). - # - # ```ruby - # find_elements :uiautomation, 'elements() - # ``` - # - # @!method ios_predicate_string_find - # find_element/s can be used with a [Predicates](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Predicates/AdditionalChapters/Introduction.html) - # - # ```ruby - # find_elements :predicate, "isWDVisible == 1" - # find_elements :predicate, 'wdName == "Buttons"' - # find_elements :predicate, 'wdValue == "SearchBar" AND isWDDivisible == 1' - # ``` - # - # @!method ios_class_chain_find - # Only for XCUITest(WebDriverAgent) - # find_element/s can be used with a [class chain]( https://github.com/facebook/WebDriverAgent/wiki/Queries) - # - # ```ruby - # # select the third child button of the first child window element - # find_elements :class_chain, 'XCUIElementTypeWindow/XCUIElementTypeButton[3]' - # # select all the children windows - # find_elements :class_chain, 'XCUIElementTypeWindow' - # # select the second last child of the second child window - # find_elements :class_chain, 'XCUIElementTypeWindow[2]/XCUIElementTypeAny[-2]' - # ``` + # rubocop:disable Style/MutableConstant FINDERS = { accessibility_id: 'accessibility id', - - uiautomator: '-android uiautomator', - - uiautomation: '-ios uiautomation', - predicate: '-ios predicate string', - class_chain: '-ios class chain' - }.freeze + } end end end diff --git a/lib/appium_lib/driver.rb b/lib/appium_lib/driver.rb index 64687255..618a9af8 100644 --- a/lib/appium_lib/driver.rb +++ b/lib/appium_lib/driver.rb @@ -23,6 +23,7 @@ require_relative 'ios/element/generic' require_relative 'ios/element/textfield' require_relative 'ios/element/text' +require_relative 'ios/mobile_methods' require_relative 'ios/xcuitest_gestures' # android @@ -34,6 +35,7 @@ require_relative 'android/element/generic' require_relative 'android/element/textfield' require_relative 'android/element/text' +require_relative 'android/mobile_methods' # device methods require_relative 'device/device' @@ -395,7 +397,10 @@ def initialize(opts = {}) extend Appium::Android else extend Appium::Ios - extend Appium::Ios::XcuitestGesture if automation_name_is_xcuitest? # Override touch actions + if automation_name_is_xcuitest? # Override touch actions + extend Appium::Ios::Xcuitest + extend Appium::Ios::Xcuitest::Gesture + end end # apply os specific patches diff --git a/lib/appium_lib/ios/mobile_methods.rb b/lib/appium_lib/ios/mobile_methods.rb new file mode 100644 index 00000000..dbfd3488 --- /dev/null +++ b/lib/appium_lib/ios/mobile_methods.rb @@ -0,0 +1,45 @@ +module Appium + module Ios + class << self + # @!method uiautomation_find + # find_element/s can be used with a [UIAutomation command](https://developer.apple.com/library/ios/documentation/ToolsLanguages/Reference/UIAWindowClassReference/UIAWindow/UIAWindow.html#//apple_ref/doc/uid/TP40009930). + # + # ```ruby + # find_elements :uiautomation, 'elements() + # ``` + # + # @!method ios_predicate_string_find + # find_element/s can be used with a [Predicates](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Predicates/AdditionalChapters/Introduction.html) + # + # ```ruby + # find_elements :predicate, "isWDVisible == 1" + # find_elements :predicate, 'wdName == "Buttons"' + # find_elements :predicate, 'wdValue == "SearchBar" AND isWDDivisible == 1' + # ``` + def extended(_mod) + ::Appium::Driver::SearchContext::FINDERS[:uiautomation] = '-ios uiautomation' + ::Appium::Driver::SearchContext::FINDERS[:predicate] = '-ios predicate string' + end + end # class << self + + module Xcuitest + class << self + # @!method ios_class_chain_find + # Only for XCUITest(WebDriverAgent) + # find_element/s can be used with a [class chain]( https://github.com/facebook/WebDriverAgent/wiki/Queries) + # + # ```ruby + # # select the third child button of the first child window element + # find_elements :class_chain, 'XCUIElementTypeWindow/XCUIElementTypeButton[3]' + # # select all the children windows + # find_elements :class_chain, 'XCUIElementTypeWindow' + # # select the second last child of the second child window + # find_elements :class_chain, 'XCUIElementTypeWindow[2]/XCUIElementTypeAny[-2]' + # ``` + def extended(_mod) + ::Appium::Driver::SearchContext::FINDERS[:class_chain] = '-ios class chain' + end + end + end + end # module Ios +end # module Appium diff --git a/lib/appium_lib/ios/xcuitest_gestures.rb b/lib/appium_lib/ios/xcuitest_gestures.rb index 5178907e..257b1a68 100644 --- a/lib/appium_lib/ios/xcuitest_gestures.rb +++ b/lib/appium_lib/ios/xcuitest_gestures.rb @@ -1,184 +1,186 @@ module Appium module Ios - module XcuitestGesture - # @param [string] direction Either 'up', 'down', 'left' or 'right'. - # @option opts [Element] :element Element to swipe on - # - # ```ruby - # swipe direction: "down" - # ``` - def swipe(direction:, element: nil) - return unless %w(up down left right).include?(direction) - - args = { direction: direction } - args[:element] = element.ref if element - - execute_script 'mobile: swipe', args - end - - # @param [string] direction Either 'up', 'down', 'left' or 'right'. - # @option opts [String] :name the accessibility id of the child element, to which scrolling is performed. - # @option opts [Element] :element Element id to long tap on. - # @option opts [bool] :to_visible Boolean parameter. If set to true then asks to scroll to the first visible - # element in the parent container. Has no effect if element is not set - # @option opts [String] :predicate_string the NSPredicate locator of the child element, - # to which the scrolling should be performed. Has no effect if element is not a container - # - # ```ruby - # scroll direction: "down" - # ``` - def scroll(direction:, name: nil, element: nil, to_visible: nil, predicate_string: nil) - return 'Set "up", "down", "left" or "right" for :direction' unless %w(up down left right).include?(direction) - - args = { direction: direction } - args[:element] = element.ref if element - args[:name] = name if name - args[:toVisible] = to_visible if to_visible - args[:predicateString] = predicate_string if predicate_string - - execute_script 'mobile: scroll', args - end - - # @param scale [scale] X tap coordinate of type float. Mandatory parameter - # @param velocity [float] Y tap coordinate of type float. Mandatory parameter - # @option opts [Element] :element Element id to long tap on. - # - # ```ruby - # pinch scale: 0.5, velocity: -1 - # ``` - def pinch(scale:, velocity: 1.0, element: nil) - return unless automation_name_is_xcuitest? - - args = { scale: scale, velocity: velocity } - args[:element] = element.ref if element - - execute_script 'mobile: pinch', args - end - - # @param x [float] X Screen x tap coordinate of type float. Mandatory parameter only if element is not set - # @param y [float] Y Screen y tap coordinate of type float. Mandatory parameter only if element is not set - # @option opts [Element] :element Element to long tap on. - # - # ```ruby - # double_tap x: 100, y: 100 - # double_tap element: find_element(:accessibility_id, "some item") - # ``` - def double_tap(x: nil, y: nil, element: nil) - return 'require XCUITest(WDA)' unless automation_name_is_xcuitest? - return 'Set x, y or element' if (x.nil? || y.nil?) && element.nil? - - args = element.nil? ? { x: x, y: y } : { element: element.ref } - execute_script 'mobile: doubleTap', args - end - - # @param x [float] Screen x long tap coordinate of type float. Mandatory parameter only if element is not set - # @param y [float] Screen y long tap coordinate of type float. Mandatory parameter only if element is not set - # @param duration [Float] The float duration of press action in seconds. Mandatory parameter - # @option opts [Element] :element The internal element identifier (as hexadecimal hash string) to long tap on - # - # ```ruby - # touch_and_hold x: 100, y: 100 - # touch_and_hold x: 100, y: 100, duration: 2.0 - # touch_and_hold element: find_element(:accessibility_id, "some item") - # ``` - def touch_and_hold(x: nil, y: nil, element: nil, duration: 1.0) - return 'require XCUITest(WDA)' unless automation_name_is_xcuitest? - return 'Set x, y or element' if (x.nil? || y.nil?) && element.nil? - - args = element.nil? ? { x: x, y: y } : { element: element.ref } - args[:duration] = duration - execute_script 'mobile: touchAndHold', args - end - - # @param [Element] :element Element to long tap on. - # - # ```ruby - # two_finger_tap element: find_element(:accessibility_id, "some item") - # ``` - def two_finger_tap(element:) - return 'require XCUITest(WDA)' unless automation_name_is_xcuitest? - - args = { element: element.ref } - execute_script 'mobile: twoFingerTap', args - end - - # @param x [float] X tap coordinate of type float. Mandatory parameter - # @param y [float] Y tap coordinate of type float. Mandatory parameter - # @option opts [Element] :element Element id to long tap on. x and y tap coordinates will be calculated - # relatively to the current element position on the screen if this argument is provided. - # Otherwise they should be calculated relatively to screen borders. - # - # ```ruby - # tap x: 100, y: 100 - # tap x: 100, y: 100, element: find_element(:accessibility_id, "some item") - # ``` - def tap(x:, y:, element: nil) - return 'require XCUITest(WDA)' unless automation_name_is_xcuitest? - - args = { x: x, y: y } - args[:element] = element.ref if element - execute_script 'mobile: tap', args - end - - # rubocop:disable Metrics/ParameterLists - # @param duration [float] Float number of seconds in range [0.5, 60]. How long the tap gesture at starting - # drag point should be before to start dragging. Mandatory parameter - # @param from_x [float] The x coordinate of starting drag point (type float). Mandatory parameter - # @param from_y [float] The y coordinate of starting drag point (type float). Mandatory parameter - # @param to_x [float] The x coordinate of ending drag point (type float). Mandatory parameter - # @param to_y [float] The y coordinate of ending drag point (type float). Mandatory parameter - # @option opts [Element] :element Element id to perform drag on. All the coordinates will be calculated - # relatively this this element position on the screen. Absolute screen coordinates are expected - # if this argument is not set - # - # ```ruby - # drag_from_to_for_duration from_x: 100, from_y: 100, to_x: 150, to_y: 150 - # ``` - def drag_from_to_for_duration(from_x:, from_y:, to_x:, to_y:, duration: 1.0, element: nil) - return 'require XCUITest(WDA)' unless automation_name_is_xcuitest? - - args = { fromX: from_x, fromY: from_y, toX: to_x, toY: to_y, duration: duration } - args[:element] = element.ref if element - execute_script 'mobile: dragFromToForDuration', args - end - # rubocop:enable Metrics/ParameterLists - - # https://github.com/facebook/WebDriverAgent/pull/523 - # https://github.com/appium/appium-xcuitest-driver/pull/420 - # @param order [String] The order to move picker to. "next" or "previous". - # @param element [Element] Element id to perform select picker wheel on. - # @option opts [Integer] :offset The value in range [0.01, 0.5]. Default is 0.2 in server side. - # https://github.com/facebook/WebDriverAgent/pull/549/files - # - # ```ruby - # select_picker_wheel order: "next", element: find_element(:accessibility_id, "some picker") - # ``` - def select_picker_wheel(element:, order:, offset: nil) - return 'require XCUITest(WDA)' unless automation_name_is_xcuitest? - return 'Set "next" or "previous" for :order' unless %w(next previous).include?(order) - - args = { element: element.ref, order: order } - args[:offset] = offset if offset - execute_script 'mobile: selectPickerWheelValue', args - end - - # @param action [String] The following actions are supported: accept, dismiss and getButtons. Mandatory parameter - # @param button_label [String] The label text of an existing alert button to click on. - # This is an optional parameter and is only valid in combination with accept and dismiss actions. - # @return {} or Selenium::WebDriver::Error::NoSuchAlertError if no action sheet or alert - # or button labels if action is equal to getButtons. - # - # ```ruby - # alert action: "accept" - # alert action: "dismiss" - # ``` - def alert(action:, button_label: nil) - return 'Set "accept", "dismiss" or "getButtons" for :action' unless %w(accept dismiss getButtons).include?(action) - - args = { action: action } - args[:button_label] if button_label - execute_script 'mobile: alert', args - end - end # module XcuitestGesture + module Xcuitest + module Gesture + # @param [string] direction Either 'up', 'down', 'left' or 'right'. + # @option opts [Element] :element Element to swipe on + # + # ```ruby + # swipe direction: "down" + # ``` + def swipe(direction:, element: nil) + return unless %w(up down left right).include?(direction) + + args = { direction: direction } + args[:element] = element.ref if element + + execute_script 'mobile: swipe', args + end + + # @param [string] direction Either 'up', 'down', 'left' or 'right'. + # @option opts [String] :name the accessibility id of the child element, to which scrolling is performed. + # @option opts [Element] :element Element id to long tap on. + # @option opts [bool] :to_visible Boolean parameter. If set to true then asks to scroll to the first visible + # element in the parent container. Has no effect if element is not set + # @option opts [String] :predicate_string the NSPredicate locator of the child element, + # to which the scrolling should be performed. Has no effect if element is not a container + # + # ```ruby + # scroll direction: "down" + # ``` + def scroll(direction:, name: nil, element: nil, to_visible: nil, predicate_string: nil) + return 'Set "up", "down", "left" or "right" for :direction' unless %w(up down left right).include?(direction) + + args = { direction: direction } + args[:element] = element.ref if element + args[:name] = name if name + args[:toVisible] = to_visible if to_visible + args[:predicateString] = predicate_string if predicate_string + + execute_script 'mobile: scroll', args + end + + # @param scale [scale] X tap coordinate of type float. Mandatory parameter + # @param velocity [float] Y tap coordinate of type float. Mandatory parameter + # @option opts [Element] :element Element id to long tap on. + # + # ```ruby + # pinch scale: 0.5, velocity: -1 + # ``` + def pinch(scale:, velocity: 1.0, element: nil) + return unless automation_name_is_xcuitest? + + args = { scale: scale, velocity: velocity } + args[:element] = element.ref if element + + execute_script 'mobile: pinch', args + end + + # @param x [float] X Screen x tap coordinate of type float. Mandatory parameter only if element is not set + # @param y [float] Y Screen y tap coordinate of type float. Mandatory parameter only if element is not set + # @option opts [Element] :element Element to long tap on. + # + # ```ruby + # double_tap x: 100, y: 100 + # double_tap element: find_element(:accessibility_id, "some item") + # ``` + def double_tap(x: nil, y: nil, element: nil) + return 'require XCUITest(WDA)' unless automation_name_is_xcuitest? + return 'Set x, y or element' if (x.nil? || y.nil?) && element.nil? + + args = element.nil? ? { x: x, y: y } : { element: element.ref } + execute_script 'mobile: doubleTap', args + end + + # @param x [float] Screen x long tap coordinate of type float. Mandatory parameter only if element is not set + # @param y [float] Screen y long tap coordinate of type float. Mandatory parameter only if element is not set + # @param duration [Float] The float duration of press action in seconds. Mandatory parameter + # @option opts [Element] :element The internal element identifier (as hexadecimal hash string) to long tap on + # + # ```ruby + # touch_and_hold x: 100, y: 100 + # touch_and_hold x: 100, y: 100, duration: 2.0 + # touch_and_hold element: find_element(:accessibility_id, "some item") + # ``` + def touch_and_hold(x: nil, y: nil, element: nil, duration: 1.0) + return 'require XCUITest(WDA)' unless automation_name_is_xcuitest? + return 'Set x, y or element' if (x.nil? || y.nil?) && element.nil? + + args = element.nil? ? { x: x, y: y } : { element: element.ref } + args[:duration] = duration + execute_script 'mobile: touchAndHold', args + end + + # @param [Element] :element Element to long tap on. + # + # ```ruby + # two_finger_tap element: find_element(:accessibility_id, "some item") + # ``` + def two_finger_tap(element:) + return 'require XCUITest(WDA)' unless automation_name_is_xcuitest? + + args = { element: element.ref } + execute_script 'mobile: twoFingerTap', args + end + + # @param x [float] X tap coordinate of type float. Mandatory parameter + # @param y [float] Y tap coordinate of type float. Mandatory parameter + # @option opts [Element] :element Element id to long tap on. x and y tap coordinates will be calculated + # relatively to the current element position on the screen if this argument is provided. + # Otherwise they should be calculated relatively to screen borders. + # + # ```ruby + # tap x: 100, y: 100 + # tap x: 100, y: 100, element: find_element(:accessibility_id, "some item") + # ``` + def tap(x:, y:, element: nil) + return 'require XCUITest(WDA)' unless automation_name_is_xcuitest? + + args = { x: x, y: y } + args[:element] = element.ref if element + execute_script 'mobile: tap', args + end + + # rubocop:disable Metrics/ParameterLists + # @param duration [float] Float number of seconds in range [0.5, 60]. How long the tap gesture at starting + # drag point should be before to start dragging. Mandatory parameter + # @param from_x [float] The x coordinate of starting drag point (type float). Mandatory parameter + # @param from_y [float] The y coordinate of starting drag point (type float). Mandatory parameter + # @param to_x [float] The x coordinate of ending drag point (type float). Mandatory parameter + # @param to_y [float] The y coordinate of ending drag point (type float). Mandatory parameter + # @option opts [Element] :element Element id to perform drag on. All the coordinates will be calculated + # relatively this this element position on the screen. Absolute screen coordinates are expected + # if this argument is not set + # + # ```ruby + # drag_from_to_for_duration from_x: 100, from_y: 100, to_x: 150, to_y: 150 + # ``` + def drag_from_to_for_duration(from_x:, from_y:, to_x:, to_y:, duration: 1.0, element: nil) + return 'require XCUITest(WDA)' unless automation_name_is_xcuitest? + + args = { fromX: from_x, fromY: from_y, toX: to_x, toY: to_y, duration: duration } + args[:element] = element.ref if element + execute_script 'mobile: dragFromToForDuration', args + end + # rubocop:enable Metrics/ParameterLists + + # https://github.com/facebook/WebDriverAgent/pull/523 + # https://github.com/appium/appium-xcuitest-driver/pull/420 + # @param order [String] The order to move picker to. "next" or "previous". + # @param element [Element] Element id to perform select picker wheel on. + # @option opts [Integer] :offset The value in range [0.01, 0.5]. Default is 0.2 in server side. + # https://github.com/facebook/WebDriverAgent/pull/549/files + # + # ```ruby + # select_picker_wheel order: "next", element: find_element(:accessibility_id, "some picker") + # ``` + def select_picker_wheel(element:, order:, offset: nil) + return 'require XCUITest(WDA)' unless automation_name_is_xcuitest? + return 'Set "next" or "previous" for :order' unless %w(next previous).include?(order) + + args = { element: element.ref, order: order } + args[:offset] = offset if offset + execute_script 'mobile: selectPickerWheelValue', args + end + + # @param action [String] The following actions are supported: accept, dismiss and getButtons. Mandatory parameter + # @param button_label [String] The label text of an existing alert button to click on. + # This is an optional parameter and is only valid in combination with accept and dismiss actions. + # @return {} or Selenium::WebDriver::Error::NoSuchAlertError if no action sheet or alert + # or button labels if action is equal to getButtons. + # + # ```ruby + # alert action: "accept" + # alert action: "dismiss" + # ``` + def alert(action:, button_label: nil) + return 'Set "accept", "dismiss" or "getButtons" for :action' unless %w(accept dismiss getButtons).include?(action) + + args = { action: action } + args[:button_label] if button_label + execute_script 'mobile: alert', args + end + end # module Gesture + end # module Xcuitest end # module Ios end # module Appium From 417774af6eb95ac93d2d81bfc61c0a217ed5f19a Mon Sep 17 00:00:00 2001 From: Kazuaki MATSUO Date: Sat, 29 Jul 2017 09:49:22 +0900 Subject: [PATCH 4/5] handle uiautomationName as hash --- android_tests/lib/android/specs/driver.rb | 2 +- ios_tests/lib/ios/specs/driver.rb | 4 ++-- ios_tests/lib/ios/specs/ios/mobile_methods.rb | 4 ++-- lib/appium_lib/driver.rb | 5 +++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/android_tests/lib/android/specs/driver.rb b/android_tests/lib/android/specs/driver.rb index 71bf8f2d..ead84439 100644 --- a/android_tests/lib/android/specs/driver.rb +++ b/android_tests/lib/android/specs/driver.rb @@ -35,7 +35,7 @@ def sauce? end t 'verify Appium::Driver::Capabilities.init_caps_for_appium' do - expected_app = File.absolute_path('api.apk') + expected_app = File.absolute_path('../test_apps/api.apk') caps = ::Appium::Driver::Capabilities.init_caps_for_appium(platformName: 'Android', app: expected_app, appPackage: 'io.appium.android.apis', diff --git a/ios_tests/lib/ios/specs/driver.rb b/ios_tests/lib/ios/specs/driver.rb index c01d4ef9..c307b4c9 100644 --- a/ios_tests/lib/ios/specs/driver.rb +++ b/ios_tests/lib/ios/specs/driver.rb @@ -58,9 +58,9 @@ def sauce? t 'verify all attributes' do actual = driver_attributes caps_app_for_teardown = actual[:caps][:app] - expected_app = File.absolute_path('UICatalog.app') + expected_app = File.absolute_path('../test_apps/UICatalog.app') - expected = { automation_name: 'XCUITest', + expected = { automation_name: :xcuitest, custom_url: false, export_session: false, default_wait: 30, diff --git a/ios_tests/lib/ios/specs/ios/mobile_methods.rb b/ios_tests/lib/ios/specs/ios/mobile_methods.rb index 8548fa6c..4a5de883 100644 --- a/ios_tests/lib/ios/specs/ios/mobile_methods.rb +++ b/ios_tests/lib/ios/specs/ios/mobile_methods.rb @@ -1,5 +1,5 @@ -# rake ios[ios/search_context] -describe 'ios/search_context' do +# rake ios[ios/mobile_methods] +describe 'ios/mobile_methods' do def before_first screen.must_equal catalog end diff --git a/lib/appium_lib/driver.rb b/lib/appium_lib/driver.rb index 618a9af8..296e6b04 100644 --- a/lib/appium_lib/driver.rb +++ b/lib/appium_lib/driver.rb @@ -388,6 +388,7 @@ def initialize(opts = {}) @appium_device = @appium_device.is_a?(Symbol) ? @appium_device : @appium_device.downcase.strip.intern if @appium_device @automation_name = @caps[:automationName] if @caps[:automationName] + @automation_name = @automation_name.is_a?(Symbol) ? @automation_name : @automation_name.downcase.strip.intern if @automation_name # load common methods extend Appium::Common @@ -480,13 +481,13 @@ def device_is_android? # Return true if automationName is 'XCUITest' # @return [Boolean] def automation_name_is_xcuitest? - !@automation_name.nil? && 'xcuitest'.casecmp(@automation_name).zero? + !@automation_name.nil? && @automation_name == :xcuitest end # Return true if automationName is 'uiautomator2' # @return [Boolean] def automation_name_is_uiautomator2? - !@automation_name.nil? && 'uiautomator2'.casecmp(@automation_name).zero? + !@automation_name.nil? && @automation_name == :uiautomator2 end # Return true if the target Appium server is over REQUIRED_VERSION_XCUITEST. From 85cd1136896346fbb3e1f9ef2429e5fd5ed22be2 Mon Sep 17 00:00:00 2001 From: Kazuaki MATSUO Date: Sat, 29 Jul 2017 10:04:28 +0900 Subject: [PATCH 5/5] fix rubocop --- lib/appium_lib/common/search_context.rb | 2 +- lib/appium_lib/driver.rb | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/appium_lib/common/search_context.rb b/lib/appium_lib/common/search_context.rb index 636918ae..addc9b33 100644 --- a/lib/appium_lib/common/search_context.rb +++ b/lib/appium_lib/common/search_context.rb @@ -3,7 +3,7 @@ class Driver module SearchContext # rubocop:disable Style/MutableConstant FINDERS = { - accessibility_id: 'accessibility id', + accessibility_id: 'accessibility id' } end end diff --git a/lib/appium_lib/driver.rb b/lib/appium_lib/driver.rb index 296e6b04..2cc9ba39 100644 --- a/lib/appium_lib/driver.rb +++ b/lib/appium_lib/driver.rb @@ -388,7 +388,9 @@ def initialize(opts = {}) @appium_device = @appium_device.is_a?(Symbol) ? @appium_device : @appium_device.downcase.strip.intern if @appium_device @automation_name = @caps[:automationName] if @caps[:automationName] - @automation_name = @automation_name.is_a?(Symbol) ? @automation_name : @automation_name.downcase.strip.intern if @automation_name + @automation_name = if @automation_name + @automation_name.is_a?(Symbol) ? @automation_name : @automation_name.downcase.strip.intern + end # load common methods extend Appium::Common