@testrevolution/bugbug-cli 11.5.1 → 12.17.1
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/README.md +4 -2
- package/package.json +10 -10
- package/src/__tests__/helper.spec.js +230 -0
- package/src/commands/help.js +3 -1
- package/src/utils/helper.js +19 -11
- package/src/utils/testReports.js +4 -4
package/README.md
CHANGED
|
@@ -34,9 +34,10 @@ bugbug remote <option>
|
|
|
34
34
|
|
|
35
35
|
options:
|
|
36
36
|
* list [test|suite|profile] [--no-wait] [--no-progress] [--debug]
|
|
37
|
-
* run [test|suite] <string:testId|suiteId> [--no-wait] [--no-progress] [--debug] [--with-details] [--profile] [--variable] [--reporter] [--output-path]
|
|
37
|
+
* run [test|suite] <string:testId|suiteId> [--no-wait] [--no-progress] [--debug] [--with-details] [--profile] [--variable] [--result-timeout <int>] [--reporter] [--output-path]
|
|
38
38
|
* status [test|suite] <string:testRunId|suiteRunId> [--no-progress] [--debug]
|
|
39
|
-
* result [test|suite] <string:testRunId|suiteRunId> [--no-progress] [--debug] [--with-details]
|
|
39
|
+
* result [test|suite] <string:testRunId|suiteRunId> [--no-progress] [--debug] [--with-details] [--result-timeout <int>]
|
|
40
|
+
* stop [test|suite] <string:testRunId|suiteRunId> [--no-progress] [--debug] [--result-timeout <int>]
|
|
40
41
|
|
|
41
42
|
optional flags:
|
|
42
43
|
* --debug - show more data (like raw API response)
|
|
@@ -47,6 +48,7 @@ optional flags:
|
|
|
47
48
|
* --with-details - show result with details
|
|
48
49
|
* --reporter <"inline"|"junit"> - the name of the reporter to use (default: "inline")
|
|
49
50
|
* --output-path - the path to save the test report; relative to the current working directory
|
|
51
|
+
* --result-timeout - modify the default result waiting time (minutes, default: 60)
|
|
50
52
|
```
|
|
51
53
|
|
|
52
54
|
Run test:
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testrevolution/bugbug-cli",
|
|
3
3
|
"description": "BugBug CLI",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "12.17.1",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"automation",
|
|
7
7
|
"cli",
|
|
@@ -18,16 +18,16 @@
|
|
|
18
18
|
"bugbug": "bin/bugbug"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@sentry/node": "^8.
|
|
22
|
-
"axios": "^1.
|
|
21
|
+
"@sentry/node": "^8.55.0",
|
|
22
|
+
"axios": "^1.10.0",
|
|
23
23
|
"console-table-printer": "^2.12.1",
|
|
24
|
-
"dotenv": "^16.4.
|
|
24
|
+
"dotenv": "^16.4.7",
|
|
25
25
|
"jest-junit": "^16.0.0",
|
|
26
26
|
"junit-xml": "^1.2.0",
|
|
27
27
|
"minimist": "^1.2.8",
|
|
28
28
|
"ora": "^5.4.1",
|
|
29
29
|
"path": "^0.12.7",
|
|
30
|
-
"validator": "^13.
|
|
30
|
+
"validator": "^13.15.0"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"start": "cross-env NODE_ENV=development node ./bin/bugbug",
|
|
@@ -41,16 +41,16 @@
|
|
|
41
41
|
"node": ">= 20.12.2"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@babel/eslint-parser": "^7.
|
|
45
|
-
"@babel/plugin-proposal-throw-expressions": "^7.
|
|
44
|
+
"@babel/eslint-parser": "^7.27.1",
|
|
45
|
+
"@babel/plugin-proposal-throw-expressions": "^7.27.1",
|
|
46
46
|
"babel-plugin-rewire": "^1.2.0",
|
|
47
47
|
"cross-env": "^7.0.3",
|
|
48
48
|
"eslint": "^8.57.1",
|
|
49
49
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
50
|
-
"eslint-plugin-import": "^2.
|
|
51
|
-
"eslint-plugin-jest": "^
|
|
50
|
+
"eslint-plugin-import": "^2.32.0",
|
|
51
|
+
"eslint-plugin-jest": "^29.0.1",
|
|
52
52
|
"eslint-plugin-node": "^11.1.0",
|
|
53
|
-
"jest": "^
|
|
53
|
+
"jest": "^30.0.3"
|
|
54
54
|
},
|
|
55
55
|
"author": "TestRevolution sp. z o.o."
|
|
56
56
|
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
const { parseTimeFromDuration, getSecondsFromDuration } = require('../utils/helper');
|
|
2
|
+
|
|
3
|
+
describe('timeParser', () => {
|
|
4
|
+
describe('parseTimeFromDuration', () => {
|
|
5
|
+
it('should parse full duration with microseconds', () => {
|
|
6
|
+
// Arrange
|
|
7
|
+
const duration = '00:01:15.704622';
|
|
8
|
+
|
|
9
|
+
// Act
|
|
10
|
+
const result = parseTimeFromDuration(duration);
|
|
11
|
+
|
|
12
|
+
// Assert
|
|
13
|
+
expect(result).toEqual({
|
|
14
|
+
hours: '00',
|
|
15
|
+
minutes: '01',
|
|
16
|
+
secondsWithMs: '15.704622',
|
|
17
|
+
seconds: '15',
|
|
18
|
+
microseconds: '704622',
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should parse duration without microseconds', () => {
|
|
23
|
+
// Arrange
|
|
24
|
+
const duration = '01:30:45';
|
|
25
|
+
|
|
26
|
+
// Act
|
|
27
|
+
const result = parseTimeFromDuration(duration);
|
|
28
|
+
|
|
29
|
+
// Assert
|
|
30
|
+
expect(result).toEqual({
|
|
31
|
+
hours: '01',
|
|
32
|
+
minutes: '30',
|
|
33
|
+
secondsWithMs: '45',
|
|
34
|
+
seconds: '45',
|
|
35
|
+
microseconds: '0',
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should parse duration with zero microseconds', () => {
|
|
40
|
+
// Arrange
|
|
41
|
+
const duration = '00:00:30.0';
|
|
42
|
+
|
|
43
|
+
// Act
|
|
44
|
+
const result = parseTimeFromDuration(duration);
|
|
45
|
+
|
|
46
|
+
// Assert
|
|
47
|
+
expect(result).toEqual({
|
|
48
|
+
hours: '00',
|
|
49
|
+
minutes: '00',
|
|
50
|
+
secondsWithMs: '30.0',
|
|
51
|
+
seconds: '30',
|
|
52
|
+
microseconds: '0',
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should handle empty string', () => {
|
|
57
|
+
// Arrange
|
|
58
|
+
const duration = '';
|
|
59
|
+
|
|
60
|
+
// Act
|
|
61
|
+
const result = parseTimeFromDuration(duration);
|
|
62
|
+
|
|
63
|
+
// Assert
|
|
64
|
+
expect(result).toEqual({
|
|
65
|
+
hours: '00',
|
|
66
|
+
minutes: '0',
|
|
67
|
+
secondsWithMs: '0.0',
|
|
68
|
+
seconds: '0',
|
|
69
|
+
microseconds: '0',
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should handle null or undefined', () => {
|
|
74
|
+
// Arrange & Act
|
|
75
|
+
const resultNull = parseTimeFromDuration(null);
|
|
76
|
+
const resultUndefined = parseTimeFromDuration(undefined);
|
|
77
|
+
|
|
78
|
+
// Assert
|
|
79
|
+
expect(resultNull).toEqual({
|
|
80
|
+
hours: '00',
|
|
81
|
+
minutes: '0',
|
|
82
|
+
secondsWithMs: '0.0',
|
|
83
|
+
seconds: '0',
|
|
84
|
+
microseconds: '0',
|
|
85
|
+
});
|
|
86
|
+
expect(resultUndefined).toEqual({
|
|
87
|
+
hours: '00',
|
|
88
|
+
minutes: '0',
|
|
89
|
+
secondsWithMs: '0.0',
|
|
90
|
+
seconds: '0',
|
|
91
|
+
microseconds: '0',
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should handle short microseconds', () => {
|
|
96
|
+
// Arrange
|
|
97
|
+
const duration = '00:01:15.5';
|
|
98
|
+
|
|
99
|
+
// Act
|
|
100
|
+
const result = parseTimeFromDuration(duration);
|
|
101
|
+
|
|
102
|
+
// Assert
|
|
103
|
+
expect(result).toEqual({
|
|
104
|
+
hours: '00',
|
|
105
|
+
minutes: '01',
|
|
106
|
+
secondsWithMs: '15.5',
|
|
107
|
+
seconds: '15',
|
|
108
|
+
microseconds: '5',
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('getSecondsFromDuration', () => {
|
|
114
|
+
it('should convert duration with microseconds to total float seconds', () => {
|
|
115
|
+
// Arrange
|
|
116
|
+
const duration = '00:01:15.704622';
|
|
117
|
+
|
|
118
|
+
// Act
|
|
119
|
+
const result = getSecondsFromDuration(duration);
|
|
120
|
+
|
|
121
|
+
// Assert
|
|
122
|
+
// 1 minute = 60 seconds, + 15 seconds = 75 seconds
|
|
123
|
+
// 75 * 1,000,000 = 75,000,000 microseconds
|
|
124
|
+
// + 704,622 microseconds = 75,704,622
|
|
125
|
+
expect(result).toBe(75.704622);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should convert duration without microseconds', () => {
|
|
129
|
+
// Arrange
|
|
130
|
+
const duration = '01:30:45';
|
|
131
|
+
|
|
132
|
+
// Act
|
|
133
|
+
const result = getSecondsFromDuration(duration);
|
|
134
|
+
|
|
135
|
+
// Assert
|
|
136
|
+
expect(result).toBe(5445);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should handle zero duration', () => {
|
|
140
|
+
// Arrange
|
|
141
|
+
const duration = '00:00:00.0';
|
|
142
|
+
|
|
143
|
+
// Act
|
|
144
|
+
const result = getSecondsFromDuration(duration);
|
|
145
|
+
|
|
146
|
+
// Assert
|
|
147
|
+
expect(result).toBe(0);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should pad short microseconds to 6 digits', () => {
|
|
151
|
+
// Arrange
|
|
152
|
+
const duration = '00:00:01.5';
|
|
153
|
+
|
|
154
|
+
// Act
|
|
155
|
+
const result = getSecondsFromDuration(duration);
|
|
156
|
+
|
|
157
|
+
// Assert
|
|
158
|
+
expect(result).toBe(1.5);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should truncate microseconds longer than 6 digits', () => {
|
|
162
|
+
// Arrange
|
|
163
|
+
const duration = '00:00:01.1234567890';
|
|
164
|
+
|
|
165
|
+
// Act
|
|
166
|
+
const result = getSecondsFromDuration(duration);
|
|
167
|
+
|
|
168
|
+
// Assert
|
|
169
|
+
expect(result).toBe(1.123456);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should handle missing microseconds with default padding', () => {
|
|
173
|
+
// Arrange
|
|
174
|
+
const duration = '00:00:05';
|
|
175
|
+
|
|
176
|
+
// Act
|
|
177
|
+
const result = getSecondsFromDuration(duration);
|
|
178
|
+
|
|
179
|
+
// Assert
|
|
180
|
+
expect(result).toBe(5);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('should handle large durations', () => {
|
|
184
|
+
// Arrange
|
|
185
|
+
const duration = '23:59:59.999999';
|
|
186
|
+
|
|
187
|
+
// Act
|
|
188
|
+
const result = getSecondsFromDuration(duration);
|
|
189
|
+
|
|
190
|
+
// Assert
|
|
191
|
+
expect(result).toBe(86399.999999);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should handle empty string', () => {
|
|
195
|
+
// Arrange
|
|
196
|
+
const duration = '';
|
|
197
|
+
|
|
198
|
+
// Act
|
|
199
|
+
const result = getSecondsFromDuration(duration);
|
|
200
|
+
|
|
201
|
+
// Assert
|
|
202
|
+
expect(result).toBe(0);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should handle null or undefined', () => {
|
|
206
|
+
// Arrange & Act
|
|
207
|
+
const resultNull = getSecondsFromDuration(null);
|
|
208
|
+
const resultUndefined = getSecondsFromDuration(undefined);
|
|
209
|
+
|
|
210
|
+
// Assert
|
|
211
|
+
expect(resultNull).toBe(0);
|
|
212
|
+
expect(resultUndefined).toBe(0);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should handle malformed duration strings gracefully', () => {
|
|
216
|
+
// Arrange
|
|
217
|
+
const duration = 'invalid:time:format';
|
|
218
|
+
|
|
219
|
+
// Act
|
|
220
|
+
const parseResult = parseTimeFromDuration(duration);
|
|
221
|
+
const millisecondsResult = getSecondsFromDuration(duration);
|
|
222
|
+
|
|
223
|
+
// Assert
|
|
224
|
+
expect(parseResult.hours).toBe('invalid');
|
|
225
|
+
expect(parseResult.minutes).toBe('time');
|
|
226
|
+
expect(parseResult.seconds).toBe('format');
|
|
227
|
+
expect(millisecondsResult).toBeNaN();
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
});
|
package/src/commands/help.js
CHANGED
|
@@ -20,7 +20,7 @@ const help = {
|
|
|
20
20
|
|
|
21
21
|
options:
|
|
22
22
|
* list [test|suite|profile] [--no-wait] [--no-progress] [--debug]
|
|
23
|
-
* run [test|suite] <string:testId|suiteId> [--no-wait] [--no-progress] [--debug] [--with-details] [--profile] [--variable] [--result-timeout <int>]
|
|
23
|
+
* run [test|suite] <string:testId|suiteId> [--no-wait] [--no-progress] [--debug] [--with-details] [--profile] [--variable] [--result-timeout <int>] [--reporter] [--output-path]
|
|
24
24
|
* status [test|suite] <string:testRunId|suiteRunId> [--no-progress] [--debug]
|
|
25
25
|
* result [test|suite] <string:testRunId|suiteRunId> [--no-progress] [--debug] [--with-details] [--result-timeout <int>]
|
|
26
26
|
* stop [test|suite] <string:testRunId|suiteRunId> [--no-progress] [--debug] [--result-timeout <int>]
|
|
@@ -32,6 +32,8 @@ const help = {
|
|
|
32
32
|
* --profile <string:"profile name"> - run with specific profile
|
|
33
33
|
* --variable <string:"varName=varValue"> - override variable during single run
|
|
34
34
|
* --with-details - show result with details
|
|
35
|
+
* --reporter <"inline"|"junit"> - the name of the reporter to use (default: "inline")
|
|
36
|
+
* --output-path - the path to save the test report; relative to the current working directory
|
|
35
37
|
* --result-timeout - modify the default result waiting time (minutes, default: 60)
|
|
36
38
|
`,
|
|
37
39
|
};
|
package/src/utils/helper.js
CHANGED
|
@@ -28,7 +28,7 @@ const getUnknownOptions = async (args, knownKeys) => {
|
|
|
28
28
|
const printErrorResponse = async (spinner, error) => {
|
|
29
29
|
if (
|
|
30
30
|
error.response?.status === 400
|
|
31
|
-
|
|
31
|
+
&& error.response?.data[0]?.message) {
|
|
32
32
|
spinner.fail(error.response.data[0].message);
|
|
33
33
|
} else {
|
|
34
34
|
spinner.fail(error.toString());
|
|
@@ -45,28 +45,36 @@ const overrideSettings = async (args) => {
|
|
|
45
45
|
|
|
46
46
|
const parseTimeFromDuration = (duration) => {
|
|
47
47
|
const [hours, minutes = '0', secondsWithMs = '0.0'] = (duration || '').split(':');
|
|
48
|
-
const [seconds,
|
|
48
|
+
const [seconds, microseconds = '0'] = secondsWithMs.split('.');
|
|
49
49
|
return {
|
|
50
|
-
hours: hours || '00',
|
|
50
|
+
hours: hours || '00',
|
|
51
|
+
minutes,
|
|
52
|
+
secondsWithMs,
|
|
53
|
+
seconds,
|
|
54
|
+
microseconds,
|
|
51
55
|
};
|
|
52
56
|
};
|
|
53
57
|
|
|
54
|
-
const
|
|
58
|
+
const getSecondsFromDuration = (duration) => {
|
|
55
59
|
const {
|
|
56
|
-
hours, minutes, seconds,
|
|
60
|
+
hours, minutes, seconds, microseconds,
|
|
57
61
|
} = parseTimeFromDuration(duration);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
const totalSeconds = parseInt(hours, 10) * 3600
|
|
63
|
+
+ parseInt(minutes, 10) * 60
|
|
64
|
+
+ parseInt(seconds, 10);
|
|
65
|
+
|
|
66
|
+
const paddedMicroseconds = (microseconds || '0').padEnd(6, '0').substring(0, 6);
|
|
67
|
+
const fractionalSeconds = parseInt(paddedMicroseconds, 10) / 1_000_000;
|
|
68
|
+
|
|
69
|
+
return totalSeconds + fractionalSeconds;
|
|
63
70
|
};
|
|
64
71
|
|
|
65
72
|
module.exports = {
|
|
66
73
|
getExitCode,
|
|
74
|
+
getSecondsFromDuration,
|
|
67
75
|
getUnknownOptions,
|
|
68
76
|
overrideSettings,
|
|
77
|
+
parseTimeFromDuration,
|
|
69
78
|
parseVariables,
|
|
70
79
|
printErrorResponse,
|
|
71
|
-
getMillisecondsFromDuration,
|
|
72
80
|
};
|
package/src/utils/testReports.js
CHANGED
|
@@ -3,7 +3,7 @@ const fs = require('node:fs');
|
|
|
3
3
|
const path = require('node:path');
|
|
4
4
|
|
|
5
5
|
const settings = require('../settings');
|
|
6
|
-
const {
|
|
6
|
+
const { getSecondsFromDuration } = require('./helper');
|
|
7
7
|
|
|
8
8
|
const getTestInJunitFormat = (testRun) => {
|
|
9
9
|
const errorDetails = testRun.errorCode ? {
|
|
@@ -14,7 +14,7 @@ const getTestInJunitFormat = (testRun) => {
|
|
|
14
14
|
return {
|
|
15
15
|
id: testRun.id,
|
|
16
16
|
name: testRun.name,
|
|
17
|
-
time:
|
|
17
|
+
time: getSecondsFromDuration(testRun.duration),
|
|
18
18
|
classname: null,
|
|
19
19
|
errors: testRun.status === settings.STATUS_ERROR ? [errorDetails] : [],
|
|
20
20
|
failures: testRun.status === settings.STATUS_FAILED ? [errorDetails] : [],
|
|
@@ -24,14 +24,14 @@ const getTestInJunitFormat = (testRun) => {
|
|
|
24
24
|
const getSuiteInJunitFormat = (suiteRun) => ({
|
|
25
25
|
id: suiteRun.id,
|
|
26
26
|
name: suiteRun.name,
|
|
27
|
-
time:
|
|
27
|
+
time: getSecondsFromDuration(suiteRun.duration),
|
|
28
28
|
testCases: suiteRun.details.map((testRun) => getTestInJunitFormat(testRun)),
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
const generateJunitReport = async (type, result, outputPath) => {
|
|
32
32
|
const testReport = {};
|
|
33
33
|
testReport.name = 'BugBug Report';
|
|
34
|
-
testReport.time =
|
|
34
|
+
testReport.time = getSecondsFromDuration(result.duration);
|
|
35
35
|
testReport.suites = [];
|
|
36
36
|
|
|
37
37
|
if (type === settings.TYPE_TEST) {
|