最详细的Selenium+Pytest自动化测试框架实战

前言

selenium自动化+ pytest测试框架

本章你需要

  • 一定的python基础——至少明白类与对象,封装继承

  • 一定的selenium基础——本篇不讲selenium,

测试框架简介
  • 测试框架有什么优点呢:

  • 代码复用率高,如果不使用框架的话,代码会很冗余
  • 可以组装日志、报告、邮件等一些高级功能
  • 提高元素等数据的可维护性,元素发生变化时,只需要更新一下配置文件
  • 使用更灵活的PageObject设计模式

测试框架的整体目录

目录/文件说明是否为python包
common这个包中存放的是常见的通用的类,如读取配置文件
config配置文件目录
logs日志目录
page对selenium的方放进行深度的封装
page_element页面元素存放目录
page_object页面对象POM设计模式,
TestCase所有的测试用例集
utils工具类
script脚本文件
conftest.pypytest胶水文件
pytest.inipytest配置文件

 这样一个简单的框架结构就清晰了。

知道了以上这些我们就开始吧!

我们在项目中先按照上面的框架指引,建好每一项目录。

注意:python包为是的,都需要添加一个__init__.py文件以标识此目录为一个python包。

首先管理时间

首先呢,因为我们很多的模块会用到时间戳,或者日期等等字符串,所以我们先单独把时间封装成一个模块。

然后让其他模块来调用即可。在utils目录新建times.py模块

  1. <span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>

  2. <span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>

  3. <span style="color:#7171bf">import</span> time

  4. <span style="color:#7171bf">import</span> datetime

  5. <span style="color:#7171bf">from</span> functools <span style="color:#7171bf">import</span> wraps

  6. <span style="color:#7171bf">def</span> <span style="color:#61aeee">timestamp</span>():

  7. <span style="color:#98c379">"""时间戳"""</span>

  8. <span style="color:#7171bf">return</span> time.time()

  9. <span style="color:#7171bf">def</span> <span style="color:#61aeee">dt_strftime</span>(fmt=<span style="color:#98c379">"%Y%m"</span>):

  10. <span style="color:#98c379">"""

  11. datetime格式化时间

  12. :param fmt "%Y%m%d %H%M%S

  13. """</span>

  14. <span style="color:#7171bf">return</span> datetime.datetime.now().strftime(fmt)

  15. <span style="color:#7171bf">def</span> <span style="color:#61aeee">sleep</span>(seconds=<span style="color:#d19a66">1.0</span>):

  16. <span style="color:#98c379">"""

  17. 睡眠时间

  18. """</span>

  19. time.sleep(seconds)

  20. <span style="color:#7171bf">def</span> <span style="color:#61aeee">running_time</span>(func):

  21. <span style="color:#98c379">"""函数运行时间"""</span>

  22. <span style="color:#61aeee"> @wraps(func)</span>

  23. <span style="color:#7171bf">def</span> <span style="color:#61aeee">wrapper</span>(*args, **kwargs):

  24. start = timestamp()

  25. res = func(*args, **kwargs)

  26. <span style="color:#7171bf">print</span>(<span style="color:#98c379">"校验元素done!用时%.3f秒!"</span> % (timestamp() - start))

  27. <span style="color:#7171bf">return</span> res

  28. <span style="color:#7171bf">return</span> wrapper

  29. <span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:

  30. <span style="color:#7171bf">print</span>(dt_strftime(<span style="color:#98c379">"%Y%m%d%H%M%S"</span>))

  31. </code></span></span>

添加配置文件

配置文件总是项目中必不可少的部分!

将固定不变的信息集中在固定的文件中

conf.py

项目中都应该有一个文件对整体的目录进行管理,我也在这个python项目中设置了此文件。

在项目config目录创建conf.py文件,所有的目录配置信息写在这个文件里面。

  1. <span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>

  2. <span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>

  3. <span style="color:#7171bf">import</span> os

  4. <span style="color:#7171bf">from</span> selenium.webdriver.common.by <span style="color:#7171bf">import</span> By

  5. <span style="color:#7171bf">from</span> utils.times <span style="color:#7171bf">import</span> dt_strftime

  6. <span style="color:#7171bf">class</span> <span style="color:#61aeee">ConfigManager</span>(<span style="color:#61aeee">object</span>):

  7. <span style="color:#5c6370"><em># 项目目录</em></span>

  8. BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

  9. <span style="color:#5c6370"><em># 页面元素目录</em></span>

  10. ELEMENT_PATH = os.path.join(BASE_DIR, <span style="color:#98c379">'page_element'</span>)

  11. <span style="color:#5c6370"><em># 报告文件</em></span>

  12. REPORT_FILE = os.path.join(BASE_DIR, <span style="color:#98c379">'report.html'</span>)

  13. <span style="color:#5c6370"><em># 元素定位的类型</em></span>

  14. LOCATE_MODE = {

  15. <span style="color:#98c379">'css'</span>: By.CSS_SELECTOR,

  16. <span style="color:#98c379">'xpath'</span>: By.XPATH,

  17. <span style="color:#98c379">'name'</span>: By.NAME,

  18. <span style="color:#98c379">'id'</span>: By.ID,

  19. <span style="color:#98c379">'class'</span>: By.CLASS_NAME

  20. }

  21. <span style="color:#5c6370"><em># 邮件信息</em></span>

  22. EMAIL_INFO = {

  23. <span style="color:#98c379">'username'</span>: <span style="color:#98c379">'1084502012@qq.com'</span>, <span style="color:#5c6370"><em># 切换成你自己的地址</em></span>

  24. <span style="color:#98c379">'password'</span>: <span style="color:#98c379">'QQ邮箱授权码'</span>,

  25. <span style="color:#98c379">'smtp_host'</span>: <span style="color:#98c379">'smtp.qq.com'</span>,

  26. <span style="color:#98c379">'smtp_port'</span>: <span style="color:#d19a66">465</span>

  27. }

  28. <span style="color:#5c6370"><em># 收件人</em></span>

  29. ADDRESSEE = [

  30. <span style="color:#98c379">'1084502012@qq.com'</span>,

  31. ]

  32. <span style="color:#61aeee"> @property</span>

  33. <span style="color:#7171bf">def</span> <span style="color:#61aeee">log_file</span>(self):

  34. <span style="color:#98c379">"""日志目录"""</span>

  35. log_dir = os.path.join(self.BASE_DIR, <span style="color:#98c379">'logs'</span>)

  36. <span style="color:#7171bf">if</span> <span style="color:#7171bf">not</span> os.path.exists(log_dir):

  37. os.makedirs(log_dir)

  38. <span style="color:#7171bf">return</span> os.path.join(log_dir, <span style="color:#98c379">'{}.log'</span>.<span style="color:#7171bf">format</span>(dt_strftime()))

  39. <span style="color:#61aeee"> @property</span>

  40. <span style="color:#7171bf">def</span> <span style="color:#61aeee">ini_file</span>(self):

  41. <span style="color:#98c379">"""配置文件"""</span>

  42. ini_file = os.path.join(self.BASE_DIR, <span style="color:#98c379">'config'</span>, <span style="color:#98c379">'config.ini'</span>)

  43. <span style="color:#7171bf">if</span> <span style="color:#7171bf">not</span> os.path.exists(ini_file):

  44. <span style="color:#7171bf">raise</span> FileNotFoundError(<span style="color:#98c379">"配置文件%s不存在!"</span> % ini_file)

  45. <span style="color:#7171bf">return</span> ini_file

  46. cm = ConfigManager()

  47. <span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:

  48. <span style="color:#7171bf">print</span>(cm.BASE_DIR)

  49. </code></span></span>

