目录:
1.appium server 环境安装
安装 Node.js
- // 查看源
- npm config get registry
-
- //更换源
- npm config set registry https://registry.npm.taobao.org
安装成功后的截图:

安装 Appium Server
- // 安装最新版本的appium server
- npm install -g appium
-
-
- // 注意 -g 参数一定要有,不能省,代表全局
- // 安装某个特定的版本
- npm install -g appium@1.19.1
安装环境检测工具
- # 安装检测工具 appium-doctor
- npm install -g appium-doctor
appium-doctor
运行 Appium 服务
- // 帮助文档
- appium --help
-
- // 运行 appium 服务
- appium
2.capability 进阶用法
deviceName
uid
多设备选择的时候,要指定 uid
默认读取设备列表的第一个设备
设备列表获取
adb devices
newCommandTimeout
60 秒autoGrantPermissions
PRINT_PAGE_SOURCE_ON_FIND_FAILURE
false测试策略
noReset
falsetrue
fullReset
默认为 false。true:新会话之前完全卸载被测应用程序
安卓
dontStopAppOnReset
false。true#打开的app退出后重新启动
adb shell am start -S 包名/activity名
#打开的app不需要退出,直接使用当前页面
adb shell am start 包名/activity名
性能相关
3.元素定位工具
uiautomatorviewer 工具安装(不推荐)
tools/bin 目录下的 uiautomatorviewer 程序uiautomatorviewer 工具功能介绍

weditor 安装与运行
pip install weditor 进行安装python -m weditor 即可weditor 功能介绍

