dd-trace 5.35.0 → 5.37.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 (125) hide show
  1. package/LICENSE-3rdparty.csv +2 -1
  2. package/index.d.ts +8 -7
  3. package/loader-hook.mjs +0 -4
  4. package/package.json +15 -14
  5. package/packages/datadog-core/index.js +1 -1
  6. package/packages/datadog-core/src/storage.js +76 -31
  7. package/packages/datadog-instrumentations/src/cucumber.js +54 -1
  8. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -2
  9. package/packages/datadog-instrumentations/src/helpers/register.js +2 -2
  10. package/packages/datadog-instrumentations/src/jest.js +105 -11
  11. package/packages/datadog-instrumentations/src/mocha/main.js +46 -4
  12. package/packages/datadog-instrumentations/src/mocha/utils.js +35 -2
  13. package/packages/datadog-instrumentations/src/mocha/worker.js +7 -0
  14. package/packages/datadog-instrumentations/src/mysql2.js +3 -3
  15. package/packages/datadog-instrumentations/src/openai.js +8 -0
  16. package/packages/datadog-instrumentations/src/playwright.js +70 -22
  17. package/packages/datadog-instrumentations/src/vitest.js +60 -6
  18. package/packages/datadog-plugin-aerospike/src/index.js +1 -1
  19. package/packages/datadog-plugin-apollo/src/gateway/fetch.js +1 -1
  20. package/packages/datadog-plugin-apollo/src/gateway/index.js +1 -1
  21. package/packages/datadog-plugin-apollo/src/gateway/request.js +1 -1
  22. package/packages/datadog-plugin-aws-sdk/src/base.js +3 -3
  23. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +4 -4
  24. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +2 -2
  25. package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
  26. package/packages/datadog-plugin-couchbase/src/index.js +2 -2
  27. package/packages/datadog-plugin-cucumber/src/index.js +31 -14
  28. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +72 -7
  29. package/packages/datadog-plugin-cypress/src/support.js +36 -29
  30. package/packages/datadog-plugin-dd-trace-api/src/index.js +1 -3
  31. package/packages/datadog-plugin-graphql/src/utils.js +8 -1
  32. package/packages/datadog-plugin-grpc/src/client.js +1 -1
  33. package/packages/datadog-plugin-grpc/src/server.js +1 -1
  34. package/packages/datadog-plugin-hapi/src/index.js +1 -1
  35. package/packages/datadog-plugin-http/src/client.js +1 -1
  36. package/packages/datadog-plugin-http/src/server.js +1 -1
  37. package/packages/datadog-plugin-http2/src/client.js +3 -3
  38. package/packages/datadog-plugin-http2/src/server.js +1 -1
  39. package/packages/datadog-plugin-jest/src/index.js +17 -12
  40. package/packages/datadog-plugin-langchain/src/tracing.js +1 -1
  41. package/packages/datadog-plugin-mariadb/src/index.js +3 -3
  42. package/packages/datadog-plugin-mocha/src/index.js +35 -16
  43. package/packages/datadog-plugin-mongodb-core/src/index.js +28 -2
  44. package/packages/datadog-plugin-next/src/index.js +4 -4
  45. package/packages/datadog-plugin-openai/src/tracing.js +2 -3
  46. package/packages/datadog-plugin-playwright/src/index.js +35 -9
  47. package/packages/datadog-plugin-rhea/src/consumer.js +1 -1
  48. package/packages/datadog-plugin-router/src/index.js +2 -2
  49. package/packages/datadog-plugin-selenium/src/index.js +1 -1
  50. package/packages/datadog-plugin-vitest/src/index.js +36 -12
  51. package/packages/dd-trace/src/appsec/graphql.js +6 -6
  52. package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +3 -7
  53. package/packages/dd-trace/src/appsec/iast/analyzers/injection-analyzer.js +15 -1
  54. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +17 -30
  55. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +2 -2
  56. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +7 -11
  57. package/packages/dd-trace/src/appsec/iast/analyzers/stored-injection-analyzer.js +11 -0
  58. package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +2 -6
  59. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +24 -4
  60. package/packages/dd-trace/src/appsec/iast/context/context-plugin.js +2 -2
  61. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +2 -2
  62. package/packages/dd-trace/src/appsec/iast/index.js +4 -2
  63. package/packages/dd-trace/src/appsec/iast/security-controls/index.js +187 -0
  64. package/packages/dd-trace/src/appsec/iast/security-controls/parser.js +96 -0
  65. package/packages/dd-trace/src/appsec/iast/taint-tracking/constants.js +6 -0
  66. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +2 -2
  67. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +8 -8
  68. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugins/kafka.js +1 -1
  69. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +65 -0
  70. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-telemetry.js +14 -5
  71. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +80 -2
  72. package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks-generator.js +1 -1
  73. package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks.js +28 -0
  74. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -1
  75. package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +5 -0
  76. package/packages/dd-trace/src/appsec/iast/utils.js +24 -0
  77. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +8 -13
  78. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +1 -0
  79. package/packages/dd-trace/src/appsec/index.js +4 -4
  80. package/packages/dd-trace/src/appsec/rasp/command_injection.js +5 -5
  81. package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +5 -5
  82. package/packages/dd-trace/src/appsec/rasp/lfi.js +3 -3
  83. package/packages/dd-trace/src/appsec/rasp/sql_injection.js +4 -4
  84. package/packages/dd-trace/src/appsec/rasp/ssrf.js +3 -3
  85. package/packages/dd-trace/src/appsec/reporter.js +3 -3
  86. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  87. package/packages/dd-trace/src/appsec/waf/index.js +1 -1
  88. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +2 -0
  89. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +31 -56
  90. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +20 -2
  91. package/packages/dd-trace/src/ci-visibility/quarantined-tests/get-quarantined-tests.js +62 -0
  92. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +5 -2
  93. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +3 -3
  94. package/packages/dd-trace/src/config.js +18 -3
  95. package/packages/dd-trace/src/data_streams_context.js +2 -2
  96. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +14 -7
  97. package/packages/dd-trace/src/debugger/devtools_client/source-maps.js +50 -0
  98. package/packages/dd-trace/src/debugger/devtools_client/state.js +38 -10
  99. package/packages/dd-trace/src/exporters/common/agents.js +1 -1
  100. package/packages/dd-trace/src/exporters/common/request.js +3 -3
  101. package/packages/dd-trace/src/iitm.js +2 -2
  102. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +1 -1
  103. package/packages/dd-trace/src/llmobs/tagger.js +12 -2
  104. package/packages/dd-trace/src/log/writer.js +3 -3
  105. package/packages/dd-trace/src/noop/span.js +1 -1
  106. package/packages/dd-trace/src/opentracing/propagation/text_map.js +5 -4
  107. package/packages/dd-trace/src/opentracing/span.js +3 -3
  108. package/packages/dd-trace/src/plugin_manager.js +3 -1
  109. package/packages/dd-trace/src/plugins/apollo.js +1 -1
  110. package/packages/dd-trace/src/plugins/ci_plugin.js +51 -4
  111. package/packages/dd-trace/src/plugins/database.js +14 -4
  112. package/packages/dd-trace/src/plugins/log_plugin.js +1 -1
  113. package/packages/dd-trace/src/plugins/plugin.js +8 -8
  114. package/packages/dd-trace/src/plugins/tracing.js +3 -3
  115. package/packages/dd-trace/src/plugins/util/git.js +3 -3
  116. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -3
  117. package/packages/dd-trace/src/plugins/util/test.js +10 -4
  118. package/packages/dd-trace/src/profiling/exporters/agent.js +3 -3
  119. package/packages/dd-trace/src/profiling/profilers/wall.js +1 -1
  120. package/packages/dd-trace/src/proxy.js +5 -1
  121. package/packages/dd-trace/src/ritm.js +2 -1
  122. package/packages/dd-trace/src/scope.js +5 -5
  123. package/packages/dd-trace/src/spanleak.js +0 -1
  124. package/packages/dd-trace/src/tracer.js +0 -14
  125. package/packages/memwatch/package.json +0 -9
