Python加Selenium自动化测试知乎网站(三)页面对象模式
页面对象(Page Object)模式是做自动化测试中用到的一种模式理念,本质上是为了复用代码和提高可维护性。页面对象是将独立页面封装为一个或多个类,将页面上的元素做统一管理,将页面涉及到的业务点做封装以便不同脚本调用。在测试脚本中也能体现出业务点,而不是千篇一律的点击输入。
比如注册页面,封装出注册的业务方法,并将相应的元素都整合在一起,如果页面发生变化,只需要更新这一个文件即可,而不用到处搜索替换。
但是有些业务是跨页面的,比如保险行业里的填写保单,是分为多个步骤分别对应不同的页面提交数据,相应的页面对象可以组合为一个工作业务流。在调用不同的方法时,也能看得出当前的业务流程。
介绍了页面对象,我们来优化上一篇文章中的代码,将元素和通用方法抽取出来。
上一篇的例子中会涉及到全局的导航栏、话题页面和搜索结果页面,我们分别来定义三个页面对象。
先新建utils/yaml_helper.py, 用来处理yaml文件中管理的元素定义:
import yaml
class YamlHelper:
@classmethod
def load_yaml(cls, file_path):
with open(file_path, "r") as f:
data = yaml.load(f, Loader=yaml.SafeLoader)
return data
然后创建一个package比如叫pages,pages里创建一个文件夹叫elements用来管理页面元素。
定义一个基础类在page_base.py中:
from utils.yaml_helper import YamlHelper
from selenium.webdriver.common.by import By
import os
class PageBase:
def __init__(self, driver):
self._driver = driver
self._elements = None
element_file = os.path.join(os.getcwd(), "pages", "elements", "%s.yaml" % self.__module__.split(".")[-1])
if os.path.exists(element_file):
self._elements = YamlHelper.load_yaml(element_file)
def element(self, name, *args):
by, value = self.format_locator(self._elements[name], *args)
return self._driver.find_element(by, value)
def elements(self, name, *args):
by, value = self.format_locator(self._elements[name], *args)
return self._driver.find_elements(by, value)
def format_locator(self, locator, *args):
web_locators = list(By.__dict__.items())
by = value = None
for k, v in web_locators:
if v == locator["by"]:
by = v
value = locator["value"].format(*args)
break
return by, value
在pages/elements中新建3个yaml文件,填入元素定义:
## topic_page.yaml
focus_topic_button:
by: xpath
value: "//div[@class='TopicActions TopicMetaCard-actions']/button[contains(@class, 'TopicActions-followButton')]"
topics:
by: xpath
value: "//div[@class='List-item TopicFeedItem']/div[@class='ContentItem ArticleItem']"
login_username_field:
by: xpath
value: "//div[@class='SignContainer-content']//input[@name='username']"
popup_close_button:
by: xpath
value: "//button[@class='Button Modal-closeButton Button--plain']"
## header.yaml
search_field:
by: xpath
value: "//div[contains(@class, 'AppHeader-SearchBar')]//input"
search_button:
by: xpath
value: "//div[contains(@class, 'AppHeader-SearchBar')]//button[contains(@class, 'SearchBar-searchButton')]"
## search_result_page.yaml
search_result_title_by_index:
by: xpath
value: "//div[@id='SearchMain']//div[@class='Card SearchResult-Card'][{0}]//h2[@class='ContentItem-title']/a"
新建3个对应的页面对象文件:
## topic_page.py
from pages.page_base import PageBase
class TopicPage(PageBase):
def count_topics_displayed(self):
return self.elements("topics")
def focus_topic_without_login(self):
self.element("focus_topic_button").click()
self.element("login_username_field").send_keys("123456")
self.element("popup_close_button").click()
## header.py
from pages.page_base import PageBase
class Header(PageBase):
def search(self, keyword):
self.element("search_field").send_keys(keyword)
self.element("search_button").click()
## search_result_page.py
from pages.page_base import PageBase
class SearchResultPage(PageBase):
def retrieve_first_result_title(self):
return self.element("search_result_title_by_index", 1).text
于是在第二篇文章中的脚本就可以优化为如下内容:
## 这是加上了页面对象的样例,但仍然有缺点,会继续在后续文章中优化
## 操作步骤仍然是话题页关注并搜索关键词检查内容
from selenium import webdriver
from pages.topic_page import TopicPage
from pages.header import Header
from pages.search_result_page import SearchResultPage
topic_url = "https://www.zhihu.com/topic/19552832/hot"
keyword = "用Django做一个简单的记账网站"
driver = webdriver.Chrome()
driver.get(topic_url)
topic_page = TopicPage(driver)
header = Header(driver)
search_result_page = SearchResultPage(driver)
topics_amount = topic_page.count_topics_displayed()
assert len(topics_amount) >= 5
topic_page.focus_topic_without_login()
header.search(keyword)
first_search_result_title = search_result_page.retrieve_first_result_title()
assert first_search_result_title == keyword
driver.close()
driver.quit()
现在的样例是不是看起来清爽了一些?变得简单了一点,元素能统一管理和维护,提高了脚本可读性,有封装复用可以看到页面跳转。但仍然有缺点,它并不稳定。接下来会描写等待机制以提高稳定性。