setup e2e tests

This commit is contained in:
simplyahmazing
2017-11-19 16:48:21 -05:00
parent dcd2abca6e
commit 7f567f55c3
11 changed files with 188 additions and 96 deletions

5
.gitignore vendored
View File

@@ -24,5 +24,8 @@ yarn-error.log*
# miscellaneous # miscellaneous
.idea .idea
config.gypi config.gypi
.python-version
# Test generated files
tests/package.json
.python-version

View File

@@ -18,8 +18,9 @@ node_js:
- '6' - '6'
before_install: before_install:
- npm install -g gulp - ./scripts/travis-xvfb.sh
- npm install - npm install -g gulp
- npm install
cache: cache:
directories: directories:
@@ -33,4 +34,4 @@ notifications:
urls: urls:
- https://zulip.org/zulipbot/travis - https://zulip.org/zulipbot/travis
on_success: always on_success: always
on_failure: always on_failure: always

View File

@@ -17,7 +17,8 @@ install:
- npm install - npm install
- npm install -g gulp - npm install -g gulp
build: off build: off
test_script: test_script:
- npm run test - npm run test
- npm run test-e2e

View File

@@ -1,9 +1,10 @@
'use strict'; 'use strict';
const gulp = require('gulp'); const gulp = require('gulp');
const mocha = require('gulp-mocha');
const electron = require('electron-connect').server.create({ const electron = require('electron-connect').server.create({
verbose: true verbose: true
}); });
const tape = require('gulp-tape');
const tapColorize = require('tap-colorize');
gulp.task('dev', () => { gulp.task('dev', () => {
// Start browser process // Start browser process
@@ -28,9 +29,11 @@ gulp.task('reload:renderer', done => {
done(); done();
}); });
// Test app using mocha+spectron gulp.task('test-e2e', () => {
gulp.task('test', () => { return gulp.src('tests/*.js')
return gulp.src('tests/index.js').pipe(mocha()); .pipe(tape({
reporter: tapColorize()
}));
}); });
gulp.task('default', ['dev', 'test']); gulp.task('default', ['dev', 'test-e2e']);

View File

@@ -23,6 +23,7 @@
"reinstall": "rm -rf node_modules; rm -rf app/node_modules; npm install", "reinstall": "rm -rf node_modules; rm -rf app/node_modules; npm install",
"postinstall": "electron-builder install-app-deps", "postinstall": "electron-builder install-app-deps",
"test": "xo", "test": "xo",
"test-e2e": "gulp test-e2e",
"dev": "gulp dev", "dev": "gulp dev",
"pack": "electron-builder --dir", "pack": "electron-builder --dir",
"dist": "electron-builder", "dist": "electron-builder",
@@ -105,15 +106,24 @@
], ],
"devDependencies": { "devDependencies": {
"assert": "1.4.1", "assert": "1.4.1",
"cp-file": "^5.0.0",
"devtron": "1.4.0", "devtron": "1.4.0",
"electron-builder": "19.45.5", "electron-builder": "19.45.5",
"electron": "1.6.14", "electron": "1.6.14",
"electron-builder": "19.29.1",
"electron-connect": "0.6.2", "electron-connect": "0.6.2",
"gulp": "3.9.1", "gulp": "3.9.1",
"gulp-mocha": "4.3.1", "gulp-mocha": "4.3.1",
"chai-as-promised": "7.1.1", "gulp-shell": "^0.6.3",
"chai": "4.1.1", "gulp-tape": "0.0.9",
"is-ci": "^1.0.10",
"looks-same": "^3.2.1",
"pngjs": "^3.0.0",
"pre-commit": "1.2.2",
"spectron": "3.7.2", "spectron": "3.7.2",
"tap-colorize": "^1.2.0",
"tap-spec": "^4.1.1",
"tape": "^4.8.0",
"xo": "0.18.2", "xo": "0.18.2",
"pre-commit": "1.2.2", "pre-commit": "1.2.2",
"electron-debug": "1.4.0" "electron-debug": "1.4.0"
@@ -156,5 +166,8 @@
"browser", "browser",
"mocha" "mocha"
] ]
},
"dependencies": {
"resemblejs": "^2.2.6"
} }
} }

