Skip to content

Commit db0f239

Browse files
committed
[rb] Support overriding default locator conversion
1 parent 79ea75e commit db0f239

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_accessor :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
#
@@ -533,7 +542,7 @@ def active_element
533542
alias switch_to_active_element active_element
534543

535544
def find_element_by(how, what, parent_ref = [])
536-
how, what = convert_locator(how, what)
545+
how, what = @locator_converter.convert(how, what)
537546

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

@@ -551,7 +560,7 @@ def find_element_by(how, what, parent_ref = [])
551560
end
552561

553562
def find_elements_by(how, what, parent_ref = [])
554-
how, what = convert_locator(how, what)
563+
how, what = @locator_converter.convert(how, what)
555564

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

@@ -668,42 +677,6 @@ def prepare_capabilities_payload(capabilities)
668677
{capabilities: capabilities}
669678
end
670679

671-
def convert_locator(how, what)
672-
how = SearchContext::FINDERS[how.to_sym] || how
673-
674-
case how
675-
when 'class name'
676-
how = 'css selector'
677-
what = ".#{escape_css(what.to_s)}"
678-
when 'id'
679-
how = 'css selector'
680-
what = "##{escape_css(what.to_s)}"
681-
when 'name'
682-
how = 'css selector'
683-
what = "*[name='#{escape_css(what.to_s)}']"
684-
end
685-
686-
if what.is_a?(Hash)
687-
what = what.each_with_object({}) do |(h, w), hash|
688-
h, w = convert_locator(h.to_s, w)
689-
hash[h] = w
690-
end
691-
end
692-
693-
[how, what]
694-
end
695-
696-
ESCAPE_CSS_REGEXP = /(['"\\#.:;,!?+<>=~*^$|%&@`{}\-\[\]()])/
697-
UNICODE_CODE_POINT = 30
698-
699-
# Escapes invalid characters in CSS selector.
700-
# @see https://mathiasbynens.be/notes/css-escapes
701-
def escape_css(string)
702-
string = string.gsub(ESCAPE_CSS_REGEXP) { |match| "\\#{match}" }
703-
string = "\\#{UNICODE_CODE_POINT + Integer(string[0])} #{string[1..]}" if string[0]&.match?(/[[:digit:]]/)
704-
705-
string
706-
end
707680
end # Bridge
708681
end # Remote
709682
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)