dd-trace 5.35.0 → 5.37.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 +2 -1
- package/index.d.ts +8 -7
- package/loader-hook.mjs +0 -4
- package/package.json +15 -14
- package/packages/datadog-core/index.js +1 -1
- package/packages/datadog-core/src/storage.js +76 -31
- package/packages/datadog-instrumentations/src/cucumber.js +54 -1
- package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -2
- package/packages/datadog-instrumentations/src/helpers/register.js +2 -2
- package/packages/datadog-instrumentations/src/jest.js +105 -11
- package/packages/datadog-instrumentations/src/mocha/main.js +46 -4
- package/packages/datadog-instrumentations/src/mocha/utils.js +35 -2
- package/packages/datadog-instrumentations/src/mocha/worker.js +7 -0
- package/packages/datadog-instrumentations/src/mysql2.js +3 -3
- package/packages/datadog-instrumentations/src/openai.js +8 -0
- package/packages/datadog-instrumentations/src/playwright.js +70 -22
- package/packages/datadog-instrumentations/src/vitest.js +60 -6
- package/packages/datadog-plugin-aerospike/src/index.js +1 -1
- package/packages/datadog-plugin-apollo/src/gateway/fetch.js +1 -1
- package/packages/datadog-plugin-apollo/src/gateway/index.js +1 -1
- package/packages/datadog-plugin-apollo/src/gateway/request.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/base.js +3 -3
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +4 -4
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +2 -2
- package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
- package/packages/datadog-plugin-couchbase/src/index.js +2 -2
- package/packages/datadog-plugin-cucumber/src/index.js +31 -14
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +72 -7
- package/packages/datadog-plugin-cypress/src/support.js +36 -29
- package/packages/datadog-plugin-dd-trace-api/src/index.js +1 -3
- package/packages/datadog-plugin-graphql/src/utils.js +8 -1
- package/packages/datadog-plugin-grpc/src/client.js +1 -1
- package/packages/datadog-plugin-grpc/src/server.js +1 -1
- package/packages/datadog-plugin-hapi/src/index.js +1 -1
- package/packages/datadog-plugin-http/src/client.js +1 -1
- package/packages/datadog-plugin-http/src/server.js +1 -1
- package/packages/datadog-plugin-http2/src/client.js +3 -3
- package/packages/datadog-plugin-http2/src/server.js +1 -1
- package/packages/datadog-plugin-jest/src/index.js +17 -12
- package/packages/datadog-plugin-langchain/src/tracing.js +1 -1
- package/packages/datadog-plugin-mariadb/src/index.js +3 -3
- package/packages/datadog-plugin-mocha/src/index.js +35 -16
- package/packages/datadog-plugin-mongodb-core/src/index.js +28 -2
- package/packages/datadog-plugin-next/src/index.js +4 -4
- package/packages/datadog-plugin-openai/src/tracing.js +2 -3
- package/packages/datadog-plugin-playwright/src/index.js +35 -9
- package/packages/datadog-plugin-rhea/src/consumer.js +1 -1
- package/packages/datadog-plugin-router/src/index.js +2 -2
- package/packages/datadog-plugin-selenium/src/index.js +1 -1
- package/packages/datadog-plugin-vitest/src/index.js +36 -12
- package/packages/dd-trace/src/appsec/graphql.js +6 -6
- package/packages/dd-trace/src/appsec/iast/analyzers/code-injection-analyzer.js +3 -7
- package/packages/dd-trace/src/appsec/iast/analyzers/injection-analyzer.js +15 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +17 -30
- package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +2 -2
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +7 -11
- package/packages/dd-trace/src/appsec/iast/analyzers/stored-injection-analyzer.js +11 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/template-injection-analyzer.js +2 -6
- package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +24 -4
- package/packages/dd-trace/src/appsec/iast/context/context-plugin.js +2 -2
- package/packages/dd-trace/src/appsec/iast/iast-plugin.js +2 -2
- package/packages/dd-trace/src/appsec/iast/index.js +4 -2
- package/packages/dd-trace/src/appsec/iast/security-controls/index.js +187 -0
- package/packages/dd-trace/src/appsec/iast/security-controls/parser.js +96 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/constants.js +6 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +2 -2
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +8 -8
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugins/kafka.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +65 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-telemetry.js +14 -5
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +80 -2
- package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks-generator.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks.js +28 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -1
- package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +5 -0
- package/packages/dd-trace/src/appsec/iast/utils.js +24 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +8 -13
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +1 -0
- package/packages/dd-trace/src/appsec/index.js +4 -4
- package/packages/dd-trace/src/appsec/rasp/command_injection.js +5 -5
- package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +5 -5
- package/packages/dd-trace/src/appsec/rasp/lfi.js +3 -3
- package/packages/dd-trace/src/appsec/rasp/sql_injection.js +4 -4
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +3 -3
- package/packages/dd-trace/src/appsec/reporter.js +3 -3
- package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
- package/packages/dd-trace/src/appsec/waf/index.js +1 -1
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +2 -0
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +31 -56
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +20 -2
- package/packages/dd-trace/src/ci-visibility/quarantined-tests/get-quarantined-tests.js +62 -0
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +5 -2
- package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +3 -3
- package/packages/dd-trace/src/config.js +18 -3
- package/packages/dd-trace/src/data_streams_context.js +2 -2
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +14 -7
- package/packages/dd-trace/src/debugger/devtools_client/source-maps.js +50 -0
- package/packages/dd-trace/src/debugger/devtools_client/state.js +38 -10
- package/packages/dd-trace/src/exporters/common/agents.js +1 -1
- package/packages/dd-trace/src/exporters/common/request.js +3 -3
- package/packages/dd-trace/src/iitm.js +2 -2
- package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +1 -1
- package/packages/dd-trace/src/llmobs/tagger.js +12 -2
- package/packages/dd-trace/src/log/writer.js +3 -3
- package/packages/dd-trace/src/noop/span.js +1 -1
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +5 -4
- package/packages/dd-trace/src/opentracing/span.js +3 -3
- package/packages/dd-trace/src/plugin_manager.js +3 -1
- package/packages/dd-trace/src/plugins/apollo.js +1 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +51 -4
- package/packages/dd-trace/src/plugins/database.js +14 -4
- package/packages/dd-trace/src/plugins/log_plugin.js +1 -1
- package/packages/dd-trace/src/plugins/plugin.js +8 -8
- package/packages/dd-trace/src/plugins/tracing.js +3 -3
- package/packages/dd-trace/src/plugins/util/git.js +3 -3
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -3
- package/packages/dd-trace/src/plugins/util/test.js +10 -4
- package/packages/dd-trace/src/profiling/exporters/agent.js +3 -3
- package/packages/dd-trace/src/profiling/profilers/wall.js +1 -1
- package/packages/dd-trace/src/proxy.js +5 -1
- package/packages/dd-trace/src/ritm.js +2 -1
- package/packages/dd-trace/src/scope.js +5 -5
- package/packages/dd-trace/src/spanleak.js +0 -1
- package/packages/dd-trace/src/tracer.js +0 -14
- package/packages/memwatch/package.json +0 -9
|
@@ -43,6 +43,7 @@ const testErrCh = channel('ci:jest:test:err')
|
|
|
43
43
|
const skippableSuitesCh = channel('ci:jest:test-suite:skippable')
|
|
44
44
|
const libraryConfigurationCh = channel('ci:jest:library-configuration')
|
|
45
45
|
const knownTestsCh = channel('ci:jest:known-tests')
|
|
46
|
+
const quarantinedTestsCh = channel('ci:jest:quarantined-tests')
|
|
46
47
|
|
|
47
48
|
const itrSkippedSuitesCh = channel('ci:jest:itr:skipped-suites')
|
|
48
49
|
|
|
@@ -70,6 +71,8 @@ let earlyFlakeDetectionFaultyThreshold = 30
|
|
|
70
71
|
let isEarlyFlakeDetectionFaulty = false
|
|
71
72
|
let hasFilteredSkippableSuites = false
|
|
72
73
|
let isKnownTestsEnabled = false
|
|
74
|
+
let isQuarantinedTestsEnabled = false
|
|
75
|
+
let quarantinedTests = {}
|
|
73
76
|
|
|
74
77
|
const sessionAsyncResource = new AsyncResource('bound-anonymous-fn')
|
|
75
78
|
|
|
@@ -140,6 +143,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
140
143
|
this.flakyTestRetriesCount = this.testEnvironmentOptions._ddFlakyTestRetriesCount
|
|
141
144
|
this.isDiEnabled = this.testEnvironmentOptions._ddIsDiEnabled
|
|
142
145
|
this.isKnownTestsEnabled = this.testEnvironmentOptions._ddIsKnownTestsEnabled
|
|
146
|
+
this.isQuarantinedTestsEnabled = this.testEnvironmentOptions._ddIsQuarantinedTestsEnabled
|
|
143
147
|
|
|
144
148
|
if (this.isKnownTestsEnabled) {
|
|
145
149
|
try {
|
|
@@ -161,6 +165,18 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
161
165
|
this.global[RETRY_TIMES] = this.flakyTestRetriesCount
|
|
162
166
|
}
|
|
163
167
|
}
|
|
168
|
+
|
|
169
|
+
if (this.isQuarantinedTestsEnabled) {
|
|
170
|
+
try {
|
|
171
|
+
const hasQuarantinedTests = !!quarantinedTests.jest
|
|
172
|
+
this.quarantinedTestsForThisSuite = hasQuarantinedTests
|
|
173
|
+
? this.getQuarantinedTestsForSuite(quarantinedTests.jest.suites[this.testSuite].tests)
|
|
174
|
+
: this.getQuarantinedTestsForSuite(this.testEnvironmentOptions._ddQuarantinedTests)
|
|
175
|
+
} catch (e) {
|
|
176
|
+
log.error('Error parsing quarantined tests', e)
|
|
177
|
+
this.isQuarantinedTestsEnabled = false
|
|
178
|
+
}
|
|
179
|
+
}
|
|
164
180
|
}
|
|
165
181
|
|
|
166
182
|
getHasSnapshotTests () {
|
|
@@ -193,8 +209,25 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
193
209
|
return knownTestsForSuite
|
|
194
210
|
}
|
|
195
211
|
|
|
212
|
+
getQuarantinedTestsForSuite (quaratinedTests) {
|
|
213
|
+
if (this.quarantinedTestsForThisSuite) {
|
|
214
|
+
return this.quarantinedTestsForThisSuite
|
|
215
|
+
}
|
|
216
|
+
let quarantinedTestsForSuite = quaratinedTests
|
|
217
|
+
// If jest is using workers, quarantined tests are serialized to json.
|
|
218
|
+
// If jest runs in band, they are not.
|
|
219
|
+
if (typeof quarantinedTestsForSuite === 'string') {
|
|
220
|
+
quarantinedTestsForSuite = JSON.parse(quarantinedTestsForSuite)
|
|
221
|
+
}
|
|
222
|
+
return Object.entries(quarantinedTestsForSuite).reduce((acc, [testName, { properties }]) => {
|
|
223
|
+
if (properties?.quarantined) {
|
|
224
|
+
acc.push(testName)
|
|
225
|
+
}
|
|
226
|
+
return acc
|
|
227
|
+
}, [])
|
|
228
|
+
}
|
|
229
|
+
|
|
196
230
|
// Add the `add_test` event we don't have the test object yet, so
|
|
197
|
-
// we use its describe block to get the full name
|
|
198
231
|
getTestNameFromAddTestEvent (event, state) {
|
|
199
232
|
const describeSuffix = getJestTestName(state.currentDescribeBlock)
|
|
200
233
|
const fullTestName = describeSuffix ? `${describeSuffix} ${event.testName}` : event.testName
|
|
@@ -303,6 +336,12 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
303
336
|
}
|
|
304
337
|
}
|
|
305
338
|
}
|
|
339
|
+
let isQuarantined = false
|
|
340
|
+
|
|
341
|
+
if (this.isQuarantinedTestsEnabled) {
|
|
342
|
+
const testName = getJestTestName(event.test)
|
|
343
|
+
isQuarantined = this.quarantinedTestsForThisSuite?.includes(testName)
|
|
344
|
+
}
|
|
306
345
|
|
|
307
346
|
const promises = {}
|
|
308
347
|
const numRetries = this.global[RETRY_TIMES]
|
|
@@ -313,10 +352,11 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
313
352
|
const asyncResource = asyncResources.get(event.test)
|
|
314
353
|
|
|
315
354
|
if (status === 'fail') {
|
|
355
|
+
const shouldSetProbe = this.isDiEnabled && willBeRetried && numTestExecutions === 1
|
|
316
356
|
asyncResource.runInAsyncScope(() => {
|
|
317
357
|
testErrCh.publish({
|
|
318
358
|
error: formatJestError(event.test.errors[0]),
|
|
319
|
-
shouldSetProbe
|
|
359
|
+
shouldSetProbe,
|
|
320
360
|
promises
|
|
321
361
|
})
|
|
322
362
|
})
|
|
@@ -336,18 +376,13 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
|
|
|
336
376
|
testFinishCh.publish({
|
|
337
377
|
status,
|
|
338
378
|
testStartLine: getTestLineStart(event.test.asyncError, this.testSuite),
|
|
339
|
-
|
|
340
|
-
shouldRemoveProbe: this.isDiEnabled && !willBeRetried
|
|
379
|
+
isQuarantined
|
|
341
380
|
})
|
|
342
381
|
})
|
|
343
382
|
|
|
344
383
|
if (promises.isProbeReady) {
|
|
345
384
|
await promises.isProbeReady
|
|
346
385
|
}
|
|
347
|
-
|
|
348
|
-
if (promises.isProbeRemoved) {
|
|
349
|
-
await promises.isProbeRemoved
|
|
350
|
-
}
|
|
351
386
|
}
|
|
352
387
|
if (event.name === 'test_skip' || event.name === 'test_todo') {
|
|
353
388
|
const asyncResource = new AsyncResource('bound-anonymous-fn')
|
|
@@ -489,6 +524,7 @@ function cliWrapper (cli, jestVersion) {
|
|
|
489
524
|
earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
|
|
490
525
|
earlyFlakeDetectionFaultyThreshold = libraryConfig.earlyFlakeDetectionFaultyThreshold
|
|
491
526
|
isKnownTestsEnabled = libraryConfig.isKnownTestsEnabled
|
|
527
|
+
isQuarantinedTestsEnabled = libraryConfig.isQuarantinedTestsEnabled
|
|
492
528
|
}
|
|
493
529
|
} catch (err) {
|
|
494
530
|
log.error('Jest library configuration error', err)
|
|
@@ -536,6 +572,25 @@ function cliWrapper (cli, jestVersion) {
|
|
|
536
572
|
}
|
|
537
573
|
}
|
|
538
574
|
|
|
575
|
+
if (isQuarantinedTestsEnabled) {
|
|
576
|
+
const quarantinedTestsPromise = new Promise((resolve) => {
|
|
577
|
+
onDone = resolve
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
sessionAsyncResource.runInAsyncScope(() => {
|
|
581
|
+
quarantinedTestsCh.publish({ onDone })
|
|
582
|
+
})
|
|
583
|
+
|
|
584
|
+
try {
|
|
585
|
+
const { err, quarantinedTests: receivedQuarantinedTests } = await quarantinedTestsPromise
|
|
586
|
+
if (!err) {
|
|
587
|
+
quarantinedTests = receivedQuarantinedTests
|
|
588
|
+
}
|
|
589
|
+
} catch (err) {
|
|
590
|
+
log.error('Jest quarantined tests error', err)
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
539
594
|
const processArgv = process.argv.slice(2).join(' ')
|
|
540
595
|
sessionAsyncResource.runInAsyncScope(() => {
|
|
541
596
|
testSessionStartCh.publish({ command: `jest ${processArgv}`, frameworkVersion: jestVersion })
|
|
@@ -605,6 +660,7 @@ function cliWrapper (cli, jestVersion) {
|
|
|
605
660
|
error,
|
|
606
661
|
isEarlyFlakeDetectionEnabled,
|
|
607
662
|
isEarlyFlakeDetectionFaulty,
|
|
663
|
+
isQuarantinedTestsEnabled,
|
|
608
664
|
onDone
|
|
609
665
|
})
|
|
610
666
|
})
|
|
@@ -638,6 +694,37 @@ function cliWrapper (cli, jestVersion) {
|
|
|
638
694
|
}
|
|
639
695
|
}
|
|
640
696
|
|
|
697
|
+
if (isQuarantinedTestsEnabled) {
|
|
698
|
+
const failedTests = result
|
|
699
|
+
.results
|
|
700
|
+
.testResults.flatMap(({ testResults, testFilePath: testSuiteAbsolutePath }) => (
|
|
701
|
+
testResults.map(({ fullName: testName, status }) => ({ testName, testSuiteAbsolutePath, status }))
|
|
702
|
+
))
|
|
703
|
+
.filter(({ status }) => status === 'failed')
|
|
704
|
+
|
|
705
|
+
let numFailedQuarantinedTests = 0
|
|
706
|
+
|
|
707
|
+
for (const { testName, testSuiteAbsolutePath } of failedTests) {
|
|
708
|
+
const testSuite = getTestSuitePath(testSuiteAbsolutePath, result.globalConfig.rootDir)
|
|
709
|
+
const isQuarantined = quarantinedTests
|
|
710
|
+
?.jest
|
|
711
|
+
?.suites
|
|
712
|
+
?.[testSuite]
|
|
713
|
+
?.tests
|
|
714
|
+
?.[testName]
|
|
715
|
+
?.properties
|
|
716
|
+
?.quarantined
|
|
717
|
+
if (isQuarantined) {
|
|
718
|
+
numFailedQuarantinedTests++
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// If every test that failed was quarantined, we'll consider the suite passed
|
|
723
|
+
if (numFailedQuarantinedTests !== 0 && result.results.numFailedTests === numFailedQuarantinedTests) {
|
|
724
|
+
result.results.success = true
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
641
728
|
return result
|
|
642
729
|
})
|
|
643
730
|
|
|
@@ -829,6 +916,8 @@ addHook({
|
|
|
829
916
|
_ddFlakyTestRetriesCount,
|
|
830
917
|
_ddIsDiEnabled,
|
|
831
918
|
_ddIsKnownTestsEnabled,
|
|
919
|
+
_ddIsQuarantinedTestsEnabled,
|
|
920
|
+
_ddQuarantinedTests,
|
|
832
921
|
...restOfTestEnvironmentOptions
|
|
833
922
|
} = testEnvironmentOptions
|
|
834
923
|
|
|
@@ -940,8 +1029,9 @@ addHook({
|
|
|
940
1029
|
})
|
|
941
1030
|
|
|
942
1031
|
/*
|
|
943
|
-
* This hook does
|
|
1032
|
+
* This hook does three things:
|
|
944
1033
|
* - Pass known tests to the workers.
|
|
1034
|
+
* - Pass quarantined tests to the workers.
|
|
945
1035
|
* - Receive trace, coverage and logs payloads from the workers.
|
|
946
1036
|
*/
|
|
947
1037
|
addHook({
|
|
@@ -951,7 +1041,7 @@ addHook({
|
|
|
951
1041
|
}, (childProcessWorker) => {
|
|
952
1042
|
const ChildProcessWorker = childProcessWorker.default
|
|
953
1043
|
shimmer.wrap(ChildProcessWorker.prototype, 'send', send => function (request) {
|
|
954
|
-
if (!isKnownTestsEnabled) {
|
|
1044
|
+
if (!isKnownTestsEnabled && !isQuarantinedTestsEnabled) {
|
|
955
1045
|
return send.apply(this, arguments)
|
|
956
1046
|
}
|
|
957
1047
|
const [type] = request
|
|
@@ -971,11 +1061,15 @@ addHook({
|
|
|
971
1061
|
const [{ globalConfig, config, path: testSuiteAbsolutePath }] = args
|
|
972
1062
|
const testSuite = getTestSuitePath(testSuiteAbsolutePath, globalConfig.rootDir || process.cwd())
|
|
973
1063
|
const suiteKnownTests = knownTests.jest?.[testSuite] || []
|
|
1064
|
+
|
|
1065
|
+
const suiteQuarantinedTests = quarantinedTests.jest?.suites?.[testSuite]?.tests || {}
|
|
1066
|
+
|
|
974
1067
|
args[0].config = {
|
|
975
1068
|
...config,
|
|
976
1069
|
testEnvironmentOptions: {
|
|
977
1070
|
...config.testEnvironmentOptions,
|
|
978
|
-
_ddKnownTests: suiteKnownTests
|
|
1071
|
+
_ddKnownTests: suiteKnownTests,
|
|
1072
|
+
_ddQuarantinedTests: suiteQuarantinedTests
|
|
979
1073
|
}
|
|
980
1074
|
}
|
|
981
1075
|
}
|
|
@@ -27,6 +27,7 @@ const {
|
|
|
27
27
|
getOnPendingHandler,
|
|
28
28
|
testFileToSuiteAr,
|
|
29
29
|
newTests,
|
|
30
|
+
testsQuarantined,
|
|
30
31
|
getTestFullName,
|
|
31
32
|
getRunTestsWrapper
|
|
32
33
|
} = require('./utils')
|
|
@@ -61,6 +62,7 @@ const testSuiteCodeCoverageCh = channel('ci:mocha:test-suite:code-coverage')
|
|
|
61
62
|
const libraryConfigurationCh = channel('ci:mocha:library-configuration')
|
|
62
63
|
const knownTestsCh = channel('ci:mocha:known-tests')
|
|
63
64
|
const skippableSuitesCh = channel('ci:mocha:test-suite:skippable')
|
|
65
|
+
const quarantinedTestsCh = channel('ci:mocha:quarantined-tests')
|
|
64
66
|
const workerReportTraceCh = channel('ci:mocha:worker-report:trace')
|
|
65
67
|
const testSessionStartCh = channel('ci:mocha:session:start')
|
|
66
68
|
const testSessionFinishCh = channel('ci:mocha:session:finish')
|
|
@@ -135,6 +137,18 @@ function getOnEndHandler (isParallel) {
|
|
|
135
137
|
}
|
|
136
138
|
}
|
|
137
139
|
|
|
140
|
+
// We subtract the errors from quarantined tests from the total number of failures
|
|
141
|
+
if (config.isQuarantinedTestsEnabled) {
|
|
142
|
+
let numFailedQuarantinedTests = 0
|
|
143
|
+
for (const test of testsQuarantined) {
|
|
144
|
+
if (isTestFailed(test)) {
|
|
145
|
+
numFailedQuarantinedTests++
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
this.stats.failures -= numFailedQuarantinedTests
|
|
149
|
+
this.failures -= numFailedQuarantinedTests
|
|
150
|
+
}
|
|
151
|
+
|
|
138
152
|
if (status === 'fail') {
|
|
139
153
|
error = new Error(`Failed tests: ${this.failures}.`)
|
|
140
154
|
}
|
|
@@ -165,6 +179,7 @@ function getOnEndHandler (isParallel) {
|
|
|
165
179
|
error,
|
|
166
180
|
isEarlyFlakeDetectionEnabled: config.isEarlyFlakeDetectionEnabled,
|
|
167
181
|
isEarlyFlakeDetectionFaulty: config.isEarlyFlakeDetectionFaulty,
|
|
182
|
+
isQuarantinedTestsEnabled: config.isQuarantinedTestsEnabled,
|
|
168
183
|
isParallel
|
|
169
184
|
})
|
|
170
185
|
})
|
|
@@ -173,6 +188,22 @@ function getOnEndHandler (isParallel) {
|
|
|
173
188
|
function getExecutionConfiguration (runner, isParallel, onFinishRequest) {
|
|
174
189
|
const mochaRunAsyncResource = new AsyncResource('bound-anonymous-fn')
|
|
175
190
|
|
|
191
|
+
const onReceivedQuarantinedTests = ({ err, quarantinedTests: receivedQuarantinedTests }) => {
|
|
192
|
+
if (err) {
|
|
193
|
+
config.quarantinedTests = {}
|
|
194
|
+
config.isQuarantinedTestsEnabled = false
|
|
195
|
+
} else {
|
|
196
|
+
config.quarantinedTests = receivedQuarantinedTests
|
|
197
|
+
}
|
|
198
|
+
if (config.isSuitesSkippingEnabled) {
|
|
199
|
+
skippableSuitesCh.publish({
|
|
200
|
+
onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites)
|
|
201
|
+
})
|
|
202
|
+
} else {
|
|
203
|
+
onFinishRequest()
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
176
207
|
const onReceivedSkippableSuites = ({ err, skippableSuites, itrCorrelationId: responseItrCorrelationId }) => {
|
|
177
208
|
if (err) {
|
|
178
209
|
suitesToSkip = []
|
|
@@ -205,8 +236,11 @@ function getExecutionConfiguration (runner, isParallel, onFinishRequest) {
|
|
|
205
236
|
} else {
|
|
206
237
|
config.knownTests = knownTests
|
|
207
238
|
}
|
|
208
|
-
|
|
209
|
-
|
|
239
|
+
if (config.isQuarantinedTestsEnabled) {
|
|
240
|
+
quarantinedTestsCh.publish({
|
|
241
|
+
onDone: mochaRunAsyncResource.bind(onReceivedQuarantinedTests)
|
|
242
|
+
})
|
|
243
|
+
} else if (config.isSuitesSkippingEnabled) {
|
|
210
244
|
skippableSuitesCh.publish({
|
|
211
245
|
onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites)
|
|
212
246
|
})
|
|
@@ -224,15 +258,20 @@ function getExecutionConfiguration (runner, isParallel, onFinishRequest) {
|
|
|
224
258
|
config.earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
|
|
225
259
|
config.earlyFlakeDetectionFaultyThreshold = libraryConfig.earlyFlakeDetectionFaultyThreshold
|
|
226
260
|
config.isKnownTestsEnabled = libraryConfig.isKnownTestsEnabled
|
|
227
|
-
// ITR
|
|
261
|
+
// ITR, auto test retries and quarantine are not supported in parallel mode yet
|
|
228
262
|
config.isSuitesSkippingEnabled = !isParallel && libraryConfig.isSuitesSkippingEnabled
|
|
229
263
|
config.isFlakyTestRetriesEnabled = !isParallel && libraryConfig.isFlakyTestRetriesEnabled
|
|
230
264
|
config.flakyTestRetriesCount = !isParallel && libraryConfig.flakyTestRetriesCount
|
|
265
|
+
config.isQuarantinedTestsEnabled = !isParallel && libraryConfig.isQuarantinedTestsEnabled
|
|
231
266
|
|
|
232
267
|
if (config.isKnownTestsEnabled) {
|
|
233
268
|
knownTestsCh.publish({
|
|
234
269
|
onDone: mochaRunAsyncResource.bind(onReceivedKnownTests)
|
|
235
270
|
})
|
|
271
|
+
} else if (config.isQuarantinedTestsEnabled) {
|
|
272
|
+
quarantinedTestsCh.publish({
|
|
273
|
+
onDone: mochaRunAsyncResource.bind(onReceivedQuarantinedTests)
|
|
274
|
+
})
|
|
236
275
|
} else if (config.isSuitesSkippingEnabled) {
|
|
237
276
|
skippableSuitesCh.publish({
|
|
238
277
|
onDone: mochaRunAsyncResource.bind(onReceivedSkippableSuites)
|
|
@@ -357,7 +396,7 @@ addHook({
|
|
|
357
396
|
|
|
358
397
|
this.once('end', getOnEndHandler(false))
|
|
359
398
|
|
|
360
|
-
this.on('test', getOnTestHandler(true
|
|
399
|
+
this.on('test', getOnTestHandler(true))
|
|
361
400
|
|
|
362
401
|
this.on('test end', getOnTestEndHandler())
|
|
363
402
|
|
|
@@ -579,6 +618,7 @@ addHook({
|
|
|
579
618
|
|
|
580
619
|
const testPath = getTestSuitePath(testSuiteAbsolutePath, process.cwd())
|
|
581
620
|
const testSuiteKnownTests = config.knownTests.mocha?.[testPath] || []
|
|
621
|
+
const testSuiteQuarantinedTests = config.quarantinedTests?.modules?.mocha?.suites?.[testPath] || []
|
|
582
622
|
|
|
583
623
|
// We pass the known tests for the test file to the worker
|
|
584
624
|
const testFileResult = await run.apply(
|
|
@@ -589,6 +629,8 @@ addHook({
|
|
|
589
629
|
...workerArgs,
|
|
590
630
|
_ddEfdNumRetries: config.earlyFlakeDetectionNumRetries,
|
|
591
631
|
_ddIsEfdEnabled: config.isEarlyFlakeDetectionEnabled,
|
|
632
|
+
_ddIsQuarantinedEnabled: config.isQuarantinedTestsEnabled,
|
|
633
|
+
_ddQuarantinedTests: testSuiteQuarantinedTests,
|
|
592
634
|
_ddKnownTests: {
|
|
593
635
|
mocha: {
|
|
594
636
|
[testPath]: testSuiteKnownTests
|
|
@@ -26,6 +26,27 @@ const testToStartLine = new WeakMap()
|
|
|
26
26
|
const testFileToSuiteAr = new Map()
|
|
27
27
|
const wrappedFunctions = new WeakSet()
|
|
28
28
|
const newTests = {}
|
|
29
|
+
const testsQuarantined = new Set()
|
|
30
|
+
|
|
31
|
+
function isQuarantinedTest (test, testsToQuarantine) {
|
|
32
|
+
const testSuite = getTestSuitePath(test.file, process.cwd())
|
|
33
|
+
const testName = test.fullTitle()
|
|
34
|
+
|
|
35
|
+
const isQuarantined = (testsToQuarantine
|
|
36
|
+
.mocha
|
|
37
|
+
?.suites
|
|
38
|
+
?.[testSuite]
|
|
39
|
+
?.tests
|
|
40
|
+
?.[testName]
|
|
41
|
+
?.properties
|
|
42
|
+
?.quarantined) ?? false
|
|
43
|
+
|
|
44
|
+
if (isQuarantined) {
|
|
45
|
+
testsQuarantined.add(test)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return isQuarantined
|
|
49
|
+
}
|
|
29
50
|
|
|
30
51
|
function isNewTest (test, knownTests) {
|
|
31
52
|
const testSuite = getTestSuitePath(test.file, process.cwd())
|
|
@@ -171,7 +192,8 @@ function getOnTestHandler (isMain) {
|
|
|
171
192
|
file: testSuiteAbsolutePath,
|
|
172
193
|
title,
|
|
173
194
|
_ddIsNew: isNew,
|
|
174
|
-
_ddIsEfdRetry: isEfdRetry
|
|
195
|
+
_ddIsEfdRetry: isEfdRetry,
|
|
196
|
+
_ddIsQuarantined: isQuarantined
|
|
175
197
|
} = test
|
|
176
198
|
|
|
177
199
|
const testInfo = {
|
|
@@ -187,6 +209,7 @@ function getOnTestHandler (isMain) {
|
|
|
187
209
|
|
|
188
210
|
testInfo.isNew = isNew
|
|
189
211
|
testInfo.isEfdRetry = isEfdRetry
|
|
212
|
+
testInfo.isQuarantined = isQuarantined
|
|
190
213
|
// We want to store the result of the new tests
|
|
191
214
|
if (isNew) {
|
|
192
215
|
const testFullName = getTestFullName(test)
|
|
@@ -360,6 +383,15 @@ function getRunTestsWrapper (runTests, config) {
|
|
|
360
383
|
}
|
|
361
384
|
})
|
|
362
385
|
}
|
|
386
|
+
|
|
387
|
+
if (config.isQuarantinedTestsEnabled) {
|
|
388
|
+
suite.tests.forEach(test => {
|
|
389
|
+
if (isQuarantinedTest(test, config.quarantinedTests)) {
|
|
390
|
+
test._ddIsQuarantined = true
|
|
391
|
+
}
|
|
392
|
+
})
|
|
393
|
+
}
|
|
394
|
+
|
|
363
395
|
return runTests.apply(this, arguments)
|
|
364
396
|
}
|
|
365
397
|
}
|
|
@@ -384,5 +416,6 @@ module.exports = {
|
|
|
384
416
|
getOnPendingHandler,
|
|
385
417
|
testFileToSuiteAr,
|
|
386
418
|
getRunTestsWrapper,
|
|
387
|
-
newTests
|
|
419
|
+
newTests,
|
|
420
|
+
testsQuarantined
|
|
388
421
|
}
|
|
@@ -33,6 +33,13 @@ addHook({
|
|
|
33
33
|
delete this.options._ddIsEfdEnabled
|
|
34
34
|
delete this.options._ddKnownTests
|
|
35
35
|
delete this.options._ddEfdNumRetries
|
|
36
|
+
delete this.options._ddQuarantinedTests
|
|
37
|
+
}
|
|
38
|
+
if (this.options._ddIsQuarantinedEnabled) {
|
|
39
|
+
config.isQuarantinedEnabled = true
|
|
40
|
+
config.quarantinedTests = this.options._ddQuarantinedTests
|
|
41
|
+
delete this.options._ddIsQuarantinedEnabled
|
|
42
|
+
delete this.options._ddQuarantinedTests
|
|
36
43
|
}
|
|
37
44
|
return run.apply(this, arguments)
|
|
38
45
|
})
|
|
@@ -6,14 +6,14 @@ const {
|
|
|
6
6
|
AsyncResource
|
|
7
7
|
} = require('./helpers/instrument')
|
|
8
8
|
const shimmer = require('../../datadog-shimmer')
|
|
9
|
-
const
|
|
9
|
+
const satisfies = require('semifies')
|
|
10
10
|
|
|
11
11
|
function wrapConnection (Connection, version) {
|
|
12
12
|
const startCh = channel('apm:mysql2:query:start')
|
|
13
13
|
const finishCh = channel('apm:mysql2:query:finish')
|
|
14
14
|
const errorCh = channel('apm:mysql2:query:error')
|
|
15
15
|
const startOuterQueryCh = channel('datadog:mysql2:outerquery:start')
|
|
16
|
-
const shouldEmitEndAfterQueryAbort =
|
|
16
|
+
const shouldEmitEndAfterQueryAbort = satisfies(version, '>=1.3.3')
|
|
17
17
|
|
|
18
18
|
shimmer.wrap(Connection.prototype, 'addCommand', addCommand => function (cmd) {
|
|
19
19
|
if (!startCh.hasSubscribers) return addCommand.apply(this, arguments)
|
|
@@ -154,7 +154,7 @@ function wrapConnection (Connection, version) {
|
|
|
154
154
|
}
|
|
155
155
|
function wrapPool (Pool, version) {
|
|
156
156
|
const startOuterQueryCh = channel('datadog:mysql2:outerquery:start')
|
|
157
|
-
const shouldEmitEndAfterQueryAbort =
|
|
157
|
+
const shouldEmitEndAfterQueryAbort = satisfies(version, '>=1.3.3')
|
|
158
158
|
|
|
159
159
|
shimmer.wrap(Pool.prototype, 'query', query => function (sql, values, cb) {
|
|
160
160
|
if (!startOuterQueryCh.hasSubscribers) return query.apply(this, arguments)
|
|
@@ -97,6 +97,14 @@ const V4_PACKAGE_SHIMS = [
|
|
|
97
97
|
targetClass: 'Translations',
|
|
98
98
|
baseResource: 'audio.translations',
|
|
99
99
|
methods: ['create']
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
file: 'resources/chat/completions/completions.js',
|
|
103
|
+
targetClass: 'Completions',
|
|
104
|
+
baseResource: 'chat.completions',
|
|
105
|
+
methods: ['create'],
|
|
106
|
+
streamedResponse: true,
|
|
107
|
+
versions: ['>=4.85.0']
|
|
100
108
|
}
|
|
101
109
|
]
|
|
102
110
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const satisfies = require('semifies')
|
|
2
2
|
|
|
3
3
|
const { addHook, channel, AsyncResource } = require('./helpers/instrument')
|
|
4
4
|
const shimmer = require('../../datadog-shimmer')
|
|
@@ -13,6 +13,7 @@ const testSessionFinishCh = channel('ci:playwright:session:finish')
|
|
|
13
13
|
|
|
14
14
|
const libraryConfigurationCh = channel('ci:playwright:library-configuration')
|
|
15
15
|
const knownTestsCh = channel('ci:playwright:known-tests')
|
|
16
|
+
const quarantinedTestsCh = channel('ci:playwright:quarantined-tests')
|
|
16
17
|
|
|
17
18
|
const testSuiteStartCh = channel('ci:playwright:test-suite:start')
|
|
18
19
|
const testSuiteFinishCh = channel('ci:playwright:test-suite:finish')
|
|
@@ -41,8 +42,24 @@ let earlyFlakeDetectionNumRetries = 0
|
|
|
41
42
|
let isFlakyTestRetriesEnabled = false
|
|
42
43
|
let flakyTestRetriesCount = 0
|
|
43
44
|
let knownTests = {}
|
|
45
|
+
let isQuarantinedTestsEnabled = false
|
|
46
|
+
let quarantinedTests = {}
|
|
44
47
|
let rootDir = ''
|
|
45
|
-
const
|
|
48
|
+
const MINIMUM_SUPPORTED_VERSION_RANGE_EFD = '>=1.38.0'
|
|
49
|
+
|
|
50
|
+
function isQuarantineTest (test) {
|
|
51
|
+
const testName = getTestFullname(test)
|
|
52
|
+
const testSuite = getTestSuitePath(test._requireFile, rootDir)
|
|
53
|
+
|
|
54
|
+
return quarantinedTests
|
|
55
|
+
?.playwright
|
|
56
|
+
?.suites
|
|
57
|
+
?.[testSuite]
|
|
58
|
+
?.tests
|
|
59
|
+
?.[testName]
|
|
60
|
+
?.properties
|
|
61
|
+
?.quarantined
|
|
62
|
+
}
|
|
46
63
|
|
|
47
64
|
function isNewTest (test) {
|
|
48
65
|
const testSuite = getTestSuitePath(test._requireFile, rootDir)
|
|
@@ -296,6 +313,7 @@ function testEndHandler (test, annotations, testStatus, error, isTimeout) {
|
|
|
296
313
|
error,
|
|
297
314
|
extraTags: annotationTags,
|
|
298
315
|
isNew: test._ddIsNew,
|
|
316
|
+
isQuarantined: test._ddIsQuarantined,
|
|
299
317
|
isEfdRetry: test._ddIsEfdRetry
|
|
300
318
|
})
|
|
301
319
|
})
|
|
@@ -424,14 +442,16 @@ function runnerHook (runnerExport, playwrightVersion) {
|
|
|
424
442
|
earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
|
|
425
443
|
isFlakyTestRetriesEnabled = libraryConfig.isFlakyTestRetriesEnabled
|
|
426
444
|
flakyTestRetriesCount = libraryConfig.flakyTestRetriesCount
|
|
445
|
+
isQuarantinedTestsEnabled = libraryConfig.isQuarantinedTestsEnabled
|
|
427
446
|
}
|
|
428
447
|
} catch (e) {
|
|
429
448
|
isEarlyFlakeDetectionEnabled = false
|
|
430
449
|
isKnownTestsEnabled = false
|
|
450
|
+
isQuarantinedTestsEnabled = false
|
|
431
451
|
log.error('Playwright session start error', e)
|
|
432
452
|
}
|
|
433
453
|
|
|
434
|
-
if (isKnownTestsEnabled &&
|
|
454
|
+
if (isKnownTestsEnabled && satisfies(playwrightVersion, MINIMUM_SUPPORTED_VERSION_RANGE_EFD)) {
|
|
435
455
|
try {
|
|
436
456
|
const { err, knownTests: receivedKnownTests } = await getChannelPromise(knownTestsCh)
|
|
437
457
|
if (!err) {
|
|
@@ -447,6 +467,20 @@ function runnerHook (runnerExport, playwrightVersion) {
|
|
|
447
467
|
}
|
|
448
468
|
}
|
|
449
469
|
|
|
470
|
+
if (isQuarantinedTestsEnabled && satisfies(playwrightVersion, MINIMUM_SUPPORTED_VERSION_RANGE_EFD)) {
|
|
471
|
+
try {
|
|
472
|
+
const { err, quarantinedTests: receivedQuarantinedTests } = await getChannelPromise(quarantinedTestsCh)
|
|
473
|
+
if (!err) {
|
|
474
|
+
quarantinedTests = receivedQuarantinedTests
|
|
475
|
+
} else {
|
|
476
|
+
isQuarantinedTestsEnabled = false
|
|
477
|
+
}
|
|
478
|
+
} catch (err) {
|
|
479
|
+
isQuarantinedTestsEnabled = false
|
|
480
|
+
log.error('Playwright quarantined tests error', err)
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
450
484
|
const projects = getProjectsFromRunner(this)
|
|
451
485
|
|
|
452
486
|
if (isFlakyTestRetriesEnabled && flakyTestRetriesCount > 0) {
|
|
@@ -479,6 +513,7 @@ function runnerHook (runnerExport, playwrightVersion) {
|
|
|
479
513
|
testSessionFinishCh.publish({
|
|
480
514
|
status: STATUS_TO_TEST_STATUS[sessionStatus],
|
|
481
515
|
isEarlyFlakeDetectionEnabled,
|
|
516
|
+
isQuarantinedTestsEnabled,
|
|
482
517
|
onDone
|
|
483
518
|
})
|
|
484
519
|
})
|
|
@@ -487,6 +522,8 @@ function runnerHook (runnerExport, playwrightVersion) {
|
|
|
487
522
|
startedSuites = []
|
|
488
523
|
remainingTestsByFile = {}
|
|
489
524
|
|
|
525
|
+
// TODO: we can trick playwright into thinking the session passed by returning
|
|
526
|
+
// 'passed' here. We might be able to use this for both EFD and Quarantined tests.
|
|
490
527
|
return runAllTestsReturn
|
|
491
528
|
})
|
|
492
529
|
|
|
@@ -540,7 +577,7 @@ addHook({
|
|
|
540
577
|
addHook({
|
|
541
578
|
name: 'playwright',
|
|
542
579
|
file: 'lib/common/suiteUtils.js',
|
|
543
|
-
versions: [
|
|
580
|
+
versions: [MINIMUM_SUPPORTED_VERSION_RANGE_EFD]
|
|
544
581
|
}, suiteUtilsPackage => {
|
|
545
582
|
// We grab `applyRepeatEachIndex` to use it later
|
|
546
583
|
// `applyRepeatEachIndex` needs to be applied to a cloned suite
|
|
@@ -552,31 +589,42 @@ addHook({
|
|
|
552
589
|
addHook({
|
|
553
590
|
name: 'playwright',
|
|
554
591
|
file: 'lib/runner/loadUtils.js',
|
|
555
|
-
versions: [
|
|
592
|
+
versions: [MINIMUM_SUPPORTED_VERSION_RANGE_EFD]
|
|
556
593
|
}, (loadUtilsPackage) => {
|
|
557
594
|
const oldCreateRootSuite = loadUtilsPackage.createRootSuite
|
|
558
595
|
|
|
559
596
|
async function newCreateRootSuite () {
|
|
597
|
+
if (!isKnownTestsEnabled && !isQuarantinedTestsEnabled) {
|
|
598
|
+
return oldCreateRootSuite.apply(this, arguments)
|
|
599
|
+
}
|
|
560
600
|
const rootSuite = await oldCreateRootSuite.apply(this, arguments)
|
|
561
|
-
|
|
562
|
-
|
|
601
|
+
|
|
602
|
+
const allTests = rootSuite.allTests()
|
|
603
|
+
|
|
604
|
+
if (isQuarantinedTestsEnabled) {
|
|
605
|
+
const testsToBeIgnored = allTests.filter(isQuarantineTest)
|
|
606
|
+
testsToBeIgnored.forEach(test => {
|
|
607
|
+
test._ddIsQuarantined = true
|
|
608
|
+
test.expectedStatus = 'skipped'
|
|
609
|
+
})
|
|
563
610
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
.filter(isNewTest)
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
611
|
+
|
|
612
|
+
if (isKnownTestsEnabled) {
|
|
613
|
+
const newTests = allTests.filter(isNewTest)
|
|
614
|
+
|
|
615
|
+
newTests.forEach(newTest => {
|
|
616
|
+
newTest._ddIsNew = true
|
|
617
|
+
if (isEarlyFlakeDetectionEnabled && newTest.expectedStatus !== 'skipped') {
|
|
618
|
+
const fileSuite = getSuiteType(newTest, 'file')
|
|
619
|
+
const projectSuite = getSuiteType(newTest, 'project')
|
|
620
|
+
for (let repeatEachIndex = 0; repeatEachIndex < earlyFlakeDetectionNumRetries; repeatEachIndex++) {
|
|
621
|
+
const copyFileSuite = deepCloneSuite(fileSuite, isNewTest)
|
|
622
|
+
applyRepeatEachIndex(projectSuite._fullProject, copyFileSuite, repeatEachIndex + 1)
|
|
623
|
+
projectSuite._addSuite(copyFileSuite)
|
|
624
|
+
}
|
|
577
625
|
}
|
|
578
|
-
}
|
|
579
|
-
}
|
|
626
|
+
})
|
|
627
|
+
}
|
|
580
628
|
|
|
581
629
|
return rootSuite
|
|
582
630
|
}
|