【python+selenium的web自動化】- PageObject模式解析及案例

miki_peng發表於2021-03-16

如果想從頭學起selenium,可以去看看這個系列的文章哦!

https://www.cnblogs.com/miki-peng/category/1942527.html

PO模式

Page Object(簡稱PO)模式,是Selenium實戰中最為流行,並且是自動化測試中最為熟悉和推崇的一種設計模式。在設計自動化測試時,把頁面元素和元素的操作方法按照頁面抽象出來,分離成一定的物件,然後再進行組織。

​ 做web自動化最頭疼的一個問題,莫過於頁面變化了,如果沒有使用PO設計模式,頁面一變化就意味著之前的元素定位甚至元素的操作方法不能用了,需要重新修改。你需要一個一個從測試指令碼中把需要修改的元素定位方式、元素的操作方法找出來,然後一一地修改。這樣的自動化指令碼不但繁瑣,維護成本也極高。

​ 而page object模式就可以很好地解決這個問題,優點?:

  • ? 減少程式碼冗餘
  • ? 業務和實現分離
  • ? 降低維護成本

​ 那到底什麼是Page Object模式,見名知意,就是頁面物件,在實際自動化測試中,一般對指令碼分為三層:

  • ? 物件層: 用於存放頁面元素定位
  • ? 邏輯層: 用於存放一些封裝好的功能用例模組
  • ? 業務層: 用於存放我們真正的測試用例的操作部分

​ 除了以上三層,還有一個基礎層,基礎層主要是針對selenium的一些常用方法,根據實際業務需要進行二次封裝,如點選、輸入等操作加入一些等待、日誌輸入、截圖等操作,方便以後檢視指令碼的執行情況及問題排查。

基礎層

​ 基礎層類名一般命名為BasePage,後續的物件層操作元素時都繼承這個基礎類,下面以點選、輸入為例:

# basepage.py
import os
import time
import datetime
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from common.logging import log
from common.constant import IMG_DIR


class BasePage:

    def __init__(self, driver: WebDriver):
        self.driver = driver

    def wait_ele_visible(self, loc, img_desc, timeout=20, frequency=0.5):
        """等待元素可見"""
        try:
            WebDriverWait(self.driver, timeout, frequency).until(EC.visibility_of_element_located(loc))
            log.info("等待:{} - 元素{}可見成功。".format(img_desc, loc))
        except:
            log.exception("等待:{} - 元素{}可見失敗!".format(img_desc, loc))
            self.save_img(img_desc)
            raise

    def get_element(self, loc, img_desc):
        """查詢元素"""
        try:
            ele = self.driver.find_element(*loc)
        except:
            log.exception("查詢:{} - 元素{}失敗!".format(img_desc, loc))
            self.save_img(img_desc)
            raise
        else:
            log.info("查詢:{} - 元素{}成功".format(img_desc, loc))
            return ele

    def click_element(self, loc, img_desc, timeout=20, frequency=0.5):
        """點選元素"""
        self.wait_ele_visible(loc, img_desc, timeout, frequency)
        ele = self.get_element(loc, img_desc)
        try:
            ele.click()
            log.info("點選:{} - 元素{}成功".format(img_desc, loc))
        except:
            log.exception("點選:{} - 元素{}失敗!".format(img_desc, loc))
            self.save_img(img_desc)
            raise

    def input_text(self, loc, value, img_desc, timeout=20, frequency=0.5):
        """在元素中輸入文字"""
        self.wait_ele_visible(loc, img_desc, timeout, frequency)
        ele = self.get_element(loc, img_desc)
        try:
            ele.send_keys(value)
            log.info("輸入:在{} - 元素{}輸入文字值({})成功".format(img_desc, loc, value))
        except:
            log.exception("輸入:在{} - 元素{}輸入文字值({})失敗!".format(img_desc, loc, value))
            self.save_img(img_desc)
            raise

    def save_img(self, img_description):
        """儲存異常截圖"""
        now = time.strftime("%Y-%m-%d %H-%M-%S ", time.localtime())
        img_path = os.path.join(IMG_DIR, now + img_description + '.png')
        try:
            self.driver.save_screenshot(img_path)
        except:
            log.exception("異常截圖失敗!")
        else:
            log.info("異常截圖成功,截圖存放在{}".format(img_path))

