selenium-webdriver 3.142.7 → 4.16.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 (188) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGES +685 -5
  3. data/Gemfile +6 -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 +93 -90
  11. data/lib/selenium/webdriver/atoms/findElements.js +122 -0
  12. data/lib/selenium/webdriver/atoms/getAttribute.js +100 -7
  13. data/lib/selenium/webdriver/atoms/isDisplayed.js +77 -78
  14. data/lib/selenium/webdriver/atoms/mutationListener.js +55 -0
  15. data/lib/selenium/webdriver/atoms.rb +5 -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 +48 -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 +7 -29
  32. data/lib/selenium/webdriver/chrome.rb +5 -20
  33. data/lib/selenium/webdriver/chromium/driver.rb +60 -0
  34. data/lib/selenium/webdriver/{chrome/bridge.rb → chromium/features.rb} +48 -17
  35. data/lib/selenium/webdriver/chromium/options.rb +243 -0
  36. data/lib/selenium/webdriver/chromium/profile.rb +113 -0
  37. data/lib/selenium/webdriver/chromium.rb +29 -0
  38. data/lib/selenium/webdriver/common/action_builder.rb +126 -238
  39. data/lib/selenium/webdriver/common/child_process.rb +124 -0
  40. data/lib/selenium/webdriver/common/driver.rb +82 -43
  41. data/lib/selenium/webdriver/common/driver_extensions/downloads_files.rb +0 -2
  42. data/lib/selenium/webdriver/common/driver_extensions/{has_location.rb → full_page_screenshot.rb} +13 -11
  43. data/lib/selenium/webdriver/common/driver_extensions/has_addons.rb +0 -2
  44. data/lib/selenium/webdriver/common/driver_extensions/has_apple_permissions.rb +49 -0
  45. data/lib/selenium/webdriver/common/driver_extensions/has_authentication.rb +87 -0
  46. data/lib/selenium/webdriver/common/driver_extensions/{has_touch_screen.rb → has_bidi.rb} +9 -9
  47. data/lib/selenium/webdriver/common/driver_extensions/has_casting.rb +86 -0
  48. data/lib/selenium/webdriver/common/driver_extensions/has_cdp.rb +36 -0
  49. data/lib/selenium/webdriver/common/driver_extensions/has_context.rb +42 -0
  50. data/lib/selenium/webdriver/common/driver_extensions/has_debugger.rb +0 -2
  51. data/lib/selenium/webdriver/common/driver_extensions/has_devtools.rb +41 -0
  52. data/lib/selenium/webdriver/common/driver_extensions/has_file_downloads.rb +65 -0
  53. data/lib/selenium/webdriver/common/driver_extensions/has_launching.rb +36 -0
  54. data/lib/selenium/webdriver/common/driver_extensions/has_log_events.rb +143 -0
  55. data/lib/selenium/webdriver/common/driver_extensions/{has_remote_status.rb → has_logs.rb} +4 -4
  56. data/lib/selenium/webdriver/common/driver_extensions/has_network_conditions.rb +16 -1
  57. data/lib/selenium/webdriver/common/driver_extensions/has_network_interception.rb +69 -0
  58. data/lib/selenium/webdriver/common/driver_extensions/has_permissions.rb +11 -13
  59. data/lib/selenium/webdriver/common/driver_extensions/has_pinned_scripts.rb +75 -0
  60. data/lib/selenium/webdriver/common/driver_extensions/{rotatable.rb → prints_page.rb} +18 -20
  61. data/lib/selenium/webdriver/common/driver_finder.rb +45 -0
  62. data/lib/selenium/webdriver/common/element.rb +89 -29
  63. data/lib/selenium/webdriver/common/error.rb +53 -194
  64. data/lib/selenium/webdriver/common/html5/shared_web_storage.rb +2 -2
  65. data/lib/selenium/webdriver/common/interactions/input_device.rb +10 -4
  66. data/lib/selenium/webdriver/common/interactions/interaction.rb +12 -22
  67. data/lib/selenium/webdriver/common/interactions/interactions.rb +24 -4
  68. data/lib/selenium/webdriver/common/interactions/key_actions.rb +10 -6
  69. data/lib/selenium/webdriver/common/interactions/key_input.rb +11 -27
  70. data/lib/selenium/webdriver/common/interactions/none_input.rb +10 -8
  71. data/lib/selenium/webdriver/common/interactions/pause.rb +49 -0
  72. data/lib/selenium/webdriver/common/interactions/pointer_actions.rb +71 -82
  73. data/lib/selenium/webdriver/common/interactions/pointer_cancel.rb +45 -0
  74. data/lib/selenium/webdriver/common/interactions/pointer_event_properties.rb +63 -0
  75. data/lib/selenium/webdriver/common/interactions/pointer_input.rb +15 -84
  76. data/lib/selenium/webdriver/common/interactions/pointer_move.rb +60 -0
  77. data/lib/selenium/webdriver/common/interactions/pointer_press.rb +85 -0
  78. data/lib/selenium/webdriver/common/interactions/scroll.rb +59 -0
  79. data/lib/selenium/webdriver/common/interactions/scroll_origin.rb +48 -0
  80. data/lib/selenium/webdriver/common/interactions/typing_interaction.rb +54 -0
  81. data/lib/selenium/webdriver/common/interactions/wheel_actions.rb +113 -0
  82. data/lib/selenium/webdriver/common/{w3c_manager.rb → interactions/wheel_input.rb} +14 -17
  83. data/lib/selenium/webdriver/common/keys.rb +1 -0
  84. data/lib/selenium/webdriver/common/local_driver.rb +46 -0
  85. data/lib/selenium/webdriver/common/log_entry.rb +2 -2
  86. data/lib/selenium/webdriver/common/logger.rb +119 -19
  87. data/lib/selenium/webdriver/common/manager.rb +11 -38
  88. data/lib/selenium/webdriver/common/options.rb +154 -23
  89. data/lib/selenium/webdriver/common/platform.rb +15 -52
  90. data/lib/selenium/webdriver/common/port_prober.rb +4 -6
  91. data/lib/selenium/webdriver/common/profile_helper.rb +11 -9
  92. data/lib/selenium/webdriver/common/proxy.rb +8 -5
  93. data/lib/selenium/webdriver/common/search_context.rb +7 -9
  94. data/lib/selenium/webdriver/common/selenium_manager.rb +140 -0
  95. data/lib/selenium/webdriver/common/service.rb +26 -143
  96. data/lib/selenium/webdriver/common/service_manager.rb +144 -0
  97. data/lib/selenium/webdriver/common/shadow_root.rb +86 -0
  98. data/lib/selenium/webdriver/common/socket_lock.rb +4 -4
  99. data/lib/selenium/webdriver/common/socket_poller.rb +4 -4
  100. data/lib/selenium/webdriver/common/takes_screenshot.rb +65 -0
  101. data/lib/selenium/webdriver/common/target_locator.rb +31 -4
  102. data/lib/selenium/webdriver/common/timeouts.rb +31 -4
  103. data/lib/selenium/webdriver/common/virtual_authenticator/credential.rb +85 -0
  104. data/lib/selenium/webdriver/common/virtual_authenticator/virtual_authenticator.rb +72 -0
  105. data/lib/selenium/webdriver/common/virtual_authenticator/virtual_authenticator_options.rb +62 -0
  106. data/lib/selenium/webdriver/common/wait.rb +1 -1
  107. data/lib/selenium/webdriver/common/websocket_connection.rb +164 -0
  108. data/lib/selenium/webdriver/common/window.rb +6 -10
  109. data/lib/selenium/webdriver/common/zipper.rb +4 -10
  110. data/lib/selenium/webdriver/common.rb +43 -20
  111. data/lib/selenium/webdriver/devtools/console_event.rb +36 -0
  112. data/lib/selenium/webdriver/devtools/exception_event.rb +34 -0
  113. data/lib/selenium/webdriver/devtools/mutation_event.rb +35 -0
  114. data/lib/selenium/webdriver/devtools/network_interceptor.rb +173 -0
  115. data/lib/selenium/webdriver/devtools/pinned_script.rb +57 -0
  116. data/lib/selenium/webdriver/devtools/request.rb +65 -0
  117. data/lib/selenium/webdriver/devtools/response.rb +64 -0
  118. data/lib/selenium/webdriver/devtools.rb +96 -0
  119. data/lib/selenium/webdriver/edge/driver.rb +11 -27
  120. data/lib/selenium/webdriver/edge/features.rb +48 -0
  121. data/lib/selenium/webdriver/edge/options.rb +18 -43
  122. data/lib/selenium/webdriver/edge/profile.rb +33 -0
  123. data/lib/selenium/webdriver/edge/service.rb +8 -23
  124. data/lib/selenium/webdriver/edge.rb +11 -16
  125. data/lib/selenium/webdriver/firefox/driver.rb +38 -19
  126. data/lib/selenium/webdriver/firefox/extension.rb +8 -0
  127. data/lib/selenium/webdriver/firefox/features.rb +70 -0
  128. data/lib/selenium/webdriver/firefox/options.rb +73 -61
  129. data/lib/selenium/webdriver/firefox/profile.rb +20 -72
  130. data/lib/selenium/webdriver/firefox/service.rb +3 -25
  131. data/lib/selenium/webdriver/firefox/util.rb +1 -1
  132. data/lib/selenium/webdriver/firefox.rb +17 -28
  133. data/lib/selenium/webdriver/ie/driver.rb +5 -45
  134. data/lib/selenium/webdriver/ie/features.rb +34 -0
  135. data/lib/selenium/webdriver/ie/options.rb +14 -44
  136. data/lib/selenium/webdriver/ie/service.rb +3 -27
  137. data/lib/selenium/webdriver/ie.rb +4 -16
  138. data/lib/selenium/webdriver/remote/bridge/commands.rb +164 -0
  139. data/lib/selenium/webdriver/remote/bridge.rb +574 -88
  140. data/lib/selenium/webdriver/remote/capabilities.rb +144 -158
  141. data/lib/selenium/webdriver/remote/driver.rb +45 -14
  142. data/lib/selenium/webdriver/remote/features.rb +75 -0
  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 +24 -33
  146. data/lib/selenium/webdriver/remote/response.rb +17 -49
  147. data/lib/selenium/webdriver/remote/server_error.rb +1 -1
  148. data/lib/selenium/webdriver/remote.rb +15 -12
  149. data/lib/selenium/webdriver/safari/driver.rb +7 -29
  150. data/lib/selenium/webdriver/safari/{bridge.rb → features.rb} +7 -5
  151. data/lib/selenium/webdriver/safari/options.rb +12 -27
  152. data/lib/selenium/webdriver/safari/service.rb +13 -11
  153. data/lib/selenium/webdriver/safari.rb +14 -20
  154. data/lib/selenium/webdriver/support/block_event_listener.rb +1 -1
  155. data/lib/selenium/webdriver/support/color.rb +24 -24
  156. data/lib/selenium/webdriver/support/event_firing_bridge.rb +5 -5
  157. data/lib/selenium/webdriver/support/guards/guard.rb +90 -0
  158. data/lib/selenium/webdriver/{firefox/marionette/bridge.rb → support/guards/guard_condition.rb} +21 -20
  159. data/lib/selenium/webdriver/support/guards.rb +95 -0
  160. data/lib/selenium/webdriver/support/relative_locator.rb +50 -0
  161. data/lib/selenium/webdriver/support/select.rb +6 -4
  162. data/lib/selenium/webdriver/support.rb +1 -0
  163. data/lib/selenium/webdriver/version.rb +1 -1
  164. data/lib/selenium/webdriver.rb +19 -17
  165. data/selenium-webdriver.gemspec +36 -18
  166. metadata +161 -91
  167. data/lib/selenium/webdriver/common/bridge_helper.rb +0 -82
  168. data/lib/selenium/webdriver/common/driver_extensions/has_network_connection.rb +0 -58
  169. data/lib/selenium/webdriver/common/driver_extensions/takes_screenshot.rb +0 -64
  170. data/lib/selenium/webdriver/common/mouse.rb +0 -89
  171. data/lib/selenium/webdriver/common/touch_action_builder.rb +0 -78
  172. data/lib/selenium/webdriver/common/touch_screen.rb +0 -123
  173. data/lib/selenium/webdriver/common/w3c_action_builder.rb +0 -212
  174. data/lib/selenium/webdriver/edge/bridge.rb +0 -76
  175. data/lib/selenium/webdriver/firefox/binary.rb +0 -187
  176. data/lib/selenium/webdriver/firefox/extension/prefs.json +0 -69
  177. data/lib/selenium/webdriver/firefox/extension/webdriver.xpi +0 -0
  178. data/lib/selenium/webdriver/firefox/launcher.rb +0 -111
  179. data/lib/selenium/webdriver/firefox/legacy/driver.rb +0 -83
  180. data/lib/selenium/webdriver/firefox/marionette/driver.rb +0 -90
  181. data/lib/selenium/webdriver/firefox/native/linux/amd64/x_ignore_nofocus.so +0 -0
  182. data/lib/selenium/webdriver/firefox/native/linux/x86/x_ignore_nofocus.so +0 -0
  183. data/lib/selenium/webdriver/remote/http/persistent.rb +0 -60
  184. data/lib/selenium/webdriver/remote/oss/bridge.rb +0 -594
  185. data/lib/selenium/webdriver/remote/oss/commands.rb +0 -223
  186. data/lib/selenium/webdriver/remote/w3c/bridge.rb +0 -605
  187. data/lib/selenium/webdriver/remote/w3c/capabilities.rb +0 -310
  188. data/lib/selenium/webdriver/remote/w3c/commands.rb +0 -157
