Skip to content

Commit 724d7dc

Browse files
committed
[rb] Support overriding default locator conversion
1 parent e078018 commit 724d7dc

File tree

2 files changed

+87
-38
lines changed

2 files changed

+87
-38
lines changed

rb/lib/selenium/webdriver/remote/bridge.rb

+11-38
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ module WebDriver
2222
module Remote
2323
class Bridge
2424
autoload :COMMANDS, 'selenium/webdriver/remote/bridge/commands'
25+
autoload :LocatorConverter, 'selenium/webdriver/remote/bridge/locator_converter'
26+
2527
include Atoms
2628

2729
PORT = 4444
@@ -31,12 +33,17 @@ class Bridge
3133

3234
class << self
3335
attr_reader :extra_commands
36+
attr_writer :locator_converter
3437

3538
def add_command(name, verb, url, &block)
3639
@extra_commands ||= {}
3740
@extra_commands[name] = [verb, url]
3841
define_method(name, &block)
3942
end
43+
44+
def locator_converter
45+
@locator_converter ||= LocatorConverter.new
46+
end
4047
end
4148

4249
#
@@ -53,6 +60,8 @@ def initialize(url:, http_client: nil)
5360
@http = http_client || Http::Default.new
5461
@http.server_url = uri
5562
@file_detector = nil
63+
64+
@locator_converter = self.class.locator_converter
5665
end
5766

5867
#
@@ -516,7 +525,7 @@ def active_element
516525
alias switch_to_active_element active_element
517526

518527
def find_element_by(how, what, parent_ref = [])
519-
how, what = convert_locator(how, what)
528+
how, what = @locator_converter.convert(how, what)
520529

521530
return execute_atom(:findElements, Support::RelativeLocator.new(what).as_json).first if how == 'relative'
522531

@@ -534,7 +543,7 @@ def find_element_by(how, what, parent_ref = [])
534543
end
535544

536545
def find_elements_by(how, what, parent_ref = [])
537-
how, what = convert_locator(how, what)
546+
how, what = @locator_converter.convert(how, what)
538547

539548
return execute_atom :findElements, Support::RelativeLocator.new(what).as_json if how == 'relative'
540549

@@ -655,42 +664,6 @@ def prepare_capabilities_payload(capabilities)
655664
{capabilities: capabilities}
656665
end
657666

658-
def convert_locator(how, what)
659-
how = SearchContext::FINDERS[how.to_sym] || how
660-
661-
case how
662-
when 'class name'
663-
how = 'css selector'
664-
what = ".#{escape_css(what.to_s)}"
665-
when 'id'
666-
how = 'css selector'
667-
what = "##{escape_css(what.to_s)}"
668-
when 'name'
669-
how = 'css selector'
670-
what = "*[name='#{escape_css(what.to_s)}']"
671-
end
672-
673-
if what.is_a?(Hash)
674-
what = what.each_with_object({}) do |(h, w), hash|
675-
h, w = convert_locator(h.to_s, w)
676-
hash[h] = w
677-
end
678-
end
679-
680-
[how, what]
681-
end
682-
683-
ESCAPE_CSS_REGEXP = /(['"\\#.:;,!?+<>=~*^$|%&@`{}\-\[\]()])/
684-
UNICODE_CODE_POINT = 30
685-
686-
# Escapes invalid characters in CSS selector.
687-
# @see https://mathiasbynens.be/notes/css-escapes
688-
def escape_css(string)
689-
string = string.gsub(ESCAPE_CSS_REGEXP) { |match| "\\#{match}" }
690-
string = "\\#{UNICODE_CODE_POINT + Integer(string[0])} #{string[1..]}" if string[0]&.match?(/[[:digit:]]/)
691-
692-
string
693-
end
694667
end # Bridge
695668
end # Remote
696669
end # WebDriver
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# frozen_string_literal: true
2+
3+
# Licensed to the Software Freedom Conservancy (SFC) under one
4+
# or more contributor license agreements. See the NOTICE file
5+
# distributed with this work for additional information
6+
# regarding copyright ownership. The SFC licenses this file
7+
# to you under the Apache License, Version 2.0 (the
8+
# "License"); you may not use this file except in compliance
9+
# with the License. You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing,
14+
# software distributed under the License is distributed on an
15+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
# KIND, either express or implied. See the License for the
17+
# specific language governing permissions and limitations
18+
# under the License.
19+
20+
module Selenium
21+
module WebDriver
22+
module Remote
23+
class Bridge
24+
class LocatorConverter
25+
ESCAPE_CSS_REGEXP = /(['"\\#.:;,!?+<>=~*^$|%&@`{}\-\[\]()])/
26+
UNICODE_CODE_POINT = 30
27+
28+
#
29+
# Converts a locator to a specification compatible one.
30+
# @param [String, Symbol] how
31+
# @param [String] what
32+
#
33+
34+
def convert(how, what)
35+
how = SearchContext.finders[how.to_sym] || how
36+
37+
case how
38+
when 'class name'
39+
how = 'css selector'
40+
what = ".#{escape_css(what.to_s)}"
41+
when 'id'
42+
how = 'css selector'
43+
what = "##{escape_css(what.to_s)}"
44+
when 'name'
45+
how = 'css selector'
46+
what = "*[name='#{escape_css(what.to_s)}']"
47+
end
48+
49+
if what.is_a?(Hash)
50+
what = what.each_with_object({}) do |(h, w), hash|
51+
h, w = convert(h.to_s, w)
52+
hash[h] = w
53+
end
54+
end
55+
56+
[how, what]
57+
end
58+
59+
private
60+
61+
#
62+
# Escapes invalid characters in CSS selector.
63+
# @see https://mathiasbynens.be/notes/css-escapes
64+
#
65+
66+
def escape_css(string)
67+
string = string.gsub(ESCAPE_CSS_REGEXP) { |match| "\\#{match}" }
68+
string = "\\#{UNICODE_CODE_POINT + Integer(string[0])} #{string[1..]}" if string[0]&.match?(/[[:digit:]]/)
69+
70+
string
71+
end
72+
end # LocatorConverter
73+
end # Bridge
74+
end # Remote
75+
end # WebDriver
76+
end # Selenium

0 commit comments

Comments
 (0)