selenuim之ActionChains类--模拟鼠标操作方法

首先通过源代码了解ActionChains类提供的模拟鼠标操作方法

源码路径: D:\Python3.7\Lib\site-packages\selenium\webdriver\common\action_chains.py

源码:

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

"""
The ActionChains implementation,
"""

import time

from selenium.webdriver.remote.command import Command

from .utils import keys_to_typing
from .actions.action_builder import ActionBuilder


class ActionChains(object):
    """
    ActionChains are a way to automate low level interactions such as
    mouse movements, mouse button actions, key press, and context menu interactions.
    This is useful for doing more complex actions like hover over and drag and drop.

    Generate user actions.
       When you call methods for actions on the ActionChains object,
       the actions are stored in a queue in the ActionChains object.
       When you call perform(), the events are fired in the order they
       are queued up.

    ActionChains can be used in a chain pattern::

        menu = driver.find_element_by_css_selector(".nav")
        hidden_submenu = driver.find_element_by_css_selector(".nav #submenu1")

        ActionChains(driver).move_to_element(menu).click(hidden_submenu).perform()

    Or actions can be queued up one by one, then performed.::

        menu = driver.find_element_by_css_selector(".nav")
        hidden_submenu = driver.find_element_by_css_selector(".nav #submenu1")

        actions = ActionChains(driver)
        actions.move_to_element(menu)
        actions.click(hidden_submenu)
        actions.perform()

    Either way, the actions are performed in the order they are called, one after
    another.
    """

    def __init__(self, driver):
        """
        Creates a new ActionChains.

        :Args:
         - driver: The WebDriver instance which performs user actions.
        """
        self._driver = driver
        self._actions = []
        if self._driver.w3c:
            self.w3c_actions = ActionBuilder(driver)

    def perform(self):
        """
        Performs all stored actions.
        """
        if self._driver.w3c:
            self.w3c_actions.perform()
        else:
            for action in self._actions:
                action()

    def reset_actions(self):
        """
            Clears actions that are already stored locally and on the remote end
        """
        if self._driver.w3c:
            self.w3c_actions.clear_actions()
        self._actions = []

    def click(self, on_element=None):
        """
        Clicks an element.

        :Args:
         - on_element: The element to click.
           If None, clicks on current mouse position.
        """
        if on_element:
            self.move_to_element(on_element)
        if self._driver.w3c:
            self.w3c_actions.pointer_action.click()
            self.w3c_actions.key_action.pause()
            self.w3c_actions.key_action.pause()
        else:
            self._actions.append(lambda: self._driver.execute(
                                 Command.CLICK, {'button': 0}))
        return self

    def click_and_hold(self, on_element=None):
        """
        Holds down the left mouse button on an element.

        :Args:
         - on_element: The element to mouse down.
           If None, clicks on current mouse position.
        """
        if on_element:
            self.move_to_element(on_element)
        if self._driver.w3c:
            self.w3c_actions.pointer_action.click_and_hold()
            self.w3c_actions.key_action.pause()
        else:
            self._actions.append(lambda: self._driver.execute(
                                 Command.MOUSE_DOWN, {}))
        return self

    def context_click(self, on_element=None):
        """
        Performs a context-click (right click) on an element.

        :Args:
         - on_element: The element to context-click.
           If None, clicks on current mouse position.
        """
        if on_element:
            self.move_to_element(on_element)
        if self._driver.w3c:
            self.w3c_actions.pointer_action.context_click()
            self.w3c_actions.key_action.pause()
            self.w3c_actions.key_action.pause()
        else:
            self._actions.append(lambda: self._driver.execute(
                                 Command.CLICK, {'button': 2}))
        return self

    def double_click(self, on_element=None):
        """
        Double-clicks an element.

        :Args:
         - on_element: The element to double-click.
           If None, clicks on current mouse position.
        """
        if on_element:
            self.move_to_element(on_element)
        if self._driver.w3c:
            self.w3c_actions.pointer_action.double_click()
            for _ in range(4):
                self.w3c_actions.key_action.pause()
        else:
            self._actions.append(lambda: self._driver.execute(
                                 Command.DOUBLE_CLICK, {}))
        return self

    def drag_and_drop(self, source, target):
        """
        Holds down the left mouse button on the source element,
           then moves to the target element and releases the mouse button.

        :Args:
         - source: The element to mouse down.
         - target: The element to mouse up.
        """
        self.click_and_hold(source)
        self.release(target)
        return self

    def drag_and_drop_by_offset(self, source, xoffset, yoffset):
        """
        Holds down the left mouse button on the source element,
           then moves to the target offset and releases the mouse button.

        :Args:
         - source: The element to mouse down.
         - xoffset: X offset to move to.
         - yoffset: Y offset to move to.
        """
        self.click_and_hold(source)
        self.move_by_offset(xoffset, yoffset)
        self.release()
        return self

    def key_down(self, value, element=None):
        """
        Sends a key press only, without releasing it.
           Should only be used with modifier keys (Control, Alt and Shift).

        :Args:
         - value: The modifier key to send. Values are defined in `Keys` class.
         - element: The element to send keys.
           If None, sends a key to current focused element.

        Example, pressing ctrl+c::

            ActionChains(driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()

        """
        if element:
            self.click(element)
        if self._driver.w3c:
            self.w3c_actions.key_action.key_down(value)
            self.w3c_actions.pointer_action.pause()
        else:
            self._actions.append(lambda: self._driver.execute(
                Command.SEND_KEYS_TO_ACTIVE_ELEMENT,
                {"value": keys_to_typing(value)}))
        return self

    def key_up(self, value, element=None):
        """
        Releases a modifier key.

        :Args:
         - value: The modifier key to send. Values are defined in Keys class.
         - element: The element to send keys.
           If None, sends a key to current focused element.

        Example, pressing ctrl+c::

            ActionChains(driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()

        """
        if element:
            self.click(element)
        if self._driver.w3c:
            self.w3c_actions.key_action.key_up(value)
            self.w3c_actions.pointer_action.pause()
        else:
            self._actions.append(lambda: self._driver.execute(
                Command.SEND_KEYS_TO_ACTIVE_ELEMENT,
                {"value": keys_to_typing(value)}))
        return self

    def move_by_offset(self, xoffset, yoffset):
        """
        Moving the mouse to an offset from current mouse position.

        :Args:
         - xoffset: X offset to move to, as a positive or negative integer.
         - yoffset: Y offset to move to, as a positive or negative integer.
        """
        if self._driver.w3c:
            self.w3c_actions.pointer_action.move_by(xoffset, yoffset)
            self.w3c_actions.key_action.pause()
        else:
            self._actions.append(lambda: self._driver.execute(
                Command.MOVE_TO, {
                    'xoffset': int(xoffset),
                    'yoffset': int(yoffset)}))
        return self

    def move_to_element(self, to_element):
        """
        Moving the mouse to the middle of an element.

        :Args:
         - to_element: The WebElement to move to.
        """
        if self._driver.w3c:
            self.w3c_actions.pointer_action.move_to(to_element)
            self.w3c_actions.key_action.pause()
        else:
            self._actions.append(lambda: self._driver.execute(
                                 Command.MOVE_TO, {'element': to_element.id}))
        return self

    def move_to_element_with_offset(self, to_element, xoffset, yoffset):
        """
        Move the mouse by an offset of the specified element.
           Offsets are relative to the top-left corner of the element.

        :Args:
         - to_element: The WebElement to move to.
         - xoffset: X offset to move to.
         - yoffset: Y offset to move to.
        """
        if self._driver.w3c:
            self.w3c_actions.pointer_action.move_to(to_element, xoffset, yoffset)
            self.w3c_actions.key_action.pause()
        else:
            self._actions.append(
                lambda: self._driver.execute(Command.MOVE_TO, {
                    'element': to_element.id,
                    'xoffset': int(xoffset),
                    'yoffset': int(yoffset)}))
        return self

    def pause(self, seconds):
        """ Pause all inputs for the specified duration in seconds """
        if self._driver.w3c:
            self.w3c_actions.pointer_action.pause(seconds)
            self.w3c_actions.key_action.pause(seconds)
        else:
            self._actions.append(lambda: time.sleep(seconds))
        return self

    def release(self, on_element=None):
        """
        Releasing a held mouse button on an element.

        :Args:
         - on_element: The element to mouse up.
           If None, releases on current mouse position.
        """
        if on_element:
            self.move_to_element(on_element)
        if self._driver.w3c:
            self.w3c_actions.pointer_action.release()
            self.w3c_actions.key_action.pause()
        else:
            self._actions.append(lambda: self._driver.execute(Command.MOUSE_UP, {}))
        return self

    def send_keys(self, *keys_to_send):
        """
        Sends keys to current focused element.

        :Args:
         - keys_to_send: The keys to send.  Modifier keys constants can be found in the
           'Keys' class.
        """
        typing = keys_to_typing(keys_to_send)
        if self._driver.w3c:
            for key in typing:
                self.key_down(key)
                self.key_up(key)
        else:
            self._actions.append(lambda: self._driver.execute(
                Command.SEND_KEYS_TO_ACTIVE_ELEMENT, {'value': typing}))
        return self

    def send_keys_to_element(self, element, *keys_to_send):
        """
        Sends keys to an element.

        :Args:
         - element: The element to send keys.
         - keys_to_send: The keys to send.  Modifier keys constants can be found in the
           'Keys' class.
        """
        self.click(element)
        self.send_keys(*keys_to_send)
        return self

    # Context manager so ActionChains can be used in a 'with .. as' statements.
    def __enter__(self):
        return self  # Return created instance of self.

    def __exit__(self, _type, _value, _traceback):
        pass  # Do nothing, does not require additional cleanup.

