@wdio/spec-reporter 9.0.0-alpha.78 → 9.0.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/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +464 -525
- package/build/types.d.ts +1 -1
- package/build/types.d.ts.map +1 -1
- package/package.json +8 -6
- package/build/types.js +0 -20
- package/build/utils.js +0 -46
- /package/{LICENSE-MIT → LICENSE} +0 -0
package/build/index.d.ts
CHANGED
|
@@ -109,6 +109,6 @@ export default class SpecReporter extends WDIOReporter {
|
|
|
109
109
|
* @param isMultiremote
|
|
110
110
|
* @return {String} Enviroment string
|
|
111
111
|
*/
|
|
112
|
-
getEnviromentCombo(capability: Capabilities.
|
|
112
|
+
getEnviromentCombo(capability: Capabilities.ResolvedTestrunnerCapabilities, verbose?: boolean, isMultiremote?: boolean): string;
|
|
113
113
|
}
|
|
114
114
|
//# sourceMappingURL=index.d.ts.map
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,OAAO,YAAY,EAAE,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAY,MAAM,gBAAgB,CAAA;AAElF,OAAO,EAAE,WAAW,EAAE,KAAK,mBAAmB,EAAE,KAAK,QAAQ,EAAmB,KAAK,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAQvH,MAAM,CAAC,OAAO,OAAO,YAAa,SAAQ,YAAY;IAClD,OAAO,CAAC,UAAU,CAAY;IAC9B,OAAO,CAAC,QAAQ,CAAI;IACpB,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,cAAc,CAAmB;IACzC,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,eAAe,CAAe;IACtC,OAAO,CAAC,oBAAoB,CAA4C;IAExE,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,kBAAkB,CAAQ;IAClC,OAAO,CAAC,YAAY,CAAO;IAC3B,OAAO,CAAC,UAAU,CAAK;IAEvB,OAAO,CAAC,YAAY,CAInB;IAED,OAAO,CAAC,QAAQ,CAKf;IACD,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,uBAAuB,CAAO;gBAEzB,OAAO,EAAE,mBAAmB;IAyBzC;;;;OAIG;IACH,eAAe,CAAC,OAAO,EAAC,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM;IAItD,aAAa,CAAE,MAAM,EAAE,WAAW;IAIlC,YAAY,CAAE,KAAK,EAAE,UAAU;IAY/B,UAAU;IAIV,SAAS,CAAE,IAAI,EAAE,SAAS;IAO1B,WAAW;IAIX,UAAU,CAAE,QAAQ,EAAE,SAAS;IAM/B,UAAU,CAAE,QAAQ,EAAE,SAAS;IAM/B,UAAU,CAAE,QAAQ,EAAE,SAAS;IAO/B,WAAW,CAAE,MAAM,EAAE,WAAW;IAIhC;;OAEG;IACH,iBAAiB,CAAE,IAAI,EAAE,SAAS,GAAG,SAAS,GAAG,UAAU;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,OAAO,YAAY,EAAE,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAY,MAAM,gBAAgB,CAAA;AAElF,OAAO,EAAE,WAAW,EAAE,KAAK,mBAAmB,EAAE,KAAK,QAAQ,EAAmB,KAAK,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAQvH,MAAM,CAAC,OAAO,OAAO,YAAa,SAAQ,YAAY;IAClD,OAAO,CAAC,UAAU,CAAY;IAC9B,OAAO,CAAC,QAAQ,CAAI;IACpB,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,cAAc,CAAmB;IACzC,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,eAAe,CAAe;IACtC,OAAO,CAAC,oBAAoB,CAA4C;IAExE,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,kBAAkB,CAAQ;IAClC,OAAO,CAAC,YAAY,CAAO;IAC3B,OAAO,CAAC,UAAU,CAAK;IAEvB,OAAO,CAAC,YAAY,CAInB;IAED,OAAO,CAAC,QAAQ,CAKf;IACD,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,uBAAuB,CAAO;gBAEzB,OAAO,EAAE,mBAAmB;IAyBzC;;;;OAIG;IACH,eAAe,CAAC,OAAO,EAAC,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM;IAItD,aAAa,CAAE,MAAM,EAAE,WAAW;IAIlC,YAAY,CAAE,KAAK,EAAE,UAAU;IAY/B,UAAU;IAIV,SAAS,CAAE,IAAI,EAAE,SAAS;IAO1B,WAAW;IAIX,UAAU,CAAE,QAAQ,EAAE,SAAS;IAM/B,UAAU,CAAE,QAAQ,EAAE,SAAS;IAM/B,UAAU,CAAE,QAAQ,EAAE,SAAS;IAO/B,WAAW,CAAE,MAAM,EAAE,WAAW;IAIhC;;OAEG;IACH,iBAAiB,CAAE,IAAI,EAAE,SAAS,GAAG,SAAS,GAAG,UAAU;IAsC3D;;OAEG;IACH,WAAW,CAAC,MAAM,EAAE,WAAW;IAkD/B;;OAEG;IACH,WAAW,CAAE,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,EAAE,QAAQ;IAkC/E;;;;OAIG;IACH,gBAAgB,CAAE,MAAM,EAAE,WAAW;IAkBrC;;;;OAIG;IACH,iBAAiB,CAAE,KAAK,EAAE,UAAU;IA8BpC;;;;OAIG;IACH,gBAAgB,CAAE,aAAa,CAAC,EAAE,MAAM;IA4FxC;;;;OAIG;IACH,eAAe,CAAE,QAAQ,EAAE,MAAM;IA0BjC;;;OAGG;IACH,iBAAiB;IAmCjB;;;OAGG;IACH,gBAAgB;IA2ChB;;;;OAIG;IACH,MAAM,CAAE,GAAG,EAAE,MAAM;IAKnB;;;;OAIG;IACH,SAAS,CAAE,KAAK,CAAC,EAAE,MAAM,OAAO;IAIhC;;;;OAIG;IACH,QAAQ,CAAE,KAAK,CAAC,EAAE,MAAM,GAAG,WAAW;IAoBtC;;;;;;OAMG;IACH,kBAAkB,CAAE,UAAU,EAAE,YAAY,CAAC,8BAA8B,EAAE,OAAO,UAAO,EAAE,aAAa,UAAQ,GAAG,MAAM;CAiD9H"}
|
package/build/index.js
CHANGED
|
@@ -1,553 +1,492 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import prettyMs from "pretty-ms";
|
|
3
|
+
import { format } from "node:util";
|
|
4
|
+
import { Chalk } from "chalk";
|
|
5
|
+
import WDIOReporter, { TestStats } from "@wdio/reporter";
|
|
6
|
+
|
|
7
|
+
// src/utils.ts
|
|
8
|
+
import Table from "easy-table";
|
|
9
|
+
import { createHmac } from "node:crypto";
|
|
10
|
+
var SEPARATOR = "\u2502";
|
|
11
|
+
var buildTableData = (rows) => rows.map((row) => {
|
|
12
|
+
const tableRow = {};
|
|
13
|
+
[...row.cells, ""].forEach((cell, idx) => {
|
|
14
|
+
tableRow[idx] = (idx === 0 ? `${SEPARATOR} ` : "") + cell;
|
|
15
|
+
});
|
|
16
|
+
return tableRow;
|
|
17
|
+
});
|
|
18
|
+
var printTable = (data) => Table.print(data, void 0, (table) => {
|
|
19
|
+
table.separator = ` ${SEPARATOR} `;
|
|
20
|
+
return table.print();
|
|
21
|
+
});
|
|
22
|
+
var getFormattedRows = (table, testIndent) => table.split("\n").filter(Boolean).map((line) => `${testIndent} ${line}`.trimRight());
|
|
23
|
+
var sauceAuthenticationToken = (user, key, sessionId) => {
|
|
24
|
+
const secret = `${user}:${key}`;
|
|
25
|
+
const token = createHmac("md5", secret).update(sessionId).digest("hex");
|
|
26
|
+
return `?auth=${token}`;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// src/index.ts
|
|
30
|
+
var DEFAULT_INDENT = " ";
|
|
31
|
+
var SpecReporter = class extends WDIOReporter {
|
|
32
|
+
_suiteUids = /* @__PURE__ */ new Set();
|
|
33
|
+
_indents = 0;
|
|
34
|
+
_suiteIndents = {};
|
|
35
|
+
_orderedSuites = [];
|
|
36
|
+
_consoleOutput = "";
|
|
37
|
+
_suiteIndent = "";
|
|
38
|
+
_preface = "";
|
|
39
|
+
_consoleLogs = [];
|
|
40
|
+
_pendingReasons = [];
|
|
41
|
+
_originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
42
|
+
_addConsoleLogs = false;
|
|
43
|
+
_realtimeReporting = false;
|
|
44
|
+
_showPreface = true;
|
|
45
|
+
_suiteName = "";
|
|
46
|
+
// Keep track of the order that suites were called
|
|
47
|
+
_stateCounts = {
|
|
48
|
+
passed: 0,
|
|
49
|
+
failed: 0,
|
|
50
|
+
skipped: 0
|
|
51
|
+
};
|
|
52
|
+
_symbols = {
|
|
53
|
+
passed: "\u2713",
|
|
54
|
+
skipped: "-",
|
|
55
|
+
pending: "?",
|
|
56
|
+
failed: "\u2716"
|
|
57
|
+
};
|
|
58
|
+
_chalk;
|
|
59
|
+
_onlyFailures = false;
|
|
60
|
+
_sauceLabsSharableLinks = true;
|
|
61
|
+
constructor(options) {
|
|
62
|
+
super(Object.assign({ stdout: true }, options));
|
|
63
|
+
this._symbols = { ...this._symbols, ...this.options.symbols || {} };
|
|
64
|
+
this._onlyFailures = options.onlyFailures || false;
|
|
65
|
+
this._realtimeReporting = options.realtimeReporting || false;
|
|
66
|
+
this._showPreface = options.showPreface !== false;
|
|
67
|
+
this._sauceLabsSharableLinks = "sauceLabsSharableLinks" in options ? options.sauceLabsSharableLinks : this._sauceLabsSharableLinks;
|
|
68
|
+
const processObj = process;
|
|
69
|
+
if (options.addConsoleLogs || this._addConsoleLogs) {
|
|
70
|
+
processObj.stdout.write = (chunk, encoding, callback) => {
|
|
71
|
+
if (typeof chunk === "string" && !chunk.includes("mwebdriver")) {
|
|
72
|
+
this._consoleOutput += chunk;
|
|
58
73
|
}
|
|
59
|
-
this.
|
|
74
|
+
return this._originalStdoutWrite(chunk, encoding, callback);
|
|
75
|
+
};
|
|
60
76
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
77
|
+
this._chalk = new Chalk(options.color === false ? { level: 0 } : {});
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* @param state state of test execution
|
|
81
|
+
* @param msg the message to print in terminal
|
|
82
|
+
* @returns colord value based on chalk to print in terminal
|
|
83
|
+
*/
|
|
84
|
+
setMessageColor(message, state) {
|
|
85
|
+
return this._chalk[this.getColor(state)](message);
|
|
86
|
+
}
|
|
87
|
+
onRunnerStart(runner) {
|
|
88
|
+
this._preface = this._showPreface ? `[${this.getEnviromentCombo(runner.capabilities, false, runner.isMultiremote).trim()} #${runner.cid}]` : "";
|
|
89
|
+
}
|
|
90
|
+
onSuiteStart(suite) {
|
|
91
|
+
this._suiteName = suite.file?.replace(process.cwd(), "");
|
|
92
|
+
this.printCurrentStats(suite);
|
|
93
|
+
this._suiteUids.add(suite.uid);
|
|
94
|
+
if (suite.type === "feature") {
|
|
95
|
+
this._indents = 0;
|
|
96
|
+
this._suiteIndents[suite.uid] = this._indents;
|
|
97
|
+
} else {
|
|
98
|
+
this._suiteIndents[suite.uid] = ++this._indents;
|
|
68
99
|
}
|
|
69
|
-
|
|
70
|
-
|
|
100
|
+
}
|
|
101
|
+
onSuiteEnd() {
|
|
102
|
+
this._indents--;
|
|
103
|
+
}
|
|
104
|
+
onHookEnd(hook) {
|
|
105
|
+
this.printCurrentStats(hook);
|
|
106
|
+
if (hook.error) {
|
|
107
|
+
this._stateCounts.failed++;
|
|
71
108
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
109
|
+
}
|
|
110
|
+
onTestStart() {
|
|
111
|
+
this._consoleOutput = "";
|
|
112
|
+
}
|
|
113
|
+
onTestPass(testStat) {
|
|
114
|
+
this.printCurrentStats(testStat);
|
|
115
|
+
this._consoleLogs.push(this._consoleOutput);
|
|
116
|
+
this._stateCounts.passed++;
|
|
117
|
+
}
|
|
118
|
+
onTestFail(testStat) {
|
|
119
|
+
this.printCurrentStats(testStat);
|
|
120
|
+
this._consoleLogs.push(this._consoleOutput);
|
|
121
|
+
this._stateCounts.failed++;
|
|
122
|
+
}
|
|
123
|
+
onTestSkip(testStat) {
|
|
124
|
+
this.printCurrentStats(testStat);
|
|
125
|
+
this._pendingReasons.push(testStat.pendingReason);
|
|
126
|
+
this._consoleLogs.push(this._consoleOutput);
|
|
127
|
+
this._stateCounts.skipped++;
|
|
128
|
+
}
|
|
129
|
+
onRunnerEnd(runner) {
|
|
130
|
+
this.printReport(runner);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Print the report to the stdout realtime
|
|
134
|
+
*/
|
|
135
|
+
printCurrentStats(stat) {
|
|
136
|
+
if (!this._realtimeReporting) {
|
|
137
|
+
return;
|
|
92
138
|
}
|
|
93
|
-
|
|
94
|
-
|
|
139
|
+
const title = stat.title, state = stat.state;
|
|
140
|
+
const divider = "------------------------------------------------------------------";
|
|
141
|
+
const indent = stat.type === "test" ? `${DEFAULT_INDENT}${this._suiteIndent}` : this.indent(stat.uid);
|
|
142
|
+
const suiteStartBanner = stat.type === "feature" || stat.type === "suite" || stat.type === "suite:start" ? `${this._preface} ${divider}
|
|
143
|
+
${this._preface} Suite started:
|
|
144
|
+
${this._preface} \xBB ${this._suiteName}
|
|
145
|
+
` : "\n";
|
|
146
|
+
const content = stat.type === "test" ? `${this._preface} ${indent}${this.setMessageColor(this.getSymbol(state), state)} ${title} \xBB ${this.setMessageColor("[", state)} ${this._suiteName} ${this.setMessageColor("]", state)}` : stat.type !== "hook" ? `${suiteStartBanner}${this._preface} ${title}` : title ? `${this._preface} Hook executed: ${title}` : void 0;
|
|
147
|
+
if (process.send && content && !process.env.VITEST_WORKER_ID) {
|
|
148
|
+
process.send({ name: "reporterRealTime", content });
|
|
95
149
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Print the report to the screen
|
|
153
|
+
*/
|
|
154
|
+
printReport(runner) {
|
|
155
|
+
if (runner.failures === 0 && this._onlyFailures === true) {
|
|
156
|
+
return;
|
|
100
157
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
158
|
+
const duration = `(${prettyMs(runner._duration)})`;
|
|
159
|
+
const preface = `[${this.getEnviromentCombo(runner.capabilities, false, runner.isMultiremote).trim()} #${runner.cid}]`;
|
|
160
|
+
const divider = "------------------------------------------------------------------";
|
|
161
|
+
const results = this.getResultDisplay(preface);
|
|
162
|
+
if (results.length === 0) {
|
|
163
|
+
return;
|
|
105
164
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
165
|
+
const testLinks = runner.isMultiremote ? Object.entries(runner.capabilities).map(([instanceName, capabilities]) => this.getTestLink({
|
|
166
|
+
capabilities,
|
|
167
|
+
sessionId: capabilities.sessionId,
|
|
168
|
+
isMultiremote: runner.isMultiremote,
|
|
169
|
+
instanceName
|
|
170
|
+
})).filter((links) => links.length) : this.getTestLink(runner);
|
|
171
|
+
const output = [
|
|
172
|
+
...this.getHeaderDisplay(runner),
|
|
173
|
+
"",
|
|
174
|
+
...results,
|
|
175
|
+
...this.getCountDisplay(duration),
|
|
176
|
+
...this.getFailureDisplay(),
|
|
177
|
+
...testLinks.length ? ["", ...testLinks] : []
|
|
178
|
+
];
|
|
179
|
+
const prefacedOutput = this._showPreface ? output.map((value) => {
|
|
180
|
+
return value ? `${preface} ${value}` : preface;
|
|
181
|
+
}) : output;
|
|
182
|
+
this.write(`${divider}
|
|
183
|
+
${prefacedOutput.join("\n")}
|
|
184
|
+
`);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* get link to saucelabs job
|
|
188
|
+
*/
|
|
189
|
+
getTestLink({ sessionId, isMultiremote, instanceName, capabilities }) {
|
|
190
|
+
const config = this.runnerStat && this.runnerStat.instanceOptions[sessionId];
|
|
191
|
+
const isSauceJob = config && config.hostname && config.hostname.includes("saucelabs") || // only show if multiremote is not used
|
|
192
|
+
capabilities["sauce:options"];
|
|
193
|
+
if (isSauceJob && config && config.user && config.key && sessionId) {
|
|
194
|
+
const multiremoteNote = isMultiremote ? ` ${instanceName}` : "";
|
|
195
|
+
const note = "Check out%s job at %s";
|
|
196
|
+
if ("testobject_test_report_url" in capabilities) {
|
|
197
|
+
return [format(note, multiremoteNote, capabilities.testobject_test_report_url)];
|
|
198
|
+
}
|
|
199
|
+
const isUSEast1 = config.headless || config.hostname?.includes("us-east-1");
|
|
200
|
+
const isUSEast4 = ["us-east-4"].includes(config?.region || "") || config.hostname?.includes("us-east-4");
|
|
201
|
+
const isEUCentral = ["eu", "eu-central-1"].includes(config?.region || "") || config.hostname?.includes("eu-central");
|
|
202
|
+
const isAPAC = ["apac", "apac-southeast-1"].includes(config?.region || "") || config.hostname?.includes("apac");
|
|
203
|
+
const dc = isUSEast1 ? ".us-east-1" : isUSEast4 ? ".us-east-4" : isEUCentral ? ".eu-central-1" : isAPAC ? ".apac-southeast-1" : "";
|
|
204
|
+
const sauceLabsSharableLinks = this._sauceLabsSharableLinks ? sauceAuthenticationToken(config.user, config.key, sessionId) : "";
|
|
205
|
+
const sauceUrl = `https://app${dc}.saucelabs.com/tests/${sessionId}${sauceLabsSharableLinks}`;
|
|
206
|
+
return [format(note, multiremoteNote, sauceUrl)];
|
|
111
207
|
}
|
|
112
|
-
|
|
113
|
-
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Get the header display for the report
|
|
212
|
+
* @param {Object} runner Runner data
|
|
213
|
+
* @return {Array} Header data
|
|
214
|
+
*/
|
|
215
|
+
getHeaderDisplay(runner) {
|
|
216
|
+
const combo = this.getEnviromentCombo(runner.capabilities, void 0, runner.isMultiremote).trim();
|
|
217
|
+
const output = [`Running: ${combo}`];
|
|
218
|
+
if (runner.capabilities.sessionId) {
|
|
219
|
+
output.push(`Session ID: ${runner.capabilities.sessionId}`);
|
|
114
220
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
221
|
+
return output;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* returns everything worth reporting from a suite
|
|
225
|
+
* @param {Object} suite test suite containing tests and hooks
|
|
226
|
+
* @return {Object[]} list of events to report
|
|
227
|
+
*/
|
|
228
|
+
getEventsToReport(suite) {
|
|
229
|
+
return [
|
|
230
|
+
/**
|
|
231
|
+
* Generate a report that shows all tests except those that failed but passed on retry, and only display failed hooks.
|
|
232
|
+
*/
|
|
233
|
+
...suite.hooksAndTests.reduce((accumulator, currentItem) => {
|
|
234
|
+
if (currentItem instanceof TestStats) {
|
|
235
|
+
const existingTestIndex = accumulator.findIndex((test) => test instanceof TestStats && test.fullTitle === currentItem.fullTitle);
|
|
236
|
+
if (existingTestIndex === -1) {
|
|
237
|
+
accumulator.push(currentItem);
|
|
238
|
+
} else {
|
|
239
|
+
const existingTest = accumulator[existingTestIndex];
|
|
240
|
+
if (currentItem.retries !== void 0 && existingTest.retries !== void 0) {
|
|
241
|
+
if (currentItem.retries > existingTest.retries) {
|
|
242
|
+
accumulator.splice(existingTestIndex, 1, currentItem);
|
|
243
|
+
} else {
|
|
244
|
+
accumulator.push(currentItem);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
} else {
|
|
249
|
+
accumulator.push(currentItem);
|
|
121
250
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
251
|
+
return accumulator;
|
|
252
|
+
}, []).filter((item) => Object.keys(item).length > 0).filter((item) => {
|
|
253
|
+
return item.type === "test" || Boolean(item.error);
|
|
254
|
+
})
|
|
255
|
+
];
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Get the results from the tests
|
|
259
|
+
* @param {Array} suites Runner suites
|
|
260
|
+
* @return {Array} Display output list
|
|
261
|
+
*/
|
|
262
|
+
getResultDisplay(prefaceString) {
|
|
263
|
+
const output = [];
|
|
264
|
+
const preface = this._showPreface ? prefaceString : "";
|
|
265
|
+
const suites = this.getOrderedSuites();
|
|
266
|
+
const specFileReferences = [];
|
|
267
|
+
for (const suite of suites) {
|
|
268
|
+
if (suite.tests.length === 0 && suite.suites.length === 0 && suite.hooks.length === 0) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
const suiteIndent = this.indent(suite.uid);
|
|
272
|
+
if (suite.file && !specFileReferences.includes(suite.file)) {
|
|
273
|
+
output.push(`${suiteIndent}\xBB ${suite.file.replace(process.cwd(), "")}`);
|
|
274
|
+
specFileReferences.push(suite.file);
|
|
275
|
+
}
|
|
276
|
+
output.push(`${suiteIndent}${suite.title}`);
|
|
277
|
+
if (suite.description) {
|
|
278
|
+
output.push(...suite.description.trim().split("\n").map((l) => `${suiteIndent}${this.setMessageColor(l.trim())}`));
|
|
279
|
+
output.push("");
|
|
280
|
+
}
|
|
281
|
+
if (suite.rule) {
|
|
282
|
+
output.push(...suite.rule.trim().split("\n").map((l) => `${suiteIndent}${this.setMessageColor(l.trim())}`));
|
|
283
|
+
}
|
|
284
|
+
const eventsToReport = this.getEventsToReport(suite);
|
|
285
|
+
for (const test of eventsToReport) {
|
|
286
|
+
const testTitle = `${test.title} ${test instanceof TestStats && test.retries && test.retries > 0 ? `(${test.retries} retries)` : ""}`;
|
|
287
|
+
const state = test.state;
|
|
288
|
+
const testIndent = `${DEFAULT_INDENT}${suiteIndent}`;
|
|
289
|
+
output.push(`${testIndent}${this.setMessageColor(this.getSymbol(state), state)} ${testTitle.trim()}`);
|
|
290
|
+
const arg = test.argument;
|
|
291
|
+
if (typeof arg === "string") {
|
|
292
|
+
const docstringIndent = " ";
|
|
293
|
+
const docstringMark = `${testIndent}${docstringIndent}"""`;
|
|
294
|
+
const docstring = String(arg);
|
|
295
|
+
const formattedDocstringLines = docstring.split("\n").filter((line) => line).map((line) => `${testIndent}${docstringIndent}${line}`);
|
|
296
|
+
output.push(...[docstringMark, ...formattedDocstringLines, docstringMark]);
|
|
297
|
+
} else {
|
|
298
|
+
const dataTable = arg;
|
|
299
|
+
if (dataTable && dataTable.rows && dataTable.rows.length) {
|
|
300
|
+
const data = buildTableData(dataTable.rows);
|
|
301
|
+
const rawTable = printTable(data);
|
|
302
|
+
const table = getFormattedRows(rawTable, testIndent);
|
|
303
|
+
output.push(...table);
|
|
304
|
+
}
|
|
142
305
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
// Don't print non failed tests
|
|
149
|
-
if (runner.failures === 0 && this._onlyFailures === true) {
|
|
150
|
-
return;
|
|
306
|
+
const pendingItem = this._pendingReasons.shift();
|
|
307
|
+
if (pendingItem) {
|
|
308
|
+
output.push("");
|
|
309
|
+
output.push(testIndent.repeat(2) + ".........Pending Reasons.........");
|
|
310
|
+
output.push(testIndent.repeat(3) + pendingItem?.replace(/\n/g, "\n".concat(preface + " ", testIndent.repeat(3))));
|
|
151
311
|
}
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
// If there are no test results then return nothing
|
|
158
|
-
if (results.length === 0) {
|
|
159
|
-
return;
|
|
312
|
+
const logItem = this._consoleLogs.shift();
|
|
313
|
+
if (logItem) {
|
|
314
|
+
output.push("");
|
|
315
|
+
output.push(testIndent.repeat(2) + ".........Console Logs.........");
|
|
316
|
+
output.push(testIndent.repeat(3) + logItem?.replace(/\n/g, "\n".concat(preface + " ", testIndent.repeat(3))));
|
|
160
317
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
isMultiremote: runner.isMultiremote,
|
|
166
|
-
instanceName
|
|
167
|
-
})).filter((links) => links.length)
|
|
168
|
-
: this.getTestLink(runner);
|
|
169
|
-
const output = [
|
|
170
|
-
...this.getHeaderDisplay(runner),
|
|
171
|
-
'',
|
|
172
|
-
...results,
|
|
173
|
-
...this.getCountDisplay(duration),
|
|
174
|
-
...this.getFailureDisplay(),
|
|
175
|
-
...(testLinks.length
|
|
176
|
-
/**
|
|
177
|
-
* if we have test links add an empty line
|
|
178
|
-
*/
|
|
179
|
-
? ['', ...testLinks]
|
|
180
|
-
: [])
|
|
181
|
-
];
|
|
182
|
-
// Prefix all values with the browser information
|
|
183
|
-
const prefacedOutput = this._showPreface ? output.map((value) => {
|
|
184
|
-
return value ? `${preface} ${value}` : preface;
|
|
185
|
-
}) : output;
|
|
186
|
-
// Output the results
|
|
187
|
-
this.write(`${divider}\n${prefacedOutput.join('\n')}\n`);
|
|
318
|
+
}
|
|
319
|
+
if (eventsToReport.length) {
|
|
320
|
+
output.push("");
|
|
321
|
+
}
|
|
188
322
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
(capabilities.alwaysMatch &&
|
|
203
|
-
capabilities.alwaysMatch['sauce:options'])));
|
|
204
|
-
if (isSauceJob && config && config.user && config.key && sessionId) {
|
|
205
|
-
const multiremoteNote = isMultiremote ? ` ${instanceName}` : '';
|
|
206
|
-
const note = 'Check out%s job at %s';
|
|
207
|
-
// The report url of RDC is in the caps that are returned
|
|
208
|
-
if ('testobject_test_report_url' in capabilities) {
|
|
209
|
-
return [format(note, multiremoteNote, capabilities.testobject_test_report_url)];
|
|
210
|
-
}
|
|
211
|
-
// VDC urls can be constructed / be made shared
|
|
212
|
-
const isUSEast1 = config.headless || (config.hostname?.includes('us-east-1'));
|
|
213
|
-
const isUSEast4 = ['us-east-4'].includes(config?.region || '') || (config.hostname?.includes('us-east-4'));
|
|
214
|
-
const isEUCentral = ['eu', 'eu-central-1'].includes(config?.region || '') || (config.hostname?.includes('eu-central'));
|
|
215
|
-
const isAPAC = ['apac', 'apac-southeast-1'].includes(config?.region || '') || (config.hostname?.includes('apac'));
|
|
216
|
-
const dc = isUSEast1 ? '.us-east-1' : isUSEast4 ? '.us-east-4' : isEUCentral ? '.eu-central-1' : isAPAC ? '.apac-southeast-1' : '';
|
|
217
|
-
const sauceLabsSharableLinks = this._sauceLabsSharableLinks
|
|
218
|
-
? sauceAuthenticationToken(config.user, config.key, sessionId)
|
|
219
|
-
: '';
|
|
220
|
-
const sauceUrl = `https://app${dc}.saucelabs.com/tests/${sessionId}${sauceLabsSharableLinks}`;
|
|
221
|
-
return [format(note, multiremoteNote, sauceUrl)];
|
|
222
|
-
}
|
|
223
|
-
return [];
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* Get the header display for the report
|
|
227
|
-
* @param {Object} runner Runner data
|
|
228
|
-
* @return {Array} Header data
|
|
229
|
-
*/
|
|
230
|
-
getHeaderDisplay(runner) {
|
|
231
|
-
const combo = this.getEnviromentCombo(runner.capabilities, undefined, runner.isMultiremote).trim();
|
|
232
|
-
// Spec file name and enviroment information
|
|
233
|
-
const output = [`Running: ${combo}`];
|
|
234
|
-
/**
|
|
235
|
-
* print session ID if not multiremote
|
|
236
|
-
*/
|
|
237
|
-
// @ts-expect-error
|
|
238
|
-
if (runner.capabilities.sessionId) {
|
|
239
|
-
// @ts-expect-error
|
|
240
|
-
output.push(`Session ID: ${runner.capabilities.sessionId}`);
|
|
241
|
-
}
|
|
242
|
-
return output;
|
|
323
|
+
return output;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Get the display for passing, failing and skipped
|
|
327
|
+
* @param {string} duration Duration string
|
|
328
|
+
* @return {Array} Count display
|
|
329
|
+
*/
|
|
330
|
+
getCountDisplay(duration) {
|
|
331
|
+
const output = [];
|
|
332
|
+
if (this._stateCounts.passed > 0) {
|
|
333
|
+
const text = `${this._stateCounts.passed} passing ${duration}`;
|
|
334
|
+
output.push(this.setMessageColor(text, "passed" /* PASSED */));
|
|
335
|
+
duration = "";
|
|
243
336
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
*/
|
|
249
|
-
getEventsToReport(suite) {
|
|
250
|
-
return [
|
|
251
|
-
/**
|
|
252
|
-
* Generate a report that shows all tests except those that failed but passed on retry, and only display failed hooks.
|
|
253
|
-
*/
|
|
254
|
-
...suite.hooksAndTests.reduce((accumulator, currentItem) => {
|
|
255
|
-
if (currentItem instanceof TestStats) {
|
|
256
|
-
const existingTestIndex = accumulator.findIndex((test) => test instanceof TestStats && test.fullTitle === currentItem.fullTitle);
|
|
257
|
-
if (existingTestIndex === -1) {
|
|
258
|
-
accumulator.push(currentItem);
|
|
259
|
-
}
|
|
260
|
-
else {
|
|
261
|
-
const existingTest = accumulator[existingTestIndex];
|
|
262
|
-
if (currentItem.retries !== undefined && existingTest.retries !== undefined) {
|
|
263
|
-
if (currentItem.retries > existingTest.retries) {
|
|
264
|
-
accumulator.splice(existingTestIndex, 1, currentItem);
|
|
265
|
-
}
|
|
266
|
-
else {
|
|
267
|
-
accumulator.push(currentItem);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
else {
|
|
273
|
-
accumulator.push(currentItem);
|
|
274
|
-
}
|
|
275
|
-
return accumulator;
|
|
276
|
-
}, []).filter((item) => Object.keys(item).length > 0).filter((item) => {
|
|
277
|
-
return item.type === 'test' || Boolean(item.error);
|
|
278
|
-
})
|
|
279
|
-
];
|
|
337
|
+
if (this._stateCounts.failed > 0) {
|
|
338
|
+
const text = `${this._stateCounts.failed} failing ${duration}`.trim();
|
|
339
|
+
output.push(this.setMessageColor(text, "failed" /* FAILED */));
|
|
340
|
+
duration = "";
|
|
280
341
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
* @return {Array} Display output list
|
|
285
|
-
*/
|
|
286
|
-
getResultDisplay(prefaceString) {
|
|
287
|
-
const output = [];
|
|
288
|
-
const preface = this._showPreface ? prefaceString : '';
|
|
289
|
-
const suites = this.getOrderedSuites();
|
|
290
|
-
const specFileReferences = [];
|
|
291
|
-
for (const suite of suites) {
|
|
292
|
-
// Don't do anything if a suite has no tests or sub suites
|
|
293
|
-
if (suite.tests.length === 0 && suite.suites.length === 0 && suite.hooks.length === 0) {
|
|
294
|
-
continue;
|
|
295
|
-
}
|
|
296
|
-
// Get the indent/starting point for this suite
|
|
297
|
-
const suiteIndent = this.indent(suite.uid);
|
|
298
|
-
// Display file path of spec
|
|
299
|
-
if (suite.file && !specFileReferences.includes(suite.file)) {
|
|
300
|
-
output.push(`${suiteIndent}» ${suite.file.replace(process.cwd(), '')}`);
|
|
301
|
-
specFileReferences.push(suite.file);
|
|
302
|
-
}
|
|
303
|
-
// Display the title of the suite
|
|
304
|
-
output.push(`${suiteIndent}${suite.title}`);
|
|
305
|
-
// display suite description (Cucumber only)
|
|
306
|
-
if (suite.description) {
|
|
307
|
-
output.push(...suite.description.trim().split('\n')
|
|
308
|
-
.map((l) => `${suiteIndent}${this.setMessageColor(l.trim())}`));
|
|
309
|
-
output.push(''); // empty line
|
|
310
|
-
}
|
|
311
|
-
// display suite rule (Cucumber only)
|
|
312
|
-
if (suite.rule) {
|
|
313
|
-
output.push(...suite.rule.trim().split('\n')
|
|
314
|
-
.map((l) => `${suiteIndent}${this.setMessageColor(l.trim())}`));
|
|
315
|
-
}
|
|
316
|
-
const eventsToReport = this.getEventsToReport(suite);
|
|
317
|
-
for (const test of eventsToReport) {
|
|
318
|
-
const testTitle = `${test.title} ${(test instanceof TestStats && test.retries && test.retries > 0) ? `(${test.retries} retries)` : ''}`;
|
|
319
|
-
const state = test.state;
|
|
320
|
-
const testIndent = `${DEFAULT_INDENT}${suiteIndent}`;
|
|
321
|
-
// Output for a single test
|
|
322
|
-
output.push(`${testIndent}${this.setMessageColor(this.getSymbol(state), state)} ${testTitle.trim()}`);
|
|
323
|
-
// print cucumber data table cells and docstring
|
|
324
|
-
const arg = test.argument;
|
|
325
|
-
if (typeof (arg) === 'string') {
|
|
326
|
-
// Doc string is the same with the indent on the output for a single test
|
|
327
|
-
const docstringIndent = ' ';
|
|
328
|
-
const docstringMark = `${testIndent}${docstringIndent}"""`;
|
|
329
|
-
const docstring = String(arg);
|
|
330
|
-
const formattedDocstringLines = docstring.split('\n').filter(line => line)
|
|
331
|
-
.map((line) => `${testIndent}${docstringIndent}${line}`);
|
|
332
|
-
output.push(...[docstringMark, ...formattedDocstringLines, docstringMark]);
|
|
333
|
-
}
|
|
334
|
-
else {
|
|
335
|
-
const dataTable = arg;
|
|
336
|
-
if (dataTable && dataTable.rows && dataTable.rows.length) {
|
|
337
|
-
const data = buildTableData(dataTable.rows);
|
|
338
|
-
const rawTable = printTable(data);
|
|
339
|
-
const table = getFormattedRows(rawTable, testIndent);
|
|
340
|
-
output.push(...table);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
// print pending reasons
|
|
344
|
-
const pendingItem = this._pendingReasons.shift();
|
|
345
|
-
if (pendingItem) {
|
|
346
|
-
output.push('');
|
|
347
|
-
output.push(testIndent.repeat(2) + '.........Pending Reasons.........');
|
|
348
|
-
output.push(testIndent.repeat(3) + pendingItem?.replace(/\n/g, '\n'.concat(preface + ' ', testIndent.repeat(3))));
|
|
349
|
-
}
|
|
350
|
-
// print console output
|
|
351
|
-
const logItem = this._consoleLogs.shift();
|
|
352
|
-
if (logItem) {
|
|
353
|
-
output.push('');
|
|
354
|
-
output.push(testIndent.repeat(2) + '.........Console Logs.........');
|
|
355
|
-
output.push(testIndent.repeat(3) + logItem?.replace(/\n/g, '\n'.concat(preface + ' ', testIndent.repeat(3))));
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
// Put a line break after each suite (only if tests exist in that suite)
|
|
359
|
-
if (eventsToReport.length) {
|
|
360
|
-
output.push('');
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
return output;
|
|
342
|
+
if (this._stateCounts.skipped > 0) {
|
|
343
|
+
const text = `${this._stateCounts.skipped} skipped ${duration}`.trim();
|
|
344
|
+
output.push(this.setMessageColor(text, "skipped" /* SKIPPED */));
|
|
364
345
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
if (
|
|
380
|
-
|
|
381
|
-
output.push(this.setMessageColor(text, State.FAILED));
|
|
382
|
-
duration = '';
|
|
346
|
+
return output;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Get display for failed tests, e.g. stack trace
|
|
350
|
+
* @return {Array} Stack trace output
|
|
351
|
+
*/
|
|
352
|
+
getFailureDisplay() {
|
|
353
|
+
let failureLength = 0;
|
|
354
|
+
const output = [];
|
|
355
|
+
const suites = this.getOrderedSuites();
|
|
356
|
+
for (const suite of suites) {
|
|
357
|
+
const suiteTitle = suite.title;
|
|
358
|
+
const eventsToReport = this.getEventsToReport(suite);
|
|
359
|
+
for (const test of eventsToReport) {
|
|
360
|
+
if (test.state !== "failed" /* FAILED */) {
|
|
361
|
+
continue;
|
|
383
362
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
363
|
+
const testTitle = test.title;
|
|
364
|
+
const errors = test.errors || (test.error ? [test.error] : []);
|
|
365
|
+
output.push(
|
|
366
|
+
"",
|
|
367
|
+
`${++failureLength}) ${suiteTitle} ${testTitle}`
|
|
368
|
+
);
|
|
369
|
+
for (const error of errors) {
|
|
370
|
+
!error?.stack?.includes("new AssertionError") ? output.push(this.setMessageColor(error.message, "failed" /* FAILED */)) : output.push(...error.message.split("\n"));
|
|
371
|
+
if (error.stack) {
|
|
372
|
+
output.push(...error.stack.split(/\n/g).map((value) => this.setMessageColor(value)));
|
|
373
|
+
}
|
|
388
374
|
}
|
|
389
|
-
|
|
375
|
+
}
|
|
390
376
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
const suiteTitle = suite.title;
|
|
401
|
-
const eventsToReport = this.getEventsToReport(suite);
|
|
402
|
-
for (const test of eventsToReport) {
|
|
403
|
-
if (test.state !== State.FAILED) {
|
|
404
|
-
continue;
|
|
405
|
-
}
|
|
406
|
-
const testTitle = test.title;
|
|
407
|
-
const errors = test.errors || (test.error ? [test.error] : []);
|
|
408
|
-
// If we get here then there is a failed test
|
|
409
|
-
output.push('', `${++failureLength}) ${suiteTitle} ${testTitle}`);
|
|
410
|
-
for (const error of errors) {
|
|
411
|
-
!error?.stack?.includes('new AssertionError')
|
|
412
|
-
? output.push(this.setMessageColor(error.message, State.FAILED))
|
|
413
|
-
: output.push(...error.message.split('\n'));
|
|
414
|
-
if (error.stack) {
|
|
415
|
-
output.push(...error.stack.split(/\n/g).map(value => this.setMessageColor(value)));
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
return output;
|
|
377
|
+
return output;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Get suites in the order they were called
|
|
381
|
+
* @return {Array} Ordered suites
|
|
382
|
+
*/
|
|
383
|
+
getOrderedSuites() {
|
|
384
|
+
if (this._orderedSuites.length) {
|
|
385
|
+
return this._orderedSuites;
|
|
421
386
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
if (this._orderedSuites.length) {
|
|
428
|
-
return this._orderedSuites;
|
|
429
|
-
}
|
|
430
|
-
this._orderedSuites = [];
|
|
431
|
-
for (const uid of this._suiteUids) {
|
|
432
|
-
for (const [suiteUid, suite] of Object.entries(this.suites)) {
|
|
433
|
-
if (suiteUid !== uid) {
|
|
434
|
-
continue;
|
|
435
|
-
}
|
|
436
|
-
this._orderedSuites.push(suite);
|
|
437
|
-
}
|
|
387
|
+
this._orderedSuites = [];
|
|
388
|
+
for (const uid of this._suiteUids) {
|
|
389
|
+
for (const [suiteUid, suite] of Object.entries(this.suites)) {
|
|
390
|
+
if (suiteUid !== uid) {
|
|
391
|
+
continue;
|
|
438
392
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
*/
|
|
442
|
-
const rootSuite = this.currentSuites[0];
|
|
443
|
-
if (rootSuite) {
|
|
444
|
-
const baseRootSuite = {
|
|
445
|
-
...rootSuite,
|
|
446
|
-
type: 'suite',
|
|
447
|
-
title: '(root)',
|
|
448
|
-
fullTitle: '(root)',
|
|
449
|
-
suites: []
|
|
450
|
-
};
|
|
451
|
-
const beforeAllHooks = rootSuite.hooks.filter((hook) => hook.state && hook.title.startsWith('"before') && hook.title.endsWith('"{root}"'));
|
|
452
|
-
const afterAllHooks = rootSuite.hooks.filter((hook) => hook.state && hook.title.startsWith('"after') && hook.title.endsWith('"{root}"'));
|
|
453
|
-
this._orderedSuites.unshift(Object.assign({}, baseRootSuite, {
|
|
454
|
-
hooks: beforeAllHooks,
|
|
455
|
-
hooksAndTests: beforeAllHooks
|
|
456
|
-
}));
|
|
457
|
-
this._orderedSuites.push(Object.assign({}, baseRootSuite, {
|
|
458
|
-
hooks: afterAllHooks,
|
|
459
|
-
hooksAndTests: afterAllHooks
|
|
460
|
-
}));
|
|
461
|
-
}
|
|
462
|
-
return this._orderedSuites;
|
|
393
|
+
this._orderedSuites.push(suite);
|
|
394
|
+
}
|
|
463
395
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
396
|
+
const rootSuite = this.currentSuites[0];
|
|
397
|
+
if (rootSuite) {
|
|
398
|
+
const baseRootSuite = {
|
|
399
|
+
...rootSuite,
|
|
400
|
+
type: "suite",
|
|
401
|
+
title: "(root)",
|
|
402
|
+
fullTitle: "(root)",
|
|
403
|
+
suites: []
|
|
404
|
+
};
|
|
405
|
+
const beforeAllHooks = rootSuite.hooks.filter((hook) => hook.state && hook.title.startsWith('"before') && hook.title.endsWith('"{root}"'));
|
|
406
|
+
const afterAllHooks = rootSuite.hooks.filter((hook) => hook.state && hook.title.startsWith('"after') && hook.title.endsWith('"{root}"'));
|
|
407
|
+
this._orderedSuites.unshift(Object.assign({}, baseRootSuite, {
|
|
408
|
+
hooks: beforeAllHooks,
|
|
409
|
+
hooksAndTests: beforeAllHooks
|
|
410
|
+
}));
|
|
411
|
+
this._orderedSuites.push(Object.assign({}, baseRootSuite, {
|
|
412
|
+
hooks: afterAllHooks,
|
|
413
|
+
hooksAndTests: afterAllHooks
|
|
414
|
+
}));
|
|
472
415
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
416
|
+
return this._orderedSuites;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Indent a suite based on where how it's nested
|
|
420
|
+
* @param {string} uid Unique suite key
|
|
421
|
+
* @return {String} Spaces for indentation
|
|
422
|
+
*/
|
|
423
|
+
indent(uid) {
|
|
424
|
+
const indents = this._suiteIndents[uid];
|
|
425
|
+
return indents === 0 ? "" : Array(indents).join(" ");
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Get a symbol based on state
|
|
429
|
+
* @param {string} state State of a test
|
|
430
|
+
* @return {String} Symbol to display
|
|
431
|
+
*/
|
|
432
|
+
getSymbol(state) {
|
|
433
|
+
return state && this._symbols[state] || "?";
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Get a color based on a given state
|
|
437
|
+
* @param {string} state Test state
|
|
438
|
+
* @return {String} State color
|
|
439
|
+
*/
|
|
440
|
+
getColor(state) {
|
|
441
|
+
let color = "gray" /* GRAY */;
|
|
442
|
+
switch (state) {
|
|
443
|
+
case "passed" /* PASSED */:
|
|
444
|
+
color = "green" /* GREEN */;
|
|
445
|
+
break;
|
|
446
|
+
case "pending" /* PENDING */:
|
|
447
|
+
case "skipped" /* SKIPPED */:
|
|
448
|
+
color = "cyan" /* CYAN */;
|
|
449
|
+
break;
|
|
450
|
+
case "failed" /* FAILED */:
|
|
451
|
+
color = "red" /* RED */;
|
|
452
|
+
break;
|
|
480
453
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
color = ChalkColors.CYAN;
|
|
496
|
-
break;
|
|
497
|
-
case State.FAILED:
|
|
498
|
-
color = ChalkColors.RED;
|
|
499
|
-
break;
|
|
500
|
-
}
|
|
501
|
-
return color;
|
|
454
|
+
return color;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Get information about the enviroment
|
|
458
|
+
* @param capability
|
|
459
|
+
* @param {Boolean} verbose
|
|
460
|
+
* @param isMultiremote
|
|
461
|
+
* @return {String} Enviroment string
|
|
462
|
+
*/
|
|
463
|
+
getEnviromentCombo(capability, verbose = true, isMultiremote = false) {
|
|
464
|
+
if (isMultiremote) {
|
|
465
|
+
const browserNames = Object.values(capability).map((c) => c.browserName);
|
|
466
|
+
const browserName = browserNames.length > 1 ? `${browserNames.slice(0, -1).join(", ")} and ${browserNames.pop()}` : browserNames.pop();
|
|
467
|
+
return `MultiremoteBrowser on ${browserName}`;
|
|
502
468
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
const device = caps['appium:deviceName'];
|
|
521
|
-
const app = ((caps['appium:app'] || caps.app) || '').replace('sauce-storage:', '');
|
|
522
|
-
const appName = app || caps['appium:bundleId'] || caps.bundleId;
|
|
523
|
-
const browser = caps.browserName || caps.browser || appName;
|
|
524
|
-
/**
|
|
525
|
-
* fallback to different capability types:
|
|
526
|
-
* browserVersion: W3C format
|
|
527
|
-
* version: JSONWP format
|
|
528
|
-
* platformVersion: mobile format
|
|
529
|
-
* browser_version: invalid BS capability
|
|
530
|
-
*/
|
|
531
|
-
const version = caps.browserVersion || caps.version || caps['appium:platformVersion'] || caps.browser_version;
|
|
532
|
-
/**
|
|
533
|
-
* fallback to different capability types:
|
|
534
|
-
* platformName: W3C format
|
|
535
|
-
* platform: JSONWP format
|
|
536
|
-
* os, os_version: invalid BS capability
|
|
537
|
-
*/
|
|
538
|
-
const platform = caps.platformName || caps['appium:platformName'] || caps.platform || (caps.os ? caps.os + (caps.os_version ? ` ${caps.os_version}` : '') : '(unknown)');
|
|
539
|
-
// Mobile capabilities
|
|
540
|
-
if (device) {
|
|
541
|
-
const program = appName || caps.browserName;
|
|
542
|
-
const executing = program ? `executing ${program}` : '';
|
|
543
|
-
if (!verbose) {
|
|
544
|
-
return `${device} ${platform} ${version}`;
|
|
545
|
-
}
|
|
546
|
-
return `${device} on ${platform} ${version} ${executing}`.trim();
|
|
547
|
-
}
|
|
548
|
-
if (!verbose) {
|
|
549
|
-
return (browser + (version ? ` ${version} ` : ' ') + (platform)).trim();
|
|
550
|
-
}
|
|
551
|
-
return browser + (version ? ` (v${version})` : '') + (` on ${platform}`);
|
|
469
|
+
const caps = "alwaysMatch" in capability ? capability.alwaysMatch : capability;
|
|
470
|
+
const device = caps["appium:deviceName"];
|
|
471
|
+
const app = (caps["appium:app"] || caps.app || "").replace("sauce-storage:", "");
|
|
472
|
+
const appName = app || caps["appium:bundleId"] || caps.bundleId;
|
|
473
|
+
const browser = caps.browserName || caps.browser || appName;
|
|
474
|
+
const version = caps.browserVersion || caps.version || caps["appium:platformVersion"] || caps.browser_version;
|
|
475
|
+
const platform = caps.platformName || caps["appium:platformName"] || caps.platform || (caps.os ? caps.os + (caps.os_version ? ` ${caps.os_version}` : "") : "(unknown)");
|
|
476
|
+
if (device) {
|
|
477
|
+
const program = appName || caps.browserName;
|
|
478
|
+
const executing = program ? `executing ${program}` : "";
|
|
479
|
+
if (!verbose) {
|
|
480
|
+
return `${device} ${platform} ${version}`;
|
|
481
|
+
}
|
|
482
|
+
return `${device} on ${platform} ${version} ${executing}`.trim();
|
|
483
|
+
}
|
|
484
|
+
if (!verbose) {
|
|
485
|
+
return (browser + (version ? ` ${version} ` : " ") + platform).trim();
|
|
552
486
|
}
|
|
553
|
-
}
|
|
487
|
+
return browser + (version ? ` (v${version})` : "") + ` on ${platform}`;
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
export {
|
|
491
|
+
SpecReporter as default
|
|
492
|
+
};
|
package/build/types.d.ts
CHANGED
|
@@ -77,7 +77,7 @@ export declare enum State {
|
|
|
77
77
|
SKIPPED = "skipped"
|
|
78
78
|
}
|
|
79
79
|
export interface TestLink {
|
|
80
|
-
capabilities: Capabilities.
|
|
80
|
+
capabilities: Capabilities.ResolvedTestrunnerCapabilities;
|
|
81
81
|
sessionId: string;
|
|
82
82
|
isMultiremote: boolean;
|
|
83
83
|
instanceName?: string;
|
package/build/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,MAAM,WAAW,UAAU;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,OAAO;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,mBAAmB;IAChC;;;;;;;;OAQG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAA;IAChC;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;IAC1B;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;;;MAIE;IACF,KAAK,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,oBAAY,WAAW;IACnB,GAAG,QAAQ;IACX,KAAK,UAAU;IACf,IAAI,SAAS;IACb,IAAI,SAAS;IACb,IAAI,SAAS;IACb,MAAM,UAAU;IAChB,MAAM,WAAW;IACjB,IAAI,SAAS;IACb,OAAO,YAAY;IACnB,KAAK,UAAU;CAClB;AAED,oBAAY,KAAK;IACb,MAAM,WAAW;IACjB,MAAM,WAAW;IACjB,OAAO,YAAY;IACnB,OAAO,YAAY;CACtB;AAED,MAAM,WAAW,QAAQ;IACrB,YAAY,EAAE,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,MAAM,WAAW,UAAU;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,OAAO;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,mBAAmB;IAChC;;;;;;;;OAQG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAA;IAChC;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;IAC1B;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;;;MAIE;IACF,KAAK,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,oBAAY,WAAW;IACnB,GAAG,QAAQ;IACX,KAAK,UAAU;IACf,IAAI,SAAS;IACb,IAAI,SAAS;IACb,IAAI,SAAS;IACb,MAAM,UAAU;IAChB,MAAM,WAAW;IACjB,IAAI,SAAS;IACb,OAAO,YAAY;IACnB,KAAK,UAAU;CAClB;AAED,oBAAY,KAAK;IACb,MAAM,WAAW;IACjB,MAAM,WAAW;IACjB,OAAO,YAAY;IACnB,OAAO,YAAY;CACtB;AAED,MAAM,WAAW,QAAQ;IACrB,YAAY,EAAE,YAAY,CAAC,8BAA8B,CAAA;IACzD,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,OAAO,CAAA;IACtB,YAAY,CAAC,EAAE,MAAM,CAAA;CACxB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wdio/spec-reporter",
|
|
3
|
-
"version": "9.0.0
|
|
3
|
+
"version": "9.0.0",
|
|
4
4
|
"description": "A WebdriverIO plugin to report in spec style",
|
|
5
5
|
"author": "Christian Bromann <mail@bromann.dev>",
|
|
6
6
|
"homepage": "https://github.com/webdriverio/webdriverio/tree/main/packages/wdio-spec-reporter",
|
|
@@ -24,13 +24,15 @@
|
|
|
24
24
|
"type": "module",
|
|
25
25
|
"types": "./build/index.d.ts",
|
|
26
26
|
"exports": {
|
|
27
|
-
".":
|
|
28
|
-
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./build/index.d.ts",
|
|
29
|
+
"import": "./build/index.js"
|
|
30
|
+
}
|
|
29
31
|
},
|
|
30
32
|
"typeScriptVersion": "3.8.3",
|
|
31
33
|
"dependencies": {
|
|
32
|
-
"@wdio/reporter": "9.0.0
|
|
33
|
-
"@wdio/types": "9.0.0
|
|
34
|
+
"@wdio/reporter": "9.0.0",
|
|
35
|
+
"@wdio/types": "9.0.0",
|
|
34
36
|
"chalk": "^5.1.2",
|
|
35
37
|
"easy-table": "^1.2.0",
|
|
36
38
|
"pretty-ms": "^9.0.0"
|
|
@@ -38,5 +40,5 @@
|
|
|
38
40
|
"publishConfig": {
|
|
39
41
|
"access": "public"
|
|
40
42
|
},
|
|
41
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "957693463371a4cb329395dcdbce8fb0c930ab93"
|
|
42
44
|
}
|
package/build/types.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export var ChalkColors;
|
|
2
|
-
(function (ChalkColors) {
|
|
3
|
-
ChalkColors["RED"] = "red";
|
|
4
|
-
ChalkColors["GREEN"] = "green";
|
|
5
|
-
ChalkColors["CYAN"] = "cyan";
|
|
6
|
-
ChalkColors["GRAY"] = "gray";
|
|
7
|
-
ChalkColors["GREY"] = "grey";
|
|
8
|
-
ChalkColors["BLACCK"] = "black";
|
|
9
|
-
ChalkColors["YELLOW"] = "yellow";
|
|
10
|
-
ChalkColors["BLUE"] = "blue";
|
|
11
|
-
ChalkColors["MAGENTA"] = "magenta";
|
|
12
|
-
ChalkColors["WHITE"] = "white";
|
|
13
|
-
})(ChalkColors || (ChalkColors = {}));
|
|
14
|
-
export var State;
|
|
15
|
-
(function (State) {
|
|
16
|
-
State["FAILED"] = "failed";
|
|
17
|
-
State["PASSED"] = "passed";
|
|
18
|
-
State["PENDING"] = "pending";
|
|
19
|
-
State["SKIPPED"] = "skipped";
|
|
20
|
-
})(State || (State = {}));
|
package/build/utils.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import Table from 'easy-table';
|
|
2
|
-
import { createHmac } from 'node:crypto';
|
|
3
|
-
const SEPARATOR = '│';
|
|
4
|
-
/**
|
|
5
|
-
* transform cucumber table to format suitable for `easy-table`
|
|
6
|
-
* @param {object[]} rows cucumber table rows
|
|
7
|
-
* @returns {object[]}
|
|
8
|
-
*/
|
|
9
|
-
export const buildTableData = (rows) => rows.map((row) => {
|
|
10
|
-
const tableRow = {};
|
|
11
|
-
[...row.cells, ''].forEach((cell, idx) => {
|
|
12
|
-
tableRow[idx] = (idx === 0 ? `${SEPARATOR} ` : '') + cell;
|
|
13
|
-
});
|
|
14
|
-
return tableRow;
|
|
15
|
-
});
|
|
16
|
-
/**
|
|
17
|
-
* returns table in string format
|
|
18
|
-
* @param {object[]} data table data
|
|
19
|
-
* @returns {string}
|
|
20
|
-
*/
|
|
21
|
-
export const printTable = (data) => Table.print(data, undefined, (table) => {
|
|
22
|
-
table.separator = ` ${SEPARATOR} `;
|
|
23
|
-
return table.print();
|
|
24
|
-
});
|
|
25
|
-
/**
|
|
26
|
-
* add indentation
|
|
27
|
-
* @param {string} table printed table
|
|
28
|
-
* @param {string} testIndent whitespaces
|
|
29
|
-
*/
|
|
30
|
-
export const getFormattedRows = (table, testIndent) => table.split('\n').filter(Boolean).map((line) => `${testIndent} ${line}`.trimRight());
|
|
31
|
-
/**
|
|
32
|
-
* Get Sauce Labs Authentication url
|
|
33
|
-
* @param {string} user
|
|
34
|
-
* @param {string} key
|
|
35
|
-
* @param {string} sessionId
|
|
36
|
-
*/
|
|
37
|
-
export const sauceAuthenticationToken = (user, key, sessionId) => {
|
|
38
|
-
const secret = `${user}:${key}`;
|
|
39
|
-
// Create the token by calling createHmac method
|
|
40
|
-
const token = createHmac('md5', secret)
|
|
41
|
-
// Update data
|
|
42
|
-
.update(sessionId)
|
|
43
|
-
// Encoding to be used
|
|
44
|
-
.digest('hex');
|
|
45
|
-
return `?auth=${token}`;
|
|
46
|
-
};
|
/package/{LICENSE-MIT → LICENSE}
RENAMED
|
File without changes
|