dd-trace 6.9.0 → 6.10.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/ci/vitest-no-worker-init-setup.mjs +112 -10
- package/index.d.ts +42 -0
- package/package.json +4 -3
- package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +111 -6
- package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +629 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +81 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +83 -1
- package/packages/datadog-instrumentations/src/jest.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/main.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/utils.js +2 -8
- package/packages/datadog-instrumentations/src/mysql.js +45 -15
- package/packages/datadog-instrumentations/src/mysql2.js +119 -18
- package/packages/datadog-instrumentations/src/path-to-regexp.js +67 -1
- package/packages/datadog-instrumentations/src/pg.js +116 -92
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +43 -4
- package/packages/datadog-instrumentations/src/vitest-main.js +169 -17
- package/packages/datadog-instrumentations/src/vitest-util.js +64 -5
- package/packages/datadog-instrumentations/src/vitest-worker.js +150 -25
- package/packages/datadog-instrumentations/src/webdriverio.js +31 -17
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +12 -6
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -5
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -5
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +1 -1
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +31 -17
- package/packages/datadog-plugin-jest/src/index.js +5 -4
- package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +2 -3
- package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -3
- package/packages/datadog-plugin-kafkajs/src/producer.js +3 -2
- package/packages/datadog-plugin-kafkajs/src/utils.js +65 -7
- package/packages/datadog-plugin-mocha/src/index.js +550 -39
- package/packages/datadog-plugin-mysql/src/index.js +37 -0
- package/packages/datadog-plugin-next/src/index.js +1 -19
- package/packages/datadog-plugin-openai-agents/src/integration.js +278 -69
- package/packages/datadog-plugin-openai-agents/src/processor.js +16 -4
- package/packages/datadog-plugin-pg/src/index.js +1 -0
- package/packages/datadog-plugin-vitest/src/index.js +8 -20
- package/packages/datadog-plugin-ws/src/util.js +2 -1
- package/packages/dd-trace/src/appsec/api_security/normalized-route.js +453 -0
- package/packages/dd-trace/src/appsec/handlers/http-request.js +13 -0
- package/packages/dd-trace/src/carrier.js +356 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +3 -0
- package/packages/dd-trace/src/config/supported-configurations.json +10 -0
- package/packages/dd-trace/src/datastreams/context.js +6 -1
- package/packages/dd-trace/src/datastreams/manager.js +5 -1
- package/packages/dd-trace/src/datastreams/pathway.js +23 -22
- package/packages/dd-trace/src/datastreams/processor.js +3 -2
- package/packages/dd-trace/src/llmobs/constants/tags.js +5 -0
- package/packages/dd-trace/src/llmobs/index.js +34 -2
- package/packages/dd-trace/src/llmobs/span_processor.js +16 -0
- package/packages/dd-trace/src/llmobs/tagger.js +96 -0
- package/packages/dd-trace/src/llmobs/util.js +78 -0
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +126 -120
- package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +4 -8
- package/packages/dd-trace/src/plugin_manager.js +2 -0
- package/packages/{datadog-plugin-jest/src/util.js → dd-trace/src/plugins/util/jest.js} +1 -62
- package/packages/dd-trace/src/plugins/util/test.js +84 -5
- package/packages/dd-trace/src/plugins/util/web.js +53 -2
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { performance } = require('node:perf_hooks')
|
|
3
4
|
const { fileURLToPath } = require('node:url')
|
|
4
5
|
|
|
5
6
|
const { channel } = require('dc-polyfill')
|
|
6
7
|
|
|
7
8
|
const CiPlugin = require('../../dd-trace/src/plugins/ci_plugin')
|
|
8
9
|
const { storage } = require('../../datadog-core')
|
|
10
|
+
const {
|
|
11
|
+
getEfdRetryCountForDuration,
|
|
12
|
+
hasEfdRetries,
|
|
13
|
+
} = require('../../dd-trace/src/ci-visibility/efd-retry-policy')
|
|
9
14
|
const log = require('../../dd-trace/src/log')
|
|
10
15
|
const {
|
|
11
16
|
sendWebdriverioWorkerMessage,
|
|
@@ -44,6 +49,8 @@ const {
|
|
|
44
49
|
TEST_FINAL_STATUS,
|
|
45
50
|
TEST_HAS_DYNAMIC_NAME,
|
|
46
51
|
TEST_FRAMEWORK_ADAPTER,
|
|
52
|
+
DYNAMIC_NAME_RE,
|
|
53
|
+
getFailedTestReplayPromise,
|
|
47
54
|
getTestSuiteExecutionKey,
|
|
48
55
|
isModifiedTest,
|
|
49
56
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
@@ -62,14 +69,24 @@ const {
|
|
|
62
69
|
|
|
63
70
|
const jasmineAdapterRunAsyncEndCh = 'tracing:orchestrion:@wdio/jasmine-framework:JasmineAdapter_run:asyncEnd'
|
|
64
71
|
const jasmineDoneCh = 'ci:webdriverio:jasmine:done'
|
|
72
|
+
const jasmineExecuteAsyncEndCh = 'tracing:orchestrion:@wdio/utils:executeAsync:asyncEnd'
|
|
73
|
+
const jasmineExecuteAsyncErrorCh = 'tracing:orchestrion:@wdio/utils:executeAsync:error'
|
|
74
|
+
const jasmineExecuteAsyncStartCh = 'tracing:orchestrion:@wdio/utils:executeAsync:start'
|
|
65
75
|
const jasmineReporterSpecDoneEndCh = 'tracing:orchestrion:@wdio/jasmine-framework:JasmineReporter_specDone:end'
|
|
76
|
+
const jasmineReporterSpecDoneStartCh = 'tracing:orchestrion:@wdio/jasmine-framework:JasmineReporter_specDone:start'
|
|
66
77
|
const jasmineReporterSpecStartedEndCh = 'tracing:orchestrion:@wdio/jasmine-framework:JasmineReporter_specStarted:end'
|
|
67
78
|
const jasmineReporterSuiteDoneEndCh = 'tracing:orchestrion:@wdio/jasmine-framework:JasmineReporter_suiteDone:end'
|
|
68
79
|
const jasmineReporterSuiteStartedEndCh = 'tracing:orchestrion:@wdio/jasmine-framework:JasmineReporter_suiteStarted:end'
|
|
80
|
+
const jasmineSpecAttemptDoneEndCh = 'tracing:orchestrion:jasmine-core:Spec_attemptDone:end'
|
|
81
|
+
const jasmineSpecExecuteStartCh = 'tracing:orchestrion:jasmine-core:Spec_execute:start'
|
|
69
82
|
const jasmineTestFunctionStartCh = 'tracing:orchestrion:@wdio/utils:testFrameworkFnWrapper:start'
|
|
70
83
|
const testFinishCh = channel('ci:mocha:test:finish')
|
|
84
|
+
const testRetryCh = channel('ci:mocha:test:retry')
|
|
71
85
|
const WEBDRIVERIO_JASMINE_ADAPTER = 'jasmine'
|
|
72
86
|
const workerFinishCh = channel('ci:mocha:worker:finish')
|
|
87
|
+
const WEBDRIVERIO_JASMINE_FAILED_EXPECTATION_COUNT = Symbol('webdriverioJasmineFailedExpectationCount')
|
|
88
|
+
const WEBDRIVERIO_JASMINE_FUNCTION_TYPE = Symbol('webdriverioJasmineFunctionType')
|
|
89
|
+
const WEBDRIVERIO_JASMINE_TEST = Symbol('webdriverioJasmineTest')
|
|
73
90
|
|
|
74
91
|
/**
|
|
75
92
|
* @typedef {object} WebdriverioJasmineResult
|
|
@@ -170,7 +187,10 @@ class MochaPlugin extends CiPlugin {
|
|
|
170
187
|
this._setRepositoryRoot(repositoryRoot)
|
|
171
188
|
if (testFrameworkAdapter === WEBDRIVERIO_JASMINE_ADAPTER) {
|
|
172
189
|
this._webdriverioJasmineState = {
|
|
190
|
+
completedTestStatuses: new Map(),
|
|
173
191
|
currentResult: undefined,
|
|
192
|
+
pendingTestFinishCallbacks: [],
|
|
193
|
+
pendingTestFinishes: 0,
|
|
174
194
|
specs: specs || [],
|
|
175
195
|
suiteErrors: new Map(),
|
|
176
196
|
suiteFiles: new Map(),
|
|
@@ -188,11 +208,82 @@ class MochaPlugin extends CiPlugin {
|
|
|
188
208
|
const result = this._webdriverioJasmineState?.currentResult
|
|
189
209
|
const type = ctx.arguments?.[1]
|
|
190
210
|
if (type === 'Test' || (type === 'Hook' && result)) {
|
|
191
|
-
return this.#
|
|
211
|
+
return this.#configureWebdriverioJasmineFunction(ctx, result, type)
|
|
212
|
+
}
|
|
213
|
+
return storage('legacy').getStore()
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
this.addBind(jasmineExecuteAsyncStartCh, (ctx) => {
|
|
217
|
+
const currentStore = storage('legacy').getStore()
|
|
218
|
+
const test = currentStore?.[WEBDRIVERIO_JASMINE_TEST]
|
|
219
|
+
if (this.testFrameworkAdapter !== WEBDRIVERIO_JASMINE_ADAPTER || !test) {
|
|
220
|
+
return currentStore
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const functionType = currentStore[WEBDRIVERIO_JASMINE_FUNCTION_TYPE]
|
|
224
|
+
if (functionType === 'Test') {
|
|
225
|
+
ctx.retryCallback = error => this.#retryWebdriverioJasmineTest(test, error)
|
|
226
|
+
}
|
|
227
|
+
const nextStore = {
|
|
228
|
+
...test.currentStore,
|
|
229
|
+
[WEBDRIVERIO_JASMINE_FUNCTION_TYPE]: functionType,
|
|
230
|
+
}
|
|
231
|
+
if (functionType === 'Hook') {
|
|
232
|
+
nextStore[WEBDRIVERIO_JASMINE_FAILED_EXPECTATION_COUNT] =
|
|
233
|
+
this._webdriverioJasmineState.currentResult?.failedExpectations?.length || 0
|
|
234
|
+
}
|
|
235
|
+
return nextStore
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
this.addBind(jasmineSpecExecuteStartCh, (ctx) => {
|
|
239
|
+
if (this.testFrameworkAdapter !== WEBDRIVERIO_JASMINE_ADAPTER) {
|
|
240
|
+
return storage('legacy').getStore()
|
|
192
241
|
}
|
|
242
|
+
|
|
243
|
+
this.#configureWebdriverioJasmineLifecycle(ctx)
|
|
193
244
|
return storage('legacy').getStore()
|
|
194
245
|
})
|
|
195
246
|
|
|
247
|
+
this.addSub(jasmineSpecAttemptDoneEndCh, (ctx) => {
|
|
248
|
+
if (this.testFrameworkAdapter !== WEBDRIVERIO_JASMINE_ADAPTER) {
|
|
249
|
+
return
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const status = typeof ctx.result === 'string' ? ctx.result : ctx.self?.result?.status
|
|
253
|
+
const runnerStatus = this.#prepareWebdriverioJasmineAttempt(ctx.self, status)
|
|
254
|
+
if (typeof ctx.result === 'string') {
|
|
255
|
+
ctx.result = runnerStatus
|
|
256
|
+
} else if (ctx.self?.result) {
|
|
257
|
+
ctx.self.result.status = runnerStatus
|
|
258
|
+
}
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
this.addSub(jasmineExecuteAsyncErrorCh, (ctx) => {
|
|
262
|
+
const currentStore = ctx.currentStore || storage('legacy').getStore()
|
|
263
|
+
const test = currentStore?.[WEBDRIVERIO_JASMINE_TEST]
|
|
264
|
+
if (
|
|
265
|
+
this.testFrameworkAdapter === WEBDRIVERIO_JASMINE_ADAPTER &&
|
|
266
|
+
test &&
|
|
267
|
+
currentStore[WEBDRIVERIO_JASMINE_FUNCTION_TYPE] === 'Hook'
|
|
268
|
+
) {
|
|
269
|
+
test.hasFinalHookFailure = true
|
|
270
|
+
}
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
this.addSub(jasmineExecuteAsyncEndCh, (ctx) => {
|
|
274
|
+
const currentStore = ctx.currentStore || storage('legacy').getStore()
|
|
275
|
+
const test = currentStore?.[WEBDRIVERIO_JASMINE_TEST]
|
|
276
|
+
const failedExpectationCount = currentStore?.[WEBDRIVERIO_JASMINE_FAILED_EXPECTATION_COUNT]
|
|
277
|
+
if (
|
|
278
|
+
this.testFrameworkAdapter === WEBDRIVERIO_JASMINE_ADAPTER &&
|
|
279
|
+
test &&
|
|
280
|
+
currentStore[WEBDRIVERIO_JASMINE_FUNCTION_TYPE] === 'Hook' &&
|
|
281
|
+
this._webdriverioJasmineState.currentResult?.failedExpectations?.length > failedExpectationCount
|
|
282
|
+
) {
|
|
283
|
+
test.hasFinalHookFailure = true
|
|
284
|
+
}
|
|
285
|
+
})
|
|
286
|
+
|
|
196
287
|
this.addSub(jasmineReporterSuiteStartedEndCh, (ctx) => {
|
|
197
288
|
if (this.testFrameworkAdapter === WEBDRIVERIO_JASMINE_ADAPTER) {
|
|
198
289
|
const suite = ctx.arguments?.[0]
|
|
@@ -239,9 +330,32 @@ class MochaPlugin extends CiPlugin {
|
|
|
239
330
|
}
|
|
240
331
|
})
|
|
241
332
|
|
|
333
|
+
this.addBind(jasmineReporterSpecDoneStartCh, (ctx) => {
|
|
334
|
+
if (this.testFrameworkAdapter !== WEBDRIVERIO_JASMINE_ADAPTER) {
|
|
335
|
+
return storage('legacy').getStore()
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const result = ctx.arguments?.[0]
|
|
339
|
+
const test = this._webdriverioJasmineState?.tests.get(result?.id)
|
|
340
|
+
const recoveredEarlyFlakeDetection = test?.isEarlyFlakeDetection &&
|
|
341
|
+
!test.hasFinalHookFailure && test.statuses.includes('pass')
|
|
342
|
+
if (
|
|
343
|
+
result.status === 'failed' &&
|
|
344
|
+
((test?.isQuarantined && !test.isAttemptToFix) || recoveredEarlyFlakeDetection)
|
|
345
|
+
) {
|
|
346
|
+
test.reportedStatus = result.status
|
|
347
|
+
result.status = 'passed'
|
|
348
|
+
}
|
|
349
|
+
return test?.currentStore || storage('legacy').getStore()
|
|
350
|
+
})
|
|
351
|
+
|
|
242
352
|
this.addSub(jasmineReporterSpecDoneEndCh, (ctx) => {
|
|
243
353
|
if (this.testFrameworkAdapter === WEBDRIVERIO_JASMINE_ADAPTER) {
|
|
244
|
-
|
|
354
|
+
const result = ctx.result
|
|
355
|
+
const finishPromise = this.#finishWebdriverioJasmineTest(ctx.arguments?.[0], ctx.self?._specs)
|
|
356
|
+
if (finishPromise) {
|
|
357
|
+
ctx.result = finishPromise.then(() => result)
|
|
358
|
+
}
|
|
245
359
|
}
|
|
246
360
|
})
|
|
247
361
|
|
|
@@ -510,10 +624,13 @@ class MochaPlugin extends CiPlugin {
|
|
|
510
624
|
this.addSub('ci:mocha:test:retry', ({
|
|
511
625
|
span,
|
|
512
626
|
isFirstAttempt,
|
|
627
|
+
isFirstFailure = isFirstAttempt,
|
|
513
628
|
willBeRetried,
|
|
514
629
|
err,
|
|
515
630
|
test,
|
|
631
|
+
isAttemptToFixRetry,
|
|
516
632
|
isAtrRetry,
|
|
633
|
+
isEfdRetry,
|
|
517
634
|
promises,
|
|
518
635
|
}) => {
|
|
519
636
|
if (span) {
|
|
@@ -528,8 +645,12 @@ class MochaPlugin extends CiPlugin {
|
|
|
528
645
|
span.setTag(TEST_STATUS, 'fail')
|
|
529
646
|
if (!isFirstAttempt) {
|
|
530
647
|
span.setTag(TEST_IS_RETRY, 'true')
|
|
531
|
-
if (
|
|
648
|
+
if (isAttemptToFixRetry) {
|
|
649
|
+
span.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.atf)
|
|
650
|
+
} else if (isAtrRetry) {
|
|
532
651
|
span.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.atr)
|
|
652
|
+
} else if (isEfdRetry) {
|
|
653
|
+
span.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.efd)
|
|
533
654
|
} else {
|
|
534
655
|
span.setTag(TEST_RETRY_REASON, TEST_RETRY_REASON_TYPES.ext)
|
|
535
656
|
}
|
|
@@ -543,7 +664,7 @@ class MochaPlugin extends CiPlugin {
|
|
|
543
664
|
'test',
|
|
544
665
|
this.getTestTelemetryTags(span)
|
|
545
666
|
)
|
|
546
|
-
if (
|
|
667
|
+
if (isFirstFailure && willBeRetried && this.di && this.libraryConfig?.isDiEnabled) {
|
|
547
668
|
const probeInformation = this.addDiProbe(err)
|
|
548
669
|
if (probeInformation) {
|
|
549
670
|
const { file, line, stackIndex, setProbePromise } = probeInformation
|
|
@@ -557,7 +678,7 @@ class MochaPlugin extends CiPlugin {
|
|
|
557
678
|
}
|
|
558
679
|
}
|
|
559
680
|
|
|
560
|
-
if (!
|
|
681
|
+
if (!isFirstFailure &&
|
|
561
682
|
willBeRetried &&
|
|
562
683
|
this.di &&
|
|
563
684
|
this.libraryConfig?.isDiEnabled &&
|
|
@@ -683,6 +804,9 @@ class MochaPlugin extends CiPlugin {
|
|
|
683
804
|
if (!state || !result?.id) {
|
|
684
805
|
return currentStore
|
|
685
806
|
}
|
|
807
|
+
if (state.completedTestStatuses.has(result.id)) {
|
|
808
|
+
return currentStore
|
|
809
|
+
}
|
|
686
810
|
|
|
687
811
|
const existingTest = state.tests.get(result.id)
|
|
688
812
|
if (existingTest) {
|
|
@@ -702,20 +826,333 @@ class MochaPlugin extends CiPlugin {
|
|
|
702
826
|
return currentStore
|
|
703
827
|
}
|
|
704
828
|
|
|
705
|
-
const
|
|
706
|
-
|
|
829
|
+
const testName = result.fullName || result.description
|
|
830
|
+
const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.sourceRoot)
|
|
831
|
+
const properties = this.libraryConfig?.testManagementTests?.mocha
|
|
832
|
+
?.suites?.[testSuite]?.tests?.[testName]?.properties || {}
|
|
833
|
+
const isAttemptToFix = this.libraryConfig?.isTestManagementTestsEnabled && properties.attempt_to_fix
|
|
834
|
+
const isDisabled = this.libraryConfig?.isTestManagementTestsEnabled && properties.disabled
|
|
835
|
+
const isQuarantined = this.libraryConfig?.isTestManagementTestsEnabled && properties.quarantined
|
|
836
|
+
const isModified = this.libraryConfig?.isImpactedTestsEnabled && isModifiedTest(
|
|
837
|
+
getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot || this.sourceRoot),
|
|
838
|
+
null,
|
|
839
|
+
null,
|
|
840
|
+
this.libraryConfig.modifiedFiles,
|
|
841
|
+
this.constructor.id
|
|
842
|
+
)
|
|
843
|
+
const knownTests = this.libraryConfig?.knownTests?.mocha
|
|
844
|
+
const isNew = this.libraryConfig?.isKnownTestsEnabled && knownTests &&
|
|
845
|
+
!(knownTests[testSuite] || []).includes(testName)
|
|
846
|
+
const isEarlyFlakeDetection = this.libraryConfig?.isEarlyFlakeDetectionEnabled &&
|
|
847
|
+
hasEfdRetries(this.libraryConfig.earlyFlakeDetectionRetryPolicy) &&
|
|
848
|
+
!isAttemptToFix && !isDisabled && (isNew || isModified)
|
|
849
|
+
const isAtr = this.libraryConfig?.isFlakyTestRetriesEnabled &&
|
|
850
|
+
!isAttemptToFix && !isEarlyFlakeDetection
|
|
851
|
+
let retryCount = 0
|
|
852
|
+
if (isAttemptToFix) {
|
|
853
|
+
retryCount = this.libraryConfig.testManagementAttemptToFixRetries
|
|
854
|
+
} else if (isEarlyFlakeDetection) {
|
|
855
|
+
retryCount = this.libraryConfig.earlyFlakeDetectionRetryPolicy.schedulingRetryCount
|
|
856
|
+
} else if (isAtr) {
|
|
857
|
+
retryCount = this.libraryConfig.flakyTestRetriesCount
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
const test = {
|
|
861
|
+
attempt: 0,
|
|
862
|
+
attemptStart: undefined,
|
|
863
|
+
currentStore: undefined,
|
|
864
|
+
earlyFlakeAbortReason: undefined,
|
|
865
|
+
hasDynamicName: isNew && DYNAMIC_NAME_RE.test(testName),
|
|
866
|
+
hasFailedAttempt: false,
|
|
867
|
+
hasFinalHookFailure: false,
|
|
868
|
+
isAttemptToFix,
|
|
869
|
+
isAtr,
|
|
870
|
+
isDisabled,
|
|
871
|
+
isEarlyFlakeDetection,
|
|
872
|
+
isModified,
|
|
873
|
+
isNew,
|
|
874
|
+
isQuarantined,
|
|
875
|
+
reportedStatus: undefined,
|
|
876
|
+
retryCount,
|
|
877
|
+
retryWait: undefined,
|
|
878
|
+
runnerStatus: undefined,
|
|
879
|
+
span: undefined,
|
|
880
|
+
statuses: [],
|
|
881
|
+
testName,
|
|
707
882
|
testSuiteAbsolutePath,
|
|
708
883
|
title: result.description,
|
|
884
|
+
}
|
|
885
|
+
state.tests.set(result.id, test)
|
|
886
|
+
this.#startWebdriverioJasmineAttempt(test, currentStore)
|
|
887
|
+
|
|
888
|
+
return test.currentStore
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/**
|
|
892
|
+
* Applies a managed Jasmine test's skip and retry policy to WebdriverIO's function wrapper.
|
|
893
|
+
*
|
|
894
|
+
* @param {object} context
|
|
895
|
+
* @param {WebdriverioJasmineResult|undefined} result
|
|
896
|
+
* @param {'Test'|'Hook'} type
|
|
897
|
+
* @returns {object|undefined}
|
|
898
|
+
*/
|
|
899
|
+
#configureWebdriverioJasmineFunction (context, result, type) {
|
|
900
|
+
const currentStore = this.#startWebdriverioJasmineTest(result)
|
|
901
|
+
const test = this._webdriverioJasmineState?.tests.get(result?.id)
|
|
902
|
+
if (!test) {
|
|
903
|
+
return currentStore
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
if (test.isAttemptToFix || test.isAtr || test.isEarlyFlakeDetection) {
|
|
907
|
+
context.arguments[6] = 0
|
|
908
|
+
}
|
|
909
|
+
return {
|
|
910
|
+
...test.currentStore,
|
|
911
|
+
[WEBDRIVERIO_JASMINE_FUNCTION_TYPE]: type,
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
/**
|
|
916
|
+
* Starts one WebdriverIO Jasmine test attempt under its suite span.
|
|
917
|
+
*
|
|
918
|
+
* @param {object} test
|
|
919
|
+
* @param {object|undefined} parentStore
|
|
920
|
+
* @returns {void}
|
|
921
|
+
*/
|
|
922
|
+
#startWebdriverioJasmineAttempt (test, parentStore) {
|
|
923
|
+
test.attemptStart = performance.now()
|
|
924
|
+
test.hasFinalHookFailure = false
|
|
925
|
+
const span = this.startTestSpan({
|
|
926
|
+
hasDynamicName: test.hasDynamicName,
|
|
927
|
+
isAttemptToFix: test.isAttemptToFix,
|
|
928
|
+
isDisabled: test.isDisabled,
|
|
929
|
+
isEfdRetry: test.isEarlyFlakeDetection && test.attempt > 0,
|
|
930
|
+
isModified: test.isModified,
|
|
931
|
+
isNew: test.isNew,
|
|
932
|
+
isParallel: true,
|
|
933
|
+
isQuarantined: test.isQuarantined,
|
|
934
|
+
testName: test.testName,
|
|
935
|
+
testSuiteAbsolutePath: test.testSuiteAbsolutePath,
|
|
936
|
+
title: test.title,
|
|
709
937
|
})
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
938
|
+
test.span = span
|
|
939
|
+
test.currentStore = {
|
|
940
|
+
...parentStore,
|
|
713
941
|
span,
|
|
714
|
-
|
|
715
|
-
}
|
|
942
|
+
[WEBDRIVERIO_JASMINE_TEST]: test,
|
|
943
|
+
}
|
|
716
944
|
this.activeTestSpan = span
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* Delays Jasmine's parent runner until every Datadog-managed spec execution has completed.
|
|
949
|
+
*
|
|
950
|
+
* @param {object} context
|
|
951
|
+
* @returns {void}
|
|
952
|
+
*/
|
|
953
|
+
#configureWebdriverioJasmineLifecycle (context) {
|
|
954
|
+
const isLegacyJasmine = Boolean(context.self?.queueableFn)
|
|
955
|
+
const spec = isLegacyJasmine ? context.self : context.arguments?.[0]
|
|
956
|
+
const onComplete = context.arguments?.[1]
|
|
957
|
+
const originalTestFunction = spec?.queueableFn?.fn
|
|
958
|
+
if (!spec?.id || typeof onComplete !== 'function' || typeof originalTestFunction !== 'function') {
|
|
959
|
+
return
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
if (this.libraryConfig?.isTestManagementTestsEnabled) {
|
|
963
|
+
this.#startWebdriverioJasmineTest(spec.result)
|
|
964
|
+
const test = this._webdriverioJasmineState?.tests.get(spec.id)
|
|
965
|
+
if (test?.isDisabled && !test.isAttemptToFix) {
|
|
966
|
+
spec.pend('Skipped by Datadog Test Management')
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
const executionArguments = [...context.arguments]
|
|
971
|
+
context.arguments[1] = (...completeArguments) => {
|
|
972
|
+
const test = this._webdriverioJasmineState?.tests.get(spec.id)
|
|
973
|
+
if (!test?.willRetry) {
|
|
974
|
+
return onComplete(...completeArguments)
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
test.willRetry = false
|
|
978
|
+
const retry = () => {
|
|
979
|
+
try {
|
|
980
|
+
this.#startNextWebdriverioJasmineAttempt(test)
|
|
981
|
+
spec.reset()
|
|
982
|
+
spec.queueableFn.fn = originalTestFunction
|
|
983
|
+
if (isLegacyJasmine) {
|
|
984
|
+
spec.execute(
|
|
985
|
+
executionArguments[0],
|
|
986
|
+
onComplete,
|
|
987
|
+
executionArguments[2],
|
|
988
|
+
executionArguments[3]
|
|
989
|
+
)
|
|
990
|
+
} else {
|
|
991
|
+
context.self._executeSpec(spec, onComplete)
|
|
992
|
+
}
|
|
993
|
+
} catch (error) {
|
|
994
|
+
log.error('WebdriverIO Jasmine retry error', error)
|
|
995
|
+
onComplete(...completeArguments)
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
const retryWait = test.retryWait
|
|
999
|
+
test.retryWait = undefined
|
|
1000
|
+
if (retryWait?.then) {
|
|
1001
|
+
retryWait.then(retry, retry)
|
|
1002
|
+
} else {
|
|
1003
|
+
retry()
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* Selects the runner-visible status and whether Jasmine should execute the full spec again.
|
|
1010
|
+
*
|
|
1011
|
+
* @param {object|undefined} spec
|
|
1012
|
+
* @param {string|undefined} status
|
|
1013
|
+
* @returns {string|undefined}
|
|
1014
|
+
*/
|
|
1015
|
+
#prepareWebdriverioJasmineAttempt (spec, status) {
|
|
1016
|
+
const state = this._webdriverioJasmineState
|
|
1017
|
+
const completedStatus = state?.completedTestStatuses.get(spec?.id)
|
|
1018
|
+
if (completedStatus) {
|
|
1019
|
+
return completedStatus
|
|
1020
|
+
}
|
|
717
1021
|
|
|
718
|
-
|
|
1022
|
+
const test = state?.tests.get(spec?.id)
|
|
1023
|
+
if (!test || !status) {
|
|
1024
|
+
return status
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
const testStatus = getJasmineStatus(status)
|
|
1028
|
+
if (
|
|
1029
|
+
testStatus !== 'skip' &&
|
|
1030
|
+
test.isEarlyFlakeDetection &&
|
|
1031
|
+
test.attempt === 0
|
|
1032
|
+
) {
|
|
1033
|
+
test.retryCount = getEfdRetryCountForDuration(
|
|
1034
|
+
performance.now() - test.attemptStart,
|
|
1035
|
+
this.libraryConfig.earlyFlakeDetectionRetryPolicy
|
|
1036
|
+
)
|
|
1037
|
+
if (test.retryCount === 0) {
|
|
1038
|
+
test.earlyFlakeAbortReason = 'slow'
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
const hasManagedRetry = testStatus !== 'skip' && test.attempt < test.retryCount
|
|
1043
|
+
test.willRetry = hasManagedRetry && (
|
|
1044
|
+
test.isAttemptToFix ||
|
|
1045
|
+
test.isEarlyFlakeDetection ||
|
|
1046
|
+
(test.isAtr && testStatus === 'fail' && !test.hasFinalHookFailure)
|
|
1047
|
+
)
|
|
1048
|
+
|
|
1049
|
+
let runnerStatus = status
|
|
1050
|
+
const recoveredEarlyFlakeDetection = test.isEarlyFlakeDetection &&
|
|
1051
|
+
!test.hasFinalHookFailure && test.statuses.includes('pass')
|
|
1052
|
+
if (
|
|
1053
|
+
testStatus === 'fail' &&
|
|
1054
|
+
(test.willRetry || (test.isQuarantined && !test.isAttemptToFix) || recoveredEarlyFlakeDetection)
|
|
1055
|
+
) {
|
|
1056
|
+
runnerStatus = 'passed'
|
|
1057
|
+
} else if (testStatus !== 'skip' && !test.willRetry && test.isAttemptToFix) {
|
|
1058
|
+
runnerStatus = test.statuses.every(previousStatus => previousStatus === 'pass') && testStatus === 'pass'
|
|
1059
|
+
? 'passed'
|
|
1060
|
+
: 'failed'
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
if (runnerStatus !== status) {
|
|
1064
|
+
test.reportedStatus = status
|
|
1065
|
+
}
|
|
1066
|
+
if (!test.willRetry) {
|
|
1067
|
+
test.runnerStatus = runnerStatus
|
|
1068
|
+
}
|
|
1069
|
+
return runnerStatus
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
/**
|
|
1073
|
+
* Finishes one Datadog-managed Jasmine attempt before the full spec is executed again.
|
|
1074
|
+
*
|
|
1075
|
+
* @param {object} test
|
|
1076
|
+
* @param {WebdriverioJasmineResult} result
|
|
1077
|
+
* @returns {void}
|
|
1078
|
+
*/
|
|
1079
|
+
#finishWebdriverioJasmineRetry (test, result) {
|
|
1080
|
+
const status = getJasmineStatus(test.reportedStatus || result.status)
|
|
1081
|
+
const error = getJasmineError(result)
|
|
1082
|
+
test.statuses.push(status)
|
|
1083
|
+
|
|
1084
|
+
if (error) {
|
|
1085
|
+
const isFirstFailure = !test.hasFailedAttempt
|
|
1086
|
+
test.hasFailedAttempt = true
|
|
1087
|
+
const promises = {}
|
|
1088
|
+
testRetryCh.publish({
|
|
1089
|
+
err: error,
|
|
1090
|
+
isAttemptToFixRetry: test.isAttemptToFix && test.attempt > 0,
|
|
1091
|
+
isAtrRetry: test.isAtr && test.attempt > 0,
|
|
1092
|
+
isEfdRetry: test.isEarlyFlakeDetection && test.attempt > 0,
|
|
1093
|
+
isFirstAttempt: test.attempt === 0,
|
|
1094
|
+
isFirstFailure,
|
|
1095
|
+
promises,
|
|
1096
|
+
test,
|
|
1097
|
+
willBeRetried: true,
|
|
1098
|
+
...test.currentStore,
|
|
1099
|
+
})
|
|
1100
|
+
test.retryWait = getFailedTestReplayPromise(promises)
|
|
1101
|
+
} else {
|
|
1102
|
+
testFinishCh.publish({
|
|
1103
|
+
hasBeenRetried: test.isAtr && test.attempt > 0,
|
|
1104
|
+
isAttemptToFixRetry: test.isAttemptToFix && test.attempt > 0,
|
|
1105
|
+
isAtrRetry: false,
|
|
1106
|
+
isLastRetry: false,
|
|
1107
|
+
status,
|
|
1108
|
+
...test.currentStore,
|
|
1109
|
+
})
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
/**
|
|
1114
|
+
* Advances a Jasmine test and starts its next attempt span.
|
|
1115
|
+
*
|
|
1116
|
+
* @param {object} test
|
|
1117
|
+
* @returns {void}
|
|
1118
|
+
*/
|
|
1119
|
+
#startNextWebdriverioJasmineAttempt (test) {
|
|
1120
|
+
test.attempt++
|
|
1121
|
+
test.reportedStatus = undefined
|
|
1122
|
+
this.#startWebdriverioJasmineAttempt(test, test.currentStore)
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
/**
|
|
1126
|
+
* Finishes an intermediate native WebdriverIO retry and starts its next span.
|
|
1127
|
+
*
|
|
1128
|
+
* @param {object} test
|
|
1129
|
+
* @param {Error|undefined} error
|
|
1130
|
+
* @returns {Promise<void>|undefined}
|
|
1131
|
+
*/
|
|
1132
|
+
#retryWebdriverioJasmineTest (test, error) {
|
|
1133
|
+
test.statuses.push('fail')
|
|
1134
|
+
const isFirstFailure = !test.hasFailedAttempt
|
|
1135
|
+
test.hasFailedAttempt = true
|
|
1136
|
+
const promises = {}
|
|
1137
|
+
testRetryCh.publish({
|
|
1138
|
+
err: error,
|
|
1139
|
+
isAttemptToFixRetry: false,
|
|
1140
|
+
isAtrRetry: false,
|
|
1141
|
+
isEfdRetry: false,
|
|
1142
|
+
isFirstAttempt: test.attempt === 0,
|
|
1143
|
+
isFirstFailure,
|
|
1144
|
+
promises,
|
|
1145
|
+
test,
|
|
1146
|
+
willBeRetried: true,
|
|
1147
|
+
...test.currentStore,
|
|
1148
|
+
})
|
|
1149
|
+
|
|
1150
|
+
const retryWait = getFailedTestReplayPromise(promises)
|
|
1151
|
+
if (retryWait?.then) {
|
|
1152
|
+
const startNextAttempt = () => this.#startNextWebdriverioJasmineAttempt(test)
|
|
1153
|
+
return retryWait.then(startNextAttempt, startNextAttempt)
|
|
1154
|
+
}
|
|
1155
|
+
this.#startNextWebdriverioJasmineAttempt(test)
|
|
719
1156
|
}
|
|
720
1157
|
|
|
721
1158
|
/**
|
|
@@ -723,7 +1160,7 @@ class MochaPlugin extends CiPlugin {
|
|
|
723
1160
|
*
|
|
724
1161
|
* @param {WebdriverioJasmineResult|undefined} result
|
|
725
1162
|
* @param {string[]} [specs]
|
|
726
|
-
* @returns {void}
|
|
1163
|
+
* @returns {Promise<void>|undefined}
|
|
727
1164
|
*/
|
|
728
1165
|
#finishWebdriverioJasmineTest (result, specs) {
|
|
729
1166
|
const state = this._webdriverioJasmineState
|
|
@@ -737,20 +1174,86 @@ class MochaPlugin extends CiPlugin {
|
|
|
737
1174
|
return
|
|
738
1175
|
}
|
|
739
1176
|
|
|
740
|
-
|
|
1177
|
+
if (state.currentResult?.id === result.id) {
|
|
1178
|
+
state.currentResult = undefined
|
|
1179
|
+
}
|
|
1180
|
+
if (test.willRetry) {
|
|
1181
|
+
this.#finishWebdriverioJasmineRetry(test, result)
|
|
1182
|
+
return
|
|
1183
|
+
}
|
|
1184
|
+
if (test._ddShouldWaitForHitProbe) {
|
|
1185
|
+
delete test._ddShouldWaitForHitProbe
|
|
1186
|
+
state.pendingTestFinishes++
|
|
1187
|
+
const finishTest = () => {
|
|
1188
|
+
this.#completeWebdriverioJasmineTest(test, result)
|
|
1189
|
+
state.pendingTestFinishes--
|
|
1190
|
+
if (state.pendingTestFinishes === 0) {
|
|
1191
|
+
const callbacks = state.pendingTestFinishCallbacks
|
|
1192
|
+
state.pendingTestFinishCallbacks = []
|
|
1193
|
+
for (const callback of callbacks) {
|
|
1194
|
+
callback()
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
return this.waitForDiBreakpointHits().then(finishTest, finishTest)
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
this.#completeWebdriverioJasmineTest(test, result)
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
/**
|
|
1205
|
+
* Finalizes the last reported attempt for one WebdriverIO Jasmine test.
|
|
1206
|
+
*
|
|
1207
|
+
* @param {object} test
|
|
1208
|
+
* @param {WebdriverioJasmineResult} result
|
|
1209
|
+
* @returns {void}
|
|
1210
|
+
*/
|
|
1211
|
+
#completeWebdriverioJasmineTest (test, result) {
|
|
1212
|
+
const state = this._webdriverioJasmineState
|
|
1213
|
+
|
|
1214
|
+
const status = getJasmineStatus(test.reportedStatus || result.status)
|
|
741
1215
|
const error = getJasmineError(result)
|
|
742
1216
|
if (error) {
|
|
743
1217
|
test.span.setTag('error', error)
|
|
744
1218
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
1219
|
+
if (!test.isEarlyFlakeDetection || status !== 'skip') {
|
|
1220
|
+
test.statuses.push(status)
|
|
1221
|
+
}
|
|
1222
|
+
const hasFailedAllRetries = test.attempt > 0 &&
|
|
1223
|
+
(test.isAttemptToFix || test.isAtr || test.isEarlyFlakeDetection) &&
|
|
1224
|
+
test.statuses.every(testStatus => testStatus === 'fail')
|
|
1225
|
+
const isSkipped = status === 'skip'
|
|
1226
|
+
const attemptToFixPassed = !isSkipped && test.isAttemptToFix &&
|
|
1227
|
+
test.statuses.every(testStatus => testStatus === 'pass')
|
|
1228
|
+
const attemptToFixFailed = !isSkipped && test.isAttemptToFix && !attemptToFixPassed
|
|
1229
|
+
let finalStatus = status
|
|
1230
|
+
if (isSkipped || (!test.isAttemptToFix && (test.isDisabled || test.isQuarantined))) {
|
|
1231
|
+
finalStatus = 'skip'
|
|
1232
|
+
} else if (test.isAttemptToFix) {
|
|
1233
|
+
finalStatus = attemptToFixPassed ? 'pass' : 'fail'
|
|
1234
|
+
} else if (test.isEarlyFlakeDetection && status !== 'skip' && !test.hasFinalHookFailure) {
|
|
1235
|
+
finalStatus = test.statuses.includes('pass') ? 'pass' : 'fail'
|
|
749
1236
|
}
|
|
1237
|
+
testFinishCh.publish({
|
|
1238
|
+
attemptToFixFailed,
|
|
1239
|
+
attemptToFixPassed,
|
|
1240
|
+
earlyFlakeAbortReason: test.earlyFlakeAbortReason,
|
|
1241
|
+
finalStatus,
|
|
1242
|
+
hasBeenRetried: !test.isAttemptToFix && !test.isEarlyFlakeDetection && test.attempt > 0,
|
|
1243
|
+
hasFailedAllRetries,
|
|
1244
|
+
isAttemptToFixRetry: test.isAttemptToFix && test.attempt > 0,
|
|
1245
|
+
isAtrRetry: test.isAtr && test.attempt > 0,
|
|
1246
|
+
isLastRetry: true,
|
|
1247
|
+
status,
|
|
1248
|
+
...test.currentStore,
|
|
1249
|
+
})
|
|
1250
|
+
state.completedTestStatuses.set(result.id, test.runnerStatus || result.status)
|
|
1251
|
+
state.tests.delete(result.id)
|
|
750
1252
|
|
|
1253
|
+
const suiteStatus = test.isQuarantined && !test.isAttemptToFix ? 'pass' : finalStatus
|
|
751
1254
|
const previousStatus = state.suiteStatuses.get(test.testSuiteAbsolutePath)
|
|
752
|
-
if (
|
|
753
|
-
state.suiteStatuses.set(test.testSuiteAbsolutePath,
|
|
1255
|
+
if (suiteStatus === 'fail' || !previousStatus || previousStatus === 'skip') {
|
|
1256
|
+
state.suiteStatuses.set(test.testSuiteAbsolutePath, suiteStatus)
|
|
754
1257
|
}
|
|
755
1258
|
}
|
|
756
1259
|
|
|
@@ -765,28 +1268,28 @@ class MochaPlugin extends CiPlugin {
|
|
|
765
1268
|
*/
|
|
766
1269
|
#finishWebdriverioJasmineWorker (context) {
|
|
767
1270
|
const state = this._webdriverioJasmineState
|
|
768
|
-
const
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
const
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
1271
|
+
const reportWorker = onDone => {
|
|
1272
|
+
const results = []
|
|
1273
|
+
const reportedFiles = new Set()
|
|
1274
|
+
for (const [file, status] of state.suiteStatuses) {
|
|
1275
|
+
const error = state.suiteErrors.get(file)
|
|
1276
|
+
const result = { file, status }
|
|
1277
|
+
if (error) {
|
|
1278
|
+
result.error = {
|
|
1279
|
+
message: error.message,
|
|
1280
|
+
stack: error.stack,
|
|
1281
|
+
}
|
|
777
1282
|
}
|
|
1283
|
+
results.push(result)
|
|
1284
|
+
reportedFiles.add(file)
|
|
778
1285
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
if (!reportedFiles.has(file)) {
|
|
785
|
-
results.push({ file, status: 'skip' })
|
|
1286
|
+
for (const spec of state.specs) {
|
|
1287
|
+
const file = normalizeJasmineFile(spec)
|
|
1288
|
+
if (!reportedFiles.has(file)) {
|
|
1289
|
+
results.push({ file, status: 'skip' })
|
|
1290
|
+
}
|
|
786
1291
|
}
|
|
787
|
-
}
|
|
788
1292
|
|
|
789
|
-
const waitForWorker = onDone => {
|
|
790
1293
|
sendWebdriverioWorkerMessage({
|
|
791
1294
|
origin: 'datadog',
|
|
792
1295
|
name: SUITE_FINISH,
|
|
@@ -797,6 +1300,14 @@ class MochaPlugin extends CiPlugin {
|
|
|
797
1300
|
}
|
|
798
1301
|
}, () => workerFinishCh.publish({ onDone }))
|
|
799
1302
|
}
|
|
1303
|
+
|
|
1304
|
+
const waitForWorker = onDone => {
|
|
1305
|
+
if (state?.pendingTestFinishes) {
|
|
1306
|
+
state.pendingTestFinishCallbacks.push(() => reportWorker(onDone))
|
|
1307
|
+
} else {
|
|
1308
|
+
reportWorker(onDone)
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
800
1311
|
context.resolveCallback = waitForWorker
|
|
801
1312
|
context.rejectCallback = waitForWorker
|
|
802
1313
|
}
|