dd-trace 5.32.0 → 5.33.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 (38) hide show
  1. package/README.md +12 -11
  2. package/index.d.ts +11 -1
  3. package/package.json +2 -2
  4. package/packages/datadog-instrumentations/src/aws-sdk.js +3 -1
  5. package/packages/datadog-instrumentations/src/cucumber.js +17 -9
  6. package/packages/datadog-instrumentations/src/jest.js +36 -21
  7. package/packages/datadog-instrumentations/src/mocha/main.js +9 -4
  8. package/packages/datadog-instrumentations/src/mocha/utils.js +4 -2
  9. package/packages/datadog-instrumentations/src/mocha/worker.js +4 -2
  10. package/packages/datadog-instrumentations/src/playwright.js +8 -3
  11. package/packages/datadog-instrumentations/src/vitest.js +35 -11
  12. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/index.js +16 -0
  13. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/tracing.js +63 -0
  14. package/packages/datadog-plugin-aws-sdk/src/services/{bedrockruntime.js → bedrockruntime/utils.js} +67 -75
  15. package/packages/datadog-plugin-cucumber/src/index.js +3 -1
  16. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -8
  17. package/packages/datadog-plugin-cypress/src/support.js +6 -2
  18. package/packages/datadog-plugin-fetch/src/index.js +3 -3
  19. package/packages/datadog-plugin-http/src/client.js +5 -33
  20. package/packages/datadog-plugin-jest/src/index.js +4 -1
  21. package/packages/datadog-plugin-mocha/src/index.js +3 -1
  22. package/packages/datadog-plugin-playwright/src/index.js +3 -1
  23. package/packages/datadog-plugin-vitest/src/index.js +16 -4
  24. package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +3 -3
  25. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +41 -24
  26. package/packages/dd-trace/src/appsec/iast/iast-context.js +12 -0
  27. package/packages/dd-trace/src/appsec/iast/path-line.js +19 -23
  28. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +1 -0
  29. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +75 -24
  30. package/packages/dd-trace/src/appsec/rasp/utils.js +10 -5
  31. package/packages/dd-trace/src/appsec/stack_trace.js +38 -28
  32. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +5 -4
  33. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +5 -3
  34. package/packages/dd-trace/src/config.js +4 -0
  35. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +59 -0
  36. package/packages/dd-trace/src/plugins/ci_plugin.js +1 -0
  37. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +0 -2
  38. package/packages/dd-trace/src/plugins/util/test.js +2 -0
@@ -25,6 +25,7 @@ const isEarlyFlakeDetectionFaultyCh = channel('ci:vitest:is-early-flake-detectio
25
25
  const taskToAsync = new WeakMap()
26
26
  const taskToStatuses = new WeakMap()
27
27
  const newTasks = new WeakSet()
28
+ let isRetryReasonEfd = false
28
29
  const switchedStatuses = new WeakSet()
29
30
  const sessionAsyncResource = new AsyncResource('bound-anonymous-fn')
30
31
 
@@ -44,14 +45,16 @@ function getProvidedContext () {
44
45
  _ddIsEarlyFlakeDetectionEnabled,
45
46
  _ddIsDiEnabled,
46
47
  _ddKnownTests: knownTests,
47
- _ddEarlyFlakeDetectionNumRetries: numRepeats
48
+ _ddEarlyFlakeDetectionNumRetries: numRepeats,
49
+ _ddIsKnownTestsEnabled: isKnownTestsEnabled
48
50
  } = globalThis.__vitest_worker__.providedContext
49
51
 
50
52
  return {
51
53
  isDiEnabled: _ddIsDiEnabled,
52
54
  isEarlyFlakeDetectionEnabled: _ddIsEarlyFlakeDetectionEnabled,
53
55
  knownTests,
54
- numRepeats
56
+ numRepeats,
57
+ isKnownTestsEnabled
55
58
  }
56
59
  } catch (e) {
57
60
  log.error('Vitest workers could not parse provided context, so some features will not work.')
@@ -59,7 +62,8 @@ function getProvidedContext () {
59
62
  isDiEnabled: false,
60
63
  isEarlyFlakeDetectionEnabled: false,
61
64
  knownTests: {},
62
- numRepeats: 0
65
+ numRepeats: 0,
66
+ isKnownTestsEnabled: false
63
67
  }
64
68
  }
