selenium-webdriver 4.1.0 → 4.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +70 -1
  3. data/LICENSE +1 -1
  4. data/NOTICE +1 -1
  5. data/lib/selenium/server.rb +14 -9
  6. data/lib/selenium/webdriver/bidi/session.rb +38 -0
  7. data/lib/selenium/webdriver/bidi.rb +55 -0
  8. data/lib/selenium/webdriver/chrome/features.rb +5 -0
  9. data/lib/selenium/webdriver/chrome/options.rb +19 -19
  10. data/lib/selenium/webdriver/chrome.rb +0 -14
  11. data/lib/selenium/webdriver/common/action_builder.rb +108 -21
  12. data/lib/selenium/webdriver/common/driver.rb +13 -55
  13. data/lib/selenium/webdriver/common/driver_extensions/{has_remote_status.rb → has_bidi.rb} +12 -5
  14. data/lib/selenium/webdriver/common/driver_extensions/has_casting.rb +10 -0
  15. data/lib/selenium/webdriver/common/driver_extensions/has_context.rb +1 -2
  16. data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +1 -1
  17. data/lib/selenium/webdriver/common/driver_extensions/has_pinned_scripts.rb +1 -1
  18. data/lib/selenium/webdriver/common/element.rb +1 -1
  19. data/lib/selenium/webdriver/common/error.rb +1 -1
  20. data/lib/selenium/webdriver/common/interactions/input_device.rb +10 -4
  21. data/lib/selenium/webdriver/common/interactions/interaction.rb +12 -25
  22. data/lib/selenium/webdriver/common/interactions/interactions.rb +24 -4
  23. data/lib/selenium/webdriver/common/interactions/key_actions.rb +5 -1
  24. data/lib/selenium/webdriver/common/interactions/key_input.rb +11 -27
  25. data/lib/selenium/webdriver/common/interactions/none_input.rb +10 -8
  26. data/lib/selenium/webdriver/common/interactions/pause.rb +49 -0
  27. data/lib/selenium/webdriver/common/interactions/pointer_actions.rb +56 -66
  28. data/lib/selenium/webdriver/common/interactions/pointer_cancel.rb +45 -0
  29. data/lib/selenium/webdriver/common/interactions/pointer_event_properties.rb +63 -0
  30. data/lib/selenium/webdriver/common/interactions/pointer_input.rb +15 -84
  31. data/lib/selenium/webdriver/common/interactions/pointer_move.rb +60 -0
  32. data/lib/selenium/webdriver/common/interactions/pointer_press.rb +85 -0
  33. data/lib/selenium/webdriver/common/interactions/scroll.rb +57 -0
  34. data/lib/selenium/webdriver/common/interactions/scroll_origin.rb +48 -0
  35. data/lib/selenium/webdriver/common/interactions/typing_interaction.rb +54 -0
  36. data/lib/selenium/webdriver/common/interactions/wheel_actions.rb +113 -0
  37. data/lib/selenium/webdriver/common/interactions/wheel_input.rb +42 -0
  38. data/lib/selenium/webdriver/common/keys.rb +1 -0
  39. data/lib/selenium/webdriver/common/manager.rb +0 -27
  40. data/lib/selenium/webdriver/common/options.rb +2 -9
  41. data/lib/selenium/webdriver/common/platform.rb +4 -4
  42. data/lib/selenium/webdriver/common/search_context.rb +0 -6
  43. data/lib/selenium/webdriver/common/service_manager.rb +2 -3
  44. data/lib/selenium/webdriver/common/shadow_root.rb +1 -1
  45. data/lib/selenium/webdriver/common/socket_poller.rb +1 -1
  46. data/lib/selenium/webdriver/common/websocket_connection.rb +149 -0
  47. data/lib/selenium/webdriver/common.rb +14 -3
  48. data/lib/selenium/webdriver/devtools/request.rb +1 -1
  49. data/lib/selenium/webdriver/devtools/response.rb +1 -1
  50. data/lib/selenium/webdriver/devtools.rb +5 -112
  51. data/lib/selenium/webdriver/edge/features.rb +1 -0
  52. data/lib/selenium/webdriver/firefox/driver.rb +1 -0
  53. data/lib/selenium/webdriver/firefox/features.rb +2 -5
  54. data/lib/selenium/webdriver/firefox/options.rb +3 -1
  55. data/lib/selenium/webdriver/firefox/profile.rb +1 -5
  56. data/lib/selenium/webdriver/firefox/util.rb +46 -0
  57. data/lib/selenium/webdriver/firefox.rb +1 -14
  58. data/lib/selenium/webdriver/ie.rb +0 -14
  59. data/lib/selenium/webdriver/remote/bridge.rb +21 -19
  60. data/lib/selenium/webdriver/remote/commands.rb +0 -5
  61. data/lib/selenium/webdriver/remote/driver.rb +0 -1
  62. data/lib/selenium/webdriver/remote/http/default.rb +6 -12
  63. data/lib/selenium/webdriver/remote/response.rb +2 -2
  64. data/lib/selenium/webdriver/safari.rb +0 -14
  65. data/lib/selenium/webdriver/support/cdp_client_generator.rb +4 -4
  66. data/lib/selenium/webdriver/support/color.rb +7 -7
  67. data/lib/selenium/webdriver/support/guards/guard_condition.rb +1 -1
  68. data/lib/selenium/webdriver/support/guards.rb +1 -1
  69. data/lib/selenium/webdriver/version.rb +1 -1
  70. data/lib/selenium/webdriver.rb +1 -0
  71. data/selenium-webdriver.gemspec +7 -4
  72. metadata +56 -8
  73. data/lib/selenium/webdriver/remote/http/persistent.rb +0 -65
@@ -20,12 +20,19 @@
20
20
  module Selenium
21
21
  module WebDriver
22
22
  module DriverExtensions
23
- module HasRemoteStatus
24
- def remote_status
25
- WebDriver.logger.deprecate('#remote_status', '#status')
26
- @bridge.status
23
+ module HasBiDi
24
+
25
+ #
26
+ # Retrieves WebDriver BiDi connection.
27
+ #
28
+ # @return [BiDi]
29
+ #
30
+
31
+ def bidi
32
+ @bidi ||= Selenium::WebDriver::BiDi.new(url: capabilities[:web_socket_url])
27
33
  end
28
- end # HasRemoteStatus
34
+
35
+ end # HasBiDi
29
36
  end # DriverExtensions
30
37
  end # WebDriver
31
38
  end # Selenium
@@ -52,6 +52,16 @@ module Selenium
52
52
  @bridge.start_cast_tab_mirroring(name)
53
53
  end
54
54
 
55
+ #
56
+ # Starts a tab mirroring session on a specific receiver target.
57
+ #
58
+ # @param [String] name the sink to use as the target
59
+ #
60
+
61
+ def start_cast_desktop_mirroring(name)
62
+ @bridge.start_cast_desktop_mirroring(name)
63
+ end
64
+
55
65
  #
56
66
  # Gets error messages when there is any issue in a Cast session.
57
67
  #
@@ -27,8 +27,7 @@ module Selenium
27
27
  # a `with` statement. The state of the context on the server is
28
28
  # saved before entering the block, and restored upon exiting it.
29
29
  #
30
- # @param [String] name which permission to set
31
- # @param [String] value what to set the permission to
30
+ # @param [String] value which context gets set (either 'chrome' or 'content')
32
31
  #
33
32
 
34
33
  def context=(value)
@@ -114,7 +114,7 @@ module Selenium
114
114
  execute_script(mutation_listener)
115
115
  devtools.page.add_script_to_evaluate_on_new_document(source: mutation_listener)
116
116
 
117
- devtools.runtime.on(:binding_called, &method(:log_mutation_event))
117
+ devtools.runtime.on(:binding_called) { |event| log_mutation_event(event) }
118
118
  end
119
119
 
120
120
  def log_mutation_event(params)
@@ -62,7 +62,7 @@ module Selenium
62
62
  #
63
63
  # Unpins script making it undefined for the subsequent calls.
64
64
  #
65
- # @param [DevTools::PinnedScript]
65
+ # @param [DevTools::PinnedScript] script
66
66
  #
67
67
 
68
68
  def unpin_script(script)
@@ -46,7 +46,7 @@ module Selenium
46
46
  alias_method :eql?, :==
47
47
 
48
48
  def hash
49
- @id.hash ^ @bridge.hash
49
+ [@id, @bridge].hash
50
50
  end
51
51
 
52
52
  #
@@ -29,7 +29,7 @@ module Selenium
29
29
  def self.for_error(error)
30
30
  return if error.nil?
31
31
 
32
- klass_name = error.split(' ').map(&:capitalize).join.sub(/Error$/, '')
32
+ klass_name = error.split.map(&:capitalize).join.sub(/Error$/, '')
33
33
  const_get("#{klass_name}Error", false)
34
34
  rescue NameError
35
35
  WebDriverError
@@ -22,8 +22,15 @@ require 'securerandom'
22
22
  module Selenium
23
23
  module WebDriver
24
24
  module Interactions
25
+ #
26
+ # Superclass for the input device sources
27
+ # Manages Array of Interaction instances for the device
28
+ #
29
+ # @api private
30
+ #
31
+
25
32
  class InputDevice
26
- attr_reader :name, :actions
33
+ attr_reader :name, :actions, :type
27
34
 
28
35
  def initialize(name = nil)
29
36
  @name = name || SecureRandom.uuid
@@ -44,9 +51,8 @@ module Selenium
44
51
  add_action(Pause.new(self, duration))
45
52
  end
46
53
 
47
- def no_actions? # Determine if only pauses are present
48
- actions = @actions.reject { |action| action.type == Interaction::PAUSE }
49
- actions.empty?
54
+ def encode
55
+ {type: type, id: name, actions: @actions.map(&:encode)} unless @actions.empty?
50
56
  end
51
57
  end # InputDevice
52
58
  end # Interactions
@@ -20,37 +20,24 @@
20
20
  module Selenium
21
21
  module WebDriver
22
22
  module Interactions
23
- class Interaction
24
- PAUSE = :pause
23
+ #
24
+ # Superclass for classes defining actions
25
+ # Do not initialize directly, only use subclass
26
+ #
27
+ # @api private
28
+ #
25
29
 
26
- attr_reader :source
30
+ class Interaction
31
+ attr_reader :type
27
32
 
28
33
  def initialize(source)
29
- unless Interactions::SOURCE_TYPES.include? source.type
30
- raise TypeError,
31
- "#{source.type} is not a valid input type"
32
- end
33
-
34
- @source = source
34
+ assert_source(source)
35
35
  end
36
- end
37
36
 
38
- class Pause < Interaction
39
- def initialize(source, duration = nil)
40
- super(source)
41
- @duration = duration
37
+ def assert_source(_source)
38
+ raise NotImplementedError, 'subclass responsibility'
42
39
  end
43
-
44
- def type
45
- PAUSE
46
- end
47
-
48
- def encode
49
- output = {type: type}
50
- output[:duration] = (@duration * 1000).to_i if @duration
51
- output
52
- end
53
- end # Interaction
40
+ end
54
41
  end # Interactions
55
42
  end # WebDriver
56
43
  end # Selenium
@@ -23,20 +23,40 @@ module Selenium
23
23
  KEY = :key
24
24
  POINTER = :pointer
25
25
  NONE = :none
26
- SOURCE_TYPES = [KEY, POINTER, NONE].freeze
26
+ WHEEL = :wheel
27
+
28
+ #
29
+ # Class methods for initializing known Input devices
30
+ #
27
31
 
28
32
  class << self
29
- def key(name)
33
+ def key(name = nil)
30
34
  KeyInput.new(name)
31
35
  end
32
36
 
33
- def pointer(kind, **kwargs)
34
- PointerInput.new(kind, **kwargs)
37
+ def pointer(kind = :mouse, name: nil)
38
+ PointerInput.new(kind, name: name)
39
+ end
40
+
41
+ def mouse(name: nil)
42
+ pointer(name: name)
43
+ end
44
+
45
+ def pen(name: nil)
46
+ pointer(:pen, name: name)
47
+ end
48
+
49
+ def touch(name: nil)
50
+ pointer(:touch, name: name)
35
51
  end
36
52
 
37
53
  def none(name = nil)
38
54
  NoneInput.new(name)
39
55
  end
56
+
57
+ def wheel(name = nil)
58
+ WheelInput.new(name)
59
+ end
40
60
  end
41
61
  end # Interactions
42
62
  end # WebDriver
@@ -134,12 +134,16 @@ module Selenium
134
134
  #
135
135
 
136
136
  def key_action(*args, action: nil, device: nil)
137
- key_input = get_device(device) || key_inputs.first
137
+ key_input = key_input(device)
138
138
  click(args.shift) if args.first.is_a? Element
139
139
  key_input.send(action, args.last)
140
140
  tick(key_input)
141
141
  self
142
142
  end
143
+
144
+ def key_input(name = nil)
145
+ device(name: name, type: Interactions::KEY) || add_key_input('keyboard')
146
+ end
143
147
  end # KeyActions
144
148
  end # WebDriver
145
149
  end # Selenium
@@ -20,17 +20,18 @@
20
20
  module Selenium
21
21
  module WebDriver
22
22
  module Interactions
23
+ #
24
+ # Creates actions specific to Key Input devices
25
+ #
26
+ # @api private
27
+ #
28
+
23
29
  class KeyInput < InputDevice
24
30
  SUBTYPES = {down: :keyDown, up: :keyUp, pause: :pause}.freeze
25
31
 
26
- def type
27
- Interactions::KEY
28
- end
29
-
30
- def encode
31
- return nil if no_actions?
32
-
33
- {type: type, id: name, actions: @actions.map(&:encode)}
32
+ def initialize(name = nil)
33
+ super
34
+ @type = Interactions::KEY
34
35
  end
35
36
 
36
37
  def create_key_down(key)
@@ -41,25 +42,8 @@ module Selenium
41
42
  add_action(TypingInteraction.new(self, :up, key))
42
43
  end
43
44
 
44
- class TypingInteraction < Interaction
45
- attr_reader :type
46
-
47
- def initialize(source, type, key)
48
- super(source)
49
- @type = assert_type(type)
50
- @key = Keys.encode_key(key)
51
- end
52
-
53
- def assert_type(type)
54
- raise TypeError, "#{type.inspect} is not a valid key subtype" unless KeyInput::SUBTYPES.key? type
55
-
56
- KeyInput::SUBTYPES[type]
57
- end
58
-
59
- def encode
60
- {type: @type, value: @key}
61
- end
62
- end # TypingInteraction
45
+ # Backward compatibility in case anyone called this directly
46
+ class TypingInteraction < Interactions::TypingInteraction; end
63
47
  end # KeyInput
64
48
  end # Interactions
65
49
  end # WebDriver
@@ -20,15 +20,17 @@
20
20
  module Selenium
21
21
  module WebDriver
22
22
  module Interactions
23
- class NoneInput < InputDevice
24
- def type
25
- Interactions::NONE
26
- end
23
+ #
24
+ # Creates actions specific to null input source
25
+ # This is primarily used for adding pauses
26
+ #
27
+ # @api private
28
+ #
27
29
 
28
- def encode
29
- return nil if no_actions?
30
-
31
- {type: type, id: name, actions: @actions.map(&:encode)}
30
+ class NoneInput < InputDevice
31
+ def initialize(name = nil)
32
+ super
33
+ @type = Interactions::NONE
32
34
  end
33
35
  end # NoneInput
34
36
  end # Interactions
@@ -0,0 +1,49 @@
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 Interactions
23
+ #
24
+ # Action to create a waiting period between actions
25
+ # Also used for synchronizing actions across devices
26
+ #
27
+ # @api private
28
+ #
29
+
30
+ class Pause < Interaction
31
+ def initialize(source, duration = nil)
32
+ super(source)
33
+ @duration = duration
34
+ @type = :pause
35
+ end
36
+
37
+ def assert_source(source)
38
+ raise TypeError, "#{source.type} is not a valid input type" unless source.is_a? InputDevice
39
+ end
40
+
41
+ def encode
42
+ output = {type: type}
43
+ output[:duration] = (@duration * 1000).to_i if @duration
44
+ output
45
+ end
46
+ end # Pause
47
+ end # Interactions
48
+ end # WebDriver
49
+ end # Selenium
@@ -23,11 +23,12 @@ module Selenium
23
23
  attr_writer :default_move_duration
24
24
 
25
25
  #
26
- # The overridable duration for movement used by methods in this module
26
+ # By default this is set to 250ms in the ActionBuilder constructor
27
+ # It can be overridden with default_move_duration=
27
28
  #
28
29
 
29
30
  def default_move_duration
30
- @default_move_duration ||= 0.25 # 250 milliseconds
31
+ @default_move_duration ||= @duration / 1000.0 # convert ms to seconds
31
32
  end
32
33
 
33
34
  #
@@ -45,8 +46,8 @@ module Selenium
45
46
  # @return [ActionBuilder] A self reference.
46
47
  #
47
48
 
48
- def pointer_down(button, device: nil)
49
- button_action(button, action: :create_pointer_down, device: device)
49
+ def pointer_down(button = :left, device: nil, **opts)
50
+ button_action(button, :create_pointer_down, device: device, **opts)
50
51
  end
51
52
 
52
53
  #
@@ -62,21 +63,21 @@ module Selenium
62
63
  # @return [ActionBuilder] A self reference.
63
64
  #
64
65
 
65
- def pointer_up(button, device: nil)
66
- button_action(button, action: :create_pointer_up, device: device)
66
+ def pointer_up(button = :left, device: nil, **opts)
67
+ button_action(button, :create_pointer_up, device: device, **opts)
67
68
  end
68
69
 
69
70
  #
70
- # Moves the mouse to the middle of the given element. The element is scrolled into
71
- # view and its location is calculated using getBoundingClientRect. Then the
72
- # mouse is moved to optional offset coordinates from the element.
71
+ # Moves the pointer to the middle of the given element.
72
+ # its location is calculated using getBoundingClientRect.
73
+ # Then the pointer is moved to optional offset coordinates from the element.
73
74
  #
74
- # This is adapted to be backward compatible from non- actions. calculates offset from the center point
75
- # of the element
75
+ # The element is not scrolled into view.
76
+ # MoveTargetOutOfBoundsError will be raised if element with offset is outside the viewport
76
77
  #
77
- # Note that when using offsets, both coordinates need to be passed.
78
+ # When using offsets, both coordinates need to be passed.
78
79
  #
79
- # @example Scroll element into view and move the mouse to it
80
+ # @example Move the pointer to element
80
81
  #
81
82
  # el = driver.find_element(id: "some_id")
82
83
  # driver.action.move_to(el).perform
@@ -95,61 +96,52 @@ module Selenium
95
96
  # @return [ActionBuilder] A self reference.
96
97
  #
97
98
 
98
- def move_to(element, right_by = nil, down_by = nil, device: nil)
99
- pointer = get_pointer(device)
100
- # New actions offset is from center of element
101
- if right_by || down_by
102
- size = element.size
103
- left_offset = (size[:width] / 2).to_i
104
- top_offset = (size[:height] / 2).to_i
105
- left = -left_offset + (right_by || 0)
106
- top = -top_offset + (down_by || 0)
107
- else
108
- left = 0
109
- top = 0
110
- end
111
- pointer.create_pointer_move(duration: default_move_duration,
112
- x: left,
113
- y: top,
114
- element: element)
99
+ def move_to(element, right_by = nil, down_by = nil, device: nil, duration: default_move_duration, **opts)
100
+ pointer = pointer_input(device)
101
+ pointer.create_pointer_move(duration: duration,
102
+ x: right_by || 0,
103
+ y: down_by || 0,
104
+ origin: element,
105
+ **opts)
115
106
  tick(pointer)
116
107
  self
117
108
  end
118
109
 
119
110
  #
120
- # Moves the mouse from its current position by the given offset.
121
- # If the coordinates provided are outside the viewport (the mouse will
122
- # end up outside the browser window) then the viewport is scrolled to
123
- # match.
111
+ # Moves the pointer from its current position by the given offset.
124
112
  #
125
- # @example Move the mouse to a certain offset from its current position
113
+ # The viewport is not scrolled if the coordinates provided are outside the viewport.
114
+ # MoveTargetOutOfBoundsError will be raised if the offsets are outside the viewport
115
+ #
116
+ # @example Move the pointer to a certain offset from its current position
126
117
  #
127
118
  # driver.action.move_by(100, 100).perform
128
119
  #
129
- # @param [Integer] right_by horizontal offset. A negative value means moving the mouse left.
130
- # @param [Integer] down_by vertical offset. A negative value means moving the mouse up.
120
+ # @param [Integer] right_by horizontal offset. A negative value means moving the pointer left.
121
+ # @param [Integer] down_by vertical offset. A negative value means moving the pointer up.
131
122
  # @param [Symbol || String] device optional name of the PointerInput device to move
132
123
  # @return [ActionBuilder] A self reference.
133
124
  # @raise [MoveTargetOutOfBoundsError] if the provided offset is outside the document's boundaries.
134
125
  #
135
126
 
136
- def move_by(right_by, down_by, device: nil)
137
- pointer = get_pointer(device)
138
- pointer.create_pointer_move(duration: default_move_duration,
127
+ def move_by(right_by, down_by, device: nil, duration: default_move_duration, **opts)
128
+ pointer = pointer_input(device)
129
+ pointer.create_pointer_move(duration: duration,
139
130
  x: Integer(right_by),
140
131
  y: Integer(down_by),
141
- origin: Interactions::PointerMove::POINTER)
132
+ origin: Interactions::PointerMove::POINTER,
133
+ **opts)
142
134
  tick(pointer)
143
135
  self
144
136
  end
145
137
 
146
138
  #
147
- # Moves the mouse to a given location in the viewport.
148
- # If the coordinates provided are outside the viewport (the mouse will
149
- # end up outside the browser window) then the viewport is scrolled to
150
- # match.
139
+ # Moves the pointer to a given location in the viewport.
140
+ #
141
+ # The viewport is not scrolled if the coordinates provided are outside the viewport.
142
+ # MoveTargetOutOfBoundsError will be raised if the offsets are outside the viewport
151
143
  #
152
- # @example Move the mouse to a certain position in the viewport
144
+ # @example Move the pointer to a certain position in the viewport
153
145
  #
154
146
  # driver.action.move_to_location(100, 100).perform
155
147
  #
@@ -160,12 +152,13 @@ module Selenium
160
152
  # @raise [MoveTargetOutOfBoundsError] if the provided x or y value is outside the document's boundaries.
