dd-trace 6.6.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 (209) hide show
  1. package/ci/diagnose.js +162 -21
  2. package/ci/init.js +0 -1
  3. package/ci/runbook.md +187 -180
  4. package/ci/test-optimization-validation/approval-artifacts.js +3 -1
  5. package/ci/test-optimization-validation/approval.js +52 -15
  6. package/ci/test-optimization-validation/ci-command-candidate.js +26 -32
  7. package/ci/test-optimization-validation/ci-discovery.js +1 -1
  8. package/ci/test-optimization-validation/ci-package-scripts.js +141 -0
  9. package/ci/test-optimization-validation/ci-remediation.js +112 -39
  10. package/ci/test-optimization-validation/cli.js +516 -681
  11. package/ci/test-optimization-validation/command-blocker.js +188 -33
  12. package/ci/test-optimization-validation/command-output-policy.js +8 -31
  13. package/ci/test-optimization-validation/command-runner.js +189 -245
  14. package/ci/test-optimization-validation/environment.js +90 -0
  15. package/ci/test-optimization-validation/executable.js +159 -388
  16. package/ci/test-optimization-validation/framework-adapters/cucumber.js +119 -0
  17. package/ci/test-optimization-validation/framework-adapters/cypress.js +107 -0
  18. package/ci/test-optimization-validation/framework-adapters/playwright.js +181 -0
  19. package/ci/test-optimization-validation/generated-files.js +84 -22
  20. package/ci/test-optimization-validation/generated-test-contract.js +281 -0
  21. package/ci/test-optimization-validation/generated-verifier.js +49 -16
  22. package/ci/test-optimization-validation/literal-environment.js +57 -0
  23. package/ci/test-optimization-validation/manifest-loader.js +2 -22
  24. package/ci/test-optimization-validation/manifest-scaffold.js +815 -515
  25. package/ci/test-optimization-validation/manifest-schema.js +494 -702
  26. package/ci/test-optimization-validation/offline-fixtures.js +4 -1
  27. package/ci/test-optimization-validation/offline-output.js +6 -6
  28. package/ci/test-optimization-validation/plan-writer.js +243 -984
  29. package/ci/test-optimization-validation/preflight-runner.js +49 -55
  30. package/ci/test-optimization-validation/redaction.js +2 -1
  31. package/ci/test-optimization-validation/report-writer.js +391 -1357
  32. package/ci/test-optimization-validation/result-semantics.js +86 -0
  33. package/ci/test-optimization-validation/runner-command.js +249 -0
  34. package/ci/test-optimization-validation/runner-contract.js +664 -0
  35. package/ci/test-optimization-validation/safe-files.js +4 -4
  36. package/ci/test-optimization-validation/scenarios/auto-test-retries.js +29 -3
  37. package/ci/test-optimization-validation/scenarios/basic-reporting.js +240 -543
  38. package/ci/test-optimization-validation/scenarios/ci-wiring.js +450 -670
  39. package/ci/test-optimization-validation/scenarios/early-flake-detection.js +4 -3
  40. package/ci/test-optimization-validation/scenarios/helpers.js +67 -9
  41. package/ci/test-optimization-validation/scenarios/test-management.js +6 -5
  42. package/ci/test-optimization-validation/source-text.js +87 -0
  43. package/ci/test-optimization-validation/test-output.js +12 -22
  44. package/ci/vitest-no-worker-init-setup.mjs +348 -51
  45. package/ext/tags.d.ts +1 -0
  46. package/index.d.ts +84 -13
  47. package/package.json +10 -4
  48. package/packages/datadog-core/src/utils/src/kebabcase.js +8 -2
  49. package/packages/datadog-esbuild/index.js +4 -2
  50. package/packages/datadog-esbuild/src/utils.js +1 -1
  51. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +4 -4
  52. package/packages/datadog-instrumentations/src/cucumber.js +9 -6
  53. package/packages/datadog-instrumentations/src/helpers/extract-package-and-module-path.js +10 -0
  54. package/packages/datadog-instrumentations/src/helpers/hook.js +19 -2
  55. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  56. package/packages/datadog-instrumentations/src/helpers/register.js +9 -3
  57. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +1 -0
  58. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  59. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/openai-agents.js +31 -0
  60. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +57 -0
  61. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +50 -18
  62. package/packages/datadog-instrumentations/src/helpers/router-helper.js +1 -1
  63. package/packages/datadog-instrumentations/src/helpers/shared-utils.js +39 -0
  64. package/packages/datadog-instrumentations/src/http/client.js +1 -3
  65. package/packages/datadog-instrumentations/src/jest.js +8 -14
  66. package/packages/datadog-instrumentations/src/mocha/common.js +1 -3
  67. package/packages/datadog-instrumentations/src/mocha/main.js +2 -42
  68. package/packages/datadog-instrumentations/src/mocha/utils.js +68 -2
  69. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +19 -0
  70. package/packages/datadog-instrumentations/src/mocha/worker.js +446 -42
  71. package/packages/datadog-instrumentations/src/moleculer/client.js +1 -1
  72. package/packages/datadog-instrumentations/src/openai-agents.js +159 -0
  73. package/packages/datadog-instrumentations/src/playwright.js +3 -2
  74. package/packages/datadog-instrumentations/src/rhea.js +1 -1
  75. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +525 -56
  76. package/packages/datadog-instrumentations/src/vitest-main.js +90 -10
  77. package/packages/datadog-instrumentations/src/webdriverio.js +914 -0
  78. package/packages/datadog-plugin-aerospike/src/index.js +1 -3
  79. package/packages/datadog-plugin-amqplib/src/client.js +1 -1
  80. package/packages/datadog-plugin-amqplib/src/producer.js +1 -1
  81. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +2 -2
  82. package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +2 -2
  83. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -3
  84. package/packages/datadog-plugin-azure-service-bus/src/producer.js +5 -3
  85. package/packages/datadog-plugin-bullmq/src/producer.js +3 -3
  86. package/packages/datadog-plugin-child_process/src/index.js +3 -3
  87. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +5 -8
  88. package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -1
  89. package/packages/datadog-plugin-fastify/src/tracing.js +1 -1
  90. package/packages/datadog-plugin-fs/src/index.js +0 -4
  91. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +14 -7
  92. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +19 -10
  93. package/packages/datadog-plugin-graphql/src/execute.js +1 -0
  94. package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -1
  95. package/packages/datadog-plugin-http/src/client.js +2 -1
  96. package/packages/datadog-plugin-jest/src/index.js +2 -3
  97. package/packages/datadog-plugin-jest/src/util.js +2 -1
  98. package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
  99. package/packages/datadog-plugin-mocha/src/index.js +25 -6
  100. package/packages/datadog-plugin-openai-agents/src/index.js +74 -0
  101. package/packages/datadog-plugin-openai-agents/src/integration.js +503 -0
  102. package/packages/datadog-plugin-openai-agents/src/processor.js +108 -0
  103. package/packages/datadog-plugin-openai-agents/src/util.js +60 -0
  104. package/packages/datadog-plugin-prisma/src/index.js +5 -1
  105. package/packages/datadog-plugin-rhea/src/consumer.js +16 -13
  106. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  107. package/packages/datadog-plugin-undici/src/index.js +2 -1
  108. package/packages/datadog-plugin-vitest/src/index.js +84 -11
  109. package/packages/datadog-webpack/index.js +2 -2
  110. package/packages/dd-trace/src/appsec/downstream_requests.js +1 -1
  111. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +1 -1
  112. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +1 -1
  113. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +1 -1
  114. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -3
  115. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  116. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +2 -2
  117. package/packages/dd-trace/src/appsec/sdk/track_event.js +1 -1
  118. package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
  119. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +7 -6
  120. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +50 -51
  121. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -1
  122. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +4 -0
  123. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/webdriverio.js +26 -0
  124. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -1
  125. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +161 -33
  126. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -3
  127. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +7 -5
  128. package/packages/dd-trace/src/config/defaults.js +14 -6
  129. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -2
  130. package/packages/dd-trace/src/config/index.js +15 -0
  131. package/packages/dd-trace/src/config/parsers.js +19 -1
  132. package/packages/dd-trace/src/config/supported-configurations.json +12 -6
  133. package/packages/dd-trace/src/datastreams/encoding.js +3 -4
  134. package/packages/dd-trace/src/debugger/devtools_client/log.js +2 -1
  135. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +1 -4
  136. package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -2
  137. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +2 -2
  138. package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +16 -2
  139. package/packages/dd-trace/src/encode/span-stats.js +7 -1
  140. package/packages/dd-trace/src/encode/tags-processors.js +2 -2
  141. package/packages/dd-trace/src/exporter.js +16 -9
  142. package/packages/dd-trace/src/exporters/common/url.js +3 -5
  143. package/packages/dd-trace/src/exporters/electron/index.js +4 -1
  144. package/packages/dd-trace/src/external-logger/src/index.js +1 -2
  145. package/packages/dd-trace/src/flare/index.js +2 -2
  146. package/packages/dd-trace/src/id.js +1 -1
  147. package/packages/dd-trace/src/llmobs/constants/tags.js +1 -1
  148. package/packages/dd-trace/src/llmobs/experiments/client.js +4 -2
  149. package/packages/dd-trace/src/llmobs/experiments/dataset.js +66 -14
  150. package/packages/dd-trace/src/llmobs/experiments/experiment.js +289 -95
  151. package/packages/dd-trace/src/llmobs/experiments/index.js +51 -12
  152. package/packages/dd-trace/src/llmobs/experiments/noop.js +95 -10
  153. package/packages/dd-trace/src/llmobs/experiments/result.js +13 -2
  154. package/packages/dd-trace/src/llmobs/experiments/util.js +218 -0
  155. package/packages/dd-trace/src/llmobs/index.js +9 -1
  156. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +104 -22
  157. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +1 -1
  158. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +1 -1
  159. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  160. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  161. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +5 -9
  162. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +20 -0
  163. package/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js +321 -0
  164. package/packages/dd-trace/src/llmobs/sdk.js +6 -12
  165. package/packages/dd-trace/src/llmobs/span_processor.js +14 -7
  166. package/packages/dd-trace/src/llmobs/tagger.js +24 -5
  167. package/packages/dd-trace/src/llmobs/util.js +56 -3
  168. package/packages/dd-trace/src/llmobs/writers/spans.js +12 -6
  169. package/packages/dd-trace/src/noop/proxy.js +2 -2
  170. package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +3 -6
  171. package/packages/dd-trace/src/openfeature/configuration_source.js +5 -8
  172. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +5 -1
  173. package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
  174. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +2 -1
  175. package/packages/dd-trace/src/opentracing/span.js +1 -1
  176. package/packages/dd-trace/src/opentracing/span_context.js +1 -1
  177. package/packages/dd-trace/src/plugins/ci_plugin.js +114 -23
  178. package/packages/dd-trace/src/plugins/index.js +2 -0
  179. package/packages/dd-trace/src/plugins/outbound.js +4 -1
  180. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  181. package/packages/dd-trace/src/plugins/tracing.js +1 -0
  182. package/packages/dd-trace/src/plugins/util/ci.js +7 -6
  183. package/packages/dd-trace/src/plugins/util/git.js +1 -1
  184. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +1 -1
  185. package/packages/dd-trace/src/plugins/util/llm.js +2 -2
  186. package/packages/dd-trace/src/plugins/util/tags.js +2 -0
  187. package/packages/dd-trace/src/plugins/util/test.js +38 -12
  188. package/packages/dd-trace/src/plugins/util/url.js +5 -5
  189. package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -1
  190. package/packages/dd-trace/src/plugins/util/web.js +1 -1
  191. package/packages/dd-trace/src/priority_sampler.js +6 -3
  192. package/packages/dd-trace/src/profiling/profilers/poisson.js +1 -1
  193. package/packages/dd-trace/src/profiling/profilers/shared.js +2 -2
  194. package/packages/dd-trace/src/ritm.js +5 -20
  195. package/packages/dd-trace/src/span_processor.js +1 -1
  196. package/packages/dd-trace/src/span_stats.js +22 -4
  197. package/packages/dd-trace/src/startup-log.js +3 -0
  198. package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -1
  199. package/packages/dd-trace/src/util.js +16 -0
  200. package/vendor/dist/@apm-js-collab/code-transformer/index.js +12 -12
  201. package/vendor/dist/pprof-format/index.js +1 -1
  202. package/vendor/dist/source-map/index.js +1 -1
  203. package/ci/test-optimization-validation/command-suitability.js +0 -471
  204. package/ci/test-optimization-validation/init-probe-preload.js +0 -163
  205. package/ci/test-optimization-validation/init-probe.js +0 -246
  206. package/ci/test-optimization-validation/late-initialization.js +0 -63
  207. package/ci/test-optimization-validation/local-command.js +0 -163
  208. package/ci/test-optimization-validation/setup-runner.js +0 -97
  209. package/ci/test-optimization-validation-manifest.schema.json +0 -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
 
