输入中文
在使用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;
public void scrollToElement(String str){
driver.findElementByAndroidUIAutomator("new UiScrollable(new UiSelector().scrollable(true).instance(0)).ScrollIntoView(new UiSelector().textContains(\"" + str + "\").instance(0)))");
}
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();
- 使用其他IP
new 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();