dd-trace 5.45.0 → 5.47.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.
- package/LICENSE-3rdparty.csv +1 -2
- package/ci/init.js +8 -0
- package/ext/exporters.d.ts +2 -1
- package/ext/exporters.js +2 -1
- package/package.json +8 -9
- package/packages/datadog-instrumentations/orchestrion.yml +52 -0
- package/packages/datadog-instrumentations/src/cucumber.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +41 -1
- package/packages/datadog-instrumentations/src/jest.js +11 -2
- package/packages/datadog-instrumentations/src/langchain.js +49 -53
- package/packages/datadog-instrumentations/src/mariadb.js +19 -0
- package/packages/datadog-instrumentations/src/mocha/main.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/utils.js +11 -3
- package/packages/datadog-instrumentations/src/orchestrion-config/index.js +5 -0
- package/packages/datadog-instrumentations/src/playwright.js +333 -46
- package/packages/datadog-instrumentations/src/router.js +1 -7
- package/packages/datadog-instrumentations/src/vitest.js +11 -3
- package/packages/datadog-plugin-cucumber/src/index.js +11 -4
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +17 -5
- package/packages/datadog-plugin-jest/src/index.js +11 -4
- package/packages/datadog-plugin-langchain/src/index.js +18 -12
- package/packages/datadog-plugin-langchain/src/tracing.js +66 -6
- package/packages/datadog-plugin-mocha/src/index.js +17 -5
- package/packages/datadog-plugin-mongodb-core/src/index.js +24 -0
- package/packages/datadog-plugin-playwright/src/index.js +124 -10
- package/packages/datadog-plugin-vitest/src/index.js +13 -8
- package/packages/datadog-shimmer/src/shimmer.js +3 -42
- package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +39 -15
- package/packages/dd-trace/src/appsec/iast/taint-tracking/filter.js +3 -3
- package/packages/dd-trace/src/appsec/iast/taint-tracking/index.js +0 -3
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +25 -12
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-telemetry.js +3 -32
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +99 -57
- package/packages/dd-trace/src/appsec/rasp/command_injection.js +1 -1
- package/packages/dd-trace/src/appsec/rasp/index.js +4 -2
- package/packages/dd-trace/src/appsec/rasp/lfi.js +1 -1
- package/packages/dd-trace/src/appsec/rasp/sql_injection.js +1 -1
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +1 -1
- package/packages/dd-trace/src/appsec/rasp/utils.js +12 -7
- package/packages/dd-trace/src/appsec/recommended.json +256 -84
- package/packages/dd-trace/src/appsec/reporter.js +6 -4
- package/packages/dd-trace/src/appsec/telemetry/index.js +27 -3
- package/packages/dd-trace/src/appsec/telemetry/rasp.js +70 -6
- package/packages/dd-trace/src/appsec/telemetry/waf.js +0 -30
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +8 -3
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +6 -4
- package/packages/dd-trace/src/config.js +9 -0
- package/packages/dd-trace/src/constants.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +102 -22
- package/packages/dd-trace/src/debugger/devtools_client/condition.js +263 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +69 -36
- package/packages/dd-trace/src/debugger/devtools_client/lock.js +8 -0
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +1 -7
- package/packages/dd-trace/src/debugger/devtools_client/send.js +2 -2
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +15 -10
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +3 -3
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +69 -62
- package/packages/dd-trace/src/debugger/devtools_client/state.js +3 -2
- package/packages/dd-trace/src/debugger/index.js +3 -0
- package/packages/dd-trace/src/encode/0.4.js +24 -17
- package/packages/dd-trace/src/exporter.js +1 -0
- package/packages/dd-trace/src/exporters/common/docker.js +37 -7
- package/packages/dd-trace/src/exporters/common/request.js +1 -4
- package/packages/dd-trace/src/format.js +58 -60
- package/packages/dd-trace/src/llmobs/plugins/base.js +2 -2
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +62 -3
- package/packages/dd-trace/src/llmobs/plugins/openai.js +1 -0
- package/packages/dd-trace/src/llmobs/plugins/vertexai.js +2 -1
- package/packages/dd-trace/src/llmobs/writers/spans/base.js +3 -3
- package/packages/dd-trace/src/log/index.js +2 -0
- package/packages/dd-trace/src/log/writer.js +19 -2
- package/packages/dd-trace/src/opentelemetry/span.js +4 -4
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +17 -3
- package/packages/dd-trace/src/opentracing/span.js +10 -0
- package/packages/dd-trace/src/plugin_manager.js +2 -0
- package/packages/dd-trace/src/plugins/util/test.js +11 -0
- package/packages/dd-trace/src/profiler.js +1 -1
- package/packages/dd-trace/src/profiling/config.js +6 -0
- package/packages/dd-trace/src/profiling/exporters/agent.js +1 -5
- package/packages/dd-trace/src/profiling/profiler.js +4 -3
- package/packages/dd-trace/src/profiling/profilers/wall.js +12 -8
- package/packages/dd-trace/src/proxy.js +5 -1
- package/packages/dd-trace/src/tagger.js +38 -26
- package/packages/dd-trace/src/util.js +1 -7
|
@@ -2,7 +2,11 @@ const satisfies = require('semifies')
|
|
|
2
2
|
|
|
3
3
|
const { addHook, channel, AsyncResource } = require('./helpers/instrument')
|
|
4
4
|
const shimmer = require('../../datadog-shimmer')
|
|
5
|
-
const {
|
|
5
|
+
const {
|
|
6
|
+
parseAnnotations,
|
|
7
|
+
getTestSuitePath,
|
|
8
|
+
PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE
|
|
9
|
+
} = require('../../dd-trace/src/plugins/util/test')
|
|
6
10
|
const log = require('../../dd-trace/src/log')
|
|
7
11
|
|
|
8
12
|
const testStartCh = channel('ci:playwright:test:start')
|
|
@@ -18,6 +22,9 @@ const testManagementTestsCh = channel('ci:playwright:test-management-tests')
|
|
|
18
22
|
const testSuiteStartCh = channel('ci:playwright:test-suite:start')
|
|
19
23
|
const testSuiteFinishCh = channel('ci:playwright:test-suite:finish')
|
|
20
24
|
|
|
25
|
+
const workerReportCh = channel('ci:playwright:worker:report')
|
|
26
|
+
const testPageGotoCh = channel('ci:playwright:test:page-goto')
|
|
27
|
+
|
|
21
28
|
const testToAr = new WeakMap()
|
|
22
29
|
const testSuiteToAr = new Map()
|
|
23
30
|
const testSuiteToTestStatuses = new Map()
|
|
@@ -255,21 +262,19 @@ function getTestFullname (test) {
|
|
|
255
262
|
return names.join(' ')
|
|
256
263
|
}
|
|
257
264
|
|
|
258
|
-
function testBeginHandler (test, browserName) {
|
|
265
|
+
function testBeginHandler (test, browserName, isMainProcess) {
|
|
259
266
|
const {
|
|
260
267
|
_requireFile: testSuiteAbsolutePath,
|
|
261
|
-
_type,
|
|
262
268
|
location: {
|
|
263
269
|
line: testSourceLine
|
|
264
|
-
}
|
|
270
|
+
},
|
|
271
|
+
_type
|
|
265
272
|
} = test
|
|
266
273
|
|
|
267
274
|
if (_type === 'beforeAll' || _type === 'afterAll') {
|
|
268
275
|
return
|
|
269
276
|
}
|
|
270
277
|
|
|
271
|
-
const testName = getTestFullname(test)
|
|
272
|
-
|
|
273
278
|
const isNewTestSuite = !startedSuites.includes(testSuiteAbsolutePath)
|
|
274
279
|
|
|
275
280
|
if (isNewTestSuite) {
|
|
@@ -286,24 +291,31 @@ function testBeginHandler (test, browserName) {
|
|
|
286
291
|
test.retries = 0
|
|
287
292
|
}
|
|
288
293
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
294
|
+
// this handles tests that do not go through the worker process (because they're skipped)
|
|
295
|
+
if (isMainProcess) {
|
|
296
|
+
const testAsyncResource = new AsyncResource('bound-anonymous-fn')
|
|
297
|
+
testToAr.set(test, testAsyncResource)
|
|
298
|
+
const testName = getTestFullname(test)
|
|
299
|
+
|
|
300
|
+
testAsyncResource.runInAsyncScope(() => {
|
|
301
|
+
testStartCh.publish({
|
|
302
|
+
testName,
|
|
303
|
+
testSuiteAbsolutePath,
|
|
304
|
+
testSourceLine,
|
|
305
|
+
browserName,
|
|
306
|
+
isDisabled: test._ddIsDisabled
|
|
307
|
+
})
|
|
298
308
|
})
|
|
299
|
-
}
|
|
309
|
+
}
|
|
300
310
|
}
|
|
301
|
-
|
|
311
|
+
|
|
312
|
+
function testEndHandler (test, annotations, testStatus, error, isTimeout, isMainProcess) {
|
|
313
|
+
const { _requireFile: testSuiteAbsolutePath, results, _type } = test
|
|
314
|
+
|
|
302
315
|
let annotationTags
|
|
303
316
|
if (annotations.length) {
|
|
304
317
|
annotationTags = parseAnnotations(annotations)
|
|
305
318
|
}
|
|
306
|
-
const { _requireFile: testSuiteAbsolutePath, results, _type } = test
|
|
307
319
|
|
|
308
320
|
if (_type === 'beforeAll' || _type === 'afterAll') {
|
|
309
321
|
const hookError = formatTestHookError(error, _type, isTimeout)
|
|
@@ -324,35 +336,40 @@ function testEndHandler (test, annotations, testStatus, error, isTimeout) {
|
|
|
324
336
|
testStatuses.push(testStatus)
|
|
325
337
|
}
|
|
326
338
|
|
|
327
|
-
let hasFailedAllRetries = false
|
|
328
|
-
let hasPassedAttemptToFixRetries = false
|
|
329
|
-
|
|
330
339
|
if (testStatuses.length === testManagementAttemptToFixRetries + 1) {
|
|
331
340
|
if (testStatuses.every(status => status === 'fail')) {
|
|
332
|
-
|
|
341
|
+
test._ddHasFailedAllRetries = true
|
|
333
342
|
} else if (testStatuses.every(status => status === 'pass')) {
|
|
334
|
-
|
|
343
|
+
test._ddHasPassedAttemptToFixRetries = true
|
|
335
344
|
}
|
|
336
345
|
}
|
|
337
346
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
347
|
+
// this handles tests that do not go through the worker process (because they're skipped)
|
|
348
|
+
if (isMainProcess) {
|
|
349
|
+
const testResult = results[results.length - 1]
|
|
350
|
+
const testAsyncResource = testToAr.get(test)
|
|
351
|
+
const isAtrRetry = testResult?.retry > 0 &&
|
|
352
|
+
isFlakyTestRetriesEnabled &&
|
|
353
|
+
!test._ddIsAttemptToFix &&
|
|
354
|
+
!test._ddIsEfdRetry
|
|
355
|
+
testAsyncResource.runInAsyncScope(() => {
|
|
356
|
+
testFinishCh.publish({
|
|
357
|
+
testStatus,
|
|
358
|
+
steps: testResult?.steps || [],
|
|
359
|
+
isRetry: testResult?.retry > 0,
|
|
360
|
+
error,
|
|
361
|
+
extraTags: annotationTags,
|
|
362
|
+
isNew: test._ddIsNew,
|
|
363
|
+
isAttemptToFix: test._ddIsAttemptToFix,
|
|
364
|
+
isAttemptToFixRetry: test._ddIsAttemptToFixRetry,
|
|
365
|
+
isQuarantined: test._ddIsQuarantined,
|
|
366
|
+
isEfdRetry: test._ddIsEfdRetry,
|
|
367
|
+
hasFailedAllRetries: test._ddHasFailedAllRetries,
|
|
368
|
+
hasPassedAttemptToFixRetries: test._ddHasPassedAttemptToFixRetries,
|
|
369
|
+
isAtrRetry
|
|
370
|
+
})
|
|
354
371
|
})
|
|
355
|
-
}
|
|
372
|
+
}
|
|
356
373
|
|
|
357
374
|
if (testSuiteToTestStatuses.has(testSuiteAbsolutePath)) {
|
|
358
375
|
testSuiteToTestStatuses.get(testSuiteAbsolutePath).push(testStatus)
|
|
@@ -416,7 +433,7 @@ function dispatcherHook (dispatcherExport) {
|
|
|
416
433
|
const { test } = dispatcher._testById.get(params.testId)
|
|
417
434
|
const projects = getProjectsFromDispatcher(dispatcher)
|
|
418
435
|
const browser = getBrowserNameFromProjects(projects, test)
|
|
419
|
-
testBeginHandler(test, browser)
|
|
436
|
+
testBeginHandler(test, browser, true)
|
|
420
437
|
} else if (method === 'testEnd') {
|
|
421
438
|
const { test } = dispatcher._testById.get(params.testId)
|
|
422
439
|
|
|
@@ -424,7 +441,14 @@ function dispatcherHook (dispatcherExport) {
|
|
|
424
441
|
const testResult = results[results.length - 1]
|
|
425
442
|
|
|
426
443
|
const isTimeout = testResult.status === 'timedOut'
|
|
427
|
-
testEndHandler(
|
|
444
|
+
testEndHandler(
|
|
445
|
+
test,
|
|
446
|
+
params.annotations,
|
|
447
|
+
STATUS_TO_TEST_STATUS[testResult.status],
|
|
448
|
+
testResult.error,
|
|
449
|
+
isTimeout,
|
|
450
|
+
true
|
|
451
|
+
)
|
|
428
452
|
}
|
|
429
453
|
})
|
|
430
454
|
|
|
@@ -443,13 +467,34 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
|
|
|
443
467
|
const test = getTestByTestId(dispatcher, testId)
|
|
444
468
|
const projects = getProjectsFromDispatcher(dispatcher)
|
|
445
469
|
const browser = getBrowserNameFromProjects(projects, test)
|
|
446
|
-
testBeginHandler(test, browser)
|
|
470
|
+
testBeginHandler(test, browser, false)
|
|
447
471
|
})
|
|
448
472
|
worker.on('testEnd', ({ testId, status, errors, annotations }) => {
|
|
449
473
|
const test = getTestByTestId(dispatcher, testId)
|
|
450
474
|
|
|
451
475
|
const isTimeout = status === 'timedOut'
|
|
452
|
-
testEndHandler(test, annotations, STATUS_TO_TEST_STATUS[status], errors && errors[0], isTimeout)
|
|
476
|
+
testEndHandler(test, annotations, STATUS_TO_TEST_STATUS[status], errors && errors[0], isTimeout, false)
|
|
477
|
+
const testResult = test.results[test.results.length - 1]
|
|
478
|
+
const isAtrRetry = testResult?.retry > 0 &&
|
|
479
|
+
isFlakyTestRetriesEnabled &&
|
|
480
|
+
!test._ddIsAttemptToFix &&
|
|
481
|
+
!test._ddIsEfdRetry
|
|
482
|
+
// We want to send the ddProperties to the worker
|
|
483
|
+
worker.process.send({
|
|
484
|
+
type: 'ddProperties',
|
|
485
|
+
testId: test.id,
|
|
486
|
+
properties: {
|
|
487
|
+
_ddIsDisabled: test._ddIsDisabled,
|
|
488
|
+
_ddIsQuarantined: test._ddIsQuarantined,
|
|
489
|
+
_ddIsAttemptToFix: test._ddIsAttemptToFix,
|
|
490
|
+
_ddIsAttemptToFixRetry: test._ddIsAttemptToFixRetry,
|
|
491
|
+
_ddIsNew: test._ddIsNew,
|
|
492
|
+
_ddIsEfdRetry: test._ddIsEfdRetry,
|
|
493
|
+
_ddHasFailedAllRetries: test._ddHasFailedAllRetries,
|
|
494
|
+
_ddHasPassedAttemptToFixRetries: test._ddHasPassedAttemptToFixRetries,
|
|
495
|
+
_ddIsAtrRetry: isAtrRetry
|
|
496
|
+
}
|
|
497
|
+
})
|
|
453
498
|
})
|
|
454
499
|
|
|
455
500
|
return worker
|
|
@@ -538,8 +583,8 @@ function runnerHook (runnerExport, playwrightVersion) {
|
|
|
538
583
|
// because they were skipped
|
|
539
584
|
tests.forEach(test => {
|
|
540
585
|
const browser = getBrowserNameFromProjects(projects, test)
|
|
541
|
-
testBeginHandler(test, browser)
|
|
542
|
-
testEndHandler(test, [], 'skip')
|
|
586
|
+
testBeginHandler(test, browser, true)
|
|
587
|
+
testEndHandler(test, [], 'skip', null, false, true)
|
|
543
588
|
})
|
|
544
589
|
})
|
|
545
590
|
|
|
@@ -720,3 +765,245 @@ addHook({
|
|
|
720
765
|
|
|
721
766
|
return loadUtilsPackage
|
|
722
767
|
})
|
|
768
|
+
|
|
769
|
+
// main process hook
|
|
770
|
+
addHook({
|
|
771
|
+
name: 'playwright',
|
|
772
|
+
file: 'lib/runner/processHost.js',
|
|
773
|
+
versions: ['>=1.38.0']
|
|
774
|
+
}, (processHostPackage) => {
|
|
775
|
+
shimmer.wrap(processHostPackage.ProcessHost.prototype, 'startRunner', startRunner => async function () {
|
|
776
|
+
this._extraEnv = {
|
|
777
|
+
...this._extraEnv,
|
|
778
|
+
// Used to detect that we're in a playwright worker
|
|
779
|
+
DD_PLAYWRIGHT_WORKER: '1'
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
const res = await startRunner.apply(this, arguments)
|
|
783
|
+
|
|
784
|
+
// We add a new listener to `this.process`, which is represents the worker
|
|
785
|
+
this.process.on('message', (message) => {
|
|
786
|
+
// These messages are [code, payload]. The payload is test data
|
|
787
|
+
if (Array.isArray(message) && message[0] === PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE) {
|
|
788
|
+
workerReportCh.publish(message[1])
|
|
789
|
+
}
|
|
790
|
+
})
|
|
791
|
+
|
|
792
|
+
return res
|
|
793
|
+
})
|
|
794
|
+
|
|
795
|
+
return processHostPackage
|
|
796
|
+
})
|
|
797
|
+
|
|
798
|
+
addHook({
|
|
799
|
+
name: 'playwright-core',
|
|
800
|
+
file: 'lib/client/page.js',
|
|
801
|
+
versions: ['>=1.38.0']
|
|
802
|
+
}, (pagePackage) => {
|
|
803
|
+
shimmer.wrap(pagePackage.Page.prototype, 'goto', goto => async function (url, options) {
|
|
804
|
+
const response = await goto.apply(this, arguments)
|
|
805
|
+
|
|
806
|
+
const page = this
|
|
807
|
+
|
|
808
|
+
const isRumActive = await page.evaluate(() => {
|
|
809
|
+
if (window.DD_RUM && window.DD_RUM.getInternalContext) {
|
|
810
|
+
return !!window.DD_RUM.getInternalContext()
|
|
811
|
+
} else {
|
|
812
|
+
return false
|
|
813
|
+
}
|
|
814
|
+
})
|
|
815
|
+
|
|
816
|
+
if (isRumActive) {
|
|
817
|
+
testPageGotoCh.publish({
|
|
818
|
+
isRumActive,
|
|
819
|
+
page
|
|
820
|
+
})
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
return response
|
|
824
|
+
})
|
|
825
|
+
|
|
826
|
+
return pagePackage
|
|
827
|
+
})
|
|
828
|
+
|
|
829
|
+
// Only in worker
|
|
830
|
+
addHook({
|
|
831
|
+
name: 'playwright',
|
|
832
|
+
file: 'lib/worker/workerMain.js',
|
|
833
|
+
versions: ['>=1.38.0']
|
|
834
|
+
}, (workerPackage) => {
|
|
835
|
+
// we assume there's only a test running at a time
|
|
836
|
+
let steps = []
|
|
837
|
+
const stepInfoByStepId = {}
|
|
838
|
+
|
|
839
|
+
shimmer.wrap(workerPackage.WorkerMain.prototype, '_runTest', _runTest => async function (test) {
|
|
840
|
+
steps = []
|
|
841
|
+
|
|
842
|
+
const {
|
|
843
|
+
_requireFile: testSuiteAbsolutePath,
|
|
844
|
+
location: {
|
|
845
|
+
line: testSourceLine
|
|
846
|
+
}
|
|
847
|
+
} = test
|
|
848
|
+
let res
|
|
849
|
+
|
|
850
|
+
let testInfo
|
|
851
|
+
const testName = getTestFullname(test)
|
|
852
|
+
const browserName = this._project.project.name
|
|
853
|
+
|
|
854
|
+
// If test events are created in the worker process I need to stop creating it in the main process
|
|
855
|
+
// Probably yet another test worker exporter is needed in addition to the ones for mocha, jest and cucumber
|
|
856
|
+
// it's probably hard to tell that's a playwright worker though, as I don't think there is a specific env variable
|
|
857
|
+
const testAsyncResource = new AsyncResource('bound-anonymous-fn')
|
|
858
|
+
// TODO - In the future we may need to implement a mechanism to send test properties
|
|
859
|
+
// to the worker process before _runTest is called
|
|
860
|
+
testAsyncResource.runInAsyncScope(() => {
|
|
861
|
+
testStartCh.publish({
|
|
862
|
+
testName,
|
|
863
|
+
testSuiteAbsolutePath,
|
|
864
|
+
testSourceLine,
|
|
865
|
+
browserName
|
|
866
|
+
})
|
|
867
|
+
|
|
868
|
+
let existAfterEachHook = false
|
|
869
|
+
|
|
870
|
+
// We try to find an existing afterEach hook with _ddHook to avoid adding a new one
|
|
871
|
+
for (const hook of test.parent._hooks) {
|
|
872
|
+
if (hook.type === 'afterEach' && hook._ddHook) {
|
|
873
|
+
existAfterEachHook = true
|
|
874
|
+
break
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// In cases where there is no afterEach hook with _ddHook, we need to add one
|
|
879
|
+
if (!existAfterEachHook) {
|
|
880
|
+
test.parent._hooks.push({
|
|
881
|
+
type: 'afterEach',
|
|
882
|
+
fn: async function ({ page }) {
|
|
883
|
+
try {
|
|
884
|
+
if (page) {
|
|
885
|
+
const isRumActive = await page.evaluate(() => {
|
|
886
|
+
if (window.DD_RUM && window.DD_RUM.stopSession) {
|
|
887
|
+
window.DD_RUM.stopSession()
|
|
888
|
+
return true
|
|
889
|
+
} else {
|
|
890
|
+
return false
|
|
891
|
+
}
|
|
892
|
+
})
|
|
893
|
+
|
|
894
|
+
if (isRumActive) {
|
|
895
|
+
const url = page.url()
|
|
896
|
+
if (url) {
|
|
897
|
+
const domain = new URL(url).hostname
|
|
898
|
+
await page.context().addCookies([{
|
|
899
|
+
name: 'datadog-ci-visibility-test-execution-id',
|
|
900
|
+
value: '',
|
|
901
|
+
domain,
|
|
902
|
+
expires: 0,
|
|
903
|
+
path: '/'
|
|
904
|
+
}])
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
} catch (e) {
|
|
909
|
+
// ignore errors
|
|
910
|
+
}
|
|
911
|
+
},
|
|
912
|
+
title: 'afterEach hook',
|
|
913
|
+
_ddHook: true
|
|
914
|
+
})
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
res = _runTest.apply(this, arguments)
|
|
918
|
+
|
|
919
|
+
testInfo = this._currentTest
|
|
920
|
+
})
|
|
921
|
+
await res
|
|
922
|
+
|
|
923
|
+
const { status, error, annotations, retry, testId } = testInfo
|
|
924
|
+
|
|
925
|
+
// testInfo.errors could be better than "error",
|
|
926
|
+
// which will only include timeout error (even though the test failed because of a different error)
|
|
927
|
+
|
|
928
|
+
let annotationTags
|
|
929
|
+
if (annotations.length) {
|
|
930
|
+
annotationTags = parseAnnotations(annotations)
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
let onDone
|
|
934
|
+
|
|
935
|
+
const flushPromise = new Promise(resolve => {
|
|
936
|
+
onDone = resolve
|
|
937
|
+
})
|
|
938
|
+
|
|
939
|
+
// Wait for ddProperties to be received and processed
|
|
940
|
+
// Create a promise that will be resolved when the properties are received
|
|
941
|
+
const ddPropertiesPromise = new Promise(resolve => {
|
|
942
|
+
const messageHandler = ({ type, testId, properties }) => {
|
|
943
|
+
if (type === 'ddProperties' && testId === test.id) {
|
|
944
|
+
// Apply the properties to the test object
|
|
945
|
+
if (properties) {
|
|
946
|
+
Object.assign(test, properties)
|
|
947
|
+
}
|
|
948
|
+
process.removeListener('message', messageHandler)
|
|
949
|
+
resolve()
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// Add the listener
|
|
954
|
+
process.on('message', messageHandler)
|
|
955
|
+
})
|
|
956
|
+
|
|
957
|
+
// Wait for the properties to be received
|
|
958
|
+
await ddPropertiesPromise
|
|
959
|
+
|
|
960
|
+
testAsyncResource.runInAsyncScope(() => {
|
|
961
|
+
testFinishCh.publish({
|
|
962
|
+
testStatus: STATUS_TO_TEST_STATUS[status],
|
|
963
|
+
steps: steps.filter(step => step.testId === testId),
|
|
964
|
+
error,
|
|
965
|
+
extraTags: annotationTags,
|
|
966
|
+
isNew: test._ddIsNew,
|
|
967
|
+
isRetry: retry > 0,
|
|
968
|
+
isEfdRetry: test._ddIsEfdRetry,
|
|
969
|
+
isAttemptToFix: test._ddIsAttemptToFix,
|
|
970
|
+
isDisabled: test._ddIsDisabled,
|
|
971
|
+
isQuarantined: test._ddIsQuarantined,
|
|
972
|
+
isAttemptToFixRetry: test._ddIsAttemptToFixRetry,
|
|
973
|
+
hasFailedAllRetries: test._ddHasFailedAllRetries,
|
|
974
|
+
hasPassedAttemptToFixRetries: test._ddHasPassedAttemptToFixRetries,
|
|
975
|
+
isAtrRetry: test._ddIsAtrRetry,
|
|
976
|
+
onDone
|
|
977
|
+
})
|
|
978
|
+
})
|
|
979
|
+
|
|
980
|
+
await flushPromise
|
|
981
|
+
|
|
982
|
+
return res
|
|
983
|
+
})
|
|
984
|
+
|
|
985
|
+
// We reproduce what happens in `Dispatcher#_onStepBegin` and `Dispatcher#_onStepEnd`,
|
|
986
|
+
// since `startTime` and `duration` are not available directly in the worker process
|
|
987
|
+
shimmer.wrap(workerPackage.WorkerMain.prototype, 'dispatchEvent', dispatchEvent => function (event, payload) {
|
|
988
|
+
if (event === 'stepBegin') {
|
|
989
|
+
stepInfoByStepId[payload.stepId] = {
|
|
990
|
+
startTime: payload.wallTime,
|
|
991
|
+
title: payload.title,
|
|
992
|
+
testId: payload.testId
|
|
993
|
+
}
|
|
994
|
+
} else if (event === 'stepEnd') {
|
|
995
|
+
const stepInfo = stepInfoByStepId[payload.stepId]
|
|
996
|
+
delete stepInfoByStepId[payload.stepId]
|
|
997
|
+
steps.push({
|
|
998
|
+
testId: stepInfo.testId,
|
|
999
|
+
startTime: new Date(stepInfo.startTime),
|
|
1000
|
+
title: stepInfo.title,
|
|
1001
|
+
duration: payload.wallTime - stepInfo.startTime,
|
|
1002
|
+
error: payload.error
|
|
1003
|
+
})
|
|
1004
|
+
}
|
|
1005
|
+
return dispatchEvent.apply(this, arguments)
|
|
1006
|
+
})
|
|
1007
|
+
|
|
1008
|
+
return workerPackage
|
|
1009
|
+
})
|
|
@@ -19,7 +19,7 @@ function createWrapRouterMethod (name) {
|
|
|
19
19
|
function wrapLayerHandle (layer, original) {
|
|
20
20
|
original._name = original._name || layer.name
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
return shimmer.wrapFunction(original, original => function () {
|
|
23
23
|
if (!enterChannel.hasSubscribers) return original.apply(this, arguments)
|
|
24
24
|
|
|
25
25
|
const matchers = layerMatchers.get(layer)
|
|
@@ -59,12 +59,6 @@ function createWrapRouterMethod (name) {
|
|
|
59
59
|
exitChannel.publish({ req })
|
|
60
60
|
}
|
|
61
61
|
})
|
|
62
|
-
|
|
63
|
-
// This is a workaround for the `loopback` library so that it can find the correct express layer
|
|
64
|
-
// that contains the real handle function
|
|
65
|
-
handle._datadog_orig = original
|
|
66
|
-
|
|
67
|
-
return handle
|
|
68
62
|
}
|
|
69
63
|
|
|
70
64
|
function wrapStack (stack, offset, matchers) {
|
|
@@ -82,7 +82,8 @@ function getProvidedContext () {
|
|
|
82
82
|
isKnownTestsEnabled: false,
|
|
83
83
|
isTestManagementTestsEnabled: false,
|
|
84
84
|
testManagementAttemptToFixRetries: 0,
|
|
85
|
-
testManagementTests: {}
|
|
85
|
+
testManagementTests: {},
|
|
86
|
+
isFlakyTestRetriesEnabled: false
|
|
86
87
|
}
|
|
87
88
|
}
|
|
88
89
|
}
|
|
@@ -466,7 +467,8 @@ addHook({
|
|
|
466
467
|
isEarlyFlakeDetectionEnabled,
|
|
467
468
|
isDiEnabled,
|
|
468
469
|
isTestManagementTestsEnabled,
|
|
469
|
-
testManagementTests
|
|
470
|
+
testManagementTests,
|
|
471
|
+
isFlakyTestRetriesEnabled
|
|
470
472
|
} = getProvidedContext()
|
|
471
473
|
|
|
472
474
|
if (isKnownTestsEnabled) {
|
|
@@ -566,6 +568,11 @@ addHook({
|
|
|
566
568
|
}
|
|
567
569
|
}
|
|
568
570
|
|
|
571
|
+
const isRetryReasonAtr = numAttempt > 0 &&
|
|
572
|
+
isFlakyTestRetriesEnabled &&
|
|
573
|
+
!isRetryReasonAttemptToFix &&
|
|
574
|
+
!isRetryReasonEfd
|
|
575
|
+
|
|
569
576
|
const asyncResource = new AsyncResource('bound-anonymous-fn')
|
|
570
577
|
taskToAsync.set(task, asyncResource)
|
|
571
578
|
|
|
@@ -580,7 +587,8 @@ addHook({
|
|
|
580
587
|
mightHitProbe: isDiEnabled && numAttempt > 0,
|
|
581
588
|
isAttemptToFix: attemptToFixTasks.has(task),
|
|
582
589
|
isDisabled: disabledTasks.has(task),
|
|
583
|
-
isQuarantined
|
|
590
|
+
isQuarantined,
|
|
591
|
+
isRetryReasonAtr
|
|
584
592
|
})
|
|
585
593
|
})
|
|
586
594
|
return onBeforeTryTask.apply(this, arguments)
|
|
@@ -33,7 +33,8 @@ const {
|
|
|
33
33
|
TEST_MANAGEMENT_IS_DISABLED,
|
|
34
34
|
TEST_MANAGEMENT_IS_ATTEMPT_TO_FIX,
|
|
35
35
|
TEST_HAS_FAILED_ALL_RETRIES,
|
|
36
|
-
TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED
|
|
36
|
+
TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED,
|
|
37
|
+
TEST_RETRY_REASON_TYPES
|
|
37
38
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
38
39
|
const { RESOURCE_NAME } = require('../../../ext/tags')
|
|
39
40
|
const { COMPONENT, ERROR_MESSAGE } = require('../../dd-trace/src/constants')
|
|
@@ -252,11 +253,16 @@ class CucumberPlugin extends CiPlugin {
|
|
|
252
253
|
}
|
|
253
254
|
})
|
|
254
255
|
|
|
255
|
-
this.addSub('ci:cucumber:test:retry', ({ isFirstAttempt, error }) => {
|
|
256
|
+
this.addSub('ci:cucumber:test:retry', ({ isFirstAttempt, error, isAtrRetry }) => {
|
|
256
257
|
const store = storage('legacy').getStore()
|
|
257
258
|
const span = store.span
|
|
258
259
|
if (!isFirstAttempt) {
|
|
259
260
|
span.setTag(TEST_IS_RETRY, 'true')
|
|
261
|
+
if (isAtrRetry) {
|
|
262
|
+
span.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.atr)
|
|
263
|
+
} else {
|
|
264
|
+
span.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.ext)
|
|
265
|
+
}
|
|
260
266
|
}
|
|
261
267
|
span.setTag('error', error)
|
|
262
268
|
if (isFirstAttempt && this.di && error && this.libraryConfig?.isDiEnabled) {
|
|
@@ -347,7 +353,7 @@ class CucumberPlugin extends CiPlugin {
|
|
|
347
353
|
span.setTag(TEST_IS_NEW, 'true')
|
|
348
354
|
if (isEfdRetry) {
|
|
349
355
|
span.setTag(TEST_IS_RETRY, 'true')
|
|
350
|
-
span.setTag(TEST_RETRY_REASON,
|
|
356
|
+
span.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.efd)
|
|
351
357
|
}
|
|
352
358
|
}
|
|
353
359
|
|
|
@@ -363,6 +369,7 @@ class CucumberPlugin extends CiPlugin {
|
|
|
363
369
|
|
|
364
370
|
if (isFlakyRetry > 0) {
|
|
365
371
|
span.setTag(TEST_IS_RETRY, 'true')
|
|
372
|
+
span.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.atr)
|
|
366
373
|
}
|
|
367
374
|
|
|
368
375
|
if (hasFailedAllRetries) {
|
|
@@ -375,7 +382,7 @@ class CucumberPlugin extends CiPlugin {
|
|
|
375
382
|
|
|
376
383
|
if (isAttemptToFixRetry) {
|
|
377
384
|
span.setTag(TEST_IS_RETRY, 'true')
|
|
378
|
-
span.setTag(TEST_RETRY_REASON,
|
|
385
|
+
span.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.atf)
|
|
379
386
|
if (hasPassedAllRetries) {
|
|
380
387
|
span.setTag(TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED, 'true')
|
|
381
388
|
}
|
|
@@ -40,7 +40,8 @@ const {
|
|
|
40
40
|
TEST_MANAGEMENT_IS_ATTEMPT_TO_FIX,
|
|
41
41
|
TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED,
|
|
42
42
|
TEST_HAS_FAILED_ALL_RETRIES,
|
|
43
|
-
getLibraryCapabilitiesTags
|
|
43
|
+
getLibraryCapabilitiesTags,
|
|
44
|
+
TEST_RETRY_REASON_TYPES
|
|
44
45
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
45
46
|
const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
|
|
46
47
|
const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
|
|
@@ -229,6 +230,7 @@ class CypressPlugin {
|
|
|
229
230
|
this.isTestsSkipped = false
|
|
230
231
|
this.isSuitesSkippingEnabled = false
|
|
231
232
|
this.isCodeCoverageEnabled = false
|
|
233
|
+
this.isFlakyTestRetriesEnabled = false
|
|
232
234
|
this.isEarlyFlakeDetectionEnabled = false
|
|
233
235
|
this.isKnownTestsEnabled = false
|
|
234
236
|
this.earlyFlakeDetectionNumRetries = 0
|
|
@@ -278,6 +280,7 @@ class CypressPlugin {
|
|
|
278
280
|
this.earlyFlakeDetectionNumRetries = earlyFlakeDetectionNumRetries
|
|
279
281
|
this.isKnownTestsEnabled = isKnownTestsEnabled
|
|
280
282
|
if (isFlakyTestRetriesEnabled) {
|
|
283
|
+
this.isFlakyTestRetriesEnabled = true
|
|
281
284
|
this.cypressConfig.retries.runMode = flakyTestRetriesCount
|
|
282
285
|
}
|
|
283
286
|
this.isTestManagementTestsEnabled = isTestManagementEnabled
|
|
@@ -654,10 +657,18 @@ class CypressPlugin {
|
|
|
654
657
|
let cypressTestStatus = CYPRESS_STATUS_TO_TEST_STATUS[cypressTest.state]
|
|
655
658
|
if (cypressTest.attempts && cypressTest.attempts[attemptIndex]) {
|
|
656
659
|
cypressTestStatus = CYPRESS_STATUS_TO_TEST_STATUS[cypressTest.attempts[attemptIndex].state]
|
|
660
|
+
const isAtrRetry = attemptIndex > 0 &&
|
|
661
|
+
this.isFlakyTestRetriesEnabled &&
|
|
662
|
+
!finishedTest.isAttemptToFix &&
|
|
663
|
+
!finishedTest.isEfdRetry
|
|
657
664
|
if (attemptIndex > 0) {
|
|
658
665
|
finishedTest.testSpan.setTag(TEST_IS_RETRY, 'true')
|
|
659
666
|
if (finishedTest.isEfdRetry) {
|
|
660
|
-
finishedTest.testSpan.setTag(TEST_RETRY_REASON,
|
|
667
|
+
finishedTest.testSpan.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.efd)
|
|
668
|
+
} else if (isAtrRetry) {
|
|
669
|
+
finishedTest.testSpan.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.atr)
|
|
670
|
+
} else {
|
|
671
|
+
finishedTest.testSpan.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.ext)
|
|
661
672
|
}
|
|
662
673
|
}
|
|
663
674
|
}
|
|
@@ -816,14 +827,14 @@ class CypressPlugin {
|
|
|
816
827
|
this.activeTestSpan.setTag(TEST_IS_NEW, 'true')
|
|
817
828
|
if (isEfdRetry) {
|
|
818
829
|
this.activeTestSpan.setTag(TEST_IS_RETRY, 'true')
|
|
819
|
-
this.activeTestSpan.setTag(TEST_RETRY_REASON,
|
|
830
|
+
this.activeTestSpan.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.efd)
|
|
820
831
|
}
|
|
821
832
|
}
|
|
822
833
|
if (isAttemptToFix) {
|
|
823
834
|
this.activeTestSpan.setTag(TEST_MANAGEMENT_IS_ATTEMPT_TO_FIX, 'true')
|
|
824
835
|
if (testStatuses.length > 1) {
|
|
825
836
|
this.activeTestSpan.setTag(TEST_IS_RETRY, 'true')
|
|
826
|
-
this.activeTestSpan.setTag(TEST_RETRY_REASON,
|
|
837
|
+
this.activeTestSpan.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.atf)
|
|
827
838
|
}
|
|
828
839
|
const isLastAttempt = testStatuses.length === this.testManagementAttemptToFixRetries + 1
|
|
829
840
|
if (isLastAttempt) {
|
|
@@ -840,7 +851,8 @@ class CypressPlugin {
|
|
|
840
851
|
testStatus,
|
|
841
852
|
finishTime: this.activeTestSpan._getTime(), // we store the finish time here
|
|
842
853
|
testSpan: this.activeTestSpan,
|
|
843
|
-
isEfdRetry
|
|
854
|
+
isEfdRetry,
|
|
855
|
+
isAttemptToFix
|
|
844
856
|
}
|
|
845
857
|
if (this.finishedTestsByFile[testSuite]) {
|
|
846
858
|
this.finishedTestsByFile[testSuite].push(finishedTest)
|