dd-trace 4.15.0 → 4.17.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 (76) hide show
  1. package/LICENSE-3rdparty.csv +2 -0
  2. package/ext/tags.d.ts +1 -0
  3. package/ext/tags.js +1 -0
  4. package/index.d.ts +1 -0
  5. package/package.json +9 -6
  6. package/packages/datadog-esbuild/index.js +30 -25
  7. package/packages/datadog-instrumentations/src/body-parser.js +4 -3
  8. package/packages/datadog-instrumentations/src/cookie-parser.js +37 -0
  9. package/packages/datadog-instrumentations/src/cucumber.js +24 -4
  10. package/packages/datadog-instrumentations/src/express-mongo-sanitize.js +45 -0
  11. package/packages/datadog-instrumentations/src/express.js +3 -2
  12. package/packages/datadog-instrumentations/src/graphql.js +5 -0
  13. package/packages/datadog-instrumentations/src/helpers/hooks.js +5 -1
  14. package/packages/datadog-instrumentations/src/http/server.js +1 -1
  15. package/packages/datadog-instrumentations/src/jest.js +20 -11
  16. package/packages/datadog-instrumentations/src/knex.js +62 -1
  17. package/packages/datadog-instrumentations/src/mocha.js +19 -4
  18. package/packages/datadog-instrumentations/src/mongodb.js +63 -0
  19. package/packages/datadog-instrumentations/src/mongoose.js +140 -1
  20. package/packages/datadog-instrumentations/src/next.js +62 -80
  21. package/packages/datadog-instrumentations/src/pg.js +14 -15
  22. package/packages/datadog-instrumentations/src/playwright.js +26 -5
  23. package/packages/datadog-plugin-cucumber/src/index.js +17 -5
  24. package/packages/datadog-plugin-cypress/src/plugin.js +38 -8
  25. package/packages/datadog-plugin-jest/src/index.js +19 -4
  26. package/packages/datadog-plugin-jest/src/util.js +45 -2
  27. package/packages/datadog-plugin-memcached/src/index.js +10 -5
  28. package/packages/datadog-plugin-mocha/src/index.js +19 -6
  29. package/packages/datadog-plugin-mysql/src/index.js +2 -2
  30. package/packages/datadog-plugin-next/src/index.js +14 -5
  31. package/packages/datadog-plugin-pg/src/index.js +2 -2
  32. package/packages/dd-trace/src/appsec/channels.js +4 -1
  33. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  34. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +166 -0
  35. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +21 -1
  36. package/packages/dd-trace/src/appsec/iast/analyzers/unvalidated-redirect-analyzer.js +3 -3
  37. package/packages/dd-trace/src/appsec/iast/analyzers/vulnerability-analyzer.js +1 -2
  38. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +4 -0
  39. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations.js +25 -12
  40. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +4 -4
  41. package/packages/dd-trace/src/appsec/iast/taint-tracking/secure-marks-generator.js +13 -0
  42. package/packages/dd-trace/src/appsec/iast/taint-tracking/source-types.js +2 -1
  43. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +16 -0
  44. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +3 -4
  45. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-regex.js +9 -0
  46. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/index.js +13 -1
  47. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +169 -0
  48. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  49. package/packages/dd-trace/src/appsec/index.js +45 -14
  50. package/packages/dd-trace/src/appsec/recommended.json +549 -24
  51. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
  52. package/packages/dd-trace/src/appsec/remote_config/index.js +2 -0
  53. package/packages/dd-trace/src/appsec/remote_config/manager.js +11 -3
  54. package/packages/dd-trace/src/appsec/reporter.js +7 -5
  55. package/packages/dd-trace/src/appsec/telemetry.js +2 -2
  56. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +18 -5
  57. package/packages/dd-trace/src/appsec/waf/waf_manager.js +5 -4
  58. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +1 -14
  59. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +1 -13
  60. package/packages/dd-trace/src/config.js +8 -0
  61. package/packages/dd-trace/src/datastreams/processor.js +6 -2
  62. package/packages/dd-trace/src/format.js +9 -1
  63. package/packages/dd-trace/src/opentracing/propagation/text_map.js +2 -2
  64. package/packages/dd-trace/src/opentracing/tracer.js +0 -2
  65. package/packages/dd-trace/src/plugin_manager.js +4 -3
  66. package/packages/dd-trace/src/plugins/database.js +14 -4
  67. package/packages/dd-trace/src/plugins/index.js +1 -0
  68. package/packages/dd-trace/src/plugins/outbound.js +4 -3
  69. package/packages/dd-trace/src/plugins/util/ci.js +17 -0
  70. package/packages/dd-trace/src/plugins/util/git.js +26 -4
  71. package/packages/dd-trace/src/plugins/util/test.js +16 -1
  72. package/packages/dd-trace/src/profiling/config.js +36 -5
  73. package/packages/dd-trace/src/profiling/profilers/wall.js +7 -1
  74. package/packages/dd-trace/src/service-naming/extra-services.js +24 -0
  75. package/packages/dd-trace/src/telemetry/index.js +10 -1
  76. package/packages/dd-trace/src/telemetry/metrics.js +0 -5
