Selenium+Pytest自动化测试框架

引言

Selenium+Pytest自动化测试框架是目前最流行的自动化测试工具之一,其强大的功能和易用性援助许多开发人员和测试人员。

当前最新消息显示,随着人们对软件质量的要求不断提高,自动化测试工作正在变得越来越重要。这也导致越来越多的公司和组织开始招聘具有自动化测试技能的人才,并愿意为其提供更高的薪酬和晋升机会。

在这种情况下,学习并掌握Selenium+Pytest自动化测试框架将成为一个非常有价值的能力,并且有望帮助你在职场上实现快速升职加薪的目标。

因此,如果你想迅速提高自己的技能并进一步发展职业生涯,请跟随我们的指引,学习Selenium+Pytest自动化测试框架,并使用它来实现高效的自动化测试工作!

前段时间有人问我登录携带登录的测试框架该怎么处理,今天就对框架做一点小升级吧,加入登录的测试功能。

selenium自动化+ pytest测试框架禅道实战

选用的测试网址为我电脑本地搭建的禅道

更改了以下的一些文件,框架为原文章框架主体

conftest.py更改

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. #!/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更改

webpage.py

添加了几个函数!

  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更改

login.yaml

  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()='退出']"

product.yaml

 
  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更改

loginpage.py

  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['我的地盘'])

productpage.py

  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更改

test_login.py

  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()

test_product.py

  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'])

测试结果

登录之后的测试用例:

测试登录的用例

总结:

感谢每一个认真阅读我文章的人!!!

作为一位过来人也是希望大家少走一些弯路,如果你不想再体验一次学习时找不到资料,没人解答问题,坚持几天便放弃的感受的话,在这里我给大家分享一些自动化测试的学习资源,希望能给你前进的路上带来帮助。

软件测试面试文档

我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

 

          视频文档获取方式:
这份文档和视频资料,对于想从事【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!以上均可以分享,点下方小卡片即可自行领取。

  • 23
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值