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
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// Capture real timers at module load, before any test can install fake timers.
|
|
4
4
|
const { performance } = require('perf_hooks')
|
|
5
|
+
const { statSync } = require('node:fs')
|
|
6
|
+
const { basename } = require('node:path')
|
|
5
7
|
const dateNow = Date.now
|
|
6
8
|
|
|
7
9
|
const { createCoverageMap } = require('../../../vendor/dist/istanbul-lib-coverage')
|
|
@@ -77,6 +79,8 @@ const {
|
|
|
77
79
|
getMaxEfdRetryCount,
|
|
78
80
|
getPullRequestBaseBranch,
|
|
79
81
|
TEST_FINAL_STATUS,
|
|
82
|
+
TEST_FAILURE_SCREENSHOT_UPLOADED,
|
|
83
|
+
TEST_FAILURE_SCREENSHOT_UPLOAD_ERROR,
|
|
80
84
|
getTestOptimizationRequestResults,
|
|
81
85
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
82
86
|
const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
|
|
@@ -136,6 +140,127 @@ const CYPRESS_STATUS_TO_TEST_STATUS = {
|
|
|
136
140
|
skipped: 'skip',
|
|
137
141
|
}
|
|
138
142
|
|
|
143
|
+
const SCREENSHOT_ATTEMPT_RE = /\(attempt \d+\)/
|
|
144
|
+
const SCREENSHOT_UPLOAD_RESULT_UPLOADED = 'uploaded'
|
|
145
|
+
const SCREENSHOT_UPLOAD_RESULT_ERROR = 'error'
|
|
146
|
+
|
|
147
|
+
function getScreenshotFilePath (screenshot) {
|
|
148
|
+
return typeof screenshot === 'string' ? screenshot : screenshot?.path
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function isFailureScreenshotByMetadata (screenshot, screenshotFilePath) {
|
|
152
|
+
if (screenshot !== null && typeof screenshot === 'object' && screenshot.testFailure !== undefined) {
|
|
153
|
+
return screenshot.testFailure === true
|
|
154
|
+
}
|
|
155
|
+
return screenshotFilePath.includes('(failed)')
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Resolves a screenshot's capture time (epoch ms) for the media upload. Cypress
|
|
160
|
+
* screenshot objects carry an ISO `takenAt`; falls back to the file's mtime, then
|
|
161
|
+
* to the current time. Stamped once here and reused on retry via the idempotency
|
|
162
|
+
* key, so the stored object is overwritten rather than duplicated.
|
|
163
|
+
*
|
|
164
|
+
* @param {object|string} screenshot - Cypress screenshot object or path
|
|
165
|
+
* @param {string} filePath - Resolved screenshot file path
|
|
166
|
+
* @returns {number} Capture time in epoch milliseconds
|
|
167
|
+
*/
|
|
168
|
+
function getScreenshotCapturedAtMs (screenshot, filePath) {
|
|
169
|
+
const takenAt = screenshot !== null && typeof screenshot === 'object' ? screenshot.takenAt : undefined
|
|
170
|
+
if (takenAt) {
|
|
171
|
+
const parsedMs = new Date(takenAt).getTime()
|
|
172
|
+
if (Number.isInteger(parsedMs) && parsedMs > 0) {
|
|
173
|
+
return parsedMs
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
return Math.floor(statSync(filePath).mtimeMs)
|
|
178
|
+
} catch {
|
|
179
|
+
return dateNow()
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function isFailureScreenshotForUpload (screenshot) {
|
|
184
|
+
const screenshotFilePath = getScreenshotFilePath(screenshot)
|
|
185
|
+
if (!screenshotFilePath) {
|
|
186
|
+
return false
|
|
187
|
+
}
|
|
188
|
+
if (screenshot !== null && typeof screenshot === 'object') {
|
|
189
|
+
// after:screenshot details omit testFailure for manual cy.screenshot() captures, so this
|
|
190
|
+
// path cannot safely fall back to the '(failed)' filename marker.
|
|
191
|
+
return screenshot.testFailure === true
|
|
192
|
+
}
|
|
193
|
+
return screenshotFilePath.includes('(failed)')
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function isFailureScreenshot (screenshot) {
|
|
197
|
+
const screenshotFilePath = getScreenshotFilePath(screenshot)
|
|
198
|
+
// Require an explicit failure signal: prefer the `testFailure` metadata, and fall back to the
|
|
199
|
+
// '(failed)' filename marker only when the RunResult omits `testFailure`. This keeps manual
|
|
200
|
+
// cy.screenshot() captures out of the failure-screenshot upload (privacy).
|
|
201
|
+
return !!screenshotFilePath && isFailureScreenshotByMetadata(screenshot, screenshotFilePath)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function getAttemptScreenshots (cypressTest, attemptIndex) {
|
|
205
|
+
if (!Array.isArray(cypressTest.attempts)) {
|
|
206
|
+
return []
|
|
207
|
+
}
|
|
208
|
+
const attempt = cypressTest.attempts[attemptIndex]
|
|
209
|
+
if (!Array.isArray(attempt?.screenshots)) {
|
|
210
|
+
return []
|
|
211
|
+
}
|
|
212
|
+
return attempt.screenshots.filter(isFailureScreenshot)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function isScreenshotForTestAttempt (screenshot, titleParts, attemptIndex) {
|
|
216
|
+
const screenshotFilePath = getScreenshotFilePath(screenshot)
|
|
217
|
+
if (!screenshotFilePath || !isFailureScreenshot(screenshot)) {
|
|
218
|
+
return false
|
|
219
|
+
}
|
|
220
|
+
for (const titlePart of titleParts) {
|
|
221
|
+
if (!screenshotFilePath.includes(titlePart)) {
|
|
222
|
+
return false
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (attemptIndex === 0) {
|
|
226
|
+
return !SCREENSHOT_ATTEMPT_RE.test(screenshotFilePath)
|
|
227
|
+
}
|
|
228
|
+
return screenshotFilePath.includes(`(attempt ${attemptIndex + 1})`)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function getTestScreenshots (cypressTest, attemptIndex, specScreenshots) {
|
|
232
|
+
const attemptScreenshots = getAttemptScreenshots(cypressTest, attemptIndex)
|
|
233
|
+
if (attemptScreenshots.length > 0) {
|
|
234
|
+
return attemptScreenshots
|
|
235
|
+
}
|
|
236
|
+
if (!Array.isArray(specScreenshots)) {
|
|
237
|
+
return []
|
|
238
|
+
}
|
|
239
|
+
const titleParts = Array.isArray(cypressTest.title) ? cypressTest.title : []
|
|
240
|
+
return specScreenshots.filter(screenshot => isScreenshotForTestAttempt(screenshot, titleParts, attemptIndex))
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function getScreenshotUploadResult (uploadResults) {
|
|
244
|
+
let hasUploaded = false
|
|
245
|
+
for (const uploadResult of uploadResults) {
|
|
246
|
+
if (uploadResult === SCREENSHOT_UPLOAD_RESULT_ERROR) {
|
|
247
|
+
return SCREENSHOT_UPLOAD_RESULT_ERROR
|
|
248
|
+
}
|
|
249
|
+
if (uploadResult === SCREENSHOT_UPLOAD_RESULT_UPLOADED) {
|
|
250
|
+
hasUploaded = true
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return hasUploaded ? SCREENSHOT_UPLOAD_RESULT_UPLOADED : undefined
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function setScreenshotUploadTags (testSpan, uploadResult) {
|
|
257
|
+
if (uploadResult === SCREENSHOT_UPLOAD_RESULT_ERROR) {
|
|
258
|
+
testSpan.setTag(TEST_FAILURE_SCREENSHOT_UPLOAD_ERROR, 'true')
|
|
259
|
+
} else if (uploadResult === SCREENSHOT_UPLOAD_RESULT_UPLOADED) {
|
|
260
|
+
testSpan.setTag(TEST_FAILURE_SCREENSHOT_UPLOADED, 'true')
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
139
264
|
function getSessionStatus (summary) {
|
|
140
265
|
if (summary.totalFailed !== undefined && summary.totalFailed > 0) {
|
|
141
266
|
return 'fail'
|
|
@@ -390,6 +515,7 @@ class CypressPlugin {
|
|
|
390
515
|
earlyFlakeDetectionFaultyThreshold = 0
|
|
391
516
|
testsToSkip = []
|
|
392
517
|
skippedTests = []
|
|
518
|
+
skippedTestIds = new Set()
|
|
393
519
|
skippableTestsCoverage = {}
|
|
394
520
|
testSessionCoverageMap = createCoverageMap()
|
|
395
521
|
hasForcedToRunSuites = false
|
|
@@ -403,6 +529,11 @@ class CypressPlugin {
|
|
|
403
529
|
newTestsWithDynamicNames = new Set()
|
|
404
530
|
attemptToFixExecutions = new Map()
|
|
405
531
|
loggedAttemptToFixTests = new Set()
|
|
532
|
+
uploadedScreenshotPaths = new Set()
|
|
533
|
+
screenshotUploadPromisesByTraceId = new Map()
|
|
534
|
+
afterScreenshotHandler = undefined
|
|
535
|
+
lastFinishedTest = null
|
|
536
|
+
pendingScreenshotUploads = []
|
|
406
537
|
|
|
407
538
|
constructor () {
|
|
408
539
|
const {
|
|
@@ -473,6 +604,7 @@ class CypressPlugin {
|
|
|
473
604
|
this.earlyFlakeDetectionFaultyThreshold = 0
|
|
474
605
|
this.testsToSkip = []
|
|
475
606
|
this.skippedTests = []
|
|
607
|
+
this.skippedTestIds = new Set()
|
|
476
608
|
this.skippableTestsCoverage = {}
|
|
477
609
|
this.testSessionCoverageMap = createCoverageMap()
|
|
478
610
|
this.hasForcedToRunSuites = false
|
|
@@ -487,6 +619,10 @@ class CypressPlugin {
|
|
|
487
619
|
this.modifiedFiles = []
|
|
488
620
|
this.attemptToFixExecutions = new Map()
|
|
489
621
|
this.loggedAttemptToFixTests = new Set()
|
|
622
|
+
this.uploadedScreenshotPaths = new Set()
|
|
623
|
+
this.screenshotUploadPromisesByTraceId = new Map()
|
|
624
|
+
this.lastFinishedTest = null
|
|
625
|
+
this.pendingScreenshotUploads = []
|
|
490
626
|
this.activeTestSpan = null
|
|
491
627
|
this.testSuiteSpan = null
|
|
492
628
|
this.testModuleSpan = null
|
|
@@ -503,6 +639,50 @@ class CypressPlugin {
|
|
|
503
639
|
this._perfOrigin = 0
|
|
504
640
|
}
|
|
505
641
|
|
|
642
|
+
/**
|
|
643
|
+
* Returns a stable after:screenshot handler so auto-instrumentation can recognize
|
|
644
|
+
* the handler registered by the manual plugin and avoid treating it as a user hook.
|
|
645
|
+
*
|
|
646
|
+
* @returns {Function} Datadog after:screenshot handler
|
|
647
|
+
*/
|
|
648
|
+
getAfterScreenshotHandler () {
|
|
649
|
+
if (!this.afterScreenshotHandler) {
|
|
650
|
+
this.afterScreenshotHandler = this.afterScreenshot.bind(this)
|
|
651
|
+
}
|
|
652
|
+
return this.afterScreenshotHandler
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Tracks a screenshot upload by its owning test trace id so the test event can be tagged
|
|
657
|
+
* before the span is finished.
|
|
658
|
+
*
|
|
659
|
+
* @param {string} traceId - Test trace id used for the upload
|
|
660
|
+
* @param {Promise<string|undefined>} uploadPromise - Promise resolving to the upload outcome
|
|
661
|
+
* @returns {void}
|
|
662
|
+
*/
|
|
663
|
+
addScreenshotUploadPromise (traceId, uploadPromise) {
|
|
664
|
+
const uploadPromises = this.screenshotUploadPromisesByTraceId.get(traceId)
|
|
665
|
+
if (uploadPromises) {
|
|
666
|
+
uploadPromises.push(uploadPromise)
|
|
667
|
+
} else {
|
|
668
|
+
this.screenshotUploadPromisesByTraceId.set(traceId, [uploadPromise])
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Returns the aggregate upload outcome for all screenshots associated with a test trace id.
|
|
674
|
+
*
|
|
675
|
+
* @param {string} traceId - Test trace id used for the upload
|
|
676
|
+
* @returns {Promise<string|undefined>|undefined} Promise resolving to the aggregate upload outcome
|
|
677
|
+
*/
|
|
678
|
+
getScreenshotUploadResultPromise (traceId) {
|
|
679
|
+
const uploadPromises = this.screenshotUploadPromisesByTraceId.get(traceId)
|
|
680
|
+
if (!uploadPromises?.length) {
|
|
681
|
+
return
|
|
682
|
+
}
|
|
683
|
+
return Promise.all(uploadPromises).then(getScreenshotUploadResult)
|
|
684
|
+
}
|
|
685
|
+
|
|
506
686
|
/**
|
|
507
687
|
* Returns the current time in the same coordinate system used by span
|
|
508
688
|
* start/finish. Captured at session span creation so it shares the same
|
|
@@ -625,6 +805,37 @@ class CypressPlugin {
|
|
|
625
805
|
})
|
|
626
806
|
}
|
|
627
807
|
|
|
808
|
+
/**
|
|
809
|
+
* Warns when screenshot upload is enabled but Cypress cannot produce or send the screenshots.
|
|
810
|
+
*
|
|
811
|
+
* @param {object} cypressConfig - Cypress resolved config
|
|
812
|
+
* @param {object} tracer - dd-trace proxy tracer
|
|
813
|
+
* @param {object} testOptimizationConfig - Test Optimization config
|
|
814
|
+
* @returns {void}
|
|
815
|
+
*/
|
|
816
|
+
warnIfMisconfiguredTestFailureScreenshots (cypressConfig, tracer, testOptimizationConfig) {
|
|
817
|
+
if (!testOptimizationConfig.DD_TEST_FAILURE_SCREENSHOTS_ENABLED) {
|
|
818
|
+
return
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
if (cypressConfig.screenshotOnRunFailure === false) {
|
|
822
|
+
log.warn(
|
|
823
|
+
'%s %s',
|
|
824
|
+
'DD_TEST_FAILURE_SCREENSHOTS_ENABLED is true, but Cypress screenshotOnRunFailure is false.',
|
|
825
|
+
'Datadog cannot upload failure screenshots unless Cypress is configured to capture them.'
|
|
826
|
+
)
|
|
827
|
+
return
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
if (!tracer?._tracer?._exporter?.canUploadTestScreenshots?.()) {
|
|
831
|
+
log.warn(
|
|
832
|
+
'%s %s',
|
|
833
|
+
'DD_TEST_FAILURE_SCREENSHOTS_ENABLED is true, but Cypress failure screenshot upload is only supported',
|
|
834
|
+
'in agentless mode.'
|
|
835
|
+
)
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
628
839
|
// Init function returns a promise that resolves with the Cypress configuration
|
|
629
840
|
// Depending on the received configuration, the Cypress configuration can be modified:
|
|
630
841
|
// for example, to enable retries for failed tests.
|
|
@@ -637,7 +848,9 @@ class CypressPlugin {
|
|
|
637
848
|
|
|
638
849
|
this.isTestIsolationEnabled = getIsTestIsolationEnabled(cypressConfig)
|
|
639
850
|
|
|
640
|
-
|
|
851
|
+
const testOptimizationConfig = getConfig().testOptimization
|
|
852
|
+
this.rumFlushWaitMillis = testOptimizationConfig.DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS
|
|
853
|
+
this.warnIfMisconfiguredTestFailureScreenshots(cypressConfig, tracer, testOptimizationConfig)
|
|
641
854
|
|
|
642
855
|
if (!this.isTestIsolationEnabled) {
|
|
643
856
|
log.warn('Test isolation is disabled, retries will not be enabled')
|
|
@@ -1148,7 +1361,7 @@ class CypressPlugin {
|
|
|
1148
1361
|
this.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
|
|
1149
1362
|
incrementCountMetric(TELEMETRY_TEST_SESSION, {
|
|
1150
1363
|
provider: this.ciProviderName,
|
|
1151
|
-
autoInjected: !!getConfig().DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER,
|
|
1364
|
+
autoInjected: !!getConfig().testOptimization.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER,
|
|
1152
1365
|
})
|
|
1153
1366
|
|
|
1154
1367
|
finishAllTraceSpans(this.testSessionSpan)
|
|
@@ -1180,10 +1393,42 @@ class CypressPlugin {
|
|
|
1180
1393
|
})
|
|
1181
1394
|
}
|
|
1182
1395
|
|
|
1396
|
+
/**
|
|
1397
|
+
* Uploads failure screenshots as soon as Cypress creates them.
|
|
1398
|
+
*
|
|
1399
|
+
* @param {object} details - Cypress screenshot details
|
|
1400
|
+
* @returns {void}
|
|
1401
|
+
*/
|
|
1402
|
+
afterScreenshot (details) {
|
|
1403
|
+
const lastFailedTestSpan = this.lastFinishedTest?.testStatus === 'fail'
|
|
1404
|
+
? this.lastFinishedTest.testSpan
|
|
1405
|
+
: undefined
|
|
1406
|
+
const testSpan = this.activeTestSpan || lastFailedTestSpan
|
|
1407
|
+
|
|
1408
|
+
if (!testSpan) {
|
|
1409
|
+
return
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
if (!isFailureScreenshotForUpload(details)) {
|
|
1413
|
+
return
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
const screenshotUploadPromise = this.uploadTestScreenshots({
|
|
1417
|
+
screenshots: [details],
|
|
1418
|
+
traceId: testSpan.context().toTraceId(),
|
|
1419
|
+
})
|
|
1420
|
+
if (screenshotUploadPromise) {
|
|
1421
|
+
this.pendingScreenshotUploads.push(screenshotUploadPromise)
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1183
1425
|
afterSpec (spec, results) {
|
|
1184
|
-
const { tests, stats } = results || {}
|
|
1426
|
+
const { tests, stats, screenshots } = results || {}
|
|
1185
1427
|
const cypressTests = tests || []
|
|
1428
|
+
const specScreenshots = screenshots || []
|
|
1186
1429
|
const finishedTests = this.finishedTestsByFile[spec.relative] || []
|
|
1430
|
+
const screenshotUploadPromises = []
|
|
1431
|
+
const testSpanFinishPromises = []
|
|
1187
1432
|
|
|
1188
1433
|
if (!this.testSuiteSpan) {
|
|
1189
1434
|
// dd:testSuiteStart hasn't been triggered for whatever reason
|
|
@@ -1290,6 +1535,20 @@ class CypressPlugin {
|
|
|
1290
1535
|
if (cypressTest.displayError) {
|
|
1291
1536
|
latestError = new Error(cypressTest.displayError)
|
|
1292
1537
|
}
|
|
1538
|
+
|
|
1539
|
+
let failedTestTraceId
|
|
1540
|
+
if (cypressTestStatus === 'fail' || finishedTest.testStatus === 'fail' || cypressTest.displayError) {
|
|
1541
|
+
failedTestTraceId = finishedTest.testSpan.context().toTraceId()
|
|
1542
|
+
const testScreenshots = getTestScreenshots(cypressTest, attemptIndex, specScreenshots)
|
|
1543
|
+
const screenshotUploadPromise = this.uploadTestScreenshots({
|
|
1544
|
+
screenshots: testScreenshots,
|
|
1545
|
+
traceId: failedTestTraceId,
|
|
1546
|
+
})
|
|
1547
|
+
if (screenshotUploadPromise) {
|
|
1548
|
+
screenshotUploadPromises.push(screenshotUploadPromise)
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1293
1552
|
// Update test status - but NOT for non-ATF quarantined tests where we intentionally
|
|
1294
1553
|
// report 'fail' to Datadog even though Cypress sees it as 'pass'
|
|
1295
1554
|
const isQuarantinedTest = finishedTest.testSpan?.context()?.getTag(TEST_MANAGEMENT_IS_QUARANTINED) === 'true'
|
|
@@ -1346,20 +1605,102 @@ class CypressPlugin {
|
|
|
1346
1605
|
}
|
|
1347
1606
|
}
|
|
1348
1607
|
|
|
1349
|
-
|
|
1608
|
+
const screenshotUploadResultPromise = failedTestTraceId
|
|
1609
|
+
? this.getScreenshotUploadResultPromise(failedTestTraceId)
|
|
1610
|
+
: undefined
|
|
1611
|
+
if (screenshotUploadResultPromise) {
|
|
1612
|
+
testSpanFinishPromises.push(screenshotUploadResultPromise.then((uploadResult) => {
|
|
1613
|
+
setScreenshotUploadTags(finishedTest.testSpan, uploadResult)
|
|
1614
|
+
this.screenshotUploadPromisesByTraceId.delete(failedTestTraceId)
|
|
1615
|
+
finishedTest.testSpan.finish(finishedTest.finishTime)
|
|
1616
|
+
}))
|
|
1617
|
+
} else {
|
|
1618
|
+
finishedTest.testSpan.finish(finishedTest.finishTime)
|
|
1619
|
+
}
|
|
1350
1620
|
}
|
|
1351
1621
|
}
|
|
1352
1622
|
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1623
|
+
const finishSuite = () => {
|
|
1624
|
+
if (this.testSuiteSpan) {
|
|
1625
|
+
const status = getSuiteStatus(stats)
|
|
1626
|
+
this.testSuiteSpan.setTag(TEST_STATUS, status)
|
|
1627
|
+
|
|
1628
|
+
if (latestError) {
|
|
1629
|
+
this.testSuiteSpan.setTag('error', latestError)
|
|
1630
|
+
}
|
|
1631
|
+
this.testSuiteSpan.finish()
|
|
1632
|
+
this.testSuiteSpan = null
|
|
1633
|
+
this.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
const waitForScreenshotUploads = () => {
|
|
1638
|
+
if (screenshotUploadPromises.length > 0 || this.pendingScreenshotUploads.length > 0) {
|
|
1639
|
+
const pendingScreenshotUploads = this.pendingScreenshotUploads
|
|
1640
|
+
this.pendingScreenshotUploads = []
|
|
1641
|
+
return Promise.all([...pendingScreenshotUploads, ...screenshotUploadPromises]).then(() => null)
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
finishSuite()
|
|
1646
|
+
|
|
1647
|
+
const screenshotUploadsPromise = waitForScreenshotUploads()
|
|
1648
|
+
if (testSpanFinishPromises.length > 0) {
|
|
1649
|
+
const testSpansPromise = Promise.all(testSpanFinishPromises).then(() => null)
|
|
1650
|
+
if (screenshotUploadsPromise) {
|
|
1651
|
+
return Promise.all([testSpansPromise, screenshotUploadsPromise]).then(() => null)
|
|
1652
|
+
}
|
|
1653
|
+
return testSpansPromise
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
return screenshotUploadsPromise
|
|
1657
|
+
}
|
|
1356
1658
|
|
|
1357
|
-
|
|
1358
|
-
|
|
1659
|
+
/**
|
|
1660
|
+
* Uploads failure screenshots for a Cypress test attempt.
|
|
1661
|
+
*
|
|
1662
|
+
* @param {object} options - Upload options
|
|
1663
|
+
* @param {Array<object|string>} options.screenshots - Cypress screenshot objects or screenshot paths
|
|
1664
|
+
* @param {string} options.traceId - Test trace id used as the screenshot key
|
|
1665
|
+
* @returns {Promise<string|undefined>|undefined}
|
|
1666
|
+
*/
|
|
1667
|
+
uploadTestScreenshots ({ screenshots, traceId }) {
|
|
1668
|
+
const exporter = this.tracer?._tracer?._exporter
|
|
1669
|
+
if (!screenshots.length ||
|
|
1670
|
+
!exporter?.canUploadTestScreenshots?.() ||
|
|
1671
|
+
!exporter.uploadTestScreenshot) {
|
|
1672
|
+
return
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
const uploadPromises = []
|
|
1676
|
+
|
|
1677
|
+
for (const screenshot of screenshots) {
|
|
1678
|
+
const filePath = getScreenshotFilePath(screenshot)
|
|
1679
|
+
if (!filePath || this.uploadedScreenshotPaths.has(filePath)) {
|
|
1680
|
+
continue
|
|
1359
1681
|
}
|
|
1360
|
-
this.
|
|
1361
|
-
|
|
1362
|
-
|
|
1682
|
+
this.uploadedScreenshotPaths.add(filePath)
|
|
1683
|
+
// Stable per-artifact key (trace + filename) so an upload retry overwrites the
|
|
1684
|
+
// same stored object instead of duplicating; the Cypress filename encodes the
|
|
1685
|
+
// test name and attempt, so it is unique within the trace.
|
|
1686
|
+
const idempotencyKey = `${traceId}:${basename(filePath)}`
|
|
1687
|
+
const capturedAtMs = getScreenshotCapturedAtMs(screenshot, filePath)
|
|
1688
|
+
uploadPromises.push(new Promise(resolve => {
|
|
1689
|
+
exporter.uploadTestScreenshot({
|
|
1690
|
+
filePath,
|
|
1691
|
+
traceId,
|
|
1692
|
+
idempotencyKey,
|
|
1693
|
+
capturedAtMs,
|
|
1694
|
+
}, (err) => {
|
|
1695
|
+
resolve(err ? SCREENSHOT_UPLOAD_RESULT_ERROR : SCREENSHOT_UPLOAD_RESULT_UPLOADED)
|
|
1696
|
+
})
|
|
1697
|
+
}))
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
if (uploadPromises.length > 0) {
|
|
1701
|
+
const uploadPromise = Promise.all(uploadPromises).then(getScreenshotUploadResult)
|
|
1702
|
+
this.addScreenshotUploadPromise(traceId, uploadPromise)
|
|
1703
|
+
return uploadPromise
|
|
1363
1704
|
}
|
|
1364
1705
|
}
|
|
1365
1706
|
|
|
@@ -1386,7 +1727,7 @@ class CypressPlugin {
|
|
|
1386
1727
|
return suitePayload
|
|
1387
1728
|
},
|
|
1388
1729
|
'dd:beforeEach': (test) => {
|
|
1389
|
-
const { testName, testSuite, isEfdRetry, efdRetryIndex } = test
|
|
1730
|
+
const { testId, testName, testSuite, isEfdRetry, efdRetryIndex } = test
|
|
1390
1731
|
if (isEfdRetry && this.shouldSkipEfdRetry(testSuite, testName, efdRetryIndex)) {
|
|
1391
1732
|
return { shouldSkip: true, shouldDiscard: true }
|
|
1392
1733
|
}
|
|
@@ -1398,7 +1739,11 @@ class CypressPlugin {
|
|
|
1398
1739
|
const { isAttemptToFix, isDisabled, isQuarantined } = this.getTestProperties(testSuite, testName)
|
|
1399
1740
|
// skip test
|
|
1400
1741
|
if (shouldSkip && !isUnskippable) {
|
|
1401
|
-
|
|
1742
|
+
const skippedTestId = `${testSuite}:${testId || testName}`
|
|
1743
|
+
if (!this.skippedTestIds.has(skippedTestId)) {
|
|
1744
|
+
this.skippedTestIds.add(skippedTestId)
|
|
1745
|
+
this.skippedTests.push(test)
|
|
1746
|
+
}
|
|
1402
1747
|
this.isTestsSkipped = true
|
|
1403
1748
|
return { shouldSkip: true }
|
|
1404
1749
|
}
|
|
@@ -1637,6 +1982,7 @@ class CypressPlugin {
|
|
|
1637
1982
|
} else {
|
|
1638
1983
|
this.finishedTestsByFile[testSuite] = [finishedTest]
|
|
1639
1984
|
}
|
|
1985
|
+
this.lastFinishedTest = finishedTest
|
|
1640
1986
|
// test spans are finished at after:spec
|
|
1641
1987
|
this.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test', {
|
|
1642
1988
|
hasCodeOwners: !!activeSpanTags[TEST_CODE_OWNERS],
|
|
@@ -18,7 +18,14 @@ class CypressPlugin extends Plugin {
|
|
|
18
18
|
// Mirrors the guard in the manual plugin entrypoint (plugin.js).
|
|
19
19
|
if (this._tracer._tracer instanceof NoopTracer) return
|
|
20
20
|
|
|
21
|
-
const {
|
|
21
|
+
const {
|
|
22
|
+
on,
|
|
23
|
+
config,
|
|
24
|
+
userAfterSpecHandlers,
|
|
25
|
+
userAfterRunHandlers,
|
|
26
|
+
userAfterScreenshotHandlers,
|
|
27
|
+
cleanupWrapper,
|
|
28
|
+
} = payload
|
|
22
29
|
|
|
23
30
|
const registerAfterRunWithCleanup = (afterRunHandler) => {
|
|
24
31
|
on('after:run', (results) => {
|
|
@@ -34,16 +41,54 @@ class CypressPlugin extends Plugin {
|
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
const cypressPlugin = require('./cypress-plugin')
|
|
44
|
+
const datadogAfterScreenshotHandler = cypressPlugin.getAfterScreenshotHandler()
|
|
45
|
+
|
|
46
|
+
// Cypress keeps a single after:screenshot handler, so registering the
|
|
47
|
+
// plugin's would drop any user handler (e.g. one that moves/renames a
|
|
48
|
+
// screenshot). Chain the user handler(s) first, threading the returned
|
|
49
|
+
// { path, size, dimensions } forward — Cypress uses the final path for
|
|
50
|
+
// downstream steps — then run the plugin's afterScreenshot on those
|
|
51
|
+
// details and propagate the value back to Cypress. The user handler runs
|
|
52
|
+
// regardless of whether screenshot upload is enabled.
|
|
53
|
+
const registerAfterScreenshot = (afterScreenshotHandler) => {
|
|
54
|
+
const filteredUserAfterScreenshotHandlers = userAfterScreenshotHandlers.filter(
|
|
55
|
+
handler => handler !== afterScreenshotHandler
|
|
56
|
+
)
|
|
57
|
+
if (filteredUserAfterScreenshotHandlers.length === 0) {
|
|
58
|
+
if (afterScreenshotHandler) on('after:screenshot', afterScreenshotHandler)
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
on('after:screenshot', (details) => {
|
|
63
|
+
const chain = filteredUserAfterScreenshotHandlers.reduce(
|
|
64
|
+
(p, h) => p.then((latestDetails) => Promise.resolve(h(latestDetails)).then(
|
|
65
|
+
// Merge the user handler's (possibly partial) return into the running details
|
|
66
|
+
// rather than replacing them, so Cypress metadata such as `testFailure` and
|
|
67
|
+
// `takenAt` survives a handler that only returns { path, size, dimensions }.
|
|
68
|
+
(returned) => (returned == null ? latestDetails : { ...latestDetails, ...returned })
|
|
69
|
+
)),
|
|
70
|
+
Promise.resolve(details)
|
|
71
|
+
)
|
|
72
|
+
return chain.then((finalDetails) => {
|
|
73
|
+
if (afterScreenshotHandler) afterScreenshotHandler(finalDetails)
|
|
74
|
+
return finalDetails
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
}
|
|
37
78
|
|
|
38
79
|
if (cypressPlugin._isInit) {
|
|
39
|
-
// Already initialized by manual plugin call — just chain user handlers
|
|
80
|
+
// Already initialized by manual plugin call — just chain user handlers.
|
|
81
|
+
// Pass the plugin's afterScreenshot so chaining a user handler doesn't drop the upload
|
|
82
|
+
// (the chained registration replaces the one plugin.js set, so it must include it).
|
|
40
83
|
for (const h of userAfterSpecHandlers) on('after:spec', h)
|
|
84
|
+
registerAfterScreenshot(datadogAfterScreenshotHandler)
|
|
41
85
|
registerAfterRunWithCleanup()
|
|
42
86
|
payload.registered = true
|
|
43
87
|
return
|
|
44
88
|
}
|
|
45
89
|
|
|
46
90
|
on('before:run', cypressPlugin.beforeRun.bind(cypressPlugin))
|
|
91
|
+
registerAfterScreenshot(datadogAfterScreenshotHandler)
|
|
47
92
|
|
|
48
93
|
on('after:spec', (spec, results) => {
|
|
49
94
|
const chain = userAfterSpecHandlers.reduce(
|
|
@@ -40,6 +40,7 @@ module.exports = function CypressPlugin (on, config) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
on('before:run', cypressPlugin.beforeRun.bind(cypressPlugin))
|
|
43
|
+
on('after:screenshot', cypressPlugin.getAfterScreenshotHandler())
|
|
43
44
|
on('after:spec', cypressPlugin.afterSpec.bind(cypressPlugin))
|
|
44
45
|
on('after:run', cypressPlugin.afterRun.bind(cypressPlugin))
|
|
45
46
|
on('task', cypressPlugin.getTasks())
|
|
@@ -13,6 +13,8 @@ let testManagementTests = {}
|
|
|
13
13
|
let isImpactedTestsEnabled = false
|
|
14
14
|
let isModifiedTest = false
|
|
15
15
|
let isTestIsolationEnabled = false
|
|
16
|
+
let hasWarnedMissingBeforeEachTaskResult = false
|
|
17
|
+
let hasWarnedMissingBeforeEachRetryResult = false
|
|
16
18
|
// Array of test names that have been retried and the reason
|
|
17
19
|
const retryReasonsByTestName = new Map()
|
|
18
20
|
// Track test errors suppressed by test management so we can still report them to Datadog.
|
|
@@ -86,6 +88,47 @@ function getTestProperties (testName) {
|
|
|
86
88
|
return { isAttemptToFix, isDisabled, isQuarantined }
|
|
87
89
|
}
|
|
88
90
|
|
|
91
|
+
/**
|
|
92
|
+
* @param {string} message
|
|
93
|
+
* @returns {void}
|
|
94
|
+
*/
|
|
95
|
+
function warnMissingBeforeEachTaskResult (message) {
|
|
96
|
+
// eslint-disable-next-line no-console
|
|
97
|
+
console.warn(message)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @param {object} test
|
|
102
|
+
* @returns {Cypress.Chainable<{ traceId?: string, shouldSkip?: boolean, shouldDiscard?: boolean }>}
|
|
103
|
+
*/
|
|
104
|
+
function runBeforeEachTask (test) {
|
|
105
|
+
return cy.task('dd:beforeEach', test).then((taskResult) => {
|
|
106
|
+
if (taskResult !== undefined && taskResult !== null) {
|
|
107
|
+
return taskResult
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!hasWarnedMissingBeforeEachTaskResult) {
|
|
111
|
+
hasWarnedMissingBeforeEachTaskResult = true
|
|
112
|
+
warnMissingBeforeEachTaskResult('Datadog Cypress dd:beforeEach task returned no result. Retrying once.')
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return cy.task('dd:beforeEach', test).then((retryTaskResult) => {
|
|
116
|
+
if (retryTaskResult !== undefined && retryTaskResult !== null) {
|
|
117
|
+
return retryTaskResult
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!hasWarnedMissingBeforeEachRetryResult) {
|
|
121
|
+
hasWarnedMissingBeforeEachRetryResult = true
|
|
122
|
+
warnMissingBeforeEachTaskResult(
|
|
123
|
+
'Datadog Cypress dd:beforeEach task returned no result after retry. Continuing with an empty task result.'
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {}
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
|
|
89
132
|
// Catch test failures for quarantined tests and suppress them
|
|
90
133
|
// By not re-throwing the error, Cypress marks the test as passed
|
|
91
134
|
// This allows quarantined tests to run but not affect the exit code
|
|
@@ -263,7 +306,8 @@ beforeEach(function () {
|
|
|
263
306
|
originalWindow = win
|
|
264
307
|
})
|
|
265
308
|
|
|
266
|
-
|
|
309
|
+
runBeforeEachTask({
|
|
310
|
+
testId: currentTest.id,
|
|
267
311
|
testName,
|
|
268
312
|
testSuite: Cypress.mocha.getRootSuite().file,
|
|
269
313
|
isEfdRetry: Cypress.mocha.getRunner().suite.ctx.currentTest._ddIsEfdRetry,
|
|
@@ -31,7 +31,7 @@ class ExpressCodeOriginForSpansPlugin extends Plugin {
|
|
|
31
31
|
layerTags.set(layer, entryTags(topOfStackFunc))
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
if (this._tracerConfig.remoteConfig
|
|
34
|
+
if (this._tracerConfig.remoteConfig.DD_REMOTE_CONFIGURATION_ENABLED) {
|
|
35
35
|
// When RC is enabled, use manual subscriptions (always pre-compute)
|
|
36
36
|
// This allows tags to be computed even when CO is disabled, so runtime enabling works
|
|
37
37
|
dc.channel('apm:express:route:added').subscribe(handleRouteAdded)
|
|
@@ -20,7 +20,7 @@ class FastifyCodeOriginForSpansPlugin extends Plugin {
|
|
|
20
20
|
web.getContext(req)?.span?.addTags(tags)
|
|
21
21
|
})
|
|
22
22
|
|
|
23
|
-
if (this._tracerConfig.remoteConfig
|
|
23
|
+
if (this._tracerConfig.remoteConfig.DD_REMOTE_CONFIGURATION_ENABLED) {
|
|
24
24
|
// When RC is enabled, use manual subscription (always pre-computes)
|
|
25
25
|
// This allows tags to be computed even when CO is disabled, so runtime enabling works
|
|
26
26
|
dc.channel('apm:fastify:route:added').subscribe(handleRouteAdded)
|