dd-trace 6.4.0 → 6.5.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 (178) hide show
  1. package/README.electron.md +7 -0
  2. package/README.md +17 -0
  3. package/ci/diagnose.js +2100 -0
  4. package/ci/init.js +23 -2
  5. package/ci/runbook.md +198 -0
  6. package/ci/test-optimization-validation/approval-artifacts.js +143 -0
  7. package/ci/test-optimization-validation/approval.js +299 -0
  8. package/ci/test-optimization-validation/artifact-id.js +20 -0
  9. package/ci/test-optimization-validation/bounded-json.js +121 -0
  10. package/ci/test-optimization-validation/ci-command-candidate.js +83 -0
  11. package/ci/test-optimization-validation/ci-discovery.js +181 -0
  12. package/ci/test-optimization-validation/ci-remediation.js +210 -0
  13. package/ci/test-optimization-validation/cli.js +903 -0
  14. package/ci/test-optimization-validation/command-blocker.js +81 -0
  15. package/ci/test-optimization-validation/command-output-policy.js +206 -0
  16. package/ci/test-optimization-validation/command-runner.js +707 -0
  17. package/ci/test-optimization-validation/command-suitability.js +471 -0
  18. package/ci/test-optimization-validation/executable-approval.js +48 -0
  19. package/ci/test-optimization-validation/executable.js +480 -0
  20. package/ci/test-optimization-validation/generated-file-policy.js +63 -0
  21. package/ci/test-optimization-validation/generated-files.js +351 -0
  22. package/ci/test-optimization-validation/generated-verifier.js +196 -0
  23. package/ci/test-optimization-validation/init-probe-preload.js +163 -0
  24. package/ci/test-optimization-validation/init-probe.js +246 -0
  25. package/ci/test-optimization-validation/late-initialization.js +63 -0
  26. package/ci/test-optimization-validation/local-command.js +163 -0
  27. package/ci/test-optimization-validation/manifest-loader.js +133 -0
  28. package/ci/test-optimization-validation/manifest-scaffold.js +738 -0
  29. package/ci/test-optimization-validation/manifest-schema.js +862 -0
  30. package/ci/test-optimization-validation/offline-fixtures.js +327 -0
  31. package/ci/test-optimization-validation/offline-output.js +406 -0
  32. package/ci/test-optimization-validation/payload-normalizer.js +72 -0
  33. package/ci/test-optimization-validation/plan-writer.js +1120 -0
  34. package/ci/test-optimization-validation/preflight-runner.js +114 -0
  35. package/ci/test-optimization-validation/redaction.js +331 -0
  36. package/ci/test-optimization-validation/report-writer.js +1508 -0
  37. package/ci/test-optimization-validation/safe-files.js +203 -0
  38. package/ci/test-optimization-validation/scenarios/auto-test-retries.js +159 -0
  39. package/ci/test-optimization-validation/scenarios/basic-reporting.js +657 -0
  40. package/ci/test-optimization-validation/scenarios/ci-wiring.js +780 -0
  41. package/ci/test-optimization-validation/scenarios/early-flake-detection.js +141 -0
  42. package/ci/test-optimization-validation/scenarios/helpers.js +539 -0
  43. package/ci/test-optimization-validation/scenarios/test-management.js +218 -0
  44. package/ci/test-optimization-validation/setup-runner.js +97 -0
  45. package/ci/test-optimization-validation/static-diagnosis.js +159 -0
  46. package/ci/test-optimization-validation/test-output.js +129 -0
  47. package/ci/test-optimization-validation-manifest.schema.json +1 -0
  48. package/ci/validate-test-optimization.js +3 -0
  49. package/ext/exporters.js +1 -0
  50. package/index.d.ts +121 -8
  51. package/init.js +18 -0
  52. package/initialize.mjs +1 -1
  53. package/loader-hook.mjs +28 -18
  54. package/openfeature.d.ts +1 -0
  55. package/openfeature.js +4 -0
  56. package/package.json +10 -7
  57. package/packages/datadog-instrumentations/src/ai.js +8 -2
  58. package/packages/datadog-instrumentations/src/child_process.js +1 -1
  59. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +1 -1
  60. package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +9 -3
  61. package/packages/datadog-instrumentations/src/cucumber.js +20 -7
  62. package/packages/datadog-instrumentations/src/cypress-config.js +398 -52
  63. package/packages/datadog-instrumentations/src/fastify.js +7 -11
  64. package/packages/datadog-instrumentations/src/helpers/hook.js +27 -6
  65. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +8 -8
  66. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +28 -2
  67. package/packages/datadog-instrumentations/src/jest.js +26 -0
  68. package/packages/datadog-instrumentations/src/mariadb.js +1 -1
  69. package/packages/datadog-instrumentations/src/mocha/main.js +0 -2
  70. package/packages/datadog-instrumentations/src/mocha/utils.js +0 -4
  71. package/packages/datadog-instrumentations/src/mongodb.js +3 -3
  72. package/packages/datadog-instrumentations/src/mongoose.js +3 -3
  73. package/packages/datadog-instrumentations/src/mquery.js +2 -2
  74. package/packages/datadog-instrumentations/src/mysql.js +1 -1
  75. package/packages/datadog-instrumentations/src/next.js +24 -0
  76. package/packages/datadog-instrumentations/src/nyc.js +0 -2
  77. package/packages/datadog-instrumentations/src/oracledb.js +2 -2
  78. package/packages/datadog-instrumentations/src/pg.js +2 -2
  79. package/packages/datadog-instrumentations/src/playwright.js +159 -22
  80. package/packages/datadog-instrumentations/src/process.js +3 -0
  81. package/packages/datadog-instrumentations/src/router.js +18 -8
  82. package/packages/datadog-instrumentations/src/selenium.js +52 -26
  83. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +2 -2
  84. package/packages/datadog-instrumentations/src/vitest-main.js +0 -2
  85. package/packages/datadog-instrumentations/src/ws.js +2 -1
  86. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +2 -2
  87. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +2 -1
  88. package/packages/datadog-plugin-aws-sdk/src/util.js +2 -2
  89. package/packages/datadog-plugin-cucumber/src/index.js +1 -1
  90. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -57
  91. package/packages/datadog-plugin-cypress/src/index.js +6 -1
  92. package/packages/datadog-plugin-cypress/src/support.js +92 -26
  93. package/packages/datadog-plugin-electron/src/ipc.js +1 -1
  94. package/packages/datadog-plugin-graphql/src/execute.js +87 -45
  95. package/packages/datadog-plugin-graphql/src/validate.js +2 -2
  96. package/packages/datadog-plugin-http2/src/client.js +1 -1
  97. package/packages/datadog-plugin-langchain/src/handlers/embedding.js +2 -1
  98. package/packages/datadog-plugin-langchain/src/handlers/language_models.js +2 -1
  99. package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
  100. package/packages/datadog-plugin-playwright/src/index.js +177 -64
  101. package/packages/datadog-plugin-router/src/index.js +11 -2
  102. package/packages/datadog-plugin-selenium/src/index.js +5 -36
  103. package/packages/datadog-plugin-ws/src/close.js +2 -1
  104. package/packages/datadog-plugin-ws/src/producer.js +2 -1
  105. package/packages/datadog-plugin-ws/src/receiver.js +2 -1
  106. package/packages/datadog-plugin-ws/src/server.js +2 -1
  107. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  108. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +2 -2
  109. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +3 -1
  110. package/packages/dd-trace/src/azure_metadata.js +2 -1
  111. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +93 -28
  112. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
  113. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/index.js +160 -0
  114. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +288 -0
  115. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/payload-projection.js +84 -0
  116. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +369 -0
  117. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +60 -0
  118. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +41 -3
  119. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -2
  120. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +1 -1
  121. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +6 -2
  122. package/packages/dd-trace/src/ci-visibility/rum.js +7 -0
  123. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
  124. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
  125. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +137 -9
  126. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +168 -8
  127. package/packages/dd-trace/src/ci-visibility/test-screenshot.js +90 -0
  128. package/packages/dd-trace/src/config/generated-config-types.d.ts +16 -0
  129. package/packages/dd-trace/src/config/helper.js +1 -0
  130. package/packages/dd-trace/src/config/index.js +10 -0
  131. package/packages/dd-trace/src/config/supported-configurations.json +66 -0
  132. package/packages/dd-trace/src/exporter.js +2 -0
  133. package/packages/dd-trace/src/exporters/common/client-library-headers.js +21 -0
  134. package/packages/dd-trace/src/exporters/common/request.js +59 -30
  135. package/packages/dd-trace/src/exporters/common/url.js +15 -1
  136. package/packages/dd-trace/src/feature-registry.js +10 -3
  137. package/packages/dd-trace/src/llmobs/experiments/client.js +113 -0
  138. package/packages/dd-trace/src/llmobs/experiments/dataset.js +154 -0
  139. package/packages/dd-trace/src/llmobs/experiments/experiment.js +283 -0
  140. package/packages/dd-trace/src/llmobs/experiments/index.js +152 -0
  141. package/packages/dd-trace/src/llmobs/experiments/noop.js +30 -0
  142. package/packages/dd-trace/src/llmobs/experiments/result.js +34 -0
  143. package/packages/dd-trace/src/llmobs/noop.js +6 -0
  144. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +20 -3
  145. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +2 -2
  146. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  147. package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +8 -0
  148. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +48 -14
  149. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +43 -0
  150. package/packages/dd-trace/src/llmobs/sdk.js +17 -0
  151. package/packages/dd-trace/src/llmobs/tagger.js +68 -0
  152. package/packages/dd-trace/src/llmobs/telemetry.js +2 -1
  153. package/packages/dd-trace/src/llmobs/util.js +32 -0
  154. package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +322 -0
  155. package/packages/dd-trace/src/openfeature/configuration_source.js +90 -0
  156. package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -23
  157. package/packages/dd-trace/src/openfeature/index.js +0 -2
  158. package/packages/dd-trace/src/openfeature/noop.js +1 -28
  159. package/packages/dd-trace/src/openfeature/register.js +16 -27
  160. package/packages/dd-trace/src/openfeature/remote_config.js +15 -18
  161. package/packages/dd-trace/src/openfeature/require-provider.js +18 -0
  162. package/packages/dd-trace/src/opentelemetry/span-ending-hook.js +10 -0
  163. package/packages/dd-trace/src/opentelemetry/span.js +5 -0
  164. package/packages/dd-trace/src/plugins/ci_plugin.js +20 -6
  165. package/packages/dd-trace/src/plugins/util/git.js +3 -2
  166. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +29 -5
  167. package/packages/dd-trace/src/plugins/util/test.js +56 -4
  168. package/packages/dd-trace/src/proxy.js +84 -7
  169. package/packages/dd-trace/src/span_processor.js +5 -0
  170. package/packages/dd-trace/src/standalone/index.js +0 -14
  171. package/packages/dd-trace/src/telemetry/send-data.js +2 -2
  172. package/packages/dd-trace/src/util.js +26 -0
  173. package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
  174. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  175. package/vendor/dist/protobufjs/index.js +1 -1
  176. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  177. package/vendor/dist/shell-quote/index.js +1 -1
  178. package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +0 -17