@@ -0,0 +1,63 @@
1
+ 'use strict'
2
+
3
+ require('./mongodb-core')
4
+
5
+ const {
6
+ channel,
7
+ addHook,
8
+ AsyncResource
9
+ } = require('./helpers/instrument')
10
+ const shimmer = require('../../datadog-shimmer')
11
+
12
+ // collection methods with filter
13
+ const collectionMethodsWithFilter = [
14
+ 'count',
15
+ 'countDocuments',
16
+ 'deleteMany',
17
+ 'deleteOne',
18
+ 'find',
19
+ 'findOneAndDelete',
20
+ 'findOneAndReplace',
21
+ 'replaceOne'
22
+ ] // findOne is ignored because it calls to find
23
+
24
+ const collectionMethodsWithTwoFilters = [
25
+ 'findOneAndUpdate',
26
+ 'updateMany',
27
+ 'updateOne'
28
+ ]
29
+
30
+ const startCh = channel('datadog:mongodb:collection:filter:start')
31
+
32
+ addHook({ name: 'mongodb', versions: ['>=3.3 <5', '5', '>=6'] }, mongodb => {
33
+ [...collectionMethodsWithFilter, ...collectionMethodsWithTwoFilters].forEach(methodName => {
34
+ if (!(methodName in mongodb.Collection.prototype)) return
35
+
36
+ const useTwoArguments = collectionMethodsWithTwoFilters.includes(methodName)
37
+
38
+ shimmer.wrap(mongodb.Collection.prototype, methodName, method => {
39
+ return function () {
40
+ if (!startCh.hasSubscribers) {
41
+ return method.apply(this, arguments)
42
+ }
43
+
44
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
45
+
46
+ return asyncResource.runInAsyncScope(() => {
47
+ const filters = [arguments[0]]
48
+ if (useTwoArguments) {
49
+ filters.push(arguments[1])
50
+ }
51
+
52
+ startCh.publish({
53
+ filters,
54
+ methodName
55
+ })
56
+
57
+ return method.apply(this, arguments)
58
+ })
59
+ }
60
+ })
61
+ })
62
+ return mongodb
63
+ })
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const { addHook } = require('./helpers/instrument')
3
+ const { addHook, channel } = require('./helpers/instrument')
4
4
  const { wrapThen } = require('./helpers/promise')
5
5
  const { AsyncResource } = require('./helpers/instrument')
6
6
  const shimmer = require('../../datadog-shimmer')
@@ -26,5 +26,144 @@ addHook({
26
26
  }
27
27
 
28
28
  shimmer.wrap(mongoose.Collection.prototype, 'addQueue', wrapAddQueue)
29
+
29
30
  return mongoose
30
31
  })
