@wdio/spec-reporter 7.17.0 → 7.17.3

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.
@@ -0,0 +1,104 @@
1
+ import WDIOReporter, { SuiteStats, HookStats, RunnerStats, TestStats } from '@wdio/reporter';
2
+ import { Capabilities } from '@wdio/types';
3
+ import type { Symbols, SpecReporterOptions, TestLink } from './types';
4
+ export default class SpecReporter extends WDIOReporter {
5
+ private _suiteUids;
6
+ private _indents;
7
+ private _suiteIndents;
8
+ private _orderedSuites;
9
+ private _consoleOutput;
10
+ private _suiteIndent;
11
+ private _preface;
12
+ private _consoleLogs;
13
+ private _originalStdoutWrite;
14
+ private _addConsoleLogs;
15
+ private _realtimeReporting;
16
+ private _showPreface;
17
+ private _suiteName;
18
+ private _stateCounts;
19
+ private _symbols;
20
+ private _onlyFailures;
21
+ private _sauceLabsSharableLinks;
22
+ constructor(options: SpecReporterOptions);
23
+ onRunnerStart(runner: RunnerStats): void;
24
+ onSuiteStart(suite: SuiteStats): void;
25
+ onSuiteEnd(): void;
26
+ onHookEnd(hook: HookStats): void;
27
+ onTestStart(): void;
28
+ onTestPass(testStat: TestStats): void;
29
+ onTestFail(testStat: TestStats): void;
30
+ onTestSkip(testStat: TestStats): void;
31
+ onRunnerEnd(runner: RunnerStats): void;
32
+ /**
33
+ * Print the report to the stdout realtime
34
+ */
35
+ printCurrentStats(stat: TestStats | HookStats | SuiteStats): void;
36
+ /**
37
+ * Print the report to the screen
38
+ */
39
+ printReport(runner: RunnerStats): void;
40
+ /**
41
+ * get link to saucelabs job
42
+ */
43
+ getTestLink({ sessionId, isMultiremote, instanceName, capabilities }: TestLink): string[];
44
+ /**
45
+ * Get the header display for the report
46
+ * @param {Object} runner Runner data
47
+ * @return {Array} Header data
48
+ */
49
+ getHeaderDisplay(runner: RunnerStats): string[];
50
+ /**
51
+ * returns everything worth reporting from a suite
52
+ * @param {Object} suite test suite containing tests and hooks
53
+ * @return {Object[]} list of events to report
54
+ */
55
+ getEventsToReport(suite: SuiteStats): (HookStats | TestStats)[];
56
+ /**
57
+ * Get the results from the tests
58
+ * @param {Array} suites Runner suites
59
+ * @return {Array} Display output list
60
+ */
61
+ getResultDisplay(prefaceString?: string): string[];
62
+ /**
63
+ * Get the display for passing, failing and skipped
64
+ * @param {String} duration Duration string
65
+ * @return {Array} Count display
66
+ */
67
+ getCountDisplay(duration: string): string[];
68
+ /**
69
+ * Get display for failed tests, e.g. stack trace
70
+ * @return {Array} Stack trace output
71
+ */
72
+ getFailureDisplay(): string[];
73
+ /**
74
+ * Get suites in the order they were called
75
+ * @return {Array} Ordered suites
76
+ */
77
+ getOrderedSuites(): SuiteStats[];
78
+ /**
79
+ * Indent a suite based on where how it's nested
80
+ * @param {String} uid Unique suite key
81
+ * @return {String} Spaces for indentation
82
+ */
83
+ indent(uid: string): string;
84
+ /**
85
+ * Get a symbol based on state
86
+ * @param {String} state State of a test
87
+ * @return {String} Symbol to display
88
+ */
89
+ getSymbol(state?: keyof Symbols): string;
90
+ /**
91
+ * Get a color based on a given state
92
+ * @param {String} state Test state
93
+ * @return {String} State color
94
+ */
95
+ getColor(state?: string): "red" | "green" | "cyan" | "gray";
96
+ /**
97
+ * Get information about the enviroment
98
+ * @param {Object} caps Enviroment details
99
+ * @param {Boolean} verbose
100
+ * @return {String} Enviroment string
101
+ */
102
+ getEnviromentCombo(capability: Capabilities.RemoteCapability, verbose?: boolean, isMultiremote?: boolean): string;
103
+ }
104
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,YAAY,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAY,MAAM,gBAAgB,CAAA;AACtG,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAI1C,OAAO,KAAK,EAAc,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAQjF,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,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;IAED,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,uBAAuB,CAAO;gBAEzB,OAAO,EAAE,mBAAmB;IAyBzC,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;IAM/B,WAAW,CAAE,MAAM,EAAE,WAAW;IAIhC;;OAEG;IACH,iBAAiB,CAAE,IAAI,EAAE,SAAS,GAAG,SAAS,GAAG,UAAU;IA+B3D;;OAEG;IACH,WAAW,CAAC,MAAM,EAAE,WAAW;IAkD/B;;OAEG;IACH,WAAW,CAAE,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,EAAE,QAAQ;IA2C/E;;;;OAIG;IACH,gBAAgB,CAAE,MAAM,EAAE,WAAW;IAkBrC;;;;OAIG;IACH,iBAAiB,CAAE,KAAK,EAAE,UAAU;IAYpC;;;;OAIG;IACH,gBAAgB,CAAE,aAAa,CAAC,EAAE,MAAM;IAyExC;;;;OAIG;IACH,eAAe,CAAE,QAAQ,EAAE,MAAM;IA0BjC;;;OAGG;IACH,iBAAiB;IAmCjB;;;OAGG;IACH,gBAAgB;IAmBhB;;;;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;IAoBxB;;;;;OAKG;IACH,kBAAkB,CAAE,UAAU,EAAE,YAAY,CAAC,gBAAgB,EAAE,OAAO,UAAO,EAAE,aAAa,UAAQ;CA6CvG"}
package/build/index.js ADDED
@@ -0,0 +1,477 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const util_1 = require("util");
7
+ const reporter_1 = __importDefault(require("@wdio/reporter"));
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const pretty_ms_1 = __importDefault(require("pretty-ms"));
10
+ const utils_1 = require("./utils");
11
+ const DEFAULT_INDENT = ' ';
12
+ class SpecReporter extends reporter_1.default {
13
+ constructor(options) {
14
+ /**
15
+ * make spec reporter to write to output stream by default
16
+ */
17
+ super(Object.assign({ stdout: true }, options));
18
+ this._suiteUids = new Set();
19
+ this._indents = 0;
20
+ this._suiteIndents = {};
21
+ this._orderedSuites = [];
22
+ this._consoleOutput = '';
23
+ this._suiteIndent = '';
24
+ this._preface = '';
25
+ this._consoleLogs = [];
26
+ this._originalStdoutWrite = process.stdout.write.bind(process.stdout);
27
+ this._addConsoleLogs = false;
28
+ this._realtimeReporting = false;
29
+ this._showPreface = true;
30
+ this._suiteName = '';
31
+ // Keep track of the order that suites were called
32
+ this._stateCounts = {
33
+ passed: 0,
34
+ failed: 0,
35
+ skipped: 0
36
+ };
37
+ this._symbols = {
38
+ passed: '✓',
39
+ skipped: '-',
40
+ pending: '?',
41
+ failed: '✖'
42
+ };
43
+ this._onlyFailures = false;
44
+ this._sauceLabsSharableLinks = true;
45
+ this._symbols = { ...this._symbols, ...this.options.symbols || {} };
46
+ this._onlyFailures = options.onlyFailures || false;
47
+ this._realtimeReporting = options.realtimeReporting || false;
48
+ this._showPreface = options.showPreface !== false;
49
+ this._sauceLabsSharableLinks = 'sauceLabsSharableLinks' in options
50
+ ? options.sauceLabsSharableLinks
51
+ : this._sauceLabsSharableLinks;
52
+ let processObj = process;
53
+ if (options.addConsoleLogs || this._addConsoleLogs) {
54
+ processObj.stdout.write = (chunk, encoding, callback) => {
55
+ if (typeof chunk === 'string' && !chunk.includes('mwebdriver')) {
56
+ this._consoleOutput += chunk;
57
+ }
58
+ return this._originalStdoutWrite(chunk, encoding, callback);
59
+ };
60
+ }
61
+ }
62
+ onRunnerStart(runner) {
63
+ this._preface = this._showPreface ? `[${this.getEnviromentCombo(runner.capabilities, false, runner.isMultiremote).trim()} #${runner.cid}]` : '';
64
+ }
65
+ onSuiteStart(suite) {
66
+ this._suiteName = suite.file.replace(process.cwd(), '');
67
+ this.printCurrentStats(suite);
68
+ this._suiteUids.add(suite.uid);
69
+ if (suite.type === 'feature') {
70
+ this._indents = 0;
71
+ this._suiteIndents[suite.uid] = this._indents;
72
+ }
73
+ else {
74
+ this._suiteIndents[suite.uid] = ++this._indents;
75
+ }
76
+ }
77
+ onSuiteEnd() {
78
+ this._indents--;
79
+ }
80
+ onHookEnd(hook) {
81
+ this.printCurrentStats(hook);
82
+ if (hook.error) {
83
+ this._stateCounts.failed++;
84
+ }
85
+ }
86
+ onTestStart() {
87
+ this._consoleOutput = '';
88
+ }
89
+ onTestPass(testStat) {
90
+ this.printCurrentStats(testStat);
91
+ this._consoleLogs.push(this._consoleOutput);
92
+ this._stateCounts.passed++;
93
+ }
94
+ onTestFail(testStat) {
95
+ this.printCurrentStats(testStat);
96
+ this._consoleLogs.push(this._consoleOutput);
97
+ this._stateCounts.failed++;
98
+ }
99
+ onTestSkip(testStat) {
100
+ this.printCurrentStats(testStat);
101
+ this._consoleLogs.push(this._consoleOutput);
102
+ this._stateCounts.skipped++;
103
+ }
104
+ onRunnerEnd(runner) {
105
+ this.printReport(runner);
106
+ }
107
+ /**
108
+ * Print the report to the stdout realtime
109
+ */
110
+ printCurrentStats(stat) {
111
+ if (!this._realtimeReporting) {
112
+ return;
113
+ }
114
+ const title = stat.title, state = stat.state;
115
+ const divider = '------------------------------------------------------------------';
116
+ const indent = (stat.type === 'test') ?
117
+ `${DEFAULT_INDENT}${this._suiteIndent}` :
118
+ this.indent(stat.uid);
119
+ const suiteStartBanner = (stat.type === 'feature' || stat.type === 'suite' || stat.type === 'suite:start') ?
120
+ `${this._preface} ${divider}\n` +
121
+ `${this._preface} Suite started : \n` +
122
+ `${this._preface} » ${this._suiteName}\n` : '\n';
123
+ const contentNonTest = stat.type !== 'hook' ?
124
+ `${suiteStartBanner}${this._preface} ${title}` :
125
+ `${this._preface} Hook executed : ${title}`;
126
+ const contentTest = `${this._preface} ${indent}` +
127
+ `${chalk_1.default[this.getColor(state)](this.getSymbol(state))} ${title}` +
128
+ ` » ${chalk_1.default[this.getColor(state)]('[')} ${this._suiteName} ${chalk_1.default[this.getColor(state)](']')}`;
129
+ process.send({
130
+ name: 'reporterRealTime',
131
+ content: stat.type === 'test' ? contentTest : contentNonTest
132
+ });
133
+ }
134
+ /**
135
+ * Print the report to the screen
136
+ */
137
+ printReport(runner) {
138
+ // Don't print non failed tests
139
+ if (runner.failures === 0 && this._onlyFailures === true) {
140
+ return;
141
+ }
142
+ const duration = `(${(0, pretty_ms_1.default)(runner._duration)})`;
143
+ const preface = `[${this.getEnviromentCombo(runner.capabilities, false, runner.isMultiremote).trim()} #${runner.cid}]`;
144
+ const divider = '------------------------------------------------------------------';
145
+ // Get the results
146
+ const results = this.getResultDisplay(preface);
147
+ // If there are no test results then return nothing
148
+ if (results.length === 0) {
149
+ return;
150
+ }
151
+ const testLinks = runner.isMultiremote
152
+ ? Object.entries(runner.capabilities).map(([instanceName, capabilities]) => this.getTestLink({
153
+ capabilities,
154
+ sessionId: capabilities.sessionId,
155
+ isMultiremote: runner.isMultiremote,
156
+ instanceName
157
+ })).filter((links) => links.length)
158
+ : this.getTestLink(runner);
159
+ const output = [
160
+ ...this.getHeaderDisplay(runner),
161
+ '',
162
+ ...results,
163
+ ...this.getCountDisplay(duration),
164
+ ...this.getFailureDisplay(),
165
+ ...(testLinks.length
166
+ /**
167
+ * if we have test links add an empty line
168
+ */
169
+ ? ['', ...testLinks]
170
+ : [])
171
+ ];
172
+ // Prefix all values with the browser information
173
+ const prefacedOutput = this._showPreface ? output.map((value) => {
174
+ return value ? `${preface} ${value}` : preface;
175
+ }) : output;
176
+ // Output the results
177
+ this.write(`${divider}\n${prefacedOutput.join('\n')}\n`);
178
+ }
179
+ /**
180
+ * get link to saucelabs job
181
+ */
182
+ getTestLink({ sessionId, isMultiremote, instanceName, capabilities }) {
183
+ var _a, _b, _c;
184
+ const config = this.runnerStat && this.runnerStat.instanceOptions[sessionId];
185
+ const isSauceJob = ((config && config.hostname && config.hostname.includes('saucelabs')) ||
186
+ // only show if multiremote is not used
187
+ capabilities && (
188
+ // check w3c cap in jsonwp caps
189
+ capabilities['sauce:options'] ||
190
+ // check jsonwp caps
191
+ capabilities.tunnelIdentifier ||
192
+ // check w3c caps
193
+ (capabilities.alwaysMatch &&
194
+ capabilities.alwaysMatch['sauce:options'])));
195
+ if (isSauceJob && config && config.user && config.key && sessionId) {
196
+ const multiremoteNote = isMultiremote ? ` ${instanceName}` : '';
197
+ const note = 'Check out%s job at %s';
198
+ // The report url of RDC is in the caps that are returned
199
+ if ('testobject_test_report_url' in capabilities) {
200
+ return [(0, util_1.format)(note, multiremoteNote, capabilities.testobject_test_report_url)];
201
+ }
202
+ // VDC urls can be constructed / be made shared
203
+ const isUSEast = config.headless || ((_a = config.hostname) === null || _a === void 0 ? void 0 : _a.includes('us-east-1'));
204
+ const isEUCentral = ['eu', 'eu-central-1'].includes((config === null || config === void 0 ? void 0 : config.region) || '') || ((_b = config.hostname) === null || _b === void 0 ? void 0 : _b.includes('eu-central'));
205
+ const isAPAC = ['apac', 'apac-southeast-1'].includes((config === null || config === void 0 ? void 0 : config.region) || '') || ((_c = config.hostname) === null || _c === void 0 ? void 0 : _c.includes('apac'));
206
+ const dc = isUSEast ? '.us-east-1' : isEUCentral ? '.eu-central-1' : isAPAC ? '.apac-southeast-1' : '';
207
+ const sauceLabsSharableLinks = this._sauceLabsSharableLinks
208
+ ? (0, utils_1.sauceAuthenticationToken)(config.user, config.key, sessionId)
209
+ : '';
210
+ const sauceUrl = `https://app${dc}.saucelabs.com/tests/${sessionId}${sauceLabsSharableLinks}`;
211
+ return [(0, util_1.format)(note, multiremoteNote, sauceUrl)];
212
+ }
213
+ return [];
214
+ }
215
+ /**
216
+ * Get the header display for the report
217
+ * @param {Object} runner Runner data
218
+ * @return {Array} Header data
219
+ */
220
+ getHeaderDisplay(runner) {
221
+ const combo = this.getEnviromentCombo(runner.capabilities, undefined, runner.isMultiremote).trim();
222
+ // Spec file name and enviroment information
223
+ const output = [`Running: ${combo}`];
224
+ /**
225
+ * print session ID if not multiremote
226
+ */
227
+ // @ts-expect-error
228
+ if (runner.capabilities.sessionId) {
229
+ // @ts-expect-error
230
+ output.push(`Session ID: ${runner.capabilities.sessionId}`);
231
+ }
232
+ return output;
233
+ }
234
+ /**
235
+ * returns everything worth reporting from a suite
236
+ * @param {Object} suite test suite containing tests and hooks
237
+ * @return {Object[]} list of events to report
238
+ */
239
+ getEventsToReport(suite) {
240
+ return [
241
+ /**
242
+ * report all tests and only hooks that failed
243
+ */
244
+ ...suite.hooksAndTests
245
+ .filter((item) => {
246
+ return item.type === 'test' || Boolean(item.error);
247
+ })
248
+ ];
249
+ }
250
+ /**
251
+ * Get the results from the tests
252
+ * @param {Array} suites Runner suites
253
+ * @return {Array} Display output list
254
+ */
255
+ getResultDisplay(prefaceString) {
256
+ const output = [];
257
+ const preface = this._showPreface ? prefaceString : '';
258
+ const suites = this.getOrderedSuites();
259
+ const specFileReferences = [];
260
+ for (const suite of suites) {
261
+ // Don't do anything if a suite has no tests or sub suites
262
+ if (suite.tests.length === 0 && suite.suites.length === 0 && suite.hooks.length === 0) {
263
+ continue;
264
+ }
265
+ // Get the indent/starting point for this suite
266
+ const suiteIndent = this.indent(suite.uid);
267
+ // Display file path of spec
268
+ if (!specFileReferences.includes(suite.file)) {
269
+ output.push(`${suiteIndent}» ${suite.file.replace(process.cwd(), '')}`);
270
+ specFileReferences.push(suite.file);
271
+ }
272
+ // Display the title of the suite
273
+ output.push(`${suiteIndent}${suite.title}`);
274
+ // display suite description (Cucumber only)
275
+ if (suite.description) {
276
+ output.push(...suite.description.trim().split('\n')
277
+ .map((l) => `${suiteIndent}${chalk_1.default.grey(l.trim())}`));
278
+ output.push(''); // empty line
279
+ }
280
+ // display suite rule (Cucumber only)
281
+ if (suite.rule) {
282
+ output.push(...suite.rule.trim().split('\n')
283
+ .map((l) => `${suiteIndent}${chalk_1.default.grey(l.trim())}`));
284
+ }
285
+ const eventsToReport = this.getEventsToReport(suite);
286
+ for (const test of eventsToReport) {
287
+ const testTitle = test.title;
288
+ const state = test.state;
289
+ const testIndent = `${DEFAULT_INDENT}${suiteIndent}`;
290
+ // Output for a single test
291
+ output.push(`${testIndent}${chalk_1.default[this.getColor(state)](this.getSymbol(state))} ${testTitle}`);
292
+ // print cucumber data table cells
293
+ const args = test.argument;
294
+ if (args && args.rows && args.rows.length) {
295
+ const data = (0, utils_1.buildTableData)(args.rows);
296
+ const rawTable = (0, utils_1.printTable)(data);
297
+ const table = (0, utils_1.getFormattedRows)(rawTable, testIndent);
298
+ output.push(...table);
299
+ }
300
+ // print console output
301
+ const logItem = this._consoleLogs.shift();
302
+ if (logItem) {
303
+ output.push('');
304
+ output.push(testIndent.repeat(2) + '.........Console Logs.........');
305
+ output.push(testIndent.repeat(3) + (logItem === null || logItem === void 0 ? void 0 : logItem.replace(/\n/g, '\n'.concat(preface + ' ', testIndent.repeat(3)))));
306
+ }
307
+ }
308
+ // Put a line break after each suite (only if tests exist in that suite)
309
+ if (eventsToReport.length) {
310
+ output.push('');
311
+ }
312
+ }
313
+ return output;
314
+ }
315
+ /**
316
+ * Get the display for passing, failing and skipped
317
+ * @param {String} duration Duration string
318
+ * @return {Array} Count display
319
+ */
320
+ getCountDisplay(duration) {
321
+ const output = [];
322
+ // Get the passes
323
+ if (this._stateCounts.passed > 0) {
324
+ const text = `${this._stateCounts.passed} passing ${duration}`;
325
+ output.push(chalk_1.default[this.getColor('passed')](text));
326
+ duration = '';
327
+ }
328
+ // Get the failures
329
+ if (this._stateCounts.failed > 0) {
330
+ const text = `${this._stateCounts.failed} failing ${duration}`.trim();
331
+ output.push(chalk_1.default[this.getColor('failed')](text));
332
+ duration = '';
333
+ }
334
+ // Get the skipped tests
335
+ if (this._stateCounts.skipped > 0) {
336
+ const text = `${this._stateCounts.skipped} skipped ${duration}`.trim();
337
+ output.push(chalk_1.default[this.getColor('skipped')](text));
338
+ }
339
+ return output;
340
+ }
341
+ /**
342
+ * Get display for failed tests, e.g. stack trace
343
+ * @return {Array} Stack trace output
344
+ */
345
+ getFailureDisplay() {
346
+ var _a;
347
+ let failureLength = 0;
348
+ const output = [];
349
+ const suites = this.getOrderedSuites();
350
+ for (const suite of suites) {
351
+ const suiteTitle = suite.title;
352
+ const eventsToReport = this.getEventsToReport(suite);
353
+ for (const test of eventsToReport) {
354
+ if (test.state !== 'failed') {
355
+ continue;
356
+ }
357
+ const testTitle = test.title;
358
+ const errors = test.errors || (test.error ? [test.error] : []);
359
+ // If we get here then there is a failed test
360
+ output.push('', `${++failureLength}) ${suiteTitle} ${testTitle}`);
361
+ for (const error of errors) {
362
+ !((_a = error === null || error === void 0 ? void 0 : error.stack) === null || _a === void 0 ? void 0 : _a.includes('new AssertionError'))
363
+ ? output.push(chalk_1.default.red(error.message))
364
+ : output.push(...error.message.split('\n'));
365
+ if (error.stack) {
366
+ output.push(...error.stack.split(/\n/g).map(value => chalk_1.default.gray(value)));
367
+ }
368
+ }
369
+ }
370
+ }
371
+ return output;
372
+ }
373
+ /**
374
+ * Get suites in the order they were called
375
+ * @return {Array} Ordered suites
376
+ */
377
+ getOrderedSuites() {
378
+ if (this._orderedSuites.length) {
379
+ return this._orderedSuites;
380
+ }
381
+ this._orderedSuites = [];
382
+ for (const uid of this._suiteUids) {
383
+ for (const [suiteUid, suite] of Object.entries(this.suites)) {
384
+ if (suiteUid !== uid) {
385
+ continue;
386
+ }
387
+ this._orderedSuites.push(suite);
388
+ }
389
+ }
390
+ return this._orderedSuites;
391
+ }
392
+ /**
393
+ * Indent a suite based on where how it's nested
394
+ * @param {String} uid Unique suite key
395
+ * @return {String} Spaces for indentation
396
+ */
397
+ indent(uid) {
398
+ const indents = this._suiteIndents[uid];
399
+ return indents === 0 ? '' : Array(indents).join(' ');
400
+ }
401
+ /**
402
+ * Get a symbol based on state
403
+ * @param {String} state State of a test
404
+ * @return {String} Symbol to display
405
+ */
406
+ getSymbol(state) {
407
+ return (state && this._symbols[state]) || '?';
408
+ }
409
+ /**
410
+ * Get a color based on a given state
411
+ * @param {String} state Test state
412
+ * @return {String} State color
413
+ */
414
+ getColor(state) {
415
+ // In case of an unknown state
416
+ let color = 'gray';
417
+ switch (state) {
418
+ case 'passed':
419
+ color = 'green';
420
+ break;
421
+ case 'pending':
422
+ case 'skipped':
423
+ color = 'cyan';
424
+ break;
425
+ case 'failed':
426
+ color = 'red';
427
+ break;
428
+ }
429
+ return color;
430
+ }
431
+ /**
432
+ * Get information about the enviroment
433
+ * @param {Object} caps Enviroment details
434
+ * @param {Boolean} verbose
435
+ * @return {String} Enviroment string
436
+ */
437
+ getEnviromentCombo(capability, verbose = true, isMultiremote = false) {
438
+ const caps = (capability.alwaysMatch ||
439
+ capability);
440
+ const device = caps.deviceName;
441
+ const browser = isMultiremote ? 'MultiremoteBrowser' : (caps.browserName || caps.browser);
442
+ /**
443
+ * fallback to different capability types:
444
+ * browserVersion: W3C format
445
+ * version: JSONWP format
446
+ * platformVersion: mobile format
447
+ * browser_version: invalid BS capability
448
+ */
449
+ const version = caps.browserVersion || caps.version || caps.platformVersion || caps.browser_version;
450
+ /**
451
+ * fallback to different capability types:
452
+ * platformName: W3C format
453
+ * platform: JSONWP format
454
+ * os, os_version: invalid BS capability
455
+ */
456
+ const platform = isMultiremote
457
+ ? ''
458
+ : caps.platformName || caps.platform || (caps.os ? caps.os + (caps.os_version ? ` ${caps.os_version}` : '') : '(unknown)');
459
+ // Mobile capabilities
460
+ if (device) {
461
+ const program = (caps.app || '').replace('sauce-storage:', '') || caps.browserName;
462
+ const executing = program ? `executing ${program}` : '';
463
+ if (!verbose) {
464
+ return `${device} ${platform} ${version}`;
465
+ }
466
+ return `${device} on ${platform} ${version} ${executing}`.trim();
467
+ }
468
+ if (!verbose) {
469
+ return (browser + (version ? ` ${version} ` : ' ') + (platform)).trim();
470
+ }
471
+ if (isMultiremote) {
472
+ return browser + (version ? ` (v${version})` : '') + ` on ${Object.values(capability).map((c) => c.browserName).join(', ')}`;
473
+ }
474
+ return browser + (version ? ` (v${version})` : '') + (` on ${platform}`);
475
+ }
476
+ }
477
+ exports.default = SpecReporter;
@@ -0,0 +1,61 @@
1
+ import { Capabilities } from '@wdio/types';
2
+ export interface StateCount {
3
+ passed: number;
4
+ failed: number;
5
+ skipped: number;
6
+ }
7
+ export interface Symbols {
8
+ passed: string;
9
+ skipped: string;
10
+ pending: string;
11
+ failed: string;
12
+ }
13
+ export interface SpecReporterOptions {
14
+ /**
15
+ * Be default the test results in Sauce Labs can only be viewed by a team member from the same team, not by a team
16
+ * member from a different team. This options will enable
17
+ * [sharable links](https://wiki.saucelabs.com/display/DOCS/Building+Sharable+Links+to+Test+Results)
18
+ * by default, which means that all tests that are executed in Sauce Labs can be viewed by everybody.
19
+ * Adding `sauceLabsSharableLinks: false`, in the reporter options will disable this feature.
20
+ *
21
+ * @default: true
22
+ */
23
+ sauceLabsSharableLinks?: boolean;
24
+ /**
25
+ * Provide custom symbols for `passed`, `failed` and or `skipped` tests
26
+ *
27
+ * @default: {passed: '✓', skipped: '-', failed: '✖'}
28
+ */
29
+ symbols?: Partial<Symbols>;
30
+ /**
31
+ * Ability to show only failed specs results
32
+ *
33
+ * @default: false
34
+ */
35
+ onlyFailures?: boolean;
36
+ /**
37
+ * Ability to show console logs from steps in report
38
+ *
39
+ * @default: false
40
+ */
41
+ addConsoleLogs?: boolean;
42
+ /**
43
+ * Ability to show test status realtime
44
+ *
45
+ * @default: `false`
46
+ */
47
+ realtimeReporting?: boolean;
48
+ /**
49
+ * Ability to show or hide preface on each line of the report ('[MultiRemote ...]')
50
+ *
51
+ * @default: `true`
52
+ */
53
+ showPreface?: boolean;
54
+ }
55
+ export interface TestLink {
56
+ capabilities: Capabilities.RemoteCapability;
57
+ sessionId: string;
58
+ isMultiremote: boolean;
59
+ instanceName?: string;
60
+ }
61
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE1C,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;IACF,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC5B;;;;OAIG;IACF,WAAW,CAAC,EAAE,OAAO,CAAA;CACzB;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"}
package/build/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,26 @@
1
+ /**
2
+ * transform cucumber table to format suitable for `easy-table`
3
+ * @param {object[]} rows cucumber table rows
4
+ * @returns {object[]}
5
+ */
6
+ export declare const buildTableData: (rows: any) => any;
7
+ /**
8
+ * returns table in string format
9
+ * @param {object[]} data table data
10
+ * @returns {string}
11
+ */
12
+ export declare const printTable: (data: any) => string;
13
+ /**
14
+ * add indentation
15
+ * @param {string} table printed table
16
+ * @param {string} testIndent whitespaces
17
+ */
18
+ export declare const getFormattedRows: (table: string, testIndent: string) => string[];
19
+ /**
20
+ * Get Sauce Labs Authentication url
21
+ * @param {string} user
22
+ * @param {string} key
23
+ * @param {string} sessionId
24
+ */
25
+ export declare const sauceAuthenticationToken: (user: string, key: string, sessionId: string) => string;
26
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,eAAO,MAAM,cAAc,SAAU,GAAG,QAMtC,CAAA;AAEF;;;;GAIG;AACH,eAAO,MAAM,UAAU,SAAU,GAAG,WAGlC,CAAA;AAEF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,UAAW,MAAM,cAAc,MAAM,aACuB,CAAA;AAEzF;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,SAAS,MAAM,OAAM,MAAM,aAAY,MAAM,WAajF,CAAA"}
package/build/utils.js ADDED
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.sauceAuthenticationToken = exports.getFormattedRows = exports.printTable = exports.buildTableData = void 0;
30
+ const easy_table_1 = __importDefault(require("easy-table"));
31
+ const Crypto = __importStar(require("crypto"));
32
+ const SEPARATOR = '│';
33
+ /**
34
+ * transform cucumber table to format suitable for `easy-table`
35
+ * @param {object[]} rows cucumber table rows
36
+ * @returns {object[]}
37
+ */
38
+ const buildTableData = (rows) => rows.map((row) => {
39
+ const tableRow = {};
40
+ [...row.cells, ''].forEach((cell, idx) => {
41
+ tableRow[idx] = (idx === 0 ? `${SEPARATOR} ` : '') + cell;
42
+ });
43
+ return tableRow;
44
+ });
45
+ exports.buildTableData = buildTableData;
46
+ /**
47
+ * returns table in string format
48
+ * @param {object[]} data table data
49
+ * @returns {string}
50
+ */
51
+ const printTable = (data) => easy_table_1.default.print(data, undefined, (table) => {
52
+ table.separator = ` ${SEPARATOR} `;
53
+ return table.print();
54
+ });
55
+ exports.printTable = printTable;
56
+ /**
57
+ * add indentation
58
+ * @param {string} table printed table
59
+ * @param {string} testIndent whitespaces
60
+ */
61
+ const getFormattedRows = (table, testIndent) => table.split('\n').filter(Boolean).map((line) => `${testIndent} ${line}`.trimRight());
62
+ exports.getFormattedRows = getFormattedRows;
63
+ /**
64
+ * Get Sauce Labs Authentication url
65
+ * @param {string} user
66
+ * @param {string} key
67
+ * @param {string} sessionId
68
+ */
69
+ const sauceAuthenticationToken = (user, key, sessionId) => {
70
+ const secret = `${user}:${key}`;
71
+ // Create the token
72
+ const token = Crypto
73
+ // Calling createHmac method
74
+ .createHmac('md5', secret)
75
+ // Update data
76
+ .update(sessionId)
77
+ // Encoding to be used
78
+ .digest('hex');
79
+ return `?auth=${token}`;
80
+ };
81
+ exports.sauceAuthenticationToken = sauceAuthenticationToken;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wdio/spec-reporter",
3
- "version": "7.17.0",
3
+ "version": "7.17.3",
4
4
  "description": "A WebdriverIO plugin to report in spec style",
5
5
  "author": "Christian Bromann <christian@saucelabs.com>",
6
6
  "homepage": "https://github.com/webdriverio/webdriverio/tree/main/packages/wdio-spec-reporter",
@@ -23,8 +23,8 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "@types/easy-table": "^0.0.33",
26
- "@wdio/reporter": "7.16.14",
27
- "@wdio/types": "7.16.14",
26
+ "@wdio/reporter": "7.17.3",
27
+ "@wdio/types": "7.17.3",
28
28
  "chalk": "^4.0.0",
29
29
  "easy-table": "^1.1.1",
30
30
  "pretty-ms": "^7.0.0"
@@ -35,5 +35,5 @@
35
35
  "publishConfig": {
36
36
  "access": "public"
37
37
  },
38
- "gitHead": "e18d2cde6ff979758830bdff4c3bc82ca9818b24"
38
+ "gitHead": "2bcb589dbdd10ca181f301a269b4dd158faab257"
39
39
  }