Refactoring BasePage Module
Element Locator Methods
def find(self, by, locator=None):
return self.driver.find_elements(*by) if isinstance(by, tuple) else self.driver.find_element(by, locator)
Enhanced Find Method with Popup Handling
class BasePage():
_black_list = [MobileBy.ID, 'image_cancel']
_error_count = 0
_error_max = 10
_params = {}
def __init__(self, driver: WebDriver = None):
self._driver = driver
def find(self, by, locator=None):
try:
self._error_count = 0
return self.driver.find_elements(*by) if isinstance(by, tuple) else self.driver.find_element(by, locator)
except Exception as e:
self._error_count += 1
if self._error_count >= self._error_max:
raise e
for black in self._black_list:
elements = self.driver.find_elements(*black)
if len(elements) > 0:
elements[0].click()
return self.find(by, locator)
raise e
Data-Driven Logic Implementation
from appium.webdriver.common.mobileby import MobileBy
from appium.webdriver.webdriver import WebDriver
import yaml
class BasePage():
_black_list = [MobileBy.ID, 'image_cancel']
_error_count = 0
_error_max = 10
_params = {}
def __init__(self, driver: WebDriver = None):
self._driver = driver
def steps(self, path):
with open(path, encoding="utf-8") as f:
steps: list[dict] = yaml.safe_load(f)
for step in steps:
if "by" in step.keys():
if "id" == step["by"]:
element = self.find(step["by"], step["locator"])
if "xpath" == step["by"]:
element = self.find(MobileBy.XPATH, step["locator"])
if "action" in step.keys():
if "click" == step["action"]:
element.click()
if "send" == step["action"]:
content: str = step["value"]
for param in self._params:
content = content.replace("{%s}" % param, self._params[param])
self.send(content, step["by"], step["locator"])
Send Method with Popup Handling
def send(self, value, by, locator=None):
try:
self.find(by, locator).send_keys(value)
self._error_count = 0
except Exception as e:
self._error_count += 1
if self._error_count >= self._error_max:
raise e
for black in self._black_list:
elements = self.driver.find_elements(*black)
if len(elements) > 0:
elements[0].click()
return self.send(by, locator)
raise e
Application Module Enhencement
from testenter.page.basepage import BasePage
from testenter.page.main import Main
from appium import webdriver
class App(BasePage):
def start(self):
_package = "com.tencent.wework"
_activity = ".launch.LaunchSplashActivity"
if self.driver is None:
desired_caps = {}
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '6.0'
desired_caps['deviceName'] = 'emulator-5554'
desired_caps['appPackage'] = _package
desired_caps['appActivity'] = _activity
desired_caps['noReset'] = 'true'
desired_caps['dontStopAppOnReset'] = 'true'
self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
self.driver.implicitly_wait(5)
else:
return self.driver.start_activity(_package, _activity)
return self
def stop(self):
pass
def restart(self):
pass
def main(self):
return Main(self._driver)
Page Operation Details
Main page navigation flow:
- Click Contacts to navigate to contact list
- Select Add Member to go to manual entry screen
- Proceed to member addition form
- Enter member details and save
- Return to previous screen
For parameterized testing with out app restart:
- Navigate back manually from manual entry to contact list
Main page implementation:
from testenter.page.addresslistpage import AddresslistPage
from testenter.page.basepage import BasePage
class Main(BasePage):
def goto_message(self):
pass
def goto_addresslist(self):
self.steps("/Users/zhaitiantian3/PycharmProjects/装饰器/testenter/steps/mainwe.yaml")
return AddresslistPage(self._driver)
def goto_workbench(self):
pass
def goto_myprofile(self):
pass
Data-Driven Implementation Explanation
The replacement logic:
if "send" == step["action"]:
content: str = step["value"]
for param in self._params:
content = content.replace("{%s}" % param, self._params[param])
self.send(content, step["by"], step["locator"])
Example with Snowball app search:
class Search(BasePage):
def search(self, value):
self._params["value"] = value
self.steps("../page/search.yaml")
Test case usage:
class Test_search():
def test_search(self):
App().start().main().goto_market().search("jd")
Parameterized Test Cases
Original approach:
@pytest.mark.parametrize("name,sex,mobile", [("测试账号05", "男", "17800000005"), ("测试账号06", "女", "17800000006")])
Enhanced approach using YAML data file:
@pytest.mark.parametrize("name,sex,mobile", yaml.safe_load(
open("/Users/zhaitiantian3/PycharmProjects/装饰器/testenter/data/contact.yaml")))