@@ -43,6 +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
47
 
47
48
  const itrSkippedSuitesCh = channel('ci:jest:itr:skipped-suites')
48
49
 
@@ -70,6 +71,8 @@ let earlyFlakeDetectionFaultyThreshold = 30
70
71
  let isEarlyFlakeDetectionFaulty = false
71
72
  let hasFilteredSkippableSuites = false
72
73
  let isKnownTestsEnabled = false
74
+ let isQuarantinedTestsEnabled = false
75
+ let quarantinedTests = {}
73
76
 
74
77
  const sessionAsyncResource = new AsyncResource('bound-anonymous-fn')
75
78
 
@@ -140,6 +143,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
140
143
  this.flakyTestRetriesCount = this.testEnvironmentOptions._ddFlakyTestRetriesCount
141
144
  this.isDiEnabled = this.testEnvironmentOptions._ddIsDiEnabled
142
145
  this.isKnownTestsEnabled = this.testEnvironmentOptions._ddIsKnownTestsEnabled
146
+ this.isQuarantinedTestsEnabled = this.testEnvironmentOptions._ddIsQuarantinedTestsEnabled
143
147
 
144
148
  if (this.isKnownTestsEnabled) {
145
149
  try {
@@ -161,6 +165,18 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
161
165
  this.global[RETRY_TIMES] = this.flakyTestRetriesCount
162
166
  }
163
167
  }
168
+
169
+ if (this.isQuarantinedTestsEnabled) {
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)
175
+ } catch (e) {
176
+ log.error('Error parsing quarantined tests', e)
177
+ this.isQuarantinedTestsEnabled = false
178
+ }
179
+ }
164
180
  }
165
181
 
166
182
  getHasSnapshotTests () {
@@ -193,8 +209,25 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
193
209
  return knownTestsForSuite
194
210
  }