View File

@@ -1,10 +1,17 @@
#!/usr/bin/env bash #!/usr/bin/env bash
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
export {no_proxy,NO_PROXY}="127.0.0.1,localhost" export {no_proxy,NO_PROXY}="127.0.0.1,localhost"
export DISPLAY=:99.0 export DISPLAY=:99.0
sh -e /etc/init.d/xvfb start sh -e /etc/init.d/xvfb start
sleep 3 sleep 3
echo 'Travis Screen Resolution:'
xdpyinfo | grep dimensions
fi fi
npm run test npm run test
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
npm run test-e2e
fi

5
scripts/travis-xvfb.sh Executable file
View File

@@ -0,0 +1,5 @@
#!/usr/bin/env bash
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16
fi

7
tests/config.js Normal file
View File

@@ -0,0 +1,7 @@
const path = require('path')
const TEST_APP_PRODUCT_NAME = 'ZulipTest'
module.exports = {
TEST_APP_PRODUCT_NAME
}

View File

@@ -1,81 +1,13 @@
const assert = require('assert') const test = require('tape')
const Application = require('spectron').Application const setup = require('./setup')
const chai = require('chai')
const { expect } = chai
const chaiAsPromised = require('chai-as-promised')
chai.should()
chai.use(chaiAsPromised)
describe('application launch', function () {
this.timeout(15000)
beforeEach(function () {
this.app = new Application({
path: require('electron'),
args: [__dirname + '/../app/renderer/main.html']
})
return this.app.start()
})
beforeEach(function () {
chaiAsPromised.transferPromiseness = this.app.transferPromiseness
})
afterEach(function () {
if (this.app && this.app.isRunning()) {
return this.app.stop()
}
})
it('shows an initial window', function () {
return this.app.client.waitUntilWindowLoaded(5000)
.getWindowCount().should.eventually.equal(2)
.browserWindow.isMinimized().should.eventually.be.false
.browserWindow.isDevToolsOpened().should.eventually.be.false
.browserWindow.isVisible().should.eventually.be.true
.browserWindow.isFocused().should.eventually.be.true
.browserWindow.getBounds().should.eventually.have.property('width').and.be.above(0)
.browserWindow.getBounds().should.eventually.have.property('height').and.be.above(0)
})
it('sets up a default organization', function () {
let app = this.app
let self = this
app.client.execute(() => {
window.confirm = function () { return true }
})
function createOrg (client, name, url, winIndex) {
return client
// Focus on settings webview
.then(switchToWebviewAtIndex.bind(null, self.app.client, winIndex))
.pause(1000) // wait for settings to load
// Fill settings form
.click('#new-server-action')
.setValue('input[id="server-info-name"]', name)
.setValue('input[id="server-info-url"]', url)
.click('#save-server-action')
.pause(500) // Need to pause while server verification takes place
.then(() => app.browserWindow.reload())
.pause(1500) // Wait for webview of org to load
}
function switchToWebviewAtIndex(client, index) {
return client
.windowHandles()
.then(function (session) {
this.window(session.value[index])
})
}
return this.app.client.waitUntilWindowLoaded(5000)
.then(() => createOrg(self.app.client, 'Zulip 1', 'chat.zulip.org', 1))
.then(switchToWebviewAtIndex.bind(null, self.app.client, 0))
.click('#add-action > i').pause(500)
.then(switchToWebviewAtIndex.bind(null, self.app.client, 2))
.then(() => createOrg(self.app.client, 'Zulip 2', 'chat.zulip.org', 2))
})
})
test('app runs', function (t) {
t.timeoutAfter(10e3)
setup.resetTestDataDir()
const app = setup.createApp()
setup.waitForLoad(app, t)
.then(() => app.client.windowByIndex(1)) // focus on webview
.then(() => app.client.waitForExist('//*[@id="new-server-container"]/div/div/div[1]/input'))
.then(() => setup.endTest(app, t),
(err) => setup.endTest(app, t, err || 'error'))
})

101
tests/setup.js Normal file
View File