@@ -19,346 +19,234 @@
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 [Boolean] deprecated_async Whether to perform the actions asynchronously per device.
37
+ # Defaults to false for backwards compatibility.
38
+ # @param [Array<Selenium::WebDriver::Interactions::InputDevices>] devices list of valid sources of input.
39
+ # @param [Boolean] async Whether to perform the actions asynchronously per device.
40
+ # @return [ActionBuilder] A self reference.
41
41
  #
42
42
 
43
- def initialize(mouse, keyboard)
44
- @devices = {
45
- mouse: mouse,
46
- keyboard: keyboard
47
- }
43
+ def initialize(bridge, devices: [], async: false, duration: 250)
44
+ @bridge = bridge
45
+ @duration = duration
46
+ @async = async
47
+ @devices = []
48
48
 
49
- @actions = []
49
+ Array(devices).each { |device| add_input(device) }
50
50
  end
51
51
 
52
52
  #
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.
53
+ # Adds a PointerInput device of the given kind
57
54
  #
58
- # Equivalent to:
59
- # driver.action.click(element).send_keys(key)
60
- # # or
61
- # driver.action.click.send_keys(key)
55
+ # @example Add a touch pointer input device
62
56
  #
63
- # @example Press a key
57
+ # builder = device.action
58
+ # builder.add_pointer_input('touch', :touch)
64
59
  #
