Skip to content
Open
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
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
6 changes: 3 additions & 3 deletions mops/abstraction/element_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,14 +266,14 @@ def hide(self) -> Element:
"""
raise NotImplementedError()

def execute_script(self, script: str, *args) -> Any:
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
8 changes: 8 additions & 0 deletions mops/base/driver_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,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
45 changes: 42 additions & 3 deletions mops/base/element.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
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.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 +698,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 +774,14 @@ def hide(self) -> Element:
self.execute_script('arguments[0].style.opacity = "0";')
return self

def execute_script(self, script: str, *args) -> Any:
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
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
44 changes: 7 additions & 37 deletions mops/playwright/play_element.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
from __future__ import annotations

import time
from abc import ABC
from typing import Union, List, Any

from PIL.Image import Image
from mops.keyboard_keys import KeyboardKeys
from mops.mixins.objects.scrolls import ScrollTo, ScrollTypes
from playwright.sync_api import TimeoutError as PlayTimeoutError, Error
from playwright.sync_api import Error
from playwright.sync_api import Page as PlaywrightPage
from playwright.sync_api import Locator, Page, Browser, BrowserContext

Expand All @@ -16,12 +14,10 @@
from mops.utils.decorators import retry
from mops.utils.selector_synchronizer import get_platform_locator, set_playwright_locator
from mops.abstraction.element_abc import ElementABC
from mops.exceptions import TimeoutException, InvalidSelectorException
from mops.exceptions import InvalidSelectorException
from mops.utils.logs import Logging
from mops.shared_utils import cut_log_data, get_image
from mops.utils.internal_utils import (
WAIT_EL,
get_timeout_in_ms,
calculate_coordinate_to_click,
is_group,
is_element,
Expand Down Expand Up @@ -105,7 +101,11 @@ def click(self, *, force_wait: bool = True, **kwargs) -> PlayElement:
if force_wait:
self.wait_visibility(silent=True)

self._first_element.click(**kwargs)
if self.driver_wrapper.is_mobile_resolution:
self._first_element.tap(**kwargs)
else:
self._first_element.click(**kwargs)

return self

def click_outside(self, x: int = -5, y: int = -5) -> PlayElement:
Expand Down Expand Up @@ -244,36 +244,6 @@ def uncheck(self) -> PlayElement:

# Element state

def scroll_into_view(
self,
block: ScrollTo = ScrollTo.CENTER,
behavior: ScrollTypes = ScrollTypes.INSTANT,
sleep: Union[int, float] = 0,
silent: bool = False,
) -> PlayElement:
"""
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:`PlayElement`
"""
if not silent:
self.log(f'Scroll element "{self.name}" into view')

self._first_element.scroll_into_view_if_needed()

if sleep:
time.sleep(sleep)

return self

def screenshot_image(self, screenshot_base: bytes = None) -> Image:
"""
Returns a :class:`PIL.Image.Image` object representing the screenshot of the web element.
Expand Down
14 changes: 10 additions & 4 deletions mops/selenium/core/core_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,21 @@ def get_window_size(self) -> Size:
"""
return Size(**self.driver.get_window_size())

def wait(self, timeout: Union[int, float] = WAIT_UNIT) -> CoreDriver:
def wait(self, timeout: Union[int, float] = WAIT_UNIT, reason: str = '') -> CoreDriver:
"""
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:`.CoreDriver` - The current instance of the driver wrapper.
"""
if reason:
self.log(reason)

time.sleep(timeout)
return self

Expand Down Expand Up @@ -285,15 +291,15 @@ def switch_to_default_content(self) -> CoreDriver:
self.driver.switch_to.default_content()
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.
"""
args = [getattr(arg, 'element', arg) for arg in args]
Expand Down
33 changes: 0 additions & 33 deletions mops/selenium/core/core_element.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
from mops.js_scripts import get_element_size_js, get_element_position_on_screen_js, hide_caret_js_script
from mops.keyboard_keys import KeyboardKeys
from mops.mixins.objects.location import Location
from mops.mixins.objects.scrolls import ScrollTo, ScrollTypes, scroll_into_view_blocks
from mops.mixins.objects.size import Size
from mops.shared_utils import cut_log_data, _scaled_screenshot
from mops.utils.internal_utils import WAIT_EL, safe_call, get_dict, is_group
Expand Down Expand Up @@ -206,38 +205,6 @@ def uncheck(self) -> CoreElement:

# Element state

def scroll_into_view(
self,
block: ScrollTo = ScrollTo.CENTER,
behavior: ScrollTypes = ScrollTypes.INSTANT,
sleep: Union[int, float] = 0,
silent: bool = False,
) -> CoreElement:
"""
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:`CoreElement`
"""
if not silent:
self.log(f'Scroll element "{self.name}" into view')

assert block in scroll_into_view_blocks, f'Provide one of {scroll_into_view_blocks} option in `block` argument'

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

if sleep:
time.sleep(sleep)

return self

def screenshot_image(self, screenshot_base: bytes = None) -> Image:
"""
Returns a :class:`PIL.Image.Image` object representing the screenshot of the web element.
Expand Down
Loading
Loading