├── .pytest_cache └── v │ └── cache │ ├── nodeids │ └── lastfailed ├── case.pyc ├── function.pyc ├── elementdb.pyc ├── keyworddb.pyc ├── Report ├── covdata.cov ├── cov_profiler.html ├── ExampleReport.html └── TestResultReport.html ├── test_other.js ├── __pycache__ ├── case.cpython-37.pyc ├── test.cpython-37.pyc ├── config.cpython-37.pyc ├── function.cpython-37.pyc ├── profiler.cpython-37.pyc ├── testcase.cpython-37.pyc ├── elementdb.cpython-37.pyc ├── interpret.cpython-37.pyc └── keyworddb.cpython-37.pyc ├── elementdb.py ├── .idea ├── encodings.xml ├── modules.xml ├── misc.xml ├── uitest-server.iml ├── inspectionProfiles │ └── Project_Default.xml └── workspace.xml ├── test.js ├── config.py ├── README.md ├── keyworddb.py ├── test.html ├── process.py ├── case.py ├── function.py ├── testcase.py ├── interpret.py └── profiler.py /.pytest_cache/v/cache/nodeids: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /case.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icesword0760/uitest-keyword/HEAD/case.pyc -------------------------------------------------------------------------------- /function.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icesword0760/uitest-keyword/HEAD/function.pyc -------------------------------------------------------------------------------- /elementdb.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icesword0760/uitest-keyword/HEAD/elementdb.pyc -------------------------------------------------------------------------------- /keyworddb.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icesword0760/uitest-keyword/HEAD/keyworddb.pyc -------------------------------------------------------------------------------- /.pytest_cache/v/cache/lastfailed: -------------------------------------------------------------------------------- 1 | { 2 | "pytest4.py::test_ehlo": true, 3 | "test4.py": true 4 | } -------------------------------------------------------------------------------- /Report/covdata.cov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icesword0760/uitest-keyword/HEAD/Report/covdata.cov -------------------------------------------------------------------------------- /test_other.js: -------------------------------------------------------------------------------- 1 | function fd(){ 2 | document.getElementById('H').innerHTML="点击了D,触发了D方法,D在另一个js里" 3 | }; -------------------------------------------------------------------------------- /__pycache__/case.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icesword0760/uitest-keyword/HEAD/__pycache__/case.cpython-37.pyc -------------------------------------------------------------------------------- /__pycache__/test.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icesword0760/uitest-keyword/HEAD/__pycache__/test.cpython-37.pyc -------------------------------------------------------------------------------- /__pycache__/config.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icesword0760/uitest-keyword/HEAD/__pycache__/config.cpython-37.pyc -------------------------------------------------------------------------------- /__pycache__/function.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icesword0760/uitest-keyword/HEAD/__pycache__/function.cpython-37.pyc -------------------------------------------------------------------------------- /__pycache__/profiler.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icesword0760/uitest-keyword/HEAD/__pycache__/profiler.cpython-37.pyc -------------------------------------------------------------------------------- /__pycache__/testcase.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icesword0760/uitest-keyword/HEAD/__pycache__/testcase.cpython-37.pyc -------------------------------------------------------------------------------- /__pycache__/elementdb.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icesword0760/uitest-keyword/HEAD/__pycache__/elementdb.cpython-37.pyc -------------------------------------------------------------------------------- /__pycache__/interpret.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icesword0760/uitest-keyword/HEAD/__pycache__/interpret.cpython-37.pyc -------------------------------------------------------------------------------- /__pycache__/keyworddb.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icesword0760/uitest-keyword/HEAD/__pycache__/keyworddb.cpython-37.pyc -------------------------------------------------------------------------------- /elementdb.py: -------------------------------------------------------------------------------- 1 | 2 | element = { 3 | '百度搜索框':('By.ID', 'kw'), 4 | '百度搜索按钮': ('By.ID', 'su'), 5 | '按钮A': ('By.ID','A'), 6 | '按钮B': ('By.ID','B'), 7 | '按钮C': ('By.ID','C'), 8 | '按钮D': ('By.ID','D'), 9 | } -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | function fa(){ 2 | document.getElementById('H').innerHTML="点击了A,触发了A方法" 3 | }; 4 | function fb(){ 5 | document.getElementById('H').innerHTML="点击了B,触发了B方法" 6 | }; 7 | function fc(){ 8 | document.getElementById('H').innerHTML="点击了C,触发了C方法" 9 | } 10 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | chrome_install_path = r"C:\Users\zyj\AppData\Local\Google\Chrome SxS\Application" #你的chrome所在的地址 2 | cov_report_path = r"C:\Users\zyj\PycharmProjects\uitest-server\Report\cov_profiler.html" #生成的覆盖率报告的地址 3 | cov_data_path = r"C:\Users\zyj\PycharmProjects\uitest-server\Report\covdata.cov" #生成的覆盖率文件的地址 -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ApexVCS 5 | 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # uitest-keyword 2 | ## 一些说明 3 | 这个demo仅仅是个思路指引,当然你也可以直接用,不过还有很多没有完善和很死板的地方需要去修补,之后ok了,我会再放上用这个框架为核心打造的web平台版。 4 | 5 | 想运行这个demon,你需要: 6 | py3的环境 7 | selenium库 8 | HTMLTestRunner_PY3(放到py3目录下的lib目录里) 9 | 适合你chrome版本的chromedriver(放到py3的根目录下) 10 | 11 | 在case模块编写用例,执行process模块执行用例,测试报告在report目录下。 12 | 13 | ## 该项目的说明博客: 14 | https://www.jianshu.com/p/b65c3014198c 15 | -------------------------------------------------------------------------------- /keyworddb.py: -------------------------------------------------------------------------------- 1 | keyword = { 2 | "输入": 'input', 3 | "点击": 'click', 4 | "打开网址": 'open_url', 5 | "等待":'wait_time', 6 | "元素文本包含": 'element_text_has', 7 | "页面文本包含": 'page_text_has', 8 | "原生打开网址": 'navigate_url', 9 | "切换至最后一个窗口": 'switch_to_last_handles', 10 | "切换至另一个窗口": 'switch_to_another_hanles', 11 | "切换iframe":'switch_to_iframe',#入参是iframe的id或者name,str类型 12 | "切换回默认环境":'switch_to_default_content',#在iframe操作完毕后,需要切换回默认环境 13 | } -------------------------------------------------------------------------------- /.idea/uitest-server.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | -------------------------------------------------------------------------------- /test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Document 10 | 11 | 12 | 13 | 14 | 15 | 16 |

