From 3f99ebcba1d64434802a1e6334f8f99b21dbdf5f Mon Sep 17 00:00:00 2001 From: "Developer @ Loverde Company" Date: Fri, 11 Sep 2026 10:44:02 -0300 Subject: [PATCH] test(uitests): write failure diagnostics to a file, not print() Confirmed (pasted full CI log around a timeout, searched for the print()'d marker string, found nothing) that plain print() from inside a UI test never reaches fastlane's xcodebuild log output on this runner - its formatter only relays lines matching its own known patterns and drops everything else. CartCheckoutFlowTests now writes app.debugDescription to NSTemporaryDirectory()+'pedifoods_uitest_diag.log' on each of its three failure points (shared dumpDiagnostics helper, appending), clearing any stale copy in class setUp(). test.yml gained a step after 'Run tests with coverage' (if: always()) that cats that file when present - TMPDIR is stable for the whole CI job, unlike the per-run-hashed workspace path that broke the original screenshot attempt. See decisions/2026-09-11-ui-test-shared-login-session.md follow-up. --- .gitea/workflows/test.yml | 9 ++++ PediFoodsUITests/CartCheckoutFlowTests.swift | 47 ++++++++++++++------ 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index f0e04d0..2dea663 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -24,3 +24,12 @@ jobs: - name: Run tests with coverage run: fastlane tests + + - name: Show UI test failure diagnostics (if any) + if: always() + run: | + if [ -f "$TMPDIR/pedifoods_uitest_diag.log" ]; then + cat "$TMPDIR/pedifoods_uitest_diag.log" + else + echo "No UI test diagnostic log was written." + fi diff --git a/PediFoodsUITests/CartCheckoutFlowTests.swift b/PediFoodsUITests/CartCheckoutFlowTests.swift index a8a9497..5e22364 100644 --- a/PediFoodsUITests/CartCheckoutFlowTests.swift +++ b/PediFoodsUITests/CartCheckoutFlowTests.swift @@ -6,6 +6,33 @@ import XCTest /// so Home shows a real store list - see /// decisions/2026-08-06-ui-test-account-and-app-attest-bypass.md. final class CartCheckoutFlowTests: XCTestCase { + /// Plain `print()` from inside a UI test does not reach the CI log on + /// this runner - fastlane's xcodebuild log formatter only relays + /// lines matching its own known patterns (Test Suite/Test Case/etc.) + /// and silently drops anything else, confirmed by pasting the full + /// log around a timeout and finding no trace of a print()'d marker + /// string. Writing to a file `cat`'d by a later CI step + /// (`.gitea/workflows/test.yml`) works regardless. `NSTemporaryDirectory()` + /// resolves to the CI job's `$TMPDIR`, consistent for the whole job + /// (both the xcodebuild-spawned test process and the later shell step + /// run under the same user session) - unlike the fixed scratch path + /// this replaced, which was hardcoded to a different machine/session + /// and silently no-op'd here. + static let diagnosticLogPath = NSTemporaryDirectory() + "pedifoods_uitest_diag.log" + + private static func dumpDiagnostics(_ title: String, app: XCUIApplication) { + let entry = "\n=== \(title) ===\n\(app.debugDescription)\n" + guard let data = entry.data(using: .utf8) else { return } + if FileManager.default.fileExists(atPath: diagnosticLogPath), + let handle = FileHandle(forWritingAtPath: diagnosticLogPath) { + handle.seekToEndOfFile() + handle.write(data) + handle.closeFile() + } else { + FileManager.default.createFile(atPath: diagnosticLogPath, contents: data) + } + } + /// The cart is on-device state (`CartState`/`SessionStateStore`, see /// `AppState.swift`), not server-side - it persists across app /// relaunches on this simulator just like the login session (see @@ -22,6 +49,10 @@ final class CartCheckoutFlowTests: XCTestCase { /// a known-empty cart instead of an ever-growing one. override class func setUp() { super.setUp() + // Start each run with a clean diagnostic log, not a leftover from + // a previous one. + try? FileManager.default.removeItem(atPath: diagnosticLogPath) + let app = XCUIApplication() app.launch() app.ensureLoggedIn() @@ -106,8 +137,7 @@ final class CartCheckoutFlowTests: XCTestCase { app.swipeDown() } if app.staticTexts["Finalizar Pedido"].waitForExistence(timeout: 10) == false { - print("=== Never returned to Checkout after Alterar dismiss - accessibility hierarchy ===") - print(app.debugDescription) + Self.dumpDiagnostics("Never returned to Checkout after Alterar dismiss", app: app) } XCTAssertTrue(app.staticTexts["Finalizar Pedido"].exists, "Never returned to Checkout") @@ -180,8 +210,7 @@ final class CartCheckoutFlowTests: XCTestCase { // a plain static text. let storeCard = app.buttons.matching(NSPredicate(format: "label CONTAINS[c] %@", "MARIBA")).firstMatch if storeCard.waitForExistence(timeout: 15) == false { - print("=== No MARIBA store card on Home - accessibility hierarchy ===") - print(app.debugDescription) + Self.dumpDiagnostics("No MARIBA store card on Home", app: app) } XCTAssertTrue(storeCard.exists, "Expected store card never appeared on Home") storeCard.tap() @@ -201,15 +230,7 @@ final class CartCheckoutFlowTests: XCTestCase { // directly instead - same fix pattern as the cart-tab icon. let addButton = app.images.matching(identifier: "plus").firstMatch if addButton.waitForExistence(timeout: 25) == false { - // A hardcoded scratch-path screenshot write here was silently - // no-oping on the CI runner (try? swallowed a write failure - // into a directory that doesn't exist on this machine's user - // account) - printing the accessibility hierarchy instead - // shows up directly in the CI log with no extra file access - // needed, same technique already used in - // decisions/2026-08-06-ui-test-account-and-app-attest-bypass.md. - print("=== Store Detail timeout - accessibility hierarchy ===") - print(app.debugDescription) + Self.dumpDiagnostics("Store Detail timeout - product list never loaded", app: app) } XCTAssertTrue(addButton.exists, "Store Detail's product list never loaded") addButton.tap()