Skip to content

Commit

Permalink
Merge pull request #10 from VicGrygorchyk/make_it_work_for_appium
Browse files Browse the repository at this point in the history
Make it work for appium
  • Loading branch information
VicGrygorchyk authored Jul 9, 2022
2 parents 025802a + b618017 commit 84f9ff9
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 29 deletions.
20 changes: 13 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
An lightweight extension to Selenium Python.<br/>
An lightweight extension to Selenium/Appium Python.<br/>
It's inspired by [Selenide](https://github.com/selenide/selenide "Selenide GitHub page") from Java world and
obsolete [Selene](https://github.com/yashaka/selene "Selene Github page")
[Selene](https://github.com/yashaka/selene "Selene Github page")

## Short Features Overview:
- **Selen-kaa** is easy integrated with your existing Selenium code,
- **Sele-kaa** doesn't break any line of your existing project!
- **Selen-kaa** is compatible with any standard Selenium methods.
- **Selen-kaa** is easy integrated with your existing either Selenium or Appium code,
- **Selen-kaa** doesn't break any line of your existing project!
- **Selen-kaa** is compatible with any standard Selenium\Appium methods.
<br/>For instance:
```
# Your old Selenium code
Expand All @@ -20,10 +20,10 @@ browser.get("https://www.seleniumhq.org/")
# any methods from the WebDriver works!
element = browser.find_element_by_css(".test-class")
```
Besides standard Selenium, **Selen-kaa** introduces more convenient way to
Besides, standard Selenium/Appium, **Selen-kaa** introduces more convenient way to
interact with a web page and web elements through `init_web_element()`
and `init_all_web_elements()`:<br/>
What it gives you? Possibility to create the web element in `__init__()` method of a Page Object,
What it brings you? Possibility to create the web element in `__init__()` method of a Page Object,
as the WebDriver would search this element only at the time of interaction with it:
```
browser = BrowserDriver(webdriver.Chrome())
Expand Down Expand Up @@ -68,6 +68,12 @@ browser.init_web_element("//div//a[contains(@href, '/imgres')]")
```
[About CSS selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors "Mozilla page")

If you need any other strategy to find an element, provide an optional `locator_strategy`, e.g. for `Appium`:
```
browser.init_web_element("**/XCUIElementTypeImage[`label == "test"`]", locator_strategy=MobileBy.IOS_CLASS_CHAIN)
browser.init_web_element("access-id", locator_strategy=MobileBy.ACCESSIBILITY_ID)
```

### More handful methods
Wait for element with css selector ".test-class" to be visible.
Condition `should` raises Timeout exception if the element is not visible within `timeout`.
Expand Down
17 changes: 13 additions & 4 deletions selen_kaa/element/se_elements_array.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Optional

from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.common.exceptions import TimeoutException
Expand All @@ -17,12 +19,17 @@ class SeElementsArray:

DEFAULT_TIMEOUT = 4

def __init__(self, webdriver: WebDriver, css_selector: str, timeout: TimeoutType = DEFAULT_TIMEOUT):
def __init__(self,
webdriver: WebDriver,
selector: str,
timeout: TimeoutType = DEFAULT_TIMEOUT,
locator_strategy: Optional[str] = None):
self._webdriver = webdriver
self._css_selector = css_selector
self._selector = selector
self._timeout = timeout
self._elements_array = []
self._element_type = None
self.locator_strategy = locator_strategy if locator_strategy else get_selector_type(self._selector)

@property
def element_type(self):
Expand All @@ -40,14 +47,16 @@ def _lazy_array(self):
if len(self._elements_array) < 1:
try:
elements_ = WebDriverWait(self._webdriver, self._timeout).until(
presence_of_all_elements_located((get_selector_type(self._css_selector), self._css_selector))
presence_of_all_elements_located((self.locator_strategy, self._selector))
)
except TimeoutException:
# return empty array if no element is present on the page
return []

for elem in elements_:
wrapped_elem = self._element_type(self._webdriver, self._css_selector, self._timeout)
wrapped_elem = self._element_type(
self._webdriver, self._selector, self._timeout, self.locator_strategy
)
wrapped_elem.web_element = elem
self._elements_array.append(wrapped_elem)

Expand Down
15 changes: 11 additions & 4 deletions selen_kaa/element/se_web_element.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Optional

from selenium.webdriver.remote.webdriver import WebDriver
from selenium.common.exceptions import NoSuchElementException, TimeoutException
from selenium.webdriver.remote.webelement import WebElement
Expand All @@ -23,13 +25,18 @@ class SeWebElement(SeElementInterface):

DEFAULT_TIMEOUT = 4

def __init__(self, webdriver: WebDriver, selector: str, timeout: TimeoutType = DEFAULT_TIMEOUT):
def __init__(self,
webdriver: WebDriver,
selector: str,
timeout: TimeoutType = DEFAULT_TIMEOUT,
locator_strategy: Optional[str] = None):
self.timeout = timeout
self._webdriver = webdriver
self._selector = selector
self._element = None
self._expect = None
self._should = None
self.locator_strategy = locator_strategy if locator_strategy else get_selector_type(self._selector)

@property
def web_element(self):
Expand All @@ -40,10 +47,10 @@ def get_web_element_by_timeout(self, timeout):
if self._element is None:
try:
return WebDriverWait(self._webdriver, timeout).until(
presence_of_element_located((get_selector_type(self.selector), self.selector))
presence_of_element_located((self.locator_strategy, self._selector))
)
except TimeoutException as exc:
raise NoSuchElementException(f"Web Element with selector {self.selector} has not been found."
raise NoSuchElementException(f"Web Element with selector {self._selector} has not been found."
f"\n{exc.msg}")
return self._element

Expand Down Expand Up @@ -99,4 +106,4 @@ def get_class(self):
return self.web_element.get_attribute("class")

def __repr__(self):
return f"Selen-kaa WebElement with selector `{self.selector}`."
return f"Selen-kaa WebElement with selector `{self._selector}`."
5 changes: 1 addition & 4 deletions selen_kaa/utils/se_utils.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
from re import match
from typing import List

from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoSuchElementException, TimeoutException
from selenium.webdriver.remote.webelement import WebElement
from selen_kaa.utils import custom_types


TimeoutType = custom_types.TimeoutType


def get_selector_type(selector):
def get_selector_type(selector: str):
""" Checks if selector is css or xpath
>>>get_selector_type(".css")
css selector
Expand Down
18 changes: 11 additions & 7 deletions selen_kaa/webdriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,30 +45,34 @@ def hooked(*args, **kwargs):
def action_chains(self):
return ActionChains(self.webdriver)

def init_web_element(self, selector: str, timeout: TimeoutType = None) -> SeWebElement:
def init_web_element(self, selector: str, timeout: TimeoutType = None, locator_strategy=None) -> SeWebElement:
"""Init a new WrappedWebElement.
Lazy initialization. Element would be called on the time of first interaction.
:param selector: str as css selector or xpath
:param selector: str as any locator, css selector or xpath
:param timeout: time to wait until element appears
:return: WrappedWebElement
:param locator_strategy: field of class `selenium.webdriver.common.by::By` or `MobileBy` for Appium
:return: SeWebElement
"""
if selector is None:
raise Exception("Selector should be not empty.")

timeout_ = DEFAULT_TIMEOUT
if timeout or timeout == 0:
timeout_ = timeout
return SeWebElement(self.webdriver, selector, timeout_)
return SeWebElement(self.webdriver, selector, timeout_, locator_strategy)

def init_all_web_elements(self, selector: str, timeout: TimeoutType = None) -> SeElementsArray:
def init_all_web_elements(self, selector: str, timeout: TimeoutType = None, locator_strategy=None) -> SeElementsArray:
"""Init a list with references to WrappedWebElement.
Lazy initialization. All elements would be called on the time of first interaction
with any of the elements.
:return: List of WrappedWebElements
:param selector: str as any locator, css selector or xpath
:param timeout: time to wait until element appears
:param locator_strategy: field of class `selenium.webdriver.common.by::By` or `MobileBy` for Appium
:return: List of SeWebElement
"""
timeout_ = DEFAULT_TIMEOUT
if timeout or timeout == 0:
timeout_ = timeout
arr = SeElementsArray(self.webdriver, selector, timeout_)
arr = SeElementsArray(self.webdriver, selector, timeout_, locator_strategy)
arr.element_type = SeWebElement
return arr
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ def get_requirements():

setup(
name="selen-kaa",
version="0.2.1",
version="0.2.2",
author="Viktor Grygorchuk",
author_email="vvgrygorchuk@gmail.com",
keywords=['Selenium', 'Test Automation'],
description="A lightweight wrapper around Selenium python repo.",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/VicGrygorchyk/selen_kaa.git",
download_url="https://github.com/VicGrygorchyk/selen_kaa/archive/0.2.1.tar.gz",
download_url="https://github.com/VicGrygorchyk/selen_kaa/archive/0.2.2.tar.gz",
packages=find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "test*"]),
install_requires=get_requirements(),
classifiers=[
Expand Down
64 changes: 64 additions & 0 deletions tests/unit_test/test_create_element.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import pytest

from selenium.webdriver.common.by import By

from selen_kaa.element.se_web_element import SeWebElement
from selen_kaa.element.se_elements_array import SeElementsArray


@pytest.mark.parametrize("selector", ("div", ".class", "div[class='test']",
"img[class^='irc_mut'][src^='http']"))
def test_create_se_web_element_by_css(selector):
element = SeWebElement(None, selector)
assert element.locator_strategy == By.CSS_SELECTOR


@pytest.mark.parametrize("selector", (
"//div", "./div/div/.class",
"//div[@id='rso']//div[@class='test tttest']//a[contains(@href, '/imgres')]",
"/html//div[contains(text(), 'The page')]",
"/bookstore/book[price>35.00]/title"
))
def test_create_se_web_element_by_xpath(selector):
element = SeWebElement(None, selector)
assert element.locator_strategy == By.XPATH


@pytest.mark.parametrize("param", (
("class-name", By.CLASS_NAME),
("img", By.TAG_NAME),
('**/XCUIElementTypeImage[`label == "test"`]', 'MobileBy.IOS_CLASS_CHAIN')
))
def test_create_se_web_element_by_custom_locator_strategy(param):
selector, locator_strat = param
element = SeWebElement(None, selector, locator_strategy=locator_strat)
assert element.locator_strategy == locator_strat


@pytest.mark.parametrize("selector", ("div", ".class", "div[class='test']",
"img[class^='irc_mut'][src^='http']"))
def test_create_web_element_arr_by_css(selector):
elements = SeElementsArray(None, selector)
assert elements.locator_strategy == By.CSS_SELECTOR


@pytest.mark.parametrize("selector", (
"//div", "./div/div/.class",
"//div[@id='rso']//div[@class='test tttest']//a[contains(@href, '/imgres')]",
"/html//div[contains(text(), 'The page')]",
"/bookstore/book[price>35.00]/title"
))
def test_create_se_web_element_arr_by_xpath(selector):
elements = SeElementsArray(None, selector)
assert elements.locator_strategy == By.XPATH


@pytest.mark.parametrize("param", (
("class-name", By.CLASS_NAME),
("img", By.TAG_NAME),
('**/XCUIElementTypeImage[`label == "test"`]', 'MobileBy.IOS_CLASS_CHAIN')
))
def test_create_se_web_element_by_custom_locator_strategy(param):
selector, locator_strat = param
elements = SeElementsArray(None, selector, locator_strategy=locator_strat)
assert elements.locator_strategy == locator_strat
1 change: 0 additions & 1 deletion tests/unit_test/test_se_utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import pytest
from selenium.webdriver.common.by import By


from selen_kaa.utils import se_utils


Expand Down

0 comments on commit 84f9ff9

Please sign in to comment.