dd-trace 5.109.0 → 5.111.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 (208) hide show
  1. package/README.md +13 -8
  2. package/ci/init.js +25 -5
  3. package/ci/vitest-no-worker-init-setup.mjs +430 -0
  4. package/ext/tags.js +2 -0
  5. package/index.d.ts +79 -24
  6. package/initialize.mjs +5 -6
  7. package/loader-hook.mjs +154 -47
  8. package/package.json +20 -16
  9. package/packages/datadog-esbuild/index.js +26 -0
  10. package/packages/datadog-esbuild/src/utils.js +46 -3
  11. package/packages/datadog-instrumentations/src/connect.js +4 -3
  12. package/packages/datadog-instrumentations/src/fastify.js +4 -12
  13. package/packages/datadog-instrumentations/src/fs.js +8 -6
  14. package/packages/datadog-instrumentations/src/graphql.js +26 -484
  15. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +81 -8
  16. package/packages/datadog-instrumentations/src/helpers/instrument.js +27 -2
  17. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +65 -0
  18. package/packages/datadog-instrumentations/src/helpers/register.js +6 -4
  19. package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +17 -0
  20. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +201 -0
  21. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +18 -2
  23. package/packages/datadog-instrumentations/src/koa.js +3 -2
  24. package/packages/datadog-instrumentations/src/mariadb.js +4 -2
  25. package/packages/datadog-instrumentations/src/mocha/main.js +3 -3
  26. package/packages/datadog-instrumentations/src/mongoose.js +14 -2
  27. package/packages/datadog-instrumentations/src/next.js +10 -10
  28. package/packages/datadog-instrumentations/src/otel-sdk-trace.js +7 -6
  29. package/packages/datadog-instrumentations/src/playwright.js +1 -1
  30. package/packages/datadog-instrumentations/src/restify.js +4 -3
  31. package/packages/datadog-instrumentations/src/router.js +18 -5
  32. package/packages/datadog-instrumentations/src/selenium.js +1 -2
  33. package/packages/datadog-instrumentations/src/tedious.js +28 -0
  34. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +1293 -0
  35. package/packages/datadog-instrumentations/src/vitest-main.js +1567 -0
  36. package/packages/datadog-instrumentations/src/vitest-util.js +249 -0
  37. package/packages/datadog-instrumentations/src/vitest-worker.js +753 -0
  38. package/packages/datadog-instrumentations/src/vitest.js +11 -1598
  39. package/packages/datadog-plugin-avsc/src/schema_iterator.js +2 -2
  40. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/client.js +2 -2
  41. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +18 -10
  42. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +47 -11
  43. package/packages/datadog-plugin-aws-sdk/src/base.js +13 -9
  44. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +43 -6
  45. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +83 -91
  46. package/packages/datadog-plugin-cucumber/src/index.js +1 -1
  47. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -5
  48. package/packages/datadog-plugin-cypress/src/support.js +45 -1
  49. package/packages/datadog-plugin-express/src/code_origin.js +1 -1
  50. package/packages/datadog-plugin-fastify/src/code_origin.js +1 -1
  51. package/packages/datadog-plugin-graphql/src/execute.js +627 -11
  52. package/packages/datadog-plugin-graphql/src/index.js +20 -8
  53. package/packages/datadog-plugin-graphql/src/parse.js +24 -4
  54. package/packages/datadog-plugin-graphql/src/validate.js +16 -5
  55. package/packages/datadog-plugin-http/src/client.js +8 -3
  56. package/packages/datadog-plugin-http/src/index.js +6 -8
  57. package/packages/datadog-plugin-http2/src/client.js +5 -2
  58. package/packages/datadog-plugin-jest/src/index.js +1 -1
  59. package/packages/datadog-plugin-mocha/src/index.js +1 -1
  60. package/packages/datadog-plugin-mysql/src/index.js +7 -2
  61. package/packages/datadog-plugin-openai/src/services.js +2 -2
  62. package/packages/datadog-plugin-playwright/src/index.js +1 -1
  63. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +2 -2
  64. package/packages/datadog-plugin-undici/src/index.js +5 -2
  65. package/packages/datadog-plugin-vitest/src/index.js +76 -64
  66. package/packages/datadog-shimmer/src/shimmer.js +37 -16
  67. package/packages/datadog-webpack/index.js +17 -1
  68. package/packages/datadog-webpack/src/optional-peer-loader.js +17 -0
  69. package/packages/dd-trace/src/aiguard/sdk.js +15 -3
  70. package/packages/dd-trace/src/appsec/api_security/index.js +55 -0
  71. package/packages/dd-trace/src/appsec/api_security/sampler.js +147 -0
  72. package/packages/dd-trace/src/appsec/channels.js +3 -1
  73. package/packages/dd-trace/src/appsec/downstream_requests.js +4 -4
  74. package/packages/dd-trace/src/appsec/graphql.js +5 -5
  75. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +26 -19
  76. package/packages/dd-trace/src/appsec/iast/security-controls/index.js +2 -2
  77. package/packages/dd-trace/src/appsec/iast/telemetry/index.js +2 -1
  78. package/packages/dd-trace/src/appsec/index.js +20 -7
  79. package/packages/dd-trace/src/appsec/lambda.js +135 -0
  80. package/packages/dd-trace/src/appsec/reporter.js +50 -10
  81. package/packages/dd-trace/src/appsec/telemetry/api_security.js +34 -0
  82. package/packages/dd-trace/src/appsec/telemetry/index.js +27 -1
  83. package/packages/dd-trace/src/appsec/waf/index.js +16 -4
  84. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -4
  85. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +21 -6
  86. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +2 -2
  87. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
  88. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +1 -1
  89. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +3 -2
  90. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -1
  91. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +55 -13
  92. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -2
  93. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +17 -0
  94. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +47 -26
  95. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +2 -2
  96. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +64 -53
  97. package/packages/dd-trace/src/ci-visibility/requests/request.js +1 -17
  98. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +3 -3
  99. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +23 -5
  100. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +128 -0
  101. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +287 -0
  102. package/packages/dd-trace/src/config/defaults.js +25 -5
  103. package/packages/dd-trace/src/config/generated-config-types.d.ts +596 -58
  104. package/packages/dd-trace/src/config/helper.js +134 -33
  105. package/packages/dd-trace/src/config/index.js +39 -23
  106. package/packages/dd-trace/src/config/major-overrides.js +4 -2
  107. package/packages/dd-trace/src/config/parsers.js +19 -1
  108. package/packages/dd-trace/src/config/remote_config.js +1 -1
  109. package/packages/dd-trace/src/config/supported-configurations.json +109 -57
  110. package/packages/dd-trace/src/constants.js +7 -0
  111. package/packages/dd-trace/src/datastreams/processor.js +11 -3
  112. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +47 -35
  113. package/packages/dd-trace/src/debugger/devtools_client/index.js +31 -66
  114. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +83 -0
  115. package/packages/dd-trace/src/debugger/devtools_client/state.js +1 -0
  116. package/packages/dd-trace/src/debugger/index.js +5 -0
  117. package/packages/dd-trace/src/debugger/probe_sampler.js +112 -0
  118. package/packages/dd-trace/src/debugger/probe_sampler_constants.js +19 -0
  119. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +77 -26
  120. package/packages/dd-trace/src/exporters/agent/index.js +1 -1
  121. package/packages/dd-trace/src/exporters/agentless/writer.js +4 -4
  122. package/packages/dd-trace/src/exporters/common/request.js +1 -20
  123. package/packages/dd-trace/src/exporters/common/url.js +34 -0
  124. package/packages/dd-trace/src/exporters/span-stats/writer.js +1 -3
  125. package/packages/dd-trace/src/guardrails/index.js +8 -5
  126. package/packages/dd-trace/src/index.js +6 -4
  127. package/packages/dd-trace/src/llmobs/constants/tags.js +6 -0
  128. package/packages/dd-trace/src/llmobs/index.js +17 -6
  129. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -4
  130. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +3 -3
  131. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +5 -1
  132. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  133. package/packages/dd-trace/src/llmobs/sdk.js +8 -6
  134. package/packages/dd-trace/src/llmobs/span_processor.js +5 -1
  135. package/packages/dd-trace/src/llmobs/tagger.js +58 -3
  136. package/packages/dd-trace/src/llmobs/telemetry.js +1 -0
  137. package/packages/dd-trace/src/llmobs/writers/base.js +1 -1
  138. package/packages/dd-trace/src/log/index.js +18 -20
  139. package/packages/dd-trace/src/openfeature/flagging_provider.js +2 -7
  140. package/packages/dd-trace/src/opentelemetry/bridge-span-base.js +5 -4
  141. package/packages/dd-trace/src/opentelemetry/metrics/index.js +54 -5
  142. package/packages/dd-trace/src/opentelemetry/metrics/instruments.js +2 -1
  143. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +40 -0
  144. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +169 -0
  145. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +4 -3
  146. package/packages/dd-trace/src/opentelemetry/metrics/time.js +19 -0
  147. package/packages/dd-trace/src/opentelemetry/otlp/common.proto +2 -2
  148. package/packages/dd-trace/src/opentelemetry/otlp/logs.proto +1 -1
  149. package/packages/dd-trace/src/opentelemetry/otlp/logs_service.proto +1 -1
  150. package/packages/dd-trace/src/opentelemetry/otlp/metrics.proto +1 -1
  151. package/packages/dd-trace/src/opentelemetry/otlp/metrics_service.proto +1 -1
  152. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +44 -38
  153. package/packages/dd-trace/src/opentelemetry/span-helpers.js +29 -13
  154. package/packages/dd-trace/src/opentelemetry/span.js +14 -3
  155. package/packages/dd-trace/src/opentelemetry/span_context.js +1 -0
  156. package/packages/dd-trace/src/opentelemetry/trace/index.js +6 -1
  157. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +3 -2
  158. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +26 -13
  159. package/packages/dd-trace/src/opentelemetry/tracer_provider.js +1 -1
  160. package/packages/dd-trace/src/opentracing/propagation/text_map.js +3 -3
  161. package/packages/dd-trace/src/opentracing/span_context.js +17 -0
  162. package/packages/dd-trace/src/opentracing/tracer.js +6 -1
  163. package/packages/dd-trace/src/plugin_manager.js +14 -8
  164. package/packages/dd-trace/src/plugins/ci_plugin.js +21 -4
  165. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +276 -0
  166. package/packages/dd-trace/src/plugins/util/llm.js +6 -1
  167. package/packages/dd-trace/src/plugins/util/test.js +91 -9
  168. package/packages/dd-trace/src/plugins/util/url.js +68 -1
  169. package/packages/dd-trace/src/plugins/util/web.js +19 -36
  170. package/packages/dd-trace/src/priority_sampler.js +1 -13
  171. package/packages/dd-trace/src/profiling/config.js +129 -198
  172. package/packages/dd-trace/src/profiling/constants.js +4 -7
  173. package/packages/dd-trace/src/profiling/exporter_cli.js +10 -10
  174. package/packages/dd-trace/src/profiling/exporters/agent.js +23 -16
  175. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +45 -14
  176. package/packages/dd-trace/src/profiling/exporters/file.js +16 -5
  177. package/packages/dd-trace/src/profiling/oom.js +71 -0
  178. package/packages/dd-trace/src/profiling/profiler.js +35 -23
  179. package/packages/dd-trace/src/profiling/profilers/events.js +14 -7
  180. package/packages/dd-trace/src/profiling/profilers/space.js +27 -18
  181. package/packages/dd-trace/src/profiling/profilers/wall.js +35 -25
  182. package/packages/dd-trace/src/proxy.js +8 -8
  183. package/packages/dd-trace/src/ritm.js +4 -2
  184. package/packages/dd-trace/src/serverless.js +1 -2
  185. package/packages/dd-trace/src/service-naming/schemas/util.js +36 -5
  186. package/packages/dd-trace/src/service-naming/schemas/v1/web.js +5 -3
  187. package/packages/dd-trace/src/span_format.js +2 -1
  188. package/packages/dd-trace/src/span_processor.js +11 -6
  189. package/packages/dd-trace/src/span_stats.js +96 -78
  190. package/packages/dd-trace/src/startup-log.js +2 -1
  191. package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
  192. package/packages/dd-trace/src/telemetry/endpoints.js +6 -4
  193. package/packages/dd-trace/src/telemetry/index.js +2 -2
  194. package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
  195. package/packages/dd-trace/src/telemetry/send-data.js +20 -18
  196. package/packages/dd-trace/src/telemetry/session-propagation.js +1 -1
  197. package/packages/dd-trace/src/telemetry/telemetry.js +9 -9
  198. package/packages/dd-trace/src/tracer.js +15 -1
  199. package/packages/dd-trace/src/util.js +14 -0
  200. package/register.js +58 -1
  201. package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
  202. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  203. package/vendor/dist/pprof-format/index.js +1 -1
  204. package/vendor/dist/protobufjs/index.js +1 -1
  205. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  206. package/vendor/dist/shell-quote/index.js +1 -1
  207. package/packages/datadog-plugin-graphql/src/resolve.js +0 -170
  208. package/packages/dd-trace/src/appsec/api_security_sampler.js +0 -117
