selenium-webdriver 4.20.1 → 4.21.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c32a4152c7c47427939aba71e601004ee0cd8ad0c2454b00b879d0bd84f01a5e
4
- data.tar.gz: d7cf6a825f70df0366b290da8e7fbf36417bcabe055f1835eca9402b6c37c791
3
+ metadata.gz: 4f5e5266f21804cd7520e61a6036171a3bda203460d944ac61cfd324283f72ae
4
+ data.tar.gz: 8a58672a580239e27c909e781b16ff588f216f535dda5e495eff5123224e36b2
5
5
  SHA512:
6
- metadata.gz: 6f121cc65df3f7174419ab881de0f8283e2bbb22698aa7582f019b1a2e3f7aae04376d0be02507ffbc968fe95512c97fb121515a16a9ff62b845a23e939005f7
7
- data.tar.gz: f0b0dfba817532e5cd9ab1ab0874463823434dcdac0f15efe199d400a53539343cdcaafdecf766596dc36cbc52c6e0454784a44fc748c21e6dc380d279bb29e4
6
+ metadata.gz: 78473145c4ce27a7acf22c58e6c204f7beac53b47a3b18139bb895c08c5d1da90ec5ac558bab429f5284604bba717415be0d8606bb298a68278a22a57947a546
7
+ data.tar.gz: b1ae030703351b2b799881b041c9554e0c23c8cfc60a6e8eb32e7f679a7ea23f1c72c7cdbca203b2830439e2488a1ff56c6763df960f8eb873f08356f79ccf91
data/CHANGES CHANGED
@@ -1,3 +1,15 @@
1
+ 4.21.0 (2024-05-16)
2
+ =========================
3
+
4
+ * Add CDP for Chrome 125 and remove 122
5
+ * Initial extensibility points for Appium
6
+ * Support registering extra headers in HTTP client
7
+ * Support overriding User-Agent in HTTP client
8
+ * Support registering extra bridge commands
9
+ * Support overriding default locator conversion
10
+ * Support registering custom finders for SearchContext
11
+ * Support using custom element classes
12
+
1
13
  4.20.1 (2024-04-25)
2
14
  =========================
3
15
 
Binary file
Binary file
Binary file
@@ -183,6 +183,7 @@ module Selenium
183
183
  def initialize(jar, opts = {})
184
184
  raise Errno::ENOENT, jar unless File.exist?(jar)
185
185
 
186
+ @java = opts.fetch(:java, 'java')
186
187
  @jar = jar
187
188
  @host = '127.0.0.1'
188
189
  @role = opts.fetch(:role, 'standalone')
@@ -241,7 +242,7 @@ module Selenium
241
242
  # extract any additional_args that start with -D as options
242
243
  properties = @additional_args.dup - @additional_args.delete_if { |arg| arg[/^-D/] }
243
244
  args = ['-jar', @jar, @role, '--port', @port.to_s]
244
- server_command = ['java'] + properties + args + @additional_args
245
+ server_command = [@java] + properties + args + @additional_args
245
246
  cp = WebDriver::ChildProcess.build(*server_command)
246
247
 
247
248
  if @log.is_a?(String)
@@ -35,6 +35,14 @@ module Selenium
35
35
  xpath: 'xpath'
36
36
  }.freeze
37
37
 
38
+ class << self
39
+ attr_accessor :extra_finders
40
+
41
+ def finders
42
+ FINDERS.merge(extra_finders || {})
43
+ end
44
+ end
45
+
38
46
  #
39
47
  # Find the first element matching the given arguments
40
48
  #
@@ -57,7 +65,7 @@ module Selenium
57
65
  def find_element(*args)
58
66
  how, what = extract_args(args)
59
67
 
60
- by = FINDERS[how.to_sym]
68
+ by = SearchContext.finders[how.to_sym]
61
69
  raise ArgumentError, "cannot find element by #{how.inspect}" unless by
62
70
 
63
71
  bridge.find_element_by by, what, ref
@@ -72,7 +80,7 @@ module Selenium
72
80
  def find_elements(*args)
73
81
  how, what = extract_args(args)
74
82
 
75
- by = FINDERS[how.to_sym]
83
+ by = SearchContext.finders[how.to_sym]
76
84
  raise ArgumentError, "cannot find elements by #{how.inspect}" unless by
77
85
 
78
86
  bridge.find_elements_by by, what, ref
@@ -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
@@ -22,6 +22,8 @@ module Selenium
22
22
  module Remote
23
23
  class Bridge
24
24
  autoload :COMMANDS, 'selenium/webdriver/remote/bridge/commands'