195
211
 
212
+ getQuarantinedTestsForSuite (quaratinedTests) {
213
+ if (this.quarantinedTestsForThisSuite) {
214
+ return this.quarantinedTestsForThisSuite
215
+ }
216
+ let quarantinedTestsForSuite = quaratinedTests
217
+ // If jest is using workers, quarantined tests are serialized to json.
218
+ // If jest runs in band, they are not.
219
+ if (typeof quarantinedTestsForSuite === 'string') {
220
+ quarantinedTestsForSuite = JSON.parse(quarantinedTestsForSuite)
221
+ }
222
+ return Object.entries(quarantinedTestsForSuite).reduce((acc, [testName, { properties }]) => {
223
+ if (properties?.quarantined) {
224
+ acc.push(testName)
225
+ }
226
+ return acc
227
+ }, [])
228
+ }
229
+
196
230
  // Add the `add_test` event we don't have the test object yet, so
197
- // we use its describe block to get the full name
198
231
  getTestNameFromAddTestEvent (event, state) {
199
232
  const describeSuffix = getJestTestName(state.currentDescribeBlock)
200
233
  const fullTestName = describeSuffix ? `${describeSuffix} ${event.testName}` : event.testName
@@ -303,6 +336,12 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
303
336
  }
304
337
  }
305
338
  }
339
+ let isQuarantined = false
340
+
341
+ if (this.isQuarantinedTestsEnabled) {
342
+ const testName = getJestTestName(event.test)
343
+ isQuarantined = this.quarantinedTestsForThisSuite?.includes(testName)
344
+ }
306
345
 
307
346
  const promises = {}
308
347
  const numRetries = this.global[RETRY_TIMES]
@@ -313,10 +352,11 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
313
352
  const asyncResource = asyncResources.get(event.test)
314
353
 
315
354
  if (status === 'fail') {
355
+ const shouldSetProbe = this.isDiEnabled && willBeRetried && numTestExecutions === 1
316
356
  asyncResource.runInAsyncScope(() => {
317
357
  testErrCh.publish({
318
358
  error: formatJestError(event.test.errors[0]),
319
- shouldSetProbe: this.isDiEnabled && willBeRetried && numTestExecutions === 1,
359
+ shouldSetProbe,
320
360
  promises
321
361
  })
322
362
  })
@@ -336,18 +376,13 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
336
376
  testFinishCh.publish({
337
377
  status,
338
378
  testStartLine: getTestLineStart(event.test.asyncError, this.testSuite),
339
- promises,
340
- shouldRemoveProbe: this.isDiEnabled && !willBeRetried
379
+ isQuarantined
341
380
  })
342
381
  })
343
382
 
344
383
  if (promises.isProbeReady) {
345
384
  await promises.isProbeReady
346
385
  }
347
-
348
- if (promises.isProbeRemoved) {
349
- await promises.isProbeRemoved
350
- }
351
386
  }
352
387
  if (event.name === 'test_skip' || event.name === 'test_todo') {
353
388
  const asyncResource = new AsyncResource('bound-anonymous-fn')
@@ -489,6 +524,7 @@ function cliWrapper (cli, jestVersion) {
489
524
  earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
490
525
  earlyFlakeDetectionFaultyThreshold = libraryConfig.earlyFlakeDetectionFaultyThreshold
491
526
  isKnownTestsEnabled = libraryConfig.isKnownTestsEnabled
527
+ isQuarantinedTestsEnabled = libraryConfig.isQuarantinedTestsEnabled
492
528
  }
493
529
  } catch (err) {
494
530
  log.error('Jest library configuration error', err)
@@ -536,6 +572,25 @@ function cliWrapper (cli, jestVersion) {
536
572
  }
537
573
  }
538
574
 
575
+ if (isQuarantinedTestsEnabled) {
576
+ const quarantinedTestsPromise = new Promise((resolve) => {
577
+ onDone = resolve
578
+ })
579
+
580
+ sessionAsyncResource.runInAsyncScope(() => {
581
+ quarantinedTestsCh.publish({ onDone })
582
+ })
583
+
584
+ try {
585
+ const { err, quarantinedTests: receivedQuarantinedTests } = await quarantinedTestsPromise
586
+ if (!err) {
587
+ quarantinedTests = receivedQuarantinedTests
588
+ }
589
+ } catch (err) {
590
+ log.error('Jest quarantined tests error', err)
591
+ }
592
+ }
593
+
539
594
  const processArgv = process.argv.slice(2).join(' ')
540
595
  sessionAsyncResource.runInAsyncScope(() => {
541
596
  testSessionStartCh.publish({ command: `jest ${processArgv}`, frameworkVersion: jestVersion })
@@ -605,6 +660,7 @@ function cliWrapper (cli, jestVersion) {
605
660
  error,
606
661
  isEarlyFlakeDetectionEnabled,
607
662
  isEarlyFlakeDetectionFaulty,
663
+ isQuarantinedTestsEnabled,
608
664
  onDone
609
665
  })
610
666
  })
