100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > 基于PageObject模式设计的web自动化测试示例

基于PageObject模式设计的web自动化测试示例

时间:2019-11-04 02:53:02

相关推荐

基于PageObject模式设计的web自动化测试示例

PageObject模式

PageObject模式:顾名思义,就是页面对象。它的核心思想是分层设计, 强调测试、逻辑、数据和驱动相互分离。一般分层会分为:

1.对象库层

2.逻辑层

3.业务层

4.数据层

但是,具体分层,还是要根据系统去设计。

目录结构

下面,是基于PageObject模式,设计课堂派的登录测试。先说一下目录结构:

Common:存放公共封装类,公共配置文件。Outputs:存放输出,日志、截图、测试报告等。PageLocators:存放各个界面的定位。PageObjects:存放各个界面的方法。TestCases:存放测试用例。TestDatas:存放测试数据。main.py:用例执行入口文件。

下面根据目录顺序,说明具体内容:

Common目录:

basepage.py,主要是公共方法的封装,比如元素等待、查找、点击等,并捕获异常,输出日志、截图信息等。具体代码如下:

import osfrom selenium.webdriver.remote.webdriver import WebDriverfrom selenium.webdriver.support import expected_conditions as ECfrom selenium.webdriver.support.wait import WebDriverWaitfrom datetime import datetimefrom Common.handle_logging import do_logfrom Common.dir_config import screenshot_dir"""目标:封装基本关键字,公共使用方法,对任何一个页面操作都可以实时捕捉异常,输出操作日志,失败截图"""class BasePage:def __init__(self, driver: WebDriver):self.driver = driver# 等待元素可见方法封装def wait_element_visible(self, loc, img_doc, timeout=20, frequency=0.5):do_log.info("在{}等待元素{}可见".format(img_doc, loc))start_time = datetime.now()try:WebDriverWait(self.driver, timeout, frequency).until(EC.visibility_of_element_located(loc))except Exception as e:# 要异常截图 - 通过截图名称,知道是那个页面,那个模块失败的。self.save_screenshot(img_doc)# 异常日志捕获do_log.error("等待元素{}可见失败。".format(loc))# 抛出异常raise eelse:do_log.info("等待元素{}可见成功。".format(loc))end_time = datetime.now()do_log.info("等待时长为:{}".format((end_time-start_time).seconds))# 等待元素存在方法封装def wait_page_contains_element(self, loc, img_doc, timeout=20, frequency=0.5):do_log.info("在{}等待元素{}存在。".format(img_doc, loc))start_time = datetime.now()try:WebDriverWait(self.driver, timeout, frequency).until(EC.presence_of_element_located(loc))except Exception as e:self.save_screenshot(img_doc)do_log.error("等待元素{}存在失败。".format(loc))raise eelse:do_log.info("等待元素{}存在成功。".format(loc))end_time = datetime.now()do_log.info("等待的时长为:{}".format((end_time-start_time).seconds))# 获取元素方法封装def get_element(self, loc, img_doc):"""查找元素。loc:元素定位img_doc: 图片描述"""do_log.info("在{}查找元素{}".format(img_doc, loc))start_time = datetime.now()try:ele = self.driver.find_element(*loc)except Exception as e:self.driver.save_screenshot(img_doc)do_log.error("查找元素{}失败。".format(loc))raise eelse:do_log.error("查找元素{}成功。".format(loc))end_time = datetime.now()do_log.info("查找元素的时长为:{}".format((end_time - start_time).seconds))return ele# 点击元素方法封装def click_element(self, loc, img_doc, timeout=20, frequency=0.5):"""前提:元素可见,找到元素"""self.wait_element_visible(loc, img_doc, timeout, frequency)ele = self.get_element(loc, img_doc)do_log.info("在{}点击元素{}".format(img_doc, loc))try:ele.click()do_log.info("元素{}点击成功。".format(loc))except Exception as e:self.save_screenshot(img_doc)do_log.error("元素{}点击失败。".format(loc))raise e# 输入内容方法封装def input_text(self, loc, img_doc, text, timeout=20, frequency=0.5):"""前提:元素可见,找到元素"""self.wait_element_visible(loc, img_doc, timeout, frequency)ele = self.get_element(loc, img_doc)do_log.info("在{}的输入框{},输入:{}".format(img_doc, loc, text))try:ele.send_keys(text)do_log.info("元素{}输入内容{}成功".format(loc, text))except Exception as e:self.save_screenshot(img_doc)do_log.error("元素{}输入文本失败。".format(loc))raise e# 获取元素的文本内容方法封装def get_element_text(self, loc, img_doc, timeout=20, frequency=0.5):"""前提:元素存在,找到元素"""self.wait_page_contains_element(loc, img_doc, timeout, frequency)ele = self.get_element(loc, img_doc)do_log.info("在{}的获取元素{}的文本值".format(img_doc, loc))try:text = ele.textexcept Exception as e:self.save_screenshot(img_doc)do_log.error("获取文本值失败。")raise eelse:do_log.info("获取元素{}文本内容成功,获取到的文本元素内容为:{}".format(loc, text))return text# 获取元素属性的方法封装def get_element_attr(self, loc, attr_name, img_doc, timeout=20, frequency=0.5):"""前提:元素存在,找到元素"""self.wait_page_contains_element(loc, img_doc, timeout, frequency)ele = self.get_element(loc, img_doc)do_log.info("在{}获取元素{}的属性{}。".format(img_doc, loc, attr_name))try:value = ele.get_attribute(attr_name)except Exception as e:self.save_screenshot(img_doc)do_log.error("获取元素{}属性值失败。".format(loc))raise eelse:do_log.info("获取到的元素{}属性值成功,属性值为:{}".format(loc, value))return value# 保存截图的方法def save_screenshot(self, img_doc):# 存储到指定目录下# filename = screenshot_dir + "{}_{}.png".format(datetime.strftime(datetime.now(), '%Y_%m_%d_%H_%M_%S'), img_doc)filename = os.path.join(screenshot_dir,"{}_{}.png".format(datetime.strftime(datetime.now(), '%Y_%m_%d_%H_%M_%S'), img_doc))self.driver.save_screenshot(filename)do_log.info("页面截图文件,保存在{}".format(filename))

