dd-trace 6.4.0 → 6.6.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 (185) hide show
  1. package/README.electron.md +7 -0
  2. package/README.md +17 -0
  3. package/ci/diagnose.js +2100 -0
  4. package/ci/init.js +23 -2
  5. package/ci/runbook.md +198 -0
  6. package/ci/test-optimization-validation/approval-artifacts.js +143 -0
  7. package/ci/test-optimization-validation/approval.js +299 -0
  8. package/ci/test-optimization-validation/artifact-id.js +20 -0
  9. package/ci/test-optimization-validation/bounded-json.js +121 -0
  10. package/ci/test-optimization-validation/ci-command-candidate.js +83 -0
  11. package/ci/test-optimization-validation/ci-discovery.js +181 -0
  12. package/ci/test-optimization-validation/ci-remediation.js +210 -0
  13. package/ci/test-optimization-validation/cli.js +903 -0
  14. package/ci/test-optimization-validation/command-blocker.js +81 -0
  15. package/ci/test-optimization-validation/command-output-policy.js +206 -0
  16. package/ci/test-optimization-validation/command-runner.js +707 -0
  17. package/ci/test-optimization-validation/command-suitability.js +471 -0
  18. package/ci/test-optimization-validation/executable-approval.js +48 -0
  19. package/ci/test-optimization-validation/executable.js +480 -0
  20. package/ci/test-optimization-validation/generated-file-policy.js +63 -0
  21. package/ci/test-optimization-validation/generated-files.js +351 -0
  22. package/ci/test-optimization-validation/generated-verifier.js +196 -0
  23. package/ci/test-optimization-validation/init-probe-preload.js +163 -0
  24. package/ci/test-optimization-validation/init-probe.js +246 -0
  25. package/ci/test-optimization-validation/late-initialization.js +63 -0
  26. package/ci/test-optimization-validation/local-command.js +163 -0
  27. package/ci/test-optimization-validation/manifest-loader.js +133 -0
  28. package/ci/test-optimization-validation/manifest-scaffold.js +738 -0
  29. package/ci/test-optimization-validation/manifest-schema.js +862 -0
  30. package/ci/test-optimization-validation/offline-fixtures.js +327 -0
  31. package/ci/test-optimization-validation/offline-output.js +406 -0
  32. package/ci/test-optimization-validation/payload-normalizer.js +72 -0
  33. package/ci/test-optimization-validation/plan-writer.js +1120 -0
  34. package/ci/test-optimization-validation/preflight-runner.js +114 -0
  35. package/ci/test-optimization-validation/redaction.js +331 -0
  36. package/ci/test-optimization-validation/report-writer.js +1508 -0
  37. package/ci/test-optimization-validation/safe-files.js +203 -0
  38. package/ci/test-optimization-validation/scenarios/auto-test-retries.js +159 -0
  39. package/ci/test-optimization-validation/scenarios/basic-reporting.js +657 -0
  40. package/ci/test-optimization-validation/scenarios/ci-wiring.js +780 -0
  41. package/ci/test-optimization-validation/scenarios/early-flake-detection.js +141 -0
  42. package/ci/test-optimization-validation/scenarios/helpers.js +539 -0
  43. package/ci/test-optimization-validation/scenarios/test-management.js +218 -0
  44. package/ci/test-optimization-validation/setup-runner.js +97 -0
  45. package/ci/test-optimization-validation/static-diagnosis.js +159 -0
  46. package/ci/test-optimization-validation/test-output.js +129 -0
  47. package/ci/test-optimization-validation-manifest.schema.json +1 -0
  48. package/ci/validate-test-optimization.js +3 -0
  49. package/ext/exporters.js +1 -0
  50. package/index.d.ts +121 -8
  51. package/init.js +18 -0
  52. package/initialize.mjs +1 -1
  53. package/loader-hook.mjs +28 -18
  54. package/openfeature.d.ts +1 -0
  55. package/openfeature.js +4 -0
  56. package/package.json +11 -8
  57. package/packages/datadog-instrumentations/src/ai.js +8 -2
  58. package/packages/datadog-instrumentations/src/child_process.js +1 -1
  59. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +1 -1
  60. package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +9 -3
  61. package/packages/datadog-instrumentations/src/cucumber.js +31 -19
  62. package/packages/datadog-instrumentations/src/cypress-config.js +398 -52
  63. package/packages/datadog-instrumentations/src/fastify.js +7 -11
  64. package/packages/datadog-instrumentations/src/helpers/channel.js +74 -0
  65. package/packages/datadog-instrumentations/src/helpers/hook.js +27 -6
  66. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +8 -8
  67. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +28 -2
  68. package/packages/datadog-instrumentations/src/jest.js +44 -17
  69. package/packages/datadog-instrumentations/src/mariadb.js +1 -1
  70. package/packages/datadog-instrumentations/src/mocha/main.js +79 -32
  71. package/packages/datadog-instrumentations/src/mocha/utils.js +0 -4
  72. package/packages/datadog-instrumentations/src/mongodb.js +3 -3
  73. package/packages/datadog-instrumentations/src/mongoose.js +3 -3
  74. package/packages/datadog-instrumentations/src/mquery.js +2 -2
  75. package/packages/datadog-instrumentations/src/mysql.js +1 -1
  76. package/packages/datadog-instrumentations/src/next.js +24 -0
  77. package/packages/datadog-instrumentations/src/nyc.js +0 -2
  78. package/packages/datadog-instrumentations/src/oracledb.js +2 -2
  79. package/packages/datadog-instrumentations/src/pg.js +2 -2
  80. package/packages/datadog-instrumentations/src/playwright.js +162 -46
  81. package/packages/datadog-instrumentations/src/process.js +3 -0
  82. package/packages/datadog-instrumentations/src/router.js +18 -8
  83. package/packages/datadog-instrumentations/src/selenium.js +52 -26
  84. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +3 -3
  85. package/packages/datadog-instrumentations/src/vitest-main.js +14 -28
  86. package/packages/datadog-instrumentations/src/vitest-util.js +0 -7
  87. package/packages/datadog-instrumentations/src/vitest-worker.js +5 -8
  88. package/packages/datadog-instrumentations/src/ws.js +2 -1
  89. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +2 -2
  90. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +2 -1
  91. package/packages/datadog-plugin-aws-sdk/src/util.js +2 -2
  92. package/packages/datadog-plugin-cucumber/src/index.js +3 -2
  93. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -57
  94. package/packages/datadog-plugin-cypress/src/index.js +6 -1
  95. package/packages/datadog-plugin-cypress/src/support.js +92 -26
  96. package/packages/datadog-plugin-electron/src/ipc.js +1 -1
  97. package/packages/datadog-plugin-graphql/src/execute.js +87 -45
  98. package/packages/datadog-plugin-graphql/src/validate.js +2 -2
  99. package/packages/datadog-plugin-http2/src/client.js +1 -1
  100. package/packages/datadog-plugin-langchain/src/handlers/embedding.js +2 -1
  101. package/packages/datadog-plugin-langchain/src/handlers/language_models.js +2 -1
  102. package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
  103. package/packages/datadog-plugin-mocha/src/index.js +2 -1
  104. package/packages/datadog-plugin-playwright/src/index.js +181 -65
  105. package/packages/datadog-plugin-router/src/index.js +11 -2
  106. package/packages/datadog-plugin-selenium/src/index.js +5 -36
  107. package/packages/datadog-plugin-vitest/src/index.js +5 -5
  108. package/packages/datadog-plugin-ws/src/close.js +2 -1
  109. package/packages/datadog-plugin-ws/src/producer.js +2 -1
  110. package/packages/datadog-plugin-ws/src/receiver.js +2 -1
  111. package/packages/datadog-plugin-ws/src/server.js +2 -1
  112. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  113. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +2 -2
  114. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +3 -1
  115. package/packages/dd-trace/src/azure_metadata.js +2 -1
  116. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +93 -28
  117. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
  118. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/index.js +160 -0
  119. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +288 -0
  120. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/payload-projection.js +84 -0
  121. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +369 -0
  122. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +60 -0
  123. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +41 -3
  124. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -2
  125. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +1 -1
  126. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +6 -2
  127. package/packages/dd-trace/src/ci-visibility/rum.js +7 -0
  128. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
  129. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
  130. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +137 -9
  131. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +168 -8
  132. package/packages/dd-trace/src/ci-visibility/test-screenshot.js +90 -0
  133. package/packages/dd-trace/src/config/generated-config-types.d.ts +16 -0
  134. package/packages/dd-trace/src/config/helper.js +1 -0
  135. package/packages/dd-trace/src/config/index.js +10 -0
  136. package/packages/dd-trace/src/config/supported-configurations.json +66 -0
  137. package/packages/dd-trace/src/exporter.js +2 -0
  138. package/packages/dd-trace/src/exporters/common/client-library-headers.js +21 -0
  139. package/packages/dd-trace/src/exporters/common/request.js +59 -30
  140. package/packages/dd-trace/src/exporters/common/url.js +15 -1
  141. package/packages/dd-trace/src/feature-registry.js +10 -3
  142. package/packages/dd-trace/src/llmobs/experiments/client.js +113 -0
  143. package/packages/dd-trace/src/llmobs/experiments/dataset.js +154 -0
  144. package/packages/dd-trace/src/llmobs/experiments/experiment.js +283 -0
  145. package/packages/dd-trace/src/llmobs/experiments/index.js +152 -0
  146. package/packages/dd-trace/src/llmobs/experiments/noop.js +30 -0
  147. package/packages/dd-trace/src/llmobs/experiments/result.js +34 -0
  148. package/packages/dd-trace/src/llmobs/noop.js +6 -0
  149. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +20 -3
  150. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +2 -2
  151. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  152. package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +8 -0
  153. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +48 -14
  154. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +43 -0
  155. package/packages/dd-trace/src/llmobs/sdk.js +17 -0
  156. package/packages/dd-trace/src/llmobs/tagger.js +68 -0
  157. package/packages/dd-trace/src/llmobs/telemetry.js +2 -1
  158. package/packages/dd-trace/src/llmobs/util.js +32 -0
  159. package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +322 -0
  160. package/packages/dd-trace/src/openfeature/configuration_source.js +90 -0
  161. package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -23
  162. package/packages/dd-trace/src/openfeature/index.js +0 -2
  163. package/packages/dd-trace/src/openfeature/noop.js +1 -28
  164. package/packages/dd-trace/src/openfeature/register.js +16 -27
  165. package/packages/dd-trace/src/openfeature/remote_config.js +15 -18
  166. package/packages/dd-trace/src/openfeature/require-provider.js +18 -0
  167. package/packages/dd-trace/src/opentelemetry/span-ending-hook.js +10 -0
  168. package/packages/dd-trace/src/opentelemetry/span.js +5 -0
  169. package/packages/dd-trace/src/plugins/ci_plugin.js +20 -6
  170. package/packages/dd-trace/src/plugins/util/git.js +3 -2
  171. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +29 -5
  172. package/packages/dd-trace/src/plugins/util/test.js +56 -4
  173. package/packages/dd-trace/src/profiling/profilers/space.js +1 -1
  174. package/packages/dd-trace/src/profiling/profilers/wall.js +1 -0
  175. package/packages/dd-trace/src/proxy.js +84 -7
  176. package/packages/dd-trace/src/span_processor.js +5 -0
  177. package/packages/dd-trace/src/standalone/index.js +0 -14
  178. package/packages/dd-trace/src/telemetry/send-data.js +2 -2
  179. package/packages/dd-trace/src/util.js +26 -0
  180. package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
  181. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  182. package/vendor/dist/protobufjs/index.js +1 -1
  183. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  184. package/vendor/dist/shell-quote/index.js +1 -1
  185. package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +0 -17
