From 991a653358a9d139fa6960d3b166a3a15262a2fd Mon Sep 17 00:00:00 2001 From: Alex Rodionov Date: Sun, 4 Jun 2023 16:04:09 -0700 Subject: [PATCH] [rb] Support overriding default locator conversion --- rb/lib/selenium/webdriver/remote/bridge.rb | 49 +++--------- .../remote/bridge/locator_converter.rb | 76 +++++++++++++++++++ 2 files changed, 87 insertions(+), 38 deletions(-) create mode 100644 rb/lib/selenium/webdriver/remote/bridge/locator_converter.rb diff --git a/rb/lib/selenium/webdriver/remote/bridge.rb b/rb/lib/selenium/webdriver/remote/bridge.rb index 655d331e1fc57..b977a99aab335 100644 --- a/rb/lib/selenium/webdriver/remote/bridge.rb +++ b/rb/lib/selenium/webdriver/remote/bridge.rb @@ -22,6 +22,8 @@ module WebDriver module Remote class Bridge autoload :COMMANDS, 'selenium/webdriver/remote/bridge/commands' + autoload :LocatorConverter, 'selenium/webdriver/remote/bridge/locator_converter' + include Atoms PORT = 4444 @@ -31,12 +33,17 @@ class Bridge class << self attr_reader :extra_commands + attr_writer :locator_converter def add_command(name, verb, url, &block) @extra_commands ||= {} @extra_commands[name] = [verb, url] define_method(name, &block) end + + def locator_converter + @locator_converter ||= LocatorConverter.new + end end # @@ -53,6 +60,8 @@ def initialize(url:, http_client: nil) @http = http_client || Http::Default.new @http.server_url = uri @file_detector = nil + + @locator_converter = self.class.locator_converter end # @@ -516,7 +525,7 @@ def active_element alias switch_to_active_element active_element def find_element_by(how, what, parent_ref = []) - how, what = convert_locator(how, what) + how, what = @locator_converter.convert(how, what) return execute_atom(:findElements, Support::RelativeLocator.new(what).as_json).first if how == 'relative' @@ -534,7 +543,7 @@ def find_element_by(how, what, parent_ref = []) end def find_elements_by(how, what, parent_ref = []) - how, what = convert_locator(how, what) + how, what = @locator_converter.convert(how, what) return execute_atom :findElements, Support::RelativeLocator.new(what).as_json if how == 'relative' @@ -655,42 +664,6 @@ def prepare_capabilities_payload(capabilities) {capabilities: capabilities} end - def convert_locator(how, what) - how = SearchContext::FINDERS[how.to_sym] || how - - case how - when 'class name' - how = 'css selector' - what = ".#{escape_css(what.to_s)}" - when 'id' - how = 'css selector' - what = "##{escape_css(what.to_s)}" - when 'name' - how = 'css selector' - what = "*[name='#{escape_css(what.to_s)}']" - 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 - - ESCAPE_CSS_REGEXP = /(['"\\#.:;,!?+<>=~*^$|%&@`{}\-\[\]()])/ - UNICODE_CODE_POINT = 30 - - # Escapes invalid characters in CSS selector. - # @see https://mathiasbynens.be/notes/css-escapes - def escape_css(string) - string = string.gsub(ESCAPE_CSS_REGEXP) { |match| "\\#{match}" } - string = "\\#{UNICODE_CODE_POINT + Integer(string[0])} #{string[1..]}" if string[0]&.match?(/[[:digit:]]/) - - string - end end # Bridge end # Remote end # WebDriver diff --git a/rb/lib/selenium/webdriver/remote/bridge/locator_converter.rb b/rb/lib/selenium/webdriver/remote/bridge/locator_converter.rb new file mode 100644 index 0000000000000..6c1065b9a5a25 --- /dev/null +++ b/rb/lib/selenium/webdriver/remote/bridge/locator_converter.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +# Licensed to the Software Freedom Conservancy (SFC) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The SFC licenses this file +# to you 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 Selenium + module WebDriver + module Remote + class Bridge + class LocatorConverter + ESCAPE_CSS_REGEXP = /(['"\\#.:;,!?+<>=~*^$|%&@`{}\-\[\]()])/ + UNICODE_CODE_POINT = 30 + + # + # Converts a locator to a specification compatible one. + # @param [String, Symbol] how + # @param [String] what + # + + def convert(how, what) + how = SearchContext.finders[how.to_sym] || how + + case how + when 'class name' + how = 'css selector' + what = ".#{escape_css(what.to_s)}" + when 'id' + how = 'css selector' + what = "##{escape_css(what.to_s)}" + when 'name' + how = 'css selector' + what = "*[name='#{escape_css(what.to_s)}']" + end + + if what.is_a?(Hash) + what = what.each_with_object({}) do |(h, w), hash| + h, w = convert(h.to_s, w) + hash[h] = w + end + end + + [how, what] + end + + private + + # + # Escapes invalid characters in CSS selector. + # @see https://mathiasbynens.be/notes/css-escapes + # + + def escape_css(string) + string = string.gsub(ESCAPE_CSS_REGEXP) { |match| "\\#{match}" } + string = "\\#{UNICODE_CODE_POINT + Integer(string[0])} #{string[1..]}" if string[0]&.match?(/[[:digit:]]/) + + string + end + end # LocatorConverter + end # Bridge + end # Remote + end # WebDriver +end # Selenium