ST-0014: Image attachments in Swift Testing (Apple platforms)

Hello Swift community,

The review of ST-0014 "Image attachments in Swift Testing (Apple platforms)" begins now and runs through Wednesday August 20, 2025. The proposal is available here:

https://github.com/swiftlang/swift-evolution/blob/main/proposals/testing/0014-image-attachments-in-swift-testing-apple-platforms.md

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to the review manager. When emailing the review manager directly, please keep the proposal link at the top of the message.

Trying it out

To try this feature out, add a dependency to the main branch of swift-testing to your project:

...
dependencies: [
  ...
  .package(
    url: "https://github.com/swiftlang/swift-testing.git",
    branch: "main"
  ),
]

Then, add two target dependencies to your test target:

.testTarget(
  ...
  dependencies: [
    ...
    .product(name: "Testing", package: "swift-testing"),
    .product(name: "_Testing_ExperimentalImageAttachments", package: "swift-testing"),
  ]

Add the following imports to any source file that will attach an image to a test:

import X
@_spi(Experimental) import _Testing_X

Where X is the module name of the image type you're using:

Image Type Module Name
CGImage CoreGraphics
CIImage CoreImage
NSImage AppKit
UIImage UIKit

Note: This extra product dependency and extra import statement are a side effect of how packages are built. They will not be required in the final implementation of this feature.

What goes into a review?

The goal of the review process is to improve the proposal under review
through constructive criticism and, eventually, determine the direction of
Swift. When writing your review, here are some questions you might want to
answer in your review:

  • What is your evaluation of the proposal?
  • Is the problem being addressed significant enough to warrant a change to Swift?
  • Does this proposal fit well with the feel and direction of Swift?
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available at

swift-evolution/process.md at main · swiftlang/swift-evolution · GitHub

Thank you for contributing to Swift!

Maarten Engels

Review Manager

9 Likes

In general, I am in favor of adding such a feature since it nicely builds on-top of the previously added Attachment support. In a previous life, I have extensively written snapshot tests for iOS applications and was desperate for XCTest back then to support adding image attachments for our CI workflows.

While reviewing the proposal the only thing that really stuck out to me was the following:

_AttachableImageWrapper is an implementation detail required by Swift's generic type system and is not itself part of this proposal. For completeness, its public interface is:

@available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)
public struct _AttachableImageWrapper<Image>: Sendable, AttachableWrapper where Image: AttachableAsCGImage {
  public var wrappedValue: Image { get }
}

I'm not sure I follow why this type needs to be underscored. From our previous design of Attachment and Attachable it was clear that we need either existing concrete types to conform to it or create our own new types. So two questions really:

  1. Why do we think we should hide this concrete type?
  2. Why isn't AttachableImageFormat a property of this type? In the alternatives considered section, it says that this type helps us to store additional information which I assumed was around the image format.
1 Like

A fair question. This type is necessary due to constraints on the Swift type system. Me-from-about-a-year-ago presciently wrote this screed in the Swift file where the type is implemented:

Attachable has a requirement with Self in non-parameter, non-return position. As far as Swift is concerned, a non-final class cannot satisfy such a requirement, and all image types we care about are non-final classes. Thus, the compiler will steadfastly refuse to allow non-final classes to conform to the Attachable protocol. We could get around this by changing the signature of withUnsafeBytes() so that the generic parameter to Attachment is not Self, but that would defeat much of the purpose of making Attachment generic in the first place. (And no, the language does not let us write where T: Self anywhere useful.)

In short, we need a helper type to implement Attachable on behalf of UIImage et al., but that helper type is never going to be directly used by a developer. You don't instantiate it directly, nor do you access it or its properties directly. Instead, you deal with the image itself or with an instance of Attachment. Consider the following test code:

@Test func `lorem ipsum`() {
  let image = /* ... */
  let attachment = Attachment(image, named: "example.tiff")
  // ...
  Attachment.record(attachment)
}

At no point does _AttachableImageWrapper appear in source. But the type does need to be public because the public Swift interface needs to refer to it in order for the type checker to find the correct overloads of Attachment.init() and Attachment.record() at compile time.

If, in the future, we find that it is useful to fully expose this type publicly, we can rename it to a non-underscored name.

As the type is only public for technical reasons, it has no interface beyond the requirements of its conformance to AttachableWrapper. However, we can certainly add a imageFormat property to Attachment, which is what a developer might need to poke/prod:

extension Attachment where AttachableValue: AttachableWrapper, AttachableValue.Wrapped: AttachableAsCGImage {
  /// The image format to use when encoding the represented image.
  public var imageFormat: AttachableImageFormat? { get }
}

I've gone ahead and implemented this property on our main branch as experimental SPI. If the community and the testing workgroup likes the addition, we can promote it to API in a future mini-proposal.

2 Likes