Add smoke tests for Hermes debugger (#1120)

* Add Hermes RN sample project to smoke tests

* Add Hermes smoke test scenario

* Add TestButton click and fix comments

* Add checking for the existence of Hermes mark

* Bump RN version for the tests

* Change TestButton name to AppTestButton
This commit is contained in:
RedMickey 2019-10-02 14:38:55 +03:00 коммит произвёл GitHub
Родитель 2f5c780b48
Коммит 49798d4dfd
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
14 изменённых файлов: 748 добавлений и 8 удалений

Просмотреть файл

@ -183,9 +183,8 @@ gulp.task("clean", () => {
"!test/resources/sampleReactNative022Project/**/*.js",
".vscode-test/",
"nls.*.json",
"!test/smoke/resources/ReactNativeSample/App.js",
"!test/smoke/resources/ExpoSample/App.js",
"!test/smoke/resources/PureRNExpoSample/App.js",
"!test/smoke/**/*.js",
"!test/smoke/**/*.js.map",
]
return del(pathsToDelete, { force: true });
});

Просмотреть файл

@ -29,6 +29,7 @@
],
"activationEvents": [
"onDebugResolve:reactnative",
"onDebugResolve:reactnativedirect",
"onDebugInitialConfigurations",
"onCommand:reactNative.runAndroidSimulator",
"onCommand:reactNative.runAndroidDevice",

Просмотреть файл

@ -0,0 +1,75 @@
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
* @flow
*/
import React from 'react';
import { SafeAreaView, StyleSheet, ScrollView, View, Text, StatusBar } from 'react-native';
import { Header, LearnMoreLinks, Colors, DebugInstructions, ReloadInstructions } from 'react-native/Libraries/NewAppScreen';
import AppTestButton from './AppTestButton';
const App: () => React$Node = () => {
console.log('Test output from debuggee');
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<ScrollView
contentInsetAdjustmentBehavior="automatic"
style={styles.scrollView}>
<Header />
{global.HermesInternal == null ? null : (
<View style={styles.engine}>
<Text style={styles.footer}>Engine: Hermes</Text>
</View>
)}
<AppTestButton />
</ScrollView>
</SafeAreaView>
</>
);
};
const styles = StyleSheet.create({
scrollView: {
backgroundColor: Colors.lighter,
},
engine: {
position: 'absolute',
right: 0,
},
body: {
backgroundColor: Colors.white,
},
sectionContainer: {
marginTop: 32,
paddingHorizontal: 24,
},
sectionTitle: {
fontSize: 24,
fontWeight: '600',
color: Colors.black,
},
sectionDescription: {
marginTop: 8,
fontSize: 18,
fontWeight: '400',
color: Colors.dark,
},
highlight: {
fontWeight: '700',
},
footer: {
color: Colors.dark,
fontSize: 12,
fontWeight: '600',
padding: 4,
paddingRight: 12,
textAlign: 'right',
},
});
export default App;

Просмотреть файл

@ -0,0 +1,25 @@
import React, { Component } from 'react';
import { View, Button } from 'react-native';
export default class AppTestButton extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
let testBooleanValue = true;
console.log('Test output from Hermes debuggee');
}
render() {
return (
<View style={{marginTop: 10}}>
<Button
onPress={this.handleClick}
title="Test Button"
/>
</View>
);
}
}

Просмотреть файл

