web自动化05-PO模式-V4-V6
PO模式介绍
1. 深入理解PO模式的思想2. 熟练掌握PO模式的分层思想
1. 存在的问题
在做UI自动化时定位元素特别依赖页面,一旦页面发生变更就不得不跟着去修改定位元素的代码。
举例:假设要对一个元素进行点击操作,而且会经常对该元素进行操作,那么你就可能会编写多
处如下代码
driver.find_element_by_id("login-btn").click()
存在的问题:
- 如果开发人员修改了这个元素的id,这时候你就不得不修改所有对应的代码
- 存在大量冗余代码
思考:如何来解决这个问题呢?
2. PO模式
PO是Page Object的缩写,PO模式是自动化测试项目开发实践的最佳设计模式之一。
核心思想是通过对界面元素的封装减少冗余代码,同时在后期维护中,若元素定位发生变化, 只
需要调整页面元素封装的代码,提高测试用例的可维护性、可读性。
PO模式可以把一个页面分为三层,对象库层、操作层、业务层。
- 对象库层:封装定位元素的方法。
- 操作层:封装对元素的操作。
- 业务层:将一个或多个操作组合起来完成一个业务功能。比如登录:需要输入帐号、密码、点击登录三个操作。
2.1 引入PO模式的好处
引入PO模式前
-
- 存在大量冗余代码
- 业务流程不清晰
- 后期维护成本大
引入PO模式后
-
- 减少冗余代码
- 业务代码和测试代码被分开,降低耦合性
- 维护成本低
PO模式实践
1. V4版本
采用PO模式的分层思想对代码进行拆分
1.1 PO分层封装
对登录页面进行分层封装:
- 对象库层:LoginPage
- 操作层:LoginHandle
- 业务层:LoginProxy
调用业务层的方法,编写测试用例:
- 测试用例:TestLogin
1.2 示例代码
from po.utils import DriverUtil class LoginPage: """ 对象库层 """ def __init__(self): self.driver = DriverUtil.get_driver() # 用户名输入框 self.username = None # 密码 self.password = None # 验证码输入框 self.verify_code = None # 登录按钮 self.login_btn = None # 忘记密码 self.forget_pwd = None def find_username(self): return self.driver.find_element_by_id("username") def find_password(self): return self.driver.find_element_by_id("password") def find_verify_code(self): return self.driver.find_element_by_id("verify_code") def find_login_btn(self): return self.driver.find_element_by_name("sbtbutton") def find_forget_pwd(self): return self.driver.find_element_by_partial_link_text("忘记密码") class LoginHandle: """ 操作层 """ def __init__(self): self.login_page = LoginPage() def input_username(self, username): self.login_page.find_username().send_keys(username) def input_password(self, pwd): self.login_page.find_password().send_keys(pwd) def input_verify_code(self, code): self.login_page.find_verify_code().send_keys(code) def click_login_btn(self): self.login_page.find_login_btn().click() def click_forget_pwd(self): self.login_page.find_forget_pwd().click() class LoginProxy: """ 业务层 """ def __init__(self): self.login_handle = LoginHandle() # 登录 def login(self, username, password, verify_code): # 输入用户名 self.login_handle.input_username(username) # 输入密码 self.login_handle.input_password(password) # 输入验证码 self.login_handle.input_verify_code(verify_code) # 点击登录按钮 self.login_handle.click_login_btn() # 跳转到忘记密码页面 def to_forget_pwd_page(self): # 点击忘记密码 self.login_handle.click_forget_pwd() import unittest from po import utils from po.utils import DriverUtil from po.v4.page.login_page import LoginProxy class TestLogin(unittest.TestCase): """ 对登录模块的功能进行测试 """ @classmethod def setUpClass(cls): cls.driver = DriverUtil.get_driver() cls.login_proxy = LoginProxy() @classmethod def tearDownClass(cls): DriverUtil.quit_driver() def setUp(self): # 打开首页 self.driver.get("http://localhost") # 点击首页的‘登录’链接,进入登录页面 self.driver.find_element_by_link_text("登录").click() # 账号不存在 def test_login_username_is_error(self): self.login_proxy.login("13099999999", "123456", "8888") # 断言提示信息 msg = utils.get_tips_msg() print("msg=", msg) self.assertIn("账号不存在", msg) # 密码错误 def test_login_password_is_error(self): self.login_proxy.login("13012345678", "123456", "8888") # 断言提示信息 msg = utils.get_tips_msg() print("msg=", msg) self.assertIn("密码错误", msg)
2. V5版本
对PO分层之后的代码继续优化
1. 优化对象库层的代码,抽取元素的定位方式,把定位信息定义在对象的属性中,便于集中管理
2. 优化操作层的代码,针对输入操作应该先清空输入框中的内容再输入新的内容
from selenium.webdriver.common.by import By from po.utils import DriverUtil class LoginPage: """ 对象库层 """ def __init__(self): self.driver = DriverUtil.get_driver() # 用户名 self.username = (By.ID, "username") # 密码 self.password = (By.ID, "password") # 验证码输入框 self.verify_code = (By.ID, "verify_code") # 登录按钮 self.login_btn = (By.NAME, "sbtbutton") # 忘记密码 self.forget_pwd = (By.PARTIAL_LINK_TEXT, "忘记密码") def find_username(self): return self.driver.find_element(self.username[0], self.username[1]) def find_password(self): return self.driver.find_element(self.password[0], self.password[1]) def find_verify_code(self): return self.driver.find_element(self.verify_code[0], self.verify_code[1]) def find_login_btn(self): return self.driver.find_element(self.login_btn[0], self.login_btn[1]) def find_forget_pwd(self): return self.driver.find_element(self.forget_pwd[0], self.forget_pwd[1]) class LoginHandle: """ 操作层 """ def __init__(self): self.login_page = LoginPage() def input_username(self, username): self.login_page.find_username().clear() self.login_page.find_username().send_keys(username) def input_password(self, pwd): self.login_page.find_password().clear() self.login_page.find_password().send_keys(pwd) def input_verify_code(self, code): self.login_page.find_verify_code().clear() self.login_page.find_verify_code().send_keys(code) def click_login_btn(self): self.login_page.find_login_btn().click() def click_forget_pwd(self): self.login_page.find_forget_pwd().click() class LoginProxy: """ 业务层 """ def __init__(self): self.login_handle = LoginHandle() # 登录 def login(self, username, password, verify_code): # 输入用户名 self.login_handle.input_username(username) # 输入密码 self.login_handle.input_password(password) # 输入验证码 self.login_handle.input_verify_code(verify_code) # 点击登录按钮 self.login_handle.click_login_btn() # 跳转到忘记密码页面 def to_forget_pwd_page(self): # 点击忘记密码 self.login_handle.click_forget_pwd()
PO模式深入封装
1. 能够采用继承的思想对PO模式进行深入的封装
1. V6版本
把共同操作提取封装到父类中,子类直接调用父类的方法,避免代码冗余
1. 对象库层-基类,把定位元素的方法定义在基类中
2. 操作层-基类,把对元素执行输入操作的方法定义在基类中
1.1 示例代码
# base_page.py from po.utils import DriverUtil class BasePage: """ 基类-对象库层 """ def __init__(self): self.driver = DriverUtil.get_driver() def find_element(self, location): return self.driver.find_element(location[0], location[1]) class BaseHandle: """ 基类-操作层 """ def input_text(self, element, text): """ 在输入框里输入文本内容,先清空再输入 :param element: 要操作的元素 :param text: 要输入的文本内容 """ element.clear() element.send_keys(text)
from selenium.webdriver.common.by import By from po.v6.common.base_page import BasePage, BaseHandle class LoginPage(BasePage): """ 对象库层 """ def __init__(self): super().__init__() # 用户名输入框 self.username = (By.ID, "username") # 密码 self.password = (By.ID, "password") # 验证码 self.verify_code = (By.ID, "verify_code") # 登录按钮 self.login_btn = (By.NAME, "sbtbutton") # 忘记密码 self.forget_pwd = (By.PARTIAL_LINK_TEXT, "忘记密码") def find_username(self): return self.find_element(self.username) def find_password(self): return self.find_element(self.password) def find_verify_code(self): return self.find_element(self.verify_code) def find_login_btn(self): return self.find_element(self.login_btn) def find_forget_pwd(self): return self.find_element(self.forget_pwd) class LoginHandle(BaseHandle): """ 操作层 """ def __init__(self): self.login_page = LoginPage() def input_username(self, username): self.input_text(self.login_page.find_username(), username) def input_password(self, pwd): self.input_text(self.login_page.find_password(), pwd) def input_verify_code(self, code): self.input_text(self.login_page.find_verify_code(), code) def click_login_btn(self): self.login_page.find_login_btn().click() def click_forget_pwd(self): self.login_page.find_forget_pwd().click() class LoginProxy: """ 业务层 """ def __init__(self): self.login_handle = LoginHandle() # 登录 def login(self, username, password, verify_code): # 输入用户名 self.login_handle.input_username(username) # 输入密码 self.login_handle.input_password(password) # 输入验证码 self.login_handle.input_verify_code(verify_code) # 点击登录按钮 self.login_handle.click_login_btn() # 跳转到忘记密码页面 def to_forget_pwd_page(self): # 点击忘记密码 self.login_handle.click_forget_pwd()