Skip to content
Merged
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
16 changes: 12 additions & 4 deletions src/locators/locators.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
from appium.webdriver.common.appiumby import AppiumBy


class Common:
text_link = (AppiumBy.ACCESSIBILITY_ID, 'Text')
content_link = (AppiumBy.ACCESSIBILITY_ID, 'Content')
menu_elements = (AppiumBy.XPATH, '//android.widget.TextView')
class Locators:

Check notice on line 4 in src/locators/locators.py

View workflow job for this annotation

GitHub Actions / Qodana Community for Python

Class has no `__init__` method

Class has no __init__ method
class main_menu:

Check notice on line 5 in src/locators/locators.py

View workflow job for this annotation

GitHub Actions / Qodana Community for Python

Class has no `__init__` method

Class has no __init__ method

Check notice on line 5 in src/locators/locators.py

View workflow job for this annotation

GitHub Actions / Qodana Community for Python

PEP 8 naming convention violation

Class names should use CapWords convention
TEXT_LINK = (AppiumBy.ACCESSIBILITY_ID, 'Text')
CONTENT_LINK = (AppiumBy.ACCESSIBILITY_ID, 'Content')
VIEWS_LINK = (AppiumBy.ACCESSIBILITY_ID, 'Views')
MENU_ELEMENTS = (AppiumBy.XPATH, '//android.widget.TextView')

class views_menu:

Check notice on line 11 in src/locators/locators.py

View workflow job for this annotation

GitHub Actions / Qodana Community for Python

Class has no `__init__` method

Class has no __init__ method

Check notice on line 11 in src/locators/locators.py

View workflow job for this annotation

GitHub Actions / Qodana Community for Python

PEP 8 naming convention violation

Class names should use CapWords convention
TEXT_FIELDS = (AppiumBy.ACCESSIBILITY_ID, 'TextFields')

class views_fields:

Check notice on line 14 in src/locators/locators.py

View workflow job for this annotation

GitHub Actions / Qodana Community for Python

Class has no `__init__` method

Class has no __init__ method

Check notice on line 14 in src/locators/locators.py

View workflow job for this annotation

GitHub Actions / Qodana Community for Python

PEP 8 naming convention violation

Class names should use CapWords convention
HINT_INPUT = (AppiumBy.ACCESSIBILITY_ID, 'hint')
55 changes: 48 additions & 7 deletions src/screens/base_screen.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import time
from typing import Tuple, Literal
from typing import Tuple, Literal, Optional

from selenium.webdriver.common.actions import interaction
from selenium.webdriver.common.actions.action_builder import ActionBuilder
from selenium.webdriver.common.actions.pointer_actions import PointerActions
from selenium.webdriver.common.actions.pointer_input import PointerInput

from screens.element_interactor import ElementInteractor
from appium.webdriver.extensions.action_helpers import ActionHelpers, ActionChains
Expand All @@ -18,20 +20,25 @@
def click(
self,
locator: Locator,
condition: Literal["clickable", "visible", "present"] = "clickable",

Check warning on line 23 in src/screens/base_screen.py

View workflow job for this annotation

GitHub Actions / Qodana Community for Python

Invalid type hints definitions and usages

'Literal' may be parameterized with literal ints, byte and unicode strings, bools, Enum values, None, other literal types, or type aliases to other literal types
):
element = self.element(locator, condition=condition)
element.click()

def tap(self, locator, **kwargs):
"""Taps on an element using ActionHelpers."""
def tap(self, locator: Locator, duration: float = 500, **kwargs):
"""Taps on an element using ActionHelpers.
Taps on an particular place with up to five fingers, holding for a
certain duration

:param locator: locator of an element
:param duration: length of time to tap, in ms"""
try:
element = self.element(locator, condition="clickable", **kwargs)
location = element.location
size = element.size
x = location["x"] + size["width"] // 2
y = location["y"] + size["height"] // 2
self.driver.tap([(x, y)])
self.driver.tap([(x, y)], duration=duration)
except Exception as e:
print(f"Error during tap action: {e}")

Expand All @@ -43,7 +50,7 @@
relative_end_y: float,
duration_ms: int = 200,
) -> None:
size = self.driver.get_window_size()
size = self.get_screen_size()
width = size["width"]
height = size["height"]
start_x = int(width * relative_start_x)
Expand All @@ -58,6 +65,41 @@
duration_ms=duration_ms,
)

