Automated testing must be-PageObject design pattern

What is PageObject?

Abstract processing of page objects (page objects include: page elements, button clicks, text box input, option box selection, etc.). The code can minimize the amount of changes to the test script after the page elements are changed, support the repetitive use of the code to the greatest extent, and make the test framework structure reasonable and clear, and the code more modular, avoiding redundancy and excessive coupling high.

 

What are the characteristics of PageObject?

  • The page is encapsulated into the Page class, and the page elements are member elements of the Page class. The implementation of the page function is placed in the methods of the Page class.

  • Encapsulate a page to be tested (or an object to be tested) into a class, such as a login page, and call it the Page class. The Page class includes all the elements on this page (or the object to be tested) (the url link of the login page, the input of the username/password text box, the acquisition of the image verification code, and the click of the login/register button), as well as for the page The operation method of the element (single-step operation or multi-step operation, generally defined class method).

  • Note: The Page class of this login page only includes the login page, and generally does not include operations on other pages (excluding the login page).

  • A test class is defined for this Page class, and each class method of the Page class is called to complete the automated test in the test class. That is, the test code is decoupled from the page code of the tested page. When the page itself changes, for example: element positioning changes, page layout changes, only the code of the corresponding Page class needs to be changed, and the code of the test class does not need to be changed . PageObject mode reduces code redundancy, can make business processes clear and easy to read, and reduces the cost of test code maintenance.

 

How to implement the PageObject design pattern?

As shown in the figure below, it is the flowchart of PageObject's classic design module:
image

It can be seen from the figure that in the test class, we will define many test methods, these test methods will include calls to the page object instance; and the page object instance is generated by the initialization operation of the page object class; for many pages The common operations that exist in all object classes will be extracted into the page object base class.

 

In this way, you can achieve:

  • There is only one definition of a page element in the entire project, and the others are called (if the page element changes, just modify this place);

  • The common operations of the Page class are further extracted to the BasePage class, reducing code redundancy.

 

PageObject's Python library

In Python, there is a Python library Page Objects specifically for PageObject. Use Page Objects to quickly implement PageObject mode.

Install page_objects
Python Common installation commands: pip install page_objects

Practical example of PageObject login page

#从page_objects包中引入PageObject和PageElement模块
from page_objects import PageObject, PageElement

from selenium import webdriver

class LoginPage(PageObject):

        username = PageElement(id_='username')

        password = PageElement(name='password')

        login = PageElement(css='input[type="submit"]')

driver = webdriver.PhantomJS()

driver.get("http://www.baidu.com")

#实例化一个类对象为:page
page = LoginPage(driver)

#给类对象的username属性赋值为:13057891256(相当于手工在登录页面的用户名输入框输入:13057891256)
page.username = '13057891256'

#给类对象的password属性赋值为:test_123(相当于手工在登录页面的密码输入框输入:test_123)
page.password = 'test_123'

#断言登录页面输入的用户名是否是:13057891256
assert page.username.text == '13057891256'

#让类对象点击登录页面的登录按钮(相当于手工在登录页面点击登录按钮)
page.login.click()

The above is just a simple example of PageObject with the login page. The knowledge of PO (PageObject) mode is much more than that. Next, please see the best practice of PageObject design mode in actual project.

 

Project combat-the best practice of PageObject design pattern

The following directory structure is the test framework structure after using the PageObject design pattern:

|--APITestPO

    |--pages

        |--ones.py

        |--base_page.py

    |--tests

        |--test_ones.py

        |--__init__.py

    |--common

        |--__init__.py

        |--selenium_action.py

        |--requests_action.py

 

__init__.py is an empty file.

The ones.py file only includes the elements, objects and operations of the Page itself, and does not include other parts, such as the initialization of the browser Driver, the initialization of requests.Session(), and the login operations.

The contents of the pages/ones.py file are as follows:

from selenium.webdriver.common.by import By

from selenium.webdriver.support.ui import WebDriverWait

from selenium.webdriver.support import expected_conditions as EC

from page_objects import PageObject, PageElement

from pages.base_page import BasePage

class OneAI(BasePage):

    PROJECT_NAME_LOCATOR = '[class="company-title-text"]'

    NEW_PROJECT_LOCATOR = '.ones-btn.ones-btn-primary'

    new_project = PageElement(css=NEW_PROJECT_LOCATOR)

    def __init__(self, login_credential, target_page):

        super().__init__(login_credential, target_page)

    def get_project_name(self):

        try:

            project_name = WebDriverWait(self.driver, 30).until(

                EC.presence_of_element_located((By.CSS_SELECTOR, self.PROJECT_NAME_LOCATOR)))

            return project_name.get_attribute("innerHTML")

        except TimeoutError:

            raise TimeoutError('Run time out')

 

The contents of the tests/test_ones.py file are as follows:

import pytest

from pages.ones import OneAI

class TestOneAI:

    # 注意:email和密码需要更改成你自己的账户密码

    @pytest.mark.parametrize('login_data, project_name, target_page', [({"password": "wuliangceshizhidaoIsGood", "email": "[email protected]"}, {"project_name":"GOODTEST"}, {"target_page": "https://ones.ai/project/#/home/project"})])

    def test_project_name_txt(self, login_data, project_name, target_page):

        print(login_data)

        one_page = OneAI(login_data, target_page)

        actual_project_name = one_page.get_project_name()

        assert actual_project_name == project_name["project_name"]

