dd-trace 6.17.0 → 6.18.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 +52 -9
- package/package.json +1 -1
- package/packages/datadog-instrumentations/src/cucumber.js +10 -5
- package/packages/datadog-instrumentations/src/cypress-config.js +27 -15
- package/packages/datadog-instrumentations/src/helpers/bundler-register.js +6 -0
- package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +0 -6
- package/packages/datadog-instrumentations/src/helpers/register.js +20 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +41 -4
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +15 -13
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +43 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +13 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +28 -3
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +4 -0
- package/packages/datadog-instrumentations/src/jest.js +27 -3
- package/packages/datadog-instrumentations/src/mocha/main.js +7 -5
- package/packages/datadog-instrumentations/src/mocha/utils.js +8 -8
- package/packages/datadog-instrumentations/src/playwright-reporter.js +3 -4
- package/packages/datadog-instrumentations/src/playwright.js +47 -6
- package/packages/datadog-instrumentations/src/undici.js +7 -3
- package/packages/datadog-instrumentations/src/vitest-main.js +91 -20
- package/packages/datadog-instrumentations/src/webdriverio.js +28 -12
- package/packages/datadog-instrumentations/src/ws.js +41 -1
- package/packages/datadog-plugin-cucumber/src/index.js +3 -4
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +44 -14
- package/packages/datadog-plugin-cypress/src/index.js +3 -1
- package/packages/datadog-plugin-jest/src/index.js +3 -4
- package/packages/datadog-plugin-mocha/src/index.js +3 -4
- package/packages/datadog-plugin-playwright/src/index.js +11 -6
- package/packages/datadog-plugin-vitest/src/index.js +3 -4
- package/packages/datadog-plugin-ws/src/producer.js +1 -1
- package/packages/datadog-shimmer/src/shimmer.js +14 -10
- package/packages/datadog-turbopack/index.js +4 -1
- package/packages/datadog-turbopack/src/loader.js +6 -2
- package/packages/dd-trace/src/agent/info.js +5 -4
- package/packages/dd-trace/src/config/generated-config-types.d.ts +15 -15
- package/packages/dd-trace/src/config/index.js +7 -6
- package/packages/dd-trace/src/config/supported-configurations.json +11 -0
- package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -1
- package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +5 -3
- package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
- package/packages/dd-trace/src/debugger/index.js +1 -1
- package/packages/dd-trace/src/evp_proxy/constants.js +2 -0
- package/packages/dd-trace/src/evp_proxy/direct.js +17 -13
- package/packages/dd-trace/src/evp_proxy/discovery.js +14 -10
- package/packages/dd-trace/src/evp_proxy/path.js +21 -7
- package/packages/dd-trace/src/exporters/common/url.js +33 -2
- package/packages/dd-trace/src/llmobs/prompts/manager.js +10 -2
- package/packages/dd-trace/src/llmobs/prompts/prompt.js +77 -11
- package/packages/dd-trace/src/llmobs/tagger.js +9 -4
- package/packages/dd-trace/src/openfeature/configuration_source.js +4 -1
- package/packages/dd-trace/src/openfeature/flagging_provider.js +3 -3
- package/packages/dd-trace/src/openfeature/index.js +6 -2
- package/packages/dd-trace/src/openfeature/writers/base.js +90 -17
- package/packages/dd-trace/src/openfeature/writers/exposures.js +4 -0
- package/packages/dd-trace/src/openfeature/writers/util.js +104 -33
- package/packages/dd-trace/src/opentracing/tracer.js +3 -3
- package/packages/dd-trace/src/plugin_manager.js +27 -8
- package/packages/dd-trace/src/plugins/ci_plugin.js +1 -1
- package/packages/dd-trace/src/plugins/util/test.js +9 -4
- package/packages/dd-trace/src/proxy.js +2 -2
- package/packages/dd-trace/src/telemetry/send-data.js +2 -1
- package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
- package/packages/dd-trace/src/tracer.js +5 -4
- package/packages/datadog-instrumentations/src/azure-cosmos.js +0 -7
- package/packages/datadog-instrumentations/src/bullmq.js +0 -11
- package/packages/datadog-instrumentations/src/langchain.js +0 -7
- package/packages/datadog-instrumentations/src/langgraph.js +0 -7
- package/packages/datadog-instrumentations/src/mercurius.js +0 -11
|
@@ -63,6 +63,7 @@ const baseReporterWaitForSyncCh = tracingChannel('orchestrion:@wdio/runner:BaseR
|
|
|
63
63
|
const runnerRunCh = tracingChannel('orchestrion:@wdio/runner:Runner_run')
|
|
64
64
|
const executeAsyncCh = tracingChannel('orchestrion:@wdio/utils:executeAsync')
|
|
65
65
|
const launcherStartInstanceCh = tracingChannel('orchestrion:@wdio/cli:Launcher_startInstance')
|
|
66
|
+
const configParserShardCh = tracingChannel('orchestrion:@wdio/config:ConfigParser_shard')
|
|
66
67
|
const localRunnerRunCh = tracingChannel('orchestrion:@wdio/local-runner:LocalRunner_run')
|
|
67
68
|
const localRunnerShutdownCh = tracingChannel('orchestrion:@wdio/local-runner:LocalRunner_shutdown')
|
|
68
69
|
const testFrameworkFnWrapperCh = tracingChannel('orchestrion:@wdio/utils:testFrameworkFnWrapper')
|
|
@@ -87,6 +88,7 @@ if (loadCh.hasSubscribers) {
|
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
const coordinatorStates = new WeakMap()
|
|
91
|
+
const emptyShardConfigurations = new WeakSet()
|
|
90
92
|
const localRunnerVersions = new WeakMap()
|
|
91
93
|
const rumBrowsers = new Set()
|
|
92
94
|
const rumCorrelationBrowsers = new Set()
|
|
@@ -936,6 +938,7 @@ function waitForRumTestStart (context) {
|
|
|
936
938
|
* @property {string} framework
|
|
937
939
|
* @property {string|undefined} rootDir
|
|
938
940
|
* @property {typeof process.env|undefined} runnerEnv
|
|
941
|
+
* @property {{total: number}|undefined} shard
|
|
939
942
|
*/
|
|
940
943
|
|
|
941
944
|
/**
|
|
@@ -1806,17 +1809,18 @@ function getSessionStatus (state) {
|
|
|
1806
1809
|
}
|
|
1807
1810
|
|
|
1808
1811
|
/**
|
|
1809
|
-
*
|
|
1812
|
+
* Explains a skipped session using the workers' discovery results.
|
|
1810
1813
|
*
|
|
1811
1814
|
* @param {CoordinatorState} state
|
|
1812
1815
|
*/
|
|
1813
|
-
function
|
|
1814
|
-
if (state.workers.size === 0)
|
|
1815
|
-
|
|
1816
|
+
function getTestSessionEmptyReason (state) {
|
|
1817
|
+
if (state.workers.size === 0 && emptyShardConfigurations.has(getRunnerConfiguration(state.localRunner))) {
|
|
1818
|
+
return 'zero_test_shard'
|
|
1819
|
+
}
|
|
1816
1820
|
for (const workerRecord of state.workers) {
|
|
1817
|
-
if (workerRecord.hasTests !== false) return
|
|
1821
|
+
if (workerRecord.hasTests !== false) return 'all_tests_skipped'
|
|
1818
1822
|
}
|
|
1819
|
-
return
|
|
1823
|
+
return 'zero_tests'
|
|
1820
1824
|
}
|
|
1821
1825
|
|
|
1822
1826
|
/**
|
|
@@ -1832,9 +1836,9 @@ function finishCoordinator (state, error, onDone) {
|
|
|
1832
1836
|
return
|
|
1833
1837
|
}
|
|
1834
1838
|
if (!state.sessionStarted) {
|
|
1835
|
-
if (!error &&
|
|
1836
|
-
|
|
1837
|
-
|
|
1839
|
+
if (!error && state.workers.size === 0 &&
|
|
1840
|
+
!emptyShardConfigurations.has(getRunnerConfiguration(state.localRunner))) {
|
|
1841
|
+
error = new Error('No test files were found.')
|
|
1838
1842
|
}
|
|
1839
1843
|
initializeCoordinator(state, () => finishCoordinator(state, error, onDone))
|
|
1840
1844
|
return
|
|
@@ -1861,7 +1865,7 @@ function finishCoordinator (state, error, onDone) {
|
|
|
1861
1865
|
const status = error ? 'fail' : getSessionStatus(state)
|
|
1862
1866
|
testSessionFinishCh.publish({
|
|
1863
1867
|
status,
|
|
1864
|
-
|
|
1868
|
+
testSessionEmptyReason: status === 'skip' ? getTestSessionEmptyReason(state) : undefined,
|
|
1865
1869
|
error,
|
|
1866
1870
|
isEarlyFlakeDetectionEnabled: state.configuration.isEarlyFlakeDetectionEnabled,
|
|
1867
1871
|
isEarlyFlakeDetectionFaulty: state.configuration.isEarlyFlakeDetectionFaulty,
|
|
@@ -1976,6 +1980,17 @@ jasmineAdapterInitCh.subscribe({
|
|
|
1976
1980
|
|
|
1977
1981
|
// dc-polyfill supports partial tracing-channel subscribers, unlike the Node.js type definition.
|
|
1978
1982
|
// @ts-expect-error
|
|
1983
|
+
configParserShardCh.subscribe({
|
|
1984
|
+
end (context) {
|
|
1985
|
+
const config = context.self._config
|
|
1986
|
+
if (testFinishCh.hasSubscribers && config?.shard?.total > 1 &&
|
|
1987
|
+
context.arguments[0].length > 0 && context.result?.length === 0) {
|
|
1988
|
+
emptyShardConfigurations.add(config)
|
|
1989
|
+
}
|
|
1990
|
+
},
|
|
1991
|
+
})
|
|
1992
|
+
|
|
1993
|
+
// @ts-expect-error See the partial tracing-channel subscriber above.
|
|
1979
1994
|
launcherStartInstanceCh.subscribe({
|
|
1980
1995
|
start (context) {
|
|
1981
1996
|
const localRunner = context.self?.runner
|
|
@@ -2050,10 +2065,11 @@ localRunnerRunCh.subscribe({
|
|
|
2050
2065
|
// @ts-expect-error See the partial tracing-channel subscriber above.
|
|
2051
2066
|
localRunnerShutdownCh.subscribe({
|
|
2052
2067
|
asyncEnd (context) {
|
|
2053
|
-
const
|
|
2054
|
-
if (!
|
|
2068
|
+
const runnerConfiguration = getRunnerConfiguration(context.self)
|
|
2069
|
+
if (!testFinishCh.hasSubscribers || !SUPPORTED_FRAMEWORK_ADAPTERS.has(runnerConfiguration?.framework)) {
|
|
2055
2070
|
return
|
|
2056
2071
|
}
|
|
2072
|
+
const state = getCoordinatorState(context.self)
|
|
2057
2073
|
|
|
2058
2074
|
// Orchestrion uses the callback for the matching settlement path to delay LocalRunner.shutdown.
|
|
2059
2075
|
const waitForCoordinator = onDone => {
|
|
@@ -94,9 +94,22 @@ function wrapSend (send) {
|
|
|
94
94
|
return send.apply(this, arguments)
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
const socket = this._sender?._socket
|
|
98
|
+
// No socket is attached before the handshake completes or after it is torn down.
|
|
99
|
+
if (!socket) {
|
|
100
|
+
return send.apply(this, arguments)
|
|
101
|
+
}
|
|
102
|
+
|
|
97
103
|
const [data, options, cb] = arguments
|
|
98
104
|
|
|
99
|
-
|
|
105
|
+
// `ws` stringifies numbers, then spreads `options` over its own inferred default, so an
|
|
106
|
+
// explicitly passed `binary` wins even when it is nullish and the frame goes out as text.
|
|
107
|
+
const payload = typeof data === 'number' ? data.toString() : data
|
|
108
|
+
const binary = options != null && Object.hasOwn(options, 'binary')
|
|
109
|
+
? Boolean(options.binary)
|
|
110
|
+
: typeof payload !== 'string'
|
|
111
|
+
const byteLength = sentDataLength(payload)
|
|
112
|
+
const ctx = { data, binary, socket, byteLength }
|
|
100
113
|
|
|
101
114
|
return typeof cb === 'function'
|
|
102
115
|
? producerCh.traceCallback(send, undefined, ctx, this, data, options, cb)
|
|
@@ -241,6 +254,33 @@ addHook({
|
|
|
241
254
|
return Receiver
|
|
242
255
|
})
|
|
243
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Byte length of an outgoing payload, following the coercion `ws` applies in `Sender.send`:
|
|
259
|
+
* strings and blobs are measured directly and everything else goes through `toBuffer()`, which
|
|
260
|
+
* yields one byte per element for plain arrays rather than concatenating them. Blob-likes carry
|
|
261
|
+
* neither a `byteLength` nor an array shape, so they are matched last to keep the common
|
|
262
|
+
* buffer path first.
|
|
263
|
+
*
|
|
264
|
+
* @param {WebSocketMessageData | number | undefined} data
|
|
265
|
+
*/
|
|
266
|
+
function sentDataLength (data) {
|
|
267
|
+
if (typeof data === 'string') {
|
|
268
|
+
return Buffer.byteLength(data)
|
|
269
|
+
}
|
|
270
|
+
// An array is one byte per element even when it carries an unrelated `byteLength`.
|
|
271
|
+
if (Array.isArray(data)) {
|
|
272
|
+
return data.length
|
|
273
|
+
}
|
|
274
|
+
// Covers views as well as `ArrayBuffer` and `SharedArrayBuffer`.
|
|
275
|
+
if (typeof (/** @type {{ byteLength?: unknown }} */ (data)?.byteLength) === 'number') {
|
|
276
|
+
return /** @type {{ byteLength: number }} */ (data).byteLength
|
|
277
|
+
}
|
|
278
|
+
// Blobs and files, detected the way `ws` does: structurally, so shimmed and cross-realm
|
|
279
|
+
// implementations it accepts are measured instead of silently reported as empty.
|
|
280
|
+
const { size } = /** @type {{ size?: unknown }} */ (data) ?? {}
|
|
281
|
+
return typeof size === 'number' ? size : 0
|
|
282
|
+
}
|
|
283
|
+
|
|
244
284
|
/**
|
|
245
285
|
* @param {WebSocketMessageData} data
|
|
246
286
|
*/
|
|
@@ -73,7 +73,7 @@ class CucumberPlugin extends CiPlugin {
|
|
|
73
73
|
isEarlyFlakeDetectionFaulty,
|
|
74
74
|
isTestManagementTestsEnabled,
|
|
75
75
|
isParallel,
|
|
76
|
-
|
|
76
|
+
testSessionEmptyReason,
|
|
77
77
|
error,
|
|
78
78
|
onDone,
|
|
79
79
|
}) => {
|
|
@@ -118,12 +118,11 @@ class CucumberPlugin extends CiPlugin {
|
|
|
118
118
|
|
|
119
119
|
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
120
120
|
this.testModuleSpan.setTag(TEST_STATUS, status)
|
|
121
|
-
if (
|
|
121
|
+
if (testSessionEmptyReason) {
|
|
122
122
|
setExpectedEmptyTestSessionTags(
|
|
123
123
|
this.testSessionSpan,
|
|
124
124
|
this.testModuleSpan,
|
|
125
|
-
|
|
126
|
-
'zero_tests'
|
|
125
|
+
testSessionEmptyReason
|
|
127
126
|
)
|
|
128
127
|
}
|
|
129
128
|
if (error) {
|
|
@@ -242,7 +242,7 @@ function getSessionStatus (summary, testSuiteStatuses) {
|
|
|
242
242
|
if (summary.status === 'failed' || summary.failures > 0 || summary.totalFailed > 0) {
|
|
243
243
|
return 'fail'
|
|
244
244
|
}
|
|
245
|
-
if (summary.totalSkipped
|
|
245
|
+
if ((summary.totalSkipped ?? 0) + (summary.totalPending ?? 0) === summary.totalTests) {
|
|
246
246
|
return 'skip'
|
|
247
247
|
}
|
|
248
248
|
return 'pass'
|
|
@@ -381,7 +381,7 @@ function getSuiteStatus (suiteStats) {
|
|
|
381
381
|
return 'fail'
|
|
382
382
|
}
|
|
383
383
|
if (suiteStats.tests !== undefined &&
|
|
384
|
-
|
|
384
|
+
suiteStats.tests === (suiteStats.pending ?? 0) + (suiteStats.skipped ?? 0)) {
|
|
385
385
|
return 'skip'
|
|
386
386
|
}
|
|
387
387
|
return 'pass'
|
|
@@ -451,6 +451,8 @@ function getFinalStatus ({
|
|
|
451
451
|
}
|
|
452
452
|
|
|
453
453
|
class CypressPlugin {
|
|
454
|
+
#pendingTestSuiteSpans = new Map()
|
|
455
|
+
|
|
454
456
|
_isInit = false
|
|
455
457
|
testEnvironmentMetadata = getTestEnvironmentMetadata(TEST_FRAMEWORK_NAME)
|
|
456
458
|
|
|
@@ -548,6 +550,7 @@ class CypressPlugin {
|
|
|
548
550
|
*
|
|
549
551
|
*/
|
|
550
552
|
resetRunState () {
|
|
553
|
+
this.#pendingTestSuiteSpans.clear()
|
|
551
554
|
this._isInit = false
|
|
552
555
|
this.finishedTestsByFile = {}
|
|
553
556
|
this.hasTestsReported = false
|
|
@@ -1048,7 +1051,7 @@ class CypressPlugin {
|
|
|
1048
1051
|
}
|
|
1049
1052
|
}
|
|
1050
1053
|
|
|
1051
|
-
|
|
1054
|
+
const testSuiteSpan = this.tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test_suite`, {
|
|
1052
1055
|
childOf: this.testModuleSpan,
|
|
1053
1056
|
tags: {
|
|
1054
1057
|
[COMPONENT]: TEST_FRAMEWORK_NAME,
|
|
@@ -1057,9 +1060,13 @@ class CypressPlugin {
|
|
|
1057
1060
|
},
|
|
1058
1061
|
integrationName: TEST_FRAMEWORK_NAME,
|
|
1059
1062
|
})
|
|
1063
|
+
this.#pendingTestSuiteSpans.set(testSuite, testSuiteSpan)
|
|
1064
|
+
return testSuiteSpan
|
|
1060
1065
|
}
|
|
1061
1066
|
|
|
1062
|
-
getTestSpan ({
|
|
1067
|
+
getTestSpan ({
|
|
1068
|
+
testName, testSuite, isUnskippable, isForcedToRun, testSourceFile, isDisabled, isQuarantined, startTime,
|
|
1069
|
+
}) {
|
|
1063
1070
|
const testSuiteTags = {
|
|
1064
1071
|
[TEST_MODULE]: TEST_FRAMEWORK_NAME,
|
|
1065
1072
|
}
|
|
@@ -1116,6 +1123,7 @@ class CypressPlugin {
|
|
|
1116
1123
|
|
|
1117
1124
|
return this.tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test`, {
|
|
1118
1125
|
childOf,
|
|
1126
|
+
startTime,
|
|
1119
1127
|
tags: {
|
|
1120
1128
|
[COMPONENT]: TEST_FRAMEWORK_NAME,
|
|
1121
1129
|
[ORIGIN_KEY]: CI_APP_ORIGIN,
|
|
@@ -1348,6 +1356,20 @@ class CypressPlugin {
|
|
|
1348
1356
|
}
|
|
1349
1357
|
|
|
1350
1358
|
afterRun (suiteStats, error) {
|
|
1359
|
+
const pendingFinalizations = []
|
|
1360
|
+
// Late manual initialization cannot intercept a later replacement of after:spec.
|
|
1361
|
+
// Run results let us finalize the suites whose browser hooks already created spans.
|
|
1362
|
+
if (this._isInit && suiteStats?.runs) {
|
|
1363
|
+
for (const result of suiteStats.runs) {
|
|
1364
|
+
const testSuiteSpan = this.#pendingTestSuiteSpans.get(result.spec?.relative)
|
|
1365
|
+
if (!testSuiteSpan) continue
|
|
1366
|
+
this.testSuiteSpan = testSuiteSpan
|
|
1367
|
+
const endedAt = Date.parse(result.stats?.endedAt)
|
|
1368
|
+
const finalization = this.afterSpec(result.spec, result, undefined,
|
|
1369
|
+
Number.isFinite(endedAt) ? endedAt : undefined)
|
|
1370
|
+
if (finalization) pendingFinalizations.push(finalization)
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1351
1373
|
const hasPendingVideoSpans = this.pendingVideoUploads.length > 0
|
|
1352
1374
|
const videoUploadsPromise = this.uploadPendingTestSuiteVideos()
|
|
1353
1375
|
if (!this._isInit) {
|
|
@@ -1358,7 +1380,11 @@ class CypressPlugin {
|
|
|
1358
1380
|
}
|
|
1359
1381
|
return
|
|
1360
1382
|
}
|
|
1361
|
-
|
|
1383
|
+
// Recovered screenshot-only tests must finish before the run closes any remaining trace spans.
|
|
1384
|
+
const finalizeRun = () => this.#finalizeRun(suiteStats, error, hasPendingVideoSpans)
|
|
1385
|
+
const finalizationPromise = pendingFinalizations.length > 0
|
|
1386
|
+
? Promise.all(pendingFinalizations).then(finalizeRun)
|
|
1387
|
+
: finalizeRun()
|
|
1362
1388
|
if (videoUploadsPromise) {
|
|
1363
1389
|
return Promise.all([videoUploadsPromise, finalizationPromise])
|
|
1364
1390
|
.then(() => this.#flushExporter(false))
|
|
@@ -1384,12 +1410,11 @@ class CypressPlugin {
|
|
|
1384
1410
|
|
|
1385
1411
|
this.testModuleSpan.setTag(TEST_STATUS, testStatus)
|
|
1386
1412
|
this.testSessionSpan.setTag(TEST_STATUS, testStatus)
|
|
1387
|
-
if (testStatus !== 'fail' && hasNoTests) {
|
|
1413
|
+
if (testStatus !== 'fail' && (hasNoTests || testStatus === 'skip')) {
|
|
1388
1414
|
setExpectedEmptyTestSessionTags(
|
|
1389
1415
|
this.testSessionSpan,
|
|
1390
1416
|
this.testModuleSpan,
|
|
1391
|
-
'
|
|
1392
|
-
'zero_tests'
|
|
1417
|
+
hasNoTests ? 'zero_tests' : 'all_tests_skipped'
|
|
1393
1418
|
)
|
|
1394
1419
|
}
|
|
1395
1420
|
if (error) {
|
|
@@ -1504,10 +1529,10 @@ class CypressPlugin {
|
|
|
1504
1529
|
}
|
|
1505
1530
|
}
|
|
1506
1531
|
|
|
1507
|
-
afterSpec (spec, results, error) {
|
|
1532
|
+
afterSpec (spec, results, error, finishTime) {
|
|
1508
1533
|
const { tests, stats, screenshots, video, error: resultError } = results || {}
|
|
1509
1534
|
const cypressTests = tests || []
|
|
1510
|
-
if (cypressTests.length > 0) this.hasTestsReported = true
|
|
1535
|
+
if (cypressTests.length > 0 || stats?.tests > 0) this.hasTestsReported = true
|
|
1511
1536
|
const specScreenshots = screenshots || []
|
|
1512
1537
|
const finishedTests = this.finishedTestsByFile[spec.relative] || []
|
|
1513
1538
|
const screenshotUploadPromises = []
|
|
@@ -1540,7 +1565,10 @@ class CypressPlugin {
|
|
|
1540
1565
|
? getTestSuitePath(spec.absolute, this.repositoryRoot)
|
|
1541
1566
|
: spec.relative
|
|
1542
1567
|
|
|
1543
|
-
|
|
1568
|
+
// Recovered tests that never ran have no execution time; keep them at the recorded suite end.
|
|
1569
|
+
const skippedTestSpan = this.getTestSpan({
|
|
1570
|
+
testName: cypressTestName, testSuite: spec.relative, testSourceFile, startTime: finishTime,
|
|
1571
|
+
})
|
|
1544
1572
|
skippedTestSpan.setTag(TEST_FINAL_STATUS, 'skip')
|
|
1545
1573
|
|
|
1546
1574
|
skippedTestSpan.setTag(TEST_STATUS, 'skip')
|
|
@@ -1578,7 +1606,7 @@ class CypressPlugin {
|
|
|
1578
1606
|
})
|
|
1579
1607
|
}
|
|
1580
1608
|
|
|
1581
|
-
testSpanFinishes.push({ testSpan: skippedTestSpan, finishTime: this._now() })
|
|
1609
|
+
testSpanFinishes.push({ testSpan: skippedTestSpan, finishTime: finishTime ?? this._now() })
|
|
1582
1610
|
}
|
|
1583
1611
|
|
|
1584
1612
|
// Make sure that reported test statuses are the same as Cypress reports.
|
|
@@ -1749,7 +1777,7 @@ class CypressPlugin {
|
|
|
1749
1777
|
}
|
|
1750
1778
|
}
|
|
1751
1779
|
|
|
1752
|
-
const testSuiteFinishTime = this._now()
|
|
1780
|
+
const testSuiteFinishTime = finishTime ?? this._now()
|
|
1753
1781
|
const suiteError = error || resultError || latestError
|
|
1754
1782
|
const suiteStatus = suiteError ||
|
|
1755
1783
|
cypressTests.some(test => CYPRESS_STATUS_TO_TEST_STATUS[test.state] === 'fail')
|
|
@@ -1771,6 +1799,7 @@ class CypressPlugin {
|
|
|
1771
1799
|
testSuiteSpan.setTag(TEST_STATUS, suiteStatus)
|
|
1772
1800
|
if (suiteError) testSuiteSpan.setTag('error', suiteError)
|
|
1773
1801
|
this.testSuiteSpan = null
|
|
1802
|
+
this.#pendingTestSuiteSpans.delete(spec.relative)
|
|
1774
1803
|
}
|
|
1775
1804
|
|
|
1776
1805
|
let testSpansPromise
|
|
@@ -2034,7 +2063,8 @@ class CypressPlugin {
|
|
|
2034
2063
|
rumTestExecutionIdCookieName: RUM_TEST_EXECUTION_ID_COOKIE_NAME,
|
|
2035
2064
|
}
|
|
2036
2065
|
|
|
2037
|
-
this.testSuiteSpan
|
|
2066
|
+
this.testSuiteSpan = this.#pendingTestSuiteSpans.get(testSuite) ||
|
|
2067
|
+
this.getTestSuiteSpan({ testSuite, testSuiteAbsolutePath })
|
|
2038
2068
|
return suitePayload
|
|
2039
2069
|
},
|
|
2040
2070
|
'dd:beforeEach': (test) => {
|
|
@@ -26,6 +26,7 @@ class CypressPlugin extends Plugin {
|
|
|
26
26
|
userAfterRunHandlers,
|
|
27
27
|
userAfterScreenshotHandlers,
|
|
28
28
|
cleanupWrapper,
|
|
29
|
+
registerBeforeRun,
|
|
29
30
|
} = payload
|
|
30
31
|
|
|
31
32
|
const registerAfterRunWithCleanup = (afterRunHandler) => {
|
|
@@ -79,6 +80,7 @@ class CypressPlugin extends Plugin {
|
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
if (cypressPlugin._isInit) {
|
|
83
|
+
registerBeforeRun()
|
|
82
84
|
// Already initialized by manual plugin call — just chain user handlers.
|
|
83
85
|
// Pass the plugin's afterScreenshot so chaining a user handler doesn't drop the upload
|
|
84
86
|
// (the chained registration replaces the one plugin.js set, so it must include it).
|
|
@@ -94,7 +96,7 @@ class CypressPlugin extends Plugin {
|
|
|
94
96
|
return
|
|
95
97
|
}
|
|
96
98
|
|
|
97
|
-
|
|
99
|
+
registerBeforeRun(cypressPlugin.beforeRun.bind(cypressPlugin))
|
|
98
100
|
registerAfterScreenshot(datadogAfterScreenshotHandler)
|
|
99
101
|
|
|
100
102
|
on('after:spec', (spec, results) => {
|
|
@@ -117,19 +117,18 @@ class JestPlugin extends CiPlugin {
|
|
|
117
117
|
isEarlyFlakeDetectionEnabled,
|
|
118
118
|
isEarlyFlakeDetectionFaulty,
|
|
119
119
|
isTestManagementTestsEnabled,
|
|
120
|
-
|
|
120
|
+
testSessionEmptyReason,
|
|
121
121
|
onDone,
|
|
122
122
|
}) => {
|
|
123
123
|
const finishSession = () => {
|
|
124
124
|
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
125
125
|
this.testModuleSpan.setTag(TEST_STATUS, status)
|
|
126
126
|
|
|
127
|
-
if (
|
|
127
|
+
if (testSessionEmptyReason) {
|
|
128
128
|
setExpectedEmptyTestSessionTags(
|
|
129
129
|
this.testSessionSpan,
|
|
130
130
|
this.testModuleSpan,
|
|
131
|
-
|
|
132
|
-
'zero_tests'
|
|
131
|
+
testSessionEmptyReason
|
|
133
132
|
)
|
|
134
133
|
}
|
|
135
134
|
|
|
@@ -885,7 +885,7 @@ class MochaPlugin extends CiPlugin {
|
|
|
885
885
|
isTestManagementEnabled,
|
|
886
886
|
isParallel,
|
|
887
887
|
isFrameworkError,
|
|
888
|
-
|
|
888
|
+
testSessionEmptyReason,
|
|
889
889
|
onDone,
|
|
890
890
|
}) => {
|
|
891
891
|
this._exportPendingWorkerTraces()
|
|
@@ -898,12 +898,11 @@ class MochaPlugin extends CiPlugin {
|
|
|
898
898
|
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
899
899
|
this.testModuleSpan.setTag(TEST_STATUS, status)
|
|
900
900
|
|
|
901
|
-
if (
|
|
901
|
+
if (testSessionEmptyReason) {
|
|
902
902
|
setExpectedEmptyTestSessionTags(
|
|
903
903
|
this.testSessionSpan,
|
|
904
904
|
this.testModuleSpan,
|
|
905
|
-
|
|
906
|
-
'zero_tests'
|
|
905
|
+
testSessionEmptyReason
|
|
907
906
|
)
|
|
908
907
|
}
|
|
909
908
|
|
|
@@ -70,8 +70,6 @@ const log = require('../../dd-trace/src/log')
|
|
|
70
70
|
|
|
71
71
|
const PLAYWRIGHT_FAILURE_SCREENSHOT_RE = /^test-failed-\d+\.png$/
|
|
72
72
|
const PLAYWRIGHT_VIDEO_CONTENT_TYPES = new Set(['video/mp4', 'video/webm'])
|
|
73
|
-
const EMPTY_SHARD_SKIP_REASON = 'No tests were assigned to this shard'
|
|
74
|
-
const EMPTY_SHARD_REASON = 'zero_test_shard'
|
|
75
73
|
const RETRY_TEST_ID = '_dd.playwright.retry_test_id'
|
|
76
74
|
const DEFER_FINAL_STATUS = '_dd.playwright.defer_final_status'
|
|
77
75
|
const noop = () => {}
|
|
@@ -186,7 +184,7 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
186
184
|
isEarlyFlakeDetectionEnabled,
|
|
187
185
|
isEarlyFlakeDetectionFaulty,
|
|
188
186
|
isTestManagementTestsEnabled,
|
|
189
|
-
|
|
187
|
+
testSessionEmptyReason,
|
|
190
188
|
error,
|
|
191
189
|
onDone,
|
|
192
190
|
}) => {
|
|
@@ -212,12 +210,11 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
212
210
|
this.testModuleSpan.setTag(TEST_STATUS, status)
|
|
213
211
|
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
214
212
|
|
|
215
|
-
if (
|
|
213
|
+
if (testSessionEmptyReason) {
|
|
216
214
|
setExpectedEmptyTestSessionTags(
|
|
217
215
|
this.testSessionSpan,
|
|
218
216
|
this.testModuleSpan,
|
|
219
|
-
|
|
220
|
-
EMPTY_SHARD_REASON
|
|
217
|
+
testSessionEmptyReason
|
|
221
218
|
)
|
|
222
219
|
}
|
|
223
220
|
|
|
@@ -526,6 +523,8 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
526
523
|
this.addSub('ci:playwright:test:finish', ({
|
|
527
524
|
span,
|
|
528
525
|
testStatus,
|
|
526
|
+
isExpectedFailure,
|
|
527
|
+
hasNonRetriableError,
|
|
529
528
|
steps,
|
|
530
529
|
error,
|
|
531
530
|
extraTags,
|
|
@@ -565,6 +564,12 @@ class PlaywrightPlugin extends CiPlugin {
|
|
|
565
564
|
if (extraTags) {
|
|
566
565
|
span.addTags(extraTags)
|
|
567
566
|
}
|
|
567
|
+
if (isExpectedFailure) {
|
|
568
|
+
span.setTag('test.result', 'xfail')
|
|
569
|
+
}
|
|
570
|
+
if (hasNonRetriableError === true) {
|
|
571
|
+
span.setTag('test.playwright.has_non_retriable_error', 'true')
|
|
572
|
+
}
|
|
568
573
|
if (isNew) {
|
|
569
574
|
span.setTag(TEST_IS_NEW, 'true')
|
|
570
575
|
if (isEfdRetry) {
|
|
@@ -580,7 +580,7 @@ class VitestPlugin extends CiPlugin {
|
|
|
580
580
|
requestErrorTags,
|
|
581
581
|
vitestPool,
|
|
582
582
|
isVitestNoWorkerInitActive,
|
|
583
|
-
|
|
583
|
+
testSessionEmptyReason,
|
|
584
584
|
onDone,
|
|
585
585
|
}) => {
|
|
586
586
|
for (const [tag, value] of Object.entries(requestErrorTags)) {
|
|
@@ -589,12 +589,11 @@ class VitestPlugin extends CiPlugin {
|
|
|
589
589
|
}
|
|
590
590
|
this.testSessionSpan.setTag(TEST_STATUS, status)
|
|
591
591
|
this.testModuleSpan.setTag(TEST_STATUS, status)
|
|
592
|
-
if (
|
|
592
|
+
if (testSessionEmptyReason) {
|
|
593
593
|
setExpectedEmptyTestSessionTags(
|
|
594
594
|
this.testSessionSpan,
|
|
595
595
|
this.testModuleSpan,
|
|
596
|
-
|
|
597
|
-
'zero_tests'
|
|
596
|
+
testSessionEmptyReason
|
|
598
597
|
)
|
|
599
598
|
}
|
|
600
599
|
if (error) {
|
|
@@ -21,7 +21,7 @@ class WSProducerPlugin extends TracingPlugin {
|
|
|
21
21
|
|
|
22
22
|
bindStart (ctx) {
|
|
23
23
|
const { byteLength, socket, binary } = ctx
|
|
24
|
-
if (!socket
|
|
24
|
+
if (!socket?.spanContext) return
|
|
25
25
|
|
|
26
26
|
const spanTags = socket.spanTags
|
|
27
27
|
const path = getSegment(spanTags['resource.name'], ' ', 1)
|
|
@@ -53,8 +53,15 @@ function copyProperties (original, wrapped) {
|
|
|
53
53
|
const descriptor = /** @type {Descriptor} */ (Object.getOwnPropertyDescriptor(original, key))
|
|
54
54
|
if (descriptor.writable && descriptor.enumerable && descriptor.configurable) {
|
|
55
55
|
wrapped[key] = original[key]
|
|
56
|
-
} else
|
|
57
|
-
|
|
56
|
+
} else {
|
|
57
|
+
try {
|
|
58
|
+
Object.defineProperty(wrapped, key, descriptor)
|
|
59
|
+
} catch (error) {
|
|
60
|
+
const wrappedDescriptor = Object.getOwnPropertyDescriptor(wrapped, key)
|
|
61
|
+
if (wrappedDescriptor?.configurable !== false || wrappedDescriptor.writable) {
|
|
62
|
+
throw error
|
|
63
|
+
}
|
|
64
|
+
}
|
|
58
65
|
}
|
|
59
66
|
}
|
|
60
67
|
}
|
|
@@ -320,19 +327,16 @@ function assertMethod (target, name, method) {
|
|
|
320
327
|
}
|
|
321
328
|
|
|
322
329
|
/**
|
|
323
|
-
* Asserts that a target is not
|
|
330
|
+
* Asserts that a target is not an identifiable class constructor.
|
|
324
331
|
*
|
|
325
332
|
* @param {Function} target - The target function.
|
|
326
|
-
* @throws {
|
|
333
|
+
* @throws {TypeError} If the target is an identifiable class constructor.
|
|
327
334
|
*/
|
|
328
335
|
function assertNotClass (target) {
|
|
329
|
-
//
|
|
330
|
-
// writable one and arrows / async / method-shorthand have none at all. The
|
|
331
|
-
// `'prototype' in target` gate skips the descriptor lookup for the no-prototype
|
|
332
|
-
// shapes; the `in` operator is cheaper than reading `target.prototype` since
|
|
333
|
-
// it returns a boolean instead of materialising the prototype reference.
|
|
336
|
+
// Frozen functions also have a non-writable `prototype`; inspect their source only then.
|
|
334
337
|
if ('prototype' in target &&
|
|
335
|
-
Object.getOwnPropertyDescriptor(target, 'prototype').writable === false
|
|
338
|
+
Object.getOwnPropertyDescriptor(target, 'prototype').writable === false &&
|
|
339
|
+
(!Object.isFrozen(target) || Function.prototype.toString.call(target).startsWith('class'))) {
|
|
336
340
|
throw new TypeError('Target is a native class constructor and cannot be wrapped.')
|
|
337
341
|
}
|
|
338
342
|
}
|
|
@@ -6,6 +6,7 @@ const path = require('node:path')
|
|
|
6
6
|
|
|
7
7
|
const satisfies = require('../../vendor/dist/semifies')
|
|
8
8
|
const hooks = require('../datadog-instrumentations/src/helpers/hooks')
|
|
9
|
+
const { getRewriteTargetNames } = require('../datadog-instrumentations/src/helpers/rewriter/targets')
|
|
9
10
|
const { SYNTHETIC_EXTENSION } = require('./src/constants')
|
|
10
11
|
|
|
11
12
|
const BUILTIN_MODULES = new Set(Module.builtinModules)
|
|
@@ -158,7 +159,9 @@ function hasDatadogLoader (value) {
|
|
|
158
159
|
*/
|
|
159
160
|
function createPackagePathPattern () {
|
|
160
161
|
const names = []
|
|
161
|
-
|
|
162
|
+
const targetPackages = new Set(Object.keys(hooks))
|
|
163
|
+
for (const name of getRewriteTargetNames()) targetPackages.add(name)
|
|
164
|
+
for (const name of targetPackages) {
|
|
162
165
|
if (!name.startsWith('.') && !BUILTIN_MODULES.has(name)) names.push(escapeRegExp(name))
|
|
163
166
|
}
|
|
164
167
|
names.sort()
|
|
@@ -11,7 +11,10 @@ const hooks = require('../../datadog-instrumentations/src/helpers/hooks')
|
|
|
11
11
|
const instrumentations = require('../../datadog-instrumentations/src/helpers/instrumentations')
|
|
12
12
|
const { filename, matchesInstrumentation } = require('../../datadog-instrumentations/src/helpers/instrumentation-utils')
|
|
13
13
|
const { createBundlerRewriter } = require('../../datadog-instrumentations/src/helpers/rewriter')
|
|
14
|
-
const {
|
|
14
|
+
const {
|
|
15
|
+
getRewriteTarget,
|
|
16
|
+
getRewriteTargetNames,
|
|
17
|
+
} = require('../../datadog-instrumentations/src/helpers/rewriter/targets')
|
|
15
18
|
const { isESMFile } = require('../../datadog-esbuild/src/utils')
|
|
16
19
|
const { SYNTHETIC_EXTENSION } = require('./constants')
|
|
17
20
|
|
|
@@ -21,6 +24,7 @@ const MODULE_BINDING = 2
|
|
|
21
24
|
const REQUIRE_BINDING = 4
|
|
22
25
|
const COMMONJS_BINDINGS = MODULE_BINDING | REQUIRE_BINDING
|
|
23
26
|
const targetPackages = new Set(Object.keys(hooks))
|
|
27
|
+
for (const name of getRewriteTargetNames()) targetPackages.add(name)
|
|
24
28
|
const entrypoints = new Map()
|
|
25
29
|
const loadedHooks = new Set()
|
|
26
30
|
const packageCache = new Map()
|
|
@@ -98,7 +102,7 @@ module.exports = function loader (source, inputSourceMap) {
|
|
|
98
102
|
}
|
|
99
103
|
|
|
100
104
|
if (publications.length > 0) code = appendCommonJsPublications(code, publications, dcModule)
|
|
101
|
-
if (rewritten && publications.length === 0) {
|
|
105
|
+
if (rewritten && publications.length === 0 && (rewriteTarget.activate || hooks[rewriteTarget.moduleName])) {
|
|
102
106
|
if (!esm && hasUnsafeCommonJsBindings(code)) {
|
|
103
107
|
this.emitWarning?.(new Error(`Skipped CommonJS activation for unsafe wrapper bindings in ${resourcePath}`))
|
|
104
108
|
this.callback(undefined, source, inputSourceMap)
|
|
@@ -17,21 +17,22 @@ module.exports = {
|
|
|
17
17
|
* Fetches agent information from the /info endpoint
|
|
18
18
|
* @param {URL} url - The agent URL
|
|
19
19
|
* @param {Function} callback - Callback function with signature (err, agentInfo)
|
|
20
|
-
* @param {{ deadline?: number, signal?: AbortSignal }} [options]
|
|
20
|
+
* @param {{ deadline?: number, signal?: AbortSignal, path?: string, retry?: boolean }} [options]
|
|
21
|
+
* Request finalization and routing options
|
|
21
22
|
* @param {Function} [makeRequest] - Request implementation
|
|
22
23
|
*/
|
|
23
24
|
function fetchAgentInfo (url, callback, options = {}, makeRequest = request) {
|
|
24
|
-
const urlKey = url.href
|
|
25
|
+
const urlKey = JSON.stringify([url.href, options.path ?? '/info'])
|
|
25
26
|
|
|
26
27
|
if (cachedUrl !== null && cachedUrl !== urlKey) {
|
|
27
|
-
//
|
|
28
|
+
// Different consumers may query different paths on the same Agent URL.
|
|
28
29
|
clearCache()
|
|
29
30
|
} else if (cachedData !== null && (Date.now() - cachedTimestamp) < CACHE_TTL_MS) {
|
|
30
31
|
// Return cached result if still valid
|
|
31
32
|
return process.nextTick(callback, null, cachedData)
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
options.path
|
|
35
|
+
options.path ??= '/info'
|
|
35
36
|
options.url = url
|
|
36
37
|
makeRequest('', options, (err, res) => {
|
|
37
38
|
if (err) {
|