Skip to content

Commit

Permalink
[py] Initial W3C Actions support
Browse files Browse the repository at this point in the history
  • Loading branch information
AutomatedTester committed Feb 27, 2017
1 parent 1476750 commit b966f72
Show file tree
Hide file tree
Showing 13 changed files with 510 additions and 13 deletions.
47 changes: 36 additions & 11 deletions py/selenium/webdriver/common/action_chains.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from selenium.webdriver.remote.command import Command

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


class ActionChains(object):
Expand Down Expand Up @@ -65,13 +66,25 @@ def __init__(self, driver):
"""
self._driver = driver
self._actions = []
if self._driver.w3c:
self.w3c_actions = ActionBuilder(driver)


def perform(self):
"""
Performs all stored actions.
"""
for action in self._actions:
action()
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 on the remote end.
"""
self._driver.execute(Command.W3C_CLEAR_ACTIONS)

def click(self, on_element=None):
"""
Expand Down Expand Up @@ -174,9 +187,12 @@ def key_down(self, value, element=None):
"""
if element:
self.click(element)
self._actions.append(lambda: self._driver.execute(
Command.SEND_KEYS_TO_ACTIVE_ELEMENT,
{"value": keys_to_typing(value)}))
if self._driver.w3c:
self.w3c_actions.key_action.key_down(value)
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):
Expand All @@ -195,9 +211,12 @@ def key_up(self, value, element=None):
"""
if element:
self.click(element)
self._actions.append(lambda: self._driver.execute(
Command.SEND_KEYS_TO_ACTIVE_ELEMENT,
{"value": keys_to_typing(value)}))
if self._driver.w3c:
self.w3c_actions.key_action.key_up(value)
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):
Expand Down Expand Up @@ -263,8 +282,11 @@ def send_keys(self, *keys_to_send):
- keys_to_send: The keys to send. Modifier keys constants can be found in the
'Keys' class.
"""
self._actions.append(lambda: self._driver.execute(
Command.SEND_KEYS_TO_ACTIVE_ELEMENT, {'value': keys_to_typing(keys_to_send)}))
if self._driver.w3c:
self.w3c_actions.key_action.send_keys(keys_to_send)
else:
self._actions.append(lambda: self._driver.execute(
Command.SEND_KEYS_TO_ACTIVE_ELEMENT, {'value': keys_to_typing(keys_to_send)}))
return self

def send_keys_to_element(self, element, *keys_to_send):
Expand All @@ -276,7 +298,10 @@ def send_keys_to_element(self, element, *keys_to_send):
- keys_to_send: The keys to send. Modifier keys constants can be found in the
'Keys' class.
"""
self._actions.append(lambda: element.send_keys(*keys_to_send))
if self._driver.w3c:
self.w3c_actions.key_action.send_keys(keys_to_send, element=element)
else:
self._actions.append(lambda: element.send_keys(*keys_to_send))
return self

# Context manager so ActionChains can be used in a 'with .. as' statements.
Expand Down
16 changes: 16 additions & 0 deletions py/selenium/webdriver/common/actions/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# 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.
83 changes: 83 additions & 0 deletions py/selenium/webdriver/common/actions/action_builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# 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.

import interaction

from .key_input import KeyInput
from .key_actions import KeyActions
from .pointer_input import PointerInput
from .pointer_actions import PointerActions

from selenium.webdriver.remote.command import Command


class ActionBuilder(object):

def __init__(self, driver, mouse=None, keyboard=None):
if mouse is None:
mouse = PointerInput("mouse", "mouse")
if keyboard is None:
keyboard = KeyInput("keyboard")
self.devices = [keyboard]
self._key_action = KeyActions(keyboard)
self._pointer_action = PointerActions(mouse)
self.driver = driver

def get_device_with(self, name):
try:
idx = self.devices.index(name)
return self.devices[idx]
except:
pass

@property
def pointer_inputs(self):
return [device for device in self.devices if device.type == interaction.POINTER]

@property
def key_inputs(self):
return [device for device in self.devices if device.type == interaction.KEY]

@property
def key_action(self):
return self._key_action

@property
def pointer_action(self):
return self._pointer_action

def add_key_input(self, name):
new_input = KeyInput(name)
self._add_input(new_input)
return new_input

def add_pointer_input(self, type_, name):
new_input = PointerInput(type_, name)
self._add_input(new_input)
return new_input

def perform(self):
enc ={"actions": []}
for device in self.devices:
enc["actions"].append(device.encode())
self.driver.execute(Command.W3C_ACTIONS, enc)

def clear_actions(self):
self.driver.execute(Command.W3C_CLEAR_ACTIONS)

def _add_input(self, input):
self.devices.append(input)
43 changes: 43 additions & 0 deletions py/selenium/webdriver/common/actions/input_device.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# 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.

import uuid


class InputDevice(object):
"""
Describes the input device being used for the action.
"""
def __init__(self, name=None):
if name is None:
self.name = uuid.uuid4()
else:
self.name = name

self.actions = []

def add_action(self, action):
"""
"""
self.actions.append(action)

def clear_actions(self):
self.actions = []

def create_pause(self, duraton=0):
pass
43 changes: 43 additions & 0 deletions py/selenium/webdriver/common/actions/interaction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# 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.


KEY = "key"
POINTER = "pointer"
NONE = "none"
SOURCE_TYPES = set([KEY, POINTER, NONE])


class Interaction(object):

PAUSE = "pause"

def __init__(self, source):
self.source = source


class Pause(Interaction):

def __init__(self, source, duration=0):
super(Interaction, self).__init__()
self.source = source
self.duration = duration

def encode(self):
output = {"type": self.PAUSE}
output["duration"] = self.duration * 1000
return output
53 changes: 53 additions & 0 deletions py/selenium/webdriver/common/actions/key_actions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# 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.
from ..utils import keys_to_typing

from .interaction import Interaction
from .key_input import KeyInput


class KeyActions(Interaction):

def __init__(self, source=None):
if source is None:
source = KeyInput()
self.source = source
super(KeyActions, self).__init__(source)

def key_down(self, letter, element=None):
return self._key_action("create_key_down",
letter, element)

def key_up(self, letter, element=None):
return self._key_action("create_key_up",
letter, element)

def pause(self, duration=0):
return self._key_action("create_pause", duration)

def send_keys(self, text, element=None):
if not isinstance(text, list):
text = keys_to_typing(text)
for letter in text:
self.key_down(letter, element)
self.key_up(letter, element)
return self

def _key_action(self, action, letter, element=None):
meth = getattr(self.source, action)
meth(letter)
return self
51 changes: 51 additions & 0 deletions py/selenium/webdriver/common/actions/key_input.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# 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.
import interaction

from input_device import InputDevice
from interaction import (Interaction,
Pause)


class KeyInput(InputDevice):
def __init__(self, name):
super(KeyInput, self).__init__()
self.name = name
self.type = interaction.KEY

def encode(self):
return {"type": self.type, "id": self.name, "actions": [acts.encode() for acts in self.actions]}

def create_key_down(self, key):
self.add_action(TypingInteraction(self, "keyDown", key))

def create_key_up(self, key):
self.add_action(TypingInteraction(self, "keyUp", key))

def create_pause(self, pause_duration=0):
self.add_action(Pause(self, pause_duration))


class TypingInteraction(Interaction):

def __init__(self, source, type_, key):
super(TypingInteraction, self).__init__(source)
self.type = type_
self.key = key

def encode(self):
return {"type": self.type, "value": self.key}
Loading

0 comments on commit b966f72

Please sign in to comment.