dd-trace 6.17.0 → 6.18.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/index.d.ts +52 -9
- package/package.json +1 -1
- package/packages/datadog-instrumentations/src/cucumber.js +10 -5
- package/packages/datadog-instrumentations/src/cypress-config.js +27 -15
- package/packages/datadog-instrumentations/src/helpers/bundler-register.js +6 -0
- package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/hooks.js +0 -6
- package/packages/datadog-instrumentations/src/helpers/register.js +20 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +41 -4
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +15 -13
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +43 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +13 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +28 -3
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +4 -0
- package/packages/datadog-instrumentations/src/jest.js +27 -3
- package/packages/datadog-instrumentations/src/mocha/main.js +7 -5
- package/packages/datadog-instrumentations/src/mocha/utils.js +8 -8
- package/packages/datadog-instrumentations/src/playwright-reporter.js +3 -4
- package/packages/datadog-instrumentations/src/playwright.js +47 -6
- package/packages/datadog-instrumentations/src/undici.js +7 -3
- package/packages/datadog-instrumentations/src/vitest-main.js +91 -20
- package/packages/datadog-instrumentations/src/webdriverio.js +28 -12
- package/packages/datadog-instrumentations/src/ws.js +41 -1
- package/packages/datadog-plugin-cucumber/src/index.js +3 -4
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +44 -14
- package/packages/datadog-plugin-cypress/src/index.js +3 -1
- package/packages/datadog-plugin-jest/src/index.js +3 -4
- package/packages/datadog-plugin-mocha/src/index.js +3 -4
- package/packages/datadog-plugin-playwright/src/index.js +11 -6
- package/packages/datadog-plugin-vitest/src/index.js +3 -4
- package/packages/datadog-plugin-ws/src/producer.js +1 -1
- package/packages/datadog-shimmer/src/shimmer.js +14 -10
- package/packages/datadog-turbopack/index.js +4 -1
- package/packages/datadog-turbopack/src/loader.js +6 -2
- package/packages/dd-trace/src/agent/info.js +5 -4
- package/packages/dd-trace/src/config/generated-config-types.d.ts +15 -15
- package/packages/dd-trace/src/config/index.js +7 -6
- package/packages/dd-trace/src/config/supported-configurations.json +11 -0
- package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -1
- package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +5 -3
- package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
- package/packages/dd-trace/src/debugger/index.js +1 -1
- package/packages/dd-trace/src/evp_proxy/constants.js +2 -0
- package/packages/dd-trace/src/evp_proxy/direct.js +17 -13
- package/packages/dd-trace/src/evp_proxy/discovery.js +14 -10
- package/packages/dd-trace/src/evp_proxy/path.js +21 -7
- package/packages/dd-trace/src/exporters/common/url.js +33 -2
- package/packages/dd-trace/src/llmobs/prompts/manager.js +10 -2
- package/packages/dd-trace/src/llmobs/prompts/prompt.js +77 -11
- package/packages/dd-trace/src/llmobs/tagger.js +9 -4
- package/packages/dd-trace/src/openfeature/configuration_source.js +4 -1
- package/packages/dd-trace/src/openfeature/flagging_provider.js +3 -3
- package/packages/dd-trace/src/openfeature/index.js +6 -2
- package/packages/dd-trace/src/openfeature/writers/base.js +90 -17
- package/packages/dd-trace/src/openfeature/writers/exposures.js +4 -0
- package/packages/dd-trace/src/openfeature/writers/util.js +104 -33
- package/packages/dd-trace/src/opentracing/tracer.js +3 -3
- package/packages/dd-trace/src/plugin_manager.js +27 -8
- package/packages/dd-trace/src/plugins/ci_plugin.js +1 -1
- package/packages/dd-trace/src/plugins/util/test.js +9 -4
- package/packages/dd-trace/src/proxy.js +2 -2
- package/packages/dd-trace/src/telemetry/send-data.js +2 -1
- package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
- package/packages/dd-trace/src/tracer.js +5 -4
- package/packages/datadog-instrumentations/src/azure-cosmos.js +0 -7
- package/packages/datadog-instrumentations/src/bullmq.js +0 -11
- package/packages/datadog-instrumentations/src/langchain.js +0 -7
- package/packages/datadog-instrumentations/src/langgraph.js +0 -7
- package/packages/datadog-instrumentations/src/mercurius.js +0 -11
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
//
|
|
3
|
+
// targets.json is generated by scripts/generate-rewriter-targets.js from ./instrumentation-registry.
|
|
4
4
|
const targets = require('./targets.json')
|
|
5
|
+
const rewriteTargetNames = new Set(Object.values(targets))
|
|
5
6
|
|
|
6
7
|
const NODE_MODULES = '/node_modules/'
|
|
7
8
|
|
|
@@ -9,6 +10,7 @@ const NODE_MODULES = '/node_modules/'
|
|
|
9
10
|
* @typedef {object} RewriteTarget
|
|
10
11
|
* @property {string} moduleName
|
|
11
12
|
* @property {string} filePath
|
|
13
|
+
* @property {boolean} [activate]
|
|
12
14
|
*/
|
|
13
15
|
|
|
14
16
|
/**
|
|
@@ -20,13 +22,36 @@ function getRewriteTarget (filename) {
|
|
|
20
22
|
if (nodeModulesIndex === -1) return
|
|
21
23
|
|
|
22
24
|
const modulePath = filename.slice(nodeModulesIndex + NODE_MODULES.length)
|
|
25
|
+
if (!Object.hasOwn(targets, modulePath)) return
|
|
23
26
|
const moduleName = targets[modulePath]
|
|
24
27
|
if (typeof moduleName !== 'string') return
|
|
28
|
+
const activate = isRewriteActivationEnabled(moduleName)
|
|
25
29
|
|
|
26
|
-
|
|
30
|
+
const rewriteTarget = {
|
|
27
31
|
moduleName,
|
|
28
32
|
filePath: modulePath.slice(moduleName.length + 1),
|
|
29
33
|
}
|
|
34
|
+
if (activate) rewriteTarget.activate = true
|
|
35
|
+
return rewriteTarget
|
|
30
36
|
}
|
|
31
37
|
|
|
32
|
-
|
|
38
|
+
function getRewriteTargetNames () {
|
|
39
|
+
return rewriteTargetNames.values()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {string} name
|
|
44
|
+
*/
|
|
45
|
+
function isRewriteTargetName (name) {
|
|
46
|
+
return rewriteTargetNames.has(name)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {string} moduleName
|
|
51
|
+
*/
|
|
52
|
+
function isRewriteActivationEnabled (moduleName) {
|
|
53
|
+
// Keep target-name-only consumers from loading every instrumentation descriptor.
|
|
54
|
+
return require('./instrumentation-registry').isRewriteActivationEnabled(moduleName)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = { getRewriteTarget, getRewriteTargetNames, isRewriteActivationEnabled, isRewriteTargetName }
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"@modelcontextprotocol/sdk/dist/esm/server/mcp.js": "@modelcontextprotocol/sdk",
|
|
31
31
|
"@openai/agents-openai/dist/openaiChatCompletionsModel.js": "@openai/agents-openai",
|
|
32
32
|
"@openai/agents-openai/dist/openaiChatCompletionsModel.mjs": "@openai/agents-openai",
|
|
33
|
+
"@playwright/test/lib/reporters/multiplexer.js": "@playwright/test",
|
|
33
34
|
"@supabase/auth-js/dist/main/GoTrueClient.js": "@supabase/auth-js",
|
|
34
35
|
"@supabase/auth-js/dist/module/GoTrueClient.js": "@supabase/auth-js",
|
|
35
36
|
"@supabase/functions-js/dist/main/FunctionsClient.js": "@supabase/functions-js",
|
|
@@ -41,6 +42,7 @@
|
|
|
41
42
|
"@supabase/storage-js/dist/index.cjs": "@supabase/storage-js",
|
|
42
43
|
"@supabase/storage-js/dist/index.mjs": "@supabase/storage-js",
|
|
43
44
|
"@wdio/cli/build/index.js": "@wdio/cli",
|
|
45
|
+
"@wdio/config/build/node/index.js": "@wdio/config",
|
|
44
46
|
"@wdio/jasmine-framework/build/index.js": "@wdio/jasmine-framework",
|
|
45
47
|
"@wdio/local-runner/build/index.js": "@wdio/local-runner",
|
|
46
48
|
"@wdio/runner/build/index.js": "@wdio/runner",
|
|
@@ -77,6 +79,8 @@
|
|
|
77
79
|
"playwright-core/lib/coreBundle.js": "playwright-core",
|
|
78
80
|
"playwright/lib/index.js": "playwright",
|
|
79
81
|
"playwright/lib/runner/index.js": "playwright",
|
|
82
|
+
"playwright/lib/runner/lastRun.js": "playwright",
|
|
83
|
+
"playwright/lib/runner/runner.js": "playwright",
|
|
80
84
|
"postgres/cjs/src/connection.js": "postgres",
|
|
81
85
|
"postgres/cjs/src/index.js": "postgres",
|
|
82
86
|
"postgres/cjs/src/query.js": "postgres",
|
|
@@ -109,6 +109,10 @@ const CHILD_MESSAGE_CALL = 1
|
|
|
109
109
|
const FLUSH_TIMEOUT = FINAL_FLUSH_TIMEOUT + FINAL_FLUSH_FALLBACK_DELAY + 5000
|
|
110
110
|
const JEST_SESSION_STATE = Symbol.for('dd-trace:jest:session')
|
|
111
111
|
const JEST_BAIL_REPORTER_PATH = require.resolve('./jest/bail-reporter')
|
|
112
|
+
const JEST_CIRCUS_TEST_RUNNER_PATH = /(?:^|[\\/])jest-circus(?:[\\/]|$)/
|
|
113
|
+
const UNSUPPORTED_JEST_TEST_RUNNER_WARNING =
|
|
114
|
+
'dd-trace Test Optimization supports jest-circus; another test runner was detected; ' +
|
|
115
|
+
'suite and test events may be incomplete.'
|
|
112
116
|
const DD_JEST_HANDLE_TEST_EVENT_WRAPPED = Symbol('dd-trace:jest:handle-test-event-wrapped')
|
|
113
117
|
const DD_JEST_HANDLE_TEST_EVENT_DATADOG = Symbol('dd-trace:jest:handle-test-event-datadog')
|
|
114
118
|
const DD_JEST_CONCURRENT_TEST_ORIGINAL = Symbol('dd-trace:jest:concurrent-test-original')
|
|
@@ -248,6 +252,7 @@ const MINIMUM_JEST_TEST_SCHEDULER_VERSION = DD_MAJOR >= 6 ? '>=28.0.0' : '>=27.0
|
|
|
248
252
|
const MINIMUM_JEST_COVERAGE_BACKFILL_VERSION = '>=28.0.0'
|
|
249
253
|
const atrSuppressedErrors = new Map()
|
|
250
254
|
let hasWarnedDeprecatedJestVersion = false
|
|
255
|
+
let hasWarnedUnsupportedJestTestRunner = false
|
|
251
256
|
let isJestCoverageBackfillSupported = false
|
|
252
257
|
let hasFinishedTestSession = false
|
|
253
258
|
let jestEachBind
|
|
@@ -322,6 +327,20 @@ function warnDeprecatedJestVersion (frameworkVersion) {
|
|
|
322
327
|
)
|
|
323
328
|
}
|
|
324
329
|
|
|
330
|
+
/** @param {object[]} configs resolved Jest project configurations */
|
|
331
|
+
function warnIfUnsupportedJestTestRunner (configs) {
|
|
332
|
+
if (hasWarnedUnsupportedJestTestRunner || !testSessionConfigurationCh.hasSubscribers) return
|
|
333
|
+
|
|
334
|
+
const usesUnsupportedTestRunner = getEnvironmentVariable('JEST_JASMINE') === '1' ||
|
|
335
|
+
configs.some(config => !JEST_CIRCUS_TEST_RUNNER_PATH.test(config.testRunner))
|
|
336
|
+
|
|
337
|
+
if (usesUnsupportedTestRunner) {
|
|
338
|
+
hasWarnedUnsupportedJestTestRunner = true
|
|
339
|
+
// eslint-disable-next-line no-console
|
|
340
|
+
console.warn(UNSUPPORTED_JEST_TEST_RUNNER_WARNING)
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
325
344
|
function getTestEnvironmentOptions (config) {
|
|
326
345
|
if (config.projectConfig && config.projectConfig.testEnvironmentOptions) { // newer versions
|
|
327
346
|
return config.projectConfig.testEnvironmentOptions
|
|
@@ -3153,6 +3172,7 @@ function getCliWrapper (isNewJestVersion) {
|
|
|
3153
3172
|
results: {
|
|
3154
3173
|
numFailedTestSuites,
|
|
3155
3174
|
numFailedTests,
|
|
3175
|
+
numPassedTests,
|
|
3156
3176
|
numRuntimeErrorTestSuites = 0,
|
|
3157
3177
|
numTotalTests,
|
|
3158
3178
|
numTotalTestSuites,
|
|
@@ -3323,16 +3343,19 @@ function getCliWrapper (isNewJestVersion) {
|
|
|
3323
3343
|
|
|
3324
3344
|
// Determine session status after EFD and quarantine checks have potentially modified success
|
|
3325
3345
|
let status, error
|
|
3326
|
-
const
|
|
3346
|
+
const hasExecutedTests = numPassedTests > 0 || numFailedTests > 0 || numSuppressedQuarantinedTests > 0
|
|
3347
|
+
const testSessionEmptyReason = result.results.success && !hasExecutedTests
|
|
3348
|
+
? (numTotalTests > 0 || isSuitesSkipped ? 'all_tests_skipped' : 'zero_tests')
|
|
3349
|
+
: undefined
|
|
3327
3350
|
if (result.results.success) {
|
|
3328
|
-
status =
|
|
3351
|
+
status = testSessionEmptyReason ? 'skip' : 'pass'
|
|
3329
3352
|
} else {
|
|
3330
3353
|
status = 'fail'
|
|
3331
3354
|
error = new Error(`Failed test suites: ${numFailedTestSuites}. Failed tests: ${numFailedTests}`)
|
|
3332
3355
|
}
|
|
3333
3356
|
|
|
3334
3357
|
await waitForTestSessionFinish(getTestSessionFinishPayload(status, error, {
|
|
3335
|
-
|
|
3358
|
+
testSessionEmptyReason,
|
|
3336
3359
|
...getTestSessionCoveragePayload(result.results, result.globalConfig?.rootDir),
|
|
3337
3360
|
}))
|
|
3338
3361
|
|
|
@@ -3625,6 +3648,7 @@ addHook({
|
|
|
3625
3648
|
}, jestAdapterWrapper)
|
|
3626
3649
|
|
|
3627
3650
|
function configureTestEnvironment (readConfigsResult) {
|
|
3651
|
+
warnIfUnsupportedJestTestRunner(readConfigsResult.configs)
|
|
3628
3652
|
repositoryRoot = getJestRepositoryRoot(readConfigsResult)
|
|
3629
3653
|
isUserCodeCoverageEnabled = !!readConfigsResult.globalConfig.collectCoverage
|
|
3630
3654
|
const isCodeCoverageEnabledBecauseOfUs = shouldCollectJestCoverageForTia() && !isUserCodeCoverageEnabled
|
|
@@ -316,8 +316,8 @@ function getOnEndHandler (isParallel, onDone) {
|
|
|
316
316
|
let status = 'pass'
|
|
317
317
|
let error = frameworkError
|
|
318
318
|
if (this.stats) {
|
|
319
|
-
status = this.stats.failures === 0 ? 'pass' : 'fail'
|
|
320
|
-
if (this.stats.tests ===
|
|
319
|
+
status = this.stats.failures === 0 && this.failures === 0 ? 'pass' : 'fail'
|
|
320
|
+
if (this.stats.tests === this.stats.pending && status === 'pass') {
|
|
321
321
|
status = 'skip'
|
|
322
322
|
}
|
|
323
323
|
} else if (this.failures !== 0) {
|
|
@@ -329,7 +329,9 @@ function getOnEndHandler (isParallel, onDone) {
|
|
|
329
329
|
} else if (status === 'fail') {
|
|
330
330
|
error = new Error(`Failed tests: ${this.failures}.`)
|
|
331
331
|
}
|
|
332
|
-
const
|
|
332
|
+
const testSessionEmptyReason = status === 'skip'
|
|
333
|
+
? (this.stats.pending > 0 || skippedSuites.length > 0 ? 'all_tests_skipped' : 'zero_tests')
|
|
334
|
+
: undefined
|
|
333
335
|
|
|
334
336
|
testFileToSuiteCtx.clear()
|
|
335
337
|
|
|
@@ -373,7 +375,7 @@ function getOnEndHandler (isParallel, onDone) {
|
|
|
373
375
|
isTestManagementEnabled: config.isTestManagementTestsEnabled,
|
|
374
376
|
isParallel,
|
|
375
377
|
isFrameworkError: arguments.length > 0,
|
|
376
|
-
|
|
378
|
+
testSessionEmptyReason,
|
|
377
379
|
}, () => {
|
|
378
380
|
try {
|
|
379
381
|
onDone()
|
|
@@ -1022,7 +1024,7 @@ function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFini
|
|
|
1022
1024
|
config.isSuitesSkippingEnabled = config.isItrEnabled && libraryConfig.isSuitesSkippingEnabled
|
|
1023
1025
|
config.isFlakyTestRetriesEnabled = libraryConfig.isFlakyTestRetriesEnabled
|
|
1024
1026
|
config.flakyTestRetriesCount = libraryConfig.flakyTestRetriesCount
|
|
1025
|
-
config.isDynamicAtrEnabled = libraryConfig.isDynamicAtrEnabled
|
|
1027
|
+
config.isDynamicAtrEnabled = libraryConfig.isDynamicAtrEnabled && satisfies(frameworkVersion, '>=8.0.0')
|
|
1026
1028
|
config.dynamicAtrBuckets = libraryConfig.dynamicAtrBuckets
|
|
1027
1029
|
config.isDiEnabled = libraryConfig.isDiEnabled
|
|
1028
1030
|
config.isTestDynamicInstrumentationEnabled = isTestDynamicInstrumentationEnabled
|
|
@@ -54,7 +54,7 @@ const testsQuarantined = new Set()
|
|
|
54
54
|
const testsStatuses = new Map()
|
|
55
55
|
const efdRetryCountByTestFullName = new Map()
|
|
56
56
|
const efdSlowAbortedTests = new Set()
|
|
57
|
-
|
|
57
|
+
let dynamicAtrRetryCountByTest = new WeakMap()
|
|
58
58
|
const attemptToFixExecutions = new Map()
|
|
59
59
|
const isMochaWorker = !!getEnvironmentVariable('MOCHA_WORKER_ID')
|
|
60
60
|
|
|
@@ -343,7 +343,7 @@ function resetRunState (rootSuite) {
|
|
|
343
343
|
testsStatuses.clear()
|
|
344
344
|
efdRetryCountByTestFullName.clear()
|
|
345
345
|
efdSlowAbortedTests.clear()
|
|
346
|
-
|
|
346
|
+
dynamicAtrRetryCountByTest = new WeakMap()
|
|
347
347
|
attemptToFixExecutions.clear()
|
|
348
348
|
loggedAttemptToFixTests.clear()
|
|
349
349
|
|
|
@@ -538,8 +538,7 @@ function runnableWrapper (RunnablePackage, libraryConfig) {
|
|
|
538
538
|
if (libraryConfig.isDynamicAtrEnabled) {
|
|
539
539
|
// Dynamic ATR: set the max possible retries initially.
|
|
540
540
|
// The actual duration-based count is computed after the first attempt.
|
|
541
|
-
const
|
|
542
|
-
const dynamicCount = dynamicAtrRetryCountByTestFullName.get(testName)
|
|
541
|
+
const dynamicCount = dynamicAtrRetryCountByTest.get(test._retriedTest || test)
|
|
543
542
|
if (dynamicCount === undefined) {
|
|
544
543
|
const maxRetries = libraryConfig.dynamicAtrBuckets
|
|
545
544
|
? Math.max(...libraryConfig.dynamicAtrBuckets)
|
|
@@ -723,12 +722,14 @@ function getTestFinishInfo (test, status, config, error) {
|
|
|
723
722
|
}
|
|
724
723
|
|
|
725
724
|
// Dynamic ATR: after the first attempt, compute the duration-based retry budget.
|
|
725
|
+
// Mocha's native retry clones point to the original test, independently of duplicate titles.
|
|
726
|
+
const originalTest = test._retriedTest || test
|
|
726
727
|
if (
|
|
727
728
|
config.isDynamicAtrEnabled &&
|
|
728
729
|
config.isFlakyTestRetriesEnabled &&
|
|
729
730
|
!test._ddIsAttemptToFix &&
|
|
730
731
|
!test._ddIsEfdRetry &&
|
|
731
|
-
!
|
|
732
|
+
!dynamicAtrRetryCountByTest.has(originalTest) &&
|
|
732
733
|
test._currentRetry === 0
|
|
733
734
|
) {
|
|
734
735
|
const duration = test.duration > 0 ? test.duration : performance.now() - test._ddStartTime
|
|
@@ -737,7 +738,7 @@ function getTestFinishInfo (test, status, config, error) {
|
|
|
737
738
|
config.earlyFlakeDetectionRetryPolicy,
|
|
738
739
|
config.dynamicAtrBuckets
|
|
739
740
|
)
|
|
740
|
-
|
|
741
|
+
dynamicAtrRetryCountByTest.set(originalTest, dynamicCount)
|
|
741
742
|
}
|
|
742
743
|
|
|
743
744
|
if (testsStatuses.get(testName)) {
|
|
@@ -1094,13 +1095,12 @@ function getOnTestRetryHandler (config) {
|
|
|
1094
1095
|
!test._ddIsAttemptToFix &&
|
|
1095
1096
|
!isEarlyFlakeDetectionTest(test, config)
|
|
1096
1097
|
if (isDynamicAtrTest && isFirstAttempt) {
|
|
1097
|
-
const testName = getTestFullName(test)
|
|
1098
1098
|
const dynamicCount = getDynamicAtrRetryCount(
|
|
1099
1099
|
test.duration > 0 ? test.duration : performance.now() - test._ddStartTime,
|
|
1100
1100
|
config.earlyFlakeDetectionRetryPolicy,
|
|
1101
1101
|
config.dynamicAtrBuckets
|
|
1102
1102
|
)
|
|
1103
|
-
|
|
1103
|
+
dynamicAtrRetryCountByTest.set(test._retriedTest || test, dynamicCount)
|
|
1104
1104
|
// Mocha emits retry before its next attempt starts; narrow its ceiling here.
|
|
1105
1105
|
test._retries = dynamicCount
|
|
1106
1106
|
}
|
|
@@ -206,16 +206,15 @@ class DatadogPlaywrightReporter {
|
|
|
206
206
|
onExit () {}
|
|
207
207
|
|
|
208
208
|
/**
|
|
209
|
-
* Reports errors
|
|
209
|
+
* Reports global run errors as well as errors from later reporters during finalization.
|
|
210
210
|
*
|
|
211
211
|
* @param {unknown} error
|
|
212
212
|
*/
|
|
213
213
|
onError (error) {
|
|
214
|
-
if (this.isFinalizing) {
|
|
215
|
-
reporterErrorCh.publish(error)
|
|
216
|
-
} else {
|
|
214
|
+
if (!this.isFinalizing) {
|
|
217
215
|
this.fatalErrorCount += 1
|
|
218
216
|
}
|
|
217
|
+
reporterErrorCh.publish(error)
|
|
219
218
|
}
|
|
220
219
|
|
|
221
220
|
/**
|
|
@@ -69,6 +69,7 @@ const dispatcherCreateWorkerCh = tracingChannel('orchestrion:playwright:Dispatch
|
|
|
69
69
|
const filterForShardCh = tracingChannel('orchestrion:playwright:filterForShard')
|
|
70
70
|
const processHostStartRunnerCh = tracingChannel('orchestrion:playwright:ProcessHost_startRunner')
|
|
71
71
|
const createRootSuiteCh = tracingChannel('orchestrion:playwright:createRootSuite')
|
|
72
|
+
const lastRunTestIdCh = tracingChannel('orchestrion:playwright:lastRunTestId')
|
|
72
73
|
const artifactsRecorderScreenshotPathCh =
|
|
73
74
|
tracingChannel('orchestrion:playwright:ArtifactsRecorder_createScreenshotAttachmentPath')
|
|
74
75
|
const snapshotRecorderScreenshotPathCh = tracingChannel('orchestrion:playwright:SnapshotRecorder_createAttachmentPath')
|
|
@@ -76,6 +77,7 @@ const saveAutomaticVideoCh = tracingChannel('orchestrion:playwright:saveAutomati
|
|
|
76
77
|
const pageGotoCh = tracingChannel('orchestrion:playwright-core:Page_goto')
|
|
77
78
|
|
|
78
79
|
const testToCtx = new WeakMap()
|
|
80
|
+
const originalTestIdsByRetry = new WeakMap()
|
|
79
81
|
const testSuiteToCtx = new Map()
|
|
80
82
|
const testSuiteToTestStatuses = new Map()
|
|
81
83
|
const testSuiteToErrors = new Map()
|
|
@@ -475,6 +477,7 @@ function deepCloneSuite (suite, filterTest, tags = [], configureCopiedTest) {
|
|
|
475
477
|
} else {
|
|
476
478
|
if (filterTest(entry)) {
|
|
477
479
|
const copiedTest = entry._clone()
|
|
480
|
+
originalTestIdsByRetry.set(copiedTest, originalTestIdsByRetry.get(entry) ?? entry.id)
|
|
478
481
|
if (configureCopiedTest) {
|
|
479
482
|
configureCopiedTest(copiedTest, entry)
|
|
480
483
|
}
|
|
@@ -915,6 +918,7 @@ function testEndHandler ({
|
|
|
915
918
|
testDuration,
|
|
916
919
|
testResultStatus,
|
|
917
920
|
expectedStatus = test.expectedStatus,
|
|
921
|
+
hasNonRetriableError,
|
|
918
922
|
}) {
|
|
919
923
|
const {
|
|
920
924
|
_requireFile: testSuiteAbsolutePath,
|
|
@@ -1087,6 +1091,8 @@ function testEndHandler ({
|
|
|
1087
1091
|
if (testCtx) {
|
|
1088
1092
|
testFinishCh.publish({
|
|
1089
1093
|
testStatus,
|
|
1094
|
+
isExpectedFailure: testResultStatus === 'failed' && expectedStatus === 'failed',
|
|
1095
|
+
hasNonRetriableError,
|
|
1090
1096
|
steps: testResult?.steps || [],
|
|
1091
1097
|
isRetry: testResult?.retry > 0,
|
|
1092
1098
|
error,
|
|
@@ -1278,7 +1284,7 @@ function onDispatcherCreateWorker (dispatcher, worker) {
|
|
|
1278
1284
|
videos.push(attachment)
|
|
1279
1285
|
}
|
|
1280
1286
|
})
|
|
1281
|
-
worker.on('testEnd', ({ testId, status, errors, annotations, duration, expectedStatus }) => {
|
|
1287
|
+
worker.on('testEnd', ({ testId, status, errors, annotations, duration, expectedStatus, hasNonRetriableError }) => {
|
|
1282
1288
|
const test = getTestByTestId(dispatcher, testId)
|
|
1283
1289
|
if (!test) return
|
|
1284
1290
|
|
|
@@ -1300,6 +1306,7 @@ function onDispatcherCreateWorker (dispatcher, worker) {
|
|
|
1300
1306
|
testDuration: duration,
|
|
1301
1307
|
testResultStatus: status,
|
|
1302
1308
|
expectedStatus,
|
|
1309
|
+
hasNonRetriableError,
|
|
1303
1310
|
}
|
|
1304
1311
|
)
|
|
1305
1312
|
const testResult = test.results.at(-1)
|
|
@@ -1406,6 +1413,8 @@ function dispatcherHook (dispatcherExport) {
|
|
|
1406
1413
|
annotations: params.annotations,
|
|
1407
1414
|
testStatus: STATUS_TO_TEST_STATUS[testResult.status],
|
|
1408
1415
|
testResultStatus: testResult.status,
|
|
1416
|
+
expectedStatus: params.expectedStatus,
|
|
1417
|
+
hasNonRetriableError: params.hasNonRetriableError,
|
|
1409
1418
|
error: testResult.error,
|
|
1410
1419
|
isTimeout,
|
|
1411
1420
|
shouldCreateTestSpan: true,
|
|
@@ -1431,7 +1440,7 @@ function dispatcherHookNew (dispatcherExport, runWrapper) {
|
|
|
1431
1440
|
|
|
1432
1441
|
function runAllTestsWrapper (runAllTests, playwrightVersion) {
|
|
1433
1442
|
// Config parameter is only available from >=1.55.0
|
|
1434
|
-
return async function (config) {
|
|
1443
|
+
return async function (config, options) {
|
|
1435
1444
|
// A later run must not inherit ATR settings when configuration fails or the plugin is disabled.
|
|
1436
1445
|
isFlakyTestRetriesEnabled = false
|
|
1437
1446
|
flakyTestRetriesCount = 0
|
|
@@ -1682,14 +1691,30 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
|
|
|
1682
1691
|
const finalStatus = hasReporterError
|
|
1683
1692
|
? 'fail'
|
|
1684
1693
|
: (preventedToFail ? 'pass' : STATUS_TO_TEST_STATUS[sessionStatus])
|
|
1685
|
-
const
|
|
1686
|
-
Boolean(
|
|
1694
|
+
const isTestDiscovery = finalStatus === 'pass' &&
|
|
1695
|
+
Boolean(
|
|
1696
|
+
config?.listOnly ||
|
|
1697
|
+
config?.cliListOnly ||
|
|
1698
|
+
runnerConfig.cliListOnly ||
|
|
1699
|
+
runnerConfig._internal?.listOnly ||
|
|
1700
|
+
options?.listMode
|
|
1701
|
+
)
|
|
1702
|
+
const isEmptyShard = Boolean(playwrightConfig.shard) &&
|
|
1687
1703
|
hasTestsBeforeSharding &&
|
|
1688
1704
|
!hasTestsAssignedToShard &&
|
|
1689
1705
|
testsReportedInGenerateSummary.size === 0
|
|
1706
|
+
const hasExecutedTests = [...testsToTestStatuses.values()].some(statuses =>
|
|
1707
|
+
statuses.some(status => status !== 'skip')
|
|
1708
|
+
)
|
|
1709
|
+
const hasSkippedTests = testsToTestStatuses.size > 0 || testsReportedInGenerateSummary.size > 0
|
|
1710
|
+
const testSessionEmptyReason = finalStatus === 'pass' && !hasExecutedTests
|
|
1711
|
+
? (isTestDiscovery
|
|
1712
|
+
? 'test_discovery'
|
|
1713
|
+
: isEmptyShard ? 'zero_test_shard' : hasSkippedTests ? 'all_tests_skipped' : 'zero_tests')
|
|
1714
|
+
: undefined
|
|
1690
1715
|
await getChannelPromise(testSessionFinishCh, {
|
|
1691
|
-
status:
|
|
1692
|
-
|
|
1716
|
+
status: testSessionEmptyReason ? 'skip' : finalStatus,
|
|
1717
|
+
testSessionEmptyReason,
|
|
1693
1718
|
error: finalizationError,
|
|
1694
1719
|
isEarlyFlakeDetectionEnabled,
|
|
1695
1720
|
isEarlyFlakeDetectionFaulty,
|
|
@@ -1898,6 +1923,14 @@ pageGotoCh.subscribe({
|
|
|
1898
1923
|
},
|
|
1899
1924
|
})
|
|
1900
1925
|
|
|
1926
|
+
// Retry clones exist only after discovery, so persist their original IDs for --last-failed.
|
|
1927
|
+
lastRunTestIdCh.subscribe({
|
|
1928
|
+
end (ctx) {
|
|
1929
|
+
const originalId = originalTestIdsByRetry.get(ctx.arguments[0])
|
|
1930
|
+
if (originalId !== undefined) ctx.result = originalId
|
|
1931
|
+
},
|
|
1932
|
+
})
|
|
1933
|
+
|
|
1901
1934
|
reporterErrorCh.subscribe((error) => {
|
|
1902
1935
|
recordReporterError(error)
|
|
1903
1936
|
})
|
|
@@ -1968,6 +2001,12 @@ snapshotRecorderScreenshotPathCh.subscribe({ end: recordAutomaticFailureScreensh
|
|
|
1968
2001
|
saveAutomaticVideoCh.subscribe({ start: recordAutomaticFailureVideoPath })
|
|
1969
2002
|
|
|
1970
2003
|
if (DD_MAJOR < 6) { // <1.38.0 is only supported up to version 5
|
|
2004
|
+
tracingChannel('orchestrion:@playwright/test:Multiplexer_onError').subscribe({
|
|
2005
|
+
start (ctx) {
|
|
2006
|
+
recordReporterError(ctx.arguments[0])
|
|
2007
|
+
},
|
|
2008
|
+
})
|
|
2009
|
+
|
|
1971
2010
|
addHook({
|
|
1972
2011
|
name: '@playwright/test',
|
|
1973
2012
|
file: 'lib/runner.js',
|
|
@@ -2656,6 +2695,8 @@ function instrumentWorkerMainMethods (workerMain) {
|
|
|
2656
2695
|
|
|
2657
2696
|
await getChannelPromise(testFinishCh, {
|
|
2658
2697
|
testStatus: STATUS_TO_TEST_STATUS[status],
|
|
2698
|
+
isExpectedFailure: status === 'failed' && testInfo.expectedStatus === 'failed',
|
|
2699
|
+
hasNonRetriableError: testInfo._hasNonRetriableError,
|
|
2659
2700
|
retryTestId,
|
|
2660
2701
|
deferFinalStatus: test._ddDeferFinalStatus,
|
|
2661
2702
|
steps: steps.filter(step => step.testId === testId),
|
|
@@ -45,9 +45,13 @@ function wrapRequestUpgrade (Request) {
|
|
|
45
45
|
const method = Request.prototype[methodName]
|
|
46
46
|
|
|
47
47
|
// Orchestrion cannot leave fixed source byte-for-byte untouched after a conditional AST match.
|
|
48
|
-
if (typeof method !== 'function' ||
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
if (typeof method !== 'function' || !requestSource.includes('channels.trailers.publish')) {
|
|
49
|
+
return Request
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const methodSource = Function.prototype.toString.call(method)
|
|
53
|
+
// Undici 6.29 delegates native upgrade completion to a private helper.
|
|
54
|
+
if (methodSource.includes('channels.trailers.publish') || methodSource.includes('#publishUpgradeTrailers')) {
|
|
51
55
|
return Request
|
|
52
56
|
}
|
|
53
57
|
|
|
@@ -65,6 +65,9 @@ const workerProcesses = new WeakSet()
|
|
|
65
65
|
const mainProcessSetupStates = new WeakMap()
|
|
66
66
|
const coverageWrappedProviders = new WeakSet()
|
|
67
67
|
const finishWrappedContexts = new WeakSet()
|
|
68
|
+
const finishedSessionContexts = new WeakSet()
|
|
69
|
+
const sessionWrappedPrototypes = new WeakSet()
|
|
70
|
+
const emptyShardContexts = new WeakSet()
|
|
68
71
|
const runFilesWrappedPrototypes = new WeakSet()
|
|
69
72
|
const activeRunFilesContexts = new WeakSet()
|
|
70
73
|
const runErrorsByContext = new WeakMap()
|
|
@@ -163,16 +166,6 @@ function getThreadsPoolWorkerExport (vitestPackage) {
|
|
|
163
166
|
return findExportByName(vitestPackage, 'ThreadsPoolWorker')
|
|
164
167
|
}
|
|
165
168
|
|
|
166
|
-
function getSessionStatus (state) {
|
|
167
|
-
if (state.getCountOfFailedTests() > 0) {
|
|
168
|
-
return 'fail'
|
|
169
|
-
}
|
|
170
|
-
if (state.pathsSet.size === 0) {
|
|
171
|
-
return 'skip'
|
|
172
|
-
}
|
|
173
|
-
return 'pass'
|
|
174
|
-
}
|
|
175
|
-
|
|
176
169
|
function getTestFilepathsFromSpecifications (testSpecifications) {
|
|
177
170
|
if (!Array.isArray(testSpecifications) || !testSpecifications.length) {
|
|
178
171
|
return
|
|
@@ -1194,6 +1187,9 @@ function safeWorkspaceProject (ctx) {
|
|
|
1194
1187
|
|
|
1195
1188
|
function getSortWrapper (sort, frameworkVersion) {
|
|
1196
1189
|
return async function () {
|
|
1190
|
+
if (this.ctx.config.shard && this.ctx.state.pathsSet.size > 0 && arguments[0].length === 0) {
|
|
1191
|
+
emptyShardContexts.add(this.ctx)
|
|
1192
|
+
}
|
|
1197
1193
|
if (!activeRunFilesContexts.has(this.ctx)) {
|
|
1198
1194
|
const testSpecifications = arguments[0]
|
|
1199
1195
|
await ensureMainProcessSetup(this.ctx, frameworkVersion, testSpecifications)
|
|
@@ -1204,18 +1200,18 @@ function getSortWrapper (sort, frameworkVersion) {
|
|
|
1204
1200
|
}
|
|
1205
1201
|
|
|
1206
1202
|
function getFinishWrapper (exitOrClose) {
|
|
1207
|
-
let isClosed = false
|
|
1208
1203
|
return async function () {
|
|
1209
|
-
if (
|
|
1204
|
+
if (finishedSessionContexts.has(this)) {
|
|
1210
1205
|
return exitOrClose.apply(this, arguments)
|
|
1211
1206
|
}
|
|
1212
|
-
isClosed = true
|
|
1213
1207
|
|
|
1214
1208
|
if (!testSessionFinishCh.hasSubscribers) {
|
|
1215
1209
|
return exitOrClose.apply(this, arguments)
|
|
1216
1210
|
}
|
|
1211
|
+
finishedSessionContexts.add(this)
|
|
1217
1212
|
|
|
1218
1213
|
const failedSuites = this.state.getFailedFilepaths()
|
|
1214
|
+
const hasRunError = runErrorsByContext.has(this)
|
|
1219
1215
|
const runError = runErrorsByContext.get(this)
|
|
1220
1216
|
runErrorsByContext.delete(this)
|
|
1221
1217
|
let error = runError
|
|
@@ -1223,19 +1219,30 @@ function getFinishWrapper (exitOrClose) {
|
|
|
1223
1219
|
error = new Error(`Test suites failed: ${failedSuites.length}.`)
|
|
1224
1220
|
}
|
|
1225
1221
|
|
|
1222
|
+
// pathsSet contains candidates before sharding, including files that never run.
|
|
1226
1223
|
const hasNoTestFiles = this.state.pathsSet.size === 0
|
|
1227
|
-
const
|
|
1224
|
+
const isEmptyShard = emptyShardContexts.has(this)
|
|
1225
|
+
const hasUnexpectedEmptySession = hasNoTestFiles && !isEmptyShard &&
|
|
1226
|
+
!areAllSuitesSkipped && !this.config.passWithNoTests
|
|
1228
1227
|
if (!error && hasUnexpectedEmptySession) {
|
|
1229
1228
|
error = new Error('No test files were found.')
|
|
1230
1229
|
}
|
|
1231
|
-
const
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1230
|
+
const tests = getTypeTasks(this.state.getFiles())
|
|
1231
|
+
const hasExecutedTests = tests.some(test =>
|
|
1232
|
+
test.mode !== 'skip' && test.mode !== 'todo' &&
|
|
1233
|
+
test.result?.state !== 'skip' && test.result?.state !== 'todo'
|
|
1234
|
+
)
|
|
1235
|
+
const hasErrors = hasRunError || error ||
|
|
1236
|
+
this.state.getCountOfFailedTests() > 0 || this.state.getUnhandledErrors().length > 0
|
|
1237
|
+
const testSessionEmptyReason = !hasErrors && !hasExecutedTests
|
|
1238
|
+
? (isEmptyShard
|
|
1239
|
+
? 'zero_test_shard'
|
|
1240
|
+
: tests.length > 0 || skippedSuites.length > 0 ? 'all_tests_skipped' : 'zero_tests')
|
|
1241
|
+
: undefined
|
|
1242
|
+
const status = hasErrors ? 'fail' : (testSessionEmptyReason ? 'skip' : 'pass')
|
|
1236
1243
|
const flushPromise = getChannelPromise(testSessionFinishCh, {
|
|
1237
1244
|
status,
|
|
1238
|
-
|
|
1245
|
+
testSessionEmptyReason,
|
|
1239
1246
|
testCodeCoverageLinesTotal,
|
|
1240
1247
|
error,
|
|
1241
1248
|
isEarlyFlakeDetectionEnabled,
|
|
@@ -1391,12 +1398,46 @@ function markVitestWorkerEnv (ctx, testSpecifications, shouldSkipWorkerInit = fa
|
|
|
1391
1398
|
config.env = getVitestWorkerEnv(config.env, shouldSkipWorkerInit)
|
|
1392
1399
|
}
|
|
1393
1400
|
|
|
1401
|
+
/**
|
|
1402
|
+
* Installs completion hooks before discovery, which can bypass runFiles entirely.
|
|
1403
|
+
* @param {Function} Vitest
|
|
1404
|
+
*/
|
|
1405
|
+
function wrapVitestSession (Vitest) {
|
|
1406
|
+
if (!Vitest?.prototype?.start || sessionWrappedPrototypes.has(Vitest.prototype)) return
|
|
1407
|
+
sessionWrappedPrototypes.add(Vitest.prototype)
|
|
1408
|
+
|
|
1409
|
+
// Empty discovery bypasses runFiles. Vitest 1 exits directly after reporting coverage,
|
|
1410
|
+
// so its finalization must be awaited before that process.exit rather than only in close.
|
|
1411
|
+
// Hashed bundle names prevent Orchestrion's exact-file matching.
|
|
1412
|
+
shimmer.wrap(Vitest.prototype, 'start', start => function () {
|
|
1413
|
+
if (!testSessionFinishCh.hasSubscribers) return start.apply(this, arguments)
|
|
1414
|
+
wrapSessionFinish(this)
|
|
1415
|
+
return start.apply(this, arguments).then(undefined, error => {
|
|
1416
|
+
if (error?.code !== 'VITEST_FILES_NOT_FOUND') runErrorsByContext.set(this, error)
|
|
1417
|
+
throw error
|
|
1418
|
+
})
|
|
1419
|
+
})
|
|
1420
|
+
shimmer.wrap(Vitest.prototype, 'reportCoverage', reportCoverage => function () {
|
|
1421
|
+
const result = reportCoverage.apply(this, arguments)
|
|
1422
|
+
if (!testSessionFinishCh.hasSubscribers || !isSessionStarted || this.config.watch ||
|
|
1423
|
+
mainProcessSetupStates.has(this) || this.state.pathsSet.size > 0) {
|
|
1424
|
+
return result
|
|
1425
|
+
}
|
|
1426
|
+
return result.then(value => {
|
|
1427
|
+
const finish = getFinishWrapper(() => value)
|
|
1428
|
+
return finish.call(this)
|
|
1429
|
+
})
|
|
1430
|
+
})
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1394
1433
|
function wrapVitestRunFiles (Vitest, frameworkVersion) {
|
|
1395
1434
|
if (!Vitest?.prototype?.runFiles || runFilesWrappedPrototypes.has(Vitest.prototype)) {
|
|
1396
1435
|
return
|
|
1397
1436
|
}
|
|
1398
1437
|
runFilesWrappedPrototypes.add(Vitest.prototype)
|
|
1399
1438
|
|
|
1439
|
+
wrapVitestSession(Vitest)
|
|
1440
|
+
|
|
1400
1441
|
shimmer.wrap(Vitest.prototype, 'runFiles', runFiles => async function (testSpecifications) {
|
|
1401
1442
|
if (activeRunFilesContexts.has(this)) {
|
|
1402
1443
|
return runFiles.apply(this, arguments)
|
|
@@ -2080,6 +2121,36 @@ addHook({
|
|
|
2080
2121
|
return wrapTinyPool(TinyPool)
|
|
2081
2122
|
})
|
|
2082
2123
|
|
|
2124
|
+
/**
|
|
2125
|
+
* Older Vitest versions expose their core instance through the logger constructor.
|
|
2126
|
+
* @param {Record<string, Function>} vitestPackage
|
|
2127
|
+
*/
|
|
2128
|
+
function wrapVitestLogger (vitestPackage) {
|
|
2129
|
+
const logger = findExportByName(vitestPackage, 'Logger')
|
|
2130
|
+
if (logger) {
|
|
2131
|
+
// Hashed bundle names prevent Orchestrion's exact-file matching.
|
|
2132
|
+
shimmer.wrap(vitestPackage, logger.key, Logger => class extends Logger {
|
|
2133
|
+
constructor (...args) {
|
|
2134
|
+
super(...args)
|
|
2135
|
+
if (testSessionFinishCh.hasSubscribers) wrapVitestSession(args[0]?.constructor)
|
|
2136
|
+
}
|
|
2137
|
+
})
|
|
2138
|
+
}
|
|
2139
|
+
return vitestPackage
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
addHook({
|
|
2143
|
+
name: 'vitest',
|
|
2144
|
+
versions: ['>=1.6.0 <2.0.5'],
|
|
2145
|
+
filePattern: 'dist/vendor/index.*',
|
|
2146
|
+
}, wrapVitestLogger)
|
|
2147
|
+
|
|
2148
|
+
addHook({
|
|
2149
|
+
name: 'vitest',
|
|
2150
|
+
versions: ['>=2.0.5 <3.0.0'],
|
|
2151
|
+
filePattern: 'dist/chunks/index.*',
|
|
2152
|
+
}, wrapVitestLogger)
|
|
2153
|
+
|
|
2083
2154
|
// There are multiple index* files across different versions of vitest,
|
|
2084
2155
|
// so we check for the existence of BaseSequencer to determine if we are in the right file
|
|
2085
2156
|
addHook({
|