这个conf文件我模仿了Django的settings.py文件的设置风格,但是又有些许差异。

在这个文件中我们可以设置自己的各个目录,也可以查看自己当前的目录。

遵循了约定:不变的常量名全部大写,函数名小写。看起来整体美观。

config.ini

在项目config目录新建一个config.ini文件,里面暂时先放入我们的需要测试的URL

  1. <span style="color:#596172"><span style="background-color:#ffffff"><code class="language-ini"><span style="color:#e06c75">[HOST]</span>

  2. <span style="color:#d19a66">HOST</span> = https://www.baidu.com

  3. </code></span></span>

读取配置文件

配置文件创建好了,接下来我们需要读取这个配置文件以使用里面的信息。

我们在common目录中新建一个readconfig.py文件

  1. <span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>

  2. <span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>

  3. <span style="color:#7171bf">import</span> configparser

  4. <span style="color:#7171bf">from</span> config.conf <span style="color:#7171bf">import</span> cm

  5. HOST = <span style="color:#98c379">'HOST'</span>

  6. <span style="color:#7171bf">class</span> <span style="color:#61aeee">ReadConfig</span>(<span style="color:#61aeee">object</span>):

  7. <span style="color:#98c379">"""配置文件"""</span>

  8. <span style="color:#7171bf">def</span> <span style="color:#61aeee">__init__</span>(self):

  9. self.config = configparser.RawConfigParser() <span style="color:#5c6370"><em># 当有%的符号时请使用Raw读取</em></span>

  10. self.config.read(cm.ini_file, encoding=<span style="color:#98c379">'utf-8'</span>)

  11. <span style="color:#7171bf">def</span> <span style="color:#61aeee">_get</span>(self, section, option):

  12. <span style="color:#98c379">"""获取"""</span>

  13. <span style="color:#7171bf">return</span> self.config.get(section, option)

  14. <span style="color:#7171bf">def</span> <span style="color:#61aeee">_set</span>(self, section, option, value):

  15. <span style="color:#98c379">"""更新"""</span>

  16. self.config.<span style="color:#7171bf">set</span>(section, option, value)

  17. <span style="color:#7171bf">with</span> <span style="color:#7171bf">open</span>(cm.ini_file, <span style="color:#98c379">'w'</span>) <span style="color:#7171bf">as</span> f:

  18. self.config.write(f)

  19. <span style="color:#61aeee"> @property</span>

  20. <span style="color:#7171bf">def</span> <span style="color:#61aeee">url</span>(self):

  21. <span style="color:#7171bf">return</span> self._get(HOST, HOST)

  22. ini = ReadConfig()

  23. <span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:

  24. <span style="color:#7171bf">print</span>(ini.url)

  25. </code></span></span>

可以看到我们用python内置的configparser模块对config.ini文件进行了读取。

对于url值的提取,我使用了高阶语法@property属性值,写法更简单。

记录操作日志

日志,大家应该都很熟悉这个名词,就是记录代码中的动作。

utils目录中新建logger.py文件。

这个文件就是我们用来在自动化测试过程中记录一些操作步骤的。

  1. <span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>

  2. <span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>

  3. <span style="color:#7171bf">import</span> logging

  4. <span style="color:#7171bf">from</span> config.conf <span style="color:#7171bf">import</span> cm

  5. <span style="color:#7171bf">class</span> <span style="color:#61aeee">Log</span>:

  6. <span style="color:#7171bf">def</span> <span style="color:#61aeee">__init__</span>(self):

  7. self.logger = logging.getLogger()

  8. <span style="color:#7171bf">if</span> <span style="color:#7171bf">not</span> self.logger.handlers:

  9. self.logger.setLevel(logging.DEBUG)

  10. <span style="color:#5c6370"><em># 创建一个handle写入文件</em></span>

  11. fh = logging.FileHandler(cm.log_file, encoding=<span style="color:#98c379">'utf-8'</span>)

  12. fh.setLevel(logging.INFO)

  13. <span style="color:#5c6370"><em># 创建一个handle输出到控制台</em></span>

  14. ch = logging.StreamHandler()

  15. ch.setLevel(logging.INFO)

  16. <span style="color:#5c6370"><em># 定义输出的格式</em></span>

  17. formatter = logging.Formatter(self.fmt)

  18. fh.setFormatter(formatter)

  19. ch.setFormatter(formatter)

  20. <span style="color:#5c6370"><em># 添加到handle</em></span>

  21. self.logger.addHandler(fh)

  22. self.logger.addHandler(ch)

  23. <span style="color:#61aeee"> @property</span>

  24. <span style="color:#7171bf">def</span> <span style="color:#61aeee">fmt</span>(self):

  25. <span style="color:#7171bf">return</span> <span style="color:#98c379">'%(levelname)s\t%(asctime)s\t[%(filename)s:%(lineno)d]\t%(message)s'</span>

  26. log = Log().logger

  27. <span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:

  28. log.info(<span style="color:#98c379">'hello world'</span>)

  29. </code></span></span>

在终端中运行该文件,就看到命令行打印出了:

  1. <span style="color:#596172"><span style="background-color:#ffffff"><code class="language-shell">INFO 2020-12-01 16:00:05,467 [logger.py:38] hello world

  2. </code></span></span>

然后在项目logs目录下生成了当月的日志文件。


简单理解POM模型

由于下面要讲元素相关的,所以首先理解一下POM模型

Page Object模式具有以下几个优点。

该观点来自 《Selenium自动化测试——基于Python语言》

  • 抽象出对象可以最大程度地降低开发人员修改页面代码对测试的影响, 所以, 你仅需要对页
    面对象进行调整, 而对测试没有影响;
  • 可以在多个测试用例中复用一部分测试代码;
  • 测试代码变得更易读、 灵活、 可维护
  • basepage ——selenium的基类,对selenium的方法进行封装
  • pageelements——页面元素,把页面元素单独提取出来,放入一个文件中
  • searchpage ——页面对象类,把selenium方法和页面元素进行整合
  • testcase ——使用pytest对整合的searchpage进行测试用例编写

通过上图我们可以看出,通过POM模型思想,我们把:

  • selenium方法
  • 页面元素
  • 页面对象
  • 测试用例

 以上四种代码主体进行了拆分,虽然在用例很少的情况下做会增加代码,但是当用例多的时候意义很大,代码量会在用例增加的时候显著减少。我们维护代码变得更加直观明显,代码可读性也变得比工厂模式强很多,代码复用率也极大的得到了提高。

简单学习元素定位