25
+ autoload :LocatorConverter, 'selenium/webdriver/remote/bridge/locator_converter'
26
+
25
27
  include Atoms
26
28
 
27
29
  PORT = 4444
@@ -29,6 +31,25 @@ module Selenium
29
31
  attr_accessor :http, :file_detector
30
32
  attr_reader :capabilities
31
33
 
34
+ class << self
35
+ attr_reader :extra_commands
36
+ attr_writer :element_class, :locator_converter
37
+
38
+ def add_command(name, verb, url, &block)
39
+ @extra_commands ||= {}
40
+ @extra_commands[name] = [verb, url]
41
+ define_method(name, &block)
42
+ end
43
+
44
+ def locator_converter
45
+ @locator_converter ||= LocatorConverter.new
46
+ end
47
+
48
+ def element_class
49
+ @element_class ||= Element
50
+ end
51
+ end
52
+
32
53
  #
33
54
  # Initializes the bridge with the given server URL
34
55
  # @param [String, URI] url url for the remote server
@@ -43,6 +64,8 @@ module Selenium
43
64
  @http = http_client || Http::Default.new
44
65
  @http.server_url = uri
45
66
  @file_detector = nil
67
+
68
+ @locator_converter = self.class.locator_converter
46
69
  end
47
70
 
48
71
  #
@@ -413,7 +436,7 @@ module Selenium
413
436
  "e.initEvent('submit', true, true);\n" \
414
437
  "if (form.dispatchEvent(e)) { HTMLFormElement.prototype.submit.call(form) }\n"
415
438
 
416
- execute_script(script, Element::ELEMENT_KEY => element)
439
+ execute_script(script, Bridge.element_class::ELEMENT_KEY => element)
417
440
  rescue Error::JavascriptError
418
441
  raise Error::UnsupportedOperationError, 'To submit an element, it must be nested inside a form element'
419
442
  end
@@ -500,13 +523,13 @@ module Selenium
500
523
  #
501
524
 
502
525
  def active_element
503
- Element.new self, element_id_from(execute(:get_active_element))
526
+ Bridge.element_class.new self, element_id_from(execute(:get_active_element))
504
527
  end
505
528
 
506
529
  alias switch_to_active_element active_element
507
530
 
508
531
  def find_element_by(how, what, parent_ref = [])
509
- how, what = convert_locator(how, what)
532
+ how, what = @locator_converter.convert(how, what)
510
533
 
511
534
  return execute_atom(:findElements, Support::RelativeLocator.new(what).as_json).first if how == 'relative'
512
535
 
@@ -520,11 +543,11 @@ module Selenium
520
543
  execute :find_element, {}, {using: how, value: what.to_s}
521
544
  end
522
545
 
523
- Element.new self, element_id_from(id)
546
+ Bridge.element_class.new self, element_id_from(id)
524
547
  end
525
548
 
526
549
  def find_elements_by(how, what, parent_ref = [])
527
- how, what = convert_locator(how, what)
550
+ how, what = @locator_converter.convert(how, what)
528
551
 
529
552
  return execute_atom :findElements, Support::RelativeLocator.new(what).as_json if how == 'relative'
530
553
 
@@ -538,7 +561,7 @@ module Selenium
538
561
  execute :find_elements, {}, {using: how, value: what.to_s}
539
562
  end
540
563
 
541
- ids.map { |id| Element.new self, element_id_from(id) }
564
+ ids.map { |id| Bridge.element_class.new self, element_id_from(id) }
542
565
  end
543
566
 
544
567
  def shadow_root(element)
@@ -612,7 +635,7 @@ module Selenium
612
635
  end
613
636
 
614
637
  def commands(command)
615
- command_list[command]
638
+ command_list[command] || Bridge.extra_commands[command]
616
639
  end
617
640
 
618
641
  def unwrap_script_result(arg)
@@ -621,7 +644,7 @@ module Selenium
621
644
  arg.map { |e| unwrap_script_result(e) }
622
645
  when Hash
623
646
  element_id = element_id_from(arg)
624
- return Element.new(self, element_id) if element_id
647
+ return Bridge.element_class.new(self, element_id) if element_id
625
648
 
626
649
  shadow_root_id = shadow_root_id_from(arg)
627
650
  return ShadowRoot.new self, shadow_root_id if shadow_root_id
@@ -633,7 +656,7 @@ module Selenium
633
656
  end
634
657
 
635
658
  def element_id_from(id)
636
- id['ELEMENT'] || id[Element::ELEMENT_KEY]
659
+ id['ELEMENT'] || id[Bridge.element_class::ELEMENT_KEY]
637
660
  end
638
661
 