65
- # driver.action.key_down(:control).perform
60
+ # @param [String] name name for the device
61
+ # @param [Symbol] kind kind of pointer device to create
62
+ # @return [Interactions::PointerInput] The pointer input added
66
63
  #
67
- # @example Press a key on an element
68
64
  #
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
65
 
83
- @actions << [:keyboard, :press, args]
84
- self
66
+ def add_pointer_input(kind, name)
67
+ add_input(Interactions.pointer(kind, name: name))
85
68
  end
86
69
 
87
70
  #
88
- # Performs a modifier key release.
89
- # Releasing a non-depressed modifier key will yield undefined behaviour.
71
+ # Adds a KeyInput device
90
72
  #
91
- # @example Release a key
73
+ # @example Add a key input device
92
74
  #
93
- # driver.action.key_up(:shift).perform
75
+ # builder = device.action
76
+ # builder.add_key_input('keyboard2')
94
77
  #
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
78
+ # @param [String] name name for the device
79
+ # @return [Interactions::KeyInput] The key input added
99
80
  #
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
81
 
112
- @actions << [:keyboard, :release, args]
113
- self
82
+ def add_key_input(name)
83
+ add_input(Interactions.key(name))
114
84
  end
115
85
 
116
86
  #
117
- # Sends keys to the active element. This differs from calling
118
- # Element#send_keys(keys) on the active element in two ways:
87
+ # Adds a WheelInput device
119
88
  #
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.
89
+ # @example Add a wheel input device
122
90
  #