在日常的工作中,我见过很多在浏览器中直接在浏览器中右键Copy Xpath复制元素的同学。这样获得的元素表达式放在 webdriver 中去运行往往是不够稳定的,像前端的一些微小改动,都会引起元素无法定位的NoSuchElementException报错。

所以在实际工作和学习中我们应该加强自己的元素定位能力,尽可能的采用xpath和CSS selector 这种相对稳定的定位语法。由于CSS selector的语法生硬难懂,对新手很不友好,而且相比xpath缺少一些定位语法。所以我们选择xpath进行我们的元素定位语法。

xpath

语法规则

对于 xpath 的介绍是一门在 XML 文档中查找信息的语言。

表达式介绍备注
/根节点绝对路径
//当前节点的所有子节点相对路径
*所有节点元素的
@属性名的前缀@class   @id
*[1][] 下标运算符
[][ ]谓词表达式//input[@id='kw']
Following-sibling当前节点之后的同级
preceding-sibling当前节点之前的同级
parent当前节点的父级节点

定位工具

  • chropath
    • 优点:这是一个Chrome浏览器的测试定位插件,类似于firepath,本人试用了一下整体感觉非常好。对小白的友好度很好。
    • 缺点:安装这个插件需要FQ。
  • Katalon录制工具
    • 录制出来的脚本里面也会有定位元素的信息
  • 自己写——本人推荐这种
    • 优点:本人推荐的方式,因为当熟练到一定程度的时候,写出来的会更直观简洁,并且在运行自动化测试中出现问题时,能快速定位。
    • 缺点:需要一定xpathCSS selector语法积累,不太容易上手。

管理页面元素

本教程选择的测试地址是百度首页,所以对应的元素也是百度首页的。

项目框架设计中有一个目录page_element就是专门来存放定位元素的文件的。

通过对各种配置文件的对比,我在这里选择的是YAML文件格式。其易读,交互性好。

我们在page_element中新建一个search.yaml文件。

  1. <span style="color:#596172"><span style="background-color:#ffffff"><code class="language-yaml"><span style="color:#98c379">搜索框:</span> <span style="color:#98c379">"id==kw"</span>

  2. <span style="color:#98c379">候选:</span> <span style="color:#98c379">"css==.bdsug-overflow"</span>

  3. <span style="color:#98c379">搜索候选:</span> <span style="color:#98c379">"css==#form div li"</span>

  4. <span style="color:#98c379">搜索按钮:</span> <span style="color:#98c379">"id==su"</span>

  5. </code></span></span>

元素定位文件创建好了,下来我们需要读取这个文件。

common目录中创建readelement.py文件。

  1. <span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>

  2. <span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>

  3. <span style="color:#7171bf">import</span> os

  4. <span style="color:#7171bf">import</span> yaml

  5. <span style="color:#7171bf">from</span> config.conf <span style="color:#7171bf">import</span> cm

  6. <span style="color:#7171bf">class</span> <span style="color:#61aeee">Element</span>(<span style="color:#61aeee">object</span>):

  7. <span style="color:#98c379">"""获取元素"""</span>

  8. <span style="color:#7171bf">def</span> <span style="color:#61aeee">__init__</span>(self, name):

  9. self.file_name = <span style="color:#98c379">'%s.yaml'</span> % name

  10. self.element_path = os.path.join(cm.ELEMENT_PATH, self.file_name)

  11. <span style="color:#7171bf">if</span> <span style="color:#7171bf">not</span> os.path.exists(self.element_path):

  12. <span style="color:#7171bf">raise</span> FileNotFoundError(<span style="color:#98c379">"%s 文件不存在!"</span> % self.element_path)

  13. <span style="color:#7171bf">with</span> <span style="color:#7171bf">open</span>(self.element_path, encoding=<span style="color:#98c379">'utf-8'</span>) <span style="color:#7171bf">as</span> f:

  14. self.data = yaml.safe_load(f)

  15. <span style="color:#7171bf">def</span> <span style="color:#61aeee">__getitem__</span>(self, item):

  16. <span style="color:#98c379">"""获取属性"""</span>

  17. data = self.data.get(item)

  18. <span style="color:#7171bf">if</span> data:

  19. name, value = data.split(<span style="color:#98c379">'=='</span>)

  20. <span style="color:#7171bf">return</span> name, value

  21. <span style="color:#7171bf">raise</span> ArithmeticError(<span style="color:#98c379">"{}中不存在关键字:{}"</span>.<span style="color:#7171bf">format</span>(self.file_name, item))

  22. <span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:

  23. search = Element(<span style="color:#98c379">'search'</span>)

  24. <span style="color:#7171bf">print</span>(search[<span style="color:#98c379">'搜索框'</span>])

  25. </code></span></span>

 通过特殊方法__getitem__实现调用任意属性,读取yaml中的值。

这样我们就实现了定位元素的存储和调用。

但是还有一个问题,我们怎么样才能确保我们写的每一项元素不出错,人为的错误是不可避免的,但是我们可以通过代码来运行对文件的审查。当前也不能所有问题都能发现。

所以我们编写一个文件,在script脚本文件目录中创建inspect.py文件,对所有的元素yaml文件进行审查。

  1. <span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>

  2. <span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>

  3. <span style="color:#7171bf">import</span> os

  4. <span style="color:#7171bf">import</span> yaml

  5. <span style="color:#7171bf">from</span> config.conf <span style="color:#7171bf">import</span> cm

  6. <span style="color:#7171bf">from</span> utils.times <span style="color:#7171bf">import</span> running_time

  7. <span style="color:#61aeee">@running_time</span>

  8. <span style="color:#7171bf">def</span> <span style="color:#61aeee">inspect_element</span>():

  9. <span style="color:#98c379">"""检查所有的元素是否正确

  10. 只能做一个简单的检查

  11. """</span>

  12. <span style="color:#7171bf">for</span> files <span style="color:#7171bf">in</span> os.listdir(cm.ELEMENT_PATH):

  13. _path = os.path.join(cm.ELEMENT_PATH, files)

  14. <span style="color:#7171bf">with</span> <span style="color:#7171bf">open</span>(_path, encoding=<span style="color:#98c379">'utf-8'</span>) <span style="color:#7171bf">as</span> f:

  15. data = yaml.safe_load(f)

  16. <span style="color:#7171bf">for</span> k <span style="color:#7171bf">in</span> data.values():

  17. <span style="color:#7171bf">try</span>:

  18. pattern, value = k.split(<span style="color:#98c379">'=='</span>)

  19. <span style="color:#7171bf">except</span> ValueError:

  20. <span style="color:#7171bf">raise</span> Exception(<span style="color:#98c379">"元素表达式中没有`==`"</span>)

  21. <span style="color:#7171bf">if</span> pattern <span style="color:#7171bf">not</span> <span style="color:#7171bf">in</span> cm.LOCATE_MODE:

  22. <span style="color:#7171bf">raise</span> Exception(<span style="color:#98c379">'%s中元素【%s】没有指定类型'</span> % (_path, k))

  23. <span style="color:#7171bf">elif</span> pattern == <span style="color:#98c379">'xpath'</span>:

  24. <span style="color:#7171bf">assert</span> <span style="color:#98c379">'//'</span> <span style="color:#7171bf">in</span> value,\

  25. <span style="color:#98c379">'%s中元素【%s】xpath类型与值不配'</span> % (_path, k)

  26. <span style="color:#7171bf">elif</span> pattern == <span style="color:#98c379">'css'</span>:

  27. <span style="color:#7171bf">assert</span> <span style="color:#98c379">'//'</span> <span style="color:#7171bf">not</span> <span style="color:#7171bf">in</span> value, \

  28. <span style="color:#98c379">'%s中元素【%s]css类型与值不配'</span> % (_path, k)

  29. <span style="color:#7171bf">else</span>:

  30. <span style="color:#7171bf">assert</span> value, <span style="color:#98c379">'%s中元素【%s】类型与值不匹配'</span> % (_path, k)

  31. <span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:

  32. inspect_element()

  33. </code></span></span>

