Selenium3与Python3实战 Web自动化测试框架
一、项目实战中PO模型的设计与封装
一般将所有的元素、数据都放在代码中,并不利于自动化代码的维护。最好的方式是尽量把数据、页面、操作进行分离开:PO设计模式
PO设计模式的优势:
- PO提供了一种业务流程与页面元素操作分离的模式,这使得测试代码变得更加清晰。
- 页面对象与用例分离,使得我们更好的复用对象。
- 可复用的页面方法代码会变得更加优化
- 更加有效的命名方式使得我们更加清晰的知道方法所操作的UI元素
1、使用PO模式实现注册页面封装
1)关于配置文件
LocalElement.py:
[RegisterElement] user_email=id>register_email user_email_error=id>register_email-error user_name=id>register_nickname user_name_error=id>register_nickname-error password=id>register_password password_error=id>register_password-error code_image=id>getcode_num code_text=id>captcha_code code_text_error=id>captcha_code-error register_button=id>register-btn
setting.py:
import os base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # 项目首路径 codeerror_path = os.path.join(base_dir,'Image','codeerror.png') # 验证码错误图片路径 code_path = os.path.join(base_dir,'Image','code.png') # 验证码图片保存路径 config_ini_dir = os.path.join(base_dir,'setting','localElement.ini') # localElement.ini 配置文件路径
2)read.ini.py :用于读取LocalElement.py文件中的配置信息
from setting.setting import config_ini_dir import configparser class Read_Ini(object): # 初始化 def __init__(self,node = None): if node: self.node = node else: self.node = 'RegisterElement' # 配置文件中的某个节点 self.cf = self.load_ini() def load_ini(self): # 加载文件 cf = configparser.ConfigParser() # 使用 configparser模块读取配置文件信息 cf.read(config_ini_dir) # 配置文件所在路径 return cf def get_value(self,key): # 获取配置文件中key的value值 data = self.cf.get(self.node,key) return data
3)base包下新建find_element.py文件:通过read_ini.py文件获取配置文件的信息,用于定位注册页面的目标元素
from util.read_ini import Read_Ini class FindElement(object): """获取元素所在位置""" def __init__(self,driver): self.driver = driver def get_element(self,key): read_ini = Read_Ini() data = read_ini.get_value(key) by,value = data.split('>') try: if by == 'id': return self.driver.find_element_by_id(value) elif by == 'name': return self.driver.find_element_by_name(value) elif by == 'className': return self.driver.find_element_by_class_name(value) elif by == 'xpath': return self.driver.find_element_by_xpath(value) else: return self.driver.find_element_by_css(value) except Exception as e: # print("find_element错误信息:",e) return None
4)page包新建register_page.py文件: 通过find_element.py文件,获取目标元素所在的位置
from base.find_element import FindElement class RegisterPage(object): """获取元素所在位置""" def __init__(self,driver): self.fd = FindElement(driver) #获取邮箱元素 def get_email_element(self): return self.fd.get_element("user_email") #获取用户名元素 def get_username_element(self): return self.fd.get_element("user_name") #获取密码元素 def get_password_element(self): return self.fd.get_element("password") #获取验证码元素 def get_code_element(self): return self.fd.get_element("code_text") #获取注册按钮元素 def get_button_element(self): return self.fd.get_element("register_button") #获取邮箱错误元素 def get_email_error_element(self): return self.fd.get_element("user_email_error") #获取用户名错误元素 def get_name_error_element(self): return self.fd.get_element("user_name_error") #获取密码错误元素 def get_password_error_element(self): return self.fd.get_element("password_error") #获取验证码错误元素 def get_code_error_element(self): return self.fd.get_element("code_text_errorr")
5)handle包下新建register_handle.py文件:结合register_page.py文件获取目标元素位置,再自动输入对应信息
#coding=utf-8 from page.register_page import RegisterPage from util.get_code_value import GetCode class RegisterHandle(object): """打开页面后自动输入相应信息""" def __init__(self,driver): self.driver = driver self.register_p = RegisterPage(self.driver) #输入邮箱 def send_user_email(self,email): # self.loger.info("输入的邮箱值是:"+email) self.register_p.get_email_element().send_keys(email) #输入用户名 def send_user_name(self,username): # self.loger.info("输入的用户名是:"+username) self.register_p.get_username_element().send_keys(username) #输入密码 def send_user_password(self,password): # self.loger.info("输入的密码是:"+password) self.register_p.get_password_element().send_keys(password) #输入验证码 def send_user_code(self,file_name): get_code_text = GetCode(self.driver) code = get_code_text.code_online(file_name) self.register_p.get_code_element().send_keys(code) #获取文字信息 def get_user_text(self,info,user_info): try:# 容错处理 if info == 'user_email_error': text = self.register_p.get_email_error_element().text # 获取邮箱错误信息 elif info == 'user_name_error': text = self.register_p.get_name_error_element().text # 获取用户名错误信息 elif info == 'password_error': text = self.register_p.get_password_error_element().text # 获取用户密码错误信息 else: text = self.register_p.get_code_error_element().text # 获取验证码错误信息 except: text = None return text #点击注册按钮 def click_register_button(self): self.register_p.get_button_element().click() #获取注册按钮文字 def get_register_btn_text(self): """如获取不到信息,表明页面已成功跳转""" return self.register_p.get_button_element().text
6)business包新建register_business.py文件:测试注册页面form表单功能逻辑
from handle.register_handle import RegisterHandle class RegisterBusiness: """测试注册页面form表单功能情况""" def __init__(self,driver): self.register_h = RegisterHandle(driver) def user_base(self,email,name,password,file_name): self.register_h.send_user_email(email) self.register_h.send_user_name(name) self.register_h.send_user_password(password) self.register_h.send_user_code(file_name) self.register_h.click_register_button() def register_succes(self): if self.register_h.get_register_btn_text() == None: # 注册成功 return True else: return False # 邮箱错误 def login_email_error(self,email,name,password,file_name): self.user_base(email,name,password,file_name) if self.register_h.get_user_text('email_error',"请输入有效的电子邮件地址") == None: #print("无错误,邮箱检验不成功") return True else: return False def login_name_error(self,email,name,password,file_name): self.user_base(email,name,password,file_name) if self.register_h.get_user_text('user_name_error',"字符长度必须大于等于4,一个中文字算2个字符") == None: #print("用户名检验不成功") return True else: return False # 密码错误 def login_password_error(self,email,name,password,file_name): self.user_base(email,name,password,file_name) if self.register_h.get_user_text('password_error',"最少需要输入 5 个字符") == None: #print("密码检验不成功") return True else: return False # 验证码错误 def login_code_error(self,email,name,password,file_name): self.user_base(email,name,password,file_name) if self.register_h.get_user_text('code_text_error',"验证码错误") == None: #print("验证码检验不成功") return True else: return False
7)case包新建first_case.py:测试注册页面form表单功能
from selenium import webdriver from setting import setting from business.register_business import RegisterBusiness class FirstCase(object): def __init__(self, file_name, url): self.driver = webdriver.Chrome() self.driver.get(url) self.driver.maximize_window() self.file_name = file_name self.login = RegisterBusiness(self.driver) def test_login_success(self): login_success = self.login.register_succes() if login_success: print("注册成功,case调试失败!") def test_login_email_error(self): email_error = self.login.login_email_error('[email protected]','aaaa','111111',self.file_name) if email_error == True: print("Error: email没有错误提示,此条case执行失败!") def test_login_username_error(self): self.driver.refresh() username_error = self.login.login_name_error('[email protected]','bbbbb','111111',self.file_name) if username_error == True: print("Error: username没有错误提示,此条case执行失败!") def test_login_password_error(self): self.driver.refresh() password_error = self.login.login_password_error('[email protected]','ccccc','111111',self.file_name) if password_error == True: print("Error: password没有错误提示,此条case执行失败!") def test_login_code_error(self): self.driver.refresh() code_error = self.login.login_code_error('[email protected]','dddddddd','111111',self.file_name) if code_error == True: print("Error: password没有错误提示,此条case执行失败!") def main_run(self): self.test_login_email_error() self.test_login_username_error() self.test_login_password_error() self.test_login_code_error() self.driver.close() if __name__ == '__main__': file_name = setting.code_path url = 'http://www.5itest.cn/register' first_case = FirstCase(file_name,url) first_case.main_run()
代码递进方向:
read.ini.py → find_element.py → register_page.py → register_handle.py → register_business.py → first_case.py
附例:
get_code_value.py:获取注册页面图片,截取验证码图片部分区域,识别验证码图片,获取验证码内容
from PIL import Image from setting.ShowapiRequest import ShowapiRequest import time class GetCode: """获取验证码图片,解析验证码图片并返回验证码值""" def __init__(self, driver): self.driver = driver def get_code_image(self, file_name): self.driver.save_screenshot(file_name) code_element = self.driver.find_element_by_id("getcode_num") left = code_element.location['x'] top = code_element.location['y'] right = code_element.size['width'] + left height = code_element.size['height'] + top im = Image.open(file_name) img = im.crop((left, top, right, height)) img.save(file_name) time.sleep(1) # 解析图片获取验证码 def code_online(self, file_name): self.get_code_image(file_name) r = ShowapiRequest("http://route.showapi.com/184-4", "62626", "d61950be50dc4dbd9969f741b8e730f5") r.addBodyPara("typeId", "35") r.addBodyPara("convert_to_jpg", "0") r.addFilePara("image", file_name) # 文件上传时设置 res = r.post() # print("test:",res.text) time.sleep(1) text = res.json()['showapi_res_body'] # print(text) try: code = text['Result'] return code except Exception as e: print('code_error:',e) return None
二、Unittest介绍及项目实战中的运用
1、Unittest简单使用:
import unittest class FirstCase01(unittest.TestCase): @classmethod def setUpClass(cls): print("所有case执行之前的前置") @classmethod def tearDownClass(cls): print("所有case执行之后的后置") def setUp(self): print("这个是case的前置条件") def tearDown(self): print("这个是case的后置调键") @unittest.skip("不执行第一条") # 跳过此条case 不执行 def testfirst01(self): print("这个第一条case") def testfirst02(self): print("这是第二条case") def testfirst03(self): print("这是第3条case") if __name__ == '__main__': #unittest.main() suite = unittest.TestSuite() # suite容器 suite.addTest(FirstCase01('testfirst02')) suite.addTest(FirstCase01('testfirst01')) suite.addTest(FirstCase01('testfirst03')) unittest.TextTestRunner().run(suite)
# suite :容器 ,结果是集合类型 suite = unittest.defaultTestLoader.discover(case_path,'unittest_*.py') # 批量选择性运行case #三个参数:第一个传入路径;第二个匹配文件名,成功则将该文件内指定的case存入suite容器中;第三个参数默认为None unittest.TextTestRunner().run(suite) # 执行上述匹配成功的case assertFalse(code_error, "msg") # 用于判断结果
2、在项目中用HTMLTestRunner输出漂亮的HTML报告
copy下述HTMLTestRunner代码,在项目中新建 HTMLTestRunner.py文件,将代码copy到里面即可使用
""" A TestRunner for use with the Python unit testing framework. It generates a HTML report to show the result at a glance. The simplest way to use this is to invoke its main method. E.g. import unittest import HTMLTestRunner ... define your tests ... if __name__ == '__main__': HTMLTestRunner.main() For more customization options, instantiates a HTMLTestRunner object. HTMLTestRunner is a counterpart to unittest's TextTestRunner. E.g. # output to a file fp = file('my_report.html', 'wb') runner = HTMLTestRunner.HTMLTestRunner( stream=fp, title='My unit test', description='This demonstrates the report output by HTMLTestRunner.' ) # Use an external stylesheet. # See the Template_mixin class for more customizable options runner.STYLESHEET_TMPL = '' # run the test runner.run(my_test_suite) ------------------------------------------------------------------------ Copyright (c) 2004-2007, Wai Yip Tung All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name Wai Yip Tung nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ # URL: http://tungwaiyip.info/software/HTMLTestRunner.html __author__ = "Wai Yip Tung" __version__ = "0.8.2" """ Change History Version 0.8.2 * Show output inline instead of popup window (Viorel Lupu). Version in 0.8.1 * Validated XHTML (Wolfgang Borgert). * Added description of test classes and test cases. Version in 0.8.0 * Define Template_mixin class for customization. * Workaround a IE 6 bug that it does not treat %(heading)s %(report)s %(ending)s