dd-trace 5.41.1 → 5.42.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 (59) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/index.d.ts +8 -1
  3. package/package.json +6 -3
  4. package/packages/datadog-esbuild/index.js +3 -1
  5. package/packages/datadog-instrumentations/src/cucumber.js +37 -29
  6. package/packages/datadog-instrumentations/src/google-cloud-vertexai.js +102 -0
  7. package/packages/datadog-instrumentations/src/{check_require_cache.js → helpers/check-require-cache.js} +2 -2
  8. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -2
  9. package/packages/datadog-instrumentations/src/helpers/register.js +4 -1
  10. package/packages/datadog-instrumentations/src/jest.js +72 -49
  11. package/packages/datadog-instrumentations/src/langchain.js +29 -10
  12. package/packages/datadog-instrumentations/src/mocha/main.js +53 -34
  13. package/packages/datadog-instrumentations/src/mocha/utils.js +34 -24
  14. package/packages/datadog-instrumentations/src/mocha/worker.js +7 -8
  15. package/packages/datadog-instrumentations/src/openai.js +1 -1
  16. package/packages/datadog-instrumentations/src/playwright.js +37 -30
  17. package/packages/datadog-instrumentations/src/vitest.js +64 -29
  18. package/packages/datadog-plugin-cucumber/src/index.js +13 -4
  19. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +41 -35
  20. package/packages/datadog-plugin-cypress/src/plugin.js +10 -0
  21. package/packages/datadog-plugin-google-cloud-vertexai/src/index.js +195 -0
  22. package/packages/datadog-plugin-jest/src/index.js +18 -6
  23. package/packages/datadog-plugin-langchain/src/handlers/embedding.js +4 -1
  24. package/packages/datadog-plugin-mocha/src/index.js +13 -4
  25. package/packages/datadog-plugin-playwright/src/index.js +19 -5
  26. package/packages/datadog-plugin-vitest/src/index.js +41 -17
  27. package/packages/dd-trace/src/appsec/api_security_sampler.js +7 -3
  28. package/packages/dd-trace/src/appsec/blocking.js +23 -16
  29. package/packages/dd-trace/src/appsec/graphql.js +13 -6
  30. package/packages/dd-trace/src/appsec/rasp/utils.js +0 -1
  31. package/packages/dd-trace/src/appsec/reporter.js +35 -0
  32. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -3
  33. package/packages/dd-trace/src/appsec/telemetry/index.js +5 -1
  34. package/packages/dd-trace/src/appsec/telemetry/rasp.js +16 -1
  35. package/packages/dd-trace/src/appsec/telemetry/waf.js +16 -1
  36. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +43 -13
  37. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +2 -2
  38. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +15 -14
  39. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +1 -2
  40. package/packages/dd-trace/src/ci-visibility/telemetry.js +2 -1
  41. package/packages/dd-trace/src/ci-visibility/{quarantined-tests/get-quarantined-tests.js → test-management/get-test-management-tests.js} +5 -5
  42. package/packages/dd-trace/src/config.js +11 -1
  43. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +9 -2
  44. package/packages/dd-trace/src/lambda/runtime/patch.js +5 -3
  45. package/packages/dd-trace/src/lambda/runtime/ritm.js +13 -18
  46. package/packages/dd-trace/src/llmobs/plugins/openai.js +27 -2
  47. package/packages/dd-trace/src/opentracing/span.js +3 -0
  48. package/packages/dd-trace/src/plugins/ci_plugin.js +38 -10
  49. package/packages/dd-trace/src/plugins/index.js +1 -0
  50. package/packages/dd-trace/src/plugins/util/git.js +7 -3
  51. package/packages/dd-trace/src/plugins/util/test.js +10 -0
  52. package/packages/dd-trace/src/plugins/util/web.js +5 -2
  53. package/packages/dd-trace/src/priority_sampler.js +116 -15
  54. package/packages/dd-trace/src/sampler.js +9 -0
  55. package/packages/dd-trace/src/standalone/product.js +6 -2
  56. package/packages/dd-trace/src/startup-log.js +2 -1
  57. package/packages/dd-trace/src/telemetry/metrics.js +0 -8
  58. package/packages/dd-trace/src/tracer.js +1 -1
  59. /package/packages/datadog-instrumentations/src/{utils/src → helpers}/extract-package-and-module-path.js +0 -0
@@ -43,7 +43,7 @@ const testErrCh = channel('ci:jest:test:err')
43
43
  const skippableSuitesCh = channel('ci:jest:test-suite:skippable')
44
44
  const libraryConfigurationCh = channel('ci:jest:library-configuration')
45
45
  const knownTestsCh = channel('ci:jest:known-tests')
