第八期_移动测试技术 Appium_20190221

参考资料

UI自动化价值

Appium安装

Android命令

adb logcat 

02-21 21:03:28.811  1153  2077 I ActivityManager: START u0 {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.xueqiu.android/.view.WelcomeActivityAlias bnds=[434,1257][645,1547]} from uid 10070
02-21 21:03:28.813  1153  2077 I ActivityManager: ActivityRecord info: ActivityInfo{15fee14 com.xueqiu.android.view.WelcomeActivityAlias}
02-21 21:03:28.815   723   758 D AwareLog: iawared: workingset_process_command subCmd = 354, uid=10192, pid=0, com.xueqiu.android
02-21 21:03:28.815   723   758 D AwareLog: iawared: workingset_send_preread_msg com.xueqiu.android
02-21 21:03:28.815   723   758 D AwareLog: iawared: workingsetStart com.xueqiu.android in monitor0
02-21 21:03:28.816  1881  2201 I hibernation: front pkg : com.xueqiu.android launcher: false
02-21 21:03:28.816  1881  2201 I hibernation: above launcher front pkgs: [com.xueqiu.android]
02-21 21:03:28.837  1153  2077 V WindowManager: addAppToken: AppWindowToken{157d780 token=Token{2722c03 ActivityRecord{a14c4b2 u0 com.xueqiu.android/.view.WelcomeActivityAlias t11782}}} controller={TaskWindowContainerController taskId=11782} at 2147483647
02-21 21:03:28.840  1153  1341 D HwPhoneWindowManager: addHwStartWindow set default on app : com.xueqiu.android
02-21 21:03:28.849  1153  2077 V ActivityManager: startProcess: name=com.xueqiu.android app=null knownToBeDead=true thread=null pid=-1
#手工打开某个界面
02-21 21:07:46.392  1153  1229 I ActivityManager: Displayed com.xueqiu.android/.stock.StockDetailActivity: +251ms

adb logcat | grep -i displayed

02-21 21:27:58.341  1153  1229 I ActivityManager: Displayed com.xueqiu.android/.view.WelcomeActivityAlias: +357ms

adb shell am start -W -S -n com.xueqiu.android/.view.WelcomeActivityAlias

Stopping: com.xueqiu.android
Starting: Intent { cmp=com.xueqiu.android/.view.WelcomeActivityAlias }
Status: ok
Activity: com.xueqiu.android/.view.WelcomeActivityAlias
ThisTime: 338
TotalTime: 338
WaitTime: 389
Complete


Appium Inspector

获取app的入口命令

  • adb logcat | grep -i displayed
  • adb shell dumpsys activity activities top
  • apkanalyse
{
  "platformName": "android",
  "deviceName": "demo",
  "appPackage": "com.xueqiu.android",
  "appActivity": ".view.WelcomeActivityAlias"
}

page source