​ 以點選click_element()為例,這裡二次封裝時加入了等待操作、日誌輸入、異常截圖,後面點選元素時就直接呼叫click_element()就可以一步到位,不需要再考慮等待、日誌、異常的情況,這裡都已經處理好了,雖然在初期寫基礎頁面會比較耗時,但只要基礎打好,在後續維護工作中會輕鬆很多。以上只是一個示例,可以根據自己的實際需要進行優化。

物件層及邏輯層

​ 物件層存放頁面元素定位,邏輯層存放元素操作方法(頁面功能),元素定位可以根據實際需要,可以單獨放在一個模組來維護,也可以存放在excel中進行集中管理;下面演示的是元素定位和元素操作方法都存放到一個模組中,一個頁面一個模組,後續頁面元素髮生變化,只需要修改在這個模組中修改對應的定位表示式或者操作方法即可。

​ 演示以百度首頁為例:

# baidu_page.py

from selenium.webdriver.common.by import By
from common.basepage import BasePage


class LoginPage(BasePage):

    login_btn = (By.XPATH, '//div[@id="u1"]//a[@name="tj_login"]')  # 登入按鈕
    username_login_btn = (By.ID, 'TANGRAM__PSP_11__footerULoginBtn')    # 使用者名稱登入按鈕
    user_input = (By.ID, 'TANGRAM__PSP_11__userName')  # 使用者資訊輸入框
    pwd_input = (By.ID, 'TANGRAM__PSP_11__password')  # 密碼輸入框
    login_submit = (By.ID, 'TANGRAM__PSP_11__submit')   # 登入提交按鈕

    def login(self, user, pwd):
        """
        百度使用者名稱登入
        :param user: 手機/郵箱/使用者名稱
        :param pwd: 密碼
        :return:
        """
        self.click_element(self.login_btn, '百度-登入')
        self.click_element(self.username_login_btn, '百度登入-使用者名稱登入')
        self.input_text(self.user_input, user, '使用者名稱登入-手機/郵箱/使用者名稱')
        self.input_text(self.pwd_input, pwd, '使用者名稱登入-密碼')
        self.click_element(self.login_submit, '使用者名稱登入-登入')

業務層

​ 用於存放真正的測試用例操作,這裡不會出現元素定位、頁面功能,所有操作都是直接呼叫邏輯層的.

測試用例 = 測試物件的功能 + 測試資料,下面以百度登入為例(用於演示,簡略寫的):

import unittest
import pytest
import ddt
from selenium import webdriver
from PageObjects.baidu_login_page import LoginPage
from testdatas import common_datas as com_d
from testdatas import login_data as lo_d
from common.logging import log


@ddt.ddt
class TestLogin(unittest.TestCase):

    def setUp(self):
        log.info("-------用例前置工作:開啟瀏覽器--------")
        self.driver = webdriver.Chrome()
        self.driver.get(com_d.baidu_url)
        self.driver.maximize_window()

    def tearDown(self):
        self.driver.quit()
        log.info("-------用例後置工作:關閉瀏覽器--------")

    @pytest.mark.smoke
    def test_login_success(self):
        # 用例:登入頁的登入功能
        # 步驟
        LoginPage(self.driver).login(lo_d.success_data['user'], lo_d.success_data['pwd'])
        # 斷言.....

​ 執行結果:

Testing started at 11:50 ...
C:\software\python\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2019.1.3\helpers\pycharm\_jb_unittest_runner.py" --path D:/learn/test/testcases/test_baidu_login.py
Launching unittests with arguments python -m unittest D:/learn/test/testcases/test_baidu_login.py in D:\learn\test\testcases

