Skip to content
Closed
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
1 change: 1 addition & 0 deletions .github/workflows/appium_android_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ jobs:
- name: Start Appium Server in the Background
run: |
nohup appium --allow-insecure chromedriver_autodownload > appium.log 2>&1 &
sleep 10

- name: Enable KVM
run: |
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/appium_ios_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ jobs:
- name: Start Appium Server in the Background
run: |
nohup appium > appium.log 2>&1 &
sleep 10

- name: Run Appium Safari tests
id: tests
Expand Down
2 changes: 1 addition & 1 deletion mops/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
__version__ = '3.2.0'
__version__ = '3.3.0'
__project_name__ = 'mops'
19 changes: 15 additions & 4 deletions mops/abstraction/driver_wrapper_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,16 @@ def quit(self, silent: bool = False, trace_path: str = 'trace.zip'):
"""
raise NotImplementedError()

def wait(self, timeout: Union[int, float] = WAIT_UNIT) -> DriverWrapper:
def wait(self, timeout: Union[int, float] = WAIT_UNIT, reason: str = '') -> DriverWrapper:
"""
Pauses the execution for a specified amount of time.

:param timeout: The time to sleep in seconds (can be an integer or float).
:type timeout: typing.Union[int, float]

:param reason: The waiting reason.
:type reason: str

:return: :obj:`.DriverWrapper` - The current instance of the driver wrapper.
"""
raise NotImplementedError()
Expand Down Expand Up @@ -222,15 +225,15 @@ def switch_to_default_content(self) -> DriverWrapper:
"""
raise NotImplementedError()