<?xml version="1.0" encoding="UTF-8"?>
<hierarchy rotation="0">
   <android.widget.FrameLayout index="0" text="" class="android.widget.FrameLayout" package="com.xueqiu.android" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,85][1080,2159]" resource-id="" instance="0">
      <android.widget.LinearLayout index="0" text="" class="android.widget.LinearLayout" package="com.xueqiu.android" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,85][1080,2159]" resource-id="" instance="0">
         <android.widget.FrameLayout index="0" text="" class="android.widget.FrameLayout" package="com.xueqiu.android" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,85][1080,2159]" resource-id="android:id/content" instance="1">
            <android.widget.RelativeLayout index="0" text="" class="android.widget.RelativeLayout" package="com.xueqiu.android" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[0,85][1080,2159]" resource-id="" instance="0">
               <android.widget.ImageView NAF="true" index="0" text="" class="android.widget.ImageView" package="com.xueqiu.android" content-desc="" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[896,530][992,626]" resource-id="com.xueqiu.android:id/image_cancel" instance="0" />
               <android.widget.LinearLayout index="1" text="" class="android.widget.LinearLayout" package="com.xueqiu.android" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[87,686][992,1642]" resource-id="com.xueqiu.android:id/container" instance="1">
                  <android.widget.TextView index="0" text="雪球需要获取下列权限&#xA;才可正常使用:" class="android.widget.TextView" package="com.xueqiu.android" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[147,776][932,934]" resource-id="" instance="0" />
                  <android.widget.LinearLayout index="1" text="" class="android.widget.LinearLayout" package="com.xueqiu.android" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[147,934][932,1342]" resource-id="com.xueqiu.android:id/permission_list" instance="2">
                     <android.widget.RelativeLayout index="0" text="" class="android.widget.RelativeLayout" package="com.xueqiu.android" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[147,994][932,1138]" resource-id="com.xueqiu.android:id/phone_permission" instance="1">
                        <android.widget.ImageView index="0" text="" class="android.widget.ImageView" package="com.xueqiu.android" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[147,994][291,1138]" resource-id="com.xueqiu.android:id/phone_permission_icon" instance="1" />
                        <android.widget.TextView index="1" text="手机/电话权限" class="android.widget.TextView" package="com.xueqiu.android" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[330,994][676,1067]" resource-id="com.xueqiu.android:id/phone_permission_name" instance="1" />
                        <android.widget.TextView index="2" text="校验IMEI&amp;MSI码,防止账号被盗" class="android.widget.TextView" package="com.xueqiu.android" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[330,1079][932,1136]" resource-id="" instance="2" />
                     </android.widget.RelativeLayout>
                     <android.widget.RelativeLayout index="1" text="" class="android.widget.RelativeLayout" package="com.xueqiu.android" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[147,1198][932,1342]" resource-id="com.xueqiu.android:id/storage_permission" instance="2">
                        <android.widget.ImageView index="0" text="" class="android.widget.ImageView" package="com.xueqiu.android" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[147,1198][291,1342]" resource-id="com.xueqiu.android:id/storage_permission_icon" instance="2" />
                        <android.widget.TextView index="1" text="存储权限" class="android.widget.TextView" package="com.xueqiu.android" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[330,1198][546,1271]" resource-id="com.xueqiu.android:id/storage_permission_name" instance="3" />
                        <android.widget.TextView index="2" text="缓存图片和视频,降低流量消耗" class="android.widget.TextView" package="com.xueqiu.android" content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[330,1283][918,1340]" resource-id="" instance="4" />
                     </android.widget.RelativeLayout>
                  </android.widget.LinearLayout>
                  <android.widget.TextView index="2" text="开启" class="android.widget.TextView" package="com.xueqiu.android" content-desc="" checkable="false" checked="false" clickable="true" enabled="true" focusable="true" focused="false" scrollable="false" long-clickable="false" password="false" selected="false" bounds="[147,1432][932,1552]" resource-id="com.xueqiu.android:id/open" instance="5" />
               </android.widget.LinearLayout>
            </android.widget.RelativeLayout>
         </android.widget.FrameLayout>
      </android.widget.LinearLayout>
   </android.widget.FrameLayout>
</hierarchy>

录制生成用例

python

# 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

from appium import webdriver

caps = {}
caps["platformName"] = "android"
caps["deviceName"] = "demo"
caps["appPackage"] = "com.xueqiu.android"
caps["appActivity"] = ".view.WelcomeActivityAlias"

driver = webdriver.Remote("http://localhost:4723/wd/hub", caps)

el1 = driver.find_element_by_id("com.xueqiu.android:id/open")
el1.click()
el2 = driver.find_element_by_id("android:id/button1")
el2.click()
el3 = driver.find_element_by_id("com.xueqiu.android:id/tv_search")
el3.click()
el4 = driver.find_element_by_id("com.xueqiu.android:id/search_input_text")
el4.send_keys("pdd")

driver.quit()

java

import io.appium.java_client.MobileElement;
import io.appium.java_client.android.AndroidDriver;
import junit.framework.TestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.net.MalformedURLException;
import java.net.URL;
import org.openqa.selenium.remote.DesiredCapabilities;

public class SampleTest {

  private AndroidDriver driver;

