selenium-webdriver 3.142.7 → 4.16.0

Sign up to get free protection for your applications and to get access to all the features.
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