Ausgabe der neuen DB Einträge
This commit is contained in:
parent
bad48e1627
commit
cfbbb9ee3d
2399 changed files with 843193 additions and 43 deletions
|
|
@ -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.
|
||||
|
|
@ -0,0 +1,363 @@
|
|||
# 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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
# 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 selenium.webdriver.remote.command import Command
|
||||
from . import interaction
|
||||
from .key_actions import KeyActions
|
||||
from .key_input import KeyInput
|
||||
from .pointer_actions import PointerActions
|
||||
from .pointer_input import PointerInput
|
||||
|
||||
|
||||
class ActionBuilder(object):
|
||||
def __init__(self, driver, mouse=None, keyboard=None):
|
||||
if mouse is None:
|
||||
mouse = PointerInput(interaction.POINTER_MOUSE, "mouse")
|
||||
if keyboard is None:
|
||||
keyboard = KeyInput(interaction.KEY)
|
||||
self.devices = [mouse, 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, kind, name):
|
||||
new_input = PointerInput(kind, name)
|
||||
self._add_input(new_input)
|
||||
return new_input
|
||||
|
||||
def perform(self):
|
||||
enc = {"actions": []}
|
||||
for device in self.devices:
|
||||
encoded = device.encode()
|
||||
if encoded['actions']:
|
||||
enc["actions"].append(encoded)
|
||||
self.driver.execute(Command.W3C_ACTIONS, enc)
|
||||
|
||||
def clear_actions(self):
|
||||
"""
|
||||
Clears actions that are already stored on the remote end
|
||||
"""
|
||||
self.driver.execute(Command.W3C_CLEAR_ACTIONS)
|
||||
|
||||
def _add_input(self, input):
|
||||
self.devices.append(input)
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
# 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])
|
||||
|
||||
POINTER_MOUSE = "mouse"
|
||||
POINTER_TOUCH = "touch"
|
||||
POINTER_PEN = "pen"
|
||||
|
||||
POINTER_KINDS = set([POINTER_MOUSE, POINTER_TOUCH, POINTER_PEN])
|
||||
|
||||
|
||||
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):
|
||||
return {
|
||||
"type": self.PAUSE,
|
||||
"duration": int(self.duration * 1000)
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
# 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 .interaction import Interaction, KEY
|
||||
from .key_input import KeyInput
|
||||
from ..utils import keys_to_typing
|
||||
|
||||
|
||||
class KeyActions(Interaction):
|
||||
|
||||
def __init__(self, source=None):
|
||||
if source is None:
|
||||
source = KeyInput(KEY)
|
||||
self.source = source
|
||||
super(KeyActions, self).__init__(source)
|
||||
|
||||
def key_down(self, letter):
|
||||
return self._key_action("create_key_down", letter)
|
||||
|
||||
def key_up(self, letter):
|
||||
return self._key_action("create_key_up", letter)
|
||||
|
||||
def pause(self, duration=0):
|
||||
return self._key_action("create_pause", duration)
|
||||
|
||||
def send_keys(self, text):
|
||||
if not isinstance(text, list):
|
||||
text = keys_to_typing(text)
|
||||
for letter in text:
|
||||
self.key_down(letter)
|
||||
self.key_up(letter)
|
||||
return self
|
||||
|
||||
def _key_action(self, action, letter):
|
||||
meth = getattr(self.source, action)
|
||||
meth(letter)
|
||||
return self
|
||||
|
|
@ -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.
|
||||
from . 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}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
class MouseButton(object):
|
||||
|
||||
LEFT = 0
|
||||
MIDDLE = 1
|
||||
RIGHT = 2
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
# 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 . import interaction
|
||||
|
||||
from .interaction import Interaction
|
||||
from .mouse_button import MouseButton
|
||||
from .pointer_input import PointerInput
|
||||
|
||||
from selenium.webdriver.remote.webelement import WebElement
|
||||
|
||||
|
||||
class PointerActions(Interaction):
|
||||
|
||||
def __init__(self, source=None):
|
||||
if source is None:
|
||||
source = PointerInput(interaction.POINTER_MOUSE, "mouse")
|
||||
self.source = source
|
||||
super(PointerActions, self).__init__(source)
|
||||
|
||||
def pointer_down(self, button=MouseButton.LEFT):
|
||||
self._button_action("create_pointer_down", button=button)
|
||||
|
||||
def pointer_up(self, button=MouseButton.LEFT):
|
||||
self._button_action("create_pointer_up", button=button)
|
||||
|
||||
def move_to(self, element, x=None, y=None):
|
||||
if not isinstance(element, WebElement):
|
||||
raise AttributeError("move_to requires a WebElement")
|
||||
if x is not None or y is not None:
|
||||
el_rect = element.rect
|
||||
left_offset = el_rect['width'] / 2
|
||||
top_offset = el_rect['height'] / 2
|
||||
left = -left_offset + (x or 0)
|
||||
top = -top_offset + (y or 0)
|
||||
else:
|
||||
left = 0
|
||||
top = 0
|
||||
self.source.create_pointer_move(origin=element, x=int(left), y=int(top))
|
||||
return self
|
||||
|
||||
def move_by(self, x, y):
|
||||
self.source.create_pointer_move(origin=interaction.POINTER, x=int(x), y=int(y))
|
||||
return self
|
||||
|
||||
def move_to_location(self, x, y):
|
||||
self.source.create_pointer_move(origin='viewport', x=int(x), y=int(y))
|
||||
return self
|
||||
|
||||
def click(self, element=None):
|
||||
if element:
|
||||
self.move_to(element)
|
||||
self.pointer_down(MouseButton.LEFT)
|
||||
self.pointer_up(MouseButton.LEFT)
|
||||
return self
|
||||
|
||||
def context_click(self, element=None):
|
||||
if element:
|
||||
self.move_to(element)
|
||||
self.pointer_down(MouseButton.RIGHT)
|
||||
self.pointer_up(MouseButton.RIGHT)
|
||||
return self
|
||||
|
||||
def click_and_hold(self, element=None):
|
||||
if element:
|
||||
self.move_to(element)
|
||||
self.pointer_down()
|
||||
return self
|
||||
|
||||
def release(self):
|
||||
self.pointer_up()
|
||||
return self
|
||||
|
||||
def double_click(self, element=None):
|
||||
if element:
|
||||
self.move_to(element)
|
||||
self.click()
|
||||
self.click()
|
||||
|
||||
def pause(self, duration=0):
|
||||
self.source.create_pause(duration)
|
||||
return self
|
||||
|
||||
def _button_action(self, action, button=MouseButton.LEFT):
|
||||
meth = getattr(self.source, action)
|
||||
meth(button)
|
||||
return self
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
# 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 .input_device import InputDevice
|
||||
from .interaction import POINTER, POINTER_KINDS
|
||||
|
||||
from selenium.common.exceptions import InvalidArgumentException
|
||||
from selenium.webdriver.remote.webelement import WebElement
|
||||
|
||||
|
||||
class PointerInput(InputDevice):
|
||||
|
||||
DEFAULT_MOVE_DURATION = 250
|
||||
|
||||
def __init__(self, kind, name):
|
||||
super(PointerInput, self).__init__()
|
||||
if (kind not in POINTER_KINDS):
|
||||
raise InvalidArgumentException("Invalid PointerInput kind '%s'" % kind)
|
||||
self.type = POINTER
|
||||
self.kind = kind
|
||||
self.name = name
|
||||
|
||||
def create_pointer_move(self, duration=DEFAULT_MOVE_DURATION, x=None, y=None, origin=None):
|
||||
action = dict(type="pointerMove", duration=duration)
|
||||
action["x"] = x
|
||||
action["y"] = y
|
||||
if isinstance(origin, WebElement):
|
||||
action["origin"] = {"element-6066-11e4-a52e-4f735466cecf": origin.id}
|
||||
elif origin is not None:
|
||||
action["origin"] = origin
|
||||
|
||||
self.add_action(action)
|
||||
|
||||
def create_pointer_down(self, button):
|
||||
self.add_action({"type": "pointerDown", "duration": 0, "button": button})
|
||||
|
||||
def create_pointer_up(self, button):
|
||||
self.add_action({"type": "pointerUp", "duration": 0, "button": button})
|
||||
|
||||
def create_pointer_cancel(self):
|
||||
self.add_action({"type": "pointerCancel"})
|
||||
|
||||
def create_pause(self, pause_duration):
|
||||
self.add_action({"type": "pause", "duration": int(pause_duration * 1000)})
|
||||
|
||||
def encode(self):
|
||||
return {"type": self.type,
|
||||
"parameters": {"pointerType": self.kind},
|
||||
"id": self.name,
|
||||
"actions": [acts for acts in self.actions]}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
# 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 Alert implementation.
|
||||
"""
|
||||
|
||||
from selenium.webdriver.common.utils import keys_to_typing
|
||||
from selenium.webdriver.remote.command import Command
|
||||
|
||||
|
||||
class Alert(object):
|
||||
"""
|
||||
Allows to work with alerts.
|
||||
|
||||
Use this class to interact with alert prompts. It contains methods for dismissing,
|
||||
accepting, inputting, and getting text from alert prompts.
|
||||
|
||||
Accepting / Dismissing alert prompts::
|
||||
|
||||
Alert(driver).accept()
|
||||
Alert(driver).dismiss()
|
||||
|
||||
Inputting a value into an alert prompt:
|
||||
|
||||
name_prompt = Alert(driver)
|
||||
name_prompt.send_keys("Willian Shakesphere")
|
||||
name_prompt.accept()
|
||||
|
||||
|
||||
Reading a the text of a prompt for verification:
|
||||
|
||||
alert_text = Alert(driver).text
|
||||
self.assertEqual("Do you wish to quit?", alert_text)
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, driver):
|
||||
"""
|
||||
Creates a new Alert.
|
||||
|
||||
:Args:
|
||||
- driver: The WebDriver instance which performs user actions.
|
||||
"""
|
||||
self.driver = driver
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
"""
|
||||
Gets the text of the Alert.
|
||||
"""
|
||||
if self.driver.w3c:
|
||||
return self.driver.execute(Command.W3C_GET_ALERT_TEXT)["value"]
|
||||
else:
|
||||
return self.driver.execute(Command.GET_ALERT_TEXT)["value"]
|
||||
|
||||
def dismiss(self):
|
||||
"""
|
||||
Dismisses the alert available.
|
||||
"""
|
||||
if self.driver.w3c:
|
||||
self.driver.execute(Command.W3C_DISMISS_ALERT)
|
||||
else:
|
||||
self.driver.execute(Command.DISMISS_ALERT)
|
||||
|
||||
def accept(self):
|
||||
"""
|
||||
Accepts the alert available.
|
||||
|
||||
Usage::
|
||||
Alert(driver).accept() # Confirm a alert dialog.
|
||||
"""
|
||||
if self.driver.w3c:
|
||||
self.driver.execute(Command.W3C_ACCEPT_ALERT)
|
||||
else:
|
||||
self.driver.execute(Command.ACCEPT_ALERT)
|
||||
|
||||
def send_keys(self, keysToSend):
|
||||
"""
|
||||
Send Keys to the Alert.
|
||||
|
||||
:Args:
|
||||
- keysToSend: The text to be sent to Alert.
|
||||
|
||||
|
||||
"""
|
||||
if self.driver.w3c:
|
||||
self.driver.execute(Command.W3C_SET_ALERT_VALUE, {'value': keys_to_typing(keysToSend),
|
||||
'text': keysToSend})
|
||||
else:
|
||||
self.driver.execute(Command.SET_ALERT_VALUE, {'text': keysToSend})
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
# 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 By implementation.
|
||||
"""
|
||||
|
||||
|
||||
class By(object):
|
||||
"""
|
||||
Set of supported locator strategies.
|
||||
"""
|
||||
|
||||
ID = "id"
|
||||
XPATH = "xpath"
|
||||
LINK_TEXT = "link text"
|
||||
PARTIAL_LINK_TEXT = "partial link text"
|
||||
NAME = "name"
|
||||
TAG_NAME = "tag name"
|
||||
CLASS_NAME = "class name"
|
||||
CSS_SELECTOR = "css selector"
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
# 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 Desired Capabilities implementation.
|
||||
"""
|
||||
|
||||
|
||||
class DesiredCapabilities(object):
|
||||
"""
|
||||
Set of default supported desired capabilities.
|
||||
|
||||
Use this as a starting point for creating a desired capabilities object for
|
||||
requesting remote webdrivers for connecting to selenium server or selenium grid.
|
||||
|
||||
Usage Example::
|
||||
|
||||
from selenium import webdriver
|
||||
|
||||
selenium_grid_url = "http://198.0.0.1:4444/wd/hub"
|
||||
|
||||
# Create a desired capabilities object as a starting point.
|
||||
capabilities = DesiredCapabilities.FIREFOX.copy()
|
||||
capabilities['platform'] = "WINDOWS"
|
||||
capabilities['version'] = "10"
|
||||
|
||||
# Instantiate an instance of Remote WebDriver with the desired capabilities.
|
||||
driver = webdriver.Remote(desired_capabilities=capabilities,
|
||||
command_executor=selenium_grid_url)
|
||||
|
||||
Note: Always use '.copy()' on the DesiredCapabilities object to avoid the side
|
||||
effects of altering the Global class instance.
|
||||
|
||||
"""
|
||||
|
||||
FIREFOX = {
|
||||
"browserName": "firefox",
|
||||
"marionette": True,
|
||||
"acceptInsecureCerts": True,
|
||||
}
|
||||
|
||||
INTERNETEXPLORER = {
|
||||
"browserName": "internet explorer",
|
||||
"version": "",
|
||||
"platform": "WINDOWS",
|
||||
}
|
||||
|
||||
EDGE = {
|
||||
"browserName": "MicrosoftEdge",
|
||||
"version": "",
|
||||
"platform": "WINDOWS"
|
||||
}
|
||||
|
||||
CHROME = {
|
||||
"browserName": "chrome",
|
||||
"version": "",
|
||||
"platform": "ANY",
|
||||
}
|
||||
|
||||
OPERA = {
|
||||
"browserName": "opera",
|
||||
"version": "",
|
||||
"platform": "ANY",
|
||||
}
|
||||
|
||||
SAFARI = {
|
||||
"browserName": "safari",
|
||||
"version": "",
|
||||
"platform": "MAC",
|
||||
}
|
||||
|
||||
HTMLUNIT = {
|
||||
"browserName": "htmlunit",
|
||||
"version": "",
|
||||
"platform": "ANY",
|
||||
}
|
||||
|
||||
HTMLUNITWITHJS = {
|
||||
"browserName": "htmlunit",
|
||||
"version": "firefox",
|
||||
"platform": "ANY",
|
||||
"javascriptEnabled": True,
|
||||
}
|
||||
|
||||
IPHONE = {
|
||||
"browserName": "iPhone",
|
||||
"version": "",
|
||||
"platform": "MAC",
|
||||
}
|
||||
|
||||
IPAD = {
|
||||
"browserName": "iPad",
|
||||
"version": "",
|
||||
"platform": "MAC",
|
||||
}
|
||||
|
||||
ANDROID = {
|
||||
"browserName": "android",
|
||||
"version": "",
|
||||
"platform": "ANDROID",
|
||||
}
|
||||
|
||||
PHANTOMJS = {
|
||||
"browserName": "phantomjs",
|
||||
"version": "",
|
||||
"platform": "ANY",
|
||||
"javascriptEnabled": True,
|
||||
}
|
||||
|
||||
WEBKITGTK = {
|
||||
"browserName": "MiniBrowser",
|
||||
"version": "",
|
||||
"platform": "ANY",
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
# 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 ApplicationCache implementaion.
|
||||
"""
|
||||
|
||||
from selenium.webdriver.remote.command import Command
|
||||
|
||||
|
||||
class ApplicationCache(object):
|
||||
|
||||
UNCACHED = 0
|
||||
IDLE = 1
|
||||
CHECKING = 2
|
||||
DOWNLOADING = 3
|
||||
UPDATE_READY = 4
|
||||
OBSOLETE = 5
|
||||
|
||||
def __init__(self, driver):
|
||||
"""
|
||||
Creates a new Aplication Cache.
|
||||
|
||||
:Args:
|
||||
- driver: The WebDriver instance which performs user actions.
|
||||
"""
|
||||
self.driver = driver
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
"""
|
||||
Returns a current status of application cache.
|
||||
"""
|
||||
return self.driver.execute(Command.GET_APP_CACHE_STATUS)['value']
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
# 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 Keys implementation.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
class Keys(object):
|
||||
"""
|
||||
Set of special keys codes.
|
||||
"""
|
||||
|
||||
NULL = '\ue000'
|
||||
CANCEL = '\ue001' # ^break
|
||||
HELP = '\ue002'
|
||||
BACKSPACE = '\ue003'
|
||||
BACK_SPACE = BACKSPACE
|
||||
TAB = '\ue004'
|
||||
CLEAR = '\ue005'
|
||||
RETURN = '\ue006'
|
||||
ENTER = '\ue007'
|
||||
SHIFT = '\ue008'
|
||||
LEFT_SHIFT = SHIFT
|
||||
CONTROL = '\ue009'
|
||||
LEFT_CONTROL = CONTROL
|
||||
ALT = '\ue00a'
|
||||
LEFT_ALT = ALT
|
||||
PAUSE = '\ue00b'
|
||||
ESCAPE = '\ue00c'
|
||||
SPACE = '\ue00d'
|
||||
PAGE_UP = '\ue00e'
|
||||
PAGE_DOWN = '\ue00f'
|
||||
END = '\ue010'
|
||||
HOME = '\ue011'
|
||||
LEFT = '\ue012'
|
||||
ARROW_LEFT = LEFT
|
||||
UP = '\ue013'
|
||||
ARROW_UP = UP
|
||||
RIGHT = '\ue014'
|
||||
ARROW_RIGHT = RIGHT
|
||||
DOWN = '\ue015'
|
||||
ARROW_DOWN = DOWN
|
||||
INSERT = '\ue016'
|
||||
DELETE = '\ue017'
|
||||
SEMICOLON = '\ue018'
|
||||
EQUALS = '\ue019'
|
||||
|
||||
NUMPAD0 = '\ue01a' # number pad keys
|
||||
NUMPAD1 = '\ue01b'
|
||||
NUMPAD2 = '\ue01c'
|
||||
NUMPAD3 = '\ue01d'
|
||||
NUMPAD4 = '\ue01e'
|
||||
NUMPAD5 = '\ue01f'
|
||||
NUMPAD6 = '\ue020'
|
||||
NUMPAD7 = '\ue021'
|
||||
NUMPAD8 = '\ue022'
|
||||
NUMPAD9 = '\ue023'
|
||||
MULTIPLY = '\ue024'
|
||||
ADD = '\ue025'
|
||||
SEPARATOR = '\ue026'
|
||||
SUBTRACT = '\ue027'
|
||||
DECIMAL = '\ue028'
|
||||
DIVIDE = '\ue029'
|
||||
|
||||
F1 = '\ue031' # function keys
|
||||
F2 = '\ue032'
|
||||
F3 = '\ue033'
|
||||
F4 = '\ue034'
|
||||
F5 = '\ue035'
|
||||
F6 = '\ue036'
|
||||
F7 = '\ue037'
|
||||
F8 = '\ue038'
|
||||
F9 = '\ue039'
|
||||
F10 = '\ue03a'
|
||||
F11 = '\ue03b'
|
||||
F12 = '\ue03c'
|
||||
|
||||
META = '\ue03d'
|
||||
COMMAND = '\ue03d'
|
||||
|
|
@ -0,0 +1,334 @@
|
|||
# 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 Proxy implementation.
|
||||
"""
|
||||
|
||||
|
||||
class ProxyTypeFactory:
|
||||
"""
|
||||
Factory for proxy types.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def make(ff_value, string):
|
||||
return {'ff_value': ff_value, 'string': string}
|
||||
|
||||
|
||||
class ProxyType:
|
||||
"""
|
||||
Set of possible types of proxy.
|
||||
|
||||
Each proxy type has 2 properties:
|
||||
'ff_value' is value of Firefox profile preference,
|
||||
'string' is id of proxy type.
|
||||
"""
|
||||
|
||||
DIRECT = ProxyTypeFactory.make(0, 'DIRECT') # Direct connection, no proxy (default on Windows).
|
||||
MANUAL = ProxyTypeFactory.make(1, 'MANUAL') # Manual proxy settings (e.g., for httpProxy).
|
||||
PAC = ProxyTypeFactory.make(2, 'PAC') # Proxy autoconfiguration from URL.
|
||||
RESERVED_1 = ProxyTypeFactory.make(3, 'RESERVED1') # Never used.
|
||||
AUTODETECT = ProxyTypeFactory.make(4, 'AUTODETECT') # Proxy autodetection (presumably with WPAD).
|
||||
SYSTEM = ProxyTypeFactory.make(5, 'SYSTEM') # Use system settings (default on Linux).
|
||||
UNSPECIFIED = ProxyTypeFactory.make(6, 'UNSPECIFIED') # Not initialized (for internal use).
|
||||
|
||||
@classmethod
|
||||
def load(cls, value):
|
||||
if isinstance(value, dict) and 'string' in value:
|
||||
value = value['string']
|
||||
value = str(value).upper()
|
||||
for attr in dir(cls):
|
||||
attr_value = getattr(cls, attr)
|
||||
if isinstance(attr_value, dict) and \
|
||||
'string' in attr_value and \
|
||||
attr_value['string'] is not None and \
|
||||
attr_value['string'] == value:
|
||||
return attr_value
|
||||
raise Exception("No proxy type is found for %s" % (value))
|
||||
|
||||
|
||||
class Proxy(object):
|
||||
"""
|
||||
Proxy contains information about proxy type and necessary proxy settings.
|
||||
"""
|
||||
|
||||
proxyType = ProxyType.UNSPECIFIED
|
||||
autodetect = False
|
||||
ftpProxy = ''
|
||||
httpProxy = ''
|
||||
noProxy = ''
|
||||
proxyAutoconfigUrl = ''
|
||||
sslProxy = ''
|
||||
socksProxy = ''
|
||||
socksUsername = ''
|
||||
socksPassword = ''
|
||||
|
||||
def __init__(self, raw=None):
|
||||
"""
|
||||
Creates a new Proxy.
|
||||
|
||||
:Args:
|
||||
- raw: raw proxy data. If None, default class values are used.
|
||||
"""
|
||||
if raw is not None:
|
||||
if 'proxyType' in raw and raw['proxyType'] is not None:
|
||||
self.proxy_type = ProxyType.load(raw['proxyType'])
|
||||
if 'ftpProxy' in raw and raw['ftpProxy'] is not None:
|
||||
self.ftp_proxy = raw['ftpProxy']
|
||||
if 'httpProxy' in raw and raw['httpProxy'] is not None:
|
||||
self.http_proxy = raw['httpProxy']
|
||||
if 'noProxy' in raw and raw['noProxy'] is not None:
|
||||
self.no_proxy = raw['noProxy']
|
||||
if 'proxyAutoconfigUrl' in raw and raw['proxyAutoconfigUrl'] is not None:
|
||||
self.proxy_autoconfig_url = raw['proxyAutoconfigUrl']
|
||||
if 'sslProxy' in raw and raw['sslProxy'] is not None:
|
||||
self.sslProxy = raw['sslProxy']
|
||||
if 'autodetect' in raw and raw['autodetect'] is not None:
|
||||
self.auto_detect = raw['autodetect']
|
||||
if 'socksProxy' in raw and raw['socksProxy'] is not None:
|
||||
self.socks_proxy = raw['socksProxy']
|
||||
if 'socksUsername' in raw and raw['socksUsername'] is not None:
|
||||
self.socks_username = raw['socksUsername']
|
||||
if 'socksPassword' in raw and raw['socksPassword'] is not None:
|
||||
self.socks_password = raw['socksPassword']
|
||||
|
||||
@property
|
||||
def proxy_type(self):
|
||||
"""
|
||||
Returns proxy type as `ProxyType`.
|
||||
"""
|
||||
return self.proxyType
|
||||
|
||||
@proxy_type.setter
|
||||
def proxy_type(self, value):
|
||||
"""
|
||||
Sets proxy type.
|
||||
|
||||
:Args:
|
||||
- value: The proxy type.
|
||||
"""
|
||||
self._verify_proxy_type_compatibility(value)
|
||||
self.proxyType = value
|
||||
|
||||
@property
|
||||
def auto_detect(self):
|
||||
"""
|
||||
Returns autodetect setting.
|
||||
"""
|
||||
return self.autodetect
|
||||
|
||||
@auto_detect.setter
|
||||
def auto_detect(self, value):
|
||||
"""
|
||||
Sets autodetect setting.
|
||||
|
||||
:Args:
|
||||
- value: The autodetect value.
|
||||
"""
|
||||
if isinstance(value, bool):
|
||||
if self.autodetect is not value:
|
||||
self._verify_proxy_type_compatibility(ProxyType.AUTODETECT)
|
||||
self.proxyType = ProxyType.AUTODETECT
|
||||
self.autodetect = value
|
||||
else:
|
||||
raise ValueError("Autodetect proxy value needs to be a boolean")
|
||||
|
||||
@property
|
||||
def ftp_proxy(self):
|
||||
"""
|
||||
Returns ftp proxy setting.
|
||||
"""
|
||||
return self.ftpProxy
|
||||
|
||||
@ftp_proxy.setter
|
||||
def ftp_proxy(self, value):
|
||||
"""
|
||||
Sets ftp proxy setting.
|
||||
|
||||
:Args:
|
||||
- value: The ftp proxy value.
|
||||
"""
|
||||
self._verify_proxy_type_compatibility(ProxyType.MANUAL)
|
||||
self.proxyType = ProxyType.MANUAL
|
||||
self.ftpProxy = value
|
||||
|
||||
@property
|
||||
def http_proxy(self):
|
||||
"""
|
||||
Returns http proxy setting.
|
||||
"""
|
||||
return self.httpProxy
|
||||
|
||||
@http_proxy.setter
|
||||
def http_proxy(self, value):
|
||||
"""
|
||||
Sets http proxy setting.
|
||||
|
||||
:Args:
|
||||
- value: The http proxy value.
|
||||
"""
|
||||
self._verify_proxy_type_compatibility(ProxyType.MANUAL)
|
||||
self.proxyType = ProxyType.MANUAL
|
||||
self.httpProxy = value
|
||||
|
||||
@property
|
||||
def no_proxy(self):
|
||||
"""
|
||||
Returns noproxy setting.
|
||||
"""
|
||||
return self.noProxy
|
||||
|
||||
@no_proxy.setter
|
||||
def no_proxy(self, value):
|
||||
"""
|
||||
Sets noproxy setting.
|
||||
|
||||
:Args:
|
||||
- value: The noproxy value.
|
||||
"""
|
||||
self._verify_proxy_type_compatibility(ProxyType.MANUAL)
|
||||
self.proxyType = ProxyType.MANUAL
|
||||
self.noProxy = value
|
||||
|
||||
@property
|
||||
def proxy_autoconfig_url(self):
|
||||
"""
|
||||
Returns proxy autoconfig url setting.
|
||||
"""
|
||||
return self.proxyAutoconfigUrl
|
||||
|
||||
@proxy_autoconfig_url.setter
|
||||
def proxy_autoconfig_url(self, value):
|
||||
"""
|
||||
Sets proxy autoconfig url setting.
|
||||
|
||||
:Args:
|
||||
- value: The proxy autoconfig url value.
|
||||
"""
|
||||
self._verify_proxy_type_compatibility(ProxyType.PAC)
|
||||
self.proxyType = ProxyType.PAC
|
||||
self.proxyAutoconfigUrl = value
|
||||
|
||||
@property
|
||||
def ssl_proxy(self):
|
||||
"""
|
||||
Returns https proxy setting.
|
||||
"""
|
||||
return self.sslProxy
|
||||
|
||||
@ssl_proxy.setter
|
||||
def ssl_proxy(self, value):
|
||||
"""
|
||||
Sets https proxy setting.
|
||||
|
||||
:Args:
|
||||
- value: The https proxy value.
|
||||
"""
|
||||
self._verify_proxy_type_compatibility(ProxyType.MANUAL)
|
||||
self.proxyType = ProxyType.MANUAL
|
||||
self.sslProxy = value
|
||||
|
||||
@property
|
||||
def socks_proxy(self):
|
||||
"""
|
||||
Returns socks proxy setting.
|
||||
"""
|
||||
return self.socksProxy
|
||||
|
||||
@socks_proxy.setter
|
||||
def socks_proxy(self, value):
|
||||
"""
|
||||
Sets socks proxy setting.
|
||||
|
||||
:Args:
|
||||
- value: The socks proxy value.
|
||||
"""
|
||||
self._verify_proxy_type_compatibility(ProxyType.MANUAL)
|
||||
self.proxyType = ProxyType.MANUAL
|
||||
self.socksProxy = value
|
||||
|
||||
@property
|
||||
def socks_username(self):
|
||||
"""
|
||||
Returns socks proxy username setting.
|
||||
"""
|
||||
return self.socksUsername
|
||||
|
||||
@socks_username.setter
|
||||
def socks_username(self, value):
|
||||
"""
|
||||
Sets socks proxy username setting.
|
||||
|
||||
:Args:
|
||||
- value: The socks proxy username value.
|
||||
"""
|
||||
self._verify_proxy_type_compatibility(ProxyType.MANUAL)
|
||||
self.proxyType = ProxyType.MANUAL
|
||||
self.socksUsername = value
|
||||
|
||||
@property
|
||||
def socks_password(self):
|
||||
"""
|
||||
Returns socks proxy password setting.
|
||||
"""
|
||||
return self.socksPassword
|
||||
|
||||
@socks_password.setter
|
||||
def socks_password(self, value):
|
||||
"""
|
||||
Sets socks proxy password setting.
|
||||
|
||||
:Args:
|
||||
- value: The socks proxy password value.
|
||||
"""
|
||||
self._verify_proxy_type_compatibility(ProxyType.MANUAL)
|
||||
self.proxyType = ProxyType.MANUAL
|
||||
self.socksPassword = value
|
||||
|
||||
def _verify_proxy_type_compatibility(self, compatibleProxy):
|
||||
if self.proxyType != ProxyType.UNSPECIFIED and self.proxyType != compatibleProxy:
|
||||
raise Exception(" Specified proxy type (%s) not compatible with current setting (%s)" % (compatibleProxy, self.proxyType))
|
||||
|
||||
def add_to_capabilities(self, capabilities):
|
||||
"""
|
||||
Adds proxy information as capability in specified capabilities.
|
||||
|
||||
:Args:
|
||||
- capabilities: The capabilities to which proxy will be added.
|
||||
"""
|
||||
proxy_caps = {}
|
||||
proxy_caps['proxyType'] = self.proxyType['string']
|
||||
if self.autodetect:
|
||||
proxy_caps['autodetect'] = self.autodetect
|
||||
if self.ftpProxy:
|
||||
proxy_caps['ftpProxy'] = self.ftpProxy
|
||||
if self.httpProxy:
|
||||
proxy_caps['httpProxy'] = self.httpProxy
|
||||
if self.proxyAutoconfigUrl:
|
||||
proxy_caps['proxyAutoconfigUrl'] = self.proxyAutoconfigUrl
|
||||
if self.sslProxy:
|
||||
proxy_caps['sslProxy'] = self.sslProxy
|
||||
if self.noProxy:
|
||||
proxy_caps['noProxy'] = self.noProxy
|
||||
if self.socksProxy:
|
||||
proxy_caps['socksProxy'] = self.socksProxy
|
||||
if self.socksUsername:
|
||||
proxy_caps['socksUsername'] = self.socksUsername
|
||||
if self.socksPassword:
|
||||
proxy_caps['socksPassword'] = self.socksPassword
|
||||
capabilities['proxy'] = proxy_caps
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
# 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 errno
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
from subprocess import PIPE
|
||||
import time
|
||||
from selenium.common.exceptions import WebDriverException
|
||||
from selenium.webdriver.common import utils
|
||||
|
||||
try:
|
||||
from subprocess import DEVNULL
|
||||
_HAS_NATIVE_DEVNULL = True
|
||||
except ImportError:
|
||||
DEVNULL = -3
|
||||
_HAS_NATIVE_DEVNULL = False
|
||||
|
||||
|
||||
class Service(object):
|
||||
|
||||
def __init__(self, executable, port=0, log_file=DEVNULL, env=None, start_error_message=""):
|
||||
self.path = executable
|
||||
|
||||
self.port = port
|
||||
if self.port == 0:
|
||||
self.port = utils.free_port()
|
||||
|
||||
if not _HAS_NATIVE_DEVNULL and log_file == DEVNULL:
|
||||
log_file = open(os.devnull, 'wb')
|
||||
|
||||
self.start_error_message = start_error_message
|
||||
self.log_file = log_file
|
||||
self.env = env or os.environ
|
||||
|
||||
@property
|
||||
def service_url(self):
|
||||
"""
|
||||
Gets the url of the Service
|
||||
"""
|
||||
return "http://%s" % utils.join_host_port('localhost', self.port)
|
||||
|
||||
def command_line_args(self):
|
||||
raise NotImplemented("This method needs to be implemented in a sub class")
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Starts the Service.
|
||||
|
||||
:Exceptions:
|
||||
- WebDriverException : Raised either when it can't start the service
|
||||
or when it can't connect to the service
|
||||
"""
|
||||
try:
|
||||
cmd = [self.path]
|
||||
cmd.extend(self.command_line_args())
|
||||
self.process = subprocess.Popen(cmd, env=self.env,
|
||||
close_fds=platform.system() != 'Windows',
|
||||
stdout=self.log_file,
|
||||
stderr=self.log_file,
|
||||
stdin=PIPE)
|
||||
except TypeError:
|
||||
raise
|
||||
except OSError as err:
|
||||
if err.errno == errno.ENOENT:
|
||||
raise WebDriverException(
|
||||
"'%s' executable needs to be in PATH. %s" % (
|
||||
os.path.basename(self.path), self.start_error_message)
|
||||
)
|
||||
elif err.errno == errno.EACCES:
|
||||
raise WebDriverException(
|
||||
"'%s' executable may have wrong permissions. %s" % (
|
||||
os.path.basename(self.path), self.start_error_message)
|
||||
)
|
||||
else:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise WebDriverException(
|
||||
"The executable %s needs to be available in the path. %s\n%s" %
|
||||
(os.path.basename(self.path), self.start_error_message, str(e)))
|
||||
count = 0
|
||||
while True:
|
||||
self.assert_process_still_running()
|
||||
if self.is_connectable():
|
||||
break
|
||||
count += 1
|
||||
time.sleep(1)
|
||||
if count == 30:
|
||||
raise WebDriverException("Can not connect to the Service %s" % self.path)
|
||||
|
||||
def assert_process_still_running(self):
|
||||
return_code = self.process.poll()
|
||||
if return_code is not None:
|
||||
raise WebDriverException(
|
||||
'Service %s unexpectedly exited. Status code was: %s'
|
||||
% (self.path, return_code)
|
||||
)
|
||||
|
||||
def is_connectable(self):
|
||||
return utils.is_connectable(self.port)
|
||||
|
||||
def send_remote_shutdown_command(self):
|
||||
try:
|
||||
from urllib import request as url_request
|
||||
URLError = url_request.URLError
|
||||
except ImportError:
|
||||
import urllib2 as url_request
|
||||
import urllib2
|
||||
URLError = urllib2.URLError
|
||||
|
||||
try:
|
||||
url_request.urlopen("%s/shutdown" % self.service_url)
|
||||
except URLError:
|
||||
return
|
||||
|
||||
for x in range(30):
|
||||
if not self.is_connectable():
|
||||
break
|
||||
else:
|
||||
time.sleep(1)
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Stops the service.
|
||||
"""
|
||||
if self.log_file != PIPE and not (self.log_file == DEVNULL and _HAS_NATIVE_DEVNULL):
|
||||
try:
|
||||
self.log_file.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if self.process is None:
|
||||
return
|
||||
|
||||
try:
|
||||
self.send_remote_shutdown_command()
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
if self.process:
|
||||
for stream in [self.process.stdin,
|
||||
self.process.stdout,
|
||||
self.process.stderr]:
|
||||
try:
|
||||
stream.close()
|
||||
except AttributeError:
|
||||
pass
|
||||
self.process.terminate()
|
||||
self.process.wait()
|
||||
self.process.kill()
|
||||
self.process = None
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def __del__(self):
|
||||
# `subprocess.Popen` doesn't send signal on `__del__`;
|
||||
# so we attempt to close the launched process when `__del__`
|
||||
# is triggered.
|
||||
try:
|
||||
self.stop()
|
||||
except Exception:
|
||||
pass
|
||||
|
|
@ -0,0 +1,192 @@
|
|||
# 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 Touch Actions implementation
|
||||
"""
|
||||
|
||||
from selenium.webdriver.remote.command import Command
|
||||
|
||||
|
||||
class TouchActions(object):
|
||||
"""
|
||||
Generate touch actions. Works like ActionChains; actions are stored in the
|
||||
TouchActions object and are fired with perform().
|
||||
"""
|
||||
|
||||
def __init__(self, driver):
|
||||
"""
|
||||
Creates a new TouchActions object.
|
||||
|
||||
:Args:
|
||||
- driver: The WebDriver instance which performs user actions.
|
||||
It should be with touchscreen enabled.
|
||||
"""
|
||||
self._driver = driver
|
||||
self._actions = []
|
||||
|
||||
def perform(self):
|
||||
"""
|
||||
Performs all stored actions.
|
||||
"""
|
||||
for action in self._actions:
|
||||
action()
|
||||
|
||||
def tap(self, on_element):
|
||||
"""
|
||||
Taps on a given element.
|
||||
|
||||
:Args:
|
||||
- on_element: The element to tap.
|
||||
"""
|
||||
self._actions.append(lambda: self._driver.execute(
|
||||
Command.SINGLE_TAP, {'element': on_element.id}))
|
||||
return self
|
||||
|
||||
def double_tap(self, on_element):
|
||||
"""
|
||||
Double taps on a given element.
|
||||
|
||||
:Args:
|
||||
- on_element: The element to tap.
|
||||
"""
|
||||
self._actions.append(lambda: self._driver.execute(
|
||||
Command.DOUBLE_TAP, {'element': on_element.id}))
|
||||
return self
|
||||
|
||||
def tap_and_hold(self, xcoord, ycoord):
|
||||
"""
|
||||
Touch down at given coordinates.
|
||||
|
||||
:Args:
|
||||
- xcoord: X Coordinate to touch down.
|
||||
- ycoord: Y Coordinate to touch down.
|
||||
"""
|
||||
self._actions.append(lambda: self._driver.execute(
|
||||
Command.TOUCH_DOWN, {
|
||||
'x': int(xcoord),
|
||||
'y': int(ycoord)}))
|
||||
return self
|
||||
|
||||
def move(self, xcoord, ycoord):
|
||||
"""
|
||||
Move held tap to specified location.
|
||||
|
||||
:Args:
|
||||
- xcoord: X Coordinate to move.
|
||||
- ycoord: Y Coordinate to move.
|
||||
"""
|
||||
self._actions.append(lambda: self._driver.execute(
|
||||
Command.TOUCH_MOVE, {
|
||||
'x': int(xcoord),
|
||||
'y': int(ycoord)}))
|
||||
return self
|
||||
|
||||
def release(self, xcoord, ycoord):
|
||||
"""
|
||||
Release previously issued tap 'and hold' command at specified location.
|
||||
|
||||
:Args:
|
||||
- xcoord: X Coordinate to release.
|
||||
- ycoord: Y Coordinate to release.
|
||||
"""
|
||||
self._actions.append(lambda: self._driver.execute(
|
||||
Command.TOUCH_UP, {
|
||||
'x': int(xcoord),
|
||||
'y': int(ycoord)}))
|
||||
return self
|
||||
|
||||
def scroll(self, xoffset, yoffset):
|
||||
"""
|
||||
Touch and scroll, moving by xoffset and yoffset.
|
||||
|
||||
:Args:
|
||||
- xoffset: X offset to scroll to.
|
||||
- yoffset: Y offset to scroll to.
|
||||
"""
|
||||
self._actions.append(lambda: self._driver.execute(
|
||||
Command.TOUCH_SCROLL, {
|
||||
'xoffset': int(xoffset),
|
||||
'yoffset': int(yoffset)}))
|
||||
return self
|
||||
|
||||
def scroll_from_element(self, on_element, xoffset, yoffset):
|
||||
"""
|
||||
Touch and scroll starting at on_element, moving by xoffset and yoffset.
|
||||
|
||||
:Args:
|
||||
- on_element: The element where scroll starts.
|
||||
- xoffset: X offset to scroll to.
|
||||
- yoffset: Y offset to scroll to.
|
||||
"""
|
||||
self._actions.append(lambda: self._driver.execute(
|
||||
Command.TOUCH_SCROLL, {
|
||||
'element': on_element.id,
|
||||
'xoffset': int(xoffset),
|
||||
'yoffset': int(yoffset)}))
|
||||
return self
|
||||
|
||||
def long_press(self, on_element):
|
||||
"""
|
||||
Long press on an element.
|
||||
|
||||
:Args:
|
||||
- on_element: The element to long press.
|
||||
"""
|
||||
self._actions.append(lambda: self._driver.execute(
|
||||
Command.LONG_PRESS, {'element': on_element.id}))
|
||||
return self
|
||||
|
||||
def flick(self, xspeed, yspeed):
|
||||
"""
|
||||
Flicks, starting anywhere on the screen.
|
||||
|
||||
:Args:
|
||||
- xspeed: The X speed in pixels per second.
|
||||
- yspeed: The Y speed in pixels per second.
|
||||
"""
|
||||
self._actions.append(lambda: self._driver.execute(
|
||||
Command.FLICK, {
|
||||
'xspeed': int(xspeed),
|
||||
'yspeed': int(yspeed)}))
|
||||
return self
|
||||
|
||||
def flick_element(self, on_element, xoffset, yoffset, speed):
|
||||
"""
|
||||
Flick starting at on_element, and moving by the xoffset and yoffset
|
||||
with specified speed.
|
||||
|
||||
:Args:
|
||||
- on_element: Flick will start at center of element.
|
||||
- xoffset: X offset to flick to.
|
||||
- yoffset: Y offset to flick to.
|
||||
- speed: Pixels per second to flick.
|
||||
"""
|
||||
self._actions.append(lambda: self._driver.execute(
|
||||
Command.FLICK, {
|
||||
'element': on_element.id,
|
||||
'xoffset': int(xoffset),
|
||||
'yoffset': int(yoffset),
|
||||
'speed': int(speed)}))
|
||||
return self
|
||||
|
||||
# Context manager so TouchActions 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.
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
# 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 Utils methods.
|
||||
"""
|
||||
import socket
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
|
||||
try:
|
||||
basestring
|
||||
except NameError:
|
||||
# Python 3
|
||||
basestring = str
|
||||
|
||||
|
||||
def free_port():
|
||||
"""
|
||||
Determines a free port using sockets.
|
||||
"""
|
||||
free_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
free_socket.bind(('0.0.0.0', 0))
|
||||
free_socket.listen(5)
|
||||
port = free_socket.getsockname()[1]
|
||||
free_socket.close()
|
||||
return port
|
||||
|
||||
|
||||
def find_connectable_ip(host, port=None):
|
||||
"""Resolve a hostname to an IP, preferring IPv4 addresses.
|
||||
|
||||
We prefer IPv4 so that we don't change behavior from previous IPv4-only
|
||||
implementations, and because some drivers (e.g., FirefoxDriver) do not
|
||||
support IPv6 connections.
|
||||
|
||||
If the optional port number is provided, only IPs that listen on the given
|
||||
port are considered.
|
||||
|
||||
:Args:
|
||||
- host - A hostname.
|
||||
- port - Optional port number.
|
||||
|
||||
:Returns:
|
||||
A single IP address, as a string. If any IPv4 address is found, one is
|
||||
returned. Otherwise, if any IPv6 address is found, one is returned. If
|
||||
neither, then None is returned.
|
||||
|
||||
"""
|
||||
try:
|
||||
addrinfos = socket.getaddrinfo(host, None)
|
||||
except socket.gaierror:
|
||||
return None
|
||||
|
||||
ip = None
|
||||
for family, _, _, _, sockaddr in addrinfos:
|
||||
connectable = True
|
||||
if port:
|
||||
connectable = is_connectable(port, sockaddr[0])
|
||||
|
||||
if connectable and family == socket.AF_INET:
|
||||
return sockaddr[0]
|
||||
if connectable and not ip and family == socket.AF_INET6:
|
||||
ip = sockaddr[0]
|
||||
return ip
|
||||
|
||||
|
||||
def join_host_port(host, port):
|
||||
"""Joins a hostname and port together.
|
||||
|
||||
This is a minimal implementation intended to cope with IPv6 literals. For
|
||||
example, _join_host_port('::1', 80) == '[::1]:80'.
|
||||
|
||||
:Args:
|
||||
- host - A hostname.
|
||||
- port - An integer port.
|
||||
|
||||
"""
|
||||
if ':' in host and not host.startswith('['):
|
||||
return '[%s]:%d' % (host, port)
|
||||
return '%s:%d' % (host, port)
|
||||
|
||||
|
||||
def is_connectable(port, host="localhost"):
|
||||
"""
|
||||
Tries to connect to the server at port to see if it is running.
|
||||
|
||||
:Args:
|
||||
- port - The port to connect.
|
||||
"""
|
||||
socket_ = None
|
||||
try:
|
||||
socket_ = socket.create_connection((host, port), 1)
|
||||
result = True
|
||||
except socket.error:
|
||||
result = False
|
||||
finally:
|
||||
if socket_:
|
||||
socket_.close()
|
||||
return result
|
||||
|
||||
|
||||
def is_url_connectable(port):
|
||||
"""
|
||||
Tries to connect to the HTTP server at /status path
|
||||
and specified port to see if it responds successfully.
|
||||
|
||||
:Args:
|
||||
- port - The port to connect.
|
||||
"""
|
||||
try:
|
||||
from urllib import request as url_request
|
||||
except ImportError:
|
||||
import urllib2 as url_request
|
||||
|
||||
try:
|
||||
res = url_request.urlopen("http://127.0.0.1:%s/status" % port)
|
||||
if res.getcode() == 200:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def keys_to_typing(value):
|
||||
"""Processes the values that will be typed in the element."""
|
||||
typing = []
|
||||
for val in value:
|
||||
if isinstance(val, Keys):
|
||||
typing.append(val)
|
||||
elif isinstance(val, int):
|
||||
val = str(val)
|
||||
for i in range(len(val)):
|
||||
typing.append(val[i])
|
||||
else:
|
||||
for i in range(len(val)):
|
||||
typing.append(val[i])
|
||||
return typing
|
||||
Loading…
Add table
Add a link
Reference in a new issue