测试文本~~~

17 | 18 | -------------------------------------------------------------------------------- /process.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import unittest 3 | import os 4 | from HTMLTestRunner_PY3 import HTMLTestRunner 5 | from testcase import TestCase 6 | 7 | def process_case(): 8 | report_title = '用例执行报告' 9 | desc = '测试执行情况展示' 10 | report_file = r".\Report\TestResultReport.html" 11 | suite = unittest.TestSuite() 12 | suite.addTests(TestCase().testcases) 13 | print(suite) 14 | with open(report_file, 'wb') as report: 15 | runner = HTMLTestRunner(stream=report, title=report_title, description=desc) 16 | runner.run(suite) 17 | 18 | #runner = unittest.TextTestRunner() 19 | #runner.run(suite) 20 | 21 | 22 | process_case() -------------------------------------------------------------------------------- /case.py: -------------------------------------------------------------------------------- 1 | cases =[ 2 | [ 3 | {'name': 'test_baidu'}, 4 | {'action': "原生打开网址", 'parmeters':['file:///C:/Users/zyj/PycharmProjects/uitest-server/test.html']}, 5 | {'action': "等待", 'parmeters': [2]}, 6 | {'action':"点击", 'parmeters':["按钮A"]}, 7 | #{'action':"点击", 'parmeters':["按钮B"]}, 8 | {'action': "等待", 'parmeters': [1]}, 9 | #{'action':"点击", 'parmeters':["按钮C"]}, 10 | #{'action': "等待", 'parmeters': [1]}, 11 | #{'action':"点击", 'parmeters':["按钮D"]}, 12 | 13 | #{'action': "点击", 'parmeters': ["百度搜索按钮"]}, 14 | #{'validate': "页面文本包含", 'parmeters': ["怪不开机"]}, 15 | ], 16 | #[ 17 | # {'name': 'test_baidu222'}, 18 | # {'action': "原生打开网址", 'parmeters':['https://www.baidu.com/']}, 19 | #{'action':"输入", 'parmeters':["百度搜索框",'100行代码打造关键字驱动的ui自动化测试框架']}, 20 | #{'action': "点击", 'parmeters': ["百度搜索按钮"]}, 21 | #{'validate': "页面文本包含", 'parmeters': ["test"]}, 22 | #], 23 | ] 24 | -------------------------------------------------------------------------------- /function.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import time 3 | import unittest 4 | 5 | def input(element,str,driver): 6 | find_element(element, driver).send_keys(str) 7 | 8 | def click(element,driver): 9 | find_element(element,driver).click() 10 | 11 | def find_element(element,driver): 12 | webelement = driver.find_element(*element) 13 | return webelement 14 | 15 | def switch_to_last_handles(driver): 16 | """ 17 | 在打开的窗口里选择最后一个 18 | :return:None 19 | """ 20 | all_handles = driver.window_handles 21 | driver.switch_to_window(all_handles[-1]) 22 | 23 | def switch_to_another_hanles(now_handle,driver): 24 | """ 25 | 只适用于打开两个窗口的情况,传入现在的窗口句柄后,选择另一个窗口 26 | :param now_handle:现在的窗口句柄 27 | :return: 28 | """ 29 | all_handles = driver.window_handles # 得到当前开启的所有窗口的句柄 30 | for handle in all_handles: 31 | if handle != now_handle: # 获取到与当前窗口不一样的窗口 32 | driver.switch_to_window(handle) 33 | 34 | def open_url(url,driver): 35 | driver.get(url) 36 | 37 | def navigate_url(url,chrome): 38 | chrome.Page.navigate(url=url) 39 | 40 | def wait_time(num): 41 | time.sleep(num) 42 | 43 | def element_text_has(element,str,driver): 44 | element = find_element(element, driver) 45 | element_text = element.text 46 | return unittest.TestCase().assertIn(str,element_text,"元素文本不包含预期值!") 47 | 48 | def page_text_has(str,driver,chrome): 49 | chrome.wait_event("Page.loadEventFired", timeout=60) 50 | page_text = driver.page_source 51 | return unittest.TestCase().assertIn(str,page_text,"页面文本不包含预期值!") 52 | 53 | def switch_to_iframe(iframe_msg,driver): 54 | driver.switch_to.frame(iframe_msg) 55 | 56 | def switch_to_default_content(driver): 57 | driver.switch_to_default_content() -------------------------------------------------------------------------------- /Report/cov_profiler.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
js名称
file:///C:/Users/zyj/PycharmProjects/uitest-server/test.js
file:///C:/Users/zyj/PycharmProjects/uitest-server/test_other.js
27 |
28 | 44 |
45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /testcase.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import os 3 | import time 4 | import PyChromeDevTools 5 | import config 6 | from selenium import webdriver 7 | from interpret import Interpret 8 | from profiler import Profiler 9 | 10 | class TestCase(object): 11 | def __init__(self): 12 | #self.driver = webdriver 13 | self.interpret = Interpret() 14 | self.casename = '' 15 | self.actions = [] 16 | self.testcases = [] 17 | self.case_factroy() 18 | 19 | def init_context(self): 20 | self.casename = '' 21 | self.actions = [] 22 | 23 | def case_factroy(self): 24 | for case in self.interpret.testcases: 25 | casename = case['casename'] 26 | actions = case['actions'] 27 | self.bind_actions(actions) 28 | self.bind_casename(casename) 29 | classname = casename.upper() 30 | testcase = type(classname, (unittest.TestCase,), {casename:run_action, 31 | 'actions':actions, 32 | #'driver':self.driver, 33 | 'tearDown':teardown, 34 | 'setUp':setup},) 35 | self.testcases.append(testcase(casename)) 36 | self.init_context() 37 | 38 | def bind_actions(self, actions): 39 | self.actions=actions 40 | 41 | def bind_casename(self, casename): 42 | self.casename = casename 43 | 44 | def retry(times): 45 | def retry_func(func): 46 | def _(*args, **kwds): 47 | for i in range(times): 48 | try: 49 | func(*args, **kwds) 50 | return 51 | except AssertionError: 52 | pass 53 | raise AssertionError(func) 54 | return _ 55 | return retry_func 56 | 57 | @retry(3) 58 | def run_action(self): 59 | for action in self.actions: 60 | for func, parmeters in action.items(): 61 | func(*parmeters) 62 | 63 | def teardown(self): 64 | self.chrome.wait_event("Page.loadEventFired", timeout=60) 65 | time.sleep(3) 66 | cov = self.chrome.Profiler.takePreciseCoverage() 67 | self.chrome.Profiler.disable() 68 | #res = c['result']['result'] 69 | cov = Profiler().make_covdata_file(config.cov_data_path,cov, ["zyj"]) 70 | report_file = config.cov_report_path 71 | with open(report_file, 'wb') as report: 72 | Profiler().make_profiler_report(stream=report, covdata=cov) 73 | self.driver.close() 74 | self.chrome.close() 75 | 76 | def setup(self): 77 | #os.chdir(r"C:\Program Files (x86)\Google\Chrome\Application") 78 | os.chdir(config.chrome_install_path) 79 | cmd = "chrome.exe --remote-debugging-port=9222" 80 | os.popen(cmd) 81 | time.sleep(1) 82 | self.chrome = PyChromeDevTools.ChromeInterface() 83 | self.options = webdriver.ChromeOptions() 84 | self.options._debugger_address = "localhost:9222" 85 | self.driver = webdriver.Chrome(chrome_options=self.options) 86 | Interpret.replace_driver(self.actions, self.driver) 87 | Interpret.replace_chrome(self.actions, self.chrome) 88 | self.chrome.Profiler.enable() 89 | self.chrome.Profiler.startPreciseCoverage() 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /interpret.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.common.by import By 2 | from case import cases 3 | from elementdb import element 4 | from keyworddb import keyword 5 | from selenium import webdriver 6 | import function 7 | import inspect 8 | 9 | class Interpret(object): 10 | def __init__(self): 11 | self.casename = '' 12 | self.action_list = [] 13 | self.testcases = [] 14 | self.testcase = { 15 | 'casename': self.casename, 16 | 'actions': self.action_list, 17 | } 18 | self.interpret_cases() 19 | 20 | def init_context(self): 21 | self.casename = '' 22 | self.action_list = [] 23 | self.testcase = { 24 | 'casename': self.casename, 25 | 'actions': self.action_list, 26 | } 27 | 28 | def interpret_cases(self): 29 | for case in cases: 30 | self.interpret_case(case) 31 | self.bind_testcase(self.testcase) 32 | self.init_context() 33 | 34 | def interpret_case(self, case): 35 | for _ in case: 36 | for k,v in _.items(): 37 | if k == 'name': 38 | self.bind_casename(v) 39 | if k == 'action' or k == 'validate': 40 | func_name = keyword[v] 41 | func = getattr(function, func_name) 42 | func_parmeters = inspect.signature(func).parameters.values()#func_parmeters是获取到的函数的参数列表 43 | parmeter_list = self.interpret_parmeters(_['parmeters'],func_parmeters) 44 | action = {func:parmeter_list} 45 | self.bind_action_list(action) 46 | 47 | def interpret_parmeters(self, parmeters, func_parmeters): 48 | parmeter_list = [] 49 | for parmeter in parmeters: 50 | parmeter_list.append(parmeter) 51 | # 遍历函数的参数名称集合,如果其中有element,则将该参数通过element映射关系,转换为元组数据并替换原有用例对应的参数 52 | for index, f_type in enumerate(func_parmeters): 53 | f_type = str(f_type) 54 | if f_type == 'element': 55 | element_keyword = parmeters[0] 56 | find_method = eval(element[element_keyword][0]) 57 | # element_tuple为元组数据,记录了对象的查找方式和位置信息 58 | element_tuple = (find_method, element[element_keyword][1]) 59 | parmeter_list[index] = element_tuple 60 | if f_type == 'driver': 61 | #print(type(self.driver)) 62 | #parmeter_list[index] = '$Web_Driver' 63 | parmeter_list.append('$Web_Driver') 64 | #parmeter_list.append(self.driver) 65 | if f_type == 'chrome': 66 | parmeter_list.append('$Chrome_Dev') 67 | return parmeter_list 68 | 69 | @classmethod 70 | def replace_driver(self, actions, driver): 71 | for action in actions: 72 | for v in action.values(): 73 | for index, _ in enumerate(v): 74 | if _ == '$Web_Driver': 75 | v[index] = driver 76 | 77 | @classmethod 78 | def replace_chrome(self, actions, chrome): 79 | for action in actions: 80 | for v in action.values(): 81 | for index, _ in enumerate(v): 82 | if _ == '$Chrome_Dev': 83 | v[index] = chrome 84 | 85 | def bind_action_list(self, action): 86 | self.testcase['actions'].append(action) 87 | 88 | def bind_casename(self, name): 89 | self.testcase['casename'] = name 90 | 91 | def bind_testcase(self, case): 92 | self.testcases.append(case) 93 | 94 | 95 | -------------------------------------------------------------------------------- /Report/ExampleReport.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 用例执行报告 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 100 | 101 | 102 | 103 | 104 | 198 | 199 |
200 | 201 | 208 |