执行该文件:

  1. <span style="color:#596172"><span style="background-color:#ffffff"><code class="language-powershell">校验元素done!用时<span style="color:#d19a66">0.002</span>秒!

  2. </code></span></span>

可以看到,很短的时间内,我们就对所填写的YAML文件进行了审查。

现在我们基本所需要的组件已经大致完成了。

接下来我们将进行最重要的一环,封装selenium。

封装Selenium基类

在工厂模式种我们是这样写的:

 
  1. <span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>

  2. <span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>

  3. <span style="color:#7171bf">import</span> time

  4. <span style="color:#7171bf">from</span> selenium <span style="color:#7171bf">import</span> webdriver

  5. driver = webdriver.Chrome()

  6. driver.get(<span style="color:#98c379">'https://www.baidu.com'</span>)

  7. driver.find_element_by_xpath(<span style="color:#98c379">"//input[@id='kw']"</span>).send_keys(<span style="color:#98c379">'selenium'</span>)

  8. driver.find_element_by_xpath(<span style="color:#98c379">"//input[@id='su']"</span>).click()

  9. time.sleep(<span style="color:#d19a66">5</span>)

  10. driver.quit()

  11. </code></span></span>

很直白,简单,又明了。

创建driver对象,打开百度网页,搜索selenium,点击搜索,然后停留5秒,查看结果,最后关闭浏览器。

那我们为什么要封装selenium的方法呢。首先我们上述这种较为原始的方法,基本不适用于平时做UI自动化测试的,因为在UI界面实际运行情况远远比较复杂,可能因为网络原因,或者控件原因,我们元素还没有显示出来,就进行点击或者输入。所以我们需要封装selenium方法,通过内置的显式等待或一定的条件语句,才能构建一个稳定的方法。而且把selenium方法封装起来,有利于平时的代码维护。

我们在page目录创建webpage.py文件。

 
  1. <span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>

  2. <span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>

  3. <span style="color:#98c379">"""

  4. selenium基类

  5. 本文件存放了selenium基类的封装方法

  6. """</span>

  7. <span style="color:#7171bf">from</span> selenium.webdriver.support <span style="color:#7171bf">import</span> expected_conditions <span style="color:#7171bf">as</span> EC

  8. <span style="color:#7171bf">from</span> selenium.webdriver.support.ui <span style="color:#7171bf">import</span> WebDriverWait

  9. <span style="color:#7171bf">from</span> selenium.common.exceptions <span style="color:#7171bf">import</span> TimeoutException

  10. <span style="color:#7171bf">from</span> config.conf <span style="color:#7171bf">import</span> cm

  11. <span style="color:#7171bf">from</span> utils.times <span style="color:#7171bf">import</span> sleep

  12. <span style="color:#7171bf">from</span> utils.logger <span style="color:#7171bf">import</span> log

  13. <span style="color:#7171bf">class</span> <span style="color:#61aeee">WebPage</span>(<span style="color:#61aeee">object</span>):

  14. <span style="color:#98c379">"""selenium基类"""</span>

  15. <span style="color:#7171bf">def</span> <span style="color:#61aeee">__init__</span>(self, driver):

  16. <span style="color:#5c6370"><em># self.driver = webdriver.Chrome()</em></span>

  17. self.driver = driver

  18. self.timeout = <span style="color:#d19a66">20</span>

  19. self.wait = WebDriverWait(self.driver, self.timeout)

  20. <span style="color:#7171bf">def</span> <span style="color:#61aeee">get_url</span>(self, url):

  21. <span style="color:#98c379">"""打开网址并验证"""</span>

  22. self.driver.maximize_window()

  23. self.driver.set_page_load_timeout(<span style="color:#d19a66">60</span>)

  24. <span style="color:#7171bf">try</span>:

  25. self.driver.get(url)

  26. self.driver.implicitly_wait(<span style="color:#d19a66">10</span>)

  27. log.info(<span style="color:#98c379">"打开网页:%s"</span> % url)

  28. <span style="color:#7171bf">except</span> TimeoutException:

  29. <span style="color:#7171bf">raise</span> TimeoutException(<span style="color:#98c379">"打开%s超时请检查网络或网址服务器"</span> % url)

  30. <span style="color:#61aeee"> @staticmethod</span>

  31. <span style="color:#7171bf">def</span> <span style="color:#61aeee">element_locator</span>(func, locator):

  32. <span style="color:#98c379">"""元素定位器"""</span>

  33. name, value = locator

  34. <span style="color:#7171bf">return</span> func(cm.LOCATE_MODE[name], value)

  35. <span style="color:#7171bf">def</span> <span style="color:#61aeee">find_element</span>(self, locator):

  36. <span style="color:#98c379">"""寻找单个元素"""</span>

  37. <span style="color:#7171bf">return</span> WebPage.element_locator(<span style="color:#7171bf">lambda</span> *args: self.wait.until(

  38. EC.presence_of_element_located(args)), locator)

  39. <span style="color:#7171bf">def</span> <span style="color:#61aeee">find_elements</span>(self, locator):

  40. <span style="color:#98c379">"""查找多个相同的元素"""</span>

  41. <span style="color:#7171bf">return</span> WebPage.element_locator(<span style="color:#7171bf">lambda</span> *args: self.wait.until(

  42. EC.presence_of_all_elements_located(args)), locator)

  43. <span style="color:#7171bf">def</span> <span style="color:#61aeee">elements_num</span>(self, locator):

  44. <span style="color:#98c379">"""获取相同元素的个数"""</span>

  45. number = <span style="color:#7171bf">len</span>(self.find_elements(locator))

  46. log.info(<span style="color:#98c379">"相同元素:{}"</span>.<span style="color:#7171bf">format</span>((locator, number)))

  47. <span style="color:#7171bf">return</span> number

  48. <span style="color:#7171bf">def</span> <span style="color:#61aeee">input_text</span>(self, locator, txt):

  49. <span style="color:#98c379">"""输入(输入前先清空)"""</span>

  50. sleep(<span style="color:#d19a66">0.5</span>)

  51. ele = self.find_element(locator)

  52. ele.clear()

  53. ele.send_keys(txt)

  54. log.info(<span style="color:#98c379">"输入文本:{}"</span>.<span style="color:#7171bf">format</span>(txt))

  55. <span style="color:#7171bf">def</span> <span style="color:#61aeee">is_click</span>(self, locator):

  56. <span style="color:#98c379">"""点击"""</span>

  57. self.find_element(locator).click()

  58. sleep()

  59. log.info(<span style="color:#98c379">"点击元素:{}"</span>.<span style="color:#7171bf">format</span>(locator))

  60. <span style="color:#7171bf">def</span> <span style="color:#61aeee">element_text</span>(self, locator):

  61. <span style="color:#98c379">"""获取当前的text"""</span>

  62. _text = self.find_element(locator).text

  63. log.info(<span style="color:#98c379">"获取文本:{}"</span>.<span style="color:#7171bf">format</span>(_text))

  64. <span style="color:#7171bf">return</span> _text

  65. <span style="color:#61aeee"> @property</span>

  66. <span style="color:#7171bf">def</span> <span style="color:#61aeee">get_source</span>(self):

  67. <span style="color:#98c379">"""获取页面源代码"""</span>

  68. <span style="color:#7171bf">return</span> self.driver.page_source

  69. <span style="color:#7171bf">def</span> <span style="color:#61aeee">refresh</span>(self):

  70. <span style="color:#98c379">"""刷新页面F5"""</span>

  71. self.driver.refresh()

  72. self.driver.implicitly_wait(<span style="color:#d19a66">30</span>)

  73. </code></span></span>