@@ -638,6 +694,37 @@ function cliWrapper (cli, jestVersion) {
638
694
  }
639
695
  }
640
696
 
697
+ if (isQuarantinedTestsEnabled) {
698
+ const failedTests = result
699
+ .results
700
+ .testResults.flatMap(({ testResults, testFilePath: testSuiteAbsolutePath }) => (
701
+ testResults.map(({ fullName: testName, status }) => ({ testName, testSuiteAbsolutePath, status }))
702
+ ))
703
+ .filter(({ status }) => status === 'failed')
704
+
705
+ let numFailedQuarantinedTests = 0
706
+
707
+ for (const { testName, testSuiteAbsolutePath } of failedTests) {
708
+ const testSuite = getTestSuitePath(testSuiteAbsolutePath, result.globalConfig.rootDir)
709
+ const isQuarantined = quarantinedTests
710
+ ?.jest
711
+ ?.suites
712
+ ?.[testSuite]
713
+ ?.tests
714
+ ?.[testName]
715
+ ?.properties
716
+ ?.quarantined
717
+ if (isQuarantined) {
718
+ numFailedQuarantinedTests++
719
+ }
720
+ }
721
+
722
+ // If every test that failed was quarantined, we'll consider the suite passed
723
+ if (numFailedQuarantinedTests !== 0 && result.results.numFailedTests === numFailedQuarantinedTests) {
724
+ result.results.success = true
725
+ }
726
+ }
727
+
641
728
  return result
642
729
  })
643
730
 
