From 2fa18ea77e919ff771fb0d2a1f458058067c944d Mon Sep 17 00:00:00 2001 From: Edward Chen <18449977+edgchen1@users.noreply.github.com> Date: Tue, 18 Oct 2022 17:21:28 -0700 Subject: [PATCH] [React Native CI] Record more info to debug E2E test (#13329) Record more info from the React Native CI E2E test. In particular, log the view hierarchy when exiting the test and dump logs from Android emulator to the build output. --- js/react_native/e2e/android/app/build.gradle | 1 + .../OnnxruntimeModuleExampleUITests.java | 80 ++++++++++++++----- js/react_native/e2e/src/App.tsx | 8 +- .../android-dump-logs-from-steps.yml | 22 +++++ .../templates/react-native-ci.yml | 65 ++++++++------- tools/python/run_adb.py | 35 ++++++++ 6 files changed, 155 insertions(+), 56 deletions(-) create mode 100644 tools/ci_build/github/azure-pipelines/templates/android-dump-logs-from-steps.yml create mode 100755 tools/python/run_adb.py diff --git a/js/react_native/e2e/android/app/build.gradle b/js/react_native/e2e/android/app/build.gradle index 51384e3101..8a8c1688d9 100644 --- a/js/react_native/e2e/android/app/build.gradle +++ b/js/react_native/e2e/android/app/build.gradle @@ -189,6 +189,7 @@ dependencies { implementation "com.facebook.react:react-native:+" // From node_modules implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" + implementation 'androidx.test.ext:junit:1.1.3' debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") { exclude group:'com.facebook.fbjni' } diff --git a/js/react_native/e2e/android/app/src/androidTest/java/com/example/reactnativeonnxruntimemodule/OnnxruntimeModuleExampleUITests.java b/js/react_native/e2e/android/app/src/androidTest/java/com/example/reactnativeonnxruntimemodule/OnnxruntimeModuleExampleUITests.java index b937ce193f..565c171641 100644 --- a/js/react_native/e2e/android/app/src/androidTest/java/com/example/reactnativeonnxruntimemodule/OnnxruntimeModuleExampleUITests.java +++ b/js/react_native/e2e/android/app/src/androidTest/java/com/example/reactnativeonnxruntimemodule/OnnxruntimeModuleExampleUITests.java @@ -1,5 +1,6 @@ package com.example.reactnativeonnxruntimemodule; +import android.util.Log; import android.view.View; import android.widget.TextView; @@ -7,9 +8,10 @@ import androidx.test.espresso.NoMatchingViewException; import androidx.test.espresso.UiController; import androidx.test.espresso.ViewAction; import androidx.test.espresso.ViewInteraction; +import androidx.test.espresso.util.TreeIterables; +import androidx.test.ext.junit.rules.ActivityScenarioRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; -import androidx.test.rule.ActivityTestRule; -import androidx.test.runner.AndroidJUnit4; import org.hamcrest.Matcher; import org.junit.Assert; @@ -20,37 +22,44 @@ import org.junit.runner.RunWith; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.isRoot; import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription; import static org.hamcrest.Matchers.allOf; @RunWith(AndroidJUnit4.class) @LargeTest public class OnnxruntimeModuleExampleUITests { + public static final String TAG = OnnxruntimeModuleExampleUITests.class.getSimpleName(); + @Rule - public ActivityTestRule activityTestRule = new ActivityTestRule<>(MainActivity.class); + public ActivityScenarioRule activityScenarioRule = new ActivityScenarioRule<>(MainActivity.class); @Test public void testExample() { - // Wait for a view displayed - int waitTime = 0; - final int sleepTime = 1000; - do { - try { - ViewInteraction view = onView(allOf(withContentDescription("output"), isDisplayed())); - if (getText(view) != null) { - break; - } - } catch (NoMatchingViewException ne) { + try { + // Wait for a view displayed + int waitTime = 0; + final int sleepTime = 1000; + do { try { - Thread.sleep(sleepTime); - } catch (InterruptedException ie) { + ViewInteraction view = onView(allOf(withContentDescription("output"), isDisplayed())); + if (getText(view) != null) { + break; + } + } catch (NoMatchingViewException ne) { + try { + Thread.sleep(sleepTime); + } catch (InterruptedException ie) { + } + waitTime += sleepTime; } - waitTime += sleepTime; - } - } while (waitTime < 180000); + } while (waitTime < 180000); - ViewInteraction view = onView(allOf(withContentDescription("output"), isDisplayed())); - Assert.assertEquals(getText(view), "Result: 3"); + ViewInteraction view = onView(allOf(withContentDescription("output"), isDisplayed())); + Assert.assertEquals("Result: 3", getText(view)); + } finally { + dumpRootViewHierarchy(); + } } private String getText(ViewInteraction matcher) { @@ -76,4 +85,35 @@ public class OnnxruntimeModuleExampleUITests { return text[0]; } + + private static void dumpRootViewHierarchy() { + ViewInteraction rootViewInteraction = onView(isRoot()); + rootViewInteraction.perform(new ViewAction() { + @Override + public Matcher getConstraints() { + return isRoot(); + } + + @Override + public String getDescription() { + return "dump view hierarchy"; + } + + @Override + public void perform(UiController uiController, View view) { + if (view == null) { + Log.w(TAG, "view is null, unable to dump view hierarchy"); + return; + } + + Log.d(TAG, "view hierarchy:"); + for (TreeIterables.ViewAndDistance viewAndDistance : TreeIterables.depthFirstViewTraversalWithDistance(view)) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < viewAndDistance.getDistanceFromRoot(); i++) builder.append(" "); + builder.append(viewAndDistance.getView().toString()); + Log.d(TAG, builder.toString()); + } + } + }); + } } diff --git a/js/react_native/e2e/src/App.tsx b/js/react_native/e2e/src/App.tsx index 07d58f26af..879ff6e63f 100644 --- a/js/react_native/e2e/src/App.tsx +++ b/js/react_native/e2e/src/App.tsx @@ -96,11 +96,9 @@ export default class App extends React.PureComponent<{}, State> { {'\n'} {imagePath && (