dd-trace 5.103.0 → 5.105.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 (213) hide show
  1. package/LICENSE-3rdparty.csv +90 -102
  2. package/index.d.ts +107 -6
  3. package/package.json +18 -17
  4. package/packages/datadog-core/src/storage.js +1 -1
  5. package/packages/datadog-instrumentations/src/aerospike.js +1 -1
  6. package/packages/datadog-instrumentations/src/ai.js +8 -7
  7. package/packages/datadog-instrumentations/src/aws-sdk.js +15 -2
  8. package/packages/datadog-instrumentations/src/azure-cosmos.js +7 -0
  9. package/packages/datadog-instrumentations/src/azure-functions.js +3 -0
  10. package/packages/datadog-instrumentations/src/cassandra-driver.js +5 -2
  11. package/packages/datadog-instrumentations/src/cucumber.js +181 -35
  12. package/packages/datadog-instrumentations/src/dns.js +54 -18
  13. package/packages/datadog-instrumentations/src/elasticsearch.js +4 -4
  14. package/packages/datadog-instrumentations/src/fastify.js +142 -82
  15. package/packages/datadog-instrumentations/src/graphql.js +188 -67
  16. package/packages/datadog-instrumentations/src/grpc/client.js +48 -32
  17. package/packages/datadog-instrumentations/src/helpers/ai-messages.js +322 -14
  18. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +1 -1
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  20. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -1
  21. package/packages/datadog-instrumentations/src/helpers/kafka.js +17 -0
  22. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +269 -0
  23. package/packages/datadog-instrumentations/src/helpers/promise-instrumentor.js +42 -0
  24. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +3 -2
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +19 -6
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-cosmos.js +50 -0
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +4 -2
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +85 -0
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +31 -229
  32. package/packages/datadog-instrumentations/src/hono.js +54 -3
  33. package/packages/datadog-instrumentations/src/http/client.js +2 -2
  34. package/packages/datadog-instrumentations/src/http/server.js +9 -4
  35. package/packages/datadog-instrumentations/src/ioredis.js +3 -3
  36. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +163 -0
  37. package/packages/datadog-instrumentations/src/jest.js +390 -183
  38. package/packages/datadog-instrumentations/src/kafkajs.js +140 -17
  39. package/packages/datadog-instrumentations/src/mariadb.js +1 -1
  40. package/packages/datadog-instrumentations/src/memcached.js +2 -1
  41. package/packages/datadog-instrumentations/src/mocha/main.js +399 -107
  42. package/packages/datadog-instrumentations/src/mocha/utils.js +48 -8
  43. package/packages/datadog-instrumentations/src/mongodb-core.js +1 -1
  44. package/packages/datadog-instrumentations/src/mongoose.js +10 -12
  45. package/packages/datadog-instrumentations/src/mysql.js +2 -2
  46. package/packages/datadog-instrumentations/src/mysql2.js +1 -1
  47. package/packages/datadog-instrumentations/src/nats.js +182 -0
  48. package/packages/datadog-instrumentations/src/nyc.js +38 -1
  49. package/packages/datadog-instrumentations/src/openai.js +33 -18
  50. package/packages/datadog-instrumentations/src/oracledb.js +6 -1
  51. package/packages/datadog-instrumentations/src/pg.js +1 -1
  52. package/packages/datadog-instrumentations/src/pino.js +17 -5
  53. package/packages/datadog-instrumentations/src/playwright.js +537 -297
  54. package/packages/datadog-instrumentations/src/router.js +80 -34
  55. package/packages/datadog-instrumentations/src/stripe.js +1 -1
  56. package/packages/datadog-instrumentations/src/vitest.js +246 -149
  57. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -1
  58. package/packages/datadog-plugin-azure-cosmos/src/index.js +144 -0
  59. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -1
  60. package/packages/datadog-plugin-azure-functions/src/index.js +5 -2
  61. package/packages/datadog-plugin-azure-service-bus/src/producer.js +1 -1
  62. package/packages/datadog-plugin-bunyan/src/index.js +28 -0
  63. package/packages/datadog-plugin-cucumber/src/index.js +17 -3
  64. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +223 -45
  65. package/packages/datadog-plugin-cypress/src/support.js +69 -1
  66. package/packages/datadog-plugin-dns/src/lookup.js +8 -6
  67. package/packages/datadog-plugin-elasticsearch/src/index.js +28 -8
  68. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +1 -1
  69. package/packages/datadog-plugin-graphql/src/execute.js +2 -0
  70. package/packages/datadog-plugin-graphql/src/resolve.js +64 -67
  71. package/packages/datadog-plugin-graphql/src/utils.js +4 -1
  72. package/packages/datadog-plugin-http/src/server.js +40 -15
  73. package/packages/datadog-plugin-jest/src/index.js +11 -3
  74. package/packages/datadog-plugin-jest/src/util.js +15 -8
  75. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +1 -1
  76. package/packages/datadog-plugin-kafkajs/src/producer.js +35 -0
  77. package/packages/datadog-plugin-langgraph/src/stream.js +1 -1
  78. package/packages/datadog-plugin-mocha/src/index.js +19 -4
  79. package/packages/datadog-plugin-mongodb-core/src/index.js +311 -35
  80. package/packages/datadog-plugin-nats/src/consumer.js +43 -0
  81. package/packages/datadog-plugin-nats/src/index.js +20 -0
  82. package/packages/datadog-plugin-nats/src/producer.js +62 -0
  83. package/packages/datadog-plugin-nats/src/util.js +33 -0
  84. package/packages/datadog-plugin-next/src/index.js +5 -3
  85. package/packages/datadog-plugin-openai/src/tracing.js +15 -2
  86. package/packages/datadog-plugin-oracledb/src/index.js +13 -2
  87. package/packages/datadog-plugin-pino/src/index.js +42 -0
  88. package/packages/datadog-plugin-playwright/src/index.js +4 -4
  89. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -1
  90. package/packages/datadog-plugin-redis/src/index.js +37 -2
  91. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  92. package/packages/datadog-plugin-router/src/index.js +33 -44
  93. package/packages/datadog-plugin-selenium/src/index.js +1 -1
  94. package/packages/datadog-plugin-undici/src/index.js +19 -0
  95. package/packages/datadog-plugin-vitest/src/index.js +24 -20
  96. package/packages/datadog-plugin-winston/src/index.js +30 -0
  97. package/packages/datadog-shimmer/src/shimmer.js +49 -21
  98. package/packages/dd-trace/src/aiguard/index.js +1 -1
  99. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  100. package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
  101. package/packages/dd-trace/src/appsec/blocking.js +2 -2
  102. package/packages/dd-trace/src/appsec/index.js +11 -4
  103. package/packages/dd-trace/src/appsec/reporter.js +24 -11
  104. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  105. package/packages/dd-trace/src/appsec/sdk/utils.js +1 -1
  106. package/packages/dd-trace/src/appsec/user_tracking.js +5 -4
  107. package/packages/dd-trace/src/baggage.js +7 -1
  108. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +0 -1
  109. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +25 -13
  110. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -1
  111. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +5 -3
  112. package/packages/dd-trace/src/ci-visibility/test-optimization-cache.js +70 -6
  113. package/packages/dd-trace/src/config/generated-config-types.d.ts +7 -2
  114. package/packages/dd-trace/src/config/supported-configurations.json +36 -8
  115. package/packages/dd-trace/src/crashtracking/crashtracker.js +15 -3
  116. package/packages/dd-trace/src/datastreams/context.js +4 -2
  117. package/packages/dd-trace/src/datastreams/writer.js +2 -4
  118. package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -8
  119. package/packages/dd-trace/src/encode/0.4.js +124 -108
  120. package/packages/dd-trace/src/encode/0.5.js +114 -26
  121. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +57 -42
  122. package/packages/dd-trace/src/encode/agentless-json.js +4 -2
  123. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -13
  124. package/packages/dd-trace/src/encode/span-stats.js +16 -16
  125. package/packages/dd-trace/src/encode/tags-processors.js +16 -0
  126. package/packages/dd-trace/src/exporters/common/agents.js +3 -1
  127. package/packages/dd-trace/src/exporters/common/request.js +3 -1
  128. package/packages/dd-trace/src/id.js +17 -4
  129. package/packages/dd-trace/src/lambda/handler.js +2 -4
  130. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  131. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  132. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  133. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +9 -7
  134. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  135. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +1 -1
  136. package/packages/dd-trace/src/llmobs/sdk.js +10 -16
  137. package/packages/dd-trace/src/llmobs/span_processor.js +3 -3
  138. package/packages/dd-trace/src/llmobs/tagger.js +9 -1
  139. package/packages/dd-trace/src/llmobs/telemetry.js +1 -1
  140. package/packages/dd-trace/src/llmobs/util.js +66 -3
  141. package/packages/dd-trace/src/log/index.js +1 -1
  142. package/packages/dd-trace/src/log/writer.js +3 -1
  143. package/packages/dd-trace/src/msgpack/chunk.js +394 -10
  144. package/packages/dd-trace/src/msgpack/index.js +96 -2
  145. package/packages/dd-trace/src/noop/span.js +3 -1
  146. package/packages/dd-trace/src/openfeature/encoding.js +70 -0
  147. package/packages/dd-trace/src/openfeature/flagging_provider.js +20 -0
  148. package/packages/dd-trace/src/openfeature/span-enrichment-hook.js +143 -0
  149. package/packages/dd-trace/src/openfeature/span-enrichment.js +149 -0
  150. package/packages/dd-trace/src/openfeature/writers/exposures.js +51 -20
  151. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -1
  152. package/packages/dd-trace/src/opentelemetry/span-helpers.js +4 -3
  153. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  154. package/packages/dd-trace/src/opentracing/propagation/log.js +18 -7
  155. package/packages/dd-trace/src/opentracing/propagation/text_map.js +62 -67
  156. package/packages/dd-trace/src/opentracing/span.js +59 -19
  157. package/packages/dd-trace/src/opentracing/span_context.js +49 -0
  158. package/packages/dd-trace/src/plugins/apollo.js +3 -1
  159. package/packages/dd-trace/src/plugins/ci_plugin.js +23 -33
  160. package/packages/dd-trace/src/plugins/database.js +7 -6
  161. package/packages/dd-trace/src/plugins/index.js +4 -0
  162. package/packages/dd-trace/src/plugins/log_injection.js +56 -0
  163. package/packages/dd-trace/src/plugins/log_plugin.js +3 -46
  164. package/packages/dd-trace/src/plugins/outbound.js +1 -1
  165. package/packages/dd-trace/src/plugins/plugin.js +15 -17
  166. package/packages/dd-trace/src/plugins/tracing.js +48 -8
  167. package/packages/dd-trace/src/plugins/util/git.js +3 -1
  168. package/packages/dd-trace/src/plugins/util/test.js +318 -13
  169. package/packages/dd-trace/src/plugins/util/web.js +89 -64
  170. package/packages/dd-trace/src/priority_sampler.js +2 -2
  171. package/packages/dd-trace/src/profiling/profiler.js +2 -2
  172. package/packages/dd-trace/src/profiling/profilers/wall.js +10 -4
  173. package/packages/dd-trace/src/sampling_rule.js +7 -7
  174. package/packages/dd-trace/src/scope.js +7 -5
  175. package/packages/dd-trace/src/service-naming/extra-services.js +14 -0
  176. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +10 -0
  177. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
  178. package/packages/dd-trace/src/service-naming/source-resolver.js +46 -0
  179. package/packages/dd-trace/src/span_format.js +190 -58
  180. package/packages/dd-trace/src/spanleak.js +1 -1
  181. package/packages/dd-trace/src/standalone/index.js +3 -3
  182. package/packages/dd-trace/src/tagger.js +0 -2
  183. package/vendor/dist/@apm-js-collab/code-transformer/index.js +70 -39
  184. package/vendor/dist/@datadog/sketches-js/LICENSE +10 -36
  185. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  186. package/vendor/dist/protobufjs/index.js +1 -1
  187. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  188. package/packages/dd-trace/src/msgpack/encoder.js +0 -308
  189. package/packages/dd-trace/src/plugins/structured_log_plugin.js +0 -9
  190. package/vendor/dist/opentracing/LICENSE +0 -201
  191. package/vendor/dist/opentracing/binary_carrier.d.ts +0 -11
  192. package/vendor/dist/opentracing/constants.d.ts +0 -61
  193. package/vendor/dist/opentracing/examples/demo/demo.d.ts +0 -2
  194. package/vendor/dist/opentracing/ext/tags.d.ts +0 -90
  195. package/vendor/dist/opentracing/functions.d.ts +0 -20
  196. package/vendor/dist/opentracing/global_tracer.d.ts +0 -14
  197. package/vendor/dist/opentracing/index.d.ts +0 -12
  198. package/vendor/dist/opentracing/index.js +0 -1
  199. package/vendor/dist/opentracing/mock_tracer/index.d.ts +0 -5
  200. package/vendor/dist/opentracing/mock_tracer/mock_context.d.ts +0 -13
  201. package/vendor/dist/opentracing/mock_tracer/mock_report.d.ts +0 -16
  202. package/vendor/dist/opentracing/mock_tracer/mock_span.d.ts +0 -50
  203. package/vendor/dist/opentracing/mock_tracer/mock_tracer.d.ts +0 -26
  204. package/vendor/dist/opentracing/noop.d.ts +0 -8
  205. package/vendor/dist/opentracing/reference.d.ts +0 -33
  206. package/vendor/dist/opentracing/span.d.ts +0 -147
  207. package/vendor/dist/opentracing/span_context.d.ts +0 -26
  208. package/vendor/dist/opentracing/test/api_compatibility.d.ts +0 -16
  209. package/vendor/dist/opentracing/test/mocktracer_implemenation.d.ts +0 -3
  210. package/vendor/dist/opentracing/test/noop_implementation.d.ts +0 -4
  211. package/vendor/dist/opentracing/test/opentracing_api.d.ts +0 -3
  212. package/vendor/dist/opentracing/test/unittest.d.ts +0 -2
  213. package/vendor/dist/opentracing/tracer.d.ts +0 -127
