browserstack-node-sdk 1.65.7 → 1.66.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/ats/src/cloudproviders/AWS.js +1 -1
  2. package/ats/src/cloudproviders/Azure.js +1 -1
  3. package/ats/src/cloudproviders/CloudProvider.js +1 -1
  4. package/ats/src/cloudproviders/GoogleCloudProvider.js +1 -1
  5. package/ats/src/cloudproviders/utils.js +1 -1
  6. package/ats/src/commands/configure.js +1 -1
  7. package/ats/src/commands/connect.js +1 -1
  8. package/ats/src/commands/connect_commands/grid.js +1 -1
  9. package/ats/src/commands/create.js +1 -1
  10. package/ats/src/commands/create_commands/grid.js +1 -1
  11. package/ats/src/commands/delete.js +1 -1
  12. package/ats/src/commands/delete_commands/grid.js +1 -1
  13. package/ats/src/commands/init.js +1 -1
  14. package/ats/src/commands/list.js +1 -1
  15. package/ats/src/commands/list_commands/grid_profiles.js +1 -1
  16. package/ats/src/commands/list_commands/grids.js +1 -1
  17. package/ats/src/commands/precheck.js +1 -1
  18. package/ats/src/commands/precheck_commands/setup_precheck.js +1 -1
  19. package/ats/src/commands/start.js +1 -1
  20. package/ats/src/commands/start_commands/grid.js +1 -1
  21. package/ats/src/commands/stop.js +1 -1
  22. package/ats/src/commands/stop_commands/grid.js +1 -1
  23. package/ats/src/config/constants.js +1 -1
  24. package/ats/src/config/logger.js +1 -1
  25. package/ats/src/index.js +1 -1
  26. package/ats/src/utils/AWSUtils.js +1 -1
  27. package/ats/src/utils/createAutoscalerStack.js +1 -1
  28. package/ats/src/utils/createClusterStack.js +1 -1
  29. package/ats/src/utils/createHubHaStack.js +1 -1
  30. package/ats/src/utils/createk8sClient.js +1 -1
  31. package/ats/src/utils/deleteEFSMountTargets.js +1 -1
  32. package/ats/src/utils/deleteStack.js +1 -1
  33. package/ats/src/utils/describeStack.js +1 -1
  34. package/ats/src/utils/gridComponents.js +1 -1
  35. package/ats/src/utils/hstUtils.js +1 -1
  36. package/ats/src/utils/instrumentationUtils.js +1 -1
  37. package/ats/src/utils/userInput.js +1 -1
  38. package/ats/src/utils/utilityMethods.js +1 -1
  39. package/ats/src/utils/webSocketUtils.js +1 -1
  40. package/generated/sdk-messages_pb.d.ts +118 -0
  41. package/generated/sdk-messages_pb.js +908 -0
  42. package/generated/sdk_grpc_pb.d.ts +10 -0
  43. package/generated/sdk_grpc_pb.js +74 -0
  44. package/package.json +1 -1
  45. package/src/bin/codeceptjs/BrowserStackPlugin.js +1 -1
  46. package/src/bin/codeceptjs/command.js +1 -1
  47. package/src/bin/codeceptjs/helper.js +1 -1
  48. package/src/bin/codeceptjs/o11yHelper.js +1 -1
  49. package/src/bin/cucumber-js/command.js +1 -1
  50. package/src/bin/cucumber-js/formatter/custom_formatter.js +1 -1
  51. package/src/bin/cucumber-js/formatter/custom_formatter_old_versions.js +1 -1
  52. package/src/bin/cucumber-js/test-observability.js +1 -1
  53. package/src/bin/cucumber-js/test.setup.js +1 -1
  54. package/src/bin/cucumber-js/test_old.setup.js +1 -1
  55. package/src/bin/cucumber-js/timeout.setup.js +1 -1
  56. package/src/bin/detox/a11ySetup.js +1 -0
  57. package/src/bin/detox/command.js +2 -0
  58. package/src/bin/detox/modifyDetoxConf.js +1 -0
  59. package/src/bin/detox/traEmitter.js +1 -0
  60. package/src/bin/integrations/command.js +1 -1
  61. package/src/bin/integrations/constants.js +1 -1
  62. package/src/bin/jasmine/bstack-reporter/customReporter.js +1 -1
  63. package/src/bin/jasmine/command.js +1 -1
  64. package/src/bin/jasmine/test.setup.js +1 -1
  65. package/src/bin/jasmine/testRunMap.js +1 -1
  66. package/src/bin/jest/command.js +1 -1
  67. package/src/bin/jest/customImplements/customCircus.js +1 -1
  68. package/src/bin/jest/customImplements/customEnvironment.js +1 -1
  69. package/src/bin/jest/customImplements/customJestAdapterInit.js +1 -1
  70. package/src/bin/jest/customImplements/customSequencer.js +1 -1
  71. package/src/bin/jest/customImplements/customTestRunnerTemplate.js +1 -1
  72. package/src/bin/jest/jest.setup.js +1 -1
  73. package/src/bin/jest/test-observability/data-hooks.js +1 -1
  74. package/src/bin/jest/test-observability/helper.js +1 -1
  75. package/src/bin/jest/test-observability/obsReporter.js +1 -1
  76. package/src/bin/jest/test-observability/test-details.js +1 -1
  77. package/src/bin/jest/test.setup.js +1 -1
  78. package/src/bin/jest/test_before.setup.js +1 -1
  79. package/src/bin/launcher/launcher.js +1 -1
  80. package/src/bin/load/index.js +1 -1
  81. package/src/bin/load/loadErrors.js +1 -1
  82. package/src/bin/load/loadUtils.js +1 -1
  83. package/src/bin/mocha/accessibility-automation/helper.js +1 -1
  84. package/src/bin/mocha/bstack-reporter/customReporter.js +1 -1
  85. package/src/bin/mocha/command.js +1 -1
  86. package/src/bin/mocha/test-observability/data-hooks.js +1 -1
  87. package/src/bin/mocha/test-observability/mochaUtils.js +1 -1
  88. package/src/bin/mocha/test.setup.js +1 -1
  89. package/src/bin/newman/command.js +1 -1
  90. package/src/bin/newman/helper.js +1 -1
  91. package/src/bin/newman/reporter/index.js +1 -1
  92. package/src/bin/newman/scriptRunner.js +1 -1
  93. package/src/bin/nightwatch/command.js +1 -1
  94. package/src/bin/node-generic/client.js +1 -1
  95. package/src/bin/playwright/accessibility-helper.js +1 -1
  96. package/src/bin/playwright/command.js +1 -1
  97. package/src/bin/playwright/globalSetup.js +1 -1
  98. package/src/bin/playwright/mock-process.js +1 -1
  99. package/src/bin/playwright/modifyPlaywrightConf.js +1 -1
  100. package/src/bin/playwright/patches.js +1 -1
  101. package/src/bin/playwright/reporter/pwUtils.js +1 -1
  102. package/src/bin/playwright/reporter/reporter.js +1 -1
  103. package/src/bin/playwright/reporter/test-details.js +1 -1
  104. package/src/bin/playwright/reporterArgs.js +1 -1
  105. package/src/bin/playwright/sessionDetails.js +1 -1
  106. package/src/bin/puppeteer/patches.js +1 -1
  107. package/src/bin/puppeteer/sessionDetails.js +1 -1
  108. package/src/bin/puppeteer/test.setup.js +1 -1
  109. package/src/bin/runner.js +1 -1
  110. package/src/bin/setup.js +1 -1
  111. package/src/bin/test-runner/index.js +1 -1
  112. package/src/bin/test-runner/runTest.js +1 -1
  113. package/src/bin/test-runner/testWorker.js +1 -1
  114. package/src/bin/testcafe/a11y-runner.js +1 -1
  115. package/src/bin/testcafe/command.js +1 -1
  116. package/src/bin/testcafe/reporter/index.js +1 -1
  117. package/src/bin/testcafe/reporter/tcUtils.js +1 -1
  118. package/src/bin/testcafe/session-registry.js +1 -1
  119. package/src/bin/testcafe/stdout-interceptor.js +1 -1
  120. package/src/bin/testcafe/testcafe-action-patcher.js +1 -1
  121. package/src/bin/testcafe/w3c-driver.js +1 -1
  122. package/src/bin/utils/apiUtilityMethods.js +1 -1
  123. package/src/bin/utils/clientTroubleshootingService.js +1 -1
  124. package/src/bin/utils/constants.js +1 -1
  125. package/src/bin/utils/log4jsAppender.js +1 -1
  126. package/src/bin/utils/logPatcher.js +1 -1
  127. package/src/bin/utils/logReportingAPI.js +1 -1
  128. package/src/bin/utils/playwright-patcher.js +1 -1
  129. package/src/bin/utils/puppeteer-patcher.js +1 -1
  130. package/src/bin/utils/sharedPlaywrightSessionDetails.js +1 -1
  131. package/src/bin/utils/sharedUtils.js +1 -1
  132. package/src/bin/v2/TestInfo.js +1 -1
  133. package/src/bin/v2/browserstackCLI.js +1 -1
  134. package/src/bin/v2/cliUtils.js +1 -1
  135. package/src/bin/v2/customTagManager.js +1 -1
  136. package/src/bin/v2/eventDispatcher.js +1 -1
  137. package/src/bin/v2/framework/automationFramework.js +1 -1
  138. package/src/bin/v2/framework/constants/automationFrameworkConstants.js +1 -1
  139. package/src/bin/v2/framework/constants/testFrameworkConstants.js +1 -1
  140. package/src/bin/v2/framework/cucumberTestFramework.js +1 -1
  141. package/src/bin/v2/framework/jestTestFramework.js +1 -1
  142. package/src/bin/v2/framework/newmanTestFramework.js +1 -1
  143. package/src/bin/v2/framework/nodeGenericTestFramework.js +1 -1
  144. package/src/bin/v2/framework/playwrightAutomationFramework.js +1 -1
  145. package/src/bin/v2/framework/playwrightTestFramework.js +1 -1
  146. package/src/bin/v2/framework/puppeteerAutomationFramework.js +1 -1
  147. package/src/bin/v2/framework/testFramework.js +1 -1
  148. package/src/bin/v2/framework/testcafeAutomationFramework.js +1 -1
  149. package/src/bin/v2/framework/testcafeTestFramework.js +1 -1
  150. package/src/bin/v2/grpcClient.js +1 -1
  151. package/src/bin/v2/instances/automationFrameworkInstance.js +1 -1
  152. package/src/bin/v2/instances/testFrameworkInstance.js +1 -1
  153. package/src/bin/v2/instances/trackedContext.js +1 -1
  154. package/src/bin/v2/instances/trackedInstance.js +1 -1
  155. package/src/bin/v2/modules/accessibilityModule.js +1 -1
  156. package/src/bin/v2/modules/baseModule.js +1 -1
  157. package/src/bin/v2/modules/loadTestModule.js +1 -1
  158. package/src/bin/v2/modules/observabilityModule.js +1 -1
  159. package/src/bin/v2/modules/playwrightDriverModule.js +1 -1
  160. package/src/bin/v2/modules/puppeteerDriverModule.js +1 -1
  161. package/src/bin/v2/modules/testcafeDriverModule.js +1 -1
  162. package/src/bin/v2/modules/testhubModule.js +1 -1
  163. package/src/bin/v2/sdkErrorHandler.js +1 -1
  164. package/src/bin/v2/state/automationFrameworkState.js +1 -1
  165. package/src/bin/v2/state/hookState.js +1 -1
  166. package/src/bin/v2/state/testFrameworkState.js +1 -1
  167. package/src/bin/v2/uploadAttachmentsManager.js +1 -1
  168. package/src/bin/v2/utils/cucumber-utils.js +1 -1
  169. package/src/bin/v2/utils/jest-utils.js +1 -1
  170. package/src/bin/vanilla-js/command.js +1 -1
  171. package/src/browserStackSdk.js +1 -1
  172. package/src/detox-driver/a11y.js +574 -0
  173. package/src/detox-driver/allocationDriver.js +26 -0
  174. package/src/detox-driver/cloudPlatformAction.js +111 -0
  175. package/src/detox-driver/environment.js +253 -0
  176. package/src/detox-driver/expect.js +44 -0
  177. package/src/detox-driver/index.js +12 -0
  178. package/src/detox-driver/login-timeout-preload.js +25 -0
  179. package/src/detox-driver/runtimeDriver.js +351 -0
  180. package/src/detox-driver/session-reporter.js +41 -0
  181. package/src/detox-driver/tra-capture.js +81 -0
  182. package/src/helpers/BrowserStackSetup.js +1 -1
  183. package/src/helpers/accessibility-automation/accessibilityRetryHelper.js +1 -1
  184. package/src/helpers/accessibility-automation/constants.js +1 -1
  185. package/src/helpers/accessibility-automation/helper.js +1 -1
  186. package/src/helpers/accessibility-automation/scripts.js +1 -1
  187. package/src/helpers/ats/constants.js +1 -1
  188. package/src/helpers/ats/helper.js +1 -1
  189. package/src/helpers/caCertHelper.js +1 -1
  190. package/src/helpers/capsMapping.js +1 -1
  191. package/src/helpers/central-scanner/constants.js +1 -1
  192. package/src/helpers/central-scanner/helpers.js +1 -1
  193. package/src/helpers/classLogger.js +1 -1
  194. package/src/helpers/driverInitErrorReporter.js +1 -1
  195. package/src/helpers/globals.js +1 -1
  196. package/src/helpers/helper.js +1 -1
  197. package/src/helpers/logger.js +1 -1
  198. package/src/helpers/metadata.js +1 -1
  199. package/src/helpers/patchHelper.js +1 -1
  200. package/src/helpers/patchHelpers.js +1 -1
  201. package/src/helpers/percy/Percy.js +1 -1
  202. package/src/helpers/percy/PercyBinary.js +1 -1
  203. package/src/helpers/percy/PercyCaptureMap.js +1 -1
  204. package/src/helpers/percy/PercyCaptureModes.js +1 -1
  205. package/src/helpers/percy/PercySDK.js +1 -1
  206. package/src/helpers/percy/index.js +1 -1
  207. package/src/helpers/percy/utils.js +1 -1
  208. package/src/helpers/performance/constants.js +1 -1
  209. package/src/helpers/performance/performance-tester.js +1 -1
  210. package/src/helpers/playwright-helper.js +1 -1
  211. package/src/helpers/processHelper.js +1 -1
  212. package/src/helpers/puppeteer-helper.js +1 -1
  213. package/src/helpers/request-spy.js +1 -1
  214. package/src/helpers/setKeepAlive.js +1 -1
  215. package/src/helpers/stackFrameUtils.js +1 -1
  216. package/src/helpers/test-observability/constants.js +1 -1
  217. package/src/helpers/test-observability/error-handler.js +1 -1
  218. package/src/helpers/test-observability/requestQueueHandler.js +1 -1
  219. package/src/helpers/test-observability/sessionHandler.js +1 -1
  220. package/src/helpers/test-observability/utils.js +1 -1
  221. package/src/helpers/test-orchestration/constants.js +1 -1
  222. package/src/helpers/test-orchestration/failfastManager.js +1 -1
  223. package/src/helpers/test-orchestration/fileHelper.js +1 -1
  224. package/src/helpers/test-orchestration/orchestrationUtils.js +1 -1
  225. package/src/helpers/test-orchestration/requestUtils.js +1 -1
  226. package/src/helpers/test-orchestration/testOrchestrationHandler.js +1 -1
  227. package/src/helpers/test-orchestration/testOrderingServerHandler.js +1 -1
  228. package/src/helpers/testhub/constants.js +1 -1
  229. package/src/helpers/testhub/testhubHandler.js +1 -1
  230. package/src/helpers/testhub/utils.js +1 -1
  231. package/src/helpers/utilities.js +1 -1
  232. package/src/helpers/w3cMapping.js +1 -1
  233. package/src/hubAllocationMethods.js +1 -1
  234. package/src/index.js +1 -1
