Merge branch 'ErjanGavalji/run-tests-on-build'

This commit is contained in:
Erjan Gavalji
2015-10-29 10:36:03 +02:00
7 changed files with 934 additions and 568 deletions

4
.gitignore vendored
View File

@@ -15,6 +15,7 @@ dist/
!css-value/**/*.*
!fetch/**/*.*
!apps/TelerikNEXT/lib/**/*.*
!build/*.*
CrossPlatformModules.sln.ide/
*.suo
CrossPlatformModules.suo
@@ -27,3 +28,6 @@ obj/
.baseDir.ts
.ctags-exclude
tags
TestRunResult.txt
.testsapprun

View File

@@ -1,50 +1,23 @@
import pages = require("ui/page");
import gridModule = require("ui/layouts/grid-layout");
import {Page} from "ui/page";
import * as trace from "trace";
import tests = require("../testRunner");
import bm = require("ui/button");
import trace = require("trace");
import textViewModule = require("ui/text-view");
class MyTraceWriter implements trace.TraceWriter {
public write(message: any, category: string) {
if (textView && message && message.toLowerCase().indexOf("failed") !== -1) {
if (textView.android) {
textView.text = message + "\r\n" + textView.text;
}
else {
textView.text = message + "\n" + textView.text;
}
}
}
}
trace.addWriter(new MyTraceWriter());
trace.enable();
trace.addCategories(trace.categories.Test + "," + trace.categories.Error);
var textView = new textViewModule.TextView();
textView.editable = false;
textView.style.fontSize = 8;
let started = false;
let page = new Page();
page.on(Page.navigatedToEvent, function () {
if (!started) {
started = true;
setTimeout(function () {
tests.runAll();
}, 10);
}
});
export function createPage() {
var button = new bm.Button();
button.text = "Run Tests";
button.on(bm.Button.tapEvent, function () {
tests.runAll();
});
var grid = new gridModule.GridLayout();
grid.addRow(new gridModule.ItemSpec(1, gridModule.GridUnitType.auto));
grid.addRow(new gridModule.ItemSpec());
gridModule.GridLayout.setRow(textView, 1);
grid.addChild(button);
grid.addChild(textView);
var page = new pages.Page();
page.content = grid;
return page;
}
//export var Page = page;

301
build/run-testsapp.grunt.js Normal file
View File

@@ -0,0 +1,301 @@
module.exports = {
run: function(grunt) {
var pathModule = require("path");
var modulesPackageConfig = grunt.file.readJSON('package.json');
//Construct and validate the arguments
var args = {
platform: grunt.option("platform"),
modulesPath: grunt.option("modulesPath"),
tnsPath: grunt.option("tnsPath"),
emulatorProcessIdentifier: grunt.option("emuPId"),
emuAvdName: grunt.option("avd"),
outFile: grunt.option("logFilePath"),
runtimePath: grunt.option("runtimePath"),
showEmu: grunt.option("showEmu")
};
(function validateInput(){
if (!(/^(Android|iOS)$/).test(args.platform)) {
throw new Error("Invalid target platform specified! Use --platform=Android|iOS");
}
if (args.platform === "Android") {
if (!args.emulatorProcessIdentifier) {
throw new Error("Please, specify an identifier of the emulator process so that it can be stopped (--emuPId=...). Too many emulators started might cause machine overload");
}
if (!args.emuAvdName) {
throw new Error("Please, specify the name of the AVD to start (--avd=...).");
}
}
}());
var localCfg = {
tnsPath: args.tnsPath || "tns",
emulatorProcessIdentifier: args.emulatorProcessIdentifier,
modulesPath: args.modulesPath || "./bin/dist/tns-core-modules-" + modulesPackageConfig.version + ".tgz",
emuAvdName: args.emuAvdName,
outFile: args.outFile || "./TestRunResult.txt",
frameworkArgument: args.runtimePath ? " --frameworkPath=" + args.runtimePath : "",
showEmu: args.showEmu || false,
workingDir:".testsapprun",
testsAppName:"TestsApp",
applicationDir: pathModule.join(".testsapprun", "TestsApp"),
appDir: pathModule.join(".testsapprun", "TestsApp", "app"),
pathToApk: "./platforms/android/build/outputs/apk/TestsApp-debug.apk",
pathToApp: "./platforms/ios/build/emulator/TestsApp.app",
deployedAppName:"org.nativescript.TestsApp",
mainActivityName:"com.tns.NativeScriptActivity",
pathToCompiledTests: "bin/dist/apps/tests",
simulatorSysLog: pathModule.join(process.env.HOME, "Library/Logs/CoreSimulator", args.emuAvdName, "/system.log"),
platform: args.platform
}
grunt.initConfig({
clean: {
workingDir: {
src: localCfg.workingDir
},
originalAppDir: {
src: [
localCfg.appDir + "/*",
"!" + pathModule.join(localCfg.appDir, "App_Resources") + ""
]
},
modules: {
src: pathModule.join(localCfg.applicationDir, "node_modules", "tns-core-modules")
},
tempExtractedModules: {
src: pathModule.join(localCfg.applicationDir, "node_modules", "package")
},
simulatorLog: {
src: localCfg.simulatorSysLog,
options: {
force: true
}
}
},
mkdir: {
workingDir: {
options: {
create: [localCfg.workingDir],
mode: 0700
}
}
},
copy: {
testsAppToRunDir: {
src: "**/*.*",
dest: localCfg.appDir,
cwd: localCfg.pathToCompiledTests,
expand: true
},
modulesToDir: {
expand: true,
src: "**/*.*",
cwd: pathModule.join(localCfg.applicationDir, "node_modules", "package"),
dest: pathModule.join(localCfg.applicationDir, "node_modules", "tns-core-modules")
},
addAndroidPermissions: {
src: "AndroidManifest.xml",
dest: localCfg.applicationDir + "/platforms/android/src/main/",
cwd: localCfg.applicationDir + "/platforms/android/src/main",
expand: true,
options: {
process: function(content, srcPath) {
var newContent = content;
var internetPermissionFinder = /((\s*)<uses-permission[^>]*android\.permission\.INTERNET[^>]*>)/;
if (!/uses-permission[^>]*android\.permission\.ACCESS_NETWORK_STATE/.test(content)) {
newContent = newContent.replace(internetPermissionFinder, "$1$2<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>");
}
if (!/uses-permission[^>]*android\.permission\.ACCESS_FINE_LOCATION/.test(content)) {
newContent = newContent.replace(internetPermissionFinder, "$1$2<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>");
}
return newContent;
}
}
},
addiOSPermissions: {
src: localCfg.testsAppName + "-Info.plist",
dest: pathModule.join(localCfg.applicationDir,"/platforms/ios/", localCfg.testsAppName) + "/",
cwd: pathModule.join(localCfg.applicationDir,"/platforms/ios/", localCfg.testsAppName),
expand: true,
options: {
process: function(content, srcPath) {
var newContent = content;
var lastDictLocator = /(<\/dict>\s*<\/plist>)$/gm;
if (!/NSAppTransportSecurity/.test(content)) {
newContent = newContent.replace(lastDictLocator, "<key>NSAppTransportSecurity</key>\n$1");
}
if (!/NSAllowsArbitraryLoads/.test(content)) {
newContent = newContent.replace(lastDictLocator, "<dict>\n<key>NSAllowsArbitraryLoads</key>\n<true/>\n</dict>\n$1");
}
return newContent;
}
}
},
simulatorLog: {
src: localCfg.simulatorSysLog,
dest: localCfg.outFile
}
},
exec: {
killAndroidEmulator: {
cmd: "pkill '" + localCfg.emulatorProcessIdentifier + "'",
exitCode: [0, 1]
},
killiOSEmulator: {
cmd: "pkill Simulator",
exitCode: [0, 1]
},
createApp: {
cmd: localCfg.tnsPath + " create " + localCfg.testsAppName,
cwd: localCfg.workingDir
},
addPlatform: {
cmd: "tns platform add " + localCfg.platform.toLowerCase() + " " + localCfg.frameworkArgument,
cwd: localCfg.applicationDir
},
restartAdb: {
cmd: "adb kill-server && adb start-server"
},
uninstallExistingAndroidApp: {
cmd: "adb uninstall " + localCfg.deployedAppName
},
installNewAndroidApp: {
cmd: "adb install " + localCfg.pathToApk,
cwd: localCfg.applicationDir
},
startAndroidEmulator: {
cmd: "emulator -avd " + localCfg.emuAvdName + " -no-audio " + (args.showEmu ? "" : "-no-window") + "&"
},
startAndroidApp: {
cmd: "adb shell am start -n " + localCfg.deployedAppName + "/" + localCfg.mainActivityName
},
uninstallExistingiOSApp: {
cmd: "xcrun simctl uninstall " + localCfg.emuAvdName + " org.nativescript." + localCfg.testsAppName,
cwd: localCfg.applicationDir
},
installNewiOSApp: {
cmd: "xcrun simctl install " + localCfg.emuAvdName + " " + localCfg.pathToApp,
cwd: localCfg.applicationDir
},
startiOSApp: {
cmd: "xcrun simctl launch " + localCfg.emuAvdName + " org.nativescript." + localCfg.testsAppName
},
},
untar: {
modules: {
src: localCfg.modulesPath,
dest: pathModule.join(localCfg.applicationDir, "node_modules")
}
},
shell: {
collectAndroidLog: {
command: "./expect.exp " + "'adb logcat' " + localCfg.outFile,
options: {
execOptions: {
maxBuffer: Infinity
}
}
},
waitiOSLogCompletion: {
command: "./expect.exp " + "'tail -f " + localCfg.simulatorSysLog + "' " + localCfg.outFile,
options: {
execOptions: {
maxBuffer: Infinity
}
}
},
startiOSSimulator: {
command: "xcrun instruments -w " + localCfg.emuAvdName,
options: {
failOnError: false
},
},
buildApp: {
command: "tns build " + localCfg.platform.toLowerCase(),
options: {
execOptions: {
maxBuffer: Infinity,
cwd: localCfg.applicationDir
}
}
},
}
});
grunt.loadNpmTasks("grunt-shell");
grunt.loadNpmTasks("grunt-exec");
grunt.loadNpmTasks("grunt-mkdir");
grunt.loadNpmTasks("grunt-contrib-clean");
grunt.loadNpmTasks("grunt-contrib-copy");
grunt.loadNpmTasks("grunt-untar");
var getPlatformSpecificTask = function(templatedTaskName) {
return templatedTaskName.replace(/\{platform\}/, localCfg.platform);
}
grunt.registerTask("startEmulatorAndroid", [
getPlatformSpecificTask("exec:startAndroidEmulator"),
]);
grunt.registerTask("startEmulatoriOS", [
getPlatformSpecificTask("shell:startiOSSimulator"),
]);
grunt.registerTask("collectLogAndroid", [
"shell:collectAndroidLog"
]);
grunt.registerTask("collectLogiOS", [
"shell:waitiOSLogCompletion",
"copy:simulatorLog"
]);
grunt.registerTask("doPostPlatformAddAndroid", [
"exec:restartAdb"
]);
grunt.registerTask("doPostPlatformAddiOS", [
"clean:simulatorLog"
]);
//xcrun instruments -s
grunt.registerTask("testsapp", [
"clean:workingDir",
"mkdir:workingDir",
getPlatformSpecificTask("exec:kill{platform}Emulator"),
getPlatformSpecificTask("startEmulator{platform}"),
"exec:createApp",
"clean:originalAppDir",
"copy:testsAppToRunDir",
"clean:modules",
"untar:modules",
"copy:modulesToDir",
"clean:tempExtractedModules",
"exec:addPlatform",
getPlatformSpecificTask("copy:add{platform}Permissions"),
"shell:buildApp",
getPlatformSpecificTask("doPostPlatformAdd{platform}"),
getPlatformSpecificTask("exec:uninstallExisting{platform}App"),
getPlatformSpecificTask("exec:installNew{platform}App"),
getPlatformSpecificTask("exec:start{platform}App"),
getPlatformSpecificTask("collectLog{platform}"),
// getPlatformSpecificTask("exec:kill{platform}Emulator"),
// "clean:workingDir"
]);
}
}

