dd-trace 5.73.0 → 5.75.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 +2 -0
- package/index.d.ts +39 -7
- package/loader-hook.mjs +52 -1
- package/package.json +8 -16
- package/packages/datadog-core/src/utils/src/set.js +5 -1
- package/packages/datadog-esbuild/index.js +105 -36
- package/packages/datadog-esbuild/src/utils.js +198 -0
- package/packages/datadog-instrumentations/src/cookie-parser.js +0 -2
- package/packages/datadog-instrumentations/src/cucumber.js +2 -2
- package/packages/datadog-instrumentations/src/express.js +82 -0
- package/packages/datadog-instrumentations/src/helpers/router-helper.js +238 -0
- package/packages/datadog-instrumentations/src/jest.js +2 -1
- package/packages/datadog-instrumentations/src/mariadb.js +9 -7
- package/packages/datadog-instrumentations/src/playwright.js +226 -93
- package/packages/datadog-instrumentations/src/router.js +63 -6
- package/packages/datadog-instrumentations/src/vitest.js +44 -12
- package/packages/datadog-instrumentations/src/ws.js +3 -3
- package/packages/datadog-plugin-aws-sdk/src/base.js +0 -1
- package/packages/datadog-plugin-express/src/code_origin.js +2 -0
- package/packages/datadog-plugin-playwright/src/index.js +74 -31
- package/packages/datadog-plugin-ws/src/close.js +1 -1
- package/packages/datadog-shimmer/src/shimmer.js +2 -0
- package/packages/dd-trace/src/aiguard/sdk.js +25 -3
- package/packages/dd-trace/src/aiguard/tags.js +4 -1
- package/packages/dd-trace/src/config-helper.js +4 -1
- package/packages/dd-trace/src/config.js +599 -592
- package/packages/dd-trace/src/config_defaults.js +14 -12
- package/packages/dd-trace/src/plugins/util/ci.js +3 -2
- package/packages/dd-trace/src/plugins/util/stacktrace.js +16 -1
- package/packages/dd-trace/src/proxy.js +1 -1
- package/packages/dd-trace/src/supported-configurations.json +1 -0
- package/packages/dd-trace/src/telemetry/endpoints.js +27 -1
- package/packages/dd-trace/src/telemetry/index.js +16 -13
- package/packages/dd-trace/src/telemetry/logs/log-collector.js +5 -3
- package/register.js +1 -11
- package/scripts/preinstall.js +3 -1
- package/version.js +2 -1
|
@@ -7,13 +7,15 @@ const shimmer = require('../../datadog-shimmer')
|
|
|
7
7
|
const {
|
|
8
8
|
parseAnnotations,
|
|
9
9
|
getTestSuitePath,
|
|
10
|
-
PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE
|
|
10
|
+
PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE,
|
|
11
|
+
getIsFaultyEarlyFlakeDetection
|
|
11
12
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
12
13
|
const log = require('../../dd-trace/src/log')
|
|
13
14
|
const { DD_MAJOR } = require('../../../version')
|
|
14
15
|
|
|
15
16
|
const testStartCh = channel('ci:playwright:test:start')
|
|
16
17
|
const testFinishCh = channel('ci:playwright:test:finish')
|
|
18
|
+
const testSkipCh = channel('ci:playwright:test:skip')
|
|
17
19
|
|
|
18
20
|
const testSessionStartCh = channel('ci:playwright:session:start')
|
|
19
21
|
const testSessionFinishCh = channel('ci:playwright:session:finish')
|
|
@@ -36,6 +38,8 @@ const testSuiteToTestStatuses = new Map()
|
|
|
36
38
|
const testSuiteToErrors = new Map()
|
|
37
39
|
const testsToTestStatuses = new Map()
|
|
38
40
|
|
|
41
|
+
const RUM_FLUSH_WAIT_TIME = 1000
|
|
42
|
+
|
|
39
43
|
let applyRepeatEachIndex = null
|
|
40
44
|
|
|
41
45
|
let startedSuites = []
|
|
@@ -52,6 +56,7 @@ let isKnownTestsEnabled = false
|
|
|
52
56
|
let isEarlyFlakeDetectionEnabled = false
|
|
53
57
|
let earlyFlakeDetectionNumRetries = 0
|
|
54
58
|
let isEarlyFlakeDetectionFaulty = false
|
|
59
|
+
let earlyFlakeDetectionFaultyThreshold = 0
|
|
55
60
|
let isFlakyTestRetriesEnabled = false
|
|
56
61
|
let flakyTestRetriesCount = 0
|
|
57
62
|
let knownTests = {}
|
|
@@ -111,8 +116,10 @@ function deepCloneSuite (suite, filterTest, tags = []) {
|
|
|
111
116
|
if (filterTest(entry)) {
|
|
112
117
|
const copiedTest = entry._clone()
|
|
113
118
|
tags.forEach(tag => {
|
|
114
|
-
|
|
115
|
-
|
|
119
|
+
const resolvedTag = typeof tag === 'function' ? tag(entry) : tag
|
|
120
|
+
|
|
121
|
+
if (resolvedTag) {
|
|
122
|
+
copiedTest[resolvedTag] = true
|
|
116
123
|
}
|
|
117
124
|
})
|
|
118
125
|
copy._addTest(copiedTest)
|
|
@@ -281,7 +288,12 @@ function getTestFullname (test) {
|
|
|
281
288
|
return names.join(' ')
|
|
282
289
|
}
|
|
283
290
|
|
|
284
|
-
function
|
|
291
|
+
function shouldFinishTestSuite (testSuiteAbsolutePath) {
|
|
292
|
+
const remainingTests = remainingTestsByFile[testSuiteAbsolutePath]
|
|
293
|
+
return !remainingTests.length || remainingTests.every(test => test.expectedStatus === 'skipped')
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function testBeginHandler (test, browserName, shouldCreateTestSpan) {
|
|
285
297
|
const {
|
|
286
298
|
_requireFile: testSuiteAbsolutePath,
|
|
287
299
|
location: {
|
|
@@ -293,6 +305,10 @@ function testBeginHandler (test, browserName, isMainProcess) {
|
|
|
293
305
|
if (_type === 'beforeAll' || _type === 'afterAll') {
|
|
294
306
|
return
|
|
295
307
|
}
|
|
308
|
+
// this means that a skipped test is being handled
|
|
309
|
+
if (!remainingTestsByFile[testSuiteAbsolutePath].length) {
|
|
310
|
+
return
|
|
311
|
+
}
|
|
296
312
|
|
|
297
313
|
const isNewTestSuite = !startedSuites.includes(testSuiteAbsolutePath)
|
|
298
314
|
|
|
@@ -309,7 +325,7 @@ function testBeginHandler (test, browserName, isMainProcess) {
|
|
|
309
325
|
}
|
|
310
326
|
|
|
311
327
|
// this handles tests that do not go through the worker process (because they're skipped)
|
|
312
|
-
if (
|
|
328
|
+
if (shouldCreateTestSpan) {
|
|
313
329
|
const testName = getTestFullname(test)
|
|
314
330
|
const testCtx = {
|
|
315
331
|
testName,
|
|
@@ -324,8 +340,20 @@ function testBeginHandler (test, browserName, isMainProcess) {
|
|
|
324
340
|
}
|
|
325
341
|
}
|
|
326
342
|
|
|
327
|
-
function testEndHandler (
|
|
328
|
-
|
|
343
|
+
function testEndHandler ({
|
|
344
|
+
test,
|
|
345
|
+
annotations,
|
|
346
|
+
testStatus,
|
|
347
|
+
error,
|
|
348
|
+
isTimeout,
|
|
349
|
+
shouldCreateTestSpan,
|
|
350
|
+
projects
|
|
351
|
+
}) {
|
|
352
|
+
const {
|
|
353
|
+
_requireFile: testSuiteAbsolutePath,
|
|
354
|
+
results,
|
|
355
|
+
_type,
|
|
356
|
+
} = test
|
|
329
357
|
|
|
330
358
|
let annotationTags
|
|
331
359
|
if (annotations.length) {
|
|
@@ -364,31 +392,34 @@ function testEndHandler (test, annotations, testStatus, error, isTimeout, isMain
|
|
|
364
392
|
}
|
|
365
393
|
|
|
366
394
|
// this handles tests that do not go through the worker process (because they're skipped)
|
|
367
|
-
if (
|
|
395
|
+
if (shouldCreateTestSpan) {
|
|
368
396
|
const testResult = results.at(-1)
|
|
369
397
|
const testCtx = testToCtx.get(test)
|
|
370
398
|
const isAtrRetry = testResult?.retry > 0 &&
|
|
371
399
|
isFlakyTestRetriesEnabled &&
|
|
372
400
|
!test._ddIsAttemptToFix &&
|
|
373
401
|
!test._ddIsEfdRetry
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
402
|
+
// if there is no testCtx, the skipped test will be created later
|
|
403
|
+
if (testCtx) {
|
|
404
|
+
testFinishCh.publish({
|
|
405
|
+
testStatus,
|
|
406
|
+
steps: testResult?.steps || [],
|
|
407
|
+
isRetry: testResult?.retry > 0,
|
|
408
|
+
error,
|
|
409
|
+
extraTags: annotationTags,
|
|
410
|
+
isNew: test._ddIsNew,
|
|
411
|
+
isAttemptToFix: test._ddIsAttemptToFix,
|
|
412
|
+
isAttemptToFixRetry: test._ddIsAttemptToFixRetry,
|
|
413
|
+
isQuarantined: test._ddIsQuarantined,
|
|
414
|
+
isEfdRetry: test._ddIsEfdRetry,
|
|
415
|
+
hasFailedAllRetries: test._ddHasFailedAllRetries,
|
|
416
|
+
hasPassedAttemptToFixRetries: test._ddHasPassedAttemptToFixRetries,
|
|
417
|
+
hasFailedAttemptToFixRetries: test._ddHasFailedAttemptToFixRetries,
|
|
418
|
+
isAtrRetry,
|
|
419
|
+
isModified: test._ddIsModified,
|
|
420
|
+
...testCtx.currentStore
|
|
421
|
+
})
|
|
422
|
+
}
|
|
392
423
|
}
|
|
393
424
|
|
|
394
425
|
if (testSuiteToTestStatuses.has(testSuiteAbsolutePath)) {
|
|
@@ -406,8 +437,25 @@ function testEndHandler (test, annotations, testStatus, error, isTimeout, isMain
|
|
|
406
437
|
.filter(currentTest => currentTest !== test)
|
|
407
438
|
}
|
|
408
439
|
|
|
409
|
-
|
|
410
|
-
|
|
440
|
+
if (shouldFinishTestSuite(testSuiteAbsolutePath)) {
|
|
441
|
+
const skippedTests = remainingTestsByFile[testSuiteAbsolutePath]
|
|
442
|
+
.filter(test => test.expectedStatus === 'skipped')
|
|
443
|
+
|
|
444
|
+
for (const test of skippedTests) {
|
|
445
|
+
const browserName = getBrowserNameFromProjects(projects, test)
|
|
446
|
+
testSkipCh.publish({
|
|
447
|
+
testName: getTestFullname(test),
|
|
448
|
+
testSuiteAbsolutePath,
|
|
449
|
+
testSourceLine: test.location.line,
|
|
450
|
+
browserName,
|
|
451
|
+
isNew: test._ddIsNew,
|
|
452
|
+
isDisabled: test._ddIsDisabled,
|
|
453
|
+
isModified: test._ddIsModified,
|
|
454
|
+
isQuarantined: test._ddIsQuarantined
|
|
455
|
+
})
|
|
456
|
+
}
|
|
457
|
+
remainingTestsByFile[testSuiteAbsolutePath] = []
|
|
458
|
+
|
|
411
459
|
const testStatuses = testSuiteToTestStatuses.get(testSuiteAbsolutePath)
|
|
412
460
|
let testSuiteStatus = 'pass'
|
|
413
461
|
if (testStatuses.includes('fail')) {
|
|
@@ -446,10 +494,13 @@ function dispatcherHook (dispatcherExport) {
|
|
|
446
494
|
shimmer.wrap(dispatcherExport.Dispatcher.prototype, '_createWorker', createWorker => function () {
|
|
447
495
|
const dispatcher = this
|
|
448
496
|
const worker = createWorker.apply(this, arguments)
|
|
497
|
+
const projects = getProjectsFromDispatcher(dispatcher)
|
|
498
|
+
|
|
499
|
+
// for older versions of playwright, `shouldCreateTestSpan` should always be true,
|
|
500
|
+
// since the `_runTest` function wrapper is not available for older versions
|
|
449
501
|
worker.process.on('message', ({ method, params }) => {
|
|
450
502
|
if (method === 'testBegin') {
|
|
451
503
|
const { test } = dispatcher._testById.get(params.testId)
|
|
452
|
-
const projects = getProjectsFromDispatcher(dispatcher)
|
|
453
504
|
const browser = getBrowserNameFromProjects(projects, test)
|
|
454
505
|
testBeginHandler(test, browser, true)
|
|
455
506
|
} else if (method === 'testEnd') {
|
|
@@ -460,12 +511,15 @@ function dispatcherHook (dispatcherExport) {
|
|
|
460
511
|
|
|
461
512
|
const isTimeout = testResult.status === 'timedOut'
|
|
462
513
|
testEndHandler(
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
514
|
+
{
|
|
515
|
+
test,
|
|
516
|
+
annotations: params.annotations,
|
|
517
|
+
testStatus: STATUS_TO_TEST_STATUS[testResult.status],
|
|
518
|
+
error: testResult.error,
|
|
519
|
+
isTimeout,
|
|
520
|
+
shouldCreateTestSpan: true,
|
|
521
|
+
projects
|
|
522
|
+
}
|
|
469
523
|
)
|
|
470
524
|
}
|
|
471
525
|
})
|
|
@@ -480,18 +534,30 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
|
|
|
480
534
|
shimmer.wrap(dispatcherExport.Dispatcher.prototype, '_createWorker', createWorker => function () {
|
|
481
535
|
const dispatcher = this
|
|
482
536
|
const worker = createWorker.apply(this, arguments)
|
|
537
|
+
const projects = getProjectsFromDispatcher(dispatcher)
|
|
483
538
|
|
|
484
539
|
worker.on('testBegin', ({ testId }) => {
|
|
485
540
|
const test = getTestByTestId(dispatcher, testId)
|
|
486
|
-
const projects = getProjectsFromDispatcher(dispatcher)
|
|
487
541
|
const browser = getBrowserNameFromProjects(projects, test)
|
|
488
|
-
|
|
542
|
+
const shouldCreateTestSpan = test.expectedStatus === 'skipped'
|
|
543
|
+
testBeginHandler(test, browser, shouldCreateTestSpan)
|
|
489
544
|
})
|
|
490
545
|
worker.on('testEnd', ({ testId, status, errors, annotations }) => {
|
|
491
546
|
const test = getTestByTestId(dispatcher, testId)
|
|
492
547
|
|
|
493
548
|
const isTimeout = status === 'timedOut'
|
|
494
|
-
|
|
549
|
+
const shouldCreateTestSpan = test.expectedStatus === 'skipped'
|
|
550
|
+
testEndHandler(
|
|
551
|
+
{
|
|
552
|
+
test,
|
|
553
|
+
annotations,
|
|
554
|
+
testStatus: STATUS_TO_TEST_STATUS[status],
|
|
555
|
+
error: errors && errors[0],
|
|
556
|
+
isTimeout,
|
|
557
|
+
shouldCreateTestSpan,
|
|
558
|
+
projects
|
|
559
|
+
}
|
|
560
|
+
)
|
|
495
561
|
const testResult = test.results.at(-1)
|
|
496
562
|
const isAtrRetry = testResult?.retry > 0 &&
|
|
497
563
|
isFlakyTestRetriesEnabled &&
|
|
@@ -542,6 +608,7 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
|
|
|
542
608
|
isKnownTestsEnabled = libraryConfig.isKnownTestsEnabled
|
|
543
609
|
isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
|
|
544
610
|
earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
|
|
611
|
+
earlyFlakeDetectionFaultyThreshold = libraryConfig.earlyFlakeDetectionFaultyThreshold
|
|
545
612
|
isFlakyTestRetriesEnabled = libraryConfig.isFlakyTestRetriesEnabled
|
|
546
613
|
flakyTestRetriesCount = libraryConfig.flakyTestRetriesCount
|
|
547
614
|
isTestManagementTestsEnabled = libraryConfig.isTestManagementEnabled
|
|
@@ -620,6 +687,9 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
|
|
|
620
687
|
|
|
621
688
|
let runAllTestsReturn = await runAllTests.apply(this, arguments)
|
|
622
689
|
|
|
690
|
+
// Tests that have only skipped tests may reach this point
|
|
691
|
+
// Skipped tests may or may not go through `testBegin` or `testEnd`
|
|
692
|
+
// depending on the playwright configuration
|
|
623
693
|
Object.values(remainingTestsByFile).forEach(tests => {
|
|
624
694
|
// `tests` should normally be empty, but if it isn't,
|
|
625
695
|
// there were tests that did not go through `testBegin` or `testEnd`,
|
|
@@ -627,7 +697,15 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
|
|
|
627
697
|
tests.forEach(test => {
|
|
628
698
|
const browser = getBrowserNameFromProjects(projects, test)
|
|
629
699
|
testBeginHandler(test, browser, true)
|
|
630
|
-
testEndHandler(
|
|
700
|
+
testEndHandler({
|
|
701
|
+
test,
|
|
702
|
+
annotations: [],
|
|
703
|
+
testStatus: 'skip',
|
|
704
|
+
error: null,
|
|
705
|
+
isTimeout: false,
|
|
706
|
+
shouldCreateTestSpan: true,
|
|
707
|
+
projects
|
|
708
|
+
})
|
|
631
709
|
})
|
|
632
710
|
})
|
|
633
711
|
|
|
@@ -761,6 +839,30 @@ addHook({
|
|
|
761
839
|
return suiteUtilsPackage
|
|
762
840
|
})
|
|
763
841
|
|
|
842
|
+
/**
|
|
843
|
+
* We could repeat the logic of `applyRepeatEachIndex` here, but it'd be more risky
|
|
844
|
+
* as playwright could change it at any time.
|
|
845
|
+
*
|
|
846
|
+
* `applyRepeatEachIndex` goes through all the tests in a suite and applies the "repeat" logic
|
|
847
|
+
* for a single repeat index.
|
|
848
|
+
*
|
|
849
|
+
* This means that the clone logic is cumbersome:
|
|
850
|
+
* - we grab the unique file suites that have new tests
|
|
851
|
+
* - we store its project suite
|
|
852
|
+
* - we clone each of these file suites for each repeat index
|
|
853
|
+
* - we execute `applyRepeatEachIndex` for each of these cloned file suites
|
|
854
|
+
* - we add the cloned file suites to the project suite
|
|
855
|
+
*/
|
|
856
|
+
function applyRetriesToTests (fileSuitesWithTestsToRetry, filterTest, tagsToApply, numRetries) {
|
|
857
|
+
for (const [fileSuite, projectSuite] of fileSuitesWithTestsToRetry.entries()) {
|
|
858
|
+
for (let repeatEachIndex = 1; repeatEachIndex <= numRetries; repeatEachIndex++) {
|
|
859
|
+
const copyFileSuite = deepCloneSuite(fileSuite, filterTest, tagsToApply)
|
|
860
|
+
applyRepeatEachIndex(projectSuite._fullProject, copyFileSuite, repeatEachIndex + 1)
|
|
861
|
+
projectSuite._addSuite(copyFileSuite)
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
764
866
|
addHook({
|
|
765
867
|
name: 'playwright',
|
|
766
868
|
file: 'lib/runner/loadUtils.js',
|
|
@@ -780,87 +882,112 @@ addHook({
|
|
|
780
882
|
const allTests = rootSuite.allTests()
|
|
781
883
|
|
|
782
884
|
if (isTestManagementTestsEnabled) {
|
|
885
|
+
const fileSuitesWithManagedTestsToProjects = new Map()
|
|
783
886
|
for (const test of allTests) {
|
|
784
887
|
const testProperties = getTestProperties(test)
|
|
888
|
+
// Disabled tests are skipped and not retried
|
|
785
889
|
if (testProperties.disabled) {
|
|
786
890
|
test._ddIsDisabled = true
|
|
787
|
-
|
|
891
|
+
test.expectedStatus = 'skipped'
|
|
892
|
+
continue
|
|
893
|
+
}
|
|
894
|
+
if (testProperties.quarantined) {
|
|
788
895
|
test._ddIsQuarantined = true
|
|
896
|
+
if (!testProperties.attemptToFix) {
|
|
897
|
+
// Do not skip quarantined tests, let them run and overwrite results post-run if they fail
|
|
898
|
+
const testFqn = getTestFullyQualifiedName(test)
|
|
899
|
+
quarantinedButNotAttemptToFixFqns.add(testFqn)
|
|
900
|
+
}
|
|
789
901
|
}
|
|
790
902
|
if (testProperties.attemptToFix) {
|
|
791
903
|
test._ddIsAttemptToFix = true
|
|
792
904
|
const fileSuite = getSuiteType(test, 'file')
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
const copyFileSuite = deepCloneSuite(fileSuite, isAttemptToFix, [
|
|
797
|
-
testProperties.disabled && '_ddIsDisabled',
|
|
798
|
-
testProperties.quarantined && '_ddIsQuarantined',
|
|
799
|
-
'_ddIsAttemptToFix',
|
|
800
|
-
'_ddIsAttemptToFixRetry'
|
|
801
|
-
])
|
|
802
|
-
applyRepeatEachIndex(projectSuite._fullProject, copyFileSuite, repeatEachIndex + 1)
|
|
803
|
-
projectSuite._addSuite(copyFileSuite)
|
|
905
|
+
|
|
906
|
+
if (!fileSuitesWithManagedTestsToProjects.has(fileSuite)) {
|
|
907
|
+
fileSuitesWithManagedTestsToProjects.set(fileSuite, getSuiteType(test, 'project'))
|
|
804
908
|
}
|
|
805
909
|
if (testProperties.disabled || testProperties.quarantined) {
|
|
806
910
|
quarantinedOrDisabledTestsAttemptToFix.push(test)
|
|
807
911
|
}
|
|
808
|
-
} else if (testProperties.disabled) {
|
|
809
|
-
test.expectedStatus = 'skipped'
|
|
810
|
-
} else if (testProperties.quarantined) {
|
|
811
|
-
// Do not skip quarantined tests, let them run and overwrite results post-run if they fail
|
|
812
|
-
const testFqn = getTestFullyQualifiedName(test)
|
|
813
|
-
quarantinedButNotAttemptToFixFqns.add(testFqn)
|
|
814
912
|
}
|
|
815
913
|
}
|
|
914
|
+
applyRetriesToTests(
|
|
915
|
+
fileSuitesWithManagedTestsToProjects,
|
|
916
|
+
(test) => test._ddIsAttemptToFix,
|
|
917
|
+
[
|
|
918
|
+
(test) => test._ddIsQuarantined && '_ddIsQuarantined',
|
|
919
|
+
'_ddIsAttemptToFix',
|
|
920
|
+
'_ddIsAttemptToFixRetry'
|
|
921
|
+
],
|
|
922
|
+
testManagementAttemptToFixRetries
|
|
923
|
+
)
|
|
816
924
|
}
|
|
817
925
|
|
|
818
926
|
if (isImpactedTestsEnabled) {
|
|
819
|
-
|
|
820
|
-
|
|
927
|
+
const impactedTests = allTests.filter(test => {
|
|
928
|
+
let isImpacted = false
|
|
929
|
+
isModifiedCh.publish({
|
|
821
930
|
filePath: test._requireFile,
|
|
822
|
-
modifiedFiles
|
|
931
|
+
modifiedFiles,
|
|
932
|
+
onDone: (isModified) => { isImpacted = isModified }
|
|
823
933
|
})
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
const copyFileSuite = deepCloneSuite(fileSuite, isModifiedTest, [
|
|
835
|
-
isNew && '_ddIsNew',
|
|
836
|
-
'_ddIsModified',
|
|
837
|
-
'_ddIsEfdRetry'
|
|
838
|
-
])
|
|
839
|
-
applyRepeatEachIndex(projectSuite._fullProject, copyFileSuite, repeatEachIndex + 1)
|
|
840
|
-
projectSuite._addSuite(copyFileSuite)
|
|
934
|
+
return isImpacted
|
|
935
|
+
})
|
|
936
|
+
|
|
937
|
+
const fileSuitesWithImpactedTestsToProjects = new Map()
|
|
938
|
+
impactedTests.forEach(impactedTest => {
|
|
939
|
+
impactedTest._ddIsModified = true
|
|
940
|
+
if (isEarlyFlakeDetectionEnabled && impactedTest.expectedStatus !== 'skipped') {
|
|
941
|
+
const fileSuite = getSuiteType(impactedTest, 'file')
|
|
942
|
+
if (!fileSuitesWithImpactedTestsToProjects.has(fileSuite)) {
|
|
943
|
+
fileSuitesWithImpactedTestsToProjects.set(fileSuite, getSuiteType(impactedTest, 'project'))
|
|
841
944
|
}
|
|
842
945
|
}
|
|
843
|
-
})
|
|
946
|
+
})
|
|
947
|
+
// If something change in the file, all tests in the file are impacted, hence the () => true filter
|
|
948
|
+
applyRetriesToTests(
|
|
949
|
+
fileSuitesWithImpactedTestsToProjects,
|
|
950
|
+
() => true,
|
|
951
|
+
[
|
|
952
|
+
'_ddIsModified',
|
|
953
|
+
'_ddIsEfdRetry',
|
|
954
|
+
(test) => (isKnownTestsEnabled && isNewTest(test) ? '_ddIsNew' : null)
|
|
955
|
+
],
|
|
956
|
+
earlyFlakeDetectionNumRetries
|
|
957
|
+
)
|
|
844
958
|
}
|
|
845
959
|
|
|
846
960
|
if (isKnownTestsEnabled) {
|
|
847
961
|
const newTests = allTests.filter(isNewTest)
|
|
848
962
|
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
963
|
+
const isFaulty = getIsFaultyEarlyFlakeDetection(
|
|
964
|
+
allTests.map(test => getTestSuitePath(test._requireFile, rootDir)),
|
|
965
|
+
knownTests.playwright,
|
|
966
|
+
earlyFlakeDetectionFaultyThreshold
|
|
967
|
+
)
|
|
968
|
+
|
|
969
|
+
if (isFaulty) {
|
|
970
|
+
isEarlyFlakeDetectionEnabled = false
|
|
971
|
+
isKnownTestsEnabled = false
|
|
972
|
+
isEarlyFlakeDetectionFaulty = true
|
|
973
|
+
} else {
|
|
974
|
+
const fileSuitesWithNewTestsToProjects = new Map()
|
|
975
|
+
newTests.forEach(newTest => {
|
|
976
|
+
newTest._ddIsNew = true
|
|
977
|
+
if (isEarlyFlakeDetectionEnabled && newTest.expectedStatus !== 'skipped' && !newTest._ddIsModified) {
|
|
978
|
+
const fileSuite = getSuiteType(newTest, 'file')
|
|
979
|
+
if (!fileSuitesWithNewTestsToProjects.has(fileSuite)) {
|
|
980
|
+
fileSuitesWithNewTestsToProjects.set(fileSuite, getSuiteType(newTest, 'project'))
|
|
981
|
+
}
|
|
862
982
|
}
|
|
863
|
-
}
|
|
983
|
+
})
|
|
984
|
+
|
|
985
|
+
applyRetriesToTests(
|
|
986
|
+
fileSuitesWithNewTestsToProjects,
|
|
987
|
+
isNewTest,
|
|
988
|
+
['_ddIsNew', '_ddIsEfdRetry'],
|
|
989
|
+
earlyFlakeDetectionNumRetries
|
|
990
|
+
)
|
|
864
991
|
}
|
|
865
992
|
}
|
|
866
993
|
|
|
@@ -953,6 +1080,9 @@ addHook({
|
|
|
953
1080
|
const stepInfoByStepId = {}
|
|
954
1081
|
|
|
955
1082
|
shimmer.wrap(workerPackage.WorkerMain.prototype, '_runTest', _runTest => async function (test) {
|
|
1083
|
+
if (test.expectedStatus === 'skipped') {
|
|
1084
|
+
return _runTest.apply(this, arguments)
|
|
1085
|
+
}
|
|
956
1086
|
steps = []
|
|
957
1087
|
|
|
958
1088
|
const {
|
|
@@ -1006,6 +1136,8 @@ addHook({
|
|
|
1006
1136
|
})
|
|
1007
1137
|
|
|
1008
1138
|
if (isRumActive) {
|
|
1139
|
+
// Give some time RUM to flush data, similar to what we do in selenium
|
|
1140
|
+
await new Promise(resolve => setTimeout(resolve, RUM_FLUSH_WAIT_TIME))
|
|
1009
1141
|
const url = page.url()
|
|
1010
1142
|
if (url) {
|
|
1011
1143
|
const domain = new URL(url).hostname
|
|
@@ -1013,9 +1145,10 @@ addHook({
|
|
|
1013
1145
|
name: 'datadog-ci-visibility-test-execution-id',
|
|
1014
1146
|
value: '',
|
|
1015
1147
|
domain,
|
|
1016
|
-
expires: 0,
|
|
1017
1148
|
path: '/'
|
|
1018
1149
|
}])
|
|
1150
|
+
} else {
|
|
1151
|
+
log.error('RUM is active but page.url() is not available')
|
|
1019
1152
|
}
|
|
1020
1153
|
}
|
|
1021
1154
|
}
|
|
@@ -5,6 +5,19 @@ const pathToRegExp = require('path-to-regexp')
|
|
|
5
5
|
const shimmer = require('../../datadog-shimmer')
|
|
6
6
|
const { addHook, channel } = require('./helpers/instrument')
|
|
7
7
|
|
|
8
|
+
const {
|
|
9
|
+
getRouterMountPaths,
|
|
10
|
+
joinPath,
|
|
11
|
+
getLayerMatchers,
|
|
12
|
+
setLayerMatchers,
|
|
13
|
+
isAppMounted,
|
|
14
|
+
setRouterMountPath,
|
|
15
|
+
extractMountPaths,
|
|
16
|
+
getRouteFullPaths,
|
|
17
|
+
wrapRouteMethodsAndPublish,
|
|
18
|
+
collectRoutesFromRouter
|
|
19
|
+
} = require('./helpers/router-helper')
|
|
20
|
+
|
|
8
21
|
function isFastStar (layer, matchers) {
|
|
9
22
|
return layer.regexp?.fast_star ?? matchers.some(matcher => matcher.path === '*')
|
|
10
23
|
}
|
|
@@ -22,7 +35,6 @@ function createWrapRouterMethod (name) {
|
|
|
22
35
|
const nextChannel = channel(`apm:${name}:middleware:next`)
|
|
23
36
|
const routeAddedChannel = channel(`apm:${name}:route:added`)
|
|
24
37
|
|
|
25
|
-
const layerMatchers = new WeakMap()
|
|
26
38
|
const regexpCache = Object.create(null)
|
|
27
39
|
|
|
28
40
|
function wrapLayerHandle (layer, original) {
|
|
@@ -31,7 +43,7 @@ function createWrapRouterMethod (name) {
|
|
|
31
43
|
return shimmer.wrapFunction(original, original => function () {
|
|
32
44
|
if (!enterChannel.hasSubscribers) return original.apply(this, arguments)
|
|
33
45
|
|
|
34
|
-
const matchers =
|
|
46
|
+
const matchers = getLayerMatchers(layer)
|
|
35
47
|
const lastIndex = arguments.length - 1
|
|
36
48
|
const name = original._name || original.name
|
|
37
49
|
const req = arguments[arguments.length > 3 ? 1 : 0]
|
|
@@ -78,7 +90,7 @@ function createWrapRouterMethod (name) {
|
|
|
78
90
|
layer.handle = wrapLayerHandle(layer, layer.handle)
|
|
79
91
|
}
|
|
80
92
|
|
|
81
|
-
|
|
93
|
+
setLayerMatchers(layer, matchers)
|
|
82
94
|
|
|
83
95
|
if (layer.route) {
|
|
84
96
|
METHODS.forEach(method => {
|
|
@@ -115,7 +127,7 @@ function createWrapRouterMethod (name) {
|
|
|
115
127
|
return arg.map(pattern => ({
|
|
116
128
|
path: pattern instanceof RegExp ? `(${pattern})` : pattern,
|
|
117
129
|
test: layer => {
|
|
118
|
-
const matchers =
|
|
130
|
+
const matchers = getLayerMatchers(layer)
|
|
119
131
|
return !isFastStar(layer, matchers) &&
|
|
120
132
|
!isFastSlash(layer, matchers) &&
|
|
121
133
|
cachedPathToRegExp(pattern).test(layer.path)
|
|
@@ -134,12 +146,12 @@ function createWrapRouterMethod (name) {
|
|
|
134
146
|
}
|
|
135
147
|
|
|
136
148
|
function wrapMethod (original) {
|
|
137
|
-
return shimmer.wrapFunction(original, original => function methodWithTrace (fn) {
|
|
149
|
+
return shimmer.wrapFunction(original, original => function methodWithTrace (fn, ...otherArgs) {
|
|
138
150
|
let offset = 0
|
|
139
151
|
if (this.stack) {
|
|
140
152
|
offset = Array.isArray(this.stack) ? this.stack.length : 1
|
|
141
153
|
}
|
|
142
|
-
const router = original.
|
|
154
|
+
const router = original.call(this, fn, ...otherArgs)
|
|
143
155
|
|
|
144
156
|
if (typeof this.stack === 'function') {
|
|
145
157
|
this.stack = [{ handle: this.stack }]
|
|
@@ -149,6 +161,51 @@ function createWrapRouterMethod (name) {
|
|
|
149
161
|
routeAddedChannel.publish({ topOfStackFunc: methodWithTrace, layer: this.stack.at(-1) })
|
|
150
162
|
}
|
|
151
163
|
|
|
164
|
+
// Publish only if this router was mounted by app.use() (prevents early '/sub/...')
|
|
165
|
+
if (routeAddedChannel.hasSubscribers && isAppMounted(this) && this.stack?.length > offset) {
|
|
166
|
+
// Handle nested router mounting for 'use' method
|
|
167
|
+
if (original.name === 'use' && otherArgs.length >= 1) {
|
|
168
|
+
const { mountPaths, startIdx } = extractMountPaths(fn)
|
|
169
|
+
|
|
170
|
+
if (mountPaths.length) {
|
|
171
|
+
const parentPaths = getRouterMountPaths(this)
|
|
172
|
+
const callArgs = [fn, ...otherArgs]
|
|
173
|
+
|
|
174
|
+
for (let i = startIdx; i < callArgs.length; i++) {
|
|
175
|
+
const nestedRouter = callArgs[i]
|
|
176
|
+
|
|
177
|
+
if (!nestedRouter || typeof nestedRouter !== 'function') continue
|
|
178
|
+
|
|
179
|
+
for (const parentPath of parentPaths) {
|
|
180
|
+
for (const normalizedMountPath of mountPaths) {
|
|
181
|
+
const fullMountPath = joinPath(parentPath, normalizedMountPath)
|
|
182
|
+
if (fullMountPath === null) continue
|
|
183
|
+
|
|
184
|
+
setRouterMountPath(nestedRouter, fullMountPath)
|
|
185
|
+
collectRoutesFromRouter(nestedRouter, fullMountPath)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const mountPaths = getRouterMountPaths(this)
|
|
193
|
+
|
|
194
|
+
if (mountPaths.length) {
|
|
195
|
+
const layer = this.stack.at(-1)
|
|
196
|
+
|
|
197
|
+
if (layer?.route) {
|
|
198
|
+
const route = layer.route
|
|
199
|
+
|
|
200
|
+
const fullPaths = mountPaths.flatMap(mountPath => getRouteFullPaths(route, mountPath))
|
|
201
|
+
|
|
202
|
+
wrapRouteMethodsAndPublish(route, fullPaths, (payload) => {
|
|
203
|
+
routeAddedChannel.publish(payload)
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
152
209
|
if (this.stack.length > offset) {
|
|
153
210
|
wrapStack(this.stack.slice(offset), extractMatchers(fn))
|
|
154
211
|
}
|