dd-trace 6.11.0 → 6.12.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 (55) hide show
  1. package/LICENSE-3rdparty.csv +5 -0
  2. package/index.d.ts +20 -3
  3. package/package.json +18 -17
  4. package/packages/datadog-instrumentations/src/cucumber.js +4 -4
  5. package/packages/datadog-instrumentations/src/mocha/utils.js +5 -2
  6. package/packages/datadog-instrumentations/src/playwright-reporter.js +149 -1
  7. package/packages/datadog-instrumentations/src/playwright.js +33 -33
  8. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +6 -8
  9. package/packages/datadog-plugin-playwright/src/index.js +4 -0
  10. package/packages/dd-trace/src/appsec/reporter.js +2 -0
  11. package/packages/dd-trace/src/appsec/telemetry/index.js +13 -0
  12. package/packages/dd-trace/src/appsec/telemetry/rasp.js +16 -0
  13. package/packages/dd-trace/src/appsec/telemetry/waf.js +24 -0
  14. package/packages/dd-trace/src/carrier.js +107 -48
  15. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +5 -5
  16. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +2 -0
  17. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +5 -3
  18. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +4 -2
  19. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +6 -4
  20. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +5 -3
  21. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
  22. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +4 -2
  23. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +4 -2
  24. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +5 -5
  25. package/packages/dd-trace/src/debugger/constants.js +1 -0
  26. package/packages/dd-trace/src/debugger/devtools_client/condition.js +1 -1
  27. package/packages/dd-trace/src/debugger/devtools_client/index.js +3 -10
  28. package/packages/dd-trace/src/debugger/index.js +4 -2
  29. package/packages/dd-trace/src/debugger/inspect-segment.js +154 -0
  30. package/packages/dd-trace/src/evp_proxy/constants.js +8 -0
  31. package/packages/dd-trace/src/evp_proxy/direct.js +64 -0
  32. package/packages/dd-trace/src/evp_proxy/discovery.js +133 -0
  33. package/packages/dd-trace/src/evp_proxy/path.js +22 -0
  34. package/packages/dd-trace/src/exporters/common/request.js +5 -2
  35. package/packages/dd-trace/src/llmobs/experiments/client.js +6 -2
  36. package/packages/dd-trace/src/llmobs/experiments/dataset.js +249 -11
  37. package/packages/dd-trace/src/llmobs/experiments/experiment.js +23 -10
  38. package/packages/dd-trace/src/llmobs/experiments/index.js +18 -6
  39. package/packages/dd-trace/src/llmobs/experiments/noop.js +64 -4
  40. package/packages/dd-trace/src/llmobs/experiments/util.js +60 -4
  41. package/packages/dd-trace/src/openfeature/constants/constants.js +0 -18
  42. package/packages/dd-trace/src/openfeature/index.js +7 -4
  43. package/packages/dd-trace/src/openfeature/writers/base.js +153 -11
  44. package/packages/dd-trace/src/openfeature/writers/exposures.js +62 -14
  45. package/packages/dd-trace/src/openfeature/writers/util.js +97 -15
  46. package/packages/dd-trace/src/opentracing/propagation/text_map.js +22 -13
  47. package/packages/dd-trace/src/telemetry/metrics.js +62 -8
  48. package/packages/dd-trace/src/telemetry/send-data.js +1 -0
  49. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  50. package/vendor/dist/https-proxy-agent/LICENSE +22 -0
  51. package/vendor/dist/https-proxy-agent/index.js +4 -0
  52. package/vendor/dist/protobufjs/index.js +1 -1
  53. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  54. package/vendor/dist/proxy-from-env/LICENSE +20 -0
  55. package/vendor/dist/proxy-from-env/index.js +1 -0
@@ -44,18 +44,21 @@
44
44
  "@oxc-project/types","https://github.com/oxc-project/oxc","['MIT']","['Boshen and oxc contributors']"
45
45
  "@tybys/wasm-util","https://github.com/toyobayashi/wasm-util","['MIT']","['toyobayashi']"
46
46
  "@types/estree","https://github.com/DefinitelyTyped/DefinitelyTyped","['MIT']","['DefinitelyTyped']"
47
+ "agent-base","https://github.com/TooTallNate/proxy-agents","['MIT']","['Nathan Rajlich']"
47
48
  "argparse","https://github.com/nodeca/argparse","['Python-2.0']","['nodeca']"
48
49
  "astring","https://github.com/davidbonnet/astring","['MIT']","['David Bonnet']"
49
50
  "cjs-module-lexer","https://github.com/nodejs/cjs-module-lexer","['MIT']","['Guy Bedford']"
50
51
  "crypto-randomuuid","npm:crypto-randomuuid","['MIT']","['Stephen Belanger']"
51
52
  "dc-polyfill","https://github.com/DataDog/dc-polyfill","['MIT']","['Thomas Hunter II']"