123
- # @example Send the text "help" to an element
91
+ # builder = device.action
92
+ # builder.add_wheel_input('wheel2')
124
93
  #
125
- # el = driver.find_element(id: "some_id")
126
- # driver.action.send_keys(el, "help").perform
94
+ # @param [String] name name for the device
95
+ # @return [Interactions::WheelInput] The wheel input added
127
96
  #
128
- # @example Send the text "help" to the currently focused element
97
+
98
+ def add_wheel_input(name)
99
+ add_input(Interactions.wheel(name))
100
+ end
101
+
129
102
  #
130
- # driver.action.send_keys("help").perform
103
+ # Retrieves the input device for the given name or type
131
104
  #
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
105
+ # @param [String] name name of the input device
106
+ # @param [String] type name of the input device
107
+ # @return [Selenium::WebDriver::Interactions::InputDevice] input device with given name or type
138
108
  #
139
109
 
140
- def send_keys(*args)
141
- @actions << [:mouse, :click, [args.shift]] if args.first.is_a? Element
110
+ def device(name: nil, type: nil)
111
+ input = @devices.find { |device| (device.name == name.to_s || name.nil?) && (device.type == type || type.nil?) }
142
112
 
143
- @actions << [:keyboard, :send_keys, args]
144
- self
113
+ raise(ArgumentError, "Can not find device: #{name}") if name && input.nil?
114
+
115
+ input
145
116
  end