  @Before
  public void setUp() throws MalformedURLException {
    DesiredCapabilities desiredCapabilities = new DesiredCapabilities();
    desiredCapabilities.setCapability("platformName", "android");
    desiredCapabilities.setCapability("deviceName", "demo");
    desiredCapabilities.setCapability("appPackage", "com.xueqiu.android");
    desiredCapabilities.setCapability("appActivity", ".view.WelcomeActivityAlias");

    URL remoteUrl = new URL("http://localhost:4723/wd/hub");

    driver = new AndroidDriver(remoteUrl, desiredCapabilities);
  }

  @Test
  public void sampleTest() {
    MobileElement el1 = (MobileElement) driver.findElementById("com.xueqiu.android:id/open");
    el1.click();
    MobileElement el2 = (MobileElement) driver.findElementById("android:id/button1");
    el2.click();
    MobileElement el3 = (MobileElement) driver.findElementById("com.xueqiu.android:id/tv_search");
    el3.click();
    MobileElement el4 = (MobileElement) driver.findElementById("com.xueqiu.android:id/search_input_text");
    el4.sendKeys("pdd");
  }

  @After
  public void tearDown() {
    driver.quit();
  }
}

uiautomatorviewer定位元素

不能与appium同时使用

定位技巧

常见的定位策略

  • id (resource-id)
  • accessibilityId (content-desc)
  • xpath 全能支持

定位示例

  • com.xueqiu.android:id/storage_permission_name
  • storage_permission_name
  • //*[@text=‘开启’]
  • //*[contains(@text, ‘手机’)]

错误log定位

