[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.
This commit is contained in:
Edward Chen 2022-10-18 17:21:28 -07:00 committed by GitHub
parent 9189ebb415
commit 2fa18ea77e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 155 additions and 56 deletions

View file

@ -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'
}

View file

@ -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<MainActivity> activityTestRule = new ActivityTestRule<>(MainActivity.class);
public ActivityScenarioRule<MainActivity> 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<View> 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());
}
}
});
}
}

View file

@ -96,11 +96,9 @@ export default class App extends React.PureComponent<{}, State> {
<Text>{'\n'}</Text>
{imagePath && (
<Image
source={
{
uri:
imagePath
}}
source={{
uri: imagePath,
}}
style={{
height: 200,
width: 200,

View file

@ -0,0 +1,22 @@
# dumps the Android logs from the given step(s)
parameters:
- name: steps
type: stepList
steps:
- task: CmdLine@2
inputs:
script: |
python3 tools/python/run_adb.py logcat --clear
displayName: "Clear Android logs"
condition: succeededOrFailed()
- ${{ parameters.steps }}
- task: CmdLine@2
inputs:
script: |
python3 tools/python/run_adb.py logcat -d
displayName: "Dump Android logs"
condition: succeededOrFailed()

View file

@ -66,7 +66,6 @@ jobs:
- script: |
python3 -m pip install -q flatbuffers
workingDirectory: '$(Build.BinariesDirectory)'
displayName: Install python modules
- script: |
@ -144,21 +143,23 @@ jobs:
workingDirectory: '$(Build.SourcesDirectory)/js/react_native/e2e/ios'
displayName: Start iOS Simulator
- task: Gradle@3
inputs:
gradleWrapperFile: '$(Build.SourcesDirectory)/js/react_native/android/gradlew'
workingDirectory: '$(Build.SourcesDirectory)/js/react_native/android'
options: '--stacktrace'
tasks: 'connectedDebugAndroidTest'
publishJUnitResults: true
testResultsFiles: '**/TEST-*.xml'
testRunTitle: 'React Native Android Instrumented Test results'
javaHomeOption: 'path'
jdkDirectory: '$(JAVA_HOME_11_X64)'
sonarQubeRunAnalysis: false
spotBugsAnalysis: false
displayName: Run React Native Android Instrumented Tests
continueOnError: false
- template: android-dump-logs-from-steps.yml
parameters:
steps:
- task: Gradle@3
inputs:
gradleWrapperFile: '$(Build.SourcesDirectory)/js/react_native/android/gradlew'
workingDirectory: '$(Build.SourcesDirectory)/js/react_native/android'
options: '--stacktrace'
tasks: 'connectedDebugAndroidTest'
publishJUnitResults: true
testResultsFiles: '**/TEST-*.xml'
testRunTitle: 'React Native Android Instrumented Test results'
javaHomeOption: 'path'
jdkDirectory: '$(JAVA_HOME_11_X64)'
sonarQubeRunAnalysis: false
spotBugsAnalysis: false
displayName: Run React Native Android Instrumented Tests
- script: |
pod install
@ -229,21 +230,23 @@ jobs:
targetFolder: $(Build.SourcesDirectory)/js/react_native/e2e/android/app/libs
displayName: Copy Android package to Android e2e test directory
- task: Gradle@3
inputs:
gradleWrapperFile: '$(Build.SourcesDirectory)/js/react_native/e2e/android/gradlew'
workingDirectory: '$(Build.SourcesDirectory)/js/react_native/e2e/android'
options: '--stacktrace'
tasks: ':app:connectedDebugAndroidTest'
publishJUnitResults: true
testResultsFiles: '**/TEST-*.xml'
testRunTitle: 'React Native Android e2e Test results'
javaHomeOption: 'path'
jdkDirectory: '$(JAVA_HOME_11_X64)'
sonarQubeRunAnalysis: false
spotBugsAnalysis: false
displayName: Run React Native Android e2e Tests
continueOnError: false
- template: android-dump-logs-from-steps.yml
parameters:
steps:
- task: Gradle@3
inputs:
gradleWrapperFile: '$(Build.SourcesDirectory)/js/react_native/e2e/android/gradlew'
workingDirectory: '$(Build.SourcesDirectory)/js/react_native/e2e/android'
options: '--stacktrace'
tasks: ':app:connectedDebugAndroidTest'
publishJUnitResults: true
testResultsFiles: '**/TEST-*.xml'
testRunTitle: 'React Native Android e2e Test results'
javaHomeOption: 'path'
jdkDirectory: '$(JAVA_HOME_11_X64)'
sonarQubeRunAnalysis: false
spotBugsAnalysis: false
displayName: Run React Native Android e2e Tests
- script: |
export FORCE_BUNDLING=1

35
tools/python/run_adb.py Executable file
View file

@ -0,0 +1,35 @@
#!/usr/bin/env python3
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
import logging
import os
import sys
import typing
from util import run
from util.android import get_sdk_tool_paths
def run_adb(android_sdk_root: str, args: typing.List[str]):
sdk_tool_paths = get_sdk_tool_paths(android_sdk_root)
run(sdk_tool_paths.adb, *args)
def main():
logging.getLogger().setLevel(logging.WARNING)
adb_args = sys.argv[1:]
android_sdk_root = os.environ.get("ANDROID_HOME") or os.environ.get("ANDROID_SDK_ROOT")
if android_sdk_root is None:
raise RuntimeError(
"Please provide the Android SDK root with environment variable 'ANDROID_HOME' or "
"environment variable 'ANDROID_SDK_ROOT'."
)
run_adb(android_sdk_root, adb_args)
if __name__ == "__main__":
main()