dd-trace 5.102.0 → 5.104.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.
Files changed (201) hide show
  1. package/ext/exporters.js +1 -0
  2. package/index.d.ts +25 -3
  3. package/package.json +15 -13
  4. package/packages/datadog-esbuild/src/utils.js +2 -2
  5. package/packages/datadog-instrumentations/src/ai.js +1 -1
  6. package/packages/datadog-instrumentations/src/aws-sdk.js +2 -2
  7. package/packages/datadog-instrumentations/src/cassandra-driver.js +5 -2
  8. package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +32 -15
  9. package/packages/datadog-instrumentations/src/couchbase.js +69 -220
  10. package/packages/datadog-instrumentations/src/cucumber.js +104 -31
  11. package/packages/datadog-instrumentations/src/elasticsearch.js +4 -4
  12. package/packages/datadog-instrumentations/src/electron/preload.js +42 -0
  13. package/packages/datadog-instrumentations/src/electron.js +240 -0
  14. package/packages/datadog-instrumentations/src/fetch.js +5 -5
  15. package/packages/datadog-instrumentations/src/graphql.js +13 -17
  16. package/packages/datadog-instrumentations/src/grpc/client.js +48 -32
  17. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +2 -2
  18. package/packages/datadog-instrumentations/src/helpers/hook.js +4 -1
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  20. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -2
  21. package/packages/datadog-instrumentations/src/helpers/kafka.js +58 -0
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +3 -2
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +19 -5
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +14 -13
  25. package/packages/datadog-instrumentations/src/http/client.js +2 -2
  26. package/packages/datadog-instrumentations/src/ioredis.js +18 -14
  27. package/packages/datadog-instrumentations/src/jest.js +382 -84
  28. package/packages/datadog-instrumentations/src/kafkajs.js +184 -174
  29. package/packages/datadog-instrumentations/src/mariadb.js +1 -1
  30. package/packages/datadog-instrumentations/src/memcached.js +2 -1
  31. package/packages/datadog-instrumentations/src/mocha/main.js +309 -56
  32. package/packages/datadog-instrumentations/src/mocha/utils.js +48 -8
  33. package/packages/datadog-instrumentations/src/mongodb-core.js +34 -9
  34. package/packages/datadog-instrumentations/src/mongoose.js +10 -12
  35. package/packages/datadog-instrumentations/src/mysql.js +2 -2
  36. package/packages/datadog-instrumentations/src/mysql2.js +1 -1
  37. package/packages/datadog-instrumentations/src/pg.js +25 -11
  38. package/packages/datadog-instrumentations/src/playwright.js +449 -60
  39. package/packages/datadog-instrumentations/src/redis.js +19 -10
  40. package/packages/datadog-instrumentations/src/router.js +4 -2
  41. package/packages/datadog-instrumentations/src/vitest.js +246 -149
  42. package/packages/datadog-plugin-apollo/src/gateway/request.js +1 -21
  43. package/packages/datadog-plugin-aws-sdk/src/base.js +18 -24
  44. package/packages/datadog-plugin-aws-sdk/src/services/cloudwatchlogs.js +1 -1
  45. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -1
  46. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -1
  47. package/packages/datadog-plugin-aws-sdk/src/services/lambda.js +1 -1
  48. package/packages/datadog-plugin-aws-sdk/src/services/redshift.js +1 -1
  49. package/packages/datadog-plugin-aws-sdk/src/services/s3.js +1 -1
  50. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +2 -2
  51. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -1
  52. package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +1 -1
  53. package/packages/datadog-plugin-couchbase/src/index.js +58 -52
  54. package/packages/datadog-plugin-cucumber/src/index.js +1 -0
  55. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +239 -40
  56. package/packages/datadog-plugin-cypress/src/support.js +13 -1
  57. package/packages/datadog-plugin-elasticsearch/src/index.js +28 -8
  58. package/packages/datadog-plugin-electron/src/index.js +17 -0
  59. package/packages/datadog-plugin-electron/src/ipc.js +143 -0
  60. package/packages/datadog-plugin-electron/src/net.js +82 -0
  61. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +27 -18
  62. package/packages/datadog-plugin-graphql/src/execute.js +6 -28
  63. package/packages/datadog-plugin-graphql/src/resolve.js +30 -35
  64. package/packages/datadog-plugin-graphql/src/tools/signature.js +32 -7
  65. package/packages/datadog-plugin-graphql/src/tools/transforms.js +118 -100
  66. package/packages/datadog-plugin-graphql/src/utils.js +33 -1
  67. package/packages/datadog-plugin-grpc/src/client.js +6 -7
  68. package/packages/datadog-plugin-grpc/src/util.js +57 -22
  69. package/packages/datadog-plugin-http/src/client.js +2 -2
  70. package/packages/datadog-plugin-jest/src/index.js +92 -50
  71. package/packages/datadog-plugin-kafkajs/src/producer.js +32 -0
  72. package/packages/datadog-plugin-mocha/src/index.js +1 -0
  73. package/packages/datadog-plugin-mongodb-core/src/index.js +70 -69
  74. package/packages/datadog-plugin-mysql/src/index.js +1 -1
  75. package/packages/datadog-plugin-openai/src/services.js +2 -1
  76. package/packages/datadog-plugin-pg/src/index.js +3 -3
  77. package/packages/datadog-plugin-playwright/src/index.js +4 -0
  78. package/packages/datadog-plugin-redis/src/index.js +54 -24
  79. package/packages/datadog-plugin-undici/src/index.js +19 -0
  80. package/packages/datadog-plugin-vitest/src/index.js +19 -7
  81. package/packages/datadog-shimmer/src/shimmer.js +35 -0
  82. package/packages/dd-trace/src/aiguard/index.js +3 -1
  83. package/packages/dd-trace/src/aiguard/sdk.js +36 -30
  84. package/packages/dd-trace/src/aiguard/tags.js +20 -11
  85. package/packages/dd-trace/src/appsec/blocking.js +2 -2
  86. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +2 -2
  87. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +1 -1
  88. package/packages/dd-trace/src/appsec/index.js +10 -3
  89. package/packages/dd-trace/src/appsec/reporter.js +19 -5
  90. package/packages/dd-trace/src/azure_metadata.js +17 -6
  91. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +4 -4
  92. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +4 -2
  93. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +6 -4
  94. package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +1 -1
  95. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -1
  96. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +5 -3
  97. package/packages/dd-trace/src/config/defaults.js +3 -14
  98. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -1
  99. package/packages/dd-trace/src/config/helper.js +4 -0
  100. package/packages/dd-trace/src/config/index.js +2 -2
  101. package/packages/dd-trace/src/config/major-overrides.js +98 -0
  102. package/packages/dd-trace/src/config/parsers.js +7 -1
  103. package/packages/dd-trace/src/config/supported-configurations.json +60 -38
  104. package/packages/dd-trace/src/crashtracking/crashtracker.js +15 -3
  105. package/packages/dd-trace/src/datastreams/checkpointer.js +2 -2
  106. package/packages/dd-trace/src/datastreams/context.js +4 -2
  107. package/packages/dd-trace/src/datastreams/manager.js +1 -1
  108. package/packages/dd-trace/src/datastreams/processor.js +2 -2
  109. package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +2 -2
  110. package/packages/dd-trace/src/debugger/devtools_client/source-maps.js +1 -1
  111. package/packages/dd-trace/src/debugger/devtools_client/state.js +2 -1
  112. package/packages/dd-trace/src/debugger/index.js +7 -7
  113. package/packages/dd-trace/src/dogstatsd.js +2 -2
  114. package/packages/dd-trace/src/encode/0.4.js +45 -54
  115. package/packages/dd-trace/src/encode/0.5.js +34 -3
  116. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +26 -19
  117. package/packages/dd-trace/src/encode/agentless-json.js +1 -1
  118. package/packages/dd-trace/src/exporter.js +2 -0
  119. package/packages/dd-trace/src/exporters/agent/index.js +2 -1
  120. package/packages/dd-trace/src/exporters/agentless/index.js +3 -2
  121. package/packages/dd-trace/src/exporters/agentless/writer.js +2 -2
  122. package/packages/dd-trace/src/exporters/common/agents.js +3 -1
  123. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +2 -1
  124. package/packages/dd-trace/src/exporters/common/request.js +4 -2
  125. package/packages/dd-trace/src/exporters/electron/index.js +49 -0
  126. package/packages/dd-trace/src/external-logger/src/index.js +2 -1
  127. package/packages/dd-trace/src/git_metadata.js +10 -8
  128. package/packages/dd-trace/src/id.js +17 -4
  129. package/packages/dd-trace/src/lambda/handler-paths.js +52 -0
  130. package/packages/dd-trace/src/lambda/handler.js +2 -4
  131. package/packages/dd-trace/src/lambda/index.js +62 -14
  132. package/packages/dd-trace/src/lambda/runtime/patch.js +21 -46
  133. package/packages/dd-trace/src/llmobs/index.js +13 -2
  134. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +45 -15
  135. package/packages/dd-trace/src/llmobs/sdk.js +10 -0
  136. package/packages/dd-trace/src/llmobs/writers/base.js +2 -1
  137. package/packages/dd-trace/src/log/writer.js +3 -1
  138. package/packages/dd-trace/src/noop/span.js +3 -1
  139. package/packages/dd-trace/src/openfeature/writers/base.js +2 -1
  140. package/packages/dd-trace/src/openfeature/writers/exposures.js +51 -20
  141. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +3 -2
  142. package/packages/dd-trace/src/opentracing/propagation/text_map.js +20 -9
  143. package/packages/dd-trace/src/payload-tagging/config/index.js +2 -2
  144. package/packages/dd-trace/src/plugins/apollo.js +3 -1
  145. package/packages/dd-trace/src/plugins/ci_plugin.js +52 -17
  146. package/packages/dd-trace/src/plugins/database.js +54 -12
  147. package/packages/dd-trace/src/plugins/index.js +1 -0
  148. package/packages/dd-trace/src/plugins/log_plugin.js +3 -1
  149. package/packages/dd-trace/src/plugins/plugin.js +2 -4
  150. package/packages/dd-trace/src/plugins/tracing.js +5 -3
  151. package/packages/dd-trace/src/plugins/util/ci.js +8 -8
  152. package/packages/dd-trace/src/plugins/util/git-cache.js +20 -18
  153. package/packages/dd-trace/src/plugins/util/git.js +3 -1
  154. package/packages/dd-trace/src/plugins/util/stacktrace.js +2 -2
  155. package/packages/dd-trace/src/plugins/util/test.js +119 -5
  156. package/packages/dd-trace/src/plugins/util/user-provided-git.js +17 -15
  157. package/packages/dd-trace/src/plugins/util/web.js +11 -0
  158. package/packages/dd-trace/src/priority_sampler.js +1 -1
  159. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  160. package/packages/dd-trace/src/profiling/profilers/wall.js +1 -1
  161. package/packages/dd-trace/src/profiling/ssi-heuristics.js +1 -1
  162. package/packages/dd-trace/src/rate_limiter.js +1 -1
  163. package/packages/dd-trace/src/remote_config/scheduler.js +1 -1
  164. package/packages/dd-trace/src/ritm.js +2 -1
  165. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +5 -8
  166. package/packages/dd-trace/src/scope.js +7 -5
  167. package/packages/dd-trace/src/serverless.js +5 -2
  168. package/packages/dd-trace/src/service-naming/extra-services.js +14 -0
  169. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +20 -0
  170. package/packages/dd-trace/src/service-naming/schemas/v0/web.js +4 -0
  171. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +20 -0
  172. package/packages/dd-trace/src/service-naming/schemas/v1/web.js +4 -0
  173. package/packages/dd-trace/src/span_stats.js +1 -1
  174. package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
  175. package/packages/dd-trace/src/telemetry/endpoints.js +1 -1
  176. package/packages/dd-trace/src/telemetry/telemetry.js +2 -2
  177. package/packages/dd-trace/src/lambda/runtime/ritm.js +0 -133
  178. package/vendor/dist/opentracing/LICENSE +0 -201
  179. package/vendor/dist/opentracing/binary_carrier.d.ts +0 -11
  180. package/vendor/dist/opentracing/constants.d.ts +0 -61
  181. package/vendor/dist/opentracing/examples/demo/demo.d.ts +0 -2
  182. package/vendor/dist/opentracing/ext/tags.d.ts +0 -90
  183. package/vendor/dist/opentracing/functions.d.ts +0 -20
  184. package/vendor/dist/opentracing/global_tracer.d.ts +0 -14
  185. package/vendor/dist/opentracing/index.d.ts +0 -12
  186. package/vendor/dist/opentracing/index.js +0 -1
  187. package/vendor/dist/opentracing/mock_tracer/index.d.ts +0 -5
  188. package/vendor/dist/opentracing/mock_tracer/mock_context.d.ts +0 -13
  189. package/vendor/dist/opentracing/mock_tracer/mock_report.d.ts +0 -16
  190. package/vendor/dist/opentracing/mock_tracer/mock_span.d.ts +0 -50
  191. package/vendor/dist/opentracing/mock_tracer/mock_tracer.d.ts +0 -26
  192. package/vendor/dist/opentracing/noop.d.ts +0 -8
  193. package/vendor/dist/opentracing/reference.d.ts +0 -33
  194. package/vendor/dist/opentracing/span.d.ts +0 -147
  195. package/vendor/dist/opentracing/span_context.d.ts +0 -26
  196. package/vendor/dist/opentracing/test/api_compatibility.d.ts +0 -16
  197. package/vendor/dist/opentracing/test/mocktracer_implemenation.d.ts +0 -3
  198. package/vendor/dist/opentracing/test/noop_implementation.d.ts +0 -4
  199. package/vendor/dist/opentracing/test/opentracing_api.d.ts +0 -3
  200. package/vendor/dist/opentracing/test/unittest.d.ts +0 -2
  201. package/vendor/dist/opentracing/tracer.d.ts +0 -127
