dd-trace 5.9.0 → 5.11.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 (54) hide show
  1. package/index.d.ts +15 -0
  2. package/package.json +3 -2
  3. package/packages/datadog-instrumentations/src/fetch.js +6 -45
  4. package/packages/datadog-instrumentations/src/helpers/fetch.js +17 -0
  5. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -1
  6. package/packages/datadog-instrumentations/src/jest.js +161 -14
  7. package/packages/datadog-instrumentations/src/kafkajs.js +4 -7
  8. package/packages/datadog-instrumentations/src/mongoose.js +2 -1
  9. package/packages/datadog-instrumentations/src/oracledb.js +1 -1
  10. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +6 -1
  11. package/packages/datadog-instrumentations/src/selenium.js +69 -0
  12. package/packages/datadog-plugin-cucumber/src/index.js +2 -2
  13. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +2 -2
  14. package/packages/datadog-plugin-cypress/src/support.js +19 -3
  15. package/packages/datadog-plugin-fetch/src/index.js +17 -11
  16. package/packages/datadog-plugin-jest/src/index.js +7 -2
  17. package/packages/datadog-plugin-mocha/src/index.js +4 -5
  18. package/packages/datadog-plugin-openai/src/services.js +2 -1
  19. package/packages/datadog-plugin-playwright/src/index.js +2 -2
  20. package/packages/datadog-plugin-selenium/src/index.js +71 -0
  21. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  22. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +70 -0
  23. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +14 -0
  24. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-rules.js +12 -0
  25. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-rule-type.js +6 -0
  26. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-analyzer.js +5 -50
  27. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-rules.js +742 -0
  28. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secrets-rules.js +539 -66
  29. package/packages/dd-trace/src/appsec/iast/analyzers/weak-hash-analyzer.js +6 -2
  30. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  31. package/packages/dd-trace/src/appsec/reporter.js +11 -10
  32. package/packages/dd-trace/src/appsec/telemetry.js +36 -7
  33. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +4 -2
  34. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +9 -2
  35. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -1
  36. package/packages/dd-trace/src/config.js +97 -10
  37. package/packages/dd-trace/src/dogstatsd.js +13 -11
  38. package/packages/dd-trace/src/index.js +5 -1
  39. package/packages/dd-trace/src/noop/dogstatsd.js +11 -0
  40. package/packages/dd-trace/src/noop/proxy.js +3 -0
  41. package/packages/dd-trace/src/opentracing/propagation/text_map.js +10 -4
  42. package/packages/dd-trace/src/opentracing/span.js +2 -0
  43. package/packages/dd-trace/src/plugins/index.js +2 -0
  44. package/packages/dd-trace/src/plugins/util/git.js +33 -11
  45. package/packages/dd-trace/src/plugins/util/test.js +34 -3
  46. package/packages/dd-trace/src/profiling/config.js +8 -4
  47. package/packages/dd-trace/src/profiling/exporters/agent.js +5 -3
  48. package/packages/dd-trace/src/profiling/profiler.js +4 -0
  49. package/packages/dd-trace/src/profiling/ssi-telemetry-mock-profiler.js +33 -0
  50. package/packages/dd-trace/src/profiling/ssi-telemetry.js +167 -0
  51. package/packages/dd-trace/src/proxy.js +7 -1
  52. package/packages/dd-trace/src/tagger.js +13 -3
  53. package/packages/dd-trace/src/telemetry/index.js +5 -4
  54. package/packages/dd-trace/src/telemetry/metrics.js +2 -2
