def detail_check(expected, real): logger.info('EXPECTED:%s' % repr(expected)) logger.info('REAL:%s' % repr(real)) if isinstance(expected, str): expect = expected try: expect = ast.literal_eval(expected) except: pass if isinstance(expect, list): for item in expect: if detail_match(item, real): flag = 1 break assert flag == 1 else: # expected = str(expected) assert detail_match(expected, real) is True elif isinstance(expected, int): real = str2int(real) assert real == round(expected) elif isinstance(expected, float): t, p1 = str2float(real) d, p2 = str2float(expected) p = min(p1, p2) assert round(t, p) == round(d, p) elif expected is None: assert real == ''
def send_markdown(self): """ markdown类型 :param title: 首屏会话透出的展示内容 :param text: markdown格式的消息内容 :return: 返回消息发送结果 """ text = '# ** 自动化测试报告 **\n ' + \ '* 开始执行时间:' + '**' + g.results['beginTime'] + '**' + ' ;\n' + \ '* 测试用例总数:' + '**' + str(g.results['testAll']) + '**' + ' ;\n' + \ '* 测试通过:' + '**' + str(g.results['testPass']) + '**' + ' ;\n' + \ '* 测试失败:' + '**' + str(g.results['testFail']) + '**' + ' ;\n' + \ '* 测试跳过:' + '**' + str(g.results['testSkip']) + '**' + ' ;\n' + \ '* 运行时间:' + '**' + str(g.results['totalTime']) + '**' + ' 。 \n' + \ '> ![自动化](http://www.11506.com/uploadfile/2018/1024/20181024102305336.jpg)\n' if isNotNull(title) and isNotNull(text): data = { "msgtype": "markdown", "markdown": { "title": title, "text": text }, "at": {} } logger.info("markdown类型:%s" % data) return self.post(data) else: logger.error("markdown类型中消息标题或内容不能为空!") raise ValueError("markdown类型中消息标题或内容不能为空!")
def call(step): output = step['output'] flag = 0 if output.get('数据结构', '') == '是' or output.get('datatype', '').lower() in ('y', 'yes'): flag = 1 if output: logger.info('Call function: %s' % repr(output)) for key in output: if key not in ('数据结构', 'datatype'): value = output[key].split('.') s = 'from sweetest.lib' + ' import ' + value[0].strip() exec(s) p = re.compile(r'[(](.*?)[)]', re.S) params = re.findall(p, value[1])[0] if len(params): params = params.split(',') for i in range(len(params)): params[i] = replace(params[i], flag) if len(params) == 1: params = params[0] g.var[key] = getattr(eval(value[0].strip()), value[1].split('(')[0].strip())(params) else: params = tuple(params) g.var[key] = getattr(eval(value[0].strip()), value[1].split('(')[0].strip())(*params) else: g.var[key] = getattr(eval(value[0].strip()), value[1].split('(')[0].strip())() logger.info('g.var[' + key +']=' + str(g.var[key]))
def current_url(cls, data, output): logger.info('DATA:%s' % repr(data['text'])) logger.info('REAL:%s' % repr(g.driver.current_url)) if data['text'].startswith('*'): assert data['text'][1:] in g.driver.current_url else: assert data['text'] == g.driver.current_url # 只能获取到元素 url for key in output: g.var[key] = g.driver.current_url
def title(cls, data, output): logger.info('DATA:%s' % repr(data['text'])) logger.info('REAL:%s' % repr(g.driver.title)) if data['text'].startswith('*'): assert data['text'][1:] in g.driver.title else: assert data['text'] == g.driver.title # 只能获取到元素标题 for key in output: g.var[key] = g.driver.title
def retrline(self, lines, n): ''' 获取邮件第n行的内容 :param lines: 邮件内容 :param n: 行数 :return: 返回第n行的内容内容 ''' line = lines[n].decode('utf-8') logger.info(line) return line
def sendmail(self): ''' 发送邮件 ''' try: self.server.sendmail(mail_username, mail_receiver, self.msg) self.server.quit() except: logger.info("***邮件没有发送成功!***") sys.exit(1) else: logger.info("***邮件发送成功!***")
def message(step): data = step['data'] text = data.get('text', '') element = step['element'] el, value = get_elem(element) if value.lower() in ('确认', 'accept'): g.driver.switch_to_alert().accept() elif value.lower() in ('取消', '关闭', 'cancel', 'close'): g.driver.switch_to_alert().dismiss() elif value.lower() in ('输入', 'input'): g.driver.switch_to_alert().send_keys(text) g.driver.switch_to_alert().accept() logger.info('--- Switch Frame: Alert') w.frame = 'Alert'
def import_suite(sheet_name): ''' 从excel文件中获取、转换测试用例集 :param sheet_name: excel表单形式的测试用例集 :return: 返回转换后的测试用例集 testsuite 是 testcase的list testcase是dict,其中steps是由测试步骤组成的list(步骤是dict结构) ''' try: testsuite = testsuite_format(data2dict(g.testcase_workbook.read(sheet_name))) logger.info('从Excel中读取测试用例集:\n' + json.dumps(testsuite, ensure_ascii=False, indent=4)) logger.info('从Excel中读取测试用例集成功!') return testsuite except: logger.exception('*** 从Excel中读取测试用例集失败 ***') g.code = -1 sys.exit(g.code)
def sql(step): element = step['element'] el, _sql = get_elem(element) logger.info('SQL: %s' % repr(_sql)) # 获取连接参数 el, value = get_elem(step['page'] + '-' + 'config') arg = data_format(value) if step['page'] not in g.db.keys(): g.db[step['page']] = DB(arg) if _sql.lower().startswith('select'): row = g.db[step['page']].fetchone(_sql) logger.info('SQL result: %s' % repr(row)) if not row: raise Exception('*** Fetch None ***') else: g.db[step['page']].execute(_sql) result = {} if _sql.lower().startswith('select'): keys = _sql[6:].split('FROM')[0].split('from')[0].strip().split(',') for i,k in enumerate(keys): keys[i] = k.split(' ')[-1] result = dict(zip(keys, row)) logger.info('keys result: %s' % repr(result)) data = step['data'] if not data: data = step['expected'] if data: for key in data: logger.info('key: %s, expect: %s, real: %s' % (repr(key), repr(data[key]), repr(result[key]))) if data[key].startswith('*'): assert data[key][1:] in result[key] else: assert data[key] == result[key] output = step['output'] if output: logger.info('output: %s' % repr(output)) for key in output: g.var[key] = result[output[key]]
def get_Ouput(step, element_location): output = step['output'] flag = 0 if output.get('数据结构', '') == '是' or output.get('datatype', '').lower() in ('y', 'yes'): flag = 1 for key in output: if key not in ('数据结构', 'datatype'): if output[key] == 'text': g.var[key] = element_location.text elif output[key] in ('text…', 'text...'): if element_location.text.endswith('...'): g.var[key] = element_location.text[:-3] else: g.var[key] = element_location.text elif re.findall(r'<(.*?)>', output[key]): g.var[key] = replace(output[key], flag) else: g.var[key] = element_location.get_attribute(output[key]) logger.info('g.var[' + key + ']=' + g.var[key])
def tab_name(step): element = step['element'] name = step['data']['text'] # 从所有窗口中查找给定元素,如果查询到就命名,否则报错 all_handles = g.driver.window_handles logger.info('All Handles: %s' % all_handles) flag = False for handle in all_handles: #logger.info('Page Source: %s \n%s' % (handle, g.driver.page_source)) #logger.info('All Windows: %s' %w.windows) if handle not in w.windows.values(): # 切换至此窗口 g.driver.switch_to_window(handle) try: # 成功定位到关键元素 element_location = locating_element(element, 'CLICK') # 添加到窗口资源池 g.windows w.windows[name] = handle # 把当前窗口名字改为新窗口名称 w.current_window = name flag = True logger.info('Current Window: %s' % repr(name)) logger.info('Current Handle: %s' % repr(handle)) except Exception as exception: pass if not flag: raise Exception( 'Tab Name Fail: the element:%s in all tab is not found' % element)
def register(self, step, handle): ''' 注册新标签页,打开链接或点击新开窗口时调用 :param step: 测试步骤 :param handle: 窗口句柄 :return: ''' # 如果有提供新窗口名字,则使用该名字,否则使用默认名字:HOME new_page = 'HOME' for k in ('新窗口', '标签页名', 'tabname'): if step['data'].get(k): new_page = step['data'].get(k) # 已存在同名的窗口,则 if new_page in list(self.pages) and handle != self.pages[new_page]: # 1. 切换到同名旧页面去关闭它 g.driver.switch_to_window(self.pages[new_page]) g.driver.close() # 2. 清除旧页面 self.pages.pop(new_page) # 然后切回当前窗口 g.driver.switch_to_window(handle) # 再添加到窗口资源池 g.pages self.pages[new_page] = handle # 把当前窗口名字改为新窗口名称 self.current_page = new_page logger.info('--- 注册新页面: %s' % repr(new_page)) logger.info('Pages:') logger.info(self.pages) # 新窗口标志置为是 self.new_window_flag = True
def switch_frame(self, frame): if frame.strip(): frame = [x.strip() for x in frame.split('|')] if frame != self.frame: if self.frame != 0: g.driver.switch_to.default_content() for f in frame: logger.info('--- Frame值: %s' % repr(f)) if f.startswith('#'): f = int(f[1:]) elif '#' in f: from sweetest.parse import elements_incase_format from sweetest.locator import locating_element element = elements_incase_format('通用', f)[2] f = locating_element(element) logger.info('--- 切换Frame: %s' % repr(f)) g.driver.switch_to.frame(f) self.frame = frame else: if self.frame != 0: g.driver.switch_to.default_content() self.frame = 0
def switch_context(self, context): if context.strip() == '': context = 'NATIVE_APP' logger.info('--- ALL Contexts:%s' % g.driver.contexts) logger.info('--- Input Context:%s' % repr(context)) if context != self.current_context: if context == '': context = None logger.info('--- Switch Context:%s' % repr(context)) g.driver.switch_to.context(context) self.current_context = context
def judge(step): data = step['data'] element = step['element'] try: element_location = locating_element(element) flag = 1 except: flag = 2 logger.info('REAL:%s' % repr(flag)) if step['data'].get('存在', '') == '否' or step['data'].get( 'judge', '').lower() == 'no': logger.info('DATA:%s' % repr(2)) assert flag == 2 else: logger.info('DATA:%s' % repr(1)) assert flag == 1
def __call__(self, *args, **kwargs): if_result = '' if self.testcase: self.testcase['result'] = 'Pass' for index, step in enumerate(self.testcase.get('steps')): # if 为否,不执行 then 语句 if step['control'] == '>' and not if_result: step['score'] = '-' continue # if 为真,不执行 else 语句 if step['control'] == '<' and if_result: step['score'] = '-' continue logger.info('执行测试步骤: %s|%s|%s' % (step['no'], step['keyword'], step['element'])) try: # 变量替换 replace_dict(step['data']) replace_dict(step['expected']) if isinstance(step['element'], str): step['element'] = replace(step['element']) elif isinstance(step['element'], list): step['element'] = replace_list(step['element']) # 处理强制等待时间 t = step['data'].get('等待时间') if t: del step['data']['等待时间'] sleep(float(t)) if g.platform.lower() in ( 'desktop', ) and step['keyword'] in web_keywords: if step['keyword'] not in ('MESSAGE', '对话框'): # 判断页面是否已和窗口做了关联,如果没有,就关联当前窗口,如果已关联,则判断是否需要切换 w.switch_window(step['page']) # 切换 frame 处理,支持变量替换 frame = replace(step['custom']) w.switch_frame(frame) sleep(2) # 根据关键字调用关键字实现 getattr(web, step['keyword'].lower())(step) elif g.platform.lower() in ( 'ios', 'android') and step['keyword'] in mobile_keywords: # 切換 context 處理 context = replace(step['custom']).strip() current_context = w.switch_context(context) if w.current_context.startswith('WEBVIEW'): # 切换标签页 tab = step['data'].get('标签页') if tab: del step['data']['标签页'] g.driver.switch_to_window(w.windows[tab]) logger.info('Current Context: %s' % repr(w.current_context)) # 根据关键字调用关键字实现 getattr(mobile, step['keyword'].lower())(step) elif step['keyword'] in http_keywords: # 根据关键字调用关键字实现 getattr(http, step['keyword'].lower())(step) elif step['keyword'].lower() == 'execute': if step['page'] in ('SNIPPET', '用例片段'): step['snippets'] = [] result, step['snippets'] = getattr( common, step['keyword'].lower())(step) logger.info('用例片段执行结果:\n') logger.info(result) self.testcase['result'] = result if result != 'Pass': break # elif step['page'] in ('SCRIPT', '脚本'): # # 判断页面是否已和窗口做了关联,如果没有,就关联当前窗口,如果已关联,则判断是否需要切换 # w.switch_window(step['page']) # # 切换 frame 处理,支持变量替换 # frame = replace(step['custom']) # w.switch_frame(frame) # common.script(step) else: # 根据关键字调用关键字实现 getattr(common, step['keyword'].lower())(step) logger.info('执行测试步骤通过: %s|%s|%s' % (step['no'], step['keyword'], step['element'])) step['score'] = 'OK' # if 语句结果赋值 if step['control'] == '^': if_result = True # 操作后,等待0.2秒 sleep(0.2) except Exception as exception: file_name = g.project_name + '-' + g.sheet_name + '-' + g.start_time + \ '#' + self.testcase['id'] + \ '-' + str(step['no']) + '.png' snapshot_file = str(Path('snapshot') / file_name) if g.platform.lower() in ( 'desktop', ) and step['keyword'] in web_keywords: try: g.driver.get_screenshot_as_file(snapshot_file) except: logger.exception('*** 保存截图失败! ***') elif g.platform.lower() in ( 'ios', 'android') and step['keyword'] in mobile_keywords: try: g.driver.switch_to_default_content() w.current_context = 'NATIVE_APP' g.driver.get_screenshot_as_file(snapshot_file) except: logger.exception('*** 保存截图失败! ***') logger.exception( '执行测试步骤失败: %s|%s|%s' % (step['no'], step['keyword'], step['element'])) step['score'] = 'NO' # if 语句结果赋值 if step['control'] == '^': if_result = False continue self.testcase['result'] = 'Fail' step['remark'] += str(exception) break if self.testcase['condition'] in ['', None]: self.testcase['setup'] = g.setup_testcase if self.testcase['condition'] in ['teardown', 'TEARDOWN']: case = {} case['id'] = self.testcase.get('id') case['title'] = self.testcase.get('title') case['result'] = self.testcase.get('result') case['steps'] = self.testcase.get('steps') g.teardowns.append(case) assert self.testcase['result'] == 'Pass'
def get_result(): logger.info('测试结果:\n' + json.dumps(g.results, ensure_ascii=False, indent=4)) return g.results
def startTest(self, test): super(TextTestResult, self).startTest(test) if self.showAll: logger.info(self.getDescription(test) + "用例开始运行" + "... ")
def run(self, test): "Run the given test case or test suite." result = self._makeResult() registerResult(result) result.failfast = self.failfast result.buffer = self.buffer result.tb_locals = self.tb_locals with warnings.catch_warnings(): if self.warnings: # if self.warnings is set, use it to filter all the warnings warnings.simplefilter(self.warnings) # if the filter is 'default' or 'always', special-case the # warnings from the deprecated unittest methods to show them # no more than once per module, because they can be fairly # noisy. The -Wd and -Wa flags can be used to bypass this # only when self.warnings is None. if self.warnings in ['default', 'always']: warnings.filterwarnings( 'module', category=DeprecationWarning, message=r'Please use assert\w+ instead.') startTime = time.time() startTestRun = getattr(result, 'startTestRun', None) if startTestRun is not None: startTestRun() try: test(result) finally: stopTestRun = getattr(result, 'stopTestRun', None) if stopTestRun is not None: stopTestRun() stopTime = time.time() timeTaken = stopTime - startTime result.printErrors() if hasattr(result, 'separator2'): logger.info(result.separator2) run = result.testsRun logger.info("Ran %d test%s in %.3fs\r\n" % (run, run != 1 and "s" or "", timeTaken)) # 记录测试运行时间 g.results['totalTime'] = str(round(timeTaken, 2)) + 's' expectedFails = unexpectedSuccesses = skipped = 0 try: results = map(len, (result.expectedFailures, result.unexpectedSuccesses, result.skipped)) except AttributeError: pass else: expectedFails, unexpectedSuccesses, skipped = results infos = [] if not result.wasSuccessful(): logger.error("FAILED!" + "\r\n") failed, errored = len(result.failures), len(result.errors) if failed: infos.append("failures=%d" % failed) if errored: infos.append("errors=%d" % errored) else: logger.info("SUCCESS!" + "\r\n") if skipped: infos.append("skipped=%d" % skipped) if expectedFails: infos.append("expected failures=%d" % expectedFails) if unexpectedSuccesses: infos.append("unexpected successes=%d" % unexpectedSuccesses) if infos: logger.info(" (%s)" % (", ".join(infos), )) else: logger.info("\n") return result
def addUnexpectedSuccess(self, test): super(TextTestResult, self).addUnexpectedSuccess(test) if self.showAll: logger.info("unexpected success") elif self.dots: logger.info("u")
def addExpectedFailure(self, test, err): super(TextTestResult, self).addExpectedFailure(test, err) if self.showAll: logger.info("expected failure") elif self.dots: logger.info("x")
def addSkip(self, test, reason): super(TextTestResult, self).addSkip(test, reason) if self.showAll: logger.info("skipped {0!r}".format(reason)) elif self.dots: logger.info("s")
def addFailure(self, test, err): super(TextTestResult, self).addFailure(test, err) if self.showAll: logger.error("测试失败" + "\r\n") elif self.dots: logger.info('F')
def addSuccess(self, test): super(TextTestResult, self).addSuccess(test) if self.showAll: logger.info("测试通过" + "\r\n") elif self.dots: logger.info('.')
def check(step): data = step['data'] if not data: data = step['expected'] element = step['element'] element_location = locating_element(element) if '#' in element: e_name = element.split('#')[0] + '#' else: e_name = element by = g.elements[e_name]['by'] output = step['output'] if by in ('title', 'current_url'): getattr(Common, by)(data, output) else: for key in data: # 预期结果 expected = data[key] # 切片操作处理 s = re.findall(r'\[.*?\]', key) if s: s = s[0] key = key.replace(s, '') if key == 'text': real = element_location.text else: real = element_location.get_attribute(key) if s: real = eval('real' + s) logger.info('DATA:%s' % repr(expected)) logger.info('REAL:%s' % repr(real)) if isinstance(expected, str): if expected.startswith('*'): assert expected[1:] in real else: assert expected == real elif isinstance(expected, int): real = str2int(real) assert real == round(expected) elif isinstance(expected, float): t, p1 = str2float(real) d, p2 = str2float(expected) p = min(p1, p2) assert round(t, p) == round(d, p) elif expected is None: assert real == '' # 获取元素其他属性 for key in output: if output[key] == 'text': g.var[key] = element_location.text elif output[key] in ('text…', 'text...'): if element_location.text.endswith('...'): g.var[key] = element_location.text[:-3] else: g.var[key] = element_location.text else: g.var[key] = element_location.get_attribute(output[key])
def request(kw, step): element = step['element'] el, url = get_elem(element) if url.startswith('/'): url = url[1:] data = step['data'] _data = {} _data['headers'] = json2dict(data.pop('headers', '{}')) if data.get('cookies'): data['cookies'] = json2dict(data['cookies']) if kw == 'get': _data['params'] = json2dict(data.pop('params', '{}')) or json2dict( data.pop('data', '{}')) elif kw == 'post': _data['data'] = json2dict(data.pop('data', '{}')) _data['json'] = json2dict(data.pop('json', '{}')) _data['files'] = eval(data.pop('files', 'None')) elif kw in ('put', 'patch'): _data['data'] = json2dict(data.pop('data', '{}')) for k in data: for s in ('{', '[', 'False', 'True'): if s in data[k]: try: data[k] = eval(data[k]) except Exception as exception: logger.warning('Try eval data fail: %s' % data[k]) break expected = step['expected'] expected['status_code'] = expected.get('status_code', None) expected['text'] = expected.get('text', None) expected['json'] = json2dict(expected.get('json', '{}')) expected['cookies'] = json2dict(expected.get('cookies', '{}')) expected['headers'] = json2dict(expected.get('headers', '{}')) if not g.http.get(step['page']): g.http[step['page']] = Http(step) http = g.http[step['page']] if kw == 'post': if http.headers_post: http.r.headers.update(eval(http.headers_post)) else: if http.headers_get: http.r.headers.update(eval(http.headers_get)) logger.info('URL: %s' % http.baseurl + url) # 处理 before_send before_send = data.pop('before_send', '') if before_send: _data, data = before_send(_data, data) else: _data, data = before_send(_data, data) if _data['headers']: for k in [x for x in _data['headers']]: if not _data['headers'][k]: del http.r.headers[k] del _data['headers'][k] http.r.headers.update(_data['headers']) if kw == 'get': r = getattr(http.r, kw)(http.baseurl + url, params=_data['params'], **data) elif kw == 'post': r = getattr(http.r, kw)(http.baseurl + url, data=_data['data'], json=_data['json'], files=_data['files'], **data) elif kw in ('put', 'patch'): r = getattr(http.r, kw)(http.baseurl + url, data=_data['data'], **data) elif kw in ('delete', 'options'): r = getattr(http.r, kw)(http.baseurl + url, **data) logger.info('status_code: %s' % repr(r.status_code)) try: # json 响应 logger.info('response json: %s' % repr(r.json())) except: # 其他响应 logger.info('response text: %s' % repr(r.text)) response = { 'status_code': r.status_code, 'headers': r.headers, '_cookies': r.cookies, 'content': r.content, 'text': r.text } try: response['cookies'] = requests.utils.dict_from_cookiejar(r.cookies) except: response['cookies'] = r.cookies try: j = r.json() response['json'] = j except: response['json'] = {} # 处理 after_receive after_receive = expected.pop('after_receive', '') if after_receive: response = after_receive(response) else: response = after_receive(response) if expected['status_code']: assert str(expected['status_code']) == str(response['status_code']) if expected['text']: if expected['text'].startswith('*'): assert expected['text'][1:] in response['text'] else: assert expected['text'] == response['text'] if expected['headers']: result = check(expected['headers'], response['headers']) if result['result']: step['remark'] += str(result['result']) logger.info('headers check result: %s' % result) assert result['code'] == 0 if expected['cookies']: logger.info('response cookies: %s' % response['cookies']) result = check(expected['cookies'], response['cookies']) if result['result']: step['remark'] += str(result['result']) logger.info('cookies check result: %s' % result) assert result['code'] == 0 if expected['json']: result = check(expected['json'], response['json']) if result['result']: step['remark'] += str(result['result']) logger.info('json check result: %s' % result) assert result['code'] == 0 output = step['output'] # if output: # logger.info('output: %s' % repr(output)) for k, v in output.items(): if v == 'status_code': g.var[k] = response['status_code'] logger.info('%s: %s' % (k, repr(g.var[k]))) elif v == 'text': g.var[k] = response['text'] logger.info('%s: %s' % (k, repr(g.var[k]))) elif k == 'json': sub = json2dict(output.get('json', '{}')) result = check(sub, response['json']) # logger.info('Compare json result: %s' % result) g.var = dict(g.var, **result['var']) logger.info('json var: %s' % (repr(result['var']))) elif k == 'cookies': sub = json2dict(output.get('cookies', '{}')) result = check(sub, response['cookies']) # logger.info('Compare json result: %s' % result) g.var = dict(g.var, **result['var']) logger.info('cookies var: %s' % (repr(result['var'])))
def switch_window(self, page): ''' 切换标签页/窗口 :param page: 目标页面 :return: ''' if self.new_window_flag: if page in list(self.pages): page = '通用' g.current_page = '通用' self.new_window_flag = False if page != '通用': if page not in list(self.pages): # 如果当前页未注册,则把当前窗口捆定到当前页面 for k in list(self.pages): if g.driver.current_window_handle == self.pages[k]: self.pages.pop(k) self.pages[page] = g.driver.current_window_handle self.current_page = page logger.info('--- 注册标签页: %s' % repr(page)) logger.info('Pages:') logger.info(self.pages) elif self.pages[page] != g.driver.current_window_handle: # 如果当前窗口为 HOME,则关闭之 if self.current_page == 'HOME': g.driver.close() self.pages.pop('HOME') # 再切换到需要操作的窗口 tw = self.pages[page] logger.info('--- 旧窗口: %s' % g.driver.current_window_handle) logger.info('--- 切换到窗口: %s' % repr(tw)) g.driver.switch_to_window(tw) self.current_page = page logger.info('--- 当前标签页: %s' % repr(page)) logger.info('Pages:') logger.info(self.pages)