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()