@@ -0,0 +1,203 @@
1
+ 'use strict'
2
+
3
+ const crypto = require('node:crypto')
4
+ const fs = require('node:fs')
5
+ const path = require('node:path')
6
+
7
+ /**
8
+ * Creates a directory while refusing symlink components and paths outside the allowed root.
9
+ *
10
+ * @param {string} root allowed root
11
+ * @param {string} directory directory to create or validate
12
+ * @param {string} label customer-facing path label
13
+ * @param {{allowRootSymlink?: boolean}} [options] validation options
14
+ */
15
+ function ensureSafeDirectory (root, directory, label, options = {}) {
16
+ const lexicalRoot = path.resolve(root)
17
+ const resolvedDirectory = path.resolve(directory)
18
+ const rootStat = fs.lstatSync(lexicalRoot)
19
+ if (rootStat.isSymbolicLink() && !options.allowRootSymlink) {
20
+ throw new Error(`Refusing ${label} because its allowed root is a symbolic link: ${lexicalRoot}`)
21
+ }
22
+ if (!rootStat.isDirectory() && !rootStat.isSymbolicLink()) {
23
+ throw new Error(`Refusing ${label} because its allowed root is not a directory: ${lexicalRoot}`)
24
+ }
25
+ if (!isPathInside(lexicalRoot, resolvedDirectory)) {
26
+ throw new Error(`Refusing ${label} outside allowed root: ${resolvedDirectory}`)
27
+ }
28
+
29
+ let current = lexicalRoot
30
+ const relative = path.relative(lexicalRoot, resolvedDirectory)
31
+ for (const segment of relative ? relative.split(path.sep) : []) {
32
+ current = path.join(current, segment)
33
+ let stat
34
+ try {
35
+ stat = fs.lstatSync(current)
36
+ } catch (error) {
37
+ if (error.code !== 'ENOENT') throw error
38
+ fs.mkdirSync(current)
39
+ stat = fs.lstatSync(current)
40
+ }
41
+
42
+ if (stat.isSymbolicLink()) {
43
+ throw new Error(`Refusing ${label} through symbolic link: ${current}`)
44
+ }
45
+ if (!stat.isDirectory()) {
46
+ throw new Error(`Refusing ${label} through non-directory path: ${current}`)
47
+ }
48
+ }
49
+
50
+ const physicalRoot = fs.realpathSync(lexicalRoot)
51
+ const physicalDirectory = fs.realpathSync(resolvedDirectory)
52
+ if (!isPathInside(physicalRoot, physicalDirectory)) {
53
+ throw new Error(`Refusing ${label} outside physical allowed root: ${resolvedDirectory}`)
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Writes a regular file without following a symbolic-link target.
59
+ *
60
+ * @param {string} root allowed root
61
+ * @param {string} filename output filename
62
+ * @param {string|Buffer} data output data
63
+ * @param {string} label customer-facing path label
64
+ */
65
+ function writeFileSafely (root, filename, data, label) {
66
+ const resolvedFilename = path.resolve(filename)
67
+ const parent = path.dirname(resolvedFilename)
68
+ ensureSafeDirectory(root, parent, label)
69
+ const parentIdentity = getDirectoryIdentity(parent, label)
70
+ const temporaryFilename = path.join(
71
+ parent,
72
+ `.${path.basename(filename)}.${crypto.randomBytes(12).toString('hex')}.tmp`
73
+ )
74
+
75
+ try {
76
+ openAndWrite(root, temporaryFilename, data, label, fs.constants.O_EXCL)
77
+ assertDirectoryIdentity(parent, parentIdentity, label)
78
+ fs.renameSync(temporaryFilename, resolvedFilename)
79
+ } catch (error) {
80
+ removeTemporaryFile(temporaryFilename, parent, parentIdentity)
81
+ throw error
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Creates a new regular file without following or replacing an existing path.
87
+ *
88
+ * @param {string} root allowed root
89
+ * @param {string} filename output filename
90
+ * @param {string|Buffer} data output data
91
+ * @param {string} label customer-facing path label
92
+ */
93
+ function createFileSafely (root, filename, data, label) {
94
+ openAndWrite(root, filename, data, label, fs.constants.O_EXCL)
95
+ }
96
+
97
+ /**
98
+ * Opens and writes a file with no-follow semantics.
99
+ *
100
+ * @param {string} root allowed root
101
+ * @param {string} filename output filename
102
+ * @param {string|Buffer} data output data
103
+ * @param {string} label customer-facing path label
104
+ * @param {number} creationFlag file creation mode
105
+ */
106
+ function openAndWrite (root, filename, data, label, creationFlag) {
107
+ const resolvedFilename = path.resolve(filename)
108
+ ensureSafeDirectory(root, path.dirname(resolvedFilename), label)
109
+ refuseSymbolicLink(resolvedFilename, label)
110
+
111
+ const flags = fs.constants.O_WRONLY |
112
+ fs.constants.O_CREAT |
113
+ creationFlag |
114
+ (fs.constants.O_NOFOLLOW || 0)
115
+ const file = fs.openSync(resolvedFilename, flags, 0o600)
116
+ try {
117
+ const stat = fs.fstatSync(file)
118
+ if (!stat.isFile() || stat.nlink !== 1) {
119
+ throw new Error(`Refusing ${label} because its output is not a private regular file: ${resolvedFilename}`)
120
+ }
121
+ fs.writeFileSync(file, data)
122
+ } finally {
123
+ fs.closeSync(file)
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Captures one directory identity without accepting a symbolic link.
129
+ *
130
+ * @param {string} directory directory path
131
+ * @param {string} label customer-facing path label
132
+ * @returns {{dev: number, ino: number}} directory identity
133
+ */
134
+ function getDirectoryIdentity (directory, label) {
135
+ const stat = fs.lstatSync(directory)
136
+ if (!stat.isDirectory() || stat.isSymbolicLink()) {
137
+ throw new Error(`Refusing ${label} because its parent is not a regular directory: ${directory}`)
138
+ }
139
+ return { dev: stat.dev, ino: stat.ino }
140
+ }
141
+
142
+ /**
143
+ * Refuses replacement of a parent directory between safe creation and publication.
144
+ *
145
+ * @param {string} directory directory path
146
+ * @param {{dev: number, ino: number}} expected expected identity
147
+ * @param {string} label customer-facing path label
148
+ */
149
+ function assertDirectoryIdentity (directory, expected, label) {
150
+ const current = getDirectoryIdentity(directory, label)
151
+ if (current.dev !== expected.dev || current.ino !== expected.ino) {
152
+ throw new Error(`Refusing ${label} because its parent directory changed during the write: ${directory}`)
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Removes only the validator-created temporary regular file or symbolic link.
158
+ *
159
+ * @param {string} filename temporary filename
160
+ * @param {string} parent expected parent directory
161
+ * @param {{dev: number, ino: number}} parentIdentity expected parent identity
162
+ */
163
+ function removeTemporaryFile (filename, parent, parentIdentity) {
164
+ try {
165
+ assertDirectoryIdentity(parent, parentIdentity, 'temporary file cleanup')
166
+ const stat = fs.lstatSync(filename)
167
+ if (stat.isFile() || stat.isSymbolicLink()) fs.unlinkSync(filename)
168
+ } catch {}
169
+ }
170
+
171
+ /**
172
+ * Refuses a final path component that is a symbolic link.
173
+ *
174
+ * @param {string} filename candidate filename
175
+ * @param {string} label customer-facing path label
176
+ */
177
+ function refuseSymbolicLink (filename, label) {
178
+ try {
179
+ if (fs.lstatSync(filename).isSymbolicLink()) {
180
+ throw new Error(`Refusing ${label} symbolic-link target: ${filename}`)
181
+ }
182
+ } catch (error) {
183
+ if (error.code !== 'ENOENT') throw error
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Checks lexical path containment.
189
+ *
190
+ * @param {string} root allowed root
191
+ * @param {string} filename candidate path
192
+ * @returns {boolean} true when the candidate is inside the root
193
+ */
194
+ function isPathInside (root, filename) {
195
+ const relative = path.relative(root, filename)
196
+ return relative === '' || (relative && !relative.startsWith('..') && !path.isAbsolute(relative))
197
+ }
198
+
199
+ module.exports = {
200
+ createFileSafely,
201
+ ensureSafeDirectory,
202
+ writeFileSafely,
203
+ }
@@ -0,0 +1,159 @@
1
+ 'use strict'
2
+
3
+ const {
4
+ discoverScenarioTests,
5
+ discoveryEvidence,
6
+ error,
7
+ failWithDebugRerun,
8
+ pass,
9
+ prepareGeneratedScenario,
10
+ requireGeneratedScenario,
11
+ runInstrumentedCommand,
12
+ testEventSamples,
13
+ testsForDiscoveredScenario,
14
+ } = require('./helpers')
15
+
16
+ async function runAutoTestRetries ({ framework, out, options }) {
17
+ const scenarioName = 'atr'
18
+ const skipResult = requireGeneratedScenario(framework, 'atr-fail-once', scenarioName)
19
+ if (skipResult) return skipResult
20
+
21
+ let outDir
22
+ try {
23
+ const { scenario } = await prepareGeneratedScenario(framework, 'atr-fail-once')
24
+ const discovery = await discoverScenarioTests({ framework, out, scenarioName, scenario, options })
25
+ if (discovery.tests.length === 0) {
26
+ return failWithDebugRerun({
27
+ command: scenario.runCommand,
28
+ diagnosis: 'The fail-once generated test was not reported during baseline identity discovery.',
29
+ evidence: discoveryEvidence(discovery),
30
+ framework,
31
+ options,
32
+ out,
33
+ outDir: discovery.outDir,
34
+ scenarioName,
35
+ })
36
+ }
37
+
38
+ const fixtureConfig = {
39
+ settings: {
40
+ flaky_test_retries_enabled: true,
41
+ },
42
+ }
43
+
44
+ const run = await runInstrumentedCommand({
45
+ framework,
46
+ out,
47
+ scenarioName,
48
+ command: scenario.runCommand,
49
+ options,
50
+ fixtureConfig,
51
+ })
52
+ outDir = run.outDir
53
+
54
+ const tests = testsForDiscoveredScenario(run.events, scenario, discovery)
55
+ const autoTestRetryEvents = tests.filter(test => test.retryReason === 'auto_test_retry')
56
+ const externalRetryEvents = tests.filter(test => test.isRetry && test.retryReason !== 'auto_test_retry')
57
+ const evidence = {
58
+ ...discoveryEvidence(discovery),
59
+ commandExitCode: run.result.exitCode,
60
+ settingsLoadedFromCache: run.offline.inputs.settings?.status === 'loaded',
61
+ matchingTestEvents: tests.length,
62
+ autoTestRetryEvents: autoTestRetryEvents.length,
63
+ externalRetryEvents: externalRetryEvents.length,
64
+ failedAttempts: tests.filter(test => test.testStatus === 'fail' || test.error === 1).length,
65
+ passedAttempts: tests.filter(test => test.testStatus === 'pass').length,
66
+ samples: testEventSamples(tests),
67
+ }
68
+
69
+ if (!evidence.settingsLoadedFromCache) {
70
+ return failWithDebugRerun({
71
+ command: scenario.runCommand,
72
+ fixtureConfig,
73
+ diagnosis: 'Auto Test Retries settings were not loaded from the offline cache fixture.',
74
+ evidence,
75
+ framework,
76
+ options,
77
+ out,
78
+ outDir,
79
+ scenarioName,
80
+ })
81
+ }
82
+
83
+ if (run.result.exitCode !== 0) {
84
+ return failWithDebugRerun({
85
+ command: scenario.runCommand,
86
+ fixtureConfig,
87
+ diagnosis: getAutoTestRetriesFailureDiagnosis(framework, evidence),
88
+ evidence,
89
+ framework,
90
+ options,
91
+ out,
92
+ outDir,
93
+ scenarioName,
94
+ })
95
+ }
96
+
97
+ if (tests.length < 2 || autoTestRetryEvents.length === 0) {
98
+ return failWithDebugRerun({
99
+ command: scenario.runCommand,
100
+ fixtureConfig,
101
+ diagnosis: getAutoTestRetriesFailureDiagnosis(framework, evidence),
102
+ evidence,
103
+ framework,
104
+ options,
105
+ out,
106
+ outDir,
107
+ scenarioName,
108
+ })
109
+ }
110
+
111
+ return pass(
112
+ framework,
113
+ scenarioName,
114
+ 'The fail-once generated test was retried and the command passed.',
115
+ evidence,
116
+ outDir
117
+ )
118
+ } catch (err) {
119
+ return error(framework, scenarioName, err, outDir)
120
+ }
121
+ }
122
+
123
+ function getAutoTestRetriesFailureDiagnosis (framework, evidence) {
124
+ const frameworkName = getFrameworkName(framework)
125
+ const retryTagSummary = getRetryTagSummary(evidence.autoTestRetryEvents)
126
+ if (evidence.autoTestRetryEvents > 0 || evidence.failedAttempts > 1) {
127
+ return 'Auto Test Retries executed for the generated test, but every attempt failed. Observed ' +
128
+ `${formatAttemptCount(evidence.failedAttempts, 'failed')}, ` +
129
+ `${formatAttemptCount(evidence.passedAttempts, 'passed retry')}, and ${retryTagSummary}. ` +
130
+ 'Review the generated test failure because retry execution itself was observed.'
131
+ }
132
+ return 'Auto Test Retries was enabled, and the generated failing test was reported, but ' +
133
+ `${frameworkName} ` +
134
+ `did not execute a retry attempt. Observed ${formatAttemptCount(evidence.failedAttempts, 'failed')}, ` +
135
+ `${formatAttemptCount(evidence.passedAttempts, 'passed retry')}, and ${retryTagSummary}.`
136
+ }
137
+
138
+ function formatAttemptCount (count, label) {
139
+ return `${count} ${label} attempt${count === 1 ? '' : 's'}`
140
+ }
141
+
142
+ function getRetryTagSummary (count) {
143
+ if (count === 0) return 'no test.retry_reason=auto_test_retry tag'
144
+ if (count === 1) return '1 event tagged with test.retry_reason=auto_test_retry'
145
+ return `${count} events tagged with test.retry_reason=auto_test_retry`
146
+ }
147
+
148
+ function getFrameworkName (framework) {
149
+ return {
150
+ cucumber: 'Cucumber',
151
+ cypress: 'Cypress',
152
+ jest: 'Jest',
153
+ mocha: 'Mocha',
154
+ playwright: 'Playwright',
155
+ vitest: 'Vitest',
156
+ }[framework.framework] || 'the test runner'
157
+ }
158
+
159
+ module.exports = { getAutoTestRetriesFailureDiagnosis, runAutoTestRetries }