dd-trace 6.7.0 → 6.8.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/ci/test-optimization-validation/command-output-policy.js +5 -5
- package/ci/test-optimization-validation/framework-adapters/playwright.js +5 -5
- package/ci/test-optimization-validation/generated-files.js +9 -9
- package/ci/test-optimization-validation/generated-test-contract.js +1 -3
- package/ci/test-optimization-validation/manifest-scaffold.js +1 -2
- package/ci/test-optimization-validation/offline-output.js +6 -6
- package/ci/test-optimization-validation/runner-command.js +1 -1
- package/ci/test-optimization-validation/safe-files.js +4 -4
- package/ci/test-optimization-validation/scenarios/basic-reporting.js +1 -1
- package/ci/test-optimization-validation/scenarios/test-management.js +2 -2
- package/ci/vitest-no-worker-init-setup.mjs +348 -51
- package/index.d.ts +77 -13
- package/package.json +9 -3
- package/packages/datadog-core/src/utils/src/kebabcase.js +8 -2
- package/packages/datadog-esbuild/index.js +4 -2
- package/packages/datadog-esbuild/src/utils.js +1 -1
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +4 -4
- package/packages/datadog-instrumentations/src/cucumber.js +9 -6
- package/packages/datadog-instrumentations/src/helpers/extract-package-and-module-path.js +10 -0
- package/packages/datadog-instrumentations/src/helpers/hook.js +19 -2
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +9 -3
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +57 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +50 -18
- package/packages/datadog-instrumentations/src/helpers/router-helper.js +1 -1
- package/packages/datadog-instrumentations/src/helpers/shared-utils.js +39 -0
- package/packages/datadog-instrumentations/src/http/client.js +1 -3
- package/packages/datadog-instrumentations/src/jest.js +8 -14
- package/packages/datadog-instrumentations/src/mocha/common.js +1 -3
- package/packages/datadog-instrumentations/src/mocha/main.js +2 -42
- package/packages/datadog-instrumentations/src/mocha/utils.js +68 -2
- package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +19 -0
- package/packages/datadog-instrumentations/src/mocha/worker.js +446 -42
- package/packages/datadog-instrumentations/src/moleculer/client.js +1 -1
- package/packages/datadog-instrumentations/src/playwright.js +3 -2
- package/packages/datadog-instrumentations/src/rhea.js +1 -1
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +525 -56
- package/packages/datadog-instrumentations/src/vitest-main.js +90 -10
- package/packages/datadog-instrumentations/src/webdriverio.js +914 -0
- package/packages/datadog-plugin-aerospike/src/index.js +1 -3
- package/packages/datadog-plugin-amqplib/src/client.js +1 -1
- package/packages/datadog-plugin-amqplib/src/producer.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +2 -2
- package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +2 -2
- package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -3
- package/packages/datadog-plugin-azure-service-bus/src/producer.js +5 -3
- package/packages/datadog-plugin-bullmq/src/producer.js +3 -3
- package/packages/datadog-plugin-child_process/src/index.js +3 -3
- package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +5 -8
- package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -1
- package/packages/datadog-plugin-fastify/src/tracing.js +1 -1
- package/packages/datadog-plugin-fs/src/index.js +0 -4
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +14 -7
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +19 -10
- package/packages/datadog-plugin-graphql/src/execute.js +1 -0
- package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -1
- package/packages/datadog-plugin-http/src/client.js +2 -1
- package/packages/datadog-plugin-jest/src/index.js +2 -3
- package/packages/datadog-plugin-jest/src/util.js +2 -1
- package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
- package/packages/datadog-plugin-mocha/src/index.js +25 -6
- package/packages/datadog-plugin-prisma/src/index.js +5 -1
- package/packages/datadog-plugin-rhea/src/consumer.js +16 -13
- package/packages/datadog-plugin-rhea/src/producer.js +1 -1
- package/packages/datadog-plugin-undici/src/index.js +2 -1
- package/packages/datadog-plugin-vitest/src/index.js +84 -11
- package/packages/datadog-webpack/index.js +2 -2
- package/packages/dd-trace/src/appsec/downstream_requests.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -3
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +2 -2
- package/packages/dd-trace/src/appsec/sdk/track_event.js +1 -1
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +7 -6
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +2 -2
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +2 -1
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +4 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/webdriverio.js +26 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -1
- package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -3
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +1 -1
- package/packages/dd-trace/src/config/defaults.js +14 -6
- package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -2
- package/packages/dd-trace/src/config/index.js +15 -0
- package/packages/dd-trace/src/config/parsers.js +19 -1
- package/packages/dd-trace/src/config/supported-configurations.json +5 -6
- package/packages/dd-trace/src/datastreams/encoding.js +3 -4
- package/packages/dd-trace/src/debugger/devtools_client/log.js +2 -1
- package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +1 -4
- package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -2
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +2 -2
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +16 -2
- package/packages/dd-trace/src/encode/tags-processors.js +2 -2
- package/packages/dd-trace/src/exporters/common/url.js +3 -5
- package/packages/dd-trace/src/exporters/electron/index.js +4 -1
- package/packages/dd-trace/src/external-logger/src/index.js +1 -2
- package/packages/dd-trace/src/flare/index.js +2 -2
- package/packages/dd-trace/src/id.js +1 -1
- package/packages/dd-trace/src/llmobs/constants/tags.js +1 -1
- package/packages/dd-trace/src/llmobs/experiments/client.js +4 -2
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +66 -14
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +289 -95
- package/packages/dd-trace/src/llmobs/experiments/index.js +51 -12
- package/packages/dd-trace/src/llmobs/experiments/noop.js +95 -10
- package/packages/dd-trace/src/llmobs/experiments/result.js +13 -2
- package/packages/dd-trace/src/llmobs/experiments/util.js +218 -0
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
- package/packages/dd-trace/src/llmobs/sdk.js +4 -11
- package/packages/dd-trace/src/llmobs/span_processor.js +9 -7
- package/packages/dd-trace/src/llmobs/tagger.js +1 -2
- package/packages/dd-trace/src/llmobs/writers/spans.js +12 -6
- package/packages/dd-trace/src/noop/proxy.js +2 -2
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +2 -1
- package/packages/dd-trace/src/opentracing/span.js +1 -1
- package/packages/dd-trace/src/opentracing/span_context.js +1 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +105 -20
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/dd-trace/src/plugins/plugin.js +1 -1
- package/packages/dd-trace/src/plugins/tracing.js +1 -0
- package/packages/dd-trace/src/plugins/util/ci.js +7 -6
- package/packages/dd-trace/src/plugins/util/git.js +1 -1
- package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +1 -1
- package/packages/dd-trace/src/plugins/util/llm.js +2 -2
- package/packages/dd-trace/src/plugins/util/tags.js +2 -0
- package/packages/dd-trace/src/plugins/util/test.js +38 -12
- package/packages/dd-trace/src/plugins/util/url.js +5 -5
- package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -1
- package/packages/dd-trace/src/plugins/util/web.js +1 -1
- package/packages/dd-trace/src/priority_sampler.js +6 -3
- package/packages/dd-trace/src/profiling/profilers/poisson.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/shared.js +2 -2
- package/packages/dd-trace/src/ritm.js +5 -20
- package/packages/dd-trace/src/startup-log.js +3 -0
- package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -1
- package/packages/dd-trace/src/util.js +16 -0
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +12 -12
- package/vendor/dist/pprof-format/index.js +1 -1
- package/vendor/dist/source-map/index.js +1 -1
|
@@ -0,0 +1,914 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { AsyncResource } = require('node:async_hooks')
|
|
4
|
+
const { fileURLToPath } = require('node:url')
|
|
5
|
+
|
|
6
|
+
const { getEnvironmentVariable } = require('../../dd-trace/src/config/helper')
|
|
7
|
+
const log = require('../../dd-trace/src/log')
|
|
8
|
+
const {
|
|
9
|
+
collectTestOptimizationSummariesFromTraces,
|
|
10
|
+
getIsFaultyEarlyFlakeDetection,
|
|
11
|
+
getTestSuitePath,
|
|
12
|
+
logTestOptimizationSummary,
|
|
13
|
+
MOCHA_WORKER_LOGS_PAYLOAD_CODE,
|
|
14
|
+
MOCHA_WORKER_TRACE_PAYLOAD_CODE,
|
|
15
|
+
TEST_SUITE_EXECUTION_ID,
|
|
16
|
+
} = require('../../dd-trace/src/plugins/util/test')
|
|
17
|
+
const { addHook, channel, tracingChannel } = require('./helpers/instrument')
|
|
18
|
+
const {
|
|
19
|
+
CONFIGURATION_REQUEST,
|
|
20
|
+
CONFIGURATION_RESPONSE,
|
|
21
|
+
SUITE_FINISH,
|
|
22
|
+
WEBDRIVERIO_WORKER_ENV,
|
|
23
|
+
WEBDRIVERIO_WORKER_EVENT,
|
|
24
|
+
WEBDRIVERIO_WORKER_ORIGIN,
|
|
25
|
+
WORKER_READY,
|
|
26
|
+
} = require('./mocha/webdriverio-protocol')
|
|
27
|
+
|
|
28
|
+
const testFinishCh = channel('ci:mocha:test:finish')
|
|
29
|
+
const testSessionStartCh = channel('ci:mocha:session:start')
|
|
30
|
+
const testSessionFinishCh = channel('ci:mocha:session:finish')
|
|
31
|
+
const testSuiteStartCh = channel('ci:mocha:test-suite:start')
|
|
32
|
+
const testSuiteFinishCh = channel('ci:mocha:test-suite:finish')
|
|
33
|
+
const knownTestsCh = channel('ci:mocha:known-tests')
|
|
34
|
+
const libraryConfigurationCh = channel('ci:mocha:library-configuration')
|
|
35
|
+
const modifiedFilesCh = channel('ci:mocha:modified-files')
|
|
36
|
+
const testManagementTestsCh = channel('ci:mocha:test-management-tests')
|
|
37
|
+
const workerReportLogsCh = channel('ci:mocha:worker-report:logs')
|
|
38
|
+
const workerReportTraceCh = channel('ci:mocha:worker-report:trace')
|
|
39
|
+
|
|
40
|
+
const launcherStartInstanceCh = tracingChannel('orchestrion:@wdio/cli:Launcher_startInstance')
|
|
41
|
+
const localRunnerRunCh = tracingChannel('orchestrion:@wdio/local-runner:LocalRunner_run')
|
|
42
|
+
const localRunnerShutdownCh = tracingChannel('orchestrion:@wdio/local-runner:LocalRunner_shutdown')
|
|
43
|
+
|
|
44
|
+
const NODE_OPTIONS_SEPARATOR_RE = /\s/
|
|
45
|
+
const TEST_FRAMEWORK = 'webdriverio'
|
|
46
|
+
|
|
47
|
+
const loadCh = channel('dd-trace:instrumentation:load')
|
|
48
|
+
if (loadCh.hasSubscribers) {
|
|
49
|
+
loadCh.publish({ name: '@wdio/local-runner' })
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const coordinatorStates = new WeakMap()
|
|
53
|
+
const localRunnerVersions = new WeakMap()
|
|
54
|
+
|
|
55
|
+
addHook({
|
|
56
|
+
name: '@wdio/local-runner',
|
|
57
|
+
versions: ['>=9.0.0'],
|
|
58
|
+
file: 'build/index.js',
|
|
59
|
+
patchDefault: true,
|
|
60
|
+
}, (LocalRunner, version) => {
|
|
61
|
+
localRunnerVersions.set(LocalRunner, version)
|
|
62
|
+
return LocalRunner
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @typedef {object} WebdriverioRunnerConfig
|
|
67
|
+
* @property {string} framework
|
|
68
|
+
* @property {string|undefined} rootDir
|
|
69
|
+
* @property {NodeJS.ProcessEnv|undefined} runnerEnv
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @typedef {object} WebdriverioLocalRunner
|
|
74
|
+
* @property {WebdriverioRunnerConfig|undefined} config
|
|
75
|
+
* @property {WebdriverioRunnerConfig|undefined} _config
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @typedef {object} WorkerRecord
|
|
80
|
+
* @property {object} worker
|
|
81
|
+
* @property {string[]} specs
|
|
82
|
+
* @property {Map<string, WebdriverioSuiteContext>} suiteContexts
|
|
83
|
+
* @property {string} testSuiteExecutionId
|
|
84
|
+
* @property {boolean|undefined} hasTests
|
|
85
|
+
* @property {number|undefined} exitCode
|
|
86
|
+
* @property {number|undefined} retries
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @typedef {object} WebdriverioSuiteContext
|
|
91
|
+
* @property {object|undefined} currentStore
|
|
92
|
+
* @property {string|undefined} status
|
|
93
|
+
* @property {string} testSuiteAbsolutePath
|
|
94
|
+
* @property {string} testSuiteExecutionId
|
|
95
|
+
*/
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @typedef {object} CoordinatorState
|
|
99
|
+
* @property {WebdriverioLocalRunner} localRunner
|
|
100
|
+
* @property {AsyncResource} asyncResource
|
|
101
|
+
* @property {object} configuration
|
|
102
|
+
* @property {boolean} initialized
|
|
103
|
+
* @property {boolean} initializing
|
|
104
|
+
* @property {Array<(configuration: object) => void>} initializationCallbacks
|
|
105
|
+
* @property {boolean} sessionStarted
|
|
106
|
+
* @property {boolean} finished
|
|
107
|
+
* @property {string|undefined} frameworkVersion
|
|
108
|
+
* @property {string} testFrameworkAdapter
|
|
109
|
+
* @property {number} activeWorkers
|
|
110
|
+
* @property {Map<string, object>} attemptToFixExecutions
|
|
111
|
+
* @property {number} maxActiveWorkers
|
|
112
|
+
* @property {Set<string>} newTestsWithDynamicNames
|
|
113
|
+
* @property {number} nextWorkerId
|
|
114
|
+
* @property {Set<string>} scheduledFiles
|
|
115
|
+
* @property {unknown} runError
|
|
116
|
+
* @property {Set<WorkerRecord>} workers
|
|
117
|
+
* @property {Map<object, string>} suiteStatuses
|
|
118
|
+
*/
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Creates the configuration consumed by Mocha workers.
|
|
122
|
+
*
|
|
123
|
+
* @returns {object}
|
|
124
|
+
*/
|
|
125
|
+
function createWorkerConfiguration () {
|
|
126
|
+
return {
|
|
127
|
+
earlyFlakeDetectionFaultyThreshold: 30,
|
|
128
|
+
earlyFlakeDetectionNumRetries: 0,
|
|
129
|
+
earlyFlakeDetectionSlowTestRetries: {},
|
|
130
|
+
flakyTestRetriesCount: 0,
|
|
131
|
+
isCodeCoverageEnabled: false,
|
|
132
|
+
isCoverageReportUploadEnabled: false,
|
|
133
|
+
isDiEnabled: false,
|
|
134
|
+
isEarlyFlakeDetectionEnabled: false,
|
|
135
|
+
isFlakyTestRetriesEnabled: false,
|
|
136
|
+
isImpactedTestsEnabled: false,
|
|
137
|
+
isItrEnabled: false,
|
|
138
|
+
isKnownTestsEnabled: false,
|
|
139
|
+
isSuitesSkippingEnabled: false,
|
|
140
|
+
isTestDynamicInstrumentationEnabled: false,
|
|
141
|
+
isTestManagementTestsEnabled: false,
|
|
142
|
+
knownTests: {},
|
|
143
|
+
modifiedFiles: [],
|
|
144
|
+
repositoryRoot: undefined,
|
|
145
|
+
testFramework: TEST_FRAMEWORK,
|
|
146
|
+
testManagementAttemptToFixRetries: 0,
|
|
147
|
+
testManagementTests: {},
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Gets the public runner configuration, or the private equivalent used by older releases.
|
|
153
|
+
*
|
|
154
|
+
* @param {WebdriverioLocalRunner} localRunner
|
|
155
|
+
* @returns {WebdriverioRunnerConfig|undefined}
|
|
156
|
+
*/
|
|
157
|
+
function getRunnerConfiguration (localRunner) {
|
|
158
|
+
return localRunner.config || localRunner._config
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Gets or creates coordinator state for a WebdriverIO local runner.
|
|
163
|
+
*
|
|
164
|
+
* @param {WebdriverioLocalRunner} localRunner
|
|
165
|
+
* @returns {CoordinatorState}
|
|
166
|
+
*/
|
|
167
|
+
function getCoordinatorState (localRunner) {
|
|
168
|
+
let state = coordinatorStates.get(localRunner)
|
|
169
|
+
if (state) {
|
|
170
|
+
return state
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
state = {
|
|
174
|
+
localRunner,
|
|
175
|
+
asyncResource: new AsyncResource('dd-trace-webdriverio-coordinator'),
|
|
176
|
+
configuration: createWorkerConfiguration(),
|
|
177
|
+
initialized: false,
|
|
178
|
+
initializing: false,
|
|
179
|
+
initializationCallbacks: [],
|
|
180
|
+
sessionStarted: false,
|
|
181
|
+
finished: false,
|
|
182
|
+
frameworkVersion: localRunnerVersions.get(localRunner.constructor),
|
|
183
|
+
testFrameworkAdapter: getRunnerConfiguration(localRunner)?.framework,
|
|
184
|
+
activeWorkers: 0,
|
|
185
|
+
attemptToFixExecutions: new Map(),
|
|
186
|
+
maxActiveWorkers: 0,
|
|
187
|
+
newTestsWithDynamicNames: new Set(),
|
|
188
|
+
nextWorkerId: 0,
|
|
189
|
+
scheduledFiles: new Set(),
|
|
190
|
+
runError: undefined,
|
|
191
|
+
workers: new Set(),
|
|
192
|
+
suiteStatuses: new Map(),
|
|
193
|
+
}
|
|
194
|
+
coordinatorStates.set(localRunner, state)
|
|
195
|
+
|
|
196
|
+
return state
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Normalizes a WebdriverIO spec identifier to a filesystem path.
|
|
201
|
+
*
|
|
202
|
+
* @param {string} file
|
|
203
|
+
* @returns {string}
|
|
204
|
+
*/
|
|
205
|
+
function normalizeFile (file) {
|
|
206
|
+
return file.startsWith('file://') ? fileURLToPath(file) : file
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Adds resolved WebdriverIO spec files to the coordinator's complete schedule.
|
|
211
|
+
*
|
|
212
|
+
* @param {CoordinatorState} state
|
|
213
|
+
* @param {string[]} files
|
|
214
|
+
* @returns {void}
|
|
215
|
+
*/
|
|
216
|
+
function addScheduledFiles (state, files) {
|
|
217
|
+
for (const file of files) {
|
|
218
|
+
state.scheduledFiles.add(normalizeFile(file))
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Checks whether worker NODE_OPTIONS contain the complete launcher options.
|
|
224
|
+
*
|
|
225
|
+
* @param {string|undefined} workerNodeOptions
|
|
226
|
+
* @param {string} launcherNodeOptions
|
|
227
|
+
* @returns {boolean}
|
|
228
|
+
*/
|
|
229
|
+
function includesNodeOptions (workerNodeOptions, launcherNodeOptions) {
|
|
230
|
+
if (!workerNodeOptions) {
|
|
231
|
+
return false
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
let index = workerNodeOptions.indexOf(launcherNodeOptions)
|
|
235
|
+
while (index !== -1) {
|
|
236
|
+
const endIndex = index + launcherNodeOptions.length
|
|
237
|
+
const startsAtBoundary = index === 0 || NODE_OPTIONS_SEPARATOR_RE.test(workerNodeOptions[index - 1])
|
|
238
|
+
const endsAtBoundary = endIndex === workerNodeOptions.length ||
|
|
239
|
+
NODE_OPTIONS_SEPARATOR_RE.test(workerNodeOptions[endIndex])
|
|
240
|
+
|
|
241
|
+
if (startsAtBoundary && endsAtBoundary) {
|
|
242
|
+
return true
|
|
243
|
+
}
|
|
244
|
+
index = workerNodeOptions.indexOf(launcherNodeOptions, index + 1)
|
|
245
|
+
}
|
|
246
|
+
return false
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Starts the single Mocha session owned by the WebdriverIO launcher.
|
|
251
|
+
*
|
|
252
|
+
* @param {CoordinatorState} state
|
|
253
|
+
* @returns {void}
|
|
254
|
+
*/
|
|
255
|
+
function startSession (state) {
|
|
256
|
+
if (state.sessionStarted) {
|
|
257
|
+
return
|
|
258
|
+
}
|
|
259
|
+
const processArgv = process.argv.slice(2).join(' ')
|
|
260
|
+
const command = processArgv ? `wdio ${processArgv}` : 'wdio'
|
|
261
|
+
const rootDir = getRunnerConfiguration(state.localRunner)?.rootDir || process.cwd()
|
|
262
|
+
|
|
263
|
+
testSessionStartCh.publish({
|
|
264
|
+
command,
|
|
265
|
+
frameworkVersion: state.frameworkVersion,
|
|
266
|
+
rootDir,
|
|
267
|
+
testFramework: TEST_FRAMEWORK,
|
|
268
|
+
testFrameworkAdapter: state.testFrameworkAdapter,
|
|
269
|
+
})
|
|
270
|
+
state.sessionStarted = true
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Completes coordinator initialization and releases waiting workers.
|
|
275
|
+
*
|
|
276
|
+
* @param {CoordinatorState} state
|
|
277
|
+
* @returns {void}
|
|
278
|
+
*/
|
|
279
|
+
function completeCoordinatorInitialization (state) {
|
|
280
|
+
if (state.initialized) {
|
|
281
|
+
return
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
state.initialized = true
|
|
285
|
+
state.initializing = false
|
|
286
|
+
startSession(state)
|
|
287
|
+
|
|
288
|
+
const callbacks = state.initializationCallbacks
|
|
289
|
+
state.initializationCallbacks = []
|
|
290
|
+
for (const callback of callbacks) {
|
|
291
|
+
callback(state.configuration)
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Runs one coordinator-owned feature request.
|
|
297
|
+
*
|
|
298
|
+
* @param {CoordinatorState} state
|
|
299
|
+
* @param {import('node:diagnostics_channel').Channel} requestChannel
|
|
300
|
+
* @param {(response: object) => void} onDone
|
|
301
|
+
* @returns {void}
|
|
302
|
+
*/
|
|
303
|
+
function requestCoordinatorData (state, requestChannel, onDone) {
|
|
304
|
+
if (!requestChannel.hasSubscribers) {
|
|
305
|
+
onDone({ err: new Error('Test optimization was not initialized correctly') })
|
|
306
|
+
return
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
try {
|
|
310
|
+
requestChannel.runStores({
|
|
311
|
+
onDone: response => state.asyncResource.runInAsyncScope(onDone, undefined, response),
|
|
312
|
+
}, () => {})
|
|
313
|
+
} catch (error) {
|
|
314
|
+
log.error('WebdriverIO Test Optimization feature request error', error)
|
|
315
|
+
onDone({ err: error })
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Maps backend framework data to the Mocha adapter's internal framework key.
|
|
321
|
+
*
|
|
322
|
+
* @param {object|undefined} frameworkData
|
|
323
|
+
* @returns {object|undefined}
|
|
324
|
+
*/
|
|
325
|
+
function getMochaFrameworkData (frameworkData) {
|
|
326
|
+
return frameworkData?.[TEST_FRAMEWORK] ?? frameworkData?.mocha
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Applies settings and requests the enabled non-TIA datasets once for the whole run.
|
|
331
|
+
*
|
|
332
|
+
* @param {CoordinatorState} state
|
|
333
|
+
* @param {object|undefined} response
|
|
334
|
+
* @returns {void}
|
|
335
|
+
*/
|
|
336
|
+
function configureCoordinator (state, response) {
|
|
337
|
+
const { configuration } = state
|
|
338
|
+
const {
|
|
339
|
+
err,
|
|
340
|
+
isTestDynamicInstrumentationEnabled,
|
|
341
|
+
libraryConfig,
|
|
342
|
+
repositoryRoot,
|
|
343
|
+
} = response || {}
|
|
344
|
+
|
|
345
|
+
configuration.repositoryRoot = repositoryRoot
|
|
346
|
+
if (err || !libraryConfig) {
|
|
347
|
+
completeCoordinatorInitialization(state)
|
|
348
|
+
return
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
configuration.earlyFlakeDetectionFaultyThreshold = libraryConfig.earlyFlakeDetectionFaultyThreshold
|
|
352
|
+
configuration.earlyFlakeDetectionNumRetries = libraryConfig.earlyFlakeDetectionNumRetries
|
|
353
|
+
configuration.earlyFlakeDetectionSlowTestRetries = libraryConfig.earlyFlakeDetectionSlowTestRetries ?? {}
|
|
354
|
+
configuration.flakyTestRetriesCount = libraryConfig.flakyTestRetriesCount
|
|
355
|
+
configuration.isDiEnabled = libraryConfig.isDiEnabled
|
|
356
|
+
configuration.isEarlyFlakeDetectionEnabled = libraryConfig.isEarlyFlakeDetectionEnabled
|
|
357
|
+
configuration.isFlakyTestRetriesEnabled = libraryConfig.isFlakyTestRetriesEnabled
|
|
358
|
+
configuration.isImpactedTestsEnabled = libraryConfig.isImpactedTestsEnabled
|
|
359
|
+
configuration.isKnownTestsEnabled = libraryConfig.isKnownTestsEnabled
|
|
360
|
+
configuration.isTestDynamicInstrumentationEnabled = isTestDynamicInstrumentationEnabled
|
|
361
|
+
configuration.isTestManagementTestsEnabled = libraryConfig.isTestManagementEnabled
|
|
362
|
+
configuration.testManagementAttemptToFixRetries = libraryConfig.testManagementAttemptToFixRetries
|
|
363
|
+
|
|
364
|
+
let pendingRequests = 1
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Completes initialization after all enabled requests settle.
|
|
368
|
+
*
|
|
369
|
+
* @returns {void}
|
|
370
|
+
*/
|
|
371
|
+
function finishRequest () {
|
|
372
|
+
pendingRequests--
|
|
373
|
+
if (pendingRequests === 0) {
|
|
374
|
+
completeCoordinatorInitialization(state)
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (configuration.isKnownTestsEnabled) {
|
|
379
|
+
pendingRequests++
|
|
380
|
+
requestCoordinatorData(state, knownTestsCh, ({ err, knownTests } = {}) => {
|
|
381
|
+
const mochaKnownTests = getMochaFrameworkData(knownTests)
|
|
382
|
+
if (err || !mochaKnownTests) {
|
|
383
|
+
configuration.isEarlyFlakeDetectionEnabled = false
|
|
384
|
+
configuration.isKnownTestsEnabled = false
|
|
385
|
+
} else {
|
|
386
|
+
configuration.knownTests = { mocha: mochaKnownTests }
|
|
387
|
+
}
|
|
388
|
+
finishRequest()
|
|
389
|
+
})
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (configuration.isTestManagementTestsEnabled) {
|
|
393
|
+
pendingRequests++
|
|
394
|
+
requestCoordinatorData(state, testManagementTestsCh, ({ err, testManagementTests } = {}) => {
|
|
395
|
+
if (err) {
|
|
396
|
+
configuration.isTestManagementTestsEnabled = false
|
|
397
|
+
configuration.testManagementAttemptToFixRetries = 0
|
|
398
|
+
} else {
|
|
399
|
+
const mochaTestManagementTests = getMochaFrameworkData(testManagementTests) || { suites: {} }
|
|
400
|
+
configuration.testManagementTests = { mocha: mochaTestManagementTests }
|
|
401
|
+
}
|
|
402
|
+
finishRequest()
|
|
403
|
+
})
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (configuration.isImpactedTestsEnabled) {
|
|
407
|
+
pendingRequests++
|
|
408
|
+
requestCoordinatorData(state, modifiedFilesCh, ({ err, modifiedFiles } = {}) => {
|
|
409
|
+
if (err) {
|
|
410
|
+
configuration.isImpactedTestsEnabled = false
|
|
411
|
+
} else {
|
|
412
|
+
configuration.modifiedFiles = modifiedFiles
|
|
413
|
+
}
|
|
414
|
+
finishRequest()
|
|
415
|
+
})
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
finishRequest()
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Requests settings once so the launcher owns initialization for every worker.
|
|
423
|
+
*
|
|
424
|
+
* @param {CoordinatorState} state
|
|
425
|
+
* @param {(configuration: object) => void} [onDone]
|
|
426
|
+
* @returns {void}
|
|
427
|
+
*/
|
|
428
|
+
function initializeCoordinator (state, onDone) {
|
|
429
|
+
if (state.initialized) {
|
|
430
|
+
onDone?.(state.configuration)
|
|
431
|
+
return
|
|
432
|
+
}
|
|
433
|
+
if (onDone) {
|
|
434
|
+
state.initializationCallbacks.push(onDone)
|
|
435
|
+
}
|
|
436
|
+
if (state.initializing) {
|
|
437
|
+
return
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
state.initializing = true
|
|
441
|
+
if (!libraryConfigurationCh.hasSubscribers) {
|
|
442
|
+
completeCoordinatorInitialization(state)
|
|
443
|
+
return
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
try {
|
|
447
|
+
libraryConfigurationCh.runStores({
|
|
448
|
+
disableTestImpactAnalysis: true,
|
|
449
|
+
frameworkVersion: state.frameworkVersion,
|
|
450
|
+
isParallel: state.maxActiveWorkers > 1,
|
|
451
|
+
testFramework: TEST_FRAMEWORK,
|
|
452
|
+
onDone: response => state.asyncResource.runInAsyncScope(
|
|
453
|
+
configureCoordinator,
|
|
454
|
+
undefined,
|
|
455
|
+
state,
|
|
456
|
+
response
|
|
457
|
+
),
|
|
458
|
+
}, () => {})
|
|
459
|
+
} catch (error) {
|
|
460
|
+
log.error('WebdriverIO Test Optimization configuration error', error)
|
|
461
|
+
completeCoordinatorInitialization(state)
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Starts suites for one worker.
|
|
467
|
+
*
|
|
468
|
+
* @param {WorkerRecord} workerRecord
|
|
469
|
+
* @param {string[]} files
|
|
470
|
+
* @returns {void}
|
|
471
|
+
*/
|
|
472
|
+
function startWorkerSuites (workerRecord, files) {
|
|
473
|
+
for (const rawFile of files) {
|
|
474
|
+
const file = normalizeFile(rawFile)
|
|
475
|
+
if (workerRecord.suiteContexts.has(file)) {
|
|
476
|
+
continue
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const suiteContext = {
|
|
480
|
+
testSuiteAbsolutePath: file,
|
|
481
|
+
testSuiteExecutionId: workerRecord.testSuiteExecutionId,
|
|
482
|
+
}
|
|
483
|
+
testSuiteStartCh.runStores(suiteContext, () => {})
|
|
484
|
+
workerRecord.suiteContexts.set(file, suiteContext)
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Finishes one suite if it is still active.
|
|
490
|
+
*
|
|
491
|
+
* @param {CoordinatorState} state
|
|
492
|
+
* @param {WorkerRecord} workerRecord
|
|
493
|
+
* @param {string} rawFile
|
|
494
|
+
* @param {string} status
|
|
495
|
+
* @returns {void}
|
|
496
|
+
*/
|
|
497
|
+
function finishWorkerSuite (state, workerRecord, rawFile, status) {
|
|
498
|
+
const file = normalizeFile(rawFile)
|
|
499
|
+
const suiteContext = workerRecord.suiteContexts.get(file)
|
|
500
|
+
if (!suiteContext || state.suiteStatuses.has(suiteContext)) {
|
|
501
|
+
return
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
state.suiteStatuses.set(suiteContext, status)
|
|
505
|
+
testSuiteFinishCh.publish({ status, ...suiteContext.currentStore })
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Finishes every active suite belonging to a worker.
|
|
510
|
+
*
|
|
511
|
+
* @param {CoordinatorState} state
|
|
512
|
+
* @param {WorkerRecord} workerRecord
|
|
513
|
+
* @param {string} status
|
|
514
|
+
* @returns {void}
|
|
515
|
+
*/
|
|
516
|
+
function finishAllWorkerSuites (state, workerRecord, status) {
|
|
517
|
+
let hasFailedSuite = false
|
|
518
|
+
for (const suiteContext of workerRecord.suiteContexts.values()) {
|
|
519
|
+
if (suiteContext.status === 'fail') {
|
|
520
|
+
hasFailedSuite = true
|
|
521
|
+
break
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
for (const [file, suiteContext] of workerRecord.suiteContexts) {
|
|
526
|
+
let suiteStatus = suiteContext.status ?? status
|
|
527
|
+
if (status === 'fail' && !hasFailedSuite && suiteStatus === 'skip') {
|
|
528
|
+
suiteStatus = 'fail'
|
|
529
|
+
}
|
|
530
|
+
finishWorkerSuite(state, workerRecord, file, suiteStatus)
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Sends a coordinator message to a WebdriverIO child process.
|
|
536
|
+
*
|
|
537
|
+
* @param {WorkerRecord} workerRecord
|
|
538
|
+
* @param {object} message
|
|
539
|
+
* @returns {void}
|
|
540
|
+
*/
|
|
541
|
+
function sendWorkerMessage (workerRecord, message) {
|
|
542
|
+
const childProcess = workerRecord.worker.childProcess
|
|
543
|
+
if (!childProcess?.connected) {
|
|
544
|
+
return
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
childProcess.send(message, (error) => {
|
|
548
|
+
if (error) {
|
|
549
|
+
log.error('WebdriverIO Test Optimization IPC error', error)
|
|
550
|
+
}
|
|
551
|
+
})
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Handles a worker request for its Mocha execution configuration.
|
|
556
|
+
*
|
|
557
|
+
* @param {CoordinatorState} state
|
|
558
|
+
* @param {WorkerRecord} workerRecord
|
|
559
|
+
* @param {object} message
|
|
560
|
+
* @returns {void}
|
|
561
|
+
*/
|
|
562
|
+
function handleConfigurationRequest (state, workerRecord, message) {
|
|
563
|
+
const { files = [], requestId } = message.content || {}
|
|
564
|
+
|
|
565
|
+
initializeCoordinator(state, (configuration) => {
|
|
566
|
+
updateEarlyFlakeDetectionFaultyState(state, files)
|
|
567
|
+
startWorkerSuites(workerRecord, files)
|
|
568
|
+
sendWorkerMessage(workerRecord, {
|
|
569
|
+
origin: 'datadog',
|
|
570
|
+
name: CONFIGURATION_RESPONSE,
|
|
571
|
+
content: {
|
|
572
|
+
configuration,
|
|
573
|
+
requestId,
|
|
574
|
+
},
|
|
575
|
+
})
|
|
576
|
+
})
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Disables EFD when the accumulated WebdriverIO run exceeds its new-suite threshold.
|
|
581
|
+
*
|
|
582
|
+
* @param {CoordinatorState} state
|
|
583
|
+
* @param {string[]} files
|
|
584
|
+
* @returns {void}
|
|
585
|
+
*/
|
|
586
|
+
function updateEarlyFlakeDetectionFaultyState (state, files) {
|
|
587
|
+
const { configuration } = state
|
|
588
|
+
if (!configuration.isKnownTestsEnabled) {
|
|
589
|
+
return
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
addScheduledFiles(state, files)
|
|
593
|
+
const scheduledSuites = []
|
|
594
|
+
for (const file of state.scheduledFiles) {
|
|
595
|
+
scheduledSuites.push(getTestSuitePath(file, process.cwd()))
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const isFaulty = getIsFaultyEarlyFlakeDetection(
|
|
599
|
+
scheduledSuites,
|
|
600
|
+
configuration.knownTests?.mocha || {},
|
|
601
|
+
configuration.earlyFlakeDetectionFaultyThreshold
|
|
602
|
+
)
|
|
603
|
+
if (isFaulty) {
|
|
604
|
+
configuration.isEarlyFlakeDetectionEnabled = false
|
|
605
|
+
configuration.isEarlyFlakeDetectionFaulty = true
|
|
606
|
+
configuration.isKnownTestsEnabled = false
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Handles suite results reported by a Mocha worker.
|
|
612
|
+
*
|
|
613
|
+
* @param {WorkerRecord} workerRecord
|
|
614
|
+
* @param {object} message
|
|
615
|
+
* @returns {void}
|
|
616
|
+
*/
|
|
617
|
+
function handleSuiteResults (workerRecord, message) {
|
|
618
|
+
const { results = [] } = message.content || {}
|
|
619
|
+
for (const { file, status } of results) {
|
|
620
|
+
const suiteContext = workerRecord.suiteContexts.get(normalizeFile(file))
|
|
621
|
+
if (suiteContext) {
|
|
622
|
+
suiteContext.status = status
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Handles all messages emitted by one WebdriverIO child process.
|
|
629
|
+
*
|
|
630
|
+
* @param {CoordinatorState} state
|
|
631
|
+
* @param {WorkerRecord} workerRecord
|
|
632
|
+
* @param {object|unknown[]} message
|
|
633
|
+
* @returns {void}
|
|
634
|
+
*/
|
|
635
|
+
function handleWorkerMessage (state, workerRecord, message) {
|
|
636
|
+
if (message?.origin === WEBDRIVERIO_WORKER_ORIGIN && message.name === WEBDRIVERIO_WORKER_EVENT) {
|
|
637
|
+
message = message.args
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if (Array.isArray(message)) {
|
|
641
|
+
const [messageCode, payload] = message
|
|
642
|
+
if (messageCode === MOCHA_WORKER_TRACE_PAYLOAD_CODE) {
|
|
643
|
+
collectTestOptimizationSummariesFromTraces(payload, {
|
|
644
|
+
attemptToFixExecutions: state.attemptToFixExecutions,
|
|
645
|
+
newTestsWithDynamicNames: state.newTestsWithDynamicNames,
|
|
646
|
+
})
|
|
647
|
+
workerReportTraceCh.publish({
|
|
648
|
+
traces: payload,
|
|
649
|
+
[TEST_SUITE_EXECUTION_ID]: workerRecord.testSuiteExecutionId,
|
|
650
|
+
})
|
|
651
|
+
} else if (messageCode === MOCHA_WORKER_LOGS_PAYLOAD_CODE) {
|
|
652
|
+
workerReportLogsCh.publish(payload)
|
|
653
|
+
}
|
|
654
|
+
return
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if (!message || typeof message !== 'object') {
|
|
658
|
+
return
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
if (message.name === WORKER_READY) {
|
|
662
|
+
initializeCoordinator(state)
|
|
663
|
+
return
|
|
664
|
+
}
|
|
665
|
+
if (message.name === CONFIGURATION_REQUEST) {
|
|
666
|
+
handleConfigurationRequest(state, workerRecord, message)
|
|
667
|
+
return
|
|
668
|
+
}
|
|
669
|
+
if (message.name === SUITE_FINISH) {
|
|
670
|
+
handleSuiteResults(workerRecord, message)
|
|
671
|
+
return
|
|
672
|
+
}
|
|
673
|
+
if (message.name === 'testFrameworkInit') {
|
|
674
|
+
workerRecord.hasTests = message.content?.hasTests
|
|
675
|
+
if (!workerRecord.hasTests && state.frameworkVersion) {
|
|
676
|
+
initializeCoordinator(state, () => {
|
|
677
|
+
startWorkerSuites(workerRecord, workerRecord.specs)
|
|
678
|
+
finishAllWorkerSuites(state, workerRecord, 'skip')
|
|
679
|
+
})
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Handles child-process exit and closes suites missing an explicit result.
|
|
686
|
+
*
|
|
687
|
+
* @param {CoordinatorState} state
|
|
688
|
+
* @param {WorkerRecord} workerRecord
|
|
689
|
+
* @param {object} exit
|
|
690
|
+
* @returns {void}
|
|
691
|
+
*/
|
|
692
|
+
function handleWorkerExit (state, workerRecord, exit) {
|
|
693
|
+
state.activeWorkers--
|
|
694
|
+
workerRecord.exitCode = exit.exitCode
|
|
695
|
+
workerRecord.retries = exit.retries
|
|
696
|
+
if (!state.sessionStarted) {
|
|
697
|
+
return
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const status = workerRecord.hasTests === false ? 'skip' : exit.exitCode === 0 ? 'pass' : 'fail'
|
|
701
|
+
if (status === 'fail' && workerRecord.suiteContexts.size === 0) {
|
|
702
|
+
startWorkerSuites(workerRecord, workerRecord.specs)
|
|
703
|
+
}
|
|
704
|
+
finishAllWorkerSuites(state, workerRecord, status)
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Registers a newly created WebdriverIO worker with the coordinator.
|
|
709
|
+
*
|
|
710
|
+
* @param {CoordinatorState} state
|
|
711
|
+
* @param {object} worker
|
|
712
|
+
* @param {string[]} specs
|
|
713
|
+
* @returns {void}
|
|
714
|
+
*/
|
|
715
|
+
function registerWorker (state, worker, specs) {
|
|
716
|
+
const normalizedSpecs = []
|
|
717
|
+
for (const spec of specs) {
|
|
718
|
+
normalizedSpecs.push(normalizeFile(spec))
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
const workerRecord = {
|
|
722
|
+
worker,
|
|
723
|
+
specs: normalizedSpecs,
|
|
724
|
+
suiteContexts: new Map(),
|
|
725
|
+
testSuiteExecutionId: String(++state.nextWorkerId),
|
|
726
|
+
hasTests: undefined,
|
|
727
|
+
exitCode: undefined,
|
|
728
|
+
retries: undefined,
|
|
729
|
+
}
|
|
730
|
+
state.activeWorkers++
|
|
731
|
+
if (state.activeWorkers > state.maxActiveWorkers) {
|
|
732
|
+
state.maxActiveWorkers = state.activeWorkers
|
|
733
|
+
}
|
|
734
|
+
state.workers.add(workerRecord)
|
|
735
|
+
|
|
736
|
+
worker.on('message', message => handleWorkerMessage(state, workerRecord, message))
|
|
737
|
+
worker.once('exit', exit => handleWorkerExit(state, workerRecord, exit))
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Calculates the final status for the coordinated session.
|
|
742
|
+
*
|
|
743
|
+
* @param {CoordinatorState} state
|
|
744
|
+
* @returns {string}
|
|
745
|
+
*/
|
|
746
|
+
function getSessionStatus (state) {
|
|
747
|
+
let hasPassingSuite = false
|
|
748
|
+
|
|
749
|
+
for (const workerRecord of state.workers) {
|
|
750
|
+
if (workerRecord.exitCode !== undefined && workerRecord.exitCode !== 0) {
|
|
751
|
+
if (workerRecord.retries > 0) {
|
|
752
|
+
continue
|
|
753
|
+
}
|
|
754
|
+
return 'fail'
|
|
755
|
+
}
|
|
756
|
+
for (const suiteContext of workerRecord.suiteContexts.values()) {
|
|
757
|
+
const status = state.suiteStatuses.get(suiteContext)
|
|
758
|
+
if (status === 'fail') {
|
|
759
|
+
return 'fail'
|
|
760
|
+
}
|
|
761
|
+
if (status === 'pass') {
|
|
762
|
+
hasPassingSuite = true
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
return hasPassingSuite ? 'pass' : 'skip'
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* Finishes the single WebdriverIO-owned Mocha session.
|
|
772
|
+
*
|
|
773
|
+
* @param {CoordinatorState} state
|
|
774
|
+
* @param {unknown} error
|
|
775
|
+
* @param {() => void} onDone
|
|
776
|
+
* @returns {void}
|
|
777
|
+
*/
|
|
778
|
+
function finishCoordinator (state, error, onDone) {
|
|
779
|
+
if (state.finished) {
|
|
780
|
+
onDone()
|
|
781
|
+
return
|
|
782
|
+
}
|
|
783
|
+
if (!state.sessionStarted) {
|
|
784
|
+
if (!error && getSessionStatus(state) !== 'fail') {
|
|
785
|
+
onDone()
|
|
786
|
+
return
|
|
787
|
+
}
|
|
788
|
+
initializeCoordinator(state, () => finishCoordinator(state, error, onDone))
|
|
789
|
+
return
|
|
790
|
+
}
|
|
791
|
+
state.finished = true
|
|
792
|
+
|
|
793
|
+
for (const workerRecord of state.workers) {
|
|
794
|
+
const status = workerRecord.hasTests === false
|
|
795
|
+
? 'skip'
|
|
796
|
+
: workerRecord.exitCode === 0 ? 'pass' : 'fail'
|
|
797
|
+
finishAllWorkerSuites(state, workerRecord, status)
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
if (!testSessionFinishCh.hasSubscribers) {
|
|
801
|
+
logTestOptimizationSummary({
|
|
802
|
+
attemptToFixExecutions: state.attemptToFixExecutions,
|
|
803
|
+
newTestsWithDynamicNames: state.newTestsWithDynamicNames,
|
|
804
|
+
})
|
|
805
|
+
onDone()
|
|
806
|
+
return
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
testSessionFinishCh.publish({
|
|
810
|
+
status: error ? 'fail' : getSessionStatus(state),
|
|
811
|
+
error,
|
|
812
|
+
isEarlyFlakeDetectionEnabled: state.configuration.isEarlyFlakeDetectionEnabled,
|
|
813
|
+
isEarlyFlakeDetectionFaulty: state.configuration.isEarlyFlakeDetectionFaulty,
|
|
814
|
+
isParallel: state.maxActiveWorkers > 1,
|
|
815
|
+
isSuitesSkipped: false,
|
|
816
|
+
isTestManagementEnabled: state.configuration.isTestManagementTestsEnabled,
|
|
817
|
+
onDone,
|
|
818
|
+
})
|
|
819
|
+
logTestOptimizationSummary({
|
|
820
|
+
attemptToFixExecutions: state.attemptToFixExecutions,
|
|
821
|
+
newTestsWithDynamicNames: state.newTestsWithDynamicNames,
|
|
822
|
+
})
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// dc-polyfill supports partial tracing-channel subscribers, unlike the Node.js type definition.
|
|
826
|
+
// @ts-expect-error
|
|
827
|
+
launcherStartInstanceCh.subscribe({
|
|
828
|
+
start (context) {
|
|
829
|
+
const localRunner = context.self?.runner
|
|
830
|
+
const runnerConfiguration = localRunner && getRunnerConfiguration(localRunner)
|
|
831
|
+
if (!testFinishCh.hasSubscribers || runnerConfiguration?.framework !== 'mocha') {
|
|
832
|
+
return
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
const state = getCoordinatorState(localRunner)
|
|
836
|
+
addScheduledFiles(state, context.arguments?.[0] || [])
|
|
837
|
+
for (const schedule of context.self._schedule || []) {
|
|
838
|
+
for (const { files } of schedule.specs || []) {
|
|
839
|
+
addScheduledFiles(state, files)
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
},
|
|
843
|
+
})
|
|
844
|
+
|
|
845
|
+
// dc-polyfill supports partial tracing-channel subscribers, unlike the Node.js type definition.
|
|
846
|
+
// @ts-expect-error
|
|
847
|
+
localRunnerRunCh.subscribe({
|
|
848
|
+
start (context) {
|
|
849
|
+
const runnerConfiguration = getRunnerConfiguration(context.self)
|
|
850
|
+
if (!testFinishCh.hasSubscribers || runnerConfiguration?.framework !== 'mocha') {
|
|
851
|
+
return
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
const state = getCoordinatorState(context.self)
|
|
855
|
+
const workerOptions = context.arguments?.[0]
|
|
856
|
+
addScheduledFiles(state, workerOptions?.specs || [])
|
|
857
|
+
let workerEnvironment = runnerConfiguration.runnerEnv || {}
|
|
858
|
+
const launcherNodeOptions = getEnvironmentVariable('NODE_OPTIONS')
|
|
859
|
+
const workerNodeOptions = workerEnvironment.NODE_OPTIONS
|
|
860
|
+
|
|
861
|
+
if (launcherNodeOptions && !includesNodeOptions(workerNodeOptions, launcherNodeOptions)) {
|
|
862
|
+
workerEnvironment = {
|
|
863
|
+
...workerEnvironment,
|
|
864
|
+
NODE_OPTIONS: workerNodeOptions
|
|
865
|
+
? `${launcherNodeOptions} ${workerNodeOptions}`
|
|
866
|
+
: launcherNodeOptions,
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
runnerConfiguration.runnerEnv = {
|
|
871
|
+
...workerEnvironment,
|
|
872
|
+
MOCHA_WORKER_ID: 'webdriverio',
|
|
873
|
+
[WEBDRIVERIO_WORKER_ENV]: 'true',
|
|
874
|
+
}
|
|
875
|
+
context.ddCoordinatorState = state
|
|
876
|
+
context.ddWorkerSpecs = workerOptions?.specs || []
|
|
877
|
+
},
|
|
878
|
+
asyncEnd (context) {
|
|
879
|
+
const state = context.ddCoordinatorState
|
|
880
|
+
if (!state) {
|
|
881
|
+
return
|
|
882
|
+
}
|
|
883
|
+
if (context.error) {
|
|
884
|
+
state.runError ??= context.error
|
|
885
|
+
return
|
|
886
|
+
}
|
|
887
|
+
if (!context.result) {
|
|
888
|
+
return
|
|
889
|
+
}
|
|
890
|
+
registerWorker(state, context.result, context.ddWorkerSpecs)
|
|
891
|
+
},
|
|
892
|
+
})
|
|
893
|
+
|
|
894
|
+
// @ts-expect-error See the partial tracing-channel subscriber above.
|
|
895
|
+
localRunnerShutdownCh.subscribe({
|
|
896
|
+
asyncEnd (context) {
|
|
897
|
+
const state = coordinatorStates.get(context.self)
|
|
898
|
+
if (!state) {
|
|
899
|
+
return
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// Orchestrion uses the callback for the matching settlement path to delay LocalRunner.shutdown.
|
|
903
|
+
const waitForCoordinator = onDone => {
|
|
904
|
+
const error = context.error ?? state.runError
|
|
905
|
+
if (state.initializing) {
|
|
906
|
+
state.initializationCallbacks.push(() => finishCoordinator(state, error, onDone))
|
|
907
|
+
} else {
|
|
908
|
+
finishCoordinator(state, error, onDone)
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
context.resolveCallback = waitForCoordinator
|
|
912
|
+
context.rejectCallback = waitForCoordinator
|
|
913
|
+
},
|
|
914
|
+
})
|