APPIUM 安卓自动化测试

1.安装夜神安装模拟器、UiAutomator、android sdk、javasdk环境;
https://pan.baidu.com/s/1Z70sPJagQG1EDRnVfzaOFA
2.配置环境变量,启动模拟器、运行UiAutomator viewer.bat,在模拟器中安装测试应用;
https://pan.baidu.com/s/1a2Bs1D8MsmDK8eakrUN5CA

  1. 也可以使用APPium desktop :https://github.com/appium/appium-desktop
    包含inspector 可以来解析定位元素;
    进行配置如下:
    {
    "deviceName": "Redmi 6",
    "platformName": "Android",
    "appPackage": "com.china.moa",
    "appActivity": "com.ecology.view.WelcomeActivity",
    "platformVersion": "8.1.0",
    "automationName": "UiAutomator2",
    "udid": "99001190304762"
    }
    手机开启开发者模式,打开USB调试;

  2. APPIUM官网文档
    appium-desktop下载

  3. 在线Xpath验证:https://freeformatter.com/xpath-tester.html

  4. 向云手机图库发送图片:
    1、上传图片,路径不同手机有差异:
    adb push {file path} /sdcard/DCIM/Camera/{file name}
    2、广播推至相册:
    adb shell am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file:///sdcard/DCIM/Camera/{file name}

  5. 修改云手机定位:
    adb -s ip:port shell "echo 'longitude=114.055939:latitude=22.657501' > /data/gps/fifo" #其中ip:port是ADB方式(公网)中记录的ip和port。
    以慈寿寺地铁为例:
    adb -s 127.0.0.1:5504 shell "echo 'longitude=116.2871874100:latitude=39.9328901600' > /data/gps/fifo" |

经纬度获取:http://www.gpsspg.com/maps.htm


  1. 下载安装APPIUM推荐官网;
  2. appium -g /tmp/run.log #appium log输出
  3. android-sdk中的avd manger.exe 可以创建模拟器或使用AndroidStudio创建模拟器;

adb基础


app信息
adb shell dumpsys activity top #获取当前界面元素
adb shell dumpsys activity activities #获取任务列表
#app入口
adb logcat |grep -i displayed
aapt dump badging mobike.apk | grep launchable-activity
apkanalyzer 最新版本的sdk中才有
#启动应用
adb shell am start -W -n com.xueqiu.android/.view.WelcomeActivityAlias -S

$adb devices #设备可用列表
$adb -s 127.0.0.1:5510 install  apk绝对路径 #安装apk
$adb shell dumpsys  activity |find "mFocusedActivity"  #查看前台应用包名;

$adb kill-server #终止adb服务
$adb start-server #启动adb服务;

$adb pull  [手机路径]  [本地路径]  #将手机文件拉取到PC;
$adb push [本地路径] [手机路径] # 将PC文件放到手机;

$adb shell am start -n 包名/入口   #启动app
$adb shell pm clean  包名  #清除应用的数据和缓存;
$adb shell input tap x  y  #坐标点击;
$adb shell pm list  packages  #列出所有包名 -s 列出系统apk路径及包名, -3 列出用户apk路径以及包名;
 $adb logcat  >  logcat.log  #打印APP日志

$aapt dump badging apk包路径   ##查看指定apk的相关信息-找app入口 launchable-activity;

deviceName值的获取:
  deviceName=192.168.137.150:5555 ip:手机ip地址,端口,通过如下命令开启
  $ adb devices //查看当前连接设备
  $ adb tcpip 5555 //开启5555端口
  $ adb connect 192.168.137.150 //连接手机看是否能连接
  $ adb devices //再查看当前连接设备

  adb usb #拔取数据线后执行,回复usb调试模式,重新插线;

jar依赖包

 <!--Java client for Appium Mobile Webdriver -->
<dependency>
    <groupId>io.appium</groupId>
    <artifactId>java-client</artifactId>
    <version>7.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.testng/testng -->
<dependency>
    <groupId>org.testng</groupId>
    <artifactId>testng</artifactId>
    <version>6.8</version>
    <scope>test</scope>
</dependency>

添加配置[Appium Desired Capabilities]

capabilities设置
❖ app apk地址
❖ appPackage 包名
❖ appActivity Activity名字
❖ automationName 默认使⽤uiautomator
❖ noReset fullReset 是否在测试前后重置相关环境
❖ unicodeKeyBoard resetKeyBoard 是否需要输⼊⾮英⽂
之外的语⾔并在测试完成后重置输⼊法

