# 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 target 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