史上最全 Appium 自动化测试从入门到框架实战精华学习笔记(三)

史上最全 Appium 自动化测试从入门到框架实战精华学习笔记(三)

本文为霍格沃兹测试学院学员学习笔记。

本系列文章汇总了从 Appium 自动化测试从基础到框架高级实战中,所涉及到的方方面面的知识点精华内容(如下所示),希望对大家快速总结和复习有所帮助。

Appium 自动化测试从基础到框架实战

  1. Appium 基础 1 (环境搭建和简介)
  2. Appium 基础 2 (元素定位和元素常用方法)
  3. Appium 基础 3 (手势操作和 uiautomator 查找元素)
  4. Appium 基础 4 (显式等待)
  5. Appium 基础 5 (toast 和参数化)
  6. Appium 基础 6 (webview)
  7. Appium_ 企业微信练习 (非 PO,增加和删除联系人)
  8. Appium_ 企业微信练习 ( PO--增加联系人)

本文为第三篇,主要讲解 Appium Toast、参数化、WebView(附实例代码)。

Toast

含义

  • 为了给当前视图显示一个浮动的显示块,与 dialog 不同它永远不会获得焦点;
  • 显示时间有限,根据用户设置的显示时间后自动消失;
  • 本身是个系统级别的控件,它归属系统 settings,当一个 App 发送消息的时候,不是自己造出来的这个弹框,它是发给系统,由系统统一进行弹框,这类的控件不在 App 内、需要特殊的控件识别方法;

Toast 定位

  • Appium 使用 UIAutomator 底层的机制来分析抓取 toast,并且把 toast 放到控件树里面,但本身并不属于控件

  • AutoMationName:UIAutomator2 这个是 Appium 本身的设置就自带的,不需要额外添加,默认就是UIAutomator2;

  • getPageSource 是无法找到 Toast 的;

  • 必须使用 Xpath 去查找:

  • //*[@class="android.widget.Toast"]

  • //*[contains(@text,"xxxxx")]

实例:Appium 自带的 App 测试 Toast

  • adb shell dumpsys window | findstr mCurrent

  • 这个命令可以找到当前的 activity,不知道 Android 高版本是不是还 ok,由于 API Demo 权限高,可直接跳到这个 activity 运行,其他 App 就不 ok 了;

  • driver.page_source 可以打印当前的页面,可以找到 Toast 的伪控件;

  • 打印 toast 的 text 出来;

driver.page_source 打印出来的东西,包含 Toast

<?xml version='1.0' encoding='UTF-8' standalone='yes' ?><hierarchy index="0" class="hierarchy" rotation="3" width="810" height="1440">  <android.widget.FrameLayout index="0" package="io.appium.android.apis" class="android.widget.FrameLayout" text="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" long-clickable="false" password="false" scrollable="false" selected="false" bounds="[0,0][810,1440]" displayed="true">    <android.view.ViewGroup index="0" package="io.appium.android.apis" class="android.view.ViewGroup" text="" resource-id="android:id/decor_content_parent" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" long-clickable="false" password="false" scrollable="false" selected="false" bounds="[0,0][810,1440]" displayed="true">      <android.widget.FrameLayout index="0" package="io.appium.android.apis" class="android.widget.FrameLayout" text="" resource-id="android:id/action_bar_container" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" long-clickable="false" password="false" scrollable="false" selected="false" bounds="[0,41][810,136]" displayed="true">        <android.view.ViewGroup index="0" package="io.appium.android.apis" class="android.view.ViewGroup" text="" resource-id="android:id/action_bar" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" long-clickable="false" password="false" scrollable="false" selected="false" bounds="[0,41][810,136]" displayed="true">          <android.widget.TextView index="0" package="io.appium.android.apis" class="android.widget.TextView" text="Views/Popup Menu" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" long-clickable="false" password="false" scrollable="false" selected="false" bounds="[27,65][324,111]" displayed="true" />        </android.view.ViewGroup>      </android.widget.FrameLayout>      <android.widget.FrameLayout index="1" package="io.appium.android.apis" class="android.widget.FrameLayout" text="" resource-id="android:id/content" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" long-clickable="false" password="false" scrollable="false" selected="false" bounds="[0,136][810,1440]" displayed="true">        <android.widget.LinearLayout index="0" package="io.appium.android.apis" class="android.widget.LinearLayout" text="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" long-clickable="false" password="false" scrollable="false" selected="false" bounds="[0,136][810,1440]" displayed="true">          <android.widget.Button index="0" package="io.appium.android.apis" class="android.widget.Button" text="Make a Popup!" content-desc="Make a Popup!" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" long-clickable="false" password="false" scrollable="false" selected="false" bounds="[297,136][513,217]" displayed="true" />        </android.widget.LinearLayout>      </android.widget.FrameLayout>    </android.view.ViewGroup>  </android.widget.FrameLayout>  #这里就找到了Tast的控件了  <android.widget.Toast index="1" package="com.android.settings" class="android.widget.Toast" text="Clicked popup menu item Search" checkable="false" checked="false" clickable="false" enabled="false" focusable="false" focused="false" long-clickable="false" password="false" scrollable="false" selected="false" bounds="[0,0][0,0]" displayed="false" /></hierarchy>