52
53
  "dd-trace","https://github.com/DataDog/dd-trace-js","['(Apache-2.0 OR BSD-3-Clause)']","['Datadog Inc. <info@datadoghq.com>']"
54
+ "debug","https://github.com/debug-js/debug","['MIT']","['Josh Junon']"
53
55
  "detect-newline","https://github.com/sindresorhus/detect-newline","['MIT']","['Sindre Sorhus']"
54
56
  "es-module-lexer","https://github.com/guybedford/es-module-lexer","['MIT']","['Guy Bedford']"
55
57
  "escape-string-regexp","https://github.com/sindresorhus/escape-string-regexp","['MIT']","['Sindre Sorhus']"
56
58
  "esquery","https://github.com/estools/esquery","['BSD-3-Clause']","['Joel Feenstra']"
57
59
  "estraverse","https://github.com/estools/estraverse","['BSD-2-Clause']","['estools']"
58
60
  "fast-fifo","https://github.com/mafintosh/fast-fifo","['MIT']","['Mathias Buus']"
61
+ "https-proxy-agent","https://github.com/TooTallNate/proxy-agents","['MIT']","['Nathan Rajlich']"
59
62
  "import-in-the-middle","https://github.com/nodejs/import-in-the-middle","['Apache-2.0']","['Bryan English']"
60
63
  "istanbul-lib-coverage","https://github.com/istanbuljs/istanbuljs","['BSD-3-Clause']","['Krishnan Anantheswaran']"
61
64
  "jest-docblock","https://github.com/jestjs/jest","['MIT']","['jestjs']"
@@ -68,6 +71,7 @@
68
71
  "lru-cache","https://github.com/isaacs/node-lru-cache","['ISC']","['Isaac Z. Schlueter']"
69
72
  "meriyah","https://github.com/meriyah/meriyah","['ISC']","['Kenny F.']"
70
73
  "module-details-from-path","https://github.com/watson/module-details-from-path","['MIT']","['Thomas Watson']"
74
+ "ms","https://github.com/vercel/ms","['MIT']","['vercel']"
71
75
  "mutexify","https://github.com/mafintosh/mutexify","['MIT']","['Mathias Buus']"
72
76
  "node-addon-api","https://github.com/nodejs/node-addon-api","['MIT']","['nodejs']"
73
77
  "node-gyp-build","https://github.com/prebuild/node-gyp-build","['MIT']","['Mathias Buus']"
@@ -75,6 +79,7 @@
75
79
  "oxc-parser","https://github.com/oxc-project/oxc","['MIT']","['Boshen and oxc contributors']"
76
80
  "pprof-format","https://github.com/DataDog/pprof-format","['MIT']","['Datadog Inc.']"
77
81
  "protobufjs","https://github.com/protobufjs/protobuf.js","['BSD-3-Clause']","['Daniel Wirtz']"
82
+ "proxy-from-env","https://github.com/Rob--W/proxy-from-env","['MIT']","['Rob Wu']"
78
83
  "queue-tick","https://github.com/mafintosh/queue-tick","['MIT']","['Mathias Buus']"
79
84
  "retry","https://github.com/tim-kos/node-retry","['MIT']","['Tim Koschützki']"
80
85
  "rfdc","https://github.com/davidmarkclements/rfdc","['MIT']","['David Mark Clements']"
package/index.d.ts CHANGED
@@ -3804,7 +3804,8 @@ declare namespace tracer {
3804
3804
  id?: string,
3805
3805
  inputData: JSONType,
3806
3806
  expectedOutput?: JSONType,
3807
- metadata?: Record<string, JSONType>
3807
+ metadata?: Record<string, JSONType>,
3808
+ tags?: string[]
3808
3809
  }>
3809
3810
  }
3810
3811
 
@@ -3837,6 +3838,8 @@ declare namespace tracer {
3837
3838
  expectedRecordCount?: number
3838
3839
  /** Maximum total time to wait, in ms. Default 30000. */
3839
3840
  maxWaitMs?: number
3841
+ /** Filter records by these tags. */
3842
+ tags?: string[]
3840
3843
  }
3841
3844
 
