Skip to content

Commit 4ecd631

Browse files
Merge pull request #7 from dmberezovskyii/PPA-0004
Ppa 0004
2 parents aaee24d + ad1d8e1 commit 4ecd631

File tree

11 files changed

+241
-141
lines changed

11 files changed

+241
-141
lines changed

config/settings.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ stage:
99
autoGrantPermissions: True
1010
appWaitForLaunch: True
1111
maxRetryCount: 40
12-
noReset: True
12+
noReset: False
1313
appWaitDuration: 30000
1414
appPackage: "io.appium.android.apis"
1515
appActivity: "io.appium.android.apis.ApiDemos"

conftest.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,10 @@ def driver(request):
6262
except Exception as e:
6363
pytest.fail(f"Failed to initialize driver: {e}")
6464

65-
yield event_driver
65+
yield driver
6666

67-
if event_driver is not None:
68-
event_driver.quit()
67+
if driver is not None:
68+
driver.quit()
6969

7070

7171
# def pytest_runtest_makereport(item, call):

src/drivers/event_listener.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,4 @@ def after_quit(self, driver):
2929
logger.info("Driver has quit.")
3030

3131
def on_exception(self, exception, driver) -> None:
32-
logger.info(f"On exception")
32+
logger.info(f"On exception")

src/locators/__init__.py

Whitespace-only changes.

src/locators/locators.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from appium.webdriver.common.appiumby import AppiumBy
2+
3+
4+
class Common:
5+
text_link = (AppiumBy.ACCESSIBILITY_ID, 'Text')
6+
content_link = (AppiumBy.ACCESSIBILITY_ID, 'Content')
7+
menu_elements = (AppiumBy.XPATH, '//android.widget.TextView')

src/screens/base_screen.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import time
2-
from typing import Tuple
2+
from typing import Tuple, Literal
33

44
from screens.element_interactor import ElementInteractor
5+
from appium.webdriver.extensions.action_helpers import ActionHelpers, ActionChains
6+
57

68
Locator = Tuple[str, str]
79

@@ -10,12 +12,20 @@ class Screen(ElementInteractor):
1012
def __init__(self, driver):
1113
super().__init__(driver)
1214

13-
def click(self):
14-
pass
15+
def click(
16+
self,
17+
locator: Locator,
18+
condition: Literal["clickable", "visible", "present"] = "clickable",
19+
):
20+
element = self.element(locator, condition=condition)
21+
element.click()
22+
23+
def tap(self, locator, **kwargs):
24+
element = self.element(locator, condition="clickable", **kwargs)
25+
self.driver.tap()
26+
action_helpers = ActionHelpers()
27+
action_helpers.tap(element)
1528

16-
def tap(self):
17-
pass
18-
1929
def tap_by_coordinates(self):
2030
pass
2131

@@ -40,7 +50,7 @@ def sleep(kwargs):
4050

4151
def get_screen_size(self):
4252
return self.driver.get_window_size()
43-
53+
4454
def back(self):
4555
self.driver.back()
4656

@@ -51,4 +61,4 @@ def reset(self):
5161
self.driver.reset()
5262

5363
def launch_app(self):
54-
self.driver.launch_app()
64+
self.driver.launch_app()

src/screens/element_interactor.py

Lines changed: 46 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,34 @@
1-
import time
21
from enum import Enum
32
from typing import Tuple, Optional, Literal, List
3+
import time
44
from selenium.webdriver.remote.webelement import WebElement
5-
from selenium.webdriver.support import expected_conditions as ec
5+
from selenium.webdriver.support import expected_conditions as EC
66
from selenium.webdriver.support.wait import WebDriverWait
7-
from selenium.common.exceptions import (
8-
TimeoutException,
9-
ElementNotVisibleException,
10-
NoSuchElementException,
11-
)
7+
from selenium.common.exceptions import TimeoutException, NoSuchElementException
128

139
Locator = Tuple[str, str]
1410

1511

1612
class WaitType(Enum):
17-
"""
18-
Enumeration for different wait durations used in WebDriverWait.
19-
"""
20-
2113
DEFAULT = 30
2214
SHORT = 5
2315
LONG = 60
2416
FLUENT = 10
2517

2618

