dd-trace 5.74.0 → 5.76.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.
- package/LICENSE-3rdparty.csv +1 -0
- package/ci/init.js +1 -0
- package/index.d.ts +11 -7
- package/loader-hook.mjs +52 -1
- package/package.json +7 -18
- package/packages/datadog-esbuild/index.js +1 -0
- package/packages/datadog-instrumentations/src/cookie-parser.js +0 -2
- package/packages/datadog-instrumentations/src/cucumber.js +2 -2
- package/packages/datadog-instrumentations/src/express-session.js +0 -1
- package/packages/datadog-instrumentations/src/mariadb.js +9 -7
- package/packages/datadog-instrumentations/src/openai.js +8 -0
- package/packages/datadog-instrumentations/src/playwright.js +163 -37
- package/packages/datadog-instrumentations/src/vitest.js +138 -20
- package/packages/datadog-plugin-aws-sdk/src/base.js +0 -1
- package/packages/datadog-plugin-openai/src/stream-helpers.js +26 -1
- package/packages/datadog-plugin-openai/src/tracing.js +46 -1
- package/packages/datadog-plugin-playwright/src/index.js +74 -31
- package/packages/datadog-plugin-vitest/src/index.js +5 -1
- package/packages/datadog-shimmer/src/shimmer.js +2 -0
- package/packages/dd-trace/src/aiguard/sdk.js +25 -3
- package/packages/dd-trace/src/aiguard/tags.js +4 -1
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +6 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +30 -7
- package/packages/dd-trace/src/config-helper.js +1 -0
- package/packages/dd-trace/src/config.js +345 -329
- package/packages/dd-trace/src/config_defaults.js +9 -0
- package/packages/dd-trace/src/llmobs/plugins/openai.js +216 -12
- package/packages/dd-trace/src/llmobs/span_processor.js +2 -1
- package/packages/dd-trace/src/llmobs/tagger.js +9 -3
- package/packages/dd-trace/src/plugins/ci_plugin.js +3 -1
- package/packages/dd-trace/src/plugins/util/test.js +6 -0
- package/packages/dd-trace/src/profiling/config.js +32 -10
- package/packages/dd-trace/src/proxy.js +1 -1
- package/packages/dd-trace/src/remote_config/capabilities.js +2 -0
- package/packages/dd-trace/src/remote_config/index.js +4 -0
- package/packages/dd-trace/src/supported-configurations.json +1 -0
- package/packages/dd-trace/src/telemetry/logs/log-collector.js +5 -3
- package/register.js +1 -11
package/LICENSE-3rdparty.csv
CHANGED
|
@@ -14,6 +14,7 @@ require,@opentelemetry/resources,Apache license 2.0,Copyright OpenTelemetry Auth
|
|
|
14
14
|
require,@isaacs/ttlcache,ISC,Copyright (c) 2022-2023 - Isaac Z. Schlueter and Contributors
|
|
15
15
|
require,crypto-randomuuid,MIT,Copyright 2021 Node.js Foundation and contributors
|
|
16
16
|
require,dc-polyfill,MIT,Copyright 2023 Datadog Inc.
|
|
17
|
+
require,escape-string-regexp,MIT,Copyright Sindre Sorhus
|
|
17
18
|
require,ignore,MIT,Copyright 2013 Kael Zhang and contributors
|
|
18
19
|
require,import-in-the-middle,Apache license 2.0,Copyright 2021 Datadog Inc.
|
|
19
20
|
require,istanbul-lib-coverage,BSD-3-Clause,Copyright 2012-2015 Yahoo! Inc.
|
package/ci/init.js
CHANGED
|
@@ -29,6 +29,7 @@ function detectTestWorkerType () {
|
|
|
29
29
|
if (getEnvironmentVariable('MOCHA_WORKER_ID')) return 'mocha'
|
|
30
30
|
if (getEnvironmentVariable('DD_PLAYWRIGHT_WORKER')) return 'playwright'
|
|
31
31
|
if (getEnvironmentVariable('TINYPOOL_WORKER_ID')) return 'vitest'
|
|
32
|
+
if (getEnvironmentVariable('DD_VITEST_WORKER')) return 'vitest'
|
|
32
33
|
return null
|
|
33
34
|
}
|
|
34
35
|
|
package/index.d.ts
CHANGED
|
@@ -181,6 +181,7 @@ interface Tracer extends opentracing.Tracer {
|
|
|
181
181
|
/** @hidden */
|
|
182
182
|
interface Plugins {
|
|
183
183
|
"aerospike": tracer.plugins.aerospike;
|
|
184
|
+
"ai": tracer.plugins.ai;
|
|
184
185
|
"amqp10": tracer.plugins.amqp10;
|
|
185
186
|
"amqplib": tracer.plugins.amqplib;
|
|
186
187
|
"anthropic": tracer.plugins.anthropic;
|
|
@@ -1642,6 +1643,12 @@ declare namespace tracer {
|
|
|
1642
1643
|
*/
|
|
1643
1644
|
interface aerospike extends Instrumentation {}
|
|
1644
1645
|
|
|
1646
|
+
/**
|
|
1647
|
+
* This plugin automatically instruments the
|
|
1648
|
+
* [Vercel AI SDK](https://ai-sdk.dev/docs/introduction) module.
|
|
1649
|
+
*/
|
|
1650
|
+
interface ai extends Instrumentation {}
|
|
1651
|
+
|
|
1645
1652
|
/**
|
|
1646
1653
|
* This plugin automatically instruments the
|
|
1647
1654
|
* [amqp10](https://github.com/noodlefrenzy/node-amqp10) module.
|
|
@@ -1698,12 +1705,6 @@ declare namespace tracer {
|
|
|
1698
1705
|
* [aws-sdk](https://github.com/aws/aws-sdk-js) module.
|
|
1699
1706
|
*/
|
|
1700
1707
|
interface aws_sdk extends Instrumentation {
|
|
1701
|
-
/**
|
|
1702
|
-
* Whether to add a suffix to the service name so that each AWS service has its own service name.
|
|
1703
|
-
* @default true
|
|
1704
|
-
*/
|
|
1705
|
-
splitByAwsService?: boolean;
|
|
1706
|
-
|
|
1707
1708
|
/**
|
|
1708
1709
|
* Whether to inject all messages during batch AWS SQS, Kinesis, and SNS send operations. Normal
|
|
1709
1710
|
* behavior is to inject the first message in batch send operations.
|
|
@@ -2821,7 +2822,10 @@ declare namespace tracer {
|
|
|
2821
2822
|
redactionValuePattern?: string,
|
|
2822
2823
|
|
|
2823
2824
|
/**
|
|
2824
|
-
* Allows to enable security controls.
|
|
2825
|
+
* Allows to enable security controls. This option is not supported when
|
|
2826
|
+
* using ESM.
|
|
2827
|
+
* @deprecated Please use the DD_IAST_SECURITY_CONTROLS_CONFIGURATION
|
|
2828
|
+
* environment variable instead.
|
|
2825
2829
|
*/
|
|
2826
2830
|
securityControlsConfiguration?: string,
|
|
2827
2831
|
|
package/loader-hook.mjs
CHANGED
|
@@ -1 +1,52 @@
|
|
|
1
|
-
|
|
1
|
+
import regexpEscape from 'escape-string-regexp'
|
|
2
|
+
import * as iitm from 'import-in-the-middle/hook.mjs'
|
|
3
|
+
import hooks from './packages/datadog-instrumentations/src/helpers/hooks.js'
|
|
4
|
+
import configHelper from './packages/dd-trace/src/config-helper.js'
|
|
5
|
+
|
|
6
|
+
// For some reason `getEnvironmentVariable` is not otherwise available to ESM.
|
|
7
|
+
const env = configHelper.getEnvironmentVariable
|
|
8
|
+
|
|
9
|
+
function initialize (data = {}) {
|
|
10
|
+
data.include ??= []
|
|
11
|
+
data.exclude ??= []
|
|
12
|
+
|
|
13
|
+
addInstrumentations(data)
|
|
14
|
+
addSecurityControls(data)
|
|
15
|
+
addExclusions(data)
|
|
16
|
+
|
|
17
|
+
return iitm.initialize(data)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function addInstrumentations (data) {
|
|
21
|
+
const instrumentations = Object.keys(hooks)
|
|
22
|
+
|
|
23
|
+
for (const moduleName of instrumentations) {
|
|
24
|
+
data.include.push(new RegExp(`node_modules/${moduleName}/(?!node_modules).+`), moduleName)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function addSecurityControls (data) {
|
|
29
|
+
const securityControls = (env('DD_IAST_SECURITY_CONTROLS_CONFIGURATION') || '')
|
|
30
|
+
.split(';')
|
|
31
|
+
.map(sc => sc.trim().split(':')[2])
|
|
32
|
+
.filter(Boolean)
|
|
33
|
+
.map(sc => sc.trim())
|
|
34
|
+
|
|
35
|
+
for (const subpath of securityControls) {
|
|
36
|
+
data.include.push(new RegExp(regexpEscape(subpath)))
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function addExclusions (data) {
|
|
41
|
+
data.exclude.push(
|
|
42
|
+
/middle/,
|
|
43
|
+
/langsmith/,
|
|
44
|
+
/openai\/_shims/,
|
|
45
|
+
/openai\/resources\/chat\/completions\/messages/,
|
|
46
|
+
/openai\/agents-core\/dist\/shims/,
|
|
47
|
+
/@anthropic-ai\/sdk\/_shims/
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export { initialize }
|
|
52
|
+
export { load, getFormat, resolve, getSource } from 'import-in-the-middle/hook.mjs'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dd-trace",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.76.0",
|
|
4
4
|
"description": "Datadog APM tracing client for JavaScript",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"typings": "index.d.ts",
|
|
@@ -125,8 +125,8 @@
|
|
|
125
125
|
"@datadog/native-appsec": "10.3.0",
|
|
126
126
|
"@datadog/native-iast-taint-tracking": "4.0.0",
|
|
127
127
|
"@datadog/native-metrics": "3.1.1",
|
|
128
|
-
"@datadog/openfeature-node-server": "0.1.0-preview.
|
|
129
|
-
"@datadog/pprof": "5.
|
|
128
|
+
"@datadog/openfeature-node-server": "0.1.0-preview.13",
|
|
129
|
+
"@datadog/pprof": "5.12.0",
|
|
130
130
|
"@datadog/sketches-js": "2.1.1",
|
|
131
131
|
"@datadog/wasm-js-rewriter": "4.0.1",
|
|
132
132
|
"@isaacs/ttlcache": "^1.4.1",
|
|
@@ -136,6 +136,7 @@
|
|
|
136
136
|
"@opentelemetry/resources": ">=1.0.0 <1.10.0",
|
|
137
137
|
"crypto-randomuuid": "^1.0.0",
|
|
138
138
|
"dc-polyfill": "^0.1.10",
|
|
139
|
+
"escape-string-regexp": "^5.0.0",
|
|
139
140
|
"ignore": "^7.0.5",
|
|
140
141
|
"import-in-the-middle": "^1.14.2",
|
|
141
142
|
"istanbul-lib-coverage": "^3.2.2",
|
|
@@ -158,25 +159,13 @@
|
|
|
158
159
|
"tlhunter-sorted-set": "^0.1.0",
|
|
159
160
|
"ttl-set": "^1.0.0"
|
|
160
161
|
},
|
|
161
|
-
"peerDependencies": {
|
|
162
|
-
"@openfeature/core": "^1.9.0",
|
|
163
|
-
"@openfeature/server-sdk": "~1.20.0"
|
|
164
|
-
},
|
|
165
|
-
"peerDependenciesMeta": {
|
|
166
|
-
"@openfeature/core": {
|
|
167
|
-
"optional": true
|
|
168
|
-
},
|
|
169
|
-
"@openfeature/server-sdk": {
|
|
170
|
-
"optional": true
|
|
171
|
-
}
|
|
172
|
-
},
|
|
173
162
|
"devDependencies": {
|
|
174
163
|
"@babel/helpers": "^7.27.6",
|
|
175
164
|
"@eslint/eslintrc": "^3.3.1",
|
|
176
165
|
"@eslint/js": "^9.29.0",
|
|
177
166
|
"@msgpack/msgpack": "^3.1.2",
|
|
178
|
-
"@openfeature/core": "^1.
|
|
179
|
-
"@openfeature/server-sdk": "
|
|
167
|
+
"@openfeature/core": "^1.9.0",
|
|
168
|
+
"@openfeature/server-sdk": "^1.20.0",
|
|
180
169
|
"@stylistic/eslint-plugin": "^5.0.0",
|
|
181
170
|
"@types/chai": "^4.3.16",
|
|
182
171
|
"@types/mocha": "^10.0.10",
|
|
@@ -214,7 +203,7 @@
|
|
|
214
203
|
"tap": "^16.3.10",
|
|
215
204
|
"tiktoken": "^1.0.21",
|
|
216
205
|
"typescript": "^5.9.2",
|
|
217
|
-
"workerpool": "^
|
|
206
|
+
"workerpool": "^10.0.0",
|
|
218
207
|
"yaml": "^2.8.0",
|
|
219
208
|
"yarn-deduplicate": "^6.0.2"
|
|
220
209
|
}
|
|
@@ -25,8 +25,6 @@ addHook({
|
|
|
25
25
|
name: 'cookie-parser',
|
|
26
26
|
versions: ['>=1.0.0']
|
|
27
27
|
}, cookieParser => {
|
|
28
|
-
// This prevents the non default export from entering the wrapping process
|
|
29
|
-
if (cookieParser.default) return cookieParser
|
|
30
28
|
return shimmer.wrapFunction(cookieParser, cookieParser => function () {
|
|
31
29
|
const cookieMiddleware = cookieParser.apply(this, arguments)
|
|
32
30
|
|
|
@@ -259,7 +259,7 @@ function wrapRun (pl, isLatestVersion, version) {
|
|
|
259
259
|
testStartCh.runStores(ctx, () => {})
|
|
260
260
|
const promises = {}
|
|
261
261
|
try {
|
|
262
|
-
this.eventBroadcaster.on('envelope',
|
|
262
|
+
this.eventBroadcaster.on('envelope', async (testCase) => {
|
|
263
263
|
// Only supported from >=8.0.0
|
|
264
264
|
if (testCase?.testCaseFinished) {
|
|
265
265
|
const { testCaseFinished: { willBeRetried } } = testCase
|
|
@@ -289,7 +289,7 @@ function wrapRun (pl, isLatestVersion, version) {
|
|
|
289
289
|
testStartCh.runStores(newCtx, () => {})
|
|
290
290
|
}
|
|
291
291
|
}
|
|
292
|
-
})
|
|
292
|
+
})
|
|
293
293
|
let promise
|
|
294
294
|
|
|
295
295
|
testFnCh.runStores(ctx, () => {
|
|
@@ -82,12 +82,7 @@ function createWrapQueryCallback (options) {
|
|
|
82
82
|
|
|
83
83
|
const cb = arguments[arguments.length - 1]
|
|
84
84
|
const ctx = { sql, conf: options }
|
|
85
|
-
|
|
86
|
-
if (typeof cb !== 'function') {
|
|
87
|
-
arguments.length += 1
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
arguments[arguments.length - 1] = shimmer.wrapFunction(cb, cb => function (err) {
|
|
85
|
+
const wrapper = (cb) => function (err) {
|
|
91
86
|
if (err) {
|
|
92
87
|
ctx.error = err
|
|
93
88
|
errorCh.publish(ctx)
|
|
@@ -96,7 +91,14 @@ function createWrapQueryCallback (options) {
|
|
|
96
91
|
return typeof cb === 'function'
|
|
97
92
|
? finishCh.runStores(ctx, cb, this, ...arguments)
|
|
98
93
|
: finishCh.publish(ctx)
|
|
99
|
-
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (typeof cb === 'function') {
|
|
97
|
+
arguments[arguments.length - 1] = shimmer.wrapFunction(cb, wrapper)
|
|
98
|
+
} else {
|
|
99
|
+
arguments.length += 1
|
|
100
|
+
arguments[arguments.length - 1] = wrapper()
|
|
101
|
+
}
|
|
100
102
|
|
|
101
103
|
return startCh.runStores(ctx, query, this, ...arguments)
|
|
102
104
|
}
|
|
@@ -22,6 +22,14 @@ const V4_PACKAGE_SHIMS = [
|
|
|
22
22
|
methods: ['create'],
|
|
23
23
|
streamedResponse: true
|
|
24
24
|
},
|
|
25
|
+
{
|
|
26
|
+
file: 'resources/responses/responses',
|
|
27
|
+
targetClass: 'Responses',
|
|
28
|
+
baseResource: 'responses',
|
|
29
|
+
methods: ['create'],
|
|
30
|
+
streamedResponse: true,
|
|
31
|
+
versions: ['>=4.87.0']
|
|
32
|
+
},
|
|
25
33
|
{
|
|
26
34
|
file: 'resources/embeddings',
|
|
27
35
|
targetClass: 'Embeddings',
|
|
@@ -15,6 +15,7 @@ const { DD_MAJOR } = require('../../../version')
|
|
|
15
15
|
|
|
16
16
|
const testStartCh = channel('ci:playwright:test:start')
|
|
17
17
|
const testFinishCh = channel('ci:playwright:test:finish')
|
|
18
|
+
const testSkipCh = channel('ci:playwright:test:skip')
|
|
18
19
|
|
|
19
20
|
const testSessionStartCh = channel('ci:playwright:session:start')
|
|
20
21
|
const testSessionFinishCh = channel('ci:playwright:session:finish')
|
|
@@ -37,6 +38,8 @@ const testSuiteToTestStatuses = new Map()
|
|
|
37
38
|
const testSuiteToErrors = new Map()
|
|
38
39
|
const testsToTestStatuses = new Map()
|
|
39
40
|
|
|
41
|
+
const RUM_FLUSH_WAIT_TIME = 1000
|
|
42
|
+
|
|
40
43
|
let applyRepeatEachIndex = null
|
|
41
44
|
|
|
42
45
|
let startedSuites = []
|
|
@@ -65,6 +68,8 @@ let modifiedFiles = {}
|
|
|
65
68
|
const quarantinedOrDisabledTestsAttemptToFix = []
|
|
66
69
|
let quarantinedButNotAttemptToFixFqns = new Set()
|
|
67
70
|
let rootDir = ''
|
|
71
|
+
let sessionProjects = []
|
|
72
|
+
|
|
68
73
|
const MINIMUM_SUPPORTED_VERSION_RANGE_EFD = '>=1.38.0' // TODO: remove this once we drop support for v5
|
|
69
74
|
|
|
70
75
|
function isValidKnownTests (receivedKnownTests) {
|
|
@@ -285,7 +290,12 @@ function getTestFullname (test) {
|
|
|
285
290
|
return names.join(' ')
|
|
286
291
|
}
|
|
287
292
|
|
|
288
|
-
function
|
|
293
|
+
function shouldFinishTestSuite (testSuiteAbsolutePath) {
|
|
294
|
+
const remainingTests = remainingTestsByFile[testSuiteAbsolutePath]
|
|
295
|
+
return !remainingTests.length || remainingTests.every(test => test.expectedStatus === 'skipped')
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function testBeginHandler (test, browserName, shouldCreateTestSpan) {
|
|
289
299
|
const {
|
|
290
300
|
_requireFile: testSuiteAbsolutePath,
|
|
291
301
|
location: {
|
|
@@ -297,6 +307,10 @@ function testBeginHandler (test, browserName, isMainProcess) {
|
|
|
297
307
|
if (_type === 'beforeAll' || _type === 'afterAll') {
|
|
298
308
|
return
|
|
299
309
|
}
|
|
310
|
+
// this means that a skipped test is being handled
|
|
311
|
+
if (!remainingTestsByFile[testSuiteAbsolutePath].length) {
|
|
312
|
+
return
|
|
313
|
+
}
|
|
300
314
|
|
|
301
315
|
const isNewTestSuite = !startedSuites.includes(testSuiteAbsolutePath)
|
|
302
316
|
|
|
@@ -313,7 +327,7 @@ function testBeginHandler (test, browserName, isMainProcess) {
|
|
|
313
327
|
}
|
|
314
328
|
|
|
315
329
|
// this handles tests that do not go through the worker process (because they're skipped)
|
|
316
|
-
if (
|
|
330
|
+
if (shouldCreateTestSpan) {
|
|
317
331
|
const testName = getTestFullname(test)
|
|
318
332
|
const testCtx = {
|
|
319
333
|
testName,
|
|
@@ -328,8 +342,20 @@ function testBeginHandler (test, browserName, isMainProcess) {
|
|
|
328
342
|
}
|
|
329
343
|
}
|
|
330
344
|
|
|
331
|
-
function testEndHandler (
|
|
332
|
-
|
|
345
|
+
function testEndHandler ({
|
|
346
|
+
test,
|
|
347
|
+
annotations,
|
|
348
|
+
testStatus,
|
|
349
|
+
error,
|
|
350
|
+
isTimeout,
|
|
351
|
+
shouldCreateTestSpan,
|
|
352
|
+
projects
|
|
353
|
+
}) {
|
|
354
|
+
const {
|
|
355
|
+
_requireFile: testSuiteAbsolutePath,
|
|
356
|
+
results,
|
|
357
|
+
_type,
|
|
358
|
+
} = test
|
|
333
359
|
|
|
334
360
|
let annotationTags
|
|
335
361
|
if (annotations.length) {
|
|
@@ -368,31 +394,34 @@ function testEndHandler (test, annotations, testStatus, error, isTimeout, isMain
|
|
|
368
394
|
}
|
|
369
395
|
|
|
370
396
|
// this handles tests that do not go through the worker process (because they're skipped)
|
|
371
|
-
if (
|
|
397
|
+
if (shouldCreateTestSpan) {
|
|
372
398
|
const testResult = results.at(-1)
|
|
373
399
|
const testCtx = testToCtx.get(test)
|
|
374
400
|
const isAtrRetry = testResult?.retry > 0 &&
|
|
375
401
|
isFlakyTestRetriesEnabled &&
|
|
376
402
|
!test._ddIsAttemptToFix &&
|
|
377
403
|
!test._ddIsEfdRetry
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
404
|
+
// if there is no testCtx, the skipped test will be created later
|
|
405
|
+
if (testCtx) {
|
|
406
|
+
testFinishCh.publish({
|
|
407
|
+
testStatus,
|
|
408
|
+
steps: testResult?.steps || [],
|
|
409
|
+
isRetry: testResult?.retry > 0,
|
|
410
|
+
error,
|
|
411
|
+
extraTags: annotationTags,
|
|
412
|
+
isNew: test._ddIsNew,
|
|
413
|
+
isAttemptToFix: test._ddIsAttemptToFix,
|
|
414
|
+
isAttemptToFixRetry: test._ddIsAttemptToFixRetry,
|
|
415
|
+
isQuarantined: test._ddIsQuarantined,
|
|
416
|
+
isEfdRetry: test._ddIsEfdRetry,
|
|
417
|
+
hasFailedAllRetries: test._ddHasFailedAllRetries,
|
|
418
|
+
hasPassedAttemptToFixRetries: test._ddHasPassedAttemptToFixRetries,
|
|
419
|
+
hasFailedAttemptToFixRetries: test._ddHasFailedAttemptToFixRetries,
|
|
420
|
+
isAtrRetry,
|
|
421
|
+
isModified: test._ddIsModified,
|
|
422
|
+
...testCtx.currentStore
|
|
423
|
+
})
|
|
424
|
+
}
|
|
396
425
|
}
|
|
397
426
|
|
|
398
427
|
if (testSuiteToTestStatuses.has(testSuiteAbsolutePath)) {
|
|
@@ -410,8 +439,25 @@ function testEndHandler (test, annotations, testStatus, error, isTimeout, isMain
|
|
|
410
439
|
.filter(currentTest => currentTest !== test)
|
|
411
440
|
}
|
|
412
441
|
|
|
413
|
-
|
|
414
|
-
|
|
442
|
+
if (shouldFinishTestSuite(testSuiteAbsolutePath)) {
|
|
443
|
+
const skippedTests = remainingTestsByFile[testSuiteAbsolutePath]
|
|
444
|
+
.filter(test => test.expectedStatus === 'skipped')
|
|
445
|
+
|
|
446
|
+
for (const test of skippedTests) {
|
|
447
|
+
const browserName = getBrowserNameFromProjects(projects, test)
|
|
448
|
+
testSkipCh.publish({
|
|
449
|
+
testName: getTestFullname(test),
|
|
450
|
+
testSuiteAbsolutePath,
|
|
451
|
+
testSourceLine: test.location.line,
|
|
452
|
+
browserName,
|
|
453
|
+
isNew: test._ddIsNew,
|
|
454
|
+
isDisabled: test._ddIsDisabled,
|
|
455
|
+
isModified: test._ddIsModified,
|
|
456
|
+
isQuarantined: test._ddIsQuarantined
|
|
457
|
+
})
|
|
458
|
+
}
|
|
459
|
+
remainingTestsByFile[testSuiteAbsolutePath] = []
|
|
460
|
+
|
|
415
461
|
const testStatuses = testSuiteToTestStatuses.get(testSuiteAbsolutePath)
|
|
416
462
|
let testSuiteStatus = 'pass'
|
|
417
463
|
if (testStatuses.includes('fail')) {
|
|
@@ -450,10 +496,14 @@ function dispatcherHook (dispatcherExport) {
|
|
|
450
496
|
shimmer.wrap(dispatcherExport.Dispatcher.prototype, '_createWorker', createWorker => function () {
|
|
451
497
|
const dispatcher = this
|
|
452
498
|
const worker = createWorker.apply(this, arguments)
|
|
499
|
+
const projects = getProjectsFromDispatcher(dispatcher)
|
|
500
|
+
sessionProjects = projects
|
|
501
|
+
|
|
502
|
+
// for older versions of playwright, `shouldCreateTestSpan` should always be true,
|
|
503
|
+
// since the `_runTest` function wrapper is not available for older versions
|
|
453
504
|
worker.process.on('message', ({ method, params }) => {
|
|
454
505
|
if (method === 'testBegin') {
|
|
455
506
|
const { test } = dispatcher._testById.get(params.testId)
|
|
456
|
-
const projects = getProjectsFromDispatcher(dispatcher)
|
|
457
507
|
const browser = getBrowserNameFromProjects(projects, test)
|
|
458
508
|
testBeginHandler(test, browser, true)
|
|
459
509
|
} else if (method === 'testEnd') {
|
|
@@ -464,12 +514,15 @@ function dispatcherHook (dispatcherExport) {
|
|
|
464
514
|
|
|
465
515
|
const isTimeout = testResult.status === 'timedOut'
|
|
466
516
|
testEndHandler(
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
517
|
+
{
|
|
518
|
+
test,
|
|
519
|
+
annotations: params.annotations,
|
|
520
|
+
testStatus: STATUS_TO_TEST_STATUS[testResult.status],
|
|
521
|
+
error: testResult.error,
|
|
522
|
+
isTimeout,
|
|
523
|
+
shouldCreateTestSpan: true,
|
|
524
|
+
projects
|
|
525
|
+
}
|
|
473
526
|
)
|
|
474
527
|
}
|
|
475
528
|
})
|
|
@@ -484,18 +537,31 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
|
|
|
484
537
|
shimmer.wrap(dispatcherExport.Dispatcher.prototype, '_createWorker', createWorker => function () {
|
|
485
538
|
const dispatcher = this
|
|
486
539
|
const worker = createWorker.apply(this, arguments)
|
|
540
|
+
const projects = getProjectsFromDispatcher(dispatcher)
|
|
541
|
+
sessionProjects = projects
|
|
487
542
|
|
|
488
543
|
worker.on('testBegin', ({ testId }) => {
|
|
489
544
|
const test = getTestByTestId(dispatcher, testId)
|
|
490
|
-
const projects = getProjectsFromDispatcher(dispatcher)
|
|
491
545
|
const browser = getBrowserNameFromProjects(projects, test)
|
|
492
|
-
|
|
546
|
+
const shouldCreateTestSpan = test.expectedStatus === 'skipped'
|
|
547
|
+
testBeginHandler(test, browser, shouldCreateTestSpan)
|
|
493
548
|
})
|
|
494
549
|
worker.on('testEnd', ({ testId, status, errors, annotations }) => {
|
|
495
550
|
const test = getTestByTestId(dispatcher, testId)
|
|
496
551
|
|
|
497
552
|
const isTimeout = status === 'timedOut'
|
|
498
|
-
|
|
553
|
+
const shouldCreateTestSpan = test.expectedStatus === 'skipped'
|
|
554
|
+
testEndHandler(
|
|
555
|
+
{
|
|
556
|
+
test,
|
|
557
|
+
annotations,
|
|
558
|
+
testStatus: STATUS_TO_TEST_STATUS[status],
|
|
559
|
+
error: errors && errors[0],
|
|
560
|
+
isTimeout,
|
|
561
|
+
shouldCreateTestSpan,
|
|
562
|
+
projects
|
|
563
|
+
}
|
|
564
|
+
)
|
|
499
565
|
const testResult = test.results.at(-1)
|
|
500
566
|
const isAtrRetry = testResult?.retry > 0 &&
|
|
501
567
|
isFlakyTestRetriesEnabled &&
|
|
@@ -625,6 +691,9 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
|
|
|
625
691
|
|
|
626
692
|
let runAllTestsReturn = await runAllTests.apply(this, arguments)
|
|
627
693
|
|
|
694
|
+
// Tests that have only skipped tests may reach this point
|
|
695
|
+
// Skipped tests may or may not go through `testBegin` or `testEnd`
|
|
696
|
+
// depending on the playwright configuration
|
|
628
697
|
Object.values(remainingTestsByFile).forEach(tests => {
|
|
629
698
|
// `tests` should normally be empty, but if it isn't,
|
|
630
699
|
// there were tests that did not go through `testBegin` or `testEnd`,
|
|
@@ -632,7 +701,15 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
|
|
|
632
701
|
tests.forEach(test => {
|
|
633
702
|
const browser = getBrowserNameFromProjects(projects, test)
|
|
634
703
|
testBeginHandler(test, browser, true)
|
|
635
|
-
testEndHandler(
|
|
704
|
+
testEndHandler({
|
|
705
|
+
test,
|
|
706
|
+
annotations: [],
|
|
707
|
+
testStatus: 'skip',
|
|
708
|
+
error: null,
|
|
709
|
+
isTimeout: false,
|
|
710
|
+
shouldCreateTestSpan: true,
|
|
711
|
+
projects
|
|
712
|
+
})
|
|
636
713
|
})
|
|
637
714
|
})
|
|
638
715
|
|
|
@@ -1007,6 +1084,9 @@ addHook({
|
|
|
1007
1084
|
const stepInfoByStepId = {}
|
|
1008
1085
|
|
|
1009
1086
|
shimmer.wrap(workerPackage.WorkerMain.prototype, '_runTest', _runTest => async function (test) {
|
|
1087
|
+
if (test.expectedStatus === 'skipped') {
|
|
1088
|
+
return _runTest.apply(this, arguments)
|
|
1089
|
+
}
|
|
1010
1090
|
steps = []
|
|
1011
1091
|
|
|
1012
1092
|
const {
|
|
@@ -1060,6 +1140,8 @@ addHook({
|
|
|
1060
1140
|
})
|
|
1061
1141
|
|
|
1062
1142
|
if (isRumActive) {
|
|
1143
|
+
// Give some time RUM to flush data, similar to what we do in selenium
|
|
1144
|
+
await new Promise(resolve => setTimeout(resolve, RUM_FLUSH_WAIT_TIME))
|
|
1063
1145
|
const url = page.url()
|
|
1064
1146
|
if (url) {
|
|
1065
1147
|
const domain = new URL(url).hostname
|
|
@@ -1067,9 +1149,10 @@ addHook({
|
|
|
1067
1149
|
name: 'datadog-ci-visibility-test-execution-id',
|
|
1068
1150
|
value: '',
|
|
1069
1151
|
domain,
|
|
1070
|
-
expires: 0,
|
|
1071
1152
|
path: '/'
|
|
1072
1153
|
}])
|
|
1154
|
+
} else {
|
|
1155
|
+
log.error('RUM is active but page.url() is not available')
|
|
1073
1156
|
}
|
|
1074
1157
|
}
|
|
1075
1158
|
}
|
|
@@ -1176,3 +1259,46 @@ addHook({
|
|
|
1176
1259
|
|
|
1177
1260
|
return workerPackage
|
|
1178
1261
|
})
|
|
1262
|
+
|
|
1263
|
+
function generateSummaryWrapper (generateSummary) {
|
|
1264
|
+
return function () {
|
|
1265
|
+
for (const test of this.suite.allTests()) {
|
|
1266
|
+
// https://github.com/microsoft/playwright/blob/bf92ffecff6f30a292b53430dbaee0207e0c61ad/packages/playwright/src/reporters/base.ts#L279
|
|
1267
|
+
const didNotRun = test.outcome() === 'skipped' &&
|
|
1268
|
+
(!test.results.length || test.expectedStatus !== 'skipped')
|
|
1269
|
+
if (didNotRun) {
|
|
1270
|
+
const {
|
|
1271
|
+
_requireFile: testSuiteAbsolutePath,
|
|
1272
|
+
location: { line: testSourceLine },
|
|
1273
|
+
} = test
|
|
1274
|
+
const browserName = getBrowserNameFromProjects(sessionProjects, test)
|
|
1275
|
+
|
|
1276
|
+
testSkipCh.publish({
|
|
1277
|
+
testName: getTestFullname(test),
|
|
1278
|
+
testSuiteAbsolutePath,
|
|
1279
|
+
testSourceLine,
|
|
1280
|
+
browserName,
|
|
1281
|
+
})
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
return generateSummary.apply(this, arguments)
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
// If a playwright project B has a dependency on project A,
|
|
1289
|
+
// and project A fails, the tests in project B will not run.
|
|
1290
|
+
// This hook is used to report tests that did not run as skipped.
|
|
1291
|
+
// Note: this is different from tests skipped via test.skip() or test.fixme()
|
|
1292
|
+
addHook({
|
|
1293
|
+
name: 'playwright',
|
|
1294
|
+
file: 'lib/reporters/base.js',
|
|
1295
|
+
versions: ['>=1.38.0']
|
|
1296
|
+
}, (reportersPackage) => {
|
|
1297
|
+
// v1.50.0 changed the name of the base reporter from BaseReporter to TerminalReporter
|
|
1298
|
+
if (reportersPackage.TerminalReporter) {
|
|
1299
|
+
shimmer.wrap(reportersPackage.TerminalReporter.prototype, 'generateSummary', generateSummaryWrapper)
|
|
1300
|
+
} else if (reportersPackage.BaseReporter) {
|
|
1301
|
+
shimmer.wrap(reportersPackage.BaseReporter.prototype, 'generateSummary', generateSummaryWrapper)
|
|
1302
|
+
}
|
|
1303
|
+
return reportersPackage
|
|
1304
|
+
})
|