20
expect.exp Executable file
View File

@@ -0,0 +1,20 @@
#! /usr/bin/expect
#exp_internal 1
#set timeout 600
#spawn grunt
#expect "TypeScript compilation complete"
#send \003
set cmd [lindex $argv 0];
set outfile [lindex $argv 1];
set timeout 600
#spawn (adb logcat | tee ./__TESTRESULT__.txt)
log_file -noappend $outfile
eval spawn $cmd
expect "=== ALL TESTS COMPLETE ==="
send \003

View File

File diff suppressed because it is too large Load Diff

View File

@@ -13,18 +13,20 @@
],
"license": "Apache-2.0",
"devDependencies": {
"chai": "3.2.0",
"grunt": "0.4.5",
"grunt-contrib-clean": "0.6.0",
"grunt-contrib-copy": "git+https://github.com/ErjanGavalji/grunt-contrib-copy.git#1c976a133210be4ce8c96313f5daf14833f7f8f9",
"grunt-env": "0.4.4",
"grunt-exec": "0.4.6",
"grunt-mkdir": "^0.1.2",
"grunt-multi-dest": "1.0.0",
"grunt-shell": "1.1.2",
"grunt-simple-mocha": "0.4.0",
"grunt-ts": "5.0.0-beta.5",
"grunt-tslint": "2.5.0",
"grunt-untar": "0.0.1",
"mocha": "2.2.5",
"grunt-simple-mocha": "0.4.0",
"grunt-env": "0.4.4",
"chai": "3.2.0",
"typescript": "1.6.2"
}
}