简要分析

从源码可以看出,ActionChains类的构造函数中初始化了一个列表
self._actions = []
ActionChains类提供的90%的模拟鼠标操作方法,末尾都有这样一句
self._actions.append()
接下来我们看ActionChains.perform()方法:

    def perform(self):
        """
        Performs all stored actions.
        """
        if self._driver.w3c:
            self.w3c_actions.perform()
        else:
            for action in self._actions:
                action()

该方法执行所有存储的操作。
根据以上,可以理解ActionChains模拟鼠标操作的工作方式:
实例化一个ActionChains类,调用类提供的模拟鼠标操作方法,调用方法并不会执行鼠标操作,而是把鼠标操作存储到self._actions列表里,需要再调用perform方法才能最终实现模拟鼠标操作。
代码逻辑应该是这样:

ActionChains(driver).某个鼠标方法名称().perform()
例如:
ActionChains(driver).move_to_element(a_element).perform()

以上,是我们使用模拟鼠标操作方法的正确方式。
当然,也有个别方法不需要通过perform来执行,而是可以执行调用该方法模拟鼠标操作。
比如drag_and_drop(source, target)、drag_and_drop_by_offset(source, xoffset, yoffse)
详情见源码:因为这两个方法并不需要将鼠标方法存储到self._actions列表里。

   def drag_and_drop(self, source, target):
        """
        Holds down the left mouse button on the source element,
           then moves to the target element and releases the mouse button.

        :Args:
         - source: The element to mouse down.
         - target: The element to mouse up.
        """
        self.click_and_hold(source)
        self.release(target)
        return self

    def drag_and_drop_by_offset(self, source, xoffset, yoffset):
        """
        Holds down the left mouse button on the source element,
           then moves to the target offset and releases the mouse button.

        :Args:
         - source: The element to mouse down.
         - xoffset: X offset to move to.
         - yoffset: Y offset to move to.
        """
        self.click_and_hold(source)
        self.move_by_offset(xoffset, yoffset)
        self.release()
        return self