32
+
33
+ const startCh = channel('datadog:mongoose:model:filter:start')
34
+ const finishCh = channel('datadog:mongoose:model:filter:finish')
35
+
36
+ const collectionMethodsWithFilter = [
37
+ 'count',
38
+ 'countDocuments',
39
+ 'deleteMany',
40
+ 'deleteOne',
41
+ 'find',
42
+ 'findOne',
43
+ 'findOneAndDelete',
44
+ 'findOneAndReplace',
45
+ 'replaceOne',
46
+ 'remove'
47
+ ]
48
+
49
+ const collectionMethodsWithTwoFilters = [
50
+ 'findOneAndUpdate',
51
+ 'updateMany',
52
+ 'updateOne'
53
+ ]
54
+
55
+ addHook({
56
+ name: 'mongoose',
57
+ versions: ['>=4.6.4 <5', '5', '6', '>=7'],
58
+ file: 'lib/model.js'
59
+ }, Model => {
60
+ [...collectionMethodsWithFilter, ...collectionMethodsWithTwoFilters].forEach(methodName => {
61
+ const useTwoArguments = collectionMethodsWithTwoFilters.includes(methodName)
62
+ if (!(methodName in Model)) return
63
+
64
+ shimmer.wrap(Model, methodName, method => {
65
+ return function wrappedModelMethod () {
66
+ if (!startCh.hasSubscribers) {
67
+ return method.apply(this, arguments)
68
+ }
69
+
70
+ const asyncResource = new AsyncResource('bound-anonymous-fn')
71
+
72
+ const filters = [arguments[0]]
73
+ if (useTwoArguments) {
74
+ filters.push(arguments[1])
75
+ }
76
+
77
+ const finish = asyncResource.bind(function () {
78
+ finishCh.publish()
79
+ })
80
+
81
+ let callbackWrapped = false
82
+ const lastArgumentIndex = arguments.length - 1
83
+
84
+ if (typeof arguments[lastArgumentIndex] === 'function') {
85
+ // is a callback, wrap it to execute finish()
86
+ shimmer.wrap(arguments, lastArgumentIndex, originalCb => {
87
+ return function () {
88
+ finish()
89
+
90
+ return originalCb.apply(this, arguments)
91
+ }
92
+ })
93
+
94
+ callbackWrapped = true
95
+ }
96
+
97
+ return asyncResource.runInAsyncScope(() => {
98
+ startCh.publish({
99
+ filters,
100
+ methodName
101
+ })
102
+
103
+ const res = method.apply(this, arguments)
104
+
105
+ // if it is not callback, wrap exec method and its then
106
+ if (!callbackWrapped) {
107
+ shimmer.wrap(res, 'exec', originalExec => {
108
+ return function wrappedExec () {
109
+ const execResult = originalExec.apply(this, arguments)
110
+
111
+ // wrap them method, wrap resolve and reject methods
112
+ shimmer.wrap(execResult, 'then', originalThen => {
113
+ return function wrappedThen () {
114
+ const resolve = arguments[0]
115
+ const reject = arguments[1]
116
+
117
+ // not using shimmer here because resolve/reject could be empty
118
+ arguments[0] = function wrappedResolve () {
119
+ finish()
120
+
121
+ if (resolve) {
122
+ return resolve.apply(this, arguments)
123
+ }
124
+ }
125
+
126
+ arguments[1] = function wrappedReject () {
127
+ finish()
128
+
129
+ if (reject) {
130
+ return reject.apply(this, arguments)
131
+ }
132
+ }
133
+
134
+ return originalThen.apply(this, arguments)
135
+ }
136
+ })
137
+
138
+ return execResult
139
+ }
140
+ })
141
+ }
142
+ return res
143
+ })
144
+ }
145
+ })
146
+ })
147
+
148
+ return Model
149
+ })
150
+
151
+ const sanitizeFilterFinishCh = channel('datadog:mongoose:sanitize-filter:finish')
152
+
153
+ addHook({
154
+ name: 'mongoose',
155
+ versions: ['6', '>=7'],
156
+ file: 'lib/helpers/query/sanitizeFilter.js'
157
+ }, sanitizeFilter => {
158
+ return shimmer.wrap(sanitizeFilter, function wrappedSanitizeFilter () {
159
+ const sanitizedObject = sanitizeFilter.apply(this, arguments)
160
+
161
+ if (sanitizeFilterFinishCh.hasSubscribers) {
162
+ sanitizeFilterFinishCh.publish({
163
+ sanitizedObject
164
+ })
165
+ }
166
+
167
+ return sanitizedObject
168
+ })
169
+ })
@@ -10,9 +10,10 @@ const startChannel = channel('apm:next:request:start')
10
10
  const finishChannel = channel('apm:next:request:finish')
11
11
  const errorChannel = channel('apm:next:request:error')
12
12
  const pageLoadChannel = channel('apm:next:page:load')
13
+ const bodyParsedChannel = channel('apm:next:body-parsed')
14
+ const queryParsedChannel = channel('apm:next:query-parsed')
13
15
 