3842
3845
  interface ExperimentResultRow {
@@ -3954,7 +3957,12 @@ declare namespace tracer {
3954
3957
  }
3955
3958
 
3956
3959
  interface Dataset {
3957
- addRecord (input: JSONType, expectedOutput?: JSONType, metadata?: Record<string, JSONType>): Dataset
3960
+ addRecord (
3961
+ input: JSONType,
3962
+ expectedOutput?: JSONType,
3963
+ metadata?: Record<string, JSONType>,
3964
+ tags?: string[]
3965
+ ): Dataset
3958
3966
  /** Update fields on an existing dataset record. */
3959
3967
  update (index: number, fields: {
3960
3968
  input?: JSONType
@@ -3963,6 +3971,12 @@ declare namespace tracer {
3963
3971
  }): Dataset
3964
3972
  /** Delete an existing dataset record. */
3965
3973
  delete (index: number): Dataset
3974
+ /** Add tags to a dataset record. */
3975
+ addTags (index: number, tags: string[]): Dataset
3976
+ /** Remove tags from a dataset record. */
3977
+ removeTags (index: number, tags: string[]): Dataset
3978
+ /** Replace all tags on a dataset record. */
3979
+ replaceTags (index: number, tags: string[]): Dataset
3966
3980
  /** Creates the dataset remotely if needed and pushes any unpushed records. */
3967
3981
  push (): Promise<DatasetPushResult>
3968
3982
  name (): string
@@ -3975,8 +3989,11 @@ declare namespace tracer {
3975
3989
  id: string | null,
3976
3990
  input: JSONType,
3977
3991
  expectedOutput: JSONType,
3978
- metadata: Record<string, JSONType>
3992
+ metadata: Record<string, JSONType>,
3993
+ tags: string[]
3979
3994
  }>
3995
+ /** Return the tags used to filter this dataset. */
3996
+ filterTags (): string[]
3980
3997
  /** Dashboard URL for the dataset, or null until pushed. */
3981
3998
  url (): string | null
3982
3999
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "6.11.0",
3
+ "version": "6.12.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -57,13 +57,14 @@
57
57
  "test:core:ci": "node scripts/c8-ci.js test:core",
58
58
  "test:code-origin": "mocha \"packages/datadog-code-origin/test/**/*.spec.js\"",
59
59
  "test:code-origin:ci": "node scripts/c8-ci.js test:code-origin",
60
+ "test:evp_proxy": "mocha \"packages/dd-trace/test/evp_proxy/**/*.spec.js\"",
60
61
  "test:lambda": "mocha \"packages/dd-trace/test/lambda/**/*.spec.js\"",
61
62
  "test:lambda:ci": "node scripts/c8-ci.js test:lambda",
62
63
  "test:llmobs:sdk": "mocha --exclude \"packages/dd-trace/test/llmobs/plugins/**/*.spec.js\" \"packages/dd-trace/test/llmobs/**/*.spec.js\"",
63
64
  "test:llmobs:sdk:ci": "node scripts/c8-ci.js test:llmobs:sdk",
64
65
  "test:llmobs:plugins": "mocha \"packages/dd-trace/test/llmobs/plugins/@(${PLUGINS})/*.spec.js\"",
65
66
  "test:llmobs:plugins:ci": "yarn services && node scripts/c8-ci.js test:llmobs:plugins",
66
- "test:openfeature": "mocha \"packages/dd-trace/test/openfeature/**/*.spec.js\"",
67
+ "test:openfeature": "mocha \"packages/dd-trace/test/evp_proxy/**/*.spec.js\" \"packages/dd-trace/test/openfeature/**/*.spec.js\"",
67
68
  "test:openfeature:ci": "node scripts/c8-ci.js test:openfeature",
68
69
  "test:plugins": "node --expose-gc ./node_modules/mocha/bin/mocha.js --fail-zero \"packages/datadog-plugin-@(${PLUGINS})/test/**/${SPEC:-*}*.spec.js\"",
69
70
  "test:plugins:ci": "yarn services && node scripts/c8-ci.js test:plugins",
@@ -186,7 +187,7 @@
186
187
  "@datadog/native-appsec": "11.0.1",
187
188
  "@datadog/native-iast-taint-tracking": "4.2.0",
188
189
  "@datadog/native-metrics": "3.1.2",
189
- "@datadog/pprof": "5.18.0",
190
+ "@datadog/pprof": "5.18.1",
190
191
  "@datadog/wasm-js-rewriter": "5.0.3",
191
192
  "@opentelemetry/api": ">=1.0.0 <1.10.0",
192
193
  "@opentelemetry/api-logs": "<1.0.0",
@@ -196,58 +197,58 @@
196
197
  "@actions/core": "^3.0.1",
197
198
  "@actions/github": "^9.1.1",
198
199
  "@babel/helpers": "^8.0.0",
199
- "@eslint/eslintrc": "^3.3.5",
200
+ "@eslint/eslintrc": "^3.3.6",
200
201
  "@eslint/js": "^10.0.1",
201
202
  "@eslint/plugin-kit": "^0.7.2",
202
203
  "@datadog/openfeature-node-server": "2.1.0",
203
204
  "@msgpack/msgpack": "^3.1.3",
204
- "@openfeature/core": "^1.11.0",
205
- "@openfeature/server-sdk": "~1.22.0",
205
+ "@openfeature/core": "^1.12.0",
206
+ "@openfeature/server-sdk": "~1.23.0",
206
207
  "@stylistic/eslint-plugin": "^5.10.0",
207
208
  "@types/mocha": "^10.0.10",
208
209
  "@types/node": "^18.19.106",
209
210
  "@types/sinon": "^22.0.0",
210
211
  "@vercel/nft": "^1.10.2",
211
212
  "@yarnpkg/lockfile": "^1.1.0",
212
- "axios": "^1.18.1",
213
+ "axios": "^1.19.0",
213
214
  "benchmark": "^2.1.4",
214
215
  "body-parser": "^2.3.0",
215
216
  "bun": "1.3.14",
216
217
  "c8": "^12.0.0",
217
218
  "codeowners-audit": "^2.9.0",
218
- "eslint": "^10.5.0",
219
- "eslint-plugin-cypress": "^6.4.2",
219
+ "eslint": "^10.8.1",
220
+ "eslint-plugin-cypress": "^7.0.0",
220
221
  "eslint-plugin-import-x": "^4.16.2",
221
- "eslint-plugin-jsdoc": "^63.0.12",
222
+ "eslint-plugin-jsdoc": "^64.2.0",
222
223
  "eslint-plugin-mocha": "^11.3.0",
223
- "eslint-plugin-n": "^18.2.1",
224
+ "eslint-plugin-n": "^18.3.0",
224
225
  "eslint-plugin-promise": "^7.3.0",
225
226
  "eslint-plugin-sonarjs": "^4.2.0",
226
227
  "eslint-plugin-unicorn": "^73.0.0",
227
228
  "express": "^5.1.0",
228
229
  "glob": "^10.4.5",
229
- "globals": "^17.7.0",
230
+ "globals": "^17.11.0",
230
231
  "graphql": "*",
231
232
  "husky": "^9.1.7",
232
233
  "istanbul-lib-report": "^3.0.0",
233
234
  "istanbul-reports": "^3.0.2",
234
235
  "jsonc-parser": "^3.3.1",
235
236
  "jszip": "^3.10.1",
236
- "minimatch": "^10.2.5",
237
- "mocha": "^11.7.6",
237
+ "minimatch": "^10.2.6",
238
+ "mocha": "^11.8.0",
238
239
  "mocha-junit-reporter": "^2.2.1",
239
240
  "mocha-multi-reporters": "^1.5.1",
240
241
  "multer": "^2.2.0",
241
- "nock": "^14.0.16",
242
+ "nock": "^14.0.17",
242
243
  "node-preload": "^0.2.1",
243
244
  "nyc": "^18.0.0",
244
245
  "octokit": "^5.0.3",
245
- "p-limit": "^7.2.0",
246
+ "p-limit": "^7.3.1",
246
247
  "proxyquire": "^2.1.3",
247
248
  "retry": "^0.13.1",
248
249
  "semifies": "^1.0.0",
249
250
  "semver": "^7.8.5",
250
- "sinon": "^22.0.0",
251
+ "sinon": "^22.1.0",
251
252
  "source-map-support": "^0.5.21",
252
253
  "test-exclude": "^8.0.0",
253
254
  "tiktoken": "^1.0.21",
@@ -1482,14 +1482,14 @@ function getWrappedRunTestCase (runTestCaseFunction, isNewerCucumberVersion = fa
1482
1482
 
1483
1483
  this.options.retry = originalRetry
1484
1484
 
1485
- if (isNewerCucumberVersion && shouldRunEarlyFlakeDetection() && (isNew || isModified)) {
1486
- return shouldBePassedByEFD
1487
- }
1488
-
1489
1485
  if (isNewerCucumberVersion && isTestManagementTestsEnabled && !isAttemptToFix && (isQuarantined || isDisabled)) {
1490
1486
  return shouldBePassedByTestManagement
1491
1487
  }
1492
1488
 
1489
+ if (isNewerCucumberVersion && shouldRunEarlyFlakeDetection() && (isNew || isModified)) {
1490
+ return shouldBePassedByEFD
1491
+ }
1492
+
1493
1493
  if (isNewerCucumberVersion && isAttemptToFix && shouldBeFailedByAttemptToFix) {
1494
1494
  return false
1495
1495
  }
@@ -299,6 +299,9 @@ function retryTest (test, numRetries, tags, retryPolicy) {
299
299
  clonedTest[tag] = true
300
300
  }
301
301
  }
302
+ if (clonedTest._ddIsQuarantined && !clonedTest._ddIsAttemptToFix) {
303
+ testsQuarantined.add(clonedTest)
304
+ }
302
305
  }
303
306
  }
304
307
 
@@ -1146,7 +1149,7 @@ function getRunTestsWrapper (runTests, config) {
1146
1149
  retryTest(
1147
1150
  test,
1148
1151
  config.earlyFlakeDetectionRetryPolicy.schedulingRetryCount,
1149
- ['_ddIsModified', '_ddIsEfdRetry'],
1152
+ ['_ddIsModified', '_ddIsEfdRetry', test._ddIsQuarantined && '_ddIsQuarantined'],
1150
1153
  config.earlyFlakeDetectionRetryPolicy
1151
1154
  )
1152
1155
  }
@@ -1165,7 +1168,7 @@ function getRunTestsWrapper (runTests, config) {
1165
1168
  retryTest(
1166
1169
  test,
1167
1170
  config.earlyFlakeDetectionRetryPolicy.schedulingRetryCount,
1168
- ['_ddIsNew', '_ddIsEfdRetry'],
1171
+ ['_ddIsNew', '_ddIsEfdRetry', test._ddIsQuarantined && '_ddIsQuarantined'],
1169
1172
  config.earlyFlakeDetectionRetryPolicy
1170
1173
  )
1171
1174
  }
@@ -3,6 +3,9 @@
3
3
  const { channel } = require('dc-polyfill')
4
4
 
5
5
  const reporterErrorCh = channel('ci:playwright:reporter:error')
6
+ const reporterRunSummaryCh = channel('ci:playwright:reporter:run-summary')
7
+ const reporterSuiteHookErrorCh = channel('ci:playwright:reporter:suite-hook-error')
8
+ const suitesWithHookErrors = new Set()
6
9
  const PLAYWRIGHT_REPORTER_ERROR_MESSAGE = 'Error in reporter'
7
10
  const PLAYWRIGHT_REPORTER_ERROR_CALLER_RE =
8
11
  /^\s*at wrapAsync .*?[\\/]playwright[\\/]lib[\\/]runner[\\/]index\.js:\d+:\d+\)?$/
