Skip to content

Conversation

grynspan
Copy link
Contributor

@grynspan grynspan commented Sep 16, 2025

This PR introduces a new experimental trait, .savingAttachments(if:), that can be used to control whether a test's attachments are saved or not.

XCTest has API around the XCTAttachment.Lifetime enumeration that developers can use to control whether attachments are saved to a test report in Xcode. This enumeration has two cases:

/*
 * Attachment will be kept regardless of the outcome of the test.
 */
XCTAttachmentLifetimeKeepAlways = 0,

/*
 * Attachment will only be kept when the test fails, and deleted otherwise.
 */
XCTAttachmentLifetimeDeleteOnSuccess = 1

I've opted to implement something a bit more granular. A developer can specify .savingAttachments(if: .testFails) and .savingAttachments(if: .testPasses) or can call some custom function of their own design like runningInCI or hasPlentyOfFloppyDiskSpace. The default behaviour if this trait is not used is to always save attachments, which is equivalent to XCTAttachmentLifetimeKeepAlways. XCTAttachmentLifetimeDeleteOnSuccess is, in effect, equivalent to .savingAttachments(if: .testFails), but I hope reads a bit more clearly in context.

Here's a usage example:

@Test(.savingAttachments(if: .testFails))
func `best test ever`() {
  Attachment.record("...") // only saves to the test report or to disk if the
                           // next line is uncommented.
  // Issue.record("sadness")
}

I've taken the opportunity to update existing documentation for Attachment and Attachable to try to use more consistent language: a test records an attachment and then the testing library saves it (somewhere). I'm sure I've missed some spots, so please point them out if you see them.

Resolves rdar://138921461.

Checklist:

  • Code and documentation should follow the style of the Style Guide.
  • If public symbols are renamed or modified, DocC references should be updated.

This PR introduces a new experimental trait, `.savingAttachments(if:)`, that can
be used to control whether a test's attachments are saved or not.

XCTest has API around the [`XCTAttachment.Lifetime`](https://developer.apple.com/documentation/xctest/xctattachment/lifetime-swift.enum)
enumeration that developers can use to control whether attachments are saved to
a test report in Xcode. This enumeration has two cases:

```objc
/*
 * Attachment will be kept regardless of the outcome of the test.
 */
XCTAttachmentLifetimeKeepAlways = 0,

/*
 * Attachment will only be kept when the test fails, and deleted otherwise.
 */
XCTAttachmentLifetimeDeleteOnSuccess = 1
```

I've opted to implement something a bit more granular. A developer can specify
`.savingAttachments(if: .testFails)` and `.savingAttachments(if: .testPasses)`
or can call some custom function of their own design like `runningInCI` or
`hasPlentyOfFloppyDiskSpace`. The default behaviour if this trait is not used is
to always save attachments, which is equivalent to
`XCTAttachmentLifetimeKeepAlways`. `XCTAttachmentLifetimeDeleteOnSuccess` is, in
effect, equivalent to `.savingAttachments(if: .testFails)`, but I hope reads a
bit more clearly in context.

Here's a usage example:

```swift
@test(.savingAttachments(if: .testFails))
func `best test ever`() {
  Attachment.record("...") // only saves to the test report or to disk if the
                           // next line is uncommented.
  // Issue.record("sadness")
}
```

I've taken the opportunity to update existing documentation for `Attachment` and
`Attachable` to try to use more consistent language: a test records an
attachment and then the testing library saves it (somewhere). I'm sure I've
missed some spots, so please point them out if you see them.

Resolves rdar://138921461.
@grynspan grynspan added this to the Swift 6.x (main) milestone Sep 16, 2025
@grynspan grynspan self-assigned this Sep 16, 2025
@grynspan grynspan added the enhancement New feature or request label Sep 16, 2025
@grynspan grynspan added public-api Affects public API issue-handling Related to Issue handling within the testing library attachments/activities 🖇️ Work related to attachments and/or activities traits Issues and PRs related to the trait subsystem or built-in traits labels Sep 16, 2025
@@ -47,9 +47,19 @@ extension Runner {
return
}

configuration.eventHandler = { [eventHandler = configuration.eventHandler] event, context in
configuration.eventHandler = { [oldEventHandler = configuration.eventHandler] event, context in
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code isn't new, but needed to move from handleEvent as it imposed the wrong layering (saving the attachment before any event handlers actually ran.)

Comment on lines +47 to +49
public static var testFails: Self {
Self { $0.hasFailed }
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should include a built-in condition representing a test which did not fail but recorded one or more issues with warning severity. If a test recorded a warning issue, preserving the attachments might be useful to help diagnose the problem.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, gimme a symbol name and I can add it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
attachments/activities 🖇️ Work related to attachments and/or activities enhancement New feature or request issue-handling Related to Issue handling within the testing library public-api Affects public API traits Issues and PRs related to the trait subsystem or built-in traits
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants