selenium-webdriver 3.142.7 → 4.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGES +611 -5
  3. data/Gemfile +5 -1
  4. data/LICENSE +1 -1
  5. data/NOTICE +2 -0
  6. data/README.md +4 -5
  7. data/bin/linux/selenium-manager +0 -0
  8. data/bin/macos/selenium-manager +0 -0
  9. data/bin/windows/selenium-manager.exe +0 -0
  10. data/lib/selenium/server.rb +94 -79
  11. data/lib/selenium/webdriver/atoms/findElements.js +121 -0
  12. data/lib/selenium/webdriver/atoms/getAttribute.js +100 -7
  13. data/lib/selenium/webdriver/atoms/isDisplayed.js +76 -78
  14. data/lib/selenium/webdriver/atoms/mutationListener.js +55 -0
  15. data/lib/selenium/webdriver/atoms.rb +2 -3
  16. data/lib/selenium/webdriver/bidi/browsing_context.rb +88 -0
  17. data/lib/selenium/webdriver/bidi/browsing_context_info.rb +35 -0
  18. data/lib/selenium/webdriver/bidi/log/base_log_entry.rb +35 -0
  19. data/lib/selenium/webdriver/bidi/log/console_log_entry.rb +35 -0
  20. data/lib/selenium/webdriver/bidi/log/filter_by.rb +40 -0
  21. data/lib/selenium/webdriver/bidi/log/generic_log_entry.rb +33 -0
  22. data/lib/selenium/webdriver/bidi/log/javascript_log_entry.rb +33 -0
  23. data/lib/selenium/webdriver/bidi/log_inspector.rb +143 -0
  24. data/lib/selenium/webdriver/bidi/navigate_result.rb +33 -0
  25. data/lib/selenium/webdriver/bidi/session.rb +51 -0
  26. data/lib/selenium/webdriver/{common/keyboard.rb → bidi.rb} +21 -35
  27. data/lib/selenium/webdriver/chrome/driver.rb +9 -86
  28. data/lib/selenium/webdriver/chrome/features.rb +44 -0
  29. data/lib/selenium/webdriver/chrome/options.rb +9 -158
  30. data/lib/selenium/webdriver/chrome/profile.rb +3 -80
  31. data/lib/selenium/webdriver/chrome/service.rb +6 -33
  32. data/lib/selenium/webdriver/chrome.rb +5 -18
  33. data/lib/selenium/webdriver/chromium/driver.rb +61 -0
  34. data/lib/selenium/webdriver/{chrome/bridge.rb → chromium/features.rb} +51 -16
  35. data/lib/selenium/webdriver/chromium/options.rb +261 -0
  36. data/lib/selenium/webdriver/chromium/profile.rb +113 -0
  37. data/lib/selenium/webdriver/chromium/service.rb +42 -0
  38. data/lib/selenium/webdriver/chromium.rb +32 -0
  39. data/lib/selenium/webdriver/common/action_builder.rb +128 -238
  40. data/lib/selenium/webdriver/common/child_process.rb +124 -0
  41. data/lib/selenium/webdriver/common/driver.rb +94 -43
  42. data/lib/selenium/webdriver/common/driver_extensions/downloads_files.rb +0 -2
  43. data/lib/selenium/webdriver/common/driver_extensions/full_page_screenshot.rb +42 -0
  44. data/lib/selenium/webdriver/common/driver_extensions/has_addons.rb +0 -2
  45. data/lib/selenium/webdriver/common/driver_extensions/has_apple_permissions.rb +49 -0
  46. data/lib/selenium/webdriver/common/driver_extensions/has_authentication.rb +87 -0
  47. data/lib/selenium/webdriver/common/driver_extensions/{has_touch_screen.rb → has_bidi.rb} +9 -9
  48. data/lib/selenium/webdriver/common/driver_extensions/has_casting.rb +86 -0
  49. data/lib/selenium/webdriver/common/driver_extensions/has_cdp.rb +36 -0
  50. data/lib/selenium/webdriver/common/driver_extensions/has_context.rb +42 -0
  51. data/lib/selenium/webdriver/common/driver_extensions/has_debugger.rb +0 -2
  52. data/lib/selenium/webdriver/common/driver_extensions/has_devtools.rb +41 -0
  53. data/lib/selenium/webdriver/common/driver_extensions/has_launching.rb +36 -0
  54. data/lib/selenium/webdriver/common/driver_extensions/has_location.rb +5 -9
  55. data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +143 -0
  56. data/lib/selenium/webdriver/common/driver_extensions/{has_remote_status.rb → has_logs.rb} +4 -4
  57. data/lib/selenium/webdriver/common/driver_extensions/has_network_conditions.rb +16 -1
  58. data/lib/selenium/webdriver/common/driver_extensions/has_network_connection.rb +6 -27
  59. data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +69 -0
  60. data/lib/selenium/webdriver/common/driver_extensions/has_permissions.rb +11 -13
  61. data/lib/selenium/webdriver/common/driver_extensions/has_pinned_scripts.rb +75 -0
  62. data/lib/selenium/webdriver/common/driver_extensions/{rotatable.rb → prints_page.rb} +18 -20
  63. data/lib/selenium/webdriver/common/driver_finder.rb +47 -0
  64. data/lib/selenium/webdriver/common/element.rb +89 -29
  65. data/lib/selenium/webdriver/common/error.rb +53 -194
  66. data/lib/selenium/webdriver/common/html5/shared_web_storage.rb +2 -2
  67. data/lib/selenium/webdriver/common/interactions/input_device.rb +10 -4
  68. data/lib/selenium/webdriver/common/interactions/interaction.rb +12 -22
  69. data/lib/selenium/webdriver/common/interactions/interactions.rb +24 -4
  70. data/lib/selenium/webdriver/common/interactions/key_actions.rb +10 -6
  71. data/lib/selenium/webdriver/common/interactions/key_input.rb +11 -27
  72. data/lib/selenium/webdriver/common/interactions/none_input.rb +10 -8
  73. data/lib/selenium/webdriver/common/interactions/pause.rb +49 -0
  74. data/lib/selenium/webdriver/common/interactions/pointer_actions.rb +71 -82
  75. data/lib/selenium/webdriver/common/interactions/pointer_cancel.rb +45 -0
  76. data/lib/selenium/webdriver/common/interactions/pointer_event_properties.rb +63 -0
  77. data/lib/selenium/webdriver/common/interactions/pointer_input.rb +15 -84
  78. data/lib/selenium/webdriver/common/interactions/pointer_move.rb +60 -0
  79. data/lib/selenium/webdriver/common/interactions/pointer_press.rb +85 -0
  80. data/lib/selenium/webdriver/common/interactions/scroll.rb +59 -0
  81. data/lib/selenium/webdriver/common/interactions/scroll_origin.rb +48 -0
  82. data/lib/selenium/webdriver/common/interactions/typing_interaction.rb +54 -0
  83. data/lib/selenium/webdriver/common/interactions/wheel_actions.rb +113 -0
  84. data/lib/selenium/webdriver/common/{w3c_manager.rb → interactions/wheel_input.rb} +14 -17
  85. data/lib/selenium/webdriver/common/keys.rb +1 -0
  86. data/lib/selenium/webdriver/common/local_driver.rb +55 -0
  87. data/lib/selenium/webdriver/common/log_entry.rb +2 -2
  88. data/lib/selenium/webdriver/common/logger.rb +119 -19
  89. data/lib/selenium/webdriver/common/manager.rb +11 -38
  90. data/lib/selenium/webdriver/common/options.rb +169 -23
  91. data/lib/selenium/webdriver/common/platform.rb +14 -6
  92. data/lib/selenium/webdriver/common/port_prober.rb +4 -6
  93. data/lib/selenium/webdriver/common/profile_helper.rb +11 -9
  94. data/lib/selenium/webdriver/common/proxy.rb +8 -5
  95. data/lib/selenium/webdriver/common/search_context.rb +7 -9
  96. data/lib/selenium/webdriver/common/selenium_manager.rb +125 -0
  97. data/lib/selenium/webdriver/common/service.rb +26 -137
  98. data/lib/selenium/webdriver/common/service_manager.rb +144 -0
  99. data/lib/selenium/webdriver/common/shadow_root.rb +86 -0
  100. data/lib/selenium/webdriver/common/socket_lock.rb +4 -4
  101. data/lib/selenium/webdriver/common/socket_poller.rb +4 -4
  102. data/lib/selenium/webdriver/common/takes_screenshot.rb +65 -0
  103. data/lib/selenium/webdriver/common/target_locator.rb +31 -4
  104. data/lib/selenium/webdriver/common/timeouts.rb +31 -4
  105. data/lib/selenium/webdriver/common/virtual_authenticator/credential.rb +85 -0
  106. data/lib/selenium/webdriver/common/virtual_authenticator/virtual_authenticator.rb +72 -0
  107. data/lib/selenium/webdriver/common/virtual_authenticator/virtual_authenticator_options.rb +62 -0
  108. data/lib/selenium/webdriver/common/wait.rb +1 -1
  109. data/lib/selenium/webdriver/common/websocket_connection.rb +164 -0
  110. data/lib/selenium/webdriver/common/window.rb +6 -10
  111. data/lib/selenium/webdriver/common/zipper.rb +4 -10
  112. data/lib/selenium/webdriver/common.rb +42 -18
  113. data/lib/selenium/webdriver/devtools/console_event.rb +36 -0
  114. data/lib/selenium/webdriver/devtools/exception_event.rb +34 -0
  115. data/lib/selenium/webdriver/devtools/mutation_event.rb +35 -0
  116. data/lib/selenium/webdriver/devtools/network_interceptor.rb +173 -0
  117. data/lib/selenium/webdriver/devtools/pinned_script.rb +57 -0
  118. data/lib/selenium/webdriver/devtools/request.rb +65 -0
  119. data/lib/selenium/webdriver/devtools/response.rb +64 -0
  120. data/lib/selenium/webdriver/devtools.rb +96 -0
  121. data/lib/selenium/webdriver/edge/driver.rb +11 -27
  122. data/lib/selenium/webdriver/edge/features.rb +44 -0
  123. data/lib/selenium/webdriver/edge/options.rb +18 -43
  124. data/lib/selenium/webdriver/edge/profile.rb +33 -0
  125. data/lib/selenium/webdriver/edge/service.rb +7 -27
  126. data/lib/selenium/webdriver/edge.rb +11 -14
  127. data/lib/selenium/webdriver/firefox/driver.rb +38 -19
  128. data/lib/selenium/webdriver/firefox/extension.rb +8 -0
  129. data/lib/selenium/webdriver/firefox/features.rb +66 -0
  130. data/lib/selenium/webdriver/firefox/options.rb +77 -50
  131. data/lib/selenium/webdriver/firefox/profile.rb +17 -71
  132. data/lib/selenium/webdriver/firefox/service.rb +3 -13
  133. data/lib/selenium/webdriver/firefox/util.rb +1 -1
  134. data/lib/selenium/webdriver/firefox.rb +17 -28
  135. data/lib/selenium/webdriver/ie/driver.rb +5 -45
  136. data/lib/selenium/webdriver/ie/options.rb +15 -46
  137. data/lib/selenium/webdriver/ie/service.rb +11 -19
  138. data/lib/selenium/webdriver/ie.rb +3 -16
  139. data/lib/selenium/webdriver/remote/bridge/commands.rb +170 -0
  140. data/lib/selenium/webdriver/remote/bridge.rb +592 -87
  141. data/lib/selenium/webdriver/remote/capabilities.rb +182 -124
  142. data/lib/selenium/webdriver/remote/driver.rb +30 -15
  143. data/lib/selenium/webdriver/remote/http/common.rb +3 -8
  144. data/lib/selenium/webdriver/remote/http/curb.rb +1 -3
  145. data/lib/selenium/webdriver/remote/http/default.rb +23 -31
  146. data/lib/selenium/webdriver/remote/response.rb +17 -49
  147. data/lib/selenium/webdriver/remote.rb +14 -12
  148. data/lib/selenium/webdriver/safari/driver.rb +7 -29
  149. data/lib/selenium/webdriver/safari/{bridge.rb → features.rb} +3 -5
  150. data/lib/selenium/webdriver/safari/options.rb +12 -27
  151. data/lib/selenium/webdriver/safari/service.rb +13 -11
  152. data/lib/selenium/webdriver/safari.rb +14 -20
  153. data/lib/selenium/webdriver/support/block_event_listener.rb +1 -1
  154. data/lib/selenium/webdriver/support/color.rb +24 -24
  155. data/lib/selenium/webdriver/support/event_firing_bridge.rb +4 -4
  156. data/lib/selenium/webdriver/support/guards/guard.rb +87 -0
  157. data/lib/selenium/webdriver/{firefox/marionette/bridge.rb → support/guards/guard_condition.rb} +21 -20
  158. data/lib/selenium/webdriver/support/guards.rb +95 -0
  159. data/lib/selenium/webdriver/support/relative_locator.rb +50 -0
  160. data/lib/selenium/webdriver/support/select.rb +6 -4
  161. data/lib/selenium/webdriver/support.rb +1 -0
  162. data/lib/selenium/webdriver/version.rb +1 -1
  163. data/lib/selenium/webdriver.rb +18 -17
  164. data/selenium-webdriver.gemspec +36 -18
  165. metadata +159 -89
  166. data/lib/selenium/webdriver/common/bridge_helper.rb +0 -82
  167. data/lib/selenium/webdriver/common/driver_extensions/takes_screenshot.rb +0 -64
  168. data/lib/selenium/webdriver/common/mouse.rb +0 -89
  169. data/lib/selenium/webdriver/common/touch_action_builder.rb +0 -78
  170. data/lib/selenium/webdriver/common/touch_screen.rb +0 -123
  171. data/lib/selenium/webdriver/common/w3c_action_builder.rb +0 -212
  172. data/lib/selenium/webdriver/edge/bridge.rb +0 -76
  173. data/lib/selenium/webdriver/firefox/binary.rb +0 -187
  174. data/lib/selenium/webdriver/firefox/extension/prefs.json +0 -69
  175. data/lib/selenium/webdriver/firefox/extension/webdriver.xpi +0 -0
  176. data/lib/selenium/webdriver/firefox/launcher.rb +0 -111
  177. data/lib/selenium/webdriver/firefox/legacy/driver.rb +0 -83
  178. data/lib/selenium/webdriver/firefox/marionette/driver.rb +0 -90
  179. data/lib/selenium/webdriver/firefox/native/linux/amd64/x_ignore_nofocus.so +0 -0
  180. data/lib/selenium/webdriver/firefox/native/linux/x86/x_ignore_nofocus.so +0 -0
  181. data/lib/selenium/webdriver/remote/http/persistent.rb +0 -60
  182. data/lib/selenium/webdriver/remote/oss/bridge.rb +0 -594
  183. data/lib/selenium/webdriver/remote/oss/commands.rb +0 -223
  184. data/lib/selenium/webdriver/remote/w3c/bridge.rb +0 -605
  185. data/lib/selenium/webdriver/remote/w3c/capabilities.rb +0 -310
  186. data/lib/selenium/webdriver/remote/w3c/commands.rb +0 -157
@@ -19,346 +19,236 @@
19
19
 
20
20
  module Selenium
21
21
  module WebDriver
22
- #
23
- # The ActionBuilder provides the user a way to set up and perform
24
- # complex user interactions.
25
- #
26
- # This class should not be instantiated directly, but is created by Driver#action
27
- #
28
- # @example
29
- #
30
- # driver.action.key_down(:shift).
31
- # click(element).
32
- # click(second_element).
33
- # key_up(:shift).
34
- # drag_and_drop(element, third_element).
35
- # perform
36
- #
37
-
38
22
  class ActionBuilder
23
+ include KeyActions # Actions specific to key inputs
24
+ include PointerActions # Actions specific to pointer inputs
25
+ include WheelActions # Actions specific to wheel inputs
26
+
27
+ attr_reader :devices
28
+
39
29
  #
40
- # @api private
30
+ # Initialize a W3C Action Builder. Differs from previous by requiring a bridge and allowing asynchronous actions.
31
+ # The W3C implementation allows asynchronous actions per device. e.g. A key can be pressed at the same time that
32
+ # the mouse is moving. Keep in mind that pauses must be added for other devices in order to line up the actions
33
+ # correctly when using asynchronous.
34
+ #
35
+ # @param [Selenium::WebDriver::Remote::Bridge] bridge the bridge for the current driver instance.
36
+ # @param [Selenium::WebDriver::Interactions::PointerInput] deprecated_mouse PointerInput for the mouse.
37
+ # @param [Selenium::WebDriver::Interactions::KeyInput] deprecated_keyboard KeyInput for the keyboard.
38
+ # @param [Boolean] deprecated_async Whether to perform the actions asynchronously per device.
39
+ # Defaults to false for backwards compatibility.
40
+ # @param [Array<Selenium::WebDriver::Interactions::InputDevices>] devices list of valid sources of input.
41
+ # @param [Boolean] async Whether to perform the actions asynchronously per device.
42
+ # @return [ActionBuilder] A self reference.
41
43
  #
42
44
 
43
- def initialize(mouse, keyboard)
44
- @devices = {
45
- mouse: mouse,
46
- keyboard: keyboard
47
- }
45
+ def initialize(bridge, devices: [], async: false, duration: 250)
46
+ @bridge = bridge
47
+ @duration = duration
48
+ @async = async
49
+ @devices = []
48
50
 
49
- @actions = []
51
+ Array(devices).each { |device| add_input(device) }
50
52
  end
51
53
 
52
54
  #
53
- # Performs a modifier key press. Does not release
54
- # the modifier key - subsequent interactions may assume it's kept pressed.
55
- # Note that the modifier key is never released implicitly - either
56
- # #key_up(key) or #send_keys(:null) must be called to release the modifier.
55
+ # Adds a PointerInput device of the given kind
57
56
  #
58
- # Equivalent to:
59
- # driver.action.click(element).send_keys(key)
60
- # # or
61
- # driver.action.click.send_keys(key)
57
+ # @example Add a touch pointer input device
62
58
  #
63
- # @example Press a key
59
+ # builder = device.action
60
+ # builder.add_pointer_input('touch', :touch)
64
61
  #
65
- # driver.action.key_down(:control).perform
62
+ # @param [String] name name for the device
63
+ # @param [Symbol] kind kind of pointer device to create
64
+ # @return [Interactions::PointerInput] The pointer input added
66
65
  #
67
- # @example Press a key on an element
68
66
  #
69
- # el = driver.find_element(id: "some_id")
70
- # driver.action.key_down(el, :shift).perform
71
- #
72
- # @overload key_down(key)
73
- # @param [:shift, :alt, :control, :command, :meta] key The modifier key to press
74
- # @overload key_down(element, key)
75
- # @param [Element] element An optional element to move to first
76
- # @param [:shift, :alt, :control, :command, :meta] key The modifier key to press
77
- # @raise [ArgumentError] if the given key is not a modifier
78
- # @return [ActionBuilder] A self reference
79
-
80
- def key_down(*args)
81
- @actions << [:mouse, :click, [args.shift]] if args.first.is_a? Element
82
67
 
83
- @actions << [:keyboard, :press, args]
84
- self
68
+ def add_pointer_input(kind, name)
69
+ add_input(Interactions.pointer(kind, name: name))
85
70
  end
86
71
 
87
72
  #
88
- # Performs a modifier key release.
89
- # Releasing a non-depressed modifier key will yield undefined behaviour.
73
+ # Adds a KeyInput device
90
74
  #
91
- # @example Release a key
75
+ # @example Add a key input device
92
76
  #
93
- # driver.action.key_up(:shift).perform
77
+ # builder = device.action
78
+ # builder.add_key_input('keyboard2')
94
79
  #
95
- # @example Release a key from an element
96
- #
97
- # el = driver.find_element(id: "some_id")
98
- # driver.action.key_up(el, :alt).perform
80
+ # @param [String] name name for the device
81
+ # @return [Interactions::KeyInput] The key input added
99
82
  #
100
- # @overload key_up(key)
101
- # @param [:shift, :alt, :control, :command, :meta] key The modifier key to release
102
- # @overload key_up(element, key)
103
- # @param [Element] element An optional element to move to first
104
- # @param [:shift, :alt, :control, :command, :meta] key The modifier key to release
105
- # @raise [ArgumentError] if the given key is not a modifier
106
- # @return [ActionBuilder] A self reference
107
- #
108
-
109
- def key_up(*args)
110
- @actions << [:mouse, :click, [args.shift]] if args.first.is_a? Element
111
83
 
112
- @actions << [:keyboard, :release, args]
113
- self
84
+ def add_key_input(name)
85
+ add_input(Interactions.key(name))
114
86
  end
115
87
 
116
88
  #
117
- # Sends keys to the active element. This differs from calling
118
- # Element#send_keys(keys) on the active element in two ways:
89
+ # Adds a WheelInput device
119
90
  #
120
- # * The modifier keys included in this call are not released.
121
- # * There is no attempt to re-focus the element - so send_keys(:tab) for switching elements should work.
91
+ # @example Add a wheel input device
122
92
  #
123
- # @example Send the text "help" to an element
93
+ # builder = device.action
94
+ # builder.add_wheel_input('wheel2')
124
95
  #
125
- # el = driver.find_element(id: "some_id")
126
- # driver.action.send_keys(el, "help").perform
96
+ # @param [String] name name for the device
97
+ # @return [Interactions::WheelInput] The wheel input added
127
98
  #
128
- # @example Send the text "help" to the currently focused element
99
+
100
+ def add_wheel_input(name)
101
+ add_input(Interactions.wheel(name))
102
+ end
103
+
129
104
  #
130
- # driver.action.send_keys("help").perform
105
+ # Retrieves the input device for the given name or type
131
106
  #
132
- # @overload send_keys(keys)
133
- # @param [Array, Symbol, String] keys The key(s) to press and release
134
- # @overload send_keys(element, keys)
135
- # @param [Element] element An optional element to move to first
136
- # @param [Array, Symbol, String] keys The key(s) to press and release
137
- # @return [ActionBuilder] A self reference
107
+ # @param [String] name name of the input device
108
+ # @param [String] type name of the input device
109
+ # @return [Selenium::WebDriver::Interactions::InputDevice] input device with given name or type
138
110
  #
139
111
 
140
- def send_keys(*args)
141
- @actions << [:mouse, :click, [args.shift]] if args.first.is_a? Element
112
+ def device(name: nil, type: nil)
113
+ input = @devices.find { |device| (device.name == name.to_s || name.nil?) && (device.type == type || type.nil?) }
142
114
 
143
- @actions << [:keyboard, :send_keys, args]
144
- self
115
+ raise(ArgumentError, "Can not find device: #{name}") if name && input.nil?
116
+
117
+ input
145
118
  end
146
119
 
147
120
  #
148
- # Clicks (without releasing) in the middle of the given element. This is
149
- # equivalent to:
121
+ # Retrieves the current PointerInput devices
150
122
  #
