Skip to content

Commit 6b7bfe3

Browse files
author
dmy.berezovskyi
committed
added element_interactor.py
1 parent 2006735 commit 6b7bfe3

2 files changed

Lines changed: 277 additions & 2 deletions

File tree

src/screens/base_screen.py

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,54 @@
1-
class Screen:
2-
pass
1+
import time
2+
from typing import Tuple
3+
4+
from screens.element_interactor import ElementInteractor
5+
6+
Locator = Tuple[str, str]
7+
8+
9+
class Screen(ElementInteractor):
10+
def __init__(self, driver):
11+
super().__init__(driver)
12+
13+
def click(self):
14+
pass
15+
16+
def tap(self):
17+
pass
18+
19+
def tap_by_coordinates(self):
20+
pass
21+
22+
def swipe(self):
23+
pass
24+
25+
def type(self):
26+
pass
27+
28+
def double_tap(self):
29+
pass
30+
31+
def long_press(self):
32+
pass
33+
34+
@staticmethod
35+
def sleep(kwargs):
36+
try:
37+
time.sleep(kwargs["sleep"])
38+
except KeyError:
39+
pass
40+
41+
def get_screen_size(self):
42+
return self.driver.get_window_size()
43+
44+
def back(self):
45+
self.driver.back()
46+
47+
def close(self):
48+
self.driver.close_app()
49+
50+
def reset(self):
51+
self.driver.reset()
52+
53+
def launch_app(self):
54+
self.driver.launch_app()

