【问题标题】:Android Espresso, Wake up device before test. How to use a custom manifest for test?Android Espresso,测试前唤醒设备。如何使用自定义清单进行测试?
【发布时间】:2021-11-17 22:21:13
【问题描述】:

我一直在用 androids 的新 espresso 框架编写测试,发现它运行良好。一件烦人的事情(不是浓缩咖啡特有的)是我必须确保我的屏幕处于唤醒状态并解锁才能运行测试。我找到了一种解决方法(通过各种来源),但我不确定集成它的最佳方法。

这就是我所做的,在我的“主页”活动中,我有以下代码:

Home.class:

public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        /************ Put this in a conditional for a test version ***********/
    KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
    KeyguardManager.KeyguardLock keyguardLock = km.newKeyguardLock("TAG");
    keyguardLock.disableKeyguard();
    getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
}

还需要添加以下权限:

<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>

因此,在完成此操作后,我的测试现在唤醒我的手机运行,这样我就不必站岗并确保屏幕在测试开始前不会关闭。

我宁愿不在我的应用程序中包含这些权限。我知道使用 gradle 可以制作具有自己的 android 清单的不同“风味”,这些清单将合并到主清单中。我正在考虑使用它,但我不想仅仅因为这个原因添加风味,因为它已经在使用测试构建类型来运行。从 android gradle 文档看来,您无法为 instrumentTest 目录创建 AndroidManifest,因为它将自动生成。

但是我想知道是否有另一种方法可以在不创建变体的情况下执行此操作,然后指定测试应该运行该变体。此外,我不确定所有这些的确切语法,并认为将这些信息放在网站上以供其他人使用会很好,因为它似乎分散在各处。

最后,如果有人知道解决唤醒电话以进行测试的问题的更好方法,我很乐意听到它,因为我不喜欢我正在尝试的这种方式。

【问题讨论】:

  • 不是简单的电源管理器部分唤醒锁定吗?
  • 我不想让我的应用在测试期间保持清醒(这会自行发生),我想唤醒它以开始测试。如果我可以从测试代码本身而不是应用程序中唤醒,我更愿意,但我不知道如何。
  • 唤醒应用程序的机制是AlarmManager。不知道浓缩咖啡,对不起

标签: android android-testing android-wake-lock android-espresso


【解决方案1】:

我实际上想出了一个非常简单的方法来处理这个问题。从主清单中删除 keyguard 和唤醒锁权限,并将它们放在 src/debug/AndroidManifest.xml 中,如下所示:

src/debug/AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
    <uses-permission android:name="android.permission.WAKE_LOCK"/>
</manifest>

当应用程序为调试而构建时,上述权限将合并到主清单中。默认情况下,构建系统使用调试构建进行仪器测试,因此可以正常工作。

然后在我的 onCreate 我把问题中提到的代码:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (BuildConfig.DEBUG) {
        KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
        KeyguardManager.KeyguardLock keyguardLock = km.newKeyguardLock("TAG");
        keyguardLock.disableKeyguard();
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
    }
    ...
}

现在我的手机可以运行测试,而无需我先手动唤醒它们,而且我不必将上述权限添加到我的应用的发布版本中。

【讨论】:

  • 为什么要修改Activity代码以适应单元测试的要求?这似乎是一种反模式......
  • 你为什么不把清单代码放在“androidTest”文件夹中,只在你的测试中使用这个代码呢?用测试样板污染你的代码是一个非常糟糕的主意。
【解决方案2】:

如果您从 CI 环境运行测试,最简单的方法是使用如下 adb 命令:

adb -s $DEVICE_ID shell input keyevent 82

这将解锁您的设备屏幕。

【讨论】:

  • 多长时间?由用户配置?如果设置为 30 秒并且测试持续 60 秒会发生什么?
  • 在我看来,这是实现这一目标的最佳解决方案。它可以作为构建步骤轻松添加到任何构建系统中。
  • 顺便说一句,你可以锁屏/关闭:adb -s $DEVICE_ID shell input keyevent 26
  • 这不会解锁,只会打开屏幕
  • input keyevent 3 (KEYCODE_HOME) 比其他键更合适。键码 26 (KEYCODE_POWER) 如果当时屏幕处于开启状态,则可能会关闭屏幕。
【解决方案3】:

现在 KeyguardLock 已被弃用,您可以简单地使用:

    if (BuildConfig.DEBUG) {
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
    }

【讨论】:

  • 当您还使用 UIAutomator 与应用外的 UI 元素进行交互时,这将不起作用。
  • 这对我不起作用。这应该在哪里调用?
【解决方案4】:

我按照 Matt 的建议创建了 src/debug/AndroidManifest.xml 文件,并将以下代码添加到了 testCase:

   @Override
    public void setUp() throws Exception {
        super.setUp();
        // Espresso will not launch our activity for us, we must launch it via getActivity().
        Activity activity = getActivity();
        KeyguardManager km = (KeyguardManager) activity.getSystemService(Context.KEYGUARD_SERVICE);
        KeyguardManager.KeyguardLock keyguardLock = km.newKeyguardLock("TAG");
        keyguardLock.disableKeyguard();
        activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);

    }

【讨论】:

  • 这很好用,除了它会导致从错误的线程触摸视图层次结构的错误,至少对我来说(我正在使用 Robotium)。我的解决方案是在 getInstrumentation().runOnMainSync() 块内运行代码。
