dd-trace 6.20.1 → 6.21.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/index.d.ts +91 -3
- package/package.json +2 -2
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +57 -4
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
- package/packages/datadog-instrumentations/src/jest.js +119 -1
- package/packages/datadog-instrumentations/src/mocha/utils.js +124 -36
- package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +61 -34
- package/packages/datadog-instrumentations/src/mocha/worker.js +6 -3
- package/packages/datadog-instrumentations/src/playwright.js +3 -0
- package/packages/datadog-instrumentations/src/vitest-main.js +0 -2
- package/packages/datadog-instrumentations/src/webdriverio.js +115 -15
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +17 -0
- package/packages/datadog-plugin-mocha/src/index.js +216 -29
- package/packages/datadog-plugin-mocha/src/webdriverio-video.js +449 -0
- package/packages/dd-trace/src/debugger/constants.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +5 -3
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +17 -12
- package/packages/dd-trace/src/debugger/devtools_client/send.js +6 -2
- package/packages/dd-trace/src/debugger/devtools_client/worker-error.js +21 -0
- package/packages/dd-trace/src/debugger/index.js +24 -2
- package/packages/dd-trace/src/debugger/probe_sampler.js +108 -11
- package/packages/dd-trace/src/exporters/common/request.js +1 -0
- package/packages/dd-trace/src/exporters/common/url.js +1 -1
- package/packages/dd-trace/src/id.js +1 -4
- package/packages/dd-trace/src/llmobs/agent-manifest.js +296 -0
- package/packages/dd-trace/src/llmobs/constants/tags.js +1 -0
- package/packages/dd-trace/src/llmobs/sdk.js +23 -6
- package/packages/dd-trace/src/llmobs/span_processor.js +17 -6
- package/packages/dd-trace/src/llmobs/tagger.js +54 -1
- package/packages/dd-trace/src/llmobs/writers/base.js +3 -1
- package/packages/dd-trace/src/llmobs/writers/util.js +3 -1
- package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +30 -1
- package/packages/dd-trace/src/openfeature/configuration_source.js +17 -2
- package/packages/dd-trace/src/openfeature/debug-logging-hook.js +25 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +29 -0
- package/packages/dd-trace/src/openfeature/remote_config.js +9 -0
- package/packages/dd-trace/src/openfeature/writers/util.js +43 -4
- package/packages/dd-trace/src/opentelemetry/resource-attributes.js +1 -1
- package/packages/dd-trace/src/otel-thread-ctx.js +2 -2
- package/packages/dd-trace/src/profiling/profilers/wall.js +4 -10
- package/packages/dd-trace/src/proxy.js +2 -0
- package/packages/dd-trace/src/serverless.js +5 -0
- package/packages/dd-trace/src/storage-channels.js +112 -46
|
@@ -26,6 +26,7 @@ const shimmer = require('../../../datadog-shimmer')
|
|
|
26
26
|
const testStartCh = channel('ci:mocha:test:start')
|
|
27
27
|
const testFinishCh = channel('ci:mocha:test:finish')
|
|
28
28
|
const testDiWaitCh = channel('ci:mocha:test:di:wait')
|
|
29
|
+
const testAfterEachFinishCh = channel('ci:mocha:after-each:finish')
|
|
29
30
|
// after a test has failed, we'll publish to this channel
|
|
30
31
|
const testRetryCh = channel('ci:mocha:test:retry')
|
|
31
32
|
const errorCh = channel('ci:mocha:test:error')
|
|
@@ -54,6 +55,7 @@ const testsQuarantined = new Set()
|
|
|
54
55
|
const testsStatuses = new Map()
|
|
55
56
|
const efdRetryCountByTestFullName = new Map()
|
|
56
57
|
const efdSlowAbortedTests = new Set()
|
|
58
|
+
const suiteHookFailureSuites = new Set()
|
|
57
59
|
let dynamicAtrRetryCountByTest = new WeakMap()
|
|
58
60
|
const attemptToFixExecutions = new Map()
|
|
59
61
|
const isMochaWorker = !!getEnvironmentVariable('MOCHA_WORKER_ID')
|
|
@@ -343,6 +345,7 @@ function resetRunState (rootSuite) {
|
|
|
343
345
|
testsStatuses.clear()
|
|
344
346
|
efdRetryCountByTestFullName.clear()
|
|
345
347
|
efdSlowAbortedTests.clear()
|
|
348
|
+
suiteHookFailureSuites.clear()
|
|
346
349
|
dynamicAtrRetryCountByTest = new WeakMap()
|
|
347
350
|
attemptToFixExecutions.clear()
|
|
348
351
|
loggedAttemptToFixTests.clear()
|
|
@@ -379,7 +382,9 @@ function resetRunState (rootSuite) {
|
|
|
379
382
|
delete test._ddTestFinishPublished
|
|
380
383
|
delete test._ddIsFinalAttempt
|
|
381
384
|
delete test._ddHookFailed
|
|
385
|
+
delete test._ddSuppressSyntheticTestLifecycle
|
|
382
386
|
delete test._ddPendingRetry
|
|
387
|
+
delete test._ddPendingHookFailureFinish
|
|
383
388
|
delete test._ddReporterStartFailed
|
|
384
389
|
delete test._ddReporterTerminalFailed
|
|
385
390
|
originalTests.push(test)
|
|
@@ -439,6 +444,21 @@ function recordTestAttempt (testsByFullName, test) {
|
|
|
439
444
|
}
|
|
440
445
|
}
|
|
441
446
|
|
|
447
|
+
function recordRetryTrackedAttempt (test) {
|
|
448
|
+
const {
|
|
449
|
+
_ddIsAttemptToFix: isAttemptToFix,
|
|
450
|
+
_ddIsModified: isModified,
|
|
451
|
+
_ddIsNew: isNew,
|
|
452
|
+
} = test
|
|
453
|
+
|
|
454
|
+
if (isNew) {
|
|
455
|
+
recordTestAttempt(newTests, test)
|
|
456
|
+
}
|
|
457
|
+
if (!isAttemptToFix && (isNew || isModified)) {
|
|
458
|
+
recordTestAttempt(efdTests, test)
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
442
462
|
function getTestStatus (test) {
|
|
443
463
|
if (test.isPending()) {
|
|
444
464
|
return 'skip'
|
|
@@ -476,6 +496,15 @@ function startTestFinish (test) {
|
|
|
476
496
|
return true
|
|
477
497
|
}
|
|
478
498
|
|
|
499
|
+
function hasHandledHookFailure (test) {
|
|
500
|
+
return test._ddHookFailed && (test._ddTestFinishPublished || test._ddPendingHookFailureFinish)
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function suppressSyntheticTestLifecycle (test) {
|
|
504
|
+
test._ddSuppressSyntheticTestLifecycle = true
|
|
505
|
+
test._ddTestFinishPublished = true
|
|
506
|
+
}
|
|
507
|
+
|
|
479
508
|
/**
|
|
480
509
|
* Copies Test Management metadata from Mocha's original runnable to its native retry clone.
|
|
481
510
|
* @param {{
|
|
@@ -571,6 +600,14 @@ function runnableWrapper (RunnablePackage, libraryConfig) {
|
|
|
571
600
|
}
|
|
572
601
|
}
|
|
573
602
|
|
|
603
|
+
const failedTestReplayPromise = (isTestHook || this.type === 'test') &&
|
|
604
|
+
test._retriedTest?._ddFailedTestReplayPromise
|
|
605
|
+
if (failedTestReplayPromise) {
|
|
606
|
+
delete test._retriedTest._ddFailedTestReplayPromise
|
|
607
|
+
const runRunnable = () => run.apply(this, args)
|
|
608
|
+
return failedTestReplayPromise.then(runRunnable, runRunnable)
|
|
609
|
+
}
|
|
610
|
+
|
|
574
611
|
return run.apply(this, args)
|
|
575
612
|
})
|
|
576
613
|
return RunnablePackage
|
|
@@ -578,6 +615,8 @@ function runnableWrapper (RunnablePackage, libraryConfig) {
|
|
|
578
615
|
|
|
579
616
|
function getOnTestHandler (isMain) {
|
|
580
617
|
return function (test) {
|
|
618
|
+
if (test._ddSuppressSyntheticTestLifecycle || hasHandledHookFailure(test)) return
|
|
619
|
+
|
|
581
620
|
const testStartLine = testToStartLine.get(test)
|
|
582
621
|
|
|
583
622
|
// This may be a retry. If this is the case, `test.fn` is already wrapped,
|
|
@@ -586,6 +625,12 @@ function getOnTestHandler (isMain) {
|
|
|
586
625
|
|
|
587
626
|
inheritDatadogPropertiesFromRetriedTest(test)
|
|
588
627
|
|
|
628
|
+
if (isInFailedSuite(test.parent)) {
|
|
629
|
+
recordRetryTrackedAttempt(test)
|
|
630
|
+
suppressSyntheticTestLifecycle(test)
|
|
631
|
+
return
|
|
632
|
+
}
|
|
633
|
+
|
|
589
634
|
const {
|
|
590
635
|
file: testSuiteAbsolutePath,
|
|
591
636
|
title,
|
|
@@ -636,13 +681,8 @@ function getOnTestHandler (isMain) {
|
|
|
636
681
|
loggedAttemptToFixTests
|
|
637
682
|
)
|
|
638
683
|
}
|
|
639
|
-
//
|
|
640
|
-
|
|
641
|
-
recordTestAttempt(newTests, test)
|
|
642
|
-
}
|
|
643
|
-
if (!isAttemptToFix && (isNew || isModified)) {
|
|
644
|
-
recordTestAttempt(efdTests, test)
|
|
645
|
-
}
|
|
684
|
+
// Keep retry accounting aligned with Mocha's runner failure counts.
|
|
685
|
+
recordRetryTrackedAttempt(test)
|
|
646
686
|
|
|
647
687
|
if (!isAttemptToFix && isDisabled) {
|
|
648
688
|
if (!originalPendingByTest.has(test)) {
|
|
@@ -657,6 +697,14 @@ function getOnTestHandler (isMain) {
|
|
|
657
697
|
}
|
|
658
698
|
}
|
|
659
699
|
|
|
700
|
+
function isInFailedSuite (suite) {
|
|
701
|
+
while (suite) {
|
|
702
|
+
if (suiteHookFailureSuites.has(suite)) return true
|
|
703
|
+
suite = suite.parent
|
|
704
|
+
}
|
|
705
|
+
return false
|
|
706
|
+
}
|
|
707
|
+
|
|
660
708
|
function getFinalStatus ({
|
|
661
709
|
status,
|
|
662
710
|
hasFailedAllRetries,
|
|
@@ -842,7 +890,7 @@ function getTestFinishInfo (test, status, config, error) {
|
|
|
842
890
|
|
|
843
891
|
function getOnTestEndHandler (config, finalAttemptHandlers) {
|
|
844
892
|
return async function (test) {
|
|
845
|
-
if (test._ddShouldSkipEfdRetry) {
|
|
893
|
+
if (test._ddShouldSkipEfdRetry || test._ddSuppressSyntheticTestLifecycle || hasHandledHookFailure(test)) {
|
|
846
894
|
return
|
|
847
895
|
}
|
|
848
896
|
const shouldWaitForHitProbe = test._retriedTest?._ddShouldWaitForHitProbe
|
|
@@ -880,24 +928,33 @@ function getOnTestEndHandler (config, finalAttemptHandlers) {
|
|
|
880
928
|
finalAttemptHandlers?.onStart?.(test)
|
|
881
929
|
}
|
|
882
930
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
931
|
+
const finish = () => {
|
|
932
|
+
if (shouldFinishTest) {
|
|
933
|
+
test._ddTestFinishPublished = true
|
|
934
|
+
testFinishCh.publish({
|
|
935
|
+
status,
|
|
936
|
+
hasBeenRetried: isMochaRetry(test),
|
|
937
|
+
isLastRetry: getIsLastRetry(test),
|
|
938
|
+
...testFinishInfo,
|
|
939
|
+
...ctx.currentStore,
|
|
940
|
+
})
|
|
941
|
+
}
|
|
886
942
|
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
status,
|
|
891
|
-
hasBeenRetried: isMochaRetry(test),
|
|
892
|
-
isLastRetry: getIsLastRetry(test),
|
|
893
|
-
...testFinishInfo,
|
|
894
|
-
...ctx.currentStore,
|
|
895
|
-
})
|
|
943
|
+
if (isFinalAttempt) {
|
|
944
|
+
finalAttemptHandlers?.onFinish?.(test)
|
|
945
|
+
}
|
|
896
946
|
}
|
|
897
947
|
|
|
898
|
-
if (
|
|
899
|
-
|
|
948
|
+
if (shouldWaitForHitProbe) {
|
|
949
|
+
const waitForHitProbePromise = waitForHitProbe()
|
|
950
|
+
if (shouldFinishTest && config.isTestFailureVideosEnabled && !test.isPending()) {
|
|
951
|
+
// Mocha does not await test-end listeners. Its hookUp continuation must wait even without teardown hooks.
|
|
952
|
+
test._ddDeferredHookEnd = { waitForHitProbePromise, publishTestFinish: finish }
|
|
953
|
+
return
|
|
954
|
+
}
|
|
955
|
+
await waitForHitProbePromise
|
|
900
956
|
}
|
|
957
|
+
finish()
|
|
901
958
|
}
|
|
902
959
|
}
|
|
903
960
|
|
|
@@ -978,17 +1035,30 @@ function finishDeferredHookEnd (test) {
|
|
|
978
1035
|
* @returns {unknown}
|
|
979
1036
|
*/
|
|
980
1037
|
function runFailedTestReplayHookUpCallback (fn, test, failedTestReplayPromise, hookThis, args) {
|
|
1038
|
+
if (test._ddPendingHookFailureFinish) {
|
|
1039
|
+
// Mocha restarts teardown at the failed suite's parent. Only the last hookUp completes the attempt.
|
|
1040
|
+
const failedSuite = /** @type {import('mocha').Suite|undefined} */ (args[1])
|
|
1041
|
+
if (args[0] && failedSuite?.parent) return fn.apply(hookThis, /** @type {unknown[]} */ (args))
|
|
1042
|
+
const finish = test._ddPendingHookFailureFinish
|
|
1043
|
+
delete test._ddPendingHookFailureFinish
|
|
1044
|
+
finish()
|
|
1045
|
+
}
|
|
981
1046
|
const pendingRetry = test._ddPendingRetry
|
|
982
1047
|
if (pendingRetry) {
|
|
983
1048
|
delete test._ddPendingRetry
|
|
984
1049
|
failedTestReplayPromise = pendingRetry()
|
|
985
1050
|
}
|
|
1051
|
+
const continueAfterCapture = () => {
|
|
1052
|
+
const context = { onDone: () => fn.apply(hookThis, args), isWaiting: false }
|
|
1053
|
+
testAfterEachFinishCh.publish(context)
|
|
1054
|
+
return context.isWaiting ? undefined : context.onDone()
|
|
1055
|
+
}
|
|
986
1056
|
const continueAfterProbe = () => {
|
|
987
1057
|
const deferredHookEndPromise = finishDeferredHookEnd(test)
|
|
988
1058
|
if (deferredHookEndPromise) {
|
|
989
|
-
return deferredHookEndPromise.then(
|
|
1059
|
+
return deferredHookEndPromise.then(continueAfterCapture, continueAfterCapture)
|
|
990
1060
|
}
|
|
991
|
-
return
|
|
1061
|
+
return continueAfterCapture()
|
|
992
1062
|
}
|
|
993
1063
|
|
|
994
1064
|
if (failedTestReplayPromise) {
|
|
@@ -1037,15 +1107,23 @@ function getOnFailHandler (isMain, config) {
|
|
|
1037
1107
|
const testFile = testOrHook.file
|
|
1038
1108
|
let test = testOrHook
|
|
1039
1109
|
const isHook = testOrHook.type === 'hook'
|
|
1110
|
+
const isBeforeEachHook = isHook && testOrHook.parent?._beforeEach?.includes(testOrHook)
|
|
1111
|
+
const isAfterEachHook = isHook && testOrHook.parent?._afterEach?.includes(testOrHook)
|
|
1112
|
+
const isTestHook = isBeforeEachHook || isAfterEachHook
|
|
1113
|
+
if (isHook && !isTestHook && testOrHook.parent) {
|
|
1114
|
+
suiteHookFailureSuites.add(testOrHook.parent)
|
|
1115
|
+
}
|
|
1040
1116
|
if (isHook && testOrHook.ctx) {
|
|
1041
1117
|
test = testOrHook.ctx.currentTest
|
|
1042
1118
|
}
|
|
1043
1119
|
let testContext
|
|
1044
1120
|
if (test) {
|
|
1121
|
+
if (!isHook && (test._ddSuppressSyntheticTestLifecycle || hasHandledHookFailure(test))) return
|
|
1122
|
+
|
|
1045
1123
|
testContext = getTestContext(test)
|
|
1046
1124
|
}
|
|
1047
1125
|
if (testContext) {
|
|
1048
|
-
if (
|
|
1126
|
+
if (isTestHook && startTestFinish(test)) {
|
|
1049
1127
|
delete test._ddPendingRetry
|
|
1050
1128
|
const hookError = new Error(`${testOrHook.fullTitle()}: ${err.message}`, { cause: err })
|
|
1051
1129
|
hookError.name = err.name
|
|
@@ -1054,17 +1132,27 @@ function getOnFailHandler (isMain, config) {
|
|
|
1054
1132
|
errorCh.runStores(testContext, () => {})
|
|
1055
1133
|
// Mocha marks the hook failed, so record the test failure before computing final metadata.
|
|
1056
1134
|
test._ddHookFailed = true
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1135
|
+
if (isBeforeEachHook && testOrHook.parent) {
|
|
1136
|
+
suiteHookFailureSuites.add(testOrHook.parent)
|
|
1137
|
+
}
|
|
1138
|
+
const finish = () => {
|
|
1139
|
+
const testFinishInfo = getTestFinishInfo(test, 'fail', config, testContext.err)
|
|
1140
|
+
if (testFinishInfo.finalStatus !== undefined) {
|
|
1141
|
+
test._ddIsFinalAttempt = true
|
|
1142
|
+
}
|
|
1143
|
+
test._ddTestFinishPublished = true
|
|
1144
|
+
testFinishCh.publish({
|
|
1145
|
+
status: 'fail',
|
|
1146
|
+
hasBeenRetried: isMochaRetry(test),
|
|
1147
|
+
...testFinishInfo,
|
|
1148
|
+
...testContext.currentStore,
|
|
1149
|
+
})
|
|
1150
|
+
}
|
|
1151
|
+
if (config.isTestFailureVideosEnabled) {
|
|
1152
|
+
test._ddPendingHookFailureFinish = finish
|
|
1153
|
+
} else {
|
|
1154
|
+
finish()
|
|
1060
1155
|
}
|
|
1061
|
-
test._ddTestFinishPublished = true
|
|
1062
|
-
testFinishCh.publish({
|
|
1063
|
-
status: 'fail',
|
|
1064
|
-
hasBeenRetried: isMochaRetry(test),
|
|
1065
|
-
...testFinishInfo,
|
|
1066
|
-
...testContext.currentStore,
|
|
1067
|
-
})
|
|
1068
1156
|
} else {
|
|
1069
1157
|
testContext.err = err
|
|
1070
1158
|
errorCh.runStores(testContext, () => {})
|
|
@@ -1105,7 +1193,7 @@ function getOnTestRetryHandler (config) {
|
|
|
1105
1193
|
test._retries = dynamicCount
|
|
1106
1194
|
}
|
|
1107
1195
|
|
|
1108
|
-
if (config.isFlakyTestRetriesEnabled && getAfterEachHooks(test).length) {
|
|
1196
|
+
if ((config.isFlakyTestRetriesEnabled || config.isTestFailureVideosEnabled) && getAfterEachHooks(test).length) {
|
|
1109
1197
|
// Mocha queues retries before afterEach; a hook failure can still cancel the retry.
|
|
1110
1198
|
test._ddPendingRetry = () => publishTestRetry(test, err, config)
|
|
1111
1199
|
return
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { clearTimeout, setTimeout } = require('node:timers')
|
|
4
|
+
|
|
3
5
|
const {
|
|
4
6
|
createWebdriverioWorkerMessage,
|
|
5
7
|
WEBDRIVERIO_WORKER_ENV,
|
|
@@ -11,6 +13,9 @@ const { FINAL_FLUSH_TIMEOUT } = require('../../../dd-trace/src/ci-visibility/fin
|
|
|
11
13
|
const SCREENSHOT_UPLOAD = 'dd:test-optimization:webdriverio:screenshot:upload'
|
|
12
14
|
const SCREENSHOT_UPLOAD_RESPONSE = 'dd:test-optimization:webdriverio:screenshot:upload:response'
|
|
13
15
|
const SCREENSHOT_UPLOAD_TIMEOUT_MS = FINAL_FLUSH_TIMEOUT + 5000
|
|
16
|
+
const VIDEO_UPLOAD = 'dd:test-optimization:webdriverio:video:upload'
|
|
17
|
+
const VIDEO_UPLOAD_RESPONSE = 'dd:test-optimization:webdriverio:video:upload:response'
|
|
18
|
+
const VIDEO_UPLOAD_TIMEOUT_MS = 5 * FINAL_FLUSH_TIMEOUT + 5000
|
|
14
19
|
|
|
15
20
|
/**
|
|
16
21
|
* Sends a message over WebdriverIO's worker IPC envelope.
|
|
@@ -34,95 +39,113 @@ function sendWebdriverioWorkerMessage (message, onError, onDone) {
|
|
|
34
39
|
})
|
|
35
40
|
}
|
|
36
41
|
|
|
37
|
-
let
|
|
38
|
-
const
|
|
42
|
+
let mediaUploadRequestId = 0
|
|
43
|
+
const mediaUploadRequests = new Map()
|
|
39
44
|
|
|
40
45
|
/**
|
|
41
|
-
* Removes shared
|
|
46
|
+
* Removes shared media response listeners when there are no pending requests.
|
|
42
47
|
*
|
|
43
48
|
*/
|
|
44
|
-
function
|
|
45
|
-
if (
|
|
49
|
+
function removeMediaUploadListeners () {
|
|
50
|
+
if (mediaUploadRequests.size !== 0) return
|
|
46
51
|
|
|
47
|
-
process.off('message',
|
|
48
|
-
process.off('disconnect',
|
|
52
|
+
process.off('message', onMediaUploadResponse)
|
|
53
|
+
process.off('disconnect', onMediaUploadDisconnect)
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
/**
|
|
52
|
-
* Completes one pending
|
|
57
|
+
* Completes one pending media upload request.
|
|
53
58
|
*
|
|
54
59
|
* @param {string} requestId
|
|
55
60
|
* @param {Error} [error]
|
|
56
61
|
*/
|
|
57
|
-
function
|
|
58
|
-
const request =
|
|
62
|
+
function finishMediaUploadRequest (requestId, error) {
|
|
63
|
+
const request = mediaUploadRequests.get(requestId)
|
|
59
64
|
if (!request) return
|
|
60
65
|
|
|
61
|
-
|
|
66
|
+
mediaUploadRequests.delete(requestId)
|
|
62
67
|
clearTimeout(request.timeout)
|
|
63
|
-
|
|
68
|
+
removeMediaUploadListeners()
|
|
64
69
|
request.onDone(error)
|
|
65
70
|
}
|
|
66
71
|
|
|
67
72
|
/**
|
|
68
|
-
* Dispatches one coordinator
|
|
73
|
+
* Dispatches one coordinator media response to its pending request.
|
|
69
74
|
*
|
|
70
75
|
* @param {object} message
|
|
71
76
|
*/
|
|
72
|
-
function
|
|
73
|
-
if (message?.name !== SCREENSHOT_UPLOAD_RESPONSE) return
|
|
77
|
+
function onMediaUploadResponse (message) {
|
|
78
|
+
if (message?.name !== SCREENSHOT_UPLOAD_RESPONSE && message?.name !== VIDEO_UPLOAD_RESPONSE) return
|
|
74
79
|
|
|
75
80
|
const { error: errorMessage, requestId } = message.content || {}
|
|
76
81
|
if (!requestId) return
|
|
77
82
|
|
|
78
|
-
|
|
83
|
+
finishMediaUploadRequest(requestId, errorMessage ? new Error(errorMessage) : undefined)
|
|
79
84
|
}
|
|
80
85
|
|
|
81
86
|
/**
|
|
82
|
-
* Fails every pending
|
|
87
|
+
* Fails every pending media upload after coordinator disconnect.
|
|
83
88
|
*
|
|
84
89
|
*/
|
|
85
|
-
function
|
|
86
|
-
for (const requestId of
|
|
87
|
-
|
|
90
|
+
function onMediaUploadDisconnect () {
|
|
91
|
+
for (const requestId of mediaUploadRequests.keys()) {
|
|
92
|
+
finishMediaUploadRequest(
|
|
88
93
|
requestId,
|
|
89
|
-
new Error('WebdriverIO coordinator disconnected during
|
|
94
|
+
new Error('WebdriverIO coordinator disconnected during media upload')
|
|
90
95
|
)
|
|
91
96
|
}
|
|
92
97
|
}
|
|
93
98
|
|
|
94
99
|
/**
|
|
95
|
-
* Requests one
|
|
100
|
+
* Requests one media upload from the WebdriverIO coordinator.
|
|
96
101
|
*
|
|
97
|
-
* @param {object} content -
|
|
102
|
+
* @param {object} content - Upload metadata
|
|
103
|
+
* @param {'screenshot'|'video'} kind
|
|
98
104
|
* @param {(error?: Error) => void} onDone - Upload completion callback
|
|
99
105
|
*/
|
|
100
|
-
function
|
|
101
|
-
const requestId = `${process.pid}-${++
|
|
106
|
+
function requestWebdriverioMediaUpload (content, kind, onDone) {
|
|
107
|
+
const requestId = `${process.pid}-${++mediaUploadRequestId}`
|
|
102
108
|
const timeout = setTimeout(() => {
|
|
103
|
-
|
|
104
|
-
}, SCREENSHOT_UPLOAD_TIMEOUT_MS)
|
|
109
|
+
finishMediaUploadRequest(requestId, new Error(`WebdriverIO ${kind} upload timed out`))
|
|
110
|
+
}, kind === 'video' ? VIDEO_UPLOAD_TIMEOUT_MS : SCREENSHOT_UPLOAD_TIMEOUT_MS)
|
|
105
111
|
timeout.unref?.()
|
|
106
|
-
if (
|
|
107
|
-
process.on('message',
|
|
108
|
-
process.once('disconnect',
|
|
112
|
+
if (mediaUploadRequests.size === 0) {
|
|
113
|
+
process.on('message', onMediaUploadResponse)
|
|
114
|
+
process.once('disconnect', onMediaUploadDisconnect)
|
|
109
115
|
}
|
|
110
|
-
|
|
116
|
+
mediaUploadRequests.set(requestId, { onDone, timeout })
|
|
111
117
|
sendWebdriverioWorkerMessage({
|
|
112
118
|
origin: 'datadog',
|
|
113
|
-
name: SCREENSHOT_UPLOAD,
|
|
119
|
+
name: kind === 'video' ? VIDEO_UPLOAD : SCREENSHOT_UPLOAD,
|
|
114
120
|
content: { ...content, requestId },
|
|
115
|
-
}, error =>
|
|
121
|
+
}, error => finishMediaUploadRequest(
|
|
116
122
|
requestId,
|
|
117
|
-
error || new Error(
|
|
123
|
+
error || new Error(`WebdriverIO ${kind} upload IPC failed`)
|
|
118
124
|
))
|
|
119
125
|
}
|
|
120
126
|
|
|
127
|
+
/**
|
|
128
|
+
* @param {object} content
|
|
129
|
+
* @param {(error?: Error) => void} onDone
|
|
130
|
+
*/
|
|
131
|
+
function requestWebdriverioScreenshotUpload (content, onDone) {
|
|
132
|
+
requestWebdriverioMediaUpload(content, 'screenshot', onDone)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @param {object} content
|
|
137
|
+
* @param {(error?: Error) => void} onDone
|
|
138
|
+
*/
|
|
139
|
+
function requestWebdriverioVideoUpload (content, onDone) {
|
|
140
|
+
requestWebdriverioMediaUpload(content, 'video', onDone)
|
|
141
|
+
}
|
|
142
|
+
|
|
121
143
|
module.exports = {
|
|
122
144
|
CONFIGURATION_REQUEST: 'dd:test-optimization:webdriverio:configuration:request',
|
|
123
145
|
CONFIGURATION_RESPONSE: 'dd:test-optimization:webdriverio:configuration:response',
|
|
124
146
|
createWebdriverioWorkerMessage,
|
|
125
147
|
requestWebdriverioScreenshotUpload,
|
|
148
|
+
requestWebdriverioVideoUpload,
|
|
126
149
|
SCREENSHOT_UPLOAD,
|
|
127
150
|
SCREENSHOT_UPLOAD_RESPONSE,
|
|
128
151
|
SCREENSHOT_UPLOAD_TIMEOUT_MS,
|
|
@@ -130,6 +153,10 @@ module.exports = {
|
|
|
130
153
|
SUITE_FINISH: 'dd:test-optimization:webdriverio:test-suite:finish',
|
|
131
154
|
WORKER_READY: 'dd:test-optimization:webdriverio:worker:ready',
|
|
132
155
|
WORKER_READY_RESPONSE: 'dd:test-optimization:webdriverio:worker:ready:response',
|
|
156
|
+
VIDEO_UPLOAD_FLUSH: 'dd:test-optimization:webdriverio:video:flush',
|
|
157
|
+
VIDEO_UPLOAD,
|
|
158
|
+
VIDEO_UPLOAD_RESPONSE,
|
|
159
|
+
VIDEO_UPLOAD_TIMEOUT_MS,
|
|
133
160
|
WEBDRIVERIO_WORKER_ENV,
|
|
134
161
|
WEBDRIVERIO_WORKER_EVENT,
|
|
135
162
|
WEBDRIVERIO_WORKER_ORIGIN,
|
|
@@ -416,7 +416,7 @@ function wrapMochaRun (Mocha, frameworkVersion) {
|
|
|
416
416
|
})
|
|
417
417
|
}
|
|
418
418
|
filterSkippedFiles(runner, skippedFiles)
|
|
419
|
-
if (isFailedTestReplayEnabled() || config.isFlakyTestRetriesEnabled) {
|
|
419
|
+
if (isFailedTestReplayEnabled() || config.isFlakyTestRetriesEnabled || config.isTestFailureVideosEnabled) {
|
|
420
420
|
patchFailedTestReplayHookUp(runner.constructor)
|
|
421
421
|
}
|
|
422
422
|
configurationReady = true
|
|
@@ -453,7 +453,7 @@ addHook({
|
|
|
453
453
|
if (!workerFinishCh.hasSubscribers) {
|
|
454
454
|
return run.apply(this, args)
|
|
455
455
|
}
|
|
456
|
-
if (isFailedTestReplayEnabled() || config.isFlakyTestRetriesEnabled) {
|
|
456
|
+
if (isFailedTestReplayEnabled() || config.isFlakyTestRetriesEnabled || config.isTestFailureVideosEnabled) {
|
|
457
457
|
patchFailedTestReplayHookUp(Runner)
|
|
458
458
|
}
|
|
459
459
|
if (isWebdriverioWorker) {
|
|
@@ -469,7 +469,10 @@ addHook({
|
|
|
469
469
|
const onRunDone = args[0]
|
|
470
470
|
if (isWebdriverioWorker && typeof onRunDone === 'function') {
|
|
471
471
|
args[0] = (...onRunDoneArgs) => {
|
|
472
|
-
|
|
472
|
+
const adjustedFailures = Number.isFinite(this.failures)
|
|
473
|
+
? Math.max(0, this.failures)
|
|
474
|
+
: onRunDoneArgs[0]
|
|
475
|
+
finishWebdriverioWorker(this, () => onRunDone(adjustedFailures, ...onRunDoneArgs.slice(1)))
|
|
473
476
|
}
|
|
474
477
|
} else {
|
|
475
478
|
// Flush after the worker finishes its Mocha run, including grouped spec files.
|
|
@@ -2657,11 +2657,14 @@ function instrumentWorkerMainMethods (workerMain) {
|
|
|
2657
2657
|
}
|
|
2658
2658
|
|
|
2659
2659
|
// In cases where there is no afterEach hook with _ddHook, we need to add one
|
|
2660
|
+
// Playwright 1.64 builds the hook step stack from hook.location, so an undefined
|
|
2661
|
+
// location ends up as [undefined] and breaks trace writing
|
|
2660
2662
|
if (!existAfterEachHook) {
|
|
2661
2663
|
test.parent._hooks.push({
|
|
2662
2664
|
type: 'afterEach',
|
|
2663
2665
|
fn: cleanupRumPages,
|
|
2664
2666
|
title: 'afterEach hook',
|
|
2667
|
+
location: test.location,
|
|
2665
2668
|
_ddHook: true,
|
|
2666
2669
|
})
|
|
2667
2670
|
}
|
|
@@ -2239,8 +2239,6 @@ addHook({
|
|
|
2239
2239
|
versions: ['>=5.0.0'],
|
|
2240
2240
|
filePattern: 'dist/chunks/index.*',
|
|
2241
2241
|
}, (vitestPackage, frameworkVersion) => {
|
|
2242
|
-
if (!getVitestExport(vitestPackage)) return vitestPackage
|
|
2243
|
-
|
|
2244
2242
|
wrapVitestInternals(vitestPackage, frameworkVersion)
|
|
2245
2243
|
|
|
2246
2244
|
const typecheckPoolWorker = getTypecheckPoolWorkerExport(vitestPackage)
|