dd-trace 6.2.0 → 6.3.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 (27) hide show
  1. package/README.md +1 -1
  2. package/package.json +3 -4
  3. package/packages/datadog-instrumentations/src/cookie.js +7 -1
  4. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -1
  5. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/modelcontextprotocol-sdk.js +30 -54
  6. package/packages/datadog-instrumentations/src/http/server.js +27 -0
  7. package/packages/datadog-instrumentations/src/modelcontextprotocol-sdk.js +248 -1
  8. package/packages/datadog-instrumentations/src/playwright.js +40 -3
  9. package/packages/datadog-plugin-azure-functions/src/index.js +2 -1
  10. package/packages/datadog-plugin-graphql/src/request.js +1 -1
  11. package/packages/datadog-plugin-modelcontextprotocol-sdk/src/tracing.js +144 -26
  12. package/packages/datadog-plugin-modelcontextprotocol-sdk/src/utils.js +30 -0
  13. package/packages/datadog-plugin-playwright/src/index.js +1 -1
  14. package/packages/datadog-shimmer/src/shimmer.js +9 -2
  15. package/packages/dd-trace/src/appsec/channels.js +1 -0
  16. package/packages/dd-trace/src/appsec/index.js +6 -3
  17. package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
  18. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +23 -16
  19. package/packages/dd-trace/src/config/config-types.d.ts +8 -0
  20. package/packages/dd-trace/src/config/defaults.js +20 -15
  21. package/packages/dd-trace/src/config/index.js +13 -3
  22. package/packages/dd-trace/src/config/parsers.js +79 -0
  23. package/packages/dd-trace/src/llmobs/constants/tags.js +1 -0
  24. package/packages/dd-trace/src/llmobs/tagger.js +15 -1
  25. package/packages/dd-trace/src/opentelemetry/span_processor.js +7 -5
  26. package/packages/dd-trace/src/opentelemetry/tracer_provider.js +32 -16
  27. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +20 -11
