Ausgabe der neuen DB Einträge

This commit is contained in:
hubobel 2022-01-02 21:50:48 +01:00
parent bad48e1627
commit cfbbb9ee3d
2399 changed files with 843193 additions and 43 deletions

View file

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

View file

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

View file

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

View file

@ -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)

View file

@ -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

View file

@ -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)
}

View file

@ -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

View file

@ -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}

View file

@ -0,0 +1,5 @@
class MouseButton(object):
LEFT = 0
MIDDLE = 1
RIGHT = 2

View file

@ -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

View file

@ -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]}

View file

@ -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})

View file

@ -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"

View file

@ -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",
}

View file

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

View file

@ -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']

View file

@ -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'

View file

@ -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

View file

@ -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

View file

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

View file

@ -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