@@ -4,19 +4,19 @@ const path = require('node:path')
4
4
 
5
5
  // Build-time half of the optional-peer mechanism shared by the webpack and esbuild plugins.
6
6
  //
7
- // Runtime files load an optional peer through `requireOptionalPeer('name')` (see
8
- // `require-optional-peer.js`), which bundlers cannot follow, so a build that does not opt into
9
- // the feature never pulls in the peer's (possibly optional) dependency chain (#8635). When the
10
- // peer is installed at build time the user has opted in, so the plugins rewrite the call into a
11
- // literal `require('name')` and let the bundler inline the peer, which keeps it working after
12
- // the bundle is relocated to a tree without the peer on disk (#8980). Peers that are absent at
13
- // build time stay opaque, so the rewrite is a no-op and the #8635 guarantee holds.
7
+ // Runtime files load an optional peer through a local `requireOptionalPeer('name')` wrapper.
8
+ // File tracers recognize its bound-require shape, while bundlers cannot follow the dynamic
9
+ // argument, so a build that does not opt into the feature never pulls in the peer's dependency
10
+ // chain (#8635). When the peer is installed at build time the user has opted in, so the plugins
11
+ // rewrite the call into a literal `require('name')` and let the bundler inline the peer, which
12
+ // keeps it working after the bundle is relocated without the peer on disk (#8980). Peers that
13
+ // are absent at build time stay opaque, so the rewrite is a no-op and the #8635 guarantee holds.
14
14
 
