├── framework ├── __init__.py ├── README.md ├── case_strategy.py ├── ReadConfig.py ├── browser_engine.py ├── logger.py ├── ConnectDataBase.py ├── base_page.py └── HTMLTestReportCN.py ├── logs ├── README.md └── 2019-06-12 │ └── 2019-06-12_01_35_06.log ├── pageobjects ├── __init__.py ├── README.md └── BaiDu │ ├── __pycache__ │ ├── Index.cpython-36.pyc │ └── news.cpython-36.pyc │ ├── news.py │ └── Index.py ├── screenshots └── README.md ├── tools └── README.md ├── test_report ├── README.md └── 百度自动化测试报告2019-06-12 │ └── 百度自动化测试报告2019-06-12_01_35_06 │ └── 自动化测试报告.html ├── config ├── README.md └── config.ini ├── requirements.txt ├── test_suites ├── README.md └── test_baidu │ ├── __pycache__ │ ├── __init__.cpython-36.pyc │ ├── test_01_index.cpython-36.pyc │ └── test_02_news.cpython-36.pyc │ ├── __init__.py │ ├── test_02_news.py │ └── test_01_index.py ├── TestRunner.py └── README.md /framework/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /logs/README.md: -------------------------------------------------------------------------------- 1 | 输出的日志文件 -------------------------------------------------------------------------------- /pageobjects/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pageobjects/README.md: -------------------------------------------------------------------------------- 1 | 页面对象 -------------------------------------------------------------------------------- /screenshots/README.md: -------------------------------------------------------------------------------- 1 | 截图保存文件夹 -------------------------------------------------------------------------------- /tools/README.md: -------------------------------------------------------------------------------- 1 | driver驱动文件夹 -------------------------------------------------------------------------------- /test_report/README.md: -------------------------------------------------------------------------------- 1 | HTML测试报告输出文件夹 -------------------------------------------------------------------------------- /config/README.md: -------------------------------------------------------------------------------- 1 | 浏览器,url的基础配置,及一些基本不需要更改的账号信息 -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | selenium==3.141.0 2 | PyMySQL==0.9.3 3 | -------------------------------------------------------------------------------- /test_suites/README.md: -------------------------------------------------------------------------------- 1 | test_suites测试用例总集合,总集合可以包括多个子集合 2 | 3 | test_xxxx为测试用例子集合,子集合可以包括多个用例 4 | 5 | TestRunner.py运行符合规则的所有测试用例 -------------------------------------------------------------------------------- /pageobjects/BaiDu/__pycache__/Index.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RongXiaoCong/WebUI-Automation-Frame/HEAD/pageobjects/BaiDu/__pycache__/Index.cpython-36.pyc -------------------------------------------------------------------------------- /pageobjects/BaiDu/__pycache__/news.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RongXiaoCong/WebUI-Automation-Frame/HEAD/pageobjects/BaiDu/__pycache__/news.cpython-36.pyc -------------------------------------------------------------------------------- /framework/README.md: -------------------------------------------------------------------------------- 1 | 页面基础类 base_page: 封装一些常用的页面操作方法 2 | 3 | 浏览器引擎类 browser_engine: 封装浏览器驱动选择,浏览器初始化 4 | 5 | 日志类 Logger: 封装日志输出及控制台输出方法 6 | 7 | 数据库类 ConnectDataBase: 封装了数据库的简单操作 8 | 9 | -------------------------------------------------------------------------------- /test_suites/test_baidu/__pycache__/__init__.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RongXiaoCong/WebUI-Automation-Frame/HEAD/test_suites/test_baidu/__pycache__/__init__.cpython-36.pyc -------------------------------------------------------------------------------- /test_suites/test_baidu/__pycache__/test_01_index.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RongXiaoCong/WebUI-Automation-Frame/HEAD/test_suites/test_baidu/__pycache__/test_01_index.cpython-36.pyc -------------------------------------------------------------------------------- /test_suites/test_baidu/__pycache__/test_02_news.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RongXiaoCong/WebUI-Automation-Frame/HEAD/test_suites/test_baidu/__pycache__/test_02_news.cpython-36.pyc -------------------------------------------------------------------------------- /config/config.ini: -------------------------------------------------------------------------------- 1 | [BROWSERTYPE] 2 | ;browserName = Firefox 3 | browserName = Chrome 4 | #browserName = IE 5 | 6 | [BROWSERATTRIBUTE] 7 | implicitly_wait = 2 8 | 9 | [TESTSERVER] 10 | url = http://www.baidu.com/ 11 | 12 | [TESTACCOUNT] 13 | account = haha 14 | password = 123456 15 | name = 测试大佬 16 | 17 | [TESTDATA] 18 | 19 | 20 | [DATABASE] 21 | host = '' 22 | user = '' 23 | password = '' 24 | database = '' 25 | port = '' 26 | 27 | -------------------------------------------------------------------------------- /pageobjects/BaiDu/news.py: -------------------------------------------------------------------------------- 1 | from framework.base_page import BasePage 2 | import unittest 3 | from selenium.webdriver.common.by import By 4 | 5 | class NewsPage(BasePage): 6 | 7 | def __init__(self, driver): 8 | super().__init__(driver) 9 | self.driver = driver 10 | 11 | ''' 12 | **********************************************百度新闻首页************************************** 13 | ''' 14 | 15 | self.i_search = (By.ID, 'ww') # 搜索框 16 | self.b_search = (By.ID, 's_btn_wr') # 搜索按钮 17 | self.b_scroll_news = (By.ID, 'imgView') # 首页的滚动新闻 18 | self.b_scroll_news_title = (By.ID, 'imgTitle') # 首页的滚动新闻的标题 19 | # self.b_news_all_tab = (By.XPATH, '//*[@id="channel-all"]//*[@class="lavalamp-item"]/a') # 新闻首页的导航条所有按钮 20 | 21 | 22 | # 其他页面元素 23 | self.b_news = (By.LINK_TEXT, '新闻') # 新闻按钮 -------------------------------------------------------------------------------- /TestRunner.py: -------------------------------------------------------------------------------- 1 | #_*_ coding:utf-8 _*_ 2 | 3 | import unittest 4 | import sys 5 | import os 6 | sys.path.append(os.path.abspath(os.path.dirname(__file__))) 7 | from framework import HTMLTestReportCN 8 | from framework.ReadConfig import ReadConfig 9 | from framework.case_strategy import CaseStrategy 10 | from test_suites.test_baidu import to_init 11 | 12 | class RunAllTests(object): 13 | 14 | def __init__(self): 15 | cs = CaseStrategy() 16 | self.test_suite = cs.collect_cases() 17 | self.tester = ReadConfig().get_test_account()['name'] 18 | 19 | def run(self): 20 | to_init().clean_test_data() 21 | 22 | # 启动测试时创建文件夹并获取报告的名字 23 | daf = HTMLTestReportCN.DirAndFiles() 24 | daf.create_dir(title='百度自动化测试报告') 25 | report_path = HTMLTestReportCN.GlobalMsg.get_value("report_path") 26 | 27 | with open(report_path, "wb") as fp: 28 | runner = HTMLTestReportCN.HTMLTestRunner(stream=fp, title='百度自动化测试报告', description='用例执行情况:', tester=self.tester) 29 | runner.run(self.test_suite) 30 | 31 | to_init().clean_test_data() 32 | 33 | if __name__ == "__main__": 34 | RunAllTests().run() 35 | 36 | -------------------------------------------------------------------------------- /test_suites/test_baidu/__init__.py: -------------------------------------------------------------------------------- 1 | import time 2 | import unittest 3 | from framework.browser_engine import BrowserEngine 4 | from framework.ReadConfig import ReadConfig 5 | from framework.HTMLTestReportCN import DirAndFiles 6 | from framework.ConnectDataBase import ConnectDataBase 7 | from pageobjects.BaiDu.Index import IndexPage 8 | from pageobjects.BaiDu.news import NewsPage 9 | 10 | 11 | class to_init(): 12 | def __init__(self): 13 | self.user_info = {"userName":"hahaha", "password":"ahahah"} 14 | self.search_info = {"1":"python大法好", "2":"人生苦短,我用python"} 15 | 16 | def get_driver(self): 17 | """打开网页并获取driver""" 18 | browser = BrowserEngine(self) 19 | driver = browser.open_browser(self) 20 | return driver 21 | 22 | def clean_test_data(self): 23 | """清理测试数据""" 24 | pass 25 | 26 | def get_daf(): 27 | """ 28 | 获取测试报告对象 29 | :return: 30 | """ 31 | return DirAndFiles() 32 | 33 | def get_bd(driver): 34 | """ 35 | 获取百度首页 36 | :return: 37 | """ 38 | return IndexPage(driver) 39 | 40 | def get_news(driver): 41 | """ 42 | 获取百度新闻页面 43 | :return: 44 | """ 45 | return NewsPage(driver) -------------------------------------------------------------------------------- /framework/case_strategy.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | 5 | class CaseStrategy: 6 | def __init__(self): 7 | self.suite_path = 'test_suites' 8 | self.case_path = 'test_baidu' 9 | self.case_pattern = 'test_0*.py' 10 | 11 | def _collect_cases(self, cases, top_dir=None): 12 | suites = unittest.defaultTestLoader.discover(self.case_path, 13 | pattern=self.case_pattern, top_level_dir=top_dir) 14 | for suite in suites: 15 | for case in suite: 16 | cases.addTest(case) 17 | 18 | def collect_cases(self, suite=True): 19 | """collect cases 20 | 21 | collect cases from the giving path by case_path via the giving pattern by case_pattern 22 | 23 | return: all cases that collected by the giving path and pattern, it is a unittest.TestSuite() 24 | 25 | """ 26 | cases = unittest.TestSuite() 27 | 28 | if suite: 29 | test_suites = [] 30 | project_dir = os.path.dirname(os.path.dirname(__file__)) 31 | for file in os.listdir(project_dir): 32 | if self.suite_path in file: 33 | suites_dir = os.path.join(project_dir,file) 34 | if os.path.isdir(suites_dir): 35 | test_suites.append(suites_dir) 36 | for test_suite in test_suites: 37 | self._collect_cases(cases, top_dir=test_suite) 38 | else: 39 | self._collect_cases(cases, top_dir=None) 40 | 41 | return cases 42 | -------------------------------------------------------------------------------- /framework/ReadConfig.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from configparser import ConfigParser 4 | import os 5 | import sys 6 | # 配置文件路径 7 | configPath = os.path.join(os.path.dirname(os.path.dirname(__file__)) , 'config/config.ini') 8 | 9 | class ReadConfig: 10 | def __init__(self): 11 | self.cf = ConfigParser() 12 | self.cf.read(configPath, encoding='UTF-8') 13 | 14 | def get_browser_type(self): 15 | browserName = self.cf.get("BROWSERTYPE", "browserName") 16 | return browserName 17 | 18 | def get_browser_attribute(self): 19 | implicitly_wait = self.cf.get("BROWSERATTRIBUTE", "implicitly_wait") 20 | return implicitly_wait 21 | 22 | def get_test_server(self): 23 | url = self.cf.get("TESTSERVER", "url") 24 | return url 25 | 26 | def get_test_account(self): 27 | account = self.cf.get("TESTACCOUNT", "account") 28 | password = self.cf.get("TESTACCOUNT", "password") 29 | name = self.cf.get("TESTACCOUNT", "name") 30 | value ={'account':account,'password':password,'name':name} 31 | return value 32 | 33 | def get_database(self): 34 | user = self.cf.get("DATABASE", "user") 35 | password = self.cf.get("DATABASE", "password") 36 | database = self.cf.get("DATABASE", "database") 37 | host = self.cf.get("DATABASE", "host") 38 | port = self.cf.get("DATABASE", "port") 39 | value = {'user':user,'password':password,'database':database,'host':host,'port':port} 40 | return value 41 | 42 | def get_test_data(self, *args): 43 | """ 44 | 从配置文件中获取测试数据,接收可变数量的参数 45 | :param args: 46 | :return: 返回字典 47 | """ 48 | a={} 49 | for i in args: 50 | a[i]=self.cf.get("TESTDATA", i) 51 | return a 52 | -------------------------------------------------------------------------------- /pageobjects/BaiDu/Index.py: -------------------------------------------------------------------------------- 1 | from framework.base_page import BasePage 2 | import unittest 3 | from selenium.webdriver.common.by import By 4 | 5 | class IndexPage(BasePage): 6 | 7 | def __init__(self, driver): 8 | super().__init__(driver) 9 | self.driver = driver 10 | 11 | ''' 12 | **********************************************百度首页************************************** 13 | ''' 14 | 15 | self.i_search = (By.ID, 'kw') # 搜索框 16 | self.b_search = (By.ID, 'su') # 搜索按钮 17 | self.b_news = (By.NAME, 'tj_trnews') # 新闻按钮 18 | self.b_hao123 = (By.NAME, 'tj_trhao123') # hao123按钮 19 | self.b_map = (By.NAME, 'tj_trmap') # 地图按钮 20 | self.b_video = (By.NAME, 'tj_trvideo') # 视频按钮 21 | self.b_tieba = (By.NAME, 'tj_trtieba') # 贴吧按钮 22 | self.b_xueshu = (By.LINK_TEXT, '学术') # 学术按钮 23 | self.b_login = (By.LINK_TEXT, '登录') # 登录按钮 24 | self.b_setting = (By.LINK_TEXT, '设置') # 设置按钮 25 | self.b_more_prod = (By.LINK_TEXT, '更多产品') # 更多产品 26 | 27 | self.b_login_user_name = (By.XPATH, '//*[text()="用户名登录"]') # 用户名登录按钮 28 | self.i_login_user_name = (By.CLASS_NAME, 'pass-text-input-userName') # 用户名输入框 29 | self.i_login_password = (By.CLASS_NAME, 'pass-text-input-password') # 密码输入框 30 | self.b_login_submit = (By.ID, 'TANGRAM__PSP_10__submit') # 用户名密码登录页的登录按钮 31 | 32 | self.b_result_title = (By.XPATH, '//*[@id = "content_left"]//*[@class = "t"]') #搜索结果标题 33 | # 其他页面内容 34 | self.t_search_result = (By.CLASS_NAME, 'nums_text') # 搜索结果 35 | self.t_hot_news = (By.XPATH, '//*[text()="热点要闻"]') # 新闻页面醒目的“热点要闻”四个字 36 | -------------------------------------------------------------------------------- /framework/browser_engine.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | import os.path 4 | from framework.ReadConfig import ReadConfig 5 | from selenium import webdriver 6 | from framework.logger import Logger 7 | 8 | logger = Logger(logger="BrowserEngine").getlog() 9 | 10 | 11 | class BrowserEngine(object): 12 | 13 | def __init__(self, driver): 14 | self.driver = driver 15 | self.dir = os.path.dirname(os.path.abspath(os.path.dirname(__file__))) # 注意相对路径获取方法 16 | # windows的驱动程序后缀有exe 17 | # self.chrome_driver_path = self.dir + '/tools/chromedriver.exe' 18 | # mac的驱动程序后缀无exe 19 | self.chrome_driver_path = self.dir + '/tools/chromedriver' 20 | 21 | def open_browser(self, driver): 22 | # 读取配置 23 | config = ReadConfig() 24 | browser = config.get_browser_type() 25 | url = config.get_test_server() 26 | implicitly_wait = config.get_browser_attribute() 27 | 28 | if browser == "Firefox": 29 | driver = webdriver.Firefox() 30 | logger.info("Starting firefox browser.") 31 | elif browser == "Chrome": 32 | driver = webdriver.Chrome(self.chrome_driver_path) 33 | logger.info("Starting Chrome browser.") 34 | elif browser == "IE": 35 | driver = webdriver.Ie() 36 | logger.info("Starting IE browser.") 37 | 38 | driver.get(url) 39 | logger.info("Open url: %s" % url) 40 | # driver.maximize_window() 41 | logger.info("Maximize the current window.") 42 | driver.implicitly_wait(implicitly_wait) 43 | logger.info("Set implicitly wait %s seconds."%str(implicitly_wait)) 44 | return driver 45 | 46 | def quit_browser(self): 47 | logger.info("Now, Close and quit the browser.") 48 | self.driver.quit() -------------------------------------------------------------------------------- /test_suites/test_baidu/test_02_news.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import time 3 | import unittest 4 | from framework.logger import Logger 5 | from selenium.webdriver.common.by import By 6 | from selenium.common.exceptions import NoSuchElementException 7 | from framework.ConnectDataBase import ConnectDataBase 8 | from framework.ReadConfig import ReadConfig 9 | from test_suites.test_baidu import * 10 | 11 | logger = Logger(logger="Baidu > News").getlog() 12 | 13 | class News(unittest.TestCase): 14 | """新闻页测试""" 15 | @classmethod 16 | def setUpClass(cls): 17 | """ 18 | 测试固件的setUp()的代码,主要是测试的前提准备工作 19 | :return: 20 | """ 21 | ini = to_init() 22 | cls.driver = ini.get_driver() 23 | cls.daf = get_daf() 24 | cls.news = get_news(cls.driver) 25 | 26 | @classmethod 27 | def tearDownClass(cls): 28 | """ 29 | 测试结束后的操作,这里基本上都是关闭浏览器 30 | :return: 31 | """ 32 | cls.driver.quit() 33 | 34 | def setUp(self): 35 | pass 36 | 37 | def test_01_check_scroll_news(self): 38 | """检查滚动新闻是否正常跳转""" 39 | self.news.wait_text(self.news.b_news, '新闻') 40 | self.news.click(self.news.b_news) 41 | self.news.wait_gone(self.news.b_news) 42 | # title = self.news.title 43 | origin_handle = self.driver.current_window_handle 44 | # 切换句柄,断言网页标题是否包含滚动新闻的标题 45 | for handle in self.driver.window_handles: 46 | if handle != origin_handle: 47 | self.driver.switch_to_window(handle) 48 | 49 | self.news.wait((By.XPATH, '//*[text()="国内"]'), displayed=False) 50 | 51 | self.news.click(self.news.b_scroll_news) 52 | self.news.sleep(3) 53 | # 这里关闭的是百度新闻首页,而非最后打开的新闻详情页,因为当前页面句柄对应百度新闻首页 54 | self.news.close() 55 | # 这里又将句柄切换回百度首页(当前页面的新建/关闭页面不会修改当前句柄,修改当前句柄会切换当前页面) 56 | self.driver.switch_to.window(origin_handle) 57 | self.news.sleep(3) 58 | logger.info(self.news.get_current_function() + ' --> Successed') -------------------------------------------------------------------------------- /test_suites/test_baidu/test_01_index.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import time 3 | import unittest 4 | from framework.logger import Logger 5 | from selenium.webdriver.common.by import By 6 | from selenium.common.exceptions import NoSuchElementException 7 | from framework.ConnectDataBase import ConnectDataBase 8 | from framework.ReadConfig import ReadConfig 9 | from test_suites.test_baidu import * 10 | 11 | logger = Logger(logger="Baidu > Index").getlog() 12 | 13 | class Index(unittest.TestCase): 14 | """首页测试""" 15 | @classmethod 16 | def setUpClass(cls): 17 | """ 18 | 测试固件的setUp()的代码,主要是测试的前提准备工作 19 | :return: 20 | """ 21 | ini = to_init() 22 | cls.driver = ini.get_driver() 23 | cls.daf = get_daf() 24 | cls.bd = get_bd(cls.driver) 25 | cls.user_info = ini.user_info 26 | cls.search_info = ini.search_info 27 | 28 | @classmethod 29 | def tearDownClass(cls): 30 | """ 31 | 测试结束后的操作,这里基本上都是关闭浏览器 32 | :return: 33 | """ 34 | cls.driver.quit() 35 | 36 | def setUp(self): 37 | pass 38 | 39 | def test_01_search(self): 40 | """搜索""" 41 | self.bd.send_keys(self.bd.i_search, self.search_info['1']) 42 | self.bd.click(self.bd.b_search) 43 | # 验证是否成功搜索到搜索结果的元素 44 | self.assertTrue(self.bd.wait(self.bd.t_search_result)) 45 | print(self.bd.get_current_function()) 46 | logger.info(self.bd.get_current_function() + ' --> Successed') 47 | 48 | def test_02_search_retry(self): 49 | """重新搜索""" 50 | title_1 = [element.text for element in self.bd.find_element(self.bd.b_result_title, None, displayed=False)] 51 | logger.info(title_1) 52 | self.bd.send_keys(self.bd.i_search, self.search_info['2']) 53 | self.bd.click(self.bd.b_search) 54 | self.bd.wait_text_gone(self.bd.b_result_title, title_1, None, displayed=False) 55 | title_2 = [element.text for element in self.bd.find_element(self.bd.b_result_title, None, displayed=False)] 56 | logger.info(title_2) 57 | logger.info(self.bd.get_current_function() + ' --> Successed') 58 | 59 | -------------------------------------------------------------------------------- /framework/logger.py: -------------------------------------------------------------------------------- 1 | # _*_ coding: utf-8 _*_ 2 | import logging 3 | import os 4 | import time 5 | 6 | 7 | class Logger(object): 8 | def __init__(self, logger): 9 | ''''' 10 | 指定保存日志的文件路径,日志级别,以及调用文件 11 | 将日志存入到指定的文件中 12 | ''' 13 | 14 | self.path = os.path.join(os.path.dirname(os.path.dirname(__file__)),'logs') 15 | 16 | now_2d = time.strftime("%Y-%m-%d", time.localtime(time.time())) 17 | now_2s = time.strftime("%Y-%m-%d_%H_%M_%S", time.localtime(time.time())) 18 | dir_path = os.path.join(self.path , now_2d) 19 | # 判断文件夹是否存在,不存在则创建 20 | if os.path.isdir(dir_path): 21 | pass 22 | else: 23 | os.makedirs(dir_path) 24 | 25 | # 创建一个logger 26 | self.logger = logging.getLogger(logger) 27 | self.logger.setLevel(logging.DEBUG) 28 | 29 | # 由于需要多次创建logger对象,所以有较低概率会产生1个以上的日志文件,此处进行一次修正 30 | try: 31 | last_log_name = os.listdir(dir_path)[-1] 32 | last_log_timestamp = time.mktime(time.strptime(last_log_name[:last_log_name.find('.')], "%Y-%m-%d_%H_%M_%S")) 33 | now_log_timestamp = time.mktime(time.strptime(now_2s, "%Y-%m-%d_%H_%M_%S")) 34 | if now_log_timestamp - last_log_timestamp < 2: 35 | # 创建一个handler,用于写入日志文件 36 | log_name = dir_path + '/' + last_log_name 37 | else: 38 | log_name = dir_path + '/' + now_2s + '.log' 39 | except IndexError: 40 | log_name = dir_path + '/' + now_2s + '.log' 41 | 42 | fh = logging.FileHandler(log_name,encoding='utf-8') 43 | fh.setLevel(logging.INFO) 44 | 45 | # 再创建一个handler,用于输出到控制台 46 | ch = logging.StreamHandler() 47 | ch.setLevel(logging.INFO) 48 | 49 | # 定义handler的输出格式 50 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 51 | fh.setFormatter(formatter) 52 | ch.setFormatter(formatter) 53 | 54 | # 给logger添加handler 55 | self.logger.addHandler(fh) 56 | self.logger.addHandler(ch) 57 | 58 | def getlog(self): 59 | return self.logger 60 | 61 | -------------------------------------------------------------------------------- /framework/ConnectDataBase.py: -------------------------------------------------------------------------------- 1 | import pymysql 2 | from framework.logger import Logger 3 | import sys 4 | from framework.ReadConfig import ReadConfig 5 | 6 | # create a logger instance 7 | logger = Logger(logger="DataBase").getlog() 8 | 9 | class ConnectDataBase: 10 | # 构造函数 11 | def __init__(self): 12 | db_dict = ReadConfig().get_database() 13 | self.host = db_dict['host'] 14 | self.user = db_dict['user'] 15 | self.pwd = db_dict['password'] 16 | self.db = db_dict['database'] 17 | self.port = int(db_dict['port']) 18 | self.conn = None 19 | self.cur = None 20 | 21 | # 连接数据库 22 | def connect(self): 23 | try: 24 | self.conn = pymysql.connect(self.host, self.user, 25 | self.pwd, port=self.port, charset='utf8') 26 | logger.info("成功连接数据库!") 27 | except Exception as e: 28 | logger.error(e) 29 | return False 30 | self.cur = self.conn.cursor() 31 | return True 32 | 33 | # 关闭数据库 34 | def close(self): 35 | # 如果数据打开,则关闭;否则没有操作 36 | if self.conn and self.cur: 37 | self.cur.close() 38 | self.conn.close() 39 | logger.info("成功结束数据库连接!") 40 | return True 41 | 42 | # 执行数据库的sq语句,主要用来做插入操作 43 | def execute(self, sql, params=None): 44 | try: 45 | if self.conn and self.cur: 46 | # 正常逻辑,执行sql,提交操作 47 | self.cur.execute(sql, params) 48 | self.conn.commit() 49 | logger.info("execute sql successed : >>>{}<<< \nparams : >>>{}<<<".format(sql,params)) 50 | except Exception as e: 51 | logger.error("execute sql failed : >>>{}<<< \nparams : >>>{}<<<".format(sql,params)) 52 | logger.error(e) 53 | return False 54 | return True 55 | 56 | # 用来查询多条数据 57 | def fetchall(self, sql, params=None): 58 | self.execute(sql, params) 59 | return self.cur.fetchall() 60 | 61 | # 用来查询单条数据 62 | def fetchone(self, sql, params=None): 63 | self.execute(sql, params) 64 | return self.cur.fetchone() 65 | 66 | 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # web UI 自动化测试框架(基于selenium) 2 | ## 框架简介 ## 3 | 基于python语言对selnium做的二次封装,主要有以下特点: 4 | 1.采用了主流的po模式 5 | 2.实现了日志的记录与输出 6 | 3.美观的测试报告输出 7 | 4.灵活的测试用例获取 8 | 5.数据库连接 9 | 6.基础信息配置 10 | 7.整合了大部分常用的方法进行了封装,尤其如find_element,click,send_keys等方法进行了高度封装,实现了智能等待 11 | 8.代码不冗余,复用率高 12 | 13 | ## 适合人群 ## 14 | 1.web自动化小白:帮助其迅速了解web自动化,并上手初步的自动化项目。 15 | 2.测试老司机:节省自己编写框架的时间,直接可用,省时省力,老少皆宜。 16 | 17 | ## 如何开始 ## 18 | 1.安装必要的环境,诸如python3+,selenium 19 | 2.下载合适的浏览器驱动,并放置在/tools文件夹下 20 | [Chrome](http://chromedriver.storage.googleapis.com/index.html) [Firefox](https://github.com/mozilla/geckodriver/releases) [Edge](https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/) 21 | 3.使用终端进入项目根目录,运行以下命令 22 | python TestRunner.py 23 | 24 | ## 框架目录构造: ## 25 | 26 | - **[config](https://github.com/RongXiaoCong/WebUI-Automation-Frame/tree/master/config):** 27 | - *[config.ini](https://github.com/RongXiaoCong/WebUI-Automation-Frame/blob/master/config/config.ini)*:浏览器,url的基础配置,及一些基本不需要更改的账号信息 28 | 29 | - **[framwork](https://github.com/RongXiaoCong/WebUI-Automation-Frame/tree/master/framework):** 30 | - *[logger.py](https://github.com/RongXiaoCong/WebUI-Automation-Frame/blob/master/framework/logger.py)*:封装了日志输入,包括文件输出和控制台的输出 31 | - *[base_page](https://github.com/RongXiaoCong/WebUI-Automation-Frame/blob/master/framework/base_page.py)*:封装了selenium库中常用的方法,包括定位,点击,输入,是否存在,等待等,是耗费了最多心力的类,本项目精华所在... 32 | - *[browser_engine](https://github.com/RongXiaoCong/WebUI-Automation-Frame/blob/master/framework/browser_engine.py)*:通过读取配置文件去选择浏览器和url,并返回浏览器对象实例 33 | - *[ConnectDataBase](https://github.com/RongXiaoCong/WebUI-Automation-Frame/blob/master/framework/ConnectDataBase.py)*:封装了数据库的简单操作 34 | - *[case_strategy](https://github.com/RongXiaoCong/WebUI-Automation-Frame/blob/master/framework/case_strategy.py)*:封装了用例采集策略 35 | 36 | - **[logs](https://github.com/RongXiaoCong/WebUI-Automation-Frame/tree/master/logs):** 37 | 接收日志文件的输出 38 | 39 | - **[pageobjects](https://github.com/RongXiaoCong/WebUI-Automation-Frame/tree/master/pageobjects):** 40 | 用于封装页面对象 41 | 42 | - **[test_report](https://github.com/RongXiaoCong/WebUI-Automation-Frame/tree/master/test_report):** 43 | 测试报告的输出文件夹 44 | 45 | - **[testsuites](https://github.com/RongXiaoCong/WebUI-Automation-Frame/tree/master/test_suites):** 46 | 用于测试用例的存放和测试用例集合 47 | 48 | - **[tools](https://github.com/RongXiaoCong/WebUI-Automation-Frame/tree/master/tools):** 49 | 存放浏览器驱动 50 | 51 | 52 | 53 | 本项目最初参考了https://github.com/StrawberryFlavor/Selenium-Framework 的目录结构,有兴趣小伙伴的可以移步 54 | 55 | 有任何建议和疑问请提issue,佛系回复。 56 | -------------------------------------------------------------------------------- /logs/2019-06-12/2019-06-12_01_35_06.log: -------------------------------------------------------------------------------- 1 | 2019-06-12 01:35:08,124 - BrowserEngine - INFO - Starting Chrome browser. 2 | 2019-06-12 01:35:12,535 - BrowserEngine - INFO - Open url: http://www.baidu.com/ 3 | 2019-06-12 01:35:12,535 - BrowserEngine - INFO - Maximize the current window. 4 | 2019-06-12 01:35:12,538 - BrowserEngine - INFO - Set implicitly wait 2 seconds. 5 | 2019-06-12 01:35:12,588 - ElementOperation - INFO - Positioned the element ('id', 'kw')[0]. 6 | 2019-06-12 01:35:12,795 - ElementOperation - INFO - SendKeys --> ('id', 'kw')[0] success 7 | 2019-06-12 01:35:12,818 - ElementOperation - INFO - Positioned the element ('id', 'su')[0]. 8 | 2019-06-12 01:35:12,869 - ElementOperation - INFO - Click --> ('id', 'su')[0] success 9 | 2019-06-12 01:35:13,861 - ElementOperation - INFO - Positioned the element ('class name', 'nums_text')[0]. 10 | 2019-06-12 01:35:13,925 - Baidu > Index - INFO - test_01_search --> Successed 11 | 2019-06-12 01:35:14,077 - ElementOperation - INFO - Positioned the elements ('xpath', '//*[@id = "content_left"]//*[@class = "t"]'). 12 | 2019-06-12 01:35:14,216 - Baidu > Index - INFO - ['Python大法好 - 微信公众号:虾神daxialu(咨询问题请加) - CSDN博客', '专栏:Python大法好 - CSDN博客', 'Python大法好啊 - 简书', 'python大法好——面向对象 - null? - 博客园', 'python大法好,这些你可不要忽略了! - 云+社区 - 腾讯云', '如何理解python大法好_百度知道', 'python大法好——网络编程 - null? - 博客园', 'Python大法好', '如何理解python大法好?_百度知道'] 13 | 2019-06-12 01:35:14,233 - ElementOperation - INFO - Positioned the element ('id', 'kw')[0]. 14 | 2019-06-12 01:35:14,360 - ElementOperation - INFO - SendKeys --> ('id', 'kw')[0] success 15 | 2019-06-12 01:35:14,376 - ElementOperation - INFO - Positioned the element ('id', 'su')[0]. 16 | 2019-06-12 01:35:14,407 - ElementOperation - INFO - Click --> ('id', 'su')[0] success 17 | 2019-06-12 01:35:14,493 - ElementOperation - INFO - Positioned the elements ('xpath', '//*[@id = "content_left"]//*[@class = "t"]'). 18 | 2019-06-12 01:35:14,569 - ElementOperation - INFO - The text of the target element is "['Python大法好 - 微信公众号:虾神daxialu(咨询问题请加) - CSDN博客', '专栏:Python大法好 - CSDN博客', 'Python大法好啊 - 简书', 'python大法好——面向对象 - null? - 博客园', 'python大法好,这些你可不要忽略了! - 云+社区 - 腾讯云', '如何理解python大法好_百度知道', 'python大法好——网络编程 - null? - 博客园', 'Python大法好', '如何理解python大法好?_百度知道']",the actual text is "['Python大法好 - 微信公众号:虾神daxialu(咨询问题请加) - CSDN博客', '专栏:Python大法好 - CSDN博客', 'Python大法好啊 - 简书', 'python大法好——面向对象 - null? - 博客园', 'python大法好,这些你可不要忽略了! - 云+社区 - 腾讯云', '如何理解python大法好_百度知道', 'python大法好——网络编程 - null? - 博客园', 'Python大法好', '如何理解python大法好?_百度知道']"... 19 | Start matching>>>>>> 20 | 2019-06-12 01:35:14,569 - ElementOperation - INFO - Sleep for 0.5 seconds 21 | 2019-06-12 01:35:15,176 - ElementOperation - INFO - Positioned the elements ('xpath', '//*[@id = "content_left"]//*[@class = "t"]'). 22 | 2019-06-12 01:35:15,318 - ElementOperation - INFO - The text of the target element is "['Python大法好 - 微信公众号:虾神daxialu(咨询问题请加) - CSDN博客', '专栏:Python大法好 - CSDN博客', 'Python大法好啊 - 简书', 'python大法好——面向对象 - null? - 博客园', 'python大法好,这些你可不要忽略了! - 云+社区 - 腾讯云', '如何理解python大法好_百度知道', 'python大法好——网络编程 - null? - 博客园', 'Python大法好', '如何理解python大法好?_百度知道']",the actual text is "['人生苦短,我用Python?为什么Python这么火? - Python达人 - CSDN博客', 'Python崛起:“人生苦短,我用Python”并非一句戏言 - qq..._CSDN博客', '为何说人生苦短我用Python', '人生苦短,我用Python(目录) - 海燕。 - 博客园', '为什么说”人生苦短,我用python“? - IT。拾荒者 - 博客园', '人生苦短,我用Python或Java', '人生苦短,我用Python', '人生苦短,我用python! - 简书', '“人生苦短,我用Python” - 简书']"... 23 | Start matching>>>>>> 24 | 2019-06-12 01:35:15,318 - ElementOperation - INFO - Matching success! 25 | 2019-06-12 01:35:15,393 - ElementOperation - INFO - Positioned the elements ('xpath', '//*[@id = "content_left"]//*[@class = "t"]'). 26 | 2019-06-12 01:35:15,469 - Baidu > Index - INFO - ['人生苦短,我用Python?为什么Python这么火? - Python达人 - CSDN博客', 'Python崛起:“人生苦短,我用Python”并非一句戏言 - qq..._CSDN博客', '为何说人生苦短我用Python', '人生苦短,我用Python(目录) - 海燕。 - 博客园', '为什么说”人生苦短,我用python“? - IT。拾荒者 - 博客园', '人生苦短,我用Python或Java', '人生苦短,我用Python', '人生苦短,我用python! - 简书', '“人生苦短,我用Python” - 简书'] 27 | 2019-06-12 01:35:15,472 - Baidu > Index - INFO - test_02_search_retry --> Successed 28 | 2019-06-12 01:35:20,900 - BrowserEngine - INFO - Starting Chrome browser. 29 | 2019-06-12 01:35:24,877 - BrowserEngine - INFO - Open url: http://www.baidu.com/ 30 | 2019-06-12 01:35:24,878 - BrowserEngine - INFO - Maximize the current window. 31 | 2019-06-12 01:35:24,879 - BrowserEngine - INFO - Set implicitly wait 2 seconds. 32 | 2019-06-12 01:35:24,935 - ElementOperation - INFO - Positioned the element ('link text', '新闻')[0]. 33 | 2019-06-12 01:35:24,963 - ElementOperation - INFO - The text of the target element is "新闻",the actual text is "新闻"... 34 | Start matching>>>>>> 35 | 2019-06-12 01:35:24,964 - ElementOperation - INFO - Matching success! 36 | 2019-06-12 01:35:25,023 - ElementOperation - INFO - Positioned the element ('link text', '新闻')[0]. 37 | 2019-06-12 01:35:26,606 - ElementOperation - INFO - Click --> ('link text', '新闻')[0] success 38 | 2019-06-12 01:35:28,672 - ElementOperation - ERROR - Failed to position the element ('link text', '新闻')[0]! 39 | 2019-06-12 01:35:28,693 - ElementOperation - INFO - Positioned the element ('xpath', '//*[text()="国内"]')[0]. 40 | 2019-06-12 01:35:28,719 - ElementOperation - INFO - Positioned the element ('id', 'imgView')[0]. 41 | 2019-06-12 01:35:28,788 - ElementOperation - INFO - Click --> ('id', 'imgView')[0] success 42 | 2019-06-12 01:35:28,818 - ElementOperation - INFO - Closing current window. 43 | 2019-06-12 01:35:28,852 - Baidu > News - INFO - test_01_check_scroll_news --> Successed 44 | -------------------------------------------------------------------------------- /test_report/百度自动化测试报告2019-06-12/百度自动化测试报告2019-06-12_01_35_06/自动化测试报告.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |测试人员 : 测试大佬
378 |开始时间 : 2019-06-12 01:35:06
379 |合计耗时 : 0:00:24.552301
380 |测试结果 : 共 3,通过 3,通过率 = 100.00%
381 |失败用例合集 :
错误用例合集 :
用例执行情况:
385 |393 | 概要{ 100.00% } 394 | 通过{ 3 } 395 | 失败{ 0 } 396 | 错误{ 0 } 397 | 所有{ 3 } 398 |
399 || 用例集/测试用例 | 413 |说明 | 414 |总计 | 415 |通过 | 416 |失败 | 417 |错误 | 418 |耗时 | 419 |详细 | 420 |
| Index | 424 |首页测试 | 425 |2 | 426 |2 | 427 |0 | 428 |0 | 429 |2.93秒 | 430 |详细 | 431 |
test_01_search |
435 | 搜索 | 436 |
437 |
440 |
441 |
442 |
443 |
444 |
451 | 445 | 446 | pt1_1: test_01_search 447 | 448 | 449 |450 | |
452 | 453 | | ||||
test_02_search_retry |
457 | 重新搜索 | 458 |通过 | 459 |460 | | ||||
| News | 464 |新闻页测试 | 465 |1 | 466 |1 | 467 |0 | 468 |0 | 469 |3.97秒 | 470 |详细 | 471 |
test_01_check_scroll_news |
475 | 检查滚动新闻是否正常跳转 | 476 |通过 | 477 |478 | | ||||
| 总计 | 482 |3 | 483 |3 | 484 |0 | 485 |0 | 486 |6.9秒 | 487 |通过率:100.00% | 488 ||