下面说明常用的模拟鼠标操作方法的作用

此处参考网络:
https://baijiahao.baidu.com/s?id=1608196589844476319&wfr=spider&for=pc

click(on_element=None) ——单击鼠标左键

click_and_hold(on_element=None) ——点击鼠标左键,不松开

context_click(on_element=None) ——点击鼠标右键

double_click(on_element=None) ——双击鼠标左键

drag_and_drop(source, target) ——拖拽到某个元素然后松开

drag_and_drop_by_offset(source, xoffset, yoffset) ——拖拽到某个坐标然后松开

key_down(value, element=None) ——按下某个键盘上的键

key_up(value, element=None) ——松开某个键

move_by_offset(xoffset, yoffset) ——鼠标从当前位置移动到某个坐标

move_to_element(to_element) ——鼠标移动到某个元素

move_to_element_with_offset(to_element, xoffset, yoffset) ——移动到距某个元素(左上角坐标)多少距离的位置

perform() ——执行链中的所有动作

release(on_element=None) ——在某个元素位置松开鼠标左键

send_keys(*keys_to_send) ——发送某个键到当前焦点的元素

send_keys_to_element(element, *keys_to_send) ——发送某个键到指定元素

最后,说明以下ActionChains类提供的click()方法与我们定位到某个元素后使用的click()方法的区别。

ActionChains类

	# ActionChains类
    def click(self, on_element=None):
        """
        Clicks an element.

        :Args:
         - on_element: The element to click.
           If None, clicks on current mouse position.
        """
        if on_element:
            self.move_to_element(on_element)
        if self._driver.w3c:
            self.w3c_actions.pointer_action.click()
            self.w3c_actions.key_action.pause()
            self.w3c_actions.key_action.pause()
        else:
            self._actions.append(lambda: self._driver.execute(
                                 Command.CLICK, {'button': 0}))
        return self

WebElement类

	# WebElement类
    def click(self):
        """Clicks the element."""
        self._execute(Command.CLICK_ELEMENT)

通过以上源码发现,ActionChains类提供的click()方法,需要将待点击的元素作为参数传入,并且先将click动作存储到self._actions列表中,需要perform才能触发点击动作,而调用WebElement类中的click()方法可以直接点击元素。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值