Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
407 changes: 297 additions & 110 deletions Components/BasicComponent.py

Large diffs are not rendered by default.

22 changes: 11 additions & 11 deletions Components/Common/Button.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from selenium.webdriver.remote.webdriver import WebDriver

from Components.BasicComponent import ComponentPiece
from Components.PerspectiveComponents.Common.Icon import CommonIcon
from Components.Common.Icon import CommonIcon
from Helpers.CSSEnumerations import CSSPropertyValue
from Helpers.Point import Point

Expand All @@ -24,17 +24,17 @@ class CommonButton(ComponentPiece):

def __init__(
self,
locator: Tuple[By, str],
locator: Tuple[Union[By, str], str],
driver: WebDriver,
parent_locator_list: Optional[List[Tuple[By, str]]] = None,
wait_timeout: float = 10,
parent_locator_list: Optional[List[Tuple[Union[By, str], str]]] = None,
timeout: float = 10,
description: Optional[str] = None,
poll_freq: float = 0.5):
super().__init__(
locator=locator,
driver=driver,
parent_locator_list=parent_locator_list,
wait_timeout=wait_timeout,
timeout=timeout,
description=description,
poll_freq=poll_freq)
self._internal_button = ComponentPiece(
Expand All @@ -47,33 +47,33 @@ def __init__(
locator=self._INTERNAL_ICON_LOCATOR,
driver=driver,
parent_locator_list=self.locator_list,
wait_timeout=1,
timeout=1,
description="The icon in use by the Button.",
poll_freq=poll_freq)
self._internal_text = ComponentPiece(
locator=self._INTERNAL_TEXT_LOCATOR,
driver=driver,
parent_locator_list=self.locator_list,
wait_timeout=1,
timeout=1,
description="The container in which the text of the Button resides.",
poll_freq=poll_freq)
self._internal_image = ComponentPiece(
locator=self._INTERNAL_IMAGE_LOCATOR,
driver=driver,
parent_locator_list=self.locator_list,
wait_timeout=1,
timeout=1,
description="The image within the Button.",
poll_freq=poll_freq)

def click(self, wait_timeout=None, binding_wait_time: float = 0.5) -> None:
def click(self, timeout=None, wait_after_click: float = 0.5) -> None:
"""
Click the button.
"""
_button = self._get_html_button()
if _button == self:
super().click(wait_timeout=wait_timeout, binding_wait_time=binding_wait_time)
super().click(timeout=timeout, wait_after_click=wait_after_click)
else:
_button.click(wait_timeout=wait_timeout, binding_wait_time=binding_wait_time)
_button.click(timeout=timeout, wait_after_click=wait_after_click)

def get_css_property_of_icon(self, property_name: Union[CSSPropertyValue, str]) -> str:
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import json
from enum import Enum
from json import JSONDecodeError
from typing import List, Optional, Tuple
from typing import List, Optional, Tuple, Union

from selenium.common.exceptions import TimeoutException, ElementNotInteractableException
from selenium.webdriver.common.by import By
Expand Down Expand Up @@ -35,17 +35,17 @@ class CommonDropdown(ComponentPiece):

def __init__(
self,
locator: Tuple[By, str],
locator: Tuple[Union[By, str], str],
driver: WebDriver,
parent_locator_list: Optional[List] = None,
wait_timeout: float = 2,
timeout: float = 2,
description: Optional[str] = None,
poll_freq: float = 0.5):
super().__init__(
locator=locator,
driver=driver,
parent_locator_list=parent_locator_list,
wait_timeout=wait_timeout,
timeout=timeout,
description=description,
poll_freq=poll_freq)
self._expand_icon = ComponentPiece(
Expand Down Expand Up @@ -73,7 +73,7 @@ def collapse(self) -> None:
Collapse the dropdown if it is already expanded. No action is taken if the dropdown is currently collapsed.
"""
if self.is_expanded():
self.wait_on_binding(0.5)
self.wait_some_time(0.5)
self._click_expansion_icon()
IAAssert.is_true(
value=self.wait_for_expansion_state(state_to_wait_on=ExpansionState.COLLAPSED),
Expand Down Expand Up @@ -111,7 +111,7 @@ def get_available_options(self) -> List[str]:
needs_to_collapse = not self.is_expanded()
self.expand()
try:
options = [option.text for option in self._available_options.find_all(wait_timeout=0.5)]
options = [option.text for option in self._available_options.find_all(timeout=0.5)]
except TimeoutException:
options = []
if needs_to_collapse:
Expand Down Expand Up @@ -178,7 +178,7 @@ def is_expanded(self) -> bool:
:returns: True, if the Dropdown is currently expanded.
"""
return (self.ACTIVE_CLASS in self.find().get_attribute("class")) \
and (self._options_container.find(wait_timeout=0) is not None)
and (self._options_container.find(timeout=0) is not None)

def option_is_disabled(self, option_text: str) -> bool:
"""
Expand Down Expand Up @@ -224,13 +224,13 @@ def option_is_visible_within_option_modal(self, option_text: str) -> bool:
bottom_within_modal = option_termination.Y <= modal_termination.Y
return visible and left_within_modal and top_within_modal and right_within_modal and bottom_within_modal

def select_option_by_text_if_not_selected(self, option_text: str, binding_wait_time: float = 0.5) -> None:
def select_option_by_text_if_not_selected(self, option_text: str, wait_after_click: float = 0.5) -> None:
"""
Select an option from the Dropdown if it is not already selected. No action is taken if the supplied option is
already selected.

:param option_text: The exact text of the option to select.
:param binding_wait_time: The amount of time (in seconds) to wait after selecting the option before allowing
:param wait_after_click: The amount of time (in seconds) to wait after selecting the option before allowing
code to continue. Ignored if the option is already selected.

:raises TimeoutException: If the specified option is not present.
Expand All @@ -240,7 +240,7 @@ def select_option_by_text_if_not_selected(self, option_text: str, binding_wait_t
self.scroll_to_element()
self.expand()
try:
self._get_option(option_text=option_text).click(wait_timeout=1, binding_wait_time=binding_wait_time)
self._get_option(option_text=option_text).click(timeout=1, wait_after_click=wait_after_click)
except TimeoutException as toe:
raise TimeoutException(msg=f"Failed to locate element with text of \"{option_text}\".") from toe
except ElementNotInteractableException as enie:
Expand All @@ -251,21 +251,21 @@ def select_option_by_text_if_not_selected(self, option_text: str, binding_wait_t
f"Container dimensions: {termination.X - origin.X} x {termination.Y - origin.Y}") from enie
assert option_text in self.get_selected_options_as_list(), f"Failed to select option: '{option_text}'."

def wait_for_expansion_state(self, state_to_wait_on: ExpansionState, wait_timeout: float = 1) -> bool:
def wait_for_expansion_state(self, state_to_wait_on: ExpansionState, timeout: float = 1) -> bool:
"""
Wait for the Dropdown to take on an expected state, before eventually returning a value which reflects whether
the Dropdown took that state.

:param state_to_wait_on: Either expanded or collapsed.
:param wait_timeout: The amount of time (in seconds) to potentially wait for the Dropdown to take on the
:param timeout: The amount of time (in seconds) to potentially wait for the Dropdown to take on the
supplied state.

:return: True, if the Dropdown eventually took the supplied state - False otherwise.
"""
exp_func = IAec.function_returns_true if state_to_wait_on == ExpansionState.EXPANDED \
else IAec.function_returns_false
try:
WebDriverWait(driver=self.driver, timeout=wait_timeout).until(
WebDriverWait(driver=self.driver, timeout=timeout).until(
exp_func(custom_function=self.is_expanded, function_args={}))
return True
except TimeoutException:
Expand All @@ -280,7 +280,7 @@ def _click_expansion_icon(self) -> None:
expected_final_state = ExpansionState.COLLAPSED if self.is_expanded() else ExpansionState.EXPANDED
self._expand_icon.click()
IAAssert.is_true(
value=self.wait_for_expansion_state(state_to_wait_on=expected_final_state, wait_timeout=1),
value=self.wait_for_expansion_state(state_to_wait_on=expected_final_state, timeout=1),
failure_msg="Failed to modify the expansion state of the Dropdown."
)

Expand Down
17 changes: 9 additions & 8 deletions Components/Common/FileUpload.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from typing import List, Optional, Tuple
from typing import List, Optional, Tuple, Union

from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webdriver import WebDriver

from Components.BasicComponent import BasicComponent, ComponentPiece
from Helpers.Formatting import FilePathFormatting


class FileUpload(BasicComponent):
Expand All @@ -15,17 +16,17 @@ class FileUpload(BasicComponent):

def __init__(
self,
locator: Tuple[By, str],
locator: Tuple[Union[By, str], str],
driver: WebDriver,
parent_locator_list: Optional[List[Tuple[By, str]]] = None,
wait_timeout: float = 10,
parent_locator_list: Optional[List[Tuple[Union[By, str], str]]] = None,
timeout: float = 10,
description: Optional[str] = None,
poll_freq: float = 0.5):
super().__init__(
locator=locator,
driver=driver,
parent_locator_list=parent_locator_list,
wait_timeout=wait_timeout,
timeout=timeout,
description=description,
poll_freq=poll_freq)
self._button = ComponentPiece(
Expand Down Expand Up @@ -60,10 +61,10 @@ def is_enabled(self) -> bool:
"""
return "disabled" not in self._button.find().get_attribute("class")

def upload_file_by_path(self, normalized_file_path: str) -> None:
def upload_file_by_path(self, file_path: str) -> None:
"""
Supply the location of a file to be uploaded.

:param normalized_file_path: The normalized (OS-agnostic) path to the file to be uploaded.
:param file_path: The path to the file to be uploaded.
"""
self._input.find().send_keys(normalized_file_path)
self._input.find().send_keys(FilePathFormatting.system_safe_file_path(file_path))
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Tuple, Optional, List
from typing import Tuple, Optional, List, Union

from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
Expand All @@ -9,7 +9,7 @@


class CommonIcon(ComponentPiece):
"""An Icon which could be used anywhere within Perspective, including within other components."""
"""An Icon which could be used anywhere - including within other components."""
# This list will grow over time, until Dev takes this on as a unit test for their Icon strip-and-zip
_KNOWN_EXTRANEOUS_D_ATTRIBUTE_LIST = [
'M24 24H0V0h24v24z',
Expand All @@ -19,17 +19,17 @@ class CommonIcon(ComponentPiece):

def __init__(
self,
locator: Tuple[By, str],
locator: Tuple[Union[By, str], str],
driver: WebDriver,
parent_locator_list: Optional[List[Tuple[By, str]]] = None,
wait_timeout: float = 2,
parent_locator_list: Optional[List[Tuple[Union[By, str], str]]] = None,
timeout: float = 2,
description: Optional[str] = None,
poll_freq: float = 0.5):
super().__init__(
locator=locator,
driver=driver,
parent_locator_list=parent_locator_list,
wait_timeout=wait_timeout,
timeout=timeout,
description=description,
poll_freq=poll_freq)
self._children = ComponentPiece(
Expand Down Expand Up @@ -60,7 +60,7 @@ def get_fill_color(self) -> str:
"""
local_fill = self.get_css_property(property_name=CSS.FILL)
try:
for child in self._children.find_all(wait_timeout=1):
for child in self._children.find_all(timeout=1):
child_fill = child.value_of_css_property("fill") # WebElement (WE), so have to use WE function
if child_fill != local_fill and child_fill != 'none': # ignore 'none' because we have 0 control over it
return child_fill
Expand Down Expand Up @@ -96,7 +96,7 @@ def get_stroke_color(self) -> str:
"""
local_stroke = self.get_css_property(property_name=CSS.STROKE)
try:
for child in self._children.find_all(wait_timeout=1):
for child in self._children.find_all(timeout=1):
child_stroke = child.value_of_css_property("stroke") # WebElement (WE), so have to use WE function
if child_stroke != local_stroke:
return child_stroke
Expand Down Expand Up @@ -128,7 +128,7 @@ def has_extraneous_border(self) -> bool:
"""
for bad_border in self._bad_borders:
try:
return bad_border.find(wait_timeout=0) is not None
return bad_border.find(timeout=0) is not None
except TimeoutException:
pass
return False
Expand All @@ -140,6 +140,6 @@ def is_rendered(self) -> bool:
:returns: True, if rendered - False otherwise.
"""
try:
return self._internal_g.find(wait_timeout=0.5) is not None
return self._internal_g.find(timeout=0.5) is not None
except TimeoutException:
return False
30 changes: 17 additions & 13 deletions Components/Common/TextInput.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,26 @@ class CommonTextInput(ComponentPiece):

def __init__(
self,
locator: Tuple[By, str],
locator: Tuple[Union[By, str], str],
driver: WebDriver,
parent_locator_list: Optional[List[Tuple[By, str]]] = None,
wait_timeout: float = 2,
parent_locator_list: Optional[List[Tuple[Union[By, str], str]]] = None,
timeout: float = 2,
description: Optional[str] = None,
poll_freq: float = 0.5):
super().__init__(
ComponentPiece.__init__(
self,
locator=locator,
driver=driver,
parent_locator_list=parent_locator_list,
wait_timeout=wait_timeout,
timeout=timeout,
description=description,
poll_freq=poll_freq)
# The internal input piece is important for when our component has a quality overlay because any reference
# made using something like an id would now refer to the <div> level instead.
self._internal_input = ComponentPiece(
locator=self._INTERNAL_INPUT_LOCATOR,
driver=self.driver,
wait_timeout=0,
timeout=0,
parent_locator_list=self.locator_list,
description="The internal <input> element, used primarily for Text Fields, or when the component contains "
"a quality overlay.",
Expand Down Expand Up @@ -70,22 +71,23 @@ def set_text(
self,
text: Union[float, str],
release_focus: bool = True,
binding_wait_time: float = 0) -> None:
wait_after: float = 0) -> None:
"""
Set the value of this component.

:param text: The text to type into the field.
:param release_focus: Dictates whether a blur() event is invoked for the component after typing the supplied
text.
:param binding_wait_time: The amount of time (in seconds) to wait after typing the supplied text before allowing
:param wait_after: The amount of time (in seconds) to wait after typing the supplied text before allowing
code to continue.
"""
text = str(text)
# strip special characters (ESC, ENTER) while leaving spaces and punctuation
expected_text = text.encode("ascii", "ignore").decode()
# strip special characters (ESC, ENTER) while leaving spaces and punctuation. '\n' and '\t are handled
# as a special cases due to the potential need in markdown components.
expected_text = ''.join(c for c in text if c.isprintable() or c in ['\n', '\t'])
if text is None or text == '':
text = ' ' + Keys.BACKSPACE
self.wait_on_text_condition(text_to_compare="", condition=TextCondition.DOES_NOT_EQUAL, wait_timeout=0.5)
self.wait_on_text_condition(text_to_compare="", condition=TextCondition.DOES_NOT_EQUAL, timeout=0.5)
current_text = self.get_text() # Do NOT wait on matching text to be in place
keys_to_send = ''.join([Keys.ARROW_RIGHT for _ in current_text] +
[Keys.BACKSPACE for _ in current_text] +
Expand All @@ -102,15 +104,17 @@ def set_text(
.move_to_element_with_offset(to_element=input_object.find(), xoffset=5, yoffset=5) \
.click() \
.perform()
if self.driver.name == 'firefox' and Keys.ESCAPE not in keys_to_send:
input_object.find().clear()
input_object.find().send_keys(keys_to_send)
if release_focus:
self.release_focus()
IAAssert.is_equal_to(
actual_value=self.wait_on_text_condition(
text_to_compare=expected_text, condition=TextCondition.EQUALS, wait_timeout=binding_wait_time + 0.5),
text_to_compare=expected_text, condition=TextCondition.EQUALS, timeout=wait_after + 0.5),
expected_value=expected_text,
failure_msg="Failed to set the value of the input.")
self.wait_on_binding(time_to_wait=binding_wait_time)
self.wait_some_time(time_to_wait=wait_after)

def _needs_to_get_input_element(self) -> bool:
"""
Expand Down
Loading