代码

from appium import webdriverfrom selenium.webdriver.support import expected_conditionsfrom selenium.webdriver.support.wait import WebDriverWaitfrom appium.webdriver.common.mobileby import MobileBy as Byclass TestFind():    def setup(self):        self.desire_cap= {            "platformName":"android",            "deviceName":"127.0.0.1:7555",            "appPackage":"io.appium.android.apis",            "appActivity":"io.appium.android.apis.view.PopupMenu1",            "noReset":"true",            "unicodeKeyboard":True        }        self.driver=webdriver.Remote("http://127.0.0.1:4723/wd/hub",self.desire_cap)        self.driver.implicitly_wait(5)    def test_search(self):        """        1.打开appium的演示app        2.直接进入到测试toast的界面        3.点击显示toast的按钮,然后通过driver.page_source获取页面        4.找到toast的伪控件        5.打印出toast的值出来        :return:        """        #点击Make a Popup的控件        self.driver.find_element(By.XPATH,'//*[@text="Make a Popup!"]').click()        #点击search的控件        self.driver.find_element(By.XPATH, '//*[@text="Search"]').click()        #打印整个布局页面的xml出来        print(self.driver.page_source)        #打印出toast的值        print(self.driver.find_element(By.XPATH, '//*[contains(@text,"popup menu")]').text)

参数化