在文件中我们对主要用了显式等待对selenium的click,send_keys等方法,做了二次封装。提高了运行的成功率。

好了我们完成了POM模型的一半左右的内容。接下来我们们进入页面对象。

创建页面对象

page_object目录下创建一个searchpage.py文件。

  1. <span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>

  2. <span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>

  3. <span style="color:#7171bf">from</span> page.webpage <span style="color:#7171bf">import</span> WebPage, sleep

  4. <span style="color:#7171bf">from</span> common.readelement <span style="color:#7171bf">import</span> Element

  5. search = Element(<span style="color:#98c379">'search'</span>)

  6. <span style="color:#7171bf">class</span> <span style="color:#61aeee">SearchPage</span>(<span style="color:#61aeee">WebPage</span>):

  7. <span style="color:#98c379">"""搜索类"""</span>

  8. <span style="color:#7171bf">def</span> <span style="color:#61aeee">input_search</span>(self, content):

  9. <span style="color:#98c379">"""输入搜索"""</span>

  10. self.input_text(search[<span style="color:#98c379">'搜索框'</span>], txt=content)

  11. sleep()

  12. <span style="color:#61aeee"> @property</span>

  13. <span style="color:#7171bf">def</span> <span style="color:#61aeee">imagine</span>(self):

  14. <span style="color:#98c379">"""搜索联想"""</span>

  15. <span style="color:#7171bf">return</span> [x.text <span style="color:#7171bf">for</span> x <span style="color:#7171bf">in</span> self.find_elements(search[<span style="color:#98c379">'候选'</span>])]

  16. <span style="color:#7171bf">def</span> <span style="color:#61aeee">click_search</span>(self):

  17. <span style="color:#98c379">"""点击搜索"""</span>

  18. self.is_click(search[<span style="color:#98c379">'搜索按钮'</span>])

  19. </code></span></span>

在该文件中我们对,输入搜索关键词,点击搜索,搜索联想,进行了封装。

并配置了注释。

在平时中我们应该养成写注释的习惯,因为过一段时间后,没有注释,代码读起来很费劲。

好了我们的页面对象此时业已完成了。下面我们开始编写测试用例。在开始测试用了之前我们先熟悉一下pytest测试框架。

简单了解Pytest 

 
  1. <span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em># content of test_sample.py</em></span>

  2. <span style="color:#7171bf">def</span> <span style="color:#61aeee">inc</span>(x):

  3. <span style="color:#7171bf">return</span> x + <span style="color:#d19a66">1</span>

  4. <span style="color:#7171bf">def</span> <span style="color:#61aeee">test_answer</span>():

  5. <span style="color:#7171bf">assert</span> inc(<span style="color:#d19a66">3</span>) == <span style="color:#d19a66">5</span>

  6. </code></span></span>

官方教程我认为写的并不适合入门阅读,而且没有汉化版。

pytest.ini

pytest项目中的配置文件,可以对pytest执行过程中操作做全局控制。

在项目根目录新建pytest.ini文件。

 
  1. <span style="color:#596172"><span style="background-color:#ffffff"><code class="language-ini"><span style="color:#e06c75">[pytest]</span>

  2. <span style="color:#d19a66">addopts</span> = --html=report.html --self-contained-html

  3. </code></span></span>

  •  addopts 指定执行时的其他参数说明:
  • --html=report/report.html --self-contained-html 生成pytest-html带样式的报告
  • -s 输出我们用例中的调式信息
  • -q 安静的进行测试
  • -v 可以输出用例更加详细的执行信息,比如用例所在的文件及用例名称等
编写测试用例

我们将使用pytest编写测试用例。

TestCase目录中创建test_search.py文件。

 
  1. <span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>

  2. <span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>

  3. <span style="color:#7171bf">import</span> re

  4. <span style="color:#7171bf">import</span> pytest

  5. <span style="color:#7171bf">from</span> utils.logger <span style="color:#7171bf">import</span> log

  6. <span style="color:#7171bf">from</span> common.readconfig <span style="color:#7171bf">import</span> ini

  7. <span style="color:#7171bf">from</span> page_object.searchpage <span style="color:#7171bf">import</span> SearchPage

  8. <span style="color:#7171bf">class</span> <span style="color:#61aeee">TestSearch</span>:

  9. <span style="color:#61aeee"> @pytest.fixture(scope=<span style="color:#3388aa">'function'</span>, autouse=<span style="color:#56b6c2">True</span>)</span>

  10. <span style="color:#7171bf">def</span> <span style="color:#61aeee">open_baidu</span>(self, drivers):

  11. <span style="color:#98c379">"""打开百度"""</span>

  12. search = SearchPage(drivers)

  13. search.get_url(ini.url)

  14. <span style="color:#7171bf">def</span> <span style="color:#61aeee">test_001</span>(self, drivers):

  15. <span style="color:#98c379">"""搜索"""</span>

  16. search = SearchPage(drivers)

  17. search.input_search(<span style="color:#98c379">"selenium"</span>)

  18. search.click_search()

  19. result = re.search(<span style="color:#98c379">r'selenium'</span>, search.get_source)

  20. log.info(result)

  21. <span style="color:#7171bf">assert</span> result

  22. <span style="color:#7171bf">def</span> <span style="color:#61aeee">test_002</span>(self, drivers):

  23. <span style="color:#98c379">"""测试搜索候选"""</span>

  24. search = SearchPage(drivers)

  25. search.input_search(<span style="color:#98c379">"selenium"</span>)

  26. log.info(<span style="color:#7171bf">list</span>(search.imagine))

  27. <span style="color:#7171bf">assert</span> <span style="color:#7171bf">all</span>([<span style="color:#98c379">"selenium"</span> <span style="color:#7171bf">in</span> i <span style="color:#7171bf">for</span> i <span style="color:#7171bf">in</span> search.imagine])

  28. <span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">'__main__'</span>:

  29. pytest.main([<span style="color:#98c379">'TestCase/test_search.py'</span>])

  30. </code></span></span>

