dd-trace 5.101.0 → 5.103.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 (235) hide show
  1. package/ext/exporters.js +1 -0
  2. package/package.json +20 -17
  3. package/packages/datadog-esbuild/src/utils.js +2 -2
  4. package/packages/datadog-instrumentations/src/aerospike.js +2 -2
  5. package/packages/datadog-instrumentations/src/ai.js +9 -9
  6. package/packages/datadog-instrumentations/src/amqplib.js +6 -7
  7. package/packages/datadog-instrumentations/src/anthropic.js +10 -10
  8. package/packages/datadog-instrumentations/src/apollo-server-core.js +3 -3
  9. package/packages/datadog-instrumentations/src/apollo-server.js +5 -5
  10. package/packages/datadog-instrumentations/src/avsc.js +6 -6
  11. package/packages/datadog-instrumentations/src/aws-sdk.js +151 -67
  12. package/packages/datadog-instrumentations/src/azure-durable-functions.js +8 -8
  13. package/packages/datadog-instrumentations/src/bluebird.js +2 -2
  14. package/packages/datadog-instrumentations/src/body-parser.js +2 -2
  15. package/packages/datadog-instrumentations/src/cassandra-driver.js +7 -7
  16. package/packages/datadog-instrumentations/src/child_process.js +12 -12
  17. package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +41 -24
  18. package/packages/datadog-instrumentations/src/connect.js +7 -7
  19. package/packages/datadog-instrumentations/src/cookie-parser.js +4 -4
  20. package/packages/datadog-instrumentations/src/cookie.js +2 -2
  21. package/packages/datadog-instrumentations/src/couchbase.js +73 -238
  22. package/packages/datadog-instrumentations/src/crypto.js +4 -4
  23. package/packages/datadog-instrumentations/src/cucumber.js +78 -17
  24. package/packages/datadog-instrumentations/src/dns.js +0 -3
  25. package/packages/datadog-instrumentations/src/elasticsearch.js +8 -11
  26. package/packages/datadog-instrumentations/src/electron/preload.js +42 -0
  27. package/packages/datadog-instrumentations/src/electron.js +240 -0
  28. package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +6 -6
  29. package/packages/datadog-instrumentations/src/express-session.js +4 -4
  30. package/packages/datadog-instrumentations/src/express.js +10 -11
  31. package/packages/datadog-instrumentations/src/fastify.js +2 -2
  32. package/packages/datadog-instrumentations/src/fetch.js +5 -5
  33. package/packages/datadog-instrumentations/src/fs.js +14 -14
  34. package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +5 -7
  35. package/packages/datadog-instrumentations/src/google-genai.js +4 -4
  36. package/packages/datadog-instrumentations/src/graphql.js +13 -12
  37. package/packages/datadog-instrumentations/src/grpc/server.js +2 -2
  38. package/packages/datadog-instrumentations/src/hapi.js +2 -2
  39. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +9 -9
  40. package/packages/datadog-instrumentations/src/helpers/hook.js +4 -1
  41. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  42. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -2
  43. package/packages/datadog-instrumentations/src/helpers/kafka.js +41 -0
  44. package/packages/datadog-instrumentations/src/helpers/promise.js +2 -2
  45. package/packages/datadog-instrumentations/src/hono.js +2 -2
  46. package/packages/datadog-instrumentations/src/http/client.js +6 -6
  47. package/packages/datadog-instrumentations/src/http/server.js +9 -9
  48. package/packages/datadog-instrumentations/src/ioredis.js +16 -12
  49. package/packages/datadog-instrumentations/src/jest.js +382 -81
  50. package/packages/datadog-instrumentations/src/kafkajs.js +165 -174
  51. package/packages/datadog-instrumentations/src/knex.js +17 -17
  52. package/packages/datadog-instrumentations/src/koa.js +12 -12
  53. package/packages/datadog-instrumentations/src/ldapjs.js +5 -5
  54. package/packages/datadog-instrumentations/src/light-my-request.js +2 -2
  55. package/packages/datadog-instrumentations/src/limitd-client.js +4 -4
  56. package/packages/datadog-instrumentations/src/lodash.js +4 -4
  57. package/packages/datadog-instrumentations/src/mariadb.js +13 -13
  58. package/packages/datadog-instrumentations/src/memcached.js +2 -2
  59. package/packages/datadog-instrumentations/src/microgateway-core.js +2 -2
  60. package/packages/datadog-instrumentations/src/mocha/common.js +3 -3
  61. package/packages/datadog-instrumentations/src/mocha/main.js +85 -11
  62. package/packages/datadog-instrumentations/src/mocha/utils.js +133 -16
  63. package/packages/datadog-instrumentations/src/mocha/worker.js +7 -5
  64. package/packages/datadog-instrumentations/src/mongodb-core.js +42 -30
  65. package/packages/datadog-instrumentations/src/mongodb.js +5 -5
  66. package/packages/datadog-instrumentations/src/mongoose.js +21 -21
  67. package/packages/datadog-instrumentations/src/mquery.js +5 -5
  68. package/packages/datadog-instrumentations/src/multer.js +4 -4
  69. package/packages/datadog-instrumentations/src/mysql.js +16 -16
  70. package/packages/datadog-instrumentations/src/mysql2.js +4 -4
  71. package/packages/datadog-instrumentations/src/net.js +14 -8
  72. package/packages/datadog-instrumentations/src/nyc.js +5 -5
  73. package/packages/datadog-instrumentations/src/openai.js +19 -19
  74. package/packages/datadog-instrumentations/src/oracledb.js +6 -6
  75. package/packages/datadog-instrumentations/src/passport-utils.js +5 -5
  76. package/packages/datadog-instrumentations/src/pg.js +39 -25
  77. package/packages/datadog-instrumentations/src/pino.js +6 -10
  78. package/packages/datadog-instrumentations/src/playwright.js +445 -68
  79. package/packages/datadog-instrumentations/src/protobufjs.js +16 -16
  80. package/packages/datadog-instrumentations/src/redis.js +20 -12
  81. package/packages/datadog-instrumentations/src/restify.js +2 -2
  82. package/packages/datadog-instrumentations/src/router.js +12 -12
  83. package/packages/datadog-instrumentations/src/stripe.js +12 -12
  84. package/packages/datadog-instrumentations/src/vitest.js +107 -26
  85. package/packages/datadog-instrumentations/src/winston.js +4 -4
  86. package/packages/datadog-instrumentations/src/ws.js +7 -7
  87. package/packages/datadog-plugin-apollo/src/gateway/request.js +1 -21
  88. package/packages/datadog-plugin-aws-sdk/src/base.js +70 -28
  89. package/packages/datadog-plugin-aws-sdk/src/services/cloudwatchlogs.js +1 -1
  90. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +20 -13
  91. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +46 -36
  92. package/packages/datadog-plugin-aws-sdk/src/services/lambda.js +34 -23
  93. package/packages/datadog-plugin-aws-sdk/src/services/redshift.js +1 -1
  94. package/packages/datadog-plugin-aws-sdk/src/services/s3.js +1 -1
  95. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +14 -15
  96. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +74 -55
  97. package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +20 -18
  98. package/packages/datadog-plugin-aws-sdk/src/util.js +22 -0
  99. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +6 -6
  100. package/packages/datadog-plugin-couchbase/src/index.js +58 -52
  101. package/packages/datadog-plugin-cucumber/src/index.js +5 -0
  102. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +215 -26
  103. package/packages/datadog-plugin-cypress/src/support.js +13 -1
  104. package/packages/datadog-plugin-electron/src/index.js +17 -0
  105. package/packages/datadog-plugin-electron/src/ipc.js +143 -0
  106. package/packages/datadog-plugin-electron/src/net.js +82 -0
  107. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +1 -5
  108. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +27 -18
  109. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +3 -1
  110. package/packages/datadog-plugin-graphql/src/execute.js +6 -28
  111. package/packages/datadog-plugin-graphql/src/resolve.js +30 -35
  112. package/packages/datadog-plugin-graphql/src/tools/signature.js +32 -7
  113. package/packages/datadog-plugin-graphql/src/tools/transforms.js +118 -100
  114. package/packages/datadog-plugin-graphql/src/utils.js +29 -0
  115. package/packages/datadog-plugin-grpc/src/client.js +6 -7
  116. package/packages/datadog-plugin-grpc/src/util.js +57 -22
  117. package/packages/datadog-plugin-http/src/client.js +3 -7
  118. package/packages/datadog-plugin-jest/src/index.js +92 -50
  119. package/packages/datadog-plugin-jest/src/util.js +1 -2
  120. package/packages/datadog-plugin-mocha/src/index.js +5 -0
  121. package/packages/datadog-plugin-mongodb-core/src/index.js +36 -69
  122. package/packages/datadog-plugin-mysql/src/index.js +1 -1
  123. package/packages/datadog-plugin-openai/src/services.js +2 -1
  124. package/packages/datadog-plugin-openai/src/tracing.js +12 -23
  125. package/packages/datadog-plugin-pg/src/index.js +3 -3
  126. package/packages/datadog-plugin-playwright/src/index.js +5 -1
  127. package/packages/datadog-plugin-redis/src/index.js +18 -23
  128. package/packages/datadog-plugin-vitest/src/index.js +8 -1
  129. package/packages/datadog-shimmer/src/shimmer.js +7 -1
  130. package/packages/dd-trace/src/aiguard/index.js +3 -1
  131. package/packages/dd-trace/src/aiguard/sdk.js +36 -30
  132. package/packages/dd-trace/src/aiguard/tags.js +20 -11
  133. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-rules.js +1 -1
  134. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-secret-rules.js +81 -81
  135. package/packages/dd-trace/src/appsec/iast/security-controls/index.js +2 -2
  136. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugins/kafka.js +2 -2
  137. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +4 -4
  138. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +2 -2
  139. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +2 -0
  140. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +1 -3
  141. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +83 -48
  142. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +1 -1
  143. package/packages/dd-trace/src/appsec/index.js +21 -24
  144. package/packages/dd-trace/src/appsec/reporter.js +3 -1
  145. package/packages/dd-trace/src/appsec/rule_manager.js +4 -2
  146. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +31 -16
  147. package/packages/dd-trace/src/azure_metadata.js +17 -6
  148. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +4 -4
  149. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +4 -2
  150. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +6 -4
  151. package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +1 -1
  152. package/packages/dd-trace/src/config/defaults.js +3 -14
  153. package/packages/dd-trace/src/config/generated-config-types.d.ts +3 -1
  154. package/packages/dd-trace/src/config/git_properties.js +2 -2
  155. package/packages/dd-trace/src/config/helper.js +4 -0
  156. package/packages/dd-trace/src/config/index.js +2 -2
  157. package/packages/dd-trace/src/config/major-overrides.js +98 -0
  158. package/packages/dd-trace/src/config/parsers.js +7 -1
  159. package/packages/dd-trace/src/config/supported-configurations.json +51 -38
  160. package/packages/dd-trace/src/datastreams/checkpointer.js +2 -2
  161. package/packages/dd-trace/src/datastreams/index.js +2 -1
  162. package/packages/dd-trace/src/datastreams/manager.js +1 -1
  163. package/packages/dd-trace/src/datastreams/processor.js +3 -4
  164. package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +2 -2
  165. package/packages/dd-trace/src/debugger/devtools_client/snapshot-pruner.js +1 -0
  166. package/packages/dd-trace/src/debugger/devtools_client/source-maps.js +1 -1
  167. package/packages/dd-trace/src/debugger/devtools_client/state.js +2 -1
  168. package/packages/dd-trace/src/debugger/index.js +7 -7
  169. package/packages/dd-trace/src/dogstatsd.js +2 -2
  170. package/packages/dd-trace/src/encode/0.4.js +748 -232
  171. package/packages/dd-trace/src/encode/0.5.js +47 -10
  172. package/packages/dd-trace/src/encode/agentless-json.js +1 -1
  173. package/packages/dd-trace/src/exporter.js +2 -0
  174. package/packages/dd-trace/src/exporters/agent/index.js +2 -1
  175. package/packages/dd-trace/src/exporters/agentless/index.js +3 -2
  176. package/packages/dd-trace/src/exporters/agentless/writer.js +2 -2
  177. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +2 -1
  178. package/packages/dd-trace/src/exporters/common/request.js +1 -1
  179. package/packages/dd-trace/src/exporters/electron/index.js +49 -0
  180. package/packages/dd-trace/src/external-logger/src/index.js +2 -1
  181. package/packages/dd-trace/src/git_metadata.js +10 -8
  182. package/packages/dd-trace/src/lambda/handler-paths.js +52 -0
  183. package/packages/dd-trace/src/lambda/index.js +62 -14
  184. package/packages/dd-trace/src/lambda/runtime/patch.js +21 -46
  185. package/packages/dd-trace/src/llmobs/index.js +13 -2
  186. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -2
  187. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +45 -15
  188. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +6 -3
  189. package/packages/dd-trace/src/llmobs/sdk.js +24 -26
  190. package/packages/dd-trace/src/llmobs/span_processor.js +25 -5
  191. package/packages/dd-trace/src/llmobs/util.js +1 -0
  192. package/packages/dd-trace/src/llmobs/writers/base.js +2 -1
  193. package/packages/dd-trace/src/msgpack/chunk.js +6 -3
  194. package/packages/dd-trace/src/openfeature/noop.js +40 -36
  195. package/packages/dd-trace/src/openfeature/writers/base.js +2 -1
  196. package/packages/dd-trace/src/openfeature/writers/exposures.js +33 -52
  197. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +2 -1
  198. package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +1 -2
  199. package/packages/dd-trace/src/opentelemetry/tracer.js +0 -22
  200. package/packages/dd-trace/src/opentracing/propagation/text_map.js +20 -9
  201. package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +2 -11
  202. package/packages/dd-trace/src/payload-tagging/config/index.js +2 -2
  203. package/packages/dd-trace/src/plugins/ci_plugin.js +49 -4
  204. package/packages/dd-trace/src/plugins/database.js +54 -12
  205. package/packages/dd-trace/src/plugins/index.js +1 -0
  206. package/packages/dd-trace/src/plugins/plugin.js +2 -4
  207. package/packages/dd-trace/src/plugins/util/ci.js +9 -9
  208. package/packages/dd-trace/src/plugins/util/git-cache.js +23 -23
  209. package/packages/dd-trace/src/plugins/util/stacktrace.js +2 -2
  210. package/packages/dd-trace/src/plugins/util/test.js +56 -12
  211. package/packages/dd-trace/src/plugins/util/url.js +1 -3
  212. package/packages/dd-trace/src/plugins/util/user-provided-git.js +18 -16
  213. package/packages/dd-trace/src/plugins/util/web.js +5 -7
  214. package/packages/dd-trace/src/priority_sampler.js +1 -1
  215. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  216. package/packages/dd-trace/src/profiling/profilers/events.js +3 -23
  217. package/packages/dd-trace/src/profiling/profilers/wall.js +5 -6
  218. package/packages/dd-trace/src/profiling/ssi-heuristics.js +1 -1
  219. package/packages/dd-trace/src/rate_limiter.js +1 -1
  220. package/packages/dd-trace/src/remote_config/scheduler.js +1 -1
  221. package/packages/dd-trace/src/ritm.js +2 -1
  222. package/packages/dd-trace/src/runtime_metrics/index.js +2 -2
  223. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +5 -8
  224. package/packages/dd-trace/src/scope.js +3 -10
  225. package/packages/dd-trace/src/serverless.js +6 -6
  226. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +27 -1
  227. package/packages/dd-trace/src/service-naming/schemas/v0/web.js +4 -0
  228. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +24 -0
  229. package/packages/dd-trace/src/service-naming/schemas/v1/web.js +4 -0
  230. package/packages/dd-trace/src/span_stats.js +1 -1
  231. package/packages/dd-trace/src/telemetry/dependencies.js +1 -1
  232. package/packages/dd-trace/src/telemetry/endpoints.js +1 -1
  233. package/packages/dd-trace/src/telemetry/telemetry.js +2 -2
  234. package/packages/dd-trace/src/tracer.js +7 -7
  235. package/packages/dd-trace/src/lambda/runtime/ritm.js +0 -133
