diff --git a/CHANGELOG.md b/CHANGELOG.md index ddfeb8b1..ac93b9e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Read `release_notes.md` for commit level details. ## [Unreleased] ### Enhancements +- Simplify internal code with Selenium 4.21.0. Now it requires selenium webdriver v4.21.0. ### Bug fixes diff --git a/appium_lib_core.gemspec b/appium_lib_core.gemspec index 77aa3173..b8fd1087 100644 --- a/appium_lib_core.gemspec +++ b/appium_lib_core.gemspec @@ -22,7 +22,7 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] - spec.add_runtime_dependency 'selenium-webdriver', '~> 4.2' + spec.add_runtime_dependency 'selenium-webdriver', '~> 4.21' spec.add_runtime_dependency 'faye-websocket', '~> 0.11.0' spec.add_development_dependency 'rake', '~> 13.0' diff --git a/lib/appium_lib_core/common/base/bridge.rb b/lib/appium_lib_core/common/base/bridge.rb index 26a23c45..3d6b5c3a 100644 --- a/lib/appium_lib_core/common/base/bridge.rb +++ b/lib/appium_lib_core/common/base/bridge.rb @@ -15,6 +15,12 @@ module Appium module Core class Base + class LocatorConverter + def convert(how, what) + [how, what] + end + end # LocatorConverter + class Bridge < ::Selenium::WebDriver::Remote::Bridge include Device::DeviceLock include Device::Keyboard @@ -31,6 +37,8 @@ class Bridge < ::Selenium::WebDriver::Remote::Bridge include Device::ExecuteDriver include Device::Orientation + Bridge.locator_converter = LocatorConverter.new + # Prefix for extra capability defined by W3C APPIUM_PREFIX = 'appium:' @@ -153,6 +161,18 @@ def json_create(value) public # command for Appium 2.0. + + # Example: + # driver.add_command(name: :available_contexts, method: :get, url: 'session/:session_id/contexts') do + # execute(:available_contexts, {}) || [] + # end + # Then, + # driver.available_contexts #=> ["NATIVE_APP"] + + # def add_command(method:, url:, name:, &block) + # Bridge.add_command name, method, url, &block + # end + def add_command(method:, url:, name:, &block) ::Appium::Logger.info "Overriding the method '#{name}' for '#{url}'" if @available_commands.key? name @@ -162,7 +182,7 @@ def add_command(method:, url:, name:, &block) end def commands(command) - @available_commands[command] + @available_commands[command] || Bridge.extra_commands[command] end def status @@ -216,52 +236,8 @@ def element_attribute(element, name) end # For Appium - # override - def active_element - ::Appium::Core::Element.new self, element_id_from(execute(:get_active_element)) - end alias switch_to_active_element active_element - # For Appium - # override - def find_element_by(how, what, parent_ref = []) - how, what = convert_locator(how, what) - - return execute_atom(:findElements, Support::RelativeLocator.new(what).as_json).first if how == 'relative' - - parent_type, parent_id = parent_ref - id = case parent_type - when :element - execute :find_child_element, { id: parent_id }, { using: how, value: what.to_s } - when :shadow_root - execute :find_shadow_child_element, { id: parent_id }, { using: how, value: what.to_s } - else - execute :find_element, {}, { using: how, value: what.to_s } - end - - ::Appium::Core::Element.new self, element_id_from(id) - end - - # For Appium - # override - def find_elements_by(how, what, parent_ref = []) - how, what = convert_locator(how, what) - - return execute_atom :findElements, Support::RelativeLocator.new(what).as_json if how == 'relative' - - parent_type, parent_id = parent_ref - ids = case parent_type - when :element - execute :find_child_elements, { id: parent_id }, { using: how, value: what.to_s } - when :shadow_root - execute :find_shadow_child_elements, { id: parent_id }, { using: how, value: what.to_s } - else - execute :find_elements, {}, { using: how, value: what.to_s } - end - - ids.map { |id| ::Appium::Core::Element.new self, element_id_from(id) } - end - # For Appium # @param [Hash] id The id which can get as a response from server # @return [::Appium::Core::Element] @@ -370,36 +346,6 @@ def unwrap_script_result(arg) arg end end - - def element_id_from(id) - id['ELEMENT'] || id['element-6066-11e4-a52e-4f735466cecf'] - end - - # Don't convert locators for Appium in native context - def convert_locator(how, what) - # case how - # when 'class name' - # how = 'css selector' - # what = ".#{escape_css(what)}" - # when 'id' - # how = 'css selector' - # what = "##{escape_css(what)}" - # when 'name' - # how = 'css selector' - # what = "*[name='#{escape_css(what)}']" - # when 'tag name' - # how = 'css selector' - # end - # - # if what.is_a?(Hash) - # what = what.each_with_object({}) do |(h, w), hash| - # h, w = convert_locator(h.to_s, w) - # hash[h] = w - # end - # end - - [how, what] - end end # class Bridge end # class Base end # module Core diff --git a/lib/appium_lib_core/common/base/driver.rb b/lib/appium_lib_core/common/base/driver.rb index 3199ca0f..3a3d0501 100644 --- a/lib/appium_lib_core/common/base/driver.rb +++ b/lib/appium_lib_core/common/base/driver.rb @@ -32,7 +32,6 @@ class Driver < ::Selenium::WebDriver::Driver include ::Selenium::WebDriver::DriverExtensions::HasWebStorage include ::Appium::Core::Base::Rotatable - include ::Appium::Core::Base::SearchContext include ::Appium::Core::Base::TakesScreenshot include ::Appium::Core::Base::HasRemoteStatus include ::Appium::Core::Base::HasLocation @@ -40,6 +39,8 @@ class Driver < ::Selenium::WebDriver::Driver include ::Appium::Core::Waitable + ::Selenium::WebDriver::SearchContext.extra_finders = APPIUM_EXTRA_FINDERS + # Private API. # Do not use this for general use. Used by flutter driver to get bridge for creating a new element attr_reader :bridge @@ -57,6 +58,7 @@ def initialize(bridge: nil, listener: nil, **opts) # rubocop:disable Lint/Missin @bidi = nil # in the selenium webdriver as well + ::Selenium::WebDriver::Remote::Bridge.element_class = ::Appium::Core::Element bridge ||= create_bridge(**opts) add_extensions(bridge.browser) @bridge = listener ? ::Appium::Support::EventFiringBridge.new(bridge, listener, **original_opts) : bridge @@ -1023,8 +1025,8 @@ def execute_driver(script: '', type: 'webdriverio', timeout_ms: nil) # ele = @driver.convert_to_element(response) #=> ::Appium::Core::Element # ele.rect #=> Can get the rect of the element # - def convert_to_element(id) - @bridge.convert_to_element id + def convert_to_element(response_id) + @bridge.convert_to_element response_id end end # class Driver end # class Base diff --git a/lib/appium_lib_core/common/base/http_default.rb b/lib/appium_lib_core/common/base/http_default.rb index ea9b23c3..da61b3c1 100644 --- a/lib/appium_lib_core/common/base/http_default.rb +++ b/lib/appium_lib_core/common/base/http_default.rb @@ -31,6 +31,9 @@ module RequestHeaders class Default < ::Selenium::WebDriver::Remote::Http::Default attr_reader :additional_headers + ::Selenium::WebDriver::Remote::Http::Common.user_agent = \ + "appium/ruby_lib_core/#{VERSION} (#{::Selenium::WebDriver::Remote::Http::Common.user_agent})" + # override def initialize(open_timeout: nil, read_timeout: nil) @open_timeout = open_timeout @@ -39,6 +42,17 @@ def initialize(open_timeout: nil, read_timeout: nil) super end + def set_additional_header(key, value) + @additional_headers[key] = value + ::Selenium::WebDriver::Remote::Http::Common.extra_headers = @additional_headers + end + + def delete_additional_header(key) + @additional_headers.delete key + ::Selenium::WebDriver::Remote::Http::Common.extra_headers = @additional_headers + @common_headers.delete key if defined? @common_headers + end + # Update server_url provided when ruby_lib _core created a default http client. # Set @http as nil to re-create http client for the server_url # @@ -59,13 +73,6 @@ def update_sending_request_to(scheme:, host:, port:, path:) @server_url = URI.parse "#{scheme}://#{host}:#{port}#{path}" end - def request(verb, url, headers, payload, redirects = 0) - headers['User-Agent'] = "appium/ruby_lib_core/#{VERSION} (#{headers['User-Agent']})" - headers = headers.merge @additional_headers unless @additional_headers.empty? - - super(verb, url, headers, payload, redirects) - end - private def validate_url_param(scheme, host, port, path) diff --git a/lib/appium_lib_core/common/base/search_context.rb b/lib/appium_lib_core/common/base/search_context.rb index 35977c09..66797950 100644 --- a/lib/appium_lib_core/common/base/search_context.rb +++ b/lib/appium_lib_core/common/base/search_context.rb @@ -12,172 +12,102 @@ # 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 +# 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 - 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 +APPIUM_EXTRA_FINDERS = { + 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' +}.freeze diff --git a/lib/appium_lib_core/driver.rb b/lib/appium_lib_core/driver.rb index ceb5eba6..27aa9d54 100644 --- a/lib/appium_lib_core/driver.rb +++ b/lib/appium_lib_core/driver.rb @@ -402,7 +402,7 @@ def start_driver(server_url: nil, if @enable_idempotency_header if @http_client.instance_variable_defined? :@additional_headers - @http_client.additional_headers[Appium::Core::Base::Http::RequestHeaders::KEYS[:idempotency]] = SecureRandom.uuid + @http_client.set_additional_header Appium::Core::Base::Http::RequestHeaders::KEYS[:idempotency], SecureRandom.uuid else ::Appium::Logger.warn 'No additional_headers attribute in this http client instance' end @@ -426,7 +426,7 @@ def start_driver(server_url: nil, if @http_client.instance_variable_defined? :@additional_headers # We only need the key for a new session request. Should remove it for other following commands. - @http_client.additional_headers.delete Appium::Core::Base::Http::RequestHeaders::KEYS[:idempotency] + @http_client.delete_additional_header Appium::Core::Base::Http::RequestHeaders::KEYS[:idempotency] end # TODO: this method can be removed after releasing Appium 2.0, and after a while diff --git a/lib/appium_lib_core/element.rb b/lib/appium_lib_core/element.rb index aa41cdc0..58eeca8f 100644 --- a/lib/appium_lib_core/element.rb +++ b/lib/appium_lib_core/element.rb @@ -17,9 +17,10 @@ module Core # Implement useful features for element. # Patch for Selenium Webdriver. class Element < ::Selenium::WebDriver::Element - include ::Appium::Core::Base::SearchContext include ::Appium::Core::Base::TakesScreenshot + ::Selenium::WebDriver::SearchContext.extra_finders = APPIUM_EXTRA_FINDERS + # Retuns the element id. # # @return [String] diff --git a/test/test_helper.rb b/test/test_helper.rb index da53f673..f35dca6f 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -453,6 +453,8 @@ def android_mock_create_session_w3c .to_return(headers: HEADER, status: 200, body: { value: nil }.to_json) driver = @core.start_driver + assert_equal ::Appium::Core::Base::Driver, driver.class + assert_equal ::Appium::Core::Base::Bridge, driver.bridge.class assert_equal({}, driver.send(:bridge).http.additional_headers) assert_requested( diff --git a/test/unit/common_test.rb b/test/unit/common_test.rb index 135c3725..51c76589 100644 --- a/test/unit/common_test.rb +++ b/test/unit/common_test.rb @@ -22,6 +22,7 @@ class AppiumCoreBaseBridgeTest < Minitest::Test include AppiumLibCoreTest::Mock def setup + ::Selenium::WebDriver::Remote::Bridge.element_class = ::Appium::Core::Element @bridge = Appium::Core::Base::Bridge.new url: 'http://127.0.0.1:4723/wd/hub' end diff --git a/test/unit/driver_test.rb b/test/unit/driver_test.rb index ad5eec0b..b6ee380e 100644 --- a/test/unit/driver_test.rb +++ b/test/unit/driver_test.rb @@ -442,27 +442,31 @@ def _android_mock_create_session_w3c_with_custom_http_client(core) # https://www.w3.org/TR/webdriver1/ def test_search_context_in_element_class - assert_equal 20, ::Appium::Core::Element::FINDERS.length - assert_equal({ class: 'class name', - class_name: 'class name', - css: 'css selector', # Defined in W3C spec - id: 'id', - link: 'link text', # Defined in W3C spec - link_text: 'link text', # Defined in W3C spec - name: 'name', - partial_link_text: 'partial link text', # Defined in W3C spec - relative: 'relative', # Defined in Selenium - tag_name: 'tag name', # Defined in W3C spec - xpath: 'xpath', # Defined in W3C spec - accessibility_id: 'accessibility id', - image: '-image', - custom: '-custom', - uiautomator: '-android uiautomator', - viewtag: '-android viewtag', - data_matcher: '-android datamatcher', - view_matcher: '-android viewmatcher', - predicate: '-ios predicate string', - class_chain: '-ios class chain' }, ::Appium::Core::Element::FINDERS) + assert_equal( + { + class: 'class name', + class_name: 'class name', + css: 'css selector', # Defined in W3C spec + id: 'id', + link: 'link text', # Defined in W3C spec + link_text: 'link text', # Defined in W3C spec + name: 'name', + partial_link_text: 'partial link text', # Defined in W3C spec + relative: 'relative', # Defined in Selenium + tag_name: 'tag name', # Defined in W3C spec + xpath: 'xpath', # Defined in W3C spec + accessibility_id: 'accessibility id', + image: '-image', + custom: '-custom', + uiautomator: '-android uiautomator', + viewtag: '-android viewtag', + data_matcher: '-android datamatcher', + view_matcher: '-android viewmatcher', + predicate: '-ios predicate string', + class_chain: '-ios class chain' + }, + ::Appium::Core::Element::FINDERS.merge(::Selenium::WebDriver::SearchContext.extra_finders) + ) end def test_attach_to_an_existing_session