@ -0,0 +1,201 @@
apply plugin: "com.android.application"
import com.android.build.OutputFile
/**
* The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
* and bundleReleaseJsAndAssets).
* These basically call `react-native bundle` with the correct arguments during the Android build
* cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
* bundle directly from the development server. Below you can see all the possible configurations
* and their defaults. If you decide to add a configuration block, make sure to add it before the
* `apply from: "../../node_modules/react-native/react.gradle"` line.
*
* project.ext.react = [
* // the name of the generated asset file containing your JS bundle
* bundleAssetName: "index.android.bundle",
*
* // the entry file for bundle generation
* entryFile: "index.android.js",
*
* // https://facebook.github.io/react-native/docs/performance#enable-the-ram-format
* bundleCommand: "ram-bundle",
*
* // whether to bundle JS and assets in debug mode
* bundleInDebug: false,
*
* // whether to bundle JS and assets in release mode
* bundleInRelease: true,
*
* // whether to bundle JS and assets in another build variant (if configured).
* // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
* // The configuration property can be in the following formats
* // 'bundleIn${productFlavor}${buildType}'
* // 'bundleIn${buildType}'
* // bundleInFreeDebug: true,
* // bundleInPaidRelease: true,
* // bundleInBeta: true,
*
* // whether to disable dev mode in custom build variants (by default only disabled in release)
* // for example: to disable dev mode in the staging build type (if configured)
* devDisabledInStaging: true,
* // The configuration property can be in the following formats
* // 'devDisabledIn${productFlavor}${buildType}'
* // 'devDisabledIn${buildType}'
*
* // the root of your project, i.e. where "package.json" lives
* root: "../../",
*
* // where to put the JS bundle asset in debug mode
* jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
*
* // where to put the JS bundle asset in release mode
* jsBundleDirRelease: "$buildDir/intermediates/assets/release",
*
* // where to put drawable resources / React Native assets, e.g. the ones you use via
* // require('./image.png')), in debug mode
* resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
*
* // where to put drawable resources / React Native assets, e.g. the ones you use via
* // require('./image.png')), in release mode
* resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
*
* // by default the gradle tasks are skipped if none of the JS files or assets change; this means
* // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
* // date; if you have any other folders that you want to ignore for performance reasons (gradle
* // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
* // for example, you might want to remove it from here.
* inputExcludes: ["android/**", "ios/**"],
*
* // override which node gets called and with what additional arguments
* nodeExecutableAndArgs: ["node"],
*
* // supply additional arguments to the packager
* extraPackagerArgs: []
* ]
*/
project.ext.react = [
entryFile: "index.js",
enableHermes: true, // clean and rebuild if changing
]
apply from: "../../node_modules/react-native/react.gradle"
/**
* Set this to true to create two separate APKs instead of one:
* - An APK that only works on ARM devices
* - An APK that only works on x86 devices
* The advantage is the size of the APK is reduced by about 4MB.
* Upload all the APKs to the Play Store and people will download
* the correct one based on the CPU architecture of their device.
*/
def enableSeparateBuildPerCPUArchitecture = false
/**
* Run Proguard to shrink the Java bytecode in release builds.
*/
def enableProguardInReleaseBuilds = false
/**
* The preferred build flavor of JavaScriptCore.
*
* For example, to use the international variant, you can use:
* `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
*
* The international variant includes ICU i18n library and necessary data
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
* give correct results when using with locales other than en-US. Note that
* this variant is about 6MiB larger per architecture than default.
*/
def jscFlavor = 'org.webkit:android-jsc:+'
/**
* Whether to enable the Hermes VM.
*
* This should be set on project.ext.react and mirrored here. If it is not set
* on project.ext.react, JavaScript will not be compiled to Hermes Bytecode
* and the benefits of using Hermes will therefore be sharply reduced.
*/
def enableHermes = project.ext.react.get("enableHermes", false);
android {
compileSdkVersion rootProject.ext.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
applicationId "com.latestrnapp"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
}
splits {
abi {
reset()
enable enableSeparateBuildPerCPUArchitecture
universalApk false // If true, also generate a universal APK
include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}
}
signingConfigs {
debug {
storeFile file('debug.keystore')
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
// Caution! In production, you need to generate your own keystore file.
// see https://facebook.github.io/react-native/docs/signed-apk-android.
signingConfig signingConfigs.debug
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
// applicationVariants are e.g. debug, release
applicationVariants.all { variant ->
variant.outputs.each { output ->
// For each separate APK per architecture, set a unique version code as described here:
// https://developer.android.com/studio/build/configure-apk-splits.html
def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride =
versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
}
}
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "com.facebook.react:react-native:+" // From node_modules
if (enableHermes) {
def hermesPath = "../../node_modules/hermes-engine/android/";
debugImplementation files(hermesPath + "hermes-debug.aar")
releaseImplementation files(hermesPath + "hermes-release.aar")
} else {
implementation jscFlavor
}
}
// Run this once to be able to run the application with BUCK
// puts all compile dependencies into folder libs for BUCK to use
task copyDownloadableDepsToLibs(type: Copy) {
from configurations.compile
into 'libs'
}
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)

Просмотреть файл

