- 17.Pytest配置文件
- 18.Pytest常用插件
- 19.Pytest高阶用法
- 20.Pytest-fixture
- 21.PO模式
- 22.项目准备
- 23.多文件区分测试用例
- 23.封装前置代码
- 24.分离测试脚本
- 25.抽取找元素的特征
- 26.抽取action
- 27.增加WebDriverWait
- 28.xpath特殊处理
- 29.Yaml数据存储文件
- 30.Python解析yaml文件
- 31.Yaml数据驱动应用
- 32.Allure报告
- 33.Allure之Pytest
- 34.Jenkins安装
- 35.Jenkins持续集成配置
- 36.jenkins触发项目构建方式
- 37.Jenkins邮件配置
- 38.XPath精确查找
- 39.XPath多条件查找
- 文件管理器
- 小作业
- 常用代码
- capabilities启动参数列表
- keyevent常用键列表
17.Pytest配置文件
pytest的配置文件通常放在测试目录下,名称为pytest.ini,命令行运行时会使用该配置文件中的配置.
配置pytest命令行运行参数
[pytest]
addopts = -s ... # 空格分隔,可添加多个命令行参数 -所有参数均为插件包的参数
配置测试搜索的路径
[pytest]
testpaths = ./scripts # 当前目录下的scripts文件夹 -可自定义
配置测试搜索的文件名
[pytest]
python_files = test_*.py
# 当前目录下的scripts文件夹下,以test_开头,以.py结尾的所有文件 -可自定义
配置测试搜索的测试类名
[pytest]
python_classes = Test*
# 当前目录下的scripts文件夹下,以test_开头,以.py结尾的所有文件中,以Test_开头的类 -可自定义
配置测试搜索的测试函数名
[pytest]
python_functions = test_*
# 当前目录下的scripts文件夹下,以test_开头,以.py结尾的所有文件中,以Test_开头的类内,以test_开头的方法 -可自定义
18.Pytest常用插件
插件列表网址:https://plugincompat.herokuapp.com
包含很多插件包,大家可依据工作的需求选择使用。
前置条件:
1.文件路径:
- Test_App
- - test_abc.py
- - pytest.ini
2.pyetst.ini配置文件内容:
[pytest]
# 命令行参数
addopts = -s
# 搜索文件名
python_files = test_*.py
# 搜索的类名
python_classes = Test*
# 搜索的函数名
python_functions = test_*
Pytest测试报告
通过命令行方式,生成xml/html格式的测试报告,存储于用户指定路径。
插件名称:pytest-html
安装方式:
1.安装包方式 python setup.py install
2.命令行 pip3 install pytest-html
使用方法:
命令行格式:pytest --html=用户路径/report.html
示例:
import pytest
class TestABC:
def setup_class(self):
print("------->setup_class")
def teardown_class(self):
print("------->teardown_class")
def test_a(self):
print("------->test_a")
assert 1
def test_b(self):
print("------->test_b")
assert 0 # 断言失败
运行方式:
1.修改Test_App/pytest.ini文件,添加报告参数,即:addopts = -s --html=./report.html
# -s:输出程序运行信息
# --html=./report.html 在当前目录下生成report.html文件
⚠️ 若要生成xml文件,可将--html=./report.html 改成 --html=./report.xml
2.命令行进入Test_App目录
3.执行命令: pytest
执行结果:
1.在当前目录会生成assets文件夹和report.html文件
Pytest控制函数执行顺序
函数修饰符的方式标记被测试函数执行的顺序.
插件名称:pytest-ordering
安装方式:
1.安装包方式 python setup.py install
2.命令行 pip3 install pytest-ordering
使用方法:
1.标记于被测试函数,@pytest.mark.run(order=x)
2.根据order传入的参数来解决运行顺序
3.order值全为正数或全为负数时,运行顺序:值越小,优先级越高
4.正数和负数同时存在:正数优先级高
默认情况下,pytest是根据测试方法名由小到大执行的,可以通过第三方插件包改变其运行顺序。
默认执行方式
示例:
import pytest
class TestABC:
def setup_class(self):
print("------->setup_class")
def teardown_class(self):
print("------->teardown_class")
def test_a(self):
print("------->test_a")
assert 1
def test_b(self):
print("------->test_b")
assert 0
if __name__ == '__main__':
pytest.main("-s test_abc.py")
执行结果:
test_abc.py
------->setup_class
------->test_a # 默认第一个运行
.
------->test_b # 默认第二个运行
F
------->teardown_class
示例:
import pytest
class TestABC:
def setup_class(self):
print("------->setup_class")
def teardown_class(self):
print("------->teardown_class")
@pytest.mark.run(order=2)
def test_a(self):
print("------->test_a")
assert 1
@pytest.mark.run(order=1)
def test_b(self):
print("------->test_b")
assert 0
if __name__ == '__main__':
pytest.main("-s test_abc.py")
执行结果:
test_abc.py
------->setup_class
------->test_b # order=1 优先运行
F
------->test_a # order=2 晚于 order=1 运行
.
------->teardown_class
Pytest失败重试
通过命令行方式,控制失败函数的重试次数。
插件名称:pytest-rerunfailures
安装方式:
1.安装包方式 python setup.py install
2.命令行 pip3 install pytest-rerunfailures
使用方法:
命令行格式:pytest --reruns n # n:为重试的次数
示例:
import pytest
class Test_ABC:
def setup_class(self):
print("------->setup_class")
def teardown_class(self):
print("------->teardown_class")
def test_a(self):
print("------->test_a")
assert 1
def test_b(self):
print("------->test_b")
assert 0 # 断言失败
运行方式:
1.修改Test_App/pytest.ini文件,添加失败重试参数,即:addopts = -s --reruns 2 --html=./report.html
# -s:输出程序运行信息
# --reruns 2 :失败测试函数重试两次
# --html=./report.html 在当前目录下生成report.html文件
2.命令行进入Test_App目录
3.执行命令: pytest
执行结果:
1.在测试报告中可以看到两次重试记录
19.Pytest高阶用法
跳过测试函数
根据特定的条件,不执行标识的测试函数.
方法:
skipif(condition, reason=None)
参数:
condition:跳过的条件,必传参数
reason:标注原因,必传参数
使用方法:
@pytest.mark.skipif(condition, reason="xxx")
示例:
import pytest
class TestABC:
def setup_class(self):
print("------->setup_class")
def teardown_class(self):
print("------->teardown_class")
def test_a(self):
print("------->test_a")
assert 1
@pytest.mark.skipif(condition=2>1,reason = "跳过该函数") # 跳过测试函数test_b
def test_b(self):
print("------->test_b")
assert 0
执行结果:
test_abc.py
------->setup_class
------->test_a #只执行了函数test_a
.
------->teardown_class
s # 跳过函数
标记为预期失败函数
标记测试函数为失败函数
方法:
xfail(condition=None, reason=None, raises=None, run=True, strict=False)
常用参数:
condition:预期失败的条件,必传参数
reason:失败的原因,必传参数
使用方法:
@pytest.mark.xfail(condition, reason="xx")
示例:
import pytest
class TestABC:
def setup_class(self):
print("------->setup_class")
def teardown_class(self):
print("------->teardown_class")
def test_a(self):
print("------->test_a")
assert 1
@pytest.mark.xfail(2 > 1, reason="标注为预期失败") # 标记为预期失败函数test_b
def test_b(self):
print("------->test_b")
assert 0
执行结果:
test_abc.py
------->setup_class
------->test_a
.
------->test_b
------->teardown_class
x # 失败标记
函数数据参数化
方便测试函数对测试属于的获取。
方法:
parametrize(argnames, argvalues, indirect=False, ids=None, scope=None)
常用参数:
argnames:参数名
argvalues:参数对应值,类型必须为list
当参数为一个时格式:[value]
当参数个数大于一个时,格式为:[(param_value1,param_value2.....),(param_value1,param_value2.....)]
使用方法:
@pytest.mark.parametrize(argnames,argvalues)
⚠️ 参数值为N个,测试方法就会运行N次
单个参数示例:
import pytest
class TestABC:
def setup_class(self):
print("------->setup_class")
def teardown_class(self):
print("------->teardown_class")
@pytest.mark.parametrize("a",[3,6]) # a参数被赋予两个值,函数会运行两遍
def test_a(self,a): # 参数必须和parametrize里面的参数一致
print("test data:a=%d"%a)
assert a%3 == 0
执行结果:
test_abc.py
------->setup_class
test data:a=3 # 运行第一次取值a=3
.
test data:a=6 # 运行第二次取值a=6
.
------->teardown_class
多个参数示例:
import pytest
class TestABC:
def setup_class(self):
print("------->setup_class")
def teardown_class(self):
print("------->teardown_class")
@pytest.mark.parametrize("a,b",[(1,2),(0,3)]) # 参数a,b均被赋予两个值,函数会运行两遍
def test_a(self,a,b): # 参数必须和parametrize里面的参数一致
print("test data:a=%d,b=%d"%(a,b))
assert a+b == 3
执行结果:
test_abc.py
------->setup_class
test data:a=1,b=2 # 运行第一次取值 a=1,b=2
.
test data:a=0,b=3 # 运行第二次取值 a=0,b=3
.
------->teardown_class
函数返回值类型示例:
import pytest
def return_test_data():
return [(1,2),(0,3)]
class TestABC:
def setup_class(self):
print("------->setup_class")
def teardown_class(self):
print("------->teardown_class")
@pytest.mark.parametrize("a,b",return_test_data()) # 使用函数返回值的形式传入参数值
def test_a(self,a,b):
print("test data:a=%d,b=%d"%(a,b))
assert a+b == 3
执行结果:
test_abc.py
------->setup_class
test data:a=1,b=2 # 运行第一次取值 a=1,b=2
.
test data:a=0,b=3 # 运行第二次取值 a=0,b=3
.
------->teardown_class
20.Pytest-fixture
fixture修饰器来标记固定的工厂函数,在其他函数,模块,类或整个工程调用它时会被激活并优先执行,
通常会被用于完成预置处理和重复操作。
方法:fixture(scope="function", params=None, autouse=False, ids=None, name=None)
常用参数:
scope:被标记方法的作用域
function" (default):作用于每个测试方法,每个test都运行一次
"class":作用于整个类,每个class的所有test只运行一次
"module":作用于整个模块,每个module的所有test只运行一次
"session:作用于整个session(慎用),每个session只运行一次
params:(list类型)提供参数数据,供调用标记方法的函数使用
autouse:是否自动运行,默认为False不运行,设置为True自动运行
fixture(通过参数引用)
示例:
import pytest
class TestABC:
@pytest.fixture()
def before(self):
print("------->before")
def test_a(self,before): # ⚠️ test_a方法传入了被fixture标识的函数,已变量的形式
print("------->test_a")
assert 1
if __name__ == '__main__':
pytest.main("-s test_abc.py")
执行结果:
test_abc.py
------->before # 发现before会优先于测试函数运行
------->test_a
.
fixture(通过函数引用)
示例:
import pytest
@pytest.fixture() # fixture标记的函数可以应用于测试类外部
def before():
print("------->before")
@pytest.mark.usefixtures("before")
class TestABC:
def setup(self):
print("------->setup")
def test_a(self):
print("------->test_a")
assert 1
if __name__ == '__main__':
pytest.main("-s test_abc.py")
执行结果:
test_abc.py
------->before # 发现before会优先于测试类运行
------->setup
------->test_a
.
fixture(默认设置为运行)
示例:
import pytest
@pytest.fixture(autouse=True) # 设置为默认运行
def before():
print("------->before")
class TestABC:
def setup(self):
print("------->setup")
def test_a(self):
print("------->test_a")
assert 1
if __name__ == '__main__':
pytest.main("-s test_abc.py")
执行结果:
test_abc.py
------->before # 发现before自动优先于测试类运行
------->setup
------->test_a
.
fixture(作用域为function)
示例:
import pytest
@pytest.fixture(scope='function',autouse=True) # 作用域设置为function,自动运行
def before():
print("------->before")
class TestABC:
def setup(self):
print("------->setup")
def test_a(self):
print("------->test_a")
assert 1
def test_b(self):
print("------->test_b")
assert 1
if __name__ == '__main__':
pytest.main("-s test_abc.py")
执行结果:
test_abc.py
------->before # 运行第一次
------->setup
------->test_a
.------->before # 运行第二次
------->setup
------->test_b
.
fixture(作用域为class)
示例:
import pytest
@pytest.fixture(scope='class',autouse=True) # 作用域设置为class,自动运行
def before():
print("------->before")
class TestABC:
def setup(self):
print("------->setup")
def test_a(self):
print("------->test_a")
assert 1
def test_b(self):
print("------->test_b")
assert 1
if __name__ == '__main__':
pytest.main("-s test_abc.py")
执行结果:
test_abc.py
------->before # 发现只运行一次
------->setup
------->test_a
.
------->setup
------->test_b
.
fixture(返回值)
示例一:
import pytest
@pytest.fixture()
def need_data():
return 2 # 返回数字2
class TestABC:
def test_a(self,need_data):
print("------->test_a")
assert need_data != 3 # 拿到返回值做一次断言
if __name__ == '__main__':
pytest.main("-s test_abc.py")
执行结果:
test_abc.py
------->test_a
.
示例二:
import pytest
@pytest.fixture(params=[1, 2, 3])
def need_data(request): # 传入参数request 系统封装参数
return request.param # 取列表中单个值,默认的取值方式
class TestABC:
def test_a(self,need_data):
print("------->test_a")
assert need_data != 3 # 断言need_data不等于3
if __name__ == '__main__':
pytest.main("-s test_abc.py")
执行结果:
# 可以发现结果运行了三次
test_abc.py
1
------->test_a
.
2
------->test_a
.
3
------->test_a
F
21.PO模式
Page Object Model
测试页面和测试脚本分离,即页面封装成类,供测试脚本进行调用。
优缺点
- 优点
1.提高测试用例的可读性;
2.减少了代码的重复;
3.提高测试用例的可维护性,特别是针对UI频繁变动的项目;
- 缺点
结构复杂: 基于流程做了模块化的拆分。
22.项目准备
需求
- 更多-移动网络-首选网络类型-点击2G
- 更多-移动网络-首选网络类型-点击3G
- 显示-搜索按钮-输入hello-点击返回
文件目录
PO模式
- scripts
- - test_settting.py
- pytest.ini
代码
test_setting.py
import time
from appium import webdriver
class TestSetting:
def setup(self):
# server 启动参数
desired_caps = {}
# 设备信息
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '5.1'
desired_caps['deviceName'] = '192.168.56.101:5555'
# app的信息
desired_caps['appPackage'] = 'com.android.settings'
desired_caps['appActivity'] = '.Settings'
# 解决输入中文
desired_caps['unicodeKeyboard'] = True
desired_caps['resetKeyboard'] = True
# 声明我们的driver对象
self.driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
def test_mobile_network_2g(self):
self.driver.find_element_by_xpath("//*[contains(@text,'更多')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'移动网络')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'首选网络类型')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'2G')]").click()
def test_mobile_network_3g(self):
self.driver.find_element_by_xpath("//*[contains(@text,'更多')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'移动网络')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'首选网络类型')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'3G')]").click()
def test_mobile_display_input(self):
self.driver.find_element_by_xpath("//*[contains(@text,'显示')]").click()
self.driver.find_element_by_id("com.android.settings:id/search").click()
self.driver.find_element_by_id("android:id/search_src_text").send_keys("hello")
self.driver.find_element_by_class_name("android.widget.ImageButton").click()
def teardown(self):
self.driver.quit()
pytest.ini
[pytest]
# 添加行参数
addopts = -s --html=./report/report.html
# 文件搜索路径
testpaths = ./scripts
# 文件名称
python_files = test_*.py
# 类名称
python_classes = Test*
# 方法名称
python_functions = test_*
23.多文件区分测试用例
需求
- 使用多个文件来区分不同的测试页面
好处
- 修改不同的功能找对应的文件即可
步骤
- 在scripts下新建test_network.py文件
- 在scripts下新建test_dispaly.py文件
- 移动不同的功能代码到对应的文件
- 移除原有的test_setting.py文件
文件目录
PO模式
- scripts
- - test_network.py
- - test_dispaly.py
- pytest.ini
代码
test_network.py
from appium import webdriver
class TestNetwork:
def setup(self):
# server 启动参数
desired_caps = {}
# 设备信息
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '5.1'
desired_caps['deviceName'] = '192.168.56.101:5555'
# app的信息
desired_caps['appPackage'] = 'com.android.settings'
desired_caps['appActivity'] = '.Settings'
# 解决输入中文
desired_caps['unicodeKeyboard'] = True
desired_caps['resetKeyboard'] = True
# 声明我们的driver对象
self.driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
def test_mobile_network_2g(self):
self.driver.find_element_by_xpath("//*[contains(@text,'更多')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'移动网络')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'首选网络类型')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'2G')]").click()
def test_mobile_network_3g(self):
self.driver.find_element_by_xpath("//*[contains(@text,'更多')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'移动网络')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'首选网络类型')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'3G')]").click()
def teardown(self):
self.driver.quit()
test_dispaly.py
from appium import webdriver
class TestDisplay:
def setup(self):
# server 启动参数
desired_caps = {}
# 设备信息
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '5.1'
desired_caps['deviceName'] = '192.168.56.101:5555'
# app的信息
desired_caps['appPackage'] = 'com.android.settings'
desired_caps['appActivity'] = '.Settings'
# 解决输入中文
desired_caps['unicodeKeyboard'] = True
desired_caps['resetKeyboard'] = True
# 声明我们的driver对象
self.driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
def test_mobile_display_input(self):
self.driver.find_element_by_xpath("//*[contains(@text,'显示')]").click()
self.driver.find_element_by_id("com.android.settings:id/search").click()
self.driver.find_element_by_id("android:id/search_src_text").send_keys("hello")
self.driver.find_element_by_class_name("android.widget.ImageButton").click()
def teardown(self):
self.driver.quit()
23.封装前置代码
需求
- 将前置代码进行封装
好处
- 前置代码只需要写一份
步骤
- 新建base文件夹
- 新建base_driver.py文件
- 新建函数init_driver
- 写入前置代码并返回
- 修改测试文件中的代码
文件目录
PO模式
- base
- - base_driver.py
- scripts
- - test_network.py
- - test_dispaly.py
- pytest.ini
代码
base_driver.py
from appium import webdriver
def init_driver():
# server 启动参数
desired_caps = {}
# 设备信息
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '5.1'
desired_caps['deviceName'] = '192.168.56.101:5555'
# app的信息
desired_caps['appPackage'] = 'com.android.settings'
desired_caps['appActivity'] = '.Settings'
# 解决输入中文
desired_caps['unicodeKeyboard'] = True
desired_caps['resetKeyboard'] = True
# 声明我们的driver对象
return webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
test_network.py
from base.base_driver import init_driver
class TestNetwork:
def setup(self):
self.driver = init_driver()
def test_mobile_network_2g(self):
self.driver.find_element_by_xpath("//*[contains(@text,'更多')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'移动网络')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'首选网络类型')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'2G')]").click()
def test_mobile_network_3g(self):
self.driver.find_element_by_xpath("//*[contains(@text,'更多')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'移动网络')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'首选网络类型')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'3G')]").click()
def teardown(self):
self.driver.quit()
test_dispaly.py
from base.base_driver import init_driver
class TestDisplay:
def setup(self):
self.driver = init_driver()
def test_mobile_display_input(self):
self.driver.find_element_by_xpath("//*[contains(@text,'显示')]").click()
self.driver.find_element_by_id("com.android.settings:id/search").click()
self.driver.find_element_by_id("android:id/search_src_text").send_keys("hello")
self.driver.find_element_by_class_name("android.widget.ImageButton").click()
def teardown(self):
self.driver.quit()
24.分离测试脚本
需求
- 测试脚本只剩流程
- 其他的步骤放倒page中
好处
- 测试脚本只专注过程
- 过程改变只需要修改脚本
步骤
- 新建page文件夹
- 新建network_page.py文件
- 新建display_page.py文件
- init函数传入driver
- init进入需要测试的页面
- page中新建“小动作”函数
- 移动代码
- 修改测试文件中的代码
文件目录
PO模式
- base
- - base_driver.py
- page
- - network_page.py
- - display_page.py
- scripts
- - test_network.py
- - test_dispaly.py
- pytest.ini
代码
base_driver.py
from appium import webdriver
def init_driver():
# server 启动参数
desired_caps = {}
# 设备信息
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '5.1'
desired_caps['deviceName'] = '192.168.56.101:5555'
# app的信息
desired_caps['appPackage'] = 'com.android.settings'
desired_caps['appActivity'] = '.Settings'
# 解决输入中文
desired_caps['unicodeKeyboard'] = True
desired_caps['resetKeyboard'] = True
# 声明我们的driver对象
return webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
network_page.py
class NetWorkPage:
def __init__(self, driver):
self.driver = driver
self.driver.find_element_by_xpath("//*[contains(@text,'更多')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'移动网络')]").click()
def click_first_network(self):
self.driver.find_element_by_xpath("//*[contains(@text,'首选网络类型')]").click()
def click_2g(self):
self.driver.find_element_by_xpath("//*[contains(@text,'2G')]").click()
def click_3g(self):
self.driver.find_element_by_xpath("//*[contains(@text,'3G')]").click()
display_page.py
class DisplayPage:
def __init__(self, driver):
self.driver = driver
self.driver.find_element_by_xpath("//*[contains(@text,'显示')]").click()
def click_search(self):
self.driver.find_element_by_id("com.android.settings:id/search").click()
def input_text(self, text):
self.driver.find_element_by_id("android:id/search_src_text").send_keys(text)
def click_back(self):
self.driver.find_element_by_class_name("android.widget.ImageButton").click()
test_network.py
import sys, os
sys.path.append(os.getcwd())
from base.base_driver import init_driver
from page.network_page import NetWorkPage
class TestNetwork:
def setup(self):
self.driver = init_driver()
self.network_page = NetWorkPage(self.driver)
def test_mobile_network_2g(self):
self.network_page.click_first_network()
self.network_page.click_2g()
def test_mobile_network_3g(self):
self.network_page.click_first_network()
self.network_page.click_3g()
def teardown(self):
self.driver.quit()
test_dispaly.py
import sys, os
sys.path.append(os.getcwd())
from base.base_driver import init_driver
from page.display_page import DisplayPage
class TestDisplay:
def setup(self):
self.driver = init_driver()
self.display_page = DisplayPage(self.driver)
def test_mobile_display_input(self):
self.display_page.click_search()
self.display_page.input_text("hello")
self.display_page.click_back()
def teardown(self):
self.driver.quit()
25.抽取找元素的特征
需求
- 将元素的特城放在函数之上
好处
- 若特征改了,流程不变,可以直接在上面修改
步骤
- 将find_element_by_xxx改为find_element
- 将方式和具体特征向上移动
文件目录
PO模式
- base
- - base_driver.py
- page
- - network_page.py
- - display_page.py
- scripts
- - test_network.py
- - test_dispaly.py
- pytest.ini
代码
base_driver.py
from appium import webdriver
def init_driver():
# server 启动参数
desired_caps = {}
# 设备信息
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '5.1'
desired_caps['deviceName'] = '192.168.56.101:5555'
# app的信息
desired_caps['appPackage'] = 'com.android.settings'
desired_caps['appActivity'] = '.Settings'
# 解决输入中文
desired_caps['unicodeKeyboard'] = True
desired_caps['resetKeyboard'] = True
# 声明我们的driver对象
return webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
network_page.py
from selenium.webdriver.common.by import By
class NetWorkPage:
more_button = By.XPATH, "//*[contains(@text,'更多')]"
mobile_network_button = By.XPATH, "//*[contains(@text,'移动网络')]"
click_first_network_button = By.XPATH, "//*[contains(@text,'首选网络类型')]"
network_2g_button = By.XPATH, "//*[contains(@text,'2G')]"
network_3g_button = By.XPATH, "//*[contains(@text,'3G')]"
def __init__(self, driver):
self.driver = driver
self.driver.find_element(self.more_button).click()
self.driver.find_element(self.mobile_network_button).click()
def click_first_network(self):
self.driver.find_element(self.click_first_network_button).click()
def click_2g(self):
self.driver.find_element(self.network_2g_button).click()
def click_3g(self):
self.driver.find_element(self.network_3g_button).click()
display_page.py
from selenium.webdriver.common.by import By
class DisplayPage:
display_button = By.XPATH, "//*[contains(@text,'显示')]"
search_button = By.ID, "com.android.settings:id/search"
search_edit_text = By.ID, "android:id/search_src_text"
back_button = By.CLASS_NAME, "android.widget.ImageButton"
def __init__(self, driver):
self.driver = driver
self.driver.find_element(self.display_button).click()
def click_search(self):
self.driver.find_element(self.search_button).click()
def input_text(self, text):
self.driver.find_element(self.search_edit_text).send_keys(text)
def click_back(self):
self.driver.find_element(self.back_button).click()
test_network.py
import sys, os
sys.path.append(os.getcwd())
from base.base_driver import init_driver
from page.network_page import NetWorkPage
class TestNetwork:
def setup(self):
self.driver = init_driver()
self.network_page = NetWorkPage(self.driver)
def test_mobile_network_2g(self):
self.network_page.click_first_network()
self.network_page.click_2g()
def test_mobile_network_3g(self):
self.network_page.click_first_network()
self.network_page.click_3g()
def teardown(self):
self.driver.quit()
test_dispaly.py
import sys, os
sys.path.append(os.getcwd())
from base.base_driver import init_driver
from page.display_page import DisplayPage
class TestDisplay:
def setup(self):
self.driver = init_driver()
self.display_page = DisplayPage(self.driver)
def test_mobile_display_input(self):
self.display_page.click_search()
self.display_page.input_text("hello")
self.display_page.click_back()
def teardown(self):
self.driver.quit()
26.抽取action
需求
- 将动作进行封装
步骤
- 新建base_action.py文件
- 将click、send_keys抽取到文件中
文件目录
PO模式
- base
- - base_driver.py
- - base_action.py
- page
- - network_page.py
- - display_page.py
- scripts
- - test_network.py
- - test_dispaly.py
- pytest.ini
代码
base_action.py
class BaseAction:
def __init__(self, driver):
self.driver = driver
def click(self, loc):
self.driver.find_element(loc[0], loc[1]).click()
def input(self, loc, text):
self.driver.find_element(loc[0], loc[1]).send_keys(text)
network_page.py
import sys, os
sys.path.append(os.getcwd())
from selenium.webdriver.common.by import By
from base.base_action import BaseAction
class NetWorkPage(BaseAction):
more_button = By.XPATH, "//*[contains(@text,'更多')]"
mobile_network_button = By.XPATH, "//*[contains(@text,'移动网络')]"
click_first_network_button = By.XPATH, "//*[contains(@text,'首选网络类型')]"
network_2g_button = By.XPATH, "//*[contains(@text,'2G')]"
network_3g_button = By.XPATH, "//*[contains(@text,'3G')]"
def __init__(self, driver):
BaseAction.__init__(self, driver)
self.click(self.more_button)
self.click(self.mobile_network_button)
def click_first_network(self):
self.click(self.click_first_network_button)
def click_2g(self):
self.click(self.network_2g_button)
def click_3g(self):
self.click(self.network_3g_button)
display_page.py
import sys, os
sys.path.append(os.getcwd())
from selenium.webdriver.common.by import By
from base.base_action import BaseAction
class DisplayPage(BaseAction):
display_button = By.XPATH, "//*[contains(@text,'显示')]"
search_button = By.ID, "com.android.settings:id/search"
search_edit_text = By.ID, "android:id/search_src_text"
back_button = By.CLASS_NAME, "android.widget.ImageButton"
def __init__(self, driver):
BaseAction.__init__(self, driver)
self.click(self.display_button)
def click_search(self):
self.click(self.search_button)
def input_text(self, text):
self.input(self.search_edit_text, text)
def click_back(self):
self.click(self.back_button)
test_network.py
import sys, os
sys.path.append(os.getcwd())
from base.base_driver import init_driver
from page.network_page import NetWorkPage
class TestNetwork:
def setup(self):
self.driver = init_driver()
self.network_page = NetWorkPage(self.driver)
def test_mobile_network_2g(self):
self.network_page.click_first_network()
self.network_page.click_2g()
def test_mobile_network_3g(self):
self.network_page.click_first_network()
self.network_page.click_3g()
def teardown(self):
self.driver.quit()
test_dispaly.py
import sys, os
import time
sys.path.append(os.getcwd())
from base.base_driver import init_driver
from page.display_page import DisplayPage
class TestDisplay:
def setup(self):
self.driver = init_driver()
self.display_page = DisplayPage(self.driver)
def test_mobile_display_input(self):
self.display_page.click_search()
self.display_page.input_text("hello")
self.display_page.click_back()
pass
def teardown(self):
self.driver.quit()
27.增加WebDriverWait
需求
- 找控件使用WebDriverWait
好处
- 防止出现cpu卡的时候找不到
步骤
- 自己新建find方法
- 在系统的基础上增加WebDriverWait
文件目录
PO模式
- base
- - base_driver.py
- - base_action.py
- page
- - network_page.py
- - display_page.py
- scripts
- - test_network.py
- - test_dispaly.py
- pytest.ini
代码
base_action.py
from selenium.webdriver.support.wait import WebDriverWait
class BaseAction:
def __init__(self, driver):
self.driver = driver
def find_element(self, loc, time=10, poll=1):
return WebDriverWait(self.driver, time, poll).until(lambda x: x.find_element(loc[0], loc[1]))
# return self.driver.find_element(by, value)
def find_elements(self, loc, time=10, poll=1):
return WebDriverWait(self.driver, time, poll).until(lambda x: x.find_elements(loc[0], loc[1]))
# return self.driver.find_elements(by, value)
def click(self, loc):
self.find_element(loc).click()
def input(self, loc, text):
self.find_element(loc).send_keys(text)
28.xpath特殊处理
需求
- //*[contains(@,’’)]是相同的可以进行抽取
好处
- 少写代码
步骤
- 获取loc后,进行拆分
- 字符串拼接
文件目录
PO模式
- base
- - base_driver.py
- - base_action.py
- page
- - network_page.py
- - display_page.py
- scripts
- - test_network.py
- - test_dispaly.py
- pytest.ini
代码
base_action.py
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
class BaseAction:
def __init__(self, driver):
self.driver = driver
def find_element(self, loc, time=10, poll=0.5):
loc_by, loc_value = loc
if loc_by == By.XPATH:
xpath_key, xpath_value = loc_value
loc_value = "//*[contains(@%s, '%s')]" % (xpath_key, xpath_value)
return WebDriverWait(self.driver, time, poll).until(lambda x: x.find_element(loc_by, loc_value))
def find_elements(self, loc, time=10, poll=0.5):
loc_by, loc_value = loc
if loc_by == By.XPATH:
xpath_key, xpath_value = loc_value
loc_value = "//*[contains(@%s, '%s')]" % (xpath_key, xpath_value)
return WebDriverWait(self.driver, time, poll).until(lambda x: x.find_elements(loc_by, loc_value))
def click(self, loc):
self.find_element(loc).click()
def input(self, loc, text):
self.find_element(loc).send_keys(text)
29.Yaml数据存储文件
YAML 是一种所有编程语言可用的友好的数据序列化标准,语法和其他高阶语言类似,并且可以简单表达清单、散列表,标量等资料形态.
- 语法规则
1.大小写敏感
2.使用缩进表示层级关系
3.缩进时不允许使用Tab键,只允许使用空格。
4.缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
- 支持的数据结构
1.对象:键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)
2.数组:一组按次序排列的值,又称为序列(sequence) / 列表(list)
3.纯量(scalars):单个的、不可再分的值
1.对象
值为字符
data.yaml animal: pets 转换为python代码 {'animal': 'pets'}
值为字典
data.yaml animal: {"ke1":"pets","key2":"app"} # python字典 转换为python代码 {animal: {"ke1":"pets","key2":"app"}} # 嵌套字典结构
2.数组
方式一
data.yaml animal: - data1 - data2 转换为python代码 {'animal': ['data1', 'data2']}
方式二
data.yaml animal: ['data1', 'data2'] # python列表 转换为python代码 {'animal': ['data1', 'data2']} # 字典嵌套列表
纯量
包含:字符串,布尔值,整数,浮点数,Null,日期
字符串
data.yaml
value: "hello"
转换为python代码
{"value":"hello"}
布尔值
data.yaml
value1: true
value2: false
转换为python代码
{'value1': True, 'value2': False}
整数,浮点数
data.yaml
value1: 12
value2: 12.102
转换为python代码
{'value1': 12, 'value2': 12.102}
空(Null)
data.yaml
value1: ~ # ~ 表示为空
转换为python代码
{'value1': None}
日期
data.yaml
value1: 2017-10-11 15:12:12
转换为python代码
{'languages': {'value1': datetime.datetime(2017, 10, 11, 15, 12, 12)}}
- 锚点&和引用*
锚点:标注一个内容,锚点名称自定义
引用:使用被标注的内容<<: *锚点名
data.yaml
data: &imp
value: 456
name:
value1: 123
<<: *imp # "<<:" 合并到当前位置,"*imp" 引用锚点imp
转换为python代码
{'data': {'value': 456}, 'name': {'value': 456, 'value1': 123}}
30.Python解析yaml文件
- PyYAML库安装
PyYAML为python解析yaml的库
安装:pip3 install -U PyYAML
- yaml文件内容
Search_Data:
search_test_001:
value: 456
expect: [4,5,6]
search_test_002:
value: "你好"
expect: {"value":"你好"}
读取yaml文件
方法
yaml.load(stream, Loader=Loader) 参数: stream:待读取文件对象
示例: import yaml with open("../Data/search_page.yaml",'r') as f: data = yaml.load(f) print(type(data)) # 打印data类型 print(data) # 打印data返回值 执行结果: <class 'dict'> {'Search_Data': { 'search_test_002': {'expect': {'value': '你好'}, 'value': '你好'}, 'search_test_001': {'expect': [4, 5, 6], 'value': 456}}}
写入yaml文件内容
{'Search_Data': {
'search_test_002': {'expect': {'value': '你好'}, 'value': '你好'},
'search_test_001': {'expect': [4, 5, 6], 'value': 456}}}
写yaml文件
- 方法
yaml.dump(data,stream,**kwds) 常用参数: data:写入数据类型为字典 stream:打开文件对象 encoding='utf-8' # 设置写入编码格式 allow_unicode=True # 是否允许unicode编码
示例:不设置编码格式 import yaml data = {'Search_Data': { 'search_test_002': {'expect': {'value': '你好'}, 'value': '你好'}, 'search_test_001': {'expect': [4, 5, 6], 'value': 456}} with open("./text.yaml","w") as f: # 在当前目录下生成text.yaml文件,若文件存在直接更新内容 yaml.dump(data,f) 执行结果: 1.当前目录生成text.yaml文件 2.文件内容: Search_Data: search_test_001: expect: [4, 5, 6] value: 456 search_test_002: expect: {value: "\u4F60\u597D"} # 中文出现乱码 value: "\u4F60\u597D" # 中文出现乱码
示例:设置编码格式 import yaml data = {'Search_Data': { 'search_test_002': {'expect': {'value': '你好'}, 'value': '你好'}, 'search_test_001': {'expect': [4, 5, 6], 'value': 456}} with open("./text.yaml","w") as f: # 在当前目录下生成text.yaml文件,若文件存在直接更新内容 yaml.dump(data,f) 执行结果: 1.当前目录生成text.yaml文件 2.文件内容: Search_Data: search_test_001: expect: [4, 5, 6] value: 456 search_test_002: expect: {value: 你好} # 中文未出现乱码 value: 你好 # 中文未出现乱码
- 方法
31.Yaml数据驱动应用
目标集成Pytest完成测试任务
- 测试项目
业务:
1.进入设置点击搜索按钮
2.输入搜索内容
3.点击返回
- 目录结构
App_Project # 项目名称
- Basic # 存储基础设施类
- __init__.py # 空文件
- Init_Driver.py # 手机驱动对象初始化
- Base.py # 方法的二次封装
- read_data.py #数据解析读取方法
- Page # 存储封装页面文件
- __init__.py # 存储页面元素
- search_page.py # 封装页面元素的操作方法
- Data # 存储数据文件
- search_data.yaml(也可以是其他文件比如txt,excel,json,数据库等)
- Test # 存储测试脚本目录
- test_search.py # 测试搜索文件
- pytest.ini # pytest运行配置文件
- 前置条件
1.手机驱动对象独立 # 见PO章节代码
2.方法的二次封装 # 见PO章节代码
3.完成页面的封装 # 见PO章节代码
- 待完成任务
1.编写数据驱动文件search_data.yaml
2.编写解析yaml文件类/方法
3.编写测试脚本
- 编写search_data.yaml
search_test_001: # 用例编号
input_text: "你好" # 测试输入数据
search_test_002:
input_text: "1234"
search_test_003:
input_text: "*&^%"
- 编写解析yaml方法
read_data.py
import yaml,os
class Read_Data:
def __init__(self,file_name):
'''
使用pytest运行在项目的根目录下运行,即App_Project目录
期望路径为:项目所在目录/App_Project/Data/file_name
'''
self.file_path = os.getcwd() + os.sep + "Data" + os.sep + file_name
def return_data(self):
with open(self.file_path,'r') as f:
data = yaml.load(f) # 读取文件内容
return data
# data:{"search_test_001":{"input_text": "你好"},"search_test_002":{"input_text": "1234"},"search_test_003":{"input_text": "*&^%"}}
- 测试脚本编写
test_search.py
import sys,os
# 因为需要在项目的根目录下运行,所以需要添加python包搜索路径
# pytest命令运行路径:App_Project目录下
# os.getcwd(): App_Project所在目录/App_Project
sys.path.append(os.getcwd())
# 导入封装好的页面类
from Page.search_page import Search_Page
# 导入独立的手机驱动对象
from Basic.Init_Driver import init_driver
from Basic.read_data import Read_Data
import pytest
def package_param_data():
list_data = [] # 存储参数值列表,格式[(用例编号1,输入内容2),(用例编号1,输入内容2)...]
yaml_data = Read_Data("search_data.yaml").return_data() # 返回yaml文件读取数据
for i in yaml_data.keys():
list_data.append((i,yaml_data.get(i).get('input_text'))) # list_data中添加参数值
return list_data
class Test_Search:
'''
我们希望测试函数运行多次,不希望每运行一次做一次初始化和退出,
所以使用setup_class,teardown_class,
测试类内只运行一次初始化和结束动作.
'''
def setup_class(self):
self.driver = init_driver()
@pytest.mark.parametrize('test_id,input_text',package_param_data()) # 参数传递三组参数,会运行三次
def test_search(self,test_id,input_text):
# 示例化页面封装类
sp = Search_Page(self.driver)
# 调用操作类
print("test_id:",test_id)
sp.input_search_text(input_text)
# 退出driver对象
def teardown_class(self):
self.driver.quit()
- pytest的配置文件
pytest.ini
[pytest]
addopts = -s --html=./report.html
# 测试路径
testpaths = ./Test
# 测试文件名
python_files = test_*.py
# 测试类名
python_classes = Test_*
# 测试的方法名
python_functions = test_*
- 项目运行
1.启动appium 服务:地址 127.0.0.1 端口 4723
2.启动模拟器
3.进入项目根目录:App_Project
4.命令行输入pytest运行测试
- 测试报告
32.Allure报告
Allure介绍
Allure是一个独立的报告插件,生成美观易读的报告,目前支持语言:Java, PHP, Ruby, Python, Scala, C#。
Allure安装
1.安装pytest的插件包pytest-allure-adaptor: pip3 install pytest-allure-adaptor
Allure帮助文档
https://docs.qameta.io/allure/#_about
生成Allure报告
命令行参数:pytest --alluredir report # 在执行命令目录生成report文件夹,文件夹下包含xml文件
- 示例
pytest.ini
[pytest]
;--html=./report.html
;删除原生html,增加Allure
addopts = -s --alluredir report
# 测试路径
testpaths = ./Test
# 测试文件名
python_files = test_*.py
# 测试类名
python_classes = Test_*
# 测试的方法名
python_functions = test_*
test_all.py
class Test_allure:
def setup(self):
pass
def teardown(self):
pass
def test_al(self):
assert 0
操作步骤:
1.命令行进入pytest.ini所在目录
2.输入命令:pytest
执行结果:
1.pytest.ini所在目录生成report文件夹,文件夹下生成一个xml文件
xml转html工具安装
mac版本
1.:brew install allure
2.进入report上级目录执行命令:allure generate report/ -o report/html
3.report目录下会生成index.html文件,即为可视化报告
windows版本
1.下载压缩包allure-2.6.0.zip
地址:https://bintray.com/qameta/generic/allure2
2.解压
3.将压缩包内的bin目录配置到path系统环境变量
4.进入report上级目录执行命令:allure generate report/ -o report/html --clean
5.report目录下会生成index.html文件,即为可视化报告
33.Allure之Pytest
添加测试步骤
方法:@allure.step(title="测试步骤001")
示例:
test_all.py
import allure, pytest
class Test_allure:
def setup(self):
pass
def teardown(self):
pass
@allure.step('我是测试步骤001')
def test_al(self, a):
assert a != 2
添加测试描述
方法:allure.attach('描述', '我是测试步骤001的描述~~~')
示例:
test_all.py
import allure, pytest
class Test_allure:
def setup(self):
pass
def teardown(self):
pass
@allure.step('我是测试步骤001')
def test_al(self, a):
allure.attach('描述', '我是测试步骤001的描述~~~')
assert a != 2
添加严重级别
测试用例设置不同的严重级别,可以帮助测试和开发人员更直观的关注重要Case.
方法:@pytest.allure.severity(Severity)
参数:
Severity:严重级别(BLOCKER,CRITICAL,NORMAL,MINOR,TRIVIAL)
使用方式:
@pytest.allure.severity(pytest.allure.severity_level.CRITICAL)
示例:
test_all.py
import allure, pytest
class Test_allure:
def setup(self):
pass
def teardown(self):
pass
@pytest.allure.severity(pytest.allure.severity_level.CRITICAL)
@allure.step('我是测试步骤001')
def test_al(self, a):
allure.attach('描述', '我是测试步骤001的描述~~~')
assert a != 2
34.Jenkins安装
Jenkins是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作,
旨在提供一个开放易用的软件平台,使软件的持续集成变成可能。
一般情况下,公司内部Jenkins安装在服务端,不需要本地安装,都已配置完成,可直接操作使用.
- 依赖java环境
jdk1.5以上
安装jenkins
1.下载jenkins.war
2.进入jenkins.war所在目录,执行:java -jar jenkins.war
3.浏览器进入:localhost:8080
安装所需插件
1.官网下载jenkins安装包
2.安装完成后,会自动打开页面(若没有可以手动打开浏览器输入:localhost:8080)
3.进入默认密码提示文件,输入系统默认密码
4.安装建议插件
5.设置用户初始化信息
6.jenkins启动
7.jenkins首页
35.Jenkins持续集成配置
Jenkins安装Allure插件
1.进入jenkins系统管理 -> 管理插件
2.点击可选插件
3.搜索框输入Allure Jenkins Plugin
4.选中安装
Jenkins安装Allure Commandline工具
1.进入jenkins系统管理 -> 全局工具安装
2.找到Allure Commandline,点击Allure Commandline安装
3.输入一个别名
4.点击新增安装-选择解压*.ip/*.tar.gz
5.解压目录选择已下载好的allure2.5.0.zip包所在目录(⚠️ 版本要一致)
6.点击保存
Jenkins新建一个项目
1.选择新建一个自由风格的软件项目 -> 点击确定
2.输入一些项目描述
3.选择GitHub project
4.输入Project url # 因我们只是举例,所以使用自己的一个github测试脚本
源码管理配置
5.勾选Git
6.Repository URL输入地址同第四步
7.点击Add添加github的用户名和密码
构建触发器
8.勾选Poll SCM # 根据定时任务,查看github版本是否更新,如果更新会自动构建项目
9.输入crontab命令
举例:
*/1 * * * * # 每一分钟检查一次
10.点击增加构建步骤,选择Execute shell
11.Command输入
mac:
export PATH=$PATH:"pytest可执行文件的目录"
pytest
windows:
PATH=$PATH;"pytest可执行文件的目录" #(到scripts)
pytest
时程表的格式如下:
f1 f2 f3 f4 f5 program
其中 f1 是表示分钟,f2 表示小时,f3 表示一个月份中的第几日,f4 表示月份,f5 表示一个星期中的第几天。program 表示要执行的程式。
构建后操作
12.点击增加构建后操作步骤,选择Allure Report
13.Path路径输入:生成的报告文件夹名称
⚠️ 文件夹名称与pytest生成的报告文件夹名称一致
36.jenkins触发项目构建方式
手动触发构建
- 点击立即构建
更新github代码
- 触发器在定时任务到达时,会出发项目构建
37.Jenkins邮件配置
发件人配置
配置邮件系统用户:
系统管理-系统设置-Jenkins Location
系统管理员邮件地址:用户名@163.com(发送邮件用户)
配置系统邮件:
系统管理-系统设置-邮件通知
SMTP服务器:例 smtp.163.com
用户默认邮件后缀:例如 @163.com
高级-使用SMTP认证
输入发送邮箱和密码 -可以使用测试邮件验证
配置(发送附件)邮件:
系统管理-系统设置-Extended E-mail Notification
SMTP server:例 smtp.163.com
Default user E-mail suffix:例如 @163.com
高级-Use SMTP Authentication - 输入发送邮件的邮箱和密码
Default Content Type: HTML(text/html)
Default Content(报告模版,使用以下html代码即可):
<hr/>(本邮件是程序自动下发的,请勿回复!)<hr/>
项目名称:$PROJECT_NAME<br/><hr/>
构建编号:$BUILD_NUMBER<br/><hr/>
git版本号:${GIT_REVISION}<br/><hr/>
构建状态:$BUILD_STATUS<br/><hr/>
触发原因:${CAUSE}<br/><hr/>
目录:${ITEM_ROOTDIR}<br/><hr/>
构建日志地址:<a href=" ">${BUILD_URL}console</a ><br/><hr/>
构建地址:<a href="$BUILD_URL">$BUILD_URL</a ><br/><hr/>
报告地址:<a href="${BUILD_URL}allure">${BUILD_URL}allure</a ><br/><hr/>
失败数:${FAILED_TESTS}<br/><hr/>
成功数:${FAILED_TESTS}<br/><hr/>
变更集:${JELLY_SCRIPT,template="html"}<br/><hr/>
收件人配置
# 添加测试报告接收邮件列表
14.点击增加构建后操作步骤,选择Editable Email Notification
15.点击Advanced Setting…
16.点击Triggers中的高级按钮
17.Recipient List输入邮件接收列表,多个邮件逗号分隔
38.XPath精确查找
//*[@text='Android']
用于查找某个属性是否等于某个值,而非包含。
39.XPath多条件查找
//*[@text='Android' and index='0']
用于通过多个条件进行查找
文件管理器
第一个
- 打开文件管理器
- 在sdcard新建一个zzz文件夹
- 在sdcard新建一个aaa文件夹
- 进入zzz文件夹
- 在zzz中创建文件 1.txt-20.txt
- 把zzz中的20个文件移动aaa中
- 判断-aaa有20个文件目录
目录
- 项目名字
- pytest.ini
- scripts
- - test_file
- page
- - file_page
- base
- - xxxx
test_file.py - 自动化脚本
脚本中有11个函数
1. 第一个
2. 如果属性弹窗中名字内容和所在位置一致 那么通过
3-10. 出了属性之外的左下角菜单所有功能
11. test_fail
assert 0
尽量写上allure的log
邮件模板,最上面加上自己的名字
邮箱:hitheima@163.com
小作业
元素定位和操作练习
- 点击搜索按钮
- 输入“无线”
- 获取当前有几条记录?
滑动和拖拽时间练习
- 想办法滑动到最后的“关于手机”
- 点击进去
- 看当前页面是不是有一个“5.1”的字符串
常用代码
前置代码
from appium import webdriver
# server 启动参数
desired_caps = {}
# 设备信息
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '5.1'
desired_caps['deviceName'] = '192.168.56.101:5555'
# app的信息
desired_caps['appPackage'] = 'com.android.settings'
desired_caps['appActivity'] = '.Settings'
# 解决输入中文
desired_caps['unicodeKeyboard'] = True
desired_caps['resetKeyboard'] = True
# 声明我们的driver对象
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
获取包名
1.Mac/Linux: 'adb shell dumpsys window windows | grep mFocusedApp’
2.在 Windows 终端运行 'adb shell dumpsys window windows’ 然后去看mFocusedApp这一行的内容。
xPath
//*[contains(@,'')]
capabilities启动参数列表
keyevent常用键列表
- 常用键展示
KEYCODE_CALL 拨号键 5
KEYCODE_ENDCALL 挂机键 6
KEYCODE_HOME 按键Home 3
KEYCODE_MENU 菜单键 82
KEYCODE_BACK 返回键 4
KEYCODE_SEARCH 搜索键 84
KEYCODE_CAMERA 拍照键 27
KEYCODE_FOCUS 拍照对焦键 80
KEYCODE_POWER 电源键 26
KEYCODE_NOTIFICATION 通知键 83
KEYCODE_MUTE 话筒静音键 91
KEYCODE_VOLUME_MUTE 扬声器静音键 164
KEYCODE_VOLUME_UP 音量增加键 24
KEYCODE_VOLUME_DOWN 音量减小键 25
KEYCODE_ENTER 回车键 66
KEYCODE_ESCAPE ESC键 111
KEYCODE_DPAD_CENTER 导航键 确定键 23
KEYCODE_DPAD_UP 导航键 向上 19
KEYCODE_DPAD_DOWN 导航键 向下 20
KEYCODE_DPAD_LEFT 导航键 向左 21
KEYCODE_DPAD_RIGHT 导航键 向右 22
KEYCODE_MOVE_HOME 光标移动到开始键 122
KEYCODE_MOVE_END 光标移动到末尾键 123
KEYCODE_PAGE_UP 向上翻页键 92
KEYCODE_PAGE_DOWN 向下翻页键 93
KEYCODE_DEL 退格键 67
KEYCODE_FORWARD_DEL 删除键 112
KEYCODE_INSERT 插入键 124
KEYCODE_TAB Tab键 61
KEYCODE_NUM_LOCK 小键盘锁 143
KEYCODE_CAPS_LOCK 大写锁定键 115
KEYCODE_BREAK Break/Pause键 121
KEYCODE_SCROLL_LOCK 滚动锁定键 116
KEYCODE_ZOOM_IN 放大键 168
KEYCODE_ZOOM_OUT 缩小键 169
KEYCODE_ALT_LEFT Alt+Left
KEYCODE_ALT_RIGHT Alt+Right
KEYCODE_CTRL_LEFT Control+Left
KEYCODE_CTRL_RIGHT Control+Right
KEYCODE_SHIFT_LEFT Shift+Left
KEYCODE_SHIFT_RIGHT Shift+Right
- 官方keyevent文档
地址: https://developer.android.com/reference/android/view/KeyEvent.html