Android Espresso: 当按钮保持按下状态时进行断言

5
我是一名对Android上的Espresso测试框架比较新的用户,我遇到了以下问题: 我希望Espresso在一个按钮上执行长按(或其他操作),并且在按钮被按下的同时,检查另一个视图的状态。
以下是(或多或少的)伪代码:
onView(withId(button_id)).perform(pressButtonDown());
onView(withId(textBox_id)).check(matches(withText("Button is pressed")));
onView(withId(button_id)).perform(releaseButton());

我尝试使用MotionEvents.sendDown()和.sendUp()编写2个自定义的Taps——PRESS和RELEASE,但并没有使其正常工作。如果这是正确的方法,我可以发布我目前所拥有的代码。

编辑:

public class PressViewActions
{
    public static ViewAction pressDown(){
        return new GeneralClickAction(HoldTap.DOWN, GeneralLocation.CENTER, Press.THUMB);
    }

    public static ViewAction release() {
        return new GeneralClickAction(HoldTap.UP, GeneralLocation.CENTER, Press.THUMB);
    }
}

以下是Tappers的代码:

public enum HoldTap implements Tapper {

    DOWN {
        @Override
        public Tapper.Status sendTap(UiController uiController, float[] coordinates, float[] precision)
        {
            checkNotNull(uiController);
            checkNotNull(coordinates);
            checkNotNull(precision);

            DownResultHolder res = MotionEvents.sendDown(uiController, coordinates, precision);

            ResultHolder.setController(uiController);
            ResultHolder.setResult(res);

            return Status.SUCCESS;
        }
    },

    UP{
        @Override
        public Tapper.Status sendTap(UiController uiController, float[] coordinates, float[] precision)
        {
            DownResultHolder res = ResultHolder.getResult();
            UiController controller = ResultHolder.getController();

            try {
                if(!MotionEvents.sendUp(controller, res.down))
                {
                    MotionEvents.sendCancel(controller, res.down);
                    return Status.FAILURE;
                }

            }
            finally {
                //res.down.recycle();
            }

            return Status.SUCCESS;
        }
    }
}

我遇到的错误如下所示:
android.support.test.espresso.PerformException: Error performing 'up click' on view 'with id: de.test.app:id/key_ptt'.
...
...
Caused by: java.lang.RuntimeException: Couldn't click at: 281.5,1117.5 precision: 25.0, 25.0 . Tapper: UP coordinate provider: CENTER precision describer: THUMB. Tried 3 times. With Rollback? false

我希望这可以帮助你。
任何帮助和想法都将不胜感激!
非常感谢!

我认为这个总体想法应该可行,发布自定义选项卡的代码。 - Gil Vegliach
1个回答

10

在Android中,一个轻拍事件由两个事件组成,即按下(down)事件和抬起(up/cancel)事件。如果您想将这两个事件分开,则不能创建"轻敲"事件,因为它们已经包含了两个事件。

话虽如此,您的想法是可行的,您只需要使用低级别API,即UiController以及MotionEvents中的帮助方法。但请注意:由于"释放"视图需要先将其保持,如果您没有进行适当的清理处理,您的测试将相互依赖

例如:在一个测试用例中,您按下一个视图,测试失败;然后在另一个测试用例中,您在一个未单击的视图上释放,您的第二个测试将通过,而实际上是不应该通过的。

我在我的Github上上传了一个完整的示例。以下是要点。首先是测试代码:

@Before
public void setUp() throws Exception {
    super.setUp();
    injectInstrumentation(InstrumentationRegistry.getInstrumentation());
    getActivity();
}

@After
public void tearDown() throws Exception {
    super.tearDown();
    LowLevelActions.tearDown();
}

@Test
public void testAssertWhilePressed() {
    onView(withId(R.id.button)).perform(pressAndHold());
    onView(withId(R.id.text)).check(matches(withText("Button is held down")));
    onView(withId(R.id.button)).perform(release());
}

接下来是低级别操作:

public class LowLevelActions {
    static MotionEvent sMotionEventDownHeldView = null;

    public static PressAndHoldAction pressAndHold() {
        return new PressAndHoldAction();
    }

    public static ReleaseAction release() {
        return new ReleaseAction();
    }

    public static void tearDown() {
        sMotionEventDownHeldView = null;
    }

    static class PressAndHoldAction implements ViewAction {
        @Override
        public Matcher<View> getConstraints() {
            return isDisplayingAtLeast(90); // Like GeneralClickAction
        }

        @Override
        public String getDescription() {
            return "Press and hold action";
        }

        @Override
        public void perform(final UiController uiController, final View view) {
            if (sMotionEventDownHeldView != null) {
                throw new AssertionError("Only one view can be held at a time");
            }

            float[] precision = Press.FINGER.describePrecision();
            float[] coords = GeneralLocation.CENTER.calculateCoordinates(view);
            sMotionEventDownHeldView = MotionEvents.sendDown(uiController, coords, precision).down;
            // TODO: save view information and make sure release() is on same view
        }
    }

    static class ReleaseAction implements ViewAction {
        @Override
        public Matcher<View> getConstraints() {
            return isDisplayingAtLeast(90);  // Like GeneralClickAction
        }

        @Override
        public String getDescription() {
            return "Release action";
        }

        @Override
        public void perform(final UiController uiController, final View view) {
            if (sMotionEventDownHeldView == null) {
                throw new AssertionError("Before calling release(), you must call pressAndHold() on a view");
            }

            float[] coords = GeneralLocation.CENTER.calculateCoordinates(view);
            MotionEvents.sendUp(uiController, sMotionEventDownHeldView, coords);
        }
    }
}

1
非常感谢!虽然没有时间进行真正的测试,但它看起来非常有前途!至少,它不会出现任何错误 ;) - stfn
1
谢谢您提供这个答案。它非常有效!不过,我还需要添加一件事情。在ReleaseAction类的perform方法内,我必须在方法底部添加“ sMotionEventDownHeldView = null;”。 - A.Sanchez.SD
我能想到的唯一改进方法就是将“LowLevelActions”重命名为“Finger”。 - Duncan McGregor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接