@@ -2,6 +2,7 @@
2
2
 
3
3
  // Capture real timers at module load time, before any test can install fake timers.
4
4
  const realSetTimeout = setTimeout
5
+ const realClearTimeout = clearTimeout
5
6
 
6
7
  const { performance } = require('node:perf_hooks')
7
8
  const satisfies = require('../../../vendor/dist/semifies')
@@ -18,13 +19,14 @@ const {
18
19
  recordAttemptToFixExecution,
19
20
  logAttemptToFixTestExecution,
20
21
  logTestOptimizationSummary,
22
+ getTestOptimizationRequestResults,
21
23
  } = require('../../dd-trace/src/plugins/util/test')
22
24
  const log = require('../../dd-trace/src/log')
23
25
  const {
24
26
  getValueFromEnvSources,
25
27
  } = require('../../dd-trace/src/config/helper')
26
28
  const { DD_MAJOR } = require('../../../version')
27
- const { addHook, channel } = require('./helpers/instrument')
29
+ const { addHook, channel, tracingChannel } = require('./helpers/instrument')
28
30
 
29
31
  const testStartCh = channel('ci:playwright:test:start')
30
32
  const testFinishCh = channel('ci:playwright:test:finish')
@@ -45,6 +47,12 @@ const testSuiteFinishCh = channel('ci:playwright:test-suite:finish')
45
47
  const workerReportCh = channel('ci:playwright:worker:report')
46
48
  const testPageGotoCh = channel('ci:playwright:test:page-goto')
47
49
 
50
+ const dispatcherRunCh = tracingChannel('orchestrion:playwright:Dispatcher_run')
51
+ const dispatcherCreateWorkerCh = tracingChannel('orchestrion:playwright:Dispatcher_createWorker')
52
+ const processHostStartRunnerCh = tracingChannel('orchestrion:playwright:ProcessHost_startRunner')
53
+ const createRootSuiteCh = tracingChannel('orchestrion:playwright:createRootSuite')
54
+ const pageGotoCh = tracingChannel('orchestrion:playwright-core:Page_goto')
55
+
48
56
  const testToCtx = new WeakMap()
49
57
  const testSuiteToCtx = new Map()
50
58
  const testSuiteToTestStatuses = new Map()
@@ -52,6 +60,7 @@ const testSuiteToErrors = new Map()
52
60
  const testsToTestStatuses = new Map()
53
61
 
54
62
  const RUM_FLUSH_WAIT_TIME = Number(getValueFromEnvSources('DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS')) || 500
63
+ const DD_PROPERTIES_TIMEOUT = 5000
55
64
 
56
65
  let applyRepeatEachIndex = null
57
66
 
@@ -94,12 +103,17 @@ const efdRetryTestsById = new Map()
94
103
  const efdScheduledOriginalTestKeys = new Set()
95
104
  const efdStartedOriginalTestKeys = new Set()
96
105
  const efdSlowAbortedTests = new Set()
