Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/rb/lib/selenium/webdriver/common/action_builder.rb
1865 views
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
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
29
#
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 [Array<Selenium::WebDriver::Interactions::InputDevices>] devices list of valid sources of input.
37
# @param [Boolean] async Whether to perform the actions asynchronously per device.
38
# @return [ActionBuilder] A self reference.
39
#
40
41
def initialize(bridge, devices: [], async: false, duration: 250)
42
@bridge = bridge
43
@duration = duration
44
@async = async
45
@devices = []
46
47
Array(devices).each { |device| add_input(device) }
48
end
49
50
#
51
# Adds a PointerInput device of the given kind
52
#
53
# @example Add a touch pointer input device
54
#
55
# builder = device.action
56
# builder.add_pointer_input('touch', :touch)
57
#
58
# @param [String] name name for the device
59
# @param [Symbol] kind kind of pointer device to create
60
# @return [Interactions::PointerInput] The pointer input added
61
#
62
#
63
64
def add_pointer_input(kind, name)
65
add_input(Interactions.pointer(kind, name: name))
66
end
67
68
#
69
# Adds a KeyInput device
70
#
71
# @example Add a key input device
72
#
73
# builder = device.action
74
# builder.add_key_input('keyboard2')
75
#
76
# @param [String] name name for the device
77
# @return [Interactions::KeyInput] The key input added
78
#
79
80
def add_key_input(name)
81
add_input(Interactions.key(name))
82
end
83
84
#
85
# Adds a WheelInput device
86
#
87
# @example Add a wheel input device
88
#
89
# builder = device.action
90
# builder.add_wheel_input('wheel2')
91
#
92
# @param [String] name name for the device
93
# @return [Interactions::WheelInput] The wheel input added
94
#
95
96
def add_wheel_input(name)
97
add_input(Interactions.wheel(name))
98
end
99
100
#
101
# Retrieves the input device for the given name or type
102
#
103
# @param [String] name name of the input device
104
# @param [String] type name of the input device
105
# @return [Selenium::WebDriver::Interactions::InputDevice] input device with given name or type
106
#
107
108
def device(name: nil, type: nil)
109
input = @devices.find { |device| (device.name == name.to_s || name.nil?) && (device.type == type || type.nil?) }
110
111
raise(ArgumentError, "Can not find device: #{name}") if name && input.nil?
112
113
input
114
end
115
116
#
117
# Retrieves the current PointerInput devices
118
#
119
# @return [Array] array of current PointerInput devices
120
#
121
122
def pointer_inputs
123
@devices.select { |device| device.type == Interactions::POINTER }
124
end
125
126
#
127
# Retrieves the current KeyInput device
128
#
129
# @return [Selenium::WebDriver::Interactions::InputDevice] current KeyInput device
130
#
131
132
def key_inputs
133
@devices.select { |device| device.type == Interactions::KEY }
134
end
135
136
#
137
# Retrieves the current WheelInput device
138
#
139
# @return [Selenium::WebDriver::Interactions::InputDevice] current WheelInput devices
140
#
141
142
def wheel_inputs
143
@devices.select { |device| device.type == Interactions::WHEEL }
144
end
145
146
#
147
# Creates a pause for the given device of the given duration. If no duration is given, the pause will only wait
148
# for all actions to complete in that tick.
149
#
150
# @example Send keys to an element
151
#
152
# action_builder = driver.action
153
# keyboard = action_builder.key_input
154
# el = driver.find_element(id: "some_id")
155
# driver.action.click(el).pause(keyboard).pause(keyboard).pause(keyboard).send_keys('keys').perform
156
#
157
# @param [InputDevice] device Input device to pause
158
# @param [Float] duration Duration to pause
159
# @return [ActionBuilder] A self reference.
160
#
161
162
def pause(device: nil, duration: 0)
163
device ||= pointer_input
164
device.create_pause(duration)
165
self
166
end
167
168
#
169
# Creates multiple pauses for the given device of the given duration.
170
#
171
# @example Send keys to an element
172
#
173
# action_builder = driver.action
174
# keyboard = action_builder.key_input
175
# el = driver.find_element(id: "some_id")
176
# driver.action.click(el).pauses(keyboard, 3).send_keys('keys').perform
177
#
178
# @param [InputDevice] device Input device to pause
179
# @param [Integer] number of pauses to add for the device
180
# @param [Float] duration Duration to pause
181
# @return [ActionBuilder] A self reference.
182
#
183
184
def pauses(device: nil, number: nil, duration: 0)
185
number ||= 2
186
device ||= pointer_input
187
duration ||= 0
188
189
number.times { device.create_pause(duration) }
190
self
191
end
192
193
#
194
# Executes the actions added to the builder.
195
#
196
197
def perform
198
@bridge.send_actions @devices.filter_map(&:encode)
199
clear_all_actions
200
nil
201
end
202
203
#
204
# Clears all actions from the builder.
205
#
206
207
def clear_all_actions
208
@devices.each(&:clear_actions)
209
end
210
211
#
212
# Releases all action states from the browser.
213
#
214
215
def release_actions
216
@bridge.release_actions
217
end
218
219
private
220
221
#
222
# Adds pauses for all devices but the given devices
223
#
224
# @param [Array[InputDevice]] action_devices Array of Input Devices performing an action in this tick.
225
#
226
227
def tick(*action_devices)
228
return if @async
229
230
@devices.each { |device| device.create_pause unless action_devices.include? device }
231
end
232
233
#
234
# Adds an InputDevice
235
#
236
237
def add_input(device)
238
device = Interactions.send(device) if device.is_a?(Symbol) && Interactions.respond_to?(device)
239
240
raise TypeError, "#{device.inspect} is not a valid InputDevice" unless device.is_a?(Interactions::InputDevice)
241
242
unless @async
243
max_device = @devices.max { |a, b| a.actions.length <=> b.actions.length }
244
pauses(device: device, number: max_device.actions.length) if max_device
245
end
246
@devices << device
247
device
248
end
249
end # ActionBuilder
250
end # WebDriver
251
end # Selenium
252
253