dd-trace 6.20.0 → 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 +6 -7
- 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/config/supported-configurations.json +2 -2
- 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/plugins/util/url.js +6 -1
- 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
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { performance } = require('node:perf_hooks')
|
|
4
|
-
const {
|
|
4
|
+
const { clearTimeout, setTimeout } = require('node:timers')
|
|
5
5
|
|
|
6
6
|
const { channel } = require('dc-polyfill')
|
|
7
7
|
|
|
8
8
|
const CiPlugin = require('../../dd-trace/src/plugins/ci_plugin')
|
|
9
9
|
const { storage } = require('../../datadog-core')
|
|
10
|
-
const { getEnvironmentVariable } = require('../../dd-trace/src/config/helper')
|
|
10
|
+
const { getEnvironmentVariable, getValueFromEnvSources } = require('../../dd-trace/src/config/helper')
|
|
11
11
|
const {
|
|
12
12
|
getEfdRetryCountForDuration,
|
|
13
13
|
hasEfdRetries,
|
|
@@ -15,16 +15,20 @@ const {
|
|
|
15
15
|
const {
|
|
16
16
|
getDynamicAtrRetryCount,
|
|
17
17
|
} = require('../../dd-trace/src/ci-visibility/dynamic-atr-retries')
|
|
18
|
+
const { FINAL_FLUSH_TIMEOUT } = require('../../dd-trace/src/ci-visibility/final-flush')
|
|
18
19
|
const {
|
|
19
20
|
SCREENSHOT_UPLOAD_RESULT_ERROR,
|
|
20
21
|
SCREENSHOT_UPLOAD_RESULT_UPLOADED,
|
|
21
22
|
setScreenshotUploadTags,
|
|
22
23
|
} = require('../../dd-trace/src/ci-visibility/test-screenshot')
|
|
24
|
+
const { setVideoUploadTags } = require('../../dd-trace/src/ci-visibility/test-video')
|
|
23
25
|
const log = require('../../dd-trace/src/log')
|
|
24
26
|
const {
|
|
25
27
|
requestWebdriverioScreenshotUpload,
|
|
28
|
+
requestWebdriverioVideoUpload,
|
|
26
29
|
sendWebdriverioWorkerMessage,
|
|
27
30
|
SUITE_FINISH,
|
|
31
|
+
VIDEO_UPLOAD_FLUSH,
|
|
28
32
|
WEBDRIVERIO_WORKER_ENV,
|
|
29
33
|
} = require('../../datadog-instrumentations/src/mocha/webdriverio-protocol')
|
|
30
34
|
|
|
@@ -83,6 +87,11 @@ const {
|
|
|
83
87
|
TELEMETRY_TEST_SESSION,
|
|
84
88
|
} = require('../../dd-trace/src/ci-visibility/telemetry')
|
|
85
89
|
|
|
90
|
+
// Load the recorder and capture clocks before test code can replace globals with fake timers.
|
|
91
|
+
const createWebdriverioVideo = require('./webdriverio-video')
|
|
92
|
+
|
|
93
|
+
const dateNow = Date.now
|
|
94
|
+
|
|
86
95
|
const jasmineAdapterRunAsyncEndCh = 'tracing:orchestrion:@wdio/jasmine-framework:JasmineAdapter_run:asyncEnd'
|
|
87
96
|
const jasmineDoneCh = 'ci:webdriverio:jasmine:done'
|
|
88
97
|
const jasmineExecuteAsyncEndCh = 'tracing:orchestrion:@wdio/utils:executeAsync:asyncEnd'
|
|
@@ -106,6 +115,7 @@ const WEBDRIVERIO_JASMINE_FAILED_EXPECTATION_COUNT = Symbol('webdriverioJasmineF
|
|
|
106
115
|
const WEBDRIVERIO_JASMINE_FUNCTION_TYPE = Symbol('webdriverioJasmineFunctionType')
|
|
107
116
|
const WEBDRIVERIO_JASMINE_TEST = Symbol('webdriverioJasmineTest')
|
|
108
117
|
const isWebdriverioWorker = !!getEnvironmentVariable(WEBDRIVERIO_WORKER_ENV)
|
|
118
|
+
const shouldRecordWebdriverioVideos = getValueFromEnvSources('DD_TEST_FAILURE_VIDEOS_ENABLED', true) === true
|
|
109
119
|
const PNG_SIGNATURE = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])
|
|
110
120
|
const BASE64_RE = /^(?:[A-Za-z\d+/]{4})*(?:[A-Za-z\d+/]{2}==|[A-Za-z\d+/]{3}=)?$/
|
|
111
121
|
|
|
@@ -121,6 +131,20 @@ const BASE64_RE = /^(?:[A-Za-z\d+/]{4})*(?:[A-Za-z\d+/]{2}==|[A-Za-z\d+/]{3}=)?$
|
|
|
121
131
|
* @property {string|undefined} status
|
|
122
132
|
*/
|
|
123
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Waits for an attempt's final capture, bounded by its existing deadline, without waiting for encoding or upload.
|
|
136
|
+
*
|
|
137
|
+
* @param {ReturnType<typeof createWebdriverioVideo>} video
|
|
138
|
+
* @param {Promise<unknown>} [finishPromise]
|
|
139
|
+
*/
|
|
140
|
+
function waitForWebdriverioCapture (video, finishPromise) {
|
|
141
|
+
if (!video) return finishPromise
|
|
142
|
+
// WDIO awaits its async framework hook, so bridge the recorder's callback before it can start the next attempt.
|
|
143
|
+
/** @type {Promise<void>} */
|
|
144
|
+
const captureWait = new Promise(resolve => video.waitForCapture(resolve))
|
|
145
|
+
return finishPromise ? Promise.all([finishPromise, captureWait]) : captureWait
|
|
146
|
+
}
|
|
147
|
+
|
|
124
148
|
/**
|
|
125
149
|
* Normalizes a WebdriverIO Jasmine spec identifier to a filesystem path.
|
|
126
150
|
*
|
|
@@ -128,7 +152,20 @@ const BASE64_RE = /^(?:[A-Za-z\d+/]{4})*(?:[A-Za-z\d+/]{2}==|[A-Za-z\d+/]{3}=)?$
|
|
|
128
152
|
* @returns {string|undefined}
|
|
129
153
|
*/
|
|
130
154
|
function normalizeJasmineFile (file) {
|
|
131
|
-
return file?.startsWith('file://') ? fileURLToPath(file) : file
|
|
155
|
+
return file?.startsWith('file://') ? require('node:url').fileURLToPath(file) : file
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function getWebdriverioJasmineSpecResult (spec) {
|
|
159
|
+
if (spec?.result) return spec.result
|
|
160
|
+
if (typeof spec?.startedEvent === 'function') return spec.startedEvent()
|
|
161
|
+
if (!spec?.id) return
|
|
162
|
+
return {
|
|
163
|
+
description: spec.description,
|
|
164
|
+
filename: spec.filename,
|
|
165
|
+
fullName: typeof spec.getFullName === 'function' ? spec.getFullName() : spec.description,
|
|
166
|
+
id: spec.id,
|
|
167
|
+
parentSuiteId: spec.parentSuiteId,
|
|
168
|
+
}
|
|
132
169
|
}
|
|
133
170
|
|
|
134
171
|
/**
|
|
@@ -245,14 +282,19 @@ function getJasmineFailureFile (result, specs) {
|
|
|
245
282
|
class MochaPlugin extends CiPlugin {
|
|
246
283
|
static id = 'mocha'
|
|
247
284
|
|
|
285
|
+
#pendingWebdriverioVideos = new Set()
|
|
286
|
+
#webdriverioCapture
|
|
287
|
+
#webdriverioVideoFlushTimer
|
|
288
|
+
|
|
248
289
|
constructor (...args) {
|
|
249
290
|
super(...args)
|
|
250
291
|
|
|
251
292
|
this._testTitleToParams = {}
|
|
252
293
|
this.sourceRoot = process.cwd()
|
|
253
294
|
this._webdriverioScreenshotUploads = new WeakMap()
|
|
254
|
-
this.
|
|
255
|
-
this.
|
|
295
|
+
this._webdriverioVideos = new Map()
|
|
296
|
+
this._pendingWebdriverioMediaUploads = 0
|
|
297
|
+
this._webdriverioMediaUploadCallbacks = []
|
|
256
298
|
|
|
257
299
|
this.addSub('ci:webdriverio:screenshot:capabilities', (ctx) => {
|
|
258
300
|
ctx.enabled = Boolean(
|
|
@@ -283,6 +325,36 @@ class MochaPlugin extends CiPlugin {
|
|
|
283
325
|
exporter.uploadTestScreenshot({ content, traceId, idempotencyKey, capturedAtMs }, onDone)
|
|
284
326
|
})
|
|
285
327
|
|
|
328
|
+
this.addSub('ci:webdriverio:video:capabilities',
|
|
329
|
+
/** @type {import('node:diagnostics_channel').ChannelListener} */ (
|
|
330
|
+
/** @param {{enabled?: boolean}} ctx */
|
|
331
|
+
(ctx) => {
|
|
332
|
+
ctx.enabled = Boolean(
|
|
333
|
+
shouldRecordWebdriverioVideos &&
|
|
334
|
+
this._tracerConfig.testOptimization?.DD_TEST_FAILURE_VIDEOS_ENABLED &&
|
|
335
|
+
(isWebdriverioWorker
|
|
336
|
+
? this.libraryConfig?.isTestFailureVideosEnabled && createWebdriverioVideo.isFfmpegAvailable()
|
|
337
|
+
: this.tracer._exporter?.canUploadTestVideos?.())
|
|
338
|
+
)
|
|
339
|
+
}
|
|
340
|
+
))
|
|
341
|
+
|
|
342
|
+
this.addSub('ci:webdriverio:video:upload',
|
|
343
|
+
/** @type {import('node:diagnostics_channel').ChannelListener} */ (
|
|
344
|
+
/**
|
|
345
|
+
* @param {{filePath: string, traceId: string, idempotencyKey: string, capturedAtMs: number,
|
|
346
|
+
* signal?: AbortSignal, onDone: (error?: Error) => void}} options
|
|
347
|
+
*/
|
|
348
|
+
({ filePath, traceId, idempotencyKey, capturedAtMs, signal, onDone }) => {
|
|
349
|
+
const exporter = this.tracer?._exporter
|
|
350
|
+
if (!exporter?.canUploadTestVideos?.() || !exporter.uploadTestVideo) {
|
|
351
|
+
onDone(new Error('WebdriverIO video upload is not supported by the active Test Optimization transport'))
|
|
352
|
+
return
|
|
353
|
+
}
|
|
354
|
+
exporter.uploadTestVideo({ filePath, traceId, idempotencyKey, capturedAtMs, signal }, onDone)
|
|
355
|
+
}
|
|
356
|
+
))
|
|
357
|
+
|
|
286
358
|
this.addSub('ci:mocha:worker:configuration', ({
|
|
287
359
|
libraryConfig,
|
|
288
360
|
repositoryRoot,
|
|
@@ -306,6 +378,19 @@ class MochaPlugin extends CiPlugin {
|
|
|
306
378
|
'active Test Optimization transport.'
|
|
307
379
|
)
|
|
308
380
|
}
|
|
381
|
+
if (
|
|
382
|
+
testFramework === WEBDRIVERIO_FRAMEWORK &&
|
|
383
|
+
shouldRecordWebdriverioVideos &&
|
|
384
|
+
this._tracerConfig.testOptimization.DD_TEST_FAILURE_VIDEOS_ENABLED &&
|
|
385
|
+
!(isWebdriverioWorker
|
|
386
|
+
? libraryConfig.isTestFailureVideosEnabled
|
|
387
|
+
: this.tracer._exporter?.canUploadTestVideos?.())
|
|
388
|
+
) {
|
|
389
|
+
log.warn(
|
|
390
|
+
'DD_TEST_FAILURE_VIDEOS_ENABLED is true, but WebdriverIO video upload is not supported by the %s',
|
|
391
|
+
'active Test Optimization transport.'
|
|
392
|
+
)
|
|
393
|
+
}
|
|
309
394
|
if (testFrameworkAdapter === WEBDRIVERIO_JASMINE_ADAPTER) {
|
|
310
395
|
this._webdriverioJasmineState = {
|
|
311
396
|
completedTestStatuses: new Map(),
|
|
@@ -481,7 +566,14 @@ class MochaPlugin extends CiPlugin {
|
|
|
481
566
|
this.addSub(jasmineReporterSpecDoneEndCh, (ctx) => {
|
|
482
567
|
if (this.testFrameworkAdapter === WEBDRIVERIO_JASMINE_ADAPTER) {
|
|
483
568
|
const result = ctx.result
|
|
484
|
-
const
|
|
569
|
+
const testResult = ctx.arguments?.[0]
|
|
570
|
+
const test = this._webdriverioJasmineState?.tests.get(testResult?.id)
|
|
571
|
+
const video = this._webdriverioVideos.get(test?.span)
|
|
572
|
+
const finishTestPromise = this.#finishWebdriverioJasmineTest(testResult, ctx.self?._specs)
|
|
573
|
+
// Replay can defer video.finish(), which must start the final capture before we wait for it.
|
|
574
|
+
const finishPromise = finishTestPromise
|
|
575
|
+
? finishTestPromise.then(() => waitForWebdriverioCapture(video))
|
|
576
|
+
: waitForWebdriverioCapture(video)
|
|
485
577
|
if (finishPromise) {
|
|
486
578
|
ctx.result = finishPromise.then(() => result)
|
|
487
579
|
}
|
|
@@ -641,6 +733,8 @@ class MochaPlugin extends CiPlugin {
|
|
|
641
733
|
this.addBind('ci:mocha:test:start', (ctx) => {
|
|
642
734
|
const store = storage('legacy').getStore()
|
|
643
735
|
const span = this.startTestSpan(ctx)
|
|
736
|
+
const { isDisabled, isAttemptToFix } = /** @type {{isDisabled?: boolean, isAttemptToFix?: boolean}} */ (ctx)
|
|
737
|
+
if (!isDisabled || isAttemptToFix) this.#startWebdriverioVideo(span)
|
|
644
738
|
|
|
645
739
|
ctx.parentStore = store
|
|
646
740
|
ctx.currentStore = { ...store, span }
|
|
@@ -650,13 +744,42 @@ class MochaPlugin extends CiPlugin {
|
|
|
650
744
|
return ctx.currentStore
|
|
651
745
|
})
|
|
652
746
|
|
|
747
|
+
this.addSub('ci:mocha:after-each:finish', (ctx) => {
|
|
748
|
+
const context = /** @type {{isWaiting: boolean, onDone: () => void}} */ (ctx)
|
|
749
|
+
const video = this.#webdriverioCapture
|
|
750
|
+
this.#webdriverioCapture = undefined
|
|
751
|
+
if (!video) return
|
|
752
|
+
context.isWaiting = true
|
|
753
|
+
video.waitForCapture(context.onDone)
|
|
754
|
+
})
|
|
755
|
+
|
|
653
756
|
this.addSub('ci:mocha:worker:finish', ({ onDone } = {}) => {
|
|
654
|
-
|
|
655
|
-
|
|
757
|
+
// An interrupted runner may exit without reporting the active attempt.
|
|
758
|
+
for (const span of this._webdriverioVideos.keys()) {
|
|
759
|
+
this.#finishWebdriverioMedia(span, 'skip', () => {})
|
|
760
|
+
}
|
|
761
|
+
const flush = () => {
|
|
762
|
+
clearTimeout(this.#webdriverioVideoFlushTimer)
|
|
763
|
+
this.#webdriverioVideoFlushTimer = undefined
|
|
764
|
+
if (isWebdriverioWorker && this.libraryConfig?.isTestFailureVideosEnabled) {
|
|
765
|
+
sendWebdriverioWorkerMessage({ name: VIDEO_UPLOAD_FLUSH, content: { complete: true } })
|
|
766
|
+
}
|
|
767
|
+
this.tracer._exporter.flush(onDone)
|
|
768
|
+
}
|
|
769
|
+
if (this._pendingWebdriverioMediaUploads === 0) {
|
|
656
770
|
flush()
|
|
657
771
|
return
|
|
658
772
|
}
|
|
659
|
-
this.
|
|
773
|
+
if (isWebdriverioWorker && this.libraryConfig?.isTestFailureVideosEnabled) {
|
|
774
|
+
sendWebdriverioWorkerMessage({ name: VIDEO_UPLOAD_FLUSH })
|
|
775
|
+
}
|
|
776
|
+
this._webdriverioMediaUploadCallbacks.push(flush)
|
|
777
|
+
if (this.#pendingWebdriverioVideos.size) {
|
|
778
|
+
this.#webdriverioVideoFlushTimer ??= setTimeout(() => {
|
|
779
|
+
for (const video of this.#pendingWebdriverioVideos) video.cancel()
|
|
780
|
+
}, FINAL_FLUSH_TIMEOUT)
|
|
781
|
+
this.#webdriverioVideoFlushTimer.unref?.()
|
|
782
|
+
}
|
|
660
783
|
})
|
|
661
784
|
|
|
662
785
|
this.addSub('ci:mocha:test:finish', ({
|
|
@@ -720,7 +843,7 @@ class MochaPlugin extends CiPlugin {
|
|
|
720
843
|
this.activeTestSpan = null
|
|
721
844
|
}
|
|
722
845
|
}
|
|
723
|
-
if (
|
|
846
|
+
if (!this.#finishWebdriverioMedia(span, status, finishSpan)) {
|
|
724
847
|
finishSpan()
|
|
725
848
|
}
|
|
726
849
|
}
|
|
@@ -830,23 +953,24 @@ class MochaPlugin extends CiPlugin {
|
|
|
830
953
|
this.runningTestProbe) {
|
|
831
954
|
finishWait = this.waitForInFlightDiBreakpointHits()
|
|
832
955
|
}
|
|
833
|
-
let
|
|
834
|
-
let
|
|
956
|
+
let mediaFinished = false
|
|
957
|
+
let canFinishMedia = false
|
|
835
958
|
let replayFinished = !finishWait
|
|
836
959
|
let spanFinished = false
|
|
837
960
|
const finishWhenReady = () => {
|
|
838
|
-
if (!spanFinished && replayFinished && (
|
|
961
|
+
if (!spanFinished && replayFinished && (mediaStarted === false || mediaFinished)) {
|
|
839
962
|
spanFinished = true
|
|
840
963
|
finishSpan()
|
|
841
964
|
}
|
|
842
965
|
}
|
|
843
|
-
const
|
|
844
|
-
|
|
845
|
-
|
|
966
|
+
const video = this._webdriverioVideos.get(span)
|
|
967
|
+
const mediaStarted = this.#finishWebdriverioMedia(span, 'fail', () => {
|
|
968
|
+
mediaFinished = true
|
|
969
|
+
if (canFinishMedia) {
|
|
846
970
|
finishWhenReady()
|
|
847
971
|
}
|
|
848
972
|
})
|
|
849
|
-
|
|
973
|
+
canFinishMedia = true
|
|
850
974
|
if (finishWait) {
|
|
851
975
|
const finishReplay = () => {
|
|
852
976
|
replayFinished = true
|
|
@@ -857,6 +981,9 @@ class MochaPlugin extends CiPlugin {
|
|
|
857
981
|
promises.finishTestPromise = finishTestPromise
|
|
858
982
|
}
|
|
859
983
|
}
|
|
984
|
+
if (video && promises) {
|
|
985
|
+
promises.finishTestPromise = waitForWebdriverioCapture(video, promises.finishTestPromise)
|
|
986
|
+
}
|
|
860
987
|
finishWhenReady()
|
|
861
988
|
}
|
|
862
989
|
})
|
|
@@ -1131,6 +1258,7 @@ class MochaPlugin extends CiPlugin {
|
|
|
1131
1258
|
title: test.title,
|
|
1132
1259
|
})
|
|
1133
1260
|
test.span = span
|
|
1261
|
+
if (!test.isDisabled || test.isAttemptToFix) this.#startWebdriverioVideo(span)
|
|
1134
1262
|
test.currentStore = {
|
|
1135
1263
|
...parentStore,
|
|
1136
1264
|
span,
|
|
@@ -1153,8 +1281,9 @@ class MochaPlugin extends CiPlugin {
|
|
|
1153
1281
|
return
|
|
1154
1282
|
}
|
|
1155
1283
|
|
|
1284
|
+
const specResult = getWebdriverioJasmineSpecResult(spec)
|
|
1156
1285
|
if (this.libraryConfig?.isTestManagementTestsEnabled) {
|
|
1157
|
-
this.#startWebdriverioJasmineTest(
|
|
1286
|
+
this.#startWebdriverioJasmineTest(specResult)
|
|
1158
1287
|
const test = this._webdriverioJasmineState?.tests.get(spec.id)
|
|
1159
1288
|
if (test?.isDisabled && !test.isAttemptToFix) {
|
|
1160
1289
|
spec.pend('Skipped by Datadog Test Management')
|
|
@@ -1486,6 +1615,71 @@ class MochaPlugin extends CiPlugin {
|
|
|
1486
1615
|
}
|
|
1487
1616
|
}
|
|
1488
1617
|
|
|
1618
|
+
/** @param {object} span - Span owning one WebdriverIO attempt */
|
|
1619
|
+
#startWebdriverioVideo (span) {
|
|
1620
|
+
if (this.testFramework !== WEBDRIVERIO_FRAMEWORK ||
|
|
1621
|
+
!shouldRecordWebdriverioVideos ||
|
|
1622
|
+
!this._tracerConfig.testOptimization?.DD_TEST_FAILURE_VIDEOS_ENABLED) return
|
|
1623
|
+
const canUpload = isWebdriverioWorker
|
|
1624
|
+
? this.libraryConfig?.isTestFailureVideosEnabled
|
|
1625
|
+
: this.tracer._exporter?.canUploadTestVideos?.()
|
|
1626
|
+
if (!canUpload) return
|
|
1627
|
+
|
|
1628
|
+
const browser = globalThis._wdioGlobals?.get?.('browser') || globalThis.browser
|
|
1629
|
+
const video = createWebdriverioVideo(browser)
|
|
1630
|
+
this.#webdriverioCapture = video
|
|
1631
|
+
if (video) this._webdriverioVideos.set(span, video)
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
/**
|
|
1635
|
+
* Finishes a span only after its independent screenshot and video uploads settle.
|
|
1636
|
+
*
|
|
1637
|
+
* @param {object} span
|
|
1638
|
+
* @param {string} status
|
|
1639
|
+
* @param {() => void} onDone
|
|
1640
|
+
*/
|
|
1641
|
+
#finishWebdriverioMedia (span, status, onDone) {
|
|
1642
|
+
const video = this._webdriverioVideos.get(span)
|
|
1643
|
+
if (!video) return status === 'fail' && this.#startWebdriverioScreenshotUpload(span, onDone)
|
|
1644
|
+
|
|
1645
|
+
this._webdriverioVideos.delete(span)
|
|
1646
|
+
this.#pendingWebdriverioVideos.add(video)
|
|
1647
|
+
this._pendingWebdriverioMediaUploads++
|
|
1648
|
+
const traceId = span.context().toTraceId()
|
|
1649
|
+
const capturedAtMs = dateNow()
|
|
1650
|
+
let pending = 2
|
|
1651
|
+
const complete = () => {
|
|
1652
|
+
if (--pending !== 0) return
|
|
1653
|
+
onDone()
|
|
1654
|
+
this.#completeWebdriverioMediaUpload()
|
|
1655
|
+
}
|
|
1656
|
+
if (status !== 'fail' || !this.#startWebdriverioScreenshotUpload(span, complete)) complete()
|
|
1657
|
+
video.finish(status === 'fail', (filePath, index, callback) => {
|
|
1658
|
+
const options = {
|
|
1659
|
+
filePath,
|
|
1660
|
+
traceId,
|
|
1661
|
+
idempotencyKey: `${traceId}:webdriverio-failure-${index}.webm`,
|
|
1662
|
+
capturedAtMs,
|
|
1663
|
+
}
|
|
1664
|
+
if (isWebdriverioWorker) requestWebdriverioVideoUpload(options, callback)
|
|
1665
|
+
else this.tracer._exporter.uploadTestVideo(options, callback)
|
|
1666
|
+
}, result => {
|
|
1667
|
+
this.#pendingWebdriverioVideos.delete(video)
|
|
1668
|
+
setVideoUploadTags(span, result)
|
|
1669
|
+
complete()
|
|
1670
|
+
})
|
|
1671
|
+
return true
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
/** Releases worker shutdown after all media-owned spans have finished. */
|
|
1675
|
+
#completeWebdriverioMediaUpload () {
|
|
1676
|
+
this._pendingWebdriverioMediaUploads--
|
|
1677
|
+
if (this._pendingWebdriverioMediaUploads !== 0) return
|
|
1678
|
+
const callbacks = this._webdriverioMediaUploadCallbacks
|
|
1679
|
+
this._webdriverioMediaUploadCallbacks = []
|
|
1680
|
+
for (const callback of callbacks) callback()
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1489
1683
|
/**
|
|
1490
1684
|
* Captures and uploads the current WebdriverIO browser state once for a failed test attempt.
|
|
1491
1685
|
*
|
|
@@ -1493,6 +1687,7 @@ class MochaPlugin extends CiPlugin {
|
|
|
1493
1687
|
* @param {() => void} [onDone] - Called after the screenshot upload finishes
|
|
1494
1688
|
*/
|
|
1495
1689
|
#startWebdriverioScreenshotUpload (span, onDone) {
|
|
1690
|
+
this._webdriverioVideos.get(span)?.capture()
|
|
1496
1691
|
if (
|
|
1497
1692
|
!span ||
|
|
1498
1693
|
this.testFramework !== WEBDRIVERIO_FRAMEWORK ||
|
|
@@ -1527,7 +1722,7 @@ class MochaPlugin extends CiPlugin {
|
|
|
1527
1722
|
finished: false,
|
|
1528
1723
|
}
|
|
1529
1724
|
this._webdriverioScreenshotUploads.set(span, upload)
|
|
1530
|
-
this.
|
|
1725
|
+
this._pendingWebdriverioMediaUploads++
|
|
1531
1726
|
const finishUpload = (result) => this.#finishWebdriverioScreenshotUpload(span, upload, result)
|
|
1532
1727
|
let captureTimeout
|
|
1533
1728
|
const failCapture = (error) => {
|
|
@@ -1586,18 +1781,10 @@ class MochaPlugin extends CiPlugin {
|
|
|
1586
1781
|
const uploadCallbacks = upload.callbacks
|
|
1587
1782
|
upload.callbacks = []
|
|
1588
1783
|
|
|
1589
|
-
this._pendingWebdriverioScreenshotUploads--
|
|
1590
|
-
let workerCallbacks = []
|
|
1591
|
-
if (this._pendingWebdriverioScreenshotUploads === 0) {
|
|
1592
|
-
workerCallbacks = this._webdriverioScreenshotUploadCallbacks
|
|
1593
|
-
this._webdriverioScreenshotUploadCallbacks = []
|
|
1594
|
-
}
|
|
1595
1784
|
for (const callback of uploadCallbacks) {
|
|
1596
1785
|
callback()
|
|
1597
1786
|
}
|
|
1598
|
-
|
|
1599
|
-
callback()
|
|
1600
|
-
}
|
|
1787
|
+
this.#completeWebdriverioMediaUpload()
|
|
1601
1788
|
}
|
|
1602
1789
|
|
|
1603
1790
|
/**
|
|
@@ -1616,7 +1803,7 @@ class MochaPlugin extends CiPlugin {
|
|
|
1616
1803
|
}
|
|
1617
1804
|
const exporter = this.tracer?._exporter
|
|
1618
1805
|
const traceId = span.context().toTraceId()
|
|
1619
|
-
const capturedAtMs =
|
|
1806
|
+
const capturedAtMs = dateNow()
|
|
1620
1807
|
let pendingUploads = screenshotList.length
|
|
1621
1808
|
let hasUploadError = false
|
|
1622
1809
|
const finishOne = (error) => {
|