dd-trace 5.90.0 → 5.92.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 (42) hide show
  1. package/index.d.ts +7 -0
  2. package/package.json +8 -7
  3. package/packages/datadog-instrumentations/src/child_process.js +14 -8
  4. package/packages/datadog-instrumentations/src/cucumber.js +18 -3
  5. package/packages/datadog-instrumentations/src/graphql.js +1 -1
  6. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  7. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  8. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +30 -0
  9. package/packages/datadog-instrumentations/src/jest.js +9 -2
  10. package/packages/datadog-instrumentations/src/langgraph.js +7 -0
  11. package/packages/datadog-instrumentations/src/mocha/main.js +32 -9
  12. package/packages/datadog-instrumentations/src/mocha/utils.js +0 -1
  13. package/packages/datadog-instrumentations/src/mocha/worker.js +2 -2
  14. package/packages/datadog-instrumentations/src/vitest.js +53 -24
  15. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +1 -1
  16. package/packages/datadog-plugin-cypress/src/support.js +5 -7
  17. package/packages/datadog-plugin-graphql/src/execute.js +2 -2
  18. package/packages/datadog-plugin-graphql/src/resolve.js +22 -35
  19. package/packages/datadog-plugin-http/src/client.js +1 -1
  20. package/packages/datadog-plugin-langgraph/src/index.js +24 -0
  21. package/packages/datadog-plugin-langgraph/src/stream.js +41 -0
  22. package/packages/dd-trace/src/config/defaults.js +3 -0
  23. package/packages/dd-trace/src/config/index.js +14 -1
  24. package/packages/dd-trace/src/config/supported-configurations.json +7 -0
  25. package/packages/dd-trace/src/constants.js +1 -0
  26. package/packages/dd-trace/src/crashtracking/crashtracker.js +1 -1
  27. package/packages/dd-trace/src/debugger/devtools_client/config.js +3 -0
  28. package/packages/dd-trace/src/dogstatsd.js +1 -0
  29. package/packages/dd-trace/src/encode/agentless-json.js +4 -1
  30. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chain.js +11 -0
  31. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +2 -0
  32. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +114 -0
  33. package/packages/dd-trace/src/plugins/ci_plugin.js +2 -2
  34. package/packages/dd-trace/src/plugins/index.js +1 -0
  35. package/packages/dd-trace/src/plugins/util/test.js +7 -10
  36. package/packages/dd-trace/src/priority_sampler.js +20 -2
  37. package/packages/dd-trace/src/process-tags/index.js +41 -34
  38. package/packages/dd-trace/src/profiling/profilers/wall.js +9 -1
  39. package/packages/dd-trace/src/proxy.js +4 -0
  40. package/packages/dd-trace/src/telemetry/send-data.js +5 -0
  41. package/packages/dd-trace/src/telemetry/session-propagation.js +78 -0
  42. package/packages/dd-trace/src/telemetry/telemetry.js +3 -0
