dd-trace 6.20.1 → 6.21.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 (43) hide show
  1. package/index.d.ts +91 -3
  2. package/package.json +2 -2
  3. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +57 -4
  4. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
  5. package/packages/datadog-instrumentations/src/jest.js +119 -1
  6. package/packages/datadog-instrumentations/src/mocha/utils.js +124 -36
  7. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +61 -34
  8. package/packages/datadog-instrumentations/src/mocha/worker.js +6 -3
  9. package/packages/datadog-instrumentations/src/playwright.js +3 -0
  10. package/packages/datadog-instrumentations/src/vitest-main.js +0 -2
  11. package/packages/datadog-instrumentations/src/webdriverio.js +115 -15
  12. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +17 -0
  13. package/packages/datadog-plugin-mocha/src/index.js +216 -29
  14. package/packages/datadog-plugin-mocha/src/webdriverio-video.js +449 -0
  15. package/packages/dd-trace/src/debugger/constants.js +1 -0
  16. package/packages/dd-trace/src/debugger/devtools_client/index.js +5 -3
  17. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +17 -12
  18. package/packages/dd-trace/src/debugger/devtools_client/send.js +6 -2
  19. package/packages/dd-trace/src/debugger/devtools_client/worker-error.js +21 -0
  20. package/packages/dd-trace/src/debugger/index.js +24 -2
  21. package/packages/dd-trace/src/debugger/probe_sampler.js +108 -11
  22. package/packages/dd-trace/src/exporters/common/request.js +1 -0
  23. package/packages/dd-trace/src/exporters/common/url.js +1 -1
  24. package/packages/dd-trace/src/id.js +1 -4
  25. package/packages/dd-trace/src/llmobs/agent-manifest.js +296 -0
  26. package/packages/dd-trace/src/llmobs/constants/tags.js +1 -0
  27. package/packages/dd-trace/src/llmobs/sdk.js +23 -6
  28. package/packages/dd-trace/src/llmobs/span_processor.js +17 -6
  29. package/packages/dd-trace/src/llmobs/tagger.js +54 -1
  30. package/packages/dd-trace/src/llmobs/writers/base.js +3 -1
  31. package/packages/dd-trace/src/llmobs/writers/util.js +3 -1
  32. package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +30 -1
  33. package/packages/dd-trace/src/openfeature/configuration_source.js +17 -2
  34. package/packages/dd-trace/src/openfeature/debug-logging-hook.js +25 -0
  35. package/packages/dd-trace/src/openfeature/flagging_provider.js +29 -0
  36. package/packages/dd-trace/src/openfeature/remote_config.js +9 -0
  37. package/packages/dd-trace/src/openfeature/writers/util.js +43 -4
  38. package/packages/dd-trace/src/opentelemetry/resource-attributes.js +1 -1
  39. package/packages/dd-trace/src/otel-thread-ctx.js +2 -2
  40. package/packages/dd-trace/src/profiling/profilers/wall.js +4 -10
  41. package/packages/dd-trace/src/proxy.js +2 -0
  42. package/packages/dd-trace/src/serverless.js +5 -0
  43. package/packages/dd-trace/src/storage-channels.js +112 -46
@@ -6,6 +6,7 @@ const realSetTimeout = setTimeout
6
6
  const { AsyncResource } = require('node:async_hooks')
7
7
  const { fileURLToPath } = require('node:url')
8
8
 
9
+ const { createFinalFlushTimeoutError, FINAL_FLUSH_TIMEOUT } = require('../../dd-trace/src/ci-visibility/final-flush')
9
10
  const { EMPTY_EFD_RETRY_POLICY } = require('../../dd-trace/src/ci-visibility/efd-retry-policy')
10
11
  const { RUM_TEST_EXECUTION_ID_COOKIE_NAME } = require('../../dd-trace/src/ci-visibility/rum')
11
12
  const { getEnvironmentVariable, getValueFromEnvSources } = require('../../dd-trace/src/config/helper')
