Added ViewCapture to all tests which implement AbstractLauncherUiTest.

This enables developers to watch failed tests in classes like
TaplTestsQuickstep in the go/web-hv tool.

Bug: 242867462
Test: Failed a test and verified that the TimeLapse bugreport entry
showed up properly in the go/web-hv tool.

Change-Id: Ic89af2a0e7102db52c52ddc668607a81c4809ed6
This commit is contained in:
Stefan Andonian
2023-04-21 19:05:47 +00:00
parent 4d559a6c92
commit fa8091661a
7 changed files with 115 additions and 13 deletions

View File

@@ -62,6 +62,7 @@ import com.android.launcher3.util.rule.FailureWatcher;
import com.android.launcher3.util.rule.SamplerRule;
import com.android.launcher3.util.rule.ScreenRecordRule;
import com.android.launcher3.util.rule.TestStabilityRule;
import com.android.launcher3.util.rule.ViewCaptureRule;
import com.android.quickstep.views.RecentsView;
import org.junit.After;
@@ -115,10 +116,12 @@ public class FallbackRecentsTest {
Utilities.enableRunningInTestHarnessForTests();
}
final ViewCaptureRule viewCaptureRule = new ViewCaptureRule();
mOrderSensitiveRules = RuleChain
.outerRule(new SamplerRule())
.around(new NavigationModeSwitchRule(mLauncher))
.around(new FailureWatcher(mDevice, mLauncher));
.around(viewCaptureRule)
.around(new FailureWatcher(mDevice, mLauncher, viewCaptureRule.getViewCapture()));
mOtherLauncherActivity = context.getPackageManager().queryIntentActivities(
getHomeIntentInPackage(context),

View File

@@ -51,6 +51,7 @@ filegroup {
"src/com/android/launcher3/util/WidgetUtils.java",
"src/com/android/launcher3/util/rule/FailureWatcher.java",
"src/com/android/launcher3/util/rule/LauncherActivityRule.java",
"src/com/android/launcher3/util/rule/ViewCaptureRule.kt",
"src/com/android/launcher3/util/rule/SamplerRule.java",
"src/com/android/launcher3/util/rule/ScreenRecordRule.java",
"src/com/android/launcher3/util/rule/ShellCommandRule.java",
@@ -132,4 +133,4 @@ android_library {
manifest: "shared/AndroidManifest.xml",
sdk_version: "current",
min_sdk_version: min_launcher3_sdk_version,
}
}

View File

@@ -72,6 +72,7 @@ import com.android.launcher3.util.rule.SamplerRule;
import com.android.launcher3.util.rule.ScreenRecordRule;
import com.android.launcher3.util.rule.ShellCommandRule;
import com.android.launcher3.util.rule.TestStabilityRule;
import com.android.launcher3.util.rule.ViewCaptureRule;
import org.junit.After;
import org.junit.Assert;
@@ -215,14 +216,15 @@ public abstract class AbstractLauncherUiTest {
}
protected TestRule getRulesInsideActivityMonitor() {
final ViewCaptureRule viewCaptureRule = new ViewCaptureRule();
final RuleChain inner = RuleChain
.outerRule(new PortraitLandscapeRunner(this))
.around(new FailureWatcher(mDevice, mLauncher));
.around(viewCaptureRule)
.around(new FailureWatcher(mDevice, mLauncher, viewCaptureRule.getViewCapture()));
return TestHelpers.isInLauncherProcess()
? RuleChain.outerRule(ShellCommandRule.setDefaultLauncher())
.around(inner) :
inner;
? RuleChain.outerRule(ShellCommandRule.setDefaultLauncher()).around(inner)
: inner;
}
@Rule

View File

@@ -7,8 +7,12 @@ import android.os.FileUtils;
import android.os.ParcelFileDescriptor.AutoCloseInputStream;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.uiautomator.UiDevice;
import com.android.app.viewcapture.ViewCapture;
import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.ui.AbstractLauncherUiTest;
@@ -29,10 +33,14 @@ public class FailureWatcher extends TestWatcher {
private static boolean sSavedBugreport = false;
final private UiDevice mDevice;
private final LauncherInstrumentation mLauncher;
@NonNull
private final ViewCapture mViewCapture;
public FailureWatcher(UiDevice device, LauncherInstrumentation launcher) {
public FailureWatcher(UiDevice device, LauncherInstrumentation launcher,
@NonNull ViewCapture viewCapture) {
mDevice = device;
mLauncher = launcher;
mViewCapture = viewCapture;
}
@Override
@@ -82,7 +90,7 @@ public class FailureWatcher extends TestWatcher {
@Override
protected void failed(Throwable e, Description description) {
onError(mLauncher, description, e);
onError(mLauncher, description, e, mViewCapture);
}
static File diagFile(Description description, String prefix, String ext) {
@@ -93,8 +101,12 @@ public class FailureWatcher extends TestWatcher {
public static void onError(LauncherInstrumentation launcher, Description description,
Throwable e) {
final UiDevice device = launcher.getDevice();
if (device == null) return;
onError(launcher, description, e, null);
}
private static void onError(LauncherInstrumentation launcher, Description description,
Throwable e, @Nullable ViewCapture viewCapture) {
final File sceenshot = diagFile(description, "TestScreenshot", "png");
final File hierarchy = diagFile(description, "Hierarchy", "zip");
@@ -109,13 +121,20 @@ public class FailureWatcher extends TestWatcher {
out.putNextEntry(new ZipEntry("visible_windows.zip"));
dumpCommand("cmd window dump-visible-window-views", out);
out.closeEntry();
} catch (IOException ex) {
if (viewCapture != null) {
out.putNextEntry(new ZipEntry("FS/data/misc/wmtrace/failed_test.vc"));
viewCapture.dumpTo(out, ApplicationProvider.getApplicationContext());
out.closeEntry();
}
} catch (Exception ignored) {
}
Log.e(TAG, "Failed test " + description.getMethodName()
+ ",\nscreenshot will be saved to " + sceenshot
+ ",\nUI dump at: " + hierarchy
+ " (use go/web-hv to open the dump file)", e);
final UiDevice device = launcher.getDevice();
device.takeScreenshot(sceenshot);
// Dump accessibility hierarchy

View File

@@ -56,4 +56,4 @@ public class LauncherActivityRule extends SimpleActivityRule<Launcher> {
return launcher.getWorkspace().getFirstMatch(op) != null;
};
}
}
}

View File

@@ -102,4 +102,4 @@ public class SimpleActivityRule<T extends Activity> implements TestRule {
}
}
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.android.launcher3.util.rule
import android.app.Activity
import android.app.Application
import android.media.permission.SafeCloseable
import android.os.Bundle
import androidx.test.core.app.ApplicationProvider
import com.android.app.viewcapture.SimpleViewCapture
import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
/**
* This JUnit TestRule registers a listener for activity lifecycle events to attach a ViewCapture
* instance that other test rules use to dump the timelapse hierarchy upon an error during a test.
*
* This rule will not work in OOP tests that don't have access to the activity under test.
*/
class ViewCaptureRule : TestRule {
val viewCapture = SimpleViewCapture("test-view-capture")
override fun apply(base: Statement, description: Description): Statement {
return object : Statement() {
override fun evaluate() {
val windowListenerCloseables = mutableListOf<SafeCloseable>()
val lifecycleCallbacks =
object : ActivityLifecycleCallbacksAdapter {
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {
super.onActivityCreated(activity, bundle)
windowListenerCloseables.add(
viewCapture.startCapture(
activity.window.decorView,
"${description.testClass?.simpleName}.${description.methodName}"
)
)
}
override fun onActivityDestroyed(activity: Activity) {
super.onActivityDestroyed(activity)
viewCapture.stopCapture(activity.window.decorView)
}
}
val application = ApplicationProvider.getApplicationContext<Application>()
application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
try {
base.evaluate()
} finally {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
// Clean up ViewCapture references here rather than in onActivityDestroyed so
// test code can access view hierarchy capture. onActivityDestroyed would delete
// view capture data before FailureWatcher could output it as a test artifact.
windowListenerCloseables.onEach(SafeCloseable::close)
}
}
}
}
}