dd-trace 5.89.0 → 5.91.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 (71) hide show
  1. package/LICENSE-3rdparty.csv +0 -3
  2. package/index.d.ts +38 -0
  3. package/package.json +12 -11
  4. package/packages/datadog-instrumentations/src/azure-durable-functions.js +75 -0
  5. package/packages/datadog-instrumentations/src/cucumber.js +58 -4
  6. package/packages/datadog-instrumentations/src/elasticsearch.js +12 -3
  7. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  8. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  9. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +30 -0
  10. package/packages/datadog-instrumentations/src/jest.js +31 -2
  11. package/packages/datadog-instrumentations/src/langgraph.js +7 -0
  12. package/packages/datadog-instrumentations/src/mocha/main.js +41 -12
  13. package/packages/datadog-instrumentations/src/mocha/utils.js +6 -1
  14. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -4
  15. package/packages/datadog-instrumentations/src/playwright.js +20 -2
  16. package/packages/datadog-instrumentations/src/prisma.js +4 -2
  17. package/packages/datadog-instrumentations/src/vitest.js +69 -24
  18. package/packages/datadog-plugin-apollo/src/gateway/execute.js +8 -0
  19. package/packages/datadog-plugin-apollo/src/gateway/fetch.js +5 -0
  20. package/packages/datadog-plugin-apollo/src/gateway/plan.js +8 -0
  21. package/packages/datadog-plugin-apollo/src/gateway/postprocessing.js +5 -0
  22. package/packages/datadog-plugin-apollo/src/gateway/request.js +4 -3
  23. package/packages/datadog-plugin-apollo/src/gateway/validate.js +4 -3
  24. package/packages/datadog-plugin-apollo/src/index.js +28 -0
  25. package/packages/datadog-plugin-azure-durable-functions/src/index.js +49 -0
  26. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +12 -2
  27. package/packages/datadog-plugin-cypress/src/support.js +5 -7
  28. package/packages/datadog-plugin-jest/src/index.js +6 -0
  29. package/packages/datadog-plugin-langgraph/src/index.js +24 -0
  30. package/packages/datadog-plugin-langgraph/src/stream.js +41 -0
  31. package/packages/datadog-plugin-playwright/src/index.js +35 -8
  32. package/packages/dd-trace/src/aiguard/noop.js +1 -1
  33. package/packages/dd-trace/src/aiguard/sdk.js +14 -5
  34. package/packages/dd-trace/src/appsec/api_security_sampler.js +22 -1
  35. package/packages/dd-trace/src/appsec/index.js +11 -1
  36. package/packages/dd-trace/src/appsec/reporter.js +28 -11
  37. package/packages/dd-trace/src/appsec/waf/index.js +1 -1
  38. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -4
  39. package/packages/dd-trace/src/config/defaults.js +1 -0
  40. package/packages/dd-trace/src/config/index.js +9 -1
  41. package/packages/dd-trace/src/config/supported-configurations.json +14 -0
  42. package/packages/dd-trace/src/constants.js +2 -0
  43. package/packages/dd-trace/src/crashtracking/crashtracker.js +1 -1
  44. package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -0
  45. package/packages/dd-trace/src/dogstatsd.js +1 -0
  46. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +1 -8
  47. package/packages/dd-trace/src/encode/agentless-json.js +67 -22
  48. package/packages/dd-trace/src/exporters/agentless/index.js +58 -15
  49. package/packages/dd-trace/src/exporters/agentless/writer.js +35 -18
  50. package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
  51. package/packages/dd-trace/src/llmobs/plugins/anthropic.js +9 -0
  52. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chain.js +11 -0
  53. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +2 -0
  54. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +114 -0
  55. package/packages/dd-trace/src/llmobs/tagger.js +8 -0
  56. package/packages/dd-trace/src/opentracing/propagation/text_map.js +1 -0
  57. package/packages/dd-trace/src/plugins/apollo.js +7 -2
  58. package/packages/dd-trace/src/plugins/ci_plugin.js +2 -2
  59. package/packages/dd-trace/src/plugins/index.js +2 -0
  60. package/packages/dd-trace/src/plugins/util/ci.js +95 -3
  61. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +36 -2
  62. package/packages/dd-trace/src/plugins/util/test.js +7 -10
  63. package/packages/dd-trace/src/plugins/util/web.js +31 -11
  64. package/packages/dd-trace/src/priority_sampler.js +20 -2
  65. package/packages/dd-trace/src/process-tags/index.js +41 -34
  66. package/packages/dd-trace/src/profiling/profilers/wall.js +9 -1
  67. package/packages/dd-trace/src/proxy.js +4 -0
  68. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +7 -0
  69. package/packages/dd-trace/src/service-naming/schemas/v0/serverless.js +4 -0
  70. package/packages/dd-trace/src/service-naming/schemas/v1/serverless.js +4 -0
  71. package/packages/dd-trace/src/standalone/product.js +2 -1