@@ -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,6 +13,8 @@ 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,
@@ -68,6 +71,7 @@ let remainingTestsByFile = {}
68
71
  let isKnownTestsEnabled = false
69
72
  let isEarlyFlakeDetectionEnabled = false
70
73
  let earlyFlakeDetectionNumRetries = 0
74
+ let earlyFlakeDetectionSlowTestRetries = {}
71
75
  let isEarlyFlakeDetectionFaulty = false
72
76
  let earlyFlakeDetectionFaultyThreshold = 0
73
77
  let isFlakyTestRetriesEnabled = false
@@ -79,13 +83,23 @@ let testManagementTests = {}
79
83
  let isImpactedTestsEnabled = false
80
84
  let modifiedFiles = {}
81
85
  let quarantinedButNotAttemptToFixFqns = new Set()
86
+ let testsReportedInGenerateSummary = new Set()
82
87
  const newTestsWithDynamicNames = new Set()
83
88
  const attemptToFixExecutions = new Map()
84
89
  const loggedAttemptToFixTests = new Set()
90
+ const efdManagedTestKeys = new Set()
91
+ const efdRetryCountByTestKey = new Map()
92
+ const efdRetryCountRequestsByTestKey = new Map()
93
+ const efdRetryTestsById = new Map()
94
+ const efdScheduledOriginalTestKeys = new Set()
95
+ const efdStartedOriginalTestKeys = new Set()
96
+ const efdSlowAbortedTests = new Set()
85
97
  let rootDir = ''
