@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.
- package/build/index.d.ts +104 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +477 -0
- package/build/types.d.ts +61 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/utils.d.ts +26 -0
- package/build/utils.d.ts.map +1 -0
- package/build/utils.js +81 -0
- package/package.json +4 -4
package/build/index.d.ts
ADDED
|
@@ -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;
|
package/build/types.d.ts
ADDED
|
@@ -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
package/build/utils.d.ts
ADDED
|
@@ -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.
|
|
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.
|
|
27
|
-
"@wdio/types": "7.
|
|
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": "
|
|
38
|
+
"gitHead": "2bcb589dbdd10ca181f301a269b4dd158faab257"
|
|
39
39
|
}
|