@@ -0,0 +1,159 @@
1
+ 'use strict'
2
+
3
+ const { channel, tracingChannel } = require('dc-polyfill')
4
+ const shimmer = require('../../datadog-shimmer')
5
+ const { addHook, getHooks } = require('./helpers/instrument')
6
+
7
+ // `WeakSet` keyed by module exports — replaces the underscored
8
+ // `mod._datadogPatched` flag while keeping dedupe semantics. Mods are kept
9
+ // alive by `require.cache` anyway, so this doesn't add lifetime to anything.
10
+ const patchedMods = new WeakSet()
11
+ const modelBaseURLs = new WeakMap()
12
+ const chatCompletionsModelConstructorCh =
13
+ tracingChannel('orchestrion:@openai/agents-openai:OpenAIChatCompletionsModel_constructor')
14
+
15
+ /**
16
+ * Capture the client base URL before the SDK stores the client in a #private field.
17
+ *
18
+ * @param {{ arguments?: [{ baseURL?: string }], self?: object }} ctx
19
+ */
20
+ function captureChatCompletionsModelBaseURL (ctx) {
21
+ const baseURL = ctx.arguments?.[0]?.baseURL
22
+ if (ctx.self && typeof baseURL === 'string') modelBaseURLs.set(ctx.self, baseURL)
23
+ }
24
+
25
+ chatCompletionsModelConstructorCh.end.subscribe(captureChatCompletionsModelBaseURL)
26
+
27
+ for (const hook of getHooks('@openai/agents-openai')) {
28
+ addHook(hook, moduleExports => moduleExports)
29
+ }
30
+
31
+ // Plugin subscribes to this and registers its TracingProcessor when
32
+ // `@openai/agents` loads. Publishing from here keeps this file free of
33
+ // any cross-package import from the plugin.
34
+ const agentsCoreLoadedCh = channel('apm:openai-agents:agents-core:loaded')
35
+
36
+ // Plugin uses addBind on this channel so that legacyStorage.run(store, fn) wraps
37
+ // the model call — including async iterator advancement for streaming responses.
38
+ // This ensures the active dd-trace span is visible to the openai plugin when it
39
+ // creates its openai.request span, correctly parenting it under the agent span.
40
+ const modelStartCh = channel('apm:openai-agents:model:start')
41
+
42
+ // Tool.invoke runs inside agents-core's public function-span context. Bind the
43
+ // corresponding dd-trace tool span around that public invocation boundary so
44
+ // spans created by user tool code inherit it.
45
+ const toolStartCh = channel('apm:openai-agents:tool:start')
46
+
47
+ // Reference to the loaded @openai/agents module, captured in the first hook
48
+ // so that wrapResponseMethod can call getCurrentSpan() without an additional
49
+ // require (and without triggering n/no-missing-require on agents-core internals).
50
+ let agentsMod
51
+
52
+ // @openai/agents >=0.8.0 moved addTraceProcessor / getCurrentSpan out of the
53
+ // top-level re-exports. The new public surface uses getGlobalTraceProvider():
54
+ // provider.registerProcessor(processor) (replaces addTraceProcessor)
55
+ // provider.getCurrentSpan() (replaces getCurrentSpan)
56
+ // Both APIs are tried so this file works across the full supported version range.
57
+ // The plugin subscriber (index.js) handles processor registration via the channel.
58
+ function getCurrentSpan () {
59
+ if (typeof agentsMod?.getCurrentSpan === 'function') {
60
+ return agentsMod.getCurrentSpan()
61
+ }
62
+ if (typeof agentsMod?.getGlobalTraceProvider === 'function') {
63
+ return agentsMod.getGlobalTraceProvider().getCurrentSpan()
64
+ }
65
+ }
66
+
67
+ function getCurrentSpanId () {
68
+ return getCurrentSpan()?.spanId
69
+ }
70
+
71
+ addHook({ name: '@openai/agents', versions: ['>=0.7.0'] }, (mod) => {
72
+ if (patchedMods.has(mod)) return mod
73
+ if (typeof mod?.addTraceProcessor !== 'function' && typeof mod?.getGlobalTraceProvider !== 'function') return mod
74
+ patchedMods.add(mod)
75
+ agentsMod = mod
76
+ if (typeof mod.tool === 'function') {
77
+ shimmer.wrap(mod, 'tool', wrapToolFactory, { replaceGetter: true })
78
+ }
79
+ agentsCoreLoadedCh.publish({ mod })
80
+ return mod
81
+ })
82
+
83
+ function wrapToolFactory (original) {
84
+ return function (...args) {
85
+ const tool = original.apply(this, args)
86
+ if (typeof tool?.invoke === 'function') {
87
+ shimmer.wrap(tool, 'invoke', wrapToolInvoke)
88
+ }
89
+ return tool
90
+ }
91
+ }
92
+
93
+ function wrapToolInvoke (original) {
94
+ return function (...args) {
95
+ const agentsCoreSpan = getCurrentSpan()
96
+ return toolStartCh.runStores({ agentsCoreSpan }, () => original.apply(this, args))
97
+ }
98
+ }
99
+
100
+ function wrapResponseMethod (original) {
101
+ return function (...args) {
102
+ const agentsCoreSpanId = getCurrentSpanId()
103
+ const baseURL = getClientBaseURL(this)
104
+ return modelStartCh.runStores({ agentsCoreSpanId, baseURL }, () => original.apply(this, args))
105
+ }
106
+ }
107
+
108
+ function wrapStreamedResponseMethod (original) {
109
+ return function (...args) {
110
+ const agentsCoreSpanId = getCurrentSpanId()
111
+ const baseURL = getClientBaseURL(this)
112
+ const context = { agentsCoreSpanId, baseURL }
113
+ const iterator = modelStartCh.runStores(context, () => original.apply(this, args))
114
+ return wrapAsyncIterator(iterator, context)
115
+ }
116
+ }
117
+
118
+ function getClientBaseURL (model) {
119
+ return model?.client?.baseURL ?? model?._client?.baseURL ?? modelBaseURLs.get(model)
120
+ }
121
+
122
+ function wrapAsyncIterator (iterator, context) {
123
+ if (!iterator || typeof iterator !== 'object') return iterator
124
+
125
+ return {
126
+ next () {
127
+ return modelStartCh.runStores(context, () => iterator.next.apply(iterator, arguments))
128
+ },
129
+ throw () {
130
+ if (typeof iterator.throw !== 'function') return Promise.reject(arguments[0])
131
+ return modelStartCh.runStores(context, () => iterator.throw.apply(iterator, arguments))
132
+ },
133
+ return () {
134
+ if (typeof iterator.return !== 'function') return Promise.resolve({ done: true, value: arguments[0] })
135
+ return modelStartCh.runStores(context, () => iterator.return.apply(iterator, arguments))
136
+ },
137
+ [Symbol.asyncIterator] () {
138
+ return this
139
+ },
140
+ }
141
+ }
142
+
143
+ addHook({ name: '@openai/agents-openai', versions: ['>=0.7.0'] }, (mod) => {
144
+ if (patchedMods.has(mod)) return mod
145
+ const responseProto = mod?.OpenAIResponsesModel?.prototype
146
+ const chatCompletionsProto = mod?.OpenAIChatCompletionsModel?.prototype
147
+ if (!responseProto && !chatCompletionsProto) return mod
148
+
149
+ patchedMods.add(mod)
150
+ for (const proto of [responseProto, chatCompletionsProto]) {
151
+ if (typeof proto?.getResponse === 'function') {
152
+ shimmer.wrap(proto, 'getResponse', wrapResponseMethod)
153
+ }
154
+ if (typeof proto?.getStreamedResponse === 'function') {
155
+ shimmer.wrap(proto, 'getStreamedResponse', wrapStreamedResponseMethod)
156
+ }
157
+ }
158
+ return mod
159
+ })