diff --git a/mops/playwright/play_element.py b/mops/playwright/play_element.py index cf55cd17..5a4d56f3 100644 --- a/mops/playwright/play_element.py +++ b/mops/playwright/play_element.py @@ -7,7 +7,7 @@ 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 +from playwright.sync_api import TimeoutError as PlayTimeoutError, Error from playwright.sync_api import Page as PlaywrightPage from playwright.sync_api import Locator, Page, Browser, BrowserContext @@ -15,7 +15,7 @@ from mops.mixins.objects.location import Location from mops.utils.selector_synchronizer import get_platform_locator, set_playwright_locator from mops.abstraction.element_abc import ElementABC -from mops.exceptions import TimeoutException +from mops.exceptions import TimeoutException, InvalidSelectorException from mops.utils.logs import Logging from mops.shared_utils import cut_log_data, get_image from mops.utils.internal_utils import ( @@ -435,7 +435,11 @@ def is_displayed(self, silent: bool = False) -> bool: if not silent: self.log(f'Check visibility of "{self.name}"') - return self._first_element.is_visible() + try: + return self._first_element.is_visible() + except Error as exc: + raise InvalidSelectorException(exc.message) from None + def is_hidden(self, silent: bool = False) -> bool: """ diff --git a/mops/selenium/core/core_element.py b/mops/selenium/core/core_element.py index d2cd4455..670c506d 100644 --- a/mops/selenium/core/core_element.py +++ b/mops/selenium/core/core_element.py @@ -390,7 +390,10 @@ def is_available(self) -> bool: :return: :class:`bool` - :obj:`True` if present in DOM """ - element = safe_call(self._find_element, wait_parent=False) + try: + element = safe_call(self._find_element, wait_parent=False) + except SeleniumInvalidSelectorException as exc: + raise InvalidSelectorException(exc.msg) from None return bool(element) diff --git a/mops/utils/selector_synchronizer.py b/mops/utils/selector_synchronizer.py index cb1e7184..fd577297 100644 --- a/mops/utils/selector_synchronizer.py +++ b/mops/utils/selector_synchronizer.py @@ -92,7 +92,7 @@ def set_selenium_selector(obj: Any): elif " " in locator: obj.locator = f'//*[contains(text(), "{locator}")]' obj.locator_type = By.XPATH - obj.log_locator = f'{LocatorType.XPATH}={locator}' + obj.log_locator = f'{LocatorType.XPATH}={obj.locator}' # Default to ID if nothing else matches @@ -108,33 +108,35 @@ def set_playwright_locator(obj: Any): Sets playwright locator & locator type """ locator = obj.locator.strip() + obj.log_locator = locator # Checking the supported locators if locator.startswith(DEFAULT_MATCH): - pass + obj.locator_type = locator.partition('=')[0] + return # Checking the regular locators elif locator.startswith(XPATH_MATCH): - obj.locator = f"{LocatorType.XPATH}={locator}" + obj.locator_type = LocatorType.XPATH elif locator.startswith(CSS_MATCH) or re.search(CSS_REGEXP, locator): - obj.locator = f"{LocatorType.CSS}={locator}" + obj.locator_type = LocatorType.CSS - elif locator in all_tags: - obj.locator = f'{LocatorType.CSS}={locator}' + elif locator in all_tags or all(tag in all_tags for tag in locator.split()): + obj.locator_type = LocatorType.CSS elif " " in locator: - obj.locator = f"{LocatorType.TEXT}={locator}" + obj.locator_type = LocatorType.TEXT # Default to ID if nothing else matches else: - obj.locator = f"{LocatorType.ID}={locator}" + obj.locator_type = LocatorType.ID + obj.locator = f'{obj.locator_type}={locator}' obj.log_locator = obj.locator - obj.locator_type = None def set_appium_selector(obj: Any): diff --git a/tests/static_tests/unit/test_selector_synchronizer.py b/tests/static_tests/unit/test_selector_synchronizer.py index bc846b18..30a23a7f 100644 --- a/tests/static_tests/unit/test_selector_synchronizer.py +++ b/tests/static_tests/unit/test_selector_synchronizer.py @@ -16,7 +16,8 @@ ("/html/body/div", "/html/body/div", By.XPATH, "xpath=/html/body/div"), ("#my_element", "#my_element", By.CSS_SELECTOR, "css=#my_element"), ("button", "button", By.CSS_SELECTOR, "css=button"), - ("div span", "div span", By.CSS_SELECTOR, "css=div span"), + ("tbody tr td span", "tbody tr td span", By.CSS_SELECTOR, "css=tbody tr td span"), + ("textarea", "textarea", By.CSS_SELECTOR, "css=textarea"), ("Some text", '//*[contains(text(), "Some text")]', By.XPATH, "xpath=Some text"), ("[href='/some/url']", "[href='/some/url']", By.CSS_SELECTOR, "css=[href='/some/url']"), ], @@ -30,24 +31,24 @@ def test_set_selenium_selector(locator_input, expected_locator, expected_locator @pytest.mark.parametrize( - "locator_input, expected_locator, expected_log_locator", + "locator_input, expected_locator", [ - ("xpath=//div", "xpath=//div", "xpath=//div"), - ("text=Hello", "text=Hello", "text=Hello"), - ("css=.class", "css=.class", "css=.class"), - ("id=my_id", "id=my_id", "id=my_id"), - ("/html/body/div", "xpath=/html/body/div", "xpath=/html/body/div"), - ("#my_element", "css=#my_element", "css=#my_element"), - ("button", "css=button", "css=button"), - ("div span", "text=div span", "text=div span"), - ("Some text", "text=Some text", "text=Some text"), - ("[href='/some/url']", "css=[href='/some/url']", "css=[href='/some/url']"), - ("", "id=", "id="), + ("xpath=//div", "xpath=//div"), + ("text=Hello", "text=Hello"), + ("css=.class", "css=.class"), + ("id=my_id", "id=my_id"), + ("/html/body/div", "xpath=/html/body/div"), + ("#my_element", "css=#my_element"), + ("button", "css=button"), + ("tbody tr td span", "css=tbody tr td span"), + ("Some text", "text=Some text"), + ("[href='/some/url']", "css=[href='/some/url']"), ], ) -def test_set_playwright_locator(locator_input, expected_locator, expected_log_locator): +def test_set_playwright_locator(locator_input, expected_locator): mock_obj = SimpleNamespace() mock_obj.locator = locator_input set_playwright_locator(mock_obj) assert mock_obj.locator == expected_locator - assert mock_obj.log_locator == expected_log_locator + assert mock_obj.log_locator == expected_locator + assert mock_obj.locator_type == expected_locator.partition('=')[0] diff --git a/tests/web_tests/test_element.py b/tests/web_tests/test_element.py index 9d94fed5..0aff7833 100644 --- a/tests/web_tests/test_element.py +++ b/tests/web_tests/test_element.py @@ -1,10 +1,12 @@ +import contextlib import random import pytest +from mops.base.element import Element from mops.mixins.internal_mixin import get_element_info from tests.adata.pages.expected_condition_page import WaitValueCardBroken -from mops.exceptions import NoSuchElementException, NoSuchParentException +from mops.exceptions import NoSuchElementException, NoSuchParentException, InvalidSelectorException from tests.adata.pages.keyboard_page import KeyboardPage @@ -157,3 +159,56 @@ def test_element_execute_script(forms_page, driver_wrapper): new_text = 'driver wrapper automation' forms_page.controls_form.german_slider.execute_script('arguments[0].textContent = arguments[1];', new_text) assert forms_page.controls_form.german_slider.text == new_text + +def test_element_locator_check(mouse_event_page, driver_wrapper): + # Let's keep Elements here, for encapsulation purposes + # Reformat test if any trouble occur + locators = ( + '.card.text-center', + '[class *= card][class *= text-center]', + '#footer', + '[href*="https://www.linkedin.com/in"]' + ) + invalid_prefixes = ('xpath', 'text', 'id') + + for locator in locators: + assert Element(locator).is_displayed() + assert Element(f'css={locator}').is_displayed() + for invalid_prefix in invalid_prefixes: + with contextlib.suppress(InvalidSelectorException): + assert not Element(f'{invalid_prefix}={locator}').is_displayed() + + locators = ('//*[contains(@class, "card")]', '//body/div') + invalid_prefixes = ('css', 'text', 'id') + for locator in locators: + assert Element(locator).is_displayed() + assert Element(f'xpath={locator}').is_displayed() + for invalid_prefix in invalid_prefixes: + with contextlib.suppress(InvalidSelectorException): + assert not Element(f'{invalid_prefix}={locator}').is_displayed() + + locators = ('drop_target', 'drag_source') + invalid_prefixes = ('css', 'xpath', 'text') + for locator in locators: + assert Element(locator).is_displayed() + assert Element(f'id={locator}').is_displayed() + + for invalid_prefix in invalid_prefixes: + with contextlib.suppress(InvalidSelectorException): + assert not Element(f'{invalid_prefix}={locator}').is_displayed() + + choose_lang_button_name = 'Choose Language' + locators = (choose_lang_button_name, ) + invalid_prefixes = ('css', 'xpath', 'id') + for locator in locators: + breakpoint() + assert Element(locator).is_displayed() + assert Element(f'text={locator}').is_displayed() + if locator == choose_lang_button_name: # Move to separate test + assert not Element('.dropdown-content').is_displayed() + Element(locator).scroll_into_view().click() + Element('.dropdown-content').wait_visibility() + + for invalid_prefix in invalid_prefixes: + with contextlib.suppress(InvalidSelectorException): + assert not Element(f'{invalid_prefix}={locator}').is_displayed()