Module pyseext.tree_helper
Module that contains our TreeHelper class.
Expand source code
"""
Module that contains our TreeHelper class.
"""
import logging
import time
from typing import Union
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.remote.webelement import WebElement
from pyseext.has_referenced_javascript import HasReferencedJavaScript
from pyseext.core import Core
from pyseext.menu_helper import MenuHelper
class TreeHelper(HasReferencedJavaScript):
"""A class to help with using trees, through Ext's interfaces."""
# Class variables
_IS_TREE_LOADING_TEMPLATE: str = "return globalThis.PySeExt.TreeHelper.isTreeLoading('{tree_cq}')"
"""The script template to use to call the JavaScript method PySeExt.TreeHelper.isTreeLoading
Requires the inserts: {tree_cq}"""
_GET_NODE_ELEMENT_TEMPLATE: str = "return globalThis.PySeExt.TreeHelper.getNodeElement('{tree_cq}', {node_text_or_data}, '{css_query}')"
"""The script template to use to call the JavaScript method PySeExt.TreeHelper.getNodeElement
Requires the inserts: {tree_cq}, {node_text_or_data}, {css_query}"""
_GET_NODE_ELEMENT_WITH_ROOT_TEMPLATE: str = "return globalThis.PySeExt.TreeHelper.getNodeElement('{tree_cq}', {node_text_or_data}, '{css_query}', {root_node_text_or_data})"
"""The script template to use to call the JavaScript method PySeExt.TreeHelper.getNodeElement including a root node to search from
Requires the inserts: {tree_cq}, {node_text_or_data}, {css_query}, {root_node_text_or_data}"""
_RELOAD_NODE_TEMPLATE: str = "return globalThis.PySeExt.TreeHelper.reloadNode('{tree_cq}', {node_text_or_data})"
"""The script template to use to call the JavaScript method PySeExt.TreeHelper.reloadNode
Requires the inserts: {tree_cq}, {node_text_or_data}"""
_RELOAD_NODE_WITH_ROOT_TEMPLATE: str = "return globalThis.PySeExt.TreeHelper.reloadNode('{tree_cq}', {node_text_or_data}, {root_node_text_or_data})"
"""The script template to use to call the JavaScript method PySeExt.TreeHelper.reloadNode including a root node to search from
Requires the inserts: {tree_cq}, {node_text_or_data}, {root_node_text_or_data}"""
_ICON_CSS_SELECTOR: str = ".x-tree-icon"
"""The CSS selector to use with get_node_element to find the node icon element.
"""
_EXPANDER_CSS_SELECTOR: str = ".x-tree-expander"
"""The CSS selector to use with get_node_element to find the node expander element.
"""
_NODE_TEXT_CSS_SELECTOR: str = ".x-tree-node-text"
"""The CSS selector to use with get_node_element to find the node text element.
"""
def __init__(self, driver: WebDriver):
"""Initialises an instance of this class
Args:
driver (WebDriver): The webdriver to use
"""
self._logger = logging.getLogger(__name__)
"""The Logger instance for this class instance"""
self._driver = driver
"""The WebDriver instance for this class instance"""
self._action_chains = ActionChains(driver)
"""The ActionChains instance for this class instance"""
self._core = Core(driver)
"""The `Core` instance for this class instance"""
self._menu_helper = MenuHelper(driver)
"""The `MenuHelper` instance for this class instance"""
# Initialise our base class
super().__init__(driver, self._logger)
def is_tree_loading(self, tree_cq: str):
"""Determine whether the tree (any part of it) is currently loading.
You should call this before calling any tree interaction methods,
since we cannot pass things back in callbacks!
Args:
tree_cq (str): The component query to use to find the tree.
Returns:
bool: True if the tree is loaded, False otherwise.
"""
script = self._IS_TREE_LOADING_TEMPLATE.format(tree_cq=tree_cq)
self.ensure_javascript_loaded()
return self._driver.execute_script(script)
def wait_until_tree_not_loading(self,
tree_cq: str,
timeout: float = 30,
poll_frequecy: float = 0.2,
recheck_time_if_false: float = 0.2):
"""Waits until the tree identified by the component query is not loading,
or the timeout is hit
Args:
tree_cq (str): The component query for the tree.
timeout (float, optional): The number of seconds to wait before erroring. Defaults to 30.
poll_frequency (float, optional): Number of seconds to poll. Defaults to 0.2.
recheck_time_if_false (float, optional): If we get a result such that no Ajax calls are in progress, this is the amount of time to wait to check again. Defaults to 0.2.
"""
WebDriverWait(self._driver, timeout, poll_frequency = poll_frequecy).until(TreeHelper.TreeNotLoadingExpectation(tree_cq, recheck_time_if_false))
def get_node_icon_element(self,
tree_cq: str,
node_text_or_data: Union[str, dict],
root_node_text_or_data: Union[str, dict, None] = None) -> WebElement:
"""Finds a node by text or data, then the child HTML element that holds it's icon.
Args:
tree_cq (str): The component query to use to find the tree.
node_text_or_data (Union[str, dict]): The node text or data to find.
root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node.
Can be an immediate parent, or higher up in the tree.
Returns:
WebElement: The DOM element for the node icon.
"""
return self.get_node_element(tree_cq, node_text_or_data, self._ICON_CSS_SELECTOR, root_node_text_or_data)
def get_node_text_element(self,
tree_cq: str,
node_text_or_data: Union[str, dict],
root_node_text_or_data: Union[str, dict, None] = None) -> WebElement:
"""Finds a node by text or data, then the child HTML element that holds it's text.
Args:
tree_cq (str): The component query to use to find the tree.
node_text_or_data (Union[str, dict]): The node text or data to find.
root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node.
Can be an immediate parent, or higher up in the tree.
Returns:
WebElement: The DOM element for the node text.
"""
return self.get_node_element(tree_cq, node_text_or_data, self._NODE_TEXT_CSS_SELECTOR, root_node_text_or_data)
def get_node_expander_element(self,
tree_cq: str,
node_text_or_data: Union[str, dict],
root_node_text_or_data: Union[str, dict, None] = None) -> WebElement:
"""Finds a node by text or data, then the child HTML element that holds it's expander UI element.
Args:
tree_cq (str): The component query to use to find the tree.
node_text_or_data (Union[str, dict]): The node text or data to find.
root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node.
Can be an immediate parent, or higher up in the tree.
Returns:
WebElement: The DOM element for the node's expander.
"""
return self.get_node_element(tree_cq, node_text_or_data, self._EXPANDER_CSS_SELECTOR, root_node_text_or_data)
def get_node_element(self,
tree_cq: str,
node_text_or_data: Union[str, dict],
css_query: str,
root_node_text_or_data: Union[str, dict, None] = None) -> WebElement:
"""Finds a node by text or data, then a child element by CSS query.
Args:
tree_cq (str): The component query to use to find the tree.
node_text_or_data (Union[str, dict]): The node text or data to find.
css_query (str): The CSS to query for in the found node row element.
Some expected ones:
Expander UI element = '.x-tree-expander'
Node icon = '.x-tree-icon'
Node text = '.x-tree-node-text'
If need those you'd use one of the other methods though.
This is in case need to click on another part of the node's row.
root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node.
Can be an immediate parent, or higher up in the tree.
Returns:
WebElement: The DOM element for the node's expander.
"""
self.wait_until_tree_not_loading(tree_cq)
if isinstance(node_text_or_data, str):
node_text_or_data = f"'{node_text_or_data}'"
if isinstance(root_node_text_or_data, str):
root_node_text_or_data = f"'{root_node_text_or_data}'"
if root_node_text_or_data:
script = self._GET_NODE_ELEMENT_WITH_ROOT_TEMPLATE.format(tree_cq=tree_cq, node_text_or_data=node_text_or_data, css_query=css_query, root_node_text_or_data=root_node_text_or_data)
else:
script = self._GET_NODE_ELEMENT_TEMPLATE.format(tree_cq=tree_cq, node_text_or_data=node_text_or_data, css_query=css_query)
self.ensure_javascript_loaded()
return self._driver.execute_script(script)
def open_node_context_menu(self,
tree_cq: str,
node_text_or_data: Union[str, dict],
root_node_text_or_data: Union[str, dict, None] = None):
"""Finds a node's icon element by text or data, then right clicks on it.
Args:
tree_cq (str): The component query to use to find the tree.
node_text_or_data (Union[str, dict]): The node text or data to find.
root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node.
Can be an immediate parent, or higher up in the tree.
"""
self.context_click_node_element(tree_cq, node_text_or_data, self._ICON_CSS_SELECTOR, root_node_text_or_data)
def check_node_context_menu_contains_items(self,
tree_cq: str,
node_text_or_data: Union[str, dict],
expected_items: list[str],
check_contains_no_additional_items: bool = True,
ignore_spacers: bool = False,
root_node_text_or_data: Union[str, dict, None] = None):
"""Opens the context menu for a node, and checks that it contains the specified items.
Args:
tree_cq (str): The component query to use to find the tree.
node_text_or_data (Union[str, dict]): The node text or data to find.
expected_items (list[str]): A list of menu items (text) that we're expecting to find.
Spacers have text containing of a single space (MenuHelper.SPACER_TEXT_CONTEXT), but can be ignored using ignore_spacers if desired.
e.g. ['Add', 'Edit', 'Delete', MenuHelper.SPACER_TEXT_CONTEXT, 'Refresh']
check_contains_no_additional_items (bool, optional): Indicates whether we should check that there are no additional items in the menu.
Defaults to True.
ignore_spacers (bool, optional): Indicates whether we should include spacers in our checks. Defaults to False.
root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node.
Can be an immediate parent, or higher up in the tree.
"""
self.open_node_context_menu(tree_cq, node_text_or_data, root_node_text_or_data)
unexpected_items: list[str] = []
# Get all menu items
menu_items = self._menu_helper.get_enabled_menu_items()
# Each element has a text member we can compare.
for item in menu_items:
if ignore_spacers is True and item.text == MenuHelper.SPACER_TEXT_CONTEXT:
continue
if not item.text in expected_items:
# Item not expected
if check_contains_no_additional_items:
unexpected_items.append(item.text)
else:
# Item expected and found, so remove from expected list.
expected_items.remove(item.text)
# If have nothing left to find, and we're not checking for unexpected items, then can break from our loop.
if not check_contains_no_additional_items and len(expected_items) == 0:
break
# Is there anything left in our expected item list?
if len(expected_items) > 0:
raise TreeHelper.ExpectedMenuItemsNotFoundException(tree_cq,
node_text_or_data,
expected_items)
# Was there anything in the menu that was not expected, and we were checking?
if len(unexpected_items) > 0:
raise TreeHelper.UnexpectedMenuItemsFoundException(tree_cq,
node_text_or_data,
unexpected_items)
def click_node_expander(self,
tree_cq: str,
node_text_or_data: Union[str, dict],
root_node_text_or_data: Union[str, dict, None] = None):
"""Finds a node's expander element by text or data, then clicks on it.
Args:
tree_cq (str): The component query to use to find the tree.
node_text_or_data (Union[str, dict]): The node text or data to find.
root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node.
Can be an immediate parent, or higher up in the tree.
"""
node = self.get_node_expander_element(tree_cq, node_text_or_data, root_node_text_or_data)
if node:
if root_node_text_or_data:
self._logger.info("Clicking expander on node '%s' (under root '%s') on tree with CQ '%s'", node_text_or_data, root_node_text_or_data, tree_cq)
else:
self._logger.info("Clicking expander on node '%s' on tree with CQ '%s'", node_text_or_data, tree_cq)
self._action_chains.move_to_element(node)
self._action_chains.click(node)
self._action_chains.perform()
else:
raise TreeHelper.NodeNotFoundException(tree_cq, node_text_or_data, root_node_text_or_data)
def click_node_element(self,
tree_cq: str,
node_text_or_data: Union[str, dict],
css_query: str,
root_node_text_or_data: Union[str, dict, None] = None):
"""Finds a node by text or data, then a child element by CSS query, then clicks on it.
Args:
tree_cq (str): The component query to use to find the tree.
node_text_or_data (Union[str, dict]): The node text or data to find.
css_query (str): The CSS to query for in the found node row element.
Some expected ones:
Expander UI element = '.x-tree-expander'
Node icon = '.x-tree-icon'
Node text = '.x-tree-node-text'
If need those you'd use one of the other methods though.
This is in case need to click on another part of the node's row.
root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node.
Can be an immediate parent, or higher up in the tree.
"""
node = self.get_node_element(tree_cq, node_text_or_data, css_query, root_node_text_or_data)
if node:
if root_node_text_or_data:
self._logger.info("Clicking on node '%s' (under root '%s'), with CSS query '%s' on tree with CQ '%s'", node_text_or_data, root_node_text_or_data, css_query, tree_cq)
else:
self._logger.info("Clicking on node '%s' with CSS query '%s' on tree with CQ '%s'", node_text_or_data, css_query, tree_cq)
self._action_chains.move_to_element(node)
self._action_chains.click(node)
self._action_chains.perform()
else:
raise TreeHelper.NodeNotFoundException(tree_cq, node_text_or_data, root_node_text_or_data)
def context_click_node_element(self,
tree_cq: str,
node_text_or_data: Union[str, dict],
css_query: str,
root_node_text_or_data: Union[str, dict, None] = None):
"""Finds a node by text or data, then a child element by CSS query, then right clicks on it.
Args:
tree_cq (str): The component query to use to find the tree.
node_text_or_data (Union[str, dict]): The node text or data to find.
css_query (str): The CSS to query for in the found node row element.
Some expected ones:
Expander UI element = '.x-tree-expander'
Node icon = '.x-tree-icon'
Node text = '.x-tree-node-text'
If need those you'd use one of the other methods though.
This is in case need to click on another part of the node's row.
root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node.
Can be an immediate parent, or higher up in the tree.
"""
node = self.get_node_element(tree_cq, node_text_or_data, css_query, root_node_text_or_data)
if node:
if root_node_text_or_data:
self._logger.info("Right clicking on node '%s' (under root '%s'), with CSS query '%s' on tree with CQ '%s'", node_text_or_data, root_node_text_or_data, css_query, tree_cq)
else:
self._logger.info("Right clicking on node '%s' with CSS query '%s' on tree with CQ '%s'", node_text_or_data, css_query, tree_cq)
self._action_chains.move_to_element(node)
self._action_chains.context_click(node)
self._action_chains.perform()
else:
raise TreeHelper.NodeNotFoundException(tree_cq, node_text_or_data, root_node_text_or_data)
def wait_for_tree_node(self,
tree_cq: str,
node_text_or_data: Union[str, dict],
parent_node_text_or_data: Union[str, dict],
timeout: float = 60) -> WebElement:
"""Method that waits until a tree node is available, refreshing the parent until it's
found or the timeout is hit.
Args:
tree_cq (str): The component query to use to find the tree.
node_text_or_data (Union[str, dict]): The node text or data to find.
parent_node_text_or_data (Union[str, dict]): The node text or data to use to find the nodes parent,
for refreshing purposes.
timeout (int, optional): The number of seconds to wait for the row before erroring. Defaults to 60.
Returns:
WebElement: The DOM element for the node icon.
"""
WebDriverWait(self._driver, timeout).until(TreeHelper.NodeFoundExpectation(tree_cq, node_text_or_data, parent_node_text_or_data))
return self.get_node_icon_element(tree_cq, node_text_or_data)
def reload_node(self,
tree_cq: str,
node_text_or_data: Union[str, dict],
root_node_text_or_data: Union[str, dict, None] = None):
"""Finds a node by text or data, and triggers a reload on it, and its children.
Args:
tree_cq (str): The component query to use to find the tree.
node_text_or_data (Union[str, dict]): The node text or data to find.
root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node.
Can be an immediate parent, or higher up in the tree.
"""
self.wait_until_tree_not_loading(tree_cq)
if isinstance(node_text_or_data, str):
node_text_or_data = f"'{node_text_or_data}'"
if isinstance(root_node_text_or_data, str):
root_node_text_or_data = f"'{root_node_text_or_data}'"
if root_node_text_or_data:
script = self._RELOAD_NODE_WITH_ROOT_TEMPLATE.format(tree_cq=tree_cq, node_text_or_data=node_text_or_data, root_node_text_or_data=root_node_text_or_data)
else:
script = self._RELOAD_NODE_TEMPLATE.format(tree_cq=tree_cq, node_text_or_data=node_text_or_data)
if root_node_text_or_data:
self._logger.info("Reloading node '%s' (under root '%s') on tree with CQ '%s'", node_text_or_data, root_node_text_or_data, tree_cq)
else:
self._logger.info("Reloading node '%s' on tree with CQ '%s'", node_text_or_data, tree_cq)
self.ensure_javascript_loaded()
self._driver.execute_script(script)
class NodeNotFoundException(Exception):
"""Exception class thrown when we failed to find the specified node"""
def __init__(self,
tree_cq: str,
node_text_or_data: Union[str, dict],
root_node_text_or_data: Union[str, dict, None] = None,
css_query: Union[str, None] = None):
"""Initialises an instance of this exception
Args:
tree_cq (str): The CQ used to find the tree
node_text_or_data (Union[str, dict]): The node text or data that we were looking for
root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node.
Can be an immediate parent, or higher up in the tree.
css_query (str, optional): The CSS that was queryed for in the node that we were looking for.
"""
if root_node_text_or_data:
if css_query:
self.message = "Failed to find node with data (or text) '{node_text_or_data}' (under root '{root_node_text_or_data}') with CSS query '{css_query}' on tree with CQ '{tree_cq}'."
else:
self.message = "Failed to find node with data (or text) '{node_text_or_data}' (under root '{root_node_text_or_data}') on tree with CQ '{tree_cq}'."
else:
if css_query:
self.message = "Failed to find node with data (or text) '{node_text_or_data}' with CSS query '{css_query}' on tree with CQ '{tree_cq}'."
else:
self.message = "Failed to find node with data (or text) '{node_text_or_data}' on tree with CQ '{tree_cq}'."
self._tree_cq = tree_cq
self._node_text_or_data = node_text_or_data
self._root_node_text_or_data = root_node_text_or_data
self._css_query = css_query
super().__init__(self.message)
def __str__(self):
"""Returns a string representation of this exception"""
if self._root_node_text_or_data:
return self.message.format(node_text_or_data=self._node_text_or_data, root_node_text_or_data=self._root_node_text_or_data, css_query=self._css_query, tree_cq=self._tree_cq)
else:
return self.message.format(node_text_or_data=self._node_text_or_data, css_query=self._css_query, tree_cq=self._tree_cq)
class TreeNotLoadingExpectation:
""" An expectation for checking that a tree is not loading."""
def __init__(self, tree_cq: str, recheck_time_if_false: Union[float, None] = None):
"""Initialises an instance of this class.
Args:
tree_cq (str): The CQ used to find the tree
recheck_time_if_false (float, optional): If we get a value of false (so there is not a call in progress),
this is the amount of time to wait to check again. Defaults to None.
"""
self._tree_cq = tree_cq
self._recheck_time_if_false = recheck_time_if_false
def __call__(self, driver):
"""Method that determines whether the tree is loading."""
tree_helper = TreeHelper(driver)
is_tree_loading = tree_helper.is_tree_loading(self._tree_cq)
if not is_tree_loading and self._recheck_time_if_false:
time.sleep(self._recheck_time_if_false)
is_tree_loading = tree_helper.is_tree_loading(self._tree_cq)
return not is_tree_loading
class NodeFoundExpectation:
""" An expectation for checking that a node has been found"""
def __init__(self,
tree_cq: str,
node_text_or_data: Union[str, dict],
parent_node_text_or_data: Union[str, dict]):
"""Initialises an instance of this class.
Args:
tree_cq (str): The component query to use to find the tree.
node_text_or_data (Union[str, dict]): The node text or data to find.
parent_node_text_or_data (Union[str, dict]): The node text or data to use to find the nodes parent,
for refreshing purposes.
"""
self._tree_cq = tree_cq
self._node_text_or_data = node_text_or_data
self._parent_node_text_or_data = parent_node_text_or_data
def __call__(self, driver):
"""Method that determines whether a node was found.
If the node is not found the parent tree node is refreshed and the load waited for.
"""
tree_helper = TreeHelper(driver)
node = tree_helper.get_node_icon_element(self._tree_cq, self._node_text_or_data, self._parent_node_text_or_data)
if node:
return True
# Trigger a reload of the parent
tree_helper.reload_node(self._tree_cq, self._parent_node_text_or_data)
return False
class UnexpectedMenuItemsFoundException(Exception):
"""Exception class thrown when we have found unexpected menu items in a context menu"""
def __init__(self,
tree_cq: str,
node_text_or_data: Union[str, dict],
unexpected_menu_items: list[str],
message: str = "Unexpected menu items {unexpected_menu_items} found on context menu, on tree with CQ '{tree_cq}' and node with text or data '{node_text_or_data}'."):
"""Initialises an instance of this exception
Args:
tree_cq (str): The component query to use to find the tree.
node_text_or_data (Union[str, dict]): The node text or data to find.
unexpected_menu_items (list[str]): A list of menu items that were unexpected.
message (str, optional): The exception message. Defaults to "Unexpected menu items {unexpected_menu_items} found on context menu, on tree with CQ '{tree_cq}' and node with text or data '{node_text_or_data}'.".
"""
self.message = message
self._tree_cq = tree_cq
self._node_text_or_data = node_text_or_data
self._unexpected_menu_items = unexpected_menu_items
super().__init__(self.message)
def __str__(self):
"""Returns a string representation of this exception"""
return self.message.format(unexpected_menu_items=self._unexpected_menu_items, tree_cq=self._tree_cq, node_text_or_data=self._node_text_or_data)
class ExpectedMenuItemsNotFoundException(Exception):
"""Exception class thrown when we have failed to find some expected menu items in a context menu"""
def __init__(self,
tree_cq: str,
node_text_or_data: Union[str, dict],
missing_menu_items: list[str],
message: str = "Expected menu items {missing_menu_items} not found on tree with CQ '{tree_cq}' and node with text or data '{node_text_or_data}'."):
"""Initialises an instance of this exception
Args:
tree_cq (str): The component query to use to find the tree.
node_text_or_data (Union[str, dict]): The node text or data to find.
missing_menu_items (list[str]): A list of menu items that were expected but not found.
message (str, optional): The exception message. Defaults to "Expected menu items {missing_menu_items} not found on tree with CQ '{tree_cq}' and node with text or data '{node_text_or_data}'.".
"""
self.message = message
self._tree_cq = tree_cq
self._node_text_or_data = node_text_or_data
self._missing_menu_items = missing_menu_items
super().__init__(self.message)
def __str__(self):
"""Returns a string representation of this exception"""
return self.message.format(missing_menu_items=self._missing_menu_items, tree_cq=self._tree_cq, node_text_or_data=self._node_text_or_data)
Classes
class TreeHelper (driver: selenium.webdriver.remote.webdriver.WebDriver)
-
A class to help with using trees, through Ext's interfaces.
Initialises an instance of this class
Args
driver
:WebDriver
- The webdriver to use
Expand source code
class TreeHelper(HasReferencedJavaScript): """A class to help with using trees, through Ext's interfaces.""" # Class variables _IS_TREE_LOADING_TEMPLATE: str = "return globalThis.PySeExt.TreeHelper.isTreeLoading('{tree_cq}')" """The script template to use to call the JavaScript method PySeExt.TreeHelper.isTreeLoading Requires the inserts: {tree_cq}""" _GET_NODE_ELEMENT_TEMPLATE: str = "return globalThis.PySeExt.TreeHelper.getNodeElement('{tree_cq}', {node_text_or_data}, '{css_query}')" """The script template to use to call the JavaScript method PySeExt.TreeHelper.getNodeElement Requires the inserts: {tree_cq}, {node_text_or_data}, {css_query}""" _GET_NODE_ELEMENT_WITH_ROOT_TEMPLATE: str = "return globalThis.PySeExt.TreeHelper.getNodeElement('{tree_cq}', {node_text_or_data}, '{css_query}', {root_node_text_or_data})" """The script template to use to call the JavaScript method PySeExt.TreeHelper.getNodeElement including a root node to search from Requires the inserts: {tree_cq}, {node_text_or_data}, {css_query}, {root_node_text_or_data}""" _RELOAD_NODE_TEMPLATE: str = "return globalThis.PySeExt.TreeHelper.reloadNode('{tree_cq}', {node_text_or_data})" """The script template to use to call the JavaScript method PySeExt.TreeHelper.reloadNode Requires the inserts: {tree_cq}, {node_text_or_data}""" _RELOAD_NODE_WITH_ROOT_TEMPLATE: str = "return globalThis.PySeExt.TreeHelper.reloadNode('{tree_cq}', {node_text_or_data}, {root_node_text_or_data})" """The script template to use to call the JavaScript method PySeExt.TreeHelper.reloadNode including a root node to search from Requires the inserts: {tree_cq}, {node_text_or_data}, {root_node_text_or_data}""" _ICON_CSS_SELECTOR: str = ".x-tree-icon" """The CSS selector to use with get_node_element to find the node icon element. """ _EXPANDER_CSS_SELECTOR: str = ".x-tree-expander" """The CSS selector to use with get_node_element to find the node expander element. """ _NODE_TEXT_CSS_SELECTOR: str = ".x-tree-node-text" """The CSS selector to use with get_node_element to find the node text element. """ def __init__(self, driver: WebDriver): """Initialises an instance of this class Args: driver (WebDriver): The webdriver to use """ self._logger = logging.getLogger(__name__) """The Logger instance for this class instance""" self._driver = driver """The WebDriver instance for this class instance""" self._action_chains = ActionChains(driver) """The ActionChains instance for this class instance""" self._core = Core(driver) """The `Core` instance for this class instance""" self._menu_helper = MenuHelper(driver) """The `MenuHelper` instance for this class instance""" # Initialise our base class super().__init__(driver, self._logger) def is_tree_loading(self, tree_cq: str): """Determine whether the tree (any part of it) is currently loading. You should call this before calling any tree interaction methods, since we cannot pass things back in callbacks! Args: tree_cq (str): The component query to use to find the tree. Returns: bool: True if the tree is loaded, False otherwise. """ script = self._IS_TREE_LOADING_TEMPLATE.format(tree_cq=tree_cq) self.ensure_javascript_loaded() return self._driver.execute_script(script) def wait_until_tree_not_loading(self, tree_cq: str, timeout: float = 30, poll_frequecy: float = 0.2, recheck_time_if_false: float = 0.2): """Waits until the tree identified by the component query is not loading, or the timeout is hit Args: tree_cq (str): The component query for the tree. timeout (float, optional): The number of seconds to wait before erroring. Defaults to 30. poll_frequency (float, optional): Number of seconds to poll. Defaults to 0.2. recheck_time_if_false (float, optional): If we get a result such that no Ajax calls are in progress, this is the amount of time to wait to check again. Defaults to 0.2. """ WebDriverWait(self._driver, timeout, poll_frequency = poll_frequecy).until(TreeHelper.TreeNotLoadingExpectation(tree_cq, recheck_time_if_false)) def get_node_icon_element(self, tree_cq: str, node_text_or_data: Union[str, dict], root_node_text_or_data: Union[str, dict, None] = None) -> WebElement: """Finds a node by text or data, then the child HTML element that holds it's icon. Args: tree_cq (str): The component query to use to find the tree. node_text_or_data (Union[str, dict]): The node text or data to find. root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree. Returns: WebElement: The DOM element for the node icon. """ return self.get_node_element(tree_cq, node_text_or_data, self._ICON_CSS_SELECTOR, root_node_text_or_data) def get_node_text_element(self, tree_cq: str, node_text_or_data: Union[str, dict], root_node_text_or_data: Union[str, dict, None] = None) -> WebElement: """Finds a node by text or data, then the child HTML element that holds it's text. Args: tree_cq (str): The component query to use to find the tree. node_text_or_data (Union[str, dict]): The node text or data to find. root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree. Returns: WebElement: The DOM element for the node text. """ return self.get_node_element(tree_cq, node_text_or_data, self._NODE_TEXT_CSS_SELECTOR, root_node_text_or_data) def get_node_expander_element(self, tree_cq: str, node_text_or_data: Union[str, dict], root_node_text_or_data: Union[str, dict, None] = None) -> WebElement: """Finds a node by text or data, then the child HTML element that holds it's expander UI element. Args: tree_cq (str): The component query to use to find the tree. node_text_or_data (Union[str, dict]): The node text or data to find. root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree. Returns: WebElement: The DOM element for the node's expander. """ return self.get_node_element(tree_cq, node_text_or_data, self._EXPANDER_CSS_SELECTOR, root_node_text_or_data) def get_node_element(self, tree_cq: str, node_text_or_data: Union[str, dict], css_query: str, root_node_text_or_data: Union[str, dict, None] = None) -> WebElement: """Finds a node by text or data, then a child element by CSS query. Args: tree_cq (str): The component query to use to find the tree. node_text_or_data (Union[str, dict]): The node text or data to find. css_query (str): The CSS to query for in the found node row element. Some expected ones: Expander UI element = '.x-tree-expander' Node icon = '.x-tree-icon' Node text = '.x-tree-node-text' If need those you'd use one of the other methods though. This is in case need to click on another part of the node's row. root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree. Returns: WebElement: The DOM element for the node's expander. """ self.wait_until_tree_not_loading(tree_cq) if isinstance(node_text_or_data, str): node_text_or_data = f"'{node_text_or_data}'" if isinstance(root_node_text_or_data, str): root_node_text_or_data = f"'{root_node_text_or_data}'" if root_node_text_or_data: script = self._GET_NODE_ELEMENT_WITH_ROOT_TEMPLATE.format(tree_cq=tree_cq, node_text_or_data=node_text_or_data, css_query=css_query, root_node_text_or_data=root_node_text_or_data) else: script = self._GET_NODE_ELEMENT_TEMPLATE.format(tree_cq=tree_cq, node_text_or_data=node_text_or_data, css_query=css_query) self.ensure_javascript_loaded() return self._driver.execute_script(script) def open_node_context_menu(self, tree_cq: str, node_text_or_data: Union[str, dict], root_node_text_or_data: Union[str, dict, None] = None): """Finds a node's icon element by text or data, then right clicks on it. Args: tree_cq (str): The component query to use to find the tree. node_text_or_data (Union[str, dict]): The node text or data to find. root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree. """ self.context_click_node_element(tree_cq, node_text_or_data, self._ICON_CSS_SELECTOR, root_node_text_or_data) def check_node_context_menu_contains_items(self, tree_cq: str, node_text_or_data: Union[str, dict], expected_items: list[str], check_contains_no_additional_items: bool = True, ignore_spacers: bool = False, root_node_text_or_data: Union[str, dict, None] = None): """Opens the context menu for a node, and checks that it contains the specified items. Args: tree_cq (str): The component query to use to find the tree. node_text_or_data (Union[str, dict]): The node text or data to find. expected_items (list[str]): A list of menu items (text) that we're expecting to find. Spacers have text containing of a single space (MenuHelper.SPACER_TEXT_CONTEXT), but can be ignored using ignore_spacers if desired. e.g. ['Add', 'Edit', 'Delete', MenuHelper.SPACER_TEXT_CONTEXT, 'Refresh'] check_contains_no_additional_items (bool, optional): Indicates whether we should check that there are no additional items in the menu. Defaults to True. ignore_spacers (bool, optional): Indicates whether we should include spacers in our checks. Defaults to False. root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree. """ self.open_node_context_menu(tree_cq, node_text_or_data, root_node_text_or_data) unexpected_items: list[str] = [] # Get all menu items menu_items = self._menu_helper.get_enabled_menu_items() # Each element has a text member we can compare. for item in menu_items: if ignore_spacers is True and item.text == MenuHelper.SPACER_TEXT_CONTEXT: continue if not item.text in expected_items: # Item not expected if check_contains_no_additional_items: unexpected_items.append(item.text) else: # Item expected and found, so remove from expected list. expected_items.remove(item.text) # If have nothing left to find, and we're not checking for unexpected items, then can break from our loop. if not check_contains_no_additional_items and len(expected_items) == 0: break # Is there anything left in our expected item list? if len(expected_items) > 0: raise TreeHelper.ExpectedMenuItemsNotFoundException(tree_cq, node_text_or_data, expected_items) # Was there anything in the menu that was not expected, and we were checking? if len(unexpected_items) > 0: raise TreeHelper.UnexpectedMenuItemsFoundException(tree_cq, node_text_or_data, unexpected_items) def click_node_expander(self, tree_cq: str, node_text_or_data: Union[str, dict], root_node_text_or_data: Union[str, dict, None] = None): """Finds a node's expander element by text or data, then clicks on it. Args: tree_cq (str): The component query to use to find the tree. node_text_or_data (Union[str, dict]): The node text or data to find. root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree. """ node = self.get_node_expander_element(tree_cq, node_text_or_data, root_node_text_or_data) if node: if root_node_text_or_data: self._logger.info("Clicking expander on node '%s' (under root '%s') on tree with CQ '%s'", node_text_or_data, root_node_text_or_data, tree_cq) else: self._logger.info("Clicking expander on node '%s' on tree with CQ '%s'", node_text_or_data, tree_cq) self._action_chains.move_to_element(node) self._action_chains.click(node) self._action_chains.perform() else: raise TreeHelper.NodeNotFoundException(tree_cq, node_text_or_data, root_node_text_or_data) def click_node_element(self, tree_cq: str, node_text_or_data: Union[str, dict], css_query: str, root_node_text_or_data: Union[str, dict, None] = None): """Finds a node by text or data, then a child element by CSS query, then clicks on it. Args: tree_cq (str): The component query to use to find the tree. node_text_or_data (Union[str, dict]): The node text or data to find. css_query (str): The CSS to query for in the found node row element. Some expected ones: Expander UI element = '.x-tree-expander' Node icon = '.x-tree-icon' Node text = '.x-tree-node-text' If need those you'd use one of the other methods though. This is in case need to click on another part of the node's row. root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree. """ node = self.get_node_element(tree_cq, node_text_or_data, css_query, root_node_text_or_data) if node: if root_node_text_or_data: self._logger.info("Clicking on node '%s' (under root '%s'), with CSS query '%s' on tree with CQ '%s'", node_text_or_data, root_node_text_or_data, css_query, tree_cq) else: self._logger.info("Clicking on node '%s' with CSS query '%s' on tree with CQ '%s'", node_text_or_data, css_query, tree_cq) self._action_chains.move_to_element(node) self._action_chains.click(node) self._action_chains.perform() else: raise TreeHelper.NodeNotFoundException(tree_cq, node_text_or_data, root_node_text_or_data) def context_click_node_element(self, tree_cq: str, node_text_or_data: Union[str, dict], css_query: str, root_node_text_or_data: Union[str, dict, None] = None): """Finds a node by text or data, then a child element by CSS query, then right clicks on it. Args: tree_cq (str): The component query to use to find the tree. node_text_or_data (Union[str, dict]): The node text or data to find. css_query (str): The CSS to query for in the found node row element. Some expected ones: Expander UI element = '.x-tree-expander' Node icon = '.x-tree-icon' Node text = '.x-tree-node-text' If need those you'd use one of the other methods though. This is in case need to click on another part of the node's row. root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree. """ node = self.get_node_element(tree_cq, node_text_or_data, css_query, root_node_text_or_data) if node: if root_node_text_or_data: self._logger.info("Right clicking on node '%s' (under root '%s'), with CSS query '%s' on tree with CQ '%s'", node_text_or_data, root_node_text_or_data, css_query, tree_cq) else: self._logger.info("Right clicking on node '%s' with CSS query '%s' on tree with CQ '%s'", node_text_or_data, css_query, tree_cq) self._action_chains.move_to_element(node) self._action_chains.context_click(node) self._action_chains.perform() else: raise TreeHelper.NodeNotFoundException(tree_cq, node_text_or_data, root_node_text_or_data) def wait_for_tree_node(self, tree_cq: str, node_text_or_data: Union[str, dict], parent_node_text_or_data: Union[str, dict], timeout: float = 60) -> WebElement: """Method that waits until a tree node is available, refreshing the parent until it's found or the timeout is hit. Args: tree_cq (str): The component query to use to find the tree. node_text_or_data (Union[str, dict]): The node text or data to find. parent_node_text_or_data (Union[str, dict]): The node text or data to use to find the nodes parent, for refreshing purposes. timeout (int, optional): The number of seconds to wait for the row before erroring. Defaults to 60. Returns: WebElement: The DOM element for the node icon. """ WebDriverWait(self._driver, timeout).until(TreeHelper.NodeFoundExpectation(tree_cq, node_text_or_data, parent_node_text_or_data)) return self.get_node_icon_element(tree_cq, node_text_or_data) def reload_node(self, tree_cq: str, node_text_or_data: Union[str, dict], root_node_text_or_data: Union[str, dict, None] = None): """Finds a node by text or data, and triggers a reload on it, and its children. Args: tree_cq (str): The component query to use to find the tree. node_text_or_data (Union[str, dict]): The node text or data to find. root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree. """ self.wait_until_tree_not_loading(tree_cq) if isinstance(node_text_or_data, str): node_text_or_data = f"'{node_text_or_data}'" if isinstance(root_node_text_or_data, str): root_node_text_or_data = f"'{root_node_text_or_data}'" if root_node_text_or_data: script = self._RELOAD_NODE_WITH_ROOT_TEMPLATE.format(tree_cq=tree_cq, node_text_or_data=node_text_or_data, root_node_text_or_data=root_node_text_or_data) else: script = self._RELOAD_NODE_TEMPLATE.format(tree_cq=tree_cq, node_text_or_data=node_text_or_data) if root_node_text_or_data: self._logger.info("Reloading node '%s' (under root '%s') on tree with CQ '%s'", node_text_or_data, root_node_text_or_data, tree_cq) else: self._logger.info("Reloading node '%s' on tree with CQ '%s'", node_text_or_data, tree_cq) self.ensure_javascript_loaded() self._driver.execute_script(script) class NodeNotFoundException(Exception): """Exception class thrown when we failed to find the specified node""" def __init__(self, tree_cq: str, node_text_or_data: Union[str, dict], root_node_text_or_data: Union[str, dict, None] = None, css_query: Union[str, None] = None): """Initialises an instance of this exception Args: tree_cq (str): The CQ used to find the tree node_text_or_data (Union[str, dict]): The node text or data that we were looking for root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree. css_query (str, optional): The CSS that was queryed for in the node that we were looking for. """ if root_node_text_or_data: if css_query: self.message = "Failed to find node with data (or text) '{node_text_or_data}' (under root '{root_node_text_or_data}') with CSS query '{css_query}' on tree with CQ '{tree_cq}'." else: self.message = "Failed to find node with data (or text) '{node_text_or_data}' (under root '{root_node_text_or_data}') on tree with CQ '{tree_cq}'." else: if css_query: self.message = "Failed to find node with data (or text) '{node_text_or_data}' with CSS query '{css_query}' on tree with CQ '{tree_cq}'." else: self.message = "Failed to find node with data (or text) '{node_text_or_data}' on tree with CQ '{tree_cq}'." self._tree_cq = tree_cq self._node_text_or_data = node_text_or_data self._root_node_text_or_data = root_node_text_or_data self._css_query = css_query super().__init__(self.message) def __str__(self): """Returns a string representation of this exception""" if self._root_node_text_or_data: return self.message.format(node_text_or_data=self._node_text_or_data, root_node_text_or_data=self._root_node_text_or_data, css_query=self._css_query, tree_cq=self._tree_cq) else: return self.message.format(node_text_or_data=self._node_text_or_data, css_query=self._css_query, tree_cq=self._tree_cq) class TreeNotLoadingExpectation: """ An expectation for checking that a tree is not loading.""" def __init__(self, tree_cq: str, recheck_time_if_false: Union[float, None] = None): """Initialises an instance of this class. Args: tree_cq (str): The CQ used to find the tree recheck_time_if_false (float, optional): If we get a value of false (so there is not a call in progress), this is the amount of time to wait to check again. Defaults to None. """ self._tree_cq = tree_cq self._recheck_time_if_false = recheck_time_if_false def __call__(self, driver): """Method that determines whether the tree is loading.""" tree_helper = TreeHelper(driver) is_tree_loading = tree_helper.is_tree_loading(self._tree_cq) if not is_tree_loading and self._recheck_time_if_false: time.sleep(self._recheck_time_if_false) is_tree_loading = tree_helper.is_tree_loading(self._tree_cq) return not is_tree_loading class NodeFoundExpectation: """ An expectation for checking that a node has been found""" def __init__(self, tree_cq: str, node_text_or_data: Union[str, dict], parent_node_text_or_data: Union[str, dict]): """Initialises an instance of this class. Args: tree_cq (str): The component query to use to find the tree. node_text_or_data (Union[str, dict]): The node text or data to find. parent_node_text_or_data (Union[str, dict]): The node text or data to use to find the nodes parent, for refreshing purposes. """ self._tree_cq = tree_cq self._node_text_or_data = node_text_or_data self._parent_node_text_or_data = parent_node_text_or_data def __call__(self, driver): """Method that determines whether a node was found. If the node is not found the parent tree node is refreshed and the load waited for. """ tree_helper = TreeHelper(driver) node = tree_helper.get_node_icon_element(self._tree_cq, self._node_text_or_data, self._parent_node_text_or_data) if node: return True # Trigger a reload of the parent tree_helper.reload_node(self._tree_cq, self._parent_node_text_or_data) return False class UnexpectedMenuItemsFoundException(Exception): """Exception class thrown when we have found unexpected menu items in a context menu""" def __init__(self, tree_cq: str, node_text_or_data: Union[str, dict], unexpected_menu_items: list[str], message: str = "Unexpected menu items {unexpected_menu_items} found on context menu, on tree with CQ '{tree_cq}' and node with text or data '{node_text_or_data}'."): """Initialises an instance of this exception Args: tree_cq (str): The component query to use to find the tree. node_text_or_data (Union[str, dict]): The node text or data to find. unexpected_menu_items (list[str]): A list of menu items that were unexpected. message (str, optional): The exception message. Defaults to "Unexpected menu items {unexpected_menu_items} found on context menu, on tree with CQ '{tree_cq}' and node with text or data '{node_text_or_data}'.". """ self.message = message self._tree_cq = tree_cq self._node_text_or_data = node_text_or_data self._unexpected_menu_items = unexpected_menu_items super().__init__(self.message) def __str__(self): """Returns a string representation of this exception""" return self.message.format(unexpected_menu_items=self._unexpected_menu_items, tree_cq=self._tree_cq, node_text_or_data=self._node_text_or_data) class ExpectedMenuItemsNotFoundException(Exception): """Exception class thrown when we have failed to find some expected menu items in a context menu""" def __init__(self, tree_cq: str, node_text_or_data: Union[str, dict], missing_menu_items: list[str], message: str = "Expected menu items {missing_menu_items} not found on tree with CQ '{tree_cq}' and node with text or data '{node_text_or_data}'."): """Initialises an instance of this exception Args: tree_cq (str): The component query to use to find the tree. node_text_or_data (Union[str, dict]): The node text or data to find. missing_menu_items (list[str]): A list of menu items that were expected but not found. message (str, optional): The exception message. Defaults to "Expected menu items {missing_menu_items} not found on tree with CQ '{tree_cq}' and node with text or data '{node_text_or_data}'.". """ self.message = message self._tree_cq = tree_cq self._node_text_or_data = node_text_or_data self._missing_menu_items = missing_menu_items super().__init__(self.message) def __str__(self): """Returns a string representation of this exception""" return self.message.format(missing_menu_items=self._missing_menu_items, tree_cq=self._tree_cq, node_text_or_data=self._node_text_or_data)
Ancestors
Class variables
var ExpectedMenuItemsNotFoundException
-
Exception class thrown when we have failed to find some expected menu items in a context menu
var NodeFoundExpectation
-
An expectation for checking that a node has been found
var NodeNotFoundException
-
Exception class thrown when we failed to find the specified node
var TreeNotLoadingExpectation
-
An expectation for checking that a tree is not loading.
var UnexpectedMenuItemsFoundException
-
Exception class thrown when we have found unexpected menu items in a context menu
Methods
-
Opens the context menu for a node, and checks that it contains the specified items.
Args
tree_cq
:str
- The component query to use to find the tree.
node_text_or_data
:Union[str, dict]
- The node text or data to find.
expected_items
:list[str]
- A list of menu items (text) that we're expecting to find. Spacers have text containing of a single space (MenuHelper.SPACER_TEXT_CONTEXT), but can be ignored using ignore_spacers if desired. e.g. ['Add', 'Edit', 'Delete', MenuHelper.SPACER_TEXT_CONTEXT, 'Refresh']
check_contains_no_additional_items
:bool
, optional- Indicates whether we should check that there are no additional items in the menu. Defaults to True.
ignore_spacers
:bool
, optional- Indicates whether we should include spacers in our checks. Defaults to False.
root_node_text_or_data
:Union[str, dict]
, optional- The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree.
Expand source code
def check_node_context_menu_contains_items(self, tree_cq: str, node_text_or_data: Union[str, dict], expected_items: list[str], check_contains_no_additional_items: bool = True, ignore_spacers: bool = False, root_node_text_or_data: Union[str, dict, None] = None): """Opens the context menu for a node, and checks that it contains the specified items. Args: tree_cq (str): The component query to use to find the tree. node_text_or_data (Union[str, dict]): The node text or data to find. expected_items (list[str]): A list of menu items (text) that we're expecting to find. Spacers have text containing of a single space (MenuHelper.SPACER_TEXT_CONTEXT), but can be ignored using ignore_spacers if desired. e.g. ['Add', 'Edit', 'Delete', MenuHelper.SPACER_TEXT_CONTEXT, 'Refresh'] check_contains_no_additional_items (bool, optional): Indicates whether we should check that there are no additional items in the menu. Defaults to True. ignore_spacers (bool, optional): Indicates whether we should include spacers in our checks. Defaults to False. root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree. """ self.open_node_context_menu(tree_cq, node_text_or_data, root_node_text_or_data) unexpected_items: list[str] = [] # Get all menu items menu_items = self._menu_helper.get_enabled_menu_items() # Each element has a text member we can compare. for item in menu_items: if ignore_spacers is True and item.text == MenuHelper.SPACER_TEXT_CONTEXT: continue if not item.text in expected_items: # Item not expected if check_contains_no_additional_items: unexpected_items.append(item.text) else: # Item expected and found, so remove from expected list. expected_items.remove(item.text) # If have nothing left to find, and we're not checking for unexpected items, then can break from our loop. if not check_contains_no_additional_items and len(expected_items) == 0: break # Is there anything left in our expected item list? if len(expected_items) > 0: raise TreeHelper.ExpectedMenuItemsNotFoundException(tree_cq, node_text_or_data, expected_items) # Was there anything in the menu that was not expected, and we were checking? if len(unexpected_items) > 0: raise TreeHelper.UnexpectedMenuItemsFoundException(tree_cq, node_text_or_data, unexpected_items)
def click_node_element(self, tree_cq: str, node_text_or_data: Union[str, dict], css_query: str, root_node_text_or_data: Union[str, dict, ForwardRef(None)] = None)
-
Finds a node by text or data, then a child element by CSS query, then clicks on it.
Args
tree_cq
:str
- The component query to use to find the tree.
node_text_or_data
:Union[str, dict]
- The node text or data to find.
css_query
:str
- The CSS to query for in the found node row element. Some expected ones: Expander UI element = '.x-tree-expander' Node icon = '.x-tree-icon' Node text = '.x-tree-node-text' If need those you'd use one of the other methods though. This is in case need to click on another part of the node's row.
root_node_text_or_data
:Union[str, dict]
, optional- The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree.
Expand source code
def click_node_element(self, tree_cq: str, node_text_or_data: Union[str, dict], css_query: str, root_node_text_or_data: Union[str, dict, None] = None): """Finds a node by text or data, then a child element by CSS query, then clicks on it. Args: tree_cq (str): The component query to use to find the tree. node_text_or_data (Union[str, dict]): The node text or data to find. css_query (str): The CSS to query for in the found node row element. Some expected ones: Expander UI element = '.x-tree-expander' Node icon = '.x-tree-icon' Node text = '.x-tree-node-text' If need those you'd use one of the other methods though. This is in case need to click on another part of the node's row. root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree. """ node = self.get_node_element(tree_cq, node_text_or_data, css_query, root_node_text_or_data) if node: if root_node_text_or_data: self._logger.info("Clicking on node '%s' (under root '%s'), with CSS query '%s' on tree with CQ '%s'", node_text_or_data, root_node_text_or_data, css_query, tree_cq) else: self._logger.info("Clicking on node '%s' with CSS query '%s' on tree with CQ '%s'", node_text_or_data, css_query, tree_cq) self._action_chains.move_to_element(node) self._action_chains.click(node) self._action_chains.perform() else: raise TreeHelper.NodeNotFoundException(tree_cq, node_text_or_data, root_node_text_or_data)
def click_node_expander(self, tree_cq: str, node_text_or_data: Union[str, dict], root_node_text_or_data: Union[str, dict, ForwardRef(None)] = None)
-
Finds a node's expander element by text or data, then clicks on it.
Args
tree_cq
:str
- The component query to use to find the tree.
node_text_or_data
:Union[str, dict]
- The node text or data to find.
root_node_text_or_data
:Union[str, dict]
, optional- The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree.
Expand source code
def click_node_expander(self, tree_cq: str, node_text_or_data: Union[str, dict], root_node_text_or_data: Union[str, dict, None] = None): """Finds a node's expander element by text or data, then clicks on it. Args: tree_cq (str): The component query to use to find the tree. node_text_or_data (Union[str, dict]): The node text or data to find. root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree. """ node = self.get_node_expander_element(tree_cq, node_text_or_data, root_node_text_or_data) if node: if root_node_text_or_data: self._logger.info("Clicking expander on node '%s' (under root '%s') on tree with CQ '%s'", node_text_or_data, root_node_text_or_data, tree_cq) else: self._logger.info("Clicking expander on node '%s' on tree with CQ '%s'", node_text_or_data, tree_cq) self._action_chains.move_to_element(node) self._action_chains.click(node) self._action_chains.perform() else: raise TreeHelper.NodeNotFoundException(tree_cq, node_text_or_data, root_node_text_or_data)
def context_click_node_element(self, tree_cq: str, node_text_or_data: Union[str, dict], css_query: str, root_node_text_or_data: Union[str, dict, ForwardRef(None)] = None)
-
Finds a node by text or data, then a child element by CSS query, then right clicks on it.
Args
tree_cq
:str
- The component query to use to find the tree.
node_text_or_data
:Union[str, dict]
- The node text or data to find.
css_query
:str
- The CSS to query for in the found node row element. Some expected ones: Expander UI element = '.x-tree-expander' Node icon = '.x-tree-icon' Node text = '.x-tree-node-text' If need those you'd use one of the other methods though. This is in case need to click on another part of the node's row.
root_node_text_or_data
:Union[str, dict]
, optional- The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree.
Expand source code
def context_click_node_element(self, tree_cq: str, node_text_or_data: Union[str, dict], css_query: str, root_node_text_or_data: Union[str, dict, None] = None): """Finds a node by text or data, then a child element by CSS query, then right clicks on it. Args: tree_cq (str): The component query to use to find the tree. node_text_or_data (Union[str, dict]): The node text or data to find. css_query (str): The CSS to query for in the found node row element. Some expected ones: Expander UI element = '.x-tree-expander' Node icon = '.x-tree-icon' Node text = '.x-tree-node-text' If need those you'd use one of the other methods though. This is in case need to click on another part of the node's row. root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree. """ node = self.get_node_element(tree_cq, node_text_or_data, css_query, root_node_text_or_data) if node: if root_node_text_or_data: self._logger.info("Right clicking on node '%s' (under root '%s'), with CSS query '%s' on tree with CQ '%s'", node_text_or_data, root_node_text_or_data, css_query, tree_cq) else: self._logger.info("Right clicking on node '%s' with CSS query '%s' on tree with CQ '%s'", node_text_or_data, css_query, tree_cq) self._action_chains.move_to_element(node) self._action_chains.context_click(node) self._action_chains.perform() else: raise TreeHelper.NodeNotFoundException(tree_cq, node_text_or_data, root_node_text_or_data)
def get_node_element(self, tree_cq: str, node_text_or_data: Union[str, dict], css_query: str, root_node_text_or_data: Union[str, dict, ForwardRef(None)] = None) ‑> selenium.webdriver.remote.webelement.WebElement
-
Finds a node by text or data, then a child element by CSS query.
Args
tree_cq
:str
- The component query to use to find the tree.
node_text_or_data
:Union[str, dict]
- The node text or data to find.
css_query
:str
- The CSS to query for in the found node row element. Some expected ones: Expander UI element = '.x-tree-expander' Node icon = '.x-tree-icon' Node text = '.x-tree-node-text' If need those you'd use one of the other methods though. This is in case need to click on another part of the node's row.
root_node_text_or_data
:Union[str, dict]
, optional- The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree.
Returns
WebElement
- The DOM element for the node's expander.
Expand source code
def get_node_element(self, tree_cq: str, node_text_or_data: Union[str, dict], css_query: str, root_node_text_or_data: Union[str, dict, None] = None) -> WebElement: """Finds a node by text or data, then a child element by CSS query. Args: tree_cq (str): The component query to use to find the tree. node_text_or_data (Union[str, dict]): The node text or data to find. css_query (str): The CSS to query for in the found node row element. Some expected ones: Expander UI element = '.x-tree-expander' Node icon = '.x-tree-icon' Node text = '.x-tree-node-text' If need those you'd use one of the other methods though. This is in case need to click on another part of the node's row. root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree. Returns: WebElement: The DOM element for the node's expander. """ self.wait_until_tree_not_loading(tree_cq) if isinstance(node_text_or_data, str): node_text_or_data = f"'{node_text_or_data}'" if isinstance(root_node_text_or_data, str): root_node_text_or_data = f"'{root_node_text_or_data}'" if root_node_text_or_data: script = self._GET_NODE_ELEMENT_WITH_ROOT_TEMPLATE.format(tree_cq=tree_cq, node_text_or_data=node_text_or_data, css_query=css_query, root_node_text_or_data=root_node_text_or_data) else: script = self._GET_NODE_ELEMENT_TEMPLATE.format(tree_cq=tree_cq, node_text_or_data=node_text_or_data, css_query=css_query) self.ensure_javascript_loaded() return self._driver.execute_script(script)
def get_node_expander_element(self, tree_cq: str, node_text_or_data: Union[str, dict], root_node_text_or_data: Union[str, dict, ForwardRef(None)] = None) ‑> selenium.webdriver.remote.webelement.WebElement
-
Finds a node by text or data, then the child HTML element that holds it's expander UI element.
Args
tree_cq
:str
- The component query to use to find the tree.
node_text_or_data
:Union[str, dict]
- The node text or data to find.
root_node_text_or_data
:Union[str, dict]
, optional- The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree.
Returns
WebElement
- The DOM element for the node's expander.
Expand source code
def get_node_expander_element(self, tree_cq: str, node_text_or_data: Union[str, dict], root_node_text_or_data: Union[str, dict, None] = None) -> WebElement: """Finds a node by text or data, then the child HTML element that holds it's expander UI element. Args: tree_cq (str): The component query to use to find the tree. node_text_or_data (Union[str, dict]): The node text or data to find. root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree. Returns: WebElement: The DOM element for the node's expander. """ return self.get_node_element(tree_cq, node_text_or_data, self._EXPANDER_CSS_SELECTOR, root_node_text_or_data)
def get_node_icon_element(self, tree_cq: str, node_text_or_data: Union[str, dict], root_node_text_or_data: Union[str, dict, ForwardRef(None)] = None) ‑> selenium.webdriver.remote.webelement.WebElement
-
Finds a node by text or data, then the child HTML element that holds it's icon.
Args
tree_cq
:str
- The component query to use to find the tree.
node_text_or_data
:Union[str, dict]
- The node text or data to find.
root_node_text_or_data
:Union[str, dict]
, optional- The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree.
Returns
WebElement
- The DOM element for the node icon.
Expand source code
def get_node_icon_element(self, tree_cq: str, node_text_or_data: Union[str, dict], root_node_text_or_data: Union[str, dict, None] = None) -> WebElement: """Finds a node by text or data, then the child HTML element that holds it's icon. Args: tree_cq (str): The component query to use to find the tree. node_text_or_data (Union[str, dict]): The node text or data to find. root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree. Returns: WebElement: The DOM element for the node icon. """ return self.get_node_element(tree_cq, node_text_or_data, self._ICON_CSS_SELECTOR, root_node_text_or_data)
def get_node_text_element(self, tree_cq: str, node_text_or_data: Union[str, dict], root_node_text_or_data: Union[str, dict, ForwardRef(None)] = None) ‑> selenium.webdriver.remote.webelement.WebElement
-
Finds a node by text or data, then the child HTML element that holds it's text.
Args
tree_cq
:str
- The component query to use to find the tree.
node_text_or_data
:Union[str, dict]
- The node text or data to find.
root_node_text_or_data
:Union[str, dict]
, optional- The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree.
Returns
WebElement
- The DOM element for the node text.
Expand source code
def get_node_text_element(self, tree_cq: str, node_text_or_data: Union[str, dict], root_node_text_or_data: Union[str, dict, None] = None) -> WebElement: """Finds a node by text or data, then the child HTML element that holds it's text. Args: tree_cq (str): The component query to use to find the tree. node_text_or_data (Union[str, dict]): The node text or data to find. root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree. Returns: WebElement: The DOM element for the node text. """ return self.get_node_element(tree_cq, node_text_or_data, self._NODE_TEXT_CSS_SELECTOR, root_node_text_or_data)
def is_tree_loading(self, tree_cq: str)
-
Determine whether the tree (any part of it) is currently loading.
You should call this before calling any tree interaction methods, since we cannot pass things back in callbacks!
Args
tree_cq
:str
- The component query to use to find the tree.
Returns
bool
- True if the tree is loaded, False otherwise.
Expand source code
def is_tree_loading(self, tree_cq: str): """Determine whether the tree (any part of it) is currently loading. You should call this before calling any tree interaction methods, since we cannot pass things back in callbacks! Args: tree_cq (str): The component query to use to find the tree. Returns: bool: True if the tree is loaded, False otherwise. """ script = self._IS_TREE_LOADING_TEMPLATE.format(tree_cq=tree_cq) self.ensure_javascript_loaded() return self._driver.execute_script(script)
-
Finds a node's icon element by text or data, then right clicks on it.
Args
tree_cq
:str
- The component query to use to find the tree.
node_text_or_data
:Union[str, dict]
- The node text or data to find.
root_node_text_or_data
:Union[str, dict]
, optional- The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree.
Expand source code
def open_node_context_menu(self, tree_cq: str, node_text_or_data: Union[str, dict], root_node_text_or_data: Union[str, dict, None] = None): """Finds a node's icon element by text or data, then right clicks on it. Args: tree_cq (str): The component query to use to find the tree. node_text_or_data (Union[str, dict]): The node text or data to find. root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree. """ self.context_click_node_element(tree_cq, node_text_or_data, self._ICON_CSS_SELECTOR, root_node_text_or_data)
def reload_node(self, tree_cq: str, node_text_or_data: Union[str, dict], root_node_text_or_data: Union[str, dict, ForwardRef(None)] = None)
-
Finds a node by text or data, and triggers a reload on it, and its children.
Args
tree_cq
:str
- The component query to use to find the tree.
node_text_or_data
:Union[str, dict]
- The node text or data to find.
root_node_text_or_data
:Union[str, dict]
, optional- The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree.
Expand source code
def reload_node(self, tree_cq: str, node_text_or_data: Union[str, dict], root_node_text_or_data: Union[str, dict, None] = None): """Finds a node by text or data, and triggers a reload on it, and its children. Args: tree_cq (str): The component query to use to find the tree. node_text_or_data (Union[str, dict]): The node text or data to find. root_node_text_or_data (Union[str, dict], optional): The text or data indicating the root node under which to search for the node. Can be an immediate parent, or higher up in the tree. """ self.wait_until_tree_not_loading(tree_cq) if isinstance(node_text_or_data, str): node_text_or_data = f"'{node_text_or_data}'" if isinstance(root_node_text_or_data, str): root_node_text_or_data = f"'{root_node_text_or_data}'" if root_node_text_or_data: script = self._RELOAD_NODE_WITH_ROOT_TEMPLATE.format(tree_cq=tree_cq, node_text_or_data=node_text_or_data, root_node_text_or_data=root_node_text_or_data) else: script = self._RELOAD_NODE_TEMPLATE.format(tree_cq=tree_cq, node_text_or_data=node_text_or_data) if root_node_text_or_data: self._logger.info("Reloading node '%s' (under root '%s') on tree with CQ '%s'", node_text_or_data, root_node_text_or_data, tree_cq) else: self._logger.info("Reloading node '%s' on tree with CQ '%s'", node_text_or_data, tree_cq) self.ensure_javascript_loaded() self._driver.execute_script(script)
def wait_for_tree_node(self, tree_cq: str, node_text_or_data: Union[str, dict], parent_node_text_or_data: Union[str, dict], timeout: float = 60) ‑> selenium.webdriver.remote.webelement.WebElement
-
Method that waits until a tree node is available, refreshing the parent until it's found or the timeout is hit.
Args
tree_cq
:str
- The component query to use to find the tree.
node_text_or_data
:Union[str, dict]
- The node text or data to find.
parent_node_text_or_data
:Union[str, dict]
- The node text or data to use to find the nodes parent, for refreshing purposes.
timeout
:int
, optional- The number of seconds to wait for the row before erroring. Defaults to 60.
Returns
WebElement
- The DOM element for the node icon.
Expand source code
def wait_for_tree_node(self, tree_cq: str, node_text_or_data: Union[str, dict], parent_node_text_or_data: Union[str, dict], timeout: float = 60) -> WebElement: """Method that waits until a tree node is available, refreshing the parent until it's found or the timeout is hit. Args: tree_cq (str): The component query to use to find the tree. node_text_or_data (Union[str, dict]): The node text or data to find. parent_node_text_or_data (Union[str, dict]): The node text or data to use to find the nodes parent, for refreshing purposes. timeout (int, optional): The number of seconds to wait for the row before erroring. Defaults to 60. Returns: WebElement: The DOM element for the node icon. """ WebDriverWait(self._driver, timeout).until(TreeHelper.NodeFoundExpectation(tree_cq, node_text_or_data, parent_node_text_or_data)) return self.get_node_icon_element(tree_cq, node_text_or_data)
def wait_until_tree_not_loading(self, tree_cq: str, timeout: float = 30, poll_frequecy: float = 0.2, recheck_time_if_false: float = 0.2)
-
Waits until the tree identified by the component query is not loading, or the timeout is hit
Args
tree_cq
:str
- The component query for the tree.
timeout
:float
, optional- The number of seconds to wait before erroring. Defaults to 30.
poll_frequency
:float
, optional- Number of seconds to poll. Defaults to 0.2.
recheck_time_if_false
:float
, optional- If we get a result such that no Ajax calls are in progress, this is the amount of time to wait to check again. Defaults to 0.2.
Expand source code
def wait_until_tree_not_loading(self, tree_cq: str, timeout: float = 30, poll_frequecy: float = 0.2, recheck_time_if_false: float = 0.2): """Waits until the tree identified by the component query is not loading, or the timeout is hit Args: tree_cq (str): The component query for the tree. timeout (float, optional): The number of seconds to wait before erroring. Defaults to 30. poll_frequency (float, optional): Number of seconds to poll. Defaults to 0.2. recheck_time_if_false (float, optional): If we get a result such that no Ajax calls are in progress, this is the amount of time to wait to check again. Defaults to 0.2. """ WebDriverWait(self._driver, timeout, poll_frequency = poll_frequecy).until(TreeHelper.TreeNotLoadingExpectation(tree_cq, recheck_time_if_false))
Inherited members