src/screens/element_interactor.py

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
import time
2+
from enum import Enum
3+
from typing import Tuple, Optional, Literal, List
4+
from selenium.webdriver.remote.webelement import WebElement
5+
from selenium.webdriver.support import expected_conditions as ec
6+
from selenium.webdriver.support.wait import WebDriverWait
7+
from selenium.common.exceptions import (
8+
TimeoutException,
9+
ElementNotVisibleException,
10+
NoSuchElementException,
11+
)
12+
13+
Locator = Tuple[str, str]
14+
15+
16+
class WaitType(Enum):
17+
"""
18+
Enumeration for different wait durations used in WebDriverWait.
19+
"""
20+
21+
DEFAULT = 30
22+
SHORT = 5
23+
LONG = 60
24+
FLUENT = 10
25+
26+
27+
class ElementInteractor:
28+
"""
29+
A utility class for interacting with web elements and handling waits.
30+
31+
This class provides various methods for waiting for elements, checking their visibility,
32+
existence
33+
"""
34+
35+
def __init__(self, driver):
36+
"""
37+
Initializes the ElementInteractor with a WebDriver instance and predefined waiters.
38+
39+
:param driver: The Selenium WebDriver instance to interact with.
40+
:type driver: WebDriver
41+
"""
42+
self.driver = driver
43+
self.waiters = {
44+
WaitType.DEFAULT: WebDriverWait(driver, WaitType.DEFAULT.value),
45+
WaitType.SHORT: WebDriverWait(driver, WaitType.SHORT.value),
46+
WaitType.LONG: WebDriverWait(driver, WaitType.LONG.value),
47+
WaitType.FLUENT: WebDriverWait(
48+
driver,
49+
WaitType.FLUENT.value,
50+
poll_frequency=1,
51+
ignored_exceptions=[ElementNotVisibleException],
52+
),
53+
}
54+
55+
def _get_waiter(self, wait_type: Optional[WaitType] = None) -> WebDriverWait:
56+
"""
57+
Returns the appropriate WebDriverWait instance based on the specified wait type.
58+
59+
:param wait_type: The type of wait (default is `WaitType.DEFAULT`).
60+
:type wait_type: Optional[WaitType]
61+
62+
:return: The WebDriverWait instance for the specified wait type.
63+
:rtype: WebDriverWait
64+
"""
65+
return self.waiters.get(wait_type, self.waiters[WaitType.DEFAULT])
66+
67+
def wait_for(
68+
self,
69+
locator: Locator,
70+
condition: Literal["clickable", "visible", "present"] = "visible",
71+
waiter: Optional[WebDriverWait] = None,
72+
) -> WebElement:
73+
"""
74+
Waits for an element to meet the specified condition.
75+
76+
:param locator: A tuple containing the strategy and value of the element locator.
77+
:param condition: The condition to wait for ("clickable", "visible", or "present").
78+
:param waiter: A custom WebDriverWait instance. Defaults to `None`, which uses the default waiter.
79+
80+
:return: The located web element once the condition is satisfied.
81+
"""
82+
waiter = waiter or self._get_waiter()
83+
conditions = {
84+
"clickable": ec.element_to_be_clickable(locator),
85+
"visible": ec.visibility_of_element_located(locator),
86+
"present": ec.presence_of_element_located(locator),
87+
}
88+
89+
if condition not in conditions:
90+
raise ValueError(f"Unknown condition: {condition}")
91+
92+
try:
93+
return waiter.until(conditions[condition])
94+
except TimeoutException as e:
95+
raise TimeoutException(
96+
f"Condition '{condition}' failed for element {locator} "
97+
f"after {waiter._timeout} seconds"
98+
) from e
99+
100+
def elements(
101+
self,
102+
locator: Locator,
103+
n: int = 3,
104+
condition: Literal["clickable", "visible", "present"] = "visible",
105+
wait_type: Optional[WaitType] = WaitType.DEFAULT,
106+
) -> List[WebElement]:
107+
"""
108+
Attempts to locate a list of elements by polling a maximum of 'n' times.
109+
110+
:param locator: A tuple containing the strategy and value of the element locator.
111+
:param n: The maximum number of attempts to find the elements. Default is 3.
112+
:param condition: The condition to wait for ("clickable", "visible", or "present").
113+
:param wait_type: The wait type to use for polling. Defaults to `WaitType.DEFAULT`.
114+
115+
:return: A list of located web elements that match the condition.
116+
"""
117+
for attempt in range(1, n + 1):
118+
try:
119+
self.wait_for(
120+
locator, condition=condition, waiter=self._get_waiter(wait_type)
121+
)
122+
return self.driver.find_elements(*locator)
123+
except NoSuchElementException:
124+
if attempt == n:
125+
raise NoSuchElementException(
126+
f"Could not locate element list with value: {locator}"
127+
)
128+
except Exception:
129+
if attempt == n:
130+
raise
131+
132+
def _assert_element_displayed(self, element: WebElement, expected: bool) -> None:
133+
"""
134+
Asserts that the element's displayed status matches the expected value.
135+
136+
:param element: The web element to check.
137+
:param expected: The expected visibility status of the element (True or False).
138+
139+
:raises AssertionError: If the element's visibility does not match the expected value.
140+
"""
141+
assert element.is_displayed() == expected
142+
143+
def _check_elements_displayed(
144+
self, elements: List[WebElement], expected: bool, index: Optional[int] = None
145+
) -> bool:
146+
"""
147+
Checks if the elements are displayed and if applicable, checks a specific element by index.
148+
149+
:param elements: The list of web elements to check.
150+
:param expected: The expected visibility status of the elements (True or False).
151+
:param index: The index of the specific element to check. If `None`, all elements are checked.
152+
:return: True if the element(s) are displayed with the expected status, otherwise False.
153+
"""
154+
if index is None:
155+
return all(e.is_displayed() == expected for e in elements)
156+
return elements[index].is_displayed() == expected
157+
158+
def is_displayed(
159+
self,
160+
locator: Locator,
161+
expected: bool = True,
162+
n: int = 3,
163+
condition: Literal["clickable", "visible", "present"] = "visible",
164+
wait_type: Optional[WaitType] = WaitType.DEFAULT,
165+
**kwargs,
166+
) -> None:
167+
"""
168+
Polls for an element to be displayed or not, and asserts the visibility.
169+
170+
:param locator: A tuple containing the strategy and value of the element locator.
171+
:param expected: The expected visibility status of the element (True or False).
172+
:param n: The maximum number of attempts to check visibility. Defaults to 3.
173+
:param condition: The condition to wait for ("clickable", "visible", or "present").
174+
:param wait_type: The wait type to use for polling. Defaults to `WaitType.DEFAULT`.
175+
176+
:raises AssertionError: If the element's visibility does not match the expected value after polling.
177+
"""
178+
for _ in range(n):
179+
try:
180+
element = self.wait_for(
181+
locator, condition=condition, waiter=self._get_waiter(wait_type)
182+
)
183+
self._assert_element_displayed(element, expected)
184+
break
185+
except Exception:
186+
time.sleep(0.5)
187+
if _ == n - 1:
188+
assert False == expected
189+
190+
def is_exist(
191+
self,
192+
locator: Locator,
193+
expected: bool = True,
194+
n: int = 3,
195+
condition: Literal["clickable", "visible", "present"] = "visible",
196+
wait_type: Optional[WaitType] = WaitType.DEFAULT,
197+
**kwargs,
198+
) -> bool:
199+
"""
200+
Polls for an element's existence and checks if it meets the expected visibility status.
201+
202+
:param locator: A tuple containing the strategy and value of the element locator.
203+
:param expected: The expected existence status of the element (True or False).
204+
:param n: The maximum number of attempts to check existence. Defaults to 3.
205+
:param condition: The condition to wait for ("clickable", "visible", or "present").
206+
:param wait_type: The wait type to use for polling. Defaults to `WaitType.DEFAULT`.
207+
:param **kwargs: Additional keyword arguments, such as `index` for checking a specific element in a list.
208+
209+
:return: `True` if the element(s) exist and match the expected visibility status, otherwise `False`.
210+
:rtype: bool
211+
"""
212+
for _ in range(n):
213+
try:
214+
elements = self.wait_for(
215+
locator, condition=condition, waiter=self._get_waiter(wait_type)
216+
)
217+
if isinstance(elements, list) and self._check_elements_displayed(
218+
elements, expected, kwargs.get("index")
219+
):
220+
return True
221+
except Exception:
222+
if _ == n - 1:
223+
return False

0 commit comments

Comments
 (0)