python selenium+pytest webUI自动化基础框架

一、框架目录结构

 

cases:存放自动化测试用例脚本,脚本按业务模块划分子目录;

common:存放框架基础功能方法,如配置解析config.py等;

config:存放配置文件;

driver:存放浏览器webdriver;

page_element: 存放web页面元素对象;

pages:存放自动化测试用例脚本的基础方法封装,用例脚本由这些方法组合完成;

results:存放脚本日志及报告;

utils:存放工具类

 

 二、common目录

 

 

 1.readconfig.py

用于读取解析config下面的配置文件

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import configparser
from config.conf import cm

WEB_INFO = 'web info'
LOGLEVEL = 'loglevel'
FIREFOX_BIN = 'firefox_binary'
TESTCASE = 'testcase'
CONTROLLER_INFO = 'controller_info'

class ReadConfig(object):
    """配置文件"""

    def __init__(self):
        self.config = configparser.RawConfigParser()
        self.config.read(cm.ini_file, encoding='utf-8')

    def _get(self, section, option):
        """获取"""
        return self.config.get(section, option)

    def _set(self, section, option, value):
        """更新"""
        self.config.set(section, option, value)
        with open(cm.ini_file, 'w') as f:
            self.config.write(f)

    @property
    def web_url(self):
        return self._get(WEB_INFO, 'web_url')

    @property
    def web_user(self):
        return self._get(WEB_INFO, 'web_user')

    @property
    def web_passwd(self):
        return self._get(WEB_INFO, 'web_passwd')

    @property
    def loglevel(self):
        return self._get(LOGLEVEL, LOGLEVEL).upper()

    @property
    def firefox_binary(self):
        return self._get(FIREFOX_BIN, FIREFOX_BIN)

    @property
    def web_broswer(self):
        return self._get(WEB_INFO, 'web_broswer')

    @property
    def testcase(self):
        return self._get(TESTCASE, TESTCASE).split(',')

    @property
    def controller_host(self):
        return self._get(CONTROLLER_INFO, 'controller_host')

    @property
    def controller_pwd(self):
        return self._get(CONTROLLER_INFO, 'controller_pwd')


CONF = ReadConfig()

if __name__ == '__main__':
    print(CONF.web_url)
View Code

2.readelement.py

用于读取page_element目录下元素定位对象yaml文件配置

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import os
import yaml
from config.conf import cm


class Element(object):
    """获取元素"""

    def __init__(self, name):
        self.file_name = '%s.yaml' % name
        self.element_path = os.path.join(cm.ELEMENT_PATH, self.file_name)
        if not os.path.exists(self.element_path):
            raise FileNotFoundError("%s 文件不存在!" % self.element_path)
        with open(self.element_path, encoding='utf-8') as f:
            self.data = yaml.safe_load(f)

    def __getitem__(self, item):
        """获取属性"""
        data = self.data.get(item)
        if data:
            name, value = data
            return name, value
        raise ArithmeticError("{}中不存在关键字:{}".format(self.file_name, item))


if __name__ == '__main__':
    search = Element('search')
    print(search['搜索框'])
View Code

3.readtestdata.py

用于读取testdata目录下数据驱动配置的Excel数据

 1 #!/usr/bin/env python3
 2 # -*- coding:utf-8 -*-
 3 import os
 4 
 5 import pandas as pd
 6 from config.conf import cm
 7 from utils.utils import gen_random_string
 8 
 9 
10 class TestData():
11     """
12     功能:获取excel文件数据
13     参数:name:excel文件名称,不包括后缀
14         caseid:与Excel文件sheet页名称相同,建议以用例ID命名
15     返回:以列表形式返回对应sheet页所有数据
16     """
17 
18     def __init__(self, name):
19         self.file_name = '%s.xls' % name
20         self.testdata_path = os.path.join(cm.TESTDATA_PATH, self.file_name)
21         if not os.path.exists(self.testdata_path):
22             raise FileNotFoundError("%s 文件不存在!" % self.testdata_path)
23         self.testdata = pd.ExcelFile(self.testdata_path, engine='xlrd')
24 
25     def __call__(self, caseid):
26         return self.testdata.parse(str(caseid), keep_default_na=False).values.tolist()
View Code

三、config目录

 

 

 1.config.ini

配置一些环境及用例信息

2.conf.py

存储项目相关目录结构

 1 #!/usr/bin/env python3
 2 # -*- coding:utf-8 -*-
 3 import os
 4 from selenium.webdriver.common.by import By
 5 from utils.times import dt_strftime
 6 
 7 
 8 class ConfigManager(object):
 9     # 项目目录
