dd-trace 6.7.0 → 6.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/ci/test-optimization-validation/command-output-policy.js +5 -5
  2. package/ci/test-optimization-validation/framework-adapters/playwright.js +5 -5
  3. package/ci/test-optimization-validation/generated-files.js +9 -9
  4. package/ci/test-optimization-validation/generated-test-contract.js +1 -3
  5. package/ci/test-optimization-validation/manifest-scaffold.js +1 -2
  6. package/ci/test-optimization-validation/offline-output.js +6 -6
  7. package/ci/test-optimization-validation/runner-command.js +1 -1
  8. package/ci/test-optimization-validation/safe-files.js +4 -4
  9. package/ci/test-optimization-validation/scenarios/basic-reporting.js +1 -1
  10. package/ci/test-optimization-validation/scenarios/test-management.js +2 -2
  11. package/ci/vitest-no-worker-init-setup.mjs +348 -51
  12. package/index.d.ts +77 -13
  13. package/package.json +9 -3
  14. package/packages/datadog-core/src/utils/src/kebabcase.js +8 -2
  15. package/packages/datadog-esbuild/index.js +4 -2
  16. package/packages/datadog-esbuild/src/utils.js +1 -1
  17. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +4 -4
  18. package/packages/datadog-instrumentations/src/cucumber.js +9 -6
  19. package/packages/datadog-instrumentations/src/helpers/extract-package-and-module-path.js +10 -0
  20. package/packages/datadog-instrumentations/src/helpers/hook.js +19 -2
  21. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  22. package/packages/datadog-instrumentations/src/helpers/register.js +9 -3
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +1 -0
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +57 -0
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +50 -18
  27. package/packages/datadog-instrumentations/src/helpers/router-helper.js +1 -1
  28. package/packages/datadog-instrumentations/src/helpers/shared-utils.js +39 -0
  29. package/packages/datadog-instrumentations/src/http/client.js +1 -3
  30. package/packages/datadog-instrumentations/src/jest.js +8 -14
  31. package/packages/datadog-instrumentations/src/mocha/common.js +1 -3
  32. package/packages/datadog-instrumentations/src/mocha/main.js +2 -42
  33. package/packages/datadog-instrumentations/src/mocha/utils.js +68 -2
  34. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +19 -0
  35. package/packages/datadog-instrumentations/src/mocha/worker.js +446 -42
  36. package/packages/datadog-instrumentations/src/moleculer/client.js +1 -1
  37. package/packages/datadog-instrumentations/src/playwright.js +3 -2
  38. package/packages/datadog-instrumentations/src/rhea.js +1 -1
  39. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +525 -56
  40. package/packages/datadog-instrumentations/src/vitest-main.js +90 -10
  41. package/packages/datadog-instrumentations/src/webdriverio.js +914 -0
  42. package/packages/datadog-plugin-aerospike/src/index.js +1 -3
  43. package/packages/datadog-plugin-amqplib/src/client.js +1 -1
  44. package/packages/datadog-plugin-amqplib/src/producer.js +1 -1
  45. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +2 -2
  46. package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +2 -2
  47. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -3
  48. package/packages/datadog-plugin-azure-service-bus/src/producer.js +5 -3
  49. package/packages/datadog-plugin-bullmq/src/producer.js +3 -3
  50. package/packages/datadog-plugin-child_process/src/index.js +3 -3
  51. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +5 -8
  52. package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -1
  53. package/packages/datadog-plugin-fastify/src/tracing.js +1 -1
  54. package/packages/datadog-plugin-fs/src/index.js +0 -4
  55. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +14 -7
  56. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +19 -10
  57. package/packages/datadog-plugin-graphql/src/execute.js +1 -0
  58. package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -1
  59. package/packages/datadog-plugin-http/src/client.js +2 -1
  60. package/packages/datadog-plugin-jest/src/index.js +2 -3
  61. package/packages/datadog-plugin-jest/src/util.js +2 -1
  62. package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
  63. package/packages/datadog-plugin-mocha/src/index.js +25 -6
  64. package/packages/datadog-plugin-prisma/src/index.js +5 -1
  65. package/packages/datadog-plugin-rhea/src/consumer.js +16 -13
  66. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  67. package/packages/datadog-plugin-undici/src/index.js +2 -1
  68. package/packages/datadog-plugin-vitest/src/index.js +84 -11
  69. package/packages/datadog-webpack/index.js +2 -2
  70. package/packages/dd-trace/src/appsec/downstream_requests.js +1 -1
  71. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +1 -1
  72. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +1 -1
  73. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +1 -1
  74. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -3
  75. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  76. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +2 -2
  77. package/packages/dd-trace/src/appsec/sdk/track_event.js +1 -1
  78. package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
  79. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +7 -6
  80. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +2 -2
  81. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -1
  82. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +4 -0
  83. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/webdriverio.js +26 -0
  84. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -1
  85. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -3
  86. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +1 -1
  87. package/packages/dd-trace/src/config/defaults.js +14 -6
  88. package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -2
  89. package/packages/dd-trace/src/config/index.js +15 -0
  90. package/packages/dd-trace/src/config/parsers.js +19 -1
  91. package/packages/dd-trace/src/config/supported-configurations.json +5 -6
  92. package/packages/dd-trace/src/datastreams/encoding.js +3 -4
  93. package/packages/dd-trace/src/debugger/devtools_client/log.js +2 -1
  94. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +1 -4
  95. package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -2
  96. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +2 -2
  97. package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +16 -2
  98. package/packages/dd-trace/src/encode/tags-processors.js +2 -2
  99. package/packages/dd-trace/src/exporters/common/url.js +3 -5
  100. package/packages/dd-trace/src/exporters/electron/index.js +4 -1
  101. package/packages/dd-trace/src/external-logger/src/index.js +1 -2
  102. package/packages/dd-trace/src/flare/index.js +2 -2
  103. package/packages/dd-trace/src/id.js +1 -1
  104. package/packages/dd-trace/src/llmobs/constants/tags.js +1 -1
  105. package/packages/dd-trace/src/llmobs/experiments/client.js +4 -2
  106. package/packages/dd-trace/src/llmobs/experiments/dataset.js +66 -14
  107. package/packages/dd-trace/src/llmobs/experiments/experiment.js +289 -95
  108. package/packages/dd-trace/src/llmobs/experiments/index.js +51 -12
  109. package/packages/dd-trace/src/llmobs/experiments/noop.js +95 -10
  110. package/packages/dd-trace/src/llmobs/experiments/result.js +13 -2
  111. package/packages/dd-trace/src/llmobs/experiments/util.js +218 -0
  112. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +1 -1
  113. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +1 -1
  114. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  115. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  116. package/packages/dd-trace/src/llmobs/sdk.js +4 -11
  117. package/packages/dd-trace/src/llmobs/span_processor.js +9 -7
  118. package/packages/dd-trace/src/llmobs/tagger.js +1 -2
  119. package/packages/dd-trace/src/llmobs/writers/spans.js +12 -6
  120. package/packages/dd-trace/src/noop/proxy.js +2 -2
  121. package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
  122. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +2 -1
  123. package/packages/dd-trace/src/opentracing/span.js +1 -1
  124. package/packages/dd-trace/src/opentracing/span_context.js +1 -1
  125. package/packages/dd-trace/src/plugins/ci_plugin.js +105 -20
  126. package/packages/dd-trace/src/plugins/index.js +1 -0
  127. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  128. package/packages/dd-trace/src/plugins/tracing.js +1 -0
  129. package/packages/dd-trace/src/plugins/util/ci.js +7 -6
  130. package/packages/dd-trace/src/plugins/util/git.js +1 -1
  131. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +1 -1
  132. package/packages/dd-trace/src/plugins/util/llm.js +2 -2
  133. package/packages/dd-trace/src/plugins/util/tags.js +2 -0
  134. package/packages/dd-trace/src/plugins/util/test.js +38 -12
  135. package/packages/dd-trace/src/plugins/util/url.js +5 -5
  136. package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -1
  137. package/packages/dd-trace/src/plugins/util/web.js +1 -1
  138. package/packages/dd-trace/src/priority_sampler.js +6 -3
  139. package/packages/dd-trace/src/profiling/profilers/poisson.js +1 -1
  140. package/packages/dd-trace/src/profiling/profilers/shared.js +2 -2
  141. package/packages/dd-trace/src/ritm.js +5 -20
  142. package/packages/dd-trace/src/startup-log.js +3 -0
  143. package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -1
  144. package/packages/dd-trace/src/util.js +16 -0
  145. package/vendor/dist/@apm-js-collab/code-transformer/index.js +12 -12
  146. package/vendor/dist/pprof-format/index.js +1 -1
  147. package/vendor/dist/source-map/index.js +1 -1