def scroll(
self,
directions: Literal["down", "up"] = "down",

Check warning on line 70 in src/screens/base_screen.py

View workflow job for this annotation

GitHub Actions / Qodana Community for Python

Invalid type hints definitions and usages

'Literal' may be parameterized with literal ints, byte and unicode strings, bools, Enum values, None, other literal types, or type aliases to other literal types
start_ratio: float = 0.7,
end_ratio: float = 0.3,
):
"""
Scrolls down the screen with customizable scroll size.

:param directions: up or down:
:param start_ratio: Percentage (0-1) from where the scroll starts
:type end_ratio: Percentage (0-1) where the scroll ends.
USAGE:
DOWN example
start_y = int(height * 0.7)
end_y = int(height * 0.3)
UP example
start_y = int(height * 0.3)
end_y = int(height * 0.7)
"""
size = self.get_screen_size()
width = size["width"]
height = size["height"]
start_x = width // 2
if directions == "down":
start_y = int(height * start_ratio)
end_y = int(height * end_ratio)
elif directions == "up":
start_y = int(height * end_ratio)
end_y = int(height * start_ratio)
else:
raise ValueError("Direction must be 'down' or 'up'")

self.scroll_by_coordinates(start_x, start_y, start_x, end_y)

def type(self, locator: Locator, text: str):
element = self.element(locator)
element.send_keys(text)
Expand All @@ -65,14 +107,13 @@
def double_tap(
self,
locator: Locator,
condition: Literal["clickable", "visible", "present"] = "clickable",

Check warning on line 110 in src/screens/base_screen.py

View workflow job for this annotation

GitHub Actions / Qodana Community for Python

Invalid type hints definitions and usages

'Literal' may be parameterized with literal ints, byte and unicode strings, bools, Enum values, None, other literal types, or type aliases to other literal types
**kwargs,
):
"""Double taps on an element."""
try:
element = self.element(locator, condition=condition, **kwargs)

Check notice on line 115 in src/screens/base_screen.py

View workflow job for this annotation

GitHub Actions / Qodana Community for Python

Unused local symbols

Local variable 'element' value is not used
action = ActionHelpers()
action.double_tap(element).perform()
# TODO
except Exception as e:
print(f"Error during double tap action: {e}")

Expand Down
44 changes: 43 additions & 1 deletion src/screens/element_interactor.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
from enum import Enum
from typing import Tuple, Optional, Literal, List
from typing import Tuple, Optional, Literal, List, cast
import time

from selenium.webdriver import ActionChains
from selenium.webdriver.common.actions import interaction
from selenium.webdriver.common.actions.action_builder import ActionBuilder
from selenium.webdriver.common.actions.pointer_input import PointerInput
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support import expected_conditions as EC

Check notice on line 10 in src/screens/element_interactor.py

View workflow job for this annotation

GitHub Actions / Qodana Community for Python

PEP 8 naming convention violation

Lowercase variable imported as non-lowercase
from selenium.webdriver.support.wait import WebDriverWait
from selenium.common.exceptions import TimeoutException, NoSuchElementException

Expand All @@ -21,7 +26,7 @@
self.driver = driver
self.waiters = {
wait_type: WebDriverWait(driver, wait_type.value)
for wait_type in WaitType

Check warning on line 29 in src/screens/element_interactor.py

View workflow job for this annotation

GitHub Actions / Qodana Community for Python

Incorrect type

Expected type 'collections.Iterable', got 'Type\[WaitType\]' instead
if wait_type != WaitType.FLUENT
}
self.waiters[WaitType.FLUENT] = WebDriverWait(
Expand All @@ -34,7 +39,7 @@
def wait_for(
self,
locator: Locator,
condition: Literal["clickable", "visible", "present"] = "visible",

Check warning on line 42 in src/screens/element_interactor.py

View workflow job for this annotation

GitHub Actions / Qodana Community for Python

Invalid type hints definitions and usages

'Literal' may be parameterized with literal ints, byte and unicode strings, bools, Enum values, None, other literal types, or type aliases to other literal types
waiter: Optional[WebDriverWait] = None,
) -> WebElement:
waiter = waiter or self._get_waiter()
Expand All @@ -49,14 +54,14 @@
return waiter.until(conditions[condition])
except TimeoutException as e:
raise TimeoutException(
f"Condition '{condition}' failed for element {locator} after {waiter._timeout} seconds"

Check notice on line 57 in src/screens/element_interactor.py

View workflow job for this annotation

GitHub Actions / Qodana Community for Python

Accessing a protected member of a class or a module

Access to a protected member _timeout of a class
) from e

def element(
self,
locator: Locator,
n: int = 3,
condition: Literal["clickable", "visible", "present"] = "visible",

Check warning on line 64 in src/screens/element_interactor.py

View workflow job for this annotation

GitHub Actions / Qodana Community for Python

Invalid type hints definitions and usages

'Literal' may be parameterized with literal ints, byte and unicode strings, bools, Enum values, None, other literal types, or type aliases to other literal types
wait_type: Optional[WaitType] = WaitType.DEFAULT,
):
for attempt in range(1, n + 1):
Expand All @@ -75,7 +80,7 @@
self,
locator: Locator,
n: int = 3,
condition: Literal["clickable", "visible", "present"] = "visible",

Check warning on line 83 in src/screens/element_interactor.py

View workflow job for this annotation

GitHub Actions / Qodana Community for Python

Invalid type hints definitions and usages

'Literal' may be parameterized with literal ints, byte and unicode strings, bools, Enum values, None, other literal types, or type aliases to other literal types
wait_type: Optional[WaitType] = WaitType.DEFAULT,
) -> List[WebElement]:
for attempt in range(1, n + 1):
Expand All @@ -95,7 +100,7 @@
locator: Locator,
expected: bool = True,
n: int = 3,
condition: Literal["clickable", "visible", "present"] = "visible",

