Appium+Python-项目实践一

Posted 一加一

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Appium+Python-项目实践一相关的知识,希望对你有一定的参考价值。

一、前言                           

  前面讲了环境搭建和常用的元素定位,后续会持续以项目实践的方式去慢慢学习以及整理各方面的知识点,具体不会详细阐述,但会贴上完整代码,想要了解更多的可以直接网上查找资料哈,接下来用企业微信的应用(订单平台)做实践,环境配置:win10+Python 3.7.4+appium 1.16.0+unittest框架+真机andorid 8.0

注:关于移动端的,暂时就只做一个项目,因为移动端的扩展性没这么多,后续应该会以web端项目来实操

二、关于Appium实现中文输入  

  1、要实现中文输入的话,在初始化里加入下面的代码就行了

# 实现中文输入,使用unicodeKeyboard的编码方式来发送字符串
\'unicodeKeyboard\': True,  
# 将键盘给隐藏起来
\'resetKeyboard\': True  

  2、如果Appium设置中文输入报错: Attempt to re-install io.appium.android.ime without first

    原因:漏卸载应用

    解决方法:查出所有的第三方安装包:一共3个,然后用adb卸载掉(adb shell pm list package -3或adb shell pm list package -3 | findstr appium),如图1

(图1:卸载应用)

  3、Appium 在 Android7.0 以上版本找不到元素的问题,解决方法:安装Uiautomator2(使用npm安装: npm install appium-uiautomator2-driver)

三、关于隐式等待和显示等待  

  1、WebDriverWait():显示等待,是针对于某个特定的元素设置的等待时间,在设置时间内,默认每隔一段时间检测一次当前页面某个元素是否存在,如果在规定的时间内找到了元素,则直接执行,即找到元素就执行相关操作,如果超过设置时间检测不到则抛出异常。默认检测频率为0.5s,默认抛出异常为:NoSuchElementException

  WebDriverWait()一般由unitl()或until_not()方法配合使用,until()调用该方法提供的驱动作为一个参数,直到返回值为True,unitl_not()调用该方法提供的驱动作为一个参数,直到返回值为False 。

  用法示例:

 1 from selenium.webdriver.support.wait import WebDriverWait
 2 from selenium.webdriver.support import expected_conditions as EC
 3 from selenium.webdriver.common.by import By
 4 
 5 loc = ("xpath", "//android.view.View[contains(@text,\'确定\')]")
 6    try:
 7        # 显示等待用法一:EC.presence_of_element_located()直到元素出现
 8        e = WebDriverWait(self.driver, 3, 0.5).until(EC.presence_of_element_located(loc))
 9        # 显示等待用法二:用By
10        #WebDriverWait(self.driver, 3, 0.5).until(EC.presence_of_element_located(By.XPATH, "//android.view.View[contains(@text,\'确定\')]"))
11        # 显示等待用法三:用lambda函数
12        #e = WebDriverWait(self.driver, 3 ,0.5).until(lambda x: self.driver.find_element_by_xpath("//android.view.View[contains(@text,\'确定\')]"))
13        e.click()
14        print("成功选择非免付啦")
15     except:
16        pass

  2、implicitly_wait():隐式等待,是设置的全局等待。设置等待时间,是对页面中的所有元素设置加载时间,如果超出了设置时间的则抛出异常。隐式等待可以理解成在规定的时间范围内,浏览器在不停的刷新页面,直到找到相关元素或者时间结束。

  3、隐式等待和显示等待都存在时,超时时间取二者中较大的。

四、关于unittest框架     

  1、简要说明

  unittest是Python中自带的单元测试框架,它里面封装好了一些校验返回的结果方法和一些用例执行前的初始化操作。unittest单元测试框架不仅可以适用于单元测试,还可以适用web自动化测试用例的开发与执行,该测试框架可组织执行测试用例,并且提供了丰富的断言方法,判断测试用例是否通过,最终生成测试结果。

