dd-trace 6.18.0 → 6.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-3rdparty.csv +1 -0
- package/ci/vitest-no-worker-init-setup.mjs +22 -12
- package/index.d.ts +166 -24
- package/package.json +7 -7
- package/packages/datadog-esbuild/index.js +9 -6
- package/packages/datadog-instrumentations/src/ai.js +26 -0
- package/packages/datadog-instrumentations/src/anthropic.js +111 -9
- package/packages/datadog-instrumentations/src/aws-sdk.js +2 -2
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +5 -1
- package/packages/datadog-instrumentations/src/cucumber.js +40 -4
- package/packages/datadog-instrumentations/src/helpers/hooks.js +0 -7
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +2 -2
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +25 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +8 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +2 -0
- package/packages/datadog-instrumentations/src/jest/session-error.js +102 -0
- package/packages/datadog-instrumentations/src/jest.js +3 -10
- package/packages/datadog-instrumentations/src/playwright.js +26 -0
- package/packages/datadog-instrumentations/src/vitest-main.js +4 -1
- package/packages/datadog-instrumentations/src/vitest-worker.js +34 -3
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +83 -29
- package/packages/datadog-plugin-cucumber/src/index.js +19 -4
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +7 -0
- package/packages/datadog-plugin-fetch/src/index.js +3 -0
- package/packages/datadog-plugin-openai/src/stream-helpers.js +50 -1
- package/packages/datadog-plugin-oracledb/src/connection-parser.js +3 -1
- package/packages/datadog-plugin-undici/src/index.js +5 -0
- package/packages/dd-trace/src/aiguard/client.js +38 -19
- package/packages/dd-trace/src/aiguard/integrations/anthropic.js +29 -4
- package/packages/dd-trace/src/aiguard/integrations/index.js +1 -1
- package/packages/dd-trace/src/aiguard/integrations/openai.js +20 -56
- package/packages/dd-trace/src/aiguard/integrations/stream.js +60 -0
- package/packages/dd-trace/src/aiguard/messages/anthropic.js +64 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +8 -4
- package/packages/dd-trace/src/config/remote_config.js +12 -0
- package/packages/dd-trace/src/config/supported-configurations.json +26 -5
- package/packages/dd-trace/src/constants.js +2 -0
- package/packages/dd-trace/src/debugger/constants.js +13 -4
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +14 -1
- package/packages/dd-trace/src/debugger/devtools_client/condition.js +109 -6
- package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +65 -6
- package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +22 -10
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +12 -8
- package/packages/dd-trace/src/debugger/devtools_client/send.js +14 -0
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +43 -5
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +3 -8
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +2 -122
- package/packages/dd-trace/src/debugger/guardrail-metrics.js +0 -2
- package/packages/dd-trace/src/debugger/index.js +60 -28
- package/packages/dd-trace/src/debugger/inspect-segment.js +92 -23
- package/packages/dd-trace/src/debugger/pause-duration-histogram.js +74 -0
- package/packages/dd-trace/src/debugger/probe_sampler.js +192 -52
- package/packages/dd-trace/src/debugger/redaction.js +139 -0
- package/packages/dd-trace/src/knuth-hash.js +17 -0
- package/packages/dd-trace/src/llmobs/constants/tags.js +33 -8
- package/packages/dd-trace/src/llmobs/experiments/client.js +66 -2
- package/packages/dd-trace/src/llmobs/experiments/evaluator.js +138 -0
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +213 -90
- package/packages/dd-trace/src/llmobs/experiments/index.js +40 -1
- package/packages/dd-trace/src/llmobs/experiments/noop.js +53 -2
- package/packages/dd-trace/src/llmobs/experiments/remote-evaluator.js +110 -0
- package/packages/dd-trace/src/llmobs/experiments/util.js +34 -6
- package/packages/dd-trace/src/llmobs/gen-ai-tags.js +127 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +18 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +33 -8
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +77 -41
- package/packages/dd-trace/src/llmobs/plugins/base.js +154 -15
- package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +162 -10
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +65 -16
- package/packages/dd-trace/src/llmobs/plugins/genai/index.js +14 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +34 -35
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +10 -0
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +25 -2
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +8 -7
- package/packages/dd-trace/src/llmobs/plugins/openai/index.js +15 -2
- package/packages/dd-trace/src/llmobs/plugins/openai/realtime.js +5 -0
- package/packages/dd-trace/src/llmobs/plugins/vertexai.js +7 -0
- package/packages/dd-trace/src/llmobs/prompts/prompt.js +2 -1
- package/packages/dd-trace/src/llmobs/sdk.js +19 -3
- package/packages/dd-trace/src/llmobs/span_processor.js +52 -91
- package/packages/dd-trace/src/llmobs/tagger.js +37 -36
- package/packages/dd-trace/src/llmobs/writers/base.js +1 -6
- package/packages/dd-trace/src/openfeature/constants/constants.js +10 -0
- package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -1
- package/packages/dd-trace/src/openfeature/writers/base.js +39 -18
- package/packages/dd-trace/src/openfeature/writers/flag-eval-evp-hook.js +105 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluation-aggregation.js +190 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluation-consumer.js +233 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluation-context.js +262 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluation-payload.js +181 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluation-pii.js +108 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluation-telemetry.js +154 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluation-worker.js +77 -0
- package/packages/dd-trace/src/openfeature/writers/flag-evaluations.js +398 -0
- package/packages/dd-trace/src/openfeature/writers/util.js +5 -4
- package/packages/dd-trace/src/opentelemetry/context_manager.js +3 -0
- package/packages/dd-trace/src/opentelemetry/span_context.js +6 -2
- package/packages/dd-trace/src/opentelemetry/trace/index.js +5 -0
- package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +6 -0
- package/packages/dd-trace/src/opentelemetry/tracer.js +16 -18
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +116 -25
- package/packages/dd-trace/src/opentracing/propagation/tracestate.js +80 -16
- package/packages/dd-trace/src/opentracing/span.js +14 -9
- package/packages/dd-trace/src/opentracing/tracer.js +10 -3
- package/packages/dd-trace/src/otel-sampling.js +171 -0
- package/packages/dd-trace/src/plugins/index.js +0 -1
- package/packages/dd-trace/src/plugins/util/test.js +4 -0
- package/packages/dd-trace/src/priority_sampler.js +155 -33
- package/packages/dd-trace/src/profiler.js +51 -5
- package/packages/dd-trace/src/profiling/profiler.js +24 -20
- package/packages/dd-trace/src/remote_config/capabilities.js +9 -0
- package/packages/dd-trace/src/ritm.js +12 -9
- package/packages/dd-trace/src/sampler.js +2 -5
- package/packages/dd-trace/src/sampling_rule.js +4 -8
- package/packages/dd-trace/src/span_format.js +6 -0
- package/packages/dd-trace/src/span_processor.js +32 -3
- package/packages/dd-trace/src/standalone/index.js +5 -4
- package/packages/dd-trace/src/standalone/tracesource_priority_sampler.js +25 -5
- package/packages/dd-trace/src/telemetry/metrics.js +4 -3
- package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
- package/packages/datadog-instrumentations/src/postgres.js +0 -7
- package/packages/datadog-instrumentations/src/supabase.js +0 -15
|
@@ -198,11 +198,11 @@ function wrapSmithySend (send) {
|
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
return channels.start.runStores(ctx, () => {
|
|
201
|
-
//
|
|
201
|
+
// Resolve region tags independently; send reports provider failures through its promise or callback.
|
|
202
202
|
this.config.region().then(region => {
|
|
203
203
|
ctx.region = region
|
|
204
204
|
channels.region.publish(ctx)
|
|
205
|
-
})
|
|
205
|
+
}, () => {})
|
|
206
206
|
|
|
207
207
|
if (cbExists) {
|
|
208
208
|
args[args.length - 1] = shimmer.wrapCallback(cb, cb => function (err, result) {
|
|
@@ -44,6 +44,7 @@ const chunkEmitTimes = new WeakMap()
|
|
|
44
44
|
* tools: Map<string, ToolState>,
|
|
45
45
|
* subagents: Map<string, Record<string, unknown>>,
|
|
46
46
|
* prompt?: string,
|
|
47
|
+
* systemPrompt?: string | string[] | { type: string, prompt?: string | string[], append?: string },
|
|
47
48
|
* model?: string,
|
|
48
49
|
* resume?: string,
|
|
49
50
|
* maxTurns?: number,
|
|
@@ -235,6 +236,7 @@ function onQueryStart (ctx) {
|
|
|
235
236
|
const prompt = queryArg.prompt
|
|
236
237
|
const sessionCtx = {
|
|
237
238
|
prompt: typeof prompt === 'string' ? prompt : undefined,
|
|
239
|
+
systemPrompt: options.systemPrompt,
|
|
238
240
|
model: options.model,
|
|
239
241
|
resume: options.resume,
|
|
240
242
|
maxTurns: options.maxTurns,
|
|
@@ -451,6 +453,7 @@ function processStep (
|
|
|
451
453
|
llmCh.traceSync(() => {}, {
|
|
452
454
|
model,
|
|
453
455
|
usage,
|
|
456
|
+
systemPrompt: parentToolUseId ? undefined : sessionCtx?.systemPrompt,
|
|
454
457
|
startTime: stepStartTime,
|
|
455
458
|
finishTime: chunkEmitTimes.get(chunks[messageEndIdx - 1]),
|
|
456
459
|
chunks,
|
|
@@ -529,7 +532,8 @@ function processChunks (chunks, agentCtx) {
|
|
|
529
532
|
const sessionCtx = agentCtx.sessionCtx
|
|
530
533
|
const getLifecycle = createStreamLookup(chunks)
|
|
531
534
|
|
|
532
|
-
const
|
|
535
|
+
const initChunk = chunks.find(chunk => chunk.type === 'system' && chunk.subtype === 'init') || chunks[0]
|
|
536
|
+
const { type, subtype, ...rest } = initChunk
|
|
533
537
|
Object.assign(agentCtx, rest)
|
|
534
538
|
if (sessionCtx) {
|
|
535
539
|
if (sessionCtx.sessionId) agentCtx.session_id = sessionCtx.sessionId
|
|
@@ -1418,13 +1418,49 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
|
|
|
1418
1418
|
|
|
1419
1419
|
if (isImpactedTestsEnabled) {
|
|
1420
1420
|
const setIsModified = (receivedIsModified) => { isModified = receivedIsModified }
|
|
1421
|
-
const
|
|
1422
|
-
|
|
1423
|
-
|
|
1421
|
+
const gherkinNodes = []
|
|
1422
|
+
// Prefix ranges catch removed Backgrounds, which have no AST node in the target document.
|
|
1423
|
+
const gherkinScopeRanges = []
|
|
1424
|
+
const feature = gherkinDocument.feature
|
|
1425
|
+
const featureChildren = feature?.children
|
|
1426
|
+
if (featureChildren?.length) {
|
|
1427
|
+
const firstFeatureChild = featureChildren[0]
|
|
1428
|
+
const firstFeatureNode = firstFeatureChild.background ?? firstFeatureChild.rule ?? firstFeatureChild.scenario
|
|
1429
|
+
const firstFeatureLine = firstFeatureNode.tags?.[0]?.location.line ?? firstFeatureNode.location.line
|
|
1430
|
+
const featurePrefix = [feature.location.line, firstFeatureLine - 1]
|
|
1431
|
+
let featureBackground
|
|
1432
|
+
for (const child of featureChildren) {
|
|
1433
|
+
if (child.background) {
|
|
1434
|
+
featureBackground = child.background
|
|
1435
|
+
continue
|
|
1436
|
+
}
|
|
1437
|
+
const rule = child.rule
|
|
1438
|
+
const children = rule?.children ?? [child]
|
|
1439
|
+
let ruleBackground
|
|
1440
|
+
for (const { background } of children) {
|
|
1441
|
+
if (background) ruleBackground = background
|
|
1442
|
+
}
|
|
1443
|
+
for (const { scenario } of children) {
|
|
1444
|
+
if (scenario && pickle.astNodeIds.includes(scenario.id)) {
|
|
1445
|
+
if (!featureBackground) gherkinScopeRanges.push(featurePrefix)
|
|
1446
|
+
if (rule?.children?.length && !ruleBackground) {
|
|
1447
|
+
const firstRuleChild = rule.children.at(0)
|
|
1448
|
+
const firstRuleNode = firstRuleChild.background ?? firstRuleChild.scenario
|
|
1449
|
+
const firstRuleLine = firstRuleNode.tags?.[0]?.location.line ?? firstRuleNode.location.line
|
|
1450
|
+
gherkinScopeRanges.push([rule.location.line, firstRuleLine - 1])
|
|
1451
|
+
}
|
|
1452
|
+
if (featureBackground) gherkinNodes.push(featureBackground)
|
|
1453
|
+
if (ruleBackground) gherkinNodes.push(ruleBackground)
|
|
1454
|
+
gherkinNodes.push(scenario)
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1424
1459
|
const stepIds = testCase?.testSteps?.flatMap(testStep => testStep.stepDefinitionIds)
|
|
1425
1460
|
|
|
1426
1461
|
isModifiedCh.publish({
|
|
1427
|
-
|
|
1462
|
+
gherkinNodes,
|
|
1463
|
+
gherkinScopeRanges,
|
|
1428
1464
|
testFileAbsolutePath: gherkinDocument.uri,
|
|
1429
1465
|
modifiedFiles,
|
|
1430
1466
|
stepIds,
|
|
@@ -2,12 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
module.exports = {
|
|
4
4
|
// Only list unprefixed node modules. They will automatically be instrumented as prefixed and unprefixed.
|
|
5
|
-
'@supabase/auth-js': { esmFirst: true, fn: () => require('../supabase') },
|
|
6
|
-
'@supabase/functions-js': { esmFirst: true, fn: () => require('../supabase') },
|
|
7
|
-
'@supabase/postgrest-js': { esmFirst: true, fn: () => require('../supabase') },
|
|
8
|
-
'@supabase/realtime-js': { esmFirst: true, fn: () => require('../supabase') },
|
|
9
|
-
'@supabase/storage-js': { esmFirst: true, fn: () => require('../supabase') },
|
|
10
|
-
'@supabase/supabase-js': { esmFirst: true, fn: () => require('../supabase') },
|
|
11
5
|
child_process: () => require('../child_process'),
|
|
12
6
|
crypto: () => require('../crypto'),
|
|
13
7
|
dns: () => require('../dns'),
|
|
@@ -142,7 +136,6 @@ module.exports = {
|
|
|
142
136
|
'pino-pretty': () => require('../pino'),
|
|
143
137
|
playwright: () => require('../playwright'),
|
|
144
138
|
'playwright-core': () => require('../playwright'),
|
|
145
|
-
postgres: { esmFirst: true, fn: () => require('../postgres') },
|
|
146
139
|
'promise-js': () => require('../promise-js'),
|
|
147
140
|
promise: () => require('../promise'),
|
|
148
141
|
protobufjs: () => require('../protobufjs'),
|
|
@@ -17,10 +17,10 @@ const registry = [
|
|
|
17
17
|
{ instrumentations: require('./instrumentations/modelcontextprotocol-sdk') },
|
|
18
18
|
{ instrumentations: require('./instrumentations/openai-agents') },
|
|
19
19
|
{ instrumentations: require('./instrumentations/playwright') },
|
|
20
|
-
{ instrumentations: require('./instrumentations/postgres') },
|
|
20
|
+
{ activate: true, instrumentations: require('./instrumentations/postgres') },
|
|
21
21
|
{ instrumentations: require('./instrumentations/webdriverio') },
|
|
22
22
|
{ instrumentations: require('./instrumentations/aws-durable-execution-sdk-js') },
|
|
23
|
-
{ instrumentations: require('./instrumentations/supabase') },
|
|
23
|
+
{ activate: true, instrumentations: require('./instrumentations/supabase') },
|
|
24
24
|
]
|
|
25
25
|
|
|
26
26
|
const activatedModules = new Set()
|
|
@@ -88,6 +88,31 @@ module.exports = [
|
|
|
88
88
|
},
|
|
89
89
|
channelName: 'resolveLanguageModel',
|
|
90
90
|
},
|
|
91
|
+
// AI SDK v7 filters runtime context from tracing channel events unless each key is included.
|
|
92
|
+
{
|
|
93
|
+
module: {
|
|
94
|
+
name: 'ai',
|
|
95
|
+
versionRange: '>=7.0.0',
|
|
96
|
+
filePath: 'dist/index.js',
|
|
97
|
+
},
|
|
98
|
+
functionQuery: {
|
|
99
|
+
functionName: 'generateText',
|
|
100
|
+
kind: 'Async',
|
|
101
|
+
},
|
|
102
|
+
channelName: 'includeRuntimeContext',
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
module: {
|
|
106
|
+
name: 'ai',
|
|
107
|
+
versionRange: '>=7.0.0',
|
|
108
|
+
filePath: 'dist/index.js',
|
|
109
|
+
},
|
|
110
|
+
functionQuery: {
|
|
111
|
+
functionName: 'streamText',
|
|
112
|
+
kind: 'Sync',
|
|
113
|
+
},
|
|
114
|
+
channelName: 'includeRuntimeContext',
|
|
115
|
+
},
|
|
91
116
|
{
|
|
92
117
|
module: {
|
|
93
118
|
name: 'ai',
|
package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
// Playwright keeps several hook targets in private local classes/functions.
|
|
4
4
|
// Keep these rewrites limited to bundled internals that addHook cannot wrap.
|
|
5
5
|
module.exports = [
|
|
6
|
+
...[
|
|
7
|
+
['>=1.38.0 <1.60.0', 'lib/common/config.js'],
|
|
8
|
+
['>=1.60.0', 'lib/common/index.js'],
|
|
9
|
+
].map(([versionRange, filePath]) => ({
|
|
10
|
+
module: { name: 'playwright', versionRange, filePath },
|
|
11
|
+
functionQuery: { className: 'FullProjectInternal', methodName: 'constructor', kind: 'Sync' },
|
|
12
|
+
channelName: 'FullProjectInternal',
|
|
13
|
+
})),
|
|
6
14
|
{
|
|
7
15
|
module: {
|
|
8
16
|
name: '@playwright/test',
|
|
@@ -77,6 +77,8 @@
|
|
|
77
77
|
"jasmine-core/lib/jasmine-core/jasmine.js": "jasmine-core",
|
|
78
78
|
"mercurius/index.js": "mercurius",
|
|
79
79
|
"playwright-core/lib/coreBundle.js": "playwright-core",
|
|
80
|
+
"playwright/lib/common/config.js": "playwright",
|
|
81
|
+
"playwright/lib/common/index.js": "playwright",
|
|
80
82
|
"playwright/lib/index.js": "playwright",
|
|
81
83
|
"playwright/lib/runner/index.js": "playwright",
|
|
82
84
|
"playwright/lib/runner/lastRun.js": "playwright",
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { stripVTControlCharacters } = require('node:util')
|
|
4
|
+
|
|
5
|
+
const { MAX_META_VALUE_LENGTH_TEST_OPTIMIZATION: MAX_LENGTH } =
|
|
6
|
+
require('../../../dd-trace/src/encode/tags-processors')
|
|
7
|
+
const log = require('../../../dd-trace/src/log')
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {{ name?: unknown, type?: unknown, message?: unknown, stack?: unknown }} SuiteError
|
|
11
|
+
* @typedef {object} JestResults
|
|
12
|
+
* @property {number} [numFailedTestSuites]
|
|
13
|
+
* @property {number} [numFailedTests]
|
|
14
|
+
* @property {{ testExecError?: SuiteError, failureMessage?: unknown, numFailingTests?: number }[]} [testResults]
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Includes complete, deduplicated suite errors only when all details fit.
|
|
19
|
+
*
|
|
20
|
+
* @param {JestResults} results
|
|
21
|
+
* @returns {Error}
|
|
22
|
+
*/
|
|
23
|
+
function getSessionError (results = {}) {
|
|
24
|
+
const { numFailedTestSuites = 0, numFailedTests = 0 } = results
|
|
25
|
+
const summary = `Failed test suites: ${numFailedTestSuites}. Failed tests: ${numFailedTests}`
|
|
26
|
+
const error = new Error(summary)
|
|
27
|
+
const groups = new Map()
|
|
28
|
+
let size = 0
|
|
29
|
+
try {
|
|
30
|
+
const { testResults } = results
|
|
31
|
+
if (!testResults) return error
|
|
32
|
+
for (const result of testResults) {
|
|
33
|
+
let name, message, stack
|
|
34
|
+
try {
|
|
35
|
+
const { testExecError } = result
|
|
36
|
+
if (testExecError && !result.numFailingTests) {
|
|
37
|
+
name = readField(testExecError.name)
|
|
38
|
+
if (name === '') name = readField(testExecError.type)
|
|
39
|
+
message = readField(testExecError.message)
|
|
40
|
+
stack = readField(testExecError.stack)
|
|
41
|
+
} else {
|
|
42
|
+
// Jest's report includes assertion failures, hook errors and any suite execution error.
|
|
43
|
+
name = 'Error'
|
|
44
|
+
message = readField(result.failureMessage)
|
|
45
|
+
stack = message
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
log.debug('Skipping unreadable Jest suite error in the test session summary')
|
|
49
|
+
continue
|
|
50
|
+
}
|
|
51
|
+
if (name === undefined || message === undefined || stack === undefined) return omitDetails(error)
|
|
52
|
+
if (!message && !stack) continue
|
|
53
|
+
name ||= 'Error'
|
|
54
|
+
const key = JSON.stringify([name, message, stack])
|
|
55
|
+
const group = groups.get(key)
|
|
56
|
+
if (group) {
|
|
57
|
+
group.count++
|
|
58
|
+
} else {
|
|
59
|
+
// Retained text must fit across error.message and error.stack; also bound grouping memory.
|
|
60
|
+
size += name.length + message.length + stack.length
|
|
61
|
+
if (size > 2 * MAX_LENGTH) return omitDetails(error)
|
|
62
|
+
groups.set(key, { name, message, stack, count: 1 })
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
log.debug('Unable to read Jest results for the test session summary')
|
|
67
|
+
return error
|
|
68
|
+
}
|
|
69
|
+
if (!groups.size) return error
|
|
70
|
+
|
|
71
|
+
const errors = [...groups.keys()].sort().map(key => groups.get(key))
|
|
72
|
+
let message = summary
|
|
73
|
+
let stack = ''
|
|
74
|
+
for (const { name, message: detail, stack: originalStack, count } of errors) {
|
|
75
|
+
message += `\n\n${name}: ${detail} (${count} suite${count === 1 ? '' : 's'})`
|
|
76
|
+
const prefix = errors.length === 1 ? '' : `Affected suites: ${count}\n`
|
|
77
|
+
stack += (stack ? '\n\n' : '') + prefix + (originalStack || `${name}: ${detail}`)
|
|
78
|
+
if (message.length > MAX_LENGTH || stack.length > MAX_LENGTH) return omitDetails(error)
|
|
79
|
+
}
|
|
80
|
+
error.name = errors.length === 1 ? errors[0].name : 'Error'
|
|
81
|
+
error.message = message
|
|
82
|
+
error.stack = stack
|
|
83
|
+
return error
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Returns undefined for oversized input before scanning or copying it.
|
|
88
|
+
* @param {unknown} value
|
|
89
|
+
*/
|
|
90
|
+
function readField (value) {
|
|
91
|
+
if (typeof value !== 'string') return ''
|
|
92
|
+
if (value.length > MAX_LENGTH) return
|
|
93
|
+
return stripVTControlCharacters(value)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** @param {Error} error */
|
|
97
|
+
function omitDetails (error) {
|
|
98
|
+
error.message += '\n\nError details exceed the size limit. See test and suite events for full details.'
|
|
99
|
+
return error
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
module.exports = { getSessionError }
|
|
@@ -58,6 +58,7 @@ const {
|
|
|
58
58
|
addCoverageBackfillUntestedFiles,
|
|
59
59
|
getCoverageBackfillFiles,
|
|
60
60
|
} = require('./jest/coverage-backfill')
|
|
61
|
+
const { getSessionError } = require('./jest/session-error')
|
|
61
62
|
const {
|
|
62
63
|
getChannelPromise,
|
|
63
64
|
publishWithCompletion,
|
|
@@ -2739,13 +2740,6 @@ function applySkippedCoverageToJestCoverageMap (coverageMap, rootDir) {
|
|
|
2739
2740
|
)
|
|
2740
2741
|
}
|
|
2741
2742
|
|
|
2742
|
-
function getSessionFinishError (results) {
|
|
2743
|
-
const numFailedTestSuites = results?.numFailedTestSuites || 0
|
|
2744
|
-
const numFailedTests = results?.numFailedTests || 0
|
|
2745
|
-
|
|
2746
|
-
return new Error(`Failed test suites: ${numFailedTestSuites}. Failed tests: ${numFailedTests}`)
|
|
2747
|
-
}
|
|
2748
|
-
|
|
2749
2743
|
function getTestSessionCoveragePayload (results, fallbackRootDir) {
|
|
2750
2744
|
const payload = {}
|
|
2751
2745
|
if (!shouldReportCodeCoverageLinesPct()) return payload
|
|
@@ -2847,7 +2841,7 @@ function getTestSessionFinishPayload (status, error, extra = {}) {
|
|
|
2847
2841
|
async function finishBailTestSession (results, fallbackRootDir) {
|
|
2848
2842
|
await waitForTestSessionFinish(getTestSessionFinishPayload(
|
|
2849
2843
|
'fail',
|
|
2850
|
-
|
|
2844
|
+
getSessionError(results),
|
|
2851
2845
|
getTestSessionCoveragePayload(results, fallbackRootDir)
|
|
2852
2846
|
))
|
|
2853
2847
|
}
|
|
@@ -3170,7 +3164,6 @@ function getCliWrapper (isNewJestVersion) {
|
|
|
3170
3164
|
|
|
3171
3165
|
const {
|
|
3172
3166
|
results: {
|
|
3173
|
-
numFailedTestSuites,
|
|
3174
3167
|
numFailedTests,
|
|
3175
3168
|
numPassedTests,
|
|
3176
3169
|
numRuntimeErrorTestSuites = 0,
|
|
@@ -3351,7 +3344,7 @@ function getCliWrapper (isNewJestVersion) {
|
|
|
3351
3344
|
status = testSessionEmptyReason ? 'skip' : 'pass'
|
|
3352
3345
|
} else {
|
|
3353
3346
|
status = 'fail'
|
|
3354
|
-
error =
|
|
3347
|
+
error = getSessionError(result.results)
|
|
3355
3348
|
}
|
|
3356
3349
|
|
|
3357
3350
|
await waitForTestSessionFinish(getTestSessionFinishPayload(status, error, {
|
|
@@ -75,6 +75,7 @@ const artifactsRecorderScreenshotPathCh =
|
|
|
75
75
|
const snapshotRecorderScreenshotPathCh = tracingChannel('orchestrion:playwright:SnapshotRecorder_createAttachmentPath')
|
|
76
76
|
const saveAutomaticVideoCh = tracingChannel('orchestrion:playwright:saveAutomaticVideo')
|
|
77
77
|
const pageGotoCh = tracingChannel('orchestrion:playwright-core:Page_goto')
|
|
78
|
+
const fullProjectInternalCh = tracingChannel('orchestrion:playwright:FullProjectInternal')
|
|
78
79
|
|
|
79
80
|
const testToCtx = new WeakMap()
|
|
80
81
|
const originalTestIdsByRetry = new WeakMap()
|
|
@@ -91,6 +92,8 @@ const isFailureScreenshotUploadEnabled =
|
|
|
91
92
|
getValueFromEnvSources('DD_TEST_FAILURE_SCREENSHOTS_ENABLED') === true
|
|
92
93
|
const isFailureVideoUploadEnabled =
|
|
93
94
|
getValueFromEnvSources('DD_TEST_FAILURE_VIDEOS_ENABLED') === true
|
|
95
|
+
const shouldEnableFailureScreenshots = getValueFromEnvSources('DD_TEST_FAILURE_SCREENSHOTS_ENABLED', true) === true
|
|
96
|
+
const shouldEnableFailureVideos = getValueFromEnvSources('DD_TEST_FAILURE_VIDEOS_ENABLED', true) === true
|
|
94
97
|
|
|
95
98
|
let applyRepeatEachIndex = null
|
|
96
99
|
let reporterError
|
|
@@ -1057,6 +1060,8 @@ function testEndHandler ({
|
|
|
1057
1060
|
if (isFlakyTestRetriesEnabled && !testProperties.attemptToFix && !test._ddIsEfdRetry &&
|
|
1058
1061
|
!(test._ddIsNew || test._ddIsModified) &&
|
|
1059
1062
|
atrRetryCount != null && atrRetryCount > 0 &&
|
|
1063
|
+
// Serial suites can add skipped results before a test's first execution.
|
|
1064
|
+
results.some((result, index) => index < results.length - 1 && result.status !== 'skipped') &&
|
|
1060
1065
|
!willRetry && testResultStatus !== expectedStatus &&
|
|
1061
1066
|
testStatuses.every(status => status === 'fail')) {
|
|
1062
1067
|
test._ddHasFailedAllRetries = true
|
|
@@ -1882,6 +1887,27 @@ dispatcherRunCh.subscribe({
|
|
|
1882
1887
|
},
|
|
1883
1888
|
})
|
|
1884
1889
|
|
|
1890
|
+
fullProjectInternalCh.subscribe({
|
|
1891
|
+
end ({ self, error }) {
|
|
1892
|
+
if (error || !libraryConfigurationCh.hasSubscribers) return
|
|
1893
|
+
|
|
1894
|
+
// Workers reload the config, so apply capture settings as each project is resolved in either process.
|
|
1895
|
+
const { project } = self
|
|
1896
|
+
if (shouldEnableFailureScreenshots && !isFailureScreenshotCaptureEnabled([project])) {
|
|
1897
|
+
const screenshot = project.use.screenshot
|
|
1898
|
+
project.use.screenshot = typeof screenshot === 'object' && screenshot !== null
|
|
1899
|
+
? { ...screenshot, mode: 'only-on-failure' }
|
|
1900
|
+
: 'only-on-failure'
|
|
1901
|
+
}
|
|
1902
|
+
if (shouldEnableFailureVideos && !isFailureVideoCaptureEnabled([project])) {
|
|
1903
|
+
const video = project.use.video
|
|
1904
|
+
project.use.video = typeof video === 'object' && video !== null
|
|
1905
|
+
? { ...video, mode: 'retain-on-failure' }
|
|
1906
|
+
: 'retain-on-failure'
|
|
1907
|
+
}
|
|
1908
|
+
},
|
|
1909
|
+
})
|
|
1910
|
+
|
|
1885
1911
|
dispatcherCreateWorkerCh.subscribe({
|
|
1886
1912
|
end (ctx) {
|
|
1887
1913
|
onDispatcherCreateWorker(ctx.self, ctx.result)
|
|
@@ -1981,6 +1981,9 @@ function getWrappedOn (on) {
|
|
|
1981
1981
|
if (event !== 'message') {
|
|
1982
1982
|
return on.apply(this, arguments)
|
|
1983
1983
|
}
|
|
1984
|
+
// Reply through Vitest's pool worker, which owns its version-specific serialization.
|
|
1985
|
+
// The listener's `this` is the underlying ChildProcess or Worker instead.
|
|
1986
|
+
const worker = this
|
|
1984
1987
|
// `arguments[1]` is the callback function, which
|
|
1985
1988
|
// we modify to intercept our messages to not interfere
|
|
1986
1989
|
// with vitest's own messages
|
|
@@ -1988,7 +1991,7 @@ function getWrappedOn (on) {
|
|
|
1988
1991
|
if (message.type !== 'Buffer' && Array.isArray(message)) {
|
|
1989
1992
|
const [interprocessCode, data] = message
|
|
1990
1993
|
if (
|
|
1991
|
-
handleEfdAdmissionMessage(
|
|
1994
|
+
handleEfdAdmissionMessage(worker, interprocessCode, data) ||
|
|
1992
1995
|
handleWorkerReport(interprocessCode, data)
|
|
1993
1996
|
) {
|
|
1994
1997
|
// If we execute the callback vitest crashes, as the message is not supported
|
|
@@ -4,6 +4,7 @@ const fs = require('node:fs')
|
|
|
4
4
|
const path = require('node:path')
|
|
5
5
|
const { performance } = require('node:perf_hooks')
|
|
6
6
|
const { fileURLToPath } = require('node:url')
|
|
7
|
+
const { deserialize } = require('node:v8')
|
|
7
8
|
const { isMainThread, parentPort } = require('node:worker_threads')
|
|
8
9
|
|
|
9
10
|
const { channel } = require('dc-polyfill')
|
|
@@ -47,6 +48,7 @@ const {
|
|
|
47
48
|
|
|
48
49
|
const EFD_SUITE_ADMISSION_TIMEOUT_MS = 5000
|
|
49
50
|
const logSubmissionFlushCh = channel('ci:log-submission:flush')
|
|
51
|
+
const wrappedTestRunners = new WeakSet()
|
|
50
52
|
const taskToCtx = new WeakMap()
|
|
51
53
|
const taskToTestProperties = new WeakMap()
|
|
52
54
|
const taskToStatuses = new WeakMap()
|
|
@@ -412,6 +414,14 @@ function finishEfdSuiteAdmissionRequest (requestId, allowed) {
|
|
|
412
414
|
* @param {unknown} message
|
|
413
415
|
*/
|
|
414
416
|
function handleEfdSuiteAdmissionResponse (message) {
|
|
417
|
+
// Older Vitest 4 forks serialize parent messages explicitly; newer forks and threads send plain values.
|
|
418
|
+
if (Buffer.isBuffer(message) || (message?.type === 'Buffer' && Array.isArray(message.data))) {
|
|
419
|
+
try {
|
|
420
|
+
message = deserialize(Buffer.from(message))
|
|
421
|
+
} catch {
|
|
422
|
+
return
|
|
423
|
+
}
|
|
424
|
+
}
|
|
415
425
|
if (!Array.isArray(message) || message[0] !== VITEST_WORKER_EFD_SUITE_ADMISSION_RESPONSE_CODE) return
|
|
416
426
|
|
|
417
427
|
const { allowed, requestId } = message[1] || {}
|
|
@@ -520,6 +530,11 @@ function wrapSuiteHookFn (hookType, fn, fallbackTask) {
|
|
|
520
530
|
}
|
|
521
531
|
|
|
522
532
|
function wrapVitestTestRunner (VitestTestRunner) {
|
|
533
|
+
// ESM hooks can expose the same runner through multiple module identities.
|
|
534
|
+
// Wrapping its lifecycle methods twice would report each retry twice.
|
|
535
|
+
if (wrappedTestRunners.has(VitestTestRunner)) return
|
|
536
|
+
wrappedTestRunners.add(VitestTestRunner)
|
|
537
|
+
|
|
523
538
|
// `onBeforeRunTask` is run before any repetition or attempt is run
|
|
524
539
|
// `onBeforeRunTask` is an async function
|
|
525
540
|
shimmer.wrap(VitestTestRunner.prototype, 'onBeforeRunTask', onBeforeRunTask => async function (task) {
|
|
@@ -623,6 +638,13 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
623
638
|
shimmer.wrap(VitestTestRunner.prototype, 'onAfterRunTask', onAfterRunTask => function (task) {
|
|
624
639
|
const { isTestManagementTestsEnabled } = getProvidedContext()
|
|
625
640
|
|
|
641
|
+
// Vitest 5 aggregates failed repetitions into the final state. Preserve the last attempt's actual result.
|
|
642
|
+
if ((attemptToFixTasks.has(task) || efdRetryTasks.has(task)) &&
|
|
643
|
+
task.result.state === 'fail' && taskToCtx.get(task)?.status === 'pass' &&
|
|
644
|
+
(task.result.errors?.length ?? 0) === (taskToReportedErrorCount.get(task) ?? 0)) {
|
|
645
|
+
task.result.state = 'pass'
|
|
646
|
+
}
|
|
647
|
+
|
|
626
648
|
if (isTestManagementTestsEnabled) {
|
|
627
649
|
const isAttemptingToFix = attemptToFixTasks.has(task)
|
|
628
650
|
const isQuarantined = quarantinedTasks.has(task)
|
|
@@ -745,7 +767,12 @@ function wrapVitestTestRunner (VitestTestRunner) {
|
|
|
745
767
|
}
|
|
746
768
|
}
|
|
747
769
|
|
|
748
|
-
|
|
770
|
+
let lastExecutionStatus = task.result.state
|
|
771
|
+
if (lastExecutionStatus === 'run' && numRepetition > 0) {
|
|
772
|
+
// Vitest 5 resets the state before each repetition but retains errors from earlier attempts.
|
|
773
|
+
const reportedErrorCount = taskToReportedErrorCount.get(task) ?? 0
|
|
774
|
+
lastExecutionStatus = (task.result.errors?.length ?? 0) > reportedErrorCount ? 'fail' : 'pass'
|
|
775
|
+
}
|
|
749
776
|
const isAtf = attemptToFixTasks.has(task)
|
|
750
777
|
const isEfd = efdRetryTasks.has(task)
|
|
751
778
|
const shouldTrackStatuses = isEfd || isAtf
|
|
@@ -1101,14 +1128,18 @@ function getStartTestsWrapper (frameworkVersion) {
|
|
|
1101
1128
|
isDisabled: disabledTasks.has(task),
|
|
1102
1129
|
isQuarantined: quarantinedTasks.has(task),
|
|
1103
1130
|
})
|
|
1104
|
-
} else if (state === 'pass' && !isSwitchedStatus) {
|
|
1131
|
+
} else if ((state === 'pass' && !isSwitchedStatus) || switchedStatus === 'pass') {
|
|
1132
|
+
// Attempt-to-fix can pass its last attempt while an earlier failure still fails the suite.
|
|
1133
|
+
if (switchedStatus === 'pass' && errors?.length) {
|
|
1134
|
+
testSuiteError = testError
|
|
1135
|
+
}
|
|
1105
1136
|
if (testCtx) {
|
|
1106
1137
|
const isSkippedByTestManagement =
|
|
1107
1138
|
!attemptToFixTasks.has(task) && (disabledTasks.has(task) || quarantinedTasks.has(task))
|
|
1108
1139
|
const promises = {}
|
|
1109
1140
|
testPassCh.publish({
|
|
1110
1141
|
task,
|
|
1111
|
-
finalStatus: isSkippedByTestManagement ? 'skip' :
|
|
1142
|
+
finalStatus: isSkippedByTestManagement ? 'skip' : state,
|
|
1112
1143
|
earlyFlakeAbortReason: efdSlowAbortedTasks.has(task) ? 'slow' : undefined,
|
|
1113
1144
|
promises,
|
|
1114
1145
|
...testCtx.currentStore,
|