14
16
  const requests = new WeakSet()
15
- const requestToNextjsPagePath = new WeakMap()
16
17
 
17
18
  function wrapHandleRequest (handleRequest) {
18
19
  return function (req, res, pathname, query) {
@@ -29,9 +30,9 @@ function wrapHandleApiRequest (handleApiRequest) {
29
30
  if (!handled) return handled
30
31
 
31
32
  return this.hasPage(pathname).then(pageFound => {
32
- const page = pageFound ? pathname : getPageFromPath(pathname, this.dynamicRoutes)
33
+ const pageData = pageFound ? { page: pathname } : getPageFromPath(pathname, this.dynamicRoutes)
33
34
 
34
- pageLoadChannel.publish({ page })
35
+ pageLoadChannel.publish(pageData)
35
36
 
36
37
  return handled
37
38
  })
@@ -84,15 +85,19 @@ function wrapFindPageComponents (findPageComponents) {
84
85
  const result = findPageComponents.apply(this, arguments)
85
86
 
86
87
  if (result) {
87
- pageLoadChannel.publish({ page: getPagePath(pathname) })
88
+ pageLoadChannel.publish(getPagePath(pathname))
88
89
  }
89
90
 
90
91
  return result
91
92
  }
92
93
  }
93
94
 
94
- function getPagePath (page) {
95
- return typeof page === 'object' ? page.pathname : page
95
+ function getPagePath (maybePage) {
96
+ if (typeof maybePage !== 'object') return { page: maybePage }
97
+
98
+ const isAppPath = maybePage.isAppPath
99
+ const page = maybePage.pathname || maybePage.page
100
+ return { page, isAppPath }
96
101
  }
97
102
 
98
103
  function getPageFromPath (page, dynamicRoutes = []) {
@@ -133,57 +138,16 @@ function instrument (req, res, handler) {
133
138
  })
134
139
  }
135
140
 
136
- function wrapSetupServerWorker (setupServerWorker) {
137
- return function (requestHandler) {
138
- arguments[0] = shimmer.wrap(requestHandler, wrapRequestHandler(requestHandler))
139
- return setupServerWorker.apply(this, arguments)
140
- }
141
- }
142
-
143
- function wrapInitialize (initialize) {
144
- return async function () {
145
- const result = await initialize.apply(this, arguments)
146
- if (Array.isArray(result)) {
147
- const requestHandler = result[0]
148
- result[0] = shimmer.wrap(requestHandler, wrapRequestHandler(requestHandler))
149
- }
150
- return result
151
- }
152
- }
153
-
154
- function wrapRequestHandler (requestHandler) {
155
- return function (req, res) {
156
- return instrument(req, res, async () => {
157
- const result = await requestHandler.apply(this, arguments) // apply here first to get page path association
158
-
159
- const page = requestToNextjsPagePath.get(req)
160
- if (page && pageLoadChannel.hasSubscribers) pageLoadChannel.publish({ page })
141
+ function wrapServeStatic (serveStatic) {
142
+ return function (req, res, path) {
143
+ return instrument(req, res, () => {
144
+ if (pageLoadChannel.hasSubscribers && path) pageLoadChannel.publish({ page: path })
161
145
 
162
- return result
146
+ return serveStatic.apply(this, arguments)
163
147
  })
164
148
  }
165
149
  }
166
150
 
167
- // these two functions make sure we get path groups for routes in standalone,
168
- // as it doesn't route through `next-server`/`base-server`
169
- function wrapGetResolveRoutes (getResolveRoutes) {
170
- return function () {
171
- const result = getResolveRoutes.apply(this, arguments)
172
- return shimmer.wrap(result, wrapResolveRoutes(result))
173
- }
174
- }
175
-
176
- function wrapResolveRoutes (resolveRoutes) {
177
- return async function (req) {
178
- const result = await resolveRoutes.apply(this, arguments)
179
- if (result && result.matchedOutput) {
180
- const path = result.matchedOutput.itemPath
181
- requestToNextjsPagePath.set(req, path)
182
- }
183
- return result
184
- }
185
- }
186
-
187
151
  function finish (ctx, result, err) {
188
152
  if (err) {
189
153
  ctx.error = err
@@ -201,25 +165,21 @@ function finish (ctx, result, err) {
201
165
 
202
166
  addHook({
203
167
  name: 'next',
204
- versions: ['>=13.4.13'],
205
- file: 'dist/server/lib/router-utils/resolve-routes.js'
206
- }, resolveRoutesModule => shimmer.wrap(resolveRoutesModule, 'getResolveRoutes', wrapGetResolveRoutes))
207
-
208
- addHook({
209
- name: 'next',
210
- versions: ['13.4.13'],
211
- file: 'dist/server/lib/setup-server-worker.js'
212
- }, setupServerWorker => shimmer.wrap(setupServerWorker, 'initializeServerWorker', wrapSetupServerWorker))
168
+ versions: ['>=11.1'],
169
+ file: 'dist/server/serve-static.js'
170
+ }, serveStatic => shimmer.wrap(serveStatic, 'serveStatic', wrapServeStatic))
213
171
 
214
172
  addHook({
215
173
  name: 'next',
216
- versions: ['>=13.4.15'],
217
- file: 'dist/server/lib/router-server.js'
218
- }, routerServer => shimmer.wrap(routerServer, 'initialize', wrapInitialize))
174
+ versions: DD_MAJOR >= 4 ? ['>=10.2 <11.1'] : ['>=9.5 <11.1'],
175
+ file: 'dist/next-server/server/serve-static.js'
176
+ }, serveStatic => shimmer.wrap(serveStatic, 'serveStatic', wrapServeStatic))
219
177
 
220
178
  addHook({ name: 'next', versions: ['>=13.2'], file: 'dist/server/next-server.js' }, nextServer => {
221
179
  const Server = nextServer.default
222
180
 
181
+ shimmer.wrap(Server.prototype, 'handleRequest', wrapHandleRequest)
182
+ shimmer.wrap(Server.prototype, 'handleApiRequest', wrapHandleApiRequestWithMatch)
223
183
  shimmer.wrap(Server.prototype, 'renderToResponse', wrapRenderToResponse)
224
184
  shimmer.wrap(Server.prototype, 'renderErrorToResponse', wrapRenderErrorToResponse)
225
185
  shimmer.wrap(Server.prototype, 'findPageComponents', wrapFindPageComponents)
@@ -227,22 +187,6 @@ addHook({ name: 'next', versions: ['>=13.2'], file: 'dist/server/next-server.js'
227
187
  return nextServer
228
188
  })
229
189
 
230
- // these functions wrapped in all versions above 13.2 except:
231
- // 13.4.13 due to tests failing when these functions are wrapped
232
- // 13.4.14 due to it not being in the NPM registry/officially released
233
- addHook({
234
- name: 'next',
235
- versions: ['>=13.2 <13.4.13', '>=13.4.15'],
236
- file: 'dist/server/next-server.js'
237
- }, nextServer => {
238
- const Server = nextServer.default
239
-
240
- shimmer.wrap(Server.prototype, 'handleRequest', wrapHandleRequest)
241
- shimmer.wrap(Server.prototype, 'handleApiRequest', wrapHandleApiRequestWithMatch)
242
-
243
- return nextServer
244
- })
245
-
246
190
  addHook({ name: 'next', versions: ['>=11.1 <13.2'], file: 'dist/server/next-server.js' }, nextServer => {
247
191
  const Server = nextServer.default
248
192
 
@@ -270,3 +214,41 @@ addHook({
270
214
 
271
215
  return nextServer
272
216
  })
217
+
218
+ addHook({
219
+ name: 'next',
220
+ versions: ['>=13'],
221
+ file: 'dist/server/web/spec-extension/request.js'
222
+ }, request => {
223
+ const nextUrlDescriptor = Object.getOwnPropertyDescriptor(request.NextRequest.prototype, 'nextUrl')
224
+ shimmer.wrap(nextUrlDescriptor, 'get', function (originalGet) {
225
+ return function wrappedGet () {
226
+ const nextUrl = originalGet.apply(this, arguments)
227
+ if (queryParsedChannel.hasSubscribers) {
228
+ const query = {}
229
+ for (const key of nextUrl.searchParams.keys()) {
230
+ if (!query[key]) {
231
+ query[key] = nextUrl.searchParams.getAll(key)
232
+ }
233
+ }
234
+
235
+ queryParsedChannel.publish({ query })
236
+ }
237
+ return nextUrl
238
+ }
239
+ })
240
+
241
+ Object.defineProperty(request.NextRequest.prototype, 'nextUrl', nextUrlDescriptor)
242
+
243
+ shimmer.massWrap(request.NextRequest.prototype, ['text', 'json'], function (originalMethod) {
244
+ return async function wrappedJson () {
245
+ const body = await originalMethod.apply(this, arguments)
246
+ bodyParsedChannel.publish({
247
+ body
248
+ })
249
+ return body
250
+ }
251
+ })
252
+
253
+ return request
254
+ })
@@ -39,28 +39,27 @@ function wrapQuery (query) {
39
39
  ? arguments[0]
40
40
  : { text: arguments[0] }
41
41
 
42
- // The query objects passed in can be pretty complex. They can be instances of EventEmitter.
43
- // For this reason we can't make a shallow clone of the object.
44
- // Some libraries, such as sql-template-tags, can provide a getter .text property.
45
- // For this reason we can't replace the .text property.
46
- // Instead, we create a new object, and set the original query as the prototype.
47
- // This allows any existing methods to still work and lets us easily provide a new query.
48
- let newQuery = {
49
- __ddInjectableQuery: '',
50
- get text () {
51
- return this.__ddInjectableQuery || Object.getPrototypeOf(this).text
52
- }
42
+ const textProp = Object.getOwnPropertyDescriptor(pgQuery, 'text')
43
+
44
+ // Only alter `text` property if safe to do so.
45
+ if (!textProp || textProp.configurable) {
46
+ const originalText = pgQuery.text
47
+
48
+ Object.defineProperty(pgQuery, 'text', {
49
+ get () {
50
+ return this?.__ddInjectableQuery || originalText
51
+ }
52
+ })
53
53
  }
54
- Object.setPrototypeOf(newQuery, pgQuery)
55
54
 
56
55
  return asyncResource.runInAsyncScope(() => {
57
56
  startCh.publish({
58
57
  params: this.connectionParameters,
59
- query: newQuery,
58
+ query: pgQuery,
60
59
  processId
61
60
  })
62
61
 
63
- arguments[0] = newQuery
62
+ arguments[0] = pgQuery
64
63
 
65
64
  const finish = asyncResource.bind(function (error) {
66
65
  if (error) {
@@ -73,7 +72,7 @@ function wrapQuery (query) {
73
72
  const queryQueue = this.queryQueue || this._queryQueue
74
73
  const activeQuery = this.activeQuery || this._activeQuery
75
74
 
76
- newQuery = queryQueue[queryQueue.length - 1] || activeQuery
75
+ const newQuery = queryQueue[queryQueue.length - 1] || activeQuery
77
76
 
78
77
  if (!newQuery) {
79
78
  return retval
@@ -181,6 +181,15 @@ function dispatcherHook (dispatcherExport) {
181
181
  return dispatcherExport
182
182
  }
183
183
 
184
+ function getTestByTestId (dispatcher, testId) {
185
+ if (dispatcher._testById) {
186
+ return dispatcher._testById.get(testId)?.test
187
+ }
188
+ if (dispatcher._allTests) {
189
+ return dispatcher._allTests.find(({ id }) => id === testId)
190
+ }
191
+ }
192
+
184
193
  function dispatcherHookNew (dispatcherExport, runWrapper) {
185
194
  shimmer.wrap(dispatcherExport.Dispatcher.prototype, 'run', runWrapper)
186
195
  shimmer.wrap(dispatcherExport.Dispatcher.prototype, '_createWorker', createWorker => function () {
@@ -188,11 +197,11 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
188
197
  const worker = createWorker.apply(this, arguments)
189
198
 
190
199
  worker.on('testBegin', ({ testId }) => {
191
- const { test } = dispatcher._testById.get(testId)
200
+ const test = getTestByTestId(dispatcher, testId)
192
201
  testBeginHandler(test)
193
202
  })
194
203
  worker.on('testEnd', ({ testId, status, errors }) => {
195
- const { test } = dispatcher._testById.get(testId)
204
+ const test = getTestByTestId(dispatcher, testId)
196
205
 
197
206
  testEndHandler(test, STATUS_TO_TEST_STATUS[status], errors && errors[0])
198
207
  })
@@ -254,7 +263,7 @@ addHook({
254
263
  addHook({
255
264
  name: '@playwright/test',
256
265
  file: 'lib/dispatcher.js',
257
- versions: ['>=1.18.0 <1.30.0']
266
+ versions: ['>=1.18.0 <1.30.0']
258
267
  }, dispatcherHook)
259
268
 
260
269
  addHook({
@@ -266,11 +275,23 @@ addHook({
266
275
  addHook({
267
276
  name: '@playwright/test',
268
277
  file: 'lib/runner/dispatcher.js',
269
- versions: ['>=1.31.0']
278
+ versions: ['>=1.31.0 <1.38.0']
270
279
  }, (dispatcher) => dispatcherHookNew(dispatcher, dispatcherRunWrapperNew))
271
280
 
272
281
  addHook({
273
282
  name: '@playwright/test',
274
283
  file: 'lib/runner/runner.js',
275
- versions: ['>=1.31.0']
284
+ versions: ['>=1.31.0 <1.38.0']
276
285
  }, runnerHook)
286
+
287
+ // From >=1.38.0
288
+ addHook({
289
+ name: 'playwright',
290
+ file: 'lib/runner/runner.js',
291
+ versions: ['>=1.38.0']
292
+ }, runnerHook)
293
+ addHook({
294
+ name: 'playwright',
295
+ file: 'lib/runner/dispatcher.js',
296
+ versions: ['>=1.38.0']
297
+ }, (dispatcher) => dispatcherHookNew(dispatcher, dispatcherRunWrapperNew))
@@ -10,7 +10,9 @@ const {
10
10
  finishAllTraceSpans,
11
11
  getTestSuitePath,
12
12
  getTestSuiteCommonTags,
13
- addIntelligentTestRunnerSpanTags
13
+ addIntelligentTestRunnerSpanTags,
14
+ TEST_ITR_UNSKIPPABLE,
15
+ TEST_ITR_FORCED_RUN
14
16
  } = require('../../dd-trace/src/plugins/util/test')
15
17
  const { RESOURCE_NAME } = require('../../../ext/tags')
16
18
  const { COMPONENT, ERROR_MESSAGE } = require('../../dd-trace/src/constants')
@@ -29,7 +31,9 @@ class CucumberPlugin extends CiPlugin {
29
31
  status,
30
32
  isSuitesSkipped,
31
33
  numSkippedSuites,
32
- testCodeCoverageLinesTotal
34
+ testCodeCoverageLinesTotal,
35
+ hasUnskippableSuites,
36
+ hasForcedToRunSuites
33
37
  }) => {
34
38
  const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.itrConfig || {}
35
39
  addIntelligentTestRunnerSpanTags(
@@ -41,7 +45,9 @@ class CucumberPlugin extends CiPlugin {
41
45
  isCodeCoverageEnabled,
42
46
  testCodeCoverageLinesTotal,
43
47
  skippingCount: numSkippedSuites,
44
- skippingType: 'suite'
48
+ skippingType: 'suite',
49
+ hasUnskippableSuites,
50
+ hasForcedToRunSuites
45
51
  }
46
52
  )
47
53
 
@@ -55,13 +61,19 @@ class CucumberPlugin extends CiPlugin {
55
61
  this.tracer._exporter.flush()
56
62
  })
57
63
 
58
- this.addSub('ci:cucumber:test-suite:start', (testSuiteFullPath) => {
64
+ this.addSub('ci:cucumber:test-suite:start', ({ testSuitePath, isUnskippable, isForcedToRun }) => {
59
65
  const testSuiteMetadata = getTestSuiteCommonTags(
60
66
  this.command,
61
67
  this.frameworkVersion,
62
- getTestSuitePath(testSuiteFullPath, this.sourceRoot),
68
+ testSuitePath,
63
69
  'cucumber'
64
70
  )
71
+ if (isUnskippable) {
72
+ testSuiteMetadata[TEST_ITR_UNSKIPPABLE] = 'true'
73
+ }
74
+ if (isForcedToRun) {
75
+ testSuiteMetadata[TEST_ITR_FORCED_RUN] = 'true'
76
+ }
65
77
  this.testSuiteSpan = this.tracer.startSpan('cucumber.test_suite', {
66
78
  childOf: this.testModuleSpan,
67
79
  tags: {