在使用Appium做移动端自动化测试的时候,会遇到输入中文的问题,大致处理方式有以下几种
将代码文件另存为UTF-8格式在Desired Capabilities中增加两个属性unicodeKeyboard和resetKeyboard capabilities.setCapability("unicodeKeyboard", true);capabilities.setCapability("resetKeyboard", true);Appium通过swipe函数处理滑动操作
public void swipe(int startx, int starty, int endx, int endy, int duration){ TouchAction touchAction = new TouchAction(this); // Appium把press-wait-move-release转换成滑动 touchAction.press(startx, starty).waitAction(duration).moveTo(endx, endy).release(); touchAction.perform(); }为了更好的兼容不同分辨率的移动设备,需要在滑动前获取屏幕分辨率
int width = driver.manager().window().getSize().width; int height = driver.manager().window().getSize().height;然后根据分辨率滑动
// 向上滑动 driver.swipe(width/2, height*3/4, width/2, height/4, duration) // 向下滑动 driver.swipe(width/2, height/4, width/2, height*3/4, duration) // 向左滑动 driver.swipe(width/4, height/2, width*3/4, height/2, duration) // 向上滑动 driver.swipe(width*3/4, height/2, width/4, height/4, duration)appium java-client 5.0以后移除了swipe方法,可以使用如下方式实现原来的swipe操作
package org.davieyang.testscripts; import java.time.Duration; import io.appium.java_client.TouchAction; import io.appium.java_client.android.AndroidDriver; import io.appium.java_client.touch.WaitOptions; import io.appium.java_client.touch.offset.PointOption; public class SwipeDemo { static Duration duration=Duration.ofSeconds(1); public void swipeToUp(AndroidDriver driver) throws InterruptedException { try{ int width = driver.manage().window().getSize().width; int height = driver.manage().window().getSize().height; TouchAction action1=new TouchAction(driver).press(PointOption.point(width/2, height*3/4)).waitAction(WaitOptions.waitOptions(duration)) .moveTo(PointOption.point(width/2, height/4)).release(); action1.perform(); }catch (Exception e){ e.printStackTrace(); } } public void swipeToDown(AndroidDriver driver) throws InterruptedException { try{ int width = driver.manage().window().getSize().width; int height = driver.manage().window().getSize().height; TouchAction action2=new TouchAction(driver).press(PointOption.point(width/2, height/4)).waitAction(WaitOptions.waitOptions(duration)) .moveTo(PointOption.point(width/2, height*3/4)).release(); action2.perform(); }catch (Exception e){ e.printStackTrace(); } } public void swipeToLeft(AndroidDriver driver) throws InterruptedException { try{ int width = driver.manage().window().getSize().width; int height = driver.manage().window().getSize().height; TouchAction action3=new TouchAction(driver).press(PointOption.point(width*3/4, height/2)).waitAction(WaitOptions.waitOptions(duration)) .moveTo(PointOption.point(width/4,height/2)).release(); action3.perform(); }catch (Exception e){ e.printStackTrace(); } } public void swipeToRight(AndroidDriver driver) throws InterruptedException { try{ int width = driver.manage().window().getSize().width; int height = driver.manage().window().getSize().height; TouchAction action4=new TouchAction(driver).press(PointOption.point(width / 4, height / 2)).waitAction(WaitOptions.waitOptions(duration)) .moveTo(PointOption.point(width*3/4,height/2)).release(); action4.perform(); }catch (Exception e){ e.printStackTrace(); } } }早期的Appium提供了Scroll方法实现滚动,新版本Appium取消了Scroll方法,但可以通过如下方法实现滚动
package org.davieyang.testscripts; import io.appium.java_client.MobileElement; import io.appium.java_client.android.AndroidDriver; public class ScrollDemo { AndroidDriver<MobileElement> driver; /** * UiSelector().scrollable(true).instance(0) 表示找到一个可滑动的对象,通过判断scrollable属性是否为true进行查找 * scrollIntoView 滑动到匹配的selector控件,如果没有匹配到,则停留在滑动列表的最下方 * 在new UiSelector().text(String).instance(0) 待匹配的selector控件中,设置text属性值为指定的string * UiSelector().textContains(string).instance(0) 待匹配的selector控件,指定text属性值包含指定的string * @param str 字符串参数 */ public void scrollToElement(String str){ driver.findElementByAndroidUIAutomator("new UiScrollable(new UiSelector().scrollable(true).instance(0)).ScrollIntoView(new UiSelector().textContains(\"" + str + "\").instance(0)))"); } /** * * @param str */ public void scrollToExactElement(String str){ driver.findElementByAndroidUIAutomator("new UiScrollable(new UiSelector().scrollable(true).instance(0)).scrollIntoView(new UiSelector().textContains(\"" + str + "\").instance(0)))"); } }Popup Window是一个弹出窗口控件,可以用来显示任意视图,而且会浮动在当前活动的顶部,通过UI Automator Viewer无法识别,通过Hierarchy Viewer才可以识别到Popup Window,解决方案有二
隐式等待有两种方法,implicitly和sleep
这是最直接的方式,设置固定的等待时间Thread.sleep(3000);
@Test(description = "sleep简单封装") private boolean testisElementPresent(By by)throws InterruptedException{ try{ Thread.sleep(1000); driver.fineElement(by); return true; }catch(NoSuchElementException e){ return false; } } @Test(description = "sleep封装") public static void testwaitTimer(int units, int mills){ DecimalFormat df = new DecimalFormat("###.##"); double totalSeconds=((double)units*mills)/1000; System.out.println("Explicit pause for" + df.format(totalSeconds)+" seconds divided by "+units+"units of time:"); try{ Thread.currentThread(); int x=0; while(x<units){ Thread.sleep(mills); System.out.println("."); x = x+1; } System.out.println("\n"); }catch(InterruptedException e){ e.printStackTrace(); } }Appium中提供了AppiumFluentWait来实现显示等待,AppiumFluentWait继承自FluentWait,AppiumFluentWait的until可以使Predicate也可以是Function,Function的返回值种类较多,可以是Object或者Boolean,而Predicate只能返回Boolean类型
@Test(description="测试FluentWait") public void testFluent(){ MobileElement mobileElement = (MobileElement)driver.findElement(By.xpath("xxxx")); new AppiumFluentWait<MobileElement>(mobileElement).withTimeout(10, TimeUnit.SECONDS).pollingEvery(100,TimeUnit.MILLISECONDS).until(new Function<MobileElement, Boolean>(){ @Override public Boolean apply(MobileElement element){ return element.getText().endsWith("xxxx"); } }); }获取CPU的性能指标
package org.davieyang.testscripts; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class adbInCodeDemo { public static void GetCpu(String packageName) throws IOException{ Runtime runtime = Runtime.getRuntime(); Process proc = runtime.exec("adb shell dumpsys cpuinfo $" + packageName); try{ if (proc.waitFor()!=0){ System.err.println("exit value = " + proc.exitValue()); } BufferedReader in = new BufferedReader(new InputStreamReader(proc.getInputStream())); String line = null; String totalCpu = null; String userCpu = null; String kernalCpu = null; while ((line=in.readLine())!=null) { if(line.contains(packageName)){ System.out.println(line); totalCpu = line.split("%")[0].trim(); userCpu = line.substring(line.indexOf(":")+1, line.indexOf("% user")).trim(); kernalCpu = line.substring(line.indexOf("+")+1, line.indexOf("% kernel")).trim(); System.out.printf("totalCpu的值为:%s%n", totalCpu); System.out.printf("userCpu的值为:%s%n", userCpu); System.out.printf("kernalCpu的值为:%s%n", kernalCpu); } } }catch (InterruptedException e){ System.err.println(e); }finally { try{ proc.destroy(); }catch (Exception ee){ System.out.println("Say something!"); } } } }使用AppiumDriverLocalService可以在代码中启动Appium服务,而不需要手动启动,这无疑给后续的各种执行方式提供了遍历,在代码中启动服务有两种情况
如果发生异常,那么很可能是使用的node.js实例与环境变量里设置的实例不一致,也可能是Appium node服务导致的(Appium.js版本小于等于1.4.16,Main.js版本大于等于1.5.0)这种情况下可以设置NODE_BINARY_PATH(node所在路径)和APPIUM_BINARY_PATH(Appium.js和Main.js的执行路径)到环境变量中或者直接在程序中指定
System.setProperty(AppiumServiceBuilder.NODE_PATH, "node path") System.setProperty(AppiumServiceBuilder.APPIUM_PATH, "appium.js path or main.js path") AppiumDriverLocalService service = AppiumDriverLocalService.buildDefaultService();或者
import io.appium.java_client.service.local.AppiumDriverLocalService; import io.appium.java_client.service.local.AppiumServiceBuilder; import io.appium.java_client.service.local.flags.GeneralServerFlag; ... AppiumDriverLocalService service = new AppiumServiceBuilder().withArgument(GeneralServerFlag.TEMP_DIRECTORY, "temporary path");需要导入一下3个包
import io.appium.java_client.service.local.flags.GeneralServerFlag; import io.appium.java_client.service.local.flags.AndroidServerFlag; import io.appium.java_client.service.local.flags.iOSServerFlag;