@ -0,0 +1,314 @@
// Copyright (c) Facebook, Inc. and its affiliates.
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
import org.apache.tools.ant.taskdefs.condition.Os
def config = project.hasProperty("react") ? project.react : [];
def cliPath = config.cliPath ?: "node_modules/react-native/cli.js"
def composeSourceMapsPath = config.composeSourceMapsPath ?: "node_modules/react-native/scripts/compose-source-maps.js"
def bundleAssetName = config.bundleAssetName ?: "index.android.bundle"
def entryFile = config.entryFile ?: "index.android.js"
def bundleCommand = config.bundleCommand ?: "bundle"
def reactRoot = file(config.root ?: "../../")
def inputExcludes = config.inputExcludes ?: ["android/**", "ios/**"]
def bundleConfig = config.bundleConfig ? "${reactRoot}/${config.bundleConfig}" : null ;
def enableVmCleanup = config.enableVmCleanup == null ? true : config.enableVmCleanup
def hermesCommand = config.hermesCommand ?: "../../node_modules/hermes-engine/%OS-BIN%/hermes"
def reactNativeDevServerPort() {
def value = project.getProperties().get("reactNativeDevServerPort")
return value != null ? value : "8081"
}
def reactNativeInspectorProxyPort() {
def value = project.getProperties().get("reactNativeInspectorProxyPort")
return value != null ? value : reactNativeDevServerPort()
}
def getHermesOSBin() {
if (Os.isFamily(Os.FAMILY_WINDOWS)) return "win64-bin";
if (Os.isFamily(Os.FAMILY_MAC)) return "osx-bin";
if (Os.isOs(null, "linux", "amd64", null)) return "linux64-bin";
throw new Exception("OS not recognized. Please set project.ext.react.hermesCommand " +
"to the path of a working Hermes compiler.");
}
// Make sure not to inspect the Hermes config unless we need it,
// to avoid breaking any JSC-only setups.
def getHermesCommand = {
// If the project specifies a Hermes command, don't second guess it.
if (!hermesCommand.contains("%OS-BIN%")) {
return hermesCommand
}
// Execution on Windows fails with / as separator
return hermesCommand
.replaceAll("%OS-BIN%", getHermesOSBin())
.replace('/' as char, File.separatorChar);
}
// Set enableHermesForVariant to a function to configure per variant,
// or set `enableHermes` to True/False to set all of them
def enableHermesForVariant = config.enableHermesForVariant ?: {
def variant -> config.enableHermes ?: false
}
android {
buildTypes.all {
resValue "integer", "react_native_dev_server_port", reactNativeDevServerPort()
resValue "integer", "react_native_inspector_proxy_port", reactNativeInspectorProxyPort()
}
}
afterEvaluate {
def isAndroidLibrary = plugins.hasPlugin("com.android.library")
def variants = isAndroidLibrary ? android.libraryVariants : android.applicationVariants
variants.all { def variant ->
// Create variant and target names
def targetName = variant.name.capitalize()
def targetPath = variant.dirName
// React js bundle directories
def jsBundleDir = file("$buildDir/generated/assets/react/${targetPath}")
def resourcesDir = file("$buildDir/generated/res/react/${targetPath}")
def jsBundleFile = file("$jsBundleDir/$bundleAssetName")
def jsSourceMapsDir = file("$buildDir/generated/sourcemaps/react/${targetPath}")
def jsIntermediateSourceMapsDir = file("$buildDir/intermediates/sourcemaps/react/${targetPath}")
def jsPackagerSourceMapFile = file("$jsIntermediateSourceMapsDir/${bundleAssetName}.packager.map")
def jsCompilerSourceMapFile = file("$jsIntermediateSourceMapsDir/${bundleAssetName}.compiler.map")
def jsOutputSourceMapFile = file("$jsSourceMapsDir/${bundleAssetName}.map")
// Additional node and packager commandline arguments
def nodeExecutableAndArgs = config.nodeExecutableAndArgs ?: ["node"]
def extraPackagerArgs = config.extraPackagerArgs ?: []
def enableHermes = enableHermesForVariant(variant)
def currentBundleTask = tasks.create(
name: "bundle${targetName}JsAndAssets",
type: Exec) {
group = "react"
description = "bundle JS and assets for ${targetName}."
// Create dirs if they are not there (e.g. the "clean" task just ran)
doFirst {
jsBundleDir.deleteDir()
jsBundleDir.mkdirs()
resourcesDir.deleteDir()
resourcesDir.mkdirs()
jsIntermediateSourceMapsDir.deleteDir()
jsIntermediateSourceMapsDir.mkdirs()
jsSourceMapsDir.deleteDir()
jsSourceMapsDir.mkdirs()
}
// Set up inputs and outputs so gradle can cache the result
inputs.files fileTree(dir: reactRoot, excludes: inputExcludes)
outputs.dir(jsBundleDir)
outputs.dir(resourcesDir)
// Set up the call to the react-native cli
workingDir(reactRoot)
// Set up dev mode
def devEnabled = !(config."devDisabledIn${targetName}"
|| targetName.toLowerCase().contains("release"))
def extraArgs = extraPackagerArgs;
if (bundleConfig) {
extraArgs = extraArgs.clone()
extraArgs.add("--config");
extraArgs.add(bundleConfig);
}
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
commandLine("cmd", "/c", *nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}",
"--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir,
"--sourcemap-output", enableHermes ? jsPackagerSourceMapFile : jsOutputSourceMapFile, *extraArgs)
} else {
commandLine(*nodeExecutableAndArgs, cliPath, bundleCommand, "--platform", "android", "--dev", "${devEnabled}",
"--reset-cache", "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir,
"--sourcemap-output", enableHermes ? jsPackagerSourceMapFile : jsOutputSourceMapFile, *extraArgs)
}
if (enableHermes) {
doLast {
def hermesFlags;
def hbcTempFile = file("${jsBundleFile}.hbc")
exec {
if (targetName.toLowerCase().contains("release")) {
// Can't use ?: since that will also substitute valid empty lists
hermesFlags = config.hermesFlagsRelease
if (hermesFlags == null) hermesFlags = ["-O", "-output-source-map"]
} else {
hermesFlags = config.hermesFlagsDebug
if (hermesFlags == null) hermesFlags = []
}
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
commandLine("cmd", "/c", getHermesCommand(), "-emit-binary", "-out", hbcTempFile, jsBundleFile, *hermesFlags)
} else {
commandLine(getHermesCommand(), "-emit-binary", "-out", hbcTempFile, jsBundleFile, *hermesFlags)
}
}
ant.move(
file: hbcTempFile,
toFile: jsBundleFile
);
if (hermesFlags.contains("-output-source-map")) {
ant.move(
// Hermes will generate a source map with this exact name
file: "${jsBundleFile}.hbc.map",
tofile: jsCompilerSourceMapFile
);
exec {
// TODO: set task dependencies for caching
// Set up the call to the compose-source-maps script
workingDir(reactRoot)
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
commandLine("cmd", "/c", *nodeExecutableAndArgs, composeSourceMapsPath, jsPackagerSourceMapFile, jsCompilerSourceMapFile, "-o", jsOutputSourceMapFile)
} else {
commandLine(*nodeExecutableAndArgs, composeSourceMapsPath, jsPackagerSourceMapFile, jsCompilerSourceMapFile, "-o", jsOutputSourceMapFile)
}
}
}
}
}
enabled config."bundleIn${targetName}" != null
? config."bundleIn${targetName}"
: config."bundleIn${variant.buildType.name.capitalize()}" != null
? config."bundleIn${variant.buildType.name.capitalize()}"
: targetName.toLowerCase().contains("release")
}
// Expose a minimal interface on the application variant and the task itself:
variant.ext.bundleJsAndAssets = currentBundleTask
currentBundleTask.ext.generatedResFolders = files(resourcesDir).builtBy(currentBundleTask)
currentBundleTask.ext.generatedAssetsFolders = files(jsBundleDir).builtBy(currentBundleTask)
// registerGeneratedResFolders for Android plugin 3.x
if (variant.respondsTo("registerGeneratedResFolders")) {
variant.registerGeneratedResFolders(currentBundleTask.generatedResFolders)
} else {
variant.registerResGeneratingTask(currentBundleTask)
}
variant.mergeResourcesProvider.get().dependsOn(currentBundleTask)
// packageApplication for Android plugin 3.x
def packageTask = variant.hasProperty("packageApplication")
? variant.packageApplicationProvider.get()
: tasks.findByName("package${targetName}")
if (variant.hasProperty("packageLibrary")) {
packageTask = variant.packageLibrary
}
// pre bundle build task for Android plugin 3.2+
def buildPreBundleTask = tasks.findByName("build${targetName}PreBundle")
def resourcesDirConfigValue = config."resourcesDir${targetName}"
if (resourcesDirConfigValue) {
def currentCopyResTask = tasks.create(
name: "copy${targetName}BundledResources",
type: Copy) {
group = "react"
description = "copy bundled resources into custom location for ${targetName}."
from(resourcesDir)
into(file(resourcesDirConfigValue))
dependsOn(currentBundleTask)
enabled(currentBundleTask.enabled)
}
packageTask.dependsOn(currentCopyResTask)
if (buildPreBundleTask != null) {
buildPreBundleTask.dependsOn(currentCopyResTask)
}
}
def currentAssetsCopyTask = tasks.create(
name: "copy${targetName}BundledJs",
type: Copy) {
group = "react"
description = "copy bundled JS into ${targetName}."
if (config."jsBundleDir${targetName}") {
from(jsBundleDir)
into(file(config."jsBundleDir${targetName}"))
} else {
into ("$buildDir/intermediates")
into ("assets/${targetPath}") {
from(jsBundleDir)
}
// Workaround for Android Gradle Plugin 3.2+ new asset directory
into ("merged_assets/${variant.name}/merge${targetName}Assets/out") {
from(jsBundleDir)
}
// Workaround for Android Gradle Plugin 3.4+ new asset directory
into ("merged_assets/${variant.name}/out") {
from(jsBundleDir)
}
}
// mergeAssets must run first, as it clears the intermediates directory
dependsOn(variant.mergeAssetsProvider.get())
enabled(currentBundleTask.enabled)
}
packageTask.dependsOn(currentAssetsCopyTask)
if (buildPreBundleTask != null) {
buildPreBundleTask.dependsOn(currentAssetsCopyTask)
}
// Delete the VM related libraries that this build doesn't need.
// The application can manage this manually by setting 'enableVmCleanup: false'
//
// This should really be done by packaging all Hermes releated libs into
// two separate HermesDebug and HermesRelease AARs, but until then we'll
// kludge it by deleting the .so files out of the /transforms/ directory.
def isRelease = targetName.toLowerCase().contains("release")
def libDir = "$buildDir/intermediates/transforms/"
def vmSelectionAction = {
fileTree(libDir).matching {
if (enableHermes) {
// For Hermes, delete all the libjsc* files
include "**/libjsc*.so"
if (isRelease) {
// Reduce size by deleting the debugger/inspector
include '**/libhermes-inspector.so'
include '**/libhermes-executor-debug.so'
} else {
// Release libs take precedence and must be removed
// to allow debugging
include '**/libhermes-executor-release.so'
}
} else {
// For JSC, delete all the libhermes* files
include "**/libhermes*.so"
}
}.visit { details ->
def targetVariant = ".*/transforms/[^/]*/${targetPath}/.*"
def path = details.file.getAbsolutePath().replace(File.separatorChar, '/' as char)
if (path.matches(targetVariant) && details.file.isFile()) {
details.file.delete()
}
}
}
if (enableVmCleanup) {
def task = tasks.findByName("package${targetName}")
task.doFirst(vmSelectionAction)
}
}
}