我们测试用了就编写好了。

  • pytest.fixture 这个实现了和unittest的setup,teardown一样的前置启动,后置清理的装饰器。

  • 第一个测试用例:

    • 我们实现了在百度selenium关键字,并点击搜索按钮,并在搜索结果中,用正则查找结果页源代码,返回数量大于10我们就认为通过。
  • 第二个测试用例:

  • 我们实现了,搜索selenium,然后断言搜索候选中的所有结果有没有selenium关键字。

最后我们的在下面写一个执行启动的语句。

这时候我们应该进入执行了,但是还有一个问题,我们还没有把driver传递。

conftest.py

我们在项目根目录下新建一个conftest.py文件。

  1. <span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>

  2. <span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>

  3. <span style="color:#7171bf">import</span> pytest

  4. <span style="color:#7171bf">from</span> py.xml <span style="color:#7171bf">import</span> html

  5. <span style="color:#7171bf">from</span> selenium <span style="color:#7171bf">import</span> webdriver

  6. driver = <span style="color:#56b6c2">None</span>

  7. <span style="color:#61aeee">@pytest.fixture(scope=<span style="color:#3388aa">'session'</span>, autouse=<span style="color:#56b6c2">True</span>)</span>

  8. <span style="color:#7171bf">def</span> <span style="color:#61aeee">drivers</span>(request):

  9. <span style="color:#7171bf">global</span> driver

  10. <span style="color:#7171bf">if</span> driver <span style="color:#7171bf">is</span> <span style="color:#56b6c2">None</span>:

  11. driver = webdriver.Chrome()

  12. driver.maximize_window()

  13. <span style="color:#7171bf">def</span> <span style="color:#61aeee">fn</span>():

  14. driver.quit()

  15. request.addfinalizer(fn)

  16. <span style="color:#7171bf">return</span> driver

  17. <span style="color:#61aeee">@pytest.hookimpl(hookwrapper=<span style="color:#56b6c2">True</span>)</span>

  18. <span style="color:#7171bf">def</span> <span style="color:#61aeee">pytest_runtest_makereport</span>(item):

  19. <span style="color:#98c379">"""

  20. 当测试失败的时候,自动截图,展示到html报告中

  21. :param item:

  22. """</span>

  23. pytest_html = item.config.pluginmanager.getplugin(<span style="color:#98c379">'html'</span>)

  24. outcome = <span style="color:#7171bf">yield</span>

  25. report = outcome.get_result()

  26. report.description = <span style="color:#7171bf">str</span>(item.function.__doc__)

  27. extra = <span style="color:#7171bf">getattr</span>(report, <span style="color:#98c379">'extra'</span>, [])

  28. <span style="color:#7171bf">if</span> report.when == <span style="color:#98c379">'call'</span> <span style="color:#7171bf">or</span> report.when == <span style="color:#98c379">"setup"</span>:

  29. xfail = <span style="color:#7171bf">hasattr</span>(report, <span style="color:#98c379">'wasxfail'</span>)

  30. <span style="color:#7171bf">if</span> (report.skipped <span style="color:#7171bf">and</span> xfail) <span style="color:#7171bf">or</span> (report.failed <span style="color:#7171bf">and</span> <span style="color:#7171bf">not</span> xfail):

  31. file_name = report.nodeid.replace(<span style="color:#98c379">"::"</span>, <span style="color:#98c379">"_"</span>) + <span style="color:#98c379">".png"</span>

  32. screen_img = _capture_screenshot()

  33. <span style="color:#7171bf">if</span> file_name:

  34. html = <span style="color:#98c379">'<div><img src="data:image/png;base64,%s" alt="screenshot" style="width:1024px;height:768px;" '</span> \

  35. <span style="color:#98c379">'onclick="window.open(this.src)" align="right"/></div>'</span> % screen_img

  36. extra.append(pytest_html.extras.html(html))

  37. report.extra = extra

  38. <span style="color:#7171bf">def</span> <span style="color:#61aeee">pytest_html_results_table_header</span>(cells):

  39. cells.insert(<span style="color:#d19a66">1</span>, html.th(<span style="color:#98c379">'用例名称'</span>))

  40. cells.insert(<span style="color:#d19a66">2</span>, html.th(<span style="color:#98c379">'Test_nodeid'</span>))

  41. cells.pop(<span style="color:#d19a66">2</span>)

  42. <span style="color:#7171bf">def</span> <span style="color:#61aeee">pytest_html_results_table_row</span>(report, cells):

  43. cells.insert(<span style="color:#d19a66">1</span>, html.td(report.description))

  44. cells.insert(<span style="color:#d19a66">2</span>, html.td(report.nodeid))

  45. cells.pop(<span style="color:#d19a66">2</span>)

  46. <span style="color:#7171bf">def</span> <span style="color:#61aeee">pytest_html_results_table_html</span>(report, data):

  47. <span style="color:#7171bf">if</span> report.passed:

  48. <span style="color:#7171bf">del</span> data[:]

  49. data.append(html.div(<span style="color:#98c379">'通过的用例未捕获日志输出.'</span>, class_=<span style="color:#98c379">'empty log'</span>))

  50. <span style="color:#7171bf">def</span> <span style="color:#61aeee">_capture_screenshot</span>():

  51. <span style="color:#98c379">'''

  52. 截图保存为base64

  53. :return:

  54. '''</span>

  55. <span style="color:#7171bf">return</span> driver.get_screenshot_as_base64()

  56. </code></span></span>

conftest.py测试框架pytest的胶水文件,里面用到了fixture的方法,封装并传递出了driver。


执行用例

以上我们已经编写完成了整个框架和测试用例。

我们进入到当前项目的主目录执行命令:

 
  1. <span style="color:#596172"><span style="background-color:#ffffff"><code class="language-powershell">pytest

  2. </code></span></span>