151
- # driver.action.move_to(element).click_and_hold
152
- #
153
- # @example Clicking and holding on some element
154
- #
155
- # el = driver.find_element(id: "some_id")
156
- # driver.action.click_and_hold(el).perform
157
- #
158
- # @param [Element] element the element to move to and click.
159
- # @return [ActionBuilder] A self reference.
123
+ # @return [Array] array of current PointerInput devices
160
124
  #
161
125
 
162
- def click_and_hold(element = nil)
163
- @actions << [:mouse, :down, [element]]
164
- self
126
+ def pointer_inputs
127
+ @devices.select { |device| device.type == Interactions::POINTER }
165
128
  end
166
129
 
167
130
  #
168
- # Releases the depressed left mouse button at the current mouse location.
131
+ # Retrieves the current KeyInput device
169
132
  #
170
- # @example Releasing an element after clicking and holding it
171
- #
172
- # el = driver.find_element(id: "some_id")
173
- # driver.action.click_and_hold(el).release.perform
174
- #
175
- # @return [ActionBuilder] A self reference.
133
+ # @return [Selenium::WebDriver::Interactions::InputDevice] current KeyInput device
176
134
  #
177
135
 
178
- def release(element = nil)
179
- @actions << [:mouse, :up, [element]]
180
- self
136
+ def key_inputs
137
+ @devices.select { |device| device.type == Interactions::KEY }
181
138
  end
182
139
 
183
140
  #
184
- # Clicks in the middle of the given element. Equivalent to:
141
+ # Retrieves the current WheelInput device
185
142
  #
186
- # driver.action.move_to(element).click
187
- #
188
- # When no element is passed, the current mouse position will be clicked.
189
- #
190
- # @example Clicking on an element
191
- #
192
- # el = driver.find_element(id: "some_id")
193
- # driver.action.click(el).perform
194
- #
195
- # @example Clicking at the current mouse position
196
- #
197
- # driver.action.click.perform
198
- #
199
- # @param [Selenium::WebDriver::Element] element An optional element to click.
200
- # @return [ActionBuilder] A self reference.
143
+ # @return [Selenium::WebDriver::Interactions::InputDevice] current WheelInput devices
201
144
  #
202
145
 
203
- def click(element = nil)
204
- @actions << [:mouse, :click, [element]]
205
- self
146
+ def wheel_inputs
147
+ @devices.select { |device| device.type == Interactions::WHEEL }
206
148
  end
207
149
 
208
150
  #
209
- # Performs a double-click at middle of the given element. Equivalent to:
210
- #
211
- # driver.action.move_to(element).double_click
151
+ # Creates a pause for the given device of the given duration. If no duration is given, the pause will only wait
152
+ # for all actions to complete in that tick.
212
153
  #
213
- # @example Double click an element
154
+ # @example Send keys to an element
214
155
  #
215
- # el = driver.find_element(id: "some_id")
216
- # driver.action.double_click(el).perform
156
+ # action_builder = driver.action
157
+ # keyboard = action_builder.key_input
158
+ # el = driver.find_element(id: "some_id")
159
+ # driver.action.click(el).pause(keyboard).pause(keyboard).pause(keyboard).send_keys('keys').perform
217
160
  #
218
- # @param [Selenium::WebDriver::Element] element An optional element to move to.
161
+ # @param [InputDevice] device Input device to pause
162
+ # @param [Float] duration Duration to pause
219
163
  # @return [ActionBuilder] A self reference.
220
164
  #
221
165
 
222
- def double_click(element = nil)
223
- @actions << [:mouse, :double_click, [element]]
166
+ def pause(device: nil, duration: 0)
167
+ device ||= pointer_input
168
+ device.create_pause(duration)
224
169
  self
225
170
  end
226
171
 
227
172
  #
228
- # Moves the mouse to the middle of the given element. The element is scrolled into
229
- # view and its location is calculated using getBoundingClientRect. Then the
230
- # mouse is moved to optional offset coordinates from the element.
231
- #
232
- # Note that when using offsets, both coordinates need to be passed.
233
- #
234
- # @example Scroll element into view and move the mouse to it
235
- #
236
- # el = driver.find_element(id: "some_id")
237
- # driver.action.move_to(el).perform
173
+ # Creates multiple pauses for the given device of the given duration.
238
174
  #
239
- # @example
175
+ # @example Send keys to an element
240
176
  #
177
+ # action_builder = driver.action
178
+ # keyboard = action_builder.key_input
241
179
  # el = driver.find_element(id: "some_id")
242
- # driver.action.move_to(el, 100, 100).perform
180
+ # driver.action.click(el).pauses(keyboard, 3).send_keys('keys').perform
243
181
  #
