Add Nightwatch.js E2E tests

Nightwatch uses Selenium, so we can run tests in real browsers. This
makes it easier to test features that use the web cryptography API.
This commit is contained in:
Alan Friedman 2016-02-24 10:01:08 -05:00
parent 078d10d177
commit 7bf011d161
10 changed files with 331 additions and 10 deletions

25
.travis.yml Normal file
View File

@ -0,0 +1,25 @@
language: node_js
node_js:
- 5.2.0
sudo: required
cache:
directories:
- node_modules
before_install:
- sudo apt-get update
- sudo apt-get install -y libappindicator1 fonts-liberation
- wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
- sudo dpkg -i google-chrome*.deb
- wget http://chromedriver.storage.googleapis.com/2.21/chromedriver_linux64.zip
- unzip chromedriver_linux64
- sudo mv chromedriver /usr/bin
before_script:
- export CHROME_BIN=/usr/bin/google-chrome
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
- "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16"
- sleep 5 # give xvfb some time to start
- gulp bundle
- node index.js &
- sleep 5
script: node_modules/mocha/bin/mocha test/unit --compilers js:babel-core/register && node_modules/nightwatch/bin/nightwatch --test test/acceptance/index.js --config test/acceptance/nightwatch.json -e chrome

View File

@ -25,7 +25,7 @@ gulp.task('start', function() {
nodemon({
script: 'index.js',
ext: 'css js mustache',
ignore: ['src/public/main.js'],
ignore: ['src/public/main.js', 'test'],
env: {
'NODE_ENV': 'development'
},
@ -34,9 +34,31 @@ gulp.task('start', function() {
});
gulp.task('test', function() {
let test = spawn(
'mocha',
['test', '--compilers', 'js:babel-core/register'],
let unitTest = spawn(
'node_modules/mocha/bin/mocha',
['test/unit', '--compilers', 'js:babel-core/register'],
{stdio: 'inherit'}
);
unitTest.on('exit', function() {
// Start app
let app = spawn('node', ['index.js']);
app.stdout.on('data', function(data) {
console.log(String(data));
});
let acceptanceTest = spawn(
'node_modules/nightwatch/bin/nightwatch',
['--test', 'test/acceptance/index.js', '--config', 'test/acceptance/nightwatch.json'],
{stdio: 'inherit'}
);
acceptanceTest.on('exit', function() {
// Kill app Node process when tests are done
app.kill();
});
});
});

View File

@ -9,6 +9,7 @@
"browserify": "^13.0.0",
"compression": "^1.6.0",
"express": "^4.13.3",
"forever": "^0.15.1",
"gulp": "^3.9.0",
"gulp-uglify": "^1.5.1",
"mustache-express": "^1.2.2",
@ -27,10 +28,17 @@
"gulp-nodemon": "^2.0.6",
"jscs": "^2.10.1",
"jshint": "^2.9.1",
"mocha": "^2.4.5",
"mocha-jscs": "^4.2.0",
"mocha-jshint": "^2.3.1",
"nightwatch": "^0.8.16",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0"
"vinyl-source-stream": "^1.1.0",
"zombie": "^4.2.1"
},
"scripts": {
"dev": "gulp start",
"test": "gulp test"
},
"author": "Daniel Seripap",
"license": "MIT"

90
test/acceptance/app.js Normal file
View File

@ -0,0 +1,90 @@
describe('Darkwire', () => {
describe('starting a room', () => {
let browser;
before((client, done) => {
browser = client
.url('http://localhost:3000/', () => {
done();
});
});
after((client, done) => {
browser.end(() => {
done();
});
});
afterEach((client, done) => {
done();
});
beforeEach((client, done) => {
done();
});
it('should show welcome modal', () => {
browser
.waitForElementVisible('#first-modal', 5000)
.assert.containsText('#first-modal .modal-title', 'Welcome to darkwire.io');
});
it('should have correct header', () => {
browser.expect.element('#first-modal .modal-title').text.to.equal('Welcome to darkwire.io');
});
describe('opening a second window', () => {
before((client, done) => {
browser.url((result) => {
let urlSplit = result.value.split('/');
let roomId = urlSplit[urlSplit.length - 1];
let url = 'http://localhost:3000/' + roomId;
browser.execute(() => {
window.open('http://localhost:3000/', '_blank');
}, [], () => {
browser.window_handles((result) => {
browser.switchWindow(result.value[1], () => {
browser.execute((id) => {
window.open('http://localhost:3000/' + id, '_self');
}, [roomId], () => {
done();
});
});
});
});
});
});
it('should not show welcome modal', () => {
browser.assert.hidden('#first-modal');
});
describe('sending messages', () => {
before((client, done) => {
browser.waitForElementPresent('ul.users li:nth-child(2)', 5000, () => {
browser.setValue('input.inputMessage', ['Hello world', browser.Keys.RETURN], () => {
done();
});
});
});
it('should work', () => {
browser.window_handles((result) => {
browser.switchWindow(result.value[0], () => {
browser.waitForElementPresent('span.messageBody', 5000, () => {
browser.assert.containsText('span.messageBody', 'Hello world');
});
});
});
});
});
});
});
});

2
test/acceptance/index.js Normal file
View File

@ -0,0 +1,2 @@
require('babel-register')();
require('./app.js');

View File

@ -0,0 +1,49 @@
{
"src_folders" : ["test"],
"output_folder" : "reports",
"custom_commands_path" : "",
"custom_assertions_path" : "",
"page_objects_path" : "",
"globals_path" : "",
"test_runner" : "mocha",
"selenium" : {
"start_process" : true,
"server_path" : "test/acceptance/bin/selenium-server-standalone-2.52.0.jar",
"log_path" : false,
"host" : "127.0.0.1",
"port" : 4444,
"cli_args" : {
"webdriver.chrome.driver" : "/usr/bin/chromedriver",
"webdriver.ie.driver" : ""
}
},
"test_settings" : {
"default" : {
"launch_url" : "http://localhost",
"selenium_port" : 4444,
"selenium_host" : "localhost",
"silent": true,
"screenshots" : {
"enabled" : false,
"path" : ""
},
"desiredCapabilities": {
"browserName": "firefox",
"javascriptEnabled": true,
"acceptSslCerts": true
}
},
"chrome" : {
"desiredCapabilities": {
"browserName": "chrome",
"javascriptEnabled": true,
"acceptSslCerts": true,
"chromeOptions" : {
"args" : ["-e", "--no-sandbox"]
}
}
}
}
}

View File

@ -1,5 +0,0 @@
import mochaJSCS from 'mocha-jscs';
import mochaJSHint from 'mocha-jshint';
mochaJSCS();
mochaJSHint();

50
test/helpers.js Normal file
View File

@ -0,0 +1,50 @@
var helpers = {
polyfillCrypto: () => {
window.crypto = {
subtle: {
generateKey: () => {
return new Promise((resolve, reject) => {
resolve({});
});
},
exportKey: () => {
return new Promise((resolve, reject) => {
resolve([{}]);
});
},
importKey: () => {
return new Promise((resolve, reject) => {
resolve([{}]);
});
},
encrypt: () => {
return {};
},
decrypt: (opts, key, data) => {
if (opts.name === 'AES-CBC') {
// This means it's decrypted a message
return new Promise((resolve, reject) => {
// "Hello world" as an array buffer
resolve(new Uint8Array([72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]));
});
} else {
return new Promise((resolve, reject) => {
resolve({});
});
}
},
sign: () => {
return {};
},
verify: () => {
return true;
}
},
getRandomValues: () => {
return [1,2,3,4];
}
};
}
};
module.exports = helpers;

80
test/unit/app.js Normal file
View File

@ -0,0 +1,80 @@
import helpers from './helpers';
import app from '../index';
import mochaJSCS from 'mocha-jscs';
import mochaJSHint from 'mocha-jshint';
const Browser = require('zombie');
Browser.localhost('localhost', 3000);
mochaJSCS();
mochaJSHint();
describe('Visiting /', () => {
const browser = new Browser();
before((done) => {
browser.on('active', () => {
// browser.evaluate needs a string, so this regex just extracts the body of the function as a string
browser.evaluate(helpers.polyfillCrypto.toString().match(/function[^{]+\{([\s\S]*)\}$/)[1]);
});
browser.visit('/', done);
});
it('should be successful', () => {
browser.assert.success();
});
it('should show welcome modal', () => {
browser.assert.evaluate('$("#first-modal:visible").length', 1);
browser.assert.text('#first-modal h4.modal-title', 'Welcome to darkwire.io');
});
describe('closing the initial modal', () => {
before((done) => {
browser.pressButton('#first-modal .modal-footer button', done);
});
it('should close the modal and show the main chat page', () => {
browser.assert.evaluate('$("#first-modal:hidden").length', 1);
});
describe('opening another tab', () => {
before((done) => {
let roomIdSplit = browser.url.split('/');
let roomId = roomIdSplit[roomIdSplit.length - 1];
browser.open();
browser.tabs.current = 1;
browser.visit(`/${roomId}`, done);
});
it('should be successful', () => {
browser.assert.success();
});
it('should not show welcome modal', () => {
browser.assert.evaluate('$("#first-modal.fade.in").length', 0);
});
describe('sending message', () => {
before((done) => {
browser.fill('.inputMessage', 'Hello world');
browser.click('span#send-message-btn', done);
});
it('should send message', () => {
browser.tabs.current = 0;
browser.assert.text('body', /Hello world/);
});
});
});
});
});