10     BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
11 
12     # 页面元素目录
13     ELEMENT_PATH = os.path.join(BASE_DIR, 'page_element')
14 
15     # 测试数据目录
16     TESTDATA_PATH = os.path.join(BASE_DIR, 'testdata')
17 
18     # 报告文件
19     REPORT_PATH = os.path.join(BASE_DIR, 'results', 'reports')
20 
21     # driver目录
22     DRIVER_PATH = os.path.join(BASE_DIR, 'driver')
23 
24     # case目录
25     CASE_PATH = os.path.join(BASE_DIR, 'cases')
26 
27     # 元素定位的类型
28     LOCATE_MODE = {
29         'css': By.CSS_SELECTOR,
30         'xpath': By.XPATH,
31         'name': By.NAME,
32         'id': By.ID,
33         'class': By.CLASS_NAME
34     }
35 
36     @property
37     def allure_json(self):
38         return os.path.join(cm.REPORT_PATH, 'allure_json')
39 
40     @property
41     def allure_xml(self):
42         return os.path.join(cm.REPORT_PATH, 'allure_xml')
43 
44     @property
45     def pytest_html(self):
46         return os.path.join(cm.REPORT_PATH, 'pytest_html')
47 
48     @property
49     def log_file(self):
50         """日志目录"""
51         log_dir = os.path.join(self.BASE_DIR, 'results', 'logs')
52         if not os.path.exists(log_dir):
53             os.makedirs(log_dir)
54         return os.path.join(log_dir, '{}.log'.format(dt_strftime('%Y%m%d')))
55 
56     @property
57     def ini_file(self):
58         """配置文件"""
59         ini_file = os.path.join(self.BASE_DIR, 'config', 'config.ini')
60         if not os.path.exists(ini_file):
61             raise FileNotFoundError("配置文件%s不存在!" % ini_file)
62         return ini_file
63 
64 
65 cm = ConfigManager()
66 if __name__ == '__main__':
67     print(cm.BASE_DIR)
View Code

四、driver目录

 

 

 1.chromedriver

谷歌浏览器驱动文件

2.gechodriver

火狐浏览器驱动文件

五、page_element目录

页面元素定位配置

 

按照页面模块创建不同的yaml文件来保存页面元素定位对象

如base.yaml,保存的是可共用(多个模块可用的)页面元素定位信息

 

六、pages目录

页面对象方法

 

 

1.base_page.py

selenium相关的基础方法,如元素定位、登录等,以及其他页面模块可共用的方法

  1 #!/usr/bin/env python3
  2 # -*- coding=utf-8 -*-
  3 
  4 #########################################################
  5 # file:     base_page.py
  6 # func:     web UI 自动化基础接口封装
  7 # author:   weiduntao
  8 # date:     2021.12.24
  9 #########################################################
 10 import os
 11 import allure
 12 from urllib import parse
 13 from selenium import webdriver
 14 from selenium.webdriver.support.ui import WebDriverWait
 15 from selenium.webdriver.support.select import Select
 16 from selenium.webdriver.support import expected_conditions as EC
 17 from selenium.common.exceptions import TimeoutException
 18 from selenium.webdriver.firefox.options import Options as firefox_op
 19 from selenium.webdriver.firefox.firefox_binary import FirefoxBinary
 20 from selenium.webdriver.firefox.service import Service
 21 from selenium.webdriver import ActionChains
 22 from selenium.webdriver.common.keys import Keys
 23 from selenium.webdriver.common.by import By
 24 from selenium.webdriver.remote.webelement import WebElement
 25 from selenium.webdriver.chrome.options import Options as chrome_op
 26 from config.conf import cm
 27 from utils.times import sleep, dt_strftime
 28 from utils.logger import Log
 29 from common.readelement import Element
 30 from common.readconfig import CONF
 31 from preset_up import PreSet
 32 import inspect
 33 
 34 base = Element('base')
 35 preset = PreSet()
 36 
 37 
 38 class BasePage():
 39     """selenium基类"""
 40 
 41     def __init__(self, driver=None):
 42         self.log = Log().logger
 43         self.report = cm.allure_json
 44         self.broswer = CONF.web_broswer or 'firefox'
 45         self.base_url = CONF.web_url
 46         self.timeout = 6
 47         if driver is None:
 48             self.set_driver(self.broswer)
 49         else:
 50             self.driver = driver
 51             self.wait = WebDriverWait(self.driver, self.timeout)
 52             self.action_chain = ActionChains(self.driver)
 53             self.refresh()
 54 
 55     def set_driver(self, driver):
 56         if 'chrome' == driver.lower().strip():
 57             options = chrome_op()
 58             options.add_argument('--start-maximized')
 59             options.add_argument("--ignore-certificate-errors")
 60             options.add_argument("--ignore-ssl-errors")
 61             self.driver = webdriver.Chrome(os.path.join(cm.DRIVER_PATH, 'chromedriver'),
 62                                            chrome_options=options)
 63         elif 'firefox' == driver.lower().strip():
 64             binary_file = CONF.firefox_binary or '/usr/bin/firefox-esr'
 65             executable_path = os.path.join(cm.DRIVER_PATH, 'geckodriver')
 66             options = firefox_op()
 67             options.binary = FirefoxBinary(binary_file)
 68             service = Service(executable_path=executable_path)
 69             my_profile = webdriver.FirefoxProfile()
 70             my_profile.accept_untrusted_certs = True
 71             self.driver = webdriver.Firefox(firefox_profile=my_profile, options=options,
 72                                             service=service)
 73             self.driver.maximize_window()
 74         else:
 75             raise Exception('暂不支持%s浏览器驱动' % driver)
 76         self.wait = WebDriverWait(self.driver, self.timeout)
 77         self.action_chain = ActionChains(self.driver)
 78 
 79     def check_driver_quit(self):
 80         """校验webdriver是否quit"""
 81         if self.driver.service.process is not None:
 82             return False
 83         return True
 84 
 85     def get_url(self, url):
 86         """打开网址并验证"""
 87         url = parse.urljoin(self.base_url, url)
 88         self.driver.set_page_load_timeout(60)
 89         try:
 90             self.driver.implicitly_wait(self.timeout)
 91             self.driver.get(url)
 92             self.log.info("打开网页:%s" % url)
 93         except TimeoutException:
 94             self.driver.quit()
 95             raise TimeoutException("打开%s超时请检查网络或网址服务器" % url)
 96 
 97     def login(self, username=None, passwd=None):
 98         # self.set_driver(self.broswer)
 99         self.get_url('/auth/login')