Просмотреть файл

@ -8,6 +8,19 @@
"request": "launch",
"platform": "android"
},
{
"name": "Debug Android (Hermes) - Experimental",
"cwd": "${workspaceFolder}",
"type": "reactnativedirect",
"request": "launch",
"platform": "android"
},
{
"name": "Attach to packager (Hermes) - Experimental",
"cwd": "${workspaceFolder}",
"type": "reactnativedirect",
"request": "attach"
},
{
"name": "Debug iOS",
"cwd": "${workspaceFolder}",

Просмотреть файл

@ -12,6 +12,7 @@ const DEBUG_OPTIONS_COMBOBOX_OPENED = `${DEBUG_OPTIONS_COMBOBOX}.monaco-select-b
const CONFIGURE = `div[id="workbench.parts.sidebar"] .actions-container .configure`;
const START = `.icon[title="Start Debugging"]`;
const STOP = `.debug-toolbar .action-label[title*=\"Stop\"]`;
const DISCONNECT = `.debug-toolbar .action-label[title*=\"Disconnect\"]`;
const STEP_OVER = `.debug-toolbar .action-label[title*=\"Step Over\"]`;
const STEP_IN = `.debug-toolbar .action-label[title*=\"Step Into\"]`;
const STEP_OUT = `.debug-toolbar .action-label[title*=\"Step Out\"]`;
@ -101,6 +102,11 @@ export class Debug extends Viewlet {
await this.spectron.client.waitForElement(NOT_DEBUG_STATUS_BAR);
}
public async disconnectFromDebugger(): Promise<any> {
await this.spectron.client.waitAndClick(DISCONNECT);
await this.spectron.client.waitForElement(NOT_DEBUG_STATUS_BAR);
}
public async waitForStackFrame(func: (stackFrame: IStackFrame) => boolean, message: string): Promise<IStackFrame> {
return await this.spectron.client.waitFor(async () => {
const stackFrames = await this.getStackFrames();

Просмотреть файл

@ -7,7 +7,7 @@ import { AppiumHelper, Platform, AppiumClient } from "./helpers/appiumHelper";
import { AndroidEmulatorHelper } from "./helpers/androidEmulatorHelper";
import { sleep } from "./helpers/utilities";
import { SmokeTestsConstants } from "./helpers/smokeTestsConstants";
import { ExpoWorkspacePath, pureRNWorkspacePath, RNworkspacePath, runVSCode } from "./main";
import { ExpoWorkspacePath, pureRNWorkspacePath, RNworkspacePath, prepareReactNativeProjectForHermesTesting, runVSCode } from "./main";
import { SetupEnvironmentHelper } from "./helpers/setupEnvironmentHelper";
import { TestRunArguments } from "./helpers/configHelper";
@ -16,8 +16,10 @@ const RN_APP_ACTIVITY_NAME = "com.latestrnapp.MainActivity";
const EXPO_APP_PACKAGE_NAME = SetupEnvironmentHelper.expoPackageName;
const EXPO_APP_ACTIVITY_NAME = `${EXPO_APP_PACKAGE_NAME}.experience.HomeActivity`;
const RNDebugConfigName = "Debug Android";
const RNHermesDebugConfigName = "Debug Android (Hermes) - Experimental";
const ExpoDebugConfigName = "Debug in Exponent";
const RNSetBreakpointOnLine = 14;
const RNHermesSetBreakpointOnLine = 11;
const ExpoSetBreakpointOnLine = 12;
const PureRNExpoSetBreakpointOnLine = 23;
// Time for Android Debug Test before it reaches timeout
@ -72,6 +74,53 @@ export function setup(testParameters?: TestRunArguments) {
console.log("Android Debug test: Debugging is stopped");
});
it("Hermes RN app Debug test", async function () {
this.timeout(debugAndroidTestTime);
prepareReactNativeProjectForHermesTesting();
AndroidEmulatorHelper.uninstallTestAppFromEmulator(RN_APP_PACKAGE_NAME);
app = await runVSCode(RNworkspacePath);
await app.workbench.explorer.openExplorerView();
await app.workbench.explorer.openFile("AppTestButton.js");
await app.runCommand("cursorTop");
console.log("Android Debug Hermes test: AppTestButton.js file is opened");
await app.workbench.debug.setBreakpointOnLine(RNHermesSetBreakpointOnLine);
console.log(`Android Debug Hermes test: Breakpoint is set on line ${RNHermesSetBreakpointOnLine}`);
await app.workbench.debug.openDebugViewlet();
console.log(`Android Debug Hermes test: Debug Viewlet opened`);
await app.workbench.debug.chooseDebugConfiguration(RNHermesDebugConfigName);
console.log(`Android Debug Hermes test: Chosen debug configuration: ${RNHermesDebugConfigName}`);
console.log("Android Debug Hermes test: Starting debugging");
await app.workbench.debug.startDebugging();
const opts = AppiumHelper.prepareAttachOptsForAndroidActivity(RN_APP_PACKAGE_NAME, RN_APP_ACTIVITY_NAME, AndroidEmulatorHelper.androidEmulatorName);
await AndroidEmulatorHelper.checkIfAppIsInstalled(RN_APP_PACKAGE_NAME, SmokeTestsConstants.androidAppBuildAndInstallTimeout);
let client = AppiumHelper.webdriverAttach(opts);
clientInited = client.init();
await app.workbench.debug.waitForDebuggingToStart();
console.log("Android Debug Hermes test: Debugging started");
console.log("Android Debug Hermes test: Checking for Hermes mark");
let isHermesWorking = await AppiumHelper.isHermesWorking(clientInited);
assert.equal(isHermesWorking, true);
console.log("Android Debug Hermes test: Reattaching to Hermes app");
await app.workbench.debug.stopDebugging();
await app.workbench.debug.chooseDebugConfiguration("Attach to packager (Hermes) - Experimental");
await app.workbench.debug.startDebugging();
console.log("Android Debug Hermes test: Reattached successfully");
await sleep(7000);
console.log("Android Debug Hermes test: Click Test Button");
await AppiumHelper.clickTestButtonHermes(clientInited);
await app.workbench.debug.waitForStackFrame(sf => sf.name === "AppTestButton.js" && sf.lineNumber === RNHermesSetBreakpointOnLine, `looking for AppTestButton.js and line ${RNHermesSetBreakpointOnLine}`);
console.log("Android Debug Hermes test: Stack frame found");
await app.workbench.debug.continue();
// await for our debug string renders in debug console
await sleep(SmokeTestsConstants.debugConsoleSearchTimeout);
console.log("Android Debug Hermes test: Searching for \"Test output from Hermes debuggee\" string in console");
let found = await app.workbench.debug.findStringInConsole("Test output from Hermes debuggee", 10000);
assert.notStrictEqual(found, false, "\"Test output from Hermes debuggee\" string is missing in debug console");
console.log("Android Debug test: \"Test output from Hermes debuggee\" string is found");
await app.workbench.debug.disconnectFromDebugger();
console.log("Android Debug Hermes test: Debugging is stopped");
});
it("Expo app Debug test", async function () {
if (testParameters && testParameters.RunBasicTests) {
this.skip();

Просмотреть файл

@ -15,7 +15,7 @@ import { TestRunArguments } from "./helpers/configHelper";
const RnAppBundleId = "org.reactjs.native.example.latestRNApp";
const RNDebugConfigName = "Debug iOS";
const ExpoDebugConfigName = "Debug in Exponent";
const RNSetBreakpointOnLine = 14;
const RNSetBreakpointOnLine = 15;
const ExpoSetBreakpointOnLine = 12;
const PureRNExpoSetBreakpointOnLine = 23;
// Time for OS Debug Test before it reaches timeout

Просмотреть файл

@ -144,6 +144,15 @@ export class AndroidEmulatorHelper {
});
}
public static uninstallTestAppFromEmulator(appPackage: string) {
console.log(`*** Uninstalling test app ${appPackage}' from Emulator`);
try {
cp.spawnSync("adb", ["shell", "pm", "uninstall", appPackage], {stdio: "inherit"});
} catch (e) {
console.error(`Error occured while uninstalling test app:\n ${e}`);
}
}
public static async enableDrawPermitForApp(packageName: string) {
const drawPermitCommand = `adb -s ${AndroidEmulatorHelper.androidEmulatorName} shell appops set ${packageName} SYSTEM_ALERT_WINDOW allow`;
console.log(`*** Enabling permission for drawing over apps via: ${drawPermitCommand}`);

Просмотреть файл

@ -243,6 +243,19 @@ export class AppiumHelper {
}
}
public static async clickTestButtonHermes(client: AppiumClient) {
console.log(`*** Pressing button with text "Test Button"...`);
const TEST_BUTTON = "//*[@text='TEST BUTTON']";
await client.click(TEST_BUTTON);
}
public static async isHermesWorking(client: AppiumClient): Promise<boolean> {
const HERMES_MARK = "//*[@text='Engine: Hermes']";
return await client
.waitForExist(HERMES_MARK, 30 * 1000)
.isExisting(HERMES_MARK);
}
private static async openExpoAppViaClipboardAndroid(client: AppiumClient, clipboard: Electron.Clipboard, expoURL: string) {
// Expo application automatically detects Expo URLs in the clipboard
// So we are copying expoURL to system clipboard and click on the special "Open from Clipboard" UI element

Просмотреть файл

@ -27,9 +27,9 @@ export class SetupEnvironmentHelper {
console.log(`*** Creating RN app via '${command}' in ${workspacePath}...`);
cp.execSync(command, { cwd: resourcesPath, stdio: "inherit" });
let customEntryPointFile = path.join(resourcesPath, customEntryPointFolder, "App.js");
let launchConfigFile = path.join(resourcesPath, "launch.json");
let vsCodeConfigPath = path.join(workspacePath, ".vscode");
const customEntryPointFile = path.join(resourcesPath, customEntryPointFolder, "App.js");
const launchConfigFile = path.join(resourcesPath, "launch.json");
const vsCodeConfigPath = path.join(workspacePath, ".vscode");
console.log(`*** Copying ${customEntryPointFile} into ${workspaceFilePath}...`);
fs.writeFileSync(workspaceFilePath, fs.readFileSync(customEntryPointFile));
@ -45,6 +45,24 @@ export class SetupEnvironmentHelper {
SetupEnvironmentHelper.patchMetroConfig(workspacePath);
}
public static prepareHermesReactNativeApplication(workspaceFilePath: string, resourcesPath: string, workspacePath: string, appName: string, customEntryPointFolder: string, version?: string) {
const commandClean = path.join(workspacePath, "android", "gradlew") + " clean";
console.log(`*** Executing ${commandClean} ...`);
cp.execSync(commandClean, { cwd: path.join(workspacePath, "android"), stdio: "inherit" });
const customEntryPointFile = path.join(resourcesPath, customEntryPointFolder, "App.js");
const testButtonPath = path.join(resourcesPath, customEntryPointFolder, "AppTestButton.js");
console.log(`*** Copying ${customEntryPointFile} into ${workspaceFilePath}...`);
fs.writeFileSync(workspaceFilePath, fs.readFileSync(customEntryPointFile));
SetupEnvironmentHelper.copyGradleFilesToHermesApp(workspacePath, resourcesPath, customEntryPointFolder);
console.log(`*** Copying ${testButtonPath} into ${workspacePath}`);
fs.copyFileSync(testButtonPath, path.join(workspacePath, "AppTestButton.js"));
}
public static prepareExpoApplication(workspaceFilePath: string, resourcesPath: string, workspacePath: string, appName: string) {
const command = `echo -ne '\\n' | expo init -t tabs --name ${appName} ${appName}`;
console.log(`*** Creating Expo app via '${command}' in ${workspacePath}...`);
@ -278,4 +296,17 @@ module.exports.hasteMapCacheDirectory = ".cache";`;
const contentAfterPatching = fs.readFileSync(metroConfigPath);
console.log(`*** Content of a metro.config.js after patching: ${contentAfterPatching}`);
}
private static copyGradleFilesToHermesApp(workspacePath: string, resourcesPath: string, customEntryPointFolder: string) {
const appGradleBuildFilePath = path.join(workspacePath, "android", "app", "build.gradle");
const resGradleBuildFilePath = path.join(resourcesPath, customEntryPointFolder, "build.gradle");
const resReactGradleFilePath = path.join(resourcesPath, customEntryPointFolder, "react.gradle"); // TODO: remove after react-native Gradle configuration fix (https://github.com/facebook/react-native/issues/25599)
const projReactGradleFilePath = path.join(workspacePath, "node_modules", "react-native", "react.gradle"); // TODO: remove after react-native Gradle configuration fix (https://github.com/facebook/react-native/issues/25599)
console.log(`*** Copying ${resGradleBuildFilePath} into ${appGradleBuildFilePath}...`);
fs.writeFileSync(appGradleBuildFilePath, fs.readFileSync(resGradleBuildFilePath));
console.log(`*** Copying ${resReactGradleFilePath} into ${projReactGradleFilePath}...`); // TODO: remove after react-native Gradle configuration fix (https://github.com/facebook/react-native/issues/25599)
fs.writeFileSync(projReactGradleFilePath, fs.readFileSync(resReactGradleFilePath));
}
}

Просмотреть файл

@ -167,6 +167,10 @@ function createApp(quality: Quality, workspaceOrFolder: string): SpectronApplica
});
}
export function prepareReactNativeProjectForHermesTesting() {
SetupEnvironmentHelper.prepareHermesReactNativeApplication(RNworkspaceFilePath, resourcesPath, RNworkspacePath, SmokeTestsConstants.RNAppName, "HermesReactNativeSample", process.env.RN_VERSION);
}
const testParams = TestConfigurator.parseTestArguments();
async function setup(): Promise<void> {
console.log("*** Test VS Code directory:", testVSCodeDirectory);