Skip to content

Commit

Permalink
feat(e2e): Functioning end-to-end tests
Browse files Browse the repository at this point in the history
This adds working e2e tests, using webdriverio and mocha via a
work-in-progress fork of gulp-webdriver. Will need to investigate using
only mocha in the project, or modifying gulp-webdriver to support both
frameworks.
  • Loading branch information
jimschubert committed Jun 15, 2015
1 parent a6b3212 commit 2984097
Show file tree
Hide file tree
Showing 15 changed files with 290 additions and 122 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ To start an electron instance:
$ npm start
```

### Testing

Run `gulp specs` to run unit tests (`gulp specs:unit`) and e2e tests (`gulp specs:e2e`).

### Build

Execute a build specific to your dev machine using the `build.js` wrapper script (which provides system defaults and other stuff around electron-packager).
Expand Down
7 changes: 6 additions & 1 deletion build.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ var exec = require('child_process').execSync,
if (nodeVersion < 0.12) {
process.stderr.write('This project requires node >= 0.12');
process.exit(1);
} else {
process.stdout.write('Preparing app for platform ' + platform + ' ' + arch + '\n');
}

var electronPackager = [
Expand All @@ -24,7 +26,7 @@ var electronPackager = [
productName,
'--prune',
'--arch=' + arch,
'--version=0.26.1',
'--version=0.27.3',
'--out=' + adjustedOut,
'--platform=' + platform,
'--ignore=./builds/',
Expand All @@ -35,7 +37,10 @@ if (asar) {
electronPackager.push('--asar');
}

process.stdout.write('Removing ' + adjustedOut + '\n');
rimraf.sync(adjustedOut);

process.stdout.write('Executing ' + electronPackager.join(' ') + '\n');
exec(electronPackager.join(' '), {
cwd: __dirname
});
63 changes: 63 additions & 0 deletions build/aurelia.extensions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/* Aurelia Webdriver Plugin */

exports = module.exports = function(client){
client.timeouts('page load',20000);
client.timeouts('script',30000);
client.timeoutsImplicitWait(30000);

client.addCommand('waitForAureliaPage', function(cb){
this.executeAsync(function(done){
document.addEventListener("aurelia-composed", function (e) {
done(true)
});
}, cb);
});

client.addCommand('waitForHttpDone', function(cb){
this.executeAsync(function(done){
document.addEventListener("aurelia-http-client-requests-drained", function (e) {
done(true)
});
}, cb);
});

client.addCommand('valueBind', function(element, parent, cb){
var callback;
if('undefined' === typeof(cb)){
callback = parent;
parent = null;
} else {
callback = cb;
}

this.executeAsync(function(bindingModel, opt_parentElement, done){
var using = opt_parentElement || document;
var matches = using.querySelectorAll('*[value\\.bind="' + bindingModel +'"]');
var result;

if (matches.length === 0) {
result = null;
} else if (matches.length === 1) {
result = matches[0];
} else {
result = matches;
}
done(result);
}, element, parent, callback);
});

client.addCommand("loadAndWaitForAureliaPage", function(pageUrl, cb) {
// TODO: Investigate chromedriver/webdriver async command uncertainty
// There seems to be an issue with chromedriver executing
// the next tests before the page loaded call completes, pause seems
// to help this and should be considered only temporary.
// Calling this command's callback on process.nextTick is a last-ditch effort.
this.url(pageUrl)
.waitForAureliaPage()
.pause(200)
.call(function(){
// Attempt to avoid electron<->chromedriver issues with applying webdriverio extensions.
process.nextTick(cb);
});
});
};
2 changes: 1 addition & 1 deletion build/tasks/clean.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ var vinylPaths = require('vinyl-paths');

// deletes all files in the output path
gulp.task('clean', function() {
return gulp.src([paths.dist + '**/*.*', '!'+paths.dist+'.gitkeep'])
return gulp.src([paths.dist + '*', '!'+paths.dist+'.gitkeep'])
.pipe(vinylPaths(del));
});
57 changes: 43 additions & 14 deletions build/tasks/specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,42 @@ var path = require('path');
var fs = require('fs');
var spawn = require('child_process').spawn;
var gulp = require('gulp');
var args = require('../args');
var paths = require('../paths');
var testConfig = require('../test-config');
var gulpUtil = require('gulp-util');
var runSequence = require('run-sequence');
var webdriver = require('gulp-webdriver');
var webdriverio = require('webdriverio');

var chromedriverPort = 4321;

var isWin32 = process.platform === 'win32';

function getExecutable() {
if(isWin32){
if (isWin32) {
return path.join(testConfig.contentsDir, testConfig.name + '.exe');
} else if (process.platform === 'darwin'){
} else if (process.platform === 'darwin') {
return path.join(testConfig.contentsDir, 'MacOS', 'Electron');
} else {
return path.join(testConfig.contentsDir, testConfig.name);
}
}

function runSpecSuite(suite){
return function(done){
function runSpecSuite(suite) {
return function (done) {
var app = getExecutable();

if(!fs.existsSync(app)){
if (!fs.existsSync(app)) {
var targetPlatform = isWin32 ? 'windows' : process.platform;
var arch = (testConfig.arch||process.arch).substring((testConfig.arch||process.arch).length-2, 4);
var arch = (testConfig.arch || process.arch).substring((testConfig.arch || process.arch).length - 2, 4);

// NOTE: the system build task isn't a pre-req for the specs task because we can build in one pass
// and run tests in another pass. Not sure if this matters, though. The targetPlatform and arch
// could be moved outside of the task, concatenated to build the test system's spec, and added to the pre-reqs.
var err = new gulpUtil.PluginError({
plugin: 'specs',
message: 'You must first build your target system. run gulp build:'+targetPlatform+':'+arch
message: 'You must first build your target system. run gulp build:' + targetPlatform + ':' + arch
});
return done(err);
}
Expand All @@ -49,13 +55,13 @@ function runSpecSuite(suite){
ATOM_INTEGRATION_TESTS_ENABLED: true
};

for(var variable in process.env){
if(process.env.hasOwnProperty(variable)) {
for (var variable in process.env) {
if (process.env.hasOwnProperty(variable)) {
env[variable] = process.env[variable];
}
}

if(isWin32){
if (isWin32) {
// prepend so we'll have cmd.exe /c appname
args.unshift('/c', app);
}
Expand All @@ -76,8 +82,8 @@ function runSpecSuite(suite){
return done(new gulpUtil.PluginError(err));
});

specRun.on('close', function (code) {
if(code !== 0){
specRun.on('exit', function (code) {
if (code !== 0) {
var err = new gulpUtil.PluginError({
plugin: 'specs',
message: stdout.join('')
Expand All @@ -96,7 +102,30 @@ function runSpecSuite(suite){
// This is only relevant on 64-bit machines to test 32-bit builds.
// --name: override package name in package.json
// Must match the same name configured during the build (used in path)
gulp.task('specs', ['specs:unit', 'specs:e2e']);
gulp.task('specs', function(callback){
return runSequence(
'specs:unit', 'specs:e2e', callback
);
});

gulp.task('specs:unit', ['build'], runSpecSuite(paths.unitTestLocation));
gulp.task('specs:e2e', ['build'], runSpecSuite(paths.e2eTestLocation));
gulp.task('specs:e2e', ['build'], function () {
// TODO: modify gulp-webdriver to allow for jasmine tests? unit tests in jasmine and e2e in mocha is not ideal.
return gulp.src(paths.e2eTestLocation + '*.js', {
read: false
})
.pipe(webdriver({
host: 'localhost',
logLevel: args.debug ? 'verbose' : 'info',
port: chromedriverPort,
timeout: 90000,
desiredCapabilities: {
browserName: 'electron',
chromeOptions: {
binary: testConfig.exe,
args: ['e2e']
}
},
applyExtensions: require('../aurelia.extensions.js')
}));
});
12 changes: 12 additions & 0 deletions build/test-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,26 @@ if (process.platform == 'win32') {
config.location = path.join(paths.dist, process.platform, arch, productName + '-win32');
config.contentsDir = config.location;
config.appDir = path.join(config.location , 'resources', 'app');
config.exe = path.join(config.location, productName + '.exe');
} else if (process.platform == 'darwin') {
config.location = path.join(paths.dist, process.platform, arch, productName + '.app');
config.contentsDir = path.join(config.location, 'Contents');
config.appDir = path.join(config.contentsDir, 'Resources', 'app');
config.exe = path.join(config.contentsDir, 'MacOS', 'Electron');
} else {
config.location = path.join(paths.dist, process.platform, arch);
config.contentsDir = config.location;
config.appDir = path.join(config.location , 'resources', 'app');
config.exe = path.join(config.contentsDir, productName);
}

// The name of the gulp task that should build for the running machine
config.systemBuildTask = ['build',
process.platform === 'win32' ? 'windows': process.platform,
process.arch === 'x64' ? '64' : '32'
].join(':');

// The location of the output for the running machine
config.systemBuildDir = path.join(paths.dist, process.platform, process.arch);

module.exports = config;
10 changes: 8 additions & 2 deletions client/app/main.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
/* global aurelia */
/* eslint no-unused-vars: [1,"after-used"] */
export function configure(aurelia) {
import {bootstrap} from 'aurelia-bootstrapper';

bootstrap(aurelia => {
aurelia.use
//.defaultBindingLanguage()
//.defaultResources()
.standardConfiguration()
.developmentLogging()
//.eventAggregator()
.plugin('aurelia-animator-css');

aurelia.start().then(a => a.setRoot());
}
});

3 changes: 2 additions & 1 deletion client/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ System.config({
"*": "app/*.js",
"github:*": "jspm_packages/github/*.js",
"npm:*": "jspm_packages/npm/*.js"
}
},
"defaultJSExtensions": true
});

System.config({
Expand Down
3 changes: 1 addition & 2 deletions client/dev.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use babel';
if(args.dev){
// Make the main window centered, visible, and opened with dev tools
let currentWindow = require('remote').getCurrentWindow();
var currentWindow = require('remote').getCurrentWindow();
currentWindow.setSize(800, 600);
currentWindow.center();
currentWindow.show();
Expand Down
25 changes: 19 additions & 6 deletions client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div aurelia-app="main">
<div>
<div class="splash">
<div class="message">Aurelia Example</div>
<i class="fa fa-spinner fa-spin"></i>
Expand All @@ -16,19 +16,32 @@
<script src="jspm_packages/system.js"></script>
<script src="config.js"></script>
<script>
require('../babel').register();
var remote = require('remote');
var path = require('path');
var args = remote.getGlobal('args');

/**
* NOTE: There seems to be an issue with how chromedriver starts an electron app,
* which prevents the app from requiring relative paths. Fix is to use __dirname
* as it exists in index.js and map files from there.
* e.g.
* require(args._root + 'client/dev');
* rather than either of these:
* require('./dev');
* require(__dirname + '/dev');
*
* ** args._root ** contains trailing slash.
*/
System.config({
"baseURL": __dirname + "/"
"baseURL": args._root + 'client'
});
System.import('aurelia-bootstrapper');

System.import('main').catch(console.error.bind(console));

// additional code to run on load.
window.onload = function(){
require('./dev');
require('./test');
require(args._root + 'client/dev');
require(args._root + 'client/test');

if (args && args.requires && args.requires.length > 0) {
for (var i = 0; i < args.requires.length; i++) {
Expand Down
17 changes: 14 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ function getArgs(){

args.specsLocation = args['specs-location'];

args.e2e = args.e2e || args['test-type'] === 'webdriver';

args._root = __dirname + '/';

return args;
}

Expand Down Expand Up @@ -99,9 +103,16 @@ app.on('ready', function () {
}

mainWindow = new BrowserWindow({
width: 600,
height: 400,
resizable: false
width: args.e2e ? 800 : 600,
height: args.e2e ? 600 : 400,
resizable: false,
// show: !args.e2e,
'node-integration': true,
'web-preferences': {
javascript: true,
'web-security': false,
'experimental-features': true
}
});

mainWindow.loadUrl(`file://${__dirname}/client/index.html`);
Expand Down
Loading

0 comments on commit 2984097

Please sign in to comment.