命令行输出:

 
  1. <span style="color:#596172"><span style="background-color:#ffffff"><code class="language-powershell">Test session starts (platform: win32, Python <span style="color:#d19a66">3.7</span>.<span style="color:#d19a66">7</span>, pytest <span style="color:#d19a66">5.3</span>.<span style="color:#d19a66">2</span>, py<span style="color:#7171bf">test-sugar</span> <span style="color:#d19a66">0.9</span>.<span style="color:#d19a66">2</span>)

  2. cachedir: .pytest_cache

  3. metadata: {<span style="color:#98c379">'Python'</span>: <span style="color:#98c379">'3.7.7'</span>, <span style="color:#98c379">'Platform'</span>: <span style="color:#98c379">'Windows-10-10.0.18362-SP0'</span>, <span style="color:#98c379">'Packages'</span>: {<span style="color:#98c379">'pytest'</span>: <span style="color:#98c379">'5.3.2'</span>, <span style="color:#98c379">'py'</span>: <span style="color:#98c379">'1.8.0'</span>, <span style="color:#98c379">'pluggy'</span>: <span style="color:#98c379">'0.13.1'</span>}, <span style="color:#98c379">'Plugins'</span>: {<span style="color:#98c379">'forked'</span>: <span style="color:#98c379">'1.1.3'</span>, <span style="color:#98c379">'html'</span>: <span style="color:#98c379">'2.0.1'</span>, <span style="color:#98c379">'metadata'</span>: <span style="color:#98c379">'1.8.0'</span>, <span style="color:#98c379">'ordering'</span>: <span style="color:#98c379">'0.6'</span>, <span style="color:#98c379">'rerunfailures'</span>: <span style="color:#98c379">'8.0'</span>, <span style="color:#98c379">'sugar'</span>: <span style="color:#98c379">'0.9.2'</span>, <span style="color:#98c379">'xdist'</span>: <span style="color:#98c379">'1.31.0'</span>}, <span style="color:#98c379">'JAVA_HOME'</span>: <span style="color:#98c379">'D:\\Program Files\\Java\\jdk1.8.0_131'</span>}

  4. rootdir: C:\Users\hoou\PycharmProjects\web<span style="color:#56b6c2">-demotest</span>, inifile: pytest.ini

  5. plugins: forked<span style="color:#56b6c2">-1</span>.<span style="color:#d19a66">1.3</span>, html<span style="color:#56b6c2">-2</span>.<span style="color:#d19a66">0.1</span>, metadata<span style="color:#56b6c2">-1</span>.<span style="color:#d19a66">8.0</span>, ordering<span style="color:#56b6c2">-0</span>.<span style="color:#d19a66">6</span>, rerunfailures<span style="color:#56b6c2">-8</span>.<span style="color:#d19a66">0</span>, sugar<span style="color:#56b6c2">-0</span>.<span style="color:#d19a66">9.2</span>, xdist<span style="color:#56b6c2">-1</span>.<span style="color:#d19a66">31.0</span>

  6. collecting ...

  7. DevTools listening on ws://<span style="color:#d19a66">127.0</span>.<span style="color:#d19a66">0.1</span>:<span style="color:#d19a66">10351</span>/devtools/browser/<span style="color:#d19a66">78</span>bef34d<span style="color:#56b6c2">-b94c-4087-b724-34fb6b2ef6d1</span>

  8. TestCase\test_search.py::TestSearch.test_001 ✓ <span style="color:#d19a66">50</span>% █████

  9. TestCase\test_search.py::TestSearch.test_002 ✓ <span style="color:#d19a66">100</span>% ██████████

  10. <span style="color:#56b6c2">-------------------------------</span> generated html file: file://C:\Users\hoou\PycharmProjects\web<span style="color:#56b6c2">-demotest</span>\report\report.html <span style="color:#56b6c2">--------------------------------</span>

  11. Results (<span style="color:#d19a66">12.90</span>s):

  12. <span style="color:#d19a66">2</span> passed

  13. </code></span></span>

可以看到两条用例已经执行成功了。

项目的report目录中生成了一个report.html文件。

这就是生成的测试报告文件。

发送邮件

当项目执行完成之后,需要发送到自己或者其他人邮箱里查看结果。

我们编写发送邮件的模块。

utils目录中新建send_mail.py文件

 
  1. <span style="color:#596172"><span style="background-color:#ffffff"><code class="language-python"><span style="color:#5c6370"><em>#!/usr/bin/env python3</em></span>

  2. <span style="color:#5c6370"><em># -*- coding:utf-8 -*-</em></span>

  3. <span style="color:#7171bf">import</span> zmail

  4. <span style="color:#7171bf">from</span> config.conf <span style="color:#7171bf">import</span> cm

  5. <span style="color:#7171bf">def</span> <span style="color:#61aeee">send_report</span>():

  6. <span style="color:#98c379">"""发送报告"""</span>

  7. <span style="color:#7171bf">with</span> <span style="color:#7171bf">open</span>(cm.REPORT_FILE, encoding=<span style="color:#98c379">'utf-8'</span>) <span style="color:#7171bf">as</span> f:

  8. content_html = f.read()

  9. <span style="color:#7171bf">try</span>:

  10. mail = {

  11. <span style="color:#98c379">'from'</span>: <span style="color:#98c379">'1084502012@qq.com'</span>,

  12. <span style="color:#98c379">'subject'</span>: <span style="color:#98c379">'最新的测试报告邮件'</span>,

  13. <span style="color:#98c379">'content_html'</span>: content_html,

  14. <span style="color:#98c379">'attachments'</span>: [cm.REPORT_FILE, ]

  15. }

  16. server = zmail.server(*cm.EMAIL_INFO.values())

  17. server.send_mail(cm.ADDRESSEE, mail)

  18. <span style="color:#7171bf">print</span>(<span style="color:#98c379">"测试邮件发送成功!"</span>)

  19. <span style="color:#7171bf">except</span> Exception <span style="color:#7171bf">as</span> e:

  20. <span style="color:#7171bf">print</span>(<span style="color:#98c379">"Error: 无法发送邮件,{}!"</span>, <span style="color:#7171bf">format</span>(e))

  21. <span style="color:#7171bf">if</span> __name__ == <span style="color:#98c379">"__main__"</span>:

  22. <span style="color:#98c379">'''请先在config/conf.py文件设置QQ邮箱的账号和密码'''</span>

  23. send_report()

  24. </code></span></span>

执行该文件:

  1. <span style="color:#596172"><span style="background-color:#ffffff"><code class="language-shell">测试邮件发送成功!

  2. </code></span></span>

可以看到测试报告邮件已经发送成功了。打开邮箱。

成功收到了邮件。

这个demo项目就算是整体完工了;是不是很有心得,在发送邮件的那一刻很有成就感。

最后,想必你已经对pytest+selenium框架有了一个整体的认知了,在自动化测试的道路上又上了一层台阶

总结:

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

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

软件测试面试文档

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

 

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/31731.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2004年-2022年 全国31省市场分割指数数据

市场分割指数在经济学领域是一个关键的概念&#xff0c;特别是在评估不同区域市场一体化水平时。陆铭等学者深入研究了市场分割问题&#xff0c;并对市场分割指数给出了定义&#xff1a;它是一个衡量在相同时间点不同区域或同一区域在不同时间点的某类商品相对价格差异的指标。…

几种常见的滤波器样式

IIR Peaking Filter IIR LowShelf Filter IIR HighShelf Filter 4. IIR LowPassFilter 5. IIR HighPass Filter FIR PeakingFilter FIR LowShelf Filter 8. FIR HighShelf Filter 8. FIR LowPass Filter 10. FIR HighPass Filter

OpenHarmony-HDF驱动框架介绍及加载过程分析

前言 HarmonyOS面向万物互联时代&#xff0c;而万物互联涉及到了大量的硬件设备&#xff0c;这些硬件的离散度很高&#xff0c;它们的性能差异与配置差异都很大&#xff0c;所以这要求使用一个更灵活、功能更强大、能耗更低的驱动框架。OpenHarmony系统HDF驱动框架采用C语言面…

【Kafka】Kafka Broker工作流程、节点服役与退役、副本、文件存储、高效读写数据-08

