手势操作

通过上一章节的学习,我们已经掌握了编写脚本的基本技巧,接下来我们要通过本章节的学习做一个有意思的手势解锁,如果时间充足,可以尝试针对地图类APP来实践一下本节课内容。 预备知识:手机屏幕二维坐标系


你平时都使用过什么手势?

聊起这个话题,相信你们在日常使用手机中都是有使用过如下的手势

上下左右滑屏(慢速)
快速滑动
滚动(滚动和滑动有什么区别?
放大缩某个控件

相信大多数时间我们所使用的手势都是滑动,因为大多数APP基本都有滑动的手势, 比如安装好的APP出现引导页,列表内容单页不够显示,而滑动是一个比较友好的方式。 这样我们在自动化APP的时候就会遇到很多种滑动的情况,接下来让我们深入的了解一下这些手势。


appium中提供的手势

Java client 默认提供的API

  • swipe --- 滑动
  • zoom --- 放大
  • pinch ---缩小
  • tap --- 点点(请思考tap和click的区别?)
  • scroll ----滚动

swipe方法详解

滑动和滚动的区别你想清楚了吗? 首先让我们了解一下appium 自带的滑动方法swipe,swipe方法的定义如下:

public void swipe(int startx,
         int starty,
         int endx,
         int endy,
         int duration)

swipe方法共有五个参数,其中参数依次代表起始点x、y坐标,终点x、y坐标和滑动时间,单位毫秒。 如果想滑的快,就把时间设置小,反之。

swipe方法的本质是封装TouchAction,通过touchAction先按下起始点坐标,再间隔时间之后移动到终点坐标并释放。
具体实现方法如下:

  TouchAction touchAction = new TouchAction(this);
        // appium converts press-wait-moveto-release to a swipe action
        touchAction.press(startx, starty).waitAction(duration)
                .moveTo(endx, endy).release();
        touchAction.perform();

提示:swipe 的高级用法,swipe 可以进一步封装,比如一些MobileElement需要滑动屏幕才能出现,这个时候就可以封装一个滚动到这些MoblieElement出现为止的方法。

zoom方法详解

通过了解swipe方法实际上zoom方法也类似 首先看一下该方法的定义:

void zoom(WebElement el);

void zoom(int x, int y);

zoom方法有两个,一个是传入一个WebElement,另外一个传入起始点坐标

zoom(x,y) 方法其实是由两个 MultiTouchAction实现的 首先根据你传入的坐标点确定偏移量,然后创建两个Action分别相反方向移动,同时间释放。

    MultiTouchAction multiTouch = new MultiTouchAction(this);
    int scrHeight = manage().window().getSize().getHeight();
    int yOffset = 100;

    if (y - 100 < 0) {
      yOffset = y;
    } else if (y + 100 > scrHeight) {
      yOffset = scrHeight - y;
    }

    TouchAction action0 = new TouchAction(this).press(x, y).moveTo(x, y - yOffset).release();
    TouchAction action1 = new TouchAction(this).press(x, y).moveTo(x, y + yOffset).release();

    multiTouch.add(action0).add(action1);

    multiTouch.perform();

另外一个zoom方法也类似,可自己研究一下。

pinch方法详解

pinch 方法做的事情个zoom刚好相反但使用方法是一样的

void pinch(WebElement el);
void pinch(int x, int y);

tap方法详解

之前有提到过一个问题,tap和click的行为到底有什么不同? 对于tap单一finger操作除了和click是通用的,但如不止一个finger那就不同咯。

先看一下tap方法

void tap(int fingers, int x, int y, int duration);
void tap(int fingers, WebElement element, int duration);

第一个的使用方法传入finger和要tap的坐标点还有间隔时间
第二个的使用方法是传finger和要tap的对象还有间隔时间

tap方法的实现代码如下:

      MultiTouchAction multiTouch = new MultiTouchAction(this);

        for (int i = 0; i < fingers; i++) {
            multiTouch.add(createTap(x, y, duration));
        }

        multiTouch.perform();

scroll方法详解

scroll是一个抽象的方法,iOS和Android各自实现

 scrollTo(String text)  
 scrollToExact(String text)

那么问题就来了,这两者有什么样的区别?

首先两者传入的都是字符串,而这个字符串是对所要滚动到的对象的描述,不同之处在于scrollTo是包含contains,而scrollToExact是equals,精确匹配。

提示:scroll可以滚动到查找某个MobileElement出现,和swipe不同的是这个滚动只能是当前屏幕,不能跨页面


自定义手势

注意在早期的Appium版本中,可使用mobile command 自定一些手势,比如swipe,flick 但是在最近的Appium中已经不再支持这些mobile command

Tried to execute non-existent mobile command 'swipe'. Most mobile commands have been ported to official client library methods. Please check your Appium library for more information and documentation


如果你在网上看到类似这样的代码,切记已经完全无效:


        JavascriptExecutor js = (JavascriptExecutor) driver;
        HashMap<String, String> swipeObject = new HashMap<String, String>();
        swipeObject.put("startX", "100");
        swipeObject.put("startY", "400");
        swipeObject.put("endX", "100");
        swipeObject.put("endY", "200");
        swipeObject.put("duration", "400");
        js.executeScript("mobile: swipe", swipeObject);

而这里我讲的自定义手势,比如向上向下滑动,是根据swipe的原理设计出来的

还可以进一步封装滑动到某MobleElement 出现 举个例子,向上滑动屏幕 :


    /**
     * This Method for swipe up
     * 
     * @author Young
     * @param driver
     * @param during
     */
    public void swipeToUp(AndroidDriver<MobileElement> driver, int during)
    {
        int width = driver.manage().window().getSize().width;
        int height = driver.manage().window().getSize().height;
        driver.swipe(width / 2, height * 3 / 4, width / 2, height / 4, during);
    }

首先获取你设备的宽度和高度,然后根据二维坐标系进行滑动,同理你可以试着封装一个手势。


一个手势解锁的demo

核心代码如下:

    @Test 
    public void GustureLockerTest() throws InterruptedException
    {
        driver.manage().timeouts().implicitlyWait(20, TimeUnit.SECONDS);

        MobileElement button = driver.findElementByAndroidUIAutomator("new UiSelector().text(\"设置手势密码\")");
        button.tap(1, 1000);
        // get all the items of gesture locker
        List<MobileElement> items = driver.findElementsByClassName("android.widget.ImageView");

        for (MobileElement item : items)
        {
            /**
             * 0 1 2 3 4 5 6 7 8
             */
            item.click();
        }

        // create a Z from 0->1->2->4->6->7->8
        TouchAction touches = new TouchAction(driver);
        touches.press(items.get(0)).waitAction(1000).moveTo(items.get(1)).waitAction(1000).moveTo(items.get(2))
                .waitAction(1000).moveTo(items.get(4)).moveTo(items.get(6)).waitAction(1000).moveTo(items.get(7))
                .waitAction(1000).moveTo(items.get(8)).release();
        touches.perform();
        Thread.sleep(1000);
        touches.press(items.get(0)).waitAction(1000).moveTo(items.get(1)).waitAction(1000).moveTo(items.get(2))
                .waitAction(1000).moveTo(items.get(4)).release();
        touches.perform();
        Assert.assertTrue(driver.findElementByName("与上一次绘制不一致,请重新绘制").isDisplayed());

    }

总结

本章我们都学习了常见手势的使用方法,其中appium自带的

  • swipe
  • tap
  • zoom
  • pinch
  • scroll

掌握了这些,可以针对一个地图类型的app实践一下

   @Test 
    public void GustureLockerTest() throws InterruptedException
    {
        driver.manage().timeouts().implicitlyWait(20, TimeUnit.SECONDS);
        int width = driver.manage().window().getSize().width;
        int height = driver.manage().window().getSize().height;
        Thread.sleep(15000);

        // swipe to right
        driver.swipe(width / 4, height / 2, width * 3 / 4, height / 2, 300);
        // swipe to left
        driver.swipe(width * 3 / 4, height / 2, width / 4, height / 2, 300);

        Thread.sleep(5000);
        driver.pinch(width / 4, height / 4);
        Thread.sleep(5000);
        driver.zoom(width / 4, height / 4);
        Thread.sleep(5000);
        // tap
        driver.tap(2, width / 2, height / 2, 1000);
    }

该app的下载地址:

https://github.com/tobecrazy/LuoHe/raw/master/app/build/outputs/apk/app-debug.apk

相关资料

demo所使用的APK
https://github.com/tobecrazy/appiumDemo/raw/master/apps/Locker.apk

官方资料: http://appium.io/slate/en/master/?java#key-event