Pytest 测试框架
把豆瓣登录的用例结合 Pytest 来编写如下:
import timefrom selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support import expected_conditions as ECfrom selenium.webdriver.support.wait import WebDriverWaitimport pytestdouban_account = {"username":"****", "password":"****"}def login(driver, account): driver.get("https://www.douban.com/") login_frame = driver.find_element_by_css_selector("div.login iframe") WebDriverWait(driver, 10).until(EC.frame_to_be_available_and_switch_to_it(login_frame)) driver.find_element_by_css_selector("li.account-tab-account").click() driver.find_element_by_css_selector("#username").send_keys(account["username"]) driver.find_element_by_css_selector("#password").send_keys(account["password"]) driver.find_element_by_css_selector(".account-form-field-submit").click() time.sleep(2) driver.switch_to.default_content()class TestDouban(object): def setup_class(self): self.driver = webdriver.Chrome() self.driver.maximize_window() def teardown_class(self): self.driver.quit() def test_login(self): login(self.driver, douban_account) assert WebDriverWait(self.driver, 10).until(EC.visibility_of_element_located((By.CLASS_NAME, "nav-user-account")))if __name__ == "__main__": pytest.main()
这时候,如果元素的位置发生了变化,需要修改定位方式,那么所有用到这个元素的测试用例都需要修改,代码维护程度依旧很高,这时候我们就需要用到 Page Object(PO)模式来重构自动化脚本。
0 2PO 模型简介
如果 100 个不同的脚本使用了相同的页面元素,只要该元素做了任何更改,那么就需要改动所有的脚本,不仅耗时,而且容易出错。
为了解决这个问题,我们可以为每个 Web 页面创建一个单独的类文件,封装该 Web 页面的元素以及操作,那么后续需要用到该 Web 页面元素或操作的脚本都可以调用这个类。如果该 Web 页面元素发生了变化,只需要修改相应的类文件即可,这种方式就是页面对象模型(POM),它有助于提高代码的可读性、可维护性和可重用性。
优点:
·减少代码的重复
·提高测试用例的可读性
·提高测试用例的可维护性
PO的实现:将单个页面上所有Web元素和在这些Web元素的操作方法都放在一个Page类文件中(所有的page文件可以统一放到 page_object 模块),而用于验证的测试用例则单独作为测试方法的一部分(可以放到 test_case 模块)。我们以登录豆瓣为例:
文件结构:
示例代码:
1. 首先创建一个 demo_login_page.py,封装登录页面的元素和操作
import timefrom selenium.webdriver.support import expected_conditions as ECfrom selenium.webdriver.support.wait import WebDriverWaitclass LoginPage: def __init__(self, driver): self.driver = driver def open_url(self, url): self.driver.get(url) def login(self, account, password): login_frame = self.driver.find_element(*("css selector", "div.login iframe")) # 切换 iframe WebDriverWait(self.driver, 10).until(EC.frame_to_be_available_and_switch_to_it(login_frame)) self.driver.find_element(*("css selector", "li.account-tab-account")).click() # 点击密码登录 self.driver.find_element(*("id", "username")).send_keys(account) # 输入账号 self.driver.find_element(*("id", "password")).send_keys(password) # 输入密码 self.driver.find_element(*("css selector", ".btn-account")).click() # 点击登录 time.sleep(2) self.driver.switch_to.default_content()
注:find_element(*("css selector", "div.login iframe")) 等同于 find_element_by_css_selector("div.login iframe")
2. 首先创建一个 demo_home_page.py,封装首页的元素和操作(如获取登录账号信息,判断是否登录成功)
from selenium.webdriver.support.wait import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECclass HomePage: def __init__(self, driver): self.driver = driver def is_account_info_exist(self): try: WebDriverWait(self.driver, 30).until(EC.visibility_of_element_located(("class name", "nav-user-account"))) return True except Exception: return False
3. 在 test_demo.py 中编写测试用例,所有的元素获取和操作都调用对应的页面类来实现
import pytestfrom selenium import webdriverfrom po.page_object.demo_home_page import HomePagefrom po.page_object.demo_login_page import LoginPageclass TestLogin(): def setup_class(self): self.driver = webdriver.Chrome() self.driver.maximize_window() self.driver.implicitly_wait(5) def teardown_class(self): self.driver.quit() def test_demo(self): self.login_page = LoginPage(self.driver) self.home_page = HomePage(self.driver) self.login_page.open_url("https://www.douban.com/") self.login_page.login("****", "****") assert self.home_page.is_account_info_exist()if __name__ == "__main__": pytest.main()
这样,无论 login 、home 页面元素如何变化,都只需要修改相应页面中的代码即可,大大降低了自动化脚本的维护成本及因脚本更新引发的出错概率。
0 3优化
PO 模式让我们仅需要在Page页面类单个文件中对变化的元素定位进行更新,但是一个项目一般都有很多个页面,如果页面都发生了变化仍然需要修改多个页面类文件,那是否尽可能使任何一个自动化脚本文件都不发生变更呢?很简单,只需要把这些定位作为数据单独存放在配置文件中即可(配置文件可以是 ini、csv、excel 等),以 ini 配置文件为例
文件结构:
示例代码:
1. page_object 模块用来存放页面操作
demo_login_page.py 用来存放登录页面的操作
import osimport timefrom configparser import ConfigParserfrom selenium.webdriver.support import expected_conditions as ECfrom selenium.webdriver.support.wait import WebDriverWaitclass LoginPage: def get_locator(self): conf = ConfigParser() conf.read(os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + "\\page_locator\\demo_locator.ini", encoding="utf-8") self.url = conf.get("Base", "url") section = "LoginPageLocator" # eval 将字符串元组转换为元组 self.login_frame = eval(conf.get(section, "login_frame")) self.login_by_pwd_btn = eval(conf.get(section, "login_by_pwd_btn")) self.account = eval(conf.get(section, "account")) self.pwd = eval(conf.get(section, "pwd")) self.login_btn = eval(conf.get(section, "login_btn")) def __init__(self, driver): self.driver = driver self.get_locator() def open_url(self): self.driver.get(self.url) def input(self, locator, value): self.driver.find_element(*locator).send_keys(value) def click(self, locator): self.driver.find_element(*locator).click() def change_frame(self, locator): frame = self.driver.find_element(*locator) WebDriverWait(self.driver, 10).until(EC.frame_to_be_available_and_switch_to_it(frame)) def login(self, account, password): self.change_frame(self.login_frame) # 切换iframe self.click(self.login_by_pwd_btn) # 点击密码登录 self.input(self.account, account) # 输入账号 self.input(self.pwd, password) # 输入密码 self.click(self.login_btn) # 点击登录 time.sleep(2) self.driver.switch_to.default_content() # 切换到原来的页面 def logout(self): pass
以上脚本将页面上的不同操作都封装成了方法,使脚本看起来更为简洁,但是如果页面很多,每个页面都要封装点击、输入的操作会特别繁琐,可以把所有的公共方法抽离出来,封装到一个公共模块中,将在下一节详细介绍~~
demo_home_page.py 用来存放home页面的操作
import osfrom configparser import ConfigParserfrom selenium.webdriver.support.wait import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECclass HomePage: def get_locator(self): conf = ConfigParser() conf.read(os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + "\\page_locator\\demo_locator.ini", encoding="utf-8") section = "HomePageLocator" self.account_info = eval(conf.get(section, "account_info")) def __init__(self, driver): self.driver = driver self.get_locator() def is_account_info_exist(self): try: WebDriverWait(self.driver, 30).until(EC.visibility_of_element_located(self.account_info)) return True except Exception: return False
注:ConfigParser 模块用于读取配置文件,用法请自行百度。
2. page_locator 模块用来存放通用地址和页面元素的定位,如 demo_locator.ini 用来存放登录页面的元素定位等信息,可以把多个页面的 locator 都存放在一个 ini 文件中,这样只需要修改 ini 文件即可
[Base]url = https://www.douban.com/;"id"、"xpath"、"link text"、"partial link text"、"name"、"tag name"、"class name"、"css selector"[LoginPageLocator]# iframelogin_frame = ("css selector", "div.login iframe")# 密码登录按钮login_by_pwd_btn = ("css selector", ".account-tab-account")# 账号输入框account = ("id", "username")# 密码输入框pwd = ("id", "password")# 登录豆瓣按钮login_btn = ("css selector", ".btn-account");登录错误信息error_msg = ("css selector", ".account-form-error span")[HomePageLocator]# 账号信息account_info = ("class name", "nav-user-account")
3. test_case 模块用来编写测试用例
import pytestfrom selenium import webdriverfrom po.page_object.demo_home_page import HomePagefrom po.page_object.demo_login_page import LoginPageclass TestLogin(): def setup_class(self): self.driver = webdriver.Chrome() self.driver.maximize_window() self.driver.implicitly_wait(5) def teardown_class(self): self.driver.quit() def test_demo(self): self.login_page = LoginPage(self.driver) self.home_page = HomePage(self.driver) self.login_page.open_url() self.login_page.login("****", "****") assert self.home_page.is_account_info_exist()if __name__ == "__main__": pytest.main()
现在所有的页面元素定位都写到一个 locator.ini 文件中,当页面元素的定位方式发生变化时,只需要修改 locator.ini 一个文件即可,大家可以自行挑选合适的配置文件保存元素定位信息。
链接:https://www.cnblogs.com/sharef/p/13639459.html
本文为51Testing经授权转载,转载文章所包含的文字来源于作者。如因内容或版权等问题,请联系51Testing进行删除
推荐阅读点击阅读☞一文搞定!那些Selenium常见的元素定位
点击阅读☞切换窗口时,用Selenium定位元素超方便
点击阅读☞用Selenium通过豆瓣的滑动验证码,超简单!
点击阅读☞随时可以调用公共方法的Selenium项目实战
点击阅读☞Selenium那些值得再次巩固的知识
戳