@wdio/browserstack-service 7.31.1 → 7.32.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.
@@ -0,0 +1,14 @@
1
+ import type { Capabilities, Options } from '@wdio/types';
2
+ import type { BrowserstackConfig, UserConfigforReporting } from './types';
3
+ export default class CrashReporter {
4
+ static userConfigForReporting: UserConfigforReporting;
5
+ private static credentialsForCrashReportUpload;
6
+ static setCredentialsForCrashReportUpload(options: BrowserstackConfig & Options.Testrunner, config: Options.Testrunner): void;
7
+ static setConfigDetails(userConfig: Options.Testrunner, capabilities: Capabilities.RemoteCapability, options: BrowserstackConfig & Options.Testrunner): void;
8
+ static uploadCrashReport(exception: any, stackTrace: string): Promise<void>;
9
+ static deletePIIKeysFromObject(obj: {
10
+ [key: string]: any;
11
+ }): void;
12
+ static filterPII(userConfig: Options.Testrunner): any;
13
+ }
14
+ //# sourceMappingURL=crash-reporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crash-reporter.d.ts","sourceRoot":"","sources":["../src/crash-reporter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAMxD,OAAO,KAAK,EAAE,kBAAkB,EAAmC,sBAAsB,EAAE,MAAM,SAAS,CAAA;AAK1G,MAAM,CAAC,OAAO,OAAO,aAAa;IAE9B,OAAc,sBAAsB,EAAE,sBAAsB,CAAK;IAEjE,OAAO,CAAC,MAAM,CAAC,+BAA+B,CAAsC;IAEpF,MAAM,CAAC,kCAAkC,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,UAAU;IAQtH,MAAM,CAAC,gBAAgB,CAAC,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,YAAY,EAAE,YAAY,CAAC,gBAAgB,EAAE,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,UAAU;WAgBxI,iBAAiB,CAAC,SAAS,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM;IA6CjE,MAAM,CAAC,uBAAuB,CAAC,GAAG,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE;IAO1D,MAAM,CAAC,SAAS,CAAC,UAAU,EAAE,OAAO,CAAC,UAAU;CAyBlD"}
@@ -0,0 +1,117 @@
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 logger_1 = __importDefault(require("@wdio/logger"));
7
+ const got_1 = __importDefault(require("got"));
8
+ // @ts-ignore
9
+ const package_json_1 = require("../package.json");
10
+ const constants_1 = require("./constants");
11
+ const util_1 = require("./util");
12
+ const log = (0, logger_1.default)('@wdio/browserstack-service');
13
+ class CrashReporter {
14
+ static setCredentialsForCrashReportUpload(options, config) {
15
+ this.credentialsForCrashReportUpload = {
16
+ username: (0, util_1.getObservabilityUser)(options, config),
17
+ password: (0, util_1.getObservabilityKey)(options, config)
18
+ };
19
+ process.env.CREDENTIALS_FOR_CRASH_REPORTING = JSON.stringify(this.credentialsForCrashReportUpload);
20
+ }
21
+ static setConfigDetails(userConfig, capabilities, options) {
22
+ const configWithoutPII = this.filterPII(userConfig);
23
+ this.userConfigForReporting = {
24
+ framework: userConfig.framework,
25
+ services: configWithoutPII.services,
26
+ capabilities: capabilities,
27
+ env: {
28
+ 'BROWSERSTACK_BUILD': process.env.BROWSERSTACK_BUILD,
29
+ 'BROWSERSTACK_BUILD_NAME': process.env.BROWSERSTACK_BUILD_NAME,
30
+ 'BUILD_TAG': process.env.BUILD_TAG
31
+ }
32
+ };
33
+ process.env.USER_CONFIG_FOR_REPORTING = JSON.stringify(this.userConfigForReporting);
34
+ this.setCredentialsForCrashReportUpload(options, userConfig);
35
+ }
36
+ static async uploadCrashReport(exception, stackTrace) {
37
+ try {
38
+ if (!this.credentialsForCrashReportUpload.username || !this.credentialsForCrashReportUpload.password) {
39
+ this.credentialsForCrashReportUpload = process.env.CREDENTIALS_FOR_CRASH_REPORTING !== undefined ? JSON.parse(process.env.CREDENTIALS_FOR_CRASH_REPORTING) : this.credentialsForCrashReportUpload;
40
+ }
41
+ }
42
+ catch (error) {
43
+ return log.error(`[Crash_Report_Upload] Failed to parse user credentials while reporting crash due to ${error}`);
44
+ }
45
+ if (!this.credentialsForCrashReportUpload.username || !this.credentialsForCrashReportUpload.password) {
46
+ return log.error('[Crash_Report_Upload] Failed to parse user credentials while reporting crash');
47
+ }
48
+ try {
49
+ if (Object.keys(this.userConfigForReporting).length === 0) {
50
+ this.userConfigForReporting = process.env.USER_CONFIG_FOR_REPORTING !== undefined ? JSON.parse(process.env.USER_CONFIG_FOR_REPORTING) : {};
51
+ }
52
+ }
53
+ catch (error) {
54
+ log.error(`[Crash_Report_Upload] Failed to parse user config while reporting crash due to ${error}`);
55
+ this.userConfigForReporting = {};
56
+ }
57
+ const data = {
58
+ hashed_id: process.env.BS_TESTOPS_BUILD_HASHED_ID,
59
+ observability_version: {
60
+ frameworkName: 'WebdriverIO-' + (this.userConfigForReporting.framework || 'null'),
61
+ sdkVersion: package_json_1.version
62
+ },
63
+ exception: {
64
+ error: exception.toString(),
65
+ stackTrace: stackTrace
66
+ },
67
+ config: this.userConfigForReporting
68
+ };
69
+ const url = `${constants_1.DATA_ENDPOINT}/api/v1/analytics`;
70
+ got_1.default.post(url, {
71
+ ...util_1.DEFAULT_REQUEST_CONFIG,
72
+ ...this.credentialsForCrashReportUpload,
73
+ json: data
74
+ }).text().then(response => {
75
+ log.debug(`[Crash_Report_Upload] Success response: ${JSON.stringify(response)}`);
76
+ }).catch((error) => {
77
+ log.error(`[Crash_Report_Upload] Failed due to ${error}`);
78
+ });
79
+ }
80
+ static deletePIIKeysFromObject(obj) {
81
+ if (!obj) {
82
+ return;
83
+ }
84
+ ['user', 'username', 'key', 'accessKey'].forEach(key => delete obj[key]);
85
+ }
86
+ static filterPII(userConfig) {
87
+ const configWithoutPII = JSON.parse(JSON.stringify(userConfig));
88
+ this.deletePIIKeysFromObject(configWithoutPII);
89
+ const finalServices = [];
90
+ const initialServices = configWithoutPII.services;
91
+ delete configWithoutPII.services;
92
+ try {
93
+ for (const serviceArray of initialServices) {
94
+ if (Array.isArray(serviceArray) && serviceArray.length >= 2 && serviceArray[0] === 'browserstack') {
95
+ for (let idx = 1; idx < serviceArray.length; idx++) {
96
+ this.deletePIIKeysFromObject(serviceArray[idx]);
97
+ serviceArray[idx] && this.deletePIIKeysFromObject(serviceArray[idx].testObservabilityOptions);
98
+ }
99
+ finalServices.push(serviceArray);
100
+ break;
101
+ }
102
+ }
103
+ }
104
+ catch (err) {
105
+ /* Wrong configuration like strings instead of json objects could break this method, needs no action */
106
+ log.error(`Error in parsing user config PII with error ${err ? (err.stack || err) : err}`);
107
+ return configWithoutPII;
108
+ }
109
+ configWithoutPII.services = finalServices;
110
+ return configWithoutPII;
111
+ }
112
+ }
113
+ /* User test config for build run minus PII */
114
+ CrashReporter.userConfigForReporting = {};
115
+ /* User credentials used for reporting crashes in browserstack service */
116
+ CrashReporter.credentialsForCrashReportUpload = {};
117
+ exports.default = CrashReporter;
@@ -2,7 +2,7 @@ import type { Capabilities, Frameworks } from '@wdio/types';
2
2
  import type { Browser, MultiRemoteBrowser } from 'webdriverio';