106
+ const ddPropertiesByTestId = new Map()
107
+ const ddPropertiesRequestsByTestId = new Map()
97
108
  let rootDir = ''
98
109
  let sessionProjects = []
99
110
 
100
111
  const MINIMUM_SUPPORTED_VERSION_RANGE_EFD = '>=1.38.0' // TODO: remove this once we drop support for v5
101
112
  const EFD_RETRY_COUNT_REQUEST = 'ddEfdRetryCountRequest'
102
113
  const EFD_RETRY_COUNT_RESPONSE = 'ddEfdRetryCountResponse'
114
+ const DD_PROPERTIES_REQUEST = 'ddPropertiesRequest'
115
+ const DD_PROPERTIES_RESPONSE = 'ddProperties'
116
+ const kDdPlaywrightWorkerInstrumented = Symbol('ddPlaywrightWorkerInstrumented')
103
117
 
104
118
  function isValidKnownTests (receivedKnownTests) {
105
119
  return !!receivedKnownTests.playwright
@@ -280,6 +294,43 @@ function sendEfdRetryCountToWorkerWhenAvailable (workerProcess, testId) {
280
294
  })
281
295
  }
282
296
 
297
+ function sendDdPropertiesToWorker (workerProcess, testId, properties) {
298
+ workerProcess.send({
299
+ type: DD_PROPERTIES_RESPONSE,
300
+ testId,
301
+ properties,
302
+ })
303
+ }
304
+
305
+ function setDdPropertiesForTest (workerProcess, testId, properties) {
306
+ ddPropertiesByTestId.set(testId, properties)
307
+
308
+ const requests = ddPropertiesRequestsByTestId.get(testId)
309
+ if (requests) {
310
+ ddPropertiesRequestsByTestId.delete(testId)
311
+ for (const resolveRequest of requests) {
312
+ resolveRequest(properties)
313
+ }
314
+ }
315
+
316
+ sendDdPropertiesToWorker(workerProcess, testId, properties)
317
+ }
318
+
319
+ function sendDdPropertiesToWorkerWhenAvailable (workerProcess, testId) {
320
+ const properties = ddPropertiesByTestId.get(testId)
321
+ if (properties) {
322
+ sendDdPropertiesToWorker(workerProcess, testId, properties)
323
+ return
324
+ }
325
+
326
+ if (!ddPropertiesRequestsByTestId.has(testId)) {
327
+ ddPropertiesRequestsByTestId.set(testId, [])
328
+ }
329
+ ddPropertiesRequestsByTestId.get(testId).push((properties) => {
330
+ sendDdPropertiesToWorker(workerProcess, testId, properties)
331
+ })
332
+ }
333
+
283
334
  /**
284
335
  * @param {object} test
285
336
  * @returns {boolean}
@@ -353,11 +404,15 @@ function getSuiteType (test, type) {
353
404
  return suite
354
405
  }
355
406
 
407
+ function isSuiteEntry (entry) {
408
+ return entry.constructor.name === 'Suite' || entry.constructor.name === '_Suite'
409
+ }
410
+
356
411
  // Copy of Suite#_deepClone but with a function to filter tests
357
412
  function deepCloneSuite (suite, filterTest, tags = [], configureCopiedTest) {
358
413
  const copy = suite._clone()
359
414
  for (const entry of suite._entries) {
360
- if (entry.constructor.name === 'Suite') {
415
+ if (isSuiteEntry(entry)) {
361
416
  copy._addSuite(deepCloneSuite(entry, filterTest, tags, configureCopiedTest))
362
417
  } else {
363
418
  if (filterTest(entry)) {
@@ -444,6 +499,10 @@ function getProjectsFromRunner (runner, configArg) {
444
499
  }
445
500
 
446
501
  function getProjectsFromDispatcher (dispatcher) {
502
+ const bundledConfig = dispatcher._testRun?.config?.config?.projects
503
+ if (bundledConfig) {
504
+ return bundledConfig
505
+ }
447
506
  const newConfig = dispatcher._config?.config?.projects
448
507
  if (newConfig) {
449
508
  return newConfig
@@ -887,31 +946,118 @@ function deferEfdRetryGroups (testGroups) {
887
946
  return [...groupsWithOriginalTests, ...efdRetryOnlyGroups]
888
947
  }
889
948
 
949
+ function prepareDispatcherRun (dispatcher, args) {
950
+ let testGroups = args[0]
951
+
952
+ // Filter out disabled tests from testGroups before they get scheduled,
953
+ // unless they have attemptToFix (in which case they should still run and be retried)
954
+ if (isTestManagementTestsEnabled) {
955
+ for (const group of testGroups) {
956
+ group.tests = group.tests.filter(test => !test._ddIsDisabled || test._ddIsAttemptToFix)
957
+ }
958
+ // Remove empty groups
959
+ testGroups = testGroups.filter(group => group.tests.length > 0)
960
+ }
961
+
962
+ if (isEarlyFlakeDetectionEnabled) {
963
+ testGroups = deferEfdRetryGroups(testGroups)
964
+ }
965
+
966
+ if (!dispatcher._allTests) {
967
+ // Removed in https://github.com/microsoft/playwright/commit/1e52c37b254a441cccf332520f60225a5acc14c7
968
+ // Not available from >=1.44.0
969
+ dispatcher._ddAllTests = testGroups.flatMap(g => g.tests)
970
+ }
971
+ remainingTestsByFile = getTestsBySuiteFromTestGroups(testGroups)
972
+ args[0] = testGroups
973
+ }
974
+
890
975
  function dispatcherRunWrapperNew (run) {
891
- return function (testGroups) {
892
- // Filter out disabled tests from testGroups before they get scheduled,
893
- // unless they have attemptToFix (in which case they should still run and be retried)
894
- if (isTestManagementTestsEnabled) {
895
- for (const group of testGroups) {
896
- group.tests = group.tests.filter(test => !test._ddIsDisabled || test._ddIsAttemptToFix)
897
- }
898
- // Remove empty groups
899
- testGroups = testGroups.filter(group => group.tests.length > 0)
976
+ return function () {
977
+ prepareDispatcherRun(this, arguments)
978
+ return run.apply(this, arguments)
979
+ }
980
+ }
981
+
982
+ function onDispatcherCreateWorker (dispatcher, worker) {
983
+ if (!worker) {
984
+ return worker
985
+ }
986
+
987
+ const projects = getProjectsFromDispatcher(dispatcher)
988
+ sessionProjects = projects
989
+
990
+ worker.on('testBegin', ({ testId }) => {
991
+ const test = getTestByTestId(dispatcher, testId)
992
+ const browser = getBrowserNameFromProjects(projects, test)
993
+ const shouldCreateTestSpan = test.expectedStatus === 'skipped'
994
+ testBeginHandler(test, browser, shouldCreateTestSpan)
995
+ })
996
+ worker.on('testEnd', ({ testId, status, errors, annotations }) => {
997
+ const test = getTestByTestId(dispatcher, testId)
998
+
999
+ const isTimeout = status === 'timedOut'
1000
+ const testStatus = STATUS_TO_TEST_STATUS[status]
1001
+ const shouldCreateTestSpan = test.expectedStatus === 'skipped'
1002
+ if (shouldCreateTestSpan && !testToCtx.has(test)) {
1003
+ testBeginHandler(test, getBrowserNameFromProjects(projects, test), true)
900
1004
  }
1005
+ testEndHandler(
1006
+ {
1007
+ test,
1008
+ annotations,
1009
+ testStatus,
1010
+ error: errors && errors[0],
1011
+ isTimeout,
1012
+ shouldCreateTestSpan,
1013
+ projects,
1014
+ }
1015
+ )
1016
+ const testResult = test.results.at(-1)
1017
+ const isAtrRetry = testResult?.retry > 0 &&
1018
+ isFlakyTestRetriesEnabled &&
1019
+ !test._ddIsAttemptToFix &&
1020
+ !test._ddIsEfdRetry
901
1021
 
902
- if (isEarlyFlakeDetectionEnabled) {
903
- testGroups = deferEfdRetryGroups(testGroups)
1022
+ // EFD retries (new or modified tests) are implemented as clones with retries=0,
1023
+ // so testWillRetry always returns false for them. Instead, we track how many
1024
+ // executions have been reported via testsToTestStatuses (updated by testEndHandler
1025
+ // above) and mark the execution final once the count reaches the expected total.
1026
+ // This mirrors how ATF finality is detected and centralizes the decision in the
1027
+ // main process, so workers only need to act on the _ddIsFinalExecution flag.
1028
+ const isEfdManagedTest = isTestEfdManaged(test)
1029
+ let isFinalExecution
1030
+ if (isEfdManagedTest) {
1031
+ const efdTestStatuses = testsToTestStatuses.get(getTestEfdKey(test)) || []
1032
+ isFinalExecution = efdTestStatuses.length === getEfdRetryCountForTest(test) + 1
1033
+ } else if (test._ddIsAttemptToFix) {
1034
+ isFinalExecution = !!(test._ddHasPassedAttemptToFixRetries || test._ddHasFailedAttemptToFixRetries)
1035
+ } else {
1036
+ isFinalExecution = !testWillRetry(test, testStatus)
904
1037
  }
905
1038
 
906
- if (!this._allTests) {
907
- // Removed in https://github.com/microsoft/playwright/commit/1e52c37b254a441cccf332520f60225a5acc14c7
908
- // Not available from >=1.44.0
909
- this._ddAllTests = testGroups.flatMap(g => g.tests)
1039
+ const ddProperties = {
1040
+ _ddIsDisabled: test._ddIsDisabled,
1041
+ _ddIsQuarantined: test._ddIsQuarantined,
1042
+ _ddIsAttemptToFix: test._ddIsAttemptToFix,
1043
+ _ddIsAttemptToFixRetry: test._ddIsAttemptToFixRetry,
1044
+ _ddIsNew: test._ddIsNew,
1045
+ _ddIsEfdRetry: test._ddIsEfdRetry,
1046
+ _ddHasFailedAllRetries: test._ddHasFailedAllRetries,
1047
+ _ddHasPassedAttemptToFixRetries: test._ddHasPassedAttemptToFixRetries,
1048
+ _ddHasFailedAttemptToFixRetries: test._ddHasFailedAttemptToFixRetries,
1049
+ _ddIsAtrRetry: isAtrRetry,
1050
+ _ddIsModified: test._ddIsModified,
1051
+ _ddIsFinalExecution: isFinalExecution,
1052
+ _ddIsEfdManagedTest: isEfdManagedTest,
1053
+ _ddEarlyFlakeAbortReason: efdSlowAbortedTests.has(getTestEfdKey(test)) ? 'slow' : undefined,
1054
+ _ddHasPassedAnyEfdAttempt: (testsToTestStatuses.get(getTestEfdKey(test)) || []).includes('pass'),
910
1055
  }
911
- remainingTestsByFile = getTestsBySuiteFromTestGroups(testGroups)
912
- arguments[0] = testGroups
913
- return run.apply(this, arguments)
914
- }
1056
+
1057
+ setDdPropertiesForTest(worker.process, test.id, ddProperties)
1058
+ })
1059
+
1060
+ return worker
915
1061
  }
916
1062
 
917
1063
  function dispatcherHook (dispatcherExport) {
@@ -959,82 +1105,7 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
959
1105
  shimmer.wrap(dispatcherExport.Dispatcher.prototype, '_createWorker', createWorker => function (...args) {
960
1106
  const dispatcher = this
961
1107
  const worker = createWorker.apply(this, args)
962
- const projects = getProjectsFromDispatcher(dispatcher)
963
- sessionProjects = projects
964
-
965
- worker.on('testBegin', ({ testId }) => {
966
- const test = getTestByTestId(dispatcher, testId)
967
- const browser = getBrowserNameFromProjects(projects, test)
968
- const shouldCreateTestSpan = test.expectedStatus === 'skipped'
969
- testBeginHandler(test, browser, shouldCreateTestSpan)
970
- })
971
- worker.on('testEnd', ({ testId, status, errors, annotations }) => {
972
- const test = getTestByTestId(dispatcher, testId)
973
-
974
- const isTimeout = status === 'timedOut'
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
- }
980
- testEndHandler(
981
- {
982
- test,
983
- annotations,
984
- testStatus,
985
- error: errors && errors[0],
986
- isTimeout,
987
- shouldCreateTestSpan,
988
- projects,
989
- }
990
- )
991
- const testResult = test.results.at(-1)
992
- const isAtrRetry = testResult?.retry > 0 &&
993
- isFlakyTestRetriesEnabled &&
994
- !test._ddIsAttemptToFix &&
995
- !test._ddIsEfdRetry
996
-
997
- // EFD retries (new or modified tests) are implemented as clones with retries=0,
998
- // so testWillRetry always returns false for them. Instead, we track how many
999
- // executions have been reported via testsToTestStatuses (updated by testEndHandler
1000
- // above) and mark the execution final once the count reaches the expected total.
1001
- // This mirrors how ATF finality is detected and centralizes the decision in the
1002
- // main process, so workers only need to act on the _ddIsFinalExecution flag.
1003
- const isEfdManagedTest = isTestEfdManaged(test)
1004
- let isFinalExecution
1005
- if (isEfdManagedTest) {
1006
- const efdTestStatuses = testsToTestStatuses.get(getTestEfdKey(test)) || []
1007
- isFinalExecution = efdTestStatuses.length === getEfdRetryCountForTest(test) + 1
1008
- } else if (test._ddIsAttemptToFix) {
1009
- isFinalExecution = !!(test._ddHasPassedAttemptToFixRetries || test._ddHasFailedAttemptToFixRetries)
1010
- } else {
1011
- isFinalExecution = !testWillRetry(test, testStatus)
1012
- }
1013
-
1014
- // We want to send the ddProperties to the worker
1015
- worker.process.send({
1016
- type: 'ddProperties',
1017
- testId: test.id,
1018
- properties: {
1019
- _ddIsDisabled: test._ddIsDisabled,
1020
- _ddIsQuarantined: test._ddIsQuarantined,
1021
- _ddIsAttemptToFix: test._ddIsAttemptToFix,
1022
- _ddIsAttemptToFixRetry: test._ddIsAttemptToFixRetry,
1023
- _ddIsNew: test._ddIsNew,
1024
- _ddIsEfdRetry: test._ddIsEfdRetry,
1025
- _ddHasFailedAllRetries: test._ddHasFailedAllRetries,
1026
- _ddHasPassedAttemptToFixRetries: test._ddHasPassedAttemptToFixRetries,
1027
- _ddHasFailedAttemptToFixRetries: test._ddHasFailedAttemptToFixRetries,
1028
- _ddIsAtrRetry: isAtrRetry,
1029
- _ddIsModified: test._ddIsModified,
1030
- _ddIsFinalExecution: isFinalExecution,
1031
- _ddIsEfdManagedTest: isEfdManagedTest,
1032
- _ddEarlyFlakeAbortReason: efdSlowAbortedTests.has(getTestEfdKey(test)) ? 'slow' : undefined,
1033
- _ddHasPassedAnyEfdAttempt: (testsToTestStatuses.get(getTestEfdKey(test)) || []).includes('pass'),
1034
- },
1035
- })
1036
- })
1037
- return worker
1108
+ return onDispatcherCreateWorker(dispatcher, worker)
1038
1109
  })
1039
1110
  return dispatcherExport
1040
1111
  }
@@ -1045,7 +1116,6 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
1045
1116
  let onDone
1046
1117
 
1047
1118
  rootDir = getRootDir(this, config)
1048
-
1049
1119
  const processArgv = process.argv.slice(2).join(' ')
1050
1120
  const command = `playwright ${processArgv}`
1051
1121
  testSessionStartCh.publish({ command, frameworkVersion: playwrightVersion, rootDir })
@@ -1075,9 +1145,24 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
1075
1145
  log.error('Playwright session start error', e)
1076
1146
  }
1077
1147
 
1078
- if (isKnownTestsEnabled && satisfies(playwrightVersion, MINIMUM_SUPPORTED_VERSION_RANGE_EFD)) {
1148
+ const isTestOptimizationSupported = satisfies(playwrightVersion, MINIMUM_SUPPORTED_VERSION_RANGE_EFD)
1149
+ const shouldGetKnownTests = isKnownTestsEnabled && isTestOptimizationSupported
1150
+ const shouldGetTestManagementTests = isTestManagementTestsEnabled && isTestOptimizationSupported
1151
+
1152
+ const {
1153
+ knownTestsResponse,
1154
+ testManagementTestsResponse,
1155
+ } = await getTestOptimizationRequestResults({
1156
+ isKnownTestsEnabled: shouldGetKnownTests,
1157
+ isTestManagementTestsEnabled: shouldGetTestManagementTests,
1158
+ getKnownTests: () => getChannelPromise(knownTestsCh),
1159
+ getTestManagementTests: () => getChannelPromise(testManagementTestsCh),
1160
+ })
1161
+
1162
+ if (shouldGetKnownTests) {
1079
1163
  try {
1080
- const { err, knownTests: receivedKnownTests } = await getChannelPromise(knownTestsCh)
1164
+ const { err, knownTests: receivedKnownTests } =
1165
+ knownTestsResponse || await getChannelPromise(knownTestsCh)
1081
1166
  if (err) {
1082
1167
  isEarlyFlakeDetectionEnabled = false
1083
1168
  isKnownTestsEnabled = false
@@ -1096,9 +1181,10 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
1096
1181
  }
1097
1182
  }
1098
1183
 
1099
- if (isTestManagementTestsEnabled && satisfies(playwrightVersion, MINIMUM_SUPPORTED_VERSION_RANGE_EFD)) {
1184
+ if (shouldGetTestManagementTests) {
1100
1185
  try {
1101
- const { err, testManagementTests: receivedTestManagementTests } = await getChannelPromise(testManagementTestsCh)
1186
+ const { err, testManagementTests: receivedTestManagementTests } =
1187
+ testManagementTestsResponse || await getChannelPromise(testManagementTestsCh)
1102
1188
  if (err) {
1103
1189
  isTestManagementTestsEnabled = false
1104
1190
  } else {
@@ -1110,7 +1196,7 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
1110
1196
  }
1111
1197
  }
1112
1198
 
1113
- if (isImpactedTestsEnabled && satisfies(playwrightVersion, MINIMUM_SUPPORTED_VERSION_RANGE_EFD)) {
1199
+ if (isImpactedTestsEnabled && isTestOptimizationSupported) {
1114
1200
  try {
1115
1201
  const { err, modifiedFiles: receivedModifiedFiles } = await getChannelPromise(modifiedFilesCh)
1116
1202
  if (err) {
@@ -1216,6 +1302,8 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
1216
1302
  efdScheduledOriginalTestKeys.clear()
1217
1303
  efdStartedOriginalTestKeys.clear()
1218
1304
  efdSlowAbortedTests.clear()
1305
+ ddPropertiesByTestId.clear()
1306
+ ddPropertiesRequestsByTestId.clear()
1219
1307
 
1220
1308
  // TODO: we can trick playwright into thinking the session passed by returning
1221
1309
  // 'passed' here. We might be able to use this for both EFD and Test Management tests.
@@ -1242,6 +1330,85 @@ function runnerHookNew (runnerExport, playwrightVersion) {
1242
1330
  return runnerExport
1243
1331
  }
1244
1332
 
1333
+ function runnerIndexHook (runnerExport, playwrightVersion) {
1334
+ let wrappedTestRunner
1335
+ runnerExport = shimmer.wrap(runnerExport, 'testRunner', function (originalGetter) {
1336
+ return function () {
1337
+ if (!wrappedTestRunner) {
1338
+ wrappedTestRunner = runnerHookNew(originalGetter.call(this), playwrightVersion)
1339
+ }
1340
+ return wrappedTestRunner
1341
+ }
1342
+ })
1343
+
1344
+ const baseReporter = runnerExport.base?.TerminalReporter
1345
+ if (baseReporter) {
1346
+ shimmer.wrap(baseReporter.prototype, 'generateSummary', generateSummaryWrapper)
1347
+ }
1348
+
1349
+ return runnerExport
1350
+ }
1351
+
1352
+ function commonIndexHook (commonExport) {
1353
+ applyRepeatEachIndex = commonExport.suiteUtils?.applyRepeatEachIndex
1354
+
1355
+ let wrappedStartProcessRunner
1356
+ commonExport = shimmer.wrap(commonExport, 'startProcessRunner', function (originalGetter) {
1357
+ return function () {
1358
+ if (!wrappedStartProcessRunner) {
1359
+ const startProcessRunner = originalGetter.call(this)
1360
+ wrappedStartProcessRunner = function (create) {
1361
+ return startProcessRunner.call(this, function () {
1362
+ const processRunner = create.apply(this, arguments)
1363
+ instrumentWorkerMainMethods(processRunner)
1364
+ return processRunner
1365
+ })
1366
+ }
1367
+ }
1368
+ return wrappedStartProcessRunner
1369
+ }
1370
+ })
1371
+
1372
+ return commonExport
1373
+ }
1374
+
1375
+ dispatcherRunCh.subscribe({
1376
+ start (ctx) {
1377
+ prepareDispatcherRun(ctx.self, ctx.arguments)
1378
+ },
1379
+ })
1380
+
1381
+ dispatcherCreateWorkerCh.subscribe({
1382
+ end (ctx) {
1383
+ onDispatcherCreateWorker(ctx.self, ctx.result)
1384
+ },
1385
+ })
1386
+
1387
+ processHostStartRunnerCh.subscribe({
1388
+ start (ctx) {
1389
+ prepareProcessHostStartRunner(ctx.self)
1390
+ },
1391
+ asyncEnd (ctx) {
1392
+ finishProcessHostStartRunner(ctx.self)
1393
+ },
1394
+ })
1395
+
1396
+ createRootSuiteCh.subscribe({
1397
+ asyncEnd (ctx) {
1398
+ if (ctx.error) {
1399
+ return
1400
+ }
1401
+ processRootSuite(ctx.result || ctx.arguments?.[0])
1402
+ },
1403
+ })
1404
+
1405
+ pageGotoCh.subscribe({
1406
+ asyncEnd (ctx) {
1407
+ // The Page.goto rewriter waits for this so tests closing immediately after navigation still get RUM tags.
1408
+ ctx.asyncEndPromise = handlePageGoto(ctx.self)
1409
+ },
1410
+ })
1411
+
1245
1412
  if (DD_MAJOR < 6) { // <1.38.0 is only supported up to version 5
1246
1413
  addHook({
1247
1414
  name: '@playwright/test',
@@ -1274,28 +1441,40 @@ if (DD_MAJOR < 6) { // <1.38.0 is only supported up to version 5
1274
1441
  }, runnerHook)
1275
1442
  }
1276
1443
 
1444
+ addHook({
1445
+ name: 'playwright',
1446
+ file: 'lib/runner/index.js',
1447
+ versions: ['>=1.60.0'],
1448
+ }, runnerIndexHook)
1449
+
1450
+ addHook({
1451
+ name: 'playwright',
1452
+ file: 'lib/common/index.js',
1453
+ versions: ['>=1.60.0'],
1454
+ }, commonIndexHook)
1455
+
1277
1456
  addHook({
1278
1457
  name: 'playwright',
1279
1458
  file: 'lib/runner/runner.js',
1280
- versions: ['>=1.38.0'],
1459
+ versions: ['>=1.38.0 <1.60.0'],
1281
1460
  }, runnerHook)
1282
1461
 
1283
1462
  addHook({
1284
1463
  name: 'playwright',
1285
1464
  file: 'lib/runner/testRunner.js',
1286
- versions: ['>=1.55.0'],
1465
+ versions: ['>=1.55.0 <1.60.0'],
1287
1466
  }, runnerHookNew)
1288
1467
 
1289
1468
  addHook({
1290
1469
  name: 'playwright',
1291
1470
  file: 'lib/runner/dispatcher.js',
1292
- versions: ['>=1.38.0'],
1471
+ versions: ['>=1.38.0 <1.60.0'],
1293
1472
  }, (dispatcher) => dispatcherHookNew(dispatcher, dispatcherRunWrapperNew))
1294
1473
 
1295
1474
  addHook({
1296
1475
  name: 'playwright',
1297
1476
  file: 'lib/common/suiteUtils.js',
1298
- versions: ['>=1.38.0'],
1477
+ versions: ['>=1.38.0 <1.60.0'],
1299
1478
  }, suiteUtilsPackage => {
1300
1479
  // We grab `applyRepeatEachIndex` to use it later
1301
1480
  // `applyRepeatEachIndex` needs to be applied to a cloned suite
@@ -1344,102 +1523,142 @@ function applyRetriesToTests (
1344
1523
  }
1345
1524
  }
1346
1525
 
1347
- addHook({
1348
- name: 'playwright',
1349
- file: 'lib/runner/loadUtils.js',
1350
- versions: ['>=1.38.0'],
1351
- }, (loadUtilsPackage) => {
1352
- const oldCreateRootSuite = loadUtilsPackage.createRootSuite
1526
+ function processRootSuite (createRootSuiteReturnValue) {
1527
+ if (!isKnownTestsEnabled && !isTestManagementTestsEnabled && !isImpactedTestsEnabled) {
1528
+ return createRootSuiteReturnValue
1529
+ }
1353
1530
 
1354
- async function newCreateRootSuite () {
1355
- if (!isKnownTestsEnabled && !isTestManagementTestsEnabled && !isImpactedTestsEnabled) {
1356
- return oldCreateRootSuite.apply(this, arguments)
1357
- }
1531
+ if (!createRootSuiteReturnValue) {
1532
+ return createRootSuiteReturnValue
1533
+ }
1358
1534
 
1359
- const createRootSuiteReturnValue = await oldCreateRootSuite.apply(this, arguments)
1360
- // From v1.56.0 on, createRootSuite returns `{ rootSuite, topLevelProjects }`
1361
- const rootSuite = createRootSuiteReturnValue.rootSuite || createRootSuiteReturnValue
1362
-
1363
- const allTests = rootSuite.allTests()
1364
-
1365
- if (isTestManagementTestsEnabled) {
1366
- const fileSuitesWithManagedTestsToProjects = new Map()
1367
- for (const test of allTests) {
1368
- const testProperties = getTestProperties(test)
1369
- // Disabled tests are skipped unless they have attemptToFix
1370
- if (testProperties.disabled) {
1371
- test._ddIsDisabled = true
1372
- if (!testProperties.attemptToFix) {
1373
- test.expectedStatus = 'skipped'
1374
- // setting test.expectedStatus to 'skipped' does not work for every case,
1375
- // so we need to filter out disabled tests in dispatcherRunWrapperNew,
1376
- // so they don't get to the workers
1377
- continue
1378
- }
1535
+ // From v1.56.0 on, createRootSuite returns `{ rootSuite, topLevelProjects }`
1536
+ const rootSuite = createRootSuiteReturnValue.rootSuite || createRootSuiteReturnValue
1537
+ if (typeof rootSuite?.allTests !== 'function') {
1538
+ return createRootSuiteReturnValue
1539
+ }
1540
+
1541
+ const allTests = rootSuite.allTests()
1542
+
1543
+ if (isTestManagementTestsEnabled) {
1544
+ const fileSuitesWithManagedTestsToProjects = new Map()
1545
+ for (const test of allTests) {
1546
+ const testProperties = getTestProperties(test)
1547
+ // Disabled tests are skipped unless they have attemptToFix
1548
+ if (testProperties.disabled) {
1549
+ test._ddIsDisabled = true
1550
+ if (!testProperties.attemptToFix) {
1551
+ test.expectedStatus = 'skipped'
1552
+ // setting test.expectedStatus to 'skipped' does not work for every case,
1553
+ // so we need to filter out disabled tests in dispatcherRunWrapperNew,
1554
+ // so they don't get to the workers
1555
+ continue
1379
1556
  }
1380
- if (testProperties.quarantined) {
1381
- test._ddIsQuarantined = true
1382
- if (!testProperties.attemptToFix) {
1383
- // Do not skip quarantined tests, let them run and overwrite results post-run if they fail
1384
- const testFqn = getTestFullyQualifiedName(test)
1385
- quarantinedButNotAttemptToFixFqns.add(testFqn)
1386
- }
1557
+ }
1558
+ if (testProperties.quarantined) {
1559
+ test._ddIsQuarantined = true
1560
+ if (!testProperties.attemptToFix) {
1561
+ // Do not skip quarantined tests, let them run and overwrite results post-run if they fail
1562
+ const testFqn = getTestFullyQualifiedName(test)
1563
+ quarantinedButNotAttemptToFixFqns.add(testFqn)
1387
1564
  }
1388
- if (testProperties.attemptToFix) {
1389
- test._ddIsAttemptToFix = true
1390
- // Prevent ATR or `--retries` from retrying attemptToFix tests
1391
- test.retries = 0
1392
- const fileSuite = getSuiteType(test, 'file')
1393
-
1394
- if (!fileSuitesWithManagedTestsToProjects.has(fileSuite)) {
1395
- fileSuitesWithManagedTestsToProjects.set(fileSuite, getSuiteType(test, 'project'))
1396
- }
1565
+ }
1566
+ if (testProperties.attemptToFix) {
1567
+ test._ddIsAttemptToFix = true
1568
+ // Prevent ATR or `--retries` from retrying attemptToFix tests
1569
+ test.retries = 0
1570
+ const fileSuite = getSuiteType(test, 'file')
1571
+
1572
+ if (!fileSuitesWithManagedTestsToProjects.has(fileSuite)) {
1573
+ fileSuitesWithManagedTestsToProjects.set(fileSuite, getSuiteType(test, 'project'))
1397
1574
  }
1398
1575
  }
1399
- applyRetriesToTests(
1400
- fileSuitesWithManagedTestsToProjects,
1401
- (test) => test._ddIsAttemptToFix,
1402
- [
1403
- (test) => test._ddIsQuarantined && '_ddIsQuarantined',
1404
- (test) => test._ddIsDisabled && '_ddIsDisabled',
1405
- '_ddIsAttemptToFix',
1406
- '_ddIsAttemptToFixRetry',
1407
- ],
1408
- testManagementAttemptToFixRetries
1409
- )
1410
1576
  }
1577
+ applyRetriesToTests(
1578
+ fileSuitesWithManagedTestsToProjects,
1579
+ (test) => test._ddIsAttemptToFix,
1580
+ [
1581
+ (test) => test._ddIsQuarantined && '_ddIsQuarantined',
1582
+ (test) => test._ddIsDisabled && '_ddIsDisabled',
1583
+ '_ddIsAttemptToFix',
1584
+ '_ddIsAttemptToFixRetry',
1585
+ ],
1586
+ testManagementAttemptToFixRetries
1587
+ )
1588
+ }
1411
1589
 
1412
- if (isImpactedTestsEnabled) {
1413
- const impactedTests = allTests.filter(test => {
1414
- let isImpacted = false
1415
- isModifiedCh.publish({
1416
- filePath: test._requireFile,
1417
- modifiedFiles,
1418
- onDone: (isModified) => { isImpacted = isModified },
1419
- })
1420
- return isImpacted
1590
+ if (isImpactedTestsEnabled) {
1591
+ const impactedTests = allTests.filter(test => {
1592
+ let isImpacted = false
1593
+ isModifiedCh.publish({
1594
+ filePath: test._requireFile,
1595
+ modifiedFiles,
1596
+ onDone: (isModified) => { isImpacted = isModified },
1421
1597
  })
1598
+ return isImpacted
1599
+ })
1422
1600
 
1423
- const fileSuitesWithImpactedTestsToProjects = new Map()
1424
- for (const impactedTest of impactedTests) {
1425
- impactedTest._ddIsModified = true
1426
- if (isEarlyFlakeDetectionEnabled && impactedTest.expectedStatus !== 'skipped') {
1427
- markEfdManagedTest(impactedTest)
1428
- const fileSuite = getSuiteType(impactedTest, 'file')
1429
- if (!fileSuitesWithImpactedTestsToProjects.has(fileSuite)) {
1430
- fileSuitesWithImpactedTestsToProjects.set(fileSuite, getSuiteType(impactedTest, 'project'))
1601
+ const fileSuitesWithImpactedTestsToProjects = new Map()
1602
+ for (const impactedTest of impactedTests) {
1603
+ impactedTest._ddIsModified = true
1604
+ if (isEarlyFlakeDetectionEnabled && impactedTest.expectedStatus !== 'skipped') {
1605
+ markEfdManagedTest(impactedTest)
1606
+ const fileSuite = getSuiteType(impactedTest, 'file')
1607
+ if (!fileSuitesWithImpactedTestsToProjects.has(fileSuite)) {
1608
+ fileSuitesWithImpactedTestsToProjects.set(fileSuite, getSuiteType(impactedTest, 'project'))
1609
+ }
1610
+ }
1611
+ }
1612
+ // If something change in the file, all tests in the file are impacted, hence the () => true filter
1613
+ applyRetriesToTests(
1614
+ fileSuitesWithImpactedTestsToProjects,
1615
+ () => true,
1616
+ [
1617
+ '_ddIsModified',
1618
+ '_ddIsEfdRetry',
1619
+ (test) => (isKnownTestsEnabled && isNewTest(test) ? '_ddIsNew' : null),
1620
+ ],
1621
+ getConfiguredEfdRetryCount(),
1622
+ (copiedTest, originalTest, retryIndex) => {
1623
+ markEfdRetryTest(copiedTest, retryIndex, originalTest)
1624
+ markEfdManagedTest(copiedTest)
1625
+ },
1626
+ getEfdRetryRepeatEachIndex
1627
+ )
1628
+ }
1629
+
1630
+ if (isKnownTestsEnabled) {
1631
+ const newTests = allTests.filter(isNewTest)
1632
+
1633
+ const isFaulty = getIsFaultyEarlyFlakeDetection(
1634
+ allTests.map(test => getTestSuitePath(test._requireFile, rootDir)),
1635
+ knownTests.playwright,
1636
+ earlyFlakeDetectionFaultyThreshold
1637
+ )
1638
+
1639
+ if (isFaulty) {
1640
+ isEarlyFlakeDetectionEnabled = false
1641
+ isKnownTestsEnabled = false
1642
+ isEarlyFlakeDetectionFaulty = true
1643
+ } else {
1644
+ const fileSuitesWithNewTestsToProjects = new Map()
1645
+ for (const newTest of newTests) {
1646
+ newTest._ddIsNew = true
1647
+ if (isEarlyFlakeDetectionEnabled && newTest.expectedStatus !== 'skipped' && !newTest._ddIsModified) {
1648
+ // Prevent ATR or `--retries` from retrying new tests if EFD is enabled
1649
+ newTest.retries = 0
1650
+ markEfdManagedTest(newTest)
1651
+ const fileSuite = getSuiteType(newTest, 'file')
1652
+ if (!fileSuitesWithNewTestsToProjects.has(fileSuite)) {
1653
+ fileSuitesWithNewTestsToProjects.set(fileSuite, getSuiteType(newTest, 'project'))
1431
1654
  }
1432
1655
  }
1433
1656
  }
1434
- // If something change in the file, all tests in the file are impacted, hence the () => true filter
1657
+
1435
1658
  applyRetriesToTests(
1436
- fileSuitesWithImpactedTestsToProjects,
1437
- () => true,
1438
- [
1439
- '_ddIsModified',
1440
- '_ddIsEfdRetry',
1441
- (test) => (isKnownTestsEnabled && isNewTest(test) ? '_ddIsNew' : null),
1442
- ],
1659
+ fileSuitesWithNewTestsToProjects,
1660
+ isNewTest,
1661
+ ['_ddIsNew', '_ddIsEfdRetry'],
1443
1662
  getConfiguredEfdRetryCount(),
1444
1663
  (copiedTest, originalTest, retryIndex) => {
1445
1664
  markEfdRetryTest(copiedTest, retryIndex, originalTest)
@@ -1448,50 +1667,20 @@ addHook({
1448
1667
  getEfdRetryRepeatEachIndex
1449
1668
  )
1450
1669
  }
1670
+ }
1451
1671
 
1452
- if (isKnownTestsEnabled) {
1453
- const newTests = allTests.filter(isNewTest)
1454
-
1455
- const isFaulty = getIsFaultyEarlyFlakeDetection(
1456
- allTests.map(test => getTestSuitePath(test._requireFile, rootDir)),
1457
- knownTests.playwright,
1458
- earlyFlakeDetectionFaultyThreshold
1459
- )
1460
-
1461
- if (isFaulty) {
1462
- isEarlyFlakeDetectionEnabled = false
1463
- isKnownTestsEnabled = false
1464
- isEarlyFlakeDetectionFaulty = true
1465
- } else {
1466
- const fileSuitesWithNewTestsToProjects = new Map()
1467
- for (const newTest of newTests) {
1468
- newTest._ddIsNew = true
1469
- if (isEarlyFlakeDetectionEnabled && newTest.expectedStatus !== 'skipped' && !newTest._ddIsModified) {
1470
- // Prevent ATR or `--retries` from retrying new tests if EFD is enabled
1471
- newTest.retries = 0
1472
- markEfdManagedTest(newTest)
1473
- const fileSuite = getSuiteType(newTest, 'file')
1474
- if (!fileSuitesWithNewTestsToProjects.has(fileSuite)) {
1475
- fileSuitesWithNewTestsToProjects.set(fileSuite, getSuiteType(newTest, 'project'))
1476
- }
1477
- }
1478
- }
1672
+ return createRootSuiteReturnValue
1673
+ }
1479
1674
 
1480
- applyRetriesToTests(
1481
- fileSuitesWithNewTestsToProjects,
1482
- isNewTest,
1483
- ['_ddIsNew', '_ddIsEfdRetry'],
1484
- getConfiguredEfdRetryCount(),
1485
- (copiedTest, originalTest, retryIndex) => {
1486
- markEfdRetryTest(copiedTest, retryIndex, originalTest)
1487
- markEfdManagedTest(copiedTest)
1488
- },
1489
- getEfdRetryRepeatEachIndex
1490
- )
1491
- }
1492
- }
1675
+ addHook({
1676
+ name: 'playwright',
1677
+ file: 'lib/runner/loadUtils.js',
1678
+ versions: ['>=1.38.0 <1.60.0'],
1679
+ }, (loadUtilsPackage) => {
1680
+ const oldCreateRootSuite = loadUtilsPackage.createRootSuite
1493
1681
 
1494
- return createRootSuiteReturnValue
1682
+ async function newCreateRootSuite () {
1683
+ return processRootSuite(await oldCreateRootSuite.apply(this, arguments))
1495
1684
  }
1496
1685
 
1497
1686
  // We need to proxy the createRootSuite function because the function is not configurable
@@ -1505,32 +1694,47 @@ addHook({
1505
1694
  })
1506
1695
  })
1507
1696
 
1697
+ function prepareProcessHostStartRunner (processHost) {
1698
+ processHost._extraEnv = {
1699
+ ...processHost._extraEnv,
1700
+ // Used to detect that we're in a playwright worker
1701
+ DD_PLAYWRIGHT_WORKER: '1',
1702
+ }
1703
+ }
1704
+
1705
+ function finishProcessHostStartRunner (processHost) {
1706
+ if (!processHost.process) {
1707
+ return
1708
+ }
1709
+
1710
+ // We add a new listener to `processHost.process`, which represents the worker
1711
+ processHost.process.on('message', (message) => {
1712
+ if (message?.type === EFD_RETRY_COUNT_REQUEST) {
1713
+ sendEfdRetryCountToWorkerWhenAvailable(processHost.process, message.testId)
1714
+ return
1715
+ }
1716
+ if (message?.type === DD_PROPERTIES_REQUEST) {
1717
+ sendDdPropertiesToWorkerWhenAvailable(processHost.process, message.testId)
1718
+ return
1719
+ }
1720
+ // These messages are [code, payload]. The payload is test data
1721
+ if (Array.isArray(message) && message[0] === PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE) {
1722
+ workerReportCh.publish(message[1])
1723
+ }
1724
+ })
1725
+ }
1726
+
1508
1727
  // main process hook
1509
1728
  addHook({
1510
1729
  name: 'playwright',
1511
1730
  file: 'lib/runner/processHost.js',
1512
- versions: ['>=1.38.0'],
1731
+ versions: ['>=1.38.0 <1.60.0'],
1513
1732
  }, (processHostPackage) => {
1514
1733
  shimmer.wrap(processHostPackage.ProcessHost.prototype, 'startRunner', startRunner => async function () {
1515
- this._extraEnv = {
1516
- ...this._extraEnv,
1517
- // Used to detect that we're in a playwright worker
1518
- DD_PLAYWRIGHT_WORKER: '1',
1519
- }
1734
+ prepareProcessHostStartRunner(this)
1520
1735
 
1521
1736
  const res = await startRunner.apply(this, arguments)
1522
-
1523
- // We add a new listener to `this.process`, which is represents the worker
1524
- this.process.on('message', (message) => {
1525
- if (message?.type === EFD_RETRY_COUNT_REQUEST) {
1526
- sendEfdRetryCountToWorkerWhenAvailable(this.process, message.testId)
1527
- return
1528
- }
1529
- // These messages are [code, payload]. The payload is test data
1530
- if (Array.isArray(message) && message[0] === PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE) {
1531
- workerReportCh.publish(message[1])
1532
- }
1533
- })
1737
+ finishProcessHostStartRunner(this)
1534
1738
 
1535
1739
  return res
1536
1740
  })
@@ -1538,34 +1742,36 @@ addHook({
1538
1742
  return processHostPackage
1539
1743
  })
1540
1744
 
1745
+ async function handlePageGoto (page) {
1746
+ try {
1747
+ if (page && typeof page.evaluate === 'function') {
1748
+ const { isRumInstrumented, isRumActive, rumSamplingRate } = await page.evaluate(detectRum)
1749
+ if (isRumInstrumented && rumSamplingRate < 100 && !isRumActive) {
1750
+ log.debug("RUM was detected on the page, but it isn't active because the sampling rate is below 100%")
1751
+ }
1752
+
1753
+ if (isRumActive) {
1754
+ testPageGotoCh.publish({
1755
+ isRumActive,
1756
+ page,
1757
+ })
1758
+ }
1759
+ }
1760
+ } catch (e) {
1761
+ // ignore errors such as redirects, context destroyed, etc
1762
+ log.error('goto hook error', e)
1763
+ }
1764
+ }
1765
+
1541
1766
  addHook({
1542
1767
  name: 'playwright-core',
1543
1768
  file: 'lib/client/page.js',
1544
- versions: ['>=1.38.0'],
1769
+ versions: ['>=1.38.0 <1.60.0'],
1545
1770
  }, (pagePackage) => {
1546
1771
  shimmer.wrap(pagePackage.Page.prototype, 'goto', goto => async function (url, options) {
1547
1772
  const response = await goto.apply(this, arguments)
1548
1773
 
1549
- const page = this
1550
-
1551
- try {
1552
- if (page) {
1553
- const { isRumInstrumented, isRumActive, rumSamplingRate } = await page.evaluate(detectRum)
1554
- if (isRumInstrumented && rumSamplingRate < 100 && !isRumActive) {
1555
- log.debug("RUM was detected on the page, but it isn't active because the sampling rate is below 100%")
1556
- }
1557
-
1558
- if (isRumActive) {
1559
- testPageGotoCh.publish({
1560
- isRumActive,
1561
- page,
1562
- })
1563
- }
1564
- }
1565
- } catch (e) {
1566
- // ignore errors such as redirects, context destroyed, etc
1567
- log.error('goto hook error', e)
1568
- }
1774
+ await handlePageGoto(this)
1569
1775
 
1570
1776
  return response
1571
1777
  })
@@ -1573,17 +1779,19 @@ addHook({
1573
1779
  return pagePackage
1574
1780
  })
1575
1781
 
1576
- // Only in worker
1577
- addHook({
1578
- name: 'playwright',
1579
- file: 'lib/worker/workerMain.js',
1580
- versions: ['>=1.38.0'],
1581
- }, (workerPackage) => {
1782
+ function instrumentWorkerMainMethods (workerMain) {
1783
+ if (!workerMain || workerMain[kDdPlaywrightWorkerInstrumented] ||
1784
+ typeof workerMain._runTest !== 'function' || typeof workerMain.dispatchEvent !== 'function') {
1785
+ return workerMain
1786
+ }
1787
+
1788
+ Object.defineProperty(workerMain, kDdPlaywrightWorkerInstrumented, { value: true })
1789
+
1582
1790
  // we assume there's only a test running at a time
1583
1791
  let steps = []
1584
1792
  const stepInfoByStepId = {}
1585
1793
 
1586
- shimmer.wrap(workerPackage.WorkerMain.prototype, '_runTest', _runTest => async function (test) {
1794
+ shimmer.wrap(workerMain, '_runTest', _runTest => async function (test) {
1587
1795
  await waitForEfdRetryCount(test)
1588
1796
  if (shouldSkipEfdRetry(test)) {
1589
1797
  test._ddShouldSkipEfdRetry = true
@@ -1619,6 +1827,27 @@ addHook({
1619
1827
  browserName,
1620
1828
  }
1621
1829
  testToCtx.set(test, testCtx)
1830
+
1831
+ // Wait for ddProperties to be received and processed. The main process sends
1832
+ // this during Playwright's testEnd event, which can happen before _runTest
1833
+ // resolves in 1.60 when retry clones run across multiple workers.
1834
+ let hasDdProperties = false
1835
+ const ddPropertiesDeferred = {}
1836
+ const ddPropertiesPromise = new Promise(resolve => {
1837
+ ddPropertiesDeferred.resolve = resolve
1838
+ })
1839
+ const ddPropertiesMessageHandler = ({ type, testId, properties }) => {
1840
+ if (type === DD_PROPERTIES_RESPONSE && testId === test.id) {
1841
+ hasDdProperties = true
1842
+ if (properties) {
1843
+ Object.assign(test, properties)
1844
+ }
1845
+ process.removeListener('message', ddPropertiesMessageHandler)
1846
+ ddPropertiesDeferred.resolve()
1847
+ }
1848
+ }
1849
+ process.on('message', ddPropertiesMessageHandler)
1850
+
1622
1851
  // TODO - In the future we may need to implement a mechanism to send test properties
1623
1852
  // to the worker process before _runTest is called
1624
1853
  testStartCh.runStores(testCtx, () => {
@@ -1691,6 +1920,16 @@ addHook({
1691
1920
  }
1692
1921
  }
1693
1922
 
1923
+ if (!hasDdProperties && process.send) {
1924
+ process.send({
1925
+ type: DD_PROPERTIES_REQUEST,
1926
+ testId: test.id,
1927
+ })
1928
+ } else if (!hasDdProperties) {
1929
+ process.removeListener('message', ddPropertiesMessageHandler)
1930
+ ddPropertiesDeferred.resolve()
1931
+ }
1932
+
1694
1933
  // testInfo.errors could be better than "error",
1695
1934
  // which will only include timeout error (even though the test failed because of a different error)
1696
1935
 
@@ -1705,26 +1944,17 @@ addHook({
1705
1944
  onDone = resolve
1706
1945
  })
1707
1946
 
1708
- // Wait for ddProperties to be received and processed
1709
- // Create a promise that will be resolved when the properties are received
1710
- const ddPropertiesPromise = new Promise(resolve => {
1711
- const messageHandler = ({ type, testId, properties }) => {
1712
- if (type === 'ddProperties' && testId === test.id) {
1713
- // Apply the properties to the test object
1714
- if (properties) {
1715
- Object.assign(test, properties)
1716
- }
1717
- process.removeListener('message', messageHandler)
1718
- resolve()
1719
- }
1720
- }
1721
-
1722
- // Add the listener
1723
- process.on('message', messageHandler)
1947
+ // Wait for the properties to be received, but do not block the worker forever if IPC fails.
1948
+ const ddPropertiesTimeoutPromise = new Promise(resolve => {
1949
+ const ddPropertiesTimeout = realSetTimeout(() => {
1950
+ process.removeListener('message', ddPropertiesMessageHandler)
1951
+ resolve()
1952
+ }, DD_PROPERTIES_TIMEOUT)
1953
+ ddPropertiesPromise.then(() => {
1954
+ realClearTimeout(ddPropertiesTimeout)
1955
+ })
1724
1956
  })
1725
-
1726
- // Wait for the properties to be received
1727
- await ddPropertiesPromise
1957
+ await Promise.race([ddPropertiesPromise, ddPropertiesTimeoutPromise])
1728
1958
 
1729
1959
  const finalStatus = getFinalStatus({
1730
1960
  isFinalExecution: test._ddIsFinalExecution,
@@ -1770,7 +2000,7 @@ addHook({
1770
2000
 
1771
2001
  // We reproduce what happens in `Dispatcher#_onStepBegin` and `Dispatcher#_onStepEnd`,
1772
2002
  // since `startTime` and `duration` are not available directly in the worker process
1773
- shimmer.wrap(workerPackage.WorkerMain.prototype, 'dispatchEvent', dispatchEvent => function (event, payload) {
2003
+ shimmer.wrap(workerMain, 'dispatchEvent', dispatchEvent => function (event, payload) {
1774
2004
  if (event === 'stepBegin') {
1775
2005
  stepInfoByStepId[payload.stepId] = {
1776
2006
  startTime: payload.wallTime,
@@ -1791,6 +2021,16 @@ addHook({
1791
2021
  return dispatchEvent.apply(this, arguments)
1792
2022
  })
1793
2023
 
2024
+ return workerMain
2025
+ }
2026
+
2027
+ // Only in worker
2028
+ addHook({
2029
+ name: 'playwright',
2030
+ file: 'lib/worker/workerMain.js',
2031
+ versions: ['>=1.38.0 <1.60.0'],
2032
+ }, (workerPackage) => {
2033
+ instrumentWorkerMainMethods(workerPackage.WorkerMain.prototype)
1794
2034
  return workerPackage
1795
2035
  })
1796
2036
 
@@ -1839,7 +2079,7 @@ function generateSummaryWrapper (generateSummary) {
1839
2079
  addHook({
1840
2080
  name: 'playwright',
1841
2081
  file: 'lib/reporters/base.js',
1842
- versions: ['>=1.38.0'],
2082
+ versions: ['>=1.38.0 <1.60.0'],
1843
2083
  }, (reportersPackage) => {
1844
2084
  // v1.50.0 changed the name of the base reporter from BaseReporter to TerminalReporter
1845
2085
  if (reportersPackage.TerminalReporter) {