# frozen_string_literal: true # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. module Appium module Core class Base module SearchContext # referenced: ::Selenium::WebDriver::SearchContext FINDERS = ::Selenium::WebDriver::SearchContext::FINDERS.merge( accessibility_id: 'accessibility id', image: '-image', custom: '-custom', # Android uiautomator: '-android uiautomator', # Unavailable in Espresso viewtag: '-android viewtag', # Available in Espresso data_matcher: '-android datamatcher', # Available in Espresso view_matcher: '-android viewmatcher', # Available in Espresso # iOS predicate: '-ios predicate string', class_chain: '-ios class chain' ) # rubocop:disable Layout/LineLength # # Find the first element matching the given arguments # # - Android can find with uiautomator like a {http://developer.android.com/tools/help/uiautomator/UiSelector.html UISelector}. # - iOS can find with a {https://developer.apple.com/library/ios/documentation/ToolsLanguages/Reference/UIAWindowClassReference/UIAWindow/UIAWindow.html#//apple_ref/doc/uid/TP40009930 UIAutomation command}. # - iOS, only for XCUITest(WebDriverAgent), can find with a {https://github.com/facebook/WebDriverAgent/wiki/Queries class chain} # # == Find with image # Return an element if current view has a partial image. The logic depends on template matching by OpenCV. # {https://github.com/appium/appium/blob/1.x/docs/en/writing-running-appium/image-comparison.md image-comparison} # # You can handle settings for the comparision following {https://github.com/appium/appium-base-driver/blob/master/lib/basedriver/device-settings.js#L6 here} # # == Espresso viewmatcher and datamatcher # Espresso has {https://developer.android.com/training/testing/espresso/basics _onView_ matcher} # and {https://medium.com/androiddevelopers/adapterviews-and-espresso-f4172aa853cf _onData_ matcher} for more reference # that allows you to target adapters instead of Views. This method find methods based on reflections # # This is a selector strategy that allows users to pass a selector of the form: # # { name: '', args: ['arg1', 'arg2', '...'], class: '' } # # - _name_: The name of a method to invoke. The method must return # a Hamcrest {http://hamcrest.org/JavaHamcrest/javadoc/1.3/org/hamcrest/Matcher.html Matcher} # - _args_: The args provided to the method # - _class_: The class name that the method is part of (defaults to org.hamcrest.Matchers). # Can be fully qualified, or simple, and simple defaults to androidx.test.espresso.matcher package # (e.g.: class=CursorMatchers fully qualified is class=androidx.test.espresso.matcher.CursorMatchers # # See example how to send viewmatcher and datamatcher in Ruby client # # # @overload find_element(how, what) # @param [Symbol, String] how The method to find the element by # @param [String] what The locator to use # # @overload find_element(opts) # @param [Hash] opts Find options # @option opts [Symbol] :how Key named after the method to find the element by, containing the locator # @return [Element] # @raise [Error::NoSuchElementError] if the element doesn't exist # # @example Find element with each keys # # # with accessibility id. All platforms. # @driver.find_elements :accessibility_id, 'Animation' # @driver.find_elements :accessibility_id, 'Animation' # # # with base64 encoded template image. All platforms. # @driver.find_elements :image, Base64.strict_encode64(File.read(file_path)) # # # For Android # ## With uiautomator # @driver.find_elements :uiautomator, 'new UiSelector().clickable(true)' # ## With viewtag, but only for Espresso # ## 'setTag'/'getTag' in https://developer.android.com/reference/android/view/View # @driver.find_elements :viewtag, 'new UiSelector().clickable(true)' # # With data_matcher. The argument should be JSON format. # @driver.find_elements :data_matcher, { name: 'hasEntry', args: %w(title Animation) }.to_json # # # For iOS # ## With :predicate # @driver.find_elements :predicate, "isWDVisible == 1" # @driver.find_elements :predicate, 'wdName == "Buttons"' # @driver.find_elements :predicate, 'wdValue == "SearchBar" AND isWDDivisible == 1' # # ## With Class Chain # ### select the third child button of the first child window element # @driver.find_elements :class_chain, 'XCUIElementTypeWindow/XCUIElementTypeButton[3]' # ### select all the children windows # @driver.find_elements :class_chain, 'XCUIElementTypeWindow' # ### select the second last child of the second child window # @driver.find_elements :class_chain, 'XCUIElementTypeWindow[2]/XCUIElementTypeAny[-2]' # ### matching predicate. ' is the mark. # @driver.find_elements :class_chain, 'XCUIElementTypeWindow['visible = 1]['name = "bla"']' # ### containing predicate. '$' is the mark. # ### Require appium-xcuitest-driver 2.54.0+. PR: https://github.com/facebook/WebDriverAgent/pull/707/files # @driver.find_elements :class_chain, 'XCUIElementTypeWindow[$name = \"bla$$$bla\"$]' # e = find_element :class_chain, "**/XCUIElementTypeWindow[$name == 'Buttons'$]" # e.tag_name #=> "XCUIElementTypeWindow" # e = find_element :class_chain, "**/XCUIElementTypeStaticText[$name == 'Buttons'$]" # e.tag_name #=> "XCUIElementTypeStaticText" # # rubocop:enable Layout/LineLength def find_element(*args) how, what = extract_args(args) by = _set_by_from_finders(how) begin bridge.find_element_by by, what.to_s, ref rescue Selenium::WebDriver::Error::TimeoutError raise Selenium::WebDriver::Error::NoSuchElementError end end # # Find all elements matching the given arguments # # @return [Array] # # @see SearchContext#find_elements # def find_elements(*args) how, what = extract_args(args) by = _set_by_from_finders(how) begin bridge.find_elements_by by, what.to_s, ref rescue Selenium::WebDriver::Error::TimeoutError [] end end private def _set_by_from_finders(how) by = FINDERS[how.to_sym] unless by raise ::Appium::Core::Error::ArgumentError, "cannot find element by #{how.inspect}. Available finders are #{FINDERS.keys}." end by end def extract_args(args) case args.size when 2 args when 1 arg = args.first unless arg.respond_to?(:shift) raise ::Appium::Core::Error::ArgumentError, "expected #{arg.inspect}:#{arg.class} to respond to #shift" end # this will be a single-entry hash, so use #shift over #first or #[] arr = arg.dup.shift raise ::Appium::Core::Error::ArgumentError, "expected #{arr.inspect} to have 2 elements" unless arr.size == 2 arr else raise ::Appium::Core::Error::ArgumentError, "wrong number of arguments (#{args.size} for 2)" end end end # module SearchContext end # class Base end # module Core end # module Appium