一些小细节

  • 参数化要解决的是一个用例可以复用的问题,比如一个用例重复使用不同的数据,就可以使用参数化,比如同一个用例,有搜索股价,比较股价,都是同一个方法,只是数据不太一样;
  • @pytest.mark.parametrize('searchkey,type,price',[('alibaba','BABA',180),('xiaomi','01810',10)
  • 用上面的方法去使用参数化;
  • def test_search(self,searchkey,type,price) 函数的参数要和参数化的参数的数量一样,字符串也要一样;
  • 一个用例,有2组参数化,就会运行两次 setup 和 teardown 的方法;
  • 使用 self.driver.find_element(By.ID,"com.xueqiu.android:id/search_input_text").send_keys(f"{searchkey}"),使用f"{searchkey}"是一个好东西,可以搭配参数化使用;

代码

from appium import webdriverfrom appium.webdriver.common.mobileby import MobileBy as Byfrom selenium.webdriver.support import expected_conditionsfrom selenium.webdriver.support.wait import WebDriverWaitimport pytestclass TestFind():    #设置caps的值    def setup(self):        self.desire_cap= {            #默认是Android            "platformName":"android",            #adb devices的sn名称            "deviceName":"127.0.0.1:7555",            #包名            "appPackage":"com.xueqiu.android",            #activity名字            "appActivity":".view.WelcomeActivityAlias",            "noReset":"true",            "unicodeKeyboard":True        }        #运行appium,前提是要打开appium server        self.driver=webdriver.Remote("http://127.0.0.1:4723/wd/hub",self.desire_cap)        self.driver.implicitly_wait(5)    #这个加不加都行,因为参数化运行都会有setup的,setup就是启动app的过程了,这个就显得有点多余了    #但好像setup并不会初始化整个app,还会停留在前一个页面上,所以还是加上比较好    def teardown(self):        self.driver.find_element(By.XPATH,'//*[@text="取消"]').click()    #这是参数化的函数,第一部分是参数化的名字,得和下面的函数参数一模一样,用字符串包含进去    #列表里面的元祖接受具体的参数化的数据,用逗号隔开,和list一样    @pytest.mark.parametrize('searchkey,type,price',[        ('alibaba','BABA',180),        ('xiaomi','01810',10)    ])    #参数哈的函数的参数要和上面的参数名字保持一致    def test_search(self,searchkey,type,price):        """        1.打开雪球app        2.点击搜索输入框        3.向搜索输入框输入“阿里巴巴”        4.在搜索的结果里选择阿里巴巴,然后点击        5.获取这只上香港 阿里巴巴的股价,并判断这只股价的价格>200        6.通过参数化的方法,用一个用例判断阿里巴巴和小米的股价        :return:        """        #显示等待进入主页,等主页的元素都加载好了        WebDriverWait(self.driver, 15).until(expected_conditions.element_to_be_clickable((By.XPATH,'//*[@text="我的"]')))        #点击搜索框        self.driver.find_element(By.ID,"com.xueqiu.android:id/tv_search").click()        #向搜索框输入阿里巴巴,小米等参数化的东西f"{searchkey}"是一个好用的东西        self.driver.find_element(By.ID,"com.xueqiu.android:id/search_input_text").send_keys(f"{searchkey}")        #找到搜索框预览结果的阿里巴巴,并点击        self.driver.find_element(By.XPATH,f"//*[@text='{type}']").click()        #选择HK股价的元素,这里是通过父类的方法去定位的        current_price=self.driver.find_element(By.XPATH,f"//*[@text='{type}']/../../..//*[@resource-id='com.xueqiu.android:id/current_price']")        #提取股价的text属性        current_price=float(current_price.text)        #判断股价是否大于200        assert current_price > price

WebView

纯 WebView 测试(只测试浏览器)的环境准备

  • 手机端

  • 被测浏览器:(不可以是第三方浏览器)safari for ios and chrome,chromium,or browser for Android

  • PC 端

  • 安装 chrome 浏览器或者 chromium

  • 下载对应手机浏览器对应的 driver

  • 客户端代码:

  • "browserName":"Browser" 或者 "browserName":"Chrome" 这个是指定的浏览器

  • "chromedriverExecutable":r"c:\chrome\chromedriver.exe" 这个是指定的chromedriver的路径

  • 如何查找app的版本:adb shell pm dump com.android.browser | findstr version

  • desire_cap

案例:打开 mumu 自带的浏览器,访问百度

  • 步骤:

  • 不通过包来打开浏览器

  • 访问百度

  • 输入 tongtong,并点击搜索

  • 注意:

  • 第一次运行 Appium,看后台的路径可以找到浏览器的 chromedriver 的版本,还可以找到 chromedriver 的路径

  • https://blog.csdn.net/huilan_same/article/details/51896672

  • 这个网站的 chromedriver 和 chrome 版本的关系更加全

代码

from time import sleepfrom appium import  webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support import expected_conditionsfrom selenium.webdriver.support.wait import WebDriverWaitclass TestFind():    def setup(self):        self.desire_cap= {            "platformName":"android",            "platformVersion":"6.0",            "deviceName":"127.0.0.1:7555",            #想要使用原生的浏览器就选择,Browser。想要选择chrome浏览器就输入Chrome            "browserName":"Browser",            "noRest":True,            #这里是指定chromedriver的路径,记得路径要全到包括chromedriver.exe            "chromedriverExecutable":r"c:\chrome\chromedriver.exe"        }        self.driver=webdriver.Remote("http://127.0.0.1:4723/wd/hub",self.desire_cap)        self.driver.implicitly_wait(5)    def teardown(self):        self.driver.quit()    def test_browser(self):        #打开移动端的百度浏览器        self.driver.get("http://m.baidu.com")        #显示等待找到搜索框是否可见,expected_conditions里面传的locator必须是一个元祖        WebDriverWait(self.driver,10).until(expected_conditions.visibility_of_element_located((By.CSS_SELECTOR,"#index-kw")))        #搜索框输入tongtong        self.driver.find_element(By.CSS_SELECTOR,"#index-kw").send_keys("tongtong")        sleep(2)        #百度一下点击一下        self.driver.find_element(By.CSS_SELECTOR, "#index-bn").click()        sleep(3)

如何判断页面是不是 WebView

  • 断网查看,如果断网显示网页加载不了就是 WebView
  • 看加载条,有加载条通常是 WebView
  • 看顶部是否有关闭按钮
  • 下拉刷新,页面有刷新就是 WebView
  • 下拉刷新的时候是否有网页提供者
  • 用工具查看,如果元素显示 WebView,则是 WebView

WebView

  • 是 Android 系统提供能显示页面的系统控件(特殊的 view)
  • < android4.4 WebView 底层实现 webkit 内部
  • =android4.4 采用 chromium 作为 WebView 底层支持,支持 HTML5、CSS3、JS

  • WebAudio:图形化的界面收听音频
  • WebGL:页面 3d 效果的渲染
  • WebRTC:直播等等,美颜

混合 WebView 测试条件

  • PC:

  • 能够访问 Google

  • 下载对应版本的 chromedriver

  • 手机端:应用代码需要打开WebView的开关

  • 代码中要添加 chromedriverExecutable

  • 有一些 WebView 可以被 UIAutomatorview 查找到,但都不推荐,可能会出现兼容性的问题,比如 text 的显示字符串会不一样

  • 如何查找当前 WebView 的网页

  • adb shell

  • logcat | grep http

  • 就能找到访问的 HTTP 了


案例1 Appium 的 API 的混合 WebView

  • 打开 API demo 的 WebView
  • 向输入框输入文本
  • 点击 i am link
  • 退出应用

代码

from time import sleepfrom appium import  webdriverfrom appium.webdriver.common.mobileby import MobileByclass TestFind():    def setup(self):        self.desire_cap= {            "platformName":"android",            "platformVersion":"6.0",            "deviceName":"127.0.0.1:7555",            "noRest":True,            "appPackage": "io.appium.android.apis",            "appActivity":"io.appium.android.apis.view.webview1",            #想要切换webview,必须得指定chromdriver,或者你的默认地址的chromedriver的版本和手机的版本是对应的            "chromedriverExecutable": r"c:\chrome\chromedriver.exe"        }        self.driver=webdriver.Remote("http://127.0.0.1:4723/wd/hub",self.desire_cap)        self.driver.implicitly_wait(5)    def teardown(self):        self.driver.quit()    def test_appium_api_webview(self):        sleep(3)        #进入到webview页面,直接打印contexts,肯定有两个,一个是原生的,一个是webview        print(self.driver.contexts)        #需要切换到webview的context的,通常是倒数第一个,记得要有chromedriverExecutable        self.driver.switch_to.context(self.driver.contexts[-1])        sleep(2)        #往输入框输入tongtong        self.driver.find_element(MobileBy.ID,"i_am_a_textbox").send_keys("tongtong")        #点击链接        self.driver.find_element(MobileBy.ID,"i am a link").click()        #打印出当前的页面布局,发现是一个webview的html的布局        print(self.driver.page_source)

案例2 雪球webview

  • 打开应用
  • 点击交易
  • 点击 A 股开户
  • 输入用户名和密码
  • 点击立即开户
  • 退出应用
  • 注:打开新的页面其实就是一个新的窗口了,要切换窗口句柄了
#由于chrome识别不到雪球的webview,元素定位有问题,所以代码搞不定from time import sleepfrom appium import  webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support import expected_conditionsfrom selenium.webdriver.support.wait import WebDriverWaitclass TestFind():    def setup(self):        self.desire_cap= {            "platformName":"android",            "platformVersion":"6.0",            "deviceName":"127.0.0.1:7555",            #想要使用原生的浏览器就选择,Browser。想要选择chrome浏览器就输入Chrome            "browserName":"Browser",            "noRest":True,            #这里是指定chromedriver的路径,记得路径要全到包括chromedriver.exe            "chromedriverExecutable":r"c:\chrome\chromedriver.exe"        }        self.driver=webdriver.Remote("http://127.0.0.1:4723/wd/hub",self.desire_cap)        self.driver.implicitly_wait(5)    def teardown(self):        self.driver.quit()    def test_browser(self):        #打开移动端的百度浏览器        self.driver.get("http://m.baidu.com")        #显示等待找到搜索框是否可见,expected_conditions里面传的locator必须是一个元祖        WebDriverWait(self.driver,10).until(expected_conditions.visibility_of_element_located((By.CSS_SELECTOR,"#index-kw")))        #搜索框输入tongtong        self.driver.find_element(By.CSS_SELECTOR,"#index-kw").send_keys("tongtong")        sleep(2)        #百度一下点击一下        self.driver.find_element(By.CSS_SELECTOR, "#index-bn").click()        sleep(3)

WebView 遇到的坑

  • 设备

  • Android 模拟器 6.0 默认支持 WebView,mumu 直接打开了,不用设置;

  • 起码模拟器和物理机需要打开 App 内开关(WebView 调试开关);

  • PC 浏览器定位元素

  • Chrome 浏览器-62版本才可以更好的看见 webview 的内部,其他的版本都有一些 bug;

  • 换成 chromium 浏览器可以避免很多坑,展示效果和速度要比 chrome 要快;

  • 代码

  • 有的设备可以使用 find_element_acessibility_id(), 不同的设备渲染的页面不同,兼容性不适合;

  • switch_to.context() 切换不同的 context,一个页面来说;

  • switch.to_window() 切换不同的窗口句柄,对不同的页面来说;

更多内容,我们在后续文章分享。

(文章来源于霍格沃兹测试学院)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342