18-1 appium 企业微信项目实战1
APP 测试的时代背景
- 按月发布->按周发布->按小时发布
- 多端发布: Android、iOS、微信小程序、h5
- 多环境发布: 联调环境、测试环境、预发布环境、线上环境
- 多机型发布: 众多设备型号、众多系统版本
- 多版本共存: 用户群体中存在多个不同的版本
- 历史回归测试任务: 成百上千条业务用例如何回归
总结:加班 + 背锅
APPIUM 框架结构
APPIUM 引擎列表
Platform | Driver | Platform Versions | Appium Version | Driver Version |
---|---|---|---|---|
iOS | XCUITest | 9.3+ | 1.6.0+ | All |
UIAutomation | 8.0 to 9.3 | All | All | |
Android | Espresso | ?+ | 1.9.0+ | All |
UiAutomator2 | ?+ | 1.6.0+ | All | |
UiAutomator | 4.3+ | All | All | |
Mac | Mac | ?+ | 1.6.4+ | All |
Windows | Windows | 10+ | 1.6.0+ | All |
APPIUM 环境安装
- Android 环境配置
https://ceshiren.com/t/topic/2270
- iOS 环境配置
https://ceshiren.com/t/topic/5530
- 几点建议:
1、Appium Desktop版本不要太老1.15以上
2、Java 1.8
3、SDK build-tools/ 下对应的版本,需要使用<=29的版本
🔗木木官网-开发者必备说明书
木木官网-开发者必备说明书:
https://mumu.163.com/help/func/20190129/30131_797867.html
🔗ios环境搭建
ios环境搭建:ios环境搭建
🔗课程贴
课程贴:课程贴
🔗capability settings
Update settings:settings[settingsKey]
页面为动态刷新时,可设置capability特殊的settings参数,进行动态idel等待时间设置
- 第一种方式:
- 第二种方式:
获取 APP 的信息
app 入口,两种方式获取:
1、通过 logcat 日志获取
mac/Linux: adb logcat ActivityManager:I | grep "cmp"
windows: adb logcat ActivityManager:I | findstr "cmp"后启动目标应用
2、通过 aapt 获取
mac/Linux: aapt dump badging wework.apk | grep launchable-activity
Windows: aapt dump badging wework.apk | findstr launchable-activity
- 启动应用命令
adb shell am start -W -n <package-name>/<activity-name> -S
课堂练习
- 企业微信打卡案例
前提条件
已登录状态( noReset=True)
打卡用例:
1、打开【企业微信】应用
2、进入【工作台】
3、点击【打卡】
4、选择【外出打卡】tab
5、点击【第N次打卡】
6、验证【外出打卡成功】
7、退出【企业微信】应用
参考代码
DesireCapability 配置
caps["noReset"] = "true"
caps['settings[waitForIdleTimeout]'] = 0 # 等待页面空闲的时间
caps['skipServerInstallation'] = ‘true' # 跳过 uiautomator2 server的安装
caps['skipDeviceInitialization'] = ‘true' # 跳过设备初始化
caps['dontStopAppOnReset'] = ‘true' # 启动之前不停止app
滚动查找元素:
self.driver.find_element(MobileBy.ANDROID_UIAUTOMATOR,'new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(new UiSelector().text("添加成员").instance(0));')
课后作业
- 使用 Appium 实现自动化添加联系人
18-2 appium 企业微信项目实战2
课程贴:https://ceshiren.com/t/topic/12237
一、常用的元素定位器
1)APP 元素定位
- 测试步骤三要素
- 定位、交互、断言
- 断言:验证最后一步操作后结果的正确性
- 定位、交互、断言
- 定位
- id 定位(优先级最高)
- XPath 定位(速度慢,定位灵活)
- Accessibility ID 定位(content-desc)1
- Uiautomator 定位(仅 Android 速度快,语法复杂)
- predicate 定位(仅 iOS )
2)XPATH 定位
xpath w3c
https://www.w3.org/TR/xpath-functions/
xpath : xml path的缩写,可以定位xml 文档中的一些标签
-
xpath表达式常用用法:
1、逻辑运算符 (not、and 、or等) # 非、与、或 2、表达式 (contains、ends_with、starts_with等)
- 绝对定位: 不推荐
- 相对定位:
//* # 所有元素
//*[contains(@resource-id, ‘login’)] # (❗️重点)包含,前面值包含后面值
//*[@text=‘登录’] # (❗️重点)text属性 = ‘登录’
//*[contains(@resource-id, ‘login’) and contains(@text, ‘登录’)] # (❗️重点)and 多层过滤,同时满足条件
//*[contains(@text, ‘登录’) or contains(@class, ‘EditText’)] # (了解)获取元素列表find_elements
//*[ends-with(@text,'号') ] | //*[starts-with(@text,'姓名') ] # 两个定位的集合列表(了解) 类似于或,并集关系
//*[@clickable=“true"]//android.widget.TextView[string-length(@text)>0 and string-length(@text)<20] # (了解) 获取text属性长度>0,且text属性长度<20
//*[contains(@text, ‘看点')]/ancestor::*//*[contains(@class, ‘EditText’)] # (轴)(了解) 利用关键字ancestor(轴),先拿到元素所有的祖先节点元素“[contains(@text, ‘看点‘)”,再去过滤;效率较低,用的比较少
3)原生定位
- Android 原生定位-Uiautomator
- 写法:’new UiSelector().text(“text")’
-
iOS 原生定位-PredicateString
例如: name == '测试'
4)TOAST 定位
appium使用uiautomator底层的机制来分析抓取toast,并且把toast放到控件树里面,但本身并不属于控件。
automationName:uiautomator2
使用xpath查找
//*[@class='android.widget.Toast']
//*[contains(@text, "xxxxx")]
5)企业微信实战 2
- 企业微信 添加联系人 练习
- 企业微信 添加多个联系人 练习
1、获取包+页面名字
抓取日志
获取当前页面名字(可获得主页名字),但不建议,建议以用户角度,从启动页进入
2、验证是否正确(shell命令启动程序)
3、代码
添加成员用例分析
工具
代码
faker造数据工具:
https://github.com/joke2k/faker
"""
__author__ = 'hogwarts_xixi'
__time__ = '2021/5/11 8:53 下午'
"""
# This sample code uses the Appium python client
# pip install Appium-Python-Client
# Then you can paste this into a file and simply run with Python
# pip install appium-python-client
from time import sleep
from appium import webdriver
from appium.webdriver.common.mobileby import MobileBy
from faker import Faker
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.support.wait import WebDriverWait
class TestWX:
def setup_class(self):
self.fake = Faker('zh_CN')
def setup(self):
# 初始化
caps = {}
caps["platformName"] = "Android"
# caps['skipDeviceInitialization'] = True
# 包名+启动页名
# mac/linux:adb logcat ActivityManager:I | grep "cmp"
# windows: adb logcat ActivityManager:I | findstr "cmp"
caps["appPackage"] = "com.tencent.wework"
caps["appActivity"] = ".launch.LaunchSplashActivity"
caps["deviceName"] = "hogwarts"
caps["noReset"] = "True"
caps["ensureWebviewsHavePages"] = True
# 动态的设置caps 参数
caps['settings[waitForIdleTimeout]'] = 0
# 与server 建立连接,并且打开 caps 里面配置的页面LaunchSplashActivity
self.driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", caps)
# 隐式等待,在调用所有的find_element /find_elements方法的时候被激活
self.driver.implicitly_wait(5)
def teardown(self):
# 资源消毁
self.driver.quit()
def test_demo(self):
# 测试用例
el1 = self.driver.find_element_by_xpath(
"/hierarchy/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.RelativeLayout/android.widget.LinearLayout/android.view.ViewGroup/android.widget.RelativeLayout[3]/android.widget.RelativeLayout/android.widget.TextView")
el1.click()
el2 = self.driver.find_element_by_xpath(
"/hierarchy/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.RelativeLayout/android.widget.FrameLayout[2]/android.widget.RelativeLayout/android.view.ViewGroup/androidx.recyclerview.widget.RecyclerView/android.widget.RelativeLayout[7]/android.widget.LinearLayout/android.widget.TextView")
el2.click()
def test_daka(self):
# 打卡功能
# 前提条件
# 已登录状态( noReset = True)
# 打卡用例:
# 1、打开【企业微信】应用
# 2、进入【工作台】
self.driver.find_element(MobileBy.XPATH, "//*[@text='工作台']").click()
# 3、点击【打卡】
# 滚动查找 [打卡]
self.driver.find_element(MobileBy.ANDROID_UIAUTOMATOR,
'new UiScrollable(new UiSelector().'
'scrollable(true).instance(0)).'
'scrollIntoView(new UiSelector().text("打卡")'
'.instance(0));').click()
# self.driver.update_settings({'waitForIdleTimeout':0})
# 4、选择【外出打卡】tab
self.driver.find_element(MobileBy.XPATH, "//*[@text='外出打卡']").click()
# 5、点击【第N次打卡】
self.driver.find_element(MobileBy.XPATH, "//*[contains(@text, '次外出')]").click()
# 6、验证【外出打卡成功】
self.driver.find_element(MobileBy.XPATH, "//*[@text='外出打卡成功']")
# 7、退出【企业微信】应用
def swipe_find(self, text):
num = 3
self.driver.implicitly_wait(1)
for i in range(num):
try:
ele= self.driver.find_element(MobileBy.XPATH, f"//*[@text='{text}']")
self.driver.implicitly_wait(5)
return ele
except:
print("未找到")
size = self.driver.get_window_size()
# 'width', 'height'
width = size.get('width')
height = size.get("height")
startx = width / 2
starty = height * 0.8
endx = startx
endy = height * 0.3
duration = 2000 # 单位是ms
self.driver.swipe(startx, starty, endx, endy, duration)
if i == 2:
self.driver.implicitly_wait(5)
raise NoSuchElementException()
def test_addcontact(self):
name = self.fake.name()
phonenum = self.fake.phone_number()
self.driver.find_element(MobileBy.XPATH, "//*[@text='通讯录']").click()
# self.driver.find_element(MobileBy.XPATH, "//*[@text='添加成员']").click()
self.swipe_find("添加成员").click()
self.driver.find_element(MobileBy.XPATH, "//*[@text='手动输入添加']").click()
# find_elements 返回元素列表[element1,element2,element3.....]
self.driver.find_element(MobileBy.XPATH, "//*[contains(@text, '姓名')]/../*[@text='必填']").send_keys(name)
self.driver.find_element(MobileBy.XPATH, "//*[contains(@text, '手机')]/..//*[@text='必填']").send_keys(phonenum)
# self.driver.find_elements(MobileBy.XPATH, '//*[@text="必填"]')[1]
self.driver.find_element(MobileBy.XPATH, "//*[@text='保存']").click()
self.driver.find_element(MobileBy.XPATH, "//*[@text='添加成功']")
# WebDriverWait(self.driver,10).until(lambda x:x.find_element(MobileBy.XPATH, "//*[@text='成功']"))
# result= self.driver.find_element(MobileBy.CLASS_NAME, "android.widget.Toast").get_attribute('text')
# print(result)
# assert result == '添加成功'
# sleep(2)
# 获取页面的源代码
# print(self.driver.page_source)
w3c find_element:
<-- 请求的返回
课堂练习
- 删除联系人
二、企业微信实战(PO 封装)
1)传统测试用例的问题
- 无法适应 UI 变化,UI 变化会导致大量的 case 需要修改
- 大量的样板代码 driver find click
- 无法清晰表达业务用例场景
2)PAGE OBJECT 模式六大原则
- Selenium 官方提出六大原则
- 公共的方法代表页面提供的服务
- 不要暴露细节
- 不要把断言和操作细节混用
- 方法可以 return 到新打开的页面
- 不要把整页的内容都放到 PO 中
- 相同的行为会产生不同的结果,可以封装不同结果
3)PO 模式主要组成元素
- Page 对象:完成对页面的封装
- Driver 对象:完成对 web、android、ios、接口的驱动
- 测试用例:调用 Page 对象实现业务并断言
- 数据封装:配置文件和数据驱动
- Utils:其他功能封装,改进原生框架不足
4)PO 模式 企业微信实战
- 演练 App:企业微信 app
- 演练语言:Python
三、框架改进方案
1)测试框架改进
- BasePage 的封装
- 初始化方法
- find 方法
- find_and_click 方法
- handle_exception 方法
坑:Appium换命令行启动更稳定,可解决👇报错
2)加入日志
- 使用标准 log 取代 print
- logging.baseConfig(level=logging.DEBUG)
- 在具体的 action 中加入 log 方便追踪
3)课后作业
- 实现添加多个联系人功能的 PO 封装
- 实现删除联系人功能的 PO 封装
content-desc 与text值一致,一般国外app具有该属性(残障辅助) ↩︎