3
3
  import type { BeforeCommandArgs, AfterCommandArgs } from '@wdio/reporter';
4
4
  import type { Pickle, ITestCaseHookParameter } from './cucumber-types';
5
- export default class InsightsHandler {
5
+ declare class _InsightsHandler {
6
6
  private _browser;
7
7
  private _framework?;
8
8
  private _tests;
@@ -31,10 +31,16 @@ export default class InsightsHandler {
31
31
  */
32
32
  browserCommand(commandType: string, args: BeforeCommandArgs & AfterCommandArgs, test?: Frameworks.Test | ITestCaseHookParameter): Promise<void>;
33
33
  private attachHookData;
34
+ private setHooksFromSuite;
34
35
  private getHierarchy;
36
+ private getTestRunId;
37
+ private getTestRunIdFromSuite;
35
38
  private sendTestRunEvent;
36
39
  private sendTestRunEventForCucumber;
37
40
  private getIntegrationsObject;
38
41
  private getIdentifier;
39
42
  }
43
+ declare const InsightsHandler: typeof _InsightsHandler;
44
+ type InsightsHandler = _InsightsHandler;
45
+ export default InsightsHandler;
40
46
  //# sourceMappingURL=insights-handler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"insights-handler.d.ts","sourceRoot":"","sources":["../src/insights-handler.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAC3D,OAAO,KAAK,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAC9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAGzE,OAAO,KAAK,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAA;AAOtE,MAAM,CAAC,OAAO,OAAO,eAAe;IASnB,OAAO,CAAC,QAAQ;IAAwI,OAAO,CAAC,UAAU,CAAC;IAPxL,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,aAAa,CAAC,CAAc;IACpC,OAAO,CAAC,SAAS,CAA2D;IAC5E,OAAO,CAAC,cAAc,CAAC,CAAQ;IAC/B,OAAO,CAAC,oBAAoB,CAAoC;gBAE3C,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,kBAAkB,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC,EAAE,YAAY,CAAC,YAAY,EAAE,aAAa,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,MAAM,EAAU,UAAU,CAAC,oBAAQ;IAa1L,MAAM;IAWN,UAAU,CAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG;IAa/C,SAAS,CAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,UAAU;IAc/D,UAAU,CAAE,IAAI,EAAE,UAAU,CAAC,IAAI;IASjC,SAAS,CAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,UAAU;IASrE;;QAEI;IAEE,cAAc,CAAE,KAAK,EAAE,sBAAsB;IA+B7C,aAAa,CAAE,KAAK,EAAE,sBAAsB;IAI5C,UAAU,CAAE,IAAI,EAAE,UAAU,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM;IAuBzD,SAAS,CAAE,IAAI,EAAE,UAAU,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,YAAY;IAgCzF,aAAa,CACf,WAAW,SAA2C,EACtD,YAAY,SAA4C;IAUtD,QAAQ;IAId;;OAEG;IAEG,cAAc,CAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,gBAAgB,EAAE,IAAI,CAAC,EAAE,UAAU,CAAC,IAAI,GAAG,sBAAsB;IA2DtI,OAAO,CAAC,cAAc;IActB,OAAO,CAAC,YAAY;YAYN,gBAAgB;YAuEhB,2BAA2B;IAsFzC,OAAO,CAAC,qBAAqB;IAW7B,OAAO,CAAC,aAAa;CAMxB"}
1
+ {"version":3,"file":"insights-handler.d.ts","sourceRoot":"","sources":["../src/insights-handler.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAC3D,OAAO,KAAK,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAC9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAGzE,OAAO,KAAK,EAAE,MAAM,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAA;AAsBtE,cAAM,gBAAgB;IASL,OAAO,CAAC,QAAQ;IAAwI,OAAO,CAAC,UAAU,CAAC;IAPxL,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,aAAa,CAAC,CAAc;IACpC,OAAO,CAAC,SAAS,CAA2D;IAC5E,OAAO,CAAC,cAAc,CAAC,CAAQ;IAC/B,OAAO,CAAC,oBAAoB,CAAoC;gBAE3C,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,kBAAkB,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC,EAAE,YAAY,CAAC,YAAY,EAAE,aAAa,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,MAAM,EAAU,UAAU,CAAC,oBAAQ;IAa1L,MAAM;IAWN,UAAU,CAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG;IAgB/C,SAAS,CAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,UAAU;IAqD/D,UAAU,CAAE,IAAI,EAAE,UAAU,CAAC,IAAI;IAUjC,SAAS,CAAE,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,UAAU;IAUrE;;QAEI;IAEE,cAAc,CAAE,KAAK,EAAE,sBAAsB;IA+B7C,aAAa,CAAE,KAAK,EAAE,sBAAsB;IAI5C,UAAU,CAAE,IAAI,EAAE,UAAU,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM;IAuBzD,SAAS,CAAE,IAAI,EAAE,UAAU,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,YAAY;IAgCzF,aAAa,CACf,WAAW,SAA2C,EACtD,YAAY,SAA4C;IAUtD,QAAQ;IAId;;OAEG;IAEG,cAAc,CAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,gBAAgB,EAAE,IAAI,CAAC,EAAE,UAAU,CAAC,IAAI,GAAG,sBAAsB;IA4DtI,OAAO,CAAC,cAAc;IActB,OAAO,CAAC,iBAAiB;IAyBzB,OAAO,CAAC,YAAY;IAapB,OAAO,CAAC,YAAY;IAiBpB,OAAO,CAAC,qBAAqB;YAoBf,gBAAgB;YA6EhB,2BAA2B;IAsFzC,OAAO,CAAC,qBAAqB;IAW7B,OAAO,CAAC,aAAa;CAMxB;AAGD,QAAA,MAAM,eAAe,EAAE,OAAO,gBAA0D,CAAA;AACxF,KAAK,eAAe,GAAG,gBAAgB,CAAA;AAEvC,eAAe,eAAe,CAAA"}
@@ -5,10 +5,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const path_1 = __importDefault(require("path"));
7
7
  const uuid_1 = require("uuid");
8
+ const reporter_1 = __importDefault(require("./reporter"));
8
9
  const util_1 = require("./util");
9
10
  const request_handler_1 = __importDefault(require("./request-handler"));
10
11
  const constants_1 = require("./constants");
11
- class InsightsHandler {
12
+ class _InsightsHandler {
12
13
  constructor(_browser, browserCaps, isAppAutomate, sessionId, _framework) {
13
14
  this._browser = _browser;
14
15
  this._framework = _framework;
@@ -36,33 +37,69 @@ class InsightsHandler {
36
37
  }
37
38
  }
38
39
  async beforeHook(test, context) {
39
- if (this._framework == 'mocha') {
40
- const fullTitle = `${test.parent} - ${test.title}`;
41
- const hookId = (0, uuid_1.v4)();
42
- this._tests[fullTitle] = {
43
- uuid: hookId,
44
- startedAt: (new Date()).toISOString()
45
- };
46
- this.attachHookData(context, hookId);
47
- await this.sendTestRunEvent(test, 'HookRunStarted');
40
+ if (!(0, util_1.frameworkSupportsHook)('before', this._framework)) {
41
+ return;
48
42
  }
43
+ const fullTitle = (0, util_1.getUniqueIdentifier)(test, this._framework);
44
+ const hookId = (0, uuid_1.v4)();
45
+ this._tests[fullTitle] = {
46
+ uuid: hookId,
47
+ startedAt: (new Date()).toISOString()
48
+ };
49
+ this.attachHookData(context, hookId);
50
+ await this.sendTestRunEvent(test, 'HookRunStarted');
49
51
  }
50
52
  async afterHook(test, result) {
51
- if (this._framework == 'mocha') {
52
- const fullTitle = (0, util_1.getUniqueIdentifier)(test);
53
- if (this._tests[fullTitle]) {
54
- this._tests[fullTitle].finishedAt = (new Date()).toISOString();
55
- }
56
- else {
57
- this._tests[fullTitle] = {
58
- finishedAt: (new Date()).toISOString()
59
- };
60
- }
61
- await this.sendTestRunEvent(test, 'HookRunFinished', result);
53
+ if (!(0, util_1.frameworkSupportsHook)('after', this._framework)) {
54
+ return;
55
+ }
56
+ const fullTitle = (0, util_1.getUniqueIdentifier)(test, this._framework);
57
+ if (this._tests[fullTitle]) {
58
+ this._tests[fullTitle].finishedAt = (new Date()).toISOString();
59
+ }
60
+ else {
61
+ this._tests[fullTitle] = {
62
+ finishedAt: (new Date()).toISOString()
63
+ };
64
+ }
65
+ await this.sendTestRunEvent(test, 'HookRunFinished', result);
66
+ const hookType = (0, util_1.getHookType)(test.title);
67
+ /*
68
+ If any of the `beforeAll`, `beforeEach`, `afterEach` then the tests after the hook won't run in mocha (https://github.com/mochajs/mocha/issues/4392)
69
+ So if any of this hook fails, then we are sending the next tests in the suite as skipped.
70
+ This won't be needed for `afterAll`, as even if `afterAll` fails all the tests that we need are already run by then, so we don't need to send the stats for them separately
71
+ */
72
+ if (!result.passed && (hookType === 'BEFORE_EACH' || hookType === 'BEFORE_ALL' || hookType === 'AFTER_EACH')) {
73
+ const sendTestSkip = async (skippedTest) => {
74
+ // We only need to send the tests that whose state is not determined yet. The state of tests which is determined will already be sent.
75
+ if (skippedTest.state === undefined) {
76
+ const fullTitle = `${skippedTest.parent.title} - ${skippedTest.title}`;
77
+ this._tests[fullTitle] = {
78
+ uuid: (0, uuid_1.v4)(),
79
+ startedAt: (new Date()).toISOString(),
80
+ finishedAt: (new Date()).toISOString()
81
+ };
82
+ await this.sendTestRunEvent(skippedTest, 'TestRunSkipped');
83
+ }
84
+ };
85
+ /*
86
+ Recursively send the tests as skipped for all suites below the hook. This is to handle nested describe blocks
87
+ */
88
+ const sendSuiteSkipped = async (suite) => {
89
+ for (const skippedTest of suite.tests) {
90
+ await sendTestSkip(skippedTest);
91
+ }
92
+ for (const skippedSuite of suite.suites) {
93
+ await sendSuiteSkipped(skippedSuite);
94
+ }
95
+ };
96
+ await sendSuiteSkipped(test.ctx.test.parent);
62
97
  }
63
98
  }
64
99
  async beforeTest(test) {
65
- const fullTitle = (0, util_1.getUniqueIdentifier)(test);
100
+ if (this._framework !== 'mocha')
101
+ return;
102
+ const fullTitle = (0, util_1.getUniqueIdentifier)(test, this._framework);
66
103
  this._tests[fullTitle] = {
67
104
  uuid: (0, uuid_1.v4)(),
68
105
  startedAt: (new Date()).toISOString()
@@ -70,7 +107,9 @@ class InsightsHandler {
70
107
  await this.sendTestRunEvent(test, 'TestRunStarted');
71
108
  }
72
109
  async afterTest(test, result) {
73
- const fullTitle = (0, util_1.getUniqueIdentifier)(test);
110
+ if (this._framework !== 'mocha')
111
+ return;
112
+ const fullTitle = (0, util_1.getUniqueIdentifier)(test, this._framework);
74
113
  this._tests[fullTitle] = {
75
114
  ...(this._tests[fullTitle] || {}),
76
115
  finishedAt: (new Date()).toISOString()
@@ -179,7 +218,8 @@ class InsightsHandler {
179
218
  return;
180
219
  }
181
220
  const identifier = this.getIdentifier(test);
182
- if (!this._tests[identifier]) {
221
+ const testMeta = this._tests[identifier] || reporter_1.default.getTests()[identifier];
222
+ if (!testMeta) {
183
223
  return;
184
224
  }
185
225
  // log screenshot
@@ -187,7 +227,7 @@ class InsightsHandler {
187
227
  await (0, util_1.uploadEventData)([{
188
228
  event_type: 'LogCreated',
189
229
  logs: [{
190
- test_run_uuid: this._tests[identifier].uuid,
230
+ test_run_uuid: testMeta.uuid,
191
231
  timestamp: new Date().toISOString(),
192
232
  message: args.result.value,
193
233
  kind: 'TEST_SCREENSHOT'
@@ -203,7 +243,7 @@ class InsightsHandler {
203
243
  const req = this._requestQueueHandler.add({
204
244
  event_type: 'LogCreated',
205
245
  logs: [{
206
- test_run_uuid: this._tests[identifier].uuid,
246
+ test_run_uuid: testMeta.uuid,
207
247
  timestamp: new Date().toISOString(),
208
248
  kind: 'HTTP',
209
249
  http_response: {
@@ -222,14 +262,37 @@ class InsightsHandler {
222
262
  * private methods
223
263
  */
224
264
  attachHookData(context, hookId) {
225
- if (!context.currentTest || !context.currentTest.parent) {
265
+ if (context.currentTest && context.currentTest.parent) {
266
+ const parentTest = `${context.currentTest.parent.title} - ${context.currentTest.title}`;
267
+ if (!this._hooks[parentTest]) {
268
+ this._hooks[parentTest] = [];
269
+ }
270
+ this._hooks[parentTest].push(hookId);
226
271
  return;
227
272
  }
228
- const parentTest = `${context.currentTest.parent.title} - ${context.currentTest.title}`;
229
- if (!this._hooks[parentTest]) {
230
- this._hooks[parentTest] = [];
273
+ else if (context.test) {
274
+ this.setHooksFromSuite(context.test.parent, hookId);
231
275
  }
232
- this._hooks[parentTest].push(hookId);
276
+ }
277
+ setHooksFromSuite(parent, hookId) {
278
+ if (!parent) {
279
+ return false;
280
+ }
281
+ if (parent.tests && parent.tests.length > 0) {
282
+ const uniqueIdentifier = (0, util_1.getUniqueIdentifier)(parent.tests[0], this._framework);
283
+ if (!this._hooks[uniqueIdentifier]) {
284
+ this._hooks[uniqueIdentifier] = [];
285
+ }
286
+ this._hooks[uniqueIdentifier].push(hookId);
287
+ return true;
288
+ }
289
+ for (const suite of parent.suites) {
290
+ const result = this.setHooksFromSuite(suite, hookId);
291
+ if (result) {
292
+ return true;
293
+ }
294
+ }
295
+ return false;
233
296
  }
234
297
  /*
235
298
  * Get hierarchy info
@@ -237,7 +300,8 @@ class InsightsHandler {
237
300
  getHierarchy(test) {
238
301
  const value = [];
239
302
  if (test.ctx && test.ctx.test) {
240
- let parent = test.ctx.test.parent;
303
+ // If we already have the parent object, utilize it else get from context
304
+ let parent = typeof test.parent === 'object' ? test.parent : test.ctx.test.parent;
241
305
  while (parent && parent.title !== '') {
242
306
  value.push(parent.title);
243
307
  parent = parent.parent;
@@ -245,9 +309,40 @@ class InsightsHandler {
245
309
  }
246
310
  return value.reverse();
247
311
  }
312
+ getTestRunId(context) {
313
+ if (!context) {
314
+ return;
315
+ }
316
+ if (context.currentTest) {
317
+ const uniqueIdentifier = (0, util_1.getUniqueIdentifier)(context.currentTest, this._framework);
318
+ return this._tests[uniqueIdentifier] && this._tests[uniqueIdentifier].uuid;
319
+ }
320
+ if (!context.test) {
321
+ return;
322
+ }
323
+ return this.getTestRunIdFromSuite(context.test.parent);
324
+ }
325
+ getTestRunIdFromSuite(parent) {
326
+ if (!parent) {
327
+ return;
328
+ }
329
+ for (const test of parent.tests) {
330
+ const uniqueIdentifier = (0, util_1.getUniqueIdentifier)(test, this._framework);
331
+ if (this._tests[uniqueIdentifier]) {
332
+ return this._tests[uniqueIdentifier].uuid;
333
+ }
334
+ }
335
+ for (const suite of parent.suites) {
336
+ const testRunId = this.getTestRunIdFromSuite(suite);
337
+ if (testRunId) {
338
+ return testRunId;
339
+ }
340
+ }
341
+ return;
342
+ }
248
343
  async sendTestRunEvent(test, eventType, results) {
249
344
  var _a;
250
- const fullTitle = (0, util_1.getUniqueIdentifier)(test);
345
+ const fullTitle = (0, util_1.getUniqueIdentifier)(test, this._framework);
251
346
  const testMetaData = this._tests[fullTitle];
252
347
  const testData = {
253
348
  uuid: testMetaData.uuid,
@@ -287,19 +382,24 @@ class InsightsHandler {
287
382
  testData.hooks = this._hooks[fullTitle];
288
383
  }
289
384
  }
290
- if (eventType == 'TestRunStarted') {
385
+ if (eventType === 'TestRunStarted' || eventType === 'TestRunSkipped' || eventType === 'HookRunStarted') {
291
386
  testData.integrations = {};
292
387
  if (this._browser && this._platformMeta) {
293
388
  const provider = (0, util_1.getCloudProvider)(this._browser);
294
389
  testData.integrations[provider] = this.getIntegrationsObject();
295
390
  }
296
391
  }
392
+ if (eventType === 'TestRunSkipped') {
393
+ testData.result = 'skipped';
394
+ eventType = 'TestRunFinished';
395
+ }
297
396
  const uploadData = {
298
397
  event_type: eventType,
299
398
  };
300
399
  /* istanbul ignore if */
301
400
  if (eventType.match(/HookRun/)) {
302
401
  testData.hook_type = ((_a = testData.name) === null || _a === void 0 ? void 0 : _a.toLowerCase()) ? (0, util_1.getHookType)(testData.name.toLowerCase()) : 'undefined';
402
+ testData.test_run_id = this.getTestRunId(test.ctx);
303
403
  uploadData.hook_run = testData;
304
404
  }
305
405
  else {
@@ -400,7 +500,9 @@ class InsightsHandler {
400
500
  if ('pickle' in test) {
401
501
  return (0, util_1.getUniqueIdentifierForCucumber)(test);
402
502
  }
403
- return (0, util_1.getUniqueIdentifier)(test);
503
+ return (0, util_1.getUniqueIdentifier)(test, this._framework);
404
504
  }
405
505
  }
506
+ // https://github.com/microsoft/TypeScript/issues/6543
507
+ const InsightsHandler = (0, util_1.o11yClassErrorHandler)(_InsightsHandler);
406
508
  exports.default = InsightsHandler;
@@ -1 +1 @@
1
- {"version":3,"file":"launcher.d.ts","sourceRoot":"","sources":["../src/launcher.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,yBAAyB,MAAM,oBAAoB,CAAA;AAE/D,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAIlE,OAAO,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA;AAMpF,KAAK,iBAAiB,GAAG,yBAAyB,CAAC,KAAK,GAAG;IACvD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI,CAAC;CAC7C,CAAA;AAED,MAAM,CAAC,OAAO,OAAO,2BAA4B,YAAW,QAAQ,CAAC,eAAe;IAQ5E,OAAO,CAAC,QAAQ;IAEhB,OAAO,CAAC,OAAO;IATnB,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC,OAAO,CAAC,UAAU,CAAC,CAAQ;IAC3B,OAAO,CAAC,YAAY,CAAC,CAAQ;IAC7B,OAAO,CAAC,SAAS,CAAC,CAAQ;IAC1B,OAAO,CAAC,gBAAgB,CAAC,CAAQ;gBAGrB,QAAQ,EAAE,kBAAkB,GAAG,OAAO,CAAC,UAAU,EACzD,YAAY,EAAE,YAAY,CAAC,gBAAgB,EACnC,OAAO,EAAE,OAAO,CAAC,UAAU;IA2DjC,SAAS,CAAE,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,EAAE,YAAY,CAAC,EAAE,YAAY,CAAC,kBAAkB;IA2GtF,UAAU;IA2CV,UAAU,CAAC,GAAG,EAAC,GAAG,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAkBrD;;;OAGG;IACG,YAAY,CAAE,SAAS,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAyBhE,WAAW,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC,kBAAkB,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAC,MAAM;IAoF3F,sBAAsB,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC,kBAAkB;IAyCrE;;;OAGG;IACH,oBAAoB;IA6BpB,sBAAsB,CAAC,QAAQ,CAAC,EAAC,MAAM,EAAE,SAAS,CAAC,EAAC,MAAM,EAAE,eAAe,CAAC,EAAC,MAAM;CAQtF"}
1
+ {"version":3,"file":"launcher.d.ts","sourceRoot":"","sources":["../src/launcher.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,yBAAyB,MAAM,oBAAoB,CAAA;AAE/D,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAKlE,OAAO,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA;AAapF,KAAK,iBAAiB,GAAG,yBAAyB,CAAC,KAAK,GAAG;IACvD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI,CAAC;CAC7C,CAAA;AAED,MAAM,CAAC,OAAO,OAAO,2BAA4B,YAAW,QAAQ,CAAC,eAAe;IAQ5E,OAAO,CAAC,QAAQ;IAEhB,OAAO,CAAC,OAAO;IATnB,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC,OAAO,CAAC,UAAU,CAAC,CAAQ;IAC3B,OAAO,CAAC,YAAY,CAAC,CAAQ;IAC7B,OAAO,CAAC,SAAS,CAAC,CAAQ;IAC1B,OAAO,CAAC,gBAAgB,CAAC,CAAQ;gBAGrB,QAAQ,EAAE,kBAAkB,GAAG,OAAO,CAAC,UAAU,EACzD,YAAY,EAAE,YAAY,CAAC,gBAAgB,EACnC,OAAO,EAAE,OAAO,CAAC,UAAU;IAqEjC,SAAS,CAAE,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,EAAE,YAAY,CAAC,EAAE,YAAY,CAAC,kBAAkB;IA2GtF,UAAU;IAsDV,UAAU,CAAC,GAAG,EAAC,GAAG,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAkBrD;;;OAGG;IACG,YAAY,CAAE,SAAS,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAyBhE,WAAW,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC,kBAAkB,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAC,MAAM;IAoF3F,sBAAsB,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC,kBAAkB;IAyCrE;;;OAGG;IACH,oBAAoB;IA6BpB,sBAAsB,CAAC,QAAQ,CAAC,EAAC,MAAM,EAAE,SAAS,CAAC,EAAC,MAAM,EAAE,eAAe,CAAC,EAAC,MAAM;CAQtF"}
package/build/launcher.js CHANGED
@@ -38,8 +38,10 @@ const BrowserstackLocalLauncher = __importStar(require("browserstack-local"));
38
38
  const logger_1 = __importDefault(require("@wdio/logger"));
39
39
  // @ts-ignore
40
40
  const package_json_1 = require("../package.json");
41
+ const crash_reporter_1 = __importDefault(require("./crash-reporter"));
41
42
  const constants_1 = require("./constants");
42
43
  const util_2 = require("./util");
44
+ const performance_tester_1 = __importDefault(require("./performance-tester"));
43
45
  const log = (0, logger_1.default)('@wdio/browserstack-service');
44
46
  class BrowserstackLauncherService {
45
47
  constructor(_options, capabilities, _config) {
@@ -96,6 +98,9 @@ class BrowserstackLauncherService {
96
98
  }
97
99
  });
98
100
  }
101
+ if (process.env.BROWSERSTACK_O11Y_PERF_MEASUREMENT) {
102
+ performance_tester_1.default.startMonitoring('performance-report-launcher.csv');
103
+ }
99
104
  // by default observability will be true unless specified as false
100
105
  this._options.testObservability = this._options.testObservability == false ? false : true;
101
106
  if (this._options.testObservability &&
@@ -103,6 +108,12 @@ class BrowserstackLauncherService {
103
108
  process.env.BROWSERSTACK_RERUN && process.env.BROWSERSTACK_RERUN_TESTS) {
104
109
  this._config.specs = process.env.BROWSERSTACK_RERUN_TESTS.split(',');
105
110
  }
111
+ try {
112
+ crash_reporter_1.default.setConfigDetails(this._config, capabilities, this._options);
113
+ }
114
+ catch (error) {
115
+ log.error(`[Crash_Report_Upload] Config processing failed due to ${error}`);
116
+ }
106
117
  }
107
118
  async onPrepare(config, capabilities) {
108
119
  /**
@@ -208,6 +219,15 @@ class BrowserstackLauncherService {
208
219
  console.log(`\nVisit https://observability.browserstack.com/builds/${process.env.BS_TESTOPS_BUILD_HASHED_ID} to view build report, insights, and many more debugging information all at one place!\n`);
209
220
  }
210
221
  }
222
+ if (process.env.BROWSERSTACK_O11Y_PERF_MEASUREMENT) {
223
+ await performance_tester_1.default.stopAndGenerate('performance-launcher.html');
224
+ performance_tester_1.default.calculateTimes(['launchTestSession', 'stopBuildUpstream']);
225
+ if (!process.env.START_TIME) {
226
+ return;
227
+ }
228
+ const duration = (new Date()).getTime() - (new Date(process.env.START_TIME)).getTime();
229
+ log.info(`Total duration is ${duration / 1000} s`);
230
+ }
211
231
  if (!this.browserstackLocal || !this.browserstackLocal.isRunning()) {
212
232
  return;
213
233
  }
@@ -0,0 +1,15 @@
1
+ /// <reference types="node" />
2
+ import { PerformanceObserver } from 'perf_hooks';
3
+ export default class PerformanceTester {
4
+ static _observer: PerformanceObserver;
5
+ static _csvWriter: any;
6
+ private static _events;
7
+ static started: boolean;
8
+ static startMonitoring(csvName?: string): void;
9
+ static getPerformance(): import("perf_hooks").Performance;
10
+ static calculateTimes(methods: string[]): number;
11
+ static stopAndGenerate(filename?: string): Promise<void>;
12
+ static generateReport(entries: any[]): string;
13
+ static generateCSV(entries: any[]): void;
14
+ }
15
+ //# sourceMappingURL=performance-tester.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"performance-tester.d.ts","sourceRoot":"","sources":["../src/performance-tester.ts"],"names":[],"mappings":";AAEA,OAAO,EAAe,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAM7D,MAAM,CAAC,OAAO,OAAO,iBAAiB;IAClC,MAAM,CAAC,SAAS,EAAE,mBAAmB,CAAA;IACrC,MAAM,CAAC,UAAU,EAAE,GAAG,CAAA;IACtB,OAAO,CAAC,MAAM,CAAC,OAAO,CAAyB;IAC/C,MAAM,CAAC,OAAO,UAAQ;IAEtB,MAAM,CAAC,eAAe,CAAC,OAAO,GAAE,MAAiC;IAiBjE,MAAM,CAAC,cAAc;IAIrB,MAAM,CAAC,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,GAAI,MAAM;WAepC,eAAe,CAAC,QAAQ,GAAE,MAA+B;IAoBtE,MAAM,CAAC,cAAc,CAAC,OAAO,EAAE,GAAG,EAAE;IAWpC,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,GAAG,EAAE;CAuBpC"}
@@ -0,0 +1,98 @@
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 csv_writer_1 = require("csv-writer");
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const perf_hooks_1 = require("perf_hooks");
9
+ const logger_1 = __importDefault(require("@wdio/logger"));
10
+ const util_1 = require("./util");
11
+ const log = (0, logger_1.default)('@wdio/browserstack-service');
12
+ class PerformanceTester {
13
+ static startMonitoring(csvName = 'performance-report.csv') {
14
+ this._observer = new perf_hooks_1.PerformanceObserver(list => {
15
+ list.getEntries().forEach(entry => {
16
+ this._events.push(entry);
17
+ });
18
+ });
19
+ this._observer.observe({ buffered: true, entryTypes: ['function'] });
20
+ this.started = true;
21
+ this._csvWriter = (0, csv_writer_1.createObjectCsvWriter)({
22
+ path: csvName,
23
+ header: [
24
+ { id: 'name', title: 'Function Name' },
25
+ { id: 'time', title: 'Execution Time (ms)' }
26
+ ]
27
+ });
28
+ }
29
+ static getPerformance() {
30
+ return perf_hooks_1.performance;
31
+ }
32
+ static calculateTimes(methods) {
33
+ const times = {};
34
+ this._events.map((entry) => {
35
+ if (!times[entry.name]) {
36
+ times[entry.name] = 0;
37
+ }
38
+ times[entry.name] += entry.duration;
39
+ });
40
+ const timeTaken = methods.reduce((a, c) => {
41
+ return times[c] + (a || 0);
42
+ }, 0);
43
+ log.info(`Time for ${methods} is `, timeTaken);
44
+ return timeTaken;
45
+ }
46
+ static async stopAndGenerate(filename = 'performance-own.html') {
47
+ if (!this.started)
48
+ return;
49
+ await (0, util_1.sleep)(2000); // Wait to 2s just to finish any running callbacks for timerify
50
+ this._observer.disconnect();
51
+ this.started = false;
52
+ this.generateCSV(this._events);
53
+ const content = this.generateReport(this._events);
54
+ const path = process.cwd() + '/' + filename;
55
+ fs_1.default.writeFile(path, content, err => {
56
+ if (err) {
57
+ log.error('Error in writing html', err);
58
+ return;
59
+ }
60
+ log.info('Performance report is at ', path);
61
+ });
62
+ }
63
+ static generateReport(entries) {
64
+ let html = '<!DOCTYPE html><html><head><title>Performance Report</title></head><body>';
65
+ html += '<h1>Performance Report</h1>';
66
+ html += '<table><thead><tr><th>Function Name</th><th>Duration (ms)</th></tr></thead><tbody>';
67
+ entries.forEach((entry) => {
68
+ html += `<tr><td>${entry.name}</td><td>${entry.duration}</td></tr>`;
69
+ });
70
+ html += '</tbody></table></body></html>';
71
+ return html;
72
+ }
73
+ static generateCSV(entries) {
74
+ const times = {};
75
+ entries.map((entry) => {
76
+ if (!times[entry.name]) {
77
+ times[entry.name] = 0;
78
+ }
79
+ times[entry.name] += entry.duration;
80
+ return {
81
+ name: entry.name,
82
+ time: entry.duration
83
+ };
84
+ });
85
+ const dat = Object.entries(times).map(([key, value]) => {
86
+ return {
87
+ name: key,
88
+ time: value
89
+ };
90
+ });
91
+ this._csvWriter.writeRecords(dat)
92
+ .then(() => log.info('Performance CSV report generated successfully'))
93
+ .catch((error) => console.error(error));
94
+ }
95
+ }
96
+ PerformanceTester._events = [];
97
+ PerformanceTester.started = false;
98
+ exports.default = PerformanceTester;
@@ -1,13 +1,31 @@
1
- import WDIOReporter, { SuiteStats, TestStats, RunnerStats } from '@wdio/reporter';
2
- export default class TestReporter extends WDIOReporter {
1
+ import WDIOReporter, { SuiteStats, TestStats, RunnerStats, HookStats } from '@wdio/reporter';
2
+ import { TestMeta } from './types';
3
+ declare class _TestReporter extends WDIOReporter {
3
4
  private _capabilities;
4
5
  private _config?;
5
6
  private _observability;
6
7
  private _sessionId?;
7
8
  private _suiteName?;
8
9
  private _requestQueueHandler;
9
- onRunnerStart(runnerStats: RunnerStats): void;
10
+ private _suites;
11
+ private static _tests;
12
+ private _gitConfigPath?;
13
+ private _gitConfigured;
14
+ onRunnerStart(runnerStats: RunnerStats): Promise<void>;
15
+ configureGit(): Promise<void>;
16
+ static getTests(): Record<string, TestMeta>;
10
17
  onSuiteStart(suiteStats: SuiteStats): void;
18
+ onSuiteEnd(): void;
19
+ needToSendData(testType?: string, event?: string): boolean;
20
+ onTestEnd(testStats: TestStats): Promise<void>;
21
+ onTestStart(testStats: TestStats): Promise<void>;
22
+ onHookStart(hookStats: HookStats): Promise<void>;
23
+ onHookEnd(hookStats: HookStats): Promise<void>;
24
+ getHookIdentifier(hookStats: HookStats): string;
25
+ sendTestRunEvent(testStats: TestStats | HookStats, eventType: string): Promise<void>;
11
26
  onTestSkip(testStats: TestStats): Promise<void>;
12
27
  }
28
+ declare const TestReporter: typeof _TestReporter;
29
+ type TestReporter = _TestReporter;
30
+ export default TestReporter;
13
31
  //# sourceMappingURL=reporter.d.ts.map