@@ -0,0 +1,111 @@
1
+ 'use strict';
2
+
3
+ // The BrowserStack cloud instrumentation bridge is driven by a `CloudPlatform`
4
+ // action. We ship vanilla, unpatched Detox, so this is built on detox's exported
5
+ // `Action` base and sent through the public `client.sendAction(action)` — no core
6
+ // patch. Params are FLAT ({method, ...extra}, e.g. {method:'launchApp', launchArgs:{}}),
7
+ // matching the bridge's expected shape.
8
+ //
9
+ // detox is the CUSTOMER's dependency, not the SDK's — resolve it from process.cwd()
10
+ // (the SDK package has no detox installed), the same way the driver module is loaded.
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ const detoxRoot = path.dirname(require.resolve('detox/package.json', {paths: [process.cwd()]}));
15
+ const {Action} = require(path.join(detoxRoot, 'src/client/actions/actions'));
16
+
17
+ // Current jest spec-file path, read from the worker's jest state. Detox opens one
18
+ // cloud session per spec file, so this ties the session id to its spec — letting the
19
+ // SDK correlate each session with the spec's verdict (from the jest reporter) and mark
20
+ // it individually. Empty string when unavailable (falls back to the run-level verdict).
21
+ function currentTestPath() {
22
+ try {
23
+ // Preferred: the spec path stashed by the spec-capture setupFilesAfterEnv module
24
+ // (jest's expect.getState().testPath is populated there but not in this driver's
25
+ // load context).
26
+ if (global.__BSTACK_DETOX_SPEC__) {
27
+ return global.__BSTACK_DETOX_SPEC__;
28
+ }
29
+ const state = global.expect && global.expect.getState && global.expect.getState();
30
+
31
+ return (state && state.testPath) || '';
32
+ } catch (err) {
33
+ return '';
34
+ }
35
+ }
36
+
37
+ // Last id seen on the socket, kept in memory for callers that need to address the LIVE
38
+ // session (the a11y scan relay) rather than reconcile it after the run.
39
+ let latestCloudSessionId = '';
40
+
41
+ // The BrowserStack session id ("cloudSessionId") rides every CloudPlatform response.
42
+ // Persist `<id>\t<specPath>` to the file the SDK's detox command points us at, so the
43
+ // main process can mark each session (status + name) on the dashboard after the run.
44
+ // Best-effort: this must never break the test run.
45
+ function recordCloudSessionId(response) {
46
+ try {
47
+ const id = response && response.cloudSessionId;
48
+ if (!id) {
49
+ return;
50
+ }
51
+ latestCloudSessionId = id;
52
+ const file = process.env.BROWSERSTACK_DETOX_SESSION_IDS_FILE;
53
+ if (file) {
54
+ fs.appendFileSync(file, id + '\t' + currentTestPath() + '\n');
55
+ }
56
+ } catch (err) { /* best-effort session capture */ }
57
+ }
58
+
59
+ const getCloudSessionId = () => latestCloudSessionId;
60
+
61
+ class CloudPlatformAction extends Action {
62
+ constructor(params) {
63
+ super('CloudPlatform', params);
64
+ }
65
+
66
+ get isAtomic() {
67
+ return true;
68
+ }
69
+
70
+ get timeout() {
71
+ return 90000;
72
+ }
73
+
74
+ async handle(response) {
75
+ this.expectResponseOfType(response, 'CloudPlatform');
76
+
77
+ return response;
78
+ }
79
+ }
80
+
81
+ // Carrier for the App Accessibility scan relay. Two deliberate departures from
82
+ // CloudPlatformAction: a caller-supplied timeout (a scan must be written off long before
83
+ // CloudPlatformAction's 90s), and NO response-type assertion — the whole point is to see
84
+ // whatever the Hub answers with, including a shape nobody has documented, instead of
85
+ // throwing an internal error that hides it.
86
+ class ScanRelayAction extends CloudPlatformAction {
87
+ constructor(params, timeoutMs, type) {
88
+ super(params);
89
+ if (type) {
90
+ this.type = type;
91
+ }
92
+ this._timeoutMs = timeoutMs;
93
+ }
94
+
95
+ get timeout() {
96
+ return this._timeoutMs;
97
+ }
98
+
99
+ async handle(response) {
100
+ return response;
101
+ }
102
+ }
103
+
104
+ async function executeCloudPlatform(client, method, extra = {}) {
105
+ const response = await client.sendAction(new CloudPlatformAction(Object.assign({method}, extra)));
106
+ recordCloudSessionId(response);
107
+
108
+ return response;
109
+ }
110
+
111
+ module.exports = {CloudPlatformAction, ScanRelayAction, executeCloudPlatform, getCloudSessionId};
@@ -0,0 +1,253 @@
1
+ 'use strict';
2
+
3
+ // Custom jest testEnvironment for BrowserStack Detox runs. Extends detox's
4
+ // DetoxCircusEnvironment (so detox's own init / listeners / artifacts still run) and
5
+ // stashes the current spec path on the Node-realm `global` that the CloudPlatform
6
+ // driver reads. The driver loads in this same Node realm during detox.init — where
7
+ // jest's sandbox `expect.getState().testPath` is NOT visible — so the environment
8
+ // (also Node realm, and holding the spec path via `context.testPath`) is the correct
9
+ // place to bridge it. Best-effort: never break detox's environment setup.
10
+ //
11
+ // TRA tap: this environment also observes jest-circus lifecycle events (tests +
12
+ // hooks) and appends JSONL records to the per-slot capture file. Detox's own
13
+ // constructor binds `this.handleTestEvent` (index.js:56) — with a subclass the bind
14
+ // resolves to the override below, which delegates to detox's via super. Capture is
15
+ // active only when the SDK main process exported the events file (TRA enabled).
16
+ const path = require('path');
17
+
18
+ const traCapture = require('./tra-capture');
19
+
20
+ const detoxRoot = path.dirname(require.resolve('detox/package.json', {paths: [process.cwd()]}));
21
+ const detoxEnvModule = require(path.join(detoxRoot, 'runners/jest/testEnvironment'));
22
+ const DetoxCircusEnvironment = detoxEnvModule && detoxEnvModule.default ? detoxEnvModule.default : detoxEnvModule;
23
+
24
+ class BrowserStackDetoxEnvironment extends DetoxCircusEnvironment {
25
+ constructor(config, context) {
26
+ super(config, context);
27
+ try {
28
+ const testPath = (context && context.testPath) || this.testPath;
29
+ if (testPath) {
30
+ global.__BSTACK_DETOX_SPEC__ = testPath;
31
+ }
32
+ this._bstackSpecPath = testPath || null;
33
+ this._bstackTests = new Map();
34
+ this._bstackHooks = new Map();
35
+ // jest re-runs a test in place under `retryTimes`, so the same identity
36
+ // fires test_start/test_done more than once. Remember the first attempt's
37
+ // uuid per identity so later attempts report as retries of it instead of
38
+ // landing as independent dashboard rows.
39
+ this._bstackFirstUuidByKey = new Map();
40
+ } catch (err) { /* best-effort spec capture */ }
41
+ }
42
+
43
+ async handleTestEvent(event, state) {
44
+ try {
45
+ if (traCapture.enabled() && event && event.name) {
46
+ this._bstackCaptureEvent(event, state, Date.now());
47
+ // A failure screenshot needs detox's async device API, so it cannot ride
48
+ // the synchronous capture switch. Awaited here, before detox's own
49
+ // handler tears the test down and the device becomes unusable.
50
+ if (event.name === 'test_done' && event.test && event.test.errors && event.test.errors.length) {
51
+ await this._bstackCaptureScreenshot(event.test);
52
+ }
53
+ // Cleared only after the failure capture above, so the driver's tee can still
54
+ // attribute that screenshot to this test.
55
+ if (event.name === 'test_done') {
56
+ global.__BSTACK_DETOX_TEST_UUID__ = null;
57
+ }
58
+ }
59
+ } catch (err) { /* best-effort: capture must never affect the run */ }
60
+ if (super.handleTestEvent) {
61
+ return super.handleTestEvent(event, state);
62
+ }
63
+ }
64
+
65
+ // Console output from the spec is only observable inside this worker, so tee it
66
+ // into the capture stream tagged with whichever test is running. Wrapping is
67
+ // guarded and always delegates to the original writer — a customer's console
68
+ // output must never be swallowed or duplicated.
69
+ _bstackPatchConsole() {
70
+ try {
71
+ if (this._bstackConsolePatched || !this.global || !this.global.console) {
72
+ return;
73
+ }
74
+ this._bstackConsolePatched = true;
75
+ const levels = {log: 'INFO', info: 'INFO', warn: 'WARN', error: 'ERROR', debug: 'DEBUG'};
76
+ Object.keys(levels).forEach((method) => {
77
+ const original = this.global.console[method];
78
+ if (typeof original !== 'function') {
79
+ return;
80
+ }
81
+ this.global.console[method] = (...args) => {
82
+ try {
83
+ const meta = this._bstackCurrentTestMeta;
84
+ if (meta && meta.uuid) {
85
+ traCapture.append({
86
+ kind: 'log',
87
+ testUuid: meta.uuid,
88
+ specPath: this._bstackSpecPath,
89
+ level: levels[method],
90
+ message: args.map((a) => (typeof a === 'string' ? a : traCapture.safeStringify(a))).join(' '),
91
+ timestamp: new Date().toISOString()
92
+ });
93
+ }
94
+ } catch (err) { /* never let capture break console */ }
95
+
96
+ return original.apply(this.global.console, args);
97
+ };
98
+ });
99
+ } catch (err) { /* best-effort */ }
100
+ }
101
+
102
+ // Screenshot on failure. `device.takeScreenshot` returns the path of the png the
103
+ // cloud driver wrote from the device's in-app Espresso capture; the image is read
104
+ // here and carried INLINE as base64, the shape the binary ultimately stores and
105
+ // the one puppeteer/playwright already ship in production. Carrying bytes rather
106
+ // than a path removes the binary's cross-process `readFileSync(filePath)` from the
107
+ // path — that read fails with only a `warn` and an emptied field, i.e. it loses
108
+ // screenshots silently. `filePath` still rides along as a fallback.
109
+ //
110
+ // Bounded by a timeout because a wedged device would otherwise hold the whole run
111
+ // hostage, and fully swallowed — a missing screenshot must never turn a suite red.
112
+ async _bstackCaptureScreenshot(test) {
113
+ try {
114
+ const device = this.global && this.global.device;
115
+ if (!device || typeof device.takeScreenshot !== 'function') {
116
+ return;
117
+ }
118
+ const meta = this._bstackTests.get(test) || {};
119
+ if (!meta.uuid) {
120
+ return;
121
+ }
122
+ const name = `failure-${meta.uuid}`;
123
+ const filePath = await Promise.race([
124
+ device.takeScreenshot(name),
125
+ new Promise((resolve) => setTimeout(() => resolve(null), 15000))
126
+ ]);
127
+ if (!filePath || typeof filePath !== 'string') {
128
+ this._bstackDebug(`screenshot: no path returned for ${name}`);
129
+
130
+ return;
131
+ }
132
+ // No record is written here on purpose: the driver tees every takeScreenshot into
133
+ // the TRA stream (bytes already in hand), so appending again would double-report.
134
+ this._bstackDebug(`screenshot: captured ${name} -> ${filePath}`);
135
+ } catch (err) {
136
+ this._bstackDebug(`screenshot: capture failed: ${err && err.message}`);
137
+ }
138
+ }
139
+
140
+ // The capture path was silently unobservable — a screenshot that never reached the
141
+ // dashboard left no trace of where it was dropped. Opt-in so it costs nothing.
142
+ _bstackDebug(message) {
143
+ try {
144
+ if (process.env.BROWSERSTACK_DETOX_SCREENSHOT_DEBUG === 'true') {
145
+ console.log(`[bstack-detox] ${message}`);
146
+ }
147
+ } catch (err) { /* best-effort */ }
148
+ }
149
+
150
+ _bstackCaptureEvent(event, state, now) {
151
+ switch (event.name) {
152
+ case 'setup': {
153
+ this._bstackPatchConsole();
154
+ break;
155
+ }
156
+ case 'test_start': {
157
+ if (event.test) {
158
+ const key = [this._bstackSpecPath, traCapture.scopesOf(event.test).join('>'), String(event.test.name || '')].join('|');
159
+ const firstUuid = this._bstackFirstUuidByKey.get(key);
160
+ const uuid = traCapture.mintUuid();
161
+ if (!firstUuid) {
162
+ this._bstackFirstUuidByKey.set(key, uuid);
163
+ }
164
+ this._bstackTests.set(event.test, {uuid, startedAt: now, retryOf: firstUuid || null});
165
+ this._bstackCurrentTestMeta = this._bstackTests.get(event.test);
166
+ // Published for the cloud driver (same Node realm, the way __BSTACK_DETOX_SPEC__
167
+ // already is): it tees EVERY device.takeScreenshot() — including the customer's
168
+ // own calls from a test body — and needs the owning test to attribute it to.
169
+ global.__BSTACK_DETOX_TEST_UUID__ = uuid;
170
+ }
171
+ break;
172
+ }
173
+ case 'test_done': {
174
+ const test = event.test;
175
+ if (!test) {
176
+ break;
177
+ }
178
+ const meta = this._bstackTests.get(test) || {};
179
+ const failed = Boolean(test.errors && test.errors.length);
180
+ traCapture.append({
181
+ kind: 'test',
182
+ uuid: meta.uuid || traCapture.mintUuid(),
183
+ specPath: this._bstackSpecPath,
184
+ name: String(test.name || ''),
185
+ scopes: traCapture.scopesOf(test),
186
+ status: failed ? 'failed' : 'passed',
187
+ startedAt: meta.startedAt || null,
188
+ endedAt: now,
189
+ durationMs: meta.startedAt ? now - meta.startedAt : null,
190
+ failure: traCapture.serializeErrors(test.errors),
191
+ ...(meta.retryOf ? {retryOf: meta.retryOf} : {})
192
+ });
193
+ this._bstackCurrentTestMeta = null;
194
+ break;
195
+ }
196
+ case 'test_skip':
197
+ case 'test_todo': {
198
+ const test = event.test;
199
+ if (!test) {
200
+ break;
201
+ }
202
+ traCapture.append({
203
+ kind: 'test',
204
+ uuid: traCapture.mintUuid(),
205
+ specPath: this._bstackSpecPath,
206
+ name: String(test.name || ''),
207
+ scopes: traCapture.scopesOf(test),
208
+ status: 'skipped',
209
+ startedAt: now,
210
+ endedAt: now,
211
+ durationMs: 0
212
+ });
213
+ break;
214
+ }
215
+ case 'hook_start': {
216
+ if (event.hook) {
217
+ this._bstackHooks.set(event.hook, {uuid: traCapture.mintUuid(), startedAt: now});
218
+ }
219
+ break;
220
+ }
221
+ case 'hook_success':
222
+ case 'hook_failure': {
223
+ const hook = event.hook;
224
+ if (!hook) {
225
+ break;
226
+ }
227
+ const meta = this._bstackHooks.get(hook) || {};
228
+ const currentTest = state && state.currentlyRunningTest;
229
+ const testMeta = currentTest ? this._bstackTests.get(currentTest) : null;
230
+ traCapture.append({
231
+ kind: 'hook',
232
+ uuid: meta.uuid || traCapture.mintUuid(),
233
+ specPath: this._bstackSpecPath,
234
+ hookType: String(hook.type || ''),
235
+ testUuid: (testMeta && testMeta.uuid) || null,
236
+ testName: currentTest ? String(currentTest.name || '') : null,
237
+ scopes: traCapture.scopesOf(hook),
238
+ status: event.name === 'hook_failure' ? 'failed' : 'passed',
239
+ startedAt: meta.startedAt || null,
240
+ endedAt: now,
241
+ durationMs: meta.startedAt ? now - meta.startedAt : null,
242
+ failure: event.error ? traCapture.serializeErrors([event.error]) : undefined
243
+ });
244
+ break;
245
+ }
246
+ default:
247
+ break;
248
+ }
249
+ }
250
+ }
251
+
252
+ module.exports = BrowserStackDetoxEnvironment;
253
+ module.exports.default = BrowserStackDetoxEnvironment;
@@ -0,0 +1,44 @@
1
+ 'use strict';
2
+
3
+ // Reuse vanilla detox's Android matchers (element/expect/waitFor/web). The built-in
4
+ // Android matchers factory remaps its deps before construction
5
+ // (`{invocationManager, device, emitter}`), but the external matchers factory
6
+ // (matchers/factories/index.js: External.createMatchers) passes
7
+ // `{invocationManager, runtimeDevice, eventEmitter}` straight through unmapped — so
8
+ // this adapter does the same remap AndroidExpect expects.
9
+ //
10
+ // detox is resolved from process.cwd() (the customer's install), not the SDK package.
11
+ const path = require('path');
12
+
13
+ const detoxRoot = path.dirname(require.resolve('detox/package.json', {paths: [process.cwd()]}));
14
+ const AndroidExpect = require(path.join(detoxRoot, 'src/android/AndroidExpect'));
15
+
16
+ const a11y = require('./a11y');
17
+
18
+ class CloudExpect extends AndroidExpect {
19
+ constructor({invocationManager, runtimeDevice, eventEmitter}) {
20
+ super({invocationManager, device: runtimeDevice, emitter: eventEmitter});
21
+ }
22
+
23
+ // App Accessibility wraps mapped element and web-view actions so a scan can fire on
24
+ // their completion. This is the SDK-owned seam for it: AndroidExpect binds
25
+ // element/web/waitFor/expect in its constructor, so a subclass override is what those
26
+ // bindings resolve to — no Detox internal is patched, and `waitFor`/`expect` are left
27
+ // alone because no matcher or expectation is a scannable command.
28
+ //
29
+ // The wrapper is installed ONLY when a11y is armed. With it disabled these overrides
30
+ // return exactly what AndroidExpect returns, so the non-a11y path is unchanged.
31
+ element(matcher) {
32
+ const element = super.element(matcher);
33
+
34
+ return a11y.enabled() ? a11y.wrap(element, 'NativeElement') : element;
35
+ }
36
+
37
+ web(matcher) {
38
+ const webView = super.web(matcher);
39
+
40
+ return a11y.enabled() ? a11y.wrap(webView, 'WebViewElement') : webView;
41
+ }
42
+ }
43
+
44
+ module.exports = CloudExpect;
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ // Vanilla Detox external device module contract (detox/src/environmentFactory.js:
4
+ // _getExternalModuleFactories). device.type must be a require-able path to this
5
+ // directory; these three exports are validated by
6
+ // devices/allocation/factories/external.js, devices/runtime/factories/external.js and
7
+ // matchers/factories/index.js's External classes.
8
+ module.exports = {
9
+ DeviceAllocationDriverClass: require('./allocationDriver'),
10
+ RuntimeDriverClass: require('./runtimeDriver'),
11
+ ExpectClass: require('./expect')
12
+ };
@@ -0,0 +1,25 @@
1
+ 'use strict';
2
+
3
+ // Login-timeout fix, loaded via NODE_OPTIONS=--require BEFORE detox loads (the SDK's
4
+ // `detox` command injects it, mirroring the nightwatch spawn preload). Vanilla detox
5
+ // times out the Login WS action at 1000ms; BrowserStack cloud device allocation takes
6
+ // longer, so the timeout must be raised before detox is required — an in-driver patch
7
+ // loads too late (Login fires before the device driver module is required).
8
+ //
9
+ // Guarded: a failure here must never break the customer's run (the default 1000ms
10
+ // window would apply and Detox would surface its own timeout, not an SDK crash).
11
+ try {
12
+ const path = require('path');
13
+ const detoxRoot = path.dirname(require.resolve('detox/package.json', {paths: [process.cwd()]}));
14
+ const actions = require(path.join(detoxRoot, 'src/client/actions/actions'));
15
+ const ms = Number(process.env.BSTACK_DETOX_LOGIN_TIMEOUT_MS || 240000);
16
+ Object.defineProperty(actions.Login.prototype, 'timeout', {
17
+ configurable: true,
18
+ get() {
19
+ return ms;
20
+ }
21
+ });
22
+ } catch (err) {
23
+ // eslint-disable-next-line no-console
24
+ console.error('[browserstack-detox] login-timeout preload failed: ' + (err && err.message));
25
+ }