Skip to content

Commit

Permalink
[rb] Support overriding default locator conversion
Browse files Browse the repository at this point in the history
  • Loading branch information
p0deje committed May 15, 2024
1 parent 4cf9aeb commit 991a653
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 38 deletions.
49 changes: 11 additions & 38 deletions rb/lib/selenium/webdriver/remote/bridge.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

#
Expand All @@ -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

#
Expand Down Expand Up @@ -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'

Expand All @@ -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'

Expand Down Expand Up @@ -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
Expand Down
76 changes: 76 additions & 0 deletions rb/lib/selenium/webdriver/remote/bridge/locator_converter.rb
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 991a653

Please sign in to comment.