51Testing软件测试论坛

标题: Selenium+Pytest自动化测试框架—禅道实战 [打印本页]

作者: lsekfe    时间: 2021-9-24 09:52
标题: Selenium+Pytest自动化测试框架—禅道实战
 前言
  有人问我登录携带登录的测试框架该怎么处理,今天就对框架做一点小升级吧,加入登录的测试功能。
  conftest.py更改
  1.  #!/usr/bin/env python3
  2.   # -*- coding:utf-8 -*-
  3.   import base64
  4.   import pytest
  5.   import allure
  6.   from py.xml import html
  7.   from selenium import webdriver
  8.   from page.webpage import WebPage
  9.   from common.readconfig import ini
  10.   from tools.send_mail import send_report
  11.   from tools.times import timestamp
  12.   from config.conf import cm
  13.   driver = None
  14.   @pytest.fixture(scope='session', autouse=True)
  15.   def drivers(request):
  16.       global driver
  17.       if driver is None:
  18.           driver = webdriver.Chrome()
  19.           web = WebPage(driver)
  20.           web.get_url(ini.url)
  21.       def fn():
  22.           driver.quit()
  23.       request.addfinalizer(fn)
  24.       return driver
  25.   @pytest.hookimpl(hookwrapper=True)
  26.   def pytest_runtest_makereport(item):
  27.       """
  28.       当测试失败的时候,自动截图,展示到html报告中
  29.       :param item:
  30.       """
  31.       pytest_html = item.config.pluginmanager.getplugin('html')
  32.       outcome = yield
  33.       report = outcome.get_result()
  34.       extra = getattr(report, 'extra', [])
  35.       if report.when == 'call' or report.when == "setup":
  36.           xfail = hasattr(report, 'wasxfail')
  37.           if (report.skipped and xfail) or (report.failed and not xfail):
  38.               screen_img = _capture_screenshot()
  39.               if screen_img:
  40.                   html = '<div><img src="data:image/png;base64,%s" alt="screenshot" style="width:1024px;height:768px;" ' \
  41.                          'onclick="window.open(this.src)" align="right"/></div>' % screen_img
  42.                   extra.append(pytest_html.extras.html(html))
  43.           report.extra = extra
  44.           report.description = str(item.function.__doc__)
  45.   def pytest_html_results_table_header(cells):
  46.       cells.insert(1, html.th('用例名称'))
  47.       cells.insert(2, html.th('Test_nodeid'))
  48.       cells.pop(2)
  49.   def pytest_html_results_table_row(report, cells):
  50.       cells.insert(1, html.td(report.description))
  51.       cells.insert(2, html.td(report.nodeid))
  52.       cells.pop(2)
  53.   def pytest_html_results_table_html(report, data):
  54.       if report.passed:
  55.           del data[:]
  56.           data.append(html.div('通过的用例未捕获日志输出.', class_='empty log'))
  57.   def pytest_html_report_title(report):
  58.       report.title = "pytest示例项目测试报告"
  59.   def pytest_configure(config):
  60.       config._metadata.clear()
  61.       config._metadata['测试项目'] = "测试百度官网搜索"
  62.       config._metadata['测试地址'] = ini.url
  63.   def pytest_html_results_summary(prefix, summary, postfix):
  64.       # prefix.clear() # 清空summary中的内容
  65.       prefix.extend([html.p("所属部门: XX公司测试部")])
  66.       prefix.extend([html.p("测试执行人: 随风挥手")])
  67.   def pytest_terminal_summary(terminalreporter, exitstatus, config):
  68.       """收集测试结果"""
  69.       result = {
  70.           "total": terminalreporter._numcollected,
  71.           'passed': len(terminalreporter.stats.get('passed', [])),
  72.           'failed': len(terminalreporter.stats.get('failed', [])),
  73.           'error': len(terminalreporter.stats.get('error', [])),
  74.           'skipped': len(terminalreporter.stats.get('skipped', [])),
  75.           # terminalreporter._sessionstarttime 会话开始时间
  76.           'total times': timestamp() - terminalreporter._sessionstarttime
  77.       }
  78.       print(result)
  79.       if result['failed'] or result['error']:
  80.           send_report()
  81.   def _capture_screenshot():
  82.       """截图保存为base64"""
  83.       now_time, screen_path = cm.screen_file
  84.       driver.save_screenshot(screen_path)
  85.       allure.attach.file(screen_path, "测试失败截图...{}".format(
  86.           now_time), allure.attachment_type.PNG)
  87.       with open(screen_path, 'rb') as f:
  88.           imagebase64 = base64.b64encode(f.read())
  89.       return imagebase64.decode()
