utils下新建file_reader.py
# -*- coding: utf-8 -*-
import yaml
import os
from xlrd import open_workbook
class YamlReader:
def __init__(self, yamlf):
if os.path.exists(yamlf):
self.yamlf = yamlf
else:
raise FileNotFoundError('文件不存在!')
self._data = None
@property
def data(self):
# 如果是第一次调用data,读取yaml文档,否则直接返回之前保存的数据
if not self._data:
with open(self.yamlf, 'rb') as f:
self._data = list(yaml.safe_load_all(f)) # load后是个generator,用list组织成列表
return self._data
class SheetTypeError(Exception):
pass
# 读取excel文件中的内容。返回list。
class ExcelReader:
"""
如:
excel中内容为:
| A | B | C |
| A1 | B1 | C1 |
| A2 | B2 | C2 |
如果 print(ExcelReader(excel, title_line=True).data),输出结果:
[{A: A1, B: B1, C:C1}, {A:A2, B:B2, C:C2}]
如果 print(ExcelReader(excel, title_line=False).data),输出结果:
[[A,B,C], [A1,B1,C1], [A2,B2,C2]]
可以指定sheet,通过index或者name:
ExcelReader(excel, sheet=2)
ExcelReader(excel, sheet='BaiDuTest')
"""
def __init__(self, excelpath, sheet=0, title_line=True):
if os.path.exists(excelpath):
self.excelpath = excelpath # excel文件路径
else:
raise FileNotFoundError('文件不存在!')
self.sheet = sheet # sheet可以是int表示表格的索引,可以是str表示表格的名称
self.title_line = title_line # 是否存在标题行,有标题行,每一行都是都是对应列名的取值;没有标题行,每一行都是一个列表
self._data = list() # 用于存储每行生成的数据。
@property
def data(self):
if not self._data:
workbook = open_workbook(self.excelpath)
if type(self.sheet) not in [int, str]:
raise SheetTypeError('Please pass in or , not {0}' .format(type(self.sheet)))
elif type(self.sheet) == int:
s = workbook.sheet_by_index(self.sheet)
else:
s = workbook.sheet_by_name(self.sheet)
if self.title_line:
title = s.row_values(0) # 首行为title
for col in range(1, s.nrows):
# 依次遍历其余行,与首行组成dict,拼到self._data中
self._data.append(dict(zip(title, s.row_values(col))))
else:
for col in range(0, s.nrows):
# 遍历所有行,拼到self._data中
self._data.append(s.row_values(col))
return self._data
if __name__ == '__main__':
y = 'D:\myProject\config\config.yml'
reader = YamlReader(y)
print(reader.data)
e = 'D:/myProject/data/baidu.xlsx'
reader = ExcelReader(e, title_line=True)
print(reader.data)
utils下新建config.py
#-*- coding: utf-8 -*-
import os
from utils.file_reader import YamlReader
# 通过当前文件的绝对路径,其父级目录一定是框架的base目录,然后确定各层的绝对路径。如果你的结构不同,可自行修改。
# 之前直接拼接的路径,修改了一下,用现在下面这种方法,可以支持linux和windows等不同的平台,也建议大家多用os.path.split()和os.path.join(),不要直接+'\\xxx\\ss'这样
BASE_PATH = os.path.split(os.path.dirname(os.path.abspath(__file__)))[0]
CONFIG_FILE = os.path.join(BASE_PATH, 'config', 'config.yml')
DATA_PATH = os.path.join(BASE_PATH, 'data')
DRIVER_PATH = os.path.join(BASE_PATH, 'drivers')
LOG_PATH = os.path.join(BASE_PATH, 'log')
REPORT_PATH = os.path.join(BASE_PATH, 'report')
class Config:
def __init__(self, config=CONFIG_FILE):
self.config = YamlReader(config).data
def get(self, element, index=0):
"""
yaml是可以通过'---'分节的。用YamlReader读取返回的是一个list,第一项是默认的节,如果有多个节,可以传入index来获取。
这样我们其实可以把框架相关的配置放在默认节,其他的关于项目的配置放在其他节中。可以在框架中实现多个项目的测试。
"""
return self.config[index].get(element)
config目录下新建config.yml
URL: http://www.baidu.com # 测试网址
emailserver: smtp.qq.com #发件箱邮件服务
from_user: [email protected]
from_passwd: tkmyrvvwigmwbihe
to_user: [email protected]
#mysql
mysql:
ip: '192.168.3.6'
user: 'root'
pwd: 'password'
db: 'mysql'
#oracle
oracle:
ip: '192.168.3.1'
user: 'root'
pwd: 'password'
port: '1521'
sid: 'barc'
log:
file_name: testcase.log # 输出日志文件名
backup: 5 # 备份名
console_level: WARNING # 控制台输出等级
file_level: DEBUG # 文件输出等级
pattern: '%(asctime)s - %(name)s - %(levelname)s - %(message)s' # 打印输出格式
utils下新建logger.py
# -*- coding: utf-8 -*-
import logging
import os
import unittest
import time
import os.path
class Logger(object):
def __init__(self, logger, CmdLevel=logging.INFO, FileLevel=logging.INFO):
self.logger = logging.getLogger(logger)
self.logger.setLevel(logging.DEBUG) # 设置日志默认级别为DEBUG
fmt = logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s - %(message)s') # 设置日志输出格式
currTime = time.strftime('%Y%m%d%H%M', time.localtime(time.time())) # 格式化当前时间
log_path = os.path.split(os.path.dirname(os.path.abspath(__file__)))[0]+'/log/'
print('得到的日志路径为:', log_path)
log_name = log_path + currTime + '.log' # 设置日志文件名称
# 设置由文件输出
fh = logging.FileHandler(log_name, encoding='utf-8') # 采用utf-8字符集格式防止出现中文乱码
fh.setFormatter(fmt)
fh.setLevel(FileLevel) # 日志级别为INFO
# 设置日志由控制台输出
# sh = logging.StreamHandler(log_name)
# sh.setFormatter(fmt)
# sh.setLevel(CmdLevel)
self.logger.addHandler(fh) # 添加handler
def getlog(self):
return self.logger
if __name__ == '__main__':
unittest.main()
utils下新建browserengine.py
# -*- coding: utf-8 -*-
from selenium import webdriver
import configparser
import sys, os
class BrowserEngine(object):
def __init__(self, driver):
self.driver = driver
browser_type = "Chrome"
def get_browser(self):
if self.browser_type == 'Firefox':
driver = webdriver.Firefox()
elif self.browser_type == 'Chrome':
driver = webdriver.Chrome()
elif self.browser_type == 'IE':
driver = webdriver.Ie()
else:
driver = webdriver.Chrome()
driver.maximize_window()
driver.implicitly_wait(10)
return driver
utils下新建conndb.py
#-*- coding: utf-8 -*-
import cx_Oracle,pymysql,json,os
from utils.config import Config
os.environ['NLS_LANG'] = 'SIMPLIFIED CHINESE_CHINA.UTF8'
myora = Config().get('oracle')
mysql = Config().get('mysql')
class TestDb(object):
def __init__(self,oracle_type):
if oracle_type == 'oracle':
self.connect = cx_Oracle.connect(myora['user'] + "/" + myora['pwd'] + "@" + myora['ip'] + ":" + myora['port'] + "/" + myora['sid'])
self.cursor = self.connect.cursor()
elif oracle_type == 'mysql':
self.connect = pymysql.connect(mysql['ip'], mysql['user'], mysql['pwd'], mysql['db'])
self.cursor = self.connect.cursor()
else:
print("请连接正确的数据库")
def select(self,sql):
try:
self.cursor.execute(sql)
result = self.cursor.fetchall()
print('查询数据成功')
return result
except Exception as e:
print(e)
finally:
self.disconnect()
#返回json串脚本
# list = []
# self.cursor.execute(sql)
# result = self.cursor.fetchall()
# col_name = self.cursor.description
# for row in result:
# dict = {}
# for col in range(len(col_name)):
# key = col_name[col][0]
# value = row[col]
# dict[key] = value
# list.append(dict)
# js = json.dumps(list, ensure_ascii=False, indent=2, separators=(',', ':'))
# return js
def insert(self, sql, list_param):
try:
self.cursor.executemany(sql, list_param)
self.connect.commit()
print("插入ok")
except Exception as e:
print(e)
finally:
self.disconnect()
def update(self, sql):
try:
self.cursor.execute(sql)
self.connect.commit()
except Exception as e:
print(e)
finally:
self.disconnect()
def delete(self, sql):
try:
self.cursor.execute(sql)
self.connect.commit()
print("delete ok")
except Exception as e:
print(e)
finally:
self.disconnect()
def disconnect(self):
self.cursor.close()
self.connect.close()
if __name__ == "__main__":
oracle = TestDb('oracle')
A = oracle.select("SELECT T.CLASS_NAME FROM table001 T WHERE T.CLASS_ID='1'")
print A[0][0]
mysql = TestDb('mysql')
B = mysql.select("select name FROM mysql.nametab")
print B[0][0]
# param = [('ww1', 'job003', 1333, 2), ('ss1', 'job004', 1444, 2)]
# # test_oracle.insert("insert into bonus(ENAME,JOB,SAL,COMM)values(:1,:2,:3,:4)",param)#也可以下面这样解决orc-1036非法变量问题
# test_oracle.insert("insert into bonus(ENAME,JOB,SAL,COMM)values (:ENAME,:JOB,:SAL,:COMM)", param)
utils下新建basepage.py
# -*- coding:utf-8 -*-
import time
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.action_chains import ActionChains
import os.path
from utils.logger import Logger
from selenium.webdriver.support.select import Select
from selenium.webdriver.common.keys import Keys
import sys
reload(sys)
sys.setdefaultencoding('utf8')
logger = Logger("BasePage").getlog()
class BasePage(object):
#"定义一个页面基类,让所有页面都继承这个类,封装一些常用的页面操作方法到这个类"
def __init__(self, driver):
self.driver = driver
#8大页面元素(对象)定位方法的封装
def find_element(self, selector):
element = ''
# if '=>' not in selector:
# return self.driver.find_element_by_id(selector)
selector_by = selector[0]
selector_value = selector[1]
if selector_by == 'i' or selector_by == 'id':
try:
element = self.driver.find_element_by_id(selector_value)
logger.info("定位元素OK,实际定位元素方法:%s ,定位的元素的属性值:%s" % (selector_by, selector_value))
except NoSuchElementException as e:
logger.error("没找到元素,抛出异常:%s" % e)
self.get_window_img() # 截取当前窗口
elif selector_by == 'n' or selector_by == 'name':
element = self.driver.find_element_by_name(selector_value)
elif selector_by == 'c' or selector_by == 'class_name':
element = self.driver.find_element_by_class_name(selector_value)
elif selector_by == 'l' or selector_by == 'link_text':
element = self.driver.find_element_by_link_text(selector_value)
elif selector_by == 'p' or selector_by == 'partial_link_text':
element = self.driver.find_element_by_partial_link_text(selector_value)
elif selector_by == 't' or selector_by == 'tag_name':
element = self.driver.find_element_by_tag_name(selector_value)
elif selector_by == 'x' or selector_by == 'xpath':
try:
element = self.driver.find_element_by_xpath(selector_value)
logger.info("定位元素OK,实际定位元素方法:%s ,定位的元素的属性值:%s" % (selector_by, selector_value))
except NoSuchElementException as e:
logger.error("没找到元素,抛出异常:%s" % e)
self.get_window_img() # 截取当前窗口
elif selector_by == 'c' or selector_by == 'css_selector':
element = self.driver.find_element_by_css_selector(selector_value)
else:
raise NameError("请输入正确的目标元素类型.")
return element # 返回变量element
#输入框
def input(self, selector, text):
el = self.find_element(selector)
el.clear()
try:
el.send_keys(text)
logger.info("输入的文本内容为:%s" % text)
except NameError as e:
logger.error("输入的内容异常,抛出异常:%s" % e)
self.get_window_img()
#清除文本框内容
def clear(self, selector):
el = self.find_element(selector)
try:
el.clear()
logger.info("清除输入框文本信息OK")
except NameError as e:
logger.error("清除输入框内容失败:抛出异常: %s" % e)
self.get_window_img()
@staticmethod
def wait(seconds):
time.sleep(seconds)
logger.info("Sleep for %d seconds" % seconds)
# 点击
def click(self, selector):
el = self.find_element(selector)
try:
el.click()
logger.info("点击元素完成")
except NameError as e:
logger.error("没有找到元素 %s" % e)
# 切到iframe
def switch_frame(self):
iframe = self.find_element(['classname','embed-responsive-item'])
try:
self.driver.switch_to_frame(iframe)
# logger.info("The element \' %s \' was clicked." % iframe.text)
except NameError as e:
logger.error("Failed to click the element with %s" % e)
# 处理标准下拉选择框,随机选择
def select(self, id):
select1 = self.find_element(id)
try:
options_list = select1.find_elements_by_tag_name('option')
del options_list[0]
s1 = choice(options_list)
Select(select1).select_by_visible_text(s1.text)
logger.info("随机选的是:%s" % s1.text)
except NameError as e:
logger.error("Failed to click the element with %s" % e)
# 模拟回车键
def enter(self, selector):
e1 = self.find_element(selector)
e1.send_keys(Keys.ENTER)
def get_alert(self):
el = self.driver.switch_to.alert # 获取窗口弹窗的方法
try:
assert '用户名或者密码错误' in el.text # el.text方法获取提示框内容
logger.info("弹窗提示正确")
el.accept() # 点击弹窗确认按钮
except Exception as e:
print('弹窗提示错误', format(e))
# 截图功能:得到截图并保存图片到项目image目录下
def get_window_img(self):
file_path = os.path.dirname(os.path.abspath('.')) + '/image/' # 设置存放截图的路径
# print('截图保存路径为:%s' % file_path)
timeset = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time())) # 格式化时间
pic_name = file_path + timeset + '.png' # 定义截图文件名称
try:
self.driver.get_screenshot_as_file(pic_name)
logger.info('截图成功,图片保存路径为:/image.')
except Exception as e:
logger.error('截图出现异常', format(e))
self.get_window_img()
#隐士等待
def sleep(self, seconds):
self.driver.implicitly_wait(seconds)
logger.info("设置隐式时间:%d 秒." % seconds)
# 关闭当前窗口
def close_window(self):
try:
self.driver.close()
logger.info("关闭当前窗口.")
except NameError as e:
logger.error("关闭当前窗口出错,抛出错误提示:%s." % e)
#进入iframe框架
def switch_to_frame(self, selector):
el = self.find_element(selector)
self.driver.switch_to.frame(el)
#切除iframe框架
def switch_to_default(self):
self.driver.switch_to.default_content()
#执行JS脚本
def execute_script(self, script):
self.driver.execute_script(script)
#右键
def right_click(self, selector):
el = self.find_element(selector)
ActionChains(self.driver).context_click(el).perform()
#上传文件
def upload_input(self, selector, file):
self.find_element(selector).send_keys(file)
def upload_not_input(self, file):
'''
上传文件 ( 标签不是 input 类型,使用 win32gui,得先安装 pywin32 依赖包)
pip install pywin32
:param browser_type: 浏览器类型(Chrome浏览器和Firefox浏览器的有区别)
:param file: 将要上传的文件(绝对路径)
单个文件:file1 = 'C:\\Users\\list_tuple_dict_test.py'
同时上传多个文件:file2 = '"C:\\Users\\list_tuple_dict_test.py" "C:\\Users\\class_def.py"'
:return: 无
'''
# Chrome 浏览器是'打开'
# 对话框
# 下载个 Spy++ 工具,定位“打开”窗口,定位到窗口的类(L):#32770, '打开'为窗口标题
dialog = win32gui.FindWindow('#32770', u'打开')
ComboBoxEx32 = win32gui.FindWindowEx(dialog, 0, 'ComboBoxEx32', None)
ComboBox = win32gui.FindWindowEx(ComboBoxEx32, 0, 'ComboBox', None)
# 上面三句依次寻找对象,直到找到输入框Edit对象的句柄
Edit = win32gui.FindWindowEx(ComboBox, 0, 'Edit', None)
# 确定按钮Button
button = win32gui.FindWindowEx(dialog, 0, 'Button', None)
# 往输入框输入绝对地址
win32gui.SendMessage(Edit, win32con.WM_SETTEXT, 0, file)
# 按button
win32gui.SendMessage(dialog, win32con.WM_COMMAND, 1, button)
utils下新建HTMLTestRunner.py
#-*- coding: utf-8 -*-
"""
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 testcase',
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 testcase
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 testcase classes and testcase 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