65
69
  }
@@ -153,6 +157,7 @@ function getSortWrapper (sort) {
153
157
  let isEarlyFlakeDetectionEnabled = false
154
158
  let earlyFlakeDetectionNumRetries = 0
155
159
  let isEarlyFlakeDetectionFaulty = false
160
+ let isKnownTestsEnabled = false
156
161
  let isDiEnabled = false
157
162
  let knownTests = {}
158
163
 
@@ -164,18 +169,20 @@ function getSortWrapper (sort) {
164
169
  isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
165
170
  earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
166
171
  isDiEnabled = libraryConfig.isDiEnabled
172
+ isKnownTestsEnabled = libraryConfig.isKnownTestsEnabled
167
173
  }
168
174
  } catch (e) {
169
175
  isFlakyTestRetriesEnabled = false
170
176
  isEarlyFlakeDetectionEnabled = false
171
177
  isDiEnabled = false
178
+ isKnownTestsEnabled = false
172
179
  }
173
180
 
174
181
  if (isFlakyTestRetriesEnabled && !this.ctx.config.retry && flakyTestRetriesCount > 0) {
175
182
  this.ctx.config.retry = flakyTestRetriesCount
176
183
  }
177
184
 
178
- if (isEarlyFlakeDetectionEnabled) {
185
+ if (isKnownTestsEnabled) {
179
186
  const knownTestsResponse = await getChannelPromise(knownTestsCh)
180
187
  if (!knownTestsResponse.err) {
181
188
  knownTests = knownTestsResponse.knownTests
@@ -192,13 +199,15 @@ function getSortWrapper (sort) {
192
199
  })
193
200
  if (isEarlyFlakeDetectionFaulty) {
194
201
  isEarlyFlakeDetectionEnabled = false
195
- log.warn('Early flake detection is disabled because the number of new tests is too high.')
202
+ isKnownTestsEnabled = false
203
+ log.warn('New test detection is disabled because the number of new tests is too high.')
196
204
  } else {
197
205
  // TODO: use this to pass session and module IDs to the worker, instead of polluting process.env
198
206
  // Note: setting this.ctx.config.provide directly does not work because it's cached
199
207
  try {
200
208
  const workspaceProject = this.ctx.getCoreWorkspaceProject()
201
- workspaceProject._provided._ddKnownTests = knownTests.vitest
209
+ workspaceProject._provided._ddIsKnownTestsEnabled = isKnownTestsEnabled
210
+ workspaceProject._provided._ddKnownTests = knownTests.vitest || {}
202
211
  workspaceProject._provided._ddIsEarlyFlakeDetectionEnabled = isEarlyFlakeDetectionEnabled
203
212
  workspaceProject._provided._ddEarlyFlakeDetectionNumRetries = earlyFlakeDetectionNumRetries
204
213
  } catch (e) {
@@ -207,6 +216,7 @@ function getSortWrapper (sort) {
207
216
  }
208
217
  } else {
209
218
  isEarlyFlakeDetectionEnabled = false
219
+ isKnownTestsEnabled = false
210
220
  }
211
221
  }
212
222
 
@@ -295,17 +305,21 @@ addHook({
295
305
  const {
296
306
  knownTests,
297
307
  isEarlyFlakeDetectionEnabled,
308
+ isKnownTestsEnabled,
298
309
  numRepeats
299
310
  } = getProvidedContext()
300
311
 
301
- if (isEarlyFlakeDetectionEnabled) {
312
+ if (isKnownTestsEnabled) {
302
313
  isNewTestCh.publish({
303
314
  knownTests,
304
315
  testSuiteAbsolutePath: task.file.filepath,
305
316
  testName,
306
317
  onDone: (isNew) => {
307
318
  if (isNew) {
308
- task.repeats = numRepeats
319
+ if (isEarlyFlakeDetectionEnabled) {
320
+ isRetryReasonEfd = task.repeats !== numRepeats
321
+ task.repeats = numRepeats
322
+ }
309
323
  newTasks.add(task)
310
324
  taskToStatuses.set(task, [])
311
325
  }
@@ -344,11 +358,12 @@ addHook({
344
358
  let isNew = false
345
359
 
346
360
  const {
361
+ isKnownTestsEnabled,
347
362
  isEarlyFlakeDetectionEnabled,
348
363
  isDiEnabled
349
364
  } = getProvidedContext()
350
365
 
351
- if (isEarlyFlakeDetectionEnabled) {
366
+ if (isKnownTestsEnabled) {
352
367
  isNew = newTasks.has(task)
353
368
  }
354
369
 
@@ -431,6 +446,7 @@ addHook({
431
446
  testName,
432
447
  testSuiteAbsolutePath: task.file.filepath,
433
448
  isRetry: numAttempt > 0 || numRepetition > 0,
449
+ isRetryReasonEfd,
434
450
  isNew,
435
451
  mightHitProbe: isDiEnabled && numAttempt > 0
436
452
  })
@@ -576,7 +592,11 @@ addHook({
576
592
  if (result) {
577
593
  const { state, duration, errors } = result
578
594
  if (state === 'skip') { // programmatic skip
579
- testSkipCh.publish({ testName: getTestName(task), testSuiteAbsolutePath: task.file.filepath })
595
+ testSkipCh.publish({
596
+ testName: getTestName(task),
597
+ testSuiteAbsolutePath: task.file.filepath,
598
+ isNew: newTasks.has(task)
599
+ })
580
600
  } else if (state === 'pass' && !isSwitchedStatus) {
581
601
  if (testAsyncResource) {
582
602
  testAsyncResource.runInAsyncScope(() => {
@@ -602,7 +622,11 @@ addHook({
602
622
  }
603
623
  }
604
624
  } else { // test.skip or test.todo
605
- testSkipCh.publish({ testName: getTestName(task), testSuiteAbsolutePath: task.file.filepath })
625
+ testSkipCh.publish({
626
+ testName: getTestName(task),
627
+ testSuiteAbsolutePath: task.file.filepath,
628
+ isNew: newTasks.has(task)
629
+ })
606
630
  }
607
631
  })
608
632
 
@@ -0,0 +1,16 @@
1
+ const CompositePlugin = require('../../../../dd-trace/src/plugins/composite')
2
+ const BedrockRuntimeTracing = require('./tracing')
3
+ const BedrockRuntimeLLMObsPlugin = require('../../../../dd-trace/src/llmobs/plugins/bedrockruntime')
4
+ class BedrockRuntimePlugin extends CompositePlugin {
5
+ static get id () {
6
+ return 'bedrockruntime'
7
+ }
8
+
9
+ static get plugins () {
10
+ return {
11
+ llmobs: BedrockRuntimeLLMObsPlugin,
12
+ tracing: BedrockRuntimeTracing
13
+ }
14
+ }
15
+ }
16
+ module.exports = BedrockRuntimePlugin
@@ -0,0 +1,63 @@
1
+ 'use strict'
2
+
3
+ const BaseAwsSdkPlugin = require('../../base')
4
+ const { parseModelId, extractRequestParams, extractTextAndResponseReason } = require('./utils')
5
+
6
+ const enabledOperations = ['invokeModel']
7
+
8
+ class BedrockRuntime extends BaseAwsSdkPlugin {
9
+ static get id () { return 'bedrockruntime' }
10
+
11
+ isEnabled (request) {
12
+ const operation = request.operation
13
+ if (!enabledOperations.includes(operation)) {
14
+ return false
15
+ }
16
+
17
+ return super.isEnabled(request)
18
+ }
19
+
20
+ generateTags (params, operation, response) {
21
+ const { modelProvider, modelName } = parseModelId(params.modelId)
22
+
23
+ const requestParams = extractRequestParams(params, modelProvider)
24
+ const textAndResponseReason = extractTextAndResponseReason(response, modelProvider, modelName)
25
+
26
+ const tags = buildTagsFromParams(requestParams, textAndResponseReason, modelProvider, modelName, operation)
27
+
28
+ return tags
29
+ }
30
+ }
31
+
32
+ function buildTagsFromParams (requestParams, textAndResponseReason, modelProvider, modelName, operation) {
33
+ const tags = {}
34
+
35
+ // add request tags
36
+ tags['resource.name'] = operation
37
+ tags['aws.bedrock.request.model'] = modelName
38
+ tags['aws.bedrock.request.model_provider'] = modelProvider.toLowerCase()
39
+ tags['aws.bedrock.request.prompt'] = requestParams.prompt
40
+ tags['aws.bedrock.request.temperature'] = requestParams.temperature
41
+ tags['aws.bedrock.request.top_p'] = requestParams.topP
42
+ tags['aws.bedrock.request.top_k'] = requestParams.topK
43
+ tags['aws.bedrock.request.max_tokens'] = requestParams.maxTokens
44
+ tags['aws.bedrock.request.stop_sequences'] = requestParams.stopSequences
45
+ tags['aws.bedrock.request.input_type'] = requestParams.inputType
46
+ tags['aws.bedrock.request.truncate'] = requestParams.truncate
47
+ tags['aws.bedrock.request.stream'] = requestParams.stream
48
+ tags['aws.bedrock.request.n'] = requestParams.n
49
+
50
+ // add response tags
51
+ if (modelName.includes('embed')) {
52
+ tags['aws.bedrock.response.embedding_length'] = textAndResponseReason.message.length
53
+ }
54
+ if (textAndResponseReason.choiceId) {
55
+ tags['aws.bedrock.response.choices.id'] = textAndResponseReason.choiceId
56
+ }
57
+ tags['aws.bedrock.response.choices.text'] = textAndResponseReason.message
58
+ tags['aws.bedrock.response.choices.finish_reason'] = textAndResponseReason.finishReason
59
+
60
+ return tags
61
+ }
62
+
63
+ module.exports = BedrockRuntime
@@ -1,7 +1,17 @@
1
1
  'use strict'
2
2
 
3
- const BaseAwsSdkPlugin = require('../base')
4
- const log = require('../../../dd-trace/src/log')
3
+ const log = require('../../../../dd-trace/src/log')
4
+
5
+ const MODEL_TYPE_IDENTIFIERS = [
6
+ 'foundation-model/',
7
+ 'custom-model/',
8
+ 'provisioned-model/',
9
+ 'imported-module/',
10
+ 'prompt/',
11
+ 'endpoint/',
12
+ 'inference-profile/',
13
+ 'default-prompt-router/'
14
+ ]
5
15
 
6
16
  const PROVIDER = {
7
17
  AI21: 'AI21',
@@ -13,44 +23,6 @@ const PROVIDER = {
13
23
  MISTRAL: 'MISTRAL'
14
24
  }
15
25
 
16
- const enabledOperations = ['invokeModel']
17
-
18
- class BedrockRuntime extends BaseAwsSdkPlugin {
19
- static get id () { return 'bedrock runtime' }
20
-
21
- isEnabled (request) {
22
- const operation = request.operation
23
- if (!enabledOperations.includes(operation)) {
24
- return false
25
- }
26
-
27
- return super.isEnabled(request)
28
- }
29
-
30
- generateTags (params, operation, response) {
31
- let tags = {}
32
- let modelName = ''
33
- let modelProvider = ''
34
- const modelMeta = params.modelId.split('.')
35
- if (modelMeta.length === 2) {
36
- [modelProvider, modelName] = modelMeta
37
- modelProvider = modelProvider.toUpperCase()
38
- } else {
39
- [, modelProvider, modelName] = modelMeta
40
- modelProvider = modelProvider.toUpperCase()
41
- }
42
-
43
- const shouldSetChoiceIds = modelProvider === PROVIDER.COHERE && !modelName.includes('embed')
44
-
45
- const requestParams = extractRequestParams(params, modelProvider)
46
- const textAndResponseReason = extractTextAndResponseReason(response, modelProvider, modelName, shouldSetChoiceIds)
47
-
48
- tags = buildTagsFromParams(requestParams, textAndResponseReason, modelProvider, modelName, operation)
49
-
50
- return tags
51
- }
52
- }
53
-
54
26
  class Generation {
55
27
  constructor ({ message = '', finishReason = '', choiceId = '' } = {}) {
56
28
  // stringify message as it could be a single generated message as well as a list of embeddings
@@ -65,6 +37,7 @@ class RequestParams {
65
37
  prompt = '',
66
38
  temperature = undefined,
67
39
  topP = undefined,
40
+ topK = undefined,
68
41
  maxTokens = undefined,
69
42
  stopSequences = [],
70
43
  inputType = '',
@@ -72,11 +45,11 @@ class RequestParams {
72
45
  stream = '',
73
46
  n = undefined
74
47
  } = {}) {
75
- // TODO: set a truncation limit to prompt
76
48
  // stringify prompt as it could be a single prompt as well as a list of message objects
77
49
  this.prompt = typeof prompt === 'string' ? prompt : JSON.stringify(prompt) || ''
78
50
  this.temperature = temperature !== undefined ? temperature : undefined
79
51
  this.topP = topP !== undefined ? topP : undefined
52
+ this.topK = topK !== undefined ? topK : undefined
80
53
  this.maxTokens = maxTokens !== undefined ? maxTokens : undefined
81
54
  this.stopSequences = stopSequences || []
82
55
  this.inputType = inputType || ''
@@ -86,11 +59,53 @@ class RequestParams {
86
59
  }
87
60
  }
88
61
 
62
+ function parseModelId (modelId) {
63
+ // Best effort to extract the model provider and model name from the bedrock model ID.
64
+ // modelId can be a 1/2 period-separated string or a full AWS ARN, based on the following formats:
65
+ // 1. Base model: "{model_provider}.{model_name}"
66
+ // 2. Cross-region model: "{region}.{model_provider}.{model_name}"
67
+ // 3. Other: Prefixed by AWS ARN "arn:aws{+region?}:bedrock:{region}:{account-id}:"
68
+ // a. Foundation model: ARN prefix + "foundation-model/{region?}.{model_provider}.{model_name}"
69
+ // b. Custom model: ARN prefix + "custom-model/{model_provider}.{model_name}"
70
+ // c. Provisioned model: ARN prefix + "provisioned-model/{model-id}"
71
+ // d. Imported model: ARN prefix + "imported-module/{model-id}"
72
+ // e. Prompt management: ARN prefix + "prompt/{prompt-id}"
73
+ // f. Sagemaker: ARN prefix + "endpoint/{model-id}"
74
+ // g. Inference profile: ARN prefix + "{application-?}inference-profile/{model-id}"
75
+ // h. Default prompt router: ARN prefix + "default-prompt-router/{prompt-id}"
76
+ // If model provider cannot be inferred from the modelId formatting, then default to "custom"
77
+ modelId = modelId.toLowerCase()
78
+ if (!modelId.startsWith('arn:aws')) {
79
+ const modelMeta = modelId.split('.')
80
+ if (modelMeta.length < 2) {
81
+ return { modelProvider: 'custom', modelName: modelMeta[0] }
82
+ }
83
+ return { modelProvider: modelMeta[modelMeta.length - 2], modelName: modelMeta[modelMeta.length - 1] }
84
+ }
85
+
86
+ for (const identifier of MODEL_TYPE_IDENTIFIERS) {
87
+ if (!modelId.includes(identifier)) {
88
+ continue
89
+ }
90
+ modelId = modelId.split(identifier).pop()
91
+ if (['foundation-model/', 'custom-model/'].includes(identifier)) {
92
+ const modelMeta = modelId.split('.')
93
+ if (modelMeta.length < 2) {
94
+ return { modelProvider: 'custom', modelName: modelId }
95
+ }
96
+ return { modelProvider: modelMeta[modelMeta.length - 2], modelName: modelMeta[modelMeta.length - 1] }
97
+ }
98
+ return { modelProvider: 'custom', modelName: modelId }
99
+ }
100
+
101
+ return { modelProvider: 'custom', modelName: 'custom' }
102
+ }
103
+
89
104
  function extractRequestParams (params, provider) {
90
105
  const requestBody = JSON.parse(params.body)
91
106
  const modelId = params.modelId
92
107
 
93
- switch (provider) {
108
+ switch (provider.toUpperCase()) {
94
109
  case PROVIDER.AI21: {
95
110
  let userPrompt = requestBody.prompt
96
111
  if (modelId.includes('jamba')) {
@@ -176,11 +191,11 @@ function extractRequestParams (params, provider) {
176
191
  }
177
192
  }
178
193
 
179
- function extractTextAndResponseReason (response, provider, modelName, shouldSetChoiceIds) {
194
+ function extractTextAndResponseReason (response, provider, modelName) {
180
195
  const body = JSON.parse(Buffer.from(response.body).toString('utf8'))
181
-
196
+ const shouldSetChoiceIds = provider.toUpperCase() === PROVIDER.COHERE && !modelName.includes('embed')
182
197
  try {
183
- switch (provider) {
198
+ switch (provider.toUpperCase()) {
184
199
  case PROVIDER.AI21: {
185
200
  if (modelName.includes('jamba')) {
186
201
  const generations = body.choices || []
@@ -262,34 +277,11 @@ function extractTextAndResponseReason (response, provider, modelName, shouldSetC
262
277
  return new Generation()
263
278
  }
264
279
 
265
- function buildTagsFromParams (requestParams, textAndResponseReason, modelProvider, modelName, operation) {
266
- const tags = {}
267
-
268
- // add request tags
269
- tags['resource.name'] = operation
270
- tags['aws.bedrock.request.model'] = modelName
271
- tags['aws.bedrock.request.model_provider'] = modelProvider
272
- tags['aws.bedrock.request.prompt'] = requestParams.prompt
273
- tags['aws.bedrock.request.temperature'] = requestParams.temperature
274
- tags['aws.bedrock.request.top_p'] = requestParams.topP
275
- tags['aws.bedrock.request.max_tokens'] = requestParams.maxTokens
276
- tags['aws.bedrock.request.stop_sequences'] = requestParams.stopSequences
277
- tags['aws.bedrock.request.input_type'] = requestParams.inputType
278
- tags['aws.bedrock.request.truncate'] = requestParams.truncate
279
- tags['aws.bedrock.request.stream'] = requestParams.stream
280
- tags['aws.bedrock.request.n'] = requestParams.n
281
-
282
- // add response tags
283
- if (modelName.includes('embed')) {
284
- tags['aws.bedrock.response.embedding_length'] = textAndResponseReason.message.length
285
- }
286
- if (textAndResponseReason.choiceId) {
287
- tags['aws.bedrock.response.choices.id'] = textAndResponseReason.choiceId
288
- }
289
- tags['aws.bedrock.response.choices.text'] = textAndResponseReason.message
290
- tags['aws.bedrock.response.choices.finish_reason'] = textAndResponseReason.finishReason
291
-
292
- return tags
280
+ module.exports = {
281
+ Generation,
282
+ RequestParams,
283
+ parseModelId,
284
+ extractRequestParams,
285
+ extractTextAndResponseReason,
286
+ PROVIDER
293
287
  }
294
-
295
- module.exports = BedrockRuntime
@@ -26,7 +26,8 @@ const {
26
26
  TEST_MODULE,
27
27
  TEST_MODULE_ID,
28
28
  TEST_SUITE,
29
- CUCUMBER_IS_PARALLEL
29
+ CUCUMBER_IS_PARALLEL,
30
+ TEST_RETRY_REASON
30
31
  } = require('../../dd-trace/src/plugins/util/test')
31
32
  const { RESOURCE_NAME } = require('../../../ext/tags')
32
33
  const { COMPONENT, ERROR_MESSAGE } = require('../../dd-trace/src/constants')
@@ -321,6 +322,7 @@ class CucumberPlugin extends CiPlugin {
321
322
  span.setTag(TEST_IS_NEW, 'true')
322
323
  if (isEfdRetry) {
323
324
  span.setTag(TEST_IS_RETRY, 'true')
325
+ span.setTag(TEST_RETRY_REASON, 'efd')
324
326
  }
325
327
  }
326
328
 
@@ -31,7 +31,8 @@ const {
31
31
  TEST_EARLY_FLAKE_ENABLED,
32
32
  getTestSessionName,
33
33
  TEST_SESSION_NAME,
34
- TEST_LEVEL_EVENT_TYPES
34
+ TEST_LEVEL_EVENT_TYPES,
35
+ TEST_RETRY_REASON
35
36
  } = require('../../dd-trace/src/plugins/util/test')
36
37
  const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
37
38
  const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
@@ -112,7 +113,7 @@ function getCypressCommand (details) {
112
113
  function getLibraryConfiguration (tracer, testConfiguration) {
113
114
  return new Promise(resolve => {
114
115
  if (!tracer._tracer._exporter?.getLibraryConfiguration) {
115
- return resolve({ err: new Error('CI Visibility was not initialized correctly') })
116
+ return resolve({ err: new Error('Test Optimization was not initialized correctly') })
116
117
  }
117
118
 
118
119
  tracer._tracer._exporter.getLibraryConfiguration(testConfiguration, (err, libraryConfig) => {
@@ -124,7 +125,7 @@ function getLibraryConfiguration (tracer, testConfiguration) {
124
125
  function getSkippableTests (tracer, testConfiguration) {
125
126
  return new Promise(resolve => {
126
127
  if (!tracer._tracer._exporter?.getSkippableSuites) {
127
- return resolve({ err: new Error('CI Visibility was not initialized correctly') })
128
+ return resolve({ err: new Error('Test Optimization was not initialized correctly') })
128
129
  }
129
130
  tracer._tracer._exporter.getSkippableSuites(testConfiguration, (err, skippableTests, correlationId) => {
130
131
  resolve({
@@ -139,7 +140,7 @@ function getSkippableTests (tracer, testConfiguration) {
139
140
  function getKnownTests (tracer, testConfiguration) {
140
141
  return new Promise(resolve => {
141
142
  if (!tracer._tracer._exporter?.getKnownTests) {
142
- return resolve({ err: new Error('CI Visibility was not initialized correctly') })
143
+ return resolve({ err: new Error('Test Optimization was not initialized correctly') })
143
144
  }
144
145
  tracer._tracer._exporter.getKnownTests(testConfiguration, (err, knownTests) => {
145
146
  resolve({
@@ -203,6 +204,7 @@ class CypressPlugin {
203
204
  this.isSuitesSkippingEnabled = false
204
205
  this.isCodeCoverageEnabled = false
205
206
  this.isEarlyFlakeDetectionEnabled = false
207
+ this.isKnownTestsEnabled = false
206
208
  this.earlyFlakeDetectionNumRetries = 0
207
209
  this.testsToSkip = []
208
210
  this.skippedTests = []
@@ -232,13 +234,15 @@ class CypressPlugin {
232
234
  isEarlyFlakeDetectionEnabled,
233
235
  earlyFlakeDetectionNumRetries,
234
236
  isFlakyTestRetriesEnabled,
235
- flakyTestRetriesCount
237
+ flakyTestRetriesCount,
238
+ isKnownTestsEnabled
236
239
  }
237
240
  } = libraryConfigurationResponse
238
241
  this.isSuitesSkippingEnabled = isSuitesSkippingEnabled
239
242
  this.isCodeCoverageEnabled = isCodeCoverageEnabled
240
243
  this.isEarlyFlakeDetectionEnabled = isEarlyFlakeDetectionEnabled
241
244
  this.earlyFlakeDetectionNumRetries = earlyFlakeDetectionNumRetries
245
+ this.isKnownTestsEnabled = isKnownTestsEnabled
242
246
  if (isFlakyTestRetriesEnabled) {
243
247
  this.cypressConfig.retries.runMode = flakyTestRetriesCount
244
248
  }
@@ -354,7 +358,7 @@ class CypressPlugin {
354
358
  this.frameworkVersion = getCypressVersion(details)
355
359
  this.rootDir = getRootDir(details)
356
360
 
357
- if (this.isEarlyFlakeDetectionEnabled) {
361
+ if (this.isKnownTestsEnabled) {
358
362
  const knownTestsResponse = await getKnownTests(
359
363
  this.tracer,
360
364
  this.testConfiguration
@@ -362,6 +366,7 @@ class CypressPlugin {
362
366
  if (knownTestsResponse.err) {
363
367
  log.error('Cypress known tests response error', knownTestsResponse.err)
364
368
  this.isEarlyFlakeDetectionEnabled = false
369
+ this.isKnownTestsEnabled = false
365
370
  } else {
366
371
  // We use TEST_FRAMEWORK_NAME for the name of the module
367
372
  this.knownTestsByTestSuite = knownTestsResponse.knownTests[TEST_FRAMEWORK_NAME]
@@ -567,6 +572,9 @@ class CypressPlugin {
567
572
  cypressTestStatus = CYPRESS_STATUS_TO_TEST_STATUS[cypressTest.attempts[attemptIndex].state]
568
573
  if (attemptIndex > 0) {
569
574
  finishedTest.testSpan.setTag(TEST_IS_RETRY, 'true')
575
+ if (finishedTest.isEfdRetry) {
576
+ finishedTest.testSpan.setTag(TEST_RETRY_REASON, 'efd')
577
+ }
570
578
  }
571
579
  }
572
580
  if (cypressTest.displayError) {
@@ -618,7 +626,8 @@ class CypressPlugin {
618
626
  const suitePayload = {
619
627
  isEarlyFlakeDetectionEnabled: this.isEarlyFlakeDetectionEnabled,
620
628
  knownTestsForSuite: this.knownTestsByTestSuite?.[testSuite] || [],
621
- earlyFlakeDetectionNumRetries: this.earlyFlakeDetectionNumRetries
629
+ earlyFlakeDetectionNumRetries: this.earlyFlakeDetectionNumRetries,
630
+ isKnownTestsEnabled: this.isKnownTestsEnabled
622
631
  }
623
632
 
624
633
  if (this.testSuiteSpan) {
@@ -703,13 +712,15 @@ class CypressPlugin {
703
712
  this.activeTestSpan.setTag(TEST_IS_NEW, 'true')
704
713
  if (isEfdRetry) {
705
714
  this.activeTestSpan.setTag(TEST_IS_RETRY, 'true')
715
+ this.activeTestSpan.setTag(TEST_RETRY_REASON, 'efd')
706
716
  }
707
717
  }
708
718
  const finishedTest = {
709
719
  testName,
710
720
  testStatus,
711
721
  finishTime: this.activeTestSpan._getTime(), // we store the finish time here
712
- testSpan: this.activeTestSpan
722
+ testSpan: this.activeTestSpan,
723
+ isEfdRetry
713
724
  }
714
725
  if (this.finishedTestsByFile[testSuite]) {
715
726
  this.finishedTestsByFile[testSuite].push(finishedTest)
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable */
2
2
  let isEarlyFlakeDetectionEnabled = false
3
+ let isKnownTestsEnabled = false
3
4
  let knownTestsForSuite = []
4
5
  let suiteTests = []
5
6
  let earlyFlakeDetectionNumRetries = 0
@@ -33,7 +34,7 @@ function retryTest (test, suiteTests) {
33
34
 
34
35
  const oldRunTests = Cypress.mocha.getRunner().runTests
35
36
  Cypress.mocha.getRunner().runTests = function (suite, fn) {
36
- if (!isEarlyFlakeDetectionEnabled) {
37
+ if (!isKnownTestsEnabled) {
37
38
  return oldRunTests.apply(this, arguments)
38
39
  }
39
40
  // We copy the new tests at the beginning of the suite run (runTests), so that they're run
@@ -41,7 +42,9 @@ Cypress.mocha.getRunner().runTests = function (suite, fn) {
41
42
  suite.tests.forEach(test => {
42
43
  if (!test._ddIsNew && !test.isPending() && isNewTest(test)) {
43
44
  test._ddIsNew = true
44
- retryTest(test, suite.tests)
45
+ if (isEarlyFlakeDetectionEnabled) {
46
+ retryTest(test, suite.tests)
47
+ }
45
48
  }
46
49
  })
47
50
 
@@ -67,6 +70,7 @@ before(function () {
67
70
  }).then((suiteConfig) => {
68
71
  if (suiteConfig) {
69
72
  isEarlyFlakeDetectionEnabled = suiteConfig.isEarlyFlakeDetectionEnabled
73
+ isKnownTestsEnabled = suiteConfig.isKnownTestsEnabled
70
74
  knownTestsForSuite = suiteConfig.knownTestsForSuite
71
75
  earlyFlakeDetectionNumRetries = suiteConfig.earlyFlakeDetectionNumRetries
72
76
  }
@@ -9,7 +9,7 @@ class FetchPlugin extends HttpClientPlugin {
9
9
  bindStart (ctx) {
10
10
  const req = ctx.req
11
11
  const options = new URL(req.url)
12
- const headers = options.headers = Object.fromEntries(req.headers.entries())
12
+ options.headers = Object.fromEntries(req.headers.entries())
13
13
 
14
14
  options.method = req.method
15
15
 
@@ -17,9 +17,9 @@ class FetchPlugin extends HttpClientPlugin {
17
17
 
18
18
  const store = super.bindStart(ctx)
19
19
 
20
- for (const name in headers) {
20
+ for (const name in options.headers) {
21
21
  if (!req.headers.has(name)) {
22
- req.headers.set(name, headers[name])
22
+ req.headers.set(name, options.headers[name])
23
23
  }
24
24
  }
25
25