2719
class ElementInteractor:
28-
"""
29-
A utility class for interacting with screen elements, waits strategy.
30-
"""
31-
3220
def __init__(self, driver):
33-
"""
34-
Initializes the ElementInteractor with a WebDriver instance and predefined waiters.
35-
36-
:param driver: The Selenium WebDriver instance to interact with.
37-
:type driver: WebDriver
38-
"""
3921
self.driver = driver
4022
self.waiters = {
41-
WaitType.DEFAULT: WebDriverWait(driver, WaitType.DEFAULT.value),
42-
WaitType.SHORT: WebDriverWait(driver, WaitType.SHORT.value),
43-
WaitType.LONG: WebDriverWait(driver, WaitType.LONG.value),
44-
WaitType.FLUENT: WebDriverWait(
45-
driver,
46-
WaitType.FLUENT.value,
47-
poll_frequency=1,
48-
ignored_exceptions=[ElementNotVisibleException],
49-
),
23+
wait_type: WebDriverWait(driver, wait_type.value)
24+
for wait_type in WaitType
25+
if wait_type != WaitType.FLUENT
5026
}
27+
self.waiters[WaitType.FLUENT] = WebDriverWait(
28+
driver, WaitType.FLUENT.value, poll_frequency=1
29+
)
5130

5231
def _get_waiter(self, wait_type: Optional[WaitType] = None) -> WebDriverWait:
53-
"""
54-
Returns the appropriate WebDriverWait instance based on the specified wait type.
55-
56-
:param wait_type: The type of wait (default is `WaitType.DEFAULT`).
57-
:type wait_type: Optional[WaitType]
58-
59-
:return: The WebDriverWait instance for the specified wait type.
60-
:rtype: WebDriverWait
61-
"""
6232
return self.waiters.get(wait_type, self.waiters[WaitType.DEFAULT])
6333