100         username = username or CONF.web_user
101         passwd = passwd or CONF.web_passwd
102         self.input_text(base['用户名'], username)
103         self.input_text(base['密码'], passwd)
104         sleep()
105         self.click(base['登录'])
106         self.log.info("用户:%s登录网页:%s成功" % (username, self.base_url))
107 
108     def relogin(self, user=preset.pre_set_data['user'], passwd=preset.pre_set_data['passwd']):
109110         # date:     2022.03.03
111         self.personal_center('登出')
112         sleep()
113         self.input_text(base['用户名'], user)
114         self.input_text(base['密码'], passwd)
115         sleep()
116         self.click(base['登录'])
117         self.log.info("用户:%s登录网页:%s成功" % (user, self.base_url))
118 
119     def personal_center(self, operation):
120         """点击页面右上角-用户名-进入个人中心"""
121         # 参数operation表示个人中心的操作,可选个人信息、修改密码、登出
122         self.log.info(inspect.getdoc(self.personal_center) + '-' + operation)
123         try:
124             self.click(base['个人中心'])
125             self.click(base['个人中心_%s' % operation])
126         except:
127             self.assert_result(False, msg='[ERROR]点击个人中心-%s操作失败' % operation)
128 
129     @staticmethod
130     def element_locator(func, locator):
131         """元素定位器"""
132         name, value = locator
133         return func(cm.LOCATE_MODE[name], value)
134 
135     def find_element(self, locator):
136         """寻找单个元素"""
137         return BasePage.element_locator(lambda *args: self.wait.until(
138             EC.presence_of_element_located(args)), locator)
139 
140     def find_elements(self, locator):
141         """查找多个相同的元素"""
142         return BasePage.element_locator(lambda *args: self.wait.until(
143             EC.presence_of_all_elements_located(args)), locator)
144 
145     def elements_num(self, locator):
146         """获取相同元素的个数"""
147         number = len(self.find_elements(locator))
148         self.log.info("相同元素:{}".format((locator, number)))
149         return number
150 
151     def find_element_clickable(self, locator):
152         """寻找单个可点击的元素"""
153         return BasePage.element_locator(lambda *args: self.wait.until(
154             EC.element_to_be_clickable(args)), locator)
155 
156     def find_element_by_element(self, element, locator):
157         """通过已知元素查找相关元素,locator"""
158         name, value = locator
159         if name != By.XPATH:
160             raise Exception('此方法只支持xpath定位方式')
161         if value.startswith('/'):
162             value = '.' + value
163         return WebDriverWait(element, self.timeout).until(EC.presence_of_element_located((name,
164                                                                                           value)))
165 
166     def alert_is_present(self):
167         """告警窗口出现"""
168         return EC.alert_is_present()(self.driver)
169 
170     def input_text(self, locator, txt):
171         """输入(输入前先清空)"""
172         if isinstance(locator, WebElement):
173             ele = locator
174         else:
175             ele = self.find_element(locator)
176         sleep(0.5)
177         ele.clear()
178         sleep(0.5)
179         if txt == '':
180             ele.send_keys('a')
181             sleep(0.5)
182             ele.send_keys(Keys.BACKSPACE)
183             sleep(0.5)
184         ele.send_keys(txt)
185         self.log.info("输入文本:{}".format(txt))
186         return ele
187 
188     def reset_actions(self):
189         """清除存储在远端的动作"""
190         self.action_chain.reset_actions()
191         sleep(1)
192 
193     def click(self, locator):
194         """点击"""
195         if isinstance(locator, WebElement):
196             _ele = locator
197         else:
198             _ele = self.find_element_clickable(locator)
199         _ele.click()
200         sleep()
201         self.log.info("点击元素:{}".format(locator))
202         return _ele
203 
204     def wait_alert(self, interval=0.5, wait_count=10):
205         """等待告警窗口出现"""
206         while wait_count > 0:
207             result = self.alert_is_present()
208             if result is not False:
209                 return result
210             sleep(interval)
211             wait_count -= 1
212         raise Exception("等待告警窗口超时")
213 
214     def get_alert_text(self, interval=0.5, wait_count=10):
215         """获取alert告警内容"""
216         alert = self.wait_alert(interval=interval, wait_count=wait_count)
217         self.log.info("获取alert告警内容成功")
218         return alert.text
219 
220     def accept_alert(self, interval=0.5, wait_count=10):
221         """告警确定"""
222         alert = self.wait_alert(interval=interval, wait_count=wait_count)
223         content = alert.text
224         alert.accept()
225         self.log.info("告警窗口确定:{}".format(alert))
226         return content
227 
228     def dismis_alert(self, interval=0.5, wait_count=10):
229         """告警取消"""
230         alert = self.wait_alert(interval=interval, wait_count=wait_count)
231         content = alert.text
232         alert.dismiss()
233         self.log.info("告警窗口取消:{}".format(alert))
234         return content
235 
236     def hover(self, locator):
237         """鼠标悬停"""
238         if isinstance(locator, WebElement):
239             _ele = locator
240         else:
241             _ele = self.find_element(locator)
242         self.action_chain.move_to_element(_ele)
243         # self.action_chain.click(_ele)
244         self.action_chain.perform()
245         sleep()
246         self.log.info("悬停元素:{}".format(locator))
247         return _ele
248 
249     def move_mouse(self, xoffset, yoffset):
250         """鼠标移动到距离当前位置(x,y)"""
251         self.action_chain.move_by_offset(xoffset, yoffset).perform()
252         sleep()
253         self.log.info('鼠标移动到距离当前位置({},{})'.format(xoffset, yoffset))
254 
255     def enter(self, locator):
256         if isinstance(locator, WebElement):
257             _ele = locator
258         else:
259             _ele = self.find_element(locator)
260         _ele.send_keys(Keys.ENTER)
261         sleep()
262         self.log.info("回车元素:{}".format(locator))
263         return _ele
264 
265     def element_text(self, locator):
266         """获取当前的text"""
267         if isinstance(locator, WebElement):
268             _ele = locator
269         else:
270             _ele = self.find_element(locator)
271         _text = _ele.text
272         self.log.info("获取文本:{}".format(_text))
273         return _text.strip()
274 
275     def assert_result(self, result, expect=True, not_negative=True, msg=''):
276         """断言,not_negative=True表示正向逻辑"""
277         if isinstance(expect, bool):
278             if not_negative and (result == expect):
279                 assert True
280             elif (not not_negative) and (result != expect):
281                 assert True
282             else:
283                 self.save_screenshot(msg)
284                 self.log.error(msg, exc_info=True)
285                 assert False
286         else:
287             if not_negative and (expect in result):
288                 assert True
289             elif (not not_negative) and (expect not in result):
290                 assert True
291             else:
292                 self.save_screenshot(msg)
293                 self.log.error(msg)
294                 assert False
295 
296     @property
297     def get_source(self):
298         """获取页面源代码"""
299         return self.driver.page_source
300 
301     def refresh(self):
302         """刷新页面F5"""
303         self.driver.refresh()
304         sleep(2)
305 
306     def save_screenshot(self, msg):
307         """截图并保存"""
308         if '/' in msg:
309             msg = msg.replace('/', '-')
310         file_name = dt_strftime(fmt='%Y%m%d%H%M%S') + '-' + str(msg) + '.png'
311         file = os.path.join(self.report, file_name)
312         self.driver.save_screenshot(file)
313         allure.attach.file(file, file_name, attachment_type=allure.attachment_type.PNG)
314 
315     @property
316     def success(self):
317         return True
318 
319     @property
320     def fail(self):
321         return False
322 
323     def select_cloud_type(self, cloud_type='云平台'):
324         """云平台首页选择类型:用户中心、云平台或者运维中心"""
325         if cloud_type not in ('用户中心', '云平台', '运维中心'):
326             raise Exception('云平台类型只能是“用户中心、云平台或者运维中心”')
327         self.click(base['平台类型选择'])
328         self.click(base[cloud_type])
329         self.log.info('选择:{}'.format(cloud_type))
330 
331     def select(self, locator, by_type, type_val):
332         """
333         下拉菜单选择
334         by_type: value --> select_by_value
335                  index --> select_by_index
336                  text --> select_by_visible_text
337         type_val: 对应于by_type的对象值
338         """
339         if isinstance(locator, WebElement):
340             _ele = locator
341         else:
342             _ele = self.find_element(locator)
343         s = Select(_ele)
344         if by_type == 'value':
345             s.select_by_value(type_val)
346         elif by_type == 'index':
347             s.select_by_index(type_val)
348         elif by_type == 'text':
349             s.select_by_visible_text(type_val)
350         else:
351             raise Exception('by_type is enum:value/index/text')
352         self.log.info('下拉菜单选择:{}'.format(type_val))
353         return s.all_selected_options
354 
355     def deselect(self, locator, by_type, type_val):
356         """
357         下拉菜单去勾选
358         by_type: value --> select_by_value
359                  index --> select_by_index
360                  text --> select_by_visible_text
361         type_val: 对应于by_type的对象值
362         """
363         if isinstance(locator, WebElement):
364             _ele = locator
365         else:
366             _ele = self.find_element(locator)
367         s = Select(_ele)
368         if by_type == 'value':
369             s.deselect_by_value(type_val)
370         elif by_type == 'index':
371             s.deselect_by_index(type_val)
372         elif by_type == 'text':
373             s.deselect_by_visible_text(type_val)
374         else:
375             raise Exception('by_type is enum:value/index/text')
376         self.log.info('下拉菜单去勾选:{}'.format(type_val))
377         return s.all_selected_options
378 
379     def table(self, locator):
380         """table表格"""
381         if isinstance(locator, WebElement):
382             _ele = locator
383         else:
384             _ele = self.find_element(locator)
385         tr_list = _ele.find_elements(By.TAG_NAME, 'tr')
386         table_info = []
387         table_info.append(tr_list[0].find_elements(By.TAG_NAME, 'th'))
388         for tr in tr_list[1:]:
389             table_info.append(tr.find_elements(By.TAG_NAME, 'td'))
390         self.log.info('获取table表格:{}'.format(locator))
391         return table_info
392 
393     def elements_li(self, locator, li_text) -> WebElement:
394         """ul/ol 中的li列表元素"""
395         if isinstance(locator, WebElement):
396             _ele = locator
397         else:
398             _ele = self.find_element(locator)
399         self.log.info('获取列表元素:{}'.format(locator))
400         lis = _ele.find_elements(By.TAG_NAME, 'li')
401         for li in lis:
402             if li.text.strip() == li_text.strip():
403                 return li
404         raise Exception('列表中未找到%s元素' % li_text)
405 
406     def select_radio_or_checkbox(self, locator):
407         """勾选单选框或复选框"""
408         if isinstance(locator, WebElement):
409             _ele = locator
410         else:
411             _ele = self.find_element(locator)
412         if _ele.is_selected():
413             self.log.info('勾选选择框:{}'.format(locator))
414             return
415         self.click(_ele)
416         if not _ele.is_selected():
417             self.log.info('勾选选择框:{}失败'.format(locator))
418             raise Exception('勾选选择框:{}失败'.format(locator))
419         self.log.info('勾选选择框:{}'.format(locator))
420 
421     def deselect_checkbox(self, locator):
422         """去勾选复选框"""
423         if isinstance(locator, WebElement):
424             _ele = locator
425         else:
426             _ele = self.find_element(locator)
427         if _ele.is_selected():
428             self.click(_ele)
429         if _ele.is_selected():
430             self.log.info('去勾选选择框:{}失败'.format(locator))
431             raise Exception('去勾选选择框:{}失败'.format(locator))
432         self.log.info('去勾选选择框:{}'.format(locator))
433 
434     def select_by_noselector(self, locator, tag_name, attr_name, *attr_value):
435         """
436         无select标签的下拉菜单选项
437         可以通过标签的属性值或者text来定位,也可以通过index来定位
438         """
439         if isinstance(locator, WebElement):
440             _ele = locator
441         else:
442             _ele = self.find_element(locator)
443         sels = _ele.find_elements(By.TAG_NAME, tag_name)
444         selected = []
445         for val in attr_value:
446             if isinstance(val, int):
447                 self.click(sels[val])
448                 selected.append(val)
449                 self.log.info('下拉菜单选择第{}个'.format(val))
450             else:
451                 for sel in sels:
452                     sel_v = sel.get_attribute(attr_name)
453                     if sel_v is None:
454                         sel_v = sel.text
455                     if sel_v.strip() == str(val).strip():
456                         self.click(sel)
457                         selected.append(sel_v)
458                         self.log.info('下拉菜单选择:{}'.format(val))
459                         break
460         if len(selected) != len(attr_value):
461             raise Exception(
462                 '如下菜单选项未被点击选中:{}'.format([i for i in attr_value if i not in selected]))
463 
464     def check_confirm_window(self, confirm_content):
465         """判断是否弹出确认窗口,如删除XX确认窗口"""
466         confirm_window = self.find_element(base['确认弹窗'])
467         if confirm_content.strip() not in confirm_window.text:
468             raise Exception('弹窗不正确,期望弹窗内容:{},实际弹窗内容:{}'.format(confirm_content,
469                                                                confirm_window.text.strip()))
470         return confirm_window
471 
472     @allure.step('点击确定')
473     def accept_confirm(self):
474         """点击确定"""
475         try:
476             self.click(base['确认'])
477             self.log.info('点击确定')
478             sleep(2)
479         except:
480             self.assert_result(False, msg='点击确定失败')
481 
482     @allure.step('点击取消')
483     def dismis_confirm(self):
484         """点击取消"""
485         try:
486             self.click(base['取消'])
487             self.log.info('点击取消')
488         except:
489             self.assert_result(False, msg='点击取消失败')
490 
491     def get_dd_by_dt(self, locator, *dt):
492         """HTML中根据dl中dt值获取对应dd值"""
493         _, val = locator
494         if not val.endswith('dl'):
495             raise Exception('locator必须定位到"dl"处')
496         dts = {}
497         _eles = self.find_elements(locator)
498         for i in dt:
499             for ele in _eles:
500                 ele_text = ele.text
501                 ele_dt = ele_text.split(':')[0].strip()
502                 if ele_dt == i.strip():
503                     ele_dd = ele_text.split(':')[1].strip()
504                     self.log.info('{}:{}'.format(ele_dt, ele_dd))
505                     dts.update({ele_dt: ele_dd})
506                     break
507         if len(dts) == len(dt):
508             return dts
509         not_in = []
510         for i in dt:
511             if i.strip() not in dts:
512                 not_in.append(i)
513         raise Exception('dt:{}不存在,请检查参数'.format(not_in))
514 
515     def match_by_name(self, table_info, match_word, primary_td=2):
516         """默认返回名称为match_word或第match_word行"""
517         if isinstance(match_word, int):
518             return table_info[match_word]
519         for tr in table_info:
520             if tr[primary_td].text == match_word:
521                 self.log.info('找到 %s 元素位置' % match_word)
522                 return tr
523         raise Exception('未找到名称为:%s 元素行' % match_word)
524 
525     @allure.step('选择搜索类型')
526     def select_search_type(self, search_type='名称'):
527         """选择搜索类型"""
528         try:
529             self.hover(base['检索类型'])
530             search_type_list = self.element_text(base['检索类型列表'])
531             if search_type in search_type_list:
532                 ele_loc = self.elements_li(base['检索类型列表'], search_type)
533                 self.click(ele_loc)
534                 self.move_mouse(0, -50)
535             else:
536                 raise Exception('检索类型异常')
537         except:
538             self.assert_result(False, msg='[ERROR]选择搜索类型失败')
539 
540     @allure.step('输入查询关键字')
541     def input_search_word(self, input_word):
542         """输入查询关键字"""
543         try:
544             _ele = self.input_text(base['输入查询关键字'], input_word)
545             self.enter(_ele)
546             sleep()
547         except:
548             self.assert_result(False, msg='[ERROR]输入查询关键字失败')
549 
550     @allure.step('清空检索项')
551552     # date:     2022.03.29
553     def clear_search(self):
554         """清空检索项"""
555         try:
556             self.click(base['清空检索项'])
557             sleep()
558         except:
559             self.assert_result(False, msg='[ERROR]清空检索项失败')
560 
561     @allure.step('选择某项属性,正序/倒叙/默认状态')
562     def adjust_sortable(self, sort_name, sort_type=None):
563         """选择某项属性,正序/倒叙/默认状态,适用于网络、子网、端口界面"""
564         try:
565             self.sort_type = sort_type or '默认'
566             th_list = self.find_elements(base['表头'])
567             if self.sort_type == '升序':
568                 self.sort_type = 'cell sortable ascending'
569             elif self.sort_type == '降序':
570                 self.sort_type = 'cell sortable descending'
571             else:
572                 self.sort_type = 'cell sortable'
573             for th in th_list:
574                 loc = self.find_element_by_element(th, base['类名'])
575                 if loc.text == sort_name:
576                     while self.sort_type != th.get_attribute('class').strip():
577                         self.click(th)
578                         sleep(1)
579                     sort_status = th.get_attribute('class')
580                     if self.sort_type != sort_status:
581                         raise Exception("预期排序“{}”,与实际排序“{}”不符".format(self.sort_type, sort_status))
582         except:
583             self.assert_result(False, msg="选择某项属性,正序/倒叙/默认状态")
584 
585     @allure.step('获取当前界面非法输入提示信息')
586     def get_validation_message(self):
587         """获取当前界面非法输入提示信息"""
588         try:
589             messages = self.find_elements(base['验证消息'])
590             meg_list = []
591             for meg in messages:
592                 meg_list.append(self.element_text(meg).strip())
593             return ''.join(meg_list)
594         except:
595             self.assert_result(False, msg="[ERROR]当前界面无验证消息")
596 
597     @allure.step('查找当前界面中置灰的按钮')
598     def find_disable_eles(self):
599         """查找当前界面中置灰的按钮"""
600         try:
601             ele_list = self.find_elements(base['置灰按钮'])
602             ele_texts = []
603             for ele in ele_list:
604                 ele_texts.append(self.element_text(ele).strip())
605             self.log.info("置灰按钮:{}".format(ele_texts))
606             return ''.join(ele_texts)
607         except:
608             self.assert_result(False, msg="[ERROR]查找当前界面中置灰的按钮失败")
609 
610     @allure.step('校验当前界面中置灰的按钮')
611     def check_disable_eles(self, ele='确定'):
612         """校验当前界面中置灰的按钮"""
613         try:
614             eles = self.find_disable_eles()
615             if ele not in eles:
616                 raise Exception("置灰按钮校验失败:预期:{},实际:{}".format(ele, eles))
617             return True
618         except:
619             self.assert_result(False, msg="置灰按钮校验失败,期望:{}".format(ele))
620 
621     @allure.step('校验非法输入提示信息')
622     def check_input_illegal(self, message):
623         """校验非法输入提示信息"""
624         try:
625             msgs = self.get_validation_message()
626             if message not in msgs:
627                 raise Exception("非法输入提示信息校验失败:预期:{},实际:{}".format(message, msgs))
628         except:
629             self.assert_result(False, msg="非法输入提示信息校验失败,期望:{}".format(message))
630 
631     @allure.step('获取select列表的值')
632     def get_select_list(self, locator):
633         """获取select列表的值"""
634         if isinstance(locator, WebElement):
635             _ele = locator
636         else:
637             _ele = self.find_element(locator)
638         s = Select(_ele)
639         s_list = []
640         for opt in s.options:
641             s_list.append(opt.text)
642         return s_list
643 
644     @allure.step('查找当前界面中隐藏的按钮')
645     def find_hidden_eles(self):
646         """查找当前界面中隐藏的按钮"""
647         try:
648             ele_list = self.find_elements(base['隐藏按钮'])
649             ele_texts = []
650             for ele in ele_list:
651                 eles_list = ele.get_attribute("innerText").strip().split("\n")
652                 for eles in eles_list:
653                     ele_texts.append(eles)
654             self.log.info("隐藏按钮:{}".format(ele_texts))
655             return ele_texts
656         except:
657             self.assert_result(False, msg="[ERROR]查找当前界面中隐藏的按钮失败")
658 
659     @allure.step('校验当前界面中隐藏的按钮')
660     def check_hidden_eles(self, ele=None):
661         """校验当前界面中隐藏的按钮"""
662         try:
663             eles = self.find_hidden_eles()
664             if ele not in eles:
665                 return False
666             return True
667         except:
668             self.assert_result(False, msg="隐藏按钮校验失败,期望:{}".format(ele))
669 
670     @allure.step('获取当前用户')
671     def get_present_user(self):
672         """获取当前用户"""
673         try:
674             user = self.element_text(base['当前用户'])
675             return user
676         except:
677             self.assert_result(False, msg="获取当前用户失败")
View Code