@@ -829,6 +916,8 @@ addHook({
829
916
  _ddFlakyTestRetriesCount,
830
917
  _ddIsDiEnabled,
831
918
  _ddIsKnownTestsEnabled,
919
+ _ddIsQuarantinedTestsEnabled,
920
+ _ddQuarantinedTests,
832
921
  ...restOfTestEnvironmentOptions
833
922
  } = testEnvironmentOptions
834
923
 
@@ -940,8 +1029,9 @@ addHook({
940
1029
  })
941
1030
 
942
1031
  /*
943
- * This hook does two things:
1032
+ * This hook does three things:
944
1033
  * - Pass known tests to the workers.
1034
+ * - Pass quarantined tests to the workers.
945
1035
  * - Receive trace, coverage and logs payloads from the workers.
946
1036
  */
947
1037
  addHook({
@@ -951,7 +1041,7 @@ addHook({
951
1041
  }, (childProcessWorker) => {
952
1042
  const ChildProcessWorker = childProcessWorker.default
953
1043
  shimmer.wrap(ChildProcessWorker.prototype, 'send', send => function (request) {
954
- if (!isKnownTestsEnabled) {
1044
+ if (!isKnownTestsEnabled && !isQuarantinedTestsEnabled) {
955
1045
  return send.apply(this, arguments)
956
1046
  }
957
1047
  const [type] = request
@@ -971,11 +1061,15 @@ addHook({
971
1061
  const [{ globalConfig, config, path: testSuiteAbsolutePath }] = args
972
1062
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, globalConfig.rootDir || process.cwd())
973
1063
  const suiteKnownTests = knownTests.jest?.[testSuite] || []
1064
+
1065
+ const suiteQuarantinedTests = quarantinedTests.jest?.suites?.[testSuite]?.tests || {}
1066
+
974
1067
  args[0].config = {
975
1068
  ...config,
976
1069
  testEnvironmentOptions: {
977
1070
  ...config.testEnvironmentOptions,
978
- _ddKnownTests: suiteKnownTests
1071
+ _ddKnownTests: suiteKnownTests,
1072
+ _ddQuarantinedTests: suiteQuarantinedTests
979
1073
  }
980
1074
  }
981
1075
  }
@@ -27,6 +27,7 @@ const {
27
27
  getOnPendingHandler,
28
28
  testFileToSuiteAr,
29
29
  newTests,
30
+ testsQuarantined,
30
31
  getTestFullName,
31
32
  getRunTestsWrapper
32
33
  } = require('./utils')
@@ -61,6 +62,7 @@ const testSuiteCodeCoverageCh = channel('ci:mocha:test-suite:code-coverage')
61
62
  const libraryConfigurationCh = channel('ci:mocha:library-configuration')
62
63
  const knownTestsCh = channel('ci:mocha:known-tests')
63
64
  const skippableSuitesCh = channel('ci:mocha:test-suite:skippable')
65
+ const quarantinedTestsCh = channel('ci:mocha:quarantined-tests')
64
66
  const workerReportTraceCh = channel('ci:mocha:worker-report:trace')
65
67
  const testSessionStartCh = channel('ci:mocha:session:start')
66
68
  const testSessionFinishCh = channel('ci:mocha:session:finish')
@@ -135,6 +137,18 @@ function getOnEndHandler (isParallel) {
135
137
  }
136
138
  }
137
139
 
140
+ // We subtract the errors from quarantined tests from the total number of failures
141
+ if (config.isQuarantinedTestsEnabled) {
142
+ let numFailedQuarantinedTests = 0
143
+ for (const test of testsQuarantined) {
144
+ if (isTestFailed(test)) {
145
+ numFailedQuarantinedTests++
146
+ }
147
+ }
148
+ this.stats.failures -= numFailedQuarantinedTests
149
+ this.failures -= numFailedQuarantinedTests
150
+ }
151
+
138
152
  if (status === 'fail') {
139
153
  error = new Error(`Failed tests: ${this.failures}.`)
140
154
  }
@@ -165,6 +179,7 @@ function getOnEndHandler (isParallel) {
165
179
  error,
166
180
  isEarlyFlakeDetectionEnabled: config.isEarlyFlakeDetectionEnabled,
167
181
  isEarlyFlakeDetectionFaulty: config.isEarlyFlakeDetectionFaulty,
182
+ isQuarantinedTestsEnabled: config.isQuarantinedTestsEnabled,
168
183
  isParallel
169
184
  })
170
185
  })
@@ -173,6 +188,22 @@ function getOnEndHandler (isParallel) {
173
188
  function getExecutionConfiguration (runner, isParallel, onFinishRequest) {
174
189
  const mochaRunAsyncResource = new AsyncResource('bound-anonymous-fn')
175
190
 
191
+ const onReceivedQuarantinedTests = ({ err, quarantinedTests: receivedQuarantinedTests }) => {
192
+ if (err) {
193
+ config.quarantinedTests = {}
194
+ config.isQuarantinedTestsEnabled = false
195
+ } else {
196
+ config.quarantinedTests = receivedQuarantinedTests
197
+ }
198
+ if (config.isSuitesSkippingEnabled) {
199
+ skippableSuitesCh.publish({
200
+ onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites)
201
+ })
202
+ } else {
203
+ onFinishRequest()
204
+ }
205
+ }
206
+
176
207
  const onReceivedSkippableSuites = ({ err, skippableSuites, itrCorrelationId: responseItrCorrelationId }) => {
177
208
  if (err) {
178
209
  suitesToSkip = []
@@ -205,8 +236,11 @@ function getExecutionConfiguration (runner, isParallel, onFinishRequest) {
205
236
  } else {
206
237
  config.knownTests = knownTests
207
238
  }
208
-
209
- if (config.isSuitesSkippingEnabled) {
239
+ if (config.isQuarantinedTestsEnabled) {
240
+ quarantinedTestsCh.publish({
241
+ onDone: mochaRunAsyncResource.bind(onReceivedQuarantinedTests)
242
+ })
243
+ } else if (config.isSuitesSkippingEnabled) {
210
244
  skippableSuitesCh.publish({
211
245
  onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites)
212
246
  })
@@ -224,15 +258,20 @@ function getExecutionConfiguration (runner, isParallel, onFinishRequest) {
224
258
  config.earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
225
259
  config.earlyFlakeDetectionFaultyThreshold = libraryConfig.earlyFlakeDetectionFaultyThreshold
226
260
  config.isKnownTestsEnabled = libraryConfig.isKnownTestsEnabled
227
- // ITR and auto test retries are not supported in parallel mode yet
261
+ // ITR, auto test retries and quarantine are not supported in parallel mode yet
228
262
  config.isSuitesSkippingEnabled = !isParallel && libraryConfig.isSuitesSkippingEnabled
229
263
  config.isFlakyTestRetriesEnabled = !isParallel && libraryConfig.isFlakyTestRetriesEnabled
230
264
  config.flakyTestRetriesCount = !isParallel && libraryConfig.flakyTestRetriesCount
265
+ config.isQuarantinedTestsEnabled = !isParallel && libraryConfig.isQuarantinedTestsEnabled
231
266
 
232
267
  if (config.isKnownTestsEnabled) {
233
268
  knownTestsCh.publish({
234
269
  onDone: mochaRunAsyncResource.bind(onReceivedKnownTests)
235
270
  })
271
+ } else if (config.isQuarantinedTestsEnabled) {
272
+ quarantinedTestsCh.publish({
273
+ onDone: mochaRunAsyncResource.bind(onReceivedQuarantinedTests)
274
+ })
236
275
  } else if (config.isSuitesSkippingEnabled) {
237
276
  skippableSuitesCh.publish({
238
277
  onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites)
@@ -357,7 +396,7 @@ addHook({
357
396
 
358
397
  this.once('end', getOnEndHandler(false))
359
398
 
360
- this.on('test', getOnTestHandler(true, newTests))
399
+ this.on('test', getOnTestHandler(true))
361
400
 
362
401
  this.on('test end', getOnTestEndHandler())
363
402
 
@@ -579,6 +618,7 @@ addHook({
579
618
 
580
619
  const testPath = getTestSuitePath(testSuiteAbsolutePath, process.cwd())
581
620
  const testSuiteKnownTests = config.knownTests.mocha?.[testPath] || []
621
+ const testSuiteQuarantinedTests = config.quarantinedTests?.modules?.mocha?.suites?.[testPath] || []
582
622
 
583
623
  // We pass the known tests for the test file to the worker
584
624
  const testFileResult = await run.apply(
@@ -589,6 +629,8 @@ addHook({
589
629
  ...workerArgs,
590
630
  _ddEfdNumRetries: config.earlyFlakeDetectionNumRetries,
591
631
  _ddIsEfdEnabled: config.isEarlyFlakeDetectionEnabled,
632
+ _ddIsQuarantinedEnabled: config.isQuarantinedTestsEnabled,
633
+ _ddQuarantinedTests: testSuiteQuarantinedTests,
592
634
  _ddKnownTests: {
593
635
  mocha: {
594
636
  [testPath]: testSuiteKnownTests
@@ -26,6 +26,27 @@ const testToStartLine = new WeakMap()
26
26
  const testFileToSuiteAr = new Map()
27
27
  const wrappedFunctions = new WeakSet()
28
28
  const newTests = {}
29
+ const testsQuarantined = new Set()
30
+
31
+ function isQuarantinedTest (test, testsToQuarantine) {
32
+ const testSuite = getTestSuitePath(test.file, process.cwd())
33
+ const testName = test.fullTitle()
34
+
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
+
48
+ return isQuarantined
49
+ }
29
50
 
30
51
  function isNewTest (test, knownTests) {
31
52
  const testSuite = getTestSuitePath(test.file, process.cwd())
@@ -171,7 +192,8 @@ function getOnTestHandler (isMain) {
171
192
  file: testSuiteAbsolutePath,
172
193
  title,
173
194
  _ddIsNew: isNew,
174
- _ddIsEfdRetry: isEfdRetry
195
+ _ddIsEfdRetry: isEfdRetry,
196
+ _ddIsQuarantined: isQuarantined
175
197
  } = test
176
198
 
177
199
  const testInfo = {
@@ -187,6 +209,7 @@ function getOnTestHandler (isMain) {
187
209
 
188
210
  testInfo.isNew = isNew
189
211
  testInfo.isEfdRetry = isEfdRetry
212
+ testInfo.isQuarantined = isQuarantined
190
213
  // We want to store the result of the new tests
191
214
  if (isNew) {
192
215
  const testFullName = getTestFullName(test)
@@ -360,6 +383,15 @@ function getRunTestsWrapper (runTests, config) {
360
383
  }
361
384
  })
362
385
  }
386
+
387
+ if (config.isQuarantinedTestsEnabled) {
388
+ suite.tests.forEach(test => {
389
+ if (isQuarantinedTest(test, config.quarantinedTests)) {
390
+ test._ddIsQuarantined = true
391
+ }
392
+ })
393
+ }
394
+
363
395
  return runTests.apply(this, arguments)
364
396
  }
365
397
  }
@@ -384,5 +416,6 @@ module.exports = {
384
416
  getOnPendingHandler,
385
417
  testFileToSuiteAr,
386
418
  getRunTestsWrapper,
387
- newTests
419
+ newTests,
420
+ testsQuarantined
388
421
  }
@@ -33,6 +33,13 @@ addHook({
33
33
  delete this.options._ddIsEfdEnabled
34
34
  delete this.options._ddKnownTests
35
35
  delete this.options._ddEfdNumRetries
36
+ delete this.options._ddQuarantinedTests
37
+ }
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
36
43
  }
37
44
  return run.apply(this, arguments)
38
45
  })
@@ -6,14 +6,14 @@ const {
6
6
  AsyncResource
7
7
  } = require('./helpers/instrument')
8
8
  const shimmer = require('../../datadog-shimmer')
9
- const semver = require('semver')
9
+ const satisfies = require('semifies')
10
10
 
11
11
  function wrapConnection (Connection, version) {
12
12
  const startCh = channel('apm:mysql2:query:start')
13
13
  const finishCh = channel('apm:mysql2:query:finish')
14
14
  const errorCh = channel('apm:mysql2:query:error')
15
15
  const startOuterQueryCh = channel('datadog:mysql2:outerquery:start')
16
- const shouldEmitEndAfterQueryAbort = semver.intersects(version, '>=1.3.3')
16
+ const shouldEmitEndAfterQueryAbort = satisfies(version, '>=1.3.3')
17
17
 
18
18
  shimmer.wrap(Connection.prototype, 'addCommand', addCommand => function (cmd) {
19
19
  if (!startCh.hasSubscribers) return addCommand.apply(this, arguments)
@@ -154,7 +154,7 @@ function wrapConnection (Connection, version) {
154
154
  }
155
155
  function wrapPool (Pool, version) {
156
156
  const startOuterQueryCh = channel('datadog:mysql2:outerquery:start')
157
- const shouldEmitEndAfterQueryAbort = semver.intersects(version, '>=1.3.3')
157
+ const shouldEmitEndAfterQueryAbort = satisfies(version, '>=1.3.3')
158
158
 
159
159
  shimmer.wrap(Pool.prototype, 'query', query => function (sql, values, cb) {
160
160
  if (!startOuterQueryCh.hasSubscribers) return query.apply(this, arguments)
@@ -97,6 +97,14 @@ const V4_PACKAGE_SHIMS = [
97
97
  targetClass: 'Translations',
98
98
  baseResource: 'audio.translations',
99
99
  methods: ['create']
100
+ },
101
+ {
102
+ file: 'resources/chat/completions/completions.js',
103
+ targetClass: 'Completions',
104
+ baseResource: 'chat.completions',
105
+ methods: ['create'],
106
+ streamedResponse: true,
107
+ versions: ['>=4.85.0']
100
108
  }
101
109
  ]
102
110
 
@@ -1,4 +1,4 @@
1
- const semver = require('semver')
1
+ const satisfies = require('semifies')
2
2
 
3
3
  const { addHook, channel, AsyncResource } = require('./helpers/instrument')
4
4
  const shimmer = require('../../datadog-shimmer')
@@ -13,6 +13,7 @@ const testSessionFinishCh = channel('ci:playwright:session:finish')
13
13
 
14
14
  const libraryConfigurationCh = channel('ci:playwright:library-configuration')
15
15
  const knownTestsCh = channel('ci:playwright:known-tests')
16
+ const quarantinedTestsCh = channel('ci:playwright:quarantined-tests')
16
17
 
17
18
  const testSuiteStartCh = channel('ci:playwright:test-suite:start')
18
19
  const testSuiteFinishCh = channel('ci:playwright:test-suite:finish')
@@ -41,8 +42,24 @@ let earlyFlakeDetectionNumRetries = 0
41
42
  let isFlakyTestRetriesEnabled = false
42
43
  let flakyTestRetriesCount = 0
43
44
  let knownTests = {}
45
+ let isQuarantinedTestsEnabled = false
46
+ let quarantinedTests = {}
44
47
  let rootDir = ''
45
- const MINIMUM_SUPPORTED_VERSION_EFD = '1.38.0'
48
+ const MINIMUM_SUPPORTED_VERSION_RANGE_EFD = '>=1.38.0'
49
+
50
+ function isQuarantineTest (test) {
51
+ const testName = getTestFullname(test)
52
+ const testSuite = getTestSuitePath(test._requireFile, rootDir)
53
+
54
+ return quarantinedTests
55
+ ?.playwright
56
+ ?.suites
57
+ ?.[testSuite]
58
+ ?.tests
59
+ ?.[testName]
60
+ ?.properties
61
+ ?.quarantined
62
+ }
46
63
 
47
64
  function isNewTest (test) {
48
65
  const testSuite = getTestSuitePath(test._requireFile, rootDir)
@@ -296,6 +313,7 @@ function testEndHandler (test, annotations, testStatus, error, isTimeout) {
296
313
  error,
297
314
  extraTags: annotationTags,
298
315
  isNew: test._ddIsNew,
316
+ isQuarantined: test._ddIsQuarantined,
299
317
  isEfdRetry: test._ddIsEfdRetry
300
318
  })
301
319
  })
@@ -424,14 +442,16 @@ function runnerHook (runnerExport, playwrightVersion) {
424
442
  earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
425
443
  isFlakyTestRetriesEnabled = libraryConfig.isFlakyTestRetriesEnabled
426
444
  flakyTestRetriesCount = libraryConfig.flakyTestRetriesCount
445
+ isQuarantinedTestsEnabled = libraryConfig.isQuarantinedTestsEnabled
427
446
  }
428
447
  } catch (e) {
429
448
  isEarlyFlakeDetectionEnabled = false
430
449
  isKnownTestsEnabled = false
450
+ isQuarantinedTestsEnabled = false
431
451
  log.error('Playwright session start error', e)
432
452
  }
433
453
 
434
- if (isKnownTestsEnabled && semver.gte(playwrightVersion, MINIMUM_SUPPORTED_VERSION_EFD)) {
454
+ if (isKnownTestsEnabled && satisfies(playwrightVersion, MINIMUM_SUPPORTED_VERSION_RANGE_EFD)) {
435
455
  try {
436
456
  const { err, knownTests: receivedKnownTests } = await getChannelPromise(knownTestsCh)
437
457
  if (!err) {
@@ -447,6 +467,20 @@ function runnerHook (runnerExport, playwrightVersion) {
447
467
  }
448
468
  }
449
469
 
470
+ if (isQuarantinedTestsEnabled && satisfies(playwrightVersion, MINIMUM_SUPPORTED_VERSION_RANGE_EFD)) {
471
+ try {
472
+ const { err, quarantinedTests: receivedQuarantinedTests } = await getChannelPromise(quarantinedTestsCh)
473
+ if (!err) {
474
+ quarantinedTests = receivedQuarantinedTests
475
+ } else {
476
+ isQuarantinedTestsEnabled = false
477
+ }
478
+ } catch (err) {
479
+ isQuarantinedTestsEnabled = false
480
+ log.error('Playwright quarantined tests error', err)
481
+ }
482
+ }
483
+
450
484
  const projects = getProjectsFromRunner(this)
451
485
 
452
486
  if (isFlakyTestRetriesEnabled && flakyTestRetriesCount > 0) {
@@ -479,6 +513,7 @@ function runnerHook (runnerExport, playwrightVersion) {
479
513
  testSessionFinishCh.publish({
480
514
  status: STATUS_TO_TEST_STATUS[sessionStatus],
481
515
  isEarlyFlakeDetectionEnabled,
516
+ isQuarantinedTestsEnabled,
482
517
  onDone
483
518
  })
484
519
  })
@@ -487,6 +522,8 @@ function runnerHook (runnerExport, playwrightVersion) {
487
522
  startedSuites = []
488
523
  remainingTestsByFile = {}
489
524
 
525
+ // TODO: we can trick playwright into thinking the session passed by returning
526
+ // 'passed' here. We might be able to use this for both EFD and Quarantined tests.
490
527
  return runAllTestsReturn
491
528
  })
492
529
 
@@ -540,7 +577,7 @@ addHook({
540
577
  addHook({
541
578
  name: 'playwright',
542
579
  file: 'lib/common/suiteUtils.js',
543
- versions: [`>=${MINIMUM_SUPPORTED_VERSION_EFD}`]
580
+ versions: [MINIMUM_SUPPORTED_VERSION_RANGE_EFD]
544
581
  }, suiteUtilsPackage => {
545
582
  // We grab `applyRepeatEachIndex` to use it later
546
583
  // `applyRepeatEachIndex` needs to be applied to a cloned suite
@@ -552,31 +589,42 @@ addHook({
552
589
  addHook({
553
590
  name: 'playwright',
554
591
  file: 'lib/runner/loadUtils.js',
555
- versions: [`>=${MINIMUM_SUPPORTED_VERSION_EFD}`]
592
+ versions: [MINIMUM_SUPPORTED_VERSION_RANGE_EFD]
556
593
  }, (loadUtilsPackage) => {
557
594
  const oldCreateRootSuite = loadUtilsPackage.createRootSuite
558
595
 
559
596
  async function newCreateRootSuite () {
597
+ if (!isKnownTestsEnabled && !isQuarantinedTestsEnabled) {
598
+ return oldCreateRootSuite.apply(this, arguments)
599
+ }
560
600
  const rootSuite = await oldCreateRootSuite.apply(this, arguments)
561
- if (!isKnownTestsEnabled) {
562
- return rootSuite
601
+
602
+ const allTests = rootSuite.allTests()
603
+
604
+ if (isQuarantinedTestsEnabled) {
605
+ const testsToBeIgnored = allTests.filter(isQuarantineTest)
606
+ testsToBeIgnored.forEach(test => {
607
+ test._ddIsQuarantined = true
608
+ test.expectedStatus = 'skipped'
609
+ })
563
610
  }
564
- const newTests = rootSuite
565
- .allTests()
566
- .filter(isNewTest)
567
-
568
- newTests.forEach(newTest => {
569
- newTest._ddIsNew = true
570
- if (isEarlyFlakeDetectionEnabled && newTest.expectedStatus !== 'skipped') {
571
- const fileSuite = getSuiteType(newTest, 'file')
572
- const projectSuite = getSuiteType(newTest, 'project')
573
- for (let repeatEachIndex = 0; repeatEachIndex < earlyFlakeDetectionNumRetries; repeatEachIndex++) {
574
- const copyFileSuite = deepCloneSuite(fileSuite, isNewTest)
575
- applyRepeatEachIndex(projectSuite._fullProject, copyFileSuite, repeatEachIndex + 1)
576
- projectSuite._addSuite(copyFileSuite)
611
+
612
+ if (isKnownTestsEnabled) {
613
+ const newTests = allTests.filter(isNewTest)
614
+
615
+ newTests.forEach(newTest => {
616
+ newTest._ddIsNew = true
617
+ if (isEarlyFlakeDetectionEnabled && newTest.expectedStatus !== 'skipped') {
618
+ const fileSuite = getSuiteType(newTest, 'file')
619
+ const projectSuite = getSuiteType(newTest, 'project')
620
+ for (let repeatEachIndex = 0; repeatEachIndex < earlyFlakeDetectionNumRetries; repeatEachIndex++) {
621
+ const copyFileSuite = deepCloneSuite(fileSuite, isNewTest)
622
+ applyRepeatEachIndex(projectSuite._fullProject, copyFileSuite, repeatEachIndex + 1)
623
+ projectSuite._addSuite(copyFileSuite)
624
+ }
577
625
  }
578
- }
579
- })
626
+ })
627
+ }
580
628
 
581
629
  return rootSuite
582
630
  }