package/index.d.ts CHANGED
@@ -262,6 +262,7 @@ interface Plugins {
262
262
  "knex": tracer.plugins.knex;
263
263
  "koa": tracer.plugins.koa;
264
264
  "langchain": tracer.plugins.langchain;
265
+ "langgraph": tracer.plugins.langgraph;
265
266
  "mariadb": tracer.plugins.mariadb;
266
267
  "memcached": tracer.plugins.memcached;
267
268
  "microgateway-core": tracer.plugins.microgateway_core;
@@ -2579,6 +2580,12 @@ declare namespace tracer {
2579
2580
 
2580
2581
  /**
2581
2582
  * This plugin automatically instruments the
2583
+ * [langgraph](https://github.com/npmjs/package/langgraph) library.
2584
+ */
2585
+ interface langgraph extends Instrumentation {}
2586
+
2587
+ /**
2588
+ * This plugin automatically instruments the
2582
2589
  * [ldapjs](https://github.com/ldapjs/node-ldapjs/) module.
2583
2590
  */
2584
2591
  interface ldapjs extends Instrumentation {}
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.90.0",
3
+ "version": "5.92.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
7
7
  "scripts": {
8
8
  "env": "bash ./plugin-env",
9
+ "prepare": "cd vendor && npm ci --include=dev",
9
10
  "preinstall": "node scripts/preinstall.js",
10
11
  "bench": "node benchmark/index.js",
11
12
  "bench:e2e:test-optimization": "node benchmark/e2e-test-optimization/benchmark-run.js",
@@ -142,17 +143,17 @@
142
143
  "@datadog/native-iast-taint-tracking": "4.1.0",
143
144
  "@datadog/native-metrics": "3.1.1",
144
145
  "@datadog/openfeature-node-server": "^1.1.0",
145
- "@datadog/pprof": "5.13.5",
146
+ "@datadog/pprof": "5.14.0",
146
147
  "@datadog/wasm-js-rewriter": "5.0.1",
147
148
  "@opentelemetry/api": ">=1.0.0 <1.10.0",
148
149
  "@opentelemetry/api-logs": "<1.0.0",
149
- "oxc-parser": "^0.116.0"
150
+ "oxc-parser": "^0.118.0"
150
151
  },
151
152
  "devDependencies": {
152
153
  "@actions/core": "^3.0.0",
153
154
  "@actions/github": "^9.0.0",
154
155
  "@babel/helpers": "^7.28.6",
155
- "@eslint/eslintrc": "^3.3.1",
156
+ "@eslint/eslintrc": "^3.3.5",
156
157
  "@eslint/js": "^9.39.2",
157
158
  "@msgpack/msgpack": "^3.1.3",
158
159
  "@openfeature/core": "^1.8.1",
@@ -165,11 +166,11 @@
165
166
  "benchmark": "^2.1.4",
166
167
  "body-parser": "^2.2.2",
167
168
  "bun": "1.3.10",
168
- "codeowners-audit": "^2.7.1",
169
+ "codeowners-audit": "^2.9.0",
169
170
  "eslint": "^9.39.2",
170
- "eslint-plugin-cypress": "^6.1.0",
171
+ "eslint-plugin-cypress": "^6.2.0",
171
172
  "eslint-plugin-import": "^2.32.0",
172
- "eslint-plugin-jsdoc": "^62.5.0",
173
+ "eslint-plugin-jsdoc": "^62.8.0",
173
174
  "eslint-plugin-mocha": "^11.2.0",
174
175
  "eslint-plugin-n": "^17.23.2",
175
176
  "eslint-plugin-promise": "^7.2.1",
@@ -12,7 +12,7 @@ const {
12
12
  const childProcessChannel = dc.tracingChannel('datadog:child_process:execution')
13
13
 
14
14
  // ignored exec method because it calls to execFile directly
15
- const execAsyncMethods = ['execFile', 'spawn']
15
+ const execAsyncMethods = ['execFile', 'spawn', 'fork']
16
16
 
17
17
  const names = ['child_process', 'node:child_process']
18
18
 
@@ -97,8 +97,10 @@ function wrapChildProcessSyncMethod (returnError, shell = false) {
97
97
  return childProcessMethod.apply(this, arguments)
98
98
  }
99
99
 
100
- const childProcessInfo = normalizeArgs(arguments, shell)
100
+ const callArgs = [...arguments]
101
+ const childProcessInfo = normalizeArgs(callArgs, shell)
101
102
  const context = createContextFromChildProcessInfo(childProcessInfo)
103
+ context.callArgs = callArgs
102
104
 
103
105
  return childProcessChannel.start.runStores(context, () => {
104
106
  try {
@@ -108,7 +110,7 @@ function wrapChildProcessSyncMethod (returnError, shell = false) {
108
110
  return returnError(error, context)
109
111
  }
110
112
 
111
- const result = childProcessMethod.apply(this, arguments)
113
+ const result = childProcessMethod.apply(this, context.callArgs)
112
114
  context.result = result
113
115
 
114
116
  return result
@@ -131,9 +133,11 @@ function wrapChildProcessCustomPromisifyMethod (customPromisifyMethod, shell) {
131
133
  return customPromisifyMethod.apply(this, arguments)
132
134
  }
133
135
 
134
- const childProcessInfo = normalizeArgs(arguments, shell)
136
+ const callArgs = [...arguments]
137
+ const childProcessInfo = normalizeArgs(callArgs, shell)
135
138
 
136
139
  const context = createContextFromChildProcessInfo(childProcessInfo)
140
+ context.callArgs = callArgs
137
141
 
138
142
  const { start, end, asyncStart, asyncEnd, error } = childProcessChannel
139
143
  start.publish(context)
@@ -143,7 +147,7 @@ function wrapChildProcessCustomPromisifyMethod (customPromisifyMethod, shell) {
143
147
  result = Promise.reject(context.abortController.signal.reason || new Error('Aborted'))
144
148
  } else {
145
149
  try {
146
- result = customPromisifyMethod.apply(this, arguments)
150
+ result = customPromisifyMethod.apply(this, context.callArgs)
147
151
  } catch (error) {
148
152
  context.error = error
149
153
  error.publish(context)
@@ -181,9 +185,11 @@ function wrapChildProcessAsyncMethod (ChildProcess, shell = false) {
181
185
  return childProcessMethod.apply(this, arguments)
182
186
  }
183
187
 
184
- const childProcessInfo = normalizeArgs(arguments, shell)
188
+ const callArgs = [...arguments]
189
+ const childProcessInfo = normalizeArgs(callArgs, shell)
185
190
 
186
191
  const context = createContextFromChildProcessInfo(childProcessInfo)
192
+ context.callArgs = callArgs
187
193
  return childProcessChannel.start.runStores(context, () => {
188
194
  let childProcess
189
195
  if (context.abortController.signal.aborted) {
@@ -194,7 +200,7 @@ function wrapChildProcessAsyncMethod (ChildProcess, shell = false) {
194
200
  const error = context.abortController.signal.reason || new Error('Aborted')
195
201
  childProcess.emit('error', error)
196
202
 
197
- const cb = arguments[arguments.length - 1]
203
+ const cb = context.callArgs[context.callArgs.length - 1]
198
204
  if (typeof cb === 'function') {
199
205
  cb(error)
200
206
  }
@@ -202,7 +208,7 @@ function wrapChildProcessAsyncMethod (ChildProcess, shell = false) {
202
208
  childProcess.emit('close')
203
209
  })
204
210
  } else {
205
- childProcess = childProcessMethod.apply(this, arguments)
211
+ childProcess = childProcessMethod.apply(this, context.callArgs)
206
212
  }
207
213
 
208
214
  if (childProcess) {
@@ -166,9 +166,9 @@ function getErrorFromCucumberResult (cucumberResult) {
166
166
  return error
167
167
  }
168
168
 
169
- function getChannelPromise (channelToPublishTo, isParallel = false, frameworkVersion = null) {
169
+ function getChannelPromise (channelToPublishTo, frameworkVersion = null) {
170
170
  return new Promise(resolve => {
171
- channelToPublishTo.publish({ onDone: resolve, isParallel, frameworkVersion })
171
+ channelToPublishTo.publish({ onDone: resolve, frameworkVersion })
172
172
  })
173
173
  }
174
174
 
@@ -505,7 +505,7 @@ function getWrappedStart (start, frameworkVersion, isParallel = false, isCoordin
505
505
  }
506
506
  let errorSkippableRequest
507
507
 
508
- const configurationResponse = await getChannelPromise(libraryConfigurationCh, isParallel, frameworkVersion)
508
+ const configurationResponse = await getChannelPromise(libraryConfigurationCh, frameworkVersion)
509
509
 
510
510
  isEarlyFlakeDetectionEnabled = configurationResponse.libraryConfig?.isEarlyFlakeDetectionEnabled
511
511
  earlyFlakeDetectionNumRetries = configurationResponse.libraryConfig?.earlyFlakeDetectionNumRetries
@@ -681,6 +681,7 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
681
681
  let isQuarantined = false
682
682
  let isModified = false
683
683
 
684
+ const originalDryRun = this.options.dryRun
684
685
  if (isTestManagementTestsEnabled) {
685
686
  const testProperties = getTestProperties(testSuitePath, pickle.name)
686
687
  isAttemptToFix = testProperties.attemptToFix
@@ -719,6 +720,9 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
719
720
  // TODO: for >=11 we could use `runTestCaseResult` instead of accumulating results in `lastStatusByPickleId`
720
721
  let runTestCaseResult = await runTestCaseFunction.apply(this, arguments)
721
722
 
723
+ // Restore dryRun so it doesn't affect subsequent tests in the same worker
724
+ this.options.dryRun = originalDryRun
725
+
722
726
  const testStatuses = lastStatusByPickleId.get(pickle.id)
723
727
  const lastTestStatus = testStatuses.at(-1)
724
728
 
@@ -1053,6 +1057,12 @@ addHook({
1053
1057
  this.options.worldParameters._ddIsFlakyTestRetriesEnabled = isFlakyTestRetriesEnabled
1054
1058
  this.options.worldParameters._ddNumTestRetries = numTestRetries
1055
1059
 
1060
+ if (isTestManagementTestsEnabled) {
1061
+ this.options.worldParameters._ddIsTestManagementTestsEnabled = true
1062
+ this.options.worldParameters._ddTestManagementTests = testManagementTests
1063
+ this.options.worldParameters._ddTestManagementAttemptToFixRetries = testManagementAttemptToFixRetries
1064
+ }
1065
+
1056
1066
  return startWorker.apply(this, arguments)
1057
1067
  })
1058
1068
  return adapterPackage
@@ -1090,6 +1100,11 @@ addHook({
1090
1100
  }
1091
1101
  isFlakyTestRetriesEnabled = !!this.options.worldParameters._ddIsFlakyTestRetriesEnabled
1092
1102
  numTestRetries = this.options.worldParameters._ddNumTestRetries ?? 0
1103
+ isTestManagementTestsEnabled = !!this.options.worldParameters._ddIsTestManagementTestsEnabled
1104
+ if (isTestManagementTestsEnabled) {
1105
+ testManagementTests = this.options.worldParameters._ddTestManagementTests
1106
+ testManagementAttemptToFixRetries = this.options.worldParameters._ddTestManagementAttemptToFixRetries
1107
+ }
1093
1108
  }
1094
1109
  )
1095
1110
  return workerPackage
@@ -277,7 +277,7 @@ function assertField (rootCtx, info, args) {
277
277
  let field = fields[pathString]
278
278
 
279
279
  if (!field) {
280
- const fieldCtx = { info, rootCtx, args }
280
+ const fieldCtx = { info, rootCtx, args, path, pathString }
281
281
  startResolveCh.publish(fieldCtx)
282
282
  field = fields[pathString] = {
283
283
  error: null,
@@ -4,6 +4,7 @@ module.exports = {
4
4
  '@anthropic-ai/sdk': { esmFirst: true, fn: () => require('../anthropic') },
5
5
  '@apollo/server': () => require('../apollo-server'),
6
6
  '@apollo/gateway': () => require('../apollo'),
7
+ '@langchain/langgraph': { esmFirst: true, fn: () => require('../langgraph') },
7
8
  'apollo-server-core': () => require('../apollo-server-core'),
8
9
  '@aws-sdk/smithy-client': () => require('../aws-sdk'),
9
10
  '@azure/event-hubs': () => require('../azure-event-hubs'),
@@ -4,4 +4,5 @@ module.exports = [
4
4
  ...require('./ai'),
5
5
  ...require('./bullmq'),
6
6
  ...require('./langchain'),
7
+ ...require('./langgraph'),
7
8
  ]
@@ -0,0 +1,30 @@
1
+ 'use strict'
2
+
3
+ module.exports = [
4
+ {
5
+ module: {
6
+ name: '@langchain/langgraph',
7
+ versionRange: '>=1.1.2',
8
+ filePath: 'dist/pregel/index.js',
9
+ },
10
+ functionQuery: {
11
+ methodName: 'stream',
12
+ className: 'Pregel',
13
+ kind: 'AsyncIterator',
14
+ },
15
+ channelName: 'Pregel_stream',
16
+ },
17
+ {
18
+ module: {
19
+ name: '@langchain/langgraph',
20
+ versionRange: '>=1.1.2',
21
+ filePath: 'dist/pregel/index.cjs',
22
+ },
23
+ functionQuery: {
24
+ methodName: 'stream',
25
+ className: 'Pregel',
26
+ kind: 'AsyncIterator',
27
+ },
28
+ channelName: 'Pregel_stream',
29
+ },
30
+ ]
@@ -702,11 +702,15 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
702
702
  const mightHitBreakpoint = this.isDiEnabled && numTestExecutions >= 2
703
703
 
704
704
  const ctx = testContexts.get(event.test)
705
+ if (!ctx) {
706
+ log.warn('"ci:jest:test_done": no context found for test "%s"', testName)
707
+ return
708
+ }
705
709
 
706
710
  const finalStatus = this.getFinalStatus(testName,
707
711
  status,
708
- !!ctx?.isNew,
709
- !!ctx?.isModified,
712
+ !!ctx.isNew,
713
+ !!ctx.isModified,
710
714
  isEfdRetry,
711
715
  isAttemptToFix,
712
716
  numTestExecutions)
@@ -761,6 +765,9 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
761
765
  efdDeterminedRetries.clear()
762
766
  efdSlowAbortedTests.clear()
763
767
  efdNewTestCandidates.clear()
768
+ retriedTestsToNumAttempts.clear()
769
+ attemptToFixRetriedTestsStatuses.clear()
770
+ testsToBeRetried.clear()
764
771
  }
765
772
  if (event.name === 'test_skip' || event.name === 'test_todo') {
766
773
  const testName = getJestTestName(event.test, this.getShouldStripSeedFromTestName())
@@ -0,0 +1,7 @@
1
+ 'use strict'
2
+
3
+ const { addHook, getHooks } = require('./helpers/instrument')
4
+
5
+ for (const hook of getHooks('@langchain/langgraph')) {
6
+ addHook(hook, exports => exports)
7
+ }
@@ -101,12 +101,12 @@ function getFilteredSuites (originalSuites) {
101
101
  }, { suitesToRun: [], skippedSuites: new Set() })
102
102
  }
103
103
 
104
- function getOnStartHandler (isParallel, frameworkVersion) {
104
+ function getOnStartHandler (frameworkVersion) {
105
105
  return function () {
106
106
  const processArgv = process.argv.slice(2).join(' ')
107
107
  const command = `mocha ${processArgv}`
108
108
  testSessionStartCh.publish({ command, frameworkVersion })
109
- if (!isParallel && skippedSuites.length) {
109
+ if (skippedSuites.length) {
110
110
  itrSkippedSuitesCh.publish({ skippedSuites, frameworkVersion })
111
111
  }
112
112
  }
@@ -315,8 +315,7 @@ function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFini
315
315
  config.isTestManagementTestsEnabled = libraryConfig.isTestManagementEnabled
316
316
  config.testManagementAttemptToFixRetries = libraryConfig.testManagementAttemptToFixRetries
317
317
  config.isImpactedTestsEnabled = libraryConfig.isImpactedTestsEnabled
318
- // ITR is not supported in parallel mode yet
319
- config.isSuitesSkippingEnabled = !isParallel && libraryConfig.isSuitesSkippingEnabled
318
+ config.isSuitesSkippingEnabled = libraryConfig.isSuitesSkippingEnabled
320
319
  config.isFlakyTestRetriesEnabled = libraryConfig.isFlakyTestRetriesEnabled
321
320
  config.flakyTestRetriesCount = libraryConfig.flakyTestRetriesCount
322
321
 
@@ -452,7 +451,7 @@ addHook({
452
451
 
453
452
  const { suitesByTestFile, numSuitesByTestFile } = getSuitesByTestFile(this.suite)
454
453
 
455
- this.once('start', getOnStartHandler(false, frameworkVersion))
454
+ this.once('start', getOnStartHandler(frameworkVersion))
456
455
 
457
456
  this.once('end', getOnEndHandler(false))
458
457
 
@@ -623,9 +622,16 @@ addHook({
623
622
  return run.apply(this, arguments)
624
623
  }
625
624
 
626
- this.once('start', getOnStartHandler(true, frameworkVersion))
625
+ this.once('start', getOnStartHandler(frameworkVersion))
627
626
  this.once('end', getOnEndHandler(true))
628
627
 
628
+ // Populate unskippable suites before config is fetched (matches serial mode at Mocha.prototype.run)
629
+ for (const filePath of files) {
630
+ if (isMarkedAsUnskippable({ path: filePath })) {
631
+ unskippableSuites.push(filePath)
632
+ }
633
+ }
634
+
629
635
  getExecutionConfiguration(this, true, frameworkVersion, () => {
630
636
  if (config.isKnownTestsEnabled) {
631
637
  const testSuites = files.map(file => getTestSuitePath(file, process.cwd()))
@@ -640,7 +646,25 @@ addHook({
640
646
  config.isEarlyFlakeDetectionFaulty = true
641
647
  }
642
648
  }
643
- run.apply(this, arguments)
649
+ if (config.isSuitesSkippingEnabled && suitesToSkip.length) {
650
+ const filteredFiles = []
651
+ const skippedFiles = []
652
+ for (const file of files) {
653
+ const testPath = getTestSuitePath(file, process.cwd())
654
+ const shouldSkip = suitesToSkip.includes(testPath)
655
+ const isUnskippable = unskippableSuites.includes(file)
656
+ if (shouldSkip && !isUnskippable) {
657
+ skippedFiles.push(testPath)
658
+ } else {
659
+ filteredFiles.push(file)
660
+ }
661
+ }
662
+ isSuitesSkipped = skippedFiles.length > 0
663
+ skippedSuites = skippedFiles
664
+ run.apply(this, [cb, { files: filteredFiles }])
665
+ } else {
666
+ run.apply(this, arguments)
667
+ }
644
668
  })
645
669
 
646
670
  return this
@@ -694,8 +718,7 @@ addHook({
694
718
  if (config.isTestManagementTestsEnabled) {
695
719
  const testSuiteTestManagementTests = config.testManagementTests?.mocha?.suites?.[testPath] || {}
696
720
  newWorkerArgs._ddIsTestManagementTestsEnabled = true
697
- // TODO: attempt to fix does not work in parallel mode yet
698
- // newWorkerArgs._ddTestManagementAttemptToFixRetries = config.testManagementAttemptToFixRetries
721
+ newWorkerArgs._ddTestManagementAttemptToFixRetries = config.testManagementAttemptToFixRetries
699
722
  newWorkerArgs._ddTestManagementTests = {
700
723
  mocha: {
701
724
  suites: {
@@ -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
  }
@@ -43,10 +43,10 @@ addHook({
43
43
  }
44
44
  if (this.options._ddIsTestManagementTestsEnabled) {
45
45
  config.isTestManagementTestsEnabled = true
46
- // TODO: attempt to fix does not work in parallel mode yet
47
- // config.testManagementAttemptToFixRetries = this.options._ddTestManagementAttemptToFixRetries
46
+ config.testManagementAttemptToFixRetries = this.options._ddTestManagementAttemptToFixRetries
48
47
  config.testManagementTests = this.options._ddTestManagementTests
49
48
  delete this.options._ddIsTestManagementTestsEnabled
49
+ delete this.options._ddTestManagementAttemptToFixRetries
50
50
  delete this.options._ddTestManagementTests
51
51
  }
52
52
  if (this.options._ddIsFlakyTestRetriesEnabled) {
@@ -146,8 +146,23 @@ function isReporterPackageNewest (vitestPackage) {
146
146
  return vitestPackage.h?.name === 'BaseSequencer'
147
147
  }
148
148
 
149
- function isBaseSequencer (vitestPackage) {
150
- 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')
151
166
  }
152
167
 
153
168
  function getChannelPromise (channelToPublishTo, frameworkVersion) {
@@ -157,19 +172,19 @@ function getChannelPromise (channelToPublishTo, frameworkVersion) {
157
172
  }
158
173
 
159
174
  function isCliApiPackage (vitestPackage) {
160
- return vitestPackage.s?.name === 'startVitest'
175
+ return !!findExportByName(vitestPackage, 'startVitest')
161
176
  }
162
177
 
163
- function isTestPackage (testPackage) {
164
- return testPackage.V?.name === 'VitestTestRunner'
178
+ function getTestRunnerExport (testPackage) {
179
+ return findExportByName(testPackage, 'VitestTestRunner') || findExportByName(testPackage, 'TestRunner')
165
180
  }
166
181
 
167
- function hasForksPoolWorker (vitestPackage) {
168
- return vitestPackage.f?.name === 'ForksPoolWorker'
182
+ function getForksPoolWorkerExport (vitestPackage) {
183
+ return findExportByName(vitestPackage, 'ForksPoolWorker')
169
184
  }
170
185
 
171
- function hasThreadsPoolWorker (vitestPackage) {
172
- return vitestPackage.T?.name === 'ThreadsPoolWorker'
186
+ function getThreadsPoolWorkerExport (vitestPackage) {
187
+ return findExportByName(vitestPackage, 'ThreadsPoolWorker')
173
188
  }
174
189
 
175
190
  function getSessionStatus (state) {
@@ -447,7 +462,11 @@ function getCliOrStartVitestWrapper (frameworkVersion) {
447
462
  }
448
463
 
449
464
  function getCreateCliWrapper (vitestPackage, frameworkVersion) {
450
- 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))
451
470
 
452
471
  return vitestPackage
453
472
  }
@@ -534,27 +553,30 @@ function getStartVitestWrapper (cliApiPackage, frameworkVersion) {
534
553
  if (!isCliApiPackage(cliApiPackage)) {
535
554
  return cliApiPackage
536
555
  }
537
- shimmer.wrap(cliApiPackage, 's', getCliOrStartVitestWrapper(frameworkVersion))
556
+ const startVitestExport = findExportByName(cliApiPackage, 'startVitest')
557
+ shimmer.wrap(cliApiPackage, startVitestExport.key, getCliOrStartVitestWrapper(frameworkVersion))
538
558
 
539
- if (hasForksPoolWorker(cliApiPackage)) {
559
+ const forksPoolWorker = getForksPoolWorkerExport(cliApiPackage)
560
+ if (forksPoolWorker) {
540
561
  // function is async
541
- shimmer.wrap(cliApiPackage.f.prototype, 'start', start => function () {
562
+ shimmer.wrap(forksPoolWorker.value.prototype, 'start', start => function () {
542
563
  vitestPool = 'child_process'
543
564
  this.env.DD_VITEST_WORKER = '1'
544
565
 
545
566
  return start.apply(this, arguments)
546
567
  })
547
- shimmer.wrap(cliApiPackage.f.prototype, 'on', getWrappedOn)
568
+ shimmer.wrap(forksPoolWorker.value.prototype, 'on', getWrappedOn)
548
569
  }
549
570
 
550
- if (hasThreadsPoolWorker(cliApiPackage)) {
571
+ const threadsPoolWorker = getThreadsPoolWorkerExport(cliApiPackage)
572
+ if (threadsPoolWorker) {
551
573
  // function is async
552
- shimmer.wrap(cliApiPackage.T.prototype, 'start', start => function () {
574
+ shimmer.wrap(threadsPoolWorker.value.prototype, 'start', start => function () {
553
575
  vitestPool = 'worker_threads'
554
576
  this.env.DD_VITEST_WORKER = '1'
555
577
  return start.apply(this, arguments)
556
578
  })
557
- shimmer.wrap(cliApiPackage.T.prototype, 'on', getWrappedOn)
579
+ shimmer.wrap(threadsPoolWorker.value.prototype, 'on', getWrappedOn)
558
580
  }
559
581
  return cliApiPackage
560
582
  }
@@ -747,7 +769,10 @@ function wrapVitestTestRunner (VitestTestRunner) {
747
769
  }
748
770
 
749
771
  const lastExecutionStatus = task.result.state
750
- 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
751
776
  const statuses = taskToStatuses.get(task)
752
777
 
753
778
  // These clauses handle task.repeats, whether EFD is enabled or not
@@ -765,8 +790,10 @@ function wrapVitestTestRunner (VitestTestRunner) {
765
790
  } else {
766
791
  testPassCh.publish({ task, ...ctx.currentStore })
767
792
  }
768
- if (shouldFlipStatus) {
793
+ if (shouldTrackStatuses) {
769
794
  statuses.push(lastExecutionStatus)
795
+ }
796
+ if (shouldFlipStatus) {
770
797
  // If we don't "reset" the result.state to "pass", once a repetition fails,
771
798
  // vitest will always consider the test as failed, so we can't read the actual status
772
799
  // This means that we change vitest's behavior:
@@ -776,7 +803,7 @@ function wrapVitestTestRunner (VitestTestRunner) {
776
803
  }
777
804
  }
778
805
  } else if (numRepetition === task.repeats) {
779
- if (shouldFlipStatus) {
806
+ if (shouldTrackStatuses) {
780
807
  statuses.push(lastExecutionStatus)
781
808
  }
782
809
 
@@ -864,11 +891,12 @@ addHook({
864
891
  versions: ['>=4.0.0'],
865
892
  filePattern: 'dist/chunks/test.*',
866
893
  }, (testPackage) => {
867
- if (!isTestPackage(testPackage)) {
894
+ const testRunner = getTestRunnerExport(testPackage)
895
+ if (!testRunner) {
868
896
  return testPackage
869
897
  }
870
898
 
871
- wrapVitestTestRunner(testPackage.V)
899
+ wrapVitestTestRunner(testRunner.value)
872
900
 
873
901
  return testPackage
874
902
  })
@@ -937,8 +965,9 @@ addHook({
937
965
  versions: ['>=3.0.9'],
938
966
  filePattern: 'dist/chunks/coverage.*',
939
967
  }, (coveragePackage) => {
940
- if (isBaseSequencer(coveragePackage)) {
941
- shimmer.wrap(coveragePackage.b.prototype, 'sort', getSortWrapper)
968
+ const baseSequencer = getBaseSequencerExport(coveragePackage)
969
+ if (baseSequencer) {
970
+ shimmer.wrap(baseSequencer.value.prototype, 'sort', getSortWrapper)
942
971
  }
943
972
  return coveragePackage
944
973
  })
@@ -607,7 +607,7 @@ class CypressPlugin {
607
607
  [TEST_SESSION_NAME]: testSessionName,
608
608
  }
609
609
  }
610
- const libraryCapabilitiesTags = getLibraryCapabilitiesTags(this.constructor.id, false, this.frameworkVersion)
610
+ const libraryCapabilitiesTags = getLibraryCapabilitiesTags(this.constructor.id, this.frameworkVersion)
611
611
  metadataTags.test = {
612
612
  ...metadataTags.test,
613
613
  ...libraryCapabilitiesTags,
@@ -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
 
@@ -58,8 +58,8 @@ function addVariableTags (config, span, variableValues) {
58
58
 
59
59
  if (variableValues && config.variables) {
60
60
  const variables = config.variables(variableValues)
61
- for (const param in variables) {
62
- tags[`graphql.variables.${param}`] = variables[param]
61
+ for (const [param, value] of Object.entries(variables)) {
62
+ tags[`graphql.variables.${param}`] = value
63
63
  }
64
64
  }
65
65