@wdio/browserstack-service 8.0.11 → 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 +48 -12
- 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 +16 -16
- package/build/service.d.ts.map +1 -1
- package/build/service.js +75 -31
- package/build/types.d.ts +121 -1
- 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
|
/**
|
|
@@ -64,7 +83,7 @@ export default class BrowserstackLauncherService {
|
|
|
64
83
|
}
|
|
65
84
|
else {
|
|
66
85
|
let app = {};
|
|
67
|
-
|
|
86
|
+
const appConfig = this._options.app;
|
|
68
87
|
try {
|
|
69
88
|
app = await this._validateApp(appConfig);
|
|
70
89
|
}
|
|
@@ -73,8 +92,7 @@ export default class BrowserstackLauncherService {
|
|
|
73
92
|
}
|
|
74
93
|
if (VALID_APP_EXTENSION.includes(path.extname(app.app))) {
|
|
75
94
|
if (fs.existsSync(app.app)) {
|
|
76
|
-
|
|
77
|
-
data = await this._uploadApp(app);
|
|
95
|
+
const data = await this._uploadApp(app);
|
|
78
96
|
log.info(`app upload completed: ${JSON.stringify(data)}`);
|
|
79
97
|
app.app = data.app_url;
|
|
80
98
|
}
|
|
@@ -88,6 +106,15 @@ export default class BrowserstackLauncherService {
|
|
|
88
106
|
log.info(`Using app: ${app.app}`);
|
|
89
107
|
this._updateCaps(capabilities, 'app', app.app);
|
|
90
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
|
+
}
|
|
91
118
|
if (!this._options.browserstackLocal) {
|
|
92
119
|
return log.info('browserstackLocal is not enabled - skipping...');
|
|
93
120
|
}
|
|
@@ -125,7 +152,14 @@ export default class BrowserstackLauncherService {
|
|
|
125
152
|
return Promise.reject(err);
|
|
126
153
|
});
|
|
127
154
|
}
|
|
128
|
-
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
|
+
}
|
|
129
163
|
if (!this.browserstackLocal || !this.browserstackLocal.isRunning()) {
|
|
130
164
|
return;
|
|
131
165
|
}
|
|
@@ -157,10 +191,12 @@ export default class BrowserstackLauncherService {
|
|
|
157
191
|
async _uploadApp(app) {
|
|
158
192
|
log.info(`uploading app ${app.app} ${app.customId ? `and custom_id: ${app.customId}` : ''} to browserstack`);
|
|
159
193
|
const form = new FormData();
|
|
160
|
-
if (app.app)
|
|
194
|
+
if (app.app) {
|
|
161
195
|
form.append('file', fs.createReadStream(app.app));
|
|
162
|
-
|
|
196
|
+
}
|
|
197
|
+
if (app.customId) {
|
|
163
198
|
form.append('custom_id', app.customId);
|
|
199
|
+
}
|
|
164
200
|
const res = await got.post('https://api-cloud.browserstack.com/app-automate/upload', {
|
|
165
201
|
body: form,
|
|
166
202
|
username: this._config.user,
|
|
@@ -175,7 +211,7 @@ export default class BrowserstackLauncherService {
|
|
|
175
211
|
* <object>: only "path" and "custom_id" should coexist as multiple properties.
|
|
176
212
|
*/
|
|
177
213
|
async _validateApp(appConfig) {
|
|
178
|
-
|
|
214
|
+
const app = {};
|
|
179
215
|
if (typeof appConfig === 'string') {
|
|
180
216
|
app.app = appConfig;
|
|
181
217
|
}
|
|
@@ -213,7 +249,7 @@ export default class BrowserstackLauncherService {
|
|
|
213
249
|
capability['browserstack.local'] = true;
|
|
214
250
|
}
|
|
215
251
|
else if (capType === 'app') {
|
|
216
|
-
capability
|
|
252
|
+
capability.app = value;
|
|
217
253
|
}
|
|
218
254
|
}
|
|
219
255
|
else if (capType === 'local') {
|
|
@@ -240,7 +276,7 @@ export default class BrowserstackLauncherService {
|
|
|
240
276
|
caps.capabilities['browserstack.local'] = true;
|
|
241
277
|
}
|
|
242
278
|
else if (capType === 'app') {
|
|
243
|
-
caps.capabilities
|
|
279
|
+
caps.capabilities.app = value;
|
|
244
280
|
}
|
|
245
281
|
}
|
|
246
282
|
else if (capType === 'local') {
|
|
@@ -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;
|
|
@@ -12,6 +13,10 @@ export default class BrowserstackService implements Services.ServiceInstance {
|
|
|
12
13
|
private _suiteTitle?;
|
|
13
14
|
private _fullTitle?;
|
|
14
15
|
private _options;
|
|
16
|
+
private _specsRan;
|
|
17
|
+
private _observability;
|
|
18
|
+
private _currentTest?;
|
|
19
|
+
private _insightsHandler?;
|
|
15
20
|
constructor(options: BrowserstackConfig & Options.Testrunner, _caps: Capabilities.RemoteCapability, _config: Options.Testrunner);
|
|
16
21
|
_updateCaps(fn: (caps: Capabilities.Capabilities | Capabilities.DesiredCapabilities) => void): void;
|
|
17
22
|
beforeSession(config: Omit<Options.Testrunner, 'capabilities'>): void;
|
|
@@ -24,33 +29,28 @@ export default class BrowserstackService implements Services.ServiceInstance {
|
|
|
24
29
|
* and `suite.fullTitle` is `undefined`, so no alternative to use for the job name.
|
|
25
30
|
*/
|
|
26
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>;
|
|
27
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>;
|
|
28
37
|
/**
|
|
29
38
|
* For CucumberJS
|
|
30
39
|
*/
|
|
31
|
-
beforeFeature(uri:
|
|
32
|
-
name: string;
|
|
33
|
-
}): Promise<void>;
|
|
40
|
+
beforeFeature(uri: string, feature: Feature): Promise<void>;
|
|
34
41
|
/**
|
|
35
42
|
* Runs before a Cucumber Scenario.
|
|
36
43
|
* @param world world object containing information on pickle and test step
|
|
37
44
|
*/
|
|
38
|
-
beforeScenario(world:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
beforeStep(step: Frameworks.PickleStep): Promise<void>;
|
|
43
|
-
afterTest(test: Frameworks.Test, context: never, results: Frameworks.TestResult): void;
|
|
44
|
-
after(result: number): Promise<void>;
|
|
45
|
-
/**
|
|
46
|
-
* For CucumberJS
|
|
47
|
-
*/
|
|
48
|
-
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>;
|
|
49
49
|
onReload(oldSessionId: string, newSessionId: string): Promise<void>;
|
|
50
50
|
_isAppAutomate(): boolean;
|
|
51
51
|
_updateJob(requestBody: any): Promise<any>;
|
|
52
52
|
_multiRemoteAction(action: MultiRemoteAction): Promise<any>;
|
|
53
|
-
_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>>;
|
|
54
54
|
_printSessionURL(): Promise<void>;
|
|
55
55
|
private _setSessionName;
|
|
56
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;
|
|
@@ -14,12 +19,20 @@ export default class BrowserstackService {
|
|
|
14
19
|
_suiteTitle;
|
|
15
20
|
_fullTitle;
|
|
16
21
|
_options;
|
|
22
|
+
_specsRan = false;
|
|
23
|
+
_observability;
|
|
24
|
+
_currentTest;
|
|
25
|
+
_insightsHandler;
|
|
17
26
|
constructor(options, _caps, _config) {
|
|
18
27
|
this._caps = _caps;
|
|
19
28
|
this._config = _config;
|
|
20
29
|
this._options = { ...DEFAULT_OPTIONS, ...options };
|
|
21
30
|
// added to maintain backward compatibility with webdriverIO v5
|
|
22
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
|
+
}
|
|
23
36
|
// Cucumber specific
|
|
24
37
|
const strict = Boolean(this._config.cucumberOpts && this._config.cucumberOpts.strict);
|
|
25
38
|
// See https://github.com/cucumber/cucumber-js/blob/master/src/runtime/index.ts#L136
|
|
@@ -47,7 +60,7 @@ export default class BrowserstackService {
|
|
|
47
60
|
this._config.user = config.user;
|
|
48
61
|
this._config.key = config.key;
|
|
49
62
|
}
|
|
50
|
-
before(caps, specs, browser) {
|
|
63
|
+
async before(caps, specs, browser) {
|
|
51
64
|
// added to maintain backward compatibility with webdriverIO v5
|
|
52
65
|
this._browser = browser ? browser : global.browser;
|
|
53
66
|
// Ensure capabilities are not null in case of multiremote
|
|
@@ -55,7 +68,19 @@ export default class BrowserstackService {
|
|
|
55
68
|
this._sessionBaseUrl = 'https://api-cloud.browserstack.com/app-automate/sessions';
|
|
56
69
|
}
|
|
57
70
|
this._scenariosThatRan = [];
|
|
58
|
-
|
|
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();
|
|
59
84
|
}
|
|
60
85
|
/**
|
|
61
86
|
* Set the default job name at the suite level to make sure we account
|
|
@@ -70,7 +95,17 @@ export default class BrowserstackService {
|
|
|
70
95
|
await this._setSessionName(suite.title);
|
|
71
96
|
}
|
|
72
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
|
+
}
|
|
73
107
|
async beforeTest(test) {
|
|
108
|
+
this._currentTest = test;
|
|
74
109
|
let suiteTitle = this._suiteTitle;
|
|
75
110
|
if (test.fullName) {
|
|
76
111
|
// For Jasmine, `suite.title` is `Jasmine__TopLevel__Suite`.
|
|
@@ -85,35 +120,15 @@ export default class BrowserstackService {
|
|
|
85
120
|
}
|
|
86
121
|
await this._setSessionName(suiteTitle, test);
|
|
87
122
|
await this._setAnnotation(`Test: ${test.fullName ?? test.title}`);
|
|
123
|
+
await this._insightsHandler?.beforeTest(test);
|
|
88
124
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
*/
|
|
92
|
-
async beforeFeature(uri, feature) {
|
|
93
|
-
this._suiteTitle = feature.name;
|
|
94
|
-
await this._setSessionName(feature.name);
|
|
95
|
-
await this._setAnnotation(`Feature: ${feature.name}`);
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Runs before a Cucumber Scenario.
|
|
99
|
-
* @param world world object containing information on pickle and test step
|
|
100
|
-
*/
|
|
101
|
-
async beforeScenario(world) {
|
|
102
|
-
const scenarioName = world.pickle.name || 'unknown scenario';
|
|
103
|
-
await this._setAnnotation(`Scenario: ${scenarioName}`);
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* For CucumberJS
|
|
107
|
-
*/
|
|
108
|
-
async beforeStep(step) {
|
|
109
|
-
const { keyword, text } = step;
|
|
110
|
-
await this._setAnnotation(`Step: ${keyword}${text}`);
|
|
111
|
-
}
|
|
112
|
-
afterTest(test, context, results) {
|
|
125
|
+
async afterTest(test, context, results) {
|
|
126
|
+
this._specsRan = true;
|
|
113
127
|
const { error, passed } = results;
|
|
114
128
|
if (!passed) {
|
|
115
129
|
this._failReasons.push((error && error.message) || 'Unknown Error');
|
|
116
130
|
}
|
|
131
|
+
await this._insightsHandler?.afterTest(test, results);
|
|
117
132
|
}
|
|
118
133
|
async after(result) {
|
|
119
134
|
const { preferScenarioName, setSessionName, setSessionStatus } = this._options;
|
|
@@ -125,16 +140,34 @@ export default class BrowserstackService {
|
|
|
125
140
|
if (setSessionStatus) {
|
|
126
141
|
const hasReasons = this._failReasons.length > 0;
|
|
127
142
|
await this._updateJob({
|
|
128
|
-
status: result === 0 ? 'passed' : 'failed',
|
|
143
|
+
status: result === 0 && this._specsRan ? 'passed' : 'failed',
|
|
129
144
|
...(setSessionName ? { name: this._fullTitle } : {}),
|
|
130
145
|
...(hasReasons ? { reason: this._failReasons.join('\n') } : {})
|
|
131
146
|
});
|
|
132
147
|
}
|
|
148
|
+
await this._insightsHandler?.uploadPending();
|
|
149
|
+
await this._insightsHandler?.teardown();
|
|
133
150
|
}
|
|
134
151
|
/**
|
|
135
152
|
* For CucumberJS
|
|
136
153
|
*/
|
|
137
|
-
|
|
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) {
|
|
170
|
+
this._specsRan = true;
|
|
138
171
|
const status = world.result?.status.toLowerCase();
|
|
139
172
|
if (status !== 'skipped') {
|
|
140
173
|
this._scenariosThatRan.push(world.pickle.name || 'unknown pickle name');
|
|
@@ -146,6 +179,14 @@ export default class BrowserstackService {
|
|
|
146
179
|
: 'Unknown Error'));
|
|
147
180
|
this._failReasons.push(exception);
|
|
148
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);
|
|
149
190
|
}
|
|
150
191
|
async onReload(oldSessionId, newSessionId) {
|
|
151
192
|
if (!this._browser) {
|
|
@@ -203,6 +244,9 @@ export default class BrowserstackService {
|
|
|
203
244
|
.map((browserName) => (action(_browser[browserName].sessionId, browserName))));
|
|
204
245
|
}
|
|
205
246
|
_update(sessionId, requestBody) {
|
|
247
|
+
if (!isBrowserstackSession(this._browser)) {
|
|
248
|
+
return Promise.resolve();
|
|
249
|
+
}
|
|
206
250
|
const sessionUrl = `${this._sessionBaseUrl}/${sessionId}.json`;
|
|
207
251
|
log.debug(`Updating Browserstack session at ${sessionUrl} with request body: `, requestBody);
|
|
208
252
|
return got.put(sessionUrl, {
|
|
@@ -212,7 +256,7 @@ export default class BrowserstackService {
|
|
|
212
256
|
});
|
|
213
257
|
}
|
|
214
258
|
async _printSessionURL() {
|
|
215
|
-
if (!this._browser) {
|
|
259
|
+
if (!this._browser || !isBrowserstackSession(this._browser)) {
|
|
216
260
|
return Promise.resolve();
|
|
217
261
|
}
|
|
218
262
|
await this._multiRemoteAction(async (sessionId, browserName) => {
|
|
@@ -254,7 +298,7 @@ export default class BrowserstackService {
|
|
|
254
298
|
return this._executeCommand('annotate', { data, level: 'info' });
|
|
255
299
|
}
|
|
256
300
|
async _executeCommand(action, args) {
|
|
257
|
-
if (!this._browser) {
|
|
301
|
+
if (!this._browser || !isBrowserstackSession(this._browser)) {
|
|
258
302
|
return Promise.resolve();
|
|
259
303
|
}
|
|
260
304
|
const cmd = { action, ...(args ? { arguments: args } : {}) };
|