46
- const quarantinedTestsCh = channel('ci:jest:quarantined-tests')
46
+ const testManagementTestsCh = channel('ci:jest:test-management-tests')
47
47
 
48
48
  const itrSkippedSuitesCh = channel('ci:jest:itr:skipped-suites')
49
49
 
@@ -71,8 +71,8 @@ let earlyFlakeDetectionFaultyThreshold = 30
71
71
  let isEarlyFlakeDetectionFaulty = false
72
72
  let hasFilteredSkippableSuites = false
73
73
  let isKnownTestsEnabled = false
74
- let isQuarantinedTestsEnabled = false
75
- let quarantinedTests = {}
74
+ let isTestManagementTestsEnabled = false
75
+ let testManagementTests = {}
76
76
 
77
77
  const sessionAsyncResource = new AsyncResource('bound-anonymous-fn')
78
78
 
@@ -143,7 +143,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
143
143
  this.flakyTestRetriesCount = this.testEnvironmentOptions._ddFlakyTestRetriesCount
144
144
  this.isDiEnabled = this.testEnvironmentOptions._ddIsDiEnabled
145
145
  this.isKnownTestsEnabled = this.testEnvironmentOptions._ddIsKnownTestsEnabled
146
- this.isQuarantinedTestsEnabled = this.testEnvironmentOptions._ddIsQuarantinedTestsEnabled
146
+ this.isTestManagementTestsEnabled = this.testEnvironmentOptions._ddIsTestManagementTestsEnabled
147
147
 
148
148
  if (this.isKnownTestsEnabled) {
149
149
  try {
@@ -166,15 +166,15 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
166
166
  }
167
167
  }
168
168
 
169
- if (this.isQuarantinedTestsEnabled) {
169
+ if (this.isTestManagementTestsEnabled) {
170
170
  try {
171
- const hasQuarantinedTests = !!quarantinedTests.jest
172
- this.quarantinedTestsForThisSuite = hasQuarantinedTests
173
- ? this.getQuarantinedTestsForSuite(quarantinedTests.jest.suites?.[this.testSuite]?.tests)
174
- : this.getQuarantinedTestsForSuite(this.testEnvironmentOptions._ddQuarantinedTests)
171
+ const hasTestManagementTests = !!testManagementTests.jest
172
+ this.testManagementTestsForThisSuite = hasTestManagementTests
173
+ ? this.getTestManagementTestsForSuite(testManagementTests.jest.suites?.[this.testSuite]?.tests)
174
+ : this.getTestManagementTestsForSuite(this.testEnvironmentOptions._ddTestManagementTests)
175
175
  } catch (e) {
176
- log.error('Error parsing quarantined tests', e)
177
- this.isQuarantinedTestsEnabled = false
176
+ log.error('Error parsing test management tests', e)
177
+ this.isTestManagementTestsEnabled = false
178
178
  }
179
179
  }
180
180
  }
@@ -209,25 +209,38 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
209
209
  return knownTestsForSuite
210
210
  }
211
211
 