@@ -43,9 +43,9 @@ class SchemaExtractor {
43
43
  let type
44
44
  let format
45
45
  let enumValues
46
- let description
47
46
  let ref
48
47
 
48
+ const description = field.doc
49
49
  const fieldType = field.type?.types ?? field.type?.typeName ?? field.type
50
50
 
51
51
  if (Array.isArray(fieldType)) {
@@ -129,7 +129,7 @@ class SchemaExtractor {
129
129
  }
130
130
 
131
131
  iterateOverSchema (builder) {
132
- this.constructor.extractSchema(this.schema, builder, 0)
132
+ SchemaExtractor.extractSchema(this.schema, builder, 0)
133
133
  }
134
134
 
135
135
  static attachSchemaOnSpan (args, span, operation, tracer) {
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const ClientPlugin = require('../../dd-trace/src/plugins/client')
4
- const { addOpMeta, unwrapDurableError } = require('./util')
4
+ const { addOpMeta, getStepDataForNext, unwrapDurableError } = require('./util')
5
5
 
6
6
  class AwsDurableExecutionSdkJsClientPlugin extends ClientPlugin {
7
7
  static id = 'aws-durable-execution-sdk-js'
@@ -29,7 +29,7 @@ class AwsDurableExecutionSdkJsClientPlugin extends ClientPlugin {
29
29
  if (operationName) {
30
30
  meta['aws.durable.operation_name'] = operationName
31
31
  }
32
- addOpMeta(meta, ctx.self)
32
+ addOpMeta(meta, getStepDataForNext(ctx.self))
33
33
 
34
34
  this.startSpan(this.operationName(), {
35
35
  resource: operationName,
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { storage } = require('../../datadog-core')
4
4
  const TracingPlugin = require('../../dd-trace/src/plugins/tracing')
5
- const { addOpMeta, unwrapDurableError } = require('./util')
5
+ const { addOpMeta, getOperationAttempt, getStepDataForNext, unwrapDurableError } = require('./util')
6
6
 
7
7
  // Span names whose direct children must keep the default resource.
8
8
  // These can have very high cardinality which is undesireable in the resource.
@@ -41,12 +41,18 @@ class BaseContextPlugin extends TracingPlugin {
41
41
  if (operationName) {
42
42
  meta['aws.durable.operation_name'] = operationName
43
43
  }
44
- addOpMeta(meta, ctx.self)
44
+ const stepInfo = getStepDataForNext(ctx.self)
45
+ addOpMeta(meta, stepInfo)
46
+
47
+ const metrics = this.constructor.retryable
48
+ ? { 'aws.durable.operation_attempt': getOperationAttempt(stepInfo.stepData) }
49
+ : undefined
45
50
 
46
51
  this.startSpan(spanName, {
47
52
  resource,
48
53
  kind: this.constructor.kind,
49
54
  meta,
55
+ metrics,
50
56
  }, ctx)
51
57
 
52
58
  return ctx.currentStore
@@ -71,11 +77,12 @@ class BaseContextPlugin extends TracingPlugin {
71
77
  }
72
78
  }
73
79
 
74
- function makeContextPlugin (method, spanName) {
80
+ function makeContextPlugin (method, spanName, { retryable }) {
75
81
  return class extends BaseContextPlugin {
76
82
  static prefix = `tracing:orchestrion:@aws/durable-execution-sdk-js:DurableContextImpl_${method}`
77
83
  static settleChannel = `apm:aws-durable-execution-sdk-js:${method}:settle`
78
84
  static spanName = spanName
85
+ static retryable = retryable
79
86
  }
80
87
  }
81
88
 
@@ -83,6 +90,7 @@ class RunInChildContextPlugin extends BaseContextPlugin {
83
90
  static prefix = 'tracing:orchestrion:@aws/durable-execution-sdk-js:DurableContextImpl_runInChildContext'
84
91
  static settleChannel = 'apm:aws-durable-execution-sdk-js:runInChildContext:settle'
85
92
  static spanName = 'aws.durable.child_context'
93
+ static retryable = false
86
94
 
87
95
  bindStart (ctx) {
88
96
  if (SUPPRESSED_CHILD_CONTEXT_SUBTYPES.has(getRunInChildContextSubType(ctx))) {
@@ -103,12 +111,12 @@ function getRunInChildContextSubType (ctx) {
103
111
  }
104
112
 
105
113
  module.exports = {
106
- step: makeContextPlugin('step', 'aws.durable.step'),
107
- wait: makeContextPlugin('wait', 'aws.durable.wait'),
108
- waitForCondition: makeContextPlugin('waitForCondition', 'aws.durable.wait_for_condition'),
109
- waitForCallback: makeContextPlugin('waitForCallback', 'aws.durable.wait_for_callback'),
110
- createCallback: makeContextPlugin('createCallback', 'aws.durable.create_callback'),
111
- map: makeContextPlugin('map', 'aws.durable.map'),
112
- parallel: makeContextPlugin('parallel', 'aws.durable.parallel'),
114
+ step: makeContextPlugin('step', 'aws.durable.step', { retryable: true }),
115
+ wait: makeContextPlugin('wait', 'aws.durable.wait', { retryable: false }),
116
+ waitForCondition: makeContextPlugin('waitForCondition', 'aws.durable.wait_for_condition', { retryable: true }),
117
+ waitForCallback: makeContextPlugin('waitForCallback', 'aws.durable.wait_for_callback', { retryable: false }),
118
+ createCallback: makeContextPlugin('createCallback', 'aws.durable.create_callback', { retryable: false }),
119
+ map: makeContextPlugin('map', 'aws.durable.map', { retryable: false }),
120
+ parallel: makeContextPlugin('parallel', 'aws.durable.parallel', { retryable: false }),
113
121
  runInChildContext: RunInChildContextPlugin,
114
122
  }
@@ -2,27 +2,63 @@
2
2
 
3
3
  const { createHash } = require('node:crypto')
4
4
 
5
+ // A checkpoint in one of these terminal states means the operation is served from the checkpoint
6
+ // on a replay rather than executed: the SDK reloads a SUCCEEDED result or re-raises a FAILED error
7
+ // without running the user function. Both also carry the 1-indexed attempt that reached the terminal
8
+ // state, so both need the same normalization to agree with the 0-indexed live run.
9
+ const REPLAYED_STATUSES = new Set(['SUCCEEDED', 'FAILED'])
10
+
5
11
  /**
6
- * Populates the replay and operation_id tags for the op the DurableContextImpl is about to
7
- * run, deriving both from a single `getNextStepId()` call. `aws.durable.replayed` is always
8
- * set ('true' when the next stepId already has a SUCCEEDED checkpoint entry, i.e. the op will
9
- * be served from the SDK's checkpoint). `aws.durable.operation_id` — the 16-hex-char MD5 of
10
- * the stepId, mirroring the SDK's internal calculation — is only added when a stepId exists.
11
- * @param {Record<string, string>} meta - The span meta/tags object to populate.
12
+ * Resolves the SDK's next stepId and its checkpoint entry in a single pass, so one span start can
13
+ * feed both addOpMeta and getOperationAttempt without traversing the SDK internals twice. `stepData`
14
+ * is undefined when there is no next stepId, or no checkpoint entry exists for it yet.
12
15
  * @param {object} [ctxImpl] - The DurableContextImpl about to run the op.
13
- * @returns {void}
16
+ * @returns {{ stepId: string | undefined, stepData: object | undefined }}
14
17
  */
15
- function addOpMeta (meta, ctxImpl) {
18
+ function getStepDataForNext (ctxImpl) {
16
19
  const stepId = ctxImpl?.getNextStepId?.()
20
+ const stepData = stepId ? ctxImpl?._executionContext?.getStepData?.(stepId) : undefined
21
+ return { stepId, stepData }
22
+ }
23
+
24
+ /**
25
+ * Populates the replay and operation_id tags from a pre-resolved step lookup (see
26
+ * getStepDataForNext). `aws.durable.replayed` is always set ('true' when the next stepId already
27
+ * has a terminal checkpoint entry — SUCCEEDED or FAILED — i.e. the op will be served from the SDK's
28
+ * checkpoint rather than executed). `aws.durable.operation_id` — the 16-hex-char MD5 of the stepId,
29
+ * mirroring the SDK's internal calculation — is only added when a stepId exists.
30
+ * @param {Record<string, string>} meta - The span meta/tags object to populate.
31
+ * @param {{ stepId?: string, stepData?: object }} stepInfo - Resolved next stepId and checkpoint entry.
32
+ * @returns {void}
33
+ */
34
+ function addOpMeta (meta, { stepId, stepData }) {
17
35
  if (!stepId) {
18
36
  meta['aws.durable.replayed'] = 'false'
19
37
  return
20
38
  }
21
- const stepData = ctxImpl?._executionContext?.getStepData?.(stepId)
22
- meta['aws.durable.replayed'] = String(stepData?.Status === 'SUCCEEDED')
39
+ meta['aws.durable.replayed'] = String(REPLAYED_STATUSES.has(stepData?.Status))
23
40
  meta['aws.durable.operation_id'] = createHash('md5').update(stepId).digest('hex').slice(0, 16)
24
41
  }
25
42
 
43
+ /**
44
+ * Returns the 0-indexed attempt number for the op (0 original, 1 first retry, …) from a pre-resolved
45
+ * checkpoint entry (see getStepDataForNext), defaulting to 0 before any checkpoint exists.
46
+ *
47
+ * StepDetails.Attempt is indexed differently depending on checkpoint status: on a pending/retry
48
+ * checkpoint it's the count of prior failed attempts (already 0-indexed), but on a terminal
49
+ * checkpoint read on replay (SUCCEEDED or FAILED) it's the 1-indexed attempt that reached that
50
+ * state. We subtract 1 in the terminal case so a replay agrees with the original run, flooring at
51
+ * 0 since the 1-indexing is observed server behavior, not an SDK guarantee.
52
+ *
53
+ * @param {object} [stepData] - The checkpoint entry for the next stepId.
54
+ * @returns {number}
55
+ */
56
+ function getOperationAttempt (stepData) {
57
+ const attempt = stepData?.StepDetails?.Attempt
58
+ if (!Number.isFinite(attempt)) return 0
59
+ return REPLAYED_STATUSES.has(stepData.Status) ? Math.max(0, attempt - 1) : attempt
60
+ }
61
+
26
62
  /**
27
63
  * The SDK wraps user errors in typed classes (StepError, ChildContextError, etc.); we follow the
28
64
  * `.cause` chain to recover the user's original Error. SDK wrappers expose a string `errorType`
@@ -40,4 +76,4 @@ function unwrapDurableError (ctx) {
40
76
  return err
41
77
  }
42
78
 
43
- module.exports = { addOpMeta, unwrapDurableError }
79
+ module.exports = { addOpMeta, getOperationAttempt, getStepDataForNext, unwrapDurableError }
@@ -108,7 +108,7 @@ class BaseAwsSdkPlugin extends ClientPlugin {
108
108
  const span = this.startSpan(this.operationFromRequest(request), {
109
109
  childOf,
110
110
  meta,
111
- service: this.serviceName(),
111
+ service: this.serviceName(request),
112
112
  integrationName: 'aws-sdk',
113
113
  }, ctx)
114
114
 
@@ -216,14 +216,18 @@ class BaseAwsSdkPlugin extends ClientPlugin {
216
216
  })
217
217
  }
218
218
 
219
- serviceName () {
220
- return this.config.service ||
221
- super.serviceName({
222
- id: 'aws',
223
- type: 'web',
224
- kind: 'client',
225
- awsService: this.serviceIdentifier,
226
- })
219
+ /**
220
+ * @param {{ params?: object }} request
221
+ */
222
+ serviceName (request) {
223
+ return super.serviceName({
224
+ id: 'aws',
225
+ type: 'web',
226
+ kind: 'client',
227
+ awsService: this.serviceIdentifier,
228
+ pluginConfig: this.config,
229
+ params: request.params,
230
+ })
227
231
  }
228
232
 
229
233
  isEnabled (request) {
@@ -2,6 +2,34 @@
2
2
  const log = require('../../../dd-trace/src/log')
3
3
  const BaseAwsSdkPlugin = require('../base')
4
4
 
5
+ // EventBridge enforces this limit over the whole PutEvents request (the sum of
6
+ // every entry), not over a single entry. 1 MiB == 1,048,576 bytes.
7
+ const MAX_PUT_EVENTS_BYTES = 1024 * 1024
8
+
9
+ /**
10
+ * Size a single `PutEventsRequestEntry` the way EventBridge does server-side:
11
+ * the UTF-8 byte length of `Source`, `DetailType`, `Detail`, and each
12
+ * `Resources` ARN, plus a flat 14 bytes when `Time` is set.
13
+ *
14
+ * @param {object} entry a single PutEvents request entry
15
+ * @param {string} [detail] overrides `entry.Detail`, used to size the entry as
16
+ * it would be sent with the injected `_datadog` context
17
+ * @returns {number}
18
+ * @see https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-putevents.html
19
+ */
20
+ function putEventEntrySize (entry, detail = entry.Detail) {
21
+ let size = entry.Time == null ? 0 : 14
22
+ if (entry.Source != null) size += Buffer.byteLength(entry.Source)
23
+ if (entry.DetailType != null) size += Buffer.byteLength(entry.DetailType)
24
+ if (detail != null) size += Buffer.byteLength(detail)
25
+ if (entry.Resources != null) {
26
+ for (const resource of entry.Resources) {
27
+ if (resource != null) size += Buffer.byteLength(resource)
28
+ }
29
+ }
30
+ return size
31
+ }
32
+
5
33
  class EventBridge extends BaseAwsSdkPlugin {
6
34
  static id = 'eventbridge'
7
35
  static isPayloadReporter = true
@@ -25,7 +53,8 @@ class EventBridge extends BaseAwsSdkPlugin {
25
53
  * Docs: https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_PutEventsRequestEntry.html
26
54
  * We cannot use the traceHeader field as that's reserved for X-Ray.
27
55
  * Detail must be a valid JSON string
28
- * Max size per event is 256kb (https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-putevent-size.html)
56
+ * Max PutEvents request size is 1mb, summed over all entries
57
+ * (https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-putevent-size.html)
29
58
  */
30
59
  requestInject (span, request) {
31
60
  const operation = request.operation
@@ -34,6 +63,7 @@ class EventBridge extends BaseAwsSdkPlugin {
34
63
  request.params.Entries &&
35
64
  request.params.Entries.length > 0 &&
36
65
  request.params.Entries[0].Detail) {
66
+ const entries = request.params.Entries
37
67
  const injected = {}
38
68
  this.tracer.inject(span, 'text_map', injected)
39
69
 
@@ -42,19 +72,26 @@ class EventBridge extends BaseAwsSdkPlugin {
42
72
  // it so the rest of the body stays in V8's optimisable surface.
43
73
  let finalData
44
74
  try {
45
- finalData = BaseAwsSdkPlugin.injectFieldIntoJsonObject(
46
- request.params.Entries[0].Detail, '_datadog', injected
47
- )
75
+ finalData = BaseAwsSdkPlugin.injectFieldIntoJsonObject(entries[0].Detail, '_datadog', injected)
48
76
  } catch (error) {
49
77
  log.error('EventBridge error injecting request', error)
50
78
  return
51
79
  }
52
80
 
53
- if (Buffer.byteLength(finalData) >= 1024 * 256) {
81
+ // EventBridge applies the 1 MiB cap to the whole request, so size every
82
+ // entry as it would be sent (the first with `_datadog` injected) and skip
83
+ // rather than tip a request AWS would otherwise accept over the limit. The
84
+ // running total only needs to clear the cap, so stop summing the moment it
85
+ // does instead of byte-counting the rest of a batch we already know is over.
86
+ let requestSize = putEventEntrySize(entries[0], finalData)
87
+ for (let i = 1; requestSize < MAX_PUT_EVENTS_BYTES && i < entries.length; i++) {
88
+ requestSize += putEventEntrySize(entries[i])
89
+ }
90
+ if (requestSize >= MAX_PUT_EVENTS_BYTES) {
54
91
  log.info('Payload size too large to pass context')
55
92
  return
56
93
  }
57
- request.params.Entries[0].Detail = finalData
94
+ entries[0].Detail = finalData
58
95
  }
59
96
  }
60
97
  }
@@ -72,44 +72,53 @@ class Sqs extends BaseAwsSdkPlugin {
72
72
  })
73
73
  }
74
74
 
75
+ /**
76
+ * Start the consumer (`aws.response`) span for a receive. The first message carrying trace
77
+ * context becomes the parent; every additional one fans in as a span link.
78
+ *
79
+ * @param {{ request: object, response: object, needsFinish?: boolean, currentStore?: object }} ctx
80
+ * @returns {object | undefined} The store to activate for the consumer span, else the parent store.
81
+ */
75
82
  #startResponseSpan (ctx) {
76
83
  const { request, response } = ctx
77
- const contextExtraction = this.responseExtract(request.params, request.operation, response)
84
+ const carriers = this.responseExtract(request.params, request.operation, response)
78
85
 
79
86
  let store = this._parentMap.get(request)
80
87
  let span
81
- let parsedMessageAttributes
82
- let parsedFirstBody
83
- let firstBodyChecked = false
84
- if (contextExtraction !== undefined) {
85
- parsedFirstBody = contextExtraction.parsedBody
86
- firstBodyChecked = contextExtraction.bodyChecked === true
87
- if (contextExtraction.datadogContext !== undefined) {
88
- parsedMessageAttributes = contextExtraction.parsedAttributes
89
- // request:start records requestTags only after the isEnabled gate, so an absent entry
90
- // means this consumer is disabled — gate on it instead of paying isEnabled again here.
91
- const requestTags = this.requestTags.get(request)
92
- if (requestTags !== undefined) {
93
- ctx.needsFinish = true
94
- const options = {
95
- childOf: contextExtraction.datadogContext,
96
- meta: {
97
- ...requestTags,
98
- 'span.kind': 'server',
99
- },
100
- integrationName: 'aws-sdk',
88
+
89
+ if (carriers !== undefined) {
90
+ // request:start records requestTags only after the isEnabled gate, so an absent entry
91
+ // means this consumer is disabled — gate on it instead of paying isEnabled again here.
92
+ const requestTags = this.requestTags.get(request)
93
+ if (requestTags !== undefined) {
94
+ // A receive can return messages from many producers; fanning the extra ones in as span
95
+ // links is the shape dd-trace-java and dd-trace-py use for batch SQS receives.
96
+ for (const carrier of carriers) {
97
+ if (carrier === undefined) continue
98
+ const datadogContext = this.tracer.extract('text_map', carrier)
99
+ // A DSM-only carrier (a non-first sendMessageBatch entry when batchPropagationEnabled
100
+ // is off) extracts to null; span.addLink dereferences the context and would throw on it.
101
+ if (datadogContext === null) continue
102
+ if (span === undefined) {
103
+ ctx.needsFinish = true
104
+ span = this.startSpan('aws.response', {
105
+ childOf: datadogContext,
106
+ meta: {
107
+ ...requestTags,
108
+ 'span.kind': 'server',
109
+ },
110
+ integrationName: 'aws-sdk',
111
+ }, ctx)
112
+ store = ctx.currentStore
113
+ } else {
114
+ span.addLink({ context: datadogContext })
101
115
  }
102
- span = this.startSpan('aws.response', options, ctx)
103
- store = ctx.currentStore
104
116
  }
105
117
  }
106
118
  }
107
119
 
108
120
  // Extract DSM context after, as we might not have a parent-child but may have a DSM context.
109
- this.responseExtractDSMContext(
110
- request.operation, request.params, response, span ?? null,
111
- { parsedAttributes: parsedMessageAttributes, parsedFirstBody, firstBodyChecked }
112
- )
121
+ this.responseExtractDSMContext(request.operation, request.params, response, span ?? null, carriers)
113
122
 
114
123
  return store
115
124
  }
@@ -183,19 +192,39 @@ class Sqs extends BaseAwsSdkPlugin {
183
192
  return tags
184
193
  }
185
194
 
195
+ /**
196
+ * Parse the trace-context carrier of every received message, in message order.
197
+ * Entries are `undefined` for messages that carry no `_datadog` context.
198
+ *
199
+ * @param {{ MaxNumberOfMessages?: number }} params
200
+ * @param {string} operation
201
+ * @param {{ Messages?: object[] }} response
202
+ * @returns {Array<Record<string, string> | undefined> | undefined}
203
+ */
186
204
  responseExtract (params, operation, response) {
187
205
  if (operation !== 'receiveMessage') return
188
- if (params.MaxNumberOfMessages && params.MaxNumberOfMessages !== 1) return
189
- if (!response || !response.Messages || !response.Messages[0]) return
206
+ if (!response?.Messages?.length) return
190
207
 
191
- let message = response.Messages[0]
192
- let parsedBody
208
+ return response.Messages.map(message => this.parseMessageCarrier(message))
209
+ }
193
210
 
211
+ /**
212
+ * Resolve the trace-context carrier for a single received message. The
213
+ * `MessageAttributes._datadog` text map (direct SQS or SNS to SQS) wins;
214
+ * otherwise the EventBridge envelope, optionally wrapped in an SNS
215
+ * `Notification` (see getEventBridgeContext). Checking MessageAttributes first
216
+ * avoids parsing a large SNS `Message` just to rule out an EventBridge envelope.
217
+ *
218
+ * @param {object} message A single `response.Messages` entry.
219
+ * @returns {Record<string, string> | undefined}
220
+ */
221
+ parseMessageCarrier (message) {
222
+ let parsedBody
194
223
  if (message.Body) {
195
224
  try {
196
225
  parsedBody = JSON.parse(message.Body)
197
226
  } catch {
198
- // SQS to SQS
227
+ // Opaque, non-JSON body (SQS to SQS).
199
228
  }
200
229
  // SNS to SQS
201
230
  if (parsedBody?.Type === 'Notification') {
@@ -203,34 +232,9 @@ class Sqs extends BaseAwsSdkPlugin {
203
232
  }
204
233
  }
205
234
 
206
- // Check MessageAttributes first (common direct-SQS/SNS path): avoids parsing
207
- // the body (e.g. a large SNS `Message`) just to rule out an EventBridge
208
- // envelope. Precedence matches responseExtractDSMContext.
209
235
  const datadogAttribute = message.MessageAttributes?._datadog
210
- if (datadogAttribute) {
211
- const parsedAttributes = this.parseDatadogAttributes(datadogAttribute)
212
- if (parsedAttributes) {
213
- return {
214
- datadogContext: this.tracer.extract('text_map', parsedAttributes),
215
- parsedAttributes,
216
- parsedBody,
217
- bodyChecked: true,
218
- }
219
- }
220
- }
221
-
222
- // Then the EventBridge envelope (optionally via SNS); see getEventBridgeContext.
223
- const eventBridgeContext = getEventBridgeContext(parsedBody)
224
- if (eventBridgeContext) {
225
- return {
226
- datadogContext: this.tracer.extract('text_map', eventBridgeContext),
227
- parsedAttributes: eventBridgeContext,
228
- parsedBody,
229
- bodyChecked: true,
230
- }
231
- }
232
-
233
- return { parsedBody, bodyChecked: true }
236
+ const carrier = datadogAttribute ? this.parseDatadogAttributes(datadogAttribute) : undefined
237
+ return carrier ?? getEventBridgeContext(parsedBody)
234
238
  }
235
239
 
236
240
  parseDatadogAttributes (attributes) {
@@ -247,51 +251,39 @@ class Sqs extends BaseAwsSdkPlugin {
247
251
  }
248
252
  }
249
253
 
250
- responseExtractDSMContext (operation, params, response, span, kwargs = {}) {
251
- let { parsedAttributes } = kwargs
252
- const { parsedFirstBody, firstBodyChecked } = kwargs
254
+ /**
255
+ * @param {string} operation
256
+ * @param {{ QueueUrl: string }} params
257
+ * @param {{ Messages?: object[] }} response
258
+ * @param {import('../../../dd-trace/src/opentracing/span') | null} span
259
+ * @param {Array<Record<string, string> | undefined>} [carriers] Per-message carriers already
260
+ * parsed by `responseExtract`; reused so each message body is parsed once. When omitted, the
261
+ * carriers are parsed here.
262
+ */
263
+ responseExtractDSMContext (operation, params, response, span, carriers) {
253
264
  if (!this.config.dsmEnabled) return
254
265
  if (operation !== 'receiveMessage') return
255
- if (!response || !response.Messages || !response.Messages[0]) return
266
+ if (!response?.Messages?.length) return
256
267
 
268
+ const messages = response.Messages
257
269
  // Only attribute payloadSize to the span when there is a single message.
258
- span = response.Messages.length > 1 ? null : span
270
+ span = messages.length > 1 ? null : span
259
271
 
260
272
  // QueueUrl is the same for the whole receive batch.
261
273
  const queue = params.QueueUrl.slice(params.QueueUrl.lastIndexOf('/') + 1)
262
274
 
263
- for (let i = 0; i < response.Messages.length; i++) {
264
- let message = response.Messages[i]
265
- if (!parsedAttributes) {
266
- let body
267
- // responseExtract already parsed message[0]; reuse that result instead of re-parsing.
268
- if (i === 0 && firstBodyChecked) {
269
- body = parsedFirstBody
270
- } else if (message.Body) {
271
- try {
272
- body = JSON.parse(message.Body)
273
- } catch {
274
- // SQS to SQS
275
- }
276
- }
277
- // SNS to SQS
278
- if (body?.Type === 'Notification') {
279
- message = body
280
- }
281
- // MessageAttributes for direct SQS/SNS; else the EventBridge envelope.
282
- parsedAttributes = message.MessageAttributes?._datadog
283
- ? this.parseDatadogAttributes(message.MessageAttributes._datadog)
284
- : getEventBridgeContext(body)
275
+ for (let i = 0; i < messages.length; i++) {
276
+ const message = messages[i]
277
+ const carrier = carriers === undefined ? this.parseMessageCarrier(message) : carriers[i]
278
+ if (carrier) {
279
+ // Inert for EventBridge until its producer emits a pathway (separate
280
+ // change) no `dd-pathway-ctx-base64` to decode yet; SQS/SNS decode now.
281
+ this.tracer.decodeDataStreamsContext(carrier)
285
282
  }
286
283
  const payloadSize = getHeadersSize({
287
284
  Body: message.Body,
288
285
  MessageAttributes: message.MessageAttributes,
289
286
  })
290
- if (parsedAttributes) {
291
- // Inert for EventBridge until its producer emits a pathway (separate
292
- // change) — no `dd-pathway-ctx-base64` to decode yet; SQS/SNS decode now.
293
- this.tracer.decodeDataStreamsContext(parsedAttributes)
294
- }
295
287
  this.tracer
296
288
  .setCheckpoint(['direction:in', `topic:${queue}`, 'type:sqs'], span, payloadSize)
297
289
  }
@@ -130,7 +130,7 @@ class CucumberPlugin extends CiPlugin {
130
130
  finishAllTraceSpans(this.testSessionSpan)
131
131
  this.telemetry.count(TELEMETRY_TEST_SESSION, {
132
132
  provider: this.ciProviderName,
133
- autoInjected: !!this._tracerConfig.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER,
133
+ autoInjected: !!this._tracerConfig.testOptimization.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER,
134
134
  })
135
135
 
136
136
  this.libraryConfig = null
@@ -11,6 +11,7 @@ const {
11
11
  TEST_IS_RUM_ACTIVE,
12
12
  TEST_CODE_OWNERS,
13
13
  getTestEnvironmentMetadata,
14
+ getTestLevelsMetadataTags,
14
15
  CI_APP_ORIGIN,
15
16
  getTestParentSpan,
16
17
  getCodeOwnersFileEntries,
@@ -23,6 +24,7 @@ const {
23
24
  TEST_MODULE_ID,
24
25
  TEST_SESSION_ID,
25
26
  TEST_COMMAND,
27
+ TEST_LEVELS_METADATA,
26
28
  TEST_MODULE,
27
29
  TEST_SOURCE_START,
28
30
  finishAllTraceSpans,
@@ -388,6 +390,7 @@ class CypressPlugin {
388
390
  earlyFlakeDetectionFaultyThreshold = 0
389
391
  testsToSkip = []
390
392
  skippedTests = []
393
+ skippedTestIds = new Set()
391
394
  skippableTestsCoverage = {}
392
395
  testSessionCoverageMap = createCoverageMap()
393
396
  hasForcedToRunSuites = false
@@ -471,6 +474,7 @@ class CypressPlugin {
471
474
  this.earlyFlakeDetectionFaultyThreshold = 0
472
475
  this.testsToSkip = []
473
476
  this.skippedTests = []
477
+ this.skippedTestIds = new Set()
474
478
  this.skippableTestsCoverage = {}
475
479
  this.testSessionCoverageMap = createCoverageMap()
476
480
  this.hasForcedToRunSuites = false
@@ -635,7 +639,7 @@ class CypressPlugin {
635
639
 
636
640
  this.isTestIsolationEnabled = getIsTestIsolationEnabled(cypressConfig)
637
641
 
638
- this.rumFlushWaitMillis = getConfig().DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS
642
+ this.rumFlushWaitMillis = getConfig().testOptimization.DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS
639
643
 
640
644
  if (!this.isTestIsolationEnabled) {
641
645
  log.warn('Test isolation is disabled, retries will not be enabled')
@@ -1043,7 +1047,13 @@ class CypressPlugin {
1043
1047
  )
1044
1048
 
1045
1049
  if (this.tracer._tracer._exporter?.addMetadataTags) {
1046
- const metadataTags = { '*': { [TEST_COMMAND]: this.command, [TEST_SESSION_NAME]: testSessionName } }
1050
+ const metadataTags = {
1051
+ [TEST_LEVELS_METADATA]: {
1052
+ [TEST_COMMAND]: this.command,
1053
+ [TEST_SESSION_NAME]: testSessionName,
1054
+ ...getTestLevelsMetadataTags(this.testEnvironmentMetadata),
1055
+ },
1056
+ }
1047
1057
  const libraryCapabilitiesTags = getLibraryCapabilitiesTags(this.constructor.id, this.frameworkVersion)
1048
1058
  metadataTags.test = {
1049
1059
  ...libraryCapabilitiesTags,
@@ -1140,7 +1150,7 @@ class CypressPlugin {
1140
1150
  this.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
1141
1151
  incrementCountMetric(TELEMETRY_TEST_SESSION, {
1142
1152
  provider: this.ciProviderName,
1143
- autoInjected: !!getConfig().DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER,
1153
+ autoInjected: !!getConfig().testOptimization.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER,
1144
1154
  })
1145
1155
 
1146
1156
  finishAllTraceSpans(this.testSessionSpan)
@@ -1378,7 +1388,7 @@ class CypressPlugin {
1378
1388
  return suitePayload
1379
1389
  },
1380
1390
  'dd:beforeEach': (test) => {
1381
- const { testName, testSuite, isEfdRetry, efdRetryIndex } = test
1391
+ const { testId, testName, testSuite, isEfdRetry, efdRetryIndex } = test
1382
1392
  if (isEfdRetry && this.shouldSkipEfdRetry(testSuite, testName, efdRetryIndex)) {
1383
1393
  return { shouldSkip: true, shouldDiscard: true }
1384
1394
  }
@@ -1390,7 +1400,11 @@ class CypressPlugin {
1390
1400
  const { isAttemptToFix, isDisabled, isQuarantined } = this.getTestProperties(testSuite, testName)
1391
1401
  // skip test
1392
1402
  if (shouldSkip && !isUnskippable) {
1393
- this.skippedTests.push(test)
1403
+ const skippedTestId = `${testSuite}:${testId || testName}`
1404
+ if (!this.skippedTestIds.has(skippedTestId)) {
1405
+ this.skippedTestIds.add(skippedTestId)
1406
+ this.skippedTests.push(test)
1407
+ }
1394
1408
  this.isTestsSkipped = true
1395
1409
  return { shouldSkip: true }
1396
1410
  }