146
117
 
147
118
  #
148
- # Clicks (without releasing) in the middle of the given element. This is
149
- # equivalent to:
119
+ # Retrieves the current PointerInput devices
150
120
  #
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.
121
+ # @return [Array] array of current PointerInput devices
160
122
  #
161
123
 
162
- def click_and_hold(element = nil)
163
- @actions << [:mouse, :down, [element]]
164
- self
124
+ def pointer_inputs
125
+ @devices.select { |device| device.type == Interactions::POINTER }
165
126
  end
166
127
 
167
128
  #
168
- # Releases the depressed left mouse button at the current mouse location.
129
+ # Retrieves the current KeyInput device
169
130
  #
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.
131
+ # @return [Selenium::WebDriver::Interactions::InputDevice] current KeyInput device
176
132
  #
177
133
 
178
- def release(element = nil)
179
- @actions << [:mouse, :up, [element]]
180
- self
134
+ def key_inputs
135
+ @devices.select { |device| device.type == Interactions::KEY }
181
136
  end
182
137
 
183
138
  #
184
- # Clicks in the middle of the given element. Equivalent to:
139
+ # Retrieves the current WheelInput device
185
140
  #
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.
141
+ # @return [Selenium::WebDriver::Interactions::InputDevice] current WheelInput devices
201
142
  #
202
143
 
203
- def click(element = nil)
204
- @actions << [:mouse, :click, [element]]
205
- self
144
+ def wheel_inputs
145
+ @devices.select { |device| device.type == Interactions::WHEEL }
206
146
  end
207
147
 
208
148
  #
209
- # Performs a double-click at middle of the given element. Equivalent to:
210
- #
211
- # driver.action.move_to(element).double_click
149
+ # Creates a pause for the given device of the given duration. If no duration is given, the pause will only wait
150
+ # for all actions to complete in that tick.
212
151
  #
213
- # @example Double click an element
152
+ # @example Send keys to an element
214
153
  #
215
- # el = driver.find_element(id: "some_id")
216
- # driver.action.double_click(el).perform
154
+ # action_builder = driver.action
155
+ # keyboard = action_builder.key_input
156
+ # el = driver.find_element(id: "some_id")
157
+ # driver.action.click(el).pause(keyboard).pause(keyboard).pause(keyboard).send_keys('keys').perform
217
158
  #