复制代码
config.ini更改
  1. [HOST]
  2.   HOST = http://127.0.0.1/zentao/user-login-L3plbnRhby9teS5odG1s.html
复制代码
conf.py更改

  1. <strong> </strong>#!/usr/bin/env python3
  2.   # -*- coding:utf-8 -*-
  3.   import os
  4.   from selenium.webdriver.common.by import By
  5.   from tools.times import datetime_strftime
  6.   class ConfigManager(object):
  7.       # 项目目录
  8.       BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  9.       # 日志目录
  10.       LOG_PATH = os.path.join(BASE_DIR, 'logs')
  11.       # 报告目录
  12.       REPORT_PATH = os.path.join(BASE_DIR, 'report', 'report.html')
  13.       ELEMENT_PATH = os.path.join(BASE_DIR, 'page_element')
  14.       # 元素定位的类型
  15.       LOCATE_MODE = {
  16.           'css': By.CSS_SELECTOR,
  17.           'xpath': By.XPATH,
  18.           'name': By.NAME,
  19.           'id': By.ID,
  20.           'class': By.CLASS_NAME
  21.       }
  22.       # 邮件信息
  23.       EMAIL_INFO = {
  24.           'username': '1084502012@qq.com',  # 切换成你自己的地址
  25.           'password': 'QQ邮箱授权码',
  26.           'smtp_host': 'smtp.qq.com',
  27.           'smtp_port': 465
  28.       }
  29.       # 收件人
  30.       ADDRESSEE = [
  31.           '1084502012@qq.com',
  32.       ]
  33.       @property
  34.       def ini_file(self):
  35.           # 配置文件
  36.           _file = os.path.join(self.BASE_DIR, 'config', 'config.ini')
  37.           if not os.path.exists(_file):
  38.               raise FileNotFoundError("配置文件%s不存在!" % _file)
  39.           return _file
  40.       def element_file(self, name):
  41.           """页面元素文件"""
  42.           element_path = os.path.join(self.ELEMENT_PATH, '%s.yaml' % name)
  43.           if not os.path.exists(element_path):
  44.               raise FileNotFoundError("%s 文件不存在!" % element_path)
  45.           return element_path
  46.       @property
  47.       def log_path(self):
  48.           log_path = os.path.join(self.BASE_DIR, 'logs')
  49.           if not os.path.exists(log_path):
  50.               os.makedirs(log_path)
  51.           return os.path.join(log_path, "%s.log" % datetime_strftime())
  52.       @property
  53.       def screen_file(self):
  54.           now_time = datetime_strftime("%Y%m%d%H%M%S")
  55.           # 截图目录
  56.           screenshot_dir = os.path.join(self.BASE_DIR, 'screen_capture')
  57.           if not os.path.exists(screenshot_dir):
  58.               os.makedirs(screenshot_dir)
  59.           screen_path = os.path.join(screenshot_dir, "{}.png".format(now_time))
  60.           return now_time, screen_path
  61.   cm = ConfigManager()
  62.   if __name__ == '__main__':
  63.       print(cm.BASE_DIR)
复制代码

