diff --git a/Sources/XCTestHTMLReportCore/Classes/Models/Summary.swift b/Sources/XCTestHTMLReportCore/Classes/Models/Summary.swift index 58831d5f..199a3efc 100644 --- a/Sources/XCTestHTMLReportCore/Classes/Models/Summary.swift +++ b/Sources/XCTestHTMLReportCore/Classes/Models/Summary.swift @@ -107,7 +107,9 @@ public struct Summary { /// Call-site checks catch failures XCResultKit surfaces as `nil`. They do /// not catch failures in *nested* decoding, where a parent object still /// decodes but a child field comes back empty. The observable symptom is an - /// attachment that resolved to no content, so check for that directly. + /// attachment whose referenced payload resolved to no content, so check + /// for that directly. Attachments without a payload reference legitimately + /// have no content and require no resolution. /// /// Idempotent across sequential calls: repeated calls do not duplicate /// faults. Dedup keys on `Attachment.faultDescription`, which assumes @@ -123,7 +125,7 @@ public struct Summary { ) for attachment in allAttachments { - guard case .none = attachment.content else { + guard attachment.payloadId != nil, case .none = attachment.content else { continue } let detail = attachment.faultDescription diff --git a/Tests/XCTestHTMLReportTests/CoreTests.swift b/Tests/XCTestHTMLReportTests/CoreTests.swift index 482f2890..f05caeb0 100644 --- a/Tests/XCTestHTMLReportTests/CoreTests.swift +++ b/Tests/XCTestHTMLReportTests/CoreTests.swift @@ -78,11 +78,11 @@ final class CoreTests: XCTestCase { // because a run still depends on the simulator behaving, and an // exact split would measure that rather than this project. // - // 17 = 14 XCTest methods + 3 Swift Testing `@Test` functions in - // SwiftTestingSuite (SampleAppUnitTests target). The 14th is - // FirstSuite.testAttachScreenshot, added by #393 to give the image - // rendering path a fixture. - XCTAssertEqual(all, 17, "One row per test method; fixed by the sample sources") + // 18 = 15 XCTest methods + 3 Swift Testing `@Test` functions in + // SwiftTestingSuite (SampleAppUnitTests target). The latest is + // FirstSuite.testAttachDeletedOnSuccess, whose passing attachment + // retains metadata but has no payload. + XCTAssertEqual(all, 18, "One row per test method; fixed by the sample sources") XCTAssertEqual( skipped, 1, diff --git a/Tests/XCTestHTMLReportTests/FaultReportingTests.swift b/Tests/XCTestHTMLReportTests/FaultReportingTests.swift index b7c6722b..8e918289 100644 --- a/Tests/XCTestHTMLReportTests/FaultReportingTests.swift +++ b/Tests/XCTestHTMLReportTests/FaultReportingTests.swift @@ -36,7 +36,7 @@ final class FaultReportingTests: XCTestCase { XCTAssertEqual(summary.faults, [], "Clean fixture must not produce faults") } - func testValidateFlagsUnresolvedAttachments() throws { + func testValidateIgnoresPayloadlessAttachments() throws { let url = try XCTUnwrap( Bundle.testBundle.url(forResource: "TestResults", withExtension: "xcresult") ) @@ -49,14 +49,27 @@ final class FaultReportingTests: XCTestCase { downsizeScaleFactor: 0.25, faultCollector: collector ) + + let payloadlessAttachments = summary.allAttachments.filter { attachment in + guard attachment.payloadId == nil else { + return false + } + if case .none = attachment.content { + return true + } + return false + } + XCTAssertFalse( + payloadlessAttachments.isEmpty, + "Fixture must contain an attachment whose payload was deleted after a passing test" + ) + summary.validate() - // Every attachment the model knows about must have resolved to real - // content. An unresolved one renders as an empty src. let unresolved = summary.faults.filter { $0.kind == .unresolvedAttachment } XCTAssertEqual( unresolved, [], - "Unresolved attachments: \(unresolved.map(\.detail))" + "Payload-less attachments must not be reported as unresolved: \(unresolved.map(\.detail))" ) } diff --git a/XCTestHTMLReportSampleApp/SampleAppUITests/FirstSuite.swift b/XCTestHTMLReportSampleApp/SampleAppUITests/FirstSuite.swift index a472dc6b..35aabd62 100644 --- a/XCTestHTMLReportSampleApp/SampleAppUITests/FirstSuite.swift +++ b/XCTestHTMLReportSampleApp/SampleAppUITests/FirstSuite.swift @@ -65,6 +65,13 @@ class FirstSuite: XCTestCase { add(screenshot) } + func testAttachDeletedOnSuccess() { + let attachment = XCTAttachment(string: "This payload is removed after the test passes") + attachment.name = "Deleted on Success" + attachment.lifetime = .deleteOnSuccess + add(attachment) + } + func testOne() { XCTContext.runActivity(named: "Text Attachment") { activity in let logs = """