244
- # @param [Selenium::WebDriver::Element] element to move to.
245
- # @param [Integer] right_by Optional offset from the top-left corner. A negative value means
246
- # coordinates right from the element.
247
- # @param [Integer] down_by Optional offset from the top-left corner. A negative value means
248
- # coordinates above the element.
182
+ # @param [InputDevice] device Input device to pause
183
+ # @param [Integer] number of pauses to add for the device
184
+ # @param [Float] duration Duration to pause
249
185
  # @return [ActionBuilder] A self reference.
250
186
  #
251
187
 
252
- def move_to(element, right_by = nil, down_by = nil)
253
- @actions << if right_by && down_by
254
- [:mouse, :move_to, [element, Integer(right_by), Integer(down_by)]]
255
- else
256
- [:mouse, :move_to, [element]]
257
- end
188
+ def pauses(device: nil, number: nil, duration: 0)
189
+ number ||= 2
190
+ device ||= pointer_input
191
+ duration ||= 0
258
192
 
193
+ number.times { device.create_pause(duration) }
259
194
  self
260
195
  end
261
196
 
262
197
  #
263
- # Moves the mouse from its current position (or 0,0) by the given offset.
264
- # If the coordinates provided are outside the viewport (the mouse will
265
- # end up outside the browser window) then the viewport is scrolled to
266
- # match.
267
- #
268
- # @example Move the mouse to a certain offset from its current position
269
- #
270
- # driver.action.move_by(100, 100).perform
271
- #
272
- # @param [Integer] right_by horizontal offset. A negative value means moving the
273
- # mouse left.
274
- # @param [Integer] down_by vertical offset. A negative value means moving the mouse
275
- # up.
276
- # @return [ActionBuilder] A self reference.
277
- # @raise [MoveTargetOutOfBoundsError] if the provided offset is outside
278
- # the document's boundaries.
198
+ # Executes the actions added to the builder.
279
199
  #
280
200
 
281
- def move_by(right_by, down_by)
282
- @actions << [:mouse, :move_by, [Integer(right_by), Integer(down_by)]]
283
- self
201
+ def perform
202
+ @bridge.send_actions @devices.filter_map(&:encode)
203
+ clear_all_actions
204
+ nil
284
205
  end
285
206
 
286
207
  #
287
- # Performs a context-click at middle of the given element. First performs
288
- # a move_to to the location of the element.
289
- #
290
- # @example Context-click at middle of given element
291
- #
292
- # el = driver.find_element(id: "some_id")
293
- # driver.action.context_click(el).perform
294
- #
295
- # @param [Selenium::WebDriver::Element] element An element to context click.
296
- # @return [ActionBuilder] A self reference.
208
+ # Clears all actions from the builder.
297
209
  #
298
210
 
299
- def context_click(element = nil)
300
- @actions << [:mouse, :context_click, [element]]
301
- self
211
+ def clear_all_actions
212
+ @devices.each(&:clear_actions)
302
213
  end
303
214
 
304
215
  #
305
- # A convenience method that performs click-and-hold at the location of the
306
- # source element, moves to the location of the target element, then
307
- # releases the mouse.
308
- #
309
- # @example Drag and drop one element onto another
310
- #
311
- # el1 = driver.find_element(id: "some_id1")
312
- # el2 = driver.find_element(id: "some_id2")
313
- # driver.action.drag_and_drop(el1, el2).perform
314
- #
315
- # @param [Selenium::WebDriver::Element] source element to emulate button down at.
316
- # @param [Selenium::WebDriver::Element] target element to move to and release the
317
- # mouse at.
318
- # @return [ActionBuilder] A self reference.
216
+ # Releases all action states from the browser.
319
217
  #
320
218
 
321
- def drag_and_drop(source, target)
322
- click_and_hold source
323
- move_to target
324
- release
325
-
326
- self
219
+ def release_actions
220
+ @bridge.release_actions
327
221
  end
328
222
 
223
+ private
224
+
329
225
  #
330
- # A convenience method that performs click-and-hold at the location of
331
- # the source element, moves by a given offset, then releases the mouse.
332
- #
333
- # @example Drag and drop an element by offset
334
- #
335
- # el = driver.find_element(id: "some_id1")
336
- # driver.action.drag_and_drop_by(el, 100, 100).perform
226
+ # Adds pauses for all devices but the given devices
337
227
  #
338
- # @param [Selenium::WebDriver::Element] source Element to emulate button down at.
339
- # @param [Integer] right_by horizontal move offset.
340
- # @param [Integer] down_by vertical move offset.
341
- # @return [ActionBuilder] A self reference.
228
+ # @param [Array[InputDevice]] action_devices Array of Input Devices performing an action in this tick.
342
229
  #
343
230
 
