@wdio/browserstack-service 7.35.0 → 7.36.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/bstackLogger.d.ts +14 -0
- package/build/bstackLogger.d.ts.map +1 -0
- package/build/bstackLogger.js +52 -0
- package/build/cleanup.d.ts +6 -2
- package/build/cleanup.d.ts.map +1 -1
- package/build/cleanup.js +104 -12
- package/build/config.d.ts +23 -0
- package/build/config.d.ts.map +1 -0
- package/build/config.js +32 -0
- package/build/constants.d.ts +17 -0
- package/build/constants.d.ts.map +1 -1
- package/build/constants.js +34 -1
- package/build/crash-reporter.js +1 -1
- package/build/data-store.d.ts +3 -0
- package/build/data-store.d.ts.map +1 -0
- package/build/data-store.js +49 -0
- package/build/exitHandler.d.ts +4 -0
- package/build/exitHandler.d.ts.map +1 -0
- package/build/exitHandler.js +37 -0
- package/build/insights-handler.d.ts +5 -11
- package/build/insights-handler.d.ts.map +1 -1
- package/build/insights-handler.js +45 -106
- package/build/instrumentation/funnelInstrumentation.d.ts +6 -0
- package/build/instrumentation/funnelInstrumentation.d.ts.map +1 -0
- package/build/instrumentation/funnelInstrumentation.js +127 -0
- package/build/launcher.d.ts +1 -2
- package/build/launcher.d.ts.map +1 -1
- package/build/launcher.js +21 -20
- package/build/reporter.d.ts +3 -3
- package/build/reporter.d.ts.map +1 -1
- package/build/reporter.js +10 -23
- package/build/request-handler.d.ts +5 -13
- package/build/request-handler.d.ts.map +1 -1
- package/build/request-handler.js +27 -48
- package/build/service.d.ts +1 -0
- package/build/service.d.ts.map +1 -1
- package/build/service.js +14 -6
- package/build/testOps/featureStats.d.ts +45 -0
- package/build/testOps/featureStats.d.ts.map +1 -0
- package/build/testOps/featureStats.js +120 -0
- package/build/testOps/featureUsage.d.ts +22 -0
- package/build/testOps/featureUsage.d.ts.map +1 -0
- package/build/testOps/featureUsage.js +46 -0
- package/build/testOps/listener.d.ts +33 -0
- package/build/testOps/listener.d.ts.map +1 -0
- package/build/testOps/listener.js +228 -0
- package/build/testOps/requestUtils.d.ts +4 -0
- package/build/testOps/requestUtils.d.ts.map +1 -0
- package/build/testOps/requestUtils.js +47 -0
- package/build/testOps/testOpsConfig.d.ts +11 -0
- package/build/testOps/testOpsConfig.d.ts.map +1 -0
- package/build/testOps/testOpsConfig.js +17 -0
- package/build/testOps/usageStats.d.ts +404 -0
- package/build/testOps/usageStats.d.ts.map +1 -0
- package/build/testOps/usageStats.js +110 -0
- package/build/types.d.ts +33 -7
- package/build/types.d.ts.map +1 -1
- package/build/util.d.ts +2 -6
- package/build/util.d.ts.map +1 -1
- package/build/util.js +60 -66
- package/package.json +3 -3
package/build/reporter.js
CHANGED
|
@@ -9,18 +9,18 @@ const reporter_1 = __importDefault(require("@wdio/reporter"));
|
|
|
9
9
|
const uuid_1 = require("uuid");
|
|
10
10
|
const logger_1 = __importDefault(require("@wdio/logger"));
|
|
11
11
|
const util_1 = require("./util");
|
|
12
|
-
const
|
|
12
|
+
const listener_1 = __importDefault(require("./testOps/listener"));
|
|
13
13
|
const log = (0, logger_1.default)('@wdio/browserstack-service');
|
|
14
14
|
class _TestReporter extends reporter_1.default {
|
|
15
15
|
constructor() {
|
|
16
16
|
super(...arguments);
|
|
17
17
|
this._capabilities = {};
|
|
18
18
|
this._observability = true;
|
|
19
|
-
this._requestQueueHandler = request_handler_1.default.getInstance();
|
|
20
19
|
this._suites = [];
|
|
21
20
|
this._gitConfigured = false;
|
|
22
21
|
this._currentHook = {};
|
|
23
22
|
this._currentTest = {};
|
|
23
|
+
this.listener = listener_1.default.getInstance();
|
|
24
24
|
}
|
|
25
25
|
async onRunnerStart(runnerStats) {
|
|
26
26
|
this._capabilities = runnerStats.capabilities;
|
|
@@ -47,10 +47,7 @@ class _TestReporter extends reporter_1.default {
|
|
|
47
47
|
stdLog.test_run_uuid = this._currentTest.uuid;
|
|
48
48
|
}
|
|
49
49
|
if (stdLog.hook_run_uuid || stdLog.test_run_uuid) {
|
|
50
|
-
|
|
51
|
-
event_type: 'LogCreated',
|
|
52
|
-
logs: [stdLog]
|
|
53
|
-
});
|
|
50
|
+
this.listener.logCreated([stdLog]);
|
|
54
51
|
}
|
|
55
52
|
}
|
|
56
53
|
setCurrentHook(hookDetails) {
|
|
@@ -122,7 +119,7 @@ class _TestReporter extends reporter_1.default {
|
|
|
122
119
|
if (testStats.fullTitle === '<unknown test>')
|
|
123
120
|
return;
|
|
124
121
|
testStats.end || (testStats.end = new Date());
|
|
125
|
-
await this.
|
|
122
|
+
this.listener.testFinished(await this.getRunData(testStats, 'TestRunFinished'));
|
|
126
123
|
}
|
|
127
124
|
async onTestStart(testStats) {
|
|
128
125
|
if (!this.needToSendData('test', 'start'))
|
|
@@ -134,7 +131,7 @@ class _TestReporter extends reporter_1.default {
|
|
|
134
131
|
_TestReporter._tests[testStats.fullTitle] = {
|
|
135
132
|
uuid: uuid,
|
|
136
133
|
};
|
|
137
|
-
await this.
|
|
134
|
+
this.listener.testStarted(await this.getRunData(testStats, 'TestRunStarted'));
|
|
138
135
|
}
|
|
139
136
|
async onHookStart(hookStats) {
|
|
140
137
|
if (!this.needToSendData('hook', 'start')) {
|
|
@@ -147,7 +144,7 @@ class _TestReporter extends reporter_1.default {
|
|
|
147
144
|
uuid: hookId,
|
|
148
145
|
startedAt: (new Date()).toISOString()
|
|
149
146
|
};
|
|
150
|
-
await this.
|
|
147
|
+
this.listener.hookStarted(await this.getRunData(hookStats, 'HookRunStarted'));
|
|
151
148
|
}
|
|
152
149
|
async onHookEnd(hookStats) {
|
|
153
150
|
if (!this.needToSendData('hook', 'end')) {
|
|
@@ -166,13 +163,13 @@ class _TestReporter extends reporter_1.default {
|
|
|
166
163
|
if (!hookStats.state && !hookStats.error) {
|
|
167
164
|
hookStats.state = 'passed';
|
|
168
165
|
}
|
|
169
|
-
await this.
|
|
166
|
+
this.listener.hookFinished(await this.getRunData(hookStats, 'HookRunFinished'));
|
|
170
167
|
}
|
|
171
168
|
getHookIdentifier(hookStats) {
|
|
172
169
|
var _a;
|
|
173
170
|
return `${hookStats.title} for ${(_a = this._suites.at(-1)) === null || _a === void 0 ? void 0 : _a.title}`;
|
|
174
171
|
}
|
|
175
|
-
async
|
|
172
|
+
async getRunData(testStats, eventType) {
|
|
176
173
|
var _a, _b, _c, _d, _e, _f;
|
|
177
174
|
const framework = (_a = this._config) === null || _a === void 0 ? void 0 : _a.framework;
|
|
178
175
|
const scopes = this._suites.map(s => s.title);
|
|
@@ -231,20 +228,10 @@ class _TestReporter extends reporter_1.default {
|
|
|
231
228
|
if (eventType == 'TestRunSkipped') {
|
|
232
229
|
eventType = 'TestRunFinished';
|
|
233
230
|
}
|
|
234
|
-
const uploadData = {
|
|
235
|
-
event_type: eventType,
|
|
236
|
-
};
|
|
237
231
|
if (eventType.match(/HookRun/)) {
|
|
238
232
|
testData.hook_type = ((_f = testData.name) === null || _f === void 0 ? void 0 : _f.toLowerCase()) ? (0, util_1.getHookType)(testData.name.toLowerCase()) : 'undefined';
|
|
239
|
-
uploadData.hook_run = testData;
|
|
240
|
-
}
|
|
241
|
-
else {
|
|
242
|
-
uploadData.test_run = testData;
|
|
243
|
-
}
|
|
244
|
-
const req = this._requestQueueHandler.add(uploadData);
|
|
245
|
-
if (req.proceed && req.data) {
|
|
246
|
-
await (0, util_1.uploadEventData)(req.data, req.url);
|
|
247
233
|
}
|
|
234
|
+
return testData;
|
|
248
235
|
}
|
|
249
236
|
async onTestSkip(testStats) {
|
|
250
237
|
// cucumber steps call this method. We don't want step skipped state so skip for cucumber
|
|
@@ -252,7 +239,7 @@ class _TestReporter extends reporter_1.default {
|
|
|
252
239
|
return;
|
|
253
240
|
testStats.start || (testStats.start = new Date());
|
|
254
241
|
testStats.end || (testStats.end = new Date());
|
|
255
|
-
await this.
|
|
242
|
+
this.listener.testFinished(await this.getRunData(testStats, 'TestRunSkipped'));
|
|
256
243
|
}
|
|
257
244
|
}
|
|
258
245
|
_TestReporter._tests = {};
|
|
@@ -1,27 +1,19 @@
|
|
|
1
1
|
import type { UploadType } from './types';
|
|
2
2
|
export default class RequestQueueHandler {
|
|
3
3
|
private queue;
|
|
4
|
-
private started;
|
|
5
4
|
private pollEventBatchInterval?;
|
|
6
|
-
|
|
5
|
+
private readonly callback?;
|
|
7
6
|
tearDownInvoked: boolean;
|
|
8
7
|
static instance: RequestQueueHandler;
|
|
9
8
|
private constructor();
|
|
10
|
-
static getInstance(): RequestQueueHandler;
|
|
11
|
-
|
|
12
|
-
add(event: UploadType): {
|
|
13
|
-
proceed: boolean;
|
|
14
|
-
data?: undefined;
|
|
15
|
-
url?: undefined;
|
|
16
|
-
} | {
|
|
17
|
-
proceed: boolean;
|
|
18
|
-
data: UploadType[] | undefined;
|
|
19
|
-
url: string;
|
|
20
|
-
};
|
|
9
|
+
static getInstance(callback?: Function): RequestQueueHandler;
|
|
10
|
+
add(event: UploadType): void;
|
|
21
11
|
shutdown(): Promise<void>;
|
|
22
12
|
startEventBatchPolling(): void;
|
|
23
13
|
resetEventBatchPolling(): void;
|
|
24
14
|
removeEventBatchPolling(tag: string): void;
|
|
15
|
+
sendBatch(): Promise<void>;
|
|
16
|
+
callCallback: (data: UploadType[], kind: string) => Promise<void>;
|
|
25
17
|
shouldProceed(): boolean;
|
|
26
18
|
}
|
|
27
19
|
//# sourceMappingURL=request-handler.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-handler.d.ts","sourceRoot":"","sources":["../src/request-handler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"request-handler.d.ts","sourceRoot":"","sources":["../src/request-handler.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAKzC,MAAM,CAAC,OAAO,OAAO,mBAAmB;IACpC,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,sBAAsB,CAAC,CAAgC;IAC/D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAU;IAC7B,eAAe,UAAQ;IAE9B,MAAM,CAAC,QAAQ,EAAE,mBAAmB,CAAA;IAGpC,OAAO;WAKO,WAAW,CAAC,QAAQ,CAAC,EAAE,QAAQ,GAAG,mBAAmB;IAOnE,GAAG,CAAE,KAAK,EAAE,UAAU;IAehB,QAAQ;IAQd,sBAAsB;IAItB,sBAAsB;IAKtB,uBAAuB,CAAE,GAAG,EAAE,MAAM;IAO9B,SAAS;IASf,YAAY,SAAgB,UAAU,EAAE,QAAQ,MAAM,mBAGrD;IAED,aAAa;CAOhB"}
|
package/build/request-handler.js
CHANGED
|
@@ -5,76 +5,48 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const logger_1 = __importDefault(require("@wdio/logger"));
|
|
7
7
|
const constants_1 = require("./constants");
|
|
8
|
-
const
|
|
8
|
+
const bstackLogger_1 = require("./bstackLogger");
|
|
9
9
|
const log = (0, logger_1.default)('@wdio/browserstack-service');
|
|
10
10
|
class RequestQueueHandler {
|
|
11
11
|
// making it private to use singleton pattern
|
|
12
|
-
constructor() {
|
|
12
|
+
constructor(callback) {
|
|
13
13
|
this.queue = [];
|
|
14
|
-
this.started = false;
|
|
15
|
-
this.pendingUploads = 0;
|
|
16
14
|
this.tearDownInvoked = false;
|
|
15
|
+
this.callCallback = async (data, kind) => {
|
|
16
|
+
bstackLogger_1.BStackLogger.debug('calling callback with kind ' + kind);
|
|
17
|
+
this.callback && await this.callback(data);
|
|
18
|
+
};
|
|
19
|
+
this.callback = callback;
|
|
20
|
+
this.startEventBatchPolling();
|
|
17
21
|
}
|
|
18
|
-
static getInstance() {
|
|
19
|
-
if (!RequestQueueHandler.instance) {
|
|
20
|
-
RequestQueueHandler.instance = new RequestQueueHandler();
|
|
22
|
+
static getInstance(callback) {
|
|
23
|
+
if (!RequestQueueHandler.instance && callback) {
|
|
24
|
+
RequestQueueHandler.instance = new RequestQueueHandler(callback);
|
|
21
25
|
}
|
|
22
26
|
return RequestQueueHandler.instance;
|
|
23
27
|
}
|
|
24
|
-
start() {
|
|
25
|
-
if (!this.started) {
|
|
26
|
-
this.started = true;
|
|
27
|
-
this.startEventBatchPolling();
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
28
|
add(event) {
|
|
31
|
-
if (!process.env.
|
|
32
|
-
|
|
33
|
-
proceed: false
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
if (!constants_1.BATCH_EVENT_TYPES.includes(event.event_type)) {
|
|
37
|
-
return {
|
|
38
|
-
proceed: true
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
if (event.logs && event.logs[0] && event.logs[0].kind === 'TEST_SCREENSHOT') {
|
|
42
|
-
return {
|
|
43
|
-
proceed: true,
|
|
44
|
-
data: [event],
|
|
45
|
-
url: constants_1.DATA_SCREENSHOT_ENDPOINT
|
|
46
|
-
};
|
|
29
|
+
if (!process.env[constants_1.TESTOPS_BUILD_COMPLETED_ENV]) {
|
|
30
|
+
throw new Error('Observability build start not completed yet.');
|
|
47
31
|
}
|
|
48
32
|
this.queue.push(event);
|
|
49
|
-
|
|
50
|
-
let data;
|
|
33
|
+
bstackLogger_1.BStackLogger.debug(`Added data to request queue. Queue length = ${this.queue.length}`);
|
|
51
34
|
const shouldProceed = this.shouldProceed();
|
|
52
35
|
if (shouldProceed) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
36
|
+
this.sendBatch().catch((e) => {
|
|
37
|
+
bstackLogger_1.BStackLogger.debug('Exception in sending batch: ' + e);
|
|
38
|
+
});
|
|
56
39
|
}
|
|
57
|
-
return {
|
|
58
|
-
proceed: shouldProceed,
|
|
59
|
-
data: data,
|
|
60
|
-
url: constants_1.DATA_BATCH_ENDPOINT
|
|
61
|
-
};
|
|
62
40
|
}
|
|
63
41
|
async shutdown() {
|
|
64
42
|
this.removeEventBatchPolling('Shutting down');
|
|
65
43
|
while (this.queue.length > 0) {
|
|
66
44
|
const data = this.queue.splice(0, constants_1.DATA_BATCH_SIZE);
|
|
67
|
-
await
|
|
45
|
+
await this.callCallback(data, 'SHUTDOWN_QUEUE');
|
|
68
46
|
}
|
|
69
47
|
}
|
|
70
48
|
startEventBatchPolling() {
|
|
71
|
-
this.pollEventBatchInterval = setInterval(
|
|
72
|
-
if (this.queue.length > 0) {
|
|
73
|
-
const data = this.queue.splice(0, constants_1.DATA_BATCH_SIZE);
|
|
74
|
-
log.debug(`Sending data from request queue. Data length = ${data.length}, Queue length after removal = ${this.queue.length}`);
|
|
75
|
-
await (0, util_1.batchAndPostEvents)(constants_1.DATA_BATCH_ENDPOINT, 'INTERVAL_QUEUE', data);
|
|
76
|
-
}
|
|
77
|
-
}, constants_1.DATA_BATCH_INTERVAL);
|
|
49
|
+
this.pollEventBatchInterval = setInterval(this.sendBatch.bind(this), constants_1.DATA_BATCH_INTERVAL);
|
|
78
50
|
}
|
|
79
51
|
resetEventBatchPolling() {
|
|
80
52
|
this.removeEventBatchPolling('Resetting');
|
|
@@ -84,9 +56,16 @@ class RequestQueueHandler {
|
|
|
84
56
|
if (this.pollEventBatchInterval) {
|
|
85
57
|
log.debug(`${tag} request queue`);
|
|
86
58
|
clearInterval(this.pollEventBatchInterval);
|
|
87
|
-
this.started = false;
|
|
88
59
|
}
|
|
89
60
|
}
|
|
61
|
+
async sendBatch() {
|
|
62
|
+
const data = this.queue.splice(0, constants_1.DATA_BATCH_SIZE);
|
|
63
|
+
if (data.length === 0) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
bstackLogger_1.BStackLogger.debug(`Sending data from request queue. Data length = ${data.length}, Queue length after removal = ${this.queue.length}`);
|
|
67
|
+
await this.callCallback(data, 'INTERVAL_QUEUE');
|
|
68
|
+
}
|
|
90
69
|
shouldProceed() {
|
|
91
70
|
if (this.tearDownInvoked) {
|
|
92
71
|
log.debug('Force request-queue shutdown, as test run event is received after teardown');
|
package/build/service.d.ts
CHANGED
package/build/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAC9E,OAAO,KAAK,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAE9D,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAA8C,MAAM,SAAS,CAAA;AAKhH,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
|
package/build/service.js
CHANGED
|
@@ -13,6 +13,9 @@ const reporter_1 = __importDefault(require("./reporter"));
|
|
|
13
13
|
const performance_tester_1 = __importDefault(require("./performance-tester"));
|
|
14
14
|
const accessibility_handler_1 = __importDefault(require("./accessibility-handler"));
|
|
15
15
|
const Percy_Handler_1 = __importDefault(require("./Percy/Percy-Handler"));
|
|
16
|
+
const listener_1 = __importDefault(require("./testOps/listener"));
|
|
17
|
+
const data_store_1 = require("./data-store");
|
|
18
|
+
const usageStats_1 = __importDefault(require("./testOps/usageStats"));
|
|
16
19
|
const log = (0, logger_1.default)('@wdio/browserstack-service');
|
|
17
20
|
class BrowserstackService {
|
|
18
21
|
constructor(options, _caps, _config) {
|
|
@@ -33,7 +36,7 @@ class BrowserstackService {
|
|
|
33
36
|
this._turboScale = this._options.turboScale;
|
|
34
37
|
if (this._observability) {
|
|
35
38
|
(_a = this._config.reporters) === null || _a === void 0 ? void 0 : _a.push(reporter_1.default);
|
|
36
|
-
if (process.env.
|
|
39
|
+
if (process.env[constants_1.PERF_MEASUREMENT_ENV]) {
|
|
37
40
|
performance_tester_1.default.startMonitoring('performance-report-service.csv');
|
|
38
41
|
}
|
|
39
42
|
}
|
|
@@ -189,7 +192,7 @@ class BrowserstackService {
|
|
|
189
192
|
await ((_c = this._accessibilityHandler) === null || _c === void 0 ? void 0 : _c.afterTest(this._suiteTitle, test));
|
|
190
193
|
}
|
|
191
194
|
async after(result) {
|
|
192
|
-
var _a
|
|
195
|
+
var _a;
|
|
193
196
|
const { preferScenarioName, setSessionName, setSessionStatus } = this._options;
|
|
194
197
|
// For Cucumber: Checks scenarios that ran (i.e. not skipped) on the session
|
|
195
198
|
// Only 1 Scenario ran and option enabled => Redefine session name to Scenario's name
|
|
@@ -204,10 +207,10 @@ class BrowserstackService {
|
|
|
204
207
|
...(hasReasons ? { reason: this._failReasons.join('\n') } : {})
|
|
205
208
|
});
|
|
206
209
|
}
|
|
207
|
-
await (
|
|
208
|
-
await ((
|
|
209
|
-
|
|
210
|
-
if (process.env.
|
|
210
|
+
await listener_1.default.getInstance().onWorkerEnd();
|
|
211
|
+
await ((_a = this._percyHandler) === null || _a === void 0 ? void 0 : _a.teardown());
|
|
212
|
+
this.saveWorkerData();
|
|
213
|
+
if (process.env[constants_1.PERF_MEASUREMENT_ENV]) {
|
|
211
214
|
await performance_tester_1.default.stopAndGenerate('performance-service.html');
|
|
212
215
|
performance_tester_1.default.calculateTimes([
|
|
213
216
|
'onRunnerStart', 'onSuiteStart', 'onSuiteEnd',
|
|
@@ -408,5 +411,10 @@ class BrowserstackService {
|
|
|
408
411
|
}
|
|
409
412
|
return (await this._browser.execute(script));
|
|
410
413
|
}
|
|
414
|
+
saveWorkerData() {
|
|
415
|
+
(0, data_store_1.saveWorkerData)({
|
|
416
|
+
usageStats: usageStats_1.default.getInstance().getDataToSave()
|
|
417
|
+
});
|
|
418
|
+
}
|
|
411
419
|
}
|
|
412
420
|
exports.default = BrowserstackService;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { FeatureStatsOverview } from '../types';
|
|
2
|
+
interface FeatureStatsMap {
|
|
3
|
+
[groupId: string]: FeatureStats;
|
|
4
|
+
}
|
|
5
|
+
interface JSONConversionSettings {
|
|
6
|
+
omitGroups?: boolean;
|
|
7
|
+
onlyGroups?: boolean;
|
|
8
|
+
nestedGroups?: boolean;
|
|
9
|
+
}
|
|
10
|
+
declare class FeatureStats {
|
|
11
|
+
private triggeredCount;
|
|
12
|
+
private sentCount;
|
|
13
|
+
private failedCount;
|
|
14
|
+
private groups;
|
|
15
|
+
mark(status: string, groupId: string): void;
|
|
16
|
+
triggered(groupId?: string): void;
|
|
17
|
+
sent(groupId?: string): void;
|
|
18
|
+
failed(groupId?: string): void;
|
|
19
|
+
success(groupId?: string): void;
|
|
20
|
+
createGroup(groupId: string): FeatureStats;
|
|
21
|
+
getTriggeredCount(): number;
|
|
22
|
+
getSentCount(): number;
|
|
23
|
+
getFailedCount(): number;
|
|
24
|
+
getUsageForGroup(groupId: string): FeatureStats;
|
|
25
|
+
getOverview(): FeatureStatsOverview;
|
|
26
|
+
getGroups(): FeatureStatsMap;
|
|
27
|
+
add(featureStats: FeatureStats): void;
|
|
28
|
+
toJSON(config?: JSONConversionSettings): {
|
|
29
|
+
triggeredCount: number;
|
|
30
|
+
sentCount: number;
|
|
31
|
+
failedCount: number;
|
|
32
|
+
} | {
|
|
33
|
+
groups: Record<string, FeatureStatsOverview>;
|
|
34
|
+
triggeredCount: number;
|
|
35
|
+
sentCount: number;
|
|
36
|
+
failedCount: number;
|
|
37
|
+
} | {
|
|
38
|
+
[x: string]: FeatureStatsOverview;
|
|
39
|
+
} | {
|
|
40
|
+
groups: Record<string, FeatureStatsOverview>;
|
|
41
|
+
};
|
|
42
|
+
static fromJSON(json: any): FeatureStats;
|
|
43
|
+
}
|
|
44
|
+
export default FeatureStats;
|
|
45
|
+
//# sourceMappingURL=featureStats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"featureStats.d.ts","sourceRoot":"","sources":["../../src/testOps/featureStats.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAGpD,UAAU,eAAe;IACrB,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,CAAC;CACnC;AAED,UAAU,sBAAsB;IAC5B,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,YAAY,CAAC,EAAE,OAAO,CAAA;CACzB;AAED,cAAM,YAAY;IACd,OAAO,CAAC,cAAc,CAAY;IAClC,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,WAAW,CAAY;IAC/B,OAAO,CAAC,MAAM,CAAsB;IAE7B,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAkB3C,SAAS,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;IAOjC,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;IAO5B,MAAM,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;IAO9B,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;IAI/B,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY;IAO1C,iBAAiB,IAAI,MAAM;IAI3B,YAAY,IAAI,MAAM;IAItB,cAAc,IAAI,MAAM;IAIxB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY;IAI/C,WAAW,IAAI,oBAAoB;IAInC,SAAS,IAAI,eAAe;IAI5B,GAAG,CAAC,YAAY,EAAE,YAAY,GAAG,IAAI;IAarC,MAAM,CAAC,MAAM,GAAE,sBAA2B;;;;;;;;;;;;;;WAqBnC,QAAQ,CAAC,IAAI,EAAE,GAAG,GAAG,YAAY;CAkBlD;AAED,eAAe,YAAY,CAAA"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const bstackLogger_1 = require("../bstackLogger");
|
|
4
|
+
const util_1 = require("../util");
|
|
5
|
+
class FeatureStats {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.triggeredCount = 0;
|
|
8
|
+
this.sentCount = 0;
|
|
9
|
+
this.failedCount = 0;
|
|
10
|
+
this.groups = {};
|
|
11
|
+
}
|
|
12
|
+
mark(status, groupId) {
|
|
13
|
+
switch (status) {
|
|
14
|
+
case 'triggered':
|
|
15
|
+
this.triggered(groupId);
|
|
16
|
+
break;
|
|
17
|
+
case 'success':
|
|
18
|
+
case 'sent':
|
|
19
|
+
this.sent(groupId);
|
|
20
|
+
break;
|
|
21
|
+
case 'failed':
|
|
22
|
+
this.failed(groupId);
|
|
23
|
+
break;
|
|
24
|
+
default:
|
|
25
|
+
bstackLogger_1.BStackLogger.debug('Request to mark usage for unknown status - ' + status);
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
triggered(groupId) {
|
|
30
|
+
this.triggeredCount += 1;
|
|
31
|
+
if (groupId) {
|
|
32
|
+
this.createGroup(groupId).triggered();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
sent(groupId) {
|
|
36
|
+
this.sentCount += 1;
|
|
37
|
+
if (groupId) {
|
|
38
|
+
this.createGroup(groupId).sent();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
failed(groupId) {
|
|
42
|
+
this.failedCount += 1;
|
|
43
|
+
if (groupId) {
|
|
44
|
+
this.createGroup(groupId).failed();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
success(groupId) {
|
|
48
|
+
this.sent(groupId);
|
|
49
|
+
}
|
|
50
|
+
createGroup(groupId) {
|
|
51
|
+
if (!this.groups[groupId]) {
|
|
52
|
+
this.groups[groupId] = new FeatureStats();
|
|
53
|
+
}
|
|
54
|
+
return this.groups[groupId];
|
|
55
|
+
}
|
|
56
|
+
getTriggeredCount() {
|
|
57
|
+
return this.triggeredCount;
|
|
58
|
+
}
|
|
59
|
+
getSentCount() {
|
|
60
|
+
return this.sentCount;
|
|
61
|
+
}
|
|
62
|
+
getFailedCount() {
|
|
63
|
+
return this.failedCount;
|
|
64
|
+
}
|
|
65
|
+
getUsageForGroup(groupId) {
|
|
66
|
+
return this.groups[groupId] || new FeatureStats();
|
|
67
|
+
}
|
|
68
|
+
getOverview() {
|
|
69
|
+
return { triggeredCount: this.triggeredCount, sentCount: this.sentCount, failedCount: this.failedCount };
|
|
70
|
+
}
|
|
71
|
+
getGroups() {
|
|
72
|
+
return this.groups;
|
|
73
|
+
}
|
|
74
|
+
add(featureStats) {
|
|
75
|
+
this.triggeredCount += featureStats.getTriggeredCount();
|
|
76
|
+
this.sentCount += featureStats.getSentCount();
|
|
77
|
+
this.failedCount += featureStats.getFailedCount();
|
|
78
|
+
Object.entries(featureStats.getGroups()).forEach(([groupId, group]) => {
|
|
79
|
+
this.createGroup(groupId).add(group);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// omitGroups: true/false -> Include groups or not
|
|
83
|
+
// onlyGroups: true/false -> data includes only groups
|
|
84
|
+
// nestedGroups: true/false -> groups will be nested in groups if true
|
|
85
|
+
toJSON(config = {}) {
|
|
86
|
+
const overviewData = !config.onlyGroups ? {
|
|
87
|
+
triggeredCount: this.triggeredCount,
|
|
88
|
+
sentCount: this.sentCount,
|
|
89
|
+
failedCount: this.failedCount
|
|
90
|
+
} : {};
|
|
91
|
+
const groupsData = {};
|
|
92
|
+
if (!config.omitGroups) {
|
|
93
|
+
Object.entries(this.groups).forEach(([groupId, group]) => {
|
|
94
|
+
groupsData[groupId] = group.toJSON(); // Currently Nested groups are only overviews
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
const group = config.nestedGroups ? { groups: groupsData } : groupsData;
|
|
98
|
+
return {
|
|
99
|
+
...overviewData,
|
|
100
|
+
...group
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
static fromJSON(json) {
|
|
104
|
+
const stats = new FeatureStats();
|
|
105
|
+
if (!json || (0, util_1.isObjectEmpty)(json)) {
|
|
106
|
+
return stats;
|
|
107
|
+
}
|
|
108
|
+
stats.triggeredCount = json.triggeredCount;
|
|
109
|
+
stats.sentCount = json.sentCount;
|
|
110
|
+
stats.failedCount = json.failedCount;
|
|
111
|
+
if (!json.groups) {
|
|
112
|
+
return stats;
|
|
113
|
+
}
|
|
114
|
+
Object.entries(json.groups).forEach(([groupId, group]) => {
|
|
115
|
+
stats.groups[groupId] = FeatureStats.fromJSON(group);
|
|
116
|
+
});
|
|
117
|
+
return stats;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
exports.default = FeatureStats;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
declare class FeatureUsage {
|
|
2
|
+
private isTriggered?;
|
|
3
|
+
private status?;
|
|
4
|
+
private error?;
|
|
5
|
+
constructor(isTriggered?: boolean);
|
|
6
|
+
getTriggered(): boolean | undefined;
|
|
7
|
+
setTriggered(triggered: boolean): void;
|
|
8
|
+
setStatus(status: string): void;
|
|
9
|
+
setError(error: string): void;
|
|
10
|
+
triggered(): void;
|
|
11
|
+
failed(e: unknown): void;
|
|
12
|
+
success(): void;
|
|
13
|
+
getStatus(): string | undefined;
|
|
14
|
+
getError(): string | undefined;
|
|
15
|
+
toJSON(): {
|
|
16
|
+
isTriggered: boolean | undefined;
|
|
17
|
+
status: string | undefined;
|
|
18
|
+
error: string | undefined;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export default FeatureUsage;
|
|
22
|
+
//# sourceMappingURL=featureUsage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"featureUsage.d.ts","sourceRoot":"","sources":["../../src/testOps/featureUsage.ts"],"names":[],"mappings":"AAEA,cAAM,YAAY;IACd,OAAO,CAAC,WAAW,CAAC,CAAS;IAC7B,OAAO,CAAC,MAAM,CAAC,CAAQ;IACvB,OAAO,CAAC,KAAK,CAAC,CAAQ;gBAEV,WAAW,CAAC,EAAE,OAAO;IAM1B,YAAY,IAAI,OAAO,GAAG,SAAS;IAInC,YAAY,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI;IAItC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI/B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI7B,SAAS,IAAI,IAAI;IAIjB,MAAM,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI;IAKxB,OAAO,IAAI,IAAI;IAIf,SAAS,IAAI,MAAM,GAAG,SAAS;IAI/B,QAAQ,IAAI,MAAM,GAAG,SAAS;IAI9B,MAAM;;;;;CAOhB;AAED,eAAe,YAAY,CAAA"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const util_1 = require("../util");
|
|
4
|
+
class FeatureUsage {
|
|
5
|
+
constructor(isTriggered) {
|
|
6
|
+
if (isTriggered !== undefined) {
|
|
7
|
+
this.isTriggered = isTriggered;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
getTriggered() {
|
|
11
|
+
return this.isTriggered;
|
|
12
|
+
}
|
|
13
|
+
setTriggered(triggered) {
|
|
14
|
+
this.isTriggered = triggered;
|
|
15
|
+
}
|
|
16
|
+
setStatus(status) {
|
|
17
|
+
this.status = status;
|
|
18
|
+
}
|
|
19
|
+
setError(error) {
|
|
20
|
+
this.error = error;
|
|
21
|
+
}
|
|
22
|
+
triggered() {
|
|
23
|
+
this.isTriggered = true;
|
|
24
|
+
}
|
|
25
|
+
failed(e) {
|
|
26
|
+
this.status = 'failed';
|
|
27
|
+
this.error = (0, util_1.getErrorString)(e);
|
|
28
|
+
}
|
|
29
|
+
success() {
|
|
30
|
+
this.status = 'success';
|
|
31
|
+
}
|
|
32
|
+
getStatus() {
|
|
33
|
+
return this.status;
|
|
34
|
+
}
|
|
35
|
+
getError() {
|
|
36
|
+
return this.error;
|
|
37
|
+
}
|
|
38
|
+
toJSON() {
|
|
39
|
+
return {
|
|
40
|
+
isTriggered: this.isTriggered,
|
|
41
|
+
status: this.status,
|
|
42
|
+
error: this.error
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
exports.default = FeatureUsage;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { CBTData, LogData, ScreenshotLog, TestData } from '../types';
|
|
2
|
+
declare class Listener {
|
|
3
|
+
private static instance;
|
|
4
|
+
private readonly usageStats;
|
|
5
|
+
private readonly testStartedStats;
|
|
6
|
+
private readonly testFinishedStats;
|
|
7
|
+
private readonly hookStartedStats;
|
|
8
|
+
private readonly hookFinishedStats;
|
|
9
|
+
private readonly cbtSessionStats;
|
|
10
|
+
private readonly logEvents;
|
|
11
|
+
private requestBatcher?;
|
|
12
|
+
private pendingUploads;
|
|
13
|
+
private constructor();
|
|
14
|
+
static getInstance(): Listener;
|
|
15
|
+
onWorkerEnd(): Promise<void>;
|
|
16
|
+
uploadPending(waitTimeout?: number, waitInterval?: number): Promise<unknown>;
|
|
17
|
+
teardown(): Promise<void>;
|
|
18
|
+
hookStarted(hookData: TestData): void;
|
|
19
|
+
hookFinished(hookData: TestData): void;
|
|
20
|
+
testStarted(testData: TestData): void;
|
|
21
|
+
testFinished(testData: TestData): void;
|
|
22
|
+
logCreated(logs: LogData[]): void;
|
|
23
|
+
onScreenshot(jsonArray: ScreenshotLog[]): Promise<void>;
|
|
24
|
+
cbtSessionCreated(data: CBTData): void;
|
|
25
|
+
private markLogs;
|
|
26
|
+
private getResult;
|
|
27
|
+
private sendBatchEvents;
|
|
28
|
+
private eventsFailed;
|
|
29
|
+
private eventsSuccess;
|
|
30
|
+
private getEventForHook;
|
|
31
|
+
}
|
|
32
|
+
export default Listener;
|
|
33
|
+
//# sourceMappingURL=listener.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listener.d.ts","sourceRoot":"","sources":["../../src/testOps/listener.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAc,MAAM,UAAU,CAAA;AAWrF,cAAM,QAAQ;IACV,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAU;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAuC;IAClE,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAiD;IAClF,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAkD;IACpF,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAiD;IAClF,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAkD;IACpF,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAgD;IAChF,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyC;IACnE,OAAO,CAAC,cAAc,CAAC,CAAqB;IAC5C,OAAO,CAAC,cAAc,CAAI;IAG1B,OAAO;WAGO,WAAW,IAAI,QAAQ;IAOxB,WAAW;IASlB,aAAa,CAAC,WAAW,SAA2C,EAAE,YAAY,SAA4C,GAAG,OAAO,CAAC,OAAO,CAAC;IASjJ,QAAQ;IASP,WAAW,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAUrC,YAAY,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAUtC,WAAW,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAUrC,YAAY,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAUtC,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAY3B,YAAY,CAAC,SAAS,EAAE,aAAa,EAAE;IAgB7C,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAU7C,OAAO,CAAC,QAAQ;IAgBhB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,eAAe;IAoBvB,OAAO,CAAC,YAAY;IAmBpB,OAAO,CAAC,aAAa;IAmBrB,OAAO,CAAC,eAAe;CAK1B;AAED,eAAe,QAAQ,CAAA"}
|