@wdio/browserstack-service 8.0.14 → 8.1.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/constants.d.ts +10 -1
- package/build/constants.d.ts.map +1 -1
- package/build/constants.js +11 -1
- package/build/cucumber-types.d.ts +177 -0
- package/build/cucumber-types.d.ts.map +1 -0
- package/build/cucumber-types.js +2 -0
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/insights-handler.d.ts +40 -0
- package/build/insights-handler.d.ts.map +1 -0
- package/build/insights-handler.js +389 -0
- package/build/launcher.d.ts +5 -2
- package/build/launcher.d.ts.map +1 -1
- package/build/launcher.js +39 -4
- package/build/reporter.d.ts +14 -0
- package/build/reporter.d.ts.map +1 -0
- package/build/reporter.js +66 -0
- package/build/request-handler.d.ts +26 -0
- package/build/request-handler.d.ts.map +1 -0
- package/build/request-handler.js +88 -0
- package/build/service.d.ts +15 -16
- package/build/service.d.ts.map +1 -1
- package/build/service.js +71 -30
- package/build/types.d.ts +119 -0
- package/build/types.d.ts.map +1 -1
- package/build/util.d.ts +68 -1
- package/build/util.d.ts.map +1 -1
- package/build/util.js +441 -1
- package/package.json +12 -5
package/build/launcher.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as BrowserstackLocalLauncher from 'browserstack-local';
|
|
2
2
|
import type { Capabilities, Services, Options } from '@wdio/types';
|
|
3
|
-
import type { BrowserstackConfig, App, AppConfig, AppUploadResponse } from './types';
|
|
3
|
+
import type { BrowserstackConfig, App, AppConfig, AppUploadResponse } from './types.js';
|
|
4
4
|
type BrowserstackLocal = BrowserstackLocalLauncher.Local & {
|
|
5
5
|
pid?: number;
|
|
6
6
|
stop(callback: (err?: any) => void): void;
|
|
@@ -9,9 +9,12 @@ export default class BrowserstackLauncherService implements Services.ServiceInst
|
|
|
9
9
|
private _options;
|
|
10
10
|
private _config;
|
|
11
11
|
browserstackLocal?: BrowserstackLocal;
|
|
12
|
+
private _buildName?;
|
|
13
|
+
private _projectName?;
|
|
14
|
+
private _buildTag?;
|
|
12
15
|
constructor(_options: BrowserstackConfig & Options.Testrunner, capabilities: Capabilities.RemoteCapability, _config: Options.Testrunner);
|
|
13
16
|
onPrepare(config?: Options.Testrunner, capabilities?: Capabilities.RemoteCapabilities): Promise<unknown>;
|
|
14
|
-
onComplete():
|
|
17
|
+
onComplete(): Promise<unknown>;
|
|
15
18
|
_uploadApp(app: App): Promise<AppUploadResponse>;
|
|
16
19
|
/**
|
|
17
20
|
* @param {String | AppConfig} appConfig <string>: should be "app file path" or "app_url" or "custom_id" or "shareable_id".
|
package/build/launcher.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"launcher.d.ts","sourceRoot":"","sources":["../src/launcher.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,yBAAyB,MAAM,oBAAoB,CAAA;AAG/D,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAElE,OAAO,KAAK,EAAE,kBAAkB,EAAE,GAAG,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"launcher.d.ts","sourceRoot":"","sources":["../src/launcher.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,yBAAyB,MAAM,oBAAoB,CAAA;AAG/D,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAElE,OAAO,KAAK,EAAE,kBAAkB,EAAE,GAAG,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AASvF,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;IAO5E,OAAO,CAAC,QAAQ;IAEhB,OAAO,CAAC,OAAO;IARnB,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC,OAAO,CAAC,UAAU,CAAC,CAAQ;IAC3B,OAAO,CAAC,YAAY,CAAC,CAAQ;IAC7B,OAAO,CAAC,SAAS,CAAC,CAAQ;gBAGd,QAAQ,EAAE,kBAAkB,GAAG,OAAO,CAAC,UAAU,EACzD,YAAY,EAAE,YAAY,CAAC,gBAAgB,EACnC,OAAO,EAAE,OAAO,CAAC,UAAU;IAmDjC,SAAS,CAAE,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,EAAE,YAAY,CAAC,EAAE,YAAY,CAAC,kBAAkB;IAuFtF,UAAU;IA2CV,UAAU,CAAC,GAAG,EAAC,GAAG,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAsBrD;;;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;CA+C9F"}
|
package/build/launcher.js
CHANGED
|
@@ -9,6 +9,7 @@ import { SevereServiceError } from 'webdriverio';
|
|
|
9
9
|
import * as BrowserstackLocalLauncher from 'browserstack-local';
|
|
10
10
|
import logger from '@wdio/logger';
|
|
11
11
|
import { VALID_APP_EXTENSION } from './constants.js';
|
|
12
|
+
import { launchTestSession, shouldAddServiceVersion, stopBuildUpstream } from './util.js';
|
|
12
13
|
const require = createRequire(import.meta.url);
|
|
13
14
|
const { version: bstackServiceVersion } = require('../package.json');
|
|
14
15
|
const log = logger('@wdio/browserstack-service');
|
|
@@ -16,6 +17,9 @@ export default class BrowserstackLauncherService {
|
|
|
16
17
|
_options;
|
|
17
18
|
_config;
|
|
18
19
|
browserstackLocal;
|
|
20
|
+
_buildName;
|
|
21
|
+
_projectName;
|
|
22
|
+
_buildTag;
|
|
19
23
|
constructor(_options, capabilities, _config) {
|
|
20
24
|
this._options = _options;
|
|
21
25
|
this._config = _config;
|
|
@@ -28,12 +32,15 @@ export default class BrowserstackLauncherService {
|
|
|
28
32
|
if (extensionCaps.length) {
|
|
29
33
|
capability['bstack:options'] = { wdioService: bstackServiceVersion };
|
|
30
34
|
}
|
|
31
|
-
else {
|
|
35
|
+
else if (shouldAddServiceVersion(this._config, this._options.testObservability)) {
|
|
32
36
|
capability['browserstack.wdioService'] = bstackServiceVersion;
|
|
33
37
|
}
|
|
34
38
|
}
|
|
35
39
|
else {
|
|
36
40
|
capability['bstack:options'].wdioService = bstackServiceVersion;
|
|
41
|
+
this._buildName = capability['bstack:options'].buildName;
|
|
42
|
+
this._projectName = capability['bstack:options'].projectName;
|
|
43
|
+
this._buildTag = capability['bstack:options'].buildTag;
|
|
37
44
|
}
|
|
38
45
|
});
|
|
39
46
|
}
|
|
@@ -44,15 +51,27 @@ export default class BrowserstackLauncherService {
|
|
|
44
51
|
if (extensionCaps.length) {
|
|
45
52
|
caps.capabilities['bstack:options'] = { wdioService: bstackServiceVersion };
|
|
46
53
|
}
|
|
47
|
-
else {
|
|
54
|
+
else if (shouldAddServiceVersion(this._config, this._options.testObservability)) {
|
|
48
55
|
caps.capabilities['browserstack.wdioService'] = bstackServiceVersion;
|
|
49
56
|
}
|
|
50
57
|
}
|
|
51
58
|
else {
|
|
52
|
-
caps.capabilities['bstack:options']
|
|
59
|
+
const bstackOptions = caps.capabilities['bstack:options'];
|
|
60
|
+
bstackOptions.wdioService = bstackServiceVersion;
|
|
61
|
+
this._buildName = bstackOptions.buildName;
|
|
62
|
+
this._projectName = bstackOptions.projectName;
|
|
63
|
+
this._buildTag = bstackOptions.buildTag;
|
|
53
64
|
}
|
|
54
65
|
});
|
|
55
66
|
}
|
|
67
|
+
// by default observability will be true unless specified as false
|
|
68
|
+
this._options.testObservability = this._options.testObservability === false ? false : true;
|
|
69
|
+
if (this._options.testObservability
|
|
70
|
+
&&
|
|
71
|
+
// update files to run if it's a rerun
|
|
72
|
+
process.env.BROWSERSTACK_RERUN && process.env.BROWSERSTACK_RERUN_TESTS) {
|
|
73
|
+
this._config.specs = process.env.BROWSERSTACK_RERUN_TESTS.split(',');
|
|
74
|
+
}
|
|
56
75
|
}
|
|
57
76
|
async onPrepare(config, capabilities) {
|
|
58
77
|
/**
|
|
@@ -87,6 +106,15 @@ export default class BrowserstackLauncherService {
|
|
|
87
106
|
log.info(`Using app: ${app.app}`);
|
|
88
107
|
this._updateCaps(capabilities, 'app', app.app);
|
|
89
108
|
}
|
|
109
|
+
if (this._options.testObservability) {
|
|
110
|
+
log.debug('Sending launch start event');
|
|
111
|
+
await launchTestSession(this._options, this._config, {
|
|
112
|
+
projectName: this._projectName,
|
|
113
|
+
buildName: this._buildName,
|
|
114
|
+
buildTag: this._buildTag,
|
|
115
|
+
bstackServiceVersion: bstackServiceVersion
|
|
116
|
+
});
|
|
117
|
+
}
|
|
90
118
|
if (!this._options.browserstackLocal) {
|
|
91
119
|
return log.info('browserstackLocal is not enabled - skipping...');
|
|
92
120
|
}
|
|
@@ -124,7 +152,14 @@ export default class BrowserstackLauncherService {
|
|
|
124
152
|
return Promise.reject(err);
|
|
125
153
|
});
|
|
126
154
|
}
|
|
127
|
-
onComplete() {
|
|
155
|
+
async onComplete() {
|
|
156
|
+
if (this._options.testObservability) {
|
|
157
|
+
log.debug('Sending stop launch event');
|
|
158
|
+
await stopBuildUpstream();
|
|
159
|
+
if (process.env.BS_TESTOPS_BUILD_HASHED_ID) {
|
|
160
|
+
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`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
128
163
|
if (!this.browserstackLocal || !this.browserstackLocal.isRunning()) {
|
|
129
164
|
return;
|
|
130
165
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { SuiteStats, TestStats, RunnerStats } from '@wdio/reporter';
|
|
2
|
+
import WDIOReporter from '@wdio/reporter';
|
|
3
|
+
export default class TestReporter extends WDIOReporter {
|
|
4
|
+
private _capabilities;
|
|
5
|
+
private _config?;
|
|
6
|
+
private _observability;
|
|
7
|
+
private _sessionId?;
|
|
8
|
+
private _suiteName?;
|
|
9
|
+
private _requestQueueHandler;
|
|
10
|
+
onRunnerStart(runnerStats: RunnerStats): void;
|
|
11
|
+
onSuiteStart(suiteStats: SuiteStats): void;
|
|
12
|
+
onTestSkip(testStats: TestStats): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=reporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AACxE,OAAO,YAAY,MAAM,gBAAgB,CAAA;AASzC,MAAM,CAAC,OAAO,OAAO,YAAa,SAAQ,YAAY;IAClD,OAAO,CAAC,aAAa,CAAgC;IACrD,OAAO,CAAC,OAAO,CAAC,CAAyC;IACzD,OAAO,CAAC,cAAc,CAAO;IAC7B,OAAO,CAAC,UAAU,CAAC,CAAQ;IAC3B,OAAO,CAAC,UAAU,CAAC,CAAQ;IAC3B,OAAO,CAAC,oBAAoB,CAAoC;IAEhE,aAAa,CAAE,WAAW,EAAE,WAAW;IASvC,YAAY,CAAE,UAAU,EAAE,UAAU;IAI9B,UAAU,CAAE,SAAS,EAAE,SAAS;CAgDzC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import WDIOReporter from '@wdio/reporter';
|
|
2
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
3
|
+
import { getCloudProvider, uploadEventData, getHierarchy } from './util.js';
|
|
4
|
+
import RequestQueueHandler from './request-handler.js';
|
|
5
|
+
export default class TestReporter extends WDIOReporter {
|
|
6
|
+
_capabilities = {};
|
|
7
|
+
_config;
|
|
8
|
+
_observability = true;
|
|
9
|
+
_sessionId;
|
|
10
|
+
_suiteName;
|
|
11
|
+
_requestQueueHandler = RequestQueueHandler.getInstance();
|
|
12
|
+
onRunnerStart(runnerStats) {
|
|
13
|
+
this._capabilities = runnerStats.capabilities;
|
|
14
|
+
this._config = runnerStats.config;
|
|
15
|
+
this._sessionId = runnerStats.sessionId;
|
|
16
|
+
if (typeof this._config.testObservability !== 'undefined') {
|
|
17
|
+
this._observability = this._config.testObservability;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
onSuiteStart(suiteStats) {
|
|
21
|
+
this._suiteName = suiteStats.file;
|
|
22
|
+
}
|
|
23
|
+
async onTestSkip(testStats) {
|
|
24
|
+
// cucumber steps call this method. We don't want step skipped state so skip for cucumber
|
|
25
|
+
const framework = this._config?.framework;
|
|
26
|
+
if (this._observability && framework !== 'cucumber') {
|
|
27
|
+
const testData = {
|
|
28
|
+
uuid: uuidv4(),
|
|
29
|
+
type: testStats.type,
|
|
30
|
+
name: testStats.title,
|
|
31
|
+
body: {
|
|
32
|
+
lang: 'webdriverio',
|
|
33
|
+
code: null
|
|
34
|
+
},
|
|
35
|
+
scope: testStats.fullTitle,
|
|
36
|
+
scopes: getHierarchy(testStats.fullTitle),
|
|
37
|
+
identifier: testStats.fullTitle,
|
|
38
|
+
file_name: this._suiteName,
|
|
39
|
+
location: this._suiteName,
|
|
40
|
+
started_at: (new Date()).toISOString(),
|
|
41
|
+
framework: framework,
|
|
42
|
+
finished_at: (new Date()).toISOString(),
|
|
43
|
+
duration_in_ms: testStats._duration,
|
|
44
|
+
retries: { limit: 0, attempts: 0 },
|
|
45
|
+
result: testStats.state,
|
|
46
|
+
};
|
|
47
|
+
const cloudProvider = getCloudProvider({ options: { hostname: this._config?.hostname } });
|
|
48
|
+
testData.integrations = {};
|
|
49
|
+
testData.integrations[cloudProvider] = {
|
|
50
|
+
capabilities: this._capabilities,
|
|
51
|
+
session_id: this._sessionId,
|
|
52
|
+
browser: this._capabilities?.browserName,
|
|
53
|
+
browser_version: this._capabilities?.browserVersion,
|
|
54
|
+
platform: this._capabilities?.platformName,
|
|
55
|
+
};
|
|
56
|
+
const uploadData = {
|
|
57
|
+
event_type: 'TestRunFinished',
|
|
58
|
+
test_run: testData
|
|
59
|
+
};
|
|
60
|
+
const req = this._requestQueueHandler.add(uploadData);
|
|
61
|
+
if (req.proceed && req.data) {
|
|
62
|
+
await uploadEventData(req.data, req.url);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { UploadType } from './types.js';
|
|
2
|
+
export default class RequestQueueHandler {
|
|
3
|
+
private queue;
|
|
4
|
+
private started;
|
|
5
|
+
private pollEventBatchInterval?;
|
|
6
|
+
pendingUploads: number;
|
|
7
|
+
static instance: RequestQueueHandler;
|
|
8
|
+
private constructor();
|
|
9
|
+
static getInstance(): RequestQueueHandler;
|
|
10
|
+
start(): void;
|
|
11
|
+
add(event: UploadType): {
|
|
12
|
+
proceed: boolean;
|
|
13
|
+
data?: undefined;
|
|
14
|
+
url?: undefined;
|
|
15
|
+
} | {
|
|
16
|
+
proceed: boolean;
|
|
17
|
+
data: UploadType[] | undefined;
|
|
18
|
+
url: string;
|
|
19
|
+
};
|
|
20
|
+
shutdown(): Promise<void>;
|
|
21
|
+
startEventBatchPolling(): void;
|
|
22
|
+
resetEventBatchPolling(): void;
|
|
23
|
+
removeEventBatchPolling(tag: string): void;
|
|
24
|
+
shouldProceed(): boolean;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=request-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-handler.d.ts","sourceRoot":"","sources":["../src/request-handler.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAK5C,MAAM,CAAC,OAAO,OAAO,mBAAmB;IACpC,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,sBAAsB,CAAC,CAAgC;IACxD,cAAc,SAAI;IAEzB,MAAM,CAAC,QAAQ,EAAE,mBAAmB,CAAA;IAGpC,OAAO;WAEO,WAAW,IAAI,mBAAmB;IAOhD,KAAK;IAOL,GAAG,CAAE,KAAK,EAAE,UAAU;;;;;;;;;IAuChB,QAAQ;IAQd,sBAAsB;IAUtB,sBAAsB;IAKtB,uBAAuB,CAAE,GAAG,EAAE,MAAM;IAQpC,aAAa;CAGhB"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import logger from '@wdio/logger';
|
|
2
|
+
import { DATA_BATCH_SIZE, DATA_BATCH_INTERVAL, DATA_BATCH_ENDPOINT, BATCH_EVENT_TYPES, DATA_SCREENSHOT_ENDPOINT } from './constants.js';
|
|
3
|
+
import { batchAndPostEvents } from './util.js';
|
|
4
|
+
const log = logger('@wdio/browserstack-service');
|
|
5
|
+
export default class RequestQueueHandler {
|
|
6
|
+
queue = [];
|
|
7
|
+
started = false;
|
|
8
|
+
pollEventBatchInterval;
|
|
9
|
+
pendingUploads = 0;
|
|
10
|
+
static instance;
|
|
11
|
+
// making it private to use singleton pattern
|
|
12
|
+
constructor() { }
|
|
13
|
+
static getInstance() {
|
|
14
|
+
if (!RequestQueueHandler.instance) {
|
|
15
|
+
RequestQueueHandler.instance = new RequestQueueHandler();
|
|
16
|
+
}
|
|
17
|
+
return RequestQueueHandler.instance;
|
|
18
|
+
}
|
|
19
|
+
start() {
|
|
20
|
+
if (!this.started) {
|
|
21
|
+
this.started = true;
|
|
22
|
+
this.startEventBatchPolling();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
add(event) {
|
|
26
|
+
if (!process.env.BS_TESTOPS_BUILD_COMPLETED) {
|
|
27
|
+
return {
|
|
28
|
+
proceed: false
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (!BATCH_EVENT_TYPES.includes(event.event_type)) {
|
|
32
|
+
return {
|
|
33
|
+
proceed: true
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (event.logs && event.logs[0] && event.logs[0].kind === 'TEST_SCREENSHOT') {
|
|
37
|
+
return {
|
|
38
|
+
proceed: true,
|
|
39
|
+
data: [event],
|
|
40
|
+
url: DATA_SCREENSHOT_ENDPOINT
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
this.queue.push(event);
|
|
44
|
+
log.debug(`Added data to request queue. Queue length = ${this.queue.length}`);
|
|
45
|
+
let data;
|
|
46
|
+
const shouldProceed = this.shouldProceed();
|
|
47
|
+
if (shouldProceed) {
|
|
48
|
+
data = this.queue.splice(0, DATA_BATCH_SIZE);
|
|
49
|
+
this.resetEventBatchPolling();
|
|
50
|
+
log.debug(`Sending data from request queue. Data length = ${data.length}, Queue length after removal = ${this.queue.length}`);
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
proceed: shouldProceed,
|
|
54
|
+
data: data,
|
|
55
|
+
url: DATA_BATCH_ENDPOINT
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
async shutdown() {
|
|
59
|
+
this.removeEventBatchPolling('Shutting down');
|
|
60
|
+
while (this.queue.length > 0) {
|
|
61
|
+
const data = this.queue.splice(0, DATA_BATCH_SIZE);
|
|
62
|
+
await batchAndPostEvents(DATA_BATCH_ENDPOINT, 'SHUTDOWN_QUEUE', data);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
startEventBatchPolling() {
|
|
66
|
+
this.pollEventBatchInterval = setInterval(async () => {
|
|
67
|
+
if (this.queue.length > 0) {
|
|
68
|
+
const data = this.queue.splice(0, DATA_BATCH_SIZE);
|
|
69
|
+
log.debug(`Sending data from request queue. Data length = ${data.length}, Queue length after removal = ${this.queue.length}`);
|
|
70
|
+
await batchAndPostEvents(DATA_BATCH_ENDPOINT, 'INTERVAL_QUEUE', data);
|
|
71
|
+
}
|
|
72
|
+
}, DATA_BATCH_INTERVAL);
|
|
73
|
+
}
|
|
74
|
+
resetEventBatchPolling() {
|
|
75
|
+
this.removeEventBatchPolling('Resetting');
|
|
76
|
+
this.startEventBatchPolling();
|
|
77
|
+
}
|
|
78
|
+
removeEventBatchPolling(tag) {
|
|
79
|
+
if (this.pollEventBatchInterval) {
|
|
80
|
+
log.debug(`${tag} request queue`);
|
|
81
|
+
clearInterval(this.pollEventBatchInterval);
|
|
82
|
+
this.started = false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
shouldProceed() {
|
|
86
|
+
return this.queue.length >= DATA_BATCH_SIZE;
|
|
87
|
+
}
|
|
88
|
+
}
|
package/build/service.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Services, Capabilities, Options, Frameworks } from '@wdio/types';
|
|
2
2
|
import type { Browser, MultiRemoteBrowser } from 'webdriverio';
|
|
3
|
-
import type { BrowserstackConfig, MultiRemoteAction } from './types';
|
|
3
|
+
import type { BrowserstackConfig, MultiRemoteAction } from './types.js';
|
|
4
|
+
import type { Pickle, Feature, ITestCaseHookParameter } from './cucumber-types.js';
|
|
4
5
|
export default class BrowserstackService implements Services.ServiceInstance {
|
|
5
6
|
private _caps;
|
|
6
7
|
private _config;
|
|
@@ -13,6 +14,9 @@ export default class BrowserstackService implements Services.ServiceInstance {
|
|
|
13
14
|
private _fullTitle?;
|
|
14
15
|
private _options;
|
|
15
16
|
private _specsRan;
|
|
17
|
+
private _observability;
|
|
18
|
+
private _currentTest?;
|
|
19
|
+
private _insightsHandler?;
|
|
16
20
|
constructor(options: BrowserstackConfig & Options.Testrunner, _caps: Capabilities.RemoteCapability, _config: Options.Testrunner);
|
|
17
21
|
_updateCaps(fn: (caps: Capabilities.Capabilities | Capabilities.DesiredCapabilities) => void): void;
|
|
18
22
|
beforeSession(config: Omit<Options.Testrunner, 'capabilities'>): void;
|
|
@@ -25,33 +29,28 @@ export default class BrowserstackService implements Services.ServiceInstance {
|
|
|
25
29
|
* and `suite.fullTitle` is `undefined`, so no alternative to use for the job name.
|
|
26
30
|
*/
|
|
27
31
|
beforeSuite(suite: Frameworks.Suite): Promise<void>;
|
|
32
|
+
beforeHook(test: Frameworks.Test, context: any): Promise<void>;
|
|
33
|
+
afterHook(test: Frameworks.Test, context: unknown, result: Frameworks.TestResult): Promise<void>;
|
|
28
34
|
beforeTest(test: Frameworks.Test): Promise<void>;
|
|
35
|
+
afterTest(test: Frameworks.Test, context: never, results: Frameworks.TestResult): Promise<void>;
|
|
36
|
+
after(result: number): Promise<void>;
|
|
29
37
|
/**
|
|
30
38
|
* For CucumberJS
|
|
31
39
|
*/
|
|
32
|
-
beforeFeature(uri:
|
|
33
|
-
name: string;
|
|
34
|
-
}): Promise<void>;
|
|
40
|
+
beforeFeature(uri: string, feature: Feature): Promise<void>;
|
|
35
41
|
/**
|
|
36
42
|
* Runs before a Cucumber Scenario.
|
|
37
43
|
* @param world world object containing information on pickle and test step
|
|
38
44
|
*/
|
|
39
|
-
beforeScenario(world:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
beforeStep(step: Frameworks.PickleStep): Promise<void>;
|
|
44
|
-
afterTest(test: Frameworks.Test, context: never, results: Frameworks.TestResult): void;
|
|
45
|
-
after(result: number): Promise<void>;
|
|
46
|
-
/**
|
|
47
|
-
* For CucumberJS
|
|
48
|
-
*/
|
|
49
|
-
afterScenario(world: Frameworks.World): void;
|
|
45
|
+
beforeScenario(world: ITestCaseHookParameter): Promise<void>;
|
|
46
|
+
afterScenario(world: ITestCaseHookParameter): Promise<void>;
|
|
47
|
+
beforeStep(step: Frameworks.PickleStep, scenario: Pickle): Promise<void>;
|
|
48
|
+
afterStep(step: Frameworks.PickleStep, scenario: Pickle, result: Frameworks.PickleResult): Promise<void>;
|
|
50
49
|
onReload(oldSessionId: string, newSessionId: string): Promise<void>;
|
|
51
50
|
_isAppAutomate(): boolean;
|
|
52
51
|
_updateJob(requestBody: any): Promise<any>;
|
|
53
52
|
_multiRemoteAction(action: MultiRemoteAction): Promise<any>;
|
|
54
|
-
_update(sessionId: string, requestBody: any): import("got").CancelableRequest<import("got").Response<string>>;
|
|
53
|
+
_update(sessionId: string, requestBody: any): Promise<void> | import("got").CancelableRequest<import("got").Response<string>>;
|
|
55
54
|
_printSessionURL(): Promise<void>;
|
|
56
55
|
private _setSessionName;
|
|
57
56
|
private _setAnnotation;
|
package/build/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
|
package/build/service.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
1
3
|
import logger from '@wdio/logger';
|
|
2
4
|
import got from 'got';
|
|
3
|
-
import { getBrowserDescription, getBrowserCapabilities, isBrowserstackCapability, getParentSuiteName } from './util.js';
|
|
5
|
+
import { getBrowserDescription, getBrowserCapabilities, isBrowserstackCapability, getParentSuiteName, isBrowserstackSession } from './util.js';
|
|
6
|
+
import InsightsHandler from './insights-handler.js';
|
|
4
7
|
import { DEFAULT_OPTIONS } from './constants.js';
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
5
10
|
const log = logger('@wdio/browserstack-service');
|
|
6
11
|
export default class BrowserstackService {
|
|
7
12
|
_caps;
|
|
@@ -15,12 +20,19 @@ export default class BrowserstackService {
|
|
|
15
20
|
_fullTitle;
|
|
16
21
|
_options;
|
|
17
22
|
_specsRan = false;
|
|
23
|
+
_observability;
|
|
24
|
+
_currentTest;
|
|
25
|
+
_insightsHandler;
|
|
18
26
|
constructor(options, _caps, _config) {
|
|
19
27
|
this._caps = _caps;
|
|
20
28
|
this._config = _config;
|
|
21
29
|
this._options = { ...DEFAULT_OPTIONS, ...options };
|
|
22
30
|
// added to maintain backward compatibility with webdriverIO v5
|
|
23
31
|
this._config || (this._config = this._options);
|
|
32
|
+
this._observability = this._options.testObservability;
|
|
33
|
+
if (this._observability) {
|
|
34
|
+
this._config.reporters?.push(path.join(__dirname, 'reporter.js'));
|
|
35
|
+
}
|
|
24
36
|
// Cucumber specific
|
|
25
37
|
const strict = Boolean(this._config.cucumberOpts && this._config.cucumberOpts.strict);
|
|
26
38
|
// See https://github.com/cucumber/cucumber-js/blob/master/src/runtime/index.ts#L136
|
|
@@ -48,7 +60,7 @@ export default class BrowserstackService {
|
|
|
48
60
|
this._config.user = config.user;
|
|
49
61
|
this._config.key = config.key;
|
|
50
62
|
}
|
|
51
|
-
before(caps, specs, browser) {
|
|
63
|
+
async before(caps, specs, browser) {
|
|
52
64
|
// added to maintain backward compatibility with webdriverIO v5
|
|
53
65
|
this._browser = browser ? browser : global.browser;
|
|
54
66
|
// Ensure capabilities are not null in case of multiremote
|
|
@@ -56,7 +68,19 @@ export default class BrowserstackService {
|
|
|
56
68
|
this._sessionBaseUrl = 'https://api-cloud.browserstack.com/app-automate/sessions';
|
|
57
69
|
}
|
|
58
70
|
this._scenariosThatRan = [];
|
|
59
|
-
|
|
71
|
+
if (this._observability && this._browser) {
|
|
72
|
+
this._insightsHandler = new InsightsHandler(this._browser, this._browser.capabilities, this._isAppAutomate(), this._browser.sessionId, this._config.framework);
|
|
73
|
+
await this._insightsHandler.before();
|
|
74
|
+
/**
|
|
75
|
+
* register command event
|
|
76
|
+
*/
|
|
77
|
+
this._browser.on('command', async (command) => await this._insightsHandler?.browserCommand('client:beforeCommand', Object.assign(command, { sessionId: this._browser?.sessionId }), this._currentTest));
|
|
78
|
+
/**
|
|
79
|
+
* register result event
|
|
80
|
+
*/
|
|
81
|
+
this._browser.on('result', async (result) => await this._insightsHandler?.browserCommand('client:afterCommand', Object.assign(result, { sessionId: this._browser?.sessionId }), this._currentTest));
|
|
82
|
+
}
|
|
83
|
+
return await this._printSessionURL();
|
|
60
84
|
}
|
|
61
85
|
/**
|
|
62
86
|
* Set the default job name at the suite level to make sure we account
|
|
@@ -71,7 +95,17 @@ export default class BrowserstackService {
|
|
|
71
95
|
await this._setSessionName(suite.title);
|
|
72
96
|
}
|
|
73
97
|
}
|
|
98
|
+
async beforeHook(test, context) {
|
|
99
|
+
if (this._config.framework !== 'cucumber') {
|
|
100
|
+
this._currentTest = test; // not update currentTest when this is called for cucumber step
|
|
101
|
+
}
|
|
102
|
+
await this._insightsHandler?.beforeHook(test, context);
|
|
103
|
+
}
|
|
104
|
+
async afterHook(test, context, result) {
|
|
105
|
+
await this._insightsHandler?.afterHook(test, result);
|
|
106
|
+
}
|
|
74
107
|
async beforeTest(test) {
|
|
108
|
+
this._currentTest = test;
|
|
75
109
|
let suiteTitle = this._suiteTitle;
|
|
76
110
|
if (test.fullName) {
|
|
77
111
|
// For Jasmine, `suite.title` is `Jasmine__TopLevel__Suite`.
|
|
@@ -86,36 +120,15 @@ export default class BrowserstackService {
|
|
|
86
120
|
}
|
|
87
121
|
await this._setSessionName(suiteTitle, test);
|
|
88
122
|
await this._setAnnotation(`Test: ${test.fullName ?? test.title}`);
|
|
123
|
+
await this._insightsHandler?.beforeTest(test);
|
|
89
124
|
}
|
|
90
|
-
|
|
91
|
-
* For CucumberJS
|
|
92
|
-
*/
|
|
93
|
-
async beforeFeature(uri, feature) {
|
|
94
|
-
this._suiteTitle = feature.name;
|
|
95
|
-
await this._setSessionName(feature.name);
|
|
96
|
-
await this._setAnnotation(`Feature: ${feature.name}`);
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Runs before a Cucumber Scenario.
|
|
100
|
-
* @param world world object containing information on pickle and test step
|
|
101
|
-
*/
|
|
102
|
-
async beforeScenario(world) {
|
|
103
|
-
const scenarioName = world.pickle.name || 'unknown scenario';
|
|
104
|
-
await this._setAnnotation(`Scenario: ${scenarioName}`);
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* For CucumberJS
|
|
108
|
-
*/
|
|
109
|
-
async beforeStep(step) {
|
|
110
|
-
const { keyword, text } = step;
|
|
111
|
-
await this._setAnnotation(`Step: ${keyword}${text}`);
|
|
112
|
-
}
|
|
113
|
-
afterTest(test, context, results) {
|
|
125
|
+
async afterTest(test, context, results) {
|
|
114
126
|
this._specsRan = true;
|
|
115
127
|
const { error, passed } = results;
|
|
116
128
|
if (!passed) {
|
|
117
129
|
this._failReasons.push((error && error.message) || 'Unknown Error');
|
|
118
130
|
}
|
|
131
|
+
await this._insightsHandler?.afterTest(test, results);
|
|
119
132
|
}
|
|
120
133
|
async after(result) {
|
|
121
134
|
const { preferScenarioName, setSessionName, setSessionStatus } = this._options;
|
|
@@ -132,11 +145,28 @@ export default class BrowserstackService {
|
|
|
132
145
|
...(hasReasons ? { reason: this._failReasons.join('\n') } : {})
|
|
133
146
|
});
|
|
134
147
|
}
|
|
148
|
+
await this._insightsHandler?.uploadPending();
|
|
149
|
+
await this._insightsHandler?.teardown();
|
|
135
150
|
}
|
|
136
151
|
/**
|
|
137
152
|
* For CucumberJS
|
|
138
153
|
*/
|
|
139
|
-
|
|
154
|
+
async beforeFeature(uri, feature) {
|
|
155
|
+
this._suiteTitle = feature.name;
|
|
156
|
+
await this._setSessionName(feature.name);
|
|
157
|
+
await this._setAnnotation(`Feature: ${feature.name}`);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Runs before a Cucumber Scenario.
|
|
161
|
+
* @param world world object containing information on pickle and test step
|
|
162
|
+
*/
|
|
163
|
+
async beforeScenario(world) {
|
|
164
|
+
this._currentTest = world;
|
|
165
|
+
await this._insightsHandler?.beforeScenario(world);
|
|
166
|
+
const scenarioName = world.pickle.name || 'unknown scenario';
|
|
167
|
+
await this._setAnnotation(`Scenario: ${scenarioName}`);
|
|
168
|
+
}
|
|
169
|
+
async afterScenario(world) {
|
|
140
170
|
this._specsRan = true;
|
|
141
171
|
const status = world.result?.status.toLowerCase();
|
|
142
172
|
if (status !== 'skipped') {
|
|
@@ -149,6 +179,14 @@ export default class BrowserstackService {
|
|
|
149
179
|
: 'Unknown Error'));
|
|
150
180
|
this._failReasons.push(exception);
|
|
151
181
|
}
|
|
182
|
+
await this._insightsHandler?.afterScenario(world);
|
|
183
|
+
}
|
|
184
|
+
async beforeStep(step, scenario) {
|
|
185
|
+
await this._insightsHandler?.beforeStep(step, scenario);
|
|
186
|
+
await this._setAnnotation(`Step: ${step.keyword}${step.text}`);
|
|
187
|
+
}
|
|
188
|
+
async afterStep(step, scenario, result) {
|
|
189
|
+
await this._insightsHandler?.afterStep(step, scenario, result);
|
|
152
190
|
}
|
|
153
191
|
async onReload(oldSessionId, newSessionId) {
|
|
154
192
|
if (!this._browser) {
|
|
@@ -206,6 +244,9 @@ export default class BrowserstackService {
|
|
|
206
244
|
.map((browserName) => (action(_browser[browserName].sessionId, browserName))));
|
|
207
245
|
}
|
|
208
246
|
_update(sessionId, requestBody) {
|
|
247
|
+
if (!isBrowserstackSession(this._browser)) {
|
|
248
|
+
return Promise.resolve();
|
|
249
|
+
}
|
|
209
250
|
const sessionUrl = `${this._sessionBaseUrl}/${sessionId}.json`;
|
|
210
251
|
log.debug(`Updating Browserstack session at ${sessionUrl} with request body: `, requestBody);
|
|
211
252
|
return got.put(sessionUrl, {
|
|
@@ -215,7 +256,7 @@ export default class BrowserstackService {
|
|
|
215
256
|
});
|
|
216
257
|
}
|
|
217
258
|
async _printSessionURL() {
|
|
218
|
-
if (!this._browser) {
|
|
259
|
+
if (!this._browser || !isBrowserstackSession(this._browser)) {
|
|
219
260
|
return Promise.resolve();
|
|
220
261
|
}
|
|
221
262
|
await this._multiRemoteAction(async (sessionId, browserName) => {
|
|
@@ -257,7 +298,7 @@ export default class BrowserstackService {
|
|
|
257
298
|
return this._executeCommand('annotate', { data, level: 'info' });
|
|
258
299
|
}
|
|
259
300
|
async _executeCommand(action, args) {
|
|
260
|
-
if (!this._browser) {
|
|
301
|
+
if (!this._browser || !isBrowserstackSession(this._browser)) {
|
|
261
302
|
return Promise.resolve();
|
|
262
303
|
}
|
|
263
304
|
const cmd = { action, ...(args ? { arguments: args } : {}) };
|