161
153
  #
162
154
 
163
- def move_to_location(x, y, device: nil)
164
- pointer = get_pointer(device)
165
- pointer.create_pointer_move(duration: default_move_duration,
155
+ def move_to_location(x, y, device: nil, duration: default_move_duration, **opts)
156
+ pointer = pointer_input(device)
157
+ pointer.create_pointer_move(duration: duration,
166
158
  x: Integer(x),
167
159
  y: Integer(y),
168
- origin: Interactions::PointerMove::VIEWPORT)
160
+ origin: Interactions::PointerMove::VIEWPORT,
161
+ **opts)
169
162
  tick(pointer)
170
163
  self
171
164
  end
@@ -186,9 +179,9 @@ module Selenium
186
179
  # @return [ActionBuilder] A self reference.
187
180
  #
188
181
 
189
- def click_and_hold(element = nil, device: nil)
182
+ def click_and_hold(element = nil, button: nil, device: nil)
190
183
  move_to(element, device: device) if element
191
- pointer_down(:left, device: device)
184
+ pointer_down(button || :left, device: device)
192
185
  self
193
186
  end
194
187
 
@@ -205,8 +198,8 @@ module Selenium
205
198
  # @return [ActionBuilder] A self reference.
206
199
  #
207
200
 
208
- def release(device: nil)
209
- pointer_up(:left, device: device)
201
+ def release(button: nil, device: nil)
202
+ pointer_up(button || :left, device: device)
210
203
  self
211
204
  end
212
205
 
@@ -232,10 +225,10 @@ module Selenium
232
225
  # @return [ActionBuilder] A self reference.
233
226
  #
234
227
 
235
- def click(element = nil, device: nil)
228
+ def click(element = nil, button: nil, device: nil)
236
229
  move_to(element, device: device) if element
237
- pointer_down(:left, device: device)
238
- pointer_up(:left, device: device)
230
+ pointer_down(button || :left, device: device)
231
+ pointer_up(button || :left, device: device)
239
232
  self
240
233
  end
241
234
 
@@ -290,10 +283,7 @@ module Selenium
290
283
  #
291
284
 
292
285
  def context_click(element = nil, device: nil)
293
- move_to(element, device: device) if element
294
- pointer_down(:right, device: device)
295
- pointer_up(:right, device: device)
296
- self
286
+ click(element, button: :right, device: device)
297
287
  end
298
288
 
299
289
  #
@@ -348,15 +338,15 @@ module Selenium
348
338
 
349
339
  private
350
340
 
351
- def button_action(button, action: nil, device: nil)
352
- pointer = get_pointer(device)
353
- pointer.send(action, button)
341
+ def button_action(button, action, device: nil, **opts)
342
+ pointer = pointer_input(device)
343
+ pointer.send(action, button, **opts)
354
344
  tick(pointer)
355
345
  self
356
346
  end
357
347
 
358
- def get_pointer(device = nil)
359
- get_device(device) || pointer_inputs.first
348
+ def pointer_input(name = nil)
349
+ device(name: name, type: Interactions::POINTER) || add_pointer_input(:mouse, 'mouse')
360
350
  end
361
351
  end # PointerActions
362
352
  end # WebDriver
@@ -0,0 +1,45 @@
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 Interactions
23
+ #
24
+ # Action to cancel any other Pointer Action.
25
+ #
26
+ # @api private
27
+ #
28
+
29
+ class PointerCancel < Interaction
30
+ def initialize(source)
31
+ super(source)
32
+ @type = :pointerCancel
33
+ end
34
+
35
+ def assert_source(source)
36
+ raise TypeError, "#{source.type} is not a valid input type" unless source.is_a? PointerInput
37
+ end
38
+
39
+ def encode
40
+ {type: type}
41
+ end
42
+ end # PointerCancel
43
+ end # Interactions
44
+ end # WebDriver
45
+ end # Selenium