1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
|
# frozen_string_literal: true
# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The SFC licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
module Selenium
module WebDriver
class ActionBuilder
include KeyActions # Actions specific to key inputs
include PointerActions # Actions specific to pointer inputs
include WheelActions # Actions specific to wheel inputs
attr_reader :devices
#
# Initialize a W3C Action Builder. Differs from previous by requiring a bridge and allowing asynchronous actions.
# The W3C implementation allows asynchronous actions per device. e.g. A key can be pressed at the same time that
# the mouse is moving. Keep in mind that pauses must be added for other devices in order to line up the actions
# correctly when using asynchronous.
#
# @param [Selenium::WebDriver::Remote::Bridge] bridge the bridge for the current driver instance.
# @param [Array<Selenium::WebDriver::Interactions::InputDevices>] devices list of valid sources of input.
# @param [Boolean] async Whether to perform the actions asynchronously per device.
# @return [ActionBuilder] A self reference.
#
def initialize(bridge, devices: [], async: false, duration: 250)
@bridge = bridge
@duration = duration
@async = async
@devices = []
Array(devices).each { |device| add_input(device) }
end
#
# Adds a PointerInput device of the given kind
#
# @example Add a touch pointer input device
#
# builder = device.action
# builder.add_pointer_input('touch', :touch)
#
# @param [String] name name for the device
# @param [Symbol] kind kind of pointer device to create
# @return [Interactions::PointerInput] The pointer input added
#
#
def add_pointer_input(kind, name)
add_input(Interactions.pointer(kind, name: name))
end
#
# Adds a KeyInput device
#
# @example Add a key input device
#
# builder = device.action
# builder.add_key_input('keyboard2')
#
# @param [String] name name for the device
# @return [Interactions::KeyInput] The key input added
#
def add_key_input(name)
add_input(Interactions.key(name))
end
#
# Adds a WheelInput device
#
# @example Add a wheel input device
#
# builder = device.action
# builder.add_wheel_input('wheel2')
#
# @param [String] name name for the device
# @return [Interactions::WheelInput] The wheel input added
#
def add_wheel_input(name)
add_input(Interactions.wheel(name))
end
#
# Retrieves the input device for the given name or type
#
# @param [String] name name of the input device
# @param [String] type name of the input device
# @return [Selenium::WebDriver::Interactions::InputDevice] input device with given name or type
#
def device(name: nil, type: nil)
input = @devices.find { |device| (device.name == name.to_s || name.nil?) && (device.type == type || type.nil?) }
raise(ArgumentError, "Can not find device: #{name}") if name && input.nil?
input
end
#
# Retrieves the current PointerInput devices
#
# @return [Array] array of current PointerInput devices
#
def pointer_inputs
@devices.select { |device| device.type == Interactions::POINTER }
end
#
# Retrieves the current KeyInput device
#
# @return [Selenium::WebDriver::Interactions::InputDevice] current KeyInput device
#
def key_inputs
@devices.select { |device| device.type == Interactions::KEY }
end
#
# Retrieves the current WheelInput device
#
# @return [Selenium::WebDriver::Interactions::InputDevice] current WheelInput devices
#
def wheel_inputs
@devices.select { |device| device.type == Interactions::WHEEL }
end
#
# Creates a pause for the given device of the given duration. If no duration is given, the pause will only wait
# for all actions to complete in that tick.
#
# @example Send keys to an element
#
# action_builder = driver.action
# keyboard = action_builder.key_input
# el = driver.find_element(id: "some_id")
# driver.action.click(el).pause(keyboard).pause(keyboard).pause(keyboard).send_keys('keys').perform
#
# @param [InputDevice] device Input device to pause
# @param [Float] duration Duration to pause
# @return [ActionBuilder] A self reference.
#
def pause(device: nil, duration: 0)
device ||= pointer_input
device.create_pause(duration)
self
end
#
# Creates multiple pauses for the given device of the given duration.
#
# @example Send keys to an element
#
# action_builder = driver.action
# keyboard = action_builder.key_input
# el = driver.find_element(id: "some_id")
# driver.action.click(el).pauses(keyboard, 3).send_keys('keys').perform
#
# @param [InputDevice] device Input device to pause
# @param [Integer] number of pauses to add for the device
# @param [Float] duration Duration to pause
# @return [ActionBuilder] A self reference.
#
def pauses(device: nil, number: nil, duration: 0)
number ||= 2
device ||= pointer_input
duration ||= 0
number.times { device.create_pause(duration) }
self
end
#
# Executes the actions added to the builder.
#
def perform
@bridge.send_actions @devices.filter_map(&:encode)
clear_all_actions
nil
end
#
# Clears all actions from the builder.
#
def clear_all_actions
@devices.each(&:clear_actions)
end
#
# Releases all action states from the browser.
#
def release_actions
@bridge.release_actions
end
private
#
# Adds pauses for all devices but the given devices
#
# @param [Array[InputDevice]] action_devices Array of Input Devices performing an action in this tick.
#
def tick(*action_devices)
return if @async
@devices.each { |device| device.create_pause unless action_devices.include? device }
end
#
# Adds an InputDevice
#
def add_input(device)
device = Interactions.send(device) if device.is_a?(Symbol) && Interactions.respond_to?(device)
raise TypeError, "#{device.inspect} is not a valid InputDevice" unless device.is_a?(Interactions::InputDevice)
unless @async
max_device = @devices.max { |a, b| a.actions.length <=> b.actions.length }
pauses(device: device, number: max_device.actions.length) if max_device
end
@devices << device
device
end
end # ActionBuilder
end # WebDriver
end # Selenium
|