JavaApp自动化测试系列[v1.0.0][Appium常见问题处理]

tech2022-09-05  116

输入中文

在使用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)

替代swipe方法

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)))"); } }

输入Android按键

/** * 清除编辑框的内容,首先获取编辑框中文本的长度,将光标移动到文本的尾部 * 按退格键直到所有文本被删除 */ public void clearText(String text){ for(int i=0; i<text.length(); i++){ // 123:KEYCODE_MOVE_END 光标移动到末尾 driver.pressKeyCode(123); // 67:KEYCODE_DEL 退格键 driver.pressKeyCode(67); } }

处理Popup Window

Popup Window是一个弹出窗口控件,可以用来显示任意视图,而且会浮动在当前活动的顶部,通过UI Automator Viewer无法识别,通过Hierarchy Viewer才可以识别到Popup Window,解决方案有二

通过Tap方法

WebElement btn = driver.findElementById("showPopupWindowButton"); btn.click(); driver.tap(1,214,475,10);

通过TouchAction方法

WebElement btn = driver.findElementById("showPopupWindowButton"); btn.click(); TouchAction action = new TouchAction(driver); action.press(214,475.release().perform());

代码示例

import java.util.*; import org.openqa.selenium.Point; public class Popuppointer{ public void ball(){ Point x1 = new Point(111,222); Point x2 = new Point(333,444); Point x3 = new Point(555,666); HashMap<Integer, Point> ball = new HashMap<Integer, Point>(); ball.put(1, x1); ball.put(2, x2); ball.put(3, x3); } }

方法调用

driver.tap(1, Popuppointer.ball().get(1).x, Popuppointer.ball().get(1).y, 30); driver.tap(1, Popuppointer.ball().get(2).x, Popuppointer.ball().get(2).y, 30); driver.tap(1, Popuppointer.ball().get(3).x, Popuppointer.ball().get(3).y, 30);

处理长按

package org.davieyang.testscripts; import io.appium.java_client.MobileElement; import io.appium.java_client.TouchAction; import io.appium.java_client.touch.LongPressOptions; import org.openqa.selenium.WebElement; import org.testng.annotations.Test; import io.appium.java_client.android.AndroidDriver; public class LongPress { AndroidDriver<MobileElement> driver; @Test public void testLongPress(){ WebElement dail = driver.findElementByName("拨号"); dail.click(); TouchAction touchAction = new TouchAction(driver); WebElement star = driver.findElementById("star"); touchAction.longPress((LongPressOptions) star).perform(); WebElement result = driver.findElementById("digits"); assert result.getText().equals("P"):"Actual value is "+ result.getText()+"did not match expected value: P"; } }

处理下拉列表框

package org.davieyang.testscripts; import io.appium.java_client.MobileElement; import io.appium.java_client.android.AndroidDriver; import java.util.List; public class LongPressDemo { AndroidDriver<MobileElement> driver; public void checkedTextView(){ // 使用class属性选择所有的单选按钮,放入List @SuppressWarnings("unchecked") List<MobileElement> checkedTextViews = (List<MobileElement>) driver.findElementsByClassName("android.widget.CheckedTextView"); for(MobileElement checkedTextView:checkedTextViews){ if (checkedTextView.getAttribute("name").equals("Ruby")){ if(!checkedTextView.isSelected()){ checkedTextView.click(); } } } } }

处理缩放

// x,y表示偏移量 public void zoom(int x, int y){ MultiTouchAction multiTouch = new MultiTouchAction(driver); int scrHeight = driver.manager().window().getSize().getHeight(); int yOffset = 100; if(y - 100 < 0){ yOffset = y; }else if(y + 100 > scrHeight){ yOffset = scrHeight - y; } TouchAction action1 = new TouchAction(driver).press(x, y).moveTo(x, y - yOffset).release(); TouchAction action2 = new TouchAction(driver).press(x, y).moveTo(x, y + yOffset).release(); multiTouch.add(action1).add(action2); multiTouch.perform(); }

检查元素文本是否可见

@Test public void testElementPresent() throws InterruptedException{ String expectValue = "Text is sometimes displayed"; MobileElement visibleButtonElement = (MobileElement)driver.findElementById("io.selendroid.testapp:id/visibleButtonTest"); visibleButtonElement.click(); By visibileButtonBy = By.id("io.selendroid.testapp:id/visibleTextView"); if(isElementPresented(visibileButtonBy)){ String visibileButtonText = driver.findElement(visibileButtonBy).getAttribute("text"); if(visibileButtonText.contains(expectValue)){ System.out.println("查找控件成功") }else{ Assert.fail("查找控件失败"); } } } public boolean isElementPresented(By by){ try{ isDisplayed = driver.findElement(by).isDisplayed(); }catch(NoSuchElementException e){ isDisplayed = false; } return isDisplayed; }

启动其他App

@Test(description="测试启动App") public void TeststartActivity(){ MobileElement meishiElement = (MobileElement) driver.findElement(By.xpath("xxxxxx")); meishiElement.click(); String androidPackage = "io.selendroid.testapp"; String androidStartActivity = ".HomeScreenActivity"; driver.startActivity(new Activity(androidPackage, androidStartActivity)); }

处理拖动

public void drag(By startElementBy, By endElementBy){ TouchAction action = new TouchAction(driver); MobileElement startElement = MobileElement endElement = action.press(startElement).perform(); action.moveTo(endElement).release().perform(); }

隐式等待

隐式等待有两种方法,implicitly和sleep

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(); } }

implicitlyWait

@Test(description="测试显示等待") public void testImplicitlyWait(){ MobileElement meishiElement = (MobileElement)driver.findElement(By.xpath("")); meishiElement.click(); driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS); try{ driver.findElement(By.xpath()); }catch(NoSuchElementException e){ Assert.fail("没有找到控件") } }

显示等待方法

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"); } }); }

代码中处理adb命令

获取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服务,而不需要手动启动,这无疑给后续的各种执行方式提供了遍历,在代码中启动服务有两种情况

没有指定参数

import io.appium.java_client.service.local.AppiumDriverLocalService; ... AppiumDriverLocalService service = AppiumDriverLocalService.buildDefaultService(); service.start(); ... service.stop();

如果发生异常,那么很可能是使用的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 = AppiumDriverLocalService.buildService(new AppiumServiceBuilder().withargument(GeneralServerFlag.TEMP_DIRECTORY, "temporary path"))

或者

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;

其他参数

使用一些特殊端口new AppiumServiceBuilder().usingPort(4000);使用空闲端口new AppiumServiceBuilder().usingAnyFreePort();使用其他IPnew AppiumServiceBuilder().withIPAddress("127.0.0.1")确定日志文件```new AppiumServiceBuilder().withLogFile(logFile);Node.js执行路径new AppiumServiceBuilder().usingDriverExecutable(nodeJSExecutable);[appium.js<=1.4.16 or main.js>=1.5.0]Main.js执行路径new AppiumServiceBuilder().withAppiumJS(new File(appiumJS));确定服务端的Desired Capabilities DesiredCapabilities serverCapabilities = new DesiredCapabilities(); //setCapabilities... AppiumServiceBuilder builder = new AppiumServiceBuilder().withCapabilities(serverCapabilities); AppiumDriverLocalService service = builder.build(); service.start(); ... service.stop();
最新回复(0)