使用 Android UI Automator 测试辅助显示器上的内容

Ben*_* P. 5 android ui-automation android-uiautomator

我有一个准系统应用程序来测试对多个显示器的支持。我的设置是运行 Android 8.1.0(“主显示器”)的定制 Android 平板电脑,通过 HDMI(提供视频信号)和 USB(提供触摸事件)连接到触摸屏(“辅助显示器”)。

该应用程序包含一个活动,显示“Hello World!” 在主显示器上,但也利用DisplayManager并在辅助显示器WindowManager上添加一个计数器和两个+/-按钮:

正常运行应用程序并与辅助显示器上的按钮交互按预期工作。

现在,我想使用UI Automator来单击+按钮并验证计数器是否记录了正确的值。这似乎是不可能的。有谁知道我怎么能做到这一点?

或者,如果 UI Automator 不是适合这项工作的工具,但还有其他一些工具可以让我为在辅助显示器上显示内容的应用程序编写端到端的黑盒式测试,我很高兴得到建议。


我调查过的一些事情

我已经使用该uiautomatorviewer工具来检查我的应用程序的布局层次结构。使用此工具只能看到主显示器上的内容:

在此处输入图片说明

我习惯于UiDevice.dumpWindowHierarchy()获取设备上所有内容的文本转储。仅转储主设备上的内容,尽管包括有关系统窗口(如状态栏和导航栏)的信息。

我已经使用过adb shell dumpsys window(同时使用tokenswindows命令)。将显示有关我在辅助显示器上创建的窗口的信息,但我似乎无法通过 Ui Automator 访问此窗口:

Display #1
WindowToken{551f82f android.os.BinderProxy@87f65ac}:
  windows=[Window{e40e275 u0 com.example.stackoverflow}]
  windowType=2038 hidden=false hasVisible=true
Run Code Online (Sandbox Code Playgroud)
Window #10 Window{20ac4ce u0 com.example.stackoverflow}:
  mDisplayId=1 mSession=Session{91fdd93 8079:u0a10089} mClient=android.os.BinderProxy@a3e65c9
  mOwnerUid=10089 mShowToOwnerOnly=true package=com.example.stackoverflow appop=SYSTEM_ALERT_WINDOW
  mAttrs=WM.LayoutParams{(0,0)(fillxfill) sim=#20 ty=2038 fl=#1280480 colorMode=0}
  Requested w=1280 h=800 mLayoutSeq=908
  mBaseLayer=121000 mSubLayer=0 mAnimLayer=121000+0=121000 mLastLayer=121000
  mToken=WindowToken{a42e219 android.os.BinderProxy@a3e65c9}
  ...
Run Code Online (Sandbox Code Playgroud)

相关代码示例

将内容添加到辅助显示(在我的 ActivityonResume()方法中):

DisplayManager manager = (DisplayManager) getApplicationContext().getSystemService(Context.DISPLAY_SERVICE);
Display display = manager.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION)[0];
Context displayContext = getApplicationContext().createDisplayContext(display);
WindowManager windowManager = (WindowManager) displayContext.getSystemService(Context.WINDOW_SERVICE);

LinearLayout root = new LinearLayout(displayContext);
WindowManager.LayoutParams params = createLayoutParams();
windowManager.addView(root, params);

View.inflate(root.getContext(), R.layout.overlay, root);
root.findViewById(R.id.minus).setOnClickListener(v -> decrementCounter());
root.findViewById(R.id.plus).setOnClickListener(v -> incrementCounter());
Run Code Online (Sandbox Code Playgroud)

我正在使用应用程序上下文而不是活动上下文,以便辅助显示上的内容不依赖于创建它的活动的生命周期。从理论上讲,我可以导航到其他活动,而此内容将保留在辅助显示器上并保持交互性。

创建LayoutParams对象:

private WindowManager.LayoutParams createLayoutParams() {
    return new WindowManager.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT,
            0, 0,
            WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
            0,
            PixelFormat.OPAQUE
    );
}
Run Code Online (Sandbox Code Playgroud)

Ömü*_*giz 0

Espresso可用于测试您的应用程序。好处是,您无需选择使用哪个。您可以同时使用它们。Espresso 在单独的线程中运行,与其他测试框架相比,它的速度确实很快。在测试中使用它们。

Espresso 测试清楚地陈述期望、交互和断言,而不会受到样板内容、自定义基础设施或混乱的实现细节的干扰。

Espresso 测试运行速度最佳!它可以让您将等待、同步、睡眠和轮询抛诸脑后,同时在应用程序 UI 静止时进行操作和断言。

这是 Espresso 的Multiprocess,这是Espresso 的备忘单

将 Espresso 与 ActivityTestRule 结合使用

以下部分介绍如何以 JUnit 4 风格创建新的 Espresso 测试并使用 ActivityTestRule 减少需要编写的样板代码量。通过使用 ActivityTestRule,测试框架在使用 @Test 注释的每个测试方法之前以及使用 @Before 注释的任何方法之前启动被测活动。测试完成后,框架会处理关闭活动,并且运行所有用 @After 注释的方法。

package com.example.android.testing.espresso.BasicSample;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;

@RunWith(AndroidJUnit4.class)
@LargeTest
public class ChangeTextBehaviorTest {

    private String stringToBetyped;

    @Rule
    public ActivityTestRule<MainActivity> activityRule
            = new ActivityTestRule<>(MainActivity.class);

    @Before
    public void initValidString() {
        // Specify a valid string.
        stringToBetyped = "Espresso";
    }

    @Test
    public void changeText_sameActivity() {
        // Type text and then press the button.
        onView(withId(R.id.editTextUserInput))
                .perform(typeText(stringToBetyped), closeSoftKeyboard());
        onView(withId(R.id.changeTextBt)).perform(click());

        // Check that the text was changed.
        onView(withId(R.id.textToBeChanged))
                .check(matches(withText(stringToBetyped)));
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您想了解更多信息,这里是测试文档

  • 谢谢你的回答。然而,Espresso 不适合我,因为它不支持端到端测试。我有两个问题:我的测试必须跨越多个活动和片段,并且我的(真实)应用程序充满了遗留代码,几乎不可能为 Espresso 正确模拟/存根......例如,实际执行登录要容易得多真正的用户通过 UI Automator 进行操作,而不是尝试模拟所有会话检查逻辑。 (2认同)