@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 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.RemoteCapability, verbose?: boolean, isMultiremote?: boolean): string;
112
+ getEnviromentCombo(capability: Capabilities.ResolvedTestrunnerCapabilities, verbose?: boolean, isMultiremote?: boolean): string;
113
113
  }
114
114
  //# sourceMappingURL=index.d.ts.map
@@ -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;IAgC3D;;OAEG;IACH,WAAW,CAAC,MAAM,EAAE,WAAW;IAkD/B;;OAEG;IACH,WAAW,CAAE,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,EAAE,QAAQ;IA4C/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,gBAAgB,EAAE,OAAO,UAAO,EAAE,aAAa,UAAQ,GAAG,MAAM;CAgDhH"}
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
- import prettyMs from 'pretty-ms';
2
- import { format } from 'node:util';
3
- import { Chalk } from 'chalk';
4
- import WDIOReporter, { TestStats } from '@wdio/reporter';
5
- import { buildTableData, printTable, getFormattedRows, sauceAuthenticationToken } from './utils.js';
6
- import { ChalkColors, State } from './types.js';
7
- const DEFAULT_INDENT = ' ';
8
- export default class SpecReporter extends WDIOReporter {
9
- _suiteUids = new Set();
10
- _indents = 0;
11
- _suiteIndents = {};
12
- _orderedSuites = [];
13
- _consoleOutput = '';
14
- _suiteIndent = '';
15
- _preface = '';
16
- _consoleLogs = [];
17
- _pendingReasons = [];
18
- _originalStdoutWrite = process.stdout.write.bind(process.stdout);
19
- _addConsoleLogs = false;
20
- _realtimeReporting = false;
21
- _showPreface = true;
22
- _suiteName = '';
23
- // Keep track of the order that suites were called
24
- _stateCounts = {
25
- passed: 0,
26
- failed: 0,
27
- skipped: 0
28
- };
29
- _symbols = {
30
- passed: '✓',
31
- skipped: '-',
32
- pending: '?',
33
- failed: '✖'
34
- };
35
- _chalk;
36
- _onlyFailures = false;
37
- _sauceLabsSharableLinks = true;
38
- constructor(options) {
39
- /**
40
- * make spec reporter to write to output stream by default
41
- */
42
- super(Object.assign({ stdout: true }, options));
43
- this._symbols = { ...this._symbols, ...this.options.symbols || {} };
44
- this._onlyFailures = options.onlyFailures || false;
45
- this._realtimeReporting = options.realtimeReporting || false;
46
- this._showPreface = options.showPreface !== false;
47
- this._sauceLabsSharableLinks = 'sauceLabsSharableLinks' in options
48
- ? options.sauceLabsSharableLinks
49
- : this._sauceLabsSharableLinks;
50
- const processObj = process;
51
- if (options.addConsoleLogs || this._addConsoleLogs) {
52
- processObj.stdout.write = (chunk, encoding, callback) => {
53
- if (typeof chunk === 'string' && !chunk.includes('mwebdriver')) {
54
- this._consoleOutput += chunk;
55
- }
56
- return this._originalStdoutWrite(chunk, encoding, callback);
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._chalk = new Chalk(options.color === false ? { level: 0 } : {});
74
+ return this._originalStdoutWrite(chunk, encoding, callback);
75
+ };
60
76
  }
61
- /**
62
- * @param state state of test execution
63
- * @param msg the message to print in terminal
64
- * @returns colord value based on chalk to print in terminal
65
- */
66
- setMessageColor(message, state) {
67
- return this._chalk[this.getColor(state)](message);
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
- onRunnerStart(runner) {
70
- this._preface = this._showPreface ? `[${this.getEnviromentCombo(runner.capabilities, false, runner.isMultiremote).trim()} #${runner.cid}]` : '';
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
- onSuiteStart(suite) {
73
- this._suiteName = suite.file?.replace(process.cwd(), '');
74
- this.printCurrentStats(suite);
75
- this._suiteUids.add(suite.uid);
76
- if (suite.type === 'feature') {
77
- this._indents = 0;
78
- this._suiteIndents[suite.uid] = this._indents;
79
- }
80
- else {
81
- this._suiteIndents[suite.uid] = ++this._indents;
82
- }
83
- }
84
- onSuiteEnd() {
85
- this._indents--;
86
- }
87
- onHookEnd(hook) {
88
- this.printCurrentStats(hook);
89
- if (hook.error) {
90
- this._stateCounts.failed++;
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
- onTestStart() {
94
- this._consoleOutput = '';
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
- onTestPass(testStat) {
97
- this.printCurrentStats(testStat);
98
- this._consoleLogs.push(this._consoleOutput);
99
- this._stateCounts.passed++;
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
- onTestFail(testStat) {
102
- this.printCurrentStats(testStat);
103
- this._consoleLogs.push(this._consoleOutput);
104
- this._stateCounts.failed++;
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
- onTestSkip(testStat) {
107
- this.printCurrentStats(testStat);
108
- this._pendingReasons.push(testStat.pendingReason);
109
- this._consoleLogs.push(this._consoleOutput);
110
- this._stateCounts.skipped++;
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
- onRunnerEnd(runner) {
113
- this.printReport(runner);
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
- * Print the report to the stdout realtime
117
- */
118
- printCurrentStats(stat) {
119
- if (!this._realtimeReporting) {
120
- return;
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
- const title = stat.title, state = stat.state;
123
- const divider = '------------------------------------------------------------------';
124
- const indent = (stat.type === 'test') ?
125
- `${DEFAULT_INDENT}${this._suiteIndent}` :
126
- this.indent(stat.uid);
127
- const suiteStartBanner = (stat.type === 'feature' || stat.type === 'suite' || stat.type === 'suite:start') ?
128
- `${this._preface} ${divider}\n` +
129
- `${this._preface} Suite started: \n` +
130
- `${this._preface} » ${this._suiteName}\n` : '\n';
131
- const content = stat.type === 'test'
132
- ? `${this._preface} ${indent}` +
133
- `${this.setMessageColor(this.getSymbol(state), state)} ${title}` +
134
- ` » ${this.setMessageColor('[', state)} ${this._suiteName} ${this.setMessageColor(']', state)}`
135
- : stat.type !== 'hook' ?
136
- `${suiteStartBanner}${this._preface} ${title}` :
137
- title
138
- ? `${this._preface} Hook executed: ${title}`
139
- : undefined;
140
- if (process.send && content) {
141
- process.send({ name: 'reporterRealTime', content });
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
- * Print the report to the screen
146
- */
147
- printReport(runner) {
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 duration = `(${prettyMs(runner._duration)})`;
153
- const preface = `[${this.getEnviromentCombo(runner.capabilities, false, runner.isMultiremote).trim()} #${runner.cid}]`;
154
- const divider = '------------------------------------------------------------------';
155
- // Get the results
156
- const results = this.getResultDisplay(preface);
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
- const testLinks = runner.isMultiremote
162
- ? Object.entries(runner.capabilities).map(([instanceName, capabilities]) => this.getTestLink({
163
- capabilities,
164
- sessionId: capabilities.sessionId,
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
- * get link to saucelabs job
191
- */
192
- getTestLink({ sessionId, isMultiremote, instanceName, capabilities }) {
193
- const config = this.runnerStat && this.runnerStat.instanceOptions[sessionId];
194
- const isSauceJob = ((config && config.hostname && config.hostname.includes('saucelabs')) ||
195
- // only show if multiremote is not used
196
- capabilities && (
197
- // check w3c cap in jsonwp caps
198
- capabilities['sauce:options'] ||
199
- // check jsonwp caps
200
- capabilities.tunnelIdentifier ||
201
- // check w3c caps
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
- * returns everything worth reporting from a suite
246
- * @param {Object} suite test suite containing tests and hooks
247
- * @return {Object[]} list of events to report
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
- * Get the results from the tests
283
- * @param {Array} suites Runner suites
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
- * Get the display for passing, failing and skipped
367
- * @param {string} duration Duration string
368
- * @return {Array} Count display
369
- */
370
- getCountDisplay(duration) {
371
- const output = [];
372
- // Get the passes
373
- if (this._stateCounts.passed > 0) {
374
- const text = `${this._stateCounts.passed} passing ${duration}`;
375
- output.push(this.setMessageColor(text, State.PASSED));
376
- duration = '';
377
- }
378
- // Get the failures
379
- if (this._stateCounts.failed > 0) {
380
- const text = `${this._stateCounts.failed} failing ${duration}`.trim();
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
- // Get the skipped tests
385
- if (this._stateCounts.skipped > 0) {
386
- const text = `${this._stateCounts.skipped} skipped ${duration}`.trim();
387
- output.push(this.setMessageColor(text, State.SKIPPED));
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
- return output;
375
+ }
390
376
  }
391
- /**
392
- * Get display for failed tests, e.g. stack trace
393
- * @return {Array} Stack trace output
394
- */
395
- getFailureDisplay() {
396
- let failureLength = 0;
397
- const output = [];
398
- const suites = this.getOrderedSuites();
399
- for (const suite of suites) {
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
- * Get suites in the order they were called
424
- * @return {Array} Ordered suites
425
- */
426
- getOrderedSuites() {
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
- * ensure we include root suite hook errors
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
- * Indent a suite based on where how it's nested
466
- * @param {string} uid Unique suite key
467
- * @return {String} Spaces for indentation
468
- */
469
- indent(uid) {
470
- const indents = this._suiteIndents[uid];
471
- return indents === 0 ? '' : Array(indents).join(' ');
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
- * Get a symbol based on state
475
- * @param {string} state State of a test
476
- * @return {String} Symbol to display
477
- */
478
- getSymbol(state) {
479
- return (state && this._symbols[state]) || '?';
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
- * Get a color based on a given state
483
- * @param {string} state Test state
484
- * @return {String} State color
485
- */
486
- getColor(state) {
487
- // In case of an unknown state
488
- let color = ChalkColors.GRAY;
489
- switch (state) {
490
- case State.PASSED:
491
- color = ChalkColors.GREEN;
492
- break;
493
- case State.PENDING:
494
- case State.SKIPPED:
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
- * Get information about the enviroment
505
- * @param capability
506
- * @param {Boolean} verbose
507
- * @param isMultiremote
508
- * @return {String} Enviroment string
509
- */
510
- getEnviromentCombo(capability, verbose = true, isMultiremote = false) {
511
- if (isMultiremote) {
512
- const browserNames = Object.values(capability).map((c) => c.browserName);
513
- const browserName = browserNames.length > 1
514
- ? `${browserNames.slice(0, -1).join(', ')} and ${browserNames.pop()}`
515
- : browserNames.pop();
516
- return `MultiremoteBrowser on ${browserName}`;
517
- }
518
- const caps = (capability.alwaysMatch ||
519
- capability);
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.RemoteCapability;
80
+ capabilities: Capabilities.ResolvedTestrunnerCapabilities;
81
81
  sessionId: string;
82
82
  isMultiremote: boolean;
83
83
  instanceName?: string;
@@ -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,gBAAgB,CAAA;IAC3C,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,OAAO,CAAA;IACtB,YAAY,CAAC,EAAE,MAAM,CAAA;CACxB"}
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-alpha.78+fee2f8a88",
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
- ".": "./build/index.js",
28
- "./package.json": "./package.json"
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-alpha.78+fee2f8a88",
33
- "@wdio/types": "9.0.0-alpha.78+fee2f8a88",
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": "fee2f8a88d132537795eaf144abf1a7e242f99ff"
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
- };
File without changes