As you can see, the test class test_ones.py has become very concise. It only includes one test method, test_project_name_txt. This function is used to test the project name we got instead of equal to the value we provided, which is GOODTEST.

It should be noted that in the test class, it should only include calls to various methods of the Page class, and you cannot directly manipulate the test class object to generate new functions in the test class.

Split the operations on Selenium/WebDriver and Requests into selenium_action.py and requests_action.py respectively.

 

The content of the selenium_action.py file is as follows:

from selenium import webdriver

class SeleniumAction(object):

    @staticmethod

    def initial_driver(browser_name='chrome'):

        browser_name = browser_name.lower()

        if browser_name not in {'chrome', 'firefox', 'ff', 'ie'}:

            browser_name = 'chrome'

        if browser_name == 'chrome':

            browser = webdriver.Chrome()

        elif browser_name in ('firefox', 'ff'):

            browser = webdriver.Firefox()

        elif browser_name == 'ie':

            webdriver.Ie()

        browser.maximize_window()

        browser.implicitly_wait(60)

        return browser

    @staticmethod

    def cookie_to_selenium_format(cookie):

        cookie_selenium_mapping = {'path': '', 'secure': '', 'name': '', 'value': '', 'expires': ''}

        cookie_dict = {}

        if getattr(cookie, 'domain_initial_dot'):

            cookie_dict['domain'] = '.' + getattr(cookie, 'domain')

        else:

            cookie_dict['domain'] = getattr(cookie, 'domain')

        for k in list(cookie_selenium_mapping.keys()):

            key = k

            value = getattr(cookie, k)

            cookie_dict[key] = value

        return cookie_dict

selenium_action.py contains all the operations for Selenium, and all the operations for the browser in the future will be placed in this py file.

 

The contents of the requests_action.py file are as follows:

import json

import traceback

import requests

from requests.packages.urllib3.exceptions import InsecureRequestWarning

# Disable https security warning

requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

class SharedAPI(object):

    def __init__(self):

        self.s = requests.session()

        self.login_url = 'https://ones.ai/project/api/project/auth/login'

        self.header = {

            "user-agent": "user-agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36",

            "content-type": "application/json"}

    def login(self, login_credential):

        try:

            result = self.s.post(self.login_url, data=json.dumps(login_credential), headers=self.header, verify=False)

            if int(result.status_code) == 200:

                pass

            else:

                raise Exception('login failed')

            return result

        except RuntimeError:

            traceback.print_exc()

    def post_api(self, url, **kwargs):

        return self.s.post(url, **kwargs)

    def get_api(self, url, **kwargs):

        return self.s.get(url, **kwargs)

Requests_action.py includes all operations on the requests library.

 

The content of the base_page.py file is as follows:

import json

import traceback

import requests

from selenium import webdriver

from page_objects import PageObject, PageElement

from common.requests_action import SharedAPI

from common.selenium_action import SeleniumAction

class BasePage(PageObject):

    def __init__(self, login_credential, target_page):

        self.api_driver = SharedAPI()

        self.loginResult = self.api_driver.login(login_credential)

        self.driver = SeleniumAction.initial_driver()

        self._api_login(login_credential, target_page)

    def _api_login(self, login_credential, target_page):

        target_url = json.loads(json.dumps(target_page))

        assert json.loads(self.loginResult.text)["user"]["email"].lower() == login_credential["email"]

        all_cookies = self.loginResult.cookies._cookies[".ones.ai"]["/"]

        self.driver.get(target_url["target_page"])

        self.driver.delete_all_cookies()

        for k, v in all_cookies.items():

                self.driver.add_cookie(SeleniumAction.cookie_to_selenium_format(v))

        self.driver.get(target_url["target_page"])

        return self.driver

As you can see, in base_page.py, the way we initialize requests.Session() and the browser's Driver is by calling the SharedAPI and SeleniumAction classes. Then the BasePage class now only includes functions that can be shared by each Page class, and no longer includes irrelevant operations.

 

summary

Today's sharing mainly introduces to you the PageObject design pattern, which is a classic design pattern in automated testing. Through PageObject, we can realize the separation of element positioning, element, and element operation, so that our automated testing framework is more reusable and can be maintained at low cost.

I don't know if you find that through the PageObject design pattern, the automated testing framework can be separated from the framework code and business code. From then on, our framework can appear to be clearly structured and easy to understand, reducing the cost of learning for other colleagues.

Welcome to pay attention to [The Way of Infinite Testing] public account , reply [receive resources],
Python programming learning resources dry goods,
Python+Appium framework APP UI automation,
Python+Selenium framework Web UI automation,
Python+Unittest framework API automation,

Resources and codes are sent for free~
There is a QR code of the official account at the bottom of the article, you can just scan it on WeChat and follow it.

Remarks: My personal public account has been officially opened, dedicated to the sharing of test technology, including: big data testing, functional testing, test development, API interface automation, test operation and maintenance, UI automation testing, etc., WeChat search public account: "Infinite The Way of Testing", or scan the QR code below:

 Add attention and let us grow together!

Guess you like

Origin blog.csdn.net/weixin_41754309/article/details/113093343