【解决方案5】:

虽然您已经接受了 Matt Wolve 的回答,但我认为使用测试样板污染您的代码并不是一个好主意(我知道使用 Espresso 在某些情况下您必须这样做,例如为自定义 IdlingResources 添加空闲标志) .我想添加另一种方法:

    @ClassRule
    public static ActivityTestRule<HomeActivity> mActivityRuleSetUp = new ActivityTestRule<>(
            HomeActivity.class);


    private static void wakeUpDevice(){
        if (BuildConfig.DEBUG){
            HomeActivity homeActivity = mActivityRuleSetUp.getActivity();

            KeyguardManager myKM = (KeyguardManager) homeActivity.getSystemService(HomeActivity.KEYGUARD_SERVICE);
            boolean isPhoneLocked = myKM.inKeyguardRestrictedInputMode();

            if (isPhoneLocked){
                homeActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
                        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                        | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
            }
        }
     }


    @BeforeClass
    public static void setUp(){
        wakeUpDevice();
    }

希望对你有帮助。

【讨论】:

  • 您能否详细说明这种方法?这似乎是我正在尝试做的事情,但并不完全了解如何进行实施。有示例链接吗?
  • wakeUpDevice() 方法缺少括号顺便说一句。为什么方法是静态的?这样做有什么意义?
  • 感谢 TheOddCoder 的括号。抱歉,但老实说,我不记得它们是否因我无法管理的限制而成为静态的。只是说如果不需要,我总是尽量避免使用每个“静态”修饰符。
【解决方案6】:

为了测试设备,在 Settings->Security 中将 Lock pattern 设置为 NONE 然后使用UiDevice 的实例并调用它的wakeUp() 方法

此方法模拟在屏幕关闭时按下电源按钮,否则如果屏幕已打开则不执行任何操作。如果屏幕处于关闭状态而刚刚打开,此方法将插入一个 500 毫秒的延迟,以让设备有时间唤醒并接受输入。

【讨论】:

    【解决方案7】:

    另一种在测试前唤醒设备的最佳方法。 只需在您的 setUp 方法中添加 ActivityLifecycleCallback

    public class Moduletest extends ActivityInstrumentationTestCase2<Your Activity>{
    
     protected void setUp(){
        super.setUp();
    
        ActivityLifecycleMonitorRegistry.getInstance().addLifecycleCallback(new ActivityLifecycleCallback() {
          @Override public void onActivityLifecycleChanged(Activity activity, Stage stage) {
            if (stage == Stage.PRE_ON_CREATE) {
              activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
            }
          }
        });
      }
    }
    

    【讨论】:

      【解决方案8】:

      我是这样做的:首先制定两条规则,一条用于 Activity,一条用于 UI Thread:

      @Rule
      public ActivityTestRule<Your Activity> mActivityRule =
              new ActivityTestRule<>(Your Activity.class, true, true);
      
      @Rule
      public UiThreadTestRule uiThreadTestRule = new UiThreadTestRule();
      

      然后在我的第一个测试方法中做了这个:

         @Test
         @LargeTest
         public void CreateAndSaveTaskEntity() throws Throwable {
      
                  uiThreadTestRule.runOnUiThread(new Runnable() {
                      @Override
                      public void run() {
                          Your Activity activity = mActivityRule.getActivity();
                  activity.getWindow()
                          .addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON |
                                  WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
                                  WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
      
                      }
                  });
      
                  //begin test cases
      
                  ...
          }
      

      当然要在AndroidManifest.xml中添加权限:

      <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
      <uses-permission android:name="android.permission.WAKE_LOCK"/>
      

      【讨论】:

        【解决方案9】:

        这是一种使用任何已弃用的 API、没有清单权限且即使设备设置了锁销也能正常工作的方法。

        选项 1:在 @Before 中

        @RunWith(AndroidJUnit4::class)
        class MyActivityTest {
        
            // Replace with your own Activity test rule.
            @get:Rule
            val composeTestRule = createAndroidComposeRule<MyActivity>()
        
            @Before
            fun setUp() {
                UiDevice.getInstance(getInstrumentation()).wakeUp()
                composeTestRule.activity.setShowWhenLocked(true)
            }
        }
        

        选项 2:规则

        @RunWith(AndroidJUnit4::class)
        class MyActivityTest {
        
            @get:Rule
            val composeTestRule = createAndroidComposeRule<MyActivity>()
        
            @get:Rule
            val screenLockRule = RunWhenScreenOffOrLockedRule()
        }
        

        /**
        * This rule will allow the tests to run even if the device's screen is off or locked.
        * Allows a developer to fire and forget running the UI Test across different devices or on the CI
        * emulator.
        */
        class RunWhenScreenOffOrLockedRule : TestRule {
            override fun apply(base: Statement, description: Description): Statement {
                return object : Statement() {
        
                    override fun evaluate() {
                        // Turn screen on
                        UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).wakeUp()
        
                        // Allow any activity to run when locked
                        ActivityLifecycleMonitorRegistry
                            .getInstance()
                            .addLifecycleCallback { activity, stage ->
                                if (stage === Stage.PRE_ON_CREATE) {
                                    activity.setShowWhenLocked(true)
                                }
                            }
        
                        // Continue with other statements
                        base.evaluate()
                    }
                }
            }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多