2.其他xx_page.py继承自base_page.py,然后封装自己的业务方法

七、results目录

测试报告及日志路径

 

 

 1.logs

保存logging模块打印的日志

2.reports.allure_json

保存allure json格式的报告

3.reports.allure_xml

保存allure xml格式的报告

4.reports.pytest_html

保存pytest HTML报告

八、testdata目录

测试数据存放位置

 

 

其中xls文件用于保存测试用例数据驱动配置

九、utils目录

 

 

 1.logger.py

全局logging

 1 #!/usr/bin/env python3
 2 # -*- coding:utf-8 -*-
 3 import logging
 4 from config.conf import cm
 5 from common.readconfig import CONF
 6 
 7 
 8 class Log():
 9     def __init__(self):
10         self.logger = logging.getLogger()
11         self.logger.setLevel(CONF.loglevel)
12         if self.logger.handlers:
13             self.logger.handlers.clear()
14 
15         # 创建一个handle写入文件
16         fh = logging.FileHandler(cm.log_file, encoding='utf-8')
17         fh.setLevel(CONF.loglevel)
18 
19         # 创建一个handle输出到控制台
20         ch = logging.StreamHandler()
21         ch.setLevel(CONF.loglevel)
22 
23         # 定义输出的格式
24         formatter = logging.Formatter(self.fmt)
25         fh.setFormatter(formatter)
26         ch.setFormatter(formatter)
27 
28         # 添加到handle
29         self.logger.addHandler(fh)
30         self.logger.addHandler(ch)
31 
32     @property
33     def fmt(self):
34         return '%(levelname)s\t%(asctime)s\t[%(filename)s:%(lineno)d]\t%(message)s'
35 
36 
37 if __name__ == '__main__':
38     log = Log().logger
39     log.info('hello world')
View Code

