@wdio/browserstack-service 8.47.1 → 8.48.3
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/accessibility-handler.d.ts.map +1 -1
- package/build/accessibility-handler.js +13 -11
- package/build/ai-handler.d.ts.map +1 -1
- package/build/ai-handler.js +11 -0
- package/build/cli/cliLogger.d.ts +1 -0
- package/build/cli/cliLogger.d.ts.map +1 -1
- package/build/cli/cliLogger.js +23 -12
- package/build/cli/cliUtils.d.ts +18 -0
- package/build/cli/cliUtils.d.ts.map +1 -1
- package/build/cli/cliUtils.js +31 -1
- package/build/cli/frameworks/wdioMochaTestFramework.d.ts.map +1 -1
- package/build/cli/frameworks/wdioMochaTestFramework.js +4 -2
- package/build/cli/grpcClient.d.ts +19 -3
- package/build/cli/grpcClient.d.ts.map +1 -1
- package/build/cli/grpcClient.js +133 -21
- package/build/cli/index.d.ts +2 -0
- package/build/cli/index.d.ts.map +1 -1
- package/build/cli/index.js +52 -4
- package/build/cli/modules/accessibilityModule.d.ts +14 -1
- package/build/cli/modules/accessibilityModule.d.ts.map +1 -1
- package/build/cli/modules/accessibilityModule.js +113 -14
- package/build/constants.d.ts +28 -0
- package/build/constants.d.ts.map +1 -1
- package/build/constants.js +37 -1
- package/build/exitHandler.d.ts +1 -1
- package/build/exitHandler.d.ts.map +1 -1
- package/build/exitHandler.js +6 -5
- package/build/insights-handler.d.ts.map +1 -1
- package/build/insights-handler.js +33 -7
- package/build/instrumentation/funnelInstrumentation.d.ts +2 -2
- package/build/instrumentation/funnelInstrumentation.d.ts.map +1 -1
- package/build/instrumentation/funnelInstrumentation.js +30 -8
- package/build/instrumentation/performance/constants.d.ts +64 -0
- package/build/instrumentation/performance/constants.d.ts.map +1 -1
- package/build/instrumentation/performance/constants.js +73 -2
- package/build/instrumentation/performance/performance-tester.d.ts +11 -0
- package/build/instrumentation/performance/performance-tester.d.ts.map +1 -1
- package/build/instrumentation/performance/performance-tester.js +115 -12
- package/build/launcher.d.ts +1 -0
- package/build/launcher.d.ts.map +1 -1
- package/build/launcher.js +151 -6
- package/build/service.d.ts.map +1 -1
- package/build/service.js +77 -5
- package/build/testorchestration/apply-orchestration.d.ts +7 -0
- package/build/testorchestration/apply-orchestration.d.ts.map +1 -0
- package/build/testorchestration/apply-orchestration.js +42 -0
- package/build/testorchestration/helpers.d.ts +20 -0
- package/build/testorchestration/helpers.d.ts.map +1 -0
- package/build/testorchestration/helpers.js +412 -0
- package/build/testorchestration/request-utils.d.ts +23 -0
- package/build/testorchestration/request-utils.d.ts.map +1 -0
- package/build/testorchestration/request-utils.js +81 -0
- package/build/testorchestration/test-ordering-server.d.ts +33 -0
- package/build/testorchestration/test-ordering-server.d.ts.map +1 -0
- package/build/testorchestration/test-ordering-server.js +168 -0
- package/build/testorchestration/testorcherstrationhandler.d.ts +50 -0
- package/build/testorchestration/testorcherstrationhandler.d.ts.map +1 -0
- package/build/testorchestration/testorcherstrationhandler.js +133 -0
- package/build/testorchestration/testorcherstrationutils.d.ts +86 -0
- package/build/testorchestration/testorcherstrationutils.d.ts.map +1 -0
- package/build/testorchestration/testorcherstrationutils.js +379 -0
- package/build/types.d.ts +21 -0
- package/build/types.d.ts.map +1 -1
- package/build/util.d.ts +33 -4
- package/build/util.d.ts.map +1 -1
- package/build/util.js +112 -18
- package/package.json +3 -2
- package/tsconfig.prod.tsbuildinfo +1 -1
|
@@ -24,7 +24,44 @@ export const EVENTS = {
|
|
|
24
24
|
SDK_CLI_ON_CONNECT: 'sdk:cli:on-connect',
|
|
25
25
|
SDK_CLI_ON_STOP: 'sdk:cli:on-stop',
|
|
26
26
|
SDK_START_BIN_SESSION: 'sdk:startBinSession',
|
|
27
|
-
SDK_CONNECT_BIN_SESSION: 'sdk:connectBinSession'
|
|
27
|
+
SDK_CONNECT_BIN_SESSION: 'sdk:connectBinSession',
|
|
28
|
+
// New events from Python SDK
|
|
29
|
+
SDK_DRIVER_INIT: 'sdk:driverInit',
|
|
30
|
+
SDK_FIND_NEAREST_HUB: 'sdk:findNearestHub',
|
|
31
|
+
SDK_AUTOMATION_FRAMEWORK_INIT: 'sdk:automationFrameworkInit',
|
|
32
|
+
SDK_AUTOMATION_FRAMEWORK_START: 'sdk:automationFrameworkStart',
|
|
33
|
+
SDK_AUTOMATION_FRAMEWORK_STOP: 'sdk:automationFrameworkStop',
|
|
34
|
+
SDK_ACCESSIBILITY_CONFIG: 'sdk:accessibilityConfig',
|
|
35
|
+
SDK_OBSERVABILITY_CONFIG: 'sdk:observabilityConfig',
|
|
36
|
+
SDK_AI_SELF_HEAL_STEP: 'sdk:aiSelfHealStep',
|
|
37
|
+
SDK_AI_SELF_HEAL_GET_RESULT: 'sdk:aiSelfHealGetResult',
|
|
38
|
+
SDK_TEST_FRAMEWORK_EVENT: 'sdk:testFrameworkEvent',
|
|
39
|
+
SDK_TEST_SESSION_EVENT: 'sdk:testSessionEvent',
|
|
40
|
+
SDK_CLI_LOG_CREATED_EVENT: 'sdk:cli:logCreatedEvent',
|
|
41
|
+
SDK_CLI_ENQUEUE_TEST_EVENT: 'sdk:cli:enqueueTestEvent',
|
|
42
|
+
SDK_ON_STOP: 'sdk:onStop',
|
|
43
|
+
SDK_SEND_LOGS: 'sdk:sendlogs',
|
|
44
|
+
// Funnel Events
|
|
45
|
+
SDK_FUNNEL_TEST_ATTEMPTED: 'sdk:funnel:test-attempted',
|
|
46
|
+
SDK_FUNNEL_TEST_SUCCESSFUL: 'sdk:funnel:test-successful',
|
|
47
|
+
// Log Upload Events
|
|
48
|
+
SDK_UPLOAD_LOGS: 'sdk:upload-logs',
|
|
49
|
+
// Key Metrics Events
|
|
50
|
+
SDK_SEND_KEY_METRICS: 'sdk:send-key-metrics',
|
|
51
|
+
SDK_KEY_METRICS_PREPARATION: 'sdk:key-metrics:preparation',
|
|
52
|
+
SDK_KEY_METRICS_UPLOAD: 'sdk:key-metrics:upload',
|
|
53
|
+
// CLI Binary Events
|
|
54
|
+
SDK_CLI_DOWNLOAD_BINARY: 'sdk:cli:download-binary',
|
|
55
|
+
SDK_CLI_BINARY_VERIFICATION: 'sdk:cli:binary-verification',
|
|
56
|
+
// Cleanup & Shutdown Events (tracking gap between driver:quit and funnel:test-successful)
|
|
57
|
+
SDK_LISTENER_WORKER_END: 'sdk:listener:worker-end',
|
|
58
|
+
SDK_PERCY_TEARDOWN: 'sdk:percy:teardown',
|
|
59
|
+
SDK_WORKER_SAVE_DATA: 'sdk:worker:save-data',
|
|
60
|
+
SDK_PERFORMANCE_REPORT_GEN: 'sdk:performance:report-generation',
|
|
61
|
+
SDK_PERFORMANCE_JSON_WRITE: 'sdk:performance:json-write',
|
|
62
|
+
SDK_PERFORMANCE_HTML_GEN: 'sdk:performance:html-generation',
|
|
63
|
+
// Device Allocation Event (tracking gap between beforeSession and before hooks)
|
|
64
|
+
SDK_DEVICE_ALLOCATION: 'sdk:device-allocation'
|
|
28
65
|
};
|
|
29
66
|
export const TESTHUB_EVENTS = {
|
|
30
67
|
START: `${EVENTS.SDK_TESTHUB}:start`,
|
|
@@ -82,5 +119,39 @@ export const DRIVER_EVENT = {
|
|
|
82
119
|
QUIT: `${EVENTS.SDK_DRIVER}:quit`,
|
|
83
120
|
GET: `${EVENTS.SDK_DRIVER}:get`,
|
|
84
121
|
PRE_EXECUTE: `${EVENTS.SDK_DRIVER}:pre-execute`,
|
|
85
|
-
POST_EXECUTE: `${EVENTS.SDK_DRIVER}:post-execute
|
|
122
|
+
POST_EXECUTE: `${EVENTS.SDK_DRIVER}:post-execute`,
|
|
123
|
+
INIT: EVENTS.SDK_DRIVER_INIT,
|
|
124
|
+
PRE_INITIALIZE: EVENTS.SDK_PRE_INITIALIZE,
|
|
125
|
+
POST_INITIALIZE: EVENTS.SDK_POST_INITIALIZE
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* Framework lifecycle events for automation framework tracking
|
|
129
|
+
*/
|
|
130
|
+
export const FRAMEWORK_EVENTS = {
|
|
131
|
+
INIT: EVENTS.SDK_AUTOMATION_FRAMEWORK_INIT,
|
|
132
|
+
START: EVENTS.SDK_AUTOMATION_FRAMEWORK_START,
|
|
133
|
+
STOP: EVENTS.SDK_AUTOMATION_FRAMEWORK_STOP
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* Module configuration events
|
|
137
|
+
*/
|
|
138
|
+
export const CONFIG_EVENTS = {
|
|
139
|
+
ACCESSIBILITY: EVENTS.SDK_ACCESSIBILITY_CONFIG,
|
|
140
|
+
OBSERVABILITY: EVENTS.SDK_OBSERVABILITY_CONFIG
|
|
141
|
+
};
|
|
142
|
+
/**
|
|
143
|
+
* AI self-healing events
|
|
144
|
+
*/
|
|
145
|
+
export const AI_EVENTS = {
|
|
146
|
+
SELF_HEAL_STEP: EVENTS.SDK_AI_SELF_HEAL_STEP,
|
|
147
|
+
SELF_HEAL_GET_RESULT: EVENTS.SDK_AI_SELF_HEAL_GET_RESULT
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Event dispatcher events for test framework and session tracking
|
|
151
|
+
*/
|
|
152
|
+
export const DISPATCHER_EVENTS = {
|
|
153
|
+
TEST_FRAMEWORK: EVENTS.SDK_TEST_FRAMEWORK_EVENT,
|
|
154
|
+
TEST_SESSION: EVENTS.SDK_TEST_SESSION_EVENT,
|
|
155
|
+
LOG_CREATED: EVENTS.SDK_CLI_LOG_CREATED_EVENT,
|
|
156
|
+
ENQUEUE_TEST: EVENTS.SDK_CLI_ENQUEUE_TEST_EVENT
|
|
86
157
|
};
|
|
@@ -4,6 +4,7 @@ type PerformanceDetails = {
|
|
|
4
4
|
failure?: string;
|
|
5
5
|
testName?: string;
|
|
6
6
|
worker?: string | number;
|
|
7
|
+
clientWorkerId?: string;
|
|
7
8
|
command?: string;
|
|
8
9
|
hookType?: string;
|
|
9
10
|
platform?: string | number;
|
|
@@ -13,6 +14,8 @@ export default class PerformanceTester {
|
|
|
13
14
|
static _csvWriter: any;
|
|
14
15
|
private static _events;
|
|
15
16
|
private static _measuredEvents;
|
|
17
|
+
private static _hasStoppedGeneration;
|
|
18
|
+
private static _stopGenerateCallCount;
|
|
16
19
|
static started: boolean;
|
|
17
20
|
static details: {
|
|
18
21
|
[key: string]: PerformanceDetails;
|
|
@@ -36,6 +39,14 @@ export default class PerformanceTester {
|
|
|
36
39
|
static measure(label: string, fn: Function, details?: {}, args?: IArguments, thisArg?: any): any;
|
|
37
40
|
static start(event: string): void;
|
|
38
41
|
static end(event: string, success?: boolean, failure?: string | unknown, details?: {}): void;
|
|
42
|
+
/**
|
|
43
|
+
* Get client worker ID in format "threadId-processId".
|
|
44
|
+
* This method provides a consistent identifier across the SDK for tracking
|
|
45
|
+
* worker-specific events and performance metrics.
|
|
46
|
+
*
|
|
47
|
+
* @returns Worker ID string in format "threadId-processId"
|
|
48
|
+
*/
|
|
49
|
+
static getClientWorkerId(): string;
|
|
39
50
|
static getProcessId(): string;
|
|
40
51
|
static sleep: (ms?: number) => Promise<unknown>;
|
|
41
52
|
static uploadEventsData(): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"performance-tester.d.ts","sourceRoot":"","sources":["../../../src/instrumentation/performance/performance-tester.ts"],"names":[],"mappings":"AAIA,OAAO,EAAe,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"performance-tester.d.ts","sourceRoot":"","sources":["../../../src/instrumentation/performance/performance-tester.ts"],"names":[],"mappings":"AAIA,OAAO,EAAe,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AAclE,KAAK,kBAAkB,GAAG;IACtB,OAAO,CAAC,EAAE,IAAI,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAC7B,CAAA;AAED,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,OAAO,CAAC,MAAM,CAAC,eAAe,CAAyB;IACvD,OAAO,CAAC,MAAM,CAAC,qBAAqB,CAAQ;IAC5C,OAAO,CAAC,MAAM,CAAC,sBAAsB,CAAI;IACzC,MAAM,CAAC,OAAO,UAAQ;IACtB,MAAM,CAAC,OAAO,EAAE;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,CAAA;KAAC,CAAK;IACxD,MAAM,CAAC,SAAS,EAAE;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAC,CAAK;IAC9C,MAAM,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC,OAAO,CAAA;IACpC,MAAM,CAAC,eAAe,EAAE,MAAM,EAAE,CAAA;IAChC,MAAM,CAAC,iBAAiB,SAAuB;IAC/C,MAAM,CAAC,iBAAiB,SAA2D;IACnF,MAAM,CAAC,kBAAkB,SAA0F;IAEnH,MAAM,CAAC,eAAe,CAAC,OAAO,GAAE,MAAiC;IAkDjE,MAAM,CAAC,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,GAAI,MAAM;WAepC,eAAe,CAAC,QAAQ,GAAE,MAA+B;IAkCtE,MAAM,CAAC,cAAc,CAAC,OAAO,EAAE,gBAAgB,EAAE;IAWjD,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,gBAAgB,EAAE;IAwB9C,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,YAG9C,MAAM,OACT,MAAM,GAAG,MAAM,cACR,uBAAuB,CAAC,GAAG,CAAC;IAUhD,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,GAAE,kBAAuB,aAOpD,GAAG,EAAE;IAQnC,MAAM,CAAC,SAAS;IAIhB,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,KAAK,EAAE,IAAI,CAAC,EAAE,UAAU,EAAE,OAAO,GAAE,GAAU;IA6FhG,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM;IAO1B,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,UAAO,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,EAAE,OAAO,KAAK;IAWlF;;;;;;OAMG;IACH,MAAM,CAAC,iBAAiB,IAAI,MAAM;IAIlC,MAAM,CAAC,YAAY;IAInB,MAAM,CAAC,KAAK,oCAAkE;WAEjE,gBAAgB;CA4GhC"}
|
|
@@ -8,13 +8,17 @@ import path from 'node:path';
|
|
|
8
8
|
import { arch, hostname, platform, type, version } from 'node:os';
|
|
9
9
|
import got from 'got';
|
|
10
10
|
import { BStackLogger } from '../../bstackLogger.js';
|
|
11
|
-
import { PERF_MEASUREMENT_ENV
|
|
11
|
+
import { PERF_MEASUREMENT_ENV } from '../../constants.js';
|
|
12
12
|
import APIUtils from '../../cli/apiUtils.js';
|
|
13
|
+
import { CLIUtils } from '../../cli/cliUtils.js';
|
|
14
|
+
import { EVENTS } from './constants.js';
|
|
13
15
|
export default class PerformanceTester {
|
|
14
16
|
static _observer;
|
|
15
17
|
static _csvWriter;
|
|
16
18
|
static _events = [];
|
|
17
19
|
static _measuredEvents = [];
|
|
20
|
+
static _hasStoppedGeneration = false;
|
|
21
|
+
static _stopGenerateCallCount = 0;
|
|
18
22
|
static started = false;
|
|
19
23
|
static details = {};
|
|
20
24
|
static eventsMap = {};
|
|
@@ -34,6 +38,14 @@ export default class PerformanceTester {
|
|
|
34
38
|
.forEach(entry => {
|
|
35
39
|
let finalEntry = entry;
|
|
36
40
|
finalEntry = entry.toJSON();
|
|
41
|
+
try {
|
|
42
|
+
if (typeof finalEntry.startTime === 'number' && typeof performance.timeOrigin === 'number') {
|
|
43
|
+
finalEntry.startTime = performance.timeOrigin + finalEntry.startTime;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
BStackLogger.debug(`Error converting startTime to epoch: ${util.format(e)}`);
|
|
48
|
+
}
|
|
37
49
|
if (this.details[entry.name]) {
|
|
38
50
|
finalEntry = Object.assign(finalEntry, this.details[entry.name]);
|
|
39
51
|
}
|
|
@@ -78,7 +90,6 @@ export default class PerformanceTester {
|
|
|
78
90
|
if (!this.started) {
|
|
79
91
|
return;
|
|
80
92
|
}
|
|
81
|
-
await PerformanceTester.sleep(PERF_METRICS_WAIT_TIME); // Wait to ensure all pending measurements are processed by the observer
|
|
82
93
|
try {
|
|
83
94
|
const eventsJson = JSON.stringify(this._measuredEvents);
|
|
84
95
|
// remove enclosing array and add a trailing comma so that we
|
|
@@ -93,8 +104,8 @@ export default class PerformanceTester {
|
|
|
93
104
|
if (!process.env[PERF_MEASUREMENT_ENV]) {
|
|
94
105
|
return;
|
|
95
106
|
}
|
|
96
|
-
await PerformanceTester.sleep(PERF_METRICS_WAIT_TIME); // Wait to 2s just to finish any running callbacks for timerify
|
|
97
107
|
this.started = false;
|
|
108
|
+
// Generate CSV and HTML reports using directly collected events
|
|
98
109
|
this.generateCSV(this._events);
|
|
99
110
|
const content = this.generateReport(this._events);
|
|
100
111
|
const dir = path.join(process.cwd(), filename);
|
|
@@ -166,27 +177,78 @@ export default class PerformanceTester {
|
|
|
166
177
|
if (!this.started || !this.isEnabled()) {
|
|
167
178
|
return fn.apply(thisArg, args);
|
|
168
179
|
}
|
|
169
|
-
|
|
170
|
-
|
|
180
|
+
// Generate unique mark names for this specific call to avoid timing conflicts
|
|
181
|
+
const uniqueId = `${Date.now()}-${Math.random().toString(36).substring(7)}`;
|
|
182
|
+
const startMark = `${label}-start-${uniqueId}`;
|
|
183
|
+
const endMark = `${label}-end-${uniqueId}`;
|
|
184
|
+
// Create the start mark with unique ID
|
|
185
|
+
performance.mark(startMark);
|
|
186
|
+
// Store details with measurement context
|
|
187
|
+
const detailsWithContext = {
|
|
188
|
+
...details,
|
|
189
|
+
measurementId: uniqueId
|
|
190
|
+
};
|
|
171
191
|
try {
|
|
172
192
|
const returnVal = fn.apply(thisArg, args);
|
|
173
193
|
if (returnVal instanceof Promise) {
|
|
174
194
|
return new Promise((resolve, reject) => {
|
|
175
195
|
returnVal
|
|
176
196
|
.then(v => {
|
|
177
|
-
|
|
197
|
+
// Use specific marks for this call
|
|
198
|
+
performance.mark(endMark);
|
|
199
|
+
performance.measure(label, startMark, endMark);
|
|
200
|
+
this.details[label] = Object.assign({
|
|
201
|
+
success: true,
|
|
202
|
+
failure: undefined
|
|
203
|
+
}, Object.assign(Object.assign({
|
|
204
|
+
clientWorkerId: PerformanceTester.getClientWorkerId(),
|
|
205
|
+
worker: PerformanceTester.getProcessId(),
|
|
206
|
+
platform: PerformanceTester.browser?.sessionId,
|
|
207
|
+
testName: PerformanceTester.scenarioThatRan?.pop()
|
|
208
|
+
}, detailsWithContext), this.details[label] || {}));
|
|
178
209
|
resolve(v);
|
|
179
210
|
}).catch(e => {
|
|
180
|
-
|
|
211
|
+
performance.mark(endMark);
|
|
212
|
+
performance.measure(label, startMark, endMark);
|
|
213
|
+
this.details[label] = Object.assign({
|
|
214
|
+
success: false,
|
|
215
|
+
failure: util.format(e)
|
|
216
|
+
}, Object.assign(Object.assign({
|
|
217
|
+
clientWorkerId: PerformanceTester.getClientWorkerId(),
|
|
218
|
+
worker: PerformanceTester.getProcessId(),
|
|
219
|
+
platform: PerformanceTester.browser?.sessionId,
|
|
220
|
+
testName: PerformanceTester.scenarioThatRan?.pop()
|
|
221
|
+
}, detailsWithContext), this.details[label] || {}));
|
|
181
222
|
reject(e);
|
|
182
223
|
});
|
|
183
224
|
});
|
|
184
225
|
}
|
|
185
|
-
|
|
226
|
+
// Synchronous execution
|
|
227
|
+
performance.mark(endMark);
|
|
228
|
+
performance.measure(label, startMark, endMark);
|
|
229
|
+
this.details[label] = Object.assign({
|
|
230
|
+
success: true,
|
|
231
|
+
failure: undefined
|
|
232
|
+
}, Object.assign(Object.assign({
|
|
233
|
+
clientWorkerId: PerformanceTester.getClientWorkerId(),
|
|
234
|
+
worker: PerformanceTester.getProcessId(),
|
|
235
|
+
platform: PerformanceTester.browser?.sessionId,
|
|
236
|
+
testName: PerformanceTester.scenarioThatRan?.pop()
|
|
237
|
+
}, detailsWithContext), this.details[label] || {}));
|
|
186
238
|
return returnVal;
|
|
187
239
|
}
|
|
188
240
|
catch (er) {
|
|
189
|
-
|
|
241
|
+
performance.mark(endMark);
|
|
242
|
+
performance.measure(label, startMark, endMark);
|
|
243
|
+
this.details[label] = Object.assign({
|
|
244
|
+
success: false,
|
|
245
|
+
failure: util.format(er)
|
|
246
|
+
}, Object.assign(Object.assign({
|
|
247
|
+
clientWorkerId: PerformanceTester.getClientWorkerId(),
|
|
248
|
+
worker: PerformanceTester.getProcessId(),
|
|
249
|
+
platform: PerformanceTester.browser?.sessionId,
|
|
250
|
+
testName: PerformanceTester.scenarioThatRan?.pop()
|
|
251
|
+
}, detailsWithContext), this.details[label] || {}));
|
|
190
252
|
throw er;
|
|
191
253
|
}
|
|
192
254
|
}
|
|
@@ -202,25 +264,59 @@ export default class PerformanceTester {
|
|
|
202
264
|
performance.mark(event + '-end');
|
|
203
265
|
performance.measure(event, event + '-start', event + '-end');
|
|
204
266
|
this.details[event] = Object.assign({ success, failure: util.format(failure) }, Object.assign(Object.assign({
|
|
267
|
+
clientWorkerId: PerformanceTester.getClientWorkerId(),
|
|
205
268
|
worker: PerformanceTester.getProcessId(),
|
|
206
269
|
platform: PerformanceTester.browser?.sessionId,
|
|
207
270
|
testName: PerformanceTester.scenarioThatRan?.pop()
|
|
208
271
|
}, details), this.details[event] || {}));
|
|
209
272
|
}
|
|
273
|
+
/**
|
|
274
|
+
* Get client worker ID in format "threadId-processId".
|
|
275
|
+
* This method provides a consistent identifier across the SDK for tracking
|
|
276
|
+
* worker-specific events and performance metrics.
|
|
277
|
+
*
|
|
278
|
+
* @returns Worker ID string in format "threadId-processId"
|
|
279
|
+
*/
|
|
280
|
+
static getClientWorkerId() {
|
|
281
|
+
return CLIUtils.getClientWorkerId();
|
|
282
|
+
}
|
|
210
283
|
static getProcessId() {
|
|
211
284
|
return `${process.pid}-${worker.threadId}`;
|
|
212
285
|
}
|
|
213
286
|
static sleep = (ms = 100) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
214
287
|
static async uploadEventsData() {
|
|
288
|
+
// Track overall key metrics operation
|
|
289
|
+
this.start(EVENTS.SDK_SEND_KEY_METRICS);
|
|
215
290
|
try {
|
|
291
|
+
const workerId = `${process.pid}`;
|
|
292
|
+
BStackLogger.debug(`[Performance Upload] Starting upload for worker ${workerId}`);
|
|
293
|
+
// Track preparation phase (collection and deduplication)
|
|
294
|
+
this.start(EVENTS.SDK_KEY_METRICS_PREPARATION);
|
|
295
|
+
// Collect all measures from performance report files and in-memory events
|
|
216
296
|
let measures = [];
|
|
217
297
|
if (await fsPromise.access(this.jsonReportDirPath).then(() => true).catch(() => false)) {
|
|
218
298
|
const files = (await fsPromise.readdir(this.jsonReportDirPath)).map(file => path.resolve(this.jsonReportDirPath, file));
|
|
219
299
|
measures = (await Promise.all(files.map((file) => fsPromise.readFile(file, 'utf-8')))).map(el => `[${el.slice(0, -1)}]`).map(el => JSON.parse(el)).flat();
|
|
220
300
|
}
|
|
301
|
+
BStackLogger.debug(`[Performance Upload] Total events from files: ${measures.length}`);
|
|
221
302
|
if (this._measuredEvents.length > 0) {
|
|
303
|
+
BStackLogger.debug(`[Performance Upload] Adding ${this._measuredEvents.length} in-memory events`);
|
|
222
304
|
measures = measures.concat(this._measuredEvents);
|
|
223
305
|
}
|
|
306
|
+
const ensureEpochTimes = (arr) => {
|
|
307
|
+
const now = Date.now();
|
|
308
|
+
const cutoff = 1e12; // ~year 2001, ms epoch
|
|
309
|
+
const timeOrigin = (typeof performance !== 'undefined' && typeof performance.timeOrigin === 'number')
|
|
310
|
+
? performance.timeOrigin
|
|
311
|
+
: (now - process.uptime() * 1000);
|
|
312
|
+
return arr.map(entry => {
|
|
313
|
+
if (typeof entry.startTime === 'number' && entry.startTime < cutoff) {
|
|
314
|
+
entry.startTime = timeOrigin + entry.startTime;
|
|
315
|
+
}
|
|
316
|
+
return entry;
|
|
317
|
+
});
|
|
318
|
+
};
|
|
319
|
+
measures = ensureEpochTimes(measures);
|
|
224
320
|
const date = new Date();
|
|
225
321
|
// yyyy-MM-dd'T'HH:mm:ss.SSSSSS Z
|
|
226
322
|
const options = {
|
|
@@ -240,6 +336,7 @@ export default class PerformanceTester {
|
|
|
240
336
|
.map(({ type, value }) => type === 'timeZoneName' ? 'Z' : value)
|
|
241
337
|
.join('')
|
|
242
338
|
.replace(',', 'T');
|
|
339
|
+
this.end(EVENTS.SDK_KEY_METRICS_PREPARATION, true);
|
|
243
340
|
const payload = {
|
|
244
341
|
event_type: 'sdk_events',
|
|
245
342
|
data: {
|
|
@@ -262,10 +359,15 @@ export default class PerformanceTester {
|
|
|
262
359
|
'content-type': 'application/json'
|
|
263
360
|
}, json: payload
|
|
264
361
|
});
|
|
265
|
-
BStackLogger.debug(`Successfully uploaded
|
|
362
|
+
BStackLogger.debug(`[Performance Upload] Successfully uploaded to EDS: ${util.format(result.body)}`);
|
|
363
|
+
this.end(EVENTS.SDK_KEY_METRICS_UPLOAD, true);
|
|
364
|
+
this.end(EVENTS.SDK_SEND_KEY_METRICS, true);
|
|
266
365
|
}
|
|
267
366
|
catch (er) {
|
|
268
|
-
BStackLogger.debug(`Failed to upload
|
|
367
|
+
BStackLogger.debug(`[Performance Upload] Failed to upload events: ${util.format(er)}`);
|
|
368
|
+
this.end(EVENTS.SDK_KEY_METRICS_UPLOAD, false, er);
|
|
369
|
+
this.end(EVENTS.SDK_KEY_METRICS_PREPARATION, false, er);
|
|
370
|
+
this.end(EVENTS.SDK_SEND_KEY_METRICS, false, er);
|
|
269
371
|
}
|
|
270
372
|
try {
|
|
271
373
|
if (await fsPromise.access(this.jsonReportDirPath).then(() => true, () => false)) {
|
|
@@ -273,10 +375,11 @@ export default class PerformanceTester {
|
|
|
273
375
|
for (const file of files) {
|
|
274
376
|
await fsPromise.unlink(path.join(this.jsonReportDirPath, file));
|
|
275
377
|
}
|
|
378
|
+
BStackLogger.debug(`[Performance Upload] Cleaned up ${files.length} temporary report files`);
|
|
276
379
|
}
|
|
277
380
|
}
|
|
278
381
|
catch (er) {
|
|
279
|
-
BStackLogger.debug(`Failed to delete
|
|
382
|
+
BStackLogger.debug(`[Performance Upload] Failed to delete temporary files: ${util.format(er)}`);
|
|
280
383
|
}
|
|
281
384
|
}
|
|
282
385
|
}
|
package/build/launcher.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export default class BrowserstackLauncherService implements Services.ServiceInst
|
|
|
30
30
|
*/
|
|
31
31
|
_validateApp(appConfig: AppConfig | string): Promise<App>;
|
|
32
32
|
_uploadServiceLogs(): Promise<void>;
|
|
33
|
+
private _removeCliOnlyCapabilityOptions;
|
|
33
34
|
_updateObjectTypeCaps(capabilities?: Capabilities.RemoteCapabilities, capType?: string, value?: {
|
|
34
35
|
[key: string]: any;
|
|
35
36
|
}): void;
|
package/build/launcher.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"launcher.d.ts","sourceRoot":"","sources":["../src/launcher.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,yBAAyB,MAAM,oBAAoB,CAAA;AAE/D,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAIlE,OAAO,KAAK,EAAE,kBAAkB,EAAE,GAAG,EAAE,SAAS,EAAE,iBAAiB,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"launcher.d.ts","sourceRoot":"","sources":["../src/launcher.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,yBAAyB,MAAM,oBAAoB,CAAA;AAE/D,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAIlE,OAAO,KAAK,EAAE,kBAAkB,EAAE,GAAG,EAAE,SAAS,EAAE,iBAAiB,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAmDxH,KAAK,iBAAiB,GAAG,yBAAyB,CAAC,KAAK,GAAG;IACvD,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI,CAAA;CAC9C,CAAA;AAED,MAAM,CAAC,OAAO,OAAO,2BAA4B,YAAW,QAAQ,CAAC,eAAe;IAY5E,OAAO,CAAC,QAAQ;IAEhB,OAAO,CAAC,OAAO;IAbnB,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;IACjC,OAAO,CAAC,wBAAwB,CAAC,CAAuB;IACxD,OAAO,CAAC,MAAM,CAAC,CAAO;IACtB,OAAO,CAAC,sBAAsB,CAAC,CAAkC;IACjE,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAoB;gBAG3C,QAAQ,EAAE,kBAAkB,GAAG,mBAAmB,EAC1D,YAAY,EAAE,YAAY,CAAC,gBAAgB,EACnC,OAAO,EAAE,OAAO,CAAC,UAAU;IAqHjC,aAAa,CAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG;IAclC,SAAS,CAAE,MAAM,EAAE,OAAO,CAAC,UAAU,EAAE,YAAY,EAAE,YAAY,CAAC,kBAAkB;IA8UpF,UAAU;IAwGV,UAAU,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,UAAU,EAAE,QAAQ,EAAE,UAAU;IA0B7G,SAAS;IAaT,UAAU,CAAC,GAAG,EAAC,GAAG,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAuBrD;;;OAGG;IACG,YAAY,CAAE,SAAS,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAyB1D,kBAAkB;IASxB,OAAO,CAAC,+BAA+B;IAmCvC,qBAAqB,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC,kBAAkB,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE;IA8GtH,WAAW,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC,kBAAkB,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM;IA+H5F,8BAA8B;IAgB9B,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;IASnF,mBAAmB;CAStB"}
|
package/build/launcher.js
CHANGED
|
@@ -16,7 +16,7 @@ import { SevereServiceError } from 'webdriverio';
|
|
|
16
16
|
import * as BrowserstackLocalLauncher from 'browserstack-local';
|
|
17
17
|
import { startPercy, stopPercy, getBestPlatformForPercySnapshot } from './Percy/PercyHelper.js';
|
|
18
18
|
import { BSTACK_SERVICE_VERSION, NOT_ALLOWED_KEYS_IN_CAPS, PERF_MEASUREMENT_ENV, RERUN_ENV, RERUN_TESTS_ENV, BROWSERSTACK_TESTHUB_UUID, VALID_APP_EXTENSION, BROWSERSTACK_PERCY, BROWSERSTACK_OBSERVABILITY, WDIO_NAMING_PREFIX } from './constants.js';
|
|
19
|
-
import { launchTestSession, shouldAddServiceVersion, stopBuildUpstream, getCiInfo, isBStackSession, isUndefined, isAccessibilityAutomationSession, isTrue, getBrowserStackUser, getBrowserStackKey, uploadLogs, ObjectsAreEqual, isValidCapsForHealing, getBooleanValueFromString, validateCapsWithNonBstackA11y, mergeChromeOptions, normalizeTestReportingConfig, normalizeTestReportingEnvVariables } from './util.js';
|
|
19
|
+
import { launchTestSession, shouldAddServiceVersion, stopBuildUpstream, getCiInfo, isBStackSession, isUndefined, isAccessibilityAutomationSession, isTrue, getBrowserStackUser, getBrowserStackKey, uploadLogs, ObjectsAreEqual, isValidCapsForHealing, getBooleanValueFromString, validateCapsWithNonBstackA11y, mergeChromeOptions, normalizeTestReportingConfig, normalizeTestReportingEnvVariables, isValidEnabledValue, isMultiRemoteCaps } from './util.js';
|
|
20
20
|
import { getProductMap } from './testHub/utils.js';
|
|
21
21
|
import CrashReporter from './crash-reporter.js';
|
|
22
22
|
import { BStackLogger } from './bstackLogger.js';
|
|
@@ -175,19 +175,76 @@ export default class BrowserstackLauncherService {
|
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
async onPrepare(config, capabilities) {
|
|
178
|
+
PerformanceTester.start(PERFORMANCE_SDK_EVENTS.FRAMEWORK_EVENTS.INIT);
|
|
178
179
|
// // Send Funnel start request
|
|
179
180
|
await sendStart(this.browserStackConfig);
|
|
181
|
+
// Convert glob patterns in specs to resolved relative paths
|
|
182
|
+
if (config.specs && Array.isArray(config.specs) && isValidEnabledValue(this._options.testOrchestrationOptions?.runSmartSelection?.enabled)) {
|
|
183
|
+
try {
|
|
184
|
+
// Import glob for expanding file patterns
|
|
185
|
+
const glob = (await import('glob')).sync;
|
|
186
|
+
const path = await import('node:path');
|
|
187
|
+
// Use ConfigParser.getFilePaths equivalent logic to expand specs
|
|
188
|
+
const expandedSpecs = [];
|
|
189
|
+
for (const specPattern of config.specs) {
|
|
190
|
+
if (typeof specPattern === 'string') {
|
|
191
|
+
if (specPattern.startsWith('file://')) {
|
|
192
|
+
expandedSpecs.push(specPattern);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
// Expand glob pattern to relative paths
|
|
196
|
+
const pattern = specPattern.replace(/\\/g, '/');
|
|
197
|
+
// Use config.rootDir which is set to the config file's directory
|
|
198
|
+
const rootDir = config.rootDir || process.cwd();
|
|
199
|
+
// Get current working directory for final relative path calculation
|
|
200
|
+
const cwd = process.cwd();
|
|
201
|
+
const filenames = glob(pattern, {
|
|
202
|
+
cwd: rootDir,
|
|
203
|
+
matchBase: true
|
|
204
|
+
}) || [];
|
|
205
|
+
// Convert paths to be relative to the current working directory (where command is run)
|
|
206
|
+
filenames
|
|
207
|
+
.forEach((filename) => {
|
|
208
|
+
let absolutePath = filename;
|
|
209
|
+
// If filename is not absolute, resolve it relative to rootDir (config file's directory)
|
|
210
|
+
if (!path.isAbsolute(filename)) {
|
|
211
|
+
absolutePath = path.resolve(rootDir, filename);
|
|
212
|
+
}
|
|
213
|
+
// Make path relative to current working directory
|
|
214
|
+
let relativePath = path.relative(cwd, absolutePath);
|
|
215
|
+
// Normalize path separators for consistency (Windows compatibility)
|
|
216
|
+
relativePath = relativePath.replace(/\\/g, '/');
|
|
217
|
+
expandedSpecs.push(relativePath);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (expandedSpecs.length > 0) {
|
|
222
|
+
BStackLogger.info(`Expanded specs from glob patterns to ${expandedSpecs.length} files`);
|
|
223
|
+
config.specs = expandedSpecs;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
BStackLogger.error(`Failed to expand spec patterns: ${error}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
180
230
|
try {
|
|
181
|
-
if
|
|
231
|
+
// Detect if multi-remote and disable CLI for those sessions
|
|
232
|
+
const isMultiremote = isMultiRemoteCaps(capabilities);
|
|
233
|
+
process.env.BROWSERSTACK_IS_MULTIREMOTE = String(isMultiremote);
|
|
234
|
+
if (CLIUtils.checkCLISupportedFrameworks(config.framework) && !isMultiremote) {
|
|
235
|
+
PerformanceTester.start(PERFORMANCE_SDK_EVENTS.FRAMEWORK_EVENTS.START);
|
|
182
236
|
CLIUtils.setFrameworkDetail(WDIO_NAMING_PREFIX + config.framework, 'WebdriverIO'); // TODO: make this constant
|
|
183
237
|
const binconfig = CLIUtils.getBinConfig(config, capabilities, this._options, this._buildTag);
|
|
184
238
|
await BrowserstackCLI.getInstance().bootstrap(this._options, config, binconfig);
|
|
185
239
|
BStackLogger.debug(`Is CLI running ${BrowserstackCLI.getInstance().isRunning()}`);
|
|
240
|
+
PerformanceTester.end(PERFORMANCE_SDK_EVENTS.FRAMEWORK_EVENTS.START);
|
|
186
241
|
}
|
|
187
242
|
}
|
|
188
243
|
catch (err) {
|
|
189
244
|
BStackLogger.error(`Error while starting CLI ${err}`);
|
|
245
|
+
PerformanceTester.end(PERFORMANCE_SDK_EVENTS.FRAMEWORK_EVENTS.START, false, util.format(err));
|
|
190
246
|
}
|
|
247
|
+
PerformanceTester.end(PERFORMANCE_SDK_EVENTS.FRAMEWORK_EVENTS.INIT);
|
|
191
248
|
// Setting up healing for those sessions where we don't add the service version capability as it indicates that the session is not being run on BrowserStack
|
|
192
249
|
if (!shouldAddServiceVersion(this._config, this._options.testObservability, capabilities)) {
|
|
193
250
|
try {
|
|
@@ -301,6 +358,7 @@ export default class BrowserstackLauncherService {
|
|
|
301
358
|
else if (isAccessibilityAutomationSession(this._accessibilityAutomation)) {
|
|
302
359
|
this._updateObjectTypeCaps(capabilities, 'accessibilityOptions', {});
|
|
303
360
|
}
|
|
361
|
+
this._removeCliOnlyCapabilityOptions(capabilities);
|
|
304
362
|
if (shouldSetupPercy) {
|
|
305
363
|
try {
|
|
306
364
|
const bestPlatformPercyCaps = getBestPlatformForPercySnapshot(capabilities);
|
|
@@ -319,6 +377,51 @@ export default class BrowserstackLauncherService {
|
|
|
319
377
|
// send testhub build uuid and product map instrumentation
|
|
320
378
|
this._updateCaps(capabilities, 'testhubBuildUuid');
|
|
321
379
|
this._updateCaps(capabilities, 'buildProductMap');
|
|
380
|
+
if (isValidEnabledValue(this._options.testOrchestrationOptions?.runSmartSelection?.enabled)) {
|
|
381
|
+
// Helper function to convert specs from cwd-relative to rootDir-relative
|
|
382
|
+
const convertToRootDirRelative = (specs) => {
|
|
383
|
+
const rootDir = config.rootDir || process.cwd();
|
|
384
|
+
const cwd = process.cwd();
|
|
385
|
+
return specs.map((spec) => {
|
|
386
|
+
if (typeof spec !== 'string') {
|
|
387
|
+
return spec;
|
|
388
|
+
}
|
|
389
|
+
// Convert from cwd-relative to absolute
|
|
390
|
+
const absolutePath = path.isAbsolute(spec) ? spec : path.resolve(cwd, spec);
|
|
391
|
+
// Then make it relative to rootDir (config file's directory)
|
|
392
|
+
const relativePath = path.relative(rootDir, absolutePath);
|
|
393
|
+
// Normalize path separators
|
|
394
|
+
return relativePath.replace(/\\/g, '/');
|
|
395
|
+
});
|
|
396
|
+
};
|
|
397
|
+
// Apply test orchestration if enabled
|
|
398
|
+
try {
|
|
399
|
+
// Import dynamically to avoid circular dependencies
|
|
400
|
+
const { applyOrchestrationIfEnabled } = await import('./testorchestration/apply-orchestration.js');
|
|
401
|
+
if (config.specs && config.specs.length > 0 && this._options.testObservability && isValidEnabledValue(this._options.testOrchestrationOptions?.runSmartSelection?.enabled)) {
|
|
402
|
+
BStackLogger.info('Applying test orchestration');
|
|
403
|
+
// Ensure we're passing string[] to applyOrchestrationIfEnabled
|
|
404
|
+
const specs = config.specs.filter(spec => typeof spec === 'string');
|
|
405
|
+
console.log(`Specs before orchestration: ${specs}`);
|
|
406
|
+
const orderedSpecs = await applyOrchestrationIfEnabled(specs, this._options);
|
|
407
|
+
console.log(`Specs after orchestration before conversion: ${orderedSpecs}`);
|
|
408
|
+
// Use ordered specs if available, otherwise use original specs
|
|
409
|
+
const specsToConvert = orderedSpecs && orderedSpecs.length > 0 ? orderedSpecs : specs;
|
|
410
|
+
config.specs = convertToRootDirRelative(specsToConvert);
|
|
411
|
+
console.log(`Specs after orchestration: ${config.specs}`);
|
|
412
|
+
BStackLogger.info('Test specs updated with orchestrated order');
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
BStackLogger.error(`Error applying test orchestration: ${error}`);
|
|
417
|
+
// On error, we still need to convert specs from cwd-relative to rootDir-relative
|
|
418
|
+
if (config.specs && config.specs.length > 0) {
|
|
419
|
+
const specs = config.specs.filter(spec => typeof spec === 'string');
|
|
420
|
+
config.specs = convertToRootDirRelative(specs);
|
|
421
|
+
BStackLogger.debug(`Specs converted back to rootDir-relative after error: ${config.specs}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
322
425
|
// local binary will be handled by CLI
|
|
323
426
|
if (BrowserstackCLI.getInstance().isRunning()) {
|
|
324
427
|
return;
|
|
@@ -368,14 +471,19 @@ export default class BrowserstackLauncherService {
|
|
|
368
471
|
}
|
|
369
472
|
async onComplete() {
|
|
370
473
|
PerformanceTester.start(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_CLEANUP);
|
|
474
|
+
PerformanceTester.start(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_ON_STOP);
|
|
475
|
+
PerformanceTester.start(PERFORMANCE_SDK_EVENTS.FRAMEWORK_EVENTS.STOP);
|
|
371
476
|
try {
|
|
372
477
|
BStackLogger.debug('Inside OnComplete hook..');
|
|
373
478
|
BStackLogger.debug('Sending stop launch event');
|
|
479
|
+
const isCLIEnabled = BrowserstackCLI.getInstance().isRunning();
|
|
374
480
|
try {
|
|
375
|
-
await (
|
|
481
|
+
await (isCLIEnabled ? BrowserstackCLI.getInstance().stop() : stopBuildUpstream());
|
|
482
|
+
PerformanceTester.end(PERFORMANCE_SDK_EVENTS.FRAMEWORK_EVENTS.STOP);
|
|
376
483
|
}
|
|
377
484
|
catch (err) {
|
|
378
|
-
BStackLogger.error(`Error while
|
|
485
|
+
BStackLogger.error(`Error while stopping CLI ${err}`);
|
|
486
|
+
PerformanceTester.end(PERFORMANCE_SDK_EVENTS.FRAMEWORK_EVENTS.STOP, false, util.format(err));
|
|
379
487
|
}
|
|
380
488
|
if (process.env[BROWSERSTACK_OBSERVABILITY] && process.env[BROWSERSTACK_TESTHUB_UUID]) {
|
|
381
489
|
console.log(`\nVisit https://automation.browserstack.com/builds/${process.env[BROWSERSTACK_TESTHUB_UUID]} to view build report, insights, and many more debugging information all at one place!\n`);
|
|
@@ -392,17 +500,22 @@ export default class BrowserstackLauncherService {
|
|
|
392
500
|
BStackLogger.info(`Total duration is ${duration / 1000} s`);
|
|
393
501
|
}
|
|
394
502
|
BStackLogger.info(`BrowserStack service run ended for id: ${this.browserStackConfig?.sdkRunID} testhub id: ${TestOpsConfig.getInstance()?.buildHashedId}`);
|
|
395
|
-
await sendFinish(this.browserStackConfig);
|
|
503
|
+
await sendFinish(this.browserStackConfig, isCLIEnabled);
|
|
396
504
|
try {
|
|
505
|
+
PerformanceTester.start(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_SEND_LOGS);
|
|
397
506
|
await this._uploadServiceLogs();
|
|
507
|
+
PerformanceTester.end(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_SEND_LOGS);
|
|
398
508
|
}
|
|
399
509
|
catch (error) {
|
|
400
510
|
BStackLogger.debug(`Failed to upload BrowserStack WDIO Service logs ${error}`);
|
|
511
|
+
PerformanceTester.end(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_SEND_LOGS, false, util.format(error));
|
|
401
512
|
}
|
|
513
|
+
PerformanceTester.end(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_ON_STOP);
|
|
402
514
|
PerformanceTester.end(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_CLEANUP);
|
|
403
515
|
await PerformanceTester.stopAndGenerate('performance-launcher.html');
|
|
404
516
|
}
|
|
405
517
|
catch (error) {
|
|
518
|
+
PerformanceTester.end(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_ON_STOP, false, util.format(error));
|
|
406
519
|
PerformanceTester.end(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_CLEANUP, false, util.format(error));
|
|
407
520
|
await PerformanceTester.stopAndGenerate('performance-launcher.html');
|
|
408
521
|
BStackLogger.error(`Error in onComplete hook: ${error}`);
|
|
@@ -532,11 +645,43 @@ export default class BrowserstackLauncherService {
|
|
|
532
645
|
}
|
|
533
646
|
async _uploadServiceLogs() {
|
|
534
647
|
const clientBuildUuid = this._getClientBuildUuid();
|
|
535
|
-
await PerformanceTester.measureWrapper(PERFORMANCE_SDK_EVENTS.EVENTS.
|
|
648
|
+
await PerformanceTester.measureWrapper(PERFORMANCE_SDK_EVENTS.EVENTS.SDK_UPLOAD_LOGS, async () => {
|
|
536
649
|
const response = await uploadLogs(getBrowserStackUser(this._config), getBrowserStackKey(this._config), clientBuildUuid);
|
|
537
650
|
BStackLogger.logToFile(`Response - ${format(response)}`, 'debug');
|
|
538
651
|
})();
|
|
539
652
|
}
|
|
653
|
+
_removeCliOnlyCapabilityOptions(capabilities) {
|
|
654
|
+
if (!capabilities || typeof capabilities !== 'object') {
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
const strip = (capability) => {
|
|
658
|
+
const capabilityRecord = capability;
|
|
659
|
+
const bstackOptions = capabilityRecord['bstack:options'];
|
|
660
|
+
if (bstackOptions && typeof bstackOptions === 'object') {
|
|
661
|
+
NOT_ALLOWED_KEYS_IN_CAPS.forEach(key => delete bstackOptions[key]);
|
|
662
|
+
}
|
|
663
|
+
NOT_ALLOWED_KEYS_IN_CAPS.forEach(key => delete capabilityRecord[`browserstack.${key}`]);
|
|
664
|
+
const alwaysMatch = capability.alwaysMatch;
|
|
665
|
+
if (alwaysMatch && typeof alwaysMatch === 'object') {
|
|
666
|
+
strip(alwaysMatch);
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
if (Array.isArray(capabilities)) {
|
|
670
|
+
capabilities
|
|
671
|
+
.flatMap((c) => {
|
|
672
|
+
if (Object.values(c).length > 0 && Object.values(c).every(c => typeof c === 'object' && c.capabilities)) {
|
|
673
|
+
return Object.values(c).map((o) => o.capabilities);
|
|
674
|
+
}
|
|
675
|
+
return c;
|
|
676
|
+
})
|
|
677
|
+
.forEach(strip);
|
|
678
|
+
}
|
|
679
|
+
else {
|
|
680
|
+
Object.entries(capabilities).forEach(([, caps]) => {
|
|
681
|
+
strip(caps.capabilities);
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
}
|
|
540
685
|
_updateObjectTypeCaps(capabilities, capType, value) {
|
|
541
686
|
try {
|
|
542
687
|
if (Array.isArray(capabilities)) {
|
package/build/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAc9E,OAAO,KAAK,EAAE,kBAAkB,EAAuB,iBAAiB,EAA8C,MAAM,YAAY,CAAA;AACxI,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
|