I use another way for locators.
I have page_object directory, which contains files for the app pages and general file - base_page. In the Base_page.py I include general action methods for another pages. Example:
from appium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as ex_cond
from src.platform import PLATFORM, IS_IOS
class BasePage:
def __init__(self, driver: webdriver) -> None:
self._driver = driver
def get_element(self, locator: str, timeout=10):
by = get_locator_by_string(locator)
return WebDriverWait(self._driver, timeout).until(
ex_cond.visibility_of_element_located(by), ' : '.join(by))
def get_no_element(self, locator: str, timeout=10):
by = get_locator_by_string(locator)
element = WebDriverWait(self._driver, timeout).until(
ex_cond.invisibility_of_element_located(by), ' : '.join(by))
if element is None:
return 'No element found'
def get_element_text(self, locator: str, timeout=10):
by = get_locator_by_string(locator)
element = WebDriverWait(self._driver, timeout).until(
ex_cond.visibility_of_element_located(by), ' : '.join(by))
if IS_IOS:
return element.get_attribute('label')
else:
return element.text
def get_element_and_click(self, locator: str, timeout=10):
by = get_locator_by_string(locator)
element = WebDriverWait(self._driver, timeout).until(
ex_cond.visibility_of_element_located(by), ' : '.join(by))
return element.click()
To find the right way for locators, I created the custom method in the same file - base_page.py. Example:
def get_locator_by_string(locator_with_type):
exploided_locator = locator_with_type.split(':', 1)
by_type = exploided_locator[0]
locator = exploided_locator[1]
if by_type == 'xpath':
return (MobileBy.XPATH, locator)
elif by_type == 'css':
return (MobileBy.CSS_SELECTOR, locator)
elif by_type == 'id':
return (MobileBy.ID, locator)
elif by_type == 'accessibility_id':
return (MobileBy.ACCESSIBILITY_ID, locator)
elif by_type == 'android_uiautomator':
return (MobileBy.ANDROID_UIAUTOMATOR, locator)
elif by_type == 'ios_uiautomation':
return (MobileBy.IOS_UIAUTOMATION, locator)
elif by_type == 'ios_predicate':
return (MobileBy.IOS_PREDICATE, locator)
elif by_type == 'class':
return (MobileBy.CLASS_NAME, locator)
else:
raise Exception(f'Cannot get type of locator. Locator
{locator_with_type}')
Get_locator_by_string doesn't consist all search methods. There is methods I need.
In the locators file I have two ways - for Android and for iOS. Example:
import allure
from src.config import BUNDLE_APP
from src.ui.base_page import BasePage, locator_for_platform
class AuthorPage(BasePage):
_author_title = locator_for_platform({
'ANDROID': 'id:%s:id/authorName' % BUNDLE_APP,
'IOS': 'accessibility_id:author_name'
})
_subscribe_button = locator_for_platform({
'ANDROID': 'id:%s:id/subscribeBackground' % BUNDLE_APP,
'IOS': 'accessibility_id:author_subscribe_button'
})
@allure.step('Press subscribe button')
def press_subscribe_button(self):
super().get_element_and_click(self._subscribe_button)
@allure.step('Get subscribe button text')
def get_subscribe_button_text(self):
return super().get_element_text(self._subscribe_button_text)
@allure.step('Get author\'s name text')
def get_author_name_text(self):
return super().get_element_text(self._author_title)
To choose correct locators for platform I use methods for platform's checking. Example:
def locator_for_platform(selectors):
return selectors.get(PLATFORM, 'Undefined Selector')
To check current platform this file:
import os
from appium import webdriver
from src import config
def get_env(key, default=None):
return os.environ.get(key=key, default=default)
def get():
if IS_ANDROID:
return webdriver.Remote(
command_executor=config.APPIUM_HOST,
desired_capabilities=config.DESIRED_CAPS_ANDROID_RESET
)
else:
return webdriver.Remote(
command_executor=config.APPIUM_HOST,
desired_capabilities=config.DESIRED_CAPS_ANDROID_RESET
)
PLATFORM = get_env('PLATFORM', 'IOS')
IS_ANDROID = PLATFORM == 'ANDROID'
IS_IOS = PLATFORM == 'IOS'
And the last file for setup current platform for test:
#!/usr/bin/env bash
export PLATFORM=IOS
export PLATFORM=ANDROID
APPIUM_HOST and DESIRED_CAPS_ANDROID_RESET / DESIRED_CAPS_ANDROID_RESET in other file. Example:
APPIUM_HOST = 'http://localhost:4445/wd/hub'
DESIRED_CAPS_IOS = {
'platformName': 'iOS',
'platformVersion': '13.3',
'deviceName': 'iPhone 8',
'automationNam': 'XCUITest',
'app': MY_APP_IOS
}
DESIRED_CAPS_ANDROID_RESET = {
'platformName': 'Android',
'platformVersion': '10',
'automationName': 'uiautomator2',
'deviceName': DEVICES['Pixel Emulator (10.0)'],
'app': MY_APP_ANDROID,
'unicodeKeyboard': 'true',
'resetKeyboard': 'true',
'disableWindowAnimation': 'true',
'autoWebviewTimeout': '2000',
'clearDeviceLogsOnStart': 'true'
}
DEVICES and MY_APP_ANDROID is another dictionaries.
I hope my experience will be helpful for you.