@wdio/browserstack-service 8.48.0 → 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.
Files changed (68) hide show
  1. package/build/accessibility-handler.d.ts.map +1 -1
  2. package/build/accessibility-handler.js +13 -11
  3. package/build/ai-handler.d.ts.map +1 -1
  4. package/build/ai-handler.js +11 -0
  5. package/build/cli/cliLogger.d.ts +1 -0
  6. package/build/cli/cliLogger.d.ts.map +1 -1
  7. package/build/cli/cliLogger.js +23 -12
  8. package/build/cli/cliUtils.d.ts +18 -0
  9. package/build/cli/cliUtils.d.ts.map +1 -1
  10. package/build/cli/cliUtils.js +29 -0
  11. package/build/cli/frameworks/wdioMochaTestFramework.d.ts.map +1 -1
  12. package/build/cli/frameworks/wdioMochaTestFramework.js +4 -2
  13. package/build/cli/grpcClient.d.ts +19 -3
  14. package/build/cli/grpcClient.d.ts.map +1 -1
  15. package/build/cli/grpcClient.js +133 -21
  16. package/build/cli/index.d.ts +2 -0
  17. package/build/cli/index.d.ts.map +1 -1
  18. package/build/cli/index.js +52 -4
  19. package/build/cli/modules/accessibilityModule.d.ts +3 -0
  20. package/build/cli/modules/accessibilityModule.d.ts.map +1 -1
  21. package/build/cli/modules/accessibilityModule.js +75 -10
  22. package/build/constants.d.ts +28 -0
  23. package/build/constants.d.ts.map +1 -1
  24. package/build/constants.js +37 -1
  25. package/build/exitHandler.d.ts +1 -1
  26. package/build/exitHandler.d.ts.map +1 -1
  27. package/build/exitHandler.js +6 -5
  28. package/build/insights-handler.d.ts.map +1 -1
  29. package/build/insights-handler.js +33 -7
  30. package/build/instrumentation/funnelInstrumentation.d.ts +2 -2
  31. package/build/instrumentation/funnelInstrumentation.d.ts.map +1 -1
  32. package/build/instrumentation/funnelInstrumentation.js +30 -8
  33. package/build/instrumentation/performance/constants.d.ts +64 -0
  34. package/build/instrumentation/performance/constants.d.ts.map +1 -1
  35. package/build/instrumentation/performance/constants.js +73 -2
  36. package/build/instrumentation/performance/performance-tester.d.ts +11 -0
  37. package/build/instrumentation/performance/performance-tester.d.ts.map +1 -1
  38. package/build/instrumentation/performance/performance-tester.js +115 -12
  39. package/build/launcher.d.ts +1 -0
  40. package/build/launcher.d.ts.map +1 -1
  41. package/build/launcher.js +151 -6
  42. package/build/service.d.ts.map +1 -1
  43. package/build/service.js +77 -5
  44. package/build/testorchestration/apply-orchestration.d.ts +7 -0
  45. package/build/testorchestration/apply-orchestration.d.ts.map +1 -0
  46. package/build/testorchestration/apply-orchestration.js +42 -0
  47. package/build/testorchestration/helpers.d.ts +20 -0
  48. package/build/testorchestration/helpers.d.ts.map +1 -0
  49. package/build/testorchestration/helpers.js +412 -0
  50. package/build/testorchestration/request-utils.d.ts +23 -0
  51. package/build/testorchestration/request-utils.d.ts.map +1 -0
  52. package/build/testorchestration/request-utils.js +81 -0
  53. package/build/testorchestration/test-ordering-server.d.ts +33 -0
  54. package/build/testorchestration/test-ordering-server.d.ts.map +1 -0
  55. package/build/testorchestration/test-ordering-server.js +168 -0
  56. package/build/testorchestration/testorcherstrationhandler.d.ts +50 -0
  57. package/build/testorchestration/testorcherstrationhandler.d.ts.map +1 -0
  58. package/build/testorchestration/testorcherstrationhandler.js +133 -0
  59. package/build/testorchestration/testorcherstrationutils.d.ts +86 -0
  60. package/build/testorchestration/testorcherstrationutils.d.ts.map +1 -0
  61. package/build/testorchestration/testorcherstrationutils.js +379 -0
  62. package/build/types.d.ts +21 -0
  63. package/build/types.d.ts.map +1 -1
  64. package/build/util.d.ts +33 -4
  65. package/build/util.d.ts.map +1 -1
  66. package/build/util.js +112 -18
  67. package/package.json +3 -2
  68. 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;AAYlE,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,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,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;IAwCjE,MAAM,CAAC,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,GAAI,MAAM;WAepC,eAAe,CAAC,QAAQ,GAAE,MAA+B;IAsCtE,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;IA6BhG,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;IAUlF,MAAM,CAAC,YAAY;IAInB,MAAM,CAAC,KAAK,oCAAkE;WAEjE,gBAAgB;CA0EhC"}
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, PERF_METRICS_WAIT_TIME } from '../../constants.js';
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
- PerformanceTester.start(label);
170
- this.details && (this.details[label] = details);
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
- PerformanceTester.end(label);
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
- PerformanceTester.end(label, false, util.format(e));
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
- PerformanceTester.end(label);
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
- PerformanceTester.end(label, false, util.format(er));
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 performance events ${util.format(result.body)}`);
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 performance events ${util.format(er)}`);
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 performance related files ${util.format(er)}`);
382
+ BStackLogger.debug(`[Performance Upload] Failed to delete temporary files: ${util.format(er)}`);
280
383
  }
281
384
  }
282
385
  }
@@ -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;
@@ -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;AAiDxH,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;IAmNpF,UAAU;IA8FV,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,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"}
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 (CLIUtils.checkCLISupportedFrameworks(config.framework)) {
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 (BrowserstackCLI.getInstance().isRunning() ? BrowserstackCLI.getInstance().stop() : stopBuildUpstream());
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 stoping CLI ${err}`);
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.SDK_AUTO_CAPTURE, async () => {
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)) {
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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"}