testem 2.7.2 → 2.9.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 +8 -1
- package/lib/config.js +14 -3
- package/lib/reporters/dev/split_log_panel.js +1 -1
- package/lib/reporters/dot_reporter.js +2 -2
- package/lib/reporters/teamcity_reporter.js +77 -44
- package/lib/runners/browser_test_runner.js +13 -3
- package/lib/server/index.js +6 -3
- package/package.json +6 -6
- package/public/testem/qunit_adapter.js +2 -1
- package/public/testem/testem_client.js +10 -0
- package/views/busterrunner.mustache +1 -0
- package/views/customrunner.mustache +1 -0
- package/views/jasmine2runner.mustache +1 -0
- package/views/jasminerunner.mustache +1 -0
- package/views/mochachairunner.mustache +1 -0
- package/views/mocharunner.mustache +1 -0
- package/views/qunitrunner.mustache +1 -0
- package/views/taprunner.mustache +2 -1
package/lib/app.js
CHANGED
|
@@ -150,7 +150,14 @@ module.exports = class App extends EventEmitter {
|
|
|
150
150
|
|
|
151
151
|
return Bluebird.using(this.runHook('before_tests'), () => {
|
|
152
152
|
return Bluebird.using(RunTimeout.with(this.config.get('timeout')), timeout => {
|
|
153
|
-
timeout.on('timeout', () =>
|
|
153
|
+
timeout.on('timeout', () => {
|
|
154
|
+
let timeoutSeconds = timeout.timeout;
|
|
155
|
+
if (timeoutSeconds) {
|
|
156
|
+
log.info(`Test suite execution has timed out (config.timeout = ${timeoutSeconds} seconds). Terminating all test runners`);
|
|
157
|
+
this.testSuiteTimedOut = true;
|
|
158
|
+
}
|
|
159
|
+
this.killRunners();
|
|
160
|
+
});
|
|
154
161
|
this.timeoutID = timeout.timeoutID; // TODO Remove, just for the tests
|
|
155
162
|
this.currentRun = this.singleRun(timeout);
|
|
156
163
|
this.emit('testRun');
|
package/lib/config.js
CHANGED
|
@@ -403,6 +403,14 @@ class Config {
|
|
|
403
403
|
this.getFileSet(srcFiles, srcFilesIgnore, callback);
|
|
404
404
|
}
|
|
405
405
|
|
|
406
|
+
getFooterScripts(callback) {
|
|
407
|
+
var want = this.get('footer_scripts') || [];
|
|
408
|
+
var dontWant = this.get('src_files_ignore') || '';
|
|
409
|
+
|
|
410
|
+
this.getFileSet(want, dontWant, callback);
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
|
|
406
414
|
getServeFiles(callback) {
|
|
407
415
|
let want = this.get('serve_files') || this.get('src_files') || '*.js';
|
|
408
416
|
let dontWant = this.get('serve_files_ignore') || this.get('src_files_ignore') || '';
|
|
@@ -468,9 +476,12 @@ class Config {
|
|
|
468
476
|
|
|
469
477
|
this.getCSSFiles((err, files) => {
|
|
470
478
|
ret.css_files = files.map(replaceSlashes);
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
479
|
+
this.getFooterScripts((err, files) => {
|
|
480
|
+
ret.footer_scripts = files;
|
|
481
|
+
if (cb) {
|
|
482
|
+
cb(err, ret);
|
|
483
|
+
}
|
|
484
|
+
});
|
|
474
485
|
});
|
|
475
486
|
});
|
|
476
487
|
}
|
|
@@ -80,8 +80,8 @@ module.exports = class DotReporter {
|
|
|
80
80
|
|
|
81
81
|
if ('expected' in error && 'actual' in error) {
|
|
82
82
|
printf(this.out, '\n' +
|
|
83
|
-
' expected: %O\n' +
|
|
84
|
-
' actual: %O\n', error.expected, error.actual);
|
|
83
|
+
' expected: %s%O\n' +
|
|
84
|
+
' actual: %O\n', (error.negative ? 'NOT ' : ''), error.expected, error.actual);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
if (error.stack) {
|
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
function namify(prefix, result) {
|
|
4
|
+
let line = (prefix ? (prefix + ' - ') : '') +
|
|
5
|
+
result.name.trim();
|
|
6
|
+
|
|
7
|
+
return escape(line);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Borrowed from https://github.com/travisjeffery/mocha-teamcity-reporter
|
|
12
|
+
* Escape the given `str`.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
function escape(str) {
|
|
16
|
+
if (!str) {
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
return str
|
|
20
|
+
.toString()
|
|
21
|
+
.replace(/\|/g, '||')
|
|
22
|
+
.replace(/\n/g, '|n')
|
|
23
|
+
.replace(/\r/g, '|r')
|
|
24
|
+
.replace(/\[/g, '|[')
|
|
25
|
+
.replace(/\]/g, '|]')
|
|
26
|
+
.replace(/\u0085/g, '|x')
|
|
27
|
+
.replace(/\u2028/g, '|l')
|
|
28
|
+
.replace(/\u2029/g, '|p')
|
|
29
|
+
.replace(/'/g, '|\'');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function teamcityLine(type, options) {
|
|
33
|
+
const attributes = Object.keys(options)
|
|
34
|
+
.map(attributeName => `${attributeName}='${options[attributeName]}'`)
|
|
35
|
+
.join(' ');
|
|
36
|
+
|
|
37
|
+
return `##teamcity[${type} ${attributes}]\n`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function runDurationAttribute(result) {
|
|
41
|
+
return typeof result.runDuration === 'number' ? {duration: result.runDuration} : undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = class TeamcityReporter {
|
|
4
45
|
constructor(silent, out) {
|
|
5
46
|
this.out = out || process.stdout;
|
|
6
47
|
this.silent = silent;
|
|
@@ -14,8 +55,13 @@ class TeamcityReporter {
|
|
|
14
55
|
}
|
|
15
56
|
|
|
16
57
|
report(prefix, data) {
|
|
17
|
-
|
|
58
|
+
const name = namify(prefix, data);
|
|
59
|
+
this.out.write(teamcityLine('testStarted', {name}));
|
|
18
60
|
this._display(prefix, data);
|
|
61
|
+
this.out.write(teamcityLine('testFinished', Object.assign(
|
|
62
|
+
{name},
|
|
63
|
+
runDurationAttribute(data)
|
|
64
|
+
)));
|
|
19
65
|
this.total++;
|
|
20
66
|
if (data.skipped) {
|
|
21
67
|
this.skipped++;
|
|
@@ -30,7 +76,10 @@ class TeamcityReporter {
|
|
|
30
76
|
}
|
|
31
77
|
this.endTime = new Date();
|
|
32
78
|
this.out.write('\n\n');
|
|
33
|
-
this.out.write('
|
|
79
|
+
this.out.write(teamcityLine('testSuiteFinished', {
|
|
80
|
+
name: 'testem.suite',
|
|
81
|
+
duration: Math.round((this.endTime - this.startTime)),
|
|
82
|
+
}));
|
|
34
83
|
this.out.write('\n\n');
|
|
35
84
|
}
|
|
36
85
|
|
|
@@ -38,52 +87,36 @@ class TeamcityReporter {
|
|
|
38
87
|
if (this.silent) {
|
|
39
88
|
return;
|
|
40
89
|
}
|
|
90
|
+
const name = namify(prefix, result);
|
|
91
|
+
|
|
41
92
|
if (result.skipped) {
|
|
42
|
-
this.out.write('
|
|
93
|
+
this.out.write(teamcityLine('testIgnored', {
|
|
94
|
+
name,
|
|
95
|
+
message: 'pending',
|
|
96
|
+
}));
|
|
43
97
|
} else if (!result.passed) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
this.out.write('##teamcity[testFailed name=\'' + this._namify(prefix, result) + '\' message=\'' + escape(message) + '\' details=\'' + escape(stack) + '\']\n');
|
|
47
|
-
}
|
|
48
|
-
this.out.write('##teamcity[testFinished name=\'' + this._namify(prefix, result) + '\'' + this._runDurationAttribute(result) + ']\n');
|
|
49
|
-
|
|
50
|
-
}
|
|
98
|
+
const hasError = result.error;
|
|
99
|
+
const attributes = {name};
|
|
51
100
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
result.name.trim();
|
|
55
|
-
return escape(line);
|
|
56
|
-
}
|
|
101
|
+
const message = (hasError && result.error.message) || '';
|
|
102
|
+
const stack = (hasError && result.error.stack) || '';
|
|
57
103
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
104
|
+
attributes.message = escape(message);
|
|
105
|
+
attributes.details = escape(stack);
|
|
61
106
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
107
|
+
if (
|
|
108
|
+
hasError &&
|
|
109
|
+
result.error.hasOwnProperty('expected') &&
|
|
110
|
+
result.error.hasOwnProperty('actual')
|
|
111
|
+
) {
|
|
112
|
+
attributes.type = 'comparisonFailure';
|
|
113
|
+
attributes.expected = (result.error.negative ? 'NOT ' : '') + escape(result.error.expected);
|
|
114
|
+
attributes.actual = escape(result.error.actual);
|
|
115
|
+
}
|
|
66
116
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
* Escape the given `str`.
|
|
70
|
-
*/
|
|
71
|
-
|
|
72
|
-
function escape(str) {
|
|
73
|
-
if (!str) {
|
|
74
|
-
return '';
|
|
117
|
+
this.out.write(teamcityLine('testFailed', attributes));
|
|
118
|
+
}
|
|
75
119
|
}
|
|
76
|
-
|
|
77
|
-
.toString()
|
|
78
|
-
.replace(/\|/g, '||')
|
|
79
|
-
.replace(/\n/g, '|n')
|
|
80
|
-
.replace(/\r/g, '|r')
|
|
81
|
-
.replace(/\[/g, '|[')
|
|
82
|
-
.replace(/\]/g, '|]')
|
|
83
|
-
.replace(/\u0085/g, '|x')
|
|
84
|
-
.replace(/\u2028/g, '|l')
|
|
85
|
-
.replace(/\u2029/g, '|p')
|
|
86
|
-
.replace(/'/g, '|\'');
|
|
87
|
-
}
|
|
120
|
+
};
|
|
88
121
|
|
|
89
|
-
module.exports =
|
|
122
|
+
module.exports.teamcityLine = teamcityLine;
|
|
@@ -21,6 +21,7 @@ module.exports = class BrowserTestRunner {
|
|
|
21
21
|
|
|
22
22
|
this.pendingTimer = undefined;
|
|
23
23
|
this.onProcessExitTimer = undefined;
|
|
24
|
+
this.exitRequested = false;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
start(onFinish) {
|
|
@@ -61,6 +62,11 @@ module.exports = class BrowserTestRunner {
|
|
|
61
62
|
return Bluebird.resolve();
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
// mark that somebody (generally app.js) has requested that the
|
|
66
|
+
// test runner exit. This is not an unexpected exit--this setting
|
|
67
|
+
// is to tell the processExit handler that.
|
|
68
|
+
this.exitRequested = true;
|
|
69
|
+
|
|
64
70
|
log.info(`Closing browser ${this.name()}.`);
|
|
65
71
|
return this.process.kill().then(() => {
|
|
66
72
|
this.process = null;
|
|
@@ -246,11 +252,15 @@ module.exports = class BrowserTestRunner {
|
|
|
246
252
|
if (this.finished) { return; }
|
|
247
253
|
|
|
248
254
|
this.onProcessExitTimer = setTimeout(() => {
|
|
249
|
-
if (this.finished) {
|
|
250
|
-
return;
|
|
255
|
+
if (this.finished) {
|
|
256
|
+
return;
|
|
251
257
|
}
|
|
252
258
|
|
|
253
|
-
this.reportResults(new Error(
|
|
259
|
+
this.reportResults(new Error(this.exitRequested
|
|
260
|
+
? "Browser exited on request from test driver"
|
|
261
|
+
: "Browser exited unexpectedly"),
|
|
262
|
+
code,
|
|
263
|
+
browserProcess);
|
|
254
264
|
}, 1000);
|
|
255
265
|
}
|
|
256
266
|
|
package/lib/server/index.js
CHANGED
|
@@ -212,7 +212,7 @@ class Server extends EventEmitter {
|
|
|
212
212
|
}
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
renderRunnerPage(err, files, res) {
|
|
215
|
+
renderRunnerPage(err, files, footerScripts, res) {
|
|
216
216
|
var config = this.config;
|
|
217
217
|
var framework = config.get('framework') || 'jasmine';
|
|
218
218
|
var css_files = config.get('css_files');
|
|
@@ -228,7 +228,8 @@ class Server extends EventEmitter {
|
|
|
228
228
|
}[framework] + '.mustache';
|
|
229
229
|
res.render(`${__dirname}/../../views/${templateFile}`, {
|
|
230
230
|
scripts: files,
|
|
231
|
-
styles: css_files
|
|
231
|
+
styles: css_files,
|
|
232
|
+
footer_scripts: footerScripts
|
|
232
233
|
});
|
|
233
234
|
}
|
|
234
235
|
|
|
@@ -249,7 +250,9 @@ class Server extends EventEmitter {
|
|
|
249
250
|
res.redirect(url);
|
|
250
251
|
} else {
|
|
251
252
|
config.getServeFiles((err, files) => {
|
|
252
|
-
|
|
253
|
+
config.getFooterScripts((err, footerScripts) => {
|
|
254
|
+
this.renderRunnerPage(err, files, footerScripts, res);
|
|
255
|
+
});
|
|
253
256
|
});
|
|
254
257
|
}
|
|
255
258
|
}
|
package/package.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"unittest",
|
|
8
8
|
"browser"
|
|
9
9
|
],
|
|
10
|
-
"version": "2.
|
|
10
|
+
"version": "2.9.0",
|
|
11
11
|
"author": "Toby Ho <airportyh@gmail.com>",
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|
|
@@ -75,16 +75,16 @@
|
|
|
75
75
|
"chai-shallow-deep-equal": "^1.4.4",
|
|
76
76
|
"cheerio": "^1.0.0-rc.2",
|
|
77
77
|
"dirty-chai": "^2.0.0",
|
|
78
|
-
"eslint": "^
|
|
78
|
+
"eslint": "^5.0.0",
|
|
79
79
|
"eslint-plugin-chai-expect": "^1.1.1",
|
|
80
80
|
"eslint-plugin-mocha": "^5.0.0",
|
|
81
81
|
"mocha": "^3.0.0",
|
|
82
82
|
"request": "^2.51.0",
|
|
83
|
-
"saucie": "^3.
|
|
83
|
+
"saucie": "^3.3.2",
|
|
84
84
|
"shelljs": "^0.8.2",
|
|
85
|
-
"sinon": "^
|
|
86
|
-
"sinon-chai": "^3.
|
|
87
|
-
"tape": "^4.
|
|
85
|
+
"sinon": "^6.0.0",
|
|
86
|
+
"sinon-chai": "^3.2.0",
|
|
87
|
+
"tape": "^4.9.1",
|
|
88
88
|
"tmp": "0.0.33"
|
|
89
89
|
},
|
|
90
90
|
"bin": {
|
|
@@ -409,5 +409,15 @@ function emit() {
|
|
|
409
409
|
|
|
410
410
|
if (typeof window !== 'undefined') {
|
|
411
411
|
window.Testem = Testem;
|
|
412
|
+
|
|
413
|
+
// Stub window.alert and window.confirm to throw error so alert and confirm does not get used in tests
|
|
414
|
+
// this will prevent browser disconnect failures
|
|
415
|
+
window.alert = function() {
|
|
416
|
+
throw new Error('[Testem] Calling window.alert() in tests is disabled, because it causes testem to fail with browser disconnect error.');
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
window.confirm = function() {
|
|
420
|
+
throw new Error('[Testem] Calling window.confirm() in tests is disabled, because it causes testem to fail with browser disconnect error.');
|
|
421
|
+
}
|
|
412
422
|
init();
|
|
413
423
|
}
|
package/views/taprunner.mustache
CHANGED
|
@@ -16,5 +16,6 @@ Testem.handleConsoleMessage = function(msg){
|
|
|
16
16
|
}
|
|
17
17
|
</script>
|
|
18
18
|
{{#scripts}}<script src="{{{src}}}"{{#attrs}} {{&.}}{{/attrs}}></script>{{/scripts}}
|
|
19
|
+
{{#footer_scripts}}<script src="{{{src}}}"{{#attrs}} {{&.}}{{/attrs}}></script>{{/footer_scripts}}
|
|
19
20
|
</body>
|
|
20
|
-
</html>
|
|
21
|
+
</html>
|