Appium+python实践
环境准备:https://www.cnblogs.com/yuntimer/p/17019315.html
获取 app 的信息
- app 入口,两种方式获取:
- 1、通过 logcat 日志获取
- Mac/Linux:
adb logcat ActivityManager:I | grep “cmp"
- Windows:
adb logcat ActivityManager:I | findstr "cmp"
- Mac/Linux:
- 2、通过 aapt 获取
- Mac/Linux:
aapt dump badging wework.apk | grep launchable-activity
- Windows:
aapt dump badging wework.apk | findstr launchable-activity
- Mac/Linux:
- 1、通过 logcat 日志获取
- 启动应用命令
adb shell am start -W -n <package-name>/<activity-name> -S
配置待测应用
platformName
:平台,Android/iOSdeviceName
:设备名appPackage
:应用的包名appActivity
:应用的页面名 ActivitynoReset
: 防止清空缓存信息
Appium inspector 页面功能键
SelectElements
:选中元素,查看层级和属性Swipe By Coordinates
:通过坐标点滑动Tap By Coordinates
:通过坐标点点击Back
:返回Refresh Source & Screenshot
:刷新页面StartRecording
:开始录制脚本Search for element
:搜索元素Copy XML Source to Clipboard
:复制 xml 结构Quit Session & Close Inspector
:退出当前 Session
capability 配置参数解析
官方解释:http://appium.io/docs/en/writing-running-appium/caps/
-
功能:配置 Appium 会话,告诉 Appium 服务器需要自动化的平台的应用程序
-
形式:键值对的集合,键对应设置的名称,值对应设置的值
-
主要分为三部分
1、公共部分
键 |
描述 | 值 |
platformName |
使用的手机操作系统 | iOS,Android,或者 Firefox0S |
platformVersion |
手机操作系统的版本 | 例如 7.1 , 4.4 |
deviceName |
使用的手机或模拟器类型 | iPhone Simulator , iPad Simulator , iPhone Retina 4-inch , Android Emulator , Galaxy S4 , 等等…. 在 iOS 上,使用 Instruments的 instruments -s devices 命令可返回一个有效的设备的列表。在 Andorid 上虽然这个参数目前已被忽略,但仍然需要添加上该参数 |
automationName |
使用哪个自动化引擎 | android 默认使用uiautomator2 ,ios 默认使用XCUTest |
noReset |
在当前 session 下不会重置应用的状态。默认值为 false |
true , false |
udid |
连接的真实设备的唯一设备编号 (Unique device identifier) | 例如 1ae203187fc012gg |
- 2、ios 部分
键 |
描述 | 值 |
bundleId |
被测应用的 bundle ID 。用于在真实设备中启动测试,也用于使用其他需要 bundle ID 的关键字启动测试。在使用 bundle ID 在真实设备上执行测试时,你可以不提供 app 关键字,但你必须提供 udid 。 |
例如 io.appium.TestApp |
autoAcceptAlerts |
当 iOS 的个人信息访问警告 (如 位置、联系人、图片) 出现时,自动选择接受( Accept )。默认值 false | true 或者 false |
showIOSLog |
是否在 appium 日志中显示从设备捕获的任何日志。默认 false | true or false |
- 3、android 部分
键 |
描述 | 值 |
appActivity |
Activity 的名字是指从你的包中所要启动的 Android acticity。他通常需要再前面添加. (例如 使用 .MainActivity 代替 MainActivity ) |
MainActivity , .Settings |
appPackage |
运行的 Android 应用的包名 | com.example.android.myApp , com.android.settings |
appWaitActivity |
用于等待启动的 Android Activity 名称 | SplashActivity |
unicodeKeyboard |
启用 Unicode 输入,默认为 false | true or false |
resetKeyboard |
true or false |
|
dontStopAppOnReset |
首次启动的时候,不停止 app | true or false |
skipDeviceInitialization |
跳过安装,权限设置等操作 | true or false |
用例结构分析
- 添加 capability 信息
- 初始化
webdriver
,添加setup
和teardown
- 添加隐式等待和
noReset
属性增强用例稳定性 - 添加断言
1 import time 2 from appium import webdriver 3 from appium.webdriver.common.mobileby import MobileBy 4 5 6 class TestDemo: 7 def setup(self): 8 desire_cap = {} 9 # 平台 10 desire_cap['platform'] = 'Android' 11 # 设备名 12 desire_cap['deviceName'] = 'emulator' 13 # app 包名 14 desire_cap['appPackage'] = 'io.appium.android.apis' 15 # app 页面名 16 desire_cap['appActivity'] = '.ApiDemos' 17 self.driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desire_cap) 18 self.driver.implicitly_wait(10) 19 20 def teardown(self): 21 time.sleep(3) 22 # 退出应用 23 self.driver.quit() 24 25 def test_api_demo(self): 26 """ 27 1、打开 API demo apk 28 2、点击 OS 控件 29 3、点击 Morse Code 控件 30 4、在搜索框中输入 阿里巴巴 31 5、返回到第一页 32 6、断言 33 :return: 34 """ 35 # 点击 OS 控件 36 self.driver.find_element_by_accessibility_id("OS").click() 37 # 点击 Morse Code 控件 38 self.driver.find_element_by_accessibility_id("Morse Code").click() 39 # 输入`阿里巴巴` 40 self.driver.find_element_by_id("io.appium.android.apis:id/text").clear() 41 self.driver.find_element_by_id("io.appium.android.apis:id/text").send_keys("阿里巴巴") 42 # 返回第一页 43 self.driver.back() 44 self.driver.back() 45 self.driver.back() 46 # 选择元素进行断言 47 result = self.driver.find_element(MobileBy.XPATH,"//*[@resource-id='android:id/text1'][1]") 48 print(result.text) 49 # 断言 50 assert result.text == "阿里巴巴"
完整脚本
常见控件定位方法
Selenium提供了八种定位方式:https://www.selenium.dev/documentation/webdriver/elements/locators/
方式 | 描述 |
class name | class 属性对应的值 |
css selector(重点) | css 表达式 |
id(重点) | id 属性对应的值 |
name(重点) | name 属性对应的值 |
link text | 查找其可见文本与搜索值匹配的锚元素 |
partial link text | 查找其可见文本包含搜索值的锚元素。如果多个元素匹配,则只会选择第一个元素。 |
tag name | 标签名称 |
xpath(重点) | xpath表达式 |
driver.find_element(By.ID, "ID属性对应的值")
driver.find_element(By.NAME, "Name属性对应的值")
driver.find_element(By.CSS_SELECTOR, "css表达式")
driver.find_element(By.XPATH, "xpath表达式")
driver.find_element(By.LINK_TEXT,"文本信息")
强制等待与隐式等待
原因:避免页面未渲染完成后操作,导致的报错
1、直接等待:time.sleep(3)
解决方案:在报错的元素操作之前添加等待
原理:强制等待,线程休眠一定时间
2、隐式等待:driver.implicitly_wait(3)
解决方案:针对于寻找元素的这个动作,使用隐式等待添加配置。
原理:设置一个等待时间,轮询查找(默认0.5秒)元素是否出现,如果没出现就抛出异常
3、显式等待
- 元素可以找到,使用点击等操作,出现报错
- 原因:
- 页面元素加载是异步加载过程,通常html会先加载完成,js、css其后
- 元素存在与否是由HTML决定,元素的交互是由css或者js决定
- 隐式等待只关注元素能不能找到,不关注元素能否点击或者进行其他的交互
- 示例:
WebDriverWait(driver实例, 最长等待时间, 轮询时间).until(结束条件)
- 如:WebDriverWait(driver, 10).until( expected_conditions.element_to_be_clickable( (By.CSS_SELECTOR, ‘#success_btn’)))
- 原理:在最长等待时间内,轮询,是否满足结束条件
类型 | 使用方式 |
原理 | 适用场景 |
直接等待 | time.sleep(等待时间)) |
强制线程等待 | 调试代码,临时性添加 |
隐式等待 | driver.implicitly_wait(等待时间) |
在时间范围内,轮询查找元素 | 解决找不到元素问题,无法解决交互问题 |
显式等待 | WebDriverWait(driver实例, 最长等待时间, 轮询时间).until(结束条件) |
设定特定的等待条件,轮询操作 | 解决特定条件下的等待问题,比如点击等交互性行为 |
自动化测试定位策略(待补充)
实践练习:
1、用例编写思路
- pytest 测试框架编写
- 添加隐式等待
- 添加 setup teardown
- 添加断言
2、代码示例
1 from appium import webdriver 2 from appium.webdriver.common.mobileby import MobileBy 3 4 # 打开【雪球】应用首页 5 # 点击搜索框(点击之前,判断搜索框的是否可用,并查看搜索框 name 属性值,并获取搜索框坐标,以及它的宽高) 6 # 向搜索框输入:alibaba 7 # 判断【阿里巴巴】是否可见 8 # 如果可见,打印“搜索成功” 9 # 如果不可见,打印“搜索失败 10 11 class TestXueQiu: 12 def setup(self): 13 desired_caps = {} 14 desired_caps['platformName'] = 'Android' 15 desired_caps['platformVersion'] = '10' 16 desired_caps['deviceName'] = 'emulator-5554' 17 desired_caps['appPackage'] = 'com.xueqiu.android' 18 desired_caps['appActivity'] = '.view.WelcomeActivityAlias' 19 desired_caps['noReset'] = 'true' 20 self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) 21 self.driver.implicitly_wait(10) 22 23 def teardown(self): 24 self.driver.quit() 25 26 def test_search(self): 27 element = self.driver.find_element(MobileBy.ID,"com.xueqiu.android:id/tv_search") 28 search_enabled = element.is_enabled() 29 print(f"搜索框的文本:{element.text},搜索框的坐标:{element.location},搜索框的size:{element.size}") 30 31 if search_enabled == True: 32 element.click() 33 self.driver.find_element(MobileBy.ID, 34 "com.xueqiu.android:id/search_input_text").\ 35 send_keys("alibaba") 36 alibaba_element = self.driver.find_element(MobileBy.XPATH, "//*[@text='阿里巴巴']") 37 # alibaba_element.is_displayed() 38 displayed = alibaba_element.get_attribute("displayed") 39 assert displayed == "true" 40 else: 41 assert False
脚本示例
2023-1-2笔记