@@ -0,0 +1,101 @@
const Application = require('spectron').Application
const cpFile = require('cp-file')
const fs = require('fs')
const isCI = require('is-ci')
const looksSame = require('looks-same')
const mkdirp = require('mkdirp')
const path = require('path')
const PNG = require('pngjs').PNG
const rimraf = require('rimraf')
const config = require('./config')
module.exports = {
createApp,
endTest,
waitForLoad,
wait,
resetTestDataDir
}
// Runs Zulip Desktop.
// Returns a promise that resolves to a Spectron Application once the app has loaded.
// Takes a Tape test. Makes some basic assertions to verify that the app loaded correctly.
function createApp (t) {
generateTestAppPackageJson()
return new Application({
path: path.join(__dirname, '..', 'node_modules', '.bin',
'electron' + (process.platform === 'win32' ? '.cmd' : '')),
args: [path.join(__dirname)], // Ensure this dir has a package.json file with a 'main' entry piont
env: {NODE_ENV: 'test'},
waitTimeout: 10e3
})
}
// Generates package.json for test app
// Reads app package.json and updates the productName to config.TEST_APP_PRODUCT_NAME
// We do this so that the app integration doesn't doesn't share the same appDataDir as the dev application
function generateTestAppPackageJson () {
let packageJson = require(path.join(__dirname, '../package.json'))
packageJson.productName = config.TEST_APP_PRODUCT_NAME
packageJson.main = '../app/main'
const testPackageJsonPath = path.join(__dirname, 'package.json')
fs.writeFileSync(testPackageJsonPath, JSON.stringify(packageJson, null, ' '), 'utf-8')
}
// Starts the app, waits for it to load, returns a promise
function waitForLoad (app, t, opts) {
if (!opts) opts = {}
return app.start().then(function () {
return app.client.waitUntilWindowLoaded()
})
.then(function() {
return app.client.pause(2000);
})
.then(function () {
return app.webContents.getTitle()
}).then(function (title) {
t.equal(title, 'Zulip', 'html title')
})
}
// Returns a promise that resolves after 'ms' milliseconds. Default: 1 second
function wait (ms) {
if (ms === undefined) ms = 1000 // Default: wait long enough for the UI to update
return new Promise(function (resolve, reject) {
setTimeout(resolve, ms)
})
}
// Quit the app, end the test, either in success (!err) or failure (err)
function endTest (app, t, err) {
return app.stop().then(function () {
t.end(err)
})
}
function getAppDataDir () {
let base
if (process.platform === 'darwin') {
base = path.join(process.env.HOME, 'Library', 'Application Support')
} else if (process.platform === 'linux') {
base = process.env.XDG_CONFIG_HOME ?
process.env.XDG_CONFIG_HOME : path.join(process.env.HOME, '.config')
} else if (process.platform === 'win32') {
base = process.env.APPDATA
} else {
console.log('Could not detect app data dir base. Exiting...')
process.exit(1)
}
console.log('Detected App Data Dir base:', base)
return path.join(base, config.TEST_APP_PRODUCT_NAME)
}
// Resets the test directory, containing domain.json, window-state.json, etc
function resetTestDataDir () {
appDataDir = getAppDataDir()
rimraf.sync(appDataDir)
rimraf.sync(path.join(__dirname, 'package.json'))
}

View File

@@ -0,0 +1,19 @@
const test = require('tape')
const setup = require('./setup')
test('add-organization', function (t) {
t.timeoutAfter(50e3)
setup.resetTestDataDir()
const app = setup.createApp()
setup.waitForLoad(app, t)
.then(() => app.client.windowByIndex(1)) // focus on webview
.then(() => app.client.setValue('.setting-input-value', 'chat.zulip.org'))
.then(() => app.client.click('.server-save-action'))
.then(() => setup.wait(5000))
.then(() => app.client.windowByIndex(0)) // Switch focus back to main win
.then(() => app.client.windowByIndex(1)) // Switch focus back to org webview
.then(() => app.client.waitForExist('//*[@id="id_username"]'))
.then(() => setup.endTest(app, t),
(err) => setup.endTest(app, t, err || 'error'))
})