testem 2.10.0 → 2.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/app.js +3 -1
- package/lib/launcher.js +20 -9
- package/lib/runners/browser_test_runner.js +12 -3
- package/lib/server/index.js +1 -11
- package/lib/utils/known-browsers.js +32 -0
- package/package.json +6 -6
- package/public/testem/qunit_adapter.js +1 -0
- package/public/testem/testem_client.js +21 -0
- package/public/testem/testem_connection.js +38 -7
package/lib/app.js
CHANGED
|
@@ -259,7 +259,9 @@ module.exports = class App extends EventEmitter {
|
|
|
259
259
|
id: id,
|
|
260
260
|
protocol: 'browser'
|
|
261
261
|
}, this.config).create();
|
|
262
|
-
|
|
262
|
+
const singleRun = this.config.get('single_run');
|
|
263
|
+
|
|
264
|
+
browser = new BrowserTestRunner(launcher, this.reporter, this.runnerIndex++, singleRun, this.config);
|
|
263
265
|
this.addRunner(browser);
|
|
264
266
|
}
|
|
265
267
|
|
package/lib/launcher.js
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const log = require('npmlog');
|
|
4
|
-
const
|
|
5
|
-
const mkdirp = require('mkdirp');
|
|
4
|
+
const tmp = require('tmp');
|
|
6
5
|
const path = require('path');
|
|
7
6
|
const Bluebird = require('bluebird');
|
|
8
7
|
|
|
9
8
|
const template = require('./strutils').template;
|
|
10
9
|
const ProcessCtl = require('./process-ctl');
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
// setup graceful cleanup: removes the created directories when an uncaught exception occurs.
|
|
12
|
+
tmp.setGracefulCleanup();
|
|
14
13
|
|
|
15
14
|
module.exports = class Launcher {
|
|
16
15
|
constructor(name, settings, config) {
|
|
@@ -52,10 +51,10 @@ module.exports = class Launcher {
|
|
|
52
51
|
}
|
|
53
52
|
|
|
54
53
|
launch() {
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
const settings = this.settings;
|
|
55
|
+
this.setupBrowserTmpDir();
|
|
57
56
|
|
|
58
|
-
return
|
|
57
|
+
return Bluebird.resolve().asCallback(() => {
|
|
59
58
|
if (settings.setup) {
|
|
60
59
|
return Bluebird.fromCallback(setupCallback => {
|
|
61
60
|
settings.setup.call(this, this.config, setupCallback);
|
|
@@ -129,9 +128,21 @@ module.exports = class Launcher {
|
|
|
129
128
|
}
|
|
130
129
|
}
|
|
131
130
|
|
|
131
|
+
setupBrowserTmpDir() {
|
|
132
|
+
const userDataDir = this.config.getUserDataDir();
|
|
133
|
+
const tmpPath = path.join(userDataDir, 'testem-' + this.id);
|
|
134
|
+
|
|
135
|
+
this.browserTmpDirectory = tmp.dirSync({
|
|
136
|
+
template: `${tmpPath}-XXXXXX`,
|
|
137
|
+
unsafeCleanup: true
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
132
141
|
browserTmpDir() {
|
|
133
|
-
|
|
142
|
+
if (!this.browserTmpDirectory) {
|
|
143
|
+
setupBrowserTmpDir();
|
|
144
|
+
}
|
|
134
145
|
|
|
135
|
-
return
|
|
146
|
+
return this.browserTmpDirectory.name;
|
|
136
147
|
}
|
|
137
148
|
};
|
|
@@ -128,6 +128,14 @@ module.exports = class BrowserTestRunner {
|
|
|
128
128
|
socket.on('all-test-results', this.onAllTestResults.bind(this));
|
|
129
129
|
socket.on('after-tests-complete', this.onAfterTests.bind(this));
|
|
130
130
|
|
|
131
|
+
const customBrowserSocketEvents = this.launcher.config.get('custom_browser_socket_events');
|
|
132
|
+
|
|
133
|
+
if (customBrowserSocketEvents) {
|
|
134
|
+
Object.keys(customBrowserSocketEvents).forEach((key) => {
|
|
135
|
+
socket.on(key, customBrowserSocketEvents[key].bind(this));
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
131
139
|
let tap = new BrowserTapConsumer(socket);
|
|
132
140
|
tap.on('test-result', this.onTestResult.bind(this));
|
|
133
141
|
tap.on('all-test-results', this.onAllTestResults.bind(this));
|
|
@@ -169,7 +177,8 @@ module.exports = class BrowserTestRunner {
|
|
|
169
177
|
launcherId: this.launcherId,
|
|
170
178
|
failed: result.failed,
|
|
171
179
|
pending: result.pending,
|
|
172
|
-
items: result.items
|
|
180
|
+
items: result.items,
|
|
181
|
+
originalResultObj: result
|
|
173
182
|
});
|
|
174
183
|
this.logs = [];
|
|
175
184
|
this.currentTestContext.state = 'complete';
|
|
@@ -252,8 +261,8 @@ module.exports = class BrowserTestRunner {
|
|
|
252
261
|
if (this.finished) { return; }
|
|
253
262
|
|
|
254
263
|
this.onProcessExitTimer = setTimeout(() => {
|
|
255
|
-
if (this.finished) {
|
|
256
|
-
return;
|
|
264
|
+
if (this.finished) {
|
|
265
|
+
return;
|
|
257
266
|
}
|
|
258
267
|
|
|
259
268
|
this.reportResults(new Error(this.exitRequested
|
package/lib/server/index.js
CHANGED
|
@@ -145,6 +145,7 @@ class Server extends EventEmitter {
|
|
|
145
145
|
configureExpress(app) {
|
|
146
146
|
app.engine('mustache', Mustache);
|
|
147
147
|
app.set('view options', {layout: false});
|
|
148
|
+
app.set('etag', 'strong');
|
|
148
149
|
app.use((req, res, next) => {
|
|
149
150
|
if (this.ieCompatMode) {
|
|
150
151
|
res.setHeader('X-UA-Compatible', `IE=${this.ieCompatMode}`);
|
|
@@ -234,9 +235,6 @@ class Server extends EventEmitter {
|
|
|
234
235
|
}
|
|
235
236
|
|
|
236
237
|
renderDefaultTestPage(req, res) {
|
|
237
|
-
res.header('Cache-Control', 'No-cache');
|
|
238
|
-
res.header('Pragma', 'No-cache');
|
|
239
|
-
|
|
240
238
|
var config = this.config;
|
|
241
239
|
var test_page = config.get('test_page')[0];
|
|
242
240
|
|
|
@@ -299,13 +297,6 @@ class Server extends EventEmitter {
|
|
|
299
297
|
});
|
|
300
298
|
}
|
|
301
299
|
|
|
302
|
-
killTheCache(req, res) {
|
|
303
|
-
res.setHeader('Cache-Control', 'No-cache');
|
|
304
|
-
res.setHeader('Pragma', 'No-cache');
|
|
305
|
-
delete req.headers['if-modified-since'];
|
|
306
|
-
delete req.headers['if-none-match'];
|
|
307
|
-
}
|
|
308
|
-
|
|
309
300
|
route(uri) {
|
|
310
301
|
var config = this.config;
|
|
311
302
|
var routes = config.get('routes') || config.get('route') || {};
|
|
@@ -338,7 +329,6 @@ class Server extends EventEmitter {
|
|
|
338
329
|
var routeRes = this.route(uri);
|
|
339
330
|
uri = routeRes.uri;
|
|
340
331
|
var wasRouted = routeRes.routed;
|
|
341
|
-
this.killTheCache(req, res);
|
|
342
332
|
var allowUnsafeDirs = config.get('unsafe_file_serving');
|
|
343
333
|
var filePath = path.resolve(config.resolvePath(uri));
|
|
344
334
|
var ext = path.extname(filePath);
|
|
@@ -34,6 +34,16 @@ function chromeArgs(browserTmpDir, url) {
|
|
|
34
34
|
];
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
function headlessChromeArgs(browserTmpDir, url) {
|
|
38
|
+
return [
|
|
39
|
+
'--headless',
|
|
40
|
+
'--disable-gpu',
|
|
41
|
+
'--disable-software-rasterizer',
|
|
42
|
+
'--mute-audio',
|
|
43
|
+
'--remote-debugging-port=0',
|
|
44
|
+
'--window-size=1440,900'].concat(chromeArgs(browserTmpDir, url));
|
|
45
|
+
}
|
|
46
|
+
|
|
37
47
|
// Return the catalogue of the browsers that Testem supports for the platform. Each 'browser object'
|
|
38
48
|
// will contain these fields:
|
|
39
49
|
//
|
|
@@ -96,6 +106,18 @@ module.exports = function knownBrowsers(platform, config) {
|
|
|
96
106
|
return chromeArgs(this.browserTmpDir(), url);
|
|
97
107
|
}
|
|
98
108
|
},
|
|
109
|
+
{
|
|
110
|
+
name: 'Headless Chrome',
|
|
111
|
+
possiblePath: chromeWinPaths(userHomeDir, 'Chrome').concat(chromeOSXPaths('Google Chrome')),
|
|
112
|
+
possibleExe: [
|
|
113
|
+
'google-chrome',
|
|
114
|
+
'google-chrome-stable',
|
|
115
|
+
'chrome'
|
|
116
|
+
],
|
|
117
|
+
args(config, url) {
|
|
118
|
+
return headlessChromeArgs(this.browserTmpDir(), url);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
99
121
|
|
|
100
122
|
// On Windows, Chrome Stable/Beta/Dev are installed on the same path
|
|
101
123
|
{
|
|
@@ -108,6 +130,16 @@ module.exports = function knownBrowsers(platform, config) {
|
|
|
108
130
|
return chromeArgs(this.browserTmpDir(), url);
|
|
109
131
|
}
|
|
110
132
|
},
|
|
133
|
+
{
|
|
134
|
+
name: 'Headless Chrome Beta',
|
|
135
|
+
possiblePath: chromeOSXPaths('Google Chrome Beta'),
|
|
136
|
+
possibleExe: [
|
|
137
|
+
'google-chrome-beta'
|
|
138
|
+
],
|
|
139
|
+
args(config, url) {
|
|
140
|
+
return headlessChromeArgs(this.browserTmpDir(), url);
|
|
141
|
+
}
|
|
142
|
+
},
|
|
111
143
|
{
|
|
112
144
|
name: 'Chrome Dev',
|
|
113
145
|
possiblePath: chromeOSXPaths('Google Chrome Dev'),
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"unittest",
|
|
8
8
|
"browser"
|
|
9
9
|
],
|
|
10
|
-
"version": "2.
|
|
10
|
+
"version": "2.14.0",
|
|
11
11
|
"author": "Toby Ho <airportyh@gmail.com>",
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"charm": "^1.0.0",
|
|
27
27
|
"commander": "^2.6.0",
|
|
28
28
|
"consolidate": "^0.15.1",
|
|
29
|
-
"execa": "^0.
|
|
29
|
+
"execa": "^1.0.0",
|
|
30
30
|
"express": "^4.10.7",
|
|
31
31
|
"fireworm": "^0.7.0",
|
|
32
32
|
"glob": "^7.0.4",
|
|
@@ -38,15 +38,16 @@
|
|
|
38
38
|
"lodash.find": "^4.5.1",
|
|
39
39
|
"lodash.uniqby": "^4.7.0",
|
|
40
40
|
"mkdirp": "^0.5.1",
|
|
41
|
-
"mustache": "^
|
|
41
|
+
"mustache": "^3.0.0",
|
|
42
42
|
"node-notifier": "^5.0.1",
|
|
43
43
|
"npmlog": "^4.0.0",
|
|
44
|
-
"printf": "^0.
|
|
44
|
+
"printf": "^0.5.1",
|
|
45
45
|
"rimraf": "^2.4.4",
|
|
46
46
|
"socket.io": "^2.1.0",
|
|
47
47
|
"spawn-args": "^0.2.0",
|
|
48
48
|
"styled_string": "0.0.1",
|
|
49
49
|
"tap-parser": "^7.0.0",
|
|
50
|
+
"tmp": "0.0.33",
|
|
50
51
|
"xmldom": "^0.1.19"
|
|
51
52
|
},
|
|
52
53
|
"files": [
|
|
@@ -84,8 +85,7 @@
|
|
|
84
85
|
"shelljs": "^0.8.2",
|
|
85
86
|
"sinon": "^6.0.0",
|
|
86
87
|
"sinon-chai": "^3.2.0",
|
|
87
|
-
"tape": "^4.9.1"
|
|
88
|
-
"tmp": "0.0.33"
|
|
88
|
+
"tape": "^4.9.1"
|
|
89
89
|
},
|
|
90
90
|
"bin": {
|
|
91
91
|
"testem": "./testem.js"
|
|
@@ -175,6 +175,7 @@ var Testem = {
|
|
|
175
175
|
handler.apply(this, argsWithoutFirst);
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
|
+
|
|
178
179
|
this.emitMessage.apply(this, arguments);
|
|
179
180
|
},
|
|
180
181
|
on: function(evt, callback) {
|
|
@@ -266,6 +267,11 @@ var Testem = {
|
|
|
266
267
|
case 'stop-run':
|
|
267
268
|
self.emit('after-tests-complete');
|
|
268
269
|
break;
|
|
270
|
+
default:
|
|
271
|
+
if (type.indexOf('testem:') === 0) {
|
|
272
|
+
self.emit(type, message.data);
|
|
273
|
+
}
|
|
274
|
+
break;
|
|
269
275
|
}
|
|
270
276
|
});
|
|
271
277
|
},
|
|
@@ -286,6 +292,21 @@ var Testem = {
|
|
|
286
292
|
// stringify for clients that only can handle string postMessages (IE <= 10)
|
|
287
293
|
return JSON.stringify(message);
|
|
288
294
|
},
|
|
295
|
+
removeEventCallbacks: function(evt, callback) {
|
|
296
|
+
var handlers = this.evtHandlers[evt];
|
|
297
|
+
var removeIdx = [];
|
|
298
|
+
if (typeof handlers === "undefined") {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
for (var i = 0; i < handlers.length; i++) {
|
|
302
|
+
if (handlers[i] === callback) {
|
|
303
|
+
removeIdx.push(i);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
for (var j = 0; j < removeIdx.length; j++) {
|
|
307
|
+
handlers.splice(j, 1);
|
|
308
|
+
}
|
|
309
|
+
},
|
|
289
310
|
runAfterTests: function() {
|
|
290
311
|
if (Testem.afterTestsQueue.length) {
|
|
291
312
|
var afterTestsCallback = Testem.afterTestsQueue.shift();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
/* globals document, parent,
|
|
1
|
+
/* globals document, parent, io, window, navigator */
|
|
2
|
+
/* globals module */
|
|
2
3
|
'use strict';
|
|
3
4
|
|
|
4
5
|
var socket;
|
|
@@ -37,11 +38,13 @@ function sendMessageToParent(type, data) {
|
|
|
37
38
|
parent.postMessage(message, '*');
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
var addListener
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
var addListener;
|
|
42
|
+
if (typeof window !== 'undefined') {
|
|
43
|
+
addListener = window.addEventListener ?
|
|
44
|
+
function(obj, evt, cb) { obj.addEventListener(evt, cb, false); } :
|
|
45
|
+
function(obj, evt, cb) { obj.attachEvent('on' + evt, cb); };
|
|
46
|
+
addListener(window, 'message', handleMessage);
|
|
47
|
+
}
|
|
45
48
|
|
|
46
49
|
var messageListeners = {};
|
|
47
50
|
function handleMessage(event) {
|
|
@@ -158,8 +161,21 @@ function init() {
|
|
|
158
161
|
});
|
|
159
162
|
}
|
|
160
163
|
|
|
164
|
+
function patchEmitterForWildcard(socket) {
|
|
165
|
+
var emit = io.Manager.prototype.emit;
|
|
166
|
+
|
|
167
|
+
function onevent (packet) {
|
|
168
|
+
var args = packet.data || [];
|
|
169
|
+
emit.call(this, '*', packet);
|
|
170
|
+
return emit.apply(this, args);
|
|
171
|
+
}
|
|
172
|
+
socket.onevent = onevent;
|
|
173
|
+
}
|
|
174
|
+
|
|
161
175
|
function initSocket(id) {
|
|
162
176
|
socket = io.connect({ reconnectionDelayMax: 1000, randomizationFactor: 0 });
|
|
177
|
+
patchEmitterForWildcard(socket);
|
|
178
|
+
|
|
163
179
|
socket.emit('browser-login', getBrowserName(navigator.userAgent), id);
|
|
164
180
|
socket.on('connect', function() {
|
|
165
181
|
connectStatus = 'connected';
|
|
@@ -176,6 +192,21 @@ function initSocket(id) {
|
|
|
176
192
|
socket.on('stop-run', function() {
|
|
177
193
|
sendMessageToParent('stop-run');
|
|
178
194
|
});
|
|
195
|
+
socket.on('*', function(event) {
|
|
196
|
+
if (event.data && event.data[0].indexOf('testem:') === 0 ) {
|
|
197
|
+
var eventName = event.data[0];
|
|
198
|
+
var eventData = event.data[1];
|
|
199
|
+
sendMessageToParent(eventName, eventData);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// We should only call init() if it ran in browser.
|
|
205
|
+
if (typeof window !== 'undefined') {
|
|
206
|
+
init();
|
|
179
207
|
}
|
|
180
208
|
|
|
181
|
-
|
|
209
|
+
// Exporting this as a module so that it can be unit tested in Node.
|
|
210
|
+
if (typeof module !== 'undefined') {
|
|
211
|
+
module.exports = patchEmitterForWildcard;
|
|
212
|
+
}
|