mirror of
https://github.com/status-im/status-app.git
synced 2026-08-27 15:11:20 +00:00
140 lines
5.4 KiB
Python
140 lines
5.4 KiB
Python
import logging
|
|
import time
|
|
|
|
import allure
|
|
|
|
import driver
|
|
from .object import QObject
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
def effective_vertical_viewport_margin(surface_height: float, max_margin: int = 24) -> int:
|
|
"""Vertical inset from scroll edges when deciding whether a row center is \"clickably\" inside.
|
|
|
|
Caps ``max_margin`` so that small scroll surfaces (CI windows, banners shrinking the flickable)
|
|
keep a non-empty inner band instead of permanently rejecting every ``cy``.
|
|
"""
|
|
if surface_height <= 0:
|
|
return 0
|
|
return min(max_margin, max(0, int((surface_height - 8) // 2)))
|
|
|
|
|
|
def row_vertically_usable_in_scroll_viewport(
|
|
scroll_bounds,
|
|
row_bounds,
|
|
max_margin: int = 24,
|
|
) -> bool:
|
|
"""True if a channel/list row is suitably positioned for interaction inside the scroll viewport.
|
|
|
|
Prefer vertical center inside an adaptive inner band; fall back to any overlap with the viewport.
|
|
"""
|
|
cy = row_bounds.y + row_bounds.height / 2
|
|
h = scroll_bounds.height
|
|
m = effective_vertical_viewport_margin(h, max_margin)
|
|
top = scroll_bounds.y + m
|
|
bot = scroll_bounds.y + h - m
|
|
if top <= cy <= bot:
|
|
return True
|
|
return (
|
|
row_bounds.y < scroll_bounds.y + scroll_bounds.height
|
|
and row_bounds.y + row_bounds.height > scroll_bounds.y
|
|
)
|
|
|
|
|
|
def _element_center_y_in_scroll_viewport(
|
|
element: QObject, scroll_surface, margin: int = 24
|
|
) -> bool:
|
|
"""True if the element's vertical center lies inside the scroll surface (global coords).
|
|
|
|
Qt Quick items clipped by a Flickable/ScrollView often keep ``visible == true``; relying on
|
|
that skips scrolling and leaves targets off-screen (see ActivityCenter._scroll_notification_into_view).
|
|
|
|
Uses ``driver.object.globalBounds`` like :meth:`QObject.bounds`, but resolves the target with a short
|
|
``waitForObjectExists`` so we do not call ``element.object`` (full UI timeout) on every scroll step.
|
|
|
|
Uses :func:`effective_vertical_viewport_margin` so narrow scroll surfaces still yield a valid band.
|
|
"""
|
|
try:
|
|
elem_obj = driver.waitForObjectExists(element.real_name, 200)
|
|
except (LookupError, RuntimeError):
|
|
return False
|
|
try:
|
|
srect = driver.object.globalBounds(scroll_surface)
|
|
crect = driver.object.globalBounds(elem_obj)
|
|
except (RuntimeError, AttributeError):
|
|
return False
|
|
cy = crect.y + crect.height / 2
|
|
m = effective_vertical_viewport_margin(srect.height, margin)
|
|
top = srect.y + m
|
|
bot = srect.y + srect.height - m
|
|
return top <= cy <= bot
|
|
|
|
|
|
def _element_center_x_in_scroll_viewport(
|
|
element: QObject, scroll_surface, margin: int = 24
|
|
) -> bool:
|
|
try:
|
|
elem_obj = driver.waitForObjectExists(element.real_name, 200)
|
|
except (LookupError, RuntimeError):
|
|
return False
|
|
try:
|
|
srect = driver.object.globalBounds(scroll_surface)
|
|
crect = driver.object.globalBounds(elem_obj)
|
|
except (RuntimeError, AttributeError):
|
|
return False
|
|
cx = crect.x + crect.width / 2
|
|
left = srect.x + margin
|
|
right = srect.x + srect.width - margin
|
|
return left <= cx <= right
|
|
|
|
|
|
class Scroll(QObject):
|
|
|
|
@allure.step('Scroll vertical down to object {1}')
|
|
def vertical_scroll_down(self, element: QObject, timeout_sec: int = 5, extra_scrolls_after: int = 0):
|
|
# First wait for element to exist (UI might need time to update after authentication)
|
|
started_at = time.monotonic()
|
|
while not element.exists:
|
|
time.sleep(0.1)
|
|
if time.monotonic() - started_at > timeout_sec:
|
|
raise LookupError(f'Element does not exist: {element}')
|
|
|
|
scroll_obj = self.object
|
|
sx = max(1, int(scroll_obj.width / 2))
|
|
sy = max(1, int(scroll_obj.height / 2))
|
|
|
|
# Scroll until the target is inside the scroll viewport (not just QML ``visible``).
|
|
started_at = time.monotonic()
|
|
while not _element_center_y_in_scroll_viewport(element, scroll_obj):
|
|
driver.mouse.scroll(scroll_obj, sx, sy, 0, -30, 1, 0.1)
|
|
if time.monotonic() - started_at > timeout_sec:
|
|
raise LookupError(f'Object not found: {element}')
|
|
|
|
# Optional: scroll more to center element (e.g. for popups that need space below to open fully)
|
|
for _ in range(extra_scrolls_after):
|
|
driver.mouse.scroll(scroll_obj, sx, sy, 0, -30, 1, 0.1)
|
|
time.sleep(0.1)
|
|
|
|
@allure.step('Scroll vertical up to object {1}')
|
|
def vertical_scroll_up(self, element: QObject, timeout_sec: int = 5):
|
|
scroll_obj = self.object
|
|
sx = max(1, int(scroll_obj.width / 2))
|
|
sy = max(1, int(scroll_obj.height / 2))
|
|
started_at = time.monotonic()
|
|
while not _element_center_y_in_scroll_viewport(element, scroll_obj):
|
|
driver.mouse.scroll(scroll_obj, sx, sy, 0, 30, 1, 0.1)
|
|
if time.monotonic() - started_at > timeout_sec:
|
|
raise LookupError(f'Object not found: {element}')
|
|
|
|
@allure.step('Scroll horizontal right to object {1}')
|
|
def horizontal_scroll_right(self, element: QObject, timeout_sec: int = 5):
|
|
scroll_obj = self.object
|
|
sx = max(1, int(scroll_obj.width / 2))
|
|
sy = max(1, int(scroll_obj.height / 2))
|
|
started_at = time.monotonic()
|
|
while not _element_center_x_in_scroll_viewport(element, scroll_obj):
|
|
driver.mouse.scroll(scroll_obj, sx, sy, 30, 0, 1, 0.1)
|
|
if time.monotonic() - started_at > timeout_sec:
|
|
raise LookupError(f'Object not found: {element}')
|