I’ve been diving deeply into Swift Testing recently, (original proposal) and wanted to pitch an enhancement for how we manage and apply traits at scale. Thanks to @jerryjrchen for the early feedback that encouraged me to bring this discussion to the forums!
The Problem
Currently, Swift Testing provides an excellent, type-safe system for applying traits (like .timeLimit, .tags, etc.) individually to a @Test or at the @Suite level.
However, this approach scales poorly when managing large codebases or enforcing CI/CD platform policies. If a platform team needs to impose a global 90-second timeout on all tests to prevent stalled jobs, they currently have to modify hundreds of files. If that policy changes, it requires another massive refactor. The lack of a global injection mechanism leads to fragile infrastructure and an over-reliance on ad-hoc command-line flags rather than a "configuration-first" strategy.
Proposed Solution
I propose introducing Globally Scoped Traits, extending Runner.Configuration to allow developers and CI environments to inject traits universally at runtime.
By intercepting the test evaluation at _constructStepGraph (or a similar step graph union point), we can automatically append a set of global traits into the test.traits array before execution.
To make this safe and predictable, this system would rely on a two-tier precedence model:
Fallback (Default): The global trait applies only if the test has no local equivalent. (e.g., a standard 2-minute timeout that doesn't override a specific test's intentional 5-minute timeout).
Enforced: The global trait strictly overrides local traits. (e.g., a hard CI cap that vetoes any local configurations).
Key Benefits & Use Cases
Pipeline Safety: Enforce global timeouts, retries, or parallelization bandwidths to save compute resources and ensure predictable runway outputs.
Telemetry & Routing: Seamlessly inject global environment tags (e.g., for Datadog, Prometheus) without needing to pollute the source code of the tests themselves.
Configuration-First Architecture: Move away from scattered flags toward a strictly typed, macro-compatible (@Test) global trait configuration.
Implementation
To achieve this, the core schema would introduce a GlobalTraits macro, implementing a "Standard Injection" logic to merge with the existing trait storage patterns.
I would love to get the workgroup's feedback on a few specific areas to help shape this:
Is modifying Runner.Configuration the preferred API surface for this, or should we consider an @_spi(ForToolsIntegrationOnly) helper?
What are the ABI stability implications we need to watch out for when modifying the trait storage schema to accommodate globally injected values?
Does the Fallback vs. Enforced precedence model make sense for the existing trait resolution logic?
I am committed to putting in the hours to implement this and would greatly appreciate any thoughts, feedback, or direction from the community!
I worked on a similar direction for GSoC this year and built a working prototype around it as well.
My approach was a bit different, though. Instead of directly injecting traits first, I tried connecting the idea through JSON configuration and CLI-driven runtime configuration.
Still, a lot of the questions around configuration flow, precedence, and SwiftPM integration felt very familiar while reading your post.
I included my proposal and prototype links in the thread I posted. If you get a chance, I’d appreciate it if you took a look. I’d also be happy to talk about some of the things I ran into while implementing it.
We actually already have an experimental implementation of global traits here, although it is not ready to merge and needs formal review before we could ship it.
One of the main blockers for this functionality is that it's unclear how it would integrate with or work alongside tool-specific features like Xcode's test plans or SwiftPM's command-line options. We don't have a good answer, and the open source Swift project cannot compel tools authors to adopt necessary API for this sort of thing.
Thanks for pointing me to that PR! It’s great to see the underlying architecture for global traits is already taking shape.
Instead of waiting for tools to adopt a new API, what if we designed the global configuration to consume the lowest common denominators that all tools already support: Environment Variables and JSON configuration files.
Building on @Demian's approach for JSON/CLI-driven configuration, we can combine these into a unified runtime layer. If we map the experimental global traits to a standard JSON schema or a set of environment variables (e.g., SWIFT_TESTING_GLOBAL_TIMEOUT, SWIFT_TESTING_GLOBAL_TAGS), we immediately solve the integration problem with zero required buy-in from tool authors:
Xcode & CI/CD: Can natively inject environment variables or pass file paths via existing UI and runner scripts.
SwiftPM: Users can pass them natively via the CLI using swift test --Xctest-strict-env.
Once this configuration layer is parsed, global traits would work exactly the same way regardless of whether they were sourced from JSON or the environment.
The last piece of the puzzle is precedence: how do we merge these global traits with the current system? I think the two-tier approach I mentioned earlier neatly solves this:
Fallback: Global traits only apply if the test lacks a local equivalent.
Enforced: Global traits strictly override local configurations.
By combining the JSON/CLI configuration approach with this precedence logic, we effectively unblock the global traits feature. If I were to focus my efforts on building out this configuration parser and precedence logic to help land PR #1534, would that be a solid direction to pursue?
(I'm going to tag @Demian here as well since you both worked on similar proposals)
Jonathan's message already alluded to this, but I'd like to unequivocally state (although I am by no means the ultimate authority on anything) that I think it's not the right time to land globally scoped traits.
Despite the interest and apparent need for a feature, a pitch sometimes cannot proceed because there are major unanswered questions about integration with the current ecosystem of tools. I think that will be the case here with globally scoped traits; it's something that only an extended period of community discussion and iteration will be able to gradually untangle.
That being said, I'd encourage you to continue exploring the Swift Testing project and look for opportunities to contribute original pitches of your own. You can start more informally with shorter forum posts to feel out interest rather than researching and implementing everything up front.
I do understand that the current approach is kind of unfeasible and doesn't cover all sectors but how about we do experimental builds to try this out and then break the eventual release in form of miniature builds with tiny incremental changes to the system. we keep on building on a foundation and start with the easiest. Rather than adopting the system as second nature , we can build it out as a beta only thing and we can ask for feedback later down the line.
It's a really big project as opposed to the documented "80 hours". But I am sure if we do parallel integration covering the same that is proposed.
But if you still think this is something that will take time we completely understand.
Caveat - I’m not familiar with the detail of how test traits are compiled.
However, something that is increasingly proving to be a point of friction on a large project I’m involved with is the inability to have a target that can import test frameworks and be a dependency of another target.
If we were to have a sharedTestTarget (naming indicative rather than a proposal) then we wouldn’t need global test traits because we could have a test traits package that is imported into our test targets. This would allow for other test helper methods to be shared rather than repeated in each test target.
Therefore, if this is possible, it would seem to be more flexible and perhaps avoids some of the other pitfalls in the approaches discussed above. But it’s quite possible I’m missing something important :)
External contributors to the project would generally be expected to prototype their work on a branch, then begin the Swift Evolution process with a pitch thread, draft proposal, etc. While the code owners do often land experimental features on the main branch, we mark them as experimental SPI where possible rather than exposing them as any sort of public beta.
Some sort of "test library" target type has come up before! That would be a SwiftPM feature and probably wouldn't involve significant changes to Swift Testing.
I don't think it's directly relevant to this thread, no. The concepts appear orthogonal to me. Please consider starting a new forum thread if you'd like to discuss this topic. Thanks!
Indeed, I am acquainted with the @spi(experimental) definition.
This is precisely what I propose. We can commence by modifying the runner and configuration files incrementally to align with the proposed architecture. Subsequently, we can develop the proposed macro. To validate the feasibility of this approach, we can initiate testing with the most fundamental traits, such as tags. This will provide a comprehensive assessment of whether this methodology is effective.
I appreciate your interest in contributing! To put what Jerry said a bit more bluntly: the Swift Testing code owners are not looking for help with the technical implementation of this feature right now. If there are other areas of Swift Testing you'd be interested in working on, please let us know.