package/README.md CHANGED
@@ -28,7 +28,7 @@ Most of the documentation for `dd-trace` is available on these webpages:
28
28
  | [`v2`](https://github.com/DataDog/dd-trace-js/tree/v2.x) | ![npm v2](https://img.shields.io/npm/v/dd-trace/latest-node12?color=white&label=%20&style=flat-square) | `>= v12` | NO | NO | **EOL** | 2022-01-28 | 2023-08-15 |
29
29
  | [`v3`](https://github.com/DataDog/dd-trace-js/tree/v3.x) | ![npm v3](https://img.shields.io/npm/v/dd-trace/latest-node14?color=white&label=%20&style=flat-square) | `>= v14` | NO | YES | **EOL** | 2022-08-15 | 2024-05-15 |
30
30
  | [`v4`](https://github.com/DataDog/dd-trace-js/tree/v4.x) | ![npm v4](https://img.shields.io/npm/v/dd-trace/latest-node16?color=white&label=%20&style=flat-square) | `>= v16` | YES | YES | **EOL** | 2023-05-12 | 2025-01-11 |
31
- | [`v5`](https://github.com/DataDog/dd-trace-js/tree/v5.x) | ![npm v5](https://img.shields.io/npm/v/dd-trace/latest-node18?color=white&label=%20&style=flat-square) | `>= v18` | YES | YES | **Maintenance** | 2024-01-11 | Unknown |
31
+ | [`v5`](https://github.com/DataDog/dd-trace-js/tree/v5.x) | ![npm v5](https://img.shields.io/npm/v/dd-trace/latest-node18?color=white&label=%20&style=flat-square) | `>= v18` | YES | YES | **Maintenance** | 2024-01-11 | 2027-07-02 |
32
32
  | [`v6`](https://github.com/DataDog/dd-trace-js/tree/v6.x) | ![npm v6](https://img.shields.io/npm/v/dd-trace/latest?color=white&label=%20&style=flat-square) | `>= v22` | YES | YES | **Current** | 2026-07-02 | Unknown |
33
33
 
34
34
  * EOL = End-of-life
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "6.2.0",
3
+ "version": "6.3.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -23,7 +23,7 @@
23
23
  "lint:inspect": "npx @eslint/config-inspector@latest",
24
24
  "lint:codeowners": "codeowners-audit",
25
25
  "lint:codeowners:ci": "codeowners-audit --glob='**/*.spec.js' --glob='benchmark/sirun/**' --glob='.agents/**' --glob='.claude/**' --glob='integration-tests/**'",
26
- "lint:editorconfig": "editorconfig-checker",
26
+ "lint:editorconfig": "docker build -q -t ec .github/editorconfig-checker && docker run --rm --volume=$PWD:/check ec",
27
27
  "release:proposal": "node scripts/release/proposal",
28
28
  "services": "node ./scripts/install_plugin_modules && node packages/dd-trace/test/setup/services",
29
29
  "test": "echo '\nError: The root \"npm test\" command is intentionally disabled.\n\nInstead, run specific test suites:\n - npm run test:trace:core\n - npm run test:appsec\n - etc.\n\nOr run individual test files:\n npx mocha path/to/test.spec.js\n\nSee CONTRIBUTING.md (Testing section) for more details.\n' && exit 1",
@@ -198,11 +198,10 @@
198
198
  "bun": "1.3.14",
199
199
  "c8": "^11.0.0",
200
200
  "codeowners-audit": "^2.9.0",
201
- "editorconfig-checker": "^6.1.1",
202
201
  "eslint": "^9.39.2",
203
202
  "eslint-plugin-cypress": "^6.4.2",
204
203
  "eslint-plugin-import": "^2.32.0",
205
- "eslint-plugin-jsdoc": "^63.0.10",
204
+ "eslint-plugin-jsdoc": "^63.0.12",
206
205
  "eslint-plugin-mocha": "^11.3.0",
207
206
  "eslint-plugin-n": "^18.2.1",
208
207
  "eslint-plugin-promise": "^7.3.0",
@@ -15,7 +15,13 @@ function wrapParse (originalParse) {
15
15
  }
16
16
  }
17
17
 
18
+ /** @param {typeof import('cookie')} cookie */
18
19
  addHook({ name: 'cookie', versions: ['>=0.4'] }, cookie => {
19
- shimmer.wrap(cookie, 'parse', wrapParse)
20
+ for (const name of ['parse', 'parseCookie']) {
21
+ if (typeof cookie[name] === 'function') {
22
+ // shimmer returns a mutable replacement when an ESM namespace export is non-configurable.
23
+ cookie = shimmer.wrap(cookie, name, wrapParse)
24
+ }
25
+ }
20
26
  return cookie
21
27
  })
@@ -20,7 +20,7 @@ module.exports = {
20
20
  '@apollo/server': () => require('../apollo-server'),
21
21
  '@apollo/gateway': () => require('../apollo'),
22
22
  '@langchain/langgraph': { esmFirst: true, fn: () => require('../langgraph') },
23
- '@modelcontextprotocol/sdk': () => require('../modelcontextprotocol-sdk'),
23
+ '@modelcontextprotocol/sdk': { esmFirst: true, fn: () => require('../modelcontextprotocol-sdk') },
24
24
  'apollo-server-core': () => require('../apollo-server-core'),
25
25
  '@aws-sdk/smithy-client': () => require('../aws-sdk'),
26
26
  '@aws/durable-execution-sdk-js': () => require('../aws-durable-execution-sdk-js'),
@@ -1,59 +1,35 @@
1
1
  'use strict'
2
2
 
3
- // NOTE: Protocol.request (dist/esm|cjs/shared/protocol.js) is intentionally not instrumented here.
4
- // It will be used for distributed tracing header injection when server-side coverage is added.
3
+ const CLIENT_METHODS = [
4
+ 'callTool',
5
+ 'listTools',
6
+ 'listResources',
7
+ 'readResource',
8
+ 'listPrompts',
9
+ 'getPrompt',
10
+ ]
11
+
12
+ const SERVER_METHODS = [
13
+ 'executeToolHandler',
14
+ ]
15
+
16
+ function clientEntries (methodName) {
17
+ return ['dist/esm/client/index.js', 'dist/cjs/client/index.js'].map(filePath => ({
18
+ module: { name: '@modelcontextprotocol/sdk', versionRange: '>=1.27.1', filePath },
19
+ functionQuery: { methodName, className: 'Client', kind: 'Async' },
20
+ channelName: `Client_${methodName}`,
21
+ }))
22
+ }
23
+
24
+ function serverEntries (methodName) {
25
+ return ['dist/esm/server/mcp.js', 'dist/cjs/server/mcp.js'].map(filePath => ({
26
+ module: { name: '@modelcontextprotocol/sdk', versionRange: '>=1.27.1', filePath },
27
+ functionQuery: { methodName, className: 'McpServer', kind: 'Async' },
28
+ channelName: `McpServer_${methodName}`,
29
+ }))
30
+ }
5
31
 
6
32
  module.exports = [
7
- {
8
- module: {
9
- name: '@modelcontextprotocol/sdk',
10
- versionRange: '>=1.27.1',
11
- filePath: 'dist/esm/client/index.js',
12
- },
13
- functionQuery: {
14
- methodName: 'callTool',
15
- className: 'Client',
16
- kind: 'Async',
17
- },
18
- channelName: 'Client_callTool',
19
- },
20
- {
21
- module: {
22
- name: '@modelcontextprotocol/sdk',
23
- versionRange: '>=1.27.1',
24
- filePath: 'dist/cjs/client/index.js',
25
- },
26
- functionQuery: {
27
- methodName: 'callTool',
28
- className: 'Client',
29
- kind: 'Async',
30
- },
31
- channelName: 'Client_callTool',
32
- },
33
- {
34
- module: {
35
- name: '@modelcontextprotocol/sdk',
36
- versionRange: '>=1.27.1',
37
- filePath: 'dist/esm/client/index.js',
38
- },
39
- functionQuery: {
40
- methodName: 'listTools',
41
- className: 'Client',
42
- kind: 'Async',
43
- },
44
- channelName: 'Client_listTools',
45
- },
46
- {
47
- module: {
48
- name: '@modelcontextprotocol/sdk',
49
- versionRange: '>=1.27.1',
50
- filePath: 'dist/cjs/client/index.js',
51
- },
52
- functionQuery: {
53
- methodName: 'listTools',
54
- className: 'Client',
55
- kind: 'Async',
56
- },
57
- channelName: 'Client_listTools',
58
- },
33
+ ...CLIENT_METHODS.flatMap(clientEntries),
34
+ ...SERVER_METHODS.flatMap(serverEntries),
59
35
  ]
@@ -13,6 +13,7 @@ const finishServerCh = channel('apm:http:server:request:finish')
13
13
  const startWriteHeadCh = channel('apm:http:server:response:writeHead:start')
14
14
  const finishSetHeaderCh = channel('datadog:http:server:response:set-header:finish')
15
15
  const startSetHeaderCh = channel('datadog:http:server:response:set-header:start')
16
+ const startInformationalResponseCh = channel('datadog:http:server:informational-response:start')
16
17
 
17
18
  const requestFinishedSet = new WeakSet()
18
19
 
@@ -24,10 +25,19 @@ addHook({ name: 'http' }, http => {
24
25
  shimmer.wrap(http.ServerResponse.prototype, 'end', wrapEnd)
25
26
  shimmer.wrap(http.ServerResponse.prototype, 'setHeader', wrapSetHeader)
26
27
  shimmer.wrap(http.ServerResponse.prototype, 'removeHeader', wrapAppendOrRemoveHeader)
28
+ if (http.ServerResponse.prototype.writeContinue) {
29
+ shimmer.wrap(http.ServerResponse.prototype, 'writeContinue', wrapInformationalResponse)
30
+ }
31
+ if (http.ServerResponse.prototype.writeProcessing) {
32
+ shimmer.wrap(http.ServerResponse.prototype, 'writeProcessing', wrapInformationalResponse)
33
+ }
27
34
  // Added in node v16.17.0
28
35
  if (http.ServerResponse.prototype.appendHeader) {
29
36
  shimmer.wrap(http.ServerResponse.prototype, 'appendHeader', wrapAppendOrRemoveHeader)
30
37
  }
38
+ if (http.ServerResponse.prototype.writeEarlyHints) {
39
+ shimmer.wrap(http.ServerResponse.prototype, 'writeEarlyHints', wrapInformationalResponse)
40
+ }
31
41
  return http
32
42
  })
33
43
 
@@ -212,6 +222,23 @@ function wrapAppendOrRemoveHeader (originalMethod) {
212
222
  }
213
223
  }
214
224
 
225
+ function wrapInformationalResponse (originalMethod) {
226
+ return function wrappedInformationalResponse (...args) {
227
+ if (!startInformationalResponseCh.hasSubscribers) {
228
+ return Reflect.apply(originalMethod, this, args)
229
+ }
230
+
231
+ const abortController = new AbortController()
232
+ startInformationalResponseCh.publish({ res: this, abortController })
233
+
234
+ if (abortController.signal.aborted) {
235
+ return
236
+ }
237
+
238
+ return Reflect.apply(originalMethod, this, args)
239
+ }
240
+ }
241
+
215
242
  function wrapEnd (end) {
216
243
  return function wrappedEnd (...args) {
217
244
  if (!startWriteHeadCh.hasSubscribers) {
@@ -1,7 +1,254 @@
1
1
  'use strict'
2
2
 
3
- const { addHook, getHooks } = require('./helpers/instrument')
3
+ const { tracingChannel } = require('dc-polyfill')
4
+ const shimmer = require('../../datadog-shimmer')
5
+ const { addHook, channel, getHooks } = require('./helpers/instrument')
4
6
 
5
7
  for (const hook of getHooks('@modelcontextprotocol/sdk')) {
6
8
  addHook(hook, exports => exports)
7
9
  }
10
+
11
+ const serverRequestCh = tracingChannel('apm:mcp:server:request')
12
+ const clientRequestInjectCh = channel('apm:mcp:client:request:inject')
13
+ const serverToolRegisteredCh = channel('apm:mcp:server:tool:registered')
14
+
15
+ const DISTRIBUTED_TRACE_META_KEY = '_dd_trace_context'
16
+ const TRACED_METHOD_PREFIX = /^(?:tools|resources|prompts)\//
17
+
18
+ // Maps Protocol instance → Map<requestId, ctx>. Shares the ctx object between
19
+ // _onrequest (span start, in the correct HTTP async context), the SDK request
20
+ // handler wrapper (result/error capture), and the SDK cleanup path (span finish).
21
+ const pendingContexts = new WeakMap()
22
+ const wrappedAbortControllerMaps = new WeakSet()
23
+ const wrappedRequestHandlerMaps = new WeakSet()
24
+ const wrappedRequestHandlers = new WeakSet()
25
+
26
+ function addTraceContextToRequest (request, traceContext) {
27
+ return {
28
+ ...request,
29
+ params: {
30
+ ...request.params,
31
+ _meta: {
32
+ ...request.params?._meta,
33
+ [DISTRIBUTED_TRACE_META_KEY]: traceContext,
34
+ },
35
+ },
36
+ }
37
+ }
38
+
39
+ function getPendingContext (protocol, requestId) {
40
+ return pendingContexts.get(protocol)?.get(requestId)
41
+ }
42
+
43
+ function finishServerRequest (protocol, requestId) {
44
+ const pending = pendingContexts.get(protocol)
45
+ const ctx = pending?.get(requestId)
46
+ if (!ctx) return
47
+
48
+ pending.delete(requestId)
49
+ serverRequestCh.asyncEnd.publish(ctx)
50
+ }
51
+
52
+ function wrapRequestHandler (protocol, handler) {
53
+ if (wrappedRequestHandlers.has(handler)) return handler
54
+
55
+ const wrappedHandler = function requestHandlerWithTrace (request, extra) {
56
+ const ctx = getPendingContext(protocol, extra?.requestId)
57
+ if (!ctx) return handler.apply(this, arguments)
58
+
59
+ let result
60
+ try {
61
+ result = handler.apply(this, arguments)
62
+ } catch (err) {
63
+ ctx.error = err
64
+ throw err
65
+ }
66
+
67
+ if (result && typeof result.then === 'function') {
68
+ result.then(result => {
69
+ ctx.result = result
70
+ }, err => {
71
+ ctx.error = err
72
+ })
73
+ return result
74
+ }
75
+
76
+ ctx.result = result
77
+ return result
78
+ }
79
+
80
+ wrappedRequestHandlers.add(wrappedHandler)
81
+ return wrappedHandler
82
+ }
83
+
84
+ function wrapRequestHandlers (protocol) {
85
+ const handlers = protocol._requestHandlers
86
+ if (!handlers || wrappedRequestHandlerMaps.has(handlers)) return
87
+
88
+ wrappedRequestHandlerMaps.add(handlers)
89
+ shimmer.wrap(handlers, 'set', function (original) {
90
+ return function setRequestHandlerWithTrace (method, handler) {
91
+ if (typeof handler === 'function') {
92
+ return original.call(this, method, wrapRequestHandler(protocol, handler))
93
+ }
94
+
95
+ return original.apply(this, arguments)
96
+ }
97
+ })
98
+ }
99
+
100
+ function wrapAbortControllers (protocol) {
101
+ const controllers = protocol._requestHandlerAbortControllers
102
+ if (!controllers || wrappedAbortControllerMaps.has(controllers)) return
103
+
104
+ wrappedAbortControllerMaps.add(controllers)
105
+ shimmer.wrap(controllers, 'delete', function (original) {
106
+ return function deleteAbortControllerWithTrace (requestId) {
107
+ finishServerRequest(protocol, requestId)
108
+ return original.apply(this, arguments)
109
+ }
110
+ })
111
+ shimmer.wrap(controllers, 'clear', function (original) {
112
+ return function clearAbortControllersWithTrace () {
113
+ for (const requestId of this.keys()) {
114
+ finishServerRequest(protocol, requestId)
115
+ }
116
+ return original.apply(this, arguments)
117
+ }
118
+ })
119
+ }
120
+
121
+ function wrapProtocol (Protocol) {
122
+ // Inject trace context into MCP request metadata so out-of-process MCP servers
123
+ // can parent server spans to the client operation span.
124
+ shimmer.wrap(Protocol.prototype, 'request', function (original) {
125
+ return function requestWithTraceContext (request, resultSchema, options) {
126
+ if (!clientRequestInjectCh.hasSubscribers || !TRACED_METHOD_PREFIX.test(request?.method)) {
127
+ return original.apply(this, arguments)
128
+ }
129
+
130
+ const ctx = {}
131
+ clientRequestInjectCh.publish(ctx)
132
+
133
+ if (!ctx.traceContext) {
134
+ return original.apply(this, arguments)
135
+ }
136
+
137
+ return original.call(this, addTraceContextToRequest(request, ctx.traceContext), resultSchema, options)
138
+ }
139
+ })
140
+
141
+ // Start spans in _onrequest — this runs inside the express POST /mcp handler's
142
+ // async context, so ALS correctly parents server spans under the HTTP span.
143
+ shimmer.wrap(Protocol.prototype, '_onrequest', function (original) {
144
+ return function _onrequestWithTrace (request, extra) {
145
+ if (!serverRequestCh.hasSubscribers || !TRACED_METHOD_PREFIX.test(request.method)) {
146
+ return original.call(this, request, extra)
147
+ }
148
+
149
+ wrapAbortControllers(this)
150
+ const ctx = { request, extra }
151
+ serverRequestCh.start.runStores(ctx, () => {
152
+ let pending = pendingContexts.get(this)
153
+ if (!pending) {
154
+ pending = new Map()
155
+ pendingContexts.set(this, pending)
156
+ }
157
+ pending.set(request.id, ctx)
158
+
159
+ try {
160
+ original.call(this, request, extra)
161
+ } catch (err) {
162
+ ctx.error = err
163
+ finishServerRequest(this, request.id)
164
+ throw err
165
+ }
166
+
167
+ // The SDK registers an AbortController only when it actually dispatches a handler.
168
+ // If none was registered (MethodNotFound path), no handler will run and our
169
+ // SDK cleanup hook will never fire — finish the span immediately.
170
+ if (!this._requestHandlerAbortControllers?.has(request.id)) {
171
+ ctx.error = { name: 'McpError', message: 'Method not found' }
172
+ finishServerRequest(this, request.id)
173
+ }
174
+ })
175
+ }
176
+ })
177
+
178
+ // The SDK's handler closure Zod-parses the request, stripping the JSON-RPC `id`.
179
+ // Use `extra.requestId` (the SDK's fullExtra field) to correlate with the pending ctx.
180
+ shimmer.wrap(Protocol.prototype, 'setRequestHandler', function (original) {
181
+ return function setRequestHandlerWithTrace () {
182
+ wrapRequestHandlers(this)
183
+ return original.apply(this, arguments)
184
+ }
185
+ })
186
+
187
+ return Protocol
188
+ }
189
+
190
+ function wrapMcpServer (McpServer) {
191
+ // Both public registration methods (tool/registerTool) delegate here — one hook covers both.
192
+ // Publishes to serverToolRegisteredCh so tracing.js can build a WeakMap of tool → name
193
+ // for O(1) lookup in McpServerToolCallPlugin (executeToolHandler receives the tool object,
194
+ // not the name string).
195
+ shimmer.wrap(McpServer.prototype, '_createRegisteredTool', function (original) {
196
+ return function (name) {
197
+ const result = original.apply(this, arguments)
198
+ if (serverToolRegisteredCh.hasSubscribers) {
199
+ const tool = this._registeredTools?.[name]
200
+ if (tool) {
201
+ serverToolRegisteredCh.publish({ tool, name })
202
+ shimmer.wrap(tool, 'update', function (update) {
203
+ return function updateWithTrace (updates) {
204
+ const result = update.apply(this, arguments)
205
+ if (updates && Object.hasOwn(updates, 'name')) {
206
+ serverToolRegisteredCh.publish({ tool, name: updates.name || undefined })
207
+ }
208
+ return result
209
+ }
210
+ })
211
+ }
212
+ }
213
+ return result
214
+ }
215
+ })
216
+
217
+ return McpServer
218
+ }
219
+
220
+ addHook({
221
+ name: '@modelcontextprotocol/sdk',
222
+ versions: ['>=1.27.1'],
223
+ file: 'dist/cjs/shared/protocol.js',
224
+ }, exports => {
225
+ wrapProtocol(exports.Protocol)
226
+ return exports
227
+ })
228
+
229
+ addHook({
230
+ name: '@modelcontextprotocol/sdk',
231
+ versions: ['>=1.27.1'],
232
+ file: 'dist/esm/shared/protocol.js',
233
+ }, exports => {
234
+ wrapProtocol(exports.Protocol)
235
+ return exports
236
+ })
237
+
238
+ addHook({
239
+ name: '@modelcontextprotocol/sdk',
240
+ versions: ['>=1.27.1'],
241
+ file: 'dist/cjs/server/mcp.js',
242
+ }, exports => {
243
+ wrapMcpServer(exports.McpServer)
244
+ return exports
245
+ })
246
+
247
+ addHook({
248
+ name: '@modelcontextprotocol/sdk',
249
+ versions: ['>=1.27.1'],
250
+ file: 'dist/esm/server/mcp.js',
251
+ }, exports => {
252
+ wrapMcpServer(exports.McpServer)
253
+ return exports
254
+ })
@@ -105,6 +105,7 @@ const efdStartedOriginalTestKeys = new Set()
105
105
  const efdSlowAbortedTests = new Set()
106
106
  const ddPropertiesByTestId = new Map()
107
107
  const ddPropertiesRequestsByTestId = new Map()
108
+ const disabledTestIds = new Set()
108
109
  let rootDir = ''
109
110
  let sessionProjects = []
110
111
 
@@ -113,6 +114,8 @@ const EFD_RETRY_COUNT_REQUEST = 'ddEfdRetryCountRequest'
113
114
  const EFD_RETRY_COUNT_RESPONSE = 'ddEfdRetryCountResponse'
114
115
  const DD_PROPERTIES_REQUEST = 'ddPropertiesRequest'
115
116
  const DD_PROPERTIES_RESPONSE = 'ddProperties'
117
+ const kDdPlaywrightDisabledTestIds = Symbol('ddPlaywrightDisabledTestIds')
118
+ const kDdPlaywrightWorkerHostInstrumented = Symbol('ddPlaywrightWorkerHostInstrumented')
116
119
  const kDdPlaywrightWorkerInstrumented = Symbol('ddPlaywrightWorkerInstrumented')
117
120
 
118
121
  function isValidKnownTests (receivedKnownTests) {
@@ -991,14 +994,36 @@ function onDispatcherCreateWorker (dispatcher, worker) {
991
994
  const projects = getProjectsFromDispatcher(dispatcher)
992
995
  sessionProjects = projects
993
996
 
997
+ if (disabledTestIds.size && !worker[kDdPlaywrightWorkerHostInstrumented] &&
998
+ typeof worker.runTestGroup === 'function') {
999
+ Object.defineProperty(worker, kDdPlaywrightWorkerHostInstrumented, { value: true })
1000
+ shimmer.wrap(worker, 'runTestGroup', runTestGroup => function (runPayload) {
1001
+ // Serial retries can restore disabled tests that were filtered from the initial dispatcher groups.
1002
+ let disabledIds
1003
+ for (const { testId } of runPayload.entries) {
1004
+ if (disabledTestIds.has(testId)) {
1005
+ disabledIds ??= []
1006
+ disabledIds.push(testId)
1007
+ }
1008
+ }
1009
+ if (disabledIds) {
1010
+ runPayload._ddDisabledTestIds = disabledIds
1011
+ }
1012
+ return runTestGroup.apply(this, arguments)
1013
+ })
1014
+ }
1015
+
994
1016
  worker.on('testBegin', ({ testId }) => {
995
1017
  const test = getTestByTestId(dispatcher, testId)
1018
+ if (!test) return
1019
+
996
1020
  const browser = getBrowserNameFromProjects(projects, test)
997
1021
  const shouldCreateTestSpan = test.expectedStatus === 'skipped'
998
1022
  testBeginHandler(test, browser, shouldCreateTestSpan)
999
1023
  })
1000
1024
  worker.on('testEnd', ({ testId, status, errors, annotations }) => {
1001
1025
  const test = getTestByTestId(dispatcher, testId)
1026
+ if (!test) return
1002
1027
 
1003
1028
  const isTimeout = status === 'timedOut'
1004
1029
  const testStatus = STATUS_TO_TEST_STATUS[status]
@@ -1308,6 +1333,7 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
1308
1333
  efdSlowAbortedTests.clear()
1309
1334
  ddPropertiesByTestId.clear()
1310
1335
  ddPropertiesRequestsByTestId.clear()
1336
+ disabledTestIds.clear()
1311
1337
 
1312
1338
  // TODO: we can trick playwright into thinking the session passed by returning
1313
1339
  // 'passed' here. We might be able to use this for both EFD and Test Management tests.
@@ -1552,6 +1578,7 @@ function processRootSuite (createRootSuiteReturnValue) {
1552
1578
  if (testProperties.disabled) {
1553
1579
  test._ddIsDisabled = true
1554
1580
  if (!testProperties.attemptToFix) {
1581
+ disabledTestIds.add(test.id)
1555
1582
  test.expectedStatus = 'skipped'
1556
1583
  // setting test.expectedStatus to 'skipped' does not work for every case,
1557
1584
  // so we need to filter out disabled tests in dispatcherRunWrapperNew,
@@ -1795,7 +1822,17 @@ function instrumentWorkerMainMethods (workerMain) {
1795
1822
  let steps = []
1796
1823
  const stepInfoByStepId = {}
1797
1824
 
1825
+ shimmer.wrap(workerMain, 'runTestGroup', runTestGroup => function (runPayload) {
1826
+ const disabledIds = runPayload._ddDisabledTestIds
1827
+ this[kDdPlaywrightDisabledTestIds] = disabledIds ? new Set(disabledIds) : undefined
1828
+ return runTestGroup.apply(this, arguments)
1829
+ })
1830
+
1798
1831
  shimmer.wrap(workerMain, '_runTest', _runTest => async function (test) {
1832
+ if (this[kDdPlaywrightDisabledTestIds]?.has(test.id)) {
1833
+ test._ddIsDisabled = true
1834
+ test.expectedStatus = 'skipped'
1835
+ }
1799
1836
  await waitForEfdRetryCount(test)
1800
1837
  if (shouldSkipEfdRetry(test)) {
1801
1838
  test._ddShouldSkipEfdRetry = true
@@ -2042,9 +2079,9 @@ function generateSummaryWrapper (generateSummary) {
2042
2079
  return function (...args) {
2043
2080
  for (const test of this.suite.allTests()) {
2044
2081
  // https://github.com/microsoft/playwright/blob/bf92ffecff6f30a292b53430dbaee0207e0c61ad/packages/playwright/src/reporters/base.ts#L279
2045
- const didNotRun = test.outcome() === 'skipped' &&
2046
- (!test.results.length || test.expectedStatus !== 'skipped')
2047
- if (didNotRun && !testsReportedInGenerateSummary.has(test)) {
2082
+ const shouldReportAsSkipped = test.outcome() === 'skipped' &&
2083
+ (disabledTestIds.has(test.id) || !test.results.length || test.expectedStatus !== 'skipped')
2084
+ if (shouldReportAsSkipped && !testsReportedInGenerateSummary.has(test)) {
2048
2085
  testsReportedInGenerateSummary.add(test)
2049
2086
  const {
2050
2087
  _requireFile: testSuiteAbsolutePath,
@@ -65,6 +65,7 @@ class AzureFunctionsPlugin extends TracingPlugin {
65
65
  service: this.serviceName(),
66
66
  type: 'serverless',
67
67
  meta,
68
+ childOf: null,
68
69
  }, ctx)
69
70
 
70
71
  if (isMessagingService) {
@@ -142,7 +143,7 @@ function mapTriggerTag (methodName) {
142
143
  // message & messages & batch with cardinality of 1 == applicationProperties
143
144
  // messages with cardinality of many == applicationPropertiesArray
144
145
  function setSpanLinks (triggerType, tracer, span, ctx) {
145
- const cardinality = ctx.invocationContext.options.trigger.cardinality
146
+ const cardinality = ctx.invocationContext.options.trigger.cardinality ?? 'one'
146
147
  const triggerMetadata = ctx.invocationContext.triggerMetadata
147
148
  const isServiceBus = triggerType === 'ServiceBus'
148
149
 
@@ -82,7 +82,7 @@ class GraphQLRequestPlugin extends TracingPlugin {
82
82
  if (result?.errors?.length) {
83
83
  span.setTag('error', result.errors[0])
84
84
  for (const error of result.errors) {
85
- extractErrorIntoSpanEvent(this._tracerConfig, span, error)
85
+ extractErrorIntoSpanEvent(this.config, span, error)
86
86
  }
87
87
  }
88
88