[info] e[35m[Appium]e[39m Welcome to Appium v1.10.0
[info] e[35m[Appium]e[39m Appium REST http interface listener started on 0.0.0.0:4723[info] e[35m[HTTP]e[39m e[37m-->e[39m e[37mPOSTe[39m e[37m/wd/hub/sessione[39m
[info] e[35m[HTTP]e[39m e[90m{"desiredCapabilities":{"newCommandTimeout":0,"connectHardwareKeyboard":true,"browserName":"firefox","version":"","javascriptEnabled":true,"platform":"ANY"}}e[39m
[debug] e[35m[MJSONWP]e[39m Calling AppiumDriver.createSession() with args: [{"newCommandTimeout":0,"connectHardwareKeyboard":true,"browserName":"firefox","version":"","javascriptEnabled":true,"platform":"ANY"},null,null]
[debug] e[35m[BaseDriver]e[39m Event 'newSessionRequested' logged at 1550757878960 (22:04:38 GMT+0800 (CST))
[debug] e[35m[BaseDriver]e[39m Event 'newSessionStarted' logged at 1550757878960 (22:04:38 GMT+0800 (CST))
[debug] e[35m[MJSONWP]e[39m Encountered internal error running command: Error: You must include a platformName capability
[debug] e[35m[MJSONWP]e[39m     at AppiumDriver.getDriverForCaps (/Applications/Appium.app/Contents/Resources/app/node_modules/appium/lib/appium.js:174:13)
[debug] e[35m[MJSONWP]e[39m     at AppiumDriver.createSession (/Applications/Appium.app/Contents/Resources/app/node_modules/appium/lib/appium.js:259:32)
[debug] e[35m[MJSONWP]e[39m     at AppiumDriver.executeCommand (/Applications/Appium.app/Contents/Resources/app/node_modules/appium-base-driver/lib/basedriver/driver.js:301:19)
[debug] e[35m[MJSONWP]e[39m     at AppiumDriver.executeCommand (/Applications/Appium.app/Contents/Resources/app/node_modules/appium/lib/appium.js:411:26)
[debug] e[35m[MJSONWP]e[39m     at asyncHandler (/Applications/Appium.app/Contents/Resources/app/node_modules/appium-base-driver/lib/protocol/protocol.js:352:34)
[debug] e[35m[MJSONWP]e[39m     at app.(anonymous function) (/Applications/Appium.app/Contents/Resources/app/node_modules/appium-base-driver/lib/protocol/protocol.js:489:15)
[debug] e[35m[MJSONWP]e[39m     at Layer.handle [as handle_request] (/Applications/Appium.app/Contents/Resources/app/node_modules/express/lib/router/layer.js:95:5)
[debug] e[35m[MJSONWP]e[39m     at next (/Applications/Appium.app/Contents/Resources/app/node_modules/express/lib/router/route.js:137:13)
[debug] e[35m[MJSONWP]e[39m     at Route.dispatch (/Applications/Appium.app/Contents/Resources/app/node_modules/express/lib/router/route.js:112:3)
[debug] e[35m[MJSONWP]e[39m     at Layer.handle [as handle_request] (/Applications/Appium.app/Contents/Resources/app/node_modules/express/lib/router/layer.js:95:5)
[debug] e[35m[MJSONWP]e[39m     at /Applications/Appium.app/Contents/Resources/app/node_modules/express/lib/router/index.js:281:22
[debug] e[35m[MJSONWP]e[39m     at Function.process_params (/Applications/Appium.app/Contents/Resources/app/node_modules/express/lib/router/index.js:335:12)
[debug] e[35m[MJSONWP]e[39m     at next (/Applications/Appium.app/Contents/Resources/app/node_modules/express/lib/router/index.js:275:10)
[debug] e[35m[MJSONWP]e[39m     at logger (/Applications/Appium.app/Contents/Resources/app/node_modules/morgan/index.js:144:5)
[debug] e[35m[MJSONWP]e[39m     at Layer.handle [as handle_request] (/Applications/Appium.app/Contents/Resources/app/node_modules/express/lib/router/layer.js:95:5)
[debug] e[35m[MJSONWP]e[39m     at trim_prefix (/Applications/Appium.app/Contents/Resources/app/node_modules/express/lib/router/index.js:317:13)
[debug] e[35m[MJSONWP]e[39m     at /Applications/Appium.app/Contents/Resources/app/node_modules/express/lib/router/index.js:284:7
[debug] e[35m[MJSONWP]e[39m     at Function.process_params (/Applications/Appium.app/Contents/Resources/app/node_modules/express/lib/router/index.js:335:12)
[debug] e[35m[MJSONWP]e[39m     at next (/Applications/Appium.app/Contents/Resources/app/node_modules/express/lib/router/index.js:275:10)
[debug] e[35m[MJSONWP]e[39m     at /Applications/Appium.app/Contents/Resources/app/node_modules/body-parser/lib/read.js:130:5
[debug] e[35m[MJSONWP]e[39m     at invokeCallback (/Applications/Appium.app/Contents/Resources/app/node_modules/raw-body/index.js:224:16)
[debug] e[35m[MJSONWP]e[39m     at done (/Applications/Appium.app/Contents/Resources/app/node_modules/raw-body/index.js:213:7)
[debug] e[35m[MJSONWP]e[39m     at IncomingMessage.onEnd (/Applications/Appium.app/Contents/Resources/app/node_modules/raw-body/index.js:273:7)
[debug] e[35m[MJSONWP]e[39m     at IncomingMessage.emit (events.js:182:13)
[debug] e[35m[MJSONWP]e[39m     at endReadableNT (_stream_readable.js:1090:12)
[debug] e[35m[MJSONWP]e[39m     at process._tickCallback (internal/process/next_tick.js:63:19)
[info] e[35m[HTTP]e[39m e[37m<-- POST /wd/hub/session e[39me[31m500e[39m e[90m12 ms - 179e[39m
[info] e[35m[HTTP]e[39m e[90me[39m
[info] e[35m[HTTP]e[39m e[37m-->e[39m e[37mDELETEe[39m e[37m/wd/hub/sessione[39m
[info] e[35m[HTTP]e[39m e[90m{}e[39m
[debug] e[35m[HTTP]e[39m No route found. Setting content type to 'text/plain'
[info] e[35m[HTTP]e[39m e[37m<-- DELETE /wd/hub/session e[39me[33m404e[39m e[90m5 ms - 57e[39m
[info] e[35m[HTTP]e[39m e[90me[39m

自定义控件识别

作业

  • 搜索股票,并点击拼多多的股票
  • 添加自选股,添加阿里巴巴股票,验证阿里巴巴已经在自主股中

把代码贴到回复里,如果能够以pytest的形式编写最佳