最基础的四个概念:TestCase,TestSuite,TestRunner,TestFixture

  2、运行流程

  先编写好TestCase,然后由TestLoader加载TestCase到TestSuite,其次由TextTestRunner来运行TestSuite,运行的结果保存在TextTestResult中,我们通过命令行或者unittest.main()执行时,main会调用TextTestRunner中的run来执行,或者我们可以直接通过TextTestRunner来执行用例

  3、unittest模块的各个属性说明

  1)unittest.TestCase:TestCase类,所有测试用例类继承的基本类。class BaiduTest(unittest.TestCase):

  2)unittest.main():使用它可以方便的将一个单元测试模块变为可直接运行的测试脚本,main()方法使用TestLoader类来搜索所有包含在该模块中以“test”命名开头的测试方法,并自动执行他们。执行方法的默认顺序是:根据ASCII码的顺序加载测试用例,数字与字母的顺序为:0-9,A-Z,a-z。所以以A开头的测试用例方法会优先执行,以a开头会后执行。

  3)unittest.TestSuite():unittest框架的TestSuite()类是用来创建测试套件的。

  4)unittest.TextTextRunner():unittest框架的TextTextRunner()类,通过该类下面的run()方法来运行suite所组装的测试用例,入参为suite测试套件。

  5)unittest.defaultTestLoader(): defaultTestLoader()类,通过该类下面的discover()方法可自动更具测试目录start_dir匹配查找测试用例文件(test*.py),并将查找到的测试用例组装到测试套件,因此可以直接通过run()方法执行discover。

  6)unittest.skip():装饰器,当运行用例时,有些用例可能不想执行等,可用装饰器暂时屏蔽该条测试用例。一种常见的用法就是比如说想调试某一个测试用例,想先屏蔽其他用例就可以用装饰器屏蔽。

    @unittest.skip(reason): skip(reason)装饰器:无条件跳过装饰的测试,并说明跳过测试的原因。

    @unittest.skipIf(reason):skipIf(condition,reason)装饰器:条件为真时,跳过装饰的测试,并说明跳过测试的原因。

    @unittest.skipUnless(reason):skipUnless(condition,reason)装饰器:条件为假时,跳过装饰的测试,并说明跳过测试的原因。

    @unittest.expectedFailure():expectedFailure()测试标记为失败。

  7)setUp():setUp()方法用于每个测试用例执行前的初始化工作。如测试用例中需要访问数据库,可以在setUp中建立数据库连接并进行初始化。如测试用例需要登录web,可以先实例化浏览器。

  8)tearDown():tearDown()方法用于每个测试用例执行之后的善后工作。如关闭数据库连接、关闭浏览器。

  9)setUpClass():setUpClass()方法用于所有测试用例前的设置工作。

  10)tearDownClass():tearDownClass()方法用于所有测试用例执行后的清理工作。

  11)addTest():addTest()方法是将测试用例添加到测试套件中。

  12)run():run()方法是运行测试套件的测试用例,入参为suite测试套件。

  13)assert*():在执行测试用例的过程中,最终用例是否执行通过,是通过判断测试得到的实际结果和预期结果是否相等决定的。

  4、添加用例运行方式

  1)用法一:用addTests方法单个添加用例

import unittest

if __name__ == \'__main__\':
    suite = unittest.TestSuite()
    # 单个添加测试用例
    suite.addTest(TestMathFunc("test_multi"))
    suite.addTest(TestMathFunc("test_divide"))
    runner = unittest.TextTestRunner(verbosity=2)
    runner.run(suite)

  2)用法二:将用例添加到一个用例集中,再通过suite统一运行

import unittest

if __name__ == \'__main__\':
    suite = unittest.TestSuite()
    # 将测试用例添加到一个用例集中
    tests = [TestMathFunc("test_add"), TestMathFunc("test_minus")]
    suite.addTests(tests)  # 将测试用例列表添加到测试组中
    runner = unittest.TextTestRunner(verbosity=2)
    runner.run(suite)

  3)用法三:这是最实用的方法,引用discover()方法,匹配指定文件夹下以test开头的测试用例

import unittest

if __name__ == \'__main__\':
    path = \'./testcase\'
    all_cases = unittest.defaultTestLoader.discover(path, \'test*.py\')
    # 找到某个目录下所有的以test开头的Python文件里面的测试用例
    runner = unittest.TextTestRunner(verbosity=2)
    runner.run(all_cases)

五、BeautifulReport测试报告  

  1、如何使用?

  1)pip安装:pip install BeautifulReport

  2)使用示例:

import unittest
from BeautifulReport import BeautifulReport

