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

输入中文

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

猜你喜欢

转载自blog.csdn.net/dawei_yang000000/article/details/108374051
今日推荐