218
- # @param [Selenium::WebDriver::Element] element An optional element to move to.
159
+ # @param [InputDevice] device Input device to pause
160
+ # @param [Float] duration Duration to pause
219
161
  # @return [ActionBuilder] A self reference.
220
162
  #
221
163
 
222
- def double_click(element = nil)
223
- @actions << [:mouse, :double_click, [element]]
164
+ def pause(device: nil, duration: 0)
165
+ device ||= pointer_input
166
+ device.create_pause(duration)
224
167
  self
225
168
  end
226
169
 
227
170
  #
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
171
+ # Creates multiple pauses for the given device of the given duration.
238
172
  #
239
- # @example
173
+ # @example Send keys to an element
240
174
  #
175
+ # action_builder = driver.action
176
+ # keyboard = action_builder.key_input
241
177
  # el = driver.find_element(id: "some_id")
242
- # driver.action.move_to(el, 100, 100).perform
178
+ # driver.action.click(el).pauses(keyboard, 3).send_keys('keys').perform
243
179
  #
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.
180
+ # @param [InputDevice] device Input device to pause
181
+ # @param [Integer] number of pauses to add for the device
182
+ # @param [Float] duration Duration to pause
249
183
  # @return [ActionBuilder] A self reference.
250
184
  #
251
185
 
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
186
+ def pauses(device: nil, number: nil, duration: 0)
187
+ number ||= 2
188
+ device ||= pointer_input
189
+ duration ||= 0
258
190
 
191
+ number.times { device.create_pause(duration) }
259
192
  self
260
193
  end
261
194
 
262
195
  #
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.
196
+ # Executes the actions added to the builder.
279
197
  #
280
198
 
281
- def move_by(right_by, down_by)
282
- @actions << [:mouse, :move_by, [Integer(right_by), Integer(down_by)]]
283
- self
199
+ def perform
200
+ @bridge.send_actions @devices.filter_map(&:encode)
201
+ clear_all_actions
202
+ nil
284
203
  end
285
204
 
286
205
  #
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.
206
+ # Clears all actions from the builder.
297
207
  #
298
208
 
299
- def context_click(element = nil)
300
- @actions << [:mouse, :context_click, [element]]
301
- self
209
+ def clear_all_actions
210
+ @devices.each(&:clear_actions)
302
211
  end
303
212
 
304
213
  #
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.
214
+ # Releases all action states from the browser.
319
215
  #
320
216
 
321
- def drag_and_drop(source, target)
322
- click_and_hold source
323
- move_to target
324
- release
325
-
326
- self
217
+ def release_actions
218
+ @bridge.release_actions
327
219
  end
328
220
 
221
+ private
222
+
329
223
  #
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
224
+ # Adds pauses for all devices but the given devices
337
225
  #
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.
226
+ # @param [Array[InputDevice]] action_devices Array of Input Devices performing an action in this tick.
342
227
  #
343
228
 
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
229
+ def tick(*action_devices)
230
+ return if @async
348
231
 
349
- self
232
+ @devices.each { |device| device.create_pause unless action_devices.include? device }
350
233
  end
351
234
 
352
235
  #
353
- # Executes the actions added to the builder.
236
+ # Adds an InputDevice
354
237
  #
355
238
 
356
- def perform
357
- @actions.each do |receiver, method, args|
358
- @devices.fetch(receiver).__send__(method, *args)
359
- end
239
+ def add_input(device)
240
+ device = Interactions.send(device) if device.is_a?(Symbol) && Interactions.respond_to?(device)
360
241
 
361
- nil
242
+ raise TypeError, "#{device.inspect} is not a valid InputDevice" unless device.is_a?(Interactions::InputDevice)
243
+
244
+ unless @async
245
+ max_device = @devices.max { |a, b| a.actions.length <=> b.actions.length }
246
+ pauses(device: device, number: max_device.actions.length) if max_device
247
+ end
248
+ @devices << device
249
+ device
362
250
  end
363
251
  end # ActionBuilder
364
252
  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 false 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 false 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