dd-trace 5.110.0 → 5.112.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 -2
- package/README.md +13 -8
- package/ci/init.js +21 -2
- package/ci/vitest-no-worker-init-setup.mjs +430 -0
- package/ext/tags.js +2 -0
- package/index.d.ts +34 -1
- package/initialize.mjs +5 -6
- package/loader-hook.mjs +76 -55
- package/package.json +37 -34
- package/packages/datadog-instrumentations/src/ai.js +45 -0
- package/packages/datadog-instrumentations/src/apollo-server.js +5 -13
- package/packages/datadog-instrumentations/src/child_process.js +3 -3
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +587 -0
- package/packages/datadog-instrumentations/src/cucumber.js +102 -43
- package/packages/datadog-instrumentations/src/cypress-config.js +11 -3
- package/packages/datadog-instrumentations/src/fastify.js +27 -8
- package/packages/datadog-instrumentations/src/fs.js +8 -6
- package/packages/datadog-instrumentations/src/graphql.js +26 -495
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/instrument.js +7 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +6 -6
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/claude-agent-sdk.js +17 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +201 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
- package/packages/datadog-instrumentations/src/jest.js +713 -63
- package/packages/datadog-instrumentations/src/mocha/main.js +24 -3
- package/packages/datadog-instrumentations/src/mocha/utils.js +128 -22
- package/packages/datadog-instrumentations/src/mocha/worker.js +13 -0
- package/packages/datadog-instrumentations/src/mongodb.js +74 -0
- package/packages/datadog-instrumentations/src/mongoose.js +13 -2
- package/packages/datadog-instrumentations/src/playwright.js +13 -9
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +1297 -0
- package/packages/datadog-instrumentations/src/vitest-main.js +1594 -0
- package/packages/datadog-instrumentations/src/vitest-util.js +254 -0
- package/packages/datadog-instrumentations/src/vitest-worker.js +823 -0
- package/packages/datadog-instrumentations/src/vitest.js +11 -1855
- package/packages/datadog-plugin-ai/src/index.js +1 -1
- package/packages/datadog-plugin-ai/src/tracing.js +66 -3
- package/packages/datadog-plugin-ai/src/utils.js +17 -4
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/client.js +2 -2
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +19 -10
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -0
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +47 -11
- package/packages/datadog-plugin-child_process/src/index.js +13 -2
- package/packages/datadog-plugin-claude-agent-sdk/src/index.js +31 -0
- package/packages/datadog-plugin-claude-agent-sdk/src/tracing.js +107 -0
- package/packages/datadog-plugin-claude-agent-sdk/src/util.js +15 -0
- package/packages/datadog-plugin-cucumber/src/index.js +15 -24
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +360 -14
- package/packages/datadog-plugin-cypress/src/index.js +47 -2
- package/packages/datadog-plugin-cypress/src/plugin.js +1 -0
- package/packages/datadog-plugin-cypress/src/support.js +45 -1
- package/packages/datadog-plugin-express/src/code_origin.js +1 -1
- package/packages/datadog-plugin-fastify/src/code_origin.js +1 -1
- package/packages/datadog-plugin-graphql/src/execute.js +639 -12
- package/packages/datadog-plugin-graphql/src/index.js +37 -9
- package/packages/datadog-plugin-graphql/src/parse.js +24 -4
- package/packages/datadog-plugin-graphql/src/utils.js +9 -2
- package/packages/datadog-plugin-graphql/src/validate.js +17 -6
- package/packages/datadog-plugin-http/src/index.js +6 -8
- package/packages/datadog-plugin-jest/src/index.js +31 -15
- package/packages/datadog-plugin-mocha/src/index.js +40 -15
- package/packages/datadog-plugin-mongodb-core/src/bulk-write.js +43 -0
- package/packages/datadog-plugin-mongodb-core/src/index.js +8 -443
- package/packages/datadog-plugin-mongodb-core/src/query.js +452 -0
- package/packages/datadog-plugin-openai/src/services.js +2 -2
- package/packages/datadog-plugin-playwright/src/index.js +4 -3
- package/packages/datadog-plugin-vitest/src/index.js +120 -72
- package/packages/datadog-shimmer/src/shimmer.js +37 -16
- package/packages/dd-trace/src/aiguard/index.js +9 -14
- package/packages/dd-trace/src/aiguard/integrations/index.js +34 -0
- package/packages/dd-trace/src/aiguard/integrations/openai.js +47 -33
- package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +42 -28
- package/packages/dd-trace/src/aiguard/sdk.js +2 -2
- package/packages/dd-trace/src/appsec/api_security/sampler.js +3 -3
- package/packages/dd-trace/src/appsec/channels.js +3 -1
- package/packages/dd-trace/src/appsec/downstream_requests.js +4 -4
- package/packages/dd-trace/src/appsec/graphql.js +14 -11
- package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +23 -23
- package/packages/dd-trace/src/appsec/iast/security-controls/index.js +2 -2
- package/packages/dd-trace/src/appsec/iast/telemetry/index.js +2 -1
- package/packages/dd-trace/src/appsec/index.js +10 -1
- package/packages/dd-trace/src/appsec/lambda.js +135 -0
- package/packages/dd-trace/src/appsec/reporter.js +49 -10
- package/packages/dd-trace/src/appsec/telemetry/index.js +1 -1
- package/packages/dd-trace/src/appsec/waf/index.js +16 -4
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +4 -4
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +26 -1
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +115 -24
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +21 -6
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +5 -2
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +6 -2
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +130 -20
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -2
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +17 -0
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +47 -26
- package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +2 -2
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +64 -53
- package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +3 -3
- package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +156 -0
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +23 -5
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +128 -0
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +287 -0
- package/packages/dd-trace/src/config/defaults.js +3 -2
- package/packages/dd-trace/src/config/generated-config-types.d.ts +71 -57
- package/packages/dd-trace/src/config/helper.js +1 -0
- package/packages/dd-trace/src/config/index.js +35 -22
- package/packages/dd-trace/src/config/major-overrides.js +4 -2
- package/packages/dd-trace/src/config/remote_config.js +1 -1
- package/packages/dd-trace/src/config/supported-configurations.json +127 -56
- package/packages/dd-trace/src/constants.js +7 -0
- package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
- package/packages/dd-trace/src/datastreams/processor.js +11 -3
- package/packages/dd-trace/src/exporters/agent/index.js +1 -1
- package/packages/dd-trace/src/exporters/agentless/writer.js +4 -4
- package/packages/dd-trace/src/llmobs/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +403 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/index.js +8 -395
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +27 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +363 -0
- package/packages/dd-trace/src/llmobs/plugins/base.js +4 -4
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +270 -0
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/utils.js +10 -0
- package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
- package/packages/dd-trace/src/llmobs/sdk.js +3 -3
- package/packages/dd-trace/src/llmobs/span_processor.js +1 -1
- package/packages/dd-trace/src/llmobs/tagger.js +3 -3
- package/packages/dd-trace/src/llmobs/writers/base.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +54 -5
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +40 -0
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +169 -0
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +38 -39
- package/packages/dd-trace/src/opentelemetry/span.js +5 -0
- package/packages/dd-trace/src/opentelemetry/trace/index.js +4 -0
- package/packages/dd-trace/src/opentelemetry/tracer_provider.js +1 -1
- package/packages/dd-trace/src/opentracing/tracer.js +6 -1
- package/packages/dd-trace/src/plugin_manager.js +23 -7
- package/packages/dd-trace/src/plugins/ci_plugin.js +159 -10
- package/packages/dd-trace/src/plugins/index.js +2 -0
- package/packages/dd-trace/src/plugins/util/llm.js +6 -1
- package/packages/dd-trace/src/plugins/util/test.js +15 -9
- package/packages/dd-trace/src/profiling/exporter_cli.js +2 -2
- package/packages/dd-trace/src/profiling/exporters/agent.js +28 -3
- package/packages/dd-trace/src/profiling/exporters/event_serializer.js +3 -3
- package/packages/dd-trace/src/profiling/profilers/wall.js +1 -1
- package/packages/dd-trace/src/proxy.js +8 -8
- package/packages/dd-trace/src/span_processor.js +5 -6
- package/packages/dd-trace/src/span_stats.js +96 -78
- package/packages/dd-trace/src/startup-log.js +2 -1
- package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
- package/packages/dd-trace/src/telemetry/endpoints.js +6 -4
- package/packages/dd-trace/src/telemetry/index.js +2 -2
- package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
- package/packages/dd-trace/src/telemetry/send-data.js +8 -8
- package/packages/dd-trace/src/telemetry/session-propagation.js +1 -1
- package/packages/dd-trace/src/telemetry/telemetry.js +9 -9
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
- package/vendor/dist/shell-quote/index.js +1 -1
- package/packages/datadog-plugin-graphql/src/resolve.js +0 -170
|
@@ -36,7 +36,7 @@ class WAFContextWrapper {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
run ({ persistent, ephemeral }, raspRule, req) {
|
|
39
|
+
run ({ persistent, ephemeral }, raspRule, req, rootSpan) {
|
|
40
40
|
if (this.ddwafContext.disposed) {
|
|
41
41
|
log.warn('[ASM] Calling run on a disposed context')
|
|
42
42
|
if (raspRule) {
|
|
@@ -156,10 +156,10 @@ class WAFContextWrapper {
|
|
|
156
156
|
metrics.wafTimeout = result.timeout
|
|
157
157
|
|
|
158
158
|
if (ruleTriggered) {
|
|
159
|
-
Reporter.reportAttack(result, req)
|
|
159
|
+
Reporter.reportAttack(result, req, rootSpan)
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
Reporter.reportAttributes(result.attributes, req)
|
|
162
|
+
Reporter.reportAttributes(result.attributes, req, rootSpan)
|
|
163
163
|
|
|
164
164
|
return result
|
|
165
165
|
} catch (err) {
|
|
@@ -171,7 +171,7 @@ class WAFContextWrapper {
|
|
|
171
171
|
wafRunFinished.publish({ payload })
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
Reporter.reportMetrics(metrics, raspRule, req)
|
|
174
|
+
Reporter.reportMetrics(metrics, raspRule, req, rootSpan)
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
|
|
@@ -9,6 +9,7 @@ const getDebuggerConfig = require('../../debugger/config')
|
|
|
9
9
|
|
|
10
10
|
const probeIdToResolveBreakpointSet = new Map()
|
|
11
11
|
const probeIdToResolveBreakpointRemove = new Map()
|
|
12
|
+
const drainRequestIdToResolveBreakpointHit = new Map()
|
|
12
13
|
|
|
13
14
|
class TestVisDynamicInstrumentation {
|
|
14
15
|
/**
|
|
@@ -57,6 +58,21 @@ class TestVisDynamicInstrumentation {
|
|
|
57
58
|
]
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Waits until all breakpoint hits already being handled by the DI worker have been posted back.
|
|
63
|
+
*
|
|
64
|
+
* @returns {Promise<void>}
|
|
65
|
+
*/
|
|
66
|
+
waitForInFlightBreakpointHits () {
|
|
67
|
+
if (!this.worker) return Promise.resolve()
|
|
68
|
+
|
|
69
|
+
const requestId = randomUUID()
|
|
70
|
+
return new Promise(resolve => {
|
|
71
|
+
drainRequestIdToResolveBreakpointHit.set(requestId, resolve)
|
|
72
|
+
this.breakpointHitChannel.port2.postMessage({ drainRequestId: requestId })
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
60
76
|
isReady () {
|
|
61
77
|
return this._readyPromise
|
|
62
78
|
}
|
|
@@ -130,7 +146,16 @@ class TestVisDynamicInstrumentation {
|
|
|
130
146
|
}
|
|
131
147
|
}).unref?.()
|
|
132
148
|
|
|
133
|
-
this.breakpointHitChannel.port2.on('message', ({ snapshot }) => {
|
|
149
|
+
this.breakpointHitChannel.port2.on('message', ({ snapshot, drainRequestId }) => {
|
|
150
|
+
if (drainRequestId) {
|
|
151
|
+
const resolve = drainRequestIdToResolveBreakpointHit.get(drainRequestId)
|
|
152
|
+
if (resolve) {
|
|
153
|
+
resolve()
|
|
154
|
+
drainRequestIdToResolveBreakpointHit.delete(drainRequestId)
|
|
155
|
+
}
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
134
159
|
const { probe: { id: probeId } } = snapshot
|
|
135
160
|
const onHit = this.onHitBreakpointByProbeId.get(probeId)
|
|
136
161
|
if (onHit) {
|
|
@@ -28,12 +28,20 @@ const {
|
|
|
28
28
|
getStackFromCallFrames,
|
|
29
29
|
} = require('../../../debugger/devtools_client/state')
|
|
30
30
|
const log = require('../../../log')
|
|
31
|
-
|
|
31
|
+
|
|
32
|
+
let processTags
|
|
33
|
+
if (config.propagateProcessTags?.enabled) {
|
|
34
|
+
processTags = require('../../../process-tags')
|
|
35
|
+
processTags.initialize()
|
|
36
|
+
}
|
|
32
37
|
|
|
33
38
|
let sessionStarted = false
|
|
39
|
+
let inFlightBreakpointHits = 0
|
|
40
|
+
let isBreakpointHitDrainScheduled = false
|
|
34
41
|
|
|
35
42
|
const breakpointIdToProbe = new Map()
|
|
36
43
|
const probeIdToBreakpointId = new Map()
|
|
44
|
+
const breakpointHitDrainRequests = []
|
|
37
45
|
|
|
38
46
|
const limits = {
|
|
39
47
|
maxReferenceDepth: DEFAULT_MAX_REFERENCE_DEPTH,
|
|
@@ -42,6 +50,55 @@ const limits = {
|
|
|
42
50
|
maxLength: DEFAULT_MAX_LENGTH,
|
|
43
51
|
}
|
|
44
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Remove empty collection arrays before sending snapshots through the Test Optimization logs path.
|
|
55
|
+
*
|
|
56
|
+
* @param {object} value - Snapshot object or sub-object.
|
|
57
|
+
* @returns {void}
|
|
58
|
+
*/
|
|
59
|
+
function removeEmptyCollectionProperties (value) {
|
|
60
|
+
const stack = [value]
|
|
61
|
+
|
|
62
|
+
while (stack.length > 0) {
|
|
63
|
+
const current = stack.pop()
|
|
64
|
+
if (!current || typeof current !== 'object') continue
|
|
65
|
+
|
|
66
|
+
if (Array.isArray(current)) {
|
|
67
|
+
for (const item of current) {
|
|
68
|
+
if (item && typeof item === 'object') {
|
|
69
|
+
stack.push(item)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
continue
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (Array.isArray(current.elements)) {
|
|
76
|
+
if (current.elements.length === 0) {
|
|
77
|
+
delete current.elements
|
|
78
|
+
} else {
|
|
79
|
+
stack.push(current.elements)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (Array.isArray(current.entries)) {
|
|
84
|
+
if (current.entries.length === 0) {
|
|
85
|
+
delete current.entries
|
|
86
|
+
} else {
|
|
87
|
+
stack.push(current.entries)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
for (const key of Object.keys(current)) {
|
|
92
|
+
if (key === 'elements' || key === 'entries') continue
|
|
93
|
+
|
|
94
|
+
const child = current[key]
|
|
95
|
+
if (child && typeof child === 'object') {
|
|
96
|
+
stack.push(child)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
45
102
|
session.on('Debugger.paused', async ({ params: { hitBreakpoints: [hitBreakpoint], callFrames } }) => {
|
|
46
103
|
const probe = breakpointIdToProbe.get(hitBreakpoint)
|
|
47
104
|
if (!probe) {
|
|
@@ -49,32 +106,47 @@ session.on('Debugger.paused', async ({ params: { hitBreakpoints: [hitBreakpoint]
|
|
|
49
106
|
return session.post('Debugger.resume')
|
|
50
107
|
}
|
|
51
108
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
id: randomUUID(),
|
|
60
|
-
timestamp: Date.now(),
|
|
61
|
-
probe: {
|
|
62
|
-
id: probe.id,
|
|
63
|
-
version: '0',
|
|
64
|
-
location: probe.location,
|
|
65
|
-
},
|
|
66
|
-
captures: {
|
|
67
|
-
lines: { [probe.location.lines[0]]: { locals: processLocalState() } },
|
|
68
|
-
},
|
|
69
|
-
stack,
|
|
70
|
-
language: 'javascript',
|
|
71
|
-
}
|
|
109
|
+
inFlightBreakpointHits++
|
|
110
|
+
try {
|
|
111
|
+
const stack = await getStackFromCallFrames(callFrames)
|
|
112
|
+
|
|
113
|
+
const { processLocalState } = await getLocalStateForCallFrame(callFrames[0], limits)
|
|
114
|
+
|
|
115
|
+
await session.post('Debugger.resume')
|
|
72
116
|
|
|
73
|
-
|
|
74
|
-
|
|
117
|
+
const snapshot = {
|
|
118
|
+
id: randomUUID(),
|
|
119
|
+
timestamp: Date.now(),
|
|
120
|
+
probe: {
|
|
121
|
+
id: probe.id,
|
|
122
|
+
version: 0,
|
|
123
|
+
location: probe.location,
|
|
124
|
+
},
|
|
125
|
+
captures: {
|
|
126
|
+
lines: { [probe.location.lines[0]]: { locals: processLocalState() } },
|
|
127
|
+
},
|
|
128
|
+
stack,
|
|
129
|
+
language: 'javascript',
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
removeEmptyCollectionProperties(snapshot.captures)
|
|
133
|
+
|
|
134
|
+
if (processTags) {
|
|
135
|
+
snapshot[processTags.DYNAMIC_INSTRUMENTATION_FIELD_NAME] = processTags.tagsObject
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
breakpointHitChannel.postMessage({ snapshot })
|
|
139
|
+
} finally {
|
|
140
|
+
inFlightBreakpointHits--
|
|
141
|
+
scheduleBreakpointHitDrain()
|
|
75
142
|
}
|
|
143
|
+
})
|
|
76
144
|
|
|
77
|
-
|
|
145
|
+
breakpointHitChannel.on('message', ({ drainRequestId }) => {
|
|
146
|
+
if (!drainRequestId) return
|
|
147
|
+
|
|
148
|
+
breakpointHitDrainRequests.push(drainRequestId)
|
|
149
|
+
scheduleBreakpointHitDrain()
|
|
78
150
|
})
|
|
79
151
|
|
|
80
152
|
breakpointRemoveChannel.on('message', async (probeId) => {
|
|
@@ -154,3 +226,22 @@ function start () {
|
|
|
154
226
|
sessionStarted = true
|
|
155
227
|
return session.post('Debugger.enable') // return instead of await to reduce number of promises created
|
|
156
228
|
}
|
|
229
|
+
|
|
230
|
+
function drainBreakpointHitRequests () {
|
|
231
|
+
if (inFlightBreakpointHits !== 0) return
|
|
232
|
+
|
|
233
|
+
for (const drainRequestId of breakpointHitDrainRequests) {
|
|
234
|
+
breakpointHitChannel.postMessage({ drainRequestId })
|
|
235
|
+
}
|
|
236
|
+
breakpointHitDrainRequests.length = 0
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function scheduleBreakpointHitDrain () {
|
|
240
|
+
if (isBreakpointHitDrainScheduled) return
|
|
241
|
+
|
|
242
|
+
isBreakpointHitDrainScheduled = true
|
|
243
|
+
setImmediate(() => {
|
|
244
|
+
isBreakpointHitDrainScheduled = false
|
|
245
|
+
drainBreakpointHitRequests()
|
|
246
|
+
})
|
|
247
|
+
}
|
|
@@ -17,6 +17,7 @@ const {
|
|
|
17
17
|
|
|
18
18
|
const { getNumFromKnownTests } = require('../../plugins/util/test')
|
|
19
19
|
const { buildCacheKey, writeToCache, withCache } = require('../requests/fs-cache')
|
|
20
|
+
const { validateKnownTestsResponse } = require('../test-optimization-http-cache-schema')
|
|
20
21
|
|
|
21
22
|
const MAX_KNOWN_TESTS_PAGES = 10_000
|
|
22
23
|
|
|
@@ -52,6 +53,19 @@ function mergeKnownTests (aggregate, page) {
|
|
|
52
53
|
return aggregate
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
function parseJsonResponse (rawJson) {
|
|
57
|
+
return typeof rawJson === 'string' ? JSON.parse(rawJson) : rawJson
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parseKnownTestsResponse (rawJson, options = {}) {
|
|
61
|
+
const parsedResponse = parseJsonResponse(rawJson)
|
|
62
|
+
if (options.validateRequiredFields) {
|
|
63
|
+
validateKnownTestsResponse(parsedResponse)
|
|
64
|
+
}
|
|
65
|
+
const { data: { attributes: { tests } } } = parsedResponse
|
|
66
|
+
return tests
|
|
67
|
+
}
|
|
68
|
+
|
|
55
69
|
function getKnownTests ({
|
|
56
70
|
url,
|
|
57
71
|
isEvpProxy,
|
|
@@ -151,12 +165,12 @@ function fetchFromApi ({
|
|
|
151
165
|
options.path = `${evpProxyPrefix}/api/v2/ci/libraries/tests`
|
|
152
166
|
options.headers['X-Datadog-EVP-Subdomain'] = 'api'
|
|
153
167
|
} else {
|
|
154
|
-
const {
|
|
155
|
-
if (!
|
|
168
|
+
const { DD_API_KEY } = getConfig()
|
|
169
|
+
if (!DD_API_KEY) {
|
|
156
170
|
return done(new Error('Known tests were not fetched because Datadog API key is not defined.'))
|
|
157
171
|
}
|
|
158
172
|
|
|
159
|
-
options.headers['dd-api-key'] =
|
|
173
|
+
options.headers['dd-api-key'] = DD_API_KEY
|
|
160
174
|
}
|
|
161
175
|
|
|
162
176
|
const configurations = {
|
|
@@ -211,8 +225,9 @@ function fetchFromApi ({
|
|
|
211
225
|
try {
|
|
212
226
|
totalResponseBytes += res.length
|
|
213
227
|
|
|
214
|
-
const
|
|
215
|
-
const
|
|
228
|
+
const parsedResponse = parseJsonResponse(res)
|
|
229
|
+
const pageTests = parseKnownTestsResponse(parsedResponse)
|
|
230
|
+
const { page_info: responsePageInfo } = parsedResponse.data.attributes
|
|
216
231
|
|
|
217
232
|
aggregateTests = mergeKnownTests(aggregateTests, pageTests)
|
|
218
233
|
|
|
@@ -251,4 +266,4 @@ function fetchFromApi ({
|
|
|
251
266
|
fetchPage(null)
|
|
252
267
|
}
|
|
253
268
|
|
|
254
|
-
module.exports = { getKnownTests }
|
|
269
|
+
module.exports = { getKnownTests, parseKnownTestsResponse }
|
|
@@ -40,7 +40,7 @@ class AgentProxyCiVisibilityExporter extends CiVisibilityExporter {
|
|
|
40
40
|
lookup,
|
|
41
41
|
protocolVersion,
|
|
42
42
|
headers,
|
|
43
|
-
|
|
43
|
+
testOptimization,
|
|
44
44
|
} = config
|
|
45
45
|
|
|
46
46
|
fetchAgentInfo(this._url, (err, agentInfo) => {
|
|
@@ -68,7 +68,10 @@ class AgentProxyCiVisibilityExporter extends CiVisibilityExporter {
|
|
|
68
68
|
evpProxyPrefix,
|
|
69
69
|
})
|
|
70
70
|
this._codeCoverageReportUrl = this._url
|
|
71
|
-
|
|
71
|
+
// Screenshot media uploads go through the Agent's evp_proxy: the uploader prefixes the
|
|
72
|
+
// path with evpProxyPrefix and sets X-Datadog-EVP-Subdomain: api (see uploadTestScreenshot).
|
|
73
|
+
this._testScreenshotUploadUrl = this._url
|
|
74
|
+
if (testOptimization.DD_TEST_FAILED_TEST_REPLAY_ENABLED) {
|
|
72
75
|
const canFowardLogs = getCanForwardDebuggerLogs(err, agentInfo)
|
|
73
76
|
if (canFowardLogs) {
|
|
74
77
|
const DynamicInstrumentationLogsWriter = require('../agentless/di-logs-writer')
|
|
@@ -26,7 +26,7 @@ class DynamicInstrumentationLogsWriter extends BaseWriter {
|
|
|
26
26
|
path: '/api/v2/logs',
|
|
27
27
|
method: 'POST',
|
|
28
28
|
headers: {
|
|
29
|
-
'dd-api-key': getConfig().
|
|
29
|
+
'dd-api-key': getConfig().DD_API_KEY,
|
|
30
30
|
'Content-Type': 'application/json',
|
|
31
31
|
},
|
|
32
32
|
timeout: this.timeout,
|
|
@@ -9,7 +9,8 @@ const CoverageWriter = require('./coverage-writer')
|
|
|
9
9
|
class AgentlessCiVisibilityExporter extends CiVisibilityExporter {
|
|
10
10
|
constructor (config) {
|
|
11
11
|
super(config)
|
|
12
|
-
const { tags, site,
|
|
12
|
+
const { tags, site, testOptimization } = config
|
|
13
|
+
const { DD_CIVISIBILITY_AGENTLESS_URL: url } = testOptimization
|
|
13
14
|
// we don't need to request /info because we are using agentless by configuration
|
|
14
15
|
this._isInitialized = true
|
|
15
16
|
this._resolveCanUseCiVisProtocol(true)
|
|
@@ -23,13 +24,15 @@ class AgentlessCiVisibilityExporter extends CiVisibilityExporter {
|
|
|
23
24
|
|
|
24
25
|
this._codeCoverageReportUrl = url || new URL(`https://ci-intake.${site}`)
|
|
25
26
|
|
|
26
|
-
if (
|
|
27
|
+
if (testOptimization.DD_TEST_FAILED_TEST_REPLAY_ENABLED) {
|
|
27
28
|
const DynamicInstrumentationLogsWriter = require('./di-logs-writer')
|
|
28
29
|
this._logsUrl = url || new URL(`https://http-intake.logs.${site}`)
|
|
29
30
|
this._logsWriter = new DynamicInstrumentationLogsWriter({ url: this._logsUrl, tags })
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
this._apiUrl = url || new URL(`https://api.${site}`)
|
|
34
|
+
// Media uploads (raw bytes + DD-API-KEY) go to the same api.<site> host as the rest of the API.
|
|
35
|
+
this._testScreenshotUploadUrl = this._apiUrl
|
|
33
36
|
// Agentless is always gzip compatible
|
|
34
37
|
this._isGzipCompatible = true
|
|
35
38
|
}
|
|
@@ -39,6 +42,7 @@ class AgentlessCiVisibilityExporter extends CiVisibilityExporter {
|
|
|
39
42
|
try {
|
|
40
43
|
apiUrl = new URL(apiUrl)
|
|
41
44
|
this._apiUrl = apiUrl
|
|
45
|
+
this._testScreenshotUploadUrl = apiUrl
|
|
42
46
|
} catch (e) {
|
|
43
47
|
log.error('Error setting CI exporter api url', e)
|
|
44
48
|
}
|
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { hostname: getHostname } = require('node:os')
|
|
3
4
|
const URL = require('url').URL
|
|
4
5
|
|
|
6
|
+
const { version: tracerVersion } = require('../../../../../package.json')
|
|
5
7
|
const { getLibraryConfiguration: getLibraryConfigurationRequest } = require('../requests/get-library-configuration')
|
|
6
8
|
const { getSkippableSuites: getSkippableSuitesRequest } = require('../intelligent-test-runner/get-skippable-suites')
|
|
7
9
|
const { getKnownTests: getKnownTestsRequest } = require('../early-flake-detection/get-known-tests')
|
|
8
10
|
const { getTestManagementTests: getTestManagementTestsRequest } =
|
|
9
11
|
require('../test-management/get-test-management-tests')
|
|
12
|
+
const { writeSettingsToCache } = require('../test-optimization-cache')
|
|
13
|
+
const { CACHE_MISS, TestOptimizationHttpCache } = require('../test-optimization-http-cache')
|
|
10
14
|
const { uploadCoverageReport: uploadCoverageReportRequest } = require('../requests/upload-coverage-report')
|
|
15
|
+
const { uploadTestScreenshot: uploadTestScreenshotRequest } = require('../requests/upload-test-screenshot')
|
|
11
16
|
const log = require('../../log')
|
|
12
17
|
const BufferingExporter = require('../../exporters/common/buffering-exporter')
|
|
13
18
|
const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../../plugins/util/tags')
|
|
14
19
|
const { sendGitMetadata: sendGitMetadataRequest } = require('./git/git_metadata')
|
|
15
20
|
|
|
21
|
+
const hostname = getHostname()
|
|
22
|
+
|
|
16
23
|
function getTestConfigurationTags (tags) {
|
|
17
24
|
if (!tags) {
|
|
18
25
|
return {}
|
|
@@ -35,6 +42,34 @@ function getIsTestSessionTrace (trace) {
|
|
|
35
42
|
const GIT_UPLOAD_TIMEOUT = 60_000 // 60 seconds
|
|
36
43
|
const CAN_USE_CI_VIS_PROTOCOL_TIMEOUT = GIT_UPLOAD_TIMEOUT
|
|
37
44
|
|
|
45
|
+
function appendLogTag (tags, key, value) {
|
|
46
|
+
if (value !== undefined) {
|
|
47
|
+
tags.push(`${key}:${value}`)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getLogTags (logMessage, { env, version }, gitRepositoryUrl, gitCommitSha) {
|
|
52
|
+
const tags = []
|
|
53
|
+
if (Array.isArray(logMessage.ddtags)) {
|
|
54
|
+
for (const tag of logMessage.ddtags) {
|
|
55
|
+
tags.push(tag)
|
|
56
|
+
}
|
|
57
|
+
} else if (logMessage.ddtags) {
|
|
58
|
+
for (const tag of logMessage.ddtags.split(',')) {
|
|
59
|
+
tags.push(tag)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
appendLogTag(tags, 'env', env)
|
|
64
|
+
appendLogTag(tags, 'version', version)
|
|
65
|
+
appendLogTag(tags, 'debugger_version', tracerVersion)
|
|
66
|
+
appendLogTag(tags, 'host_name', hostname)
|
|
67
|
+
appendLogTag(tags, GIT_COMMIT_SHA, gitCommitSha)
|
|
68
|
+
appendLogTag(tags, GIT_REPOSITORY_URL, gitRepositoryUrl)
|
|
69
|
+
|
|
70
|
+
return tags.join(',')
|
|
71
|
+
}
|
|
72
|
+
|
|
38
73
|
class CiVisibilityExporter extends BufferingExporter {
|
|
39
74
|
constructor (config) {
|
|
40
75
|
super(config)
|
|
@@ -42,10 +77,14 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
42
77
|
this._coverageTimer = undefined
|
|
43
78
|
this._logsTimer = undefined
|
|
44
79
|
this._coverageBuffer = []
|
|
80
|
+
this._testOptimizationHttpCache = new TestOptimizationHttpCache()
|
|
45
81
|
// The library can use new features like ITR and test suite level visibility
|
|
46
82
|
// AKA CI Vis Protocol
|
|
47
83
|
this._canUseCiVisProtocol = false
|
|
48
84
|
|
|
85
|
+
this._isTestFailureScreenshotsEnabled =
|
|
86
|
+
Boolean(config?.testOptimization?.DD_TEST_FAILURE_SCREENSHOTS_ENABLED)
|
|
87
|
+
|
|
49
88
|
const gitUploadTimeoutId = setTimeout(() => {
|
|
50
89
|
this._resolveGit(new Error('Timeout while uploading git metadata'))
|
|
51
90
|
}, GIT_UPLOAD_TIMEOUT)
|
|
@@ -86,7 +125,7 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
86
125
|
}
|
|
87
126
|
|
|
88
127
|
shouldRequestSkippableSuites () {
|
|
89
|
-
return !!(this._config.
|
|
128
|
+
return !!(this._config.testOptimization.DD_CIVISIBILITY_ITR_ENABLED &&
|
|
90
129
|
this._canUseCiVisProtocol &&
|
|
91
130
|
this._libraryConfig?.isSuitesSkippingEnabled)
|
|
92
131
|
}
|
|
@@ -101,7 +140,7 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
101
140
|
shouldRequestTestManagementTests () {
|
|
102
141
|
return !!(
|
|
103
142
|
this._canUseCiVisProtocol &&
|
|
104
|
-
this._config.
|
|
143
|
+
this._config.testOptimization.DD_TEST_MANAGEMENT_ENABLED &&
|
|
105
144
|
this._libraryConfig?.isTestManagementEnabled
|
|
106
145
|
)
|
|
107
146
|
}
|
|
@@ -133,11 +172,21 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
133
172
|
if (!this.shouldRequestSkippableSuites()) {
|
|
134
173
|
return callback(null, [])
|
|
135
174
|
}
|
|
175
|
+
const requestConfiguration = this.getRequestConfiguration(testConfiguration)
|
|
176
|
+
const cachedSkippableSuites = this._testOptimizationHttpCache.readSkippableSuites({
|
|
177
|
+
testLevel: requestConfiguration.testLevel,
|
|
178
|
+
isCoverageReportUploadEnabled: requestConfiguration.isCoverageReportUploadEnabled,
|
|
179
|
+
})
|
|
180
|
+
if (cachedSkippableSuites !== CACHE_MISS) {
|
|
181
|
+
const { skippableSuites, correlationId, coverage } = cachedSkippableSuites
|
|
182
|
+
return callback(null, skippableSuites, correlationId, coverage)
|
|
183
|
+
}
|
|
184
|
+
|
|
136
185
|
this._gitUploadPromise.then(gitUploadError => {
|
|
137
186
|
if (gitUploadError) {
|
|
138
187
|
return callback(gitUploadError, [])
|
|
139
188
|
}
|
|
140
|
-
getSkippableSuitesRequest(
|
|
189
|
+
getSkippableSuitesRequest(requestConfiguration, callback)
|
|
141
190
|
})
|
|
142
191
|
}
|
|
143
192
|
|
|
@@ -145,6 +194,10 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
145
194
|
if (!this.shouldRequestKnownTests()) {
|
|
146
195
|
return callback(null)
|
|
147
196
|
}
|
|
197
|
+
const cachedKnownTests = this._testOptimizationHttpCache.readKnownTests()
|
|
198
|
+
if (cachedKnownTests !== CACHE_MISS) {
|
|
199
|
+
return callback(null, cachedKnownTests)
|
|
200
|
+
}
|
|
148
201
|
getKnownTestsRequest(this.getRequestConfiguration(testConfiguration), callback)
|
|
149
202
|
}
|
|
150
203
|
|
|
@@ -152,6 +205,10 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
152
205
|
if (!this.shouldRequestTestManagementTests()) {
|
|
153
206
|
return callback(null)
|
|
154
207
|
}
|
|
208
|
+
const cachedTestManagementTests = this._testOptimizationHttpCache.readTestManagementTests()
|
|
209
|
+
if (cachedTestManagementTests !== CACHE_MISS) {
|
|
210
|
+
return callback(null, cachedTestManagementTests)
|
|
211
|
+
}
|
|
155
212
|
getTestManagementTestsRequest(this.getRequestConfiguration(testConfiguration), callback)
|
|
156
213
|
}
|
|
157
214
|
|
|
@@ -161,13 +218,30 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
161
218
|
*/
|
|
162
219
|
getLibraryConfiguration (testConfiguration, callback) {
|
|
163
220
|
const { repositoryUrl } = testConfiguration
|
|
164
|
-
this.sendGitMetadata(repositoryUrl)
|
|
165
221
|
this._canUseCiVisProtocolPromise.then((canUseCiVisProtocol) => {
|
|
166
222
|
if (!canUseCiVisProtocol) {
|
|
167
223
|
return callback(null, {})
|
|
168
224
|
}
|
|
169
225
|
const configuration = this.getRequestConfiguration(testConfiguration)
|
|
226
|
+
const cachedLibraryConfig = this._testOptimizationHttpCache.readSettings()
|
|
227
|
+
if (cachedLibraryConfig !== CACHE_MISS) {
|
|
228
|
+
log.debug('Test Optimization HTTP cache settings found, skipping settings request')
|
|
229
|
+
writeSettingsToCache(cachedLibraryConfig)
|
|
230
|
+
this._libraryConfig = this.filterConfiguration(cachedLibraryConfig)
|
|
231
|
+
const canUseCachedSkippableSuites = !this.shouldRequestSkippableSuites() ||
|
|
232
|
+
this._testOptimizationHttpCache.hasValidSkippableSuites({
|
|
233
|
+
testLevel: configuration.testLevel,
|
|
234
|
+
isCoverageReportUploadEnabled: configuration.isCoverageReportUploadEnabled,
|
|
235
|
+
})
|
|
236
|
+
if (this._libraryConfig.requireGit && !canUseCachedSkippableSuites) {
|
|
237
|
+
this.sendGitMetadata(repositoryUrl)
|
|
238
|
+
} else {
|
|
239
|
+
this._resolveGit()
|
|
240
|
+
}
|
|
241
|
+
return callback(null, this._libraryConfig)
|
|
242
|
+
}
|
|
170
243
|
|
|
244
|
+
this.sendGitMetadata(repositoryUrl)
|
|
171
245
|
getLibraryConfigurationRequest(configuration, (err, libraryConfig) => {
|
|
172
246
|
/**
|
|
173
247
|
* **Important**: this._libraryConfig remains empty in testing frameworks
|
|
@@ -218,29 +292,32 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
218
292
|
isImpactedTestsEnabled,
|
|
219
293
|
isCoverageReportUploadEnabled,
|
|
220
294
|
} = remoteConfiguration
|
|
295
|
+
const { testOptimization } = this._config
|
|
221
296
|
return {
|
|
222
297
|
isCodeCoverageEnabled,
|
|
223
298
|
isSuitesSkippingEnabled,
|
|
224
299
|
isItrEnabled,
|
|
225
300
|
requireGit,
|
|
226
|
-
isEarlyFlakeDetectionEnabled:
|
|
301
|
+
isEarlyFlakeDetectionEnabled:
|
|
302
|
+
isEarlyFlakeDetectionEnabled && testOptimization.DD_CIVISIBILITY_EARLY_FLAKE_DETECTION_ENABLED,
|
|
227
303
|
earlyFlakeDetectionNumRetries,
|
|
228
304
|
earlyFlakeDetectionSlowTestRetries,
|
|
229
305
|
earlyFlakeDetectionFaultyThreshold,
|
|
230
|
-
isFlakyTestRetriesEnabled: isFlakyTestRetriesEnabled &&
|
|
231
|
-
flakyTestRetriesCount:
|
|
232
|
-
isDiEnabled: isDiEnabled &&
|
|
306
|
+
isFlakyTestRetriesEnabled: isFlakyTestRetriesEnabled && testOptimization.DD_CIVISIBILITY_FLAKY_RETRY_ENABLED,
|
|
307
|
+
flakyTestRetriesCount: testOptimization.DD_CIVISIBILITY_FLAKY_RETRY_COUNT,
|
|
308
|
+
isDiEnabled: isDiEnabled && testOptimization.DD_TEST_FAILED_TEST_REPLAY_ENABLED,
|
|
233
309
|
isKnownTestsEnabled,
|
|
234
|
-
isTestManagementEnabled: isTestManagementEnabled &&
|
|
310
|
+
isTestManagementEnabled: isTestManagementEnabled && testOptimization.DD_TEST_MANAGEMENT_ENABLED,
|
|
235
311
|
testManagementAttemptToFixRetries:
|
|
236
|
-
testManagementAttemptToFixRetries ??
|
|
237
|
-
isImpactedTestsEnabled:
|
|
312
|
+
testManagementAttemptToFixRetries ?? testOptimization.DD_TEST_MANAGEMENT_ATTEMPT_TO_FIX_RETRIES,
|
|
313
|
+
isImpactedTestsEnabled:
|
|
314
|
+
isImpactedTestsEnabled && testOptimization.DD_CIVISIBILITY_IMPACTED_TESTS_DETECTION_ENABLED,
|
|
238
315
|
isCoverageReportUploadEnabled,
|
|
239
316
|
}
|
|
240
317
|
}
|
|
241
318
|
|
|
242
319
|
sendGitMetadata (repositoryUrl) {
|
|
243
|
-
if (!this._config.
|
|
320
|
+
if (!this._config.testOptimization.DD_CIVISIBILITY_GIT_UPLOAD_ENABLED) {
|
|
244
321
|
return
|
|
245
322
|
}
|
|
246
323
|
this._canUseCiVisProtocolPromise.then((canUseCiVisProtocol) => {
|
|
@@ -297,28 +374,26 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
297
374
|
const { service, env, version } = this._config
|
|
298
375
|
|
|
299
376
|
return {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
`${GIT_REPOSITORY_URL}:${gitRepositoryUrl}`,
|
|
303
|
-
`${GIT_COMMIT_SHA}:${gitCommitSha}`,
|
|
304
|
-
].join(','),
|
|
377
|
+
...logMessage,
|
|
378
|
+
ddtags: getLogTags(logMessage, { env, version }, gitRepositoryUrl, gitCommitSha),
|
|
305
379
|
level: 'error',
|
|
306
380
|
service,
|
|
381
|
+
hostname,
|
|
307
382
|
dd: {
|
|
308
|
-
...
|
|
383
|
+
...logMessage.dd,
|
|
309
384
|
service,
|
|
310
385
|
env,
|
|
311
386
|
version,
|
|
312
387
|
},
|
|
313
388
|
ddsource: 'dd_debugger',
|
|
314
|
-
...logMessage,
|
|
315
389
|
}
|
|
316
390
|
}
|
|
317
391
|
|
|
318
392
|
// DI logs
|
|
319
393
|
exportDiLogs (testEnvironmentMetadata, logMessage) {
|
|
320
394
|
// TODO: could we lose logs if it's not initialized?
|
|
321
|
-
if (!this._config.
|
|
395
|
+
if (!this._config.testOptimization.DD_TEST_FAILED_TEST_REPLAY_ENABLED ||
|
|
396
|
+
!this._isInitialized || !this._canForwardLogs) {
|
|
322
397
|
return
|
|
323
398
|
}
|
|
324
399
|
|
|
@@ -416,6 +491,41 @@ class CiVisibilityExporter extends BufferingExporter {
|
|
|
416
491
|
evpProxyPrefix: this.evpProxyPrefix,
|
|
417
492
|
}, callback)
|
|
418
493
|
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Returns whether the exporter can upload test failure screenshots.
|
|
497
|
+
*
|
|
498
|
+
* @returns {boolean}
|
|
499
|
+
*/
|
|
500
|
+
canUploadTestScreenshots () {
|
|
501
|
+
return Boolean(this._testScreenshotUploadUrl) && this._isTestFailureScreenshotsEnabled
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Uploads a single test screenshot to the Test Optimization media intake.
|
|
506
|
+
*
|
|
507
|
+
* @param {object} options - Upload options
|
|
508
|
+
* @param {string} options.filePath - Path to the screenshot file
|
|
509
|
+
* @param {string} options.traceId - Test trace id used as the screenshot key
|
|
510
|
+
* @param {string} options.idempotencyKey - Stable per-artifact key, reused on retry
|
|
511
|
+
* @param {number} options.capturedAtMs - Capture time in epoch milliseconds
|
|
512
|
+
* @param {Function} callback - Callback function (err)
|
|
513
|
+
*/
|
|
514
|
+
uploadTestScreenshot ({ filePath, traceId, idempotencyKey, capturedAtMs }, callback) {
|
|
515
|
+
if (!this._testScreenshotUploadUrl) {
|
|
516
|
+
return callback(new Error('Test screenshot upload URL not configured'))
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
uploadTestScreenshotRequest({
|
|
520
|
+
filePath,
|
|
521
|
+
traceId,
|
|
522
|
+
idempotencyKey,
|
|
523
|
+
capturedAtMs,
|
|
524
|
+
url: this._testScreenshotUploadUrl,
|
|
525
|
+
isEvpProxy: !!this._isUsingEvpProxy,
|
|
526
|
+
evpProxyPrefix: this.evpProxyPrefix,
|
|
527
|
+
}, callback)
|
|
528
|
+
}
|
|
419
529
|
}
|
|
420
530
|
|
|
421
531
|
module.exports = CiVisibilityExporter
|