# Appium知识总结 **Repository Path**: gao-mang/appium-knowledge-summary ## Basic Information - **Project Name**: Appium知识总结 - **Description**: Appium常用的一些API操作及方法总结,包括:搜索、长按、点击、定位、滑动等;adb shell命令封装,包括:查看端口是否占用、常用命令的方法封装、app资源消耗的方法封装等,主要总结是移动端自动化测试时,经常遇到的一些问题的解决方案。 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 2 - **Created**: 2022-08-02 - **Last Updated**: 2022-08-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Appium知识总结 #### 简介 Appium常用的一些API操作及方法总结,包括:搜索、长按、点击、定位、滑动、JS操作等;adb shell命令封装,包括:查看端口是否占用、常用命令的方法封装、app资源消耗的方法封装等,主要总结是移动端自动化测试时,经常遇到的一些问题的解决方案。 #### 搜索 1. API方法 ``` driver.findElement(By.id(“package name:id/btn_search”)).click; ``` 2. 键盘事件 ``` //搜索键:84 driver.sendKeyEvent(84); driver.pressKeyCode(84); //回车键:66 driver.sendKeyEvent(AndroidKeyCode.ENTER); ``` 3. adb命令 ``` public static void adbSearch() { ProcessBuilder pb = new ProcessBuilder(adbPath, "shell", "input", "keyevent", "66"); try { pb.start(); } catch (IOException e1) { e1.printStackTrace(); } } ``` #### 长按 1. 长按操作 ``` // 长按发送图片 TouchAction Action = new TouchAction(driver); Action.longPress(driver.findElement(By.id("package name:id/imageview_msg_image"))).perform(); ``` 2. 元素长按 ``` Action.press(driver.findElement(By.id("package name:id/tv_record_audio_text"))).waitAction(5000).release().perform(); ``` 3. 强制长按 ``` //强制长按10s,推荐使用该方法 Action.longPress(driver.findElement(By.id("package name:id/z6")), 10000).perform(); ``` 4. 坐标长按 ``` Action.longPress(343, 1108).perform(); ``` 5. JS长按 ``` public static void jsLongPress(AndroidDriver driver, By by) { WebElement ele = driver.findElement(by); JavascriptExecutor js = (JavascriptExecutor) driver; HashMap longPress = new HashMap(); longPress.put("ele", ((RemoteWebElement) ele).getId()); js.executeScript("mobile: longClick", longPress); } ``` #### 点击 1. API方法 ``` driver.tap(1, 123, 456, 500); ``` 2. 集成方法 ``` driverUtil.clickByCoordinate(driver, 428, 1681); ``` 3. 封装方法 ``` public void tab(int x, int y) { try { TouchAction ta = new TouchAction(driver); ta.tap(x, y).release().perform(); } catch (Exception e) { e.printStackTrace(); } } ``` 4. 显现点击 ``` //出现就点,不出现就跳过(如:首页广告) By search = By.id("package name:id/btn_search"); element.waitForElement(driver, search, time); element.checkandClick(driver, search); ``` 5. 随机点击 ``` public static void randomClick(AndroidDriver driver, By by) { Random random = new Random();// 定义随机类 elementUtil.elementsExists(driver, by); List picture = driver.findElements(by); int result = random.nextInt(picture.size());// 返回[0,size)集合中的整数,注意不包括size picture.get(result).click(); } ``` 6. 集合点击 ``` public static WebElement FindByElementsClick(AndroidDriver driver, By by, int index, int timeOut) { WebElement ele = null; if (elementUtil.elementsExists(driver, by)) { ele = driver.findElements(by).get(index); ele.click(); } return ele; } ``` 7. className顺序点击 ``` public static void classNameClick(AndroidDriver driver, String className, int index){ elementUtil.elementsExists(driver, By.className(className)); driver.findElementsByClassName(className).get(index).click(); } ``` 8. className倒序点击 ``` public static void classNameDownClick(AndroidDriver driver, String className, int index) { List list = driver.findElementsByClassName(className); elementUtil.elementsExists(driver, By.className(className)); list.get(list.size() - index).click(); } ``` 9. Selector点击 ``` public static void selectorClick(AndroidDriver driver, String uiSelector, int timeOut) { elementUtil.waitForElement(driver, MobileBy.AndroidUIAutomator(uiSelector), timeOut).click(); } ``` 10. 点击相对坐标 ``` public static void clickScreenWithPercentage(AndroidDriver driver, int i, int j) { int width = driver.manage().window().getSize().width; int height = driver.manage().window().getSize().height; try { driver.tap(1, width * i / 100, height * j / 100, 200); } catch (Exception e) { e.printStackTrace(); } } ``` 11. 点击绝对坐标 ``` public static void clickByCoordinate(AndroidDriver driver, int x, int y) { TouchAction ta = new TouchAction(driver); try { ta.tap(x, y).release().perform(); } catch (Exception e) { e.printStackTrace(); } } ``` 12. 双击操作 ``` public void clickDouble(AndroidDriver driver, By by) { WebElement webElement = driver.findElement(by); new Actions(driver).doubleClick(webElement).perform(); } ``` ### 滑动 1. 左滑几次 ``` public static void swipeLeft(AndroidDriver driver, int times) { for (int i = 0; i < times; i++) { timeUtil.sleep(800); driverUtil.swipeWithPercent(driver, 80, 50, 20, 50); timeUtil.sleep(1200); } } ``` 2. 右滑几次 ``` public static void swipeRight(AndroidDriver driver, int times) { for (int i = 0; i < times; i++) { timeUtil.sleep(800); driverUtil.swipeWithPercent(driver, 20, 50, 80, 50); timeUtil.sleep(1200); } } ``` 3. 上滑几次 ``` public static void swipeUp(AndroidDriver driver, int times) { for (int i = 0; i < times; i++) { timeUtil.sleep(800); driverUtil.swipeWithPercent(driver, 50, 80, 50, 20); timeUtil.sleep(1200); } } ``` 4. 下滑几次 ``` public static void swipeDown(AndroidDriver driver, int times) { for (int i = 0; i < times; i++) { timeUtil.sleep(800); driverUtil.swipeWithPercent(driver, 50, 20, 50, 80); timeUtil.sleep(1200); } } ``` 5. 元素右滑 ``` public static void elementRightSwipe(AndroidDriver driver, By by) { WebElement element = driver.findElement(by); // 获取控件起始×坐标 int xAxisStartPoint = element.getLocation().getX(); System.out.println("起始坐标:" + xAxisStartPoint); // 获取控件最大宽度 int xAxisEndPoint = xAxisStartPoint + element.getSize().getWidth(); System.out.println("结束坐标:" + xAxisEndPoint); // 获取控件的高度 int yAxis = element.getLocation().getY(); System.out.println("高度为:" + yAxis); driver.swipe(xAxisEndPoint - 20, yAxis + 20, xAxisStartPoint + 10, yAxis + 20, 1000); } ``` 6. 元素左滑 ``` public static void elementLiftSwipe(AndroidDriver driver, By by) { WebElement element = driver.findElement(by); int xAxisStartPoint = element.getLocation().getX(); int xAxisEndPoint = xAxisStartPoint + element.getSize().getWidth(); int yAxis = element.getLocation().getY(); driver.swipe(xAxisEndPoint + 10, yAxis + 20, xAxisStartPoint - 20, yAxis + 20, 1000); } ``` 7. JS滑动 ``` public static void jsSwipe(AndroidDriver driver, double startX, double startY, double endX, double endY, int times) { JavascriptExecutor jse = (JavascriptExecutor) driver; HashMap swipeObj = new HashMap(); swipeObj.put("startX", startX); swipeObj.put("startY", startY); swipeObj.put("endX", endX); swipeObj.put("endY", endY); for (int i = 0; i < times; i++) { jse.executeScript("mobile: swipe", swipeObj); } } ``` 8. 比例滑动 ``` public static void swipeWithPercent(AndroidDriver driver, int beginXpercent, int beginYpercent, int endXpercent, int endYpercent) { Assert.assertFalse("参数传入错误", (beginXpercent <= 0 || beginXpercent >= 100) && (beginYpercent <= 0 || beginYpercent >= 100) && (endXpercent <= 0 || endXpercent >= 100) && (endYpercent <= 0 || endYpercent >= 100)); int x = driver.manage().window().getSize().width; int y = driver.manage().window().getSize().height; try { driver.swipe(x / 100 * beginXpercent, y / 100 * beginYpercent, x / 100 * endXpercent, y / 100 * endYpercent, 300); Thread.sleep(1200); } catch (Exception e) { e.printStackTrace(); } } ``` 9. 拖拽元素 ``` public void swipeElement(AndroidDriver driver, By by1, By by2) { new TouchAction(driver).longPress(driver.findElement(by1)).moveTo(driver.findElement(by2)).release().perform(); } //移动到某个元素上 public void movetoElement(AndroidDriver driver, By by) { new TouchAction(driver).moveTo(driver.findElement(by)); } ``` 10. Touch滑动 ``` public void appiumSwipe(AndroidDriver driver, int x, int y, int x1, int y1) { try { // action类分解动作,先长按,再移动到指定位置,再松开 new TouchAction(driver).longPress(x, y).moveTo(x1, y1).release().perform(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } ``` ### 手机驱动未安装 ``` **报错提示:** org.openqa.selenium.SessionNotCreatedException: A new session could not be created. (Original error: Device ZX1C622BU3 was not in the list of connected devices) (WARNING: The server did not provide any stacktrace information) ``` 解决方案:可能手机驱动未安装成功或为安装,利用电脑自动安装和360手机助手安装驱动 ### 未连接到手机 ``` **报错提示:** error: Failed to start an Appium session, err was: Error: Command failed: C:\Windows\system32\cmd.exe /s /c “D:\android-sdk-windows\platform-tools\adb.exe -s adb server version (32) doesn’t match this client (36); killing… wait-for-device”  error: could not install smartsocket listener: cannot bind to 127.0.0.1:5037: ``` 解决方案:没有打开USB调试、5037端口号占用、进程占用、断开USB连接再重新连接 ### 端口占用 ``` // 查看端口是否被占用 public static boolean isPortUsing(int port) { boolean flag = false; // 如果端口没有被占用,就返回false,反之,返回true try { InetAddress theAddress = InetAddress.getByName("127.0.0.1"); Socket socket = new Socket(theAddress, port); flag = true; socket.close(); } catch (IOException e) { } return flag; } ``` - 软件占用,比如:数据库等软件 例如:运行时Appium中报错 error: Unhandled error: Error: connect ECONNREFUSED ``` 解决方案:mysql和node.js使用的同一个端口,换一个端口号就行 ``` - 进程占用,比如:360手机助手、应用宝、豌豆荚等进程 ``` 解决方案:关闭360手机助手等软件;任务管理器中杀掉进程,比如adb.exe adb kill-server和adb start-server杀掉和开启 ``` ### appWaitActivity ``` **报错提示:** org.openqa.selenium.SessionNotCreatedException: A new session could not be created. (Original error: com.qihoo360.contacts/.ui.mainscreen.MainTabBase never started. Current: com.qihoo360.contacts/.danmu.ui.DanmuSplashActivity) (WARNING: The server did not provide any stacktrace information) ``` 解决方案: ``` capabilities.setCapability("appWaitActivity",".ICloudLeaderActivity"); capabilities.setCapability("appWaitActivity", ".Main"); ``` Package有时候也会不一样需要多添加一句capabilities.setCapability("appWaitPackage", "报错current那一个"); - 获取应用的启动时延时,可能会减少或添加其他的appWaitActivity; - 在进行无缓存业务运行时,运行前要手动在设置应用程序中清理缓存,也有可能要添加appWaitPackage和AppWaitActivity(前提条件:循环运行时经常报错)。 ### 会话时间(60s) ``` //收到下一条命令的超时时间,超过时间会自动关闭session,默认60s capabilities.setCapability("newCommandTimeout", 360000); ``` ### 中文输入 ``` capabilities.setCapability("unicodeKeyboard", true); capabilities.setCapability("resetKeyboard", true); ``` - 输入不稳定解决方法:利用adb shell命令调用其他输入法 ``` C:\Users\LITP>adb shell ime list -s com.baidu.input_mi/.ImeService com.sohu.inputmethod.sogou.xiaomi/.SogouIME io.appium.android.ime/.UnicodeIME ``` - 封装方法: ``` private void excuteAdbShell(String s) { Runtime runtime=Runtime.getRuntime(); try{ runtime.exec(s); }catch(Exception e){ System.out.println("执行命令:"+s+"出错"); } } ``` - 调用方式: ``` excuteAdbShell("adb shell ime set com.sohu.inputmethod.sogou.xiaomi/.SogouIME"); //再次点击输入框,调取键盘,软键盘被成功调出 clickView(page.getSearch()); //点击右下角的搜索,即ENTER键 pressKeyCode(AndroidKeyCode.ENTER); //再次切回 输入法键盘为Appium unicodeKeyboard excuteAdbShell("adb shell ime set io.appium.android.ime/.UnicodeIME"); ``` ### 时间等待 - 显性等待: ``` WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10)); element = wait.Until((d) => { return driver.FindElement(By.Id("userName")); }); ``` - 隐性等待: ``` driver.manage().timeouts().implicitlyWait(30,TimeUnit.SECONDS); ``` - 线程等待 ``` Thread.sleep(2000); ``` ### 运行时错误 - 接触不良 ``` **手机断开连接需要重新连接,报错提示:** Error: Command failed: C:\Windows\system32\cmd.exe /s /c “D:\android-sdk-windows\platform-tools\adb.exe -s 8806a0b0 shell “echo ‘ready‘“error: unknown host service ``` - 会话占用 ``` **报错提示:** error: Failed to start an Appium session, err was: Error: Requested a new session but one was in progress ``` 之前的会话没有关闭,然后你又运行了测试实例,也没有设置覆盖.  解决办法:  1. 重新停止appium服务,开启Appium服务 2. 在Genarel Setting那里设置覆盖Session,重启Appium 3. 测试结束在方法里加:driver.quit() ### Ctrl+V操作 ``` public static void CtrlVPaste(WebElement el){ el.click(); //获取焦点 //Ctrl+V组合操作 driver.sendKeyEvent(50,AndroidKeyMetastate.META_CTRL_ON); } ``` ### 刷新操作 ``` public void refreshWithCtrlF5(AndroidDriver driver) { driver.getKeyboard().sendKeys(Keys.CONTROL, Keys.F5); } ``` ### 后台切换 ``` driver.runAppInBackground(5); driver.sendKeyEvent(3);//Home键 driver.sendKeyEvent(AndroidKeyCode.HOME); ``` ### Classname定位 - 通用方法 ``` List lis = driver.findElementsByClassName("Android.widget.ImageView");//获取ImageView的所有元素 WebElement targetEle = lis.get(0);//获取列表中第一个元素 //封装方法 public List getAllImges(){ List lis = driver.findElementsByClassName(AndroidClassName.IMAGEVIEW); return lis; } ``` - 特用方法 ``` List lists = driver.findElementsByAndroidUIAutomator("new UiSelector().className("+"android.widget.ImageView"+").index(4)"); //封装方法 public List getElementsByClassAndIndex(String classname,int index){ List lis =null; lis = driver.findElementsByAndroidUIAutomator("new UiSelector().className("+classname+").index("+index+")"); return lis; } ``` ``` List lister = driver.findElementsByAndroidUIAutomator("new UiSelector().className("+"android.widget.ImageView"+").index(4).clickable(true)"); //封装方法 public List getElementsByClassAndIndexAndClickable(String classname,int index){ List lis =null; lis = driver.findElementsByAndroidUIAutomator("new UiSelector().className("+classname+").index("+index+").clickable(true)"); return lis; } ``` ### Xpath插件 - 将lazy-uiautomatorviewer 源码编译生成的jar包uiautomatorviewer.jar拷贝到安卓安装目录下的 \android-sdk\tools\lib 文件夹中替换掉原来的uiautomatorviewer.jar包。 - 参考文献:[APP自动化框架LazyAndroid使用手册](https://blog.csdn.net/kaka1121/article/details/53301517) ### Xpath-contains方法 ``` driver.findElement(By.xpath("//android.widget.ImageView[contains(@index,'4')]")).click(); List pic = driver.findElements(By.xpath("//android.widget.ImageView[contains(@NAF,'true')]")); pic.get(4).click(); //XPath使用类,文本属性和资源id:(有概率点错) driver.findElement(By.xpath("//android.widget.Button[contains(@resource-id,'digit5') and @text='5']")); ``` ``` //封装方法 public List getWebElementList(By by) { return driver.findElements(by); } public void details() throws InterruptedException{ List weList = null; weList = getWebElementList(By.tagName("android.widget.EditView")); for (WebElement we : weList) { if(we.getText().toLowerCase().contains("login")){ we.sendKeys("Hello"); } } } ``` ``` //集成方法 By Smallvideo = By.xpath(element.getXpathString("android.widget.TextView", "text", "小视频")); element.waitForElement(driver, Smallvideo , 15); element.checkandClick(driver, Smallvideo ); By backup = By.xpath(element.getXpathString("android.widget.TextView", "text", "安全备份")); if (element.waitForElement(driver, backup, 15)) { element.checkandClick(driver, backup); } else { System.out.println("作废 - " + i); i--; continue; } ```