@@ -140,7 +140,6 @@ function runnableWrapper (RunnablePackage, libraryConfig) {
140
140
  if (!testFinishCh.hasSubscribers) {
141
141
  return run.apply(this, arguments)
142
142
  }
143
- // Flaky test retries does not work in parallel mode
144
143
  if (libraryConfig?.isFlakyTestRetriesEnabled) {
145
144
  this.retries(libraryConfig?.flakyTestRetriesCount)
146
145
  }
@@ -289,6 +288,12 @@ function getOnTestEndHandler (config) {
289
288
  hasFailedAllRetries = true
290
289
  }
291
290
 
291
+ // ATR: set hasFailedAllRetries when all auto test retries were exhausted and every attempt failed
292
+ if (config.isFlakyTestRetriesEnabled && !test._ddIsAttemptToFix && !test._ddIsEfdRetry &&
293
+ getIsLastRetry(test) && testStatuses.every(status => status === 'fail')) {
294
+ hasFailedAllRetries = true
295
+ }
296
+
292
297
  const isAttemptToFixRetry = test._ddIsAttemptToFix && testStatuses.length > 1
293
298
  const isAtrRetry = config.isFlakyTestRetriesEnabled &&
294
299
  !test._ddIsAttemptToFix &&
@@ -10,6 +10,7 @@ const {
10
10
  getOnHookEndHandler,
11
11
  getOnFailHandler,
12
12
  getOnPendingHandler,
13
+ getOnTestRetryHandler,
13
14
  getRunTestsWrapper,
14
15
  } = require('./utils')
15
16
  require('./common')
@@ -42,12 +43,18 @@ addHook({
42
43
  }
43
44
  if (this.options._ddIsTestManagementTestsEnabled) {
44
45
  config.isTestManagementTestsEnabled = true
45
- // TODO: attempt to fix does not work in parallel mode yet
46
- // config.testManagementAttemptToFixRetries = this.options._ddTestManagementAttemptToFixRetries
46
+ config.testManagementAttemptToFixRetries = this.options._ddTestManagementAttemptToFixRetries
47
47
  config.testManagementTests = this.options._ddTestManagementTests
48
48
  delete this.options._ddIsTestManagementTestsEnabled
49
+ delete this.options._ddTestManagementAttemptToFixRetries
49
50
  delete this.options._ddTestManagementTests
50
51
  }
52
+ if (this.options._ddIsFlakyTestRetriesEnabled) {
53
+ config.isFlakyTestRetriesEnabled = true
54
+ config.flakyTestRetriesCount = this.options._ddFlakyTestRetriesCount
55
+ delete this.options._ddIsFlakyTestRetriesEnabled
56
+ delete this.options._ddFlakyTestRetriesCount
57
+ }
51
58
  return run.apply(this, arguments)
52
59
  })
53
60
 
