moving to scripts
This commit is contained in:
@@ -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.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,329 @@
|
||||
# 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,
|
||||
"""
|
||||
|
||||
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, duration=250):
|
||||
"""
|
||||
Creates a new ActionChains.
|
||||
|
||||
:Args:
|
||||
- driver: The WebDriver instance which performs user actions.
|
||||
- duration: override the default 250 msecs of DEFAULT_MOVE_DURATION in PointerInput
|
||||
"""
|
||||
self._driver = driver
|
||||
self._actions = []
|
||||
self.w3c_actions = ActionBuilder(driver, duration=duration)
|
||||
|
||||
def perform(self):
|
||||
"""
|
||||
Performs all stored actions.
|
||||
"""
|
||||
self.w3c_actions.perform()
|
||||
|
||||
def reset_actions(self):
|
||||
"""
|
||||
Clears actions that are already stored locally and on the remote end
|
||||
"""
|
||||
self.w3c_actions.clear_actions()
|
||||
for device in self.w3c_actions.devices:
|
||||
device.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)
|
||||
|
||||
self.w3c_actions.pointer_action.click()
|
||||
self.w3c_actions.key_action.pause()
|
||||
self.w3c_actions.key_action.pause()
|
||||
|
||||
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)
|
||||
|
||||
self.w3c_actions.pointer_action.click_and_hold()
|
||||
self.w3c_actions.key_action.pause()
|
||||
|
||||
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)
|
||||
|
||||
self.w3c_actions.pointer_action.context_click()
|
||||
self.w3c_actions.key_action.pause()
|
||||
self.w3c_actions.key_action.pause()
|
||||
|
||||
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)
|
||||
|
||||
self.w3c_actions.pointer_action.double_click()
|
||||
for _ in range(4):
|
||||
self.w3c_actions.key_action.pause()
|
||||
|
||||
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)
|
||||
|
||||
self.w3c_actions.key_action.key_down(value)
|
||||
self.w3c_actions.pointer_action.pause()
|
||||
|
||||
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)
|
||||
|
||||
self.w3c_actions.key_action.key_up(value)
|
||||
self.w3c_actions.pointer_action.pause()
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
self.w3c_actions.pointer_action.move_by(xoffset, yoffset)
|
||||
self.w3c_actions.key_action.pause()
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
self.w3c_actions.pointer_action.move_to(to_element)
|
||||
self.w3c_actions.key_action.pause()
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
self.w3c_actions.pointer_action.move_to(to_element,
|
||||
int(xoffset),
|
||||
int(yoffset))
|
||||
self.w3c_actions.key_action.pause()
|
||||
|
||||
return self
|
||||
|
||||
def pause(self, seconds):
|
||||
""" Pause all inputs for the specified duration in seconds """
|
||||
|
||||
self.w3c_actions.pointer_action.pause(seconds)
|
||||
self.w3c_actions.key_action.pause(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)
|
||||
|
||||
self.w3c_actions.pointer_action.release()
|
||||
self.w3c_actions.key_action.pause()
|
||||
|
||||
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)
|
||||
|
||||
for key in typing:
|
||||
self.key_down(key)
|
||||
self.key_up(key)
|
||||
|
||||
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.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,86 @@
|
||||
# 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, duration=250):
|
||||
if not mouse:
|
||||
mouse = PointerInput(interaction.POINTER_MOUSE, "mouse")
|
||||
if not keyboard:
|
||||
keyboard = KeyInput(interaction.KEY)
|
||||
self.devices = [mouse, keyboard]
|
||||
self._key_action = KeyActions(keyboard)
|
||||
self._pointer_action = PointerActions(mouse, duration=duration)
|
||||
self.driver = driver
|
||||
|
||||
def get_device_with(self, name):
|
||||
try:
|
||||
idx = self.devices.index(name)
|
||||
return self.devices[idx]
|
||||
except Exception:
|
||||
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)
|
||||
device.actions = []
|
||||
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 not name:
|
||||
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, duration=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 not source:
|
||||
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,23 @@
|
||||
# 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.
|
||||
|
||||
|
||||
class MouseButton(object):
|
||||
|
||||
LEFT = 0
|
||||
MIDDLE = 1
|
||||
RIGHT = 2
|
||||
@@ -0,0 +1,109 @@
|
||||
# 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, duration=250):
|
||||
"""
|
||||
Args:
|
||||
- source: PointerInput instance
|
||||
- duration: override the default 250 msecs of DEFAULT_MOVE_DURATION in source
|
||||
"""
|
||||
if not source:
|
||||
source = PointerInput(interaction.POINTER_MOUSE, "mouse")
|
||||
self.source = source
|
||||
self._duration = duration
|
||||
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 or y:
|
||||
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, duration=self._duration, x=int(left), y=int(top))
|
||||
return self
|
||||
|
||||
def move_by(self, x, y):
|
||||
self.source.create_pointer_move(origin=interaction.POINTER, duration=self._duration, x=int(x), y=int(y))
|
||||
return self
|
||||
|
||||
def move_to_location(self, x, y):
|
||||
self.source.create_pointer_move(origin='viewport', duration=self._duration, 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.pointer_down(MouseButton.LEFT)
|
||||
self.pointer_up(MouseButton.LEFT)
|
||||
self.pointer_down(MouseButton.LEFT)
|
||||
self.pointer_up(MouseButton.LEFT)
|
||||
return self
|
||||
|
||||
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:
|
||||
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,90 @@
|
||||
# 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.
|
||||
"""
|
||||
return self.driver.execute(Command.W3C_GET_ALERT_TEXT)["value"]
|
||||
|
||||
def dismiss(self):
|
||||
"""
|
||||
Dismisses the alert available.
|
||||
"""
|
||||
self.driver.execute(Command.W3C_DISMISS_ALERT)
|
||||
|
||||
def accept(self):
|
||||
"""
|
||||
Accepts the alert available.
|
||||
|
||||
Usage::
|
||||
Alert(driver).accept() # Confirm a alert dialog.
|
||||
"""
|
||||
self.driver.execute(Command.W3C_ACCEPT_ALERT)
|
||||
|
||||
def send_keys(self, keysToSend):
|
||||
"""
|
||||
Send Keys to the Alert.
|
||||
|
||||
:Args:
|
||||
- keysToSend: The text to be sent to Alert.
|
||||
"""
|
||||
self.driver.execute(Command.W3C_SET_ALERT_VALUE, {'value': keys_to_typing(keysToSend), 'text': keysToSend})
|
||||
@@ -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.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,483 @@
|
||||
# The MIT License(MIT)
|
||||
#
|
||||
# Copyright(c) 2018 Hyperion Gray
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files(the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
# This code comes from https://github.com/HyperionGray/trio-chrome-devtools-protocol/tree/master/trio_cdp
|
||||
|
||||
# flake8: noqa
|
||||
|
||||
from trio_websocket import (
|
||||
ConnectionClosed as WsConnectionClosed,
|
||||
connect_websocket_url,
|
||||
)
|
||||
import trio
|
||||
from collections import defaultdict
|
||||
from contextlib import (contextmanager, asynccontextmanager)
|
||||
from dataclasses import dataclass
|
||||
import contextvars
|
||||
import importlib
|
||||
import itertools
|
||||
import json
|
||||
import logging
|
||||
import typing
|
||||
|
||||
|
||||
logger = logging.getLogger('trio_cdp')
|
||||
T = typing.TypeVar('T')
|
||||
MAX_WS_MESSAGE_SIZE = 2**24
|
||||
|
||||
devtools = None
|
||||
version = None
|
||||
|
||||
|
||||
def import_devtools(ver):
|
||||
global devtools
|
||||
global version
|
||||
version = ver
|
||||
devtools = importlib.import_module("selenium.webdriver.common.devtools.v{}".format(version))
|
||||
|
||||
|
||||
_connection_context: contextvars.ContextVar = contextvars.ContextVar('connection_context')
|
||||
_session_context: contextvars.ContextVar = contextvars.ContextVar('session_context')
|
||||
|
||||
|
||||
def get_connection_context(fn_name):
|
||||
'''
|
||||
Look up the current connection. If there is no current connection, raise a
|
||||
``RuntimeError`` with a helpful message.
|
||||
'''
|
||||
try:
|
||||
return _connection_context.get()
|
||||
except LookupError:
|
||||
raise RuntimeError(f'{fn_name}() must be called in a connection context.')
|
||||
|
||||
|
||||
def get_session_context(fn_name):
|
||||
'''
|
||||
Look up the current session. If there is no current session, raise a
|
||||
``RuntimeError`` with a helpful message.
|
||||
'''
|
||||
try:
|
||||
return _session_context.get()
|
||||
except LookupError:
|
||||
raise RuntimeError(f'{fn_name}() must be called in a session context.')
|
||||
|
||||
|
||||
@contextmanager
|
||||
def connection_context(connection):
|
||||
''' This context manager installs ``connection`` as the session context for the current
|
||||
Trio task. '''
|
||||
token = _connection_context.set(connection)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
_connection_context.reset(token)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def session_context(session):
|
||||
''' This context manager installs ``session`` as the session context for the current
|
||||
Trio task. '''
|
||||
token = _session_context.set(session)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
_session_context.reset(token)
|
||||
|
||||
|
||||
def set_global_connection(connection):
|
||||
'''
|
||||
Install ``connection`` in the root context so that it will become the default
|
||||
connection for all tasks. This is generally not recommended, except it may be
|
||||
necessary in certain use cases such as running inside Jupyter notebook.
|
||||
'''
|
||||
global _connection_context
|
||||
_connection_context = contextvars.ContextVar('_connection_context',
|
||||
default=connection)
|
||||
|
||||
|
||||
def set_global_session(session):
|
||||
'''
|
||||
Install ``session`` in the root context so that it will become the default
|
||||
session for all tasks. This is generally not recommended, except it may be
|
||||
necessary in certain use cases such as running inside Jupyter notebook.
|
||||
'''
|
||||
global _session_context
|
||||
_session_context = contextvars.ContextVar('_session_context', default=session)
|
||||
|
||||
|
||||
class BrowserError(Exception):
|
||||
''' This exception is raised when the browser's response to a command
|
||||
indicates that an error occurred. '''
|
||||
|
||||
def __init__(self, obj):
|
||||
self.code = obj['code']
|
||||
self.message = obj['message']
|
||||
self.detail = obj.get('data')
|
||||
|
||||
def __str__(self):
|
||||
return 'BrowserError<code={} message={}> {}'.format(self.code,
|
||||
self.message, self.detail)
|
||||
|
||||
|
||||
class CdpConnectionClosed(WsConnectionClosed):
|
||||
''' Raised when a public method is called on a closed CDP connection. '''
|
||||
|
||||
def __init__(self, reason):
|
||||
'''
|
||||
Constructor.
|
||||
:param reason:
|
||||
:type reason: wsproto.frame_protocol.CloseReason
|
||||
'''
|
||||
self.reason = reason
|
||||
|
||||
def __repr__(self):
|
||||
''' Return representation. '''
|
||||
return '{}<{}>'.format(self.__class__.__name__, self.reason)
|
||||
|
||||
|
||||
class InternalError(Exception):
|
||||
''' This exception is only raised when there is faulty logic in TrioCDP or
|
||||
the integration with PyCDP. '''
|
||||
|
||||
|
||||
@dataclass
|
||||
class CmEventProxy:
|
||||
''' A proxy object returned by :meth:`CdpBase.wait_for()``. After the
|
||||
context manager executes, this proxy object will have a value set that
|
||||
contains the returned event. '''
|
||||
value: typing.Any = None
|
||||
|
||||
|
||||
class CdpBase:
|
||||
|
||||
def __init__(self, ws, session_id, target_id):
|
||||
self.ws = ws
|
||||
self.session_id = session_id
|
||||
self.target_id = target_id
|
||||
self.channels = defaultdict(set)
|
||||
self.id_iter = itertools.count()
|
||||
self.inflight_cmd = dict()
|
||||
self.inflight_result = dict()
|
||||
|
||||
async def execute(self, cmd: typing.Generator[dict, T, typing.Any]) -> T:
|
||||
'''
|
||||
Execute a command on the server and wait for the result.
|
||||
:param cmd: any CDP command
|
||||
:returns: a CDP result
|
||||
'''
|
||||
cmd_id = next(self.id_iter)
|
||||
cmd_event = trio.Event()
|
||||
self.inflight_cmd[cmd_id] = cmd, cmd_event
|
||||
request = next(cmd)
|
||||
request['id'] = cmd_id
|
||||
if self.session_id:
|
||||
request['sessionId'] = self.session_id
|
||||
request_str = json.dumps(request)
|
||||
try:
|
||||
await self.ws.send_message(request_str)
|
||||
except WsConnectionClosed as wcc:
|
||||
raise CdpConnectionClosed(wcc.reason) from None
|
||||
await cmd_event.wait()
|
||||
response = self.inflight_result.pop(cmd_id)
|
||||
if isinstance(response, Exception):
|
||||
raise response
|
||||
return response
|
||||
|
||||
def listen(self, *event_types, buffer_size=10):
|
||||
''' Return an async iterator that iterates over events matching the
|
||||
indicated types. '''
|
||||
sender, receiver = trio.open_memory_channel(buffer_size)
|
||||
for event_type in event_types:
|
||||
self.channels[event_type].add(sender)
|
||||
return receiver
|
||||
|
||||
@asynccontextmanager
|
||||
async def wait_for(self, event_type: typing.Type[T], buffer_size=10) -> \
|
||||
typing.AsyncGenerator[CmEventProxy, None]:
|
||||
'''
|
||||
Wait for an event of the given type and return it.
|
||||
This is an async context manager, so you should open it inside an async
|
||||
with block. The block will not exit until the indicated event is
|
||||
received.
|
||||
'''
|
||||
sender, receiver = trio.open_memory_channel(buffer_size)
|
||||
self.channels[event_type].add(sender)
|
||||
proxy = CmEventProxy()
|
||||
yield proxy
|
||||
async with receiver:
|
||||
event = await receiver.receive()
|
||||
proxy.value = event
|
||||
|
||||
def _handle_data(self, data):
|
||||
'''
|
||||
Handle incoming WebSocket data.
|
||||
:param dict data: a JSON dictionary
|
||||
'''
|
||||
if 'id' in data:
|
||||
self._handle_cmd_response(data)
|
||||
else:
|
||||
self._handle_event(data)
|
||||
|
||||
def _handle_cmd_response(self, data):
|
||||
'''
|
||||
Handle a response to a command. This will set an event flag that will
|
||||
return control to the task that called the command.
|
||||
:param dict data: response as a JSON dictionary
|
||||
'''
|
||||
cmd_id = data['id']
|
||||
try:
|
||||
cmd, event = self.inflight_cmd.pop(cmd_id)
|
||||
except KeyError:
|
||||
logger.warning('Got a message with a command ID that does'
|
||||
' not exist: {}'.format(data))
|
||||
return
|
||||
if 'error' in data:
|
||||
# If the server reported an error, convert it to an exception and do
|
||||
# not process the response any further.
|
||||
self.inflight_result[cmd_id] = BrowserError(data['error'])
|
||||
else:
|
||||
# Otherwise, continue the generator to parse the JSON result
|
||||
# into a CDP object.
|
||||
try:
|
||||
response = cmd.send(data['result'])
|
||||
raise InternalError("The command's generator function "
|
||||
"did not exit when expected!")
|
||||
except StopIteration as exit:
|
||||
return_ = exit.value
|
||||
self.inflight_result[cmd_id] = return_
|
||||
event.set()
|
||||
|
||||
def _handle_event(self, data):
|
||||
'''
|
||||
Handle an event.
|
||||
:param dict data: event as a JSON dictionary
|
||||
'''
|
||||
global devtools
|
||||
event = devtools.util.parse_json_event(data)
|
||||
logger.debug('Received event: %s', event)
|
||||
to_remove = set()
|
||||
for sender in self.channels[type(event)]:
|
||||
try:
|
||||
sender.send_nowait(event)
|
||||
except trio.WouldBlock:
|
||||
logger.error('Unable to send event "%r" due to full channel %s',
|
||||
event, sender)
|
||||
except trio.BrokenResourceError:
|
||||
to_remove.add(sender)
|
||||
if to_remove:
|
||||
self.channels[type(event)] -= to_remove
|
||||
|
||||
|
||||
class CdpSession(CdpBase):
|
||||
'''
|
||||
Contains the state for a CDP session.
|
||||
Generally you should not instantiate this object yourself; you should call
|
||||
:meth:`CdpConnection.open_session`.
|
||||
'''
|
||||
|
||||
def __init__(self, ws, session_id, target_id):
|
||||
'''
|
||||
Constructor.
|
||||
:param trio_websocket.WebSocketConnection ws:
|
||||
:param devtools.target.SessionID session_id:
|
||||
:param devtools.target.TargetID target_id:
|
||||
'''
|
||||
super().__init__(ws, session_id, target_id)
|
||||
|
||||
self._dom_enable_count = 0
|
||||
self._dom_enable_lock = trio.Lock()
|
||||
self._page_enable_count = 0
|
||||
self._page_enable_lock = trio.Lock()
|
||||
|
||||
@asynccontextmanager
|
||||
async def dom_enable(self):
|
||||
'''
|
||||
A context manager that executes ``dom.enable()`` when it enters and then
|
||||
calls ``dom.disable()``.
|
||||
This keeps track of concurrent callers and only disables DOM events when
|
||||
all callers have exited.
|
||||
'''
|
||||
global devtools
|
||||
async with self._dom_enable_lock:
|
||||
self._dom_enable_count += 1
|
||||
if self._dom_enable_count == 1:
|
||||
await self.execute(devtools.dom.enable())
|
||||
|
||||
yield
|
||||
|
||||
async with self._dom_enable_lock:
|
||||
self._dom_enable_count -= 1
|
||||
if self._dom_enable_count == 0:
|
||||
await self.execute(devtools.dom.disable())
|
||||
|
||||
@asynccontextmanager
|
||||
async def page_enable(self):
|
||||
'''
|
||||
A context manager that executes ``page.enable()`` when it enters and
|
||||
then calls ``page.disable()`` when it exits.
|
||||
This keeps track of concurrent callers and only disables page events
|
||||
when all callers have exited.
|
||||
'''
|
||||
global devtools
|
||||
async with self._page_enable_lock:
|
||||
self._page_enable_count += 1
|
||||
if self._page_enable_count == 1:
|
||||
await self.execute(devtools.page.enable())
|
||||
|
||||
yield
|
||||
|
||||
async with self._page_enable_lock:
|
||||
self._page_enable_count -= 1
|
||||
if self._page_enable_count == 0:
|
||||
await self.execute(devtools.page.disable())
|
||||
|
||||
|
||||
class CdpConnection(CdpBase, trio.abc.AsyncResource):
|
||||
'''
|
||||
Contains the connection state for a Chrome DevTools Protocol server.
|
||||
CDP can multiplex multiple "sessions" over a single connection. This class
|
||||
corresponds to the "root" session, i.e. the implicitly created session that
|
||||
has no session ID. This class is responsible for reading incoming WebSocket
|
||||
messages and forwarding them to the corresponding session, as well as
|
||||
handling messages targeted at the root session itself.
|
||||
You should generally call the :func:`open_cdp()` instead of
|
||||
instantiating this class directly.
|
||||
'''
|
||||
|
||||
def __init__(self, ws):
|
||||
'''
|
||||
Constructor
|
||||
:param trio_websocket.WebSocketConnection ws:
|
||||
'''
|
||||
super().__init__(ws, session_id=None, target_id=None)
|
||||
self.sessions = dict()
|
||||
|
||||
async def aclose(self):
|
||||
'''
|
||||
Close the underlying WebSocket connection.
|
||||
This will cause the reader task to gracefully exit when it tries to read
|
||||
the next message from the WebSocket. All of the public APIs
|
||||
(``execute()``, ``listen()``, etc.) will raise
|
||||
``CdpConnectionClosed`` after the CDP connection is closed.
|
||||
It is safe to call this multiple times.
|
||||
'''
|
||||
await self.ws.aclose()
|
||||
|
||||
@asynccontextmanager
|
||||
async def open_session(self, target_id) -> \
|
||||
typing.AsyncIterator[CdpSession]:
|
||||
'''
|
||||
This context manager opens a session and enables the "simple" style of calling
|
||||
CDP APIs.
|
||||
For example, inside a session context, you can call ``await dom.get_document()``
|
||||
and it will execute on the current session automatically.
|
||||
'''
|
||||
session = await self.connect_session(target_id)
|
||||
with session_context(session):
|
||||
yield session
|
||||
|
||||
async def connect_session(self, target_id) -> 'CdpSession':
|
||||
'''
|
||||
Returns a new :class:`CdpSession` connected to the specified target.
|
||||
'''
|
||||
global devtools
|
||||
session_id = await self.execute(devtools.target.attach_to_target(
|
||||
target_id, True))
|
||||
session = CdpSession(self.ws, session_id, target_id)
|
||||
self.sessions[session_id] = session
|
||||
return session
|
||||
|
||||
async def _reader_task(self):
|
||||
'''
|
||||
Runs in the background and handles incoming messages: dispatching
|
||||
responses to commands and events to listeners.
|
||||
'''
|
||||
global devtools
|
||||
while True:
|
||||
try:
|
||||
message = await self.ws.get_message()
|
||||
except WsConnectionClosed:
|
||||
# If the WebSocket is closed, we don't want to throw an
|
||||
# exception from the reader task. Instead we will throw
|
||||
# exceptions from the public API methods, and we can quietly
|
||||
# exit the reader task here.
|
||||
break
|
||||
try:
|
||||
data = json.loads(message)
|
||||
except json.JSONDecodeError:
|
||||
raise BrowserError({
|
||||
'code': -32700,
|
||||
'message': 'Client received invalid JSON',
|
||||
'data': message
|
||||
})
|
||||
logger.debug('Received message %r', data)
|
||||
if 'sessionId' in data:
|
||||
session_id = devtools.target.SessionID(data['sessionId'])
|
||||
try:
|
||||
session = self.sessions[session_id]
|
||||
except KeyError:
|
||||
raise BrowserError('Browser sent a message for an invalid '
|
||||
'session: {!r}'.format(session_id))
|
||||
session._handle_data(data)
|
||||
else:
|
||||
self._handle_data(data)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def open_cdp(url) -> typing.AsyncIterator[CdpConnection]:
|
||||
'''
|
||||
This async context manager opens a connection to the browser specified by
|
||||
``url`` before entering the block, then closes the connection when the block
|
||||
exits.
|
||||
The context manager also sets the connection as the default connection for the
|
||||
current task, so that commands like ``await target.get_targets()`` will run on this
|
||||
connection automatically. If you want to use multiple connections concurrently, it
|
||||
is recommended to open each on in a separate task.
|
||||
'''
|
||||
|
||||
async with trio.open_nursery() as nursery:
|
||||
conn = await connect_cdp(nursery, url)
|
||||
try:
|
||||
with connection_context(conn):
|
||||
yield conn
|
||||
finally:
|
||||
await conn.aclose()
|
||||
|
||||
|
||||
async def connect_cdp(nursery, url) -> CdpConnection:
|
||||
'''
|
||||
Connect to the browser specified by ``url`` and spawn a background task in the
|
||||
specified nursery.
|
||||
The ``open_cdp()`` context manager is preferred in most situations. You should only
|
||||
use this function if you need to specify a custom nursery.
|
||||
This connection is not automatically closed! You can either use the connection
|
||||
object as a context manager (``async with conn:``) or else call ``await
|
||||
conn.aclose()`` on it when you are done with it.
|
||||
If ``set_context`` is True, then the returned connection will be installed as
|
||||
the default connection for the current task. This argument is for unusual use cases,
|
||||
such as running inside of a notebook.
|
||||
'''
|
||||
ws = await connect_websocket_url(nursery, url,
|
||||
max_message_size=MAX_WS_MESSAGE_SIZE)
|
||||
cdp_conn = CdpConnection(ws)
|
||||
nursery.start_soon(cdp_conn._reader_task)
|
||||
return cdp_conn
|
||||
@@ -0,0 +1,25 @@
|
||||
# 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 enum import Enum
|
||||
|
||||
|
||||
class Console(Enum):
|
||||
|
||||
ALL = "all"
|
||||
LOG = "log"
|
||||
ERROR = "error"
|
||||
@@ -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,113 @@
|
||||
# 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",
|
||||
"acceptInsecureCerts": True,
|
||||
"moz:debuggerAddress": True,
|
||||
}
|
||||
|
||||
INTERNETEXPLORER = {
|
||||
"browserName": "internet explorer",
|
||||
"platformName": "windows",
|
||||
}
|
||||
|
||||
EDGE = {
|
||||
"browserName": "MicrosoftEdge",
|
||||
}
|
||||
|
||||
CHROME = {
|
||||
"browserName": "chrome",
|
||||
}
|
||||
|
||||
OPERA = {
|
||||
"browserName": "opera",
|
||||
}
|
||||
|
||||
SAFARI = {
|
||||
"browserName": "safari",
|
||||
"platformName": "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",
|
||||
}
|
||||
|
||||
WEBKITGTK = {
|
||||
"browserName": "MiniBrowser",
|
||||
"version": "",
|
||||
"platform": "ANY",
|
||||
}
|
||||
|
||||
WPEWEBKIT = {
|
||||
"browserName": "MiniBrowser",
|
||||
"version": "",
|
||||
"platform": "ANY",
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
# DO NOT EDIT THIS FILE!
|
||||
#
|
||||
# This file is generated from the CDP specification. If you need to make
|
||||
# changes, edit the generator and regenerate all of the modules.
|
||||
from . import accessibility
|
||||
from . import animation
|
||||
from . import application_cache
|
||||
from . import audits
|
||||
from . import background_service
|
||||
from . import browser
|
||||
from . import css
|
||||
from . import cache_storage
|
||||
from . import cast
|
||||
from . import console
|
||||
from . import dom
|
||||
from . import dom_debugger
|
||||
from . import dom_snapshot
|
||||
from . import dom_storage
|
||||
from . import database
|
||||
from . import debugger
|
||||
from . import device_orientation
|
||||
from . import emulation
|
||||
from . import fetch
|
||||
from . import headless_experimental
|
||||
from . import heap_profiler
|
||||
from . import io
|
||||
from . import indexed_db
|
||||
from . import input_
|
||||
from . import inspector
|
||||
from . import layer_tree
|
||||
from . import log
|
||||
from . import media
|
||||
from . import memory
|
||||
from . import network
|
||||
from . import overlay
|
||||
from . import page
|
||||
from . import performance
|
||||
from . import profiler
|
||||
from . import runtime
|
||||
from . import schema
|
||||
from . import security
|
||||
from . import service_worker
|
||||
from . import storage
|
||||
from . import system_info
|
||||
from . import target
|
||||
from . import tethering
|
||||
from . import tracing
|
||||
from . import web_audio
|
||||
from . import web_authn
|
||||
from . import util
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,455 @@
|
||||
# DO NOT EDIT THIS FILE!
|
||||
#
|
||||
# This file is generated from the CDP specification. If you need to make
|
||||
# changes, edit the generator and regenerate all of the modules.
|
||||
#
|
||||
# CDP domain: Accessibility (experimental)
|
||||
from __future__ import annotations
|
||||
from .util import event_class, T_JSON_DICT
|
||||
from dataclasses import dataclass
|
||||
import enum
|
||||
import typing
|
||||
from . import dom
|
||||
from . import runtime
|
||||
|
||||
|
||||
class AXNodeId(str):
|
||||
'''
|
||||
Unique accessibility node identifier.
|
||||
'''
|
||||
def to_json(self) -> str:
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json: str) -> AXNodeId:
|
||||
return cls(json)
|
||||
|
||||
def __repr__(self):
|
||||
return 'AXNodeId({})'.format(super().__repr__())
|
||||
|
||||
|
||||
class AXValueType(enum.Enum):
|
||||
'''
|
||||
Enum of possible property types.
|
||||
'''
|
||||
BOOLEAN = "boolean"
|
||||
TRISTATE = "tristate"
|
||||
BOOLEAN_OR_UNDEFINED = "booleanOrUndefined"
|
||||
IDREF = "idref"
|
||||
IDREF_LIST = "idrefList"
|
||||
INTEGER = "integer"
|
||||
NODE = "node"
|
||||
NODE_LIST = "nodeList"
|
||||
NUMBER = "number"
|
||||
STRING = "string"
|
||||
COMPUTED_STRING = "computedString"
|
||||
TOKEN = "token"
|
||||
TOKEN_LIST = "tokenList"
|
||||
DOM_RELATION = "domRelation"
|
||||
ROLE = "role"
|
||||
INTERNAL_ROLE = "internalRole"
|
||||
VALUE_UNDEFINED = "valueUndefined"
|
||||
|
||||
def to_json(self):
|
||||
return self.value
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(json)
|
||||
|
||||
|
||||
class AXValueSourceType(enum.Enum):
|
||||
'''
|
||||
Enum of possible property sources.
|
||||
'''
|
||||
ATTRIBUTE = "attribute"
|
||||
IMPLICIT = "implicit"
|
||||
STYLE = "style"
|
||||
CONTENTS = "contents"
|
||||
PLACEHOLDER = "placeholder"
|
||||
RELATED_ELEMENT = "relatedElement"
|
||||
|
||||
def to_json(self):
|
||||
return self.value
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(json)
|
||||
|
||||
|
||||
class AXValueNativeSourceType(enum.Enum):
|
||||
'''
|
||||
Enum of possible native property sources (as a subtype of a particular AXValueSourceType).
|
||||
'''
|
||||
FIGCAPTION = "figcaption"
|
||||
LABEL = "label"
|
||||
LABELFOR = "labelfor"
|
||||
LABELWRAPPED = "labelwrapped"
|
||||
LEGEND = "legend"
|
||||
TABLECAPTION = "tablecaption"
|
||||
TITLE = "title"
|
||||
OTHER = "other"
|
||||
|
||||
def to_json(self):
|
||||
return self.value
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(json)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AXValueSource:
|
||||
'''
|
||||
A single source for a computed AX property.
|
||||
'''
|
||||
#: What type of source this is.
|
||||
type_: AXValueSourceType
|
||||
|
||||
#: The value of this property source.
|
||||
value: typing.Optional[AXValue] = None
|
||||
|
||||
#: The name of the relevant attribute, if any.
|
||||
attribute: typing.Optional[str] = None
|
||||
|
||||
#: The value of the relevant attribute, if any.
|
||||
attribute_value: typing.Optional[AXValue] = None
|
||||
|
||||
#: Whether this source is superseded by a higher priority source.
|
||||
superseded: typing.Optional[bool] = None
|
||||
|
||||
#: The native markup source for this value, e.g. a <label> element.
|
||||
native_source: typing.Optional[AXValueNativeSourceType] = None
|
||||
|
||||
#: The value, such as a node or node list, of the native source.
|
||||
native_source_value: typing.Optional[AXValue] = None
|
||||
|
||||
#: Whether the value for this property is invalid.
|
||||
invalid: typing.Optional[bool] = None
|
||||
|
||||
#: Reason for the value being invalid, if it is.
|
||||
invalid_reason: typing.Optional[str] = None
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['type'] = self.type_.to_json()
|
||||
if self.value is not None:
|
||||
json['value'] = self.value.to_json()
|
||||
if self.attribute is not None:
|
||||
json['attribute'] = self.attribute
|
||||
if self.attribute_value is not None:
|
||||
json['attributeValue'] = self.attribute_value.to_json()
|
||||
if self.superseded is not None:
|
||||
json['superseded'] = self.superseded
|
||||
if self.native_source is not None:
|
||||
json['nativeSource'] = self.native_source.to_json()
|
||||
if self.native_source_value is not None:
|
||||
json['nativeSourceValue'] = self.native_source_value.to_json()
|
||||
if self.invalid is not None:
|
||||
json['invalid'] = self.invalid
|
||||
if self.invalid_reason is not None:
|
||||
json['invalidReason'] = self.invalid_reason
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
type_=AXValueSourceType.from_json(json['type']),
|
||||
value=AXValue.from_json(json['value']) if 'value' in json else None,
|
||||
attribute=str(json['attribute']) if 'attribute' in json else None,
|
||||
attribute_value=AXValue.from_json(json['attributeValue']) if 'attributeValue' in json else None,
|
||||
superseded=bool(json['superseded']) if 'superseded' in json else None,
|
||||
native_source=AXValueNativeSourceType.from_json(json['nativeSource']) if 'nativeSource' in json else None,
|
||||
native_source_value=AXValue.from_json(json['nativeSourceValue']) if 'nativeSourceValue' in json else None,
|
||||
invalid=bool(json['invalid']) if 'invalid' in json else None,
|
||||
invalid_reason=str(json['invalidReason']) if 'invalidReason' in json else None,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AXRelatedNode:
|
||||
#: The BackendNodeId of the related DOM node.
|
||||
backend_dom_node_id: dom.BackendNodeId
|
||||
|
||||
#: The IDRef value provided, if any.
|
||||
idref: typing.Optional[str] = None
|
||||
|
||||
#: The text alternative of this node in the current context.
|
||||
text: typing.Optional[str] = None
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['backendDOMNodeId'] = self.backend_dom_node_id.to_json()
|
||||
if self.idref is not None:
|
||||
json['idref'] = self.idref
|
||||
if self.text is not None:
|
||||
json['text'] = self.text
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
backend_dom_node_id=dom.BackendNodeId.from_json(json['backendDOMNodeId']),
|
||||
idref=str(json['idref']) if 'idref' in json else None,
|
||||
text=str(json['text']) if 'text' in json else None,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AXProperty:
|
||||
#: The name of this property.
|
||||
name: AXPropertyName
|
||||
|
||||
#: The value of this property.
|
||||
value: AXValue
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['name'] = self.name.to_json()
|
||||
json['value'] = self.value.to_json()
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
name=AXPropertyName.from_json(json['name']),
|
||||
value=AXValue.from_json(json['value']),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AXValue:
|
||||
'''
|
||||
A single computed AX property.
|
||||
'''
|
||||
#: The type of this value.
|
||||
type_: AXValueType
|
||||
|
||||
#: The computed value of this property.
|
||||
value: typing.Optional[typing.Any] = None
|
||||
|
||||
#: One or more related nodes, if applicable.
|
||||
related_nodes: typing.Optional[typing.List[AXRelatedNode]] = None
|
||||
|
||||
#: The sources which contributed to the computation of this property.
|
||||
sources: typing.Optional[typing.List[AXValueSource]] = None
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['type'] = self.type_.to_json()
|
||||
if self.value is not None:
|
||||
json['value'] = self.value
|
||||
if self.related_nodes is not None:
|
||||
json['relatedNodes'] = [i.to_json() for i in self.related_nodes]
|
||||
if self.sources is not None:
|
||||
json['sources'] = [i.to_json() for i in self.sources]
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
type_=AXValueType.from_json(json['type']),
|
||||
value=json['value'] if 'value' in json else None,
|
||||
related_nodes=[AXRelatedNode.from_json(i) for i in json['relatedNodes']] if 'relatedNodes' in json else None,
|
||||
sources=[AXValueSource.from_json(i) for i in json['sources']] if 'sources' in json else None,
|
||||
)
|
||||
|
||||
|
||||
class AXPropertyName(enum.Enum):
|
||||
'''
|
||||
Values of AXProperty name:
|
||||
- from 'busy' to 'roledescription': states which apply to every AX node
|
||||
- from 'live' to 'root': attributes which apply to nodes in live regions
|
||||
- from 'autocomplete' to 'valuetext': attributes which apply to widgets
|
||||
- from 'checked' to 'selected': states which apply to widgets
|
||||
- from 'activedescendant' to 'owns' - relationships between elements other than parent/child/sibling.
|
||||
'''
|
||||
BUSY = "busy"
|
||||
DISABLED = "disabled"
|
||||
EDITABLE = "editable"
|
||||
FOCUSABLE = "focusable"
|
||||
FOCUSED = "focused"
|
||||
HIDDEN = "hidden"
|
||||
HIDDEN_ROOT = "hiddenRoot"
|
||||
INVALID = "invalid"
|
||||
KEYSHORTCUTS = "keyshortcuts"
|
||||
SETTABLE = "settable"
|
||||
ROLEDESCRIPTION = "roledescription"
|
||||
LIVE = "live"
|
||||
ATOMIC = "atomic"
|
||||
RELEVANT = "relevant"
|
||||
ROOT = "root"
|
||||
AUTOCOMPLETE = "autocomplete"
|
||||
HAS_POPUP = "hasPopup"
|
||||
LEVEL = "level"
|
||||
MULTISELECTABLE = "multiselectable"
|
||||
ORIENTATION = "orientation"
|
||||
MULTILINE = "multiline"
|
||||
READONLY = "readonly"
|
||||
REQUIRED = "required"
|
||||
VALUEMIN = "valuemin"
|
||||
VALUEMAX = "valuemax"
|
||||
VALUETEXT = "valuetext"
|
||||
CHECKED = "checked"
|
||||
EXPANDED = "expanded"
|
||||
MODAL = "modal"
|
||||
PRESSED = "pressed"
|
||||
SELECTED = "selected"
|
||||
ACTIVEDESCENDANT = "activedescendant"
|
||||
CONTROLS = "controls"
|
||||
DESCRIBEDBY = "describedby"
|
||||
DETAILS = "details"
|
||||
ERRORMESSAGE = "errormessage"
|
||||
FLOWTO = "flowto"
|
||||
LABELLEDBY = "labelledby"
|
||||
OWNS = "owns"
|
||||
|
||||
def to_json(self):
|
||||
return self.value
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(json)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AXNode:
|
||||
'''
|
||||
A node in the accessibility tree.
|
||||
'''
|
||||
#: Unique identifier for this node.
|
||||
node_id: AXNodeId
|
||||
|
||||
#: Whether this node is ignored for accessibility
|
||||
ignored: bool
|
||||
|
||||
#: Collection of reasons why this node is hidden.
|
||||
ignored_reasons: typing.Optional[typing.List[AXProperty]] = None
|
||||
|
||||
#: This ``Node``'s role, whether explicit or implicit.
|
||||
role: typing.Optional[AXValue] = None
|
||||
|
||||
#: The accessible name for this ``Node``.
|
||||
name: typing.Optional[AXValue] = None
|
||||
|
||||
#: The accessible description for this ``Node``.
|
||||
description: typing.Optional[AXValue] = None
|
||||
|
||||
#: The value for this ``Node``.
|
||||
value: typing.Optional[AXValue] = None
|
||||
|
||||
#: All other properties
|
||||
properties: typing.Optional[typing.List[AXProperty]] = None
|
||||
|
||||
#: IDs for each of this node's child nodes.
|
||||
child_ids: typing.Optional[typing.List[AXNodeId]] = None
|
||||
|
||||
#: The backend ID for the associated DOM node, if any.
|
||||
backend_dom_node_id: typing.Optional[dom.BackendNodeId] = None
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['nodeId'] = self.node_id.to_json()
|
||||
json['ignored'] = self.ignored
|
||||
if self.ignored_reasons is not None:
|
||||
json['ignoredReasons'] = [i.to_json() for i in self.ignored_reasons]
|
||||
if self.role is not None:
|
||||
json['role'] = self.role.to_json()
|
||||
if self.name is not None:
|
||||
json['name'] = self.name.to_json()
|
||||
if self.description is not None:
|
||||
json['description'] = self.description.to_json()
|
||||
if self.value is not None:
|
||||
json['value'] = self.value.to_json()
|
||||
if self.properties is not None:
|
||||
json['properties'] = [i.to_json() for i in self.properties]
|
||||
if self.child_ids is not None:
|
||||
json['childIds'] = [i.to_json() for i in self.child_ids]
|
||||
if self.backend_dom_node_id is not None:
|
||||
json['backendDOMNodeId'] = self.backend_dom_node_id.to_json()
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
node_id=AXNodeId.from_json(json['nodeId']),
|
||||
ignored=bool(json['ignored']),
|
||||
ignored_reasons=[AXProperty.from_json(i) for i in json['ignoredReasons']] if 'ignoredReasons' in json else None,
|
||||
role=AXValue.from_json(json['role']) if 'role' in json else None,
|
||||
name=AXValue.from_json(json['name']) if 'name' in json else None,
|
||||
description=AXValue.from_json(json['description']) if 'description' in json else None,
|
||||
value=AXValue.from_json(json['value']) if 'value' in json else None,
|
||||
properties=[AXProperty.from_json(i) for i in json['properties']] if 'properties' in json else None,
|
||||
child_ids=[AXNodeId.from_json(i) for i in json['childIds']] if 'childIds' in json else None,
|
||||
backend_dom_node_id=dom.BackendNodeId.from_json(json['backendDOMNodeId']) if 'backendDOMNodeId' in json else None,
|
||||
)
|
||||
|
||||
|
||||
def disable() -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Disables the accessibility domain.
|
||||
'''
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Accessibility.disable',
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
def enable() -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Enables the accessibility domain which causes ``AXNodeId``'s to remain consistent between method calls.
|
||||
This turns on accessibility for the page, which can impact performance until accessibility is disabled.
|
||||
'''
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Accessibility.enable',
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
def get_partial_ax_tree(
|
||||
node_id: typing.Optional[dom.NodeId] = None,
|
||||
backend_node_id: typing.Optional[dom.BackendNodeId] = None,
|
||||
object_id: typing.Optional[runtime.RemoteObjectId] = None,
|
||||
fetch_relatives: typing.Optional[bool] = None
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,typing.List[AXNode]]:
|
||||
'''
|
||||
Fetches the accessibility node and partial accessibility tree for this DOM node, if it exists.
|
||||
|
||||
**EXPERIMENTAL**
|
||||
|
||||
:param node_id: *(Optional)* Identifier of the node to get the partial accessibility tree for.
|
||||
:param backend_node_id: *(Optional)* Identifier of the backend node to get the partial accessibility tree for.
|
||||
:param object_id: *(Optional)* JavaScript object id of the node wrapper to get the partial accessibility tree for.
|
||||
:param fetch_relatives: *(Optional)* Whether to fetch this nodes ancestors, siblings and children. Defaults to true.
|
||||
:returns: The ``Accessibility.AXNode`` for this DOM node, if it exists, plus its ancestors, siblings and children, if requested.
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
if node_id is not None:
|
||||
params['nodeId'] = node_id.to_json()
|
||||
if backend_node_id is not None:
|
||||
params['backendNodeId'] = backend_node_id.to_json()
|
||||
if object_id is not None:
|
||||
params['objectId'] = object_id.to_json()
|
||||
if fetch_relatives is not None:
|
||||
params['fetchRelatives'] = fetch_relatives
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Accessibility.getPartialAXTree',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
return [AXNode.from_json(i) for i in json['nodes']]
|
||||
|
||||
|
||||
def get_full_ax_tree() -> typing.Generator[T_JSON_DICT,T_JSON_DICT,typing.List[AXNode]]:
|
||||
'''
|
||||
Fetches the entire accessibility tree
|
||||
|
||||
**EXPERIMENTAL**
|
||||
|
||||
:returns:
|
||||
'''
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Accessibility.getFullAXTree',
|
||||
}
|
||||
json = yield cmd_dict
|
||||
return [AXNode.from_json(i) for i in json['nodes']]
|
||||
@@ -0,0 +1,415 @@
|
||||
# DO NOT EDIT THIS FILE!
|
||||
#
|
||||
# This file is generated from the CDP specification. If you need to make
|
||||
# changes, edit the generator and regenerate all of the modules.
|
||||
#
|
||||
# CDP domain: Animation (experimental)
|
||||
from __future__ import annotations
|
||||
from .util import event_class, T_JSON_DICT
|
||||
from dataclasses import dataclass
|
||||
import enum
|
||||
import typing
|
||||
from . import dom
|
||||
from . import runtime
|
||||
|
||||
|
||||
@dataclass
|
||||
class Animation:
|
||||
'''
|
||||
Animation instance.
|
||||
'''
|
||||
#: ``Animation``'s id.
|
||||
id_: str
|
||||
|
||||
#: ``Animation``'s name.
|
||||
name: str
|
||||
|
||||
#: ``Animation``'s internal paused state.
|
||||
paused_state: bool
|
||||
|
||||
#: ``Animation``'s play state.
|
||||
play_state: str
|
||||
|
||||
#: ``Animation``'s playback rate.
|
||||
playback_rate: float
|
||||
|
||||
#: ``Animation``'s start time.
|
||||
start_time: float
|
||||
|
||||
#: ``Animation``'s current time.
|
||||
current_time: float
|
||||
|
||||
#: Animation type of ``Animation``.
|
||||
type_: str
|
||||
|
||||
#: ``Animation``'s source animation node.
|
||||
source: typing.Optional[AnimationEffect] = None
|
||||
|
||||
#: A unique ID for ``Animation`` representing the sources that triggered this CSS
|
||||
#: animation/transition.
|
||||
css_id: typing.Optional[str] = None
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['id'] = self.id_
|
||||
json['name'] = self.name
|
||||
json['pausedState'] = self.paused_state
|
||||
json['playState'] = self.play_state
|
||||
json['playbackRate'] = self.playback_rate
|
||||
json['startTime'] = self.start_time
|
||||
json['currentTime'] = self.current_time
|
||||
json['type'] = self.type_
|
||||
if self.source is not None:
|
||||
json['source'] = self.source.to_json()
|
||||
if self.css_id is not None:
|
||||
json['cssId'] = self.css_id
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
id_=str(json['id']),
|
||||
name=str(json['name']),
|
||||
paused_state=bool(json['pausedState']),
|
||||
play_state=str(json['playState']),
|
||||
playback_rate=float(json['playbackRate']),
|
||||
start_time=float(json['startTime']),
|
||||
current_time=float(json['currentTime']),
|
||||
type_=str(json['type']),
|
||||
source=AnimationEffect.from_json(json['source']) if 'source' in json else None,
|
||||
css_id=str(json['cssId']) if 'cssId' in json else None,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AnimationEffect:
|
||||
'''
|
||||
AnimationEffect instance
|
||||
'''
|
||||
#: ``AnimationEffect``'s delay.
|
||||
delay: float
|
||||
|
||||
#: ``AnimationEffect``'s end delay.
|
||||
end_delay: float
|
||||
|
||||
#: ``AnimationEffect``'s iteration start.
|
||||
iteration_start: float
|
||||
|
||||
#: ``AnimationEffect``'s iterations.
|
||||
iterations: float
|
||||
|
||||
#: ``AnimationEffect``'s iteration duration.
|
||||
duration: float
|
||||
|
||||
#: ``AnimationEffect``'s playback direction.
|
||||
direction: str
|
||||
|
||||
#: ``AnimationEffect``'s fill mode.
|
||||
fill: str
|
||||
|
||||
#: ``AnimationEffect``'s timing function.
|
||||
easing: str
|
||||
|
||||
#: ``AnimationEffect``'s target node.
|
||||
backend_node_id: typing.Optional[dom.BackendNodeId] = None
|
||||
|
||||
#: ``AnimationEffect``'s keyframes.
|
||||
keyframes_rule: typing.Optional[KeyframesRule] = None
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['delay'] = self.delay
|
||||
json['endDelay'] = self.end_delay
|
||||
json['iterationStart'] = self.iteration_start
|
||||
json['iterations'] = self.iterations
|
||||
json['duration'] = self.duration
|
||||
json['direction'] = self.direction
|
||||
json['fill'] = self.fill
|
||||
json['easing'] = self.easing
|
||||
if self.backend_node_id is not None:
|
||||
json['backendNodeId'] = self.backend_node_id.to_json()
|
||||
if self.keyframes_rule is not None:
|
||||
json['keyframesRule'] = self.keyframes_rule.to_json()
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
delay=float(json['delay']),
|
||||
end_delay=float(json['endDelay']),
|
||||
iteration_start=float(json['iterationStart']),
|
||||
iterations=float(json['iterations']),
|
||||
duration=float(json['duration']),
|
||||
direction=str(json['direction']),
|
||||
fill=str(json['fill']),
|
||||
easing=str(json['easing']),
|
||||
backend_node_id=dom.BackendNodeId.from_json(json['backendNodeId']) if 'backendNodeId' in json else None,
|
||||
keyframes_rule=KeyframesRule.from_json(json['keyframesRule']) if 'keyframesRule' in json else None,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class KeyframesRule:
|
||||
'''
|
||||
Keyframes Rule
|
||||
'''
|
||||
#: List of animation keyframes.
|
||||
keyframes: typing.List[KeyframeStyle]
|
||||
|
||||
#: CSS keyframed animation's name.
|
||||
name: typing.Optional[str] = None
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['keyframes'] = [i.to_json() for i in self.keyframes]
|
||||
if self.name is not None:
|
||||
json['name'] = self.name
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
keyframes=[KeyframeStyle.from_json(i) for i in json['keyframes']],
|
||||
name=str(json['name']) if 'name' in json else None,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class KeyframeStyle:
|
||||
'''
|
||||
Keyframe Style
|
||||
'''
|
||||
#: Keyframe's time offset.
|
||||
offset: str
|
||||
|
||||
#: ``AnimationEffect``'s timing function.
|
||||
easing: str
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['offset'] = self.offset
|
||||
json['easing'] = self.easing
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
offset=str(json['offset']),
|
||||
easing=str(json['easing']),
|
||||
)
|
||||
|
||||
|
||||
def disable() -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Disables animation domain notifications.
|
||||
'''
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Animation.disable',
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
def enable() -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Enables animation domain notifications.
|
||||
'''
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Animation.enable',
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
def get_current_time(
|
||||
id_: str
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,float]:
|
||||
'''
|
||||
Returns the current time of the an animation.
|
||||
|
||||
:param id_: Id of animation.
|
||||
:returns: Current time of the page.
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
params['id'] = id_
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Animation.getCurrentTime',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
return float(json['currentTime'])
|
||||
|
||||
|
||||
def get_playback_rate() -> typing.Generator[T_JSON_DICT,T_JSON_DICT,float]:
|
||||
'''
|
||||
Gets the playback rate of the document timeline.
|
||||
|
||||
:returns: Playback rate for animations on page.
|
||||
'''
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Animation.getPlaybackRate',
|
||||
}
|
||||
json = yield cmd_dict
|
||||
return float(json['playbackRate'])
|
||||
|
||||
|
||||
def release_animations(
|
||||
animations: typing.List[str]
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Releases a set of animations to no longer be manipulated.
|
||||
|
||||
:param animations: List of animation ids to seek.
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
params['animations'] = [i for i in animations]
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Animation.releaseAnimations',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
def resolve_animation(
|
||||
animation_id: str
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,runtime.RemoteObject]:
|
||||
'''
|
||||
Gets the remote object of the Animation.
|
||||
|
||||
:param animation_id: Animation id.
|
||||
:returns: Corresponding remote object.
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
params['animationId'] = animation_id
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Animation.resolveAnimation',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
return runtime.RemoteObject.from_json(json['remoteObject'])
|
||||
|
||||
|
||||
def seek_animations(
|
||||
animations: typing.List[str],
|
||||
current_time: float
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Seek a set of animations to a particular time within each animation.
|
||||
|
||||
:param animations: List of animation ids to seek.
|
||||
:param current_time: Set the current time of each animation.
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
params['animations'] = [i for i in animations]
|
||||
params['currentTime'] = current_time
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Animation.seekAnimations',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
def set_paused(
|
||||
animations: typing.List[str],
|
||||
paused: bool
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Sets the paused state of a set of animations.
|
||||
|
||||
:param animations: Animations to set the pause state of.
|
||||
:param paused: Paused state to set to.
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
params['animations'] = [i for i in animations]
|
||||
params['paused'] = paused
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Animation.setPaused',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
def set_playback_rate(
|
||||
playback_rate: float
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Sets the playback rate of the document timeline.
|
||||
|
||||
:param playback_rate: Playback rate for animations on page
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
params['playbackRate'] = playback_rate
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Animation.setPlaybackRate',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
def set_timing(
|
||||
animation_id: str,
|
||||
duration: float,
|
||||
delay: float
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Sets the timing of an animation node.
|
||||
|
||||
:param animation_id: Animation id.
|
||||
:param duration: Duration of the animation.
|
||||
:param delay: Delay of the animation.
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
params['animationId'] = animation_id
|
||||
params['duration'] = duration
|
||||
params['delay'] = delay
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Animation.setTiming',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
@event_class('Animation.animationCanceled')
|
||||
@dataclass
|
||||
class AnimationCanceled:
|
||||
'''
|
||||
Event for when an animation has been cancelled.
|
||||
'''
|
||||
#: Id of the animation that was cancelled.
|
||||
id_: str
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json: T_JSON_DICT) -> AnimationCanceled:
|
||||
return cls(
|
||||
id_=str(json['id'])
|
||||
)
|
||||
|
||||
|
||||
@event_class('Animation.animationCreated')
|
||||
@dataclass
|
||||
class AnimationCreated:
|
||||
'''
|
||||
Event for each animation that has been created.
|
||||
'''
|
||||
#: Id of the animation that was created.
|
||||
id_: str
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json: T_JSON_DICT) -> AnimationCreated:
|
||||
return cls(
|
||||
id_=str(json['id'])
|
||||
)
|
||||
|
||||
|
||||
@event_class('Animation.animationStarted')
|
||||
@dataclass
|
||||
class AnimationStarted:
|
||||
'''
|
||||
Event for animation that has been started.
|
||||
'''
|
||||
#: Animation that was started.
|
||||
animation: Animation
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json: T_JSON_DICT) -> AnimationStarted:
|
||||
return cls(
|
||||
animation=Animation.from_json(json['animation'])
|
||||
)
|
||||
@@ -0,0 +1,205 @@
|
||||
# DO NOT EDIT THIS FILE!
|
||||
#
|
||||
# This file is generated from the CDP specification. If you need to make
|
||||
# changes, edit the generator and regenerate all of the modules.
|
||||
#
|
||||
# CDP domain: ApplicationCache (experimental)
|
||||
from __future__ import annotations
|
||||
from .util import event_class, T_JSON_DICT
|
||||
from dataclasses import dataclass
|
||||
import enum
|
||||
import typing
|
||||
from . import page
|
||||
|
||||
|
||||
@dataclass
|
||||
class ApplicationCacheResource:
|
||||
'''
|
||||
Detailed application cache resource information.
|
||||
'''
|
||||
#: Resource url.
|
||||
url: str
|
||||
|
||||
#: Resource size.
|
||||
size: int
|
||||
|
||||
#: Resource type.
|
||||
type_: str
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['url'] = self.url
|
||||
json['size'] = self.size
|
||||
json['type'] = self.type_
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
url=str(json['url']),
|
||||
size=int(json['size']),
|
||||
type_=str(json['type']),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ApplicationCache:
|
||||
'''
|
||||
Detailed application cache information.
|
||||
'''
|
||||
#: Manifest URL.
|
||||
manifest_url: str
|
||||
|
||||
#: Application cache size.
|
||||
size: float
|
||||
|
||||
#: Application cache creation time.
|
||||
creation_time: float
|
||||
|
||||
#: Application cache update time.
|
||||
update_time: float
|
||||
|
||||
#: Application cache resources.
|
||||
resources: typing.List[ApplicationCacheResource]
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['manifestURL'] = self.manifest_url
|
||||
json['size'] = self.size
|
||||
json['creationTime'] = self.creation_time
|
||||
json['updateTime'] = self.update_time
|
||||
json['resources'] = [i.to_json() for i in self.resources]
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
manifest_url=str(json['manifestURL']),
|
||||
size=float(json['size']),
|
||||
creation_time=float(json['creationTime']),
|
||||
update_time=float(json['updateTime']),
|
||||
resources=[ApplicationCacheResource.from_json(i) for i in json['resources']],
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class FrameWithManifest:
|
||||
'''
|
||||
Frame identifier - manifest URL pair.
|
||||
'''
|
||||
#: Frame identifier.
|
||||
frame_id: page.FrameId
|
||||
|
||||
#: Manifest URL.
|
||||
manifest_url: str
|
||||
|
||||
#: Application cache status.
|
||||
status: int
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['frameId'] = self.frame_id.to_json()
|
||||
json['manifestURL'] = self.manifest_url
|
||||
json['status'] = self.status
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
frame_id=page.FrameId.from_json(json['frameId']),
|
||||
manifest_url=str(json['manifestURL']),
|
||||
status=int(json['status']),
|
||||
)
|
||||
|
||||
|
||||
def enable() -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Enables application cache domain notifications.
|
||||
'''
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'ApplicationCache.enable',
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
def get_application_cache_for_frame(
|
||||
frame_id: page.FrameId
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,ApplicationCache]:
|
||||
'''
|
||||
Returns relevant application cache data for the document in given frame.
|
||||
|
||||
:param frame_id: Identifier of the frame containing document whose application cache is retrieved.
|
||||
:returns: Relevant application cache data for the document in given frame.
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
params['frameId'] = frame_id.to_json()
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'ApplicationCache.getApplicationCacheForFrame',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
return ApplicationCache.from_json(json['applicationCache'])
|
||||
|
||||
|
||||
def get_frames_with_manifests() -> typing.Generator[T_JSON_DICT,T_JSON_DICT,typing.List[FrameWithManifest]]:
|
||||
'''
|
||||
Returns array of frame identifiers with manifest urls for each frame containing a document
|
||||
associated with some application cache.
|
||||
|
||||
:returns: Array of frame identifiers with manifest urls for each frame containing a document associated with some application cache.
|
||||
'''
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'ApplicationCache.getFramesWithManifests',
|
||||
}
|
||||
json = yield cmd_dict
|
||||
return [FrameWithManifest.from_json(i) for i in json['frameIds']]
|
||||
|
||||
|
||||
def get_manifest_for_frame(
|
||||
frame_id: page.FrameId
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,str]:
|
||||
'''
|
||||
Returns manifest URL for document in the given frame.
|
||||
|
||||
:param frame_id: Identifier of the frame containing document whose manifest is retrieved.
|
||||
:returns: Manifest URL for document in the given frame.
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
params['frameId'] = frame_id.to_json()
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'ApplicationCache.getManifestForFrame',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
return str(json['manifestURL'])
|
||||
|
||||
|
||||
@event_class('ApplicationCache.applicationCacheStatusUpdated')
|
||||
@dataclass
|
||||
class ApplicationCacheStatusUpdated:
|
||||
#: Identifier of the frame containing document whose application cache updated status.
|
||||
frame_id: page.FrameId
|
||||
#: Manifest URL.
|
||||
manifest_url: str
|
||||
#: Updated application cache status.
|
||||
status: int
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json: T_JSON_DICT) -> ApplicationCacheStatusUpdated:
|
||||
return cls(
|
||||
frame_id=page.FrameId.from_json(json['frameId']),
|
||||
manifest_url=str(json['manifestURL']),
|
||||
status=int(json['status'])
|
||||
)
|
||||
|
||||
|
||||
@event_class('ApplicationCache.networkStateUpdated')
|
||||
@dataclass
|
||||
class NetworkStateUpdated:
|
||||
is_now_online: bool
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json: T_JSON_DICT) -> NetworkStateUpdated:
|
||||
return cls(
|
||||
is_now_online=bool(json['isNowOnline'])
|
||||
)
|
||||
@@ -0,0 +1,527 @@
|
||||
# DO NOT EDIT THIS FILE!
|
||||
#
|
||||
# This file is generated from the CDP specification. If you need to make
|
||||
# changes, edit the generator and regenerate all of the modules.
|
||||
#
|
||||
# CDP domain: Audits (experimental)
|
||||
from __future__ import annotations
|
||||
from .util import event_class, T_JSON_DICT
|
||||
from dataclasses import dataclass
|
||||
import enum
|
||||
import typing
|
||||
from . import network
|
||||
from . import page
|
||||
|
||||
|
||||
@dataclass
|
||||
class AffectedCookie:
|
||||
'''
|
||||
Information about a cookie that is affected by an inspector issue.
|
||||
'''
|
||||
#: The following three properties uniquely identify a cookie
|
||||
name: str
|
||||
|
||||
path: str
|
||||
|
||||
domain: str
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['name'] = self.name
|
||||
json['path'] = self.path
|
||||
json['domain'] = self.domain
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
name=str(json['name']),
|
||||
path=str(json['path']),
|
||||
domain=str(json['domain']),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AffectedRequest:
|
||||
'''
|
||||
Information about a request that is affected by an inspector issue.
|
||||
'''
|
||||
#: The unique request id.
|
||||
request_id: network.RequestId
|
||||
|
||||
url: typing.Optional[str] = None
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['requestId'] = self.request_id.to_json()
|
||||
if self.url is not None:
|
||||
json['url'] = self.url
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
request_id=network.RequestId.from_json(json['requestId']),
|
||||
url=str(json['url']) if 'url' in json else None,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AffectedFrame:
|
||||
'''
|
||||
Information about the frame affected by an inspector issue.
|
||||
'''
|
||||
frame_id: page.FrameId
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['frameId'] = self.frame_id.to_json()
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
frame_id=page.FrameId.from_json(json['frameId']),
|
||||
)
|
||||
|
||||
|
||||
class SameSiteCookieExclusionReason(enum.Enum):
|
||||
EXCLUDE_SAME_SITE_UNSPECIFIED_TREATED_AS_LAX = "ExcludeSameSiteUnspecifiedTreatedAsLax"
|
||||
EXCLUDE_SAME_SITE_NONE_INSECURE = "ExcludeSameSiteNoneInsecure"
|
||||
|
||||
def to_json(self):
|
||||
return self.value
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(json)
|
||||
|
||||
|
||||
class SameSiteCookieWarningReason(enum.Enum):
|
||||
WARN_SAME_SITE_UNSPECIFIED_CROSS_SITE_CONTEXT = "WarnSameSiteUnspecifiedCrossSiteContext"
|
||||
WARN_SAME_SITE_NONE_INSECURE = "WarnSameSiteNoneInsecure"
|
||||
WARN_SAME_SITE_UNSPECIFIED_LAX_ALLOW_UNSAFE = "WarnSameSiteUnspecifiedLaxAllowUnsafe"
|
||||
WARN_SAME_SITE_STRICT_LAX_DOWNGRADE_STRICT = "WarnSameSiteStrictLaxDowngradeStrict"
|
||||
WARN_SAME_SITE_STRICT_CROSS_DOWNGRADE_STRICT = "WarnSameSiteStrictCrossDowngradeStrict"
|
||||
WARN_SAME_SITE_STRICT_CROSS_DOWNGRADE_LAX = "WarnSameSiteStrictCrossDowngradeLax"
|
||||
WARN_SAME_SITE_LAX_CROSS_DOWNGRADE_STRICT = "WarnSameSiteLaxCrossDowngradeStrict"
|
||||
WARN_SAME_SITE_LAX_CROSS_DOWNGRADE_LAX = "WarnSameSiteLaxCrossDowngradeLax"
|
||||
|
||||
def to_json(self):
|
||||
return self.value
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(json)
|
||||
|
||||
|
||||
class SameSiteCookieOperation(enum.Enum):
|
||||
SET_COOKIE = "SetCookie"
|
||||
READ_COOKIE = "ReadCookie"
|
||||
|
||||
def to_json(self):
|
||||
return self.value
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(json)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SameSiteCookieIssueDetails:
|
||||
'''
|
||||
This information is currently necessary, as the front-end has a difficult
|
||||
time finding a specific cookie. With this, we can convey specific error
|
||||
information without the cookie.
|
||||
'''
|
||||
cookie: AffectedCookie
|
||||
|
||||
cookie_warning_reasons: typing.List[SameSiteCookieWarningReason]
|
||||
|
||||
cookie_exclusion_reasons: typing.List[SameSiteCookieExclusionReason]
|
||||
|
||||
#: Optionally identifies the site-for-cookies and the cookie url, which
|
||||
#: may be used by the front-end as additional context.
|
||||
operation: SameSiteCookieOperation
|
||||
|
||||
site_for_cookies: typing.Optional[str] = None
|
||||
|
||||
cookie_url: typing.Optional[str] = None
|
||||
|
||||
request: typing.Optional[AffectedRequest] = None
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['cookie'] = self.cookie.to_json()
|
||||
json['cookieWarningReasons'] = [i.to_json() for i in self.cookie_warning_reasons]
|
||||
json['cookieExclusionReasons'] = [i.to_json() for i in self.cookie_exclusion_reasons]
|
||||
json['operation'] = self.operation.to_json()
|
||||
if self.site_for_cookies is not None:
|
||||
json['siteForCookies'] = self.site_for_cookies
|
||||
if self.cookie_url is not None:
|
||||
json['cookieUrl'] = self.cookie_url
|
||||
if self.request is not None:
|
||||
json['request'] = self.request.to_json()
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
cookie=AffectedCookie.from_json(json['cookie']),
|
||||
cookie_warning_reasons=[SameSiteCookieWarningReason.from_json(i) for i in json['cookieWarningReasons']],
|
||||
cookie_exclusion_reasons=[SameSiteCookieExclusionReason.from_json(i) for i in json['cookieExclusionReasons']],
|
||||
operation=SameSiteCookieOperation.from_json(json['operation']),
|
||||
site_for_cookies=str(json['siteForCookies']) if 'siteForCookies' in json else None,
|
||||
cookie_url=str(json['cookieUrl']) if 'cookieUrl' in json else None,
|
||||
request=AffectedRequest.from_json(json['request']) if 'request' in json else None,
|
||||
)
|
||||
|
||||
|
||||
class MixedContentResolutionStatus(enum.Enum):
|
||||
MIXED_CONTENT_BLOCKED = "MixedContentBlocked"
|
||||
MIXED_CONTENT_AUTOMATICALLY_UPGRADED = "MixedContentAutomaticallyUpgraded"
|
||||
MIXED_CONTENT_WARNING = "MixedContentWarning"
|
||||
|
||||
def to_json(self):
|
||||
return self.value
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(json)
|
||||
|
||||
|
||||
class MixedContentResourceType(enum.Enum):
|
||||
AUDIO = "Audio"
|
||||
BEACON = "Beacon"
|
||||
CSP_REPORT = "CSPReport"
|
||||
DOWNLOAD = "Download"
|
||||
EVENT_SOURCE = "EventSource"
|
||||
FAVICON = "Favicon"
|
||||
FONT = "Font"
|
||||
FORM = "Form"
|
||||
FRAME = "Frame"
|
||||
IMAGE = "Image"
|
||||
IMPORT = "Import"
|
||||
MANIFEST = "Manifest"
|
||||
PING = "Ping"
|
||||
PLUGIN_DATA = "PluginData"
|
||||
PLUGIN_RESOURCE = "PluginResource"
|
||||
PREFETCH = "Prefetch"
|
||||
RESOURCE = "Resource"
|
||||
SCRIPT = "Script"
|
||||
SERVICE_WORKER = "ServiceWorker"
|
||||
SHARED_WORKER = "SharedWorker"
|
||||
STYLESHEET = "Stylesheet"
|
||||
TRACK = "Track"
|
||||
VIDEO = "Video"
|
||||
WORKER = "Worker"
|
||||
XML_HTTP_REQUEST = "XMLHttpRequest"
|
||||
XSLT = "XSLT"
|
||||
|
||||
def to_json(self):
|
||||
return self.value
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(json)
|
||||
|
||||
|
||||
@dataclass
|
||||
class MixedContentIssueDetails:
|
||||
#: The way the mixed content issue is being resolved.
|
||||
resolution_status: MixedContentResolutionStatus
|
||||
|
||||
#: The unsafe http url causing the mixed content issue.
|
||||
insecure_url: str
|
||||
|
||||
#: The url responsible for the call to an unsafe url.
|
||||
main_resource_url: str
|
||||
|
||||
#: The type of resource causing the mixed content issue (css, js, iframe,
|
||||
#: form,...). Marked as optional because it is mapped to from
|
||||
#: blink::mojom::RequestContextType, which will be replaced
|
||||
#: by network::mojom::RequestDestination
|
||||
resource_type: typing.Optional[MixedContentResourceType] = None
|
||||
|
||||
#: The mixed content request.
|
||||
#: Does not always exist (e.g. for unsafe form submission urls).
|
||||
request: typing.Optional[AffectedRequest] = None
|
||||
|
||||
#: Optional because not every mixed content issue is necessarily linked to a frame.
|
||||
frame: typing.Optional[AffectedFrame] = None
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['resolutionStatus'] = self.resolution_status.to_json()
|
||||
json['insecureURL'] = self.insecure_url
|
||||
json['mainResourceURL'] = self.main_resource_url
|
||||
if self.resource_type is not None:
|
||||
json['resourceType'] = self.resource_type.to_json()
|
||||
if self.request is not None:
|
||||
json['request'] = self.request.to_json()
|
||||
if self.frame is not None:
|
||||
json['frame'] = self.frame.to_json()
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
resolution_status=MixedContentResolutionStatus.from_json(json['resolutionStatus']),
|
||||
insecure_url=str(json['insecureURL']),
|
||||
main_resource_url=str(json['mainResourceURL']),
|
||||
resource_type=MixedContentResourceType.from_json(json['resourceType']) if 'resourceType' in json else None,
|
||||
request=AffectedRequest.from_json(json['request']) if 'request' in json else None,
|
||||
frame=AffectedFrame.from_json(json['frame']) if 'frame' in json else None,
|
||||
)
|
||||
|
||||
|
||||
class BlockedByResponseReason(enum.Enum):
|
||||
'''
|
||||
Enum indicating the reason a response has been blocked. These reasons are
|
||||
refinements of the net error BLOCKED_BY_RESPONSE.
|
||||
'''
|
||||
COEP_FRAME_RESOURCE_NEEDS_COEP_HEADER = "CoepFrameResourceNeedsCoepHeader"
|
||||
COOP_SANDBOXED_I_FRAME_CANNOT_NAVIGATE_TO_COOP_PAGE = "CoopSandboxedIFrameCannotNavigateToCoopPage"
|
||||
CORP_NOT_SAME_ORIGIN = "CorpNotSameOrigin"
|
||||
CORP_NOT_SAME_ORIGIN_AFTER_DEFAULTED_TO_SAME_ORIGIN_BY_COEP = "CorpNotSameOriginAfterDefaultedToSameOriginByCoep"
|
||||
CORP_NOT_SAME_SITE = "CorpNotSameSite"
|
||||
|
||||
def to_json(self):
|
||||
return self.value
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(json)
|
||||
|
||||
|
||||
@dataclass
|
||||
class BlockedByResponseIssueDetails:
|
||||
'''
|
||||
Details for a request that has been blocked with the BLOCKED_BY_RESPONSE
|
||||
code. Currently only used for COEP/COOP, but may be extended to include
|
||||
some CSP errors in the future.
|
||||
'''
|
||||
request: AffectedRequest
|
||||
|
||||
reason: BlockedByResponseReason
|
||||
|
||||
frame: typing.Optional[AffectedFrame] = None
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['request'] = self.request.to_json()
|
||||
json['reason'] = self.reason.to_json()
|
||||
if self.frame is not None:
|
||||
json['frame'] = self.frame.to_json()
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
request=AffectedRequest.from_json(json['request']),
|
||||
reason=BlockedByResponseReason.from_json(json['reason']),
|
||||
frame=AffectedFrame.from_json(json['frame']) if 'frame' in json else None,
|
||||
)
|
||||
|
||||
|
||||
class HeavyAdResolutionStatus(enum.Enum):
|
||||
HEAVY_AD_BLOCKED = "HeavyAdBlocked"
|
||||
HEAVY_AD_WARNING = "HeavyAdWarning"
|
||||
|
||||
def to_json(self):
|
||||
return self.value
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(json)
|
||||
|
||||
|
||||
class HeavyAdReason(enum.Enum):
|
||||
NETWORK_TOTAL_LIMIT = "NetworkTotalLimit"
|
||||
CPU_TOTAL_LIMIT = "CpuTotalLimit"
|
||||
CPU_PEAK_LIMIT = "CpuPeakLimit"
|
||||
|
||||
def to_json(self):
|
||||
return self.value
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(json)
|
||||
|
||||
|
||||
@dataclass
|
||||
class HeavyAdIssueDetails:
|
||||
#: The resolution status, either blocking the content or warning.
|
||||
resolution: HeavyAdResolutionStatus
|
||||
|
||||
#: The reason the ad was blocked, total network or cpu or peak cpu.
|
||||
reason: HeavyAdReason
|
||||
|
||||
#: The frame that was blocked.
|
||||
frame: AffectedFrame
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['resolution'] = self.resolution.to_json()
|
||||
json['reason'] = self.reason.to_json()
|
||||
json['frame'] = self.frame.to_json()
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
resolution=HeavyAdResolutionStatus.from_json(json['resolution']),
|
||||
reason=HeavyAdReason.from_json(json['reason']),
|
||||
frame=AffectedFrame.from_json(json['frame']),
|
||||
)
|
||||
|
||||
|
||||
class InspectorIssueCode(enum.Enum):
|
||||
'''
|
||||
A unique identifier for the type of issue. Each type may use one of the
|
||||
optional fields in InspectorIssueDetails to convey more specific
|
||||
information about the kind of issue.
|
||||
'''
|
||||
SAME_SITE_COOKIE_ISSUE = "SameSiteCookieIssue"
|
||||
MIXED_CONTENT_ISSUE = "MixedContentIssue"
|
||||
BLOCKED_BY_RESPONSE_ISSUE = "BlockedByResponseIssue"
|
||||
HEAVY_AD_ISSUE = "HeavyAdIssue"
|
||||
|
||||
def to_json(self):
|
||||
return self.value
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(json)
|
||||
|
||||
|
||||
@dataclass
|
||||
class InspectorIssueDetails:
|
||||
'''
|
||||
This struct holds a list of optional fields with additional information
|
||||
specific to the kind of issue. When adding a new issue code, please also
|
||||
add a new optional field to this type.
|
||||
'''
|
||||
same_site_cookie_issue_details: typing.Optional[SameSiteCookieIssueDetails] = None
|
||||
|
||||
mixed_content_issue_details: typing.Optional[MixedContentIssueDetails] = None
|
||||
|
||||
blocked_by_response_issue_details: typing.Optional[BlockedByResponseIssueDetails] = None
|
||||
|
||||
heavy_ad_issue_details: typing.Optional[HeavyAdIssueDetails] = None
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
if self.same_site_cookie_issue_details is not None:
|
||||
json['sameSiteCookieIssueDetails'] = self.same_site_cookie_issue_details.to_json()
|
||||
if self.mixed_content_issue_details is not None:
|
||||
json['mixedContentIssueDetails'] = self.mixed_content_issue_details.to_json()
|
||||
if self.blocked_by_response_issue_details is not None:
|
||||
json['blockedByResponseIssueDetails'] = self.blocked_by_response_issue_details.to_json()
|
||||
if self.heavy_ad_issue_details is not None:
|
||||
json['heavyAdIssueDetails'] = self.heavy_ad_issue_details.to_json()
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
same_site_cookie_issue_details=SameSiteCookieIssueDetails.from_json(json['sameSiteCookieIssueDetails']) if 'sameSiteCookieIssueDetails' in json else None,
|
||||
mixed_content_issue_details=MixedContentIssueDetails.from_json(json['mixedContentIssueDetails']) if 'mixedContentIssueDetails' in json else None,
|
||||
blocked_by_response_issue_details=BlockedByResponseIssueDetails.from_json(json['blockedByResponseIssueDetails']) if 'blockedByResponseIssueDetails' in json else None,
|
||||
heavy_ad_issue_details=HeavyAdIssueDetails.from_json(json['heavyAdIssueDetails']) if 'heavyAdIssueDetails' in json else None,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class InspectorIssue:
|
||||
'''
|
||||
An inspector issue reported from the back-end.
|
||||
'''
|
||||
code: InspectorIssueCode
|
||||
|
||||
details: InspectorIssueDetails
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['code'] = self.code.to_json()
|
||||
json['details'] = self.details.to_json()
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
code=InspectorIssueCode.from_json(json['code']),
|
||||
details=InspectorIssueDetails.from_json(json['details']),
|
||||
)
|
||||
|
||||
|
||||
def get_encoded_response(
|
||||
request_id: network.RequestId,
|
||||
encoding: str,
|
||||
quality: typing.Optional[float] = None,
|
||||
size_only: typing.Optional[bool] = None
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,typing.Tuple[typing.Optional[str], int, int]]:
|
||||
'''
|
||||
Returns the response body and size if it were re-encoded with the specified settings. Only
|
||||
applies to images.
|
||||
|
||||
:param request_id: Identifier of the network request to get content for.
|
||||
:param encoding: The encoding to use.
|
||||
:param quality: *(Optional)* The quality of the encoding (0-1). (defaults to 1)
|
||||
:param size_only: *(Optional)* Whether to only return the size information (defaults to false).
|
||||
:returns: A tuple with the following items:
|
||||
|
||||
0. **body** - *(Optional)* The encoded body as a base64 string. Omitted if sizeOnly is true.
|
||||
1. **originalSize** - Size before re-encoding.
|
||||
2. **encodedSize** - Size after re-encoding.
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
params['requestId'] = request_id.to_json()
|
||||
params['encoding'] = encoding
|
||||
if quality is not None:
|
||||
params['quality'] = quality
|
||||
if size_only is not None:
|
||||
params['sizeOnly'] = size_only
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Audits.getEncodedResponse',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
return (
|
||||
str(json['body']) if 'body' in json else None,
|
||||
int(json['originalSize']),
|
||||
int(json['encodedSize'])
|
||||
)
|
||||
|
||||
|
||||
def disable() -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Disables issues domain, prevents further issues from being reported to the client.
|
||||
'''
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Audits.disable',
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
def enable() -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Enables issues domain, sends the issues collected so far to the client by means of the
|
||||
``issueAdded`` event.
|
||||
'''
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Audits.enable',
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
@event_class('Audits.issueAdded')
|
||||
@dataclass
|
||||
class IssueAdded:
|
||||
issue: InspectorIssue
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json: T_JSON_DICT) -> IssueAdded:
|
||||
return cls(
|
||||
issue=InspectorIssue.from_json(json['issue'])
|
||||
)
|
||||
@@ -0,0 +1,208 @@
|
||||
# DO NOT EDIT THIS FILE!
|
||||
#
|
||||
# This file is generated from the CDP specification. If you need to make
|
||||
# changes, edit the generator and regenerate all of the modules.
|
||||
#
|
||||
# CDP domain: BackgroundService (experimental)
|
||||
from __future__ import annotations
|
||||
from .util import event_class, T_JSON_DICT
|
||||
from dataclasses import dataclass
|
||||
import enum
|
||||
import typing
|
||||
from . import network
|
||||
from . import service_worker
|
||||
|
||||
|
||||
class ServiceName(enum.Enum):
|
||||
'''
|
||||
The Background Service that will be associated with the commands/events.
|
||||
Every Background Service operates independently, but they share the same
|
||||
API.
|
||||
'''
|
||||
BACKGROUND_FETCH = "backgroundFetch"
|
||||
BACKGROUND_SYNC = "backgroundSync"
|
||||
PUSH_MESSAGING = "pushMessaging"
|
||||
NOTIFICATIONS = "notifications"
|
||||
PAYMENT_HANDLER = "paymentHandler"
|
||||
PERIODIC_BACKGROUND_SYNC = "periodicBackgroundSync"
|
||||
|
||||
def to_json(self):
|
||||
return self.value
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(json)
|
||||
|
||||
|
||||
@dataclass
|
||||
class EventMetadata:
|
||||
'''
|
||||
A key-value pair for additional event information to pass along.
|
||||
'''
|
||||
key: str
|
||||
|
||||
value: str
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['key'] = self.key
|
||||
json['value'] = self.value
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
key=str(json['key']),
|
||||
value=str(json['value']),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class BackgroundServiceEvent:
|
||||
#: Timestamp of the event (in seconds).
|
||||
timestamp: network.TimeSinceEpoch
|
||||
|
||||
#: The origin this event belongs to.
|
||||
origin: str
|
||||
|
||||
#: The Service Worker ID that initiated the event.
|
||||
service_worker_registration_id: service_worker.RegistrationID
|
||||
|
||||
#: The Background Service this event belongs to.
|
||||
service: ServiceName
|
||||
|
||||
#: A description of the event.
|
||||
event_name: str
|
||||
|
||||
#: An identifier that groups related events together.
|
||||
instance_id: str
|
||||
|
||||
#: A list of event-specific information.
|
||||
event_metadata: typing.List[EventMetadata]
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['timestamp'] = self.timestamp.to_json()
|
||||
json['origin'] = self.origin
|
||||
json['serviceWorkerRegistrationId'] = self.service_worker_registration_id.to_json()
|
||||
json['service'] = self.service.to_json()
|
||||
json['eventName'] = self.event_name
|
||||
json['instanceId'] = self.instance_id
|
||||
json['eventMetadata'] = [i.to_json() for i in self.event_metadata]
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
timestamp=network.TimeSinceEpoch.from_json(json['timestamp']),
|
||||
origin=str(json['origin']),
|
||||
service_worker_registration_id=service_worker.RegistrationID.from_json(json['serviceWorkerRegistrationId']),
|
||||
service=ServiceName.from_json(json['service']),
|
||||
event_name=str(json['eventName']),
|
||||
instance_id=str(json['instanceId']),
|
||||
event_metadata=[EventMetadata.from_json(i) for i in json['eventMetadata']],
|
||||
)
|
||||
|
||||
|
||||
def start_observing(
|
||||
service: ServiceName
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Enables event updates for the service.
|
||||
|
||||
:param service:
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
params['service'] = service.to_json()
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'BackgroundService.startObserving',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
def stop_observing(
|
||||
service: ServiceName
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Disables event updates for the service.
|
||||
|
||||
:param service:
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
params['service'] = service.to_json()
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'BackgroundService.stopObserving',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
def set_recording(
|
||||
should_record: bool,
|
||||
service: ServiceName
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Set the recording state for the service.
|
||||
|
||||
:param should_record:
|
||||
:param service:
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
params['shouldRecord'] = should_record
|
||||
params['service'] = service.to_json()
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'BackgroundService.setRecording',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
def clear_events(
|
||||
service: ServiceName
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Clears all stored data for the service.
|
||||
|
||||
:param service:
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
params['service'] = service.to_json()
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'BackgroundService.clearEvents',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
@event_class('BackgroundService.recordingStateChanged')
|
||||
@dataclass
|
||||
class RecordingStateChanged:
|
||||
'''
|
||||
Called when the recording state for the service has been updated.
|
||||
'''
|
||||
is_recording: bool
|
||||
service: ServiceName
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json: T_JSON_DICT) -> RecordingStateChanged:
|
||||
return cls(
|
||||
is_recording=bool(json['isRecording']),
|
||||
service=ServiceName.from_json(json['service'])
|
||||
)
|
||||
|
||||
|
||||
@event_class('BackgroundService.backgroundServiceEventReceived')
|
||||
@dataclass
|
||||
class BackgroundServiceEventReceived:
|
||||
'''
|
||||
Called with all existing backgroundServiceEvents when enabled, and all new
|
||||
events afterwards if enabled and recording.
|
||||
'''
|
||||
background_service_event: BackgroundServiceEvent
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json: T_JSON_DICT) -> BackgroundServiceEventReceived:
|
||||
return cls(
|
||||
background_service_event=BackgroundServiceEvent.from_json(json['backgroundServiceEvent'])
|
||||
)
|
||||
@@ -0,0 +1,579 @@
|
||||
# DO NOT EDIT THIS FILE!
|
||||
#
|
||||
# This file is generated from the CDP specification. If you need to make
|
||||
# changes, edit the generator and regenerate all of the modules.
|
||||
#
|
||||
# CDP domain: Browser
|
||||
from __future__ import annotations
|
||||
from .util import event_class, T_JSON_DICT
|
||||
from dataclasses import dataclass
|
||||
import enum
|
||||
import typing
|
||||
from . import target
|
||||
|
||||
|
||||
class BrowserContextID(str):
|
||||
def to_json(self) -> str:
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json: str) -> BrowserContextID:
|
||||
return cls(json)
|
||||
|
||||
def __repr__(self):
|
||||
return 'BrowserContextID({})'.format(super().__repr__())
|
||||
|
||||
|
||||
class WindowID(int):
|
||||
def to_json(self) -> int:
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json: int) -> WindowID:
|
||||
return cls(json)
|
||||
|
||||
def __repr__(self):
|
||||
return 'WindowID({})'.format(super().__repr__())
|
||||
|
||||
|
||||
class WindowState(enum.Enum):
|
||||
'''
|
||||
The state of the browser window.
|
||||
'''
|
||||
NORMAL = "normal"
|
||||
MINIMIZED = "minimized"
|
||||
MAXIMIZED = "maximized"
|
||||
FULLSCREEN = "fullscreen"
|
||||
|
||||
def to_json(self):
|
||||
return self.value
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(json)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Bounds:
|
||||
'''
|
||||
Browser window bounds information
|
||||
'''
|
||||
#: The offset from the left edge of the screen to the window in pixels.
|
||||
left: typing.Optional[int] = None
|
||||
|
||||
#: The offset from the top edge of the screen to the window in pixels.
|
||||
top: typing.Optional[int] = None
|
||||
|
||||
#: The window width in pixels.
|
||||
width: typing.Optional[int] = None
|
||||
|
||||
#: The window height in pixels.
|
||||
height: typing.Optional[int] = None
|
||||
|
||||
#: The window state. Default to normal.
|
||||
window_state: typing.Optional[WindowState] = None
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
if self.left is not None:
|
||||
json['left'] = self.left
|
||||
if self.top is not None:
|
||||
json['top'] = self.top
|
||||
if self.width is not None:
|
||||
json['width'] = self.width
|
||||
if self.height is not None:
|
||||
json['height'] = self.height
|
||||
if self.window_state is not None:
|
||||
json['windowState'] = self.window_state.to_json()
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
left=int(json['left']) if 'left' in json else None,
|
||||
top=int(json['top']) if 'top' in json else None,
|
||||
width=int(json['width']) if 'width' in json else None,
|
||||
height=int(json['height']) if 'height' in json else None,
|
||||
window_state=WindowState.from_json(json['windowState']) if 'windowState' in json else None,
|
||||
)
|
||||
|
||||
|
||||
class PermissionType(enum.Enum):
|
||||
ACCESSIBILITY_EVENTS = "accessibilityEvents"
|
||||
AUDIO_CAPTURE = "audioCapture"
|
||||
BACKGROUND_SYNC = "backgroundSync"
|
||||
BACKGROUND_FETCH = "backgroundFetch"
|
||||
CLIPBOARD_READ_WRITE = "clipboardReadWrite"
|
||||
CLIPBOARD_SANITIZED_WRITE = "clipboardSanitizedWrite"
|
||||
DURABLE_STORAGE = "durableStorage"
|
||||
FLASH = "flash"
|
||||
GEOLOCATION = "geolocation"
|
||||
MIDI = "midi"
|
||||
MIDI_SYSEX = "midiSysex"
|
||||
NFC = "nfc"
|
||||
NOTIFICATIONS = "notifications"
|
||||
PAYMENT_HANDLER = "paymentHandler"
|
||||
PERIODIC_BACKGROUND_SYNC = "periodicBackgroundSync"
|
||||
PROTECTED_MEDIA_IDENTIFIER = "protectedMediaIdentifier"
|
||||
SENSORS = "sensors"
|
||||
VIDEO_CAPTURE = "videoCapture"
|
||||
IDLE_DETECTION = "idleDetection"
|
||||
WAKE_LOCK_SCREEN = "wakeLockScreen"
|
||||
WAKE_LOCK_SYSTEM = "wakeLockSystem"
|
||||
|
||||
def to_json(self):
|
||||
return self.value
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(json)
|
||||
|
||||
|
||||
class PermissionSetting(enum.Enum):
|
||||
GRANTED = "granted"
|
||||
DENIED = "denied"
|
||||
PROMPT = "prompt"
|
||||
|
||||
def to_json(self):
|
||||
return self.value
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(json)
|
||||
|
||||
|
||||
@dataclass
|
||||
class PermissionDescriptor:
|
||||
'''
|
||||
Definition of PermissionDescriptor defined in the Permissions API:
|
||||
https://w3c.github.io/permissions/#dictdef-permissiondescriptor.
|
||||
'''
|
||||
#: Name of permission.
|
||||
#: See https://cs.chromium.org/chromium/src/third_party/blink/renderer/modules/permissions/permission_descriptor.idl for valid permission names.
|
||||
name: str
|
||||
|
||||
#: For "midi" permission, may also specify sysex control.
|
||||
sysex: typing.Optional[bool] = None
|
||||
|
||||
#: For "push" permission, may specify userVisibleOnly.
|
||||
#: Note that userVisibleOnly = true is the only currently supported type.
|
||||
user_visible_only: typing.Optional[bool] = None
|
||||
|
||||
#: For "wake-lock" permission, must specify type as either "screen" or "system".
|
||||
type_: typing.Optional[str] = None
|
||||
|
||||
#: For "clipboard" permission, may specify allowWithoutSanitization.
|
||||
allow_without_sanitization: typing.Optional[bool] = None
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['name'] = self.name
|
||||
if self.sysex is not None:
|
||||
json['sysex'] = self.sysex
|
||||
if self.user_visible_only is not None:
|
||||
json['userVisibleOnly'] = self.user_visible_only
|
||||
if self.type_ is not None:
|
||||
json['type'] = self.type_
|
||||
if self.allow_without_sanitization is not None:
|
||||
json['allowWithoutSanitization'] = self.allow_without_sanitization
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
name=str(json['name']),
|
||||
sysex=bool(json['sysex']) if 'sysex' in json else None,
|
||||
user_visible_only=bool(json['userVisibleOnly']) if 'userVisibleOnly' in json else None,
|
||||
type_=str(json['type']) if 'type' in json else None,
|
||||
allow_without_sanitization=bool(json['allowWithoutSanitization']) if 'allowWithoutSanitization' in json else None,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Bucket:
|
||||
'''
|
||||
Chrome histogram bucket.
|
||||
'''
|
||||
#: Minimum value (inclusive).
|
||||
low: int
|
||||
|
||||
#: Maximum value (exclusive).
|
||||
high: int
|
||||
|
||||
#: Number of samples.
|
||||
count: int
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['low'] = self.low
|
||||
json['high'] = self.high
|
||||
json['count'] = self.count
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
low=int(json['low']),
|
||||
high=int(json['high']),
|
||||
count=int(json['count']),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Histogram:
|
||||
'''
|
||||
Chrome histogram.
|
||||
'''
|
||||
#: Name.
|
||||
name: str
|
||||
|
||||
#: Sum of sample values.
|
||||
sum_: int
|
||||
|
||||
#: Total number of samples.
|
||||
count: int
|
||||
|
||||
#: Buckets.
|
||||
buckets: typing.List[Bucket]
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['name'] = self.name
|
||||
json['sum'] = self.sum_
|
||||
json['count'] = self.count
|
||||
json['buckets'] = [i.to_json() for i in self.buckets]
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
name=str(json['name']),
|
||||
sum_=int(json['sum']),
|
||||
count=int(json['count']),
|
||||
buckets=[Bucket.from_json(i) for i in json['buckets']],
|
||||
)
|
||||
|
||||
|
||||
def set_permission(
|
||||
permission: PermissionDescriptor,
|
||||
setting: PermissionSetting,
|
||||
origin: typing.Optional[str] = None,
|
||||
browser_context_id: typing.Optional[BrowserContextID] = None
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Set permission settings for given origin.
|
||||
|
||||
**EXPERIMENTAL**
|
||||
|
||||
:param permission: Descriptor of permission to override.
|
||||
:param setting: Setting of the permission.
|
||||
:param origin: *(Optional)* Origin the permission applies to, all origins if not specified.
|
||||
:param browser_context_id: *(Optional)* Context to override. When omitted, default browser context is used.
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
params['permission'] = permission.to_json()
|
||||
params['setting'] = setting.to_json()
|
||||
if origin is not None:
|
||||
params['origin'] = origin
|
||||
if browser_context_id is not None:
|
||||
params['browserContextId'] = browser_context_id.to_json()
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Browser.setPermission',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
def grant_permissions(
|
||||
permissions: typing.List[PermissionType],
|
||||
origin: typing.Optional[str] = None,
|
||||
browser_context_id: typing.Optional[BrowserContextID] = None
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Grant specific permissions to the given origin and reject all others.
|
||||
|
||||
**EXPERIMENTAL**
|
||||
|
||||
:param permissions:
|
||||
:param origin: *(Optional)* Origin the permission applies to, all origins if not specified.
|
||||
:param browser_context_id: *(Optional)* BrowserContext to override permissions. When omitted, default browser context is used.
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
params['permissions'] = [i.to_json() for i in permissions]
|
||||
if origin is not None:
|
||||
params['origin'] = origin
|
||||
if browser_context_id is not None:
|
||||
params['browserContextId'] = browser_context_id.to_json()
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Browser.grantPermissions',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
def reset_permissions(
|
||||
browser_context_id: typing.Optional[BrowserContextID] = None
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Reset all permission management for all origins.
|
||||
|
||||
**EXPERIMENTAL**
|
||||
|
||||
:param browser_context_id: *(Optional)* BrowserContext to reset permissions. When omitted, default browser context is used.
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
if browser_context_id is not None:
|
||||
params['browserContextId'] = browser_context_id.to_json()
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Browser.resetPermissions',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
def set_download_behavior(
|
||||
behavior: str,
|
||||
browser_context_id: typing.Optional[BrowserContextID] = None,
|
||||
download_path: typing.Optional[str] = None
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Set the behavior when downloading a file.
|
||||
|
||||
**EXPERIMENTAL**
|
||||
|
||||
:param behavior: Whether to allow all or deny all download requests, or use default Chrome behavior if available (otherwise deny). ``allowAndName`` allows download and names files according to their dowmload guids.
|
||||
:param browser_context_id: *(Optional)* BrowserContext to set download behavior. When omitted, default browser context is used.
|
||||
:param download_path: *(Optional)* The default path to save downloaded files to. This is requred if behavior is set to 'allow' or 'allowAndName'.
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
params['behavior'] = behavior
|
||||
if browser_context_id is not None:
|
||||
params['browserContextId'] = browser_context_id.to_json()
|
||||
if download_path is not None:
|
||||
params['downloadPath'] = download_path
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Browser.setDownloadBehavior',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
def close() -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Close browser gracefully.
|
||||
'''
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Browser.close',
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
def crash() -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Crashes browser on the main thread.
|
||||
|
||||
**EXPERIMENTAL**
|
||||
'''
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Browser.crash',
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
def crash_gpu_process() -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Crashes GPU process.
|
||||
|
||||
**EXPERIMENTAL**
|
||||
'''
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Browser.crashGpuProcess',
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
def get_version() -> typing.Generator[T_JSON_DICT,T_JSON_DICT,typing.Tuple[str, str, str, str, str]]:
|
||||
'''
|
||||
Returns version information.
|
||||
|
||||
:returns: A tuple with the following items:
|
||||
|
||||
0. **protocolVersion** - Protocol version.
|
||||
1. **product** - Product name.
|
||||
2. **revision** - Product revision.
|
||||
3. **userAgent** - User-Agent.
|
||||
4. **jsVersion** - V8 version.
|
||||
'''
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Browser.getVersion',
|
||||
}
|
||||
json = yield cmd_dict
|
||||
return (
|
||||
str(json['protocolVersion']),
|
||||
str(json['product']),
|
||||
str(json['revision']),
|
||||
str(json['userAgent']),
|
||||
str(json['jsVersion'])
|
||||
)
|
||||
|
||||
|
||||
def get_browser_command_line() -> typing.Generator[T_JSON_DICT,T_JSON_DICT,typing.List[str]]:
|
||||
'''
|
||||
Returns the command line switches for the browser process if, and only if
|
||||
--enable-automation is on the commandline.
|
||||
|
||||
**EXPERIMENTAL**
|
||||
|
||||
:returns: Commandline parameters
|
||||
'''
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Browser.getBrowserCommandLine',
|
||||
}
|
||||
json = yield cmd_dict
|
||||
return [str(i) for i in json['arguments']]
|
||||
|
||||
|
||||
def get_histograms(
|
||||
query: typing.Optional[str] = None,
|
||||
delta: typing.Optional[bool] = None
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,typing.List[Histogram]]:
|
||||
'''
|
||||
Get Chrome histograms.
|
||||
|
||||
**EXPERIMENTAL**
|
||||
|
||||
:param query: *(Optional)* Requested substring in name. Only histograms which have query as a substring in their name are extracted. An empty or absent query returns all histograms.
|
||||
:param delta: *(Optional)* If true, retrieve delta since last call.
|
||||
:returns: Histograms.
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
if query is not None:
|
||||
params['query'] = query
|
||||
if delta is not None:
|
||||
params['delta'] = delta
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Browser.getHistograms',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
return [Histogram.from_json(i) for i in json['histograms']]
|
||||
|
||||
|
||||
def get_histogram(
|
||||
name: str,
|
||||
delta: typing.Optional[bool] = None
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,Histogram]:
|
||||
'''
|
||||
Get a Chrome histogram by name.
|
||||
|
||||
**EXPERIMENTAL**
|
||||
|
||||
:param name: Requested histogram name.
|
||||
:param delta: *(Optional)* If true, retrieve delta since last call.
|
||||
:returns: Histogram.
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
params['name'] = name
|
||||
if delta is not None:
|
||||
params['delta'] = delta
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Browser.getHistogram',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
return Histogram.from_json(json['histogram'])
|
||||
|
||||
|
||||
def get_window_bounds(
|
||||
window_id: WindowID
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,Bounds]:
|
||||
'''
|
||||
Get position and size of the browser window.
|
||||
|
||||
**EXPERIMENTAL**
|
||||
|
||||
:param window_id: Browser window id.
|
||||
:returns: Bounds information of the window. When window state is 'minimized', the restored window position and size are returned.
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
params['windowId'] = window_id.to_json()
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Browser.getWindowBounds',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
return Bounds.from_json(json['bounds'])
|
||||
|
||||
|
||||
def get_window_for_target(
|
||||
target_id: typing.Optional[target.TargetID] = None
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,typing.Tuple[WindowID, Bounds]]:
|
||||
'''
|
||||
Get the browser window that contains the devtools target.
|
||||
|
||||
**EXPERIMENTAL**
|
||||
|
||||
:param target_id: *(Optional)* Devtools agent host id. If called as a part of the session, associated targetId is used.
|
||||
:returns: A tuple with the following items:
|
||||
|
||||
0. **windowId** - Browser window id.
|
||||
1. **bounds** - Bounds information of the window. When window state is 'minimized', the restored window position and size are returned.
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
if target_id is not None:
|
||||
params['targetId'] = target_id.to_json()
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Browser.getWindowForTarget',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
return (
|
||||
WindowID.from_json(json['windowId']),
|
||||
Bounds.from_json(json['bounds'])
|
||||
)
|
||||
|
||||
|
||||
def set_window_bounds(
|
||||
window_id: WindowID,
|
||||
bounds: Bounds
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Set position and/or size of the browser window.
|
||||
|
||||
**EXPERIMENTAL**
|
||||
|
||||
:param window_id: Browser window id.
|
||||
:param bounds: New window bounds. The 'minimized', 'maximized' and 'fullscreen' states cannot be combined with 'left', 'top', 'width' or 'height'. Leaves unspecified fields unchanged.
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
params['windowId'] = window_id.to_json()
|
||||
params['bounds'] = bounds.to_json()
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Browser.setWindowBounds',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
def set_dock_tile(
|
||||
badge_label: typing.Optional[str] = None,
|
||||
image: typing.Optional[str] = None
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Set dock tile details, platform-specific.
|
||||
|
||||
**EXPERIMENTAL**
|
||||
|
||||
:param badge_label: *(Optional)*
|
||||
:param image: *(Optional)* Png encoded image.
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
if badge_label is not None:
|
||||
params['badgeLabel'] = badge_label
|
||||
if image is not None:
|
||||
params['image'] = image
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Browser.setDockTile',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
@@ -0,0 +1,287 @@
|
||||
# DO NOT EDIT THIS FILE!
|
||||
#
|
||||
# This file is generated from the CDP specification. If you need to make
|
||||
# changes, edit the generator and regenerate all of the modules.
|
||||
#
|
||||
# CDP domain: CacheStorage (experimental)
|
||||
from __future__ import annotations
|
||||
from .util import event_class, T_JSON_DICT
|
||||
from dataclasses import dataclass
|
||||
import enum
|
||||
import typing
|
||||
|
||||
class CacheId(str):
|
||||
'''
|
||||
Unique identifier of the Cache object.
|
||||
'''
|
||||
def to_json(self) -> str:
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json: str) -> CacheId:
|
||||
return cls(json)
|
||||
|
||||
def __repr__(self):
|
||||
return 'CacheId({})'.format(super().__repr__())
|
||||
|
||||
|
||||
class CachedResponseType(enum.Enum):
|
||||
'''
|
||||
type of HTTP response cached
|
||||
'''
|
||||
BASIC = "basic"
|
||||
CORS = "cors"
|
||||
DEFAULT = "default"
|
||||
ERROR = "error"
|
||||
OPAQUE_RESPONSE = "opaqueResponse"
|
||||
OPAQUE_REDIRECT = "opaqueRedirect"
|
||||
|
||||
def to_json(self):
|
||||
return self.value
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(json)
|
||||
|
||||
|
||||
@dataclass
|
||||
class DataEntry:
|
||||
'''
|
||||
Data entry.
|
||||
'''
|
||||
#: Request URL.
|
||||
request_url: str
|
||||
|
||||
#: Request method.
|
||||
request_method: str
|
||||
|
||||
#: Request headers
|
||||
request_headers: typing.List[Header]
|
||||
|
||||
#: Number of seconds since epoch.
|
||||
response_time: float
|
||||
|
||||
#: HTTP response status code.
|
||||
response_status: int
|
||||
|
||||
#: HTTP response status text.
|
||||
response_status_text: str
|
||||
|
||||
#: HTTP response type
|
||||
response_type: CachedResponseType
|
||||
|
||||
#: Response headers
|
||||
response_headers: typing.List[Header]
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['requestURL'] = self.request_url
|
||||
json['requestMethod'] = self.request_method
|
||||
json['requestHeaders'] = [i.to_json() for i in self.request_headers]
|
||||
json['responseTime'] = self.response_time
|
||||
json['responseStatus'] = self.response_status
|
||||
json['responseStatusText'] = self.response_status_text
|
||||
json['responseType'] = self.response_type.to_json()
|
||||
json['responseHeaders'] = [i.to_json() for i in self.response_headers]
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
request_url=str(json['requestURL']),
|
||||
request_method=str(json['requestMethod']),
|
||||
request_headers=[Header.from_json(i) for i in json['requestHeaders']],
|
||||
response_time=float(json['responseTime']),
|
||||
response_status=int(json['responseStatus']),
|
||||
response_status_text=str(json['responseStatusText']),
|
||||
response_type=CachedResponseType.from_json(json['responseType']),
|
||||
response_headers=[Header.from_json(i) for i in json['responseHeaders']],
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Cache:
|
||||
'''
|
||||
Cache identifier.
|
||||
'''
|
||||
#: An opaque unique id of the cache.
|
||||
cache_id: CacheId
|
||||
|
||||
#: Security origin of the cache.
|
||||
security_origin: str
|
||||
|
||||
#: The name of the cache.
|
||||
cache_name: str
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['cacheId'] = self.cache_id.to_json()
|
||||
json['securityOrigin'] = self.security_origin
|
||||
json['cacheName'] = self.cache_name
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
cache_id=CacheId.from_json(json['cacheId']),
|
||||
security_origin=str(json['securityOrigin']),
|
||||
cache_name=str(json['cacheName']),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Header:
|
||||
name: str
|
||||
|
||||
value: str
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['name'] = self.name
|
||||
json['value'] = self.value
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
name=str(json['name']),
|
||||
value=str(json['value']),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class CachedResponse:
|
||||
'''
|
||||
Cached response
|
||||
'''
|
||||
#: Entry content, base64-encoded.
|
||||
body: str
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['body'] = self.body
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
body=str(json['body']),
|
||||
)
|
||||
|
||||
|
||||
def delete_cache(
|
||||
cache_id: CacheId
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Deletes a cache.
|
||||
|
||||
:param cache_id: Id of cache for deletion.
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
params['cacheId'] = cache_id.to_json()
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'CacheStorage.deleteCache',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
def delete_entry(
|
||||
cache_id: CacheId,
|
||||
request: str
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Deletes a cache entry.
|
||||
|
||||
:param cache_id: Id of cache where the entry will be deleted.
|
||||
:param request: URL spec of the request.
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
params['cacheId'] = cache_id.to_json()
|
||||
params['request'] = request
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'CacheStorage.deleteEntry',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
def request_cache_names(
|
||||
security_origin: str
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,typing.List[Cache]]:
|
||||
'''
|
||||
Requests cache names.
|
||||
|
||||
:param security_origin: Security origin.
|
||||
:returns: Caches for the security origin.
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
params['securityOrigin'] = security_origin
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'CacheStorage.requestCacheNames',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
return [Cache.from_json(i) for i in json['caches']]
|
||||
|
||||
|
||||
def request_cached_response(
|
||||
cache_id: CacheId,
|
||||
request_url: str,
|
||||
request_headers: typing.List[Header]
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,CachedResponse]:
|
||||
'''
|
||||
Fetches cache entry.
|
||||
|
||||
:param cache_id: Id of cache that contains the entry.
|
||||
:param request_url: URL spec of the request.
|
||||
:param request_headers: headers of the request.
|
||||
:returns: Response read from the cache.
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
params['cacheId'] = cache_id.to_json()
|
||||
params['requestURL'] = request_url
|
||||
params['requestHeaders'] = [i.to_json() for i in request_headers]
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'CacheStorage.requestCachedResponse',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
return CachedResponse.from_json(json['response'])
|
||||
|
||||
|
||||
def request_entries(
|
||||
cache_id: CacheId,
|
||||
skip_count: typing.Optional[int] = None,
|
||||
page_size: typing.Optional[int] = None,
|
||||
path_filter: typing.Optional[str] = None
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,typing.Tuple[typing.List[DataEntry], float]]:
|
||||
'''
|
||||
Requests data from cache.
|
||||
|
||||
:param cache_id: ID of cache to get entries from.
|
||||
:param skip_count: *(Optional)* Number of records to skip.
|
||||
:param page_size: *(Optional)* Number of records to fetch.
|
||||
:param path_filter: *(Optional)* If present, only return the entries containing this substring in the path
|
||||
:returns: A tuple with the following items:
|
||||
|
||||
0. **cacheDataEntries** - Array of object store data entries.
|
||||
1. **returnCount** - Count of returned entries from this storage. If pathFilter is empty, it is the count of all entries from this storage.
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
params['cacheId'] = cache_id.to_json()
|
||||
if skip_count is not None:
|
||||
params['skipCount'] = skip_count
|
||||
if page_size is not None:
|
||||
params['pageSize'] = page_size
|
||||
if path_filter is not None:
|
||||
params['pathFilter'] = path_filter
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'CacheStorage.requestEntries',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
return (
|
||||
[DataEntry.from_json(i) for i in json['cacheDataEntries']],
|
||||
float(json['returnCount'])
|
||||
)
|
||||
@@ -0,0 +1,153 @@
|
||||
# DO NOT EDIT THIS FILE!
|
||||
#
|
||||
# This file is generated from the CDP specification. If you need to make
|
||||
# changes, edit the generator and regenerate all of the modules.
|
||||
#
|
||||
# CDP domain: Cast (experimental)
|
||||
from __future__ import annotations
|
||||
from .util import event_class, T_JSON_DICT
|
||||
from dataclasses import dataclass
|
||||
import enum
|
||||
import typing
|
||||
|
||||
@dataclass
|
||||
class Sink:
|
||||
name: str
|
||||
|
||||
id_: str
|
||||
|
||||
#: Text describing the current session. Present only if there is an active
|
||||
#: session on the sink.
|
||||
session: typing.Optional[str] = None
|
||||
|
||||
def to_json(self):
|
||||
json = dict()
|
||||
json['name'] = self.name
|
||||
json['id'] = self.id_
|
||||
if self.session is not None:
|
||||
json['session'] = self.session
|
||||
return json
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json):
|
||||
return cls(
|
||||
name=str(json['name']),
|
||||
id_=str(json['id']),
|
||||
session=str(json['session']) if 'session' in json else None,
|
||||
)
|
||||
|
||||
|
||||
def enable(
|
||||
presentation_url: typing.Optional[str] = None
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Starts observing for sinks that can be used for tab mirroring, and if set,
|
||||
sinks compatible with ``presentationUrl`` as well. When sinks are found, a
|
||||
``sinksUpdated`` event is fired.
|
||||
Also starts observing for issue messages. When an issue is added or removed,
|
||||
an ``issueUpdated`` event is fired.
|
||||
|
||||
:param presentation_url: *(Optional)*
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
if presentation_url is not None:
|
||||
params['presentationUrl'] = presentation_url
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Cast.enable',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
def disable() -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Stops observing for sinks and issues.
|
||||
'''
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Cast.disable',
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
def set_sink_to_use(
|
||||
sink_name: str
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Sets a sink to be used when the web page requests the browser to choose a
|
||||
sink via Presentation API, Remote Playback API, or Cast SDK.
|
||||
|
||||
:param sink_name:
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
params['sinkName'] = sink_name
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Cast.setSinkToUse',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
def start_tab_mirroring(
|
||||
sink_name: str
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Starts mirroring the tab to the sink.
|
||||
|
||||
:param sink_name:
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
params['sinkName'] = sink_name
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Cast.startTabMirroring',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
def stop_casting(
|
||||
sink_name: str
|
||||
) -> typing.Generator[T_JSON_DICT,T_JSON_DICT,None]:
|
||||
'''
|
||||
Stops the active Cast session on the sink.
|
||||
|
||||
:param sink_name:
|
||||
'''
|
||||
params: T_JSON_DICT = dict()
|
||||
params['sinkName'] = sink_name
|
||||
cmd_dict: T_JSON_DICT = {
|
||||
'method': 'Cast.stopCasting',
|
||||
'params': params,
|
||||
}
|
||||
json = yield cmd_dict
|
||||
|
||||
|
||||
@event_class('Cast.sinksUpdated')
|
||||
@dataclass
|
||||
class SinksUpdated:
|
||||
'''
|
||||
This is fired whenever the list of available sinks changes. A sink is a
|
||||
device or a software surface that you can cast to.
|
||||
'''
|
||||
sinks: typing.List[Sink]
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json: T_JSON_DICT) -> SinksUpdated:
|
||||
return cls(
|
||||
sinks=[Sink.from_json(i) for i in json['sinks']]
|
||||
)
|
||||
|
||||
|
||||
@event_class('Cast.issueUpdated')
|
||||
@dataclass
|
||||
class IssueUpdated:
|
||||
'''
|
||||
This is fired whenever the outstanding issue/error message changes.
|
||||
``issueMessage`` is empty if there is no issue.
|
||||
'''
|
||||
issue_message: str
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json: T_JSON_DICT) -> IssueUpdated:
|
||||
return cls(
|
||||
issue_message=str(json['issueMessage'])
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user