@@ -27,7 +30,53 @@ function isPlaywrightReporterError (message) {
27
30
  }
28
31
  }
29
32
 
33
+ /**
34
+ * Returns whether a finalized Playwright result contains a failed suite hook.
35
+ *
36
+ * @param {Array<object>} steps
37
+ * @returns {boolean}
38
+ */
39
+ function hasFailedSuiteHook (steps) {
40
+ if (!steps) return false
41
+
42
+ for (const step of steps) {
43
+ if (step.error && (step.title === 'beforeAll hook' || step.title === 'afterAll hook')) return true
44
+ if (hasFailedSuiteHook(step.steps)) return true
45
+ }
46
+ return false
47
+ }
48
+
30
49
  class DatadogPlaywrightReporter {
50
+ /**
51
+ * Creates a reporter that observes the finalized Playwright result independently of user reporters.
52
+ *
53
+ * @param {object} [options]
54
+ * @param {boolean} [options.captureReporterErrors]
55
+ */
56
+ constructor ({ captureReporterErrors = true } = {}) {
57
+ this.captureReporterErrors = captureReporterErrors
58
+ this.fatalErrorCount = 0
59
+ }
60
+
61
+ /**
62
+ * Identifies this reporter as implementing Playwright's reporter v2 contract.
63
+ *
64
+ * @returns {'v2'}
65
+ */
66
+ version () {
67
+ return 'v2'
68
+ }
69
+
70
+ /**
71
+ * Implements the reporter v2 lifecycle hook required by older Playwright versions.
72
+ *
73
+ * @param {object} config
74
+ * @returns {void}
75
+ */
76
+ onConfigure (config) {
77
+ this.failOnFlakyTests = config.failOnFlakyTests
78
+ }
79
+
31
80
  /**
32
81
  * Restores console error after Playwright completes the reporter lifecycle.
33
82
  *
@@ -45,12 +94,96 @@ class DatadogPlaywrightReporter {
45
94
  DatadogPlaywrightReporter.originalConsoleError = undefined
46
95
  }
47
96
 
97
+ /**
98
+ * Records the suite so finalized test outcomes can be counted in onEnd.
99
+ *
100
+ * @param {object} configOrSuite
101
+ * @param {object} [suite]
102
+ * @returns {void}
103
+ */
104
+ onBegin (configOrSuite, suite) {
105
+ this.suite = suite || configOrSuite
106
+ if (suite) this.failOnFlakyTests = configOrSuite.failOnFlakyTests
107
+ suitesWithHookErrors.clear()
108
+ }
109
+
110
+ /**
111
+ * Implements the reporter v2 lifecycle hook required by older Playwright versions.
112
+ *
113
+ * @returns {void}
114
+ */
115
+ onTestBegin () {}
116
+
117
+ /**
118
+ * Implements the reporter v2 lifecycle hook required by older Playwright versions.
119
+ *
120
+ * @returns {void}
121
+ */
122
+ onStdOut () {}
123
+
124
+ /**
125
+ * Implements the reporter v2 lifecycle hook required by older Playwright versions.
126
+ *
127
+ * @returns {void}
128
+ */
129
+ onStdErr () {}
130
+
131
+ /**
132
+ * Implements the reporter v2 lifecycle hook required by older Playwright versions.
133
+ *
134
+ * @returns {void}
135
+ */
136
+ onTestEnd () {}
137
+
138
+ /**
139
+ * Implements the reporter v2 lifecycle hook required by older Playwright versions.
140
+ *
141
+ * @returns {void}
142
+ */
143
+ onStepBegin () {}
144
+
145
+ /**
146
+ * Implements the reporter v2 lifecycle hook required by older Playwright versions.
147
+ *
148
+ * @returns {void}
149
+ */
150
+ onStepEnd () {}
151
+
48
152
  /**
49
153
  * Marks the beginning of reporter finalization so later reporter errors can be identified.
50
154
  *
51
155
  * @returns {void}
52
156
  */
53
157
  onEnd () {
158
+ let failureCount = this.fatalErrorCount
159
+ let quarantinedFailureCount = 0
160
+ let hasIncompleteTests = suitesWithHookErrors.size > 0
161
+ const tests = this.suite?.allTests?.()
162
+ if (tests) {
163
+ for (const test of tests) {
164
+ const outcome = test.outcome()
165
+ const finalResult = test.results.at(-1)
166
+ hasIncompleteTests ||= hasFailedSuiteHook(finalResult?.steps)
167
+ if (outcome === 'unexpected' || (outcome === 'flaky' && this.failOnFlakyTests)) {
168
+ failureCount += 1
169
+ const finalResultStatus = finalResult?.status
170
+ const hasFailed = finalResultStatus === 'failed' || finalResultStatus === 'timedOut'
171
+ if (outcome === 'unexpected' && hasFailed && test._ddIsQuarantined && !test._ddIsAttemptToFix) {
172
+ quarantinedFailureCount += 1
173
+ }
174
+ } else if (outcome === 'skipped' && !test._ddShouldSkipEfdRetry &&
175
+ (!test._ddIsDisabled || test._ddIsAttemptToFix)) {
176
+ const { results } = test
177
+ hasIncompleteTests ||= results.some(result => result.status === 'interrupted') ||
178
+ !results.length || test.expectedStatus !== 'skipped'
179
+ }
180
+ }
181
+ }
182
+ reporterRunSummaryCh.publish({ failureCount, quarantinedFailureCount, hasIncompleteTests })
183
+ suitesWithHookErrors.clear()
184
+
185
+ if (!this.captureReporterErrors) return
186
+
54
187
  this.isFinalizing = true
55
188
  // Playwright 1.60 and 1.61 only expose reporter errors through this exact console call.
56
189
  // eslint-disable-next-line no-console
@@ -76,6 +209,13 @@ class DatadogPlaywrightReporter {
76
209
  DatadogPlaywrightReporter.consoleError = consoleError
77
210
  }
78
211
 
212
+ /**
213
+ * Implements the reporter v2 lifecycle hook required by older Playwright versions.
214
+ *
215
+ * @returns {void}
216
+ */
217
+ onExit () {}
218
+
79
219
  /**
80
220
  * Reports errors emitted by Playwright while later reporters are finalizing.
81
221
  *
@@ -83,7 +223,11 @@ class DatadogPlaywrightReporter {
83
223
  * @returns {void}
84
224
  */
85
225
  onError (error) {
86
- if (this.isFinalizing) reporterErrorCh.publish(error)
226
+ if (this.isFinalizing) {
227
+ reporterErrorCh.publish(error)
228
+ } else {
229
+ this.fatalErrorCount += 1
230
+ }
87
231
  }
88
232
 
89
233
  /**
@@ -96,4 +240,8 @@ class DatadogPlaywrightReporter {
96
240
  }
97
241
  }
98
242
 
243
+ reporterSuiteHookErrorCh.subscribe((testSuiteAbsolutePath) => {
244
+ suitesWithHookErrors.add(testSuiteAbsolutePath)
245
+ })
246
+
99
247
  module.exports = DatadogPlaywrightReporter
@@ -46,6 +46,8 @@ const testSessionStartCh = channel('ci:playwright:session:start')
46
46
  const testSessionConfigurationCh = channel('ci:playwright:session:configuration')
47
47
  const testSessionFinishCh = channel('ci:playwright:session:finish')
48
48
  const reporterErrorCh = channel('ci:playwright:reporter:error')
49
+ const reporterRunSummaryCh = channel('ci:playwright:reporter:run-summary')
50
+ const reporterSuiteHookErrorCh = channel('ci:playwright:reporter:suite-hook-error')
49
51
 
50
52
  const libraryConfigurationCh = channel('ci:playwright:library-configuration')
51
53
  const knownTestsCh = channel('ci:playwright:known-tests')
@@ -111,7 +113,7 @@ let testManagementAttemptToFixRetries = 0
111
113
  let testManagementTests = {}
112
114
  let isImpactedTestsEnabled = false
113
115
  let modifiedFiles = {}
114
- let quarantinedButNotAttemptToFixFqns = new Set()
116
+ let playwrightRunSummary
115
117
  let recordedTestOptimizationExecutions = new Set()
116
118
  let testsReportedInGenerateSummary = new Set()
117
119
  const newTestsWithDynamicNames = new Set()
@@ -785,6 +787,7 @@ function finishTestSuiteIfDone (testSuiteAbsolutePath, projects) {
785
787
  testSourceLine: test.location.line,
786
788
  browserName,
787
789
  isNew: test._ddIsNew,
790
+ isAttemptToFix: test._ddIsAttemptToFix,
788
791
  isDisabled: test._ddIsDisabled,
789
792
  isModified: test._ddIsModified,
790
793
  isQuarantined: test._ddIsQuarantined,
@@ -832,6 +835,7 @@ function testEndHandler ({
832
835
 
833
836
  if (hookError) {
834
837
  addErrorToTestSuite(testSuiteAbsolutePath, hookError)
838
+ reporterSuiteHookErrorCh.publish(testSuiteAbsolutePath)
835
839
  }
836
840
  return
837
841
  }
@@ -917,9 +921,7 @@ function testEndHandler ({
917
921
 
918
922
  // Check if all EFD retries failed
919
923
  const efdRetryCount = getEfdRetryCountForTest(test)
920
- if (efdRetryCount > 0 && testStatuses.length === efdRetryCount + 1 &&
921
- (test._ddIsNew || test._ddIsModified) &&
922
- isEarlyFlakeDetectionEnabled &&
924
+ if (isEfdManagedTest && efdRetryCount > 0 && testStatuses.length === efdRetryCount + 1 &&
923
925
  testStatuses.every(status => status === 'fail')) {
924
926
  test._ddHasFailedAllRetries = true
925
927
  }
@@ -1259,6 +1261,7 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
1259
1261
  return async function (config) {
1260
1262
  reporterError = undefined
1261
1263
  hasReporterError = false
1264
+ playwrightRunSummary = undefined
1262
1265
  let restoreReporterConsoleError
1263
1266
  if (satisfies(playwrightVersion, '>=1.60.0') && config?.config) {
1264
1267
  const DatadogPlaywrightReporter = require('./playwright-reporter')
@@ -1433,23 +1436,8 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
1433
1436
  const sessionStatus = runAllTestsReturn.status || runAllTestsReturn
1434
1437
 
1435
1438
  if (isTestManagementTestsEnabled && sessionStatus === 'failed') {
1436
- let totalFailedTestCount = 0
1437
- let totalPureQuarantinedFailedTestCount = 0
1438
-
1439
- for (const [fqn, testStatuses] of testsToTestStatuses) {
1440
- // Only count as failed if the final status (after retries) is 'fail'
1441
- const lastStatus = testStatuses.at(-1)
1442
- if (lastStatus === 'fail') {
1443
- totalFailedTestCount += 1
1444
- if (quarantinedButNotAttemptToFixFqns.has(fqn)) {
1445
- totalPureQuarantinedFailedTestCount += 1
1446
- }
1447
- }
1448
- }
1449
-
1450
- const totalIgnorableFailures = totalPureQuarantinedFailedTestCount
1451
-
1452
- if (totalFailedTestCount > 0 && totalFailedTestCount === totalIgnorableFailures) {
1439
+ const { failureCount, quarantinedFailureCount, hasIncompleteTests } = playwrightRunSummary || {}
1440
+ if (!hasIncompleteTests && quarantinedFailureCount > 0 && quarantinedFailureCount === failureCount) {
1453
1441
  runAllTestsReturn = 'passed'
1454
1442
  preventedToFail = true
1455
1443
  }
@@ -1476,7 +1464,7 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
1476
1464
 
1477
1465
  startedSuites = []
1478
1466
  remainingTestsByFile = {}
1479
- quarantinedButNotAttemptToFixFqns = new Set()
1467
+ playwrightRunSummary = undefined
1480
1468
  recordedTestOptimizationExecutions = new Set()
1481
1469
  testsReportedInGenerateSummary = new Set()
1482
1470
  efdManagedTestKeys.clear()
@@ -1507,6 +1495,8 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
1507
1495
  function reportersHook (reportersPackage) {
1508
1496
  return shimmer.wrap(reportersPackage, 'createReporters', createReporters => async function () {
1509
1497
  const reporters = await createReporters.apply(this, arguments)
1498
+ const DatadogPlaywrightReporter = require('./playwright-reporter')
1499
+ reporters.unshift(new DatadogPlaywrightReporter({ captureReporterErrors: false }))
1510
1500
  for (const reporter of reporters) {
1511
1501
  if (instrumentedPlaywrightReporters.has(reporter)) continue
1512
1502
 
@@ -1663,6 +1653,10 @@ reporterErrorCh.subscribe((error) => {
1663
1653
  recordReporterError(error)
1664
1654
  })
1665
1655
 
1656
+ reporterRunSummaryCh.subscribe((runSummary) => {
1657
+ playwrightRunSummary = runSummary
1658
+ })
1659
+
1666
1660
  /**
1667
1661
  * Records a reporter failure even when the reporter throws a falsy value.
1668
1662
  *
@@ -1894,11 +1888,6 @@ function processRootSuite (createRootSuiteReturnValue) {
1894
1888
  }
1895
1889
  if (testProperties.quarantined) {
1896
1890
  test._ddIsQuarantined = true
1897
- if (!testProperties.attemptToFix) {
1898
- // Do not skip quarantined tests, let them run and overwrite results post-run if they fail
1899
- const testFqn = getTestFullyQualifiedName(test)
1900
- quarantinedButNotAttemptToFixFqns.add(testFqn)
1901
- }
1902
1891
  }
1903
1892
  if (testProperties.attemptToFix) {
1904
1893
  test._ddIsAttemptToFix = true
@@ -1938,7 +1927,10 @@ function processRootSuite (createRootSuiteReturnValue) {
1938
1927
  const fileSuitesWithImpactedTestsToProjects = new Map()
1939
1928
  for (const impactedTest of impactedTests) {
1940
1929
  impactedTest._ddIsModified = true
1941
- if (shouldRunEarlyFlakeDetection() && impactedTest.expectedStatus !== 'skipped') {
1930
+ if (shouldRunEarlyFlakeDetection() && impactedTest.expectedStatus !== 'skipped' &&
1931
+ !impactedTest._ddIsAttemptToFix) {
1932
+ // Prevent ATR or `--retries` from retrying tests that EFD manages.
1933
+ impactedTest.retries = 0
1942
1934
  markEfdManagedTest(impactedTest)
1943
1935
  const fileSuite = getSuiteType(impactedTest, 'file')
1944
1936
  if (!fileSuitesWithImpactedTestsToProjects.has(fileSuite)) {
@@ -1946,14 +1938,15 @@ function processRootSuite (createRootSuiteReturnValue) {
1946
1938
  }
1947
1939
  }
1948
1940
  }
1949
- // If something change in the file, all tests in the file are impacted, hence the () => true filter
1941
+ // If something changes in the file, all tests in the file are impacted, except attempt-to-fix tests managed above.
1950
1942
  applyRetriesToTests(
1951
1943
  fileSuitesWithImpactedTestsToProjects,
1952
- () => true,
1944
+ (test) => !test._ddIsAttemptToFix,
1953
1945
  [
1954
1946
  '_ddIsModified',
1955
1947
  '_ddIsEfdRetry',
1956
1948
  (test) => (isKnownTestsEnabled && isNewTest(test) ? '_ddIsNew' : null),
1949
+ (test) => test._ddIsQuarantined && '_ddIsQuarantined',
1957
1950
  ],
1958
1951
  earlyFlakeDetectionRetryPolicy.schedulingRetryCount,
1959
1952
  (copiedTest, originalTest, retryIndex) => {
@@ -1981,7 +1974,8 @@ function processRootSuite (createRootSuiteReturnValue) {
1981
1974
  const fileSuitesWithNewTestsToProjects = new Map()
1982
1975
  for (const newTest of newTests) {
1983
1976
  newTest._ddIsNew = true
1984
- if (shouldRunEarlyFlakeDetection() && newTest.expectedStatus !== 'skipped' && !newTest._ddIsModified) {
1977
+ if (shouldRunEarlyFlakeDetection() && newTest.expectedStatus !== 'skipped' &&
1978
+ !newTest._ddIsModified && !newTest._ddIsAttemptToFix) {
1985
1979
  // Prevent ATR or `--retries` from retrying tests that EFD manages.
1986
1980
  newTest.retries = 0
1987
1981
  markEfdManagedTest(newTest)
@@ -1994,8 +1988,12 @@ function processRootSuite (createRootSuiteReturnValue) {
1994
1988
 
1995
1989
  applyRetriesToTests(
1996
1990
  fileSuitesWithNewTestsToProjects,
1997
- isNewTest,
1998
- ['_ddIsNew', '_ddIsEfdRetry'],
1991
+ (test) => !test._ddIsAttemptToFix && isNewTest(test),
1992
+ [
1993
+ '_ddIsNew',
1994
+ '_ddIsEfdRetry',
1995
+ (test) => test._ddIsQuarantined && '_ddIsQuarantined',
1996
+ ],
1999
1997
  earlyFlakeDetectionRetryPolicy.schedulingRetryCount,
2000
1998
  (copiedTest, originalTest, retryIndex) => {
2001
1999
  markEfdRetryTest(copiedTest, retryIndex, originalTest)
@@ -2447,6 +2445,7 @@ function generateSummaryWrapper (generateSummary) {
2447
2445
  line: testSourceLine,
2448
2446
  },
2449
2447
  _ddIsNew: isNew,
2448
+ _ddIsAttemptToFix: isAttemptToFix,
2450
2449
  _ddIsDisabled: isDisabled,
2451
2450
  _ddIsModified: isModified,
2452
2451
  _ddIsQuarantined: isQuarantined,
@@ -2461,6 +2460,7 @@ function generateSummaryWrapper (generateSummary) {
2461
2460
  testSourceLine,
2462
2461
  browserName,
2463
2462
  isNew,
2463
+ isAttemptToFix,
2464
2464
  isDisabled,
2465
2465
  isModified,
2466
2466
  isQuarantined,