#(https://github.com/appium/appium/blob/master/docs/en/writing-running-appium/caps.md)
        desiredCapabilities.setCapability("deviceName", "06f8794b7d29");
        desiredCapabilities.setCapability("platformName", Platform.ANDROID);
        desiredCapabilities.setCapability("appPackage", "com.xx.xx");
        desiredCapabilities.setCapability("appActivity","com.xx.xx.xx" );
        //uiautomator2自动化引擎,解决输入框输入不了数据的问题,
        desiredCapabilities.setCapability("automationName","uiautomator2" );
        //noReset 不清除应用启动数据;默认清理false;
        desiredCapabilities.setCapability("noReset",true);
#创建驱动
        AndroidDriver<WebElement> androidDriver;
#找到页面元素并操作页面元素来模拟用户操作
        androidDriver.findElementByXPath("");
        androidDriver.findElementById("");
#通过断言和日志查看测试结果
         Assert

元素定位

ID定位 resource-id

将相同ID值的元素放在集合汇总,再去通过集合的索引访问;

androidDriver.findElementById(“”);

text定位

androidDriver.fiindElementByAndroidUIAutomator(“new UiSelector().text(\"长沙\")”);  
MobileBy.AndroidUIAutomator("new UiSelector().className(\"android.widget.Button\").textMatches(\".*允许.*\")");

这里使用了原生AndroidUIAutomator;

XPath定位

xpath定位符
❖ 绝对定位: 不推荐
❖ 相对定位:
❖ //*
❖ //[contains(@resource-id, ‘login’)]
❖ //
[@text=‘登录’]
❖ //[contains(@resource-id, ‘login’) and contains(@text, ‘登录’)]
❖ //
[contains(@text, ‘登录’) or contains(@label, ‘登录’)]
❖ //[contains(@text, '看点')]/ancestor:://[contains(name(), ‘EditText’)]
❖ //
[@clickable="true"]//android.widget.TextView[string-length(@text)>0 and string-length(@text)<20]
❖//android.view.View[@content-desc="您的职务"]/preceding-sibling::android.widget.EditText[1] # preceding-sibling 是找当前的前面的兄弟节点;

androidDriver.findeElementByXpath("相对路径");
androidDriver.findeElementByXpath("//android.widget.TextView[@text='我发起的']");

通过class属性元素定位的话class在页面有较多,通常不适用;可以通过结合Xpath定位,80%元素可以使用,APPIUM对XPath定位有了一定的优化性能不用担心;

accessibility id定位

在UIAutomator中么有这个属性, 对应是content-desc属性;


image.png
self.driver.find_element(MobileBy.ACCESSIBILITY_ID,'输入11位手机号').click()

坐标定位

受屏幕尺寸/分辨率/DPPI影响,万不得已不要用;

元素等待

元素加载时间不一致,会导致元素无法定位超时报错;灵活定制定位元素等待时间;

强制等待

固定的等待时间

Thread.sleep();

隐式等待

针对全局元素设置等待时间

androidDriver.manage().timeouts().implicitlyWait(30,TimeUnit.SECONDS);

显示等待

针对特定的某个元素,不可以对非元素;
WebDriverWait 与隐式等待不同的是不会一直等到元素出现,显示等待会在超过设定时间后抛出异常。

WebDriverWait webDriverWait = new WebDriverWait(androidDriver, 10);
        WebElement webElement = webDriverWait.until(new ExpectedCondition<WebElement>() {
            @NullableDecl
            public WebElement apply(@NullableDecl WebDriver input) {
                return androidDriver.findElementById("com.chinat.moa:id/sdl__negative_button");
            }
        });
WebDriverWait wait = new WebDriverWait(driver, 10);
WebElement element = 
wait.until(ExpectedConditions.elementToBeClickable(By.id("someid")));


手势操作

❖ press : TouchAction().press(el0).moveTo(el1).release()
❖ release
❖ moveTo
❖ tap wait
❖ longPress
❖ cancel
❖ perform

手势操作-滑动

java-client5.0之前提供了滑动API,单次滑动(下拉刷新)

 public void refresh() {
       //java-client 4.1.2 
        //void swipe(int startx, int starty, int endx, int endy, int duration)  //duration滑动的时间;
        androidDriver.swipe(356,594,356,794,800)
        //java-client 6.1.0
        TouchAction touchAction = new TouchAction(androidDriver);
        //把原始的坐标转换成PointOption类型的;
        PointOption startPointOption = PointOption.point(356,594);
        PointOption endPointOption = PointOption.point(356,794);
        //把原始的时间转换成Duration类型的;
        Duration duration = Duration.ofMillis(800);
        WaitOptions waitOptions = new WaitOptions().withDuration(duration);
        touchAction.press(startPointOption).waitAction(waitOptions).press(endPointOption).release();
        touchAction.perform();
    }

手势操作-九宫格解锁

连续多次滑动(九宫格解锁)

    @Test
    public void MultiSwipe() throws InterruptedException{
        Thread.sleep(100);
        //实例化TouchAction对象
        TouchAction touchAction = new TouchAction(androidDriver);
        //把原始的坐标转换成PointOption类型的;
        PointOption pointOption1 = PointOption.point(150,427);
        PointOption pointOption2 = PointOption.point(362,427);
        PointOption pointOption3 = PointOption.point(569,427);
        PointOption pointOption4 = PointOption.point(356,625);
        PointOption pointOption5 = PointOption.point(356,850);
        PointOption pointOption6 = PointOption.point(356,850);
        PointOption pointOption7 = PointOption.point(356,850);
        //把原始的时间转换成Duration类型的;
        Duration duration = Duration.ofMillis(800);
        WaitOptions waitOptions = new WaitOptions().withDuration(duration);

        touchAction.press(pointOption1).moveTo(pointOption2).moveTo(pointOption3).moveTo(pointOption4).moveTo(pointOption5).moveTo(pointOption6).moveTo(pointOption7).release();
        touchAction.perform();
    }

手势操作-多点触摸

MultiTouchAction类可以模拟用户多点触摸操作;
主要包含add()/perform()两个方法;
可以结合TouchAction类模拟多根手指的滑动效果;
原理介绍:
B->A同时C->D是放大效果,反之是缩小;

image.png

    @Test
    public void testMultiTouch() throws InterruptedException {
        Thread.sleep(6000);
        //1.实例化MultiTouchAction对象
        MultiTouchAction multiTouchAction = new MultiTouchAction(androidDriver);
        //2.实例化两个TouchAction
        TouchAction touchAction1 = new TouchAction<>(androidDriver);
        TouchAction touchAction2 = new TouchAction<>(androidDriver);
        //获得当前屏幕的高宽;
        int x = androidDriver.manage().window().getSize().getWidth();
        int y = androidDriver.manage().window().getSize().getHeight();
        //第一根手指的动作从B点滑动到A点;
        touchAction1.press(PointOption.point(x * 4 / 10, y * 4 / 10)).waitAction(WaitOptions.waitOptions(Duration.ofMillis(100)))
                .moveTo(PointOption.point(x * 2 / 10, y * 2 / 10)).release();
        //第一根手指的动作从B点滑动到A点;
        touchAction2.press(PointOption.point(x * 6 / 10, y * 6 / 10)).waitAction(WaitOptions.waitOptions(Duration.ofMillis(100)))
                .moveTo(PointOption.point(x * 8 / 10, y * 8 / 10)).release();
        //添加触摸动作到MultiTouchAction
        multiTouchAction.add(touchAction1).add(touchAction2);
        multiTouchAction.perform();
    }

APPIUM常用API

1.startActivity 实现页面跳转(包括APP内部页面及APP相互跳转)

//开启某一个activity实现跳转;
//首先我们创建activitiy对象,用Activity构建方法初始化,参数为对应的包名和类名;
//1.app内部跳转;
Activity activity = new Activity("com.chinatower.moa", "com.ecology.view.MainActivity");
androidDriver.startActivity(activity);

//2.app相互跳转,必须要是跳转app的启动入口;
Activity activityApp = new Activity("com.android.browser", "com.android.view.browser.BrowserView");
androidDriver.startActivity(activity);

2.getPageSource 得到当前页面的dom结构;
可以用于断言当前页面是否有某个元素,或者判断当前页面有没有产生变化:如上下滚动判断是否已经到了底端/顶端;

String pageSource = androidDriver.getPageSource();
System.out.println(pageSource);

3.currentActivity() 获得当前页的类名;

String actual = androidDriver.currentActivity();

4.resetApp重置应用的数据;
有些场景需要清除应用的数据,相当于第一次安装时候的状态,比如第一次启动APP的引导页、登录等;

//重置应用数据;
androidDriver.resetApp();

5.isAppInstalled判断App是否安装;

//获取到应用是否安装
androidDriver.isAppInstalled("com.android.browser");

6.pressKey 安卓平台独有,向系统发送键值事件,不同的键值对应不同的功能,如keyevent(4)标识手机的HOME按键;

//pressKey
KeyEvent keyEvent = new KeyEvent();
keyEvent.withKey(AndroidKey.HOME);
androidDriver.pressKey(keyEvent);

7.getScreenshotAs截图功能,当测试用例执行失败之后进行屏幕截图,保存到本地为了更好的查找问题;

//getScreenshotAs截图功能
File file = androidDriver.getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(file,new File("D:\\test.png" ));

8.getDeviceTime获取设备当前时间

//获取设备当前时间
 androidDriver.getDeviceTime();
  1. getDisplayDensity获取设备DPI,不是分辨率
//获取设备DPI
 androidDriver.getDisplayDensity();

10.getAutomationName获取当前自动化引擎

//获取当前自动化引擎
androidDriver.getAutomationName();

11.getOrientation获取设备的横竖屏状态;

//获取设备的横竖屏状态;
androidDriver.getOrientation();

Toast元素获取

  • 获取要求:Java-client 5.0+;使用UIAntomator2自动化引起;Android系统版本5.0+;
    必须使⽤xpath查找
 //*[@class='android.widget.Toast']
 //*[contains(@text, "xxxxx")]
 #获取方式
By.xpath(“//*[contains(@text,'toast部分信息‘)]”)

常⽤功能

https://github.com/appium/appium/blob/master/docs/en/
writing-running-appium/appium-bindings.md
❖ 系统操作
❖ lock background hideKeyBoard openNotifications shake
❖ startActivity currentActivity getCurrentPackage
❖ app操作
❖ installApp removeApp isInstalled closeApp launchApp reset
getAppStrings
❖ getContextHandles getContext context

Hybrid自动化准备 俗称H5

如何区分H5和原生页面,定位中类为webview的是H5,打开开发者调试-UI布局原生页面会有框框;

Hybrid自动化准备

Appium提供的解决方案基于UIAutomator + ChromeDriver;
准备:

  1. 准备android4.4+版本以上的手机、模拟器;
  2. 在app源码中将webview调试模式打开;
  3. webview.setWebContentsDebuggingEnabled(true);
  4. 安装UC开发者工具;uc-devtools工具
    设置为本地资源,链接上手机活模拟器后,打开一个包含H5页面的APP,HOME中即可有inspect检测到;
    image.png

    image.png

    5.开启手机开发者模式-打开布局,若是原生页面会有很多框;
    原生页面:
    image.png

    H5页面:
    image.png

线上App开启WebView调试(root)

如果是第三方线上APP,一般webview debug开关都是关闭的,这个就需要借助第三方工具才能打开;
解决方案:
Xposed+WebviewDebugHook
Xposed是一个框架,能够集成很多功能模块,这些模块能够在不修改APK的情况下,修改APP的运行方式,将Xposed安装到手机,下载对应的x86活arm框架;
然后安装WebviewDebugHook,勾选Xposed模块中WebviewDebugHook激活,通过模块来开启APP的WebView debug模式;

image.png

上下文

  • 获取所有的contexts
    driver.getContextHandles();
  • 切换到webview视图
    driver.context(webview视图)
  • 定位webview中的元素,并执行操作
    web网页元素定位和操作
  • 切换回默认的视图
    driver.context(nativer视图)
  • 示例


    image.png

  • 常见错误chromedriver和Chrome 版本不符,下载对应的版本chromedriver 替换appium的目录下的即可;
  • 报错截图:


    image.png
  • 可在git查询chromedriver和Chrome 版本,淘宝查询支持版本;


    image.png
image.png
  • 替换appium目录下的chromedriver:


    image.png

非root设备开启线上APP的Webview调试

安卓VitualXposed+WebviewDebugHook后,选择对应应用;

adb install C:\Users\Tower\Documents\VirtualXposed_AOSP_0.17.3.apk
adb install C:\Users\Tower\Documents\WebViewDebugHook.apk

APPIUM自动化原理解析

image.png

Appium Android⾃动化流程分析

appium -g <log file path>

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

推荐阅读更多精彩内容