@@ -2,6 +2,8 @@
2
2
 
3
3
  const { addHook, channel } = require('../helpers/instrument')
4
4
  const shimmer = require('../../../datadog-shimmer')
5
+ const { getEnvironmentVariable } = require('../../../dd-trace/src/config/helper')
6
+ const log = require('../../../dd-trace/src/log')
5
7
  const { DD_MAJOR } = require('../../../../version')
6
8
 
7
9
  const {
@@ -14,14 +16,351 @@ const {
14
16
  getOnTestRetryHandler,
15
17
  getRunTestsWrapper,
16
18
  patchFailedTestReplayHookUp,
19
+ adjustRunnerFailuresForTestOptimization,
20
+ efdTests,
21
+ getTestFullName,
17
22
  } = require('./utils')
23
+ const {
24
+ CONFIGURATION_REQUEST,
25
+ CONFIGURATION_RESPONSE,
26
+ createWebdriverioWorkerMessage,
27
+ SUITE_FINISH,
28
+ WEBDRIVERIO_WORKER_ENV,
29
+ WORKER_READY,
30
+ } = require('./webdriverio-protocol')
18
31
  require('./common')
19
32
 
20
33
  const MINIMUM_MOCHA_VERSION = DD_MAJOR >= 6 ? '>=8.0.0' : '>=5.2.0'
21
34
 
22
35
  const workerFinishCh = channel('ci:mocha:worker:finish')
36
+ const workerConfigurationCh = channel('ci:mocha:worker:configuration')
23
37
 
24
38
  const config = {}
39
+ const runnerToFiles = new WeakMap()
40
+ const runnerToFailedHooks = new WeakMap()
41
+ const isWebdriverioWorker = !!getEnvironmentVariable(WEBDRIVERIO_WORKER_ENV)
42
+ let configurationRequestId = 0
43
+
44
+ /**
45
+ * Sends a message to the WebdriverIO launcher without surfacing closed IPC channels.
46
+ *
47
+ * @param {object} message
48
+ * @param {() => void} [onError]
49
+ * @param {() => void} [onDone]
50
+ * @returns {void}
51
+ */
52
+ function sendWebdriverioMessage (message, onError, onDone) {
53
+ if (!process.send || !process.connected) {
54
+ onError?.()
55
+ onDone?.()
56
+ return
57
+ }
58
+
59
+ process.send(createWebdriverioWorkerMessage(message), (error) => {
60
+ if (error) {
61
+ log.error('WebdriverIO Test Optimization IPC error', error)
62
+ onError?.()
63
+ }
64
+ onDone?.()
65
+ })
66
+ }
67
+
68
+ /**
69
+ * Applies configuration encoded as private Mocha options by its parallel runner.
70
+ *
71
+ * @param {object} options
72
+ * @returns {void}
73
+ */
74
+ function applyMochaOptions (options) {
75
+ if (options._ddIsKnownTestsEnabled) {
76
+ config.isKnownTestsEnabled = true
77
+ config.isEarlyFlakeDetectionEnabled = options._ddIsEfdEnabled
78
+ config.knownTests = options._ddKnownTests
79
+ config.earlyFlakeDetectionNumRetries = options._ddEfdNumRetries
80
+ config.earlyFlakeDetectionSlowTestRetries = options._ddEfdSlowTestRetries ?? {}
81
+ delete options._ddIsEfdEnabled
82
+ delete options._ddKnownTests
83
+ delete options._ddEfdNumRetries
84
+ delete options._ddEfdSlowTestRetries
85
+ delete options._ddIsKnownTestsEnabled
86
+ }
87
+ if (options._ddIsImpactedTestsEnabled) {
88
+ config.isImpactedTestsEnabled = true
89
+ config.modifiedFiles = options._ddModifiedFiles
90
+ delete options._ddIsImpactedTestsEnabled
91
+ delete options._ddModifiedFiles
92
+ }
93
+ if (options._ddIsTestManagementTestsEnabled) {
94
+ config.isTestManagementTestsEnabled = true
95
+ config.testManagementAttemptToFixRetries = options._ddTestManagementAttemptToFixRetries
96
+ config.testManagementTests = options._ddTestManagementTests
97
+ delete options._ddIsTestManagementTestsEnabled
98
+ delete options._ddTestManagementAttemptToFixRetries
99
+ delete options._ddTestManagementTests
100
+ }
101
+ if (options._ddIsFlakyTestRetriesEnabled) {
102
+ config.isFlakyTestRetriesEnabled = true
103
+ config.flakyTestRetriesCount = options._ddFlakyTestRetriesCount
104
+ delete options._ddIsFlakyTestRetriesEnabled
105
+ delete options._ddFlakyTestRetriesCount
106
+ }
107
+ if (options._ddIsFailedTestReplayEnabled) {
108
+ config.isTestDynamicInstrumentationEnabled = true
109
+ config.isDiEnabled = true
110
+ delete options._ddIsFailedTestReplayEnabled
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Removes files selected for suite-level skipping from an already loaded Mocha root suite.
116
+ *
117
+ * @param {object} runner
118
+ * @param {string[]} skippedFiles
119
+ * @returns {void}
120
+ */
121
+ function filterSkippedFiles (runner, skippedFiles) {
122
+ if (!skippedFiles.length) {
123
+ return
124
+ }
125
+
126
+ const skippedFilesSet = new Set(skippedFiles)
127
+ runner.suite.suites = runner.suite.suites.filter(suite => !skippedFilesSet.has(suite.file))
128
+ runner.suite.tests = runner.suite.tests.filter(test => !skippedFilesSet.has(test.file))
129
+ }
130
+
131
+ /**
132
+ * Requests configuration from the WebdriverIO launcher before releasing Mocha's delayed root suite.
133
+ *
134
+ * @param {string} frameworkVersion
135
+ * @param {string[]} files
136
+ * @param {(response: object) => void} onDone
137
+ * @returns {void}
138
+ */
139
+ function requestWebdriverioConfiguration (frameworkVersion, files, onDone) {
140
+ const requestId = `${process.pid}-${++configurationRequestId}`
141
+ let finished = false
142
+
143
+ /**
144
+ * Finishes the configuration request exactly once.
145
+ *
146
+ * @param {object} response
147
+ * @returns {void}
148
+ */
149
+ function finish (response) {
150
+ if (finished) {
151
+ return
152
+ }
153
+ finished = true
154
+ clearTimeout(timeout)
155
+ process.off('message', onMessage)
156
+ process.off('disconnect', onDisconnect)
157
+ onDone(response)
158
+ }
159
+
160
+ /**
161
+ * Receives the matching coordinator response.
162
+ *
163
+ * @param {object} message
164
+ * @returns {void}
165
+ */
166
+ function onMessage (message) {
167
+ if (message?.name === CONFIGURATION_RESPONSE && message.content?.requestId === requestId) {
168
+ finish(message.content)
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Releases the runner if its parent disconnects.
174
+ *
175
+ * @returns {void}
176
+ */
177
+ function onDisconnect () {
178
+ finish({})
179
+ }
180
+
181
+ const timeout = setTimeout(() => finish({}), 30_000)
182
+ process.on('message', onMessage)
183
+ process.once('disconnect', onDisconnect)
184
+ sendWebdriverioMessage({
185
+ origin: 'datadog',
186
+ name: CONFIGURATION_REQUEST,
187
+ content: {
188
+ files,
189
+ frameworkVersion,
190
+ requestId,
191
+ },
192
+ }, () => finish({}))
193
+ }
194
+
195
+ /**
196
+ * Reports the Mocha version as soon as WebdriverIO loads its framework adapter.
197
+ *
198
+ * @param {string} frameworkVersion
199
+ * @returns {void}
200
+ */
201
+ function reportWebdriverioWorkerReady (frameworkVersion) {
202
+ if (!isWebdriverioWorker) {
203
+ return
204
+ }
205
+
206
+ sendWebdriverioMessage({
207
+ origin: 'datadog',
208
+ name: WORKER_READY,
209
+ content: { frameworkVersion },
210
+ })
211
+ }
212
+
213
+ /**
214
+ * Checks whether Test Optimization converts a failed WebdriverIO test attempt to a passing result.
215
+ *
216
+ * @param {object|undefined} test
217
+ * @returns {boolean}
218
+ */
219
+ function isWebdriverioFailureSuppressed (test) {
220
+ if (!test) {
221
+ return false
222
+ }
223
+ const hasPassingEfdAttempt = config.isEarlyFlakeDetectionEnabled &&
224
+ efdTests[getTestFullName(test)]?.some(attempt => attempt.state === 'passed' && !attempt._ddHookFailed)
225
+ return (test._ddIsQuarantined && !test._ddIsAttemptToFix) || hasPassingEfdAttempt
226
+ }
227
+
228
+ /**
229
+ * Gets the test owned by a per-test hook without associating suite hooks with boundary tests.
230
+ *
231
+ * @param {object} hook
232
+ * @returns {object|undefined}
233
+ */
234
+ function getWebdriverioHookTest (hook) {
235
+ const { parent } = hook
236
+ const isTestHook = parent?._beforeEach?.includes(hook) || parent?._afterEach?.includes(hook)
237
+ return isTestHook ? hook.ctx?.currentTest : undefined
238
+ }
239
+
240
+ /**
241
+ * Removes managed hook failures from WebdriverIO's Mocha runner totals.
242
+ *
243
+ * @param {object} runner
244
+ * @returns {void}
245
+ */
246
+ function adjustWebdriverioHookFailures (runner) {
247
+ let suppressedFailures = 0
248
+ for (const { test } of runnerToFailedHooks.get(runner) || []) {
249
+ if (isWebdriverioFailureSuppressed(test)) {
250
+ suppressedFailures++
251
+ }
252
+ }
253
+ if (runner.stats) {
254
+ runner.stats.failures -= suppressedFailures
255
+ }
256
+ runner.failures -= suppressedFailures
257
+ }
258
+
259
+ /**
260
+ * Computes a final result for every file loaded into one Mocha worker.
261
+ *
262
+ * @param {object} runner
263
+ * @returns {object[]}
264
+ */
265
+ function getWebdriverioSuiteResults (runner) {
266
+ const resultsByFile = new Map()
267
+ const files = runnerToFiles.get(runner) || []
268
+
269
+ for (const file of files) {
270
+ resultsByFile.set(file, {
271
+ file,
272
+ hasPassingTest: false,
273
+ status: 'skip',
274
+ })
275
+ }
276
+
277
+ runner.suite.eachTest(test => {
278
+ const result = resultsByFile.get(test.file)
279
+ if (!result) {
280
+ return
281
+ }
282
+ const isSuppressedFailure = isWebdriverioFailureSuppressed(test)
283
+ if ((test.state === 'failed' || test.timedOut || test._ddHookFailed) && !isSuppressedFailure) {
284
+ result.status = 'fail'
285
+ } else if (test.state === 'passed' || isSuppressedFailure) {
286
+ result.hasPassingTest = true
287
+ }
288
+ })
289
+
290
+ for (const { file, test } of runnerToFailedHooks.get(runner) || []) {
291
+ const result = resultsByFile.get(file)
292
+ if (result && !isWebdriverioFailureSuppressed(test)) {
293
+ result.status = 'fail'
294
+ }
295
+ }
296
+
297
+ const results = []
298
+ let hasFailedSuite = false
299
+ for (const result of resultsByFile.values()) {
300
+ if (result.status === 'fail') {
301
+ hasFailedSuite = true
302
+ } else if (result.hasPassingTest) {
303
+ result.status = 'pass'
304
+ }
305
+ delete result.hasPassingTest
306
+ results.push(result)
307
+ }
308
+
309
+ if (runner.failures > 0 && !hasFailedSuite) {
310
+ for (const result of results) {
311
+ result.status = 'fail'
312
+ }
313
+ }
314
+
315
+ return results
316
+ }
317
+
318
+ /**
319
+ * Sends suite results to the WebdriverIO launcher.
320
+ *
321
+ * @param {object} runner
322
+ * @param {() => void} [onDone]
323
+ * @returns {void}
324
+ */
325
+ function reportWebdriverioSuiteResults (runner, onDone) {
326
+ if (!isWebdriverioWorker) {
327
+ onDone?.()
328
+ return
329
+ }
330
+
331
+ sendWebdriverioMessage({
332
+ origin: 'datadog',
333
+ name: SUITE_FINISH,
334
+ content: {
335
+ results: getWebdriverioSuiteResults(runner),
336
+ },
337
+ }, undefined, onDone)
338
+ }
339
+
340
+ /**
341
+ * Flushes worker payloads before reporting suite results and completing Mocha.
342
+ *
343
+ * @param {object} runner
344
+ * @param {() => void} onDone
345
+ * @returns {void}
346
+ */
347
+ function finishWebdriverioWorker (runner, onDone) {
348
+ try {
349
+ workerFinishCh.publish({
350
+ onDone: () => {
351
+ try {
352
+ reportWebdriverioSuiteResults(runner, onDone)
353
+ } catch (error) {
354
+ log.error('WebdriverIO Test Optimization worker completion error', error)
355
+ onDone()
356
+ }
357
+ },
358
+ })
359
+ } catch (error) {
360
+ log.error('WebdriverIO Test Optimization worker completion error', error)
361
+ onDone()
362
+ }
363
+ }
25
364
 
26
365
  function isFailedTestReplayEnabled () {
27
366
  return config.isTestDynamicInstrumentationEnabled && config.isDiEnabled
@@ -31,46 +370,77 @@ addHook({
31
370
  name: 'mocha',
32
371
  versions: ['>=8.0.0'],
33
372
  file: 'lib/mocha.js',
34
- }, (Mocha) => {
373
+ }, (Mocha, frameworkVersion) => {
374
+ reportWebdriverioWorkerReady(frameworkVersion)
375
+
35
376
  shimmer.wrap(Mocha.prototype, 'run', run => function (...args) {
36
- if (this.options._ddIsKnownTestsEnabled) {
37
- config.isKnownTestsEnabled = true
38
- config.isEarlyFlakeDetectionEnabled = this.options._ddIsEfdEnabled
39
- config.knownTests = this.options._ddKnownTests
40
- config.earlyFlakeDetectionNumRetries = this.options._ddEfdNumRetries
41
- config.earlyFlakeDetectionSlowTestRetries = this.options._ddEfdSlowTestRetries ?? {}
42
- delete this.options._ddIsEfdEnabled
43
- delete this.options._ddKnownTests
44
- delete this.options._ddEfdNumRetries
45
- delete this.options._ddEfdSlowTestRetries
46
- delete this.options._ddIsKnownTestsEnabled
47
- }
48
- if (this.options._ddIsImpactedTestsEnabled) {
49
- config.isImpactedTestsEnabled = true
50
- config.modifiedFiles = this.options._ddModifiedFiles
51
- delete this.options._ddIsImpactedTestsEnabled
52
- delete this.options._ddModifiedFiles
53
- }
54
- if (this.options._ddIsTestManagementTestsEnabled) {
55
- config.isTestManagementTestsEnabled = true
56
- config.testManagementAttemptToFixRetries = this.options._ddTestManagementAttemptToFixRetries
57
- config.testManagementTests = this.options._ddTestManagementTests
58
- delete this.options._ddIsTestManagementTestsEnabled
59
- delete this.options._ddTestManagementAttemptToFixRetries
60
- delete this.options._ddTestManagementTests
61
- }
62
- if (this.options._ddIsFlakyTestRetriesEnabled) {
63
- config.isFlakyTestRetriesEnabled = true
64
- config.flakyTestRetriesCount = this.options._ddFlakyTestRetriesCount
65
- delete this.options._ddIsFlakyTestRetriesEnabled
66
- delete this.options._ddFlakyTestRetriesCount
67
- }
68
- if (this.options._ddIsFailedTestReplayEnabled) {
69
- config.isTestDynamicInstrumentationEnabled = true
70
- config.isDiEnabled = true
71
- delete this.options._ddIsFailedTestReplayEnabled
377
+ applyMochaOptions(this.options)
378
+ if (!isWebdriverioWorker || !workerFinishCh.hasSubscribers) {
379
+ return run.apply(this, args)
72
380
  }
73
- return run.apply(this, args)
381
+
382
+ const isUserDelayed = this.options.delay
383
+ const rootSuite = this.suite
384
+ const rootSuiteRun = rootSuite.run
385
+ const hasOwnRootSuiteRun = Object.hasOwn(rootSuite, 'run')
386
+ let configurationReady = false
387
+ let userReady = !isUserDelayed
388
+
389
+ /**
390
+ * Restores the root suite method after both delayed-mode gates are open.
391
+ *
392
+ * @returns {void}
393
+ */
394
+ function restoreRootSuiteRun () {
395
+ if (hasOwnRootSuiteRun) {
396
+ rootSuite.run = rootSuiteRun
397
+ } else {
398
+ delete rootSuite.run
399
+ }
400
+ }
401
+
402
+ if (isUserDelayed) {
403
+ rootSuite.run = function (...args) {
404
+ userReady = true
405
+ if (!configurationReady) {
406
+ return
407
+ }
408
+ restoreRootSuiteRun()
409
+ return rootSuiteRun.apply(this, args)
410
+ }
411
+ }
412
+
413
+ this.options.delay = true
414
+ const files = [...this.files]
415
+ const runner = run.apply(this, args)
416
+ runnerToFiles.set(runner, files)
417
+
418
+ requestWebdriverioConfiguration(frameworkVersion, files, ({
419
+ configuration,
420
+ skippedFiles = [],
421
+ }) => {
422
+ if (configuration) {
423
+ Object.assign(config, configuration)
424
+ workerConfigurationCh.publish({
425
+ libraryConfig: config,
426
+ repositoryRoot: config.repositoryRoot,
427
+ testFramework: config.testFramework,
428
+ })
429
+ }
430
+ filterSkippedFiles(runner, skippedFiles)
431
+ if (isFailedTestReplayEnabled()) {
432
+ patchFailedTestReplayHookUp(runner.constructor)
433
+ }
434
+ configurationReady = true
435
+ if (userReady) {
436
+ if (isUserDelayed) {
437
+ restoreRootSuiteRun()
438
+ }
439
+ rootSuite.run()
440
+ }
441
+ })
442
+
443
+ return runner
74
444
  })
75
445
 
76
446
  return Mocha
@@ -91,10 +461,32 @@ addHook({
91
461
  if (isFailedTestReplayEnabled()) {
92
462
  patchFailedTestReplayHookUp(Runner)
93
463
  }
94
- // We flush when the worker ends with its test file (a mocha instance in a worker runs a single test file)
95
- this.once('end', () => {
96
- workerFinishCh.publish()
97
- })
464
+ if (isWebdriverioWorker) {
465
+ this.prependOnceListener('end', () => {
466
+ try {
467
+ adjustRunnerFailuresForTestOptimization(this, config)
468
+ adjustWebdriverioHookFailures(this)
469
+ } catch (error) {
470
+ log.error('WebdriverIO Test Optimization failure adjustment error', error)
471
+ }
472
+ })
473
+ }
474
+ const onRunDone = args[0]
475
+ if (isWebdriverioWorker && typeof onRunDone === 'function') {
476
+ args[0] = (...onRunDoneArgs) => {
477
+ finishWebdriverioWorker(this, () => onRunDone(...onRunDoneArgs))
478
+ }
479
+ } else {
480
+ // Flush after the worker finishes its Mocha run, including grouped spec files.
481
+ this.once('end', () => {
482
+ try {
483
+ workerFinishCh.publish()
484
+ reportWebdriverioSuiteResults(this)
485
+ } catch (error) {
486
+ log.error('WebdriverIO Test Optimization worker completion error', error)
487
+ }
488
+ })
489
+ }
98
490
  this.on('test', getOnTestHandler(false))
99
491
 
100
492
  this.on('test end', getOnTestEndHandler(config))
@@ -104,6 +496,18 @@ addHook({
104
496
  // If the hook passes, 'hook end' will be emitted. Otherwise, 'fail' will be emitted
105
497
  this.on('hook end', getOnHookEndHandler(config))
106
498
 
499
+ if (isWebdriverioWorker) {
500
+ const failedHooks = []
501
+ runnerToFailedHooks.set(this, failedHooks)
502
+ this.on('fail', runnable => {
503
+ if (runnable.type === 'hook' && runnable.file) {
504
+ failedHooks.push({
505
+ file: runnable.file,
506
+ test: getWebdriverioHookTest(runnable),
507
+ })
508
+ }
509
+ })
510
+ }
107
511
  this.on('fail', getOnFailHandler(false, config))
108
512
 
109
513
  this.on('pending', getOnPendingHandler())
@@ -10,7 +10,7 @@ const errorChannel = channel('apm:moleculer:call:error')
10
10
  function wrapCall (call) {
11
11
  return function (actionName, params, opts) {
12
12
  opts = arguments[2] = opts || {}
13
- opts.meta = opts.meta || {}
13
+ opts.meta ||= {}
14
14
 
15
15
  arguments.length = Math.max(3, arguments.length)
16
16
 
@@ -1350,7 +1350,7 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
1350
1350
  let totalFailedTestCount = 0
1351
1351
  let totalPureQuarantinedFailedTestCount = 0
1352
1352
 
1353
- for (const [fqn, testStatuses] of testsToTestStatuses.entries()) {
1353
+ for (const [fqn, testStatuses] of testsToTestStatuses) {
1354
1354
  // Only count as failed if the final status (after retries) is 'fail'
1355
1355
  const lastStatus = testStatuses.at(-1)
1356
1356
  if (lastStatus === 'fail') {
@@ -1494,7 +1494,8 @@ createRootSuiteCh.subscribe({
1494
1494
  pageGotoCh.subscribe({
1495
1495
  asyncEnd (ctx) {
1496
1496
  // The Page.goto rewriter waits for this so tests closing immediately after navigation still get RUM tags.
1497
- ctx.asyncEndPromise = handlePageGoto(ctx.self)
1497
+ const rumDetectionPromise = handlePageGoto(ctx.self)
1498
+ ctx.resolveCallback = onDone => rumDetectionPromise.then(onDone, onDone)
1498
1499
  },
1499
1500
  })
1500
1501
 
@@ -222,7 +222,7 @@ function beforeFinish (delivery, state) {
222
222
  }
223
223
 
224
224
  function getStateFromData (stateData) {
225
- if (stateData && stateData.descriptor && stateData.descriptor) {
225
+ if (stateData?.descriptor) {
226
226
  switch (stateData.descriptor.value) {
227
227
  case 0x24: return 'accepted'
228
228
  case 0x25: return 'rejected'