前言:
本文为在霍格沃兹测试开发学社中学习到的一些技术写出来分享给大家,希望有志同道合的小伙伴可以一起交流技术,一起进步~ 😘
Android 是通过容器的布局属性来管理子控件的位置关系,布局过程就是把界面上的所有空间根据他们的间距的大小摆放在正确的位置。
布局是一种可用于放置很多控件的容器,它可以按照一定的规律调整内部控件的位置,从而编写出精美的界面。当然,布局的内部除了放置控件外,也可以放置布局,通过多层布局的嵌套,我们就能完成一些比较复杂的界面。
iOS去掉了布局的概念,直接用变量之间的相对关系完成位置的计算。
系统:MacOS X
开发工具:Xcode
开发语言:ObjectC
安装文件:.ipa文件/.app文件
注意:
使用Appium测试iOS应用需要使用MacOS操作系统。
元素定位,实际上就是定位控件。想要一个脚本同时支持android/iOS两个系统,就得保证元素属性(id,aid,xpath等)一致。
dom定义:
dom应用定义:
Xpath的定义:
Android的层级结构:
iOS与Andriod的dom结构的区别:
Appium Inspector上的元素属性展示:
from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy
class TestXueQiu:
def setup(self):
# 创建一个字典
desire_cap = {}
# 平台
desire_cap['platform'] = 'Android'
#手机系统版本
desire_cap['platformVersion']= '6.0'
# 设备名
desire_cap['deviceName'] = '127.0.0.1:7555'
# app 包名
desire_cap['appPackage'] = 'com.xueqiu.android'
# app 页面名
desire_cap['appActivity'] = '.common.MainActivity'
desire_cap['noReset'] = 'true'
#每次测试重置数据
desire_cap['skipDeviceInitization']= 'true'
#保证可以正常输入中文
desire_cap['unicodeKeyBoard'] = 'true'
desire_cap['resetKeyBoard'] = 'true'
self.driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desire_cap)
self.driver.implicitly_wait(10)
def teardown(self):
# 回收session
self.driver.quit()
def test_api_demo(self):
"""
1、打开 雪球 app
2、点击搜索输入框
3、像搜索框中输入“阿里巴巴”
4、在搜索结果中选择“阿里巴巴”,然后进行点击
5、获取这只上 阿里巴巴的股价,并判断这只股价的价格<200
6、断言
:return:
"""
#定位搜索框并进行点击,使用id进行定位
self.driver.find_element(AppiumBy.ID,'com.xueqiu.android:id/tv_search').click()
#搜索框输入“阿里巴巴”,使用id进行定位
self.driver.find_element(AppiumBy.ID,'com.xueqiu.android:id/search_input_text').send_keys("阿里巴巴")
#在搜索结果中选择“阿里巴巴”,并且点击;使用Xpath进行定位
self.driver.find_element(AppiumBy.XPATH,"//*[@resource-id='com.xueqiu.android:id/name' and @text='阿里巴巴']").click()
#获取当前股价,使用id进行定位
current_price=float(self.driver.find_element(AppiumBy.ID,'com.xueqiu.android:id/current_price').text)
# 断言
assert current_price<200
当存在无法直接定位的元素,或者通过控件定位无法精准的定位我们想要的元素的时可以使用xapth的层级定位方法来精准定位我们想要的元素。
以下示例都根据此xml文档进行演示
<bookstore>
<book>
<title lang="eng">Harry Pottertitle>
<price>29.99price>
book>
<book>
<title lang="eng">Learning XMLtitle>
<price>39.95price>
book>
bookstore>
XPath 使用路径表达式在 XML 文档中选取节点。节点是通过沿着路径或者 step 来选取的。
语法:
表达式 | 描述 |
---|---|
nodename | 选取此节点的所有子节点。 |
/ | 从根节点选取。 |
// | 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。 |
. | 选取当前节点。 |
… | 选取当前节点的父节点。 |
@ | 选取属性。 |
示例:
路径表达式 | 结果 |
---|---|
bookstore | 选取 bookstore 元素的所有子节点。 |
/bookstore | 选取根元素 bookstore。 注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径! |
bookstore/book | 选取属于 bookstore 的子元素的所有 book 元素。 |
//book | 选取所有 book 子元素,而不管它们在文档中的位置。 |
bookstore//book | 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。 |
//@lang | 选取名为 lang 的所有属性。 |
示例:
在下面的表格中,我们列出了带有谓语的一些路径表达式,以及表达式的结果:
路径表达式 | 结果 |
---|---|
/bookstore/book[1] | 选取属于 bookstore 子元素的第一个 book 元素。 |
/bookstore/book[last()] | 选取属于 bookstore 子元素的最后一个 book 元素。 |
/bookstore/book[last()-1] | 选取属于 bookstore 子元素的倒数第二个 book 元素。 |
/bookstore/book[position()< 3] | 选取最前面的两个属于 bookstore 元素的子元素的 book 元素。 |
//title[@lang] | 选取所有拥有名为 lang 的属性的 title 元素。 |
//title[@lang=‘eng’] | 选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。 |
/bookstore/book[price>35.00] | 选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。 |
/bookstore/book[price>35.00]/title | 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。 |
语法:
XPath 通配符可用来选取未知的 XML 元素。
通配符 | 描述 |
---|---|
* | 匹配任何元素节点。 |
@* | 匹配任何属性节点。 |
node() | 匹配任何类型的节点。 |
示例:
在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:
路径表达式 | 结果 |
---|---|
/bookstore/* | 选取 bookstore 元素的所有子元素。 |
//* | 选取文档中的所有元素。 |
//title[@*] | 选取所有带有属性的 title 元素。 |
语法:
通过在路径表达式中使用|
运算符,您可以选取若干个路径。
示例:
在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:
路径表达式 | 结果 |
---|---|
//book/title | //book/price | 选取 book 元素的所有 title 和 price 元素。 |
//title | //price | 选取文档中的所有 title 和 price 元素。 |
/bookstore/book/title | //price | 选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。 |
优点:
缺点:
使用方法:
driver.find_element_by_android_uiautomator(表达式)
可定位元素:
new UiSelector().resourceid("id")
new UiSelector().className("className")
new UiSelector().description("content-desc属性")
new UiSelector().text("text文本")
new UiSelector().textContains("包含text文本")
new UiSelector().textStartsWith("以text开头的文本")
new UiSelector().textMathes("正则表达式")
备注:Appium不支持通过text进行定位的,如果要想通过text进行定位需要借助xpath表达式,但uiautomator可以使用text进行定位。
可以自由添加多个属性进行定位
例1:id与text属性组合
ind_text ='resourceId("com.baidu.yuedu:ia/ webbooktitle" ).text("小说")'
driver.find_element_by_android_uiautomator(id_text)
例2:class与text属性组合
class_text='className("android.widget.TextView").text("图书")'
driver.find_element_by_android_uiautomator(class_text)
有时候不能直接定位某个元素,但是它的父元素很好定位,这时候就先定位父元素,通过父元素找儿子。
示例:
son='resourceId("com.baidu.yuedu:id /rl_tabs".childselector(text("股票")'
driver.find_element_by_android_uiautomator(son)
有时候父元素不好定位,但是跟他相邻的兄弟元素很好定位,这时候就可以通过兄弟元素,找到通一父级元素下的子元素
示例:
brother = 'resourceId("com.baidu.yuedu:id/lefttitle").fromParent(text("用户"))'
driver.find_element_by_android_uiautomator(brother)
书写方法:
实现滚动查询找元素:
'new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(new UiSelector().text("查找的文本".instance(0)));'
示例:
from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support.wait import WebDriverWait
class TestWebView:
def setup_class(self):
desired_caps = {}
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '6.0'
desired_caps['deviceName'] = 'emulator-5554'
desired_caps['appPackage'] = 'com.example.android.apis'
desired_caps['appActivity'] = '.ApiDemos'
desired_caps['noReset'] = 'true'
desired_caps['chromeOptions']={'w3c':False}
#设置chromedriver所在路径
desired_caps['chromedriverExecutableDir']='E:\chromedriver_webview_test'
#设置显式指定chromedriver和webview版本对应关系
#desired_caps['chromedriverChromeMappingFile']='E:\chromedriver_webview_test\mapping.json'
#开启chromedriverlog
desired_caps['showChromedriverLog']=True
self.driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
# 使用隐式等待
self.driver.implicitly_wait(10)
def teardown(self):
self.driver.quit()
def test_webview(self):
self.driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR,"new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(new UiSelector().text(\"Views\").instance(0))").click()
self.driver.find_element(AppiumBy.ANDROID_UIAUTOMATOR,"new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(new UiSelector().text(\"WebView\").instance(0))").click()
#打印上下文情况
#['NATIVE_APP', 'WEBVIEW_com.example.android.apis']
print(self.driver.contexts)
#webview组件加载慢的时候不一定会及时出现webview上下文,最好显示等待
WebDriverWait(self.driver,5).until(lambda driver:len(self.driver.contexts)>1)
#最好显示指定,在多进程同时有webview的时候,最后一个context不一定是当前app的webview
self.driver.switch_to.context('WEBVIEW_com.example.android.apis')
from selenium.webdriver.common.by import By
self.driver.find_element(By.LINK_TEXT,'Hello World! - 1').click()
#查看当前所在context
#WEBVIEW_com.example.android.apis
print(self.driver.current_context)
在 Android的Appium Server 1.19.0版本以后和iOS的Appium Server 1.21.0 版本以后,定位方式增加了 CSS Selector。Css selector 会转化为 Android/iOS 原生定位的定位策略,Android转为Android Uiautomator定位方式,而iOS会转为 class chain 定位方式。
id:
driver.find_element_by_css_selector('#id')
description:
driver.find_element_by_css_selector('*[description="Some description"]')
text:
driver.find_element_by_css_selector('*[text="Some description"]')
.android.widget.EditText
.android.widget.TextView[description="Some description"]
driver.find_element_by_css_selector('.android.widget.EditText')
driver.find_element_by_css_selector('.android.widget.TextView[description="Some description"]')
from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy
class TestXueQiu:
def setup(self):
desired_caps = {}
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '6.0'
desired_caps['deviceName'] = '127.0.0.1:7555'
desired_caps['appPackage'] = 'com.xueqiu.android'
desired_caps['appActivity'] = '.view.WelcomeActivityAlias'
desired_caps['noReset'] = 'true'
self.driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
self.driver.implicitly_wait(10)
def teardown(self):
self.driver.quit()
def test_search(self):
"""
1. 打开【雪球】应用首页
2. 点击搜索框
3. 向搜索框输入:alibaba
4. 判断【阿里巴巴】可见
"""
# 点击搜索框
element = self.driver.find_element(AppiumBy.CSS_SELECTOR, "#com\.xueqiu\.android\:id\/tv_search")
element.click()
# 向搜索框输入:alibaba
self.driver.find_element(AppiumBy.CSS_SELECTOR, "#com\.xueqiu\.android\:id\/search_input_text").send_keys("alibaba")
alibaba_element = self.driver.find_element(AppiumBy.CSS_SELECTOR, "*[text='阿里巴巴']")
displayed = alibaba_element.get_attribute("displayed")
print(displayed)
# 判断【阿里巴巴】可见
assert displayed == "true"
❗ 注意:匹配公式前每一个符号都要加上转义符"
\
"
文末说明:
接口测试中我们很容易混淆Session、cookie和token,你知道他们有什么区别吗?快来跟我一起看,一篇文章让你了解三者的区别。😎
⬇⬇⬇⬇⬇⬇⬇
👍👍👍:接口测试经典面试题:Session、cookie、token有什么区别?