【Kafka】Kafka Broker工作流程、节点服役与退役、副本、文件存储、高效读写数据 1. Kafka Broker 工作流程1.1 Zookeeper 存储的 Kafka 信息1.2 Kafka Broker总体工作流程1.2.1 Controller介绍 1.3 Broker 重要参数 2. 节点服役与退役3. Kafka副本 1. Kafka Broker 工作流程 …

GUI Guider(V1.7.2) 设计UI在嵌入式系统上的应用(N32G45XVL-STB)

目录 概述 1 使用GUI Guider 设计UI 1.1 创建页面 1.2 页面切换事件实现 1.3 生成代码和仿真 1.3.1 生成和编译代码 1.3.2 仿真UI 2 GUI Guider生成的代码结构 2.1 代码结构介绍 2.2 Project目录下的文件 3 板卡上移植UI 3.1 加载代码至工程目录 3.2 主函数中调…

【环境变量问题:计算机删除环境变量的恢复方法;此环境变量太大。此对话框允许将值设置为最长2047个字符】

不小心误删了win10系统环境变量可以试试下文方法恢复。 本方法针对修改环境变量未重启的用户可以使用&#xff0c;如果修改环境变量&#xff0c;然后还重启了&#xff0c;只能说重新来。 方法一&#xff1a;使用命令提示符恢复 被修改的系统Path只是同步到了注册表中&#x…

2024软考系规考前复习20问!看看你能答上来多少

今天给大家整理了——2024系统规划与管理师考前20问&#xff0c;这是一份很重要的软考备考必看干货&#xff0c;包含很多核心知识点。有PDF版&#xff0c;可打印下来&#xff0c;过完一遍教材后&#xff0c;来刷一刷、背一背&#xff0c;说不定可以帮你拿下不少分。 第1问- 信息…

2024.6.23周报

目录 摘要 ABSTRACT 一、文献阅读 一、题目 二、摘要 三、网络架构 四、创新点 五、文章解读 1、Introduction 2、Method 3、实验 4、结论 二、代码实验 总结 摘要 本周阅读了一篇题目为NAS-PINN: NEURAL ARCHITECTURE SEARCH-GUIDED PHYSICS-INFORMED NEURAL N…

解决电脑关机难题:电脑关不了机的原因以及方法

在使用电脑的日常生活中&#xff0c;有时会遇到一些烦人的问题&#xff0c;其中之一就是电脑关不了机。当您尝试关闭电脑时&#xff0c;它可能会停留在某个界面&#xff0c;或者根本不响应关机指令。这种情况不仅令人困惑&#xff0c;还可能导致数据丢失或系统损坏。 在本文中…

DS:堆的应用——两种算法和TOP-K问题

欢迎来到Harper.Lee的学习世界&#xff01;博主主页传送门&#xff1a;Harper.Lee的博客主页想要一起进步的uu可以来后台找我哦&#xff01; 一、堆的排序 1.1 向上调整——建小堆 1.1.1 代码实现 //时间复杂度&#xff1a;O(N*logN) //空间复杂度&#xff1a;O(logN) for (…

计算机网络知识点汇总

计算机网络知识点汇总 第1章计算机网络体系结构 1.1 计算机网络概述 1.1.1 计算机网络的概念 ​ 计算机网络是由若干个结点(node)和连接这些结点的链路(link)组成。网络中的结点可以是就三级、集线器、交换机、或者路由器等&#xff0c;网络之间通过路由器进行互联&#xf…

Nodejs 第七十九章(Kafka进阶)

kafka前置知识在上一章讲过了 不再复述 kafka进阶 1. server.properties配置文件 server.properties是Kafka服务器的配置文件&#xff0c;它用于配置Kafka服务的各个方面&#xff0c;包括网络设置、日志存储、消息保留策略、安全认证 #broker的全局唯一编号&#xff0c;不能…

MySQL数据库初体验+数据库管理(其一)

【1】 操作系统介绍&#xff1a; Linux操作系统有 RedHat CentOS Debian Ubuntu OpenSUSE 信创标准 国产系统 &#xff1a; 华为&#xff08;欧拉&#xff09; 阿里&#xff08;龙蜥&#xff09; 腾讯 &#xff08;tencentOS&#xff09; 麒麟&#xf…

【日记】梦到兄长要给鳄鱼换牙齿……(421 字)

正文 今天中午睡了一个小时多一点&#xff0c;做了一个很奇怪的梦。梦见兄长要给一条鳄鱼换牙齿&#xff0c;还说早上不好操作&#xff0c;要三天之后的中午或晚上&#xff0c;颇有一种翻黄历寻个良辰吉日之感。但我没那样大的耐性&#xff0c;便捏住鳄鱼的嘴&#xff0c;左摔右…

实战18:基于tkinter+jupyter notebook开发的情感分析系统

项目演示: 完整代码: import pandas as pd import numpy as np from collections import Counter import re import jieba from tqdm import tqdm from sklearn.metrics import roc_curve, auc import joblib import gensim from sklearn.svm import SVC from gensim.mode…

STM32小项目———感应垃圾桶

文章目录 前言一、超声波测距1.超声波简介2.超声波测距原理2.超声波测距步骤 二、舵机的控制三、硬件搭建及功能展示总结 前言 一个学习STM32的小白~ 有问题请评论区或私信指出 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、超声波测距 1.超声波…

Studying-代码随想录训练营day16| 513找到左下角的值、112.路径总和、106从中序与后序遍历序列构造二叉树

第十六天&#xff0c;二叉树part03&#x1f4aa;&#x1f4aa;&#x1f4aa;&#xff0c;编程语言&#xff1a;C 目录 513找到左下角的值 112.路径总和 113.路径总和II 106从中序与后序遍历序列构造二叉树 105.从前序与中序遍历序列构造二叉树 总结 513找到左下角的值…

[240621] Anthropic 发布了 Claude 3.5 Sonnet AI 助手 | Socket.IO 拒绝服务漏洞

目录 Anthropic 发布 Claude 3.5 Sonnet AI 助手Scoket.IO 拒绝服务漏洞&#xff08;CVE-2024-38355&#xff09; Anthropic 发布 Claude 3.5 Sonnet AI 助手 Claude 3.5 Sonnet: 更智能、更快速、更安全的 AI 助手 一、 引言 Anthropic 发布了 Claude 3.5 Sonnet&#xff0…

慢阻肺患者为何容易营养不良?朗格力教你轻松改善

#肺科营养#朗格力#班古营养#复合营养素#肺部营养#肺部健康# 慢阻肺是我国常见的、高患病率的慢性呼吸系统疾病,会对肺结构和功能产生影响,从而引起各种不良反应,其中营养不良是常见并发症之一。慢阻肺为什么会发生营养不良?营养不良又是怎么伤害慢阻肺的呢?为什么像班古精准…

鸿蒙开发:【进程模型概述】

进程模型概述 系统的进程模型如下图所示&#xff1a; 应用中&#xff08;同一包名&#xff09;的所有PageAbility、ServiceAbility、DataAbility、FormAbility运行在同一个独立进程中&#xff0c;即图中绿色部分的“Main Process”。 WebView拥有独立的渲染进程&#xff0c;即…