package/index.d.ts CHANGED
@@ -187,6 +187,7 @@ interface Plugins {
187
187
  "restify": tracer.plugins.restify;
188
188
  "rhea": tracer.plugins.rhea;
189
189
  "router": tracer.plugins.router;
190
+ "selenium": tracer.plugins.selenium;
190
191
  "sharedb": tracer.plugins.sharedb;
191
192
  "tedious": tracer.plugins.tedious;
192
193
  "winston": tracer.plugins.winston;
@@ -789,6 +790,14 @@ declare namespace tracer {
789
790
  */
790
791
  gauge(stat: string, value?: number, tags?: { [tag: string]: string|number }): void
791
792
 
793
+ /**
794
+ * Sets a histogram value, optionally specifying tags.
795
+ * @param {string} stat The dot-separated metric name.
796
+ * @param {number} value The amount to increment the stat by.
797
+ * @param {[tag:string]:string|number} tags Tags to pass along, such as `{ foo: 'bar' }`. Values are combined with config.tags.
798
+ */
799
+ histogram(stat: string, value?: number, tags?: { [tag: string]: string|number }): void
800
+
792
801
  /**
793
802
  * Forces any unsent metrics to be sent
794
803
  *
@@ -1728,6 +1737,12 @@ declare namespace tracer {
1728
1737
  */
1729
1738
  interface router extends Integration {}
1730
1739
 
1740
+ /**
1741
+ * This plugin automatically instruments the
1742
+ * [selenium-webdriver](https://www.npmjs.com/package/selenium-webdriver) module.
1743
+ */
1744
+ interface selenium extends Integration {}
1745
+
1731
1746
  /**
1732
1747
  * This plugin automatically instruments the
1733
1748
  * [sharedb](https://github.com/share/sharedb) module.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "5.9.0",
3
+ "version": "5.11.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -36,6 +36,7 @@
36
36
  "test:integration:cucumber": "mocha --colors --timeout 30000 \"integration-tests/cucumber/*.spec.js\"",
37
37
  "test:integration:cypress": "mocha --colors --timeout 30000 \"integration-tests/cypress/*.spec.js\"",
38
38
  "test:integration:playwright": "mocha --colors --timeout 30000 \"integration-tests/playwright/*.spec.js\"",
39
+ "test:integration:selenium": "mocha --colors --timeout 30000 \"integration-tests/selenium/*.spec.js\"",
39
40
  "test:integration:profiler": "mocha --colors --timeout 90000 \"integration-tests/profiler/*.spec.js\"",
40
41
  "test:integration:serverless": "mocha --colors --timeout 30000 \"integration-tests/serverless/*.spec.js\"",
41
42
  "test:integration:plugins": "mocha --colors --exit -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/datadog-plugin-@($(echo $PLUGINS))/test/integration-test/**/*.spec.js\"",
@@ -69,7 +70,7 @@
69
70
  "node": ">=18"
70
71
  },
71
72
  "dependencies": {
72
- "@datadog/native-appsec": "7.1.0",
73
+ "@datadog/native-appsec": "7.1.1",
73
74
  "@datadog/native-iast-rewriter": "2.3.0",
74
75
  "@datadog/native-iast-taint-tracking": "1.7.0",
75
76
  "@datadog/native-metrics": "^2.0.0",
@@ -1,51 +1,12 @@
1
1
  'use strict'
2
2
 
3
3
  const shimmer = require('../../datadog-shimmer')
4
- const { channel } = require('./helpers/instrument')
5
-
6
- const startChannel = channel('apm:fetch:request:start')
7
- const finishChannel = channel('apm:fetch:request:finish')
8
- const errorChannel = channel('apm:fetch:request:error')
9
-
10
- function wrapFetch (fetch, Request) {
11
- if (typeof fetch !== 'function') return fetch
12
-
13
- return function (input, init) {
14
- if (!startChannel.hasSubscribers) return fetch.apply(this, arguments)
15
-
16
- const req = new Request(input, init)
17
- const headers = req.headers
18
- const message = { req, headers }
19
-
20
- return startChannel.runStores(message, () => {
21
- // Request object is read-only so we need new objects to change headers.
22
- arguments[0] = message.req
23
- arguments[1] = { headers: message.headers }
24
-
25
- return fetch.apply(this, arguments)
26
- .then(
27
- res => {
28
- message.res = res
29
-
30
- finishChannel.publish(message)
31
-
32
- return res
33
- },
34
- err => {
35
- if (err.name !== 'AbortError') {
36
- message.error = err
37
- errorChannel.publish(message)
38
- }
39
-
40
- finishChannel.publish(message)
41
-
42
- throw err
43
- }
44
- )
45
- })
46
- }
47
- }
4
+ const { tracingChannel } = require('dc-polyfill')
5
+ const { createWrapFetch } = require('./helpers/fetch')
48
6
 
49
7
  if (globalThis.fetch) {
50
- globalThis.fetch = shimmer.wrap(fetch, wrapFetch(fetch, globalThis.Request))
8
+ const ch = tracingChannel('apm:fetch:request')
9
+ const wrapFetch = createWrapFetch(globalThis.Request, ch)
10
+
11
+ globalThis.fetch = shimmer.wrap(fetch, wrapFetch(fetch))
51
12
  }
@@ -0,0 +1,17 @@
1
+ 'use strict'
2
+
3
+ exports.createWrapFetch = function createWrapFetch (Request, ch) {
4
+ return function wrapFetch (fetch) {
5
+ if (typeof fetch !== 'function') return fetch
6
+
7
+ return function (input, init) {
8
+ if (!ch.start.hasSubscribers) return fetch.apply(this, arguments)
9
+
10
+ const req = new Request(input, init)
11
+ const headers = req.headers
12
+ const ctx = { req, headers }
13
+
14
+ return ch.tracePromise(() => fetch.call(this, req, { headers: ctx.headers }), ctx)
15
+ }
16
+ }
17
+ }
@@ -58,6 +58,7 @@ module.exports = {
58
58
  'jest-environment-node': () => require('../jest'),
59
59
  'jest-environment-jsdom': () => require('../jest'),
60
60
  'jest-jasmine2': () => require('../jest'),
61
+ 'jest-runtime': () => require('../jest'),
61
62
  'jest-worker': () => require('../jest'),
62
63
  knex: () => require('../knex'),
63
64
  koa: () => require('../koa'),
@@ -103,8 +104,9 @@ module.exports = {
103
104
  restify: () => require('../restify'),
104
105
  rhea: () => require('../rhea'),
105
106
  router: () => require('../router'),
106
- sharedb: () => require('../sharedb'),
107
+ 'selenium-webdriver': () => require('../selenium'),
107
108
  sequelize: () => require('../sequelize'),
109
+ sharedb: () => require('../sharedb'),
108
110
  tedious: () => require('../tedious'),
109
111
  when: () => require('../when'),
110
112
  winston: () => require('../winston')
@@ -11,7 +11,8 @@ const {
11
11
  getTestSuitePath,
12
12
  getTestParametersString,
13
13
  addEfdStringToTestName,
14
- removeEfdStringFromTestName
14
+ removeEfdStringFromTestName,
15
+ getIsFaultyEarlyFlakeDetection
15
16
  } = require('../../dd-trace/src/plugins/util/test')
16
17
  const {
17
18
  getFormattedJestTestParameters,
@@ -44,11 +45,14 @@ const knownTestsCh = channel('ci:jest:known-tests')
44
45
 
45
46
  const itrSkippedSuitesCh = channel('ci:jest:itr:skipped-suites')
46
47
 
48
+ // Message sent by jest's main process to workers to run a test suite (=test file)
49
+ // https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-worker/src/types.ts#L37
50
+ const CHILD_MESSAGE_CALL = 1
47
51
  // Maximum time we'll wait for the tracer to flush
48
52
  const FLUSH_TIMEOUT = 10000
49
53
 
50
54
  let skippableSuites = []
51
- let knownTests = []
55
+ let knownTests = {}
52
56
  let isCodeCoverageEnabled = false
53
57
  let isSuitesSkippingEnabled = false
54
58
  let isUserCodeCoverageEnabled = false
@@ -58,6 +62,8 @@ let hasUnskippableSuites = false
58
62
  let hasForcedToRunSuites = false
59
63
  let isEarlyFlakeDetectionEnabled = false
60
64
  let earlyFlakeDetectionNumRetries = 0
65
+ let earlyFlakeDetectionFaultyThreshold = 30
66
+ let isEarlyFlakeDetectionFaulty = false
61
67
  let hasFilteredSkippableSuites = false
62
68
 
63
69
  const sessionAsyncResource = new AsyncResource('bound-anonymous-fn')
@@ -73,6 +79,7 @@ const specStatusToTestStatus = {
73
79
  const asyncResources = new WeakMap()
74
80
  const originalTestFns = new WeakMap()
75
81
  const retriedTestsToNumAttempts = new Map()
82
+ const newTestsTestStatuses = new Map()
76
83
 
77
84
  // based on https://github.com/facebook/jest/blob/main/packages/jest-circus/src/formatNodeAssertErrors.ts#L41
78
85
  function formatJestError (errors) {
@@ -101,6 +108,13 @@ function getTestEnvironmentOptions (config) {
101
108
  return {}
102
109
  }
103
110
 
111
+ function getEfdStats (testStatuses) {
112
+ return testStatuses.reduce((acc, testStatus) => {
113
+ acc[testStatus]++
114
+ return acc
115
+ }, { pass: 0, fail: 0 })
116
+ }
117
+
104
118
  function getWrappedEnvironment (BaseEnvironment, jestVersion) {
105
119
  return class DatadogEnvironment extends BaseEnvironment {
106
120
  constructor (config, context) {
@@ -110,6 +124,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
110
124
  this.testSuite = getTestSuitePath(context.testPath, rootDir)
111
125
  this.nameToParams = {}
112
126
  this.global._ddtrace = global._ddtrace
127
+ this.hasSnapshotTests = undefined
113
128
 
114
129
  this.displayName = config.projectConfig?.displayName?.name
115
130
  this.testEnvironmentOptions = getTestEnvironmentOptions(config)
@@ -123,9 +138,12 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
123
138
  this.isEarlyFlakeDetectionEnabled = this.testEnvironmentOptions._ddIsEarlyFlakeDetectionEnabled
124
139
 
125
140
  if (this.isEarlyFlakeDetectionEnabled) {
141
+ const hasKnownTests = !!knownTests.jest
126
142
  earlyFlakeDetectionNumRetries = this.testEnvironmentOptions._ddEarlyFlakeDetectionNumRetries
127
143
  try {
128
- this.knownTestsForThisSuite = this.getKnownTestsForSuite(this.testEnvironmentOptions._ddKnownTests)
144
+ this.knownTestsForThisSuite = hasKnownTests
145
+ ? (knownTests.jest[this.testSuite] || [])
146
+ : this.getKnownTestsForSuite(this.testEnvironmentOptions._ddKnownTests)
129
147
  } catch (e) {
130
148
  // If there has been an error parsing the tests, we'll disable Early Flake Deteciton
131
149
  this.isEarlyFlakeDetectionEnabled = false
@@ -133,6 +151,21 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
133
151
  }
134
152
  }
135
153
 
154
+ getHasSnapshotTests () {
155
+ if (this.hasSnapshotTests !== undefined) {
156
+ return this.hasSnapshotTests
157
+ }
158
+ let hasSnapshotTests = true
159
+ try {
160
+ const { _snapshotData } = this.context.expect.getState().snapshotState
161
+ hasSnapshotTests = Object.keys(_snapshotData).length > 0
162
+ } catch (e) {
163
+ // if we can't be sure, we'll err on the side of caution and assume it has snapshots
164
+ }
165
+ this.hasSnapshotTests = hasSnapshotTests
166
+ return hasSnapshotTests
167
+ }
168
+
136
169
  // Function that receives a list of known tests for a test service and
137
170
  // returns the ones that belong to the current suite
138
171
  getKnownTestsForSuite (knownTests) {
@@ -145,7 +178,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
145
178
  if (typeof knownTestsForSuite === 'string') {
146
179
  knownTestsForSuite = JSON.parse(knownTestsForSuite)
147
180
  }
148
- return knownTestsForSuite.jest?.[this.testSuite] || []
181
+ return knownTestsForSuite
149
182
  }
150
183
 
151
184
  // Add the `add_test` event we don't have the test object yet, so
@@ -217,6 +250,13 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
217
250
  const isSkipped = event.mode === 'todo' || event.mode === 'skip'
218
251
  if (isNew && !isSkipped && !retriedTestsToNumAttempts.has(testName)) {
219
252
  retriedTestsToNumAttempts.set(testName, 0)
253
+ // Retrying snapshots has proven to be problematic, so we'll skip them for now
254
+ // We'll still detect new tests, but we won't retry them.
255
+ // TODO: do not bail out of EFD with the whole test suite
256
+ if (this.getHasSnapshotTests()) {
257
+ log.warn('Early flake detection is disabled for suites with snapshots')
258
+ return
259
+ }
220
260
  for (let retryIndex = 0; retryIndex < earlyFlakeDetectionNumRetries; retryIndex++) {
221
261
  if (this.global.test) {
222
262
  this.global.test(addEfdStringToTestName(event.testName, retryIndex), event.fn, event.timeout)
@@ -242,6 +282,19 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
242
282
  })
243
283
  // restore in case it is retried
244
284
  event.test.fn = originalTestFns.get(event.test)
285
+ // We'll store the test statuses of the retries
286
+ if (this.isEarlyFlakeDetectionEnabled) {
287
+ const testName = getJestTestName(event.test)
288
+ const originalTestName = removeEfdStringFromTestName(testName)
289
+ const isNewTest = retriedTestsToNumAttempts.has(originalTestName)
290
+ if (isNewTest) {
291
+ if (newTestsTestStatuses.has(originalTestName)) {
292
+ newTestsTestStatuses.get(originalTestName).push(status)
293
+ } else {
294
+ newTestsTestStatuses.set(originalTestName, [status])
295
+ }
296
+ }
297
+ }
245
298
  })
246
299
  }
247
300
  if (event.name === 'test_skip' || event.name === 'test_todo') {
@@ -285,7 +338,7 @@ function applySuiteSkipping (originalTests, rootDir, frameworkVersion) {
285
338
  numSkippedSuites = jestSuitesToRun.skippedSuites.length
286
339
 
287
340
  itrSkippedSuitesCh.publish({ skippedSuites: jestSuitesToRun.skippedSuites, frameworkVersion })
288
- skippableSuites = []
341
+
289
342
  return jestSuitesToRun.suitesToRun
290
343
  }
291
344
 
@@ -383,6 +436,7 @@ function cliWrapper (cli, jestVersion) {
383
436
  isSuitesSkippingEnabled = libraryConfig.isSuitesSkippingEnabled
384
437
  isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
385
438
  earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
439
+ earlyFlakeDetectionFaultyThreshold = libraryConfig.earlyFlakeDetectionFaultyThreshold
386
440
  }
387
441
  } catch (err) {
388
442
  log.error(err)
@@ -497,6 +551,7 @@ function cliWrapper (cli, jestVersion) {
497
551
  hasForcedToRunSuites,
498
552
  error,
499
553
  isEarlyFlakeDetectionEnabled,
554
+ isEarlyFlakeDetectionFaulty,
500
555
  onDone
501
556
  })
502
557
  })
@@ -508,6 +563,28 @@ function cliWrapper (cli, jestVersion) {
508
563
 
509
564
  numSkippedSuites = 0
510
565
 
566
+ /**
567
+ * If Early Flake Detection (EFD) is enabled the logic is as follows:
568
+ * - If all attempts for a test are failing, the test has failed and we will let the test process fail.
569
+ * - If just a single attempt passes, we will prevent the test process from failing.
570
+ * The rationale behind is the following: you may still be able to block your CI pipeline by gating
571
+ * on flakiness (the test will be considered flaky), but you may choose to unblock the pipeline too.
572
+ */
573
+
574
+ if (isEarlyFlakeDetectionEnabled) {
575
+ let numFailedTestsToIgnore = 0
576
+ for (const testStatuses of newTestsTestStatuses.values()) {
577
+ const { pass, fail } = getEfdStats(testStatuses)
578
+ if (pass > 0) { // as long as one passes, we'll consider the test passed
579
+ numFailedTestsToIgnore += fail
580
+ }
581
+ }
582
+ // If every test that failed was an EFD retry, we'll consider the suite passed
583
+ if (numFailedTestsToIgnore !== 0 && result.results.numFailedTests === numFailedTestsToIgnore) {
584
+ result.results.success = true
585
+ }
586
+ }
587
+
511
588
  return result
512
589
  })
513
590
 
@@ -619,7 +696,6 @@ function configureTestEnvironment (readConfigsResult) {
619
696
  // because `jestAdapterWrapper` runs in a different process. We have to go through `testEnvironmentOptions`
620
697
  configs.forEach(config => {
621
698
  config.testEnvironmentOptions._ddTestCodeCoverageEnabled = isCodeCoverageEnabled
622
- config.testEnvironmentOptions._ddKnownTests = knownTests
623
699
  })
624
700
 
625
701
  isUserCodeCoverageEnabled = !!readConfigsResult.globalConfig.collectCoverage
@@ -712,13 +788,26 @@ addHook({
712
788
  const SearchSource = searchSourcePackage.default ? searchSourcePackage.default : searchSourcePackage
713
789
 
714
790
  shimmer.wrap(SearchSource.prototype, 'getTestPaths', getTestPaths => async function () {
715
- if (!isSuitesSkippingEnabled || !skippableSuites.length) {
716
- return getTestPaths.apply(this, arguments)
717
- }
718
-
791
+ const testPaths = await getTestPaths.apply(this, arguments)
719
792
  const [{ rootDir, shard }] = arguments
720
793
 
721
- if (shard?.shardCount > 1) {
794
+ if (isEarlyFlakeDetectionEnabled) {
795
+ const projectSuites = testPaths.tests.map(test => getTestSuitePath(test.path, test.context.config.rootDir))
796
+ const isFaulty =
797
+ getIsFaultyEarlyFlakeDetection(projectSuites, knownTests.jest || {}, earlyFlakeDetectionFaultyThreshold)
798
+ if (isFaulty) {
799
+ log.error('Early flake detection is disabled because the number of new suites is too high.')
800
+ isEarlyFlakeDetectionEnabled = false
801
+ const testEnvironmentOptions = testPaths.tests[0]?.context?.config?.testEnvironmentOptions
802
+ // Project config is shared among all tests, so we can modify it here
803
+ if (testEnvironmentOptions) {
804
+ testEnvironmentOptions._ddIsEarlyFlakeDetectionEnabled = false
805
+ }
806
+ isEarlyFlakeDetectionFaulty = true
807
+ }
808
+ }
809
+
810
+ if (shard?.shardCount > 1 || !isSuitesSkippingEnabled || !skippableSuites.length) {
722
811
  // If the user is using jest sharding, we want to apply the filtering of tests in the shard process.
723
812
  // The reason for this is the following:
724
813
  // The tests for different shards are likely being run in different CI jobs so
@@ -726,10 +815,8 @@ addHook({
726
815
  // If the skippable endpoint is returning different suites and we filter the list of tests here,
727
816
  // the base list of tests that is used for sharding might be different,
728
817
  // causing the shards to potentially run the same suite.
729
- return getTestPaths.apply(this, arguments)
818
+ return testPaths
730
819
  }
731
-
732
- const testPaths = await getTestPaths.apply(this, arguments)
733
820
  const { tests } = testPaths
734
821
 
735
822
  const suitesToRun = applySuiteSkipping(tests, rootDir, frameworkVersion)
@@ -789,12 +876,72 @@ if (DD_MAJOR < 4) {
789
876
  }, jasmineAsyncInstallWraper)
790
877
  }
791
878
 
879
+ const LIBRARIES_BYPASSING_JEST_REQUIRE_ENGINE = [
880
+ 'selenium-webdriver'
881
+ ]
882
+
883
+ function shouldBypassJestRequireEngine (moduleName) {
884
+ return (
885
+ LIBRARIES_BYPASSING_JEST_REQUIRE_ENGINE.some(library => moduleName.includes(library))
886
+ )
887
+ }
888
+
889
+ addHook({
890
+ name: 'jest-runtime',
891
+ versions: ['>=24.8.0']
892
+ }, (runtimePackage) => {
893
+ const Runtime = runtimePackage.default ? runtimePackage.default : runtimePackage
894
+
895
+ shimmer.wrap(Runtime.prototype, 'requireModuleOrMock', requireModuleOrMock => function (from, moduleName) {
896
+ // TODO: do this for every library that we instrument
897
+ if (shouldBypassJestRequireEngine(moduleName)) {
898
+ // To bypass jest's own require engine
899
+ return this._requireCoreModule(moduleName)
900
+ }
901
+ return requireModuleOrMock.apply(this, arguments)
902
+ })
903
+
904
+ return runtimePackage
905
+ })
906
+
792
907
  addHook({
793
908
  name: 'jest-worker',
794
909
  versions: ['>=24.9.0'],
795
910
  file: 'build/workers/ChildProcessWorker.js'
796
911
  }, (childProcessWorker) => {
797
912
  const ChildProcessWorker = childProcessWorker.default
913
+ shimmer.wrap(ChildProcessWorker.prototype, 'send', send => function (request) {
914
+ if (!isEarlyFlakeDetectionEnabled) {
915
+ return send.apply(this, arguments)
916
+ }
917
+ const [type] = request
918
+ // eslint-disable-next-line
919
+ // https://github.com/jestjs/jest/blob/1d682f21c7a35da4d3ab3a1436a357b980ebd0fa/packages/jest-worker/src/workers/ChildProcessWorker.ts#L424
920
+ if (type === CHILD_MESSAGE_CALL) {
921
+ // This is the message that the main process sends to the worker to run a test suite (=test file).
922
+ // In here we modify the config.testEnvironmentOptions to include the known tests for the suite.
923
+ // This way the suite only knows about the tests that are part of it.
924
+ const args = request[request.length - 1]
925
+ if (args.length > 1) {
926
+ return send.apply(this, arguments)
927
+ }
928
+ if (!args[0]?.config) {
929
+ return send.apply(this, arguments)
930
+ }
931
+ const [{ globalConfig, config, path: testSuiteAbsolutePath }] = args
932
+ const testSuite = getTestSuitePath(testSuiteAbsolutePath, globalConfig.rootDir || process.cwd())
933
+ const suiteKnownTests = knownTests.jest?.[testSuite] || []
934
+ args[0].config = {
935
+ ...config,
936
+ testEnvironmentOptions: {
937
+ ...config.testEnvironmentOptions,
938
+ _ddKnownTests: suiteKnownTests
939
+ }
940
+ }
941
+ }
942
+
943
+ return send.apply(this, arguments)
944
+ })
798
945
  shimmer.wrap(ChildProcessWorker.prototype, '_onMessage', _onMessage => function () {
799
946
  const [code, data] = arguments[0]
800
947
  if (code === JEST_WORKER_TRACE_PAYLOAD_CODE) { // datadog trace payload
@@ -68,7 +68,10 @@ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKaf
68
68
  const result = send.apply(this, arguments)
69
69
 
70
70
  result.then(
71
- innerAsyncResource.bind(() => producerFinishCh.publish(undefined)),
71
+ innerAsyncResource.bind(res => {
72
+ producerFinishCh.publish(undefined)
73
+ producerCommitCh.publish(res)
74
+ }),
72
75
  innerAsyncResource.bind(err => {
73
76
  if (err) {
74
77
  producerErrorCh.publish(err)
@@ -77,12 +80,6 @@ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKaf
77
80
  })
78
81
  )
79
82
 
80
- result.then(res => {
81
- if (producerCommitCh.hasSubscribers) {
82
- producerCommitCh.publish(res)
83
- }
84
- })
85
-
86
83
  return result
87
84
  } catch (e) {
88
85
  producerErrorCh.publish(e)
@@ -21,7 +21,8 @@ addHook({
21
21
  name: 'mongoose',
22
22
  versions: ['>=4.6.4 <5', '5', '6', '>=7']
23
23
  }, mongoose => {
24
- if (mongoose.Promise !== global.Promise) {
24
+ // As of Mongoose 7, custom promise libraries are no longer supported and mongoose.Promise may be undefined
25
+ if (mongoose.Promise && mongoose.Promise !== global.Promise) {
25
26
  shimmer.wrap(mongoose.Promise.prototype, 'then', wrapThen)
26
27
  }
27
28
 
@@ -21,7 +21,7 @@ function finish (err) {
21
21
  finishChannel.publish(undefined)
22
22
  }
23
23
 
24
- addHook({ name: 'oracledb', versions: ['5'] }, oracledb => {
24
+ addHook({ name: 'oracledb', versions: ['>=5'] }, oracledb => {
25
25
  shimmer.wrap(oracledb.Connection.prototype, 'execute', execute => {
26
26
  return function wrappedExecute (dbQuery, ...args) {
27
27
  if (!startChannel.hasSubscribers) {
@@ -4,7 +4,12 @@ const { addHook } = require('./helpers/instrument')
4
4
  const shimmer = require('../../datadog-shimmer')
5
5
  const tracer = require('../../dd-trace')
6
6
 
7
- if (process.env.DD_TRACE_OTEL_ENABLED) {
7
+ const otelSdkEnabled = process.env.DD_TRACE_OTEL_ENABLED ||
8
+ process.env.OTEL_SDK_DISABLED
9
+ ? !process.env.OTEL_SDK_DISABLED
10
+ : undefined
11
+
12
+ if (otelSdkEnabled) {
8
13
  addHook({
9
14
  name: '@opentelemetry/sdk-trace-node',
10
15
  file: 'build/src/NodeTracerProvider.js',
@@ -0,0 +1,69 @@
1
+ const { addHook, channel } = require('./helpers/instrument')
2
+ const shimmer = require('../../datadog-shimmer')
3
+
4
+ const ciSeleniumDriverGetStartCh = channel('ci:selenium:driver:get')
5
+
6
+ const RUM_STOP_SESSION_SCRIPT = `
7
+ if (window.DD_RUM && window.DD_RUM.stopSession) {
8
+ window.DD_RUM.stopSession();
9
+ return true;
10
+ } else {
11
+ return false;
12
+ }
13
+ `
14
+ const IS_RUM_ACTIVE_SCRIPT = 'return !!window.DD_RUM'
15
+
16
+ const DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS = 500
17
+ const DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME = 'datadog-ci-visibility-test-execution-id'
18
+
19
+ // TODO: can we increase the supported version range?
20
+ addHook({
21
+ name: 'selenium-webdriver',
22
+ versions: ['>=4.11.0']
23
+ }, (seleniumPackage, seleniumVersion) => {
24
+ // TODO: do not turn this into async. Use promises
25
+ shimmer.wrap(seleniumPackage.WebDriver.prototype, 'get', get => async function () {
26
+ let traceId
27
+ const setTraceId = (inputTraceId) => {
28
+ traceId = inputTraceId
29
+ }
30
+ const getResult = await get.apply(this, arguments)
31
+
32
+ const isRumActive = await this.executeScript(IS_RUM_ACTIVE_SCRIPT)
33
+ const capabilities = await this.getCapabilities()
34
+
35
+ ciSeleniumDriverGetStartCh.publish({
36
+ setTraceId,
37
+ seleniumVersion,
38
+ browserName: capabilities.getBrowserName(),
39
+ browserVersion: capabilities.getBrowserVersion(),
40
+ isRumActive
41
+ })
42
+
43
+ await this.manage().addCookie({
44
+ name: DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME,
45
+ value: traceId
46
+ })
47
+
48
+ return getResult
49
+ })
50
+
51
+ shimmer.wrap(seleniumPackage.WebDriver.prototype, 'quit', quit => async function () {
52
+ const isRumActive = await this.executeScript(RUM_STOP_SESSION_SCRIPT)
53
+
54
+ if (isRumActive) {
55
+ // We'll have time for RUM to flush the events (there's no callback to know when it's done)
56
+ await new Promise(resolve => {
57
+ setTimeout(() => {
58
+ resolve()
59
+ }, DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS)
60
+ })
61
+ }
62
+
63
+ await this.manage().deleteCookie(DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME)
64
+
65
+ return quit.apply(this, arguments)
66
+ })
67
+
68
+ return seleniumPackage
69
+ })
@@ -16,7 +16,7 @@ const {
16
16
  TEST_CODE_OWNERS,
17
17
  ITR_CORRELATION_ID,
18
18
  TEST_SOURCE_FILE,
19
- TEST_EARLY_FLAKE_IS_ENABLED,
19
+ TEST_EARLY_FLAKE_ENABLED,
20
20
  TEST_IS_NEW,
21
21
  TEST_IS_RETRY
22
22
  } = require('../../dd-trace/src/plugins/util/test')
@@ -68,7 +68,7 @@ class CucumberPlugin extends CiPlugin {
68
68
  }
69
69
  )
70
70
  if (isEarlyFlakeDetectionEnabled) {
71
- this.testSessionSpan.setTag(TEST_EARLY_FLAKE_IS_ENABLED, 'true')
71
+ this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ENABLED, 'true')
72
72
  }
73
73
 
74
74
  this.testSessionSpan.setTag(TEST_STATUS, status)
@@ -28,7 +28,7 @@ const {
28
28
  TEST_SOURCE_FILE,
29
29
  TEST_IS_NEW,
30
30
  TEST_IS_RETRY,
31
- TEST_EARLY_FLAKE_IS_ENABLED
31
+ TEST_EARLY_FLAKE_ENABLED
32
32
  } = require('../../dd-trace/src/plugins/util/test')
33
33
  const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
34
34
  const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
@@ -364,7 +364,7 @@ class CypressPlugin {
364
364
  getTestModuleCommonTags(this.command, this.frameworkVersion, TEST_FRAMEWORK_NAME)
365
365
 
366
366
  if (this.isEarlyFlakeDetectionEnabled) {
367
- testSessionSpanMetadata[TEST_EARLY_FLAKE_IS_ENABLED] = 'true'
367
+ testSessionSpanMetadata[TEST_EARLY_FLAKE_ENABLED] = 'true'
368
368
  }
369
369
 
370
370
  this.testSessionSpan = this.tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test_session`, {