86
98
  let sessionProjects = []
87
99
 
88
100
  const MINIMUM_SUPPORTED_VERSION_RANGE_EFD = '>=1.38.0' // TODO: remove this once we drop support for v5
101
+ const EFD_RETRY_COUNT_REQUEST = 'ddEfdRetryCountRequest'
102
+ const EFD_RETRY_COUNT_RESPONSE = 'ddEfdRetryCountResponse'
89
103
 
90
104
  function isValidKnownTests (receivedKnownTests) {
91
105
  return !!receivedKnownTests.playwright
@@ -96,6 +110,222 @@ function getTestFullyQualifiedName (test) {
96
110
  return `${test._requireFile} ${fullname}`
97
111
  }
98
112
 
113
+ /**
114
+ * @param {object} test
115
+ * @returns {string|undefined}
116
+ */
117
+ function getTestProjectKey (test) {
118
+ const { _projectIndex, _projectId } = test
119
+ if (_projectIndex !== undefined) {
120
+ return `index:${_projectIndex}`
121
+ }
122
+ if (_projectId !== undefined) {
123
+ return `id:${_projectId}`
124
+ }
125
+
126
+ const projectSuite = getSuiteType(test, 'project')
127
+ const projectName = projectSuite?._fullProject?.project?.name ||
128
+ projectSuite?._fullProject?.name ||
129
+ projectSuite?.title
130
+ if (projectName) {
131
+ return `name:${projectName}`
132
+ }
133
+ }
134
+
135
+ /**
136
+ * @param {object} test
137
+ * @returns {number|undefined}
138
+ */
139
+ function getTestEfdRepeatEachIndex (test) {
140
+ if (Object.hasOwn(test, '_ddEfdOriginalRepeatEachIndex')) {
141
+ return test._ddEfdOriginalRepeatEachIndex
142
+ }
143
+ return test.repeatEachIndex
144
+ }
145
+
146
+ /**
147
+ * @param {object} test
148
+ * @returns {string|undefined}
149
+ */
150
+ function getTestRepeatEachKey (test) {
151
+ const repeatEachIndex = getTestEfdRepeatEachIndex(test)
152
+ if (repeatEachIndex !== undefined) {
153
+ return `repeat:${repeatEachIndex}`
154
+ }
155
+ }
156
+
157
+ /**
158
+ * @param {object} test
159
+ * @returns {string}
160
+ */
161
+ function getTestEfdKey (test) {
162
+ const projectKey = getTestProjectKey(test)
163
+ const repeatEachKey = getTestRepeatEachKey(test)
164
+ const testFqn = getTestFullyQualifiedName(test)
165
+ return [projectKey, repeatEachKey, testFqn].filter(Boolean).join(' ')
166
+ }
167
+
168
+ function getConfiguredEfdRetryCount () {
169
+ if (!earlyFlakeDetectionSlowTestRetries || !Object.keys(earlyFlakeDetectionSlowTestRetries).length) {
170
+ return earlyFlakeDetectionNumRetries
171
+ }
172
+ return getMaxEfdRetryCount(earlyFlakeDetectionSlowTestRetries)
173
+ }
174
+
175
+ function markEfdManagedTest (test) {
176
+ test._ddIsEfdManagedTest = true
177
+ test._ddEfdSlowTestRetries = earlyFlakeDetectionSlowTestRetries
178
+ efdManagedTestKeys.add(getTestEfdKey(test))
179
+ }
180
+
181
+ function markEfdRetryTest (test, retryIndex, originalTest) {
182
+ test._ddIsEfdRetry = true
183
+ test._ddEfdRetryIndex = retryIndex
184
+ if (originalTest) {
185
+ test._ddEfdOriginalRepeatEachIndex = getTestEfdRepeatEachIndex(originalTest)
186
+ }
187
+ }
188
+
189
+ function registerEfdRetryTest (test) {
190
+ if (!test._ddIsEfdRetry) {
191
+ return
192
+ }
193
+
194
+ efdRetryTestsById.set(test.id, {
195
+ retryIndex: test._ddEfdRetryIndex,
196
+ testEfdKey: getTestEfdKey(test),
197
+ })
198
+ }
199
+
200
+ function getTestEfdSlowTestRetries (test) {
201
+ return test._ddEfdSlowTestRetries || earlyFlakeDetectionSlowTestRetries
202
+ }
203
+
204
+ function isTestEfdManaged (test) {
205
+ return !!test._ddIsEfdManagedTest || (
206
+ (test._ddIsNew || test._ddIsModified) &&
207
+ !test._ddIsAttemptToFix &&
208
+ isEarlyFlakeDetectionEnabled
209
+ )
210
+ }
211
+
212
+ function getFileSuiteRepeatEachIndex (fileSuite) {
213
+ const test = fileSuite.allTests()[0]
214
+ return test ? getTestEfdRepeatEachIndex(test) || 0 : 0
215
+ }
216
+
217
+ function getEfdRetryRepeatEachIndex (fileSuite, projectSuite, retryIndex, retryCount) {
218
+ const nativeRepeatEach = projectSuite._fullProject?.project?.repeatEach || 1
219
+ const originalRepeatEachIndex = getFileSuiteRepeatEachIndex(fileSuite)
220
+ return nativeRepeatEach + (originalRepeatEachIndex * retryCount) + retryIndex - 1
221
+ }
222
+
223
+ function getEfdRetryCountForTest (test) {
224
+ return efdRetryCountByTestKey.get(getTestEfdKey(test)) ?? getConfiguredEfdRetryCount()
225
+ }
226
+
227
+ function setEfdRetryCountForTest (test, retryCount) {
228
+ const testEfdKey = getTestEfdKey(test)
229
+ efdRetryCountByTestKey.set(testEfdKey, retryCount)
230
+
231
+ const requests = efdRetryCountRequestsByTestKey.get(testEfdKey)
232
+ if (requests) {
233
+ efdRetryCountRequestsByTestKey.delete(testEfdKey)
234
+ for (const resolveRequest of requests) {
235
+ resolveRequest(retryCount)
236
+ }
237
+ }
238
+ }
239
+
240
+ function sendEfdRetryCountToWorker (workerProcess, testId, retryIndex, retryCount) {
241
+ workerProcess.send({
242
+ type: EFD_RETRY_COUNT_RESPONSE,
243
+ testId,
244
+ isEfdRetry: retryIndex !== undefined,
245
+ retryIndex,
246
+ retryCount,
247
+ })
248
+ }
249
+
250
+ function sendEfdRetryCountToWorkerWhenAvailable (workerProcess, testId) {
251
+ const efdRetryTest = efdRetryTestsById.get(testId)
252
+ if (!efdRetryTest) {
253
+ sendEfdRetryCountToWorker(workerProcess, testId)
254
+ return
255
+ }
256
+
257
+ const { retryIndex, testEfdKey } = efdRetryTest
258
+
259
+ if (!testEfdKey || !efdManagedTestKeys.has(testEfdKey)) {
260
+ sendEfdRetryCountToWorker(workerProcess, testId)
261
+ return
262
+ }
263
+
264
+ const retryCount = efdRetryCountByTestKey.get(testEfdKey)
265
+ if (retryCount !== undefined) {
266
+ sendEfdRetryCountToWorker(workerProcess, testId, retryIndex, retryCount)
267
+ return
268
+ }
269
+
270
+ if (!efdStartedOriginalTestKeys.has(testEfdKey) && !efdScheduledOriginalTestKeys.has(testEfdKey)) {
271
+ sendEfdRetryCountToWorker(workerProcess, testId, retryIndex, 0)
272
+ return
273
+ }
274
+
275
+ if (!efdRetryCountRequestsByTestKey.has(testEfdKey)) {
276
+ efdRetryCountRequestsByTestKey.set(testEfdKey, [])
277
+ }
278
+ efdRetryCountRequestsByTestKey.get(testEfdKey).push((retryCount) => {
279
+ sendEfdRetryCountToWorker(workerProcess, testId, retryIndex, retryCount)
280
+ })
281
+ }
282
+
283
+ /**
284
+ * @param {object} test
285
+ * @returns {boolean}
286
+ */
287
+ function shouldRequestEfdRetryCount (test) {
288
+ // The main process remains the source of truth. repeatEachIndex is only used as
289
+ // a cheap worker-side filter so first executions do not block on coordination.
290
+ return test._ddIsEfdRetry || test.repeatEachIndex > 0
291
+ }
292
+
293
+ function waitForEfdRetryCount (test) {
294
+ if (!process.send || !shouldRequestEfdRetryCount(test)) {
295
+ return Promise.resolve()
296
+ }
297
+
298
+ const testEfdKey = getTestEfdKey(test)
299
+ return new Promise(resolve => {
300
+ const messageHandler = (message) => {
301
+ if (message?.type === EFD_RETRY_COUNT_RESPONSE && message.testId === test.id) {
302
+ if (message.isEfdRetry) {
303
+ test._ddIsEfdRetry = true
304
+ test._ddEfdRetryIndex = message.retryIndex
305
+ test._ddEfdRetryCount = message.retryCount
306
+ efdRetryCountByTestKey.set(testEfdKey, message.retryCount)
307
+ }
308
+ process.removeListener('message', messageHandler)
309
+ resolve()
310
+ }
311
+ }
312
+
313
+ process.on('message', messageHandler)
314
+ process.send({
315
+ type: EFD_RETRY_COUNT_REQUEST,
316
+ testId: test.id,
317
+ })
318
+ })
319
+ }
320
+
321
+ function shouldSkipEfdRetry (test) {
322
+ if (!test._ddIsEfdRetry) {
323
+ return false
324
+ }
325
+ const retryCount = test._ddEfdRetryCount ?? efdRetryCountByTestKey.get(getTestEfdKey(test))
326
+ return retryCount !== undefined && test._ddEfdRetryIndex > retryCount
327
+ }
328
+
99
329
  function getTestProperties (test) {
100
330
  const testName = getTestFullname(test)
101
331
  const testSuite = getTestSuitePath(test._requireFile, rootDir)
@@ -124,14 +354,17 @@ function getSuiteType (test, type) {
124
354
  }
125
355
 
126
356
  // Copy of Suite#_deepClone but with a function to filter tests
127
- function deepCloneSuite (suite, filterTest, tags = []) {
357
+ function deepCloneSuite (suite, filterTest, tags = [], configureCopiedTest) {
128
358
  const copy = suite._clone()
129
359
  for (const entry of suite._entries) {
130
360
  if (entry.constructor.name === 'Suite') {
131
- copy._addSuite(deepCloneSuite(entry, filterTest, tags))
361
+ copy._addSuite(deepCloneSuite(entry, filterTest, tags, configureCopiedTest))
132
362
  } else {
133
363
  if (filterTest(entry)) {
134
364
  const copiedTest = entry._clone()
365
+ if (configureCopiedTest) {
366
+ configureCopiedTest(copiedTest, entry)
367
+ }
135
368
  for (const tag of tags) {
136
369
  const resolvedTag = typeof tag === 'function' ? tag(entry) : tag
137
370
 
@@ -302,6 +535,7 @@ function getFinalStatus ({
302
535
  isAttemptToFix,
303
536
  hasFailedAllRetries,
304
537
  hasFailedAttemptToFixRetries,
538
+ hasPassedAnyEfdAttempt,
305
539
  testStatus,
306
540
  }) {
307
541
  if (!isFinalExecution) {
@@ -310,9 +544,12 @@ function getFinalStatus ({
310
544
  if (isDisabled || isQuarantined || testStatus === 'skip') {
311
545
  return 'skip'
312
546
  }
313
- if (isAtrRetry || isEfdManagedTest) {
547
+ if (isAtrRetry) {
314
548
  return hasFailedAllRetries ? 'fail' : 'pass'
315
549
  }
550
+ if (isEfdManagedTest) {
551
+ return hasPassedAnyEfdAttempt ? 'pass' : 'fail'
552
+ }
316
553
  if (isAttemptToFix) {
317
554
  return hasFailedAttemptToFixRetries ? 'fail' : 'pass'
318
555
  }
@@ -349,6 +586,14 @@ function testBeginHandler (test, browserName, shouldCreateTestSpan) {
349
586
  if (_type === 'beforeAll' || _type === 'afterAll') {
350
587
  return
351
588
  }
589
+ if (shouldSkipEfdRetry(test)) {
590
+ test._ddShouldSkipEfdRetry = true
591
+ return
592
+ }
593
+ test._ddStartTime = performance.now()
594
+ if (isTestEfdManaged(test) && !test._ddIsEfdRetry) {
595
+ efdStartedOriginalTestKeys.add(getTestEfdKey(test))
596
+ }
352
597
  // this means that a skipped test is being handled
353
598
  if (!remainingTestsByFile[testSuiteAbsolutePath].length) {
354
599
  return
@@ -390,6 +635,45 @@ function testBeginHandler (test, browserName, shouldCreateTestSpan) {
390
635
  }
391
636
  }
392
637
 
638
+ function finishTestSuiteIfDone (testSuiteAbsolutePath, projects) {
639
+ if (!shouldFinishTestSuite(testSuiteAbsolutePath)) {
640
+ return
641
+ }
642
+
643
+ const skippedTests = remainingTestsByFile[testSuiteAbsolutePath]
644
+ .filter(test => test.expectedStatus === 'skipped')
645
+
646
+ for (const test of skippedTests) {
647
+ const browserName = getBrowserNameFromProjects(projects, test)
648
+ testSkipCh.publish({
649
+ testName: getTestFullname(test),
650
+ testSuiteAbsolutePath,
651
+ testSourceFileAbsolutePath: test.location.file,
652
+ testSourceLine: test.location.line,
653
+ browserName,
654
+ isNew: test._ddIsNew,
655
+ isDisabled: test._ddIsDisabled,
656
+ isModified: test._ddIsModified,
657
+ isQuarantined: test._ddIsQuarantined,
658
+ })
659
+ }
660
+ remainingTestsByFile[testSuiteAbsolutePath] = []
661
+
662
+ const testStatuses = testSuiteToTestStatuses.get(testSuiteAbsolutePath)
663
+ let testSuiteStatus = 'pass'
664
+ if (testStatuses?.includes('fail')) {
665
+ testSuiteStatus = 'fail'
666
+ } else if (testStatuses?.every(status => status === 'skip')) {
667
+ testSuiteStatus = 'skip'
668
+ }
669
+
670
+ const suiteError = getTestSuiteError(testSuiteAbsolutePath)
671
+ const testSuiteCtx = testSuiteToCtx.get(testSuiteAbsolutePath)
672
+ if (testSuiteCtx) {
673
+ testSuiteFinishCh.publish({ status: testSuiteStatus, error: suiteError, ...testSuiteCtx.currentStore })
674
+ }
675
+ }
676
+
393
677
  function testEndHandler ({
394
678
  test,
395
679
  annotations,
@@ -419,11 +703,21 @@ function testEndHandler ({
419
703
  return
420
704
  }
421
705
 
706
+ if (test._ddShouldSkipEfdRetry || shouldSkipEfdRetry(test)) {
707
+ test._ddShouldSkipEfdRetry = true
708
+ remainingTestsByFile[testSuiteAbsolutePath] = remainingTestsByFile[testSuiteAbsolutePath]
709
+ .filter(currentTest => currentTest !== test)
710
+ finishTestSuiteIfDone(testSuiteAbsolutePath, projects)
711
+ return
712
+ }
713
+
714
+ const isEfdManagedTest = isTestEfdManaged(test)
422
715
  const testFqn = getTestFullyQualifiedName(test)
423
- const testStatuses = testsToTestStatuses.get(testFqn) || []
716
+ const testStatusKey = isEfdManagedTest ? getTestEfdKey(test) : testFqn
717
+ const testStatuses = testsToTestStatuses.get(testStatusKey) || []
424
718
 
425
719
  if (testStatuses.length === 0) {
426
- testsToTestStatuses.set(testFqn, [testStatus])
720
+ testsToTestStatuses.set(testStatusKey, [testStatus])
427
721
  if (test._ddIsNew && DYNAMIC_NAME_RE.test(getTestFullname(test))) {
428
722
  newTestsWithDynamicNames.add(`${getTestSuitePath(test._requireFile, rootDir)} › ${getTestFullname(test)}`)
429
723
  }
@@ -431,6 +725,17 @@ function testEndHandler ({
431
725
  testStatuses.push(testStatus)
432
726
  }
433
727
 
728
+ const testEfdKey = getTestEfdKey(test)
729
+ if (isEfdManagedTest && !test._ddIsEfdRetry && !efdRetryCountByTestKey.has(testEfdKey)) {
730
+ const testResult = results.at(-1)
731
+ const duration = testResult?.duration > 0 ? testResult.duration : performance.now() - test._ddStartTime
732
+ const retryCount = getEfdRetryCount(duration, getTestEfdSlowTestRetries(test))
733
+ setEfdRetryCountForTest(test, retryCount)
734
+ if (retryCount === 0) {
735
+ efdSlowAbortedTests.add(testEfdKey)
736
+ }
737
+ }
738
+
434
739
  const testProperties = getTestProperties(test)
435
740
 
436
741
  if (testProperties.attemptToFix) {
@@ -459,9 +764,10 @@ function testEndHandler ({
459
764
  }
460
765
 
461
766
  // Check if all EFD retries failed
462
- if (testStatuses.length === earlyFlakeDetectionNumRetries + 1 &&
767
+ const efdRetryCount = getEfdRetryCountForTest(test)
768
+ if (efdRetryCount > 0 && testStatuses.length === efdRetryCount + 1 &&
463
769
  (test._ddIsNew || test._ddIsModified) &&
464
- test._ddIsEfdRetry &&
770
+ isEarlyFlakeDetectionEnabled &&
465
771
  testStatuses.every(status => status === 'fail')) {
466
772
  test._ddHasFailedAllRetries = true
467
773
  }
@@ -479,9 +785,6 @@ function testEndHandler ({
479
785
  if (shouldCreateTestSpan) {
480
786
  const testResult = results.at(-1)
481
787
  const testCtx = testToCtx.get(test)
482
- const isEfdManagedTest = (test._ddIsNew || test._ddIsModified) &&
483
- !test._ddIsAttemptToFix &&
484
- isEarlyFlakeDetectionEnabled
485
788
  const isAtrRetry = testResult?.retry > 0 &&
486
789
  isFlakyTestRetriesEnabled &&
487
790
  !test._ddIsAttemptToFix &&
@@ -496,6 +799,7 @@ function testEndHandler ({
496
799
  isAttemptToFix: test._ddIsAttemptToFix,
497
800
  hasFailedAllRetries: test._ddHasFailedAllRetries,
498
801
  hasFailedAttemptToFixRetries: test._ddHasFailedAttemptToFixRetries,
802
+ hasPassedAnyEfdAttempt: testStatuses.includes('pass'),
499
803
  testStatus,
500
804
  })
501
805
 
@@ -519,6 +823,7 @@ function testEndHandler ({
519
823
  isAtrRetry,
520
824
  isModified: test._ddIsModified,
521
825
  finalStatus,
826
+ earlyFlakeAbortReason: efdSlowAbortedTests.has(testEfdKey) ? 'slow' : undefined,
522
827
  ...testCtx.currentStore,
523
828
  })
524
829
  }
@@ -539,45 +844,47 @@ function testEndHandler ({
539
844
  .filter(currentTest => currentTest !== test)
540
845
  }
541
846
 
542
- if (shouldFinishTestSuite(testSuiteAbsolutePath)) {
543
- const skippedTests = remainingTestsByFile[testSuiteAbsolutePath]
544
- .filter(test => test.expectedStatus === 'skipped')
847
+ finishTestSuiteIfDone(testSuiteAbsolutePath, projects)
848
+ }
849
+
850
+ function dispatcherRunWrapper (run) {
851
+ return function (...args) {
852
+ remainingTestsByFile = getTestsBySuiteFromTestsById(this._testById)
853
+ return run.apply(this, args)
854
+ }
855
+ }
856
+
857
+ function deferEfdRetryGroups (testGroups) {
858
+ const groupsWithOriginalTests = []
859
+ const efdRetryOnlyGroups = []
545
860
 
546
- for (const test of skippedTests) {
547
- const browserName = getBrowserNameFromProjects(projects, test)
548
- testSkipCh.publish({
549
- testName: getTestFullname(test),
550
- testSuiteAbsolutePath,
551
- testSourceFileAbsolutePath: test.location.file,
552
- testSourceLine: test.location.line,
553
- browserName,
554
- isNew: test._ddIsNew,
555
- isDisabled: test._ddIsDisabled,
556
- isModified: test._ddIsModified,
557
- isQuarantined: test._ddIsQuarantined,
558
- })
861
+ for (const group of testGroups) {
862
+ const originalTests = []
863
+ const efdRetryTests = []
864
+
865
+ for (const test of group.tests) {
866
+ if (test._ddIsEfdRetry) {
867
+ efdRetryTests.push(test)
868
+ } else {
869
+ originalTests.push(test)
870
+ if (isTestEfdManaged(test)) {
871
+ efdScheduledOriginalTestKeys.add(getTestEfdKey(test))
872
+ }
873
+ }
559
874
  }
560
- remainingTestsByFile[testSuiteAbsolutePath] = []
561
875
 
562
- const testStatuses = testSuiteToTestStatuses.get(testSuiteAbsolutePath)
563
- let testSuiteStatus = 'pass'
564
- if (testStatuses.includes('fail')) {
565
- testSuiteStatus = 'fail'
566
- } else if (testStatuses.every(status => status === 'skip')) {
567
- testSuiteStatus = 'skip'
876
+ if (efdRetryTests.length && originalTests.length) {
877
+ group.tests = [...originalTests, ...efdRetryTests]
568
878
  }
569
879
 
570
- const suiteError = getTestSuiteError(testSuiteAbsolutePath)
571
- const testSuiteCtx = testSuiteToCtx.get(testSuiteAbsolutePath)
572
- testSuiteFinishCh.publish({ status: testSuiteStatus, error: suiteError, ...testSuiteCtx.currentStore })
880
+ if (originalTests.length) {
881
+ groupsWithOriginalTests.push(group)
882
+ } else {
883
+ efdRetryOnlyGroups.push(group)
884
+ }
573
885
  }
574
- }
575
886
 
576
- function dispatcherRunWrapper (run) {
577
- return function () {
578
- remainingTestsByFile = getTestsBySuiteFromTestsById(this._testById)
579
- return run.apply(this, arguments)
580
- }
887
+ return [...groupsWithOriginalTests, ...efdRetryOnlyGroups]
581
888
  }
582
889
 
583
890
  function dispatcherRunWrapperNew (run) {
@@ -592,21 +899,26 @@ function dispatcherRunWrapperNew (run) {
592
899
  testGroups = testGroups.filter(group => group.tests.length > 0)
593
900
  }
594
901
 
902
+ if (isEarlyFlakeDetectionEnabled) {
903
+ testGroups = deferEfdRetryGroups(testGroups)
904
+ }
905
+
595
906
  if (!this._allTests) {
596
907
  // Removed in https://github.com/microsoft/playwright/commit/1e52c37b254a441cccf332520f60225a5acc14c7
597
908
  // Not available from >=1.44.0
598
909
  this._ddAllTests = testGroups.flatMap(g => g.tests)
599
910
  }
600
911
  remainingTestsByFile = getTestsBySuiteFromTestGroups(testGroups)
912
+ arguments[0] = testGroups
601
913
  return run.apply(this, arguments)
602
914
  }
603
915
  }
604
916
 
605
917
  function dispatcherHook (dispatcherExport) {
606
918
  shimmer.wrap(dispatcherExport.Dispatcher.prototype, 'run', dispatcherRunWrapper)
607
- shimmer.wrap(dispatcherExport.Dispatcher.prototype, '_createWorker', createWorker => function () {
919
+ shimmer.wrap(dispatcherExport.Dispatcher.prototype, '_createWorker', createWorker => function (...args) {
608
920
  const dispatcher = this
609
- const worker = createWorker.apply(this, arguments)
921
+ const worker = createWorker.apply(this, args)
610
922
  const projects = getProjectsFromDispatcher(dispatcher)
611
923
  sessionProjects = projects
612
924
 
@@ -637,7 +949,6 @@ function dispatcherHook (dispatcherExport) {
637
949
  )
638
950
  }
639
951
  })
640
-
641
952
  return worker
642
953
  })
643
954
  return dispatcherExport
@@ -645,9 +956,9 @@ function dispatcherHook (dispatcherExport) {
645
956
 
646
957
  function dispatcherHookNew (dispatcherExport, runWrapper) {
647
958
  shimmer.wrap(dispatcherExport.Dispatcher.prototype, 'run', runWrapper)
648
- shimmer.wrap(dispatcherExport.Dispatcher.prototype, '_createWorker', createWorker => function () {
959
+ shimmer.wrap(dispatcherExport.Dispatcher.prototype, '_createWorker', createWorker => function (...args) {
649
960
  const dispatcher = this
650
- const worker = createWorker.apply(this, arguments)
961
+ const worker = createWorker.apply(this, args)
651
962
  const projects = getProjectsFromDispatcher(dispatcher)
652
963
  sessionProjects = projects
653
964
 
@@ -661,8 +972,11 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
661
972
  const test = getTestByTestId(dispatcher, testId)
662
973
 
663
974
  const isTimeout = status === 'timedOut'
664
- const shouldCreateTestSpan = test.expectedStatus === 'skipped'
665
975
  const testStatus = STATUS_TO_TEST_STATUS[status]
976
+ const shouldCreateTestSpan = test.expectedStatus === 'skipped'
977
+ if (shouldCreateTestSpan && !testToCtx.has(test)) {
978
+ testBeginHandler(test, getBrowserNameFromProjects(projects, test), true)
979
+ }
666
980
  testEndHandler(
667
981
  {
668
982
  test,
@@ -686,14 +1000,11 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
686
1000
  // above) and mark the execution final once the count reaches the expected total.
687
1001
  // This mirrors how ATF finality is detected and centralizes the decision in the
688
1002
  // main process, so workers only need to act on the _ddIsFinalExecution flag.
689
- const isEfdManagedTest = (test._ddIsNew || test._ddIsModified) &&
690
- !test._ddIsAttemptToFix &&
691
- isEarlyFlakeDetectionEnabled
1003
+ const isEfdManagedTest = isTestEfdManaged(test)
692
1004
  let isFinalExecution
693
1005
  if (isEfdManagedTest) {
694
- const testFqn = getTestFullyQualifiedName(test)
695
- const efdTestStatuses = testsToTestStatuses.get(testFqn) || []
696
- isFinalExecution = efdTestStatuses.length === earlyFlakeDetectionNumRetries + 1
1006
+ const efdTestStatuses = testsToTestStatuses.get(getTestEfdKey(test)) || []
1007
+ isFinalExecution = efdTestStatuses.length === getEfdRetryCountForTest(test) + 1
697
1008
  } else if (test._ddIsAttemptToFix) {
698
1009
  isFinalExecution = !!(test._ddHasPassedAttemptToFixRetries || test._ddHasFailedAttemptToFixRetries)
699
1010
  } else {
@@ -718,10 +1029,11 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
718
1029
  _ddIsModified: test._ddIsModified,
719
1030
  _ddIsFinalExecution: isFinalExecution,
720
1031
  _ddIsEfdManagedTest: isEfdManagedTest,
1032
+ _ddEarlyFlakeAbortReason: efdSlowAbortedTests.has(getTestEfdKey(test)) ? 'slow' : undefined,
1033
+ _ddHasPassedAnyEfdAttempt: (testsToTestStatuses.get(getTestEfdKey(test)) || []).includes('pass'),
721
1034
  },
722
1035
  })
723
1036
  })
724
-
725
1037
  return worker
726
1038
  })
727
1039
  return dispatcherExport
@@ -747,6 +1059,7 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
747
1059
  isKnownTestsEnabled = libraryConfig.isKnownTestsEnabled
748
1060
  isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
749
1061
  earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
1062
+ earlyFlakeDetectionSlowTestRetries = libraryConfig.earlyFlakeDetectionSlowTestRetries ?? {}
750
1063
  earlyFlakeDetectionFaultyThreshold = libraryConfig.earlyFlakeDetectionFaultyThreshold
751
1064
  isFlakyTestRetriesEnabled = libraryConfig.isFlakyTestRetriesEnabled
752
1065
  flakyTestRetriesCount = libraryConfig.flakyTestRetriesCount
@@ -835,15 +1148,16 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
835
1148
  // there were tests that did not go through `testBegin` or `testEnd`,
836
1149
  // because they were skipped
837
1150
  for (const test of tests) {
1151
+ const alreadyReported = testsReportedInGenerateSummary.has(test)
838
1152
  const browser = getBrowserNameFromProjects(projects, test)
839
- testBeginHandler(test, browser, true)
1153
+ testBeginHandler(test, browser, !alreadyReported)
840
1154
  testEndHandler({
841
1155
  test,
842
1156
  annotations: [],
843
1157
  testStatus: 'skip',
844
1158
  error: null,
845
1159
  isTimeout: false,
846
- shouldCreateTestSpan: true,
1160
+ shouldCreateTestSpan: !alreadyReported,
847
1161
  projects,
848
1162
  })
849
1163
  }
@@ -894,6 +1208,14 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
894
1208
  startedSuites = []
895
1209
  remainingTestsByFile = {}
896
1210
  quarantinedButNotAttemptToFixFqns = new Set()
1211
+ testsReportedInGenerateSummary = new Set()
1212
+ efdManagedTestKeys.clear()
1213
+ efdRetryCountByTestKey.clear()
1214
+ efdRetryCountRequestsByTestKey.clear()
1215
+ efdRetryTestsById.clear()
1216
+ efdScheduledOriginalTestKeys.clear()
1217
+ efdStartedOriginalTestKeys.clear()
1218
+ efdSlowAbortedTests.clear()
897
1219
 
898
1220
  // TODO: we can trick playwright into thinking the session passed by returning
899
1221
  // 'passed' here. We might be able to use this for both EFD and Test Management tests.
@@ -995,12 +1317,29 @@ addHook({
995
1317
  * - we execute `applyRepeatEachIndex` for each of these cloned file suites
996
1318
  * - we add the cloned file suites to the project suite
997
1319
  */
998
- function applyRetriesToTests (fileSuitesWithTestsToRetry, filterTest, tagsToApply, numRetries) {
1320
+ function applyRetriesToTests (
1321
+ fileSuitesWithTestsToRetry,
1322
+ filterTest,
1323
+ tagsToApply,
1324
+ numRetries,
1325
+ configureCopiedTest,
1326
+ getRetryRepeatEachIndex
1327
+ ) {
999
1328
  for (const [fileSuite, projectSuite] of fileSuitesWithTestsToRetry.entries()) {
1000
1329
  for (let repeatEachIndex = 1; repeatEachIndex <= numRetries; repeatEachIndex++) {
1001
- const copyFileSuite = deepCloneSuite(fileSuite, filterTest, tagsToApply)
1002
- applyRepeatEachIndex(projectSuite._fullProject, copyFileSuite, repeatEachIndex + 1)
1330
+ const copyFileSuite = deepCloneSuite(fileSuite, filterTest, tagsToApply, (copiedTest, originalTest) => {
1331
+ if (configureCopiedTest) {
1332
+ configureCopiedTest(copiedTest, originalTest, repeatEachIndex)
1333
+ }
1334
+ })
1335
+ const retryRepeatEachIndex = getRetryRepeatEachIndex
1336
+ ? getRetryRepeatEachIndex(fileSuite, projectSuite, repeatEachIndex, numRetries)
1337
+ : repeatEachIndex + 1
1338
+ applyRepeatEachIndex(projectSuite._fullProject, copyFileSuite, retryRepeatEachIndex)
1003
1339
  projectSuite._addSuite(copyFileSuite)
1340
+ for (const copiedTest of copyFileSuite.allTests()) {
1341
+ registerEfdRetryTest(copiedTest)
1342
+ }
1004
1343
  }
1005
1344
  }
1006
1345
  }
@@ -1085,6 +1424,7 @@ addHook({
1085
1424
  for (const impactedTest of impactedTests) {
1086
1425
  impactedTest._ddIsModified = true
1087
1426
  if (isEarlyFlakeDetectionEnabled && impactedTest.expectedStatus !== 'skipped') {
1427
+ markEfdManagedTest(impactedTest)
1088
1428
  const fileSuite = getSuiteType(impactedTest, 'file')
1089
1429
  if (!fileSuitesWithImpactedTestsToProjects.has(fileSuite)) {
1090
1430
  fileSuitesWithImpactedTestsToProjects.set(fileSuite, getSuiteType(impactedTest, 'project'))
@@ -1100,7 +1440,12 @@ addHook({
1100
1440
  '_ddIsEfdRetry',
1101
1441
  (test) => (isKnownTestsEnabled && isNewTest(test) ? '_ddIsNew' : null),
1102
1442
  ],
1103
- earlyFlakeDetectionNumRetries
1443
+ getConfiguredEfdRetryCount(),
1444
+ (copiedTest, originalTest, retryIndex) => {
1445
+ markEfdRetryTest(copiedTest, retryIndex, originalTest)
1446
+ markEfdManagedTest(copiedTest)
1447
+ },
1448
+ getEfdRetryRepeatEachIndex
1104
1449
  )
1105
1450
  }
1106
1451
 
@@ -1124,6 +1469,7 @@ addHook({
1124
1469
  if (isEarlyFlakeDetectionEnabled && newTest.expectedStatus !== 'skipped' && !newTest._ddIsModified) {
1125
1470
  // Prevent ATR or `--retries` from retrying new tests if EFD is enabled
1126
1471
  newTest.retries = 0
1472
+ markEfdManagedTest(newTest)
1127
1473
  const fileSuite = getSuiteType(newTest, 'file')
1128
1474
  if (!fileSuitesWithNewTestsToProjects.has(fileSuite)) {
1129
1475
  fileSuitesWithNewTestsToProjects.set(fileSuite, getSuiteType(newTest, 'project'))
@@ -1135,7 +1481,12 @@ addHook({
1135
1481
  fileSuitesWithNewTestsToProjects,
1136
1482
  isNewTest,
1137
1483
  ['_ddIsNew', '_ddIsEfdRetry'],
1138
- earlyFlakeDetectionNumRetries
1484
+ getConfiguredEfdRetryCount(),
1485
+ (copiedTest, originalTest, retryIndex) => {
1486
+ markEfdRetryTest(copiedTest, retryIndex, originalTest)
1487
+ markEfdManagedTest(copiedTest)
1488
+ },
1489
+ getEfdRetryRepeatEachIndex
1139
1490
  )
1140
1491
  }
1141
1492
  }
@@ -1144,7 +1495,7 @@ addHook({
1144
1495
  }
1145
1496
 
1146
1497
  // We need to proxy the createRootSuite function because the function is not configurable
1147
- const proxy = new Proxy(loadUtilsPackage, {
1498
+ return new Proxy(loadUtilsPackage, {
1148
1499
  get (target, prop) {
1149
1500
  if (prop === 'createRootSuite') {
1150
1501
  return newCreateRootSuite
@@ -1152,8 +1503,6 @@ addHook({
1152
1503
  return target[prop]
1153
1504
  },
1154
1505
  })
1155
-
1156
- return proxy
1157
1506
  })
1158
1507
 
1159
1508
  // main process hook
@@ -1173,6 +1522,10 @@ addHook({
1173
1522
 
1174
1523
  // We add a new listener to `this.process`, which is represents the worker
1175
1524
  this.process.on('message', (message) => {
1525
+ if (message?.type === EFD_RETRY_COUNT_REQUEST) {
1526
+ sendEfdRetryCountToWorkerWhenAvailable(this.process, message.testId)
1527
+ return
1528
+ }
1176
1529
  // These messages are [code, payload]. The payload is test data
1177
1530
  if (Array.isArray(message) && message[0] === PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE) {
1178
1531
  workerReportCh.publish(message[1])
@@ -1231,9 +1584,15 @@ addHook({
1231
1584
  const stepInfoByStepId = {}
1232
1585
 
1233
1586
  shimmer.wrap(workerPackage.WorkerMain.prototype, '_runTest', _runTest => async function (test) {
1587
+ await waitForEfdRetryCount(test)
1588
+ if (shouldSkipEfdRetry(test)) {
1589
+ test._ddShouldSkipEfdRetry = true
1590
+ test.expectedStatus = 'skipped'
1591
+ }
1234
1592
  if (test.expectedStatus === 'skipped') {
1235
1593
  return _runTest.apply(this, arguments)
1236
1594
  }
1595
+ test._ddStartTime = performance.now()
1237
1596
  steps = []
1238
1597
 
1239
1598
  const {
@@ -1316,6 +1675,21 @@ addHook({
1316
1675
  await res
1317
1676
 
1318
1677
  const { status, error, annotations, retry, testId } = testInfo
1678
+ const testEfdKey = getTestEfdKey(test)
1679
+ const isEfdManagedTest = isTestEfdManaged(test)
1680
+ if (isEfdManagedTest && !test._ddIsEfdRetry && !efdRetryCountByTestKey.has(testEfdKey)) {
1681
+ const duration = test.results?.at(-1)?.duration > 0
1682
+ ? test.results.at(-1).duration
1683
+ : performance.now() - test._ddStartTime
1684
+ const retryCount = getEfdRetryCount(
1685
+ duration,
1686
+ getTestEfdSlowTestRetries(test)
1687
+ )
1688
+ setEfdRetryCountForTest(test, retryCount)
1689
+ if (retryCount === 0) {
1690
+ efdSlowAbortedTests.add(testEfdKey)
1691
+ }
1692
+ }
1319
1693
 
1320
1694
  // testInfo.errors could be better than "error",
1321
1695
  // which will only include timeout error (even though the test failed because of a different error)
@@ -1361,6 +1735,7 @@ addHook({
1361
1735
  isAttemptToFix: test._ddIsAttemptToFix,
1362
1736
  hasFailedAllRetries: test._ddHasFailedAllRetries,
1363
1737
  hasFailedAttemptToFixRetries: test._ddHasFailedAttemptToFixRetries,
1738
+ hasPassedAnyEfdAttempt: test._ddHasPassedAnyEfdAttempt,
1364
1739
  testStatus: STATUS_TO_TEST_STATUS[status],
1365
1740
  })
1366
1741
 
@@ -1384,6 +1759,7 @@ addHook({
1384
1759
  isModified: test._ddIsModified,
1385
1760
  onDone,
1386
1761
  finalStatus,
1762
+ earlyFlakeAbortReason: test._ddEarlyFlakeAbortReason,
1387
1763
  ...testCtx.currentStore,
1388
1764
  })
1389
1765
 
@@ -1419,12 +1795,13 @@ addHook({
1419
1795
  })
1420
1796
 
1421
1797
  function generateSummaryWrapper (generateSummary) {
1422
- return function () {
1798
+ return function (...args) {
1423
1799
  for (const test of this.suite.allTests()) {
1424
1800
  // https://github.com/microsoft/playwright/blob/bf92ffecff6f30a292b53430dbaee0207e0c61ad/packages/playwright/src/reporters/base.ts#L279
1425
1801
  const didNotRun = test.outcome() === 'skipped' &&
1426
1802
  (!test.results.length || test.expectedStatus !== 'skipped')
1427
- if (didNotRun) {
1803
+ if (didNotRun && !testsReportedInGenerateSummary.has(test)) {
1804
+ testsReportedInGenerateSummary.add(test)
1428
1805
  const {
1429
1806
  _requireFile: testSuiteAbsolutePath,
1430
1807
  location: {
@@ -1451,7 +1828,7 @@ function generateSummaryWrapper (generateSummary) {
1451
1828
  })
1452
1829
  }
1453
1830
  }
1454
- return generateSummary.apply(this, arguments)
1831
+ return generateSummary.apply(this, args)
1455
1832
  }
1456
1833
  }
1457
1834