Check warning on line 103 in src/screens/element_interactor.py

View workflow job for this annotation

GitHub Actions / Qodana Community for Python

Invalid type hints definitions and usages

'Literal' may be parameterized with literal ints, byte and unicode strings, bools, Enum values, None, other literal types, or type aliases to other literal types
wait_type: Optional[WaitType] = None,
) -> None:
wait_type = wait_type or WaitType.DEFAULT
Expand All @@ -120,7 +125,7 @@
locator: Locator,
expected: bool = True,
n: int = 3,
condition: Literal["clickable", "visible", "present"] = "visible",

Check warning on line 128 in src/screens/element_interactor.py

View workflow job for this annotation

GitHub Actions / Qodana Community for Python

Invalid type hints definitions and usages

'Literal' may be parameterized with literal ints, byte and unicode strings, bools, Enum values, None, other literal types, or type aliases to other literal types
wait_type: Optional[WaitType] = WaitType.DEFAULT,
) -> bool:
for _ in range(n):
Expand All @@ -136,3 +141,40 @@
pass
time.sleep(0.5)
return not expected

def scroll_by_coordinates(
self,
start_x: int,
start_y: int,
end_x: int,
end_y: int,
duration: Optional[int] = None,
):
"""Scrolls from one set of coordinates to another.

Args:
start_x: X coordinate to start scrolling from.
start_y: Y coordinate to start scrolling from.
end_x: X coordinate to scroll to.
end_y: Y coordinate to scroll to.
duration: Defines speed of scroll action. Default is 600 ms.

Returns:
Self instance.
"""
if duration is None:
duration = 1000

touch_input = PointerInput(interaction.POINTER_TOUCH, "touch")
actions = ActionChains(self.driver)

actions.w3c_actions = ActionBuilder(self.driver, mouse = touch_input)
actions.w3c_actions.pointer_action.move_to_location(start_x, start_y)
actions.w3c_actions.pointer_action.pointer_down()
actions.w3c_actions = ActionBuilder(self.driver, mouse=touch_input, duration=duration)

actions.w3c_actions.pointer_action.move_to_location(end_x, end_y)
actions.w3c_actions.pointer_action.release()

actions.perform()

15 changes: 12 additions & 3 deletions src/screens/main_screen/main_screen.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
from locators.locators import Common
from typing import Literal

from locators.locators import Locators
from screens.base_screen import Screen


class MainScreen(Screen):

def __init__(self, driver):
super().__init__(driver)
self.locators = Locators()

def click_on_text_link(self):
self.click(locator = Common.text_link)
self.click(locator = self.locators.main_menu.TEXT_LINK)

def tap_on_text_link(self):
self.tap(locator = Common.text_link)
self.tap(locator = self.locators.main_menu.TEXT_LINK)

def scroll_view_by_coordinates(self, direction: Literal['down', 'up'] = 'down'):

Check warning on line 19 in src/screens/main_screen/main_screen.py

View workflow job for this annotation

GitHub Actions / Qodana Community for Python

Invalid type hints definitions and usages

'Literal' may be parameterized with literal ints, byte and unicode strings, bools, Enum values, None, other literal types, or type aliases to other literal types
self.tap(locator = self.locators.main_menu.VIEWS_LINK)
self.scroll(directions = direction)


6 changes: 5 additions & 1 deletion tests/test_p1/test_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ def test_click(self, setup):
self.main_screen.click_on_text_link()

def test_tap(self, setup):
self.main_screen.tap_on_text_link()
self.main_screen.tap_on_text_link()

def test_scroll_by_coordinates(self, setup):
self.main_screen.scroll_view_by_coordinates(direction = "down")
self.main_screen.scroll('up')
Loading