if __name__ == \'__main__\':
    #"."表示当前目录,"*tests.py"匹配当前目录下所有tests.py结尾的用例
    test_suite = unittest.defaultTestLoader.discover(\'./tests\', pattern=\'test*.py\') 
   result = BeautifulReport(test_suite) 
   result.report(filename=\'测试报告\', description=\'测试deafult报告\', report_dir=\'report\', theme=\'theme_default\')

  2、简要说明:

  1)BeautifulReport.report

    report (
        filename -> 测试报告名称, 如果不指定默认文件名为report.html
        description -> 测试报告用例名称展示
        report_dir=\'.\' -> 报告文件写入路径
        theme=\'theme_default\' -> 报告主题样式 theme_default theme_cyan theme_candy theme_memories
        )

  2)BeautifulReport.add_test_img:如果使用报告过程中需要把测试报告的截图放在报告中, 可以使用add_test_img方法

  3)add_test_img ( *pargs ):可以在测试用例上挂载一个装饰器, 实例内容如下:

    默认存放的图片路径是img, 需要在当前测试项目的启动路径下, 创建一个img文件夹

    传递给装饰器的图片,在运行测试前可以不存在, 运行测试之后生成即可.

    当文件在报告中展示后, 想要看到原图, 可以点击报告中的缩略图查看完整的截图

六、完整项目代码示例    

  1、项目目录结构,如图2

(图2:项目目录结构示例)

  2、测试用例text_wxwork.py

注:可能会涉及到敏感信息,所以有些case的注释和text会用XXX代替  

 1 from appium import webdriver  
 2 import unittest 
 3 import time
 4