59
running-tests.md Normal file
View File

@@ -0,0 +1,59 @@
Running NativeScript Tests
=========================
# Details
NativeScript is a framework for building applications on mobile devices. Many
of its components are UI elements which get tested most efficiently on the
respective device or simulator/emulator. Thus, running the tests involves
the following steps:
- Create a NativeScript project
- Build it to a native image
- Start a simulator/emulator
- Deploy the application image
- Start the application
- Monitor its output
- Gather test output
These steps are automated via the `run-testsapp.grunt.js` grunt script, located
under the `build` directory. It gets called by the main `gruntfile.js`, but is
split to a separate file for simplicity.
# Prerequisites
- Node JS
- grunt
- NativeScript CLI
- Android/iOS setup
- expect
# Arguments:
>As this is a [grunt](http://gruntjs.com/) script, the arguments are passed
the grunt way (`--argName=argValue`)
- `platform`: The platform to run the tests application on: iOS or Android
- `showEmu`: [Optional] Specifies whether the emulator should get launched
in a window or headless mode. Defaults to `false`.
- `modulesPath`: [Optional] The path to the tns-core-modules npm package
to be tested. Defaults to the npm package, located in the current
`bin/dist/` folder. The modules must have been built before that.
- `tnsPath`: [Optional] The path to the NativeScript executable. If not
found, the globally installed `tns` gets called.
- `emuPId`: The ID of the emulator process. This one is used to refresh the
emulator process, as the emulator sometimes hangs.
- `avd`: The name of the Android Virtual Device or the iOS simulator GUID
to be started to run the tests.
- `logFilePath`: [Optional] The path to the file, which the test app run
log will get saved to. Defaults to "./TestRunResult.txt".
- `runtimePath`: [Optional] The path to a custom iOS or Android Runtime
package to have the tests run onto. If not specified, the newest available
build on [npmjs.com](http://npmjs.com) -
[tns-ios](https://www.npmjs.com/package/tns-ios) or
[tns-android](https://www.npmjs.com/package/tns-android).
# Sample run:
```
grunt testsapp --platform=Android [--tnsPath="tns"] --emuPId=".*emulator64-x86"
--avd="Api19" [--logFilePath="./TestRunResult.txt"] [--androidRuntimePath="./tns-android.tgz"] --showEmu=true
```