appium inspector 安装与运行
appium inspector 功能简介
对选中元素操作
4.高级定位技巧-xpath 定位
xpath 函数
包含-contains()
Xpath 表达式中的一个函数
contains()函数匹配==属性值==中包含的==字符串==
//*[contains(@属性,"属性值")] xpath 轴
父子-当前节点的父节点
- //*[@text="HK"]/..
-
- //*[@text="HK"]/parent::*
父子-当前节点的子节点
//*[@resource-id="com.xueqiu.android:id/stock_layout"]/child::*
爷孙-当前节点的爷爷
- //*[@text="HK"]/../..
-
- //*[@text="HK"]/parent::*/parent::*
爷孙-当前节点的孙子
//*[@resource-id="com.xueqiu.android:id/stock_layout"]/child::*/child::*
祖先-ancestor
返回当前节点的所有祖先
//*[@text="HK"]/ancestor::android.widget.RelativeLayout 显式指定要返回的祖先
//*[@text="HK"]/ancestor::android.widget.RelativeLayout[1] 兄弟姐妹-sibling
following-sibling
选择当前节点之后的所有兄弟节点
节点后有一个兄弟节点
//*[@text="HK"]/following-sibling::* 节点后有多个兄弟节点
//*[@resource-id="com.xueqiu.android:id/stock_layout"]/following-sibling::*[@resource-id="com.xueqiu.android:id/price_layout preceding-sibling
选择当前节点之前的所有兄弟节点
节点前有一个兄弟节点
//*[@text="09988"]/preceding-sibling::* 节点前有多个兄弟节点
//*[@resource-id="com.xueqiu.android:id/add_attention"]/preceding-sibling::*[@resource-id="com.xueqiu.android:id/price_layou XPath 运算符
AND
可以在 XPath 表达式中放置 2 个条件
在 AND 两个条件都应该为真的情况下,才能找到元素
//*[@resource-id="com.xueqiu.android:id/current_price" and @text="107.8"] OR
可以在 XPath 表达式中放置 2 个条件
在 OR 的情况下,两个条件中的任何一个为真,就可找到元素。
OR 定位获取的是并集
//*[@resource-id="com.xueqiu.android:id/tv_stock_add_follow" or @text="加自选"] 5.高级定位技巧-css 定位与原生定位
原生定位
- # ID 定位
- driver.find_element_by_android_uiautomator('new UiSelector().resourceId("
")' ) -
- # 组合定位
- driver.find_element_by_android_uiautomator('new UiSelector().resourceId("com.xueqiu.android:id/tab_name").text("我的")')
css selector 定位介绍
- 代码:
- driver.find_element(AppiumBy.CSS_SELECTOR,"#com\.xueqiu\.android\:id\/tv_search")
-
- 解析前:
- {"using":"css selector","value":"#com\\.xueqiu\\.android\\:id\\/tv_search"}
-
- 解析后:
- {"strategy":"-android uiautomator","selector":"new UiSelector().resourceId(\"com.xueqiu.android:id/tv_search\")",...}
css selector 用法
- # 通过 id
- elementById("someResourceID")`
- -> `elementsByCss("#someResourceID")
- # 通过 class
- elementsByClassName("android.widget.TextView")`
- -> `elementsByCss("android.widget.TextView")
- # 通过 accessibility id
- elementsByAccessibilityId("Some Content Description")`
- -> `elementsByCss('*[description="Some Content Description"]')
- # 通过 xpath
- elementsByXpath("//android.widget.TextView[@description='Accessibility']")`
- -> `elementsByCss("android.widget.TextView[description='Accessibility']")
iOS css selector 定位
总结
6.特殊控件 toast 识别
Toast 是什么
一种消息框类型
永远不会获得焦点,无法被点击
Toast显示的时间有限,Toast会根据用户设置的显示时间后自动消失
是系统级别的控件,属于系统settings
Toast类的思想:就是尽可能不引人注意,同时还向用户显示信息,希望他们看到
Toast 定位
Toast 定位
xpath 可以找到 ``` //*[@class="android.widget.Toast"]
//*[contains(@text,"xxx")] ``` xxx:toast的文本内容
- driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(50));
- driver.findElement(AppiumBy.xpath("//*[@class=\"android.widget.Toast\"]"));
代码示例:
- from appium import webdriver
- from appium.webdriver.common.mobileby import MobileBy
-
-
- class TestToast():
- def setup(self):
- desire = {
- 'platformName': 'android',
- 'platformVersion': '6.0',
- 'deviceName': 'emulator-5554',
- 'appPackage': 'io.appium.android.apis',
- 'appActivity': 'io.appium.android.apis.view.PopupMenu1',
- 'automationName': 'uiautomator2'
- }
- self.driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desire)
- self.driver.implicitly_wait(5)
-
- def teardown(self):
- self.driver.quit()
-
- def test_toast(self):
- self.driver.find_element(MobileBy.ACCESSIBILITY_ID, "Make a Popup!").click()
- self.driver.find_element(MobileBy.XPATH, "//*[@text='Search']").click()
- print(self.driver.find_element(MobileBy.XPATH, "//*[@class='android.widget.Toast']").text)
- # print(self.driver.find_element(MobileBy.XPATH, "//*[contains(@text, 'Clicked popup')]").text)
7.显式等待高级使用
Wait 等待
driver.implicitly_wait(TIMEOUT)WebDriverWait(self.driver,10,0.5).until(expected_conditions.visibility_of_element_located(LOCATOR))显式等待
NoSuchElementExceptionWebDriverWait 和 expected_conditions 两个类显式等待
WebDriverWait 用法
expected_conditions 类
presence_of_element_located 判断元素是否被加到了 DOM 树里,并不代表该元素一定可见
WebDriverWait().until(expected_conditions.presence_of_element_located(元素对象))visibility_of_element_located 判断某个元素是否可见,可见代表元素非隐藏,并且元素的宽和高都不等于 0
WebDriverWait().until(expected_conditions.visibility_of_element_located(元素定位符))使用 lambda 表达式
WebDriverWait(driver,time).until(lambda x:x.find_element_by_id("someId")总结三种等待方式
8.高级控件交互方法
Actions
W3C 事件流

用法
代码
- ...
- # 定义ActionChains 实例
- actions = ActionChains(driver)
- # 第一步:定义输入源
- # ActionChains里有个属性是ActionBuilder类型的, 使用的就是w3c协议
- # 可以定义鼠标指针源,键盘源,滚轮源事件
- actions.w3c_actions = ActionBuilder(driver, mouse=PointerInput(interaction.POINTER_TOUCH, "touch"))
- # 第二步:定义动作
- # 移动到起点-> 按下-> 滑动-> 抬起
- actions.w3c_actions.pointer_action.move_to_location(115, 183)
- actions.w3c_actions.pointer_action.pointer_down()
- actions.w3c_actions.pointer_action.move_to_location(362, 179)
- actions.w3c_actions.pointer_action.release()
- actions.perform()
- ...
练习

代码示例:
- import time
- from appium import webdriver
- from appium.webdriver.common.appiumby import AppiumBy
- from selenium.webdriver import ActionChains
- from selenium.webdriver.common.actions import interaction
- from selenium.webdriver.common.actions.action_builder import ActionBuilder
- from selenium.webdriver.common.actions.pointer_input import PointerInput
-
-
- class TestAction:
- def setup(self):
- caps = {}
- caps["platformName"] = "Android"
- caps["appPackage"] = "cn.kmob.screenfingermovelock"
- caps["appActivity"] = "com.samsung.ui.FlashActivity"
- caps["deviceName"] = "127.0.0.1:7555"
- caps["ensureWebviewsHavePages"] = True
- caps["noReset"] = "true"
- self.driver = webdriver.Remote("http://localhost:4723/wd/hub", caps)
- self.driver.implicitly_wait(15)
-
- def teardown(self):
- self.driver.quit()
-
- def test_action(self):
- el1 = self.driver.find_element(AppiumBy.ID,"cn.kmob.screenfingermovelock:id/patternTxt")
- el1.click()
- # 定义ActionChains 实例
- actions = ActionChains(self.driver)
- # 第一步:定义输入源
- # ActionChains里有个属性是ActionBuilder类型的, 使用的就是w3c协议
- # 可以定义鼠标指针源,键盘源,滚轮源事件
- actions.w3c_actions = ActionBuilder(self.driver, mouse=PointerInput(interaction.POINTER_TOUCH, "touch"))
- # 第二步:定义动作
- # 移动到起点-> 按下-> 滑动-> 抬起
- actions.w3c_actions.pointer_action.move_to_location(118, 176)
- actions.w3c_actions.pointer_action.pointer_down()
- actions.w3c_actions.pointer_action.move_to_location(355, 176)
- actions.w3c_actions.pointer_action.pause(0.5)
- actions.w3c_actions.pointer_action.move_to_location(751, 221)
- actions.w3c_actions.pointer_action.pause(0.5)
- actions.w3c_actions.pointer_action.move_to_location(752, 518)
- actions.w3c_actions.pointer_action.pause(0.5)
- actions.w3c_actions.pointer_action.move_to_location(751, 821)
- actions.w3c_actions.pointer_action.release()
- actions.perform()
- time.sleep(2)
9.设备交互api
常用的设备交互命令(建议去appium官网查看文档)
模拟电话、短信
- driver.makeGsmCall(PHONE_NUMBER, GsmCallActions.CALL);
- driver.makeGsmCall(PHONE_NUMBER, GsmCallActions.ACCEPT);
- driver.makeGsmCall(PHONE_NUMBER, GsmCallActions.CANCEL);
- driver.sendSMS("555-123-4567", “Appium Test”);
网络设置
- def set_network_connection(self, connection_type: int) -> int:
- """Sets the network connection type. Android only.
- Possible values:
- +--------------------+------+------+---------------+
- | Value (Alias) | Data | Wifi | Airplane Mode |
- +====================+======+======+===============+
- | 0 (None) | 0 | 0 | 0 |
- +--------------------+------+------+---------------+
- | 1 (Airplane Mode) | 0 | 0 | 1 |
- +--------------------+------+------+---------------+
- | 2 (Wifi only) | 0 | 1 | 0 |
- +--------------------+------+------+---------------+
- | 4 (Data only) | 1 | 0 | 0 |
- +--------------------+------+------+---------------+
- | 6 (All network on) | 1 | 1 | 0 |
- +--------------------+------+------+---------------+
- self.driver.set_network_connection(1)
- self.driver.set_network_connection(4)
横竖屏切换
driver.rotate(Screenorientation.LANDSCAPE)driver.rotate(Screenorientation.PORTRAIT)获取日志
- self.driver.log_types
- self.driver.get_log("logcat")
其它常用操作
driver.lock()driver.get_screenshot_as_file('./photos/img.png')self.driver.start_recording_screen()self.driver.stop_recording_screen()代码示例:
- import time
- from appium import webdriver
- from appium.webdriver.extensions.android.gsm import GsmCallActions
-
-
- class TestDevice:
- def setup(self):
- caps = {}
- caps["platformName"] = "Android"
- caps['platformVersion'] = '6.0'
- caps["appPackage"] = "cn.kmob.screenfingermovelock"
- caps["appActivity"] = "com.samsung.ui.FlashActivity"
- caps["deviceName"] = "127.0.0.1:7555"
- caps["ensureWebviewsHavePages"] = True
- caps["noReset"] = "true"
- caps['unicodeKeyBoard'] = 'true'
- caps['resetKeyBoard'] = 'true'
- self.driver = webdriver.Remote("http://localhost:4723/wd/hub", caps)
- self.driver.implicitly_wait(15)
-
- def teardown(self):
- self.driver.quit()
-
- def test_mobile(self):
- self.driver.make_gsm_call('15033657989', GsmCallActions.CALL)
- self.driver.send_sms('15033657956', 'hello appium api')
- time.sleep(5)
- self.driver.set_network_connection(1)
- time.sleep(2)
- self.driver.set_network_connection(4)
- time.sleep(2)
- self.driver.get_screenshot_as_file('./datas/screenshot/img.png')
10.模拟器控制
android 模拟器创建
配置
代码示例:
test_simulator_control.py
- import time
-
- from appium import webdriver
-
-
- class TestControl:
- def setup(self):
- caps = {}
- caps["platformName"] = "Android"
- caps['platformVersion'] = '6.0'
- caps["appPackage"] = "cn.kmob.screenfingermovelock"
- caps["appActivity"] = "com.samsung.ui.FlashActivity"
- caps["deviceName"] = "127.0.0.1:7555"
- caps["ensureWebviewsHavePages"] = True
- caps['unicodeKeyBoard'] = 'true'
- caps['resetKeyBoard'] = 'true'
- caps["noReset"] = "true"
- caps['avd'] = 'Resizable_Experimental_API_34'
- self.driver = webdriver.Remote("http://localhost:4723/wd/hub", caps)
- self.driver.implicitly_wait(15)
-
- def teardown(self):
- self.driver.quit()
-
- def test_control(self):
- self.driver.set_network_connection(1)
- time.sleep(2)
- self.driver.set_network_connection(4)
- time.sleep(2)
- self.driver.get_screenshot_as_file('./datas/screenshot/img.png')
11.雪球财经app股票详情功能点自动化测试实战
产品分析
Page Object 模式六大原则
总结
用例设计
实战练习思路
优化用例-封装样板代码(基类)
优化用例-页面元素提取
优化用例-复用driver
代码示例:
base/base_page.py
- from appium.webdriver.webdriver import WebDriver
-
-
- class BasePage:
- def __init__(self, driver: WebDriver = None):
- self.driver = driver
-
- def find(self, by, locator):
- return self.driver.find_element(by, locator)
-
- def find_and_click(self, by, locator):
- self.find(by, locator).click()
-
- def find_and_send(self, by, locator, text):
- self.find(by, locator).send_keys(text)
-
- def find_and_gettext(self, by, locator):
- return self.find(by, locator).text
base/xueqiu_app.py
- from appium import webdriver
-
- from app_automation_testing.test_xueqiu_po.base.base_page import BasePage
-
-
- class XueQiuApp(BasePage):
- def start(self):
- if self.driver == None:
- print("driver == None")
- caps = {}
- caps["platformName"] = "Android"
- caps['platformVersion'] = '6.0'
- caps["appPackage"] = "com.xueqiu.android"
- caps["appActivity"] = ".view.WelcomeActivityAlias"
- caps["deviceName"] = "127.0.0.1:7555 device"
- caps["noReset"] = "true"
- self.driver = webdriver.Remote("http://localhost:4723/wd/hub", caps)
- self.driver.implicitly_wait(10)
- else:
- print("复用driver")
- self.driver.launch_app()
- return self
-
- def restart(self):
- pass
-
- def stop(self):
- self.driver.quit()
-
- def goto_main(self):
- from app_automation_testing.test_xueqiu_po.page.main_page import MainPage
- return MainPage(self.driver)
page/main_page.py
- from appium.webdriver.common.appiumby import AppiumBy
-
- from app_automation_testing.test_xueqiu_po.base.xueqiu_app import XueQiuApp
-
-
- class MainPage(XueQiuApp):
- _search_box_element = (AppiumBy.ID, "com.xueqiu.android:id/tv_search")
-
- def click_search(self):
- self.find_and_click(*self._search_box_element)
-
- from app_automation_testing.test_xueqiu_po.page.search_page import SearchPage
- return SearchPage(self.driver)
page/search_page.py
- from appium.webdriver.common.appiumby import AppiumBy
- from appium.webdriver.webdriver import WebDriver
-
- from app_automation_testing.test_xueqiu_po.base.xueqiu_app import XueQiuApp
-
-
- class SearchPage(XueQiuApp):
-
- def input_searchcentent(self, searchkey):
- self.find_and_send(AppiumBy.ID, "com.xueqiu.android:id/search_input_text", searchkey)
-
- from app_automation_testing.test_xueqiu_po.page.search_result_page import SearchResultPage
- return self
-
- def click_searchresult(self, text):
- self.find_and_click(AppiumBy.XPATH, f'//*[@text="{text}"]')
-
- from app_automation_testing.test_xueqiu_po.page.search_result_page import SearchResultPage
- return SearchResultPage(self.driver)
page/search_result_page.py
- from appium.webdriver.common.appiumby import AppiumBy
-
- from app_automation_testing.test_xueqiu_po.base.xueqiu_app import XueQiuApp
-
-
- class SearchResultPage(XueQiuApp):
-
- def goto_stock_tab(self):
- self.find_and_click(AppiumBy.XPATH, '//*[@text="股票"]')
- return self
-
- def get_price(self):
- current_price = self.find_and_gettext(AppiumBy.XPATH,
- "//*[@text='BABA']/../../..//*[@resource-id='com.xueqiu.android:id/current_price']")
- return float(current_price)
case/test_xueqiu.py
- from hamcrest import assert_that, close_to
-
- from app_automation_testing.test_xueqiu_po.base.xueqiu_app import XueQiuApp
-
-
- class TestXueQiu:
- def setup_class(self):
- self.xueqiuapp = XueQiuApp()
-
- def setup(self):
- self.main = self.xueqiuapp.start().goto_main()
-
- def teardown_class(self):
- self.xueqiuapp.stop()
-
- def test_search(self):
- search_key = "alibaba"
- search_result = 'BABA'
-
- stock_price = self.main.click_search(). \
- input_searchcentent(search_key). \
- click_searchresult(search_result). \
- goto_stock_tab(). \
- get_price()
-
- assert_that(stock_price, close_to(110, 110 * 0.5))
-
- def test_search1(self):
- search_key = "alibaba"
- search_result = 'BABA'
-
- stock_price = self.main.click_search(). \
- input_searchcentent(search_key). \
- click_searchresult(search_result). \
- goto_stock_tab(). \
- get_price()
-
- assert_that(stock_price, close_to(110, 110 * 0.5))