344
- def drag_and_drop_by(source, right_by, down_by)
345
- click_and_hold source
346
- move_by right_by, down_by
347
- release
231
+ def tick(*action_devices)
232
+ return if @async
348
233
 
349
- self
234
+ @devices.each { |device| device.create_pause unless action_devices.include? device }
350
235
  end
351
236
 
352
237
  #
353
- # Executes the actions added to the builder.
238
+ # Adds an InputDevice
354
239
  #
355
240
 
356
- def perform
357
- @actions.each do |receiver, method, args|
358
- @devices.fetch(receiver).__send__(method, *args)
359
- end
241
+ def add_input(device)
242
+ device = Interactions.send(device) if device.is_a?(Symbol) && Interactions.respond_to?(device)
360
243
 
361
- nil
244
+ raise TypeError, "#{device.inspect} is not a valid InputDevice" unless device.is_a?(Interactions::InputDevice)
245
+
246
+ unless @async
247
+ max_device = @devices.max { |a, b| a.actions.length <=> b.actions.length }
248
+ pauses(device: device, number: max_device.actions.length) if max_device
249
+ end
250
+ @devices << device
251
+ device
362
252
  end
363
253
  end # ActionBuilder
364
254
  end # WebDriver
@@ -0,0 +1,124 @@
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
+ #
23
+ # @api private
24
+ #
25
+
26
+ class ChildProcess
27
+ TimeoutError = Class.new(StandardError)
28
+
29
+ SIGTERM = 'TERM'
30
+ SIGKILL = 'KILL'
31
+
32
+ POLL_INTERVAL = 0.1
33
+
34
+ attr_accessor :detach
35
+ attr_writer :io
36
+
37
+ def self.build(*command)
38
+ new(*command)
39
+ end
40
+
41
+ def initialize(*command)
42
+ @command = command
43
+ @detach = false
44
+ @pid = nil
45
+ @status = nil
46
+ end
47
+
48
+ def io
49
+ @io ||= Platform.null_device
50
+ end
51
+
52
+ def start
53
+ options = {%i[out err] => io}
54
+ options[:pgroup] = true unless Platform.windows? # NOTE: this is a bug only in Windows 7
55
+
56
+ WebDriver.logger.debug("Starting process: #{@command} with #{options}", id: :process)
57
+ @pid = Process.spawn(*@command, options)
58
+ WebDriver.logger.debug(" -> pid: #{@pid}", id: :process)
59
+
60
+ Process.detach(@pid) if detach
61
+ end
62
+
63
+ def stop(timeout = 3)
64
+ return unless @pid
65
+ return if exited?
66
+
67
+ WebDriver.logger.debug("Sending TERM to process: #{@pid}", id: :process)
68
+ terminate(@pid)
69
+ poll_for_exit(timeout)
70
+
71
+ WebDriver.logger.debug(" -> stopped #{@pid}", id: :process)
72
+ rescue TimeoutError, Errno::EINVAL
73
+ WebDriver.logger.debug(" -> sending KILL to process: #{@pid}", id: :process)
74
+ kill(@pid)
75
+ wait
76
+ WebDriver.logger.debug(" -> killed #{@pid}", id: :process)
77
+ end
78
+
79
+ def alive?
80
+ @pid && !exited?
81
+ end
82
+
83
+ def exited?
84
+ return unless @pid
85
+
86
+ WebDriver.logger.debug("Checking if #{@pid} is exited:", id: :process)
87
+ _, @status = Process.waitpid2(@pid, Process::WNOHANG | Process::WUNTRACED) if @status.nil?
88
+ return if @status.nil?
89
+
90
+ exit_code = @status.exitstatus || @status.termsig
91
+ WebDriver.logger.debug(" -> exit code is #{exit_code.inspect}", id: :process)
92
+
93
+ !!exit_code
94
+ end
95
+
96
+ def poll_for_exit(timeout)
97
+ WebDriver.logger.debug("Polling #{timeout} seconds for exit of #{@pid}", id: :process)
98
+
99
+ end_time = Time.now + timeout
100
+ sleep POLL_INTERVAL until exited? || Time.now > end_time
101
+
102
+ raise TimeoutError, " -> #{@pid} still alive after #{timeout} seconds" unless exited?
103
+ end
104
+
105
+ def wait
106
+ return if exited?
107
+
108
+ _, @status = Process.waitpid2(@pid)
109
+ end
110
+
111
+ private
112
+
113
+ def terminate(pid)
114
+ Process.kill(SIGTERM, pid)
115
+ end
116
+
117
+ def kill(pid)
118
+ Process.kill(SIGKILL, pid)
119
+ rescue Errno::ECHILD, Errno::ESRCH
120
+ # already dead
121
+ end
122
+ end # ChildProcess
123
+ end # WebDriver
124
+ end # Selenium