page更改
  添加了几个函数!
  1. #!/usr/bin/env python3
  2.   # -*- coding:utf-8 -*-
  3.   """
  4.   selenium基类
  5.   本文件存放了selenium基类的封装方法
  6.   """
  7.   from selenium.webdriver.support import expected_conditions as EC
  8.   from selenium.webdriver.support.ui import WebDriverWait
  9.   from selenium.common.exceptions import TimeoutException, NoSuchElementException
  10.   from config.conf import cm
  11.   from tools.times import sleep
  12.   from tools.logger import Logger
  13.   log = Logger(__name__).logger
  14.   class WebPage(object):
  15.       """selenium基类"""
  16.       def __init__(self, driver):
  17.           # self.driver = webdriver.Chrome()
  18.           self.driver = driver
  19.           self.timeout = 20
  20.           self.wait = WebDriverWait(self.driver, self.timeout)
  21.       def get_url(self, url):
  22.           """打开网址并验证"""
  23.           self.driver.maximize_window()
  24.           self.driver.set_page_load_timeout(60)
  25.           try:
  26.               self.driver.get(url)
  27.               self.driver.implicitly_wait(10)
  28.               log.info("打开网页:%s" % url)
  29.           except TimeoutException:
  30.               raise TimeoutException("打开%s超时请检查网络或网址服务器" % url)
  31.       @staticmethod
  32.       def element_locator(func, locator):
  33.           """元素定位器"""
  34.           name, value = locator
  35.           return func(cm.LOCATE_MODE[name], value)
  36.       def find_element(self, locator):
  37.           """寻找单个元素"""
  38.           return WebPage.element_locator(lambda *args: self.wait.until(
  39.               EC.presence_of_element_located(args)), locator)
  40.       def find_elements(self, locator):
  41.           """查找多个相同的元素"""
  42.           return WebPage.element_locator(lambda *args: self.wait.until(
  43.               EC.presence_of_all_elements_located(args)), locator)
  44.       def focus(self):
  45.           """聚焦元素"""
  46.           self.driver.execute_script("window.scrollTo(0,document.body.scrollHeight)")
  47.       def elements_num(self, locator):
  48.           """获取相同元素的个数"""
  49.           number = len(self.find_elements(locator))
  50.           log.info("相同元素:{}".format((locator, number)))
  51.           return number
  52.       def input_text(self, locator, txt):
  53.           """输入(输入前先清空)"""
  54.           sleep(0.5)
  55.           ele = self.find_element(locator)
  56.           ele.clear()
  57.           ele.send_keys(txt)
  58.           log.info("输入文本:{}".format(txt))
  59.       def is_click(self, locator):
  60.           """点击"""
  61.           ele = self.find_element(locator)
  62.           ele.click()
  63.           sleep()
  64.           log.info("点击元素:{}".format(locator))
  65.       def is_exists(self, locator):
  66.           """元素是否存在(DOM)"""
  67.           try:
  68.               WebPage.element_locator(lambda *args: EC.presence_of_element_located(args)(self.driver), locator)
  69.               return True
  70.           except NoSuchElementException:
  71.               return False
  72.       def alert_exists(self):
  73.           """判断弹框是否出现,并返回弹框的文字"""
  74.           alert = EC.alert_is_present()(self.driver)
  75.           if alert:
  76.               text = alert.text
  77.               log.info("Alert弹窗提示为:%s" % text)
  78.               alert.accept()
  79.               return text
  80.           else:
  81.               log.error("没有Alert弹窗提示!")
  82.       def element_text(self, locator):
  83.           """获取当前的text"""
  84.           _text = self.find_element(locator).text
  85.           log.info("获取文本:{}".format(_text))
  86.           return _text
  87.       def get_attribute(self, locator, name):
  88.           """获取元素属性"""
  89.           return self.find_element(locator).get_attribute(name)
  90.       @property
  91.       def get_source(self):
  92.           """获取页面源代码"""
  93.           return self.driver.page_source
  94.       def refresh(self):
  95.           """刷新页面F5"""
  96.           self.driver.refresh()
  97.           self.driver.implicitly_wait(30)
  98.   if __name__ == "__main__":
  99.       pass
复制代码
page_element更改
  1. 账号: "css==input[name=account]"
  2.   密码: "css==input[name=password]"
  3.   登录: "css==button#submit"
  4.   我的地盘: "xpath==//nav[@id='navbar']//span[text()=' 我的地盘']"
  5.   右上角名称: "css==.user-name"
  6.   退出登录: "xpath==//a[text()='退出']"
复制代码
  1.  产品按钮: "xpath==//nav[@id='navbar']//a[text()='产品']"
  2.   添加产品: "xpath==//div[@id='pageActions']//a[text()=' 添加产品']"
  3.   产品名称: "css==#name"
  4.   产品代号: "css==#code"
  5.   保存产品: "css==#submit"
  6.   产品列表: "xpath==//ul[@class='nav nav-stacked nav-secondary scrollbar-hover']//a[1]"
复制代码

