Implementing PO Design Pattern in Appium Framework - Enhanced Page Object Structure

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:

  1. Click Contacts to navigate to contact list
  2. Select Add Member to go to manual entry screen
  3. Proceed to member addition form
  4. Enter member details and save
  5. 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")))

Tags: appium po-design-pattern automation-framework mobile-testing data-driven-testing

Posted on Wed, 13 May 2026 02:49:10 +0000 by Piranha