639
662
  def shadow_root_id_from(id)
@@ -644,43 +667,6 @@ module Selenium
644
667
  capabilities = {alwaysMatch: capabilities} if !capabilities['alwaysMatch'] && !capabilities['firstMatch']
645
668
  {capabilities: capabilities}
646
669
  end
647
-
648
- def convert_locator(how, what)
649
- how = SearchContext::FINDERS[how.to_sym] || how
650
-
651
- case how
652
- when 'class name'
653
- how = 'css selector'
654
- what = ".#{escape_css(what.to_s)}"
655
- when 'id'
656
- how = 'css selector'
657
- what = "##{escape_css(what.to_s)}"
658
- when 'name'
659
- how = 'css selector'
660
- what = "*[name='#{escape_css(what.to_s)}']"
661
- end
662
-
663
- if what.is_a?(Hash)
664
- what = what.each_with_object({}) do |(h, w), hash|
665
- h, w = convert_locator(h.to_s, w)
666
- hash[h] = w
667
- end
668
- end
669
-
670
- [how, what]
671
- end
672
-
673
- ESCAPE_CSS_REGEXP = /(['"\\#.:;,!?+<>=~*^$|%&@`{}\-\[\]()])/
674
- UNICODE_CODE_POINT = 30
675
-
676
- # Escapes invalid characters in CSS selector.
677
- # @see https://mathiasbynens.be/notes/css-escapes
678
- def escape_css(string)
679
- string = string.gsub(ESCAPE_CSS_REGEXP) { |match| "\\#{match}" }
680
- string = "\\#{UNICODE_CODE_POINT + Integer(string[0])} #{string[1..]}" if string[0]&.match?(/[[:digit:]]/)
681
-
682
- string
683
- end
684
670
  end # Bridge
685
671
  end # Remote
686
672
  end # WebDriver
@@ -26,10 +26,18 @@ module Selenium
26
26
  CONTENT_TYPE = 'application/json'
27
27
  DEFAULT_HEADERS = {
28
28
  'Accept' => CONTENT_TYPE,
29
- 'Content-Type' => "#{CONTENT_TYPE}; charset=UTF-8",
30
- 'User-Agent' => "selenium/#{WebDriver::VERSION} (ruby #{Platform.os})"
29
+ 'Content-Type' => "#{CONTENT_TYPE}; charset=UTF-8"
31
30
  }.freeze
32
31
 
32
+ class << self
33
+ attr_accessor :extra_headers
34
+ attr_writer :user_agent
35
+
36
+ def user_agent
37
+ @user_agent ||= "selenium/#{WebDriver::VERSION} (ruby #{Platform.os})"
38
+ end
39
+ end
40
+
33
41
  attr_writer :server_url
34
42
 
35
43
  def quit_errors
@@ -42,7 +50,7 @@ module Selenium
42
50
 
43
51
  def call(verb, url, command_hash)
44
52
  url = server_url.merge(url) unless url.is_a?(URI)
45
- headers = DEFAULT_HEADERS.dup
53
+ headers = common_headers.dup
46
54
  headers['Cache-Control'] = 'no-cache' if verb == :get
47
55
 
48
56
  if command_hash
@@ -61,6 +69,16 @@ module Selenium
61
69
 
62
70
  private
63
71
 
72
+ def common_headers
73
+ @common_headers ||= begin
74
+ headers = DEFAULT_HEADERS.dup
75
+ headers['User-Agent'] = Common.user_agent
76
+ headers = headers.merge(Common.extra_headers || {})
77
+
78
+ headers
79
+ end
80
+ end
81
+
64
82
  def server_url
65
83
  return @server_url if @server_url
66
84
 
@@ -19,6 +19,6 @@
19
19
 
20
20
  module Selenium
21
21
  module WebDriver
22
- VERSION = '4.20.1'
22
+ VERSION = '4.21.0'
23
23
  end # WebDriver
24
24
  end # Selenium
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: selenium-webdriver
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.20.1
4
+ version: 4.21.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Rodionov
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2024-04-26 00:00:00.000000000 Z
13
+ date: 2024-05-16 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: base64
@@ -395,6 +395,7 @@ files:
395
395
  - lib/selenium/webdriver/remote.rb
396
396
  - lib/selenium/webdriver/remote/bridge.rb
397
397
  - lib/selenium/webdriver/remote/bridge/commands.rb
398
+ - lib/selenium/webdriver/remote/bridge/locator_converter.rb
398
399
  - lib/selenium/webdriver/remote/capabilities.rb
399
400
  - lib/selenium/webdriver/remote/driver.rb
400
401
  - lib/selenium/webdriver/remote/features.rb