handle_logging.py,日志的封装,定义日志输出信息,主要代码如下:

import loggingimport osfrom datetime import datetimefrom mon_conf import logger_name, log_filename, logger_level, console_level, simple_formatter, file_level, verbose_formatterfrom Common.dir_config import logs_dirclass HandleLog:"""封装日志处理的类"""def __init__(self):# 定义日志收起器, 创建logger对象self.case_logger = logging.getLogger(logger_name)# 日志等级 NOTSET(0), DEBUG(10), INFO(20), WARNING(30), ERROR(40), CRITICAL(50)# 设置之后,只能收集当前等级及以上的日志信息。如设置为warning级别,只能手机warning、error、critical等级的# case_logger.setLevel(logging.DEBUG)self.case_logger.setLevel(logger_level)# 定义日志输出渠道# 输出到控制台console_handler = logging.StreamHandler(console_level)# 输出到文件print("log输出路径:{}".format(logs_dir))log_name = os.path.join(logs_dir, datetime.strftime(datetime.now(), '%Y_%m_%d_%H_%M_%S') + log_filename)file_handler = logging.FileHandler(log_name,encoding='utf-8')# 日志输出渠道的等级, 日志输出的等级,不能高于收集器的等级# console_handler.setLevel(logging.ERROR) 与 console_handler.setLevel("DEBUG")等价console_handler.setLevel(console_level)file_handler.setLevel(file_level)# 定义日志显示的格式# 简单格式simple = logging.Formatter(simple_formatter)# 稍微详细的格式verbose = logging.Formatter(verbose_formatter)# 控制台显示简单日志console_handler.setFormatter(simple)# 文件显示稍微详细的日志file_handler.setFormatter(verbose)# 将日志收集器与输出渠道对接self.case_logger.addHandler(console_handler)self.case_logger.addHandler(file_handler)def get_logger(self):return self.case_logger# 创建对象,可以直接调用do_log = HandleLog().get_logger()if __name__ == "__main__":do_log.debug("debug")