page_object更改
  1.  #!/usr/bin/env python3
  2.   # -*- coding: utf-8 -*-
  3.   from page.webpage import WebPage
  4.   from common.readelement import Element
  5.   login = Element('login')
  6.   class LoginPage(WebPage):
  7.       """登录类"""
  8.       def username(self, name):
  9.           """用户名"""
  10.           self.input_text(login['账号'], name)
  11.       def password(self, pwd):
  12.           """密码"""
  13.           self.input_text(login['密码'], pwd)
  14.       def submit(self):
  15.           """登录"""
  16.           self.is_click(login['登录'])
  17.       def quit_login(self):
  18.           """退出登录"""
  19.           self.is_click(login['右上角名称'])
  20.           self.is_click(login['退出登录'])
  21.       def login_success(self):
  22.           """验证登录"""
  23.           return self.is_exists(login['我的地盘'])
复制代码
  1. #!/usr/bin/env python3
  2.   # -*- coding:utf-8 -*-
  3.   from page.webpage import WebPage, sleep
  4.   from common.readelement import Element
  5.   product = Element('product')
  6.   class ProductPage(WebPage):
  7.       """产品类"""
  8.       def click_product(self):
  9.           """点击产品"""
  10.           self.is_click(product['产品按钮'])
  11.       def add_product(self):
  12.           """添加产品"""
  13.           self.is_click(product['添加产品'])
  14.       def add_product_content(self, name, code):
  15.           """添加产品内容"""
  16.           self.input_text(product['产品名称'], name)
  17.           self.input_text(product['产品代号'], code)
  18.       def save_product(self):
  19.           """保存产品"""
  20.           self.focus()
  21.           self.is_click(product['保存产品'])
  22.       def product_list(self):
  23.           """产品列表"""
  24.           return [i.get_attribute('title') for i in self.find_elements(product['产品列表'])]
  25.   if __name__ == '__main__':
  26.       a = product['产品列表'][1] + "[1]"
  27.       print(a)
复制代码
 TestCase更改
  1.  #!/usr/bin/env python3
  2.   # -*- coding: utf-8 -*-
  3.   import pytest
  4.   from tools.times import sleep
  5.   from page_object.loginpage import LoginPage
  6.   class TestLogin:
  7.       """测试登录"""
  8.       @pytest.mark.parametrize("name,pwd", [('admin', 'Admin123456'), ('test', 'test123')])
  9.       def test_001(self, drivers, name, pwd):
  10.           login = LoginPage(drivers)
  11.           login.username(name)
  12.           login.password(pwd)
  13.           login.submit()
  14.           sleep(3)
  15.           res = login.alert_exists()
  16.           if res:
  17.               assert res == "登录失败,请检查您的用户名或密码是否填写正确。"
  18.           elif login.login_success():
  19.               login.quit_login()
复制代码
  1.  #!/usr/bin/env python3
  2.   # -*- coding:utf-8 -*-
  3.   import pytest
  4.   import allure
  5.   from random import randint
  6.   from tools.times import sleep
  7.   from page_object.loginpage import LoginPage
  8.   from page_object.productpage import ProductPage
  9.   @allure.feature("测试产品模块")
  10.   class TestProduct:
  11.       @pytest.fixture(scope='class', autouse=True)
  12.       def is_login(self, request, drivers):
  13.           login = LoginPage(drivers)
  14.           login.username('admin')
  15.           login.password('Admin123456')
  16.           login.submit()
  17.           sleep(3)
  18.           def logout():
  19.               login.quit_login()
  20.           request.addfinalizer(logout)
  21.       @allure.story("测试添加产品")
  22.       def test_001(self, drivers):
  23.           """搜索"""
  24.           product = ProductPage(drivers)
  25.           product.click_product()
  26.           product.add_product()
  27.           name, code = randint(100, 999), randint(100, 999)
  28.           product.add_product_content(name, code)
  29.           product.save_product()
  30.           sleep(3)
  31.           product.click_product()
  32.           assert str(name) in product.product_list()
  33.   if __name__ == '__main__':
  34.       pytest.main(['TestCase/test_aproduct.py'])
复制代码

测试结果
  登录之后的测试用例:
[attach]134565[/attach]


测试登录的用例:
[attach]134567[/attach]
[attach]134568[/attach]







欢迎光临 51Testing软件测试论坛 (http://bbs.51testing.com/) Powered by Discuz! X3.2