@@ -30,6 +31,9 @@ const {
30
31
  SCREENSHOT_UPLOAD_RESPONSE,
31
32
  sendWebdriverioWorkerMessage,
32
33
  SUITE_FINISH,
34
+ VIDEO_UPLOAD,
35
+ VIDEO_UPLOAD_FLUSH,
36
+ VIDEO_UPLOAD_RESPONSE,
33
37
  WEBDRIVERIO_WORKER_ENV,
34
38
  WEBDRIVERIO_WORKER_EVENT,
35
39
  WEBDRIVERIO_WORKER_ORIGIN,
@@ -52,6 +56,8 @@ const logSubmissionFlushCh = channel('ci:log-submission:flush')
52
56
  const modifiedFilesCh = channel('ci:mocha:modified-files')
53
57
  const screenshotCapabilitiesCh = channel('ci:webdriverio:screenshot:capabilities')
54
58
  const screenshotUploadCh = channel('ci:webdriverio:screenshot:upload')
59
+ const videoCapabilitiesCh = channel('ci:webdriverio:video:capabilities')
60
+ const videoUploadCh = channel('ci:webdriverio:video:upload')
55
61
  const testManagementTestsCh = channel('ci:mocha:test-management-tests')
56
62
  const workerConfigurationCh = channel('ci:mocha:worker:configuration')
57
63
  const workerReportLogsCh = channel('ci:mocha:worker-report:logs')
@@ -59,8 +65,10 @@ const workerReportTelemetryCh = channel('ci:mocha:worker-report:telemetry')
59
65
  const workerReportTraceCh = channel('ci:mocha:worker-report:trace')
60
66
 
61
67
  const jasmineAdapterInitCh = tracingChannel('orchestrion:@wdio/jasmine-framework:JasmineAdapter_init')
68
+ const mochaAdapterInitCh = tracingChannel('orchestrion:@wdio/mocha-framework:MochaAdapter_init')
62
69
  const baseReporterWaitForSyncCh = tracingChannel('orchestrion:@wdio/runner:BaseReporter_waitForSync')
63
70
  const runnerRunCh = tracingChannel('orchestrion:@wdio/runner:Runner_run')
71
+ const runnerStartSessionCh = tracingChannel('orchestrion:@wdio/runner:Runner_startSession')
64
72
  const executeAsyncCh = tracingChannel('orchestrion:@wdio/utils:executeAsync')
65
73
  const launcherStartInstanceCh = tracingChannel('orchestrion:@wdio/cli:Launcher_startInstance')
66
74
  const configParserShardCh = tracingChannel('orchestrion:@wdio/config:ConfigParser_shard')
@@ -80,7 +88,7 @@ const TEST_FRAMEWORK = 'webdriverio'
80
88
  const SET_RUM_COOKIE_SCRIPT = 'cookie => { globalThis.document.cookie = cookie }'
81
89
  const RUM_FLUSH_WAIT_TIME = getValueFromEnvSources('DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS')
82
90
  const isWebdriverioWorker = !!getEnvironmentVariable(WEBDRIVERIO_WORKER_ENV)
83
- let jasmineWorkerRequestId = 0
91
+ let workerRequestId = 0
84
92
 
85
93
  const loadCh = channel('dd-trace:instrumentation:load')
86
94
  if (loadCh.hasSubscribers) {
@@ -957,6 +965,8 @@ function waitForRumTestStart (context) {
957
965
  * @property {boolean|undefined} hasTests
958
966
  * @property {number|undefined} exitCode
959
967
  * @property {number|undefined} retries
968
+ * @property {AbortController|undefined} videoController
969
+ * @property {ReturnType<typeof setTimeout>|undefined} videoFlushTimer
960
970
  */
961
971
 
962
972
  /**
@@ -1019,6 +1029,7 @@ function createWorkerConfiguration () {
1019
1029
  isItrEnabled: false,
1020
1030
  isKnownTestsEnabled: false,
1021
1031
  isTestFailureScreenshotsEnabled: false,
1032
+ isTestFailureVideosEnabled: false,
1022
1033
  isSuitesSkippingEnabled: false,
1023
1034
  isTestDynamicInstrumentationEnabled: false,
1024
1035
  isTestManagementTestsEnabled: false,
@@ -1032,22 +1043,23 @@ function createWorkerConfiguration () {
1032
1043
  }
1033
1044
 
1034
1045
  /**
1035
- * Configures a Jasmine worker and waits for its coordinator-owned optimization settings.
1046
+ * Configures a framework worker before browser session creation using its coordinator-owned settings.
1036
1047
  *
1037
1048
  * @param {{_jrunner?: {env?: {addReporter?: (reporter: object) => void}}, _specs?: string[]}} adapter
1038
1049
  * @param {{
1039
1050
  * resolveCallback?: (onDone: () => void) => void,
1040
1051
  * rejectCallback?: (onDone: () => void) => void
1041
1052
  * }} context
1053
+ * @param {'mocha'|'jasmine'} testFrameworkAdapter
1042
1054
  */
1043
- function initializeJasmineWorker (adapter, context) {
1055
+ function initializeFrameworkWorker (adapter, context, testFrameworkAdapter) {
1044
1056
  if (!isWebdriverioWorker) {
1045
1057
  return
1046
1058
  }
1047
1059
 
1048
1060
  const specs = adapter?._specs || []
1049
1061
 
1050
- if (jasmineDoneCh.hasSubscribers) {
1062
+ if (testFrameworkAdapter === JASMINE_FRAMEWORK_ADAPTER && jasmineDoneCh.hasSubscribers) {
1051
1063
  adapter?._jrunner?.env?.addReporter?.({
1052
1064
  /**
1053
1065
  * Publishes run-level failures that are absent from suiteDone.
@@ -1066,11 +1078,11 @@ function initializeJasmineWorker (adapter, context) {
1066
1078
  * @param {() => void} onDone
1067
1079
  */
1068
1080
  const waitForCoordinator = onDone => {
1069
- const requestId = `${process.pid}-${++jasmineWorkerRequestId}`
1081
+ const requestId = `${process.pid}-${++workerRequestId}`
1070
1082
  let finished = false
1071
1083
 
1072
1084
  /**
1073
- * Releases Jasmine initialization exactly once.
1085
+ * Releases framework initialization exactly once.
1074
1086
  *
1075
1087
  * @param {object} [configuration]
1076
1088
  */
@@ -1087,7 +1099,7 @@ function initializeJasmineWorker (adapter, context) {
1087
1099
  repositoryRoot: configuration.repositoryRoot,
1088
1100
  specs,
1089
1101
  testFramework: TEST_FRAMEWORK,
1090
- testFrameworkAdapter: JASMINE_FRAMEWORK_ADAPTER,
1102
+ testFrameworkAdapter,
1091
1103
  })
1092
1104
  onDone()
1093
1105
  }
@@ -1111,7 +1123,7 @@ function initializeJasmineWorker (adapter, context) {
1111
1123
  name: WORKER_READY,
1112
1124
  content: {
1113
1125
  requestId,
1114
- testFrameworkAdapter: JASMINE_FRAMEWORK_ADAPTER,
1126
+ testFrameworkAdapter,
1115
1127
  },
1116
1128
  }, error => {
1117
1129
  if (error) {
@@ -1327,6 +1339,9 @@ function configureCoordinator (state, response) {
1327
1339
  const screenshotCapabilities = {}
1328
1340
  screenshotCapabilitiesCh.publish(screenshotCapabilities)
1329
1341
  configuration.isTestFailureScreenshotsEnabled = screenshotCapabilities.enabled === true
1342
+ const videoCapabilities = {}
1343
+ videoCapabilitiesCh.publish(videoCapabilities)
1344
+ configuration.isTestFailureVideosEnabled = videoCapabilities.enabled === true
1330
1345
 
1331
1346
  configuration.repositoryRoot = repositoryRoot
1332
1347
  if (err || !libraryConfig) {
@@ -1667,6 +1682,13 @@ function handleWorkerMessage (state, workerRecord, message) {
1667
1682
  return
1668
1683
  }
1669
1684
 
1685
+ if (message.name === WORKER_READY || message.name === CONFIGURATION_REQUEST) {
1686
+ // Watch mode reuses the worker. Keep old requests cancelled while the new run gets a fresh signal.
1687
+ clearTimeout(workerRecord.videoFlushTimer)
1688
+ workerRecord.videoFlushTimer = undefined
1689
+ workerRecord.videoController?.abort(new Error('WebdriverIO video run completed'))
1690
+ workerRecord.videoController = undefined
1691
+ }
1670
1692
  if (message.name === WORKER_READY) {
1671
1693
  initializeCoordinator(state, configuration => {
1672
1694
  if (state.testFrameworkAdapter === JASMINE_FRAMEWORK_ADAPTER) {
@@ -1687,23 +1709,49 @@ function handleWorkerMessage (state, workerRecord, message) {
1687
1709
  handleConfigurationRequest(state, workerRecord, message)
1688
1710
  return
1689
1711
  }
1690
- if (message.name === SCREENSHOT_UPLOAD) {
1712
+ if (message.name === VIDEO_UPLOAD_FLUSH) {
1713
+ if (message.content?.complete) {
1714
+ clearTimeout(workerRecord.videoFlushTimer)
1715
+ workerRecord.videoFlushTimer = undefined
1716
+ // The worker may have reached its own deadline before the coordinator's timer fires.
1717
+ workerRecord.videoController?.abort(new Error('WebdriverIO video flush completed'))
1718
+ return
1719
+ }
1720
+ const controller = workerRecord.videoController ??= new AbortController()
1721
+ workerRecord.videoFlushTimer ??= setTimeout(() => {
1722
+ controller.abort(createFinalFlushTimeoutError())
1723
+ }, FINAL_FLUSH_TIMEOUT)
1724
+ workerRecord.videoFlushTimer.unref?.()
1725
+ return
1726
+ }
1727
+ if (message.name === SCREENSHOT_UPLOAD || message.name === VIDEO_UPLOAD) {
1728
+ const isVideo = message.name === VIDEO_UPLOAD
1729
+ const uploadCh = isVideo ? videoUploadCh : screenshotUploadCh
1691
1730
  const { requestId, ...content } = message.content || {}
1692
1731
  const respond = (error) => sendWorkerMessage(workerRecord, {
1693
1732
  origin: 'datadog',
1694
- name: SCREENSHOT_UPLOAD_RESPONSE,
1733
+ name: isVideo ? VIDEO_UPLOAD_RESPONSE : SCREENSHOT_UPLOAD_RESPONSE,
1695
1734
  content: {
1696
1735
  error: error?.message,
1697
1736
  requestId,
1698
1737
  },
1699
1738
  })
1700
- if (!requestId || !screenshotUploadCh.hasSubscribers) {
1701
- respond(new Error('WebdriverIO screenshot upload is not available'))
1739
+ if (!requestId || !uploadCh.hasSubscribers) {
1740
+ respond(new Error('WebdriverIO media upload is not available'))
1702
1741
  } else {
1703
1742
  try {
1704
- screenshotUploadCh.publish({ ...content, onDone: respond })
1743
+ let signal
1744
+ if (isVideo) {
1745
+ workerRecord.videoController ??= new AbortController()
1746
+ signal = workerRecord.videoController.signal
1747
+ }
1748
+ uploadCh.publish({
1749
+ ...content,
1750
+ signal,
1751
+ onDone: respond,
1752
+ })
1705
1753
  } catch (error) {
1706
- log.error('WebdriverIO screenshot upload error: %s', error?.message || String(error))
1754
+ log.error('WebdriverIO media upload error: %s', error?.message || String(error))
1707
1755
  respond(error)
1708
1756
  }
1709
1757
  }
@@ -1732,6 +1780,8 @@ function handleWorkerMessage (state, workerRecord, message) {
1732
1780
  * @param {object} exit
1733
1781
  */
1734
1782
  function handleWorkerExit (state, workerRecord, exit) {
1783
+ clearTimeout(workerRecord.videoFlushTimer)
1784
+ workerRecord.videoController?.abort(new Error('WebdriverIO worker exited during video upload'))
1735
1785
  state.activeWorkers--
1736
1786
  workerRecord.exitCode = exit.exitCode
1737
1787
  workerRecord.retries = exit.retries
@@ -1768,6 +1818,8 @@ function registerWorker (state, worker, specs) {
1768
1818
  hasTests: undefined,
1769
1819
  exitCode: undefined,
1770
1820
  retries: undefined,
1821
+ videoController: undefined,
1822
+ videoFlushTimer: undefined,
1771
1823
  }
1772
1824
  state.activeWorkers++
1773
1825
  if (state.activeWorkers > state.maxActiveWorkers) {
@@ -1917,6 +1969,44 @@ function waitForRumCleanupBeforeSessionEnd (context) {
1917
1969
  context.rumCleanupCallback = cleanupAllRumBrowsers
1918
1970
  }
1919
1971
 
1972
+ /**
1973
+ * Opted-in background screenshots must not dismiss dialogs. Preserve explicit customer policies.
1974
+ *
1975
+ * @param {{
1976
+ * arguments: [{framework?: string, sessionId?: string, automationProtocol?: string}, object],
1977
+ * self: {_isMultiremote?: boolean, _isMultiRemote?: boolean}
1978
+ * }} context
1979
+ */
1980
+ function configureVideoPromptBehavior (context) {
1981
+ if (!testFinishCh.hasSubscribers || getValueFromEnvSources('DD_TEST_FAILURE_VIDEOS_ENABLED', true) !== true) return
1982
+ const [config, capabilities] = context.arguments
1983
+ if (!SUPPORTED_FRAMEWORK_ADAPTERS.has(config?.framework) || 'sessionId' in config ||
1984
+ config.automationProtocol === './protocol-stub.js') return
1985
+ try {
1986
+ const videoCapabilities = {}
1987
+ videoCapabilitiesCh.publish(videoCapabilities)
1988
+ if (!videoCapabilities.enabled) return
1989
+ /** @param {{unhandledPromptBehavior?: unknown}} caps - Capabilities for one browser session. */
1990
+ const withDefault = caps => caps && caps.unhandledPromptBehavior === undefined
1991
+ ? { ...caps, unhandledPromptBehavior: 'ignore' }
1992
+ : caps
1993
+ // Prepare every session before replacing the argument, so a read failure cannot leave partial defaults.
1994
+ context.arguments[1] = (context.self._isMultiremote || context.self._isMultiRemote)
1995
+ ? Object.fromEntries(Object.entries(capabilities).map(([name, options]) => {
1996
+ const session = { ...options }
1997
+ session.capabilities = withDefault(session.capabilities)
1998
+ return [name, session]
1999
+ }))
2000
+ : withDefault(capabilities)
2001
+ } catch (error) {
2002
+ log.error('Error configuring WebdriverIO video prompt handling: %s', error?.message || String(error))
2003
+ }
2004
+ }
2005
+
2006
+ runnerStartSessionCh.start.subscribe(
2007
+ /** @type {import('node:diagnostics_channel').ChannelListener} */ (configureVideoPromptBehavior)
2008
+ )
2009
+
1920
2010
  baseReporterWaitForSyncCh.asyncEnd.subscribe(
1921
2011
  /** @type {import('node:diagnostics_channel').ChannelListener} */ (waitForLogSubmissionAtWorkerExit)
1922
2012
  )
@@ -1974,7 +2064,17 @@ executeAsyncCh.subscribe({
1974
2064
  // @ts-expect-error See the partial tracing-channel subscriber above.
1975
2065
  jasmineAdapterInitCh.subscribe({
1976
2066
  asyncEnd (context) {
1977
- initializeJasmineWorker(context.result || context.self, context)
2067
+ initializeFrameworkWorker(context.result || context.self, context, JASMINE_FRAMEWORK_ADAPTER)
2068
+ },
2069
+ })
2070
+
2071
+ // @ts-expect-error See the partial tracing-channel subscriber above.
2072
+ mochaAdapterInitCh.subscribe({
2073
+ asyncEnd (context) {
2074
+ // Mocha normally receives worker settings in run(), after WebdriverIO creates its browser session.
2075
+ if (getValueFromEnvSources('DD_TEST_FAILURE_VIDEOS_ENABLED', true) === true) {
2076
+ initializeFrameworkWorker(context.result || context.self, context, MOCHA_FRAMEWORK_ADAPTER)
2077
+ }
1978
2078
  },
1979
2079
  })
1980
2080
 
@@ -51,6 +51,8 @@ const {
51
51
  TEST_ITR_UNSKIPPABLE,
52
52
  TEST_ITR_FORCED_RUN,
53
53
  TEST_ITR_SKIPPING_ENABLED,
54
+ TEST_ITR_SKIPPING_COUNT,
55
+ TEST_ITR_TESTS_SKIPPED,
54
56
  ITR_CORRELATION_ID,
55
57
  TEST_SOURCE_FILE,
56
58
  TEST_IS_NEW,
@@ -1045,6 +1047,11 @@ class CypressPlugin {
1045
1047
  ...this.getSessionItrSkippingEnabledTags(),
1046
1048
  }
1047
1049
 
1050
+ if (this.isItrEnabled) {
1051
+ testSuiteSpanMetadata[TEST_ITR_SKIPPING_COUNT] = 0
1052
+ testSuiteSpanMetadata[TEST_ITR_TESTS_SKIPPED] = 'false'
1053
+ }
1054
+
1048
1055
  this.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
1049
1056
 
1050
1057
  if (testSuiteAbsolutePath) {
@@ -2092,6 +2099,16 @@ class CypressPlugin {
2092
2099
  if (!this.skippedTestIds.has(skippedTestId)) {
2093
2100
  this.skippedTestIds.add(skippedTestId)
2094
2101
  this.skippedTests.push(test)
2102
+ const testSuiteSpan = this.#pendingTestSuiteSpans.get(testSuite)
2103
+ if (this.isItrEnabled && testSuiteSpan) {
2104
+ try {
2105
+ const count = testSuiteSpan.context().getTag(TEST_ITR_SKIPPING_COUNT) || 0
2106
+ testSuiteSpan.setTag(TEST_ITR_SKIPPING_COUNT, count + 1)
2107
+ testSuiteSpan.setTag(TEST_ITR_TESTS_SKIPPED, 'true')
2108
+ } catch (error) {
2109
+ log.error('Could not report Test Impact Analysis skips for suite %s: %s', testSuite, error)
2110
+ }
2111
+ }
2095
2112
  }
2096
2113
  this.isTestsSkipped = true
2097
2114
  return { shouldSkip: true }