File: //lib/ruby/vendor_ruby/selenium/webdriver/common/action_builder.rb
# 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
#
# The ActionBuilder provides the user a way to set up and perform
# complex user interactions.
#
# This class should not be instantiated directly, but is created by Driver#action
#
# @example
#
# driver.action.key_down(:shift).
# click(element).
# click(second_element).
# key_up(:shift).
# drag_and_drop(element, third_element).
# perform
#
class ActionBuilder
#
# @api private
#
def initialize(mouse, keyboard)
@devices = {
mouse: mouse,
keyboard: keyboard
}
@actions = []
end
#
# Performs a modifier key press. Does not release
# the modifier key - subsequent interactions may assume it's kept pressed.
# Note that the modifier key is never released implicitly - either
# #key_up(key) or #send_keys(:null) must be called to release the modifier.
#
# Equivalent to:
# driver.action.click(element).send_keys(key)
# # or
# driver.action.click.send_keys(key)
#
# @example Press a key
#
# driver.action.key_down(:control).perform
#
# @example Press a key on an element
#
# el = driver.find_element(id: "some_id")
# driver.action.key_down(el, :shift).perform
#
# @overload key_down(key)
# @param [:shift, :alt, :control, :command, :meta] key The modifier key to press
# @overload key_down(element, key)
# @param [Element] element An optional element to move to first
# @param [:shift, :alt, :control, :command, :meta] key The modifier key to press
# @raise [ArgumentError] if the given key is not a modifier
# @return [ActionBuilder] A self reference
def key_down(*args)
@actions << [:mouse, :click, [args.shift]] if args.first.is_a? Element
@actions << [:keyboard, :press, args]
self
end
#
# Performs a modifier key release.
# Releasing a non-depressed modifier key will yield undefined behaviour.
#
# @example Release a key
#
# driver.action.key_up(:shift).perform
#
# @example Release a key from an element
#
# el = driver.find_element(id: "some_id")
# driver.action.key_up(el, :alt).perform
#
# @overload key_up(key)
# @param [:shift, :alt, :control, :command, :meta] key The modifier key to release
# @overload key_up(element, key)
# @param [Element] element An optional element to move to first
# @param [:shift, :alt, :control, :command, :meta] key The modifier key to release
# @raise [ArgumentError] if the given key is not a modifier
# @return [ActionBuilder] A self reference
#
def key_up(*args)
@actions << [:mouse, :click, [args.shift]] if args.first.is_a? Element
@actions << [:keyboard, :release, args]
self
end
#
# Sends keys to the active element. This differs from calling
# Element#send_keys(keys) on the active element in two ways:
#
# * The modifier keys included in this call are not released.
# * There is no attempt to re-focus the element - so send_keys(:tab) for switching elements should work.
#
# @example Send the text "help" to an element
#
# el = driver.find_element(id: "some_id")
# driver.action.send_keys(el, "help").perform
#
# @example Send the text "help" to the currently focused element
#
# driver.action.send_keys("help").perform
#
# @overload send_keys(keys)
# @param [Array, Symbol, String] keys The key(s) to press and release
# @overload send_keys(element, keys)
# @param [Element] element An optional element to move to first
# @param [Array, Symbol, String] keys The key(s) to press and release
# @return [ActionBuilder] A self reference
#
def send_keys(*args)
@actions << [:mouse, :click, [args.shift]] if args.first.is_a? Element
@actions << [:keyboard, :send_keys, args]
self
end
#
# Clicks (without releasing) in the middle of the given element. This is
# equivalent to:
#
# driver.action.move_to(element).click_and_hold
#
# @example Clicking and holding on some element
#
# el = driver.find_element(id: "some_id")
# driver.action.click_and_hold(el).perform
#
# @param [Element] element the element to move to and click.
# @return [ActionBuilder] A self reference.
#
def click_and_hold(element = nil)
@actions << [:mouse, :down, [element]]
self
end
#
# Releases the depressed left mouse button at the current mouse location.
#
# @example Releasing an element after clicking and holding it
#
# el = driver.find_element(id: "some_id")
# driver.action.click_and_hold(el).release.perform
#
# @return [ActionBuilder] A self reference.
#
def release(element = nil)
@actions << [:mouse, :up, [element]]
self
end
#
# Clicks in the middle of the given element. Equivalent to:
#
# driver.action.move_to(element).click
#
# When no element is passed, the current mouse position will be clicked.
#
# @example Clicking on an element
#
# el = driver.find_element(id: "some_id")
# driver.action.click(el).perform
#
# @example Clicking at the current mouse position
#
# driver.action.click.perform
#
# @param [Selenium::WebDriver::Element] element An optional element to click.
# @return [ActionBuilder] A self reference.
#
def click(element = nil)
@actions << [:mouse, :click, [element]]
self
end
#
# Performs a double-click at middle of the given element. Equivalent to:
#
# driver.action.move_to(element).double_click
#
# @example Double click an element
#
# el = driver.find_element(id: "some_id")
# driver.action.double_click(el).perform
#
# @param [Selenium::WebDriver::Element] element An optional element to move to.
# @return [ActionBuilder] A self reference.
#
def double_click(element = nil)
@actions << [:mouse, :double_click, [element]]
self
end
#
# Moves the mouse to the middle of the given element. The element is scrolled into
# view and its location is calculated using getBoundingClientRect. Then the
# mouse is moved to optional offset coordinates from the element.
#
# Note that when using offsets, both coordinates need to be passed.
#
# @example Scroll element into view and move the mouse to it
#
# el = driver.find_element(id: "some_id")
# driver.action.move_to(el).perform
#
# @example
#
# el = driver.find_element(id: "some_id")
# driver.action.move_to(el, 100, 100).perform
#
# @param [Selenium::WebDriver::Element] element to move to.
# @param [Integer] right_by Optional offset from the top-left corner. A negative value means
# coordinates right from the element.
# @param [Integer] down_by Optional offset from the top-left corner. A negative value means
# coordinates above the element.
# @return [ActionBuilder] A self reference.
#
def move_to(element, right_by = nil, down_by = nil)
@actions << if right_by && down_by
[:mouse, :move_to, [element, Integer(right_by), Integer(down_by)]]
else
[:mouse, :move_to, [element]]
end
self
end
#
# Moves the mouse from its current position (or 0,0) by the given offset.
# If the coordinates provided are outside the viewport (the mouse will
# end up outside the browser window) then the viewport is scrolled to
# match.
#
# @example Move the mouse to a certain offset from its current position
#
# driver.action.move_by(100, 100).perform
#
# @param [Integer] right_by horizontal offset. A negative value means moving the
# mouse left.
# @param [Integer] down_by vertical offset. A negative value means moving the mouse
# up.
# @return [ActionBuilder] A self reference.
# @raise [MoveTargetOutOfBoundsError] if the provided offset is outside
# the document's boundaries.
#
def move_by(right_by, down_by)
@actions << [:mouse, :move_by, [Integer(right_by), Integer(down_by)]]
self
end
#
# Performs a context-click at middle of the given element. First performs
# a move_to to the location of the element.
#
# @example Context-click at middle of given element
#
# el = driver.find_element(id: "some_id")
# driver.action.context_click(el).perform
#
# @param [Selenium::WebDriver::Element] element An element to context click.
# @return [ActionBuilder] A self reference.
#
def context_click(element = nil)
@actions << [:mouse, :context_click, [element]]
self
end
#
# A convenience method that performs click-and-hold at the location of the
# source element, moves to the location of the target element, then
# releases the mouse.
#
# @example Drag and drop one element onto another
#
# el1 = driver.find_element(id: "some_id1")
# el2 = driver.find_element(id: "some_id2")
# driver.action.drag_and_drop(el1, el2).perform
#
# @param [Selenium::WebDriver::Element] source element to emulate button down at.
# @param [Selenium::WebDriver::Element] target element to move to and release the
# mouse at.
# @return [ActionBuilder] A self reference.
#
def drag_and_drop(source, target)
click_and_hold source
move_to target
release
self
end
#
# A convenience method that performs click-and-hold at the location of
# the source element, moves by a given offset, then releases the mouse.
#
# @example Drag and drop an element by offset
#
# el = driver.find_element(id: "some_id1")
# driver.action.drag_and_drop_by(el, 100, 100).perform
#
# @param [Selenium::WebDriver::Element] source Element to emulate button down at.
# @param [Integer] right_by horizontal move offset.
# @param [Integer] down_by vertical move offset.
# @return [ActionBuilder] A self reference.
#
def drag_and_drop_by(source, right_by, down_by)
click_and_hold source
move_by right_by, down_by
release
self
end
#
# Executes the actions added to the builder.
#
def perform
@actions.each do |receiver, method, args|
@devices.fetch(receiver).__send__(method, *args)
end
nil
end
end # ActionBuilder
end # WebDriver
end # Selenium