2.times.py
时间相关方法

 1 #!/usr/bin/env python3
 2 # -*- coding:utf-8 -*-
 3 import time
 4 import datetime
 5 from functools import wraps
 6 
 7 
 8 def timestamp():
 9     """时间戳"""
10     return time.time()
11 
12 
13 def dt_strftime(fmt="%Y%m"):
14     """
15     datetime格式化时间
16     :param fmt "%Y%m%d%H%M%S
17     """
18     return datetime.datetime.now().strftime(fmt)
19 
20 
21 def sleep(seconds=1.0):
22     """
23     睡眠时间
24     """
25     time.sleep(seconds)
26 
27 
28 def running_time(func):
29     """函数运行时间"""
30 
31     @wraps(func)
32     def wrapper(*args, **kwargs):
33         start = timestamp()
34         res = func(*args, **kwargs)
35         print("校验元素done!用时%.3f秒!" % (timestamp() - start))
36         return res
37 
38     return wrapper
39 
40 
41 if __name__ == '__main__':
42     print(dt_strftime("%Y%m%d%H%M%S"))
View Code

 

3.utils.py

通用工具方法

 1 #!/usr/bin/env python3
 2 # -*- coding=utf-8 -*-
 3 import random
 4 import string
 5 import time
 6 
 7 
 8 def gen_random_string(pre_fix='test_', str_len=10):
 9     return pre_fix + ''.join(
10         random.choice(string.digits + string.ascii_letters) for _ in range(str_len - len(pre_fix)))
11 
12 
13 def gen_random_num(pre_fix='181', str_len=11):
14     return pre_fix + ''.join(random.choice(string.digits) for _ in range(str_len - len(pre_fix)))
15 
16 
17 def replace_file_content(file, old_content, new_content):
18     """
19     替换文件内容
20     file: 要替换内容的文件路径
21     old_content:要替换的内容,可迭代对象,如列表等
22     new_content:替换后的内容,可迭代对象,如列表等
23     """
24     replaces = zip(old_content, new_content)
25     with open(file, encoding='utf-8') as fr:
26         content = fr.read()
27         for old, new in replaces:
28             content = content.replace(old, new)
29     with open(file, 'w', encoding='utf-8') as fw:
30         fw.write(content)
31 
32 
33 def unicode():
34     val = random.randint(0x4e00, 0x9fbf)
35     return chr(val)
36 
37 
38 def create_name(n):
39     """随机生成n个汉字"""
40     name = ''
41     for i in range(n):
42         s = unicode()
43         name = name + s
44     return name
45 
46 
47 def create_digits(n):
48     """随机生成n个特殊字符"""
49     seed = "!@#$%^&*()+=-"
50     sa = []
51     for i in range(n):
52         sa.append(random.choice(seed))
53     salt = ''.join(sa)
54     return salt
55 
56 
57 def create_letters(n):
58     """随机生成n个随机生成英文字母"""
59     name = ''
60     for i in range(n):
61         s = random.choice(string.ascii_letters)
62         name = name + s
63     return name
64 
65 
66 def wait_util_success(count=10, interval=0.5):
67     """装饰器,用作循环等待函数执行成功,若超时则抛异常"""
68 
69     def wrapper(func):
70         def decorater(*args, **kwargs):
71             for i in range(count):
72                 try:
73                     return func(*args, **kwargs)
74                 except Exception as e:
75                     if i == count - 1:
76                         raise Exception(e)
77                 time.sleep(interval)
78 
79         return decorater
80 
81     return wrapper
82 
83 
84 if __name__ == '__main__':
85     replace_file_content(
86         'results/reports/allure_json/207b0bb4-9969-4e95-bbfa-90b2210a429d-attachment.txt',
87         ['', '', '', '', ''], ['' for _ in range(5)])
View Code

 

posted on 2022-06-09 18:09  fdzwdt  阅读(2401)  评论(1编辑  收藏  举报