15
15
  // Files that load an optional peer this way, as suffixes of the resolved module path. The same
16
16
  // suffix matches the repo layout and `node_modules/dd-trace`. Add a file here to extend the
17
17
  // mechanism to a new optional peer; no plugin change is needed.
18
18
  const OPTIONAL_PEER_FILES = [
19
- 'packages/dd-trace/src/openfeature/flagging_provider.js',
19
+ 'packages/dd-trace/src/openfeature/require-provider.js',
20
20
  ]
21
21
 
22
22
  // Captures the peer name from `requireOptionalPeer('name')` / `requireOptionalPeer("name")`.
@@ -1,8 +1,34 @@
1
1
  'use strict'
2
2
 
3
- // Playwright 1.60 bundles several former hook targets into local classes/functions.
4
- // Keep these rewrites limited to private bundled internals that addHook cannot wrap.
3
+ // Playwright keeps several hook targets in private local classes/functions.
4
+ // Keep these rewrites limited to bundled internals that addHook cannot wrap.
5
5
  module.exports = [
6
+ {
7
+ module: {
8
+ name: 'playwright',
9
+ versionRange: '>=1.38.0 <1.51.0',
10
+ filePath: 'lib/index.js',
11
+ },
12
+ functionQuery: {
13
+ className: 'ArtifactsRecorder',
14
+ methodName: '_createScreenshotAttachmentPath',
15
+ kind: 'Sync',
16
+ },
17
+ channelName: 'ArtifactsRecorder_createScreenshotAttachmentPath',
18
+ },
19
+ {
20
+ module: {
21
+ name: 'playwright',
22
+ versionRange: '>=1.51.0',
23
+ filePath: 'lib/index.js',
24
+ },
25
+ functionQuery: {
26
+ className: 'SnapshotRecorder',
27
+ methodName: '_createAttachmentPath',
28
+ kind: 'Sync',
29
+ },
30
+ channelName: 'SnapshotRecorder_createAttachmentPath',
31
+ },
6
32
  {
7
33
  module: {
8
34
  name: 'playwright',
@@ -1858,6 +1858,26 @@ function resetSuiteSkippingRunState () {
1858
1858
  coverageBackfillFiles = undefined
1859
1859
  }
1860
1860
 
1861
+ function resetLibraryConfiguration () {
1862
+ knownTests = {}
1863
+ isCodeCoverageEnabled = false
1864
+ isCoverageReportUploadEnabled = false
1865
+ isItrEnabled = false
1866
+ isSuitesSkippingEnabled = false
1867
+ isEarlyFlakeDetectionEnabled = false
1868
+ earlyFlakeDetectionNumRetries = 0
1869
+ earlyFlakeDetectionSlowTestRetries = {}
1870
+ earlyFlakeDetectionFaultyThreshold = 30
1871
+ isEarlyFlakeDetectionFaulty = false
1872
+ isKnownTestsEnabled = false
1873
+ isTestManagementTestsEnabled = false
1874
+ testManagementTests = {}
1875
+ testManagementAttemptToFixRetries = 0
1876
+ isImpactedTestsEnabled = false
1877
+ modifiedFiles = {}
1878
+ repositoryRoot = undefined
1879
+ }
1880
+
1861
1881
  function applySuiteSkipping (originalTests, rootDir, frameworkVersion) {
1862
1882
  if (!isItrEnabled || !isSuitesSkippingEnabled) return originalTests
1863
1883
 
@@ -2211,11 +2231,13 @@ function getCliWrapper (isNewJestVersion) {
2211
2231
  resetSuiteSkippingRunState()
2212
2232
  hasFinishedTestSession = false
2213
2233
 
2234
+ let shouldResetLibraryConfiguration = true
2214
2235
  try {
2215
2236
  const { err, libraryConfig } = await getChannelPromise(libraryConfigurationCh, {
2216
2237
  frameworkVersion: jestVersion,
2217
2238
  })
2218
2239
  if (!err) {
2240
+ shouldResetLibraryConfiguration = false
2219
2241
  isCodeCoverageEnabled = libraryConfig.isCodeCoverageEnabled
2220
2242
  isCoverageReportUploadEnabled = libraryConfig.isCoverageReportUploadEnabled
2221
2243
  isItrEnabled = libraryConfig.isItrEnabled
@@ -2231,6 +2253,10 @@ function getCliWrapper (isNewJestVersion) {
2231
2253
  }
2232
2254
  } catch (err) {
2233
2255
  log.error('Jest library configuration error', err)
2256
+ } finally {
2257
+ if (shouldResetLibraryConfiguration) {
2258
+ resetLibraryConfiguration()
2259
+ }
2234
2260
  }
2235
2261
 
2236
2262
  const {
@@ -135,7 +135,7 @@ function wrapPoolMethod (createConnection) {
135
135
 
136
136
  function wrapPoolGetConnectionMethod (getConnection) {
137
137
  return function wrappedGetConnection (...args) {
138
- const cb = args[args.length - 1]
138
+ const cb = args.at(-1)
139
139
  if (typeof cb !== 'function') return getConnection.apply(this, args)
140
140
 
141
141
  const ctx = {}
@@ -553,7 +553,6 @@ addHook({
553
553
 
554
554
  const runner = run.apply(this, args)
555
555
 
556
- // eslint-disable-next-line unicorn/no-array-for-each
557
556
  this.files.forEach((path) => {
558
557
  const isUnskippable = isMarkedAsUnskippable({ path })
559
558
  if (isUnskippable) {
@@ -915,7 +914,6 @@ addHook({
915
914
  status = 'skip'
916
915
  } else {
917
916
  // has to check every test in the test file
918
- // eslint-disable-next-line unicorn/no-array-for-each
919
917
  suitesInTestFile.forEach(suite => {
920
918
  suite.eachTest(test => {
921
919
  if (test.state === 'failed' || test.timedOut) {
@@ -229,7 +229,6 @@ function getSuitesByTestFile (root) {
229
229
  suitesByTestFile[suite.file] = [suite]
230
230
  }
231
231
  }
232
- // eslint-disable-next-line unicorn/no-array-for-each
233
232
  suite.suites.forEach(suite => {
234
233
  getSuites(suite)
235
234
  })
@@ -920,7 +919,6 @@ function getOnPendingHandler () {
920
919
  function getRunTestsWrapper (runTests, config) {
921
920
  return function (suite) {
922
921
  if (config.isTestManagementTestsEnabled) {
923
- // eslint-disable-next-line unicorn/no-array-for-each
924
922
  suite.tests.forEach((test) => {
925
923
  const { isAttemptToFix, isDisabled, isQuarantined } = getTestProperties(test, config.testManagementTests)
926
924
  if (isAttemptToFix && !test.isPending()) {
@@ -944,7 +942,6 @@ function getRunTestsWrapper (runTests, config) {
944
942
  }
945
943
 
946
944
  if (config.isImpactedTestsEnabled) {
947
- // eslint-disable-next-line unicorn/no-array-for-each
948
945
  suite.tests.forEach((test) => {
949
946
  isModifiedCh.publish({
950
947
  modifiedFiles: config.modifiedFiles,
@@ -968,7 +965,6 @@ function getRunTestsWrapper (runTests, config) {
968
965
 
969
966
  if (config.isKnownTestsEnabled) {
970
967
  // by the time we reach `this.on('test')`, it is too late. We need to add retries here
971
- // eslint-disable-next-line unicorn/no-array-for-each
972
968
  suite.tests.forEach((test) => {
973
969
  if (!test.isPending() && isNewTest(test, config.knownTests)) {
974
970
  test._ddIsNew = true
@@ -20,11 +20,11 @@ const collectionMethodsWithFilter = [
20
20
  'replaceOne',
21
21
  ] // findOne is ignored because it calls to find
22
22
 
23
- const collectionMethodsWithTwoFilters = [
23
+ const collectionMethodsWithTwoFilters = new Set([
24
24
  'findOneAndUpdate',
25
25
  'updateMany',
26
26
  'updateOne',
27
- ]
27
+ ])
28
28
 
29
29
  const startCh = channel('datadog:mongodb:collection:filter:start')
30
30
 
@@ -36,7 +36,7 @@ addHook({ name: 'mongodb', versions: ['>=3.3 <5', '5', '>=6'] }, mongodb => {
36
36
  for (const methodName of [...collectionMethodsWithFilter, ...collectionMethodsWithTwoFilters]) {
37
37
  if (!(methodName in mongodb.Collection.prototype)) continue
38
38
 
39
- const useTwoArguments = collectionMethodsWithTwoFilters.includes(methodName)
39
+ const useTwoArguments = collectionMethodsWithTwoFilters.has(methodName)
40
40
 
41
41
  shimmer.wrap(mongodb.Collection.prototype, methodName, method => {
42
42
  return function (...args) {
@@ -55,11 +55,11 @@ const collectionMethodsWithFilter = [
55
55
  'remove',
56
56
  ]
57
57
 
58
- const collectionMethodsWithTwoFilters = [
58
+ const collectionMethodsWithTwoFilters = new Set([
59
59
  'findOneAndUpdate',
60
60
  'updateMany',
61
61
  'updateOne',
62
- ]
62
+ ])
63
63
 
64
64
  addHook({
65
65
  name: 'mongoose',
@@ -67,7 +67,7 @@ addHook({
67
67
  file: 'lib/model.js',
68
68
  }, Model => {
69
69
  for (const methodName of [...collectionMethodsWithFilter, ...collectionMethodsWithTwoFilters]) {
70
- const useTwoArguments = collectionMethodsWithTwoFilters.includes(methodName)
70
+ const useTwoArguments = collectionMethodsWithTwoFilters.has(methodName)
71
71
  if (!(methodName in Model)) continue
72
72
 
73
73
  shimmer.wrap(Model, methodName, method => {
@@ -20,14 +20,14 @@ const methods = [
20
20
  'where',
21
21
  ]
22
22
 
23
- const methodsOptionalArgs = ['findOneAndUpdate']
23
+ const methodsOptionalArgs = new Set(['findOneAndUpdate'])
24
24
 
25
25
  function getFilters (args, methodName) {
26
26
  const [arg0, arg1] = args
27
27
 
28
28
  const filters = arg0 !== null && typeof arg0 === 'object' ? [arg0] : []
29
29
 
30
- if (arg1 !== null && typeof arg1 === 'object' && methodsOptionalArgs.includes(methodName)) {
30
+ if (arg1 !== null && typeof arg1 === 'object' && methodsOptionalArgs.has(methodName)) {
31
31
  filters.push(arg1)
32
32
  }
33
33
 
@@ -84,7 +84,7 @@ addHook({ name: 'mysql', file: 'lib/Pool.js', versions: ['>=2'] }, Pool => {
84
84
  const finish = () => finishPoolQueryCh.publish(ctx)
85
85
 
86
86
  return startPoolQueryCh.runStores(ctx, () => {
87
- const cb = args[args.length - 1]
87
+ const cb = args.at(-1)
88
88
  if (typeof cb === 'function') {
89
89
  args[args.length - 1] = shimmer.wrapCallback(cb, cb => function (...args) {
90
90
  return finishPoolQueryCh.runStores(ctx, cb, this, ...args)
@@ -1,6 +1,9 @@
1
1
  'use strict'
2
2
 
3
3
  const shimmer = require('../../datadog-shimmer')
4
+ const nomenclature = require('../../dd-trace/src/service-naming')
5
+ const spanEndingHook = require('../../dd-trace/src/opentelemetry/span-ending-hook')
6
+ const { RESOURCE_NAME } = require('../../../ext/tags')
4
7
  const { channel, addHook } = require('./helpers/instrument')
5
8
 
6
9
  const startChannel = channel('apm:next:request:start')
@@ -21,6 +24,27 @@ const NEXT_REQUEST_META = Symbol.for('NextInternalRequestMeta')
21
24
  const META_IS_MIDDLEWARE = 'middlewareInvoke'
22
25
  const encounteredMiddleware = new WeakSet()
23
26
 
27
+ // `next.span_type` value Next.js sets on its own OTel root request span; the whole detection surface.
28
+ const NEXT_BASE_SERVER_HANDLE_REQUEST = 'BaseServer.handleRequest'
29
+
30
+ // In OTel-bridge mode (`plugins: false` + `new tracer.TracerProvider().register()`) Next emits its
31
+ // own OTel spans and renames the root request span to `${method} ${route}` at finish, which the
32
+ // bridge routes into the DD operation name and leaves the resource as the bare method — the reverse
33
+ // of Datadog's contract. Correct it via the bridge's pre-finish hook. See span-ending-hook.js.
34
+ spanEndingHook.hook = (ddSpan) => {
35
+ const tags = ddSpan.context().getTags()
36
+ if (tags['next.span_type'] !== NEXT_BASE_SERVER_HANDLE_REQUEST) return
37
+
38
+ const method = tags['http.method']
39
+ const route = tags['next.route'] ?? tags['http.route']
40
+ // Next already wrote the RSC-aware `${method} ${route}` into `next.span_name`; prefer it so we
41
+ // mirror Next's own naming, and only construct the resource when it is absent.
42
+ const resource = tags['next.span_name'] ?? (route ? `${method} ${route}` : method)
43
+
44
+ ddSpan.setOperationName(nomenclature.opName('web', 'server', 'next'))
45
+ ddSpan.setTag(RESOURCE_NAME, resource)
46
+ }
47
+
24
48
  function wrapHandleRequest (handleRequest) {
25
49
  return function (req, res, pathname, query) {
26
50
  return instrument(req, res, () => handleRequest.apply(this, arguments))
@@ -84,8 +84,6 @@ addHook({
84
84
  onDone: resolve,
85
85
  })
86
86
  })
87
- }).catch(() => {
88
- // Ignore errors - report generation failed
89
87
  })
90
88
  }
91
89
 
@@ -144,8 +144,8 @@ addHook({ name: 'oracledb', versions: ['>=5'], file: 'lib/oracledb.js' }, oracle
144
144
  shimmer.wrap(oracledb.Pool.prototype, 'getConnection', getConnection => {
145
145
  return function wrappedGetConnection (...args) {
146
146
  let callback
147
- if (typeof args[args.length - 1] === 'function') {
148
- callback = args[args.length - 1]
147
+ if (typeof args.at(-1) === 'function') {
148
+ callback = args.at(-1)
149
149
  }
150
150
  if (callback) {
151
151
  args[args.length - 1] = shimmer.wrapFunction(callback, callback => (err, connection) => {
@@ -97,7 +97,7 @@ function wrapQuery (query) {
97
97
 
98
98
  // Based on: https://github.com/brianc/node-postgres/blob/54eb0fa216aaccd727765641e7d1cf5da2bc483d/packages/pg/lib/client.js#L510
99
99
  const reusingQuery = typeof pgQuery.submit === 'function'
100
- const callback = args[args.length - 1]
100
+ const callback = args.at(-1)
101
101
 
102
102
  finish(error)
103
103
 
@@ -197,7 +197,7 @@ function wrapPoolQuery (query) {
197
197
  const ctx = { query: pgQuery, abortController }
198
198
 
199
199
  return startPoolQueryCh.runStores(ctx, () => {
200
- const cb = args[args.length - 1]
200
+ const cb = args.at(-1)
201
201
 
202
202
  if (abortController.signal.aborted) {
203
203
  const error = abortController.signal.reason || new Error('Aborted')
@@ -25,6 +25,9 @@ const log = require('../../dd-trace/src/log')
25
25
  const {
26
26
  getValueFromEnvSources,
27
27
  } = require('../../dd-trace/src/config/helper')
28
+ const {
29
+ RUM_TEST_EXECUTION_ID_COOKIE_NAME: RUM_COOKIE_NAME,
30
+ } = require('../../dd-trace/src/ci-visibility/rum')
28
31
  const { DD_MAJOR } = require('../../../version')
29
32
  const { addHook, channel, tracingChannel } = require('./helpers/instrument')
30
33
 
@@ -33,6 +36,7 @@ const testFinishCh = channel('ci:playwright:test:finish')
33
36
  const testSkipCh = channel('ci:playwright:test:skip')
34
37
 
35
38
  const testSessionStartCh = channel('ci:playwright:session:start')
39
+ const testSessionConfigurationCh = channel('ci:playwright:session:configuration')
36
40
  const testSessionFinishCh = channel('ci:playwright:session:finish')
37
41
 
38
42
  const libraryConfigurationCh = channel('ci:playwright:library-configuration')
@@ -51,6 +55,9 @@ const dispatcherRunCh = tracingChannel('orchestrion:playwright:Dispatcher_run')
51
55
  const dispatcherCreateWorkerCh = tracingChannel('orchestrion:playwright:Dispatcher_createWorker')
52
56
  const processHostStartRunnerCh = tracingChannel('orchestrion:playwright:ProcessHost_startRunner')
53
57
  const createRootSuiteCh = tracingChannel('orchestrion:playwright:createRootSuite')
58
+ const artifactsRecorderScreenshotPathCh =
59
+ tracingChannel('orchestrion:playwright:ArtifactsRecorder_createScreenshotAttachmentPath')
60
+ const snapshotRecorderScreenshotPathCh = tracingChannel('orchestrion:playwright:SnapshotRecorder_createAttachmentPath')
54
61
  const pageGotoCh = tracingChannel('orchestrion:playwright-core:Page_goto')
55
62
 
56
63
  const testToCtx = new WeakMap()
@@ -61,6 +68,8 @@ const testsToTestStatuses = new Map()
61
68
 
62
69
  const RUM_FLUSH_WAIT_TIME = getValueFromEnvSources('DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS')
63
70
  const DD_PROPERTIES_TIMEOUT = 5000
71
+ const isFailureScreenshotUploadEnabled =
72
+ getValueFromEnvSources('DD_TEST_FAILURE_SCREENSHOTS_ENABLED') === true
64
73
 
65
74
  let applyRepeatEachIndex = null
66
75
 
@@ -115,8 +124,21 @@ const EFD_RETRY_COUNT_RESPONSE = 'ddEfdRetryCountResponse'
115
124
  const DD_PROPERTIES_REQUEST = 'ddPropertiesRequest'
116
125
  const DD_PROPERTIES_RESPONSE = 'ddProperties'
117
126
  const kDdPlaywrightDisabledTestIds = Symbol('ddPlaywrightDisabledTestIds')
127
+ const kDdPlaywrightFailureScreenshots = Symbol('ddPlaywrightFailureScreenshots')
118
128
  const kDdPlaywrightWorkerHostInstrumented = Symbol('ddPlaywrightWorkerHostInstrumented')
119
129
  const kDdPlaywrightWorkerInstrumented = Symbol('ddPlaywrightWorkerInstrumented')
130
+ const PLAYWRIGHT_FAILURE_SCREENSHOT_PATH_RE = /(?:^|[\\/])test-failed-\d+\.png$/
131
+ const automaticFailureScreenshotPaths = new Set()
132
+
133
+ /**
134
+ * Returns whether Playwright's internal screenshot recorder created an attachment.
135
+ *
136
+ * @param {object} attachment - Playwright attachment payload
137
+ * @returns {boolean}
138
+ */
139
+ function isAutomaticFailureScreenshotAttachment (attachment) {
140
+ return typeof attachment?.path === 'string' && automaticFailureScreenshotPaths.delete(attachment.path)
141
+ }
120
142
 
121
143
  function isValidKnownTests (receivedKnownTests) {
122
144
  return !!receivedKnownTests.playwright
@@ -501,6 +523,23 @@ function getProjectsFromRunner (runner, configArg) {
501
523
  })
502
524
  }
503
525
 
526
+ /**
527
+ * Returns whether at least one Playwright project captures automatic screenshots for failed tests.
528
+ *
529
+ * @param {Array<object>} projects - Playwright projects with resolved use options
530
+ * @returns {boolean} Whether failure screenshot capture is enabled
531
+ */
532
+ function isFailureScreenshotCaptureEnabled (projects) {
533
+ for (const project of projects) {
534
+ const screenshot = project.use?.screenshot
535
+ const mode = typeof screenshot === 'object' && screenshot !== null ? screenshot.mode : screenshot
536
+ if (mode === 'on' || mode === 'only-on-failure' || mode === 'on-first-failure') {
537
+ return true
538
+ }
539
+ }
540
+ return false
541
+ }
542
+
504
543
  function getProjectsFromDispatcher (dispatcher) {
505
544
  const bundledConfig = dispatcher._testRun?.config?.config?.projects
506
545
  if (bundledConfig) {
@@ -993,6 +1032,7 @@ function onDispatcherCreateWorker (dispatcher, worker) {
993
1032
 
994
1033
  const projects = getProjectsFromDispatcher(dispatcher)
995
1034
  sessionProjects = projects
1035
+ const automaticFailureScreenshotPathsByTestId = new Map()
996
1036
 
997
1037
  if (disabledTestIds.size && !worker[kDdPlaywrightWorkerHostInstrumented] &&
998
1038
  typeof worker.runTestGroup === 'function') {
@@ -1021,6 +1061,16 @@ function onDispatcherCreateWorker (dispatcher, worker) {
1021
1061
  const shouldCreateTestSpan = test.expectedStatus === 'skipped'
1022
1062
  testBeginHandler(test, browser, shouldCreateTestSpan)
1023
1063
  })
1064
+ worker.on('attach', ({ testId, path, _ddIsAutomaticFailureScreenshot }) => {
1065
+ if (!_ddIsAutomaticFailureScreenshot) return
1066
+
1067
+ let screenshotPaths = automaticFailureScreenshotPathsByTestId.get(testId)
1068
+ if (!screenshotPaths) {
1069
+ screenshotPaths = new Set()
1070
+ automaticFailureScreenshotPathsByTestId.set(testId, screenshotPaths)
1071
+ }
1072
+ screenshotPaths.add(path)
1073
+ })
1024
1074
  worker.on('testEnd', ({ testId, status, errors, annotations }) => {
1025
1075
  const test = getTestByTestId(dispatcher, testId)
1026
1076
  if (!test) return
@@ -1043,6 +1093,20 @@ function onDispatcherCreateWorker (dispatcher, worker) {
1043
1093
  }
1044
1094
  )
1045
1095
  const testResult = test.results.at(-1)
1096
+ const automaticFailureScreenshotPaths = automaticFailureScreenshotPathsByTestId.get(testId)
1097
+ automaticFailureScreenshotPathsByTestId.delete(testId)
1098
+ if (testStatus === 'fail' && automaticFailureScreenshotPaths?.size && testResult?.attachments?.length) {
1099
+ const screenshots = []
1100
+ for (const attachment of testResult.attachments) {
1101
+ if (automaticFailureScreenshotPaths.has(attachment.path)) {
1102
+ screenshots.push(attachment)
1103
+ }
1104
+ }
1105
+ if (screenshots.length) {
1106
+ worker[kDdPlaywrightFailureScreenshots] ??= []
1107
+ worker[kDdPlaywrightFailureScreenshots].push(screenshots)
1108
+ }
1109
+ }
1046
1110
  const isAtrRetry = testResult?.retry > 0 &&
1047
1111
  isFlakyTestRetriesEnabled &&
1048
1112
  !test._ddIsAttemptToFix &&
@@ -1145,9 +1209,16 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
1145
1209
  let onDone
1146
1210
 
1147
1211
  rootDir = getRootDir(this, config)
1212
+ const projects = getProjectsFromRunner(this, config)
1213
+ const isFailureScreenshotEnabled = isFailureScreenshotCaptureEnabled(projects)
1148
1214
  const processArgv = process.argv.slice(2).join(' ')
1149
1215
  const command = `playwright ${processArgv}`
1150
- testSessionStartCh.publish({ command, frameworkVersion: playwrightVersion, rootDir })
1216
+ testSessionStartCh.publish({
1217
+ command,
1218
+ frameworkVersion: playwrightVersion,
1219
+ rootDir,
1220
+ isFailureScreenshotEnabled,
1221
+ })
1151
1222
 
1152
1223
  try {
1153
1224
  const { err, libraryConfig } = await getChannelPromise(
@@ -1174,6 +1245,8 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
1174
1245
  log.error('Playwright session start error', e)
1175
1246
  }
1176
1247
 
1248
+ testSessionConfigurationCh.publish({ isFailureScreenshotEnabled })
1249
+
1177
1250
  const isTestOptimizationSupported = satisfies(playwrightVersion, MINIMUM_SUPPORTED_VERSION_RANGE_EFD)
1178
1251
  const shouldGetKnownTests = isKnownTestsEnabled && isTestOptimizationSupported
1179
1252
  const shouldGetTestManagementTests = isTestManagementTestsEnabled && isTestOptimizationSupported
@@ -1239,8 +1312,6 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
1239
1312
  }
1240
1313
  }
1241
1314
 
1242
- const projects = getProjectsFromRunner(this, config)
1243
-
1244
1315
  // ATR and `--retries` are now compatible with Test Management.
1245
1316
  // Test Management tests have their retries set to 0 at the test level,
1246
1317
  // preventing them from being retried by ATR or `--retries`.
@@ -1288,7 +1359,7 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
1288
1359
 
1289
1360
  for (const [fqn, testStatuses] of testsToTestStatuses.entries()) {
1290
1361
  // Only count as failed if the final status (after retries) is 'fail'
1291
- const lastStatus = testStatuses[testStatuses.length - 1]
1362
+ const lastStatus = testStatuses.at(-1)
1292
1363
  if (lastStatus === 'fail') {
1293
1364
  totalFailedTestCount += 1
1294
1365
  if (quarantinedButNotAttemptToFixFqns.has(fqn)) {
@@ -1439,6 +1510,23 @@ pageGotoCh.subscribe({
1439
1510
  },
1440
1511
  })
1441
1512
 
1513
+ /**
1514
+ * Records a path created by Playwright's automatic screenshot recorder.
1515
+ *
1516
+ * @param {object} ctx - Orchestrion context
1517
+ * @returns {void}
1518
+ */
1519
+ function recordAutomaticFailureScreenshotPath (ctx) {
1520
+ if (isFailureScreenshotUploadEnabled &&
1521
+ typeof ctx.result === 'string' &&
1522
+ PLAYWRIGHT_FAILURE_SCREENSHOT_PATH_RE.test(ctx.result)) {
1523
+ automaticFailureScreenshotPaths.add(ctx.result)
1524
+ }
1525
+ }
1526
+
1527
+ artifactsRecorderScreenshotPathCh.subscribe({ end: recordAutomaticFailureScreenshotPath })
1528
+ snapshotRecorderScreenshotPathCh.subscribe({ end: recordAutomaticFailureScreenshotPath })
1529
+
1442
1530
  if (DD_MAJOR < 6) { // <1.38.0 is only supported up to version 5
1443
1531
  addHook({
1444
1532
  name: '@playwright/test',
@@ -1750,7 +1838,10 @@ function finishProcessHostStartRunner (processHost) {
1750
1838
  }
1751
1839
  // These messages are [code, payload]. The payload is test data
1752
1840
  if (Array.isArray(message) && message[0] === PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE) {
1753
- workerReportCh.publish(message[1])
1841
+ workerReportCh.publish({
1842
+ serializedTraces: message[1],
1843
+ screenshots: processHost[kDdPlaywrightFailureScreenshots]?.shift(),
1844
+ })
1754
1845
  }
1755
1846
  })
1756
1847
  }
@@ -1774,23 +1865,64 @@ addHook({
1774
1865
  })
1775
1866
 
1776
1867
  async function handlePageGoto (page) {
1868
+ if (!testPageGotoCh.hasSubscribers || !page || typeof page.evaluate !== 'function') {
1869
+ return
1870
+ }
1871
+
1872
+ let rumState
1777
1873
  try {
1778
- if (page && typeof page.evaluate === 'function') {
1779
- const { isRumInstrumented, isRumActive, rumSamplingRate } = await page.evaluate(detectRum)
1780
- if (isRumInstrumented && rumSamplingRate < 100 && !isRumActive) {
1781
- log.debug("RUM was detected on the page, but it isn't active because the sampling rate is below 100%")
1782
- }
1874
+ rumState = await page.evaluate(detectRum)
1875
+ } catch (error) {
1876
+ // Redirects and closed contexts can make page evaluation fail after a successful navigation.
1877
+ log.error('Playwright RUM detection error', error)
1878
+ return
1879
+ }
1783
1880
 
1784
- if (isRumActive) {
1785
- testPageGotoCh.publish({
1786
- isRumActive,
1787
- page,
1788
- })
1789
- }
1790
- }
1791
- } catch (e) {
1792
- // ignore errors such as redirects, context destroyed, etc
1793
- log.error('goto hook error', e)
1881
+ if (!rumState) {
1882
+ return
1883
+ }
1884
+
1885
+ const { isRumInstrumented, isRumActive, rumSamplingRate } = rumState
1886
+ if (isRumInstrumented && rumSamplingRate < 100 && !isRumActive) {
1887
+ log.debug("RUM was detected on the page, but it isn't active because the sampling rate is below 100%")
1888
+ }
1889
+ if (!isRumActive) {
1890
+ return
1891
+ }
1892
+
1893
+ let browserVersion
1894
+ try {
1895
+ browserVersion = page.context().browser()?.version()
1896
+ } catch (error) {
1897
+ log.error('Playwright browser metadata error', error)
1898
+ }
1899
+
1900
+ const context = {
1901
+ isRumActive,
1902
+ browserVersion,
1903
+ testExecutionId: undefined,
1904
+ }
1905
+ try {
1906
+ testPageGotoCh.publish(context)
1907
+ } catch (error) {
1908
+ log.error('Playwright RUM correlation channel error', error)
1909
+ return
1910
+ }
1911
+
1912
+ if (!context.testExecutionId) {
1913
+ return
1914
+ }
1915
+
1916
+ try {
1917
+ const domain = new URL(page.url()).hostname
1918
+ await page.context().addCookies([{
1919
+ name: RUM_COOKIE_NAME,
1920
+ value: context.testExecutionId,
1921
+ domain,
1922
+ path: '/',
1923
+ }])
1924
+ } catch (error) {
1925
+ log.error('Playwright RUM correlation cookie error', error)
1794
1926
  }
1795
1927
  }
1796
1928
 
@@ -1918,10 +2050,11 @@ function instrumentWorkerMainMethods (workerMain) {
1918
2050
  if (url) {
1919
2051
  const domain = new URL(url).hostname
1920
2052
  await page.context().addCookies([{
1921
- name: 'datadog-ci-visibility-test-execution-id',
2053
+ name: RUM_COOKIE_NAME,
1922
2054
  value: '',
1923
2055
  domain,
1924
2056
  path: '/',
2057
+ expires: 0,
1925
2058
  }])
1926
2059
  } else {
1927
2060
  log.error('RUM is active but page.url() is not available')
@@ -2042,7 +2175,9 @@ function instrumentWorkerMainMethods (workerMain) {
2042
2175
  // We reproduce what happens in `Dispatcher#_onStepBegin` and `Dispatcher#_onStepEnd`,
2043
2176
  // since `startTime` and `duration` are not available directly in the worker process
2044
2177
  shimmer.wrap(workerMain, 'dispatchEvent', dispatchEvent => function (event, payload) {
2045
- if (event === 'stepBegin') {
2178
+ if (event === 'testBegin' || event === 'testEnd') {
2179
+ automaticFailureScreenshotPaths.clear()
2180
+ } else if (event === 'stepBegin') {
2046
2181
  stepInfoByStepId[payload.stepId] = {
2047
2182
  startTime: payload.wallTime,
2048
2183
  title: payload.title,
@@ -2058,6 +2193,8 @@ function instrumentWorkerMainMethods (workerMain) {
2058
2193
  duration: payload.wallTime - stepInfo.startTime,
2059
2194
  error: payload.error,
2060
2195
  })
2196
+ } else if (event === 'attach' && isAutomaticFailureScreenshotAttachment(payload)) {
2197
+ payload._ddIsAutomaticFailureScreenshot = true
2061
2198
  }
2062
2199
  return dispatchEvent.apply(this, arguments)
2063
2200
  })
@@ -1,5 +1,7 @@
1
1
  'use strict'
2
2
 
3
+ const { syncBuiltinESMExports } = require('node:module')
4
+
3
5
  const { channel } = require('dc-polyfill')
4
6
  const shimmer = require('../../datadog-shimmer')
5
7
 
@@ -26,4 +28,5 @@ if (process.setUncaughtExceptionCaptureCallback) {
26
28
  return result
27
29
  }
28
30
  })
31
+ syncBuiltinESMExports()
29
32
  }