common_conf.py,存放公共的配置信息,比如,我这里只配置了log的设置:

# 日志收集器名称logger_name = "case_log"# 日志收集器的级别logger_level = "DEBUG"# 输出的日志名称log_filename = "cases.log"# 输出到控制台的日志级别console_level = "ERROR"# 输出到日志文 件中的级别file_level = "DEBUG"# 日志输出内容simple_formatter = "%(asctime)s - [%(levelname)s] - [msg]: %(message)s"verbose_formatter = "%(asctime)s - [%(levelname)s] - %(lineno)d - %(name)s - [msg]: %(message)s"

dir_config.py,项目路径的配置文件:

import os# 项目根目录base_dir = os.path.split(os.path.split(os.path.abspath(__file__))[0])[0]print(base_dir)# 截图存放地址screenshot_dir = os.path.join(base_dir, "Outputs\\screenshots")# 日志存放地址logs_dir = os.path.join(base_dir, "Outputs\\logs")# 报告存放地址reports_dir = os.path.join(base_dir, "Outputs\\reports")# 测试用例存放地址test_cases_dir = os.path.join(base_dir, "TestCases")

Outputs目录

logs存放日志文件reports存放测试报告screenshots存放错误截图

PageLocators

login_page_locators.py,登录界面的元素定位:

from mon.by import Byclass LoginPageLocators:"""账号登录的定位"""# 账户输入框user_input_box = (By.XPATH, '//input[@class="el-input__inner" and @placeholder="请输入邮箱/手机号/账号"]')# 密码输入框password_input_box = (By.XPATH, '//input[@class="el-input__inner" and @placeholder="请输入密码"]')# 登录按钮login_button = (By.XPATH, '//div[@class="login-tab"]/div/div/button')# 不输入密码时的提示信息: 请输入密码 的定位not_input_password_err_msg = (By.XPATH, '//*[@class="el-form-item__error"]')# 不输入账户时弹出的提示信息:用户名不能为空 的定位user_is_null_msg = (By.XPATH, '//*[text()="用户名不能为空"]')# 输入不存在的用户时,弹出的提示信息:用户不存在 的定位user_not_exist_msg = (By.XPATH, '//*[text()="用户不存在"]')

main_page_locators.py, 主页元素定位:

from mon.by import Byclass MainPageLocator:# 用户头像的元素定位user_logo = (By.XPATH, '//img[@class="avatar"]')

PageObjects

login_page.py,登录界面的操作方法:

from PageLocators.login_page_locators import LoginPageLocatorsfrom Common.basepage import BasePagefrom mon.keys import Keysclass LoginPage(BasePage):def account_login(self, username, password):# 账户登录,登录方法# 输入用户名self.input_text(LoginPageLocators.user_input_box, "登录界面_账户登录_账户输入框", username)# 输入密码self.input_text(LoginPageLocators.password_input_box, "登录界面_账户登录_密码输入框", password)# 点击 登录按钮# self.click_element(LoginPageLocators.login_button, "登录界面_账户登录_登录按钮")# 点击enter键self.get_element(LoginPageLocators.login_button, "登录界面_账户登录_点击enter").send_keys(Keys.ENTER)def get_no_password_err_msg(self):"""不输入密码时,会出现文本提示信息:请输入密码"""text = self.get_element_text(LoginPageLocators.not_input_password_err_msg, "登录页_不输入密码_提示信息")# 返回错误提示信息return textdef get_alert_err_msg(self, locator):"""不输入用户名、密码错误、账户不存在时,点击登录,会弹出提示框提示,获取提示框中的错误信息"""text = self.get_element_text(locator, "登录页_不输入用户名、密码错误、账户不存在_弹出错误信息")# 返回错误提示信息return text

main.py,主页中的操作方法:

from Common.basepage import BasePagefrom PageLocators.main_page_locator import MainPageLocatorclass MainPage(BasePage):def if_user_login_is_exist(self):"""判断元素是否存在,存在返回True, 不存在返回False:return boolean"""try:self.wait_element_visible(MainPageLocator.user_logo, "主页_用户头像元素")except TimeoutError:return Falseelse:return True

TestCases

test_login.py,存放测试登录的用例(设计了4条用例):

import unittestfrom selenium import webdriverfrom PageLocators.login_page_locators import LoginPageLocatorsfrom PageObjects.login_page import LoginPagefrom PageObjects.main_page import MainPagefrom TestDatas import common_datas as cdfrom TestDatas import login_datas as ldfrom Common.handle_logging import do_logclass TestLogin(unittest.TestCase):def setUp(self) -> None:do_log.info("打开浏览器")# 打开谷歌浏览器,访问课堂派self.driver = webdriver.Chrome()self.driver.maximize_window()self.driver.get(cd.login_url)self.lp = LoginPage(self.driver)def tearDown(self) -> None:do_log.info("退出浏览器")# 退出浏览器会话self.driver.quit()def test_login_success(self):"""测试场景:登录成功"""do_log.info("执行用例:test_login_success")self.lp.account_login(ld.normal_datas['user'], ld.normal_datas['passwd'])self.assertTrue(MainPage(self.driver).if_user_login_is_exist())def test_login_no_password(self):"""测试场景:不输入密码"""do_log.info("执行用例:test_login_no_password")self.lp.account_login(ld.login_no_password_datas['user'], ld.login_no_password_datas['passwd'])self.assertEqual(ld.login_no_password_datas['check'], self.lp.get_no_password_err_msg())def test_login_no_user(self):"""测试场景:不输入账户"""do_log.info("执行用例:test_login_no_user")self.lp.account_login(ld.login_no_user_datas['user'], ld.login_no_user_datas['passwd'])self.assertTrue(ld.login_no_user_datas['check'], self.lp.get_alert_err_msg(LoginPageLocators.user_is_null_msg))def test_login_user_not_exist(self):"""测试场景:输入的账户不存在"""do_log.info("执行用例:test_login_user_not_exist")self.lp.account_login(ld.login_user_not_exist['user'], ld.login_user_not_exist['passwd'])self.assertTrue(ld.login_user_not_exist['check'], self.lp.get_alert_err_msg(LoginPageLocators.user_not_exist_msg))

TestDatas

common_datas.py, 公共数据:

# 登录地址login_url = '登录地址'# 登录信息test_datas = {"user": "账户信息", "passwd": "密码"}

# 正常场景的数据normal_datas = {"user": "账户", "passwd": "密码"}# 异常场景的数据login_no_password_datas = {"user": "账户", "passwd": "", "check": "请输入密码"}login_no_user_datas = {"user": "", "passwd": "密码", "check": "用户名不能为空"}login_user_not_exist = {"user": "不存在的账户", "passwd": "密码", "check": "用户不存在"}

main.py

入口文件:

#!/usr/bin/env python# -*- coding:utf-8 -*-# @Time : /10/16 22:03# @Author : admin# @File : main.py# @Software: PyCharmimport unittestimport osfrom datetime import datetimefrom HTMLTestRunner import HTMLTestRunnerfrom Common.dir_config import reports_dir, test_cases_dir# 指定路径下查找用例, 可以输入. 代表当前目录suit = unittest.defaultTestLoader.discover(test_cases_dir)"""以日期命名报告,避免覆盖"""str_now = datetime.strftime(datetime.now(), '%Y_%m_%d_%H_%M_%S')print("report的输出路径:{}".format(reports_dir))name = os.path.join(reports_dir, str_now +"_report.html")report = open(name, mode='wb')"""verbosity 代表报告的详细程度, 0 report 2 , 2是最详细的"""runner = HTMLTestRunner(stream=report, title='课堂派的web的报告', verbosity=2, description='测试课堂派的web报告')runner.run(suit)report.close()

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。