@@ -3,6 +3,7 @@
3
3
  // Capture real timers at module load time, before any test can install fake timers.
4
4
  const realSetTimeout = setTimeout
5
5
 
6
+ const { performance } = require('node:perf_hooks')
6
7
  const satisfies = require('../../../vendor/dist/semifies')
7
8
 
8
9
  const shimmer = require('../../datadog-shimmer')
@@ -12,9 +13,12 @@ const {
12
13
  PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE,
13
14
  getIsFaultyEarlyFlakeDetection,
14
15
  DYNAMIC_NAME_RE,
16
+ getEfdRetryCount,
17
+ getMaxEfdRetryCount,
15
18
  recordAttemptToFixExecution,
16
19
  logAttemptToFixTestExecution,
17
20
  logTestOptimizationSummary,
21
+ getTestOptimizationRequestResults,
18
22
  } = require('../../dd-trace/src/plugins/util/test')
19
23
  const log = require('../../dd-trace/src/log')
20
24
  const {
@@ -68,6 +72,7 @@ let remainingTestsByFile = {}
68
72
  let isKnownTestsEnabled = false
69
73
  let isEarlyFlakeDetectionEnabled = false
70
74
  let earlyFlakeDetectionNumRetries = 0
75
+ let earlyFlakeDetectionSlowTestRetries = {}
71
76
  let isEarlyFlakeDetectionFaulty = false
72
77
  let earlyFlakeDetectionFaultyThreshold = 0
73
78
  let isFlakyTestRetriesEnabled = false
@@ -83,10 +88,19 @@ let testsReportedInGenerateSummary = new Set()
83
88
  const newTestsWithDynamicNames = new Set()
84
89
  const attemptToFixExecutions = new Map()
85
90
  const loggedAttemptToFixTests = new Set()
91
+ const efdManagedTestKeys = new Set()
92
+ const efdRetryCountByTestKey = new Map()
93
+ const efdRetryCountRequestsByTestKey = new Map()
94
+ const efdRetryTestsById = new Map()
95
+ const efdScheduledOriginalTestKeys = new Set()
96
+ const efdStartedOriginalTestKeys = new Set()
97
+ const efdSlowAbortedTests = new Set()
86
98
  let rootDir = ''
87
99
  let sessionProjects = []
88
100
 
89
101
  const MINIMUM_SUPPORTED_VERSION_RANGE_EFD = '>=1.38.0' // TODO: remove this once we drop support for v5
102
+ const EFD_RETRY_COUNT_REQUEST = 'ddEfdRetryCountRequest'
103
+ const EFD_RETRY_COUNT_RESPONSE = 'ddEfdRetryCountResponse'
90
104
 
91
105
  function isValidKnownTests (receivedKnownTests) {
92
106
  return !!receivedKnownTests.playwright
@@ -97,6 +111,222 @@ function getTestFullyQualifiedName (test) {
97
111
  return `${test._requireFile} ${fullname}`
98
112
  }
99
113
 
114
+ /**
115
+ * @param {object} test
116
+ * @returns {string|undefined}
117
+ */
118
+ function getTestProjectKey (test) {
119
+ const { _projectIndex, _projectId } = test
120
+ if (_projectIndex !== undefined) {
121
+ return `index:${_projectIndex}`
122
+ }
123
+ if (_projectId !== undefined) {
124
+ return `id:${_projectId}`
125
+ }
126
+
127
+ const projectSuite = getSuiteType(test, 'project')
128
+ const projectName = projectSuite?._fullProject?.project?.name ||
129
+ projectSuite?._fullProject?.name ||
130
+ projectSuite?.title
131
+ if (projectName) {
132
+ return `name:${projectName}`
133
+ }
134
+ }
135
+
136
+ /**
137
+ * @param {object} test
138
+ * @returns {number|undefined}
139
+ */
140
+ function getTestEfdRepeatEachIndex (test) {
141
+ if (Object.hasOwn(test, '_ddEfdOriginalRepeatEachIndex')) {
142
+ return test._ddEfdOriginalRepeatEachIndex
143
+ }
144
+ return test.repeatEachIndex
145
+ }
146
+
147
+ /**
148
+ * @param {object} test
149
+ * @returns {string|undefined}
150
+ */
151
+ function getTestRepeatEachKey (test) {
152
+ const repeatEachIndex = getTestEfdRepeatEachIndex(test)
153
+ if (repeatEachIndex !== undefined) {
154
+ return `repeat:${repeatEachIndex}`
155
+ }
156
+ }
157
+
158
+ /**
159
+ * @param {object} test
160
+ * @returns {string}
161
+ */
162
+ function getTestEfdKey (test) {
163
+ const projectKey = getTestProjectKey(test)
164
+ const repeatEachKey = getTestRepeatEachKey(test)
165
+ const testFqn = getTestFullyQualifiedName(test)
166
+ return [projectKey, repeatEachKey, testFqn].filter(Boolean).join(' ')
167
+ }
168
+
169
+ function getConfiguredEfdRetryCount () {
170
+ if (!earlyFlakeDetectionSlowTestRetries || !Object.keys(earlyFlakeDetectionSlowTestRetries).length) {
171
+ return earlyFlakeDetectionNumRetries
172
+ }
173
+ return getMaxEfdRetryCount(earlyFlakeDetectionSlowTestRetries)
174
+ }
175
+
176
+ function markEfdManagedTest (test) {
177
+ test._ddIsEfdManagedTest = true
178
+ test._ddEfdSlowTestRetries = earlyFlakeDetectionSlowTestRetries
179
+ efdManagedTestKeys.add(getTestEfdKey(test))
180
+ }
181
+
182
+ function markEfdRetryTest (test, retryIndex, originalTest) {
183
+ test._ddIsEfdRetry = true
184
+ test._ddEfdRetryIndex = retryIndex
185
+ if (originalTest) {
186
+ test._ddEfdOriginalRepeatEachIndex = getTestEfdRepeatEachIndex(originalTest)
187
+ }
188
+ }
189
+
190
+ function registerEfdRetryTest (test) {
191
+ if (!test._ddIsEfdRetry) {
192
+ return
193
+ }
194
+
195
+ efdRetryTestsById.set(test.id, {
196
+ retryIndex: test._ddEfdRetryIndex,
197
+ testEfdKey: getTestEfdKey(test),
198
+ })
199
+ }
200
+
201
+ function getTestEfdSlowTestRetries (test) {
202
+ return test._ddEfdSlowTestRetries || earlyFlakeDetectionSlowTestRetries
203
+ }
204
+
205
+ function isTestEfdManaged (test) {
206
+ return !!test._ddIsEfdManagedTest || (
207
+ (test._ddIsNew || test._ddIsModified) &&
208
+ !test._ddIsAttemptToFix &&
209
+ isEarlyFlakeDetectionEnabled
210
+ )
211
+ }
212
+
213
+ function getFileSuiteRepeatEachIndex (fileSuite) {
214
+ const test = fileSuite.allTests()[0]
215
+ return test ? getTestEfdRepeatEachIndex(test) || 0 : 0
216
+ }
217
+
218
+ function getEfdRetryRepeatEachIndex (fileSuite, projectSuite, retryIndex, retryCount) {
219
+ const nativeRepeatEach = projectSuite._fullProject?.project?.repeatEach || 1
220
+ const originalRepeatEachIndex = getFileSuiteRepeatEachIndex(fileSuite)
221
+ return nativeRepeatEach + (originalRepeatEachIndex * retryCount) + retryIndex - 1
222
+ }
223
+
224
+ function getEfdRetryCountForTest (test) {
225
+ return efdRetryCountByTestKey.get(getTestEfdKey(test)) ?? getConfiguredEfdRetryCount()
226
+ }
227
+
228
+ function setEfdRetryCountForTest (test, retryCount) {
229
+ const testEfdKey = getTestEfdKey(test)
230
+ efdRetryCountByTestKey.set(testEfdKey, retryCount)
231
+
232
+ const requests = efdRetryCountRequestsByTestKey.get(testEfdKey)
233
+ if (requests) {
234
+ efdRetryCountRequestsByTestKey.delete(testEfdKey)
235
+ for (const resolveRequest of requests) {
236
+ resolveRequest(retryCount)
237
+ }
238
+ }
239
+ }
240
+
241
+ function sendEfdRetryCountToWorker (workerProcess, testId, retryIndex, retryCount) {
242
+ workerProcess.send({
243
+ type: EFD_RETRY_COUNT_RESPONSE,
244
+ testId,
245
+ isEfdRetry: retryIndex !== undefined,
246
+ retryIndex,
247
+ retryCount,
248
+ })
249
+ }
250
+
251
+ function sendEfdRetryCountToWorkerWhenAvailable (workerProcess, testId) {
252
+ const efdRetryTest = efdRetryTestsById.get(testId)
253
+ if (!efdRetryTest) {
254
+ sendEfdRetryCountToWorker(workerProcess, testId)
255
+ return
256
+ }
257
+
258
+ const { retryIndex, testEfdKey } = efdRetryTest
259
+
260
+ if (!testEfdKey || !efdManagedTestKeys.has(testEfdKey)) {
261
+ sendEfdRetryCountToWorker(workerProcess, testId)
262
+ return
263
+ }
264
+
265
+ const retryCount = efdRetryCountByTestKey.get(testEfdKey)
266
+ if (retryCount !== undefined) {
267
+ sendEfdRetryCountToWorker(workerProcess, testId, retryIndex, retryCount)
268
+ return
269
+ }
270
+
271
+ if (!efdStartedOriginalTestKeys.has(testEfdKey) && !efdScheduledOriginalTestKeys.has(testEfdKey)) {
272
+ sendEfdRetryCountToWorker(workerProcess, testId, retryIndex, 0)
273
+ return
274
+ }
275
+
276
+ if (!efdRetryCountRequestsByTestKey.has(testEfdKey)) {
277
+ efdRetryCountRequestsByTestKey.set(testEfdKey, [])
278
+ }
279
+ efdRetryCountRequestsByTestKey.get(testEfdKey).push((retryCount) => {
280
+ sendEfdRetryCountToWorker(workerProcess, testId, retryIndex, retryCount)
281
+ })
282
+ }
283
+
284
+ /**
285
+ * @param {object} test
286
+ * @returns {boolean}
287
+ */
288
+ function shouldRequestEfdRetryCount (test) {
289
+ // The main process remains the source of truth. repeatEachIndex is only used as
290
+ // a cheap worker-side filter so first executions do not block on coordination.
291
+ return test._ddIsEfdRetry || test.repeatEachIndex > 0
292
+ }
293
+
294
+ function waitForEfdRetryCount (test) {
295
+ if (!process.send || !shouldRequestEfdRetryCount(test)) {
296
+ return Promise.resolve()
297
+ }
298
+
299
+ const testEfdKey = getTestEfdKey(test)
300
+ return new Promise(resolve => {
301
+ const messageHandler = (message) => {
302
+ if (message?.type === EFD_RETRY_COUNT_RESPONSE && message.testId === test.id) {
303
+ if (message.isEfdRetry) {
304
+ test._ddIsEfdRetry = true
305
+ test._ddEfdRetryIndex = message.retryIndex
306
+ test._ddEfdRetryCount = message.retryCount
307
+ efdRetryCountByTestKey.set(testEfdKey, message.retryCount)
308
+ }
309
+ process.removeListener('message', messageHandler)
310
+ resolve()
311
+ }
312
+ }
313
+
314
+ process.on('message', messageHandler)
315
+ process.send({
316
+ type: EFD_RETRY_COUNT_REQUEST,
317
+ testId: test.id,
318
+ })
319
+ })
320
+ }
321
+
322
+ function shouldSkipEfdRetry (test) {
323
+ if (!test._ddIsEfdRetry) {
324
+ return false
325
+ }
326
+ const retryCount = test._ddEfdRetryCount ?? efdRetryCountByTestKey.get(getTestEfdKey(test))
327
+ return retryCount !== undefined && test._ddEfdRetryIndex > retryCount
328
+ }
329
+
100
330
  function getTestProperties (test) {
101
331
  const testName = getTestFullname(test)
102
332
  const testSuite = getTestSuitePath(test._requireFile, rootDir)
@@ -125,14 +355,17 @@ function getSuiteType (test, type) {
125
355
  }
126
356
 
127
357
  // Copy of Suite#_deepClone but with a function to filter tests
128
- function deepCloneSuite (suite, filterTest, tags = []) {
358
+ function deepCloneSuite (suite, filterTest, tags = [], configureCopiedTest) {
129
359
  const copy = suite._clone()
130
360
  for (const entry of suite._entries) {
131
361
  if (entry.constructor.name === 'Suite') {
132
- copy._addSuite(deepCloneSuite(entry, filterTest, tags))
362
+ copy._addSuite(deepCloneSuite(entry, filterTest, tags, configureCopiedTest))
133
363
  } else {
134
364
  if (filterTest(entry)) {
135
365
  const copiedTest = entry._clone()
366
+ if (configureCopiedTest) {
367
+ configureCopiedTest(copiedTest, entry)
368
+ }
136
369
  for (const tag of tags) {
137
370
  const resolvedTag = typeof tag === 'function' ? tag(entry) : tag
138
371
 
@@ -303,6 +536,7 @@ function getFinalStatus ({
303
536
  isAttemptToFix,
304
537
  hasFailedAllRetries,
305
538
  hasFailedAttemptToFixRetries,
539
+ hasPassedAnyEfdAttempt,
306
540
  testStatus,
307
541
  }) {
308
542
  if (!isFinalExecution) {
@@ -311,9 +545,12 @@ function getFinalStatus ({
311
545
  if (isDisabled || isQuarantined || testStatus === 'skip') {
312
546
  return 'skip'
313
547
  }
314
- if (isAtrRetry || isEfdManagedTest) {
548
+ if (isAtrRetry) {
315
549
  return hasFailedAllRetries ? 'fail' : 'pass'
316
550
  }
551
+ if (isEfdManagedTest) {
552
+ return hasPassedAnyEfdAttempt ? 'pass' : 'fail'
553
+ }
317
554
  if (isAttemptToFix) {
318
555
  return hasFailedAttemptToFixRetries ? 'fail' : 'pass'
319
556
  }
@@ -350,6 +587,14 @@ function testBeginHandler (test, browserName, shouldCreateTestSpan) {
350
587
  if (_type === 'beforeAll' || _type === 'afterAll') {
351
588
  return
352
589
  }
590
+ if (shouldSkipEfdRetry(test)) {
591
+ test._ddShouldSkipEfdRetry = true
592
+ return
593
+ }
594
+ test._ddStartTime = performance.now()
595
+ if (isTestEfdManaged(test) && !test._ddIsEfdRetry) {
596
+ efdStartedOriginalTestKeys.add(getTestEfdKey(test))
597
+ }
353
598
  // this means that a skipped test is being handled
354
599
  if (!remainingTestsByFile[testSuiteAbsolutePath].length) {
355
600
  return
@@ -391,6 +636,45 @@ function testBeginHandler (test, browserName, shouldCreateTestSpan) {
391
636
  }
392
637
  }
393
638
 
639
+ function finishTestSuiteIfDone (testSuiteAbsolutePath, projects) {
640
+ if (!shouldFinishTestSuite(testSuiteAbsolutePath)) {
641
+ return
642
+ }
643
+
644
+ const skippedTests = remainingTestsByFile[testSuiteAbsolutePath]
645
+ .filter(test => test.expectedStatus === 'skipped')
646
+
647
+ for (const test of skippedTests) {
648
+ const browserName = getBrowserNameFromProjects(projects, test)
649
+ testSkipCh.publish({
650
+ testName: getTestFullname(test),
651
+ testSuiteAbsolutePath,
652
+ testSourceFileAbsolutePath: test.location.file,
653
+ testSourceLine: test.location.line,
654
+ browserName,
655
+ isNew: test._ddIsNew,
656
+ isDisabled: test._ddIsDisabled,
657
+ isModified: test._ddIsModified,
658
+ isQuarantined: test._ddIsQuarantined,
659
+ })
660
+ }
661
+ remainingTestsByFile[testSuiteAbsolutePath] = []
662
+
663
+ const testStatuses = testSuiteToTestStatuses.get(testSuiteAbsolutePath)
664
+ let testSuiteStatus = 'pass'
665
+ if (testStatuses?.includes('fail')) {
666
+ testSuiteStatus = 'fail'
667
+ } else if (testStatuses?.every(status => status === 'skip')) {
668
+ testSuiteStatus = 'skip'
669
+ }
670
+
671
+ const suiteError = getTestSuiteError(testSuiteAbsolutePath)
672
+ const testSuiteCtx = testSuiteToCtx.get(testSuiteAbsolutePath)
673
+ if (testSuiteCtx) {
674
+ testSuiteFinishCh.publish({ status: testSuiteStatus, error: suiteError, ...testSuiteCtx.currentStore })
675
+ }
676
+ }
677
+
394
678
  function testEndHandler ({
395
679
  test,
396
680
  annotations,
@@ -420,11 +704,21 @@ function testEndHandler ({
420
704
  return
421
705
  }
422
706
 
707
+ if (test._ddShouldSkipEfdRetry || shouldSkipEfdRetry(test)) {
708
+ test._ddShouldSkipEfdRetry = true
709
+ remainingTestsByFile[testSuiteAbsolutePath] = remainingTestsByFile[testSuiteAbsolutePath]
710
+ .filter(currentTest => currentTest !== test)
711
+ finishTestSuiteIfDone(testSuiteAbsolutePath, projects)
712
+ return
713
+ }
714
+
715
+ const isEfdManagedTest = isTestEfdManaged(test)
423
716
  const testFqn = getTestFullyQualifiedName(test)
424
- const testStatuses = testsToTestStatuses.get(testFqn) || []
717
+ const testStatusKey = isEfdManagedTest ? getTestEfdKey(test) : testFqn
718
+ const testStatuses = testsToTestStatuses.get(testStatusKey) || []
425
719
 
426
720
  if (testStatuses.length === 0) {
427
- testsToTestStatuses.set(testFqn, [testStatus])
721
+ testsToTestStatuses.set(testStatusKey, [testStatus])
428
722
  if (test._ddIsNew && DYNAMIC_NAME_RE.test(getTestFullname(test))) {
429
723
  newTestsWithDynamicNames.add(`${getTestSuitePath(test._requireFile, rootDir)} › ${getTestFullname(test)}`)
430
724
  }
@@ -432,6 +726,17 @@ function testEndHandler ({
432
726
  testStatuses.push(testStatus)
433
727
  }
434
728
 
729
+ const testEfdKey = getTestEfdKey(test)
730
+ if (isEfdManagedTest && !test._ddIsEfdRetry && !efdRetryCountByTestKey.has(testEfdKey)) {
731
+ const testResult = results.at(-1)
732
+ const duration = testResult?.duration > 0 ? testResult.duration : performance.now() - test._ddStartTime
733
+ const retryCount = getEfdRetryCount(duration, getTestEfdSlowTestRetries(test))
734
+ setEfdRetryCountForTest(test, retryCount)
735
+ if (retryCount === 0) {
736
+ efdSlowAbortedTests.add(testEfdKey)
737
+ }
738
+ }
739
+
435
740
  const testProperties = getTestProperties(test)
436
741
 
437
742
  if (testProperties.attemptToFix) {
@@ -460,9 +765,10 @@ function testEndHandler ({
460
765
  }
461
766
 
462
767
  // Check if all EFD retries failed
463
- if (testStatuses.length === earlyFlakeDetectionNumRetries + 1 &&
768
+ const efdRetryCount = getEfdRetryCountForTest(test)
769
+ if (efdRetryCount > 0 && testStatuses.length === efdRetryCount + 1 &&
464
770
  (test._ddIsNew || test._ddIsModified) &&
465
- test._ddIsEfdRetry &&
771
+ isEarlyFlakeDetectionEnabled &&
466
772
  testStatuses.every(status => status === 'fail')) {
467
773
  test._ddHasFailedAllRetries = true
468
774
  }
@@ -480,9 +786,6 @@ function testEndHandler ({
480
786
  if (shouldCreateTestSpan) {
481
787
  const testResult = results.at(-1)
482
788
  const testCtx = testToCtx.get(test)
483
- const isEfdManagedTest = (test._ddIsNew || test._ddIsModified) &&
484
- !test._ddIsAttemptToFix &&
485
- isEarlyFlakeDetectionEnabled
486
789
  const isAtrRetry = testResult?.retry > 0 &&
487
790
  isFlakyTestRetriesEnabled &&
488
791
  !test._ddIsAttemptToFix &&
@@ -497,6 +800,7 @@ function testEndHandler ({
497
800
  isAttemptToFix: test._ddIsAttemptToFix,
498
801
  hasFailedAllRetries: test._ddHasFailedAllRetries,
499
802
  hasFailedAttemptToFixRetries: test._ddHasFailedAttemptToFixRetries,
803
+ hasPassedAnyEfdAttempt: testStatuses.includes('pass'),
500
804
  testStatus,
501
805
  })
502
806
 
@@ -520,6 +824,7 @@ function testEndHandler ({
520
824
  isAtrRetry,
521
825
  isModified: test._ddIsModified,
522
826
  finalStatus,
827
+ earlyFlakeAbortReason: efdSlowAbortedTests.has(testEfdKey) ? 'slow' : undefined,
523
828
  ...testCtx.currentStore,
524
829
  })
525
830
  }
@@ -540,38 +845,7 @@ function testEndHandler ({
540
845
  .filter(currentTest => currentTest !== test)
541
846
  }
542
847
 
543
- if (shouldFinishTestSuite(testSuiteAbsolutePath)) {
544
- const skippedTests = remainingTestsByFile[testSuiteAbsolutePath]
545
- .filter(test => test.expectedStatus === 'skipped')
546
-
547
- for (const test of skippedTests) {
548
- const browserName = getBrowserNameFromProjects(projects, test)
549
- testSkipCh.publish({
550
- testName: getTestFullname(test),
551
- testSuiteAbsolutePath,
552
- testSourceFileAbsolutePath: test.location.file,
553
- testSourceLine: test.location.line,
554
- browserName,
555
- isNew: test._ddIsNew,
556
- isDisabled: test._ddIsDisabled,
557
- isModified: test._ddIsModified,
558
- isQuarantined: test._ddIsQuarantined,
559
- })
560
- }
561
- remainingTestsByFile[testSuiteAbsolutePath] = []
562
-
563
- const testStatuses = testSuiteToTestStatuses.get(testSuiteAbsolutePath)
564
- let testSuiteStatus = 'pass'
565
- if (testStatuses.includes('fail')) {
566
- testSuiteStatus = 'fail'
567
- } else if (testStatuses.every(status => status === 'skip')) {
568
- testSuiteStatus = 'skip'
569
- }
570
-
571
- const suiteError = getTestSuiteError(testSuiteAbsolutePath)
572
- const testSuiteCtx = testSuiteToCtx.get(testSuiteAbsolutePath)
573
- testSuiteFinishCh.publish({ status: testSuiteStatus, error: suiteError, ...testSuiteCtx.currentStore })
574
- }
848
+ finishTestSuiteIfDone(testSuiteAbsolutePath, projects)
575
849
  }
576
850
 
577
851
  function dispatcherRunWrapper (run) {
@@ -581,6 +855,39 @@ function dispatcherRunWrapper (run) {
581
855
  }
582
856
  }
583
857
 
858
+ function deferEfdRetryGroups (testGroups) {
859
+ const groupsWithOriginalTests = []
860
+ const efdRetryOnlyGroups = []
861
+
862
+ for (const group of testGroups) {
863
+ const originalTests = []
864
+ const efdRetryTests = []
865
+
866
+ for (const test of group.tests) {
867
+ if (test._ddIsEfdRetry) {
868
+ efdRetryTests.push(test)
869
+ } else {
870
+ originalTests.push(test)
871
+ if (isTestEfdManaged(test)) {
872
+ efdScheduledOriginalTestKeys.add(getTestEfdKey(test))
873
+ }
874
+ }
875
+ }
876
+
877
+ if (efdRetryTests.length && originalTests.length) {
878
+ group.tests = [...originalTests, ...efdRetryTests]
879
+ }
880
+
881
+ if (originalTests.length) {
882
+ groupsWithOriginalTests.push(group)
883
+ } else {
884
+ efdRetryOnlyGroups.push(group)
885
+ }
886
+ }
887
+
888
+ return [...groupsWithOriginalTests, ...efdRetryOnlyGroups]
889
+ }
890
+
584
891
  function dispatcherRunWrapperNew (run) {
585
892
  return function (testGroups) {
586
893
  // Filter out disabled tests from testGroups before they get scheduled,
@@ -593,12 +900,17 @@ function dispatcherRunWrapperNew (run) {
593
900
  testGroups = testGroups.filter(group => group.tests.length > 0)
594
901
  }
595
902
 
903
+ if (isEarlyFlakeDetectionEnabled) {
904
+ testGroups = deferEfdRetryGroups(testGroups)
905
+ }
906
+
596
907
  if (!this._allTests) {
597
908
  // Removed in https://github.com/microsoft/playwright/commit/1e52c37b254a441cccf332520f60225a5acc14c7
598
909
  // Not available from >=1.44.0
599
910
  this._ddAllTests = testGroups.flatMap(g => g.tests)
600
911
  }
601
912
  remainingTestsByFile = getTestsBySuiteFromTestGroups(testGroups)
913
+ arguments[0] = testGroups
602
914
  return run.apply(this, arguments)
603
915
  }
604
916
  }
@@ -638,7 +950,6 @@ function dispatcherHook (dispatcherExport) {
638
950
  )
639
951
  }
640
952
  })
641
-
642
953
  return worker
643
954
  })
644
955
  return dispatcherExport
@@ -690,14 +1001,11 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
690
1001
  // above) and mark the execution final once the count reaches the expected total.
691
1002
  // This mirrors how ATF finality is detected and centralizes the decision in the
692
1003
  // main process, so workers only need to act on the _ddIsFinalExecution flag.
693
- const isEfdManagedTest = (test._ddIsNew || test._ddIsModified) &&
694
- !test._ddIsAttemptToFix &&
695
- isEarlyFlakeDetectionEnabled
1004
+ const isEfdManagedTest = isTestEfdManaged(test)
696
1005
  let isFinalExecution
697
1006
  if (isEfdManagedTest) {
698
- const testFqn = getTestFullyQualifiedName(test)
699
- const efdTestStatuses = testsToTestStatuses.get(testFqn) || []
700
- isFinalExecution = efdTestStatuses.length === earlyFlakeDetectionNumRetries + 1
1007
+ const efdTestStatuses = testsToTestStatuses.get(getTestEfdKey(test)) || []
1008
+ isFinalExecution = efdTestStatuses.length === getEfdRetryCountForTest(test) + 1
701
1009
  } else if (test._ddIsAttemptToFix) {
702
1010
  isFinalExecution = !!(test._ddHasPassedAttemptToFixRetries || test._ddHasFailedAttemptToFixRetries)
703
1011
  } else {
@@ -722,10 +1030,11 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
722
1030
  _ddIsModified: test._ddIsModified,
723
1031
  _ddIsFinalExecution: isFinalExecution,
724
1032
  _ddIsEfdManagedTest: isEfdManagedTest,
1033
+ _ddEarlyFlakeAbortReason: efdSlowAbortedTests.has(getTestEfdKey(test)) ? 'slow' : undefined,
1034
+ _ddHasPassedAnyEfdAttempt: (testsToTestStatuses.get(getTestEfdKey(test)) || []).includes('pass'),
725
1035
  },
726
1036
  })
727
1037
  })
728
-
729
1038
  return worker
730
1039
  })
731
1040
  return dispatcherExport
@@ -751,6 +1060,7 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
751
1060
  isKnownTestsEnabled = libraryConfig.isKnownTestsEnabled
752
1061
  isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
753
1062
  earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
1063
+ earlyFlakeDetectionSlowTestRetries = libraryConfig.earlyFlakeDetectionSlowTestRetries ?? {}
754
1064
  earlyFlakeDetectionFaultyThreshold = libraryConfig.earlyFlakeDetectionFaultyThreshold
755
1065
  isFlakyTestRetriesEnabled = libraryConfig.isFlakyTestRetriesEnabled
756
1066
  flakyTestRetriesCount = libraryConfig.flakyTestRetriesCount
@@ -766,9 +1076,24 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
766
1076
  log.error('Playwright session start error', e)
767
1077
  }
768
1078
 
769
- if (isKnownTestsEnabled && satisfies(playwrightVersion, MINIMUM_SUPPORTED_VERSION_RANGE_EFD)) {
1079
+ const isTestOptimizationSupported = satisfies(playwrightVersion, MINIMUM_SUPPORTED_VERSION_RANGE_EFD)
1080
+ const shouldGetKnownTests = isKnownTestsEnabled && isTestOptimizationSupported
1081
+ const shouldGetTestManagementTests = isTestManagementTestsEnabled && isTestOptimizationSupported
1082
+
1083
+ const {
1084
+ knownTestsResponse,
1085
+ testManagementTestsResponse,
1086
+ } = await getTestOptimizationRequestResults({
1087
+ isKnownTestsEnabled: shouldGetKnownTests,
1088
+ isTestManagementTestsEnabled: shouldGetTestManagementTests,
1089
+ getKnownTests: () => getChannelPromise(knownTestsCh),
1090
+ getTestManagementTests: () => getChannelPromise(testManagementTestsCh),
1091
+ })
1092
+
1093
+ if (shouldGetKnownTests) {
770
1094
  try {
771
- const { err, knownTests: receivedKnownTests } = await getChannelPromise(knownTestsCh)
1095
+ const { err, knownTests: receivedKnownTests } =
1096
+ knownTestsResponse || await getChannelPromise(knownTestsCh)
772
1097
  if (err) {
773
1098
  isEarlyFlakeDetectionEnabled = false
774
1099
  isKnownTestsEnabled = false
@@ -787,9 +1112,10 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
787
1112
  }
788
1113
  }
789
1114
 
790
- if (isTestManagementTestsEnabled && satisfies(playwrightVersion, MINIMUM_SUPPORTED_VERSION_RANGE_EFD)) {
1115
+ if (shouldGetTestManagementTests) {
791
1116
  try {
792
- const { err, testManagementTests: receivedTestManagementTests } = await getChannelPromise(testManagementTestsCh)
1117
+ const { err, testManagementTests: receivedTestManagementTests } =
1118
+ testManagementTestsResponse || await getChannelPromise(testManagementTestsCh)
793
1119
  if (err) {
794
1120
  isTestManagementTestsEnabled = false
795
1121
  } else {
@@ -801,7 +1127,7 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
801
1127
  }
802
1128
  }
803
1129
 
804
- if (isImpactedTestsEnabled && satisfies(playwrightVersion, MINIMUM_SUPPORTED_VERSION_RANGE_EFD)) {
1130
+ if (isImpactedTestsEnabled && isTestOptimizationSupported) {
805
1131
  try {
806
1132
  const { err, modifiedFiles: receivedModifiedFiles } = await getChannelPromise(modifiedFilesCh)
807
1133
  if (err) {
@@ -900,6 +1226,13 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
900
1226
  remainingTestsByFile = {}
901
1227
  quarantinedButNotAttemptToFixFqns = new Set()
902
1228
  testsReportedInGenerateSummary = new Set()
1229
+ efdManagedTestKeys.clear()
1230
+ efdRetryCountByTestKey.clear()
1231
+ efdRetryCountRequestsByTestKey.clear()
1232
+ efdRetryTestsById.clear()
1233
+ efdScheduledOriginalTestKeys.clear()
1234
+ efdStartedOriginalTestKeys.clear()
1235
+ efdSlowAbortedTests.clear()
903
1236
 
904
1237
  // TODO: we can trick playwright into thinking the session passed by returning
905
1238
  // 'passed' here. We might be able to use this for both EFD and Test Management tests.
@@ -1001,12 +1334,29 @@ addHook({
1001
1334
  * - we execute `applyRepeatEachIndex` for each of these cloned file suites
1002
1335
  * - we add the cloned file suites to the project suite
1003
1336
  */
1004
- function applyRetriesToTests (fileSuitesWithTestsToRetry, filterTest, tagsToApply, numRetries) {
1337
+ function applyRetriesToTests (
1338
+ fileSuitesWithTestsToRetry,
1339
+ filterTest,
1340
+ tagsToApply,
1341
+ numRetries,
1342
+ configureCopiedTest,
1343
+ getRetryRepeatEachIndex
1344
+ ) {
1005
1345
  for (const [fileSuite, projectSuite] of fileSuitesWithTestsToRetry.entries()) {
1006
1346
  for (let repeatEachIndex = 1; repeatEachIndex <= numRetries; repeatEachIndex++) {
1007
- const copyFileSuite = deepCloneSuite(fileSuite, filterTest, tagsToApply)
1008
- applyRepeatEachIndex(projectSuite._fullProject, copyFileSuite, repeatEachIndex + 1)
1347
+ const copyFileSuite = deepCloneSuite(fileSuite, filterTest, tagsToApply, (copiedTest, originalTest) => {
1348
+ if (configureCopiedTest) {
1349
+ configureCopiedTest(copiedTest, originalTest, repeatEachIndex)
1350
+ }
1351
+ })
1352
+ const retryRepeatEachIndex = getRetryRepeatEachIndex
1353
+ ? getRetryRepeatEachIndex(fileSuite, projectSuite, repeatEachIndex, numRetries)
1354
+ : repeatEachIndex + 1
1355
+ applyRepeatEachIndex(projectSuite._fullProject, copyFileSuite, retryRepeatEachIndex)
1009
1356
  projectSuite._addSuite(copyFileSuite)
1357
+ for (const copiedTest of copyFileSuite.allTests()) {
1358
+ registerEfdRetryTest(copiedTest)
1359
+ }
1010
1360
  }
1011
1361
  }
1012
1362
  }
@@ -1091,6 +1441,7 @@ addHook({
1091
1441
  for (const impactedTest of impactedTests) {
1092
1442
  impactedTest._ddIsModified = true
1093
1443
  if (isEarlyFlakeDetectionEnabled && impactedTest.expectedStatus !== 'skipped') {
1444
+ markEfdManagedTest(impactedTest)
1094
1445
  const fileSuite = getSuiteType(impactedTest, 'file')
1095
1446
  if (!fileSuitesWithImpactedTestsToProjects.has(fileSuite)) {
1096
1447
  fileSuitesWithImpactedTestsToProjects.set(fileSuite, getSuiteType(impactedTest, 'project'))
@@ -1106,7 +1457,12 @@ addHook({
1106
1457
  '_ddIsEfdRetry',
1107
1458
  (test) => (isKnownTestsEnabled && isNewTest(test) ? '_ddIsNew' : null),
1108
1459
  ],
1109
- earlyFlakeDetectionNumRetries
1460
+ getConfiguredEfdRetryCount(),
1461
+ (copiedTest, originalTest, retryIndex) => {
1462
+ markEfdRetryTest(copiedTest, retryIndex, originalTest)
1463
+ markEfdManagedTest(copiedTest)
1464
+ },
1465
+ getEfdRetryRepeatEachIndex
1110
1466
  )
1111
1467
  }
1112
1468
 
@@ -1130,6 +1486,7 @@ addHook({
1130
1486
  if (isEarlyFlakeDetectionEnabled && newTest.expectedStatus !== 'skipped' && !newTest._ddIsModified) {
1131
1487
  // Prevent ATR or `--retries` from retrying new tests if EFD is enabled
1132
1488
  newTest.retries = 0
1489
+ markEfdManagedTest(newTest)
1133
1490
  const fileSuite = getSuiteType(newTest, 'file')
1134
1491
  if (!fileSuitesWithNewTestsToProjects.has(fileSuite)) {
1135
1492
  fileSuitesWithNewTestsToProjects.set(fileSuite, getSuiteType(newTest, 'project'))
@@ -1141,7 +1498,12 @@ addHook({
1141
1498
  fileSuitesWithNewTestsToProjects,
1142
1499
  isNewTest,
1143
1500
  ['_ddIsNew', '_ddIsEfdRetry'],
1144
- earlyFlakeDetectionNumRetries
1501
+ getConfiguredEfdRetryCount(),
1502
+ (copiedTest, originalTest, retryIndex) => {
1503
+ markEfdRetryTest(copiedTest, retryIndex, originalTest)
1504
+ markEfdManagedTest(copiedTest)
1505
+ },
1506
+ getEfdRetryRepeatEachIndex
1145
1507
  )
1146
1508
  }
1147
1509
  }
@@ -1177,6 +1539,10 @@ addHook({
1177
1539
 
1178
1540
  // We add a new listener to `this.process`, which is represents the worker
1179
1541
  this.process.on('message', (message) => {
1542
+ if (message?.type === EFD_RETRY_COUNT_REQUEST) {
1543
+ sendEfdRetryCountToWorkerWhenAvailable(this.process, message.testId)
1544
+ return
1545
+ }
1180
1546
  // These messages are [code, payload]. The payload is test data
1181
1547
  if (Array.isArray(message) && message[0] === PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE) {
1182
1548
  workerReportCh.publish(message[1])
@@ -1235,9 +1601,15 @@ addHook({
1235
1601
  const stepInfoByStepId = {}
1236
1602
 
1237
1603
  shimmer.wrap(workerPackage.WorkerMain.prototype, '_runTest', _runTest => async function (test) {
1604
+ await waitForEfdRetryCount(test)
1605
+ if (shouldSkipEfdRetry(test)) {
1606
+ test._ddShouldSkipEfdRetry = true
1607
+ test.expectedStatus = 'skipped'
1608
+ }
1238
1609
  if (test.expectedStatus === 'skipped') {
1239
1610
  return _runTest.apply(this, arguments)
1240
1611
  }
1612
+ test._ddStartTime = performance.now()
1241
1613
  steps = []
1242
1614
 
1243
1615
  const {
@@ -1320,6 +1692,21 @@ addHook({
1320
1692
  await res
1321
1693
 
1322
1694
  const { status, error, annotations, retry, testId } = testInfo
1695
+ const testEfdKey = getTestEfdKey(test)
1696
+ const isEfdManagedTest = isTestEfdManaged(test)
1697
+ if (isEfdManagedTest && !test._ddIsEfdRetry && !efdRetryCountByTestKey.has(testEfdKey)) {
1698
+ const duration = test.results?.at(-1)?.duration > 0
1699
+ ? test.results.at(-1).duration
1700
+ : performance.now() - test._ddStartTime
1701
+ const retryCount = getEfdRetryCount(
1702
+ duration,
1703
+ getTestEfdSlowTestRetries(test)
1704
+ )
1705
+ setEfdRetryCountForTest(test, retryCount)
1706
+ if (retryCount === 0) {
1707
+ efdSlowAbortedTests.add(testEfdKey)
1708
+ }
1709
+ }
1323
1710
 
1324
1711
  // testInfo.errors could be better than "error",
1325
1712
  // which will only include timeout error (even though the test failed because of a different error)
@@ -1365,6 +1752,7 @@ addHook({
1365
1752
  isAttemptToFix: test._ddIsAttemptToFix,
1366
1753
  hasFailedAllRetries: test._ddHasFailedAllRetries,
1367
1754
  hasFailedAttemptToFixRetries: test._ddHasFailedAttemptToFixRetries,
1755
+ hasPassedAnyEfdAttempt: test._ddHasPassedAnyEfdAttempt,
1368
1756
  testStatus: STATUS_TO_TEST_STATUS[status],
1369
1757
  })
1370
1758
 
@@ -1388,6 +1776,7 @@ addHook({
1388
1776
  isModified: test._ddIsModified,
1389
1777
  onDone,
1390
1778
  finalStatus,
1779
+ earlyFlakeAbortReason: test._ddEarlyFlakeAbortReason,
1391
1780
  ...testCtx.currentStore,
1392
1781
  })
1393
1782