测试执行情况展示

209 |
210 | 211 | 212 |
213 | 214 | 215 | 216 |
217 |

218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 |
测试套件/测试用例总数通过失败错误查看
testcase.TEST_BAIDU1100详情
test_baidu
通过
总计1100 
259 | 260 |
 
261 | 262 | 307 | 308 |
309 | 310 | 311 | -------------------------------------------------------------------------------- /Report/TestResultReport.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 用例执行报告 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 100 | 101 | 102 | 103 | 104 | 198 | 199 |
200 | 201 | 208 |

测试执行情况展示

209 |
210 | 211 | 212 |
213 | 214 | 215 | 216 |
217 |

218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 |
测试套件/测试用例总数通过失败错误查看
testcase.TEST_BAIDU1100详情
test_baidu
通过
总计1100 
259 | 260 |
 
261 | 262 | 307 | 308 |
309 | 310 | 311 | -------------------------------------------------------------------------------- /profiler.py: -------------------------------------------------------------------------------- 1 | import test 2 | import requests 3 | import copy 4 | import pickle 5 | 6 | class ProfilerResultTemplet(object): 7 | """ 8 | 模板类,主要放置生成覆盖率网页的模板 9 | """ 10 | 11 | MINTEMPLET = r""" 12 | 13 | 14 | 15 | 16 | 17 | 30 | 31 | 32 | 33 | 34 | 35 | %(jslist)s 36 |
js名称
37 |
38 | %(pre)s 39 |
40 | 41 | 42 | 43 | 44 | 45 | """ 46 | JSLISTTEMPLET = r""" %(js_name)s """ 47 | 48 | CODETEMPLET = r"""""" 51 | 52 | class Profiler(ProfilerResultTemplet): 53 | 54 | def merge_same_func_ranges(self, covdata): 55 | """ 56 | 当一个js的覆盖率数据包含多个相同的被统计函数时,合并这些被统计函数的覆盖率数据(ranges) 57 | :param covdata:过滤掉不需要域名和已覆盖函数的覆盖率数据 58 | :return: covdata:合并完的统计数据,该数据不会存在一个js文件含有多个相同名称函数的情况了 59 | """ 60 | copy_cov = copy.deepcopy(covdata) 61 | for index,data in enumerate(covdata): 62 | for function in data['functions']: 63 | functionName = function['functionName'] 64 | function_ranges = [func['ranges'] for func in copy_cov[index]['functions'] if functionName == func['functionName']] 65 | if len(function_ranges) > 1: 66 | function_ranges = [range for ranges in function_ranges for range in ranges] 67 | ranges = self.by_rule_merge_ranges(function_ranges) 68 | function_item = { 69 | 'functionName': functionName, 70 | 'ranges': ranges, 71 | 'isBlockCoverage': False 72 | } 73 | list(map(lambda func: copy_cov[index]['functions'].remove(func),[func for func in covdata[index]['functions'] if functionName == func['functionName']])) 74 | copy_cov[index]['functions'].append(function_item) 75 | return copy_cov 76 | 77 | def by_rule_merge_ranges(self,list): 78 | """ 79 | 当传入坐标数组时,将数组内的坐标转换为唯一坐标(即 既不包含(或者被包含)其他坐标,也不能和其他坐标连续的坐标) 80 | :param list:传入的坐标数组 81 | :return:转换后的坐标数组 82 | """ 83 | merge_list = [] 84 | def _merge(ranges): 85 | copy_ranges = copy.deepcopy(ranges) 86 | ranges_length = len(ranges) - 1 87 | start_offset = copy_ranges[0]['startOffset'] 88 | end_offset = copy_ranges[0]['endOffset'] 89 | for j in range(ranges_length): 90 | if j < ranges_length: 91 | next_start_offset = ranges[j + 1]['startOffset'] 92 | next_end_offset = ranges[j + 1]['endOffset'] 93 | if start_offset <= ( 94 | next_end_offset + 1) and start_offset >= next_start_offset and end_offset >= next_end_offset: 95 | start_offset = next_start_offset 96 | copy_ranges.remove(ranges[j + 1]) 97 | elif end_offset >= ( 98 | next_start_offset - 1) and end_offset <= next_end_offset and start_offset <= next_start_offset: 99 | end_offset = next_end_offset 100 | copy_ranges.remove(ranges[j + 1]) 101 | elif start_offset >= next_start_offset and end_offset <= next_end_offset: 102 | start_offset = next_start_offset 103 | end_offset = next_end_offset 104 | copy_ranges.remove(ranges[j + 1]) 105 | elif start_offset <= next_start_offset and end_offset >= next_end_offset: 106 | copy_ranges.remove(ranges[j + 1]) 107 | copy_ranges.remove(copy_ranges[0]) 108 | merge_list.append({ 109 | 'startOffset': start_offset, 110 | 'endOffset': end_offset, 111 | 'count': 0 112 | }) 113 | if len(copy_ranges) == 0: 114 | return 115 | else: 116 | _merge(copy_ranges) 117 | _merge(list) 118 | return merge_list 119 | 120 | def cov_domain_filter(self, covdata, domains): 121 | """ 122 | 过滤统计结果,只保留含有指定域名称的js统计结果 123 | :param covdata: 覆盖率统计结果 124 | :param domains: 指定的域名称 125 | :return: 过滤后的统计结果 126 | """ 127 | covdata = covdata['result']['result'] 128 | cov_filter_datas = [] 129 | if domains: 130 | for data in covdata: 131 | for domain in domains: 132 | if domain in data['url']: 133 | cov_filter_datas.append(data) 134 | return cov_filter_datas 135 | else: 136 | return covdata 137 | 138 | def cov_not_count_filter(self, covdata): 139 | """ 140 | 过滤统计结果,只保留未被覆盖到的函数统计结果 141 | :param covdata: cov_domain_filter()后的覆盖率统计结果 142 | :return: 过滤后的统计结果 143 | """ 144 | copy_cov = copy.deepcopy(covdata) 145 | #如果是被覆盖到函数,则移除该函数的统计数据 146 | for index,data in enumerate(covdata): 147 | for f_index,function in enumerate(data['functions']): 148 | for range in function['ranges']: 149 | if range['count'] is not 0: 150 | copy_cov[index]['functions'][f_index]['ranges'].remove(range) 151 | covdata = copy.deepcopy(copy_cov) 152 | #如果某函数统计数据为空,则移除该函数 153 | for index,data in enumerate(copy_cov): 154 | for function in data['functions']: 155 | if len(function['ranges']) == 0: 156 | covdata[index]['functions'].remove(function) 157 | return covdata 158 | 159 | def code_html_filter(self, codedata): 160 | """ 161 | 转义js代码里的html标签防止干扰展示 162 | :param codedata: 待转义的js代码 163 | :return: 转义后的js代码 164 | """ 165 | codedata = codedata.replace("<", "<") 166 | codedata = codedata.replace(">", "<") 167 | codedata = codedata.replace("""<font style="background:yellow" bgcolor="yellow"<""", """""") 168 | codedata = codedata.replace("</font<", "") 169 | return codedata 170 | 171 | def make_jslist_tmp(self, pid, url): 172 | """ 173 | 生成js列表部分的html模板填充方法 174 | :param pid: js的id 175 | :param url: js的url路径 176 | :return: 填充好的模板 177 | """ 178 | jslist_tmp = self.JSLISTTEMPLET % dict( 179 | pid = pid, 180 | js_name=url 181 | ) 182 | return jslist_tmp 183 | 184 | def make_codepre_tmp(self,pid,url,positions): 185 | """ 186 | 生成js代码块部分的html模板填充方法 187 | :param pid: 与js的id对应的id 188 | :param url: 获取js代码用的url 189 | :param positions: 未被覆盖的js的索引位置 190 | :return: 填充好的模板 191 | """ 192 | codepre_tmp = self.CODETEMPLET % dict( 193 | pid=pid, 194 | code_data=self.insert_backgroud(self.get_js(url), positions) 195 | ) 196 | return codepre_tmp 197 | 198 | def make_cov_profiler_temp(self, covdata): 199 | """ 200 | 生成最终的覆盖率模板的填充方法 201 | :param covdata: 覆盖率数据 202 | :return: 填充好的模板 203 | """ 204 | jslists_tmp = "" 205 | codepres_tmp = "" 206 | for index,data in enumerate(covdata): 207 | if data['url'].count('.js') and len(data['functions']) > 0: 208 | url = data['url'].replace(' ', '') 209 | jslists_tmp=jslists_tmp+(self.make_jslist_tmp(index,url)) 210 | positions = [] 211 | for function in data['functions']: 212 | for offset in function['ranges']: 213 | startOffset = offset['startOffset'] 214 | endOffset = offset['endOffset'] 215 | positions.append((startOffset,endOffset)) 216 | codepres_tmp=codepres_tmp+(self.make_codepre_tmp(index,url,positions)) 217 | cov_profiler_temp = [jslists_tmp, codepres_tmp] 218 | return cov_profiler_temp 219 | 220 | def insert_backgroud(self, codedata, positions): 221 | """ 222 | 将显示未覆盖代码的背景颜色的html标签,插入原始的js代码中,未覆盖代码会有黄色背景作为展示 223 | 【!重要的】这个方法采用了较为牺牲性能(比较懒)的插入方法,会对统计速度产生严重影响! 224 | :param codedata: 原始的js代码 225 | :param positions: 未被覆盖的js代码的索引 226 | :return: 插入背景色完毕的代码 227 | """ 228 | sourcedata = copy.deepcopy(codedata) 229 | for index,position in enumerate(positions): 230 | codedata = list(codedata) 231 | count_code = "".join(sourcedata[position[0]:position[1]]) 232 | if len(count_code) > 0 and count_code is not " ": 233 | insert_complet_str = """"""+count_code+"" 234 | codedata = "".join(codedata) 235 | codedata = codedata.replace(count_code,insert_complet_str,1) 236 | codedata = "".join(codedata) 237 | codedata = self.code_html_filter(codedata) 238 | return codedata 239 | 240 | def make_profiler_report(self, covdata, stream): 241 | """ 242 | 生成报告的方法 243 | :param covdata:覆盖率数据 244 | :param stream:写入流 245 | :return: 246 | """ 247 | cov_profiler_temp = self.make_cov_profiler_temp(covdata) 248 | templet = self.MINTEMPLET % dict( 249 | jslist = cov_profiler_temp[0], 250 | pre = cov_profiler_temp[1] 251 | ) 252 | stream.write(templet.encode('utf8')) 253 | 254 | def get_js(self, url): 255 | """ 256 | 如果是本地文件则打开本地文件,不是则通过js的网络路径获取其代码 257 | :param url: js的网络路径 258 | :return: 获取到的js代码 259 | """ 260 | if url.startswith('file:'): 261 | url_path = url[8:] 262 | with open(url_path, 'rb') as stream: 263 | js = stream.read() 264 | js = js.decode(encoding="utf-8") 265 | else: 266 | js = requests.get(url=url).text 267 | return js 268 | 269 | def make_covdata_file(self,cov_path,covdata,domains): 270 | """ 271 | 接受原始的覆盖率数据,过滤并和本地数据对比(如果没有则生成),写入本地 272 | 【!你来你也这么多for】 273 | :param covdata:原始覆盖率数据 274 | :return: 275 | """ 276 | covdata = self.merge_same_func_ranges(self.cov_not_count_filter(self.cov_domain_filter(covdata,domains))) 277 | try: 278 | with open(cov_path, 'rb') as cov_stream: 279 | covfile = pickle.load(cov_stream) 280 | covfile = self.merge_same_func_ranges(covfile) 281 | #如果新文件含有本地文件没有的js的统计情况,则在本地文件追加该js的统计情况 282 | #todo 这里jsid需要改为用jsname作为标识值 283 | covfile_jid = [data['scriptId'] for data in covfile] 284 | covdata_jid = [data['scriptId'] for data in covdata] 285 | difference_jid_list = [jid for jid in covdata_jid if jid not in covfile_jid] 286 | for item in difference_jid_list: 287 | for data in covdata: 288 | if data['scriptId'] == item: 289 | covfile.append(data) 290 | for item in covdata: 291 | scriptId = item['scriptId'] 292 | for fileitem in covfile: 293 | if fileitem['scriptId'] == scriptId: 294 | #如果新文件的js统计中,有方法的统计情况和本地文件的该js的该方法的统计情况有出入,则按规则在本地文件内追加或者修改该统计情况 295 | for file_func in fileitem['functions']: 296 | funcname = file_func['functionName'] 297 | data_func_ranges = [func['ranges'] for func in item['functions'] if func['functionName'] == funcname] 298 | data_func_ranges = [func_range for func_ranges in data_func_ranges for func_range in func_ranges] 299 | file_func_ranges = file_func['ranges'] 300 | if data_func_ranges != file_func_ranges: 301 | ranges = file_func_ranges + [item for item in data_func_ranges if item not in file_func_ranges] 302 | ranges = self.by_rule_merge_ranges(ranges) 303 | file_func['ranges'] = ranges 304 | #如果新文件的js统计中,含有本地文件没有的该js的方法的统计情况,则在本地文件的该js统计中,追加该方法的统计情况 305 | difference_func_list = [func for func in item['functions'] if func not in fileitem['functions'] ] 306 | if difference_func_list: 307 | #print(difference_func_list) 308 | fileitem['functions'] = fileitem['functions']+difference_func_list 309 | with open(cov_path, 'wb') as cov_stream: 310 | pickle.dump(covfile, cov_stream) 311 | return covfile 312 | except FileNotFoundError: 313 | with open(cov_path, 'wb') as cov_stream: 314 | pickle.dump(covdata, cov_stream) 315 | return covdata 316 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 107 | 108 | 109 | 110 | div_id 111 | showTestDetail 112 | HTML_TMPL 113 | chart_script 114 | stream 115 | showOutput 116 | font style="background:yellow 117 | hahahahah 118 | hahaha 119 | 17 120 | 999 121 | push.apply 122 | Chrome 123 | applyToTag 124 | cb 125 | push.2y7i._typeof 126 | wewewewewewewewewew 127 | wocao 128 | {'scriptId': '17' 129 | 'scriptId': '17' 130 | requestId 131 | response 132 | 133 | 134 | 135 | 170 | 171 | 172 | 173 | 174 | true 175 | DEFINITION_ORDER 176 | 177 | 178 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 |