6434
def wait_for(
@@ -67,50 +37,47 @@ def wait_for(
6737
condition: Literal["clickable", "visible", "present"] = "visible",
6838
waiter: Optional[WebDriverWait] = None,
6939
) -> WebElement:
70-
"""
71-
Waits for an element to meet the specified condition.
72-
73-
:param locator: A tuple containing the strategy and value of the element locator.
74-
:param condition: The condition to wait for ("clickable", "visible", or "present").
75-
:param waiter: A custom WebDriverWait instance. Defaults to `None`, which uses the default waiter.
76-
77-
:return: The located web element once the condition is satisfied.
78-
"""
7940
waiter = waiter or self._get_waiter()
8041
conditions = {
81-
"clickable": ec.element_to_be_clickable(locator),
82-
"visible": ec.visibility_of_element_located(locator),
83-
"present": ec.presence_of_element_located(locator),
42+
"clickable": EC.element_to_be_clickable(locator),
43+
"visible": EC.visibility_of_element_located(locator),
44+
"present": EC.presence_of_element_located(locator),
8445
}
85-
8646
if condition not in conditions:
8747
raise ValueError(f"Unknown condition: {condition}")
88-
8948
try:
9049
return waiter.until(conditions[condition])
9150
except TimeoutException as e:
9251
raise TimeoutException(
93-
f"Condition '{condition}' failed for element {locator} "
94-
f"after {waiter._timeout} seconds"
52+
f"Condition '{condition}' failed for element {locator} after {waiter._timeout} seconds"
9553
) from e
9654

55+
def element(
56+
self,
57+
locator: Locator,
58+
n: int = 3,
59+
condition: Literal["clickable", "visible", "present"] = "visible",
60+
wait_type: Optional[WaitType] = WaitType.DEFAULT,
61+
):
62+
for attempt in range(1, n + 1):
63+
try:
64+
self.wait_for(
65+
locator, condition=condition, waiter=self._get_waiter(wait_type)
66+
)
67+
return self.driver.find_element(*locator)
68+
except NoSuchElementException:
69+
if attempt == n:
70+
raise NoSuchElementException(
71+
f"Could not locate element with value: {locator}"
72+
)
73+
9774
def elements(
9875
self,
9976
locator: Locator,
10077
n: int = 3,
10178
condition: Literal["clickable", "visible", "present"] = "visible",
10279
wait_type: Optional[WaitType] = WaitType.DEFAULT,
10380
) -> List[WebElement]:
104-
"""
105-
Attempts to locate a list of elements by polling a maximum of 'n' times.
106-
107-
:param locator: A tuple containing the strategy and value of the element locator.
108-
:param n: The maximum number of attempts to find the elements. Default is 3.
109-
:param condition: The condition to wait for ("clickable", "visible", or "present").
110-
:param wait_type: The wait type to use for polling. Defaults to `WaitType.DEFAULT`.
111-
112-
:return: A list of located web elements that match the condition.
113-
"""
11481
for attempt in range(1, n + 1):
11582
try:
11683
self.wait_for(
@@ -122,35 +89,6 @@ def elements(
12289
raise NoSuchElementException(
12390
f"Could not locate element list with value: {locator}"
12491
)
125-
except Exception:
126-
if attempt == n:
127-
raise
128-
129-
def _assert_element_displayed(self, element: WebElement, expected: bool) -> None:
130-
"""
131-
Asserts that the element's displayed status matches the expected value.
132-
133-
:param element: The web element to check.
134-
:param expected: The expected visibility status of the element (True or False).
135-
136-
:raises AssertionError: If the element's visibility does not match the expected value.
137-
"""
138-
assert element.is_displayed() == expected
139-
140-
def _check_elements_displayed(
141-
self, elements: List[WebElement], expected: bool, index: Optional[int] = None
142-
) -> bool:
143-
"""
144-
Checks if the elements are displayed and if applicable, checks a specific element by index.
145-
146-
:param elements: The list of web elements to check.
147-
:param expected: The expected visibility status of the elements (True or False).
148-
:param index: The index of the specific element to check. If `None`, all elements are checked.
149-
:return: True if the element(s) are displayed with the expected status, otherwise False.
150-
"""
151-
if index is None:
152-
return all(e.is_displayed() == expected for e in elements)
153-
return elements[index].is_displayed() == expected
15492

15593
def is_displayed(
15694
self,
@@ -160,15 +98,6 @@ def is_displayed(
16098
condition: Literal["clickable", "visible", "present"] = "visible",
16199
wait_type: Optional[WaitType] = None,
162100
) -> None:
163-
"""Checks for an element to be displayed or not, and asserts the visibility.
164-
165-
:param locator: A tuple containing the strategy and value of the element locator.
166-
:param expected: The expected visibility status of the element (True or False).
167-
:param n: The maximum number of attempts to check visibility. Defaults to 3.
168-
:param condition: The condition to wait for ("clickable", "visible", or "present").
169-
:param wait_type: The wait type to use for polling. Defaults to `WaitType.DEFAULT`.
170-
171-
:raises AssertionError: If the element's visibility does not match the expected value"""
172101
wait_type = wait_type or WaitType.DEFAULT
173102
for _ in range(n):
174103
try:
@@ -179,7 +108,12 @@ def is_displayed(
179108
return
180109
except Exception:
181110
time.sleep(0.5)
182-
assert False == expected
111+
if expected: # Assert if the element is expected to be displayed but isn't
112+
raise AssertionError(f"Element {locator} was not displayed as expected.")
113+
else: # Assert if the element should not be displayed but is
114+
raise AssertionError(
115+
f"Element {locator} was displayed when it shouldn't be."
116+
)
183117

184118
def is_exist(
185119
self,
@@ -188,30 +122,17 @@ def is_exist(
188122
n: int = 3,
189123
condition: Literal["clickable", "visible", "present"] = "visible",
190124
wait_type: Optional[WaitType] = WaitType.DEFAULT,
191-
**kwargs,
192125
) -> bool:
193-
"""
194-
Checks for an element's existence and checks if it meets the expected visibility status.
195-
196-
:param locator: A tuple containing the strategy and value of the element locator.
197-
:param expected: The expected existence status of the element (True or False).
198-
:param n: The maximum number of attempts to check existence. Defaults to 3.
199-
:param condition: The condition to wait for ("clickable", "visible", or "present").
200-
:param wait_type: The wait type to use for polling. Defaults to `WaitType.DEFAULT`.
201-
:param **kwargs: Additional keyword arguments, such as `index` for checking a specific element in a list.
202-
203-
:return: `True` if the element(s) exist and match the expected visibility status, otherwise `False`.
204-
:rtype: bool
205-
"""
206126
for _ in range(n):
207127
try:
208-
elements = self.wait_for(
209-
locator, condition=condition, waiter=self._get_waiter(wait_type)
128+
element = self.element(
129+
locator, n=1, condition=condition, wait_type=wait_type
210130
)
211-
if isinstance(elements, list) and self._check_elements_displayed(
212-
elements, expected, kwargs.get("index")
213-
):
131+
return element.is_displayed() == expected
132+
except NoSuchElementException:
133+
if not expected:
214134
return True
215135
except Exception:
216-
if _ == n - 1:
217-
return False
136+
pass
137+
time.sleep(0.5)
138+
return not expected

src/screens/main/__init__.py

Whitespace-only changes.

src/screens/main/main_screen.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from locators.locators import Common
2+
from screens.base_screen import Screen
3+
4+
5+
class MainScreen(Screen):
6+
7+
def __init__(self, driver):
8+
super().__init__(driver)
9+
10+
def click_on_text_link(self):
11+
self.click(locator = Common.text_link)

0 commit comments

Comments
 (0)