def execute_script(self, script: str, *args) -> Any:
def execute_script(self, script: str, *args: Any) -> Any:
"""
Synchronously executes JavaScript in the current window or frame.
Compatible with Selenium's `execute_script` method.

:param script: The JavaScript code to execute.
:type script: str
:param args: Any arguments to pass to the JavaScript (e.g., Element object).
:type args: list
:param args: Any arguments to pass to the JavaScript.
:type args: :obj:`typing.Any`
:return: :obj:`typing.Any` - The result of the JavaScript execution.
"""
raise NotImplementedError()
Expand Down Expand Up @@ -307,6 +310,14 @@ def save_screenshot(
"""
raise NotImplementedError()

def get_scroll_position(self) -> int:
"""
Returns the current vertical scroll position of the page.

:return: :class:`int` - Current vertical scroll offset in pixels.
"""
raise NotImplementedError()

def assert_screenshot(
self,
filename: str = '',
Expand Down
14 changes: 11 additions & 3 deletions mops/abstraction/element_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,14 +266,22 @@ def hide(self) -> Element:
"""
raise NotImplementedError()

def execute_script(self, script: str, *args) -> Any:
def show(self) -> Element:
"""
Shows the element.

:return: :class:`Element`
"""
raise NotImplementedError()

def execute_script(self, script: str, *args: Any) -> Any:
"""
Executes a JavaScript script on the element.

:param script: JavaScript code to be executed, referring to the element as ``arguments[0]``.
:type script: str
:param args: Additional arguments for the script,
that appear in script as ``arguments[1]`` ``arguments[2]`` etc.
:param args: Any arguments to pass to the JavaScript.
:type args: :obj:`typing.Any`
:return: :obj:`typing.Any` result from the script.
"""
raise NotImplementedError()
Expand Down
23 changes: 16 additions & 7 deletions mops/base/driver_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from mops.mixins.objects.box import Box
from mops.mixins.objects.driver import Driver
from mops.mixins.objects.visual_comaprison_mixin import hide_before_screenshot, reveal_after_screenshot
from mops.visual_comparison import VisualComparison
from mops.abstraction.driver_wrapper_abc import DriverWrapperABC
from mops.playwright.play_driver import PlayDriver
Expand Down Expand Up @@ -220,6 +221,14 @@ def save_screenshot(

return image_object

def get_scroll_position(self) -> int:
"""
Returns the current vertical scroll position of the page.

:return: :class:`int` - Current vertical scroll offset in pixels.
"""
return self.execute_script('return window.pageYOffset')

def assert_screenshot(
self,
filename: str = '',
Expand Down Expand Up @@ -263,17 +272,17 @@ def assert_screenshot(
delay = delay or VisualComparison.default_delay
remove = [remove] if type(remove) is not list and remove else remove

if hide:
if not isinstance(hide, list):
hide = [hide]
for object_to_hide in hide:
object_to_hide.hide()
hide_before_screenshot(hide, is_optional=False, dw=self)
self.wait(delay)
hide_before_screenshot(VisualComparison.always_hide, is_optional=False, dw=self)

VisualComparison(self).assert_screenshot(
filename=filename, test_name=test_name, name_suffix=name_suffix, threshold=threshold, delay=delay,
scroll=False, remove=remove, fill_background=False, cut_box=cut_box
filename=filename, test_name=test_name, name_suffix=name_suffix, threshold=threshold,
remove=remove, fill_background=False, cut_box=cut_box
)

reveal_after_screenshot(VisualComparison.always_hide, dw=self)

def soft_assert_screenshot(
self,
filename: str = '',
Expand Down
72 changes: 62 additions & 10 deletions mops/base/element.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from __future__ import annotations

import time
from copy import copy
from typing import Union, List, Type, Tuple, Optional, TYPE_CHECKING

from PIL.Image import Image

from mops.mixins.objects.scrolls import ScrollTo, ScrollTypes, scroll_into_view_blocks
from mops.mixins.objects.visual_comaprison_mixin import hide_before_screenshot, reveal_after_screenshot
from mops.mixins.objects.wait_result import Result
from playwright.sync_api import Page as PlaywrightDriver
from appium.webdriver.webdriver import WebDriver as AppiumDriver
Expand Down Expand Up @@ -696,6 +699,43 @@ def is_fully_visible(self, check_displaying: bool = True, silent: bool = False)

return is_visible

def scroll_into_view(
self,
block: ScrollTo = ScrollTo.CENTER,
behavior: ScrollTypes = ScrollTypes.INSTANT,
sleep: Union[int, float] = 0,
silent: bool = False,
) -> Element:
"""
Scrolls the element into view using a JavaScript script.

:param block: The scrolling block alignment. One of the :class:`.ScrollTo` options.
:type block: ScrollTo
:param behavior: The scrolling behavior. One of the :class:`.ScrollTypes` options.
:type behavior: ScrollTypes
:param sleep: Delay in seconds after scrolling. Can be an integer or a float.
:type sleep: typing.Union[int, float]
:param silent: If :obj:`True`, suppresses logging.
:type silent: bool
:return: :class:`Element`
"""
if not silent:
self.log(f'Scroll element "{self.name}" into view')

if block not in scroll_into_view_blocks:
message = f'Provide one of {scroll_into_view_blocks} option in `block` argument'
raise UnsuitableArgumentsException(message)

self.execute_script(
'arguments[0].scrollIntoView({block: arguments[1], behavior: arguments[2]});',
block, behavior
)

if sleep:
time.sleep(sleep)

return self

def save_screenshot(
self,
file_name: str,
Expand Down Expand Up @@ -735,14 +775,23 @@ def hide(self) -> Element:
self.execute_script('arguments[0].style.opacity = "0";')
return self

def execute_script(self, script: str, *args) -> Any:
def show(self) -> Element:
"""
Shows the element.

:return: :class:`Element`
"""
self.execute_script('arguments[0].style.opacity = "1";')
return self

def execute_script(self, script: str, *args: Any) -> Any:
"""
Executes a JavaScript script on the element.

:param script: JavaScript code to be executed, referring to the element as ``arguments[0]``.
:type script: str
:param args: Additional arguments for the script,
that appear in script as ``arguments[1]`` ``arguments[2]`` etc.
:param args: Any arguments to pass to the JavaScript.
:type args: :obj:`typing.Any`
:return: :obj:`typing.Any` result from the script.
"""
return self.driver_wrapper.execute_script(script, *[self, *[arg for arg in args]])
Expand Down Expand Up @@ -797,17 +846,20 @@ def assert_screenshot(
delay = delay or VisualComparison.default_delay
remove = [remove] if type(remove) is not list and remove else remove

if hide:
if not isinstance(hide, list):
hide = [hide]
for object_to_hide in hide:
object_to_hide.hide()
if scroll:
self.scroll_into_view()

hide_before_screenshot(hide, is_optional=False, dw=self.driver_wrapper)
self.driver_wrapper.wait(delay)
hide_before_screenshot(VisualComparison.always_hide, is_optional=False, dw=self.driver_wrapper)

VisualComparison(self.driver_wrapper, self).assert_screenshot(
filename=filename, test_name=test_name, name_suffix=name_suffix, threshold=threshold, delay=delay,
scroll=scroll, remove=remove, fill_background=fill_background, cut_box=cut_box
filename=filename, test_name=test_name, name_suffix=name_suffix, threshold=threshold,
remove=remove, fill_background=fill_background, cut_box=cut_box
)

reveal_after_screenshot(VisualComparison.always_hide, dw=self.driver_wrapper)

def soft_assert_screenshot(
self,
filename: str = '',
Expand Down
34 changes: 34 additions & 0 deletions mops/mixins/objects/visual_comaprison_mixin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from __future__ import annotations

from typing import Union, Any, TYPE_CHECKING

if TYPE_CHECKING:
from mops.base.element import Element
from mops.base.driver_wrapper import DriverWrapper


def hide_elements(objects_to_hide: Union[list[Element], Element], is_optional: bool, dw: DriverWrapper):
for object_to_hide in objects_to_hide:

if is_optional:
object_to_hide = object_to_hide(dw)
if object_to_hide.is_displayed(silent=True):
object_to_hide.hide(silent=True)
else:
object_to_hide.hide(silent=True)



def hide_before_screenshot(objects_to_hide: Union[list, Any], is_optional: bool, dw: DriverWrapper = None):
if objects_to_hide:
if not isinstance(objects_to_hide, list):
objects_to_hide = [objects_to_hide]

hide_elements(objects_to_hide, is_optional=is_optional, dw=dw)


def reveal_after_screenshot(objects_to_reveal: Union[list, Any], dw: DriverWrapper):
for object_to_reveal in objects_to_reveal:
object_to_reveal = object_to_reveal(dw)
if object_to_reveal.is_displayed(silent=True):
object_to_reveal.show(silent=True)
14 changes: 10 additions & 4 deletions mops/playwright/play_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,21 @@ def is_firefox(self) -> bool:
"""
return self.browser_name.lower() == 'firefox'

def wait(self, timeout: Union[int, float] = WAIT_UNIT) -> PlayDriver:
def wait(self, timeout: Union[int, float] = WAIT_UNIT, reason: str = '') -> PlayDriver:
"""
Pauses the execution for a specified amount of time.

:param timeout: The time to sleep in seconds (can be an integer or float).
:type timeout: typing.Union[int, float]

:param reason: The waiting reason.
:type reason: str

:return: :obj:`.PlayDriver` - The current instance of the driver wrapper.
"""
if reason:
self.log(reason)

self.driver.wait_for_timeout(get_timeout_in_ms(timeout))
return self

Expand Down Expand Up @@ -245,15 +251,15 @@ def switch_to_default_content(self) -> PlayDriver:
self.driver = self._base_driver
return self

def execute_script(self, script: str, *args) -> Any:
def execute_script(self, script: str, *args: Any) -> Any:
"""
Synchronously executes JavaScript in the current window or frame.
Compatible with Selenium's `execute_script` method.

:param script: The JavaScript code to execute.
:type script: str
:param args: Any arguments to pass to the JavaScript (e.g., Element object).
:type args: list
:param args: Any arguments to pass to the JavaScript.
:type args: :obj:`typing.Any`
:return: :obj:`typing.Any` - The result of the JavaScript execution.
"""
script = script.replace('return ', '')
Expand Down
Loading
Loading