@@ -74,6 +81,8 @@ addHook({
74
81
 
75
82
  this.on('test end', getOnTestEndHandler(config))
76
83
 
84
+ this.on('retry', getOnTestRetryHandler(config))
85
+
77
86
  // If the hook passes, 'hook end' will be emitted. Otherwise, 'fail' will be emitted
78
87
  this.on('hook end', getOnHookEndHandler())
79
88
 
@@ -92,5 +101,4 @@ addHook({
92
101
  name: 'mocha',
93
102
  versions: ['>=5.2.0'],
94
103
  file: 'lib/runnable.js',
95
- }, runnableWrapper)
96
- // TODO: parallel mode does not support flaky test retries, so no library config is passed.
104
+ }, (runnablePackage) => runnableWrapper(runnablePackage, config))
@@ -302,6 +302,7 @@ function testBeginHandler (test, browserName, shouldCreateTestSpan) {
302
302
  const {
303
303
  _requireFile: testSuiteAbsolutePath,
304
304
  location: {
305
+ file: testSourceFileAbsolutePath,
305
306
  line: testSourceLine,
306
307
  },
307
308
  _type,
@@ -319,7 +320,7 @@ function testBeginHandler (test, browserName, shouldCreateTestSpan) {
319
320
 
320
321
  if (isNewTestSuite) {
321
322
  startedSuites.push(testSuiteAbsolutePath)
322
- const testSuiteCtx = { testSuiteAbsolutePath }
323
+ const testSuiteCtx = { testSuiteAbsolutePath, testSourceFileAbsolutePath }
323
324
  testSuiteToCtx.set(testSuiteAbsolutePath, testSuiteCtx)
324
325
  testSuiteStartCh.runStores(testSuiteCtx, () => {})
325
326
  }
@@ -335,6 +336,7 @@ function testBeginHandler (test, browserName, shouldCreateTestSpan) {
335
336
  const testCtx = {
336
337
  testName,
337
338
  testSuiteAbsolutePath,
339
+ testSourceFileAbsolutePath,
338
340
  testSourceLine,
339
341
  browserName,
340
342
  isDisabled: test._ddIsDisabled,
@@ -404,6 +406,15 @@ function testEndHandler ({
404
406
  test._ddHasFailedAllRetries = true
405
407
  }
406
408
 
409
+ // ATR: set _ddHasFailedAllRetries when all auto test retries were exhausted and every attempt failed
410
+ if (isFlakyTestRetriesEnabled && !testProperties.attemptToFix && !test._ddIsEfdRetry &&
411
+ !(test._ddIsNew || test._ddIsModified) &&
412
+ flakyTestRetriesCount != null && flakyTestRetriesCount > 0 &&
413
+ testStatuses.length === flakyTestRetriesCount + 1 &&
414
+ testStatuses.every(status => status === 'fail')) {
415
+ test._ddHasFailedAllRetries = true
416
+ }
417
+
407
418
  // this handles tests that do not go through the worker process (because they're skipped)
408
419
  if (shouldCreateTestSpan) {
409
420
  const testResult = results.at(-1)
@@ -459,6 +470,7 @@ function testEndHandler ({
459
470
  testSkipCh.publish({
460
471
  testName: getTestFullname(test),
461
472
  testSuiteAbsolutePath,
473
+ testSourceFileAbsolutePath: test.location.file,
462
474
  testSourceLine: test.location.line,
463
475
  browserName,
464
476
  isNew: test._ddIsNew,
@@ -1144,6 +1156,7 @@ addHook({
1144
1156
  const {
1145
1157
  _requireFile: testSuiteAbsolutePath,
1146
1158
  location: {
1159
+ file: testSourceFileAbsolutePath,
1147
1160
  line: testSourceLine,
1148
1161
  },
1149
1162
  } = test
@@ -1159,6 +1172,7 @@ addHook({
1159
1172
  const testCtx = {
1160
1173
  testName,
1161
1174
  testSuiteAbsolutePath,
1175
+ testSourceFileAbsolutePath,
1162
1176
  testSourceLine,
1163
1177
  browserName,
1164
1178
  }
@@ -1322,7 +1336,10 @@ function generateSummaryWrapper (generateSummary) {
1322
1336
  if (didNotRun) {
1323
1337
  const {
1324
1338
  _requireFile: testSuiteAbsolutePath,
1325
- location: { line: testSourceLine },
1339
+ location: {
1340
+ file: testSourceFileAbsolutePath,
1341
+ line: testSourceLine,
1342
+ },
1326
1343
  _ddIsNew: isNew,
1327
1344
  _ddIsDisabled: isDisabled,
1328
1345
  _ddIsModified: isModified,
@@ -1333,6 +1350,7 @@ function generateSummaryWrapper (generateSummary) {
1333
1350
  testSkipCh.publish({
1334
1351
  testName: getTestFullname(test),
1335
1352
  testSuiteAbsolutePath,
1353
+ testSourceFileAbsolutePath,
1336
1354
  testSourceLine,
1337
1355
  browserName,
1338
1356
  isNew,
@@ -172,8 +172,10 @@ const prismaHook = (runtime, versions, name, isIitm) => {
172
172
  prismaHelperInit.publish(prismaHelperCtx)
173
173
 
174
174
  const helper = prismaHelperCtx.helper
175
- this._tracingHelper = helper
176
- this._engine.tracingHelper = helper
175
+ if (helper) {
176
+ this._tracingHelper = helper
177
+ this._engine.tracingHelper = helper
178
+ }
177
179
  }
178
180
  }
179
181
  }
@@ -95,6 +95,7 @@ function getProvidedContext () {
95
95
  _ddTestManagementAttemptToFixRetries: testManagementAttemptToFixRetries,
96
96
  _ddTestManagementTests: testManagementTests,
97
97
  _ddIsFlakyTestRetriesEnabled: isFlakyTestRetriesEnabled,
98
+ _ddFlakyTestRetriesCount: flakyTestRetriesCount,
98
99
  _ddIsImpactedTestsEnabled: isImpactedTestsEnabled,
99
100
  _ddModifiedFiles: modifiedFiles,
100
101
  } = globalThis.__vitest_worker__.providedContext
@@ -109,6 +110,7 @@ function getProvidedContext () {
109
110
  testManagementAttemptToFixRetries,
110
111
  testManagementTests,
111
112
  isFlakyTestRetriesEnabled,
113
+ flakyTestRetriesCount: flakyTestRetriesCount ?? 0,
112
114
  isImpactedTestsEnabled,
113
115
  modifiedFiles,
114
116
  }
@@ -124,6 +126,7 @@ function getProvidedContext () {
124
126
  testManagementAttemptToFixRetries: 0,
125
127
  testManagementTests: {},
126
128
  isFlakyTestRetriesEnabled: false,
129
+ flakyTestRetriesCount: 0,
127
130
  isImpactedTestsEnabled: false,
128
131
  modifiedFiles: {},
129
132
  }
@@ -143,8 +146,23 @@ function isReporterPackageNewest (vitestPackage) {
143
146
  return vitestPackage.h?.name === 'BaseSequencer'
144
147
  }
145
148
 
146
- function isBaseSequencer (vitestPackage) {
147
- return vitestPackage.b?.name === 'BaseSequencer'
149
+ /**
150
+ * Finds an export by its `.name` property in a minified vitest chunk.
151
+ * Minified export keys change across versions, so we search by function/class name.
152
+ * @param {object} pkg - The module exports object
153
+ * @param {string} name - The `.name` value to look for
154
+ * @returns {{ key: string, value: Function } | undefined}
155
+ */
156
+ function findExportByName (pkg, name) {
157
+ for (const [key, value] of Object.entries(pkg)) {
158
+ if (value?.name === name) {
159
+ return { key, value }
160
+ }
161
+ }
162
+ }
163
+
164
+ function getBaseSequencerExport (vitestPackage) {
165
+ return findExportByName(vitestPackage, 'BaseSequencer')
148
166
  }
149
167
 
150
168
  function getChannelPromise (channelToPublishTo, frameworkVersion) {
@@ -154,19 +172,19 @@ function getChannelPromise (channelToPublishTo, frameworkVersion) {
154
172
  }
155
173
 
156
174
  function isCliApiPackage (vitestPackage) {
157
- return vitestPackage.s?.name === 'startVitest'
175
+ return !!findExportByName(vitestPackage, 'startVitest')
158
176
  }
159
177
 
160
- function isTestPackage (testPackage) {
161
- return testPackage.V?.name === 'VitestTestRunner'
178
+ function getTestRunnerExport (testPackage) {
179
+ return findExportByName(testPackage, 'VitestTestRunner') || findExportByName(testPackage, 'TestRunner')
162
180
  }
163
181
 
164
- function hasForksPoolWorker (vitestPackage) {
165
- return vitestPackage.f?.name === 'ForksPoolWorker'
182
+ function getForksPoolWorkerExport (vitestPackage) {
183
+ return findExportByName(vitestPackage, 'ForksPoolWorker')
166
184
  }
167
185
 
168
- function hasThreadsPoolWorker (vitestPackage) {
169
- return vitestPackage.T?.name === 'ThreadsPoolWorker'
186
+ function getThreadsPoolWorkerExport (vitestPackage) {
187
+ return findExportByName(vitestPackage, 'ThreadsPoolWorker')
170
188
  }
171
189
 
172
190
  function getSessionStatus (state) {
@@ -260,6 +278,7 @@ function getSortWrapper (sort, frameworkVersion) {
260
278
  ? this.ctx.getCoreWorkspaceProject()
261
279
  : this.ctx.getRootProject()
262
280
  workspaceProject._provided._ddIsFlakyTestRetriesEnabled = isFlakyTestRetriesEnabled
281
+ workspaceProject._provided._ddFlakyTestRetriesCount = flakyTestRetriesCount
263
282
  } catch {
264
283
  log.warn('Could not send library configuration to workers.')
265
284
  }
@@ -443,7 +462,11 @@ function getCliOrStartVitestWrapper (frameworkVersion) {
443
462
  }
444
463
 
445
464
  function getCreateCliWrapper (vitestPackage, frameworkVersion) {
446
- shimmer.wrap(vitestPackage, 'c', getCliOrStartVitestWrapper(frameworkVersion))
465
+ const createCliExport = findExportByName(vitestPackage, 'createCLI')
466
+ if (!createCliExport) {
467
+ return vitestPackage
468
+ }
469
+ shimmer.wrap(vitestPackage, createCliExport.key, getCliOrStartVitestWrapper(frameworkVersion))
447
470
 
448
471
  return vitestPackage
449
472
  }
@@ -530,27 +553,30 @@ function getStartVitestWrapper (cliApiPackage, frameworkVersion) {
530
553
  if (!isCliApiPackage(cliApiPackage)) {
531
554
  return cliApiPackage
532
555
  }
533
- shimmer.wrap(cliApiPackage, 's', getCliOrStartVitestWrapper(frameworkVersion))
556
+ const startVitestExport = findExportByName(cliApiPackage, 'startVitest')
557
+ shimmer.wrap(cliApiPackage, startVitestExport.key, getCliOrStartVitestWrapper(frameworkVersion))
534
558
 
535
- if (hasForksPoolWorker(cliApiPackage)) {
559
+ const forksPoolWorker = getForksPoolWorkerExport(cliApiPackage)
560
+ if (forksPoolWorker) {
536
561
  // function is async
537
- shimmer.wrap(cliApiPackage.f.prototype, 'start', start => function () {
562
+ shimmer.wrap(forksPoolWorker.value.prototype, 'start', start => function () {
538
563
  vitestPool = 'child_process'
539
564
  this.env.DD_VITEST_WORKER = '1'
540
565
 
541
566
  return start.apply(this, arguments)
542
567
  })
543
- shimmer.wrap(cliApiPackage.f.prototype, 'on', getWrappedOn)
568
+ shimmer.wrap(forksPoolWorker.value.prototype, 'on', getWrappedOn)
544
569
  }
545
570
 
546
- if (hasThreadsPoolWorker(cliApiPackage)) {
571
+ const threadsPoolWorker = getThreadsPoolWorkerExport(cliApiPackage)
572
+ if (threadsPoolWorker) {
547
573
  // function is async
548
- shimmer.wrap(cliApiPackage.T.prototype, 'start', start => function () {
574
+ shimmer.wrap(threadsPoolWorker.value.prototype, 'start', start => function () {
549
575
  vitestPool = 'worker_threads'
550
576
  this.env.DD_VITEST_WORKER = '1'
551
577
  return start.apply(this, arguments)
552
578
  })
553
- shimmer.wrap(cliApiPackage.T.prototype, 'on', getWrappedOn)
579
+ shimmer.wrap(threadsPoolWorker.value.prototype, 'on', getWrappedOn)
554
580
  }
555
581
  return cliApiPackage
556
582
  }
@@ -657,6 +683,9 @@ function wrapVitestTestRunner (VitestTestRunner) {
657
683
  }
658
684
  task.result.state = 'pass'
659
685
  } else if (isQuarantined) {
686
+ if (task.result.state === 'fail') {
687
+ switchedStatuses.add(task)
688
+ }
660
689
  task.result.state = 'pass'
661
690
  }
662
691
  }
@@ -740,7 +769,10 @@ function wrapVitestTestRunner (VitestTestRunner) {
740
769
  }
741
770
 
742
771
  const lastExecutionStatus = task.result.state
743
- const shouldFlipStatus = isEarlyFlakeDetectionEnabled || attemptToFixTasks.has(task)
772
+ const isAtf = attemptToFixTasks.has(task)
773
+ const isQuarantinedOrDisabledAtf = isAtf && (quarantinedTasks.has(task) || disabledTasks.has(task))
774
+ const shouldTrackStatuses = isEarlyFlakeDetectionEnabled || isAtf
775
+ const shouldFlipStatus = isEarlyFlakeDetectionEnabled || isQuarantinedOrDisabledAtf
744
776
  const statuses = taskToStatuses.get(task)
745
777
 
746
778
  // These clauses handle task.repeats, whether EFD is enabled or not
@@ -758,8 +790,10 @@ function wrapVitestTestRunner (VitestTestRunner) {
758
790
  } else {
759
791
  testPassCh.publish({ task, ...ctx.currentStore })
760
792
  }
761
- if (shouldFlipStatus) {
793
+ if (shouldTrackStatuses) {
762
794
  statuses.push(lastExecutionStatus)
795
+ }
796
+ if (shouldFlipStatus) {
763
797
  // If we don't "reset" the result.state to "pass", once a repetition fails,
764
798
  // vitest will always consider the test as failed, so we can't read the actual status
765
799
  // This means that we change vitest's behavior:
@@ -769,7 +803,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
769
803
  }
770
804
  }
771
805
  } else if (numRepetition === task.repeats) {
772
- if (shouldFlipStatus) {
806
+ if (shouldTrackStatuses) {
773
807
  statuses.push(lastExecutionStatus)
774
808
  }
775
809
 
@@ -857,11 +891,12 @@ addHook({
857
891
  versions: ['>=4.0.0'],
858
892
  filePattern: 'dist/chunks/test.*',
859
893
  }, (testPackage) => {
860
- if (!isTestPackage(testPackage)) {
894
+ const testRunner = getTestRunnerExport(testPackage)
895
+ if (!testRunner) {
861
896
  return testPackage
862
897
  }
863
898
 
864
- wrapVitestTestRunner(testPackage.V)
899
+ wrapVitestTestRunner(testRunner.value)
865
900
 
866
901
  return testPackage
867
902
  })
@@ -930,8 +965,9 @@ addHook({
930
965
  versions: ['>=3.0.9'],
931
966
  filePattern: 'dist/chunks/coverage.*',
932
967
  }, (coveragePackage) => {
933
- if (isBaseSequencer(coveragePackage)) {
934
- shimmer.wrap(coveragePackage.b.prototype, 'sort', getSortWrapper)
968
+ const baseSequencer = getBaseSequencerExport(coveragePackage)
969
+ if (baseSequencer) {
970
+ shimmer.wrap(baseSequencer.value.prototype, 'sort', getSortWrapper)
935
971
  }
936
972
  return coveragePackage
937
973
  })
@@ -1047,6 +1083,15 @@ addHook({
1047
1083
  }
1048
1084
  }
1049
1085
 
1086
+ // ATR: set hasFailedAllRetries when all auto test retries were exhausted and every attempt failed
1087
+ if (providedContext.isFlakyTestRetriesEnabled && !attemptToFixTasks.has(task) &&
1088
+ !newTasks.has(task) && !modifiedTasks.has(task)) {
1089
+ const maxRetries = providedContext.flakyTestRetriesCount ?? 0
1090
+ if (maxRetries > 0 && task.result?.retryCount === maxRetries) {
1091
+ hasFailedAllRetries = true
1092
+ }
1093
+ }
1094
+
1050
1095
  if (testCtx) {
1051
1096
  const isRetry = task.result?.retryCount > 0
1052
1097
  // `duration` is the duration of all the retries, so it can't be used if there are retries
@@ -5,6 +5,14 @@ const ApolloBasePlugin = require('../../../dd-trace/src/plugins/apollo')
5
5
  class ApolloGatewayExecutePlugin extends ApolloBasePlugin {
6
6
  static operation = 'execute'
7
7
  static prefix = 'tracing:apm:apollo:gateway:execute'
8
+
9
+ onAsyncStart (ctx) {
10
+ const span = ctx?.currentStore?.span
11
+
12
+ if (!span) return
13
+
14
+ this.config.hooks.execute(span, ctx)
15
+ }
8
16
  }
9
17
 
10
18
  module.exports = ApolloGatewayExecutePlugin
@@ -29,6 +29,11 @@ class ApolloGatewayFetchPlugin extends ApolloBasePlugin {
29
29
 
30
30
  return ctx.currentStore
31
31
  }
32
+
33
+ onAsyncStart (ctx) {
34
+ const span = ctx?.currentStore?.span
35
+ this.config.hooks.fetch(span, ctx)
36
+ }
32
37
  }
33
38
 
34
39
  module.exports = ApolloGatewayFetchPlugin
@@ -5,6 +5,14 @@ const ApolloBasePlugin = require('../../../dd-trace/src/plugins/apollo')
5
5
  class ApolloGatewayPlanPlugin extends ApolloBasePlugin {
6
6
  static operation = 'plan'
7
7
  static prefix = 'tracing:apm:apollo:gateway:plan'
8
+
9
+ onEnd (ctx) {
10
+ const span = ctx?.currentStore?.span
11
+
12
+ if (!span) return
13
+
14
+ this.config.hooks.plan(span, ctx)
15
+ }
8
16
  }
9
17
 
10
18
  module.exports = ApolloGatewayPlanPlugin
@@ -5,6 +5,11 @@ const ApolloBasePlugin = require('../../../dd-trace/src/plugins/apollo')
5
5
  class ApolloGatewayPostProcessingPlugin extends ApolloBasePlugin {
6
6
  static operation = 'postprocessing'
7
7
  static prefix = 'tracing:apm:apollo:gateway:postprocessing'
8
+
9
+ onAsyncStart (ctx) {
10
+ const span = ctx?.currentStore?.span
11
+ this.config.hooks.postprocessing(span, ctx)
12
+ }
8
13
  }
9
14
 
10
15
  module.exports = ApolloGatewayPostProcessingPlugin
@@ -52,15 +52,16 @@ class ApolloGatewayRequestPlugin extends ApolloBasePlugin {
52
52
  return ctx.currentStore
53
53
  }
54
54
 
55
- asyncStart (ctx) {
55
+ onAsyncStart (ctx) {
56
56
  const errors = ctx?.result?.errors
57
57
  // apollo gateway catches certain errors and returns them in the result object
58
58
  // we want to capture these errors as spans
59
59
  if (Array.isArray(errors) && errors.at(-1)?.stack && errors.at(-1).message) {
60
60
  ctx.currentStore.span.setTag('error', errors.at(-1))
61
61
  }
62
- ctx.currentStore.span.finish()
63
- return ctx.parentStore
62
+
63
+ const span = ctx?.currentStore?.span
64
+ this.config.hooks.request(span, ctx)
64
65
  }
65
66
  }
66
67
 
@@ -6,16 +6,17 @@ class ApolloGatewayValidatePlugin extends ApolloBasePlugin {
6
6
  static operation = 'validate'
7
7
  static prefix = 'tracing:apm:apollo:gateway:validate'
8
8
 
9
- end (ctx) {
9
+ onEnd (ctx) {
10
10
  const result = ctx.result
11
- const span = ctx.currentStore?.span
11
+ const span = ctx?.currentStore?.span
12
12
 
13
13
  if (!span) return
14
14
 
15
15
  if (Array.isArray(result) && result.at(-1)?.stack && result.at(-1).message) {
16
16
  span.setTag('error', result.at(-1))
17
17
  }
18
- span.finish()
18
+
19
+ this.config.hooks.validate(span, ctx)
19
20
  }
20
21
  }
21
22
 
@@ -10,6 +10,34 @@ class ApolloPlugin extends CompositePlugin {
10
10
  gateway: ApolloGatewayPlugin,
11
11
  }
12
12
  }
13
+
14
+ /**
15
+ * @override
16
+ */
17
+ configure (config) {
18
+ return super.configure(validateConfig(config))
19
+ }
20
+ }
21
+
22
+ const noop = () => {}
23
+
24
+ function validateConfig (config) {
25
+ return {
26
+ ...config,
27
+ hooks: getHooks(config),
28
+ }
29
+ }
30
+
31
+ function getHooks (config) {
32
+ const hooks = config?.hooks
33
+ const request = hooks?.request ?? noop
34
+ const validate = hooks?.validate ?? noop
35
+ const plan = hooks?.plan ?? noop
36
+ const execute = hooks?.execute ?? noop
37
+ const fetch = hooks?.fetch ?? noop
38
+ const postprocessing = hooks?.postprocessing ?? noop
39
+
40
+ return { request, validate, plan, execute, fetch, postprocessing }
13
41
  }
14
42
 
15
43
  module.exports = ApolloPlugin
@@ -0,0 +1,49 @@
1
+ 'use strict'
2
+
3
+ const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
4
+
5
+ class AzureDurableFunctionsPlugin extends TracingPlugin {
6
+ static get id () { return 'azure-durable-functions' }
7
+ static get operation () { return 'invoke' }
8
+ static get prefix () { return 'tracing:datadog:azure:durable-functions:invoke' }
9
+ static get type () { return 'serverless' }
10
+ static get kind () { return 'server' }
11
+
12
+ bindStart (ctx) {
13
+ const span = this.startSpan(this.operationName(), {
14
+ kind: 'internal',
15
+ type: 'serverless',
16
+
17
+ meta: {
18
+ component: 'azure-functions',
19
+ 'aas.function.name': ctx.functionName,
20
+ 'aas.function.trigger': ctx.trigger,
21
+ 'resource.name': `${ctx.trigger} ${ctx.functionName}`,
22
+ },
23
+ }, ctx)
24
+
25
+ // in the case of entity functions, operationName should be available
26
+ if (ctx.operationName) {
27
+ span.setTag('aas.function.operation', ctx.operationName)
28
+ span.setTag('resource.name', `${ctx.trigger} ${ctx.functionName} ${ctx.operationName}`
29
+ )
30
+ }
31
+
32
+ ctx.span = span
33
+ return ctx.currentStore
34
+ }
35
+
36
+ end (ctx) {
37
+ // We only want to run finish here if this is a synchronous operation
38
+ // Only synchronous operations would have `result` or `error` on `end`
39
+ // So we skip operations that dont
40
+ if (!ctx.hasOwnProperty('result') && !ctx.hasOwnProperty('error')) return
41
+ super.finish(ctx)
42
+ }
43
+
44
+ asyncStart (ctx) {
45
+ super.finish(ctx)
46
+ }
47
+ }
48
+
49
+ module.exports = AzureDurableFunctionsPlugin
@@ -247,6 +247,7 @@ class CypressPlugin {
247
247
  isSuitesSkippingEnabled = false
248
248
  isCodeCoverageEnabled = false
249
249
  isFlakyTestRetriesEnabled = false
250
+ flakyTestRetriesCount = 0
250
251
  isEarlyFlakeDetectionEnabled = false
251
252
  isKnownTestsEnabled = false
252
253
  earlyFlakeDetectionNumRetries = 0
@@ -354,7 +355,10 @@ class CypressPlugin {
354
355
  this.isKnownTestsEnabled = isKnownTestsEnabled
355
356
  if (isFlakyTestRetriesEnabled && this.isTestIsolationEnabled) {
356
357
  this.isFlakyTestRetriesEnabled = true
357
- this.cypressConfig.retries.runMode = flakyTestRetriesCount
358
+ this.flakyTestRetriesCount = flakyTestRetriesCount ?? 0
359
+ this.cypressConfig.retries.runMode = this.flakyTestRetriesCount
360
+ } else {
361
+ this.flakyTestRetriesCount = 0
358
362
  }
359
363
  this.isTestManagementTestsEnabled = isTestManagementEnabled
360
364
  this.testManagementAttemptToFixRetries = testManagementAttemptToFixRetries
@@ -603,7 +607,7 @@ class CypressPlugin {
603
607
  [TEST_SESSION_NAME]: testSessionName,
604
608
  }
605
609
  }
606
- const libraryCapabilitiesTags = getLibraryCapabilitiesTags(this.constructor.id, false, this.frameworkVersion)
610
+ const libraryCapabilitiesTags = getLibraryCapabilitiesTags(this.constructor.id, this.frameworkVersion)
607
611
  metadataTags.test = {
608
612
  ...metadataTags.test,
609
613
  ...libraryCapabilitiesTags,
@@ -1019,6 +1023,12 @@ class CypressPlugin {
1019
1023
  }
1020
1024
  }
1021
1025
  }
1026
+ // ATR: set TEST_HAS_FAILED_ALL_RETRIES when all auto test retries were exhausted and every attempt failed
1027
+ if (this.isFlakyTestRetriesEnabled && !isAttemptToFix && !isEfdRetry &&
1028
+ this.flakyTestRetriesCount > 0 && testStatuses.length === this.flakyTestRetriesCount + 1 &&
1029
+ testStatuses.every(status => status === 'fail')) {
1030
+ this.activeTestSpan.setTag(TEST_HAS_FAILED_ALL_RETRIES, 'true')
1031
+ }
1022
1032
 
1023
1033
  // Ensure quarantined tests reported from support.js are tagged
1024
1034
  // (This catches cases where the test ran but failed, but Cypress saw it as passed)
@@ -61,18 +61,16 @@ Cypress.on('fail', (err, runnable) => {
61
61
  }
62
62
 
63
63
  const testName = runnable.fullTitle()
64
- const { isQuarantined, isAttemptToFix } = getTestProperties(testName)
64
+ const { isQuarantined, isDisabled } = getTestProperties(testName)
65
65
 
66
- // For pure quarantined tests (not attemptToFix), suppress the failure
67
- // This makes the test "pass" from Cypress's perspective while we still track the error
68
- if (isQuarantined && !isAttemptToFix) {
69
- // Store the error so we can report it to Datadog in afterEach
66
+ // Suppress failures for quarantined or disabled tests so they don't affect the exit code.
67
+ // This applies regardless of attempt-to-fix status: per spec, quarantined/disabled test
68
+ // results are always ignored.
69
+ if (isQuarantined || isDisabled) {
70
70
  quarantinedTestErrors.set(testName, err)
71
- // Don't re-throw - this prevents Cypress from marking the test as failed
72
71
  return
73
72
  }
74
73
 
75
- // For all other tests (including attemptToFix), let the error propagate normally
76
74
  throw err
77
75
  })
78
76
 
@@ -386,6 +386,12 @@ class JestPlugin extends CiPlugin {
386
386
  return ctx.currentStore
387
387
  })
388
388
 
389
+ this.addBind('ci:jest:test-suite:hook:fn', (ctx) => {
390
+ const testSuiteSpan = this.testSuiteSpanPerTestSuiteAbsolutePath.get(ctx.testSuiteAbsolutePath)
391
+ const store = storage('legacy').getStore()
392
+ return { ...store, span: testSuiteSpan }
393
+ })
394
+
389
395
  this.addSub('ci:jest:test:finish', ({
390
396
  span,
391
397
  status,
@@ -0,0 +1,24 @@
1
+ 'use strict'
2
+
3
+ const CompositePlugin = require('../../dd-trace/src/plugins/composite')
4
+ const langgraphLLMObsPlugins = require('../../dd-trace/src/llmobs/plugins/langgraph')
5
+ const streamPlugin = require('./stream')
6
+
7
+ const plugins = {}
8
+
9
+ // CRITICAL: LLMObs plugins MUST come first
10
+ for (const Plugin of langgraphLLMObsPlugins) {
11
+ plugins[Plugin.id] = Plugin
12
+ }
13
+
14
+ // Tracing plugins second
15
+ for (const Plugin of streamPlugin) {
16
+ plugins[Plugin.id] = Plugin
17
+ }
18
+
19
+ class LanggraphPlugin extends CompositePlugin {
20
+ static id = 'langgraph'
21
+ static plugins = plugins
22
+ }
23
+
24
+ module.exports = LanggraphPlugin