Process finished with exit code 0
2021-03-14 11:50:47,238-【test_baidu_login.py-->line:27】-INFO:-------用例前置工作:開啟瀏覽器--------
2021-03-14 11:50:51,327-【basepage.py-->line:38】-INFO:等待:百度-登入 - 元素('xpath', '//div[@id="u1"]//a[@name="tj_login"]')可見成功,耗時0:00:00.056843秒
2021-03-14 11:50:51,339-【basepage.py-->line:77】-INFO:查詢:百度-登入 - 元素('xpath', '//div[@id="u1"]//a[@name="tj_login"]')成功
2021-03-14 11:50:51,414-【basepage.py-->line:86】-INFO:點選:百度-登入 - 元素('xpath', '//div[@id="u1"]//a[@name="tj_login"]')成功
2021-03-14 11:50:53,463-【basepage.py-->line:38】-INFO:等待:百度登入-使用者名稱登入 - 元素('id', 'TANGRAM__PSP_11__footerULoginBtn')可見成功,耗時0:00:02.048293秒
2021-03-14 11:50:53,474-【basepage.py-->line:77】-INFO:查詢:百度登入-使用者名稱登入 - 元素('id', 'TANGRAM__PSP_11__footerULoginBtn')成功
2021-03-14 11:50:53,535-【basepage.py-->line:86】-INFO:點選:百度登入-使用者名稱登入 - 元素('id', 'TANGRAM__PSP_11__footerULoginBtn')成功
2021-03-14 11:50:53,576-【basepage.py-->line:38】-INFO:等待:使用者名稱登入-手機/郵箱/使用者名稱 - 元素('id', 'TANGRAM__PSP_11__userName')可見成功,耗時0:00:00.040890秒
2021-03-14 11:50:53,584-【basepage.py-->line:77】-INFO:查詢:使用者名稱登入-手機/郵箱/使用者名稱 - 元素('id', 'TANGRAM__PSP_11__userName')成功
2021-03-14 11:50:53,714-【basepage.py-->line:98】-INFO:輸入:在使用者名稱登入-手機/郵箱/使用者名稱 - 元素('id', 'TANGRAM__PSP_11__userName')輸入文字值(15692004245)成功
2021-03-14 11:50:53,759-【basepage.py-->line:38】-INFO:等待:使用者名稱登入-密碼 - 元素('id', 'TANGRAM__PSP_11__password')可見成功,耗時0:00:00.043882秒
2021-03-14 11:50:53,771-【basepage.py-->line:77】-INFO:查詢:使用者名稱登入-密碼 - 元素('id', 'TANGRAM__PSP_11__password')成功
2021-03-14 11:50:53,925-【basepage.py-->line:98】-INFO:輸入:在使用者名稱登入-密碼 - 元素('id', 'TANGRAM__PSP_11__password')輸入文字值(phang0209)成功
2021-03-14 11:50:53,958-【basepage.py-->line:38】-INFO:等待:使用者名稱登入-登入 - 元素('id', 'TANGRAM__PSP_11__submit')可見成功,耗時0:00:00.031914秒
2021-03-14 11:50:53,969-【basepage.py-->line:77】-INFO:查詢:使用者名稱登入-登入 - 元素('id', 'TANGRAM__PSP_11__submit')成功
2021-03-14 11:50:54,051-【basepage.py-->line:86】-INFO:點選:使用者名稱登入-登入 - 元素('id', 'TANGRAM__PSP_11__submit')成功
2021-03-14 11:50:56,426-【test_baidu_login.py-->line:35】-INFO:-------用例後置工作:關閉瀏覽器--------


Ran 1 test in 9.191s

OK

​ 從輸出日誌來看,每一步操作都清晰可見,出現問題也能快速定位,這些都可以根據實際需要來優化。

相關文章