212
- getQuarantinedTestsForSuite (quarantined) {
213
- if (this.quarantinedTestsForThisSuite) {
214
- return this.quarantinedTestsForThisSuite
212
+ getTestManagementTestsForSuite (testManagementTests) {
213
+ if (this.testManagementTestsForThisSuite) {
214
+ return this.testManagementTestsForThisSuite
215
215
  }
216
- if (!quarantined) {
217
- return []
216
+ // TODO - ADD ATTEMPT_TO_FIX tests
217
+ if (!testManagementTests) {
218
+ return {
219
+ disabled: [],
220
+ quarantined: []
221
+ }
218
222
  }
219
- let quarantinedTestsForSuite = quarantined
220
- // If jest is using workers, quarantined tests are serialized to json.
223
+ let testManagementTestsForSuite = testManagementTests
224
+ // If jest is using workers, test management tests are serialized to json.
221
225
  // If jest runs in band, they are not.
222
- if (typeof quarantinedTestsForSuite === 'string') {
223
- quarantinedTestsForSuite = JSON.parse(quarantinedTestsForSuite)
226
+ if (typeof testManagementTestsForSuite === 'string') {
227
+ testManagementTestsForSuite = JSON.parse(testManagementTestsForSuite)
228
+ }
229
+
230
+ const result = {
231
+ disabled: [],
232
+ quarantined: []
224
233
  }
225
- return Object.entries(quarantinedTestsForSuite).reduce((acc, [testName, { properties }]) => {
226
- if (properties?.quarantined) {
227
- acc.push(testName)
234
+
235
+ Object.entries(testManagementTestsForSuite).forEach(([testName, { properties }]) => {
236
+ if (properties?.disabled) {
237
+ result.disabled.push(testName)
238
+ } else if (properties?.quarantined) {
239
+ result.quarantined.push(testName)
228
240
  }
229
- return acc
230
- }, [])
241
+ })
242
+
243
+ return result
231
244
  }
232
245
 
233
246
  // Add the `add_test` event we don't have the test object yet, so
@@ -275,6 +288,13 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
275
288
  retriedTestsToNumAttempts.set(originalTestName, numEfdRetry + 1)
276
289
  }
277
290
  }
291
+
292
+ if (this.isTestManagementTestsEnabled) {
293
+ const isDisabled = this.testManagementTestsForThisSuite?.disabled?.includes(testName)
294
+ if (isDisabled) {
295
+ event.test.mode = 'skip'
296
+ }
297
+ }
278
298
  const isJestRetry = event.test?.invocations > 1
279
299
  asyncResource.runInAsyncScope(() => {
280
300
  testStartCh.publish({
@@ -341,9 +361,9 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
341
361
  }
342
362
  let isQuarantined = false
343
363
 
344
- if (this.isQuarantinedTestsEnabled) {
364
+ if (this.isTestManagementTestsEnabled) {
345
365
  const testName = getJestTestName(event.test)
346
- isQuarantined = this.quarantinedTestsForThisSuite?.includes(testName)
366
+ isQuarantined = this.testManagementTestsForThisSuite?.quarantined?.includes(testName)
347
367
  }
348
368
 
349
369
  const promises = {}
@@ -391,12 +411,15 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
391
411
  const asyncResource = new AsyncResource('bound-anonymous-fn')
392
412
  asyncResource.runInAsyncScope(() => {
393
413
  testSkippedCh.publish({
394
- name: getJestTestName(event.test),
395
- suite: this.testSuite,
396
- testSourceFile: this.testSourceFile,
397
- displayName: this.displayName,
398
- frameworkVersion: jestVersion,
399
- testStartLine: getTestLineStart(event.test.asyncError, this.testSuite)
414
+ test: {
415
+ name: getJestTestName(event.test),
416
+ suite: this.testSuite,
417
+ testSourceFile: this.testSourceFile,
418
+ displayName: this.displayName,
419
+ frameworkVersion: jestVersion,
420
+ testStartLine: getTestLineStart(event.test.asyncError, this.testSuite)
421
+ },
422
+ isDisabled: this.testManagementTestsForThisSuite?.disabled?.includes(getJestTestName(event.test))
400
423
  })
401
424
  })
402
425
  }
@@ -528,7 +551,7 @@ function cliWrapper (cli, jestVersion) {
528
551
  earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
529
552
  earlyFlakeDetectionFaultyThreshold = libraryConfig.earlyFlakeDetectionFaultyThreshold
530
553
  isKnownTestsEnabled = libraryConfig.isKnownTestsEnabled
531
- isQuarantinedTestsEnabled = libraryConfig.isQuarantinedTestsEnabled
554
+ isTestManagementTestsEnabled = libraryConfig.isTestManagementEnabled
532
555
  }
533
556
  } catch (err) {
534
557
  log.error('Jest library configuration error', err)
@@ -576,22 +599,22 @@ function cliWrapper (cli, jestVersion) {
576
599
  }
577
600
  }
578
601
 
579
- if (isQuarantinedTestsEnabled) {
580
- const quarantinedTestsPromise = new Promise((resolve) => {
602
+ if (isTestManagementTestsEnabled) {
603
+ const testManagementTestsPromise = new Promise((resolve) => {
581
604
  onDone = resolve
582
605
  })
583
606
 
584
607
  sessionAsyncResource.runInAsyncScope(() => {
585
- quarantinedTestsCh.publish({ onDone })
608
+ testManagementTestsCh.publish({ onDone })
586
609
  })
587
610
 
588
611
  try {
589
- const { err, quarantinedTests: receivedQuarantinedTests } = await quarantinedTestsPromise
612
+ const { err, testManagementTests: receivedTestManagementTests } = await testManagementTestsPromise
590
613
  if (!err) {
591
- quarantinedTests = receivedQuarantinedTests
614
+ testManagementTests = receivedTestManagementTests
592
615
  }
593
616
  } catch (err) {
594
- log.error('Jest quarantined tests error', err)
617
+ log.error('Jest test management tests error', err)
595
618
  }
596
619
  }
597
620
 
@@ -664,7 +687,7 @@ function cliWrapper (cli, jestVersion) {
664
687
  error,
665
688
  isEarlyFlakeDetectionEnabled,
666
689
  isEarlyFlakeDetectionFaulty,
667
- isQuarantinedTestsEnabled,
690
+ isTestManagementTestsEnabled,
668
691
  onDone
669
692
  })
670
693
  })
@@ -698,7 +721,7 @@ function cliWrapper (cli, jestVersion) {
698
721
  }
699
722
  }
700
723
 
701
- if (isQuarantinedTestsEnabled) {
724
+ if (isTestManagementTestsEnabled) {
702
725
  const failedTests = result
703
726
  .results
704
727
  .testResults.flatMap(({ testResults, testFilePath: testSuiteAbsolutePath }) => (
@@ -710,7 +733,7 @@ function cliWrapper (cli, jestVersion) {
710
733
 
711
734
  for (const { testName, testSuiteAbsolutePath } of failedTests) {
712
735
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, result.globalConfig.rootDir)
713
- const isQuarantined = quarantinedTests
736
+ const isQuarantined = testManagementTests
714
737
  ?.jest
715
738
  ?.suites
716
739
  ?.[testSuite]
@@ -922,8 +945,8 @@ addHook({
922
945
  _ddFlakyTestRetriesCount,
923
946
  _ddIsDiEnabled,
924
947
  _ddIsKnownTestsEnabled,
925
- _ddIsQuarantinedTestsEnabled,
926
- _ddQuarantinedTests,
948
+ _ddIsTestManagementTestsEnabled,
949
+ _ddTestManagementTests,
927
950
  ...restOfTestEnvironmentOptions
928
951
  } = testEnvironmentOptions
929
952
 
@@ -1037,7 +1060,7 @@ addHook({
1037
1060
  /*
1038
1061
  * This hook does three things:
1039
1062
  * - Pass known tests to the workers.
1040
- * - Pass quarantined tests to the workers.
1063
+ * - Pass test management tests to the workers.
1041
1064
  * - Receive trace, coverage and logs payloads from the workers.
1042
1065
  */
1043
1066
  addHook({
@@ -1047,7 +1070,7 @@ addHook({
1047
1070
  }, (childProcessWorker) => {
1048
1071
  const ChildProcessWorker = childProcessWorker.default
1049
1072
  shimmer.wrap(ChildProcessWorker.prototype, 'send', send => function (request) {
1050
- if (!isKnownTestsEnabled && !isQuarantinedTestsEnabled) {
1073
+ if (!isKnownTestsEnabled && !isTestManagementTestsEnabled) {
1051
1074
  return send.apply(this, arguments)
1052
1075
  }
1053
1076
  const [type] = request
@@ -1068,14 +1091,14 @@ addHook({
1068
1091
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, globalConfig.rootDir || process.cwd())
1069
1092
  const suiteKnownTests = knownTests?.jest?.[testSuite] || []
1070
1093
 
1071
- const suiteQuarantinedTests = quarantinedTests.jest?.suites?.[testSuite]?.tests || {}
1094
+ const suiteTestManagementTests = testManagementTests.jest?.suites?.[testSuite]?.tests || {}
1072
1095
 
1073
1096
  args[0].config = {
1074
1097
  ...config,
1075
1098
  testEnvironmentOptions: {
1076
1099
  ...config.testEnvironmentOptions,
1077
1100
  _ddKnownTests: suiteKnownTests,
1078
- _ddQuarantinedTests: suiteQuarantinedTests
1101
+ _ddTestManagementTests: suiteTestManagementTests
1079
1102
  }
1080
1103
  }
1081
1104
  }
@@ -61,17 +61,36 @@ for (const extension of extensions) {
61
61
  return exports
62
62
  })
63
63
 
64
- addHook({ name: '@langchain/openai', file: `dist/embeddings.${extension}`, versions: ['>=0.1'] }, exports => {
65
- const OpenAIEmbeddings = exports.OpenAIEmbeddings
64
+ addHook({ name: '@langchain/core', file: `dist/embeddings.${extension}`, versions: ['>=0.1'] }, exports => {
65
+ // we cannot patch the prototype of the Embeddings class directly
66
+ // this is because the "abstract class Embeddings" is transpiled from TypeScript to not include abstract functions
67
+ // thus, we patch the exported class directly instead instead.
68
+
69
+ shimmer.wrap(exports, 'Embeddings', Embeddings => {
70
+ return class extends Embeddings {
71
+ constructor (...args) {
72
+ super(...args)
73
+
74
+ const namespace = ['langchain', 'embeddings']
75
+
76
+ // when originally implemented, we only wrapped OpenAI embeddings
77
+ // these embeddings had the resource name of `langchain.embeddings.openai.OpenAIEmbeddings`
78
+ // we need to make sure `openai` is appended to the resource name until a new tracer major version
79
+ if (this.constructor.name === 'OpenAIEmbeddings') {
80
+ namespace.push('openai')
81
+ }
82
+
83
+ shimmer.wrap(this, 'embedQuery', embedQuery => wrapLangChainPromise(embedQuery, 'embedding', namespace))
84
+ shimmer.wrap(this, 'embedDocuments',
85
+ embedDocuments => wrapLangChainPromise(embedDocuments, 'embedding', namespace))
86
+ }
87
+
88
+ static [Symbol.hasInstance] (instance) {
89
+ return instance instanceof Embeddings
90
+ }
91
+ }
92
+ })
66
93
 
67
- // OpenAI (and Embeddings in general) do not define an lc_namespace
68
- const namespace = ['langchain', 'embeddings', 'openai']
69
- shimmer.wrap(OpenAIEmbeddings.prototype, 'embedDocuments', embedDocuments =>
70
- wrapLangChainPromise(embedDocuments, 'embedding', namespace)
71
- )
72
- shimmer.wrap(OpenAIEmbeddings.prototype, 'embedQuery', embedQuery =>
73
- wrapLangChainPromise(embedQuery, 'embedding', namespace)
74
- )
75
94
  return exports
76
95
  })
77
96
  }
@@ -17,6 +17,7 @@ const {
17
17
 
18
18
  const {
19
19
  isNewTest,
20
+ getTestProperties,
20
21
  getSuitesByTestFile,
21
22
  runnableWrapper,
22
23
  getOnTestHandler,
@@ -62,7 +63,7 @@ const testSuiteCodeCoverageCh = channel('ci:mocha:test-suite:code-coverage')
62
63
  const libraryConfigurationCh = channel('ci:mocha:library-configuration')
63
64
  const knownTestsCh = channel('ci:mocha:known-tests')
64
65
  const skippableSuitesCh = channel('ci:mocha:test-suite:skippable')
65
- const quarantinedTestsCh = channel('ci:mocha:quarantined-tests')
66
+ const testManagementTestsCh = channel('ci:mocha:test-management-tests')
66
67
  const workerReportTraceCh = channel('ci:mocha:worker-report:trace')
67
68
  const testSessionStartCh = channel('ci:mocha:session:start')
68
69
  const testSessionFinishCh = channel('ci:mocha:session:finish')
@@ -76,7 +77,7 @@ function isTestFailed (test) {
76
77
  return test.isFailed()
77
78
  }
78
79
  if (test.isPending) {
79
- return !test.isPending() && test.state !== 'failed'
80
+ return !test.isPending() && test.state === 'failed'
80
81
  }
81
82
  return false
82
83
  }
@@ -138,7 +139,7 @@ function getOnEndHandler (isParallel) {
138
139
  }
139
140
 
140
141
  // We subtract the errors from quarantined tests from the total number of failures
141
- if (config.isQuarantinedTestsEnabled) {
142
+ if (config.isTestManagementTestsEnabled) {
142
143
  let numFailedQuarantinedTests = 0
143
144
  for (const test of testsQuarantined) {
144
145
  if (isTestFailed(test)) {
@@ -179,7 +180,7 @@ function getOnEndHandler (isParallel) {
179
180
  error,
180
181
  isEarlyFlakeDetectionEnabled: config.isEarlyFlakeDetectionEnabled,
181
182
  isEarlyFlakeDetectionFaulty: config.isEarlyFlakeDetectionFaulty,
182
- isQuarantinedTestsEnabled: config.isQuarantinedTestsEnabled,
183
+ isTestManagementEnabled: config.isTestManagementTestsEnabled,
183
184
  isParallel
184
185
  })
185
186
  })
@@ -188,12 +189,12 @@ function getOnEndHandler (isParallel) {
188
189
  function getExecutionConfiguration (runner, isParallel, onFinishRequest) {
189
190
  const mochaRunAsyncResource = new AsyncResource('bound-anonymous-fn')
190
191
 
191
- const onReceivedQuarantinedTests = ({ err, quarantinedTests: receivedQuarantinedTests }) => {
192
+ const onReceivedTestManagementTests = ({ err, testManagementTests: receivedTestManagementTests }) => {
192
193
  if (err) {
193
- config.quarantinedTests = {}
194
- config.isQuarantinedTestsEnabled = false
194
+ config.testManagementTests = {}
195
+ config.isTestManagementTestsEnabled = false
195
196
  } else {
196
- config.quarantinedTests = receivedQuarantinedTests
197
+ config.testManagementTests = receivedTestManagementTests
197
198
  }
198
199
  if (config.isSuitesSkippingEnabled) {
199
200
  skippableSuitesCh.publish({
@@ -236,9 +237,9 @@ function getExecutionConfiguration (runner, isParallel, onFinishRequest) {
236
237
  } else {
237
238
  config.knownTests = knownTests
238
239
  }
239
- if (config.isQuarantinedTestsEnabled) {
240
- quarantinedTestsCh.publish({
241
- onDone: mochaRunAsyncResource.bind(onReceivedQuarantinedTests)
240
+ if (config.isTestManagementTestsEnabled) {
241
+ testManagementTestsCh.publish({
242
+ onDone: mochaRunAsyncResource.bind(onReceivedTestManagementTests)
242
243
  })
243
244
  } else if (config.isSuitesSkippingEnabled) {
244
245
  skippableSuitesCh.publish({
@@ -258,19 +259,19 @@ function getExecutionConfiguration (runner, isParallel, onFinishRequest) {
258
259
  config.earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
259
260
  config.earlyFlakeDetectionFaultyThreshold = libraryConfig.earlyFlakeDetectionFaultyThreshold
260
261
  config.isKnownTestsEnabled = libraryConfig.isKnownTestsEnabled
261
- // ITR, auto test retries and quarantine are not supported in parallel mode yet
262
+ config.isTestManagementTestsEnabled = libraryConfig.isTestManagementEnabled
263
+ // ITR and auto test retries are not supported in parallel mode yet
262
264
  config.isSuitesSkippingEnabled = !isParallel && libraryConfig.isSuitesSkippingEnabled
263
265
  config.isFlakyTestRetriesEnabled = !isParallel && libraryConfig.isFlakyTestRetriesEnabled
264
266
  config.flakyTestRetriesCount = !isParallel && libraryConfig.flakyTestRetriesCount
265
- config.isQuarantinedTestsEnabled = !isParallel && libraryConfig.isQuarantinedTestsEnabled
266
267
 
267
268
  if (config.isKnownTestsEnabled) {
268
269
  knownTestsCh.publish({
269
270
  onDone: mochaRunAsyncResource.bind(onReceivedKnownTests)
270
271
  })
271
- } else if (config.isQuarantinedTestsEnabled) {
272
- quarantinedTestsCh.publish({
273
- onDone: mochaRunAsyncResource.bind(onReceivedQuarantinedTests)
272
+ } else if (config.isTestManagementTestsEnabled) {
273
+ testManagementTestsCh.publish({
274
+ onDone: mochaRunAsyncResource.bind(onReceivedTestManagementTests)
274
275
  })
275
276
  } else if (config.isSuitesSkippingEnabled) {
276
277
  skippableSuitesCh.publish({
@@ -282,7 +283,8 @@ function getExecutionConfiguration (runner, isParallel, onFinishRequest) {
282
283
  }
283
284
 
284
285
  libraryConfigurationCh.publish({
285
- onDone: mochaRunAsyncResource.bind(onReceivedConfiguration)
286
+ onDone: mochaRunAsyncResource.bind(onReceivedConfiguration),
287
+ isParallel
286
288
  })
287
289
  }
288
290
 
@@ -613,41 +615,54 @@ addHook({
613
615
  const { BufferedWorkerPool } = BufferedWorkerPoolPackage
614
616
 
615
617
  shimmer.wrap(BufferedWorkerPool.prototype, 'run', run => async function (testSuiteAbsolutePath, workerArgs) {
616
- if (!testStartCh.hasSubscribers || !config.isKnownTestsEnabled) {
618
+ if (!testStartCh.hasSubscribers || (!config.isKnownTestsEnabled && !config.isTestManagementTestsEnabled)) {
617
619
  return run.apply(this, arguments)
618
620
  }
619
621
 
620
622
  const testPath = getTestSuitePath(testSuiteAbsolutePath, process.cwd())
621
- const testSuiteKnownTests = config.knownTests.mocha?.[testPath] || []
622
- const testSuiteQuarantinedTests = config.quarantinedTests?.modules?.mocha?.suites?.[testPath] || []
623
+
624
+ const newWorkerArgs = { ...workerArgs }
625
+
626
+ if (config.isKnownTestsEnabled) {
627
+ const testSuiteKnownTests = config.knownTests.mocha?.[testPath] || []
628
+ newWorkerArgs._ddEfdNumRetries = config.earlyFlakeDetectionNumRetries
629
+ newWorkerArgs._ddIsEfdEnabled = config.isEarlyFlakeDetectionEnabled
630
+ newWorkerArgs._ddIsKnownTestsEnabled = true
631
+ newWorkerArgs._ddKnownTests = {
632
+ mocha: {
633
+ [testPath]: testSuiteKnownTests
634
+ }
635
+ }
636
+ }
637
+ if (config.isTestManagementTestsEnabled) {
638
+ const testSuiteTestManagementTests = config.testManagementTests?.mocha?.suites?.[testPath] || {}
639
+ newWorkerArgs._ddIsTestManagementTestsEnabled = true
640
+ newWorkerArgs._ddTestManagementTests = {
641
+ mocha: {
642
+ suites: {
643
+ [testPath]: testSuiteTestManagementTests
644
+ }
645
+ }
646
+ }
647
+ }
623
648
 
624
649
  // We pass the known tests for the test file to the worker
625
650
  const testFileResult = await run.apply(
626
651
  this,
627
652
  [
628
653
  testSuiteAbsolutePath,
629
- {
630
- ...workerArgs,
631
- _ddEfdNumRetries: config.earlyFlakeDetectionNumRetries,
632
- _ddIsEfdEnabled: config.isEarlyFlakeDetectionEnabled,
633
- _ddIsQuarantinedEnabled: config.isQuarantinedTestsEnabled,
634
- _ddQuarantinedTests: testSuiteQuarantinedTests,
635
- _ddKnownTests: {
636
- mocha: {
637
- [testPath]: testSuiteKnownTests
638
- }
639
- }
640
- }
654
+ newWorkerArgs
641
655
  ]
642
656
  )
657
+
643
658
  const tests = testFileResult
644
659
  .events
645
660
  .filter(event => event.eventName === 'test end')
646
661
  .map(event => event.data)
647
662
 
648
- // `newTests` is filled in the worker process, so we need to use the test results to fill it here too.
649
663
  for (const test of tests) {
650
- if (isNewTest(test, config.knownTests)) {
664
+ // `newTests` is filled in the worker process, so we need to use the test results to fill it here too.
665
+ if (config.isKnownTestsEnabled && isNewTest(test, config.knownTests)) {
651
666
  const testFullName = getTestFullName(test)
652
667
  const tests = newTests[testFullName]
653
668
 
@@ -657,6 +672,10 @@ addHook({
657
672
  tests.push(test)
658
673
  }
659
674
  }
675
+ // `testsQuarantined` is filled in the worker process, so we need to use the test results to fill it here too.
676
+ if (config.isTestManagementTestsEnabled && getTestProperties(test, config.testManagementTests).isQuarantined) {
677
+ testsQuarantined.add(test)
678
+ }
660
679
  }
661
680
  return testFileResult
662
681
  })
@@ -28,24 +28,26 @@ const wrappedFunctions = new WeakSet()
28
28
  const newTests = {}
29
29
  const testsQuarantined = new Set()
30
30
 
31
- function isQuarantinedTest (test, testsToQuarantine) {
31
+ function getAfterEachHooks (testOrHook) {
32
+ const hooks = []
33
+
34
+ while (testOrHook.parent) {
35
+ if (testOrHook.parent._afterEach) {
36
+ hooks.push(...testOrHook.parent._afterEach)
37
+ }
38
+ testOrHook = testOrHook.parent
39
+ }
40
+ return hooks
41
+ }
42
+
43
+ function getTestProperties (test, testManagementTests) {
32
44
  const testSuite = getTestSuitePath(test.file, process.cwd())
33
45
  const testName = test.fullTitle()
34
46
 
35
- const isQuarantined = (testsToQuarantine
36
- .mocha
37
- ?.suites
38
- ?.[testSuite]
39
- ?.tests
40
- ?.[testName]
41
- ?.properties
42
- ?.quarantined) ?? false
43
-
44
- if (isQuarantined) {
45
- testsQuarantined.add(test)
46
- }
47
+ const { disabled: isDisabled, quarantined: isQuarantined } =
48
+ testManagementTests?.mocha?.suites?.[testSuite]?.tests?.[testName]?.properties || {}
47
49
 
48
- return isQuarantined
50
+ return { isDisabled, isQuarantined }
49
51
  }
50
52
 
51
53
  function isNewTest (test, knownTests) {
@@ -193,6 +195,7 @@ function getOnTestHandler (isMain) {
193
195
  title,
194
196
  _ddIsNew: isNew,
195
197
  _ddIsEfdRetry: isEfdRetry,
198
+ _ddIsDisabled: isDisabled,
196
199
  _ddIsQuarantined: isQuarantined
197
200
  } = test
198
201
 
@@ -209,6 +212,7 @@ function getOnTestHandler (isMain) {
209
212
 
210
213
  testInfo.isNew = isNew
211
214
  testInfo.isEfdRetry = isEfdRetry
215
+ testInfo.isDisabled = isDisabled
212
216
  testInfo.isQuarantined = isQuarantined
213
217
  // We want to store the result of the new tests
214
218
  if (isNew) {
@@ -220,6 +224,10 @@ function getOnTestHandler (isMain) {
220
224
  }
221
225
  }
222
226
 
227
+ if (isDisabled) {
228
+ test.pending = true
229
+ }
230
+
223
231
  asyncResource.runInAsyncScope(() => {
224
232
  testStartCh.publish(testInfo)
225
233
  })
@@ -242,7 +250,7 @@ function getOnTestEndHandler () {
242
250
  }
243
251
 
244
252
  // if there are afterEach to be run, we don't finish the test yet
245
- if (asyncResource && !test.parent._afterEach.length) {
253
+ if (asyncResource && !getAfterEachHooks(test).length) {
246
254
  asyncResource.runInAsyncScope(() => {
247
255
  testFinishCh.publish({
248
256
  status,
@@ -257,18 +265,15 @@ function getOnTestEndHandler () {
257
265
  function getOnHookEndHandler () {
258
266
  return function (hook) {
259
267
  const test = hook.ctx.currentTest
260
- if (test && hook.parent._afterEach.includes(hook)) { // only if it's an afterEach
261
- const isLastRetry = getIsLastRetry(test)
262
- if (test._retries > 0 && !isLastRetry) {
263
- return
264
- }
265
- const isLastAfterEach = hook.parent._afterEach.indexOf(hook) === hook.parent._afterEach.length - 1
268
+ const afterEachHooks = getAfterEachHooks(hook)
269
+ if (test && afterEachHooks.includes(hook)) { // only if it's an afterEach
270
+ const isLastAfterEach = afterEachHooks.indexOf(hook) === afterEachHooks.length - 1
266
271
  if (isLastAfterEach) {
267
272
  const status = getTestStatus(test)
268
273
  const asyncResource = getTestAsyncResource(test)
269
274
  if (asyncResource) {
270
275
  asyncResource.runInAsyncScope(() => {
271
- testFinishCh.publish({ status, hasBeenRetried: isMochaRetry(test), isLastRetry })
276
+ testFinishCh.publish({ status, hasBeenRetried: isMochaRetry(test), isLastRetry: getIsLastRetry(test) })
272
277
  })
273
278
  }
274
279
  }
@@ -384,9 +389,13 @@ function getRunTestsWrapper (runTests, config) {
384
389
  })
385
390
  }
386
391
 
387
- if (config.isQuarantinedTestsEnabled) {
392
+ if (config.isTestManagementTestsEnabled) {
388
393
  suite.tests.forEach(test => {
389
- if (isQuarantinedTest(test, config.quarantinedTests)) {
394
+ const { isDisabled, isQuarantined } = getTestProperties(test, config.testManagementTests)
395
+ if (isDisabled) {
396
+ test._ddIsDisabled = true
397
+ } else if (isQuarantined) {
398
+ testsQuarantined.add(test)
390
399
  test._ddIsQuarantined = true
391
400
  }
392
401
  })
@@ -398,6 +407,7 @@ function getRunTestsWrapper (runTests, config) {
398
407
 
399
408
  module.exports = {
400
409
  isNewTest,
410
+ getTestProperties,
401
411
  retryTest,
402
412
  getSuitesByTestFile,
403
413
  isMochaRetry,
@@ -24,8 +24,7 @@ addHook({
24
24
  file: 'lib/mocha.js'
25
25
  }, (Mocha) => {
26
26
  shimmer.wrap(Mocha.prototype, 'run', run => function () {
27
- if (this.options._ddKnownTests) {
28
- // If there are known tests, it means isKnownTestsEnabled should be true
27
+ if (this.options._ddIsKnownTestsEnabled) {
29
28
  config.isKnownTestsEnabled = true
30
29
  config.isEarlyFlakeDetectionEnabled = this.options._ddIsEfdEnabled
31
30
  config.knownTests = this.options._ddKnownTests
@@ -33,13 +32,13 @@ addHook({
33
32
  delete this.options._ddIsEfdEnabled
34
33
  delete this.options._ddKnownTests
35
34
  delete this.options._ddEfdNumRetries
36
- delete this.options._ddQuarantinedTests
35
+ delete this.options._ddIsKnownTestsEnabled
37
36
  }
38
- if (this.options._ddIsQuarantinedEnabled) {
39
- config.isQuarantinedEnabled = true
40
- config.quarantinedTests = this.options._ddQuarantinedTests
41
- delete this.options._ddIsQuarantinedEnabled
42
- delete this.options._ddQuarantinedTests
37
+ if (this.options._ddIsTestManagementTestsEnabled) {
38
+ config.isTestManagementTestsEnabled = true
39
+ config.testManagementTests = this.options._ddTestManagementTests
40
+ delete this.options._ddIsTestManagementTestsEnabled
41
+ delete this.options._ddTestManagementTests
43
42
  }
44
43
  return run.apply(this, arguments)
45
44
  })
@@ -391,5 +391,5 @@ function finish (ctx, response, error) {
391
391
  }
392
392
 
393
393
  function getOption (args, option, defaultValue) {
394
- return args[args.length - 1]?.[option] || defaultValue
394
+ return args[0]?.[option] || defaultValue
395
395
  }