import datetime 5 from selenium.webdriver.support.ui import WebDriverWait 6 from selenium.webdriver.support import expected_conditions as EC 7 from BeautifulReport import BeautifulReport 8 import os 9 from selenium.webdriver.common.by import By 10 11 class wxworkTest(unittest.TestCase): 12 \'\'\'企业微信-XXX移动端\'\'\' 13 @classmethod 14 def setUpClass(cls): 15 \'\'\' 所有的测试方法运行前运行,使用@classmethod装饰器进行修饰,整个测试过程中只执行一次 \'\'\' 16 wxwork = { 17 \'platformName\': \'Android\', # android的apk 18 \'deviceName\': \'WTKDU16905017501\', # 手机设备名称,通过adb devices查看 19 \'platformVersion\': \'8.0\', # android系统的版本号 20 \'appPackage\': \'com.tencent.wework\', # apk包名 21 \'appActivity\': \'.launch.LaunchSplashActivity\', # apk的启动Activity 22 #\'unicodeKeyboard\': True, # 实现中文输入,使用unicodeKeyboard的编码方式来发送字符串 23 #\'resetKeyboard\': True # 将键盘给隐藏起来 24 } 25 cls.driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", wxwork) # 连接appium 26 cls.driver.implicitly_wait(10) # 隐式等待,最长等待10s 27 # 切换到工作台 28 cls.driver.find_element_by_xpath("//*[@text=\'工作台\']").click() 29 # 调用上滑方法,找到应用并进入 30 cls.up(4, 500) 31 time.sleep(1) 32 cls.driver.find_element_by_xpath("//*[@text=\'XXX测试\']").click() 33 # 在移动端应用进入主页 34 cls.driver.find_element_by_xpath("//*[@text=\'XXX采购\']").click() 35 print("已进入XXX移动端/我的工作台") 36 37 @classmethod 38 def tearDownClass(cls): 39 \'\'\' 所有的测试方法运行后运行,使用@classmethod装饰器进行修饰,整个测试过程中只执行一次 \'\'\' 40 cls.driver.quit() # 退出应用 41 print("已退出企业微信") 42 43 @classmethod 44 def getsize(self): 45 \'\'\'定义方法:自动获取手机屏幕\'\'\' 46 x = self.driver.get_window_size()[\'width\'] 47 y = self.driver.get_window_size()[\'height\'] 48 return (x, y) 49 50 @classmethod 51 def up(self,n,t): 52 \'\'\'定义方法:上滑手势操作\'\'\' 53 screensize = self.getsize() 54 x1 = int(screensize[0] * 0.5) 55 y1 = int(screensize[1] * 0.7) 56 y2 = int(screensize[1] * 0.3) 57 for i in range(0, n): 58 self.driver.swipe(x1, y1, x1, y2, t) 59 print("", i + 1, "次上滑成功啦") 60 61 def save_img1(self): 62 """直接指定路径,并用当前时间生成截图""" 63 # 获取当前时间,用于命名截图 64 pic_time = datetime.datetime.now().strftime("%Y-%m-%d %H-%M-%S") 65 # 截图保存写入的路径中 66 self.driver.get_screenshot_as_file("E:\\\\08PyCharmProject\\\\Case1\\\\img\\\\" + pic_time + ".png") 67 68 def save_img(self, img_name): 69 \'\'\'用于放在测试报告的截图,os.path.abspath:返回当前目录的绝对路径,传入一个img_name,并存储到指定路径下\'\'\' 70 self.driver.get_screenshot_as_file(\'{}\\{}.png\'.format(os.path.abspath(\'E:\\\\08PyCharmProject\\\\Case1\\\\img\'), img_name)) 71 72 def editClear(self, text): 73 \'\'\'定义清除文本框方法\'\'\' 74 # 123代表光标移动到末尾 75 self.driver.keyevent(123) 76 for i in range(0, len(text)): 77 self.driver.keyevent(67) # 67退格键 78 # 清空后回车 79 self.driver.keyevent(66) 80 81 def main_purchurse(self): 82 \'\'\'定义重新进入主页面的方法\'\'\' 83 \'\'\'用text不管用,用xpath的话,每个用例最终停留的界面不同,所以也不行,最后忍痛决定用tap\'\'\' 84 # 坐标点击我的工作台 85 self.driver.tap([(720, 1644), (1080, 1792)], 500) 86 # 坐标点击XXX 87 self.driver.tap([(0, 1644),(360, 1792)],500) 88 print("已在XXX主页面") 89 90 def detail_purchurse(self): 91 \'\'\'定义重新进入XXX明细页面的方法\'\'\' 92 # 坐标点击我的工作台 93 self.driver.tap([(720, 1644), (1080, 1792)], 500) 94 # 坐标点击XXX 95 self.driver.tap([(0, 1644), (360, 1792)], 500) 96 # 通过定位电话图标,点击进入XXX明细页面 97 self.driver.find_element_by_xpath("//*[@text=\'ABs3ach\']").click() 98 print("已在XXX明细页面") 99 100 def test_case1(self): 101 \'\'\'case1:我的工作台:各种类型的订单统计跳转功能测试\'\'\' 103 self.driver.find_element_by_xpath("//*[@text=\'未发货\']").click() 104 print("case1执行成功:完成了工作台的所有跳转测试") 124 125 @BeautifulReport.add_test_img(\'test_case2\') # 在测试用例中挂载一个装饰器,报错自动截图 126 def test_case2(self): 127 \'\'\'case2:我的工作台/XXX/XXX,来回切换(报错自动截图贴报告,不报错时用时间命名的截图,不贴报告)\'\'\' 128 self.main_purchurse() # 调用方法:我的工作台切换到XXX 129 # XXX切换到XXXX:tap定位 130 self.driver.tap([(360, 1644), (720, 1792)], 500) 131 print("成功切换到XXXX模块") 132 self.save_img1() # 截图断言弹窗提示语,没报错则调用另一个方法截图,没有入参 另一个方法不会附在测试报告上 133 print("case2执行成功:完成底部大模块的切换测试")150 151 @BeautifulReport.add_test_img(\'test_case4\') #报错自动截图 152 def test_case4(self): 153 \'\'\'case4:XXX主表:待下单状态的功能校验(报错自动截图贴报告,不报错也截图,不贴报告)\'\'\' 154 self.main_purchurse() # 调用方法:我的工作台切换到XXX 155 # 搜索功能测试:回车键搜索 156 self.driver.find_element_by_xpath("//android.widget.EditText[@text=\'供应商/采购员\']").send_keys("M0001") 157 self.driver.keyevent(66) # 回车键搜索 158 print("搜索成功") 159 # 获取文本框的文本,用于传参给editClear函数 160 get_text = self.driver.find_element_by_xpath("//android.widget.EditText[@text=\'供应商/采购员\']").get_attribute(\'text\') 161 # 再次点击文本框,用于清空操作 162 self.driver.find_element_by_xpath("//android.widget.EditText[@text=\'供应商/采购员\']").click() 163 self.editClear(get_text) # 调用方法:清空文本框的值 164 # 获取清空后文本框的文本,用于判断是否删除成功 165 get_text1 = self.driver.find_element_by_xpath("//android.widget.EditText[@text=\'供应商/采购员\']").get_attribute("text") 166 print((get_text1)) 167 if get_text1 == "供应商/采购员": 168 print("文本框删除成功") 169 else: 170 print("文本框删除失败") 171 # 点击分享 172 self.driver.find_element_by_xpath("//*[@text=\'分享\']").click() 173 print("您想要分享给微信好友,还是企业微信好友呢?") 174 self.save_img(\'test_case4\') # 不报错也自动截图 175 self.driver.find_element_by_xpath("//*[@text=\'取消\']").click() 176 print("取消分享") 177 print("case4执行成功:完成XXX-待下单状态的功能测试") 178 179 def test_case5(self): 180 \'\'\'case5:XXX明细页的状态切换测试\'\'\' 181 self.detail_purchurse() # 调用方法:进入XXX明细页 182 # 点击采购中tab列表,因为text有时不起作用,保险起见这里用了兄弟元素定位 183 self.driver.find_element_by_xpath("//android.widget.Button[@text=\'一键操作\']/../android.view.View[4]//*[contains(@text,\'采购中\')]").click() 184 print("已进入XXX明细页的采购中列表") 185 \'\'\'这里删除了一些不重要的\'\'\'193
       print("case5执行成功:完成版料明细页的状态切换") 194 195 def test_case6(self): 196 \'\'\'case6:XXX明细页:待下单功能校验1-搜索区\'\'\' 197 self.detail_purchurse() # 调用方法:进入XXX明细页 198 # 搜索框输入sku,回车搜索 199 self.driver.find_element_by_xpath("//*[@class=\'android.widget.EditText\']").send_keys("F00000583") 200 self.driver.keyevent(66) 201 print("sku搜索成功") 202 # 获取文本框的文本,用于传参给editClear函数 203 get_text = self.driver.find_element_by_xpath("//*[@class=\'android.widget.EditText\']").get_attribute(\'text\') 204 print(get_text) 205 # 再次点击文本框,用于清空操作 206 self.driver.find_element_by_xpath("//*[@class=\'android.widget.EditText\']").click() 207 self.editClear(get_text) # 调用方法:清空文本框的值 208 # 获取清空后文本框的文本,用于判断是否删除成功 209 get_text1 = self.driver.find_element_by_xpath("//*[@class=\'android.widget.EditText\']").get_attribute("text") 211 print((get_text1)) 212 if get_text1 == "供应商/采购单号/物料名称/sku/设计款号/采购员": 213 print("文本框删除成功") 214 else: 215 print("文本框删除失败") 216 # 目标交期排序功能测试 217 self.driver.find_element_by_xpath("//*[@text=\'目标交期\']").click() 218 print("目标交期筛选成功") 219 # 筛选功能测试(兄弟元素定位) 220 self.driver.find_element_by_xpath("//android.widget.Button[@text=\'一键操作\']/../android.widget.Button").click() 221 self.driver.find_element_by_xpath("//android.widget.Button[@text=\'超期\']").click()
225 self.driver.find_element_by_xpath("//android.widget.Button[@text=\'重置\']").click() 226 self.driver.find_element_by_xpath("//android.widget.Button[@text=\'超期\']").click() 227 self.driver.find_element_by_xpath("//android.widget.Button[@text=\'确定\']").click() 228 print("成功输入超期筛选条件") 229 print("搜索成功") 230 # 一键操作功能测试:确认采购 231 self.driver.find_element_by_xpath("//android.widget.Button[@text=\'一键操作\']").click() 232 #<

以上是关于Appium+Python-项目实践一的主要内容,如果未能解决你的问题,请参考以下文章

Python + Robotframework + Appium 之APP自动化测试实践

python+appium从Mysql数据库中获取验证码

python+appium从Mysql数据库中获取验证码

Appium+python自动化(三十九)-Appium自动化测试框架综合实践 - 代码实现(超详解)

Python + Flask 项目开发实践系列《二》

python+appium自动化测试获取短信+图片验证码